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