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