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