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