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