MDL-62065 core_access: First deprecation of get roles on exact context
[moodle.git] / lib / accesslib.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
17 /**
18  * This file contains functions for managing user access
19  *
20  * <b>Public API vs internals</b>
21  *
22  * General users probably only care about
23  *
24  * Context handling
25  * - context_course::instance($courseid), context_module::instance($cm->id), context_coursecat::instance($catid)
26  * - context::instance_by_id($contextid)
27  * - $context->get_parent_contexts();
28  * - $context->get_child_contexts();
29  *
30  * Whether the user can do something...
31  * - has_capability()
32  * - has_any_capability()
33  * - has_all_capabilities()
34  * - require_capability()
35  * - require_login() (from moodlelib)
36  * - is_enrolled()
37  * - is_viewing()
38  * - is_guest()
39  * - is_siteadmin()
40  * - isguestuser()
41  * - isloggedin()
42  *
43  * What courses has this user access to?
44  * - get_enrolled_users()
45  *
46  * What users can do X in this context?
47  * - get_enrolled_users() - at and bellow course context
48  * - get_users_by_capability() - above course context
49  *
50  * Modify roles
51  * - role_assign()
52  * - role_unassign()
53  * - role_unassign_all()
54  *
55  * Advanced - for internal use only
56  * - load_all_capabilities()
57  * - reload_all_capabilities()
58  * - has_capability_in_accessdata()
59  * - get_user_roles_sitewide_accessdata()
60  * - etc.
61  *
62  * <b>Name conventions</b>
63  *
64  * "ctx" means context
65  *
66  * <b>accessdata</b>
67  *
68  * Access control data is held in the "accessdata" array
69  * which - for the logged-in user, will be in $USER->access
70  *
71  * For other users can be generated and passed around (but may also be cached
72  * against userid in $ACCESSLIB_PRIVATE->accessdatabyuser).
73  *
74  * $accessdata is a multidimensional array, holding
75  * role assignments (RAs), role-capabilities-perm sets
76  * (role defs) and a list of courses we have loaded
77  * data for.
78  *
79  * Things are keyed on "contextpaths" (the path field of
80  * the context table) for fast walking up/down the tree.
81  * <code>
82  * $accessdata['ra'][$contextpath] = array($roleid=>$roleid)
83  *                  [$contextpath] = array($roleid=>$roleid)
84  *                  [$contextpath] = array($roleid=>$roleid)
85  * </code>
86  *
87  * <b>Stale accessdata</b>
88  *
89  * For the logged-in user, accessdata is long-lived.
90  *
91  * On each pageload we load $ACCESSLIB_PRIVATE->dirtycontexts which lists
92  * context paths affected by changes. Any check at-or-below
93  * a dirty context will trigger a transparent reload of accessdata.
94  *
95  * Changes at the system level will force the reload for everyone.
96  *
97  * <b>Default role caps</b>
98  * The default role assignment is not in the DB, so we
99  * add it manually to accessdata.
100  *
101  * This means that functions that work directly off the
102  * DB need to ensure that the default role caps
103  * are dealt with appropriately.
104  *
105  * @package    core_access
106  * @copyright  1999 onwards Martin Dougiamas  http://dougiamas.com
107  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
108  */
110 defined('MOODLE_INTERNAL') || die();
112 /** No capability change */
113 define('CAP_INHERIT', 0);
114 /** Allow permission, overrides CAP_PREVENT defined in parent contexts */
115 define('CAP_ALLOW', 1);
116 /** Prevent permission, overrides CAP_ALLOW defined in parent contexts */
117 define('CAP_PREVENT', -1);
118 /** Prohibit permission, overrides everything in current and child contexts */
119 define('CAP_PROHIBIT', -1000);
121 /** System context level - only one instance in every system */
122 define('CONTEXT_SYSTEM', 10);
123 /** User context level -  one instance for each user describing what others can do to user */
124 define('CONTEXT_USER', 30);
125 /** Course category context level - one instance for each category */
126 define('CONTEXT_COURSECAT', 40);
127 /** Course context level - one instances for each course */
128 define('CONTEXT_COURSE', 50);
129 /** Course module context level - one instance for each course module */
130 define('CONTEXT_MODULE', 70);
131 /**
132  * Block context level - one instance for each block, sticky blocks are tricky
133  * because ppl think they should be able to override them at lower contexts.
134  * Any other context level instance can be parent of block context.
135  */
136 define('CONTEXT_BLOCK', 80);
138 /** Capability allow management of trusts - NOT IMPLEMENTED YET - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
139 define('RISK_MANAGETRUST', 0x0001);
140 /** Capability allows changes in system configuration - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
141 define('RISK_CONFIG',      0x0002);
142 /** Capability allows user to add scripted content - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
143 define('RISK_XSS',         0x0004);
144 /** Capability allows access to personal user information - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
145 define('RISK_PERSONAL',    0x0008);
146 /** Capability allows users to add content others may see - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
147 define('RISK_SPAM',        0x0010);
148 /** capability allows mass delete of data belonging to other users - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
149 define('RISK_DATALOSS',    0x0020);
151 /** rolename displays - the name as defined in the role definition, localised if name empty */
152 define('ROLENAME_ORIGINAL', 0);
153 /** rolename displays - the name as defined by a role alias at the course level, falls back to ROLENAME_ORIGINAL if alias not present */
154 define('ROLENAME_ALIAS', 1);
155 /** rolename displays - Both, like this:  Role alias (Original) */
156 define('ROLENAME_BOTH', 2);
157 /** rolename displays - the name as defined in the role definition and the shortname in brackets */
158 define('ROLENAME_ORIGINALANDSHORT', 3);
159 /** rolename displays - the name as defined by a role alias, in raw form suitable for editing */
160 define('ROLENAME_ALIAS_RAW', 4);
161 /** rolename displays - the name is simply short role name */
162 define('ROLENAME_SHORT', 5);
164 if (!defined('CONTEXT_CACHE_MAX_SIZE')) {
165     /** maximum size of context cache - it is possible to tweak this config.php or in any script before inclusion of context.php */
166     define('CONTEXT_CACHE_MAX_SIZE', 2500);
169 /**
170  * Although this looks like a global variable, it isn't really.
171  *
172  * It is just a private implementation detail to accesslib that MUST NOT be used elsewhere.
173  * It is used to cache various bits of data between function calls for performance reasons.
174  * Sadly, a PHP global variable is the only way to implement this, without rewriting everything
175  * as methods of a class, instead of functions.
176  *
177  * @access private
178  * @global stdClass $ACCESSLIB_PRIVATE
179  * @name $ACCESSLIB_PRIVATE
180  */
181 global $ACCESSLIB_PRIVATE;
182 $ACCESSLIB_PRIVATE = new stdClass();
183 $ACCESSLIB_PRIVATE->cacheroledefs    = array(); // Holds site-wide role definitions.
184 $ACCESSLIB_PRIVATE->dirtycontexts    = null;    // Dirty contexts cache, loaded from DB once per page
185 $ACCESSLIB_PRIVATE->accessdatabyuser = array(); // Holds the cache of $accessdata structure for users (including $USER)
187 /**
188  * Clears accesslib's private caches. ONLY BE USED BY UNIT TESTS
189  *
190  * This method should ONLY BE USED BY UNIT TESTS. It clears all of
191  * accesslib's private caches. You need to do this before setting up test data,
192  * and also at the end of the tests.
193  *
194  * @access private
195  * @return void
196  */
197 function accesslib_clear_all_caches_for_unit_testing() {
198     global $USER;
199     if (!PHPUNIT_TEST) {
200         throw new coding_exception('You must not call clear_all_caches outside of unit tests.');
201     }
203     accesslib_clear_all_caches(true);
205     unset($USER->access);
208 /**
209  * Clears accesslib's private caches. ONLY BE USED FROM THIS LIBRARY FILE!
210  *
211  * This reset does not touch global $USER.
212  *
213  * @access private
214  * @param bool $resetcontexts
215  * @return void
216  */
217 function accesslib_clear_all_caches($resetcontexts) {
218     global $ACCESSLIB_PRIVATE;
220     $ACCESSLIB_PRIVATE->dirtycontexts    = null;
221     $ACCESSLIB_PRIVATE->accessdatabyuser = array();
222     $ACCESSLIB_PRIVATE->cacheroledefs    = array();
224     $cache = cache::make('core', 'roledefs');
225     $cache->purge();
227     if ($resetcontexts) {
228         context_helper::reset_caches();
229     }
232 /**
233  * Clears accesslib's private cache of a specific role or roles. ONLY BE USED FROM THIS LIBRARY FILE!
234  *
235  * This reset does not touch global $USER.
236  *
237  * @access private
238  * @param int|array $roles
239  * @return void
240  */
241 function accesslib_clear_role_cache($roles) {
242     global $ACCESSLIB_PRIVATE;
244     if (!is_array($roles)) {
245         $roles = [$roles];
246     }
248     foreach ($roles as $role) {
249         if (isset($ACCESSLIB_PRIVATE->cacheroledefs[$role])) {
250             unset($ACCESSLIB_PRIVATE->cacheroledefs[$role]);
251         }
252     }
254     $cache = cache::make('core', 'roledefs');
255     $cache->delete_many($roles);
258 /**
259  * Role is assigned at system context.
260  *
261  * @access private
262  * @param int $roleid
263  * @return array
264  */
265 function get_role_access($roleid) {
266     $accessdata = get_empty_accessdata();
267     $accessdata['ra']['/'.SYSCONTEXTID] = array((int)$roleid => (int)$roleid);
268     return $accessdata;
271 /**
272  * Fetch raw "site wide" role definitions.
273  * Even MUC static acceleration cache appears a bit slow for this.
274  * Important as can be hit hundreds of times per page.
275  *
276  * @param array $roleids List of role ids to fetch definitions for.
277  * @return array Complete definition for each requested role.
278  */
279 function get_role_definitions(array $roleids) {
280     global $ACCESSLIB_PRIVATE;
282     if (empty($roleids)) {
283         return array();
284     }
286     // Grab all keys we have not yet got in our static cache.
287     if ($uncached = array_diff($roleids, array_keys($ACCESSLIB_PRIVATE->cacheroledefs))) {
288         $cache = cache::make('core', 'roledefs');
289         foreach ($cache->get_many($uncached) as $roleid => $cachedroledef) {
290             if (is_array($cachedroledef)) {
291                 $ACCESSLIB_PRIVATE->cacheroledefs[$roleid] = $cachedroledef;
292             }
293         }
295         // Check we have the remaining keys from the MUC.
296         if ($uncached = array_diff($roleids, array_keys($ACCESSLIB_PRIVATE->cacheroledefs))) {
297             $uncached = get_role_definitions_uncached($uncached);
298             $ACCESSLIB_PRIVATE->cacheroledefs += $uncached;
299             $cache->set_many($uncached);
300         }
301     }
303     // Return just the roles we need.
304     return array_intersect_key($ACCESSLIB_PRIVATE->cacheroledefs, array_flip($roleids));
307 /**
308  * Query raw "site wide" role definitions.
309  *
310  * @param array $roleids List of role ids to fetch definitions for.
311  * @return array Complete definition for each requested role.
312  */
313 function get_role_definitions_uncached(array $roleids) {
314     global $DB;
316     if (empty($roleids)) {
317         return array();
318     }
320     // Create a blank results array: even if a role has no capabilities,
321     // we need to ensure it is included in the results to show we have
322     // loaded all the capabilities that there are.
323     $rdefs = array();
324     foreach ($roleids as $roleid) {
325         $rdefs[$roleid] = array();
326     }
328     // Load all the capabilities for these roles in all contexts.
329     list($sql, $params) = $DB->get_in_or_equal($roleids);
330     $sql = "SELECT ctx.path, rc.roleid, rc.capability, rc.permission
331               FROM {role_capabilities} rc
332               JOIN {context} ctx ON rc.contextid = ctx.id
333              WHERE rc.roleid $sql";
334     $rs = $DB->get_recordset_sql($sql, $params);
336     // Store the capabilities into the expected data structure.
337     foreach ($rs as $rd) {
338         if (!isset($rdefs[$rd->roleid][$rd->path])) {
339             $rdefs[$rd->roleid][$rd->path] = array();
340         }
341         $rdefs[$rd->roleid][$rd->path][$rd->capability] = (int) $rd->permission;
342     }
344     $rs->close();
346     // Sometimes (e.g. get_user_capability_course_helper::get_capability_info_at_each_context)
347     // we process role definitinons in a way that requires we see parent contexts
348     // before child contexts. This sort ensures that works (and is faster than
349     // sorting in the SQL query).
350     foreach ($rdefs as $roleid => $rdef) {
351         ksort($rdefs[$roleid]);
352     }
354     return $rdefs;
357 /**
358  * Get the default guest role, this is used for guest account,
359  * search engine spiders, etc.
360  *
361  * @return stdClass role record
362  */
363 function get_guest_role() {
364     global $CFG, $DB;
366     if (empty($CFG->guestroleid)) {
367         if ($roles = $DB->get_records('role', array('archetype'=>'guest'))) {
368             $guestrole = array_shift($roles);   // Pick the first one
369             set_config('guestroleid', $guestrole->id);
370             return $guestrole;
371         } else {
372             debugging('Can not find any guest role!');
373             return false;
374         }
375     } else {
376         if ($guestrole = $DB->get_record('role', array('id'=>$CFG->guestroleid))) {
377             return $guestrole;
378         } else {
379             // somebody is messing with guest roles, remove incorrect setting and try to find a new one
380             set_config('guestroleid', '');
381             return get_guest_role();
382         }
383     }
386 /**
387  * Check whether a user has a particular capability in a given context.
388  *
389  * For example:
390  *      $context = context_module::instance($cm->id);
391  *      has_capability('mod/forum:replypost', $context)
392  *
393  * By default checks the capabilities of the current user, but you can pass a
394  * different userid. By default will return true for admin users, but you can override that with the fourth argument.
395  *
396  * Guest and not-logged-in users can never get any dangerous capability - that is any write capability
397  * or capabilities with XSS, config or data loss risks.
398  *
399  * @category access
400  *
401  * @param string $capability the name of the capability to check. For example mod/forum:view
402  * @param context $context the context to check the capability in. You normally get this with instance method of a context class.
403  * @param integer|stdClass $user A user id or object. By default (null) checks the permissions of the current user.
404  * @param boolean $doanything If false, ignores effect of admin role assignment
405  * @return boolean true if the user has this capability. Otherwise false.
406  */
407 function has_capability($capability, context $context, $user = null, $doanything = true) {
408     global $USER, $CFG, $SCRIPT, $ACCESSLIB_PRIVATE;
410     if (during_initial_install()) {
411         if ($SCRIPT === "/$CFG->admin/index.php"
412                 or $SCRIPT === "/$CFG->admin/cli/install.php"
413                 or $SCRIPT === "/$CFG->admin/cli/install_database.php"
414                 or (defined('BEHAT_UTIL') and BEHAT_UTIL)
415                 or (defined('PHPUNIT_UTIL') and PHPUNIT_UTIL)) {
416             // we are in an installer - roles can not work yet
417             return true;
418         } else {
419             return false;
420         }
421     }
423     if (strpos($capability, 'moodle/legacy:') === 0) {
424         throw new coding_exception('Legacy capabilities can not be used any more!');
425     }
427     if (!is_bool($doanything)) {
428         throw new coding_exception('Capability parameter "doanything" is wierd, only true or false is allowed. This has to be fixed in code.');
429     }
431     // capability must exist
432     if (!$capinfo = get_capability_info($capability)) {
433         debugging('Capability "'.$capability.'" was not found! This has to be fixed in code.');
434         return false;
435     }
437     if (!isset($USER->id)) {
438         // should never happen
439         $USER->id = 0;
440         debugging('Capability check being performed on a user with no ID.', DEBUG_DEVELOPER);
441     }
443     // make sure there is a real user specified
444     if ($user === null) {
445         $userid = $USER->id;
446     } else {
447         $userid = is_object($user) ? $user->id : $user;
448     }
450     // make sure forcelogin cuts off not-logged-in users if enabled
451     if (!empty($CFG->forcelogin) and $userid == 0) {
452         return false;
453     }
455     // make sure the guest account and not-logged-in users never get any risky caps no matter what the actual settings are.
456     if (($capinfo->captype === 'write') or ($capinfo->riskbitmask & (RISK_XSS | RISK_CONFIG | RISK_DATALOSS))) {
457         if (isguestuser($userid) or $userid == 0) {
458             return false;
459         }
460     }
462     // somehow make sure the user is not deleted and actually exists
463     if ($userid != 0) {
464         if ($userid == $USER->id and isset($USER->deleted)) {
465             // this prevents one query per page, it is a bit of cheating,
466             // but hopefully session is terminated properly once user is deleted
467             if ($USER->deleted) {
468                 return false;
469             }
470         } else {
471             if (!context_user::instance($userid, IGNORE_MISSING)) {
472                 // no user context == invalid userid
473                 return false;
474             }
475         }
476     }
478     // context path/depth must be valid
479     if (empty($context->path) or $context->depth == 0) {
480         // this should not happen often, each upgrade tries to rebuild the context paths
481         debugging('Context id '.$context->id.' does not have valid path, please use context_helper::build_all_paths()');
482         if (is_siteadmin($userid)) {
483             return true;
484         } else {
485             return false;
486         }
487     }
489     // Find out if user is admin - it is not possible to override the doanything in any way
490     // and it is not possible to switch to admin role either.
491     if ($doanything) {
492         if (is_siteadmin($userid)) {
493             if ($userid != $USER->id) {
494                 return true;
495             }
496             // make sure switchrole is not used in this context
497             if (empty($USER->access['rsw'])) {
498                 return true;
499             }
500             $parts = explode('/', trim($context->path, '/'));
501             $path = '';
502             $switched = false;
503             foreach ($parts as $part) {
504                 $path .= '/' . $part;
505                 if (!empty($USER->access['rsw'][$path])) {
506                     $switched = true;
507                     break;
508                 }
509             }
510             if (!$switched) {
511                 return true;
512             }
513             //ok, admin switched role in this context, let's use normal access control rules
514         }
515     }
517     // Careful check for staleness...
518     $context->reload_if_dirty();
520     if ($USER->id == $userid) {
521         if (!isset($USER->access)) {
522             load_all_capabilities();
523         }
524         $access =& $USER->access;
526     } else {
527         // make sure user accessdata is really loaded
528         get_user_accessdata($userid, true);
529         $access =& $ACCESSLIB_PRIVATE->accessdatabyuser[$userid];
530     }
532     return has_capability_in_accessdata($capability, $context, $access);
535 /**
536  * Check if the user has any one of several capabilities from a list.
537  *
538  * This is just a utility method that calls has_capability in a loop. Try to put
539  * the capabilities that most users are likely to have first in the list for best
540  * performance.
541  *
542  * @category access
543  * @see has_capability()
544  *
545  * @param array $capabilities an array of capability names.
546  * @param context $context the context to check the capability in. You normally get this with instance method of a context class.
547  * @param integer|stdClass $user A user id or object. By default (null) checks the permissions of the current user.
548  * @param boolean $doanything If false, ignore effect of admin role assignment
549  * @return boolean true if the user has any of these capabilities. Otherwise false.
550  */
551 function has_any_capability(array $capabilities, context $context, $user = null, $doanything = true) {
552     foreach ($capabilities as $capability) {
553         if (has_capability($capability, $context, $user, $doanything)) {
554             return true;
555         }
556     }
557     return false;
560 /**
561  * Check if the user has all the capabilities in a list.
562  *
563  * This is just a utility method that calls has_capability in a loop. Try to put
564  * the capabilities that fewest users are likely to have first in the list for best
565  * performance.
566  *
567  * @category access
568  * @see has_capability()
569  *
570  * @param array $capabilities an array of capability names.
571  * @param context $context the context to check the capability in. You normally get this with instance method of a context class.
572  * @param integer|stdClass $user A user id or object. By default (null) checks the permissions of the current user.
573  * @param boolean $doanything If false, ignore effect of admin role assignment
574  * @return boolean true if the user has all of these capabilities. Otherwise false.
575  */
576 function has_all_capabilities(array $capabilities, context $context, $user = null, $doanything = true) {
577     foreach ($capabilities as $capability) {
578         if (!has_capability($capability, $context, $user, $doanything)) {
579             return false;
580         }
581     }
582     return true;
585 /**
586  * Is course creator going to have capability in a new course?
587  *
588  * This is intended to be used in enrolment plugins before or during course creation,
589  * do not use after the course is fully created.
590  *
591  * @category access
592  *
593  * @param string $capability the name of the capability to check.
594  * @param context $context course or category context where is course going to be created
595  * @param integer|stdClass $user A user id or object. By default (null) checks the permissions of the current user.
596  * @return boolean true if the user will have this capability.
597  *
598  * @throws coding_exception if different type of context submitted
599  */
600 function guess_if_creator_will_have_course_capability($capability, context $context, $user = null) {
601     global $CFG;
603     if ($context->contextlevel != CONTEXT_COURSE and $context->contextlevel != CONTEXT_COURSECAT) {
604         throw new coding_exception('Only course or course category context expected');
605     }
607     if (has_capability($capability, $context, $user)) {
608         // User already has the capability, it could be only removed if CAP_PROHIBIT
609         // was involved here, but we ignore that.
610         return true;
611     }
613     if (!has_capability('moodle/course:create', $context, $user)) {
614         return false;
615     }
617     if (!enrol_is_enabled('manual')) {
618         return false;
619     }
621     if (empty($CFG->creatornewroleid)) {
622         return false;
623     }
625     if ($context->contextlevel == CONTEXT_COURSE) {
626         if (is_viewing($context, $user, 'moodle/role:assign') or is_enrolled($context, $user, 'moodle/role:assign')) {
627             return false;
628         }
629     } else {
630         if (has_capability('moodle/course:view', $context, $user) and has_capability('moodle/role:assign', $context, $user)) {
631             return false;
632         }
633     }
635     // Most likely they will be enrolled after the course creation is finished,
636     // does the new role have the required capability?
637     list($neededroles, $forbiddenroles) = get_roles_with_cap_in_context($context, $capability);
638     return isset($neededroles[$CFG->creatornewroleid]);
641 /**
642  * Check if the user is an admin at the site level.
643  *
644  * Please note that use of proper capabilities is always encouraged,
645  * this function is supposed to be used from core or for temporary hacks.
646  *
647  * @category access
648  *
649  * @param  int|stdClass  $user_or_id user id or user object
650  * @return bool true if user is one of the administrators, false otherwise
651  */
652 function is_siteadmin($user_or_id = null) {
653     global $CFG, $USER;
655     if ($user_or_id === null) {
656         $user_or_id = $USER;
657     }
659     if (empty($user_or_id)) {
660         return false;
661     }
662     if (!empty($user_or_id->id)) {
663         $userid = $user_or_id->id;
664     } else {
665         $userid = $user_or_id;
666     }
668     // Because this script is called many times (150+ for course page) with
669     // the same parameters, it is worth doing minor optimisations. This static
670     // cache stores the value for a single userid, saving about 2ms from course
671     // page load time without using significant memory. As the static cache
672     // also includes the value it depends on, this cannot break unit tests.
673     static $knownid, $knownresult, $knownsiteadmins;
674     if ($knownid === $userid && $knownsiteadmins === $CFG->siteadmins) {
675         return $knownresult;
676     }
677     $knownid = $userid;
678     $knownsiteadmins = $CFG->siteadmins;
680     $siteadmins = explode(',', $CFG->siteadmins);
681     $knownresult = in_array($userid, $siteadmins);
682     return $knownresult;
685 /**
686  * Returns true if user has at least one role assign
687  * of 'coursecontact' role (is potentially listed in some course descriptions).
688  *
689  * @param int $userid
690  * @return bool
691  */
692 function has_coursecontact_role($userid) {
693     global $DB, $CFG;
695     if (empty($CFG->coursecontact)) {
696         return false;
697     }
698     $sql = "SELECT 1
699               FROM {role_assignments}
700              WHERE userid = :userid AND roleid IN ($CFG->coursecontact)";
701     return $DB->record_exists_sql($sql, array('userid'=>$userid));
704 /**
705  * Does the user have a capability to do something?
706  *
707  * Walk the accessdata array and return true/false.
708  * Deals with prohibits, role switching, aggregating
709  * capabilities, etc.
710  *
711  * The main feature of here is being FAST and with no
712  * side effects.
713  *
714  * Notes:
715  *
716  * Switch Role merges with default role
717  * ------------------------------------
718  * If you are a teacher in course X, you have at least
719  * teacher-in-X + defaultloggedinuser-sitewide. So in the
720  * course you'll have techer+defaultloggedinuser.
721  * We try to mimic that in switchrole.
722  *
723  * Permission evaluation
724  * ---------------------
725  * Originally there was an extremely complicated way
726  * to determine the user access that dealt with
727  * "locality" or role assignments and role overrides.
728  * Now we simply evaluate access for each role separately
729  * and then verify if user has at least one role with allow
730  * and at the same time no role with prohibit.
731  *
732  * @access private
733  * @param string $capability
734  * @param context $context
735  * @param array $accessdata
736  * @return bool
737  */
738 function has_capability_in_accessdata($capability, context $context, array &$accessdata) {
739     global $CFG;
741     // Build $paths as a list of current + all parent "paths" with order bottom-to-top
742     $path = $context->path;
743     $paths = array($path);
744     while($path = rtrim($path, '0123456789')) {
745         $path = rtrim($path, '/');
746         if ($path === '') {
747             break;
748         }
749         $paths[] = $path;
750     }
752     $roles = array();
753     $switchedrole = false;
755     // Find out if role switched
756     if (!empty($accessdata['rsw'])) {
757         // From the bottom up...
758         foreach ($paths as $path) {
759             if (isset($accessdata['rsw'][$path])) {
760                 // Found a switchrole assignment - check for that role _plus_ the default user role
761                 $roles = array($accessdata['rsw'][$path]=>null, $CFG->defaultuserroleid=>null);
762                 $switchedrole = true;
763                 break;
764             }
765         }
766     }
768     if (!$switchedrole) {
769         // get all users roles in this context and above
770         foreach ($paths as $path) {
771             if (isset($accessdata['ra'][$path])) {
772                 foreach ($accessdata['ra'][$path] as $roleid) {
773                     $roles[$roleid] = null;
774                 }
775             }
776         }
777     }
779     // Now find out what access is given to each role, going bottom-->up direction
780     $rdefs = get_role_definitions(array_keys($roles));
781     $allowed = false;
783     foreach ($roles as $roleid => $ignored) {
784         foreach ($paths as $path) {
785             if (isset($rdefs[$roleid][$path][$capability])) {
786                 $perm = (int)$rdefs[$roleid][$path][$capability];
787                 if ($perm === CAP_PROHIBIT) {
788                     // any CAP_PROHIBIT found means no permission for the user
789                     return false;
790                 }
791                 if (is_null($roles[$roleid])) {
792                     $roles[$roleid] = $perm;
793                 }
794             }
795         }
796         // CAP_ALLOW in any role means the user has a permission, we continue only to detect prohibits
797         $allowed = ($allowed or $roles[$roleid] === CAP_ALLOW);
798     }
800     return $allowed;
803 /**
804  * A convenience function that tests has_capability, and displays an error if
805  * the user does not have that capability.
806  *
807  * NOTE before Moodle 2.0, this function attempted to make an appropriate
808  * require_login call before checking the capability. This is no longer the case.
809  * You must call require_login (or one of its variants) if you want to check the
810  * user is logged in, before you call this function.
811  *
812  * @see has_capability()
813  *
814  * @param string $capability the name of the capability to check. For example mod/forum:view
815  * @param context $context the context to check the capability in. You normally get this with context_xxxx::instance().
816  * @param int $userid A user id. By default (null) checks the permissions of the current user.
817  * @param bool $doanything If false, ignore effect of admin role assignment
818  * @param string $errormessage The error string to to user. Defaults to 'nopermissions'.
819  * @param string $stringfile The language file to load the error string from. Defaults to 'error'.
820  * @return void terminates with an error if the user does not have the given capability.
821  */
822 function require_capability($capability, context $context, $userid = null, $doanything = true,
823                             $errormessage = 'nopermissions', $stringfile = '') {
824     if (!has_capability($capability, $context, $userid, $doanything)) {
825         throw new required_capability_exception($context, $capability, $errormessage, $stringfile);
826     }
829 /**
830  * Return a nested array showing all role assignments for the user.
831  * [ra] => [contextpath][roleid] = roleid
832  *
833  * @access private
834  * @param int $userid - the id of the user
835  * @return array access info array
836  */
837 function get_user_roles_sitewide_accessdata($userid) {
838     global $CFG, $DB;
840     $accessdata = get_empty_accessdata();
842     // start with the default role
843     if (!empty($CFG->defaultuserroleid)) {
844         $syscontext = context_system::instance();
845         $accessdata['ra'][$syscontext->path][(int)$CFG->defaultuserroleid] = (int)$CFG->defaultuserroleid;
846     }
848     // load the "default frontpage role"
849     if (!empty($CFG->defaultfrontpageroleid)) {
850         $frontpagecontext = context_course::instance(get_site()->id);
851         if ($frontpagecontext->path) {
852             $accessdata['ra'][$frontpagecontext->path][(int)$CFG->defaultfrontpageroleid] = (int)$CFG->defaultfrontpageroleid;
853         }
854     }
856     // Preload every assigned role.
857     $sql = "SELECT ctx.path, ra.roleid, ra.contextid
858               FROM {role_assignments} ra
859               JOIN {context} ctx ON ctx.id = ra.contextid
860              WHERE ra.userid = :userid";
862     $rs = $DB->get_recordset_sql($sql, array('userid' => $userid));
864     foreach ($rs as $ra) {
865         // RAs leafs are arrays to support multi-role assignments...
866         $accessdata['ra'][$ra->path][(int)$ra->roleid] = (int)$ra->roleid;
867     }
869     $rs->close();
871     return $accessdata;
874 /**
875  * Returns empty accessdata structure.
876  *
877  * @access private
878  * @return array empt accessdata
879  */
880 function get_empty_accessdata() {
881     $accessdata               = array(); // named list
882     $accessdata['ra']         = array();
883     $accessdata['time']       = time();
884     $accessdata['rsw']        = array();
886     return $accessdata;
889 /**
890  * Get accessdata for a given user.
891  *
892  * @access private
893  * @param int $userid
894  * @param bool $preloadonly true means do not return access array
895  * @return array accessdata
896  */
897 function get_user_accessdata($userid, $preloadonly=false) {
898     global $CFG, $ACCESSLIB_PRIVATE, $USER;
900     if (isset($USER->access)) {
901         $ACCESSLIB_PRIVATE->accessdatabyuser[$USER->id] = $USER->access;
902     }
904     if (!isset($ACCESSLIB_PRIVATE->accessdatabyuser[$userid])) {
905         if (empty($userid)) {
906             if (!empty($CFG->notloggedinroleid)) {
907                 $accessdata = get_role_access($CFG->notloggedinroleid);
908             } else {
909                 // weird
910                 return get_empty_accessdata();
911             }
913         } else if (isguestuser($userid)) {
914             if ($guestrole = get_guest_role()) {
915                 $accessdata = get_role_access($guestrole->id);
916             } else {
917                 //weird
918                 return get_empty_accessdata();
919             }
921         } else {
922             // Includes default role and frontpage role.
923             $accessdata = get_user_roles_sitewide_accessdata($userid);
924         }
926         $ACCESSLIB_PRIVATE->accessdatabyuser[$userid] = $accessdata;
927     }
929     if ($preloadonly) {
930         return;
931     } else {
932         return $ACCESSLIB_PRIVATE->accessdatabyuser[$userid];
933     }
936 /**
937  * A convenience function to completely load all the capabilities
938  * for the current user. It is called from has_capability() and functions change permissions.
939  *
940  * Call it only _after_ you've setup $USER and called check_enrolment_plugins();
941  * @see check_enrolment_plugins()
942  *
943  * @access private
944  * @return void
945  */
946 function load_all_capabilities() {
947     global $USER;
949     // roles not installed yet - we are in the middle of installation
950     if (during_initial_install()) {
951         return;
952     }
954     if (!isset($USER->id)) {
955         // this should not happen
956         $USER->id = 0;
957     }
959     unset($USER->access);
960     $USER->access = get_user_accessdata($USER->id);
962     // Clear to force a refresh
963     unset($USER->mycourses);
965     // init/reset internal enrol caches - active course enrolments and temp access
966     $USER->enrol = array('enrolled'=>array(), 'tempguest'=>array());
969 /**
970  * A convenience function to completely reload all the capabilities
971  * for the current user when roles have been updated in a relevant
972  * context -- but PRESERVING switchroles and loginas.
973  * This function resets all accesslib and context caches.
974  *
975  * That is - completely transparent to the user.
976  *
977  * Note: reloads $USER->access completely.
978  *
979  * @access private
980  * @return void
981  */
982 function reload_all_capabilities() {
983     global $USER, $DB, $ACCESSLIB_PRIVATE;
985     // copy switchroles
986     $sw = array();
987     if (!empty($USER->access['rsw'])) {
988         $sw = $USER->access['rsw'];
989     }
991     accesslib_clear_all_caches(true);
992     unset($USER->access);
993     $ACCESSLIB_PRIVATE->dirtycontexts = array(); // prevent dirty flags refetching on this page
995     load_all_capabilities();
997     foreach ($sw as $path => $roleid) {
998         if ($record = $DB->get_record('context', array('path'=>$path))) {
999             $context = context::instance_by_id($record->id);
1000             role_switch($roleid, $context);
1001         }
1002     }
1005 /**
1006  * Adds a temp role to current USER->access array.
1007  *
1008  * Useful for the "temporary guest" access we grant to logged-in users.
1009  * This is useful for enrol plugins only.
1010  *
1011  * @since Moodle 2.2
1012  * @param context_course $coursecontext
1013  * @param int $roleid
1014  * @return void
1015  */
1016 function load_temp_course_role(context_course $coursecontext, $roleid) {
1017     global $USER, $SITE;
1019     if (empty($roleid)) {
1020         debugging('invalid role specified in load_temp_course_role()');
1021         return;
1022     }
1024     if ($coursecontext->instanceid == $SITE->id) {
1025         debugging('Can not use temp roles on the frontpage');
1026         return;
1027     }
1029     if (!isset($USER->access)) {
1030         load_all_capabilities();
1031     }
1033     $coursecontext->reload_if_dirty();
1035     if (isset($USER->access['ra'][$coursecontext->path][$roleid])) {
1036         return;
1037     }
1039     $USER->access['ra'][$coursecontext->path][(int)$roleid] = (int)$roleid;
1042 /**
1043  * Removes any extra guest roles from current USER->access array.
1044  * This is useful for enrol plugins only.
1045  *
1046  * @since Moodle 2.2
1047  * @param context_course $coursecontext
1048  * @return void
1049  */
1050 function remove_temp_course_roles(context_course $coursecontext) {
1051     global $DB, $USER, $SITE;
1053     if ($coursecontext->instanceid == $SITE->id) {
1054         debugging('Can not use temp roles on the frontpage');
1055         return;
1056     }
1058     if (empty($USER->access['ra'][$coursecontext->path])) {
1059         //no roles here, weird
1060         return;
1061     }
1063     $sql = "SELECT DISTINCT ra.roleid AS id
1064               FROM {role_assignments} ra
1065              WHERE ra.contextid = :contextid AND ra.userid = :userid";
1066     $ras = $DB->get_records_sql($sql, array('contextid'=>$coursecontext->id, 'userid'=>$USER->id));
1068     $USER->access['ra'][$coursecontext->path] = array();
1069     foreach($ras as $r) {
1070         $USER->access['ra'][$coursecontext->path][(int)$r->id] = (int)$r->id;
1071     }
1074 /**
1075  * Returns array of all role archetypes.
1076  *
1077  * @return array
1078  */
1079 function get_role_archetypes() {
1080     return array(
1081         'manager'        => 'manager',
1082         'coursecreator'  => 'coursecreator',
1083         'editingteacher' => 'editingteacher',
1084         'teacher'        => 'teacher',
1085         'student'        => 'student',
1086         'guest'          => 'guest',
1087         'user'           => 'user',
1088         'frontpage'      => 'frontpage'
1089     );
1092 /**
1093  * Assign the defaults found in this capability definition to roles that have
1094  * the corresponding legacy capabilities assigned to them.
1095  *
1096  * @param string $capability
1097  * @param array $legacyperms an array in the format (example):
1098  *                      'guest' => CAP_PREVENT,
1099  *                      'student' => CAP_ALLOW,
1100  *                      'teacher' => CAP_ALLOW,
1101  *                      'editingteacher' => CAP_ALLOW,
1102  *                      'coursecreator' => CAP_ALLOW,
1103  *                      'manager' => CAP_ALLOW
1104  * @return boolean success or failure.
1105  */
1106 function assign_legacy_capabilities($capability, $legacyperms) {
1108     $archetypes = get_role_archetypes();
1110     foreach ($legacyperms as $type => $perm) {
1112         $systemcontext = context_system::instance();
1113         if ($type === 'admin') {
1114             debugging('Legacy type admin in access.php was renamed to manager, please update the code.');
1115             $type = 'manager';
1116         }
1118         if (!array_key_exists($type, $archetypes)) {
1119             print_error('invalidlegacy', '', '', $type);
1120         }
1122         if ($roles = get_archetype_roles($type)) {
1123             foreach ($roles as $role) {
1124                 // Assign a site level capability.
1125                 if (!assign_capability($capability, $perm, $role->id, $systemcontext->id)) {
1126                     return false;
1127                 }
1128             }
1129         }
1130     }
1131     return true;
1134 /**
1135  * Verify capability risks.
1136  *
1137  * @param stdClass $capability a capability - a row from the capabilities table.
1138  * @return boolean whether this capability is safe - that is, whether people with the
1139  *      safeoverrides capability should be allowed to change it.
1140  */
1141 function is_safe_capability($capability) {
1142     return !((RISK_DATALOSS | RISK_MANAGETRUST | RISK_CONFIG | RISK_XSS | RISK_PERSONAL) & $capability->riskbitmask);
1145 /**
1146  * Get the local override (if any) for a given capability in a role in a context
1147  *
1148  * @param int $roleid
1149  * @param int $contextid
1150  * @param string $capability
1151  * @return stdClass local capability override
1152  */
1153 function get_local_override($roleid, $contextid, $capability) {
1154     global $DB;
1155     return $DB->get_record('role_capabilities', array('roleid'=>$roleid, 'capability'=>$capability, 'contextid'=>$contextid));
1158 /**
1159  * Returns context instance plus related course and cm instances
1160  *
1161  * @param int $contextid
1162  * @return array of ($context, $course, $cm)
1163  */
1164 function get_context_info_array($contextid) {
1165     global $DB;
1167     $context = context::instance_by_id($contextid, MUST_EXIST);
1168     $course  = null;
1169     $cm      = null;
1171     if ($context->contextlevel == CONTEXT_COURSE) {
1172         $course = $DB->get_record('course', array('id'=>$context->instanceid), '*', MUST_EXIST);
1174     } else if ($context->contextlevel == CONTEXT_MODULE) {
1175         $cm = get_coursemodule_from_id('', $context->instanceid, 0, false, MUST_EXIST);
1176         $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
1178     } else if ($context->contextlevel == CONTEXT_BLOCK) {
1179         $parent = $context->get_parent_context();
1181         if ($parent->contextlevel == CONTEXT_COURSE) {
1182             $course = $DB->get_record('course', array('id'=>$parent->instanceid), '*', MUST_EXIST);
1183         } else if ($parent->contextlevel == CONTEXT_MODULE) {
1184             $cm = get_coursemodule_from_id('', $parent->instanceid, 0, false, MUST_EXIST);
1185             $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
1186         }
1187     }
1189     return array($context, $course, $cm);
1192 /**
1193  * Function that creates a role
1194  *
1195  * @param string $name role name
1196  * @param string $shortname role short name
1197  * @param string $description role description
1198  * @param string $archetype
1199  * @return int id or dml_exception
1200  */
1201 function create_role($name, $shortname, $description, $archetype = '') {
1202     global $DB;
1204     if (strpos($archetype, 'moodle/legacy:') !== false) {
1205         throw new coding_exception('Use new role archetype parameter in create_role() instead of old legacy capabilities.');
1206     }
1208     // verify role archetype actually exists
1209     $archetypes = get_role_archetypes();
1210     if (empty($archetypes[$archetype])) {
1211         $archetype = '';
1212     }
1214     // Insert the role record.
1215     $role = new stdClass();
1216     $role->name        = $name;
1217     $role->shortname   = $shortname;
1218     $role->description = $description;
1219     $role->archetype   = $archetype;
1221     //find free sortorder number
1222     $role->sortorder = $DB->get_field('role', 'MAX(sortorder) + 1', array());
1223     if (empty($role->sortorder)) {
1224         $role->sortorder = 1;
1225     }
1226     $id = $DB->insert_record('role', $role);
1228     return $id;
1231 /**
1232  * Function that deletes a role and cleanups up after it
1233  *
1234  * @param int $roleid id of role to delete
1235  * @return bool always true
1236  */
1237 function delete_role($roleid) {
1238     global $DB;
1240     // first unssign all users
1241     role_unassign_all(array('roleid'=>$roleid));
1243     // cleanup all references to this role, ignore errors
1244     $DB->delete_records('role_capabilities',   array('roleid'=>$roleid));
1245     $DB->delete_records('role_allow_assign',   array('roleid'=>$roleid));
1246     $DB->delete_records('role_allow_assign',   array('allowassign'=>$roleid));
1247     $DB->delete_records('role_allow_override', array('roleid'=>$roleid));
1248     $DB->delete_records('role_allow_override', array('allowoverride'=>$roleid));
1249     $DB->delete_records('role_names',          array('roleid'=>$roleid));
1250     $DB->delete_records('role_context_levels', array('roleid'=>$roleid));
1252     // Get role record before it's deleted.
1253     $role = $DB->get_record('role', array('id'=>$roleid));
1255     // Finally delete the role itself.
1256     $DB->delete_records('role', array('id'=>$roleid));
1258     // Trigger event.
1259     $event = \core\event\role_deleted::create(
1260         array(
1261             'context' => context_system::instance(),
1262             'objectid' => $roleid,
1263             'other' =>
1264                 array(
1265                     'shortname' => $role->shortname,
1266                     'description' => $role->description,
1267                     'archetype' => $role->archetype
1268                 )
1269             )
1270         );
1271     $event->add_record_snapshot('role', $role);
1272     $event->trigger();
1274     // Reset any cache of this role, including MUC.
1275     accesslib_clear_role_cache($roleid);
1277     return true;
1280 /**
1281  * Function to write context specific overrides, or default capabilities.
1282  *
1283  * NOTE: use $context->mark_dirty() after this
1284  *
1285  * @param string $capability string name
1286  * @param int $permission CAP_ constants
1287  * @param int $roleid role id
1288  * @param int|context $contextid context id
1289  * @param bool $overwrite
1290  * @return bool always true or exception
1291  */
1292 function assign_capability($capability, $permission, $roleid, $contextid, $overwrite = false) {
1293     global $USER, $DB;
1295     if ($contextid instanceof context) {
1296         $context = $contextid;
1297     } else {
1298         $context = context::instance_by_id($contextid);
1299     }
1301     if (empty($permission) || $permission == CAP_INHERIT) { // if permission is not set
1302         unassign_capability($capability, $roleid, $context->id);
1303         return true;
1304     }
1306     $existing = $DB->get_record('role_capabilities', array('contextid'=>$context->id, 'roleid'=>$roleid, 'capability'=>$capability));
1308     if ($existing and !$overwrite) {   // We want to keep whatever is there already
1309         return true;
1310     }
1312     $cap = new stdClass();
1313     $cap->contextid    = $context->id;
1314     $cap->roleid       = $roleid;
1315     $cap->capability   = $capability;
1316     $cap->permission   = $permission;
1317     $cap->timemodified = time();
1318     $cap->modifierid   = empty($USER->id) ? 0 : $USER->id;
1320     if ($existing) {
1321         $cap->id = $existing->id;
1322         $DB->update_record('role_capabilities', $cap);
1323     } else {
1324         if ($DB->record_exists('context', array('id'=>$context->id))) {
1325             $DB->insert_record('role_capabilities', $cap);
1326         }
1327     }
1329     // Reset any cache of this role, including MUC.
1330     accesslib_clear_role_cache($roleid);
1332     return true;
1335 /**
1336  * Unassign a capability from a role.
1337  *
1338  * NOTE: use $context->mark_dirty() after this
1339  *
1340  * @param string $capability the name of the capability
1341  * @param int $roleid the role id
1342  * @param int|context $contextid null means all contexts
1343  * @return boolean true or exception
1344  */
1345 function unassign_capability($capability, $roleid, $contextid = null) {
1346     global $DB;
1348     if (!empty($contextid)) {
1349         if ($contextid instanceof context) {
1350             $context = $contextid;
1351         } else {
1352             $context = context::instance_by_id($contextid);
1353         }
1354         // delete from context rel, if this is the last override in this context
1355         $DB->delete_records('role_capabilities', array('capability'=>$capability, 'roleid'=>$roleid, 'contextid'=>$context->id));
1356     } else {
1357         $DB->delete_records('role_capabilities', array('capability'=>$capability, 'roleid'=>$roleid));
1358     }
1360     // Reset any cache of this role, including MUC.
1361     accesslib_clear_role_cache($roleid);
1363     return true;
1366 /**
1367  * Get the roles that have a given capability assigned to it
1368  *
1369  * This function does not resolve the actual permission of the capability.
1370  * It just checks for permissions and overrides.
1371  * Use get_roles_with_cap_in_context() if resolution is required.
1372  *
1373  * @param string $capability capability name (string)
1374  * @param string $permission optional, the permission defined for this capability
1375  *                      either CAP_ALLOW, CAP_PREVENT or CAP_PROHIBIT. Defaults to null which means any.
1376  * @param stdClass $context null means any
1377  * @return array of role records
1378  */
1379 function get_roles_with_capability($capability, $permission = null, $context = null) {
1380     global $DB;
1382     if ($context) {
1383         $contexts = $context->get_parent_context_ids(true);
1384         list($insql, $params) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED, 'ctx');
1385         $contextsql = "AND rc.contextid $insql";
1386     } else {
1387         $params = array();
1388         $contextsql = '';
1389     }
1391     if ($permission) {
1392         $permissionsql = " AND rc.permission = :permission";
1393         $params['permission'] = $permission;
1394     } else {
1395         $permissionsql = '';
1396     }
1398     $sql = "SELECT r.*
1399               FROM {role} r
1400              WHERE r.id IN (SELECT rc.roleid
1401                               FROM {role_capabilities} rc
1402                              WHERE rc.capability = :capname
1403                                    $contextsql
1404                                    $permissionsql)";
1405     $params['capname'] = $capability;
1408     return $DB->get_records_sql($sql, $params);
1411 /**
1412  * This function makes a role-assignment (a role for a user in a particular context)
1413  *
1414  * @param int $roleid the role of the id
1415  * @param int $userid userid
1416  * @param int|context $contextid id of the context
1417  * @param string $component example 'enrol_ldap', defaults to '' which means manual assignment,
1418  * @param int $itemid id of enrolment/auth plugin
1419  * @param string $timemodified defaults to current time
1420  * @return int new/existing id of the assignment
1421  */
1422 function role_assign($roleid, $userid, $contextid, $component = '', $itemid = 0, $timemodified = '') {
1423     global $USER, $DB, $CFG;
1425     // first of all detect if somebody is using old style parameters
1426     if ($contextid === 0 or is_numeric($component)) {
1427         throw new coding_exception('Invalid call to role_assign(), code needs to be updated to use new order of parameters');
1428     }
1430     // now validate all parameters
1431     if (empty($roleid)) {
1432         throw new coding_exception('Invalid call to role_assign(), roleid can not be empty');
1433     }
1435     if (empty($userid)) {
1436         throw new coding_exception('Invalid call to role_assign(), userid can not be empty');
1437     }
1439     if ($itemid) {
1440         if (strpos($component, '_') === false) {
1441             throw new coding_exception('Invalid call to role_assign(), component must start with plugin type such as"enrol_" when itemid specified', 'component:'.$component);
1442         }
1443     } else {
1444         $itemid = 0;
1445         if ($component !== '' and strpos($component, '_') === false) {
1446             throw new coding_exception('Invalid call to role_assign(), invalid component string', 'component:'.$component);
1447         }
1448     }
1450     if (!$DB->record_exists('user', array('id'=>$userid, 'deleted'=>0))) {
1451         throw new coding_exception('User ID does not exist or is deleted!', 'userid:'.$userid);
1452     }
1454     if ($contextid instanceof context) {
1455         $context = $contextid;
1456     } else {
1457         $context = context::instance_by_id($contextid, MUST_EXIST);
1458     }
1460     if (!$timemodified) {
1461         $timemodified = time();
1462     }
1464     // Check for existing entry
1465     $ras = $DB->get_records('role_assignments', array('roleid'=>$roleid, 'contextid'=>$context->id, 'userid'=>$userid, 'component'=>$component, 'itemid'=>$itemid), 'id');
1467     if ($ras) {
1468         // role already assigned - this should not happen
1469         if (count($ras) > 1) {
1470             // very weird - remove all duplicates!
1471             $ra = array_shift($ras);
1472             foreach ($ras as $r) {
1473                 $DB->delete_records('role_assignments', array('id'=>$r->id));
1474             }
1475         } else {
1476             $ra = reset($ras);
1477         }
1479         // actually there is no need to update, reset anything or trigger any event, so just return
1480         return $ra->id;
1481     }
1483     // Create a new entry
1484     $ra = new stdClass();
1485     $ra->roleid       = $roleid;
1486     $ra->contextid    = $context->id;
1487     $ra->userid       = $userid;
1488     $ra->component    = $component;
1489     $ra->itemid       = $itemid;
1490     $ra->timemodified = $timemodified;
1491     $ra->modifierid   = empty($USER->id) ? 0 : $USER->id;
1492     $ra->sortorder    = 0;
1494     $ra->id = $DB->insert_record('role_assignments', $ra);
1496     // mark context as dirty - again expensive, but needed
1497     $context->mark_dirty();
1499     if (!empty($USER->id) && $USER->id == $userid) {
1500         // If the user is the current user, then do full reload of capabilities too.
1501         reload_all_capabilities();
1502     }
1504     require_once($CFG->libdir . '/coursecatlib.php');
1505     coursecat::role_assignment_changed($roleid, $context);
1507     $event = \core\event\role_assigned::create(array(
1508         'context' => $context,
1509         'objectid' => $ra->roleid,
1510         'relateduserid' => $ra->userid,
1511         'other' => array(
1512             'id' => $ra->id,
1513             'component' => $ra->component,
1514             'itemid' => $ra->itemid
1515         )
1516     ));
1517     $event->add_record_snapshot('role_assignments', $ra);
1518     $event->trigger();
1520     return $ra->id;
1523 /**
1524  * Removes one role assignment
1525  *
1526  * @param int $roleid
1527  * @param int  $userid
1528  * @param int  $contextid
1529  * @param string $component
1530  * @param int  $itemid
1531  * @return void
1532  */
1533 function role_unassign($roleid, $userid, $contextid, $component = '', $itemid = 0) {
1534     // first make sure the params make sense
1535     if ($roleid == 0 or $userid == 0 or $contextid == 0) {
1536         throw new coding_exception('Invalid call to role_unassign(), please use role_unassign_all() when removing multiple role assignments');
1537     }
1539     if ($itemid) {
1540         if (strpos($component, '_') === false) {
1541             throw new coding_exception('Invalid call to role_assign(), component must start with plugin type such as "enrol_" when itemid specified', 'component:'.$component);
1542         }
1543     } else {
1544         $itemid = 0;
1545         if ($component !== '' and strpos($component, '_') === false) {
1546             throw new coding_exception('Invalid call to role_assign(), invalid component string', 'component:'.$component);
1547         }
1548     }
1550     role_unassign_all(array('roleid'=>$roleid, 'userid'=>$userid, 'contextid'=>$contextid, 'component'=>$component, 'itemid'=>$itemid), false, false);
1553 /**
1554  * Removes multiple role assignments, parameters may contain:
1555  *   'roleid', 'userid', 'contextid', 'component', 'enrolid'.
1556  *
1557  * @param array $params role assignment parameters
1558  * @param bool $subcontexts unassign in subcontexts too
1559  * @param bool $includemanual include manual role assignments too
1560  * @return void
1561  */
1562 function role_unassign_all(array $params, $subcontexts = false, $includemanual = false) {
1563     global $USER, $CFG, $DB;
1564     require_once($CFG->libdir . '/coursecatlib.php');
1566     if (!$params) {
1567         throw new coding_exception('Missing parameters in role_unsassign_all() call');
1568     }
1570     $allowed = array('roleid', 'userid', 'contextid', 'component', 'itemid');
1571     foreach ($params as $key=>$value) {
1572         if (!in_array($key, $allowed)) {
1573             throw new coding_exception('Unknown role_unsassign_all() parameter key', 'key:'.$key);
1574         }
1575     }
1577     if (isset($params['component']) and $params['component'] !== '' and strpos($params['component'], '_') === false) {
1578         throw new coding_exception('Invalid component paramter in role_unsassign_all() call', 'component:'.$params['component']);
1579     }
1581     if ($includemanual) {
1582         if (!isset($params['component']) or $params['component'] === '') {
1583             throw new coding_exception('include manual parameter requires component parameter in role_unsassign_all() call');
1584         }
1585     }
1587     if ($subcontexts) {
1588         if (empty($params['contextid'])) {
1589             throw new coding_exception('subcontexts paramtere requires component parameter in role_unsassign_all() call');
1590         }
1591     }
1593     $ras = $DB->get_records('role_assignments', $params);
1594     foreach($ras as $ra) {
1595         $DB->delete_records('role_assignments', array('id'=>$ra->id));
1596         if ($context = context::instance_by_id($ra->contextid, IGNORE_MISSING)) {
1597             // this is a bit expensive but necessary
1598             $context->mark_dirty();
1599             // If the user is the current user, then do full reload of capabilities too.
1600             if (!empty($USER->id) && $USER->id == $ra->userid) {
1601                 reload_all_capabilities();
1602             }
1603             $event = \core\event\role_unassigned::create(array(
1604                 'context' => $context,
1605                 'objectid' => $ra->roleid,
1606                 'relateduserid' => $ra->userid,
1607                 'other' => array(
1608                     'id' => $ra->id,
1609                     'component' => $ra->component,
1610                     'itemid' => $ra->itemid
1611                 )
1612             ));
1613             $event->add_record_snapshot('role_assignments', $ra);
1614             $event->trigger();
1615             coursecat::role_assignment_changed($ra->roleid, $context);
1616         }
1617     }
1618     unset($ras);
1620     // process subcontexts
1621     if ($subcontexts and $context = context::instance_by_id($params['contextid'], IGNORE_MISSING)) {
1622         if ($params['contextid'] instanceof context) {
1623             $context = $params['contextid'];
1624         } else {
1625             $context = context::instance_by_id($params['contextid'], IGNORE_MISSING);
1626         }
1628         if ($context) {
1629             $contexts = $context->get_child_contexts();
1630             $mparams = $params;
1631             foreach($contexts as $context) {
1632                 $mparams['contextid'] = $context->id;
1633                 $ras = $DB->get_records('role_assignments', $mparams);
1634                 foreach($ras as $ra) {
1635                     $DB->delete_records('role_assignments', array('id'=>$ra->id));
1636                     // this is a bit expensive but necessary
1637                     $context->mark_dirty();
1638                     // If the user is the current user, then do full reload of capabilities too.
1639                     if (!empty($USER->id) && $USER->id == $ra->userid) {
1640                         reload_all_capabilities();
1641                     }
1642                     $event = \core\event\role_unassigned::create(
1643                         array('context'=>$context, 'objectid'=>$ra->roleid, 'relateduserid'=>$ra->userid,
1644                             'other'=>array('id'=>$ra->id, 'component'=>$ra->component, 'itemid'=>$ra->itemid)));
1645                     $event->add_record_snapshot('role_assignments', $ra);
1646                     $event->trigger();
1647                     coursecat::role_assignment_changed($ra->roleid, $context);
1648                 }
1649             }
1650         }
1651     }
1653     // do this once more for all manual role assignments
1654     if ($includemanual) {
1655         $params['component'] = '';
1656         role_unassign_all($params, $subcontexts, false);
1657     }
1660 /**
1661  * Determines if a user is currently logged in
1662  *
1663  * @category   access
1664  *
1665  * @return bool
1666  */
1667 function isloggedin() {
1668     global $USER;
1670     return (!empty($USER->id));
1673 /**
1674  * Determines if a user is logged in as real guest user with username 'guest'.
1675  *
1676  * @category   access
1677  *
1678  * @param int|object $user mixed user object or id, $USER if not specified
1679  * @return bool true if user is the real guest user, false if not logged in or other user
1680  */
1681 function isguestuser($user = null) {
1682     global $USER, $DB, $CFG;
1684     // make sure we have the user id cached in config table, because we are going to use it a lot
1685     if (empty($CFG->siteguest)) {
1686         if (!$guestid = $DB->get_field('user', 'id', array('username'=>'guest', 'mnethostid'=>$CFG->mnet_localhost_id))) {
1687             // guest does not exist yet, weird
1688             return false;
1689         }
1690         set_config('siteguest', $guestid);
1691     }
1692     if ($user === null) {
1693         $user = $USER;
1694     }
1696     if ($user === null) {
1697         // happens when setting the $USER
1698         return false;
1700     } else if (is_numeric($user)) {
1701         return ($CFG->siteguest == $user);
1703     } else if (is_object($user)) {
1704         if (empty($user->id)) {
1705             return false; // not logged in means is not be guest
1706         } else {
1707             return ($CFG->siteguest == $user->id);
1708         }
1710     } else {
1711         throw new coding_exception('Invalid user parameter supplied for isguestuser() function!');
1712     }
1715 /**
1716  * Does user have a (temporary or real) guest access to course?
1717  *
1718  * @category   access
1719  *
1720  * @param context $context
1721  * @param stdClass|int $user
1722  * @return bool
1723  */
1724 function is_guest(context $context, $user = null) {
1725     global $USER;
1727     // first find the course context
1728     $coursecontext = $context->get_course_context();
1730     // make sure there is a real user specified
1731     if ($user === null) {
1732         $userid = isset($USER->id) ? $USER->id : 0;
1733     } else {
1734         $userid = is_object($user) ? $user->id : $user;
1735     }
1737     if (isguestuser($userid)) {
1738         // can not inspect or be enrolled
1739         return true;
1740     }
1742     if (has_capability('moodle/course:view', $coursecontext, $user)) {
1743         // viewing users appear out of nowhere, they are neither guests nor participants
1744         return false;
1745     }
1747     // consider only real active enrolments here
1748     if (is_enrolled($coursecontext, $user, '', true)) {
1749         return false;
1750     }
1752     return true;
1755 /**
1756  * Returns true if the user has moodle/course:view capability in the course,
1757  * this is intended for admins, managers (aka small admins), inspectors, etc.
1758  *
1759  * @category   access
1760  *
1761  * @param context $context
1762  * @param int|stdClass $user if null $USER is used
1763  * @param string $withcapability extra capability name
1764  * @return bool
1765  */
1766 function is_viewing(context $context, $user = null, $withcapability = '') {
1767     // first find the course context
1768     $coursecontext = $context->get_course_context();
1770     if (isguestuser($user)) {
1771         // can not inspect
1772         return false;
1773     }
1775     if (!has_capability('moodle/course:view', $coursecontext, $user)) {
1776         // admins are allowed to inspect courses
1777         return false;
1778     }
1780     if ($withcapability and !has_capability($withcapability, $context, $user)) {
1781         // site admins always have the capability, but the enrolment above blocks
1782         return false;
1783     }
1785     return true;
1788 /**
1789  * Returns true if the user is able to access the course.
1790  *
1791  * This function is in no way, shape, or form a substitute for require_login.
1792  * It should only be used in circumstances where it is not possible to call require_login
1793  * such as the navigation.
1794  *
1795  * This function checks many of the methods of access to a course such as the view
1796  * capability, enrollments, and guest access. It also makes use of the cache
1797  * generated by require_login for guest access.
1798  *
1799  * The flags within the $USER object that are used here should NEVER be used outside
1800  * of this function can_access_course and require_login. Doing so WILL break future
1801  * versions.
1802  *
1803  * @param stdClass $course record
1804  * @param stdClass|int|null $user user record or id, current user if null
1805  * @param string $withcapability Check for this capability as well.
1806  * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
1807  * @return boolean Returns true if the user is able to access the course
1808  */
1809 function can_access_course(stdClass $course, $user = null, $withcapability = '', $onlyactive = false) {
1810     global $DB, $USER;
1812     // this function originally accepted $coursecontext parameter
1813     if ($course instanceof context) {
1814         if ($course instanceof context_course) {
1815             debugging('deprecated context parameter, please use $course record');
1816             $coursecontext = $course;
1817             $course = $DB->get_record('course', array('id'=>$coursecontext->instanceid));
1818         } else {
1819             debugging('Invalid context parameter, please use $course record');
1820             return false;
1821         }
1822     } else {
1823         $coursecontext = context_course::instance($course->id);
1824     }
1826     if (!isset($USER->id)) {
1827         // should never happen
1828         $USER->id = 0;
1829         debugging('Course access check being performed on a user with no ID.', DEBUG_DEVELOPER);
1830     }
1832     // make sure there is a user specified
1833     if ($user === null) {
1834         $userid = $USER->id;
1835     } else {
1836         $userid = is_object($user) ? $user->id : $user;
1837     }
1838     unset($user);
1840     if ($withcapability and !has_capability($withcapability, $coursecontext, $userid)) {
1841         return false;
1842     }
1844     if ($userid == $USER->id) {
1845         if (!empty($USER->access['rsw'][$coursecontext->path])) {
1846             // the fact that somebody switched role means they can access the course no matter to what role they switched
1847             return true;
1848         }
1849     }
1851     if (!$course->visible and !has_capability('moodle/course:viewhiddencourses', $coursecontext, $userid)) {
1852         return false;
1853     }
1855     if (is_viewing($coursecontext, $userid)) {
1856         return true;
1857     }
1859     if ($userid != $USER->id) {
1860         // for performance reasons we do not verify temporary guest access for other users, sorry...
1861         return is_enrolled($coursecontext, $userid, '', $onlyactive);
1862     }
1864     // === from here we deal only with $USER ===
1866     $coursecontext->reload_if_dirty();
1868     if (isset($USER->enrol['enrolled'][$course->id])) {
1869         if ($USER->enrol['enrolled'][$course->id] > time()) {
1870             return true;
1871         }
1872     }
1873     if (isset($USER->enrol['tempguest'][$course->id])) {
1874         if ($USER->enrol['tempguest'][$course->id] > time()) {
1875             return true;
1876         }
1877     }
1879     if (is_enrolled($coursecontext, $USER, '', $onlyactive)) {
1880         return true;
1881     }
1883     // if not enrolled try to gain temporary guest access
1884     $instances = $DB->get_records('enrol', array('courseid'=>$course->id, 'status'=>ENROL_INSTANCE_ENABLED), 'sortorder, id ASC');
1885     $enrols = enrol_get_plugins(true);
1886     foreach($instances as $instance) {
1887         if (!isset($enrols[$instance->enrol])) {
1888             continue;
1889         }
1890         // Get a duration for the guest access, a timestamp in the future, 0 (always) or false.
1891         $until = $enrols[$instance->enrol]->try_guestaccess($instance);
1892         if ($until !== false and $until > time()) {
1893             $USER->enrol['tempguest'][$course->id] = $until;
1894             return true;
1895         }
1896     }
1897     if (isset($USER->enrol['tempguest'][$course->id])) {
1898         unset($USER->enrol['tempguest'][$course->id]);
1899         remove_temp_course_roles($coursecontext);
1900     }
1902     return false;
1905 /**
1906  * Loads the capability definitions for the component (from file).
1907  *
1908  * Loads the capability definitions for the component (from file). If no
1909  * capabilities are defined for the component, we simply return an empty array.
1910  *
1911  * @access private
1912  * @param string $component full plugin name, examples: 'moodle', 'mod_forum'
1913  * @return array array of capabilities
1914  */
1915 function load_capability_def($component) {
1916     $defpath = core_component::get_component_directory($component).'/db/access.php';
1918     $capabilities = array();
1919     if (file_exists($defpath)) {
1920         require($defpath);
1921         if (!empty(${$component.'_capabilities'})) {
1922             // BC capability array name
1923             // since 2.0 we prefer $capabilities instead - it is easier to use and matches db/* files
1924             debugging('componentname_capabilities array is deprecated, please use $capabilities array only in access.php files');
1925             $capabilities = ${$component.'_capabilities'};
1926         }
1927     }
1929     return $capabilities;
1932 /**
1933  * Gets the capabilities that have been cached in the database for this component.
1934  *
1935  * @access private
1936  * @param string $component - examples: 'moodle', 'mod_forum'
1937  * @return array array of capabilities
1938  */
1939 function get_cached_capabilities($component = 'moodle') {
1940     global $DB;
1941     $caps = get_all_capabilities();
1942     $componentcaps = array();
1943     foreach ($caps as $cap) {
1944         if ($cap['component'] == $component) {
1945             $componentcaps[] = (object) $cap;
1946         }
1947     }
1948     return $componentcaps;
1951 /**
1952  * Returns default capabilities for given role archetype.
1953  *
1954  * @param string $archetype role archetype
1955  * @return array
1956  */
1957 function get_default_capabilities($archetype) {
1958     global $DB;
1960     if (!$archetype) {
1961         return array();
1962     }
1964     $alldefs = array();
1965     $defaults = array();
1966     $components = array();
1967     $allcaps = get_all_capabilities();
1969     foreach ($allcaps as $cap) {
1970         if (!in_array($cap['component'], $components)) {
1971             $components[] = $cap['component'];
1972             $alldefs = array_merge($alldefs, load_capability_def($cap['component']));
1973         }
1974     }
1975     foreach($alldefs as $name=>$def) {
1976         // Use array 'archetypes if available. Only if not specified, use 'legacy'.
1977         if (isset($def['archetypes'])) {
1978             if (isset($def['archetypes'][$archetype])) {
1979                 $defaults[$name] = $def['archetypes'][$archetype];
1980             }
1981         // 'legacy' is for backward compatibility with 1.9 access.php
1982         } else {
1983             if (isset($def['legacy'][$archetype])) {
1984                 $defaults[$name] = $def['legacy'][$archetype];
1985             }
1986         }
1987     }
1989     return $defaults;
1992 /**
1993  * Return default roles that can be assigned, overridden or switched
1994  * by give role archetype.
1995  *
1996  * @param string $type  assign|override|switch|view
1997  * @param string $archetype
1998  * @return array of role ids
1999  */
2000 function get_default_role_archetype_allows($type, $archetype) {
2001     global $DB;
2003     if (empty($archetype)) {
2004         return array();
2005     }
2007     $roles = $DB->get_records('role');
2008     $archetypemap = array();
2009     foreach ($roles as $role) {
2010         if ($role->archetype) {
2011             $archetypemap[$role->archetype][$role->id] = $role->id;
2012         }
2013     }
2015     $defaults = array(
2016         'assign' => array(
2017             'manager'        => array('manager', 'coursecreator', 'editingteacher', 'teacher', 'student'),
2018             'coursecreator'  => array(),
2019             'editingteacher' => array('teacher', 'student'),
2020             'teacher'        => array(),
2021             'student'        => array(),
2022             'guest'          => array(),
2023             'user'           => array(),
2024             'frontpage'      => array(),
2025         ),
2026         'override' => array(
2027             'manager'        => array('manager', 'coursecreator', 'editingteacher', 'teacher', 'student', 'guest', 'user', 'frontpage'),
2028             'coursecreator'  => array(),
2029             'editingteacher' => array('teacher', 'student', 'guest'),
2030             'teacher'        => array(),
2031             'student'        => array(),
2032             'guest'          => array(),
2033             'user'           => array(),
2034             'frontpage'      => array(),
2035         ),
2036         'switch' => array(
2037             'manager'        => array('editingteacher', 'teacher', 'student', 'guest'),
2038             'coursecreator'  => array(),
2039             'editingteacher' => array('teacher', 'student', 'guest'),
2040             'teacher'        => array('student', 'guest'),
2041             'student'        => array(),
2042             'guest'          => array(),
2043             'user'           => array(),
2044             'frontpage'      => array(),
2045         ),
2046         'view' => array(
2047             'manager'        => array('manager', 'coursecreator', 'editingteacher', 'teacher', 'student', 'guest', 'user', 'frontpage'),
2048             'coursecreator'  => array('coursecreator', 'editingteacher', 'teacher', 'student'),
2049             'editingteacher' => array('coursecreator', 'editingteacher', 'teacher', 'student'),
2050             'teacher'        => array('coursecreator', 'editingteacher', 'teacher', 'student'),
2051             'student'        => array('coursecreator', 'editingteacher', 'teacher', 'student'),
2052             'guest'          => array(),
2053             'user'           => array(),
2054             'frontpage'      => array(),
2055         ),
2056     );
2058     if (!isset($defaults[$type][$archetype])) {
2059         debugging("Unknown type '$type'' or archetype '$archetype''");
2060         return array();
2061     }
2063     $return = array();
2064     foreach ($defaults[$type][$archetype] as $at) {
2065         if (isset($archetypemap[$at])) {
2066             foreach ($archetypemap[$at] as $roleid) {
2067                 $return[$roleid] = $roleid;
2068             }
2069         }
2070     }
2072     return $return;
2075 /**
2076  * Reset role capabilities to default according to selected role archetype.
2077  * If no archetype selected, removes all capabilities.
2078  *
2079  * This applies to capabilities that are assigned to the role (that you could
2080  * edit in the 'define roles' interface), and not to any capability overrides
2081  * in different locations.
2082  *
2083  * @param int $roleid ID of role to reset capabilities for
2084  */
2085 function reset_role_capabilities($roleid) {
2086     global $DB;
2088     $role = $DB->get_record('role', array('id'=>$roleid), '*', MUST_EXIST);
2089     $defaultcaps = get_default_capabilities($role->archetype);
2091     $systemcontext = context_system::instance();
2093     $DB->delete_records('role_capabilities',
2094             array('roleid' => $roleid, 'contextid' => $systemcontext->id));
2096     foreach($defaultcaps as $cap=>$permission) {
2097         assign_capability($cap, $permission, $roleid, $systemcontext->id);
2098     }
2100     // Reset any cache of this role, including MUC.
2101     accesslib_clear_role_cache($roleid);
2103     // Mark the system context dirty.
2104     context_system::instance()->mark_dirty();
2107 /**
2108  * Updates the capabilities table with the component capability definitions.
2109  * If no parameters are given, the function updates the core moodle
2110  * capabilities.
2111  *
2112  * Note that the absence of the db/access.php capabilities definition file
2113  * will cause any stored capabilities for the component to be removed from
2114  * the database.
2115  *
2116  * @access private
2117  * @param string $component examples: 'moodle', 'mod/forum', 'block/quiz_results'
2118  * @return boolean true if success, exception in case of any problems
2119  */
2120 function update_capabilities($component = 'moodle') {
2121     global $DB, $OUTPUT;
2123     $storedcaps = array();
2125     $filecaps = load_capability_def($component);
2126     foreach($filecaps as $capname=>$unused) {
2127         if (!preg_match('|^[a-z]+/[a-z_0-9]+:[a-z_0-9]+$|', $capname)) {
2128             debugging("Coding problem: Invalid capability name '$capname', use 'clonepermissionsfrom' field for migration.");
2129         }
2130     }
2132     // It is possible somebody directly modified the DB (according to accesslib_test anyway).
2133     // So ensure our updating is based on fresh data.
2134     cache::make('core', 'capabilities')->delete('core_capabilities');
2136     $cachedcaps = get_cached_capabilities($component);
2137     if ($cachedcaps) {
2138         foreach ($cachedcaps as $cachedcap) {
2139             array_push($storedcaps, $cachedcap->name);
2140             // update risk bitmasks and context levels in existing capabilities if needed
2141             if (array_key_exists($cachedcap->name, $filecaps)) {
2142                 if (!array_key_exists('riskbitmask', $filecaps[$cachedcap->name])) {
2143                     $filecaps[$cachedcap->name]['riskbitmask'] = 0; // no risk if not specified
2144                 }
2145                 if ($cachedcap->captype != $filecaps[$cachedcap->name]['captype']) {
2146                     $updatecap = new stdClass();
2147                     $updatecap->id = $cachedcap->id;
2148                     $updatecap->captype = $filecaps[$cachedcap->name]['captype'];
2149                     $DB->update_record('capabilities', $updatecap);
2150                 }
2151                 if ($cachedcap->riskbitmask != $filecaps[$cachedcap->name]['riskbitmask']) {
2152                     $updatecap = new stdClass();
2153                     $updatecap->id = $cachedcap->id;
2154                     $updatecap->riskbitmask = $filecaps[$cachedcap->name]['riskbitmask'];
2155                     $DB->update_record('capabilities', $updatecap);
2156                 }
2158                 if (!array_key_exists('contextlevel', $filecaps[$cachedcap->name])) {
2159                     $filecaps[$cachedcap->name]['contextlevel'] = 0; // no context level defined
2160                 }
2161                 if ($cachedcap->contextlevel != $filecaps[$cachedcap->name]['contextlevel']) {
2162                     $updatecap = new stdClass();
2163                     $updatecap->id = $cachedcap->id;
2164                     $updatecap->contextlevel = $filecaps[$cachedcap->name]['contextlevel'];
2165                     $DB->update_record('capabilities', $updatecap);
2166                 }
2167             }
2168         }
2169     }
2171     // Flush the cached again, as we have changed DB.
2172     cache::make('core', 'capabilities')->delete('core_capabilities');
2174     // Are there new capabilities in the file definition?
2175     $newcaps = array();
2177     foreach ($filecaps as $filecap => $def) {
2178         if (!$storedcaps ||
2179                 ($storedcaps && in_array($filecap, $storedcaps) === false)) {
2180             if (!array_key_exists('riskbitmask', $def)) {
2181                 $def['riskbitmask'] = 0; // no risk if not specified
2182             }
2183             $newcaps[$filecap] = $def;
2184         }
2185     }
2186     // Add new capabilities to the stored definition.
2187     $existingcaps = $DB->get_records_menu('capabilities', array(), 'id', 'id, name');
2188     foreach ($newcaps as $capname => $capdef) {
2189         $capability = new stdClass();
2190         $capability->name         = $capname;
2191         $capability->captype      = $capdef['captype'];
2192         $capability->contextlevel = $capdef['contextlevel'];
2193         $capability->component    = $component;
2194         $capability->riskbitmask  = $capdef['riskbitmask'];
2196         $DB->insert_record('capabilities', $capability, false);
2198         if (isset($capdef['clonepermissionsfrom']) && in_array($capdef['clonepermissionsfrom'], $existingcaps)){
2199             if ($rolecapabilities = $DB->get_records('role_capabilities', array('capability'=>$capdef['clonepermissionsfrom']))){
2200                 foreach ($rolecapabilities as $rolecapability){
2201                     //assign_capability will update rather than insert if capability exists
2202                     if (!assign_capability($capname, $rolecapability->permission,
2203                                             $rolecapability->roleid, $rolecapability->contextid, true)){
2204                          echo $OUTPUT->notification('Could not clone capabilities for '.$capname);
2205                     }
2206                 }
2207             }
2208         // we ignore archetype key if we have cloned permissions
2209         } else if (isset($capdef['archetypes']) && is_array($capdef['archetypes'])) {
2210             assign_legacy_capabilities($capname, $capdef['archetypes']);
2211         // 'legacy' is for backward compatibility with 1.9 access.php
2212         } else if (isset($capdef['legacy']) && is_array($capdef['legacy'])) {
2213             assign_legacy_capabilities($capname, $capdef['legacy']);
2214         }
2215     }
2216     // Are there any capabilities that have been removed from the file
2217     // definition that we need to delete from the stored capabilities and
2218     // role assignments?
2219     capabilities_cleanup($component, $filecaps);
2221     // reset static caches
2222     accesslib_clear_all_caches(false);
2224     // Flush the cached again, as we have changed DB.
2225     cache::make('core', 'capabilities')->delete('core_capabilities');
2227     return true;
2230 /**
2231  * Deletes cached capabilities that are no longer needed by the component.
2232  * Also unassigns these capabilities from any roles that have them.
2233  * NOTE: this function is called from lib/db/upgrade.php
2234  *
2235  * @access private
2236  * @param string $component examples: 'moodle', 'mod_forum', 'block_quiz_results'
2237  * @param array $newcapdef array of the new capability definitions that will be
2238  *                     compared with the cached capabilities
2239  * @return int number of deprecated capabilities that have been removed
2240  */
2241 function capabilities_cleanup($component, $newcapdef = null) {
2242     global $DB;
2244     $removedcount = 0;
2246     if ($cachedcaps = get_cached_capabilities($component)) {
2247         foreach ($cachedcaps as $cachedcap) {
2248             if (empty($newcapdef) ||
2249                         array_key_exists($cachedcap->name, $newcapdef) === false) {
2251                 // Remove from capabilities cache.
2252                 $DB->delete_records('capabilities', array('name'=>$cachedcap->name));
2253                 $removedcount++;
2254                 // Delete from roles.
2255                 if ($roles = get_roles_with_capability($cachedcap->name)) {
2256                     foreach($roles as $role) {
2257                         if (!unassign_capability($cachedcap->name, $role->id)) {
2258                             print_error('cannotunassigncap', 'error', '', (object)array('cap'=>$cachedcap->name, 'role'=>$role->name));
2259                         }
2260                     }
2261                 }
2262             } // End if.
2263         }
2264     }
2265     if ($removedcount) {
2266         cache::make('core', 'capabilities')->delete('core_capabilities');
2267     }
2268     return $removedcount;
2271 /**
2272  * Returns an array of all the known types of risk
2273  * The array keys can be used, for example as CSS class names, or in calls to
2274  * print_risk_icon. The values are the corresponding RISK_ constants.
2275  *
2276  * @return array all the known types of risk.
2277  */
2278 function get_all_risks() {
2279     return array(
2280         'riskmanagetrust' => RISK_MANAGETRUST,
2281         'riskconfig'      => RISK_CONFIG,
2282         'riskxss'         => RISK_XSS,
2283         'riskpersonal'    => RISK_PERSONAL,
2284         'riskspam'        => RISK_SPAM,
2285         'riskdataloss'    => RISK_DATALOSS,
2286     );
2289 /**
2290  * Return a link to moodle docs for a given capability name
2291  *
2292  * @param stdClass $capability a capability - a row from the mdl_capabilities table.
2293  * @return string the human-readable capability name as a link to Moodle Docs.
2294  */
2295 function get_capability_docs_link($capability) {
2296     $url = get_docs_url('Capabilities/' . $capability->name);
2297     return '<a onclick="this.target=\'docspopup\'" href="' . $url . '">' . get_capability_string($capability->name) . '</a>';
2300 /**
2301  * This function pulls out all the resolved capabilities (overrides and
2302  * defaults) of a role used in capability overrides in contexts at a given
2303  * context.
2304  *
2305  * @param int $roleid
2306  * @param context $context
2307  * @param string $cap capability, optional, defaults to ''
2308  * @return array Array of capabilities
2309  */
2310 function role_context_capabilities($roleid, context $context, $cap = '') {
2311     global $DB;
2313     $contexts = $context->get_parent_context_ids(true);
2314     $contexts = '('.implode(',', $contexts).')';
2316     $params = array($roleid);
2318     if ($cap) {
2319         $search = " AND rc.capability = ? ";
2320         $params[] = $cap;
2321     } else {
2322         $search = '';
2323     }
2325     $sql = "SELECT rc.*
2326               FROM {role_capabilities} rc, {context} c
2327              WHERE rc.contextid in $contexts
2328                    AND rc.roleid = ?
2329                    AND rc.contextid = c.id $search
2330           ORDER BY c.contextlevel DESC, rc.capability DESC";
2332     $capabilities = array();
2334     if ($records = $DB->get_records_sql($sql, $params)) {
2335         // We are traversing via reverse order.
2336         foreach ($records as $record) {
2337             // If not set yet (i.e. inherit or not set at all), or currently we have a prohibit
2338             if (!isset($capabilities[$record->capability]) || $record->permission<-500) {
2339                 $capabilities[$record->capability] = $record->permission;
2340             }
2341         }
2342     }
2343     return $capabilities;
2346 /**
2347  * Constructs array with contextids as first parameter and context paths,
2348  * in both cases bottom top including self.
2349  *
2350  * @access private
2351  * @param context $context
2352  * @return array
2353  */
2354 function get_context_info_list(context $context) {
2355     $contextids = explode('/', ltrim($context->path, '/'));
2356     $contextpaths = array();
2357     $contextids2 = $contextids;
2358     while ($contextids2) {
2359         $contextpaths[] = '/' . implode('/', $contextids2);
2360         array_pop($contextids2);
2361     }
2362     return array($contextids, $contextpaths);
2365 /**
2366  * Check if context is the front page context or a context inside it
2367  *
2368  * Returns true if this context is the front page context, or a context inside it,
2369  * otherwise false.
2370  *
2371  * @param context $context a context object.
2372  * @return bool
2373  */
2374 function is_inside_frontpage(context $context) {
2375     $frontpagecontext = context_course::instance(SITEID);
2376     return strpos($context->path . '/', $frontpagecontext->path . '/') === 0;
2379 /**
2380  * Returns capability information (cached)
2381  *
2382  * @param string $capabilityname
2383  * @return stdClass or null if capability not found
2384  */
2385 function get_capability_info($capabilityname) {
2386     global $ACCESSLIB_PRIVATE, $DB; // one request per page only
2388     $caps = get_all_capabilities();
2390     if (!isset($caps[$capabilityname])) {
2391         return null;
2392     }
2394     return (object) $caps[$capabilityname];
2397 /**
2398  * Returns all capabilitiy records, preferably from MUC and not database.
2399  *
2400  * @return array All capability records indexed by capability name
2401  */
2402 function get_all_capabilities() {
2403     global $DB;
2404     $cache = cache::make('core', 'capabilities');
2405     if (!$allcaps = $cache->get('core_capabilities')) {
2406         $rs = $DB->get_recordset('capabilities');
2407         $allcaps = array();
2408         foreach ($rs as $capability) {
2409             $capability->riskbitmask = (int) $capability->riskbitmask;
2410             $allcaps[$capability->name] = (array) $capability;
2411         }
2412         $rs->close();
2413         $cache->set('core_capabilities', $allcaps);
2414     }
2415     return $allcaps;
2418 /**
2419  * Returns the human-readable, translated version of the capability.
2420  * Basically a big switch statement.
2421  *
2422  * @param string $capabilityname e.g. mod/choice:readresponses
2423  * @return string
2424  */
2425 function get_capability_string($capabilityname) {
2427     // Typical capability name is 'plugintype/pluginname:capabilityname'
2428     list($type, $name, $capname) = preg_split('|[/:]|', $capabilityname);
2430     if ($type === 'moodle') {
2431         $component = 'core_role';
2432     } else if ($type === 'quizreport') {
2433         //ugly hack!!
2434         $component = 'quiz_'.$name;
2435     } else {
2436         $component = $type.'_'.$name;
2437     }
2439     $stringname = $name.':'.$capname;
2441     if ($component === 'core_role' or get_string_manager()->string_exists($stringname, $component)) {
2442         return get_string($stringname, $component);
2443     }
2445     $dir = core_component::get_component_directory($component);
2446     if (!file_exists($dir)) {
2447         // plugin broken or does not exist, do not bother with printing of debug message
2448         return $capabilityname.' ???';
2449     }
2451     // something is wrong in plugin, better print debug
2452     return get_string($stringname, $component);
2455 /**
2456  * This gets the mod/block/course/core etc strings.
2457  *
2458  * @param string $component
2459  * @param int $contextlevel
2460  * @return string|bool String is success, false if failed
2461  */
2462 function get_component_string($component, $contextlevel) {
2464     if ($component === 'moodle' or $component === 'core') {
2465         switch ($contextlevel) {
2466             // TODO MDL-46123: this should probably use context level names instead
2467             case CONTEXT_SYSTEM:    return get_string('coresystem');
2468             case CONTEXT_USER:      return get_string('users');
2469             case CONTEXT_COURSECAT: return get_string('categories');
2470             case CONTEXT_COURSE:    return get_string('course');
2471             case CONTEXT_MODULE:    return get_string('activities');
2472             case CONTEXT_BLOCK:     return get_string('block');
2473             default:                print_error('unknowncontext');
2474         }
2475     }
2477     list($type, $name) = core_component::normalize_component($component);
2478     $dir = core_component::get_plugin_directory($type, $name);
2479     if (!file_exists($dir)) {
2480         // plugin not installed, bad luck, there is no way to find the name
2481         return $component.' ???';
2482     }
2484     switch ($type) {
2485         // TODO MDL-46123: this is really hacky and should be improved.
2486         case 'quiz':         return get_string($name.':componentname', $component);// insane hack!!!
2487         case 'repository':   return get_string('repository', 'repository').': '.get_string('pluginname', $component);
2488         case 'gradeimport':  return get_string('gradeimport', 'grades').': '.get_string('pluginname', $component);
2489         case 'gradeexport':  return get_string('gradeexport', 'grades').': '.get_string('pluginname', $component);
2490         case 'gradereport':  return get_string('gradereport', 'grades').': '.get_string('pluginname', $component);
2491         case 'webservice':   return get_string('webservice', 'webservice').': '.get_string('pluginname', $component);
2492         case 'block':        return get_string('block').': '.get_string('pluginname', basename($component));
2493         case 'mod':
2494             if (get_string_manager()->string_exists('pluginname', $component)) {
2495                 return get_string('activity').': '.get_string('pluginname', $component);
2496             } else {
2497                 return get_string('activity').': '.get_string('modulename', $component);
2498             }
2499         default: return get_string('pluginname', $component);
2500     }
2503 /**
2504  * Gets the list of roles assigned to this context and up (parents)
2505  * from the aggregation of:
2506  * a) the list of roles that are visible on user profile page and participants page (profileroles setting) and;
2507  * b) if applicable, those roles that are assigned in the context.
2508  *
2509  * @param context $context
2510  * @return array
2511  */
2512 function get_profile_roles(context $context) {
2513     global $CFG, $DB;
2514     // If the current user can assign roles, then they can see all roles on the profile and participants page,
2515     // provided the roles are assigned to at least 1 user in the context. If not, only the policy-defined roles.
2516     if (has_capability('moodle/role:assign', $context)) {
2517         $rolesinscope = array_keys(get_all_roles($context));
2518     } else {
2519         $rolesinscope = empty($CFG->profileroles) ? [] : array_map('trim', explode(',', $CFG->profileroles));
2520     }
2522     if (empty($rolesinscope)) {
2523         return [];
2524     }
2526     list($rallowed, $params) = $DB->get_in_or_equal($rolesinscope, SQL_PARAMS_NAMED, 'a');
2527     list($contextlist, $cparams) = $DB->get_in_or_equal($context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'p');
2528     $params = array_merge($params, $cparams);
2530     if ($coursecontext = $context->get_course_context(false)) {
2531         $params['coursecontext'] = $coursecontext->id;
2532     } else {
2533         $params['coursecontext'] = 0;
2534     }
2536     $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder, rn.name AS coursealias
2537               FROM {role_assignments} ra, {role} r
2538          LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
2539              WHERE r.id = ra.roleid
2540                    AND ra.contextid $contextlist
2541                    AND r.id $rallowed
2542           ORDER BY r.sortorder ASC";
2544     return $DB->get_records_sql($sql, $params);
2547 /**
2548  * Gets the list of roles assigned to this context and up (parents)
2549  *
2550  * @param context $context
2551  * @param boolean $includeparents, false means without parents.
2552  * @return array
2553  */
2554 function get_roles_used_in_context(context $context, $includeparents = true) {
2555     global $DB;
2557     if ($includeparents === true) {
2558         list($contextlist, $params) = $DB->get_in_or_equal($context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'cl');
2559     } else {
2560         list($contextlist, $params) = $DB->get_in_or_equal($context->id, SQL_PARAMS_NAMED, 'cl');
2561     }
2563     if ($coursecontext = $context->get_course_context(false)) {
2564         $params['coursecontext'] = $coursecontext->id;
2565     } else {
2566         $params['coursecontext'] = 0;
2567     }
2569     $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder, rn.name AS coursealias
2570               FROM {role_assignments} ra, {role} r
2571          LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
2572              WHERE r.id = ra.roleid
2573                    AND ra.contextid $contextlist
2574           ORDER BY r.sortorder ASC";
2576     return $DB->get_records_sql($sql, $params);
2579 /**
2580  * This function is used to print roles column in user profile page.
2581  * It is using the CFG->profileroles to limit the list to only interesting roles.
2582  * (The permission tab has full details of user role assignments.)
2583  *
2584  * @param int $userid
2585  * @param int $courseid
2586  * @return string
2587  */
2588 function get_user_roles_in_course($userid, $courseid) {
2589     global $CFG, $DB;
2590     if ($courseid == SITEID) {
2591         $context = context_system::instance();
2592     } else {
2593         $context = context_course::instance($courseid);
2594     }
2595     // If the current user can assign roles, then they can see all roles on the profile and participants page,
2596     // provided the roles are assigned to at least 1 user in the context. If not, only the policy-defined roles.
2597     if (has_capability('moodle/role:assign', $context)) {
2598         $rolesinscope = array_keys(get_all_roles($context));
2599     } else {
2600         $rolesinscope = empty($CFG->profileroles) ? [] : array_map('trim', explode(',', $CFG->profileroles));
2601     }
2602     if (empty($rolesinscope)) {
2603         return '';
2604     }
2606     list($rallowed, $params) = $DB->get_in_or_equal($rolesinscope, SQL_PARAMS_NAMED, 'a');
2607     list($contextlist, $cparams) = $DB->get_in_or_equal($context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'p');
2608     $params = array_merge($params, $cparams);
2610     if ($coursecontext = $context->get_course_context(false)) {
2611         $params['coursecontext'] = $coursecontext->id;
2612     } else {
2613         $params['coursecontext'] = 0;
2614     }
2616     $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder, rn.name AS coursealias
2617               FROM {role_assignments} ra, {role} r
2618          LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
2619              WHERE r.id = ra.roleid
2620                    AND ra.contextid $contextlist
2621                    AND r.id $rallowed
2622                    AND ra.userid = :userid
2623           ORDER BY r.sortorder ASC";
2624     $params['userid'] = $userid;
2626     $rolestring = '';
2628     if ($roles = $DB->get_records_sql($sql, $params)) {
2629         $viewableroles = get_viewable_roles($context, $userid);
2631         $rolenames = array();
2632         foreach ($roles as $roleid => $unused) {
2633             if (isset($viewableroles[$roleid])) {
2634                 $url = new moodle_url('/user/index.php', ['contextid' => $context->id, 'roleid' => $roleid]);
2635                 $rolenames[] = '<a href="' . $url . '">' . $viewableroles[$roleid] . '</a>';
2636             }
2637         }
2638         $rolestring = implode(',', $rolenames);
2639     }
2641     return $rolestring;
2644 /**
2645  * Checks if a user can assign users to a particular role in this context
2646  *
2647  * @param context $context
2648  * @param int $targetroleid - the id of the role you want to assign users to
2649  * @return boolean
2650  */
2651 function user_can_assign(context $context, $targetroleid) {
2652     global $DB;
2654     // First check to see if the user is a site administrator.
2655     if (is_siteadmin()) {
2656         return true;
2657     }
2659     // Check if user has override capability.
2660     // If not return false.
2661     if (!has_capability('moodle/role:assign', $context)) {
2662         return false;
2663     }
2664     // pull out all active roles of this user from this context(or above)
2665     if ($userroles = get_user_roles($context)) {
2666         foreach ($userroles as $userrole) {
2667             // if any in the role_allow_override table, then it's ok
2668             if ($DB->get_record('role_allow_assign', array('roleid'=>$userrole->roleid, 'allowassign'=>$targetroleid))) {
2669                 return true;
2670             }
2671         }
2672     }
2674     return false;
2677 /**
2678  * Returns all site roles in correct sort order.
2679  *
2680  * Note: this method does not localise role names or descriptions,
2681  *       use role_get_names() if you need role names.
2682  *
2683  * @param context $context optional context for course role name aliases
2684  * @return array of role records with optional coursealias property
2685  */
2686 function get_all_roles(context $context = null) {
2687     global $DB;
2689     if (!$context or !$coursecontext = $context->get_course_context(false)) {
2690         $coursecontext = null;
2691     }
2693     if ($coursecontext) {
2694         $sql = "SELECT r.*, rn.name AS coursealias
2695                   FROM {role} r
2696              LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
2697               ORDER BY r.sortorder ASC";
2698         return $DB->get_records_sql($sql, array('coursecontext'=>$coursecontext->id));
2700     } else {
2701         return $DB->get_records('role', array(), 'sortorder ASC');
2702     }
2705 /**
2706  * Returns roles of a specified archetype
2707  *
2708  * @param string $archetype
2709  * @return array of full role records
2710  */
2711 function get_archetype_roles($archetype) {
2712     global $DB;
2713     return $DB->get_records('role', array('archetype'=>$archetype), 'sortorder ASC');
2716 /**
2717  * Gets all the user roles assigned in this context, or higher contexts for a list of users.
2718  *
2719  * If you try using the combination $userids = [], $checkparentcontexts = true then this is likely
2720  * to cause an out-of-memory error on large Moodle sites, so this combination is deprecated and
2721  * outputs a warning, even though it is the default.
2722  *
2723  * @param context $context
2724  * @param array $userids. An empty list means fetch all role assignments for the context.
2725  * @param bool $checkparentcontexts defaults to true
2726  * @param string $order defaults to 'c.contextlevel DESC, r.sortorder ASC'
2727  * @return array
2728  */
2729 function get_users_roles(context $context, $userids = [], $checkparentcontexts = true, $order = 'c.contextlevel DESC, r.sortorder ASC') {
2730     global $DB;
2732     if (!$userids && $checkparentcontexts) {
2733         debugging('Please do not call get_users_roles() with $checkparentcontexts = true ' .
2734                 'and $userids array not set. This combination causes large Moodle sites ' .
2735                 'with lots of site-wide role assignemnts to run out of memory.', DEBUG_DEVELOPER);
2736     }
2738     if ($checkparentcontexts) {
2739         $contextids = $context->get_parent_context_ids();
2740     } else {
2741         $contextids = array();
2742     }
2743     $contextids[] = $context->id;
2745     list($contextids, $params) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, 'con');
2747     // If userids was passed as an empty array, we fetch all role assignments for the course.
2748     if (empty($userids)) {
2749         $useridlist = ' IS NOT NULL ';
2750         $uparams = [];
2751     } else {
2752         list($useridlist, $uparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED, 'uids');
2753     }
2755     $sql = "SELECT ra.*, r.name, r.shortname, ra.userid
2756               FROM {role_assignments} ra, {role} r, {context} c
2757              WHERE ra.userid $useridlist
2758                    AND ra.roleid = r.id
2759                    AND ra.contextid = c.id
2760                    AND ra.contextid $contextids
2761           ORDER BY $order";
2763     $all = $DB->get_records_sql($sql , array_merge($params, $uparams));
2765     // Return results grouped by userid.
2766     $result = [];
2767     foreach ($all as $id => $record) {
2768         if (!isset($result[$record->userid])) {
2769             $result[$record->userid] = [];
2770         }
2771         $result[$record->userid][$record->id] = $record;
2772     }
2774     // Make sure all requested users are included in the result, even if they had no role assignments.
2775     foreach ($userids as $id) {
2776         if (!isset($result[$id])) {
2777             $result[$id] = [];
2778         }
2779     }
2781     return $result;
2785 /**
2786  * Gets all the user roles assigned in this context, or higher contexts
2787  * this is mainly used when checking if a user can assign a role, or overriding a role
2788  * i.e. we need to know what this user holds, in order to verify against allow_assign and
2789  * allow_override tables
2790  *
2791  * @param context $context
2792  * @param int $userid
2793  * @param bool $checkparentcontexts defaults to true
2794  * @param string $order defaults to 'c.contextlevel DESC, r.sortorder ASC'
2795  * @return array
2796  */
2797 function get_user_roles(context $context, $userid = 0, $checkparentcontexts = true, $order = 'c.contextlevel DESC, r.sortorder ASC') {
2798     global $USER, $DB;
2800     if (empty($userid)) {
2801         if (empty($USER->id)) {
2802             return array();
2803         }
2804         $userid = $USER->id;
2805     }
2807     if ($checkparentcontexts) {
2808         $contextids = $context->get_parent_context_ids();
2809     } else {
2810         $contextids = array();
2811     }
2812     $contextids[] = $context->id;
2814     list($contextids, $params) = $DB->get_in_or_equal($contextids, SQL_PARAMS_QM);
2816     array_unshift($params, $userid);
2818     $sql = "SELECT ra.*, r.name, r.shortname
2819               FROM {role_assignments} ra, {role} r, {context} c
2820              WHERE ra.userid = ?
2821                    AND ra.roleid = r.id
2822                    AND ra.contextid = c.id
2823                    AND ra.contextid $contextids
2824           ORDER BY $order";
2826     return $DB->get_records_sql($sql ,$params);
2829 /**
2830  * Like get_user_roles, but adds in the authenticated user role, and the front
2831  * page roles, if applicable.
2832  *
2833  * @param context $context the context.
2834  * @param int $userid optional. Defaults to $USER->id
2835  * @return array of objects with fields ->userid, ->contextid and ->roleid.
2836  */
2837 function get_user_roles_with_special(context $context, $userid = 0) {
2838     global $CFG, $USER;
2840     if (empty($userid)) {
2841         if (empty($USER->id)) {
2842             return array();
2843         }
2844         $userid = $USER->id;
2845     }
2847     $ras = get_user_roles($context, $userid);
2849     // Add front-page role if relevant.
2850     $defaultfrontpageroleid = isset($CFG->defaultfrontpageroleid) ? $CFG->defaultfrontpageroleid : 0;
2851     $isfrontpage = ($context->contextlevel == CONTEXT_COURSE && $context->instanceid == SITEID) ||
2852             is_inside_frontpage($context);
2853     if ($defaultfrontpageroleid && $isfrontpage) {
2854         $frontpagecontext = context_course::instance(SITEID);
2855         $ra = new stdClass();
2856         $ra->userid = $userid;
2857         $ra->contextid = $frontpagecontext->id;
2858         $ra->roleid = $defaultfrontpageroleid;
2859         $ras[] = $ra;
2860     }
2862     // Add authenticated user role if relevant.
2863     $defaultuserroleid      = isset($CFG->defaultuserroleid) ? $CFG->defaultuserroleid : 0;
2864     if ($defaultuserroleid && !isguestuser($userid)) {
2865         $systemcontext = context_system::instance();
2866         $ra = new stdClass();
2867         $ra->userid = $userid;
2868         $ra->contextid = $systemcontext->id;
2869         $ra->roleid = $defaultuserroleid;
2870         $ras[] = $ra;
2871     }
2873     return $ras;
2876 /**
2877  * Creates a record in the role_allow_override table
2878  *
2879  * @param int $fromroleid source roleid
2880  * @param int $targetroleid target roleid
2881  * @return void
2882  */
2883 function core_role_set_override_allowed($fromroleid, $targetroleid) {
2884     global $DB;
2886     $record = new stdClass();
2887     $record->roleid        = $fromroleid;
2888     $record->allowoverride = $targetroleid;
2889     $DB->insert_record('role_allow_override', $record);
2892 /**
2893  * Creates a record in the role_allow_assign table
2894  *
2895  * @param int $fromroleid source roleid
2896  * @param int $targetroleid target roleid
2897  * @return void
2898  */
2899 function core_role_set_assign_allowed($fromroleid, $targetroleid) {
2900     global $DB;
2902     $record = new stdClass();
2903     $record->roleid      = $fromroleid;
2904     $record->allowassign = $targetroleid;
2905     $DB->insert_record('role_allow_assign', $record);
2908 /**
2909  * Creates a record in the role_allow_switch table
2910  *
2911  * @param int $fromroleid source roleid
2912  * @param int $targetroleid target roleid
2913  * @return void
2914  */
2915 function core_role_set_switch_allowed($fromroleid, $targetroleid) {
2916     global $DB;
2918     $record = new stdClass();
2919     $record->roleid      = $fromroleid;
2920     $record->allowswitch = $targetroleid;
2921     $DB->insert_record('role_allow_switch', $record);
2924 /**
2925  * Creates a record in the role_allow_view table
2926  *
2927  * @param int $fromroleid source roleid
2928  * @param int $targetroleid target roleid
2929  * @return void
2930  */
2931 function core_role_set_view_allowed($fromroleid, $targetroleid) {
2932     global $DB;
2934     $record = new stdClass();
2935     $record->roleid      = $fromroleid;
2936     $record->allowview = $targetroleid;
2937     $DB->insert_record('role_allow_view', $record);
2940 /**
2941  * Gets a list of roles that this user can assign in this context
2942  *
2943  * @param context $context the context.
2944  * @param int $rolenamedisplay the type of role name to display. One of the
2945  *      ROLENAME_X constants. Default ROLENAME_ALIAS.
2946  * @param bool $withusercounts if true, count the number of users with each role.
2947  * @param integer|object $user A user id or object. By default (null) checks the permissions of the current user.
2948  * @return array if $withusercounts is false, then an array $roleid => $rolename.
2949  *      if $withusercounts is true, returns a list of three arrays,
2950  *      $rolenames, $rolecounts, and $nameswithcounts.
2951  */
2952 function get_assignable_roles(context $context, $rolenamedisplay = ROLENAME_ALIAS, $withusercounts = false, $user = null) {
2953     global $USER, $DB;
2955     // make sure there is a real user specified
2956     if ($user === null) {
2957         $userid = isset($USER->id) ? $USER->id : 0;
2958     } else {
2959         $userid = is_object($user) ? $user->id : $user;
2960     }
2962     if (!has_capability('moodle/role:assign', $context, $userid)) {
2963         if ($withusercounts) {
2964             return array(array(), array(), array());
2965         } else {
2966             return array();
2967         }
2968     }
2970     $params = array();
2971     $extrafields = '';
2973     if ($withusercounts) {
2974         $extrafields = ', (SELECT count(u.id)
2975                              FROM {role_assignments} cra JOIN {user} u ON cra.userid = u.id
2976                             WHERE cra.roleid = r.id AND cra.contextid = :conid AND u.deleted = 0
2977                           ) AS usercount';
2978         $params['conid'] = $context->id;
2979     }
2981     if (is_siteadmin($userid)) {
2982         // show all roles allowed in this context to admins
2983         $assignrestriction = "";
2984     } else {
2985         $parents = $context->get_parent_context_ids(true);
2986         $contexts = implode(',' , $parents);
2987         $assignrestriction = "JOIN (SELECT DISTINCT raa.allowassign AS id
2988                                       FROM {role_allow_assign} raa
2989                                       JOIN {role_assignments} ra ON ra.roleid = raa.roleid
2990                                      WHERE ra.userid = :userid AND ra.contextid IN ($contexts)
2991                                    ) ar ON ar.id = r.id";
2992         $params['userid'] = $userid;
2993     }
2994     $params['contextlevel'] = $context->contextlevel;
2996     if ($coursecontext = $context->get_course_context(false)) {
2997         $params['coursecontext'] = $coursecontext->id;
2998     } else {
2999         $params['coursecontext'] = 0; // no course aliases
3000         $coursecontext = null;
3001     }
3002     $sql = "SELECT r.id, r.name, r.shortname, rn.name AS coursealias $extrafields
3003               FROM {role} r
3004               $assignrestriction
3005               JOIN {role_context_levels} rcl ON (rcl.contextlevel = :contextlevel AND r.id = rcl.roleid)
3006          LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
3007           ORDER BY r.sortorder ASC";
3008     $roles = $DB->get_records_sql($sql, $params);
3010     $rolenames = role_fix_names($roles, $coursecontext, $rolenamedisplay, true);
3012     if (!$withusercounts) {
3013         return $rolenames;
3014     }
3016     $rolecounts = array();
3017     $nameswithcounts = array();
3018     foreach ($roles as $role) {
3019         $nameswithcounts[$role->id] = $rolenames[$role->id] . ' (' . $roles[$role->id]->usercount . ')';
3020         $rolecounts[$role->id] = $roles[$role->id]->usercount;
3021     }
3022     return array($rolenames, $rolecounts, $nameswithcounts);
3025 /**
3026  * Gets a list of roles that this user can switch to in a context
3027  *
3028  * Gets a list of roles that this user can switch to in a context, for the switchrole menu.
3029  * This function just process the contents of the role_allow_switch table. You also need to
3030  * test the moodle/role:switchroles to see if the user is allowed to switch in the first place.
3031  *
3032  * @param context $context a context.
3033  * @return array an array $roleid => $rolename.
3034  */
3035 function get_switchable_roles(context $context) {
3036     global $USER, $DB;
3038     // You can't switch roles without this capability.
3039     if (!has_capability('moodle/role:switchroles', $context)) {
3040         return [];
3041     }
3043     $params = array();
3044     $extrajoins = '';
3045     $extrawhere = '';
3046     if (!is_siteadmin()) {
3047         // Admins are allowed to switch to any role with.
3048         // Others are subject to the additional constraint that the switch-to role must be allowed by
3049         // 'role_allow_switch' for some role they have assigned in this context or any parent.
3050         $parents = $context->get_parent_context_ids(true);
3051         $contexts = implode(',' , $parents);
3053         $extrajoins = "JOIN {role_allow_switch} ras ON ras.allowswitch = rc.roleid
3054         JOIN {role_assignments} ra ON ra.roleid = ras.roleid";
3055         $extrawhere = "WHERE ra.userid = :userid AND ra.contextid IN ($contexts)";
3056         $params['userid'] = $USER->id;
3057     }
3059     if ($coursecontext = $context->get_course_context(false)) {
3060         $params['coursecontext'] = $coursecontext->id;
3061     } else {
3062         $params['coursecontext'] = 0; // no course aliases
3063         $coursecontext = null;
3064     }
3066     $query = "
3067         SELECT r.id, r.name, r.shortname, rn.name AS coursealias
3068           FROM (SELECT DISTINCT rc.roleid
3069                   FROM {role_capabilities} rc
3070                   $extrajoins
3071                   $extrawhere) idlist
3072           JOIN {role} r ON r.id = idlist.roleid
3073      LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
3074       ORDER BY r.sortorder";
3075     $roles = $DB->get_records_sql($query, $params);
3077     return role_fix_names($roles, $context, ROLENAME_ALIAS, true);
3080 /**
3081  * Gets a list of roles that this user can view in a context
3082  *
3083  * @param context $context a context.
3084  * @param int $userid id of user.
3085  * @return array an array $roleid => $rolename.
3086  */
3087 function get_viewable_roles(context $context, $userid = null) {
3088     global $USER, $DB;
3090     if ($userid == null) {
3091         $userid = $USER->id;
3092     }
3094     $params = array();
3095     $extrajoins = '';
3096     $extrawhere = '';
3097     if (!is_siteadmin()) {
3098         // Admins are allowed to view any role.
3099         // Others are subject to the additional constraint that the view role must be allowed by
3100         // 'role_allow_view' for some role they have assigned in this context or any parent.
3101         $contexts = $context->get_parent_context_ids(true);
3102         list($insql, $inparams) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED);
3104         $extrajoins = "JOIN {role_allow_view} ras ON ras.allowview = r.id
3105                        JOIN {role_assignments} ra ON ra.roleid = ras.roleid";
3106         $extrawhere = "WHERE ra.userid = :userid AND ra.contextid $insql";
3108         $params += $inparams;
3109         $params['userid'] = $userid;
3110     }
3112     if ($coursecontext = $context->get_course_context(false)) {
3113         $params['coursecontext'] = $coursecontext->id;
3114     } else {
3115         $params['coursecontext'] = 0; // No course aliases.
3116         $coursecontext = null;
3117     }
3119     $query = "
3120         SELECT r.id, r.name, r.shortname, rn.name AS coursealias, r.sortorder
3121           FROM {role} r
3122           $extrajoins
3123      LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
3124           $extrawhere
3125       GROUP BY r.id, r.name, r.shortname, rn.name, r.sortorder
3126       ORDER BY r.sortorder";
3127     $roles = $DB->get_records_sql($query, $params);
3129     return role_fix_names($roles, $context, ROLENAME_ALIAS, true);
3132 /**
3133  * Gets a list of roles that this user can override in this context.
3134  *
3135  * @param context $context the context.
3136  * @param int $rolenamedisplay the type of role name to display. One of the
3137  *      ROLENAME_X constants. Default ROLENAME_ALIAS.
3138  * @param bool $withcounts if true, count the number of overrides that are set for each role.
3139  * @return array if $withcounts is false, then an array $roleid => $rolename.
3140  *      if $withusercounts is true, returns a list of three arrays,
3141  *      $rolenames, $rolecounts, and $nameswithcounts.
3142  */
3143 function get_overridable_roles(context $context, $rolenamedisplay = ROLENAME_ALIAS, $withcounts = false) {
3144     global $USER, $DB;
3146     if (!has_any_capability(array('moodle/role:safeoverride', 'moodle/role:override'), $context)) {
3147         if ($withcounts) {
3148             return array(array(), array(), array());
3149         } else {
3150             return array();
3151         }
3152     }
3154     $parents = $context->get_parent_context_ids(true);
3155     $contexts = implode(',' , $parents);
3157     $params = array();
3158     $extrafields = '';
3160     $params['userid'] = $USER->id;
3161     if ($withcounts) {
3162         $extrafields = ', (SELECT COUNT(rc.id) FROM {role_capabilities} rc
3163                 WHERE rc.roleid = ro.id AND rc.contextid = :conid) AS overridecount';
3164         $params['conid'] = $context->id;
3165     }
3167     if ($coursecontext = $context->get_course_context(false)) {
3168         $params['coursecontext'] = $coursecontext->id;
3169     } else {
3170         $params['coursecontext'] = 0; // no course aliases
3171         $coursecontext = null;
3172     }
3174     if (is_siteadmin()) {
3175         // show all roles to admins
3176         $roles = $DB->get_records_sql("
3177             SELECT ro.id, ro.name, ro.shortname, rn.name AS coursealias $extrafields
3178               FROM {role} ro
3179          LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = ro.id)
3180           ORDER BY ro.sortorder ASC", $params);
3182     } else {
3183         $roles = $DB->get_records_sql("
3184             SELECT ro.id, ro.name, ro.shortname, rn.name AS coursealias $extrafields
3185               FROM {role} ro
3186               JOIN (SELECT DISTINCT r.id
3187                       FROM {role} r
3188                       JOIN {role_allow_override} rao ON r.id = rao.allowoverride
3189                       JOIN {role_assignments} ra ON rao.roleid = ra.roleid
3190                      WHERE ra.userid = :userid AND ra.contextid IN ($contexts)
3191                    ) inline_view ON ro.id = inline_view.id
3192          LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = ro.id)
3193           ORDER BY ro.sortorder ASC", $params);
3194     }
3196     $rolenames = role_fix_names($roles, $context, $rolenamedisplay, true);
3198     if (!$withcounts) {
3199         return $rolenames;
3200     }
3202     $rolecounts = array();
3203     $nameswithcounts = array();
3204     foreach ($roles as $role) {
3205         $nameswithcounts[$role->id] = $rolenames[$role->id] . ' (' . $roles[$role->id]->overridecount . ')';
3206         $rolecounts[$role->id] = $roles[$role->id]->overridecount;
3207     }
3208     return array($rolenames, $rolecounts, $nameswithcounts);
3211 /**
3212  * Create a role menu suitable for default role selection in enrol plugins.
3213  *
3214  * @package    core_enrol
3215  *
3216  * @param context $context
3217  * @param int $addroleid current or default role - always added to list
3218  * @return array roleid=>localised role name
3219  */
3220 function get_default_enrol_roles(context $context, $addroleid = null) {
3221     global $DB;
3223     $params = array('contextlevel'=>CONTEXT_COURSE);
3225     if ($coursecontext = $context->get_course_context(false)) {
3226         $params['coursecontext'] = $coursecontext->id;
3227     } else {
3228         $params['coursecontext'] = 0; // no course names
3229         $coursecontext = null;
3230     }
3232     if ($addroleid) {
3233         $addrole = "OR r.id = :addroleid";
3234         $params['addroleid'] = $addroleid;
3235     } else {
3236         $addrole = "";
3237     }
3239     $sql = "SELECT r.id, r.name, r.shortname, rn.name AS coursealias
3240               FROM {role} r
3241          LEFT JOIN {role_context_levels} rcl ON (rcl.roleid = r.id AND rcl.contextlevel = :contextlevel)
3242          LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
3243              WHERE rcl.id IS NOT NULL $addrole
3244           ORDER BY sortorder DESC";
3246     $roles = $DB->get_records_sql($sql, $params);
3248     return role_fix_names($roles, $context, ROLENAME_BOTH, true);
3251 /**
3252  * Return context levels where this role is assignable.
3253  *
3254  * @param integer $roleid the id of a role.
3255  * @return array list of the context levels at which this role may be assigned.
3256  */
3257 function get_role_contextlevels($roleid) {
3258     global $DB;
3259     return $DB->get_records_menu('role_context_levels', array('roleid' => $roleid),
3260             'contextlevel', 'id,contextlevel');
3263 /**
3264  * Return roles suitable for assignment at the specified context level.
3265  *
3266  * NOTE: this function name looks like a typo, should be probably get_roles_for_contextlevel()
3267  *
3268  * @param integer $contextlevel a contextlevel.
3269  * @return array list of role ids that are assignable at this context level.
3270  */
3271 function get_roles_for_contextlevels($contextlevel) {
3272     global $DB;
3273     return $DB->get_records_menu('role_context_levels', array('contextlevel' => $contextlevel),
3274             '', 'id,roleid');
3277 /**
3278  * Returns default context levels where roles can be assigned.
3279  *
3280  * @param string $rolearchetype one of the role archetypes - that is, one of the keys
3281  *      from the array returned by get_role_archetypes();
3282  * @return array list of the context levels at which this type of role may be assigned by default.
3283  */
3284 function get_default_contextlevels($rolearchetype) {
3285     static $defaults = array(
3286         'manager'        => array(CONTEXT_SYSTEM, CONTEXT_COURSECAT, CONTEXT_COURSE),
3287         'coursecreator'  => array(CONTEXT_SYSTEM, CONTEXT_COURSECAT),
3288         'editingteacher' => array(CONTEXT_COURSE, CONTEXT_MODULE),
3289         'teacher'        => array(CONTEXT_COURSE, CONTEXT_MODULE),
3290         'student'        => array(CONTEXT_COURSE, CONTEXT_MODULE),
3291         'guest'          => array(),
3292         'user'           => array(),
3293         'frontpage'      => array());
3295     if (isset($defaults[$rolearchetype])) {
3296         return $defaults[$rolearchetype];
3297     } else {
3298         return array();
3299     }
3302 /**
3303  * Set the context levels at which a particular role can be assigned.
3304  * Throws exceptions in case of error.
3305  *
3306  * @param integer $roleid the id of a role.
3307  * @param array $contextlevels the context levels at which this role should be assignable,
3308  *      duplicate levels are removed.
3309  * @return void
3310  */
3311 function set_role_contextlevels($roleid, array $contextlevels) {
3312     global $DB;
3313     $DB->delete_records('role_context_levels', array('roleid' => $roleid));
3314     $rcl = new stdClass();
3315     $rcl->roleid = $roleid;
3316     $contextlevels = array_unique($contextlevels);
3317     foreach ($contextlevels as $level) {
3318         $rcl->contextlevel = $level;
3319         $DB->insert_record('role_context_levels', $rcl, false, true);
3320     }
3323 /**
3324  * Who has this capability in this context?
3325  *
3326  * This can be a very expensive call - use sparingly and keep
3327  * the results if you are going to need them again soon.
3328  *
3329  * Note if $fields is empty this function attempts to get u.*
3330  * which can get rather large - and has a serious perf impact
3331  * on some DBs.
3332  *
3333  * @param context $context
3334  * @param string|array $capability - capability name(s)
3335  * @param string $fields - fields to be pulled. The user table is aliased to 'u'. u.id MUST be included.
3336  * @param string $sort - the sort order. Default is lastaccess time.
3337  * @param mixed $limitfrom - number of records to skip (offset)
3338  * @param mixed $limitnum - number of records to fetch
3339  * @param string|array $groups - single group or array of groups - only return
3340  *               users who are in one of these group(s).
3341  * @param string|array $exceptions - list of users to exclude, comma separated or array
3342  * @param bool $doanything_ignored not used any more, admin accounts are never returned
3343  * @param bool $view_ignored - use get_enrolled_sql() instead
3344  * @param bool $useviewallgroups if $groups is set the return users who
3345  *               have capability both $capability and moodle/site:accessallgroups
3346  *               in this context, as well as users who have $capability and who are
3347  *               in $groups.
3348  * @return array of user records
3349  */
3350 function get_users_by_capability(context $context, $capability, $fields = '', $sort = '', $limitfrom = '', $limitnum = '',
3351                                  $groups = '', $exceptions = '', $doanything_ignored = null, $view_ignored = null, $useviewallgroups = false) {
3352     global $CFG, $DB;
3354     $defaultuserroleid      = isset($CFG->defaultuserroleid) ? $CFG->defaultuserroleid : 0;
3355     $defaultfrontpageroleid = isset($CFG->defaultfrontpageroleid) ? $CFG->defaultfrontpageroleid : 0;
3357     $ctxids = trim($context->path, '/');
3358     $ctxids = str_replace('/', ',', $ctxids);
3360     // Context is the frontpage
3361     $iscoursepage = false; // coursepage other than fp
3362     $isfrontpage = false;
3363     if ($context->contextlevel == CONTEXT_COURSE) {
3364         if ($context->instanceid == SITEID) {
3365             $isfrontpage = true;
3366         } else {
3367             $iscoursepage = true;
3368         }
3369     }
3370     $isfrontpage = ($isfrontpage || is_inside_frontpage($context));
3372     $caps = (array)$capability;
3374     // construct list of context paths bottom-->top
3375     list($contextids, $paths) = get_context_info_list($context);
3377     // we need to find out all roles that have these capabilities either in definition or in overrides
3378     $defs = array();
3379     list($incontexts, $params) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, 'con');
3380     list($incaps, $params2) = $DB->get_in_or_equal($caps, SQL_PARAMS_NAMED, 'cap');
3381     $params = array_merge($params, $params2);
3382     $sql = "SELECT rc.id, rc.roleid, rc.permission, rc.capability, ctx.path
3383               FROM {role_capabilities} rc
3384               JOIN {context} ctx on rc.contextid = ctx.id
3385              WHERE rc.contextid $incontexts AND rc.capability $incaps";
3387     $rcs = $DB->get_records_sql($sql, $params);
3388     foreach ($rcs as $rc) {
3389         $defs[$rc->capability][$rc->path][$rc->roleid] = $rc->permission;
3390     }
3392     // go through the permissions bottom-->top direction to evaluate the current permission,
3393     // first one wins (prohibit is an exception that always wins)
3394     $access = array();
3395     foreach ($caps as $cap) {
3396         foreach ($paths as $path) {
3397             if (empty($defs[$cap][$path])) {
3398                 continue;
3399             }
3400             foreach($defs[$cap][$path] as $roleid => $perm) {
3401                 if ($perm == CAP_PROHIBIT) {
3402                     $access[$cap][$roleid] = CAP_PROHIBIT;
3403                     continue;
3404                 }
3405                 if (!isset($access[$cap][$roleid])) {
3406                     $access[$cap][$roleid] = (int)$perm;
3407                 }
3408             }
3409         }
3410     }
3412     // make lists of roles that are needed and prohibited in this context
3413     $needed = array(); // one of these is enough
3414     $prohibited = array(); // must not have any of these
3415     foreach ($caps as $cap) {
3416         if (empty($access[$cap])) {
3417             continue;
3418         }
3419         foreach ($access[$cap] as $roleid => $perm) {
3420             if ($perm == CAP_PROHIBIT) {
3421                 unset($needed[$cap][$roleid]);
3422                 $prohibited[$cap][$roleid] = true;
3423             } else if ($perm == CAP_ALLOW and empty($prohibited[$cap][$roleid])) {
3424                 $needed[$cap][$roleid] = true;
3425             }
3426         }
3427         if (empty($needed[$cap]) or !empty($prohibited[$cap][$defaultuserroleid])) {
3428             // easy, nobody has the permission
3429             unset($needed[$cap]);
3430             unset($prohibited[$cap]);
3431         } else if ($isfrontpage and !empty($prohibited[$cap][$defaultfrontpageroleid])) {
3432             // everybody is disqualified on the frontpage
3433             unset($needed[$cap]);
3434             unset($prohibited[$cap]);
3435         }
3436         if (empty($prohibited[$cap])) {
3437             unset($prohibited[$cap]);
3438         }
3439     }
3441     if (empty($needed)) {
3442         // there can not be anybody if no roles match this request
3443         return array();
3444     }
3446     if (empty($prohibited)) {
3447         // we can compact the needed roles
3448         $n = array();
3449         foreach ($needed as $cap) {
3450             foreach ($cap as $roleid=>$unused) {
3451                 $n[$roleid] = true;
3452             }
3453         }
3454         $needed = array('any'=>$n);
3455         unset($n);
3456     }
3458     // ***** Set up default fields ******
3459     if (empty($fields)) {
3460         if ($iscoursepage) {
3461             $fields = 'u.*, ul.timeaccess AS lastaccess';
3462         } else {
3463             $fields = 'u.*';
3464         }
3465     } else {
3466         if ($CFG->debugdeveloper && strpos($fields, 'u.*') === false && strpos($fields, 'u.id') === false) {
3467             debugging('u.id must be included in the list of fields passed to get_users_by_capability().', DEBUG_DEVELOPER);
3468         }
3469     }
3471     // Set up default sort
3472     if (empty($sort)) { // default to course lastaccess or just lastaccess
3473         if ($iscoursepage) {
3474             $sort = 'ul.timeaccess';
3475         } else {
3476             $sort = 'u.lastaccess';
3477         }
3478     }
3480     // Prepare query clauses
3481     $wherecond = array();
3482     $params    = array();
3483     $joins     = array();
3485     // User lastaccess JOIN
3486     if ((strpos($sort, 'ul.timeaccess') === false) and (strpos($fields, 'ul.timeaccess') === false)) {
3487          // user_lastaccess is not required MDL-13810
3488     } else {
3489         if ($iscoursepage) {
3490             $joins[] = "LEFT OUTER JOIN {user_lastaccess} ul ON (ul.userid = u.id AND ul.courseid = {$context->instanceid})";
3491         } else {
3492             throw new coding_exception('Invalid sort in get_users_by_capability(), ul.timeaccess allowed only for course contexts.');
3493         }
3494     }
3496     // We never return deleted users or guest account.
3497     $wherecond[] = "u.deleted = 0 AND u.id <> :guestid";
3498     $params['guestid'] = $CFG->siteguest;
3500     // Groups
3501     if ($groups) {
3502         $groups = (array)$groups;
3503         list($grouptest, $grpparams) = $DB->get_in_or_equal($groups, SQL_PARAMS_NAMED, 'grp');
3504         $grouptest = "u.id IN (SELECT userid FROM {groups_members} gm WHERE gm.groupid $grouptest)";
3505         $params = array_merge($params, $grpparams);
3507         if ($useviewallgroups) {
3508             $viewallgroupsusers = get_users_by_capability($context, 'moodle/site:accessallgroups', 'u.id, u.id', '', '', '', '', $exceptions);
3509             if (!empty($viewallgroupsusers)) {
3510                 $wherecond[] =  "($grouptest OR u.id IN (" . implode(',', array_keys($viewallgroupsusers)) . '))';
3511             } else {
3512                 $wherecond[] =  "($grouptest)";
3513             }
3514         } else {
3515             $wherecond[] =  "($grouptest)";
3516         }
3517     }
3519     // User exceptions
3520     if (!empty($exceptions)) {
3521         $exceptions = (array)$exceptions;
3522         list($exsql, $exparams) = $DB->get_in_or_equal($exceptions, SQL_PARAMS_NAMED, 'exc', false);
3523         $params = array_merge($params, $exparams);
3524         $wherecond[] = "u.id $exsql";
3525     }
3527     // now add the needed and prohibited roles conditions as joins
3528     if (!empty($needed['any'])) {
3529         // simple case - there are no prohibits involved
3530         if (!empty($needed['any'][$defaultuserroleid]) or ($isfrontpage and !empty($needed['any'][$defaultfrontpageroleid]))) {
3531             // everybody
3532         } else {
3533             $joins[] = "JOIN (SELECT DISTINCT userid
3534                                 FROM {role_assignments}
3535                                WHERE contextid IN ($ctxids)
3536                                      AND roleid IN (".implode(',', array_keys($needed['any'])) .")
3537                              ) ra ON ra.userid = u.id";
3538         }
3539     } else {
3540         $unions = array();
3541         $everybody = false;
3542         foreach ($needed as $cap=>$unused) {
3543             if (empty($prohibited[$cap])) {
3544                 if (!empty($needed[$cap][$defaultuserroleid]) or ($isfrontpage and !empty($needed[$cap][$defaultfrontpageroleid]))) {
3545                     $everybody = true;
3546                     break;
3547                 } else {
3548                     $unions[] = "SELECT userid
3549                                    FROM {role_assignments}
3550                                   WHERE contextid IN ($ctxids)
3551                                         AND roleid IN (".implode(',', array_keys($needed[$cap])) .")";
3552                 }
3553             } else {
3554                 if (!empty($prohibited[$cap][$defaultuserroleid]) or ($isfrontpage and !empty($prohibited[$cap][$defaultfrontpageroleid]))) {
3555                     // nobody can have this cap because it is prevented in default roles
3556                     continue;
3558                 } else if (!empty($needed[$cap][$defaultuserroleid]) or ($isfrontpage and !empty($needed[$cap][$defaultfrontpageroleid]))) {
3559                     // everybody except the prohibitted - hiding does not matter
3560                     $unions[] = "SELECT id AS userid
3561                                    FROM {user}
3562                                   WHERE id NOT IN (SELECT userid
3563                                                      FROM {role_assignments}
3564                                                     WHERE contextid IN ($ctxids)
3565                                                           AND roleid IN (".implode(',', array_keys($prohibited[$cap])) ."))";
3567                 } else {
3568                     $unions[] = "SELECT userid
3569                                    FROM {role_assignments}
3570                                   WHERE contextid IN ($ctxids) AND roleid IN (".implode(',', array_keys($needed[$cap])) .")
3571                                         AND userid NOT IN (
3572                                             SELECT userid
3573                                               FROM {role_assignments}
3574                                              WHERE contextid IN ($ctxids)
3575                                                     AND roleid IN (" . implode(',', array_keys($prohibited[$cap])) . ")
3576                                                         )";
3577                 }
3578             }
3579         }
3580         if (!$everybody) {
3581             if ($unions) {
3582                 $joins[] = "JOIN (SELECT DISTINCT userid FROM ( ".implode(' UNION ', $unions)." ) us) ra ON ra.userid = u.id";
3583             } else {
3584                 // only prohibits found - nobody can be matched
3585                 $wherecond[] = "1 = 2";
3586             }
3587         }
3588     }
3590     // Collect WHERE conditions and needed joins
3591     $where = implode(' AND ', $wherecond);
3592     if ($where !== '') {
3593         $where = 'WHERE ' . $where;
3594     }
3595     $joins = implode("\n", $joins);
3597     // Ok, let's get the users!
3598     $sql = "SELECT $fields
3599               FROM {user} u
3600             $joins
3601             $where
3602           ORDER BY $sort";
3604     return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
3607 /**
3608  * Re-sort a users array based on a sorting policy
3609  *
3610  * Will re-sort a $users results array (from get_users_by_capability(), usually)
3611  * based on a sorting policy. This is to support the odd practice of
3612  * sorting teachers by 'authority', where authority was "lowest id of the role
3613  * assignment".
3614  *
3615  * Will execute 1 database query. Only suitable for small numbers of users, as it
3616  * uses an u.id IN() clause.
3617  *
3618  * Notes about the sorting criteria.
3619  *
3620  * As a default, we cannot rely on role.sortorder because then
3621  * admins/coursecreators will always win. That is why the sane
3622  * rule "is locality matters most", with sortorder as 2nd
3623  * consideration.
3624  *
3625  * If you want role.sortorder, use the 'sortorder' policy, and
3626  * name explicitly what roles you want to cover. It's probably
3627  * a good idea to see what roles have the capabilities you want
3628  * (array_diff() them against roiles that have 'can-do-anything'
3629  * to weed out admin-ish roles. Or fetch a list of roles from
3630  * variables like $CFG->coursecontact .
3631  *
3632  * @param array $users Users array, keyed on userid
3633  * @param context $context
3634  * @param array $roles ids of the roles to include, optional
3635  * @param string $sortpolicy defaults to locality, more about
3636  * @return array sorted copy of the array
3637  */
3638 function sort_by_roleassignment_authority($users, context $context, $roles = array(), $sortpolicy = 'locality') {
3639     global $DB;
3641     $userswhere = ' ra.userid IN (' . implode(',',array_keys($users)) . ')';
3642     $contextwhere = 'AND ra.contextid IN ('.str_replace('/', ',',substr($context->path, 1)).')';
3643     if (empty($roles)) {
3644         $roleswhere = '';
3645     } else {
3646         $roleswhere = ' AND ra.roleid IN ('.implode(',',$roles).')';
3647     }
3649     $sql = "SELECT ra.userid
3650               FROM {role_assignments} ra
3651               JOIN {role} r
3652                    ON ra.roleid=r.id
3653               JOIN {context} ctx
3654                    ON ra.contextid=ctx.id
3655              WHERE $userswhere
3656                    $contextwhere
3657                    $roleswhere";
3659     // Default 'locality' policy -- read PHPDoc notes
3660     // about sort policies...
3661     $orderby = 'ORDER BY '
3662                     .'ctx.depth DESC, '  /* locality wins */
3663                     .'r.sortorder ASC, ' /* rolesorting 2nd criteria */
3664                     .'ra.id';            /* role assignment order tie-breaker */
3665     if ($sortpolicy === 'sortorder') {