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