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