|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00002 // This file is part of Moodle - http://moodle.org/ 00003 // 00004 // Moodle is free software: you can redistribute it and/or modify 00005 // it under the terms of the GNU General Public License as published by 00006 // the Free Software Foundation, either version 3 of the License, or 00007 // (at your option) any later version. 00008 // 00009 // Moodle is distributed in the hope that it will be useful, 00010 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00012 // GNU General Public License for more details. 00013 // 00014 // You should have received a copy of the GNU General Public License 00015 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 00016 00126 defined('MOODLE_INTERNAL') || die(); 00127 00129 define('CAP_INHERIT', 0); 00131 define('CAP_ALLOW', 1); 00133 define('CAP_PREVENT', -1); 00135 define('CAP_PROHIBIT', -1000); 00136 00138 define('CONTEXT_SYSTEM', 10); 00140 define('CONTEXT_USER', 30); 00142 define('CONTEXT_COURSECAT', 40); 00144 define('CONTEXT_COURSE', 50); 00146 define('CONTEXT_MODULE', 70); 00152 define('CONTEXT_BLOCK', 80); 00153 00155 define('RISK_MANAGETRUST', 0x0001); 00157 define('RISK_CONFIG', 0x0002); 00159 define('RISK_XSS', 0x0004); 00161 define('RISK_PERSONAL', 0x0008); 00163 define('RISK_SPAM', 0x0010); 00165 define('RISK_DATALOSS', 0x0020); 00166 00168 define('ROLENAME_ORIGINAL', 0); 00170 define('ROLENAME_ALIAS', 1); 00172 define('ROLENAME_BOTH', 2); 00174 define('ROLENAME_ORIGINALANDSHORT', 3); 00176 define('ROLENAME_ALIAS_RAW', 4); 00178 define('ROLENAME_SHORT', 5); 00179 00181 if (!defined('CONTEXT_CACHE_MAX_SIZE')) { 00182 define('CONTEXT_CACHE_MAX_SIZE', 2500); 00183 } 00184 00197 global $ACCESSLIB_PRIVATE; 00198 $ACCESSLIB_PRIVATE = new stdClass(); 00199 $ACCESSLIB_PRIVATE->dirtycontexts = null; // Dirty contexts cache, loaded from DB once per page 00200 $ACCESSLIB_PRIVATE->accessdatabyuser = array(); // Holds the cache of $accessdata structure for users (including $USER) 00201 $ACCESSLIB_PRIVATE->rolepermissions = array(); // role permissions cache - helps a lot with mem usage 00202 $ACCESSLIB_PRIVATE->capabilities = null; // detailed information about the capabilities 00203 00213 function accesslib_clear_all_caches_for_unit_testing() { 00214 global $UNITTEST, $USER; 00215 if (empty($UNITTEST->running)) { 00216 throw new coding_exception('You must not call clear_all_caches outside of unit tests.'); 00217 } 00218 00219 accesslib_clear_all_caches(true); 00220 00221 unset($USER->access); 00222 } 00223 00233 function accesslib_clear_all_caches($resetcontexts) { 00234 global $ACCESSLIB_PRIVATE; 00235 00236 $ACCESSLIB_PRIVATE->dirtycontexts = null; 00237 $ACCESSLIB_PRIVATE->accessdatabyuser = array(); 00238 $ACCESSLIB_PRIVATE->rolepermissions = array(); 00239 $ACCESSLIB_PRIVATE->capabilities = null; 00240 00241 if ($resetcontexts) { 00242 context_helper::reset_caches(); 00243 } 00244 } 00245 00253 function get_role_access($roleid) { 00254 global $DB, $ACCESSLIB_PRIVATE; 00255 00256 /* Get it in 1 DB query... 00257 * - relevant role caps at the root and down 00258 * to the course level - but not below 00259 */ 00260 00261 //TODO: MUC - this could be cached in shared memory to speed up first page loading, web crawlers, etc. 00262 00263 $accessdata = get_empty_accessdata(); 00264 00265 $accessdata['ra']['/'.SYSCONTEXTID] = array((int)$roleid => (int)$roleid); 00266 00267 // 00268 // Overrides for the role IN ANY CONTEXTS 00269 // down to COURSE - not below - 00270 // 00271 $sql = "SELECT ctx.path, 00272 rc.capability, rc.permission 00273 FROM {context} ctx 00274 JOIN {role_capabilities} rc ON rc.contextid = ctx.id 00275 LEFT JOIN {context} cctx 00276 ON (cctx.contextlevel = ".CONTEXT_COURSE." AND ctx.path LIKE ".$DB->sql_concat('cctx.path',"'/%'").") 00277 WHERE rc.roleid = ? AND cctx.id IS NULL"; 00278 $params = array($roleid); 00279 00280 // we need extra caching in CLI scripts and cron 00281 $rs = $DB->get_recordset_sql($sql, $params); 00282 foreach ($rs as $rd) { 00283 $k = "{$rd->path}:{$roleid}"; 00284 $accessdata['rdef'][$k][$rd->capability] = (int)$rd->permission; 00285 } 00286 $rs->close(); 00287 00288 // share the role definitions 00289 foreach ($accessdata['rdef'] as $k=>$unused) { 00290 if (!isset($ACCESSLIB_PRIVATE->rolepermissions[$k])) { 00291 $ACCESSLIB_PRIVATE->rolepermissions[$k] = $accessdata['rdef'][$k]; 00292 } 00293 $accessdata['rdef_count']++; 00294 $accessdata['rdef'][$k] =& $ACCESSLIB_PRIVATE->rolepermissions[$k]; 00295 } 00296 00297 return $accessdata; 00298 } 00299 00306 function get_guest_role() { 00307 global $CFG, $DB; 00308 00309 if (empty($CFG->guestroleid)) { 00310 if ($roles = $DB->get_records('role', array('archetype'=>'guest'))) { 00311 $guestrole = array_shift($roles); // Pick the first one 00312 set_config('guestroleid', $guestrole->id); 00313 return $guestrole; 00314 } else { 00315 debugging('Can not find any guest role!'); 00316 return false; 00317 } 00318 } else { 00319 if ($guestrole = $DB->get_record('role', array('id'=>$CFG->guestroleid))) { 00320 return $guestrole; 00321 } else { 00322 // somebody is messing with guest roles, remove incorrect setting and try to find a new one 00323 set_config('guestroleid', ''); 00324 return get_guest_role(); 00325 } 00326 } 00327 } 00328 00348 function has_capability($capability, context $context, $user = null, $doanything = true) { 00349 global $USER, $CFG, $SCRIPT, $ACCESSLIB_PRIVATE; 00350 00351 if (during_initial_install()) { 00352 if ($SCRIPT === "/$CFG->admin/index.php" or $SCRIPT === "/$CFG->admin/cli/install.php" or $SCRIPT === "/$CFG->admin/cli/install_database.php") { 00353 // we are in an installer - roles can not work yet 00354 return true; 00355 } else { 00356 return false; 00357 } 00358 } 00359 00360 if (strpos($capability, 'moodle/legacy:') === 0) { 00361 throw new coding_exception('Legacy capabilities can not be used any more!'); 00362 } 00363 00364 if (!is_bool($doanything)) { 00365 throw new coding_exception('Capability parameter "doanything" is wierd, only true or false is allowed. This has to be fixed in code.'); 00366 } 00367 00368 // capability must exist 00369 if (!$capinfo = get_capability_info($capability)) { 00370 debugging('Capability "'.$capability.'" was not found! This has to be fixed in code.'); 00371 return false; 00372 } 00373 00374 if (!isset($USER->id)) { 00375 // should never happen 00376 $USER->id = 0; 00377 } 00378 00379 // make sure there is a real user specified 00380 if ($user === null) { 00381 $userid = $USER->id; 00382 } else { 00383 $userid = is_object($user) ? $user->id : $user; 00384 } 00385 00386 // make sure forcelogin cuts off not-logged-in users if enabled 00387 if (!empty($CFG->forcelogin) and $userid == 0) { 00388 return false; 00389 } 00390 00391 // make sure the guest account and not-logged-in users never get any risky caps no matter what the actual settings are. 00392 if (($capinfo->captype === 'write') or ($capinfo->riskbitmask & (RISK_XSS | RISK_CONFIG | RISK_DATALOSS))) { 00393 if (isguestuser($userid) or $userid == 0) { 00394 return false; 00395 } 00396 } 00397 00398 // somehow make sure the user is not deleted and actually exists 00399 if ($userid != 0) { 00400 if ($userid == $USER->id and isset($USER->deleted)) { 00401 // this prevents one query per page, it is a bit of cheating, 00402 // but hopefully session is terminated properly once user is deleted 00403 if ($USER->deleted) { 00404 return false; 00405 } 00406 } else { 00407 if (!context_user::instance($userid, IGNORE_MISSING)) { 00408 // no user context == invalid userid 00409 return false; 00410 } 00411 } 00412 } 00413 00414 // context path/depth must be valid 00415 if (empty($context->path) or $context->depth == 0) { 00416 // this should not happen often, each upgrade tries to rebuild the context paths 00417 debugging('Context id '.$context->id.' does not have valid path, please use build_context_path()'); 00418 if (is_siteadmin($userid)) { 00419 return true; 00420 } else { 00421 return false; 00422 } 00423 } 00424 00425 // Find out if user is admin - it is not possible to override the doanything in any way 00426 // and it is not possible to switch to admin role either. 00427 if ($doanything) { 00428 if (is_siteadmin($userid)) { 00429 if ($userid != $USER->id) { 00430 return true; 00431 } 00432 // make sure switchrole is not used in this context 00433 if (empty($USER->access['rsw'])) { 00434 return true; 00435 } 00436 $parts = explode('/', trim($context->path, '/')); 00437 $path = ''; 00438 $switched = false; 00439 foreach ($parts as $part) { 00440 $path .= '/' . $part; 00441 if (!empty($USER->access['rsw'][$path])) { 00442 $switched = true; 00443 break; 00444 } 00445 } 00446 if (!$switched) { 00447 return true; 00448 } 00449 //ok, admin switched role in this context, let's use normal access control rules 00450 } 00451 } 00452 00453 // Careful check for staleness... 00454 $context->reload_if_dirty(); 00455 00456 if ($USER->id == $userid) { 00457 if (!isset($USER->access)) { 00458 load_all_capabilities(); 00459 } 00460 $access =& $USER->access; 00461 00462 } else { 00463 // make sure user accessdata is really loaded 00464 get_user_accessdata($userid, true); 00465 $access =& $ACCESSLIB_PRIVATE->accessdatabyuser[$userid]; 00466 } 00467 00468 00469 // Load accessdata for below-the-course context if necessary, 00470 // all contexts at and above all courses are already loaded 00471 if ($context->contextlevel != CONTEXT_COURSE and $coursecontext = $context->get_course_context(false)) { 00472 load_course_context($userid, $coursecontext, $access); 00473 } 00474 00475 return has_capability_in_accessdata($capability, $context, $access); 00476 } 00477 00492 function has_any_capability(array $capabilities, context $context, $userid = null, $doanything = true) { 00493 foreach ($capabilities as $capability) { 00494 if (has_capability($capability, $context, $userid, $doanything)) { 00495 return true; 00496 } 00497 } 00498 return false; 00499 } 00500 00515 function has_all_capabilities(array $capabilities, context $context, $userid = null, $doanything = true) { 00516 foreach ($capabilities as $capability) { 00517 if (!has_capability($capability, $context, $userid, $doanything)) { 00518 return false; 00519 } 00520 } 00521 return true; 00522 } 00523 00533 function is_siteadmin($user_or_id = null) { 00534 global $CFG, $USER; 00535 00536 if ($user_or_id === null) { 00537 $user_or_id = $USER; 00538 } 00539 00540 if (empty($user_or_id)) { 00541 return false; 00542 } 00543 if (!empty($user_or_id->id)) { 00544 $userid = $user_or_id->id; 00545 } else { 00546 $userid = $user_or_id; 00547 } 00548 00549 $siteadmins = explode(',', $CFG->siteadmins); 00550 return in_array($userid, $siteadmins); 00551 } 00552 00560 function has_coursecontact_role($userid) { 00561 global $DB, $CFG; 00562 00563 if (empty($CFG->coursecontact)) { 00564 return false; 00565 } 00566 $sql = "SELECT 1 00567 FROM {role_assignments} 00568 WHERE userid = :userid AND roleid IN ($CFG->coursecontact)"; 00569 return $DB->record_exists_sql($sql, array('userid'=>$userid)); 00570 } 00571 00606 function has_capability_in_accessdata($capability, context $context, array &$accessdata) { 00607 global $CFG; 00608 00609 // Build $paths as a list of current + all parent "paths" with order bottom-to-top 00610 $path = $context->path; 00611 $paths = array($path); 00612 while($path = rtrim($path, '0123456789')) { 00613 $path = rtrim($path, '/'); 00614 if ($path === '') { 00615 break; 00616 } 00617 $paths[] = $path; 00618 } 00619 00620 $roles = array(); 00621 $switchedrole = false; 00622 00623 // Find out if role switched 00624 if (!empty($accessdata['rsw'])) { 00625 // From the bottom up... 00626 foreach ($paths as $path) { 00627 if (isset($accessdata['rsw'][$path])) { 00628 // Found a switchrole assignment - check for that role _plus_ the default user role 00629 $roles = array($accessdata['rsw'][$path]=>null, $CFG->defaultuserroleid=>null); 00630 $switchedrole = true; 00631 break; 00632 } 00633 } 00634 } 00635 00636 if (!$switchedrole) { 00637 // get all users roles in this context and above 00638 foreach ($paths as $path) { 00639 if (isset($accessdata['ra'][$path])) { 00640 foreach ($accessdata['ra'][$path] as $roleid) { 00641 $roles[$roleid] = null; 00642 } 00643 } 00644 } 00645 } 00646 00647 // Now find out what access is given to each role, going bottom-->up direction 00648 $allowed = false; 00649 foreach ($roles as $roleid => $ignored) { 00650 foreach ($paths as $path) { 00651 if (isset($accessdata['rdef']["{$path}:$roleid"][$capability])) { 00652 $perm = (int)$accessdata['rdef']["{$path}:$roleid"][$capability]; 00653 if ($perm === CAP_PROHIBIT) { 00654 // any CAP_PROHIBIT found means no permission for the user 00655 return false; 00656 } 00657 if (is_null($roles[$roleid])) { 00658 $roles[$roleid] = $perm; 00659 } 00660 } 00661 } 00662 // CAP_ALLOW in any role means the user has a permission, we continue only to detect prohibits 00663 $allowed = ($allowed or $roles[$roleid] === CAP_ALLOW); 00664 } 00665 00666 return $allowed; 00667 } 00668 00688 function require_capability($capability, context $context, $userid = null, $doanything = true, 00689 $errormessage = 'nopermissions', $stringfile = '') { 00690 if (!has_capability($capability, $context, $userid, $doanything)) { 00691 throw new required_capability_exception($context, $capability, $errormessage, $stringfile); 00692 } 00693 } 00694 00710 function get_user_access_sitewide($userid) { 00711 global $CFG, $DB, $ACCESSLIB_PRIVATE; 00712 00713 /* Get in a few cheap DB queries... 00714 * - role assignments 00715 * - relevant role caps 00716 * - above and within this user's RAs 00717 * - below this user's RAs - limited to course level 00718 */ 00719 00720 // raparents collects paths & roles we need to walk up the parenthood to build the minimal rdef 00721 $raparents = array(); 00722 $accessdata = get_empty_accessdata(); 00723 00724 // start with the default role 00725 if (!empty($CFG->defaultuserroleid)) { 00726 $syscontext = context_system::instance(); 00727 $accessdata['ra'][$syscontext->path][(int)$CFG->defaultuserroleid] = (int)$CFG->defaultuserroleid; 00728 $raparents[$CFG->defaultuserroleid][$syscontext->id] = $syscontext->id; 00729 } 00730 00731 // load the "default frontpage role" 00732 if (!empty($CFG->defaultfrontpageroleid)) { 00733 $frontpagecontext = context_course::instance(get_site()->id); 00734 if ($frontpagecontext->path) { 00735 $accessdata['ra'][$frontpagecontext->path][(int)$CFG->defaultfrontpageroleid] = (int)$CFG->defaultfrontpageroleid; 00736 $raparents[$CFG->defaultfrontpageroleid][$frontpagecontext->id] = $frontpagecontext->id; 00737 } 00738 } 00739 00740 // preload every assigned role at and above course context 00741 $sql = "SELECT ctx.path, ra.roleid, ra.contextid 00742 FROM {role_assignments} ra 00743 JOIN {context} ctx 00744 ON ctx.id = ra.contextid 00745 LEFT JOIN {block_instances} bi 00746 ON (ctx.contextlevel = ".CONTEXT_BLOCK." AND bi.id = ctx.instanceid) 00747 LEFT JOIN {context} bpctx 00748 ON (bpctx.id = bi.parentcontextid) 00749 WHERE ra.userid = :userid 00750 AND (ctx.contextlevel <= ".CONTEXT_COURSE." OR bpctx.contextlevel < ".CONTEXT_COURSE.")"; 00751 $params = array('userid'=>$userid); 00752 $rs = $DB->get_recordset_sql($sql, $params); 00753 foreach ($rs as $ra) { 00754 // RAs leafs are arrays to support multi-role assignments... 00755 $accessdata['ra'][$ra->path][(int)$ra->roleid] = (int)$ra->roleid; 00756 $raparents[$ra->roleid][$ra->contextid] = $ra->contextid; 00757 } 00758 $rs->close(); 00759 00760 if (empty($raparents)) { 00761 return $accessdata; 00762 } 00763 00764 // now get overrides of interesting roles in all interesting child contexts 00765 // hopefully we will not run out of SQL limits here, 00766 // users would have to have very many roles at/above course context... 00767 $sqls = array(); 00768 $params = array(); 00769 00770 static $cp = 0; 00771 foreach ($raparents as $roleid=>$ras) { 00772 $cp++; 00773 list($sqlcids, $cids) = $DB->get_in_or_equal($ras, SQL_PARAMS_NAMED, 'c'.$cp.'_'); 00774 $params = array_merge($params, $cids); 00775 $params['r'.$cp] = $roleid; 00776 $sqls[] = "(SELECT ctx.path, rc.roleid, rc.capability, rc.permission 00777 FROM {role_capabilities} rc 00778 JOIN {context} ctx 00779 ON (ctx.id = rc.contextid) 00780 JOIN {context} pctx 00781 ON (pctx.id $sqlcids 00782 AND (ctx.id = pctx.id 00783 OR ctx.path LIKE ".$DB->sql_concat('pctx.path',"'/%'")." 00784 OR pctx.path LIKE ".$DB->sql_concat('ctx.path',"'/%'").")) 00785 LEFT JOIN {block_instances} bi 00786 ON (ctx.contextlevel = ".CONTEXT_BLOCK." AND bi.id = ctx.instanceid) 00787 LEFT JOIN {context} bpctx 00788 ON (bpctx.id = bi.parentcontextid) 00789 WHERE rc.roleid = :r{$cp} 00790 AND (ctx.contextlevel <= ".CONTEXT_COURSE." OR bpctx.contextlevel < ".CONTEXT_COURSE.") 00791 )"; 00792 } 00793 00794 // fixed capability order is necessary for rdef dedupe 00795 $rs = $DB->get_recordset_sql(implode("\nUNION\n", $sqls). "ORDER BY capability", $params); 00796 00797 foreach ($rs as $rd) { 00798 $k = $rd->path.':'.$rd->roleid; 00799 $accessdata['rdef'][$k][$rd->capability] = (int)$rd->permission; 00800 } 00801 $rs->close(); 00802 00803 // share the role definitions 00804 foreach ($accessdata['rdef'] as $k=>$unused) { 00805 if (!isset($ACCESSLIB_PRIVATE->rolepermissions[$k])) { 00806 $ACCESSLIB_PRIVATE->rolepermissions[$k] = $accessdata['rdef'][$k]; 00807 } 00808 $accessdata['rdef_count']++; 00809 $accessdata['rdef'][$k] =& $ACCESSLIB_PRIVATE->rolepermissions[$k]; 00810 } 00811 00812 return $accessdata; 00813 } 00814 00826 function load_course_context($userid, context_course $coursecontext, &$accessdata) { 00827 global $DB, $CFG, $ACCESSLIB_PRIVATE; 00828 00829 if (empty($coursecontext->path)) { 00830 // weird, this should not happen 00831 return; 00832 } 00833 00834 if (isset($accessdata['loaded'][$coursecontext->instanceid])) { 00835 // already loaded, great! 00836 return; 00837 } 00838 00839 $roles = array(); 00840 00841 if (empty($userid)) { 00842 if (!empty($CFG->notloggedinroleid)) { 00843 $roles[$CFG->notloggedinroleid] = $CFG->notloggedinroleid; 00844 } 00845 00846 } else if (isguestuser($userid)) { 00847 if ($guestrole = get_guest_role()) { 00848 $roles[$guestrole->id] = $guestrole->id; 00849 } 00850 00851 } else { 00852 // Interesting role assignments at, above and below the course context 00853 list($parentsaself, $params) = $DB->get_in_or_equal($coursecontext->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'pc_'); 00854 $params['userid'] = $userid; 00855 $params['children'] = $coursecontext->path."/%"; 00856 $sql = "SELECT ra.*, ctx.path 00857 FROM {role_assignments} ra 00858 JOIN {context} ctx ON ra.contextid = ctx.id 00859 WHERE ra.userid = :userid AND (ctx.id $parentsaself OR ctx.path LIKE :children)"; 00860 $rs = $DB->get_recordset_sql($sql, $params); 00861 00862 // add missing role definitions 00863 foreach ($rs as $ra) { 00864 $accessdata['ra'][$ra->path][(int)$ra->roleid] = (int)$ra->roleid; 00865 $roles[$ra->roleid] = $ra->roleid; 00866 } 00867 $rs->close(); 00868 00869 // add the "default frontpage role" when on the frontpage 00870 if (!empty($CFG->defaultfrontpageroleid)) { 00871 $frontpagecontext = context_course::instance(get_site()->id); 00872 if ($frontpagecontext->id == $coursecontext->id) { 00873 $roles[$CFG->defaultfrontpageroleid] = $CFG->defaultfrontpageroleid; 00874 } 00875 } 00876 00877 // do not forget the default role 00878 if (!empty($CFG->defaultuserroleid)) { 00879 $roles[$CFG->defaultuserroleid] = $CFG->defaultuserroleid; 00880 } 00881 } 00882 00883 if (!$roles) { 00884 // weird, default roles must be missing... 00885 $accessdata['loaded'][$coursecontext->instanceid] = 1; 00886 return; 00887 } 00888 00889 // now get overrides of interesting roles in all interesting contexts (this course + children + parents) 00890 $params = array('c'=>$coursecontext->id); 00891 list($parentsaself, $rparams) = $DB->get_in_or_equal($coursecontext->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'pc_'); 00892 $params = array_merge($params, $rparams); 00893 list($roleids, $rparams) = $DB->get_in_or_equal($roles, SQL_PARAMS_NAMED, 'r_'); 00894 $params = array_merge($params, $rparams); 00895 00896 $sql = "SELECT ctx.path, rc.roleid, rc.capability, rc.permission 00897 FROM {role_capabilities} rc 00898 JOIN {context} ctx 00899 ON (ctx.id = rc.contextid) 00900 JOIN {context} cctx 00901 ON (cctx.id = :c 00902 AND (ctx.id $parentsaself OR ctx.path LIKE ".$DB->sql_concat('cctx.path',"'/%'").")) 00903 WHERE rc.roleid $roleids 00904 ORDER BY rc.capability"; // fixed capability order is necessary for rdef dedupe 00905 $rs = $DB->get_recordset_sql($sql, $params); 00906 00907 $newrdefs = array(); 00908 foreach ($rs as $rd) { 00909 $k = $rd->path.':'.$rd->roleid; 00910 if (isset($accessdata['rdef'][$k])) { 00911 continue; 00912 } 00913 $newrdefs[$k][$rd->capability] = (int)$rd->permission; 00914 } 00915 $rs->close(); 00916 00917 // share new role definitions 00918 foreach ($newrdefs as $k=>$unused) { 00919 if (!isset($ACCESSLIB_PRIVATE->rolepermissions[$k])) { 00920 $ACCESSLIB_PRIVATE->rolepermissions[$k] = $newrdefs[$k]; 00921 } 00922 $accessdata['rdef_count']++; 00923 $accessdata['rdef'][$k] =& $ACCESSLIB_PRIVATE->rolepermissions[$k]; 00924 } 00925 00926 $accessdata['loaded'][$coursecontext->instanceid] = 1; 00927 00928 // we want to deduplicate the USER->access from time to time, this looks like a good place, 00929 // because we have to do it before the end of session 00930 dedupe_user_access(); 00931 } 00932 00946 function load_role_access_by_context($roleid, context $context, &$accessdata) { 00947 global $DB, $ACCESSLIB_PRIVATE; 00948 00949 /* Get the relevant rolecaps into rdef 00950 * - relevant role caps 00951 * - at ctx and above 00952 * - below this ctx 00953 */ 00954 00955 if (empty($context->path)) { 00956 // weird, this should not happen 00957 return; 00958 } 00959 00960 list($parentsaself, $params) = $DB->get_in_or_equal($context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'pc_'); 00961 $params['roleid'] = $roleid; 00962 $params['childpath'] = $context->path.'/%'; 00963 00964 $sql = "SELECT ctx.path, rc.capability, rc.permission 00965 FROM {role_capabilities} rc 00966 JOIN {context} ctx ON (rc.contextid = ctx.id) 00967 WHERE rc.roleid = :roleid AND (ctx.id $parentsaself OR ctx.path LIKE :childpath) 00968 ORDER BY rc.capability"; // fixed capability order is necessary for rdef dedupe 00969 $rs = $DB->get_recordset_sql($sql, $params); 00970 00971 $newrdefs = array(); 00972 foreach ($rs as $rd) { 00973 $k = $rd->path.':'.$roleid; 00974 if (isset($accessdata['rdef'][$k])) { 00975 continue; 00976 } 00977 $newrdefs[$k][$rd->capability] = (int)$rd->permission; 00978 } 00979 $rs->close(); 00980 00981 // share new role definitions 00982 foreach ($newrdefs as $k=>$unused) { 00983 if (!isset($ACCESSLIB_PRIVATE->rolepermissions[$k])) { 00984 $ACCESSLIB_PRIVATE->rolepermissions[$k] = $newrdefs[$k]; 00985 } 00986 $accessdata['rdef_count']++; 00987 $accessdata['rdef'][$k] =& $ACCESSLIB_PRIVATE->rolepermissions[$k]; 00988 } 00989 } 00990 00997 function get_empty_accessdata() { 00998 $accessdata = array(); // named list 00999 $accessdata['ra'] = array(); 01000 $accessdata['rdef'] = array(); 01001 $accessdata['rdef_count'] = 0; // this bloody hack is necessary because count($array) is slooooowwww in PHP 01002 $accessdata['rdef_lcc'] = 0; // rdef_count during the last compression 01003 $accessdata['loaded'] = array(); // loaded course contexts 01004 $accessdata['time'] = time(); 01005 $accessdata['rsw'] = array(); 01006 01007 return $accessdata; 01008 } 01009 01018 function get_user_accessdata($userid, $preloadonly=false) { 01019 global $CFG, $ACCESSLIB_PRIVATE, $USER; 01020 01021 if (!empty($USER->acces['rdef']) and empty($ACCESSLIB_PRIVATE->rolepermissions)) { 01022 // share rdef from USER session with rolepermissions cache in order to conserve memory 01023 foreach($USER->acces['rdef'] as $k=>$v) { 01024 $ACCESSLIB_PRIVATE->rolepermissions[$k] =& $USER->acces['rdef'][$k]; 01025 } 01026 $ACCESSLIB_PRIVATE->accessdatabyuser[$USER->id] = $USER->acces; 01027 } 01028 01029 if (!isset($ACCESSLIB_PRIVATE->accessdatabyuser[$userid])) { 01030 if (empty($userid)) { 01031 if (!empty($CFG->notloggedinroleid)) { 01032 $accessdata = get_role_access($CFG->notloggedinroleid); 01033 } else { 01034 // weird 01035 return get_empty_accessdata(); 01036 } 01037 01038 } else if (isguestuser($userid)) { 01039 if ($guestrole = get_guest_role()) { 01040 $accessdata = get_role_access($guestrole->id); 01041 } else { 01042 //weird 01043 return get_empty_accessdata(); 01044 } 01045 01046 } else { 01047 $accessdata = get_user_access_sitewide($userid); // includes default role and frontpage role 01048 } 01049 01050 $ACCESSLIB_PRIVATE->accessdatabyuser[$userid] = $accessdata; 01051 } 01052 01053 if ($preloadonly) { 01054 return; 01055 } else { 01056 return $ACCESSLIB_PRIVATE->accessdatabyuser[$userid]; 01057 } 01058 } 01059 01067 function dedupe_user_access() { 01068 global $USER; 01069 01070 if (CLI_SCRIPT) { 01071 // no session in CLI --> no compression necessary 01072 return; 01073 } 01074 01075 if (empty($USER->access['rdef_count'])) { 01076 // weird, this should not happen 01077 return; 01078 } 01079 01080 // the rdef is growing only, we never remove stuff from it, the rdef_lcc helps us to detect new stuff in rdef 01081 if ($USER->access['rdef_count'] - $USER->access['rdef_lcc'] > 10) { 01082 // do not compress after each change, wait till there is more stuff to be done 01083 return; 01084 } 01085 01086 $hashmap = array(); 01087 foreach ($USER->access['rdef'] as $k=>$def) { 01088 $hash = sha1(serialize($def)); 01089 if (isset($hashmap[$hash])) { 01090 $USER->access['rdef'][$k] =& $hashmap[$hash]; 01091 } else { 01092 $hashmap[$hash] =& $USER->access['rdef'][$k]; 01093 } 01094 } 01095 01096 $USER->access['rdef_lcc'] = $USER->access['rdef_count']; 01097 } 01098 01109 function load_all_capabilities() { 01110 global $USER; 01111 01112 // roles not installed yet - we are in the middle of installation 01113 if (during_initial_install()) { 01114 return; 01115 } 01116 01117 if (!isset($USER->id)) { 01118 // this should not happen 01119 $USER->id = 0; 01120 } 01121 01122 unset($USER->access); 01123 $USER->access = get_user_accessdata($USER->id); 01124 01125 // deduplicate the overrides to minimize session size 01126 dedupe_user_access(); 01127 01128 // Clear to force a refresh 01129 unset($USER->mycourses); 01130 01131 // init/reset internal enrol caches - active course enrolments and temp access 01132 $USER->enrol = array('enrolled'=>array(), 'tempguest'=>array()); 01133 } 01134 01148 function reload_all_capabilities() { 01149 global $USER, $DB, $ACCESSLIB_PRIVATE; 01150 01151 // copy switchroles 01152 $sw = array(); 01153 if (!empty($USER->access['rsw'])) { 01154 $sw = $USER->access['rsw']; 01155 } 01156 01157 accesslib_clear_all_caches(true); 01158 unset($USER->access); 01159 $ACCESSLIB_PRIVATE->dirtycontexts = array(); // prevent dirty flags refetching on this page 01160 01161 load_all_capabilities(); 01162 01163 foreach ($sw as $path => $roleid) { 01164 if ($record = $DB->get_record('context', array('path'=>$path))) { 01165 $context = context::instance_by_id($record->id); 01166 role_switch($roleid, $context); 01167 } 01168 } 01169 } 01170 01181 function load_temp_course_role(context_course $coursecontext, $roleid) { 01182 global $USER, $SITE; 01183 01184 if (empty($roleid)) { 01185 debugging('invalid role specified in load_temp_course_role()'); 01186 return; 01187 } 01188 01189 if ($coursecontext->instanceid == $SITE->id) { 01190 debugging('Can not use temp roles on the frontpage'); 01191 return; 01192 } 01193 01194 if (!isset($USER->access)) { 01195 load_all_capabilities(); 01196 } 01197 01198 $coursecontext->reload_if_dirty(); 01199 01200 if (isset($USER->access['ra'][$coursecontext->path][$roleid])) { 01201 return; 01202 } 01203 01204 // load course stuff first 01205 load_course_context($USER->id, $coursecontext, $USER->access); 01206 01207 $USER->access['ra'][$coursecontext->path][(int)$roleid] = (int)$roleid; 01208 01209 load_role_access_by_context($roleid, $coursecontext, $USER->access); 01210 } 01211 01219 function remove_temp_course_roles(context_course $coursecontext) { 01220 global $DB, $USER, $SITE; 01221 01222 if ($coursecontext->instanceid == $SITE->id) { 01223 debugging('Can not use temp roles on the frontpage'); 01224 return; 01225 } 01226 01227 if (empty($USER->access['ra'][$coursecontext->path])) { 01228 //no roles here, weird 01229 return; 01230 } 01231 01232 $sql = "SELECT DISTINCT ra.roleid AS id 01233 FROM {role_assignments} ra 01234 WHERE ra.contextid = :contextid AND ra.userid = :userid"; 01235 $ras = $DB->get_records_sql($sql, array('contextid'=>$coursecontext->id, 'userid'=>$USER->id)); 01236 01237 $USER->access['ra'][$coursecontext->path] = array(); 01238 foreach($ras as $r) { 01239 $USER->access['ra'][$coursecontext->path][(int)$r->id] = (int)$r->id; 01240 } 01241 } 01242 01248 function get_role_archetypes() { 01249 return array( 01250 'manager' => 'manager', 01251 'coursecreator' => 'coursecreator', 01252 'editingteacher' => 'editingteacher', 01253 'teacher' => 'teacher', 01254 'student' => 'student', 01255 'guest' => 'guest', 01256 'user' => 'user', 01257 'frontpage' => 'frontpage' 01258 ); 01259 } 01260 01275 function assign_legacy_capabilities($capability, $legacyperms) { 01276 01277 $archetypes = get_role_archetypes(); 01278 01279 foreach ($legacyperms as $type => $perm) { 01280 01281 $systemcontext = context_system::instance(); 01282 if ($type === 'admin') { 01283 debugging('Legacy type admin in access.php was renamed to manager, please update the code.'); 01284 $type = 'manager'; 01285 } 01286 01287 if (!array_key_exists($type, $archetypes)) { 01288 print_error('invalidlegacy', '', '', $type); 01289 } 01290 01291 if ($roles = get_archetype_roles($type)) { 01292 foreach ($roles as $role) { 01293 // Assign a site level capability. 01294 if (!assign_capability($capability, $perm, $role->id, $systemcontext->id)) { 01295 return false; 01296 } 01297 } 01298 } 01299 } 01300 return true; 01301 } 01302 01310 function is_safe_capability($capability) { 01311 return !((RISK_DATALOSS | RISK_MANAGETRUST | RISK_CONFIG | RISK_XSS | RISK_PERSONAL) & $capability->riskbitmask); 01312 } 01313 01322 function get_local_override($roleid, $contextid, $capability) { 01323 global $DB; 01324 return $DB->get_record('role_capabilities', array('roleid'=>$roleid, 'capability'=>$capability, 'contextid'=>$contextid)); 01325 } 01326 01333 function get_context_info_array($contextid) { 01334 global $DB; 01335 01336 $context = context::instance_by_id($contextid, MUST_EXIST); 01337 $course = null; 01338 $cm = null; 01339 01340 if ($context->contextlevel == CONTEXT_COURSE) { 01341 $course = $DB->get_record('course', array('id'=>$context->instanceid), '*', MUST_EXIST); 01342 01343 } else if ($context->contextlevel == CONTEXT_MODULE) { 01344 $cm = get_coursemodule_from_id('', $context->instanceid, 0, false, MUST_EXIST); 01345 $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST); 01346 01347 } else if ($context->contextlevel == CONTEXT_BLOCK) { 01348 $parent = $context->get_parent_context(); 01349 01350 if ($parent->contextlevel == CONTEXT_COURSE) { 01351 $course = $DB->get_record('course', array('id'=>$parent->instanceid), '*', MUST_EXIST); 01352 } else if ($parent->contextlevel == CONTEXT_MODULE) { 01353 $cm = get_coursemodule_from_id('', $parent->instanceid, 0, false, MUST_EXIST); 01354 $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST); 01355 } 01356 } 01357 01358 return array($context, $course, $cm); 01359 } 01360 01370 function create_role($name, $shortname, $description, $archetype = '') { 01371 global $DB; 01372 01373 if (strpos($archetype, 'moodle/legacy:') !== false) { 01374 throw new coding_exception('Use new role archetype parameter in create_role() instead of old legacy capabilities.'); 01375 } 01376 01377 // verify role archetype actually exists 01378 $archetypes = get_role_archetypes(); 01379 if (empty($archetypes[$archetype])) { 01380 $archetype = ''; 01381 } 01382 01383 // Insert the role record. 01384 $role = new stdClass(); 01385 $role->name = $name; 01386 $role->shortname = $shortname; 01387 $role->description = $description; 01388 $role->archetype = $archetype; 01389 01390 //find free sortorder number 01391 $role->sortorder = $DB->get_field('role', 'MAX(sortorder) + 1', array()); 01392 if (empty($role->sortorder)) { 01393 $role->sortorder = 1; 01394 } 01395 $id = $DB->insert_record('role', $role); 01396 01397 return $id; 01398 } 01399 01406 function delete_role($roleid) { 01407 global $DB; 01408 01409 // first unssign all users 01410 role_unassign_all(array('roleid'=>$roleid)); 01411 01412 // cleanup all references to this role, ignore errors 01413 $DB->delete_records('role_capabilities', array('roleid'=>$roleid)); 01414 $DB->delete_records('role_allow_assign', array('roleid'=>$roleid)); 01415 $DB->delete_records('role_allow_assign', array('allowassign'=>$roleid)); 01416 $DB->delete_records('role_allow_override', array('roleid'=>$roleid)); 01417 $DB->delete_records('role_allow_override', array('allowoverride'=>$roleid)); 01418 $DB->delete_records('role_names', array('roleid'=>$roleid)); 01419 $DB->delete_records('role_context_levels', array('roleid'=>$roleid)); 01420 01421 // finally delete the role itself 01422 // get this before the name is gone for logging 01423 $rolename = $DB->get_field('role', 'name', array('id'=>$roleid)); 01424 01425 $DB->delete_records('role', array('id'=>$roleid)); 01426 01427 add_to_log(SITEID, 'role', 'delete', 'admin/roles/action=delete&roleid='.$roleid, $rolename, ''); 01428 01429 return true; 01430 } 01431 01444 function assign_capability($capability, $permission, $roleid, $contextid, $overwrite = false) { 01445 global $USER, $DB; 01446 01447 if ($contextid instanceof context) { 01448 $context = $contextid; 01449 } else { 01450 $context = context::instance_by_id($contextid); 01451 } 01452 01453 if (empty($permission) || $permission == CAP_INHERIT) { // if permission is not set 01454 unassign_capability($capability, $roleid, $context->id); 01455 return true; 01456 } 01457 01458 $existing = $DB->get_record('role_capabilities', array('contextid'=>$context->id, 'roleid'=>$roleid, 'capability'=>$capability)); 01459 01460 if ($existing and !$overwrite) { // We want to keep whatever is there already 01461 return true; 01462 } 01463 01464 $cap = new stdClass(); 01465 $cap->contextid = $context->id; 01466 $cap->roleid = $roleid; 01467 $cap->capability = $capability; 01468 $cap->permission = $permission; 01469 $cap->timemodified = time(); 01470 $cap->modifierid = empty($USER->id) ? 0 : $USER->id; 01471 01472 if ($existing) { 01473 $cap->id = $existing->id; 01474 $DB->update_record('role_capabilities', $cap); 01475 } else { 01476 if ($DB->record_exists('context', array('id'=>$context->id))) { 01477 $DB->insert_record('role_capabilities', $cap); 01478 } 01479 } 01480 return true; 01481 } 01482 01493 function unassign_capability($capability, $roleid, $contextid = null) { 01494 global $DB; 01495 01496 if (!empty($contextid)) { 01497 if ($contextid instanceof context) { 01498 $context = $contextid; 01499 } else { 01500 $context = context::instance_by_id($contextid); 01501 } 01502 // delete from context rel, if this is the last override in this context 01503 $DB->delete_records('role_capabilities', array('capability'=>$capability, 'roleid'=>$roleid, 'contextid'=>$context->id)); 01504 } else { 01505 $DB->delete_records('role_capabilities', array('capability'=>$capability, 'roleid'=>$roleid)); 01506 } 01507 return true; 01508 } 01509 01523 function get_roles_with_capability($capability, $permission = null, $context = null) { 01524 global $DB; 01525 01526 if ($context) { 01527 $contexts = $context->get_parent_context_ids(true); 01528 list($insql, $params) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED, 'ctx'); 01529 $contextsql = "AND rc.contextid $insql"; 01530 } else { 01531 $params = array(); 01532 $contextsql = ''; 01533 } 01534 01535 if ($permission) { 01536 $permissionsql = " AND rc.permission = :permission"; 01537 $params['permission'] = $permission; 01538 } else { 01539 $permissionsql = ''; 01540 } 01541 01542 $sql = "SELECT r.* 01543 FROM {role} r 01544 WHERE r.id IN (SELECT rc.roleid 01545 FROM {role_capabilities} rc 01546 WHERE rc.capability = :capname 01547 $contextsql 01548 $permissionsql)"; 01549 $params['capname'] = $capability; 01550 01551 01552 return $DB->get_records_sql($sql, $params); 01553 } 01554 01566 function role_assign($roleid, $userid, $contextid, $component = '', $itemid = 0, $timemodified = '') { 01567 global $USER, $DB; 01568 01569 // first of all detect if somebody is using old style parameters 01570 if ($contextid === 0 or is_numeric($component)) { 01571 throw new coding_exception('Invalid call to role_assign(), code needs to be updated to use new order of parameters'); 01572 } 01573 01574 // now validate all parameters 01575 if (empty($roleid)) { 01576 throw new coding_exception('Invalid call to role_assign(), roleid can not be empty'); 01577 } 01578 01579 if (empty($userid)) { 01580 throw new coding_exception('Invalid call to role_assign(), userid can not be empty'); 01581 } 01582 01583 if ($itemid) { 01584 if (strpos($component, '_') === false) { 01585 throw new coding_exception('Invalid call to role_assign(), component must start with plugin type such as"enrol_" when itemid specified', 'component:'.$component); 01586 } 01587 } else { 01588 $itemid = 0; 01589 if ($component !== '' and strpos($component, '_') === false) { 01590 throw new coding_exception('Invalid call to role_assign(), invalid component string', 'component:'.$component); 01591 } 01592 } 01593 01594 if (!$DB->record_exists('user', array('id'=>$userid, 'deleted'=>0))) { 01595 throw new coding_exception('User ID does not exist or is deleted!', 'userid:'.$userid); 01596 } 01597 01598 if ($contextid instanceof context) { 01599 $context = $contextid; 01600 } else { 01601 $context = context::instance_by_id($contextid, MUST_EXIST); 01602 } 01603 01604 if (!$timemodified) { 01605 $timemodified = time(); 01606 } 01607 01608 // Check for existing entry 01609 // TODO: Revisit this sql_empty() use once Oracle bindings are improved. MDL-29765 01610 $component = ($component === '') ? $DB->sql_empty() : $component; 01611 $ras = $DB->get_records('role_assignments', array('roleid'=>$roleid, 'contextid'=>$context->id, 'userid'=>$userid, 'component'=>$component, 'itemid'=>$itemid), 'id'); 01612 01613 if ($ras) { 01614 // role already assigned - this should not happen 01615 if (count($ras) > 1) { 01616 // very weird - remove all duplicates! 01617 $ra = array_shift($ras); 01618 foreach ($ras as $r) { 01619 $DB->delete_records('role_assignments', array('id'=>$r->id)); 01620 } 01621 } else { 01622 $ra = reset($ras); 01623 } 01624 01625 // actually there is no need to update, reset anything or trigger any event, so just return 01626 return $ra->id; 01627 } 01628 01629 // Create a new entry 01630 $ra = new stdClass(); 01631 $ra->roleid = $roleid; 01632 $ra->contextid = $context->id; 01633 $ra->userid = $userid; 01634 $ra->component = $component; 01635 $ra->itemid = $itemid; 01636 $ra->timemodified = $timemodified; 01637 $ra->modifierid = empty($USER->id) ? 0 : $USER->id; 01638 01639 $ra->id = $DB->insert_record('role_assignments', $ra); 01640 01641 // mark context as dirty - again expensive, but needed 01642 $context->mark_dirty(); 01643 01644 if (!empty($USER->id) && $USER->id == $userid) { 01645 // If the user is the current user, then do full reload of capabilities too. 01646 reload_all_capabilities(); 01647 } 01648 01649 events_trigger('role_assigned', $ra); 01650 01651 return $ra->id; 01652 } 01653 01664 function role_unassign($roleid, $userid, $contextid, $component = '', $itemid = 0) { 01665 // first make sure the params make sense 01666 if ($roleid == 0 or $userid == 0 or $contextid == 0) { 01667 throw new coding_exception('Invalid call to role_unassign(), please use role_unassign_all() when removing multiple role assignments'); 01668 } 01669 01670 if ($itemid) { 01671 if (strpos($component, '_') === false) { 01672 throw new coding_exception('Invalid call to role_assign(), component must start with plugin type such as "enrol_" when itemid specified', 'component:'.$component); 01673 } 01674 } else { 01675 $itemid = 0; 01676 if ($component !== '' and strpos($component, '_') === false) { 01677 throw new coding_exception('Invalid call to role_assign(), invalid component string', 'component:'.$component); 01678 } 01679 } 01680 01681 role_unassign_all(array('roleid'=>$roleid, 'userid'=>$userid, 'contextid'=>$contextid, 'component'=>$component, 'itemid'=>$itemid), false, false); 01682 } 01683 01693 function role_unassign_all(array $params, $subcontexts = false, $includemanual = false) { 01694 global $USER, $CFG, $DB; 01695 01696 if (!$params) { 01697 throw new coding_exception('Missing parameters in role_unsassign_all() call'); 01698 } 01699 01700 $allowed = array('roleid', 'userid', 'contextid', 'component', 'itemid'); 01701 foreach ($params as $key=>$value) { 01702 if (!in_array($key, $allowed)) { 01703 throw new coding_exception('Unknown role_unsassign_all() parameter key', 'key:'.$key); 01704 } 01705 } 01706 01707 if (isset($params['component']) and $params['component'] !== '' and strpos($params['component'], '_') === false) { 01708 throw new coding_exception('Invalid component paramter in role_unsassign_all() call', 'component:'.$params['component']); 01709 } 01710 01711 if ($includemanual) { 01712 if (!isset($params['component']) or $params['component'] === '') { 01713 throw new coding_exception('include manual parameter requires component parameter in role_unsassign_all() call'); 01714 } 01715 } 01716 01717 if ($subcontexts) { 01718 if (empty($params['contextid'])) { 01719 throw new coding_exception('subcontexts paramtere requires component parameter in role_unsassign_all() call'); 01720 } 01721 } 01722 01723 // TODO: Revisit this sql_empty() use once Oracle bindings are improved. MDL-29765 01724 if (isset($params['component'])) { 01725 $params['component'] = ($params['component'] === '') ? $DB->sql_empty() : $params['component']; 01726 } 01727 $ras = $DB->get_records('role_assignments', $params); 01728 foreach($ras as $ra) { 01729 $DB->delete_records('role_assignments', array('id'=>$ra->id)); 01730 if ($context = context::instance_by_id($ra->contextid, IGNORE_MISSING)) { 01731 // this is a bit expensive but necessary 01732 $context->mark_dirty(); 01734 if (!empty($USER->id) && $USER->id == $ra->userid) { 01735 reload_all_capabilities(); 01736 } 01737 } 01738 events_trigger('role_unassigned', $ra); 01739 } 01740 unset($ras); 01741 01742 // process subcontexts 01743 if ($subcontexts and $context = context::instance_by_id($params['contextid'], IGNORE_MISSING)) { 01744 if ($params['contextid'] instanceof context) { 01745 $context = $params['contextid']; 01746 } else { 01747 $context = context::instance_by_id($params['contextid'], IGNORE_MISSING); 01748 } 01749 01750 if ($context) { 01751 $contexts = $context->get_child_contexts(); 01752 $mparams = $params; 01753 foreach($contexts as $context) { 01754 $mparams['contextid'] = $context->id; 01755 $ras = $DB->get_records('role_assignments', $mparams); 01756 foreach($ras as $ra) { 01757 $DB->delete_records('role_assignments', array('id'=>$ra->id)); 01758 // this is a bit expensive but necessary 01759 $context->mark_dirty(); 01761 if (!empty($USER->id) && $USER->id == $ra->userid) { 01762 reload_all_capabilities(); 01763 } 01764 events_trigger('role_unassigned', $ra); 01765 } 01766 } 01767 } 01768 } 01769 01770 // do this once more for all manual role assignments 01771 if ($includemanual) { 01772 $params['component'] = ''; 01773 role_unassign_all($params, $subcontexts, false); 01774 } 01775 } 01776 01782 function isloggedin() { 01783 global $USER; 01784 01785 return (!empty($USER->id)); 01786 } 01787 01794 function isguestuser($user = null) { 01795 global $USER, $DB, $CFG; 01796 01797 // make sure we have the user id cached in config table, because we are going to use it a lot 01798 if (empty($CFG->siteguest)) { 01799 if (!$guestid = $DB->get_field('user', 'id', array('username'=>'guest', 'mnethostid'=>$CFG->mnet_localhost_id))) { 01800 // guest does not exist yet, weird 01801 return false; 01802 } 01803 set_config('siteguest', $guestid); 01804 } 01805 if ($user === null) { 01806 $user = $USER; 01807 } 01808 01809 if ($user === null) { 01810 // happens when setting the $USER 01811 return false; 01812 01813 } else if (is_numeric($user)) { 01814 return ($CFG->siteguest == $user); 01815 01816 } else if (is_object($user)) { 01817 if (empty($user->id)) { 01818 return false; // not logged in means is not be guest 01819 } else { 01820 return ($CFG->siteguest == $user->id); 01821 } 01822 01823 } else { 01824 throw new coding_exception('Invalid user parameter supplied for isguestuser() function!'); 01825 } 01826 } 01827 01835 function is_guest(context $context, $user = null) { 01836 global $USER; 01837 01838 // first find the course context 01839 $coursecontext = $context->get_course_context(); 01840 01841 // make sure there is a real user specified 01842 if ($user === null) { 01843 $userid = isset($USER->id) ? $USER->id : 0; 01844 } else { 01845 $userid = is_object($user) ? $user->id : $user; 01846 } 01847 01848 if (isguestuser($userid)) { 01849 // can not inspect or be enrolled 01850 return true; 01851 } 01852 01853 if (has_capability('moodle/course:view', $coursecontext, $user)) { 01854 // viewing users appear out of nowhere, they are neither guests nor participants 01855 return false; 01856 } 01857 01858 // consider only real active enrolments here 01859 if (is_enrolled($coursecontext, $user, '', true)) { 01860 return false; 01861 } 01862 01863 return true; 01864 } 01865 01875 function is_viewing(context $context, $user = null, $withcapability = '') { 01876 // first find the course context 01877 $coursecontext = $context->get_course_context(); 01878 01879 if (isguestuser($user)) { 01880 // can not inspect 01881 return false; 01882 } 01883 01884 if (!has_capability('moodle/course:view', $coursecontext, $user)) { 01885 // admins are allowed to inspect courses 01886 return false; 01887 } 01888 01889 if ($withcapability and !has_capability($withcapability, $context, $user)) { 01890 // site admins always have the capability, but the enrolment above blocks 01891 return false; 01892 } 01893 01894 return true; 01895 } 01896 01909 function is_enrolled(context $context, $user = null, $withcapability = '', $onlyactive = false) { 01910 global $USER, $DB; 01911 01912 // first find the course context 01913 $coursecontext = $context->get_course_context(); 01914 01915 // make sure there is a real user specified 01916 if ($user === null) { 01917 $userid = isset($USER->id) ? $USER->id : 0; 01918 } else { 01919 $userid = is_object($user) ? $user->id : $user; 01920 } 01921 01922 if (empty($userid)) { 01923 // not-logged-in! 01924 return false; 01925 } else if (isguestuser($userid)) { 01926 // guest account can not be enrolled anywhere 01927 return false; 01928 } 01929 01930 if ($coursecontext->instanceid == SITEID) { 01931 // everybody participates on frontpage 01932 } else { 01933 // try cached info first - the enrolled flag is set only when active enrolment present 01934 if ($USER->id == $userid) { 01935 $coursecontext->reload_if_dirty(); 01936 if (isset($USER->enrol['enrolled'][$coursecontext->instanceid])) { 01937 if ($USER->enrol['enrolled'][$coursecontext->instanceid] > time()) { 01938 return true; 01939 } 01940 } 01941 } 01942 01943 if ($onlyactive) { 01944 // look for active enrolments only 01945 $until = enrol_get_enrolment_end($coursecontext->instanceid, $userid); 01946 01947 if ($until === false) { 01948 return false; 01949 } 01950 01951 if ($USER->id == $userid) { 01952 if ($until == 0) { 01953 $until = ENROL_MAX_TIMESTAMP; 01954 } 01955 $USER->enrol['enrolled'][$coursecontext->instanceid] = $until; 01956 if (isset($USER->enrol['tempguest'][$coursecontext->instanceid])) { 01957 unset($USER->enrol['tempguest'][$coursecontext->instanceid]); 01958 remove_temp_course_roles($coursecontext); 01959 } 01960 } 01961 01962 } else { 01963 // any enrolment is good for us here, even outdated, disabled or inactive 01964 $sql = "SELECT 'x' 01965 FROM {user_enrolments} ue 01966 JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid) 01967 JOIN {user} u ON u.id = ue.userid 01968 WHERE ue.userid = :userid AND u.deleted = 0"; 01969 $params = array('userid'=>$userid, 'courseid'=>$coursecontext->instanceid); 01970 if (!$DB->record_exists_sql($sql, $params)) { 01971 return false; 01972 } 01973 } 01974 } 01975 01976 if ($withcapability and !has_capability($withcapability, $context, $userid)) { 01977 return false; 01978 } 01979 01980 return true; 01981 } 01982 02004 function can_access_course(stdClass $course, $user = null, $withcapability = '', $onlyactive = false) { 02005 global $DB, $USER; 02006 02007 // this function originally accepted $coursecontext parameter 02008 if ($course instanceof context) { 02009 if ($course instanceof context_course) { 02010 debugging('deprecated context parameter, please use $course record'); 02011 $coursecontext = $course; 02012 $course = $DB->get_record('course', array('id'=>$coursecontext->instanceid)); 02013 } else { 02014 debugging('Invalid context parameter, please use $course record'); 02015 return false; 02016 } 02017 } else { 02018 $coursecontext = context_course::instance($course->id); 02019 } 02020 02021 if (!isset($USER->id)) { 02022 // should never happen 02023 $USER->id = 0; 02024 } 02025 02026 // make sure there is a user specified 02027 if ($user === null) { 02028 $userid = $USER->id; 02029 } else { 02030 $userid = is_object($user) ? $user->id : $user; 02031 } 02032 unset($user); 02033 02034 if ($withcapability and !has_capability($withcapability, $coursecontext, $userid)) { 02035 return false; 02036 } 02037 02038 if ($userid == $USER->id) { 02039 if (!empty($USER->access['rsw'][$coursecontext->path])) { 02040 // the fact that somebody switched role means they can access the course no matter to what role they switched 02041 return true; 02042 } 02043 } 02044 02045 if (!$course->visible and !has_capability('moodle/course:viewhiddencourses', $coursecontext, $userid)) { 02046 return false; 02047 } 02048 02049 if (is_viewing($coursecontext, $userid)) { 02050 return true; 02051 } 02052 02053 if ($userid != $USER->id) { 02054 // for performance reasons we do not verify temporary guest access for other users, sorry... 02055 return is_enrolled($coursecontext, $userid, '', $onlyactive); 02056 } 02057 02058 // === from here we deal only with $USER === 02059 02060 $coursecontext->reload_if_dirty(); 02061 02062 if (isset($USER->enrol['enrolled'][$course->id])) { 02063 if ($USER->enrol['enrolled'][$course->id] > time()) { 02064 return true; 02065 } 02066 } 02067 if (isset($USER->enrol['tempguest'][$course->id])) { 02068 if ($USER->enrol['tempguest'][$course->id] > time()) { 02069 return true; 02070 } 02071 } 02072 02073 if (is_enrolled($coursecontext, $USER, '', $onlyactive)) { 02074 return true; 02075 } 02076 02077 // if not enrolled try to gain temporary guest access 02078 $instances = $DB->get_records('enrol', array('courseid'=>$course->id, 'status'=>ENROL_INSTANCE_ENABLED), 'sortorder, id ASC'); 02079 $enrols = enrol_get_plugins(true); 02080 foreach($instances as $instance) { 02081 if (!isset($enrols[$instance->enrol])) { 02082 continue; 02083 } 02084 // Get a duration for the guest access, a timestamp in the future, 0 (always) or false. 02085 $until = $enrols[$instance->enrol]->try_guestaccess($instance); 02086 if ($until !== false and $until > time()) { 02087 $USER->enrol['tempguest'][$course->id] = $until; 02088 return true; 02089 } 02090 } 02091 if (isset($USER->enrol['tempguest'][$course->id])) { 02092 unset($USER->enrol['tempguest'][$course->id]); 02093 remove_temp_course_roles($coursecontext); 02094 } 02095 02096 return false; 02097 } 02098 02111 function get_enrolled_sql(context $context, $withcapability = '', $groupid = 0, $onlyactive = false) { 02112 global $DB, $CFG; 02113 02114 // use unique prefix just in case somebody makes some SQL magic with the result 02115 static $i = 0; 02116 $i++; 02117 $prefix = 'eu'.$i.'_'; 02118 02119 // first find the course context 02120 $coursecontext = $context->get_course_context(); 02121 02122 $isfrontpage = ($coursecontext->instanceid == SITEID); 02123 02124 $joins = array(); 02125 $wheres = array(); 02126 $params = array(); 02127 02128 list($contextids, $contextpaths) = get_context_info_list($context); 02129 02130 // get all relevant capability info for all roles 02131 if ($withcapability) { 02132 list($incontexts, $cparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, 'ctx'); 02133 $cparams['cap'] = $withcapability; 02134 02135 $defs = array(); 02136 $sql = "SELECT rc.id, rc.roleid, rc.permission, ctx.path 02137 FROM {role_capabilities} rc 02138 JOIN {context} ctx on rc.contextid = ctx.id 02139 WHERE rc.contextid $incontexts AND rc.capability = :cap"; 02140 $rcs = $DB->get_records_sql($sql, $cparams); 02141 foreach ($rcs as $rc) { 02142 $defs[$rc->path][$rc->roleid] = $rc->permission; 02143 } 02144 02145 $access = array(); 02146 if (!empty($defs)) { 02147 foreach ($contextpaths as $path) { 02148 if (empty($defs[$path])) { 02149 continue; 02150 } 02151 foreach($defs[$path] as $roleid => $perm) { 02152 if ($perm == CAP_PROHIBIT) { 02153 $access[$roleid] = CAP_PROHIBIT; 02154 continue; 02155 } 02156 if (!isset($access[$roleid])) { 02157 $access[$roleid] = (int)$perm; 02158 } 02159 } 02160 } 02161 } 02162 02163 unset($defs); 02164 02165 // make lists of roles that are needed and prohibited 02166 $needed = array(); // one of these is enough 02167 $prohibited = array(); // must not have any of these 02168 foreach ($access as $roleid => $perm) { 02169 if ($perm == CAP_PROHIBIT) { 02170 unset($needed[$roleid]); 02171 $prohibited[$roleid] = true; 02172 } else if ($perm == CAP_ALLOW and empty($prohibited[$roleid])) { 02173 $needed[$roleid] = true; 02174 } 02175 } 02176 02177 $defaultuserroleid = isset($CFG->defaultuserroleid) ? $CFG->defaultuserroleid : 0; 02178 $defaultfrontpageroleid = isset($CFG->defaultfrontpageroleid) ? $CFG->defaultfrontpageroleid : 0; 02179 02180 $nobody = false; 02181 02182 if ($isfrontpage) { 02183 if (!empty($prohibited[$defaultuserroleid]) or !empty($prohibited[$defaultfrontpageroleid])) { 02184 $nobody = true; 02185 } else if (!empty($needed[$defaultuserroleid]) or !empty($needed[$defaultfrontpageroleid])) { 02186 // everybody not having prohibit has the capability 02187 $needed = array(); 02188 } else if (empty($needed)) { 02189 $nobody = true; 02190 } 02191 } else { 02192 if (!empty($prohibited[$defaultuserroleid])) { 02193 $nobody = true; 02194 } else if (!empty($needed[$defaultuserroleid])) { 02195 // everybody not having prohibit has the capability 02196 $needed = array(); 02197 } else if (empty($needed)) { 02198 $nobody = true; 02199 } 02200 } 02201 02202 if ($nobody) { 02203 // nobody can match so return some SQL that does not return any results 02204 $wheres[] = "1 = 2"; 02205 02206 } else { 02207 02208 if ($needed) { 02209 $ctxids = implode(',', $contextids); 02210 $roleids = implode(',', array_keys($needed)); 02211 $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))"; 02212 } 02213 02214 if ($prohibited) { 02215 $ctxids = implode(',', $contextids); 02216 $roleids = implode(',', array_keys($prohibited)); 02217 $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))"; 02218 $wheres[] = "{$prefix}ra4.id IS NULL"; 02219 } 02220 02221 if ($groupid) { 02222 $joins[] = "JOIN {groups_members} {$prefix}gm ON ({$prefix}gm.userid = {$prefix}u.id AND {$prefix}gm.groupid = :{$prefix}gmid)"; 02223 $params["{$prefix}gmid"] = $groupid; 02224 } 02225 } 02226 02227 } else { 02228 if ($groupid) { 02229 $joins[] = "JOIN {groups_members} {$prefix}gm ON ({$prefix}gm.userid = {$prefix}u.id AND {$prefix}gm.groupid = :{$prefix}gmid)"; 02230 $params["{$prefix}gmid"] = $groupid; 02231 } 02232 } 02233 02234 $wheres[] = "{$prefix}u.deleted = 0 AND {$prefix}u.id <> :{$prefix}guestid"; 02235 $params["{$prefix}guestid"] = $CFG->siteguest; 02236 02237 if ($isfrontpage) { 02238 // all users are "enrolled" on the frontpage 02239 } else { 02240 $joins[] = "JOIN {user_enrolments} {$prefix}ue ON {$prefix}ue.userid = {$prefix}u.id"; 02241 $joins[] = "JOIN {enrol} {$prefix}e ON ({$prefix}e.id = {$prefix}ue.enrolid AND {$prefix}e.courseid = :{$prefix}courseid)"; 02242 $params[$prefix.'courseid'] = $coursecontext->instanceid; 02243 02244 if ($onlyactive) { 02245 $wheres[] = "{$prefix}ue.status = :{$prefix}active AND {$prefix}e.status = :{$prefix}enabled"; 02246 $wheres[] = "{$prefix}ue.timestart < :{$prefix}now1 AND ({$prefix}ue.timeend = 0 OR {$prefix}ue.timeend > :{$prefix}now2)"; 02247 $now = round(time(), -2); // rounding helps caching in DB 02248 $params = array_merge($params, array($prefix.'enabled'=>ENROL_INSTANCE_ENABLED, 02249 $prefix.'active'=>ENROL_USER_ACTIVE, 02250 $prefix.'now1'=>$now, $prefix.'now2'=>$now)); 02251 } 02252 } 02253 02254 $joins = implode("\n", $joins); 02255 $wheres = "WHERE ".implode(" AND ", $wheres); 02256 02257 $sql = "SELECT DISTINCT {$prefix}u.id 02258 FROM {user} {$prefix}u 02259 $joins 02260 $wheres"; 02261 02262 return array($sql, $params); 02263 } 02264 02277 function get_enrolled_users(context $context, $withcapability = '', $groupid = 0, $userfields = 'u.*', $orderby = '', $limitfrom = 0, $limitnum = 0) { 02278 global $DB; 02279 02280 list($esql, $params) = get_enrolled_sql($context, $withcapability, $groupid); 02281 $sql = "SELECT $userfields 02282 FROM {user} u 02283 JOIN ($esql) je ON je.id = u.id 02284 WHERE u.deleted = 0"; 02285 02286 if ($orderby) { 02287 $sql = "$sql ORDER BY $orderby"; 02288 } else { 02289 $sql = "$sql ORDER BY u.lastname ASC, u.firstname ASC"; 02290 } 02291 02292 return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum); 02293 } 02294 02303 function count_enrolled_users(context $context, $withcapability = '', $groupid = 0) { 02304 global $DB; 02305 02306 list($esql, $params) = get_enrolled_sql($context, $withcapability, $groupid); 02307 $sql = "SELECT count(u.id) 02308 FROM {user} u 02309 JOIN ($esql) je ON je.id = u.id 02310 WHERE u.deleted = 0"; 02311 02312 return $DB->count_records_sql($sql, $params); 02313 } 02314 02324 function load_capability_def($component) { 02325 $defpath = get_component_directory($component).'/db/access.php'; 02326 02327 $capabilities = array(); 02328 if (file_exists($defpath)) { 02329 require($defpath); 02330 if (!empty(${$component.'_capabilities'})) { 02331 // BC capability array name 02332 // since 2.0 we prefer $capabilities instead - it is easier to use and matches db/* files 02333 debugging('componentname_capabilities array is deprecated, please use $capabilities array only in access.php files'); 02334 $capabilities = ${$component.'_capabilities'}; 02335 } 02336 } 02337 02338 return $capabilities; 02339 } 02340 02347 function get_cached_capabilities($component = 'moodle') { 02348 global $DB; 02349 return $DB->get_records('capabilities', array('component'=>$component)); 02350 } 02351 02358 function get_default_capabilities($archetype) { 02359 global $DB; 02360 02361 if (!$archetype) { 02362 return array(); 02363 } 02364 02365 $alldefs = array(); 02366 $defaults = array(); 02367 $components = array(); 02368 $allcaps = $DB->get_records('capabilities'); 02369 02370 foreach ($allcaps as $cap) { 02371 if (!in_array($cap->component, $components)) { 02372 $components[] = $cap->component; 02373 $alldefs = array_merge($alldefs, load_capability_def($cap->component)); 02374 } 02375 } 02376 foreach($alldefs as $name=>$def) { 02377 // Use array 'archetypes if available. Only if not specified, use 'legacy'. 02378 if (isset($def['archetypes'])) { 02379 if (isset($def['archetypes'][$archetype])) { 02380 $defaults[$name] = $def['archetypes'][$archetype]; 02381 } 02382 // 'legacy' is for backward compatibility with 1.9 access.php 02383 } else { 02384 if (isset($def['legacy'][$archetype])) { 02385 $defaults[$name] = $def['legacy'][$archetype]; 02386 } 02387 } 02388 } 02389 02390 return $defaults; 02391 } 02392 02400 function reset_role_capabilities($roleid) { 02401 global $DB; 02402 02403 $role = $DB->get_record('role', array('id'=>$roleid), '*', MUST_EXIST); 02404 $defaultcaps = get_default_capabilities($role->archetype); 02405 02406 $systemcontext = context_system::instance(); 02407 02408 $DB->delete_records('role_capabilities', array('roleid'=>$roleid)); 02409 02410 foreach($defaultcaps as $cap=>$permission) { 02411 assign_capability($cap, $permission, $roleid, $systemcontext->id); 02412 } 02413 } 02414 02427 function update_capabilities($component = 'moodle') { 02428 global $DB, $OUTPUT; 02429 02430 $storedcaps = array(); 02431 02432 $filecaps = load_capability_def($component); 02433 foreach($filecaps as $capname=>$unused) { 02434 if (!preg_match('|^[a-z]+/[a-z_0-9]+:[a-z_0-9]+$|', $capname)) { 02435 debugging("Coding problem: Invalid capability name '$capname', use 'clonepermissionsfrom' field for migration."); 02436 } 02437 } 02438 02439 $cachedcaps = get_cached_capabilities($component); 02440 if ($cachedcaps) { 02441 foreach ($cachedcaps as $cachedcap) { 02442 array_push($storedcaps, $cachedcap->name); 02443 // update risk bitmasks and context levels in existing capabilities if needed 02444 if (array_key_exists($cachedcap->name, $filecaps)) { 02445 if (!array_key_exists('riskbitmask', $filecaps[$cachedcap->name])) { 02446 $filecaps[$cachedcap->name]['riskbitmask'] = 0; // no risk if not specified 02447 } 02448 if ($cachedcap->captype != $filecaps[$cachedcap->name]['captype']) { 02449 $updatecap = new stdClass(); 02450 $updatecap->id = $cachedcap->id; 02451 $updatecap->captype = $filecaps[$cachedcap->name]['captype']; 02452 $DB->update_record('capabilities', $updatecap); 02453 } 02454 if ($cachedcap->riskbitmask != $filecaps[$cachedcap->name]['riskbitmask']) { 02455 $updatecap = new stdClass(); 02456 $updatecap->id = $cachedcap->id; 02457 $updatecap->riskbitmask = $filecaps[$cachedcap->name]['riskbitmask']; 02458 $DB->update_record('capabilities', $updatecap); 02459 } 02460 02461 if (!array_key_exists('contextlevel', $filecaps[$cachedcap->name])) { 02462 $filecaps[$cachedcap->name]['contextlevel'] = 0; // no context level defined 02463 } 02464 if ($cachedcap->contextlevel != $filecaps[$cachedcap->name]['contextlevel']) { 02465 $updatecap = new stdClass(); 02466 $updatecap->id = $cachedcap->id; 02467 $updatecap->contextlevel = $filecaps[$cachedcap->name]['contextlevel']; 02468 $DB->update_record('capabilities', $updatecap); 02469 } 02470 } 02471 } 02472 } 02473 02474 // Are there new capabilities in the file definition? 02475 $newcaps = array(); 02476 02477 foreach ($filecaps as $filecap => $def) { 02478 if (!$storedcaps || 02479 ($storedcaps && in_array($filecap, $storedcaps) === false)) { 02480 if (!array_key_exists('riskbitmask', $def)) { 02481 $def['riskbitmask'] = 0; // no risk if not specified 02482 } 02483 $newcaps[$filecap] = $def; 02484 } 02485 } 02486 // Add new capabilities to the stored definition. 02487 foreach ($newcaps as $capname => $capdef) { 02488 $capability = new stdClass(); 02489 $capability->name = $capname; 02490 $capability->captype = $capdef['captype']; 02491 $capability->contextlevel = $capdef['contextlevel']; 02492 $capability->component = $component; 02493 $capability->riskbitmask = $capdef['riskbitmask']; 02494 02495 $DB->insert_record('capabilities', $capability, false); 02496 02497 if (isset($capdef['clonepermissionsfrom']) && in_array($capdef['clonepermissionsfrom'], $storedcaps)){ 02498 if ($rolecapabilities = $DB->get_records('role_capabilities', array('capability'=>$capdef['clonepermissionsfrom']))){ 02499 foreach ($rolecapabilities as $rolecapability){ 02500 //assign_capability will update rather than insert if capability exists 02501 if (!assign_capability($capname, $rolecapability->permission, 02502 $rolecapability->roleid, $rolecapability->contextid, true)){ 02503 echo $OUTPUT->notification('Could not clone capabilities for '.$capname); 02504 } 02505 } 02506 } 02507 // we ignore archetype key if we have cloned permissions 02508 } else if (isset($capdef['archetypes']) && is_array($capdef['archetypes'])) { 02509 assign_legacy_capabilities($capname, $capdef['archetypes']); 02510 // 'legacy' is for backward compatibility with 1.9 access.php 02511 } else if (isset($capdef['legacy']) && is_array($capdef['legacy'])) { 02512 assign_legacy_capabilities($capname, $capdef['legacy']); 02513 } 02514 } 02515 // Are there any capabilities that have been removed from the file 02516 // definition that we need to delete from the stored capabilities and 02517 // role assignments? 02518 capabilities_cleanup($component, $filecaps); 02519 02520 // reset static caches 02521 accesslib_clear_all_caches(false); 02522 02523 return true; 02524 } 02525 02535 function capabilities_cleanup($component, $newcapdef = null) { 02536 global $DB; 02537 02538 $removedcount = 0; 02539 02540 if ($cachedcaps = get_cached_capabilities($component)) { 02541 foreach ($cachedcaps as $cachedcap) { 02542 if (empty($newcapdef) || 02543 array_key_exists($cachedcap->name, $newcapdef) === false) { 02544 02545 // Remove from capabilities cache. 02546 $DB->delete_records('capabilities', array('name'=>$cachedcap->name)); 02547 $removedcount++; 02548 // Delete from roles. 02549 if ($roles = get_roles_with_capability($cachedcap->name)) { 02550 foreach($roles as $role) { 02551 if (!unassign_capability($cachedcap->name, $role->id)) { 02552 print_error('cannotunassigncap', 'error', '', (object)array('cap'=>$cachedcap->name, 'role'=>$role->name)); 02553 } 02554 } 02555 } 02556 } // End if. 02557 } 02558 } 02559 return $removedcount; 02560 } 02561 02569 function get_all_risks() { 02570 return array( 02571 'riskmanagetrust' => RISK_MANAGETRUST, 02572 'riskconfig' => RISK_CONFIG, 02573 'riskxss' => RISK_XSS, 02574 'riskpersonal' => RISK_PERSONAL, 02575 'riskspam' => RISK_SPAM, 02576 'riskdataloss' => RISK_DATALOSS, 02577 ); 02578 } 02579 02586 function get_capability_docs_link($capability) { 02587 $url = get_docs_url('Capabilities/' . $capability->name); 02588 return '<a onclick="this.target=\'docspopup\'" href="' . $url . '">' . get_capability_string($capability->name) . '</a>'; 02589 } 02590 02601 function role_context_capabilities($roleid, context $context, $cap = '') { 02602 global $DB; 02603 02604 $contexts = $context->get_parent_context_ids(true); 02605 $contexts = '('.implode(',', $contexts).')'; 02606 02607 $params = array($roleid); 02608 02609 if ($cap) { 02610 $search = " AND rc.capability = ? "; 02611 $params[] = $cap; 02612 } else { 02613 $search = ''; 02614 } 02615 02616 $sql = "SELECT rc.* 02617 FROM {role_capabilities} rc, {context} c 02618 WHERE rc.contextid in $contexts 02619 AND rc.roleid = ? 02620 AND rc.contextid = c.id $search 02621 ORDER BY c.contextlevel DESC, rc.capability DESC"; 02622 02623 $capabilities = array(); 02624 02625 if ($records = $DB->get_records_sql($sql, $params)) { 02626 // We are traversing via reverse order. 02627 foreach ($records as $record) { 02628 // If not set yet (i.e. inherit or not set at all), or currently we have a prohibit 02629 if (!isset($capabilities[$record->capability]) || $record->permission<-500) { 02630 $capabilities[$record->capability] = $record->permission; 02631 } 02632 } 02633 } 02634 return $capabilities; 02635 } 02636 02645 function get_context_info_list(context $context) { 02646 $contextids = explode('/', ltrim($context->path, '/')); 02647 $contextpaths = array(); 02648 $contextids2 = $contextids; 02649 while ($contextids2) { 02650 $contextpaths[] = '/' . implode('/', $contextids2); 02651 array_pop($contextids2); 02652 } 02653 return array($contextids, $contextpaths); 02654 } 02655 02665 function is_inside_frontpage(context $context) { 02666 $frontpagecontext = context_course::instance(SITEID); 02667 return strpos($context->path . '/', $frontpagecontext->path . '/') === 0; 02668 } 02669 02676 function get_capability_info($capabilityname) { 02677 global $ACCESSLIB_PRIVATE, $DB; // one request per page only 02678 02679 //TODO: MUC - this could be cached in shared memory, it would eliminate 1 query per page 02680 02681 if (empty($ACCESSLIB_PRIVATE->capabilities)) { 02682 $ACCESSLIB_PRIVATE->capabilities = array(); 02683 $caps = $DB->get_records('capabilities', array(), 'id, name, captype, riskbitmask'); 02684 foreach ($caps as $cap) { 02685 $capname = $cap->name; 02686 unset($cap->id); 02687 unset($cap->name); 02688 $cap->riskbitmask = (int)$cap->riskbitmask; 02689 $ACCESSLIB_PRIVATE->capabilities[$capname] = $cap; 02690 } 02691 } 02692 02693 return isset($ACCESSLIB_PRIVATE->capabilities[$capabilityname]) ? $ACCESSLIB_PRIVATE->capabilities[$capabilityname] : null; 02694 } 02695 02703 function get_capability_string($capabilityname) { 02704 02705 // Typical capability name is 'plugintype/pluginname:capabilityname' 02706 list($type, $name, $capname) = preg_split('|[/:]|', $capabilityname); 02707 02708 if ($type === 'moodle') { 02709 $component = 'core_role'; 02710 } else if ($type === 'quizreport') { 02711 //ugly hack!! 02712 $component = 'quiz_'.$name; 02713 } else { 02714 $component = $type.'_'.$name; 02715 } 02716 02717 $stringname = $name.':'.$capname; 02718 02719 if ($component === 'core_role' or get_string_manager()->string_exists($stringname, $component)) { 02720 return get_string($stringname, $component); 02721 } 02722 02723 $dir = get_component_directory($component); 02724 if (!file_exists($dir)) { 02725 // plugin broken or does not exist, do not bother with printing of debug message 02726 return $capabilityname.' ???'; 02727 } 02728 02729 // something is wrong in plugin, better print debug 02730 return get_string($stringname, $component); 02731 } 02732 02740 function get_component_string($component, $contextlevel) { 02741 02742 if ($component === 'moodle' or $component === 'core') { 02743 switch ($contextlevel) { 02744 // TODO: this should probably use context level names instead 02745 case CONTEXT_SYSTEM: return get_string('coresystem'); 02746 case CONTEXT_USER: return get_string('users'); 02747 case CONTEXT_COURSECAT: return get_string('categories'); 02748 case CONTEXT_COURSE: return get_string('course'); 02749 case CONTEXT_MODULE: return get_string('activities'); 02750 case CONTEXT_BLOCK: return get_string('block'); 02751 default: print_error('unknowncontext'); 02752 } 02753 } 02754 02755 list($type, $name) = normalize_component($component); 02756 $dir = get_plugin_directory($type, $name); 02757 if (!file_exists($dir)) { 02758 // plugin not installed, bad luck, there is no way to find the name 02759 return $component.' ???'; 02760 } 02761 02762 switch ($type) { 02763 // TODO: this is really hacky, anyway it should be probably moved to lib/pluginlib.php 02764 case 'quiz': return get_string($name.':componentname', $component);// insane hack!!! 02765 case 'repository': return get_string('repository', 'repository').': '.get_string('pluginname', $component); 02766 case 'gradeimport': return get_string('gradeimport', 'grades').': '.get_string('pluginname', $component); 02767 case 'gradeexport': return get_string('gradeexport', 'grades').': '.get_string('pluginname', $component); 02768 case 'gradereport': return get_string('gradereport', 'grades').': '.get_string('pluginname', $component); 02769 case 'webservice': return get_string('webservice', 'webservice').': '.get_string('pluginname', $component); 02770 case 'block': return get_string('block').': '.get_string('pluginname', basename($component)); 02771 case 'mod': 02772 if (get_string_manager()->string_exists('pluginname', $component)) { 02773 return get_string('activity').': '.get_string('pluginname', $component); 02774 } else { 02775 return get_string('activity').': '.get_string('modulename', $component); 02776 } 02777 default: return get_string('pluginname', $component); 02778 } 02779 } 02780 02789 function get_profile_roles(context $context) { 02790 global $CFG, $DB; 02791 02792 if (empty($CFG->profileroles)) { 02793 return array(); 02794 } 02795 02796 list($rallowed, $params) = $DB->get_in_or_equal(explode(',', $CFG->profileroles), SQL_PARAMS_NAMED, 'a'); 02797 list($contextlist, $cparams) = $DB->get_in_or_equal($context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'p'); 02798 $params = array_merge($params, $cparams); 02799 02800 $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder 02801 FROM {role_assignments} ra, {role} r 02802 WHERE r.id = ra.roleid 02803 AND ra.contextid $contextlist 02804 AND r.id $rallowed 02805 ORDER BY r.sortorder ASC"; 02806 02807 return $DB->get_records_sql($sql, $params); 02808 } 02809 02816 function get_roles_used_in_context(context $context) { 02817 global $DB; 02818 02819 list($contextlist, $params) = $DB->get_in_or_equal($context->get_parent_context_ids(true)); 02820 02821 $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder 02822 FROM {role_assignments} ra, {role} r 02823 WHERE r.id = ra.roleid 02824 AND ra.contextid $contextlist 02825 ORDER BY r.sortorder ASC"; 02826 02827 return $DB->get_records_sql($sql, $params); 02828 } 02829 02839 function get_user_roles_in_course($userid, $courseid) { 02840 global $CFG, $DB; 02841 02842 if (empty($CFG->profileroles)) { 02843 return ''; 02844 } 02845 02846 if ($courseid == SITEID) { 02847 $context = context_system::instance(); 02848 } else { 02849 $context = context_course::instance($courseid); 02850 } 02851 02852 if (empty($CFG->profileroles)) { 02853 return array(); 02854 } 02855 02856 list($rallowed, $params) = $DB->get_in_or_equal(explode(',', $CFG->profileroles), SQL_PARAMS_NAMED, 'a'); 02857 list($contextlist, $cparams) = $DB->get_in_or_equal($context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'p'); 02858 $params = array_merge($params, $cparams); 02859 02860 $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder 02861 FROM {role_assignments} ra, {role} r 02862 WHERE r.id = ra.roleid 02863 AND ra.contextid $contextlist 02864 AND r.id $rallowed 02865 AND ra.userid = :userid 02866 ORDER BY r.sortorder ASC"; 02867 $params['userid'] = $userid; 02868 02869 $rolestring = ''; 02870 02871 if ($roles = $DB->get_records_sql($sql, $params)) { 02872 foreach ($roles as $userrole) { 02873 $rolenames[$userrole->id] = $userrole->name; 02874 } 02875 02876 $rolenames = role_fix_names($rolenames, $context); // Substitute aliases 02877 02878 foreach ($rolenames as $roleid => $rolename) { 02879 $rolenames[$roleid] = '<a href="'.$CFG->wwwroot.'/user/index.php?contextid='.$context->id.'&roleid='.$roleid.'">'.$rolename.'</a>'; 02880 } 02881 $rolestring = implode(',', $rolenames); 02882 } 02883 02884 return $rolestring; 02885 } 02886 02894 function user_can_assign(context $context, $targetroleid) { 02895 global $DB; 02896 02897 // first check if user has override capability 02898 // if not return false; 02899 if (!has_capability('moodle/role:assign', $context)) { 02900 return false; 02901 } 02902 // pull out all active roles of this user from this context(or above) 02903 if ($userroles = get_user_roles($context)) { 02904 foreach ($userroles as $userrole) { 02905 // if any in the role_allow_override table, then it's ok 02906 if ($DB->get_record('role_allow_assign', array('roleid'=>$userrole->roleid, 'allowassign'=>$targetroleid))) { 02907 return true; 02908 } 02909 } 02910 } 02911 02912 return false; 02913 } 02914 02920 function get_all_roles() { 02921 global $DB; 02922 return $DB->get_records('role', null, 'sortorder ASC'); 02923 } 02924 02931 function get_archetype_roles($archetype) { 02932 global $DB; 02933 return $DB->get_records('role', array('archetype'=>$archetype), 'sortorder ASC'); 02934 } 02935 02948 function get_user_roles(context $context, $userid = 0, $checkparentcontexts = true, $order = 'c.contextlevel DESC, r.sortorder ASC') { 02949 global $USER, $DB; 02950 02951 if (empty($userid)) { 02952 if (empty($USER->id)) { 02953 return array(); 02954 } 02955 $userid = $USER->id; 02956 } 02957 02958 if ($checkparentcontexts) { 02959 $contextids = $context->get_parent_context_ids(); 02960 } else { 02961 $contextids = array(); 02962 } 02963 $contextids[] = $context->id; 02964 02965 list($contextids, $params) = $DB->get_in_or_equal($contextids, SQL_PARAMS_QM); 02966 02967 array_unshift($params, $userid); 02968 02969 $sql = "SELECT ra.*, r.name, r.shortname 02970 FROM {role_assignments} ra, {role} r, {context} c 02971 WHERE ra.userid = ? 02972 AND ra.roleid = r.id 02973 AND ra.contextid = c.id 02974 AND ra.contextid $contextids 02975 ORDER BY $order"; 02976 02977 return $DB->get_records_sql($sql ,$params); 02978 } 02979 02987 function allow_override($sroleid, $troleid) { 02988 global $DB; 02989 02990 $record = new stdClass(); 02991 $record->roleid = $sroleid; 02992 $record->allowoverride = $troleid; 02993 $DB->insert_record('role_allow_override', $record); 02994 } 02995 03003 function allow_assign($fromroleid, $targetroleid) { 03004 global $DB; 03005 03006 $record = new stdClass(); 03007 $record->roleid = $fromroleid; 03008 $record->allowassign = $targetroleid; 03009 $DB->insert_record('role_allow_assign', $record); 03010 } 03011 03019 function allow_switch($fromroleid, $targetroleid) { 03020 global $DB; 03021 03022 $record = new stdClass(); 03023 $record->roleid = $fromroleid; 03024 $record->allowswitch = $targetroleid; 03025 $DB->insert_record('role_allow_switch', $record); 03026 } 03027 03040 function get_assignable_roles(context $context, $rolenamedisplay = ROLENAME_ALIAS, $withusercounts = false, $user = null) { 03041 global $USER, $DB; 03042 03043 // make sure there is a real user specified 03044 if ($user === null) { 03045 $userid = isset($USER->id) ? $USER->id : 0; 03046 } else { 03047 $userid = is_object($user) ? $user->id : $user; 03048 } 03049 03050 if (!has_capability('moodle/role:assign', $context, $userid)) { 03051 if ($withusercounts) { 03052 return array(array(), array(), array()); 03053 } else { 03054 return array(); 03055 } 03056 } 03057 03058 $parents = $context->get_parent_context_ids(true); 03059 $contexts = implode(',' , $parents); 03060 03061 $params = array(); 03062 $extrafields = ''; 03063 if ($rolenamedisplay == ROLENAME_ORIGINALANDSHORT or $rolenamedisplay == ROLENAME_SHORT) { 03064 $extrafields .= ', r.shortname'; 03065 } 03066 03067 if ($withusercounts) { 03068 $extrafields = ', (SELECT count(u.id) 03069 FROM {role_assignments} cra JOIN {user} u ON cra.userid = u.id 03070 WHERE cra.roleid = r.id AND cra.contextid = :conid AND u.deleted = 0 03071 ) AS usercount'; 03072 $params['conid'] = $context->id; 03073 } 03074 03075 if (is_siteadmin($userid)) { 03076 // show all roles allowed in this context to admins 03077 $assignrestriction = ""; 03078 } else { 03079 $assignrestriction = "JOIN (SELECT DISTINCT raa.allowassign AS id 03080 FROM {role_allow_assign} raa 03081 JOIN {role_assignments} ra ON ra.roleid = raa.roleid 03082 WHERE ra.userid = :userid AND ra.contextid IN ($contexts) 03083 ) ar ON ar.id = r.id"; 03084 $params['userid'] = $userid; 03085 } 03086 $params['contextlevel'] = $context->contextlevel; 03087 $sql = "SELECT r.id, r.name $extrafields 03088 FROM {role} r 03089 $assignrestriction 03090 JOIN {role_context_levels} rcl ON r.id = rcl.roleid 03091 WHERE rcl.contextlevel = :contextlevel 03092 ORDER BY r.sortorder ASC"; 03093 $roles = $DB->get_records_sql($sql, $params); 03094 03095 $rolenames = array(); 03096 foreach ($roles as $role) { 03097 if ($rolenamedisplay == ROLENAME_SHORT) { 03098 $rolenames[$role->id] = $role->shortname; 03099 continue; 03100 } 03101 $rolenames[$role->id] = $role->name; 03102 if ($rolenamedisplay == ROLENAME_ORIGINALANDSHORT) { 03103 $rolenames[$role->id] .= ' (' . $role->shortname . ')'; 03104 } 03105 } 03106 if ($rolenamedisplay != ROLENAME_ORIGINALANDSHORT and $rolenamedisplay != ROLENAME_SHORT) { 03107 $rolenames = role_fix_names($rolenames, $context, $rolenamedisplay); 03108 } 03109 03110 if (!$withusercounts) { 03111 return $rolenames; 03112 } 03113 03114 $rolecounts = array(); 03115 $nameswithcounts = array(); 03116 foreach ($roles as $role) { 03117 $nameswithcounts[$role->id] = $rolenames[$role->id] . ' (' . $roles[$role->id]->usercount . ')'; 03118 $rolecounts[$role->id] = $roles[$role->id]->usercount; 03119 } 03120 return array($rolenames, $rolecounts, $nameswithcounts); 03121 } 03122 03133 function get_switchable_roles(context $context) { 03134 global $USER, $DB; 03135 03136 $params = array(); 03137 $extrajoins = ''; 03138 $extrawhere = ''; 03139 if (!is_siteadmin()) { 03140 // Admins are allowed to switch to any role with. 03141 // Others are subject to the additional constraint that the switch-to role must be allowed by 03142 // 'role_allow_switch' for some role they have assigned in this context or any parent. 03143 $parents = $context->get_parent_context_ids(true); 03144 $contexts = implode(',' , $parents); 03145 03146 $extrajoins = "JOIN {role_allow_switch} ras ON ras.allowswitch = rc.roleid 03147 JOIN {role_assignments} ra ON ra.roleid = ras.roleid"; 03148 $extrawhere = "WHERE ra.userid = :userid AND ra.contextid IN ($contexts)"; 03149 $params['userid'] = $USER->id; 03150 } 03151 03152 $query = " 03153 SELECT r.id, r.name 03154 FROM (SELECT DISTINCT rc.roleid 03155 FROM {role_capabilities} rc 03156 $extrajoins 03157 $extrawhere) idlist 03158 JOIN {role} r ON r.id = idlist.roleid 03159 ORDER BY r.sortorder"; 03160 03161 $rolenames = $DB->get_records_sql_menu($query, $params); 03162 return role_fix_names($rolenames, $context, ROLENAME_ALIAS); 03163 } 03164 03176 function get_overridable_roles(context $context, $rolenamedisplay = ROLENAME_ALIAS, $withcounts = false) { 03177 global $USER, $DB; 03178 03179 if (!has_any_capability(array('moodle/role:safeoverride', 'moodle/role:override'), $context)) { 03180 if ($withcounts) { 03181 return array(array(), array(), array()); 03182 } else { 03183 return array(); 03184 } 03185 } 03186 03187 $parents = $context->get_parent_context_ids(true); 03188 $contexts = implode(',' , $parents); 03189 03190 $params = array(); 03191 $extrafields = ''; 03192 if ($rolenamedisplay == ROLENAME_ORIGINALANDSHORT) { 03193 $extrafields .= ', ro.shortname'; 03194 } 03195 03196 $params['userid'] = $USER->id; 03197 if ($withcounts) { 03198 $extrafields = ', (SELECT COUNT(rc.id) FROM {role_capabilities} rc 03199 WHERE rc.roleid = ro.id AND rc.contextid = :conid) AS overridecount'; 03200 $params['conid'] = $context->id; 03201 } 03202 03203 if (is_siteadmin()) { 03204 // show all roles to admins 03205 $roles = $DB->get_records_sql(" 03206 SELECT ro.id, ro.name$extrafields 03207 FROM {role} ro 03208 ORDER BY ro.sortorder ASC", $params); 03209 03210 } else { 03211 $roles = $DB->get_records_sql(" 03212 SELECT ro.id, ro.name$extrafields 03213 FROM {role} ro 03214 JOIN (SELECT DISTINCT r.id 03215 FROM {role} r 03216 JOIN {role_allow_override} rao ON r.id = rao.allowoverride 03217 JOIN {role_assignments} ra ON rao.roleid = ra.roleid 03218 WHERE ra.userid = :userid AND ra.contextid IN ($contexts) 03219 ) inline_view ON ro.id = inline_view.id 03220 ORDER BY ro.sortorder ASC", $params); 03221 } 03222 03223 $rolenames = array(); 03224 foreach ($roles as $role) { 03225 $rolenames[$role->id] = $role->name; 03226 if ($rolenamedisplay == ROLENAME_ORIGINALANDSHORT) { 03227 $rolenames[$role->id] .= ' (' . $role->shortname . ')'; 03228 } 03229 } 03230 if ($rolenamedisplay != ROLENAME_ORIGINALANDSHORT) { 03231 $rolenames = role_fix_names($rolenames, $context, $rolenamedisplay); 03232 } 03233 03234 if (!$withcounts) { 03235 return $rolenames; 03236 } 03237 03238 $rolecounts = array(); 03239 $nameswithcounts = array(); 03240 foreach ($roles as $role) { 03241 $nameswithcounts[$role->id] = $rolenames[$role->id] . ' (' . $roles[$role->id]->overridecount . ')'; 03242 $rolecounts[$role->id] = $roles[$role->id]->overridecount; 03243 } 03244 return array($rolenames, $rolecounts, $nameswithcounts); 03245 } 03246 03253 function get_default_enrol_roles(context $context, $addroleid = null) { 03254 global $DB; 03255 03256 $params = array('contextlevel'=>CONTEXT_COURSE); 03257 if ($addroleid) { 03258 $addrole = "OR r.id = :addroleid"; 03259 $params['addroleid'] = $addroleid; 03260 } else { 03261 $addrole = ""; 03262 } 03263 $sql = "SELECT r.id, r.name 03264 FROM {role} r 03265 LEFT JOIN {role_context_levels} rcl ON (rcl.roleid = r.id AND rcl.contextlevel = :contextlevel) 03266 WHERE rcl.id IS NOT NULL $addrole 03267 ORDER BY sortorder DESC"; 03268 03269 $roles = $DB->get_records_sql_menu($sql, $params); 03270 $roles = role_fix_names($roles, $context, ROLENAME_BOTH); 03271 03272 return $roles; 03273 } 03274 03280 function get_role_contextlevels($roleid) { 03281 global $DB; 03282 return $DB->get_records_menu('role_context_levels', array('roleid' => $roleid), 03283 'contextlevel', 'id,contextlevel'); 03284 } 03285 03294 function get_roles_for_contextlevels($contextlevel) { 03295 global $DB; 03296 return $DB->get_records_menu('role_context_levels', array('contextlevel' => $contextlevel), 03297 '', 'id,roleid'); 03298 } 03299 03307 function get_default_contextlevels($rolearchetype) { 03308 static $defaults = array( 03309 'manager' => array(CONTEXT_SYSTEM, CONTEXT_COURSECAT, CONTEXT_COURSE), 03310 'coursecreator' => array(CONTEXT_SYSTEM, CONTEXT_COURSECAT), 03311 'editingteacher' => array(CONTEXT_COURSE, CONTEXT_MODULE), 03312 'teacher' => array(CONTEXT_COURSE, CONTEXT_MODULE), 03313 'student' => array(CONTEXT_COURSE, CONTEXT_MODULE), 03314 'guest' => array(), 03315 'user' => array(), 03316 'frontpage' => array()); 03317 03318 if (isset($defaults[$rolearchetype])) { 03319 return $defaults[$rolearchetype]; 03320 } else { 03321 return array(); 03322 } 03323 } 03324 03334 function set_role_contextlevels($roleid, array $contextlevels) { 03335 global $DB; 03336 $DB->delete_records('role_context_levels', array('roleid' => $roleid)); 03337 $rcl = new stdClass(); 03338 $rcl->roleid = $roleid; 03339 $contextlevels = array_unique($contextlevels); 03340 foreach ($contextlevels as $level) { 03341 $rcl->contextlevel = $level; 03342 $DB->insert_record('role_context_levels', $rcl, false, true); 03343 } 03344 } 03345 03373 function get_users_by_capability(context $context, $capability, $fields = '', $sort = '', $limitfrom = '', $limitnum = '', 03374 $groups = '', $exceptions = '', $doanything_ignored = null, $view_ignored = null, $useviewallgroups = false) { 03375 global $CFG, $DB; 03376 03377 $defaultuserroleid = isset($CFG->defaultuserroleid) ? $CFG->defaultuserroleid : 0; 03378 $defaultfrontpageroleid = isset($CFG->defaultfrontpageroleid) ? $CFG->defaultfrontpageroleid : 0; 03379 03380 $ctxids = trim($context->path, '/'); 03381 $ctxids = str_replace('/', ',', $ctxids); 03382 03383 // Context is the frontpage 03384 $iscoursepage = false; // coursepage other than fp 03385 $isfrontpage = false; 03386 if ($context->contextlevel == CONTEXT_COURSE) { 03387 if ($context->instanceid == SITEID) { 03388 $isfrontpage = true; 03389 } else { 03390 $iscoursepage = true; 03391 } 03392 } 03393 $isfrontpage = ($isfrontpage || is_inside_frontpage($context)); 03394 03395 $caps = (array)$capability; 03396 03397 // construct list of context paths bottom-->top 03398 list($contextids, $paths) = get_context_info_list($context); 03399 03400 // we need to find out all roles that have these capabilities either in definition or in overrides 03401 $defs = array(); 03402 list($incontexts, $params) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, 'con'); 03403 list($incaps, $params2) = $DB->get_in_or_equal($caps, SQL_PARAMS_NAMED, 'cap'); 03404 $params = array_merge($params, $params2); 03405 $sql = "SELECT rc.id, rc.roleid, rc.permission, rc.capability, ctx.path 03406 FROM {role_capabilities} rc 03407 JOIN {context} ctx on rc.contextid = ctx.id 03408 WHERE rc.contextid $incontexts AND rc.capability $incaps"; 03409 03410 $rcs = $DB->get_records_sql($sql, $params); 03411 foreach ($rcs as $rc) { 03412 $defs[$rc->capability][$rc->path][$rc->roleid] = $rc->permission; 03413 } 03414 03415 // go through the permissions bottom-->top direction to evaluate the current permission, 03416 // first one wins (prohibit is an exception that always wins) 03417 $access = array(); 03418 foreach ($caps as $cap) { 03419 foreach ($paths as $path) { 03420 if (empty($defs[$cap][$path])) { 03421 continue; 03422 } 03423 foreach($defs[$cap][$path] as $roleid => $perm) { 03424 if ($perm == CAP_PROHIBIT) { 03425 $access[$cap][$roleid] = CAP_PROHIBIT; 03426 continue; 03427 } 03428 if (!isset($access[$cap][$roleid])) { 03429 $access[$cap][$roleid] = (int)$perm; 03430 } 03431 } 03432 } 03433 } 03434 03435 // make lists of roles that are needed and prohibited in this context 03436 $needed = array(); // one of these is enough 03437 $prohibited = array(); // must not have any of these 03438 foreach ($caps as $cap) { 03439 if (empty($access[$cap])) { 03440 continue; 03441 } 03442 foreach ($access[$cap] as $roleid => $perm) { 03443 if ($perm == CAP_PROHIBIT) { 03444 unset($needed[$cap][$roleid]); 03445 $prohibited[$cap][$roleid] = true; 03446 } else if ($perm == CAP_ALLOW and empty($prohibited[$cap][$roleid])) { 03447 $needed[$cap][$roleid] = true; 03448 } 03449 } 03450 if (empty($needed[$cap]) or !empty($prohibited[$cap][$defaultuserroleid])) { 03451 // easy, nobody has the permission 03452 unset($needed[$cap]); 03453 unset($prohibited[$cap]); 03454 } else if ($isfrontpage and !empty($prohibited[$cap][$defaultfrontpageroleid])) { 03455 // everybody is disqualified on the frontapge 03456 unset($needed[$cap]); 03457 unset($prohibited[$cap]); 03458 } 03459 if (empty($prohibited[$cap])) { 03460 unset($prohibited[$cap]); 03461 } 03462 } 03463 03464 if (empty($needed)) { 03465 // there can not be anybody if no roles match this request 03466 return array(); 03467 } 03468 03469 if (empty($prohibited)) { 03470 // we can compact the needed roles 03471 $n = array(); 03472 foreach ($needed as $cap) { 03473 foreach ($cap as $roleid=>$unused) { 03474 $n[$roleid] = true; 03475 } 03476 } 03477 $needed = array('any'=>$n); 03478 unset($n); 03479 } 03480 03482 if (empty($fields)) { 03483 if ($iscoursepage) { 03484 $fields = 'u.*, ul.timeaccess AS lastaccess'; 03485 } else { 03486 $fields = 'u.*'; 03487 } 03488 } else { 03489 if (debugging('', DEBUG_DEVELOPER) && strpos($fields, 'u.*') === false && strpos($fields, 'u.id') === false) { 03490 debugging('u.id must be included in the list of fields passed to get_users_by_capability().', DEBUG_DEVELOPER); 03491 } 03492 } 03493 03495 if (empty($sort)) { // default to course lastaccess or just lastaccess 03496 if ($iscoursepage) { 03497 $sort = 'ul.timeaccess'; 03498 } else { 03499 $sort = 'u.lastaccess'; 03500 } 03501 } 03502 03503 // Prepare query clauses 03504 $wherecond = array(); 03505 $params = array(); 03506 $joins = array(); 03507 03508 // User lastaccess JOIN 03509 if ((strpos($sort, 'ul.timeaccess') === false) and (strpos($fields, 'ul.timeaccess') === false)) { 03510 // user_lastaccess is not required MDL-13810 03511 } else { 03512 if ($iscoursepage) { 03513 $joins[] = "LEFT OUTER JOIN {user_lastaccess} ul ON (ul.userid = u.id AND ul.courseid = {$context->instanceid})"; 03514 } else { 03515 throw new coding_exception('Invalid sort in get_users_by_capability(), ul.timeaccess allowed only for course contexts.'); 03516 } 03517 } 03518 03520 $wherecond[] = "u.deleted = 0 AND u.id <> :guestid"; 03521 $params['guestid'] = $CFG->siteguest; 03522 03524 if ($groups) { 03525 $groups = (array)$groups; 03526 list($grouptest, $grpparams) = $DB->get_in_or_equal($groups, SQL_PARAMS_NAMED, 'grp'); 03527 $grouptest = "u.id IN (SELECT userid FROM {groups_members} gm WHERE gm.groupid $grouptest)"; 03528 $params = array_merge($params, $grpparams); 03529 03530 if ($useviewallgroups) { 03531 $viewallgroupsusers = get_users_by_capability($context, 'moodle/site:accessallgroups', 'u.id, u.id', '', '', '', '', $exceptions); 03532 if (!empty($viewallgroupsusers)) { 03533 $wherecond[] = "($grouptest OR u.id IN (" . implode(',', array_keys($viewallgroupsusers)) . '))'; 03534 } else { 03535 $wherecond[] = "($grouptest)"; 03536 } 03537 } else { 03538 $wherecond[] = "($grouptest)"; 03539 } 03540 } 03541 03543 if (!empty($exceptions)) { 03544 $exceptions = (array)$exceptions; 03545 list($exsql, $exparams) = $DB->get_in_or_equal($exceptions, SQL_PARAMS_NAMED, 'exc', false); 03546 $params = array_merge($params, $exparams); 03547 $wherecond[] = "u.id $exsql"; 03548 } 03549 03550 // now add the needed and prohibited roles conditions as joins 03551 if (!empty($needed['any'])) { 03552 // simple case - there are no prohibits involved 03553 if (!empty($needed['any'][$defaultuserroleid]) or ($isfrontpage and !empty($needed['any'][$defaultfrontpageroleid]))) { 03554 // everybody 03555 } else { 03556 $joins[] = "JOIN (SELECT DISTINCT userid 03557 FROM {role_assignments} 03558 WHERE contextid IN ($ctxids) 03559 AND roleid IN (".implode(',', array_keys($needed['any'])) .") 03560 ) ra ON ra.userid = u.id"; 03561 } 03562 } else { 03563 $unions = array(); 03564 $everybody = false; 03565 foreach ($needed as $cap=>$unused) { 03566 if (empty($prohibited[$cap])) { 03567 if (!empty($needed[$cap][$defaultuserroleid]) or ($isfrontpage and !empty($needed[$cap][$defaultfrontpageroleid]))) { 03568 $everybody = true; 03569 break; 03570 } else { 03571 $unions[] = "SELECT userid 03572 FROM {role_assignments} 03573 WHERE contextid IN ($ctxids) 03574 AND roleid IN (".implode(',', array_keys($needed[$cap])) .")"; 03575 } 03576 } else { 03577 if (!empty($prohibited[$cap][$defaultuserroleid]) or ($isfrontpage and !empty($prohibited[$cap][$defaultfrontpageroleid]))) { 03578 // nobody can have this cap because it is prevented in default roles 03579 continue; 03580 03581 } else if (!empty($needed[$cap][$defaultuserroleid]) or ($isfrontpage and !empty($needed[$cap][$defaultfrontpageroleid]))) { 03582 // everybody except the prohibitted - hiding does not matter 03583 $unions[] = "SELECT id AS userid 03584 FROM {user} 03585 WHERE id NOT IN (SELECT userid 03586 FROM {role_assignments} 03587 WHERE contextid IN ($ctxids) 03588 AND roleid IN (".implode(',', array_keys($prohibited[$cap])) ."))"; 03589 03590 } else { 03591 $unions[] = "SELECT userid 03592 FROM {role_assignments} 03593 WHERE contextid IN ($ctxids) 03594 AND roleid IN (".implode(',', array_keys($needed[$cap])) .") 03595 AND roleid NOT IN (".implode(',', array_keys($prohibited[$cap])) .")"; 03596 } 03597 } 03598 } 03599 if (!$everybody) { 03600 if ($unions) { 03601 $joins[] = "JOIN (SELECT DISTINCT userid FROM ( ".implode(' UNION ', $unions)." ) us) ra ON ra.userid = u.id"; 03602 } else { 03603 // only prohibits found - nobody can be matched 03604 $wherecond[] = "1 = 2"; 03605 } 03606 } 03607 } 03608 03609 // Collect WHERE conditions and needed joins 03610 $where = implode(' AND ', $wherecond); 03611 if ($where !== '') { 03612 $where = 'WHERE ' . $where; 03613 } 03614 $joins = implode("\n", $joins); 03615 03617 $sql = "SELECT $fields 03618 FROM {user} u 03619 $joins 03620 $where 03621 ORDER BY $sort"; 03622 03623 return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum); 03624 } 03625 03657 function sort_by_roleassignment_authority($users, context $context, $roles = array(), $sortpolicy = 'locality') { 03658 global $DB; 03659 03660 $userswhere = ' ra.userid IN (' . implode(',',array_keys($users)) . ')'; 03661 $contextwhere = 'AND ra.contextid IN ('.str_replace('/', ',',substr($context->path, 1)).')'; 03662 if (empty($roles)) { 03663 $roleswhere = ''; 03664 } else { 03665 $roleswhere = ' AND ra.roleid IN ('.implode(',',$roles).')'; 03666 } 03667 03668 $sql = "SELECT ra.userid 03669 FROM {role_assignments} ra 03670 JOIN {role} r 03671 ON ra.roleid=r.id 03672 JOIN {context} ctx 03673 ON ra.contextid=ctx.id 03674 WHERE $userswhere 03675 $contextwhere 03676 $roleswhere"; 03677 03678 // Default 'locality' policy -- read PHPDoc notes 03679 // about sort policies... 03680 $orderby = 'ORDER BY ' 03681 .'ctx.depth DESC, ' /* locality wins */ 03682 .'r.sortorder ASC, ' /* rolesorting 2nd criteria */ 03683 .'ra.id'; /* role assignment order tie-breaker */ 03684 if ($sortpolicy === 'sortorder') { 03685 $orderby = 'ORDER BY ' 03686 .'r.sortorder ASC, ' /* rolesorting 2nd criteria */ 03687 .'ra.id'; /* role assignment order tie-breaker */ 03688 } 03689 03690 $sortedids = $DB->get_fieldset_sql($sql . $orderby); 03691 $sortedusers = array(); 03692 $seen = array(); 03693 03694 foreach ($sortedids as $id) { 03695 // Avoid duplicates 03696 if (isset($seen[$id])) { 03697 continue; 03698 } 03699 $seen[$id] = true; 03700 03701 // assign 03702 $sortedusers[$id] = $users[$id]; 03703 } 03704 return $sortedusers; 03705 } 03706 03723 function get_role_users($roleid, context $context, $parent = false, $fields = '', 03724 $sort = 'u.lastname, u.firstname', $gethidden_ignored = null, $group = '', 03725 $limitfrom = '', $limitnum = '', $extrawheretest = '', $whereparams = array()) { 03726 global $DB; 03727 03728 if (empty($fields)) { 03729 $fields = 'u.id, u.confirmed, u.username, u.firstname, u.lastname, '. 03730 'u.maildisplay, u.mailformat, u.maildigest, u.email, u.emailstop, u.city, '. 03731 'u.country, u.picture, u.idnumber, u.department, u.institution, '. 03732 'u.lang, u.timezone, u.lastaccess, u.mnethostid, r.name AS rolename, r.sortorder'; 03733 } 03734 03735 $parentcontexts = ''; 03736 if ($parent) { 03737 $parentcontexts = substr($context->path, 1); // kill leading slash 03738 $parentcontexts = str_replace('/', ',', $parentcontexts); 03739 if ($parentcontexts !== '') { 03740 $parentcontexts = ' OR ra.contextid IN ('.$parentcontexts.' )'; 03741 } 03742 } 03743 03744 if ($roleid) { 03745 list($rids, $params) = $DB->get_in_or_equal($roleid, SQL_PARAMS_QM); 03746 $roleselect = "AND ra.roleid $rids"; 03747 } else { 03748 $params = array(); 03749 $roleselect = ''; 03750 } 03751 03752 if ($group) { 03753 $groupjoin = "JOIN {groups_members} gm ON gm.userid = u.id"; 03754 $groupselect = " AND gm.groupid = ? "; 03755 $params[] = $group; 03756 } else { 03757 $groupjoin = ''; 03758 $groupselect = ''; 03759 } 03760 03761 array_unshift($params, $context->id); 03762 03763 if ($extrawheretest) { 03764 $extrawheretest = ' AND ' . $extrawheretest; 03765 $params = array_merge($params, $whereparams); 03766 } 03767 03768 $sql = "SELECT DISTINCT $fields, ra.roleid 03769 FROM {role_assignments} ra 03770 JOIN {user} u ON u.id = ra.userid 03771 JOIN {role} r ON ra.roleid = r.id 03772 $groupjoin 03773 WHERE (ra.contextid = ? $parentcontexts) 03774 $roleselect 03775 $groupselect 03776 $extrawheretest 03777 ORDER BY $sort"; // join now so that we can just use fullname() later 03778 03779 return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum); 03780 } 03781 03790 function count_role_users($roleid, context $context, $parent = false) { 03791 global $DB; 03792 03793 if ($parent) { 03794 if ($contexts = $context->get_parent_context_ids()) { 03795 $parentcontexts = ' OR r.contextid IN ('.implode(',', $contexts).')'; 03796 } else { 03797 $parentcontexts = ''; 03798 } 03799 } else { 03800 $parentcontexts = ''; 03801 } 03802 03803 if ($roleid) { 03804 list($rids, $params) = $DB->get_in_or_equal($roleid, SQL_PARAMS_QM); 03805 $roleselect = "AND r.roleid $rids"; 03806 } else { 03807 $params = array(); 03808 $roleselect = ''; 03809 } 03810 03811 array_unshift($params, $context->id); 03812 03813 $sql = "SELECT COUNT(u.id) 03814 FROM {role_assignments} r 03815 JOIN {user} u ON u.id = r.userid 03816 WHERE (r.contextid = ? $parentcontexts) 03817 $roleselect 03818 AND u.deleted = 0"; 03819 03820 return $DB->count_records_sql($sql, $params); 03821 } 03822 03836 function get_user_capability_course($capability, $userid = null, $doanything = true, $fieldsexceptid = '', $orderby = '') { 03837 global $DB; 03838 03839 // Convert fields list and ordering 03840 $fieldlist = ''; 03841 if ($fieldsexceptid) { 03842 $fields = explode(',', $fieldsexceptid); 03843 foreach($fields as $field) { 03844 $fieldlist .= ',c.'.$field; 03845 } 03846 } 03847 if ($orderby) { 03848 $fields = explode(',', $orderby); 03849 $orderby = ''; 03850 foreach($fields as $field) { 03851 if ($orderby) { 03852 $orderby .= ','; 03853 } 03854 $orderby .= 'c.'.$field; 03855 } 03856 $orderby = 'ORDER BY '.$orderby; 03857 } 03858 03859 // Obtain a list of everything relevant about all courses including context. 03860 // Note the result can be used directly as a context (we are going to), the course 03861 // fields are just appended. 03862 03863 $contextpreload = context_helper::get_preload_record_columns_sql('x'); 03864 03865 $courses = array(); 03866 $rs = $DB->get_recordset_sql("SELECT c.id $fieldlist, $contextpreload 03867 FROM {course} c 03868 JOIN {context} x ON (c.id=x.instanceid AND x.contextlevel=".CONTEXT_COURSE.") 03869 $orderby"); 03870 // Check capability for each course in turn 03871 foreach ($rs as $course) { 03872 context_helper::preload_from_record($course); 03873 $context = context_course::instance($course->id); 03874 if (has_capability($capability, $context, $userid, $doanything)) { 03875 // We've got the capability. Make the record look like a course record 03876 // and store it 03877 $courses[] = $course; 03878 } 03879 } 03880 $rs->close(); 03881 return empty($courses) ? false : $courses; 03882 } 03883 03891 function get_roles_on_exact_context(context $context) { 03892 global $DB; 03893 03894 return $DB->get_records_sql("SELECT r.* 03895 FROM {role_assignments} ra, {role} r 03896 WHERE ra.roleid = r.id AND ra.contextid = ?", 03897 array($context->id)); 03898 } 03899 03917 function role_switch($roleid, context $context) { 03918 global $USER; 03919 03920 // 03921 // Plan of action 03922 // 03923 // - Add the ghost RA to $USER->access 03924 // as $USER->access['rsw'][$path] = $roleid 03925 // 03926 // - Make sure $USER->access['rdef'] has the roledefs 03927 // it needs to honour the switcherole 03928 // 03929 // Roledefs will get loaded "deep" here - down to the last child 03930 // context. Note that 03931 // 03932 // - When visiting subcontexts, our selective accessdata loading 03933 // will still work fine - though those ra/rdefs will be ignored 03934 // appropriately while the switch is in place 03935 // 03936 // - If a switcherole happens at a category with tons of courses 03937 // (that have many overrides for switched-to role), the session 03938 // will get... quite large. Sometimes you just can't win. 03939 // 03940 // To un-switch just unset($USER->access['rsw'][$path]) 03941 // 03942 // Note: it is not possible to switch to roles that do not have course:view 03943 03944 if (!isset($USER->access)) { 03945 load_all_capabilities(); 03946 } 03947 03948 03949 // Add the switch RA 03950 if ($roleid == 0) { 03951 unset($USER->access['rsw'][$context->path]); 03952 return true; 03953 } 03954 03955 $USER->access['rsw'][$context->path] = $roleid; 03956 03957 // Load roledefs 03958 load_role_access_by_context($roleid, $context, $USER->access); 03959 03960 return true; 03961 } 03962 03973 function is_role_switched($courseid) { 03974 global $USER; 03975 $context = context_course::instance($courseid, MUST_EXIST); 03976 return (!empty($USER->access['rsw'][$context->path])); 03977 } 03978 03985 function get_roles_with_override_on_context(context $context) { 03986 global $DB; 03987 03988 return $DB->get_records_sql("SELECT r.* 03989 FROM {role_capabilities} rc, {role} r 03990 WHERE rc.roleid = r.id AND rc.contextid = ?", 03991 array($context->id)); 03992 } 03993 04001 function get_capabilities_from_role_on_context($role, context $context) { 04002 global $DB; 04003 04004 return $DB->get_records_sql("SELECT * 04005 FROM {role_capabilities} 04006 WHERE contextid = ? AND roleid = ?", 04007 array($context->id, $role->id)); 04008 } 04009 04017 function get_roles_with_assignment_on_context(context $context) { 04018 global $DB; 04019 04020 return $DB->get_records_sql("SELECT r.* 04021 FROM {role_assignments} ra, {role} r 04022 WHERE ra.roleid = r.id AND ra.contextid = ?", 04023 array($context->id)); 04024 } 04025 04033 function get_users_from_role_on_context($role, context $context) { 04034 global $DB; 04035 04036 return $DB->get_records_sql("SELECT * 04037 FROM {role_assignments} 04038 WHERE contextid = ? AND roleid = ?", 04039 array($context->id, $role->id)); 04040 } 04041 04051 function user_has_role_assignment($userid, $roleid, $contextid = 0) { 04052 global $DB; 04053 04054 if ($contextid) { 04055 if (!$context = context::instance_by_id($contextid, IGNORE_MISSING)) { 04056 return false; 04057 } 04058 $parents = $context->get_parent_context_ids(true); 04059 list($contexts, $params) = $DB->get_in_or_equal($parents, SQL_PARAMS_NAMED, 'r'); 04060 $params['userid'] = $userid; 04061 $params['roleid'] = $roleid; 04062 04063 $sql = "SELECT COUNT(ra.id) 04064 FROM {role_assignments} ra 04065 WHERE ra.userid = :userid AND ra.roleid = :roleid AND ra.contextid $contexts"; 04066 04067 $count = $DB->get_field_sql($sql, $params); 04068 return ($count > 0); 04069 04070 } else { 04071 return $DB->record_exists('role_assignments', array('userid'=>$userid, 'roleid'=>$roleid)); 04072 } 04073 } 04074 04082 function role_get_name($role, context_course $coursecontext) { 04083 global $DB; 04084 04085 if ($r = $DB->get_record('role_names', array('roleid'=>$role->id, 'contextid'=>$coursecontext->id))) { 04086 return strip_tags(format_string($r->name)); 04087 } else { 04088 return strip_tags(format_string($role->name)); 04089 } 04090 } 04091 04100 function role_fix_names($roleoptions, context $context, $rolenamedisplay = ROLENAME_ALIAS) { 04101 global $DB; 04102 04103 // Make sure we have a course context. 04104 $coursecontext = $context->get_course_context(false); 04105 04106 // Make sure we are working with an array roleid => name. Normally we 04107 // want to use the unlocalised name if the localised one is not present. 04108 $newnames = array(); 04109 foreach ($roleoptions as $rid => $roleorname) { 04110 if ($rolenamedisplay != ROLENAME_ALIAS_RAW) { 04111 if (is_object($roleorname)) { 04112 $newnames[$rid] = $roleorname->name; 04113 } else { 04114 $newnames[$rid] = $roleorname; 04115 } 04116 } else { 04117 $newnames[$rid] = ''; 04118 } 04119 } 04120 04121 // If necessary, get the localised names. 04122 if ($rolenamedisplay != ROLENAME_ORIGINAL && !empty($coursecontext->id)) { 04123 // The get the relevant renames, and use them. 04124 $aliasnames = $DB->get_records('role_names', array('contextid'=>$coursecontext->id)); 04125 foreach ($aliasnames as $alias) { 04126 if (isset($newnames[$alias->roleid])) { 04127 if ($rolenamedisplay == ROLENAME_ALIAS || $rolenamedisplay == ROLENAME_ALIAS_RAW) { 04128 $newnames[$alias->roleid] = $alias->name; 04129 } else if ($rolenamedisplay == ROLENAME_BOTH) { 04130 $newnames[$alias->roleid] = $alias->name . ' (' . $roleoptions[$alias->roleid] . ')'; 04131 } 04132 } 04133 } 04134 } 04135 04136 // Finally, apply format_string and put the result in the right place. 04137 foreach ($roleoptions as $rid => $roleorname) { 04138 if ($rolenamedisplay != ROLENAME_ALIAS_RAW) { 04139 $newnames[$rid] = strip_tags(format_string($newnames[$rid])); 04140 } 04141 if (is_object($roleorname)) { 04142 $roleoptions[$rid]->localname = $newnames[$rid]; 04143 } else { 04144 $roleoptions[$rid] = $newnames[$rid]; 04145 } 04146 } 04147 return $roleoptions; 04148 } 04149 04163 function component_level_changed($cap, $comp, $contextlevel) { 04164 04165 if (strstr($cap->component, '/') && strstr($comp, '/')) { 04166 $compsa = explode('/', $cap->component); 04167 $compsb = explode('/', $comp); 04168 04169 // list of system reports 04170 if (($compsa[0] == 'report') && ($compsb[0] == 'report')) { 04171 return false; 04172 } 04173 04174 // we are in gradebook, still 04175 if (($compsa[0] == 'gradeexport' || $compsa[0] == 'gradeimport' || $compsa[0] == 'gradereport') && 04176 ($compsb[0] == 'gradeexport' || $compsb[0] == 'gradeimport' || $compsb[0] == 'gradereport')) { 04177 return false; 04178 } 04179 04180 if (($compsa[0] == 'coursereport') && ($compsb[0] == 'coursereport')) { 04181 return false; 04182 } 04183 } 04184 04185 return ($cap->component != $comp || $cap->contextlevel != $contextlevel); 04186 } 04187 04195 function fix_role_sortorder($allroles) { 04196 global $DB; 04197 04198 $rolesort = array(); 04199 $i = 0; 04200 foreach ($allroles as $role) { 04201 $rolesort[$i] = $role->id; 04202 if ($role->sortorder != $i) { 04203 $r = new stdClass(); 04204 $r->id = $role->id; 04205 $r->sortorder = $i; 04206 $DB->update_record('role', $r); 04207 $allroles[$role->id]->sortorder = $i; 04208 } 04209 $i++; 04210 } 04211 return $rolesort; 04212 } 04213 04221 function switch_roles($first, $second) { 04222 global $DB; 04223 $temp = $DB->get_field('role', 'MAX(sortorder) + 1', array()); 04224 $result = $DB->set_field('role', 'sortorder', $temp, array('sortorder' => $first->sortorder)); 04225 $result = $result && $DB->set_field('role', 'sortorder', $first->sortorder, array('sortorder' => $second->sortorder)); 04226 $result = $result && $DB->set_field('role', 'sortorder', $second->sortorder, array('sortorder' => $temp)); 04227 return $result; 04228 } 04229 04236 function role_cap_duplicate($sourcerole, $targetrole) { 04237 global $DB; 04238 04239 $systemcontext = context_system::instance(); 04240 $caps = $DB->get_records_sql("SELECT * 04241 FROM {role_capabilities} 04242 WHERE roleid = ? AND contextid = ?", 04243 array($sourcerole->id, $systemcontext->id)); 04244 // adding capabilities 04245 foreach ($caps as $cap) { 04246 unset($cap->id); 04247 $cap->roleid = $targetrole; 04248 $DB->insert_record('role_capabilities', $cap); 04249 } 04250 } 04251 04262 function get_roles_with_cap_in_context($context, $capability) { 04263 global $DB; 04264 04265 $ctxids = trim($context->path, '/'); // kill leading slash 04266 $ctxids = str_replace('/', ',', $ctxids); 04267 04268 $sql = "SELECT rc.id, rc.roleid, rc.permission, ctx.depth 04269 FROM {role_capabilities} rc 04270 JOIN {context} ctx ON ctx.id = rc.contextid 04271 WHERE rc.capability = :cap AND ctx.id IN ($ctxids) 04272 ORDER BY rc.roleid ASC, ctx.depth DESC"; 04273 $params = array('cap'=>$capability); 04274 04275 if (!$capdefs = $DB->get_records_sql($sql, $params)) { 04276 // no cap definitions --> no capability 04277 return array(array(), array()); 04278 } 04279 04280 $forbidden = array(); 04281 $needed = array(); 04282 foreach($capdefs as $def) { 04283 if (isset($forbidden[$def->roleid])) { 04284 continue; 04285 } 04286 if ($def->permission == CAP_PROHIBIT) { 04287 $forbidden[$def->roleid] = $def->roleid; 04288 unset($needed[$def->roleid]); 04289 continue; 04290 } 04291 if (!isset($needed[$def->roleid])) { 04292 if ($def->permission == CAP_ALLOW) { 04293 $needed[$def->roleid] = true; 04294 } else if ($def->permission == CAP_PREVENT) { 04295 $needed[$def->roleid] = false; 04296 } 04297 } 04298 } 04299 unset($capdefs); 04300 04301 // remove all those roles not allowing 04302 foreach($needed as $key=>$value) { 04303 if (!$value) { 04304 unset($needed[$key]); 04305 } else { 04306 $needed[$key] = $key; 04307 } 04308 } 04309 04310 return array($needed, $forbidden); 04311 } 04312 04321 function get_roles_with_caps_in_context($context, $capabilities) { 04322 $neededarr = array(); 04323 $forbiddenarr = array(); 04324 foreach($capabilities as $caprequired) { 04325 list($neededarr[], $forbiddenarr[]) = get_roles_with_cap_in_context($context, $caprequired); 04326 } 04327 04328 $rolesthatcanrate = array(); 04329 if (!empty($neededarr)) { 04330 foreach ($neededarr as $needed) { 04331 if (empty($rolesthatcanrate)) { 04332 $rolesthatcanrate = $needed; 04333 } else { 04334 //only want roles that have all caps 04335 $rolesthatcanrate = array_intersect_key($rolesthatcanrate,$needed); 04336 } 04337 } 04338 } 04339 if (!empty($forbiddenarr) && !empty($rolesthatcanrate)) { 04340 foreach ($forbiddenarr as $forbidden) { 04341 //remove any roles that are forbidden any of the caps 04342 $rolesthatcanrate = array_diff($rolesthatcanrate, $forbidden); 04343 } 04344 } 04345 return $rolesthatcanrate; 04346 } 04347 04356 function get_role_names_with_caps_in_context($context, $capabilities) { 04357 global $DB; 04358 04359 $rolesthatcanrate = get_roles_with_caps_in_context($context, $capabilities); 04360 04361 $allroles = array(); 04362 $roles = $DB->get_records('role', null, 'sortorder DESC'); 04363 foreach ($roles as $roleid=>$role) { 04364 $allroles[$roleid] = $role->name; 04365 } 04366 04367 $rolenames = array(); 04368 foreach ($rolesthatcanrate as $r) { 04369 $rolenames[$r] = $allroles[$r]; 04370 } 04371 $rolenames = role_fix_names($rolenames, $context); 04372 return $rolenames; 04373 } 04374 04384 function prohibit_is_removable($roleid, context $context, $capability) { 04385 global $DB; 04386 04387 $ctxids = trim($context->path, '/'); // kill leading slash 04388 $ctxids = str_replace('/', ',', $ctxids); 04389 04390 $params = array('roleid'=>$roleid, 'cap'=>$capability, 'prohibit'=>CAP_PROHIBIT); 04391 04392 $sql = "SELECT ctx.id 04393 FROM {role_capabilities} rc 04394 JOIN {context} ctx ON ctx.id = rc.contextid 04395 WHERE rc.roleid = :roleid AND rc.permission = :prohibit AND rc.capability = :cap AND ctx.id IN ($ctxids) 04396 ORDER BY ctx.depth DESC"; 04397 04398 if (!$prohibits = $DB->get_records_sql($sql, $params)) { 04399 // no prohibits == nothing to remove 04400 return true; 04401 } 04402 04403 if (count($prohibits) > 1) { 04404 // more prohibints can not be removed 04405 return false; 04406 } 04407 04408 return !empty($prohibits[$context->id]); 04409 } 04410 04420 function role_change_permission($roleid, $context, $capname, $permission) { 04421 global $DB; 04422 04423 if ($permission == CAP_INHERIT) { 04424 unassign_capability($capname, $roleid, $context->id); 04425 $context->mark_dirty(); 04426 return; 04427 } 04428 04429 $ctxids = trim($context->path, '/'); // kill leading slash 04430 $ctxids = str_replace('/', ',', $ctxids); 04431 04432 $params = array('roleid'=>$roleid, 'cap'=>$capname); 04433 04434 $sql = "SELECT ctx.id, rc.permission, ctx.depth 04435 FROM {role_capabilities} rc 04436 JOIN {context} ctx ON ctx.id = rc.contextid 04437 WHERE rc.roleid = :roleid AND rc.capability = :cap AND ctx.id IN ($ctxids) 04438 ORDER BY ctx.depth DESC"; 04439 04440 if ($existing = $DB->get_records_sql($sql, $params)) { 04441 foreach($existing as $e) { 04442 if ($e->permission == CAP_PROHIBIT) { 04443 // prohibit can not be overridden, no point in changing anything 04444 return; 04445 } 04446 } 04447 $lowest = array_shift($existing); 04448 if ($lowest->permission == $permission) { 04449 // permission already set in this context or parent - nothing to do 04450 return; 04451 } 04452 if ($existing) { 04453 $parent = array_shift($existing); 04454 if ($parent->permission == $permission) { 04455 // permission already set in parent context or parent - just unset in this context 04456 // we do this because we want as few overrides as possible for performance reasons 04457 unassign_capability($capname, $roleid, $context->id); 04458 $context->mark_dirty(); 04459 return; 04460 } 04461 } 04462 04463 } else { 04464 if ($permission == CAP_PREVENT) { 04465 // nothing means role does not have permission 04466 return; 04467 } 04468 } 04469 04470 // assign the needed capability 04471 assign_capability($capname, $permission, $roleid, $context->id, true); 04472 04473 // force cap reloading 04474 $context->mark_dirty(); 04475 } 04476 04477 04490 abstract class context extends stdClass { 04491 04492 /* 04493 * Google confirms that no other important framework is using "context" class, 04494 * we could use something else like mcontext or moodle_context, but we need to type 04495 * this very often which would be annoying and it would take too much space... 04496 * 04497 * This class is derived from stdClass for backwards compatibility with 04498 * odl $context record that was returned from DML $DB->get_record() 04499 */ 04500 04501 protected $_id; 04502 protected $_contextlevel; 04503 protected $_instanceid; 04504 protected $_path; 04505 protected $_depth; 04506 04507 /* context caching info */ 04508 04509 private static $cache_contextsbyid = array(); 04510 private static $cache_contexts = array(); 04511 protected static $cache_count = 0; // why do we do count contexts? Because count($array) is horribly slow for large arrays 04512 04513 protected static $cache_preloaded = array(); 04514 protected static $systemcontext = null; 04515 04520 protected static function reset_caches() { 04521 self::$cache_contextsbyid = array(); 04522 self::$cache_contexts = array(); 04523 self::$cache_count = 0; 04524 self::$cache_preloaded = array(); 04525 04526 self::$systemcontext = null; 04527 } 04528 04537 protected static function cache_add(context $context) { 04538 if (isset(self::$cache_contextsbyid[$context->id])) { 04539 // already cached, no need to do anything - this is relatively cheap, we do all this because count() is slow 04540 return; 04541 } 04542 04543 if (self::$cache_count >= CONTEXT_CACHE_MAX_SIZE) { 04544 $i = 0; 04545 foreach(self::$cache_contextsbyid as $ctx) { 04546 $i++; 04547 if ($i <= 100) { 04548 // we want to keep the first contexts to be loaded on this page, hopefully they will be needed again later 04549 continue; 04550 } 04551 if ($i > (CONTEXT_CACHE_MAX_SIZE / 3)) { 04552 // we remove oldest third of the contexts to make room for more contexts 04553 break; 04554 } 04555 unset(self::$cache_contextsbyid[$ctx->id]); 04556 unset(self::$cache_contexts[$ctx->contextlevel][$ctx->instanceid]); 04557 self::$cache_count--; 04558 } 04559 } 04560 04561 self::$cache_contexts[$context->contextlevel][$context->instanceid] = $context; 04562 self::$cache_contextsbyid[$context->id] = $context; 04563 self::$cache_count++; 04564 } 04565 04573 protected static function cache_remove(context $context) { 04574 if (!isset(self::$cache_contextsbyid[$context->id])) { 04575 // not cached, no need to do anything - this is relatively cheap, we do all this because count() is slow 04576 return; 04577 } 04578 unset(self::$cache_contexts[$context->contextlevel][$context->instanceid]); 04579 unset(self::$cache_contextsbyid[$context->id]); 04580 04581 self::$cache_count--; 04582 04583 if (self::$cache_count < 0) { 04584 self::$cache_count = 0; 04585 } 04586 } 04587 04596 protected static function cache_get($contextlevel, $instance) { 04597 if (isset(self::$cache_contexts[$contextlevel][$instance])) { 04598 return self::$cache_contexts[$contextlevel][$instance]; 04599 } 04600 return false; 04601 } 04602 04610 protected static function cache_get_by_id($id) { 04611 if (isset(self::$cache_contextsbyid[$id])) { 04612 return self::$cache_contextsbyid[$id]; 04613 } 04614 return false; 04615 } 04616 04624 protected static function preload_from_record(stdClass $rec) { 04625 if (empty($rec->ctxid) or empty($rec->ctxlevel) or empty($rec->ctxinstance) or empty($rec->ctxpath) or empty($rec->ctxdepth)) { 04626 // $rec does not have enough data, passed here repeatedly or context does not exist yet 04627 return; 04628 } 04629 04630 // note: in PHP5 the objects are passed by reference, no need to return $rec 04631 $record = new stdClass(); 04632 $record->id = $rec->ctxid; unset($rec->ctxid); 04633 $record->contextlevel = $rec->ctxlevel; unset($rec->ctxlevel); 04634 $record->instanceid = $rec->ctxinstance; unset($rec->ctxinstance); 04635 $record->path = $rec->ctxpath; unset($rec->ctxpath); 04636 $record->depth = $rec->ctxdepth; unset($rec->ctxdepth); 04637 04638 return context::create_instance_from_record($record); 04639 } 04640 04641 04642 // ====== magic methods ======= 04643 04649 public function __set($name, $value) { 04650 debugging('Can not change context instance properties!'); 04651 } 04652 04658 public function __get($name) { 04659 switch ($name) { 04660 case 'id': return $this->_id; 04661 case 'contextlevel': return $this->_contextlevel; 04662 case 'instanceid': return $this->_instanceid; 04663 case 'path': return $this->_path; 04664 case 'depth': return $this->_depth; 04665 04666 default: 04667 debugging('Invalid context property accessed! '.$name); 04668 return null; 04669 } 04670 } 04671 04677 public function __isset($name) { 04678 switch ($name) { 04679 case 'id': return isset($this->_id); 04680 case 'contextlevel': return isset($this->_contextlevel); 04681 case 'instanceid': return isset($this->_instanceid); 04682 case 'path': return isset($this->_path); 04683 case 'depth': return isset($this->_depth); 04684 04685 default: return false; 04686 } 04687 04688 } 04689 04694 public function __unset($name) { 04695 debugging('Can not unset context instance properties!'); 04696 } 04697 04698 // ====== general context methods ====== 04699 04706 protected function __construct(stdClass $record) { 04707 $this->_id = $record->id; 04708 $this->_contextlevel = (int)$record->contextlevel; 04709 $this->_instanceid = $record->instanceid; 04710 $this->_path = $record->path; 04711 $this->_depth = $record->depth; 04712 } 04713 04720 protected static function create_instance_from_record(stdClass $record) { 04721 $classname = context_helper::get_class_for_level($record->contextlevel); 04722 04723 if ($context = context::cache_get_by_id($record->id)) { 04724 return $context; 04725 } 04726 04727 $context = new $classname($record); 04728 context::cache_add($context); 04729 04730 return $context; 04731 } 04732 04738 protected static function merge_context_temp_table() { 04739 global $DB; 04740 04741 /* MDL-11347: 04742 * - mysql does not allow to use FROM in UPDATE statements 04743 * - using two tables after UPDATE works in mysql, but might give unexpected 04744 * results in pg 8 (depends on configuration) 04745 * - using table alias in UPDATE does not work in pg < 8.2 04746 * 04747 * Different code for each database - mostly for performance reasons 04748 */ 04749 04750 $dbfamily = $DB->get_dbfamily(); 04751 if ($dbfamily == 'mysql') { 04752 $updatesql = "UPDATE {context} ct, {context_temp} temp 04753 SET ct.path = temp.path, 04754 ct.depth = temp.depth 04755 WHERE ct.id = temp.id"; 04756 } else if ($dbfamily == 'oracle') { 04757 $updatesql = "UPDATE {context} ct 04758 SET (ct.path, ct.depth) = 04759 (SELECT temp.path, temp.depth 04760 FROM {context_temp} temp 04761 WHERE temp.id=ct.id) 04762 WHERE EXISTS (SELECT 'x' 04763 FROM {context_temp} temp 04764 WHERE temp.id = ct.id)"; 04765 } else if ($dbfamily == 'postgres' or $dbfamily == 'mssql') { 04766 $updatesql = "UPDATE {context} 04767 SET path = temp.path, 04768 depth = temp.depth 04769 FROM {context_temp} temp 04770 WHERE temp.id={context}.id"; 04771 } else { 04772 // sqlite and others 04773 $updatesql = "UPDATE {context} 04774 SET path = (SELECT path FROM {context_temp} WHERE id = {context}.id), 04775 depth = (SELECT depth FROM {context_temp} WHERE id = {context}.id) 04776 WHERE id IN (SELECT id FROM {context_temp})"; 04777 } 04778 04779 $DB->execute($updatesql); 04780 } 04781 04791 public static function instance_by_id($id, $strictness = MUST_EXIST) { 04792 global $DB; 04793 04794 if (get_called_class() !== 'context' and get_called_class() !== 'context_helper') { 04795 // some devs might confuse context->id and instanceid, better prevent these mistakes completely 04796 throw new coding_exception('use only context::instance_by_id() for real context levels use ::instance() methods'); 04797 } 04798 04799 if ($id == SYSCONTEXTID) { 04800 return context_system::instance(0, $strictness); 04801 } 04802 04803 if (is_array($id) or is_object($id) or empty($id)) { 04804 throw new coding_exception('Invalid context id specified context::instance_by_id()'); 04805 } 04806 04807 if ($context = context::cache_get_by_id($id)) { 04808 return $context; 04809 } 04810 04811 if ($record = $DB->get_record('context', array('id'=>$id), '*', $strictness)) { 04812 return context::create_instance_from_record($record); 04813 } 04814 04815 return false; 04816 } 04817 04824 public function update_moved(context $newparent) { 04825 global $DB; 04826 04827 $frompath = $this->_path; 04828 $newpath = $newparent->path . '/' . $this->_id; 04829 04830 $trans = $DB->start_delegated_transaction(); 04831 04832 $this->mark_dirty(); 04833 04834 $setdepth = ''; 04835 if (($newparent->depth +1) != $this->_depth) { 04836 $diff = $newparent->depth - $this->_depth + 1; 04837 $setdepth = ", depth = depth + $diff"; 04838 } 04839 $sql = "UPDATE {context} 04840 SET path = ? 04841 $setdepth 04842 WHERE id = ?"; 04843 $params = array($newpath, $this->_id); 04844 $DB->execute($sql, $params); 04845 04846 $this->_path = $newpath; 04847 $this->_depth = $newparent->depth + 1; 04848 04849 $sql = "UPDATE {context} 04850 SET path = ".$DB->sql_concat("?", $DB->sql_substr("path", strlen($frompath)+1))." 04851 $setdepth 04852 WHERE path LIKE ?"; 04853 $params = array($newpath, "{$frompath}/%"); 04854 $DB->execute($sql, $params); 04855 04856 $this->mark_dirty(); 04857 04858 context::reset_caches(); 04859 04860 $trans->allow_commit(); 04861 } 04862 04869 public function reset_paths($rebuild = true) { 04870 global $DB; 04871 04872 if ($this->_path) { 04873 $this->mark_dirty(); 04874 } 04875 $DB->set_field_select('context', 'depth', 0, "path LIKE '%/$this->_id/%'"); 04876 $DB->set_field_select('context', 'path', NULL, "path LIKE '%/$this->_id/%'"); 04877 if ($this->_contextlevel != CONTEXT_SYSTEM) { 04878 $DB->set_field('context', 'depth', 0, array('id'=>$this->_id)); 04879 $DB->set_field('context', 'path', NULL, array('id'=>$this->_id)); 04880 $this->_depth = 0; 04881 $this->_path = null; 04882 } 04883 04884 if ($rebuild) { 04885 context_helper::build_all_paths(false); 04886 } 04887 04888 context::reset_caches(); 04889 } 04890 04894 public function delete_content() { 04895 global $CFG, $DB; 04896 04897 blocks_delete_all_for_context($this->_id); 04898 filter_delete_all_for_context($this->_id); 04899 04900 require_once($CFG->dirroot . '/comment/lib.php'); 04901 comment::delete_comments(array('contextid'=>$this->_id)); 04902 04903 require_once($CFG->dirroot.'/rating/lib.php'); 04904 $delopt = new stdclass(); 04905 $delopt->contextid = $this->_id; 04906 $rm = new rating_manager(); 04907 $rm->delete_ratings($delopt); 04908 04909 // delete all files attached to this context 04910 $fs = get_file_storage(); 04911 $fs->delete_area_files($this->_id); 04912 04913 // delete all advanced grading data attached to this context 04914 require_once($CFG->dirroot.'/grade/grading/lib.php'); 04915 grading_manager::delete_all_for_context($this->_id); 04916 04917 // now delete stuff from role related tables, role_unassign_all 04918 // and unenrol should be called earlier to do proper cleanup 04919 $DB->delete_records('role_assignments', array('contextid'=>$this->_id)); 04920 $DB->delete_records('role_capabilities', array('contextid'=>$this->_id)); 04921 $DB->delete_records('role_names', array('contextid'=>$this->_id)); 04922 } 04923 04927 public function delete() { 04928 global $DB; 04929 04930 // double check the context still exists 04931 if (!$DB->record_exists('context', array('id'=>$this->_id))) { 04932 context::cache_remove($this); 04933 return; 04934 } 04935 04936 $this->delete_content(); 04937 $DB->delete_records('context', array('id'=>$this->_id)); 04938 // purge static context cache if entry present 04939 context::cache_remove($this); 04940 04941 // do not mark dirty contexts if parents unknown 04942 if (!is_null($this->_path) and $this->_depth > 0) { 04943 $this->mark_dirty(); 04944 } 04945 } 04946 04947 // ====== context level related methods ====== 04948 04958 protected static function insert_context_record($contextlevel, $instanceid, $parentpath) { 04959 global $DB; 04960 04961 $record = new stdClass(); 04962 $record->contextlevel = $contextlevel; 04963 $record->instanceid = $instanceid; 04964 $record->depth = 0; 04965 $record->path = null; //not known before insert 04966 04967 $record->id = $DB->insert_record('context', $record); 04968 04969 // now add path if known - it can be added later 04970 if (!is_null($parentpath)) { 04971 $record->path = $parentpath.'/'.$record->id; 04972 $record->depth = substr_count($record->path, '/'); 04973 $DB->update_record('context', $record); 04974 } 04975 04976 return $record; 04977 } 04978 04988 public function get_context_name($withprefix = true, $short = false) { 04989 // must be implemented in all context levels 04990 throw new coding_exception('can not get name of abstract context'); 04991 } 04992 04998 public abstract function get_url(); 04999 05005 public abstract function get_capabilities(); 05006 05022 public function get_child_contexts() { 05023 global $DB; 05024 05025 $sql = "SELECT ctx.* 05026 FROM {context} ctx 05027 WHERE ctx.path LIKE ?"; 05028 $params = array($this->_path.'/%'); 05029 $records = $DB->get_records_sql($sql, $params); 05030 05031 $result = array(); 05032 foreach ($records as $record) { 05033 $result[$record->id] = context::create_instance_from_record($record); 05034 } 05035 05036 return $result; 05037 } 05038 05046 public function get_parent_contexts($includeself = false) { 05047 if (!$contextids = $this->get_parent_context_ids($includeself)) { 05048 return array(); 05049 } 05050 05051 $result = array(); 05052 foreach ($contextids as $contextid) { 05053 $parent = context::instance_by_id($contextid, MUST_EXIST); 05054 $result[$parent->id] = $parent; 05055 } 05056 05057 return $result; 05058 } 05059 05067 public function get_parent_context_ids($includeself = false) { 05068 if (empty($this->_path)) { 05069 return array(); 05070 } 05071 05072 $parentcontexts = trim($this->_path, '/'); // kill leading slash 05073 $parentcontexts = explode('/', $parentcontexts); 05074 if (!$includeself) { 05075 array_pop($parentcontexts); // and remove its own id 05076 } 05077 05078 return array_reverse($parentcontexts); 05079 } 05080 05086 public function get_parent_context() { 05087 if (empty($this->_path) or $this->_id == SYSCONTEXTID) { 05088 return false; 05089 } 05090 05091 $parentcontexts = trim($this->_path, '/'); // kill leading slash 05092 $parentcontexts = explode('/', $parentcontexts); 05093 array_pop($parentcontexts); // self 05094 $contextid = array_pop($parentcontexts); // immediate parent 05095 05096 return context::instance_by_id($contextid, MUST_EXIST); 05097 } 05098 05105 public function get_course_context($strict = true) { 05106 if ($strict) { 05107 throw new coding_exception('Context does not belong to any course.'); 05108 } else { 05109 return false; 05110 } 05111 } 05112 05119 protected static function get_cleanup_sql() { 05120 throw new coding_exception('get_cleanup_sql() method must be implemented in all context levels'); 05121 } 05122 05130 protected static function build_paths($force) { 05131 throw new coding_exception('build_paths() method must be implemented in all context levels'); 05132 } 05133 05140 protected static function create_level_instances() { 05141 throw new coding_exception('create_level_instances() method must be implemented in all context levels'); 05142 } 05143 05148 public function reload_if_dirty() { 05149 global $ACCESSLIB_PRIVATE, $USER; 05150 05151 // Load dirty contexts list if needed 05152 if (CLI_SCRIPT) { 05153 if (!isset($ACCESSLIB_PRIVATE->dirtycontexts)) { 05154 // we do not load dirty flags in CLI and cron 05155 $ACCESSLIB_PRIVATE->dirtycontexts = array(); 05156 } 05157 } else { 05158 if (!isset($ACCESSLIB_PRIVATE->dirtycontexts)) { 05159 if (!isset($USER->access['time'])) { 05160 // nothing was loaded yet, we do not need to check dirty contexts now 05161 return; 05162 } 05163 // no idea why -2 is there, server cluster time difference maybe... (skodak) 05164 $ACCESSLIB_PRIVATE->dirtycontexts = get_cache_flags('accesslib/dirtycontexts', $USER->access['time']-2); 05165 } 05166 } 05167 05168 foreach ($ACCESSLIB_PRIVATE->dirtycontexts as $path=>$unused) { 05169 if ($path === $this->_path or strpos($this->_path, $path.'/') === 0) { 05170 // reload all capabilities of USER and others - preserving loginas, roleswitches, etc 05171 // and then cleanup any marks of dirtyness... at least from our short term memory! :-) 05172 reload_all_capabilities(); 05173 break; 05174 } 05175 } 05176 } 05177 05181 public function mark_dirty() { 05182 global $CFG, $USER, $ACCESSLIB_PRIVATE; 05183 05184 if (during_initial_install()) { 05185 return; 05186 } 05187 05188 // only if it is a non-empty string 05189 if (is_string($this->_path) && $this->_path !== '') { 05190 set_cache_flag('accesslib/dirtycontexts', $this->_path, 1, time()+$CFG->sessiontimeout); 05191 if (isset($ACCESSLIB_PRIVATE->dirtycontexts)) { 05192 $ACCESSLIB_PRIVATE->dirtycontexts[$this->_path] = 1; 05193 } else { 05194 if (CLI_SCRIPT) { 05195 $ACCESSLIB_PRIVATE->dirtycontexts = array($this->_path => 1); 05196 } else { 05197 if (isset($USER->access['time'])) { 05198 $ACCESSLIB_PRIVATE->dirtycontexts = get_cache_flags('accesslib/dirtycontexts', $USER->access['time']-2); 05199 } else { 05200 $ACCESSLIB_PRIVATE->dirtycontexts = array($this->_path => 1); 05201 } 05202 // flags not loaded yet, it will be done later in $context->reload_if_dirty() 05203 } 05204 } 05205 } 05206 } 05207 } 05208 05209 05222 class context_helper extends context { 05223 05224 private static $alllevels = array( 05225 CONTEXT_SYSTEM => 'context_system', 05226 CONTEXT_USER => 'context_user', 05227 CONTEXT_COURSECAT => 'context_coursecat', 05228 CONTEXT_COURSE => 'context_course', 05229 CONTEXT_MODULE => 'context_module', 05230 CONTEXT_BLOCK => 'context_block', 05231 ); 05232 05236 protected function __construct() { 05237 } 05238 05246 public static function get_class_for_level($contextlevel) { 05247 if (isset(self::$alllevels[$contextlevel])) { 05248 return self::$alllevels[$contextlevel]; 05249 } else { 05250 throw new coding_exception('Invalid context level specified'); 05251 } 05252 } 05253 05260 public static function get_all_levels() { 05261 return self::$alllevels; 05262 } 05263 05271 public static function cleanup_instances() { 05272 global $DB; 05273 $sqls = array(); 05274 foreach (self::$alllevels as $level=>$classname) { 05275 $sqls[] = $classname::get_cleanup_sql(); 05276 } 05277 05278 $sql = implode(" UNION ", $sqls); 05279 05280 // it is probably better to use transactions, it might be faster too 05281 $transaction = $DB->start_delegated_transaction(); 05282 05283 $rs = $DB->get_recordset_sql($sql); 05284 foreach ($rs as $record) { 05285 $context = context::create_instance_from_record($record); 05286 $context->delete(); 05287 } 05288 $rs->close(); 05289 05290 $transaction->allow_commit(); 05291 } 05292 05301 public static function create_instances($contextlevel = null, $buildpaths = true) { 05302 foreach (self::$alllevels as $level=>$classname) { 05303 if ($contextlevel and $level > $contextlevel) { 05304 // skip potential sub-contexts 05305 continue; 05306 } 05307 $classname::create_level_instances(); 05308 if ($buildpaths) { 05309 $classname::build_paths(false); 05310 } 05311 } 05312 } 05313 05321 public static function build_all_paths($force = false) { 05322 foreach (self::$alllevels as $classname) { 05323 $classname::build_paths($force); 05324 } 05325 05326 // reset static course cache - it might have incorrect cached data 05327 accesslib_clear_all_caches(true); 05328 } 05329 05334 public static function reset_caches() { 05335 context::reset_caches(); 05336 } 05337 05347 public static function get_preload_record_columns($tablealias) { 05348 return array("$tablealias.id"=>"ctxid", "$tablealias.path"=>"ctxpath", "$tablealias.depth"=>"ctxdepth", "$tablealias.contextlevel"=>"ctxlevel", "$tablealias.instanceid"=>"ctxinstance"); 05349 } 05350 05360 public static function get_preload_record_columns_sql($tablealias) { 05361 return "$tablealias.id AS ctxid, $tablealias.path AS ctxpath, $tablealias.depth AS ctxdepth, $tablealias.contextlevel AS ctxlevel, $tablealias.instanceid AS ctxinstance"; 05362 } 05363 05373 public static function preload_from_record(stdClass $rec) { 05374 context::preload_from_record($rec); 05375 } 05376 05385 public static function preload_course($courseid) { 05386 // Users can call this multiple times without doing any harm 05387 if (isset(context::$cache_preloaded[$courseid])) { 05388 return; 05389 } 05390 $coursecontext = context_course::instance($courseid); 05391 $coursecontext->get_child_contexts(); 05392 05393 context::$cache_preloaded[$courseid] = true; 05394 } 05395 05404 public static function delete_instance($contextlevel, $instanceid) { 05405 global $DB; 05406 05407 // double check the context still exists 05408 if ($record = $DB->get_record('context', array('contextlevel'=>$contextlevel, 'instanceid'=>$instanceid))) { 05409 $context = context::create_instance_from_record($record); 05410 $context->delete(); 05411 } else { 05412 // we should try to purge the cache anyway 05413 } 05414 } 05415 05423 public static function get_level_name($contextlevel) { 05424 $classname = context_helper::get_class_for_level($contextlevel); 05425 return $classname::get_level_name(); 05426 } 05427 05431 public function get_url() { 05432 } 05433 05437 public function get_capabilities() { 05438 } 05439 } 05440 05441 05447 class context_system extends context { 05453 protected function __construct(stdClass $record) { 05454 parent::__construct($record); 05455 if ($record->contextlevel != CONTEXT_SYSTEM) { 05456 throw new coding_exception('Invalid $record->contextlevel in context_system constructor.'); 05457 } 05458 } 05459 05466 public static function get_level_name() { 05467 return get_string('coresystem'); 05468 } 05469 05477 public function get_context_name($withprefix = true, $short = false) { 05478 return self::get_level_name(); 05479 } 05480 05486 public function get_url() { 05487 return new moodle_url('/'); 05488 } 05489 05495 public function get_capabilities() { 05496 global $DB; 05497 05498 $sort = 'ORDER BY contextlevel,component,name'; // To group them sensibly for display 05499 05500 $params = array(); 05501 $sql = "SELECT * 05502 FROM {capabilities}"; 05503 05504 return $DB->get_records_sql($sql.' '.$sort, $params); 05505 } 05506 05511 protected static function create_level_instances() { 05512 // nothing to do here, the system context is created automatically in installer 05513 self::instance(0); 05514 } 05515 05525 public static function instance($instanceid = 0, $strictness = MUST_EXIST, $cache = true) { 05526 global $DB; 05527 05528 if ($instanceid != 0) { 05529 debugging('context_system::instance(): invalid $id parameter detected, should be 0'); 05530 } 05531 05532 if (defined('SYSCONTEXTID') and $cache) { // dangerous: define this in config.php to eliminate 1 query/page 05533 if (!isset(context::$systemcontext)) { 05534 $record = new stdClass(); 05535 $record->id = SYSCONTEXTID; 05536 $record->contextlevel = CONTEXT_SYSTEM; 05537 $record->instanceid = 0; 05538 $record->path = '/'.SYSCONTEXTID; 05539 $record->depth = 1; 05540 context::$systemcontext = new context_system($record); 05541 } 05542 return context::$systemcontext; 05543 } 05544 05545 05546 try { 05547 // we ignore the strictness completely because system context must except except during install 05548 $record = $DB->get_record('context', array('contextlevel'=>CONTEXT_SYSTEM), '*', MUST_EXIST); 05549 } catch (dml_exception $e) { 05550 //table or record does not exist 05551 if (!during_initial_install()) { 05552 // do not mess with system context after install, it simply must exist 05553 throw $e; 05554 } 05555 $record = null; 05556 } 05557 05558 if (!$record) { 05559 $record = new stdClass(); 05560 $record->contextlevel = CONTEXT_SYSTEM; 05561 $record->instanceid = 0; 05562 $record->depth = 1; 05563 $record->path = null; //not known before insert 05564 05565 try { 05566 if ($DB->count_records('context')) { 05567 // contexts already exist, this is very weird, system must be first!!! 05568 return null; 05569 } 05570 if (defined('SYSCONTEXTID')) { 05571 // this would happen only in unittest on sites that went through weird 1.7 upgrade 05572 $record->id = SYSCONTEXTID; 05573 $DB->import_record('context', $record); 05574 $DB->get_manager()->reset_sequence('context'); 05575 } else { 05576 $record->id = $DB->insert_record('context', $record); 05577 } 05578 } catch (dml_exception $e) { 05579 // can not create context - table does not exist yet, sorry 05580 return null; 05581 } 05582 } 05583 05584 if ($record->instanceid != 0) { 05585 // this is very weird, somebody must be messing with context table 05586 debugging('Invalid system context detected'); 05587 } 05588 05589 if ($record->depth != 1 or $record->path != '/'.$record->id) { 05590 // fix path if necessary, initial install or path reset 05591 $record->depth = 1; 05592 $record->path = '/'.$record->id; 05593 $DB->update_record('context', $record); 05594 } 05595 05596 if (!defined('SYSCONTEXTID')) { 05597 define('SYSCONTEXTID', $record->id); 05598 } 05599 05600 context::$systemcontext = new context_system($record); 05601 return context::$systemcontext; 05602 } 05603 05611 public function get_child_contexts() { 05612 global $DB; 05613 05614 debugging('Fetching of system context child courses is strongly discouraged on production servers (it may eat all available memory)!'); 05615 05616 // Just get all the contexts except for CONTEXT_SYSTEM level 05617 // and hope we don't OOM in the process - don't cache 05618 $sql = "SELECT c.* 05619 FROM {context} c 05620 WHERE contextlevel > ".CONTEXT_SYSTEM; 05621 $records = $DB->get_records_sql($sql); 05622 05623 $result = array(); 05624 foreach ($records as $record) { 05625 $result[$record->id] = context::create_instance_from_record($record); 05626 } 05627 05628 return $result; 05629 } 05630 05637 protected static function get_cleanup_sql() { 05638 $sql = " 05639 SELECT c.* 05640 FROM {context} c 05641 WHERE 1=2 05642 "; 05643 05644 return $sql; 05645 } 05646 05653 protected static function build_paths($force) { 05654 global $DB; 05655 05656 /* note: ignore $force here, we always do full test of system context */ 05657 05658 // exactly one record must exist 05659 $record = $DB->get_record('context', array('contextlevel'=>CONTEXT_SYSTEM), '*', MUST_EXIST); 05660 05661 if ($record->instanceid != 0) { 05662 debugging('Invalid system context detected'); 05663 } 05664 05665 if (defined('SYSCONTEXTID') and $record->id != SYSCONTEXTID) { 05666 debugging('Invalid SYSCONTEXTID detected'); 05667 } 05668 05669 if ($record->depth != 1 or $record->path != '/'.$record->id) { 05670 // fix path if necessary, initial install or path reset 05671 $record->depth = 1; 05672 $record->path = '/'.$record->id; 05673 $DB->update_record('context', $record); 05674 } 05675 } 05676 } 05677 05678 05684 class context_user extends context { 05691 protected function __construct(stdClass $record) { 05692 parent::__construct($record); 05693 if ($record->contextlevel != CONTEXT_USER) { 05694 throw new coding_exception('Invalid $record->contextlevel in context_user constructor.'); 05695 } 05696 } 05697 05704 public static function get_level_name() { 05705 return get_string('user'); 05706 } 05707 05715 public function get_context_name($withprefix = true, $short = false) { 05716 global $DB; 05717 05718 $name = ''; 05719 if ($user = $DB->get_record('user', array('id'=>$this->_instanceid, 'deleted'=>0))) { 05720 if ($withprefix){ 05721 $name = get_string('user').': '; 05722 } 05723 $name .= fullname($user); 05724 } 05725 return $name; 05726 } 05727 05733 public function get_url() { 05734 global $COURSE; 05735 05736 if ($COURSE->id == SITEID) { 05737 $url = new moodle_url('/user/profile.php', array('id'=>$this->_instanceid)); 05738 } else { 05739 $url = new moodle_url('/user/view.php', array('id'=>$this->_instanceid, 'courseid'=>$COURSE->id)); 05740 } 05741 return $url; 05742 } 05743 05749 public function get_capabilities() { 05750 global $DB; 05751 05752 $sort = 'ORDER BY contextlevel,component,name'; // To group them sensibly for display 05753 05754 $extracaps = array('moodle/grade:viewall'); 05755 list($extra, $params) = $DB->get_in_or_equal($extracaps, SQL_PARAMS_NAMED, 'cap'); 05756 $sql = "SELECT * 05757 FROM {capabilities} 05758 WHERE contextlevel = ".CONTEXT_USER." 05759 OR name $extra"; 05760 05761 return $records = $DB->get_records_sql($sql.' '.$sort, $params); 05762 } 05763 05772 public static function instance($instanceid, $strictness = MUST_EXIST) { 05773 global $DB; 05774 05775 if ($context = context::cache_get(CONTEXT_USER, $instanceid)) { 05776 return $context; 05777 } 05778 05779 if (!$record = $DB->get_record('context', array('contextlevel'=>CONTEXT_USER, 'instanceid'=>$instanceid))) { 05780 if ($user = $DB->get_record('user', array('id'=>$instanceid, 'deleted'=>0), 'id', $strictness)) { 05781 $record = context::insert_context_record(CONTEXT_USER, $user->id, '/'.SYSCONTEXTID, 0); 05782 } 05783 } 05784 05785 if ($record) { 05786 $context = new context_user($record); 05787 context::cache_add($context); 05788 return $context; 05789 } 05790 05791 return false; 05792 } 05793 05798 protected static function create_level_instances() { 05799 global $DB; 05800 05801 $sql = "INSERT INTO {context} (contextlevel, instanceid) 05802 SELECT ".CONTEXT_USER.", u.id 05803 FROM {user} u 05804 WHERE u.deleted = 0 05805 AND NOT EXISTS (SELECT 'x' 05806 FROM {context} cx 05807 WHERE u.id = cx.instanceid AND cx.contextlevel=".CONTEXT_USER.")"; 05808 $DB->execute($sql); 05809 } 05810 05817 protected static function get_cleanup_sql() { 05818 $sql = " 05819 SELECT c.* 05820 FROM {context} c 05821 LEFT OUTER JOIN {user} u ON (c.instanceid = u.id AND u.deleted = 0) 05822 WHERE u.id IS NULL AND c.contextlevel = ".CONTEXT_USER." 05823 "; 05824 05825 return $sql; 05826 } 05827 05834 protected static function build_paths($force) { 05835 global $DB; 05836 05837 // first update normal users 05838 $sql = "UPDATE {context} 05839 SET depth = 2, 05840 path = ".$DB->sql_concat("'/".SYSCONTEXTID."/'", 'id')." 05841 WHERE contextlevel=".CONTEXT_USER; 05842 $DB->execute($sql); 05843 } 05844 } 05845 05846 05852 class context_coursecat extends context { 05859 protected function __construct(stdClass $record) { 05860 parent::__construct($record); 05861 if ($record->contextlevel != CONTEXT_COURSECAT) { 05862 throw new coding_exception('Invalid $record->contextlevel in context_coursecat constructor.'); 05863 } 05864 } 05865 05872 public static function get_level_name() { 05873 return get_string('category'); 05874 } 05875 05883 public function get_context_name($withprefix = true, $short = false) { 05884 global $DB; 05885 05886 $name = ''; 05887 if ($category = $DB->get_record('course_categories', array('id'=>$this->_instanceid))) { 05888 if ($withprefix){ 05889 $name = get_string('category').': '; 05890 } 05891 $name .= format_string($category->name, true, array('context' => $this)); 05892 } 05893 return $name; 05894 } 05895 05901 public function get_url() { 05902 return new moodle_url('/course/category.php', array('id'=>$this->_instanceid)); 05903 } 05904 05910 public function get_capabilities() { 05911 global $DB; 05912 05913 $sort = 'ORDER BY contextlevel,component,name'; // To group them sensibly for display 05914 05915 $params = array(); 05916 $sql = "SELECT * 05917 FROM {capabilities} 05918 WHERE contextlevel IN (".CONTEXT_COURSECAT.",".CONTEXT_COURSE.",".CONTEXT_MODULE.",".CONTEXT_BLOCK.")"; 05919 05920 return $DB->get_records_sql($sql.' '.$sort, $params); 05921 } 05922 05931 public static function instance($instanceid, $strictness = MUST_EXIST) { 05932 global $DB; 05933 05934 if ($context = context::cache_get(CONTEXT_COURSECAT, $instanceid)) { 05935 return $context; 05936 } 05937 05938 if (!$record = $DB->get_record('context', array('contextlevel'=>CONTEXT_COURSECAT, 'instanceid'=>$instanceid))) { 05939 if ($category = $DB->get_record('course_categories', array('id'=>$instanceid), 'id,parent', $strictness)) { 05940 if ($category->parent) { 05941 $parentcontext = context_coursecat::instance($category->parent); 05942 $record = context::insert_context_record(CONTEXT_COURSECAT, $category->id, $parentcontext->path); 05943 } else { 05944 $record = context::insert_context_record(CONTEXT_COURSECAT, $category->id, '/'.SYSCONTEXTID, 0); 05945 } 05946 } 05947 } 05948 05949 if ($record) { 05950 $context = new context_coursecat($record); 05951 context::cache_add($context); 05952 return $context; 05953 } 05954 05955 return false; 05956 } 05957 05964 public function get_child_contexts() { 05965 global $DB; 05966 05967 $sql = "SELECT ctx.* 05968 FROM {context} ctx 05969 WHERE ctx.path LIKE ? AND (ctx.depth = ? OR ctx.contextlevel = ?)"; 05970 $params = array($this->_path.'/%', $this->depth+1, CONTEXT_COURSECAT); 05971 $records = $DB->get_records_sql($sql, $params); 05972 05973 $result = array(); 05974 foreach ($records as $record) { 05975 $result[$record->id] = context::create_instance_from_record($record); 05976 } 05977 05978 return $result; 05979 } 05980 05985 protected static function create_level_instances() { 05986 global $DB; 05987 05988 $sql = "INSERT INTO {context} (contextlevel, instanceid) 05989 SELECT ".CONTEXT_COURSECAT.", cc.id 05990 FROM {course_categories} cc 05991 WHERE NOT EXISTS (SELECT 'x' 05992 FROM {context} cx 05993 WHERE cc.id = cx.instanceid AND cx.contextlevel=".CONTEXT_COURSECAT.")"; 05994 $DB->execute($sql); 05995 } 05996 06003 protected static function get_cleanup_sql() { 06004 $sql = " 06005 SELECT c.* 06006 FROM {context} c 06007 LEFT OUTER JOIN {course_categories} cc ON c.instanceid = cc.id 06008 WHERE cc.id IS NULL AND c.contextlevel = ".CONTEXT_COURSECAT." 06009 "; 06010 06011 return $sql; 06012 } 06013 06020 protected static function build_paths($force) { 06021 global $DB; 06022 06023 if ($force or $DB->record_exists_select('context', "contextlevel = ".CONTEXT_COURSECAT." AND (depth = 0 OR path IS NULL)")) { 06024 if ($force) { 06025 $ctxemptyclause = $emptyclause = ''; 06026 } else { 06027 $ctxemptyclause = "AND (ctx.path IS NULL OR ctx.depth = 0)"; 06028 $emptyclause = "AND ({context}.path IS NULL OR {context}.depth = 0)"; 06029 } 06030 06031 $base = '/'.SYSCONTEXTID; 06032 06033 // Normal top level categories 06034 $sql = "UPDATE {context} 06035 SET depth=2, 06036 path=".$DB->sql_concat("'$base/'", 'id')." 06037 WHERE contextlevel=".CONTEXT_COURSECAT." 06038 AND EXISTS (SELECT 'x' 06039 FROM {course_categories} cc 06040 WHERE cc.id = {context}.instanceid AND cc.depth=1) 06041 $emptyclause"; 06042 $DB->execute($sql); 06043 06044 // Deeper categories - one query per depthlevel 06045 $maxdepth = $DB->get_field_sql("SELECT MAX(depth) FROM {course_categories}"); 06046 for ($n=2; $n<=$maxdepth; $n++) { 06047 $sql = "INSERT INTO {context_temp} (id, path, depth) 06048 SELECT ctx.id, ".$DB->sql_concat('pctx.path', "'/'", 'ctx.id').", pctx.depth+1 06049 FROM {context} ctx 06050 JOIN {course_categories} cc ON (cc.id = ctx.instanceid AND ctx.contextlevel = ".CONTEXT_COURSECAT." AND cc.depth = $n) 06051 JOIN {context} pctx ON (pctx.instanceid = cc.parent AND pctx.contextlevel = ".CONTEXT_COURSECAT.") 06052 WHERE pctx.path IS NOT NULL AND pctx.depth > 0 06053 $ctxemptyclause"; 06054 $trans = $DB->start_delegated_transaction(); 06055 $DB->delete_records('context_temp'); 06056 $DB->execute($sql); 06057 context::merge_context_temp_table(); 06058 $DB->delete_records('context_temp'); 06059 $trans->allow_commit(); 06060 06061 } 06062 } 06063 } 06064 } 06065 06066 06072 class context_course extends context { 06079 protected function __construct(stdClass $record) { 06080 parent::__construct($record); 06081 if ($record->contextlevel != CONTEXT_COURSE) { 06082 throw new coding_exception('Invalid $record->contextlevel in context_course constructor.'); 06083 } 06084 } 06085 06092 public static function get_level_name() { 06093 return get_string('course'); 06094 } 06095 06103 public function get_context_name($withprefix = true, $short = false) { 06104 global $DB; 06105 06106 $name = ''; 06107 if ($this->_instanceid == SITEID) { 06108 $name = get_string('frontpage', 'admin'); 06109 } else { 06110 if ($course = $DB->get_record('course', array('id'=>$this->_instanceid))) { 06111 if ($withprefix){ 06112 $name = get_string('course').': '; 06113 } 06114 if ($short){ 06115 $name .= format_string($course->shortname, true, array('context' => $this)); 06116 } else { 06117 $name .= format_string($course->fullname); 06118 } 06119 } 06120 } 06121 return $name; 06122 } 06123 06129 public function get_url() { 06130 if ($this->_instanceid != SITEID) { 06131 return new moodle_url('/course/view.php', array('id'=>$this->_instanceid)); 06132 } 06133 06134 return new moodle_url('/'); 06135 } 06136 06142 public function get_capabilities() { 06143 global $DB; 06144 06145 $sort = 'ORDER BY contextlevel,component,name'; // To group them sensibly for display 06146 06147 $params = array(); 06148 $sql = "SELECT * 06149 FROM {capabilities} 06150 WHERE contextlevel IN (".CONTEXT_COURSE.",".CONTEXT_MODULE.",".CONTEXT_BLOCK.")"; 06151 06152 return $DB->get_records_sql($sql.' '.$sort, $params); 06153 } 06154 06161 public function get_course_context($strict = true) { 06162 return $this; 06163 } 06164 06173 public static function instance($instanceid, $strictness = MUST_EXIST) { 06174 global $DB; 06175 06176 if ($context = context::cache_get(CONTEXT_COURSE, $instanceid)) { 06177 return $context; 06178 } 06179 06180 if (!$record = $DB->get_record('context', array('contextlevel'=>CONTEXT_COURSE, 'instanceid'=>$instanceid))) { 06181 if ($course = $DB->get_record('course', array('id'=>$instanceid), 'id,category', $strictness)) { 06182 if ($course->category) { 06183 $parentcontext = context_coursecat::instance($course->category); 06184 $record = context::insert_context_record(CONTEXT_COURSE, $course->id, $parentcontext->path); 06185 } else { 06186 $record = context::insert_context_record(CONTEXT_COURSE, $course->id, '/'.SYSCONTEXTID, 0); 06187 } 06188 } 06189 } 06190 06191 if ($record) { 06192 $context = new context_course($record); 06193 context::cache_add($context); 06194 return $context; 06195 } 06196 06197 return false; 06198 } 06199 06204 protected static function create_level_instances() { 06205 global $DB; 06206 06207 $sql = "INSERT INTO {context} (contextlevel, instanceid) 06208 SELECT ".CONTEXT_COURSE.", c.id 06209 FROM {course} c 06210 WHERE NOT EXISTS (SELECT 'x' 06211 FROM {context} cx 06212 WHERE c.id = cx.instanceid AND cx.contextlevel=".CONTEXT_COURSE.")"; 06213 $DB->execute($sql); 06214 } 06215 06222 protected static function get_cleanup_sql() { 06223 $sql = " 06224 SELECT c.* 06225 FROM {context} c 06226 LEFT OUTER JOIN {course} co ON c.instanceid = co.id 06227 WHERE co.id IS NULL AND c.contextlevel = ".CONTEXT_COURSE." 06228 "; 06229 06230 return $sql; 06231 } 06232 06239 protected static function build_paths($force) { 06240 global $DB; 06241 06242 if ($force or $DB->record_exists_select('context', "contextlevel = ".CONTEXT_COURSE." AND (depth = 0 OR path IS NULL)")) { 06243 if ($force) { 06244 $ctxemptyclause = $emptyclause = ''; 06245 } else { 06246 $ctxemptyclause = "AND (ctx.path IS NULL OR ctx.depth = 0)"; 06247 $emptyclause = "AND ({context}.path IS NULL OR {context}.depth = 0)"; 06248 } 06249 06250 $base = '/'.SYSCONTEXTID; 06251 06252 // Standard frontpage 06253 $sql = "UPDATE {context} 06254 SET depth = 2, 06255 path = ".$DB->sql_concat("'$base/'", 'id')." 06256 WHERE contextlevel = ".CONTEXT_COURSE." 06257 AND EXISTS (SELECT 'x' 06258 FROM {course} c 06259 WHERE c.id = {context}.instanceid AND c.category = 0) 06260 $emptyclause"; 06261 $DB->execute($sql); 06262 06263 // standard courses 06264 $sql = "INSERT INTO {context_temp} (id, path, depth) 06265 SELECT ctx.id, ".$DB->sql_concat('pctx.path', "'/'", 'ctx.id').", pctx.depth+1 06266 FROM {context} ctx 06267 JOIN {course} c ON (c.id = ctx.instanceid AND ctx.contextlevel = ".CONTEXT_COURSE." AND c.category <> 0) 06268 JOIN {context} pctx ON (pctx.instanceid = c.category AND pctx.contextlevel = ".CONTEXT_COURSECAT.") 06269 WHERE pctx.path IS NOT NULL AND pctx.depth > 0 06270 $ctxemptyclause"; 06271 $trans = $DB->start_delegated_transaction(); 06272 $DB->delete_records('context_temp'); 06273 $DB->execute($sql); 06274 context::merge_context_temp_table(); 06275 $DB->delete_records('context_temp'); 06276 $trans->allow_commit(); 06277 } 06278 } 06279 } 06280 06281 06287 class context_module extends context { 06294 protected function __construct(stdClass $record) { 06295 parent::__construct($record); 06296 if ($record->contextlevel != CONTEXT_MODULE) { 06297 throw new coding_exception('Invalid $record->contextlevel in context_module constructor.'); 06298 } 06299 } 06300 06307 public static function get_level_name() { 06308 return get_string('activitymodule'); 06309 } 06310 06319 public function get_context_name($withprefix = true, $short = false) { 06320 global $DB; 06321 06322 $name = ''; 06323 if ($cm = $DB->get_record_sql("SELECT cm.*, md.name AS modname 06324 FROM {course_modules} cm 06325 JOIN {modules} md ON md.id = cm.module 06326 WHERE cm.id = ?", array($this->_instanceid))) { 06327 if ($mod = $DB->get_record($cm->modname, array('id' => $cm->instance))) { 06328 if ($withprefix){ 06329 $name = get_string('modulename', $cm->modname).': '; 06330 } 06331 $name .= $mod->name; 06332 } 06333 } 06334 return $name; 06335 } 06336 06342 public function get_url() { 06343 global $DB; 06344 06345 if ($modname = $DB->get_field_sql("SELECT md.name AS modname 06346 FROM {course_modules} cm 06347 JOIN {modules} md ON md.id = cm.module 06348 WHERE cm.id = ?", array($this->_instanceid))) { 06349 return new moodle_url('/mod/' . $modname . '/view.php', array('id'=>$this->_instanceid)); 06350 } 06351 06352 return new moodle_url('/'); 06353 } 06354 06360 public function get_capabilities() { 06361 global $DB, $CFG; 06362 06363 $sort = 'ORDER BY contextlevel,component,name'; // To group them sensibly for display 06364 06365 $cm = $DB->get_record('course_modules', array('id'=>$this->_instanceid)); 06366 $module = $DB->get_record('modules', array('id'=>$cm->module)); 06367 06368 $subcaps = array(); 06369 $subpluginsfile = "$CFG->dirroot/mod/$module->name/db/subplugins.php"; 06370 if (file_exists($subpluginsfile)) { 06371 $subplugins = array(); // should be redefined in the file 06372 include($subpluginsfile); 06373 if (!empty($subplugins)) { 06374 foreach (array_keys($subplugins) as $subplugintype) { 06375 foreach (array_keys(get_plugin_list($subplugintype)) as $subpluginname) { 06376 $subcaps = array_merge($subcaps, array_keys(load_capability_def($subplugintype.'_'.$subpluginname))); 06377 } 06378 } 06379 } 06380 } 06381 06382 $modfile = "$CFG->dirroot/mod/$module->name/lib.php"; 06383 $extracaps = array(); 06384 if (file_exists($modfile)) { 06385 include_once($modfile); 06386 $modfunction = $module->name.'_get_extra_capabilities'; 06387 if (function_exists($modfunction)) { 06388 $extracaps = $modfunction(); 06389 } 06390 } 06391 06392 $extracaps = array_merge($subcaps, $extracaps); 06393 $extra = ''; 06394 list($extra, $params) = $DB->get_in_or_equal( 06395 $extracaps, SQL_PARAMS_NAMED, 'cap0', true, ''); 06396 if (!empty($extra)) { 06397 $extra = "OR name $extra"; 06398 } 06399 $sql = "SELECT * 06400 FROM {capabilities} 06401 WHERE (contextlevel = ".CONTEXT_MODULE." 06402 AND (component = :component OR component = 'moodle')) 06403 $extra"; 06404 $params['component'] = "mod_$module->name"; 06405 06406 return $DB->get_records_sql($sql.' '.$sort, $params); 06407 } 06408 06415 public function get_course_context($strict = true) { 06416 return $this->get_parent_context(); 06417 } 06418 06427 public static function instance($instanceid, $strictness = MUST_EXIST) { 06428 global $DB; 06429 06430 if ($context = context::cache_get(CONTEXT_MODULE, $instanceid)) { 06431 return $context; 06432 } 06433 06434 if (!$record = $DB->get_record('context', array('contextlevel'=>CONTEXT_MODULE, 'instanceid'=>$instanceid))) { 06435 if ($cm = $DB->get_record('course_modules', array('id'=>$instanceid), 'id,course', $strictness)) { 06436 $parentcontext = context_course::instance($cm->course); 06437 $record = context::insert_context_record(CONTEXT_MODULE, $cm->id, $parentcontext->path); 06438 } 06439 } 06440 06441 if ($record) { 06442 $context = new context_module($record); 06443 context::cache_add($context); 06444 return $context; 06445 } 06446 06447 return false; 06448 } 06449 06454 protected static function create_level_instances() { 06455 global $DB; 06456 06457 $sql = "INSERT INTO {context} (contextlevel, instanceid) 06458 SELECT ".CONTEXT_MODULE.", cm.id 06459 FROM {course_modules} cm 06460 WHERE NOT EXISTS (SELECT 'x' 06461 FROM {context} cx 06462 WHERE cm.id = cx.instanceid AND cx.contextlevel=".CONTEXT_MODULE.")"; 06463 $DB->execute($sql); 06464 } 06465 06472 protected static function get_cleanup_sql() { 06473 $sql = " 06474 SELECT c.* 06475 FROM {context} c 06476 LEFT OUTER JOIN {course_modules} cm ON c.instanceid = cm.id 06477 WHERE cm.id IS NULL AND c.contextlevel = ".CONTEXT_MODULE." 06478 "; 06479 06480 return $sql; 06481 } 06482 06489 protected static function build_paths($force) { 06490 global $DB; 06491 06492 if ($force or $DB->record_exists_select('context', "contextlevel = ".CONTEXT_MODULE." AND (depth = 0 OR path IS NULL)")) { 06493 if ($force) { 06494 $ctxemptyclause = ''; 06495 } else { 06496 $ctxemptyclause = "AND (ctx.path IS NULL OR ctx.depth = 0)"; 06497 } 06498 06499 $sql = "INSERT INTO {context_temp} (id, path, depth) 06500 SELECT ctx.id, ".$DB->sql_concat('pctx.path', "'/'", 'ctx.id').", pctx.depth+1 06501 FROM {context} ctx 06502 JOIN {course_modules} cm ON (cm.id = ctx.instanceid AND ctx.contextlevel = ".CONTEXT_MODULE.") 06503 JOIN {context} pctx ON (pctx.instanceid = cm.course AND pctx.contextlevel = ".CONTEXT_COURSE.") 06504 WHERE pctx.path IS NOT NULL AND pctx.depth > 0 06505 $ctxemptyclause"; 06506 $trans = $DB->start_delegated_transaction(); 06507 $DB->delete_records('context_temp'); 06508 $DB->execute($sql); 06509 context::merge_context_temp_table(); 06510 $DB->delete_records('context_temp'); 06511 $trans->allow_commit(); 06512 } 06513 } 06514 } 06515 06516 06522 class context_block extends context { 06529 protected function __construct(stdClass $record) { 06530 parent::__construct($record); 06531 if ($record->contextlevel != CONTEXT_BLOCK) { 06532 throw new coding_exception('Invalid $record->contextlevel in context_block constructor'); 06533 } 06534 } 06535 06542 public static function get_level_name() { 06543 return get_string('block'); 06544 } 06545 06553 public function get_context_name($withprefix = true, $short = false) { 06554 global $DB, $CFG; 06555 06556 $name = ''; 06557 if ($blockinstance = $DB->get_record('block_instances', array('id'=>$this->_instanceid))) { 06558 global $CFG; 06559 require_once("$CFG->dirroot/blocks/moodleblock.class.php"); 06560 require_once("$CFG->dirroot/blocks/$blockinstance->blockname/block_$blockinstance->blockname.php"); 06561 $blockname = "block_$blockinstance->blockname"; 06562 if ($blockobject = new $blockname()) { 06563 if ($withprefix){ 06564 $name = get_string('block').': '; 06565 } 06566 $name .= $blockobject->title; 06567 } 06568 } 06569 06570 return $name; 06571 } 06572 06578 public function get_url() { 06579 $parentcontexts = $this->get_parent_context(); 06580 return $parentcontexts->get_url(); 06581 } 06582 06588 public function get_capabilities() { 06589 global $DB; 06590 06591 $sort = 'ORDER BY contextlevel,component,name'; // To group them sensibly for display 06592 06593 $params = array(); 06594 $bi = $DB->get_record('block_instances', array('id' => $this->_instanceid)); 06595 06596 $extra = ''; 06597 $extracaps = block_method_result($bi->blockname, 'get_extra_capabilities'); 06598 if ($extracaps) { 06599 list($extra, $params) = $DB->get_in_or_equal($extracaps, SQL_PARAMS_NAMED, 'cap'); 06600 $extra = "OR name $extra"; 06601 } 06602 06603 $sql = "SELECT * 06604 FROM {capabilities} 06605 WHERE (contextlevel = ".CONTEXT_BLOCK." 06606 AND component = :component) 06607 $extra"; 06608 $params['component'] = 'block_' . $bi->blockname; 06609 06610 return $DB->get_records_sql($sql.' '.$sort, $params); 06611 } 06612 06619 public function get_course_context($strict = true) { 06620 $parentcontext = $this->get_parent_context(); 06621 return $parentcontext->get_course_context($strict); 06622 } 06623 06632 public static function instance($instanceid, $strictness = MUST_EXIST) { 06633 global $DB; 06634 06635 if ($context = context::cache_get(CONTEXT_BLOCK, $instanceid)) { 06636 return $context; 06637 } 06638 06639 if (!$record = $DB->get_record('context', array('contextlevel'=>CONTEXT_BLOCK, 'instanceid'=>$instanceid))) { 06640 if ($bi = $DB->get_record('block_instances', array('id'=>$instanceid), 'id,parentcontextid', $strictness)) { 06641 $parentcontext = context::instance_by_id($bi->parentcontextid); 06642 $record = context::insert_context_record(CONTEXT_BLOCK, $bi->id, $parentcontext->path); 06643 } 06644 } 06645 06646 if ($record) { 06647 $context = new context_block($record); 06648 context::cache_add($context); 06649 return $context; 06650 } 06651 06652 return false; 06653 } 06654 06659 public function get_child_contexts() { 06660 return array(); 06661 } 06662 06667 protected static function create_level_instances() { 06668 global $DB; 06669 06670 $sql = "INSERT INTO {context} (contextlevel, instanceid) 06671 SELECT ".CONTEXT_BLOCK.", bi.id 06672 FROM {block_instances} bi 06673 WHERE NOT EXISTS (SELECT 'x' 06674 FROM {context} cx 06675 WHERE bi.id = cx.instanceid AND cx.contextlevel=".CONTEXT_BLOCK.")"; 06676 $DB->execute($sql); 06677 } 06678 06685 protected static function get_cleanup_sql() { 06686 $sql = " 06687 SELECT c.* 06688 FROM {context} c 06689 LEFT OUTER JOIN {block_instances} bi ON c.instanceid = bi.id 06690 WHERE bi.id IS NULL AND c.contextlevel = ".CONTEXT_BLOCK." 06691 "; 06692 06693 return $sql; 06694 } 06695 06702 protected static function build_paths($force) { 06703 global $DB; 06704 06705 if ($force or $DB->record_exists_select('context', "contextlevel = ".CONTEXT_BLOCK." AND (depth = 0 OR path IS NULL)")) { 06706 if ($force) { 06707 $ctxemptyclause = ''; 06708 } else { 06709 $ctxemptyclause = "AND (ctx.path IS NULL OR ctx.depth = 0)"; 06710 } 06711 06712 // pctx.path IS NOT NULL prevents fatal problems with broken block instances that point to invalid context parent 06713 $sql = "INSERT INTO {context_temp} (id, path, depth) 06714 SELECT ctx.id, ".$DB->sql_concat('pctx.path', "'/'", 'ctx.id').", pctx.depth+1 06715 FROM {context} ctx 06716 JOIN {block_instances} bi ON (bi.id = ctx.instanceid AND ctx.contextlevel = ".CONTEXT_BLOCK.") 06717 JOIN {context} pctx ON (pctx.id = bi.parentcontextid) 06718 WHERE (pctx.path IS NOT NULL AND pctx.depth > 0) 06719 $ctxemptyclause"; 06720 $trans = $DB->start_delegated_transaction(); 06721 $DB->delete_records('context_temp'); 06722 $DB->execute($sql); 06723 context::merge_context_temp_table(); 06724 $DB->delete_records('context_temp'); 06725 $trans->allow_commit(); 06726 } 06727 } 06728 } 06729 06730 06731 // ============== DEPRECATED FUNCTIONS ========================================== 06732 // Old context related functions were deprecated in 2.0, it is recommended 06733 // to use context classes in new code. Old function can be used when 06734 // creating patches that are supposed to be backported to older stable branches. 06735 // These deprecated functions will not be removed in near future, 06736 // before removing devs will be warned with a debugging message first, 06737 // then we will add error message and only after that we can remove the functions 06738 // completely. 06739 06740 06750 function load_temp_role($context, $roleid, array $accessdata) { 06751 debugging('load_temp_role() is deprecated, please use load_temp_course_role() instead, temp role not loaded.'); 06752 return $accessdata; 06753 } 06754 06763 function remove_temp_roles($context, array $accessdata) { 06764 debugging('remove_temp_role() is deprecated, please use remove_temp_course_roles() instead.'); 06765 return $accessdata; 06766 } 06767 06775 function get_system_context($cache = true) { 06776 return context_system::instance(0, IGNORE_MISSING, $cache); 06777 } 06778 06791 function get_context_instance($contextlevel, $instance = 0, $strictness = IGNORE_MISSING) { 06792 $instances = (array)$instance; 06793 $contexts = array(); 06794 06795 $classname = context_helper::get_class_for_level($contextlevel); 06796 06797 // we do not load multiple contexts any more, PAGE should be responsible for any preloading 06798 foreach ($instances as $inst) { 06799 $contexts[$inst] = $classname::instance($inst, $strictness); 06800 } 06801 06802 if (is_array($instance)) { 06803 return $contexts; 06804 } else { 06805 return $contexts[$instance]; 06806 } 06807 } 06808 06818 function get_context_instance_by_id($id, $strictness = IGNORE_MISSING) { 06819 return context::instance_by_id($id, $strictness); 06820 } 06821 06832 function get_parent_contexts(context $context, $includeself = false) { 06833 return $context->get_parent_context_ids($includeself); 06834 } 06835 06844 function get_parent_contextid(context $context) { 06845 if ($parent = $context->get_parent_context()) { 06846 return $parent->id; 06847 } else { 06848 return false; 06849 } 06850 } 06851 06869 function get_child_contexts(context $context) { 06870 return $context->get_child_contexts(); 06871 } 06872 06881 function create_contexts($contextlevel = null, $buildpaths = true) { 06882 context_helper::create_instances($contextlevel, $buildpaths); 06883 } 06884 06891 function cleanup_contexts() { 06892 context_helper::cleanup_instances(); 06893 return true; 06894 } 06895 06903 function build_context_path($force = false) { 06904 context_helper::build_all_paths($force); 06905 } 06906 06914 function rebuild_contexts(array $fixcontexts) { 06915 foreach ($fixcontexts as $fixcontext) { 06916 $fixcontext->reset_paths(false); 06917 } 06918 context_helper::build_all_paths(false); 06919 } 06920 06930 function preload_course_contexts($courseid) { 06931 context_helper::preload_course($courseid); 06932 } 06933 06944 function context_instance_preload_sql($joinon, $contextlevel, $tablealias) { 06945 $select = ", ".context_helper::get_preload_record_columns_sql($tablealias); 06946 $join = "LEFT JOIN {context} $tablealias ON ($tablealias.instanceid = $joinon AND $tablealias.contextlevel = $contextlevel)"; 06947 return array($select, $join); 06948 } 06949 06958 function context_instance_preload(stdClass $rec) { 06959 context_helper::preload_from_record($rec); 06960 } 06961 06968 function mark_context_dirty($path) { 06969 global $CFG, $USER, $ACCESSLIB_PRIVATE; 06970 06971 if (during_initial_install()) { 06972 return; 06973 } 06974 06975 // only if it is a non-empty string 06976 if (is_string($path) && $path !== '') { 06977 set_cache_flag('accesslib/dirtycontexts', $path, 1, time()+$CFG->sessiontimeout); 06978 if (isset($ACCESSLIB_PRIVATE->dirtycontexts)) { 06979 $ACCESSLIB_PRIVATE->dirtycontexts[$path] = 1; 06980 } else { 06981 if (CLI_SCRIPT) { 06982 $ACCESSLIB_PRIVATE->dirtycontexts = array($path => 1); 06983 } else { 06984 if (isset($USER->access['time'])) { 06985 $ACCESSLIB_PRIVATE->dirtycontexts = get_cache_flags('accesslib/dirtycontexts', $USER->access['time']-2); 06986 } else { 06987 $ACCESSLIB_PRIVATE->dirtycontexts = array($path => 1); 06988 } 06989 // flags not loaded yet, it will be done later in $context->reload_if_dirty() 06990 } 06991 } 06992 } 06993 } 06994 07011 function context_moved(context $context, context $newparent) { 07012 $context->update_moved($newparent); 07013 } 07014 07025 function delete_context($contextlevel, $instanceid, $deleterecord = true) { 07026 if ($deleterecord) { 07027 context_helper::delete_instance($contextlevel, $instanceid); 07028 } else { 07029 $classname = context_helper::get_class_for_level($contextlevel); 07030 if ($context = $classname::instance($instanceid, IGNORE_MISSING)) { 07031 $context->delete_content(); 07032 } 07033 } 07034 07035 return true; 07036 } 07037 07044 function get_contextlevel_name($contextlevel) { 07045 return context_helper::get_level_name($contextlevel); 07046 } 07047 07059 function print_context_name(context $context, $withprefix = true, $short = false) { 07060 return $context->get_context_name($withprefix, $short); 07061 } 07062 07072 function get_context_url(context $context) { 07073 return $context->get_url(); 07074 } 07075 07084 function get_course_context(context $context) { 07085 return $context->get_course_context(true); 07086 } 07087 07095 function get_courseid_from_context(context $context) { 07096 if ($coursecontext = $context->get_course_context(false)) { 07097 return $coursecontext->instanceid; 07098 } else { 07099 return false; 07100 } 07101 } 07102 07117 function get_user_courses_bycap($userid, $cap, $accessdata_ignored, $doanything_ignored, $sort = 'c.sortorder ASC', $fields = null, $limit_ignored = 0) { 07118 07119 $courses = enrol_get_users_courses($userid, true, $fields, $sort); 07120 foreach ($courses as $id=>$course) { 07121 $context = context_course::instance($id); 07122 if (!has_capability($cap, $context, $userid)) { 07123 unset($courses[$id]); 07124 } 07125 } 07126 07127 return $courses; 07128 } 07129 07146 function fetch_context_capabilities(context $context) { 07147 return $context->get_capabilities(); 07148 } 07149 07162 function get_sorted_contexts($select, $params = array()) { 07163 07164 //TODO: we should probably rewrite all the code that is using this thing, the trouble is we MUST NOT modify the context instances... 07165 07166 global $DB; 07167 if ($select) { 07168 $select = 'WHERE ' . $select; 07169 } 07170 return $DB->get_records_sql(" 07171 SELECT ctx.* 07172 FROM {context} ctx 07173 LEFT JOIN {user} u ON ctx.contextlevel = " . CONTEXT_USER . " AND u.id = ctx.instanceid 07174 LEFT JOIN {course_categories} cat ON ctx.contextlevel = " . CONTEXT_COURSECAT . " AND cat.id = ctx.instanceid 07175 LEFT JOIN {course} c ON ctx.contextlevel = " . CONTEXT_COURSE . " AND c.id = ctx.instanceid 07176 LEFT JOIN {course_modules} cm ON ctx.contextlevel = " . CONTEXT_MODULE . " AND cm.id = ctx.instanceid 07177 LEFT JOIN {block_instances} bi ON ctx.contextlevel = " . CONTEXT_BLOCK . " AND bi.id = ctx.instanceid 07178 $select 07179 ORDER BY ctx.contextlevel, bi.defaultregion, COALESCE(cat.sortorder, c.sortorder, cm.section, bi.defaultweight), u.lastname, u.firstname, cm.id 07180 ", $params); 07181 } 07182 07191 function get_role_context_caps($roleid, context $context) { 07192 global $DB; 07193 07194 //this is really slow!!!! - do not use above course context level! 07195 $result = array(); 07196 $result[$context->id] = array(); 07197 07198 // first emulate the parent context capabilities merging into context 07199 $searchcontexts = array_reverse($context->get_parent_context_ids(true)); 07200 foreach ($searchcontexts as $cid) { 07201 if ($capabilities = $DB->get_records('role_capabilities', array('roleid'=>$roleid, 'contextid'=>$cid))) { 07202 foreach ($capabilities as $cap) { 07203 if (!array_key_exists($cap->capability, $result[$context->id])) { 07204 $result[$context->id][$cap->capability] = 0; 07205 } 07206 $result[$context->id][$cap->capability] += $cap->permission; 07207 } 07208 } 07209 } 07210 07211 // now go through the contexts below given context 07212 $searchcontexts = array_keys($context->get_child_contexts()); 07213 foreach ($searchcontexts as $cid) { 07214 if ($capabilities = $DB->get_records('role_capabilities', array('roleid'=>$roleid, 'contextid'=>$cid))) { 07215 foreach ($capabilities as $cap) { 07216 if (!array_key_exists($cap->contextid, $result)) { 07217 $result[$cap->contextid] = array(); 07218 } 07219 $result[$cap->contextid][$cap->capability] = $cap->permission; 07220 } 07221 } 07222 } 07223 07224 return $result; 07225 } 07226 07236 function get_related_contexts_string(context $context) { 07237 07238 if ($parents = $context->get_parent_context_ids()) { 07239 return (' IN ('.$context->id.','.implode(',', $parents).')'); 07240 } else { 07241 return (' ='.$context->id); 07242 } 07243 }