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