MDL-40392 Navigation -> my courses listing tests
[moodle.git] / lib / accesslib.php
blobdbb20318e700412a315d61c38e1e9b67e31ed496
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17 /**
18 * This file contains functions for managing user access
20 * <b>Public API vs internals</b>
22 * General users probably only care about
24 * Context handling
25 * - context_course::instance($courseid), context_module::instance($cm->id), context_coursecat::instance($catid)
26 * - context::instance_by_id($contextid)
27 * - $context->get_parent_contexts();
28 * - $context->get_child_contexts();
30 * Whether the user can do something...
31 * - has_capability()
32 * - has_any_capability()
33 * - has_all_capabilities()
34 * - require_capability()
35 * - require_login() (from moodlelib)
36 * - is_enrolled()
37 * - is_viewing()
38 * - is_guest()
39 * - is_siteadmin()
40 * - isguestuser()
41 * - isloggedin()
43 * What courses has this user access to?
44 * - get_enrolled_users()
46 * What users can do X in this context?
47 * - get_enrolled_users() - at and bellow course context
48 * - get_users_by_capability() - above course context
50 * Modify roles
51 * - role_assign()
52 * - role_unassign()
53 * - role_unassign_all()
55 * Advanced - for internal use only
56 * - load_all_capabilities()
57 * - reload_all_capabilities()
58 * - has_capability_in_accessdata()
59 * - get_user_access_sitewide()
60 * - load_course_context()
61 * - load_role_access_by_context()
62 * - etc.
64 * <b>Name conventions</b>
66 * "ctx" means context
68 * <b>accessdata</b>
70 * Access control data is held in the "accessdata" array
71 * which - for the logged-in user, will be in $USER->access
73 * For other users can be generated and passed around (but may also be cached
74 * against userid in $ACCESSLIB_PRIVATE->accessdatabyuser).
76 * $accessdata is a multidimensional array, holding
77 * role assignments (RAs), role-capabilities-perm sets
78 * (role defs) and a list of courses we have loaded
79 * data for.
81 * Things are keyed on "contextpaths" (the path field of
82 * the context table) for fast walking up/down the tree.
83 * <code>
84 * $accessdata['ra'][$contextpath] = array($roleid=>$roleid)
85 * [$contextpath] = array($roleid=>$roleid)
86 * [$contextpath] = array($roleid=>$roleid)
87 * </code>
89 * Role definitions are stored like this
90 * (no cap merge is done - so it's compact)
92 * <code>
93 * $accessdata['rdef']["$contextpath:$roleid"]['mod/forum:viewpost'] = 1
94 * ['mod/forum:editallpost'] = -1
95 * ['mod/forum:startdiscussion'] = -1000
96 * </code>
98 * See how has_capability_in_accessdata() walks up the tree.
100 * First we only load rdef and ra down to the course level, but not below.
101 * This keeps accessdata small and compact. Below-the-course ra/rdef
102 * are loaded as needed. We keep track of which courses we have loaded ra/rdef in
103 * <code>
104 * $accessdata['loaded'] = array($courseid1=>1, $courseid2=>1)
105 * </code>
107 * <b>Stale accessdata</b>
109 * For the logged-in user, accessdata is long-lived.
111 * On each pageload we load $ACCESSLIB_PRIVATE->dirtycontexts which lists
112 * context paths affected by changes. Any check at-or-below
113 * a dirty context will trigger a transparent reload of accessdata.
115 * Changes at the system level will force the reload for everyone.
117 * <b>Default role caps</b>
118 * The default role assignment is not in the DB, so we
119 * add it manually to accessdata.
121 * This means that functions that work directly off the
122 * DB need to ensure that the default role caps
123 * are dealt with appropriately.
125 * @package core_access
126 * @copyright 1999 onwards Martin Dougiamas http://dougiamas.com
127 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
130 defined('MOODLE_INTERNAL') || die();
132 /** No capability change */
133 define('CAP_INHERIT', 0);
134 /** Allow permission, overrides CAP_PREVENT defined in parent contexts */
135 define('CAP_ALLOW', 1);
136 /** Prevent permission, overrides CAP_ALLOW defined in parent contexts */
137 define('CAP_PREVENT', -1);
138 /** Prohibit permission, overrides everything in current and child contexts */
139 define('CAP_PROHIBIT', -1000);
141 /** System context level - only one instance in every system */
142 define('CONTEXT_SYSTEM', 10);
143 /** User context level - one instance for each user describing what others can do to user */
144 define('CONTEXT_USER', 30);
145 /** Course category context level - one instance for each category */
146 define('CONTEXT_COURSECAT', 40);
147 /** Course context level - one instances for each course */
148 define('CONTEXT_COURSE', 50);
149 /** Course module context level - one instance for each course module */
150 define('CONTEXT_MODULE', 70);
152 * Block context level - one instance for each block, sticky blocks are tricky
153 * because ppl think they should be able to override them at lower contexts.
154 * Any other context level instance can be parent of block context.
156 define('CONTEXT_BLOCK', 80);
158 /** Capability allow management of trusts - NOT IMPLEMENTED YET - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
159 define('RISK_MANAGETRUST', 0x0001);
160 /** Capability allows changes in system configuration - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
161 define('RISK_CONFIG', 0x0002);
162 /** Capability allows user to add scripted content - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
163 define('RISK_XSS', 0x0004);
164 /** Capability allows access to personal user information - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
165 define('RISK_PERSONAL', 0x0008);
166 /** Capability allows users to add content others may see - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
167 define('RISK_SPAM', 0x0010);
168 /** capability allows mass delete of data belonging to other users - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
169 define('RISK_DATALOSS', 0x0020);
171 /** rolename displays - the name as defined in the role definition, localised if name empty */
172 define('ROLENAME_ORIGINAL', 0);
173 /** rolename displays - the name as defined by a role alias at the course level, falls back to ROLENAME_ORIGINAL if alias not present */
174 define('ROLENAME_ALIAS', 1);
175 /** rolename displays - Both, like this: Role alias (Original) */
176 define('ROLENAME_BOTH', 2);
177 /** rolename displays - the name as defined in the role definition and the shortname in brackets */
178 define('ROLENAME_ORIGINALANDSHORT', 3);
179 /** rolename displays - the name as defined by a role alias, in raw form suitable for editing */
180 define('ROLENAME_ALIAS_RAW', 4);
181 /** rolename displays - the name is simply short role name */
182 define('ROLENAME_SHORT', 5);
184 if (!defined('CONTEXT_CACHE_MAX_SIZE')) {
185 /** maximum size of context cache - it is possible to tweak this config.php or in any script before inclusion of context.php */
186 define('CONTEXT_CACHE_MAX_SIZE', 2500);
190 * Although this looks like a global variable, it isn't really.
192 * It is just a private implementation detail to accesslib that MUST NOT be used elsewhere.
193 * It is used to cache various bits of data between function calls for performance reasons.
194 * Sadly, a PHP global variable is the only way to implement this, without rewriting everything
195 * as methods of a class, instead of functions.
197 * @access private
198 * @global stdClass $ACCESSLIB_PRIVATE
199 * @name $ACCESSLIB_PRIVATE
201 global $ACCESSLIB_PRIVATE;
202 $ACCESSLIB_PRIVATE = new stdClass();
203 $ACCESSLIB_PRIVATE->dirtycontexts = null; // Dirty contexts cache, loaded from DB once per page
204 $ACCESSLIB_PRIVATE->accessdatabyuser = array(); // Holds the cache of $accessdata structure for users (including $USER)
205 $ACCESSLIB_PRIVATE->rolepermissions = array(); // role permissions cache - helps a lot with mem usage
206 $ACCESSLIB_PRIVATE->capabilities = null; // detailed information about the capabilities
209 * Clears accesslib's private caches. ONLY BE USED BY UNIT TESTS
211 * This method should ONLY BE USED BY UNIT TESTS. It clears all of
212 * accesslib's private caches. You need to do this before setting up test data,
213 * and also at the end of the tests.
215 * @access private
216 * @return void
218 function accesslib_clear_all_caches_for_unit_testing() {
219 global $USER;
220 if (!PHPUNIT_TEST) {
221 throw new coding_exception('You must not call clear_all_caches outside of unit tests.');
224 accesslib_clear_all_caches(true);
226 unset($USER->access);
230 * Clears accesslib's private caches. ONLY BE USED FROM THIS LIBRARY FILE!
232 * This reset does not touch global $USER.
234 * @access private
235 * @param bool $resetcontexts
236 * @return void
238 function accesslib_clear_all_caches($resetcontexts) {
239 global $ACCESSLIB_PRIVATE;
241 $ACCESSLIB_PRIVATE->dirtycontexts = null;
242 $ACCESSLIB_PRIVATE->accessdatabyuser = array();
243 $ACCESSLIB_PRIVATE->rolepermissions = array();
244 $ACCESSLIB_PRIVATE->capabilities = null;
246 if ($resetcontexts) {
247 context_helper::reset_caches();
252 * Gets the accessdata for role "sitewide" (system down to course)
254 * @access private
255 * @param int $roleid
256 * @return array
258 function get_role_access($roleid) {
259 global $DB, $ACCESSLIB_PRIVATE;
261 /* Get it in 1 DB query...
262 * - relevant role caps at the root and down
263 * to the course level - but not below
266 //TODO: MUC - this could be cached in shared memory to speed up first page loading, web crawlers, etc.
268 $accessdata = get_empty_accessdata();
270 $accessdata['ra']['/'.SYSCONTEXTID] = array((int)$roleid => (int)$roleid);
272 // Overrides for the role IN ANY CONTEXTS down to COURSE - not below -.
275 $sql = "SELECT ctx.path,
276 rc.capability, rc.permission
277 FROM {context} ctx
278 JOIN {role_capabilities} rc ON rc.contextid = ctx.id
279 LEFT JOIN {context} cctx
280 ON (cctx.contextlevel = ".CONTEXT_COURSE." AND ctx.path LIKE ".$DB->sql_concat('cctx.path',"'/%'").")
281 WHERE rc.roleid = ? AND cctx.id IS NULL";
282 $params = array($roleid);
285 // Note: the commented out query is 100% accurate but slow, so let's cheat instead by hardcoding the blocks mess directly.
287 $sql = "SELECT COALESCE(ctx.path, bctx.path) AS path, rc.capability, rc.permission
288 FROM {role_capabilities} rc
289 LEFT JOIN {context} ctx ON (ctx.id = rc.contextid AND ctx.contextlevel <= ".CONTEXT_COURSE.")
290 LEFT JOIN ({context} bctx
291 JOIN {block_instances} bi ON (bi.id = bctx.instanceid)
292 JOIN {context} pctx ON (pctx.id = bi.parentcontextid AND pctx.contextlevel < ".CONTEXT_COURSE.")
293 ) ON (bctx.id = rc.contextid AND bctx.contextlevel = ".CONTEXT_BLOCK.")
294 WHERE rc.roleid = :roleid AND (ctx.id IS NOT NULL OR bctx.id IS NOT NULL)";
295 $params = array('roleid'=>$roleid);
297 // we need extra caching in CLI scripts and cron
298 $rs = $DB->get_recordset_sql($sql, $params);
299 foreach ($rs as $rd) {
300 $k = "{$rd->path}:{$roleid}";
301 $accessdata['rdef'][$k][$rd->capability] = (int)$rd->permission;
303 $rs->close();
305 // share the role definitions
306 foreach ($accessdata['rdef'] as $k=>$unused) {
307 if (!isset($ACCESSLIB_PRIVATE->rolepermissions[$k])) {
308 $ACCESSLIB_PRIVATE->rolepermissions[$k] = $accessdata['rdef'][$k];
310 $accessdata['rdef_count']++;
311 $accessdata['rdef'][$k] =& $ACCESSLIB_PRIVATE->rolepermissions[$k];
314 return $accessdata;
318 * Get the default guest role, this is used for guest account,
319 * search engine spiders, etc.
321 * @return stdClass role record
323 function get_guest_role() {
324 global $CFG, $DB;
326 if (empty($CFG->guestroleid)) {
327 if ($roles = $DB->get_records('role', array('archetype'=>'guest'))) {
328 $guestrole = array_shift($roles); // Pick the first one
329 set_config('guestroleid', $guestrole->id);
330 return $guestrole;
331 } else {
332 debugging('Can not find any guest role!');
333 return false;
335 } else {
336 if ($guestrole = $DB->get_record('role', array('id'=>$CFG->guestroleid))) {
337 return $guestrole;
338 } else {
339 // somebody is messing with guest roles, remove incorrect setting and try to find a new one
340 set_config('guestroleid', '');
341 return get_guest_role();
347 * Check whether a user has a particular capability in a given context.
349 * For example:
350 * $context = context_module::instance($cm->id);
351 * has_capability('mod/forum:replypost', $context)
353 * By default checks the capabilities of the current user, but you can pass a
354 * different userid. By default will return true for admin users, but you can override that with the fourth argument.
356 * Guest and not-logged-in users can never get any dangerous capability - that is any write capability
357 * or capabilities with XSS, config or data loss risks.
359 * @category access
361 * @param string $capability the name of the capability to check. For example mod/forum:view
362 * @param context $context the context to check the capability in. You normally get this with instance method of a context class.
363 * @param integer|stdClass $user A user id or object. By default (null) checks the permissions of the current user.
364 * @param boolean $doanything If false, ignores effect of admin role assignment
365 * @return boolean true if the user has this capability. Otherwise false.
367 function has_capability($capability, context $context, $user = null, $doanything = true) {
368 global $USER, $CFG, $SCRIPT, $ACCESSLIB_PRIVATE;
370 if (during_initial_install()) {
371 if ($SCRIPT === "/$CFG->admin/index.php" or $SCRIPT === "/$CFG->admin/cli/install.php" or $SCRIPT === "/$CFG->admin/cli/install_database.php") {
372 // we are in an installer - roles can not work yet
373 return true;
374 } else {
375 return false;
379 if (strpos($capability, 'moodle/legacy:') === 0) {
380 throw new coding_exception('Legacy capabilities can not be used any more!');
383 if (!is_bool($doanything)) {
384 throw new coding_exception('Capability parameter "doanything" is wierd, only true or false is allowed. This has to be fixed in code.');
387 // capability must exist
388 if (!$capinfo = get_capability_info($capability)) {
389 debugging('Capability "'.$capability.'" was not found! This has to be fixed in code.');
390 return false;
393 if (!isset($USER->id)) {
394 // should never happen
395 $USER->id = 0;
396 debugging('Capability check being performed on a user with no ID.', DEBUG_DEVELOPER);
399 // make sure there is a real user specified
400 if ($user === null) {
401 $userid = $USER->id;
402 } else {
403 $userid = is_object($user) ? $user->id : $user;
406 // make sure forcelogin cuts off not-logged-in users if enabled
407 if (!empty($CFG->forcelogin) and $userid == 0) {
408 return false;
411 // make sure the guest account and not-logged-in users never get any risky caps no matter what the actual settings are.
412 if (($capinfo->captype === 'write') or ($capinfo->riskbitmask & (RISK_XSS | RISK_CONFIG | RISK_DATALOSS))) {
413 if (isguestuser($userid) or $userid == 0) {
414 return false;
418 // somehow make sure the user is not deleted and actually exists
419 if ($userid != 0) {
420 if ($userid == $USER->id and isset($USER->deleted)) {
421 // this prevents one query per page, it is a bit of cheating,
422 // but hopefully session is terminated properly once user is deleted
423 if ($USER->deleted) {
424 return false;
426 } else {
427 if (!context_user::instance($userid, IGNORE_MISSING)) {
428 // no user context == invalid userid
429 return false;
434 // context path/depth must be valid
435 if (empty($context->path) or $context->depth == 0) {
436 // this should not happen often, each upgrade tries to rebuild the context paths
437 debugging('Context id '.$context->id.' does not have valid path, please use build_context_path()');
438 if (is_siteadmin($userid)) {
439 return true;
440 } else {
441 return false;
445 // Find out if user is admin - it is not possible to override the doanything in any way
446 // and it is not possible to switch to admin role either.
447 if ($doanything) {
448 if (is_siteadmin($userid)) {
449 if ($userid != $USER->id) {
450 return true;
452 // make sure switchrole is not used in this context
453 if (empty($USER->access['rsw'])) {
454 return true;
456 $parts = explode('/', trim($context->path, '/'));
457 $path = '';
458 $switched = false;
459 foreach ($parts as $part) {
460 $path .= '/' . $part;
461 if (!empty($USER->access['rsw'][$path])) {
462 $switched = true;
463 break;
466 if (!$switched) {
467 return true;
469 //ok, admin switched role in this context, let's use normal access control rules
473 // Careful check for staleness...
474 $context->reload_if_dirty();
476 if ($USER->id == $userid) {
477 if (!isset($USER->access)) {
478 load_all_capabilities();
480 $access =& $USER->access;
482 } else {
483 // make sure user accessdata is really loaded
484 get_user_accessdata($userid, true);
485 $access =& $ACCESSLIB_PRIVATE->accessdatabyuser[$userid];
489 // Load accessdata for below-the-course context if necessary,
490 // all contexts at and above all courses are already loaded
491 if ($context->contextlevel != CONTEXT_COURSE and $coursecontext = $context->get_course_context(false)) {
492 load_course_context($userid, $coursecontext, $access);
495 return has_capability_in_accessdata($capability, $context, $access);
499 * Check if the user has any one of several capabilities from a list.
501 * This is just a utility method that calls has_capability in a loop. Try to put
502 * the capabilities that most users are likely to have first in the list for best
503 * performance.
505 * @category access
506 * @see has_capability()
508 * @param array $capabilities an array of capability names.
509 * @param context $context the context to check the capability in. You normally get this with instance method of a context class.
510 * @param integer|stdClass $user A user id or object. By default (null) checks the permissions of the current user.
511 * @param boolean $doanything If false, ignore effect of admin role assignment
512 * @return boolean true if the user has any of these capabilities. Otherwise false.
514 function has_any_capability(array $capabilities, context $context, $user = null, $doanything = true) {
515 foreach ($capabilities as $capability) {
516 if (has_capability($capability, $context, $user, $doanything)) {
517 return true;
520 return false;
524 * Check if the user has all the capabilities in a list.
526 * This is just a utility method that calls has_capability in a loop. Try to put
527 * the capabilities that fewest users are likely to have first in the list for best
528 * performance.
530 * @category access
531 * @see has_capability()
533 * @param array $capabilities an array of capability names.
534 * @param context $context the context to check the capability in. You normally get this with instance method of a context class.
535 * @param integer|stdClass $user A user id or object. By default (null) checks the permissions of the current user.
536 * @param boolean $doanything If false, ignore effect of admin role assignment
537 * @return boolean true if the user has all of these capabilities. Otherwise false.
539 function has_all_capabilities(array $capabilities, context $context, $user = null, $doanything = true) {
540 foreach ($capabilities as $capability) {
541 if (!has_capability($capability, $context, $user, $doanything)) {
542 return false;
545 return true;
549 * Check if the user is an admin at the site level.
551 * Please note that use of proper capabilities is always encouraged,
552 * this function is supposed to be used from core or for temporary hacks.
554 * @category access
556 * @param int|stdClass $user_or_id user id or user object
557 * @return bool true if user is one of the administrators, false otherwise
559 function is_siteadmin($user_or_id = null) {
560 global $CFG, $USER;
562 if ($user_or_id === null) {
563 $user_or_id = $USER;
566 if (empty($user_or_id)) {
567 return false;
569 if (!empty($user_or_id->id)) {
570 $userid = $user_or_id->id;
571 } else {
572 $userid = $user_or_id;
575 // Because this script is called many times (150+ for course page) with
576 // the same parameters, it is worth doing minor optimisations. This static
577 // cache stores the value for a single userid, saving about 2ms from course
578 // page load time without using significant memory. As the static cache
579 // also includes the value it depends on, this cannot break unit tests.
580 static $knownid, $knownresult, $knownsiteadmins;
581 if ($knownid === $userid && $knownsiteadmins === $CFG->siteadmins) {
582 return $knownresult;
584 $knownid = $userid;
585 $knownsiteadmins = $CFG->siteadmins;
587 $siteadmins = explode(',', $CFG->siteadmins);
588 $knownresult = in_array($userid, $siteadmins);
589 return $knownresult;
593 * Returns true if user has at least one role assign
594 * of 'coursecontact' role (is potentially listed in some course descriptions).
596 * @param int $userid
597 * @return bool
599 function has_coursecontact_role($userid) {
600 global $DB, $CFG;
602 if (empty($CFG->coursecontact)) {
603 return false;
605 $sql = "SELECT 1
606 FROM {role_assignments}
607 WHERE userid = :userid AND roleid IN ($CFG->coursecontact)";
608 return $DB->record_exists_sql($sql, array('userid'=>$userid));
612 * Does the user have a capability to do something?
614 * Walk the accessdata array and return true/false.
615 * Deals with prohibits, role switching, aggregating
616 * capabilities, etc.
618 * The main feature of here is being FAST and with no
619 * side effects.
621 * Notes:
623 * Switch Role merges with default role
624 * ------------------------------------
625 * If you are a teacher in course X, you have at least
626 * teacher-in-X + defaultloggedinuser-sitewide. So in the
627 * course you'll have techer+defaultloggedinuser.
628 * We try to mimic that in switchrole.
630 * Permission evaluation
631 * ---------------------
632 * Originally there was an extremely complicated way
633 * to determine the user access that dealt with
634 * "locality" or role assignments and role overrides.
635 * Now we simply evaluate access for each role separately
636 * and then verify if user has at least one role with allow
637 * and at the same time no role with prohibit.
639 * @access private
640 * @param string $capability
641 * @param context $context
642 * @param array $accessdata
643 * @return bool
645 function has_capability_in_accessdata($capability, context $context, array &$accessdata) {
646 global $CFG;
648 // Build $paths as a list of current + all parent "paths" with order bottom-to-top
649 $path = $context->path;
650 $paths = array($path);
651 while($path = rtrim($path, '0123456789')) {
652 $path = rtrim($path, '/');
653 if ($path === '') {
654 break;
656 $paths[] = $path;
659 $roles = array();
660 $switchedrole = false;
662 // Find out if role switched
663 if (!empty($accessdata['rsw'])) {
664 // From the bottom up...
665 foreach ($paths as $path) {
666 if (isset($accessdata['rsw'][$path])) {
667 // Found a switchrole assignment - check for that role _plus_ the default user role
668 $roles = array($accessdata['rsw'][$path]=>null, $CFG->defaultuserroleid=>null);
669 $switchedrole = true;
670 break;
675 if (!$switchedrole) {
676 // get all users roles in this context and above
677 foreach ($paths as $path) {
678 if (isset($accessdata['ra'][$path])) {
679 foreach ($accessdata['ra'][$path] as $roleid) {
680 $roles[$roleid] = null;
686 // Now find out what access is given to each role, going bottom-->up direction
687 $allowed = false;
688 foreach ($roles as $roleid => $ignored) {
689 foreach ($paths as $path) {
690 if (isset($accessdata['rdef']["{$path}:$roleid"][$capability])) {
691 $perm = (int)$accessdata['rdef']["{$path}:$roleid"][$capability];
692 if ($perm === CAP_PROHIBIT) {
693 // any CAP_PROHIBIT found means no permission for the user
694 return false;
696 if (is_null($roles[$roleid])) {
697 $roles[$roleid] = $perm;
701 // CAP_ALLOW in any role means the user has a permission, we continue only to detect prohibits
702 $allowed = ($allowed or $roles[$roleid] === CAP_ALLOW);
705 return $allowed;
709 * A convenience function that tests has_capability, and displays an error if
710 * the user does not have that capability.
712 * NOTE before Moodle 2.0, this function attempted to make an appropriate
713 * require_login call before checking the capability. This is no longer the case.
714 * You must call require_login (or one of its variants) if you want to check the
715 * user is logged in, before you call this function.
717 * @see has_capability()
719 * @param string $capability the name of the capability to check. For example mod/forum:view
720 * @param context $context the context to check the capability in. You normally get this with {@link get_context_instance}.
721 * @param int $userid A user id. By default (null) checks the permissions of the current user.
722 * @param bool $doanything If false, ignore effect of admin role assignment
723 * @param string $errormessage The error string to to user. Defaults to 'nopermissions'.
724 * @param string $stringfile The language file to load the error string from. Defaults to 'error'.
725 * @return void terminates with an error if the user does not have the given capability.
727 function require_capability($capability, context $context, $userid = null, $doanything = true,
728 $errormessage = 'nopermissions', $stringfile = '') {
729 if (!has_capability($capability, $context, $userid, $doanything)) {
730 throw new required_capability_exception($context, $capability, $errormessage, $stringfile);
735 * Return a nested array showing role assignments
736 * all relevant role capabilities for the user at
737 * site/course_category/course levels
739 * We do _not_ delve deeper than courses because the number of
740 * overrides at the module/block levels can be HUGE.
742 * [ra] => [/path][roleid]=roleid
743 * [rdef] => [/path:roleid][capability]=permission
745 * @access private
746 * @param int $userid - the id of the user
747 * @return array access info array
749 function get_user_access_sitewide($userid) {
750 global $CFG, $DB, $ACCESSLIB_PRIVATE;
752 /* Get in a few cheap DB queries...
753 * - role assignments
754 * - relevant role caps
755 * - above and within this user's RAs
756 * - below this user's RAs - limited to course level
759 // raparents collects paths & roles we need to walk up the parenthood to build the minimal rdef
760 $raparents = array();
761 $accessdata = get_empty_accessdata();
763 // start with the default role
764 if (!empty($CFG->defaultuserroleid)) {
765 $syscontext = context_system::instance();
766 $accessdata['ra'][$syscontext->path][(int)$CFG->defaultuserroleid] = (int)$CFG->defaultuserroleid;
767 $raparents[$CFG->defaultuserroleid][$syscontext->id] = $syscontext->id;
770 // load the "default frontpage role"
771 if (!empty($CFG->defaultfrontpageroleid)) {
772 $frontpagecontext = context_course::instance(get_site()->id);
773 if ($frontpagecontext->path) {
774 $accessdata['ra'][$frontpagecontext->path][(int)$CFG->defaultfrontpageroleid] = (int)$CFG->defaultfrontpageroleid;
775 $raparents[$CFG->defaultfrontpageroleid][$frontpagecontext->id] = $frontpagecontext->id;
779 // preload every assigned role at and above course context
780 $sql = "SELECT ctx.path, ra.roleid, ra.contextid
781 FROM {role_assignments} ra
782 JOIN {context} ctx
783 ON ctx.id = ra.contextid
784 LEFT JOIN {block_instances} bi
785 ON (ctx.contextlevel = ".CONTEXT_BLOCK." AND bi.id = ctx.instanceid)
786 LEFT JOIN {context} bpctx
787 ON (bpctx.id = bi.parentcontextid)
788 WHERE ra.userid = :userid
789 AND (ctx.contextlevel <= ".CONTEXT_COURSE." OR bpctx.contextlevel < ".CONTEXT_COURSE.")";
790 $params = array('userid'=>$userid);
791 $rs = $DB->get_recordset_sql($sql, $params);
792 foreach ($rs as $ra) {
793 // RAs leafs are arrays to support multi-role assignments...
794 $accessdata['ra'][$ra->path][(int)$ra->roleid] = (int)$ra->roleid;
795 $raparents[$ra->roleid][$ra->contextid] = $ra->contextid;
797 $rs->close();
799 if (empty($raparents)) {
800 return $accessdata;
803 // now get overrides of interesting roles in all interesting child contexts
804 // hopefully we will not run out of SQL limits here,
805 // users would have to have very many roles at/above course context...
806 $sqls = array();
807 $params = array();
809 static $cp = 0;
810 foreach ($raparents as $roleid=>$ras) {
811 $cp++;
812 list($sqlcids, $cids) = $DB->get_in_or_equal($ras, SQL_PARAMS_NAMED, 'c'.$cp.'_');
813 $params = array_merge($params, $cids);
814 $params['r'.$cp] = $roleid;
815 $sqls[] = "(SELECT ctx.path, rc.roleid, rc.capability, rc.permission
816 FROM {role_capabilities} rc
817 JOIN {context} ctx
818 ON (ctx.id = rc.contextid)
819 JOIN {context} pctx
820 ON (pctx.id $sqlcids
821 AND (ctx.id = pctx.id
822 OR ctx.path LIKE ".$DB->sql_concat('pctx.path',"'/%'")."
823 OR pctx.path LIKE ".$DB->sql_concat('ctx.path',"'/%'")."))
824 LEFT JOIN {block_instances} bi
825 ON (ctx.contextlevel = ".CONTEXT_BLOCK." AND bi.id = ctx.instanceid)
826 LEFT JOIN {context} bpctx
827 ON (bpctx.id = bi.parentcontextid)
828 WHERE rc.roleid = :r{$cp}
829 AND (ctx.contextlevel <= ".CONTEXT_COURSE." OR bpctx.contextlevel < ".CONTEXT_COURSE.")
833 // fixed capability order is necessary for rdef dedupe
834 $rs = $DB->get_recordset_sql(implode("\nUNION\n", $sqls). "ORDER BY capability", $params);
836 foreach ($rs as $rd) {
837 $k = $rd->path.':'.$rd->roleid;
838 $accessdata['rdef'][$k][$rd->capability] = (int)$rd->permission;
840 $rs->close();
842 // share the role definitions
843 foreach ($accessdata['rdef'] as $k=>$unused) {
844 if (!isset($ACCESSLIB_PRIVATE->rolepermissions[$k])) {
845 $ACCESSLIB_PRIVATE->rolepermissions[$k] = $accessdata['rdef'][$k];
847 $accessdata['rdef_count']++;
848 $accessdata['rdef'][$k] =& $ACCESSLIB_PRIVATE->rolepermissions[$k];
851 return $accessdata;
855 * Add to the access ctrl array the data needed by a user for a given course.
857 * This function injects all course related access info into the accessdata array.
859 * @access private
860 * @param int $userid the id of the user
861 * @param context_course $coursecontext course context
862 * @param array $accessdata accessdata array (modified)
863 * @return void modifies $accessdata parameter
865 function load_course_context($userid, context_course $coursecontext, &$accessdata) {
866 global $DB, $CFG, $ACCESSLIB_PRIVATE;
868 if (empty($coursecontext->path)) {
869 // weird, this should not happen
870 return;
873 if (isset($accessdata['loaded'][$coursecontext->instanceid])) {
874 // already loaded, great!
875 return;
878 $roles = array();
880 if (empty($userid)) {
881 if (!empty($CFG->notloggedinroleid)) {
882 $roles[$CFG->notloggedinroleid] = $CFG->notloggedinroleid;
885 } else if (isguestuser($userid)) {
886 if ($guestrole = get_guest_role()) {
887 $roles[$guestrole->id] = $guestrole->id;
890 } else {
891 // Interesting role assignments at, above and below the course context
892 list($parentsaself, $params) = $DB->get_in_or_equal($coursecontext->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'pc_');
893 $params['userid'] = $userid;
894 $params['children'] = $coursecontext->path."/%";
895 $sql = "SELECT ra.*, ctx.path
896 FROM {role_assignments} ra
897 JOIN {context} ctx ON ra.contextid = ctx.id
898 WHERE ra.userid = :userid AND (ctx.id $parentsaself OR ctx.path LIKE :children)";
899 $rs = $DB->get_recordset_sql($sql, $params);
901 // add missing role definitions
902 foreach ($rs as $ra) {
903 $accessdata['ra'][$ra->path][(int)$ra->roleid] = (int)$ra->roleid;
904 $roles[$ra->roleid] = $ra->roleid;
906 $rs->close();
908 // add the "default frontpage role" when on the frontpage
909 if (!empty($CFG->defaultfrontpageroleid)) {
910 $frontpagecontext = context_course::instance(get_site()->id);
911 if ($frontpagecontext->id == $coursecontext->id) {
912 $roles[$CFG->defaultfrontpageroleid] = $CFG->defaultfrontpageroleid;
916 // do not forget the default role
917 if (!empty($CFG->defaultuserroleid)) {
918 $roles[$CFG->defaultuserroleid] = $CFG->defaultuserroleid;
922 if (!$roles) {
923 // weird, default roles must be missing...
924 $accessdata['loaded'][$coursecontext->instanceid] = 1;
925 return;
928 // now get overrides of interesting roles in all interesting contexts (this course + children + parents)
929 $params = array('c'=>$coursecontext->id);
930 list($parentsaself, $rparams) = $DB->get_in_or_equal($coursecontext->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'pc_');
931 $params = array_merge($params, $rparams);
932 list($roleids, $rparams) = $DB->get_in_or_equal($roles, SQL_PARAMS_NAMED, 'r_');
933 $params = array_merge($params, $rparams);
935 $sql = "SELECT ctx.path, rc.roleid, rc.capability, rc.permission
936 FROM {role_capabilities} rc
937 JOIN {context} ctx
938 ON (ctx.id = rc.contextid)
939 JOIN {context} cctx
940 ON (cctx.id = :c
941 AND (ctx.id $parentsaself OR ctx.path LIKE ".$DB->sql_concat('cctx.path',"'/%'")."))
942 WHERE rc.roleid $roleids
943 ORDER BY rc.capability"; // fixed capability order is necessary for rdef dedupe
944 $rs = $DB->get_recordset_sql($sql, $params);
946 $newrdefs = array();
947 foreach ($rs as $rd) {
948 $k = $rd->path.':'.$rd->roleid;
949 if (isset($accessdata['rdef'][$k])) {
950 continue;
952 $newrdefs[$k][$rd->capability] = (int)$rd->permission;
954 $rs->close();
956 // share new role definitions
957 foreach ($newrdefs as $k=>$unused) {
958 if (!isset($ACCESSLIB_PRIVATE->rolepermissions[$k])) {
959 $ACCESSLIB_PRIVATE->rolepermissions[$k] = $newrdefs[$k];
961 $accessdata['rdef_count']++;
962 $accessdata['rdef'][$k] =& $ACCESSLIB_PRIVATE->rolepermissions[$k];
965 $accessdata['loaded'][$coursecontext->instanceid] = 1;
967 // we want to deduplicate the USER->access from time to time, this looks like a good place,
968 // because we have to do it before the end of session
969 dedupe_user_access();
973 * Add to the access ctrl array the data needed by a role for a given context.
975 * The data is added in the rdef key.
976 * This role-centric function is useful for role_switching
977 * and temporary course roles.
979 * @access private
980 * @param int $roleid the id of the user
981 * @param context $context needs path!
982 * @param array $accessdata accessdata array (is modified)
983 * @return array
985 function load_role_access_by_context($roleid, context $context, &$accessdata) {
986 global $DB, $ACCESSLIB_PRIVATE;
988 /* Get the relevant rolecaps into rdef
989 * - relevant role caps
990 * - at ctx and above
991 * - below this ctx
994 if (empty($context->path)) {
995 // weird, this should not happen
996 return;
999 list($parentsaself, $params) = $DB->get_in_or_equal($context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'pc_');
1000 $params['roleid'] = $roleid;
1001 $params['childpath'] = $context->path.'/%';
1003 $sql = "SELECT ctx.path, rc.capability, rc.permission
1004 FROM {role_capabilities} rc
1005 JOIN {context} ctx ON (rc.contextid = ctx.id)
1006 WHERE rc.roleid = :roleid AND (ctx.id $parentsaself OR ctx.path LIKE :childpath)
1007 ORDER BY rc.capability"; // fixed capability order is necessary for rdef dedupe
1008 $rs = $DB->get_recordset_sql($sql, $params);
1010 $newrdefs = array();
1011 foreach ($rs as $rd) {
1012 $k = $rd->path.':'.$roleid;
1013 if (isset($accessdata['rdef'][$k])) {
1014 continue;
1016 $newrdefs[$k][$rd->capability] = (int)$rd->permission;
1018 $rs->close();
1020 // share new role definitions
1021 foreach ($newrdefs as $k=>$unused) {
1022 if (!isset($ACCESSLIB_PRIVATE->rolepermissions[$k])) {
1023 $ACCESSLIB_PRIVATE->rolepermissions[$k] = $newrdefs[$k];
1025 $accessdata['rdef_count']++;
1026 $accessdata['rdef'][$k] =& $ACCESSLIB_PRIVATE->rolepermissions[$k];
1031 * Returns empty accessdata structure.
1033 * @access private
1034 * @return array empt accessdata
1036 function get_empty_accessdata() {
1037 $accessdata = array(); // named list
1038 $accessdata['ra'] = array();
1039 $accessdata['rdef'] = array();
1040 $accessdata['rdef_count'] = 0; // this bloody hack is necessary because count($array) is slooooowwww in PHP
1041 $accessdata['rdef_lcc'] = 0; // rdef_count during the last compression
1042 $accessdata['loaded'] = array(); // loaded course contexts
1043 $accessdata['time'] = time();
1044 $accessdata['rsw'] = array();
1046 return $accessdata;
1050 * Get accessdata for a given user.
1052 * @access private
1053 * @param int $userid
1054 * @param bool $preloadonly true means do not return access array
1055 * @return array accessdata
1057 function get_user_accessdata($userid, $preloadonly=false) {
1058 global $CFG, $ACCESSLIB_PRIVATE, $USER;
1060 if (!empty($USER->acces['rdef']) and empty($ACCESSLIB_PRIVATE->rolepermissions)) {
1061 // share rdef from USER session with rolepermissions cache in order to conserve memory
1062 foreach($USER->acces['rdef'] as $k=>$v) {
1063 $ACCESSLIB_PRIVATE->rolepermissions[$k] =& $USER->acces['rdef'][$k];
1065 $ACCESSLIB_PRIVATE->accessdatabyuser[$USER->id] = $USER->acces;
1068 if (!isset($ACCESSLIB_PRIVATE->accessdatabyuser[$userid])) {
1069 if (empty($userid)) {
1070 if (!empty($CFG->notloggedinroleid)) {
1071 $accessdata = get_role_access($CFG->notloggedinroleid);
1072 } else {
1073 // weird
1074 return get_empty_accessdata();
1077 } else if (isguestuser($userid)) {
1078 if ($guestrole = get_guest_role()) {
1079 $accessdata = get_role_access($guestrole->id);
1080 } else {
1081 //weird
1082 return get_empty_accessdata();
1085 } else {
1086 $accessdata = get_user_access_sitewide($userid); // includes default role and frontpage role
1089 $ACCESSLIB_PRIVATE->accessdatabyuser[$userid] = $accessdata;
1092 if ($preloadonly) {
1093 return;
1094 } else {
1095 return $ACCESSLIB_PRIVATE->accessdatabyuser[$userid];
1100 * Try to minimise the size of $USER->access by eliminating duplicate override storage,
1101 * this function looks for contexts with the same overrides and shares them.
1103 * @access private
1104 * @return void
1106 function dedupe_user_access() {
1107 global $USER;
1109 if (CLI_SCRIPT) {
1110 // no session in CLI --> no compression necessary
1111 return;
1114 if (empty($USER->access['rdef_count'])) {
1115 // weird, this should not happen
1116 return;
1119 // the rdef is growing only, we never remove stuff from it, the rdef_lcc helps us to detect new stuff in rdef
1120 if ($USER->access['rdef_count'] - $USER->access['rdef_lcc'] > 10) {
1121 // do not compress after each change, wait till there is more stuff to be done
1122 return;
1125 $hashmap = array();
1126 foreach ($USER->access['rdef'] as $k=>$def) {
1127 $hash = sha1(serialize($def));
1128 if (isset($hashmap[$hash])) {
1129 $USER->access['rdef'][$k] =& $hashmap[$hash];
1130 } else {
1131 $hashmap[$hash] =& $USER->access['rdef'][$k];
1135 $USER->access['rdef_lcc'] = $USER->access['rdef_count'];
1139 * A convenience function to completely load all the capabilities
1140 * for the current user. It is called from has_capability() and functions change permissions.
1142 * Call it only _after_ you've setup $USER and called check_enrolment_plugins();
1143 * @see check_enrolment_plugins()
1145 * @access private
1146 * @return void
1148 function load_all_capabilities() {
1149 global $USER;
1151 // roles not installed yet - we are in the middle of installation
1152 if (during_initial_install()) {
1153 return;
1156 if (!isset($USER->id)) {
1157 // this should not happen
1158 $USER->id = 0;
1161 unset($USER->access);
1162 $USER->access = get_user_accessdata($USER->id);
1164 // deduplicate the overrides to minimize session size
1165 dedupe_user_access();
1167 // Clear to force a refresh
1168 unset($USER->mycourses);
1170 // init/reset internal enrol caches - active course enrolments and temp access
1171 $USER->enrol = array('enrolled'=>array(), 'tempguest'=>array());
1175 * A convenience function to completely reload all the capabilities
1176 * for the current user when roles have been updated in a relevant
1177 * context -- but PRESERVING switchroles and loginas.
1178 * This function resets all accesslib and context caches.
1180 * That is - completely transparent to the user.
1182 * Note: reloads $USER->access completely.
1184 * @access private
1185 * @return void
1187 function reload_all_capabilities() {
1188 global $USER, $DB, $ACCESSLIB_PRIVATE;
1190 // copy switchroles
1191 $sw = array();
1192 if (!empty($USER->access['rsw'])) {
1193 $sw = $USER->access['rsw'];
1196 accesslib_clear_all_caches(true);
1197 unset($USER->access);
1198 $ACCESSLIB_PRIVATE->dirtycontexts = array(); // prevent dirty flags refetching on this page
1200 load_all_capabilities();
1202 foreach ($sw as $path => $roleid) {
1203 if ($record = $DB->get_record('context', array('path'=>$path))) {
1204 $context = context::instance_by_id($record->id);
1205 role_switch($roleid, $context);
1211 * Adds a temp role to current USER->access array.
1213 * Useful for the "temporary guest" access we grant to logged-in users.
1214 * This is useful for enrol plugins only.
1216 * @since 2.2
1217 * @param context_course $coursecontext
1218 * @param int $roleid
1219 * @return void
1221 function load_temp_course_role(context_course $coursecontext, $roleid) {
1222 global $USER, $SITE;
1224 if (empty($roleid)) {
1225 debugging('invalid role specified in load_temp_course_role()');
1226 return;
1229 if ($coursecontext->instanceid == $SITE->id) {
1230 debugging('Can not use temp roles on the frontpage');
1231 return;
1234 if (!isset($USER->access)) {
1235 load_all_capabilities();
1238 $coursecontext->reload_if_dirty();
1240 if (isset($USER->access['ra'][$coursecontext->path][$roleid])) {
1241 return;
1244 // load course stuff first
1245 load_course_context($USER->id, $coursecontext, $USER->access);
1247 $USER->access['ra'][$coursecontext->path][(int)$roleid] = (int)$roleid;
1249 load_role_access_by_context($roleid, $coursecontext, $USER->access);
1253 * Removes any extra guest roles from current USER->access array.
1254 * This is useful for enrol plugins only.
1256 * @since 2.2
1257 * @param context_course $coursecontext
1258 * @return void
1260 function remove_temp_course_roles(context_course $coursecontext) {
1261 global $DB, $USER, $SITE;
1263 if ($coursecontext->instanceid == $SITE->id) {
1264 debugging('Can not use temp roles on the frontpage');
1265 return;
1268 if (empty($USER->access['ra'][$coursecontext->path])) {
1269 //no roles here, weird
1270 return;
1273 $sql = "SELECT DISTINCT ra.roleid AS id
1274 FROM {role_assignments} ra
1275 WHERE ra.contextid = :contextid AND ra.userid = :userid";
1276 $ras = $DB->get_records_sql($sql, array('contextid'=>$coursecontext->id, 'userid'=>$USER->id));
1278 $USER->access['ra'][$coursecontext->path] = array();
1279 foreach($ras as $r) {
1280 $USER->access['ra'][$coursecontext->path][(int)$r->id] = (int)$r->id;
1285 * Returns array of all role archetypes.
1287 * @return array
1289 function get_role_archetypes() {
1290 return array(
1291 'manager' => 'manager',
1292 'coursecreator' => 'coursecreator',
1293 'editingteacher' => 'editingteacher',
1294 'teacher' => 'teacher',
1295 'student' => 'student',
1296 'guest' => 'guest',
1297 'user' => 'user',
1298 'frontpage' => 'frontpage'
1303 * Assign the defaults found in this capability definition to roles that have
1304 * the corresponding legacy capabilities assigned to them.
1306 * @param string $capability
1307 * @param array $legacyperms an array in the format (example):
1308 * 'guest' => CAP_PREVENT,
1309 * 'student' => CAP_ALLOW,
1310 * 'teacher' => CAP_ALLOW,
1311 * 'editingteacher' => CAP_ALLOW,
1312 * 'coursecreator' => CAP_ALLOW,
1313 * 'manager' => CAP_ALLOW
1314 * @return boolean success or failure.
1316 function assign_legacy_capabilities($capability, $legacyperms) {
1318 $archetypes = get_role_archetypes();
1320 foreach ($legacyperms as $type => $perm) {
1322 $systemcontext = context_system::instance();
1323 if ($type === 'admin') {
1324 debugging('Legacy type admin in access.php was renamed to manager, please update the code.');
1325 $type = 'manager';
1328 if (!array_key_exists($type, $archetypes)) {
1329 print_error('invalidlegacy', '', '', $type);
1332 if ($roles = get_archetype_roles($type)) {
1333 foreach ($roles as $role) {
1334 // Assign a site level capability.
1335 if (!assign_capability($capability, $perm, $role->id, $systemcontext->id)) {
1336 return false;
1341 return true;
1345 * Verify capability risks.
1347 * @param stdClass $capability a capability - a row from the capabilities table.
1348 * @return boolean whether this capability is safe - that is, whether people with the
1349 * safeoverrides capability should be allowed to change it.
1351 function is_safe_capability($capability) {
1352 return !((RISK_DATALOSS | RISK_MANAGETRUST | RISK_CONFIG | RISK_XSS | RISK_PERSONAL) & $capability->riskbitmask);
1356 * Get the local override (if any) for a given capability in a role in a context
1358 * @param int $roleid
1359 * @param int $contextid
1360 * @param string $capability
1361 * @return stdClass local capability override
1363 function get_local_override($roleid, $contextid, $capability) {
1364 global $DB;
1365 return $DB->get_record('role_capabilities', array('roleid'=>$roleid, 'capability'=>$capability, 'contextid'=>$contextid));
1369 * Returns context instance plus related course and cm instances
1371 * @param int $contextid
1372 * @return array of ($context, $course, $cm)
1374 function get_context_info_array($contextid) {
1375 global $DB;
1377 $context = context::instance_by_id($contextid, MUST_EXIST);
1378 $course = null;
1379 $cm = null;
1381 if ($context->contextlevel == CONTEXT_COURSE) {
1382 $course = $DB->get_record('course', array('id'=>$context->instanceid), '*', MUST_EXIST);
1384 } else if ($context->contextlevel == CONTEXT_MODULE) {
1385 $cm = get_coursemodule_from_id('', $context->instanceid, 0, false, MUST_EXIST);
1386 $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
1388 } else if ($context->contextlevel == CONTEXT_BLOCK) {
1389 $parent = $context->get_parent_context();
1391 if ($parent->contextlevel == CONTEXT_COURSE) {
1392 $course = $DB->get_record('course', array('id'=>$parent->instanceid), '*', MUST_EXIST);
1393 } else if ($parent->contextlevel == CONTEXT_MODULE) {
1394 $cm = get_coursemodule_from_id('', $parent->instanceid, 0, false, MUST_EXIST);
1395 $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
1399 return array($context, $course, $cm);
1403 * Function that creates a role
1405 * @param string $name role name
1406 * @param string $shortname role short name
1407 * @param string $description role description
1408 * @param string $archetype
1409 * @return int id or dml_exception
1411 function create_role($name, $shortname, $description, $archetype = '') {
1412 global $DB;
1414 if (strpos($archetype, 'moodle/legacy:') !== false) {
1415 throw new coding_exception('Use new role archetype parameter in create_role() instead of old legacy capabilities.');
1418 // verify role archetype actually exists
1419 $archetypes = get_role_archetypes();
1420 if (empty($archetypes[$archetype])) {
1421 $archetype = '';
1424 // Insert the role record.
1425 $role = new stdClass();
1426 $role->name = $name;
1427 $role->shortname = $shortname;
1428 $role->description = $description;
1429 $role->archetype = $archetype;
1431 //find free sortorder number
1432 $role->sortorder = $DB->get_field('role', 'MAX(sortorder) + 1', array());
1433 if (empty($role->sortorder)) {
1434 $role->sortorder = 1;
1436 $id = $DB->insert_record('role', $role);
1438 return $id;
1442 * Function that deletes a role and cleanups up after it
1444 * @param int $roleid id of role to delete
1445 * @return bool always true
1447 function delete_role($roleid) {
1448 global $DB;
1450 // first unssign all users
1451 role_unassign_all(array('roleid'=>$roleid));
1453 // cleanup all references to this role, ignore errors
1454 $DB->delete_records('role_capabilities', array('roleid'=>$roleid));
1455 $DB->delete_records('role_allow_assign', array('roleid'=>$roleid));
1456 $DB->delete_records('role_allow_assign', array('allowassign'=>$roleid));
1457 $DB->delete_records('role_allow_override', array('roleid'=>$roleid));
1458 $DB->delete_records('role_allow_override', array('allowoverride'=>$roleid));
1459 $DB->delete_records('role_names', array('roleid'=>$roleid));
1460 $DB->delete_records('role_context_levels', array('roleid'=>$roleid));
1462 // finally delete the role itself
1463 // get this before the name is gone for logging
1464 $rolename = $DB->get_field('role', 'name', array('id'=>$roleid));
1466 $DB->delete_records('role', array('id'=>$roleid));
1468 add_to_log(SITEID, 'role', 'delete', 'admin/roles/action=delete&roleid='.$roleid, $rolename, '');
1470 return true;
1474 * Function to write context specific overrides, or default capabilities.
1476 * NOTE: use $context->mark_dirty() after this
1478 * @param string $capability string name
1479 * @param int $permission CAP_ constants
1480 * @param int $roleid role id
1481 * @param int|context $contextid context id
1482 * @param bool $overwrite
1483 * @return bool always true or exception
1485 function assign_capability($capability, $permission, $roleid, $contextid, $overwrite = false) {
1486 global $USER, $DB;
1488 if ($contextid instanceof context) {
1489 $context = $contextid;
1490 } else {
1491 $context = context::instance_by_id($contextid);
1494 if (empty($permission) || $permission == CAP_INHERIT) { // if permission is not set
1495 unassign_capability($capability, $roleid, $context->id);
1496 return true;
1499 $existing = $DB->get_record('role_capabilities', array('contextid'=>$context->id, 'roleid'=>$roleid, 'capability'=>$capability));
1501 if ($existing and !$overwrite) { // We want to keep whatever is there already
1502 return true;
1505 $cap = new stdClass();
1506 $cap->contextid = $context->id;
1507 $cap->roleid = $roleid;
1508 $cap->capability = $capability;
1509 $cap->permission = $permission;
1510 $cap->timemodified = time();
1511 $cap->modifierid = empty($USER->id) ? 0 : $USER->id;
1513 if ($existing) {
1514 $cap->id = $existing->id;
1515 $DB->update_record('role_capabilities', $cap);
1516 } else {
1517 if ($DB->record_exists('context', array('id'=>$context->id))) {
1518 $DB->insert_record('role_capabilities', $cap);
1521 return true;
1525 * Unassign a capability from a role.
1527 * NOTE: use $context->mark_dirty() after this
1529 * @param string $capability the name of the capability
1530 * @param int $roleid the role id
1531 * @param int|context $contextid null means all contexts
1532 * @return boolean true or exception
1534 function unassign_capability($capability, $roleid, $contextid = null) {
1535 global $DB;
1537 if (!empty($contextid)) {
1538 if ($contextid instanceof context) {
1539 $context = $contextid;
1540 } else {
1541 $context = context::instance_by_id($contextid);
1543 // delete from context rel, if this is the last override in this context
1544 $DB->delete_records('role_capabilities', array('capability'=>$capability, 'roleid'=>$roleid, 'contextid'=>$context->id));
1545 } else {
1546 $DB->delete_records('role_capabilities', array('capability'=>$capability, 'roleid'=>$roleid));
1548 return true;
1552 * Get the roles that have a given capability assigned to it
1554 * This function does not resolve the actual permission of the capability.
1555 * It just checks for permissions and overrides.
1556 * Use get_roles_with_cap_in_context() if resolution is required.
1558 * @param string $capability capability name (string)
1559 * @param string $permission optional, the permission defined for this capability
1560 * either CAP_ALLOW, CAP_PREVENT or CAP_PROHIBIT. Defaults to null which means any.
1561 * @param stdClass $context null means any
1562 * @return array of role records
1564 function get_roles_with_capability($capability, $permission = null, $context = null) {
1565 global $DB;
1567 if ($context) {
1568 $contexts = $context->get_parent_context_ids(true);
1569 list($insql, $params) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED, 'ctx');
1570 $contextsql = "AND rc.contextid $insql";
1571 } else {
1572 $params = array();
1573 $contextsql = '';
1576 if ($permission) {
1577 $permissionsql = " AND rc.permission = :permission";
1578 $params['permission'] = $permission;
1579 } else {
1580 $permissionsql = '';
1583 $sql = "SELECT r.*
1584 FROM {role} r
1585 WHERE r.id IN (SELECT rc.roleid
1586 FROM {role_capabilities} rc
1587 WHERE rc.capability = :capname
1588 $contextsql
1589 $permissionsql)";
1590 $params['capname'] = $capability;
1593 return $DB->get_records_sql($sql, $params);
1597 * This function makes a role-assignment (a role for a user in a particular context)
1599 * @param int $roleid the role of the id
1600 * @param int $userid userid
1601 * @param int|context $contextid id of the context
1602 * @param string $component example 'enrol_ldap', defaults to '' which means manual assignment,
1603 * @param int $itemid id of enrolment/auth plugin
1604 * @param string $timemodified defaults to current time
1605 * @return int new/existing id of the assignment
1607 function role_assign($roleid, $userid, $contextid, $component = '', $itemid = 0, $timemodified = '') {
1608 global $USER, $DB;
1610 // first of all detect if somebody is using old style parameters
1611 if ($contextid === 0 or is_numeric($component)) {
1612 throw new coding_exception('Invalid call to role_assign(), code needs to be updated to use new order of parameters');
1615 // now validate all parameters
1616 if (empty($roleid)) {
1617 throw new coding_exception('Invalid call to role_assign(), roleid can not be empty');
1620 if (empty($userid)) {
1621 throw new coding_exception('Invalid call to role_assign(), userid can not be empty');
1624 if ($itemid) {
1625 if (strpos($component, '_') === false) {
1626 throw new coding_exception('Invalid call to role_assign(), component must start with plugin type such as"enrol_" when itemid specified', 'component:'.$component);
1628 } else {
1629 $itemid = 0;
1630 if ($component !== '' and strpos($component, '_') === false) {
1631 throw new coding_exception('Invalid call to role_assign(), invalid component string', 'component:'.$component);
1635 if (!$DB->record_exists('user', array('id'=>$userid, 'deleted'=>0))) {
1636 throw new coding_exception('User ID does not exist or is deleted!', 'userid:'.$userid);
1639 if ($contextid instanceof context) {
1640 $context = $contextid;
1641 } else {
1642 $context = context::instance_by_id($contextid, MUST_EXIST);
1645 if (!$timemodified) {
1646 $timemodified = time();
1649 // Check for existing entry
1650 $ras = $DB->get_records('role_assignments', array('roleid'=>$roleid, 'contextid'=>$context->id, 'userid'=>$userid, 'component'=>$component, 'itemid'=>$itemid), 'id');
1652 if ($ras) {
1653 // role already assigned - this should not happen
1654 if (count($ras) > 1) {
1655 // very weird - remove all duplicates!
1656 $ra = array_shift($ras);
1657 foreach ($ras as $r) {
1658 $DB->delete_records('role_assignments', array('id'=>$r->id));
1660 } else {
1661 $ra = reset($ras);
1664 // actually there is no need to update, reset anything or trigger any event, so just return
1665 return $ra->id;
1668 // Create a new entry
1669 $ra = new stdClass();
1670 $ra->roleid = $roleid;
1671 $ra->contextid = $context->id;
1672 $ra->userid = $userid;
1673 $ra->component = $component;
1674 $ra->itemid = $itemid;
1675 $ra->timemodified = $timemodified;
1676 $ra->modifierid = empty($USER->id) ? 0 : $USER->id;
1678 $ra->id = $DB->insert_record('role_assignments', $ra);
1680 // mark context as dirty - again expensive, but needed
1681 $context->mark_dirty();
1683 if (!empty($USER->id) && $USER->id == $userid) {
1684 // If the user is the current user, then do full reload of capabilities too.
1685 reload_all_capabilities();
1688 events_trigger('role_assigned', $ra);
1690 return $ra->id;
1694 * Removes one role assignment
1696 * @param int $roleid
1697 * @param int $userid
1698 * @param int|context $contextid
1699 * @param string $component
1700 * @param int $itemid
1701 * @return void
1703 function role_unassign($roleid, $userid, $contextid, $component = '', $itemid = 0) {
1704 // first make sure the params make sense
1705 if ($roleid == 0 or $userid == 0 or $contextid == 0) {
1706 throw new coding_exception('Invalid call to role_unassign(), please use role_unassign_all() when removing multiple role assignments');
1709 if ($itemid) {
1710 if (strpos($component, '_') === false) {
1711 throw new coding_exception('Invalid call to role_assign(), component must start with plugin type such as "enrol_" when itemid specified', 'component:'.$component);
1713 } else {
1714 $itemid = 0;
1715 if ($component !== '' and strpos($component, '_') === false) {
1716 throw new coding_exception('Invalid call to role_assign(), invalid component string', 'component:'.$component);
1720 role_unassign_all(array('roleid'=>$roleid, 'userid'=>$userid, 'contextid'=>$contextid, 'component'=>$component, 'itemid'=>$itemid), false, false);
1724 * Removes multiple role assignments, parameters may contain:
1725 * 'roleid', 'userid', 'contextid', 'component', 'enrolid'.
1727 * @param array $params role assignment parameters
1728 * @param bool $subcontexts unassign in subcontexts too
1729 * @param bool $includemanual include manual role assignments too
1730 * @return void
1732 function role_unassign_all(array $params, $subcontexts = false, $includemanual = false) {
1733 global $USER, $CFG, $DB;
1735 if (!$params) {
1736 throw new coding_exception('Missing parameters in role_unsassign_all() call');
1739 $allowed = array('roleid', 'userid', 'contextid', 'component', 'itemid');
1740 foreach ($params as $key=>$value) {
1741 if (!in_array($key, $allowed)) {
1742 throw new coding_exception('Unknown role_unsassign_all() parameter key', 'key:'.$key);
1746 if (isset($params['component']) and $params['component'] !== '' and strpos($params['component'], '_') === false) {
1747 throw new coding_exception('Invalid component paramter in role_unsassign_all() call', 'component:'.$params['component']);
1750 if ($includemanual) {
1751 if (!isset($params['component']) or $params['component'] === '') {
1752 throw new coding_exception('include manual parameter requires component parameter in role_unsassign_all() call');
1756 if ($subcontexts) {
1757 if (empty($params['contextid'])) {
1758 throw new coding_exception('subcontexts paramtere requires component parameter in role_unsassign_all() call');
1762 $ras = $DB->get_records('role_assignments', $params);
1763 foreach($ras as $ra) {
1764 $DB->delete_records('role_assignments', array('id'=>$ra->id));
1765 if ($context = context::instance_by_id($ra->contextid, IGNORE_MISSING)) {
1766 // this is a bit expensive but necessary
1767 $context->mark_dirty();
1768 // If the user is the current user, then do full reload of capabilities too.
1769 if (!empty($USER->id) && $USER->id == $ra->userid) {
1770 reload_all_capabilities();
1773 events_trigger('role_unassigned', $ra);
1775 unset($ras);
1777 // process subcontexts
1778 if ($subcontexts and $context = context::instance_by_id($params['contextid'], IGNORE_MISSING)) {
1779 if ($params['contextid'] instanceof context) {
1780 $context = $params['contextid'];
1781 } else {
1782 $context = context::instance_by_id($params['contextid'], IGNORE_MISSING);
1785 if ($context) {
1786 $contexts = $context->get_child_contexts();
1787 $mparams = $params;
1788 foreach($contexts as $context) {
1789 $mparams['contextid'] = $context->id;
1790 $ras = $DB->get_records('role_assignments', $mparams);
1791 foreach($ras as $ra) {
1792 $DB->delete_records('role_assignments', array('id'=>$ra->id));
1793 // this is a bit expensive but necessary
1794 $context->mark_dirty();
1795 // If the user is the current user, then do full reload of capabilities too.
1796 if (!empty($USER->id) && $USER->id == $ra->userid) {
1797 reload_all_capabilities();
1799 events_trigger('role_unassigned', $ra);
1805 // do this once more for all manual role assignments
1806 if ($includemanual) {
1807 $params['component'] = '';
1808 role_unassign_all($params, $subcontexts, false);
1813 * Determines if a user is currently logged in
1815 * @category access
1817 * @return bool
1819 function isloggedin() {
1820 global $USER;
1822 return (!empty($USER->id));
1826 * Determines if a user is logged in as real guest user with username 'guest'.
1828 * @category access
1830 * @param int|object $user mixed user object or id, $USER if not specified
1831 * @return bool true if user is the real guest user, false if not logged in or other user
1833 function isguestuser($user = null) {
1834 global $USER, $DB, $CFG;
1836 // make sure we have the user id cached in config table, because we are going to use it a lot
1837 if (empty($CFG->siteguest)) {
1838 if (!$guestid = $DB->get_field('user', 'id', array('username'=>'guest', 'mnethostid'=>$CFG->mnet_localhost_id))) {
1839 // guest does not exist yet, weird
1840 return false;
1842 set_config('siteguest', $guestid);
1844 if ($user === null) {
1845 $user = $USER;
1848 if ($user === null) {
1849 // happens when setting the $USER
1850 return false;
1852 } else if (is_numeric($user)) {
1853 return ($CFG->siteguest == $user);
1855 } else if (is_object($user)) {
1856 if (empty($user->id)) {
1857 return false; // not logged in means is not be guest
1858 } else {
1859 return ($CFG->siteguest == $user->id);
1862 } else {
1863 throw new coding_exception('Invalid user parameter supplied for isguestuser() function!');
1868 * Does user have a (temporary or real) guest access to course?
1870 * @category access
1872 * @param context $context
1873 * @param stdClass|int $user
1874 * @return bool
1876 function is_guest(context $context, $user = null) {
1877 global $USER;
1879 // first find the course context
1880 $coursecontext = $context->get_course_context();
1882 // make sure there is a real user specified
1883 if ($user === null) {
1884 $userid = isset($USER->id) ? $USER->id : 0;
1885 } else {
1886 $userid = is_object($user) ? $user->id : $user;
1889 if (isguestuser($userid)) {
1890 // can not inspect or be enrolled
1891 return true;
1894 if (has_capability('moodle/course:view', $coursecontext, $user)) {
1895 // viewing users appear out of nowhere, they are neither guests nor participants
1896 return false;
1899 // consider only real active enrolments here
1900 if (is_enrolled($coursecontext, $user, '', true)) {
1901 return false;
1904 return true;
1908 * Returns true if the user has moodle/course:view capability in the course,
1909 * this is intended for admins, managers (aka small admins), inspectors, etc.
1911 * @category access
1913 * @param context $context
1914 * @param int|stdClass $user if null $USER is used
1915 * @param string $withcapability extra capability name
1916 * @return bool
1918 function is_viewing(context $context, $user = null, $withcapability = '') {
1919 // first find the course context
1920 $coursecontext = $context->get_course_context();
1922 if (isguestuser($user)) {
1923 // can not inspect
1924 return false;
1927 if (!has_capability('moodle/course:view', $coursecontext, $user)) {
1928 // admins are allowed to inspect courses
1929 return false;
1932 if ($withcapability and !has_capability($withcapability, $context, $user)) {
1933 // site admins always have the capability, but the enrolment above blocks
1934 return false;
1937 return true;
1941 * Returns true if user is enrolled (is participating) in course
1942 * this is intended for students and teachers.
1944 * Since 2.2 the result for active enrolments and current user are cached.
1946 * @package core_enrol
1947 * @category access
1949 * @param context $context
1950 * @param int|stdClass $user if null $USER is used, otherwise user object or id expected
1951 * @param string $withcapability extra capability name
1952 * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
1953 * @return bool
1955 function is_enrolled(context $context, $user = null, $withcapability = '', $onlyactive = false) {
1956 global $USER, $DB;
1958 // first find the course context
1959 $coursecontext = $context->get_course_context();
1961 // make sure there is a real user specified
1962 if ($user === null) {
1963 $userid = isset($USER->id) ? $USER->id : 0;
1964 } else {
1965 $userid = is_object($user) ? $user->id : $user;
1968 if (empty($userid)) {
1969 // not-logged-in!
1970 return false;
1971 } else if (isguestuser($userid)) {
1972 // guest account can not be enrolled anywhere
1973 return false;
1976 if ($coursecontext->instanceid == SITEID) {
1977 // everybody participates on frontpage
1978 } else {
1979 // try cached info first - the enrolled flag is set only when active enrolment present
1980 if ($USER->id == $userid) {
1981 $coursecontext->reload_if_dirty();
1982 if (isset($USER->enrol['enrolled'][$coursecontext->instanceid])) {
1983 if ($USER->enrol['enrolled'][$coursecontext->instanceid] > time()) {
1984 if ($withcapability and !has_capability($withcapability, $context, $userid)) {
1985 return false;
1987 return true;
1992 if ($onlyactive) {
1993 // look for active enrolments only
1994 $until = enrol_get_enrolment_end($coursecontext->instanceid, $userid);
1996 if ($until === false) {
1997 return false;
2000 if ($USER->id == $userid) {
2001 if ($until == 0) {
2002 $until = ENROL_MAX_TIMESTAMP;
2004 $USER->enrol['enrolled'][$coursecontext->instanceid] = $until;
2005 if (isset($USER->enrol['tempguest'][$coursecontext->instanceid])) {
2006 unset($USER->enrol['tempguest'][$coursecontext->instanceid]);
2007 remove_temp_course_roles($coursecontext);
2011 } else {
2012 // any enrolment is good for us here, even outdated, disabled or inactive
2013 $sql = "SELECT 'x'
2014 FROM {user_enrolments} ue
2015 JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
2016 JOIN {user} u ON u.id = ue.userid
2017 WHERE ue.userid = :userid AND u.deleted = 0";
2018 $params = array('userid'=>$userid, 'courseid'=>$coursecontext->instanceid);
2019 if (!$DB->record_exists_sql($sql, $params)) {
2020 return false;
2025 if ($withcapability and !has_capability($withcapability, $context, $userid)) {
2026 return false;
2029 return true;
2033 * Returns true if the user is able to access the course.
2035 * This function is in no way, shape, or form a substitute for require_login.
2036 * It should only be used in circumstances where it is not possible to call require_login
2037 * such as the navigation.
2039 * This function checks many of the methods of access to a course such as the view
2040 * capability, enrollments, and guest access. It also makes use of the cache
2041 * generated by require_login for guest access.
2043 * The flags within the $USER object that are used here should NEVER be used outside
2044 * of this function can_access_course and require_login. Doing so WILL break future
2045 * versions.
2047 * @param stdClass $course record
2048 * @param stdClass|int|null $user user record or id, current user if null
2049 * @param string $withcapability Check for this capability as well.
2050 * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
2051 * @return boolean Returns true if the user is able to access the course
2053 function can_access_course(stdClass $course, $user = null, $withcapability = '', $onlyactive = false) {
2054 global $DB, $USER;
2056 // this function originally accepted $coursecontext parameter
2057 if ($course instanceof context) {
2058 if ($course instanceof context_course) {
2059 debugging('deprecated context parameter, please use $course record');
2060 $coursecontext = $course;
2061 $course = $DB->get_record('course', array('id'=>$coursecontext->instanceid));
2062 } else {
2063 debugging('Invalid context parameter, please use $course record');
2064 return false;
2066 } else {
2067 $coursecontext = context_course::instance($course->id);
2070 if (!isset($USER->id)) {
2071 // should never happen
2072 $USER->id = 0;
2073 debugging('Course access check being performed on a user with no ID.', DEBUG_DEVELOPER);
2076 // make sure there is a user specified
2077 if ($user === null) {
2078 $userid = $USER->id;
2079 } else {
2080 $userid = is_object($user) ? $user->id : $user;
2082 unset($user);
2084 if ($withcapability and !has_capability($withcapability, $coursecontext, $userid)) {
2085 return false;
2088 if ($userid == $USER->id) {
2089 if (!empty($USER->access['rsw'][$coursecontext->path])) {
2090 // the fact that somebody switched role means they can access the course no matter to what role they switched
2091 return true;
2095 if (!$course->visible and !has_capability('moodle/course:viewhiddencourses', $coursecontext, $userid)) {
2096 return false;
2099 if (is_viewing($coursecontext, $userid)) {
2100 return true;
2103 if ($userid != $USER->id) {
2104 // for performance reasons we do not verify temporary guest access for other users, sorry...
2105 return is_enrolled($coursecontext, $userid, '', $onlyactive);
2108 // === from here we deal only with $USER ===
2110 $coursecontext->reload_if_dirty();
2112 if (isset($USER->enrol['enrolled'][$course->id])) {
2113 if ($USER->enrol['enrolled'][$course->id] > time()) {
2114 return true;
2117 if (isset($USER->enrol['tempguest'][$course->id])) {
2118 if ($USER->enrol['tempguest'][$course->id] > time()) {
2119 return true;
2123 if (is_enrolled($coursecontext, $USER, '', $onlyactive)) {
2124 return true;
2127 // if not enrolled try to gain temporary guest access
2128 $instances = $DB->get_records('enrol', array('courseid'=>$course->id, 'status'=>ENROL_INSTANCE_ENABLED), 'sortorder, id ASC');
2129 $enrols = enrol_get_plugins(true);
2130 foreach($instances as $instance) {
2131 if (!isset($enrols[$instance->enrol])) {
2132 continue;
2134 // Get a duration for the guest access, a timestamp in the future, 0 (always) or false.
2135 $until = $enrols[$instance->enrol]->try_guestaccess($instance);
2136 if ($until !== false and $until > time()) {
2137 $USER->enrol['tempguest'][$course->id] = $until;
2138 return true;
2141 if (isset($USER->enrol['tempguest'][$course->id])) {
2142 unset($USER->enrol['tempguest'][$course->id]);
2143 remove_temp_course_roles($coursecontext);
2146 return false;
2150 * Returns array with sql code and parameters returning all ids
2151 * of users enrolled into course.
2153 * This function is using 'eu[0-9]+_' prefix for table names and parameters.
2155 * @package core_enrol
2156 * @category access
2158 * @param context $context
2159 * @param string $withcapability
2160 * @param int $groupid 0 means ignore groups, any other value limits the result by group id
2161 * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
2162 * @return array list($sql, $params)
2164 function get_enrolled_sql(context $context, $withcapability = '', $groupid = 0, $onlyactive = false) {
2165 global $DB, $CFG;
2167 // use unique prefix just in case somebody makes some SQL magic with the result
2168 static $i = 0;
2169 $i++;
2170 $prefix = 'eu'.$i.'_';
2172 // first find the course context
2173 $coursecontext = $context->get_course_context();
2175 $isfrontpage = ($coursecontext->instanceid == SITEID);
2177 $joins = array();
2178 $wheres = array();
2179 $params = array();
2181 list($contextids, $contextpaths) = get_context_info_list($context);
2183 // get all relevant capability info for all roles
2184 if ($withcapability) {
2185 list($incontexts, $cparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, 'ctx');
2186 $cparams['cap'] = $withcapability;
2188 $defs = array();
2189 $sql = "SELECT rc.id, rc.roleid, rc.permission, ctx.path
2190 FROM {role_capabilities} rc
2191 JOIN {context} ctx on rc.contextid = ctx.id
2192 WHERE rc.contextid $incontexts AND rc.capability = :cap";
2193 $rcs = $DB->get_records_sql($sql, $cparams);
2194 foreach ($rcs as $rc) {
2195 $defs[$rc->path][$rc->roleid] = $rc->permission;
2198 $access = array();
2199 if (!empty($defs)) {
2200 foreach ($contextpaths as $path) {
2201 if (empty($defs[$path])) {
2202 continue;
2204 foreach($defs[$path] as $roleid => $perm) {
2205 if ($perm == CAP_PROHIBIT) {
2206 $access[$roleid] = CAP_PROHIBIT;
2207 continue;
2209 if (!isset($access[$roleid])) {
2210 $access[$roleid] = (int)$perm;
2216 unset($defs);
2218 // make lists of roles that are needed and prohibited
2219 $needed = array(); // one of these is enough
2220 $prohibited = array(); // must not have any of these
2221 foreach ($access as $roleid => $perm) {
2222 if ($perm == CAP_PROHIBIT) {
2223 unset($needed[$roleid]);
2224 $prohibited[$roleid] = true;
2225 } else if ($perm == CAP_ALLOW and empty($prohibited[$roleid])) {
2226 $needed[$roleid] = true;
2230 $defaultuserroleid = isset($CFG->defaultuserroleid) ? $CFG->defaultuserroleid : 0;
2231 $defaultfrontpageroleid = isset($CFG->defaultfrontpageroleid) ? $CFG->defaultfrontpageroleid : 0;
2233 $nobody = false;
2235 if ($isfrontpage) {
2236 if (!empty($prohibited[$defaultuserroleid]) or !empty($prohibited[$defaultfrontpageroleid])) {
2237 $nobody = true;
2238 } else if (!empty($needed[$defaultuserroleid]) or !empty($needed[$defaultfrontpageroleid])) {
2239 // everybody not having prohibit has the capability
2240 $needed = array();
2241 } else if (empty($needed)) {
2242 $nobody = true;
2244 } else {
2245 if (!empty($prohibited[$defaultuserroleid])) {
2246 $nobody = true;
2247 } else if (!empty($needed[$defaultuserroleid])) {
2248 // everybody not having prohibit has the capability
2249 $needed = array();
2250 } else if (empty($needed)) {
2251 $nobody = true;
2255 if ($nobody) {
2256 // nobody can match so return some SQL that does not return any results
2257 $wheres[] = "1 = 2";
2259 } else {
2261 if ($needed) {
2262 $ctxids = implode(',', $contextids);
2263 $roleids = implode(',', array_keys($needed));
2264 $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))";
2267 if ($prohibited) {
2268 $ctxids = implode(',', $contextids);
2269 $roleids = implode(',', array_keys($prohibited));
2270 $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))";
2271 $wheres[] = "{$prefix}ra4.id IS NULL";
2274 if ($groupid) {
2275 $joins[] = "JOIN {groups_members} {$prefix}gm ON ({$prefix}gm.userid = {$prefix}u.id AND {$prefix}gm.groupid = :{$prefix}gmid)";
2276 $params["{$prefix}gmid"] = $groupid;
2280 } else {
2281 if ($groupid) {
2282 $joins[] = "JOIN {groups_members} {$prefix}gm ON ({$prefix}gm.userid = {$prefix}u.id AND {$prefix}gm.groupid = :{$prefix}gmid)";
2283 $params["{$prefix}gmid"] = $groupid;
2287 $wheres[] = "{$prefix}u.deleted = 0 AND {$prefix}u.id <> :{$prefix}guestid";
2288 $params["{$prefix}guestid"] = $CFG->siteguest;
2290 if ($isfrontpage) {
2291 // all users are "enrolled" on the frontpage
2292 } else {
2293 $joins[] = "JOIN {user_enrolments} {$prefix}ue ON {$prefix}ue.userid = {$prefix}u.id";
2294 $joins[] = "JOIN {enrol} {$prefix}e ON ({$prefix}e.id = {$prefix}ue.enrolid AND {$prefix}e.courseid = :{$prefix}courseid)";
2295 $params[$prefix.'courseid'] = $coursecontext->instanceid;
2297 if ($onlyactive) {
2298 $wheres[] = "{$prefix}ue.status = :{$prefix}active AND {$prefix}e.status = :{$prefix}enabled";
2299 $wheres[] = "{$prefix}ue.timestart < :{$prefix}now1 AND ({$prefix}ue.timeend = 0 OR {$prefix}ue.timeend > :{$prefix}now2)";
2300 $now = round(time(), -2); // rounding helps caching in DB
2301 $params = array_merge($params, array($prefix.'enabled'=>ENROL_INSTANCE_ENABLED,
2302 $prefix.'active'=>ENROL_USER_ACTIVE,
2303 $prefix.'now1'=>$now, $prefix.'now2'=>$now));
2307 $joins = implode("\n", $joins);
2308 $wheres = "WHERE ".implode(" AND ", $wheres);
2310 $sql = "SELECT DISTINCT {$prefix}u.id
2311 FROM {user} {$prefix}u
2312 $joins
2313 $wheres";
2315 return array($sql, $params);
2319 * Returns list of users enrolled into course.
2321 * @package core_enrol
2322 * @category access
2324 * @param context $context
2325 * @param string $withcapability
2326 * @param int $groupid 0 means ignore groups, any other value limits the result by group id
2327 * @param string $userfields requested user record fields
2328 * @param string $orderby
2329 * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set).
2330 * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
2331 * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
2332 * @return array of user records
2334 function get_enrolled_users(context $context, $withcapability = '', $groupid = 0, $userfields = 'u.*', $orderby = null,
2335 $limitfrom = 0, $limitnum = 0, $onlyactive = false) {
2336 global $DB;
2338 list($esql, $params) = get_enrolled_sql($context, $withcapability, $groupid, $onlyactive);
2339 $sql = "SELECT $userfields
2340 FROM {user} u
2341 JOIN ($esql) je ON je.id = u.id
2342 WHERE u.deleted = 0";
2344 if ($orderby) {
2345 $sql = "$sql ORDER BY $orderby";
2346 } else {
2347 list($sort, $sortparams) = users_order_by_sql('u');
2348 $sql = "$sql ORDER BY $sort";
2349 $params = array_merge($params, $sortparams);
2352 return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
2356 * Counts list of users enrolled into course (as per above function)
2358 * @package core_enrol
2359 * @category access
2361 * @param context $context
2362 * @param string $withcapability
2363 * @param int $groupid 0 means ignore groups, any other value limits the result by group id
2364 * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
2365 * @return array of user records
2367 function count_enrolled_users(context $context, $withcapability = '', $groupid = 0, $onlyactive = false) {
2368 global $DB;
2370 list($esql, $params) = get_enrolled_sql($context, $withcapability, $groupid, $onlyactive);
2371 $sql = "SELECT count(u.id)
2372 FROM {user} u
2373 JOIN ($esql) je ON je.id = u.id
2374 WHERE u.deleted = 0";
2376 return $DB->count_records_sql($sql, $params);
2380 * Loads the capability definitions for the component (from file).
2382 * Loads the capability definitions for the component (from file). If no
2383 * capabilities are defined for the component, we simply return an empty array.
2385 * @access private
2386 * @param string $component full plugin name, examples: 'moodle', 'mod_forum'
2387 * @return array array of capabilities
2389 function load_capability_def($component) {
2390 $defpath = get_component_directory($component).'/db/access.php';
2392 $capabilities = array();
2393 if (file_exists($defpath)) {
2394 require($defpath);
2395 if (!empty(${$component.'_capabilities'})) {
2396 // BC capability array name
2397 // since 2.0 we prefer $capabilities instead - it is easier to use and matches db/* files
2398 debugging('componentname_capabilities array is deprecated, please use $capabilities array only in access.php files');
2399 $capabilities = ${$component.'_capabilities'};
2403 return $capabilities;
2407 * Gets the capabilities that have been cached in the database for this component.
2409 * @access private
2410 * @param string $component - examples: 'moodle', 'mod_forum'
2411 * @return array array of capabilities
2413 function get_cached_capabilities($component = 'moodle') {
2414 global $DB;
2415 return $DB->get_records('capabilities', array('component'=>$component));
2419 * Returns default capabilities for given role archetype.
2421 * @param string $archetype role archetype
2422 * @return array
2424 function get_default_capabilities($archetype) {
2425 global $DB;
2427 if (!$archetype) {
2428 return array();
2431 $alldefs = array();
2432 $defaults = array();
2433 $components = array();
2434 $allcaps = $DB->get_records('capabilities');
2436 foreach ($allcaps as $cap) {
2437 if (!in_array($cap->component, $components)) {
2438 $components[] = $cap->component;
2439 $alldefs = array_merge($alldefs, load_capability_def($cap->component));
2442 foreach($alldefs as $name=>$def) {
2443 // Use array 'archetypes if available. Only if not specified, use 'legacy'.
2444 if (isset($def['archetypes'])) {
2445 if (isset($def['archetypes'][$archetype])) {
2446 $defaults[$name] = $def['archetypes'][$archetype];
2448 // 'legacy' is for backward compatibility with 1.9 access.php
2449 } else {
2450 if (isset($def['legacy'][$archetype])) {
2451 $defaults[$name] = $def['legacy'][$archetype];
2456 return $defaults;
2460 * Reset role capabilities to default according to selected role archetype.
2461 * If no archetype selected, removes all capabilities.
2463 * @param int $roleid
2464 * @return void
2466 function reset_role_capabilities($roleid) {
2467 global $DB;
2469 $role = $DB->get_record('role', array('id'=>$roleid), '*', MUST_EXIST);
2470 $defaultcaps = get_default_capabilities($role->archetype);
2472 $systemcontext = context_system::instance();
2474 $DB->delete_records('role_capabilities', array('roleid'=>$roleid));
2476 foreach($defaultcaps as $cap=>$permission) {
2477 assign_capability($cap, $permission, $roleid, $systemcontext->id);
2482 * Updates the capabilities table with the component capability definitions.
2483 * If no parameters are given, the function updates the core moodle
2484 * capabilities.
2486 * Note that the absence of the db/access.php capabilities definition file
2487 * will cause any stored capabilities for the component to be removed from
2488 * the database.
2490 * @access private
2491 * @param string $component examples: 'moodle', 'mod/forum', 'block/quiz_results'
2492 * @return boolean true if success, exception in case of any problems
2494 function update_capabilities($component = 'moodle') {
2495 global $DB, $OUTPUT;
2497 $storedcaps = array();
2499 $filecaps = load_capability_def($component);
2500 foreach($filecaps as $capname=>$unused) {
2501 if (!preg_match('|^[a-z]+/[a-z_0-9]+:[a-z_0-9]+$|', $capname)) {
2502 debugging("Coding problem: Invalid capability name '$capname', use 'clonepermissionsfrom' field for migration.");
2506 $cachedcaps = get_cached_capabilities($component);
2507 if ($cachedcaps) {
2508 foreach ($cachedcaps as $cachedcap) {
2509 array_push($storedcaps, $cachedcap->name);
2510 // update risk bitmasks and context levels in existing capabilities if needed
2511 if (array_key_exists($cachedcap->name, $filecaps)) {
2512 if (!array_key_exists('riskbitmask', $filecaps[$cachedcap->name])) {
2513 $filecaps[$cachedcap->name]['riskbitmask'] = 0; // no risk if not specified
2515 if ($cachedcap->captype != $filecaps[$cachedcap->name]['captype']) {
2516 $updatecap = new stdClass();
2517 $updatecap->id = $cachedcap->id;
2518 $updatecap->captype = $filecaps[$cachedcap->name]['captype'];
2519 $DB->update_record('capabilities', $updatecap);
2521 if ($cachedcap->riskbitmask != $filecaps[$cachedcap->name]['riskbitmask']) {
2522 $updatecap = new stdClass();
2523 $updatecap->id = $cachedcap->id;
2524 $updatecap->riskbitmask = $filecaps[$cachedcap->name]['riskbitmask'];
2525 $DB->update_record('capabilities', $updatecap);
2528 if (!array_key_exists('contextlevel', $filecaps[$cachedcap->name])) {
2529 $filecaps[$cachedcap->name]['contextlevel'] = 0; // no context level defined
2531 if ($cachedcap->contextlevel != $filecaps[$cachedcap->name]['contextlevel']) {
2532 $updatecap = new stdClass();
2533 $updatecap->id = $cachedcap->id;
2534 $updatecap->contextlevel = $filecaps[$cachedcap->name]['contextlevel'];
2535 $DB->update_record('capabilities', $updatecap);
2541 // Are there new capabilities in the file definition?
2542 $newcaps = array();
2544 foreach ($filecaps as $filecap => $def) {
2545 if (!$storedcaps ||
2546 ($storedcaps && in_array($filecap, $storedcaps) === false)) {
2547 if (!array_key_exists('riskbitmask', $def)) {
2548 $def['riskbitmask'] = 0; // no risk if not specified
2550 $newcaps[$filecap] = $def;
2553 // Add new capabilities to the stored definition.
2554 $existingcaps = $DB->get_records_menu('capabilities', array(), 'id', 'id, name');
2555 foreach ($newcaps as $capname => $capdef) {
2556 $capability = new stdClass();
2557 $capability->name = $capname;
2558 $capability->captype = $capdef['captype'];
2559 $capability->contextlevel = $capdef['contextlevel'];
2560 $capability->component = $component;
2561 $capability->riskbitmask = $capdef['riskbitmask'];
2563 $DB->insert_record('capabilities', $capability, false);
2565 if (isset($capdef['clonepermissionsfrom']) && in_array($capdef['clonepermissionsfrom'], $existingcaps)){
2566 if ($rolecapabilities = $DB->get_records('role_capabilities', array('capability'=>$capdef['clonepermissionsfrom']))){
2567 foreach ($rolecapabilities as $rolecapability){
2568 //assign_capability will update rather than insert if capability exists
2569 if (!assign_capability($capname, $rolecapability->permission,
2570 $rolecapability->roleid, $rolecapability->contextid, true)){
2571 echo $OUTPUT->notification('Could not clone capabilities for '.$capname);
2575 // we ignore archetype key if we have cloned permissions
2576 } else if (isset($capdef['archetypes']) && is_array($capdef['archetypes'])) {
2577 assign_legacy_capabilities($capname, $capdef['archetypes']);
2578 // 'legacy' is for backward compatibility with 1.9 access.php
2579 } else if (isset($capdef['legacy']) && is_array($capdef['legacy'])) {
2580 assign_legacy_capabilities($capname, $capdef['legacy']);
2583 // Are there any capabilities that have been removed from the file
2584 // definition that we need to delete from the stored capabilities and
2585 // role assignments?
2586 capabilities_cleanup($component, $filecaps);
2588 // reset static caches
2589 accesslib_clear_all_caches(false);
2591 return true;
2595 * Deletes cached capabilities that are no longer needed by the component.
2596 * Also unassigns these capabilities from any roles that have them.
2598 * @access private
2599 * @param string $component examples: 'moodle', 'mod_forum', 'block_quiz_results'
2600 * @param array $newcapdef array of the new capability definitions that will be
2601 * compared with the cached capabilities
2602 * @return int number of deprecated capabilities that have been removed
2604 function capabilities_cleanup($component, $newcapdef = null) {
2605 global $DB;
2607 $removedcount = 0;
2609 if ($cachedcaps = get_cached_capabilities($component)) {
2610 foreach ($cachedcaps as $cachedcap) {
2611 if (empty($newcapdef) ||
2612 array_key_exists($cachedcap->name, $newcapdef) === false) {
2614 // Remove from capabilities cache.
2615 $DB->delete_records('capabilities', array('name'=>$cachedcap->name));
2616 $removedcount++;
2617 // Delete from roles.
2618 if ($roles = get_roles_with_capability($cachedcap->name)) {
2619 foreach($roles as $role) {
2620 if (!unassign_capability($cachedcap->name, $role->id)) {
2621 print_error('cannotunassigncap', 'error', '', (object)array('cap'=>$cachedcap->name, 'role'=>$role->name));
2625 } // End if.
2628 return $removedcount;
2632 * Returns an array of all the known types of risk
2633 * The array keys can be used, for example as CSS class names, or in calls to
2634 * print_risk_icon. The values are the corresponding RISK_ constants.
2636 * @return array all the known types of risk.
2638 function get_all_risks() {
2639 return array(
2640 'riskmanagetrust' => RISK_MANAGETRUST,
2641 'riskconfig' => RISK_CONFIG,
2642 'riskxss' => RISK_XSS,
2643 'riskpersonal' => RISK_PERSONAL,
2644 'riskspam' => RISK_SPAM,
2645 'riskdataloss' => RISK_DATALOSS,
2650 * Return a link to moodle docs for a given capability name
2652 * @param stdClass $capability a capability - a row from the mdl_capabilities table.
2653 * @return string the human-readable capability name as a link to Moodle Docs.
2655 function get_capability_docs_link($capability) {
2656 $url = get_docs_url('Capabilities/' . $capability->name);
2657 return '<a onclick="this.target=\'docspopup\'" href="' . $url . '">' . get_capability_string($capability->name) . '</a>';
2661 * This function pulls out all the resolved capabilities (overrides and
2662 * defaults) of a role used in capability overrides in contexts at a given
2663 * context.
2665 * @param int $roleid
2666 * @param context $context
2667 * @param string $cap capability, optional, defaults to ''
2668 * @return array Array of capabilities
2670 function role_context_capabilities($roleid, context $context, $cap = '') {
2671 global $DB;
2673 $contexts = $context->get_parent_context_ids(true);
2674 $contexts = '('.implode(',', $contexts).')';
2676 $params = array($roleid);
2678 if ($cap) {
2679 $search = " AND rc.capability = ? ";
2680 $params[] = $cap;
2681 } else {
2682 $search = '';
2685 $sql = "SELECT rc.*
2686 FROM {role_capabilities} rc, {context} c
2687 WHERE rc.contextid in $contexts
2688 AND rc.roleid = ?
2689 AND rc.contextid = c.id $search
2690 ORDER BY c.contextlevel DESC, rc.capability DESC";
2692 $capabilities = array();
2694 if ($records = $DB->get_records_sql($sql, $params)) {
2695 // We are traversing via reverse order.
2696 foreach ($records as $record) {
2697 // If not set yet (i.e. inherit or not set at all), or currently we have a prohibit
2698 if (!isset($capabilities[$record->capability]) || $record->permission<-500) {
2699 $capabilities[$record->capability] = $record->permission;
2703 return $capabilities;
2707 * Constructs array with contextids as first parameter and context paths,
2708 * in both cases bottom top including self.
2710 * @access private
2711 * @param context $context
2712 * @return array
2714 function get_context_info_list(context $context) {
2715 $contextids = explode('/', ltrim($context->path, '/'));
2716 $contextpaths = array();
2717 $contextids2 = $contextids;
2718 while ($contextids2) {
2719 $contextpaths[] = '/' . implode('/', $contextids2);
2720 array_pop($contextids2);
2722 return array($contextids, $contextpaths);
2726 * Check if context is the front page context or a context inside it
2728 * Returns true if this context is the front page context, or a context inside it,
2729 * otherwise false.
2731 * @param context $context a context object.
2732 * @return bool
2734 function is_inside_frontpage(context $context) {
2735 $frontpagecontext = context_course::instance(SITEID);
2736 return strpos($context->path . '/', $frontpagecontext->path . '/') === 0;
2740 * Returns capability information (cached)
2742 * @param string $capabilityname
2743 * @return stdClass or null if capability not found
2745 function get_capability_info($capabilityname) {
2746 global $ACCESSLIB_PRIVATE, $DB; // one request per page only
2748 //TODO: MUC - this could be cached in shared memory, it would eliminate 1 query per page
2750 if (empty($ACCESSLIB_PRIVATE->capabilities)) {
2751 $ACCESSLIB_PRIVATE->capabilities = array();
2752 $caps = $DB->get_records('capabilities', array(), 'id, name, captype, riskbitmask');
2753 foreach ($caps as $cap) {
2754 $capname = $cap->name;
2755 unset($cap->id);
2756 unset($cap->name);
2757 $cap->riskbitmask = (int)$cap->riskbitmask;
2758 $ACCESSLIB_PRIVATE->capabilities[$capname] = $cap;
2762 return isset($ACCESSLIB_PRIVATE->capabilities[$capabilityname]) ? $ACCESSLIB_PRIVATE->capabilities[$capabilityname] : null;
2766 * Returns the human-readable, translated version of the capability.
2767 * Basically a big switch statement.
2769 * @param string $capabilityname e.g. mod/choice:readresponses
2770 * @return string
2772 function get_capability_string($capabilityname) {
2774 // Typical capability name is 'plugintype/pluginname:capabilityname'
2775 list($type, $name, $capname) = preg_split('|[/:]|', $capabilityname);
2777 if ($type === 'moodle') {
2778 $component = 'core_role';
2779 } else if ($type === 'quizreport') {
2780 //ugly hack!!
2781 $component = 'quiz_'.$name;
2782 } else {
2783 $component = $type.'_'.$name;
2786 $stringname = $name.':'.$capname;
2788 if ($component === 'core_role' or get_string_manager()->string_exists($stringname, $component)) {
2789 return get_string($stringname, $component);
2792 $dir = get_component_directory($component);
2793 if (!file_exists($dir)) {
2794 // plugin broken or does not exist, do not bother with printing of debug message
2795 return $capabilityname.' ???';
2798 // something is wrong in plugin, better print debug
2799 return get_string($stringname, $component);
2803 * This gets the mod/block/course/core etc strings.
2805 * @param string $component
2806 * @param int $contextlevel
2807 * @return string|bool String is success, false if failed
2809 function get_component_string($component, $contextlevel) {
2811 if ($component === 'moodle' or $component === 'core') {
2812 switch ($contextlevel) {
2813 // TODO: this should probably use context level names instead
2814 case CONTEXT_SYSTEM: return get_string('coresystem');
2815 case CONTEXT_USER: return get_string('users');
2816 case CONTEXT_COURSECAT: return get_string('categories');
2817 case CONTEXT_COURSE: return get_string('course');
2818 case CONTEXT_MODULE: return get_string('activities');
2819 case CONTEXT_BLOCK: return get_string('block');
2820 default: print_error('unknowncontext');
2824 list($type, $name) = normalize_component($component);
2825 $dir = get_plugin_directory($type, $name);
2826 if (!file_exists($dir)) {
2827 // plugin not installed, bad luck, there is no way to find the name
2828 return $component.' ???';
2831 switch ($type) {
2832 // TODO: this is really hacky, anyway it should be probably moved to lib/pluginlib.php
2833 case 'quiz': return get_string($name.':componentname', $component);// insane hack!!!
2834 case 'repository': return get_string('repository', 'repository').': '.get_string('pluginname', $component);
2835 case 'gradeimport': return get_string('gradeimport', 'grades').': '.get_string('pluginname', $component);
2836 case 'gradeexport': return get_string('gradeexport', 'grades').': '.get_string('pluginname', $component);
2837 case 'gradereport': return get_string('gradereport', 'grades').': '.get_string('pluginname', $component);
2838 case 'webservice': return get_string('webservice', 'webservice').': '.get_string('pluginname', $component);
2839 case 'block': return get_string('block').': '.get_string('pluginname', basename($component));
2840 case 'mod':
2841 if (get_string_manager()->string_exists('pluginname', $component)) {
2842 return get_string('activity').': '.get_string('pluginname', $component);
2843 } else {
2844 return get_string('activity').': '.get_string('modulename', $component);
2846 default: return get_string('pluginname', $component);
2851 * Gets the list of roles assigned to this context and up (parents)
2852 * from the list of roles that are visible on user profile page
2853 * and participants page.
2855 * @param context $context
2856 * @return array
2858 function get_profile_roles(context $context) {
2859 global $CFG, $DB;
2861 if (empty($CFG->profileroles)) {
2862 return array();
2865 list($rallowed, $params) = $DB->get_in_or_equal(explode(',', $CFG->profileroles), SQL_PARAMS_NAMED, 'a');
2866 list($contextlist, $cparams) = $DB->get_in_or_equal($context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'p');
2867 $params = array_merge($params, $cparams);
2869 if ($coursecontext = $context->get_course_context(false)) {
2870 $params['coursecontext'] = $coursecontext->id;
2871 } else {
2872 $params['coursecontext'] = 0;
2875 $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder, rn.name AS coursealias
2876 FROM {role_assignments} ra, {role} r
2877 LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
2878 WHERE r.id = ra.roleid
2879 AND ra.contextid $contextlist
2880 AND r.id $rallowed
2881 ORDER BY r.sortorder ASC";
2883 return $DB->get_records_sql($sql, $params);
2887 * Gets the list of roles assigned to this context and up (parents)
2889 * @param context $context
2890 * @return array
2892 function get_roles_used_in_context(context $context) {
2893 global $DB;
2895 list($contextlist, $params) = $DB->get_in_or_equal($context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'cl');
2897 if ($coursecontext = $context->get_course_context(false)) {
2898 $params['coursecontext'] = $coursecontext->id;
2899 } else {
2900 $params['coursecontext'] = 0;
2903 $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder, rn.name AS coursealias
2904 FROM {role_assignments} ra, {role} r
2905 LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
2906 WHERE r.id = ra.roleid
2907 AND ra.contextid $contextlist
2908 ORDER BY r.sortorder ASC";
2910 return $DB->get_records_sql($sql, $params);
2914 * This function is used to print roles column in user profile page.
2915 * It is using the CFG->profileroles to limit the list to only interesting roles.
2916 * (The permission tab has full details of user role assignments.)
2918 * @param int $userid
2919 * @param int $courseid
2920 * @return string
2922 function get_user_roles_in_course($userid, $courseid) {
2923 global $CFG, $DB;
2925 if (empty($CFG->profileroles)) {
2926 return '';
2929 if ($courseid == SITEID) {
2930 $context = context_system::instance();
2931 } else {
2932 $context = context_course::instance($courseid);
2935 if (empty($CFG->profileroles)) {
2936 return array();
2939 list($rallowed, $params) = $DB->get_in_or_equal(explode(',', $CFG->profileroles), SQL_PARAMS_NAMED, 'a');
2940 list($contextlist, $cparams) = $DB->get_in_or_equal($context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'p');
2941 $params = array_merge($params, $cparams);
2943 if ($coursecontext = $context->get_course_context(false)) {
2944 $params['coursecontext'] = $coursecontext->id;
2945 } else {
2946 $params['coursecontext'] = 0;
2949 $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder, rn.name AS coursealias
2950 FROM {role_assignments} ra, {role} r
2951 LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
2952 WHERE r.id = ra.roleid
2953 AND ra.contextid $contextlist
2954 AND r.id $rallowed
2955 AND ra.userid = :userid
2956 ORDER BY r.sortorder ASC";
2957 $params['userid'] = $userid;
2959 $rolestring = '';
2961 if ($roles = $DB->get_records_sql($sql, $params)) {
2962 $rolenames = role_fix_names($roles, $context, ROLENAME_ALIAS, true); // Substitute aliases
2964 foreach ($rolenames as $roleid => $rolename) {
2965 $rolenames[$roleid] = '<a href="'.$CFG->wwwroot.'/user/index.php?contextid='.$context->id.'&amp;roleid='.$roleid.'">'.$rolename.'</a>';
2967 $rolestring = implode(',', $rolenames);
2970 return $rolestring;
2974 * Checks if a user can assign users to a particular role in this context
2976 * @param context $context
2977 * @param int $targetroleid - the id of the role you want to assign users to
2978 * @return boolean
2980 function user_can_assign(context $context, $targetroleid) {
2981 global $DB;
2983 // First check to see if the user is a site administrator.
2984 if (is_siteadmin()) {
2985 return true;
2988 // Check if user has override capability.
2989 // If not return false.
2990 if (!has_capability('moodle/role:assign', $context)) {
2991 return false;
2993 // pull out all active roles of this user from this context(or above)
2994 if ($userroles = get_user_roles($context)) {
2995 foreach ($userroles as $userrole) {
2996 // if any in the role_allow_override table, then it's ok
2997 if ($DB->get_record('role_allow_assign', array('roleid'=>$userrole->roleid, 'allowassign'=>$targetroleid))) {
2998 return true;
3003 return false;
3007 * Returns all site roles in correct sort order.
3009 * Note: this method does not localise role names or descriptions,
3010 * use role_get_names() if you need role names.
3012 * @param context $context optional context for course role name aliases
3013 * @return array of role records with optional coursealias property
3015 function get_all_roles(context $context = null) {
3016 global $DB;
3018 if (!$context or !$coursecontext = $context->get_course_context(false)) {
3019 $coursecontext = null;
3022 if ($coursecontext) {
3023 $sql = "SELECT r.*, rn.name AS coursealias
3024 FROM {role} r
3025 LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
3026 ORDER BY r.sortorder ASC";
3027 return $DB->get_records_sql($sql, array('coursecontext'=>$coursecontext->id));
3029 } else {
3030 return $DB->get_records('role', array(), 'sortorder ASC');
3035 * Returns roles of a specified archetype
3037 * @param string $archetype
3038 * @return array of full role records
3040 function get_archetype_roles($archetype) {
3041 global $DB;
3042 return $DB->get_records('role', array('archetype'=>$archetype), 'sortorder ASC');
3046 * Gets all the user roles assigned in this context, or higher contexts
3047 * this is mainly used when checking if a user can assign a role, or overriding a role
3048 * i.e. we need to know what this user holds, in order to verify against allow_assign and
3049 * allow_override tables
3051 * @param context $context
3052 * @param int $userid
3053 * @param bool $checkparentcontexts defaults to true
3054 * @param string $order defaults to 'c.contextlevel DESC, r.sortorder ASC'
3055 * @return array
3057 function get_user_roles(context $context, $userid = 0, $checkparentcontexts = true, $order = 'c.contextlevel DESC, r.sortorder ASC') {
3058 global $USER, $DB;
3060 if (empty($userid)) {
3061 if (empty($USER->id)) {
3062 return array();
3064 $userid = $USER->id;
3067 if ($checkparentcontexts) {
3068 $contextids = $context->get_parent_context_ids();
3069 } else {
3070 $contextids = array();
3072 $contextids[] = $context->id;
3074 list($contextids, $params) = $DB->get_in_or_equal($contextids, SQL_PARAMS_QM);
3076 array_unshift($params, $userid);
3078 $sql = "SELECT ra.*, r.name, r.shortname
3079 FROM {role_assignments} ra, {role} r, {context} c
3080 WHERE ra.userid = ?
3081 AND ra.roleid = r.id
3082 AND ra.contextid = c.id
3083 AND ra.contextid $contextids
3084 ORDER BY $order";
3086 return $DB->get_records_sql($sql ,$params);
3090 * Like get_user_roles, but adds in the authenticated user role, and the front
3091 * page roles, if applicable.
3093 * @param context $context the context.
3094 * @param int $userid optional. Defaults to $USER->id
3095 * @return array of objects with fields ->userid, ->contextid and ->roleid.
3097 function get_user_roles_with_special(context $context, $userid = 0) {
3098 global $CFG, $USER;
3100 if (empty($userid)) {
3101 if (empty($USER->id)) {
3102 return array();
3104 $userid = $USER->id;
3107 $ras = get_user_roles($context, $userid);
3109 // Add front-page role if relevant.
3110 $defaultfrontpageroleid = isset($CFG->defaultfrontpageroleid) ? $CFG->defaultfrontpageroleid : 0;
3111 $isfrontpage = ($context->contextlevel == CONTEXT_COURSE && $context->instanceid == SITEID) ||
3112 is_inside_frontpage($context);
3113 if ($defaultfrontpageroleid && $isfrontpage) {
3114 $frontpagecontext = context_course::instance(SITEID);
3115 $ra = new stdClass();
3116 $ra->userid = $userid;
3117 $ra->contextid = $frontpagecontext->id;
3118 $ra->roleid = $defaultfrontpageroleid;
3119 $ras[] = $ra;
3122 // Add authenticated user role if relevant.
3123 $defaultuserroleid = isset($CFG->defaultuserroleid) ? $CFG->defaultuserroleid : 0;
3124 if ($defaultuserroleid && !isguestuser($userid)) {
3125 $systemcontext = context_system::instance();
3126 $ra = new stdClass();
3127 $ra->userid = $userid;
3128 $ra->contextid = $systemcontext->id;
3129 $ra->roleid = $defaultuserroleid;
3130 $ras[] = $ra;
3133 return $ras;
3137 * Creates a record in the role_allow_override table
3139 * @param int $sroleid source roleid
3140 * @param int $troleid target roleid
3141 * @return void
3143 function allow_override($sroleid, $troleid) {
3144 global $DB;
3146 $record = new stdClass();
3147 $record->roleid = $sroleid;
3148 $record->allowoverride = $troleid;
3149 $DB->insert_record('role_allow_override', $record);
3153 * Creates a record in the role_allow_assign table
3155 * @param int $fromroleid source roleid
3156 * @param int $targetroleid target roleid
3157 * @return void
3159 function allow_assign($fromroleid, $targetroleid) {
3160 global $DB;
3162 $record = new stdClass();
3163 $record->roleid = $fromroleid;
3164 $record->allowassign = $targetroleid;
3165 $DB->insert_record('role_allow_assign', $record);
3169 * Creates a record in the role_allow_switch table
3171 * @param int $fromroleid source roleid
3172 * @param int $targetroleid target roleid
3173 * @return void
3175 function allow_switch($fromroleid, $targetroleid) {
3176 global $DB;
3178 $record = new stdClass();
3179 $record->roleid = $fromroleid;
3180 $record->allowswitch = $targetroleid;
3181 $DB->insert_record('role_allow_switch', $record);
3185 * Gets a list of roles that this user can assign in this context
3187 * @param context $context the context.
3188 * @param int $rolenamedisplay the type of role name to display. One of the
3189 * ROLENAME_X constants. Default ROLENAME_ALIAS.
3190 * @param bool $withusercounts if true, count the number of users with each role.
3191 * @param integer|object $user A user id or object. By default (null) checks the permissions of the current user.
3192 * @return array if $withusercounts is false, then an array $roleid => $rolename.
3193 * if $withusercounts is true, returns a list of three arrays,
3194 * $rolenames, $rolecounts, and $nameswithcounts.
3196 function get_assignable_roles(context $context, $rolenamedisplay = ROLENAME_ALIAS, $withusercounts = false, $user = null) {
3197 global $USER, $DB;
3199 // make sure there is a real user specified
3200 if ($user === null) {
3201 $userid = isset($USER->id) ? $USER->id : 0;
3202 } else {
3203 $userid = is_object($user) ? $user->id : $user;
3206 if (!has_capability('moodle/role:assign', $context, $userid)) {
3207 if ($withusercounts) {
3208 return array(array(), array(), array());
3209 } else {
3210 return array();
3214 $params = array();
3215 $extrafields = '';
3217 if ($withusercounts) {
3218 $extrafields = ', (SELECT count(u.id)
3219 FROM {role_assignments} cra JOIN {user} u ON cra.userid = u.id
3220 WHERE cra.roleid = r.id AND cra.contextid = :conid AND u.deleted = 0
3221 ) AS usercount';
3222 $params['conid'] = $context->id;
3225 if (is_siteadmin($userid)) {
3226 // show all roles allowed in this context to admins
3227 $assignrestriction = "";
3228 } else {
3229 $parents = $context->get_parent_context_ids(true);
3230 $contexts = implode(',' , $parents);
3231 $assignrestriction = "JOIN (SELECT DISTINCT raa.allowassign AS id
3232 FROM {role_allow_assign} raa
3233 JOIN {role_assignments} ra ON ra.roleid = raa.roleid
3234 WHERE ra.userid = :userid AND ra.contextid IN ($contexts)
3235 ) ar ON ar.id = r.id";
3236 $params['userid'] = $userid;
3238 $params['contextlevel'] = $context->contextlevel;
3240 if ($coursecontext = $context->get_course_context(false)) {
3241 $params['coursecontext'] = $coursecontext->id;
3242 } else {
3243 $params['coursecontext'] = 0; // no course aliases
3244 $coursecontext = null;
3246 $sql = "SELECT r.id, r.name, r.shortname, rn.name AS coursealias $extrafields
3247 FROM {role} r
3248 $assignrestriction
3249 JOIN {role_context_levels} rcl ON (rcl.contextlevel = :contextlevel AND r.id = rcl.roleid)
3250 LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
3251 ORDER BY r.sortorder ASC";
3252 $roles = $DB->get_records_sql($sql, $params);
3254 $rolenames = role_fix_names($roles, $coursecontext, $rolenamedisplay, true);
3256 if (!$withusercounts) {
3257 return $rolenames;
3260 $rolecounts = array();
3261 $nameswithcounts = array();
3262 foreach ($roles as $role) {
3263 $nameswithcounts[$role->id] = $rolenames[$role->id] . ' (' . $roles[$role->id]->usercount . ')';
3264 $rolecounts[$role->id] = $roles[$role->id]->usercount;
3266 return array($rolenames, $rolecounts, $nameswithcounts);
3270 * Gets a list of roles that this user can switch to in a context
3272 * Gets a list of roles that this user can switch to in a context, for the switchrole menu.
3273 * This function just process the contents of the role_allow_switch table. You also need to
3274 * test the moodle/role:switchroles to see if the user is allowed to switch in the first place.
3276 * @param context $context a context.
3277 * @return array an array $roleid => $rolename.
3279 function get_switchable_roles(context $context) {
3280 global $USER, $DB;
3282 $params = array();
3283 $extrajoins = '';
3284 $extrawhere = '';
3285 if (!is_siteadmin()) {
3286 // Admins are allowed to switch to any role with.
3287 // Others are subject to the additional constraint that the switch-to role must be allowed by
3288 // 'role_allow_switch' for some role they have assigned in this context or any parent.
3289 $parents = $context->get_parent_context_ids(true);
3290 $contexts = implode(',' , $parents);
3292 $extrajoins = "JOIN {role_allow_switch} ras ON ras.allowswitch = rc.roleid
3293 JOIN {role_assignments} ra ON ra.roleid = ras.roleid";
3294 $extrawhere = "WHERE ra.userid = :userid AND ra.contextid IN ($contexts)";
3295 $params['userid'] = $USER->id;
3298 if ($coursecontext = $context->get_course_context(false)) {
3299 $params['coursecontext'] = $coursecontext->id;
3300 } else {
3301 $params['coursecontext'] = 0; // no course aliases
3302 $coursecontext = null;
3305 $query = "
3306 SELECT r.id, r.name, r.shortname, rn.name AS coursealias
3307 FROM (SELECT DISTINCT rc.roleid
3308 FROM {role_capabilities} rc
3309 $extrajoins
3310 $extrawhere) idlist
3311 JOIN {role} r ON r.id = idlist.roleid
3312 LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
3313 ORDER BY r.sortorder";
3314 $roles = $DB->get_records_sql($query, $params);
3316 return role_fix_names($roles, $context, ROLENAME_ALIAS, true);
3320 * Gets a list of roles that this user can override in this context.
3322 * @param context $context the context.
3323 * @param int $rolenamedisplay the type of role name to display. One of the
3324 * ROLENAME_X constants. Default ROLENAME_ALIAS.
3325 * @param bool $withcounts if true, count the number of overrides that are set for each role.
3326 * @return array if $withcounts is false, then an array $roleid => $rolename.
3327 * if $withusercounts is true, returns a list of three arrays,
3328 * $rolenames, $rolecounts, and $nameswithcounts.
3330 function get_overridable_roles(context $context, $rolenamedisplay = ROLENAME_ALIAS, $withcounts = false) {
3331 global $USER, $DB;
3333 if (!has_any_capability(array('moodle/role:safeoverride', 'moodle/role:override'), $context)) {
3334 if ($withcounts) {
3335 return array(array(), array(), array());
3336 } else {
3337 return array();
3341 $parents = $context->get_parent_context_ids(true);
3342 $contexts = implode(',' , $parents);
3344 $params = array();
3345 $extrafields = '';
3347 $params['userid'] = $USER->id;
3348 if ($withcounts) {
3349 $extrafields = ', (SELECT COUNT(rc.id) FROM {role_capabilities} rc
3350 WHERE rc.roleid = ro.id AND rc.contextid = :conid) AS overridecount';
3351 $params['conid'] = $context->id;
3354 if ($coursecontext = $context->get_course_context(false)) {
3355 $params['coursecontext'] = $coursecontext->id;
3356 } else {
3357 $params['coursecontext'] = 0; // no course aliases
3358 $coursecontext = null;
3361 if (is_siteadmin()) {
3362 // show all roles to admins
3363 $roles = $DB->get_records_sql("
3364 SELECT ro.id, ro.name, ro.shortname, rn.name AS coursealias $extrafields
3365 FROM {role} ro
3366 LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = ro.id)
3367 ORDER BY ro.sortorder ASC", $params);
3369 } else {
3370 $roles = $DB->get_records_sql("
3371 SELECT ro.id, ro.name, ro.shortname, rn.name AS coursealias $extrafields
3372 FROM {role} ro
3373 JOIN (SELECT DISTINCT r.id
3374 FROM {role} r
3375 JOIN {role_allow_override} rao ON r.id = rao.allowoverride
3376 JOIN {role_assignments} ra ON rao.roleid = ra.roleid
3377 WHERE ra.userid = :userid AND ra.contextid IN ($contexts)
3378 ) inline_view ON ro.id = inline_view.id
3379 LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = ro.id)
3380 ORDER BY ro.sortorder ASC", $params);
3383 $rolenames = role_fix_names($roles, $context, $rolenamedisplay, true);
3385 if (!$withcounts) {
3386 return $rolenames;
3389 $rolecounts = array();
3390 $nameswithcounts = array();
3391 foreach ($roles as $role) {
3392 $nameswithcounts[$role->id] = $rolenames[$role->id] . ' (' . $roles[$role->id]->overridecount . ')';
3393 $rolecounts[$role->id] = $roles[$role->id]->overridecount;
3395 return array($rolenames, $rolecounts, $nameswithcounts);
3399 * Create a role menu suitable for default role selection in enrol plugins.
3401 * @package core_enrol
3403 * @param context $context
3404 * @param int $addroleid current or default role - always added to list
3405 * @return array roleid=>localised role name
3407 function get_default_enrol_roles(context $context, $addroleid = null) {
3408 global $DB;
3410 $params = array('contextlevel'=>CONTEXT_COURSE);
3412 if ($coursecontext = $context->get_course_context(false)) {
3413 $params['coursecontext'] = $coursecontext->id;
3414 } else {
3415 $params['coursecontext'] = 0; // no course names
3416 $coursecontext = null;
3419 if ($addroleid) {
3420 $addrole = "OR r.id = :addroleid";
3421 $params['addroleid'] = $addroleid;
3422 } else {
3423 $addrole = "";
3426 $sql = "SELECT r.id, r.name, r.shortname, rn.name AS coursealias
3427 FROM {role} r
3428 LEFT JOIN {role_context_levels} rcl ON (rcl.roleid = r.id AND rcl.contextlevel = :contextlevel)
3429 LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
3430 WHERE rcl.id IS NOT NULL $addrole
3431 ORDER BY sortorder DESC";
3433 $roles = $DB->get_records_sql($sql, $params);
3435 return role_fix_names($roles, $context, ROLENAME_BOTH, true);
3439 * Return context levels where this role is assignable.
3441 * @param integer $roleid the id of a role.
3442 * @return array list of the context levels at which this role may be assigned.
3444 function get_role_contextlevels($roleid) {
3445 global $DB;
3446 return $DB->get_records_menu('role_context_levels', array('roleid' => $roleid),
3447 'contextlevel', 'id,contextlevel');
3451 * Return roles suitable for assignment at the specified context level.
3453 * NOTE: this function name looks like a typo, should be probably get_roles_for_contextlevel()
3455 * @param integer $contextlevel a contextlevel.
3456 * @return array list of role ids that are assignable at this context level.
3458 function get_roles_for_contextlevels($contextlevel) {
3459 global $DB;
3460 return $DB->get_records_menu('role_context_levels', array('contextlevel' => $contextlevel),
3461 '', 'id,roleid');
3465 * Returns default context levels where roles can be assigned.
3467 * @param string $rolearchetype one of the role archetypes - that is, one of the keys
3468 * from the array returned by get_role_archetypes();
3469 * @return array list of the context levels at which this type of role may be assigned by default.
3471 function get_default_contextlevels($rolearchetype) {
3472 static $defaults = array(
3473 'manager' => array(CONTEXT_SYSTEM, CONTEXT_COURSECAT, CONTEXT_COURSE),
3474 'coursecreator' => array(CONTEXT_SYSTEM, CONTEXT_COURSECAT),
3475 'editingteacher' => array(CONTEXT_COURSE, CONTEXT_MODULE),
3476 'teacher' => array(CONTEXT_COURSE, CONTEXT_MODULE),
3477 'student' => array(CONTEXT_COURSE, CONTEXT_MODULE),
3478 'guest' => array(),
3479 'user' => array(),
3480 'frontpage' => array());
3482 if (isset($defaults[$rolearchetype])) {
3483 return $defaults[$rolearchetype];
3484 } else {
3485 return array();
3490 * Set the context levels at which a particular role can be assigned.
3491 * Throws exceptions in case of error.
3493 * @param integer $roleid the id of a role.
3494 * @param array $contextlevels the context levels at which this role should be assignable,
3495 * duplicate levels are removed.
3496 * @return void
3498 function set_role_contextlevels($roleid, array $contextlevels) {
3499 global $DB;
3500 $DB->delete_records('role_context_levels', array('roleid' => $roleid));
3501 $rcl = new stdClass();
3502 $rcl->roleid = $roleid;
3503 $contextlevels = array_unique($contextlevels);
3504 foreach ($contextlevels as $level) {
3505 $rcl->contextlevel = $level;
3506 $DB->insert_record('role_context_levels', $rcl, false, true);
3511 * Who has this capability in this context?
3513 * This can be a very expensive call - use sparingly and keep
3514 * the results if you are going to need them again soon.
3516 * Note if $fields is empty this function attempts to get u.*
3517 * which can get rather large - and has a serious perf impact
3518 * on some DBs.
3520 * @param context $context
3521 * @param string|array $capability - capability name(s)
3522 * @param string $fields - fields to be pulled. The user table is aliased to 'u'. u.id MUST be included.
3523 * @param string $sort - the sort order. Default is lastaccess time.
3524 * @param mixed $limitfrom - number of records to skip (offset)
3525 * @param mixed $limitnum - number of records to fetch
3526 * @param string|array $groups - single group or array of groups - only return
3527 * users who are in one of these group(s).
3528 * @param string|array $exceptions - list of users to exclude, comma separated or array
3529 * @param bool $doanything_ignored not used any more, admin accounts are never returned
3530 * @param bool $view_ignored - use get_enrolled_sql() instead
3531 * @param bool $useviewallgroups if $groups is set the return users who
3532 * have capability both $capability and moodle/site:accessallgroups
3533 * in this context, as well as users who have $capability and who are
3534 * in $groups.
3535 * @return array of user records
3537 function get_users_by_capability(context $context, $capability, $fields = '', $sort = '', $limitfrom = '', $limitnum = '',
3538 $groups = '', $exceptions = '', $doanything_ignored = null, $view_ignored = null, $useviewallgroups = false) {
3539 global $CFG, $DB;
3541 $defaultuserroleid = isset($CFG->defaultuserroleid) ? $CFG->defaultuserroleid : 0;
3542 $defaultfrontpageroleid = isset($CFG->defaultfrontpageroleid) ? $CFG->defaultfrontpageroleid : 0;
3544 $ctxids = trim($context->path, '/');
3545 $ctxids = str_replace('/', ',', $ctxids);
3547 // Context is the frontpage
3548 $iscoursepage = false; // coursepage other than fp
3549 $isfrontpage = false;
3550 if ($context->contextlevel == CONTEXT_COURSE) {
3551 if ($context->instanceid == SITEID) {
3552 $isfrontpage = true;
3553 } else {
3554 $iscoursepage = true;
3557 $isfrontpage = ($isfrontpage || is_inside_frontpage($context));
3559 $caps = (array)$capability;
3561 // construct list of context paths bottom-->top
3562 list($contextids, $paths) = get_context_info_list($context);
3564 // we need to find out all roles that have these capabilities either in definition or in overrides
3565 $defs = array();
3566 list($incontexts, $params) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, 'con');
3567 list($incaps, $params2) = $DB->get_in_or_equal($caps, SQL_PARAMS_NAMED, 'cap');
3568 $params = array_merge($params, $params2);
3569 $sql = "SELECT rc.id, rc.roleid, rc.permission, rc.capability, ctx.path
3570 FROM {role_capabilities} rc
3571 JOIN {context} ctx on rc.contextid = ctx.id
3572 WHERE rc.contextid $incontexts AND rc.capability $incaps";
3574 $rcs = $DB->get_records_sql($sql, $params);
3575 foreach ($rcs as $rc) {
3576 $defs[$rc->capability][$rc->path][$rc->roleid] = $rc->permission;
3579 // go through the permissions bottom-->top direction to evaluate the current permission,
3580 // first one wins (prohibit is an exception that always wins)
3581 $access = array();
3582 foreach ($caps as $cap) {
3583 foreach ($paths as $path) {
3584 if (empty($defs[$cap][$path])) {
3585 continue;
3587 foreach($defs[$cap][$path] as $roleid => $perm) {
3588 if ($perm == CAP_PROHIBIT) {
3589 $access[$cap][$roleid] = CAP_PROHIBIT;
3590 continue;
3592 if (!isset($access[$cap][$roleid])) {
3593 $access[$cap][$roleid] = (int)$perm;
3599 // make lists of roles that are needed and prohibited in this context
3600 $needed = array(); // one of these is enough
3601 $prohibited = array(); // must not have any of these
3602 foreach ($caps as $cap) {
3603 if (empty($access[$cap])) {
3604 continue;
3606 foreach ($access[$cap] as $roleid => $perm) {
3607 if ($perm == CAP_PROHIBIT) {
3608 unset($needed[$cap][$roleid]);
3609 $prohibited[$cap][$roleid] = true;
3610 } else if ($perm == CAP_ALLOW and empty($prohibited[$cap][$roleid])) {
3611 $needed[$cap][$roleid] = true;
3614 if (empty($needed[$cap]) or !empty($prohibited[$cap][$defaultuserroleid])) {
3615 // easy, nobody has the permission
3616 unset($needed[$cap]);
3617 unset($prohibited[$cap]);
3618 } else if ($isfrontpage and !empty($prohibited[$cap][$defaultfrontpageroleid])) {
3619 // everybody is disqualified on the frontpage
3620 unset($needed[$cap]);
3621 unset($prohibited[$cap]);
3623 if (empty($prohibited[$cap])) {
3624 unset($prohibited[$cap]);
3628 if (empty($needed)) {
3629 // there can not be anybody if no roles match this request
3630 return array();
3633 if (empty($prohibited)) {
3634 // we can compact the needed roles
3635 $n = array();
3636 foreach ($needed as $cap) {
3637 foreach ($cap as $roleid=>$unused) {
3638 $n[$roleid] = true;
3641 $needed = array('any'=>$n);
3642 unset($n);
3645 // ***** Set up default fields ******
3646 if (empty($fields)) {
3647 if ($iscoursepage) {
3648 $fields = 'u.*, ul.timeaccess AS lastaccess';
3649 } else {
3650 $fields = 'u.*';
3652 } else {
3653 if (debugging('', DEBUG_DEVELOPER) && strpos($fields, 'u.*') === false && strpos($fields, 'u.id') === false) {
3654 debugging('u.id must be included in the list of fields passed to get_users_by_capability().', DEBUG_DEVELOPER);
3658 // Set up default sort
3659 if (empty($sort)) { // default to course lastaccess or just lastaccess
3660 if ($iscoursepage) {
3661 $sort = 'ul.timeaccess';
3662 } else {
3663 $sort = 'u.lastaccess';
3667 // Prepare query clauses
3668 $wherecond = array();
3669 $params = array();
3670 $joins = array();
3672 // User lastaccess JOIN
3673 if ((strpos($sort, 'ul.timeaccess') === false) and (strpos($fields, 'ul.timeaccess') === false)) {
3674 // user_lastaccess is not required MDL-13810
3675 } else {
3676 if ($iscoursepage) {
3677 $joins[] = "LEFT OUTER JOIN {user_lastaccess} ul ON (ul.userid = u.id AND ul.courseid = {$context->instanceid})";
3678 } else {
3679 throw new coding_exception('Invalid sort in get_users_by_capability(), ul.timeaccess allowed only for course contexts.');
3683 // We never return deleted users or guest account.
3684 $wherecond[] = "u.deleted = 0 AND u.id <> :guestid";
3685 $params['guestid'] = $CFG->siteguest;
3687 // Groups
3688 if ($groups) {
3689 $groups = (array)$groups;
3690 list($grouptest, $grpparams) = $DB->get_in_or_equal($groups, SQL_PARAMS_NAMED, 'grp');
3691 $grouptest = "u.id IN (SELECT userid FROM {groups_members} gm WHERE gm.groupid $grouptest)";
3692 $params = array_merge($params, $grpparams);
3694 if ($useviewallgroups) {
3695 $viewallgroupsusers = get_users_by_capability($context, 'moodle/site:accessallgroups', 'u.id, u.id', '', '', '', '', $exceptions);
3696 if (!empty($viewallgroupsusers)) {
3697 $wherecond[] = "($grouptest OR u.id IN (" . implode(',', array_keys($viewallgroupsusers)) . '))';
3698 } else {
3699 $wherecond[] = "($grouptest)";
3701 } else {
3702 $wherecond[] = "($grouptest)";
3706 // User exceptions
3707 if (!empty($exceptions)) {
3708 $exceptions = (array)$exceptions;
3709 list($exsql, $exparams) = $DB->get_in_or_equal($exceptions, SQL_PARAMS_NAMED, 'exc', false);
3710 $params = array_merge($params, $exparams);
3711 $wherecond[] = "u.id $exsql";
3714 // now add the needed and prohibited roles conditions as joins
3715 if (!empty($needed['any'])) {
3716 // simple case - there are no prohibits involved
3717 if (!empty($needed['any'][$defaultuserroleid]) or ($isfrontpage and !empty($needed['any'][$defaultfrontpageroleid]))) {
3718 // everybody
3719 } else {
3720 $joins[] = "JOIN (SELECT DISTINCT userid
3721 FROM {role_assignments}
3722 WHERE contextid IN ($ctxids)
3723 AND roleid IN (".implode(',', array_keys($needed['any'])) .")
3724 ) ra ON ra.userid = u.id";
3726 } else {
3727 $unions = array();
3728 $everybody = false;
3729 foreach ($needed as $cap=>$unused) {
3730 if (empty($prohibited[$cap])) {
3731 if (!empty($needed[$cap][$defaultuserroleid]) or ($isfrontpage and !empty($needed[$cap][$defaultfrontpageroleid]))) {
3732 $everybody = true;
3733 break;
3734 } else {
3735 $unions[] = "SELECT userid
3736 FROM {role_assignments}
3737 WHERE contextid IN ($ctxids)
3738 AND roleid IN (".implode(',', array_keys($needed[$cap])) .")";
3740 } else {
3741 if (!empty($prohibited[$cap][$defaultuserroleid]) or ($isfrontpage and !empty($prohibited[$cap][$defaultfrontpageroleid]))) {
3742 // nobody can have this cap because it is prevented in default roles
3743 continue;
3745 } else if (!empty($needed[$cap][$defaultuserroleid]) or ($isfrontpage and !empty($needed[$cap][$defaultfrontpageroleid]))) {
3746 // everybody except the prohibitted - hiding does not matter
3747 $unions[] = "SELECT id AS userid
3748 FROM {user}
3749 WHERE id NOT IN (SELECT userid
3750 FROM {role_assignments}
3751 WHERE contextid IN ($ctxids)
3752 AND roleid IN (".implode(',', array_keys($prohibited[$cap])) ."))";
3754 } else {
3755 $unions[] = "SELECT userid
3756 FROM {role_assignments}
3757 WHERE contextid IN ($ctxids)
3758 AND roleid IN (".implode(',', array_keys($needed[$cap])) .")
3759 AND roleid NOT IN (".implode(',', array_keys($prohibited[$cap])) .")";
3763 if (!$everybody) {
3764 if ($unions) {
3765 $joins[] = "JOIN (SELECT DISTINCT userid FROM ( ".implode(' UNION ', $unions)." ) us) ra ON ra.userid = u.id";
3766 } else {
3767 // only prohibits found - nobody can be matched
3768 $wherecond[] = "1 = 2";
3773 // Collect WHERE conditions and needed joins
3774 $where = implode(' AND ', $wherecond);
3775 if ($where !== '') {
3776 $where = 'WHERE ' . $where;
3778 $joins = implode("\n", $joins);
3780 // Ok, let's get the users!
3781 $sql = "SELECT $fields
3782 FROM {user} u
3783 $joins
3784 $where
3785 ORDER BY $sort";
3787 return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
3791 * Re-sort a users array based on a sorting policy
3793 * Will re-sort a $users results array (from get_users_by_capability(), usually)
3794 * based on a sorting policy. This is to support the odd practice of
3795 * sorting teachers by 'authority', where authority was "lowest id of the role
3796 * assignment".
3798 * Will execute 1 database query. Only suitable for small numbers of users, as it
3799 * uses an u.id IN() clause.
3801 * Notes about the sorting criteria.
3803 * As a default, we cannot rely on role.sortorder because then
3804 * admins/coursecreators will always win. That is why the sane
3805 * rule "is locality matters most", with sortorder as 2nd
3806 * consideration.
3808 * If you want role.sortorder, use the 'sortorder' policy, and
3809 * name explicitly what roles you want to cover. It's probably
3810 * a good idea to see what roles have the capabilities you want
3811 * (array_diff() them against roiles that have 'can-do-anything'
3812 * to weed out admin-ish roles. Or fetch a list of roles from
3813 * variables like $CFG->coursecontact .
3815 * @param array $users Users array, keyed on userid
3816 * @param context $context
3817 * @param array $roles ids of the roles to include, optional
3818 * @param string $sortpolicy defaults to locality, more about
3819 * @return array sorted copy of the array
3821 function sort_by_roleassignment_authority($users, context $context, $roles = array(), $sortpolicy = 'locality') {
3822 global $DB;
3824 $userswhere = ' ra.userid IN (' . implode(',',array_keys($users)) . ')';
3825 $contextwhere = 'AND ra.contextid IN ('.str_replace('/', ',',substr($context->path, 1)).')';
3826 if (empty($roles)) {
3827 $roleswhere = '';
3828 } else {
3829 $roleswhere = ' AND ra.roleid IN ('.implode(',',$roles).')';
3832 $sql = "SELECT ra.userid
3833 FROM {role_assignments} ra
3834 JOIN {role} r
3835 ON ra.roleid=r.id
3836 JOIN {context} ctx
3837 ON ra.contextid=ctx.id
3838 WHERE $userswhere
3839 $contextwhere
3840 $roleswhere";
3842 // Default 'locality' policy -- read PHPDoc notes
3843 // about sort policies...
3844 $orderby = 'ORDER BY '
3845 .'ctx.depth DESC, ' /* locality wins */
3846 .'r.sortorder ASC, ' /* rolesorting 2nd criteria */
3847 .'ra.id'; /* role assignment order tie-breaker */
3848 if ($sortpolicy === 'sortorder') {
3849 $orderby = 'ORDER BY '
3850 .'r.sortorder ASC, ' /* rolesorting 2nd criteria */
3851 .'ra.id'; /* role assignment order tie-breaker */
3854 $sortedids = $DB->get_fieldset_sql($sql . $orderby);
3855 $sortedusers = array();
3856 $seen = array();
3858 foreach ($sortedids as $id) {
3859 // Avoid duplicates
3860 if (isset($seen[$id])) {
3861 continue;
3863 $seen[$id] = true;
3865 // assign
3866 $sortedusers[$id] = $users[$id];
3868 return $sortedusers;
3872 * Gets all the users assigned this role in this context or higher
3874 * @param int $roleid (can also be an array of ints!)
3875 * @param context $context
3876 * @param bool $parent if true, get list of users assigned in higher context too
3877 * @param string $fields fields from user (u.) , role assignment (ra) or role (r.)
3878 * @param string $sort sort from user (u.) , role assignment (ra.) or role (r.).
3879 * null => use default sort from users_order_by_sql.
3880 * @param bool $all true means all, false means limit to enrolled users
3881 * @param string $group defaults to ''
3882 * @param mixed $limitfrom defaults to ''
3883 * @param mixed $limitnum defaults to ''
3884 * @param string $extrawheretest defaults to ''
3885 * @param array $whereorsortparams any paramter values used by $sort or $extrawheretest.
3886 * @return array
3888 function get_role_users($roleid, context $context, $parent = false, $fields = '',
3889 $sort = null, $all = true, $group = '',
3890 $limitfrom = '', $limitnum = '', $extrawheretest = '', $whereorsortparams = array()) {
3891 global $DB;
3893 if (empty($fields)) {
3894 $fields = 'u.id, u.confirmed, u.username, u.firstname, u.lastname, '.
3895 'u.maildisplay, u.mailformat, u.maildigest, u.email, u.emailstop, u.city, '.
3896 'u.country, u.picture, u.idnumber, u.department, u.institution, '.
3897 'u.lang, u.timezone, u.lastaccess, u.mnethostid, r.name AS rolename, r.sortorder, '.
3898 'r.shortname AS roleshortname, rn.name AS rolecoursealias';
3901 $parentcontexts = '';
3902 if ($parent) {
3903 $parentcontexts = substr($context->path, 1); // kill leading slash
3904 $parentcontexts = str_replace('/', ',', $parentcontexts);
3905 if ($parentcontexts !== '') {
3906 $parentcontexts = ' OR ra.contextid IN ('.$parentcontexts.' )';
3910 if ($roleid) {
3911 list($rids, $params) = $DB->get_in_or_equal($roleid, SQL_PARAMS_NAMED, 'r');
3912 $roleselect = "AND ra.roleid $rids";
3913 } else {
3914 $params = array();
3915 $roleselect = '';
3918 if ($coursecontext = $context->get_course_context(false)) {
3919 $params['coursecontext'] = $coursecontext->id;
3920 } else {
3921 $params['coursecontext'] = 0;
3924 if ($group) {
3925 $groupjoin = "JOIN {groups_members} gm ON gm.userid = u.id";
3926 $groupselect = " AND gm.groupid = :groupid ";
3927 $params['groupid'] = $group;
3928 } else {
3929 $groupjoin = '';
3930 $groupselect = '';
3933 $params['contextid'] = $context->id;
3935 if ($extrawheretest) {
3936 $extrawheretest = ' AND ' . $extrawheretest;
3939 if ($whereorsortparams) {
3940 $params = array_merge($params, $whereorsortparams);
3943 if (!$sort) {
3944 list($sort, $sortparams) = users_order_by_sql('u');
3945 $params = array_merge($params, $sortparams);
3948 if ($all === null) {
3949 // Previously null was used to indicate that parameter was not used.
3950 $all = true;
3952 if (!$all and $coursecontext) {
3953 // Do not use get_enrolled_sql() here for performance reasons.
3954 $ejoin = "JOIN {user_enrolments} ue ON ue.userid = u.id
3955 JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :ecourseid)";
3956 $params['ecourseid'] = $coursecontext->instanceid;
3957 } else {
3958 $ejoin = "";
3961 $sql = "SELECT DISTINCT $fields, ra.roleid
3962 FROM {role_assignments} ra
3963 JOIN {user} u ON u.id = ra.userid
3964 JOIN {role} r ON ra.roleid = r.id
3965 $ejoin
3966 LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
3967 $groupjoin
3968 WHERE (ra.contextid = :contextid $parentcontexts)
3969 $roleselect
3970 $groupselect
3971 $extrawheretest
3972 ORDER BY $sort"; // join now so that we can just use fullname() later
3974 return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
3978 * Counts all the users assigned this role in this context or higher
3980 * @param int|array $roleid either int or an array of ints
3981 * @param context $context
3982 * @param bool $parent if true, get list of users assigned in higher context too
3983 * @return int Returns the result count
3985 function count_role_users($roleid, context $context, $parent = false) {
3986 global $DB;
3988 if ($parent) {
3989 if ($contexts = $context->get_parent_context_ids()) {
3990 $parentcontexts = ' OR r.contextid IN ('.implode(',', $contexts).')';
3991 } else {
3992 $parentcontexts = '';
3994 } else {
3995 $parentcontexts = '';
3998 if ($roleid) {
3999 list($rids, $params) = $DB->get_in_or_equal($roleid, SQL_PARAMS_QM);
4000 $roleselect = "AND r.roleid $rids";
4001 } else {
4002 $params = array();
4003 $roleselect = '';
4006 array_unshift($params, $context->id);
4008 $sql = "SELECT COUNT(u.id)
4009 FROM {role_assignments} r
4010 JOIN {user} u ON u.id = r.userid
4011 WHERE (r.contextid = ? $parentcontexts)
4012 $roleselect
4013 AND u.deleted = 0";
4015 return $DB->count_records_sql($sql, $params);
4019 * This function gets the list of courses that this user has a particular capability in.
4020 * It is still not very efficient.
4022 * @param string $capability Capability in question
4023 * @param int $userid User ID or null for current user
4024 * @param bool $doanything True if 'doanything' is permitted (default)
4025 * @param string $fieldsexceptid Leave blank if you only need 'id' in the course records;
4026 * otherwise use a comma-separated list of the fields you require, not including id
4027 * @param string $orderby If set, use a comma-separated list of fields from course
4028 * table with sql modifiers (DESC) if needed
4029 * @return array Array of courses, may have zero entries. Or false if query failed.
4031 function get_user_capability_course($capability, $userid = null, $doanything = true, $fieldsexceptid = '', $orderby = '') {
4032 global $DB;
4034 // Convert fields list and ordering
4035 $fieldlist = '';
4036 if ($fieldsexceptid) {
4037 $fields = explode(',', $fieldsexceptid);
4038 foreach($fields as $field) {
4039 $fieldlist .= ',c.'.$field;
4042 if ($orderby) {
4043 $fields = explode(',', $orderby);
4044 $orderby = '';
4045 foreach($fields as $field) {
4046 if ($orderby) {
4047 $orderby .= ',';
4049 $orderby .= 'c.'.$field;
4051 $orderby = 'ORDER BY '.$orderby;
4054 // Obtain a list of everything relevant about all courses including context.
4055 // Note the result can be used directly as a context (we are going to), the course
4056 // fields are just appended.
4058 $contextpreload = context_helper::get_preload_record_columns_sql('x');
4060 $courses = array();
4061 $rs = $DB->get_recordset_sql("SELECT c.id $fieldlist, $contextpreload
4062 FROM {course} c
4063 JOIN {context} x ON (c.id=x.instanceid AND x.contextlevel=".CONTEXT_COURSE.")
4064 $orderby");
4065 // Check capability for each course in turn
4066 foreach ($rs as $course) {
4067 context_helper::preload_from_record($course);
4068 $context = context_course::instance($course->id);
4069 if (has_capability($capability, $context, $userid, $doanything)) {
4070 // We've got the capability. Make the record look like a course record
4071 // and store it
4072 $courses[] = $course;
4075 $rs->close();
4076 return empty($courses) ? false : $courses;
4080 * This function finds the roles assigned directly to this context only
4081 * i.e. no roles in parent contexts
4083 * @param context $context
4084 * @return array
4086 function get_roles_on_exact_context(context $context) {
4087 global $DB;
4089 return $DB->get_records_sql("SELECT r.*
4090 FROM {role_assignments} ra, {role} r
4091 WHERE ra.roleid = r.id AND ra.contextid = ?",
4092 array($context->id));
4096 * Switches the current user to another role for the current session and only
4097 * in the given context.
4099 * The caller *must* check
4100 * - that this op is allowed
4101 * - that the requested role can be switched to in this context (use get_switchable_roles)
4102 * - that the requested role is NOT $CFG->defaultuserroleid
4104 * To "unswitch" pass 0 as the roleid.
4106 * This function *will* modify $USER->access - beware
4108 * @param integer $roleid the role to switch to.
4109 * @param context $context the context in which to perform the switch.
4110 * @return bool success or failure.
4112 function role_switch($roleid, context $context) {
4113 global $USER;
4116 // Plan of action
4118 // - Add the ghost RA to $USER->access
4119 // as $USER->access['rsw'][$path] = $roleid
4121 // - Make sure $USER->access['rdef'] has the roledefs
4122 // it needs to honour the switcherole
4124 // Roledefs will get loaded "deep" here - down to the last child
4125 // context. Note that
4127 // - When visiting subcontexts, our selective accessdata loading
4128 // will still work fine - though those ra/rdefs will be ignored
4129 // appropriately while the switch is in place
4131 // - If a switcherole happens at a category with tons of courses
4132 // (that have many overrides for switched-to role), the session
4133 // will get... quite large. Sometimes you just can't win.
4135 // To un-switch just unset($USER->access['rsw'][$path])
4137 // Note: it is not possible to switch to roles that do not have course:view
4139 if (!isset($USER->access)) {
4140 load_all_capabilities();
4144 // Add the switch RA
4145 if ($roleid == 0) {
4146 unset($USER->access['rsw'][$context->path]);
4147 return true;
4150 $USER->access['rsw'][$context->path] = $roleid;
4152 // Load roledefs
4153 load_role_access_by_context($roleid, $context, $USER->access);
4155 return true;
4159 * Checks if the user has switched roles within the given course.
4161 * Note: You can only switch roles within the course, hence it takes a course id
4162 * rather than a context. On that note Petr volunteered to implement this across
4163 * all other contexts, all requests for this should be forwarded to him ;)
4165 * @param int $courseid The id of the course to check
4166 * @return bool True if the user has switched roles within the course.
4168 function is_role_switched($courseid) {
4169 global $USER;
4170 $context = context_course::instance($courseid, MUST_EXIST);
4171 return (!empty($USER->access['rsw'][$context->path]));
4175 * Get any role that has an override on exact context
4177 * @param context $context The context to check
4178 * @return array An array of roles
4180 function get_roles_with_override_on_context(context $context) {
4181 global $DB;
4183 return $DB->get_records_sql("SELECT r.*
4184 FROM {role_capabilities} rc, {role} r
4185 WHERE rc.roleid = r.id AND rc.contextid = ?",
4186 array($context->id));
4190 * Get all capabilities for this role on this context (overrides)
4192 * @param stdClass $role
4193 * @param context $context
4194 * @return array
4196 function get_capabilities_from_role_on_context($role, context $context) {
4197 global $DB;
4199 return $DB->get_records_sql("SELECT *
4200 FROM {role_capabilities}
4201 WHERE contextid = ? AND roleid = ?",
4202 array($context->id, $role->id));
4206 * Find out which roles has assignment on this context
4208 * @param context $context
4209 * @return array
4212 function get_roles_with_assignment_on_context(context $context) {
4213 global $DB;
4215 return $DB->get_records_sql("SELECT r.*
4216 FROM {role_assignments} ra, {role} r
4217 WHERE ra.roleid = r.id AND ra.contextid = ?",
4218 array($context->id));
4222 * Find all user assignment of users for this role, on this context
4224 * @param stdClass $role
4225 * @param context $context
4226 * @return array
4228 function get_users_from_role_on_context($role, context $context) {
4229 global $DB;
4231 return $DB->get_records_sql("SELECT *
4232 FROM {role_assignments}
4233 WHERE contextid = ? AND roleid = ?",
4234 array($context->id, $role->id));
4238 * Simple function returning a boolean true if user has roles
4239 * in context or parent contexts, otherwise false.
4241 * @param int $userid
4242 * @param int $roleid
4243 * @param int $contextid empty means any context
4244 * @return bool
4246 function user_has_role_assignment($userid, $roleid, $contextid = 0) {
4247 global $DB;
4249 if ($contextid) {
4250 if (!$context = context::instance_by_id($contextid, IGNORE_MISSING)) {
4251 return false;
4253 $parents = $context->get_parent_context_ids(true);
4254 list($contexts, $params) = $DB->get_in_or_equal($parents, SQL_PARAMS_NAMED, 'r');
4255 $params['userid'] = $userid;
4256 $params['roleid'] = $roleid;
4258 $sql = "SELECT COUNT(ra.id)
4259 FROM {role_assignments} ra
4260 WHERE ra.userid = :userid AND ra.roleid = :roleid AND ra.contextid $contexts";
4262 $count = $DB->get_field_sql($sql, $params);
4263 return ($count > 0);
4265 } else {
4266 return $DB->record_exists('role_assignments', array('userid'=>$userid, 'roleid'=>$roleid));
4271 * Get localised role name or alias if exists and format the text.
4273 * @param stdClass $role role object
4274 * - optional 'coursealias' property should be included for performance reasons if course context used
4275 * - description property is not required here
4276 * @param context|bool $context empty means system context
4277 * @param int $rolenamedisplay type of role name
4278 * @return string localised role name or course role name alias
4280 function role_get_name(stdClass $role, $context = null, $rolenamedisplay = ROLENAME_ALIAS) {
4281 global $DB;
4283 if ($rolenamedisplay == ROLENAME_SHORT) {
4284 return $role->shortname;
4287 if (!$context or !$coursecontext = $context->get_course_context(false)) {
4288 $coursecontext = null;
4291 if ($coursecontext and !property_exists($role, 'coursealias') and ($rolenamedisplay == ROLENAME_ALIAS or $rolenamedisplay == ROLENAME_BOTH or $rolenamedisplay == ROLENAME_ALIAS_RAW)) {
4292 $role = clone($role); // Do not modify parameters.
4293 if ($r = $DB->get_record('role_names', array('roleid'=>$role->id, 'contextid'=>$coursecontext->id))) {
4294 $role->coursealias = $r->name;
4295 } else {
4296 $role->coursealias = null;
4300 if ($rolenamedisplay == ROLENAME_ALIAS_RAW) {
4301 if ($coursecontext) {
4302 return $role->coursealias;
4303 } else {
4304 return null;
4308 if (trim($role->name) !== '') {
4309 // For filtering always use context where was the thing defined - system for roles here.
4310 $original = format_string($role->name, true, array('context'=>context_system::instance()));
4312 } else {
4313 // Empty role->name means we want to see localised role name based on shortname,
4314 // only default roles are supposed to be localised.
4315 switch ($role->shortname) {
4316 case 'manager': $original = get_string('manager', 'role'); break;
4317 case 'coursecreator': $original = get_string('coursecreators'); break;
4318 case 'editingteacher': $original = get_string('defaultcourseteacher'); break;
4319 case 'teacher': $original = get_string('noneditingteacher'); break;
4320 case 'student': $original = get_string('defaultcoursestudent'); break;
4321 case 'guest': $original = get_string('guest'); break;
4322 case 'user': $original = get_string('authenticateduser'); break;
4323 case 'frontpage': $original = get_string('frontpageuser', 'role'); break;
4324 // We should not get here, the role UI should require the name for custom roles!
4325 default: $original = $role->shortname; break;
4329 if ($rolenamedisplay == ROLENAME_ORIGINAL) {
4330 return $original;
4333 if ($rolenamedisplay == ROLENAME_ORIGINALANDSHORT) {
4334 return "$original ($role->shortname)";
4337 if ($rolenamedisplay == ROLENAME_ALIAS) {
4338 if ($coursecontext and trim($role->coursealias) !== '') {
4339 return format_string($role->coursealias, true, array('context'=>$coursecontext));
4340 } else {
4341 return $original;
4345 if ($rolenamedisplay == ROLENAME_BOTH) {
4346 if ($coursecontext and trim($role->coursealias) !== '') {
4347 return format_string($role->coursealias, true, array('context'=>$coursecontext)) . " ($original)";
4348 } else {
4349 return $original;
4353 throw new coding_exception('Invalid $rolenamedisplay parameter specified in role_get_name()');
4357 * Returns localised role description if available.
4358 * If the name is empty it tries to find the default role name using
4359 * hardcoded list of default role names or other methods in the future.
4361 * @param stdClass $role
4362 * @return string localised role name
4364 function role_get_description(stdClass $role) {
4365 if (!html_is_blank($role->description)) {
4366 return format_text($role->description, FORMAT_HTML, array('context'=>context_system::instance()));
4369 switch ($role->shortname) {
4370 case 'manager': return get_string('managerdescription', 'role');
4371 case 'coursecreator': return get_string('coursecreatorsdescription');
4372 case 'editingteacher': return get_string('defaultcourseteacherdescription');
4373 case 'teacher': return get_string('noneditingteacherdescription');
4374 case 'student': return get_string('defaultcoursestudentdescription');
4375 case 'guest': return get_string('guestdescription');
4376 case 'user': return get_string('authenticateduserdescription');
4377 case 'frontpage': return get_string('frontpageuserdescription', 'role');
4378 default: return '';
4383 * Get all the localised role names for a context.
4385 * In new installs default roles have empty names, this function
4386 * add localised role names using current language pack.
4388 * @param context $context the context, null means system context
4389 * @param array of role objects with a ->localname field containing the context-specific role name.
4390 * @param int $rolenamedisplay
4391 * @param bool $returnmenu true means id=>localname, false means id=>rolerecord
4392 * @return array Array of context-specific role names, or role objects with a ->localname field added.
4394 function role_get_names(context $context = null, $rolenamedisplay = ROLENAME_ALIAS, $returnmenu = null) {
4395 return role_fix_names(get_all_roles($context), $context, $rolenamedisplay, $returnmenu);
4399 * Prepare list of roles for display, apply aliases and localise default role names.
4401 * @param array $roleoptions array roleid => roleobject (with optional coursealias), strings are accepted for backwards compatibility only
4402 * @param context $context the context, null means system context
4403 * @param int $rolenamedisplay
4404 * @param bool $returnmenu null means keep the same format as $roleoptions, true means id=>localname, false means id=>rolerecord
4405 * @return array Array of context-specific role names, or role objects with a ->localname field added.
4407 function role_fix_names($roleoptions, context $context = null, $rolenamedisplay = ROLENAME_ALIAS, $returnmenu = null) {
4408 global $DB;
4410 if (empty($roleoptions)) {
4411 return array();
4414 if (!$context or !$coursecontext = $context->get_course_context(false)) {
4415 $coursecontext = null;
4418 // We usually need all role columns...
4419 $first = reset($roleoptions);
4420 if ($returnmenu === null) {
4421 $returnmenu = !is_object($first);
4424 if (!is_object($first) or !property_exists($first, 'shortname')) {
4425 $allroles = get_all_roles($context);
4426 foreach ($roleoptions as $rid => $unused) {
4427 $roleoptions[$rid] = $allroles[$rid];
4431 // Inject coursealias if necessary.
4432 if ($coursecontext and ($rolenamedisplay == ROLENAME_ALIAS_RAW or $rolenamedisplay == ROLENAME_ALIAS or $rolenamedisplay == ROLENAME_BOTH)) {
4433 $first = reset($roleoptions);
4434 if (!property_exists($first, 'coursealias')) {
4435 $aliasnames = $DB->get_records('role_names', array('contextid'=>$coursecontext->id));
4436 foreach ($aliasnames as $alias) {
4437 if (isset($roleoptions[$alias->roleid])) {
4438 $roleoptions[$alias->roleid]->coursealias = $alias->name;
4444 // Add localname property.
4445 foreach ($roleoptions as $rid => $role) {
4446 $roleoptions[$rid]->localname = role_get_name($role, $coursecontext, $rolenamedisplay);
4449 if (!$returnmenu) {
4450 return $roleoptions;
4453 $menu = array();
4454 foreach ($roleoptions as $rid => $role) {
4455 $menu[$rid] = $role->localname;
4458 return $menu;
4462 * Aids in detecting if a new line is required when reading a new capability
4464 * This function helps admin/roles/manage.php etc to detect if a new line should be printed
4465 * when we read in a new capability.
4466 * Most of the time, if the 2 components are different we should print a new line, (e.g. course system->rss client)
4467 * but when we are in grade, all reports/import/export capabilities should be together
4469 * @param string $cap component string a
4470 * @param string $comp component string b
4471 * @param int $contextlevel
4472 * @return bool whether 2 component are in different "sections"
4474 function component_level_changed($cap, $comp, $contextlevel) {
4476 if (strstr($cap->component, '/') && strstr($comp, '/')) {
4477 $compsa = explode('/', $cap->component);
4478 $compsb = explode('/', $comp);
4480 // list of system reports
4481 if (($compsa[0] == 'report') && ($compsb[0] == 'report')) {
4482 return false;
4485 // we are in gradebook, still
4486 if (($compsa[0] == 'gradeexport' || $compsa[0] == 'gradeimport' || $compsa[0] == 'gradereport') &&
4487 ($compsb[0] == 'gradeexport' || $compsb[0] == 'gradeimport' || $compsb[0] == 'gradereport')) {
4488 return false;
4491 if (($compsa[0] == 'coursereport') && ($compsb[0] == 'coursereport')) {
4492 return false;
4496 return ($cap->component != $comp || $cap->contextlevel != $contextlevel);
4500 * Fix the roles.sortorder field in the database, so it contains sequential integers,
4501 * and return an array of roleids in order.
4503 * @param array $allroles array of roles, as returned by get_all_roles();
4504 * @return array $role->sortorder =-> $role->id with the keys in ascending order.
4506 function fix_role_sortorder($allroles) {
4507 global $DB;
4509 $rolesort = array();
4510 $i = 0;
4511 foreach ($allroles as $role) {
4512 $rolesort[$i] = $role->id;
4513 if ($role->sortorder != $i) {
4514 $r = new stdClass();
4515 $r->id = $role->id;
4516 $r->sortorder = $i;
4517 $DB->update_record('role', $r);
4518 $allroles[$role->id]->sortorder = $i;
4520 $i++;
4522 return $rolesort;
4526 * Switch the sort order of two roles (used in admin/roles/manage.php).
4528 * @param stdClass $first The first role. Actually, only ->sortorder is used.
4529 * @param stdClass $second The second role. Actually, only ->sortorder is used.
4530 * @return boolean success or failure
4532 function switch_roles($first, $second) {
4533 global $DB;
4534 $temp = $DB->get_field('role', 'MAX(sortorder) + 1', array());
4535 $result = $DB->set_field('role', 'sortorder', $temp, array('sortorder' => $first->sortorder));
4536 $result = $result && $DB->set_field('role', 'sortorder', $first->sortorder, array('sortorder' => $second->sortorder));
4537 $result = $result && $DB->set_field('role', 'sortorder', $second->sortorder, array('sortorder' => $temp));
4538 return $result;
4542 * Duplicates all the base definitions of a role
4544 * @param stdClass $sourcerole role to copy from
4545 * @param int $targetrole id of role to copy to
4547 function role_cap_duplicate($sourcerole, $targetrole) {
4548 global $DB;
4550 $systemcontext = context_system::instance();
4551 $caps = $DB->get_records_sql("SELECT *
4552 FROM {role_capabilities}
4553 WHERE roleid = ? AND contextid = ?",
4554 array($sourcerole->id, $systemcontext->id));
4555 // adding capabilities
4556 foreach ($caps as $cap) {
4557 unset($cap->id);
4558 $cap->roleid = $targetrole;
4559 $DB->insert_record('role_capabilities', $cap);
4564 * Returns two lists, this can be used to find out if user has capability.
4565 * Having any needed role and no forbidden role in this context means
4566 * user has this capability in this context.
4567 * Use get_role_names_with_cap_in_context() if you need role names to display in the UI
4569 * @param stdClass $context
4570 * @param string $capability
4571 * @return array($neededroles, $forbiddenroles)
4573 function get_roles_with_cap_in_context($context, $capability) {
4574 global $DB;
4576 $ctxids = trim($context->path, '/'); // kill leading slash
4577 $ctxids = str_replace('/', ',', $ctxids);
4579 $sql = "SELECT rc.id, rc.roleid, rc.permission, ctx.depth
4580 FROM {role_capabilities} rc
4581 JOIN {context} ctx ON ctx.id = rc.contextid
4582 WHERE rc.capability = :cap AND ctx.id IN ($ctxids)
4583 ORDER BY rc.roleid ASC, ctx.depth DESC";
4584 $params = array('cap'=>$capability);
4586 if (!$capdefs = $DB->get_records_sql($sql, $params)) {
4587 // no cap definitions --> no capability
4588 return array(array(), array());
4591 $forbidden = array();
4592 $needed = array();
4593 foreach($capdefs as $def) {
4594 if (isset($forbidden[$def->roleid])) {
4595 continue;
4597 if ($def->permission == CAP_PROHIBIT) {
4598 $forbidden[$def->roleid] = $def->roleid;
4599 unset($needed[$def->roleid]);
4600 continue;
4602 if (!isset($needed[$def->roleid])) {
4603 if ($def->permission == CAP_ALLOW) {
4604 $needed[$def->roleid] = true;
4605 } else if ($def->permission == CAP_PREVENT) {
4606 $needed[$def->roleid] = false;
4610 unset($capdefs);
4612 // remove all those roles not allowing
4613 foreach($needed as $key=>$value) {
4614 if (!$value) {
4615 unset($needed[$key]);
4616 } else {
4617 $needed[$key] = $key;
4621 return array($needed, $forbidden);
4625 * Returns an array of role IDs that have ALL of the the supplied capabilities
4626 * Uses get_roles_with_cap_in_context(). Returns $allowed minus $forbidden
4628 * @param stdClass $context
4629 * @param array $capabilities An array of capabilities
4630 * @return array of roles with all of the required capabilities
4632 function get_roles_with_caps_in_context($context, $capabilities) {
4633 $neededarr = array();
4634 $forbiddenarr = array();
4635 foreach($capabilities as $caprequired) {
4636 list($neededarr[], $forbiddenarr[]) = get_roles_with_cap_in_context($context, $caprequired);
4639 $rolesthatcanrate = array();
4640 if (!empty($neededarr)) {
4641 foreach ($neededarr as $needed) {
4642 if (empty($rolesthatcanrate)) {
4643 $rolesthatcanrate = $needed;
4644 } else {
4645 //only want roles that have all caps
4646 $rolesthatcanrate = array_intersect_key($rolesthatcanrate,$needed);
4650 if (!empty($forbiddenarr) && !empty($rolesthatcanrate)) {
4651 foreach ($forbiddenarr as $forbidden) {
4652 //remove any roles that are forbidden any of the caps
4653 $rolesthatcanrate = array_diff($rolesthatcanrate, $forbidden);
4656 return $rolesthatcanrate;
4660 * Returns an array of role names that have ALL of the the supplied capabilities
4661 * Uses get_roles_with_caps_in_context(). Returns $allowed minus $forbidden
4663 * @param stdClass $context
4664 * @param array $capabilities An array of capabilities
4665 * @return array of roles with all of the required capabilities
4667 function get_role_names_with_caps_in_context($context, $capabilities) {
4668 global $DB;
4670 $rolesthatcanrate = get_roles_with_caps_in_context($context, $capabilities);
4671 $allroles = $DB->get_records('role', null, 'sortorder DESC');
4673 $roles = array();
4674 foreach ($rolesthatcanrate as $r) {
4675 $roles[$r] = $allroles[$r];
4678 return role_fix_names($roles, $context, ROLENAME_ALIAS, true);
4682 * This function verifies the prohibit comes from this context
4683 * and there are no more prohibits in parent contexts.
4685 * @param int $roleid
4686 * @param context $context
4687 * @param string $capability name
4688 * @return bool
4690 function prohibit_is_removable($roleid, context $context, $capability) {
4691 global $DB;
4693 $ctxids = trim($context->path, '/'); // kill leading slash
4694 $ctxids = str_replace('/', ',', $ctxids);
4696 $params = array('roleid'=>$roleid, 'cap'=>$capability, 'prohibit'=>CAP_PROHIBIT);
4698 $sql = "SELECT ctx.id
4699 FROM {role_capabilities} rc
4700 JOIN {context} ctx ON ctx.id = rc.contextid
4701 WHERE rc.roleid = :roleid AND rc.permission = :prohibit AND rc.capability = :cap AND ctx.id IN ($ctxids)
4702 ORDER BY ctx.depth DESC";
4704 if (!$prohibits = $DB->get_records_sql($sql, $params)) {
4705 // no prohibits == nothing to remove
4706 return true;
4709 if (count($prohibits) > 1) {
4710 // more prohibits can not be removed
4711 return false;
4714 return !empty($prohibits[$context->id]);
4718 * More user friendly role permission changing,
4719 * it should produce as few overrides as possible.
4721 * @param int $roleid
4722 * @param stdClass $context
4723 * @param string $capname capability name
4724 * @param int $permission
4725 * @return void
4727 function role_change_permission($roleid, $context, $capname, $permission) {
4728 global $DB;
4730 if ($permission == CAP_INHERIT) {
4731 unassign_capability($capname, $roleid, $context->id);
4732 $context->mark_dirty();
4733 return;
4736 $ctxids = trim($context->path, '/'); // kill leading slash
4737 $ctxids = str_replace('/', ',', $ctxids);
4739 $params = array('roleid'=>$roleid, 'cap'=>$capname);
4741 $sql = "SELECT ctx.id, rc.permission, ctx.depth
4742 FROM {role_capabilities} rc
4743 JOIN {context} ctx ON ctx.id = rc.contextid
4744 WHERE rc.roleid = :roleid AND rc.capability = :cap AND ctx.id IN ($ctxids)
4745 ORDER BY ctx.depth DESC";
4747 if ($existing = $DB->get_records_sql($sql, $params)) {
4748 foreach($existing as $e) {
4749 if ($e->permission == CAP_PROHIBIT) {
4750 // prohibit can not be overridden, no point in changing anything
4751 return;
4754 $lowest = array_shift($existing);
4755 if ($lowest->permission == $permission) {
4756 // permission already set in this context or parent - nothing to do
4757 return;
4759 if ($existing) {
4760 $parent = array_shift($existing);
4761 if ($parent->permission == $permission) {
4762 // permission already set in parent context or parent - just unset in this context
4763 // we do this because we want as few overrides as possible for performance reasons
4764 unassign_capability($capname, $roleid, $context->id);
4765 $context->mark_dirty();
4766 return;
4770 } else {
4771 if ($permission == CAP_PREVENT) {
4772 // nothing means role does not have permission
4773 return;
4777 // assign the needed capability
4778 assign_capability($capname, $permission, $roleid, $context->id, true);
4780 // force cap reloading
4781 $context->mark_dirty();
4786 * Basic moodle context abstraction class.
4788 * Google confirms that no other important framework is using "context" class,
4789 * we could use something else like mcontext or moodle_context, but we need to type
4790 * this very often which would be annoying and it would take too much space...
4792 * This class is derived from stdClass for backwards compatibility with
4793 * odl $context record that was returned from DML $DB->get_record()
4795 * @package core_access
4796 * @category access
4797 * @copyright Petr Skoda {@link http://skodak.org}
4798 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4799 * @since 2.2
4801 * @property-read int $id context id
4802 * @property-read int $contextlevel CONTEXT_SYSTEM, CONTEXT_COURSE, etc.
4803 * @property-read int $instanceid id of related instance in each context
4804 * @property-read string $path path to context, starts with system context
4805 * @property-read int $depth
4807 abstract class context extends stdClass implements IteratorAggregate {
4810 * The context id
4811 * Can be accessed publicly through $context->id
4812 * @var int
4814 protected $_id;
4817 * The context level
4818 * Can be accessed publicly through $context->contextlevel
4819 * @var int One of CONTEXT_* e.g. CONTEXT_COURSE, CONTEXT_MODULE
4821 protected $_contextlevel;
4824 * Id of the item this context is related to e.g. COURSE_CONTEXT => course.id
4825 * Can be accessed publicly through $context->instanceid
4826 * @var int
4828 protected $_instanceid;
4831 * The path to the context always starting from the system context
4832 * Can be accessed publicly through $context->path
4833 * @var string
4835 protected $_path;
4838 * The depth of the context in relation to parent contexts
4839 * Can be accessed publicly through $context->depth
4840 * @var int
4842 protected $_depth;
4845 * @var array Context caching info
4847 private static $cache_contextsbyid = array();
4850 * @var array Context caching info
4852 private static $cache_contexts = array();
4855 * Context count
4856 * Why do we do count contexts? Because count($array) is horribly slow for large arrays
4857 * @var int
4859 protected static $cache_count = 0;
4862 * @var array Context caching info
4864 protected static $cache_preloaded = array();
4867 * @var context_system The system context once initialised
4869 protected static $systemcontext = null;
4872 * Resets the cache to remove all data.
4873 * @static
4875 protected static function reset_caches() {
4876 self::$cache_contextsbyid = array();
4877 self::$cache_contexts = array();
4878 self::$cache_count = 0;
4879 self::$cache_preloaded = array();
4881 self::$systemcontext = null;
4885 * Adds a context to the cache. If the cache is full, discards a batch of
4886 * older entries.
4888 * @static
4889 * @param context $context New context to add
4890 * @return void
4892 protected static function cache_add(context $context) {
4893 if (isset(self::$cache_contextsbyid[$context->id])) {
4894 // already cached, no need to do anything - this is relatively cheap, we do all this because count() is slow
4895 return;
4898 if (self::$cache_count >= CONTEXT_CACHE_MAX_SIZE) {
4899 $i = 0;
4900 foreach(self::$cache_contextsbyid as $ctx) {
4901 $i++;
4902 if ($i <= 100) {
4903 // we want to keep the first contexts to be loaded on this page, hopefully they will be needed again later
4904 continue;
4906 if ($i > (CONTEXT_CACHE_MAX_SIZE / 3)) {
4907 // we remove oldest third of the contexts to make room for more contexts
4908 break;
4910 unset(self::$cache_contextsbyid[$ctx->id]);
4911 unset(self::$cache_contexts[$ctx->contextlevel][$ctx->instanceid]);
4912 self::$cache_count--;
4916 self::$cache_contexts[$context->contextlevel][$context->instanceid] = $context;
4917 self::$cache_contextsbyid[$context->id] = $context;
4918 self::$cache_count++;
4922 * Removes a context from the cache.
4924 * @static
4925 * @param context $context Context object to remove
4926 * @return void
4928 protected static function cache_remove(context $context) {
4929 if (!isset(self::$cache_contextsbyid[$context->id])) {
4930 // not cached, no need to do anything - this is relatively cheap, we do all this because count() is slow
4931 return;
4933 unset(self::$cache_contexts[$context->contextlevel][$context->instanceid]);
4934 unset(self::$cache_contextsbyid[$context->id]);
4936 self::$cache_count--;
4938 if (self::$cache_count < 0) {
4939 self::$cache_count = 0;
4944 * Gets a context from the cache.
4946 * @static
4947 * @param int $contextlevel Context level
4948 * @param int $instance Instance ID
4949 * @return context|bool Context or false if not in cache
4951 protected static function cache_get($contextlevel, $instance) {
4952 if (isset(self::$cache_contexts[$contextlevel][$instance])) {
4953 return self::$cache_contexts[$contextlevel][$instance];
4955 return false;
4959 * Gets a context from the cache based on its id.
4961 * @static
4962 * @param int $id Context ID
4963 * @return context|bool Context or false if not in cache
4965 protected static function cache_get_by_id($id) {
4966 if (isset(self::$cache_contextsbyid[$id])) {
4967 return self::$cache_contextsbyid[$id];
4969 return false;
4973 * Preloads context information from db record and strips the cached info.
4975 * @static
4976 * @param stdClass $rec
4977 * @return void (modifies $rec)
4979 protected static function preload_from_record(stdClass $rec) {
4980 if (empty($rec->ctxid) or empty($rec->ctxlevel) or empty($rec->ctxinstance) or empty($rec->ctxpath) or empty($rec->ctxdepth)) {
4981 // $rec does not have enough data, passed here repeatedly or context does not exist yet
4982 return;
4985 // note: in PHP5 the objects are passed by reference, no need to return $rec
4986 $record = new stdClass();
4987 $record->id = $rec->ctxid; unset($rec->ctxid);
4988 $record->contextlevel = $rec->ctxlevel; unset($rec->ctxlevel);
4989 $record->instanceid = $rec->ctxinstance; unset($rec->ctxinstance);
4990 $record->path = $rec->ctxpath; unset($rec->ctxpath);
4991 $record->depth = $rec->ctxdepth; unset($rec->ctxdepth);
4993 return context::create_instance_from_record($record);
4997 // ====== magic methods =======
5000 * Magic setter method, we do not want anybody to modify properties from the outside
5001 * @param string $name
5002 * @param mixed $value
5004 public function __set($name, $value) {
5005 debugging('Can not change context instance properties!');
5009 * Magic method getter, redirects to read only values.
5010 * @param string $name
5011 * @return mixed
5013 public function __get($name) {
5014 switch ($name) {
5015 case 'id': return $this->_id;
5016 case 'contextlevel': return $this->_contextlevel;
5017 case 'instanceid': return $this->_instanceid;
5018 case 'path': return $this->_path;
5019 case 'depth': return $this->_depth;
5021 default:
5022 debugging('Invalid context property accessed! '.$name);
5023 return null;
5028 * Full support for isset on our magic read only properties.
5029 * @param string $name
5030 * @return bool
5032 public function __isset($name) {
5033 switch ($name) {
5034 case 'id': return isset($this->_id);
5035 case 'contextlevel': return isset($this->_contextlevel);
5036 case 'instanceid': return isset($this->_instanceid);
5037 case 'path': return isset($this->_path);
5038 case 'depth': return isset($this->_depth);
5040 default: return false;
5046 * ALl properties are read only, sorry.
5047 * @param string $name
5049 public function __unset($name) {
5050 debugging('Can not unset context instance properties!');
5053 // ====== implementing method from interface IteratorAggregate ======
5056 * Create an iterator because magic vars can't be seen by 'foreach'.
5058 * Now we can convert context object to array using convert_to_array(),
5059 * and feed it properly to json_encode().
5061 public function getIterator() {
5062 $ret = array(
5063 'id' => $this->id,
5064 'contextlevel' => $this->contextlevel,
5065 'instanceid' => $this->instanceid,
5066 'path' => $this->path,
5067 'depth' => $this->depth
5069 return new ArrayIterator($ret);
5072 // ====== general context methods ======
5075 * Constructor is protected so that devs are forced to
5076 * use context_xxx::instance() or context::instance_by_id().
5078 * @param stdClass $record
5080 protected function __construct(stdClass $record) {
5081 $this->_id = $record->id;
5082 $this->_contextlevel = (int)$record->contextlevel;
5083 $this->_instanceid = $record->instanceid;
5084 $this->_path = $record->path;
5085 $this->_depth = $record->depth;
5089 * This function is also used to work around 'protected' keyword problems in context_helper.
5090 * @static
5091 * @param stdClass $record
5092 * @return context instance
5094 protected static function create_instance_from_record(stdClass $record) {
5095 $classname = context_helper::get_class_for_level($record->contextlevel);
5097 if ($context = context::cache_get_by_id($record->id)) {
5098 return $context;
5101 $context = new $classname($record);
5102 context::cache_add($context);
5104 return $context;
5108 * Copy prepared new contexts from temp table to context table,
5109 * we do this in db specific way for perf reasons only.
5110 * @static
5112 protected static function merge_context_temp_table() {
5113 global $DB;
5115 /* MDL-11347:
5116 * - mysql does not allow to use FROM in UPDATE statements
5117 * - using two tables after UPDATE works in mysql, but might give unexpected
5118 * results in pg 8 (depends on configuration)
5119 * - using table alias in UPDATE does not work in pg < 8.2
5121 * Different code for each database - mostly for performance reasons
5124 $dbfamily = $DB->get_dbfamily();
5125 if ($dbfamily == 'mysql') {
5126 $updatesql = "UPDATE {context} ct, {context_temp} temp
5127 SET ct.path = temp.path,
5128 ct.depth = temp.depth
5129 WHERE ct.id = temp.id";
5130 } else if ($dbfamily == 'oracle') {
5131 $updatesql = "UPDATE {context} ct
5132 SET (ct.path, ct.depth) =
5133 (SELECT temp.path, temp.depth
5134 FROM {context_temp} temp
5135 WHERE temp.id=ct.id)
5136 WHERE EXISTS (SELECT 'x'
5137 FROM {context_temp} temp
5138 WHERE temp.id = ct.id)";
5139 } else if ($dbfamily == 'postgres' or $dbfamily == 'mssql') {
5140 $updatesql = "UPDATE {context}
5141 SET path = temp.path,
5142 depth = temp.depth
5143 FROM {context_temp} temp
5144 WHERE temp.id={context}.id";
5145 } else {
5146 // sqlite and others
5147 $updatesql = "UPDATE {context}
5148 SET path = (SELECT path FROM {context_temp} WHERE id = {context}.id),
5149 depth = (SELECT depth FROM {context_temp} WHERE id = {context}.id)
5150 WHERE id IN (SELECT id FROM {context_temp})";
5153 $DB->execute($updatesql);
5157 * Get a context instance as an object, from a given context id.
5159 * @static
5160 * @param int $id context id
5161 * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found;
5162 * MUST_EXIST means throw exception if no record found
5163 * @return context|bool the context object or false if not found
5165 public static function instance_by_id($id, $strictness = MUST_EXIST) {
5166 global $DB;
5168 if (get_called_class() !== 'context' and get_called_class() !== 'context_helper') {
5169 // some devs might confuse context->id and instanceid, better prevent these mistakes completely
5170 throw new coding_exception('use only context::instance_by_id() for real context levels use ::instance() methods');
5173 if ($id == SYSCONTEXTID) {
5174 return context_system::instance(0, $strictness);
5177 if (is_array($id) or is_object($id) or empty($id)) {
5178 throw new coding_exception('Invalid context id specified context::instance_by_id()');
5181 if ($context = context::cache_get_by_id($id)) {
5182 return $context;
5185 if ($record = $DB->get_record('context', array('id'=>$id), '*', $strictness)) {
5186 return context::create_instance_from_record($record);
5189 return false;
5193 * Update context info after moving context in the tree structure.
5195 * @param context $newparent
5196 * @return void
5198 public function update_moved(context $newparent) {
5199 global $DB;
5201 $frompath = $this->_path;
5202 $newpath = $newparent->path . '/' . $this->_id;
5204 $trans = $DB->start_delegated_transaction();
5206 $this->mark_dirty();
5208 $setdepth = '';
5209 if (($newparent->depth +1) != $this->_depth) {
5210 $diff = $newparent->depth - $this->_depth + 1;
5211 $setdepth = ", depth = depth + $diff";
5213 $sql = "UPDATE {context}
5214 SET path = ?
5215 $setdepth
5216 WHERE id = ?";
5217 $params = array($newpath, $this->_id);
5218 $DB->execute($sql, $params);
5220 $this->_path = $newpath;
5221 $this->_depth = $newparent->depth + 1;
5223 $sql = "UPDATE {context}
5224 SET path = ".$DB->sql_concat("?", $DB->sql_substr("path", strlen($frompath)+1))."
5225 $setdepth
5226 WHERE path LIKE ?";
5227 $params = array($newpath, "{$frompath}/%");
5228 $DB->execute($sql, $params);
5230 $this->mark_dirty();
5232 context::reset_caches();
5234 $trans->allow_commit();
5238 * Remove all context path info and optionally rebuild it.
5240 * @param bool $rebuild
5241 * @return void
5243 public function reset_paths($rebuild = true) {
5244 global $DB;
5246 if ($this->_path) {
5247 $this->mark_dirty();
5249 $DB->set_field_select('context', 'depth', 0, "path LIKE '%/$this->_id/%'");
5250 $DB->set_field_select('context', 'path', NULL, "path LIKE '%/$this->_id/%'");
5251 if ($this->_contextlevel != CONTEXT_SYSTEM) {
5252 $DB->set_field('context', 'depth', 0, array('id'=>$this->_id));
5253 $DB->set_field('context', 'path', NULL, array('id'=>$this->_id));
5254 $this->_depth = 0;
5255 $this->_path = null;
5258 if ($rebuild) {
5259 context_helper::build_all_paths(false);
5262 context::reset_caches();
5266 * Delete all data linked to content, do not delete the context record itself
5268 public function delete_content() {
5269 global $CFG, $DB;
5271 blocks_delete_all_for_context($this->_id);
5272 filter_delete_all_for_context($this->_id);
5274 require_once($CFG->dirroot . '/comment/lib.php');
5275 comment::delete_comments(array('contextid'=>$this->_id));
5277 require_once($CFG->dirroot.'/rating/lib.php');
5278 $delopt = new stdclass();
5279 $delopt->contextid = $this->_id;
5280 $rm = new rating_manager();
5281 $rm->delete_ratings($delopt);
5283 // delete all files attached to this context
5284 $fs = get_file_storage();
5285 $fs->delete_area_files($this->_id);
5287 // delete all advanced grading data attached to this context
5288 require_once($CFG->dirroot.'/grade/grading/lib.php');
5289 grading_manager::delete_all_for_context($this->_id);
5291 // now delete stuff from role related tables, role_unassign_all
5292 // and unenrol should be called earlier to do proper cleanup
5293 $DB->delete_records('role_assignments', array('contextid'=>$this->_id));
5294 $DB->delete_records('role_capabilities', array('contextid'=>$this->_id));
5295 $DB->delete_records('role_names', array('contextid'=>$this->_id));
5299 * Delete the context content and the context record itself
5301 public function delete() {
5302 global $DB;
5304 // double check the context still exists
5305 if (!$DB->record_exists('context', array('id'=>$this->_id))) {
5306 context::cache_remove($this);
5307 return;
5310 $this->delete_content();
5311 $DB->delete_records('context', array('id'=>$this->_id));
5312 // purge static context cache if entry present
5313 context::cache_remove($this);
5315 // do not mark dirty contexts if parents unknown
5316 if (!is_null($this->_path) and $this->_depth > 0) {
5317 $this->mark_dirty();
5321 // ====== context level related methods ======
5324 * Utility method for context creation
5326 * @static
5327 * @param int $contextlevel
5328 * @param int $instanceid
5329 * @param string $parentpath
5330 * @return stdClass context record
5332 protected static function insert_context_record($contextlevel, $instanceid, $parentpath) {
5333 global $DB;
5335 $record = new stdClass();
5336 $record->contextlevel = $contextlevel;
5337 $record->instanceid = $instanceid;
5338 $record->depth = 0;
5339 $record->path = null; //not known before insert
5341 $record->id = $DB->insert_record('context', $record);
5343 // now add path if known - it can be added later
5344 if (!is_null($parentpath)) {
5345 $record->path = $parentpath.'/'.$record->id;
5346 $record->depth = substr_count($record->path, '/');
5347 $DB->update_record('context', $record);
5350 return $record;
5354 * Returns human readable context identifier.
5356 * @param boolean $withprefix whether to prefix the name of the context with the
5357 * type of context, e.g. User, Course, Forum, etc.
5358 * @param boolean $short whether to use the short name of the thing. Only applies
5359 * to course contexts
5360 * @return string the human readable context name.
5362 public function get_context_name($withprefix = true, $short = false) {
5363 // must be implemented in all context levels
5364 throw new coding_exception('can not get name of abstract context');
5368 * Returns the most relevant URL for this context.
5370 * @return moodle_url
5372 public abstract function get_url();
5375 * Returns array of relevant context capability records.
5377 * @return array
5379 public abstract function get_capabilities();
5382 * Recursive function which, given a context, find all its children context ids.
5384 * For course category contexts it will return immediate children and all subcategory contexts.
5385 * It will NOT recurse into courses or subcategories categories.
5386 * If you want to do that, call it on the returned courses/categories.
5388 * When called for a course context, it will return the modules and blocks
5389 * displayed in the course page and blocks displayed on the module pages.
5391 * If called on a user/course/module context it _will_ populate the cache with the appropriate
5392 * contexts ;-)
5394 * @return array Array of child records
5396 public function get_child_contexts() {
5397 global $DB;
5399 $sql = "SELECT ctx.*
5400 FROM {context} ctx
5401 WHERE ctx.path LIKE ?";
5402 $params = array($this->_path.'/%');
5403 $records = $DB->get_records_sql($sql, $params);
5405 $result = array();
5406 foreach ($records as $record) {
5407 $result[$record->id] = context::create_instance_from_record($record);
5410 return $result;
5414 * Returns parent contexts of this context in reversed order, i.e. parent first,
5415 * then grand parent, etc.
5417 * @param bool $includeself tre means include self too
5418 * @return array of context instances
5420 public function get_parent_contexts($includeself = false) {
5421 if (!$contextids = $this->get_parent_context_ids($includeself)) {
5422 return array();
5425 $result = array();
5426 foreach ($contextids as $contextid) {
5427 $parent = context::instance_by_id($contextid, MUST_EXIST);
5428 $result[$parent->id] = $parent;
5431 return $result;
5435 * Returns parent contexts of this context in reversed order, i.e. parent first,
5436 * then grand parent, etc.
5438 * @param bool $includeself tre means include self too
5439 * @return array of context ids
5441 public function get_parent_context_ids($includeself = false) {
5442 if (empty($this->_path)) {
5443 return array();
5446 $parentcontexts = trim($this->_path, '/'); // kill leading slash
5447 $parentcontexts = explode('/', $parentcontexts);
5448 if (!$includeself) {
5449 array_pop($parentcontexts); // and remove its own id
5452 return array_reverse($parentcontexts);
5456 * Returns parent context
5458 * @return context
5460 public function get_parent_context() {
5461 if (empty($this->_path) or $this->_id == SYSCONTEXTID) {
5462 return false;
5465 $parentcontexts = trim($this->_path, '/'); // kill leading slash
5466 $parentcontexts = explode('/', $parentcontexts);
5467 array_pop($parentcontexts); // self
5468 $contextid = array_pop($parentcontexts); // immediate parent
5470 return context::instance_by_id($contextid, MUST_EXIST);
5474 * Is this context part of any course? If yes return course context.
5476 * @param bool $strict true means throw exception if not found, false means return false if not found
5477 * @return course_context context of the enclosing course, null if not found or exception
5479 public function get_course_context($strict = true) {
5480 if ($strict) {
5481 throw new coding_exception('Context does not belong to any course.');
5482 } else {
5483 return false;
5488 * Returns sql necessary for purging of stale context instances.
5490 * @static
5491 * @return string cleanup SQL
5493 protected static function get_cleanup_sql() {
5494 throw new coding_exception('get_cleanup_sql() method must be implemented in all context levels');
5498 * Rebuild context paths and depths at context level.
5500 * @static
5501 * @param bool $force
5502 * @return void
5504 protected static function build_paths($force) {
5505 throw new coding_exception('build_paths() method must be implemented in all context levels');
5509 * Create missing context instances at given level
5511 * @static
5512 * @return void
5514 protected static function create_level_instances() {
5515 throw new coding_exception('create_level_instances() method must be implemented in all context levels');
5519 * Reset all cached permissions and definitions if the necessary.
5520 * @return void
5522 public function reload_if_dirty() {
5523 global $ACCESSLIB_PRIVATE, $USER;
5525 // Load dirty contexts list if needed
5526 if (CLI_SCRIPT) {
5527 if (!isset($ACCESSLIB_PRIVATE->dirtycontexts)) {
5528 // we do not load dirty flags in CLI and cron
5529 $ACCESSLIB_PRIVATE->dirtycontexts = array();
5531 } else {
5532 if (!isset($ACCESSLIB_PRIVATE->dirtycontexts)) {
5533 if (!isset($USER->access['time'])) {
5534 // nothing was loaded yet, we do not need to check dirty contexts now
5535 return;
5537 // no idea why -2 is there, server cluster time difference maybe... (skodak)
5538 $ACCESSLIB_PRIVATE->dirtycontexts = get_cache_flags('accesslib/dirtycontexts', $USER->access['time']-2);
5542 foreach ($ACCESSLIB_PRIVATE->dirtycontexts as $path=>$unused) {
5543 if ($path === $this->_path or strpos($this->_path, $path.'/') === 0) {
5544 // reload all capabilities of USER and others - preserving loginas, roleswitches, etc
5545 // and then cleanup any marks of dirtyness... at least from our short term memory! :-)
5546 reload_all_capabilities();
5547 break;
5553 * Mark a context as dirty (with timestamp) so as to force reloading of the context.
5555 public function mark_dirty() {
5556 global $CFG, $USER, $ACCESSLIB_PRIVATE;
5558 if (during_initial_install()) {
5559 return;
5562 // only if it is a non-empty string
5563 if (is_string($this->_path) && $this->_path !== '') {
5564 set_cache_flag('accesslib/dirtycontexts', $this->_path, 1, time()+$CFG->sessiontimeout);
5565 if (isset($ACCESSLIB_PRIVATE->dirtycontexts)) {
5566 $ACCESSLIB_PRIVATE->dirtycontexts[$this->_path] = 1;
5567 } else {
5568 if (CLI_SCRIPT) {
5569 $ACCESSLIB_PRIVATE->dirtycontexts = array($this->_path => 1);
5570 } else {
5571 if (isset($USER->access['time'])) {
5572 $ACCESSLIB_PRIVATE->dirtycontexts = get_cache_flags('accesslib/dirtycontexts', $USER->access['time']-2);
5573 } else {
5574 $ACCESSLIB_PRIVATE->dirtycontexts = array($this->_path => 1);
5576 // flags not loaded yet, it will be done later in $context->reload_if_dirty()
5585 * Context maintenance and helper methods.
5587 * This is "extends context" is a bloody hack that tires to work around the deficiencies
5588 * in the "protected" keyword in PHP, this helps us to hide all the internals of context
5589 * level implementation from the rest of code, the code completion returns what developers need.
5591 * Thank you Tim Hunt for helping me with this nasty trick.
5593 * @package core_access
5594 * @category access
5595 * @copyright Petr Skoda {@link http://skodak.org}
5596 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5597 * @since 2.2
5599 class context_helper extends context {
5602 * @var array An array mapping context levels to classes
5604 private static $alllevels = array(
5605 CONTEXT_SYSTEM => 'context_system',
5606 CONTEXT_USER => 'context_user',
5607 CONTEXT_COURSECAT => 'context_coursecat',
5608 CONTEXT_COURSE => 'context_course',
5609 CONTEXT_MODULE => 'context_module',
5610 CONTEXT_BLOCK => 'context_block',
5614 * Instance does not make sense here, only static use
5616 protected function __construct() {
5620 * Returns a class name of the context level class
5622 * @static
5623 * @param int $contextlevel (CONTEXT_SYSTEM, etc.)
5624 * @return string class name of the context class
5626 public static function get_class_for_level($contextlevel) {
5627 if (isset(self::$alllevels[$contextlevel])) {
5628 return self::$alllevels[$contextlevel];
5629 } else {
5630 throw new coding_exception('Invalid context level specified');
5635 * Returns a list of all context levels
5637 * @static
5638 * @return array int=>string (level=>level class name)
5640 public static function get_all_levels() {
5641 return self::$alllevels;
5645 * Remove stale contexts that belonged to deleted instances.
5646 * Ideally all code should cleanup contexts properly, unfortunately accidents happen...
5648 * @static
5649 * @return void
5651 public static function cleanup_instances() {
5652 global $DB;
5653 $sqls = array();
5654 foreach (self::$alllevels as $level=>$classname) {
5655 $sqls[] = $classname::get_cleanup_sql();
5658 $sql = implode(" UNION ", $sqls);
5660 // it is probably better to use transactions, it might be faster too
5661 $transaction = $DB->start_delegated_transaction();
5663 $rs = $DB->get_recordset_sql($sql);
5664 foreach ($rs as $record) {
5665 $context = context::create_instance_from_record($record);
5666 $context->delete();
5668 $rs->close();
5670 $transaction->allow_commit();
5674 * Create all context instances at the given level and above.
5676 * @static
5677 * @param int $contextlevel null means all levels
5678 * @param bool $buildpaths
5679 * @return void
5681 public static function create_instances($contextlevel = null, $buildpaths = true) {
5682 foreach (self::$alllevels as $level=>$classname) {
5683 if ($contextlevel and $level > $contextlevel) {
5684 // skip potential sub-contexts
5685 continue;
5687 $classname::create_level_instances();
5688 if ($buildpaths) {
5689 $classname::build_paths(false);
5695 * Rebuild paths and depths in all context levels.
5697 * @static
5698 * @param bool $force false means add missing only
5699 * @return void
5701 public static function build_all_paths($force = false) {
5702 foreach (self::$alllevels as $classname) {
5703 $classname::build_paths($force);
5706 // reset static course cache - it might have incorrect cached data
5707 accesslib_clear_all_caches(true);
5711 * Resets the cache to remove all data.
5712 * @static
5714 public static function reset_caches() {
5715 context::reset_caches();
5719 * Returns all fields necessary for context preloading from user $rec.
5721 * This helps with performance when dealing with hundreds of contexts.
5723 * @static
5724 * @param string $tablealias context table alias in the query
5725 * @return array (table.column=>alias, ...)
5727 public static function get_preload_record_columns($tablealias) {
5728 return array("$tablealias.id"=>"ctxid", "$tablealias.path"=>"ctxpath", "$tablealias.depth"=>"ctxdepth", "$tablealias.contextlevel"=>"ctxlevel", "$tablealias.instanceid"=>"ctxinstance");
5732 * Returns all fields necessary for context preloading from user $rec.
5734 * This helps with performance when dealing with hundreds of contexts.
5736 * @static
5737 * @param string $tablealias context table alias in the query
5738 * @return string
5740 public static function get_preload_record_columns_sql($tablealias) {
5741 return "$tablealias.id AS ctxid, $tablealias.path AS ctxpath, $tablealias.depth AS ctxdepth, $tablealias.contextlevel AS ctxlevel, $tablealias.instanceid AS ctxinstance";
5745 * Preloads context information from db record and strips the cached info.
5747 * The db request has to contain all columns from context_helper::get_preload_record_columns().
5749 * @static
5750 * @param stdClass $rec
5751 * @return void (modifies $rec)
5753 public static function preload_from_record(stdClass $rec) {
5754 context::preload_from_record($rec);
5758 * Preload all contexts instances from course.
5760 * To be used if you expect multiple queries for course activities...
5762 * @static
5763 * @param int $courseid
5765 public static function preload_course($courseid) {
5766 // Users can call this multiple times without doing any harm
5767 if (isset(context::$cache_preloaded[$courseid])) {
5768 return;
5770 $coursecontext = context_course::instance($courseid);
5771 $coursecontext->get_child_contexts();
5773 context::$cache_preloaded[$courseid] = true;
5777 * Delete context instance
5779 * @static
5780 * @param int $contextlevel
5781 * @param int $instanceid
5782 * @return void
5784 public static function delete_instance($contextlevel, $instanceid) {
5785 global $DB;
5787 // double check the context still exists
5788 if ($record = $DB->get_record('context', array('contextlevel'=>$contextlevel, 'instanceid'=>$instanceid))) {
5789 $context = context::create_instance_from_record($record);
5790 $context->delete();
5791 } else {
5792 // we should try to purge the cache anyway
5797 * Returns the name of specified context level
5799 * @static
5800 * @param int $contextlevel
5801 * @return string name of the context level
5803 public static function get_level_name($contextlevel) {
5804 $classname = context_helper::get_class_for_level($contextlevel);
5805 return $classname::get_level_name();
5809 * not used
5811 public function get_url() {
5815 * not used
5817 public function get_capabilities() {
5823 * System context class
5825 * @package core_access
5826 * @category access
5827 * @copyright Petr Skoda {@link http://skodak.org}
5828 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5829 * @since 2.2
5831 class context_system extends context {
5833 * Please use context_system::instance() if you need the instance of context.
5835 * @param stdClass $record
5837 protected function __construct(stdClass $record) {
5838 parent::__construct($record);
5839 if ($record->contextlevel != CONTEXT_SYSTEM) {
5840 throw new coding_exception('Invalid $record->contextlevel in context_system constructor.');
5845 * Returns human readable context level name.
5847 * @static
5848 * @return string the human readable context level name.
5850 public static function get_level_name() {
5851 return get_string('coresystem');
5855 * Returns human readable context identifier.
5857 * @param boolean $withprefix does not apply to system context
5858 * @param boolean $short does not apply to system context
5859 * @return string the human readable context name.
5861 public function get_context_name($withprefix = true, $short = false) {
5862 return self::get_level_name();
5866 * Returns the most relevant URL for this context.
5868 * @return moodle_url
5870 public function get_url() {
5871 return new moodle_url('/');
5875 * Returns array of relevant context capability records.
5877 * @return array
5879 public function get_capabilities() {
5880 global $DB;
5882 $sort = 'ORDER BY contextlevel,component,name'; // To group them sensibly for display
5884 $params = array();
5885 $sql = "SELECT *
5886 FROM {capabilities}";
5888 return $DB->get_records_sql($sql.' '.$sort, $params);
5892 * Create missing context instances at system context
5893 * @static
5895 protected static function create_level_instances() {
5896 // nothing to do here, the system context is created automatically in installer
5897 self::instance(0);
5901 * Returns system context instance.
5903 * @static
5904 * @param int $instanceid
5905 * @param int $strictness
5906 * @param bool $cache
5907 * @return context_system context instance
5909 public static function instance($instanceid = 0, $strictness = MUST_EXIST, $cache = true) {
5910 global $DB;
5912 if ($instanceid != 0) {
5913 debugging('context_system::instance(): invalid $id parameter detected, should be 0');
5916 if (defined('SYSCONTEXTID') and $cache) { // dangerous: define this in config.php to eliminate 1 query/page
5917 if (!isset(context::$systemcontext)) {
5918 $record = new stdClass();
5919 $record->id = SYSCONTEXTID;
5920 $record->contextlevel = CONTEXT_SYSTEM;
5921 $record->instanceid = 0;
5922 $record->path = '/'.SYSCONTEXTID;
5923 $record->depth = 1;
5924 context::$systemcontext = new context_system($record);
5926 return context::$systemcontext;
5930 try {
5931 // we ignore the strictness completely because system context must except except during install
5932 $record = $DB->get_record('context', array('contextlevel'=>CONTEXT_SYSTEM), '*', MUST_EXIST);
5933 } catch (dml_exception $e) {
5934 //table or record does not exist
5935 if (!during_initial_install()) {
5936 // do not mess with system context after install, it simply must exist
5937 throw $e;
5939 $record = null;
5942 if (!$record) {
5943 $record = new stdClass();
5944 $record->contextlevel = CONTEXT_SYSTEM;
5945 $record->instanceid = 0;
5946 $record->depth = 1;
5947 $record->path = null; //not known before insert
5949 try {
5950 if ($DB->count_records('context')) {
5951 // contexts already exist, this is very weird, system must be first!!!
5952 return null;
5954 if (defined('SYSCONTEXTID')) {
5955 // this would happen only in unittest on sites that went through weird 1.7 upgrade
5956 $record->id = SYSCONTEXTID;
5957 $DB->import_record('context', $record);
5958 $DB->get_manager()->reset_sequence('context');
5959 } else {
5960 $record->id = $DB->insert_record('context', $record);
5962 } catch (dml_exception $e) {
5963 // can not create context - table does not exist yet, sorry
5964 return null;
5968 if ($record->instanceid != 0) {
5969 // this is very weird, somebody must be messing with context table
5970 debugging('Invalid system context detected');
5973 if ($record->depth != 1 or $record->path != '/'.$record->id) {
5974 // fix path if necessary, initial install or path reset
5975 $record->depth = 1;
5976 $record->path = '/'.$record->id;
5977 $DB->update_record('context', $record);
5980 if (!defined('SYSCONTEXTID')) {
5981 define('SYSCONTEXTID', $record->id);
5984 context::$systemcontext = new context_system($record);
5985 return context::$systemcontext;
5989 * Returns all site contexts except the system context, DO NOT call on production servers!!
5991 * Contexts are not cached.
5993 * @return array
5995 public function get_child_contexts() {
5996 global $DB;
5998 debugging('Fetching of system context child courses is strongly discouraged on production servers (it may eat all available memory)!');
6000 // Just get all the contexts except for CONTEXT_SYSTEM level
6001 // and hope we don't OOM in the process - don't cache
6002 $sql = "SELECT c.*
6003 FROM {context} c
6004 WHERE contextlevel > ".CONTEXT_SYSTEM;
6005 $records = $DB->get_records_sql($sql);
6007 $result = array();
6008 foreach ($records as $record) {
6009 $result[$record->id] = context::create_instance_from_record($record);
6012 return $result;
6016 * Returns sql necessary for purging of stale context instances.
6018 * @static
6019 * @return string cleanup SQL
6021 protected static function get_cleanup_sql() {
6022 $sql = "
6023 SELECT c.*
6024 FROM {context} c
6025 WHERE 1=2
6028 return $sql;
6032 * Rebuild context paths and depths at system context level.
6034 * @static
6035 * @param bool $force
6037 protected static function build_paths($force) {
6038 global $DB;
6040 /* note: ignore $force here, we always do full test of system context */
6042 // exactly one record must exist
6043 $record = $DB->get_record('context', array('contextlevel'=>CONTEXT_SYSTEM), '*', MUST_EXIST);
6045 if ($record->instanceid != 0) {
6046 debugging('Invalid system context detected');
6049 if (defined('SYSCONTEXTID') and $record->id != SYSCONTEXTID) {
6050 debugging('Invalid SYSCONTEXTID detected');
6053 if ($record->depth != 1 or $record->path != '/'.$record->id) {
6054 // fix path if necessary, initial install or path reset
6055 $record->depth = 1;
6056 $record->path = '/'.$record->id;
6057 $DB->update_record('context', $record);
6064 * User context class
6066 * @package core_access
6067 * @category access
6068 * @copyright Petr Skoda {@link http://skodak.org}
6069 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6070 * @since 2.2
6072 class context_user extends context {
6074 * Please use context_user::instance($userid) if you need the instance of context.
6075 * Alternatively if you know only the context id use context::instance_by_id($contextid)
6077 * @param stdClass $record
6079 protected function __construct(stdClass $record) {
6080 parent::__construct($record);
6081 if ($record->contextlevel != CONTEXT_USER) {
6082 throw new coding_exception('Invalid $record->contextlevel in context_user constructor.');
6087 * Returns human readable context level name.
6089 * @static
6090 * @return string the human readable context level name.
6092 public static function get_level_name() {
6093 return get_string('user');
6097 * Returns human readable context identifier.
6099 * @param boolean $withprefix whether to prefix the name of the context with User
6100 * @param boolean $short does not apply to user context
6101 * @return string the human readable context name.
6103 public function get_context_name($withprefix = true, $short = false) {
6104 global $DB;
6106 $name = '';
6107 if ($user = $DB->get_record('user', array('id'=>$this->_instanceid, 'deleted'=>0))) {
6108 if ($withprefix){
6109 $name = get_string('user').': ';
6111 $name .= fullname($user);
6113 return $name;
6117 * Returns the most relevant URL for this context.
6119 * @return moodle_url
6121 public function get_url() {
6122 global $COURSE;
6124 if ($COURSE->id == SITEID) {
6125 $url = new moodle_url('/user/profile.php', array('id'=>$this->_instanceid));
6126 } else {
6127 $url = new moodle_url('/user/view.php', array('id'=>$this->_instanceid, 'courseid'=>$COURSE->id));
6129 return $url;
6133 * Returns array of relevant context capability records.
6135 * @return array
6137 public function get_capabilities() {
6138 global $DB;
6140 $sort = 'ORDER BY contextlevel,component,name'; // To group them sensibly for display
6142 $extracaps = array('moodle/grade:viewall');
6143 list($extra, $params) = $DB->get_in_or_equal($extracaps, SQL_PARAMS_NAMED, 'cap');
6144 $sql = "SELECT *
6145 FROM {capabilities}
6146 WHERE contextlevel = ".CONTEXT_USER."
6147 OR name $extra";
6149 return $records = $DB->get_records_sql($sql.' '.$sort, $params);
6153 * Returns user context instance.
6155 * @static
6156 * @param int $instanceid
6157 * @param int $strictness
6158 * @return context_user context instance
6160 public static function instance($instanceid, $strictness = MUST_EXIST) {
6161 global $DB;
6163 if ($context = context::cache_get(CONTEXT_USER, $instanceid)) {
6164 return $context;
6167 if (!$record = $DB->get_record('context', array('contextlevel'=>CONTEXT_USER, 'instanceid'=>$instanceid))) {
6168 if ($user = $DB->get_record('user', array('id'=>$instanceid, 'deleted'=>0), 'id', $strictness)) {
6169 $record = context::insert_context_record(CONTEXT_USER, $user->id, '/'.SYSCONTEXTID, 0);
6173 if ($record) {
6174 $context = new context_user($record);
6175 context::cache_add($context);
6176 return $context;
6179 return false;
6183 * Create missing context instances at user context level
6184 * @static
6186 protected static function create_level_instances() {
6187 global $DB;
6189 $sql = "INSERT INTO {context} (contextlevel, instanceid)
6190 SELECT ".CONTEXT_USER.", u.id
6191 FROM {user} u
6192 WHERE u.deleted = 0
6193 AND NOT EXISTS (SELECT 'x'
6194 FROM {context} cx
6195 WHERE u.id = cx.instanceid AND cx.contextlevel=".CONTEXT_USER.")";
6196 $DB->execute($sql);
6200 * Returns sql necessary for purging of stale context instances.
6202 * @static
6203 * @return string cleanup SQL
6205 protected static function get_cleanup_sql() {
6206 $sql = "
6207 SELECT c.*
6208 FROM {context} c
6209 LEFT OUTER JOIN {user} u ON (c.instanceid = u.id AND u.deleted = 0)
6210 WHERE u.id IS NULL AND c.contextlevel = ".CONTEXT_USER."
6213 return $sql;
6217 * Rebuild context paths and depths at user context level.
6219 * @static
6220 * @param bool $force
6222 protected static function build_paths($force) {
6223 global $DB;
6225 // First update normal users.
6226 $path = $DB->sql_concat('?', 'id');
6227 $pathstart = '/' . SYSCONTEXTID . '/';
6228 $params = array($pathstart);
6230 if ($force) {
6231 $where = "depth <> 2 OR path IS NULL OR path <> ({$path})";
6232 $params[] = $pathstart;
6233 } else {
6234 $where = "depth = 0 OR path IS NULL";
6237 $sql = "UPDATE {context}
6238 SET depth = 2,
6239 path = {$path}
6240 WHERE contextlevel = " . CONTEXT_USER . "
6241 AND ($where)";
6242 $DB->execute($sql, $params);
6248 * Course category context class
6250 * @package core_access
6251 * @category access
6252 * @copyright Petr Skoda {@link http://skodak.org}
6253 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6254 * @since 2.2
6256 class context_coursecat extends context {
6258 * Please use context_coursecat::instance($coursecatid) if you need the instance of context.
6259 * Alternatively if you know only the context id use context::instance_by_id($contextid)
6261 * @param stdClass $record
6263 protected function __construct(stdClass $record) {
6264 parent::__construct($record);
6265 if ($record->contextlevel != CONTEXT_COURSECAT) {
6266 throw new coding_exception('Invalid $record->contextlevel in context_coursecat constructor.');
6271 * Returns human readable context level name.
6273 * @static
6274 * @return string the human readable context level name.
6276 public static function get_level_name() {
6277 return get_string('category');
6281 * Returns human readable context identifier.
6283 * @param boolean $withprefix whether to prefix the name of the context with Category
6284 * @param boolean $short does not apply to course categories
6285 * @return string the human readable context name.
6287 public function get_context_name($withprefix = true, $short = false) {
6288 global $DB;
6290 $name = '';
6291 if ($category = $DB->get_record('course_categories', array('id'=>$this->_instanceid))) {
6292 if ($withprefix){
6293 $name = get_string('category').': ';
6295 $name .= format_string($category->name, true, array('context' => $this));
6297 return $name;
6301 * Returns the most relevant URL for this context.
6303 * @return moodle_url
6305 public function get_url() {
6306 return new moodle_url('/course/index.php', array('categoryid' => $this->_instanceid));
6310 * Returns array of relevant context capability records.
6312 * @return array
6314 public function get_capabilities() {
6315 global $DB;
6317 $sort = 'ORDER BY contextlevel,component,name'; // To group them sensibly for display
6319 $params = array();
6320 $sql = "SELECT *
6321 FROM {capabilities}
6322 WHERE contextlevel IN (".CONTEXT_COURSECAT.",".CONTEXT_COURSE.",".CONTEXT_MODULE.",".CONTEXT_BLOCK.")";
6324 return $DB->get_records_sql($sql.' '.$sort, $params);
6328 * Returns course category context instance.
6330 * @static
6331 * @param int $instanceid
6332 * @param int $strictness
6333 * @return context_coursecat context instance
6335 public static function instance($instanceid, $strictness = MUST_EXIST) {
6336 global $DB;
6338 if ($context = context::cache_get(CONTEXT_COURSECAT, $instanceid)) {
6339 return $context;
6342 if (!$record = $DB->get_record('context', array('contextlevel'=>CONTEXT_COURSECAT, 'instanceid'=>$instanceid))) {
6343 if ($category = $DB->get_record('course_categories', array('id'=>$instanceid), 'id,parent', $strictness)) {
6344 if ($category->parent) {
6345 $parentcontext = context_coursecat::instance($category->parent);
6346 $record = context::insert_context_record(CONTEXT_COURSECAT, $category->id, $parentcontext->path);
6347 } else {
6348 $record = context::insert_context_record(CONTEXT_COURSECAT, $category->id, '/'.SYSCONTEXTID, 0);
6353 if ($record) {
6354 $context = new context_coursecat($record);
6355 context::cache_add($context);
6356 return $context;
6359 return false;
6363 * Returns immediate child contexts of category and all subcategories,
6364 * children of subcategories and courses are not returned.
6366 * @return array
6368 public function get_child_contexts() {
6369 global $DB;
6371 $sql = "SELECT ctx.*
6372 FROM {context} ctx
6373 WHERE ctx.path LIKE ? AND (ctx.depth = ? OR ctx.contextlevel = ?)";
6374 $params = array($this->_path.'/%', $this->depth+1, CONTEXT_COURSECAT);
6375 $records = $DB->get_records_sql($sql, $params);
6377 $result = array();
6378 foreach ($records as $record) {
6379 $result[$record->id] = context::create_instance_from_record($record);
6382 return $result;
6386 * Create missing context instances at course category context level
6387 * @static
6389 protected static function create_level_instances() {
6390 global $DB;
6392 $sql = "INSERT INTO {context} (contextlevel, instanceid)
6393 SELECT ".CONTEXT_COURSECAT.", cc.id
6394 FROM {course_categories} cc
6395 WHERE NOT EXISTS (SELECT 'x'
6396 FROM {context} cx
6397 WHERE cc.id = cx.instanceid AND cx.contextlevel=".CONTEXT_COURSECAT.")";
6398 $DB->execute($sql);
6402 * Returns sql necessary for purging of stale context instances.
6404 * @static
6405 * @return string cleanup SQL
6407 protected static function get_cleanup_sql() {
6408 $sql = "
6409 SELECT c.*
6410 FROM {context} c
6411 LEFT OUTER JOIN {course_categories} cc ON c.instanceid = cc.id
6412 WHERE cc.id IS NULL AND c.contextlevel = ".CONTEXT_COURSECAT."
6415 return $sql;
6419 * Rebuild context paths and depths at course category context level.
6421 * @static
6422 * @param bool $force
6424 protected static function build_paths($force) {
6425 global $DB;
6427 if ($force or $DB->record_exists_select('context', "contextlevel = ".CONTEXT_COURSECAT." AND (depth = 0 OR path IS NULL)")) {
6428 if ($force) {
6429 $ctxemptyclause = $emptyclause = '';
6430 } else {
6431 $ctxemptyclause = "AND (ctx.path IS NULL OR ctx.depth = 0)";
6432 $emptyclause = "AND ({context}.path IS NULL OR {context}.depth = 0)";
6435 $base = '/'.SYSCONTEXTID;
6437 // Normal top level categories
6438 $sql = "UPDATE {context}
6439 SET depth=2,
6440 path=".$DB->sql_concat("'$base/'", 'id')."
6441 WHERE contextlevel=".CONTEXT_COURSECAT."
6442 AND EXISTS (SELECT 'x'
6443 FROM {course_categories} cc
6444 WHERE cc.id = {context}.instanceid AND cc.depth=1)
6445 $emptyclause";
6446 $DB->execute($sql);
6448 // Deeper categories - one query per depthlevel
6449 $maxdepth = $DB->get_field_sql("SELECT MAX(depth) FROM {course_categories}");
6450 for ($n=2; $n<=$maxdepth; $n++) {
6451 $sql = "INSERT INTO {context_temp} (id, path, depth)
6452 SELECT ctx.id, ".$DB->sql_concat('pctx.path', "'/'", 'ctx.id').", pctx.depth+1
6453 FROM {context} ctx
6454 JOIN {course_categories} cc ON (cc.id = ctx.instanceid AND ctx.contextlevel = ".CONTEXT_COURSECAT." AND cc.depth = $n)
6455 JOIN {context} pctx ON (pctx.instanceid = cc.parent AND pctx.contextlevel = ".CONTEXT_COURSECAT.")
6456 WHERE pctx.path IS NOT NULL AND pctx.depth > 0
6457 $ctxemptyclause";
6458 $trans = $DB->start_delegated_transaction();
6459 $DB->delete_records('context_temp');
6460 $DB->execute($sql);
6461 context::merge_context_temp_table();
6462 $DB->delete_records('context_temp');
6463 $trans->allow_commit();
6472 * Course context class
6474 * @package core_access
6475 * @category access
6476 * @copyright Petr Skoda {@link http://skodak.org}
6477 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6478 * @since 2.2
6480 class context_course extends context {
6482 * Please use context_course::instance($courseid) if you need the instance of context.
6483 * Alternatively if you know only the context id use context::instance_by_id($contextid)
6485 * @param stdClass $record
6487 protected function __construct(stdClass $record) {
6488 parent::__construct($record);
6489 if ($record->contextlevel != CONTEXT_COURSE) {
6490 throw new coding_exception('Invalid $record->contextlevel in context_course constructor.');
6495 * Returns human readable context level name.
6497 * @static
6498 * @return string the human readable context level name.
6500 public static function get_level_name() {
6501 return get_string('course');
6505 * Returns human readable context identifier.
6507 * @param boolean $withprefix whether to prefix the name of the context with Course
6508 * @param boolean $short whether to use the short name of the thing.
6509 * @return string the human readable context name.
6511 public function get_context_name($withprefix = true, $short = false) {
6512 global $DB;
6514 $name = '';
6515 if ($this->_instanceid == SITEID) {
6516 $name = get_string('frontpage', 'admin');
6517 } else {
6518 if ($course = $DB->get_record('course', array('id'=>$this->_instanceid))) {
6519 if ($withprefix){
6520 $name = get_string('course').': ';
6522 if ($short){
6523 $name .= format_string($course->shortname, true, array('context' => $this));
6524 } else {
6525 $name .= format_string(get_course_display_name_for_list($course));
6529 return $name;
6533 * Returns the most relevant URL for this context.
6535 * @return moodle_url
6537 public function get_url() {
6538 if ($this->_instanceid != SITEID) {
6539 return new moodle_url('/course/view.php', array('id'=>$this->_instanceid));
6542 return new moodle_url('/');
6546 * Returns array of relevant context capability records.
6548 * @return array
6550 public function get_capabilities() {
6551 global $DB;
6553 $sort = 'ORDER BY contextlevel,component,name'; // To group them sensibly for display
6555 $params = array();
6556 $sql = "SELECT *
6557 FROM {capabilities}
6558 WHERE contextlevel IN (".CONTEXT_COURSE.",".CONTEXT_MODULE.",".CONTEXT_BLOCK.")";
6560 return $DB->get_records_sql($sql.' '.$sort, $params);
6564 * Is this context part of any course? If yes return course context.
6566 * @param bool $strict true means throw exception if not found, false means return false if not found
6567 * @return course_context context of the enclosing course, null if not found or exception
6569 public function get_course_context($strict = true) {
6570 return $this;
6574 * Returns course context instance.
6576 * @static
6577 * @param int $instanceid
6578 * @param int $strictness
6579 * @return context_course context instance
6581 public static function instance($instanceid, $strictness = MUST_EXIST) {
6582 global $DB;
6584 if ($context = context::cache_get(CONTEXT_COURSE, $instanceid)) {
6585 return $context;
6588 if (!$record = $DB->get_record('context', array('contextlevel'=>CONTEXT_COURSE, 'instanceid'=>$instanceid))) {
6589 if ($course = $DB->get_record('course', array('id'=>$instanceid), 'id,category', $strictness)) {
6590 if ($course->category) {
6591 $parentcontext = context_coursecat::instance($course->category);
6592 $record = context::insert_context_record(CONTEXT_COURSE, $course->id, $parentcontext->path);
6593 } else {
6594 $record = context::insert_context_record(CONTEXT_COURSE, $course->id, '/'.SYSCONTEXTID, 0);
6599 if ($record) {
6600 $context = new context_course($record);
6601 context::cache_add($context);
6602 return $context;
6605 return false;
6609 * Create missing context instances at course context level
6610 * @static
6612 protected static function create_level_instances() {
6613 global $DB;
6615 $sql = "INSERT INTO {context} (contextlevel, instanceid)
6616 SELECT ".CONTEXT_COURSE.", c.id
6617 FROM {course} c
6618 WHERE NOT EXISTS (SELECT 'x'
6619 FROM {context} cx
6620 WHERE c.id = cx.instanceid AND cx.contextlevel=".CONTEXT_COURSE.")";
6621 $DB->execute($sql);
6625 * Returns sql necessary for purging of stale context instances.
6627 * @static
6628 * @return string cleanup SQL
6630 protected static function get_cleanup_sql() {
6631 $sql = "
6632 SELECT c.*
6633 FROM {context} c
6634 LEFT OUTER JOIN {course} co ON c.instanceid = co.id
6635 WHERE co.id IS NULL AND c.contextlevel = ".CONTEXT_COURSE."
6638 return $sql;
6642 * Rebuild context paths and depths at course context level.
6644 * @static
6645 * @param bool $force
6647 protected static function build_paths($force) {
6648 global $DB;
6650 if ($force or $DB->record_exists_select('context', "contextlevel = ".CONTEXT_COURSE." AND (depth = 0 OR path IS NULL)")) {
6651 if ($force) {
6652 $ctxemptyclause = $emptyclause = '';
6653 } else {
6654 $ctxemptyclause = "AND (ctx.path IS NULL OR ctx.depth = 0)";
6655 $emptyclause = "AND ({context}.path IS NULL OR {context}.depth = 0)";
6658 $base = '/'.SYSCONTEXTID;
6660 // Standard frontpage
6661 $sql = "UPDATE {context}
6662 SET depth = 2,
6663 path = ".$DB->sql_concat("'$base/'", 'id')."
6664 WHERE contextlevel = ".CONTEXT_COURSE."
6665 AND EXISTS (SELECT 'x'
6666 FROM {course} c
6667 WHERE c.id = {context}.instanceid AND c.category = 0)
6668 $emptyclause";
6669 $DB->execute($sql);
6671 // standard courses
6672 $sql = "INSERT INTO {context_temp} (id, path, depth)
6673 SELECT ctx.id, ".$DB->sql_concat('pctx.path', "'/'", 'ctx.id').", pctx.depth+1
6674 FROM {context} ctx
6675 JOIN {course} c ON (c.id = ctx.instanceid AND ctx.contextlevel = ".CONTEXT_COURSE." AND c.category <> 0)
6676 JOIN {context} pctx ON (pctx.instanceid = c.category AND pctx.contextlevel = ".CONTEXT_COURSECAT.")
6677 WHERE pctx.path IS NOT NULL AND pctx.depth > 0
6678 $ctxemptyclause";
6679 $trans = $DB->start_delegated_transaction();
6680 $DB->delete_records('context_temp');
6681 $DB->execute($sql);
6682 context::merge_context_temp_table();
6683 $DB->delete_records('context_temp');
6684 $trans->allow_commit();
6691 * Course module context class
6693 * @package core_access
6694 * @category access
6695 * @copyright Petr Skoda {@link http://skodak.org}
6696 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6697 * @since 2.2
6699 class context_module extends context {
6701 * Please use context_module::instance($cmid) if you need the instance of context.
6702 * Alternatively if you know only the context id use context::instance_by_id($contextid)
6704 * @param stdClass $record
6706 protected function __construct(stdClass $record) {
6707 parent::__construct($record);
6708 if ($record->contextlevel != CONTEXT_MODULE) {
6709 throw new coding_exception('Invalid $record->contextlevel in context_module constructor.');
6714 * Returns human readable context level name.
6716 * @static
6717 * @return string the human readable context level name.
6719 public static function get_level_name() {
6720 return get_string('activitymodule');
6724 * Returns human readable context identifier.
6726 * @param boolean $withprefix whether to prefix the name of the context with the
6727 * module name, e.g. Forum, Glossary, etc.
6728 * @param boolean $short does not apply to module context
6729 * @return string the human readable context name.
6731 public function get_context_name($withprefix = true, $short = false) {
6732 global $DB;
6734 $name = '';
6735 if ($cm = $DB->get_record_sql("SELECT cm.*, md.name AS modname
6736 FROM {course_modules} cm
6737 JOIN {modules} md ON md.id = cm.module
6738 WHERE cm.id = ?", array($this->_instanceid))) {
6739 if ($mod = $DB->get_record($cm->modname, array('id' => $cm->instance))) {
6740 if ($withprefix){
6741 $name = get_string('modulename', $cm->modname).': ';
6743 $name .= format_string($mod->name, true, array('context' => $this));
6746 return $name;
6750 * Returns the most relevant URL for this context.
6752 * @return moodle_url
6754 public function get_url() {
6755 global $DB;
6757 if ($modname = $DB->get_field_sql("SELECT md.name AS modname
6758 FROM {course_modules} cm
6759 JOIN {modules} md ON md.id = cm.module
6760 WHERE cm.id = ?", array($this->_instanceid))) {
6761 return new moodle_url('/mod/' . $modname . '/view.php', array('id'=>$this->_instanceid));
6764 return new moodle_url('/');
6768 * Returns array of relevant context capability records.
6770 * @return array
6772 public function get_capabilities() {
6773 global $DB, $CFG;
6775 $sort = 'ORDER BY contextlevel,component,name'; // To group them sensibly for display
6777 $cm = $DB->get_record('course_modules', array('id'=>$this->_instanceid));
6778 $module = $DB->get_record('modules', array('id'=>$cm->module));
6780 $subcaps = array();
6781 $subpluginsfile = "$CFG->dirroot/mod/$module->name/db/subplugins.php";
6782 if (file_exists($subpluginsfile)) {
6783 $subplugins = array(); // should be redefined in the file
6784 include($subpluginsfile);
6785 if (!empty($subplugins)) {
6786 foreach (array_keys($subplugins) as $subplugintype) {
6787 foreach (array_keys(get_plugin_list($subplugintype)) as $subpluginname) {
6788 $subcaps = array_merge($subcaps, array_keys(load_capability_def($subplugintype.'_'.$subpluginname)));
6794 $modfile = "$CFG->dirroot/mod/$module->name/lib.php";
6795 $extracaps = array();
6796 if (file_exists($modfile)) {
6797 include_once($modfile);
6798 $modfunction = $module->name.'_get_extra_capabilities';
6799 if (function_exists($modfunction)) {
6800 $extracaps = $modfunction();
6804 $extracaps = array_merge($subcaps, $extracaps);
6805 $extra = '';
6806 list($extra, $params) = $DB->get_in_or_equal(
6807 $extracaps, SQL_PARAMS_NAMED, 'cap0', true, '');
6808 if (!empty($extra)) {
6809 $extra = "OR name $extra";
6811 $sql = "SELECT *
6812 FROM {capabilities}
6813 WHERE (contextlevel = ".CONTEXT_MODULE."
6814 AND (component = :component OR component = 'moodle'))
6815 $extra";
6816 $params['component'] = "mod_$module->name";
6818 return $DB->get_records_sql($sql.' '.$sort, $params);
6822 * Is this context part of any course? If yes return course context.
6824 * @param bool $strict true means throw exception if not found, false means return false if not found
6825 * @return course_context context of the enclosing course, null if not found or exception
6827 public function get_course_context($strict = true) {
6828 return $this->get_parent_context();
6832 * Returns module context instance.
6834 * @static
6835 * @param int $instanceid
6836 * @param int $strictness
6837 * @return context_module context instance
6839 public static function instance($instanceid, $strictness = MUST_EXIST) {
6840 global $DB;
6842 if ($context = context::cache_get(CONTEXT_MODULE, $instanceid)) {
6843 return $context;
6846 if (!$record = $DB->get_record('context', array('contextlevel'=>CONTEXT_MODULE, 'instanceid'=>$instanceid))) {
6847 if ($cm = $DB->get_record('course_modules', array('id'=>$instanceid), 'id,course', $strictness)) {
6848 $parentcontext = context_course::instance($cm->course);
6849 $record = context::insert_context_record(CONTEXT_MODULE, $cm->id, $parentcontext->path);
6853 if ($record) {
6854 $context = new context_module($record);
6855 context::cache_add($context);
6856 return $context;
6859 return false;
6863 * Create missing context instances at module context level
6864 * @static
6866 protected static function create_level_instances() {
6867 global $DB;
6869 $sql = "INSERT INTO {context} (contextlevel, instanceid)
6870 SELECT ".CONTEXT_MODULE.", cm.id
6871 FROM {course_modules} cm
6872 WHERE NOT EXISTS (SELECT 'x'
6873 FROM {context} cx
6874 WHERE cm.id = cx.instanceid AND cx.contextlevel=".CONTEXT_MODULE.")";
6875 $DB->execute($sql);
6879 * Returns sql necessary for purging of stale context instances.
6881 * @static
6882 * @return string cleanup SQL
6884 protected static function get_cleanup_sql() {
6885 $sql = "
6886 SELECT c.*
6887 FROM {context} c
6888 LEFT OUTER JOIN {course_modules} cm ON c.instanceid = cm.id
6889 WHERE cm.id IS NULL AND c.contextlevel = ".CONTEXT_MODULE."
6892 return $sql;
6896 * Rebuild context paths and depths at module context level.
6898 * @static
6899 * @param bool $force
6901 protected static function build_paths($force) {
6902 global $DB;
6904 if ($force or $DB->record_exists_select('context', "contextlevel = ".CONTEXT_MODULE." AND (depth = 0 OR path IS NULL)")) {
6905 if ($force) {
6906 $ctxemptyclause = '';
6907 } else {
6908 $ctxemptyclause = "AND (ctx.path IS NULL OR ctx.depth = 0)";
6911 $sql = "INSERT INTO {context_temp} (id, path, depth)
6912 SELECT ctx.id, ".$DB->sql_concat('pctx.path', "'/'", 'ctx.id').", pctx.depth+1
6913 FROM {context} ctx
6914 JOIN {course_modules} cm ON (cm.id = ctx.instanceid AND ctx.contextlevel = ".CONTEXT_MODULE.")
6915 JOIN {context} pctx ON (pctx.instanceid = cm.course AND pctx.contextlevel = ".CONTEXT_COURSE.")
6916 WHERE pctx.path IS NOT NULL AND pctx.depth > 0
6917 $ctxemptyclause";
6918 $trans = $DB->start_delegated_transaction();
6919 $DB->delete_records('context_temp');
6920 $DB->execute($sql);
6921 context::merge_context_temp_table();
6922 $DB->delete_records('context_temp');
6923 $trans->allow_commit();
6930 * Block context class
6932 * @package core_access
6933 * @category access
6934 * @copyright Petr Skoda {@link http://skodak.org}
6935 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6936 * @since 2.2
6938 class context_block extends context {
6940 * Please use context_block::instance($blockinstanceid) if you need the instance of context.
6941 * Alternatively if you know only the context id use context::instance_by_id($contextid)
6943 * @param stdClass $record
6945 protected function __construct(stdClass $record) {
6946 parent::__construct($record);
6947 if ($record->contextlevel != CONTEXT_BLOCK) {
6948 throw new coding_exception('Invalid $record->contextlevel in context_block constructor');
6953 * Returns human readable context level name.
6955 * @static
6956 * @return string the human readable context level name.
6958 public static function get_level_name() {
6959 return get_string('block');
6963 * Returns human readable context identifier.
6965 * @param boolean $withprefix whether to prefix the name of the context with Block
6966 * @param boolean $short does not apply to block context
6967 * @return string the human readable context name.
6969 public function get_context_name($withprefix = true, $short = false) {
6970 global $DB, $CFG;
6972 $name = '';
6973 if ($blockinstance = $DB->get_record('block_instances', array('id'=>$this->_instanceid))) {
6974 global $CFG;
6975 require_once("$CFG->dirroot/blocks/moodleblock.class.php");
6976 require_once("$CFG->dirroot/blocks/$blockinstance->blockname/block_$blockinstance->blockname.php");
6977 $blockname = "block_$blockinstance->blockname";
6978 if ($blockobject = new $blockname()) {
6979 if ($withprefix){
6980 $name = get_string('block').': ';
6982 $name .= $blockobject->title;
6986 return $name;
6990 * Returns the most relevant URL for this context.
6992 * @return moodle_url
6994 public function get_url() {
6995 $parentcontexts = $this->get_parent_context();
6996 return $parentcontexts->get_url();
7000 * Returns array of relevant context capability records.
7002 * @return array
7004 public function get_capabilities() {
7005 global $DB;
7007 $sort = 'ORDER BY contextlevel,component,name'; // To group them sensibly for display
7009 $params = array();
7010 $bi = $DB->get_record('block_instances', array('id' => $this->_instanceid));
7012 $extra = '';
7013 $extracaps = block_method_result($bi->blockname, 'get_extra_capabilities');
7014 if ($extracaps) {
7015 list($extra, $params) = $DB->get_in_or_equal($extracaps, SQL_PARAMS_NAMED, 'cap');
7016 $extra = "OR name $extra";
7019 $sql = "SELECT *
7020 FROM {capabilities}
7021 WHERE (contextlevel = ".CONTEXT_BLOCK."
7022 AND component = :component)
7023 $extra";
7024 $params['component'] = 'block_' . $bi->blockname;
7026 return $DB->get_records_sql($sql.' '.$sort, $params);
7030 * Is this context part of any course? If yes return course context.
7032 * @param bool $strict true means throw exception if not found, false means return false if not found
7033 * @return course_context context of the enclosing course, null if not found or exception
7035 public function get_course_context($strict = true) {
7036 $parentcontext = $this->get_parent_context();
7037 return $parentcontext->get_course_context($strict);
7041 * Returns block context instance.
7043 * @static
7044 * @param int $instanceid
7045 * @param int $strictness
7046 * @return context_block context instance
7048 public static function instance($instanceid, $strictness = MUST_EXIST) {
7049 global $DB;
7051 if ($context = context::cache_get(CONTEXT_BLOCK, $instanceid)) {
7052 return $context;
7055 if (!$record = $DB->get_record('context', array('contextlevel'=>CONTEXT_BLOCK, 'instanceid'=>$instanceid))) {
7056 if ($bi = $DB->get_record('block_instances', array('id'=>$instanceid), 'id,parentcontextid', $strictness)) {
7057 $parentcontext = context::instance_by_id($bi->parentcontextid);
7058 $record = context::insert_context_record(CONTEXT_BLOCK, $bi->id, $parentcontext->path);
7062 if ($record) {
7063 $context = new context_block($record);
7064 context::cache_add($context);
7065 return $context;
7068 return false;
7072 * Block do not have child contexts...
7073 * @return array
7075 public function get_child_contexts() {
7076 return array();
7080 * Create missing context instances at block context level
7081 * @static
7083 protected static function create_level_instances() {
7084 global $DB;
7086 $sql = "INSERT INTO {context} (contextlevel, instanceid)
7087 SELECT ".CONTEXT_BLOCK.", bi.id
7088 FROM {block_instances} bi
7089 WHERE NOT EXISTS (SELECT 'x'
7090 FROM {context} cx
7091 WHERE bi.id = cx.instanceid AND cx.contextlevel=".CONTEXT_BLOCK.")";
7092 $DB->execute($sql);
7096 * Returns sql necessary for purging of stale context instances.
7098 * @static
7099 * @return string cleanup SQL
7101 protected static function get_cleanup_sql() {
7102 $sql = "
7103 SELECT c.*
7104 FROM {context} c
7105 LEFT OUTER JOIN {block_instances} bi ON c.instanceid = bi.id
7106 WHERE bi.id IS NULL AND c.contextlevel = ".CONTEXT_BLOCK."
7109 return $sql;
7113 * Rebuild context paths and depths at block context level.
7115 * @static
7116 * @param bool $force
7118 protected static function build_paths($force) {
7119 global $DB;
7121 if ($force or $DB->record_exists_select('context', "contextlevel = ".CONTEXT_BLOCK." AND (depth = 0 OR path IS NULL)")) {
7122 if ($force) {
7123 $ctxemptyclause = '';
7124 } else {
7125 $ctxemptyclause = "AND (ctx.path IS NULL OR ctx.depth = 0)";
7128 // pctx.path IS NOT NULL prevents fatal problems with broken block instances that point to invalid context parent
7129 $sql = "INSERT INTO {context_temp} (id, path, depth)
7130 SELECT ctx.id, ".$DB->sql_concat('pctx.path', "'/'", 'ctx.id').", pctx.depth+1
7131 FROM {context} ctx
7132 JOIN {block_instances} bi ON (bi.id = ctx.instanceid AND ctx.contextlevel = ".CONTEXT_BLOCK.")
7133 JOIN {context} pctx ON (pctx.id = bi.parentcontextid)
7134 WHERE (pctx.path IS NOT NULL AND pctx.depth > 0)
7135 $ctxemptyclause";
7136 $trans = $DB->start_delegated_transaction();
7137 $DB->delete_records('context_temp');
7138 $DB->execute($sql);
7139 context::merge_context_temp_table();
7140 $DB->delete_records('context_temp');
7141 $trans->allow_commit();
7147 // ============== DEPRECATED FUNCTIONS ==========================================
7148 // Old context related functions were deprecated in 2.0, it is recommended
7149 // to use context classes in new code. Old function can be used when
7150 // creating patches that are supposed to be backported to older stable branches.
7151 // These deprecated functions will not be removed in near future,
7152 // before removing devs will be warned with a debugging message first,
7153 // then we will add error message and only after that we can remove the functions
7154 // completely.
7158 * Not available any more, use load_temp_course_role() instead.
7160 * @deprecated since 2.2
7161 * @param stdClass $context
7162 * @param int $roleid
7163 * @param array $accessdata
7164 * @return array
7166 function load_temp_role($context, $roleid, array $accessdata) {
7167 debugging('load_temp_role() is deprecated, please use load_temp_course_role() instead, temp role not loaded.');
7168 return $accessdata;
7172 * Not available any more, use remove_temp_course_roles() instead.
7174 * @deprecated since 2.2
7175 * @param stdClass $context
7176 * @param array $accessdata
7177 * @return array access data
7179 function remove_temp_roles($context, array $accessdata) {
7180 debugging('remove_temp_role() is deprecated, please use remove_temp_course_roles() instead.');
7181 return $accessdata;
7185 * Returns system context or null if can not be created yet.
7187 * @deprecated since 2.2, use context_system::instance()
7188 * @param bool $cache use caching
7189 * @return context system context (null if context table not created yet)
7191 function get_system_context($cache = true) {
7192 return context_system::instance(0, IGNORE_MISSING, $cache);
7196 * Get the context instance as an object. This function will create the
7197 * context instance if it does not exist yet.
7199 * @deprecated since 2.2, use context_course::instance() or other relevant class instead
7200 * @param integer $contextlevel The context level, for example CONTEXT_COURSE, or CONTEXT_MODULE.
7201 * @param integer $instance The instance id. For $level = CONTEXT_COURSE, this would be $course->id,
7202 * for $level = CONTEXT_MODULE, this would be $cm->id. And so on. Defaults to 0
7203 * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found;
7204 * MUST_EXIST means throw exception if no record or multiple records found
7205 * @return context The context object.
7207 function get_context_instance($contextlevel, $instance = 0, $strictness = IGNORE_MISSING) {
7208 $instances = (array)$instance;
7209 $contexts = array();
7211 $classname = context_helper::get_class_for_level($contextlevel);
7213 // we do not load multiple contexts any more, PAGE should be responsible for any preloading
7214 foreach ($instances as $inst) {
7215 $contexts[$inst] = $classname::instance($inst, $strictness);
7218 if (is_array($instance)) {
7219 return $contexts;
7220 } else {
7221 return $contexts[$instance];
7226 * Get a context instance as an object, from a given context id.
7228 * @deprecated since 2.2, use context::instance_by_id($id) instead
7229 * @param int $id context id
7230 * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found;
7231 * MUST_EXIST means throw exception if no record or multiple records found
7232 * @return context|bool the context object or false if not found.
7234 function get_context_instance_by_id($id, $strictness = IGNORE_MISSING) {
7235 return context::instance_by_id($id, $strictness);
7239 * Recursive function which, given a context, find all parent context ids,
7240 * and return the array in reverse order, i.e. parent first, then grand
7241 * parent, etc.
7243 * @deprecated since 2.2, use $context->get_parent_context_ids() instead
7244 * @param context $context
7245 * @param bool $includeself optional, defaults to false
7246 * @return array
7248 function get_parent_contexts(context $context, $includeself = false) {
7249 return $context->get_parent_context_ids($includeself);
7253 * Return the id of the parent of this context, or false if there is no parent (only happens if this
7254 * is the site context.)
7256 * @deprecated since 2.2, use $context->get_parent_context() instead
7257 * @param context $context
7258 * @return integer the id of the parent context.
7260 function get_parent_contextid(context $context) {
7261 if ($parent = $context->get_parent_context()) {
7262 return $parent->id;
7263 } else {
7264 return false;
7269 * Recursive function which, given a context, find all its children context ids.
7271 * For course category contexts it will return immediate children only categories and courses.
7272 * It will NOT recurse into courses or child categories.
7273 * If you want to do that, call it on the returned courses/categories.
7275 * When called for a course context, it will return the modules and blocks
7276 * displayed in the course page.
7278 * If called on a user/course/module context it _will_ populate the cache with the appropriate
7279 * contexts ;-)
7281 * @deprecated since 2.2, use $context->get_child_contexts() instead
7282 * @param context $context
7283 * @return array Array of child records
7285 function get_child_contexts(context $context) {
7286 return $context->get_child_contexts();
7290 * Precreates all contexts including all parents
7292 * @deprecated since 2.2
7293 * @param int $contextlevel empty means all
7294 * @param bool $buildpaths update paths and depths
7295 * @return void
7297 function create_contexts($contextlevel = null, $buildpaths = true) {
7298 context_helper::create_instances($contextlevel, $buildpaths);
7302 * Remove stale context records
7304 * @deprecated since 2.2, use context_helper::cleanup_instances() instead
7305 * @return bool
7307 function cleanup_contexts() {
7308 context_helper::cleanup_instances();
7309 return true;
7313 * Populate context.path and context.depth where missing.
7315 * @deprecated since 2.2, use context_helper::build_all_paths() instead
7316 * @param bool $force force a complete rebuild of the path and depth fields, defaults to false
7317 * @return void
7319 function build_context_path($force = false) {
7320 context_helper::build_all_paths($force);
7324 * Rebuild all related context depth and path caches
7326 * @deprecated since 2.2
7327 * @param array $fixcontexts array of contexts, strongtyped
7328 * @return void
7330 function rebuild_contexts(array $fixcontexts) {
7331 foreach ($fixcontexts as $fixcontext) {
7332 $fixcontext->reset_paths(false);
7334 context_helper::build_all_paths(false);
7338 * Preloads all contexts relating to a course: course, modules. Block contexts
7339 * are no longer loaded here. The contexts for all the blocks on the current
7340 * page are now efficiently loaded by {@link block_manager::load_blocks()}.
7342 * @deprecated since 2.2
7343 * @param int $courseid Course ID
7344 * @return void
7346 function preload_course_contexts($courseid) {
7347 context_helper::preload_course($courseid);
7351 * Preloads context information together with instances.
7352 * Use context_instance_preload() to strip the context info from the record and cache the context instance.
7354 * @deprecated since 2.2
7355 * @param string $joinon for example 'u.id'
7356 * @param string $contextlevel context level of instance in $joinon
7357 * @param string $tablealias context table alias
7358 * @return array with two values - select and join part
7360 function context_instance_preload_sql($joinon, $contextlevel, $tablealias) {
7361 $select = ", ".context_helper::get_preload_record_columns_sql($tablealias);
7362 $join = "LEFT JOIN {context} $tablealias ON ($tablealias.instanceid = $joinon AND $tablealias.contextlevel = $contextlevel)";
7363 return array($select, $join);
7367 * Preloads context information from db record and strips the cached info.
7368 * The db request has to contain both the $join and $select from context_instance_preload_sql()
7370 * @deprecated since 2.2
7371 * @param stdClass $rec
7372 * @return void (modifies $rec)
7374 function context_instance_preload(stdClass $rec) {
7375 context_helper::preload_from_record($rec);
7379 * Mark a context as dirty (with timestamp) so as to force reloading of the context.
7381 * @deprecated since 2.2, use $context->mark_dirty() instead
7382 * @param string $path context path
7384 function mark_context_dirty($path) {
7385 global $CFG, $USER, $ACCESSLIB_PRIVATE;
7387 if (during_initial_install()) {
7388 return;
7391 // only if it is a non-empty string
7392 if (is_string($path) && $path !== '') {
7393 set_cache_flag('accesslib/dirtycontexts', $path, 1, time()+$CFG->sessiontimeout);
7394 if (isset($ACCESSLIB_PRIVATE->dirtycontexts)) {
7395 $ACCESSLIB_PRIVATE->dirtycontexts[$path] = 1;
7396 } else {
7397 if (CLI_SCRIPT) {
7398 $ACCESSLIB_PRIVATE->dirtycontexts = array($path => 1);
7399 } else {
7400 if (isset($USER->access['time'])) {
7401 $ACCESSLIB_PRIVATE->dirtycontexts = get_cache_flags('accesslib/dirtycontexts', $USER->access['time']-2);
7402 } else {
7403 $ACCESSLIB_PRIVATE->dirtycontexts = array($path => 1);
7405 // flags not loaded yet, it will be done later in $context->reload_if_dirty()
7412 * Update the path field of the context and all dep. subcontexts that follow
7414 * Update the path field of the context and
7415 * all the dependent subcontexts that follow
7416 * the move.
7418 * The most important thing here is to be as
7419 * DB efficient as possible. This op can have a
7420 * massive impact in the DB.
7422 * @deprecated since 2.2
7423 * @param context $context context obj
7424 * @param context $newparent new parent obj
7425 * @return void
7427 function context_moved(context $context, context $newparent) {
7428 $context->update_moved($newparent);
7432 * Remove a context record and any dependent entries,
7433 * removes context from static context cache too
7435 * @deprecated since 2.2, use $context->delete_content() instead
7436 * @param int $contextlevel
7437 * @param int $instanceid
7438 * @param bool $deleterecord false means keep record for now
7439 * @return bool returns true or throws an exception
7441 function delete_context($contextlevel, $instanceid, $deleterecord = true) {
7442 if ($deleterecord) {
7443 context_helper::delete_instance($contextlevel, $instanceid);
7444 } else {
7445 $classname = context_helper::get_class_for_level($contextlevel);
7446 if ($context = $classname::instance($instanceid, IGNORE_MISSING)) {
7447 $context->delete_content();
7451 return true;
7455 * Returns context level name
7457 * @deprecated since 2.2
7458 * @param integer $contextlevel $context->context level. One of the CONTEXT_... constants.
7459 * @return string the name for this type of context.
7461 function get_contextlevel_name($contextlevel) {
7462 return context_helper::get_level_name($contextlevel);
7466 * Prints human readable context identifier.
7468 * @deprecated since 2.2
7469 * @param context $context the context.
7470 * @param boolean $withprefix whether to prefix the name of the context with the
7471 * type of context, e.g. User, Course, Forum, etc.
7472 * @param boolean $short whether to user the short name of the thing. Only applies
7473 * to course contexts
7474 * @return string the human readable context name.
7476 function print_context_name(context $context, $withprefix = true, $short = false) {
7477 return $context->get_context_name($withprefix, $short);
7481 * Get a URL for a context, if there is a natural one. For example, for
7482 * CONTEXT_COURSE, this is the course page. For CONTEXT_USER it is the
7483 * user profile page.
7485 * @deprecated since 2.2
7486 * @param context $context the context.
7487 * @return moodle_url
7489 function get_context_url(context $context) {
7490 return $context->get_url();
7494 * Is this context part of any course? if yes return course context,
7495 * if not return null or throw exception.
7497 * @deprecated since 2.2, use $context->get_course_context() instead
7498 * @param context $context
7499 * @return course_context context of the enclosing course, null if not found or exception
7501 function get_course_context(context $context) {
7502 return $context->get_course_context(true);
7506 * Returns current course id or null if outside of course based on context parameter.
7508 * @deprecated since 2.2, use $context->get_course_context instead
7509 * @param context $context
7510 * @return int|bool related course id or false
7512 function get_courseid_from_context(context $context) {
7513 if ($coursecontext = $context->get_course_context(false)) {
7514 return $coursecontext->instanceid;
7515 } else {
7516 return false;
7521 * Get an array of courses where cap requested is available
7522 * and user is enrolled, this can be relatively slow.
7524 * @deprecated since 2.2, use enrol_get_users_courses() instead
7525 * @param int $userid A user id. By default (null) checks the permissions of the current user.
7526 * @param string $cap - name of the capability
7527 * @param array $accessdata_ignored
7528 * @param bool $doanything_ignored
7529 * @param string $sort - sorting fields - prefix each fieldname with "c."
7530 * @param array $fields - additional fields you are interested in...
7531 * @param int $limit_ignored
7532 * @return array $courses - ordered array of course objects - see notes above
7534 function get_user_courses_bycap($userid, $cap, $accessdata_ignored, $doanything_ignored, $sort = 'c.sortorder ASC', $fields = null, $limit_ignored = 0) {
7536 $courses = enrol_get_users_courses($userid, true, $fields, $sort);
7537 foreach ($courses as $id=>$course) {
7538 $context = context_course::instance($id);
7539 if (!has_capability($cap, $context, $userid)) {
7540 unset($courses[$id]);
7544 return $courses;
7548 * Extracts the relevant capabilities given a contextid.
7549 * All case based, example an instance of forum context.
7550 * Will fetch all forum related capabilities, while course contexts
7551 * Will fetch all capabilities
7553 * capabilities
7554 * `name` varchar(150) NOT NULL,
7555 * `captype` varchar(50) NOT NULL,
7556 * `contextlevel` int(10) NOT NULL,
7557 * `component` varchar(100) NOT NULL,
7559 * @deprecated since 2.2
7560 * @param context $context
7561 * @return array
7563 function fetch_context_capabilities(context $context) {
7564 return $context->get_capabilities();
7568 * Runs get_records select on context table and returns the result
7569 * Does get_records_select on the context table, and returns the results ordered
7570 * by contextlevel, and then the natural sort order within each level.
7571 * for the purpose of $select, you need to know that the context table has been
7572 * aliased to ctx, so for example, you can call get_sorted_contexts('ctx.depth = 3');
7574 * @deprecated since 2.2
7575 * @param string $select the contents of the WHERE clause. Remember to do ctx.fieldname.
7576 * @param array $params any parameters required by $select.
7577 * @return array the requested context records.
7579 function get_sorted_contexts($select, $params = array()) {
7581 //TODO: we should probably rewrite all the code that is using this thing, the trouble is we MUST NOT modify the context instances...
7583 global $DB;
7584 if ($select) {
7585 $select = 'WHERE ' . $select;
7587 return $DB->get_records_sql("
7588 SELECT ctx.*
7589 FROM {context} ctx
7590 LEFT JOIN {user} u ON ctx.contextlevel = " . CONTEXT_USER . " AND u.id = ctx.instanceid
7591 LEFT JOIN {course_categories} cat ON ctx.contextlevel = " . CONTEXT_COURSECAT . " AND cat.id = ctx.instanceid
7592 LEFT JOIN {course} c ON ctx.contextlevel = " . CONTEXT_COURSE . " AND c.id = ctx.instanceid
7593 LEFT JOIN {course_modules} cm ON ctx.contextlevel = " . CONTEXT_MODULE . " AND cm.id = ctx.instanceid
7594 LEFT JOIN {block_instances} bi ON ctx.contextlevel = " . CONTEXT_BLOCK . " AND bi.id = ctx.instanceid
7595 $select
7596 ORDER BY ctx.contextlevel, bi.defaultregion, COALESCE(cat.sortorder, c.sortorder, cm.section, bi.defaultweight), u.lastname, u.firstname, cm.id
7597 ", $params);
7601 * This is really slow!!! do not use above course context level
7603 * @deprecated since 2.2
7604 * @param int $roleid
7605 * @param context $context
7606 * @return array
7608 function get_role_context_caps($roleid, context $context) {
7609 global $DB;
7611 //this is really slow!!!! - do not use above course context level!
7612 $result = array();
7613 $result[$context->id] = array();
7615 // first emulate the parent context capabilities merging into context
7616 $searchcontexts = array_reverse($context->get_parent_context_ids(true));
7617 foreach ($searchcontexts as $cid) {
7618 if ($capabilities = $DB->get_records('role_capabilities', array('roleid'=>$roleid, 'contextid'=>$cid))) {
7619 foreach ($capabilities as $cap) {
7620 if (!array_key_exists($cap->capability, $result[$context->id])) {
7621 $result[$context->id][$cap->capability] = 0;
7623 $result[$context->id][$cap->capability] += $cap->permission;
7628 // now go through the contexts below given context
7629 $searchcontexts = array_keys($context->get_child_contexts());
7630 foreach ($searchcontexts as $cid) {
7631 if ($capabilities = $DB->get_records('role_capabilities', array('roleid'=>$roleid, 'contextid'=>$cid))) {
7632 foreach ($capabilities as $cap) {
7633 if (!array_key_exists($cap->contextid, $result)) {
7634 $result[$cap->contextid] = array();
7636 $result[$cap->contextid][$cap->capability] = $cap->permission;
7641 return $result;
7645 * Gets a string for sql calls, searching for stuff in this context or above
7647 * NOTE: use $DB->get_in_or_equal($context->get_parent_context_ids()...
7649 * @deprecated since 2.2, $context->use get_parent_context_ids() instead
7650 * @param context $context
7651 * @return string
7653 function get_related_contexts_string(context $context) {
7655 if ($parents = $context->get_parent_context_ids()) {
7656 return (' IN ('.$context->id.','.implode(',', $parents).')');
7657 } else {
7658 return (' ='.$context->id);
7663 * Given context and array of users, returns array of users whose enrolment status is suspended,
7664 * or enrolment has expired or has not started. Also removes those users from the given array
7666 * @param context $context context in which suspended users should be extracted.
7667 * @param array $users list of users.
7668 * @param array $ignoreusers array of user ids to ignore, e.g. guest
7669 * @return array list of suspended users.
7671 function extract_suspended_users($context, &$users, $ignoreusers=array()) {
7672 global $DB;
7674 // Get active enrolled users.
7675 list($sql, $params) = get_enrolled_sql($context, null, null, true);
7676 $activeusers = $DB->get_records_sql($sql, $params);
7678 // Move suspended users to a separate array & remove from the initial one.
7679 $susers = array();
7680 if (sizeof($activeusers)) {
7681 foreach ($users as $userid => $user) {
7682 if (!array_key_exists($userid, $activeusers) && !in_array($userid, $ignoreusers)) {
7683 $susers[$userid] = $user;
7684 unset($users[$userid]);
7688 return $susers;
7692 * Given context and array of users, returns array of user ids whose enrolment status is suspended,
7693 * or enrolment has expired or not started.
7695 * @param context $context context in which user enrolment is checked.
7696 * @return array list of suspended user id's.
7698 function get_suspended_userids($context){
7699 global $DB;
7701 // Get all enrolled users.
7702 list($sql, $params) = get_enrolled_sql($context);
7703 $users = $DB->get_records_sql($sql, $params);
7705 // Get active enrolled users.
7706 list($sql, $params) = get_enrolled_sql($context, null, null, true);
7707 $activeusers = $DB->get_records_sql($sql, $params);
7709 $susers = array();
7710 if (sizeof($activeusers) != sizeof($users)) {
7711 foreach ($users as $userid => $user) {
7712 if (!array_key_exists($userid, $activeusers)) {
7713 $susers[$userid] = $userid;
7717 return $susers;