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