| [ Index ] |
PHP Cross Reference of Moodle 1.9.3 [Build 15-Oct-2008] |
[Summary view] [Print] [Text view]
1 <?php // $Id: accesslib.php,v 1.421.2.78 2008/09/24 17:26:27 stronk7 Exp $ 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 onwards 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 26 /** 27 * Public API vs internals 28 * ----------------------- 29 * 30 * General users probably only care about 31 * 32 * Context handling 33 * - get_context_instance() 34 * - get_context_instance_by_id() 35 * - get_parent_contexts() 36 * - get_child_contexts() 37 * 38 * Whether the user can do something... 39 * - has_capability() 40 * - require_capability() 41 * - require_login() (from moodlelib) 42 * 43 * What courses has this user access to? 44 * - get_user_courses_bycap() 45 * 46 * What users can do X in this context? 47 * - get_users_by_capability() 48 * 49 * Enrol/unenrol 50 * - enrol_into_course() 51 * - role_assign()/role_unassign() 52 * 53 * 54 * Advanced use 55 * - load_all_capabilities() 56 * - reload_all_capabilities() 57 * - $ACCESS global 58 * - has_capability_in_accessdata() 59 * - is_siteadmin() 60 * - get_user_access_sitewide() 61 * - load_subcontext() 62 * - get_role_access_bycontext() 63 * 64 * Name conventions 65 * ---------------- 66 * 67 * - "ctx" means context 68 * 69 * accessdata 70 * ---------- 71 * 72 * Access control data is held in the "accessdata" array 73 * which - for the logged-in user, will be in $USER->access 74 * 75 * For other users can be generated and passed around (but see 76 * the $ACCESS global). 77 * 78 * $accessdata is a multidimensional array, holding 79 * role assignments (RAs), role-capabilities-perm sets 80 * (role defs) and a list of courses we have loaded 81 * data for. 82 * 83 * Things are keyed on "contextpaths" (the path field of 84 * the context table) for fast walking up/down the tree. 85 * 86 * $accessdata[ra][$contextpath]= array($roleid) 87 * [$contextpath]= array($roleid) 88 * [$contextpath]= array($roleid) 89 * 90 * Role definitions are stored like this 91 * (no cap merge is done - so it's compact) 92 * 93 * $accessdata[rdef][$contextpath:$roleid][mod/forum:viewpost] = 1 94 * [mod/forum:editallpost] = -1 95 * [mod/forum:startdiscussion] = -1000 96 * 97 * See how has_capability_in_accessdata() walks up/down the tree. 98 * 99 * Normally - specially for the logged-in user, we only load 100 * rdef and ra down to the course level, but not below. This 101 * keeps accessdata small and compact. Below-the-course ra/rdef 102 * are loaded as needed. We keep track of which courses we 103 * have loaded ra/rdef in 104 * 105 * $accessdata[loaded] = array($contextpath, $contextpath) 106 * 107 * Stale accessdata 108 * ---------------- 109 * 110 * For the logged-in user, accessdata is long-lived. 111 * 112 * On each pageload we load $DIRTYPATHS which lists 113 * context paths affected by changes. Any check at-or-below 114 * a dirty context will trigger a transparent reload of accessdata. 115 * 116 * Changes at the sytem level will force the reload for everyone. 117 * 118 * Default role caps 119 * ----------------- 120 * The default role assignment is not in the DB, so we 121 * add it manually to accessdata. 122 * 123 * This means that functions that work directly off the 124 * DB need to ensure that the default role caps 125 * are dealt with appropriately. 126 * 127 */ 128 129 require_once $CFG->dirroot.'/lib/blocklib.php'; 130 131 // permission definitions 132 define('CAP_INHERIT', 0); 133 define('CAP_ALLOW', 1); 134 define('CAP_PREVENT', -1); 135 define('CAP_PROHIBIT', -1000); 136 137 // context definitions 138 define('CONTEXT_SYSTEM', 10); 139 define('CONTEXT_USER', 30); 140 define('CONTEXT_COURSECAT', 40); 141 define('CONTEXT_COURSE', 50); 142 define('CONTEXT_GROUP', 60); 143 define('CONTEXT_MODULE', 70); 144 define('CONTEXT_BLOCK', 80); 145 146 // capability risks - see http://docs.moodle.org/en/Development:Hardening_new_Roles_system 147 define('RISK_MANAGETRUST', 0x0001); 148 define('RISK_CONFIG', 0x0002); 149 define('RISK_XSS', 0x0004); 150 define('RISK_PERSONAL', 0x0008); 151 define('RISK_SPAM', 0x0010); 152 define('RISK_DATALOSS', 0x0020); 153 154 // rolename displays 155 define('ROLENAME_ORIGINAL', 0);// the name as defined in the role definition 156 define('ROLENAME_ALIAS', 1); // the name as defined by a role alias 157 define('ROLENAME_BOTH', 2); // Both, like this: Role alias (Original) 158 159 require_once($CFG->dirroot.'/group/lib.php'); 160 161 $context_cache = array(); // Cache of all used context objects for performance (by level and instance) 162 $context_cache_id = array(); // Index to above cache by id 163 164 $DIRTYCONTEXTS = null; // dirty contexts cache 165 $ACCESS = array(); // cache of caps for cron user switching and has_capability for other users (==not $USER) 166 $RDEFS = array(); // role definitions cache - helps a lot with mem usage in cron 167 168 function get_role_context_caps($roleid, $context) { 169 //this is really slow!!!! - do not use above course context level! 170 $result = array(); 171 $result[$context->id] = array(); 172 173 // first emulate the parent context capabilities merging into context 174 $searchcontexts = array_reverse(get_parent_contexts($context)); 175 array_push($searchcontexts, $context->id); 176 foreach ($searchcontexts as $cid) { 177 if ($capabilities = get_records_select('role_capabilities', "roleid = $roleid AND contextid = $cid")) { 178 foreach ($capabilities as $cap) { 179 if (!array_key_exists($cap->capability, $result[$context->id])) { 180 $result[$context->id][$cap->capability] = 0; 181 } 182 $result[$context->id][$cap->capability] += $cap->permission; 183 } 184 } 185 } 186 187 // now go through the contexts bellow given context 188 $searchcontexts = array_keys(get_child_contexts($context)); 189 foreach ($searchcontexts as $cid) { 190 if ($capabilities = get_records_select('role_capabilities', "roleid = $roleid AND contextid = $cid")) { 191 foreach ($capabilities as $cap) { 192 if (!array_key_exists($cap->contextid, $result)) { 193 $result[$cap->contextid] = array(); 194 } 195 $result[$cap->contextid][$cap->capability] = $cap->permission; 196 } 197 } 198 } 199 200 return $result; 201 } 202 203 /** 204 * Gets the accessdata for role "sitewide" 205 * (system down to course) 206 * 207 * @return array 208 */ 209 function get_role_access($roleid, $accessdata=NULL) { 210 211 global $CFG; 212 213 /* Get it in 1 cheap DB query... 214 * - relevant role caps at the root and down 215 * to the course level - but not below 216 */ 217 if (is_null($accessdata)) { 218 $accessdata = array(); // named list 219 $accessdata['ra'] = array(); 220 $accessdata['rdef'] = array(); 221 $accessdata['loaded'] = array(); 222 } 223 224 // 225 // Overrides for the role IN ANY CONTEXTS 226 // down to COURSE - not below - 227 // 228 $sql = "SELECT ctx.path, 229 rc.capability, rc.permission 230 FROM {$CFG->prefix}context ctx 231 JOIN {$CFG->prefix}role_capabilities rc 232 ON rc.contextid=ctx.id 233 WHERE rc.roleid = {$roleid} 234 AND ctx.contextlevel <= ".CONTEXT_COURSE." 235 ORDER BY ctx.depth, ctx.path"; 236 237 // we need extra caching in cron only 238 if (defined('FULLME') and FULLME === 'cron') { 239 static $cron_cache = array(); 240 241 if (!isset($cron_cache[$roleid])) { 242 $cron_cache[$roleid] = array(); 243 if ($rs = get_recordset_sql($sql)) { 244 while ($rd = rs_fetch_next_record($rs)) { 245 $cron_cache[$roleid][] = $rd; 246 } 247 rs_close($rs); 248 } 249 } 250 251 foreach ($cron_cache[$roleid] as $rd) { 252 $k = "{$rd->path}:{$roleid}"; 253 $accessdata['rdef'][$k][$rd->capability] = $rd->permission; 254 } 255 256 } else { 257 if ($rs = get_recordset_sql($sql)) { 258 while ($rd = rs_fetch_next_record($rs)) { 259 $k = "{$rd->path}:{$roleid}"; 260 $accessdata['rdef'][$k][$rd->capability] = $rd->permission; 261 } 262 unset($rd); 263 rs_close($rs); 264 } 265 } 266 267 return $accessdata; 268 } 269 270 /** 271 * Gets the accessdata for role "sitewide" 272 * (system down to course) 273 * 274 * @return array 275 */ 276 function get_default_frontpage_role_access($roleid, $accessdata=NULL) { 277 278 global $CFG; 279 280 $frontpagecontext = get_context_instance(CONTEXT_COURSE, SITEID); 281 $base = '/'. SYSCONTEXTID .'/'. $frontpagecontext->id; 282 283 // 284 // Overrides for the role in any contexts related to the course 285 // 286 $sql = "SELECT ctx.path, 287 rc.capability, rc.permission 288 FROM {$CFG->prefix}context ctx 289 JOIN {$CFG->prefix}role_capabilities rc 290 ON rc.contextid=ctx.id 291 WHERE rc.roleid = {$roleid} 292 AND (ctx.id = ".SYSCONTEXTID." OR ctx.path LIKE '$base/%') 293 AND ctx.contextlevel <= ".CONTEXT_COURSE." 294 ORDER BY ctx.depth, ctx.path"; 295 296 if ($rs = get_recordset_sql($sql)) { 297 while ($rd = rs_fetch_next_record($rs)) { 298 $k = "{$rd->path}:{$roleid}"; 299 $accessdata['rdef'][$k][$rd->capability] = $rd->permission; 300 } 301 unset($rd); 302 rs_close($rs); 303 } 304 305 return $accessdata; 306 } 307 308 309 /** 310 * Get the default guest role 311 * @return object role 312 */ 313 function get_guest_role() { 314 global $CFG; 315 316 if (empty($CFG->guestroleid)) { 317 if ($roles = get_roles_with_capability('moodle/legacy:guest', CAP_ALLOW)) { 318 $guestrole = array_shift($roles); // Pick the first one 319 set_config('guestroleid', $guestrole->id); 320 return $guestrole; 321 } else { 322 debugging('Can not find any guest role!'); 323 return false; 324 } 325 } else { 326 if ($guestrole = get_record('role','id', $CFG->guestroleid)) { 327 return $guestrole; 328 } else { 329 //somebody is messing with guest roles, remove incorrect setting and try to find a new one 330 set_config('guestroleid', ''); 331 return get_guest_role(); 332 } 333 } 334 } 335 336 /** 337 * This function returns whether the current user has the capability of performing a function 338 * For example, we can do has_capability('mod/forum:replypost',$context) in forum 339 * @param string $capability - name of the capability (or debugcache or clearcache) 340 * @param object $context - a context object (record from context table) 341 * @param integer $userid - a userid number, empty if current $USER 342 * @param bool $doanything - if false, ignore do anything 343 * @return bool 344 */ 345 function has_capability($capability, $context, $userid=NULL, $doanything=true) { 346 global $USER, $ACCESS, $CFG, $DIRTYCONTEXTS; 347 348 // the original $CONTEXT here was hiding serious errors 349 // for security reasons do not reuse previous context 350 if (empty($context)) { 351 debugging('Incorrect context specified'); 352 return false; 353 } 354 355 /// Some sanity checks 356 if (debugging('',DEBUG_DEVELOPER)) { 357 static $capsnames = null; // one request per page only 358 359 if (is_null($capsnames)) { 360 if ($caps = get_records('capabilities', '', '', '', 'id, name')) { 361 $capsnames = array(); 362 foreach ($caps as $cap) { 363 $capsnames[$cap->name] = true; 364 } 365 } 366 } 367 if ($capsnames) { // ignore if can not fetch caps 368 if (!isset($capsnames[$capability])) { 369 debugging('Capability "'.$capability.'" was not found! This should be fixed in code.'); 370 } 371 } 372 if (!is_bool($doanything)) { 373 debugging('Capability parameter "doanything" is wierd ("'.$doanything.'"). This should be fixed in code.'); 374 } 375 } 376 377 if (empty($userid)) { // we must accept null, 0, '0', '' etc. in $userid 378 $userid = $USER->id; 379 } 380 381 if (is_null($context->path) or $context->depth == 0) { 382 //this should not happen 383 $contexts = array(SYSCONTEXTID, $context->id); 384 $context->path = '/'.SYSCONTEXTID.'/'.$context->id; 385 debugging('Context id '.$context->id.' does not have valid path, please use build_context_path()', DEBUG_DEVELOPER); 386 387 } else { 388 $contexts = explode('/', $context->path); 389 array_shift($contexts); 390 } 391 392 if (defined('FULLME') && FULLME === 'cron' && !isset($USER->access)) { 393 // In cron, some modules setup a 'fake' $USER, 394 // ensure we load the appropriate accessdata. 395 if (isset($ACCESS[$userid])) { 396 $DIRTYCONTEXTS = NULL; //load fresh dirty contexts 397 } else { 398 load_user_accessdata($userid); 399 $DIRTYCONTEXTS = array(); 400 } 401 $USER->access = $ACCESS[$userid]; 402 403 } else if ($USER->id == $userid && !isset($USER->access)) { 404 // caps not loaded yet - better to load them to keep BC with 1.8 405 // not-logged-in user or $USER object set up manually first time here 406 load_all_capabilities(); 407 $ACCESS = array(); // reset the cache for other users too, the dirty contexts are empty now 408 $RDEFS = array(); 409 } 410 411 // Load dirty contexts list if needed 412 if (!isset($DIRTYCONTEXTS)) { 413 if (isset($USER->access['time'])) { 414 $DIRTYCONTEXTS = get_dirty_contexts($USER->access['time']); 415 } 416 else { 417 $DIRTYCONTEXTS = array(); 418 } 419 } 420 421 // Careful check for staleness... 422 if (count($DIRTYCONTEXTS) !== 0 and is_contextpath_dirty($contexts, $DIRTYCONTEXTS)) { 423 // reload all capabilities - preserving loginas, roleswitches, etc 424 // and then cleanup any marks of dirtyness... at least from our short 425 // term memory! :-) 426 $ACCESS = array(); 427 $RDEFS = array(); 428 429 if (defined('FULLME') && FULLME === 'cron') { 430 load_user_accessdata($userid); 431 $USER->access = $ACCESS[$userid]; 432 $DIRTYCONTEXTS = array(); 433 434 } else { 435 reload_all_capabilities(); 436 } 437 } 438 439 // divulge how many times we are called 440 //// error_log("has_capability: id:{$context->id} path:{$context->path} userid:$userid cap:$capability"); 441 442 if ($USER->id == $userid) { // we must accept strings and integers in $userid 443 // 444 // For the logged in user, we have $USER->access 445 // which will have all RAs and caps preloaded for 446 // course and above contexts. 447 // 448 // Contexts below courses && contexts that do not 449 // hang from courses are loaded into $USER->access 450 // on demand, and listed in $USER->access[loaded] 451 // 452 if ($context->contextlevel <= CONTEXT_COURSE) { 453 // Course and above are always preloaded 454 return has_capability_in_accessdata($capability, $context, $USER->access, $doanything); 455 } 456 // Load accessdata for below-the-course contexts 457 if (!path_inaccessdata($context->path,$USER->access)) { 458 // error_log("loading access for context {$context->path} for $capability at {$context->contextlevel} {$context->id}"); 459 // $bt = debug_backtrace(); 460 // error_log("bt {$bt[0]['file']} {$bt[0]['line']}"); 461 load_subcontext($USER->id, $context, $USER->access); 462 } 463 return has_capability_in_accessdata($capability, $context, $USER->access, $doanything); 464 } 465 466 if (!isset($ACCESS[$userid])) { 467 load_user_accessdata($userid); 468 } 469 if ($context->contextlevel <= CONTEXT_COURSE) { 470 // Course and above are always preloaded 471 return has_capability_in_accessdata($capability, $context, $ACCESS[$userid], $doanything); 472 } 473 // Load accessdata for below-the-course contexts as needed 474 if (!path_inaccessdata($context->path, $ACCESS[$userid])) { 475 // error_log("loading access for context {$context->path} for $capability at {$context->contextlevel} {$context->id}"); 476 // $bt = debug_backtrace(); 477 // error_log("bt {$bt[0]['file']} {$bt[0]['line']}"); 478 load_subcontext($userid, $context, $ACCESS[$userid]); 479 } 480 return has_capability_in_accessdata($capability, $context, $ACCESS[$userid], $doanything); 481 } 482 483 /** 484 * This function returns whether the current user has any of the capabilities in the 485 * $capabilities array. This is a simple wrapper around has_capability for convinience. 486 * 487 * There are probably tricks that could be done to improve the performance here, for example, 488 * check the capabilities that are already cached first. 489 * 490 * @param array $capabilities - an array of capability names. 491 * @param object $context - a context object (record from context table) 492 * @param integer $userid - a userid number, empty if current $USER 493 * @param bool $doanything - if false, ignore do anything 494 * @return bool 495 */ 496 function has_any_capability($capabilities, $context, $userid=NULL, $doanything=true) { 497 foreach ($capabilities as $capability) { 498 if (has_capability($capability, $context, $userid, $doanything)) { 499 return true; 500 } 501 } 502 return false; 503 } 504 505 /** 506 * Uses 1 DB query to answer whether a user is an admin at the sitelevel. 507 * It depends on DB schema >=1.7 but does not depend on the new datastructures 508 * in v1.9 (context.path, or $USER->access) 509 * 510 * Will return true if the userid has any of 511 * - moodle/site:config 512 * - moodle/legacy:admin 513 * - moodle/site:doanything 514 * 515 * @param int $userid 516 * @returns bool $isadmin 517 */ 518 function is_siteadmin($userid) { 519 global $CFG; 520 521 $sql = "SELECT SUM(rc.permission) 522 FROM " . $CFG->prefix . "role_capabilities rc 523 JOIN " . $CFG->prefix . "context ctx 524 ON ctx.id=rc.contextid 525 JOIN " . $CFG->prefix . "role_assignments ra 526 ON ra.roleid=rc.roleid AND ra.contextid=ctx.id 527 WHERE ctx.contextlevel=10 528 AND ra.userid={$userid} 529 AND rc.capability IN ('moodle/site:config', 'moodle/legacy:admin', 'moodle/site:doanything') 530 GROUP BY rc.capability 531 HAVING SUM(rc.permission) > 0"; 532 533 $isadmin = record_exists_sql($sql); 534 return $isadmin; 535 } 536 537 function get_course_from_path ($path) { 538 // assume that nothing is more than 1 course deep 539 if (preg_match('!^(/.+)/\d+$!', $path, $matches)) { 540 return $matches[1]; 541 } 542 return false; 543 } 544 545 function path_inaccessdata($path, $accessdata) { 546 547 // assume that contexts hang from sys or from a course 548 // this will only work well with stuff that hangs from a course 549 if (in_array($path, $accessdata['loaded'], true)) { 550 // error_log("found it!"); 551 return true; 552 } 553 $base = '/' . SYSCONTEXTID; 554 while (preg_match('!^(/.+)/\d+$!', $path, $matches)) { 555 $path = $matches[1]; 556 if ($path === $base) { 557 return false; 558 } 559 if (in_array($path, $accessdata['loaded'], true)) { 560 return true; 561 } 562 } 563 return false; 564 } 565 566 /** 567 * Walk the accessdata array and return true/false. 568 * Deals with prohibits, roleswitching, aggregating 569 * capabilities, etc. 570 * 571 * The main feature of here is being FAST and with no 572 * side effects. 573 * 574 * Notes: 575 * 576 * Switch Roles exits early 577 * ----------------------- 578 * cap checks within a switchrole need to exit early 579 * in our bottom up processing so they don't "see" that 580 * there are real RAs that can do all sorts of things. 581 * 582 * Switch Role merges with default role 583 * ------------------------------------ 584 * If you are a teacher in course X, you have at least 585 * teacher-in-X + defaultloggedinuser-sitewide. So in the 586 * course you'll have techer+defaultloggedinuser. 587 * We try to mimic that in switchrole. 588 * 589 * Local-most role definition and role-assignment wins 590 * --------------------------------------------------- 591 * So if the local context has said 'allow', it wins 592 * over a high-level context that says 'deny'. 593 * This is applied when walking rdefs, and RAs. 594 * Only at the same context the values are SUM()med. 595 * 596 * The exception is CAP_PROHIBIT. 597 * 598 * "Guest default role" exception 599 * ------------------------------ 600 * 601 * See MDL-7513 and $ignoreguest below for details. 602 * 603 * The rule is that 604 * 605 * IF we are being asked about moodle/legacy:guest 606 * OR moodle/course:view 607 * FOR a real, logged-in user 608 * AND we reached the top of the path in ra and rdef 609 * AND that role has moodle/legacy:guest === 1... 610 * THEN we act as if we hadn't seen it. 611 * 612 * 613 * To Do: 614 * 615 * - Document how it works 616 * - Rewrite in ASM :-) 617 * 618 */ 619 function has_capability_in_accessdata($capability, $context, $accessdata, $doanything) { 620 621 global $CFG; 622 623 $path = $context->path; 624 625 // build $contexts as a list of "paths" of the current 626 // contexts and parents with the order top-to-bottom 627 $contexts = array($path); 628 while (preg_match('!^(/.+)/\d+$!', $path, $matches)) { 629 $path = $matches[1]; 630 array_unshift($contexts, $path); 631 } 632 633 $ignoreguest = false; 634 if (isset($accessdata['dr']) 635 && ($capability == 'moodle/course:view' 636 || $capability == 'moodle/legacy:guest')) { 637 // At the base, ignore rdefs where moodle/legacy:guest 638 // is set 639 $ignoreguest = $accessdata['dr']; 640 } 641 642 // Coerce it to an int 643 $CAP_PROHIBIT = (int)CAP_PROHIBIT; 644 645 $cc = count($contexts); 646 647 $can = 0; 648 $capdepth = 0; 649 650 // 651 // role-switches loop 652 // 653 if (isset($accessdata['rsw'])) { 654 // check for isset() is fast 655 // empty() is slow... 656 if (empty($accessdata['rsw'])) { 657 unset($accessdata['rsw']); // keep things fast and unambiguous 658 break; 659 } 660 // From the bottom up... 661 for ($n=$cc-1;$n>=0;$n--) { 662 $ctxp = $contexts[$n]; 663 if (isset($accessdata['rsw'][$ctxp])) { 664 // Found a switchrole assignment 665 // check for that role _plus_ the default user role 666 $ras = array($accessdata['rsw'][$ctxp],$CFG->defaultuserroleid); 667 for ($rn=0;$rn<2;$rn++) { 668 $roleid = (int)$ras[$rn]; 669 // Walk the path for capabilities 670 // from the bottom up... 671 for ($m=$cc-1;$m>=0;$m--) { 672 $capctxp = $contexts[$m]; 673 if (isset($accessdata['rdef']["{$capctxp}:$roleid"][$capability])) { 674 $perm = (int)$accessdata['rdef']["{$capctxp}:$roleid"][$capability]; 675 676 // The most local permission (first to set) wins 677 // the only exception is CAP_PROHIBIT 678 if ($can === 0) { 679 $can = $perm; 680 } elseif ($perm === $CAP_PROHIBIT) { 681 $can = $perm; 682 break; 683 } 684 } 685 } 686 } 687 // As we are dealing with a switchrole, 688 // we return _here_, do _not_ walk up 689 // the hierarchy any further 690 if ($can < 1) { 691 if ($doanything) { 692 // didn't find it as an explicit cap, 693 // but maybe the user candoanything in this context... 694 return has_capability_in_accessdata('moodle/site:doanything', $context, $accessdata, false); 695 } else { 696 return false; 697 } 698 } else { 699 return true; 700 } 701 702 } 703 } 704 } 705 706 // 707 // Main loop for normal RAs 708 // From the bottom up... 709 // 710 for ($n=$cc-1;$n>=0;$n--) { 711 $ctxp = $contexts[$n]; 712 if (isset($accessdata['ra'][$ctxp])) { 713 // Found role assignments on this leaf 714 $ras = $accessdata['ra'][$ctxp]; 715 716 $rc = count($ras); 717 $ctxcan = 0; 718 $ctxcapdepth = 0; 719 for ($rn=0;$rn<$rc;$rn++) { 720 $roleid = (int)$ras[$rn]; 721 $rolecan = 0; 722 $rolecapdepth = 0; 723 // Walk the path for capabilities 724 // from the bottom up... 725 for ($m=$cc-1;$m>=0;$m--) { 726 $capctxp = $contexts[$m]; 727 // ignore some guest caps 728 // at base ra and rdef 729 if ($ignoreguest == $roleid 730 && $n === 0 731 && $m === 0 732 && isset($accessdata['rdef']["{$capctxp}:$roleid"]['moodle/legacy:guest']) 733 && $accessdata['rdef']["{$capctxp}:$roleid"]['moodle/legacy:guest'] > 0) { 734 continue; 735 } 736 if (isset($accessdata['rdef']["{$capctxp}:$roleid"][$capability])) { 737 $perm = (int)$accessdata['rdef']["{$capctxp}:$roleid"][$capability]; 738 // The most local permission (first to set) wins 739 // the only exception is CAP_PROHIBIT 740 if ($rolecan === 0) { 741 $rolecan = $perm; 742 $rolecapdepth = $m; 743 } elseif ($perm === $CAP_PROHIBIT) { 744 $rolecan = $perm; 745 $rolecapdepth = $m; 746 break; 747 } 748 } 749 } 750 // Rules for RAs at the same context... 751 // - prohibits always wins 752 // - permissions at the same ctxlevel & capdepth are added together 753 // - deeper capdepth wins 754 if ($ctxcan === $CAP_PROHIBIT || $rolecan === $CAP_PROHIBIT) { 755 $ctxcan = $CAP_PROHIBIT; 756 $ctxcapdepth = 0; 757 } elseif ($ctxcapdepth === $rolecapdepth) { 758 $ctxcan += $rolecan; 759 } elseif ($ctxcapdepth < $rolecapdepth) { 760 $ctxcan = $rolecan; 761 $ctxcapdepth = $rolecapdepth; 762 } else { // ctxcaptdepth is deeper 763 // rolecap ignored 764 } 765 } 766 // The most local RAs with a defined 767 // permission ($ctxcan) win, except 768 // for CAP_PROHIBIT 769 // NOTE: If we want the deepest RDEF to 770 // win regardless of the depth of the RA, 771 // change the elseif below to read 772 // ($can === 0 || $capdepth < $ctxcapdepth) { 773 if ($ctxcan === $CAP_PROHIBIT) { 774 $can = $ctxcan; 775 break; 776 } elseif ($can === 0) { // see note above 777 $can = $ctxcan; 778 $capdepth = $ctxcapdepth; 779 } 780 } 781 } 782 783 if ($can < 1) { 784 if ($doanything) { 785 // didn't find it as an explicit cap, 786 // but maybe the user candoanything in this context... 787 return has_capability_in_accessdata('moodle/site:doanything', $context, $accessdata, false); 788 } else { 789 return false; 790 } 791 } else { 792 return true; 793 } 794 795 } 796 797 function aggregate_roles_from_accessdata($context, $accessdata) { 798 799 $path = $context->path; 800 801 // build $contexts as a list of "paths" of the current 802 // contexts and parents with the order top-to-bottom 803 $contexts = array($path); 804 while (preg_match('!^(/.+)/\d+$!', $path, $matches)) { 805 $path = $matches[1]; 806 array_unshift($contexts, $path); 807 } 808 809 $cc = count($contexts); 810 811 $roles = array(); 812 // From the bottom up... 813 for ($n=$cc-1;$n>=0;$n--) { 814 $ctxp = $contexts[$n]; 815 if (isset($accessdata['ra'][$ctxp]) && count($accessdata['ra'][$ctxp])) { 816 // Found assignments on this leaf 817 $addroles = $accessdata['ra'][$ctxp]; 818 $roles = array_merge($roles, $addroles); 819 } 820 } 821 822 return array_unique($roles); 823 } 824 825 /** 826 * This is an easy to use function, combining has_capability() with require_course_login(). 827 * And will call those where needed. 828 * 829 * It checks for a capability assertion being true. If it isn't 830 * then the page is terminated neatly with a standard error message. 831 * 832 * If the user is not logged in, or is using 'guest' access or other special "users, 833 * it provides a logon prompt. 834 * 835 * @param string $capability - name of the capability 836 * @param object $context - a context object (record from context table) 837 * @param integer $userid - a userid number 838 * @param bool $doanything - if false, ignore do anything 839 * @param string $errorstring - an errorstring 840 * @param string $stringfile - which stringfile to get it from 841 */ 842 function require_capability($capability, $context, $userid=NULL, $doanything=true, 843 $errormessage='nopermissions', $stringfile='') { 844 845 global $USER, $CFG; 846 847 /* Empty $userid means current user, if the current user is not logged in, 848 * then make sure they are (if needed). 849 * Originally there was a check for loaded permissions - it is not needed here. 850 * Context is now required parameter, the cached $CONTEXT was only hiding errors. 851 */ 852 $errorlink = ''; 853 854 if (empty($userid)) { 855 if ($context->contextlevel == CONTEXT_COURSE) { 856 require_login($context->instanceid); 857 858 } else if ($context->contextlevel == CONTEXT_MODULE) { 859 if (!$cm = get_record('course_modules', 'id', $context->instanceid)) { 860 error('Incorrect module'); 861 } 862 if (!$course = get_record('course', 'id', $cm->course)) { 863 error('Incorrect course.'); 864 } 865 require_course_login($course, true, $cm); 866 $errorlink = $CFG->wwwroot.'/course/view.php?id='.$cm->course; 867 868 } else if ($context->contextlevel == CONTEXT_SYSTEM) { 869 if (!empty($CFG->forcelogin)) { 870 require_login(); 871 } 872 873 } else { 874 require_login(); 875 } 876 } 877 878 /// OK, if they still don't have the capability then print a nice error message 879 880 if (!has_capability($capability, $context, $userid, $doanything)) { 881 $capabilityname = get_capability_string($capability); 882 print_error($errormessage, $stringfile, $errorlink, $capabilityname); 883 } 884 } 885 886 /** 887 * Get an array of courses (with magic extra bits) 888 * where the accessdata and in DB enrolments show 889 * that the cap requested is available. 890 * 891 * The main use is for get_my_courses(). 892 * 893 * Notes 894 * 895 * - $fields is an array of fieldnames to ADD 896 * so name the fields you really need, which will 897 * be added and uniq'd 898 * 899 * - the course records have $c->context which is a fully 900 * valid context object. Saves you a query per course! 901 * 902 * - the course records have $c->categorypath to make 903 * category lookups cheap 904 * 905 * - current implementation is split in - 906 * 907 * - if the user has the cap systemwide, stupidly 908 * grab *every* course for a capcheck. This eats 909 * a TON of bandwidth, specially on large sites 910 * with separate DBs... 911 * 912 * - otherwise, fetch "likely" courses with a wide net 913 * that should get us _cheaply_ at least the courses we need, and some 914 * we won't - we get courses that... 915 * - are in a category where user has the cap 916 * - or where use has a role-assignment (any kind) 917 * - or where the course has an override on for this cap 918 * 919 * - walk the courses recordset checking the caps oneach one 920 * the checks are all in memory and quite fast 921 * (though we could implement a specialised variant of the 922 * has_capability_in_accessdata() code to speed it up) 923 * 924 * @param string $capability - name of the capability 925 * @param array $accessdata - accessdata session array 926 * @param bool $doanything - if false, ignore do anything 927 * @param string $sort - sorting fields - prefix each fieldname with "c." 928 * @param array $fields - additional fields you are interested in... 929 * @param int $limit - set if you want to limit the number of courses 930 * @return array $courses - ordered array of course objects - see notes above 931 * 932 */ 933 function get_user_courses_bycap($userid, $cap, $accessdata, $doanything, $sort='c.sortorder ASC', $fields=NULL, $limit=0) { 934 935 global $CFG; 936 937 // Slim base fields, let callers ask for what they need... 938 $basefields = array('id', 'sortorder', 'shortname', 'idnumber'); 939 940 if (!is_null($fields)) { 941 $fields = array_merge($basefields, $fields); 942 $fields = array_unique($fields); 943 } else { 944 $fields = $basefields; 945 } 946 $coursefields = 'c.' .implode(',c.', $fields); 947 948 $sort = trim($sort); 949 if ($sort !== '') { 950 $sort = "ORDER BY $sort"; 951 } 952 953 $sysctx = get_context_instance(CONTEXT_SYSTEM); 954 if (has_capability_in_accessdata($cap, $sysctx, $accessdata, $doanything)) { 955 // 956 // Apparently the user has the cap sitewide, so walk *every* course 957 // (the cap checks are moderately fast, but this moves massive bandwidth w the db) 958 // Yuck. 959 // 960 $sql = "SELECT $coursefields, 961 ctx.id AS ctxid, ctx.path AS ctxpath, 962 ctx.depth AS ctxdepth, ctx.contextlevel AS ctxlevel, 963 cc.path AS categorypath 964 FROM {$CFG->prefix}course c 965 JOIN {$CFG->prefix}course_categories cc 966 ON c.category=cc.id 967 JOIN {$CFG->prefix}context ctx 968 ON (c.id=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE.") 969 $sort "; 970 $rs = get_recordset_sql($sql); 971 } else { 972 // 973 // narrow down where we have the caps to a few contexts 974 // this will be a combination of 975 // - courses where user has an explicit enrolment 976 // - courses that have an override (any status) on that capability 977 // - categories where user has the rights (granted status) on that capability 978 // 979 $sql = "SELECT ctx.* 980 FROM {$CFG->prefix}context ctx 981 WHERE ctx.contextlevel=".CONTEXT_COURSECAT." 982 ORDER BY ctx.depth"; 983 $rs = get_recordset_sql($sql); 984 $catpaths = array(); 985 while ($catctx = rs_fetch_next_record($rs)) { 986 if ($catctx->path != '' 987 && has_capability_in_accessdata($cap, $catctx, $accessdata, $doanything)) { 988 $catpaths[] = $catctx->path; 989 } 990 } 991 rs_close($rs); 992 $catclause = ''; 993 if (count($catpaths)) { 994 $cc = count($catpaths); 995 for ($n=0;$n<$cc;$n++) { 996 $catpaths[$n] = "ctx.path LIKE '{$catpaths[$n]}/%'"; 997 } 998 $catclause = 'WHERE (' . implode(' OR ', $catpaths) .')'; 999 } 1000 unset($catpaths); 1001 1002 $capany = ''; 1003 if ($doanything) { 1004 $capany = " OR rc.capability='moodle/site:doanything'"; 1005 } 1006 1007 /// UNION 3 queries: 1008 /// - user role assignments in courses 1009 /// - user capability (override - any status) in courses 1010 /// - user right (granted status) in categories (optionally executed) 1011 /// Enclosing the 3-UNION into an inline_view to avoid column names conflict and making the ORDER BY cross-db 1012 /// and to allow selection of TEXT columns in the query (MSSQL and Oracle limitation). MDL-16209 1013 $sql = " 1014 SELECT $coursefields, ctxid, ctxpath, ctxdepth, ctxlevel, categorypath 1015 FROM ( 1016 SELECT c.id, 1017 ctx.id AS ctxid, ctx.path AS ctxpath, 1018 ctx.depth AS ctxdepth, ctx.contextlevel AS ctxlevel, 1019 cc.path AS categorypath 1020 FROM {$CFG->prefix}course c 1021 JOIN {$CFG->prefix}course_categories cc 1022 ON c.category=cc.id 1023 JOIN {$CFG->prefix}context ctx 1024 ON (c.id=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE.") 1025 JOIN {$CFG->prefix}role_assignments ra 1026 ON (ra.contextid=ctx.id AND ra.userid=$userid) 1027 UNION 1028 SELECT c.id, 1029 ctx.id AS ctxid, ctx.path AS ctxpath, 1030 ctx.depth AS ctxdepth, ctx.contextlevel AS ctxlevel, 1031 cc.path AS categorypath 1032 FROM {$CFG->prefix}course c 1033 JOIN {$CFG->prefix}course_categories cc 1034 ON c.category=cc.id 1035 JOIN {$CFG->prefix}context ctx 1036 ON (c.id=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE.") 1037 JOIN {$CFG->prefix}role_capabilities rc 1038 ON (rc.contextid=ctx.id AND (rc.capability='$cap' $capany)) "; 1039 1040 if (!empty($catclause)) { /// If we have found the right in categories, add child courses here too 1041 $sql .= " 1042 UNION 1043 SELECT c.id, 1044 ctx.id AS ctxid, ctx.path AS ctxpath, 1045 ctx.depth AS ctxdepth, ctx.contextlevel AS ctxlevel, 1046 cc.path AS categorypath 1047 FROM {$CFG->prefix}course c 1048 JOIN {$CFG->prefix}course_categories cc 1049 ON c.category=cc.id 1050 JOIN {$CFG->prefix}context ctx 1051 ON (c.id=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE.") 1052 $catclause"; 1053 } 1054 1055 /// Close the inline_view and join with courses table to get requested $coursefields 1056 $sql .= " 1057 ) inline_view 1058 INNER JOIN {$CFG->prefix}course c 1059 ON inline_view.id = c.id"; 1060 1061 /// To keep cross-db we need to strip any prefix in the ORDER BY clause for queries using UNION 1062 $sql .= " 1063 " . preg_replace('/[a-z]+\./i', '', $sort); /// Add ORDER BY clause 1064 1065 $rs = get_recordset_sql($sql); 1066 } 1067 1068 /// Confirm rights (granted capability) for each course returned 1069 $courses = array(); 1070 $cc = 0; // keep count 1071 while ($c = rs_fetch_next_record($rs)) { 1072 // build the context obj 1073 $c = make_context_subobj($c); 1074 1075 if (has_capability_in_accessdata($cap, $c->context, $accessdata, $doanything)) { 1076 $courses[] = $c; 1077 if ($limit > 0 && $cc++ > $limit) { 1078 break; 1079 } 1080 } 1081 } 1082 rs_close($rs); 1083 return $courses; 1084 } 1085 1086 1087 /** 1088 * It will return a nested array showing role assignments 1089 * all relevant role capabilities for the user at 1090 * site/metacourse/course_category/course levels 1091 * 1092 * We do _not_ delve deeper than courses because the number of 1093 * overrides at the module/block levels is HUGE. 1094 * 1095 * [ra] => [/path/] = array(roleid, roleid) 1096 * [rdef] => [/path/:roleid][capability]=permission 1097 * [loaded] => array('/path', '/path') 1098 * 1099 * @param $userid integer - the id of the user 1100 * 1101 */ 1102 function get_user_access_sitewide($userid) { 1103 1104 global $CFG; 1105 1106 // this flag has not been set! 1107 // (not clean install, or upgraded successfully to 1.7 and up) 1108 if (empty($CFG->rolesactive)) { 1109 return false; 1110 } 1111 1112 /* Get in 3 cheap DB queries... 1113 * - role assignments - with role_caps 1114 * - relevant role caps 1115 * - above this user's RAs 1116 * - below this user's RAs - limited to course level 1117 */ 1118 1119 $accessdata = array(); // named list 1120 $accessdata['ra'] = array(); 1121 $accessdata['rdef'] = array(); 1122 $accessdata['loaded'] = array(); 1123 1124 $sitectx = get_system_context(); 1125 $base = '/'.$sitectx->id; 1126 1127 // 1128 // Role assignments - and any rolecaps directly linked 1129 // because it's cheap to read rolecaps here over many 1130 // RAs 1131 // 1132 $sql = "SELECT ctx.path, ra.roleid, rc.capability, rc.permission 1133 FROM {$CFG->prefix}role_assignments ra 1134 JOIN {$CFG->prefix}context ctx 1135 ON ra.contextid=ctx.id 1136 LEFT OUTER JOIN {$CFG->prefix}role_capabilities rc 1137 ON (rc.roleid=ra.roleid AND rc.contextid=ra.contextid) 1138 WHERE ra.userid = $userid AND ctx.contextlevel <= ".CONTEXT_COURSE." 1139 ORDER BY ctx.depth, ctx.path, ra.roleid"; 1140 $rs = get_recordset_sql($sql); 1141 // 1142 // raparents collects paths & roles we need to walk up 1143 // the parenthood to build the rdef 1144 // 1145 // the array will bulk up a bit with dups 1146 // which we'll later clear up 1147 // 1148 $raparents = array(); 1149 $lastseen = ''; 1150 if ($rs) { 1151 while ($ra = rs_fetch_next_record($rs)) { 1152 // RAs leafs are arrays to support multi 1153 // role assignments... 1154 if (!isset($accessdata['ra'][$ra->path])) { 1155 $accessdata['ra'][$ra->path] = array(); 1156 } 1157 // only add if is not a repeat caused 1158 // by capability join... 1159 // (this check is cheaper than in_array()) 1160 if ($lastseen !== $ra->path.':'.$ra->roleid) { 1161 $lastseen = $ra->path.':'.$ra->roleid; 1162 array_push($accessdata['ra'][$ra->path], $ra->roleid); 1163 $parentids = explode('/', $ra->path); 1164 array_shift($parentids); // drop empty leading "context" 1165 array_pop($parentids); // drop _this_ context 1166 1167 if (isset($raparents[$ra->roleid])) { 1168 $raparents[$ra->roleid] = array_merge($raparents[$ra->roleid], 1169 $parentids); 1170 } else { 1171 $raparents[$ra->roleid] = $parentids; 1172 } 1173 } 1174 // Always add the roleded 1175 if (!empty($ra->capability)) { 1176 $k = "{$ra->path}:{$ra->roleid}"; 1177 $accessdata['rdef'][$k][$ra->capability] = $ra->permission; 1178 } 1179 } 1180 unset($ra); 1181 rs_close($rs); 1182 } 1183 1184 // Walk up the tree to grab all the roledefs 1185 // of interest to our user... 1186 // NOTE: we use a series of IN clauses here - which 1187 // might explode on huge sites with very convoluted nesting of 1188 // categories... - extremely unlikely that the number of categories 1189 // and roletypes is so large that we hit the limits of IN() 1190 $clauses = array(); 1191 foreach ($raparents as $roleid=>$contexts) { 1192 $contexts = implode(',', array_unique($contexts)); 1193 if ($contexts ==! '') { 1194 $clauses[] = "(roleid=$roleid AND contextid IN ($contexts))"; 1195 } 1196 } 1197 $clauses = implode(" OR ", $clauses); 1198 if ($clauses !== '') { 1199 $sql = "SELECT ctx.path, rc.roleid, rc.capability, rc.permission 1200 FROM {$CFG->prefix}role_capabilities rc 1201 JOIN {$CFG->prefix}context ctx 1202 ON rc.contextid=ctx.id 1203 WHERE $clauses 1204 ORDER BY ctx.depth ASC, ctx.path DESC, rc.roleid ASC "; 1205 1206 $rs = get_recordset_sql($sql); 1207 unset($clauses); 1208 1209 if ($rs) { 1210 while ($rd = rs_fetch_next_record($rs)) { 1211 $k = "{$rd->path}:{$rd->roleid}"; 1212 $accessdata['rdef'][$k][$rd->capability] = $rd->permission; 1213 } 1214 unset($rd); 1215 rs_close($rs); 1216 } 1217 } 1218 1219 // 1220 // Overrides for the role assignments IN SUBCONTEXTS 1221 // (though we still do _not_ go below the course level. 1222 // 1223 // NOTE that the JOIN w sctx is with 3-way triangulation to 1224 // catch overrides to the applicable role in any subcontext, based 1225 // on the path field of the parent. 1226 // 1227 $sql = "SELECT sctx.path, ra.roleid, 1228 ctx.path AS parentpath, 1229 rco.capability, rco.permission 1230 FROM {$CFG->prefix}role_assignments ra 1231 JOIN {$CFG->prefix}context ctx 1232 ON ra.contextid=ctx.id 1233 JOIN {$CFG->prefix}context sctx 1234 ON (sctx.path LIKE " . sql_concat('ctx.path',"'/%'"). " ) 1235 JOIN {$CFG->prefix}role_capabilities rco 1236 ON (rco.roleid=ra.roleid AND rco.contextid=sctx.id) 1237 WHERE ra.userid = $userid 1238 AND sctx.contextlevel <= ".CONTEXT_COURSE." 1239 ORDER BY sctx.depth, sctx.path, ra.roleid"; 1240 1241 $rs = get_recordset_sql($sql); 1242 if ($rs) { 1243 while ($rd = rs_fetch_next_record($rs)) { 1244 $k = "{$rd->path}:{$rd->roleid}"; 1245 $accessdata['rdef'][$k][$rd->capability] = $rd->permission; 1246 } 1247 unset($rd); 1248 rs_close($rs); 1249 } 1250 return $accessdata; 1251 } 1252 1253 /** 1254 * It add to the access ctrl array the data 1255 * needed by a user for a given context 1256 * 1257 * @param $userid integer - the id of the user 1258 * @param $context context obj - needs path! 1259 * @param $accessdata array accessdata array 1260 */ 1261 function load_subcontext($userid, $context, &$accessdata) { 1262 1263 global $CFG; 1264 1265 1266 1267 /* Get the additional RAs and relevant rolecaps 1268 * - role assignments - with role_caps 1269 * - relevant role caps 1270 * - above this user's RAs 1271 * - below this user's RAs - limited to course level 1272 */ 1273 1274 $base = "/" . SYSCONTEXTID; 1275 1276 // 1277 // Replace $context with the target context we will 1278 // load. Normally, this will be a course context, but 1279 // may be a different top-level context. 1280 // 1281 // We have 3 cases 1282 // 1283 // - Course 1284 // - BLOCK/PERSON/USER/COURSE(sitecourse) hanging from SYSTEM 1285 // - BLOCK/MODULE/GROUP hanging from a course 1286 // 1287 // For course contexts, we _already_ have the RAs 1288 // but the cost of re-fetching is minimal so we don't care. 1289 // 1290 if ($context->contextlevel !== CONTEXT_COURSE 1291 && $context->path !== "$base/{$context->id}") { 1292 // Case BLOCK/MODULE/GROUP hanging from a course 1293 // Assumption: the course _must_ be our parent 1294 // If we ever see stuff nested further this needs to 1295 // change to do 1 query over the exploded path to 1296 // find out which one is the course 1297 $courses = explode('/',get_course_from_path($context->path)); 1298 $targetid = array_pop($courses); 1299 $context = get_context_instance_by_id($targetid); 1300 1301 } 1302 1303 // 1304 // Role assignments in the context and below 1305 // 1306 $sql = "SELECT ctx.path, ra.roleid 1307 FROM {$CFG->prefix}role_assignments ra 1308 JOIN {$CFG->prefix}context ctx 1309 ON ra.contextid=ctx.id 1310 WHERE ra.userid = $userid 1311 AND (ctx.path = '{$context->path}' OR ctx.path LIKE '{$context->path}/%') 1312 ORDER BY ctx.depth, ctx.path, ra.roleid"; 1313 $rs = get_recordset_sql($sql); 1314 1315 // 1316 // Read in the RAs, preventing duplicates 1317 // 1318 $localroles = array(); 1319 $lastseen = ''; 1320 while ($ra = rs_fetch_next_record($rs)) { 1321 if (!isset($accessdata['ra'][$ra->path])) { 1322 $accessdata['ra'][$ra->path] = array(); 1323 } 1324 // only add if is not a repeat caused 1325 // by capability join... 1326 // (this check is cheaper than in_array()) 1327 if ($lastseen !== $ra->path.':'.$ra->roleid) { 1328 $lastseen = $ra->path.':'.$ra->roleid; 1329 array_push($accessdata['ra'][$ra->path], $ra->roleid); 1330 array_push($localroles, $ra->roleid); 1331 } 1332 } 1333 rs_close($rs); 1334 1335 // 1336 // Walk up and down the tree to grab all the roledefs 1337 // of interest to our user... 1338 // 1339 // NOTES 1340 // - we use IN() but the number of roles is very limited. 1341 // 1342 $courseroles = aggregate_roles_from_accessdata($context, $accessdata); 1343 1344 // Do we have any interesting "local" roles? 1345 $localroles = array_diff($localroles,$courseroles); // only "new" local roles 1346 $wherelocalroles=''; 1347 if (count($localroles)) { 1348 // Role defs for local roles in 'higher' contexts... 1349 $contexts = substr($context->path, 1); // kill leading slash 1350 $contexts = str_replace('/', ',', $contexts); 1351 $localroleids = implode(',',$localroles); 1352 $wherelocalroles="OR (rc.roleid IN ({$localroleids}) 1353 AND ctx.id IN ($contexts))" ; 1354 } 1355 1356 // We will want overrides for all of them 1357 $whereroles = ''; 1358 if ($roleids = implode(',',array_merge($courseroles,$localroles))) { 1359 $whereroles = "rc.roleid IN ($roleids) AND"; 1360 } 1361 $sql = "SELECT ctx.path, rc.roleid, rc.capability, rc.permission 1362 FROM {$CFG->prefix}role_capabilities rc 1363 JOIN {$CFG->prefix}context ctx 1364 ON rc.contextid=ctx.id 1365 WHERE ($whereroles 1366 (ctx.id={$context->id} OR ctx.path LIKE '{$context->path}/%')) 1367 $wherelocalroles 1368 ORDER BY ctx.depth ASC, ctx.path DESC, rc.roleid ASC "; 1369 1370 $newrdefs = array(); 1371 if ($rs = get_recordset_sql($sql)) { 1372 while ($rd = rs_fetch_next_record($rs)) { 1373 $k = "{$rd->path}:{$rd->roleid}"; 1374 if (!array_key_exists($k, $newrdefs)) { 1375 $newrdefs[$k] = array(); 1376 } 1377 $newrdefs[$k][$rd->capability] = $rd->permission; 1378 } 1379 rs_close($rs); 1380 } else { 1381 debugging('Bad SQL encountered!'); 1382 } 1383 1384 compact_rdefs($newrdefs); 1385 foreach ($newrdefs as $key=>$value) { 1386 $accessdata['rdef'][$key] =& $newrdefs[$key]; 1387 } 1388 1389 // error_log("loaded {$context->path}"); 1390 $accessdata['loaded'][] = $context->path; 1391 } 1392 1393 /** 1394 * It add to the access ctrl array the data 1395 * needed by a role for a given context. 1396 * 1397 * The data is added in the rdef key. 1398 * 1399 * This role-centric function is useful for role_switching 1400 * and to get an overview of what a role gets under a 1401 * given context and below... 1402 * 1403 * @param $roleid integer - the id of the user 1404 * @param $context context obj - needs path! 1405 * @param $accessdata accessdata array 1406 * 1407 */ 1408 function get_role_access_bycontext($roleid, $context, $accessdata=NULL) { 1409 1410 global $CFG; 1411 1412 /* Get the relevant rolecaps into rdef 1413 * - relevant role caps 1414 * - at ctx and above 1415 * - below this ctx 1416 */ 1417 1418 if (is_null($accessdata)) { 1419 $accessdata = array(); // named list 1420 $accessdata['ra'] = array(); 1421 $accessdata['rdef'] = array(); 1422 $accessdata['loaded'] = array(); 1423 } 1424 1425 $contexts = substr($context->path, 1); // kill leading slash 1426 $contexts = str_replace('/', ',', $contexts); 1427 1428 // 1429 // Walk up and down the tree to grab all the roledefs 1430 // of interest to our role... 1431 // 1432 // NOTE: we use an IN clauses here - which 1433 // might explode on huge sites with very convoluted nesting of 1434 // categories... - extremely unlikely that the number of nested 1435 // categories is so large that we hit the limits of IN() 1436 // 1437 $sql = "SELECT ctx.path, rc.capability, rc.permission 1438 FROM {$CFG->prefix}role_capabilities rc 1439 JOIN {$CFG->prefix}context ctx 1440 ON rc.contextid=ctx.id 1441 WHERE rc.roleid=$roleid AND 1442 ( ctx.id IN ($contexts) OR 1443 ctx.path LIKE '{$context->path}/%' ) 1444 ORDER BY ctx.depth ASC, ctx.path DESC, rc.roleid ASC "; 1445 1446 $rs = get_recordset_sql($sql); 1447 while ($rd = rs_fetch_next_record($rs)) { 1448 $k = "{$rd->path}:{$roleid}"; 1449 $accessdata['rdef'][$k][$rd->capability] = $rd->permission; 1450 } 1451 rs_close($rs); 1452 1453 return $accessdata; 1454 } 1455 1456 /** 1457 * Load accessdata for a user 1458 * into the $ACCESS global 1459 * 1460 * Used by has_capability() - but feel free 1461 * to call it if you are about to run a BIG 1462 * cron run across a bazillion users. 1463 * 1464 */ 1465 function load_user_accessdata($userid) { 1466 global $ACCESS,$CFG; 1467 1468 $base = '/'.SYSCONTEXTID; 1469 1470 $accessdata = get_user_access_sitewide($userid); 1471 $frontpagecontext = get_context_instance(CONTEXT_COURSE, SITEID); 1472 // 1473 // provide "default role" & set 'dr' 1474 // 1475 if (!empty($CFG->defaultuserroleid)) { 1476 $accessdata = get_role_access($CFG->defaultuserroleid, $accessdata); 1477 if (!isset($accessdata['ra'][$base])) { 1478 $accessdata['ra'][$base] = array($CFG->defaultuserroleid); 1479 } else { 1480 array_push($accessdata['ra'][$base], $CFG->defaultuserroleid); 1481 } 1482 $accessdata['dr'] = $CFG->defaultuserroleid; 1483 } 1484 1485 // 1486 // provide "default frontpage role" 1487 // 1488 if (!empty($CFG->defaultfrontpageroleid)) { 1489 $base = '/'. SYSCONTEXTID .'/'. $frontpagecontext->id; 1490 $accessdata = get_default_frontpage_role_access($CFG->defaultfrontpageroleid, $accessdata); 1491 if (!isset($accessdata['ra'][$base])) { 1492 $accessdata['ra'][$base] = array($CFG->defaultfrontpageroleid); 1493 } else { 1494 array_push($accessdata['ra'][$base], $CFG->defaultfrontpageroleid); 1495 } 1496 } 1497 // for dirty timestamps in cron 1498 $accessdata['time'] = time(); 1499 1500 $ACCESS[$userid] = $accessdata; 1501 compact_rdefs($ACCESS[$userid]['rdef']); 1502 1503 return true; 1504 } 1505 1506 /** 1507 * Use shared copy of role definistions stored in $RDEFS; 1508 * @param array $rdefs array of role definitions in contexts 1509 */ 1510 function compact_rdefs(&$rdefs) { 1511 global $RDEFS; 1512 1513 /* 1514 * This is a basic sharing only, we could also 1515 * use md5 sums of values. The main purpose is to 1516 * reduce mem in cron jobs - many users in $ACCESS array. 1517 */ 1518 1519 foreach ($rdefs as $key => $value) { 1520 if (!array_key_exists($key, $RDEFS)) { 1521 $RDEFS[$key] = $rdefs[$key]; 1522 } 1523 $rdefs[$key] =& $RDEFS[$key]; 1524 } 1525 } 1526 1527 /** 1528 * A convenience function to completely load all the capabilities 1529 * for the current user. This is what gets called from complete_user_login() 1530 * for example. Call it only _after_ you've setup $USER and called 1531 * check_enrolment_plugins(); 1532 * 1533 */ 1534 function load_all_capabilities() { 1535 global $USER, $CFG, $DIRTYCONTEXTS; 1536 1537 $base = '/'.SYSCONTEXTID; 1538 1539 if (isguestuser()) { 1540 $guest = get_guest_role(); 1541 1542 // Load the rdefs 1543 $USER->access = get_role_access($guest->id); 1544 // Put the ghost enrolment in place... 1545 $USER->access['ra'][$base] = array($guest->id); 1546 1547 1548 } else if (isloggedin()) { 1549 1550 $accessdata = get_user_access_sitewide($USER->id); 1551 1552 // 1553 // provide "default role" & set 'dr' 1554 // 1555 if (!empty($CFG->defaultuserroleid)) { 1556 $accessdata = get_role_access($CFG->defaultuserroleid, $accessdata); 1557 if (!isset($accessdata['ra'][$base])) { 1558 $accessdata['ra'][$base] = array($CFG->defaultuserroleid); 1559 } else { 1560 array_push($accessdata['ra'][$base], $CFG->defaultuserroleid); 1561 } 1562 $accessdata['dr'] = $CFG->defaultuserroleid; 1563 } 1564 1565 $frontpagecontext = get_context_instance(CONTEXT_COURSE, SITEID); 1566 1567 // 1568 // provide "default frontpage role" 1569 // 1570 if (!empty($CFG->defaultfrontpageroleid)) { 1571 $base = '/'. SYSCONTEXTID .'/'. $frontpagecontext->id; 1572 $accessdata = get_default_frontpage_role_access($CFG->defaultfrontpageroleid, $accessdata); 1573 if (!isset($accessdata['ra'][$base])) { 1574 $accessdata['ra'][$base] = array($CFG->defaultfrontpageroleid); 1575 } else { 1576 array_push($accessdata['ra'][$base], $CFG->defaultfrontpageroleid); 1577 } 1578 } 1579 $USER->access = $accessdata; 1580 1581 } else if (!empty($CFG->notloggedinroleid)) { 1582 $USER->access = get_role_access($CFG->notloggedinroleid); 1583 $USER->access['ra'][$base] = array($CFG->notloggedinroleid); 1584 } 1585 1586 // Timestamp to read dirty context timestamps later 1587 $USER->access['time'] = time(); 1588 $DIRTYCONTEXTS = array(); 1589 1590 // Clear to force a refresh 1591 unset($USER->mycourses); 1592 } 1593 1594 /** 1595 * A convenience function to completely reload all the capabilities 1596 * for the current user when roles have been updated in a relevant 1597 * context -- but PRESERVING switchroles and loginas. 1598 * 1599 * That is - completely transparent to the user. 1600 * 1601 * Note: rewrites $USER->access completely. 1602 * 1603 */ 1604 function reload_all_capabilities() { 1605 global $USER,$CFG; 1606 1607 // error_log("reloading"); 1608 // copy switchroles 1609 $sw = array(); 1610 if (isset($USER->access['rsw'])) { 1611 $sw = $USER->access['rsw']; 1612 // error_log(print_r($sw,1)); 1613 } 1614 1615 unset($USER->access); 1616 unset($USER->mycourses); 1617 1618 load_all_capabilities(); 1619 1620 foreach ($sw as $path => $roleid) { 1621 $context = get_record('context', 'path', $path); 1622 role_switch($roleid, $context); 1623 } 1624 1625 } 1626 1627 /* 1628 * Adds a temp role to an accessdata array. 1629 * 1630 * Useful for the "temporary guest" access 1631 * we grant to logged-in users. 1632 * 1633 * Note - assumes a course context! 1634 * 1635 */ 1636 function load_temp_role($context, $roleid, $accessdata) { 1637 1638 global $CFG; 1639 1640 // 1641 // Load rdefs for the role in - 1642 // - this context 1643 // - all the parents 1644 // - and below - IOWs overrides... 1645 // 1646 1647 // turn the path into a list of context ids 1648 $contexts = substr($context->path, 1); // kill leading slash 1649 $contexts = str_replace('/', ',', $contexts); 1650 1651 $sql = "SELECT ctx.path, 1652 rc.capability, rc.permission 1653 FROM {$CFG->prefix}context ctx 1654 JOIN {$CFG->prefix}role_capabilities rc 1655 ON rc.contextid=ctx.id 1656 WHERE (ctx.id IN ($contexts) 1657 OR ctx.path LIKE '{$context->path}/%') 1658 AND rc.roleid = {$roleid} 1659 ORDER BY ctx.depth, ctx.path"; 1660 $rs = get_recordset_sql($sql); 1661 while ($rd = rs_fetch_next_record($rs)) { 1662 $k = "{$rd->path}:{$roleid}"; 1663 $accessdata['rdef'][$k][$rd->capability] = $rd->permission; 1664 } 1665 rs_close($rs); 1666 1667 // 1668 // Say we loaded everything for the course context 1669 // - which we just did - if the user gets a proper 1670 // RA in this session, this data will need to be reloaded, 1671 // but that is handled by the complete accessdata reload 1672 // 1673 array_push($accessdata['loaded'], $context->path); 1674 1675 // 1676 // Add the ghost RA 1677 // 1678 if (isset($accessdata['ra'][$context->path])) { 1679 array_push($accessdata['ra'][$context->path], $roleid); 1680 } else { 1681 $accessdata['ra'][$context->path] = array($roleid); 1682 } 1683 1684 return $accessdata; 1685 } 1686 1687 1688 /** 1689 * Check all the login enrolment information for the given user object 1690 * by querying the enrolment plugins 1691 */ 1692 function check_enrolment_plugins(&$user) { 1693 global $CFG; 1694 1695 static $inprogress; // To prevent this function being called more than once in an invocation 1696 1697 if (!empty($inprogress[$user->id])) { 1698 return; 1699 } 1700 1701 $inprogress[$user->id] = true; // Set the flag 1702 1703 require_once($CFG->dirroot .'/enrol/enrol.class.php'); 1704 1705 if (!($plugins = explode(',', $CFG->enrol_plugins_enabled))) { 1706 $plugins = array($CFG->enrol); 1707 } 1708 1709 foreach ($plugins as $plugin) { 1710 $enrol = enrolment_factory::factory($plugin); 1711 if (method_exists($enrol, 'setup_enrolments')) { /// Plugin supports Roles (Moodle 1.7 and later) 1712 $enrol->setup_enrolments($user); 1713 } else { /// Run legacy enrolment methods 1714 if (method_exists($enrol, 'get_student_courses')) { 1715 $enrol->get_student_courses($user); 1716 } 1717 if (method_exists($enrol, 'get_teacher_courses')) { 1718 $enrol->get_teacher_courses($user); 1719 } 1720 1721 /// deal with $user->students and $user->teachers stuff 1722 unset($user->student); 1723 unset($user->teacher); 1724 } 1725 unset($enrol); 1726 } 1727 1728 unset($inprogress[$user->id]); // Unset the flag 1729 } 1730 1731 /** 1732 * Installs the roles system. 1733 * This function runs on a fresh install as well as on an upgrade from the old 1734 * hard-coded student/teacher/admin etc. roles to the new roles system. 1735 */ 1736 function moodle_install_roles() { 1737 1738 global $CFG, $db; 1739 1740 /// Create a system wide context for assignemnt. 1741 $systemcontext = $context = get_context_instance(CONTEXT_SYSTEM); 1742 1743 1744 /// Create default/legacy roles and capabilities. 1745 /// (1 legacy capability per legacy role at system level). 1746 1747 $adminrole = create_role(addslashes(get_string('administrator')), 'admin', 1748 addslashes(get_string('administratordescription')), 'moodle/legacy:admin'); 1749 $coursecreatorrole = create_role(addslashes(get_string('coursecreators')), 'coursecreator', 1750 addslashes(get_string('coursecreatorsdescription')), 'moodle/legacy:coursecreator'); 1751 $editteacherrole = create_role(addslashes(get_string('defaultcourseteacher')), 'editingteacher', 1752 addslashes(get_string('defaultcourseteacherdescription')), 'moodle/legacy:editingteacher'); 1753 $noneditteacherrole = create_role(addslashes(get_string('noneditingteacher')), 'teacher', 1754 addslashes(get_string('noneditingteacherdescription')), 'moodle/legacy:teacher'); 1755 $studentrole = create_role(addslashes(get_string('defaultcoursestudent')), 'student', 1756 addslashes(get_string('defaultcoursestudentdescription')), 'moodle/legacy:student'); 1757 $guestrole = create_role(addslashes(get_string('guest')), 'guest', 1758 addslashes(get_string('guestdescription')), 'moodle/legacy:guest'); 1759 $userrole = create_role(addslashes(get_string('authenticateduser')), 'user', 1760 addslashes(get_string('authenticateduserdescription')), 'moodle/legacy:user'); 1761 1762 /// Now is the correct moment to install capabilities - after creation of legacy roles, but before assigning of roles 1763 1764 if (!assign_capability('moodle/site:doanything', CAP_ALLOW, $adminrole, $systemcontext->id)) { 1765 error('Could not assign moodle/site:doanything to the admin role'); 1766 } 1767 if (!update_capabilities()) { 1768 error('Had trouble upgrading the core capabilities for the Roles System'); 1769 } 1770 1771 /// Look inside user_admin, user_creator, user_teachers, user_students and 1772 /// assign above new roles. If a user has both teacher and student role, 1773 /// only teacher role is assigned. The assignment should be system level. 1774 1775 $dbtables = $db->MetaTables('TABLES'); 1776 1777 /// Set up the progress bar 1778 1779 $usertables = array('user_admins', 'user_coursecreators', 'user_teachers', 'user_students'); 1780 1781 $totalcount = $progresscount = 0; 1782 foreach ($usertables as $usertable) { 1783 if (in_array($CFG->prefix.$usertable, $dbtables)) { 1784 $totalcount += count_records($usertable); 1785 } 1786 } 1787 1788 print_progress(0, $totalcount, 5, 1, 'Processing role assignments'); 1789 1790 /// Upgrade the admins. 1791 /// Sort using id ASC, first one is primary admin. 1792 1793 if (in_array($CFG->prefix.'user_admins', $dbtables)) { 1794 if ($rs = get_recordset_sql('SELECT * from '.$CFG->prefix.'user_admins ORDER BY ID ASC')) { 1795 while ($admin = rs_fetch_next_record($rs)) { 1796 role_assign($adminrole, $admin->userid, 0, $systemcontext->id); 1797 $progresscount++; 1798 print_progress($progresscount, $totalcount, 5, 1, 'Processing role assignments'); 1799 } 1800 rs_close($rs); 1801 } 1802 } else { 1803 // This is a fresh install. 1804 } 1805 1806 1807 /// Upgrade course creators. 1808 if (in_array($CFG->prefix.'user_coursecreators', $dbtables)) { 1809 if ($rs = get_recordset('user_coursecreators')) { 1810 while ($coursecreator = rs_fetch_next_record($rs)) { 1811 role_assign($coursecreatorrole, $coursecreator->userid, 0, $systemcontext->id); 1812 $progresscount++; 1813 print_progress($progresscount, $totalcount, 5, 1, 'Processing role assignments'); 1814 } 1815 rs_close($rs); 1816 } 1817 } 1818 1819 1820 /// Upgrade editting teachers and non-editting teachers. 1821 if (in_array($CFG->prefix.'user_teachers', $dbtables)) { 1822 if ($rs = get_recordset('user_teachers')) { 1823 while ($teacher = rs_fetch_next_record($rs)) { 1824 1825 // removed code here to ignore site level assignments 1826 // since the contexts are separated now 1827 1828 // populate the user_lastaccess table 1829 $access = new object(); 1830 $access->timeaccess = $teacher->timeaccess; 1831 $access->userid = $teacher->userid; 1832 $access->courseid = $teacher->course; 1833 insert_record('user_lastaccess', $access); 1834 1835 // assign the default student role 1836 $coursecontext = get_context_instance(CONTEXT_COURSE, $teacher->course); // needs cache 1837 // hidden teacher 1838 if ($teacher->authority == 0) { 1839 $hiddenteacher = 1; 1840 } else { 1841 $hiddenteacher = 0; 1842 } 1843 1844 if ($teacher->editall) { // editting teacher 1845 role_assign($editteacherrole, $teacher->userid, 0, $coursecontext->id, $teacher->timestart, $teacher->timeend, $hiddenteacher, $teacher->enrol, $teacher->timemodified); 1846 } else { 1847 role_assign($noneditteacherrole, $teacher->userid, 0, $coursecontext->id, $teacher->timestart, $teacher->timeend, $hiddenteacher, $teacher->enrol, $teacher->timemodified); 1848 } 1849 $progresscount++; 1850 print_progress($progresscount, $totalcount, 5, 1, 'Processing role assignments'); 1851 } 1852 rs_close($rs); 1853 } 1854 } 1855 1856 1857 /// Upgrade students. 1858 if (in_array($CFG->prefix.'user_students', $dbtables)) { 1859 if ($rs = get_recordset('user_students')) { 1860 while ($student = rs_fetch_next_record($rs)) { 1861 1862 // populate the user_lastaccess table 1863 $access = new object; 1864 $access->timeaccess = $student->timeaccess; 1865 $access->userid = $student->userid; 1866 $access->courseid = $student->course; 1867 insert_record('user_lastaccess', $access); 1868 1869 // assign the default student role 1870 $coursecontext = get_context_instance(CONTEXT_COURSE, $student->course); 1871 role_assign($studentrole, $student->userid, 0, $coursecontext->id, $student->timestart, $student->timeend, 0, $student->enrol, $student->time); 1872 $progresscount++; 1873 print_progress($progresscount, $totalcount, 5, 1, 'Processing role assignments'); 1874 } 1875 rs_close($rs); 1876 } 1877 } 1878 1879 1880 /// Upgrade guest (only 1 entry). 1881 if ($guestuser = get_record('user', 'username', 'guest')) { 1882 role_assign($guestrole, $guestuser->id, 0, $systemcontext->id); 1883 } 1884 print_progress($totalcount, $totalcount, 5, 1, 'Processing role assignments'); 1885 1886 1887 /// Insert the correct records for legacy roles 1888 allow_assign($adminrole, $adminrole); 1889 allow_assign($adminrole, $coursecreatorrole); 1890 allow_assign($adminrole, $noneditteacherrole); 1891 allow_assign($adminrole, $editteacherrole); 1892 allow_assign($adminrole, $studentrole); 1893 allow_assign($adminrole, $guestrole); 1894 1895 allow_assign($coursecreatorrole, $noneditteacherrole); 1896 allow_assign($coursecreatorrole, $editteacherrole); 1897 allow_assign($coursecreatorrole, $studentrole); 1898 allow_assign($coursecreatorrole, $guestrole); 1899 1900 allow_assign($editteacherrole, $noneditteacherrole); 1901 allow_assign($editteacherrole, $studentrole); 1902 allow_assign($editteacherrole, $guestrole); 1903 1904 /// Set up default allow override matrix 1905 allow_override($adminrole, $adminrole); 1906 allow_override($adminrole, $coursecreatorrole); 1907 allow_override($adminrole, $noneditteacherrole); 1908 allow_override($adminrole, $editteacherrole); 1909 allow_override($adminrole, $studentrole); 1910 allow_override($adminrole, $guestrole); 1911 allow_override($adminrole, $userrole); 1912 1913 //See MDL-15841 1914 //allow_override($editteacherrole, $noneditteacherrole); 1915 //allow_override($editteacherrole, $studentrole); 1916 //allow_override($editteacherrole, $guestrole); 1917 1918 1919 /// Delete the old user tables when we are done 1920 1921 $tables = array('user_students', 'user_teachers', 'user_coursecreators', 'user_admins'); 1922 foreach ($tables as $tablename) { 1923 $table = new XMLDBTable($tablename); 1924 if (table_exists($table)) { 1925 drop_table($table); 1926 } 1927 } 1928 } 1929 1930 /** 1931 * Returns array of all legacy roles. 1932 */ 1933 function get_legacy_roles() { 1934 return array( 1935 'admin' => 'moodle/legacy:admin', 1936 'coursecreator' => 'moodle/legacy:coursecreator', 1937 'editingteacher' => 'moodle/legacy:editingteacher', 1938 'teacher' => 'moodle/legacy:teacher', 1939 'student' => 'moodle/legacy:student', 1940 'guest' => 'moodle/legacy:guest', 1941 'user' => 'moodle/legacy:user' 1942 ); 1943 } 1944 1945 function get_legacy_type($roleid) { 1946 $sitecontext = get_context_instance(CONTEXT_SYSTEM); 1947 $legacyroles = get_legacy_roles(); 1948 1949 $result = ''; 1950 foreach($legacyroles as $ltype=>$lcap) { 1951 $localoverride = get_local_override($roleid, $sitecontext->id, $lcap); 1952 if (!empty($localoverride->permission) and $localoverride->permission == CAP_ALLOW) { 1953 //choose first selected legacy capability - reset the rest 1954 if (empty($result)) { 1955 $result = $ltype; 1956 } else { 1957 unassign_capability($lcap, $roleid); 1958 } 1959 } 1960 } 1961 1962 return $result; 1963 } 1964 1965 /** 1966 * Assign the defaults found in this capabality definition to roles that have 1967 * the corresponding legacy capabilities assigned to them. 1968 * @param $legacyperms - an array in the format (example): 1969 * 'guest' => CAP_PREVENT, 1970 * 'student' => CAP_ALLOW, 1971 * 'teacher' => CAP_ALLOW, 1972 * 'editingteacher' => CAP_ALLOW, 1973 * 'coursecreator' => CAP_ALLOW, 1974 * 'admin' => CAP_ALLOW 1975 * @return boolean - success or failure. 1976 */ 1977 function assign_legacy_capabilities($capability, $legacyperms) { 1978 1979 $legacyroles = get_legacy_roles(); 1980 1981 foreach ($legacyperms as $type => $perm) { 1982 1983 $systemcontext = get_context_instance(CONTEXT_SYSTEM); 1984 1985 if (!array_key_exists($type, $legacyroles)) { 1986 error('Incorrect legacy role definition for type: '.$type); 1987 } 1988 1989 if ($roles = get_roles_with_capability($legacyroles[$type], CAP_ALLOW)) { 1990 foreach ($roles as $role) { 1991 // Assign a site level capability. 1992 if (!assign_capability($capability, $perm, $role->id, $systemcontext->id)) { 1993 return false; 1994 } 1995 } 1996 } 1997 } 1998 return true; 1999 } 2000 2001 2002 /** 2003 * Checks to see if a capability is a legacy capability. 2004 * @param $capabilityname 2005 * @return boolean 2006 */ 2007 function islegacy($capabilityname) { 2008 if (strpos($capabilityname, 'moodle/legacy') === 0) { 2009 return true; 2010 } else { 2011 return false; 2012 } 2013 } 2014 2015 2016 2017 /********************************** 2018 * Context Manipulation functions * 2019 **********************************/ 2020 2021 /** 2022 * Create a new context record for use by all roles-related stuff 2023 * assumes that the caller has done the homework. 2024 * 2025 * @param $level 2026 * @param $instanceid 2027 * 2028 * @return object newly created context 2029 */ 2030 function create_context($contextlevel, $instanceid) { 2031 2032 global $CFG; 2033 2034 if ($contextlevel == CONTEXT_SYSTEM) { 2035 return create_system_context(); 2036 } 2037 2038 $context = new object(); 2039 $context->contextlevel = $contextlevel; 2040 $context->instanceid = $instanceid; 2041 2042 // Define $context->path based on the parent 2043 // context. In other words... Who is your daddy? 2044 $basepath = '/' . SYSCONTEXTID; 2045 $basedepth = 1; 2046 2047 $result = true; 2048 2049 switch ($contextlevel) { 2050 case CONTEXT_COURSECAT: 2051 $sql = "SELECT ctx.path, ctx.depth 2052 FROM {$CFG->prefix}context ctx 2053 JOIN {$CFG->prefix}course_categories cc 2054 ON (cc.parent=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSECAT.") 2055 WHERE cc.id={$instanceid}"; 2056 if ($p = get_record_sql($sql)) { 2057 $basepath = $p->path; 2058 $basedepth = $p->depth; 2059 } else if ($category = get_record('course_categories', 'id', $instanceid)) { 2060 if (empty($category->parent)) { 2061 // ok - this is a top category 2062 } else if ($parent = get_context_instance(CONTEXT_COURSECAT, $category->parent)) { 2063 $basepath = $parent->path; 2064 $basedepth = $parent->depth; 2065 } else { 2066 // wrong parent category - no big deal, this can be fixed later 2067 $basepath = null; 2068 $basedepth = 0; 2069 } 2070 } else { 2071 // incorrect category id 2072 $result = false; 2073 } 2074 break; 2075 2076 case CONTEXT_COURSE: 2077 $sql = "SELECT ctx.path, ctx.depth 2078 FROM {$CFG->prefix}context ctx 2079 JOIN {$CFG->prefix}course c 2080 ON (c.category=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSECAT.") 2081 WHERE c.id={$instanceid} AND c.id !=" . SITEID; 2082 if ($p = get_record_sql($sql)) { 2083 $basepath = $p->path; 2084 $basedepth = $p->depth; 2085 } else if ($course = get_record('course', 'id', $instanceid)) { 2086 if ($course->id == SITEID) { 2087 //ok - no parent category 2088 } else if ($parent = get_context_instance(CONTEXT_COURSECAT, $course->category)) { 2089 $basepath = $parent->path; 2090 $basedepth = $parent->depth; 2091 } else { 2092 // wrong parent category of course - no big deal, this can be fixed later 2093 $basepath = null; 2094 $basedepth = 0; 2095 } 2096 } else if ($instanceid == SITEID) { 2097 // no errors for missing site course during installation 2098 return false; 2099 } else { 2100 // incorrect course id 2101 $result = false; 2102 } 2103 break; 2104 2105 case CONTEXT_MODULE: 2106 $sql = "SELECT ctx.path, ctx.depth 2107 FROM {$CFG->prefix}context ctx 2108 JOIN {$CFG->prefix}course_modules cm 2109 ON (cm.course=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE.") 2110 WHERE cm.id={$instanceid}"; 2111 if ($p = get_record_sql($sql)) { 2112 $basepath = $p->path; 2113 $basedepth = $p->depth; 2114 } else if ($cm = get_record('course_modules', 'id', $instanceid)) { 2115 if ($parent = get_context_instance(CONTEXT_COURSE, $cm->course)) { 2116 $basepath = $parent->path; 2117 $basedepth = $parent->depth; 2118 } else { 2119 // course does not exist - modules can not exist without a course 2120 $result = false; 2121 } 2122 } else { 2123 // cm does not exist 2124 $result = false; 2125 } 2126 break; 2127 2128 case CONTEXT_BLOCK: 2129 // Only non-pinned & course-page based 2130 $sql = "SELECT ctx.path, ctx.depth 2131 FROM {$CFG->prefix}context ctx 2132 JOIN {$CFG->prefix}block_instance bi 2133 ON (bi.pageid=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE.") 2134 WHERE bi.id={$instanceid} AND bi.pagetype='course-view'&q