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