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