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