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