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