MDL-68974 admin: Prevent login as outside of the desired context
[moodle.git] / lib / accesslib.php
CommitLineData
46808d7c 1<?php
117bd748
PS
2// This file is part of Moodle - http://moodle.org/
3//
46808d7c 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.
117bd748 13//
46808d7c 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/>.
6cdd0f9c 16
92e53168 17/**
46808d7c 18 * This file contains functions for managing user access
19 *
cc3edaa4 20 * <b>Public API vs internals</b>
5a4e7398 21 *
92e53168 22 * General users probably only care about
23 *
dcd6a775 24 * Context handling
e922fe23
PS
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();
5a4e7398 29 *
dcd6a775 30 * Whether the user can do something...
92e53168 31 * - has_capability()
8a1b1c32 32 * - has_any_capability()
33 * - has_all_capabilities()
efd6fce5 34 * - require_capability()
dcd6a775 35 * - require_login() (from moodlelib)
f76249cc
PS
36 * - is_enrolled()
37 * - is_viewing()
38 * - is_guest()
e922fe23 39 * - is_siteadmin()
f76249cc
PS
40 * - isguestuser()
41 * - isloggedin()
dcd6a775 42 *
43 * What courses has this user access to?
e922fe23 44 * - get_enrolled_users()
dcd6a775 45 *
db70c4bd 46 * What users can do X in this context?
f76249cc
PS
47 * - get_enrolled_users() - at and bellow course context
48 * - get_users_by_capability() - above course context
db70c4bd 49 *
e922fe23
PS
50 * Modify roles
51 * - role_assign()
52 * - role_unassign()
53 * - role_unassign_all()
5a4e7398 54 *
e922fe23 55 * Advanced - for internal use only
dcd6a775 56 * - load_all_capabilities()
57 * - reload_all_capabilities()
bb2c22bd 58 * - has_capability_in_accessdata()
e705e69e 59 * - get_user_roles_sitewide_accessdata()
e922fe23 60 * - etc.
dcd6a775 61 *
cc3edaa4 62 * <b>Name conventions</b>
5a4e7398 63 *
cc3edaa4 64 * "ctx" means context
b2f349a4
JC
65 * "ra" means role assignment
66 * "rdef" means role definition
92e53168 67 *
cc3edaa4 68 * <b>accessdata</b>
92e53168 69 *
70 * Access control data is held in the "accessdata" array
71 * which - for the logged-in user, will be in $USER->access
5a4e7398 72 *
d867e696 73 * For other users can be generated and passed around (but may also be cached
e922fe23 74 * against userid in $ACCESSLIB_PRIVATE->accessdatabyuser).
92e53168 75 *
bb2c22bd 76 * $accessdata is a multidimensional array, holding
b2f349a4 77 * role assignments (RAs), role switches and initialization time.
92e53168 78 *
5a4e7398 79 * Things are keyed on "contextpaths" (the path field of
92e53168 80 * the context table) for fast walking up/down the tree.
cc3edaa4 81 * <code>
e922fe23
PS
82 * $accessdata['ra'][$contextpath] = array($roleid=>$roleid)
83 * [$contextpath] = array($roleid=>$roleid)
84 * [$contextpath] = array($roleid=>$roleid)
117bd748 85 * </code>
92e53168 86 *
cc3edaa4 87 * <b>Stale accessdata</b>
92e53168 88 *
89 * For the logged-in user, accessdata is long-lived.
90 *
d867e696 91 * On each pageload we load $ACCESSLIB_PRIVATE->dirtycontexts which lists
92e53168 92 * context paths affected by changes. Any check at-or-below
93 * a dirty context will trigger a transparent reload of accessdata.
5a4e7398 94 *
4f65e0fb 95 * Changes at the system level will force the reload for everyone.
92e53168 96 *
cc3edaa4 97 * <b>Default role caps</b>
5a4e7398 98 * The default role assignment is not in the DB, so we
99 * add it manually to accessdata.
92e53168 100 *
101 * This means that functions that work directly off the
102 * DB need to ensure that the default role caps
5a4e7398 103 * are dealt with appropriately.
92e53168 104 *
f76249cc 105 * @package core_access
78bfb562
PS
106 * @copyright 1999 onwards Martin Dougiamas http://dougiamas.com
107 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
92e53168 108 */
bbbf2d40 109
78bfb562
PS
110defined('MOODLE_INTERNAL') || die();
111
e922fe23 112/** No capability change */
e49e61bf 113define('CAP_INHERIT', 0);
e922fe23 114/** Allow permission, overrides CAP_PREVENT defined in parent contexts */
bbbf2d40 115define('CAP_ALLOW', 1);
e922fe23 116/** Prevent permission, overrides CAP_ALLOW defined in parent contexts */
bbbf2d40 117define('CAP_PREVENT', -1);
e922fe23 118/** Prohibit permission, overrides everything in current and child contexts */
bbbf2d40 119define('CAP_PROHIBIT', -1000);
120
e922fe23 121/** System context level - only one instance in every system */
bbbf2d40 122define('CONTEXT_SYSTEM', 10);
e922fe23 123/** User context level - one instance for each user describing what others can do to user */
4b10f08b 124define('CONTEXT_USER', 30);
e922fe23 125/** Course category context level - one instance for each category */
bbbf2d40 126define('CONTEXT_COURSECAT', 40);
e922fe23 127/** Course context level - one instances for each course */
bbbf2d40 128define('CONTEXT_COURSE', 50);
e922fe23 129/** Course module context level - one instance for each course module */
bbbf2d40 130define('CONTEXT_MODULE', 70);
e922fe23
PS
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 */
bbbf2d40 136define('CONTEXT_BLOCK', 80);
137
e922fe23 138/** Capability allow management of trusts - NOT IMPLEMENTED YET - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
21b6db6e 139define('RISK_MANAGETRUST', 0x0001);
e922fe23 140/** Capability allows changes in system configuration - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
a6b02b65 141define('RISK_CONFIG', 0x0002);
f76249cc 142/** Capability allows user to add scripted content - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
21b6db6e 143define('RISK_XSS', 0x0004);
e922fe23 144/** Capability allows access to personal user information - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
21b6db6e 145define('RISK_PERSONAL', 0x0008);
f76249cc 146/** Capability allows users to add content others may see - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
21b6db6e 147define('RISK_SPAM', 0x0010);
e922fe23 148/** capability allows mass delete of data belonging to other users - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
3a0c6cca 149define('RISK_DATALOSS', 0x0020);
21b6db6e 150
c52551dc 151/** rolename displays - the name as defined in the role definition, localised if name empty */
cc3edaa4 152define('ROLENAME_ORIGINAL', 0);
c52551dc 153/** rolename displays - the name as defined by a role alias at the course level, falls back to ROLENAME_ORIGINAL if alias not present */
cc3edaa4 154define('ROLENAME_ALIAS', 1);
e922fe23 155/** rolename displays - Both, like this: Role alias (Original) */
cc3edaa4 156define('ROLENAME_BOTH', 2);
e922fe23 157/** rolename displays - the name as defined in the role definition and the shortname in brackets */
cc3edaa4 158define('ROLENAME_ORIGINALANDSHORT', 3);
e922fe23 159/** rolename displays - the name as defined by a role alias, in raw form suitable for editing */
cc3edaa4 160define('ROLENAME_ALIAS_RAW', 4);
e922fe23 161/** rolename displays - the name is simply short role name */
df997f84 162define('ROLENAME_SHORT', 5);
cc3edaa4 163
e922fe23 164if (!defined('CONTEXT_CACHE_MAX_SIZE')) {
f76249cc 165 /** maximum size of context cache - it is possible to tweak this config.php or in any script before inclusion of context.php */
e922fe23 166 define('CONTEXT_CACHE_MAX_SIZE', 2500);
4d10247f 167}
168
cc3edaa4 169/**
117bd748 170 * Although this looks like a global variable, it isn't really.
cc3edaa4 171 *
117bd748
PS
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.
4f65e0fb 174 * Sadly, a PHP global variable is the only way to implement this, without rewriting everything
cc3edaa4 175 * as methods of a class, instead of functions.
176 *
f76249cc 177 * @access private
cc3edaa4 178 * @global stdClass $ACCESSLIB_PRIVATE
179 * @name $ACCESSLIB_PRIVATE
180 */
f14438dc 181global $ACCESSLIB_PRIVATE;
62e65b21 182$ACCESSLIB_PRIVATE = new stdClass();
4bdd7693 183$ACCESSLIB_PRIVATE->cacheroledefs = array(); // Holds site-wide role definitions.
e922fe23 184$ACCESSLIB_PRIVATE->dirtycontexts = null; // Dirty contexts cache, loaded from DB once per page
1d049e08 185$ACCESSLIB_PRIVATE->dirtyusers = null; // Dirty users cache, loaded from DB once per $USER->id
e922fe23 186$ACCESSLIB_PRIVATE->accessdatabyuser = array(); // Holds the cache of $accessdata structure for users (including $USER)
bbbf2d40 187
d867e696 188/**
46808d7c 189 * Clears accesslib's private caches. ONLY BE USED BY UNIT TESTS
117bd748 190 *
d867e696 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,
4f65e0fb 193 * and also at the end of the tests.
e922fe23 194 *
f76249cc 195 * @access private
e922fe23 196 * @return void
d867e696 197 */
198function accesslib_clear_all_caches_for_unit_testing() {
c8b3346c
PS
199 global $USER;
200 if (!PHPUNIT_TEST) {
d867e696 201 throw new coding_exception('You must not call clear_all_caches outside of unit tests.');
202 }
e922fe23
PS
203
204 accesslib_clear_all_caches(true);
b2f349a4 205 accesslib_reset_role_cache();
d867e696 206
207 unset($USER->access);
208}
209
46808d7c 210/**
e922fe23 211 * Clears accesslib's private caches. ONLY BE USED FROM THIS LIBRARY FILE!
46808d7c 212 *
e922fe23
PS
213 * This reset does not touch global $USER.
214 *
f76249cc 215 * @access private
e922fe23
PS
216 * @param bool $resetcontexts
217 * @return void
46808d7c 218 */
e922fe23
PS
219function accesslib_clear_all_caches($resetcontexts) {
220 global $ACCESSLIB_PRIVATE;
e7876c1e 221
e922fe23 222 $ACCESSLIB_PRIVATE->dirtycontexts = null;
1d049e08 223 $ACCESSLIB_PRIVATE->dirtyusers = null;
e922fe23 224 $ACCESSLIB_PRIVATE->accessdatabyuser = array();
e7876c1e 225
e922fe23
PS
226 if ($resetcontexts) {
227 context_helper::reset_caches();
e7876c1e 228 }
eef879ec 229}
230
b2f349a4
JC
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 */
243function accesslib_reset_role_cache() {
244 global $ACCESSLIB_PRIVATE;
245
246 $ACCESSLIB_PRIVATE->cacheroledefs = array();
247 $cache = cache::make('core', 'roledefs');
248 $cache->purge();
249}
250
4bdd7693
SK
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 */
260function accesslib_clear_role_cache($roles) {
261 global $ACCESSLIB_PRIVATE;
262
263 if (!is_array($roles)) {
264 $roles = [$roles];
265 }
266
267 foreach ($roles as $role) {
268 if (isset($ACCESSLIB_PRIVATE->cacheroledefs[$role])) {
269 unset($ACCESSLIB_PRIVATE->cacheroledefs[$role]);
270 }
271 }
272
273 $cache = cache::make('core', 'roledefs');
274 $cache->delete_many($roles);
275}
276
eef879ec 277/**
e705e69e 278 * Role is assigned at system context.
343effbe 279 *
f76249cc 280 * @access private
46808d7c 281 * @param int $roleid
e0376a62 282 * @return array
eef879ec 283 */
e922fe23 284function get_role_access($roleid) {
e705e69e
TL
285 $accessdata = get_empty_accessdata();
286 $accessdata['ra']['/'.SYSCONTEXTID] = array((int)$roleid => (int)$roleid);
287 return $accessdata;
288}
eef879ec 289
e705e69e
TL
290/**
291 * Fetch raw "site wide" role definitions.
4bdd7693
SK
292 * Even MUC static acceleration cache appears a bit slow for this.
293 * Important as can be hit hundreds of times per page.
e705e69e
TL
294 *
295 * @param array $roleids List of role ids to fetch definitions for.
e705e69e
TL
296 * @return array Complete definition for each requested role.
297 */
4bdd7693
SK
298function get_role_definitions(array $roleids) {
299 global $ACCESSLIB_PRIVATE;
e705e69e
TL
300
301 if (empty($roleids)) {
302 return array();
303 }
e7876c1e 304
4bdd7693
SK
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');
a1bc8928
TH
308 foreach ($cache->get_many($uncached) as $roleid => $cachedroledef) {
309 if (is_array($cachedroledef)) {
310 $ACCESSLIB_PRIVATE->cacheroledefs[$roleid] = $cachedroledef;
311 }
312 }
2c4bccd5 313
4bdd7693
SK
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 }
e705e69e 320 }
2c4bccd5 321
4bdd7693
SK
322 // Return just the roles we need.
323 return array_intersect_key($ACCESSLIB_PRIVATE->cacheroledefs, array_flip($roleids));
e705e69e
TL
324}
325
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 */
332function get_role_definitions_uncached(array $roleids) {
333 global $DB;
334
335 if (empty($roleids)) {
336 return array();
8d2b18a8 337 }
e0376a62 338
a1bc8928
TH
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.
e705e69e 342 $rdefs = array();
a1bc8928
TH
343 foreach ($roleids as $roleid) {
344 $rdefs[$roleid] = array();
345 }
e705e69e 346
a1bc8928
TH
347 // Load all the capabilities for these roles in all contexts.
348 list($sql, $params) = $DB->get_in_or_equal($roleids);
e705e69e 349 $sql = "SELECT ctx.path, rc.roleid, rc.capability, rc.permission
4bdd7693
SK
350 FROM {role_capabilities} rc
351 JOIN {context} ctx ON rc.contextid = ctx.id
c40f6adb 352 JOIN {capabilities} cap ON rc.capability = cap.name
aceb84ad 353 WHERE rc.roleid $sql";
e705e69e
TL
354 $rs = $DB->get_recordset_sql($sql, $params);
355
a1bc8928 356 // Store the capabilities into the expected data structure.
e705e69e
TL
357 foreach ($rs as $rd) {
358 if (!isset($rdefs[$rd->roleid][$rd->path])) {
e705e69e 359 $rdefs[$rd->roleid][$rd->path] = array();
4e1fe7d1 360 }
e705e69e 361 $rdefs[$rd->roleid][$rd->path][$rd->capability] = (int) $rd->permission;
4e1fe7d1 362 }
363
e705e69e 364 $rs->close();
aceb84ad
TM
365
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 }
373
e705e69e 374 return $rdefs;
4e1fe7d1 375}
376
8f8ed475 377/**
e922fe23
PS
378 * Get the default guest role, this is used for guest account,
379 * search engine spiders, etc.
117bd748 380 *
e922fe23 381 * @return stdClass role record
8f8ed475 382 */
383function get_guest_role() {
f33e1ed4 384 global $CFG, $DB;
ebce32b5 385
386 if (empty($CFG->guestroleid)) {
4f0c2d00 387 if ($roles = $DB->get_records('role', array('archetype'=>'guest'))) {
ebce32b5 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 }
8f8ed475 395 } else {
f33e1ed4 396 if ($guestrole = $DB->get_record('role', array('id'=>$CFG->guestroleid))) {
ebce32b5 397 return $guestrole;
398 } else {
e922fe23 399 // somebody is messing with guest roles, remove incorrect setting and try to find a new one
ebce32b5 400 set_config('guestroleid', '');
401 return get_guest_role();
402 }
8f8ed475 403 }
404}
405
128f0984 406/**
4f65e0fb 407 * Check whether a user has a particular capability in a given context.
46808d7c 408 *
e922fe23 409 * For example:
f76249cc
PS
410 * $context = context_module::instance($cm->id);
411 * has_capability('mod/forum:replypost', $context)
46808d7c 412 *
4f65e0fb 413 * By default checks the capabilities of the current user, but you can pass a
4f0c2d00
PS
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.
117bd748 418 *
f76249cc
PS
419 * @category access
420 *
41e87d30 421 * @param string $capability the name of the capability to check. For example mod/forum:view
f76249cc
PS
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.
4f0c2d00 424 * @param boolean $doanything If false, ignores effect of admin role assignment
41e87d30 425 * @return boolean true if the user has this capability. Otherwise false.
128f0984 426 */
e922fe23
PS
427function has_capability($capability, context $context, $user = null, $doanything = true) {
428 global $USER, $CFG, $SCRIPT, $ACCESSLIB_PRIVATE;
18818abf 429
31a99877 430 if (during_initial_install()) {
e938ea39
MG
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)) {
18818abf 436 // we are in an installer - roles can not work yet
437 return true;
438 } else {
439 return false;
440 }
441 }
7f97ea29 442
4f0c2d00
PS
443 if (strpos($capability, 'moodle/legacy:') === 0) {
444 throw new coding_exception('Legacy capabilities can not be used any more!');
445 }
446
e922fe23
PS
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 }
450
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.');
7d0c81b3 454 return false;
74ac5b66 455 }
e922fe23
PS
456
457 if (!isset($USER->id)) {
458 // should never happen
459 $USER->id = 0;
c90e6b46 460 debugging('Capability check being performed on a user with no ID.', DEBUG_DEVELOPER);
4f0c2d00 461 }
7f97ea29 462
4f0c2d00 463 // make sure there is a real user specified
62e65b21 464 if ($user === null) {
e922fe23 465 $userid = $USER->id;
4f0c2d00 466 } else {
2b55aca7 467 $userid = is_object($user) ? $user->id : $user;
cc3d5e10 468 }
469
e922fe23
PS
470 // make sure forcelogin cuts off not-logged-in users if enabled
471 if (!empty($CFG->forcelogin) and $userid == 0) {
4f0c2d00
PS
472 return false;
473 }
e922fe23 474
4f0c2d00 475 // make sure the guest account and not-logged-in users never get any risky caps no matter what the actual settings are.
e922fe23 476 if (($capinfo->captype === 'write') or ($capinfo->riskbitmask & (RISK_XSS | RISK_CONFIG | RISK_DATALOSS))) {
4f0c2d00
PS
477 if (isguestuser($userid) or $userid == 0) {
478 return false;
c84a2dbe 479 }
7f97ea29 480 }
481
0616f045
AN
482 // Check whether context locking is enabled.
483 if (!empty($CFG->contextlocking)) {
bb41db9c
AN
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 }
0616f045
AN
494 }
495 }
496
e922fe23
PS
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 }
128f0984 505 } else {
e922fe23
PS
506 if (!context_user::instance($userid, IGNORE_MISSING)) {
507 // no user context == invalid userid
508 return false;
509 }
7be3be1b 510 }
148eb2a7 511 }
128f0984 512
e922fe23
PS
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
188458a6 516 debugging('Context id '.$context->id.' does not have valid path, please use context_helper::build_all_paths()');
e922fe23
PS
517 if (is_siteadmin($userid)) {
518 return true;
128f0984 519 } else {
e922fe23 520 return false;
128f0984 521 }
148eb2a7 522 }
128f0984 523
81bb3a65
AN
524 if (!empty($USER->loginascontext)) {
525 // The current user is logged in as another user and can assume their identity at or below the `loginascontext`
526 // defined in the USER session.
527 // The user may not assume their identity at any other location.
528 if (!$USER->loginascontext->is_parent_of($context, true)) {
529 // The context being checked is not the specified context, or one of its children.
530 return false;
531 }
532 }
533
4f0c2d00
PS
534 // Find out if user is admin - it is not possible to override the doanything in any way
535 // and it is not possible to switch to admin role either.
536 if ($doanything) {
537 if (is_siteadmin($userid)) {
7abbc5c2
PS
538 if ($userid != $USER->id) {
539 return true;
540 }
541 // make sure switchrole is not used in this context
542 if (empty($USER->access['rsw'])) {
543 return true;
544 }
545 $parts = explode('/', trim($context->path, '/'));
546 $path = '';
547 $switched = false;
548 foreach ($parts as $part) {
549 $path .= '/' . $part;
550 if (!empty($USER->access['rsw'][$path])) {
551 $switched = true;
552 break;
553 }
554 }
555 if (!$switched) {
556 return true;
557 }
558 //ok, admin switched role in this context, let's use normal access control rules
4f0c2d00
PS
559 }
560 }
561
e922fe23
PS
562 // Careful check for staleness...
563 $context->reload_if_dirty();
13a79475 564
e922fe23
PS
565 if ($USER->id == $userid) {
566 if (!isset($USER->access)) {
567 load_all_capabilities();
7f97ea29 568 }
e922fe23 569 $access =& $USER->access;
bb2c22bd 570
e922fe23
PS
571 } else {
572 // make sure user accessdata is really loaded
573 get_user_accessdata($userid, true);
574 $access =& $ACCESSLIB_PRIVATE->accessdatabyuser[$userid];
204a369c 575 }
4f0c2d00 576
e922fe23 577 return has_capability_in_accessdata($capability, $context, $access);
7f97ea29 578}
579
3fc3ebf2 580/**
41e87d30 581 * Check if the user has any one of several capabilities from a list.
46808d7c 582 *
41e87d30 583 * This is just a utility method that calls has_capability in a loop. Try to put
584 * the capabilities that most users are likely to have first in the list for best
585 * performance.
3fc3ebf2 586 *
f76249cc 587 * @category access
46808d7c 588 * @see has_capability()
f76249cc 589 *
41e87d30 590 * @param array $capabilities an array of capability names.
f76249cc
PS
591 * @param context $context the context to check the capability in. You normally get this with instance method of a context class.
592 * @param integer|stdClass $user A user id or object. By default (null) checks the permissions of the current user.
4f0c2d00 593 * @param boolean $doanything If false, ignore effect of admin role assignment
41e87d30 594 * @return boolean true if the user has any of these capabilities. Otherwise false.
3fc3ebf2 595 */
f76249cc 596function has_any_capability(array $capabilities, context $context, $user = null, $doanything = true) {
3fc3ebf2 597 foreach ($capabilities as $capability) {
f76249cc 598 if (has_capability($capability, $context, $user, $doanything)) {
3fc3ebf2 599 return true;
600 }
601 }
602 return false;
603}
604
8a1b1c32 605/**
41e87d30 606 * Check if the user has all the capabilities in a list.
46808d7c 607 *
41e87d30 608 * This is just a utility method that calls has_capability in a loop. Try to put
609 * the capabilities that fewest users are likely to have first in the list for best
610 * performance.
8a1b1c32 611 *
f76249cc 612 * @category access
46808d7c 613 * @see has_capability()
f76249cc 614 *
41e87d30 615 * @param array $capabilities an array of capability names.
f76249cc
PS
616 * @param context $context the context to check the capability in. You normally get this with instance method of a context class.
617 * @param integer|stdClass $user A user id or object. By default (null) checks the permissions of the current user.
4f0c2d00 618 * @param boolean $doanything If false, ignore effect of admin role assignment
41e87d30 619 * @return boolean true if the user has all of these capabilities. Otherwise false.
8a1b1c32 620 */
f76249cc 621function has_all_capabilities(array $capabilities, context $context, $user = null, $doanything = true) {
8a1b1c32 622 foreach ($capabilities as $capability) {
f76249cc 623 if (!has_capability($capability, $context, $user, $doanything)) {
8a1b1c32 624 return false;
625 }
626 }
627 return true;
628}
629
a56ec918
PS
630/**
631 * Is course creator going to have capability in a new course?
632 *
633 * This is intended to be used in enrolment plugins before or during course creation,
634 * do not use after the course is fully created.
635 *
636 * @category access
637 *
638 * @param string $capability the name of the capability to check.
639 * @param context $context course or category context where is course going to be created
640 * @param integer|stdClass $user A user id or object. By default (null) checks the permissions of the current user.
641 * @return boolean true if the user will have this capability.
642 *
c6f5e84f 643 * @throws coding_exception if different type of context submitted
a56ec918 644 */
54d5308e 645function guess_if_creator_will_have_course_capability($capability, context $context, $user = null) {
a56ec918
PS
646 global $CFG;
647
648 if ($context->contextlevel != CONTEXT_COURSE and $context->contextlevel != CONTEXT_COURSECAT) {
649 throw new coding_exception('Only course or course category context expected');
650 }
651
652 if (has_capability($capability, $context, $user)) {
653 // User already has the capability, it could be only removed if CAP_PROHIBIT
654 // was involved here, but we ignore that.
655 return true;
656 }
657
658 if (!has_capability('moodle/course:create', $context, $user)) {
659 return false;
660 }
661
662 if (!enrol_is_enabled('manual')) {
663 return false;
664 }
665
666 if (empty($CFG->creatornewroleid)) {
667 return false;
668 }
669
670 if ($context->contextlevel == CONTEXT_COURSE) {
671 if (is_viewing($context, $user, 'moodle/role:assign') or is_enrolled($context, $user, 'moodle/role:assign')) {
672 return false;
673 }
674 } else {
675 if (has_capability('moodle/course:view', $context, $user) and has_capability('moodle/role:assign', $context, $user)) {
676 return false;
677 }
678 }
679
680 // Most likely they will be enrolled after the course creation is finished,
681 // does the new role have the required capability?
682 list($neededroles, $forbiddenroles) = get_roles_with_cap_in_context($context, $capability);
683 return isset($neededroles[$CFG->creatornewroleid]);
684}
685
128f0984 686/**
4f0c2d00 687 * Check if the user is an admin at the site level.
46808d7c 688 *
4f0c2d00
PS
689 * Please note that use of proper capabilities is always encouraged,
690 * this function is supposed to be used from core or for temporary hacks.
39407442 691 *
f76249cc
PS
692 * @category access
693 *
e922fe23
PS
694 * @param int|stdClass $user_or_id user id or user object
695 * @return bool true if user is one of the administrators, false otherwise
39407442 696 */
62e65b21 697function is_siteadmin($user_or_id = null) {
4f0c2d00 698 global $CFG, $USER;
39407442 699
62e65b21 700 if ($user_or_id === null) {
4f0c2d00
PS
701 $user_or_id = $USER;
702 }
6cab02ac 703
4f0c2d00
PS
704 if (empty($user_or_id)) {
705 return false;
706 }
707 if (!empty($user_or_id->id)) {
4f0c2d00
PS
708 $userid = $user_or_id->id;
709 } else {
710 $userid = $user_or_id;
711 }
6cab02ac 712
0d9e5992 713 // Because this script is called many times (150+ for course page) with
714 // the same parameters, it is worth doing minor optimisations. This static
715 // cache stores the value for a single userid, saving about 2ms from course
716 // page load time without using significant memory. As the static cache
717 // also includes the value it depends on, this cannot break unit tests.
718 static $knownid, $knownresult, $knownsiteadmins;
719 if ($knownid === $userid && $knownsiteadmins === $CFG->siteadmins) {
720 return $knownresult;
721 }
722 $knownid = $userid;
723 $knownsiteadmins = $CFG->siteadmins;
724
4f0c2d00 725 $siteadmins = explode(',', $CFG->siteadmins);
0d9e5992 726 $knownresult = in_array($userid, $siteadmins);
727 return $knownresult;
6cab02ac 728}
729
bbdb7070 730/**
4f0c2d00 731 * Returns true if user has at least one role assign
df997f84 732 * of 'coursecontact' role (is potentially listed in some course descriptions).
62e65b21 733 *
e922fe23
PS
734 * @param int $userid
735 * @return bool
bbdb7070 736 */
df997f84 737function has_coursecontact_role($userid) {
2fb34345 738 global $DB, $CFG;
bbdb7070 739
df997f84 740 if (empty($CFG->coursecontact)) {
4f0c2d00
PS
741 return false;
742 }
743 $sql = "SELECT 1
744 FROM {role_assignments}
df997f84 745 WHERE userid = :userid AND roleid IN ($CFG->coursecontact)";
b2cd6570 746 return $DB->record_exists_sql($sql, array('userid'=>$userid));
bbdb7070 747}
748
128f0984 749/**
01a2ce80 750 * Does the user have a capability to do something?
46808d7c 751 *
31c2de82 752 * Walk the accessdata array and return true/false.
e922fe23 753 * Deals with prohibits, role switching, aggregating
6a8d9a38 754 * capabilities, etc.
755 *
756 * The main feature of here is being FAST and with no
5a4e7398 757 * side effects.
6a8d9a38 758 *
3ac81bd1 759 * Notes:
760 *
3d034f77 761 * Switch Role merges with default role
762 * ------------------------------------
763 * If you are a teacher in course X, you have at least
764 * teacher-in-X + defaultloggedinuser-sitewide. So in the
765 * course you'll have techer+defaultloggedinuser.
766 * We try to mimic that in switchrole.
767 *
01a2ce80
PS
768 * Permission evaluation
769 * ---------------------
4f65e0fb 770 * Originally there was an extremely complicated way
01a2ce80 771 * to determine the user access that dealt with
4f65e0fb
PS
772 * "locality" or role assignments and role overrides.
773 * Now we simply evaluate access for each role separately
01a2ce80
PS
774 * and then verify if user has at least one role with allow
775 * and at the same time no role with prohibit.
776 *
f76249cc 777 * @access private
46808d7c 778 * @param string $capability
e922fe23 779 * @param context $context
46808d7c 780 * @param array $accessdata
46808d7c 781 * @return bool
6a8d9a38 782 */
e922fe23 783function has_capability_in_accessdata($capability, context $context, array &$accessdata) {
3ac81bd1 784 global $CFG;
785
01a2ce80 786 // Build $paths as a list of current + all parent "paths" with order bottom-to-top
e922fe23
PS
787 $path = $context->path;
788 $paths = array($path);
1ed243c2 789 while ($path = rtrim($path, '0123456789')) {
e922fe23
PS
790 $path = rtrim($path, '/');
791 if ($path === '') {
792 break;
793 }
794 $paths[] = $path;
3ac81bd1 795 }
796
01a2ce80
PS
797 $roles = array();
798 $switchedrole = false;
6a8d9a38 799
01a2ce80
PS
800 // Find out if role switched
801 if (!empty($accessdata['rsw'])) {
6a8d9a38 802 // From the bottom up...
01a2ce80 803 foreach ($paths as $path) {
1209cb5c 804 if (isset($accessdata['rsw'][$path])) {
01a2ce80 805 // Found a switchrole assignment - check for that role _plus_ the default user role
62e65b21 806 $roles = array($accessdata['rsw'][$path]=>null, $CFG->defaultuserroleid=>null);
01a2ce80
PS
807 $switchedrole = true;
808 break;
6a8d9a38 809 }
810 }
811 }
812
01a2ce80
PS
813 if (!$switchedrole) {
814 // get all users roles in this context and above
815 foreach ($paths as $path) {
816 if (isset($accessdata['ra'][$path])) {
817 foreach ($accessdata['ra'][$path] as $roleid) {
62e65b21 818 $roles[$roleid] = null;
7f97ea29 819 }
01a2ce80
PS
820 }
821 }
7f97ea29 822 }
823
01a2ce80 824 // Now find out what access is given to each role, going bottom-->up direction
e705e69e 825 $rdefs = get_role_definitions(array_keys($roles));
e922fe23 826 $allowed = false;
e705e69e 827
01a2ce80
PS
828 foreach ($roles as $roleid => $ignored) {
829 foreach ($paths as $path) {
e705e69e
TL
830 if (isset($rdefs[$roleid][$path][$capability])) {
831 $perm = (int)$rdefs[$roleid][$path][$capability];
e922fe23
PS
832 if ($perm === CAP_PROHIBIT) {
833 // any CAP_PROHIBIT found means no permission for the user
834 return false;
835 }
836 if (is_null($roles[$roleid])) {
01a2ce80
PS
837 $roles[$roleid] = $perm;
838 }
839 }
7f97ea29 840 }
e922fe23
PS
841 // CAP_ALLOW in any role means the user has a permission, we continue only to detect prohibits
842 $allowed = ($allowed or $roles[$roleid] === CAP_ALLOW);
7f97ea29 843 }
844
e922fe23 845 return $allowed;
018d4b52 846}
847
0468976c 848/**
41e87d30 849 * A convenience function that tests has_capability, and displays an error if
850 * the user does not have that capability.
8a9c1c1c 851 *
41e87d30 852 * NOTE before Moodle 2.0, this function attempted to make an appropriate
853 * require_login call before checking the capability. This is no longer the case.
854 * You must call require_login (or one of its variants) if you want to check the
855 * user is logged in, before you call this function.
efd6fce5 856 *
46808d7c 857 * @see has_capability()
858 *
41e87d30 859 * @param string $capability the name of the capability to check. For example mod/forum:view
bea86e84 860 * @param context $context the context to check the capability in. You normally get this with context_xxxx::instance().
e922fe23 861 * @param int $userid A user id. By default (null) checks the permissions of the current user.
4f0c2d00 862 * @param bool $doanything If false, ignore effect of admin role assignment
e922fe23 863 * @param string $errormessage The error string to to user. Defaults to 'nopermissions'.
41e87d30 864 * @param string $stringfile The language file to load the error string from. Defaults to 'error'.
865 * @return void terminates with an error if the user does not have the given capability.
0468976c 866 */
e922fe23 867function require_capability($capability, context $context, $userid = null, $doanything = true,
41e87d30 868 $errormessage = 'nopermissions', $stringfile = '') {
d74067e8 869 if (!has_capability($capability, $context, $userid, $doanything)) {
9a0df45a 870 throw new required_capability_exception($context, $capability, $errormessage, $stringfile);
0468976c 871 }
872}
873
01436f75
MP
874/**
875 * A convenience function that tests has_capability for a list of capabilities, and displays an error if
876 * the user does not have that capability.
877 *
878 * This is just a utility method that calls has_capability in a loop. Try to put
879 * the capabilities that fewest users are likely to have first in the list for best
880 * performance.
881 *
882 * @category access
883 * @see has_capability()
884 *
885 * @param array $capabilities an array of capability names.
886 * @param context $context the context to check the capability in. You normally get this with context_xxxx::instance().
887 * @param int $userid A user id. By default (null) checks the permissions of the current user.
888 * @param bool $doanything If false, ignore effect of admin role assignment
889 * @param string $errormessage The error string to to user. Defaults to 'nopermissions'.
890 * @param string $stringfile The language file to load the error string from. Defaults to 'error'.
891 * @return void terminates with an error if the user does not have the given capability.
892 */
893function require_all_capabilities(array $capabilities, context $context, $userid = null, $doanything = true,
894 $errormessage = 'nopermissions', $stringfile = ''): void {
895 foreach ($capabilities as $capability) {
896 if (!has_capability($capability, $context, $userid, $doanything)) {
897 throw new required_capability_exception($context, $capability, $errormessage, $stringfile);
898 }
899 }
900}
901
a9bee37e 902/**
e705e69e
TL
903 * Return a nested array showing all role assignments for the user.
904 * [ra] => [contextpath][roleid] = roleid
a9bee37e 905 *
f76249cc 906 * @access private
62e65b21 907 * @param int $userid - the id of the user
e922fe23 908 * @return array access info array
a9bee37e 909 */
e705e69e 910function get_user_roles_sitewide_accessdata($userid) {
4bdd7693 911 global $CFG, $DB;
a9bee37e 912
e922fe23 913 $accessdata = get_empty_accessdata();
a9bee37e 914
e922fe23
PS
915 // start with the default role
916 if (!empty($CFG->defaultuserroleid)) {
917 $syscontext = context_system::instance();
918 $accessdata['ra'][$syscontext->path][(int)$CFG->defaultuserroleid] = (int)$CFG->defaultuserroleid;
e922fe23
PS
919 }
920
921 // load the "default frontpage role"
922 if (!empty($CFG->defaultfrontpageroleid)) {
923 $frontpagecontext = context_course::instance(get_site()->id);
924 if ($frontpagecontext->path) {
925 $accessdata['ra'][$frontpagecontext->path][(int)$CFG->defaultfrontpageroleid] = (int)$CFG->defaultfrontpageroleid;
e922fe23
PS
926 }
927 }
928
e705e69e 929 // Preload every assigned role.
e7f2c2cc 930 $sql = "SELECT ctx.path, ra.roleid, ra.contextid
4bdd7693
SK
931 FROM {role_assignments} ra
932 JOIN {context} ctx ON ctx.id = ra.contextid
933 WHERE ra.userid = :userid";
e705e69e
TL
934
935 $rs = $DB->get_recordset_sql($sql, array('userid' => $userid));
936
e922fe23
PS
937 foreach ($rs as $ra) {
938 // RAs leafs are arrays to support multi-role assignments...
939 $accessdata['ra'][$ra->path][(int)$ra->roleid] = (int)$ra->roleid;
a9bee37e 940 }
a9bee37e 941
e922fe23 942 $rs->close();
a9bee37e 943
bb2c22bd 944 return $accessdata;
a9bee37e 945}
946
e922fe23
PS
947/**
948 * Returns empty accessdata structure.
99ddfdf4 949 *
f76249cc 950 * @access private
e922fe23
PS
951 * @return array empt accessdata
952 */
953function get_empty_accessdata() {
954 $accessdata = array(); // named list
955 $accessdata['ra'] = array();
e922fe23 956 $accessdata['time'] = time();
843ca761 957 $accessdata['rsw'] = array();
e922fe23 958
bb2c22bd 959 return $accessdata;
6f1bce30 960}
961
a2cf7f1b 962/**
e922fe23 963 * Get accessdata for a given user.
204a369c 964 *
f76249cc 965 * @access private
46808d7c 966 * @param int $userid
e922fe23
PS
967 * @param bool $preloadonly true means do not return access array
968 * @return array accessdata
5a4e7398 969 */
e922fe23
PS
970function get_user_accessdata($userid, $preloadonly=false) {
971 global $CFG, $ACCESSLIB_PRIVATE, $USER;
204a369c 972
e705e69e 973 if (isset($USER->access)) {
529b85b0 974 $ACCESSLIB_PRIVATE->accessdatabyuser[$USER->id] = $USER->access;
204a369c 975 }
976
e922fe23
PS
977 if (!isset($ACCESSLIB_PRIVATE->accessdatabyuser[$userid])) {
978 if (empty($userid)) {
979 if (!empty($CFG->notloggedinroleid)) {
980 $accessdata = get_role_access($CFG->notloggedinroleid);
981 } else {
982 // weird
983 return get_empty_accessdata();
984 }
985
986 } else if (isguestuser($userid)) {
987 if ($guestrole = get_guest_role()) {
988 $accessdata = get_role_access($guestrole->id);
989 } else {
990 //weird
991 return get_empty_accessdata();
992 }
993
994 } else {
4bdd7693
SK
995 // Includes default role and frontpage role.
996 $accessdata = get_user_roles_sitewide_accessdata($userid);
4e1fe7d1 997 }
128f0984 998
e922fe23
PS
999 $ACCESSLIB_PRIVATE->accessdatabyuser[$userid] = $accessdata;
1000 }
a2cf7f1b 1001
e922fe23
PS
1002 if ($preloadonly) {
1003 return;
1004 } else {
1005 return $ACCESSLIB_PRIVATE->accessdatabyuser[$userid];
1006 }
204a369c 1007}
ef989bd9 1008
6f1bce30 1009/**
46808d7c 1010 * A convenience function to completely load all the capabilities
e922fe23
PS
1011 * for the current user. It is called from has_capability() and functions change permissions.
1012 *
1013 * Call it only _after_ you've setup $USER and called check_enrolment_plugins();
46808d7c 1014 * @see check_enrolment_plugins()
117bd748 1015 *
f76249cc 1016 * @access private
62e65b21 1017 * @return void
2f1a4248 1018 */
1019function load_all_capabilities() {
e922fe23 1020 global $USER;
bbbf2d40 1021
18818abf 1022 // roles not installed yet - we are in the middle of installation
31a99877 1023 if (during_initial_install()) {
1045a007 1024 return;
1025 }
1026
e922fe23
PS
1027 if (!isset($USER->id)) {
1028 // this should not happen
1029 $USER->id = 0;
2f1a4248 1030 }
55e68c29 1031
e922fe23
PS
1032 unset($USER->access);
1033 $USER->access = get_user_accessdata($USER->id);
1034
55e68c29 1035 // Clear to force a refresh
e922fe23 1036 unset($USER->mycourses);
bbfdff34
PS
1037
1038 // init/reset internal enrol caches - active course enrolments and temp access
1039 $USER->enrol = array('enrolled'=>array(), 'tempguest'=>array());
bbbf2d40 1040}
1041
ef989bd9 1042/**
5a4e7398 1043 * A convenience function to completely reload all the capabilities
ef989bd9 1044 * for the current user when roles have been updated in a relevant
5a4e7398 1045 * context -- but PRESERVING switchroles and loginas.
e922fe23 1046 * This function resets all accesslib and context caches.
ef989bd9 1047 *
1048 * That is - completely transparent to the user.
5a4e7398 1049 *
e922fe23 1050 * Note: reloads $USER->access completely.
ef989bd9 1051 *
f76249cc 1052 * @access private
62e65b21 1053 * @return void
ef989bd9 1054 */
1055function reload_all_capabilities() {
e922fe23 1056 global $USER, $DB, $ACCESSLIB_PRIVATE;
ef989bd9 1057
ef989bd9 1058 // copy switchroles
1059 $sw = array();
843ca761 1060 if (!empty($USER->access['rsw'])) {
ef989bd9 1061 $sw = $USER->access['rsw'];
ef989bd9 1062 }
1063
e922fe23 1064 accesslib_clear_all_caches(true);
ef989bd9 1065 unset($USER->access);
1d049e08
JC
1066
1067 // Prevent dirty flags refetching on this page.
1068 $ACCESSLIB_PRIVATE->dirtycontexts = array();
1069 $ACCESSLIB_PRIVATE->dirtyusers = array($USER->id => false);
5a4e7398 1070
ef989bd9 1071 load_all_capabilities();
1072
1073 foreach ($sw as $path => $roleid) {
e922fe23
PS
1074 if ($record = $DB->get_record('context', array('path'=>$path))) {
1075 $context = context::instance_by_id($record->id);
e8ce6ca3
DM
1076 if (has_capability('moodle/role:switchroles', $context)) {
1077 role_switch($roleid, $context);
1078 }
e922fe23 1079 }
ef989bd9 1080 }
ef989bd9 1081}
2f1a4248 1082
f33e1ed4 1083/**
e922fe23 1084 * Adds a temp role to current USER->access array.
343effbe 1085 *
e922fe23 1086 * Useful for the "temporary guest" access we grant to logged-in users.
f76249cc 1087 * This is useful for enrol plugins only.
343effbe 1088 *
5bcfd504 1089 * @since Moodle 2.2
e922fe23 1090 * @param context_course $coursecontext
46808d7c 1091 * @param int $roleid
e922fe23 1092 * @return void
343effbe 1093 */
e922fe23 1094function load_temp_course_role(context_course $coursecontext, $roleid) {
bbfdff34 1095 global $USER, $SITE;
5a4e7398 1096
e922fe23
PS
1097 if (empty($roleid)) {
1098 debugging('invalid role specified in load_temp_course_role()');
1099 return;
1100 }
343effbe 1101
bbfdff34
PS
1102 if ($coursecontext->instanceid == $SITE->id) {
1103 debugging('Can not use temp roles on the frontpage');
1104 return;
1105 }
1106
e922fe23
PS
1107 if (!isset($USER->access)) {
1108 load_all_capabilities();
f33e1ed4 1109 }
343effbe 1110
e922fe23 1111 $coursecontext->reload_if_dirty();
343effbe 1112
e922fe23
PS
1113 if (isset($USER->access['ra'][$coursecontext->path][$roleid])) {
1114 return;
343effbe 1115 }
1116
e922fe23 1117 $USER->access['ra'][$coursecontext->path][(int)$roleid] = (int)$roleid;
343effbe 1118}
1119
efe12f6c 1120/**
e922fe23 1121 * Removes any extra guest roles from current USER->access array.
f76249cc 1122 * This is useful for enrol plugins only.
e922fe23 1123 *
5bcfd504 1124 * @since Moodle 2.2
e922fe23
PS
1125 * @param context_course $coursecontext
1126 * @return void
64026e8c 1127 */
e922fe23 1128function remove_temp_course_roles(context_course $coursecontext) {
bbfdff34
PS
1129 global $DB, $USER, $SITE;
1130
1131 if ($coursecontext->instanceid == $SITE->id) {
1132 debugging('Can not use temp roles on the frontpage');
1133 return;
1134 }
e922fe23
PS
1135
1136 if (empty($USER->access['ra'][$coursecontext->path])) {
1137 //no roles here, weird
1138 return;
1139 }
1140
df997f84
PS
1141 $sql = "SELECT DISTINCT ra.roleid AS id
1142 FROM {role_assignments} ra
1143 WHERE ra.contextid = :contextid AND ra.userid = :userid";
e922fe23 1144 $ras = $DB->get_records_sql($sql, array('contextid'=>$coursecontext->id, 'userid'=>$USER->id));
e4ec4e41 1145
e922fe23 1146 $USER->access['ra'][$coursecontext->path] = array();
1ed243c2 1147 foreach ($ras as $r) {
e922fe23 1148 $USER->access['ra'][$coursecontext->path][(int)$r->id] = (int)$r->id;
0831484c 1149 }
64026e8c 1150}
1151
3562486b 1152/**
4f0c2d00 1153 * Returns array of all role archetypes.
cc3edaa4 1154 *
46808d7c 1155 * @return array
3562486b 1156 */
4f0c2d00 1157function get_role_archetypes() {
3562486b 1158 return array(
4f0c2d00
PS
1159 'manager' => 'manager',
1160 'coursecreator' => 'coursecreator',
1161 'editingteacher' => 'editingteacher',
1162 'teacher' => 'teacher',
1163 'student' => 'student',
1164 'guest' => 'guest',
1165 'user' => 'user',
1166 'frontpage' => 'frontpage'
3562486b 1167 );
1168}
1169
bbbf2d40 1170/**
4f65e0fb 1171 * Assign the defaults found in this capability definition to roles that have
bbbf2d40 1172 * the corresponding legacy capabilities assigned to them.
cc3edaa4 1173 *
46808d7c 1174 * @param string $capability
1175 * @param array $legacyperms an array in the format (example):
bbbf2d40 1176 * 'guest' => CAP_PREVENT,
1177 * 'student' => CAP_ALLOW,
1178 * 'teacher' => CAP_ALLOW,
1179 * 'editingteacher' => CAP_ALLOW,
1180 * 'coursecreator' => CAP_ALLOW,
4f0c2d00 1181 * 'manager' => CAP_ALLOW
46808d7c 1182 * @return boolean success or failure.
bbbf2d40 1183 */
1184function assign_legacy_capabilities($capability, $legacyperms) {
eef868d1 1185
4f0c2d00 1186 $archetypes = get_role_archetypes();
3562486b 1187
bbbf2d40 1188 foreach ($legacyperms as $type => $perm) {
eef868d1 1189
e922fe23 1190 $systemcontext = context_system::instance();
4f0c2d00
PS
1191 if ($type === 'admin') {
1192 debugging('Legacy type admin in access.php was renamed to manager, please update the code.');
1193 $type = 'manager';
1194 }
eef868d1 1195
4f0c2d00 1196 if (!array_key_exists($type, $archetypes)) {
e49ef64a 1197 print_error('invalidlegacy', '', '', $type);
3562486b 1198 }
eef868d1 1199
4f0c2d00 1200 if ($roles = get_archetype_roles($type)) {
2e85fffe 1201 foreach ($roles as $role) {
1202 // Assign a site level capability.
1203 if (!assign_capability($capability, $perm, $role->id, $systemcontext->id)) {
1204 return false;
1205 }
bbbf2d40 1206 }
1207 }
1208 }
1209 return true;
1210}
1211
faf75fe7 1212/**
e922fe23
PS
1213 * Verify capability risks.
1214 *
f76249cc 1215 * @param stdClass $capability a capability - a row from the capabilities table.
ed149942 1216 * @return boolean whether this capability is safe - that is, whether people with the
faf75fe7 1217 * safeoverrides capability should be allowed to change it.
1218 */
1219function is_safe_capability($capability) {
4659454a 1220 return !((RISK_DATALOSS | RISK_MANAGETRUST | RISK_CONFIG | RISK_XSS | RISK_PERSONAL) & $capability->riskbitmask);
faf75fe7 1221}
cee0901c 1222
bbbf2d40 1223/**
e922fe23 1224 * Get the local override (if any) for a given capability in a role in a context
a0760047 1225 *
e922fe23
PS
1226 * @param int $roleid
1227 * @param int $contextid
1228 * @param string $capability
1229 * @return stdClass local capability override
bbbf2d40 1230 */
e922fe23
PS
1231function get_local_override($roleid, $contextid, $capability) {
1232 global $DB;
c40f6adb
AN
1233
1234 return $DB->get_record_sql("
1235 SELECT rc.*
1236 FROM {role_capabilities} rc
1237 JOIN {capability} cap ON rc.capability = cap.name
1238 WHERE rc.roleid = :roleid AND rc.capability = :capability AND rc.contextid = :contextid", [
1239 'roleid' => $roleid,
1240 'contextid' => $contextid,
1241 'capability' => $capability,
1242
1243 ]);
e922fe23 1244}
e40413be 1245
e922fe23
PS
1246/**
1247 * Returns context instance plus related course and cm instances
1248 *
1249 * @param int $contextid
1250 * @return array of ($context, $course, $cm)
1251 */
1252function get_context_info_array($contextid) {
74df2951
DW
1253 global $DB;
1254
e922fe23
PS
1255 $context = context::instance_by_id($contextid, MUST_EXIST);
1256 $course = null;
1257 $cm = null;
e40413be 1258
e922fe23 1259 if ($context->contextlevel == CONTEXT_COURSE) {
74df2951 1260 $course = $DB->get_record('course', array('id'=>$context->instanceid), '*', MUST_EXIST);
e40413be 1261
e922fe23
PS
1262 } else if ($context->contextlevel == CONTEXT_MODULE) {
1263 $cm = get_coursemodule_from_id('', $context->instanceid, 0, false, MUST_EXIST);
74df2951 1264 $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
7d0c81b3 1265
e922fe23
PS
1266 } else if ($context->contextlevel == CONTEXT_BLOCK) {
1267 $parent = $context->get_parent_context();
f689028c 1268
e922fe23 1269 if ($parent->contextlevel == CONTEXT_COURSE) {
74df2951 1270 $course = $DB->get_record('course', array('id'=>$parent->instanceid), '*', MUST_EXIST);
e922fe23
PS
1271 } else if ($parent->contextlevel == CONTEXT_MODULE) {
1272 $cm = get_coursemodule_from_id('', $parent->instanceid, 0, false, MUST_EXIST);
74df2951 1273 $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
e922fe23 1274 }
bbbf2d40 1275 }
4f0c2d00 1276
e922fe23 1277 return array($context, $course, $cm);
bbbf2d40 1278}
1279
efe12f6c 1280/**
e922fe23 1281 * Function that creates a role
46808d7c 1282 *
e922fe23
PS
1283 * @param string $name role name
1284 * @param string $shortname role short name
1285 * @param string $description role description
1286 * @param string $archetype
1287 * @return int id or dml_exception
8ba412da 1288 */
e922fe23
PS
1289function create_role($name, $shortname, $description, $archetype = '') {
1290 global $DB;
7d0c81b3 1291
e922fe23
PS
1292 if (strpos($archetype, 'moodle/legacy:') !== false) {
1293 throw new coding_exception('Use new role archetype parameter in create_role() instead of old legacy capabilities.');
8ba412da 1294 }
7d0c81b3 1295
e922fe23
PS
1296 // verify role archetype actually exists
1297 $archetypes = get_role_archetypes();
1298 if (empty($archetypes[$archetype])) {
1299 $archetype = '';
7d0c81b3 1300 }
1301
e922fe23
PS
1302 // Insert the role record.
1303 $role = new stdClass();
1304 $role->name = $name;
1305 $role->shortname = $shortname;
1306 $role->description = $description;
1307 $role->archetype = $archetype;
1308
1309 //find free sortorder number
1310 $role->sortorder = $DB->get_field('role', 'MAX(sortorder) + 1', array());
1311 if (empty($role->sortorder)) {
1312 $role->sortorder = 1;
7d0c81b3 1313 }
e922fe23 1314 $id = $DB->insert_record('role', $role);
7d0c81b3 1315
e922fe23 1316 return $id;
8ba412da 1317}
b51ece5b 1318
9991d157 1319/**
e922fe23 1320 * Function that deletes a role and cleanups up after it
cc3edaa4 1321 *
e922fe23
PS
1322 * @param int $roleid id of role to delete
1323 * @return bool always true
9991d157 1324 */
e922fe23
PS
1325function delete_role($roleid) {
1326 global $DB;
b51ece5b 1327
e922fe23
PS
1328 // first unssign all users
1329 role_unassign_all(array('roleid'=>$roleid));
2ac56258 1330
e922fe23
PS
1331 // cleanup all references to this role, ignore errors
1332 $DB->delete_records('role_capabilities', array('roleid'=>$roleid));
1333 $DB->delete_records('role_allow_assign', array('roleid'=>$roleid));
1334 $DB->delete_records('role_allow_assign', array('allowassign'=>$roleid));
1335 $DB->delete_records('role_allow_override', array('roleid'=>$roleid));
1336 $DB->delete_records('role_allow_override', array('allowoverride'=>$roleid));
1337 $DB->delete_records('role_names', array('roleid'=>$roleid));
1338 $DB->delete_records('role_context_levels', array('roleid'=>$roleid));
a05bcfba 1339
a7524e35
RT
1340 // Get role record before it's deleted.
1341 $role = $DB->get_record('role', array('id'=>$roleid));
582bae08 1342
a7524e35 1343 // Finally delete the role itself.
e922fe23 1344 $DB->delete_records('role', array('id'=>$roleid));
582bae08 1345
a7524e35
RT
1346 // Trigger event.
1347 $event = \core\event\role_deleted::create(
1348 array(
1349 'context' => context_system::instance(),
1350 'objectid' => $roleid,
1351 'other' =>
1352 array(
a7524e35
RT
1353 'shortname' => $role->shortname,
1354 'description' => $role->description,
1355 'archetype' => $role->archetype
1356 )
1357 )
1358 );
1359 $event->add_record_snapshot('role', $role);
1360 $event->trigger();
196f1a25 1361
e705e69e 1362 // Reset any cache of this role, including MUC.
4bdd7693 1363 accesslib_clear_role_cache($roleid);
e705e69e 1364
196f1a25 1365 return true;
9991d157 1366}
1367
9a81a606 1368/**
e922fe23 1369 * Function to write context specific overrides, or default capabilities.
cc3edaa4 1370 *
e922fe23
PS
1371 * @param string $capability string name
1372 * @param int $permission CAP_ constants
1373 * @param int $roleid role id
1374 * @param int|context $contextid context id
1375 * @param bool $overwrite
1376 * @return bool always true or exception
9a81a606 1377 */
e922fe23
PS
1378function assign_capability($capability, $permission, $roleid, $contextid, $overwrite = false) {
1379 global $USER, $DB;
9a81a606 1380
e922fe23
PS
1381 if ($contextid instanceof context) {
1382 $context = $contextid;
1383 } else {
1384 $context = context::instance_by_id($contextid);
9a81a606 1385 }
1386
c40f6adb
AN
1387 // Capability must exist.
1388 if (!$capinfo = get_capability_info($capability)) {
1389 throw new coding_exception("Capability '{$capability}' was not found! This has to be fixed in code.");
1390 }
1391
e922fe23
PS
1392 if (empty($permission) || $permission == CAP_INHERIT) { // if permission is not set
1393 unassign_capability($capability, $roleid, $context->id);
1394 return true;
9a81a606 1395 }
1396
e922fe23 1397 $existing = $DB->get_record('role_capabilities', array('contextid'=>$context->id, 'roleid'=>$roleid, 'capability'=>$capability));
9a81a606 1398
e922fe23
PS
1399 if ($existing and !$overwrite) { // We want to keep whatever is there already
1400 return true;
9a81a606 1401 }
1402
e922fe23
PS
1403 $cap = new stdClass();
1404 $cap->contextid = $context->id;
1405 $cap->roleid = $roleid;
1406 $cap->capability = $capability;
1407 $cap->permission = $permission;
1408 $cap->timemodified = time();
1409 $cap->modifierid = empty($USER->id) ? 0 : $USER->id;
e92c286c 1410
e922fe23
PS
1411 if ($existing) {
1412 $cap->id = $existing->id;
1413 $DB->update_record('role_capabilities', $cap);
1414 } else {
1415 if ($DB->record_exists('context', array('id'=>$context->id))) {
1416 $DB->insert_record('role_capabilities', $cap);
1417 }
9a81a606 1418 }
e705e69e 1419
7b0afca8
SL
1420 // Trigger capability_assigned event.
1421 \core\event\capability_assigned::create([
1422 'userid' => $cap->modifierid,
1423 'context' => $context,
1424 'objectid' => $roleid,
1425 'other' => [
1426 'capability' => $capability,
1427 'oldpermission' => $existing->permission ?? CAP_INHERIT,
1428 'permission' => $permission
1429 ]
1430 ])->trigger();
1431
e705e69e 1432 // Reset any cache of this role, including MUC.
4bdd7693 1433 accesslib_clear_role_cache($roleid);
e705e69e 1434
e922fe23 1435 return true;
9a81a606 1436}
1437
17b0efae 1438/**
e922fe23 1439 * Unassign a capability from a role.
17b0efae 1440 *
e922fe23
PS
1441 * @param string $capability the name of the capability
1442 * @param int $roleid the role id
1443 * @param int|context $contextid null means all contexts
1444 * @return boolean true or exception
17b0efae 1445 */
e922fe23 1446function unassign_capability($capability, $roleid, $contextid = null) {
a98bafa1 1447 global $DB, $USER;
17b0efae 1448
c40f6adb
AN
1449 // Capability must exist.
1450 if (!$capinfo = get_capability_info($capability)) {
1451 throw new coding_exception("Capability '{$capability}' was not found! This has to be fixed in code.");
1452 }
1453
e922fe23
PS
1454 if (!empty($contextid)) {
1455 if ($contextid instanceof context) {
1456 $context = $contextid;
1457 } else {
1458 $context = context::instance_by_id($contextid);
1459 }
1460 // delete from context rel, if this is the last override in this context
1461 $DB->delete_records('role_capabilities', array('capability'=>$capability, 'roleid'=>$roleid, 'contextid'=>$context->id));
1462 } else {
1463 $DB->delete_records('role_capabilities', array('capability'=>$capability, 'roleid'=>$roleid));
17b0efae 1464 }
e705e69e 1465
a98bafa1
SL
1466 // Trigger capability_assigned event.
1467 \core\event\capability_unassigned::create([
1468 'userid' => $USER->id,
1469 'context' => $context ?? context_system::instance(),
1470 'objectid' => $roleid,
1471 'other' => [
1472 'capability' => $capability,
1473 ]
1474 ])->trigger();
1475
e705e69e 1476 // Reset any cache of this role, including MUC.
4bdd7693 1477 accesslib_clear_role_cache($roleid);
e705e69e 1478
17b0efae 1479 return true;
1480}
1481
00653161 1482/**
e922fe23 1483 * Get the roles that have a given capability assigned to it
00653161 1484 *
e922fe23
PS
1485 * This function does not resolve the actual permission of the capability.
1486 * It just checks for permissions and overrides.
1487 * Use get_roles_with_cap_in_context() if resolution is required.
1488 *
34223e03
SH
1489 * @param string $capability capability name (string)
1490 * @param string $permission optional, the permission defined for this capability
e922fe23 1491 * either CAP_ALLOW, CAP_PREVENT or CAP_PROHIBIT. Defaults to null which means any.
34223e03 1492 * @param stdClass $context null means any
e922fe23 1493 * @return array of role records
00653161 1494 */
e922fe23
PS
1495function get_roles_with_capability($capability, $permission = null, $context = null) {
1496 global $DB;
00653161 1497
e922fe23
PS
1498 if ($context) {
1499 $contexts = $context->get_parent_context_ids(true);
1500 list($insql, $params) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED, 'ctx');
1501 $contextsql = "AND rc.contextid $insql";
1502 } else {
1503 $params = array();
1504 $contextsql = '';
00653161 1505 }
1506
e922fe23
PS
1507 if ($permission) {
1508 $permissionsql = " AND rc.permission = :permission";
1509 $params['permission'] = $permission;
1510 } else {
1511 $permissionsql = '';
00653161 1512 }
e922fe23
PS
1513
1514 $sql = "SELECT r.*
1515 FROM {role} r
1516 WHERE r.id IN (SELECT rc.roleid
1517 FROM {role_capabilities} rc
c40f6adb 1518 JOIN {capabilities} cap ON rc.capability = cap.name
e922fe23
PS
1519 WHERE rc.capability = :capname
1520 $contextsql
1521 $permissionsql)";
1522 $params['capname'] = $capability;
1523
1524
1525 return $DB->get_records_sql($sql, $params);
00653161 1526}
1527
bbbf2d40 1528/**
e922fe23 1529 * This function makes a role-assignment (a role for a user in a particular context)
46808d7c 1530 *
e922fe23
PS
1531 * @param int $roleid the role of the id
1532 * @param int $userid userid
1533 * @param int|context $contextid id of the context
1534 * @param string $component example 'enrol_ldap', defaults to '' which means manual assignment,
1535 * @param int $itemid id of enrolment/auth plugin
1536 * @param string $timemodified defaults to current time
1537 * @return int new/existing id of the assignment
bbbf2d40 1538 */
e922fe23 1539function role_assign($roleid, $userid, $contextid, $component = '', $itemid = 0, $timemodified = '') {
442f12f8 1540 global $USER, $DB;
d9a35e12 1541
e922fe23
PS
1542 // first of all detect if somebody is using old style parameters
1543 if ($contextid === 0 or is_numeric($component)) {
1544 throw new coding_exception('Invalid call to role_assign(), code needs to be updated to use new order of parameters');
8ba412da 1545 }
1546
e922fe23
PS
1547 // now validate all parameters
1548 if (empty($roleid)) {
1549 throw new coding_exception('Invalid call to role_assign(), roleid can not be empty');
a36a3a3f 1550 }
1551
e922fe23
PS
1552 if (empty($userid)) {
1553 throw new coding_exception('Invalid call to role_assign(), userid can not be empty');
1554 }
d0e538ba 1555
e922fe23
PS
1556 if ($itemid) {
1557 if (strpos($component, '_') === false) {
1558 throw new coding_exception('Invalid call to role_assign(), component must start with plugin type such as"enrol_" when itemid specified', 'component:'.$component);
65bcf17b 1559 }
e922fe23
PS
1560 } else {
1561 $itemid = 0;
1562 if ($component !== '' and strpos($component, '_') === false) {
1563 throw new coding_exception('Invalid call to role_assign(), invalid component string', 'component:'.$component);
65bcf17b 1564 }
e922fe23 1565 }
65bcf17b 1566
e922fe23
PS
1567 if (!$DB->record_exists('user', array('id'=>$userid, 'deleted'=>0))) {
1568 throw new coding_exception('User ID does not exist or is deleted!', 'userid:'.$userid);
1569 }
65bcf17b 1570
e922fe23
PS
1571 if ($contextid instanceof context) {
1572 $context = $contextid;
1573 } else {
1574 $context = context::instance_by_id($contextid, MUST_EXIST);
e5605780 1575 }
1576
e922fe23
PS
1577 if (!$timemodified) {
1578 $timemodified = time();
1579 }
65bcf17b 1580
e968c5f9 1581 // Check for existing entry
e922fe23 1582 $ras = $DB->get_records('role_assignments', array('roleid'=>$roleid, 'contextid'=>$context->id, 'userid'=>$userid, 'component'=>$component, 'itemid'=>$itemid), 'id');
65bcf17b 1583
e922fe23
PS
1584 if ($ras) {
1585 // role already assigned - this should not happen
1586 if (count($ras) > 1) {
1587 // very weird - remove all duplicates!
1588 $ra = array_shift($ras);
1589 foreach ($ras as $r) {
1590 $DB->delete_records('role_assignments', array('id'=>$r->id));
1591 }
1592 } else {
1593 $ra = reset($ras);
65bcf17b 1594 }
e5605780 1595
e922fe23
PS
1596 // actually there is no need to update, reset anything or trigger any event, so just return
1597 return $ra->id;
1598 }
5a4e7398 1599
e922fe23
PS
1600 // Create a new entry
1601 $ra = new stdClass();
1602 $ra->roleid = $roleid;
1603 $ra->contextid = $context->id;
1604 $ra->userid = $userid;
1605 $ra->component = $component;
1606 $ra->itemid = $itemid;
1607 $ra->timemodified = $timemodified;
1608 $ra->modifierid = empty($USER->id) ? 0 : $USER->id;
cd5be9a5 1609 $ra->sortorder = 0;
65bcf17b 1610
e922fe23 1611 $ra->id = $DB->insert_record('role_assignments', $ra);
65bcf17b 1612
1d049e08
JC
1613 // Role assignments have changed, so mark user as dirty.
1614 mark_user_dirty($userid);
0468976c 1615
442f12f8 1616 core_course_category::role_assignment_changed($roleid, $context);
5667e602 1617
02a5a4b2
MN
1618 $event = \core\event\role_assigned::create(array(
1619 'context' => $context,
1620 'objectid' => $ra->roleid,
1621 'relateduserid' => $ra->userid,
1622 'other' => array(
1623 'id' => $ra->id,
1624 'component' => $ra->component,
1625 'itemid' => $ra->itemid
1626 )
1627 ));
5fef139c 1628 $event->add_record_snapshot('role_assignments', $ra);
b474bec3 1629 $event->trigger();
bbbf2d40 1630
e922fe23
PS
1631 return $ra->id;
1632}
cee0901c 1633
340ea4e8 1634/**
e922fe23 1635 * Removes one role assignment
cc3edaa4 1636 *
e922fe23
PS
1637 * @param int $roleid
1638 * @param int $userid
fbf4acdc 1639 * @param int $contextid
e922fe23
PS
1640 * @param string $component
1641 * @param int $itemid
1642 * @return void
340ea4e8 1643 */
e922fe23
PS
1644function role_unassign($roleid, $userid, $contextid, $component = '', $itemid = 0) {
1645 // first make sure the params make sense
1646 if ($roleid == 0 or $userid == 0 or $contextid == 0) {
1647 throw new coding_exception('Invalid call to role_unassign(), please use role_unassign_all() when removing multiple role assignments');
340ea4e8 1648 }
1649
e922fe23
PS
1650 if ($itemid) {
1651 if (strpos($component, '_') === false) {
1652 throw new coding_exception('Invalid call to role_assign(), component must start with plugin type such as "enrol_" when itemid specified', 'component:'.$component);
1653 }
1654 } else {
1655 $itemid = 0;
1656 if ($component !== '' and strpos($component, '_') === false) {
1657 throw new coding_exception('Invalid call to role_assign(), invalid component string', 'component:'.$component);
1658 }
340ea4e8 1659 }
1660
e922fe23 1661 role_unassign_all(array('roleid'=>$roleid, 'userid'=>$userid, 'contextid'=>$contextid, 'component'=>$component, 'itemid'=>$itemid), false, false);
340ea4e8 1662}
1663
8737be58 1664/**
e922fe23
PS
1665 * Removes multiple role assignments, parameters may contain:
1666 * 'roleid', 'userid', 'contextid', 'component', 'enrolid'.
cc3edaa4 1667 *
e922fe23
PS
1668 * @param array $params role assignment parameters
1669 * @param bool $subcontexts unassign in subcontexts too
1670 * @param bool $includemanual include manual role assignments too
1671 * @return void
01a2ce80 1672 */
e922fe23
PS
1673function role_unassign_all(array $params, $subcontexts = false, $includemanual = false) {
1674 global $USER, $CFG, $DB;
01a2ce80 1675
e922fe23
PS
1676 if (!$params) {
1677 throw new coding_exception('Missing parameters in role_unsassign_all() call');
1678 }
01a2ce80 1679
e922fe23
PS
1680 $allowed = array('roleid', 'userid', 'contextid', 'component', 'itemid');
1681 foreach ($params as $key=>$value) {
1682 if (!in_array($key, $allowed)) {
1683 throw new coding_exception('Unknown role_unsassign_all() parameter key', 'key:'.$key);
01a2ce80
PS
1684 }
1685 }
1686
e922fe23
PS
1687 if (isset($params['component']) and $params['component'] !== '' and strpos($params['component'], '_') === false) {
1688 throw new coding_exception('Invalid component paramter in role_unsassign_all() call', 'component:'.$params['component']);
28765cfc 1689 }
e922fe23
PS
1690
1691 if ($includemanual) {
1692 if (!isset($params['component']) or $params['component'] === '') {
1693 throw new coding_exception('include manual parameter requires component parameter in role_unsassign_all() call');
1694 }
35716b86
PS
1695 }
1696
e922fe23
PS
1697 if ($subcontexts) {
1698 if (empty($params['contextid'])) {
1699 throw new coding_exception('subcontexts paramtere requires component parameter in role_unsassign_all() call');
1700 }
35716b86
PS
1701 }
1702
e922fe23 1703 $ras = $DB->get_records('role_assignments', $params);
1ed243c2 1704 foreach ($ras as $ra) {
e922fe23
PS
1705 $DB->delete_records('role_assignments', array('id'=>$ra->id));
1706 if ($context = context::instance_by_id($ra->contextid, IGNORE_MISSING)) {
1d049e08
JC
1707 // Role assignments have changed, so mark user as dirty.
1708 mark_user_dirty($ra->userid);
1709
02a5a4b2
MN
1710 $event = \core\event\role_unassigned::create(array(
1711 'context' => $context,
1712 'objectid' => $ra->roleid,
1713 'relateduserid' => $ra->userid,
1714 'other' => array(
1715 'id' => $ra->id,
1716 'component' => $ra->component,
1717 'itemid' => $ra->itemid
1718 )
1719 ));
5fef139c 1720 $event->add_record_snapshot('role_assignments', $ra);
b474bec3 1721 $event->trigger();
442f12f8 1722 core_course_category::role_assignment_changed($ra->roleid, $context);
e922fe23 1723 }
35716b86 1724 }
e922fe23
PS
1725 unset($ras);
1726
1727 // process subcontexts
1728 if ($subcontexts and $context = context::instance_by_id($params['contextid'], IGNORE_MISSING)) {
1729 if ($params['contextid'] instanceof context) {
1730 $context = $params['contextid'];
1731 } else {
1732 $context = context::instance_by_id($params['contextid'], IGNORE_MISSING);
1733 }
35716b86 1734
e922fe23
PS
1735 if ($context) {
1736 $contexts = $context->get_child_contexts();
1737 $mparams = $params;
1ed243c2 1738 foreach ($contexts as $context) {
e922fe23
PS
1739 $mparams['contextid'] = $context->id;
1740 $ras = $DB->get_records('role_assignments', $mparams);
1ed243c2 1741 foreach ($ras as $ra) {
e922fe23 1742 $DB->delete_records('role_assignments', array('id'=>$ra->id));
1d049e08
JC
1743 // Role assignments have changed, so mark user as dirty.
1744 mark_user_dirty($ra->userid);
1745
b474bec3
PS
1746 $event = \core\event\role_unassigned::create(
1747 array('context'=>$context, 'objectid'=>$ra->roleid, 'relateduserid'=>$ra->userid,
c4297815 1748 'other'=>array('id'=>$ra->id, 'component'=>$ra->component, 'itemid'=>$ra->itemid)));
5fef139c 1749 $event->add_record_snapshot('role_assignments', $ra);
b474bec3 1750 $event->trigger();
442f12f8 1751 core_course_category::role_assignment_changed($ra->roleid, $context);
e922fe23
PS
1752 }
1753 }
1754 }
35716b86
PS
1755 }
1756
e922fe23
PS
1757 // do this once more for all manual role assignments
1758 if ($includemanual) {
1759 $params['component'] = '';
1760 role_unassign_all($params, $subcontexts, false);
1761 }
35716b86
PS
1762}
1763
1d049e08
JC
1764/**
1765 * Mark a user as dirty (with timestamp) so as to force reloading of the user session.
1766 *
1767 * @param int $userid
1768 * @return void
1769 */
1770function mark_user_dirty($userid) {
1771 global $CFG, $ACCESSLIB_PRIVATE;
1772
1773 if (during_initial_install()) {
1774 return;
1775 }
1776
1777 // Throw exception if invalid userid is provided.
1778 if (empty($userid)) {
1779 throw new coding_exception('Invalid user parameter supplied for mark_user_dirty() function!');
1780 }
1781
1782 // Set dirty flag in database, set dirty field locally, and clear local accessdata cache.
1783 set_cache_flag('accesslib/dirtyusers', $userid, 1, time() + $CFG->sessiontimeout);
1784 $ACCESSLIB_PRIVATE->dirtyusers[$userid] = 1;
1785 unset($ACCESSLIB_PRIVATE->accessdatabyuser[$userid]);
1786}
1787
e922fe23
PS
1788/**
1789 * Determines if a user is currently logged in
1790 *
f76249cc
PS
1791 * @category access
1792 *
e922fe23
PS
1793 * @return bool
1794 */
1795function isloggedin() {
1796 global $USER;
bbbf2d40 1797
e922fe23
PS
1798 return (!empty($USER->id));
1799}
bbbf2d40 1800
cee0901c 1801/**
e922fe23 1802 * Determines if a user is logged in as real guest user with username 'guest'.
cc3edaa4 1803 *
f76249cc
PS
1804 * @category access
1805 *
e922fe23
PS
1806 * @param int|object $user mixed user object or id, $USER if not specified
1807 * @return bool true if user is the real guest user, false if not logged in or other user
bbbf2d40 1808 */
e922fe23
PS
1809function isguestuser($user = null) {
1810 global $USER, $DB, $CFG;
eef868d1 1811
e922fe23
PS
1812 // make sure we have the user id cached in config table, because we are going to use it a lot
1813 if (empty($CFG->siteguest)) {
1814 if (!$guestid = $DB->get_field('user', 'id', array('username'=>'guest', 'mnethostid'=>$CFG->mnet_localhost_id))) {
1815 // guest does not exist yet, weird
1816 return false;
1817 }
1818 set_config('siteguest', $guestid);
4f0c2d00 1819 }
e922fe23
PS
1820 if ($user === null) {
1821 $user = $USER;
4f0c2d00
PS
1822 }
1823
e922fe23
PS
1824 if ($user === null) {
1825 // happens when setting the $USER
1826 return false;
31f26796 1827
e922fe23
PS
1828 } else if (is_numeric($user)) {
1829 return ($CFG->siteguest == $user);
eef868d1 1830
e922fe23
PS
1831 } else if (is_object($user)) {
1832 if (empty($user->id)) {
1833 return false; // not logged in means is not be guest
1834 } else {
1835 return ($CFG->siteguest == $user->id);
1836 }
b5959f30 1837
e922fe23
PS
1838 } else {
1839 throw new coding_exception('Invalid user parameter supplied for isguestuser() function!');
1840 }
bbbf2d40 1841}
1842
8420bee9 1843/**
e922fe23 1844 * Does user have a (temporary or real) guest access to course?
cc3edaa4 1845 *
f76249cc
PS
1846 * @category access
1847 *
e922fe23
PS
1848 * @param context $context
1849 * @param stdClass|int $user
1850 * @return bool
8420bee9 1851 */
e922fe23
PS
1852function is_guest(context $context, $user = null) {
1853 global $USER;
c421ad4b 1854
e922fe23
PS
1855 // first find the course context
1856 $coursecontext = $context->get_course_context();
c421ad4b 1857
e922fe23
PS
1858 // make sure there is a real user specified
1859 if ($user === null) {
1860 $userid = isset($USER->id) ? $USER->id : 0;
1861 } else {
1862 $userid = is_object($user) ? $user->id : $user;
1863 }
60ace1e1 1864
e922fe23
PS
1865 if (isguestuser($userid)) {
1866 // can not inspect or be enrolled
1867 return true;
1868 }
5a4e7398 1869
e922fe23
PS
1870 if (has_capability('moodle/course:view', $coursecontext, $user)) {
1871 // viewing users appear out of nowhere, they are neither guests nor participants
1872 return false;
1873 }
5a4e7398 1874
e922fe23
PS
1875 // consider only real active enrolments here
1876 if (is_enrolled($coursecontext, $user, '', true)) {
1877 return false;
1878 }
8420bee9 1879
4f0c2d00 1880 return true;
8420bee9 1881}
1882
bbbf2d40 1883/**
e922fe23
PS
1884 * Returns true if the user has moodle/course:view capability in the course,
1885 * this is intended for admins, managers (aka small admins), inspectors, etc.
46808d7c 1886 *
f76249cc
PS
1887 * @category access
1888 *
e922fe23 1889 * @param context $context
34223e03 1890 * @param int|stdClass $user if null $USER is used
e922fe23
PS
1891 * @param string $withcapability extra capability name
1892 * @return bool
bbbf2d40 1893 */
e922fe23
PS
1894function is_viewing(context $context, $user = null, $withcapability = '') {
1895 // first find the course context
1896 $coursecontext = $context->get_course_context();
eef868d1 1897
e922fe23
PS
1898 if (isguestuser($user)) {
1899 // can not inspect
1900 return false;
98882637 1901 }
eef868d1 1902
e922fe23
PS
1903 if (!has_capability('moodle/course:view', $coursecontext, $user)) {
1904 // admins are allowed to inspect courses
1905 return false;
e7876c1e 1906 }
1907
e922fe23
PS
1908 if ($withcapability and !has_capability($withcapability, $context, $user)) {
1909 // site admins always have the capability, but the enrolment above blocks
1910 return false;
e7876c1e 1911 }
e922fe23 1912
4f0c2d00 1913 return true;
bbbf2d40 1914}
1915
bbbf2d40 1916/**
e922fe23 1917 * Returns true if the user is able to access the course.
117bd748 1918 *
e922fe23
PS
1919 * This function is in no way, shape, or form a substitute for require_login.
1920 * It should only be used in circumstances where it is not possible to call require_login
1921 * such as the navigation.
1922 *
1923 * This function checks many of the methods of access to a course such as the view
1924 * capability, enrollments, and guest access. It also makes use of the cache
1925 * generated by require_login for guest access.
1926 *
1927 * The flags within the $USER object that are used here should NEVER be used outside
1928 * of this function can_access_course and require_login. Doing so WILL break future
1929 * versions.
1930 *
1344b0ca
PS
1931 * @param stdClass $course record
1932 * @param stdClass|int|null $user user record or id, current user if null
e922fe23
PS
1933 * @param string $withcapability Check for this capability as well.
1934 * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
e922fe23
PS
1935 * @return boolean Returns true if the user is able to access the course
1936 */
1344b0ca 1937function can_access_course(stdClass $course, $user = null, $withcapability = '', $onlyactive = false) {
e922fe23 1938 global $DB, $USER;
eef868d1 1939
1344b0ca
PS
1940 // this function originally accepted $coursecontext parameter
1941 if ($course instanceof context) {
1942 if ($course instanceof context_course) {
1943 debugging('deprecated context parameter, please use $course record');
1944 $coursecontext = $course;
1945 $course = $DB->get_record('course', array('id'=>$coursecontext->instanceid));
1946 } else {
1947 debugging('Invalid context parameter, please use $course record');
1948 return false;
1949 }
1950 } else {
1951 $coursecontext = context_course::instance($course->id);
1952 }
1953
1954 if (!isset($USER->id)) {
1955 // should never happen
1956 $USER->id = 0;
c90e6b46 1957 debugging('Course access check being performed on a user with no ID.', DEBUG_DEVELOPER);
1344b0ca
PS
1958 }
1959
1960 // make sure there is a user specified
1961 if ($user === null) {
1962 $userid = $USER->id;
1963 } else {
1964 $userid = is_object($user) ? $user->id : $user;
1965 }
1966 unset($user);
bbbf2d40 1967
1344b0ca
PS
1968 if ($withcapability and !has_capability($withcapability, $coursecontext, $userid)) {
1969 return false;
1970 }
1971
1972 if ($userid == $USER->id) {
1973 if (!empty($USER->access['rsw'][$coursecontext->path])) {
1974 // the fact that somebody switched role means they can access the course no matter to what role they switched
1975 return true;
1976 }
1977 }
1978
0f14c827 1979 if (!$course->visible and !has_capability('moodle/course:viewhiddencourses', $coursecontext, $userid)) {
1344b0ca
PS
1980 return false;
1981 }
1982
1983 if (is_viewing($coursecontext, $userid)) {
e922fe23 1984 return true;
bbbf2d40 1985 }
1986
1344b0ca
PS
1987 if ($userid != $USER->id) {
1988 // for performance reasons we do not verify temporary guest access for other users, sorry...
1989 return is_enrolled($coursecontext, $userid, '', $onlyactive);
1990 }
1991
0f14c827
PS
1992 // === from here we deal only with $USER ===
1993
bbfdff34
PS
1994 $coursecontext->reload_if_dirty();
1995
1344b0ca 1996 if (isset($USER->enrol['enrolled'][$course->id])) {
bbfdff34 1997 if ($USER->enrol['enrolled'][$course->id] > time()) {
1344b0ca
PS
1998 return true;
1999 }
2000 }
2001 if (isset($USER->enrol['tempguest'][$course->id])) {
bbfdff34 2002 if ($USER->enrol['tempguest'][$course->id] > time()) {
1344b0ca
PS
2003 return true;
2004 }
2005 }
7700027f 2006
bbfdff34 2007 if (is_enrolled($coursecontext, $USER, '', $onlyactive)) {
1344b0ca 2008 return true;
96608a55 2009 }
c421ad4b 2010
beff3806
MG
2011 if (!core_course_category::can_view_course_info($course)) {
2012 // No guest access if user does not have capability to browse courses.
2013 return false;
2014 }
2015
1344b0ca
PS
2016 // if not enrolled try to gain temporary guest access
2017 $instances = $DB->get_records('enrol', array('courseid'=>$course->id, 'status'=>ENROL_INSTANCE_ENABLED), 'sortorder, id ASC');
2018 $enrols = enrol_get_plugins(true);
1ed243c2 2019 foreach ($instances as $instance) {
1344b0ca
PS
2020 if (!isset($enrols[$instance->enrol])) {
2021 continue;
2022 }
bbfdff34 2023 // Get a duration for the guest access, a timestamp in the future, 0 (always) or false.
1344b0ca 2024 $until = $enrols[$instance->enrol]->try_guestaccess($instance);
bbfdff34 2025 if ($until !== false and $until > time()) {
1344b0ca
PS
2026 $USER->enrol['tempguest'][$course->id] = $until;
2027 return true;
e922fe23 2028 }
4e5f3064 2029 }
bbfdff34
PS
2030 if (isset($USER->enrol['tempguest'][$course->id])) {
2031 unset($USER->enrol['tempguest'][$course->id]);
2032 remove_temp_course_roles($coursecontext);
2033 }
1344b0ca
PS
2034
2035 return false;
bbbf2d40 2036}
2037
e922fe23
PS
2038/**
2039 * Loads the capability definitions for the component (from file).
2040 *
2041 * Loads the capability definitions for the component (from file). If no
2042 * capabilities are defined for the component, we simply return an empty array.
2043 *
f76249cc 2044 * @access private
e922fe23
PS
2045 * @param string $component full plugin name, examples: 'moodle', 'mod_forum'
2046 * @return array array of capabilities
2047 */
2048function load_capability_def($component) {
b0d1d941 2049 $defpath = core_component::get_component_directory($component).'/db/access.php';
e922fe23
PS
2050
2051 $capabilities = array();
2052 if (file_exists($defpath)) {
2053 require($defpath);
2054 if (!empty(${$component.'_capabilities'})) {
2055 // BC capability array name
2056 // since 2.0 we prefer $capabilities instead - it is easier to use and matches db/* files
99ddfdf4 2057 debugging('componentname_capabilities array is deprecated, please use $capabilities array only in access.php files');
e922fe23
PS
2058 $capabilities = ${$component.'_capabilities'};
2059 }
4f0c2d00
PS
2060 }
2061
e922fe23 2062 return $capabilities;
4f0c2d00
PS
2063}
2064
e922fe23
PS
2065/**
2066 * Gets the capabilities that have been cached in the database for this component.
2067 *
f76249cc 2068 * @access private
e922fe23
PS
2069 * @param string $component - examples: 'moodle', 'mod_forum'
2070 * @return array array of capabilities
2071 */
2072function get_cached_capabilities($component = 'moodle') {
2073 global $DB;
aa701743
TL
2074 $caps = get_all_capabilities();
2075 $componentcaps = array();
2076 foreach ($caps as $cap) {
2077 if ($cap['component'] == $component) {
2078 $componentcaps[] = (object) $cap;
2079 }
2080 }
2081 return $componentcaps;
e922fe23 2082}
4f0c2d00
PS
2083
2084/**
e922fe23 2085 * Returns default capabilities for given role archetype.
4f0c2d00 2086 *
e922fe23
PS
2087 * @param string $archetype role archetype
2088 * @return array
4f0c2d00 2089 */
e922fe23
PS
2090function get_default_capabilities($archetype) {
2091 global $DB;
4f0c2d00 2092
e922fe23
PS
2093 if (!$archetype) {
2094 return array();
4f0c2d00
PS
2095 }
2096
e922fe23
PS
2097 $alldefs = array();
2098 $defaults = array();
2099 $components = array();
aa701743 2100 $allcaps = get_all_capabilities();
4f0c2d00 2101
e922fe23 2102 foreach ($allcaps as $cap) {
aa701743
TL
2103 if (!in_array($cap['component'], $components)) {
2104 $components[] = $cap['component'];
2105 $alldefs = array_merge($alldefs, load_capability_def($cap['component']));
e922fe23
PS
2106 }
2107 }
1ed243c2 2108 foreach ($alldefs as $name=>$def) {
e922fe23
PS
2109 // Use array 'archetypes if available. Only if not specified, use 'legacy'.
2110 if (isset($def['archetypes'])) {
2111 if (isset($def['archetypes'][$archetype])) {
2112 $defaults[$name] = $def['archetypes'][$archetype];
2113 }
2114 // 'legacy' is for backward compatibility with 1.9 access.php
2115 } else {
2116 if (isset($def['legacy'][$archetype])) {
2117 $defaults[$name] = $def['legacy'][$archetype];
2118 }
2119 }
4f0c2d00
PS
2120 }
2121
e922fe23 2122 return $defaults;
4f0c2d00
PS
2123}
2124
5e72efd4
PS
2125/**
2126 * Return default roles that can be assigned, overridden or switched
2127 * by give role archetype.
2128 *
a63cd3e2 2129 * @param string $type assign|override|switch|view
5e72efd4
PS
2130 * @param string $archetype
2131 * @return array of role ids
2132 */
2133function get_default_role_archetype_allows($type, $archetype) {
2134 global $DB;
2135
2136 if (empty($archetype)) {
2137 return array();
2138 }
2139
2140 $roles = $DB->get_records('role');
2141 $archetypemap = array();
2142 foreach ($roles as $role) {
2143 if ($role->archetype) {
2144 $archetypemap[$role->archetype][$role->id] = $role->id;
2145 }
2146 }
2147
2148 $defaults = array(
2149 'assign' => array(
2150 'manager' => array('manager', 'coursecreator', 'editingteacher', 'teacher', 'student'),
2151 'coursecreator' => array(),
2152 'editingteacher' => array('teacher', 'student'),
2153 'teacher' => array(),
2154 'student' => array(),
2155 'guest' => array(),
2156 'user' => array(),
2157 'frontpage' => array(),
2158 ),
2159 'override' => array(
2160 'manager' => array('manager', 'coursecreator', 'editingteacher', 'teacher', 'student', 'guest', 'user', 'frontpage'),
2161 'coursecreator' => array(),
2162 'editingteacher' => array('teacher', 'student', 'guest'),
2163 'teacher' => array(),
2164 'student' => array(),
2165 'guest' => array(),
2166 'user' => array(),
2167 'frontpage' => array(),
2168 ),
2169 'switch' => array(
2170 'manager' => array('editingteacher', 'teacher', 'student', 'guest'),
2171 'coursecreator' => array(),
2172 'editingteacher' => array('teacher', 'student', 'guest'),
2173 'teacher' => array('student', 'guest'),
2174 'student' => array(),
2175 'guest' => array(),
2176 'user' => array(),
2177 'frontpage' => array(),
2178 ),
a63cd3e2
AH
2179 'view' => array(
2180 'manager' => array('manager', 'coursecreator', 'editingteacher', 'teacher', 'student', 'guest', 'user', 'frontpage'),
2181 'coursecreator' => array('coursecreator', 'editingteacher', 'teacher', 'student'),
2182 'editingteacher' => array('coursecreator', 'editingteacher', 'teacher', 'student'),
2183 'teacher' => array('coursecreator', 'editingteacher', 'teacher', 'student'),
2184 'student' => array('coursecreator', 'editingteacher', 'teacher', 'student'),
2185 'guest' => array(),
2186 'user' => array(),
2187 'frontpage' => array(),
2188 ),
5e72efd4
PS
2189 );
2190
2191 if (!isset($defaults[$type][$archetype])) {
2192 debugging("Unknown type '$type'' or archetype '$archetype''");
2193 return array();
2194 }
2195
2196 $return = array();
2197 foreach ($defaults[$type][$archetype] as $at) {
2198 if (isset($archetypemap[$at])) {
2199 foreach ($archetypemap[$at] as $roleid) {
2200 $return[$roleid] = $roleid;
2201 }
2202 }
2203 }
2204
2205 return $return;
2206}
2207
4f0c2d00 2208/**
e922fe23
PS
2209 * Reset role capabilities to default according to selected role archetype.
2210 * If no archetype selected, removes all capabilities.
4f0c2d00 2211 *
0f1882ed 2212 * This applies to capabilities that are assigned to the role (that you could
2213 * edit in the 'define roles' interface), and not to any capability overrides
2214 * in different locations.
2215 *
2216 * @param int $roleid ID of role to reset capabilities for
4f0c2d00 2217 */
e922fe23
PS
2218function reset_role_capabilities($roleid) {
2219 global $DB;
4f0c2d00 2220
e922fe23
PS
2221 $role = $DB->get_record('role', array('id'=>$roleid), '*', MUST_EXIST);
2222 $defaultcaps = get_default_capabilities($role->archetype);
4f0c2d00 2223
e922fe23 2224 $systemcontext = context_system::instance();
df997f84 2225
3978da9e 2226 $DB->delete_records('role_capabilities',
0f1882ed 2227 array('roleid' => $roleid, 'contextid' => $systemcontext->id));
4f0c2d00 2228
1ed243c2 2229 foreach ($defaultcaps as $cap=>$permission) {
e922fe23 2230 assign_capability($cap, $permission, $roleid, $systemcontext->id);
4f0c2d00 2231 }
0f1882ed 2232
e705e69e 2233 // Reset any cache of this role, including MUC.
4bdd7693 2234 accesslib_clear_role_cache($roleid);
4f0c2d00
PS
2235}
2236
ed1d72ea 2237/**
e922fe23
PS
2238 * Updates the capabilities table with the component capability definitions.
2239 * If no parameters are given, the function updates the core moodle
2240 * capabilities.
ed1d72ea 2241 *
e922fe23
PS
2242 * Note that the absence of the db/access.php capabilities definition file
2243 * will cause any stored capabilities for the component to be removed from
2244 * the database.
ed1d72ea 2245 *
f76249cc 2246 * @access private
9e8e75a0 2247 * @param string $component examples: 'moodle', 'mod_forum', 'block_quiz_results'
e922fe23 2248 * @return boolean true if success, exception in case of any problems
ed1d72ea 2249 */
e922fe23
PS
2250function update_capabilities($component = 'moodle') {
2251 global $DB, $OUTPUT;
ed1d72ea 2252
e922fe23 2253 $storedcaps = array();
ed1d72ea 2254
e922fe23 2255 $filecaps = load_capability_def($component);
1ed243c2 2256 foreach ($filecaps as $capname=>$unused) {
e922fe23
PS
2257 if (!preg_match('|^[a-z]+/[a-z_0-9]+:[a-z_0-9]+$|', $capname)) {
2258 debugging("Coding problem: Invalid capability name '$capname', use 'clonepermissionsfrom' field for migration.");
2259 }
ed1d72ea
SH
2260 }
2261
aa701743
TL
2262 // It is possible somebody directly modified the DB (according to accesslib_test anyway).
2263 // So ensure our updating is based on fresh data.
2264 cache::make('core', 'capabilities')->delete('core_capabilities');
2265
e922fe23
PS
2266 $cachedcaps = get_cached_capabilities($component);
2267 if ($cachedcaps) {
2268 foreach ($cachedcaps as $cachedcap) {
2269 array_push($storedcaps, $cachedcap->name);
2270 // update risk bitmasks and context levels in existing capabilities if needed
2271 if (array_key_exists($cachedcap->name, $filecaps)) {
2272 if (!array_key_exists('riskbitmask', $filecaps[$cachedcap->name])) {
2273 $filecaps[$cachedcap->name]['riskbitmask'] = 0; // no risk if not specified
2274 }
2275 if ($cachedcap->captype != $filecaps[$cachedcap->name]['captype']) {
2276 $updatecap = new stdClass();
2277 $updatecap->id = $cachedcap->id;
2278 $updatecap->captype = $filecaps[$cachedcap->name]['captype'];
2279 $DB->update_record('capabilities', $updatecap);
2280 }
2281 if ($cachedcap->riskbitmask != $filecaps[$cachedcap->name]['riskbitmask']) {
2282 $updatecap = new stdClass();
2283 $updatecap->id = $cachedcap->id;
2284 $updatecap->riskbitmask = $filecaps[$cachedcap->name]['riskbitmask'];
2285 $DB->update_record('capabilities', $updatecap);
2286 }
ed1d72ea 2287
e922fe23
PS
2288 if (!array_key_exists('contextlevel', $filecaps[$cachedcap->name])) {
2289 $filecaps[$cachedcap->name]['contextlevel'] = 0; // no context level defined
2290 }
2291 if ($cachedcap->contextlevel != $filecaps[$cachedcap->name]['contextlevel']) {
2292 $updatecap = new stdClass();
2293 $updatecap->id = $cachedcap->id;
2294 $updatecap->contextlevel = $filecaps[$cachedcap->name]['contextlevel'];
2295 $DB->update_record('capabilities', $updatecap);
2296 }
ed1d72ea 2297 }
e922fe23
PS
2298 }
2299 }
2300
aa701743
TL
2301 // Flush the cached again, as we have changed DB.
2302 cache::make('core', 'capabilities')->delete('core_capabilities');
2303
e922fe23
PS
2304 // Are there new capabilities in the file definition?
2305 $newcaps = array();
2306
2307 foreach ($filecaps as $filecap => $def) {
2308 if (!$storedcaps ||
2309 ($storedcaps && in_array($filecap, $storedcaps) === false)) {
2310 if (!array_key_exists('riskbitmask', $def)) {
2311 $def['riskbitmask'] = 0; // no risk if not specified
ed1d72ea 2312 }
e922fe23 2313 $newcaps[$filecap] = $def;
ed1d72ea
SH
2314 }
2315 }
e922fe23 2316 // Add new capabilities to the stored definition.
a36e170f 2317 $existingcaps = $DB->get_records_menu('capabilities', array(), 'id', 'id, name');
e922fe23
PS
2318 foreach ($newcaps as $capname => $capdef) {
2319 $capability = new stdClass();
2320 $capability->name = $capname;
2321 $capability->captype = $capdef['captype'];
2322 $capability->contextlevel = $capdef['contextlevel'];
2323 $capability->component = $component;
2324 $capability->riskbitmask = $capdef['riskbitmask'];
ed1d72ea 2325
e922fe23
PS
2326 $DB->insert_record('capabilities', $capability, false);
2327
c40f6adb
AN
2328 // Flush the cached, as we have changed DB.
2329 cache::make('core', 'capabilities')->delete('core_capabilities');
2330
a36e170f 2331 if (isset($capdef['clonepermissionsfrom']) && in_array($capdef['clonepermissionsfrom'], $existingcaps)){
e922fe23
PS
2332 if ($rolecapabilities = $DB->get_records('role_capabilities', array('capability'=>$capdef['clonepermissionsfrom']))){
2333 foreach ($rolecapabilities as $rolecapability){
2334 //assign_capability will update rather than insert if capability exists
2335 if (!assign_capability($capname, $rolecapability->permission,
2336 $rolecapability->roleid, $rolecapability->contextid, true)){
2337 echo $OUTPUT->notification('Could not clone capabilities for '.$capname);
2338 }
2339 }
2340 }
2341 // we ignore archetype key if we have cloned permissions
2342 } else if (isset($capdef['archetypes']) && is_array($capdef['archetypes'])) {
2343 assign_legacy_capabilities($capname, $capdef['archetypes']);
2344 // 'legacy' is for backward compatibility with 1.9 access.php
2345 } else if (isset($capdef['legacy']) && is_array($capdef['legacy'])) {
2346 assign_legacy_capabilities($capname, $capdef['legacy']);
ed1d72ea
SH
2347 }
2348 }
e922fe23
PS
2349 // Are there any capabilities that have been removed from the file
2350 // definition that we need to delete from the stored capabilities and
2351 // role assignments?
2352 capabilities_cleanup($component, $filecaps);
2353
2354 // reset static caches
b2f349a4 2355 accesslib_reset_role_cache();
e922fe23 2356
aa701743
TL
2357 // Flush the cached again, as we have changed DB.
2358 cache::make('core', 'capabilities')->delete('core_capabilities');
2359
e922fe23 2360 return true;
ed1d72ea
SH
2361}
2362
4f0c2d00 2363/**
e922fe23
PS
2364 * Deletes cached capabilities that are no longer needed by the component.
2365 * Also unassigns these capabilities from any roles that have them.
c2ca2d8e 2366 * NOTE: this function is called from lib/db/upgrade.php
df997f84 2367 *
f76249cc 2368 * @access private
e922fe23
PS
2369 * @param string $component examples: 'moodle', 'mod_forum', 'block_quiz_results'
2370 * @param array $newcapdef array of the new capability definitions that will be
2371 * compared with the cached capabilities
2372 * @return int number of deprecated capabilities that have been removed
4f0c2d00 2373 */
e922fe23
PS
2374function capabilities_cleanup($component, $newcapdef = null) {
2375 global $DB;
4f0c2d00 2376
e922fe23 2377 $removedcount = 0;
4f0c2d00 2378
e922fe23
PS
2379 if ($cachedcaps = get_cached_capabilities($component)) {
2380 foreach ($cachedcaps as $cachedcap) {
2381 if (empty($newcapdef) ||
2382 array_key_exists($cachedcap->name, $newcapdef) === false) {
4f0c2d00 2383
e922fe23
PS
2384 // Delete from roles.
2385 if ($roles = get_roles_with_capability($cachedcap->name)) {
1ed243c2 2386 foreach ($roles as $role) {
e922fe23
PS
2387 if (!unassign_capability($cachedcap->name, $role->id)) {
2388 print_error('cannotunassigncap', 'error', '', (object)array('cap'=>$cachedcap->name, 'role'=>$role->name));
2389 }
2390 }
2391 }
c40f6adb
AN
2392
2393 // Remove from role_capabilities for any old ones.
2394 $DB->delete_records('role_capabilities', array('capability' => $cachedcap->name));
2395
2396 // Remove from capabilities cache.
2397 $DB->delete_records('capabilities', array('name' => $cachedcap->name));
2398 $removedcount++;
e922fe23
PS
2399 } // End if.
2400 }
2401 }
aa701743
TL
2402 if ($removedcount) {
2403 cache::make('core', 'capabilities')->delete('core_capabilities');
2404 }
e922fe23
PS
2405 return $removedcount;
2406}
2407
e922fe23
PS
2408/**
2409 * Returns an array of all the known types of risk
2410 * The array keys can be used, for example as CSS class names, or in calls to
2411 * print_risk_icon. The values are the corresponding RISK_ constants.
2412 *
2413 * @return array all the known types of risk.
2414 */
2415function get_all_risks() {
2416 return array(
2417 'riskmanagetrust' => RISK_MANAGETRUST,
2418 'riskconfig' => RISK_CONFIG,
2419 'riskxss' => RISK_XSS,
2420 'riskpersonal' => RISK_PERSONAL,
2421 'riskspam' => RISK_SPAM,
2422 'riskdataloss' => RISK_DATALOSS,
2423 );
2424}
2425
2426/**
2427 * Return a link to moodle docs for a given capability name
2428 *
f76249cc 2429 * @param stdClass $capability a capability - a row from the mdl_capabilities table.
e922fe23
PS
2430 * @return string the human-readable capability name as a link to Moodle Docs.
2431 */
2432function get_capability_docs_link($capability) {
2433 $url = get_docs_url('Capabilities/' . $capability->name);
2434 return '<a onclick="this.target=\'docspopup\'" href="' . $url . '">' . get_capability_string($capability->name) . '</a>';
2435}
2436
2437/**
2438 * This function pulls out all the resolved capabilities (overrides and
2439 * defaults) of a role used in capability overrides in contexts at a given
2440 * context.
2441 *
e922fe23 2442 * @param int $roleid
34223e03 2443 * @param context $context
e922fe23 2444 * @param string $cap capability, optional, defaults to ''
34223e03 2445 * @return array Array of capabilities
e922fe23
PS
2446 */
2447function role_context_capabilities($roleid, context $context, $cap = '') {
2448 global $DB;
2449
2450 $contexts = $context->get_parent_context_ids(true);
2451 $contexts = '('.implode(',', $contexts).')';
2452
2453 $params = array($roleid);
2454
2455 if ($cap) {
2456 $search = " AND rc.capability = ? ";
2457 $params[] = $cap;
2458 } else {
2459 $search = '';
2460 }
2461
2462 $sql = "SELECT rc.*
c40f6adb
AN
2463 FROM {role_capabilities} rc
2464 JOIN {context} c ON rc.contextid = c.id
2465 JOIN {capabilities} cap ON rc.capability = cap.name
e922fe23
PS
2466 WHERE rc.contextid in $contexts
2467 AND rc.roleid = ?
c40f6adb 2468 $search
e922fe23
PS
2469 ORDER BY c.contextlevel DESC, rc.capability DESC";
2470
2471 $capabilities = array();
2472
2473 if ($records = $DB->get_records_sql($sql, $params)) {
2474 // We are traversing via reverse order.
2475 foreach ($records as $record) {
2476 // If not set yet (i.e. inherit or not set at all), or currently we have a prohibit
2477 if (!isset($capabilities[$record->capability]) || $record->permission<-500) {
2478 $capabilities[$record->capability] = $record->permission;
2479 }
2480 }
2481 }
2482 return $capabilities;
2483}
2484
2485/**
2486 * Constructs array with contextids as first parameter and context paths,
2487 * in both cases bottom top including self.
2488 *
f76249cc 2489 * @access private
e922fe23
PS
2490 * @param context $context
2491 * @return array
2492 */
2493function get_context_info_list(context $context) {
2494 $contextids = explode('/', ltrim($context->path, '/'));
2495 $contextpaths = array();
2496 $contextids2 = $contextids;
2497 while ($contextids2) {
2498 $contextpaths[] = '/' . implode('/', $contextids2);
2499 array_pop($contextids2);
2500 }
2501 return array($contextids, $contextpaths);
2502}
2503
2504/**
2505 * Check if context is the front page context or a context inside it
2506 *
2507 * Returns true if this context is the front page context, or a context inside it,
2508 * otherwise false.
2509 *
2510 * @param context $context a context object.
2511 * @return bool
2512 */
2513function is_inside_frontpage(context $context) {
2514 $frontpagecontext = context_course::instance(SITEID);
2515 return strpos($context->path . '/', $frontpagecontext->path . '/') === 0;
2516}
2517
2518/**
2519 * Returns capability information (cached)
2520 *
2521 * @param string $capabilityname
f76249cc 2522 * @return stdClass or null if capability not found
e922fe23
PS
2523 */
2524function get_capability_info($capabilityname) {
aa701743 2525 $caps = get_all_capabilities();
e922fe23 2526
aa701743
TL
2527 if (!isset($caps[$capabilityname])) {
2528 return null;
e922fe23
PS
2529 }
2530
aa701743
TL
2531 return (object) $caps[$capabilityname];
2532}
2533
2534/**
2535 * Returns all capabilitiy records, preferably from MUC and not database.
2536 *
a0dffaa9 2537 * @return array All capability records indexed by capability name
aa701743
TL
2538 */
2539function get_all_capabilities() {
2540 global $DB;
2541 $cache = cache::make('core', 'capabilities');
2542 if (!$allcaps = $cache->get('core_capabilities')) {
a0dffaa9
DP
2543 $rs = $DB->get_recordset('capabilities');
2544 $allcaps = array();
2545 foreach ($rs as $capability) {
2546 $capability->riskbitmask = (int) $capability->riskbitmask;
2547 $allcaps[$capability->name] = (array) $capability;
aa701743 2548 }
a0dffaa9 2549 $rs->close();
aa701743
TL
2550 $cache->set('core_capabilities', $allcaps);
2551 }
2552 return $allcaps;
e922fe23
PS
2553}
2554
2555/**
2556 * Returns the human-readable, translated version of the capability.
2557 * Basically a big switch statement.
2558 *
2559 * @param string $capabilityname e.g. mod/choice:readresponses
2560 * @return string
2561 */
2562function get_capability_string($capabilityname) {
2563
2564 // Typical capability name is 'plugintype/pluginname:capabilityname'
2565 list($type, $name, $capname) = preg_split('|[/:]|', $capabilityname);
2566
2567 if ($type === 'moodle') {
2568 $component = 'core_role';
2569 } else if ($type === 'quizreport') {
2570 //ugly hack!!
2571 $component = 'quiz_'.$name;
2572 } else {
2573 $component = $type.'_'.$name;
2574 }
2575
2576 $stringname = $name.':'.$capname;
2577
2578 if ($component === 'core_role' or get_string_manager()->string_exists($stringname, $component)) {
2579 return get_string($stringname, $component);
2580 }
2581
b0d1d941 2582 $dir = core_component::get_component_directory($component);
e922fe23
PS
2583 if (!file_exists($dir)) {
2584 // plugin broken or does not exist, do not bother with printing of debug message
2585 return $capabilityname.' ???';
2586 }
2587
2588 // something is wrong in plugin, better print debug
2589 return get_string($stringname, $component);
2590}
2591
2592/**
2593 * This gets the mod/block/course/core etc strings.
2594 *
2595 * @param string $component
2596 * @param int $contextlevel
2597 * @return string|bool String is success, false if failed
2598 */
2599function get_component_string($component, $contextlevel) {
2600
45c4d4aa
TH
2601 if ($component === 'moodle' || $component === 'core') {
2602 return context_helper::get_level_name($contextlevel);
e922fe23
PS
2603 }
2604
56da374e 2605 list($type, $name) = core_component::normalize_component($component);
1c74b260 2606 $dir = core_component::get_plugin_directory($type, $name);
e922fe23
PS
2607 if (!file_exists($dir)) {
2608 // plugin not installed, bad luck, there is no way to find the name
45c4d4aa 2609 return $component . ' ???';
e922fe23
PS
2610 }
2611
45c4d4aa 2612 // Some plugin types need an extra prefix to make the name easy to understand.
e922fe23 2613 switch ($type) {
45c4d4aa
TH
2614 case 'quiz':
2615 $prefix = get_string('quizreport', 'quiz') . ': ';
2616 break;
2617 case 'repository':
2618 $prefix = get_string('repository', 'repository') . ': ';
2619 break;
2620 case 'gradeimport':
2621 $prefix = get_string('gradeimport', 'grades') . ': ';
2622 break;
2623 case 'gradeexport':
2624 $prefix = get_string('gradeexport', 'grades') . ': ';
2625 break;
2626 case 'gradereport':
2627 $prefix = get_string('gradereport', 'grades') . ': ';
2628 break;
2629 case 'webservice':
2630 $prefix = get_string('webservice', 'webservice') . ': ';
2631 break;
2632 case 'block':
2633 $prefix = get_string('block') . ': ';
2634 break;
e922fe23 2635 case 'mod':
45c4d4aa
TH
2636 $prefix = get_string('activity') . ': ';
2637 break;
2638
2639 // Default case, just use the plugin name.
2640 default:
2641 $prefix = '';
e922fe23 2642 }
45c4d4aa 2643 return $prefix . get_string('pluginname', $component);
e922fe23
PS
2644}
2645
2646/**
2647 * Gets the list of roles assigned to this context and up (parents)
7b305826
JD
2648 * from the aggregation of:
2649 * a) the list of roles that are visible on user profile page and participants page (profileroles setting) and;
b7e9f1cc 2650 * b) if applicable, those roles that are assigned in the context.
e922fe23
PS
2651 *
2652 * @param context $context
2653 * @return array
2654 */
2655function get_profile_roles(context $context) {
2656 global $CFG, $DB;
b7e9f1cc
JD
2657 // If the current user can assign roles, then they can see all roles on the profile and participants page,
2658 // provided the roles are assigned to at least 1 user in the context. If not, only the policy-defined roles.
2659 if (has_capability('moodle/role:assign', $context)) {
2660 $rolesinscope = array_keys(get_all_roles($context));
2661 } else {
2662 $rolesinscope = empty($CFG->profileroles) ? [] : array_map('trim', explode(',', $CFG->profileroles));
2663 }
2664
7b305826
JD
2665 if (empty($rolesinscope)) {
2666 return [];
e922fe23
PS
2667 }
2668
7b305826 2669 list($rallowed, $params) = $DB->get_in_or_equal($rolesinscope, SQL_PARAMS_NAMED, 'a');
e922fe23
PS
2670 list($contextlist, $cparams) = $DB->get_in_or_equal($context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'p');
2671 $params = array_merge($params, $cparams);
2672
c52551dc
PS
2673 if ($coursecontext = $context->get_course_context(false)) {
2674 $params['coursecontext'] = $coursecontext->id;
2675 } else {
2676 $params['coursecontext'] = 0;
2677 }
2678
2679 $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder, rn.name AS coursealias
e922fe23 2680 FROM {role_assignments} ra, {role} r
c52551dc 2681 LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
e922fe23
PS
2682 WHERE r.id = ra.roleid
2683 AND ra.contextid $contextlist
2684 AND r.id $rallowed
2685 ORDER BY r.sortorder ASC";
2686
2687 return $DB->get_records_sql($sql, $params);
2688}
2689
2690/**
2691 * Gets the list of roles assigned to this context and up (parents)
2692 *
2693 * @param context $context
df536ab1 2694 * @param boolean $includeparents, false means without parents.
e922fe23
PS
2695 * @return array
2696 */
df536ab1 2697function get_roles_used_in_context(context $context, $includeparents = true) {
e922fe23
PS
2698 global $DB;
2699
df536ab1 2700 if ($includeparents === true) {
2701 list($contextlist, $params) = $DB->get_in_or_equal($context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'cl');
2702 } else {
2703 list($contextlist, $params) = $DB->get_in_or_equal($context->id, SQL_PARAMS_NAMED, 'cl');
2704 }
c52551dc
PS
2705
2706 if ($coursecontext = $context->get_course_context(false)) {
2707 $params['coursecontext'] = $coursecontext->id;
2708 } else {
2709 $params['coursecontext'] = 0;
2710 }
e922fe23 2711
c52551dc 2712 $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder, rn.name AS coursealias
e922fe23 2713 FROM {role_assignments} ra, {role} r
c52551dc 2714 LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
e922fe23
PS
2715 WHERE r.id = ra.roleid
2716 AND ra.contextid $contextlist
2717 ORDER BY r.sortorder ASC";
2718
2719 return $DB->get_records_sql($sql, $params);
2720}
2721
2722/**
2723 * This function is used to print roles column in user profile page.
2724 * It is using the CFG->profileroles to limit the list to only interesting roles.
2725 * (The permission tab has full details of user role assignments.)
2726 *
2727 * @param int $userid
2728 * @param int $courseid
2729 * @return string
2730 */
2731function get_user_roles_in_course($userid, $courseid) {
2732 global $CFG, $DB;
e922fe23
PS
2733 if ($courseid == SITEID) {
2734 $context = context_system::instance();
2735 } else {
2736 $context = context_course::instance($courseid);
2737 }
b7e9f1cc
JD
2738 // If the current user can assign roles, then they can see all roles on the profile and participants page,
2739 // provided the roles are assigned to at least 1 user in the context. If not, only the policy-defined roles.
2740 if (has_capability('moodle/role:assign', $context)) {
2741 $rolesinscope = array_keys(get_all_roles($context));
2742 } else {
2743 $rolesinscope = empty($CFG->profileroles) ? [] : array_map('trim', explode(',', $CFG->profileroles));
2744 }
da655d9e
JD
2745 if (empty($rolesinscope)) {
2746 return '';
2747 }
e922fe23 2748
da655d9e 2749 list($rallowed, $params) = $DB->get_in_or_equal($rolesinscope, SQL_PARAMS_NAMED, 'a');
e922fe23
PS
2750 list($contextlist, $cparams) = $DB->get_in_or_equal($context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'p');
2751 $params = array_merge($params, $cparams);
2752
c52551dc
PS
2753 if ($coursecontext = $context->get_course_context(false)) {
2754 $params['coursecontext'] = $coursecontext->id;
2755 } else {
2756 $params['coursecontext'] = 0;
2757 }
2758
2759 $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder, rn.name AS coursealias
e922fe23 2760 FROM {role_assignments} ra, {role} r
c52551dc 2761 LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
e922fe23
PS
2762 WHERE r.id = ra.roleid
2763 AND ra.contextid $contextlist
2764 AND r.id $rallowed
2765 AND ra.userid = :userid
2766 ORDER BY r.sortorder ASC";
2767 $params['userid'] = $userid;
2768
2769 $rolestring = '';
2770
2771 if ($roles = $DB->get_records_sql($sql, $params)) {
a63cd3e2 2772 $viewableroles = get_viewable_roles($context, $userid);
e922fe23 2773
a63cd3e2
AH
2774 $rolenames = array();
2775 foreach ($roles as $roleid => $unused) {
2776 if (isset($viewableroles[$roleid])) {
2777 $url = new moodle_url('/user/index.php', ['contextid' => $context->id, 'roleid' => $roleid]);
2778 $rolenames[] = '<a href="' . $url . '">' . $viewableroles[$roleid] . '</a>';
2779 }
e922fe23
PS
2780 }
2781 $rolestring = implode(',', $rolenames);
2782 }
2783
2784 return $rolestring;
2785}
2786
2787/**
2788 * Checks if a user can assign users to a particular role in this context
2789 *
2790 * @param context $context
2791 * @param int $targetroleid - the id of the role you want to assign users to
2792 * @return boolean
2793 */
2794function user_can_assign(context $context, $targetroleid) {
2795 global $DB;
2796
3370f216
AG
2797 // First check to see if the user is a site administrator.
2798 if (is_siteadmin()) {
2799 return true;
2800 }
2801
2802 // Check if user has override capability.
2803 // If not return false.
e922fe23
PS
2804 if (!has_capability('moodle/role:assign', $context)) {
2805 return false;
2806 }
2807 // pull out all active roles of this user from this context(or above)
2808 if ($userroles = get_user_roles($context)) {
2809 foreach ($userroles as $userrole) {
2810 // if any in the role_allow_override table, then it's ok
2811 if ($DB->get_record('role_allow_assign', array('roleid'=>$userrole->roleid, 'allowassign'=>$targetroleid))) {
2812 return true;
2813 }
2814 }
2815 }
2816
2817 return false;
2818}
2819
2820/**
2821 * Returns all site roles in correct sort order.
2822 *
fa0ad435
PS
2823 * Note: this method does not localise role names or descriptions,
2824 * use role_get_names() if you need role names.
2825 *
c52551dc
PS
2826 * @param context $context optional context for course role name aliases
2827 * @return array of role records with optional coursealias property
e922fe23 2828 */
c52551dc 2829function get_all_roles(context $context = null) {
e922fe23 2830 global $DB;
c52551dc
PS
2831
2832 if (!$context or !$coursecontext = $context->get_course_context(false)) {
2833 $coursecontext = null;
2834 }
2835
2836 if ($coursecontext) {
2837 $sql = "SELECT r.*, rn.name AS coursealias
2838 FROM {role} r
2839 LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
2840 ORDER BY r.sortorder ASC";
2841 return $DB->get_records_sql($sql, array('coursecontext'=>$coursecontext->id));
2842
2843 } else {
2844 return $DB->get_records('role', array(), 'sortorder ASC');
2845 }
e922fe23
PS
2846}
2847
2848/**
2849 * Returns roles of a specified archetype
2850 *
2851 * @param string $archetype
2852 * @return array of full role records
2853 */
2854function get_archetype_roles($archetype) {
2855 global $DB;
2856 return $DB->get_records('role', array('archetype'=>$archetype), 'sortorder ASC');
2857}
2858
0ae28fad
DW
2859/**
2860 * Gets all the user roles assigned in this context, or higher contexts for a list of users.
2861 *
5359c517
TH
2862 * If you try using the combination $userids = [], $checkparentcontexts = true then this is likely
2863 * to cause an out-of-memory error on large Moodle sites, so this combination is deprecated and
2864 * outputs a warning, even though it is the default.
2865 *
0ae28fad
DW
2866 * @param context $context
2867 * @param array $userids. An empty list means fetch all role assignments for the context.
2868 * @param bool $checkparentcontexts defaults to true
2869 * @param string $order defaults to 'c.contextlevel DESC, r.sortorder ASC'
2870 * @return array
2871 */
2872function get_users_roles(context $context, $userids = [], $checkparentcontexts = true, $order = 'c.contextlevel DESC, r.sortorder ASC') {
5359c517
TH
2873 global $DB;
2874
2875 if (!$userids && $checkparentcontexts) {
2876 debugging('Please do not call get_users_roles() with $checkparentcontexts = true ' .
2877 'and $userids array not set. This combination causes large Moodle sites ' .
2878 'with lots of site-wide role assignemnts to run out of memory.', DEBUG_DEVELOPER);
2879 }
0ae28fad
DW
2880
2881 if ($checkparentcontexts) {
2882 $contextids = $context->get_parent_context_ids();
2883 } else {
2884 $contextids = array();
2885 }
2886 $contextids[] = $context->id;
2887
2888 list($contextids, $params) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, 'con');
2889
2890 // If userids was passed as an empty array, we fetch all role assignments for the course.
2891 if (empty($userids)) {
2892 $useridlist = ' IS NOT NULL ';
2893 $uparams = [];
2894 } else {
2895 list($useridlist, $uparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED, 'uids');
2896 }
2897
2898 $sql = "SELECT ra.*, r.name, r.shortname, ra.userid
2899 FROM {role_assignments} ra, {role} r, {context} c
2900 WHERE ra.userid $useridlist
2901 AND ra.roleid = r.id
2902 AND ra.contextid = c.id
2903 AND ra.contextid $contextids
2904 ORDER BY $order";
2905
2906 $all = $DB->get_records_sql($sql , array_merge($params, $uparams));
2907
2908 // Return results grouped by userid.
2909 $result = [];
2910 foreach ($all as $id => $record) {
2911 if (!isset($result[$record->userid])) {
2912 $result[$record->userid] = [];
2913 }
2914 $result[$record->userid][$record->id] = $record;
2915 }
2916
2917 // Make sure all requested users are included in the result, even if they had no role assignments.
2918 foreach ($userids as $id) {
2919 if (!isset($result[$id])) {
2920 $result[$id] = [];
2921 }
2922 }
2923
2924 return $result;
2925}
2926
2927
e922fe23
PS
2928/**
2929 * Gets all the user roles assigned in this context, or higher contexts
2930 * this is mainly used when checking if a user can assign a role, or overriding a role
2931 * i.e. we need to know what this user holds, in order to verify against allow_assign and
2932 * allow_override tables
2933 *
2934 * @param context $context
2935 * @param int $userid
2936 * @param bool $checkparentcontexts defaults to true
2937 * @param string $order defaults to 'c.contextlevel DESC, r.sortorder ASC'
2938 * @return array
2939 */
2940function get_user_roles(context $context, $userid = 0, $checkparentcontexts = true, $order = 'c.contextlevel DESC, r.sortorder ASC') {
2941 global $USER, $DB;
2942
2943 if (empty($userid)) {
2944 if (empty($USER->id)) {
2945 return array();
2946 }
2947 $userid = $USER->id;
2948 }
2949
2950 if ($checkparentcontexts) {
2951 $contextids = $context->get_parent_context_ids();
2952 } else {
2953 $contextids = array();
2954 }
2955 $contextids[] = $context->id;
2956
2957 list($contextids, $params) = $DB->get_in_or_equal($contextids, SQL_PARAMS_QM);
2958
2959 array_unshift($params, $userid);
2960
2961 $sql = "SELECT ra.*, r.name, r.shortname
2962 FROM {role_assignments} ra, {role} r, {context} c
2963 WHERE ra.userid = ?
2964 AND ra.roleid = r.id
2965 AND ra.contextid = c.id
2966 AND ra.contextid $contextids
2967 ORDER BY $order";
2968
2969 return $DB->get_records_sql($sql ,$params);
2970}
2971
ab0c7007
TH
2972/**
2973 * Like get_user_roles, but adds in the authenticated user role, and the front
2974 * page roles, if applicable.
2975 *
2976 * @param context $context the context.
2977 * @param int $userid optional. Defaults to $USER->id
2978 * @return array of objects with fields ->userid, ->contextid and ->roleid.
2979 */
2980function get_user_roles_with_special(context $context, $userid = 0) {
2981 global $CFG, $USER;
2982
2983 if (empty($userid)) {
2984 if (empty($USER->id)) {
2985 return array();
2986 }
2987 $userid = $USER->id;
2988 }
2989
2990 $ras = get_user_roles($context, $userid);
2991
2992 // Add front-page role if relevant.
2993 $defaultfrontpageroleid = isset($CFG->defaultfrontpageroleid) ? $CFG->defaultfrontpageroleid : 0;
2994 $isfrontpage = ($context->contextlevel == CONTEXT_COURSE && $context->instanceid == SITEID) ||
2995 is_inside_frontpage($context);
2996 if ($defaultfrontpageroleid && $isfrontpage) {
2997 $frontpagecontext = context_course::instance(SITEID);
2998 $ra = new stdClass();
2999 $ra->userid = $userid;
3000 $ra->contextid = $frontpagecontext->id;
3001 $ra->roleid = $defaultfrontpageroleid;
3002 $ras[] = $ra;
3003 }
3004
3005 // Add authenticated user role if relevant.
3006 $defaultuserroleid = isset($CFG->defaultuserroleid) ? $CFG->defaultuserroleid : 0;
3007 if ($defaultuserroleid && !isguestuser($userid)) {
3008 $systemcontext = context_system::instance();
3009 $ra = new stdClass();
3010 $ra->userid = $userid;
3011 $ra->contextid = $systemcontext->id;
3012 $ra->roleid = $defaultuserroleid;
3013 $ras[] = $ra;
3014 }
3015
3016 return $ras;
3017}
3018
e922fe23
PS
3019/**
3020 * Creates a record in the role_allow_override table
3021 *
64cd4596
AH
3022 * @param int $fromroleid source roleid
3023 * @param int $targetroleid target roleid
e922fe23
PS
3024 * @return void
3025 */
64cd4596 3026function core_role_set_override_allowed($fromroleid, $targetroleid) {
e922fe23
PS
3027 global $DB;
3028
3029 $record = new stdClass();
64cd4596
AH
3030 $record->roleid = $fromroleid;
3031 $record->allowoverride = $targetroleid;
e922fe23
PS
3032 $DB->insert_record('role_allow_override', $record);
3033}
3034
3035/**
3036 * Creates a record in the role_allow_assign table
3037 *
3038 * @param int $fromroleid source roleid
3039 * @param int $targetroleid target roleid
3040 * @return void
3041 */
64cd4596 3042function core_role_set_assign_allowed($fromroleid, $targetroleid) {
e922fe23
PS
3043 global $DB;
3044
3045 $record = new stdClass();
3046 $record->roleid = $fromroleid;
3047 $record->allowassign = $targetroleid;
3048 $DB->insert_record('role_allow_assign', $record);
3049}
3050
3051/**
3052 * Creates a record in the role_allow_switch table
3053 *
3054 * @param int $fromroleid source roleid
3055 * @param int $targetroleid target roleid
3056 * @return void
3057 */
64cd4596 3058function core_role_set_switch_allowed($fromroleid, $targetroleid) {
e922fe23
PS
3059 global $DB;
3060
3061 $record = new stdClass();
3062 $record->roleid = $fromroleid;
3063 $record->allowswitch = $targetroleid;
3064 $DB->insert_record('role_allow_switch', $record);
3065}
3066
a63cd3e2
AH
3067/**
3068 * Creates a record in the role_allow_view table
3069 *
3070 * @param int $fromroleid source roleid
3071 * @param int $targetroleid target roleid
3072 * @return void
3073 */
3074function core_role_set_view_allowed($fromroleid, $targetroleid) {
3075 global $DB;
3076
3077 $record = new stdClass();
3078 $record->roleid = $fromroleid;
3079 $record->allowview = $targetroleid;
3080 $DB->insert_record('role_allow_view', $record);
3081}
3082
e922fe23
PS
3083/**
3084 * Gets a list of roles that this user can assign in this context
3085 *
3086 * @param context $context the context.
3087 * @param int $rolenamedisplay the type of role name to display. One of the
3088 * ROLENAME_X constants. Default ROLENAME_ALIAS.
3089 * @param bool $withusercounts if true, count the number of users with each role.
3090 * @param integer|object $user A user id or object. By default (null) checks the permissions of the current user.
3091 * @return array if $withusercounts is false, then an array $roleid => $rolename.
3092 * if $withusercounts is true, returns a list of three arrays,
3093 * $rolenames, $rolecounts, and $nameswithcounts.
3094 */
3095function get_assignable_roles(context $context, $rolenamedisplay = ROLENAME_ALIAS, $withusercounts = false, $user = null) {
3096 global $USER, $DB;
3097
3098 // make sure there is a real user specified
3099 if ($user === null) {
3100 $userid = isset($USER->id) ? $USER->id : 0;
3101 } else {
3102 $userid = is_object($user) ? $user->id : $user;
3103 }
3104
3105 if (!has_capability('moodle/role:assign', $context, $userid)) {
3106 if ($withusercounts) {
3107 return array(array(), array(), array());
3108 } else {
3109 return array();
3110 }
3111 }
3112
e922fe23
PS
3113 $params = array();
3114 $extrafields = '';
e922fe23
PS
3115
3116 if ($withusercounts) {
9ded266b 3117 $extrafields = ', (SELECT COUNT(DISTINCT u.id)
e922fe23
PS
3118 FROM {role_assignments} cra JOIN {user} u ON cra.userid = u.id
3119 WHERE cra.roleid = r.id AND cra.contextid = :conid AND u.deleted = 0
3120 ) AS usercount';
3121 $params['conid'] = $context->id;
3122 }
3123
3124 if (is_siteadmin($userid)) {
3125 // show all roles allowed in this context to admins
3126 $assignrestriction = "";
3127 } else {
c52551dc
PS
3128 $parents = $context->get_parent_context_ids(true);
3129 $contexts = implode(',' , $parents);
e922fe23
PS
3130 $assignrestriction = "JOIN (SELECT DISTINCT raa.allowassign AS id
3131 FROM {role_allow_assign} raa
3132 JOIN {role_assignments} ra ON ra.roleid = raa.roleid
3133 WHERE ra.userid = :userid AND ra.contextid IN ($contexts)
3134 ) ar ON ar.id = r.id";
3135 $params['userid'] = $userid;
3136 }
3137 $params['contextlevel'] = $context->contextlevel;
c52551dc
PS
3138
3139 if ($coursecontext = $context->get_course_context(false)) {
3140 $params['coursecontext'] = $coursecontext->id;
3141 } else {
3142 $params['coursecontext'] = 0; // no course aliases
3143 $coursecontext = null;
3144 }
3145 $sql = "SELECT r.id, r.name, r.shortname, rn.name AS coursealias $extrafields
e922fe23
PS
3146 FROM {role} r
3147 $assignrestriction
c52551dc
PS
3148 JOIN {role_context_levels} rcl ON (rcl.contextlevel = :contextlevel AND r.id = rcl.roleid)
3149 LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
e922fe23
PS
3150 ORDER BY r.sortorder ASC";
3151 $roles = $DB->get_records_sql($sql, $params);
3152
c52551dc 3153 $rolenames = role_fix_names($roles, $coursecontext, $rolenamedisplay, true);
e922fe23
PS
3154
3155 if (!$withusercounts) {
3156 return $rolenames;
3157 }
3158
3159 $rolecounts = array();
3160 $nameswithcounts = array();
3161 foreach ($roles as $role) {
3162 $nameswithcounts[$role->id] = $rolenames[$role->id] . ' (' . $roles[$role->id]->usercount . ')';
3163 $rolecounts[$role->id] = $roles[$role->id]->usercount;
3164 }
3165 return array($rolenames, $rolecounts, $nameswithcounts);
3166}
3167
3168/**
3169 * Gets a list of roles that this user can switch to in a context
3170 *
3171 * Gets a list of roles that this user can switch to in a context, for the switchrole menu.
3172 * This function just process the contents of the role_allow_switch table. You also need to
3173 * test the moodle/role:switchroles to see if the user is allowed to switch in the first place.
3174 *
3175 * @param context $context a context.
a9165e69
PH
3176 * @param int $rolenamedisplay the type of role name to display. One of the
3177 * ROLENAME_X constants. Default ROLENAME_ALIAS.
e922fe23
PS
3178 * @return array an array $roleid => $rolename.
3179 */
a9165e69 3180function get_switchable_roles(context $context, $rolenamedisplay = ROLENAME_ALIAS) {
e922fe23
PS
3181 global $USER, $DB;
3182
2fedc0da
DW
3183 // You can't switch roles without this capability.
3184 if (!has_capability('moodle/role:switchroles', $context)) {
3185 return [];
3186 }
3187
e922fe23
PS
3188 $params = array();
3189 $extrajoins = '';
3190 $extrawhere = '';
3191 if (!is_siteadmin()) {
3192 // Admins are allowed to switch to any role with.
3193 // Others are subject to the additional constraint that the switch-to role must be allowed by
3194 // 'role_allow_switch' for some role they have assigned in this context or any parent.
3195 $parents = $context->get_parent_context_ids(true);
3196 $contexts = implode(',' , $parents);
3197
3198 $extrajoins = "JOIN {role_allow_switch} ras ON ras.allowswitch = rc.roleid
3199 JOIN {role_assignments} ra ON ra.roleid = ras.roleid";
3200 $extrawhere = "WHERE ra.userid = :userid AND ra.contextid IN ($contexts)";
3201 $params['userid'] = $USER->id;
3202 }
3203
c52551dc
PS
3204 if ($coursecontext = $context->get_course_context(false)) {
3205 $params['coursecontext'] = $coursecontext->id;
3206 } else {
3207 $params['coursecontext'] = 0; // no course aliases
3208 $coursecontext = null;
3209 }
3210
e922fe23 3211 $query = "
c52551dc 3212 SELECT r.id, r.name, r.shortname, rn.name AS coursealias
e922fe23
PS
3213 FROM (SELECT DISTINCT rc.roleid
3214 FROM {role_capabilities} rc
c40f6adb 3215
e922fe23
PS
3216 $extrajoins
3217 $extrawhere) idlist
3218 JOIN {role} r ON r.id = idlist.roleid
c52551dc 3219 LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
e922fe23 3220 ORDER BY r.sortorder";
c52551dc 3221 $roles = $DB->get_records_sql($query, $params);
e922fe23 3222
a9165e69 3223 return role_fix_names($roles, $context, $rolenamedisplay, true);
e922fe23
PS
3224}
3225
a63cd3e2
AH
3226/**
3227 * Gets a list of roles that this user can view in a context
3228 *
3229 * @param context $context a context.
3230 * @param int $userid id of user.
a9165e69
PH
3231 * @param int $rolenamedisplay the type of role name to display. One of the
3232 * ROLENAME_X constants. Default ROLENAME_ALIAS.
a63cd3e2
AH
3233 * @return array an array $roleid => $rolename.
3234 */
a9165e69 3235function get_viewable_roles(context $context, $userid = null, $rolenamedisplay = ROLENAME_ALIAS) {
a63cd3e2
AH
3236 global $USER, $DB;
3237
3238 if ($userid == null) {
3239 $userid = $USER->id;
3240 }
3241
3242 $params = array();
3243 $extrajoins = '';
3244 $extrawhere = '';
3245 if (!is_siteadmin()) {
3246 // Admins are allowed to view any role.
3247 // Others are subject to the additional constraint that the view role must be allowed by
3248 // 'role_allow_view' for some role they have assigned in this context or any parent.
3249 $contexts = $context->get_parent_context_ids(true);
3250 list($insql, $inparams) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED);
3251
3252 $extrajoins = "JOIN {role_allow_view} ras ON ras.allowview = r.id
3253 JOIN {role_assignments} ra ON ra.roleid = ras.roleid";
3254 $extrawhere = "WHERE ra.userid = :userid AND ra.contextid $insql";
3255
3256 $params += $inparams;
3257 $params['userid'] = $userid;
3258 }
3259
3260 if ($coursecontext = $context->get_course_context(false)) {
3261 $params['coursecontext'] = $coursecontext->id;
3262 } else {
3263 $params['coursecontext'] = 0; // No course aliases.
3264 $coursecontext = null;
3265 }
3266
3267 $query = "
8403325c 3268 SELECT r.id, r.name, r.shortname, rn.name AS coursealias, r.sortorder
a63cd3e2
AH
3269 FROM {role} r
3270 $extrajoins
3271 LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
3272 $extrawhere
8403325c 3273 GROUP BY r.id, r.name, r.shortname, rn.name, r.sortorder
a63cd3e2
AH
3274 ORDER BY r.sortorder";
3275 $roles = $DB->get_records_sql($query, $para