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