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