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