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