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