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