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