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