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