Commit | Line | Data |
---|---|---|
46808d7c | 1 | <?php |
2 | ||
117bd748 PS |
3 | // This file is part of Moodle - http://moodle.org/ |
4 | // | |
46808d7c | 5 | // Moodle is free software: you can redistribute it and/or modify |
6 | // it under the terms of the GNU General Public License as published by | |
7 | // the Free Software Foundation, either version 3 of the License, or | |
8 | // (at your option) any later version. | |
9 | // | |
10 | // Moodle is distributed in the hope that it will be useful, | |
11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | // GNU General Public License for more details. | |
117bd748 | 14 | // |
46808d7c | 15 | // You should have received a copy of the GNU General Public License |
16 | // along with Moodle. If not, see <http://www.gnu.org/licenses/>. | |
6cdd0f9c | 17 | |
92e53168 | 18 | /** |
46808d7c | 19 | * This file contains functions for managing user access |
20 | * | |
cc3edaa4 | 21 | * <b>Public API vs internals</b> |
5a4e7398 | 22 | * |
92e53168 | 23 | * General users probably only care about |
24 | * | |
dcd6a775 | 25 | * Context handling |
26 | * - get_context_instance() | |
27 | * - get_context_instance_by_id() | |
28 | * - get_parent_contexts() | |
29 | * - get_child_contexts() | |
5a4e7398 | 30 | * |
dcd6a775 | 31 | * Whether the user can do something... |
92e53168 | 32 | * - has_capability() |
8a1b1c32 | 33 | * - has_any_capability() |
34 | * - has_all_capabilities() | |
efd6fce5 | 35 | * - require_capability() |
dcd6a775 | 36 | * - require_login() (from moodlelib) |
37 | * | |
38 | * What courses has this user access to? | |
92e53168 | 39 | * - get_user_courses_bycap() |
dcd6a775 | 40 | * |
db70c4bd | 41 | * What users can do X in this context? |
42 | * - get_users_by_capability() | |
43 | * | |
dcd6a775 | 44 | * Enrol/unenrol |
ad833c42 | 45 | * - enrol_into_course() |
46 | * - role_assign()/role_unassign() | |
5a4e7398 | 47 | * |
92e53168 | 48 | * |
49 | * Advanced use | |
dcd6a775 | 50 | * - load_all_capabilities() |
51 | * - reload_all_capabilities() | |
bb2c22bd | 52 | * - has_capability_in_accessdata() |
dcd6a775 | 53 | * - is_siteadmin() |
54 | * - get_user_access_sitewide() | |
a2cf7f1b | 55 | * - load_subcontext() |
dcd6a775 | 56 | * - get_role_access_bycontext() |
57 | * | |
cc3edaa4 | 58 | * <b>Name conventions</b> |
5a4e7398 | 59 | * |
cc3edaa4 | 60 | * "ctx" means context |
92e53168 | 61 | * |
cc3edaa4 | 62 | * <b>accessdata</b> |
92e53168 | 63 | * |
64 | * Access control data is held in the "accessdata" array | |
65 | * which - for the logged-in user, will be in $USER->access | |
5a4e7398 | 66 | * |
d867e696 | 67 | * For other users can be generated and passed around (but may also be cached |
68 | * against userid in $ACCESSLIB_PRIVATE->accessdatabyuser. | |
92e53168 | 69 | * |
bb2c22bd | 70 | * $accessdata is a multidimensional array, holding |
5a4e7398 | 71 | * role assignments (RAs), role-capabilities-perm sets |
51be70d2 | 72 | * (role defs) and a list of courses we have loaded |
92e53168 | 73 | * data for. |
74 | * | |
5a4e7398 | 75 | * Things are keyed on "contextpaths" (the path field of |
92e53168 | 76 | * the context table) for fast walking up/down the tree. |
cc3edaa4 | 77 | * <code> |
bb2c22bd | 78 | * $accessdata[ra][$contextpath]= array($roleid) |
79 | * [$contextpath]= array($roleid) | |
5a4e7398 | 80 | * [$contextpath]= array($roleid) |
117bd748 | 81 | * </code> |
92e53168 | 82 | * |
83 | * Role definitions are stored like this | |
84 | * (no cap merge is done - so it's compact) | |
85 | * | |
cc3edaa4 | 86 | * <code> |
bb2c22bd | 87 | * $accessdata[rdef][$contextpath:$roleid][mod/forum:viewpost] = 1 |
88 | * [mod/forum:editallpost] = -1 | |
89 | * [mod/forum:startdiscussion] = -1000 | |
cc3edaa4 | 90 | * </code> |
92e53168 | 91 | * |
bb2c22bd | 92 | * See how has_capability_in_accessdata() walks up/down the tree. |
92e53168 | 93 | * |
94 | * Normally - specially for the logged-in user, we only load | |
95 | * rdef and ra down to the course level, but not below. This | |
96 | * keeps accessdata small and compact. Below-the-course ra/rdef | |
97 | * are loaded as needed. We keep track of which courses we | |
5a4e7398 | 98 | * have loaded ra/rdef in |
cc3edaa4 | 99 | * <code> |
5a4e7398 | 100 | * $accessdata[loaded] = array($contextpath, $contextpath) |
cc3edaa4 | 101 | * </code> |
92e53168 | 102 | * |
cc3edaa4 | 103 | * <b>Stale accessdata</b> |
92e53168 | 104 | * |
105 | * For the logged-in user, accessdata is long-lived. | |
106 | * | |
d867e696 | 107 | * On each pageload we load $ACCESSLIB_PRIVATE->dirtycontexts which lists |
92e53168 | 108 | * context paths affected by changes. Any check at-or-below |
109 | * a dirty context will trigger a transparent reload of accessdata. | |
5a4e7398 | 110 | * |
4f65e0fb | 111 | * Changes at the system level will force the reload for everyone. |
92e53168 | 112 | * |
cc3edaa4 | 113 | * <b>Default role caps</b> |
5a4e7398 | 114 | * The default role assignment is not in the DB, so we |
115 | * add it manually to accessdata. | |
92e53168 | 116 | * |
117 | * This means that functions that work directly off the | |
118 | * DB need to ensure that the default role caps | |
5a4e7398 | 119 | * are dealt with appropriately. |
92e53168 | 120 | * |
46808d7c | 121 | * @package moodlecore |
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 | |
cc3edaa4 | 126 | /** permission definitions */ |
e49e61bf | 127 | define('CAP_INHERIT', 0); |
cc3edaa4 | 128 | /** permission definitions */ |
bbbf2d40 | 129 | define('CAP_ALLOW', 1); |
cc3edaa4 | 130 | /** permission definitions */ |
bbbf2d40 | 131 | define('CAP_PREVENT', -1); |
cc3edaa4 | 132 | /** permission definitions */ |
bbbf2d40 | 133 | define('CAP_PROHIBIT', -1000); |
134 | ||
cc3edaa4 | 135 | /** context definitions */ |
bbbf2d40 | 136 | define('CONTEXT_SYSTEM', 10); |
cc3edaa4 | 137 | /** context definitions */ |
4b10f08b | 138 | define('CONTEXT_USER', 30); |
cc3edaa4 | 139 | /** context definitions */ |
bbbf2d40 | 140 | define('CONTEXT_COURSECAT', 40); |
cc3edaa4 | 141 | /** context definitions */ |
bbbf2d40 | 142 | define('CONTEXT_COURSE', 50); |
cc3edaa4 | 143 | /** context definitions */ |
bbbf2d40 | 144 | define('CONTEXT_MODULE', 70); |
cc3edaa4 | 145 | /** context definitions */ |
bbbf2d40 | 146 | define('CONTEXT_BLOCK', 80); |
147 | ||
cc3edaa4 | 148 | /** capability risks - see {@link http://docs.moodle.org/en/Development:Hardening_new_Roles_system} */ |
21b6db6e | 149 | define('RISK_MANAGETRUST', 0x0001); |
cc3edaa4 | 150 | /** capability risks - see {@link http://docs.moodle.org/en/Development:Hardening_new_Roles_system} */ |
a6b02b65 | 151 | define('RISK_CONFIG', 0x0002); |
cc3edaa4 | 152 | /** capability risks - see {@link http://docs.moodle.org/en/Development:Hardening_new_Roles_system} */ |
21b6db6e | 153 | define('RISK_XSS', 0x0004); |
cc3edaa4 | 154 | /** capability risks - see {@link http://docs.moodle.org/en/Development:Hardening_new_Roles_system} */ |
21b6db6e | 155 | define('RISK_PERSONAL', 0x0008); |
cc3edaa4 | 156 | /** capability risks - see {@link http://docs.moodle.org/en/Development:Hardening_new_Roles_system} */ |
21b6db6e | 157 | define('RISK_SPAM', 0x0010); |
cc3edaa4 | 158 | /** capability risks - see {@link http://docs.moodle.org/en/Development:Hardening_new_Roles_system} */ |
3a0c6cca | 159 | define('RISK_DATALOSS', 0x0020); |
21b6db6e | 160 | |
cc3edaa4 | 161 | /** rolename displays - the name as defined in the role definition */ |
162 | define('ROLENAME_ORIGINAL', 0); | |
163 | /** rolename displays - the name as defined by a role alias */ | |
164 | define('ROLENAME_ALIAS', 1); | |
165 | /** rolename displays - Both, like this: Role alias (Original)*/ | |
166 | define('ROLENAME_BOTH', 2); | |
167 | /** rolename displays - the name as defined in the role definition and the shortname in brackets*/ | |
168 | define('ROLENAME_ORIGINALANDSHORT', 3); | |
169 | /** rolename displays - the name as defined by a role alias, in raw form suitable for editing*/ | |
170 | define('ROLENAME_ALIAS_RAW', 4); | |
df997f84 PS |
171 | /** rolename displays - the name is simply short role name*/ |
172 | define('ROLENAME_SHORT', 5); | |
cc3edaa4 | 173 | |
174 | /** size limit for context cache */ | |
117bd748 | 175 | if (!defined('MAX_CONTEXT_CACHE_SIZE')) { |
4d10247f | 176 | define('MAX_CONTEXT_CACHE_SIZE', 5000); |
177 | } | |
178 | ||
cc3edaa4 | 179 | /** |
117bd748 | 180 | * Although this looks like a global variable, it isn't really. |
cc3edaa4 | 181 | * |
117bd748 PS |
182 | * It is just a private implementation detail to accesslib that MUST NOT be used elsewhere. |
183 | * It is used to cache various bits of data between function calls for performance reasons. | |
4f65e0fb | 184 | * Sadly, a PHP global variable is the only way to implement this, without rewriting everything |
cc3edaa4 | 185 | * as methods of a class, instead of functions. |
186 | * | |
187 | * @global stdClass $ACCESSLIB_PRIVATE | |
188 | * @name $ACCESSLIB_PRIVATE | |
189 | */ | |
d867e696 | 190 | $ACCESSLIB_PRIVATE = new stdClass; |
191 | $ACCESSLIB_PRIVATE->contexts = array(); // Cache of context objects by level and instance | |
192 | $ACCESSLIB_PRIVATE->contextsbyid = array(); // Cache of context objects by id | |
4f0c2d00 PS |
193 | $ACCESSLIB_PRIVATE->systemcontext = NULL; // Used in get_system_context |
194 | $ACCESSLIB_PRIVATE->dirtycontexts = NULL; // Dirty contexts cache | |
d867e696 | 195 | $ACCESSLIB_PRIVATE->accessdatabyuser = array(); // Holds the $accessdata structure for users other than $USER |
196 | $ACCESSLIB_PRIVATE->roledefinitions = array(); // role definitions cache - helps a lot with mem usage in cron | |
197 | $ACCESSLIB_PRIVATE->croncache = array(); // Used in get_role_access | |
198 | $ACCESSLIB_PRIVATE->preloadedcourses = array(); // Used in preload_course_contexts. | |
4f0c2d00 | 199 | $ACCESSLIB_PRIVATE->capabilities = NULL; // detailed information about the capabilities |
bbbf2d40 | 200 | |
d867e696 | 201 | /** |
46808d7c | 202 | * Clears accesslib's private caches. ONLY BE USED BY UNIT TESTS |
117bd748 | 203 | * |
d867e696 | 204 | * This method should ONLY BE USED BY UNIT TESTS. It clears all of |
205 | * accesslib's private caches. You need to do this before setting up test data, | |
4f65e0fb | 206 | * and also at the end of the tests. |
cc3edaa4 | 207 | * @global object |
208 | * @global object | |
209 | * @global object | |
d867e696 | 210 | */ |
211 | function accesslib_clear_all_caches_for_unit_testing() { | |
212 | global $UNITTEST, $USER, $ACCESSLIB_PRIVATE; | |
213 | if (empty($UNITTEST->running)) { | |
214 | throw new coding_exception('You must not call clear_all_caches outside of unit tests.'); | |
215 | } | |
216 | $ACCESSLIB_PRIVATE->contexts = array(); | |
217 | $ACCESSLIB_PRIVATE->contextsbyid = array(); | |
4f0c2d00 PS |
218 | $ACCESSLIB_PRIVATE->systemcontext = NULL; |
219 | $ACCESSLIB_PRIVATE->dirtycontexts = NULL; | |
d867e696 | 220 | $ACCESSLIB_PRIVATE->accessdatabyuser = array(); |
221 | $ACCESSLIB_PRIVATE->roledefinitions = array(); | |
222 | $ACCESSLIB_PRIVATE->croncache = array(); | |
223 | $ACCESSLIB_PRIVATE->preloadedcourses = array(); | |
4f0c2d00 | 224 | $ACCESSLIB_PRIVATE->capabilities = NULL; |
d867e696 | 225 | |
226 | unset($USER->access); | |
227 | } | |
228 | ||
229 | /** | |
230 | * Private function. Add a context object to accesslib's caches. | |
cc3edaa4 | 231 | * @global object |
46808d7c | 232 | * @param object $context |
d867e696 | 233 | */ |
234 | function cache_context($context) { | |
235 | global $ACCESSLIB_PRIVATE; | |
4d10247f | 236 | |
237 | // If there are too many items in the cache already, remove items until | |
238 | // there is space | |
239 | while (count($ACCESSLIB_PRIVATE->contextsbyid) >= MAX_CONTEXT_CACHE_SIZE) { | |
56f3599b | 240 | $first = reset($ACCESSLIB_PRIVATE->contextsbyid); |
241 | unset($ACCESSLIB_PRIVATE->contextsbyid[$first->id]); | |
4d10247f | 242 | unset($ACCESSLIB_PRIVATE->contexts[$first->contextlevel][$first->instanceid]); |
243 | } | |
244 | ||
d867e696 | 245 | $ACCESSLIB_PRIVATE->contexts[$context->contextlevel][$context->instanceid] = $context; |
246 | $ACCESSLIB_PRIVATE->contextsbyid[$context->id] = $context; | |
247 | } | |
7700027f | 248 | |
46808d7c | 249 | /** |
250 | * This is really slow!!! do not use above course context level | |
251 | * | |
cc3edaa4 | 252 | * @global object |
46808d7c | 253 | * @param int $roleid |
254 | * @param object $context | |
255 | * @return array | |
256 | */ | |
eef879ec | 257 | function get_role_context_caps($roleid, $context) { |
f33e1ed4 | 258 | global $DB; |
259 | ||
eef879ec | 260 | //this is really slow!!!! - do not use above course context level! |
261 | $result = array(); | |
262 | $result[$context->id] = array(); | |
e7876c1e | 263 | |
eef879ec | 264 | // first emulate the parent context capabilities merging into context |
265 | $searchcontexts = array_reverse(get_parent_contexts($context)); | |
266 | array_push($searchcontexts, $context->id); | |
267 | foreach ($searchcontexts as $cid) { | |
f33e1ed4 | 268 | if ($capabilities = $DB->get_records('role_capabilities', array('roleid'=>$roleid, 'contextid'=>$cid))) { |
eef879ec | 269 | foreach ($capabilities as $cap) { |
270 | if (!array_key_exists($cap->capability, $result[$context->id])) { | |
271 | $result[$context->id][$cap->capability] = 0; | |
272 | } | |
273 | $result[$context->id][$cap->capability] += $cap->permission; | |
274 | } | |
275 | } | |
276 | } | |
e7876c1e | 277 | |
eef879ec | 278 | // now go through the contexts bellow given context |
19bb8a05 | 279 | $searchcontexts = array_keys(get_child_contexts($context)); |
eef879ec | 280 | foreach ($searchcontexts as $cid) { |
f33e1ed4 | 281 | if ($capabilities = $DB->get_records('role_capabilities', array('roleid'=>$roleid, 'contextid'=>$cid))) { |
eef879ec | 282 | foreach ($capabilities as $cap) { |
283 | if (!array_key_exists($cap->contextid, $result)) { | |
284 | $result[$cap->contextid] = array(); | |
285 | } | |
286 | $result[$cap->contextid][$cap->capability] = $cap->permission; | |
287 | } | |
288 | } | |
e7876c1e | 289 | } |
290 | ||
eef879ec | 291 | return $result; |
292 | } | |
293 | ||
eef879ec | 294 | /** |
46808d7c | 295 | * Gets the accessdata for role "sitewide" (system down to course) |
343effbe | 296 | * |
cc3edaa4 | 297 | * @global object |
298 | * @global object | |
46808d7c | 299 | * @param int $roleid |
4f0c2d00 | 300 | * @param array $accessdata defaults to NULL |
e0376a62 | 301 | * @return array |
eef879ec | 302 | */ |
bb2c22bd | 303 | function get_role_access($roleid, $accessdata=NULL) { |
cdfa3035 | 304 | |
f33e1ed4 | 305 | global $CFG, $DB; |
eef879ec | 306 | |
e0376a62 | 307 | /* Get it in 1 cheap DB query... |
308 | * - relevant role caps at the root and down | |
309 | * to the course level - but not below | |
310 | */ | |
bb2c22bd | 311 | if (is_null($accessdata)) { |
312 | $accessdata = array(); // named list | |
313 | $accessdata['ra'] = array(); | |
314 | $accessdata['rdef'] = array(); | |
315 | $accessdata['loaded'] = array(); | |
e7876c1e | 316 | } |
317 | ||
e0376a62 | 318 | // |
319 | // Overrides for the role IN ANY CONTEXTS | |
320 | // down to COURSE - not below - | |
321 | // | |
322 | $sql = "SELECT ctx.path, | |
323 | rc.capability, rc.permission | |
f33e1ed4 | 324 | FROM {context} ctx |
325 | JOIN {role_capabilities} rc | |
326 | ON rc.contextid=ctx.id | |
327 | WHERE rc.roleid = ? | |
328 | AND ctx.contextlevel <= ".CONTEXT_COURSE." | |
329 | ORDER BY ctx.depth, ctx.path"; | |
330 | $params = array($roleid); | |
133d5a97 | 331 | |
a91b910e | 332 | // we need extra caching in CLI scripts and cron |
333 | if (CLI_SCRIPT) { | |
d867e696 | 334 | global $ACCESSLIB_PRIVATE; |
133d5a97 | 335 | |
d867e696 | 336 | if (!isset($ACCESSLIB_PRIVATE->croncache[$roleid])) { |
337 | $ACCESSLIB_PRIVATE->croncache[$roleid] = array(); | |
f33e1ed4 | 338 | if ($rs = $DB->get_recordset_sql($sql, $params)) { |
339 | foreach ($rs as $rd) { | |
d867e696 | 340 | $ACCESSLIB_PRIVATE->croncache[$roleid][] = $rd; |
133d5a97 | 341 | } |
f33e1ed4 | 342 | $rs->close(); |
133d5a97 | 343 | } |
344 | } | |
345 | ||
d867e696 | 346 | foreach ($ACCESSLIB_PRIVATE->croncache[$roleid] as $rd) { |
03cedd62 | 347 | $k = "{$rd->path}:{$roleid}"; |
348 | $accessdata['rdef'][$k][$rd->capability] = $rd->permission; | |
e0376a62 | 349 | } |
5a4e7398 | 350 | |
133d5a97 | 351 | } else { |
f33e1ed4 | 352 | if ($rs = $DB->get_recordset_sql($sql, $params)) { |
353 | foreach ($rs as $rd) { | |
133d5a97 | 354 | $k = "{$rd->path}:{$roleid}"; |
355 | $accessdata['rdef'][$k][$rd->capability] = $rd->permission; | |
356 | } | |
357 | unset($rd); | |
f33e1ed4 | 358 | $rs->close(); |
133d5a97 | 359 | } |
8d2b18a8 | 360 | } |
e0376a62 | 361 | |
bb2c22bd | 362 | return $accessdata; |
e7876c1e | 363 | } |
364 | ||
4e1fe7d1 | 365 | /** |
46808d7c | 366 | * Gets the accessdata for role "sitewide" (system down to course) |
4e1fe7d1 | 367 | * |
cc3edaa4 | 368 | * @global object |
369 | * @global object | |
46808d7c | 370 | * @param int $roleid |
4f0c2d00 | 371 | * @param array $accessdata defaults to NULL |
4e1fe7d1 | 372 | * @return array |
373 | */ | |
374 | function get_default_frontpage_role_access($roleid, $accessdata=NULL) { | |
375 | ||
f33e1ed4 | 376 | global $CFG, $DB; |
5a4e7398 | 377 | |
4e1fe7d1 | 378 | $frontpagecontext = get_context_instance(CONTEXT_COURSE, SITEID); |
379 | $base = '/'. SYSCONTEXTID .'/'. $frontpagecontext->id; | |
5a4e7398 | 380 | |
4e1fe7d1 | 381 | // |
382 | // Overrides for the role in any contexts related to the course | |
383 | // | |
384 | $sql = "SELECT ctx.path, | |
385 | rc.capability, rc.permission | |
f33e1ed4 | 386 | FROM {context} ctx |
387 | JOIN {role_capabilities} rc | |
388 | ON rc.contextid=ctx.id | |
389 | WHERE rc.roleid = ? | |
390 | AND (ctx.id = ".SYSCONTEXTID." OR ctx.path LIKE ?) | |
391 | AND ctx.contextlevel <= ".CONTEXT_COURSE." | |
392 | ORDER BY ctx.depth, ctx.path"; | |
393 | $params = array($roleid, "$base/%"); | |
5a4e7398 | 394 | |
f33e1ed4 | 395 | if ($rs = $DB->get_recordset_sql($sql, $params)) { |
396 | foreach ($rs as $rd) { | |
03cedd62 | 397 | $k = "{$rd->path}:{$roleid}"; |
398 | $accessdata['rdef'][$k][$rd->capability] = $rd->permission; | |
4e1fe7d1 | 399 | } |
03cedd62 | 400 | unset($rd); |
f33e1ed4 | 401 | $rs->close(); |
4e1fe7d1 | 402 | } |
403 | ||
404 | return $accessdata; | |
405 | } | |
406 | ||
407 | ||
8f8ed475 | 408 | /** |
409 | * Get the default guest role | |
117bd748 | 410 | * |
cc3edaa4 | 411 | * @global object |
412 | * @global object | |
8f8ed475 | 413 | * @return object role |
414 | */ | |
415 | function get_guest_role() { | |
f33e1ed4 | 416 | global $CFG, $DB; |
ebce32b5 | 417 | |
418 | if (empty($CFG->guestroleid)) { | |
4f0c2d00 | 419 | if ($roles = $DB->get_records('role', array('archetype'=>'guest'))) { |
ebce32b5 | 420 | $guestrole = array_shift($roles); // Pick the first one |
421 | set_config('guestroleid', $guestrole->id); | |
422 | return $guestrole; | |
423 | } else { | |
424 | debugging('Can not find any guest role!'); | |
425 | return false; | |
426 | } | |
8f8ed475 | 427 | } else { |
f33e1ed4 | 428 | if ($guestrole = $DB->get_record('role', array('id'=>$CFG->guestroleid))) { |
ebce32b5 | 429 | return $guestrole; |
430 | } else { | |
431 | //somebody is messing with guest roles, remove incorrect setting and try to find a new one | |
432 | set_config('guestroleid', ''); | |
433 | return get_guest_role(); | |
434 | } | |
8f8ed475 | 435 | } |
436 | } | |
437 | ||
128f0984 | 438 | /** |
4f65e0fb | 439 | * Check whether a user has a particular capability in a given context. |
46808d7c | 440 | * |
41e87d30 | 441 | * For example:: |
442 | * $context = get_context_instance(CONTEXT_MODULE, $cm->id); | |
443 | * has_capability('mod/forum:replypost',$context) | |
46808d7c | 444 | * |
4f65e0fb | 445 | * By default checks the capabilities of the current user, but you can pass a |
4f0c2d00 PS |
446 | * different userid. By default will return true for admin users, but you can override that with the fourth argument. |
447 | * | |
448 | * Guest and not-logged-in users can never get any dangerous capability - that is any write capability | |
449 | * or capabilities with XSS, config or data loss risks. | |
117bd748 | 450 | * |
41e87d30 | 451 | * @param string $capability the name of the capability to check. For example mod/forum:view |
452 | * @param object $context the context to check the capability in. You normally get this with {@link get_context_instance}. | |
4f0c2d00 PS |
453 | * @param integer|object $user A user id or object. By default (NULL) checks the permissions of the current user. |
454 | * @param boolean $doanything If false, ignores effect of admin role assignment | |
41e87d30 | 455 | * @return boolean true if the user has this capability. Otherwise false. |
128f0984 | 456 | */ |
4f0c2d00 | 457 | function has_capability($capability, $context, $user = NULL, $doanything=true) { |
d867e696 | 458 | global $USER, $CFG, $DB, $SCRIPT, $ACCESSLIB_PRIVATE; |
18818abf | 459 | |
31a99877 | 460 | if (during_initial_install()) { |
18818abf | 461 | if ($SCRIPT === "/$CFG->admin/index.php" or $SCRIPT === "/$CFG->admin/cliupgrade.php") { |
462 | // we are in an installer - roles can not work yet | |
463 | return true; | |
464 | } else { | |
465 | return false; | |
466 | } | |
467 | } | |
7f97ea29 | 468 | |
4f0c2d00 PS |
469 | if (strpos($capability, 'moodle/legacy:') === 0) { |
470 | throw new coding_exception('Legacy capabilities can not be used any more!'); | |
471 | } | |
472 | ||
7d0c81b3 | 473 | // the original $CONTEXT here was hiding serious errors |
128f0984 | 474 | // for security reasons do not reuse previous context |
7d0c81b3 | 475 | if (empty($context)) { |
476 | debugging('Incorrect context specified'); | |
477 | return false; | |
74ac5b66 | 478 | } |
4f0c2d00 PS |
479 | if (!is_bool($doanything)) { |
480 | throw new coding_exception('Capability parameter "doanything" is wierd ("'.$doanything.'"). This has to be fixed in code.'); | |
481 | } | |
7f97ea29 | 482 | |
4f0c2d00 PS |
483 | // make sure there is a real user specified |
484 | if ($user === NULL) { | |
485 | $userid = !empty($USER->id) ? $USER->id : 0; | |
486 | } else { | |
487 | $userid = !empty($user->id) ? $user->id : $user; | |
cc3d5e10 | 488 | } |
489 | ||
4f0c2d00 PS |
490 | // capability must exist |
491 | if (!$capinfo = get_capability_info($capability)) { | |
492 | debugging('Capability "'.$capability.'" was not found! This should be fixed in code.'); | |
493 | return false; | |
494 | } | |
495 | // make sure the guest account and not-logged-in users never get any risky caps no matter what the actual settings are. | |
496 | if (($capinfo->captype === 'write') or ((int)$capinfo->riskbitmask & (RISK_XSS | RISK_CONFIG | RISK_DATALOSS))) { | |
497 | if (isguestuser($userid) or $userid == 0) { | |
498 | return false; | |
c84a2dbe | 499 | } |
7f97ea29 | 500 | } |
501 | ||
128f0984 | 502 | if (is_null($context->path) or $context->depth == 0) { |
503 | //this should not happen | |
504 | $contexts = array(SYSCONTEXTID, $context->id); | |
505 | $context->path = '/'.SYSCONTEXTID.'/'.$context->id; | |
506 | debugging('Context id '.$context->id.' does not have valid path, please use build_context_path()', DEBUG_DEVELOPER); | |
507 | ||
7f97ea29 | 508 | } else { |
509 | $contexts = explode('/', $context->path); | |
510 | array_shift($contexts); | |
511 | } | |
512 | ||
a91b910e | 513 | if (CLI_SCRIPT && !isset($USER->access)) { |
1a9b6787 | 514 | // In cron, some modules setup a 'fake' $USER, |
515 | // ensure we load the appropriate accessdata. | |
d867e696 | 516 | if (isset($ACCESSLIB_PRIVATE->accessdatabyuser[$userid])) { |
517 | $ACCESSLIB_PRIVATE->dirtycontexts = NULL; //load fresh dirty contexts | |
128f0984 | 518 | } else { |
1a9b6787 | 519 | load_user_accessdata($userid); |
d867e696 | 520 | $ACCESSLIB_PRIVATE->dirtycontexts = array(); |
1a9b6787 | 521 | } |
d867e696 | 522 | $USER->access = $ACCESSLIB_PRIVATE->accessdatabyuser[$userid]; |
7d0c81b3 | 523 | |
7fde45a7 | 524 | } else if (isset($USER->id) && ($USER->id == $userid) && !isset($USER->access)) { |
7d0c81b3 | 525 | // caps not loaded yet - better to load them to keep BC with 1.8 |
128f0984 | 526 | // not-logged-in user or $USER object set up manually first time here |
7d0c81b3 | 527 | load_all_capabilities(); |
d867e696 | 528 | $ACCESSLIB_PRIVATE->accessdatabyuser = array(); // reset the cache for other users too, the dirty contexts are empty now |
529 | $ACCESSLIB_PRIVATE->roledefinitions = array(); | |
1a9b6787 | 530 | } |
531 | ||
128f0984 | 532 | // Load dirty contexts list if needed |
d867e696 | 533 | if (!isset($ACCESSLIB_PRIVATE->dirtycontexts)) { |
7be3be1b | 534 | if (isset($USER->access['time'])) { |
d867e696 | 535 | $ACCESSLIB_PRIVATE->dirtycontexts = get_dirty_contexts($USER->access['time']); |
7be3be1b | 536 | } |
537 | else { | |
d867e696 | 538 | $ACCESSLIB_PRIVATE->dirtycontexts = array(); |
7be3be1b | 539 | } |
148eb2a7 | 540 | } |
128f0984 | 541 | |
542 | // Careful check for staleness... | |
d867e696 | 543 | if (count($ACCESSLIB_PRIVATE->dirtycontexts) !== 0 and is_contextpath_dirty($contexts, $ACCESSLIB_PRIVATE->dirtycontexts)) { |
ef989bd9 | 544 | // reload all capabilities - preserving loginas, roleswitches, etc |
545 | // and then cleanup any marks of dirtyness... at least from our short | |
546 | // term memory! :-) | |
d867e696 | 547 | $ACCESSLIB_PRIVATE->accessdatabyuser = array(); |
548 | $ACCESSLIB_PRIVATE->roledefinitions = array(); | |
128f0984 | 549 | |
a91b910e | 550 | if (CLI_SCRIPT) { |
128f0984 | 551 | load_user_accessdata($userid); |
d867e696 | 552 | $USER->access = $ACCESSLIB_PRIVATE->accessdatabyuser[$userid]; |
553 | $ACCESSLIB_PRIVATE->dirtycontexts = array(); | |
128f0984 | 554 | |
555 | } else { | |
556 | reload_all_capabilities(); | |
557 | } | |
148eb2a7 | 558 | } |
128f0984 | 559 | |
4f0c2d00 PS |
560 | // Find out if user is admin - it is not possible to override the doanything in any way |
561 | // and it is not possible to switch to admin role either. | |
562 | if ($doanything) { | |
563 | if (is_siteadmin($userid)) { | |
7abbc5c2 PS |
564 | if ($userid != $USER->id) { |
565 | return true; | |
566 | } | |
567 | // make sure switchrole is not used in this context | |
568 | if (empty($USER->access['rsw'])) { | |
569 | return true; | |
570 | } | |
571 | $parts = explode('/', trim($context->path, '/')); | |
572 | $path = ''; | |
573 | $switched = false; | |
574 | foreach ($parts as $part) { | |
575 | $path .= '/' . $part; | |
576 | if (!empty($USER->access['rsw'][$path])) { | |
577 | $switched = true; | |
578 | break; | |
579 | } | |
580 | } | |
581 | if (!$switched) { | |
582 | return true; | |
583 | } | |
584 | //ok, admin switched role in this context, let's use normal access control rules | |
4f0c2d00 PS |
585 | } |
586 | } | |
587 | ||
13a79475 | 588 | // divulge how many times we are called |
589 | //// error_log("has_capability: id:{$context->id} path:{$context->path} userid:$userid cap:$capability"); | |
590 | ||
7fde45a7 | 591 | if (isset($USER->id) && ($USER->id == $userid)) { // we must accept strings and integers in $userid |
74ac5b66 | 592 | // |
593 | // For the logged in user, we have $USER->access | |
594 | // which will have all RAs and caps preloaded for | |
595 | // course and above contexts. | |
596 | // | |
597 | // Contexts below courses && contexts that do not | |
598 | // hang from courses are loaded into $USER->access | |
599 | // on demand, and listed in $USER->access[loaded] | |
600 | // | |
7f97ea29 | 601 | if ($context->contextlevel <= CONTEXT_COURSE) { |
602 | // Course and above are always preloaded | |
4f0c2d00 | 603 | return has_capability_in_accessdata($capability, $context, $USER->access); |
7f97ea29 | 604 | } |
420bfab1 | 605 | // Load accessdata for below-the-course contexts |
31c2de82 | 606 | if (!path_inaccessdata($context->path,$USER->access)) { |
6100dad0 | 607 | // error_log("loading access for context {$context->path} for $capability at {$context->contextlevel} {$context->id}"); |
74ac5b66 | 608 | // $bt = debug_backtrace(); |
609 | // error_log("bt {$bt[0]['file']} {$bt[0]['line']}"); | |
a2cf7f1b | 610 | load_subcontext($USER->id, $context, $USER->access); |
74ac5b66 | 611 | } |
4f0c2d00 | 612 | return has_capability_in_accessdata($capability, $context, $USER->access); |
7f97ea29 | 613 | } |
bb2c22bd | 614 | |
d867e696 | 615 | if (!isset($ACCESSLIB_PRIVATE->accessdatabyuser[$userid])) { |
204a369c | 616 | load_user_accessdata($userid); |
617 | } | |
4f0c2d00 | 618 | |
420bfab1 | 619 | if ($context->contextlevel <= CONTEXT_COURSE) { |
620 | // Course and above are always preloaded | |
4f0c2d00 | 621 | return has_capability_in_accessdata($capability, $context, $ACCESSLIB_PRIVATE->accessdatabyuser[$userid]); |
420bfab1 | 622 | } |
623 | // Load accessdata for below-the-course contexts as needed | |
d867e696 | 624 | if (!path_inaccessdata($context->path, $ACCESSLIB_PRIVATE->accessdatabyuser[$userid])) { |
6100dad0 | 625 | // error_log("loading access for context {$context->path} for $capability at {$context->contextlevel} {$context->id}"); |
420bfab1 | 626 | // $bt = debug_backtrace(); |
627 | // error_log("bt {$bt[0]['file']} {$bt[0]['line']}"); | |
d867e696 | 628 | load_subcontext($userid, $context, $ACCESSLIB_PRIVATE->accessdatabyuser[$userid]); |
420bfab1 | 629 | } |
4f0c2d00 | 630 | return has_capability_in_accessdata($capability, $context, $ACCESSLIB_PRIVATE->accessdatabyuser[$userid]); |
7f97ea29 | 631 | } |
632 | ||
3fc3ebf2 | 633 | /** |
41e87d30 | 634 | * Check if the user has any one of several capabilities from a list. |
46808d7c | 635 | * |
41e87d30 | 636 | * This is just a utility method that calls has_capability in a loop. Try to put |
637 | * the capabilities that most users are likely to have first in the list for best | |
638 | * performance. | |
3fc3ebf2 | 639 | * |
640 | * There are probably tricks that could be done to improve the performance here, for example, | |
641 | * check the capabilities that are already cached first. | |
642 | * | |
46808d7c | 643 | * @see has_capability() |
41e87d30 | 644 | * @param array $capabilities an array of capability names. |
645 | * @param object $context the context to check the capability in. You normally get this with {@link get_context_instance}. | |
4f0c2d00 PS |
646 | * @param integer $userid A user id. By default (NULL) checks the permissions of the current user. |
647 | * @param boolean $doanything If false, ignore effect of admin role assignment | |
41e87d30 | 648 | * @return boolean true if the user has any of these capabilities. Otherwise false. |
3fc3ebf2 | 649 | */ |
650 | function has_any_capability($capabilities, $context, $userid=NULL, $doanything=true) { | |
41e87d30 | 651 | if (!is_array($capabilities)) { |
652 | debugging('Incorrect $capabilities parameter in has_any_capabilities() call - must be an array'); | |
653 | return false; | |
654 | } | |
3fc3ebf2 | 655 | foreach ($capabilities as $capability) { |
59664877 | 656 | if (has_capability($capability, $context, $userid, $doanything)) { |
3fc3ebf2 | 657 | return true; |
658 | } | |
659 | } | |
660 | return false; | |
661 | } | |
662 | ||
8a1b1c32 | 663 | /** |
41e87d30 | 664 | * Check if the user has all the capabilities in a list. |
46808d7c | 665 | * |
41e87d30 | 666 | * This is just a utility method that calls has_capability in a loop. Try to put |
667 | * the capabilities that fewest users are likely to have first in the list for best | |
668 | * performance. | |
8a1b1c32 | 669 | * |
670 | * There are probably tricks that could be done to improve the performance here, for example, | |
671 | * check the capabilities that are already cached first. | |
672 | * | |
46808d7c | 673 | * @see has_capability() |
41e87d30 | 674 | * @param array $capabilities an array of capability names. |
675 | * @param object $context the context to check the capability in. You normally get this with {@link get_context_instance}. | |
4f0c2d00 PS |
676 | * @param integer $userid A user id. By default (NULL) checks the permissions of the current user. |
677 | * @param boolean $doanything If false, ignore effect of admin role assignment | |
41e87d30 | 678 | * @return boolean true if the user has all of these capabilities. Otherwise false. |
8a1b1c32 | 679 | */ |
680 | function has_all_capabilities($capabilities, $context, $userid=NULL, $doanything=true) { | |
3ce50127 | 681 | if (!is_array($capabilities)) { |
682 | debugging('Incorrect $capabilities parameter in has_all_capabilities() call - must be an array'); | |
683 | return false; | |
684 | } | |
8a1b1c32 | 685 | foreach ($capabilities as $capability) { |
686 | if (!has_capability($capability, $context, $userid, $doanything)) { | |
687 | return false; | |
688 | } | |
689 | } | |
690 | return true; | |
691 | } | |
692 | ||
128f0984 | 693 | /** |
4f0c2d00 | 694 | * Check if the user is an admin at the site level. |
46808d7c | 695 | * |
4f0c2d00 PS |
696 | * Please note that use of proper capabilities is always encouraged, |
697 | * this function is supposed to be used from core or for temporary hacks. | |
39407442 | 698 | * |
4f0c2d00 PS |
699 | * @param int|object $user_or_id user id or user object |
700 | * @returns bool true if user is one of the administrators, false otherwise | |
39407442 | 701 | */ |
4f0c2d00 PS |
702 | function is_siteadmin($user_or_id = NULL) { |
703 | global $CFG, $USER; | |
39407442 | 704 | |
4f0c2d00 PS |
705 | if ($user_or_id === NULL) { |
706 | $user_or_id = $USER; | |
707 | } | |
6cab02ac | 708 | |
4f0c2d00 PS |
709 | if (empty($user_or_id)) { |
710 | return false; | |
711 | } | |
712 | if (!empty($user_or_id->id)) { | |
713 | // we support | |
714 | $userid = $user_or_id->id; | |
715 | } else { | |
716 | $userid = $user_or_id; | |
717 | } | |
6cab02ac | 718 | |
4f0c2d00 PS |
719 | $siteadmins = explode(',', $CFG->siteadmins); |
720 | return in_array($userid, $siteadmins); | |
6cab02ac | 721 | } |
722 | ||
bbdb7070 | 723 | /** |
4f0c2d00 | 724 | * Returns true if user has at least one role assign |
df997f84 | 725 | * of 'coursecontact' role (is potentially listed in some course descriptions). |
4f0c2d00 PS |
726 | * @param $userid |
727 | * @return unknown_type | |
bbdb7070 | 728 | */ |
df997f84 | 729 | function has_coursecontact_role($userid) { |
bbdb7070 | 730 | global $DB; |
731 | ||
df997f84 | 732 | if (empty($CFG->coursecontact)) { |
4f0c2d00 PS |
733 | return false; |
734 | } | |
735 | $sql = "SELECT 1 | |
736 | FROM {role_assignments} | |
df997f84 | 737 | WHERE userid = :userid AND roleid IN ($CFG->coursecontact)"; |
4f0c2d00 | 738 | return $DB->record_exists($sql, array('userid'=>$userid)); |
bbdb7070 | 739 | } |
740 | ||
46808d7c | 741 | /** |
742 | * @param string $path | |
743 | * @return string | |
744 | */ | |
4f0c2d00 | 745 | function get_course_from_path($path) { |
7f97ea29 | 746 | // assume that nothing is more than 1 course deep |
747 | if (preg_match('!^(/.+)/\d+$!', $path, $matches)) { | |
748 | return $matches[1]; | |
749 | } | |
750 | return false; | |
751 | } | |
752 | ||
46808d7c | 753 | /** |
754 | * @param string $path | |
755 | * @param array $accessdata | |
756 | * @return bool | |
757 | */ | |
bb2c22bd | 758 | function path_inaccessdata($path, $accessdata) { |
4f0c2d00 PS |
759 | if (empty($accessdata['loaded'])) { |
760 | return false; | |
761 | } | |
74ac5b66 | 762 | |
763 | // assume that contexts hang from sys or from a course | |
764 | // this will only work well with stuff that hangs from a course | |
bb2c22bd | 765 | if (in_array($path, $accessdata['loaded'], true)) { |
b738808b | 766 | // error_log("found it!"); |
74ac5b66 | 767 | return true; |
768 | } | |
769 | $base = '/' . SYSCONTEXTID; | |
770 | while (preg_match('!^(/.+)/\d+$!', $path, $matches)) { | |
771 | $path = $matches[1]; | |
772 | if ($path === $base) { | |
773 | return false; | |
774 | } | |
bb2c22bd | 775 | if (in_array($path, $accessdata['loaded'], true)) { |
74ac5b66 | 776 | return true; |
777 | } | |
778 | } | |
779 | return false; | |
780 | } | |
781 | ||
128f0984 | 782 | /** |
01a2ce80 | 783 | * Does the user have a capability to do something? |
46808d7c | 784 | * |
31c2de82 | 785 | * Walk the accessdata array and return true/false. |
6a8d9a38 | 786 | * Deals with prohibits, roleswitching, aggregating |
787 | * capabilities, etc. | |
788 | * | |
789 | * The main feature of here is being FAST and with no | |
5a4e7398 | 790 | * side effects. |
6a8d9a38 | 791 | * |
3ac81bd1 | 792 | * Notes: |
793 | * | |
794 | * Switch Roles exits early | |
01a2ce80 | 795 | * ------------------------ |
3ac81bd1 | 796 | * cap checks within a switchrole need to exit early |
797 | * in our bottom up processing so they don't "see" that | |
798 | * there are real RAs that can do all sorts of things. | |
799 | * | |
3d034f77 | 800 | * Switch Role merges with default role |
801 | * ------------------------------------ | |
802 | * If you are a teacher in course X, you have at least | |
803 | * teacher-in-X + defaultloggedinuser-sitewide. So in the | |
804 | * course you'll have techer+defaultloggedinuser. | |
805 | * We try to mimic that in switchrole. | |
806 | * | |
01a2ce80 PS |
807 | * Permission evaluation |
808 | * --------------------- | |
4f65e0fb | 809 | * Originally there was an extremely complicated way |
01a2ce80 | 810 | * to determine the user access that dealt with |
4f65e0fb PS |
811 | * "locality" or role assignments and role overrides. |
812 | * Now we simply evaluate access for each role separately | |
01a2ce80 PS |
813 | * and then verify if user has at least one role with allow |
814 | * and at the same time no role with prohibit. | |
815 | * | |
46808d7c | 816 | * @param string $capability |
817 | * @param object $context | |
818 | * @param array $accessdata | |
46808d7c | 819 | * @return bool |
6a8d9a38 | 820 | */ |
4f0c2d00 | 821 | function has_capability_in_accessdata($capability, $context, array $accessdata) { |
3ac81bd1 | 822 | global $CFG; |
823 | ||
01a2ce80 PS |
824 | if (empty($context->id)) { |
825 | throw new coding_exception('Invalid context specified'); | |
7f97ea29 | 826 | } |
3ac81bd1 | 827 | |
01a2ce80 PS |
828 | // Build $paths as a list of current + all parent "paths" with order bottom-to-top |
829 | $contextids = explode('/', trim($context->path, '/')); | |
830 | $paths = array($context->path); | |
831 | while ($contextids) { | |
832 | array_pop($contextids); | |
833 | $paths[] = '/' . implode('/', $contextids); | |
3ac81bd1 | 834 | } |
01a2ce80 | 835 | unset($contextids); |
3ac81bd1 | 836 | |
01a2ce80 PS |
837 | $roles = array(); |
838 | $switchedrole = false; | |
6a8d9a38 | 839 | |
01a2ce80 PS |
840 | // Find out if role switched |
841 | if (!empty($accessdata['rsw'])) { | |
6a8d9a38 | 842 | // From the bottom up... |
01a2ce80 | 843 | foreach ($paths as $path) { |
1209cb5c | 844 | if (isset($accessdata['rsw'][$path])) { |
01a2ce80 | 845 | // Found a switchrole assignment - check for that role _plus_ the default user role |
4f0c2d00 | 846 | $roles = array($accessdata['rsw'][$path]=>NULL, $CFG->defaultuserroleid=>NULL); |
01a2ce80 PS |
847 | $switchedrole = true; |
848 | break; | |
6a8d9a38 | 849 | } |
850 | } | |
851 | } | |
852 | ||
01a2ce80 PS |
853 | if (!$switchedrole) { |
854 | // get all users roles in this context and above | |
855 | foreach ($paths as $path) { | |
856 | if (isset($accessdata['ra'][$path])) { | |
857 | foreach ($accessdata['ra'][$path] as $roleid) { | |
4f0c2d00 | 858 | $roles[$roleid] = NULL; |
7f97ea29 | 859 | } |
01a2ce80 PS |
860 | } |
861 | } | |
7f97ea29 | 862 | } |
863 | ||
01a2ce80 PS |
864 | // Now find out what access is given to each role, going bottom-->up direction |
865 | foreach ($roles as $roleid => $ignored) { | |
866 | foreach ($paths as $path) { | |
867 | if (isset($accessdata['rdef']["{$path}:$roleid"][$capability])) { | |
868 | $perm = (int)$accessdata['rdef']["{$path}:$roleid"][$capability]; | |
869 | if ($perm === CAP_PROHIBIT or is_null($roles[$roleid])) { | |
870 | $roles[$roleid] = $perm; | |
871 | } | |
872 | } | |
7f97ea29 | 873 | } |
01a2ce80 PS |
874 | } |
875 | // any CAP_PROHIBIT found means no permission for the user | |
876 | if (array_search(CAP_PROHIBIT, $roles) !== false) { | |
877 | return false; | |
7f97ea29 | 878 | } |
879 | ||
01a2ce80 PS |
880 | // at least one CAP_ALLOW means the user has a permission |
881 | return (array_search(CAP_ALLOW, $roles) !== false); | |
7f97ea29 | 882 | } |
018d4b52 | 883 | |
46808d7c | 884 | /** |
885 | * @param object $context | |
886 | * @param array $accessdata | |
887 | * @return array | |
888 | */ | |
bb2c22bd | 889 | function aggregate_roles_from_accessdata($context, $accessdata) { |
018d4b52 | 890 | |
891 | $path = $context->path; | |
892 | ||
893 | // build $contexts as a list of "paths" of the current | |
894 | // contexts and parents with the order top-to-bottom | |
895 | $contexts = array($path); | |
896 | while (preg_match('!^(/.+)/\d+$!', $path, $matches)) { | |
897 | $path = $matches[1]; | |
898 | array_unshift($contexts, $path); | |
899 | } | |
018d4b52 | 900 | |
901 | $cc = count($contexts); | |
902 | ||
903 | $roles = array(); | |
904 | // From the bottom up... | |
905 | for ($n=$cc-1;$n>=0;$n--) { | |
906 | $ctxp = $contexts[$n]; | |
bb2c22bd | 907 | if (isset($accessdata['ra'][$ctxp]) && count($accessdata['ra'][$ctxp])) { |
6cc59cb2 | 908 | // Found assignments on this leaf |
bb2c22bd | 909 | $addroles = $accessdata['ra'][$ctxp]; |
6cc59cb2 | 910 | $roles = array_merge($roles, $addroles); |
018d4b52 | 911 | } |
912 | } | |
913 | ||
914 | return array_unique($roles); | |
915 | } | |
916 | ||
0468976c | 917 | /** |
41e87d30 | 918 | * A convenience function that tests has_capability, and displays an error if |
919 | * the user does not have that capability. | |
8a9c1c1c | 920 | * |
41e87d30 | 921 | * NOTE before Moodle 2.0, this function attempted to make an appropriate |
922 | * require_login call before checking the capability. This is no longer the case. | |
923 | * You must call require_login (or one of its variants) if you want to check the | |
924 | * user is logged in, before you call this function. | |
efd6fce5 | 925 | * |
46808d7c | 926 | * @see has_capability() |
927 | * | |
41e87d30 | 928 | * @param string $capability the name of the capability to check. For example mod/forum:view |
929 | * @param object $context the context to check the capability in. You normally get this with {@link get_context_instance}. | |
4f0c2d00 PS |
930 | * @param integer $userid A user id. By default (NULL) checks the permissions of the current user. |
931 | * @param bool $doanything If false, ignore effect of admin role assignment | |
41e87d30 | 932 | * @param string $errorstring The error string to to user. Defaults to 'nopermissions'. |
933 | * @param string $stringfile The language file to load the error string from. Defaults to 'error'. | |
934 | * @return void terminates with an error if the user does not have the given capability. | |
0468976c | 935 | */ |
41e87d30 | 936 | function require_capability($capability, $context, $userid = NULL, $doanything = true, |
937 | $errormessage = 'nopermissions', $stringfile = '') { | |
d74067e8 | 938 | if (!has_capability($capability, $context, $userid, $doanything)) { |
9a0df45a | 939 | throw new required_capability_exception($context, $capability, $errormessage, $stringfile); |
0468976c | 940 | } |
941 | } | |
942 | ||
128f0984 | 943 | /** |
46808d7c | 944 | * Get an array of courses where cap requested is available |
df997f84 | 945 | * and user is enrolled, this can be relatively slow. |
46808d7c | 946 | * |
e1d5e5c1 | 947 | * @param string $capability - name of the capability |
df997f84 PS |
948 | * @param array $accessdata_ignored |
949 | * @param bool $doanything_ignored | |
e1d5e5c1 | 950 | * @param string $sort - sorting fields - prefix each fieldname with "c." |
951 | * @param array $fields - additional fields you are interested in... | |
df997f84 | 952 | * @param int $limit_ignored |
e1d5e5c1 | 953 | * @return array $courses - ordered array of course objects - see notes above |
e1d5e5c1 | 954 | */ |
df997f84 | 955 | function get_user_courses_bycap($userid, $cap, $accessdata_ignored, $doanything_ignored, $sort='c.sortorder ASC', $fields=NULL, $limit_ignored=0) { |
e1d5e5c1 | 956 | |
df997f84 | 957 | //TODO: this should be most probably deprecated |
e1d5e5c1 | 958 | |
df997f84 PS |
959 | $courses = enrol_get_users_courses($userid, true, $fields, $sort); |
960 | foreach ($courses as $id=>$course) { | |
961 | $context = get_context_instance(CONTEXT_COURSE, $id); | |
962 | if (!has_capability($cap, $context, $userid)) { | |
963 | unset($courses[$id]); | |
573674bf | 964 | } |
e1d5e5c1 | 965 | } |
5a4e7398 | 966 | |
e1d5e5c1 | 967 | return $courses; |
968 | } | |
969 | ||
b5a645b4 | 970 | |
a9bee37e | 971 | /** |
46808d7c | 972 | * Return a nested array showing role assignments |
a9bee37e | 973 | * all relevant role capabilities for the user at |
df997f84 | 974 | * site/course_category/course levels |
a9bee37e | 975 | * |
976 | * We do _not_ delve deeper than courses because the number of | |
977 | * overrides at the module/block levels is HUGE. | |
978 | * | |
f5930992 | 979 | * [ra] => [/path/][]=roleid |
a9bee37e | 980 | * [rdef] => [/path/:roleid][capability]=permission |
74ac5b66 | 981 | * [loaded] => array('/path', '/path') |
a9bee37e | 982 | * |
cc3edaa4 | 983 | * @global object |
984 | * @global object | |
a9bee37e | 985 | * @param $userid integer - the id of the user |
a9bee37e | 986 | */ |
74ac5b66 | 987 | function get_user_access_sitewide($userid) { |
a9bee37e | 988 | |
f33e1ed4 | 989 | global $CFG, $DB; |
a9bee37e | 990 | |
a9bee37e | 991 | /* Get in 3 cheap DB queries... |
f5930992 | 992 | * - role assignments |
a9bee37e | 993 | * - relevant role caps |
f5930992 | 994 | * - above and within this user's RAs |
a9bee37e | 995 | * - below this user's RAs - limited to course level |
996 | */ | |
997 | ||
bb2c22bd | 998 | $accessdata = array(); // named list |
999 | $accessdata['ra'] = array(); | |
1000 | $accessdata['rdef'] = array(); | |
1001 | $accessdata['loaded'] = array(); | |
a9bee37e | 1002 | |
a9bee37e | 1003 | // |
f5930992 | 1004 | // Role assignments |
a9bee37e | 1005 | // |
f5930992 | 1006 | $sql = "SELECT ctx.path, ra.roleid |
f33e1ed4 | 1007 | FROM {role_assignments} ra |
f5930992 | 1008 | JOIN {context} ctx ON ctx.id=ra.contextid |
1009 | WHERE ra.userid = ? AND ctx.contextlevel <= ".CONTEXT_COURSE; | |
f33e1ed4 | 1010 | $params = array($userid); |
1011 | $rs = $DB->get_recordset_sql($sql, $params); | |
f5930992 | 1012 | |
018d4b52 | 1013 | // |
1014 | // raparents collects paths & roles we need to walk up | |
1015 | // the parenthood to build the rdef | |
1016 | // | |
a9bee37e | 1017 | $raparents = array(); |
128f0984 | 1018 | if ($rs) { |
f33e1ed4 | 1019 | foreach ($rs as $ra) { |
03cedd62 | 1020 | // RAs leafs are arrays to support multi |
1021 | // role assignments... | |
1022 | if (!isset($accessdata['ra'][$ra->path])) { | |
1023 | $accessdata['ra'][$ra->path] = array(); | |
1024 | } | |
f5930992 | 1025 | array_push($accessdata['ra'][$ra->path], $ra->roleid); |
1026 | ||
1027 | // Concatenate as string the whole path (all related context) | |
1028 | // for this role. This is damn faster than using array_merge() | |
1029 | // Will unique them later | |
1030 | if (isset($raparents[$ra->roleid])) { | |
1031 | $raparents[$ra->roleid] .= $ra->path; | |
1032 | } else { | |
1033 | $raparents[$ra->roleid] = $ra->path; | |
03cedd62 | 1034 | } |
a9bee37e | 1035 | } |
03cedd62 | 1036 | unset($ra); |
f33e1ed4 | 1037 | $rs->close(); |
a9bee37e | 1038 | } |
a9bee37e | 1039 | |
1040 | // Walk up the tree to grab all the roledefs | |
1041 | // of interest to our user... | |
f5930992 | 1042 | // |
a9bee37e | 1043 | // NOTE: we use a series of IN clauses here - which |
1044 | // might explode on huge sites with very convoluted nesting of | |
1045 | // categories... - extremely unlikely that the number of categories | |
1046 | // and roletypes is so large that we hit the limits of IN() | |
f5930992 | 1047 | $clauses = ''; |
f33e1ed4 | 1048 | $cparams = array(); |
f5930992 | 1049 | foreach ($raparents as $roleid=>$strcontexts) { |
1050 | $contexts = implode(',', array_unique(explode('/', trim($strcontexts, '/')))); | |
a9bee37e | 1051 | if ($contexts ==! '') { |
f5930992 | 1052 | if ($clauses) { |
1053 | $clauses .= ' OR '; | |
1054 | } | |
1055 | $clauses .= "(roleid=? AND contextid IN ($contexts))"; | |
f33e1ed4 | 1056 | $cparams[] = $roleid; |
a9bee37e | 1057 | } |
1058 | } | |
f5930992 | 1059 | |
d4c4ecb8 | 1060 | if ($clauses !== '') { |
1061 | $sql = "SELECT ctx.path, rc.roleid, rc.capability, rc.permission | |
f33e1ed4 | 1062 | FROM {role_capabilities} rc |
1063 | JOIN {context} ctx | |
d4c4ecb8 | 1064 | ON rc.contextid=ctx.id |
f5930992 | 1065 | WHERE $clauses"; |
1066 | ||
d4c4ecb8 | 1067 | unset($clauses); |
f5930992 | 1068 | $rs = $DB->get_recordset_sql($sql, $cparams); |
a9bee37e | 1069 | |
0dbb2191 | 1070 | if ($rs) { |
f33e1ed4 | 1071 | foreach ($rs as $rd) { |
0dbb2191 | 1072 | $k = "{$rd->path}:{$rd->roleid}"; |
1073 | $accessdata['rdef'][$k][$rd->capability] = $rd->permission; | |
1074 | } | |
1075 | unset($rd); | |
f33e1ed4 | 1076 | $rs->close(); |
a9bee37e | 1077 | } |
1078 | } | |
a9bee37e | 1079 | |
1080 | // | |
1081 | // Overrides for the role assignments IN SUBCONTEXTS | |
1082 | // (though we still do _not_ go below the course level. | |
1083 | // | |
1084 | // NOTE that the JOIN w sctx is with 3-way triangulation to | |
1085 | // catch overrides to the applicable role in any subcontext, based | |
1086 | // on the path field of the parent. | |
1087 | // | |
1088 | $sql = "SELECT sctx.path, ra.roleid, | |
1089 | ctx.path AS parentpath, | |
1090 | rco.capability, rco.permission | |
f33e1ed4 | 1091 | FROM {role_assignments} ra |
1092 | JOIN {context} ctx | |
1093 | ON ra.contextid=ctx.id | |
1094 | JOIN {context} sctx | |
5a4e7398 | 1095 | ON (sctx.path LIKE " . $DB->sql_concat('ctx.path',"'/%'"). " ) |
f33e1ed4 | 1096 | JOIN {role_capabilities} rco |
1097 | ON (rco.roleid=ra.roleid AND rco.contextid=sctx.id) | |
1098 | WHERE ra.userid = ? | |
004865e2 | 1099 | AND ctx.contextlevel <= ".CONTEXT_COURSECAT." |
1100 | AND sctx.contextlevel <= ".CONTEXT_COURSE." | |
f33e1ed4 | 1101 | ORDER BY sctx.depth, sctx.path, ra.roleid"; |
1102 | $params = array($userid); | |
1103 | $rs = $DB->get_recordset_sql($sql, $params); | |
0dbb2191 | 1104 | if ($rs) { |
f33e1ed4 | 1105 | foreach ($rs as $rd) { |
0dbb2191 | 1106 | $k = "{$rd->path}:{$rd->roleid}"; |
1107 | $accessdata['rdef'][$k][$rd->capability] = $rd->permission; | |
1108 | } | |
1109 | unset($rd); | |
f33e1ed4 | 1110 | $rs->close(); |
a9bee37e | 1111 | } |
bb2c22bd | 1112 | return $accessdata; |
a9bee37e | 1113 | } |
1114 | ||
74ac5b66 | 1115 | /** |
46808d7c | 1116 | * Add to the access ctrl array the data needed by a user for a given context |
74ac5b66 | 1117 | * |
cc3edaa4 | 1118 | * @global object |
1119 | * @global object | |
46808d7c | 1120 | * @param integer $userid the id of the user |
1121 | * @param object $context needs path! | |
1122 | * @param array $accessdata accessdata array | |
74ac5b66 | 1123 | */ |
a2cf7f1b | 1124 | function load_subcontext($userid, $context, &$accessdata) { |
74ac5b66 | 1125 | |
f33e1ed4 | 1126 | global $CFG, $DB; |
018d4b52 | 1127 | |
1128 | /* Get the additional RAs and relevant rolecaps | |
74ac5b66 | 1129 | * - role assignments - with role_caps |
1130 | * - relevant role caps | |
1131 | * - above this user's RAs | |
1132 | * - below this user's RAs - limited to course level | |
1133 | */ | |
1134 | ||
74ac5b66 | 1135 | $base = "/" . SYSCONTEXTID; |
1136 | ||
53fb75dc | 1137 | // |
1138 | // Replace $context with the target context we will | |
1139 | // load. Normally, this will be a course context, but | |
1140 | // may be a different top-level context. | |
1141 | // | |
1142 | // We have 3 cases | |
74ac5b66 | 1143 | // |
1144 | // - Course | |
1145 | // - BLOCK/PERSON/USER/COURSE(sitecourse) hanging from SYSTEM | |
1146 | // - BLOCK/MODULE/GROUP hanging from a course | |
1147 | // | |
1148 | // For course contexts, we _already_ have the RAs | |
1149 | // but the cost of re-fetching is minimal so we don't care. | |
74ac5b66 | 1150 | // |
5a4e7398 | 1151 | if ($context->contextlevel !== CONTEXT_COURSE |
53fb75dc | 1152 | && $context->path !== "$base/{$context->id}") { |
1153 | // Case BLOCK/MODULE/GROUP hanging from a course | |
74ac5b66 | 1154 | // Assumption: the course _must_ be our parent |
1155 | // If we ever see stuff nested further this needs to | |
1156 | // change to do 1 query over the exploded path to | |
1157 | // find out which one is the course | |
c2f10673 | 1158 | $courses = explode('/',get_course_from_path($context->path)); |
1159 | $targetid = array_pop($courses); | |
53fb75dc | 1160 | $context = get_context_instance_by_id($targetid); |
5a4e7398 | 1161 | |
74ac5b66 | 1162 | } |
1163 | ||
1164 | // | |
53fb75dc | 1165 | // Role assignments in the context and below |
74ac5b66 | 1166 | // |
53fb75dc | 1167 | $sql = "SELECT ctx.path, ra.roleid |
f33e1ed4 | 1168 | FROM {role_assignments} ra |
1169 | JOIN {context} ctx | |
1170 | ON ra.contextid=ctx.id | |
1171 | WHERE ra.userid = ? | |
1172 | AND (ctx.path = ? OR ctx.path LIKE ?) | |
082e777a | 1173 | ORDER BY ctx.depth, ctx.path, ra.roleid"; |
f33e1ed4 | 1174 | $params = array($userid, $context->path, $context->path."/%"); |
1175 | $rs = $DB->get_recordset_sql($sql, $params); | |
74ac5b66 | 1176 | |
5a4e7398 | 1177 | // |
082e777a | 1178 | // Read in the RAs, preventing duplicates |
018d4b52 | 1179 | // |
f33e1ed4 | 1180 | if ($rs) { |
1181 | $localroles = array(); | |
082e777a | 1182 | $lastseen = ''; |
f33e1ed4 | 1183 | foreach ($rs as $ra) { |
1184 | if (!isset($accessdata['ra'][$ra->path])) { | |
1185 | $accessdata['ra'][$ra->path] = array(); | |
1186 | } | |
082e777a | 1187 | // only add if is not a repeat caused |
1188 | // by capability join... | |
1189 | // (this check is cheaper than in_array()) | |
1190 | if ($lastseen !== $ra->path.':'.$ra->roleid) { | |
1191 | $lastseen = $ra->path.':'.$ra->roleid; | |
1192 | array_push($accessdata['ra'][$ra->path], $ra->roleid); | |
1193 | array_push($localroles, $ra->roleid); | |
1194 | } | |
74ac5b66 | 1195 | } |
f33e1ed4 | 1196 | $rs->close(); |
74ac5b66 | 1197 | } |
74ac5b66 | 1198 | |
018d4b52 | 1199 | // |
53fb75dc | 1200 | // Walk up and down the tree to grab all the roledefs |
74ac5b66 | 1201 | // of interest to our user... |
018d4b52 | 1202 | // |
53fb75dc | 1203 | // NOTES |
1204 | // - we use IN() but the number of roles is very limited. | |
1205 | // | |
bb2c22bd | 1206 | $courseroles = aggregate_roles_from_accessdata($context, $accessdata); |
53fb75dc | 1207 | |
1208 | // Do we have any interesting "local" roles? | |
1209 | $localroles = array_diff($localroles,$courseroles); // only "new" local roles | |
1210 | $wherelocalroles=''; | |
1211 | if (count($localroles)) { | |
1212 | // Role defs for local roles in 'higher' contexts... | |
1213 | $contexts = substr($context->path, 1); // kill leading slash | |
1214 | $contexts = str_replace('/', ',', $contexts); | |
1215 | $localroleids = implode(',',$localroles); | |
5a4e7398 | 1216 | $wherelocalroles="OR (rc.roleid IN ({$localroleids}) |
53fb75dc | 1217 | AND ctx.id IN ($contexts))" ; |
74ac5b66 | 1218 | } |
1219 | ||
53fb75dc | 1220 | // We will want overrides for all of them |
7e17f43b | 1221 | $whereroles = ''; |
1222 | if ($roleids = implode(',',array_merge($courseroles,$localroles))) { | |
1223 | $whereroles = "rc.roleid IN ($roleids) AND"; | |
1224 | } | |
53fb75dc | 1225 | $sql = "SELECT ctx.path, rc.roleid, rc.capability, rc.permission |
f33e1ed4 | 1226 | FROM {role_capabilities} rc |
1227 | JOIN {context} ctx | |
1228 | ON rc.contextid=ctx.id | |
1229 | WHERE ($whereroles | |
1230 | (ctx.id=? OR ctx.path LIKE ?)) | |
1231 | $wherelocalroles | |
1232 | ORDER BY ctx.depth ASC, ctx.path DESC, rc.roleid ASC "; | |
1233 | $params = array($context->id, $context->path."/%"); | |
53fb75dc | 1234 | |
a2cf7f1b | 1235 | $newrdefs = array(); |
f33e1ed4 | 1236 | if ($rs = $DB->get_recordset_sql($sql, $params)) { |
1237 | foreach ($rs as $rd) { | |
03cedd62 | 1238 | $k = "{$rd->path}:{$rd->roleid}"; |
a2cf7f1b | 1239 | if (!array_key_exists($k, $newrdefs)) { |
1240 | $newrdefs[$k] = array(); | |
1241 | } | |
1242 | $newrdefs[$k][$rd->capability] = $rd->permission; | |
74ac5b66 | 1243 | } |
f33e1ed4 | 1244 | $rs->close(); |
7e17f43b | 1245 | } else { |
1246 | debugging('Bad SQL encountered!'); | |
74ac5b66 | 1247 | } |
74ac5b66 | 1248 | |
a2cf7f1b | 1249 | compact_rdefs($newrdefs); |
1250 | foreach ($newrdefs as $key=>$value) { | |
1251 | $accessdata['rdef'][$key] =& $newrdefs[$key]; | |
1252 | } | |
74ac5b66 | 1253 | |
6100dad0 | 1254 | // error_log("loaded {$context->path}"); |
bb2c22bd | 1255 | $accessdata['loaded'][] = $context->path; |
74ac5b66 | 1256 | } |
2f1a4248 | 1257 | |
eef879ec | 1258 | /** |
46808d7c | 1259 | * Add to the access ctrl array the data needed by a role for a given context. |
6f1bce30 | 1260 | * |
1261 | * The data is added in the rdef key. | |
1262 | * | |
1263 | * This role-centric function is useful for role_switching | |
1264 | * and to get an overview of what a role gets under a | |
1265 | * given context and below... | |
1266 | * | |
cc3edaa4 | 1267 | * @global object |
1268 | * @global object | |
46808d7c | 1269 | * @param integer $roleid the id of the user |
1270 | * @param object $context needs path! | |
4f0c2d00 | 1271 | * @param array $accessdata accessdata array NULL by default |
46808d7c | 1272 | * @return array |
6f1bce30 | 1273 | */ |
bb2c22bd | 1274 | function get_role_access_bycontext($roleid, $context, $accessdata=NULL) { |
6f1bce30 | 1275 | |
f33e1ed4 | 1276 | global $CFG, $DB; |
6f1bce30 | 1277 | |
1278 | /* Get the relevant rolecaps into rdef | |
1279 | * - relevant role caps | |
1280 | * - at ctx and above | |
1281 | * - below this ctx | |
1282 | */ | |
1283 | ||
bb2c22bd | 1284 | if (is_null($accessdata)) { |
1285 | $accessdata = array(); // named list | |
1286 | $accessdata['ra'] = array(); | |
1287 | $accessdata['rdef'] = array(); | |
1288 | $accessdata['loaded'] = array(); | |
6f1bce30 | 1289 | } |
5a4e7398 | 1290 | |
6f1bce30 | 1291 | $contexts = substr($context->path, 1); // kill leading slash |
1292 | $contexts = str_replace('/', ',', $contexts); | |
1293 | ||
1294 | // | |
1295 | // Walk up and down the tree to grab all the roledefs | |
1296 | // of interest to our role... | |
1297 | // | |
1298 | // NOTE: we use an IN clauses here - which | |
1299 | // might explode on huge sites with very convoluted nesting of | |
1300 | // categories... - extremely unlikely that the number of nested | |
1301 | // categories is so large that we hit the limits of IN() | |
1302 | // | |
1303 | $sql = "SELECT ctx.path, rc.capability, rc.permission | |
f33e1ed4 | 1304 | FROM {role_capabilities} rc |
1305 | JOIN {context} ctx | |
1306 | ON rc.contextid=ctx.id | |
1307 | WHERE rc.roleid=? AND | |
5a4e7398 | 1308 | ( ctx.id IN ($contexts) OR |
f33e1ed4 | 1309 | ctx.path LIKE ? ) |
1310 | ORDER BY ctx.depth ASC, ctx.path DESC, rc.roleid ASC "; | |
1311 | $params = array($roleid, $context->path."/%"); | |
1312 | ||
1313 | if ($rs = $DB->get_recordset_sql($sql, $params)) { | |
1314 | foreach ($rs as $rd) { | |
1315 | $k = "{$rd->path}:{$roleid}"; | |
1316 | $accessdata['rdef'][$k][$rd->capability] = $rd->permission; | |
1317 | } | |
1318 | $rs->close(); | |
6f1bce30 | 1319 | } |
6f1bce30 | 1320 | |
bb2c22bd | 1321 | return $accessdata; |
6f1bce30 | 1322 | } |
1323 | ||
a2cf7f1b | 1324 | /** |
46808d7c | 1325 | * Load accessdata for a user into the $ACCESSLIB_PRIVATE->accessdatabyuser global |
204a369c | 1326 | * |
1327 | * Used by has_capability() - but feel free | |
5a4e7398 | 1328 | * to call it if you are about to run a BIG |
204a369c | 1329 | * cron run across a bazillion users. |
1330 | * | |
cc3edaa4 | 1331 | * @global object |
1332 | * @global object | |
46808d7c | 1333 | * @param int $userid |
1334 | * @return array returns ACCESSLIB_PRIVATE->accessdatabyuser[userid] | |
5a4e7398 | 1335 | */ |
204a369c | 1336 | function load_user_accessdata($userid) { |
d867e696 | 1337 | global $CFG, $ACCESSLIB_PRIVATE; |
6f1bce30 | 1338 | |
7293b3c6 | 1339 | $base = '/'.SYSCONTEXTID; |
204a369c | 1340 | |
bb2c22bd | 1341 | $accessdata = get_user_access_sitewide($userid); |
5a4e7398 | 1342 | $frontpagecontext = get_context_instance(CONTEXT_COURSE, SITEID); |
3ac81bd1 | 1343 | // |
7293b3c6 | 1344 | // provide "default role" & set 'dr' |
3ac81bd1 | 1345 | // |
7d0c81b3 | 1346 | if (!empty($CFG->defaultuserroleid)) { |
1347 | $accessdata = get_role_access($CFG->defaultuserroleid, $accessdata); | |
1348 | if (!isset($accessdata['ra'][$base])) { | |
1349 | $accessdata['ra'][$base] = array($CFG->defaultuserroleid); | |
1350 | } else { | |
1351 | array_push($accessdata['ra'][$base], $CFG->defaultuserroleid); | |
1352 | } | |
1353 | $accessdata['dr'] = $CFG->defaultuserroleid; | |
204a369c | 1354 | } |
1355 | ||
4e1fe7d1 | 1356 | // |
1357 | // provide "default frontpage role" | |
1358 | // | |
3d811bc1 | 1359 | if (!empty($CFG->defaultfrontpageroleid)) { |
4e1fe7d1 | 1360 | $base = '/'. SYSCONTEXTID .'/'. $frontpagecontext->id; |
3d811bc1 | 1361 | $accessdata = get_default_frontpage_role_access($CFG->defaultfrontpageroleid, $accessdata); |
4e1fe7d1 | 1362 | if (!isset($accessdata['ra'][$base])) { |
3d811bc1 | 1363 | $accessdata['ra'][$base] = array($CFG->defaultfrontpageroleid); |
4e1fe7d1 | 1364 | } else { |
3d811bc1 | 1365 | array_push($accessdata['ra'][$base], $CFG->defaultfrontpageroleid); |
4e1fe7d1 | 1366 | } |
1367 | } | |
128f0984 | 1368 | // for dirty timestamps in cron |
1369 | $accessdata['time'] = time(); | |
1370 | ||
d867e696 | 1371 | $ACCESSLIB_PRIVATE->accessdatabyuser[$userid] = $accessdata; |
1372 | compact_rdefs($ACCESSLIB_PRIVATE->accessdatabyuser[$userid]['rdef']); | |
a2cf7f1b | 1373 | |
d867e696 | 1374 | return $ACCESSLIB_PRIVATE->accessdatabyuser[$userid]; |
204a369c | 1375 | } |
ef989bd9 | 1376 | |
a2cf7f1b | 1377 | /** |
4f65e0fb | 1378 | * Use shared copy of role definitions stored in ACCESSLIB_PRIVATE->roledefinitions; |
cc3edaa4 | 1379 | * |
1380 | * @global object | |
a2cf7f1b | 1381 | * @param array $rdefs array of role definitions in contexts |
1382 | */ | |
1383 | function compact_rdefs(&$rdefs) { | |
d867e696 | 1384 | global $ACCESSLIB_PRIVATE; |
a2cf7f1b | 1385 | |
1386 | /* | |
1387 | * This is a basic sharing only, we could also | |
1388 | * use md5 sums of values. The main purpose is to | |
d867e696 | 1389 | * reduce mem in cron jobs - many users in $ACCESSLIB_PRIVATE->accessdatabyuser array. |
a2cf7f1b | 1390 | */ |
1391 | ||
1392 | foreach ($rdefs as $key => $value) { | |
d867e696 | 1393 | if (!array_key_exists($key, $ACCESSLIB_PRIVATE->roledefinitions)) { |
1394 | $ACCESSLIB_PRIVATE->roledefinitions[$key] = $rdefs[$key]; | |
a2cf7f1b | 1395 | } |
d867e696 | 1396 | $rdefs[$key] =& $ACCESSLIB_PRIVATE->roledefinitions[$key]; |
a2cf7f1b | 1397 | } |
1398 | } | |
1399 | ||
6f1bce30 | 1400 | /** |
46808d7c | 1401 | * A convenience function to completely load all the capabilities |
1402 | * for the current user. This is what gets called from complete_user_login() | |
1403 | * for example. Call it only _after_ you've setup $USER and called | |
1404 | * check_enrolment_plugins(); | |
1405 | * @see check_enrolment_plugins() | |
117bd748 | 1406 | * |
cc3edaa4 | 1407 | * @global object |
1408 | * @global object | |
1409 | * @global object | |
2f1a4248 | 1410 | */ |
1411 | function load_all_capabilities() { | |
d867e696 | 1412 | global $USER, $CFG, $ACCESSLIB_PRIVATE; |
bbbf2d40 | 1413 | |
18818abf | 1414 | // roles not installed yet - we are in the middle of installation |
31a99877 | 1415 | if (during_initial_install()) { |
1045a007 | 1416 | return; |
1417 | } | |
1418 | ||
e0376a62 | 1419 | $base = '/'.SYSCONTEXTID; |
1420 | ||
eef879ec | 1421 | if (isguestuser()) { |
e0376a62 | 1422 | $guest = get_guest_role(); |
1423 | ||
1424 | // Load the rdefs | |
1425 | $USER->access = get_role_access($guest->id); | |
1426 | // Put the ghost enrolment in place... | |
33b6014f | 1427 | $USER->access['ra'][$base] = array($guest->id); |
eef879ec | 1428 | |
7293b3c6 | 1429 | |
eef879ec | 1430 | } else if (isloggedin()) { |
eef879ec | 1431 | |
bb2c22bd | 1432 | $accessdata = get_user_access_sitewide($USER->id); |
3887fe4a | 1433 | |
7293b3c6 | 1434 | // |
1435 | // provide "default role" & set 'dr' | |
1436 | // | |
7d0c81b3 | 1437 | if (!empty($CFG->defaultuserroleid)) { |
1438 | $accessdata = get_role_access($CFG->defaultuserroleid, $accessdata); | |
1439 | if (!isset($accessdata['ra'][$base])) { | |
1440 | $accessdata['ra'][$base] = array($CFG->defaultuserroleid); | |
1441 | } else { | |
1442 | array_push($accessdata['ra'][$base], $CFG->defaultuserroleid); | |
1443 | } | |
1444 | $accessdata['dr'] = $CFG->defaultuserroleid; | |
c0aa9f09 | 1445 | } |
7293b3c6 | 1446 | |
4e1fe7d1 | 1447 | $frontpagecontext = get_context_instance(CONTEXT_COURSE, SITEID); |
de5e137a | 1448 | |
4e1fe7d1 | 1449 | // |
1450 | // provide "default frontpage role" | |
1451 | // | |
3d811bc1 | 1452 | if (!empty($CFG->defaultfrontpageroleid)) { |
4e1fe7d1 | 1453 | $base = '/'. SYSCONTEXTID .'/'. $frontpagecontext->id; |
1454 | $accessdata = get_default_frontpage_role_access($CFG->defaultfrontpageroleid, $accessdata); | |
1455 | if (!isset($accessdata['ra'][$base])) { | |
1456 | $accessdata['ra'][$base] = array($CFG->defaultfrontpageroleid); | |
1457 | } else { | |
1458 | array_push($accessdata['ra'][$base], $CFG->defaultfrontpageroleid); | |
1459 | } | |
5a4e7398 | 1460 | } |
4e1fe7d1 | 1461 | $USER->access = $accessdata; |
5a4e7398 | 1462 | |
7d0c81b3 | 1463 | } else if (!empty($CFG->notloggedinroleid)) { |
1464 | $USER->access = get_role_access($CFG->notloggedinroleid); | |
1465 | $USER->access['ra'][$base] = array($CFG->notloggedinroleid); | |
2f1a4248 | 1466 | } |
55e68c29 | 1467 | |
128f0984 | 1468 | // Timestamp to read dirty context timestamps later |
148eb2a7 | 1469 | $USER->access['time'] = time(); |
d867e696 | 1470 | $ACCESSLIB_PRIVATE->dirtycontexts = array(); |
55e68c29 | 1471 | |
1472 | // Clear to force a refresh | |
1473 | unset($USER->mycourses); | |
bbbf2d40 | 1474 | } |
1475 | ||
ef989bd9 | 1476 | /** |
5a4e7398 | 1477 | * A convenience function to completely reload all the capabilities |
ef989bd9 | 1478 | * for the current user when roles have been updated in a relevant |
5a4e7398 | 1479 | * context -- but PRESERVING switchroles and loginas. |
ef989bd9 | 1480 | * |
1481 | * That is - completely transparent to the user. | |
5a4e7398 | 1482 | * |
ef989bd9 | 1483 | * Note: rewrites $USER->access completely. |
1484 | * | |
cc3edaa4 | 1485 | * @global object |
1486 | * @global object | |
ef989bd9 | 1487 | */ |
1488 | function reload_all_capabilities() { | |
f33e1ed4 | 1489 | global $USER, $DB; |
ef989bd9 | 1490 | |
b738808b | 1491 | // error_log("reloading"); |
ef989bd9 | 1492 | // copy switchroles |
1493 | $sw = array(); | |
1494 | if (isset($USER->access['rsw'])) { | |
1495 | $sw = $USER->access['rsw']; | |
b738808b | 1496 | // error_log(print_r($sw,1)); |
ef989bd9 | 1497 | } |
1498 | ||
1499 | unset($USER->access); | |
54f9d9ae | 1500 | unset($USER->mycourses); |
5a4e7398 | 1501 | |
ef989bd9 | 1502 | load_all_capabilities(); |
1503 | ||
1504 | foreach ($sw as $path => $roleid) { | |
f33e1ed4 | 1505 | $context = $DB->get_record('context', array('path'=>$path)); |
ef989bd9 | 1506 | role_switch($roleid, $context); |
1507 | } | |
1508 | ||
1509 | } | |
2f1a4248 | 1510 | |
f33e1ed4 | 1511 | /** |
343effbe | 1512 | * Adds a temp role to an accessdata array. |
1513 | * | |
1514 | * Useful for the "temporary guest" access | |
1515 | * we grant to logged-in users. | |
1516 | * | |
1517 | * Note - assumes a course context! | |
1518 | * | |
46808d7c | 1519 | * @param object $content |
1520 | * @param int $roleid | |
1521 | * @param array $accessdata | |
1522 | * @return array Returns access data | |
343effbe | 1523 | */ |
df997f84 | 1524 | function load_temp_role($context, $roleid, array $accessdata) { |
f33e1ed4 | 1525 | global $CFG, $DB; |
343effbe | 1526 | |
1527 | // | |
1528 | // Load rdefs for the role in - | |
1529 | // - this context | |
1530 | // - all the parents | |
1531 | // - and below - IOWs overrides... | |
1532 | // | |
5a4e7398 | 1533 | |
343effbe | 1534 | // turn the path into a list of context ids |
1535 | $contexts = substr($context->path, 1); // kill leading slash | |
1536 | $contexts = str_replace('/', ',', $contexts); | |
1537 | ||
f33e1ed4 | 1538 | $sql = "SELECT ctx.path, rc.capability, rc.permission |
1539 | FROM {context} ctx | |
1540 | JOIN {role_capabilities} rc | |
1541 | ON rc.contextid=ctx.id | |
1542 | WHERE (ctx.id IN ($contexts) | |
1543 | OR ctx.path LIKE ?) | |
1544 | AND rc.roleid = ? | |
1545 | ORDER BY ctx.depth, ctx.path"; | |
5a4e7398 | 1546 | $params = array($context->path."/%", $roleid); |
1547 | if ($rs = $DB->get_recordset_sql($sql, $params)) { | |
f33e1ed4 | 1548 | foreach ($rs as $rd) { |
1549 | $k = "{$rd->path}:{$roleid}"; | |
1550 | $accessdata['rdef'][$k][$rd->capability] = $rd->permission; | |
1551 | } | |
3bea11c8 | 1552 | $rs->close(); |
f33e1ed4 | 1553 | } |
343effbe | 1554 | |
1555 | // | |
1556 | // Say we loaded everything for the course context | |
1557 | // - which we just did - if the user gets a proper | |
1558 | // RA in this session, this data will need to be reloaded, | |
1559 | // but that is handled by the complete accessdata reload | |
1560 | // | |
bb2c22bd | 1561 | array_push($accessdata['loaded'], $context->path); |
343effbe | 1562 | |
1563 | // | |
1564 | // Add the ghost RA | |
1565 | // | |
bb2c22bd | 1566 | if (isset($accessdata['ra'][$context->path])) { |
1567 | array_push($accessdata['ra'][$context->path], $roleid); | |
343effbe | 1568 | } else { |
bb2c22bd | 1569 | $accessdata['ra'][$context->path] = array($roleid); |
343effbe | 1570 | } |
1571 | ||
bb2c22bd | 1572 | return $accessdata; |
343effbe | 1573 | } |
1574 | ||
efe12f6c | 1575 | /** |
df997f84 PS |
1576 | * Removes any extra guest roels from accessdata |
1577 | * @param object $context | |
1578 | * @param array $accessdata | |
1579 | * @return array access data | |
64026e8c | 1580 | */ |
df997f84 PS |
1581 | function remove_temp_roles($context, array $accessdata) { |
1582 | global $DB, $USER; | |
1583 | $sql = "SELECT DISTINCT ra.roleid AS id | |
1584 | FROM {role_assignments} ra | |
1585 | WHERE ra.contextid = :contextid AND ra.userid = :userid"; | |
1586 | $ras = $DB->get_records_sql($sql, array('contextid'=>$context->id, 'userid'=>$USER->id)); | |
e4ec4e41 | 1587 | |
df997f84 PS |
1588 | $accessdata['ra'][$context->path] = array_keys($ras); |
1589 | return $accessdata; | |
64026e8c | 1590 | } |
1591 | ||
3562486b | 1592 | /** |
4f0c2d00 | 1593 | * Returns array of all role archetypes. |
cc3edaa4 | 1594 | * |
46808d7c | 1595 | * @return array |
3562486b | 1596 | */ |
4f0c2d00 | 1597 | function get_role_archetypes() { |
3562486b | 1598 | return array( |
4f0c2d00 PS |
1599 | 'manager' => 'manager', |
1600 | 'coursecreator' => 'coursecreator', | |
1601 | 'editingteacher' => 'editingteacher', | |
1602 | 'teacher' => 'teacher', | |
1603 | 'student' => 'student', | |
1604 | 'guest' => 'guest', | |
1605 | 'user' => 'user', | |
1606 | 'frontpage' => 'frontpage' | |
3562486b | 1607 | ); |
1608 | } | |
1609 | ||
bbbf2d40 | 1610 | /** |
4f65e0fb | 1611 | * Assign the defaults found in this capability definition to roles that have |
bbbf2d40 | 1612 | * the corresponding legacy capabilities assigned to them. |
cc3edaa4 | 1613 | * |
46808d7c | 1614 | * @param string $capability |
1615 | * @param array $legacyperms an array in the format (example): | |
bbbf2d40 | 1616 | * 'guest' => CAP_PREVENT, |
1617 | * 'student' => CAP_ALLOW, | |
1618 | * 'teacher' => CAP_ALLOW, | |
1619 | * 'editingteacher' => CAP_ALLOW, | |
1620 | * 'coursecreator' => CAP_ALLOW, | |
4f0c2d00 | 1621 | * 'manager' => CAP_ALLOW |
46808d7c | 1622 | * @return boolean success or failure. |
bbbf2d40 | 1623 | */ |
1624 | function assign_legacy_capabilities($capability, $legacyperms) { | |
eef868d1 | 1625 | |
4f0c2d00 | 1626 | $archetypes = get_role_archetypes(); |
3562486b | 1627 | |
bbbf2d40 | 1628 | foreach ($legacyperms as $type => $perm) { |
eef868d1 | 1629 | |
21c9bace | 1630 | $systemcontext = get_context_instance(CONTEXT_SYSTEM); |
4f0c2d00 PS |
1631 | if ($type === 'admin') { |
1632 | debugging('Legacy type admin in access.php was renamed to manager, please update the code.'); | |
1633 | $type = 'manager'; | |
1634 | } | |
eef868d1 | 1635 | |
4f0c2d00 | 1636 | if (!array_key_exists($type, $archetypes)) { |
e49ef64a | 1637 | print_error('invalidlegacy', '', '', $type); |
3562486b | 1638 | } |
eef868d1 | 1639 | |
4f0c2d00 | 1640 | if ($roles = get_archetype_roles($type)) { |
2e85fffe | 1641 | foreach ($roles as $role) { |
1642 | // Assign a site level capability. | |
1643 | if (!assign_capability($capability, $perm, $role->id, $systemcontext->id)) { | |
1644 | return false; | |
1645 | } | |
bbbf2d40 | 1646 | } |
1647 | } | |
1648 | } | |
1649 | return true; | |
1650 | } | |
1651 | ||
faf75fe7 | 1652 | /** |
ed149942 PS |
1653 | * @param object $capability a capability - a row from the capabilities table. |
1654 | * @return boolean whether this capability is safe - that is, whether people with the | |
faf75fe7 | 1655 | * safeoverrides capability should be allowed to change it. |
1656 | */ | |
1657 | function is_safe_capability($capability) { | |
4659454a | 1658 | return !((RISK_DATALOSS | RISK_MANAGETRUST | RISK_CONFIG | RISK_XSS | RISK_PERSONAL) & $capability->riskbitmask); |
faf75fe7 | 1659 | } |
cee0901c | 1660 | |
1661 | /********************************** | |
bbbf2d40 | 1662 | * Context Manipulation functions * |
1663 | **********************************/ | |
1664 | ||
bbbf2d40 | 1665 | /** |
a0760047 | 1666 | * Context creation - internal implementation. |
46808d7c | 1667 | * |
9991d157 | 1668 | * Create a new context record for use by all roles-related stuff |
4881f2d3 | 1669 | * assumes that the caller has done the homework. |
1670 | * | |
a0760047 PS |
1671 | * DO NOT CALL THIS DIRECTLY, instead use {@link get_context_instance}! |
1672 | * | |
46808d7c | 1673 | * @param int $contextlevel |
1674 | * @param int $instanceid | |
4f0c2d00 | 1675 | * @param int $strictness |
e40413be | 1676 | * @return object newly created context |
bbbf2d40 | 1677 | */ |
4f0c2d00 | 1678 | function create_context($contextlevel, $instanceid, $strictness=IGNORE_MISSING) { |
e40413be | 1679 | |
5a4e7398 | 1680 | global $CFG, $DB; |
e40413be | 1681 | |
4881f2d3 | 1682 | if ($contextlevel == CONTEXT_SYSTEM) { |
1683 | return create_system_context(); | |
1684 | } | |
c421ad4b | 1685 | |
4881f2d3 | 1686 | $context = new object(); |
1687 | $context->contextlevel = $contextlevel; | |
1688 | $context->instanceid = $instanceid; | |
e40413be | 1689 | |
1690 | // Define $context->path based on the parent | |
1691 | // context. In other words... Who is your daddy? | |
ca92b391 | 1692 | $basepath = '/' . SYSCONTEXTID; |
1693 | $basedepth = 1; | |
e40413be | 1694 | |
7d0c81b3 | 1695 | $result = true; |
4f0c2d00 | 1696 | $error_message = NULL; |
7d0c81b3 | 1697 | |
e40413be | 1698 | switch ($contextlevel) { |
1699 | case CONTEXT_COURSECAT: | |
5a4e7398 | 1700 | $sql = "SELECT ctx.path, ctx.depth |
1701 | FROM {context} ctx | |
1702 | JOIN {course_categories} cc | |
1703 | ON (cc.parent=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSECAT.") | |
1704 | WHERE cc.id=?"; | |
1705 | $params = array($instanceid); | |
1706 | if ($p = $DB->get_record_sql($sql, $params)) { | |
ca92b391 | 1707 | $basepath = $p->path; |
1708 | $basedepth = $p->depth; | |
4f0c2d00 | 1709 | } else if ($category = $DB->get_record('course_categories', array('id'=>$instanceid), '*', $strictness)) { |
7d0c81b3 | 1710 | if (empty($category->parent)) { |
1711 | // ok - this is a top category | |
1712 | } else if ($parent = get_context_instance(CONTEXT_COURSECAT, $category->parent)) { | |
1713 | $basepath = $parent->path; | |
1714 | $basedepth = $parent->depth; | |
1715 | } else { | |
1716 | // wrong parent category - no big deal, this can be fixed later | |
4f0c2d00 | 1717 | $basepath = NULL; |
7d0c81b3 | 1718 | $basedepth = 0; |
1719 | } | |
1720 | } else { | |
1721 | // incorrect category id | |
f689028c | 1722 | $error_message = "incorrect course category id ($instanceid)"; |
7d0c81b3 | 1723 | $result = false; |
e40413be | 1724 | } |
1725 | break; | |
1726 | ||
1727 | case CONTEXT_COURSE: | |
ca92b391 | 1728 | $sql = "SELECT ctx.path, ctx.depth |
5a4e7398 | 1729 | FROM {context} ctx |
1730 | JOIN {course} c | |
1731 | ON (c.category=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSECAT.") | |
1732 | WHERE c.id=? AND c.id !=" . SITEID; | |
1733 | $params = array($instanceid); | |
1734 | if ($p = $DB->get_record_sql($sql, $params)) { | |
ca92b391 | 1735 | $basepath = $p->path; |
1736 | $basedepth = $p->depth; | |
4f0c2d00 | 1737 | } else if ($course = $DB->get_record('course', array('id'=>$instanceid), '*', $strictness)) { |
7d0c81b3 | 1738 | if ($course->id == SITEID) { |
1739 | //ok - no parent category | |
1740 | } else if ($parent = get_context_instance(CONTEXT_COURSECAT, $course->category)) { | |
1741 | $basepath = $parent->path; | |
1742 | $basedepth = $parent->depth; | |
1743 | } else { | |
1744 | // wrong parent category of course - no big deal, this can be fixed later | |
4f0c2d00 | 1745 | $basepath = NULL; |
7d0c81b3 | 1746 | $basedepth = 0; |
1747 | } | |
1748 | } else if ($instanceid == SITEID) { | |
1749 | // no errors for missing site course during installation | |
1750 | return false; | |
1751 | } else { | |
1752 | // incorrect course id | |
f689028c | 1753 | $error_message = "incorrect course id ($instanceid)"; |
7d0c81b3 | 1754 | $result = false; |
e40413be | 1755 | } |
1756 | break; | |
1757 | ||
1758 | case CONTEXT_MODULE: | |
ca92b391 | 1759 | $sql = "SELECT ctx.path, ctx.depth |
5a4e7398 | 1760 | FROM {context} ctx |
1761 | JOIN {course_modules} cm | |
1762 | ON (cm.course=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE.") | |
1763 | WHERE cm.id=?"; | |
1764 | $params = array($instanceid); | |
1765 | if ($p = $DB->get_record_sql($sql, $params)) { | |
7d0c81b3 | 1766 | $basepath = $p->path; |
1767 | $basedepth = $p->depth; | |
4f0c2d00 PS |
1768 | } else if ($cm = $DB->get_record('course_modules', array('id'=>$instanceid), '*', $strictness)) { |
1769 | if ($parent = get_context_instance(CONTEXT_COURSE, $cm->course, $strictness)) { | |
7d0c81b3 | 1770 | $basepath = $parent->path; |
1771 | $basedepth = $parent->depth; | |
1772 | } else { | |
1773 | // course does not exist - modules can not exist without a course | |
f689028c | 1774 | $error_message = "course does not exist ($cm->course) - modules can not exist without a course"; |
7d0c81b3 | 1775 | $result = false; |
1776 | } | |
1777 | } else { | |
1778 | // cm does not exist | |
837e6a44 | 1779 | $error_message = "cm with id $instanceid does not exist"; |
7d0c81b3 | 1780 | $result = false; |
1781 | } | |
e40413be | 1782 | break; |
1783 | ||
1784 | case CONTEXT_BLOCK: | |
ca92b391 | 1785 | $sql = "SELECT ctx.path, ctx.depth |
f474a4e5 | 1786 | FROM {context} ctx |
13a0d3d3 | 1787 | JOIN {block_instances} bi ON (bi.parentcontextid=ctx.id) |
e92c286c | 1788 | WHERE bi.id = ?"; |
f474a4e5 | 1789 | $params = array($instanceid, CONTEXT_COURSE); |
4f0c2d00 | 1790 | if ($p = $DB->get_record_sql($sql, $params, '*', $strictness)) { |
ca92b391 | 1791 | $basepath = $p->path; |
1792 | $basedepth = $p->depth; | |
7d0c81b3 | 1793 | } else { |
1794 | // block does not exist | |
f474a4e5 | 1795 | $error_message = 'block or parent context does not exist'; |
7d0c81b3 | 1796 | $result = false; |
ca92b391 | 1797 | } |
e40413be | 1798 | break; |
1799 | case CONTEXT_USER: | |
1800 | // default to basepath | |
1801 | break; | |
e40413be | 1802 | } |
1803 | ||
7d0c81b3 | 1804 | // if grandparents unknown, maybe rebuild_context_path() will solve it later |
1805 | if ($basedepth != 0) { | |
1806 | $context->depth = $basedepth+1; | |
1807 | } | |
1808 | ||
4f0c2d00 | 1809 | if (!$result) { |
4881f2d3 | 1810 | debugging('Error: could not insert new context level "'. |
1811 | s($contextlevel).'", instance "'. | |
f689028c | 1812 | s($instanceid).'". ' . $error_message); |
1813 | ||
7d0c81b3 | 1814 | return false; |
bbbf2d40 | 1815 | } |
4f0c2d00 PS |
1816 | |
1817 | $id = $DB->insert_record('context', $context); | |
1818 | // can't set the full path till we know the id! | |
1819 | if ($basedepth != 0 and !empty($basepath)) { | |
1820 | $DB->set_field('context', 'path', $basepath.'/'. $id, array('id'=>$id)); | |
1821 | } | |
1822 | return get_context_instance_by_id($id); | |
bbbf2d40 | 1823 | } |
1824 | ||
efe12f6c | 1825 | /** |
4f0c2d00 | 1826 | * Returns system context or NULL if can not be created yet. |
46808d7c | 1827 | * |
1828 | * @todo can not use get_record() because we do not know if query failed :-( | |
1829 | * switch to get_record() later | |
1830 | * | |
cc3edaa4 | 1831 | * @global object |
1832 | * @global object | |
0ecff22d | 1833 | * @param bool $cache use caching |
4f0c2d00 | 1834 | * @return mixed system context or NULL |
8ba412da | 1835 | */ |
7d0c81b3 | 1836 | function get_system_context($cache=true) { |
d867e696 | 1837 | global $DB, $ACCESSLIB_PRIVATE; |
7d0c81b3 | 1838 | if ($cache and defined('SYSCONTEXTID')) { |
d867e696 | 1839 | if (is_null($ACCESSLIB_PRIVATE->systemcontext)) { |
1840 | $ACCESSLIB_PRIVATE->systemcontext = new object(); | |
1841 | $ACCESSLIB_PRIVATE->systemcontext->id = SYSCONTEXTID; | |
1842 | $ACCESSLIB_PRIVATE->systemcontext->contextlevel = CONTEXT_SYSTEM; | |
1843 | $ACCESSLIB_PRIVATE->systemcontext->instanceid = 0; | |
1844 | $ACCESSLIB_PRIVATE->systemcontext->path = '/'.SYSCONTEXTID; | |
1845 | $ACCESSLIB_PRIVATE->systemcontext->depth = 1; | |
7d0c81b3 | 1846 | } |
d867e696 | 1847 | return $ACCESSLIB_PRIVATE->systemcontext; |
7d0c81b3 | 1848 | } |
c23b0ea1 | 1849 | try { |
df97c6ee | 1850 | $context = $DB->get_record('context', array('contextlevel'=>CONTEXT_SYSTEM)); |
0ecff22d | 1851 | } catch (dml_exception $e) { |
c23b0ea1 | 1852 | //table does not exist yet, sorry |
4f0c2d00 | 1853 | return NULL; |
c23b0ea1 | 1854 | } |
1855 | ||
1856 | if (!$context) { | |
8ba412da | 1857 | $context = new object(); |
1858 | $context->contextlevel = CONTEXT_SYSTEM; | |
7d0c81b3 | 1859 | $context->instanceid = 0; |
1860 | $context->depth = 1; | |
1861 | $context->path = NULL; //not known before insert | |
1862 | ||
0ecff22d | 1863 | try { |
a8f3a651 | 1864 | $context->id = $DB->insert_record('context', $context); |
0ecff22d | 1865 | } catch (dml_exception $e) { |
a8f3a651 | 1866 | // can not create context yet, sorry |
4f0c2d00 | 1867 | return NULL; |
8ba412da | 1868 | } |
1869 | } | |
7d0c81b3 | 1870 | |
1871 | if (!isset($context->depth) or $context->depth != 1 or $context->instanceid != 0 or $context->path != '/'.$context->id) { | |
1872 | $context->instanceid = 0; | |
1873 | $context->path = '/'.$context->id; | |
1874 | $context->depth = 1; | |
5a4e7398 | 1875 | $DB->update_record('context', $context); |
7d0c81b3 | 1876 | } |
1877 | ||
1878 | if (!defined('SYSCONTEXTID')) { | |
1879 | define('SYSCONTEXTID', $context->id); | |
1880 | } | |
1881 | ||
d867e696 | 1882 | $ACCESSLIB_PRIVATE->systemcontext = $context; |
1883 | return $ACCESSLIB_PRIVATE->systemcontext; | |
8ba412da | 1884 | } |
b51ece5b | 1885 | |
9991d157 | 1886 | /** |
b51ece5b | 1887 | * Remove a context record and any dependent entries, |
1888 | * removes context from static context cache too | |
cc3edaa4 | 1889 | * |
46808d7c | 1890 | * @param int $level |
1891 | * @param int $instanceid | |
196f1a25 | 1892 | * @return bool returns true or throws an exception |
9991d157 | 1893 | */ |
1894 | function delete_context($contextlevel, $instanceid) { | |
8432f5e6 | 1895 | global $DB, $ACCESSLIB_PRIVATE, $CFG; |
b51ece5b | 1896 | |
1897 | // do not use get_context_instance(), because the related object might not exist, | |
1898 | // or the context does not exist yet and it would be created now | |
5a4e7398 | 1899 | if ($context = $DB->get_record('context', array('contextlevel'=>$contextlevel, 'instanceid'=>$instanceid))) { |
196f1a25 PS |
1900 | $DB->delete_records('role_assignments', array('contextid'=>$context->id)); |
1901 | $DB->delete_records('role_capabilities', array('contextid'=>$context->id)); | |
1902 | $DB->delete_records('context', array('id'=>$context->id)); | |
1903 | $DB->delete_records('role_names', array('contextid'=>$context->id)); | |
b51ece5b | 1904 | |
a05bcfba PS |
1905 | // delete all files attached to this context |
1906 | $fs = get_file_storage(); | |
1907 | $fs->delete_area_files($context->id); | |
1908 | ||
b51ece5b | 1909 | // do not mark dirty contexts if parents unknown |
1910 | if (!is_null($context->path) and $context->depth > 0) { | |
1911 | mark_context_dirty($context->path); | |
1912 | } | |
1913 | ||
1914 | // purge static context cache if entry present | |
d867e696 | 1915 | unset($ACCESSLIB_PRIVATE->contexts[$contextlevel][$instanceid]); |
1916 | unset($ACCESSLIB_PRIVATE->contextsbyid[$context->id]); | |
b51ece5b | 1917 | |
6bf44482 | 1918 | blocks_delete_all_for_context($context->id); |
9434fef4 | 1919 | filter_delete_all_for_context($context->id); |
9991d157 | 1920 | } |
196f1a25 PS |
1921 | |
1922 | return true; | |
9991d157 | 1923 | } |
1924 | ||
9a81a606 | 1925 | /** |
1926 | * Precreates all contexts including all parents | |
cc3edaa4 | 1927 | * |
1928 | * @global object | |
46808d7c | 1929 | * @param int $contextlevel empty means all |
9a81a606 | 1930 | * @param bool $buildpaths update paths and depths |
1931 | * @return void | |
1932 | */ | |
4f0c2d00 | 1933 | function create_contexts($contextlevel=NULL, $buildpaths=true) { |
5a4e7398 | 1934 | global $DB; |
9a81a606 | 1935 | |
1936 | //make sure system context exists | |
1937 | $syscontext = get_system_context(false); | |
1938 | ||
5c8e6cb1 | 1939 | if (empty($contextlevel) or $contextlevel == CONTEXT_COURSECAT |
1940 | or $contextlevel == CONTEXT_COURSE | |
1941 | or $contextlevel == CONTEXT_MODULE | |
1942 | or $contextlevel == CONTEXT_BLOCK) { | |
5a4e7398 | 1943 | $sql = "INSERT INTO {context} (contextlevel, instanceid) |
9a81a606 | 1944 | SELECT ".CONTEXT_COURSECAT.", cc.id |
5a4e7398 | 1945 | FROM {course}_categories cc |
9a81a606 | 1946 | WHERE NOT EXISTS (SELECT 'x' |
5a4e7398 | 1947 | FROM {context} cx |
9a81a606 | 1948 | WHERE cc.id = cx.instanceid AND cx.contextlevel=".CONTEXT_COURSECAT.")"; |
5a4e7398 | 1949 | $DB->execute($sql); |
9a81a606 | 1950 | |
1951 | } | |
1952 | ||
5c8e6cb1 | 1953 | if (empty($contextlevel) or $contextlevel == CONTEXT_COURSE |
1954 | or $contextlevel == CONTEXT_MODULE | |
1955 | or $contextlevel == CONTEXT_BLOCK) { | |
5a4e7398 | 1956 | $sql = "INSERT INTO {context} (contextlevel, instanceid) |
9a81a606 | 1957 | SELECT ".CONTEXT_COURSE.", c.id |
5a4e7398 | 1958 | FROM {course} c |
9a81a606 | 1959 | WHERE NOT EXISTS (SELECT 'x' |
5a4e7398 | 1960 | FROM {context} cx |
9a81a606 | 1961 | WHERE c.id = cx.instanceid AND cx.contextlevel=".CONTEXT_COURSE.")"; |
5a4e7398 | 1962 | $DB->execute($sql); |
9a81a606 | 1963 | |
1964 | } | |
1965 | ||
e92c286c | 1966 | if (empty($contextlevel) or $contextlevel == CONTEXT_MODULE |
1967 | or $contextlevel == CONTEXT_BLOCK) { | |
5a4e7398 | 1968 | $sql = "INSERT INTO {context} (contextlevel, instanceid) |
9a81a606 | 1969 | SELECT ".CONTEXT_MODULE.", cm.id |
5a4e7398 | 1970 | FROM {course}_modules cm |
9a81a606 | 1971 | WHERE NOT EXISTS (SELECT 'x' |
5a4e7398 | 1972 | FROM {context} cx |
9a81a606 | 1973 | WHERE cm.id = cx.instanceid AND cx.contextlevel=".CONTEXT_MODULE.")"; |
5a4e7398 | 1974 | $DB->execute($sql); |
9a81a606 | 1975 | } |
1976 | ||
e92c286c | 1977 | if (empty($contextlevel) or $contextlevel == CONTEXT_USER |
1978 | or $contextlevel == CONTEXT_BLOCK) { | |
5a4e7398 | 1979 | $sql = "INSERT INTO {context} (contextlevel, instanceid) |
9a81a606 | 1980 | SELECT ".CONTEXT_USER.", u.id |
5a4e7398 | 1981 | FROM {user} u |
9a81a606 | 1982 | WHERE u.deleted=0 |
1983 | AND NOT EXISTS (SELECT 'x' | |
5a4e7398 | 1984 | FROM {context} cx |
9a81a606 | 1985 | WHERE u.id = cx.instanceid AND cx.contextlevel=".CONTEXT_USER.")"; |
5a4e7398 | 1986 | $DB->execute($sql); |
9a81a606 | 1987 | |
1988 | } | |
1989 | ||
e92c286c | 1990 | if (empty($contextlevel) or $contextlevel == CONTEXT_BLOCK) { |
1991 | $sql = "INSERT INTO {context} (contextlevel, instanceid) | |
1992 | SELECT ".CONTEXT_BLOCK.", bi.id | |
1993 | FROM {block_instances} bi | |
1994 | WHERE NOT EXISTS (SELECT 'x' | |
1995 | FROM {context} cx | |
1996 | WHERE bi.id = cx.instanceid AND cx.contextlevel=".CONTEXT_BLOCK.")"; | |
1997 | $DB->execute($sql); | |
1998 | } | |
1999 | ||
9a81a606 | 2000 | if ($buildpaths) { |
5a4e7398 | 2001 | build_context_path(false); |
9a81a606 | 2002 | } |
2003 | } | |
2004 | ||
17b0efae | 2005 | /** |
2006 | * Remove stale context records | |
2007 | * | |
cc3edaa4 | 2008 | * @global object |
17b0efae | 2009 | * @return bool |
2010 | */ | |
2011 | function cleanup_contexts() { | |
5a4e7398 | 2012 | global $DB; |
17b0efae | 2013 | |
70dd126e | 2014 | $sql = " SELECT c.contextlevel, |
17b0efae | 2015 | c.instanceid AS instanceid |
5a4e7398 | 2016 | FROM {context} c |
2017 | LEFT OUTER JOIN {course}_categories t | |
2018 | ON c.instanceid = t.id | |
2019 | WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_COURSECAT." | |
17b0efae | 2020 | UNION |
70dd126e | 2021 | SELECT c.contextlevel, |
2022 | c.instanceid | |
5a4e7398 | 2023 | FROM {context} c |
2024 | LEFT OUTER JOIN {course} t | |
2025 | ON c.instanceid = t.id | |
2026 | WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_COURSE." | |
17b0efae | 2027 | UNION |
70dd126e | 2028 | SELECT c.contextlevel, |
2029 | c.instanceid | |
5a4e7398 | 2030 | FROM {context} c |
2031 | LEFT OUTER JOIN {course}_modules t | |
2032 | ON c.instanceid = t.id | |
2033 | WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_MODULE." | |
17b0efae | 2034 | UNION |
70dd126e | 2035 | SELECT c.contextlevel, |
2036 | c.instanceid | |
5a4e7398 | 2037 | FROM {context} c |
2038 | LEFT OUTER JOIN {user} t | |
2039 | ON c.instanceid = t.id | |
2040 | WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_USER." | |
17b0efae | 2041 | UNION |
70dd126e | 2042 | SELECT c.contextlevel, |
2043 | c.instanceid | |
5a4e7398 | 2044 | FROM {context} c |
f474a4e5 | 2045 | LEFT OUTER JOIN {block_instances} t |
2046 | ON c.instanceid = t.id | |
5a4e7398 | 2047 | WHERE t.id IS NULL AND c.contextlevel = ".CONTEXT_BLOCK." |
17b0efae | 2048 | "; |
d5a8d9aa PS |
2049 | |
2050 | // transactions used only for performance reasons here | |
2051 | $transaction = $DB->start_delegated_transaction(); | |
2052 | ||
5a4e7398 | 2053 | if ($rs = $DB->get_recordset_sql($sql)) { |
5a4e7398 | 2054 | foreach ($rs as $ctx) { |
d5a8d9aa | 2055 | delete_context($ctx->contextlevel, $ctx->instanceid); |
17b0efae | 2056 | } |
5a4e7398 | 2057 | $rs->close(); |
17b0efae | 2058 | } |
d5a8d9aa PS |
2059 | |
2060 | $transaction->allow_commit(); | |
17b0efae | 2061 | return true; |
2062 | } | |
2063 | ||
00653161 | 2064 | /** |
e92c286c | 2065 | * Preloads all contexts relating to a course: course, modules. Block contexts |
2066 | * are no longer loaded here. The contexts for all the blocks on the current | |
2067 | * page are now efficiently loaded by {@link block_manager::load_blocks()}. | |
00653161 | 2068 | * |
2069 | * @param int $courseid Course ID | |
d993468d | 2070 | * @return void |
00653161 | 2071 | */ |
2072 | function preload_course_contexts($courseid) { | |
d867e696 | 2073 | global $DB, $ACCESSLIB_PRIVATE; |
00653161 | 2074 | |
2075 | // Users can call this multiple times without doing any harm | |
d867e696 | 2076 | global $ACCESSLIB_PRIVATE; |
2077 | if (array_key_exists($courseid, $ACCESSLIB_PRIVATE->preloadedcourses)) { | |
00653161 | 2078 | return; |
2079 | } | |
2080 | ||
d993468d | 2081 | $params = array($courseid, $courseid, $courseid); |
2082 | $sql = "SELECT x.instanceid, x.id, x.contextlevel, x.path, x.depth | |
2083 | FROM {course_modules} cm | |
2084 | JOIN {context} x ON x.instanceid=cm.id | |
2085 | WHERE cm.course=? AND x.contextlevel=".CONTEXT_MODULE." | |
2086 | ||
d993468d | 2087 | UNION ALL |
2088 | ||
2089 | SELECT x.instanceid, x.id, x.contextlevel, x.path, x.depth | |
2090 | FROM {context} x | |
2091 | WHERE x.instanceid=? AND x.contextlevel=".CONTEXT_COURSE.""; | |
2092 | ||
2093 | $rs = $DB->get_recordset_sql($sql, $params); | |
00653161 | 2094 | foreach($rs as $context) { |
d867e696 | 2095 | cache_context($context); |
00653161 | 2096 | } |
2097 | $rs->close(); | |
d867e696 | 2098 | $ACCESSLIB_PRIVATE->preloadedcourses[$courseid] = true; |
00653161 | 2099 | } |
2100 | ||
bbbf2d40 | 2101 | /** |
2102 | * Get the context instance as an object. This function will create the | |
2103 | * context instance if it does not exist yet. | |
46808d7c | 2104 | * |
2105 | * @todo Remove code branch from previous fix MDL-9016 which is no longer needed | |
2106 | * | |
e765b5d3 | 2107 | * @param integer $level The context level, for example CONTEXT_COURSE, or CONTEXT_MODULE. |
2108 | * @param integer $instance The instance id. For $level = CONTEXT_COURSE, this would be $course->id, | |
46808d7c | 2109 | * for $level = CONTEXT_MODULE, this would be $cm->id. And so on. Defaults to 0 |
4f0c2d00 PS |
2110 | * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found; |
2111 | * MUST_EXIST means throw exception if no record or multiple records found | |
e765b5d3 | 2112 | * @return object The context object. |
bbbf2d40 | 2113 | */ |
4f0c2d00 | 2114 | function get_context_instance($contextlevel, $instance=0, $strictness=IGNORE_MISSING) { |
e5605780 | 2115 | |
d867e696 | 2116 | global $DB, $ACCESSLIB_PRIVATE; |
8ead7b59 | 2117 | static $allowed_contexts = array(CONTEXT_SYSTEM, CONTEXT_USER, CONTEXT_COURSECAT, CONTEXT_COURSE, CONTEXT_MODULE, CONTEXT_BLOCK); |
d9a35e12 | 2118 | |
7d0c81b3 | 2119 | /// System context has special cache |
8ba412da | 2120 | if ($contextlevel == CONTEXT_SYSTEM) { |
7d0c81b3 | 2121 | return get_system_context(); |
8ba412da | 2122 | } |
2123 | ||
a36a3a3f | 2124 | /// check allowed context levels |
2125 | if (!in_array($contextlevel, $allowed_contexts)) { | |
7bfa3101 | 2126 | // fatal error, code must be fixed - probably typo or switched parameters |
e49ef64a | 2127 | print_error('invalidcourselevel'); |
a36a3a3f | 2128 | } |
2129 | ||
65bcf17b | 2130 | if (!is_array($instance)) { |
2131 | /// Check the cache | |
d867e696 | 2132 | if (isset($ACCESSLIB_PRIVATE->contexts[$contextlevel][$instance])) { // Already cached |
2133 | return $ACCESSLIB_PRIVATE->contexts[$contextlevel][$instance]; | |
65bcf17b | 2134 | } |
2135 | ||
2136 | /// Get it from the database, or create it | |
5a4e7398 | 2137 | if (!$context = $DB->get_record('context', array('contextlevel'=>$contextlevel, 'instanceid'=>$instance))) { |
4f0c2d00 | 2138 | $context = create_context($contextlevel, $instance, $strictness); |
65bcf17b | 2139 | } |
2140 | ||
2141 | /// Only add to cache if context isn't empty. | |
2142 | if (!empty($context)) { | |
d867e696 | 2143 | cache_context($context); |
65bcf17b | 2144 | } |
2145 | ||
2146 | return $context; | |
e5605780 | 2147 | } |
2148 | ||
65bcf17b | 2149 | |
2150 | /// ok, somebody wants to load several contexts to save some db queries ;-) | |
2151 | $instances = $instance; | |
2152 | $result = array(); | |
2153 | ||
2154 | foreach ($instances as $key=>$instance) { | |
2155 | /// Check the cache first | |
d867e696 | 2156 | if (isset($ACCESSLIB_PRIVATE->contexts[$contextlevel][$instance])) { // Already cached |
2157 | $result[$instance] = $ACCESSLIB_PRIVATE->contexts[$contextlevel][$instance]; | |
65bcf17b | 2158 | unset($instances[$key]); |
2159 | continue; | |
2160 | } | |
e5605780 | 2161 | } |
2162 | ||
65bcf17b | 2163 | if ($instances) { |
5a4e7398 | 2164 | list($instanceids, $params) = $DB->get_in_or_equal($instances, SQL_PARAMS_QM); |
2165 | array_unshift($params, $contextlevel); | |
2166 | $sql = "SELECT instanceid, id, contextlevel, path, depth | |
2167 | FROM {context} | |
2168 | WHERE contextlevel=? AND instanceid $instanceids"; | |
2169 | ||
2170 | if (!$contexts = $DB->get_records_sql($sql, $params)) { | |
65bcf17b | 2171 | $contexts = array(); |
2172 | } | |
2173 | ||
2174 | foreach ($instances as $instance) { | |
2175 | if (isset($contexts[$instance])) { | |
2176 | $context = $contexts[$instance]; | |
2177 | } else { | |
2178 | $context = create_context($contextlevel, $instance); | |
2179 | } | |
2180 | ||
2181 | if (!empty($context)) { | |
d867e696 | 2182 | cache_context($context); |
65bcf17b | 2183 | } |
2184 | ||
2185 | $result[$instance] = $context; | |
2186 | } | |
ccfc5ecc | 2187 | } |
0468976c | 2188 | |
65bcf17b | 2189 | return $result; |
bbbf2d40 | 2190 | } |
2191 | ||
cee0901c | 2192 | |
340ea4e8 | 2193 | /** |
e765b5d3 | 2194 | * Get a context instance as an object, from a given context id. |
cc3edaa4 | 2195 | * |
65bcf17b | 2196 | * @param mixed $id a context id or array of ids. |
01a2ce80 PS |
2197 | * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found; |
2198 | * MUST_EXIST means throw exception if no record or multiple records found | |
46808d7c | 2199 | * @return mixed object, array of the context object, or false. |
340ea4e8 | 2200 | */ |
01a2ce80 | 2201 | function get_context_instance_by_id($id, $strictness=IGNORE_MISSING) { |
d867e696 | 2202 | global $DB, $ACCESSLIB_PRIVATE; |
d9a35e12 | 2203 | |
7d0c81b3 | 2204 | if ($id == SYSCONTEXTID) { |
2205 | return get_system_context(); | |
2206 | } | |
2207 | ||
d867e696 | 2208 | if (isset($ACCESSLIB_PRIVATE->contextsbyid[$id])) { // Already cached |
2209 | return $ACCESSLIB_PRIVATE->contextsbyid[$id]; | |
340ea4e8 | 2210 | } |
2211 | ||
01a2ce80 | 2212 | if ($context = $DB->get_record('context', array('id'=>$id), '*', $strictness)) { |
d867e696 | 2213 | cache_context($context); |
340ea4e8 | 2214 | return $context; |
2215 | } | |
2216 | ||
2217 | return false; | |
2218 | } | |
2219 | ||
bbbf2d40 | 2220 | |
8737be58 | 2221 | /** |
2222 | * Get the local override (if any) for a given capability in a role in a context | |
cc3edaa4 | 2223 | * |
2224 | * @global object | |
46808d7c | 2225 | * @param int $roleid |
2226 | * @param int $contextid | |
2227 | * @param string $capability | |
8737be58 | 2228 | */ |
2229 | function get_local_override($roleid, $contextid, $capability) { | |
5a4e7398 | 2230 | global $DB; |
2231 | return $DB->get_record('role_capabilities', array('roleid'=>$roleid, 'capability'=>$capability, 'contextid'=>$contextid)); | |
8737be58 | 2232 | } |
2233 | ||
01a2ce80 PS |
2234 | /** |
2235 | * Returns context instance plus related course and cm instances | |
2236 | * @param int $contextid | |
2237 | * @return array of ($context, $course, $cm) | |
2238 | */ | |
2239 | function get_context_info_array($contextid) { | |
2240 | global $DB; | |
2241 | ||
2242 | $context = get_context_instance_by_id($contextid, MUST_EXIST); | |
4f0c2d00 PS |
2243 | $course = NULL; |
2244 | $cm = NULL; | |
01a2ce80 PS |
2245 | |
2246 | if ($context->contextlevel == CONTEXT_COURSE) { | |
2247 | $course = $DB->get_record('course', array('id'=>$context->instanceid), '*', MUST_EXIST); | |
2248 | ||
2249 | } else if ($context->contextlevel == CONTEXT_MODULE) { | |
2250 | $cm = get_coursemodule_from_id('', $context->instanceid, 0, false, MUST_EXIST); | |
2251 | $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST); | |
2252 | ||
2253 | } else if ($context->contextlevel == CONTEXT_BLOCK) { | |
2254 | $parentcontexts = get_parent_contexts($context, false); | |
2255 | $parent = reset($parentcontexts); | |
2256 | $parent = get_context_instance_by_id($parent); | |
2257 | ||
2258 | if ($parent->contextlevel == CONTEXT_COURSE) { | |
2259 | $course = $DB->get_record('course', array('id'=>$parent->instanceid), '*', MUST_EXIST); | |
2260 | } else if ($parent->contextlevel == CONTEXT_MODULE) { | |
2261 | $cm = get_coursemodule_from_id('', $parent->instanceid, 0, false, MUST_EXIST); | |
2262 | $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST); | |
2263 | } | |
2264 | } | |
2265 | ||
2266 | return array($context, $course, $cm); | |
2267 | } | |
8737be58 | 2268 | |
bbbf2d40 | 2269 | |
46808d7c | 2270 | ////////////////////////////////////// |
2271 | // DB TABLE RELATED FUNCTIONS // | |
2272 | ////////////////////////////////////// | |
bbbf2d40 | 2273 | |
cee0901c | 2274 | /** |
bbbf2d40 | 2275 | * function that creates a role |
cc3edaa4 | 2276 | * |
2277 | * @global object | |
46808d7c | 2278 | * @param string $name role name |
2279 | * @param string $shortname role short name | |
2280 | * @param string $description role description | |
4f0c2d00 | 2281 | * @param string $archetype |
46808d7c | 2282 | * @return mixed id or dml_exception |
bbbf2d40 | 2283 | */ |
4f0c2d00 | 2284 | function create_role($name, $shortname, $description, $archetype='') { |
f33e1ed4 | 2285 | global $DB; |
eef868d1 | 2286 | |
4f0c2d00 PS |
2287 | if (strpos($archetype, 'moodle/legacy:') !== false) { |
2288 | throw new coding_exception('Use new role archetype parameter in create_role() instead of old legacy capabilities.'); | |
2289 | } | |
2290 | ||
2291 | // verify role archetype actually exists | |
2292 | $archetypes = get_role_archetypes(); | |
2293 | if (empty($archetypes[$archetype])) { | |
2294 | $archetype = ''; | |
2295 | } | |
2296 | ||
bbdb7070 | 2297 | // Get the system context. |
19a4a32e | 2298 | $context = get_context_instance(CONTEXT_SYSTEM); |
31f26796 | 2299 | |
bbdb7070 | 2300 | // Insert the role record. |
b5959f30 | 2301 | $role = new object(); |
ac173d3e | 2302 | $role->name = $name; |
2303 | $role->shortname = $shortname; | |
98882637 | 2304 | $role->description = $description; |
4f0c2d00 | 2305 | $role->archetype = $archetype; |
eef868d1 | 2306 | |
8420bee9 | 2307 | //find free sortorder number |
bbdb7070 | 2308 | $role->sortorder = $DB->get_field('role', 'MAX(sortorder) + 1', array()); |
716dd163 | 2309 | if (empty($role->sortorder)) { |
2310 | $role->sortorder = 1; | |
2311 | } | |
bbdb7070 | 2312 | $id = $DB->insert_record('role', $role); |
b5959f30 | 2313 | |
bbdb7070 | 2314 | return $id; |
bbbf2d40 | 2315 | } |
2316 | ||
8420bee9 | 2317 | /** |
46808d7c | 2318 | * Function that deletes a role and cleanups up after it |
cc3edaa4 | 2319 | * |
46808d7c | 2320 | * @param int $roleid id of role to delete |
4f65e0fb | 2321 | * @return bool always true |
8420bee9 | 2322 | */ |
2323 | function delete_role($roleid) { | |
f33e1ed4 | 2324 | global $CFG, $DB; |
c421ad4b | 2325 | |
4f0c2d00 | 2326 | // first unssign all users |
df997f84 | 2327 | role_unassign_all(array('roleid'=>$roleid)); |
c421ad4b | 2328 | |
4f0c2d00 PS |
2329 | // cleanup all references to this role, ignore errors |
2330 | $DB->delete_records('role_capabilities', array('roleid'=>$roleid)); | |
2331 | $DB->delete_records('role_allow_assign', array('roleid'=>$roleid)); | |
2332 | $DB->delete_records('role_allow_assign', array('allowassign'=>$roleid)); | |
2333 | $DB->delete_records('role_allow_override', array('roleid'=>$roleid)); | |
2334 | $DB->delete_records('role_allow_override', array('allowoverride'=>$roleid)); | |
2335 | $DB->delete_records('role_names', array('roleid'=>$roleid)); | |
2336 | $DB->delete_records('role_context_levels', array('roleid'=>$roleid)); | |
60ace1e1 | 2337 | |
4f0c2d00 | 2338 | // finally delete the role itself |
cb8cb8bf | 2339 | // get this before the name is gone for logging |
f33e1ed4 | 2340 | $rolename = $DB->get_field('role', 'name', array('id'=>$roleid)); |
5a4e7398 | 2341 | |
4f0c2d00 | 2342 | $DB->delete_records('role', array('id'=>$roleid)); |
5a4e7398 | 2343 | |
4f0c2d00 | 2344 | add_to_log(SITEID, 'role', 'delete', 'admin/roles/action=delete&roleid='.$roleid, $rolename, ''); |
8420bee9 | 2345 | |
4f0c2d00 | 2346 | return true; |
8420bee9 | 2347 | } |
2348 | ||
bbbf2d40 | 2349 | /** |
2350 | * Function to write context specific overrides, or default capabilities. | |
46808d7c | 2351 | * |
cc3edaa4 | 2352 | * @global object |
2353 | * @global object | |
46808d7c | 2354 | * @param string module string name |
2355 | * @param string capability string name | |
2356 | * @param int contextid context id | |
2357 | * @param int roleid role id | |
2358 | * @param int permission int 1,-1 or -1000 should not be writing if permission is 0 | |
2359 | * @return bool | |
bbbf2d40 | 2360 | */ |
e7876c1e | 2361 | function assign_capability($capability, $permission, $roleid, $contextid, $overwrite=false) { |
f33e1ed4 | 2362 | global $USER, $DB; |
eef868d1 | 2363 | |
96986241 | 2364 | if (empty($permission) || $permission == CAP_INHERIT) { // if permission is not set |
eef868d1 | 2365 | unassign_capability($capability, $roleid, $contextid); |
96986241 | 2366 | return true; |
98882637 | 2367 | } |
eef868d1 | 2368 | |
f33e1ed4 | 2369 | $existing = $DB->get_record('role_capabilities', array('contextid'=>$contextid, 'roleid'=>$roleid, 'capability'=>$capability)); |
e7876c1e | 2370 | |
2371 | if ($existing and !$overwrite) { // We want to keep whatever is there already | |
2372 | return true; | |
2373 | } | |
2374 | ||
bbbf2d40 | 2375 | $cap = new object; |
2376 | $cap->contextid = $contextid; | |
2377 | $cap->roleid = $roleid; | |
2378 | $cap->capability = $capability; | |
2379 | $cap->permission = $permission; | |
2380 | $cap->timemodified = time(); | |
9db12da7 | 2381 | $cap->modifierid = empty($USER->id) ? 0 : $USER->id; |
e7876c1e | 2382 | |
2383 | if ($existing) { | |
2384 | $cap->id = $existing->id; | |
4f0c2d00 | 2385 | $DB->update_record('role_capabilities', $cap); |
e7876c1e | 2386 | } else { |
f33e1ed4 | 2387 | $c = $DB->get_record('context', array('id'=>$contextid)); |
4f0c2d00 | 2388 | $DB->insert_record('role_capabilities', $cap); |
e7876c1e | 2389 | } |
4f0c2d00 | 2390 | return true; |
bbbf2d40 | 2391 | } |
2392 | ||
bbbf2d40 | 2393 | /** |
2394 | * Unassign a capability from a role. | |
117bd748 | 2395 | * |
cc3edaa4 | 2396 | * @global object |
46808d7c | 2397 | * @param int $roleid the role id |
2398 | * @param string $capability the name of the capability | |
2399 | * @return boolean success or failure | |
bbbf2d40 | 2400 | */ |
2401 | function unassign_capability($capability, $roleid, $contextid=NULL) { | |
f33e1ed4 | 2402 | global $DB; |
eef868d1 | 2403 | |
4f0c2d00 | 2404 | if (!empty($contextid)) { |
c345bb58 | 2405 | // delete from context rel, if this is the last override in this context |
4f0c2d00 | 2406 | $DB->delete_records('role_capabilities', array('capability'=>$capability, 'roleid'=>$roleid, 'contextid'=>$contextid)); |
98882637 | 2407 | } else { |
4f0c2d00 | 2408 | $DB->delete_records('role_capabilities', array('capability'=>$capability, 'roleid'=>$roleid)); |
98882637 | 2409 | } |
4f0c2d00 | 2410 | return true; |
bbbf2d40 | 2411 | } |
2412 | ||
2413 | ||
2414 | /** | |
46808d7c | 2415 | * Get the roles that have a given capability assigned to it |
759ac72d | 2416 | * Get the roles that have a given capability assigned to it. This function |
2417 | * does not resolve the actual permission of the capability. It just checks | |
2418 | * for assignment only. | |
cc3edaa4 | 2419 | * |
2420 | * @global object | |
2421 | * @global object | |
46808d7c | 2422 | * @param string $capability - capability name (string) |
4f0c2d00 | 2423 | * @param string $permission - optional, the permission defined for this capability |
46808d7c | 2424 | * either CAP_ALLOW, CAP_PREVENT or CAP_PROHIBIT. Defaults to NULL |
2425 | * @param object $contect | |
2426 | * @return mixed array or role objects | |
bbbf2d40 | 2427 | */ |
4f0c2d00 | 2428 | function get_roles_with_capability($capability, $permission=NULL, $context=NULL) { |
f33e1ed4 | 2429 | global $CFG, $DB; |
eef868d1 | 2430 | |
f33e1ed4 | 2431 | $params = array(); |
5a4e7398 | 2432 | |
ec7a8b79 | 2433 | if ($context) { |
2434 | if ($contexts = get_parent_contexts($context)) { | |
2435 | $listofcontexts = '('.implode(',', $contexts).')'; | |
2436 | } else { | |
21c9bace | 2437 | $sitecontext = get_context_instance(CONTEXT_SYSTEM); |
eef868d1 | 2438 | $listofcontexts = '('.$sitecontext->id.')'; // must be site |
2439 | } | |
f33e1ed4 | 2440 | $contextstr = "AND (rc.contextid = ? OR rc.contextid IN $listofcontexts)"; |
2441 | $params[] = $context->id; | |
ec7a8b79 | 2442 | } else { |
2443 | $contextstr = ''; | |
2444 | } | |
eef868d1 | 2445 | |
2446 | $selectroles = "SELECT r.* | |
5a4e7398 | 2447 | FROM {role} r, |
2448 | {role_capabilities} rc | |
f33e1ed4 | 2449 | WHERE rc.capability = ? |
5a4e7398 | 2450 | AND rc.roleid = r.id $contextstr"; |
bbbf2d40 | 2451 | |
f33e1ed4 | 2452 | array_unshift($params, $capability); |
2453 | ||
bbbf2d40 | 2454 | if (isset($permission)) { |
f33e1ed4 | 2455 | $selectroles .= " AND rc.permission = ?"; |
2456 | $params[] = $permission; | |
bbbf2d40 | 2457 | } |
f33e1ed4 | 2458 | return $DB->get_records_sql($selectroles, $params); |
bbbf2d40 | 2459 | } |
2460 | ||
2461 | ||
2462 | /** | |
df997f84 | 2463 | * This function makes a role-assignment (a role for a user in a particular context) |
117bd748 | 2464 | * |
46808d7c | 2465 | * @param int $roleid the role of the id |
2466 | * @param int $userid userid | |
46808d7c | 2467 | * @param int $contextid id of the context |
df997f84 PS |
2468 | * @param string $component example 'enrol_ldap', defaults to '' which means manual assignment, |
2469 | * @prama int $itemid id of enrolment/auth plugin | |
2470 | * @param string $timemodified defaults to current time | |
2471 | * @return int new/existing id of the assignment | |
bbbf2d40 | 2472 | */ |
df997f84 | 2473 | function role_assign($roleid, $userid, $contextid, $component = '', $itemid = 0, $timemodified = '') { |
f33e1ed4 | 2474 | global $USER, $CFG, $DB; |
bbbf2d40 | 2475 | |
df997f84 PS |
2476 | // first of all detect if somebody is using old style parameters |
2477 | if ($contextid === 0 or is_numeric($component)) { | |
2478 | throw new coding_exception('Invalid call to role_assign(), code needs to be updated to use new order of parameters'); | |
2479 | } | |
a9e1c058 | 2480 | |
df997f84 | 2481 | // now validate all parameters |
bbbf2d40 | 2482 | if (empty($roleid)) { |
df997f84 | 2483 | throw new coding_exception('Invalid call to role_assign(), roleid can not be empty'); |
bbbf2d40 | 2484 | } |
2485 | ||
e8c2189d | 2486 | if (empty($userid)) { |
df997f84 | 2487 | throw new coding_exception('Invalid call to role_assign(), userid can not be empty'); |
bbbf2d40 | 2488 | } |
eef868d1 | 2489 | |
df997f84 PS |
2490 | if ($itemid) { |
2491 | if (strpos($component, '_') === false) { | |
2492 | throw new coding_exception('Invalid call to role_assign(), component must start with plugin type such as"enrol_" when itemid specified', 'component:'.$component); | |
2493 | } | |
2494 | } else { | |
2495 | $itemid = 0; | |
2496 | if ($component !== '' and strpos($component, '_') === false) { | |
2497 | throw new coding_exception('Invalid call to role_assign(), invalid component string', 'component:'.$component); | |
2498 | } | |
7700027f | 2499 | } |
bbbf2d40 | 2500 | |
df997f84 PS |
2501 | if (!$DB->record_exists('user', array('id'=>$userid, 'deleted'=>0))) { |
2502 | throw new coding_exception('User ID does not exist or is deleted!', 'userid:'.$userid); | |
a9e1c058 | 2503 | return false; |
bbbf2d40 | 2504 | } |
2505 | ||
df997f84 | 2506 | $context = get_context_instance_by_id($contextid, MUST_EXIST); |
a9e1c058 | 2507 | |
69b0088c | 2508 | if (!$timemodified) { |
c421ad4b | 2509 | $timemodified = time(); |
69b0088c | 2510 | } |
7700027f | 2511 | |
a9e1c058 | 2512 | /// Check for existing entry |
df997f84 PS |
2513 | $ras = $DB->get_records('role_assignments', array('roleid'=>$roleid, 'contextid'=>$context->id, 'userid'=>$userid, 'component'=>$component, 'itemid'=>$itemid), 'id'); |
2514 | ||
2515 | if ($ras) { | |
2516 | // role already assigned - this should not happen | |
2517 | if (count($ras) > 1) { | |
2518 | //very weird - remove all duplicates! | |
2519 | $ra = array_shift($ras); | |
2520 | foreach ($ras as $r) { | |
2521 | $DB->delete_records('role_assignments', array('id'=>$r->id)); | |
2522 | } | |
2523 | } else { | |
2524 | $ra = reset($ras); | |
2525 | } | |
128f0984 | 2526 | |
df997f84 PS |
2527 | // actually there is no need to update, reset anything or trigger any event, so just return |
2528 | return $ra->id; | |
96608a55 | 2529 | } |
c421ad4b | 2530 | |
df997f84 PS |
2531 | // Create a new entry |
2532 | $ra = new object(); | |
2533 | $ra->roleid = $roleid; | |
2534 | $ra->contextid = $context->id; | |
2535 | $ra->userid = $userid; | |
2536 | $ra->component = $component; | |
2537 | $ra->itemid = $itemid; | |
2538 | $ra->timemodified = $timemodified; | |
2539 | $ra->modifierid = empty($USER->id) ? 0 : $USER->id; | |
eef868d1 | 2540 | |
df997f84 PS |
2541 | $ra->id = $DB->insert_record('role_assignments', $ra); |
2542 | ||
2543 | // mark context as dirty - again expensive, but needed | |
2544 | mark_context_dirty($context->path); | |
2545 | ||
2546 | if (!empty($USER->id) && $USER->id == $userid) { | |
2547 | // If the user is the current user, then do full reload of capabilities too. | |
2548 | load_all_capabilities(); | |
4e5f3064 | 2549 | } |
6eb4f823 | 2550 | |
96608a55 | 2551 | events_trigger('role_assigned', $ra); |
2552 | ||
386c151e | 2553 | return $ra->id; |
bbbf2d40 | 2554 | } |
2555 | ||
bbbf2d40 | 2556 | /** |
df997f84 | 2557 | * Removes one role assignment |
cc3edaa4 | 2558 | * |
df997f84 PS |
2559 | * @param int $roleid |
2560 | * @param int $userid | |
2561 | * @param int $contextid | |
2562 | * @param string $component | |
2563 | * @param int $itemid | |
2564 | * @return void | |
bbbf2d40 | 2565 | */ |
df997f84 | 2566 | function role_unassign($roleid, $userid, $contextid, $component = '', $itemid = 0) { |
5a4e7398 | 2567 | global $USER, $CFG, $DB; |
5a4e7398 | 2568 | |
df997f84 PS |
2569 | // first make sure the params make sense |
2570 | if ($roleid == 0 or $userid == 0 or $contextid == 0) { | |
2571 | throw new coding_exception('Invalid call to role_unassign(), please use role_unassign_all() when removing multiple role assignments'); | |
6bc1e5d5 | 2572 | } |
d74067e8 | 2573 | |
df997f84 PS |
2574 | if ($itemid) { |
2575 | if (strpos($component, '_') === false) { | |
2576 | throw new coding_exception('Invalid call to role_assign(), component must start with plugin type such as "enrol_" when itemid specified', 'component:'.$component); | |
2577 | } | |
2578 | } else { | |
2579 | $itemid = 0; | |
2580 | if ($component !== '' and strpos($component, '_') === false) { | |
2581 | throw new coding_exception('Invalid call to role_assign(), invalid component string', 'component:'.$component); | |
4f0c2d00 PS |
2582 | } |
2583 | } | |
2584 | ||
df997f84 | 2585 | role_unassign_all(array('roleid'=>$roleid, 'userid'=>$userid, 'contextid'=>$contextid, 'component'=>$component, 'itemid'=>$itemid), false, false); |
4f0c2d00 PS |
2586 | } |
2587 | ||
2588 | /** | |
df997f84 PS |
2589 | * Removes multiple role assignments, parameters may contain: |
2590 | * 'roleid', 'userid', 'contextid', 'component', 'enrolid'. | |
4f0c2d00 | 2591 | * |
df997f84 PS |
2592 | * @param array $params role assignment parameters |
2593 | * @param bool $subcontexts unassign in subcontexts too | |
2594 | * @param bool $includmanual include manual role assignments too | |
2595 | * @return void | |
4f0c2d00 | 2596 | */ |
df997f84 PS |
2597 | function role_unassign_all(array $params, $subcontexts = false, $includemanual=false) { |
2598 | global $USER, $CFG, $DB; | |
4f0c2d00 | 2599 | |
df997f84 PS |
2600 | if (!$params) { |
2601 | throw new coding_exception('Missing parameters in role_unsassign_all() call'); | |
4f0c2d00 PS |
2602 | } |
2603 | ||
df997f84 PS |
2604 | $allowed = array('roleid', 'userid', 'contextid', 'component', 'itemid'); |
2605 | foreach ($params as $key=>$value) { | |
2606 | if (!in_array($key, $allowed)) { | |
2607 | throw new coding_exception('Unknown role_unsassign_all() parameter key', 'key:'.$key); | |
2608 | } | |
2609 | } | |
4f0c2d00 | 2610 | |
df997f84 PS |
2611 | if (isset($params['component']) and $params['component'] !== '' and strpos($params['component'], '_') === false) { |
2612 | throw new coding_exception('Invalid component paramter in role_unsassign_all() call', 'component:'.$params['component']); | |
2613 | } | |
4f0c2d00 | 2614 | |
df997f84 PS |
2615 | if ($includemanual) { |
2616 | if (!isset($params['component']) or $params['component'] === '') { | |
2617 | throw new coding_exception('include manual parameter requires component parameter in role_unsassign_all() call'); | |
4f0c2d00 | 2618 | } |
df997f84 | 2619 | } |
4f0c2d00 | 2620 | |
df997f84 PS |
2621 | if ($subcontexts) { |
2622 | if (empty($params['contextid'])) { | |
2623 | throw new coding_exception('subcontexts paramtere requires component parameter in role_unsassign_all() call'); | |
2624 | } | |
2625 | } | |
4f0c2d00 | 2626 | |
df997f84 PS |
2627 | $ras = $DB->get_records('role_assignments', $params); |
2628 | foreach($ras as $ra) { | |
2629 | $DB->delete_records('role_assignments', array('id'=>$ra->id)); | |
2630 | if ($context = get_context_instance_by_id($ra->contextid)) { | |
2631 | // this is a bit expensive but necessary | |
2632 | mark_context_dirty($context->path); | |
2633 | /// If the user is the current user, then do full reload of capabilities too. | |
2634 | if (!empty($USER->id) && $USER->id == $ra->userid) { | |
2635 | load_all_capabilities(); | |
2636 | } | |
2637 | } | |
2638 | events_trigger('role_unassigned', $ra); | |
2639 | } | |
2640 | unset($ras); | |
4f0c2d00 | 2641 | |
df997f84 PS |
2642 | // process subcontexts |
2643 | if ($subcontexts and $context = get_context_instance_by_id($params['contextid'])) { | |
2644 | $contexts = get_child_contexts($context); | |
2645 | $mparams = $params; | |
2646 | foreach($contexts as $context) { | |
2647 | $mparams['contextid'] = $context->id; | |
2648 | $ras = $DB->get_records('role_assignments', $mparams); | |
2649 | foreach($ras as $ra) { | |
2650 | $DB->delete_records('role_assignments', array('id'=>$ra->id)); | |
2651 | // this is a bit expensive but necessary | |
2652 | mark_context_dirty($context->path); | |
2653 | /// If the user is the current user, then do full reload of capabilities too. | |
2654 | if (!empty($USER->id) && $USER->id == $ra->userid) { | |
2655 | load_all_capabilities(); | |
2656 | } | |
2657 | events_trigger('role_unassigned', $ra); | |
2658 | } | |
2659 | } | |
4f0c2d00 PS |
2660 | } |
2661 | ||
df997f84 PS |
2662 | // do this once more for all manual role assignments |
2663 | if ($includemanual) { | |
2664 | $params['component'] = ''; | |
2665 | role_unassign_all($params, $subcontexts, false); | |
2666 | } | |
4f0c2d00 PS |
2667 | } |
2668 | ||
df997f84 | 2669 | |
4f0c2d00 PS |
2670 | /** |
2671 | * Determines if a user is currently logged in | |
2672 | * | |
2673 | * @return bool | |
2674 | */ | |
2675 | function isloggedin() { | |
2676 | global $USER; | |
2677 | ||
2678 | return (!empty($USER->id)); | |
2679 | } | |
2680 | ||
2681 | /** | |
2682 | * Determines if a user is logged in as real guest user with username 'guest'. | |
2683 | * | |
df997f84 | 2684 | * @param int|object $user mixed user object or id, $USER if not specified |
4f0c2d00 PS |
2685 | * @return bool true if user is the real guest user, false if not logged in or other user |
2686 | */ | |
2687 | function isguestuser($user = NULL) { | |
2688 | global $USER, $DB, $CFG; | |
2689 | ||
2690 | // make sure we have the user id cached in config table, because we are going to use it a lot | |
2691 | if (empty($CFG->siteguest)) { | |
2692 | if (!$guestid = $DB->get_field('user', 'id', array('username'=>'guest', 'mnethostid'=>$CFG->mnet_localhost_id))) { | |
2693 | // guest does not exist yet, weird | |
2694 | return false; | |
2695 | } | |
2696 | set_config('siteguest', $guestid); | |
2697 | } | |
2698 | if ($user === NULL) { | |
2699 | $user = $USER; | |
2700 | } | |
2701 | ||
2702 | if ($user === NULL) { | |
2703 | // happens when setting the $USER | |
2704 | return false; | |
2705 | ||
2706 | } else if (is_numeric($user)) { | |
2707 | return ($CFG->siteguest == $user); | |
2708 | ||
2709 | } else if (is_object($user)) { | |
2710 | if (empty($user->id)) { | |
2711 | return false; // not logged in means is not be guest | |
2712 | } else { | |
2713 | return ($CFG->siteguest == $user->id); | |
2714 | } | |
2715 | ||
2716 | } else { | |
2717 | throw new coding_exception('Invalid user parameter supplied for isguestuser() function!'); | |
2718 | } | |
2719 | } | |
2720 | ||
2721 | /** | |
2722 | * Does user have a (temporary or real) guest access to course? | |
2723 | * | |
2724 | * @param object $context | |
2725 | * @param object|int $user | |
2726 | * @return bool | |
2727 | */ | |
2728 | function is_guest($context, $user = NULL) { | |
2729 | // first find the course context | |
2730 | $coursecontext = get_course_context($context); | |
2731 | ||
2732 | // make sure there is a real user specified | |
2733 | if ($user === NULL) { | |
2734 | $userid = !empty($USER->id) ? $USER->id : 0; | |
2735 | } else { | |
2736 | $userid = !empty($user->id) ? $user->id : $user; | |
2737 | } | |
2738 | ||
2739 | if (isguestuser($userid)) { | |
2740 | // can not inspect or be enrolled | |
2741 | return true; | |
2742 | } | |
2743 | ||
2744 | if (has_capability('moodle/course:view', $coursecontext, $user)) { | |
2745 | // viewing users appear out of nowhere, they are neither guests nor participants | |
2746 | return false; | |
2747 | } | |
2748 | ||
df997f84 PS |
2749 | // consider only real active enrolments here |
2750 | if (is_enrolled($coursecontext, $user, '', true)) { | |
4f0c2d00 PS |
2751 | return false; |
2752 | } | |
2753 | ||
2754 | return true; | |
2755 | } | |
2756 | ||
2757 | ||
2758 | /** | |
2418d71e | 2759 | * Returns true if the user has moodle/course:view capability in the course, |
4f0c2d00 PS |
2760 | * this is intended for admins, managers (aka small admins), inspectors, etc. |
2761 | * | |
2762 | * @param object $context | |
2763 | * @param int|object $user, if NULL $USER is used | |
2764 | * @param string $withcapability extra capability name | |
2765 | * @return bool | |
2766 | */ | |
2767 | function is_viewing($context, $user = NULL, $withcapability = '') { | |
2768 | global $USER; | |
2769 | ||
2770 | // first find the course context | |
2771 | $coursecontext = get_course_context($context); | |
2772 | ||
2773 | if (isguestuser($user)) { | |
2774 | // can not inspect | |
83bbafaa | 2775 | return false; |
4f0c2d00 PS |
2776 | } |
2777 | ||
2778 | if (!has_capability('moodle/course:view', $coursecontext, $user)) { | |
2779 | // admins are allowed to inspect courses | |
2780 | return false; | |
2781 | } | |
2782 | ||
2783 | if ($withcapability and !has_capability($withcapability, $context, $user)) { | |
2784 | // site admins always have the capability, but the enrolment above blocks | |
2785 | return false; | |
2786 | } | |
2787 | ||
2788 | return true; | |
2789 | } | |
2790 | ||
2791 | /** | |
2792 | * Returns true if user is enrolled (is participating) in course | |
2793 | * this is intended for students and teachers. | |
2794 | * | |
2795 | * @param object $context | |
4f65e0fb | 2796 | * @param int|object $user, if NULL $USER is used, otherwise user object or id expected |
4f0c2d00 | 2797 | * @param string $withcapability extra capability name |
df997f84 | 2798 | * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions |
4f0c2d00 PS |
2799 | * @return bool |
2800 | */ | |
df997f84 PS |
2801 | function is_enrolled($context, $user = NULL, $withcapability = '', $onlyactive = false) { |
2802 | global $USER, $DB; | |
4f0c2d00 PS |
2803 | |
2804 | // first find the course context | |
2805 | $coursecontext = get_course_context($context); | |
2806 | ||
2807 | // make sure there is a real user specified | |
2808 | if ($user === NULL) { | |
2809 | $userid = !empty($USER->id) ? $USER->id : 0; | |
2810 | } else { | |
2811 | $userid = !empty($user->id) ? $user->id : $user; | |
2812 | } | |
2813 | ||
2814 | if (empty($userid)) { | |
2815 | // not-logged-in! | |
2816 | return false; | |
2817 | } else if (isguestuser($userid)) { | |
2818 | // guest account can not be enrolled anywhere | |
2819 | return false; | |
2820 | } | |
2821 | ||
df997f84 PS |
2822 | if ($coursecontext->instanceid == SITEID) { |
2823 | // everybody participates on frontpage | |
2824 | } else { | |
2825 | if ($onlyactive) { | |
2826 | $sql = "SELECT ue.* | |
2827 | FROM {user_enrolments} ue | |
2828 | JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid) | |
2829 | JOIN {user} u ON u.id = ue.userid | |
2830 | WHERE ue.userid = :userid AND ue.status = :active AND e.status = :enabled AND u.deleted = 0"; | |
2831 | $params = array('enabled'=>ENROL_INSTANCE_ENABLED, 'active'=>ENROL_USER_ACTIVE, 'userid'=>$userid, 'courseid'=>$coursecontext->instanceid); | |
2832 | // this result should be very small, better not do the complex time checks in sql for now ;-) | |
2833 | $enrolments = $DB->get_records_sql($sql, $params); | |
2834 | $now = time(); | |
2835 | // make sure the enrol period is ok | |
2836 | $result = false; | |
2837 | foreach ($enrolments as $e) { | |
2838 | if ($e->timestart > $now) { | |
2839 | continue; | |
2840 | } | |
2841 | if ($e->timeend and $e->timeend < $now) { | |
2842 | continue; | |
2843 | } | |
2844 | $result = true; | |
2845 | break; | |
2846 | } | |
2847 | if (!$result) { | |
2848 | return false; | |
2849 | } | |
2850 | ||
2851 | } else { | |
2852 | // any enrolment is good for us here, even outdated, disabled or inactive | |
2853 | $sql = "SELECT 'x' | |
2854 | FROM {user_enrolments} ue | |
2855 | JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid) | |
2856 | JOIN {user} u ON u.id = ue.userid | |
2857 | WHERE ue.userid = :userid AND u.deleted = 0"; | |
2858 | $params = array('userid'=>$userid, 'courseid'=>$coursecontext->instanceid); | |
2859 | if (!$DB->record_exists_sql($sql, $params)) { | |
2860 | return false; | |
2861 | } | |
2862 | } | |
4f0c2d00 PS |
2863 | } |
2864 | ||
2865 | if ($withcapability and !has_capability($withcapability, $context, $userid)) { | |
2866 | return false; | |
2867 | } | |
2868 | ||
2869 | return true; | |
2870 | } | |
2871 | ||
2872 | /** | |
2873 | * Returns array with sql code and parameters returning all ids | |
2874 | * of users enrolled into course. | |
df997f84 PS |
2875 | * |
2876 | * This function is using 'eu[0-9]+_' prefix for table names and parameters. | |
2877 | * | |
4f0c2d00 PS |
2878 | * @param object $context |
2879 | * @param string $withcapability | |
2880 | * @param int $groupid 0 means ignore groups, any other value limits the result by group id | |
df997f84 | 2881 | * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions |
4f0c2d00 PS |
2882 | * @return array list($sql, $params) |
2883 | */ | |
df997f84 | 2884 | function get_enrolled_sql($context, $withcapability = '', $groupid = 0, $onlyactive = false) { |
4f0c2d00 PS |
2885 | global $DB; |
2886 | ||
df997f84 PS |
2887 | // use unique prefix just in case somebody makes some SQL magic with the result |
2888 | static $i = 0; | |
2889 | $i++; | |
2890 | $prefix = 'eu'.$i.'_'; | |
4f0c2d00 PS |
2891 | |
2892 | // first find the course context | |
df997f84 | 2893 | $coursecontext = get_course_context($context); |
4f0c2d00 | 2894 | |
df997f84 | 2895 | $isfrontpage = ($coursecontext->instanceid == SITEID); |
4f0c2d00 | 2896 | |
df997f84 PS |
2897 | $joins = array(); |
2898 | $wheres = array(); | |
2899 | $params = array(); | |
4f0c2d00 PS |
2900 | |
2901 | list($contextids, $contextpaths) = get_context_info_list($context); | |
4f0c2d00 PS |
2902 | |
2903 | // get all relevant capability info for all roles | |
2904 | if ($withcapability) { | |
df997f84 PS |
2905 | list($incontexts, $cparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, 'ctx00'); |
2906 | $cparams['cap'] = $withcapability; | |
2907 | ||
2908 | $defs = array(); | |
2909 | $sql = "SELECT rc.id, rc.roleid, rc.permission, ctx.path | |
2910 | FROM {role_capabilities} rc | |
2911 | JOIN {context} ctx on rc.contextid = ctx.id | |
2912 | WHERE rc.contextid $incontexts AND rc.capability = :cap"; | |
2913 | $rcs = $DB->get_records_sql($sql, $cparams); | |
2914 | foreach ($rcs as $rc) { | |
2915 | $defs[$rc->path][$rc->roleid] = $rc->permission; | |
4f0c2d00 | 2916 | } |
4f0c2d00 | 2917 | |
df997f84 PS |
2918 | $access = array(); |
2919 | if (!empty($defs)) { | |
2920 | foreach ($contextpaths as $path) { | |
2921 | if (empty($defs[$path])) { | |
4f0c2d00 PS |
2922 | continue; |
2923 | } | |
df997f84 PS |
2924 | foreach($defs[$path] as $roleid => $perm) { |
2925 | if ($perm == CAP_PROHIBIT) { | |
2926 | $access[$roleid] = CAP_PROHIBIT; | |
2927 | continue; | |
2928 | } | |
2929 | if (!isset($access[$roleid])) { | |
2930 | $access[$roleid] = (int)$perm; | |
2931 | } | |
4f0c2d00 PS |
2932 | } |
2933 | } | |
2934 | } | |
4f0c2d00 | 2935 | |
df997f84 | 2936 | unset($defs); |
4f0c2d00 | 2937 | |
df997f84 PS |
2938 | // make lists of roles that are needed and prohibited |
2939 | $needed = array(); // one of these is enough | |
2940 | $prohibited = array(); // must not have any of these | |
2941 | if ($withcapability) { | |
2942 | foreach ($access as $roleid => $perm) { | |
2943 | if ($perm == CAP_PROHIBIT) { | |
2944 | unset($needed[$roleid]); | |
2945 | $prohibited[$roleid] = true; | |
2946 | } else if ($perm == CAP_ALLOW and empty($prohibited[$roleid])) { | |
2947 | $needed[$roleid] = true; | |
2948 | } | |
4f0c2d00 PS |
2949 | } |
2950 | } | |
4f0c2d00 | 2951 | |
df997f84 PS |
2952 | $defaultuserroleid = isset($CFG->defaultuserroleid) ? $CFG->defaultuserroleid : NULL; |
2953 | $defaultfrontpageroleid = isset($CFG->defaultfrontpageroleid) ? $CFG->defaultfrontpageroleid : NULL; | |
2515adf9 | 2954 | |
df997f84 | 2955 | $nobody = false; |
2515adf9 | 2956 | |
4f0c2d00 PS |
2957 | if ($isfrontpage) { |
2958 | if (!empty($prohibited[$defaultuserroleid]) or !empty($prohibited[$defaultfrontpageroleid])) { | |
2959 | $nobody = true; | |
2960 | } else if (!empty($neded[$defaultuserroleid]) or !empty($neded[$defaultfrontpageroleid])) { | |
2961 | // everybody not having prohibit has the capability | |
2962 | $needed = array(); | |
2963 | } else if (empty($needed)) { | |
2964 | $nobody = true; | |
2965 | } | |
2966 | } else { | |
2967 | if (!empty($prohibited[$defaultuserroleid])) { | |
2968 | $nobody = true; | |
2969 | } else if (!empty($neded[$defaultuserroleid])) { | |
2970 | // everybody not having prohibit has the capability | |
2971 | $needed = array(); | |
2972 | } else if (empty($needed)) { | |
2973 | $nobody = true; | |
0f161e1f | 2974 | } |
d74067e8 | 2975 | } |
4e5f3064 | 2976 | |
df997f84 PS |
2977 | if ($nobody) { |
2978 | // nobody can match so return some SQL that does not return any results | |
2979 | $wheres[] = "1 = 2"; | |
bbbf2d40 | 2980 | |
df997f84 | 2981 | } else { |
b963384f | 2982 | |
df997f84 PS |
2983 | if ($needed) { |
2984 | $ctxids = implode(',', $contextids); | |
2985 | $roleids = implode(',', array_keys($needed)); | |
2986 | $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))"; | |
2987 | } | |
b963384f | 2988 | |
df997f84 PS |
2989 | if ($prohibited) { |
2990 | $ctxids = implode(',', $contextids); | |
2991 | $roleids = implode(',', array_keys($prohibited)); | |
2992 | $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))"; | |
2993 | $wheres[] = "{$prefix}ra4 IS NULL"; | |
2994 | } | |
c4381ef5 | 2995 | |
df997f84 PS |
2996 | if ($groupid) { |
2997 | $joins[] = "JOIN {groups_members} {$prefix}gm ON ({$prefix}gm.userid = {$prefix}u.id AND {$prefix}gm.id = :{$prefix}gmid)"; | |
2998 | $params["{$prefix}gmid"] = $groupid; | |
2999 | } | |
3000 | } | |
c4381ef5 | 3001 | |
4f0c2d00 | 3002 | } |
eef868d1 | 3003 | |
df997f84 PS |
3004 | $wheres[] = "{$prefix}u.deleted = 0 AND {$prefix}u.username <> 'guest'"; |
3005 | ||
3006 | if ($isfrontpage) { | |
3007 | // all users are "enrolled" on the frontpage | |
3008 | } else { | |
3009 | $joins[] = "JOIN {user_enrolments} {$prefix}ue ON {$prefix}ue.userid = {$prefix}u.id"; | |
3010 | $joins[] = "JOIN {enrol} {$prefix}e ON ({$prefix}e.id = {$prefix}ue.enrolid AND {$prefix}e.courseid = :{$prefix}courseid)"; | |
3011 | $params[$prefix.'courseid'] = $coursecontext->instanceid; | |
3012 | ||
3013 | if ($onlyactive) { | |
3014 | $wheres[] = "{$prefix}ue.status = :{$prefix}active AND {$prefix}e.status = :{$prefix}enabled"; | |
3015 | $wheres[] = "{$prefix}ue.timestart < :{$prefix}now1 AND ({$prefix}ue.timeend = 0 OR {$prefix}ue.timeend > :{$prefix}now2)"; | |
3016 | $now = round(time(), -2); // rounding helps caching in DB | |
3017 | $params = array_merge($params, array($prefix.'enabled'=>ENROL_INSTANCE_ENABLED, | |
3018 | $prefix.'active'=>ENROL_USER_ACTIVE, | |
3019 | $prefix.'now1'=>$now, $prefix.'now2'=>$now)); | |
3020 | } | |
4f0c2d00 | 3021 | } |
a9d4ea78 | 3022 | |
4f0c2d00 PS |
3023 | $joins = implode("\n", $joins); |
3024 | $wheres = "WHERE ".implode(" AND ", $wheres); | |
eef868d1 | 3025 | |
df997f84 PS |
3026 | $sql = "SELECT DISTINCT {$prefix}u.id |
3027 | FROM {user} {$prefix}u | |
4f0c2d00 PS |
3028 | $joins |
3029 | $wheres"; | |
b963384f | 3030 | |
4f0c2d00 PS |
3031 | return array($sql, $params); |
3032 | } | |
3033 | ||
3034 | /** | |
3035 | * Returns list of users enrolled into course. | |
3036 | * @param object $context | |
3037 | * @param string $withcapability | |
3038 | * @param int $groupid 0 means ignore groups, any other value limits the result by group id | |
3039 | * @param string $userfields requested user record fields | |
3040 | * @param string $orderby | |
3041 | * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set). | |
3042 | * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set). | |
3043 | * @return array of user records | |
3044 | */ | |
3045 | function get_enrolled_users($context, $withcapability = '', $groupid = 0, $userfields = 'u.*', $orderby = '', $limitfrom = 0, $limitnum = 0) { | |
3046 | global $DB; | |
3047 | ||
3048 | list($esql, $params) = get_enrolled_sql($context, $withcapability, $groupid); | |
3049 | $sql = "SELECT $userfields | |
3050 | FROM {user} u | |
3051 | JOIN ($esql) je ON je.id = u.id | |
3052 | WHERE u.deleted = 0"; | |
3053 | ||
3054 | if ($orderby) { | |
3055 | $sql = "$sql ORDER BY $orderby"; | |
3056 | } else { | |
3057 | $sql = "$sql ORDER BY u.lastname ASC, u.firstname ASC"; | |
b963384f | 3058 | } |
3059 | ||
4f0c2d00 | 3060 | return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum); |
b963384f | 3061 | } |
3062 | ||
dcc779cc MD |
3063 | /** |
3064 | * Counts list of users enrolled into course (as per above function) | |
3065 | * @param object $context | |
3066 | * @param string $withcapability | |
3067 | * @param int $groupid 0 means ignore groups, any other value limits the result by group id | |
3068 | * @return array of user records | |
3069 | */ | |
3070 | function count_enrolled_users($context, $withcapability = '', $groupid = 0) { | |
3071 | global $DB; | |
3072 | ||
3073 | list($esql, $params) = get_enrolled_sql($context, $withcapability, $groupid); | |
3074 | $sql = "SELECT count(u.id) | |
3075 | FROM {user} u | |
3076 | JOIN ($esql) je ON je.id = u.id | |
3077 | WHERE u.deleted = 0"; | |
3078 | ||
3079 | return $DB->count_records_sql($sql, $params); | |
3080 | } | |
3081 | ||
3082 | ||
bbbf2d40 | 3083 | /** |
46808d7c | 3084 | * Loads the capability definitions for the component (from file). |
3085 | * | |
bbbf2d40 | 3086 | * Loads the capability definitions for the component (from file). If no |
3087 | * capabilities are defined for the component, we simply return an empty array. | |
46808d7c | 3088 | * |
cc3edaa4 | 3089 | * @global object |
17da2e6f | 3090 | * @param string $component full plugin name, examples: 'moodle', 'mod_forum' |
46808d7c | 3091 | * @return array array of capabilities |
bbbf2d40 | 3092 | */ |
3093 | function load_capability_def($component) { | |
17da2e6f | 3094 | $defpath = get_component_directory($component).'/db/access.php'; |
5ca3c838 | 3095 | |
bbbf2d40 | 3096 | $capabilities = array(); |
bbbf2d40 | 3097 | if (file_exists($defpath)) { |
dc268b2f | 3098 | require($defpath); |
0ac940b3 | 3099 | if (!empty(${$component.'_capabilities'})) { |
4f0c2d00 | 3100 | // BC capability array name |
0ac940b3 | 3101 | // since 2.0 we prefer $capabilities instead - it is easier to use and matches db/* files |
4f0c2d00 | 3102 | debugging('componentname_capabilities array is deprecated, please use capabilities array only in access.php files'); |
0ac940b3 PS |
3103 | $capabilities = ${$component.'_capabilities'}; |
3104 | } | |
bbbf2d40 | 3105 | } |
17da2e6f | 3106 | |
bbbf2d40 | 3107 | return $capabilities; |
3108 | } | |
3109 | ||
3110 | ||
3111 | /** | |
46808d7c | 3112 | * Gets the capabilities that have been cached in the database for this component. |
17da2e6f | 3113 | * @param string $component - examples: 'moodle', 'mod_forum' |
46808d7c | 3114 | * @return array array of capabilities |
bbbf2d40 | 3115 | */ |
3116 | function get_cached_capabilities($component='moodle') { | |
5a4e7398 | 3117 | global $DB; |
17da2e6f | 3118 | return $DB->get_records('capabilities', array('component'=>$component)); |
bbbf2d40 | 3119 | } |
3120 | ||
3562486b | 3121 | /** |
4f0c2d00 PS |
3122 | * Returns default capabilities for given role archetype. |
3123 | * @param string $archetype role archetype | |
3562486b | 3124 | * @return array |
3125 | */ | |
4f0c2d00 | 3126 | function get_default_capabilities($archetype) { |
5a4e7398 | 3127 | global $DB; |
4f0c2d00 PS |
3128 | |
3129 | if (!$archetype) { | |
3130 | return array(); | |
3131 | } | |
3132 | ||
3562486b | 3133 | $alldefs = array(); |
3134 | $defaults = array(); | |
3135 | $components = array(); | |
4f0c2d00 PS |
3136 | $allcaps = $DB->get_records('capabilities'); |
3137 | ||
3562486b | 3138 | foreach ($allcaps as $cap) { |
efe12f6c | 3139 | if (!in_array($cap->component, $components)) { |
3562486b | 3140 | $components[] = $cap->component; |
3141 | $alldefs = array_merge($alldefs, load_capability_def($cap->component)); | |
3142 | } | |
3143 | } | |
3144 | foreach($alldefs as $name=>$def) { | |
9e0c17fb SM |
3145 | // Use array 'archetypes if available. Only if not specified, use 'legacy'. |
3146 | if (isset($def['archetypes'])) { | |
3147 | if (isset($def['archetypes'][$archetype])) { | |
3148 | $defaults[$name] = $def['archetypes'][$archetype]; | |
3149 | } | |
3150 | // 'legacy' is for backward compatibility with 1.9 access.php | |
3151 | } else { | |
3152 | if (isset($def['legacy'][$archetype])) { | |
3153 | $defaults[$name] = $def['legacy'][$archetype]; | |
3154 | } | |