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