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