MDL-43130 access: fix user counting when retrieving assignable roles.
[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  * "ra" means role assignment
66  * "rdef" means role definition
67  *
68  * <b>accessdata</b>
69  *
70  * Access control data is held in the "accessdata" array
71  * which - for the logged-in user, will be in $USER->access
72  *
73  * For other users can be generated and passed around (but may also be cached
74  * against userid in $ACCESSLIB_PRIVATE->accessdatabyuser).
75  *
76  * $accessdata is a multidimensional array, holding
77  * role assignments (RAs), role switches and initialization time.
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->dirtyusers       = null;    // Dirty users cache, loaded from DB once per $USER->id
186 $ACCESSLIB_PRIVATE->accessdatabyuser = array(); // Holds the cache of $accessdata structure for users (including $USER)
188 /**
189  * Clears accesslib's private caches. ONLY BE USED BY UNIT TESTS
190  *
191  * This method should ONLY BE USED BY UNIT TESTS. It clears all of
192  * accesslib's private caches. You need to do this before setting up test data,
193  * and also at the end of the tests.
194  *
195  * @access private
196  * @return void
197  */
198 function accesslib_clear_all_caches_for_unit_testing() {
199     global $USER;
200     if (!PHPUNIT_TEST) {
201         throw new coding_exception('You must not call clear_all_caches outside of unit tests.');
202     }
204     accesslib_clear_all_caches(true);
205     accesslib_reset_role_cache();
207     unset($USER->access);
210 /**
211  * Clears accesslib's private caches. ONLY BE USED FROM THIS LIBRARY FILE!
212  *
213  * This reset does not touch global $USER.
214  *
215  * @access private
216  * @param bool $resetcontexts
217  * @return void
218  */
219 function accesslib_clear_all_caches($resetcontexts) {
220     global $ACCESSLIB_PRIVATE;
222     $ACCESSLIB_PRIVATE->dirtycontexts    = null;
223     $ACCESSLIB_PRIVATE->dirtyusers       = null;
224     $ACCESSLIB_PRIVATE->accessdatabyuser = array();
226     if ($resetcontexts) {
227         context_helper::reset_caches();
228     }
231 /**
232  * Full reset of accesslib's private role cache. ONLY TO BE USED FROM THIS LIBRARY FILE!
233  *
234  * This reset does not touch global $USER.
235  *
236  * Note: Only use this when the roles that need a refresh are unknown.
237  *
238  * @see accesslib_clear_role_cache()
239  *
240  * @access private
241  * @return void
242  */
243 function accesslib_reset_role_cache() {
244     global $ACCESSLIB_PRIVATE;
246     $ACCESSLIB_PRIVATE->cacheroledefs = array();
247     $cache = cache::make('core', 'roledefs');
248     $cache->purge();
251 /**
252  * Clears accesslib's private cache of a specific role or roles. ONLY BE USED FROM THIS LIBRARY FILE!
253  *
254  * This reset does not touch global $USER.
255  *
256  * @access private
257  * @param int|array $roles
258  * @return void
259  */
260 function accesslib_clear_role_cache($roles) {
261     global $ACCESSLIB_PRIVATE;
263     if (!is_array($roles)) {
264         $roles = [$roles];
265     }
267     foreach ($roles as $role) {
268         if (isset($ACCESSLIB_PRIVATE->cacheroledefs[$role])) {
269             unset($ACCESSLIB_PRIVATE->cacheroledefs[$role]);
270         }
271     }
273     $cache = cache::make('core', 'roledefs');
274     $cache->delete_many($roles);
277 /**
278  * Role is assigned at system context.
279  *
280  * @access private
281  * @param int $roleid
282  * @return array
283  */
284 function get_role_access($roleid) {
285     $accessdata = get_empty_accessdata();
286     $accessdata['ra']['/'.SYSCONTEXTID] = array((int)$roleid => (int)$roleid);
287     return $accessdata;
290 /**
291  * Fetch raw "site wide" role definitions.
292  * Even MUC static acceleration cache appears a bit slow for this.
293  * Important as can be hit hundreds of times per page.
294  *
295  * @param array $roleids List of role ids to fetch definitions for.
296  * @return array Complete definition for each requested role.
297  */
298 function get_role_definitions(array $roleids) {
299     global $ACCESSLIB_PRIVATE;
301     if (empty($roleids)) {
302         return array();
303     }
305     // Grab all keys we have not yet got in our static cache.
306     if ($uncached = array_diff($roleids, array_keys($ACCESSLIB_PRIVATE->cacheroledefs))) {
307         $cache = cache::make('core', 'roledefs');
308         foreach ($cache->get_many($uncached) as $roleid => $cachedroledef) {
309             if (is_array($cachedroledef)) {
310                 $ACCESSLIB_PRIVATE->cacheroledefs[$roleid] = $cachedroledef;
311             }
312         }
314         // Check we have the remaining keys from the MUC.
315         if ($uncached = array_diff($roleids, array_keys($ACCESSLIB_PRIVATE->cacheroledefs))) {
316             $uncached = get_role_definitions_uncached($uncached);
317             $ACCESSLIB_PRIVATE->cacheroledefs += $uncached;
318             $cache->set_many($uncached);
319         }
320     }
322     // Return just the roles we need.
323     return array_intersect_key($ACCESSLIB_PRIVATE->cacheroledefs, array_flip($roleids));
326 /**
327  * Query raw "site wide" role definitions.
328  *
329  * @param array $roleids List of role ids to fetch definitions for.
330  * @return array Complete definition for each requested role.
331  */
332 function get_role_definitions_uncached(array $roleids) {
333     global $DB;
335     if (empty($roleids)) {
336         return array();
337     }
339     // Create a blank results array: even if a role has no capabilities,
340     // we need to ensure it is included in the results to show we have
341     // loaded all the capabilities that there are.
342     $rdefs = array();
343     foreach ($roleids as $roleid) {
344         $rdefs[$roleid] = array();
345     }
347     // Load all the capabilities for these roles in all contexts.
348     list($sql, $params) = $DB->get_in_or_equal($roleids);
349     $sql = "SELECT ctx.path, rc.roleid, rc.capability, rc.permission
350               FROM {role_capabilities} rc
351               JOIN {context} ctx ON rc.contextid = ctx.id
352               JOIN {capabilities} cap ON rc.capability = cap.name
353              WHERE rc.roleid $sql";
354     $rs = $DB->get_recordset_sql($sql, $params);
356     // Store the capabilities into the expected data structure.
357     foreach ($rs as $rd) {
358         if (!isset($rdefs[$rd->roleid][$rd->path])) {
359             $rdefs[$rd->roleid][$rd->path] = array();
360         }
361         $rdefs[$rd->roleid][$rd->path][$rd->capability] = (int) $rd->permission;
362     }
364     $rs->close();
366     // Sometimes (e.g. get_user_capability_course_helper::get_capability_info_at_each_context)
367     // we process role definitinons in a way that requires we see parent contexts
368     // before child contexts. This sort ensures that works (and is faster than
369     // sorting in the SQL query).
370     foreach ($rdefs as $roleid => $rdef) {
371         ksort($rdefs[$roleid]);
372     }
374     return $rdefs;
377 /**
378  * Get the default guest role, this is used for guest account,
379  * search engine spiders, etc.
380  *
381  * @return stdClass role record
382  */
383 function get_guest_role() {
384     global $CFG, $DB;
386     if (empty($CFG->guestroleid)) {
387         if ($roles = $DB->get_records('role', array('archetype'=>'guest'))) {
388             $guestrole = array_shift($roles);   // Pick the first one
389             set_config('guestroleid', $guestrole->id);
390             return $guestrole;
391         } else {
392             debugging('Can not find any guest role!');
393             return false;
394         }
395     } else {
396         if ($guestrole = $DB->get_record('role', array('id'=>$CFG->guestroleid))) {
397             return $guestrole;
398         } else {
399             // somebody is messing with guest roles, remove incorrect setting and try to find a new one
400             set_config('guestroleid', '');
401             return get_guest_role();
402         }
403     }
406 /**
407  * Check whether a user has a particular capability in a given context.
408  *
409  * For example:
410  *      $context = context_module::instance($cm->id);
411  *      has_capability('mod/forum:replypost', $context)
412  *
413  * By default checks the capabilities of the current user, but you can pass a
414  * different userid. By default will return true for admin users, but you can override that with the fourth argument.
415  *
416  * Guest and not-logged-in users can never get any dangerous capability - that is any write capability
417  * or capabilities with XSS, config or data loss risks.
418  *
419  * @category access
420  *
421  * @param string $capability the name of the capability to check. For example mod/forum:view
422  * @param context $context the context to check the capability in. You normally get this with instance method of a context class.
423  * @param integer|stdClass $user A user id or object. By default (null) checks the permissions of the current user.
424  * @param boolean $doanything If false, ignores effect of admin role assignment
425  * @return boolean true if the user has this capability. Otherwise false.
426  */
427 function has_capability($capability, context $context, $user = null, $doanything = true) {
428     global $USER, $CFG, $SCRIPT, $ACCESSLIB_PRIVATE;
430     if (during_initial_install()) {
431         if ($SCRIPT === "/$CFG->admin/index.php"
432                 or $SCRIPT === "/$CFG->admin/cli/install.php"
433                 or $SCRIPT === "/$CFG->admin/cli/install_database.php"
434                 or (defined('BEHAT_UTIL') and BEHAT_UTIL)
435                 or (defined('PHPUNIT_UTIL') and PHPUNIT_UTIL)) {
436             // we are in an installer - roles can not work yet
437             return true;
438         } else {
439             return false;
440         }
441     }
443     if (strpos($capability, 'moodle/legacy:') === 0) {
444         throw new coding_exception('Legacy capabilities can not be used any more!');
445     }
447     if (!is_bool($doanything)) {
448         throw new coding_exception('Capability parameter "doanything" is wierd, only true or false is allowed. This has to be fixed in code.');
449     }
451     // capability must exist
452     if (!$capinfo = get_capability_info($capability)) {
453         debugging('Capability "'.$capability.'" was not found! This has to be fixed in code.');
454         return false;
455     }
457     if (!isset($USER->id)) {
458         // should never happen
459         $USER->id = 0;
460         debugging('Capability check being performed on a user with no ID.', DEBUG_DEVELOPER);
461     }
463     // make sure there is a real user specified
464     if ($user === null) {
465         $userid = $USER->id;
466     } else {
467         $userid = is_object($user) ? $user->id : $user;
468     }
470     // make sure forcelogin cuts off not-logged-in users if enabled
471     if (!empty($CFG->forcelogin) and $userid == 0) {
472         return false;
473     }
475     // make sure the guest account and not-logged-in users never get any risky caps no matter what the actual settings are.
476     if (($capinfo->captype === 'write') or ($capinfo->riskbitmask & (RISK_XSS | RISK_CONFIG | RISK_DATALOSS))) {
477         if (isguestuser($userid) or $userid == 0) {
478             return false;
479         }
480     }
482     // Check whether context locking is enabled.
483     if (!empty($CFG->contextlocking)) {
484         if ($capinfo->captype === 'write' && $context->locked) {
485             // Context locking applies to any write capability in a locked context.
486             // It does not apply to moodle/site:managecontextlocks - this is to allow context locking to be unlocked.
487             if ($capinfo->name !== 'moodle/site:managecontextlocks') {
488                 // It applies to all users who are not site admins.
489                 // It also applies to site admins when contextlockappliestoadmin is set.
490                 if (!is_siteadmin($userid) || !empty($CFG->contextlockappliestoadmin)) {
491                     return false;
492                 }
493             }
494         }
495     }
497     // somehow make sure the user is not deleted and actually exists
498     if ($userid != 0) {
499         if ($userid == $USER->id and isset($USER->deleted)) {
500             // this prevents one query per page, it is a bit of cheating,
501             // but hopefully session is terminated properly once user is deleted
502             if ($USER->deleted) {
503                 return false;
504             }
505         } else {
506             if (!context_user::instance($userid, IGNORE_MISSING)) {
507                 // no user context == invalid userid
508                 return false;
509             }
510         }
511     }
513     // context path/depth must be valid
514     if (empty($context->path) or $context->depth == 0) {
515         // this should not happen often, each upgrade tries to rebuild the context paths
516         debugging('Context id '.$context->id.' does not have valid path, please use context_helper::build_all_paths()');
517         if (is_siteadmin($userid)) {
518             return true;
519         } else {
520             return false;
521         }
522     }
524     // Find out if user is admin - it is not possible to override the doanything in any way
525     // and it is not possible to switch to admin role either.
526     if ($doanything) {
527         if (is_siteadmin($userid)) {
528             if ($userid != $USER->id) {
529                 return true;
530             }
531             // make sure switchrole is not used in this context
532             if (empty($USER->access['rsw'])) {
533                 return true;
534             }
535             $parts = explode('/', trim($context->path, '/'));
536             $path = '';
537             $switched = false;
538             foreach ($parts as $part) {
539                 $path .= '/' . $part;
540                 if (!empty($USER->access['rsw'][$path])) {
541                     $switched = true;
542                     break;
543                 }
544             }
545             if (!$switched) {
546                 return true;
547             }
548             //ok, admin switched role in this context, let's use normal access control rules
549         }
550     }
552     // Careful check for staleness...
553     $context->reload_if_dirty();
555     if ($USER->id == $userid) {
556         if (!isset($USER->access)) {
557             load_all_capabilities();
558         }
559         $access =& $USER->access;
561     } else {
562         // make sure user accessdata is really loaded
563         get_user_accessdata($userid, true);
564         $access =& $ACCESSLIB_PRIVATE->accessdatabyuser[$userid];
565     }
567     return has_capability_in_accessdata($capability, $context, $access);
570 /**
571  * Check if the user has any one of several capabilities from a list.
572  *
573  * This is just a utility method that calls has_capability in a loop. Try to put
574  * the capabilities that most users are likely to have first in the list for best
575  * performance.
576  *
577  * @category access
578  * @see has_capability()
579  *
580  * @param array $capabilities an array of capability names.
581  * @param context $context the context to check the capability in. You normally get this with instance method of a context class.
582  * @param integer|stdClass $user A user id or object. By default (null) checks the permissions of the current user.
583  * @param boolean $doanything If false, ignore effect of admin role assignment
584  * @return boolean true if the user has any of these capabilities. Otherwise false.
585  */
586 function has_any_capability(array $capabilities, context $context, $user = null, $doanything = true) {
587     foreach ($capabilities as $capability) {
588         if (has_capability($capability, $context, $user, $doanything)) {
589             return true;
590         }
591     }
592     return false;
595 /**
596  * Check if the user has all the capabilities in a list.
597  *
598  * This is just a utility method that calls has_capability in a loop. Try to put
599  * the capabilities that fewest users are likely to have first in the list for best
600  * performance.
601  *
602  * @category access
603  * @see has_capability()
604  *
605  * @param array $capabilities an array of capability names.
606  * @param context $context the context to check the capability in. You normally get this with instance method of a context class.
607  * @param integer|stdClass $user A user id or object. By default (null) checks the permissions of the current user.
608  * @param boolean $doanything If false, ignore effect of admin role assignment
609  * @return boolean true if the user has all of these capabilities. Otherwise false.
610  */
611 function has_all_capabilities(array $capabilities, context $context, $user = null, $doanything = true) {
612     foreach ($capabilities as $capability) {
613         if (!has_capability($capability, $context, $user, $doanything)) {
614             return false;
615         }
616     }
617     return true;
620 /**
621  * Is course creator going to have capability in a new course?
622  *
623  * This is intended to be used in enrolment plugins before or during course creation,
624  * do not use after the course is fully created.
625  *
626  * @category access
627  *
628  * @param string $capability the name of the capability to check.
629  * @param context $context course or category context where is course going to be created
630  * @param integer|stdClass $user A user id or object. By default (null) checks the permissions of the current user.
631  * @return boolean true if the user will have this capability.
632  *
633  * @throws coding_exception if different type of context submitted
634  */
635 function guess_if_creator_will_have_course_capability($capability, context $context, $user = null) {
636     global $CFG;
638     if ($context->contextlevel != CONTEXT_COURSE and $context->contextlevel != CONTEXT_COURSECAT) {
639         throw new coding_exception('Only course or course category context expected');
640     }
642     if (has_capability($capability, $context, $user)) {
643         // User already has the capability, it could be only removed if CAP_PROHIBIT
644         // was involved here, but we ignore that.
645         return true;
646     }
648     if (!has_capability('moodle/course:create', $context, $user)) {
649         return false;
650     }
652     if (!enrol_is_enabled('manual')) {
653         return false;
654     }
656     if (empty($CFG->creatornewroleid)) {
657         return false;
658     }
660     if ($context->contextlevel == CONTEXT_COURSE) {
661         if (is_viewing($context, $user, 'moodle/role:assign') or is_enrolled($context, $user, 'moodle/role:assign')) {
662             return false;
663         }
664     } else {
665         if (has_capability('moodle/course:view', $context, $user) and has_capability('moodle/role:assign', $context, $user)) {
666             return false;
667         }
668     }
670     // Most likely they will be enrolled after the course creation is finished,
671     // does the new role have the required capability?
672     list($neededroles, $forbiddenroles) = get_roles_with_cap_in_context($context, $capability);
673     return isset($neededroles[$CFG->creatornewroleid]);
676 /**
677  * Check if the user is an admin at the site level.
678  *
679  * Please note that use of proper capabilities is always encouraged,
680  * this function is supposed to be used from core or for temporary hacks.
681  *
682  * @category access
683  *
684  * @param  int|stdClass  $user_or_id user id or user object
685  * @return bool true if user is one of the administrators, false otherwise
686  */
687 function is_siteadmin($user_or_id = null) {
688     global $CFG, $USER;
690     if ($user_or_id === null) {
691         $user_or_id = $USER;
692     }
694     if (empty($user_or_id)) {
695         return false;
696     }
697     if (!empty($user_or_id->id)) {
698         $userid = $user_or_id->id;
699     } else {
700         $userid = $user_or_id;
701     }
703     // Because this script is called many times (150+ for course page) with
704     // the same parameters, it is worth doing minor optimisations. This static
705     // cache stores the value for a single userid, saving about 2ms from course
706     // page load time without using significant memory. As the static cache
707     // also includes the value it depends on, this cannot break unit tests.
708     static $knownid, $knownresult, $knownsiteadmins;
709     if ($knownid === $userid && $knownsiteadmins === $CFG->siteadmins) {
710         return $knownresult;
711     }
712     $knownid = $userid;
713     $knownsiteadmins = $CFG->siteadmins;
715     $siteadmins = explode(',', $CFG->siteadmins);
716     $knownresult = in_array($userid, $siteadmins);
717     return $knownresult;
720 /**
721  * Returns true if user has at least one role assign
722  * of 'coursecontact' role (is potentially listed in some course descriptions).
723  *
724  * @param int $userid
725  * @return bool
726  */
727 function has_coursecontact_role($userid) {
728     global $DB, $CFG;
730     if (empty($CFG->coursecontact)) {
731         return false;
732     }
733     $sql = "SELECT 1
734               FROM {role_assignments}
735              WHERE userid = :userid AND roleid IN ($CFG->coursecontact)";
736     return $DB->record_exists_sql($sql, array('userid'=>$userid));
739 /**
740  * Does the user have a capability to do something?
741  *
742  * Walk the accessdata array and return true/false.
743  * Deals with prohibits, role switching, aggregating
744  * capabilities, etc.
745  *
746  * The main feature of here is being FAST and with no
747  * side effects.
748  *
749  * Notes:
750  *
751  * Switch Role merges with default role
752  * ------------------------------------
753  * If you are a teacher in course X, you have at least
754  * teacher-in-X + defaultloggedinuser-sitewide. So in the
755  * course you'll have techer+defaultloggedinuser.
756  * We try to mimic that in switchrole.
757  *
758  * Permission evaluation
759  * ---------------------
760  * Originally there was an extremely complicated way
761  * to determine the user access that dealt with
762  * "locality" or role assignments and role overrides.
763  * Now we simply evaluate access for each role separately
764  * and then verify if user has at least one role with allow
765  * and at the same time no role with prohibit.
766  *
767  * @access private
768  * @param string $capability
769  * @param context $context
770  * @param array $accessdata
771  * @return bool
772  */
773 function has_capability_in_accessdata($capability, context $context, array &$accessdata) {
774     global $CFG;
776     // Build $paths as a list of current + all parent "paths" with order bottom-to-top
777     $path = $context->path;
778     $paths = array($path);
779     while($path = rtrim($path, '0123456789')) {
780         $path = rtrim($path, '/');
781         if ($path === '') {
782             break;
783         }
784         $paths[] = $path;
785     }
787     $roles = array();
788     $switchedrole = false;
790     // Find out if role switched
791     if (!empty($accessdata['rsw'])) {
792         // From the bottom up...
793         foreach ($paths as $path) {
794             if (isset($accessdata['rsw'][$path])) {
795                 // Found a switchrole assignment - check for that role _plus_ the default user role
796                 $roles = array($accessdata['rsw'][$path]=>null, $CFG->defaultuserroleid=>null);
797                 $switchedrole = true;
798                 break;
799             }
800         }
801     }
803     if (!$switchedrole) {
804         // get all users roles in this context and above
805         foreach ($paths as $path) {
806             if (isset($accessdata['ra'][$path])) {
807                 foreach ($accessdata['ra'][$path] as $roleid) {
808                     $roles[$roleid] = null;
809                 }
810             }
811         }
812     }
814     // Now find out what access is given to each role, going bottom-->up direction
815     $rdefs = get_role_definitions(array_keys($roles));
816     $allowed = false;
818     foreach ($roles as $roleid => $ignored) {
819         foreach ($paths as $path) {
820             if (isset($rdefs[$roleid][$path][$capability])) {
821                 $perm = (int)$rdefs[$roleid][$path][$capability];
822                 if ($perm === CAP_PROHIBIT) {
823                     // any CAP_PROHIBIT found means no permission for the user
824                     return false;
825                 }
826                 if (is_null($roles[$roleid])) {
827                     $roles[$roleid] = $perm;
828                 }
829             }
830         }
831         // CAP_ALLOW in any role means the user has a permission, we continue only to detect prohibits
832         $allowed = ($allowed or $roles[$roleid] === CAP_ALLOW);
833     }
835     return $allowed;
838 /**
839  * A convenience function that tests has_capability, and displays an error if
840  * the user does not have that capability.
841  *
842  * NOTE before Moodle 2.0, this function attempted to make an appropriate
843  * require_login call before checking the capability. This is no longer the case.
844  * You must call require_login (or one of its variants) if you want to check the
845  * user is logged in, before you call this function.
846  *
847  * @see has_capability()
848  *
849  * @param string $capability the name of the capability to check. For example mod/forum:view
850  * @param context $context the context to check the capability in. You normally get this with context_xxxx::instance().
851  * @param int $userid A user id. By default (null) checks the permissions of the current user.
852  * @param bool $doanything If false, ignore effect of admin role assignment
853  * @param string $errormessage The error string to to user. Defaults to 'nopermissions'.
854  * @param string $stringfile The language file to load the error string from. Defaults to 'error'.
855  * @return void terminates with an error if the user does not have the given capability.
856  */
857 function require_capability($capability, context $context, $userid = null, $doanything = true,
858                             $errormessage = 'nopermissions', $stringfile = '') {
859     if (!has_capability($capability, $context, $userid, $doanything)) {
860         throw new required_capability_exception($context, $capability, $errormessage, $stringfile);
861     }
864 /**
865  * Return a nested array showing all role assignments for the user.
866  * [ra] => [contextpath][roleid] = roleid
867  *
868  * @access private
869  * @param int $userid - the id of the user
870  * @return array access info array
871  */
872 function get_user_roles_sitewide_accessdata($userid) {
873     global $CFG, $DB;
875     $accessdata = get_empty_accessdata();
877     // start with the default role
878     if (!empty($CFG->defaultuserroleid)) {
879         $syscontext = context_system::instance();
880         $accessdata['ra'][$syscontext->path][(int)$CFG->defaultuserroleid] = (int)$CFG->defaultuserroleid;
881     }
883     // load the "default frontpage role"
884     if (!empty($CFG->defaultfrontpageroleid)) {
885         $frontpagecontext = context_course::instance(get_site()->id);
886         if ($frontpagecontext->path) {
887             $accessdata['ra'][$frontpagecontext->path][(int)$CFG->defaultfrontpageroleid] = (int)$CFG->defaultfrontpageroleid;
888         }
889     }
891     // Preload every assigned role.
892     $sql = "SELECT ctx.path, ra.roleid, ra.contextid
893               FROM {role_assignments} ra
894               JOIN {context} ctx ON ctx.id = ra.contextid
895              WHERE ra.userid = :userid";
897     $rs = $DB->get_recordset_sql($sql, array('userid' => $userid));
899     foreach ($rs as $ra) {
900         // RAs leafs are arrays to support multi-role assignments...
901         $accessdata['ra'][$ra->path][(int)$ra->roleid] = (int)$ra->roleid;
902     }
904     $rs->close();
906     return $accessdata;
909 /**
910  * Returns empty accessdata structure.
911  *
912  * @access private
913  * @return array empt accessdata
914  */
915 function get_empty_accessdata() {
916     $accessdata               = array(); // named list
917     $accessdata['ra']         = array();
918     $accessdata['time']       = time();
919     $accessdata['rsw']        = array();
921     return $accessdata;
924 /**
925  * Get accessdata for a given user.
926  *
927  * @access private
928  * @param int $userid
929  * @param bool $preloadonly true means do not return access array
930  * @return array accessdata
931  */
932 function get_user_accessdata($userid, $preloadonly=false) {
933     global $CFG, $ACCESSLIB_PRIVATE, $USER;
935     if (isset($USER->access)) {
936         $ACCESSLIB_PRIVATE->accessdatabyuser[$USER->id] = $USER->access;
937     }
939     if (!isset($ACCESSLIB_PRIVATE->accessdatabyuser[$userid])) {
940         if (empty($userid)) {
941             if (!empty($CFG->notloggedinroleid)) {
942                 $accessdata = get_role_access($CFG->notloggedinroleid);
943             } else {
944                 // weird
945                 return get_empty_accessdata();
946             }
948         } else if (isguestuser($userid)) {
949             if ($guestrole = get_guest_role()) {
950                 $accessdata = get_role_access($guestrole->id);
951             } else {
952                 //weird
953                 return get_empty_accessdata();
954             }
956         } else {
957             // Includes default role and frontpage role.
958             $accessdata = get_user_roles_sitewide_accessdata($userid);
959         }
961         $ACCESSLIB_PRIVATE->accessdatabyuser[$userid] = $accessdata;
962     }
964     if ($preloadonly) {
965         return;
966     } else {
967         return $ACCESSLIB_PRIVATE->accessdatabyuser[$userid];
968     }
971 /**
972  * A convenience function to completely load all the capabilities
973  * for the current user. It is called from has_capability() and functions change permissions.
974  *
975  * Call it only _after_ you've setup $USER and called check_enrolment_plugins();
976  * @see check_enrolment_plugins()
977  *
978  * @access private
979  * @return void
980  */
981 function load_all_capabilities() {
982     global $USER;
984     // roles not installed yet - we are in the middle of installation
985     if (during_initial_install()) {
986         return;
987     }
989     if (!isset($USER->id)) {
990         // this should not happen
991         $USER->id = 0;
992     }
994     unset($USER->access);
995     $USER->access = get_user_accessdata($USER->id);
997     // Clear to force a refresh
998     unset($USER->mycourses);
1000     // init/reset internal enrol caches - active course enrolments and temp access
1001     $USER->enrol = array('enrolled'=>array(), 'tempguest'=>array());
1004 /**
1005  * A convenience function to completely reload all the capabilities
1006  * for the current user when roles have been updated in a relevant
1007  * context -- but PRESERVING switchroles and loginas.
1008  * This function resets all accesslib and context caches.
1009  *
1010  * That is - completely transparent to the user.
1011  *
1012  * Note: reloads $USER->access completely.
1013  *
1014  * @access private
1015  * @return void
1016  */
1017 function reload_all_capabilities() {
1018     global $USER, $DB, $ACCESSLIB_PRIVATE;
1020     // copy switchroles
1021     $sw = array();
1022     if (!empty($USER->access['rsw'])) {
1023         $sw = $USER->access['rsw'];
1024     }
1026     accesslib_clear_all_caches(true);
1027     unset($USER->access);
1029     // Prevent dirty flags refetching on this page.
1030     $ACCESSLIB_PRIVATE->dirtycontexts = array();
1031     $ACCESSLIB_PRIVATE->dirtyusers    = array($USER->id => false);
1033     load_all_capabilities();
1035     foreach ($sw as $path => $roleid) {
1036         if ($record = $DB->get_record('context', array('path'=>$path))) {
1037             $context = context::instance_by_id($record->id);
1038             if (has_capability('moodle/role:switchroles', $context)) {
1039                 role_switch($roleid, $context);
1040             }
1041         }
1042     }
1045 /**
1046  * Adds a temp role to current USER->access array.
1047  *
1048  * Useful for the "temporary guest" access we grant to logged-in users.
1049  * This is useful for enrol plugins only.
1050  *
1051  * @since Moodle 2.2
1052  * @param context_course $coursecontext
1053  * @param int $roleid
1054  * @return void
1055  */
1056 function load_temp_course_role(context_course $coursecontext, $roleid) {
1057     global $USER, $SITE;
1059     if (empty($roleid)) {
1060         debugging('invalid role specified in load_temp_course_role()');
1061         return;
1062     }
1064     if ($coursecontext->instanceid == $SITE->id) {
1065         debugging('Can not use temp roles on the frontpage');
1066         return;
1067     }
1069     if (!isset($USER->access)) {
1070         load_all_capabilities();
1071     }
1073     $coursecontext->reload_if_dirty();
1075     if (isset($USER->access['ra'][$coursecontext->path][$roleid])) {
1076         return;
1077     }
1079     $USER->access['ra'][$coursecontext->path][(int)$roleid] = (int)$roleid;
1082 /**
1083  * Removes any extra guest roles from current USER->access array.
1084  * This is useful for enrol plugins only.
1085  *
1086  * @since Moodle 2.2
1087  * @param context_course $coursecontext
1088  * @return void
1089  */
1090 function remove_temp_course_roles(context_course $coursecontext) {
1091     global $DB, $USER, $SITE;
1093     if ($coursecontext->instanceid == $SITE->id) {
1094         debugging('Can not use temp roles on the frontpage');
1095         return;
1096     }
1098     if (empty($USER->access['ra'][$coursecontext->path])) {
1099         //no roles here, weird
1100         return;
1101     }
1103     $sql = "SELECT DISTINCT ra.roleid AS id
1104               FROM {role_assignments} ra
1105              WHERE ra.contextid = :contextid AND ra.userid = :userid";
1106     $ras = $DB->get_records_sql($sql, array('contextid'=>$coursecontext->id, 'userid'=>$USER->id));
1108     $USER->access['ra'][$coursecontext->path] = array();
1109     foreach($ras as $r) {
1110         $USER->access['ra'][$coursecontext->path][(int)$r->id] = (int)$r->id;
1111     }
1114 /**
1115  * Returns array of all role archetypes.
1116  *
1117  * @return array
1118  */
1119 function get_role_archetypes() {
1120     return array(
1121         'manager'        => 'manager',
1122         'coursecreator'  => 'coursecreator',
1123         'editingteacher' => 'editingteacher',
1124         'teacher'        => 'teacher',
1125         'student'        => 'student',
1126         'guest'          => 'guest',
1127         'user'           => 'user',
1128         'frontpage'      => 'frontpage'
1129     );
1132 /**
1133  * Assign the defaults found in this capability definition to roles that have
1134  * the corresponding legacy capabilities assigned to them.
1135  *
1136  * @param string $capability
1137  * @param array $legacyperms an array in the format (example):
1138  *                      'guest' => CAP_PREVENT,
1139  *                      'student' => CAP_ALLOW,
1140  *                      'teacher' => CAP_ALLOW,
1141  *                      'editingteacher' => CAP_ALLOW,
1142  *                      'coursecreator' => CAP_ALLOW,
1143  *                      'manager' => CAP_ALLOW
1144  * @return boolean success or failure.
1145  */
1146 function assign_legacy_capabilities($capability, $legacyperms) {
1148     $archetypes = get_role_archetypes();
1150     foreach ($legacyperms as $type => $perm) {
1152         $systemcontext = context_system::instance();
1153         if ($type === 'admin') {
1154             debugging('Legacy type admin in access.php was renamed to manager, please update the code.');
1155             $type = 'manager';
1156         }
1158         if (!array_key_exists($type, $archetypes)) {
1159             print_error('invalidlegacy', '', '', $type);
1160         }
1162         if ($roles = get_archetype_roles($type)) {
1163             foreach ($roles as $role) {
1164                 // Assign a site level capability.
1165                 if (!assign_capability($capability, $perm, $role->id, $systemcontext->id)) {
1166                     return false;
1167                 }
1168             }
1169         }
1170     }
1171     return true;
1174 /**
1175  * Verify capability risks.
1176  *
1177  * @param stdClass $capability a capability - a row from the capabilities table.
1178  * @return boolean whether this capability is safe - that is, whether people with the
1179  *      safeoverrides capability should be allowed to change it.
1180  */
1181 function is_safe_capability($capability) {
1182     return !((RISK_DATALOSS | RISK_MANAGETRUST | RISK_CONFIG | RISK_XSS | RISK_PERSONAL) & $capability->riskbitmask);
1185 /**
1186  * Get the local override (if any) for a given capability in a role in a context
1187  *
1188  * @param int $roleid
1189  * @param int $contextid
1190  * @param string $capability
1191  * @return stdClass local capability override
1192  */
1193 function get_local_override($roleid, $contextid, $capability) {
1194     global $DB;
1196     return $DB->get_record_sql("
1197         SELECT rc.*
1198           FROM {role_capabilities} rc
1199           JOIN {capability} cap ON rc.capability = cap.name
1200          WHERE rc.roleid = :roleid AND rc.capability = :capability AND rc.contextid = :contextid", [
1201             'roleid' => $roleid,
1202             'contextid' => $contextid,
1203             'capability' => $capability,
1205         ]);
1208 /**
1209  * Returns context instance plus related course and cm instances
1210  *
1211  * @param int $contextid
1212  * @return array of ($context, $course, $cm)
1213  */
1214 function get_context_info_array($contextid) {
1215     global $DB;
1217     $context = context::instance_by_id($contextid, MUST_EXIST);
1218     $course  = null;
1219     $cm      = null;
1221     if ($context->contextlevel == CONTEXT_COURSE) {
1222         $course = $DB->get_record('course', array('id'=>$context->instanceid), '*', MUST_EXIST);
1224     } else if ($context->contextlevel == CONTEXT_MODULE) {
1225         $cm = get_coursemodule_from_id('', $context->instanceid, 0, false, MUST_EXIST);
1226         $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
1228     } else if ($context->contextlevel == CONTEXT_BLOCK) {
1229         $parent = $context->get_parent_context();
1231         if ($parent->contextlevel == CONTEXT_COURSE) {
1232             $course = $DB->get_record('course', array('id'=>$parent->instanceid), '*', MUST_EXIST);
1233         } else if ($parent->contextlevel == CONTEXT_MODULE) {
1234             $cm = get_coursemodule_from_id('', $parent->instanceid, 0, false, MUST_EXIST);
1235             $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
1236         }
1237     }
1239     return array($context, $course, $cm);
1242 /**
1243  * Function that creates a role
1244  *
1245  * @param string $name role name
1246  * @param string $shortname role short name
1247  * @param string $description role description
1248  * @param string $archetype
1249  * @return int id or dml_exception
1250  */
1251 function create_role($name, $shortname, $description, $archetype = '') {
1252     global $DB;
1254     if (strpos($archetype, 'moodle/legacy:') !== false) {
1255         throw new coding_exception('Use new role archetype parameter in create_role() instead of old legacy capabilities.');
1256     }
1258     // verify role archetype actually exists
1259     $archetypes = get_role_archetypes();
1260     if (empty($archetypes[$archetype])) {
1261         $archetype = '';
1262     }
1264     // Insert the role record.
1265     $role = new stdClass();
1266     $role->name        = $name;
1267     $role->shortname   = $shortname;
1268     $role->description = $description;
1269     $role->archetype   = $archetype;
1271     //find free sortorder number
1272     $role->sortorder = $DB->get_field('role', 'MAX(sortorder) + 1', array());
1273     if (empty($role->sortorder)) {
1274         $role->sortorder = 1;
1275     }
1276     $id = $DB->insert_record('role', $role);
1278     return $id;
1281 /**
1282  * Function that deletes a role and cleanups up after it
1283  *
1284  * @param int $roleid id of role to delete
1285  * @return bool always true
1286  */
1287 function delete_role($roleid) {
1288     global $DB;
1290     // first unssign all users
1291     role_unassign_all(array('roleid'=>$roleid));
1293     // cleanup all references to this role, ignore errors
1294     $DB->delete_records('role_capabilities',   array('roleid'=>$roleid));
1295     $DB->delete_records('role_allow_assign',   array('roleid'=>$roleid));
1296     $DB->delete_records('role_allow_assign',   array('allowassign'=>$roleid));
1297     $DB->delete_records('role_allow_override', array('roleid'=>$roleid));
1298     $DB->delete_records('role_allow_override', array('allowoverride'=>$roleid));
1299     $DB->delete_records('role_names',          array('roleid'=>$roleid));
1300     $DB->delete_records('role_context_levels', array('roleid'=>$roleid));
1302     // Get role record before it's deleted.
1303     $role = $DB->get_record('role', array('id'=>$roleid));
1305     // Finally delete the role itself.
1306     $DB->delete_records('role', array('id'=>$roleid));
1308     // Trigger event.
1309     $event = \core\event\role_deleted::create(
1310         array(
1311             'context' => context_system::instance(),
1312             'objectid' => $roleid,
1313             'other' =>
1314                 array(
1315                     'shortname' => $role->shortname,
1316                     'description' => $role->description,
1317                     'archetype' => $role->archetype
1318                 )
1319             )
1320         );
1321     $event->add_record_snapshot('role', $role);
1322     $event->trigger();
1324     // Reset any cache of this role, including MUC.
1325     accesslib_clear_role_cache($roleid);
1327     return true;
1330 /**
1331  * Function to write context specific overrides, or default capabilities.
1332  *
1333  * @param string $capability string name
1334  * @param int $permission CAP_ constants
1335  * @param int $roleid role id
1336  * @param int|context $contextid context id
1337  * @param bool $overwrite
1338  * @return bool always true or exception
1339  */
1340 function assign_capability($capability, $permission, $roleid, $contextid, $overwrite = false) {
1341     global $USER, $DB;
1343     if ($contextid instanceof context) {
1344         $context = $contextid;
1345     } else {
1346         $context = context::instance_by_id($contextid);
1347     }
1349     // Capability must exist.
1350     if (!$capinfo = get_capability_info($capability)) {
1351         throw new coding_exception("Capability '{$capability}' was not found! This has to be fixed in code.");
1352     }
1354     if (empty($permission) || $permission == CAP_INHERIT) { // if permission is not set
1355         unassign_capability($capability, $roleid, $context->id);
1356         return true;
1357     }
1359     $existing = $DB->get_record('role_capabilities', array('contextid'=>$context->id, 'roleid'=>$roleid, 'capability'=>$capability));
1361     if ($existing and !$overwrite) {   // We want to keep whatever is there already
1362         return true;
1363     }
1365     $cap = new stdClass();
1366     $cap->contextid    = $context->id;
1367     $cap->roleid       = $roleid;
1368     $cap->capability   = $capability;
1369     $cap->permission   = $permission;
1370     $cap->timemodified = time();
1371     $cap->modifierid   = empty($USER->id) ? 0 : $USER->id;
1373     if ($existing) {
1374         $cap->id = $existing->id;
1375         $DB->update_record('role_capabilities', $cap);
1376     } else {
1377         if ($DB->record_exists('context', array('id'=>$context->id))) {
1378             $DB->insert_record('role_capabilities', $cap);
1379         }
1380     }
1382     // Reset any cache of this role, including MUC.
1383     accesslib_clear_role_cache($roleid);
1385     return true;
1388 /**
1389  * Unassign a capability from a role.
1390  *
1391  * @param string $capability the name of the capability
1392  * @param int $roleid the role id
1393  * @param int|context $contextid null means all contexts
1394  * @return boolean true or exception
1395  */
1396 function unassign_capability($capability, $roleid, $contextid = null) {
1397     global $DB;
1399     // Capability must exist.
1400     if (!$capinfo = get_capability_info($capability)) {
1401         throw new coding_exception("Capability '{$capability}' was not found! This has to be fixed in code.");
1402     }
1404     if (!empty($contextid)) {
1405         if ($contextid instanceof context) {
1406             $context = $contextid;
1407         } else {
1408             $context = context::instance_by_id($contextid);
1409         }
1410         // delete from context rel, if this is the last override in this context
1411         $DB->delete_records('role_capabilities', array('capability'=>$capability, 'roleid'=>$roleid, 'contextid'=>$context->id));
1412     } else {
1413         $DB->delete_records('role_capabilities', array('capability'=>$capability, 'roleid'=>$roleid));
1414     }
1416     // Reset any cache of this role, including MUC.
1417     accesslib_clear_role_cache($roleid);
1419     return true;
1422 /**
1423  * Get the roles that have a given capability assigned to it
1424  *
1425  * This function does not resolve the actual permission of the capability.
1426  * It just checks for permissions and overrides.
1427  * Use get_roles_with_cap_in_context() if resolution is required.
1428  *
1429  * @param string $capability capability name (string)
1430  * @param string $permission optional, the permission defined for this capability
1431  *                      either CAP_ALLOW, CAP_PREVENT or CAP_PROHIBIT. Defaults to null which means any.
1432  * @param stdClass $context null means any
1433  * @return array of role records
1434  */
1435 function get_roles_with_capability($capability, $permission = null, $context = null) {
1436     global $DB;
1438     if ($context) {
1439         $contexts = $context->get_parent_context_ids(true);
1440         list($insql, $params) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED, 'ctx');
1441         $contextsql = "AND rc.contextid $insql";
1442     } else {
1443         $params = array();
1444         $contextsql = '';
1445     }
1447     if ($permission) {
1448         $permissionsql = " AND rc.permission = :permission";
1449         $params['permission'] = $permission;
1450     } else {
1451         $permissionsql = '';
1452     }
1454     $sql = "SELECT r.*
1455               FROM {role} r
1456              WHERE r.id IN (SELECT rc.roleid
1457                               FROM {role_capabilities} rc
1458                               JOIN {capabilities} cap ON rc.capability = cap.name
1459                              WHERE rc.capability = :capname
1460                                    $contextsql
1461                                    $permissionsql)";
1462     $params['capname'] = $capability;
1465     return $DB->get_records_sql($sql, $params);
1468 /**
1469  * This function makes a role-assignment (a role for a user in a particular context)
1470  *
1471  * @param int $roleid the role of the id
1472  * @param int $userid userid
1473  * @param int|context $contextid id of the context
1474  * @param string $component example 'enrol_ldap', defaults to '' which means manual assignment,
1475  * @param int $itemid id of enrolment/auth plugin
1476  * @param string $timemodified defaults to current time
1477  * @return int new/existing id of the assignment
1478  */
1479 function role_assign($roleid, $userid, $contextid, $component = '', $itemid = 0, $timemodified = '') {
1480     global $USER, $DB;
1482     // first of all detect if somebody is using old style parameters
1483     if ($contextid === 0 or is_numeric($component)) {
1484         throw new coding_exception('Invalid call to role_assign(), code needs to be updated to use new order of parameters');
1485     }
1487     // now validate all parameters
1488     if (empty($roleid)) {
1489         throw new coding_exception('Invalid call to role_assign(), roleid can not be empty');
1490     }
1492     if (empty($userid)) {
1493         throw new coding_exception('Invalid call to role_assign(), userid can not be empty');
1494     }
1496     if ($itemid) {
1497         if (strpos($component, '_') === false) {
1498             throw new coding_exception('Invalid call to role_assign(), component must start with plugin type such as"enrol_" when itemid specified', 'component:'.$component);
1499         }
1500     } else {
1501         $itemid = 0;
1502         if ($component !== '' and strpos($component, '_') === false) {
1503             throw new coding_exception('Invalid call to role_assign(), invalid component string', 'component:'.$component);
1504         }
1505     }
1507     if (!$DB->record_exists('user', array('id'=>$userid, 'deleted'=>0))) {
1508         throw new coding_exception('User ID does not exist or is deleted!', 'userid:'.$userid);
1509     }
1511     if ($contextid instanceof context) {
1512         $context = $contextid;
1513     } else {
1514         $context = context::instance_by_id($contextid, MUST_EXIST);
1515     }
1517     if (!$timemodified) {
1518         $timemodified = time();
1519     }
1521     // Check for existing entry
1522     $ras = $DB->get_records('role_assignments', array('roleid'=>$roleid, 'contextid'=>$context->id, 'userid'=>$userid, 'component'=>$component, 'itemid'=>$itemid), 'id');
1524     if ($ras) {
1525         // role already assigned - this should not happen
1526         if (count($ras) > 1) {
1527             // very weird - remove all duplicates!
1528             $ra = array_shift($ras);
1529             foreach ($ras as $r) {
1530                 $DB->delete_records('role_assignments', array('id'=>$r->id));
1531             }
1532         } else {
1533             $ra = reset($ras);
1534         }
1536         // actually there is no need to update, reset anything or trigger any event, so just return
1537         return $ra->id;
1538     }
1540     // Create a new entry
1541     $ra = new stdClass();
1542     $ra->roleid       = $roleid;
1543     $ra->contextid    = $context->id;
1544     $ra->userid       = $userid;
1545     $ra->component    = $component;
1546     $ra->itemid       = $itemid;
1547     $ra->timemodified = $timemodified;
1548     $ra->modifierid   = empty($USER->id) ? 0 : $USER->id;
1549     $ra->sortorder    = 0;
1551     $ra->id = $DB->insert_record('role_assignments', $ra);
1553     // Role assignments have changed, so mark user as dirty.
1554     mark_user_dirty($userid);
1556     core_course_category::role_assignment_changed($roleid, $context);
1558     $event = \core\event\role_assigned::create(array(
1559         'context' => $context,
1560         'objectid' => $ra->roleid,
1561         'relateduserid' => $ra->userid,
1562         'other' => array(
1563             'id' => $ra->id,
1564             'component' => $ra->component,
1565             'itemid' => $ra->itemid
1566         )
1567     ));
1568     $event->add_record_snapshot('role_assignments', $ra);
1569     $event->trigger();
1571     return $ra->id;
1574 /**
1575  * Removes one role assignment
1576  *
1577  * @param int $roleid
1578  * @param int  $userid
1579  * @param int  $contextid
1580  * @param string $component
1581  * @param int  $itemid
1582  * @return void
1583  */
1584 function role_unassign($roleid, $userid, $contextid, $component = '', $itemid = 0) {
1585     // first make sure the params make sense
1586     if ($roleid == 0 or $userid == 0 or $contextid == 0) {
1587         throw new coding_exception('Invalid call to role_unassign(), please use role_unassign_all() when removing multiple role assignments');
1588     }
1590     if ($itemid) {
1591         if (strpos($component, '_') === false) {
1592             throw new coding_exception('Invalid call to role_assign(), component must start with plugin type such as "enrol_" when itemid specified', 'component:'.$component);
1593         }
1594     } else {
1595         $itemid = 0;
1596         if ($component !== '' and strpos($component, '_') === false) {
1597             throw new coding_exception('Invalid call to role_assign(), invalid component string', 'component:'.$component);
1598         }
1599     }
1601     role_unassign_all(array('roleid'=>$roleid, 'userid'=>$userid, 'contextid'=>$contextid, 'component'=>$component, 'itemid'=>$itemid), false, false);
1604 /**
1605  * Removes multiple role assignments, parameters may contain:
1606  *   'roleid', 'userid', 'contextid', 'component', 'enrolid'.
1607  *
1608  * @param array $params role assignment parameters
1609  * @param bool $subcontexts unassign in subcontexts too
1610  * @param bool $includemanual include manual role assignments too
1611  * @return void
1612  */
1613 function role_unassign_all(array $params, $subcontexts = false, $includemanual = false) {
1614     global $USER, $CFG, $DB;
1616     if (!$params) {
1617         throw new coding_exception('Missing parameters in role_unsassign_all() call');
1618     }
1620     $allowed = array('roleid', 'userid', 'contextid', 'component', 'itemid');
1621     foreach ($params as $key=>$value) {
1622         if (!in_array($key, $allowed)) {
1623             throw new coding_exception('Unknown role_unsassign_all() parameter key', 'key:'.$key);
1624         }
1625     }
1627     if (isset($params['component']) and $params['component'] !== '' and strpos($params['component'], '_') === false) {
1628         throw new coding_exception('Invalid component paramter in role_unsassign_all() call', 'component:'.$params['component']);
1629     }
1631     if ($includemanual) {
1632         if (!isset($params['component']) or $params['component'] === '') {
1633             throw new coding_exception('include manual parameter requires component parameter in role_unsassign_all() call');
1634         }
1635     }
1637     if ($subcontexts) {
1638         if (empty($params['contextid'])) {
1639             throw new coding_exception('subcontexts paramtere requires component parameter in role_unsassign_all() call');
1640         }
1641     }
1643     $ras = $DB->get_records('role_assignments', $params);
1644     foreach($ras as $ra) {
1645         $DB->delete_records('role_assignments', array('id'=>$ra->id));
1646         if ($context = context::instance_by_id($ra->contextid, IGNORE_MISSING)) {
1647             // Role assignments have changed, so mark user as dirty.
1648             mark_user_dirty($ra->userid);
1650             $event = \core\event\role_unassigned::create(array(
1651                 'context' => $context,
1652                 'objectid' => $ra->roleid,
1653                 'relateduserid' => $ra->userid,
1654                 'other' => array(
1655                     'id' => $ra->id,
1656                     'component' => $ra->component,
1657                     'itemid' => $ra->itemid
1658                 )
1659             ));
1660             $event->add_record_snapshot('role_assignments', $ra);
1661             $event->trigger();
1662             core_course_category::role_assignment_changed($ra->roleid, $context);
1663         }
1664     }
1665     unset($ras);
1667     // process subcontexts
1668     if ($subcontexts and $context = context::instance_by_id($params['contextid'], IGNORE_MISSING)) {
1669         if ($params['contextid'] instanceof context) {
1670             $context = $params['contextid'];
1671         } else {
1672             $context = context::instance_by_id($params['contextid'], IGNORE_MISSING);
1673         }
1675         if ($context) {
1676             $contexts = $context->get_child_contexts();
1677             $mparams = $params;
1678             foreach($contexts as $context) {
1679                 $mparams['contextid'] = $context->id;
1680                 $ras = $DB->get_records('role_assignments', $mparams);
1681                 foreach($ras as $ra) {
1682                     $DB->delete_records('role_assignments', array('id'=>$ra->id));
1683                     // Role assignments have changed, so mark user as dirty.
1684                     mark_user_dirty($ra->userid);
1686                     $event = \core\event\role_unassigned::create(
1687                         array('context'=>$context, 'objectid'=>$ra->roleid, 'relateduserid'=>$ra->userid,
1688                             'other'=>array('id'=>$ra->id, 'component'=>$ra->component, 'itemid'=>$ra->itemid)));
1689                     $event->add_record_snapshot('role_assignments', $ra);
1690                     $event->trigger();
1691                     core_course_category::role_assignment_changed($ra->roleid, $context);
1692                 }
1693             }
1694         }
1695     }
1697     // do this once more for all manual role assignments
1698     if ($includemanual) {
1699         $params['component'] = '';
1700         role_unassign_all($params, $subcontexts, false);
1701     }
1704 /**
1705  * Mark a user as dirty (with timestamp) so as to force reloading of the user session.
1706  *
1707  * @param int $userid
1708  * @return void
1709  */
1710 function mark_user_dirty($userid) {
1711     global $CFG, $ACCESSLIB_PRIVATE;
1713     if (during_initial_install()) {
1714         return;
1715     }
1717     // Throw exception if invalid userid is provided.
1718     if (empty($userid)) {
1719         throw new coding_exception('Invalid user parameter supplied for mark_user_dirty() function!');
1720     }
1722     // Set dirty flag in database, set dirty field locally, and clear local accessdata cache.
1723     set_cache_flag('accesslib/dirtyusers', $userid, 1, time() + $CFG->sessiontimeout);
1724     $ACCESSLIB_PRIVATE->dirtyusers[$userid] = 1;
1725     unset($ACCESSLIB_PRIVATE->accessdatabyuser[$userid]);
1728 /**
1729  * Determines if a user is currently logged in
1730  *
1731  * @category   access
1732  *
1733  * @return bool
1734  */
1735 function isloggedin() {
1736     global $USER;
1738     return (!empty($USER->id));
1741 /**
1742  * Determines if a user is logged in as real guest user with username 'guest'.
1743  *
1744  * @category   access
1745  *
1746  * @param int|object $user mixed user object or id, $USER if not specified
1747  * @return bool true if user is the real guest user, false if not logged in or other user
1748  */
1749 function isguestuser($user = null) {
1750     global $USER, $DB, $CFG;
1752     // make sure we have the user id cached in config table, because we are going to use it a lot
1753     if (empty($CFG->siteguest)) {
1754         if (!$guestid = $DB->get_field('user', 'id', array('username'=>'guest', 'mnethostid'=>$CFG->mnet_localhost_id))) {
1755             // guest does not exist yet, weird
1756             return false;
1757         }
1758         set_config('siteguest', $guestid);
1759     }
1760     if ($user === null) {
1761         $user = $USER;
1762     }
1764     if ($user === null) {
1765         // happens when setting the $USER
1766         return false;
1768     } else if (is_numeric($user)) {
1769         return ($CFG->siteguest == $user);
1771     } else if (is_object($user)) {
1772         if (empty($user->id)) {
1773             return false; // not logged in means is not be guest
1774         } else {
1775             return ($CFG->siteguest == $user->id);
1776         }
1778     } else {
1779         throw new coding_exception('Invalid user parameter supplied for isguestuser() function!');
1780     }
1783 /**
1784  * Does user have a (temporary or real) guest access to course?
1785  *
1786  * @category   access
1787  *
1788  * @param context $context
1789  * @param stdClass|int $user
1790  * @return bool
1791  */
1792 function is_guest(context $context, $user = null) {
1793     global $USER;
1795     // first find the course context
1796     $coursecontext = $context->get_course_context();
1798     // make sure there is a real user specified
1799     if ($user === null) {
1800         $userid = isset($USER->id) ? $USER->id : 0;
1801     } else {
1802         $userid = is_object($user) ? $user->id : $user;
1803     }
1805     if (isguestuser($userid)) {
1806         // can not inspect or be enrolled
1807         return true;
1808     }
1810     if (has_capability('moodle/course:view', $coursecontext, $user)) {
1811         // viewing users appear out of nowhere, they are neither guests nor participants
1812         return false;
1813     }
1815     // consider only real active enrolments here
1816     if (is_enrolled($coursecontext, $user, '', true)) {
1817         return false;
1818     }
1820     return true;
1823 /**
1824  * Returns true if the user has moodle/course:view capability in the course,
1825  * this is intended for admins, managers (aka small admins), inspectors, etc.
1826  *
1827  * @category   access
1828  *
1829  * @param context $context
1830  * @param int|stdClass $user if null $USER is used
1831  * @param string $withcapability extra capability name
1832  * @return bool
1833  */
1834 function is_viewing(context $context, $user = null, $withcapability = '') {
1835     // first find the course context
1836     $coursecontext = $context->get_course_context();
1838     if (isguestuser($user)) {
1839         // can not inspect
1840         return false;
1841     }
1843     if (!has_capability('moodle/course:view', $coursecontext, $user)) {
1844         // admins are allowed to inspect courses
1845         return false;
1846     }
1848     if ($withcapability and !has_capability($withcapability, $context, $user)) {
1849         // site admins always have the capability, but the enrolment above blocks
1850         return false;
1851     }
1853     return true;
1856 /**
1857  * Returns true if the user is able to access the course.
1858  *
1859  * This function is in no way, shape, or form a substitute for require_login.
1860  * It should only be used in circumstances where it is not possible to call require_login
1861  * such as the navigation.
1862  *
1863  * This function checks many of the methods of access to a course such as the view
1864  * capability, enrollments, and guest access. It also makes use of the cache
1865  * generated by require_login for guest access.
1866  *
1867  * The flags within the $USER object that are used here should NEVER be used outside
1868  * of this function can_access_course and require_login. Doing so WILL break future
1869  * versions.
1870  *
1871  * @param stdClass $course record
1872  * @param stdClass|int|null $user user record or id, current user if null
1873  * @param string $withcapability Check for this capability as well.
1874  * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
1875  * @return boolean Returns true if the user is able to access the course
1876  */
1877 function can_access_course(stdClass $course, $user = null, $withcapability = '', $onlyactive = false) {
1878     global $DB, $USER;
1880     // this function originally accepted $coursecontext parameter
1881     if ($course instanceof context) {
1882         if ($course instanceof context_course) {
1883             debugging('deprecated context parameter, please use $course record');
1884             $coursecontext = $course;
1885             $course = $DB->get_record('course', array('id'=>$coursecontext->instanceid));
1886         } else {
1887             debugging('Invalid context parameter, please use $course record');
1888             return false;
1889         }
1890     } else {
1891         $coursecontext = context_course::instance($course->id);
1892     }
1894     if (!isset($USER->id)) {
1895         // should never happen
1896         $USER->id = 0;
1897         debugging('Course access check being performed on a user with no ID.', DEBUG_DEVELOPER);
1898     }
1900     // make sure there is a user specified
1901     if ($user === null) {
1902         $userid = $USER->id;
1903     } else {
1904         $userid = is_object($user) ? $user->id : $user;
1905     }
1906     unset($user);
1908     if ($withcapability and !has_capability($withcapability, $coursecontext, $userid)) {
1909         return false;
1910     }
1912     if ($userid == $USER->id) {
1913         if (!empty($USER->access['rsw'][$coursecontext->path])) {
1914             // the fact that somebody switched role means they can access the course no matter to what role they switched
1915             return true;
1916         }
1917     }
1919     if (!$course->visible and !has_capability('moodle/course:viewhiddencourses', $coursecontext, $userid)) {
1920         return false;
1921     }
1923     if (is_viewing($coursecontext, $userid)) {
1924         return true;
1925     }
1927     if ($userid != $USER->id) {
1928         // for performance reasons we do not verify temporary guest access for other users, sorry...
1929         return is_enrolled($coursecontext, $userid, '', $onlyactive);
1930     }
1932     // === from here we deal only with $USER ===
1934     $coursecontext->reload_if_dirty();
1936     if (isset($USER->enrol['enrolled'][$course->id])) {
1937         if ($USER->enrol['enrolled'][$course->id] > time()) {
1938             return true;
1939         }
1940     }
1941     if (isset($USER->enrol['tempguest'][$course->id])) {
1942         if ($USER->enrol['tempguest'][$course->id] > time()) {
1943             return true;
1944         }
1945     }
1947     if (is_enrolled($coursecontext, $USER, '', $onlyactive)) {
1948         return true;
1949     }
1951     // if not enrolled try to gain temporary guest access
1952     $instances = $DB->get_records('enrol', array('courseid'=>$course->id, 'status'=>ENROL_INSTANCE_ENABLED), 'sortorder, id ASC');
1953     $enrols = enrol_get_plugins(true);
1954     foreach($instances as $instance) {
1955         if (!isset($enrols[$instance->enrol])) {
1956             continue;
1957         }
1958         // Get a duration for the guest access, a timestamp in the future, 0 (always) or false.
1959         $until = $enrols[$instance->enrol]->try_guestaccess($instance);
1960         if ($until !== false and $until > time()) {
1961             $USER->enrol['tempguest'][$course->id] = $until;
1962             return true;
1963         }
1964     }
1965     if (isset($USER->enrol['tempguest'][$course->id])) {
1966         unset($USER->enrol['tempguest'][$course->id]);
1967         remove_temp_course_roles($coursecontext);
1968     }
1970     return false;
1973 /**
1974  * Loads the capability definitions for the component (from file).
1975  *
1976  * Loads the capability definitions for the component (from file). If no
1977  * capabilities are defined for the component, we simply return an empty array.
1978  *
1979  * @access private
1980  * @param string $component full plugin name, examples: 'moodle', 'mod_forum'
1981  * @return array array of capabilities
1982  */
1983 function load_capability_def($component) {
1984     $defpath = core_component::get_component_directory($component).'/db/access.php';
1986     $capabilities = array();
1987     if (file_exists($defpath)) {
1988         require($defpath);
1989         if (!empty(${$component.'_capabilities'})) {
1990             // BC capability array name
1991             // since 2.0 we prefer $capabilities instead - it is easier to use and matches db/* files
1992             debugging('componentname_capabilities array is deprecated, please use $capabilities array only in access.php files');
1993             $capabilities = ${$component.'_capabilities'};
1994         }
1995     }
1997     return $capabilities;
2000 /**
2001  * Gets the capabilities that have been cached in the database for this component.
2002  *
2003  * @access private
2004  * @param string $component - examples: 'moodle', 'mod_forum'
2005  * @return array array of capabilities
2006  */
2007 function get_cached_capabilities($component = 'moodle') {
2008     global $DB;
2009     $caps = get_all_capabilities();
2010     $componentcaps = array();
2011     foreach ($caps as $cap) {
2012         if ($cap['component'] == $component) {
2013             $componentcaps[] = (object) $cap;
2014         }
2015     }
2016     return $componentcaps;
2019 /**
2020  * Returns default capabilities for given role archetype.
2021  *
2022  * @param string $archetype role archetype
2023  * @return array
2024  */
2025 function get_default_capabilities($archetype) {
2026     global $DB;
2028     if (!$archetype) {
2029         return array();
2030     }
2032     $alldefs = array();
2033     $defaults = array();
2034     $components = array();
2035     $allcaps = get_all_capabilities();
2037     foreach ($allcaps as $cap) {
2038         if (!in_array($cap['component'], $components)) {
2039             $components[] = $cap['component'];
2040             $alldefs = array_merge($alldefs, load_capability_def($cap['component']));
2041         }
2042     }
2043     foreach($alldefs as $name=>$def) {
2044         // Use array 'archetypes if available. Only if not specified, use 'legacy'.
2045         if (isset($def['archetypes'])) {
2046             if (isset($def['archetypes'][$archetype])) {
2047                 $defaults[$name] = $def['archetypes'][$archetype];
2048             }
2049         // 'legacy' is for backward compatibility with 1.9 access.php
2050         } else {
2051             if (isset($def['legacy'][$archetype])) {
2052                 $defaults[$name] = $def['legacy'][$archetype];
2053             }
2054         }
2055     }
2057     return $defaults;
2060 /**
2061  * Return default roles that can be assigned, overridden or switched
2062  * by give role archetype.
2063  *
2064  * @param string $type  assign|override|switch|view
2065  * @param string $archetype
2066  * @return array of role ids
2067  */
2068 function get_default_role_archetype_allows($type, $archetype) {
2069     global $DB;
2071     if (empty($archetype)) {
2072         return array();
2073     }
2075     $roles = $DB->get_records('role');
2076     $archetypemap = array();
2077     foreach ($roles as $role) {
2078         if ($role->archetype) {
2079             $archetypemap[$role->archetype][$role->id] = $role->id;
2080         }
2081     }
2083     $defaults = array(
2084         'assign' => array(
2085             'manager'        => array('manager', 'coursecreator', 'editingteacher', 'teacher', 'student'),
2086             'coursecreator'  => array(),
2087             'editingteacher' => array('teacher', 'student'),
2088             'teacher'        => array(),
2089             'student'        => array(),
2090             'guest'          => array(),
2091             'user'           => array(),
2092             'frontpage'      => array(),
2093         ),
2094         'override' => array(
2095             'manager'        => array('manager', 'coursecreator', 'editingteacher', 'teacher', 'student', 'guest', 'user', 'frontpage'),
2096             'coursecreator'  => array(),
2097             'editingteacher' => array('teacher', 'student', 'guest'),
2098             'teacher'        => array(),
2099             'student'        => array(),
2100             'guest'          => array(),
2101             'user'           => array(),
2102             'frontpage'      => array(),
2103         ),
2104         'switch' => array(
2105             'manager'        => array('editingteacher', 'teacher', 'student', 'guest'),
2106             'coursecreator'  => array(),
2107             'editingteacher' => array('teacher', 'student', 'guest'),
2108             'teacher'        => array('student', 'guest'),
2109             'student'        => array(),
2110             'guest'          => array(),
2111             'user'           => array(),
2112             'frontpage'      => array(),
2113         ),
2114         'view' => array(
2115             'manager'        => array('manager', 'coursecreator', 'editingteacher', 'teacher', 'student', 'guest', 'user', 'frontpage'),
2116             'coursecreator'  => array('coursecreator', 'editingteacher', 'teacher', 'student'),
2117             'editingteacher' => array('coursecreator', 'editingteacher', 'teacher', 'student'),
2118             'teacher'        => array('coursecreator', 'editingteacher', 'teacher', 'student'),
2119             'student'        => array('coursecreator', 'editingteacher', 'teacher', 'student'),
2120             'guest'          => array(),
2121             'user'           => array(),
2122             'frontpage'      => array(),
2123         ),
2124     );
2126     if (!isset($defaults[$type][$archetype])) {
2127         debugging("Unknown type '$type'' or archetype '$archetype''");
2128         return array();
2129     }
2131     $return = array();
2132     foreach ($defaults[$type][$archetype] as $at) {
2133         if (isset($archetypemap[$at])) {
2134             foreach ($archetypemap[$at] as $roleid) {
2135                 $return[$roleid] = $roleid;
2136             }
2137         }
2138     }
2140     return $return;
2143 /**
2144  * Reset role capabilities to default according to selected role archetype.
2145  * If no archetype selected, removes all capabilities.
2146  *
2147  * This applies to capabilities that are assigned to the role (that you could
2148  * edit in the 'define roles' interface), and not to any capability overrides
2149  * in different locations.
2150  *
2151  * @param int $roleid ID of role to reset capabilities for
2152  */
2153 function reset_role_capabilities($roleid) {
2154     global $DB;
2156     $role = $DB->get_record('role', array('id'=>$roleid), '*', MUST_EXIST);
2157     $defaultcaps = get_default_capabilities($role->archetype);
2159     $systemcontext = context_system::instance();
2161     $DB->delete_records('role_capabilities',
2162             array('roleid' => $roleid, 'contextid' => $systemcontext->id));
2164     foreach($defaultcaps as $cap=>$permission) {
2165         assign_capability($cap, $permission, $roleid, $systemcontext->id);
2166     }
2168     // Reset any cache of this role, including MUC.
2169     accesslib_clear_role_cache($roleid);
2172 /**
2173  * Updates the capabilities table with the component capability definitions.
2174  * If no parameters are given, the function updates the core moodle
2175  * capabilities.
2176  *
2177  * Note that the absence of the db/access.php capabilities definition file
2178  * will cause any stored capabilities for the component to be removed from
2179  * the database.
2180  *
2181  * @access private
2182  * @param string $component examples: 'moodle', 'mod/forum', 'block/quiz_results'
2183  * @return boolean true if success, exception in case of any problems
2184  */
2185 function update_capabilities($component = 'moodle') {
2186     global $DB, $OUTPUT;
2188     $storedcaps = array();
2190     $filecaps = load_capability_def($component);
2191     foreach($filecaps as $capname=>$unused) {
2192         if (!preg_match('|^[a-z]+/[a-z_0-9]+:[a-z_0-9]+$|', $capname)) {
2193             debugging("Coding problem: Invalid capability name '$capname', use 'clonepermissionsfrom' field for migration.");
2194         }
2195     }
2197     // It is possible somebody directly modified the DB (according to accesslib_test anyway).
2198     // So ensure our updating is based on fresh data.
2199     cache::make('core', 'capabilities')->delete('core_capabilities');
2201     $cachedcaps = get_cached_capabilities($component);
2202     if ($cachedcaps) {
2203         foreach ($cachedcaps as $cachedcap) {
2204             array_push($storedcaps, $cachedcap->name);
2205             // update risk bitmasks and context levels in existing capabilities if needed
2206             if (array_key_exists($cachedcap->name, $filecaps)) {
2207                 if (!array_key_exists('riskbitmask', $filecaps[$cachedcap->name])) {
2208                     $filecaps[$cachedcap->name]['riskbitmask'] = 0; // no risk if not specified
2209                 }
2210                 if ($cachedcap->captype != $filecaps[$cachedcap->name]['captype']) {
2211                     $updatecap = new stdClass();
2212                     $updatecap->id = $cachedcap->id;
2213                     $updatecap->captype = $filecaps[$cachedcap->name]['captype'];
2214                     $DB->update_record('capabilities', $updatecap);
2215                 }
2216                 if ($cachedcap->riskbitmask != $filecaps[$cachedcap->name]['riskbitmask']) {
2217                     $updatecap = new stdClass();
2218                     $updatecap->id = $cachedcap->id;
2219                     $updatecap->riskbitmask = $filecaps[$cachedcap->name]['riskbitmask'];
2220                     $DB->update_record('capabilities', $updatecap);
2221                 }
2223                 if (!array_key_exists('contextlevel', $filecaps[$cachedcap->name])) {
2224                     $filecaps[$cachedcap->name]['contextlevel'] = 0; // no context level defined
2225                 }
2226                 if ($cachedcap->contextlevel != $filecaps[$cachedcap->name]['contextlevel']) {
2227                     $updatecap = new stdClass();
2228                     $updatecap->id = $cachedcap->id;
2229                     $updatecap->contextlevel = $filecaps[$cachedcap->name]['contextlevel'];
2230                     $DB->update_record('capabilities', $updatecap);
2231                 }
2232             }
2233         }
2234     }
2236     // Flush the cached again, as we have changed DB.
2237     cache::make('core', 'capabilities')->delete('core_capabilities');
2239     // Are there new capabilities in the file definition?
2240     $newcaps = array();
2242     foreach ($filecaps as $filecap => $def) {
2243         if (!$storedcaps ||
2244                 ($storedcaps && in_array($filecap, $storedcaps) === false)) {
2245             if (!array_key_exists('riskbitmask', $def)) {
2246                 $def['riskbitmask'] = 0; // no risk if not specified
2247             }
2248             $newcaps[$filecap] = $def;
2249         }
2250     }
2251     // Add new capabilities to the stored definition.
2252     $existingcaps = $DB->get_records_menu('capabilities', array(), 'id', 'id, name');
2253     foreach ($newcaps as $capname => $capdef) {
2254         $capability = new stdClass();
2255         $capability->name         = $capname;
2256         $capability->captype      = $capdef['captype'];
2257         $capability->contextlevel = $capdef['contextlevel'];
2258         $capability->component    = $component;
2259         $capability->riskbitmask  = $capdef['riskbitmask'];
2261         $DB->insert_record('capabilities', $capability, false);
2263         // Flush the cached, as we have changed DB.
2264         cache::make('core', 'capabilities')->delete('core_capabilities');
2266         if (isset($capdef['clonepermissionsfrom']) && in_array($capdef['clonepermissionsfrom'], $existingcaps)){
2267             if ($rolecapabilities = $DB->get_records('role_capabilities', array('capability'=>$capdef['clonepermissionsfrom']))){
2268                 foreach ($rolecapabilities as $rolecapability){
2269                     //assign_capability will update rather than insert if capability exists
2270                     if (!assign_capability($capname, $rolecapability->permission,
2271                                             $rolecapability->roleid, $rolecapability->contextid, true)){
2272                          echo $OUTPUT->notification('Could not clone capabilities for '.$capname);
2273                     }
2274                 }
2275             }
2276         // we ignore archetype key if we have cloned permissions
2277         } else if (isset($capdef['archetypes']) && is_array($capdef['archetypes'])) {
2278             assign_legacy_capabilities($capname, $capdef['archetypes']);
2279         // 'legacy' is for backward compatibility with 1.9 access.php
2280         } else if (isset($capdef['legacy']) && is_array($capdef['legacy'])) {
2281             assign_legacy_capabilities($capname, $capdef['legacy']);
2282         }
2283     }
2284     // Are there any capabilities that have been removed from the file
2285     // definition that we need to delete from the stored capabilities and
2286     // role assignments?
2287     capabilities_cleanup($component, $filecaps);
2289     // reset static caches
2290     accesslib_reset_role_cache();
2292     // Flush the cached again, as we have changed DB.
2293     cache::make('core', 'capabilities')->delete('core_capabilities');
2295     return true;
2298 /**
2299  * Deletes cached capabilities that are no longer needed by the component.
2300  * Also unassigns these capabilities from any roles that have them.
2301  * NOTE: this function is called from lib/db/upgrade.php
2302  *
2303  * @access private
2304  * @param string $component examples: 'moodle', 'mod_forum', 'block_quiz_results'
2305  * @param array $newcapdef array of the new capability definitions that will be
2306  *                     compared with the cached capabilities
2307  * @return int number of deprecated capabilities that have been removed
2308  */
2309 function capabilities_cleanup($component, $newcapdef = null) {
2310     global $DB;
2312     $removedcount = 0;
2314     if ($cachedcaps = get_cached_capabilities($component)) {
2315         foreach ($cachedcaps as $cachedcap) {
2316             if (empty($newcapdef) ||
2317                         array_key_exists($cachedcap->name, $newcapdef) === false) {
2319                 // Delete from roles.
2320                 if ($roles = get_roles_with_capability($cachedcap->name)) {
2321                     foreach($roles as $role) {
2322                         if (!unassign_capability($cachedcap->name, $role->id)) {
2323                             print_error('cannotunassigncap', 'error', '', (object)array('cap'=>$cachedcap->name, 'role'=>$role->name));
2324                         }
2325                     }
2326                 }
2328                 // Remove from role_capabilities for any old ones.
2329                 $DB->delete_records('role_capabilities', array('capability' => $cachedcap->name));
2331                 // Remove from capabilities cache.
2332                 $DB->delete_records('capabilities', array('name' => $cachedcap->name));
2333                 $removedcount++;
2334             } // End if.
2335         }
2336     }
2337     if ($removedcount) {
2338         cache::make('core', 'capabilities')->delete('core_capabilities');
2339     }
2340     return $removedcount;
2343 /**
2344  * Returns an array of all the known types of risk
2345  * The array keys can be used, for example as CSS class names, or in calls to
2346  * print_risk_icon. The values are the corresponding RISK_ constants.
2347  *
2348  * @return array all the known types of risk.
2349  */
2350 function get_all_risks() {
2351     return array(
2352         'riskmanagetrust' => RISK_MANAGETRUST,
2353         'riskconfig'      => RISK_CONFIG,
2354         'riskxss'         => RISK_XSS,
2355         'riskpersonal'    => RISK_PERSONAL,
2356         'riskspam'        => RISK_SPAM,
2357         'riskdataloss'    => RISK_DATALOSS,
2358     );
2361 /**
2362  * Return a link to moodle docs for a given capability name
2363  *
2364  * @param stdClass $capability a capability - a row from the mdl_capabilities table.
2365  * @return string the human-readable capability name as a link to Moodle Docs.
2366  */
2367 function get_capability_docs_link($capability) {
2368     $url = get_docs_url('Capabilities/' . $capability->name);
2369     return '<a onclick="this.target=\'docspopup\'" href="' . $url . '">' . get_capability_string($capability->name) . '</a>';
2372 /**
2373  * This function pulls out all the resolved capabilities (overrides and
2374  * defaults) of a role used in capability overrides in contexts at a given
2375  * context.
2376  *
2377  * @param int $roleid
2378  * @param context $context
2379  * @param string $cap capability, optional, defaults to ''
2380  * @return array Array of capabilities
2381  */
2382 function role_context_capabilities($roleid, context $context, $cap = '') {
2383     global $DB;
2385     $contexts = $context->get_parent_context_ids(true);
2386     $contexts = '('.implode(',', $contexts).')';
2388     $params = array($roleid);
2390     if ($cap) {
2391         $search = " AND rc.capability = ? ";
2392         $params[] = $cap;
2393     } else {
2394         $search = '';
2395     }
2397     $sql = "SELECT rc.*
2398               FROM {role_capabilities} rc
2399               JOIN {context} c ON rc.contextid = c.id
2400               JOIN {capabilities} cap ON rc.capability = cap.name
2401              WHERE rc.contextid in $contexts
2402                    AND rc.roleid = ?
2403                    $search
2404           ORDER BY c.contextlevel DESC, rc.capability DESC";
2406     $capabilities = array();
2408     if ($records = $DB->get_records_sql($sql, $params)) {
2409         // We are traversing via reverse order.
2410         foreach ($records as $record) {
2411             // If not set yet (i.e. inherit or not set at all), or currently we have a prohibit
2412             if (!isset($capabilities[$record->capability]) || $record->permission<-500) {
2413                 $capabilities[$record->capability] = $record->permission;
2414             }
2415         }
2416     }
2417     return $capabilities;
2420 /**
2421  * Constructs array with contextids as first parameter and context paths,
2422  * in both cases bottom top including self.
2423  *
2424  * @access private
2425  * @param context $context
2426  * @return array
2427  */
2428 function get_context_info_list(context $context) {
2429     $contextids = explode('/', ltrim($context->path, '/'));
2430     $contextpaths = array();
2431     $contextids2 = $contextids;
2432     while ($contextids2) {
2433         $contextpaths[] = '/' . implode('/', $contextids2);
2434         array_pop($contextids2);
2435     }
2436     return array($contextids, $contextpaths);
2439 /**
2440  * Check if context is the front page context or a context inside it
2441  *
2442  * Returns true if this context is the front page context, or a context inside it,
2443  * otherwise false.
2444  *
2445  * @param context $context a context object.
2446  * @return bool
2447  */
2448 function is_inside_frontpage(context $context) {
2449     $frontpagecontext = context_course::instance(SITEID);
2450     return strpos($context->path . '/', $frontpagecontext->path . '/') === 0;
2453 /**
2454  * Returns capability information (cached)
2455  *
2456  * @param string $capabilityname
2457  * @return stdClass or null if capability not found
2458  */
2459 function get_capability_info($capabilityname) {
2460     $caps = get_all_capabilities();
2462     if (!isset($caps[$capabilityname])) {
2463         return null;
2464     }
2466     return (object) $caps[$capabilityname];
2469 /**
2470  * Returns all capabilitiy records, preferably from MUC and not database.
2471  *
2472  * @return array All capability records indexed by capability name
2473  */
2474 function get_all_capabilities() {
2475     global $DB;
2476     $cache = cache::make('core', 'capabilities');
2477     if (!$allcaps = $cache->get('core_capabilities')) {
2478         $rs = $DB->get_recordset('capabilities');
2479         $allcaps = array();
2480         foreach ($rs as $capability) {
2481             $capability->riskbitmask = (int) $capability->riskbitmask;
2482             $allcaps[$capability->name] = (array) $capability;
2483         }
2484         $rs->close();
2485         $cache->set('core_capabilities', $allcaps);
2486     }
2487     return $allcaps;
2490 /**
2491  * Returns the human-readable, translated version of the capability.
2492  * Basically a big switch statement.
2493  *
2494  * @param string $capabilityname e.g. mod/choice:readresponses
2495  * @return string
2496  */
2497 function get_capability_string($capabilityname) {
2499     // Typical capability name is 'plugintype/pluginname:capabilityname'
2500     list($type, $name, $capname) = preg_split('|[/:]|', $capabilityname);
2502     if ($type === 'moodle') {
2503         $component = 'core_role';
2504     } else if ($type === 'quizreport') {
2505         //ugly hack!!
2506         $component = 'quiz_'.$name;
2507     } else {
2508         $component = $type.'_'.$name;
2509     }
2511     $stringname = $name.':'.$capname;
2513     if ($component === 'core_role' or get_string_manager()->string_exists($stringname, $component)) {
2514         return get_string($stringname, $component);
2515     }
2517     $dir = core_component::get_component_directory($component);
2518     if (!file_exists($dir)) {
2519         // plugin broken or does not exist, do not bother with printing of debug message
2520         return $capabilityname.' ???';
2521     }
2523     // something is wrong in plugin, better print debug
2524     return get_string($stringname, $component);
2527 /**
2528  * This gets the mod/block/course/core etc strings.
2529  *
2530  * @param string $component
2531  * @param int $contextlevel
2532  * @return string|bool String is success, false if failed
2533  */
2534 function get_component_string($component, $contextlevel) {
2536     if ($component === 'moodle' or $component === 'core') {
2537         switch ($contextlevel) {
2538             // TODO MDL-46123: this should probably use context level names instead
2539             case CONTEXT_SYSTEM:    return get_string('coresystem');
2540             case CONTEXT_USER:      return get_string('users');
2541             case CONTEXT_COURSECAT: return get_string('categories');
2542             case CONTEXT_COURSE:    return get_string('course');
2543             case CONTEXT_MODULE:    return get_string('activities');
2544             case CONTEXT_BLOCK:     return get_string('block');
2545             default:                print_error('unknowncontext');
2546         }
2547     }
2549     list($type, $name) = core_component::normalize_component($component);
2550     $dir = core_component::get_plugin_directory($type, $name);
2551     if (!file_exists($dir)) {
2552         // plugin not installed, bad luck, there is no way to find the name
2553         return $component.' ???';
2554     }
2556     switch ($type) {
2557         // TODO MDL-46123: this is really hacky and should be improved.
2558         case 'quiz':         return get_string($name.':componentname', $component);// insane hack!!!
2559         case 'repository':   return get_string('repository', 'repository').': '.get_string('pluginname', $component);
2560         case 'gradeimport':  return get_string('gradeimport', 'grades').': '.get_string('pluginname', $component);
2561         case 'gradeexport':  return get_string('gradeexport', 'grades').': '.get_string('pluginname', $component);
2562         case 'gradereport':  return get_string('gradereport', 'grades').': '.get_string('pluginname', $component);
2563         case 'webservice':   return get_string('webservice', 'webservice').': '.get_string('pluginname', $component);
2564         case 'block':        return get_string('block').': '.get_string('pluginname', basename($component));
2565         case 'mod':
2566             if (get_string_manager()->string_exists('pluginname', $component)) {
2567                 return get_string('activity').': '.get_string('pluginname', $component);
2568             } else {
2569                 return get_string('activity').': '.get_string('modulename', $component);
2570             }
2571         default: return get_string('pluginname', $component);
2572     }
2575 /**
2576  * Gets the list of roles assigned to this context and up (parents)
2577  * from the aggregation of:
2578  * a) the list of roles that are visible on user profile page and participants page (profileroles setting) and;
2579  * b) if applicable, those roles that are assigned in the context.
2580  *
2581  * @param context $context
2582  * @return array
2583  */
2584 function get_profile_roles(context $context) {
2585     global $CFG, $DB;
2586     // If the current user can assign roles, then they can see all roles on the profile and participants page,
2587     // provided the roles are assigned to at least 1 user in the context. If not, only the policy-defined roles.
2588     if (has_capability('moodle/role:assign', $context)) {
2589         $rolesinscope = array_keys(get_all_roles($context));
2590     } else {
2591         $rolesinscope = empty($CFG->profileroles) ? [] : array_map('trim', explode(',', $CFG->profileroles));
2592     }
2594     if (empty($rolesinscope)) {
2595         return [];
2596     }
2598     list($rallowed, $params) = $DB->get_in_or_equal($rolesinscope, SQL_PARAMS_NAMED, 'a');
2599     list($contextlist, $cparams) = $DB->get_in_or_equal($context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'p');
2600     $params = array_merge($params, $cparams);
2602     if ($coursecontext = $context->get_course_context(false)) {
2603         $params['coursecontext'] = $coursecontext->id;
2604     } else {
2605         $params['coursecontext'] = 0;
2606     }
2608     $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder, rn.name AS coursealias
2609               FROM {role_assignments} ra, {role} r
2610          LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
2611              WHERE r.id = ra.roleid
2612                    AND ra.contextid $contextlist
2613                    AND r.id $rallowed
2614           ORDER BY r.sortorder ASC";
2616     return $DB->get_records_sql($sql, $params);
2619 /**
2620  * Gets the list of roles assigned to this context and up (parents)
2621  *
2622  * @param context $context
2623  * @param boolean $includeparents, false means without parents.
2624  * @return array
2625  */
2626 function get_roles_used_in_context(context $context, $includeparents = true) {
2627     global $DB;
2629     if ($includeparents === true) {
2630         list($contextlist, $params) = $DB->get_in_or_equal($context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'cl');
2631     } else {
2632         list($contextlist, $params) = $DB->get_in_or_equal($context->id, SQL_PARAMS_NAMED, 'cl');
2633     }
2635     if ($coursecontext = $context->get_course_context(false)) {
2636         $params['coursecontext'] = $coursecontext->id;
2637     } else {
2638         $params['coursecontext'] = 0;
2639     }
2641     $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder, rn.name AS coursealias
2642               FROM {role_assignments} ra, {role} r
2643          LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
2644              WHERE r.id = ra.roleid
2645                    AND ra.contextid $contextlist
2646           ORDER BY r.sortorder ASC";
2648     return $DB->get_records_sql($sql, $params);
2651 /**
2652  * This function is used to print roles column in user profile page.
2653  * It is using the CFG->profileroles to limit the list to only interesting roles.
2654  * (The permission tab has full details of user role assignments.)
2655  *
2656  * @param int $userid
2657  * @param int $courseid
2658  * @return string
2659  */
2660 function get_user_roles_in_course($userid, $courseid) {
2661     global $CFG, $DB;
2662     if ($courseid == SITEID) {
2663         $context = context_system::instance();
2664     } else {
2665         $context = context_course::instance($courseid);
2666     }
2667     // If the current user can assign roles, then they can see all roles on the profile and participants page,
2668     // provided the roles are assigned to at least 1 user in the context. If not, only the policy-defined roles.
2669     if (has_capability('moodle/role:assign', $context)) {
2670         $rolesinscope = array_keys(get_all_roles($context));
2671     } else {
2672         $rolesinscope = empty($CFG->profileroles) ? [] : array_map('trim', explode(',', $CFG->profileroles));
2673     }
2674     if (empty($rolesinscope)) {
2675         return '';
2676     }
2678     list($rallowed, $params) = $DB->get_in_or_equal($rolesinscope, SQL_PARAMS_NAMED, 'a');
2679     list($contextlist, $cparams) = $DB->get_in_or_equal($context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'p');
2680     $params = array_merge($params, $cparams);
2682     if ($coursecontext = $context->get_course_context(false)) {
2683         $params['coursecontext'] = $coursecontext->id;
2684     } else {
2685         $params['coursecontext'] = 0;
2686     }
2688     $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder, rn.name AS coursealias
2689               FROM {role_assignments} ra, {role} r
2690          LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
2691              WHERE r.id = ra.roleid
2692                    AND ra.contextid $contextlist
2693                    AND r.id $rallowed
2694                    AND ra.userid = :userid
2695           ORDER BY r.sortorder ASC";
2696     $params['userid'] = $userid;
2698     $rolestring = '';
2700     if ($roles = $DB->get_records_sql($sql, $params)) {
2701         $viewableroles = get_viewable_roles($context, $userid);
2703         $rolenames = array();
2704         foreach ($roles as $roleid => $unused) {
2705             if (isset($viewableroles[$roleid])) {
2706                 $url = new moodle_url('/user/index.php', ['contextid' => $context->id, 'roleid' => $roleid]);
2707                 $rolenames[] = '<a href="' . $url . '">' . $viewableroles[$roleid] . '</a>';
2708             }
2709         }
2710         $rolestring = implode(',', $rolenames);
2711     }
2713     return $rolestring;
2716 /**
2717  * Checks if a user can assign users to a particular role in this context
2718  *
2719  * @param context $context
2720  * @param int $targetroleid - the id of the role you want to assign users to
2721  * @return boolean
2722  */
2723 function user_can_assign(context $context, $targetroleid) {
2724     global $DB;
2726     // First check to see if the user is a site administrator.
2727     if (is_siteadmin()) {
2728         return true;
2729     }
2731     // Check if user has override capability.
2732     // If not return false.
2733     if (!has_capability('moodle/role:assign', $context)) {
2734         return false;
2735     }
2736     // pull out all active roles of this user from this context(or above)
2737     if ($userroles = get_user_roles($context)) {
2738         foreach ($userroles as $userrole) {
2739             // if any in the role_allow_override table, then it's ok
2740             if ($DB->get_record('role_allow_assign', array('roleid'=>$userrole->roleid, 'allowassign'=>$targetroleid))) {
2741                 return true;
2742             }
2743         }
2744     }
2746     return false;
2749 /**
2750  * Returns all site roles in correct sort order.
2751  *
2752  * Note: this method does not localise role names or descriptions,
2753  *       use role_get_names() if you need role names.
2754  *
2755  * @param context $context optional context for course role name aliases
2756  * @return array of role records with optional coursealias property
2757  */
2758 function get_all_roles(context $context = null) {
2759     global $DB;
2761     if (!$context or !$coursecontext = $context->get_course_context(false)) {
2762         $coursecontext = null;
2763     }
2765     if ($coursecontext) {
2766         $sql = "SELECT r.*, rn.name AS coursealias
2767                   FROM {role} r
2768              LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
2769               ORDER BY r.sortorder ASC";
2770         return $DB->get_records_sql($sql, array('coursecontext'=>$coursecontext->id));
2772     } else {
2773         return $DB->get_records('role', array(), 'sortorder ASC');
2774     }
2777 /**
2778  * Returns roles of a specified archetype
2779  *
2780  * @param string $archetype
2781  * @return array of full role records
2782  */
2783 function get_archetype_roles($archetype) {
2784     global $DB;
2785     return $DB->get_records('role', array('archetype'=>$archetype), 'sortorder ASC');
2788 /**
2789  * Gets all the user roles assigned in this context, or higher contexts for a list of users.
2790  *
2791  * If you try using the combination $userids = [], $checkparentcontexts = true then this is likely
2792  * to cause an out-of-memory error on large Moodle sites, so this combination is deprecated and
2793  * outputs a warning, even though it is the default.
2794  *
2795  * @param context $context
2796  * @param array $userids. An empty list means fetch all role assignments for the context.
2797  * @param bool $checkparentcontexts defaults to true
2798  * @param string $order defaults to 'c.contextlevel DESC, r.sortorder ASC'
2799  * @return array
2800  */
2801 function get_users_roles(context $context, $userids = [], $checkparentcontexts = true, $order = 'c.contextlevel DESC, r.sortorder ASC') {
2802     global $DB;
2804     if (!$userids && $checkparentcontexts) {
2805         debugging('Please do not call get_users_roles() with $checkparentcontexts = true ' .
2806                 'and $userids array not set. This combination causes large Moodle sites ' .
2807                 'with lots of site-wide role assignemnts to run out of memory.', DEBUG_DEVELOPER);
2808     }
2810     if ($checkparentcontexts) {
2811         $contextids = $context->get_parent_context_ids();
2812     } else {
2813         $contextids = array();
2814     }
2815     $contextids[] = $context->id;
2817     list($contextids, $params) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, 'con');
2819     // If userids was passed as an empty array, we fetch all role assignments for the course.
2820     if (empty($userids)) {
2821         $useridlist = ' IS NOT NULL ';
2822         $uparams = [];
2823     } else {
2824         list($useridlist, $uparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED, 'uids');
2825     }
2827     $sql = "SELECT ra.*, r.name, r.shortname, ra.userid
2828               FROM {role_assignments} ra, {role} r, {context} c
2829              WHERE ra.userid $useridlist
2830                    AND ra.roleid = r.id
2831                    AND ra.contextid = c.id
2832                    AND ra.contextid $contextids
2833           ORDER BY $order";
2835     $all = $DB->get_records_sql($sql , array_merge($params, $uparams));
2837     // Return results grouped by userid.
2838     $result = [];
2839     foreach ($all as $id => $record) {
2840         if (!isset($result[$record->userid])) {
2841             $result[$record->userid] = [];
2842         }
2843         $result[$record->userid][$record->id] = $record;
2844     }
2846     // Make sure all requested users are included in the result, even if they had no role assignments.
2847     foreach ($userids as $id) {
2848         if (!isset($result[$id])) {
2849             $result[$id] = [];
2850         }
2851     }
2853     return $result;
2857 /**
2858  * Gets all the user roles assigned in this context, or higher contexts
2859  * this is mainly used when checking if a user can assign a role, or overriding a role
2860  * i.e. we need to know what this user holds, in order to verify against allow_assign and
2861  * allow_override tables
2862  *
2863  * @param context $context
2864  * @param int $userid
2865  * @param bool $checkparentcontexts defaults to true
2866  * @param string $order defaults to 'c.contextlevel DESC, r.sortorder ASC'
2867  * @return array
2868  */
2869 function get_user_roles(context $context, $userid = 0, $checkparentcontexts = true, $order = 'c.contextlevel DESC, r.sortorder ASC') {
2870     global $USER, $DB;
2872     if (empty($userid)) {
2873         if (empty($USER->id)) {
2874             return array();
2875         }
2876         $userid = $USER->id;
2877     }
2879     if ($checkparentcontexts) {
2880         $contextids = $context->get_parent_context_ids();
2881     } else {
2882         $contextids = array();
2883     }
2884     $contextids[] = $context->id;
2886     list($contextids, $params) = $DB->get_in_or_equal($contextids, SQL_PARAMS_QM);
2888     array_unshift($params, $userid);
2890     $sql = "SELECT ra.*, r.name, r.shortname
2891               FROM {role_assignments} ra, {role} r, {context} c
2892              WHERE ra.userid = ?
2893                    AND ra.roleid = r.id
2894                    AND ra.contextid = c.id
2895                    AND ra.contextid $contextids
2896           ORDER BY $order";
2898     return $DB->get_records_sql($sql ,$params);
2901 /**
2902  * Like get_user_roles, but adds in the authenticated user role, and the front
2903  * page roles, if applicable.
2904  *
2905  * @param context $context the context.
2906  * @param int $userid optional. Defaults to $USER->id
2907  * @return array of objects with fields ->userid, ->contextid and ->roleid.
2908  */
2909 function get_user_roles_with_special(context $context, $userid = 0) {
2910     global $CFG, $USER;
2912     if (empty($userid)) {
2913         if (empty($USER->id)) {
2914             return array();
2915         }
2916         $userid = $USER->id;
2917     }
2919     $ras = get_user_roles($context, $userid);
2921     // Add front-page role if relevant.
2922     $defaultfrontpageroleid = isset($CFG->defaultfrontpageroleid) ? $CFG->defaultfrontpageroleid : 0;
2923     $isfrontpage = ($context->contextlevel == CONTEXT_COURSE && $context->instanceid == SITEID) ||
2924             is_inside_frontpage($context);
2925     if ($defaultfrontpageroleid && $isfrontpage) {
2926         $frontpagecontext = context_course::instance(SITEID);
2927         $ra = new stdClass();
2928         $ra->userid = $userid;
2929         $ra->contextid = $frontpagecontext->id;
2930         $ra->roleid = $defaultfrontpageroleid;
2931         $ras[] = $ra;
2932     }
2934     // Add authenticated user role if relevant.
2935     $defaultuserroleid      = isset($CFG->defaultuserroleid) ? $CFG->defaultuserroleid : 0;
2936     if ($defaultuserroleid && !isguestuser($userid)) {
2937         $systemcontext = context_system::instance();
2938         $ra = new stdClass();
2939         $ra->userid = $userid;
2940         $ra->contextid = $systemcontext->id;
2941         $ra->roleid = $defaultuserroleid;
2942         $ras[] = $ra;
2943     }
2945     return $ras;
2948 /**
2949  * Creates a record in the role_allow_override table
2950  *
2951  * @param int $fromroleid source roleid
2952  * @param int $targetroleid target roleid
2953  * @return void
2954  */
2955 function core_role_set_override_allowed($fromroleid, $targetroleid) {
2956     global $DB;
2958     $record = new stdClass();
2959     $record->roleid        = $fromroleid;
2960     $record->allowoverride = $targetroleid;
2961     $DB->insert_record('role_allow_override', $record);
2964 /**
2965  * Creates a record in the role_allow_assign table
2966  *
2967  * @param int $fromroleid source roleid
2968  * @param int $targetroleid target roleid
2969  * @return void
2970  */
2971 function core_role_set_assign_allowed($fromroleid, $targetroleid) {
2972     global $DB;
2974     $record = new stdClass();
2975     $record->roleid      = $fromroleid;
2976     $record->allowassign = $targetroleid;
2977     $DB->insert_record('role_allow_assign', $record);
2980 /**
2981  * Creates a record in the role_allow_switch table
2982  *
2983  * @param int $fromroleid source roleid
2984  * @param int $targetroleid target roleid
2985  * @return void
2986  */
2987 function core_role_set_switch_allowed($fromroleid, $targetroleid) {
2988     global $DB;
2990     $record = new stdClass();
2991     $record->roleid      = $fromroleid;
2992     $record->allowswitch = $targetroleid;
2993     $DB->insert_record('role_allow_switch', $record);
2996 /**
2997  * Creates a record in the role_allow_view table
2998  *
2999  * @param int $fromroleid source roleid
3000  * @param int $targetroleid target roleid
3001  * @return void
3002  */
3003 function core_role_set_view_allowed($fromroleid, $targetroleid) {
3004     global $DB;
3006     $record = new stdClass();
3007     $record->roleid      = $fromroleid;
3008     $record->allowview = $targetroleid;
3009     $DB->insert_record('role_allow_view', $record);
3012 /**
3013  * Gets a list of roles that this user can assign in this context
3014  *
3015  * @param context $context the context.
3016  * @param int $rolenamedisplay the type of role name to display. One of the
3017  *      ROLENAME_X constants. Default ROLENAME_ALIAS.
3018  * @param bool $withusercounts if true, count the number of users with each role.
3019  * @param integer|object $user A user id or object. By default (null) checks the permissions of the current user.
3020  * @return array if $withusercounts is false, then an array $roleid => $rolename.
3021  *      if $withusercounts is true, returns a list of three arrays,
3022  *      $rolenames, $rolecounts, and $nameswithcounts.
3023  */
3024 function get_assignable_roles(context $context, $rolenamedisplay = ROLENAME_ALIAS, $withusercounts = false, $user = null) {
3025     global $USER, $DB;
3027     // make sure there is a real user specified
3028     if ($user === null) {
3029         $userid = isset($USER->id) ? $USER->id : 0;
3030     } else {
3031         $userid = is_object($user) ? $user->id : $user;
3032     }
3034     if (!has_capability('moodle/role:assign', $context, $userid)) {
3035         if ($withusercounts) {
3036             return array(array(), array(), array());
3037         } else {
3038             return array();
3039         }
3040     }
3042     $params = array();
3043     $extrafields = '';
3045     if ($withusercounts) {
3046         $extrafields = ', (SELECT COUNT(DISTINCT u.id)
3047                              FROM {role_assignments} cra JOIN {user} u ON cra.userid = u.id
3048                             WHERE cra.roleid = r.id AND cra.contextid = :conid AND u.deleted = 0
3049                           ) AS usercount';
3050         $params['conid'] = $context->id;
3051     }
3053     if (is_siteadmin($userid)) {
3054         // show all roles allowed in this context to admins
3055         $assignrestriction = "";
3056     } else {
3057         $parents = $context->get_parent_context_ids(true);
3058         $contexts = implode(',' , $parents);
3059         $assignrestriction = "JOIN (SELECT DISTINCT raa.allowassign AS id
3060                                       FROM {role_allow_assign} raa
3061                                       JOIN {role_assignments} ra ON ra.roleid = raa.roleid
3062                                      WHERE ra.userid = :userid AND ra.contextid IN ($contexts)
3063                                    ) ar ON ar.id = r.id";
3064         $params['userid'] = $userid;
3065     }
3066     $params['contextlevel'] = $context->contextlevel;
3068     if ($coursecontext = $context->get_course_context(false)) {
3069         $params['coursecontext'] = $coursecontext->id;
3070     } else {
3071         $params['coursecontext'] = 0; // no course aliases
3072         $coursecontext = null;
3073     }
3074     $sql = "SELECT r.id, r.name, r.shortname, rn.name AS coursealias $extrafields
3075               FROM {role} r
3076               $assignrestriction
3077               JOIN {role_context_levels} rcl ON (rcl.contextlevel = :contextlevel AND r.id = rcl.roleid)
3078          LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
3079           ORDER BY r.sortorder ASC";
3080     $roles = $DB->get_records_sql($sql, $params);
3082     $rolenames = role_fix_names($roles, $coursecontext, $rolenamedisplay, true);
3084     if (!$withusercounts) {
3085         return $rolenames;
3086     }
3088     $rolecounts = array();
3089     $nameswithcounts = array();
3090     foreach ($roles as $role) {
3091         $nameswithcounts[$role->id] = $rolenames[$role->id] . ' (' . $roles[$role->id]->usercount . ')';
3092         $rolecounts[$role->id] = $roles[$role->id]->usercount;
3093     }
3094     return array($rolenames, $rolecounts, $nameswithcounts);
3097 /**
3098  * Gets a list of roles that this user can switch to in a context
3099  *
3100  * Gets a list of roles that this user can switch to in a context, for the switchrole menu.
3101  * This function just process the contents of the role_allow_switch table. You also need to
3102  * test the moodle/role:switchroles to see if the user is allowed to switch in the first place.
3103  *
3104  * @param context $context a context.
3105  * @return array an array $roleid => $rolename.
3106  */
3107 function get_switchable_roles(context $context) {
3108     global $USER, $DB;
3110     // You can't switch roles without this capability.
3111     if (!has_capability('moodle/role:switchroles', $context)) {
3112         return [];
3113     }
3115     $params = array();
3116     $extrajoins = '';
3117     $extrawhere = '';
3118     if (!is_siteadmin()) {
3119         // Admins are allowed to switch to any role with.
3120         // Others are subject to the additional constraint that the switch-to role must be allowed by
3121         // 'role_allow_switch' for some role they have assigned in this context or any parent.
3122         $parents = $context->get_parent_context_ids(true);
3123         $contexts = implode(',' , $parents);
3125         $extrajoins = "JOIN {role_allow_switch} ras ON ras.allowswitch = rc.roleid
3126         JOIN {role_assignments} ra ON ra.roleid = ras.roleid";
3127         $extrawhere = "WHERE ra.userid = :userid AND ra.contextid IN ($contexts)";
3128         $params['userid'] = $USER->id;
3129     }
3131     if ($coursecontext = $context->get_course_context(false)) {
3132         $params['coursecontext'] = $coursecontext->id;
3133     } else {
3134         $params['coursecontext'] = 0; // no course aliases
3135         $coursecontext = null;
3136     }
3138     $query = "
3139         SELECT r.id, r.name, r.shortname, rn.name AS coursealias
3140           FROM (SELECT DISTINCT rc.roleid
3141                   FROM {role_capabilities} rc
3143                   $extrajoins
3144                   $extrawhere) idlist
3145           JOIN {role} r ON r.id = idlist.roleid
3146      LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
3147       ORDER BY r.sortorder";
3148     $roles = $DB->get_records_sql($query, $params);
3150     return role_fix_names($roles, $context, ROLENAME_ALIAS, true);
3153 /**
3154  * Gets a list of roles that this user can view in a context
3155  *
3156  * @param context $context a context.
3157  * @param int $userid id of user.
3158  * @return array an array $roleid => $rolename.
3159  */
3160 function get_viewable_roles(context $context, $userid = null) {
3161     global $USER, $DB;
3163     if ($userid == null) {
3164         $userid = $USER->id;
3165     }
3167     $params = array();
3168     $extrajoins = '';
3169     $extrawhere = '';
3170     if (!is_siteadmin()) {
3171         // Admins are allowed to view any role.
3172         // Others are subject to the additional constraint that the view role must be allowed by
3173         // 'role_allow_view' for some role they have assigned in this context or any parent.
3174         $contexts = $context->get_parent_context_ids(true);
3175         list($insql, $inparams) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED);
3177         $extrajoins = "JOIN {role_allow_view} ras ON ras.allowview = r.id
3178                        JOIN {role_assignments} ra ON ra.roleid = ras.roleid";
3179         $extrawhere = "WHERE ra.userid = :userid AND ra.contextid $insql";
3181         $params += $inparams;
3182         $params['userid'] = $userid;
3183     }
3185     if ($coursecontext = $context->get_course_context(false)) {
3186         $params['coursecontext'] = $coursecontext->id;
3187     } else {
3188         $params['coursecontext'] = 0; // No course aliases.
3189         $coursecontext = null;
3190     }
3192     $query = "
3193         SELECT r.id, r.name, r.shortname, rn.name AS coursealias, r.sortorder
3194           FROM {role} r
3195           $extrajoins
3196      LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
3197           $extrawhere
3198       GROUP BY r.id, r.name, r.shortname, rn.name, r.sortorder
3199       ORDER BY r.sortorder";
3200     $roles = $DB->get_records_sql($query, $params);
3202     return role_fix_names($roles, $context, ROLENAME_ALIAS, true);
3205 /**
3206  * Gets a list of roles that this user can override in this context.
3207  *
3208  * @param context $context the context.
3209  * @param int $rolenamedisplay the type of role name to display. One of the
3210  *      ROLENAME_X constants. Default ROLENAME_ALIAS.
3211  * @param bool $withcounts if true, count the number of overrides that are set for each role.
3212  * @return array if $withcounts is false, then an array $roleid => $rolename.
3213  *      if $withusercounts is true, returns a list of three arrays,
3214  *      $rolenames, $rolecounts, and $nameswithcounts.
3215  */
3216 function get_overridable_roles(context $context, $rolenamedisplay = ROLENAME_ALIAS, $withcounts = false) {
3217     global $USER, $DB;
3219     if (!has_any_capability(array('moodle/role:safeoverride', 'moodle/role:override'), $context)) {
3220         if ($withcounts) {
3221             return array(array(), array(), array());
3222         } else {
3223             return array();
3224         }
3225     }
3227     $parents = $context->get_parent_context_ids(true);
3228     $contexts = implode(',' , $parents);
3230     $params = array();
3231     $extrafields = '';
3233     $params['userid'] = $USER->id;
3234     if ($withcounts) {
3235         $extrafields = ', (SELECT COUNT(rc.id) FROM {role_capabilities} rc
3236                 WHERE rc.roleid = ro.id AND rc.contextid = :conid) AS overridecount';
3237         $params['conid'] = $context->id;
3238     }
3240     if ($coursecontext = $context->get_course_context(false)) {
3241         $params['coursecontext'] = $coursecontext->id;
3242     } else {
3243         $params['coursecontext'] = 0; // no course aliases
3244         $coursecontext = null;
3245     }
3247     if (is_siteadmin()) {
3248         // show all roles to admins
3249         $roles = $DB->get_records_sql("
3250             SELECT ro.id, ro.name, ro.shortname, rn.name AS coursealias $extrafields
3251               FROM {role} ro
3252          LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = ro.id)
3253           ORDER BY ro.sortorder ASC", $params);
3255     } else {
3256         $roles = $DB->get_records_sql("
3257             SELECT ro.id, ro.name, ro.shortname, rn.name AS coursealias $extrafields
3258               FROM {role} ro
3259               JOIN (SELECT DISTINCT r.id
3260                       FROM {role} r
3261                       JOIN {role_allow_override} rao ON r.id = rao.allowoverride
3262                       JOIN {role_assignments} ra ON rao.roleid = ra.roleid
3263                      WHERE ra.userid = :userid AND ra.contextid IN ($contexts)
3264                    ) inline_view ON ro.id = inline_view.id
3265          LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = ro.id)
3266           ORDER BY ro.sortorder ASC", $params);
3267     }
3269     $rolenames = role_fix_names($roles, $context, $rolenamedisplay, true);
3271     if (!$withcounts) {
3272         return $rolenames;
3273     }
3275     $rolecounts = array();
3276     $nameswithcounts = array();
3277     foreach ($roles as $role) {
3278         $nameswithcounts[$role->id] = $rolenames[$role->id] . ' (' . $roles[$role->id]->overridecount . ')';
3279         $rolecounts[$role->id] = $roles[$role->id]->overridecount;
3280     }
3281     return array($rolenames, $rolecounts, $nameswithcounts);
3284 /**
3285  * Create a role menu suitable for default role selection in enrol plugins.
3286  *
3287  * @package    core_enrol
3288  *
3289  * @param context $context
3290  * @param int $addroleid current or default role - always added to list
3291  * @return array roleid=>localised role name
3292  */
3293 function get_default_enrol_roles(context $context, $addroleid = null) {
3294     global $DB;
3296     $params = array('contextlevel'=>CONTEXT_COURSE);
3298     if ($coursecontext = $context->get_course_context(false)) {
3299         $params['coursecontext'] = $coursecontext->id;
3300     } else {
3301         $params['coursecontext'] = 0; // no course names
3302         $coursecontext = null;
3303     }
3305     if ($addroleid) {
3306         $addrole = "OR r.id = :addroleid";
3307         $params['addroleid'] = $addroleid;
3308     } else {
3309         $addrole = "";
3310     }
3312     $sql = "SELECT r.id, r.name, r.shortname, rn.name AS coursealias
3313               FROM {role} r
3314          LEFT JOIN {role_context_levels} rcl ON (rcl.roleid = r.id AND rcl.contextlevel = :contextlevel)
3315          LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
3316              WHERE rcl.id IS NOT NULL $addrole
3317           ORDER BY sortorder DESC";
3319     $roles = $DB->get_records_sql($sql, $params);
3321     return role_fix_names($roles, $context, ROLENAME_BOTH, true);
3324 /**
3325  * Return context levels where this role is assignable.
3326  *
3327  * @param integer $roleid the id of a role.
3328  * @return array list of the context levels at which this role may be assigned.
3329  */
3330 function get_role_contextlevels($roleid) {
3331     global $DB;
3332     return $DB->get_records_menu('role_context_levels', array('roleid' => $roleid),
3333             'contextlevel', 'id,contextlevel');
3336 /**
3337  * Return roles suitable for assignment at the specified context level.
3338  *
3339  * NOTE: this function name looks like a typo, should be probably get_roles_for_contextlevel()
3340  *
3341  * @param integer $contextlevel a contextlevel.
3342  * @return array list of role ids that are assignable at this context level.
3343  */
3344 function get_roles_for_contextlevels($contextlevel) {
3345     global $DB;
3346     return $DB->get_records_menu('role_context_levels', array('contextlevel' => $contextlevel),
3347             '', 'id,roleid');
3350 /**
3351  * Returns default context levels where roles can be assigned.
3352  *
3353  * @param string $rolearchetype one of the role archetypes - that is, one of the keys
3354  *      from the array returned by get_role_archetypes();
3355  * @return array list of the context levels at which this type of role may be assigned by default.
3356  */
3357 function get_default_contextlevels($rolearchetype) {
3358     static $defaults = array(
3359         'manager'        => array(CONTEXT_SYSTEM, CONTEXT_COURSECAT, CONTEXT_COURSE),
3360         'coursecreator'  => array(CONTEXT_SYSTEM, CONTEXT_COURSECAT),
3361         'editingteacher' => array(CONTEXT_COURSE, CONTEXT_MODULE),
3362         'teacher'        => array(CONTEXT_COURSE, CONTEXT_MODULE),
3363         'student'        => array(CONTEXT_COURSE, CONTEXT_MODULE),
3364         'guest'          => array(),
3365         'user'           => array(),
3366         'frontpage'      => array());
3368     if (isset($defaults[$rolearchetype])) {
3369         return $defaults[$rolearchetype];
3370     } else {
3371         return array();
3372     }
3375 /**
3376  * Set the context levels at which a particular role can be assigned.
3377  * Throws exceptions in case of error.
3378  *
3379  * @param integer $roleid the id of a role.
3380  * @param array $contextlevels the context levels at which this role should be assignable,
3381  *      duplicate levels are removed.
3382  * @return void
3383  */
3384 function set_role_contextlevels($roleid, array $contextlevels) {
3385     global $DB;
3386     $DB->delete_records('role_context_levels', array('roleid' => $roleid));
3387     $rcl = new stdClass();
3388     $rcl->roleid = $roleid;
3389     $contextlevels = array_unique($contextlevels);
3390     foreach ($contextlevels as $level) {
3391         $rcl->contextlevel = $level;
3392         $DB->insert_record('role_context_levels', $rcl, false, true);
3393     }
3396 /**
3397  * Who has this capability in this context?
3398  *
3399  * This can be a very expensive call - use sparingly and keep
3400  * the results if you are going to need them again soon.
3401  *
3402  * Note if $fields is empty this function attempts to get u.*
3403  * which can get rather large - and has a serious perf impact
3404  * on some DBs.
3405  *
3406  * @param context $context
3407  * @param string|array $capability - capability name(s)
3408  * @param string $fields - fields to be pulled. The user table is aliased to 'u'. u.id MUST be included.
3409  * @param string $sort - the sort order. Default is lastaccess time.
3410  * @param mixed $limitfrom - number of records to skip (offset)
3411  * @param mixed $limitnum - number of records to fetch
3412  * @param string|array $groups - single group or array of groups - only return
3413  *               users who are in one of these group(s).
3414  * @param string|array $exceptions - list of users to exclude, comma separated or array
3415  * @param bool $doanything_ignored not used any more, admin accounts are never returned
3416  * @param bool $view_ignored - use get_enrolled_sql() instead
3417  * @param bool $useviewallgroups if $groups is set the return users who
3418  *               have capability both $capability and moodle/site:accessallgroups
3419  *               in this context, as well as users who have $capability and who are
3420  *               in $groups.
3421  * @return array of user records
3422  */
3423 function get_users_by_capability(context $context, $capability, $fields = '', $sort = '', $limitfrom = '', $limitnum = '',
3424                                  $groups = '', $exceptions = '', $doanything_ignored = null, $view_ignored = null, $useviewallgroups = false) {
3425     global $CFG, $DB;
3427     $defaultuserroleid      = isset($CFG->defaultuserroleid) ? $CFG->defaultuserroleid : 0;
3428     $defaultfrontpageroleid = isset($CFG->defaultfrontpageroleid) ? $CFG->defaultfrontpageroleid : 0;
3430     $ctxids = trim($context->path, '/');
3431     $ctxids = str_replace('/', ',', $ctxids);
3433     // Context is the frontpage
3434     $iscoursepage = false; // coursepage other than fp
3435     $isfrontpage = false;
3436     if ($context->contextlevel == CONTEXT_COURSE) {
3437         if ($context->instanceid == SITEID) {
3438             $isfrontpage = true;
3439         } else {
3440             $iscoursepage = true;
3441         }
3442     }
3443     $isfrontpage = ($isfrontpage || is_inside_frontpage($context));
3445     $caps = (array)$capability;
3447     // construct list of context paths bottom-->top
3448     list($contextids, $paths) = get_context_info_list($context);
3450     // we need to find out all roles that have these capabilities either in definition or in overrides
3451     $defs = array();
3452     list($incontexts, $params) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, 'con');
3453     list($incaps, $params2) = $DB->get_in_or_equal($caps, SQL_PARAMS_NAMED, 'cap');
3455     // Check whether context locking is enabled.
3456     // Filter out any write capability if this is the case.
3457     $excludelockedcaps = '';
3458     $excludelockedcapsparams = [];
3459     if (!empty($CFG->contextlocking) && $context->locked) {
3460         $excludelockedcaps = 'AND (cap.captype = :capread OR cap.name = :managelockscap)';
3461         $excludelockedcapsparams['capread'] = 'read';
3462         $excludelockedcapsparams['managelockscap'] = 'moodle/site:managecontextlocks';
3463     }
3465     $params = array_merge($params, $params2, $excludelockedcapsparams);
3466     $sql = "SELECT rc.id, rc.roleid, rc.permission, rc.capability, ctx.path
3467               FROM {role_capabilities} rc
3468               JOIN {capabilities} cap ON rc.capability = cap.name
3469               JOIN {context} ctx on rc.contextid = ctx.id
3470              WHERE rc.contextid $incontexts AND rc.capability $incaps $excludelockedcaps";
3472     $rcs = $DB->get_records_sql($sql, $params);
3473     foreach ($rcs as $rc) {
3474         $defs[$rc->capability][$rc->path][$rc->roleid] = $rc->permission;
3475     }
3477     // go through the permissions bottom-->top direction to evaluate the current permission,
3478     // first one wins (prohibit is an exception that always wins)
3479     $access = array();
3480     foreach ($caps as $cap) {
3481         foreach ($paths as $path) {
3482             if (empty($defs[$cap][$path])) {
3483                 continue;
3484             }
3485             foreach($defs[$cap][$path] as $roleid => $perm) {
3486                 if ($perm == CAP_PROHIBIT) {
3487                     $access[$cap][$roleid] = CAP_PROHIBIT;
3488                     continue;
3489                 }
3490                 if (!isset($access[$cap][$roleid])) {
3491                     $access[$cap][$roleid] = (int)$perm;
3492                 }
3493             }
3494         }
3495     }
3497     // make lists of roles that are needed and prohibited in this context
3498     $needed = array(); // one of these is enough
3499     $prohibited = array(); // must not have any of these
3500     foreach ($caps as $cap) {
3501         if (empty($access[$cap])) {
3502             continue;
3503         }
3504         foreach ($access[$cap] as $roleid => $perm) {
3505             if ($perm == CAP_PROHIBIT) {
3506                 unset($needed[$cap][$roleid]);
3507                 $prohibited[$cap][$roleid] = true;
3508             } else if ($perm == CAP_ALLOW and empty($prohibited[$cap][$roleid])) {
3509                 $needed[$cap][$roleid] = true;
3510             }
3511         }
3512         if (empty($needed[$cap]) or !empty($prohibited[$cap][$defaultuserroleid])) {
3513             // easy, nobody has the permission
3514             unset($needed[$cap]);
3515             unset($prohibited[$cap]);
3516         } else if ($isfrontpage and !empty($prohibited[$cap][$defaultfrontpageroleid])) {
3517             // everybody is disqualified on the frontpage
3518             unset($needed[$cap]);
3519             unset($prohibited[$cap]);
3520         }
3521         if (empty($prohibited[$cap])) {
3522             unset($prohibited[$cap]);
3523         }
3524     }
3526     if (empty($needed)) {
3527         // there can not be anybody if no roles match this request
3528         return array();
3529     }
3531     if (empty($prohibited)) {
3532         // we can compact the needed roles
3533         $n = array();
3534         foreach ($needed as $cap) {
3535             foreach ($cap as $roleid=>$unused) {
3536                 $n[$roleid] = true;
3537             }
3538         }
3539         $needed = array('any'=>$n);
3540         unset($n);
3541     }
3543     // ***** Set up default fields ******
3544     if (empty($fields)) {
3545         if ($iscoursepage) {
3546             $fields = 'u.*, ul.timeaccess AS lastaccess';
3547         } else {
3548             $fields = 'u.*';
3549         }
3550     } else {
3551         if ($CFG->debugdeveloper && strpos($fields, 'u.*') === false && strpos($fields, 'u.id') === false) {
3552             debugging('u.id must be included in the list of fields passed to get_users_by_capability().', DEBUG_DEVELOPER);
3553         }
3554     }
3556     // Set up default sort
3557     if (empty($sort)) { // default to course lastaccess or just lastaccess
3558         if ($iscoursepage) {
3559             $sort = 'ul.timeaccess';
3560         } else {
3561             $sort = 'u.lastaccess';
3562         }
3563     }
3565     // Prepare query clauses
3566     $wherecond = array();
3567     $params    = array();
3568     $joins     = array();
3570     // User lastaccess JOIN
3571     if ((strpos($sort, 'ul.timeaccess') === false) and (strpos($fields, 'ul.timeaccess') === false)) {
3572          // user_lastaccess is not required MDL-13810
3573     } else {
3574         if ($iscoursepage) {
3575             $joins[] = "LEFT OUTER JOIN {user_lastaccess} ul ON (ul.userid = u.id AND ul.courseid = {$context->instanceid})";
3576         } else {
3577             throw new coding_exception('Invalid sort in get_users_by_capability(), ul.timeaccess allowed only for course contexts.');
3578         }
3579     }
3581     // We never return deleted users or guest account.
3582     $wherecond[] = "u.deleted = 0 AND u.id <> :guestid";
3583     $params['guestid'] = $CFG->siteguest;
3585     // Groups
3586     if ($groups) {
3587         $groups = (array)$groups;
3588         list($grouptest, $grpparams) = $DB->get_in_or_equal($groups, SQL_PARAMS_NAMED, 'grp');
3589         $joins[] = "LEFT OUTER JOIN (SELECT DISTINCT userid
3590                                        FROM {groups_members}
3591                                       WHERE groupid $grouptest
3592                                     ) gm ON gm.userid = u.id";
3594         $params = array_merge($params, $grpparams);
3596         $grouptest = 'gm.userid IS NOT NULL';
3597         if ($useviewallgroups) {
3598             $viewallgroupsusers = get_users_by_capability($context, 'moodle/site:accessallgroups', 'u.id, u.id', '', '', '', '', $exceptions);
3599             if (!empty($viewallgroupsusers)) {
3600                 $grouptest .= ' OR u.id IN (' . implode(',', array_keys($viewallgroupsusers)) . ')';
3601             }
3602         }
3603         $wherecond[] = "($grouptest)";
3604     }
3606     // User exceptions
3607     if (!empty($exceptions)) {
3608         $exceptions = (array)$exceptions;
3609         list($exsql, $exparams) = $DB->get_in_or_equal($exceptions, SQL_PARAMS_NAMED, 'exc', false);
3610         $params = array_merge($params, $exparams);
3611         $wherecond[] = "u.id $exsql";
3612     }
3614     // now add the needed and prohibited roles conditions as joins
3615     if (!empty($needed['any'])) {
3616         // simple case - there are no prohibits involved
3617         if (!empty($needed['any'][$defaultuserroleid]) or ($isfrontpage and !empty($needed['any'][$defaultfrontpageroleid]))) {
3618             // everybody
3619         } else {
3620             $joins[] = "JOIN (SELECT DISTINCT userid
3621                                 FROM {role_assignments}
3622                                WHERE contextid IN ($ctxids)
3623                                      AND roleid IN (".implode(',', array_keys($needed['any'])) .")
3624                              ) ra ON ra.userid = u.id";
3625         }
3626     } else {
3627         $unions = array();
3628         $everybody = false;
3629         foreach ($needed as $cap=>$unused) {
3630             if (empty($prohibited[$cap])) {
3631                 if (!empty($needed[$cap][$defaultuserroleid]) or ($isfrontpage and !empty($needed[$cap][$defaultfrontpageroleid]))) {
3632                     $everybody = true;
3633                     break;
3634                 } else {
3635                     $unions[] = "SELECT userid
3636                                    FROM {role_assignments}
3637                                   WHERE contextid IN ($ctxids)
3638                                         AND roleid IN (".implode(',', array_keys($needed[$cap])) .")";
3639                 }
3640             } else {
3641                 if (!empty($prohibited[$cap][$defaultuserroleid]) or ($isfrontpage and !empty($prohibited[$cap][$defaultfrontpageroleid]))) {
3642                     // nobody can have this cap because it is prevented in default roles
3643                     continue;
3645                 } else if (!empty($needed[$cap][$defaultuserroleid]) or ($isfrontpage and !empty($needed[$cap][$defaultfrontpageroleid]))) {
3646                     // everybody except the prohibitted - hiding does not matter
3647                     $unions[] = "SELECT id AS userid
3648                                    FROM {user}
3649                                   WHERE id NOT IN (SELECT userid
3650                                                      FROM {role_assignments}
3651                                                     WHERE contextid IN ($ctxids)
3652                                                           AND roleid IN (".implode(',', array_keys($prohibited[$cap])) ."))";
3654                 } else {
3655                     $unions[] = "SELECT userid
3656                                    FROM {role_assignments}
3657                                   WHERE contextid IN ($ctxids) AND roleid IN (".implode(',', array_keys($needed[$cap])) .")
3658                                         AND userid NOT IN (
3659                                             SELECT userid
3660                                               FROM {role_assignments}
3661                                              WHERE contextid IN ($ctxids)
3662                                                     AND roleid IN (" . implode(',', array_keys($prohibited[$cap])) . ")
3663                                                         )";
3664                 }
3665             }
3666         }
3667         if (!$everybody) {