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