[ Index ] |
PHP Cross Reference of moodle-2.8 |
[Summary view] [Print] [Text view]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * This file contains functions for managing user access 19 * 20 * <b>Public API vs internals</b> 21 * 22 * General users probably only care about 23 * 24 * Context handling 25 * - context_course::instance($courseid), context_module::instance($cm->id), context_coursecat::instance($catid) 26 * - context::instance_by_id($contextid) 27 * - $context->get_parent_contexts(); 28 * - $context->get_child_contexts(); 29 * 30 * Whether the user can do something... 31 * - has_capability() 32 * - has_any_capability() 33 * - has_all_capabilities() 34 * - require_capability() 35 * - require_login() (from moodlelib) 36 * - is_enrolled() 37 * - is_viewing() 38 * - is_guest() 39 * - is_siteadmin() 40 * - isguestuser() 41 * - isloggedin() 42 * 43 * What courses has this user access to? 44 * - get_enrolled_users() 45 * 46 * What users can do X in this context? 47 * - get_enrolled_users() - at and bellow course context 48 * - get_users_by_capability() - above course context 49 * 50 * Modify roles 51 * - role_assign() 52 * - role_unassign() 53 * - role_unassign_all() 54 * 55 * Advanced - for internal use only 56 * - load_all_capabilities() 57 * - reload_all_capabilities() 58 * - has_capability_in_accessdata() 59 * - get_user_access_sitewide() 60 * - load_course_context() 61 * - load_role_access_by_context() 62 * - etc. 63 * 64 * <b>Name conventions</b> 65 * 66 * "ctx" means context 67 * 68 * <b>accessdata</b> 69 * 70 * Access control data is held in the "accessdata" array 71 * which - for the logged-in user, will be in $USER->access 72 * 73 * For other users can be generated and passed around (but may also be cached 74 * against userid in $ACCESSLIB_PRIVATE->accessdatabyuser). 75 * 76 * $accessdata is a multidimensional array, holding 77 * role assignments (RAs), role-capabilities-perm sets 78 * (role defs) and a list of courses we have loaded 79 * data for. 80 * 81 * Things are keyed on "contextpaths" (the path field of 82 * the context table) for fast walking up/down the tree. 83 * <code> 84 * $accessdata['ra'][$contextpath] = array($roleid=>$roleid) 85 * [$contextpath] = array($roleid=>$roleid) 86 * [$contextpath] = array($roleid=>$roleid) 87 * </code> 88 * 89 * Role definitions are stored like this 90 * (no cap merge is done - so it's compact) 91 * 92 * <code> 93 * $accessdata['rdef']["$contextpath:$roleid"]['mod/forum:viewpost'] = 1 94 * ['mod/forum:editallpost'] = -1 95 * ['mod/forum:startdiscussion'] = -1000 96 * </code> 97 * 98 * See how has_capability_in_accessdata() walks up the tree. 99 * 100 * First we only load rdef and ra down to the course level, but not below. 101 * This keeps accessdata small and compact. Below-the-course ra/rdef 102 * are loaded as needed. We keep track of which courses we have loaded ra/rdef in 103 * <code> 104 * $accessdata['loaded'] = array($courseid1=>1, $courseid2=>1) 105 * </code> 106 * 107 * <b>Stale accessdata</b> 108 * 109 * For the logged-in user, accessdata is long-lived. 110 * 111 * On each pageload we load $ACCESSLIB_PRIVATE->dirtycontexts which lists 112 * context paths affected by changes. Any check at-or-below 113 * a dirty context will trigger a transparent reload of accessdata. 114 * 115 * Changes at the system level will force the reload for everyone. 116 * 117 * <b>Default role caps</b> 118 * The default role assignment is not in the DB, so we 119 * add it manually to accessdata. 120 * 121 * This means that functions that work directly off the 122 * DB need to ensure that the default role caps 123 * are dealt with appropriately. 124 * 125 * @package core_access 126 * @copyright 1999 onwards Martin Dougiamas http://dougiamas.com 127 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 128 */ 129 130 defined('MOODLE_INTERNAL') || die(); 131 132 /** No capability change */ 133 define('CAP_INHERIT', 0); 134 /** Allow permission, overrides CAP_PREVENT defined in parent contexts */ 135 define('CAP_ALLOW', 1); 136 /** Prevent permission, overrides CAP_ALLOW defined in parent contexts */ 137 define('CAP_PREVENT', -1); 138 /** Prohibit permission, overrides everything in current and child contexts */ 139 define('CAP_PROHIBIT', -1000); 140 141 /** System context level - only one instance in every system */ 142 define('CONTEXT_SYSTEM', 10); 143 /** User context level - one instance for each user describing what others can do to user */ 144 define('CONTEXT_USER', 30); 145 /** Course category context level - one instance for each category */ 146 define('CONTEXT_COURSECAT', 40); 147 /** Course context level - one instances for each course */ 148 define('CONTEXT_COURSE', 50); 149 /** Course module context level - one instance for each course module */ 150 define('CONTEXT_MODULE', 70); 151 /** 152 * Block context level - one instance for each block, sticky blocks are tricky 153 * because ppl think they should be able to override them at lower contexts. 154 * Any other context level instance can be parent of block context. 155 */ 156 define('CONTEXT_BLOCK', 80); 157 158 /** Capability allow management of trusts - NOT IMPLEMENTED YET - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */ 159 define('RISK_MANAGETRUST', 0x0001); 160 /** Capability allows changes in system configuration - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */ 161 define('RISK_CONFIG', 0x0002); 162 /** Capability allows user to add scripted content - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */ 163 define('RISK_XSS', 0x0004); 164 /** Capability allows access to personal user information - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */ 165 define('RISK_PERSONAL', 0x0008); 166 /** Capability allows users to add content others may see - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */ 167 define('RISK_SPAM', 0x0010); 168 /** capability allows mass delete of data belonging to other users - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */ 169 define('RISK_DATALOSS', 0x0020); 170 171 /** rolename displays - the name as defined in the role definition, localised if name empty */ 172 define('ROLENAME_ORIGINAL', 0); 173 /** rolename displays - the name as defined by a role alias at the course level, falls back to ROLENAME_ORIGINAL if alias not present */ 174 define('ROLENAME_ALIAS', 1); 175 /** rolename displays - Both, like this: Role alias (Original) */ 176 define('ROLENAME_BOTH', 2); 177 /** rolename displays - the name as defined in the role definition and the shortname in brackets */ 178 define('ROLENAME_ORIGINALANDSHORT', 3); 179 /** rolename displays - the name as defined by a role alias, in raw form suitable for editing */ 180 define('ROLENAME_ALIAS_RAW', 4); 181 /** rolename displays - the name is simply short role name */ 182 define('ROLENAME_SHORT', 5); 183 184 if (!defined('CONTEXT_CACHE_MAX_SIZE')) { 185 /** maximum size of context cache - it is possible to tweak this config.php or in any script before inclusion of context.php */ 186 define('CONTEXT_CACHE_MAX_SIZE', 2500); 187 } 188 189 /** 190 * Although this looks like a global variable, it isn't really. 191 * 192 * It is just a private implementation detail to accesslib that MUST NOT be used elsewhere. 193 * It is used to cache various bits of data between function calls for performance reasons. 194 * Sadly, a PHP global variable is the only way to implement this, without rewriting everything 195 * as methods of a class, instead of functions. 196 * 197 * @access private 198 * @global stdClass $ACCESSLIB_PRIVATE 199 * @name $ACCESSLIB_PRIVATE 200 */ 201 global $ACCESSLIB_PRIVATE; 202 $ACCESSLIB_PRIVATE = new stdClass(); 203 $ACCESSLIB_PRIVATE->dirtycontexts = null; // Dirty contexts cache, loaded from DB once per page 204 $ACCESSLIB_PRIVATE->accessdatabyuser = array(); // Holds the cache of $accessdata structure for users (including $USER) 205 $ACCESSLIB_PRIVATE->rolepermissions = array(); // role permissions cache - helps a lot with mem usage 206 $ACCESSLIB_PRIVATE->capabilities = null; // detailed information about the capabilities 207 208 /** 209 * Clears accesslib's private caches. ONLY BE USED BY UNIT TESTS 210 * 211 * This method should ONLY BE USED BY UNIT TESTS. It clears all of 212 * accesslib's private caches. You need to do this before setting up test data, 213 * and also at the end of the tests. 214 * 215 * @access private 216 * @return void 217 */ 218 function accesslib_clear_all_caches_for_unit_testing() { 219 global $USER; 220 if (!PHPUNIT_TEST) { 221 throw new coding_exception('You must not call clear_all_caches outside of unit tests.'); 222 } 223 224 accesslib_clear_all_caches(true); 225 226 unset($USER->access); 227 } 228 229 /** 230 * Clears accesslib's private caches. ONLY BE USED FROM THIS LIBRARY FILE! 231 * 232 * This reset does not touch global $USER. 233 * 234 * @access private 235 * @param bool $resetcontexts 236 * @return void 237 */ 238 function accesslib_clear_all_caches($resetcontexts) { 239 global $ACCESSLIB_PRIVATE; 240 241 $ACCESSLIB_PRIVATE->dirtycontexts = null; 242 $ACCESSLIB_PRIVATE->accessdatabyuser = array(); 243 $ACCESSLIB_PRIVATE->rolepermissions = array(); 244 $ACCESSLIB_PRIVATE->capabilities = null; 245 246 if ($resetcontexts) { 247 context_helper::reset_caches(); 248 } 249 } 250 251 /** 252 * Gets the accessdata for role "sitewide" (system down to course) 253 * 254 * @access private 255 * @param int $roleid 256 * @return array 257 */ 258 function get_role_access($roleid) { 259 global $DB, $ACCESSLIB_PRIVATE; 260 261 /* Get it in 1 DB query... 262 * - relevant role caps at the root and down 263 * to the course level - but not below 264 */ 265 266 //TODO: MUC - this could be cached in shared memory to speed up first page loading, web crawlers, etc. 267 268 $accessdata = get_empty_accessdata(); 269 270 $accessdata['ra']['/'.SYSCONTEXTID] = array((int)$roleid => (int)$roleid); 271 272 // Overrides for the role IN ANY CONTEXTS down to COURSE - not below -. 273 274 /* 275 $sql = "SELECT ctx.path, 276 rc.capability, rc.permission 277 FROM {context} ctx 278 JOIN {role_capabilities} rc ON rc.contextid = ctx.id 279 LEFT JOIN {context} cctx 280 ON (cctx.contextlevel = ".CONTEXT_COURSE." AND ctx.path LIKE ".$DB->sql_concat('cctx.path',"'/%'").") 281 WHERE rc.roleid = ? AND cctx.id IS NULL"; 282 $params = array($roleid); 283 */ 284 285 // Note: the commented out query is 100% accurate but slow, so let's cheat instead by hardcoding the blocks mess directly. 286 287 $sql = "SELECT COALESCE(ctx.path, bctx.path) AS path, rc.capability, rc.permission 288 FROM {role_capabilities} rc 289 LEFT JOIN {context} ctx ON (ctx.id = rc.contextid AND ctx.contextlevel <= ".CONTEXT_COURSE.") 290 LEFT JOIN ({context} bctx 291 JOIN {block_instances} bi ON (bi.id = bctx.instanceid) 292 JOIN {context} pctx ON (pctx.id = bi.parentcontextid AND pctx.contextlevel < ".CONTEXT_COURSE.") 293 ) ON (bctx.id = rc.contextid AND bctx.contextlevel = ".CONTEXT_BLOCK.") 294 WHERE rc.roleid = :roleid AND (ctx.id IS NOT NULL OR bctx.id IS NOT NULL)"; 295 $params = array('roleid'=>$roleid); 296 297 // we need extra caching in CLI scripts and cron 298 $rs = $DB->get_recordset_sql($sql, $params); 299 foreach ($rs as $rd) { 300 $k = "{$rd->path}:{$roleid}"; 301 $accessdata['rdef'][$k][$rd->capability] = (int)$rd->permission; 302 } 303 $rs->close(); 304 305 // share the role definitions 306 foreach ($accessdata['rdef'] as $k=>$unused) { 307 if (!isset($ACCESSLIB_PRIVATE->rolepermissions[$k])) { 308 $ACCESSLIB_PRIVATE->rolepermissions[$k] = $accessdata['rdef'][$k]; 309 } 310 $accessdata['rdef_count']++; 311 $accessdata['rdef'][$k] =& $ACCESSLIB_PRIVATE->rolepermissions[$k]; 312 } 313 314 return $accessdata; 315 } 316 317 /** 318 * Get the default guest role, this is used for guest account, 319 * search engine spiders, etc. 320 * 321 * @return stdClass role record 322 */ 323 function get_guest_role() { 324 global $CFG, $DB; 325 326 if (empty($CFG->guestroleid)) { 327 if ($roles = $DB->get_records('role', array('archetype'=>'guest'))) { 328 $guestrole = array_shift($roles); // Pick the first one 329 set_config('guestroleid', $guestrole->id); 330 return $guestrole; 331 } else { 332 debugging('Can not find any guest role!'); 333 return false; 334 } 335 } else { 336 if ($guestrole = $DB->get_record('role', array('id'=>$CFG->guestroleid))) { 337 return $guestrole; 338 } else { 339 // somebody is messing with guest roles, remove incorrect setting and try to find a new one 340 set_config('guestroleid', ''); 341 return get_guest_role(); 342 } 343 } 344 } 345 346 /** 347 * Check whether a user has a particular capability in a given context. 348 * 349 * For example: 350 * $context = context_module::instance($cm->id); 351 * has_capability('mod/forum:replypost', $context) 352 * 353 * By default checks the capabilities of the current user, but you can pass a 354 * different userid. By default will return true for admin users, but you can override that with the fourth argument. 355 * 356 * Guest and not-logged-in users can never get any dangerous capability - that is any write capability 357 * or capabilities with XSS, config or data loss risks. 358 * 359 * @category access 360 * 361 * @param string $capability the name of the capability to check. For example mod/forum:view 362 * @param context $context the context to check the capability in. You normally get this with instance method of a context class. 363 * @param integer|stdClass $user A user id or object. By default (null) checks the permissions of the current user. 364 * @param boolean $doanything If false, ignores effect of admin role assignment 365 * @return boolean true if the user has this capability. Otherwise false. 366 */ 367 function has_capability($capability, context $context, $user = null, $doanything = true) { 368 global $USER, $CFG, $SCRIPT, $ACCESSLIB_PRIVATE; 369 370 if (during_initial_install()) { 371 if ($SCRIPT === "/$CFG->admin/index.php" or $SCRIPT === "/$CFG->admin/cli/install.php" or $SCRIPT === "/$CFG->admin/cli/install_database.php") { 372 // we are in an installer - roles can not work yet 373 return true; 374 } else { 375 return false; 376 } 377 } 378 379 if (strpos($capability, 'moodle/legacy:') === 0) { 380 throw new coding_exception('Legacy capabilities can not be used any more!'); 381 } 382 383 if (!is_bool($doanything)) { 384 throw new coding_exception('Capability parameter "doanything" is wierd, only true or false is allowed. This has to be fixed in code.'); 385 } 386 387 // capability must exist 388 if (!$capinfo = get_capability_info($capability)) { 389 debugging('Capability "'.$capability.'" was not found! This has to be fixed in code.'); 390 return false; 391 } 392 393 if (!isset($USER->id)) { 394 // should never happen 395 $USER->id = 0; 396 debugging('Capability check being performed on a user with no ID.', DEBUG_DEVELOPER); 397 } 398 399 // make sure there is a real user specified 400 if ($user === null) { 401 $userid = $USER->id; 402 } else { 403 $userid = is_object($user) ? $user->id : $user; 404 } 405 406 // make sure forcelogin cuts off not-logged-in users if enabled 407 if (!empty($CFG->forcelogin) and $userid == 0) { 408 return false; 409 } 410 411 // make sure the guest account and not-logged-in users never get any risky caps no matter what the actual settings are. 412 if (($capinfo->captype === 'write') or ($capinfo->riskbitmask & (RISK_XSS | RISK_CONFIG | RISK_DATALOSS))) { 413 if (isguestuser($userid) or $userid == 0) { 414 return false; 415 } 416 } 417 418 // somehow make sure the user is not deleted and actually exists 419 if ($userid != 0) { 420 if ($userid == $USER->id and isset($USER->deleted)) { 421 // this prevents one query per page, it is a bit of cheating, 422 // but hopefully session is terminated properly once user is deleted 423 if ($USER->deleted) { 424 return false; 425 } 426 } else { 427 if (!context_user::instance($userid, IGNORE_MISSING)) { 428 // no user context == invalid userid 429 return false; 430 } 431 } 432 } 433 434 // context path/depth must be valid 435 if (empty($context->path) or $context->depth == 0) { 436 // this should not happen often, each upgrade tries to rebuild the context paths 437 debugging('Context id '.$context->id.' does not have valid path, please use build_context_path()'); 438 if (is_siteadmin($userid)) { 439 return true; 440 } else { 441 return false; 442 } 443 } 444 445 // Find out if user is admin - it is not possible to override the doanything in any way 446 // and it is not possible to switch to admin role either. 447 if ($doanything) { 448 if (is_siteadmin($userid)) { 449 if ($userid != $USER->id) { 450 return true; 451 } 452 // make sure switchrole is not used in this context 453 if (empty($USER->access['rsw'])) { 454 return true; 455 } 456 $parts = explode('/', trim($context->path, '/')); 457 $path = ''; 458 $switched = false; 459 foreach ($parts as $part) { 460 $path .= '/' . $part; 461 if (!empty($USER->access['rsw'][$path])) { 462 $switched = true; 463 break; 464 } 465 } 466 if (!$switched) { 467 return true; 468 } 469 //ok, admin switched role in this context, let's use normal access control rules 470 } 471 } 472 473 // Careful check for staleness... 474 $context->reload_if_dirty(); 475 476 if ($USER->id == $userid) { 477 if (!isset($USER->access)) { 478 load_all_capabilities(); 479 } 480 $access =& $USER->access; 481 482 } else { 483 // make sure user accessdata is really loaded 484 get_user_accessdata($userid, true); 485 $access =& $ACCESSLIB_PRIVATE->accessdatabyuser[$userid]; 486 } 487 488 489 // Load accessdata for below-the-course context if necessary, 490 // all contexts at and above all courses are already loaded 491 if ($context->contextlevel != CONTEXT_COURSE and $coursecontext = $context->get_course_context(false)) { 492 load_course_context($userid, $coursecontext, $access); 493 } 494 495 return has_capability_in_accessdata($capability, $context, $access); 496 } 497 498 /** 499 * Check if the user has any one of several capabilities from a list. 500 * 501 * This is just a utility method that calls has_capability in a loop. Try to put 502 * the capabilities that most users are likely to have first in the list for best 503 * performance. 504 * 505 * @category access 506 * @see has_capability() 507 * 508 * @param array $capabilities an array of capability names. 509 * @param context $context the context to check the capability in. You normally get this with instance method of a context class. 510 * @param integer|stdClass $user A user id or object. By default (null) checks the permissions of the current user. 511 * @param boolean $doanything If false, ignore effect of admin role assignment 512 * @return boolean true if the user has any of these capabilities. Otherwise false. 513 */ 514 function has_any_capability(array $capabilities, context $context, $user = null, $doanything = true) { 515 foreach ($capabilities as $capability) { 516 if (has_capability($capability, $context, $user, $doanything)) { 517 return true; 518 } 519 } 520 return false; 521 } 522 523 /** 524 * Check if the user has all the capabilities in a list. 525 * 526 * This is just a utility method that calls has_capability in a loop. Try to put 527 * the capabilities that fewest users are likely to have first in the list for best 528 * performance. 529 * 530 * @category access 531 * @see has_capability() 532 * 533 * @param array $capabilities an array of capability names. 534 * @param context $context the context to check the capability in. You normally get this with instance method of a context class. 535 * @param integer|stdClass $user A user id or object. By default (null) checks the permissions of the current user. 536 * @param boolean $doanything If false, ignore effect of admin role assignment 537 * @return boolean true if the user has all of these capabilities. Otherwise false. 538 */ 539 function has_all_capabilities(array $capabilities, context $context, $user = null, $doanything = true) { 540 foreach ($capabilities as $capability) { 541 if (!has_capability($capability, $context, $user, $doanything)) { 542 return false; 543 } 544 } 545 return true; 546 } 547 548 /** 549 * Is course creator going to have capability in a new course? 550 * 551 * This is intended to be used in enrolment plugins before or during course creation, 552 * do not use after the course is fully created. 553 * 554 * @category access 555 * 556 * @param string $capability the name of the capability to check. 557 * @param context $context course or category context where is course going to be created 558 * @param integer|stdClass $user A user id or object. By default (null) checks the permissions of the current user. 559 * @return boolean true if the user will have this capability. 560 * 561 * @throws coding_exception if different type of context submitted 562 */ 563 function guess_if_creator_will_have_course_capability($capability, context $context, $user = null) { 564 global $CFG; 565 566 if ($context->contextlevel != CONTEXT_COURSE and $context->contextlevel != CONTEXT_COURSECAT) { 567 throw new coding_exception('Only course or course category context expected'); 568 } 569 570 if (has_capability($capability, $context, $user)) { 571 // User already has the capability, it could be only removed if CAP_PROHIBIT 572 // was involved here, but we ignore that. 573 return true; 574 } 575 576 if (!has_capability('moodle/course:create', $context, $user)) { 577 return false; 578 } 579 580 if (!enrol_is_enabled('manual')) { 581 return false; 582 } 583 584 if (empty($CFG->creatornewroleid)) { 585 return false; 586 } 587 588 if ($context->contextlevel == CONTEXT_COURSE) { 589 if (is_viewing($context, $user, 'moodle/role:assign') or is_enrolled($context, $user, 'moodle/role:assign')) { 590 return false; 591 } 592 } else { 593 if (has_capability('moodle/course:view', $context, $user) and has_capability('moodle/role:assign', $context, $user)) { 594 return false; 595 } 596 } 597 598 // Most likely they will be enrolled after the course creation is finished, 599 // does the new role have the required capability? 600 list($neededroles, $forbiddenroles) = get_roles_with_cap_in_context($context, $capability); 601 return isset($neededroles[$CFG->creatornewroleid]); 602 } 603 604 /** 605 * Check if the user is an admin at the site level. 606 * 607 * Please note that use of proper capabilities is always encouraged, 608 * this function is supposed to be used from core or for temporary hacks. 609 * 610 * @category access 611 * 612 * @param int|stdClass $user_or_id user id or user object 613 * @return bool true if user is one of the administrators, false otherwise 614 */ 615 function is_siteadmin($user_or_id = null) { 616 global $CFG, $USER; 617 618 if ($user_or_id === null) { 619 $user_or_id = $USER; 620 } 621 622 if (empty($user_or_id)) { 623 return false; 624 } 625 if (!empty($user_or_id->id)) { 626 $userid = $user_or_id->id; 627 } else { 628 $userid = $user_or_id; 629 } 630 631 // Because this script is called many times (150+ for course page) with 632 // the same parameters, it is worth doing minor optimisations. This static 633 // cache stores the value for a single userid, saving about 2ms from course 634 // page load time without using significant memory. As the static cache 635 // also includes the value it depends on, this cannot break unit tests. 636 static $knownid, $knownresult, $knownsiteadmins; 637 if ($knownid === $userid && $knownsiteadmins === $CFG->siteadmins) { 638 return $knownresult; 639 } 640 $knownid = $userid; 641 $knownsiteadmins = $CFG->siteadmins; 642 643 $siteadmins = explode(',', $CFG->siteadmins); 644 $knownresult = in_array($userid, $siteadmins); 645 return $knownresult; 646 } 647 648 /** 649 * Returns true if user has at least one role assign 650 * of 'coursecontact' role (is potentially listed in some course descriptions). 651 * 652 * @param int $userid 653 * @return bool 654 */ 655 function has_coursecontact_role($userid) { 656 global $DB, $CFG; 657 658 if (empty($CFG->coursecontact)) { 659 return false; 660 } 661 $sql = "SELECT 1 662 FROM {role_assignments} 663 WHERE userid = :userid AND roleid IN ($CFG->coursecontact)"; 664 return $DB->record_exists_sql($sql, array('userid'=>$userid)); 665 } 666 667 /** 668 * Does the user have a capability to do something? 669 * 670 * Walk the accessdata array and return true/false. 671 * Deals with prohibits, role switching, aggregating 672 * capabilities, etc. 673 * 674 * The main feature of here is being FAST and with no 675 * side effects. 676 * 677 * Notes: 678 * 679 * Switch Role merges with default role 680 * ------------------------------------ 681 * If you are a teacher in course X, you have at least 682 * teacher-in-X + defaultloggedinuser-sitewide. So in the 683 * course you'll have techer+defaultloggedinuser. 684 * We try to mimic that in switchrole. 685 * 686 * Permission evaluation 687 * --------------------- 688 * Originally there was an extremely complicated way 689 * to determine the user access that dealt with 690 * "locality" or role assignments and role overrides. 691 * Now we simply evaluate access for each role separately 692 * and then verify if user has at least one role with allow 693 * and at the same time no role with prohibit. 694 * 695 * @access private 696 * @param string $capability 697 * @param context $context 698 * @param array $accessdata 699 * @return bool 700 */ 701 function has_capability_in_accessdata($capability, context $context, array &$accessdata) { 702 global $CFG; 703 704 // Build $paths as a list of current + all parent "paths" with order bottom-to-top 705 $path = $context->path; 706 $paths = array($path); 707 while($path = rtrim($path, '0123456789')) { 708 $path = rtrim($path, '/'); 709 if ($path === '') { 710 break; 711 } 712 $paths[] = $path; 713 } 714 715 $roles = array(); 716 $switchedrole = false; 717 718 // Find out if role switched 719 if (!empty($accessdata['rsw'])) { 720 // From the bottom up... 721 foreach ($paths as $path) { 722 if (isset($accessdata['rsw'][$path])) { 723 // Found a switchrole assignment - check for that role _plus_ the default user role 724 $roles = array($accessdata['rsw'][$path]=>null, $CFG->defaultuserroleid=>null); 725 $switchedrole = true; 726 break; 727 } 728 } 729 } 730 731 if (!$switchedrole) { 732 // get all users roles in this context and above 733 foreach ($paths as $path) { 734 if (isset($accessdata['ra'][$path])) { 735 foreach ($accessdata['ra'][$path] as $roleid) { 736 $roles[$roleid] = null; 737 } 738 } 739 } 740 } 741 742 // Now find out what access is given to each role, going bottom-->up direction 743 $allowed = false; 744 foreach ($roles as $roleid => $ignored) { 745 foreach ($paths as $path) { 746 if (isset($accessdata['rdef']["{$path}:$roleid"][$capability])) { 747 $perm = (int)$accessdata['rdef']["{$path}:$roleid"][$capability]; 748 if ($perm === CAP_PROHIBIT) { 749 // any CAP_PROHIBIT found means no permission for the user 750 return false; 751 } 752 if (is_null($roles[$roleid])) { 753 $roles[$roleid] = $perm; 754 } 755 } 756 } 757 // CAP_ALLOW in any role means the user has a permission, we continue only to detect prohibits 758 $allowed = ($allowed or $roles[$roleid] === CAP_ALLOW); 759 } 760 761 return $allowed; 762 } 763 764 /** 765 * A convenience function that tests has_capability, and displays an error if 766 * the user does not have that capability. 767 * 768 * NOTE before Moodle 2.0, this function attempted to make an appropriate 769 * require_login call before checking the capability. This is no longer the case. 770 * You must call require_login (or one of its variants) if you want to check the 771 * user is logged in, before you call this function. 772 * 773 * @see has_capability() 774 * 775 * @param string $capability the name of the capability to check. For example mod/forum:view 776 * @param context $context the context to check the capability in. You normally get this with context_xxxx::instance(). 777 * @param int $userid A user id. By default (null) checks the permissions of the current user. 778 * @param bool $doanything If false, ignore effect of admin role assignment 779 * @param string $errormessage The error string to to user. Defaults to 'nopermissions'. 780 * @param string $stringfile The language file to load the error string from. Defaults to 'error'. 781 * @return void terminates with an error if the user does not have the given capability. 782 */ 783 function require_capability($capability, context $context, $userid = null, $doanything = true, 784 $errormessage = 'nopermissions', $stringfile = '') { 785 if (!has_capability($capability, $context, $userid, $doanything)) { 786 throw new required_capability_exception($context, $capability, $errormessage, $stringfile); 787 } 788 } 789 790 /** 791 * Return a nested array showing role assignments 792 * all relevant role capabilities for the user at 793 * site/course_category/course levels 794 * 795 * We do _not_ delve deeper than courses because the number of 796 * overrides at the module/block levels can be HUGE. 797 * 798 * [ra] => [/path][roleid]=roleid 799 * [rdef] => [/path:roleid][capability]=permission 800 * 801 * @access private 802 * @param int $userid - the id of the user 803 * @return array access info array 804 */ 805 function get_user_access_sitewide($userid) { 806 global $CFG, $DB, $ACCESSLIB_PRIVATE; 807 808 /* Get in a few cheap DB queries... 809 * - role assignments 810 * - relevant role caps 811 * - above and within this user's RAs 812 * - below this user's RAs - limited to course level 813 */ 814 815 // raparents collects paths & roles we need to walk up the parenthood to build the minimal rdef 816 $raparents = array(); 817 $accessdata = get_empty_accessdata(); 818 819 // start with the default role 820 if (!empty($CFG->defaultuserroleid)) { 821 $syscontext = context_system::instance(); 822 $accessdata['ra'][$syscontext->path][(int)$CFG->defaultuserroleid] = (int)$CFG->defaultuserroleid; 823 $raparents[$CFG->defaultuserroleid][$syscontext->id] = $syscontext->id; 824 } 825 826 // load the "default frontpage role" 827 if (!empty($CFG->defaultfrontpageroleid)) { 828 $frontpagecontext = context_course::instance(get_site()->id); 829 if ($frontpagecontext->path) { 830 $accessdata['ra'][$frontpagecontext->path][(int)$CFG->defaultfrontpageroleid] = (int)$CFG->defaultfrontpageroleid; 831 $raparents[$CFG->defaultfrontpageroleid][$frontpagecontext->id] = $frontpagecontext->id; 832 } 833 } 834 835 // preload every assigned role at and above course context 836 $sql = "SELECT ctx.path, ra.roleid, ra.contextid 837 FROM {role_assignments} ra 838 JOIN {context} ctx 839 ON ctx.id = ra.contextid 840 LEFT JOIN {block_instances} bi 841 ON (ctx.contextlevel = ".CONTEXT_BLOCK." AND bi.id = ctx.instanceid) 842 LEFT JOIN {context} bpctx 843 ON (bpctx.id = bi.parentcontextid) 844 WHERE ra.userid = :userid 845 AND (ctx.contextlevel <= ".CONTEXT_COURSE." OR bpctx.contextlevel < ".CONTEXT_COURSE.")"; 846 $params = array('userid'=>$userid); 847 $rs = $DB->get_recordset_sql($sql, $params); 848 foreach ($rs as $ra) { 849 // RAs leafs are arrays to support multi-role assignments... 850 $accessdata['ra'][$ra->path][(int)$ra->roleid] = (int)$ra->roleid; 851 $raparents[$ra->roleid][$ra->contextid] = $ra->contextid; 852 } 853 $rs->close(); 854 855 if (empty($raparents)) { 856 return $accessdata; 857 } 858 859 // now get overrides of interesting roles in all interesting child contexts 860 // hopefully we will not run out of SQL limits here, 861 // users would have to have very many roles at/above course context... 862 $sqls = array(); 863 $params = array(); 864 865 static $cp = 0; 866 foreach ($raparents as $roleid=>$ras) { 867 $cp++; 868 list($sqlcids, $cids) = $DB->get_in_or_equal($ras, SQL_PARAMS_NAMED, 'c'.$cp.'_'); 869 $params = array_merge($params, $cids); 870 $params['r'.$cp] = $roleid; 871 $sqls[] = "(SELECT ctx.path, rc.roleid, rc.capability, rc.permission 872 FROM {role_capabilities} rc 873 JOIN {context} ctx 874 ON (ctx.id = rc.contextid) 875 JOIN {context} pctx 876 ON (pctx.id $sqlcids 877 AND (ctx.id = pctx.id 878 OR ctx.path LIKE ".$DB->sql_concat('pctx.path',"'/%'")." 879 OR pctx.path LIKE ".$DB->sql_concat('ctx.path',"'/%'").")) 880 LEFT JOIN {block_instances} bi 881 ON (ctx.contextlevel = ".CONTEXT_BLOCK." AND bi.id = ctx.instanceid) 882 LEFT JOIN {context} bpctx 883 ON (bpctx.id = bi.parentcontextid) 884 WHERE rc.roleid = :r{$cp} 885 AND (ctx.contextlevel <= ".CONTEXT_COURSE." OR bpctx.contextlevel < ".CONTEXT_COURSE.") 886 )"; 887 } 888 889 // fixed capability order is necessary for rdef dedupe 890 $rs = $DB->get_recordset_sql(implode("\nUNION\n", $sqls). "ORDER BY capability", $params); 891 892 foreach ($rs as $rd) { 893 $k = $rd->path.':'.$rd->roleid; 894 $accessdata['rdef'][$k][$rd->capability] = (int)$rd->permission; 895 } 896 $rs->close(); 897 898 // share the role definitions 899 foreach ($accessdata['rdef'] as $k=>$unused) { 900 if (!isset($ACCESSLIB_PRIVATE->rolepermissions[$k])) { 901 $ACCESSLIB_PRIVATE->rolepermissions[$k] = $accessdata['rdef'][$k]; 902 } 903 $accessdata['rdef_count']++; 904 $accessdata['rdef'][$k] =& $ACCESSLIB_PRIVATE->rolepermissions[$k]; 905 } 906 907 return $accessdata; 908 } 909 910 /** 911 * Add to the access ctrl array the data needed by a user for a given course. 912 * 913 * This function injects all course related access info into the accessdata array. 914 * 915 * @access private 916 * @param int $userid the id of the user 917 * @param context_course $coursecontext course context 918 * @param array $accessdata accessdata array (modified) 919 * @return void modifies $accessdata parameter 920 */ 921 function load_course_context($userid, context_course $coursecontext, &$accessdata) { 922 global $DB, $CFG, $ACCESSLIB_PRIVATE; 923 924 if (empty($coursecontext->path)) { 925 // weird, this should not happen 926 return; 927 } 928 929 if (isset($accessdata['loaded'][$coursecontext->instanceid])) { 930 // already loaded, great! 931 return; 932 } 933 934 $roles = array(); 935 936 if (empty($userid)) { 937 if (!empty($CFG->notloggedinroleid)) { 938 $roles[$CFG->notloggedinroleid] = $CFG->notloggedinroleid; 939 } 940 941 } else if (isguestuser($userid)) { 942 if ($guestrole = get_guest_role()) { 943 $roles[$guestrole->id] = $guestrole->id; 944 } 945 946 } else { 947 // Interesting role assignments at, above and below the course context 948 list($parentsaself, $params) = $DB->get_in_or_equal($coursecontext->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'pc_'); 949 $params['userid'] = $userid; 950 $params['children'] = $coursecontext->path."/%"; 951 $sql = "SELECT ra.*, ctx.path 952 FROM {role_assignments} ra 953 JOIN {context} ctx ON ra.contextid = ctx.id 954 WHERE ra.userid = :userid AND (ctx.id $parentsaself OR ctx.path LIKE :children)"; 955 $rs = $DB->get_recordset_sql($sql, $params); 956 957 // add missing role definitions 958 foreach ($rs as $ra) { 959 $accessdata['ra'][$ra->path][(int)$ra->roleid] = (int)$ra->roleid; 960 $roles[$ra->roleid] = $ra->roleid; 961 } 962 $rs->close(); 963 964 // add the "default frontpage role" when on the frontpage 965 if (!empty($CFG->defaultfrontpageroleid)) { 966 $frontpagecontext = context_course::instance(get_site()->id); 967 if ($frontpagecontext->id == $coursecontext->id) { 968 $roles[$CFG->defaultfrontpageroleid] = $CFG->defaultfrontpageroleid; 969 } 970 } 971 972 // do not forget the default role 973 if (!empty($CFG->defaultuserroleid)) { 974 $roles[$CFG->defaultuserroleid] = $CFG->defaultuserroleid; 975 } 976 } 977 978 if (!$roles) { 979 // weird, default roles must be missing... 980 $accessdata['loaded'][$coursecontext->instanceid] = 1; 981 return; 982 } 983 984 // now get overrides of interesting roles in all interesting contexts (this course + children + parents) 985 $params = array('c'=>$coursecontext->id); 986 list($parentsaself, $rparams) = $DB->get_in_or_equal($coursecontext->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'pc_'); 987 $params = array_merge($params, $rparams); 988 list($roleids, $rparams) = $DB->get_in_or_equal($roles, SQL_PARAMS_NAMED, 'r_'); 989 $params = array_merge($params, $rparams); 990 991 $sql = "SELECT ctx.path, rc.roleid, rc.capability, rc.permission 992 FROM {role_capabilities} rc 993 JOIN {context} ctx 994 ON (ctx.id = rc.contextid) 995 JOIN {context} cctx 996 ON (cctx.id = :c 997 AND (ctx.id $parentsaself OR ctx.path LIKE ".$DB->sql_concat('cctx.path',"'/%'").")) 998 WHERE rc.roleid $roleids 999 ORDER BY rc.capability"; // fixed capability order is necessary for rdef dedupe 1000 $rs = $DB->get_recordset_sql($sql, $params); 1001 1002 $newrdefs = array(); 1003 foreach ($rs as $rd) { 1004 $k = $rd->path.':'.$rd->roleid; 1005 if (isset($accessdata['rdef'][$k])) { 1006 continue; 1007 } 1008 $newrdefs[$k][$rd->capability] = (int)$rd->permission; 1009 } 1010 $rs->close(); 1011 1012 // share new role definitions 1013 foreach ($newrdefs as $k=>$unused) { 1014 if (!isset($ACCESSLIB_PRIVATE->rolepermissions[$k])) { 1015 $ACCESSLIB_PRIVATE->rolepermissions[$k] = $newrdefs[$k]; 1016 } 1017 $accessdata['rdef_count']++; 1018 $accessdata['rdef'][$k] =& $ACCESSLIB_PRIVATE->rolepermissions[$k]; 1019 } 1020 1021 $accessdata['loaded'][$coursecontext->instanceid] = 1; 1022 1023 // we want to deduplicate the USER->access from time to time, this looks like a good place, 1024 // because we have to do it before the end of session 1025 dedupe_user_access(); 1026 } 1027 1028 /** 1029 * Add to the access ctrl array the data needed by a role for a given context. 1030 * 1031 * The data is added in the rdef key. 1032 * This role-centric function is useful for role_switching 1033 * and temporary course roles. 1034 * 1035 * @access private 1036 * @param int $roleid the id of the user 1037 * @param context $context needs path! 1038 * @param array $accessdata accessdata array (is modified) 1039 * @return array 1040 */ 1041 function load_role_access_by_context($roleid, context $context, &$accessdata) { 1042 global $DB, $ACCESSLIB_PRIVATE; 1043 1044 /* Get the relevant rolecaps into rdef 1045 * - relevant role caps 1046 * - at ctx and above 1047 * - below this ctx 1048 */ 1049 1050 if (empty($context->path)) { 1051 // weird, this should not happen 1052 return; 1053 } 1054 1055 list($parentsaself, $params) = $DB->get_in_or_equal($context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'pc_'); 1056 $params['roleid'] = $roleid; 1057 $params['childpath'] = $context->path.'/%'; 1058 1059 $sql = "SELECT ctx.path, rc.capability, rc.permission 1060 FROM {role_capabilities} rc 1061 JOIN {context} ctx ON (rc.contextid = ctx.id) 1062 WHERE rc.roleid = :roleid AND (ctx.id $parentsaself OR ctx.path LIKE :childpath) 1063 ORDER BY rc.capability"; // fixed capability order is necessary for rdef dedupe 1064 $rs = $DB->get_recordset_sql($sql, $params); 1065 1066 $newrdefs = array(); 1067 foreach ($rs as $rd) { 1068 $k = $rd->path.':'.$roleid; 1069 if (isset($accessdata['rdef'][$k])) { 1070 continue; 1071 } 1072 $newrdefs[$k][$rd->capability] = (int)$rd->permission; 1073 } 1074 $rs->close(); 1075 1076 // share new role definitions 1077 foreach ($newrdefs as $k=>$unused) { 1078 if (!isset($ACCESSLIB_PRIVATE->rolepermissions[$k])) { 1079 $ACCESSLIB_PRIVATE->rolepermissions[$k] = $newrdefs[$k]; 1080 } 1081 $accessdata['rdef_count']++; 1082 $accessdata['rdef'][$k] =& $ACCESSLIB_PRIVATE->rolepermissions[$k]; 1083 } 1084 } 1085 1086 /** 1087 * Returns empty accessdata structure. 1088 * 1089 * @access private 1090 * @return array empt accessdata 1091 */ 1092 function get_empty_accessdata() { 1093 $accessdata = array(); // named list 1094 $accessdata['ra'] = array(); 1095 $accessdata['rdef'] = array(); 1096 $accessdata['rdef_count'] = 0; // this bloody hack is necessary because count($array) is slooooowwww in PHP 1097 $accessdata['rdef_lcc'] = 0; // rdef_count during the last compression 1098 $accessdata['loaded'] = array(); // loaded course contexts 1099 $accessdata['time'] = time(); 1100 $accessdata['rsw'] = array(); 1101 1102 return $accessdata; 1103 } 1104 1105 /** 1106 * Get accessdata for a given user. 1107 * 1108 * @access private 1109 * @param int $userid 1110 * @param bool $preloadonly true means do not return access array 1111 * @return array accessdata 1112 */ 1113 function get_user_accessdata($userid, $preloadonly=false) { 1114 global $CFG, $ACCESSLIB_PRIVATE, $USER; 1115 1116 if (!empty($USER->access['rdef']) and empty($ACCESSLIB_PRIVATE->rolepermissions)) { 1117 // share rdef from USER session with rolepermissions cache in order to conserve memory 1118 foreach ($USER->access['rdef'] as $k=>$v) { 1119 $ACCESSLIB_PRIVATE->rolepermissions[$k] =& $USER->access['rdef'][$k]; 1120 } 1121 $ACCESSLIB_PRIVATE->accessdatabyuser[$USER->id] = $USER->access; 1122 } 1123 1124 if (!isset($ACCESSLIB_PRIVATE->accessdatabyuser[$userid])) { 1125 if (empty($userid)) { 1126 if (!empty($CFG->notloggedinroleid)) { 1127 $accessdata = get_role_access($CFG->notloggedinroleid); 1128 } else { 1129 // weird 1130 return get_empty_accessdata(); 1131 } 1132 1133 } else if (isguestuser($userid)) { 1134 if ($guestrole = get_guest_role()) { 1135 $accessdata = get_role_access($guestrole->id); 1136 } else { 1137 //weird 1138 return get_empty_accessdata(); 1139 } 1140 1141 } else { 1142 $accessdata = get_user_access_sitewide($userid); // includes default role and frontpage role 1143 } 1144 1145 $ACCESSLIB_PRIVATE->accessdatabyuser[$userid] = $accessdata; 1146 } 1147 1148 if ($preloadonly) { 1149 return; 1150 } else { 1151 return $ACCESSLIB_PRIVATE->accessdatabyuser[$userid]; 1152 } 1153 } 1154 1155 /** 1156 * Try to minimise the size of $USER->access by eliminating duplicate override storage, 1157 * this function looks for contexts with the same overrides and shares them. 1158 * 1159 * @access private 1160 * @return void 1161 */ 1162 function dedupe_user_access() { 1163 global $USER; 1164 1165 if (CLI_SCRIPT) { 1166 // no session in CLI --> no compression necessary 1167 return; 1168 } 1169 1170 if (empty($USER->access['rdef_count'])) { 1171 // weird, this should not happen 1172 return; 1173 } 1174 1175 // the rdef is growing only, we never remove stuff from it, the rdef_lcc helps us to detect new stuff in rdef 1176 if ($USER->access['rdef_count'] - $USER->access['rdef_lcc'] > 10) { 1177 // do not compress after each change, wait till there is more stuff to be done 1178 return; 1179 } 1180 1181 $hashmap = array(); 1182 foreach ($USER->access['rdef'] as $k=>$def) { 1183 $hash = sha1(serialize($def)); 1184 if (isset($hashmap[$hash])) { 1185 $USER->access['rdef'][$k] =& $hashmap[$hash]; 1186 } else { 1187 $hashmap[$hash] =& $USER->access['rdef'][$k]; 1188 } 1189 } 1190 1191 $USER->access['rdef_lcc'] = $USER->access['rdef_count']; 1192 } 1193 1194 /** 1195 * A convenience function to completely load all the capabilities 1196 * for the current user. It is called from has_capability() and functions change permissions. 1197 * 1198 * Call it only _after_ you've setup $USER and called check_enrolment_plugins(); 1199 * @see check_enrolment_plugins() 1200 * 1201 * @access private 1202 * @return void 1203 */ 1204 function load_all_capabilities() { 1205 global $USER; 1206 1207 // roles not installed yet - we are in the middle of installation 1208 if (during_initial_install()) { 1209 return; 1210 } 1211 1212 if (!isset($USER->id)) { 1213 // this should not happen 1214 $USER->id = 0; 1215 } 1216 1217 unset($USER->access); 1218 $USER->access = get_user_accessdata($USER->id); 1219 1220 // deduplicate the overrides to minimize session size 1221 dedupe_user_access(); 1222 1223 // Clear to force a refresh 1224 unset($USER->mycourses); 1225 1226 // init/reset internal enrol caches - active course enrolments and temp access 1227 $USER->enrol = array('enrolled'=>array(), 'tempguest'=>array()); 1228 } 1229 1230 /** 1231 * A convenience function to completely reload all the capabilities 1232 * for the current user when roles have been updated in a relevant 1233 * context -- but PRESERVING switchroles and loginas. 1234 * This function resets all accesslib and context caches. 1235 * 1236 * That is - completely transparent to the user. 1237 * 1238 * Note: reloads $USER->access completely. 1239 * 1240 * @access private 1241 * @return void 1242 */ 1243 function reload_all_capabilities() { 1244 global $USER, $DB, $ACCESSLIB_PRIVATE; 1245 1246 // copy switchroles 1247 $sw = array(); 1248 if (!empty($USER->access['rsw'])) { 1249 $sw = $USER->access['rsw']; 1250 } 1251 1252 accesslib_clear_all_caches(true); 1253 unset($USER->access); 1254 $ACCESSLIB_PRIVATE->dirtycontexts = array(); // prevent dirty flags refetching on this page 1255 1256 load_all_capabilities(); 1257 1258 foreach ($sw as $path => $roleid) { 1259 if ($record = $DB->get_record('context', array('path'=>$path))) { 1260 $context = context::instance_by_id($record->id); 1261 role_switch($roleid, $context); 1262 } 1263 } 1264 } 1265 1266 /** 1267 * Adds a temp role to current USER->access array. 1268 * 1269 * Useful for the "temporary guest" access we grant to logged-in users. 1270 * This is useful for enrol plugins only. 1271 * 1272 * @since Moodle 2.2 1273 * @param context_course $coursecontext 1274 * @param int $roleid 1275 * @return void 1276 */ 1277 function load_temp_course_role(context_course $coursecontext, $roleid) { 1278 global $USER, $SITE; 1279 1280 if (empty($roleid)) { 1281 debugging('invalid role specified in load_temp_course_role()'); 1282 return; 1283 } 1284 1285 if ($coursecontext->instanceid == $SITE->id) { 1286 debugging('Can not use temp roles on the frontpage'); 1287 return; 1288 } 1289 1290 if (!isset($USER->access)) { 1291 load_all_capabilities(); 1292 } 1293 1294 $coursecontext->reload_if_dirty(); 1295 1296 if (isset($USER->access['ra'][$coursecontext->path][$roleid])) { 1297 return; 1298 } 1299 1300 // load course stuff first 1301 load_course_context($USER->id, $coursecontext, $USER->access); 1302 1303 $USER->access['ra'][$coursecontext->path][(int)$roleid] = (int)$roleid; 1304 1305 load_role_access_by_context($roleid, $coursecontext, $USER->access); 1306 } 1307 1308 /** 1309 * Removes any extra guest roles from current USER->access array. 1310 * This is useful for enrol plugins only. 1311 * 1312 * @since Moodle 2.2 1313 * @param context_course $coursecontext 1314 * @return void 1315 */ 1316 function remove_temp_course_roles(context_course $coursecontext) { 1317 global $DB, $USER, $SITE; 1318 1319 if ($coursecontext->instanceid == $SITE->id) { 1320 debugging('Can not use temp roles on the frontpage'); 1321 return; 1322 } 1323 1324 if (empty($USER->access['ra'][$coursecontext->path])) { 1325 //no roles here, weird 1326 return; 1327 } 1328 1329 $sql = "SELECT DISTINCT ra.roleid AS id 1330 FROM {role_assignments} ra 1331 WHERE ra.contextid = :contextid AND ra.userid = :userid"; 1332 $ras = $DB->get_records_sql($sql, array('contextid'=>$coursecontext->id, 'userid'=>$USER->id)); 1333 1334 $USER->access['ra'][$coursecontext->path] = array(); 1335 foreach($ras as $r) { 1336 $USER->access['ra'][$coursecontext->path][(int)$r->id] = (int)$r->id; 1337 } 1338 } 1339 1340 /** 1341 * Returns array of all role archetypes. 1342 * 1343 * @return array 1344 */ 1345 function get_role_archetypes() { 1346 return array( 1347 'manager' => 'manager', 1348 'coursecreator' => 'coursecreator', 1349 'editingteacher' => 'editingteacher', 1350 'teacher' => 'teacher', 1351 'student' => 'student', 1352 'guest' => 'guest', 1353 'user' => 'user', 1354 'frontpage' => 'frontpage' 1355 ); 1356 } 1357 1358 /** 1359 * Assign the defaults found in this capability definition to roles that have 1360 * the corresponding legacy capabilities assigned to them. 1361 * 1362 * @param string $capability 1363 * @param array $legacyperms an array in the format (example): 1364 * 'guest' => CAP_PREVENT, 1365 * 'student' => CAP_ALLOW, 1366 * 'teacher' => CAP_ALLOW, 1367 * 'editingteacher' => CAP_ALLOW, 1368 * 'coursecreator' => CAP_ALLOW, 1369 * 'manager' => CAP_ALLOW 1370 * @return boolean success or failure. 1371 */ 1372 function assign_legacy_capabilities($capability, $legacyperms) { 1373 1374 $archetypes = get_role_archetypes(); 1375 1376 foreach ($legacyperms as $type => $perm) { 1377 1378 $systemcontext = context_system::instance(); 1379 if ($type === 'admin') { 1380 debugging('Legacy type admin in access.php was renamed to manager, please update the code.'); 1381 $type = 'manager'; 1382 } 1383 1384 if (!array_key_exists($type, $archetypes)) { 1385 print_error('invalidlegacy', '', '', $type); 1386 } 1387 1388 if ($roles = get_archetype_roles($type)) { 1389 foreach ($roles as $role) { 1390 // Assign a site level capability. 1391 if (!assign_capability($capability, $perm, $role->id, $systemcontext->id)) { 1392 return false; 1393 } 1394 } 1395 } 1396 } 1397 return true; 1398 } 1399 1400 /** 1401 * Verify capability risks. 1402 * 1403 * @param stdClass $capability a capability - a row from the capabilities table. 1404 * @return boolean whether this capability is safe - that is, whether people with the 1405 * safeoverrides capability should be allowed to change it. 1406 */ 1407 function is_safe_capability($capability) { 1408 return !((RISK_DATALOSS | RISK_MANAGETRUST | RISK_CONFIG | RISK_XSS | RISK_PERSONAL) & $capability->riskbitmask); 1409 } 1410 1411 /** 1412 * Get the local override (if any) for a given capability in a role in a context 1413 * 1414 * @param int $roleid 1415 * @param int $contextid 1416 * @param string $capability 1417 * @return stdClass local capability override 1418 */ 1419 function get_local_override($roleid, $contextid, $capability) { 1420 global $DB; 1421 return $DB->get_record('role_capabilities', array('roleid'=>$roleid, 'capability'=>$capability, 'contextid'=>$contextid)); 1422 } 1423 1424 /** 1425 * Returns context instance plus related course and cm instances 1426 * 1427 * @param int $contextid 1428 * @return array of ($context, $course, $cm) 1429 */ 1430 function get_context_info_array($contextid) { 1431 global $DB; 1432 1433 $context = context::instance_by_id($contextid, MUST_EXIST); 1434 $course = null; 1435 $cm = null; 1436 1437 if ($context->contextlevel == CONTEXT_COURSE) { 1438 $course = $DB->get_record('course', array('id'=>$context->instanceid), '*', MUST_EXIST); 1439 1440 } else if ($context->contextlevel == CONTEXT_MODULE) { 1441 $cm = get_coursemodule_from_id('', $context->instanceid, 0, false, MUST_EXIST); 1442 $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST); 1443 1444 } else if ($context->contextlevel == CONTEXT_BLOCK) { 1445 $parent = $context->get_parent_context(); 1446 1447 if ($parent->contextlevel == CONTEXT_COURSE) { 1448 $course = $DB->get_record('course', array('id'=>$parent->instanceid), '*', MUST_EXIST); 1449 } else if ($parent->contextlevel == CONTEXT_MODULE) { 1450 $cm = get_coursemodule_from_id('', $parent->instanceid, 0, false, MUST_EXIST); 1451 $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST); 1452 } 1453 } 1454 1455 return array($context, $course, $cm); 1456 } 1457 1458 /** 1459 * Function that creates a role 1460 * 1461 * @param string $name role name 1462 * @param string $shortname role short name 1463 * @param string $description role description 1464 * @param string $archetype 1465 * @return int id or dml_exception 1466 */ 1467 function create_role($name, $shortname, $description, $archetype = '') { 1468 global $DB; 1469 1470 if (strpos($archetype, 'moodle/legacy:') !== false) { 1471 throw new coding_exception('Use new role archetype parameter in create_role() instead of old legacy capabilities.'); 1472 } 1473 1474 // verify role archetype actually exists 1475 $archetypes = get_role_archetypes(); 1476 if (empty($archetypes[$archetype])) { 1477 $archetype = ''; 1478 } 1479 1480 // Insert the role record. 1481 $role = new stdClass(); 1482 $role->name = $name; 1483 $role->shortname = $shortname; 1484 $role->description = $description; 1485 $role->archetype = $archetype; 1486 1487 //find free sortorder number 1488 $role->sortorder = $DB->get_field('role', 'MAX(sortorder) + 1', array()); 1489 if (empty($role->sortorder)) { 1490 $role->sortorder = 1; 1491 } 1492 $id = $DB->insert_record('role', $role); 1493 1494 return $id; 1495 } 1496 1497 /** 1498 * Function that deletes a role and cleanups up after it 1499 * 1500 * @param int $roleid id of role to delete 1501 * @return bool always true 1502 */ 1503 function delete_role($roleid) { 1504 global $DB; 1505 1506 // first unssign all users 1507 role_unassign_all(array('roleid'=>$roleid)); 1508 1509 // cleanup all references to this role, ignore errors 1510 $DB->delete_records('role_capabilities', array('roleid'=>$roleid)); 1511 $DB->delete_records('role_allow_assign', array('roleid'=>$roleid)); 1512 $DB->delete_records('role_allow_assign', array('allowassign'=>$roleid)); 1513 $DB->delete_records('role_allow_override', array('roleid'=>$roleid)); 1514 $DB->delete_records('role_allow_override', array('allowoverride'=>$roleid)); 1515 $DB->delete_records('role_names', array('roleid'=>$roleid)); 1516 $DB->delete_records('role_context_levels', array('roleid'=>$roleid)); 1517 1518 // Get role record before it's deleted. 1519 $role = $DB->get_record('role', array('id'=>$roleid)); 1520 1521 // Finally delete the role itself. 1522 $DB->delete_records('role', array('id'=>$roleid)); 1523 1524 // Trigger event. 1525 $event = \core\event\role_deleted::create( 1526 array( 1527 'context' => context_system::instance(), 1528 'objectid' => $roleid, 1529 'other' => 1530 array( 1531 'shortname' => $role->shortname, 1532 'description' => $role->description, 1533 'archetype' => $role->archetype 1534 ) 1535 ) 1536 ); 1537 $event->add_record_snapshot('role', $role); 1538 $event->trigger(); 1539 1540 return true; 1541 } 1542 1543 /** 1544 * Function to write context specific overrides, or default capabilities. 1545 * 1546 * NOTE: use $context->mark_dirty() after this 1547 * 1548 * @param string $capability string name 1549 * @param int $permission CAP_ constants 1550 * @param int $roleid role id 1551 * @param int|context $contextid context id 1552 * @param bool $overwrite 1553 * @return bool always true or exception 1554 */ 1555 function assign_capability($capability, $permission, $roleid, $contextid, $overwrite = false) { 1556 global $USER, $DB; 1557 1558 if ($contextid instanceof context) { 1559 $context = $contextid; 1560 } else { 1561 $context = context::instance_by_id($contextid); 1562 } 1563 1564 if (empty($permission) || $permission == CAP_INHERIT) { // if permission is not set 1565 unassign_capability($capability, $roleid, $context->id); 1566 return true; 1567 } 1568 1569 $existing = $DB->get_record('role_capabilities', array('contextid'=>$context->id, 'roleid'=>$roleid, 'capability'=>$capability)); 1570 1571 if ($existing and !$overwrite) { // We want to keep whatever is there already 1572 return true; 1573 } 1574 1575 $cap = new stdClass(); 1576 $cap->contextid = $context->id; 1577 $cap->roleid = $roleid; 1578 $cap->capability = $capability; 1579 $cap->permission = $permission; 1580 $cap->timemodified = time(); 1581 $cap->modifierid = empty($USER->id) ? 0 : $USER->id; 1582 1583 if ($existing) { 1584 $cap->id = $existing->id; 1585 $DB->update_record('role_capabilities', $cap); 1586 } else { 1587 if ($DB->record_exists('context', array('id'=>$context->id))) { 1588 $DB->insert_record('role_capabilities', $cap); 1589 } 1590 } 1591 return true; 1592 } 1593 1594 /** 1595 * Unassign a capability from a role. 1596 * 1597 * NOTE: use $context->mark_dirty() after this 1598 * 1599 * @param string $capability the name of the capability 1600 * @param int $roleid the role id 1601 * @param int|context $contextid null means all contexts 1602 * @return boolean true or exception 1603 */ 1604 function unassign_capability($capability, $roleid, $contextid = null) { 1605 global $DB; 1606 1607 if (!empty($contextid)) { 1608 if ($contextid instanceof context) { 1609 $context = $contextid; 1610 } else { 1611 $context = context::instance_by_id($contextid); 1612 } 1613 // delete from context rel, if this is the last override in this context 1614 $DB->delete_records('role_capabilities', array('capability'=>$capability, 'roleid'=>$roleid, 'contextid'=>$context->id)); 1615 } else { 1616 $DB->delete_records('role_capabilities', array('capability'=>$capability, 'roleid'=>$roleid)); 1617 } 1618 return true; 1619 } 1620 1621 /** 1622 * Get the roles that have a given capability assigned to it 1623 * 1624 * This function does not resolve the actual permission of the capability. 1625 * It just checks for permissions and overrides. 1626 * Use get_roles_with_cap_in_context() if resolution is required. 1627 * 1628 * @param string $capability capability name (string) 1629 * @param string $permission optional, the permission defined for this capability 1630 * either CAP_ALLOW, CAP_PREVENT or CAP_PROHIBIT. Defaults to null which means any. 1631 * @param stdClass $context null means any 1632 * @return array of role records 1633 */ 1634 function get_roles_with_capability($capability, $permission = null, $context = null) { 1635 global $DB; 1636 1637 if ($context) { 1638 $contexts = $context->get_parent_context_ids(true); 1639 list($insql, $params) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED, 'ctx'); 1640 $contextsql = "AND rc.contextid $insql"; 1641 } else { 1642 $params = array(); 1643 $contextsql = ''; 1644 } 1645 1646 if ($permission) { 1647 $permissionsql = " AND rc.permission = :permission"; 1648 $params['permission'] = $permission; 1649 } else { 1650 $permissionsql = ''; 1651 } 1652 1653 $sql = "SELECT r.* 1654 FROM {role} r 1655 WHERE r.id IN (SELECT rc.roleid 1656 FROM {role_capabilities} rc 1657 WHERE rc.capability = :capname 1658 $contextsql 1659 $permissionsql)"; 1660 $params['capname'] = $capability; 1661 1662 1663 return $DB->get_records_sql($sql, $params); 1664 } 1665 1666 /** 1667 * This function makes a role-assignment (a role for a user in a particular context) 1668 * 1669 * @param int $roleid the role of the id 1670 * @param int $userid userid 1671 * @param int|context $contextid id of the context 1672 * @param string $component example 'enrol_ldap', defaults to '' which means manual assignment, 1673 * @param int $itemid id of enrolment/auth plugin 1674 * @param string $timemodified defaults to current time 1675 * @return int new/existing id of the assignment 1676 */ 1677 function role_assign($roleid, $userid, $contextid, $component = '', $itemid = 0, $timemodified = '') { 1678 global $USER, $DB; 1679 1680 // first of all detect if somebody is using old style parameters 1681 if ($contextid === 0 or is_numeric($component)) { 1682 throw new coding_exception('Invalid call to role_assign(), code needs to be updated to use new order of parameters'); 1683 } 1684 1685 // now validate all parameters 1686 if (empty($roleid)) { 1687 throw new coding_exception('Invalid call to role_assign(), roleid can not be empty'); 1688 } 1689 1690 if (empty($userid)) { 1691 throw new coding_exception('Invalid call to role_assign(), userid can not be empty'); 1692 } 1693 1694 if ($itemid) { 1695 if (strpos($component, '_') === false) { 1696 throw new coding_exception('Invalid call to role_assign(), component must start with plugin type such as"enrol_" when itemid specified', 'component:'.$component); 1697 } 1698 } else { 1699 $itemid = 0; 1700 if ($component !== '' and strpos($component, '_') === false) { 1701 throw new coding_exception('Invalid call to role_assign(), invalid component string', 'component:'.$component); 1702 } 1703 } 1704 1705 if (!$DB->record_exists('user', array('id'=>$userid, 'deleted'=>0))) { 1706 throw new coding_exception('User ID does not exist or is deleted!', 'userid:'.$userid); 1707 } 1708 1709 if ($contextid instanceof context) { 1710 $context = $contextid; 1711 } else { 1712 $context = context::instance_by_id($contextid, MUST_EXIST); 1713 } 1714 1715 if (!$timemodified) { 1716 $timemodified = time(); 1717 } 1718 1719 // Check for existing entry 1720 $ras = $DB->get_records('role_assignments', array('roleid'=>$roleid, 'contextid'=>$context->id, 'userid'=>$userid, 'component'=>$component, 'itemid'=>$itemid), 'id'); 1721 1722 if ($ras) { 1723 // role already assigned - this should not happen 1724 if (count($ras) > 1) { 1725 // very weird - remove all duplicates! 1726 $ra = array_shift($ras); 1727 foreach ($ras as $r) { 1728 $DB->delete_records('role_assignments', array('id'=>$r->id)); 1729 } 1730 } else { 1731 $ra = reset($ras); 1732 } 1733 1734 // actually there is no need to update, reset anything or trigger any event, so just return 1735 return $ra->id; 1736 } 1737 1738 // Create a new entry 1739 $ra = new stdClass(); 1740 $ra->roleid = $roleid; 1741 $ra->contextid = $context->id; 1742 $ra->userid = $userid; 1743 $ra->component = $component; 1744 $ra->itemid = $itemid; 1745 $ra->timemodified = $timemodified; 1746 $ra->modifierid = empty($USER->id) ? 0 : $USER->id; 1747 $ra->sortorder = 0; 1748 1749 $ra->id = $DB->insert_record('role_assignments', $ra); 1750 1751 // mark context as dirty - again expensive, but needed 1752 $context->mark_dirty(); 1753 1754 if (!empty($USER->id) && $USER->id == $userid) { 1755 // If the user is the current user, then do full reload of capabilities too. 1756 reload_all_capabilities(); 1757 } 1758 1759 $event = \core\event\role_assigned::create(array( 1760 'context' => $context, 1761 'objectid' => $ra->roleid, 1762 'relateduserid' => $ra->userid, 1763 'other' => array( 1764 'id' => $ra->id, 1765 'component' => $ra->component, 1766 'itemid' => $ra->itemid 1767 ) 1768 )); 1769 $event->add_record_snapshot('role_assignments', $ra); 1770 $event->trigger(); 1771 1772 return $ra->id; 1773 } 1774 1775 /** 1776 * Removes one role assignment 1777 * 1778 * @param int $roleid 1779 * @param int $userid 1780 * @param int $contextid 1781 * @param string $component 1782 * @param int $itemid 1783 * @return void 1784 */ 1785 function role_unassign($roleid, $userid, $contextid, $component = '', $itemid = 0) { 1786 // first make sure the params make sense 1787 if ($roleid == 0 or $userid == 0 or $contextid == 0) { 1788 throw new coding_exception('Invalid call to role_unassign(), please use role_unassign_all() when removing multiple role assignments'); 1789 } 1790 1791 if ($itemid) { 1792 if (strpos($component, '_') === false) { 1793 throw new coding_exception('Invalid call to role_assign(), component must start with plugin type such as "enrol_" when itemid specified', 'component:'.$component); 1794 } 1795 } else { 1796 $itemid = 0; 1797 if ($component !== '' and strpos($component, '_') === false) { 1798 throw new coding_exception('Invalid call to role_assign(), invalid component string', 'component:'.$component); 1799 } 1800 } 1801 1802 role_unassign_all(array('roleid'=>$roleid, 'userid'=>$userid, 'contextid'=>$contextid, 'component'=>$component, 'itemid'=>$itemid), false, false); 1803 } 1804 1805 /** 1806 * Removes multiple role assignments, parameters may contain: 1807 * 'roleid', 'userid', 'contextid', 'component', 'enrolid'. 1808 * 1809 * @param array $params role assignment parameters 1810 * @param bool $subcontexts unassign in subcontexts too 1811 * @param bool $includemanual include manual role assignments too 1812 * @return void 1813 */ 1814 function role_unassign_all(array $params, $subcontexts = false, $includemanual = false) { 1815 global $USER, $CFG, $DB; 1816 1817 if (!$params) { 1818 throw new coding_exception('Missing parameters in role_unsassign_all() call'); 1819 } 1820 1821 $allowed = array('roleid', 'userid', 'contextid', 'component', 'itemid'); 1822 foreach ($params as $key=>$value) { 1823 if (!in_array($key, $allowed)) { 1824 throw new coding_exception('Unknown role_unsassign_all() parameter key', 'key:'.$key); 1825 } 1826 } 1827 1828 if (isset($params['component']) and $params['component'] !== '' and strpos($params['component'], '_') === false) { 1829 throw new coding_exception('Invalid component paramter in role_unsassign_all() call', 'component:'.$params['component']); 1830 } 1831 1832 if ($includemanual) { 1833 if (!isset($params['component']) or $params['component'] === '') { 1834 throw new coding_exception('include manual parameter requires component parameter in role_unsassign_all() call'); 1835 } 1836 } 1837 1838 if ($subcontexts) { 1839 if (empty($params['contextid'])) { 1840 throw new coding_exception('subcontexts paramtere requires component parameter in role_unsassign_all() call'); 1841 } 1842 } 1843 1844 $ras = $DB->get_records('role_assignments', $params); 1845 foreach($ras as $ra) { 1846 $DB->delete_records('role_assignments', array('id'=>$ra->id)); 1847 if ($context = context::instance_by_id($ra->contextid, IGNORE_MISSING)) { 1848 // this is a bit expensive but necessary 1849 $context->mark_dirty(); 1850 // If the user is the current user, then do full reload of capabilities too. 1851 if (!empty($USER->id) && $USER->id == $ra->userid) { 1852 reload_all_capabilities(); 1853 } 1854 $event = \core\event\role_unassigned::create(array( 1855 'context' => $context, 1856 'objectid' => $ra->roleid, 1857 'relateduserid' => $ra->userid, 1858 'other' => array( 1859 'id' => $ra->id, 1860 'component' => $ra->component, 1861 'itemid' => $ra->itemid 1862 ) 1863 )); 1864 $event->add_record_snapshot('role_assignments', $ra); 1865 $event->trigger(); 1866 } 1867 } 1868 unset($ras); 1869 1870 // process subcontexts 1871 if ($subcontexts and $context = context::instance_by_id($params['contextid'], IGNORE_MISSING)) { 1872 if ($params['contextid'] instanceof context) { 1873 $context = $params['contextid']; 1874 } else { 1875 $context = context::instance_by_id($params['contextid'], IGNORE_MISSING); 1876 } 1877 1878 if ($context) { 1879 $contexts = $context->get_child_contexts(); 1880 $mparams = $params; 1881 foreach($contexts as $context) { 1882 $mparams['contextid'] = $context->id; 1883 $ras = $DB->get_records('role_assignments', $mparams); 1884 foreach($ras as $ra) { 1885 $DB->delete_records('role_assignments', array('id'=>$ra->id)); 1886 // this is a bit expensive but necessary 1887 $context->mark_dirty(); 1888 // If the user is the current user, then do full reload of capabilities too. 1889 if (!empty($USER->id) && $USER->id == $ra->userid) { 1890 reload_all_capabilities(); 1891 } 1892 $event = \core\event\role_unassigned::create( 1893 array('context'=>$context, 'objectid'=>$ra->roleid, 'relateduserid'=>$ra->userid, 1894 'other'=>array('id'=>$ra->id, 'component'=>$ra->component, 'itemid'=>$ra->itemid))); 1895 $event->add_record_snapshot('role_assignments', $ra); 1896 $event->trigger(); 1897 } 1898 } 1899 } 1900 } 1901 1902 // do this once more for all manual role assignments 1903 if ($includemanual) { 1904 $params['component'] = ''; 1905 role_unassign_all($params, $subcontexts, false); 1906 } 1907 } 1908 1909 /** 1910 * Determines if a user is currently logged in 1911 * 1912 * @category access 1913 * 1914 * @return bool 1915 */ 1916 function isloggedin() { 1917 global $USER; 1918 1919 return (!empty($USER->id)); 1920 } 1921 1922 /** 1923 * Determines if a user is logged in as real guest user with username 'guest'. 1924 * 1925 * @category access 1926 * 1927 * @param int|object $user mixed user object or id, $USER if not specified 1928 * @return bool true if user is the real guest user, false if not logged in or other user 1929 */ 1930 function isguestuser($user = null) { 1931 global $USER, $DB, $CFG; 1932 1933 // make sure we have the user id cached in config table, because we are going to use it a lot 1934 if (empty($CFG->siteguest)) { 1935 if (!$guestid = $DB->get_field('user', 'id', array('username'=>'guest', 'mnethostid'=>$CFG->mnet_localhost_id))) { 1936 // guest does not exist yet, weird 1937 return false; 1938 } 1939 set_config('siteguest', $guestid); 1940 } 1941 if ($user === null) { 1942 $user = $USER; 1943 } 1944 1945 if ($user === null) { 1946 // happens when setting the $USER 1947 return false; 1948 1949 } else if (is_numeric($user)) { 1950 return ($CFG->siteguest == $user); 1951 1952 } else if (is_object($user)) { 1953 if (empty($user->id)) { 1954 return false; // not logged in means is not be guest 1955 } else { 1956 return ($CFG->siteguest == $user->id); 1957 } 1958 1959 } else { 1960 throw new coding_exception('Invalid user parameter supplied for isguestuser() function!'); 1961 } 1962 } 1963 1964 /** 1965 * Does user have a (temporary or real) guest access to course? 1966 * 1967 * @category access 1968 * 1969 * @param context $context 1970 * @param stdClass|int $user 1971 * @return bool 1972 */ 1973 function is_guest(context $context, $user = null) { 1974 global $USER; 1975 1976 // first find the course context 1977 $coursecontext = $context->get_course_context(); 1978 1979 // make sure there is a real user specified 1980 if ($user === null) { 1981 $userid = isset($USER->id) ? $USER->id : 0; 1982 } else { 1983 $userid = is_object($user) ? $user->id : $user; 1984 } 1985 1986 if (isguestuser($userid)) { 1987 // can not inspect or be enrolled 1988 return true; 1989 } 1990 1991 if (has_capability('moodle/course:view', $coursecontext, $user)) { 1992 // viewing users appear out of nowhere, they are neither guests nor participants 1993 return false; 1994 } 1995 1996 // consider only real active enrolments here 1997 if (is_enrolled($coursecontext, $user, '', true)) { 1998 return false; 1999 } 2000 2001 return true; 2002 } 2003 2004 /** 2005 * Returns true if the user has moodle/course:view capability in the course, 2006 * this is intended for admins, managers (aka small admins), inspectors, etc. 2007 * 2008 * @category access 2009 * 2010 * @param context $context 2011 * @param int|stdClass $user if null $USER is used 2012 * @param string $withcapability extra capability name 2013 * @return bool 2014 */ 2015 function is_viewing(context $context, $user = null, $withcapability = '') { 2016 // first find the course context 2017 $coursecontext = $context->get_course_context(); 2018 2019 if (isguestuser($user)) { 2020 // can not inspect 2021 return false; 2022 } 2023 2024 if (!has_capability('moodle/course:view', $coursecontext, $user)) { 2025 // admins are allowed to inspect courses 2026 return false; 2027 } 2028 2029 if ($withcapability and !has_capability($withcapability, $context, $user)) { 2030 // site admins always have the capability, but the enrolment above blocks 2031 return false; 2032 } 2033 2034 return true; 2035 } 2036 2037 /** 2038 * Returns true if user is enrolled (is participating) in course 2039 * this is intended for students and teachers. 2040 * 2041 * Since 2.2 the result for active enrolments and current user are cached. 2042 * 2043 * @package core_enrol 2044 * @category access 2045 * 2046 * @param context $context 2047 * @param int|stdClass $user if null $USER is used, otherwise user object or id expected 2048 * @param string $withcapability extra capability name 2049 * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions 2050 * @return bool 2051 */ 2052 function is_enrolled(context $context, $user = null, $withcapability = '', $onlyactive = false) { 2053 global $USER, $DB; 2054 2055 // first find the course context 2056 $coursecontext = $context->get_course_context(); 2057 2058 // make sure there is a real user specified 2059 if ($user === null) { 2060 $userid = isset($USER->id) ? $USER->id : 0; 2061 } else { 2062 $userid = is_object($user) ? $user->id : $user; 2063 } 2064 2065 if (empty($userid)) { 2066 // not-logged-in! 2067 return false; 2068 } else if (isguestuser($userid)) { 2069 // guest account can not be enrolled anywhere 2070 return false; 2071 } 2072 2073 if ($coursecontext->instanceid == SITEID) { 2074 // everybody participates on frontpage 2075 } else { 2076 // try cached info first - the enrolled flag is set only when active enrolment present 2077 if ($USER->id == $userid) { 2078 $coursecontext->reload_if_dirty(); 2079 if (isset($USER->enrol['enrolled'][$coursecontext->instanceid])) { 2080 if ($USER->enrol['enrolled'][$coursecontext->instanceid] > time()) { 2081 if ($withcapability and !has_capability($withcapability, $context, $userid)) { 2082 return false; 2083 } 2084 return true; 2085 } 2086 } 2087 } 2088 2089 if ($onlyactive) { 2090 // look for active enrolments only 2091 $until = enrol_get_enrolment_end($coursecontext->instanceid, $userid); 2092 2093 if ($until === false) { 2094 return false; 2095 } 2096 2097 if ($USER->id == $userid) { 2098 if ($until == 0) { 2099 $until = ENROL_MAX_TIMESTAMP; 2100 } 2101 $USER->enrol['enrolled'][$coursecontext->instanceid] = $until; 2102 if (isset($USER->enrol['tempguest'][$coursecontext->instanceid])) { 2103 unset($USER->enrol['tempguest'][$coursecontext->instanceid]); 2104 remove_temp_course_roles($coursecontext); 2105 } 2106 } 2107 2108 } else { 2109 // any enrolment is good for us here, even outdated, disabled or inactive 2110 $sql = "SELECT 'x' 2111 FROM {user_enrolments} ue 2112 JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid) 2113 JOIN {user} u ON u.id = ue.userid 2114 WHERE ue.userid = :userid AND u.deleted = 0"; 2115 $params = array('userid'=>$userid, 'courseid'=>$coursecontext->instanceid); 2116 if (!$DB->record_exists_sql($sql, $params)) { 2117 return false; 2118 } 2119 } 2120 } 2121 2122 if ($withcapability and !has_capability($withcapability, $context, $userid)) { 2123 return false; 2124 } 2125 2126 return true; 2127 } 2128 2129 /** 2130 * Returns true if the user is able to access the course. 2131 * 2132 * This function is in no way, shape, or form a substitute for require_login. 2133 * It should only be used in circumstances where it is not possible to call require_login 2134 * such as the navigation. 2135 * 2136 * This function checks many of the methods of access to a course such as the view 2137 * capability, enrollments, and guest access. It also makes use of the cache 2138 * generated by require_login for guest access. 2139 * 2140 * The flags within the $USER object that are used here should NEVER be used outside 2141 * of this function can_access_course and require_login. Doing so WILL break future 2142 * versions. 2143 * 2144 * @param stdClass $course record 2145 * @param stdClass|int|null $user user record or id, current user if null 2146 * @param string $withcapability Check for this capability as well. 2147 * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions 2148 * @return boolean Returns true if the user is able to access the course 2149 */ 2150 function can_access_course(stdClass $course, $user = null, $withcapability = '', $onlyactive = false) { 2151 global $DB, $USER; 2152 2153 // this function originally accepted $coursecontext parameter 2154 if ($course instanceof context) { 2155 if ($course instanceof context_course) { 2156 debugging('deprecated context parameter, please use $course record'); 2157 $coursecontext = $course; 2158 $course = $DB->get_record('course', array('id'=>$coursecontext->instanceid)); 2159 } else { 2160 debugging('Invalid context parameter, please use $course record'); 2161 return false; 2162 } 2163 } else { 2164 $coursecontext = context_course::instance($course->id); 2165 } 2166 2167 if (!isset($USER->id)) { 2168 // should never happen 2169 $USER->id = 0; 2170 debugging('Course access check being performed on a user with no ID.', DEBUG_DEVELOPER); 2171 } 2172 2173 // make sure there is a user specified 2174 if ($user === null) { 2175 $userid = $USER->id; 2176 } else { 2177 $userid = is_object($user) ? $user->id : $user; 2178 } 2179 unset($user); 2180 2181 if ($withcapability and !has_capability($withcapability, $coursecontext, $userid)) { 2182 return false; 2183 } 2184 2185 if ($userid == $USER->id) { 2186 if (!empty($USER->access['rsw'][$coursecontext->path])) { 2187 // the fact that somebody switched role means they can access the course no matter to what role they switched 2188 return true; 2189 } 2190 } 2191 2192 if (!$course->visible and !has_capability('moodle/course:viewhiddencourses', $coursecontext, $userid)) { 2193 return false; 2194 } 2195 2196 if (is_viewing($coursecontext, $userid)) { 2197 return true; 2198 } 2199 2200 if ($userid != $USER->id) { 2201 // for performance reasons we do not verify temporary guest access for other users, sorry... 2202 return is_enrolled($coursecontext, $userid, '', $onlyactive); 2203 } 2204 2205 // === from here we deal only with $USER === 2206 2207 $coursecontext->reload_if_dirty(); 2208 2209 if (isset($USER->enrol['enrolled'][$course->id])) { 2210 if ($USER->enrol['enrolled'][$course->id] > time()) { 2211 return true; 2212 } 2213 } 2214 if (isset($USER->enrol['tempguest'][$course->id])) { 2215 if ($USER->enrol['tempguest'][$course->id] > time()) { 2216 return true; 2217 } 2218 } 2219 2220 if (is_enrolled($coursecontext, $USER, '', $onlyactive)) { 2221 return true; 2222 } 2223 2224 // if not enrolled try to gain temporary guest access 2225 $instances = $DB->get_records('enrol', array('courseid'=>$course->id, 'status'=>ENROL_INSTANCE_ENABLED), 'sortorder, id ASC'); 2226 $enrols = enrol_get_plugins(true); 2227 foreach($instances as $instance) { 2228 if (!isset($enrols[$instance->enrol])) { 2229 continue; 2230 } 2231 // Get a duration for the guest access, a timestamp in the future, 0 (always) or false. 2232 $until = $enrols[$instance->enrol]->try_guestaccess($instance); 2233 if ($until !== false and $until > time()) { 2234 $USER->enrol['tempguest'][$course->id] = $until; 2235 return true; 2236 } 2237 } 2238 if (isset($USER->enrol['tempguest'][$course->id])) { 2239 unset($USER->enrol['tempguest'][$course->id]); 2240 remove_temp_course_roles($coursecontext); 2241 } 2242 2243 return false; 2244 } 2245 2246 /** 2247 * Returns array with sql code and parameters returning all ids 2248 * of users enrolled into course. 2249 * 2250 * This function is using 'eu[0-9]+_' prefix for table names and parameters. 2251 * 2252 * @package core_enrol 2253 * @category access 2254 * 2255 * @param context $context 2256 * @param string $withcapability 2257 * @param int $groupid 0 means ignore groups, any other value limits the result by group id 2258 * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions 2259 * @return array list($sql, $params) 2260 */ 2261 function get_enrolled_sql(context $context, $withcapability = '', $groupid = 0, $onlyactive = false) { 2262 global $DB, $CFG; 2263 2264 // use unique prefix just in case somebody makes some SQL magic with the result 2265 static $i = 0; 2266 $i++; 2267 $prefix = 'eu'.$i.'_'; 2268 2269 // first find the course context 2270 $coursecontext = $context->get_course_context(); 2271 2272 $isfrontpage = ($coursecontext->instanceid == SITEID); 2273 2274 $joins = array(); 2275 $wheres = array(); 2276 $params = array(); 2277 2278 list($contextids, $contextpaths) = get_context_info_list($context); 2279 2280 // get all relevant capability info for all roles 2281 if ($withcapability) { 2282 list($incontexts, $cparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, 'ctx'); 2283 $cparams['cap'] = $withcapability; 2284 2285 $defs = array(); 2286 $sql = "SELECT rc.id, rc.roleid, rc.permission, ctx.path 2287 FROM {role_capabilities} rc 2288 JOIN {context} ctx on rc.contextid = ctx.id 2289 WHERE rc.contextid $incontexts AND rc.capability = :cap"; 2290 $rcs = $DB->get_records_sql($sql, $cparams); 2291 foreach ($rcs as $rc) { 2292 $defs[$rc->path][$rc->roleid] = $rc->permission; 2293 } 2294 2295 $access = array(); 2296 if (!empty($defs)) { 2297 foreach ($contextpaths as $path) { 2298 if (empty($defs[$path])) { 2299 continue; 2300 } 2301 foreach($defs[$path] as $roleid => $perm) { 2302 if ($perm == CAP_PROHIBIT) { 2303 $access[$roleid] = CAP_PROHIBIT; 2304 continue; 2305 } 2306 if (!isset($access[$roleid])) { 2307 $access[$roleid] = (int)$perm; 2308 } 2309 } 2310 } 2311 } 2312 2313 unset($defs); 2314 2315 // make lists of roles that are needed and prohibited 2316 $needed = array(); // one of these is enough 2317 $prohibited = array(); // must not have any of these 2318 foreach ($access as $roleid => $perm) { 2319 if ($perm == CAP_PROHIBIT) { 2320 unset($needed[$roleid]); 2321 $prohibited[$roleid] = true; 2322 } else if ($perm == CAP_ALLOW and empty($prohibited[$roleid])) { 2323 $needed[$roleid] = true; 2324 } 2325 } 2326 2327 $defaultuserroleid = isset($CFG->defaultuserroleid) ? $CFG->defaultuserroleid : 0; 2328 $defaultfrontpageroleid = isset($CFG->defaultfrontpageroleid) ? $CFG->defaultfrontpageroleid : 0; 2329 2330 $nobody = false; 2331 2332 if ($isfrontpage) { 2333 if (!empty($prohibited[$defaultuserroleid]) or !empty($prohibited[$defaultfrontpageroleid])) { 2334 $nobody = true; 2335 } else if (!empty($needed[$defaultuserroleid]) or !empty($needed[$defaultfrontpageroleid])) { 2336 // everybody not having prohibit has the capability 2337 $needed = array(); 2338 } else if (empty($needed)) { 2339 $nobody = true; 2340 } 2341 } else { 2342 if (!empty($prohibited[$defaultuserroleid])) { 2343 $nobody = true; 2344 } else if (!empty($needed[$defaultuserroleid])) { 2345 // everybody not having prohibit has the capability 2346 $needed = array(); 2347 } else if (empty($needed)) { 2348 $nobody = true; 2349 } 2350 } 2351 2352 if ($nobody) { 2353 // nobody can match so return some SQL that does not return any results 2354 $wheres[] = "1 = 2"; 2355 2356 } else { 2357 2358 if ($needed) { 2359 $ctxids = implode(',', $contextids); 2360 $roleids = implode(',', array_keys($needed)); 2361 $joins[] = "JOIN {role_assignments} {$prefix}ra3 ON ({$prefix}ra3.userid = {$prefix}u.id AND {$prefix}ra3.roleid IN ($roleids) AND {$prefix}ra3.contextid IN ($ctxids))"; 2362 } 2363 2364 if ($prohibited) { 2365 $ctxids = implode(',', $contextids); 2366 $roleids = implode(',', array_keys($prohibited)); 2367 $joins[] = "LEFT JOIN {role_assignments} {$prefix}ra4 ON ({$prefix}ra4.userid = {$prefix}u.id AND {$prefix}ra4.roleid IN ($roleids) AND {$prefix}ra4.contextid IN ($ctxids))"; 2368 $wheres[] = "{$prefix}ra4.id IS NULL"; 2369 } 2370 2371 if ($groupid) { 2372 $joins[] = "JOIN {groups_members} {$prefix}gm ON ({$prefix}gm.userid = {$prefix}u.id AND {$prefix}gm.groupid = :{$prefix}gmid)"; 2373 $params["{$prefix}gmid"] = $groupid; 2374 } 2375 } 2376 2377 } else { 2378 if ($groupid) { 2379 $joins[] = "JOIN {groups_members} {$prefix}gm ON ({$prefix}gm.userid = {$prefix}u.id AND {$prefix}gm.groupid = :{$prefix}gmid)"; 2380 $params["{$prefix}gmid"] = $groupid; 2381 } 2382 } 2383 2384 $wheres[] = "{$prefix}u.deleted = 0 AND {$prefix}u.id <> :{$prefix}guestid"; 2385 $params["{$prefix}guestid"] = $CFG->siteguest; 2386 2387 if ($isfrontpage) { 2388 // all users are "enrolled" on the frontpage 2389 } else { 2390 $joins[] = "JOIN {user_enrolments} {$prefix}ue ON {$prefix}ue.userid = {$prefix}u.id"; 2391 $joins[] = "JOIN {enrol} {$prefix}e ON ({$prefix}e.id = {$prefix}ue.enrolid AND {$prefix}e.courseid = :{$prefix}courseid)"; 2392 $params[$prefix.'courseid'] = $coursecontext->instanceid; 2393 2394 if ($onlyactive) { 2395 $wheres[] = "{$prefix}ue.status = :{$prefix}active AND {$prefix}e.status = :{$prefix}enabled"; 2396 $wheres[] = "{$prefix}ue.timestart < :{$prefix}now1 AND ({$prefix}ue.timeend = 0 OR {$prefix}ue.timeend > :{$prefix}now2)"; 2397 $now = round(time(), -2); // rounding helps caching in DB 2398 $params = array_merge($params, array($prefix.'enabled'=>ENROL_INSTANCE_ENABLED, 2399 $prefix.'active'=>ENROL_USER_ACTIVE, 2400 $prefix.'now1'=>$now, $prefix.'now2'=>$now)); 2401 } 2402 } 2403 2404 $joins = implode("\n", $joins); 2405 $wheres = "WHERE ".implode(" AND ", $wheres); 2406 2407 $sql = "SELECT DISTINCT {$prefix}u.id 2408 FROM {user} {$prefix}u 2409 $joins 2410 $wheres"; 2411 2412 return array($sql, $params); 2413 } 2414 2415 /** 2416 * Returns list of users enrolled into course. 2417 * 2418 * @package core_enrol 2419 * @category access 2420 * 2421 * @param context $context 2422 * @param string $withcapability 2423 * @param int $groupid 0 means ignore groups, any other value limits the result by group id 2424 * @param string $userfields requested user record fields 2425 * @param string $orderby 2426 * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set). 2427 * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set). 2428 * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions 2429 * @return array of user records 2430 */ 2431 function get_enrolled_users(context $context, $withcapability = '', $groupid = 0, $userfields = 'u.*', $orderby = null, 2432 $limitfrom = 0, $limitnum = 0, $onlyactive = false) { 2433 global $DB; 2434 2435 list($esql, $params) = get_enrolled_sql($context, $withcapability, $groupid, $onlyactive); 2436 $sql = "SELECT $userfields 2437 FROM {user} u 2438 JOIN ($esql) je ON je.id = u.id 2439 WHERE u.deleted = 0"; 2440 2441 if ($orderby) { 2442 $sql = "$sql ORDER BY $orderby"; 2443 } else { 2444 list($sort, $sortparams) = users_order_by_sql('u'); 2445 $sql = "$sql ORDER BY $sort"; 2446 $params = array_merge($params, $sortparams); 2447 } 2448 2449 return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum); 2450 } 2451 2452 /** 2453 * Counts list of users enrolled into course (as per above function) 2454 * 2455 * @package core_enrol 2456 * @category access 2457 * 2458 * @param context $context 2459 * @param string $withcapability 2460 * @param int $groupid 0 means ignore groups, any other value limits the result by group id 2461 * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions 2462 * @return array of user records 2463 */ 2464 function count_enrolled_users(context $context, $withcapability = '', $groupid = 0, $onlyactive = false) { 2465 global $DB; 2466 2467 list($esql, $params) = get_enrolled_sql($context, $withcapability, $groupid, $onlyactive); 2468 $sql = "SELECT count(u.id) 2469 FROM {user} u 2470 JOIN ($esql) je ON je.id = u.id 2471 WHERE u.deleted = 0"; 2472 2473 return $DB->count_records_sql($sql, $params); 2474 } 2475 2476 /** 2477 * Loads the capability definitions for the component (from file). 2478 * 2479 * Loads the capability definitions for the component (from file). If no 2480 * capabilities are defined for the component, we simply return an empty array. 2481 * 2482 * @access private 2483 * @param string $component full plugin name, examples: 'moodle', 'mod_forum' 2484 * @return array array of capabilities 2485 */ 2486 function load_capability_def($component) { 2487 $defpath = core_component::get_component_directory($component).'/db/access.php'; 2488 2489 $capabilities = array(); 2490 if (file_exists($defpath)) { 2491 require($defpath); 2492 if (!empty(${$component.'_capabilities'})) { 2493 // BC capability array name 2494 // since 2.0 we prefer $capabilities instead - it is easier to use and matches db/* files 2495 debugging('componentname_capabilities array is deprecated, please use $capabilities array only in access.php files'); 2496 $capabilities = ${$component.'_capabilities'}; 2497 } 2498 } 2499 2500 return $capabilities; 2501 } 2502 2503 /** 2504 * Gets the capabilities that have been cached in the database for this component. 2505 * 2506 * @access private 2507 * @param string $component - examples: 'moodle', 'mod_forum' 2508 * @return array array of capabilities 2509 */ 2510 function get_cached_capabilities($component = 'moodle') { 2511 global $DB; 2512 return $DB->get_records('capabilities', array('component'=>$component)); 2513 } 2514 2515 /** 2516 * Returns default capabilities for given role archetype. 2517 * 2518 * @param string $archetype role archetype 2519 * @return array 2520 */ 2521 function get_default_capabilities($archetype) { 2522 global $DB; 2523 2524 if (!$archetype) { 2525 return array(); 2526 } 2527 2528 $alldefs = array(); 2529 $defaults = array(); 2530 $components = array(); 2531 $allcaps = $DB->get_records('capabilities'); 2532 2533 foreach ($allcaps as $cap) { 2534 if (!in_array($cap->component, $components)) { 2535 $components[] = $cap->component; 2536 $alldefs = array_merge($alldefs, load_capability_def($cap->component)); 2537 } 2538 } 2539 foreach($alldefs as $name=>$def) { 2540 // Use array 'archetypes if available. Only if not specified, use 'legacy'. 2541 if (isset($def['archetypes'])) { 2542 if (isset($def['archetypes'][$archetype])) { 2543 $defaults[$name] = $def['archetypes'][$archetype]; 2544 } 2545 // 'legacy' is for backward compatibility with 1.9 access.php 2546 } else { 2547 if (isset($def['legacy'][$archetype])) { 2548 $defaults[$name] = $def['legacy'][$archetype]; 2549 } 2550 } 2551 } 2552 2553 return $defaults; 2554 } 2555 2556 /** 2557 * Return default roles that can be assigned, overridden or switched 2558 * by give role archetype. 2559 * 2560 * @param string $type assign|override|switch 2561 * @param string $archetype 2562 * @return array of role ids 2563 */ 2564 function get_default_role_archetype_allows($type, $archetype) { 2565 global $DB; 2566 2567 if (empty($archetype)) { 2568 return array(); 2569 } 2570 2571 $roles = $DB->get_records('role'); 2572 $archetypemap = array(); 2573 foreach ($roles as $role) { 2574 if ($role->archetype) { 2575 $archetypemap[$role->archetype][$role->id] = $role->id; 2576 } 2577 } 2578 2579 $defaults = array( 2580 'assign' => array( 2581 'manager' => array('manager', 'coursecreator', 'editingteacher', 'teacher', 'student'), 2582 'coursecreator' => array(), 2583 'editingteacher' => array('teacher', 'student'), 2584 'teacher' => array(), 2585 'student' => array(), 2586 'guest' => array(), 2587 'user' => array(), 2588 'frontpage' => array(), 2589 ), 2590 'override' => array( 2591 'manager' => array('manager', 'coursecreator', 'editingteacher', 'teacher', 'student', 'guest', 'user', 'frontpage'), 2592 'coursecreator' => array(), 2593 'editingteacher' => array('teacher', 'student', 'guest'), 2594 'teacher' => array(), 2595 'student' => array(), 2596 'guest' => array(), 2597 'user' => array(), 2598 'frontpage' => array(), 2599 ), 2600 'switch' => array( 2601 'manager' => array('editingteacher', 'teacher', 'student', 'guest'), 2602 'coursecreator' => array(), 2603 'editingteacher' => array('teacher', 'student', 'guest'), 2604 'teacher' => array('student', 'guest'), 2605 'student' => array(), 2606 'guest' => array(), 2607 'user' => array(), 2608 'frontpage' => array(), 2609 ), 2610 ); 2611 2612 if (!isset($defaults[$type][$archetype])) { 2613 debugging("Unknown type '$type'' or archetype '$archetype''"); 2614 return array(); 2615 } 2616 2617 $return = array(); 2618 foreach ($defaults[$type][$archetype] as $at) { 2619 if (isset($archetypemap[$at])) { 2620 foreach ($archetypemap[$at] as $roleid) { 2621 $return[$roleid] = $roleid; 2622 } 2623 } 2624 } 2625 2626 return $return; 2627 } 2628 2629 /** 2630 * Reset role capabilities to default according to selected role archetype. 2631 * If no archetype selected, removes all capabilities. 2632 * 2633 * This applies to capabilities that are assigned to the role (that you could 2634 * edit in the 'define roles' interface), and not to any capability overrides 2635 * in different locations. 2636 * 2637 * @param int $roleid ID of role to reset capabilities for 2638 */ 2639 function reset_role_capabilities($roleid) { 2640 global $DB; 2641 2642 $role = $DB->get_record('role', array('id'=>$roleid), '*', MUST_EXIST); 2643 $defaultcaps = get_default_capabilities($role->archetype); 2644 2645 $systemcontext = context_system::instance(); 2646 2647 $DB->delete_records('role_capabilities', 2648 array('roleid' => $roleid, 'contextid' => $systemcontext->id)); 2649 2650 foreach($defaultcaps as $cap=>$permission) { 2651 assign_capability($cap, $permission, $roleid, $systemcontext->id); 2652 } 2653 2654 // Mark the system context dirty. 2655 context_system::instance()->mark_dirty(); 2656 } 2657 2658 /** 2659 * Updates the capabilities table with the component capability definitions. 2660 * If no parameters are given, the function updates the core moodle 2661 * capabilities. 2662 * 2663 * Note that the absence of the db/access.php capabilities definition file 2664 * will cause any stored capabilities for the component to be removed from 2665 * the database. 2666 * 2667 * @access private 2668 * @param string $component examples: 'moodle', 'mod/forum', 'block/quiz_results' 2669 * @return boolean true if success, exception in case of any problems 2670 */ 2671 function update_capabilities($component = 'moodle') { 2672 global $DB, $OUTPUT; 2673 2674 $storedcaps = array(); 2675 2676 $filecaps = load_capability_def($component); 2677 foreach($filecaps as $capname=>$unused) { 2678 if (!preg_match('|^[a-z]+/[a-z_0-9]+:[a-z_0-9]+$|', $capname)) { 2679 debugging("Coding problem: Invalid capability name '$capname', use 'clonepermissionsfrom' field for migration."); 2680 } 2681 } 2682 2683 $cachedcaps = get_cached_capabilities($component); 2684 if ($cachedcaps) { 2685 foreach ($cachedcaps as $cachedcap) { 2686 array_push($storedcaps, $cachedcap->name); 2687 // update risk bitmasks and context levels in existing capabilities if needed 2688 if (array_key_exists($cachedcap->name, $filecaps)) { 2689 if (!array_key_exists('riskbitmask', $filecaps[$cachedcap->name])) { 2690 $filecaps[$cachedcap->name]['riskbitmask'] = 0; // no risk if not specified 2691 } 2692 if ($cachedcap->captype != $filecaps[$cachedcap->name]['captype']) { 2693 $updatecap = new stdClass(); 2694 $updatecap->id = $cachedcap->id; 2695 $updatecap->captype = $filecaps[$cachedcap->name]['captype']; 2696 $DB->update_record('capabilities', $updatecap); 2697 } 2698 if ($cachedcap->riskbitmask != $filecaps[$cachedcap->name]['riskbitmask']) { 2699 $updatecap = new stdClass(); 2700 $updatecap->id = $cachedcap->id; 2701 $updatecap->riskbitmask = $filecaps[$cachedcap->name]['riskbitmask']; 2702 $DB->update_record('capabilities', $updatecap); 2703 } 2704 2705 if (!array_key_exists('contextlevel', $filecaps[$cachedcap->name])) { 2706 $filecaps[$cachedcap->name]['contextlevel'] = 0; // no context level defined 2707 } 2708 if ($cachedcap->contextlevel != $filecaps[$cachedcap->name]['contextlevel']) { 2709 $updatecap = new stdClass(); 2710 $updatecap->id = $cachedcap->id; 2711 $updatecap->contextlevel = $filecaps[$cachedcap->name]['contextlevel']; 2712 $DB->update_record('capabilities', $updatecap); 2713 } 2714 } 2715 } 2716 } 2717 2718 // Are there new capabilities in the file definition? 2719 $newcaps = array(); 2720 2721 foreach ($filecaps as $filecap => $def) { 2722 if (!$storedcaps || 2723 ($storedcaps && in_array($filecap, $storedcaps) === false)) { 2724 if (!array_key_exists('riskbitmask', $def)) { 2725 $def['riskbitmask'] = 0; // no risk if not specified 2726 } 2727 $newcaps[$filecap] = $def; 2728 } 2729 } 2730 // Add new capabilities to the stored definition. 2731 $existingcaps = $DB->get_records_menu('capabilities', array(), 'id', 'id, name'); 2732 foreach ($newcaps as $capname => $capdef) { 2733 $capability = new stdClass(); 2734 $capability->name = $capname; 2735 $capability->captype = $capdef['captype']; 2736 $capability->contextlevel = $capdef['contextlevel']; 2737 $capability->component = $component; 2738 $capability->riskbitmask = $capdef['riskbitmask']; 2739 2740 $DB->insert_record('capabilities', $capability, false); 2741 2742 if (isset($capdef['clonepermissionsfrom']) && in_array($capdef['clonepermissionsfrom'], $existingcaps)){ 2743 if ($rolecapabilities = $DB->get_records('role_capabilities', array('capability'=>$capdef['clonepermissionsfrom']))){ 2744 foreach ($rolecapabilities as $rolecapability){ 2745 //assign_capability will update rather than insert if capability exists 2746 if (!assign_capability($capname, $rolecapability->permission, 2747 $rolecapability->roleid, $rolecapability->contextid, true)){ 2748 echo $OUTPUT->notification('Could not clone capabilities for '.$capname); 2749 } 2750 } 2751 } 2752 // we ignore archetype key if we have cloned permissions 2753 } else if (isset($capdef['archetypes']) && is_array($capdef['archetypes'])) { 2754 assign_legacy_capabilities($capname, $capdef['archetypes']); 2755 // 'legacy' is for backward compatibility with 1.9 access.php 2756 } else if (isset($capdef['legacy']) && is_array($capdef['legacy'])) { 2757 assign_legacy_capabilities($capname, $capdef['legacy']); 2758 } 2759 } 2760 // Are there any capabilities that have been removed from the file 2761 // definition that we need to delete from the stored capabilities and 2762 // role assignments? 2763 capabilities_cleanup($component, $filecaps); 2764 2765 // reset static caches 2766 accesslib_clear_all_caches(false); 2767 2768 return true; 2769 } 2770 2771 /** 2772 * Deletes cached capabilities that are no longer needed by the component. 2773 * Also unassigns these capabilities from any roles that have them. 2774 * NOTE: this function is called from lib/db/upgrade.php 2775 * 2776 * @access private 2777 * @param string $component examples: 'moodle', 'mod_forum', 'block_quiz_results' 2778 * @param array $newcapdef array of the new capability definitions that will be 2779 * compared with the cached capabilities 2780 * @return int number of deprecated capabilities that have been removed 2781 */ 2782 function capabilities_cleanup($component, $newcapdef = null) { 2783 global $DB; 2784 2785 $removedcount = 0; 2786 2787 if ($cachedcaps = get_cached_capabilities($component)) { 2788 foreach ($cachedcaps as $cachedcap) { 2789 if (empty($newcapdef) || 2790 array_key_exists($cachedcap->name, $newcapdef) === false) { 2791 2792 // Remove from capabilities cache. 2793 $DB->delete_records('capabilities', array('name'=>$cachedcap->name)); 2794 $removedcount++; 2795 // Delete from roles. 2796 if ($roles = get_roles_with_capability($cachedcap->name)) { 2797 foreach($roles as $role) { 2798 if (!unassign_capability($cachedcap->name, $role->id)) { 2799 print_error('cannotunassigncap', 'error', '', (object)array('cap'=>$cachedcap->name, 'role'=>$role->name)); 2800 } 2801 } 2802 } 2803 } // End if. 2804 } 2805 } 2806 return $removedcount; 2807 } 2808 2809 /** 2810 * Returns an array of all the known types of risk 2811 * The array keys can be used, for example as CSS class names, or in calls to 2812 * print_risk_icon. The values are the corresponding RISK_ constants. 2813 * 2814 * @return array all the known types of risk. 2815 */ 2816 function get_all_risks() { 2817 return array( 2818 'riskmanagetrust' => RISK_MANAGETRUST, 2819 'riskconfig' => RISK_CONFIG, 2820 'riskxss' => RISK_XSS, 2821 'riskpersonal' => RISK_PERSONAL, 2822 'riskspam' => RISK_SPAM, 2823 'riskdataloss' => RISK_DATALOSS, 2824 ); 2825 } 2826 2827 /** 2828 * Return a link to moodle docs for a given capability name 2829 * 2830 * @param stdClass $capability a capability - a row from the mdl_capabilities table. 2831 * @return string the human-readable capability name as a link to Moodle Docs. 2832 */ 2833 function get_capability_docs_link($capability) { 2834 $url = get_docs_url('Capabilities/' . $capability->name); 2835 return '<a onclick="this.target=\'docspopup\'" href="' . $url . '">' . get_capability_string($capability->name) . '</a>'; 2836 } 2837 2838 /** 2839 * This function pulls out all the resolved capabilities (overrides and 2840 * defaults) of a role used in capability overrides in contexts at a given 2841 * context. 2842 * 2843 * @param int $roleid 2844 * @param context $context 2845 * @param string $cap capability, optional, defaults to '' 2846 * @return array Array of capabilities 2847 */ 2848 function role_context_capabilities($roleid, context $context, $cap = '') { 2849 global $DB; 2850 2851 $contexts = $context->get_parent_context_ids(true); 2852 $contexts = '('.implode(',', $contexts).')'; 2853 2854 $params = array($roleid); 2855 2856 if ($cap) { 2857 $search = " AND rc.capability = ? "; 2858 $params[] = $cap; 2859 } else { 2860 $search = ''; 2861 } 2862 2863 $sql = "SELECT rc.* 2864 FROM {role_capabilities} rc, {context} c 2865 WHERE rc.contextid in $contexts 2866 AND rc.roleid = ? 2867 AND rc.contextid = c.id $search 2868 ORDER BY c.contextlevel DESC, rc.capability DESC"; 2869 2870 $capabilities = array(); 2871 2872 if ($records = $DB->get_records_sql($sql, $params)) { 2873 // We are traversing via reverse order. 2874 foreach ($records as $record) { 2875 // If not set yet (i.e. inherit or not set at all), or currently we have a prohibit 2876 if (!isset($capabilities[$record->capability]) || $record->permission<-500) { 2877 $capabilities[$record->capability] = $record->permission; 2878 } 2879 } 2880 } 2881 return $capabilities; 2882 } 2883 2884 /** 2885 * Constructs array with contextids as first parameter and context paths, 2886 * in both cases bottom top including self. 2887 * 2888 * @access private 2889 * @param context $context 2890 * @return array 2891 */ 2892 function get_context_info_list(context $context) { 2893 $contextids = explode('/', ltrim($context->path, '/')); 2894 $contextpaths = array(); 2895 $contextids2 = $contextids; 2896 while ($contextids2) { 2897 $contextpaths[] = '/' . implode('/', $contextids2); 2898 array_pop($contextids2); 2899 } 2900 return array($contextids, $contextpaths); 2901 } 2902 2903 /** 2904 * Check if context is the front page context or a context inside it 2905 * 2906 * Returns true if this context is the front page context, or a context inside it, 2907 * otherwise false. 2908 * 2909 * @param context $context a context object. 2910 * @return bool 2911 */ 2912 function is_inside_frontpage(context $context) { 2913 $frontpagecontext = context_course::instance(SITEID); 2914 return strpos($context->path . '/', $frontpagecontext->path . '/') === 0; 2915 } 2916 2917 /** 2918 * Returns capability information (cached) 2919 * 2920 * @param string $capabilityname 2921 * @return stdClass or null if capability not found 2922 */ 2923 function get_capability_info($capabilityname) { 2924 global $ACCESSLIB_PRIVATE, $DB; // one request per page only 2925 2926 //TODO: MUC - this could be cached in shared memory, it would eliminate 1 query per page 2927 2928 if (empty($ACCESSLIB_PRIVATE->capabilities)) { 2929 $ACCESSLIB_PRIVATE->capabilities = array(); 2930 $caps = $DB->get_records('capabilities', array(), 'id, name, captype, riskbitmask'); 2931 foreach ($caps as $cap) { 2932 $capname = $cap->name; 2933 unset($cap->id); 2934 unset($cap->name); 2935 $cap->riskbitmask = (int)$cap->riskbitmask; 2936 $ACCESSLIB_PRIVATE->capabilities[$capname] = $cap; 2937 } 2938 } 2939 2940 return isset($ACCESSLIB_PRIVATE->capabilities[$capabilityname]) ? $ACCESSLIB_PRIVATE->capabilities[$capabilityname] : null; 2941 } 2942 2943 /** 2944 * Returns the human-readable, translated version of the capability. 2945 * Basically a big switch statement. 2946 * 2947 * @param string $capabilityname e.g. mod/choice:readresponses 2948 * @return string 2949 */ 2950 function get_capability_string($capabilityname) { 2951 2952 // Typical capability name is 'plugintype/pluginname:capabilityname' 2953 list($type, $name, $capname) = preg_split('|[/:]|', $capabilityname); 2954 2955 if ($type === 'moodle') { 2956 $component = 'core_role'; 2957 } else if ($type === 'quizreport') { 2958 //ugly hack!! 2959 $component = 'quiz_'.$name; 2960 } else { 2961 $component = $type.'_'.$name; 2962 } 2963 2964 $stringname = $name.':'.$capname; 2965 2966 if ($component === 'core_role' or get_string_manager()->string_exists($stringname, $component)) { 2967 return get_string($stringname, $component); 2968 } 2969 2970 $dir = core_component::get_component_directory($component); 2971 if (!file_exists($dir)) { 2972 // plugin broken or does not exist, do not bother with printing of debug message 2973 return $capabilityname.' ???'; 2974 } 2975 2976 // something is wrong in plugin, better print debug 2977 return get_string($stringname, $component); 2978 } 2979 2980 /** 2981 * This gets the mod/block/course/core etc strings. 2982 * 2983 * @param string $component 2984 * @param int $contextlevel 2985 * @return string|bool String is success, false if failed 2986 */ 2987 function get_component_string($component, $contextlevel) { 2988 2989 if ($component === 'moodle' or $component === 'core') { 2990 switch ($contextlevel) { 2991 // TODO MDL-46123: this should probably use context level names instead 2992 case CONTEXT_SYSTEM: return get_string('coresystem'); 2993 case CONTEXT_USER: return get_string('users'); 2994 case CONTEXT_COURSECAT: return get_string('categories'); 2995 case CONTEXT_COURSE: return get_string('course'); 2996 case CONTEXT_MODULE: return get_string('activities'); 2997 case CONTEXT_BLOCK: return get_string('block'); 2998 default: print_error('unknowncontext'); 2999 } 3000 } 3001 3002 list($type, $name) = core_component::normalize_component($component); 3003 $dir = core_component::get_plugin_directory($type, $name); 3004 if (!file_exists($dir)) { 3005 // plugin not installed, bad luck, there is no way to find the name 3006 return $component.' ???'; 3007 } 3008 3009 switch ($type) { 3010 // TODO MDL-46123: this is really hacky and should be improved. 3011 case 'quiz': return get_string($name.':componentname', $component);// insane hack!!! 3012 case 'repository': return get_string('repository', 'repository').': '.get_string('pluginname', $component); 3013 case 'gradeimport': return get_string('gradeimport', 'grades').': '.get_string('pluginname', $component); 3014 case 'gradeexport': return get_string('gradeexport', 'grades').': '.get_string('pluginname', $component); 3015 case 'gradereport': return get_string('gradereport', 'grades').': '.get_string('pluginname', $component); 3016 case 'webservice': return get_string('webservice', 'webservice').': '.get_string('pluginname', $component); 3017 case 'block': return get_string('block').': '.get_string('pluginname', basename($component)); 3018 case 'mod': 3019 if (get_string_manager()->string_exists('pluginname', $component)) { 3020 return get_string('activity').': '.get_string('pluginname', $component); 3021 } else { 3022 return get_string('activity').': '.get_string('modulename', $component); 3023 } 3024 default: return get_string('pluginname', $component); 3025 } 3026 } 3027 3028 /** 3029 * Gets the list of roles assigned to this context and up (parents) 3030 * from the list of roles that are visible on user profile page 3031 * and participants page. 3032 * 3033 * @param context $context 3034 * @return array 3035 */ 3036 function get_profile_roles(context $context) { 3037 global $CFG, $DB; 3038 3039 if (empty($CFG->profileroles)) { 3040 return array(); 3041 } 3042 3043 list($rallowed, $params) = $DB->get_in_or_equal(explode(',', $CFG->profileroles), SQL_PARAMS_NAMED, 'a'); 3044 list($contextlist, $cparams) = $DB->get_in_or_equal($context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'p'); 3045 $params = array_merge($params, $cparams); 3046 3047 if ($coursecontext = $context->get_course_context(false)) { 3048 $params['coursecontext'] = $coursecontext->id; 3049 } else { 3050 $params['coursecontext'] = 0; 3051 } 3052 3053 $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder, rn.name AS coursealias 3054 FROM {role_assignments} ra, {role} r 3055 LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id) 3056 WHERE r.id = ra.roleid 3057 AND ra.contextid $contextlist 3058 AND r.id $rallowed 3059 ORDER BY r.sortorder ASC"; 3060 3061 return $DB->get_records_sql($sql, $params); 3062 } 3063 3064 /** 3065 * Gets the list of roles assigned to this context and up (parents) 3066 * 3067 * @param context $context 3068 * @return array 3069 */ 3070 function get_roles_used_in_context(context $context) { 3071 global $DB; 3072 3073 list($contextlist, $params) = $DB->get_in_or_equal($context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'cl'); 3074 3075 if ($coursecontext = $context->get_course_context(false)) { 3076 $params['coursecontext'] = $coursecontext->id; 3077 } else { 3078 $params['coursecontext'] = 0; 3079 } 3080 3081 $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder, rn.name AS coursealias 3082 FROM {role_assignments} ra, {role} r 3083 LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id) 3084 WHERE r.id = ra.roleid 3085 AND ra.contextid $contextlist 3086 ORDER BY r.sortorder ASC"; 3087 3088 return $DB->get_records_sql($sql, $params); 3089 } 3090 3091 /** 3092 * This function is used to print roles column in user profile page. 3093 * It is using the CFG->profileroles to limit the list to only interesting roles. 3094 * (The permission tab has full details of user role assignments.) 3095 * 3096 * @param int $userid 3097 * @param int $courseid 3098 * @return string 3099 */ 3100 function get_user_roles_in_course($userid, $courseid) { 3101 global $CFG, $DB; 3102 3103 if (empty($CFG->profileroles)) { 3104 return ''; 3105 } 3106 3107 if ($courseid == SITEID) { 3108 $context = context_system::instance(); 3109 } else { 3110 $context = context_course::instance($courseid); 3111 } 3112 3113 list($rallowed, $params) = $DB->get_in_or_equal(explode(',', $CFG->profileroles), SQL_PARAMS_NAMED, 'a'); 3114 list($contextlist, $cparams) = $DB->get_in_or_equal($context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'p'); 3115 $params = array_merge($params, $cparams); 3116 3117 if ($coursecontext = $context->get_course_context(false)) { 3118 $params['coursecontext'] = $coursecontext->id; 3119 } else { 3120 $params['coursecontext'] = 0; 3121 } 3122 3123 $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder, rn.name AS coursealias 3124 FROM {role_assignments} ra, {role} r 3125 LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id) 3126 WHERE r.id = ra.roleid 3127 AND ra.contextid $contextlist 3128 AND r.id $rallowed 3129 AND ra.userid = :userid 3130 ORDER BY r.sortorder ASC"; 3131 $params['userid'] = $userid; 3132 3133 $rolestring = ''; 3134 3135 if ($roles = $DB->get_records_sql($sql, $params)) { 3136 $rolenames = role_fix_names($roles, $context, ROLENAME_ALIAS, true); // Substitute aliases 3137 3138 foreach ($rolenames as $roleid => $rolename) { 3139 $rolenames[$roleid] = '<a href="'.$CFG->wwwroot.'/user/index.php?contextid='.$context->id.'&roleid='.$roleid.'">'.$rolename.'</a>'; 3140 } 3141 $rolestring = implode(',', $rolenames); 3142 } 3143 3144 return $rolestring; 3145 } 3146 3147 /** 3148 * Checks if a user can assign users to a particular role in this context 3149 * 3150 * @param context $context 3151 * @param int $targetroleid - the id of the role you want to assign users to 3152 * @return boolean 3153 */ 3154 function user_can_assign(context $context, $targetroleid) { 3155 global $DB; 3156 3157 // First check to see if the user is a site administrator. 3158 if (is_siteadmin()) { 3159 return true; 3160 } 3161 3162 // Check if user has override capability. 3163 // If not return false. 3164 if (!has_capability('moodle/role:assign', $context)) { 3165 return false; 3166 } 3167 // pull out all active roles of this user from this context(or above) 3168 if ($userroles = get_user_roles($context)) { 3169 foreach ($userroles as $userrole) { 3170 // if any in the role_allow_override table, then it's ok 3171 if ($DB->get_record('role_allow_assign', array('roleid'=>$userrole->roleid, 'allowassign'=>$targetroleid))) { 3172 return true; 3173 } 3174 } 3175 } 3176 3177 return false; 3178 } 3179 3180 /** 3181 * Returns all site roles in correct sort order. 3182 * 3183 * Note: this method does not localise role names or descriptions, 3184 * use role_get_names() if you need role names. 3185 * 3186 * @param context $context optional context for course role name aliases 3187 * @return array of role records with optional coursealias property 3188 */ 3189 function get_all_roles(context $context = null) { 3190 global $DB; 3191 3192 if (!$context or !$coursecontext = $context->get_course_context(false)) { 3193 $coursecontext = null; 3194 } 3195 3196 if ($coursecontext) { 3197 $sql = "SELECT r.*, rn.name AS coursealias 3198 FROM {role} r 3199 LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id) 3200 ORDER BY r.sortorder ASC"; 3201 return $DB->get_records_sql($sql, array('coursecontext'=>$coursecontext->id)); 3202 3203 } else { 3204 return $DB->get_records('role', array(), 'sortorder ASC'); 3205 } 3206 } 3207 3208 /** 3209 * Returns roles of a specified archetype 3210 * 3211 * @param string $archetype 3212 * @return array of full role records 3213 */ 3214 function get_archetype_roles($archetype) { 3215 global $DB; 3216 return $DB->get_records('role', array('archetype'=>$archetype), 'sortorder ASC'); 3217 } 3218 3219 /** 3220 * Gets all the user roles assigned in this context, or higher contexts 3221 * this is mainly used when checking if a user can assign a role, or overriding a role 3222 * i.e. we need to know what this user holds, in order to verify against allow_assign and 3223 * allow_override tables 3224 * 3225 * @param context $context 3226 * @param int $userid 3227 * @param bool $checkparentcontexts defaults to true 3228 * @param string $order defaults to 'c.contextlevel DESC, r.sortorder ASC' 3229 * @return array 3230 */ 3231 function get_user_roles(context $context, $userid = 0, $checkparentcontexts = true, $order = 'c.contextlevel DESC, r.sortorder ASC') { 3232 global $USER, $DB; 3233 3234 if (empty($userid)) { 3235 if (empty($USER->id)) { 3236 return array(); 3237 } 3238 $userid = $USER->id; 3239 } 3240 3241 if ($checkparentcontexts) { 3242 $contextids = $context->get_parent_context_ids(); 3243 } else { 3244 $contextids = array(); 3245 } 3246 $contextids[] = $context->id; 3247 3248 list($contextids, $params) = $DB->get_in_or_equal($contextids, SQL_PARAMS_QM); 3249 3250 array_unshift($params, $userid); 3251 3252 $sql = "SELECT ra.*, r.name, r.shortname 3253 FROM {role_assignments} ra, {role} r, {context} c 3254 WHERE ra.userid = ? 3255 AND ra.roleid = r.id 3256 AND ra.contextid = c.id 3257 AND ra.contextid $contextids 3258 ORDER BY $order"; 3259 3260 return $DB->get_records_sql($sql ,$params); 3261 } 3262 3263 /** 3264 * Like get_user_roles, but adds in the authenticated user role, and the front 3265 * page roles, if applicable. 3266 * 3267 * @param context $context the context. 3268 * @param int $userid optional. Defaults to $USER->id 3269 * @return array of objects with fields ->userid, ->contextid and ->roleid. 3270 */ 3271 function get_user_roles_with_special(context $context, $userid = 0) { 3272 global $CFG, $USER; 3273 3274 if (empty($userid)) { 3275 if (empty($USER->id)) { 3276 return array(); 3277 } 3278 $userid = $USER->id; 3279 } 3280 3281 $ras = get_user_roles($context, $userid); 3282 3283 // Add front-page role if relevant. 3284 $defaultfrontpageroleid = isset($CFG->defaultfrontpageroleid) ? $CFG->defaultfrontpageroleid : 0; 3285 $isfrontpage = ($context->contextlevel == CONTEXT_COURSE && $context->instanceid == SITEID) || 3286 is_inside_frontpage($context); 3287 if ($defaultfrontpageroleid && $isfrontpage) { 3288 $frontpagecontext = context_course::instance(SITEID); 3289 $ra = new stdClass(); 3290 $ra->userid = $userid; 3291 $ra->contextid = $frontpagecontext->id; 3292 $ra->roleid = $defaultfrontpageroleid; 3293 $ras[] = $ra; 3294 } 3295 3296 // Add authenticated user role if relevant. 3297 $defaultuserroleid = isset($CFG->defaultuserroleid) ? $CFG->defaultuserroleid : 0; 3298 if ($defaultuserroleid && !isguestuser($userid)) { 3299 $systemcontext = context_system::instance(); 3300 $ra = new stdClass(); 3301 $ra->userid = $userid; 3302 $ra->contextid = $systemcontext->id; 3303 $ra->roleid = $defaultuserroleid; 3304 $ras[] = $ra; 3305 } 3306 3307 return $ras; 3308 } 3309 3310 /** 3311 * Creates a record in the role_allow_override table 3312 * 3313 * @param int $sroleid source roleid 3314 * @param int $troleid target roleid 3315 * @return void 3316 */ 3317 function allow_override($sroleid, $troleid) { 3318 global $DB; 3319 3320 $record = new stdClass(); 3321 $record->roleid = $sroleid; 3322 $record->allowoverride = $troleid; 3323 $DB->insert_record('role_allow_override', $record); 3324 } 3325 3326 /** 3327 * Creates a record in the role_allow_assign table 3328 * 3329 * @param int $fromroleid source roleid 3330 * @param int $targetroleid target roleid 3331 * @return void 3332 */ 3333 function allow_assign($fromroleid, $targetroleid) { 3334 global $DB; 3335 3336 $record = new stdClass(); 3337 $record->roleid = $fromroleid; 3338 $record->allowassign = $targetroleid; 3339 $DB->insert_record('role_allow_assign', $record); 3340 } 3341 3342 /** 3343 * Creates a record in the role_allow_switch table 3344 * 3345 * @param int $fromroleid source roleid 3346 * @param int $targetroleid target roleid 3347 * @return void 3348 */ 3349 function allow_switch($fromroleid, $targetroleid) { 3350 global $DB; 3351 3352 $record = new stdClass(); 3353 $record->roleid = $fromroleid; 3354 $record->allowswitch = $targetroleid; 3355 $DB->insert_record('role_allow_switch', $record); 3356 } 3357 3358 /** 3359 * Gets a list of roles that this user can assign in this context 3360 * 3361 * @param context $context the context. 3362 * @param int $rolenamedisplay the type of role name to display. One of the 3363 * ROLENAME_X constants. Default ROLENAME_ALIAS. 3364 * @param bool $withusercounts if true, count the number of users with each role. 3365 * @param integer|object $user A user id or object. By default (null) checks the permissions of the current user. 3366 * @return array if $withusercounts is false, then an array $roleid => $rolename. 3367 * if $withusercounts is true, returns a list of three arrays, 3368 * $rolenames, $rolecounts, and $nameswithcounts. 3369 */ 3370 function get_assignable_roles(context $context, $rolenamedisplay = ROLENAME_ALIAS, $withusercounts = false, $user = null) { 3371 global $USER, $DB; 3372 3373 // make sure there is a real user specified 3374 if ($user === null) { 3375 $userid = isset($USER->id) ? $USER->id : 0; 3376 } else { 3377 $userid = is_object($user) ? $user->id : $user; 3378 } 3379 3380 if (!has_capability('moodle/role:assign', $context, $userid)) { 3381 if ($withusercounts) { 3382 return array(array(), array(), array()); 3383 } else { 3384 return array(); 3385 } 3386 } 3387 3388 $params = array(); 3389 $extrafields = ''; 3390 3391 if ($withusercounts) { 3392 $extrafields = ', (SELECT count(u.id) 3393 FROM {role_assignments} cra JOIN {user} u ON cra.userid = u.id 3394 WHERE cra.roleid = r.id AND cra.contextid = :conid AND u.deleted = 0 3395 ) AS usercount'; 3396 $params['conid'] = $context->id; 3397 } 3398 3399 if (is_siteadmin($userid)) { 3400 // show all roles allowed in this context to admins 3401 $assignrestriction = ""; 3402 } else { 3403 $parents = $context->get_parent_context_ids(true); 3404 $contexts = implode(',' , $parents); 3405 $assignrestriction = "JOIN (SELECT DISTINCT raa.allowassign AS id 3406 FROM {role_allow_assign} raa 3407 JOIN {role_assignments} ra ON ra.roleid = raa.roleid 3408 WHERE ra.userid = :userid AND ra.contextid IN ($contexts) 3409 ) ar ON ar.id = r.id"; 3410 $params['userid'] = $userid; 3411 } 3412 $params['contextlevel'] = $context->contextlevel; 3413 3414 if ($coursecontext = $context->get_course_context(false)) { 3415 $params['coursecontext'] = $coursecontext->id; 3416 } else { 3417 $params['coursecontext'] = 0; // no course aliases 3418 $coursecontext = null; 3419 } 3420 $sql = "SELECT r.id, r.name, r.shortname, rn.name AS coursealias $extrafields 3421 FROM {role} r 3422 $assignrestriction 3423 JOIN {role_context_levels} rcl ON (rcl.contextlevel = :contextlevel AND r.id = rcl.roleid) 3424 LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id) 3425 ORDER BY r.sortorder ASC"; 3426 $roles = $DB->get_records_sql($sql, $params); 3427 3428 $rolenames = role_fix_names($roles, $coursecontext, $rolenamedisplay, true); 3429 3430 if (!$withusercounts) { 3431 return $rolenames; 3432 } 3433 3434 $rolecounts = array(); 3435 $nameswithcounts = array(); 3436 foreach ($roles as $role) { 3437 $nameswithcounts[$role->id] = $rolenames[$role->id] . ' (' . $roles[$role->id]->usercount . ')'; 3438 $rolecounts[$role->id] = $roles[$role->id]->usercount; 3439 } 3440 return array($rolenames, $rolecounts, $nameswithcounts); 3441 } 3442 3443 /** 3444 * Gets a list of roles that this user can switch to in a context 3445 * 3446 * Gets a list of roles that this user can switch to in a context, for the switchrole menu. 3447 * This function just process the contents of the role_allow_switch table. You also need to 3448 * test the moodle/role:switchroles to see if the user is allowed to switch in the first place. 3449 * 3450 * @param context $context a context. 3451 * @return array an array $roleid => $rolename. 3452 */ 3453 function get_switchable_roles(context $context) { 3454 global $USER, $DB; 3455 3456 $params = array(); 3457 $extrajoins = ''; 3458 $extrawhere = ''; 3459 if (!is_siteadmin()) { 3460 // Admins are allowed to switch to any role with. 3461 // Others are subject to the additional constraint that the switch-to role must be allowed by 3462 // 'role_allow_switch' for some role they have assigned in this context or any parent. 3463 $parents = $context->get_parent_context_ids(true); 3464 $contexts = implode(',' , $parents); 3465 3466 $extrajoins = "JOIN {role_allow_switch} ras ON ras.allowswitch = rc.roleid 3467 JOIN {role_assignments} ra ON ra.roleid = ras.roleid"; 3468 $extrawhere = "WHERE ra.userid = :userid AND ra.contextid IN ($contexts)"; 3469 $params['userid'] = $USER->id; 3470 } 3471 3472 if ($coursecontext = $context->get_course_context(false)) { 3473 $params['coursecontext'] = $coursecontext->id; 3474 } else { 3475 $params['coursecontext'] = 0; // no course aliases 3476 $coursecontext = null; 3477 } 3478 3479 $query = " 3480 SELECT r.id, r.name, r.shortname, rn.name AS coursealias 3481 FROM (SELECT DISTINCT rc.roleid 3482 FROM {role_capabilities} rc 3483 $extrajoins 3484 $extrawhere) idlist 3485 JOIN {role} r ON r.id = idlist.roleid 3486 LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id) 3487 ORDER BY r.sortorder"; 3488 $roles = $DB->get_records_sql($query, $params); 3489 3490 return role_fix_names($roles, $context, ROLENAME_ALIAS, true); 3491 } 3492 3493 /** 3494 * Gets a list of roles that this user can override in this context. 3495 * 3496 * @param context $context the context. 3497 * @param int $rolenamedisplay the type of role name to display. One of the 3498 * ROLENAME_X constants. Default ROLENAME_ALIAS. 3499 * @param bool $withcounts if true, count the number of overrides that are set for each role. 3500 * @return array if $withcounts is false, then an array $roleid => $rolename. 3501 * if $withusercounts is true, returns a list of three arrays, 3502 * $rolenames, $rolecounts, and $nameswithcounts. 3503 */ 3504 function get_overridable_roles(context $context, $rolenamedisplay = ROLENAME_ALIAS, $withcounts = false) { 3505 global $USER, $DB; 3506 3507 if (!has_any_capability(array('moodle/role:safeoverride', 'moodle/role:override'), $context)) { 3508 if ($withcounts) { 3509 return array(array(), array(), array()); 3510 } else { 3511 return array(); 3512 } 3513 } 3514 3515 $parents = $context->get_parent_context_ids(true); 3516 $contexts = implode(',' , $parents); 3517 3518 $params = array(); 3519 $extrafields = ''; 3520 3521 $params['userid'] = $USER->id; 3522 if ($withcounts) { 3523 $extrafields = ', (SELECT COUNT(rc.id) FROM {role_capabilities} rc 3524 WHERE rc.roleid = ro.id AND rc.contextid = :conid) AS overridecount'; 3525 $params['conid'] = $context->id; 3526 } 3527 3528 if ($coursecontext = $context->get_course_context(false)) { 3529 $params['coursecontext'] = $coursecontext->id; 3530 } else { 3531 $params['coursecontext'] = 0; // no course aliases 3532 $coursecontext = null; 3533 } 3534 3535 if (is_siteadmin()) { 3536 // show all roles to admins 3537 $roles = $DB->get_records_sql(" 3538 SELECT ro.id, ro.name, ro.shortname, rn.name AS coursealias $extrafields 3539 FROM {role} ro 3540 LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = ro.id) 3541 ORDER BY ro.sortorder ASC", $params); 3542 3543 } else { 3544 $roles = $DB->get_records_sql(" 3545 SELECT ro.id, ro.name, ro.shortname, rn.name AS coursealias $extrafields 3546 FROM {role} ro 3547 JOIN (SELECT DISTINCT r.id 3548 FROM {role} r 3549 JOIN {role_allow_override} rao ON r.id = rao.allowoverride 3550 JOIN {role_assignments} ra ON rao.roleid = ra.roleid 3551 WHERE ra.userid = :userid AND ra.contextid IN ($contexts) 3552 ) inline_view ON ro.id = inline_view.id 3553 LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = ro.id) 3554 ORDER BY ro.sortorder ASC", $params); 3555 } 3556 3557 $rolenames = role_fix_names($roles, $context, $rolenamedisplay, true); 3558 3559 if (!$withcounts) { 3560 return $rolenames; 3561 } 3562 3563 $rolecounts = array(); 3564 $nameswithcounts = array(); 3565 foreach ($roles as $role) { 3566 $nameswithcounts[$role->id] = $rolenames[$role->id] . ' (' . $roles[$role->id]->overridecount . ')'; 3567 $rolecounts[$role->id] = $roles[$role->id]->overridecount; 3568 } 3569 return array($rolenames, $rolecounts, $nameswithcounts); 3570 } 3571 3572 /** 3573 * Create a role menu suitable for default role selection in enrol plugins. 3574 * 3575 * @package core_enrol 3576 * 3577 * @param context $context 3578 * @param int $addroleid current or default role - always added to list 3579 * @return array roleid=>localised role name 3580 */ 3581 function get_default_enrol_roles(context $context, $addroleid = null) { 3582 global $DB; 3583 3584 $params = array('contextlevel'=>CONTEXT_COURSE); 3585 3586 if ($coursecontext = $context->get_course_context(false)) { 3587 $params['coursecontext'] = $coursecontext->id; 3588 } else { 3589 $params['coursecontext'] = 0; // no course names 3590 $coursecontext = null; 3591 } 3592 3593 if ($addroleid) { 3594 $addrole = "OR r.id = :addroleid"; 3595 $params['addroleid'] = $addroleid; 3596 } else { 3597 $addrole = ""; 3598 } 3599 3600 $sql = "SELECT r.id, r.name, r.shortname, rn.name AS coursealias 3601 FROM {role} r 3602 LEFT JOIN {role_context_levels} rcl ON (rcl.roleid = r.id AND rcl.contextlevel = :contextlevel) 3603 LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id) 3604 WHERE rcl.id IS NOT NULL $addrole 3605 ORDER BY sortorder DESC"; 3606 3607 $roles = $DB->get_records_sql($sql, $params); 3608 3609 return role_fix_names($roles, $context, ROLENAME_BOTH, true); 3610 } 3611 3612 /** 3613 * Return context levels where this role is assignable. 3614 * 3615 * @param integer $roleid the id of a role. 3616 * @return array list of the context levels at which this role may be assigned. 3617 */ 3618 function get_role_contextlevels($roleid) { 3619 global $DB; 3620 return $DB->get_records_menu('role_context_levels', array('roleid' => $roleid), 3621 'contextlevel', 'id,contextlevel'); 3622 } 3623 3624 /** 3625 * Return roles suitable for assignment at the specified context level. 3626 * 3627 * NOTE: this function name looks like a typo, should be probably get_roles_for_contextlevel() 3628 * 3629 * @param integer $contextlevel a contextlevel. 3630 * @return array list of role ids that are assignable at this context level. 3631 */ 3632 function get_roles_for_contextlevels($contextlevel) { 3633 global $DB; 3634 return $DB->get_records_menu('role_context_levels', array('contextlevel' => $contextlevel), 3635 '', 'id,roleid'); 3636 } 3637 3638 /** 3639 * Returns default context levels where roles can be assigned. 3640 * 3641 * @param string $rolearchetype one of the role archetypes - that is, one of the keys 3642 * from the array returned by get_role_archetypes(); 3643 * @return array list of the context levels at which this type of role may be assigned by default. 3644 */ 3645 function get_default_contextlevels($rolearchetype) { 3646 static $defaults = array( 3647 'manager' => array(CONTEXT_SYSTEM, CONTEXT_COURSECAT, CONTEXT_COURSE), 3648 'coursecreator' => array(CONTEXT_SYSTEM, CONTEXT_COURSECAT), 3649 'editingteacher' => array(CONTEXT_COURSE, CONTEXT_MODULE), 3650 'teacher' => array(CONTEXT_COURSE, CONTEXT_MODULE), 3651 'student' => array(CONTEXT_COURSE, CONTEXT_MODULE), 3652 'guest' => array(), 3653 'user' => array(), 3654 'frontpage' => array()); 3655 3656 if (isset($defaults[$rolearchetype])) { 3657 return $defaults[$rolearchetype]; 3658 } else { 3659 return array(); 3660 } 3661 } 3662 3663 /** 3664 * Set the context levels at which a particular role can be assigned. 3665 * Throws exceptions in case of error. 3666 * 3667 * @param integer $roleid the id of a role. 3668 * @param array $contextlevels the context levels at which this role should be assignable, 3669 * duplicate levels are removed. 3670 * @return void 3671 */ 3672 function set_role_contextlevels($roleid, array $contextlevels) { 3673 global $DB; 3674 $DB->delete_records('role_context_levels', array('roleid' => $roleid)); 3675 $rcl = new stdClass(); 3676 $rcl->roleid = $roleid; 3677 $contextlevels = array_unique($contextlevels); 3678 foreach ($contextlevels as $level) { 3679 $rcl->contextlevel = $level; 3680 $DB->insert_record('role_context_levels', $rcl, false, true); 3681 } 3682 } 3683 3684 /** 3685 * Who has this capability in this context? 3686 * 3687 * This can be a very expensive call - use sparingly and keep 3688 * the results if you are going to need them again soon. 3689 * 3690 * Note if $fields is empty this function attempts to get u.* 3691 * which can get rather large - and has a serious perf impact 3692 * on some DBs. 3693 * 3694 * @param context $context 3695 * @param string|array $capability - capability name(s) 3696 * @param string $fields - fields to be pulled. The user table is aliased to 'u'. u.id MUST be included. 3697 * @param string $sort - the sort order. Default is lastaccess time. 3698 * @param mixed $limitfrom - number of records to skip (offset) 3699 * @param mixed $limitnum - number of records to fetch 3700 * @param string|array $groups - single group or array of groups - only return 3701 * users who are in one of these group(s). 3702 * @param string|array $exceptions - list of users to exclude, comma separated or array 3703 * @param bool $doanything_ignored not used any more, admin accounts are never returned 3704 * @param bool $view_ignored - use get_enrolled_sql() instead 3705 * @param bool $useviewallgroups if $groups is set the return users who 3706 * have capability both $capability and moodle/site:accessallgroups 3707 * in this context, as well as users who have $capability and who are 3708 * in $groups. 3709 * @return array of user records 3710 */ 3711 function get_users_by_capability(context $context, $capability, $fields = '', $sort = '', $limitfrom = '', $limitnum = '', 3712 $groups = '', $exceptions = '', $doanything_ignored = null, $view_ignored = null, $useviewallgroups = false) { 3713 global $CFG, $DB; 3714 3715 $defaultuserroleid = isset($CFG->defaultuserroleid) ? $CFG->defaultuserroleid : 0; 3716 $defaultfrontpageroleid = isset($CFG->defaultfrontpageroleid) ? $CFG->defaultfrontpageroleid : 0; 3717 3718 $ctxids = trim($context->path, '/'); 3719 $ctxids = str_replace('/', ',', $ctxids); 3720 3721 // Context is the frontpage 3722 $iscoursepage = false; // coursepage other than fp 3723 $isfrontpage = false; 3724 if ($context->contextlevel == CONTEXT_COURSE) { 3725 if ($context->instanceid == SITEID) { 3726 $isfrontpage = true; 3727 } else { 3728 $iscoursepage = true; 3729 } 3730 } 3731 $isfrontpage = ($isfrontpage || is_inside_frontpage($context)); 3732 3733 $caps = (array)$capability; 3734 3735 // construct list of context paths bottom-->top 3736 list($contextids, $paths) = get_context_info_list($context); 3737 3738 // we need to find out all roles that have these capabilities either in definition or in overrides 3739 $defs = array(); 3740 list($incontexts, $params) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, 'con'); 3741 list($incaps, $params2) = $DB->get_in_or_equal($caps, SQL_PARAMS_NAMED, 'cap'); 3742 $params = array_merge($params, $params2); 3743 $sql = "SELECT rc.id, rc.roleid, rc.permission, rc.capability, ctx.path 3744 FROM {role_capabilities} rc 3745 JOIN {context} ctx on rc.contextid = ctx.id 3746 WHERE rc.contextid $incontexts AND rc.capability $incaps"; 3747 3748 $rcs = $DB->get_records_sql($sql, $params); 3749 foreach ($rcs as $rc) { 3750 $defs[$rc->capability][$rc->path][$rc->roleid] = $rc->permission; 3751 } 3752 3753 // go through the permissions bottom-->top direction to evaluate the current permission, 3754 // first one wins (prohibit is an exception that always wins) 3755 $access = array(); 3756 foreach ($caps as $cap) { 3757 foreach ($paths as $path) { 3758 if (empty($defs[$cap][$path])) { 3759 continue; 3760 } 3761 foreach($defs[$cap][$path] as $roleid => $perm) { 3762 if ($perm == CAP_PROHIBIT) { 3763 $access[$cap][$roleid] = CAP_PROHIBIT; 3764 continue; 3765 } 3766 if (!isset($access[$cap][$roleid])) { 3767 $access[$cap][$roleid] = (int)$perm; 3768 } 3769 } 3770 } 3771 } 3772 3773 // make lists of roles that are needed and prohibited in this context 3774 $needed = array(); // one of these is enough 3775 $prohibited = array(); // must not have any of these 3776 foreach ($caps as $cap) { 3777 if (empty($access[$cap])) { 3778 continue; 3779 } 3780 foreach ($access[$cap] as $roleid => $perm) { 3781 if ($perm == CAP_PROHIBIT) { 3782 unset($needed[$cap][$roleid]); 3783 $prohibited[$cap][$roleid] = true; 3784 } else if ($perm == CAP_ALLOW and empty($prohibited[$cap][$roleid])) { 3785 $needed[$cap][$roleid] = true; 3786 } 3787 } 3788 if (empty($needed[$cap]) or !empty($prohibited[$cap][$defaultuserroleid])) { 3789 // easy, nobody has the permission 3790 unset($needed[$cap]); 3791 unset($prohibited[$cap]); 3792 } else if ($isfrontpage and !empty($prohibited[$cap][$defaultfrontpageroleid])) { 3793 // everybody is disqualified on the frontpage 3794 unset($needed[$cap]); 3795 unset($prohibited[$cap]); 3796 } 3797 if (empty($prohibited[$cap])) { 3798 unset($prohibited[$cap]); 3799 } 3800 } 3801 3802 if (empty($needed)) { 3803 // there can not be anybody if no roles match this request 3804 return array(); 3805 } 3806 3807 if (empty($prohibited)) { 3808 // we can compact the needed roles 3809 $n = array(); 3810 foreach ($needed as $cap) { 3811 foreach ($cap as $roleid=>$unused) { 3812 $n[$roleid] = true; 3813 } 3814 } 3815 $needed = array('any'=>$n); 3816 unset($n); 3817 } 3818 3819 // ***** Set up default fields ****** 3820 if (empty($fields)) { 3821 if ($iscoursepage) { 3822 $fields = 'u.*, ul.timeaccess AS lastaccess'; 3823 } else { 3824 $fields = 'u.*'; 3825 } 3826 } else { 3827 if ($CFG->debugdeveloper && strpos($fields, 'u.*') === false && strpos($fields, 'u.id') === false) { 3828 debugging('u.id must be included in the list of fields passed to get_users_by_capability().', DEBUG_DEVELOPER); 3829 } 3830 } 3831 3832 // Set up default sort 3833 if (empty($sort)) { // default to course lastaccess or just lastaccess 3834 if ($iscoursepage) { 3835 $sort = 'ul.timeaccess'; 3836 } else { 3837 $sort = 'u.lastaccess'; 3838 } 3839 } 3840 3841 // Prepare query clauses 3842 $wherecond = array(); 3843 $params = array(); 3844 $joins = array(); 3845 3846 // User lastaccess JOIN 3847 if ((strpos($sort, 'ul.timeaccess') === false) and (strpos($fields, 'ul.timeaccess') === false)) { 3848 // user_lastaccess is not required MDL-13810 3849 } else { 3850 if ($iscoursepage) { 3851 $joins[] = "LEFT OUTER JOIN {user_lastaccess} ul ON (ul.userid = u.id AND ul.courseid = {$context->instanceid})"; 3852 } else { 3853 throw new coding_exception('Invalid sort in get_users_by_capability(), ul.timeaccess allowed only for course contexts.'); 3854 } 3855 } 3856 3857 // We never return deleted users or guest account. 3858 $wherecond[] = "u.deleted = 0 AND u.id <> :guestid"; 3859 $params['guestid'] = $CFG->siteguest; 3860 3861 // Groups 3862 if ($groups) { 3863 $groups = (array)$groups; 3864 list($grouptest, $grpparams) = $DB->get_in_or_equal($groups, SQL_PARAMS_NAMED, 'grp'); 3865 $grouptest = "u.id IN (SELECT userid FROM {groups_members} gm WHERE gm.groupid $grouptest)"; 3866 $params = array_merge($params, $grpparams); 3867 3868 if ($useviewallgroups) { 3869 $viewallgroupsusers = get_users_by_capability($context, 'moodle/site:accessallgroups', 'u.id, u.id', '', '', '', '', $exceptions); 3870 if (!empty($viewallgroupsusers)) { 3871 $wherecond[] = "($grouptest OR u.id IN (" . implode(',', array_keys($viewallgroupsusers)) . '))'; 3872 } else { 3873 $wherecond[] = "($grouptest)"; 3874 } 3875 } else { 3876 $wherecond[] = "($grouptest)"; 3877 } 3878 } 3879 3880 // User exceptions 3881 if (!empty($exceptions)) { 3882 $exceptions = (array)$exceptions; 3883 list($exsql, $exparams) = $DB->get_in_or_equal($exceptions, SQL_PARAMS_NAMED, 'exc', false); 3884 $params = array_merge($params, $exparams); 3885 $wherecond[] = "u.id $exsql"; 3886 } 3887 3888 // now add the needed and prohibited roles conditions as joins 3889 if (!empty($needed['any'])) { 3890 // simple case - there are no prohibits involved 3891 if (!empty($needed['any'][$defaultuserroleid]) or ($isfrontpage and !empty($needed['any'][$defaultfrontpageroleid]))) { 3892 // everybody 3893 } else { 3894 $joins[] = "JOIN (SELECT DISTINCT userid 3895 FROM {role_assignments} 3896 WHERE contextid IN ($ctxids) 3897 AND roleid IN (".implode(',', array_keys($needed['any'])) .") 3898 ) ra ON ra.userid = u.id"; 3899 } 3900 } else { 3901 $unions = array(); 3902 $everybody = false; 3903 foreach ($needed as $cap=>$unused) { 3904 if (empty($prohibited[$cap])) { 3905 if (!empty($needed[$cap][$defaultuserroleid]) or ($isfrontpage and !empty($needed[$cap][$defaultfrontpageroleid]))) { 3906 $everybody = true; 3907 break; 3908 } else { 3909 $unions[] = "SELECT userid 3910 FROM {role_assignments} 3911 WHERE contextid IN ($ctxids) 3912 AND roleid IN (".implode(',', array_keys($needed[$cap])) .")"; 3913 } 3914 } else { 3915 if (!empty($prohibited[$cap][$defaultuserroleid]) or ($isfrontpage and !empty($prohibited[$cap][$defaultfrontpageroleid]))) { 3916 // nobody can have this cap because it is prevented in default roles 3917 continue; 3918 3919 } else if (!empty($needed[$cap][$defaultuserroleid]) or ($isfrontpage and !empty($needed[$cap][$defaultfrontpageroleid]))) { 3920 // everybody except the prohibitted - hiding does not matter 3921 $unions[] = "SELECT id AS userid 3922 FROM {user} 3923 WHERE id NOT IN (SELECT userid 3924 FROM {role_assignments} 3925 WHERE contextid IN ($ctxids) 3926 AND roleid IN (".implode(',', array_keys($prohibited[$cap])) ."))"; 3927 3928 } else { 3929 $unions[] = "SELECT userid 3930 FROM {role_assignments} 3931 WHERE contextid IN ($ctxids) 3932 AND roleid IN (".implode(',', array_keys($needed[$cap])) .") 3933 AND roleid NOT IN (".implode(',', array_keys($prohibited[$cap])) .")"; 3934 } 3935 } 3936 } 3937 if (!$everybody) { 3938 if ($unions) { 3939 $joins[] = "JOIN (SELECT DISTINCT userid FROM ( ".implode(' UNION ', $unions)." ) us) ra ON ra.userid = u.id"; 3940 } else { 3941 // only prohibits found - nobody can be matched 3942 $wherecond[] = "1 = 2"; 3943 } 3944 } 3945 } 3946 3947 // Collect WHERE conditions and needed joins 3948 $where = implode(' AND ', $wherecond); 3949 if ($where !== '') { 3950 $where = 'WHERE ' . $where; 3951 } 3952 $joins = implode("\n", $joins); 3953 3954 // Ok, let's get the users! 3955 $sql = "SELECT $fields 3956 FROM {user} u 3957 $joins 3958 $where 3959 ORDER BY $sort"; 3960 3961 return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum); 3962 } 3963 3964 /** 3965 * Re-sort a users array based on a sorting policy 3966 * 3967 * Will re-sort a $users results array (from get_users_by_capability(), usually) 3968 * based on a sorting policy. This is to support the odd practice of 3969 * sorting teachers by 'authority', where authority was "lowest id of the role 3970 * assignment". 3971 * 3972 * Will execute 1 database query. Only suitable for small numbers of users, as it 3973 * uses an u.id IN() clause. 3974 * 3975 * Notes about the sorting criteria. 3976 * 3977 * As a default, we cannot rely on role.sortorder because then 3978 * admins/coursecreators will always win. That is why the sane 3979 * rule "is locality matters most", with sortorder as 2nd 3980 * consideration. 3981 * 3982 * If you want role.sortorder, use the 'sortorder' policy, and 3983 * name explicitly what roles you want to cover. It's probably 3984 * a good idea to see what roles have the capabilities you want 3985 * (array_diff() them against roiles that have 'can-do-anything' 3986 * to weed out admin-ish roles. Or fetch a list of roles from 3987 * variables like $CFG->coursecontact . 3988 * 3989 * @param array $users Users array, keyed on userid 3990 * @param context $context 3991 * @param array $roles ids of the roles to include, optional 3992 * @param string $sortpolicy defaults to locality, more about 3993 * @return array sorted copy of the array 3994 */ 3995 function sort_by_roleassignment_authority($users, context $context, $roles = array(), $sortpolicy = 'locality') { 3996 global $DB; 3997 3998 $userswhere = ' ra.userid IN (' . implode(',',array_keys($users)) . ')'; 3999 $contextwhere = 'AND ra.contextid IN ('.str_replace('/', ',',substr($context->path, 1)).')'; 4000 if (empty($roles)) { 4001 $roleswhere = ''; 4002 } else { 4003 $roleswhere = ' AND ra.roleid IN ('.implode(',',$roles).')'; 4004 } 4005 4006 $sql = "SELECT ra.userid 4007 FROM {role_assignments} ra 4008 JOIN {role} r 4009 ON ra.roleid=r.id 4010 JOIN {context} ctx 4011 ON ra.contextid=ctx.id 4012 WHERE $userswhere 4013 $contextwhere 4014 $roleswhere"; 4015 4016 // Default 'locality' policy -- read PHPDoc notes 4017 // about sort policies... 4018 $orderby = 'ORDER BY ' 4019 .'ctx.depth DESC, ' /* locality wins */ 4020 .'r.sortorder ASC, ' /* rolesorting 2nd criteria */ 4021 .'ra.id'; /* role assignment order tie-breaker */ 4022 if ($sortpolicy === 'sortorder') { 4023 $orderby = 'ORDER BY ' 4024 .'r.sortorder ASC, ' /* rolesorting 2nd criteria */ 4025 .'ra.id'; /* role assignment order tie-breaker */ 4026 } 4027 4028 $sortedids = $DB->get_fieldset_sql($sql . $orderby); 4029 $sortedusers = array(); 4030 $seen = array(); 4031 4032 foreach ($sortedids as $id) { 4033 // Avoid duplicates 4034 if (isset($seen[$id])) { 4035 continue; 4036 } 4037 $seen[$id] = true; 4038 4039 // assign 4040 $sortedusers[$id] = $users[$id]; 4041 } 4042 return $sortedusers; 4043 } 4044 4045 /** 4046 * Gets all the users assigned this role in this context or higher 4047 * 4048 * @param int $roleid (can also be an array of ints!) 4049 * @param context $context 4050 * @param bool $parent if true, get list of users assigned in higher context too 4051 * @param string $fields fields from user (u.) , role assignment (ra) or role (r.) 4052 * @param string $sort sort from user (u.) , role assignment (ra.) or role (r.). 4053 * null => use default sort from users_order_by_sql. 4054 * @param bool $all true means all, false means limit to enrolled users 4055 * @param string $group defaults to '' 4056 * @param mixed $limitfrom defaults to '' 4057 * @param mixed $limitnum defaults to '' 4058 * @param string $extrawheretest defaults to '' 4059 * @param array $whereorsortparams any paramter values used by $sort or $extrawheretest. 4060 * @return array 4061 */ 4062 function get_role_users($roleid, context $context, $parent = false, $fields = '', 4063 $sort = null, $all = true, $group = '', 4064 $limitfrom = '', $limitnum = '', $extrawheretest = '', $whereorsortparams = array()) { 4065 global $DB; 4066 4067 if (empty($fields)) { 4068 $allnames = get_all_user_name_fields(true, 'u'); 4069 $fields = 'u.id, u.confirmed, u.username, '. $allnames . ', ' . 4070 'u.maildisplay, u.mailformat, u.maildigest, u.email, u.emailstop, u.city, '. 4071 'u.country, u.picture, u.idnumber, u.department, u.institution, '. 4072 'u.lang, u.timezone, u.lastaccess, u.mnethostid, r.name AS rolename, r.sortorder, '. 4073 'r.shortname AS roleshortname, rn.name AS rolecoursealias'; 4074 } 4075 4076 $parentcontexts = ''; 4077 if ($parent) { 4078 $parentcontexts = substr($context->path, 1); // kill leading slash 4079 $parentcontexts = str_replace('/', ',', $parentcontexts); 4080 if ($parentcontexts !== '') { 4081 $parentcontexts = ' OR ra.contextid IN ('.$parentcontexts.' )'; 4082 } 4083 } 4084 4085 if ($roleid) { 4086 list($rids, $params) = $DB->get_in_or_equal($roleid, SQL_PARAMS_NAMED, 'r'); 4087 $roleselect = "AND ra.roleid $rids"; 4088 } else { 4089 $params = array(); 4090 $roleselect = ''; 4091 } 4092 4093 if ($coursecontext = $context->get_course_context(false)) { 4094 $params['coursecontext'] = $coursecontext->id; 4095 } else { 4096 $params['coursecontext'] = 0; 4097 } 4098 4099 if ($group) { 4100 $groupjoin = "JOIN {groups_members} gm ON gm.userid = u.id"; 4101 $groupselect = " AND gm.groupid = :groupid "; 4102 $params['groupid'] = $group; 4103 } else { 4104 $groupjoin = ''; 4105 $groupselect = ''; 4106 } 4107 4108 $params['contextid'] = $context->id; 4109 4110 if ($extrawheretest) { 4111 $extrawheretest = ' AND ' . $extrawheretest; 4112 } 4113 4114 if ($whereorsortparams) { 4115 $params = array_merge($params, $whereorsortparams); 4116 } 4117 4118 if (!$sort) { 4119 list($sort, $sortparams) = users_order_by_sql('u'); 4120 $params = array_merge($params, $sortparams); 4121 } 4122 4123 if ($all === null) { 4124 // Previously null was used to indicate that parameter was not used. 4125 $all = true; 4126 } 4127 if (!$all and $coursecontext) { 4128 // Do not use get_enrolled_sql() here for performance reasons. 4129 $ejoin = "JOIN {user_enrolments} ue ON ue.userid = u.id 4130 JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :ecourseid)"; 4131 $params['ecourseid'] = $coursecontext->instanceid; 4132 } else { 4133 $ejoin = ""; 4134 } 4135 4136 $sql = "SELECT DISTINCT $fields, ra.roleid 4137 FROM {role_assignments} ra 4138 JOIN {user} u ON u.id = ra.userid 4139 JOIN {role} r ON ra.roleid = r.id 4140 $ejoin 4141 LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id) 4142 $groupjoin 4143 WHERE (ra.contextid = :contextid $parentcontexts) 4144 $roleselect 4145 $groupselect 4146 $extrawheretest 4147 ORDER BY $sort"; // join now so that we can just use fullname() later 4148 4149 return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum); 4150 } 4151 4152 /** 4153 * Counts all the users assigned this role in this context or higher 4154 * 4155 * @param int|array $roleid either int or an array of ints 4156 * @param context $context 4157 * @param bool $parent if true, get list of users assigned in higher context too 4158 * @return int Returns the result count 4159 */ 4160 function count_role_users($roleid, context $context, $parent = false) { 4161 global $DB; 4162 4163 if ($parent) { 4164 if ($contexts = $context->get_parent_context_ids()) { 4165 $parentcontexts = ' OR r.contextid IN ('.implode(',', $contexts).')'; 4166 } else { 4167 $parentcontexts = ''; 4168 } 4169 } else { 4170 $parentcontexts = ''; 4171 } 4172 4173 if ($roleid) { 4174 list($rids, $params) = $DB->get_in_or_equal($roleid, SQL_PARAMS_QM); 4175 $roleselect = "AND r.roleid $rids"; 4176 } else { 4177 $params = array(); 4178 $roleselect = ''; 4179 } 4180 4181 array_unshift($params, $context->id); 4182 4183 $sql = "SELECT COUNT(u.id) 4184 FROM {role_assignments} r 4185 JOIN {user} u ON u.id = r.userid 4186 WHERE (r.contextid = ? $parentcontexts) 4187 $roleselect 4188 AND u.deleted = 0"; 4189 4190 return $DB->count_records_sql($sql, $params); 4191 } 4192 4193 /** 4194 * This function gets the list of courses that this user has a particular capability in. 4195 * It is still not very efficient. 4196 * 4197 * @param string $capability Capability in question 4198 * @param int $userid User ID or null for current user 4199 * @param bool $doanything True if 'doanything' is permitted (default) 4200 * @param string $fieldsexceptid Leave blank if you only need 'id' in the course records; 4201 * otherwise use a comma-separated list of the fields you require, not including id 4202 * @param string $orderby If set, use a comma-separated list of fields from course 4203 * table with sql modifiers (DESC) if needed 4204 * @return array|bool Array of courses, if none found false is returned. 4205 */ 4206 function get_user_capability_course($capability, $userid = null, $doanything = true, $fieldsexceptid = '', $orderby = '') { 4207 global $DB; 4208 4209 // Convert fields list and ordering 4210 $fieldlist = ''; 4211 if ($fieldsexceptid) { 4212 $fields = explode(',', $fieldsexceptid); 4213 foreach($fields as $field) { 4214 $fieldlist .= ',c.'.$field; 4215 } 4216 } 4217 if ($orderby) { 4218 $fields = explode(',', $orderby); 4219 $orderby = ''; 4220 foreach($fields as $field) { 4221 if ($orderby) { 4222 $orderby .= ','; 4223 } 4224 $orderby .= 'c.'.$field; 4225 } 4226 $orderby = 'ORDER BY '.$orderby; 4227 } 4228 4229 // Obtain a list of everything relevant about all courses including context. 4230 // Note the result can be used directly as a context (we are going to), the course 4231 // fields are just appended. 4232 4233 $contextpreload = context_helper::get_preload_record_columns_sql('x'); 4234 4235 $courses = array(); 4236 $rs = $DB->get_recordset_sql("SELECT c.id $fieldlist, $contextpreload 4237 FROM {course} c 4238 JOIN {context} x ON (c.id=x.instanceid AND x.contextlevel=".CONTEXT_COURSE.") 4239 $orderby"); 4240 // Check capability for each course in turn 4241 foreach ($rs as $course) { 4242 context_helper::preload_from_record($course); 4243 $context = context_course::instance($course->id); 4244 if (has_capability($capability, $context, $userid, $doanything)) { 4245 // We've got the capability. Make the record look like a course record 4246 // and store it 4247 $courses[] = $course; 4248 } 4249 } 4250 $rs->close(); 4251 return empty($courses) ? false : $courses; 4252 } 4253 4254 /** 4255 * This function finds the roles assigned directly to this context only 4256 * i.e. no roles in parent contexts 4257 * 4258 * @param context $context 4259 * @return array 4260 */ 4261 function get_roles_on_exact_context(context $context) { 4262 global $DB; 4263 4264 return $DB->get_records_sql("SELECT r.* 4265 FROM {role_assignments} ra, {role} r 4266 WHERE ra.roleid = r.id AND ra.contextid = ?", 4267 array($context->id)); 4268 } 4269 4270 /** 4271 * Switches the current user to another role for the current session and only 4272 * in the given context. 4273 * 4274 * The caller *must* check 4275 * - that this op is allowed 4276 * - that the requested role can be switched to in this context (use get_switchable_roles) 4277 * - that the requested role is NOT $CFG->defaultuserroleid 4278 * 4279 * To "unswitch" pass 0 as the roleid. 4280 * 4281 * This function *will* modify $USER->access - beware 4282 * 4283 * @param integer $roleid the role to switch to. 4284 * @param context $context the context in which to perform the switch. 4285 * @return bool success or failure. 4286 */ 4287 function role_switch($roleid, context $context) { 4288 global $USER; 4289 4290 // 4291 // Plan of action 4292 // 4293 // - Add the ghost RA to $USER->access 4294 // as $USER->access['rsw'][$path] = $roleid 4295 // 4296 // - Make sure $USER->access['rdef'] has the roledefs 4297 // it needs to honour the switcherole 4298 // 4299 // Roledefs will get loaded "deep" here - down to the last child 4300 // context. Note that 4301 // 4302 // - When visiting subcontexts, our selective accessdata loading 4303 // will still work fine - though those ra/rdefs will be ignored 4304 // appropriately while the switch is in place 4305 // 4306 // - If a switcherole happens at a category with tons of courses 4307 // (that have many overrides for switched-to role), the session 4308 // will get... quite large. Sometimes you just can't win. 4309 // 4310 // To un-switch just unset($USER->access['rsw'][$path]) 4311 // 4312 // Note: it is not possible to switch to roles that do not have course:view 4313 4314 if (!isset($USER->access)) { 4315 load_all_capabilities(); 4316 } 4317 4318 4319 // Add the switch RA 4320 if ($roleid == 0) { 4321 unset($USER->access['rsw'][$context->path]); 4322 return true; 4323 } 4324 4325 $USER->access['rsw'][$context->path] = $roleid; 4326 4327 // Load roledefs 4328 load_role_access_by_context($roleid, $context, $USER->access); 4329 4330 return true; 4331 } 4332 4333 /** 4334 * Checks if the user has switched roles within the given course. 4335 * 4336 * Note: You can only switch roles within the course, hence it takes a course id 4337 * rather than a context. On that note Petr volunteered to implement this across 4338 * all other contexts, all requests for this should be forwarded to him ;) 4339 * 4340 * @param int $courseid The id of the course to check 4341 * @return bool True if the user has switched roles within the course. 4342 */ 4343 function is_role_switched($courseid) { 4344 global $USER; 4345 $context = context_course::instance($courseid, MUST_EXIST); 4346 return (!empty($USER->access['rsw'][$context->path])); 4347 } 4348 4349 /** 4350 * Get any role that has an override on exact context 4351 * 4352 * @param context $context The context to check 4353 * @return array An array of roles 4354 */ 4355 function get_roles_with_override_on_context(context $context) { 4356 global $DB; 4357 4358 return $DB->get_records_sql("SELECT r.* 4359 FROM {role_capabilities} rc, {role} r 4360 WHERE rc.roleid = r.id AND rc.contextid = ?", 4361 array($context->id)); 4362 } 4363 4364 /** 4365 * Get all capabilities for this role on this context (overrides) 4366 * 4367 * @param stdClass $role 4368 * @param context $context 4369 * @return array 4370 */ 4371 function get_capabilities_from_role_on_context($role, context $context) { 4372 global $DB; 4373 4374 return $DB->get_records_sql("SELECT * 4375 FROM {role_capabilities} 4376 WHERE contextid = ? AND roleid = ?", 4377 array($context->id, $role->id)); 4378 } 4379 4380 /** 4381 * Find out which roles has assignment on this context 4382 * 4383 * @param context $context 4384 * @return array 4385 * 4386 */ 4387 function get_roles_with_assignment_on_context(context $context) { 4388 global $DB; 4389 4390 return $DB->get_records_sql("SELECT r.* 4391 FROM {role_assignments} ra, {role} r 4392 WHERE ra.roleid = r.id AND ra.contextid = ?", 4393 array($context->id)); 4394 } 4395 4396 /** 4397 * Find all user assignment of users for this role, on this context 4398 * 4399 * @param stdClass $role 4400 * @param context $context 4401 * @return array 4402 */ 4403 function get_users_from_role_on_context($role, context $context) { 4404 global $DB; 4405 4406 return $DB->get_records_sql("SELECT * 4407 FROM {role_assignments} 4408 WHERE contextid = ? AND roleid = ?", 4409 array($context->id, $role->id)); 4410 } 4411 4412 /** 4413 * Simple function returning a boolean true if user has roles 4414 * in context or parent contexts, otherwise false. 4415 * 4416 * @param int $userid 4417 * @param int $roleid 4418 * @param int $contextid empty means any context 4419 * @return bool 4420 */ 4421 function user_has_role_assignment($userid, $roleid, $contextid = 0) { 4422 global $DB; 4423 4424 if ($contextid) { 4425 if (!$context = context::instance_by_id($contextid, IGNORE_MISSING)) { 4426 return false; 4427 } 4428 $parents = $context->get_parent_context_ids(true); 4429 list($contexts, $params) = $DB->get_in_or_equal($parents, SQL_PARAMS_NAMED, 'r'); 4430 $params['userid'] = $userid; 4431 $params['roleid'] = $roleid; 4432 4433 $sql = "SELECT COUNT(ra.id) 4434 FROM {role_assignments} ra 4435 WHERE ra.userid = :userid AND ra.roleid = :roleid AND ra.contextid $contexts"; 4436 4437 $count = $DB->get_field_sql($sql, $params); 4438 return ($count > 0); 4439 4440 } else { 4441 return $DB->record_exists('role_assignments', array('userid'=>$userid, 'roleid'=>$roleid)); 4442 } 4443 } 4444 4445 /** 4446 * Get localised role name or alias if exists and format the text. 4447 * 4448 * @param stdClass $role role object 4449 * - optional 'coursealias' property should be included for performance reasons if course context used 4450 * - description property is not required here 4451 * @param context|bool $context empty means system context 4452 * @param int $rolenamedisplay type of role name 4453 * @return string localised role name or course role name alias 4454 */ 4455 function role_get_name(stdClass $role, $context = null, $rolenamedisplay = ROLENAME_ALIAS) { 4456 global $DB; 4457 4458 if ($rolenamedisplay == ROLENAME_SHORT) { 4459 return $role->shortname; 4460 } 4461 4462 if (!$context or !$coursecontext = $context->get_course_context(false)) { 4463 $coursecontext = null; 4464 } 4465 4466 if ($coursecontext and !property_exists($role, 'coursealias') and ($rolenamedisplay == ROLENAME_ALIAS or $rolenamedisplay == ROLENAME_BOTH or $rolenamedisplay == ROLENAME_ALIAS_RAW)) { 4467 $role = clone($role); // Do not modify parameters. 4468 if ($r = $DB->get_record('role_names', array('roleid'=>$role->id, 'contextid'=>$coursecontext->id))) { 4469 $role->coursealias = $r->name; 4470 } else { 4471 $role->coursealias = null; 4472 } 4473 } 4474 4475 if ($rolenamedisplay == ROLENAME_ALIAS_RAW) { 4476 if ($coursecontext) { 4477 return $role->coursealias; 4478 } else { 4479 return null; 4480 } 4481 } 4482 4483 if (trim($role->name) !== '') { 4484 // For filtering always use context where was the thing defined - system for roles here. 4485 $original = format_string($role->name, true, array('context'=>context_system::instance())); 4486 4487 } else { 4488 // Empty role->name means we want to see localised role name based on shortname, 4489 // only default roles are supposed to be localised. 4490 switch ($role->shortname) { 4491 case 'manager': $original = get_string('manager', 'role'); break; 4492 case 'coursecreator': $original = get_string('coursecreators'); break; 4493 case 'editingteacher': $original = get_string('defaultcourseteacher'); break; 4494 case 'teacher': $original = get_string('noneditingteacher'); break; 4495 case 'student': $original = get_string('defaultcoursestudent'); break; 4496 case 'guest': $original = get_string('guest'); break; 4497 case 'user': $original = get_string('authenticateduser'); break; 4498 case 'frontpage': $original = get_string('frontpageuser', 'role'); break; 4499 // We should not get here, the role UI should require the name for custom roles! 4500 default: $original = $role->shortname; break; 4501 } 4502 } 4503 4504 if ($rolenamedisplay == ROLENAME_ORIGINAL) { 4505 return $original; 4506 } 4507 4508 if ($rolenamedisplay == ROLENAME_ORIGINALANDSHORT) { 4509 return "$original ($role->shortname)"; 4510 } 4511 4512 if ($rolenamedisplay == ROLENAME_ALIAS) { 4513 if ($coursecontext and trim($role->coursealias) !== '') { 4514 return format_string($role->coursealias, true, array('context'=>$coursecontext)); 4515 } else { 4516 return $original; 4517 } 4518 } 4519 4520 if ($rolenamedisplay == ROLENAME_BOTH) { 4521 if ($coursecontext and trim($role->coursealias) !== '') { 4522 return format_string($role->coursealias, true, array('context'=>$coursecontext)) . " ($original)"; 4523 } else { 4524 return $original; 4525 } 4526 } 4527 4528 throw new coding_exception('Invalid $rolenamedisplay parameter specified in role_get_name()'); 4529 } 4530 4531 /** 4532 * Returns localised role description if available. 4533 * If the name is empty it tries to find the default role name using 4534 * hardcoded list of default role names or other methods in the future. 4535 * 4536 * @param stdClass $role 4537 * @return string localised role name 4538 */ 4539 function role_get_description(stdClass $role) { 4540 if (!html_is_blank($role->description)) { 4541 return format_text($role->description, FORMAT_HTML, array('context'=>context_system::instance())); 4542 } 4543 4544 switch ($role->shortname) { 4545 case 'manager': return get_string('managerdescription', 'role'); 4546 case 'coursecreator': return get_string('coursecreatorsdescription'); 4547 case 'editingteacher': return get_string('defaultcourseteacherdescription'); 4548 case 'teacher': return get_string('noneditingteacherdescription'); 4549 case 'student': return get_string('defaultcoursestudentdescription'); 4550 case 'guest': return get_string('guestdescription'); 4551 case 'user': return get_string('authenticateduserdescription'); 4552 case 'frontpage': return get_string('frontpageuserdescription', 'role'); 4553 default: return ''; 4554 } 4555 } 4556 4557 /** 4558 * Get all the localised role names for a context. 4559 * 4560 * In new installs default roles have empty names, this function 4561 * add localised role names using current language pack. 4562 * 4563 * @param context $context the context, null means system context 4564 * @param array of role objects with a ->localname field containing the context-specific role name. 4565 * @param int $rolenamedisplay 4566 * @param bool $returnmenu true means id=>localname, false means id=>rolerecord 4567 * @return array Array of context-specific role names, or role objects with a ->localname field added. 4568 */ 4569 function role_get_names(context $context = null, $rolenamedisplay = ROLENAME_ALIAS, $returnmenu = null) { 4570 return role_fix_names(get_all_roles($context), $context, $rolenamedisplay, $returnmenu); 4571 } 4572 4573 /** 4574 * Prepare list of roles for display, apply aliases and localise default role names. 4575 * 4576 * @param array $roleoptions array roleid => roleobject (with optional coursealias), strings are accepted for backwards compatibility only 4577 * @param context $context the context, null means system context 4578 * @param int $rolenamedisplay 4579 * @param bool $returnmenu null means keep the same format as $roleoptions, true means id=>localname, false means id=>rolerecord 4580 * @return array Array of context-specific role names, or role objects with a ->localname field added. 4581 */ 4582 function role_fix_names($roleoptions, context $context = null, $rolenamedisplay = ROLENAME_ALIAS, $returnmenu = null) { 4583 global $DB; 4584 4585 if (empty($roleoptions)) { 4586 return array(); 4587 } 4588 4589 if (!$context or !$coursecontext = $context->get_course_context(false)) { 4590 $coursecontext = null; 4591 } 4592 4593 // We usually need all role columns... 4594 $first = reset($roleoptions); 4595 if ($returnmenu === null) { 4596 $returnmenu = !is_object($first); 4597 } 4598 4599 if (!is_object($first) or !property_exists($first, 'shortname')) { 4600 $allroles = get_all_roles($context); 4601 foreach ($roleoptions as $rid => $unused) { 4602 $roleoptions[$rid] = $allroles[$rid]; 4603 } 4604 } 4605 4606 // Inject coursealias if necessary. 4607 if ($coursecontext and ($rolenamedisplay == ROLENAME_ALIAS_RAW or $rolenamedisplay == ROLENAME_ALIAS or $rolenamedisplay == ROLENAME_BOTH)) { 4608 $first = reset($roleoptions); 4609 if (!property_exists($first, 'coursealias')) { 4610 $aliasnames = $DB->get_records('role_names', array('contextid'=>$coursecontext->id)); 4611 foreach ($aliasnames as $alias) { 4612 if (isset($roleoptions[$alias->roleid])) { 4613 $roleoptions[$alias->roleid]->coursealias = $alias->name; 4614 } 4615 } 4616 } 4617 } 4618 4619 // Add localname property. 4620 foreach ($roleoptions as $rid => $role) { 4621 $roleoptions[$rid]->localname = role_get_name($role, $coursecontext, $rolenamedisplay); 4622 } 4623 4624 if (!$returnmenu) { 4625 return $roleoptions; 4626 } 4627 4628 $menu = array(); 4629 foreach ($roleoptions as $rid => $role) { 4630 $menu[$rid] = $role->localname; 4631 } 4632 4633 return $menu; 4634 } 4635 4636 /** 4637 * Aids in detecting if a new line is required when reading a new capability 4638 * 4639 * This function helps admin/roles/manage.php etc to detect if a new line should be printed 4640 * when we read in a new capability. 4641 * Most of the time, if the 2 components are different we should print a new line, (e.g. course system->rss client) 4642 * but when we are in grade, all reports/import/export capabilities should be together 4643 * 4644 * @param string $cap component string a 4645 * @param string $comp component string b 4646 * @param int $contextlevel 4647 * @return bool whether 2 component are in different "sections" 4648 */ 4649 function component_level_changed($cap, $comp, $contextlevel) { 4650 4651 if (strstr($cap->component, '/') && strstr($comp, '/')) { 4652 $compsa = explode('/', $cap->component); 4653 $compsb = explode('/', $comp); 4654 4655 // list of system reports 4656 if (($compsa[0] == 'report') && ($compsb[0] == 'report')) { 4657 return false; 4658 } 4659 4660 // we are in gradebook, still 4661 if (($compsa[0] == 'gradeexport' || $compsa[0] == 'gradeimport' || $compsa[0] == 'gradereport') && 4662 ($compsb[0] == 'gradeexport' || $compsb[0] == 'gradeimport' || $compsb[0] == 'gradereport')) { 4663 return false; 4664 } 4665 4666 if (($compsa[0] == 'coursereport') && ($compsb[0] == 'coursereport')) { 4667 return false; 4668 } 4669 } 4670 4671 return ($cap->component != $comp || $cap->contextlevel != $contextlevel); 4672 } 4673 4674 /** 4675 * Fix the roles.sortorder field in the database, so it contains sequential integers, 4676 * and return an array of roleids in order. 4677 * 4678 * @param array $allroles array of roles, as returned by get_all_roles(); 4679 * @return array $role->sortorder =-> $role->id with the keys in ascending order. 4680 */ 4681 function fix_role_sortorder($allroles) { 4682 global $DB; 4683 4684 $rolesort = array(); 4685 $i = 0; 4686 foreach ($allroles as $role) { 4687 $rolesort[$i] = $role->id; 4688 if ($role->sortorder != $i) { 4689 $r = new stdClass(); 4690 $r->id = $role->id; 4691 $r->sortorder = $i; 4692 $DB->update_record('role', $r); 4693 $allroles[$role->id]->sortorder = $i; 4694 } 4695 $i++; 4696 } 4697 return $rolesort; 4698 } 4699 4700 /** 4701 * Switch the sort order of two roles (used in admin/roles/manage.php). 4702 * 4703 * @param stdClass $first The first role. Actually, only ->sortorder is used. 4704 * @param stdClass $second The second role. Actually, only ->sortorder is used. 4705 * @return boolean success or failure 4706 */ 4707 function switch_roles($first, $second) { 4708 global $DB; 4709 $temp = $DB->get_field('role', 'MAX(sortorder) + 1', array()); 4710 $result = $DB->set_field('role', 'sortorder', $temp, array('sortorder' => $first->sortorder)); 4711 $result = $result && $DB->set_field('role', 'sortorder', $first->sortorder, array('sortorder' => $second->sortorder)); 4712 $result = $result && $DB->set_field('role', 'sortorder', $second->sortorder, array('sortorder' => $temp)); 4713 return $result; 4714 } 4715 4716 /** 4717 * Duplicates all the base definitions of a role 4718 * 4719 * @param stdClass $sourcerole role to copy from 4720 * @param int $targetrole id of role to copy to 4721 */ 4722 function role_cap_duplicate($sourcerole, $targetrole) { 4723 global $DB; 4724 4725 $systemcontext = context_system::instance(); 4726 $caps = $DB->get_records_sql("SELECT * 4727 FROM {role_capabilities} 4728 WHERE roleid = ? AND contextid = ?", 4729 array($sourcerole->id, $systemcontext->id)); 4730 // adding capabilities 4731 foreach ($caps as $cap) { 4732 unset($cap->id); 4733 $cap->roleid = $targetrole; 4734 $DB->insert_record('role_capabilities', $cap); 4735 } 4736 } 4737 4738 /** 4739 * Returns two lists, this can be used to find out if user has capability. 4740 * Having any needed role and no forbidden role in this context means 4741 * user has this capability in this context. 4742 * Use get_role_names_with_cap_in_context() if you need role names to display in the UI 4743 * 4744 * @param stdClass $context 4745 * @param string $capability 4746 * @return array($neededroles, $forbiddenroles) 4747 */ 4748 function get_roles_with_cap_in_context($context, $capability) { 4749 global $DB; 4750 4751 $ctxids = trim($context->path, '/'); // kill leading slash 4752 $ctxids = str_replace('/', ',', $ctxids); 4753 4754 $sql = "SELECT rc.id, rc.roleid, rc.permission, ctx.depth 4755 FROM {role_capabilities} rc 4756 JOIN {context} ctx ON ctx.id = rc.contextid 4757 WHERE rc.capability = :cap AND ctx.id IN ($ctxids) 4758 ORDER BY rc.roleid ASC, ctx.depth DESC"; 4759 $params = array('cap'=>$capability); 4760 4761 if (!$capdefs = $DB->get_records_sql($sql, $params)) { 4762 // no cap definitions --> no capability 4763 return array(array(), array()); 4764 } 4765 4766 $forbidden = array(); 4767 $needed = array(); 4768 foreach($capdefs as $def) { 4769 if (isset($forbidden[$def->roleid])) { 4770 continue; 4771 } 4772 if ($def->permission == CAP_PROHIBIT) { 4773 $forbidden[$def->roleid] = $def->roleid; 4774 unset($needed[$def->roleid]); 4775 continue; 4776 } 4777 if (!isset($needed[$def->roleid])) { 4778 if ($def->permission == CAP_ALLOW) { 4779 $needed[$def->roleid] = true; 4780 } else if ($def->permission == CAP_PREVENT) { 4781 $needed[$def->roleid] = false; 4782 } 4783 } 4784 } 4785 unset($capdefs); 4786 4787 // remove all those roles not allowing 4788 foreach($needed as $key=>$value) { 4789 if (!$value) { 4790 unset($needed[$key]); 4791 } else { 4792 $needed[$key] = $key; 4793 } 4794 } 4795 4796 return array($needed, $forbidden); 4797 } 4798 4799 /** 4800 * Returns an array of role IDs that have ALL of the the supplied capabilities 4801 * Uses get_roles_with_cap_in_context(). Returns $allowed minus $forbidden 4802 * 4803 * @param stdClass $context 4804 * @param array $capabilities An array of capabilities 4805 * @return array of roles with all of the required capabilities 4806 */ 4807 function get_roles_with_caps_in_context($context, $capabilities) { 4808 $neededarr = array(); 4809 $forbiddenarr = array(); 4810 foreach($capabilities as $caprequired) { 4811 list($neededarr[], $forbiddenarr[]) = get_roles_with_cap_in_context($context, $caprequired); 4812 } 4813 4814 $rolesthatcanrate = array(); 4815 if (!empty($neededarr)) { 4816 foreach ($neededarr as $needed) { 4817 if (empty($rolesthatcanrate)) { 4818 $rolesthatcanrate = $needed; 4819 } else { 4820 //only want roles that have all caps 4821 $rolesthatcanrate = array_intersect_key($rolesthatcanrate,$needed); 4822 } 4823 } 4824 } 4825 if (!empty($forbiddenarr) && !empty($rolesthatcanrate)) { 4826 foreach ($forbiddenarr as $forbidden) { 4827 //remove any roles that are forbidden any of the caps 4828 $rolesthatcanrate = array_diff($rolesthatcanrate, $forbidden); 4829 } 4830 } 4831 return $rolesthatcanrate; 4832 } 4833 4834 /** 4835 * Returns an array of role names that have ALL of the the supplied capabilities 4836 * Uses get_roles_with_caps_in_context(). Returns $allowed minus $forbidden 4837 * 4838 * @param stdClass $context 4839 * @param array $capabilities An array of capabilities 4840 * @return array of roles with all of the required capabilities 4841 */ 4842 function get_role_names_with_caps_in_context($context, $capabilities) { 4843 global $DB; 4844 4845 $rolesthatcanrate = get_roles_with_caps_in_context($context, $capabilities); 4846 $allroles = $DB->get_records('role', null, 'sortorder DESC'); 4847 4848 $roles = array(); 4849 foreach ($rolesthatcanrate as $r) { 4850 $roles[$r] = $allroles[$r]; 4851 } 4852 4853 return role_fix_names($roles, $context, ROLENAME_ALIAS, true); 4854 } 4855 4856 /** 4857 * This function verifies the prohibit comes from this context 4858 * and there are no more prohibits in parent contexts. 4859 * 4860 * @param int $roleid 4861 * @param context $context 4862 * @param string $capability name 4863 * @return bool 4864 */ 4865 function prohibit_is_removable($roleid, context $context, $capability) { 4866 global $DB; 4867 4868 $ctxids = trim($context->path, '/'); // kill leading slash 4869 $ctxids = str_replace('/', ',', $ctxids); 4870 4871 $params = array('roleid'=>$roleid, 'cap'=>$capability, 'prohibit'=>CAP_PROHIBIT); 4872 4873 $sql = "SELECT ctx.id 4874 FROM {role_capabilities} rc 4875 JOIN {context} ctx ON ctx.id = rc.contextid 4876 WHERE rc.roleid = :roleid AND rc.permission = :prohibit AND rc.capability = :cap AND ctx.id IN ($ctxids) 4877 ORDER BY ctx.depth DESC"; 4878 4879 if (!$prohibits = $DB->get_records_sql($sql, $params)) { 4880 // no prohibits == nothing to remove 4881 return true; 4882 } 4883 4884 if (count($prohibits) > 1) { 4885 // more prohibits can not be removed 4886 return false; 4887 } 4888 4889 return !empty($prohibits[$context->id]); 4890 } 4891 4892 /** 4893 * More user friendly role permission changing, 4894 * it should produce as few overrides as possible. 4895 * 4896 * @param int $roleid 4897 * @param stdClass $context 4898 * @param string $capname capability name 4899 * @param int $permission 4900 * @return void 4901 */ 4902 function role_change_permission($roleid, $context, $capname, $permission) { 4903 global $DB; 4904 4905 if ($permission == CAP_INHERIT) { 4906 unassign_capability($capname, $roleid, $context->id); 4907 $context->mark_dirty(); 4908 return; 4909 } 4910 4911 $ctxids = trim($context->path, '/'); // kill leading slash 4912 $ctxids = str_replace('/', ',', $ctxids); 4913 4914 $params = array('roleid'=>$roleid, 'cap'=>$capname); 4915 4916 $sql = "SELECT ctx.id, rc.permission, ctx.depth 4917 FROM {role_capabilities} rc 4918 JOIN {context} ctx ON ctx.id = rc.contextid 4919 WHERE rc.roleid = :roleid AND rc.capability = :cap AND ctx.id IN ($ctxids) 4920 ORDER BY ctx.depth DESC"; 4921 4922 if ($existing = $DB->get_records_sql($sql, $params)) { 4923 foreach($existing as $e) { 4924 if ($e->permission == CAP_PROHIBIT) { 4925 // prohibit can not be overridden, no point in changing anything 4926 return; 4927 } 4928 } 4929 $lowest = array_shift($existing); 4930 if ($lowest->permission == $permission) { 4931 // permission already set in this context or parent - nothing to do 4932 return; 4933 } 4934 if ($existing) { 4935 $parent = array_shift($existing); 4936 if ($parent->permission == $permission) { 4937 // permission already set in parent context or parent - just unset in this context 4938 // we do this because we want as few overrides as possible for performance reasons 4939 unassign_capability($capname, $roleid, $context->id); 4940 $context->mark_dirty(); 4941 return; 4942 } 4943 } 4944 4945 } else { 4946 if ($permission == CAP_PREVENT) { 4947 // nothing means role does not have permission 4948 return; 4949 } 4950 } 4951 4952 // assign the needed capability 4953 assign_capability($capname, $permission, $roleid, $context->id, true); 4954 4955 // force cap reloading 4956 $context->mark_dirty(); 4957 } 4958 4959 4960 /** 4961 * Basic moodle context abstraction class. 4962 * 4963 * Google confirms that no other important framework is using "context" class, 4964 * we could use something else like mcontext or moodle_context, but we need to type 4965 * this very often which would be annoying and it would take too much space... 4966 * 4967 * This class is derived from stdClass for backwards compatibility with 4968 * odl $context record that was returned from DML $DB->get_record() 4969 * 4970 * @package core_access 4971 * @category access 4972 * @copyright Petr Skoda {@link http://skodak.org} 4973 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 4974 * @since Moodle 2.2 4975 * 4976 * @property-read int $id context id 4977 * @property-read int $contextlevel CONTEXT_SYSTEM, CONTEXT_COURSE, etc. 4978 * @property-read int $instanceid id of related instance in each context 4979 * @property-read string $path path to context, starts with system context 4980 * @property-read int $depth 4981 */ 4982 abstract class context extends stdClass implements IteratorAggregate { 4983 4984 /** 4985 * The context id 4986 * Can be accessed publicly through $context->id 4987 * @var int 4988 */ 4989 protected $_id; 4990 4991 /** 4992 * The context level 4993 * Can be accessed publicly through $context->contextlevel 4994 * @var int One of CONTEXT_* e.g. CONTEXT_COURSE, CONTEXT_MODULE 4995 */ 4996 protected $_contextlevel; 4997 4998 /** 4999 * Id of the item this context is related to e.g. COURSE_CONTEXT => course.id 5000 * Can be accessed publicly through $context->instanceid 5001 * @var int 5002 */ 5003 protected $_instanceid; 5004 5005 /** 5006 * The path to the context always starting from the system context 5007 * Can be accessed publicly through $context->path 5008 * @var string 5009 */ 5010 protected $_path; 5011 5012 /** 5013 * The depth of the context in relation to parent contexts 5014 * Can be accessed publicly through $context->depth 5015 * @var int 5016 */ 5017 protected $_depth; 5018 5019 /** 5020 * @var array Context caching info 5021 */ 5022 private static $cache_contextsbyid = array(); 5023 5024 /** 5025 * @var array Context caching info 5026 */ 5027 private static $cache_contexts = array(); 5028 5029 /** 5030 * Context count 5031 * Why do we do count contexts? Because count($array) is horribly slow for large arrays 5032 * @var int 5033 */ 5034 protected static $cache_count = 0; 5035 5036 /** 5037 * @var array Context caching info 5038 */ 5039 protected static $cache_preloaded = array(); 5040 5041 /** 5042 * @var context_system The system context once initialised 5043 */ 5044 protected static $systemcontext = null; 5045 5046 /** 5047 * Resets the cache to remove all data. 5048 * @static 5049 */ 5050 protected static function reset_caches() { 5051 self::$cache_contextsbyid = array(); 5052 self::$cache_contexts = array(); 5053 self::$cache_count = 0; 5054 self::$cache_preloaded = array(); 5055 5056 self::$systemcontext = null; 5057 } 5058 5059 /** 5060 * Adds a context to the cache. If the cache is full, discards a batch of 5061 * older entries. 5062 * 5063 * @static 5064 * @param context $context New context to add 5065 * @return void 5066 */ 5067 protected static function cache_add(context $context) { 5068 if (isset(self::$cache_contextsbyid[$context->id])) { 5069 // already cached, no need to do anything - this is relatively cheap, we do all this because count() is slow 5070 return; 5071 } 5072 5073 if (self::$cache_count >= CONTEXT_CACHE_MAX_SIZE) { 5074 $i = 0; 5075 foreach(self::$cache_contextsbyid as $ctx) { 5076 $i++; 5077 if ($i <= 100) { 5078 // we want to keep the first contexts to be loaded on this page, hopefully they will be needed again later 5079 continue; 5080 } 5081 if ($i > (CONTEXT_CACHE_MAX_SIZE / 3)) { 5082 // we remove oldest third of the contexts to make room for more contexts 5083 break; 5084 } 5085 unset(self::$cache_contextsbyid[$ctx->id]); 5086 unset(self::$cache_contexts[$ctx->contextlevel][$ctx->instanceid]); 5087 self::$cache_count--; 5088 } 5089 } 5090 5091 self::$cache_contexts[$context->contextlevel][$context->instanceid] = $context; 5092 self::$cache_contextsbyid[$context->id] = $context; 5093 self::$cache_count++; 5094 } 5095 5096 /** 5097 * Removes a context from the cache. 5098 * 5099 * @static 5100 * @param context $context Context object to remove 5101 * @return void 5102 */ 5103 protected static function cache_remove(context $context) { 5104 if (!isset(self::$cache_contextsbyid[$context->id])) { 5105 // not cached, no need to do anything - this is relatively cheap, we do all this because count() is slow 5106 return; 5107 } 5108 unset(self::$cache_contexts[$context->contextlevel][$context->instanceid]); 5109 unset(self::$cache_contextsbyid[$context->id]); 5110 5111 self::$cache_count--; 5112 5113 if (self::$cache_count < 0) { 5114 self::$cache_count = 0; 5115 } 5116 } 5117 5118 /** 5119 * Gets a context from the cache. 5120 * 5121 * @static 5122 * @param int $contextlevel Context level 5123 * @param int $instance Instance ID 5124 * @return context|bool Context or false if not in cache 5125 */ 5126 protected static function cache_get($contextlevel, $instance) { 5127 if (isset(self::$cache_contexts[$contextlevel][$instance])) { 5128 return self::$cache_contexts[$contextlevel][$instance]; 5129 } 5130 return false; 5131 } 5132 5133 /** 5134 * Gets a context from the cache based on its id. 5135 * 5136 * @static 5137 * @param int $id Context ID 5138 * @return context|bool Context or false if not in cache 5139 */ 5140 protected static function cache_get_by_id($id) { 5141 if (isset(self::$cache_contextsbyid[$id])) { 5142 return self::$cache_contextsbyid[$id]; 5143 } 5144 return false; 5145 } 5146 5147 /** 5148 * Preloads context information from db record and strips the cached info. 5149 * 5150 * @static 5151 * @param stdClass $rec 5152 * @return void (modifies $rec) 5153 */ 5154 protected static function preload_from_record(stdClass $rec) { 5155 if (empty($rec->ctxid) or empty($rec->ctxlevel) or !isset($rec->ctxinstance) or empty($rec->ctxpath) or empty($rec->ctxdepth)) { 5156 // $rec does not have enough data, passed here repeatedly or context does not exist yet 5157 return; 5158 } 5159 5160 // note: in PHP5 the objects are passed by reference, no need to return $rec 5161 $record = new stdClass(); 5162 $record->id = $rec->ctxid; unset($rec->ctxid); 5163 $record->contextlevel = $rec->ctxlevel; unset($rec->ctxlevel); 5164 $record->instanceid = $rec->ctxinstance; unset($rec->ctxinstance); 5165 $record->path = $rec->ctxpath; unset($rec->ctxpath); 5166 $record->depth = $rec->ctxdepth; unset($rec->ctxdepth); 5167 5168 return context::create_instance_from_record($record); 5169 } 5170 5171 5172 // ====== magic methods ======= 5173 5174 /** 5175 * Magic setter method, we do not want anybody to modify properties from the outside 5176 * @param string $name 5177 * @param mixed $value 5178 */ 5179 public function __set($name, $value) { 5180 debugging('Can not change context instance properties!'); 5181 } 5182 5183 /** 5184 * Magic method getter, redirects to read only values. 5185 * @param string $name 5186 * @return mixed 5187 */ 5188 public function __get($name) { 5189 switch ($name) { 5190 case 'id': return $this->_id; 5191 case 'contextlevel': return $this->_contextlevel; 5192 case 'instanceid': return $this->_instanceid; 5193 case 'path': return $this->_path; 5194 case 'depth': return $this->_depth; 5195 5196 default: 5197 debugging('Invalid context property accessed! '.$name); 5198 return null; 5199 } 5200 } 5201 5202 /** 5203 * Full support for isset on our magic read only properties. 5204 * @param string $name 5205 * @return bool 5206 */ 5207 public function __isset($name) { 5208 switch ($name) { 5209 case 'id': return isset($this->_id); 5210 case 'contextlevel': return isset($this->_contextlevel); 5211 case 'instanceid': return isset($this->_instanceid); 5212 case 'path': return isset($this->_path); 5213 case 'depth': return isset($this->_depth); 5214 5215 default: return false; 5216 } 5217 5218 } 5219 5220 /** 5221 * ALl properties are read only, sorry. 5222 * @param string $name 5223 */ 5224 public function __unset($name) { 5225 debugging('Can not unset context instance properties!'); 5226 } 5227 5228 // ====== implementing method from interface IteratorAggregate ====== 5229 5230 /** 5231 * Create an iterator because magic vars can't be seen by 'foreach'. 5232 * 5233 * Now we can convert context object to array using convert_to_array(), 5234 * and feed it properly to json_encode(). 5235 */ 5236 public function getIterator() { 5237 $ret = array( 5238 'id' => $this->id, 5239 'contextlevel' => $this->contextlevel, 5240 'instanceid' => $this->instanceid, 5241 'path' => $this->path, 5242 'depth' => $this->depth 5243 ); 5244 return new ArrayIterator($ret); 5245 } 5246 5247 // ====== general context methods ====== 5248 5249 /** 5250 * Constructor is protected so that devs are forced to 5251 * use context_xxx::instance() or context::instance_by_id(). 5252 * 5253 * @param stdClass $record 5254 */ 5255 protected function __construct(stdClass $record) { 5256 $this->_id = (int)$record->id; 5257 $this->_contextlevel = (int)$record->contextlevel; 5258 $this->_instanceid = $record->instanceid; 5259 $this->_path = $record->path; 5260 $this->_depth = $record->depth; 5261 } 5262 5263 /** 5264 * This function is also used to work around 'protected' keyword problems in context_helper. 5265 * @static 5266 * @param stdClass $record 5267 * @return context instance 5268 */ 5269 protected static function create_instance_from_record(stdClass $record) { 5270 $classname = context_helper::get_class_for_level($record->contextlevel); 5271 5272 if ($context = context::cache_get_by_id($record->id)) { 5273 return $context; 5274 } 5275 5276 $context = new $classname($record); 5277 context::cache_add($context); 5278 5279 return $context; 5280 } 5281 5282 /** 5283 * Copy prepared new contexts from temp table to context table, 5284 * we do this in db specific way for perf reasons only. 5285 * @static 5286 */ 5287 protected static function merge_context_temp_table() { 5288 global $DB; 5289 5290 /* MDL-11347: 5291 * - mysql does not allow to use FROM in UPDATE statements 5292 * - using two tables after UPDATE works in mysql, but might give unexpected 5293 * results in pg 8 (depends on configuration) 5294 * - using table alias in UPDATE does not work in pg < 8.2 5295 * 5296 * Different code for each database - mostly for performance reasons 5297 */ 5298 5299 $dbfamily = $DB->get_dbfamily(); 5300 if ($dbfamily == 'mysql') { 5301 $updatesql = "UPDATE {context} ct, {context_temp} temp 5302 SET ct.path = temp.path, 5303 ct.depth = temp.depth 5304 WHERE ct.id = temp.id"; 5305 } else if ($dbfamily == 'oracle') { 5306 $updatesql = "UPDATE {context} ct 5307 SET (ct.path, ct.depth) = 5308 (SELECT temp.path, temp.depth 5309 FROM {context_temp} temp 5310 WHERE temp.id=ct.id) 5311 WHERE EXISTS (SELECT 'x' 5312 FROM {context_temp} temp 5313 WHERE temp.id = ct.id)"; 5314 } else if ($dbfamily == 'postgres' or $dbfamily == 'mssql') { 5315 $updatesql = "UPDATE {context} 5316 SET path = temp.path, 5317 depth = temp.depth 5318 FROM {context_temp} temp 5319 WHERE temp.id={context}.id"; 5320 } else { 5321 // sqlite and others 5322 $updatesql = "UPDATE {context} 5323 SET path = (SELECT path FROM {context_temp} WHERE id = {context}.id), 5324 depth = (SELECT depth FROM {context_temp} WHERE id = {context}.id) 5325 WHERE id IN (SELECT id FROM {context_temp})"; 5326 } 5327 5328 $DB->execute($updatesql); 5329 } 5330 5331 /** 5332 * Get a context instance as an object, from a given context id. 5333 * 5334 * @static 5335 * @param int $id context id 5336 * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found; 5337 * MUST_EXIST means throw exception if no record found 5338 * @return context|bool the context object or false if not found 5339 */ 5340 public static function instance_by_id($id, $strictness = MUST_EXIST) { 5341 global $DB; 5342 5343 if (get_called_class() !== 'context' and get_called_class() !== 'context_helper') { 5344 // some devs might confuse context->id and instanceid, better prevent these mistakes completely 5345 throw new coding_exception('use only context::instance_by_id() for real context levels use ::instance() methods'); 5346 } 5347 5348 if ($id == SYSCONTEXTID) { 5349 return context_system::instance(0, $strictness); 5350 } 5351 5352 if (is_array($id) or is_object($id) or empty($id)) { 5353 throw new coding_exception('Invalid context id specified context::instance_by_id()'); 5354 } 5355 5356 if ($context = context::cache_get_by_id($id)) { 5357 return $context; 5358 } 5359 5360 if ($record = $DB->get_record('context', array('id'=>$id), '*', $strictness)) { 5361 return context::create_instance_from_record($record); 5362 } 5363 5364 return false; 5365 } 5366 5367 /** 5368 * Update context info after moving context in the tree structure. 5369 * 5370 * @param context $newparent 5371 * @return void 5372 */ 5373 public function update_moved(context $newparent) { 5374 global $DB; 5375 5376 $frompath = $this->_path; 5377 $newpath = $newparent->path . '/' . $this->_id; 5378 5379 $trans = $DB->start_delegated_transaction(); 5380 5381 $this->mark_dirty(); 5382 5383 $setdepth = ''; 5384 if (($newparent->depth +1) != $this->_depth) { 5385 $diff = $newparent->depth - $this->_depth + 1; 5386 $setdepth = ", depth = depth + $diff"; 5387 } 5388 $sql = "UPDATE {context} 5389 SET path = ? 5390 $setdepth 5391 WHERE id = ?"; 5392 $params = array($newpath, $this->_id); 5393 $DB->execute($sql, $params); 5394 5395 $this->_path = $newpath; 5396 $this->_depth = $newparent->depth + 1; 5397 5398 $sql = "UPDATE {context} 5399 SET path = ".$DB->sql_concat("?", $DB->sql_substr("path", strlen($frompath)+1))." 5400 $setdepth 5401 WHERE path LIKE ?"; 5402 $params = array($newpath, "{$frompath}/%"); 5403 $DB->execute($sql, $params); 5404 5405 $this->mark_dirty(); 5406 5407 context::reset_caches(); 5408 5409 $trans->allow_commit(); 5410 } 5411 5412 /** 5413 * Remove all context path info and optionally rebuild it. 5414 * 5415 * @param bool $rebuild 5416 * @return void 5417 */ 5418 public function reset_paths($rebuild = true) { 5419 global $DB; 5420 5421 if ($this->_path) { 5422 $this->mark_dirty(); 5423 } 5424 $DB->set_field_select('context', 'depth', 0, "path LIKE '%/$this->_id/%'"); 5425 $DB->set_field_select('context', 'path', NULL, "path LIKE '%/$this->_id/%'"); 5426 if ($this->_contextlevel != CONTEXT_SYSTEM) { 5427 $DB->set_field('context', 'depth', 0, array('id'=>$this->_id)); 5428 $DB->set_field('context', 'path', NULL, array('id'=>$this->_id)); 5429 $this->_depth = 0; 5430 $this->_path = null; 5431 } 5432 5433 if ($rebuild) { 5434 context_helper::build_all_paths(false); 5435 } 5436 5437 context::reset_caches(); 5438 } 5439 5440 /** 5441 * Delete all data linked to content, do not delete the context record itself 5442 */ 5443 public function delete_content() { 5444 global $CFG, $DB; 5445 5446 blocks_delete_all_for_context($this->_id); 5447 filter_delete_all_for_context($this->_id); 5448 5449 require_once($CFG->dirroot . '/comment/lib.php'); 5450 comment::delete_comments(array('contextid'=>$this->_id)); 5451 5452 require_once($CFG->dirroot.'/rating/lib.php'); 5453 $delopt = new stdclass(); 5454 $delopt->contextid = $this->_id; 5455 $rm = new rating_manager(); 5456 $rm->delete_ratings($delopt); 5457 5458 // delete all files attached to this context 5459 $fs = get_file_storage(); 5460 $fs->delete_area_files($this->_id); 5461 5462 // Delete all repository instances attached to this context. 5463 require_once($CFG->dirroot . '/repository/lib.php'); 5464 repository::delete_all_for_context($this->_id); 5465 5466 // delete all advanced grading data attached to this context 5467 require_once($CFG->dirroot.'/grade/grading/lib.php'); 5468 grading_manager::delete_all_for_context($this->_id); 5469 5470 // now delete stuff from role related tables, role_unassign_all 5471 // and unenrol should be called earlier to do proper cleanup 5472 $DB->delete_records('role_assignments', array('contextid'=>$this->_id)); 5473 $DB->delete_records('role_capabilities', array('contextid'=>$this->_id)); 5474 $DB->delete_records('role_names', array('contextid'=>$this->_id)); 5475 } 5476 5477 /** 5478 * Delete the context content and the context record itself 5479 */ 5480 public function delete() { 5481 global $DB; 5482 5483 if ($this->_contextlevel <= CONTEXT_SYSTEM) { 5484 throw new coding_exception('Cannot delete system context'); 5485 } 5486 5487 // double check the context still exists 5488 if (!$DB->record_exists('context', array('id'=>$this->_id))) { 5489 context::cache_remove($this); 5490 return; 5491 } 5492 5493 $this->delete_content(); 5494 $DB->delete_records('context', array('id'=>$this->_id)); 5495 // purge static context cache if entry present 5496 context::cache_remove($this); 5497 5498 // do not mark dirty contexts if parents unknown 5499 if (!is_null($this->_path) and $this->_depth > 0) { 5500 $this->mark_dirty(); 5501 } 5502 } 5503 5504 // ====== context level related methods ====== 5505 5506 /** 5507 * Utility method for context creation 5508 * 5509 * @static 5510 * @param int $contextlevel 5511 * @param int $instanceid 5512 * @param string $parentpath 5513 * @return stdClass context record 5514 */ 5515 protected static function insert_context_record($contextlevel, $instanceid, $parentpath) { 5516 global $DB; 5517 5518 $record = new stdClass(); 5519 $record->contextlevel = $contextlevel; 5520 $record->instanceid = $instanceid; 5521 $record->depth = 0; 5522 $record->path = null; //not known before insert 5523 5524 $record->id = $DB->insert_record('context', $record); 5525 5526 // now add path if known - it can be added later 5527 if (!is_null($parentpath)) { 5528 $record->path = $parentpath.'/'.$record->id; 5529 $record->depth = substr_count($record->path, '/'); 5530 $DB->update_record('context', $record); 5531 } 5532 5533 return $record; 5534 } 5535 5536 /** 5537 * Returns human readable context identifier. 5538 * 5539 * @param boolean $withprefix whether to prefix the name of the context with the 5540 * type of context, e.g. User, Course, Forum, etc. 5541 * @param boolean $short whether to use the short name of the thing. Only applies 5542 * to course contexts 5543 * @return string the human readable context name. 5544 */ 5545 public function get_context_name($withprefix = true, $short = false) { 5546 // must be implemented in all context levels 5547 throw new coding_exception('can not get name of abstract context'); 5548 } 5549 5550 /** 5551 * Returns the most relevant URL for this context. 5552 * 5553 * @return moodle_url 5554 */ 5555 public abstract function get_url(); 5556 5557 /** 5558 * Returns array of relevant context capability records. 5559 * 5560 * @return array 5561 */ 5562 public abstract function get_capabilities(); 5563 5564 /** 5565 * Recursive function which, given a context, find all its children context ids. 5566 * 5567 * For course category contexts it will return immediate children and all subcategory contexts. 5568 * It will NOT recurse into courses or subcategories categories. 5569 * If you want to do that, call it on the returned courses/categories. 5570 * 5571 * When called for a course context, it will return the modules and blocks 5572 * displayed in the course page and blocks displayed on the module pages. 5573 * 5574 * If called on a user/course/module context it _will_ populate the cache with the appropriate 5575 * contexts ;-) 5576 * 5577 * @return array Array of child records 5578 */ 5579 public function get_child_contexts() { 5580 global $DB; 5581 5582 if (empty($this->_path) or empty($this->_depth)) { 5583 debugging('Can not find child contexts of context '.$this->_id.' try rebuilding of context paths'); 5584 return array(); 5585 } 5586 5587 $sql = "SELECT ctx.* 5588 FROM {context} ctx 5589 WHERE ctx.path LIKE ?"; 5590 $params = array($this->_path.'/%'); 5591 $records = $DB->get_records_sql($sql, $params); 5592 5593 $result = array(); 5594 foreach ($records as $record) { 5595 $result[$record->id] = context::create_instance_from_record($record); 5596 } 5597 5598 return $result; 5599 } 5600 5601 /** 5602 * Returns parent contexts of this context in reversed order, i.e. parent first, 5603 * then grand parent, etc. 5604 * 5605 * @param bool $includeself tre means include self too 5606 * @return array of context instances 5607 */ 5608 public function get_parent_contexts($includeself = false) { 5609 if (!$contextids = $this->get_parent_context_ids($includeself)) { 5610 return array(); 5611 } 5612 5613 $result = array(); 5614 foreach ($contextids as $contextid) { 5615 $parent = context::instance_by_id($contextid, MUST_EXIST); 5616 $result[$parent->id] = $parent; 5617 } 5618 5619 return $result; 5620 } 5621 5622 /** 5623 * Returns parent contexts of this context in reversed order, i.e. parent first, 5624 * then grand parent, etc. 5625 * 5626 * @param bool $includeself tre means include self too 5627 * @return array of context ids 5628 */ 5629 public function get_parent_context_ids($includeself = false) { 5630 if (empty($this->_path)) { 5631 return array(); 5632 } 5633 5634 $parentcontexts = trim($this->_path, '/'); // kill leading slash 5635 $parentcontexts = explode('/', $parentcontexts); 5636 if (!$includeself) { 5637 array_pop($parentcontexts); // and remove its own id 5638 } 5639 5640 return array_reverse($parentcontexts); 5641 } 5642 5643 /** 5644 * Returns parent context 5645 * 5646 * @return context 5647 */ 5648 public function get_parent_context() { 5649 if (empty($this->_path) or $this->_id == SYSCONTEXTID) { 5650 return false; 5651 } 5652 5653 $parentcontexts = trim($this->_path, '/'); // kill leading slash 5654 $parentcontexts = explode('/', $parentcontexts); 5655 array_pop($parentcontexts); // self 5656 $contextid = array_pop($parentcontexts); // immediate parent 5657 5658 return context::instance_by_id($contextid, MUST_EXIST); 5659 } 5660 5661 /** 5662 * Is this context part of any course? If yes return course context. 5663 * 5664 * @param bool $strict true means throw exception if not found, false means return false if not found 5665 * @return context_course context of the enclosing course, null if not found or exception 5666 */ 5667 public function get_course_context($strict = true) { 5668 if ($strict) { 5669 throw new coding_exception('Context does not belong to any course.'); 5670 } else { 5671 return false; 5672 } 5673 } 5674 5675 /** 5676 * Returns sql necessary for purging of stale context instances. 5677 * 5678 * @static 5679 * @return string cleanup SQL 5680 */ 5681 protected static function get_cleanup_sql() { 5682 throw new coding_exception('get_cleanup_sql() method must be implemented in all context levels'); 5683 } 5684 5685 /** 5686 * Rebuild context paths and depths at context level. 5687 * 5688 * @static 5689 * @param bool $force 5690 * @return void 5691 */ 5692 protected static function build_paths($force) { 5693 throw new coding_exception('build_paths() method must be implemented in all context levels'); 5694 } 5695 5696 /** 5697 * Create missing context instances at given level 5698 * 5699 * @static 5700 * @return void 5701 */ 5702 protected static function create_level_instances() { 5703 throw new coding_exception('create_level_instances() method must be implemented in all context levels'); 5704 } 5705 5706 /** 5707 * Reset all cached permissions and definitions if the necessary. 5708 * @return void 5709 */ 5710 public function reload_if_dirty() { 5711 global $ACCESSLIB_PRIVATE, $USER; 5712 5713 // Load dirty contexts list if needed 5714 if (CLI_SCRIPT) { 5715 if (!isset($ACCESSLIB_PRIVATE->dirtycontexts)) { 5716 // we do not load dirty flags in CLI and cron 5717 $ACCESSLIB_PRIVATE->dirtycontexts = array(); 5718 } 5719 } else { 5720 if (!isset($ACCESSLIB_PRIVATE->dirtycontexts)) { 5721 if (!isset($USER->access['time'])) { 5722 // nothing was loaded yet, we do not need to check dirty contexts now 5723 return; 5724 } 5725 // no idea why -2 is there, server cluster time difference maybe... (skodak) 5726 $ACCESSLIB_PRIVATE->dirtycontexts = get_cache_flags('accesslib/dirtycontexts', $USER->access['time']-2); 5727 } 5728 } 5729 5730 foreach ($ACCESSLIB_PRIVATE->dirtycontexts as $path=>$unused) { 5731 if ($path === $this->_path or strpos($this->_path, $path.'/') === 0) { 5732 // reload all capabilities of USER and others - preserving loginas, roleswitches, etc 5733 // and then cleanup any marks of dirtyness... at least from our short term memory! :-) 5734 reload_all_capabilities(); 5735 break; 5736 } 5737 } 5738 } 5739 5740 /** 5741 * Mark a context as dirty (with timestamp) so as to force reloading of the context. 5742 */ 5743 public function mark_dirty() { 5744 global $CFG, $USER, $ACCESSLIB_PRIVATE; 5745 5746 if (during_initial_install()) { 5747 return; 5748 } 5749 5750 // only if it is a non-empty string 5751 if (is_string($this->_path) && $this->_path !== '') { 5752 set_cache_flag('accesslib/dirtycontexts', $this->_path, 1, time()+$CFG->sessiontimeout); 5753 if (isset($ACCESSLIB_PRIVATE->dirtycontexts)) { 5754 $ACCESSLIB_PRIVATE->dirtycontexts[$this->_path] = 1; 5755 } else { 5756 if (CLI_SCRIPT) { 5757 $ACCESSLIB_PRIVATE->dirtycontexts = array($this->_path => 1); 5758 } else { 5759 if (isset($USER->access['time'])) { 5760 $ACCESSLIB_PRIVATE->dirtycontexts = get_cache_flags('accesslib/dirtycontexts', $USER->access['time']-2); 5761 } else { 5762 $ACCESSLIB_PRIVATE->dirtycontexts = array($this->_path => 1); 5763 } 5764 // flags not loaded yet, it will be done later in $context->reload_if_dirty() 5765 } 5766 } 5767 } 5768 } 5769 } 5770 5771 5772 /** 5773 * Context maintenance and helper methods. 5774 * 5775 * This is "extends context" is a bloody hack that tires to work around the deficiencies 5776 * in the "protected" keyword in PHP, this helps us to hide all the internals of context 5777 * level implementation from the rest of code, the code completion returns what developers need. 5778 * 5779 * Thank you Tim Hunt for helping me with this nasty trick. 5780 * 5781 * @package core_access 5782 * @category access 5783 * @copyright Petr Skoda {@link http://skodak.org} 5784 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 5785 * @since Moodle 2.2 5786 */ 5787 class context_helper extends context { 5788 5789 /** 5790 * @var array An array mapping context levels to classes 5791 */ 5792 private static $alllevels; 5793 5794 /** 5795 * Instance does not make sense here, only static use 5796 */ 5797 protected function __construct() { 5798 } 5799 5800 /** 5801 * Reset internal context levels array. 5802 */ 5803 public static function reset_levels() { 5804 self::$alllevels = null; 5805 } 5806 5807 /** 5808 * Initialise context levels, call before using self::$alllevels. 5809 */ 5810 private static function init_levels() { 5811 global $CFG; 5812 5813 if (isset(self::$alllevels)) { 5814 return; 5815 } 5816 self::$alllevels = array( 5817 CONTEXT_SYSTEM => 'context_system', 5818 CONTEXT_USER => 'context_user', 5819 CONTEXT_COURSECAT => 'context_coursecat', 5820 CONTEXT_COURSE => 'context_course', 5821 CONTEXT_MODULE => 'context_module', 5822 CONTEXT_BLOCK => 'context_block', 5823 ); 5824 5825 if (empty($CFG->custom_context_classes)) { 5826 return; 5827 } 5828 5829 $levels = $CFG->custom_context_classes; 5830 if (!is_array($levels)) { 5831 $levels = @unserialize($levels); 5832 } 5833 if (!is_array($levels)) { 5834 debugging('Invalid $CFG->custom_context_classes detected, value ignored.', DEBUG_DEVELOPER); 5835 return; 5836 } 5837 5838 // Unsupported custom levels, use with care!!! 5839 foreach ($levels as $level => $classname) { 5840 self::$alllevels[$level] = $classname; 5841 } 5842 ksort(self::$alllevels); 5843 } 5844 5845 /** 5846 * Returns a class name of the context level class 5847 * 5848 * @static 5849 * @param int $contextlevel (CONTEXT_SYSTEM, etc.) 5850 * @return string class name of the context class 5851 */ 5852 public static function get_class_for_level($contextlevel) { 5853 self::init_levels(); 5854 if (isset(self::$alllevels[$contextlevel])) { 5855 return self::$alllevels[$contextlevel]; 5856 } else { 5857 throw new coding_exception('Invalid context level specified'); 5858 } 5859 } 5860 5861 /** 5862 * Returns a list of all context levels 5863 * 5864 * @static 5865 * @return array int=>string (level=>level class name) 5866 */ 5867 public static function get_all_levels() { 5868 self::init_levels(); 5869 return self::$alllevels; 5870 } 5871 5872 /** 5873 * Remove stale contexts that belonged to deleted instances. 5874 * Ideally all code should cleanup contexts properly, unfortunately accidents happen... 5875 * 5876 * @static 5877 * @return void 5878 */ 5879 public static function cleanup_instances() { 5880 global $DB; 5881 self::init_levels(); 5882 5883 $sqls = array(); 5884 foreach (self::$alllevels as $level=>$classname) { 5885 $sqls[] = $classname::get_cleanup_sql(); 5886 } 5887 5888 $sql = implode(" UNION ", $sqls); 5889 5890 // it is probably better to use transactions, it might be faster too 5891 $transaction = $DB->start_delegated_transaction(); 5892 5893 $rs = $DB->get_recordset_sql($sql); 5894 foreach ($rs as $record) { 5895 $context = context::create_instance_from_record($record); 5896 $context->delete(); 5897 } 5898 $rs->close(); 5899 5900 $transaction->allow_commit(); 5901 } 5902 5903 /** 5904 * Create all context instances at the given level and above. 5905 * 5906 * @static 5907 * @param int $contextlevel null means all levels 5908 * @param bool $buildpaths 5909 * @return void 5910 */ 5911 public static function create_instances($contextlevel = null, $buildpaths = true) { 5912 self::init_levels(); 5913 foreach (self::$alllevels as $level=>$classname) { 5914 if ($contextlevel and $level > $contextlevel) { 5915 // skip potential sub-contexts 5916 continue; 5917 } 5918 $classname::create_level_instances(); 5919 if ($buildpaths) { 5920 $classname::build_paths(false); 5921 } 5922 } 5923 } 5924 5925 /** 5926 * Rebuild paths and depths in all context levels. 5927 * 5928 * @static 5929 * @param bool $force false means add missing only 5930 * @return void 5931 */ 5932 public static function build_all_paths($force = false) { 5933 self::init_levels(); 5934 foreach (self::$alllevels as $classname) { 5935 $classname::build_paths($force); 5936 } 5937 5938 // reset static course cache - it might have incorrect cached data 5939 accesslib_clear_all_caches(true); 5940 } 5941 5942 /** 5943 * Resets the cache to remove all data. 5944 * @static 5945 */ 5946 public static function reset_caches() { 5947 context::reset_caches(); 5948 } 5949 5950 /** 5951 * Returns all fields necessary for context preloading from user $rec. 5952 * 5953 * This helps with performance when dealing with hundreds of contexts. 5954 * 5955 * @static 5956 * @param string $tablealias context table alias in the query 5957 * @return array (table.column=>alias, ...) 5958 */ 5959 public static function get_preload_record_columns($tablealias) { 5960 return array("$tablealias.id"=>"ctxid", "$tablealias.path"=>"ctxpath", "$tablealias.depth"=>"ctxdepth", "$tablealias.contextlevel"=>"ctxlevel", "$tablealias.instanceid"=>"ctxinstance"); 5961 } 5962 5963 /** 5964 * Returns all fields necessary for context preloading from user $rec. 5965 * 5966 * This helps with performance when dealing with hundreds of contexts. 5967 * 5968 * @static 5969 * @param string $tablealias context table alias in the query 5970 * @return string 5971 */ 5972 public static function get_preload_record_columns_sql($tablealias) { 5973 return "$tablealias.id AS ctxid, $tablealias.path AS ctxpath, $tablealias.depth AS ctxdepth, $tablealias.contextlevel AS ctxlevel, $tablealias.instanceid AS ctxinstance"; 5974 } 5975 5976 /** 5977 * Preloads context information from db record and strips the cached info. 5978 * 5979 * The db request has to contain all columns from context_helper::get_preload_record_columns(). 5980 * 5981 * @static 5982 * @param stdClass $rec 5983 * @return void (modifies $rec) 5984 */ 5985 public static function preload_from_record(stdClass $rec) { 5986 context::preload_from_record($rec); 5987 } 5988 5989 /** 5990 * Preload all contexts instances from course. 5991 * 5992 * To be used if you expect multiple queries for course activities... 5993 * 5994 * @static 5995 * @param int $courseid 5996 */ 5997 public static function preload_course($courseid) { 5998 // Users can call this multiple times without doing any harm 5999 if (isset(context::$cache_preloaded[$courseid])) { 6000 return; 6001 } 6002 $coursecontext = context_course::instance($courseid); 6003 $coursecontext->get_child_contexts(); 6004 6005 context::$cache_preloaded[$courseid] = true; 6006 } 6007 6008 /** 6009 * Delete context instance 6010 * 6011 * @static 6012 * @param int $contextlevel 6013 * @param int $instanceid 6014 * @return void 6015 */ 6016 public static function delete_instance($contextlevel, $instanceid) { 6017 global $DB; 6018 6019 // double check the context still exists 6020 if ($record = $DB->get_record('context', array('contextlevel'=>$contextlevel, 'instanceid'=>$instanceid))) { 6021 $context = context::create_instance_from_record($record); 6022 $context->delete(); 6023 } else { 6024 // we should try to purge the cache anyway 6025 } 6026 } 6027 6028 /** 6029 * Returns the name of specified context level 6030 * 6031 * @static 6032 * @param int $contextlevel 6033 * @return string name of the context level 6034 */ 6035 public static function get_level_name($contextlevel) { 6036 $classname = context_helper::get_class_for_level($contextlevel); 6037 return $classname::get_level_name(); 6038 } 6039 6040 /** 6041 * not used 6042 */ 6043 public function get_url() { 6044 } 6045 6046 /** 6047 * not used 6048 */ 6049 public function get_capabilities() { 6050 } 6051 } 6052 6053 6054 /** 6055 * System context class 6056 * 6057 * @package core_access 6058 * @category access 6059 * @copyright Petr Skoda {@link http://skodak.org} 6060 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 6061 * @since Moodle 2.2 6062 */ 6063 class context_system extends context { 6064 /** 6065 * Please use context_system::instance() if you need the instance of context. 6066 * 6067 * @param stdClass $record 6068 */ 6069 protected function __construct(stdClass $record) { 6070 parent::__construct($record); 6071 if ($record->contextlevel != CONTEXT_SYSTEM) { 6072 throw new coding_exception('Invalid $record->contextlevel in context_system constructor.'); 6073 } 6074 } 6075 6076 /** 6077 * Returns human readable context level name. 6078 * 6079 * @static 6080 * @return string the human readable context level name. 6081 */ 6082 public static function get_level_name() { 6083 return get_string('coresystem'); 6084 } 6085 6086 /** 6087 * Returns human readable context identifier. 6088 * 6089 * @param boolean $withprefix does not apply to system context 6090 * @param boolean $short does not apply to system context 6091 * @return string the human readable context name. 6092 */ 6093 public function get_context_name($withprefix = true, $short = false) { 6094 return self::get_level_name(); 6095 } 6096 6097 /** 6098 * Returns the most relevant URL for this context. 6099 * 6100 * @return moodle_url 6101 */ 6102 public function get_url() { 6103 return new moodle_url('/'); 6104 } 6105 6106 /** 6107 * Returns array of relevant context capability records. 6108 * 6109 * @return array 6110 */ 6111 public function get_capabilities() { 6112 global $DB; 6113 6114 $sort = 'ORDER BY contextlevel,component,name'; // To group them sensibly for display 6115 6116 $params = array(); 6117 $sql = "SELECT * 6118 FROM {capabilities}"; 6119 6120 return $DB->get_records_sql($sql.' '.$sort, $params); 6121 } 6122 6123 /** 6124 * Create missing context instances at system context 6125 * @static 6126 */ 6127 protected static function create_level_instances() { 6128 // nothing to do here, the system context is created automatically in installer 6129 self::instance(0); 6130 } 6131 6132 /** 6133 * Returns system context instance. 6134 * 6135 * @static 6136 * @param int $instanceid 6137 * @param int $strictness 6138 * @param bool $cache 6139 * @return context_system context instance 6140 */ 6141 public static function instance($instanceid = 0, $strictness = MUST_EXIST, $cache = true) { 6142 global $DB; 6143 6144 if ($instanceid != 0) { 6145 debugging('context_system::instance(): invalid $id parameter detected, should be 0'); 6146 } 6147 6148 if (defined('SYSCONTEXTID') and $cache) { // dangerous: define this in config.php to eliminate 1 query/page 6149 if (!isset(context::$systemcontext)) { 6150 $record = new stdClass(); 6151 $record->id = SYSCONTEXTID; 6152 $record->contextlevel = CONTEXT_SYSTEM; 6153 $record->instanceid = 0; 6154 $record->path = '/'.SYSCONTEXTID; 6155 $record->depth = 1; 6156 context::$systemcontext = new context_system($record); 6157 } 6158 return context::$systemcontext; 6159 } 6160 6161 6162 try { 6163 // We ignore the strictness completely because system context must exist except during install. 6164 $record = $DB->get_record('context', array('contextlevel'=>CONTEXT_SYSTEM), '*', MUST_EXIST); 6165 } catch (dml_exception $e) { 6166 //table or record does not exist 6167 if (!during_initial_install()) { 6168 // do not mess with system context after install, it simply must exist 6169 throw $e; 6170 } 6171 $record = null; 6172 } 6173 6174 if (!$record) { 6175 $record = new stdClass(); 6176 $record->contextlevel = CONTEXT_SYSTEM; 6177 $record->instanceid = 0; 6178 $record->depth = 1; 6179 $record->path = null; //not known before insert 6180 6181 try { 6182 if ($DB->count_records('context')) { 6183 // contexts already exist, this is very weird, system must be first!!! 6184 return null; 6185 } 6186 if (defined('SYSCONTEXTID')) { 6187 // this would happen only in unittest on sites that went through weird 1.7 upgrade 6188 $record->id = SYSCONTEXTID; 6189 $DB->import_record('context', $record); 6190 $DB->get_manager()->reset_sequence('context'); 6191 } else { 6192 $record->id = $DB->insert_record('context', $record); 6193 } 6194 } catch (dml_exception $e) { 6195 // can not create context - table does not exist yet, sorry 6196 return null; 6197 } 6198 } 6199 6200 if ($record->instanceid != 0) { 6201 // this is very weird, somebody must be messing with context table 6202 debugging('Invalid system context detected'); 6203 } 6204 6205 if ($record->depth != 1 or $record->path != '/'.$record->id) { 6206 // fix path if necessary, initial install or path reset 6207 $record->depth = 1; 6208 $record->path = '/'.$record->id; 6209 $DB->update_record('context', $record); 6210 } 6211 6212 if (!defined('SYSCONTEXTID')) { 6213 define('SYSCONTEXTID', $record->id); 6214 } 6215 6216 context::$systemcontext = new context_system($record); 6217 return context::$systemcontext; 6218 } 6219 6220 /** 6221 * Returns all site contexts except the system context, DO NOT call on production servers!! 6222 * 6223 * Contexts are not cached. 6224 * 6225 * @return array 6226 */ 6227 public function get_child_contexts() { 6228 global $DB; 6229 6230 debugging('Fetching of system context child courses is strongly discouraged on production servers (it may eat all available memory)!'); 6231 6232 // Just get all the contexts except for CONTEXT_SYSTEM level 6233 // and hope we don't OOM in the process - don't cache 6234 $sql = "SELECT c.* 6235 FROM {context} c 6236 WHERE contextlevel > ".CONTEXT_SYSTEM; 6237 $records = $DB->get_records_sql($sql); 6238 6239 $result = array(); 6240 foreach ($records as $record) { 6241 $result[$record->id] = context::create_instance_from_record($record); 6242 } 6243 6244 return $result; 6245 } 6246 6247 /** 6248 * Returns sql necessary for purging of stale context instances. 6249 * 6250 * @static 6251 * @return string cleanup SQL 6252 */ 6253 protected static function get_cleanup_sql() { 6254 $sql = " 6255 SELECT c.* 6256 FROM {context} c 6257 WHERE 1=2 6258 "; 6259 6260 return $sql; 6261 } 6262 6263 /** 6264 * Rebuild context paths and depths at system context level. 6265 * 6266 * @static 6267 * @param bool $force 6268 */ 6269 protected static function build_paths($force) { 6270 global $DB; 6271 6272 /* note: ignore $force here, we always do full test of system context */ 6273 6274 // exactly one record must exist 6275 $record = $DB->get_record('context', array('contextlevel'=>CONTEXT_SYSTEM), '*', MUST_EXIST); 6276 6277 if ($record->instanceid != 0) { 6278 debugging('Invalid system context detected'); 6279 } 6280 6281 if (defined('SYSCONTEXTID') and $record->id != SYSCONTEXTID) { 6282 debugging('Invalid SYSCONTEXTID detected'); 6283 } 6284 6285 if ($record->depth != 1 or $record->path != '/'.$record->id) { 6286 // fix path if necessary, initial install or path reset 6287 $record->depth = 1; 6288 $record->path = '/'.$record->id; 6289 $DB->update_record('context', $record); 6290 } 6291 } 6292 } 6293 6294 6295 /** 6296 * User context class 6297 * 6298 * @package core_access 6299 * @category access 6300 * @copyright Petr Skoda {@link http://skodak.org} 6301 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 6302 * @since Moodle 2.2 6303 */ 6304 class context_user extends context { 6305 /** 6306 * Please use context_user::instance($userid) if you need the instance of context. 6307 * Alternatively if you know only the context id use context::instance_by_id($contextid) 6308 * 6309 * @param stdClass $record 6310 */ 6311 protected function __construct(stdClass $record) { 6312 parent::__construct($record); 6313 if ($record->contextlevel != CONTEXT_USER) { 6314 throw new coding_exception('Invalid $record->contextlevel in context_user constructor.'); 6315 } 6316 } 6317 6318 /** 6319 * Returns human readable context level name. 6320 * 6321 * @static 6322 * @return string the human readable context level name. 6323 */ 6324 public static function get_level_name() { 6325 return get_string('user'); 6326 } 6327 6328 /** 6329 * Returns human readable context identifier. 6330 * 6331 * @param boolean $withprefix whether to prefix the name of the context with User 6332 * @param boolean $short does not apply to user context 6333 * @return string the human readable context name. 6334 */ 6335 public function get_context_name($withprefix = true, $short = false) { 6336 global $DB; 6337 6338 $name = ''; 6339 if ($user = $DB->get_record('user', array('id'=>$this->_instanceid, 'deleted'=>0))) { 6340 if ($withprefix){ 6341 $name = get_string('user').': '; 6342 } 6343 $name .= fullname($user); 6344 } 6345 return $name; 6346 } 6347 6348 /** 6349 * Returns the most relevant URL for this context. 6350 * 6351 * @return moodle_url 6352 */ 6353 public function get_url() { 6354 global $COURSE; 6355 6356 if ($COURSE->id == SITEID) { 6357 $url = new moodle_url('/user/profile.php', array('id'=>$this->_instanceid)); 6358 } else { 6359 $url = new moodle_url('/user/view.php', array('id'=>$this->_instanceid, 'courseid'=>$COURSE->id)); 6360 } 6361 return $url; 6362 } 6363 6364 /** 6365 * Returns array of relevant context capability records. 6366 * 6367 * @return array 6368 */ 6369 public function get_capabilities() { 6370 global $DB; 6371 6372 $sort = 'ORDER BY contextlevel,component,name'; // To group them sensibly for display 6373 6374 $extracaps = array('moodle/grade:viewall'); 6375 list($extra, $params) = $DB->get_in_or_equal($extracaps, SQL_PARAMS_NAMED, 'cap'); 6376 $sql = "SELECT * 6377 FROM {capabilities} 6378 WHERE contextlevel = ".CONTEXT_USER." 6379 OR name $extra"; 6380 6381 return $records = $DB->get_records_sql($sql.' '.$sort, $params); 6382 } 6383 6384 /** 6385 * Returns user context instance. 6386 * 6387 * @static 6388 * @param int $instanceid 6389 * @param int $strictness 6390 * @return context_user context instance 6391 */ 6392 public static function instance($instanceid, $strictness = MUST_EXIST) { 6393 global $DB; 6394 6395 if ($context = context::cache_get(CONTEXT_USER, $instanceid)) { 6396 return $context; 6397 } 6398 6399 if (!$record = $DB->get_record('context', array('contextlevel'=>CONTEXT_USER, 'instanceid'=>$instanceid))) { 6400 if ($user = $DB->get_record('user', array('id'=>$instanceid, 'deleted'=>0), 'id', $strictness)) { 6401 $record = context::insert_context_record(CONTEXT_USER, $user->id, '/'.SYSCONTEXTID, 0); 6402 } 6403 } 6404 6405 if ($record) { 6406 $context = new context_user($record); 6407 context::cache_add($context); 6408 return $context; 6409 } 6410 6411 return false; 6412 } 6413 6414 /** 6415 * Create missing context instances at user context level 6416 * @static 6417 */ 6418 protected static function create_level_instances() { 6419 global $DB; 6420 6421 $sql = "INSERT INTO {context} (contextlevel, instanceid) 6422 SELECT ".CONTEXT_USER.", u.id 6423 FROM {user} u 6424 WHERE u.deleted = 0 6425 AND NOT EXISTS (SELECT 'x' 6426 FROM {context} cx 6427 WHERE u.id = cx.instanceid AND cx.contextlevel=".CONTEXT_USER.")"; 6428 $DB->execute($sql); 6429 } 6430 6431 /** 6432 * Returns sql necessary for purging of stale context instances. 6433 * 6434 * @static 6435 * @return string cleanup SQL 6436 */ 6437 protected static function get_cleanup_sql() { 6438 $sql = " 6439 SELECT c.* 6440 FROM {context} c 6441 LEFT OUTER JOIN {user} u ON (c.instanceid = u.id AND u.deleted = 0) 6442 WHERE u.id IS NULL AND c.contextlevel = ".CONTEXT_USER." 6443 "; 6444 6445 return $sql; 6446 } 6447 6448 /** 6449 * Rebuild context paths and depths at user context level. 6450 * 6451 * @static 6452 * @param bool $force 6453 */ 6454 protected static function build_paths($force) { 6455 global $DB; 6456 6457 // First update normal users. 6458 $path = $DB->sql_concat('?', 'id'); 6459 $pathstart = '/' . SYSCONTEXTID . '/'; 6460 $params = array($pathstart); 6461 6462 if ($force) { 6463 $where = "depth <> 2 OR path IS NULL OR path <> ({$path})"; 6464 $params[] = $pathstart; 6465 } else { 6466 $where = "depth = 0 OR path IS NULL"; 6467 } 6468 6469 $sql = "UPDATE {context} 6470 SET depth = 2, 6471 path = {$path} 6472 WHERE contextlevel = " . CONTEXT_USER . " 6473 AND ($where)"; 6474 $DB->execute($sql, $params); 6475 } 6476 } 6477 6478 6479 /** 6480 * Course category context class 6481 * 6482 * @package core_access 6483 * @category access 6484 * @copyright Petr Skoda {@link http://skodak.org} 6485 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 6486 * @since Moodle 2.2 6487 */ 6488 class context_coursecat extends context { 6489 /** 6490 * Please use context_coursecat::instance($coursecatid) if you need the instance of context. 6491 * Alternatively if you know only the context id use context::instance_by_id($contextid) 6492 * 6493 * @param stdClass $record 6494 */ 6495 protected function __construct(stdClass $record) { 6496 parent::__construct($record); 6497 if ($record->contextlevel != CONTEXT_COURSECAT) { 6498 throw new coding_exception('Invalid $record->contextlevel in context_coursecat constructor.'); 6499 } 6500 } 6501 6502 /** 6503 * Returns human readable context level name. 6504 * 6505 * @static 6506 * @return string the human readable context level name. 6507 */ 6508 public static function get_level_name() { 6509 return get_string('category'); 6510 } 6511 6512 /** 6513 * Returns human readable context identifier. 6514 * 6515 * @param boolean $withprefix whether to prefix the name of the context with Category 6516 * @param boolean $short does not apply to course categories 6517 * @return string the human readable context name. 6518 */ 6519 public function get_context_name($withprefix = true, $short = false) { 6520 global $DB; 6521 6522 $name = ''; 6523 if ($category = $DB->get_record('course_categories', array('id'=>$this->_instanceid))) { 6524 if ($withprefix){ 6525 $name = get_string('category').': '; 6526 } 6527 $name .= format_string($category->name, true, array('context' => $this)); 6528 } 6529 return $name; 6530 } 6531 6532 /** 6533 * Returns the most relevant URL for this context. 6534 * 6535 * @return moodle_url 6536 */ 6537 public function get_url() { 6538 return new moodle_url('/course/index.php', array('categoryid' => $this->_instanceid)); 6539 } 6540 6541 /** 6542 * Returns array of relevant context capability records. 6543 * 6544 * @return array 6545 */ 6546 public function get_capabilities() { 6547 global $DB; 6548 6549 $sort = 'ORDER BY contextlevel,component,name'; // To group them sensibly for display 6550 6551 $params = array(); 6552 $sql = "SELECT * 6553 FROM {capabilities} 6554 WHERE contextlevel IN (".CONTEXT_COURSECAT.",".CONTEXT_COURSE.",".CONTEXT_MODULE.",".CONTEXT_BLOCK.")"; 6555 6556 return $DB->get_records_sql($sql.' '.$sort, $params); 6557 } 6558 6559 /** 6560 * Returns course category context instance. 6561 * 6562 * @static 6563 * @param int $instanceid 6564 * @param int $strictness 6565 * @return context_coursecat context instance 6566 */ 6567 public static function instance($instanceid, $strictness = MUST_EXIST) { 6568 global $DB; 6569 6570 if ($context = context::cache_get(CONTEXT_COURSECAT, $instanceid)) { 6571 return $context; 6572 } 6573 6574 if (!$record = $DB->get_record('context', array('contextlevel'=>CONTEXT_COURSECAT, 'instanceid'=>$instanceid))) { 6575 if ($category = $DB->get_record('course_categories', array('id'=>$instanceid), 'id,parent', $strictness)) { 6576 if ($category->parent) { 6577 $parentcontext = context_coursecat::instance($category->parent); 6578 $record = context::insert_context_record(CONTEXT_COURSECAT, $category->id, $parentcontext->path); 6579 } else { 6580 $record = context::insert_context_record(CONTEXT_COURSECAT, $category->id, '/'.SYSCONTEXTID, 0); 6581 } 6582 } 6583 } 6584 6585 if ($record) { 6586 $context = new context_coursecat($record); 6587 context::cache_add($context); 6588 return $context; 6589 } 6590 6591 return false; 6592 } 6593 6594 /** 6595 * Returns immediate child contexts of category and all subcategories, 6596 * children of subcategories and courses are not returned. 6597 * 6598 * @return array 6599 */ 6600 public function get_child_contexts() { 6601 global $DB; 6602 6603 if (empty($this->_path) or empty($this->_depth)) { 6604 debugging('Can not find child contexts of context '.$this->_id.' try rebuilding of context paths'); 6605 return array(); 6606 } 6607 6608 $sql = "SELECT ctx.* 6609 FROM {context} ctx 6610 WHERE ctx.path LIKE ? AND (ctx.depth = ? OR ctx.contextlevel = ?)"; 6611 $params = array($this->_path.'/%', $this->depth+1, CONTEXT_COURSECAT); 6612 $records = $DB->get_records_sql($sql, $params); 6613 6614 $result = array(); 6615 foreach ($records as $record) { 6616 $result[$record->id] = context::create_instance_from_record($record); 6617 } 6618 6619 return $result; 6620 } 6621 6622 /** 6623 * Create missing context instances at course category context level 6624 * @static 6625 */ 6626 protected static function create_level_instances() { 6627 global $DB; 6628 6629 $sql = "INSERT INTO {context} (contextlevel, instanceid) 6630 SELECT ".CONTEXT_COURSECAT.", cc.id 6631 FROM {course_categories} cc 6632 WHERE NOT EXISTS (SELECT 'x' 6633 FROM {context} cx 6634 WHERE cc.id = cx.instanceid AND cx.contextlevel=".CONTEXT_COURSECAT.")"; 6635 $DB->execute($sql); 6636 } 6637 6638 /** 6639 * Returns sql necessary for purging of stale context instances. 6640 * 6641 * @static 6642 * @return string cleanup SQL 6643 */ 6644 protected static function get_cleanup_sql() { 6645 $sql = " 6646 SELECT c.* 6647 FROM {context} c 6648 LEFT OUTER JOIN {course_categories} cc ON c.instanceid = cc.id 6649 WHERE cc.id IS NULL AND c.contextlevel = ".CONTEXT_COURSECAT." 6650 "; 6651 6652 return $sql; 6653 } 6654 6655 /** 6656 * Rebuild context paths and depths at course category context level. 6657 * 6658 * @static 6659 * @param bool $force 6660 */ 6661 protected static function build_paths($force) { 6662 global $DB; 6663 6664 if ($force or $DB->record_exists_select('context', "contextlevel = ".CONTEXT_COURSECAT." AND (depth = 0 OR path IS NULL)")) { 6665 if ($force) { 6666 $ctxemptyclause = $emptyclause = ''; 6667 } else { 6668 $ctxemptyclause = "AND (ctx.path IS NULL OR ctx.depth = 0)"; 6669 $emptyclause = "AND ({context}.path IS NULL OR {context}.depth = 0)"; 6670 } 6671 6672 $base = '/'.SYSCONTEXTID; 6673 6674 // Normal top level categories 6675 $sql = "UPDATE {context} 6676 SET depth=2, 6677 path=".$DB->sql_concat("'$base/'", 'id')." 6678 WHERE contextlevel=".CONTEXT_COURSECAT." 6679 AND EXISTS (SELECT 'x' 6680 FROM {course_categories} cc 6681 WHERE cc.id = {context}.instanceid AND cc.depth=1) 6682 $emptyclause"; 6683 $DB->execute($sql); 6684 6685 // Deeper categories - one query per depthlevel 6686 $maxdepth = $DB->get_field_sql("SELECT MAX(depth) FROM {course_categories}"); 6687 for ($n=2; $n<=$maxdepth; $n++) { 6688 $sql = "INSERT INTO {context_temp} (id, path, depth) 6689 SELECT ctx.id, ".$DB->sql_concat('pctx.path', "'/'", 'ctx.id').", pctx.depth+1 6690 FROM {context} ctx 6691 JOIN {course_categories} cc ON (cc.id = ctx.instanceid AND ctx.contextlevel = ".CONTEXT_COURSECAT." AND cc.depth = $n) 6692 JOIN {context} pctx ON (pctx.instanceid = cc.parent AND pctx.contextlevel = ".CONTEXT_COURSECAT.") 6693 WHERE pctx.path IS NOT NULL AND pctx.depth > 0 6694 $ctxemptyclause"; 6695 $trans = $DB->start_delegated_transaction(); 6696 $DB->delete_records('context_temp'); 6697 $DB->execute($sql); 6698 context::merge_context_temp_table(); 6699 $DB->delete_records('context_temp'); 6700 $trans->allow_commit(); 6701 6702 } 6703 } 6704 } 6705 } 6706 6707 6708 /** 6709 * Course context class 6710 * 6711 * @package core_access 6712 * @category access 6713 * @copyright Petr Skoda {@link http://skodak.org} 6714 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 6715 * @since Moodle 2.2 6716 */ 6717 class context_course extends context { 6718 /** 6719 * Please use context_course::instance($courseid) if you need the instance of context. 6720 * Alternatively if you know only the context id use context::instance_by_id($contextid) 6721 * 6722 * @param stdClass $record 6723 */ 6724 protected function __construct(stdClass $record) { 6725 parent::__construct($record); 6726 if ($record->contextlevel != CONTEXT_COURSE) { 6727 throw new coding_exception('Invalid $record->contextlevel in context_course constructor.'); 6728 } 6729 } 6730 6731 /** 6732 * Returns human readable context level name. 6733 * 6734 * @static 6735 * @return string the human readable context level name. 6736 */ 6737 public static function get_level_name() { 6738 return get_string('course'); 6739 } 6740 6741 /** 6742 * Returns human readable context identifier. 6743 * 6744 * @param boolean $withprefix whether to prefix the name of the context with Course 6745 * @param boolean $short whether to use the short name of the thing. 6746 * @return string the human readable context name. 6747 */ 6748 public function get_context_name($withprefix = true, $short = false) { 6749 global $DB; 6750 6751 $name = ''; 6752 if ($this->_instanceid == SITEID) { 6753 $name = get_string('frontpage', 'admin'); 6754 } else { 6755 if ($course = $DB->get_record('course', array('id'=>$this->_instanceid))) { 6756 if ($withprefix){ 6757 $name = get_string('course').': '; 6758 } 6759 if ($short){ 6760 $name .= format_string($course->shortname, true, array('context' => $this)); 6761 } else { 6762 $name .= format_string(get_course_display_name_for_list($course)); 6763 } 6764 } 6765 } 6766 return $name; 6767 } 6768 6769 /** 6770 * Returns the most relevant URL for this context. 6771 * 6772 * @return moodle_url 6773 */ 6774 public function get_url() { 6775 if ($this->_instanceid != SITEID) { 6776 return new moodle_url('/course/view.php', array('id'=>$this->_instanceid)); 6777 } 6778 6779 return new moodle_url('/'); 6780 } 6781 6782 /** 6783 * Returns array of relevant context capability records. 6784 * 6785 * @return array 6786 */ 6787 public function get_capabilities() { 6788 global $DB; 6789 6790 $sort = 'ORDER BY contextlevel,component,name'; // To group them sensibly for display 6791 6792 $params = array(); 6793 $sql = "SELECT * 6794 FROM {capabilities} 6795 WHERE contextlevel IN (".CONTEXT_COURSE.",".CONTEXT_MODULE.",".CONTEXT_BLOCK.")"; 6796 6797 return $DB->get_records_sql($sql.' '.$sort, $params); 6798 } 6799 6800 /** 6801 * Is this context part of any course? If yes return course context. 6802 * 6803 * @param bool $strict true means throw exception if not found, false means return false if not found 6804 * @return context_course context of the enclosing course, null if not found or exception 6805 */ 6806 public function get_course_context($strict = true) { 6807 return $this; 6808 } 6809 6810 /** 6811 * Returns course context instance. 6812 * 6813 * @static 6814 * @param int $instanceid 6815 * @param int $strictness 6816 * @return context_course context instance 6817 */ 6818 public static function instance($instanceid, $strictness = MUST_EXIST) { 6819 global $DB; 6820 6821 if ($context = context::cache_get(CONTEXT_COURSE, $instanceid)) { 6822 return $context; 6823 } 6824 6825 if (!$record = $DB->get_record('context', array('contextlevel'=>CONTEXT_COURSE, 'instanceid'=>$instanceid))) { 6826 if ($course = $DB->get_record('course', array('id'=>$instanceid), 'id,category', $strictness)) { 6827 if ($course->category) { 6828 $parentcontext = context_coursecat::instance($course->category); 6829 $record = context::insert_context_record(CONTEXT_COURSE, $course->id, $parentcontext->path); 6830 } else { 6831 $record = context::insert_context_record(CONTEXT_COURSE, $course->id, '/'.SYSCONTEXTID, 0); 6832 } 6833 } 6834 } 6835 6836 if ($record) { 6837 $context = new context_course($record); 6838 context::cache_add($context); 6839 return $context; 6840 } 6841 6842 return false; 6843 } 6844 6845 /** 6846 * Create missing context instances at course context level 6847 * @static 6848 */ 6849 protected static function create_level_instances() { 6850 global $DB; 6851 6852 $sql = "INSERT INTO {context} (contextlevel, instanceid) 6853 SELECT ".CONTEXT_COURSE.", c.id 6854 FROM {course} c 6855 WHERE NOT EXISTS (SELECT 'x' 6856 FROM {context} cx 6857 WHERE c.id = cx.instanceid AND cx.contextlevel=".CONTEXT_COURSE.")"; 6858 $DB->execute($sql); 6859 } 6860 6861 /** 6862 * Returns sql necessary for purging of stale context instances. 6863 * 6864 * @static 6865 * @return string cleanup SQL 6866 */ 6867 protected static function get_cleanup_sql() { 6868 $sql = " 6869 SELECT c.* 6870 FROM {context} c 6871 LEFT OUTER JOIN {course} co ON c.instanceid = co.id 6872 WHERE co.id IS NULL AND c.contextlevel = ".CONTEXT_COURSE." 6873 "; 6874 6875 return $sql; 6876 } 6877 6878 /** 6879 * Rebuild context paths and depths at course context level. 6880 * 6881 * @static 6882 * @param bool $force 6883 */ 6884 protected static function build_paths($force) { 6885 global $DB; 6886 6887 if ($force or $DB->record_exists_select('context', "contextlevel = ".CONTEXT_COURSE." AND (depth = 0 OR path IS NULL)")) { 6888 if ($force) { 6889 $ctxemptyclause = $emptyclause = ''; 6890 } else { 6891 $ctxemptyclause = "AND (ctx.path IS NULL OR ctx.depth = 0)"; 6892 $emptyclause = "AND ({context}.path IS NULL OR {context}.depth = 0)"; 6893 } 6894 6895 $base = '/'.SYSCONTEXTID; 6896 6897 // Standard frontpage 6898 $sql = "UPDATE {context} 6899 SET depth = 2, 6900 path = ".$DB->sql_concat("'$base/'", 'id')." 6901 WHERE contextlevel = ".CONTEXT_COURSE." 6902 AND EXISTS (SELECT 'x' 6903 FROM {course} c 6904 WHERE c.id = {context}.instanceid AND c.category = 0) 6905 $emptyclause"; 6906 $DB->execute($sql); 6907 6908 // standard courses 6909 $sql = "INSERT INTO {context_temp} (id, path, depth) 6910 SELECT ctx.id, ".$DB->sql_concat('pctx.path', "'/'", 'ctx.id').", pctx.depth+1 6911 FROM {context} ctx 6912 JOIN {course} c ON (c.id = ctx.instanceid AND ctx.contextlevel = ".CONTEXT_COURSE." AND c.category <> 0) 6913 JOIN {context} pctx ON (pctx.instanceid = c.category AND pctx.contextlevel = ".CONTEXT_COURSECAT.") 6914 WHERE pctx.path IS NOT NULL AND pctx.depth > 0 6915 $ctxemptyclause"; 6916 $trans = $DB->start_delegated_transaction(); 6917 $DB->delete_records('context_temp'); 6918 $DB->execute($sql); 6919 context::merge_context_temp_table(); 6920 $DB->delete_records('context_temp'); 6921 $trans->allow_commit(); 6922 } 6923 } 6924 } 6925 6926 6927 /** 6928 * Course module context class 6929 * 6930 * @package core_access 6931 * @category access 6932 * @copyright Petr Skoda {@link http://skodak.org} 6933 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 6934 * @since Moodle 2.2 6935 */ 6936 class context_module extends context { 6937 /** 6938 * Please use context_module::instance($cmid) if you need the instance of context. 6939 * Alternatively if you know only the context id use context::instance_by_id($contextid) 6940 * 6941 * @param stdClass $record 6942 */ 6943 protected function __construct(stdClass $record) { 6944 parent::__construct($record); 6945 if ($record->contextlevel != CONTEXT_MODULE) { 6946 throw new coding_exception('Invalid $record->contextlevel in context_module constructor.'); 6947 } 6948 } 6949 6950 /** 6951 * Returns human readable context level name. 6952 * 6953 * @static 6954 * @return string the human readable context level name. 6955 */ 6956 public static function get_level_name() { 6957 return get_string('activitymodule'); 6958 } 6959 6960 /** 6961 * Returns human readable context identifier. 6962 * 6963 * @param boolean $withprefix whether to prefix the name of the context with the 6964 * module name, e.g. Forum, Glossary, etc. 6965 * @param boolean $short does not apply to module context 6966 * @return string the human readable context name. 6967 */ 6968 public function get_context_name($withprefix = true, $short = false) { 6969 global $DB; 6970 6971 $name = ''; 6972 if ($cm = $DB->get_record_sql("SELECT cm.*, md.name AS modname 6973 FROM {course_modules} cm 6974 JOIN {modules} md ON md.id = cm.module 6975 WHERE cm.id = ?", array($this->_instanceid))) { 6976 if ($mod = $DB->get_record($cm->modname, array('id' => $cm->instance))) { 6977 if ($withprefix){ 6978 $name = get_string('modulename', $cm->modname).': '; 6979 } 6980 $name .= format_string($mod->name, true, array('context' => $this)); 6981 } 6982 } 6983 return $name; 6984 } 6985 6986 /** 6987 * Returns the most relevant URL for this context. 6988 * 6989 * @return moodle_url 6990 */ 6991 public function get_url() { 6992 global $DB; 6993 6994 if ($modname = $DB->get_field_sql("SELECT md.name AS modname 6995 FROM {course_modules} cm 6996 JOIN {modules} md ON md.id = cm.module 6997 WHERE cm.id = ?", array($this->_instanceid))) { 6998 return new moodle_url('/mod/' . $modname . '/view.php', array('id'=>$this->_instanceid)); 6999 } 7000 7001 return new moodle_url('/'); 7002 } 7003 7004 /** 7005 * Returns array of relevant context capability records. 7006 * 7007 * @return array 7008 */ 7009 public function get_capabilities() { 7010 global $DB, $CFG; 7011 7012 $sort = 'ORDER BY contextlevel,component,name'; // To group them sensibly for display 7013 7014 $cm = $DB->get_record('course_modules', array('id'=>$this->_instanceid)); 7015 $module = $DB->get_record('modules', array('id'=>$cm->module)); 7016 7017 $subcaps = array(); 7018 $subpluginsfile = "$CFG->dirroot/mod/$module->name/db/subplugins.php"; 7019 if (file_exists($subpluginsfile)) { 7020 $subplugins = array(); // should be redefined in the file 7021 include($subpluginsfile); 7022 if (!empty($subplugins)) { 7023 foreach (array_keys($subplugins) as $subplugintype) { 7024 foreach (array_keys(core_component::get_plugin_list($subplugintype)) as $subpluginname) { 7025 $subcaps = array_merge($subcaps, array_keys(load_capability_def($subplugintype.'_'.$subpluginname))); 7026 } 7027 } 7028 } 7029 } 7030 7031 $modfile = "$CFG->dirroot/mod/$module->name/lib.php"; 7032 $extracaps = array(); 7033 if (file_exists($modfile)) { 7034 include_once($modfile); 7035 $modfunction = $module->name.'_get_extra_capabilities'; 7036 if (function_exists($modfunction)) { 7037 $extracaps = $modfunction(); 7038 } 7039 } 7040 7041 $extracaps = array_merge($subcaps, $extracaps); 7042 $extra = ''; 7043 list($extra, $params) = $DB->get_in_or_equal( 7044 $extracaps, SQL_PARAMS_NAMED, 'cap0', true, ''); 7045 if (!empty($extra)) { 7046 $extra = "OR name $extra"; 7047 } 7048 $sql = "SELECT * 7049 FROM {capabilities} 7050 WHERE (contextlevel = ".CONTEXT_MODULE." 7051 AND (component = :component OR component = 'moodle')) 7052 $extra"; 7053 $params['component'] = "mod_$module->name"; 7054 7055 return $DB->get_records_sql($sql.' '.$sort, $params); 7056 } 7057 7058 /** 7059 * Is this context part of any course? If yes return course context. 7060 * 7061 * @param bool $strict true means throw exception if not found, false means return false if not found 7062 * @return context_course context of the enclosing course, null if not found or exception 7063 */ 7064 public function get_course_context($strict = true) { 7065 return $this->get_parent_context(); 7066 } 7067 7068 /** 7069 * Returns module context instance. 7070 * 7071 * @static 7072 * @param int $instanceid 7073 * @param int $strictness 7074 * @return context_module context instance 7075 */ 7076 public static function instance($instanceid, $strictness = MUST_EXIST) { 7077 global $DB; 7078 7079 if ($context = context::cache_get(CONTEXT_MODULE, $instanceid)) { 7080 return $context; 7081 } 7082 7083 if (!$record = $DB->get_record('context', array('contextlevel'=>CONTEXT_MODULE, 'instanceid'=>$instanceid))) { 7084 if ($cm = $DB->get_record('course_modules', array('id'=>$instanceid), 'id,course', $strictness)) { 7085 $parentcontext = context_course::instance($cm->course); 7086 $record = context::insert_context_record(CONTEXT_MODULE, $cm->id, $parentcontext->path); 7087 } 7088 } 7089 7090 if ($record) { 7091 $context = new context_module($record); 7092 context::cache_add($context); 7093 return $context; 7094 } 7095 7096 return false; 7097 } 7098 7099 /** 7100 * Create missing context instances at module context level 7101 * @static 7102 */ 7103 protected static function create_level_instances() { 7104 global $DB; 7105 7106 $sql = "INSERT INTO {context} (contextlevel, instanceid) 7107 SELECT ".CONTEXT_MODULE.", cm.id 7108 FROM {course_modules} cm 7109 WHERE NOT EXISTS (SELECT 'x' 7110 FROM {context} cx 7111 WHERE cm.id = cx.instanceid AND cx.contextlevel=".CONTEXT_MODULE.")"; 7112 $DB->execute($sql); 7113 } 7114 7115 /** 7116 * Returns sql necessary for purging of stale context instances. 7117 * 7118 * @static 7119 * @return string cleanup SQL 7120 */ 7121 protected static function get_cleanup_sql() { 7122 $sql = " 7123 SELECT c.* 7124 FROM {context} c 7125 LEFT OUTER JOIN {course_modules} cm ON c.instanceid = cm.id 7126 WHERE cm.id IS NULL AND c.contextlevel = ".CONTEXT_MODULE." 7127 "; 7128 7129 return $sql; 7130 } 7131 7132 /** 7133 * Rebuild context paths and depths at module context level. 7134 * 7135 * @static 7136 * @param bool $force 7137 */ 7138 protected static function build_paths($force) { 7139 global $DB; 7140 7141 if ($force or $DB->record_exists_select('context', "contextlevel = ".CONTEXT_MODULE." AND (depth = 0 OR path IS NULL)")) { 7142 if ($force) { 7143 $ctxemptyclause = ''; 7144 } else { 7145 $ctxemptyclause = "AND (ctx.path IS NULL OR ctx.depth = 0)"; 7146 } 7147 7148 $sql = "INSERT INTO {context_temp} (id, path, depth) 7149 SELECT ctx.id, ".$DB->sql_concat('pctx.path', "'/'", 'ctx.id').", pctx.depth+1 7150 FROM {context} ctx 7151 JOIN {course_modules} cm ON (cm.id = ctx.instanceid AND ctx.contextlevel = ".CONTEXT_MODULE.") 7152 JOIN {context} pctx ON (pctx.instanceid = cm.course AND pctx.contextlevel = ".CONTEXT_COURSE.") 7153 WHERE pctx.path IS NOT NULL AND pctx.depth > 0 7154 $ctxemptyclause"; 7155 $trans = $DB->start_delegated_transaction(); 7156 $DB->delete_records('context_temp'); 7157 $DB->execute($sql); 7158 context::merge_context_temp_table(); 7159 $DB->delete_records('context_temp'); 7160 $trans->allow_commit(); 7161 } 7162 } 7163 } 7164 7165 7166 /** 7167 * Block context class 7168 * 7169 * @package core_access 7170 * @category access 7171 * @copyright Petr Skoda {@link http://skodak.org} 7172 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 7173 * @since Moodle 2.2 7174 */ 7175 class context_block extends context { 7176 /** 7177 * Please use context_block::instance($blockinstanceid) if you need the instance of context. 7178 * Alternatively if you know only the context id use context::instance_by_id($contextid) 7179 * 7180 * @param stdClass $record 7181 */ 7182 protected function __construct(stdClass $record) { 7183 parent::__construct($record); 7184 if ($record->contextlevel != CONTEXT_BLOCK) { 7185 throw new coding_exception('Invalid $record->contextlevel in context_block constructor'); 7186 } 7187 } 7188 7189 /** 7190 * Returns human readable context level name. 7191 * 7192 * @static 7193 * @return string the human readable context level name. 7194 */ 7195 public static function get_level_name() { 7196 return get_string('block'); 7197 } 7198 7199 /** 7200 * Returns human readable context identifier. 7201 * 7202 * @param boolean $withprefix whether to prefix the name of the context with Block 7203 * @param boolean $short does not apply to block context 7204 * @return string the human readable context name. 7205 */ 7206 public function get_context_name($withprefix = true, $short = false) { 7207 global $DB, $CFG; 7208 7209 $name = ''; 7210 if ($blockinstance = $DB->get_record('block_instances', array('id'=>$this->_instanceid))) { 7211 global $CFG; 7212 require_once("$CFG->dirroot/blocks/moodleblock.class.php"); 7213 require_once("$CFG->dirroot/blocks/$blockinstance->blockname/block_$blockinstance->blockname.php"); 7214 $blockname = "block_$blockinstance->blockname"; 7215 if ($blockobject = new $blockname()) { 7216 if ($withprefix){ 7217 $name = get_string('block').': '; 7218 } 7219 $name .= $blockobject->title; 7220 } 7221 } 7222 7223 return $name; 7224 } 7225 7226 /** 7227 * Returns the most relevant URL for this context. 7228 * 7229 * @return moodle_url 7230 */ 7231 public function get_url() { 7232 $parentcontexts = $this->get_parent_context(); 7233 return $parentcontexts->get_url(); 7234 } 7235 7236 /** 7237 * Returns array of relevant context capability records. 7238 * 7239 * @return array 7240 */ 7241 public function get_capabilities() { 7242 global $DB; 7243 7244 $sort = 'ORDER BY contextlevel,component,name'; // To group them sensibly for display 7245 7246 $params = array(); 7247 $bi = $DB->get_record('block_instances', array('id' => $this->_instanceid)); 7248 7249 $extra = ''; 7250 $extracaps = block_method_result($bi->blockname, 'get_extra_capabilities'); 7251 if ($extracaps) { 7252 list($extra, $params) = $DB->get_in_or_equal($extracaps, SQL_PARAMS_NAMED, 'cap'); 7253 $extra = "OR name $extra"; 7254 } 7255 7256 $sql = "SELECT * 7257 FROM {capabilities} 7258 WHERE (contextlevel = ".CONTEXT_BLOCK." 7259 AND component = :component) 7260 $extra"; 7261 $params['component'] = 'block_' . $bi->blockname; 7262 7263 return $DB->get_records_sql($sql.' '.$sort, $params); 7264 } 7265 7266 /** 7267 * Is this context part of any course? If yes return course context. 7268 * 7269 * @param bool $strict true means throw exception if not found, false means return false if not found 7270 * @return context_course context of the enclosing course, null if not found or exception 7271 */ 7272 public function get_course_context($strict = true) { 7273 $parentcontext = $this->get_parent_context(); 7274 return $parentcontext->get_course_context($strict); 7275 } 7276 7277 /** 7278 * Returns block context instance. 7279 * 7280 * @static 7281 * @param int $instanceid 7282 * @param int $strictness 7283 * @return context_block context instance 7284 */ 7285 public static function instance($instanceid, $strictness = MUST_EXIST) { 7286 global $DB; 7287 7288 if ($context = context::cache_get(CONTEXT_BLOCK, $instanceid)) { 7289 return $context; 7290 } 7291 7292 if (!$record = $DB->get_record('context', array('contextlevel'=>CONTEXT_BLOCK, 'instanceid'=>$instanceid))) { 7293 if ($bi = $DB->get_record('block_instances', array('id'=>$instanceid), 'id,parentcontextid', $strictness)) { 7294 $parentcontext = context::instance_by_id($bi->parentcontextid); 7295 $record = context::insert_context_record(CONTEXT_BLOCK, $bi->id, $parentcontext->path); 7296 } 7297 } 7298 7299 if ($record) { 7300 $context = new context_block($record); 7301 context::cache_add($context); 7302 return $context; 7303 } 7304 7305 return false; 7306 } 7307 7308 /** 7309 * Block do not have child contexts... 7310 * @return array 7311 */ 7312 public function get_child_contexts() { 7313 return array(); 7314 } 7315 7316 /** 7317 * Create missing context instances at block context level 7318 * @static 7319 */ 7320 protected static function create_level_instances() { 7321 global $DB; 7322 7323 $sql = "INSERT INTO {context} (contextlevel, instanceid) 7324 SELECT ".CONTEXT_BLOCK.", bi.id 7325 FROM {block_instances} bi 7326 WHERE NOT EXISTS (SELECT 'x' 7327 FROM {context} cx 7328 WHERE bi.id = cx.instanceid AND cx.contextlevel=".CONTEXT_BLOCK.")"; 7329 $DB->execute($sql); 7330 } 7331 7332 /** 7333 * Returns sql necessary for purging of stale context instances. 7334 * 7335 * @static 7336 * @return string cleanup SQL 7337 */ 7338 protected static function get_cleanup_sql() { 7339 $sql = " 7340 SELECT c.* 7341 FROM {context} c 7342 LEFT OUTER JOIN {block_instances} bi ON c.instanceid = bi.id 7343 WHERE bi.id IS NULL AND c.contextlevel = ".CONTEXT_BLOCK." 7344 "; 7345 7346 return $sql; 7347 } 7348 7349 /** 7350 * Rebuild context paths and depths at block context level. 7351 * 7352 * @static 7353 * @param bool $force 7354 */ 7355 protected static function build_paths($force) { 7356 global $DB; 7357 7358 if ($force or $DB->record_exists_select('context', "contextlevel = ".CONTEXT_BLOCK." AND (depth = 0 OR path IS NULL)")) { 7359 if ($force) { 7360 $ctxemptyclause = ''; 7361 } else { 7362 $ctxemptyclause = "AND (ctx.path IS NULL OR ctx.depth = 0)"; 7363 } 7364 7365 // pctx.path IS NOT NULL prevents fatal problems with broken block instances that point to invalid context parent 7366 $sql = "INSERT INTO {context_temp} (id, path, depth) 7367 SELECT ctx.id, ".$DB->sql_concat('pctx.path', "'/'", 'ctx.id').", pctx.depth+1 7368 FROM {context} ctx 7369 JOIN {block_instances} bi ON (bi.id = ctx.instanceid AND ctx.contextlevel = ".CONTEXT_BLOCK.") 7370 JOIN {context} pctx ON (pctx.id = bi.parentcontextid) 7371 WHERE (pctx.path IS NOT NULL AND pctx.depth > 0) 7372 $ctxemptyclause"; 7373 $trans = $DB->start_delegated_transaction(); 7374 $DB->delete_records('context_temp'); 7375 $DB->execute($sql); 7376 context::merge_context_temp_table(); 7377 $DB->delete_records('context_temp'); 7378 $trans->allow_commit(); 7379 } 7380 } 7381 } 7382 7383 7384 // ============== DEPRECATED FUNCTIONS ========================================== 7385 // Old context related functions were deprecated in 2.0, it is recommended 7386 // to use context classes in new code. Old function can be used when 7387 // creating patches that are supposed to be backported to older stable branches. 7388 // These deprecated functions will not be removed in near future, 7389 // before removing devs will be warned with a debugging message first, 7390 // then we will add error message and only after that we can remove the functions 7391 // completely. 7392 7393 /** 7394 * Runs get_records select on context table and returns the result 7395 * Does get_records_select on the context table, and returns the results ordered 7396 * by contextlevel, and then the natural sort order within each level. 7397 * for the purpose of $select, you need to know that the context table has been 7398 * aliased to ctx, so for example, you can call get_sorted_contexts('ctx.depth = 3'); 7399 * 7400 * @param string $select the contents of the WHERE clause. Remember to do ctx.fieldname. 7401 * @param array $params any parameters required by $select. 7402 * @return array the requested context records. 7403 */ 7404 function get_sorted_contexts($select, $params = array()) { 7405 7406 //TODO: we should probably rewrite all the code that is using this thing, the trouble is we MUST NOT modify the context instances... 7407 7408 global $DB; 7409 if ($select) { 7410 $select = 'WHERE ' . $select; 7411 } 7412 return $DB->get_records_sql(" 7413 SELECT ctx.* 7414 FROM {context} ctx 7415 LEFT JOIN {user} u ON ctx.contextlevel = " . CONTEXT_USER . " AND u.id = ctx.instanceid 7416 LEFT JOIN {course_categories} cat ON ctx.contextlevel = " . CONTEXT_COURSECAT . " AND cat.id = ctx.instanceid 7417 LEFT JOIN {course} c ON ctx.contextlevel = " . CONTEXT_COURSE . " AND c.id = ctx.instanceid 7418 LEFT JOIN {course_modules} cm ON ctx.contextlevel = " . CONTEXT_MODULE . " AND cm.id = ctx.instanceid 7419 LEFT JOIN {block_instances} bi ON ctx.contextlevel = " . CONTEXT_BLOCK . " AND bi.id = ctx.instanceid 7420 $select 7421 ORDER BY ctx.contextlevel, bi.defaultregion, COALESCE(cat.sortorder, c.sortorder, cm.section, bi.defaultweight), u.lastname, u.firstname, cm.id 7422 ", $params); 7423 } 7424 7425 /** 7426 * Given context and array of users, returns array of users whose enrolment status is suspended, 7427 * or enrolment has expired or has not started. Also removes those users from the given array 7428 * 7429 * @param context $context context in which suspended users should be extracted. 7430 * @param array $users list of users. 7431 * @param array $ignoreusers array of user ids to ignore, e.g. guest 7432 * @return array list of suspended users. 7433 */ 7434 function extract_suspended_users($context, &$users, $ignoreusers=array()) { 7435 global $DB; 7436 7437 // Get active enrolled users. 7438 list($sql, $params) = get_enrolled_sql($context, null, null, true); 7439 $activeusers = $DB->get_records_sql($sql, $params); 7440 7441 // Move suspended users to a separate array & remove from the initial one. 7442 $susers = array(); 7443 if (sizeof($activeusers)) { 7444 foreach ($users as $userid => $user) { 7445 if (!array_key_exists($userid, $activeusers) && !in_array($userid, $ignoreusers)) { 7446 $susers[$userid] = $user; 7447 unset($users[$userid]); 7448 } 7449 } 7450 } 7451 return $susers; 7452 } 7453 7454 /** 7455 * Given context and array of users, returns array of user ids whose enrolment status is suspended, 7456 * or enrolment has expired or not started. 7457 * 7458 * @param context $context context in which user enrolment is checked. 7459 * @param bool $usecache Enable or disable (default) the request cache 7460 * @return array list of suspended user id's. 7461 */ 7462 function get_suspended_userids(context $context, $usecache = false) { 7463 global $DB; 7464 7465 // Check the cache first for performance reasons if enabled. 7466 if ($usecache) { 7467 $cache = cache::make('core', 'suspended_userids'); 7468 $susers = $cache->get($context->id); 7469 if ($susers !== false) { 7470 return $susers; 7471 } 7472 } 7473 7474 // Get all enrolled users. 7475 list($sql, $params) = get_enrolled_sql($context); 7476 $users = $DB->get_records_sql($sql, $params); 7477 7478 // Get active enrolled users. 7479 list($sql, $params) = get_enrolled_sql($context, null, null, true); 7480 $activeusers = $DB->get_records_sql($sql, $params); 7481 7482 $susers = array(); 7483 if (sizeof($activeusers) != sizeof($users)) { 7484 foreach ($users as $userid => $user) { 7485 if (!array_key_exists($userid, $activeusers)) { 7486 $susers[$userid] = $userid; 7487 } 7488 } 7489 } 7490 7491 // Cache results for the remainder of this request. 7492 if ($usecache) { 7493 $cache->set($context->id, $susers); 7494 } 7495 7496 // Return. 7497 return $susers; 7498 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 20:29:05 2014 | Cross-referenced by PHPXref 0.7.1 |