"Fossies" - the Fresh Open Source Software Archive

Member "moodle/lib/accesslib.php" (6 Sep 2019, 269924 Bytes) of package /linux/www/moodle-3.6.6.tgz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "accesslib.php" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 3.6.5_vs_3.6.6.

    1 <?php
    2 // This file is part of Moodle - http://moodle.org/
    3 //
    4 // Moodle is free software: you can redistribute it and/or modify
    5 // it under the terms of the GNU General Public License as published by
    6 // the Free Software Foundation, either version 3 of the License, or
    7 // (at your option) any later version.
    8 //
    9 // Moodle is distributed in the hope that it will be useful,
   10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
   11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   12 // GNU General Public License for more details.
   13 //
   14 // You should have received a copy of the GNU General Public License
   15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
   16 
   17 /**
   18  * This file contains functions for managing user access
   19  *
   20  * <b>Public API vs internals</b>
   21  *
   22  * General users probably only care about
   23  *
   24  * Context handling
   25  * - context_course::instance($courseid), context_module::instance($cm->id), context_coursecat::instance($catid)
   26  * - context::instance_by_id($contextid)
   27  * - $context->get_parent_contexts();
   28  * - $context->get_child_contexts();
   29  *
   30  * Whether the user can do something...
   31  * - has_capability()
   32  * - has_any_capability()
   33  * - has_all_capabilities()
   34  * - require_capability()
   35  * - require_login() (from moodlelib)
   36  * - is_enrolled()
   37  * - is_viewing()
   38  * - is_guest()
   39  * - is_siteadmin()
   40  * - isguestuser()
   41  * - isloggedin()
   42  *
   43  * What courses has this user access to?
   44  * - get_enrolled_users()
   45  *
   46  * What users can do X in this context?
   47  * - get_enrolled_users() - at and bellow course context
   48  * - get_users_by_capability() - above course context
   49  *
   50  * Modify roles
   51  * - role_assign()
   52  * - role_unassign()
   53  * - role_unassign_all()
   54  *
   55  * Advanced - for internal use only
   56  * - load_all_capabilities()
   57  * - reload_all_capabilities()
   58  * - has_capability_in_accessdata()
   59  * - get_user_roles_sitewide_accessdata()
   60  * - etc.
   61  *
   62  * <b>Name conventions</b>
   63  *
   64  * "ctx" means context
   65  * "ra" means role assignment
   66  * "rdef" means role definition
   67  *
   68  * <b>accessdata</b>
   69  *
   70  * Access control data is held in the "accessdata" array
   71  * which - for the logged-in user, will be in $USER->access
   72  *
   73  * For other users can be generated and passed around (but may also be cached
   74  * against userid in $ACCESSLIB_PRIVATE->accessdatabyuser).
   75  *
   76  * $accessdata is a multidimensional array, holding
   77  * role assignments (RAs), role switches and initialization time.
   78  *
   79  * Things are keyed on "contextpaths" (the path field of
   80  * the context table) for fast walking up/down the tree.
   81  * <code>
   82  * $accessdata['ra'][$contextpath] = array($roleid=>$roleid)
   83  *                  [$contextpath] = array($roleid=>$roleid)
   84  *                  [$contextpath] = array($roleid=>$roleid)
   85  * </code>
   86  *
   87  * <b>Stale accessdata</b>
   88  *
   89  * For the logged-in user, accessdata is long-lived.
   90  *
   91  * On each pageload we load $ACCESSLIB_PRIVATE->dirtycontexts which lists
   92  * context paths affected by changes. Any check at-or-below
   93  * a dirty context will trigger a transparent reload of accessdata.
   94  *
   95  * Changes at the system level will force the reload for everyone.
   96  *
   97  * <b>Default role caps</b>
   98  * The default role assignment is not in the DB, so we
   99  * add it manually to accessdata.
  100  *
  101  * This means that functions that work directly off the
  102  * DB need to ensure that the default role caps
  103  * are dealt with appropriately.
  104  *
  105  * @package    core_access
  106  * @copyright  1999 onwards Martin Dougiamas  http://dougiamas.com
  107  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  108  */
  109 
  110 defined('MOODLE_INTERNAL') || die();
  111 
  112 /** No capability change */
  113 define('CAP_INHERIT', 0);
  114 /** Allow permission, overrides CAP_PREVENT defined in parent contexts */
  115 define('CAP_ALLOW', 1);
  116 /** Prevent permission, overrides CAP_ALLOW defined in parent contexts */
  117 define('CAP_PREVENT', -1);
  118 /** Prohibit permission, overrides everything in current and child contexts */
  119 define('CAP_PROHIBIT', -1000);
  120 
  121 /** System context level - only one instance in every system */
  122 define('CONTEXT_SYSTEM', 10);
  123 /** User context level -  one instance for each user describing what others can do to user */
  124 define('CONTEXT_USER', 30);
  125 /** Course category context level - one instance for each category */
  126 define('CONTEXT_COURSECAT', 40);
  127 /** Course context level - one instances for each course */
  128 define('CONTEXT_COURSE', 50);
  129 /** Course module context level - one instance for each course module */
  130 define('CONTEXT_MODULE', 70);
  131 /**
  132  * Block context level - one instance for each block, sticky blocks are tricky
  133  * because ppl think they should be able to override them at lower contexts.
  134  * Any other context level instance can be parent of block context.
  135  */
  136 define('CONTEXT_BLOCK', 80);
  137 
  138 /** Capability allow management of trusts - NOT IMPLEMENTED YET - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
  139 define('RISK_MANAGETRUST', 0x0001);
  140 /** Capability allows changes in system configuration - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
  141 define('RISK_CONFIG',      0x0002);
  142 /** Capability allows user to add scripted content - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
  143 define('RISK_XSS',         0x0004);
  144 /** Capability allows access to personal user information - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
  145 define('RISK_PERSONAL',    0x0008);
  146 /** Capability allows users to add content others may see - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
  147 define('RISK_SPAM',        0x0010);
  148 /** capability allows mass delete of data belonging to other users - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
  149 define('RISK_DATALOSS',    0x0020);
  150 
  151 /** rolename displays - the name as defined in the role definition, localised if name empty */
  152 define('ROLENAME_ORIGINAL', 0);
  153 /** rolename displays - the name as defined by a role alias at the course level, falls back to ROLENAME_ORIGINAL if alias not present */
  154 define('ROLENAME_ALIAS', 1);
  155 /** rolename displays - Both, like this:  Role alias (Original) */
  156 define('ROLENAME_BOTH', 2);
  157 /** rolename displays - the name as defined in the role definition and the shortname in brackets */
  158 define('ROLENAME_ORIGINALANDSHORT', 3);
  159 /** rolename displays - the name as defined by a role alias, in raw form suitable for editing */
  160 define('ROLENAME_ALIAS_RAW', 4);
  161 /** rolename displays - the name is simply short role name */
  162 define('ROLENAME_SHORT', 5);
  163 
  164 if (!defined('CONTEXT_CACHE_MAX_SIZE')) {
  165     /** maximum size of context cache - it is possible to tweak this config.php or in any script before inclusion of context.php */
  166     define('CONTEXT_CACHE_MAX_SIZE', 2500);
  167 }
  168 
  169 /**
  170  * Although this looks like a global variable, it isn't really.
  171  *
  172  * It is just a private implementation detail to accesslib that MUST NOT be used elsewhere.
  173  * It is used to cache various bits of data between function calls for performance reasons.
  174  * Sadly, a PHP global variable is the only way to implement this, without rewriting everything
  175  * as methods of a class, instead of functions.
  176  *
  177  * @access private
  178  * @global stdClass $ACCESSLIB_PRIVATE
  179  * @name $ACCESSLIB_PRIVATE
  180  */
  181 global $ACCESSLIB_PRIVATE;
  182 $ACCESSLIB_PRIVATE = new stdClass();
  183 $ACCESSLIB_PRIVATE->cacheroledefs    = array(); // Holds site-wide role definitions.
  184 $ACCESSLIB_PRIVATE->dirtycontexts    = null;    // Dirty contexts cache, loaded from DB once per page
  185 $ACCESSLIB_PRIVATE->dirtyusers       = null;    // Dirty users cache, loaded from DB once per $USER->id
  186 $ACCESSLIB_PRIVATE->accessdatabyuser = array(); // Holds the cache of $accessdata structure for users (including $USER)
  187 
  188 /**
  189  * Clears accesslib's private caches. ONLY BE USED BY UNIT TESTS
  190  *
  191  * This method should ONLY BE USED BY UNIT TESTS. It clears all of
  192  * accesslib's private caches. You need to do this before setting up test data,
  193  * and also at the end of the tests.
  194  *
  195  * @access private
  196  * @return void
  197  */
  198 function accesslib_clear_all_caches_for_unit_testing() {
  199     global $USER;
  200     if (!PHPUNIT_TEST) {
  201         throw new coding_exception('You must not call clear_all_caches outside of unit tests.');
  202     }
  203 
  204     accesslib_clear_all_caches(true);
  205     accesslib_reset_role_cache();
  206 
  207     unset($USER->access);
  208 }
  209 
  210 /**
  211  * Clears accesslib's private caches. ONLY BE USED FROM THIS LIBRARY FILE!
  212  *
  213  * This reset does not touch global $USER.
  214  *
  215  * @access private
  216  * @param bool $resetcontexts
  217  * @return void
  218  */
  219 function accesslib_clear_all_caches($resetcontexts) {
  220     global $ACCESSLIB_PRIVATE;
  221 
  222     $ACCESSLIB_PRIVATE->dirtycontexts    = null;
  223     $ACCESSLIB_PRIVATE->dirtyusers       = null;
  224     $ACCESSLIB_PRIVATE->accessdatabyuser = array();
  225 
  226     if ($resetcontexts) {
  227         context_helper::reset_caches();
  228     }
  229 }
  230 
  231 /**
  232  * Full reset of accesslib's private role cache. ONLY TO BE USED FROM THIS LIBRARY FILE!
  233  *
  234  * This reset does not touch global $USER.
  235  *
  236  * Note: Only use this when the roles that need a refresh are unknown.
  237  *
  238  * @see accesslib_clear_role_cache()
  239  *
  240  * @access private
  241  * @return void
  242  */
  243 function accesslib_reset_role_cache() {
  244     global $ACCESSLIB_PRIVATE;
  245 
  246     $ACCESSLIB_PRIVATE->cacheroledefs = array();
  247     $cache = cache::make('core', 'roledefs');
  248     $cache->purge();
  249 }
  250 
  251 /**
  252  * Clears accesslib's private cache of a specific role or roles. ONLY BE USED FROM THIS LIBRARY FILE!
  253  *
  254  * This reset does not touch global $USER.
  255  *
  256  * @access private
  257  * @param int|array $roles
  258  * @return void
  259  */
  260 function accesslib_clear_role_cache($roles) {
  261     global $ACCESSLIB_PRIVATE;
  262 
  263     if (!is_array($roles)) {
  264         $roles = [$roles];
  265     }
  266 
  267     foreach ($roles as $role) {
  268         if (isset($ACCESSLIB_PRIVATE->cacheroledefs[$role])) {
  269             unset($ACCESSLIB_PRIVATE->cacheroledefs[$role]);
  270         }
  271     }
  272 
  273     $cache = cache::make('core', 'roledefs');
  274     $cache->delete_many($roles);
  275 }
  276 
  277 /**
  278  * Role is assigned at system context.
  279  *
  280  * @access private
  281  * @param int $roleid
  282  * @return array
  283  */
  284 function get_role_access($roleid) {
  285     $accessdata = get_empty_accessdata();
  286     $accessdata['ra']['/'.SYSCONTEXTID] = array((int)$roleid => (int)$roleid);
  287     return $accessdata;
  288 }
  289 
  290 /**
  291  * Fetch raw "site wide" role definitions.
  292  * Even MUC static acceleration cache appears a bit slow for this.
  293  * Important as can be hit hundreds of times per page.
  294  *
  295  * @param array $roleids List of role ids to fetch definitions for.
  296  * @return array Complete definition for each requested role.
  297  */
  298 function get_role_definitions(array $roleids) {
  299     global $ACCESSLIB_PRIVATE;
  300 
  301     if (empty($roleids)) {
  302         return array();
  303     }
  304 
  305     // Grab all keys we have not yet got in our static cache.
  306     if ($uncached = array_diff($roleids, array_keys($ACCESSLIB_PRIVATE->cacheroledefs))) {
  307         $cache = cache::make('core', 'roledefs');
  308         foreach ($cache->get_many($uncached) as $roleid => $cachedroledef) {
  309             if (is_array($cachedroledef)) {
  310                 $ACCESSLIB_PRIVATE->cacheroledefs[$roleid] = $cachedroledef;
  311             }
  312         }
  313 
  314         // Check we have the remaining keys from the MUC.
  315         if ($uncached = array_diff($roleids, array_keys($ACCESSLIB_PRIVATE->cacheroledefs))) {
  316             $uncached = get_role_definitions_uncached($uncached);
  317             $ACCESSLIB_PRIVATE->cacheroledefs += $uncached;
  318             $cache->set_many($uncached);
  319         }
  320     }
  321 
  322     // Return just the roles we need.
  323     return array_intersect_key($ACCESSLIB_PRIVATE->cacheroledefs, array_flip($roleids));
  324 }
  325 
  326 /**
  327  * Query raw "site wide" role definitions.
  328  *
  329  * @param array $roleids List of role ids to fetch definitions for.
  330  * @return array Complete definition for each requested role.
  331  */
  332 function get_role_definitions_uncached(array $roleids) {
  333     global $DB;
  334 
  335     if (empty($roleids)) {
  336         return array();
  337     }
  338 
  339     // Create a blank results array: even if a role has no capabilities,
  340     // we need to ensure it is included in the results to show we have
  341     // loaded all the capabilities that there are.
  342     $rdefs = array();
  343     foreach ($roleids as $roleid) {
  344         $rdefs[$roleid] = array();
  345     }
  346 
  347     // Load all the capabilities for these roles in all contexts.
  348     list($sql, $params) = $DB->get_in_or_equal($roleids);
  349     $sql = "SELECT ctx.path, rc.roleid, rc.capability, rc.permission
  350               FROM {role_capabilities} rc
  351               JOIN {context} ctx ON rc.contextid = ctx.id
  352               JOIN {capabilities} cap ON rc.capability = cap.name
  353              WHERE rc.roleid $sql";
  354     $rs = $DB->get_recordset_sql($sql, $params);
  355 
  356     // Store the capabilities into the expected data structure.
  357     foreach ($rs as $rd) {
  358         if (!isset($rdefs[$rd->roleid][$rd->path])) {
  359             $rdefs[$rd->roleid][$rd->path] = array();
  360         }
  361         $rdefs[$rd->roleid][$rd->path][$rd->capability] = (int) $rd->permission;
  362     }
  363 
  364     $rs->close();
  365 
  366     // Sometimes (e.g. get_user_capability_course_helper::get_capability_info_at_each_context)
  367     // we process role definitinons in a way that requires we see parent contexts
  368     // before child contexts. This sort ensures that works (and is faster than
  369     // sorting in the SQL query).
  370     foreach ($rdefs as $roleid => $rdef) {
  371         ksort($rdefs[$roleid]);
  372     }
  373 
  374     return $rdefs;
  375 }
  376 
  377 /**
  378  * Get the default guest role, this is used for guest account,
  379  * search engine spiders, etc.
  380  *
  381  * @return stdClass role record
  382  */
  383 function get_guest_role() {
  384     global $CFG, $DB;
  385 
  386     if (empty($CFG->guestroleid)) {
  387         if ($roles = $DB->get_records('role', array('archetype'=>'guest'))) {
  388             $guestrole = array_shift($roles);   // Pick the first one
  389             set_config('guestroleid', $guestrole->id);
  390             return $guestrole;
  391         } else {
  392             debugging('Can not find any guest role!');
  393             return false;
  394         }
  395     } else {
  396         if ($guestrole = $DB->get_record('role', array('id'=>$CFG->guestroleid))) {
  397             return $guestrole;
  398         } else {
  399             // somebody is messing with guest roles, remove incorrect setting and try to find a new one
  400             set_config('guestroleid', '');
  401             return get_guest_role();
  402         }
  403     }
  404 }
  405 
  406 /**
  407  * Check whether a user has a particular capability in a given context.
  408  *
  409  * For example:
  410  *      $context = context_module::instance($cm->id);
  411  *      has_capability('mod/forum:replypost', $context)
  412  *
  413  * By default checks the capabilities of the current user, but you can pass a
  414  * different userid. By default will return true for admin users, but you can override that with the fourth argument.
  415  *
  416  * Guest and not-logged-in users can never get any dangerous capability - that is any write capability
  417  * or capabilities with XSS, config or data loss risks.
  418  *
  419  * @category access
  420  *
  421  * @param string $capability the name of the capability to check. For example mod/forum:view
  422  * @param context $context the context to check the capability in. You normally get this with instance method of a context class.
  423  * @param integer|stdClass $user A user id or object. By default (null) checks the permissions of the current user.
  424  * @param boolean $doanything If false, ignores effect of admin role assignment
  425  * @return boolean true if the user has this capability. Otherwise false.
  426  */
  427 function has_capability($capability, context $context, $user = null, $doanything = true) {
  428     global $USER, $CFG, $SCRIPT, $ACCESSLIB_PRIVATE;
  429 
  430     if (during_initial_install()) {
  431         if ($SCRIPT === "/$CFG->admin/index.php"
  432                 or $SCRIPT === "/$CFG->admin/cli/install.php"
  433                 or $SCRIPT === "/$CFG->admin/cli/install_database.php"
  434                 or (defined('BEHAT_UTIL') and BEHAT_UTIL)
  435                 or (defined('PHPUNIT_UTIL') and PHPUNIT_UTIL)) {
  436             // we are in an installer - roles can not work yet
  437             return true;
  438         } else {
  439             return false;
  440         }
  441     }
  442 
  443     if (strpos($capability, 'moodle/legacy:') === 0) {
  444         throw new coding_exception('Legacy capabilities can not be used any more!');
  445     }
  446 
  447     if (!is_bool($doanything)) {
  448         throw new coding_exception('Capability parameter "doanything" is wierd, only true or false is allowed. This has to be fixed in code.');
  449     }
  450 
  451     // capability must exist
  452     if (!$capinfo = get_capability_info($capability)) {
  453         debugging('Capability "'.$capability.'" was not found! This has to be fixed in code.');
  454         return false;
  455     }
  456 
  457     if (!isset($USER->id)) {
  458         // should never happen
  459         $USER->id = 0;
  460         debugging('Capability check being performed on a user with no ID.', DEBUG_DEVELOPER);
  461     }
  462 
  463     // make sure there is a real user specified
  464     if ($user === null) {
  465         $userid = $USER->id;
  466     } else {
  467         $userid = is_object($user) ? $user->id : $user;
  468     }
  469 
  470     // make sure forcelogin cuts off not-logged-in users if enabled
  471     if (!empty($CFG->forcelogin) and $userid == 0) {
  472         return false;
  473     }
  474 
  475     // make sure the guest account and not-logged-in users never get any risky caps no matter what the actual settings are.
  476     if (($capinfo->captype === 'write') or ($capinfo->riskbitmask & (RISK_XSS | RISK_CONFIG | RISK_DATALOSS))) {
  477         if (isguestuser($userid) or $userid == 0) {
  478             return false;
  479         }
  480     }
  481 
  482     // Check whether context locking is enabled.
  483     if (!empty($CFG->contextlocking)) {
  484         if ($capinfo->captype === 'write' && $context->locked) {
  485             // Context locking applies to any write capability in a locked context.
  486             // It does not apply to moodle/site:managecontextlocks - this is to allow context locking to be unlocked.
  487             if ($capinfo->name !== 'moodle/site:managecontextlocks') {
  488                 // It applies to all users who are not site admins.
  489                 // It also applies to site admins when contextlockappliestoadmin is set.
  490                 if (!is_siteadmin($userid) || !empty($CFG->contextlockappliestoadmin)) {
  491                     return false;
  492                 }
  493             }
  494         }
  495     }
  496 
  497     // somehow make sure the user is not deleted and actually exists
  498     if ($userid != 0) {
  499         if ($userid == $USER->id and isset($USER->deleted)) {
  500             // this prevents one query per page, it is a bit of cheating,
  501             // but hopefully session is terminated properly once user is deleted
  502             if ($USER->deleted) {
  503                 return false;
  504             }
  505         } else {
  506             if (!context_user::instance($userid, IGNORE_MISSING)) {
  507                 // no user context == invalid userid
  508                 return false;
  509             }
  510         }
  511     }
  512 
  513     // context path/depth must be valid
  514     if (empty($context->path) or $context->depth == 0) {
  515         // this should not happen often, each upgrade tries to rebuild the context paths
  516         debugging('Context id '.$context->id.' does not have valid path, please use context_helper::build_all_paths()');
  517         if (is_siteadmin($userid)) {
  518             return true;
  519         } else {
  520             return false;
  521         }
  522     }
  523 
  524     // Find out if user is admin - it is not possible to override the doanything in any way
  525     // and it is not possible to switch to admin role either.
  526     if ($doanything) {
  527         if (is_siteadmin($userid)) {
  528             if ($userid != $USER->id) {
  529                 return true;
  530             }
  531             // make sure switchrole is not used in this context
  532             if (empty($USER->access['rsw'])) {
  533                 return true;
  534             }
  535             $parts = explode('/', trim($context->path, '/'));
  536             $path = '';
  537             $switched = false;
  538             foreach ($parts as $part) {
  539                 $path .= '/' . $part;
  540                 if (!empty($USER->access['rsw'][$path])) {
  541                     $switched = true;
  542                     break;
  543                 }
  544             }
  545             if (!$switched) {
  546                 return true;
  547             }
  548             //ok, admin switched role in this context, let's use normal access control rules
  549         }
  550     }
  551 
  552     // Careful check for staleness...
  553     $context->reload_if_dirty();
  554 
  555     if ($USER->id == $userid) {
  556         if (!isset($USER->access)) {
  557             load_all_capabilities();
  558         }
  559         $access =& $USER->access;
  560 
  561     } else {
  562         // make sure user accessdata is really loaded
  563         get_user_accessdata($userid, true);
  564         $access =& $ACCESSLIB_PRIVATE->accessdatabyuser[$userid];
  565     }
  566 
  567     return has_capability_in_accessdata($capability, $context, $access);
  568 }
  569 
  570 /**
  571  * Check if the user has any one of several capabilities from a list.
  572  *
  573  * This is just a utility method that calls has_capability in a loop. Try to put
  574  * the capabilities that most users are likely to have first in the list for best
  575  * performance.
  576  *
  577  * @category access
  578  * @see has_capability()
  579  *
  580  * @param array $capabilities an array of capability names.
  581  * @param context $context the context to check the capability in. You normally get this with instance method of a context class.
  582  * @param integer|stdClass $user A user id or object. By default (null) checks the permissions of the current user.
  583  * @param boolean $doanything If false, ignore effect of admin role assignment
  584  * @return boolean true if the user has any of these capabilities. Otherwise false.
  585  */
  586 function has_any_capability(array $capabilities, context $context, $user = null, $doanything = true) {
  587     foreach ($capabilities as $capability) {
  588         if (has_capability($capability, $context, $user, $doanything)) {
  589             return true;
  590         }
  591     }
  592     return false;
  593 }
  594 
  595 /**
  596  * Check if the user has all the capabilities in a list.
  597  *
  598  * This is just a utility method that calls has_capability in a loop. Try to put
  599  * the capabilities that fewest users are likely to have first in the list for best
  600  * performance.
  601  *
  602  * @category access
  603  * @see has_capability()
  604  *
  605  * @param array $capabilities an array of capability names.
  606  * @param context $context the context to check the capability in. You normally get this with instance method of a context class.
  607  * @param integer|stdClass $user A user id or object. By default (null) checks the permissions of the current user.
  608  * @param boolean $doanything If false, ignore effect of admin role assignment
  609  * @return boolean true if the user has all of these capabilities. Otherwise false.
  610  */
  611 function has_all_capabilities(array $capabilities, context $context, $user = null, $doanything = true) {
  612     foreach ($capabilities as $capability) {
  613         if (!has_capability($capability, $context, $user, $doanything)) {
  614             return false;
  615         }
  616     }
  617     return true;
  618 }
  619 
  620 /**
  621  * Is course creator going to have capability in a new course?
  622  *
  623  * This is intended to be used in enrolment plugins before or during course creation,
  624  * do not use after the course is fully created.
  625  *
  626  * @category access
  627  *
  628  * @param string $capability the name of the capability to check.
  629  * @param context $context course or category context where is course going to be created
  630  * @param integer|stdClass $user A user id or object. By default (null) checks the permissions of the current user.
  631  * @return boolean true if the user will have this capability.
  632  *
  633  * @throws coding_exception if different type of context submitted
  634  */
  635 function guess_if_creator_will_have_course_capability($capability, context $context, $user = null) {
  636     global $CFG;
  637 
  638     if ($context->contextlevel != CONTEXT_COURSE and $context->contextlevel != CONTEXT_COURSECAT) {
  639         throw new coding_exception('Only course or course category context expected');
  640     }
  641 
  642     if (has_capability($capability, $context, $user)) {
  643         // User already has the capability, it could be only removed if CAP_PROHIBIT
  644         // was involved here, but we ignore that.
  645         return true;
  646     }
  647 
  648     if (!has_capability('moodle/course:create', $context, $user)) {
  649         return false;
  650     }
  651 
  652     if (!enrol_is_enabled('manual')) {
  653         return false;
  654     }
  655 
  656     if (empty($CFG->creatornewroleid)) {
  657         return false;
  658     }
  659 
  660     if ($context->contextlevel == CONTEXT_COURSE) {
  661         if (is_viewing($context, $user, 'moodle/role:assign') or is_enrolled($context, $user, 'moodle/role:assign')) {
  662             return false;
  663         }
  664     } else {
  665         if (has_capability('moodle/course:view', $context, $user) and has_capability('moodle/role:assign', $context, $user)) {
  666             return false;
  667         }
  668     }
  669 
  670     // Most likely they will be enrolled after the course creation is finished,
  671     // does the new role have the required capability?
  672     list($neededroles, $forbiddenroles) = get_roles_with_cap_in_context($context, $capability);
  673     return isset($neededroles[$CFG->creatornewroleid]);
  674 }
  675 
  676 /**
  677  * Check if the user is an admin at the site level.
  678  *
  679  * Please note that use of proper capabilities is always encouraged,
  680  * this function is supposed to be used from core or for temporary hacks.
  681  *
  682  * @category access
  683  *
  684  * @param  int|stdClass  $user_or_id user id or user object
  685  * @return bool true if user is one of the administrators, false otherwise
  686  */
  687 function is_siteadmin($user_or_id = null) {
  688     global $CFG, $USER;
  689 
  690     if ($user_or_id === null) {
  691         $user_or_id = $USER;
  692     }
  693 
  694     if (empty($user_or_id)) {
  695         return false;
  696     }
  697     if (!empty($user_or_id->id)) {
  698         $userid = $user_or_id->id;
  699     } else {
  700         $userid = $user_or_id;
  701     }
  702 
  703     // Because this script is called many times (150+ for course page) with
  704     // the same parameters, it is worth doing minor optimisations. This static
  705     // cache stores the value for a single userid, saving about 2ms from course
  706     // page load time without using significant memory. As the static cache
  707     // also includes the value it depends on, this cannot break unit tests.
  708     static $knownid, $knownresult, $knownsiteadmins;
  709     if ($knownid === $userid && $knownsiteadmins === $CFG->siteadmins) {
  710         return $knownresult;
  711     }
  712     $knownid = $userid;
  713     $knownsiteadmins = $CFG->siteadmins;
  714 
  715     $siteadmins = explode(',', $CFG->siteadmins);
  716     $knownresult = in_array($userid, $siteadmins);
  717     return $knownresult;
  718 }
  719 
  720 /**
  721  * Returns true if user has at least one role assign
  722  * of 'coursecontact' role (is potentially listed in some course descriptions).
  723  *
  724  * @param int $userid
  725  * @return bool
  726  */
  727 function has_coursecontact_role($userid) {
  728     global $DB, $CFG;
  729 
  730     if (empty($CFG->coursecontact)) {
  731         return false;
  732     }
  733     $sql = "SELECT 1
  734               FROM {role_assignments}
  735              WHERE userid = :userid AND roleid IN ($CFG->coursecontact)";
  736     return $DB->record_exists_sql($sql, array('userid'=>$userid));
  737 }
  738 
  739 /**
  740  * Does the user have a capability to do something?
  741  *
  742  * Walk the accessdata array and return true/false.
  743  * Deals with prohibits, role switching, aggregating
  744  * capabilities, etc.
  745  *
  746  * The main feature of here is being FAST and with no
  747  * side effects.
  748  *
  749  * Notes:
  750  *
  751  * Switch Role merges with default role
  752  * ------------------------------------
  753  * If you are a teacher in course X, you have at least
  754  * teacher-in-X + defaultloggedinuser-sitewide. So in the
  755  * course you'll have techer+defaultloggedinuser.
  756  * We try to mimic that in switchrole.
  757  *
  758  * Permission evaluation
  759  * ---------------------
  760  * Originally there was an extremely complicated way
  761  * to determine the user access that dealt with
  762  * "locality" or role assignments and role overrides.
  763  * Now we simply evaluate access for each role separately
  764  * and then verify if user has at least one role with allow
  765  * and at the same time no role with prohibit.
  766  *
  767  * @access private
  768  * @param string $capability
  769  * @param context $context
  770  * @param array $accessdata
  771  * @return bool
  772  */
  773 function has_capability_in_accessdata($capability, context $context, array &$accessdata) {
  774     global $CFG;
  775 
  776     // Build $paths as a list of current + all parent "paths" with order bottom-to-top
  777     $path = $context->path;
  778     $paths = array($path);
  779     while($path = rtrim($path, '0123456789')) {
  780         $path = rtrim($path, '/');
  781         if ($path === '') {
  782             break;
  783         }
  784         $paths[] = $path;
  785     }
  786 
  787     $roles = array();
  788     $switchedrole = false;
  789 
  790     // Find out if role switched
  791     if (!empty($accessdata['rsw'])) {
  792         // From the bottom up...
  793         foreach ($paths as $path) {
  794             if (isset($accessdata['rsw'][$path])) {
  795                 // Found a switchrole assignment - check for that role _plus_ the default user role
  796                 $roles = array($accessdata['rsw'][$path]=>null, $CFG->defaultuserroleid=>null);
  797                 $switchedrole = true;
  798                 break;
  799             }
  800         }
  801     }
  802 
  803     if (!$switchedrole) {
  804         // get all users roles in this context and above
  805         foreach ($paths as $path) {
  806             if (isset($accessdata['ra'][$path])) {
  807                 foreach ($accessdata['ra'][$path] as $roleid) {
  808                     $roles[$roleid] = null;
  809                 }
  810             }
  811         }
  812     }
  813 
  814     // Now find out what access is given to each role, going bottom-->up direction
  815     $rdefs = get_role_definitions(array_keys($roles));
  816     $allowed = false;
  817 
  818     foreach ($roles as $roleid => $ignored) {
  819         foreach ($paths as $path) {
  820             if (isset($rdefs[$roleid][$path][$capability])) {
  821                 $perm = (int)$rdefs[$roleid][$path][$capability];
  822                 if ($perm === CAP_PROHIBIT) {
  823                     // any CAP_PROHIBIT found means no permission for the user
  824                     return false;
  825                 }
  826                 if (is_null($roles[$roleid])) {
  827                     $roles[$roleid] = $perm;
  828                 }
  829             }
  830         }
  831         // CAP_ALLOW in any role means the user has a permission, we continue only to detect prohibits
  832         $allowed = ($allowed or $roles[$roleid] === CAP_ALLOW);
  833     }
  834 
  835     return $allowed;
  836 }
  837 
  838 /**
  839  * A convenience function that tests has_capability, and displays an error if
  840  * the user does not have that capability.
  841  *
  842  * NOTE before Moodle 2.0, this function attempted to make an appropriate
  843  * require_login call before checking the capability. This is no longer the case.
  844  * You must call require_login (or one of its variants) if you want to check the
  845  * user is logged in, before you call this function.
  846  *
  847  * @see has_capability()
  848  *
  849  * @param string $capability the name of the capability to check. For example mod/forum:view
  850  * @param context $context the context to check the capability in. You normally get this with context_xxxx::instance().
  851  * @param int $userid A user id. By default (null) checks the permissions of the current user.
  852  * @param bool $doanything If false, ignore effect of admin role assignment
  853  * @param string $errormessage The error string to to user. Defaults to 'nopermissions'.
  854  * @param string $stringfile The language file to load the error string from. Defaults to 'error'.
  855  * @return void terminates with an error if the user does not have the given capability.
  856  */
  857 function require_capability($capability, context $context, $userid = null, $doanything = true,
  858                             $errormessage = 'nopermissions', $stringfile = '') {
  859     if (!has_capability($capability, $context, $userid, $doanything)) {
  860         throw new required_capability_exception($context, $capability, $errormessage, $stringfile);
  861     }
  862 }
  863 
  864 /**
  865  * Return a nested array showing all role assignments for the user.
  866  * [ra] => [contextpath][roleid] = roleid
  867  *
  868  * @access private
  869  * @param int $userid - the id of the user
  870  * @return array access info array
  871  */
  872 function get_user_roles_sitewide_accessdata($userid) {
  873     global $CFG, $DB;
  874 
  875     $accessdata = get_empty_accessdata();
  876 
  877     // start with the default role
  878     if (!empty($CFG->defaultuserroleid)) {
  879         $syscontext = context_system::instance();
  880         $accessdata['ra'][$syscontext->path][(int)$CFG->defaultuserroleid] = (int)$CFG->defaultuserroleid;
  881     }
  882 
  883     // load the "default frontpage role"
  884     if (!empty($CFG->defaultfrontpageroleid)) {
  885         $frontpagecontext = context_course::instance(get_site()->id);
  886         if ($frontpagecontext->path) {
  887             $accessdata['ra'][$frontpagecontext->path][(int)$CFG->defaultfrontpageroleid] = (int)$CFG->defaultfrontpageroleid;
  888         }
  889     }
  890 
  891     // Preload every assigned role.
  892     $sql = "SELECT ctx.path, ra.roleid, ra.contextid
  893               FROM {role_assignments} ra
  894               JOIN {context} ctx ON ctx.id = ra.contextid
  895              WHERE ra.userid = :userid";
  896 
  897     $rs = $DB->get_recordset_sql($sql, array('userid' => $userid));
  898 
  899     foreach ($rs as $ra) {
  900         // RAs leafs are arrays to support multi-role assignments...
  901         $accessdata['ra'][$ra->path][(int)$ra->roleid] = (int)$ra->roleid;
  902     }
  903 
  904     $rs->close();
  905 
  906     return $accessdata;
  907 }
  908 
  909 /**
  910  * Returns empty accessdata structure.
  911  *
  912  * @access private
  913  * @return array empt accessdata
  914  */
  915 function get_empty_accessdata() {
  916     $accessdata               = array(); // named list
  917     $accessdata['ra']         = array();
  918     $accessdata['time']       = time();
  919     $accessdata['rsw']        = array();
  920 
  921     return $accessdata;
  922 }
  923 
  924 /**
  925  * Get accessdata for a given user.
  926  *
  927  * @access private
  928  * @param int $userid
  929  * @param bool $preloadonly true means do not return access array
  930  * @return array accessdata
  931  */
  932 function get_user_accessdata($userid, $preloadonly=false) {
  933     global $CFG, $ACCESSLIB_PRIVATE, $USER;
  934 
  935     if (isset($USER->access)) {
  936         $ACCESSLIB_PRIVATE->accessdatabyuser[$USER->id] = $USER->access;
  937     }
  938 
  939     if (!isset($ACCESSLIB_PRIVATE->accessdatabyuser[$userid])) {
  940         if (empty($userid)) {
  941             if (!empty($CFG->notloggedinroleid)) {
  942                 $accessdata = get_role_access($CFG->notloggedinroleid);
  943             } else {
  944                 // weird
  945                 return get_empty_accessdata();
  946             }
  947 
  948         } else if (isguestuser($userid)) {
  949             if ($guestrole = get_guest_role()) {
  950                 $accessdata = get_role_access($guestrole->id);
  951             } else {
  952                 //weird
  953                 return get_empty_accessdata();
  954             }
  955 
  956         } else {
  957             // Includes default role and frontpage role.
  958             $accessdata = get_user_roles_sitewide_accessdata($userid);
  959         }
  960 
  961         $ACCESSLIB_PRIVATE->accessdatabyuser[$userid] = $accessdata;
  962     }
  963 
  964     if ($preloadonly) {
  965         return;
  966     } else {
  967         return $ACCESSLIB_PRIVATE->accessdatabyuser[$userid];
  968     }
  969 }
  970 
  971 /**
  972  * A convenience function to completely load all the capabilities
  973  * for the current user. It is called from has_capability() and functions change permissions.
  974  *
  975  * Call it only _after_ you've setup $USER and called check_enrolment_plugins();
  976  * @see check_enrolment_plugins()
  977  *
  978  * @access private
  979  * @return void
  980  */
  981 function load_all_capabilities() {
  982     global $USER;
  983 
  984     // roles not installed yet - we are in the middle of installation
  985     if (during_initial_install()) {
  986         return;
  987     }
  988 
  989     if (!isset($USER->id)) {
  990         // this should not happen
  991         $USER->id = 0;
  992     }
  993 
  994     unset($USER->access);
  995     $USER->access = get_user_accessdata($USER->id);
  996 
  997     // Clear to force a refresh
  998     unset($USER->mycourses);
  999 
 1000     // init/reset internal enrol caches - active course enrolments and temp access
 1001     $USER->enrol = array('enrolled'=>array(), 'tempguest'=>array());
 1002 }
 1003 
 1004 /**
 1005  * A convenience function to completely reload all the capabilities
 1006  * for the current user when roles have been updated in a relevant
 1007  * context -- but PRESERVING switchroles and loginas.
 1008  * This function resets all accesslib and context caches.
 1009  *
 1010  * That is - completely transparent to the user.
 1011  *
 1012  * Note: reloads $USER->access completely.
 1013  *
 1014  * @access private
 1015  * @return void
 1016  */
 1017 function reload_all_capabilities() {
 1018     global $USER, $DB, $ACCESSLIB_PRIVATE;
 1019 
 1020     // copy switchroles
 1021     $sw = array();
 1022     if (!empty($USER->access['rsw'])) {
 1023         $sw = $USER->access['rsw'];
 1024     }
 1025 
 1026     accesslib_clear_all_caches(true);
 1027     unset($USER->access);
 1028 
 1029     // Prevent dirty flags refetching on this page.
 1030     $ACCESSLIB_PRIVATE->dirtycontexts = array();
 1031     $ACCESSLIB_PRIVATE->dirtyusers    = array($USER->id => false);
 1032 
 1033     load_all_capabilities();
 1034 
 1035     foreach ($sw as $path => $roleid) {
 1036         if ($record = $DB->get_record('context', array('path'=>$path))) {
 1037             $context = context::instance_by_id($record->id);
 1038             if (has_capability('moodle/role:switchroles', $context)) {
 1039                 role_switch($roleid, $context);
 1040             }
 1041         }
 1042     }
 1043 }
 1044 
 1045 /**
 1046  * Adds a temp role to current USER->access array.
 1047  *
 1048  * Useful for the "temporary guest" access we grant to logged-in users.
 1049  * This is useful for enrol plugins only.
 1050  *
 1051  * @since Moodle 2.2
 1052  * @param context_course $coursecontext
 1053  * @param int $roleid
 1054  * @return void
 1055  */
 1056 function load_temp_course_role(context_course $coursecontext, $roleid) {
 1057     global $USER, $SITE;
 1058 
 1059     if (empty($roleid)) {
 1060         debugging('invalid role specified in load_temp_course_role()');
 1061         return;
 1062     }
 1063 
 1064     if ($coursecontext->instanceid == $SITE->id) {
 1065         debugging('Can not use temp roles on the frontpage');
 1066         return;
 1067     }
 1068 
 1069     if (!isset($USER->access)) {
 1070         load_all_capabilities();
 1071     }
 1072 
 1073     $coursecontext->reload_if_dirty();
 1074 
 1075     if (isset($USER->access['ra'][$coursecontext->path][$roleid])) {
 1076         return;
 1077     }
 1078 
 1079     $USER->access['ra'][$coursecontext->path][(int)$roleid] = (int)$roleid;
 1080 }
 1081 
 1082 /**
 1083  * Removes any extra guest roles from current USER->access array.
 1084  * This is useful for enrol plugins only.
 1085  *
 1086  * @since Moodle 2.2
 1087  * @param context_course $coursecontext
 1088  * @return void
 1089  */
 1090 function remove_temp_course_roles(context_course $coursecontext) {
 1091     global $DB, $USER, $SITE;
 1092 
 1093     if ($coursecontext->instanceid == $SITE->id) {
 1094         debugging('Can not use temp roles on the frontpage');
 1095         return;
 1096     }
 1097 
 1098     if (empty($USER->access['ra'][$coursecontext->path])) {
 1099         //no roles here, weird
 1100         return;
 1101     }
 1102 
 1103     $sql = "SELECT DISTINCT ra.roleid AS id
 1104               FROM {role_assignments} ra
 1105              WHERE ra.contextid = :contextid AND ra.userid = :userid";
 1106     $ras = $DB->get_records_sql($sql, array('contextid'=>$coursecontext->id, 'userid'=>$USER->id));
 1107 
 1108     $USER->access['ra'][$coursecontext->path] = array();
 1109     foreach($ras as $r) {
 1110         $USER->access['ra'][$coursecontext->path][(int)$r->id] = (int)$r->id;
 1111     }
 1112 }
 1113 
 1114 /**
 1115  * Returns array of all role archetypes.
 1116  *
 1117  * @return array
 1118  */
 1119 function get_role_archetypes() {
 1120     return array(
 1121         'manager'        => 'manager',
 1122         'coursecreator'  => 'coursecreator',
 1123         'editingteacher' => 'editingteacher',
 1124         'teacher'        => 'teacher',
 1125         'student'        => 'student',
 1126         'guest'          => 'guest',
 1127         'user'           => 'user',
 1128         'frontpage'      => 'frontpage'
 1129     );
 1130 }
 1131 
 1132 /**
 1133  * Assign the defaults found in this capability definition to roles that have
 1134  * the corresponding legacy capabilities assigned to them.
 1135  *
 1136  * @param string $capability
 1137  * @param array $legacyperms an array in the format (example):
 1138  *                      'guest' => CAP_PREVENT,
 1139  *                      'student' => CAP_ALLOW,
 1140  *                      'teacher' => CAP_ALLOW,
 1141  *                      'editingteacher' => CAP_ALLOW,
 1142  *                      'coursecreator' => CAP_ALLOW,
 1143  *                      'manager' => CAP_ALLOW
 1144  * @return boolean success or failure.
 1145  */
 1146 function assign_legacy_capabilities($capability, $legacyperms) {
 1147 
 1148     $archetypes = get_role_archetypes();
 1149 
 1150     foreach ($legacyperms as $type => $perm) {
 1151 
 1152         $systemcontext = context_system::instance();
 1153         if ($type === 'admin') {
 1154             debugging('Legacy type admin in access.php was renamed to manager, please update the code.');
 1155             $type = 'manager';
 1156         }
 1157 
 1158         if (!array_key_exists($type, $archetypes)) {
 1159             print_error('invalidlegacy', '', '', $type);
 1160         }
 1161 
 1162         if ($roles = get_archetype_roles($type)) {
 1163             foreach ($roles as $role) {
 1164                 // Assign a site level capability.
 1165                 if (!assign_capability($capability, $perm, $role->id, $systemcontext->id)) {
 1166                     return false;
 1167                 }
 1168             }
 1169         }
 1170     }
 1171     return true;
 1172 }
 1173 
 1174 /**
 1175  * Verify capability risks.
 1176  *
 1177  * @param stdClass $capability a capability - a row from the capabilities table.
 1178  * @return boolean whether this capability is safe - that is, whether people with the
 1179  *      safeoverrides capability should be allowed to change it.
 1180  */
 1181 function is_safe_capability($capability) {
 1182     return !((RISK_DATALOSS | RISK_MANAGETRUST | RISK_CONFIG | RISK_XSS | RISK_PERSONAL) & $capability->riskbitmask);
 1183 }
 1184 
 1185 /**
 1186  * Get the local override (if any) for a given capability in a role in a context
 1187  *
 1188  * @param int $roleid
 1189  * @param int $contextid
 1190  * @param string $capability
 1191  * @return stdClass local capability override
 1192  */
 1193 function get_local_override($roleid, $contextid, $capability) {
 1194     global $DB;
 1195 
 1196     return $DB->get_record_sql("
 1197         SELECT rc.*
 1198           FROM {role_capabilities} rc
 1199           JOIN {capability} cap ON rc.capability = cap.name
 1200          WHERE rc.roleid = :roleid AND rc.capability = :capability AND rc.contextid = :contextid", [
 1201             'roleid' => $roleid,
 1202             'contextid' => $contextid,
 1203             'capability' => $capability,
 1204 
 1205         ]);
 1206 }
 1207 
 1208 /**
 1209  * Returns context instance plus related course and cm instances
 1210  *
 1211  * @param int $contextid
 1212  * @return array of ($context, $course, $cm)
 1213  */
 1214 function get_context_info_array($contextid) {
 1215     global $DB;
 1216 
 1217     $context = context::instance_by_id($contextid, MUST_EXIST);
 1218     $course  = null;
 1219     $cm      = null;
 1220 
 1221     if ($context->contextlevel == CONTEXT_COURSE) {
 1222         $course = $DB->get_record('course', array('id'=>$context->instanceid), '*', MUST_EXIST);
 1223 
 1224     } else if ($context->contextlevel == CONTEXT_MODULE) {
 1225         $cm = get_coursemodule_from_id('', $context->instanceid, 0, false, MUST_EXIST);
 1226         $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
 1227 
 1228     } else if ($context->contextlevel == CONTEXT_BLOCK) {
 1229         $parent = $context->get_parent_context();
 1230 
 1231         if ($parent->contextlevel == CONTEXT_COURSE) {
 1232             $course = $DB->get_record('course', array('id'=>$parent->instanceid), '*', MUST_EXIST);
 1233         } else if ($parent->contextlevel == CONTEXT_MODULE) {
 1234             $cm = get_coursemodule_from_id('', $parent->instanceid, 0, false, MUST_EXIST);
 1235             $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
 1236         }
 1237     }
 1238 
 1239     return array($context, $course, $cm);
 1240 }
 1241 
 1242 /**
 1243  * Function that creates a role
 1244  *
 1245  * @param string $name role name
 1246  * @param string $shortname role short name
 1247  * @param string $description role description
 1248  * @param string $archetype
 1249  * @return int id or dml_exception
 1250  */
 1251 function create_role($name, $shortname, $description, $archetype = '') {
 1252     global $DB;
 1253 
 1254     if (strpos($archetype, 'moodle/legacy:') !== false) {
 1255         throw new coding_exception('Use new role archetype parameter in create_role() instead of old legacy capabilities.');
 1256     }
 1257 
 1258     // verify role archetype actually exists
 1259     $archetypes = get_role_archetypes();
 1260     if (empty($archetypes[$archetype])) {
 1261         $archetype = '';
 1262     }
 1263 
 1264     // Insert the role record.
 1265     $role = new stdClass();
 1266     $role->name        = $name;
 1267     $role->shortname   = $shortname;
 1268     $role->description = $description;
 1269     $role->archetype   = $archetype;
 1270 
 1271     //find free sortorder number
 1272     $role->sortorder = $DB->get_field('role', 'MAX(sortorder) + 1', array());
 1273     if (empty($role->sortorder)) {
 1274         $role->sortorder = 1;
 1275     }
 1276     $id = $DB->insert_record('role', $role);
 1277 
 1278     return $id;
 1279 }
 1280 
 1281 /**
 1282  * Function that deletes a role and cleanups up after it
 1283  *
 1284  * @param int $roleid id of role to delete
 1285  * @return bool always true
 1286  */
 1287 function delete_role($roleid) {
 1288     global $DB;
 1289 
 1290     // first unssign all users
 1291     role_unassign_all(array('roleid'=>$roleid));
 1292 
 1293     // cleanup all references to this role, ignore errors
 1294     $DB->delete_records('role_capabilities',   array('roleid'=>$roleid));
 1295     $DB->delete_records('role_allow_assign',   array('roleid'=>$roleid));
 1296     $DB->delete_records('role_allow_assign',   array('allowassign'=>$roleid));
 1297     $DB->delete_records('role_allow_override', array('roleid'=>$roleid));
 1298     $DB->delete_records('role_allow_override', array('allowoverride'=>$roleid));
 1299     $DB->delete_records('role_names',          array('roleid'=>$roleid));
 1300     $DB->delete_records('role_context_levels', array('roleid'=>$roleid));
 1301 
 1302     // Get role record before it's deleted.
 1303     $role = $DB->get_record('role', array('id'=>$roleid));
 1304 
 1305     // Finally delete the role itself.
 1306     $DB->delete_records('role', array('id'=>$roleid));
 1307 
 1308     // Trigger event.
 1309     $event = \core\event\role_deleted::create(
 1310         array(
 1311             'context' => context_system::instance(),
 1312             'objectid' => $roleid,
 1313             'other' =>
 1314                 array(
 1315                     'shortname' => $role->shortname,
 1316                     'description' => $role->description,
 1317                     'archetype' => $role->archetype
 1318                 )
 1319             )
 1320         );
 1321     $event->add_record_snapshot('role', $role);
 1322     $event->trigger();
 1323 
 1324     // Reset any cache of this role, including MUC.
 1325     accesslib_clear_role_cache($roleid);
 1326 
 1327     return true;
 1328 }
 1329 
 1330 /**
 1331  * Function to write context specific overrides, or default capabilities.
 1332  *
 1333  * @param string $capability string name
 1334  * @param int $permission CAP_ constants
 1335  * @param int $roleid role id
 1336  * @param int|context $contextid context id
 1337  * @param bool $overwrite
 1338  * @return bool always true or exception
 1339  */
 1340 function assign_capability($capability, $permission, $roleid, $contextid, $overwrite = false) {
 1341     global $USER, $DB;
 1342 
 1343     if ($contextid instanceof context) {
 1344         $context = $contextid;
 1345     } else {
 1346         $context = context::instance_by_id($contextid);
 1347     }
 1348 
 1349     // Capability must exist.
 1350     if (!$capinfo = get_capability_info($capability)) {
 1351         throw new coding_exception("Capability '{$capability}' was not found! This has to be fixed in code.");
 1352     }
 1353 
 1354     if (empty($permission) || $permission == CAP_INHERIT) { // if permission is not set
 1355         unassign_capability($capability, $roleid, $context->id);
 1356         return true;
 1357     }
 1358 
 1359     $existing = $DB->get_record('role_capabilities', array('contextid'=>$context->id, 'roleid'=>$roleid, 'capability'=>$capability));
 1360 
 1361     if ($existing and !$overwrite) {   // We want to keep whatever is there already
 1362         return true;
 1363     }
 1364 
 1365     $cap = new stdClass();
 1366     $cap->contextid    = $context->id;
 1367     $cap->roleid       = $roleid;
 1368     $cap->capability   = $capability;
 1369     $cap->permission   = $permission;
 1370     $cap->timemodified = time();
 1371     $cap->modifierid   = empty($USER->id) ? 0 : $USER->id;
 1372 
 1373     if ($existing) {
 1374         $cap->id = $existing->id;
 1375         $DB->update_record('role_capabilities', $cap);
 1376     } else {
 1377         if ($DB->record_exists('context', array('id'=>$context->id))) {
 1378             $DB->insert_record('role_capabilities', $cap);
 1379         }
 1380     }
 1381 
 1382     // Reset any cache of this role, including MUC.
 1383     accesslib_clear_role_cache($roleid);
 1384 
 1385     return true;
 1386 }
 1387 
 1388 /**
 1389  * Unassign a capability from a role.
 1390  *
 1391  * @param string $capability the name of the capability
 1392  * @param int $roleid the role id
 1393  * @param int|context $contextid null means all contexts
 1394  * @return boolean true or exception
 1395  */
 1396 function unassign_capability($capability, $roleid, $contextid = null) {
 1397     global $DB;
 1398 
 1399     // Capability must exist.
 1400     if (!$capinfo = get_capability_info($capability)) {
 1401         throw new coding_exception("Capability '{$capability}' was not found! This has to be fixed in code.");
 1402     }
 1403 
 1404     if (!empty($contextid)) {
 1405         if ($contextid instanceof context) {
 1406             $context = $contextid;
 1407         } else {
 1408             $context = context::instance_by_id($contextid);
 1409         }
 1410         // delete from context rel, if this is the last override in this context
 1411         $DB->delete_records('role_capabilities', array('capability'=>$capability, 'roleid'=>$roleid, 'contextid'=>$context->id));
 1412     } else {
 1413         $DB->delete_records('role_capabilities', array('capability'=>$capability, 'roleid'=>$roleid));
 1414     }
 1415 
 1416     // Reset any cache of this role, including MUC.
 1417     accesslib_clear_role_cache($roleid);
 1418 
 1419     return true;
 1420 }
 1421 
 1422 /**
 1423  * Get the roles that have a given capability assigned to it
 1424  *
 1425  * This function does not resolve the actual permission of the capability.
 1426  * It just checks for permissions and overrides.
 1427  * Use get_roles_with_cap_in_context() if resolution is required.
 1428  *
 1429  * @param string $capability capability name (string)
 1430  * @param string $permission optional, the permission defined for this capability
 1431  *                      either CAP_ALLOW, CAP_PREVENT or CAP_PROHIBIT. Defaults to null which means any.
 1432  * @param stdClass $context null means any
 1433  * @return array of role records
 1434  */
 1435 function get_roles_with_capability($capability, $permission = null, $context = null) {
 1436     global $DB;
 1437 
 1438     if ($context) {
 1439         $contexts = $context->get_parent_context_ids(true);
 1440         list($insql, $params) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED, 'ctx');
 1441         $contextsql = "AND rc.contextid $insql";
 1442     } else {
 1443         $params = array();
 1444         $contextsql = '';
 1445     }
 1446 
 1447     if ($permission) {
 1448         $permissionsql = " AND rc.permission = :permission";
 1449         $params['permission'] = $permission;
 1450     } else {
 1451         $permissionsql = '';
 1452     }
 1453 
 1454     $sql = "SELECT r.*
 1455               FROM {role} r
 1456              WHERE r.id IN (SELECT rc.roleid
 1457                               FROM {role_capabilities} rc
 1458                               JOIN {capabilities} cap ON rc.capability = cap.name
 1459                              WHERE rc.capability = :capname
 1460                                    $contextsql
 1461                                    $permissionsql)";
 1462     $params['capname'] = $capability;
 1463 
 1464 
 1465     return $DB->get_records_sql($sql, $params);
 1466 }
 1467 
 1468 /**
 1469  * This function makes a role-assignment (a role for a user in a particular context)
 1470  *
 1471  * @param int $roleid the role of the id
 1472  * @param int $userid userid
 1473  * @param int|context $contextid id of the context
 1474  * @param string $component example 'enrol_ldap', defaults to '' which means manual assignment,
 1475  * @param int $itemid id of enrolment/auth plugin
 1476  * @param string $timemodified defaults to current time
 1477  * @return int new/existing id of the assignment
 1478  */
 1479 function role_assign($roleid, $userid, $contextid, $component = '', $itemid = 0, $timemodified = '') {
 1480     global $USER, $DB;
 1481 
 1482     // first of all detect if somebody is using old style parameters
 1483     if ($contextid === 0 or is_numeric($component)) {
 1484         throw new coding_exception('Invalid call to role_assign(), code needs to be updated to use new order of parameters');
 1485     }
 1486 
 1487     // now validate all parameters
 1488     if (empty($roleid)) {
 1489         throw new coding_exception('Invalid call to role_assign(), roleid can not be empty');
 1490     }
 1491 
 1492     if (empty($userid)) {
 1493         throw new coding_exception('Invalid call to role_assign(), userid can not be empty');
 1494     }
 1495 
 1496     if ($itemid) {
 1497         if (strpos($component, '_') === false) {
 1498             throw new coding_exception('Invalid call to role_assign(), component must start with plugin type such as"enrol_" when itemid specified', 'component:'.$component);
 1499         }
 1500     } else {
 1501         $itemid = 0;
 1502         if ($component !== '' and strpos($component, '_') === false) {
 1503             throw new coding_exception('Invalid call to role_assign(), invalid component string', 'component:'.$component);
 1504         }
 1505     }
 1506 
 1507     if (!$DB->record_exists('user', array('id'=>$userid, 'deleted'=>0))) {
 1508         throw new coding_exception('User ID does not exist or is deleted!', 'userid:'.$userid);
 1509     }
 1510 
 1511     if ($contextid instanceof context) {
 1512         $context = $contextid;
 1513     } else {
 1514         $context = context::instance_by_id($contextid, MUST_EXIST);
 1515     }
 1516 
 1517     if (!$timemodified) {
 1518         $timemodified = time();
 1519     }
 1520 
 1521     // Check for existing entry
 1522     $ras = $DB->get_records('role_assignments', array('roleid'=>$roleid, 'contextid'=>$context->id, 'userid'=>$userid, 'component'=>$component, 'itemid'=>$itemid), 'id');
 1523 
 1524     if ($ras) {
 1525         // role already assigned - this should not happen
 1526         if (count($ras) > 1) {
 1527             // very weird - remove all duplicates!
 1528             $ra = array_shift($ras);
 1529             foreach ($ras as $r) {
 1530                 $DB->delete_records('role_assignments', array('id'=>$r->id));
 1531             }
 1532         } else {
 1533             $ra = reset($ras);
 1534         }
 1535 
 1536         // actually there is no need to update, reset anything or trigger any event, so just return
 1537         return $ra->id;
 1538     }
 1539 
 1540     // Create a new entry
 1541     $ra = new stdClass();
 1542     $ra->roleid       = $roleid;
 1543     $ra->contextid    = $context->id;
 1544     $ra->userid       = $userid;
 1545     $ra->component    = $component;
 1546     $ra->itemid       = $itemid;
 1547     $ra->timemodified = $timemodified;
 1548     $ra->modifierid   = empty($USER->id) ? 0 : $USER->id;
 1549     $ra->sortorder    = 0;
 1550 
 1551     $ra->id = $DB->insert_record('role_assignments', $ra);
 1552 
 1553     // Role assignments have changed, so mark user as dirty.
 1554     mark_user_dirty($userid);
 1555 
 1556     core_course_category::role_assignment_changed($roleid, $context);
 1557 
 1558     $event = \core\event\role_assigned::create(array(
 1559         'context' => $context,
 1560         'objectid' => $ra->roleid,
 1561         'relateduserid' => $ra->userid,
 1562         'other' => array(
 1563             'id' => $ra->id,
 1564             'component' => $ra->component,
 1565             'itemid' => $ra->itemid
 1566         )
 1567     ));
 1568     $event->add_record_snapshot('role_assignments', $ra);
 1569     $event->trigger();
 1570 
 1571     return $ra->id;
 1572 }
 1573 
 1574 /**
 1575  * Removes one role assignment
 1576  *
 1577  * @param int $roleid
 1578  * @param int  $userid
 1579  * @param int  $contextid
 1580  * @param string $component
 1581  * @param int  $itemid
 1582  * @return void
 1583  */
 1584 function role_unassign($roleid, $userid, $contextid, $component = '', $itemid = 0) {
 1585     // first make sure the params make sense
 1586     if ($roleid == 0 or $userid == 0 or $contextid == 0) {
 1587         throw new coding_exception('Invalid call to role_unassign(), please use role_unassign_all() when removing multiple role assignments');
 1588     }
 1589 
 1590     if ($itemid) {
 1591         if (strpos($component, '_') === false) {
 1592             throw new coding_exception('Invalid call to role_assign(), component must start with plugin type such as "enrol_" when itemid specified', 'component:'.$component);
 1593         }
 1594     } else {
 1595         $itemid = 0;
 1596         if ($component !== '' and strpos($component, '_') === false) {
 1597             throw new coding_exception('Invalid call to role_assign(), invalid component string', 'component:'.$component);
 1598         }
 1599     }
 1600 
 1601     role_unassign_all(array('roleid'=>$roleid, 'userid'=>$userid, 'contextid'=>$contextid, 'component'=>$component, 'itemid'=>$itemid), false, false);
 1602 }
 1603 
 1604 /**
 1605  * Removes multiple role assignments, parameters may contain:
 1606  *   'roleid', 'userid', 'contextid', 'component', 'enrolid'.
 1607  *
 1608  * @param array $params role assignment parameters
 1609  * @param bool $subcontexts unassign in subcontexts too
 1610  * @param bool $includemanual include manual role assignments too
 1611  * @return void
 1612  */
 1613 function role_unassign_all(array $params, $subcontexts = false, $includemanual = false) {
 1614     global $USER, $CFG, $DB;
 1615 
 1616     if (!$params) {
 1617         throw new coding_exception('Missing parameters in role_unsassign_all() call');
 1618     }
 1619 
 1620     $allowed = array('roleid', 'userid', 'contextid', 'component', 'itemid');
 1621     foreach ($params as $key=>$value) {
 1622         if (!in_array($key, $allowed)) {
 1623             throw new coding_exception('Unknown role_unsassign_all() parameter key', 'key:'.$key);
 1624         }
 1625     }
 1626 
 1627     if (isset($params['component']) and $params['component'] !== '' and strpos($params['component'], '_') === false) {
 1628         throw new coding_exception('Invalid component paramter in role_unsassign_all() call', 'component:'.$params['component']);
 1629     }
 1630 
 1631     if ($includemanual) {
 1632         if (!isset($params['component']) or $params['component'] === '') {
 1633             throw new coding_exception('include manual parameter requires component parameter in role_unsassign_all() call');
 1634         }
 1635     }
 1636 
 1637     if ($subcontexts) {
 1638         if (empty($params['contextid'])) {
 1639             throw new coding_exception('subcontexts paramtere requires component parameter in role_unsassign_all() call');
 1640         }
 1641     }
 1642 
 1643     $ras = $DB->get_records('role_assignments', $params);
 1644     foreach($ras as $ra) {
 1645         $DB->delete_records('role_assignments', array('id'=>$ra->id));
 1646         if ($context = context::instance_by_id($ra->contextid, IGNORE_MISSING)) {
 1647             // Role assignments have changed, so mark user as dirty.
 1648             mark_user_dirty($ra->userid);
 1649 
 1650             $event = \core\event\role_unassigned::create(array(
 1651                 'context' => $context,
 1652                 'objectid' => $ra->roleid,
 1653                 'relateduserid' => $ra->userid,
 1654                 'other' => array(
 1655                     'id' => $ra->id,
 1656                     'component' => $ra->component,
 1657                     'itemid' => $ra->itemid
 1658                 )
 1659             ));
 1660             $event->add_record_snapshot('role_assignments', $ra);
 1661             $event->trigger();
 1662             core_course_category::role_assignment_changed($ra->roleid, $context);
 1663         }
 1664     }
 1665     unset($ras);
 1666 
 1667     // process subcontexts
 1668     if ($subcontexts and $context = context::instance_by_id($params['contextid'], IGNORE_MISSING)) {
 1669         if ($params['contextid'] instanceof context) {
 1670             $context = $params['contextid'];
 1671         } else {
 1672             $context = context::instance_by_id($params['contextid'], IGNORE_MISSING);
 1673         }
 1674 
 1675         if ($context) {
 1676             $contexts = $context->get_child_contexts();
 1677             $mparams = $params;
 1678             foreach($contexts as $context) {
 1679                 $mparams['contextid'] = $context->id;
 1680                 $ras = $DB->get_records('role_assignments', $mparams);
 1681                 foreach($ras as $ra) {
 1682                     $DB->delete_records('role_assignments', array('id'=>$ra->id));
 1683                     // Role assignments have changed, so mark user as dirty.
 1684                     mark_user_dirty($ra->userid);
 1685 
 1686                     $event = \core\event\role_unassigned::create(
 1687                         array('context'=>$context, 'objectid'=>$ra->roleid, 'relateduserid'=>$ra->userid,
 1688                             'other'=>array('id'=>$ra->id, 'component'=>$ra->component, 'itemid'=>$ra->itemid)));
 1689                     $event->add_record_snapshot('role_assignments', $ra);
 1690                     $event->trigger();
 1691                     core_course_category::role_assignment_changed($ra->roleid, $context);
 1692                 }
 1693             }
 1694         }
 1695     }
 1696 
 1697     // do this once more for all manual role assignments
 1698     if ($includemanual) {
 1699         $params['component'] = '';
 1700         role_unassign_all($params, $subcontexts, false);
 1701     }
 1702 }
 1703 
 1704 /**
 1705  * Mark a user as dirty (with timestamp) so as to force reloading of the user session.
 1706  *
 1707  * @param int $userid
 1708  * @return void
 1709  */
 1710 function mark_user_dirty($userid) {
 1711     global $CFG, $ACCESSLIB_PRIVATE;
 1712 
 1713     if (during_initial_install()) {
 1714         return;
 1715     }
 1716 
 1717     // Throw exception if invalid userid is provided.
 1718     if (empty($userid)) {
 1719         throw new coding_exception('Invalid user parameter supplied for mark_user_dirty() function!');
 1720     }
 1721 
 1722     // Set dirty flag in database, set dirty field locally, and clear local accessdata cache.
 1723     set_cache_flag('accesslib/dirtyusers', $userid, 1, time() + $CFG->sessiontimeout);
 1724     $ACCESSLIB_PRIVATE->dirtyusers[$userid] = 1;
 1725     unset($ACCESSLIB_PRIVATE->accessdatabyuser[$userid]);
 1726 }
 1727 
 1728 /**
 1729  * Determines if a user is currently logged in
 1730  *
 1731  * @category   access
 1732  *
 1733  * @return bool
 1734  */
 1735 function isloggedin() {
 1736     global $USER;
 1737 
 1738     return (!empty($USER->id));
 1739 }
 1740 
 1741 /**
 1742  * Determines if a user is logged in as real guest user with username 'guest'.
 1743  *
 1744  * @category   access
 1745  *
 1746  * @param int|object $user mixed user object or id, $USER if not specified
 1747  * @return bool true if user is the real guest user, false if not logged in or other user
 1748  */
 1749 function isguestuser($user = null) {
 1750     global $USER, $DB, $CFG;
 1751 
 1752     // make sure we have the user id cached in config table, because we are going to use it a lot
 1753     if (empty($CFG->siteguest)) {
 1754         if (!$guestid = $DB->get_field('user', 'id', array('username'=>'guest', 'mnethostid'=>$CFG->mnet_localhost_id))) {
 1755             // guest does not exist yet, weird
 1756             return false;
 1757         }
 1758         set_config('siteguest', $guestid);
 1759     }
 1760     if ($user === null) {
 1761         $user = $USER;
 1762     }
 1763 
 1764     if ($user === null) {
 1765         // happens when setting the $USER
 1766         return false;
 1767 
 1768     } else if (is_numeric($user)) {
 1769         return ($CFG->siteguest == $user);
 1770 
 1771     } else if (is_object($user)) {
 1772         if (empty($user->id)) {
 1773             return false; // not logged in means is not be guest
 1774         } else {
 1775             return ($CFG->siteguest == $user->id);
 1776         }
 1777 
 1778     } else {
 1779         throw new coding_exception('Invalid user parameter supplied for isguestuser() function!');
 1780     }
 1781 }
 1782 
 1783 /**
 1784  * Does user have a (temporary or real) guest access to course?
 1785  *
 1786  * @category   access
 1787  *
 1788  * @param context $context
 1789  * @param stdClass|int $user
 1790  * @return bool
 1791  */
 1792 function is_guest(context $context, $user = null) {
 1793     global $USER;
 1794 
 1795     // first find the course context
 1796     $coursecontext = $context->get_course_context();
 1797 
 1798     // make sure there is a real user specified
 1799     if ($user === null) {
 1800         $userid = isset($USER->id) ? $USER->id : 0;
 1801     } else {
 1802         $userid = is_object($user) ? $user->id : $user;
 1803     }
 1804 
 1805     if (isguestuser($userid)) {
 1806         // can not inspect or be enrolled
 1807         return true;
 1808     }
 1809 
 1810     if (has_capability('moodle/course:view', $coursecontext, $user)) {
 1811         // viewing users appear out of nowhere, they are neither guests nor participants
 1812         return false;
 1813     }
 1814 
 1815     // consider only real active enrolments here
 1816     if (is_enrolled($coursecontext, $user, '', true)) {
 1817         return false;
 1818     }
 1819 
 1820     return true;
 1821 }
 1822 
 1823 /**
 1824  * Returns true if the user has moodle/course:view capability in the course,
 1825  * this is intended for admins, managers (aka small admins), inspectors, etc.
 1826  *
 1827  * @category   access
 1828  *
 1829  * @param context $context
 1830  * @param int|stdClass $user if null $USER is used
 1831  * @param string $withcapability extra capability name
 1832  * @return bool
 1833  */
 1834 function is_viewing(context $context, $user = null, $withcapability = '') {
 1835     // first find the course context
 1836     $coursecontext = $context->get_course_context();
 1837 
 1838     if (isguestuser($user)) {
 1839         // can not inspect
 1840         return false;
 1841     }
 1842 
 1843     if (!has_capability('moodle/course:view', $coursecontext, $user)) {
 1844         // admins are allowed to inspect courses
 1845         return false;
 1846     }
 1847 
 1848     if ($withcapability and !has_capability($withcapability, $context, $user)) {
 1849         // site admins always have the capability, but the enrolment above blocks
 1850         return false;
 1851     }
 1852 
 1853     return true;
 1854 }
 1855 
 1856 /**
 1857  * Returns true if the user is able to access the course.
 1858  *
 1859  * This function is in no way, shape, or form a substitute for require_login.
 1860  * It should only be used in circumstances where it is not possible to call require_login
 1861  * such as the navigation.
 1862  *
 1863  * This function checks many of the methods of access to a course such as the view
 1864  * capability, enrollments, and guest access. It also makes use of the cache
 1865  * generated by require_login for guest access.
 1866  *
 1867  * The flags within the $USER object that are used here should NEVER be used outside
 1868  * of this function can_access_course and require_login. Doing so WILL break future
 1869  * versions.
 1870  *
 1871  * @param stdClass $course record
 1872  * @param stdClass|int|null $user user record or id, current user if null
 1873  * @param string $withcapability Check for this capability as well.
 1874  * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
 1875  * @return boolean Returns true if the user is able to access the course
 1876  */
 1877 function can_access_course(stdClass $course, $user = null, $withcapability = '', $onlyactive = false) {
 1878     global $DB, $USER;
 1879 
 1880     // this function originally accepted $coursecontext parameter
 1881     if ($course instanceof context) {
 1882         if ($course instanceof context_course) {
 1883             debugging('deprecated context parameter, please use $course record');
 1884             $coursecontext = $course;
 1885             $course = $DB->get_record('course', array('id'=>$coursecontext->instanceid));
 1886         } else {
 1887             debugging('Invalid context parameter, please use $course record');
 1888             return false;
 1889         }
 1890     } else {
 1891         $coursecontext = context_course::instance($course->id);
 1892     }
 1893 
 1894     if (!isset($USER->id)) {
 1895         // should never happen
 1896         $USER->id = 0;
 1897         debugging('Course access check being performed on a user with no ID.', DEBUG_DEVELOPER);
 1898     }
 1899 
 1900     // make sure there is a user specified
 1901     if ($user === null) {
 1902         $userid = $USER->id;
 1903     } else {
 1904         $userid = is_object($user) ? $user->id : $user;
 1905     }
 1906     unset($user);
 1907 
 1908     if ($withcapability and !has_capability($withcapability, $coursecontext, $userid)) {
 1909         return false;
 1910     }
 1911 
 1912     if ($userid == $USER->id) {
 1913         if (!empty($USER->access['rsw'][$coursecontext->path])) {
 1914             // the fact that somebody switched role means they can access the course no matter to what role they switched
 1915             return true;
 1916         }
 1917     }
 1918 
 1919     if (!$course->visible and !has_capability('moodle/course:viewhiddencourses', $coursecontext, $userid)) {
 1920         return false;
 1921     }
 1922 
 1923     if (is_viewing($coursecontext, $userid)) {
 1924         return true;
 1925     }
 1926 
 1927     if ($userid != $USER->id) {
 1928         // for performance reasons we do not verify temporary guest access for other users, sorry...
 1929         return is_enrolled($coursecontext, $userid, '', $onlyactive);
 1930     }
 1931 
 1932     // === from here we deal only with $USER ===
 1933 
 1934     $coursecontext->reload_if_dirty();
 1935 
 1936     if (isset($USER->enrol['enrolled'][$course->id])) {
 1937         if ($USER->enrol['enrolled'][$course->id] > time()) {
 1938             return true;
 1939         }
 1940     }
 1941     if (isset($USER->enrol['tempguest'][$course->id])) {
 1942         if ($USER->enrol['tempguest'][$course->id] > time()) {
 1943             return true;
 1944         }
 1945     }
 1946 
 1947     if (is_enrolled($coursecontext, $USER, '', $onlyactive)) {
 1948         return true;
 1949     }
 1950 
 1951     // if not enrolled try to gain temporary guest access
 1952     $instances = $DB->get_records('enrol', array('courseid'=>$course->id, 'status'=>ENROL_INSTANCE_ENABLED), 'sortorder, id ASC');
 1953     $enrols = enrol_get_plugins(true);
 1954     foreach($instances as $instance) {
 1955         if (!isset($enrols[$instance->enrol])) {
 1956             continue;
 1957         }
 1958         // Get a duration for the guest access, a timestamp in the future, 0 (always) or false.
 1959         $until = $enrols[$instance->enrol]->try_guestaccess($instance);
 1960         if ($until !== false and $until > time()) {
 1961             $USER->enrol['tempguest'][$course->id] = $until;
 1962             return true;
 1963         }
 1964     }
 1965     if (isset($USER->enrol['tempguest'][$course->id])) {
 1966         unset($USER->enrol['tempguest'][$course->id]);
 1967         remove_temp_course_roles($coursecontext);
 1968     }
 1969 
 1970     return false;
 1971 }
 1972 
 1973 /**
 1974  * Loads the capability definitions for the component (from file).
 1975  *
 1976  * Loads the capability definitions for the component (from file). If no
 1977  * capabilities are defined for the component, we simply return an empty array.
 1978  *
 1979  * @access private
 1980  * @param string $component full plugin name, examples: 'moodle', 'mod_forum'
 1981  * @return array array of capabilities
 1982  */
 1983 function load_capability_def($component) {
 1984     $defpath = core_component::get_component_directory($component).'/db/access.php';
 1985 
 1986     $capabilities = array();
 1987     if (file_exists($defpath)) {
 1988         require($defpath);
 1989         if (!empty(${$component.'_capabilities'})) {
 1990             // BC capability array name
 1991             // since 2.0 we prefer $capabilities instead - it is easier to use and matches db/* files
 1992             debugging('componentname_capabilities array is deprecated, please use $capabilities array only in access.php files');
 1993             $capabilities = ${$component.'_capabilities'};
 1994         }
 1995     }
 1996 
 1997     return $capabilities;
 1998 }
 1999 
 2000 /**
 2001  * Gets the capabilities that have been cached in the database for this component.
 2002  *
 2003  * @access private
 2004  * @param string $component - examples: 'moodle', 'mod_forum'
 2005  * @return array array of capabilities
 2006  */
 2007 function get_cached_capabilities($component = 'moodle') {
 2008     global $DB;
 2009     $caps = get_all_capabilities();
 2010     $componentcaps = array();
 2011     foreach ($caps as $cap) {
 2012         if ($cap['component'] == $component) {
 2013             $componentcaps[] = (object) $cap;
 2014         }
 2015     }
 2016     return $componentcaps;
 2017 }
 2018 
 2019 /**
 2020  * Returns default capabilities for given role archetype.
 2021  *
 2022  * @param string $archetype role archetype
 2023  * @return array
 2024  */
 2025 function get_default_capabilities($archetype) {
 2026     global $DB;
 2027 
 2028     if (!$archetype) {
 2029         return array();
 2030     }
 2031 
 2032     $alldefs = array();
 2033     $defaults = array();
 2034     $components = array();
 2035     $allcaps = get_all_capabilities();
 2036 
 2037     foreach ($allcaps as $cap) {
 2038         if (!in_array($cap['component'], $components)) {
 2039             $components[] = $cap['component'];
 2040             $alldefs = array_merge($alldefs, load_capability_def($cap['component']));
 2041         }
 2042     }
 2043     foreach($alldefs as $name=>$def) {
 2044         // Use array 'archetypes if available. Only if not specified, use 'legacy'.
 2045         if (isset($def['archetypes'])) {
 2046             if (isset($def['archetypes'][$archetype])) {
 2047                 $defaults[$name] = $def['archetypes'][$archetype];
 2048             }
 2049         // 'legacy' is for backward compatibility with 1.9 access.php
 2050         } else {
 2051             if (isset($def['legacy'][$archetype])) {
 2052                 $defaults[$name] = $def['legacy'][$archetype];
 2053             }
 2054         }
 2055     }
 2056 
 2057     return $defaults;
 2058 }
 2059 
 2060 /**
 2061  * Return default roles that can be assigned, overridden or switched
 2062  * by give role archetype.
 2063  *
 2064  * @param string $type  assign|override|switch|view
 2065  * @param string $archetype
 2066  * @return array of role ids
 2067  */
 2068 function get_default_role_archetype_allows($type, $archetype) {
 2069     global $DB;
 2070 
 2071     if (empty($archetype)) {
 2072         return array();
 2073     }
 2074 
 2075     $roles = $DB->get_records('role');
 2076     $archetypemap = array();
 2077     foreach ($roles as $role) {
 2078         if ($role->archetype) {
 2079             $archetypemap[$role->archetype][$role->id] = $role->id;
 2080         }
 2081     }
 2082 
 2083     $defaults = array(
 2084         'assign' => array(
 2085             'manager'        => array('manager', 'coursecreator', 'editingteacher', 'teacher', 'student'),
 2086             'coursecreator'  => array(),
 2087             'editingteacher' => array('teacher', 'student'),
 2088             'teacher'        => array(),
 2089             'student'        => array(),
 2090             'guest'          => array(),
 2091             'user'           => array(),
 2092             'frontpage'      => array(),
 2093         ),
 2094         'override' => array(
 2095             'manager'        => array('manager', 'coursecreator', 'editingteacher', 'teacher', 'student', 'guest', 'user', 'frontpage'),
 2096             'coursecreator'  => array(),
 2097             'editingteacher' => array('teacher', 'student', 'guest'),
 2098             'teacher'        => array(),
 2099             'student'        => array(),
 2100             'guest'          => array(),
 2101             'user'           => array(),
 2102             'frontpage'      => array(),
 2103         ),
 2104         'switch' => array(
 2105             'manager'        => array('editingteacher', 'teacher', 'student', 'guest'),
 2106             'coursecreator'  => array(),
 2107             'editingteacher' => array('teacher', 'student', 'guest'),
 2108             'teacher'        => array('student', 'guest'),
 2109             'student'        => array(),
 2110             'guest'          => array(),
 2111             'user'           => array(),
 2112             'frontpage'      => array(),
 2113         ),
 2114         'view' => array(
 2115             'manager'        => array('manager', 'coursecreator', 'editingteacher', 'teacher', 'student', 'guest', 'user', 'frontpage'),
 2116             'coursecreator'  => array('coursecreator', 'editingteacher', 'teacher', 'student'),
 2117             'editingteacher' => array('coursecreator', 'editingteacher', 'teacher', 'student'),
 2118             'teacher'        => array('coursecreator', 'editingteacher', 'teacher', 'student'),
 2119             'student'        => array('coursecreator', 'editingteacher', 'teacher', 'student'),
 2120             'guest'          => array(),
 2121             'user'           => array(),
 2122             'frontpage'      => array(),
 2123         ),
 2124     );
 2125 
 2126     if (!isset($defaults[$type][$archetype])) {
 2127         debugging("Unknown type '$type'' or archetype '$archetype''");
 2128         return array();
 2129     }
 2130 
 2131     $return = array();
 2132     foreach ($defaults[$type][$archetype] as $at) {
 2133         if (isset($archetypemap[$at])) {
 2134             foreach ($archetypemap[$at] as $roleid) {
 2135                 $return[$roleid] = $roleid;
 2136             }
 2137         }
 2138     }
 2139 
 2140     return $return;
 2141 }
 2142 
 2143 /**
 2144  * Reset role capabilities to default according to selected role archetype.
 2145  * If no archetype selected, removes all capabilities.
 2146  *
 2147  * This applies to capabilities that are assigned to the role (that you could
 2148  * edit in the 'define roles' interface), and not to any capability overrides
 2149  * in different locations.
 2150  *
 2151  * @param int $roleid ID of role to reset capabilities for
 2152  */
 2153 function reset_role_capabilities($roleid) {
 2154     global $DB;
 2155 
 2156     $role = $DB->get_record('role', array('id'=>$roleid), '*', MUST_EXIST);
 2157     $defaultcaps = get_default_capabilities($role->archetype);
 2158 
 2159     $systemcontext = context_system::instance();
 2160 
 2161     $DB->delete_records('role_capabilities',
 2162             array('roleid' => $roleid, 'contextid' => $systemcontext->id));
 2163 
 2164     foreach($defaultcaps as $cap=>$permission) {
 2165         assign_capability($cap, $permission, $roleid, $systemcontext->id);
 2166     }
 2167 
 2168     // Reset any cache of this role, including MUC.
 2169     accesslib_clear_role_cache($roleid);
 2170 }
 2171 
 2172 /**
 2173  * Updates the capabilities table with the component capability definitions.
 2174  * If no parameters are given, the function updates the core moodle
 2175  * capabilities.
 2176  *
 2177  * Note that the absence of the db/access.php capabilities definition file
 2178  * will cause any stored capabilities for the component to be removed from
 2179  * the database.
 2180  *
 2181  * @access private
 2182  * @param string $component examples: 'moodle', 'mod/forum', 'block/quiz_results'
 2183  * @return boolean true if success, exception in case of any problems
 2184  */
 2185 function update_capabilities($component = 'moodle') {
 2186     global $DB, $OUTPUT;
 2187 
 2188     $storedcaps = array();
 2189 
 2190     $filecaps = load_capability_def($component);
 2191     foreach($filecaps as $capname=>$unused) {
 2192         if (!preg_match('|^[a-z]+/[a-z_0-9]+:[a-z_0-9]+$|', $capname)) {
 2193             debugging("Coding problem: Invalid capability name '$capname', use 'clonepermissionsfrom' field for migration.");
 2194         }
 2195     }
 2196 
 2197     // It is possible somebody directly modified the DB (according to accesslib_test anyway).
 2198     // So ensure our updating is based on fresh data.
 2199     cache::make('core', 'capabilities')->delete('core_capabilities');
 2200 
 2201     $cachedcaps = get_cached_capabilities($component);
 2202     if ($cachedcaps) {
 2203         foreach ($cachedcaps as $cachedcap) {
 2204             array_push($storedcaps, $cachedcap->name);
 2205             // update risk bitmasks and context levels in existing capabilities if needed
 2206             if (array_key_exists($cachedcap->name, $filecaps)) {
 2207                 if (!array_key_exists('riskbitmask', $filecaps[$cachedcap->name])) {
 2208                     $filecaps[$cachedcap->name]['riskbitmask'] = 0; // no risk if not specified
 2209                 }
 2210                 if ($cachedcap->captype != $filecaps[$cachedcap->name]['captype']) {
 2211                     $updatecap = new stdClass();
 2212                     $updatecap->id = $cachedcap->id;
 2213                     $updatecap->captype = $filecaps[$cachedcap->name]['captype'];
 2214                     $DB->update_record('capabilities', $updatecap);
 2215                 }
 2216                 if ($cachedcap->riskbitmask != $filecaps[$cachedcap->name]['riskbitmask']) {
 2217                     $updatecap = new stdClass();
 2218                     $updatecap->id = $cachedcap->id;
 2219                     $updatecap->riskbitmask = $filecaps[$cachedcap->name]['riskbitmask'];
 2220                     $DB->update_record('capabilities', $updatecap);
 2221                 }
 2222 
 2223                 if (!array_key_exists('contextlevel', $filecaps[$cachedcap->name])) {
 2224                     $filecaps[$cachedcap->name]['contextlevel'] = 0; // no context level defined
 2225                 }
 2226                 if ($cachedcap->contextlevel != $filecaps[$cachedcap->name]['contextlevel']) {
 2227                     $updatecap = new stdClass();
 2228                     $updatecap->id = $cachedcap->id;
 2229                     $updatecap->contextlevel = $filecaps[$cachedcap->name]['contextlevel'];
 2230                     $DB->update_record('capabilities', $updatecap);
 2231                 }
 2232             }
 2233         }
 2234     }
 2235 
 2236     // Flush the cached again, as we have changed DB.
 2237     cache::make('core', 'capabilities')->delete('core_capabilities');
 2238 
 2239     // Are there new capabilities in the file definition?
 2240     $newcaps = array();
 2241 
 2242     foreach ($filecaps as $filecap => $def) {
 2243         if (!$storedcaps ||
 2244                 ($storedcaps && in_array($filecap, $storedcaps) === false)) {
 2245             if (!array_key_exists('riskbitmask', $def)) {
 2246                 $def['riskbitmask'] = 0; // no risk if not specified
 2247             }
 2248             $newcaps[$filecap] = $def;
 2249         }
 2250     }
 2251     // Add new capabilities to the stored definition.
 2252     $existingcaps = $DB->get_records_menu('capabilities', array(), 'id', 'id, name');
 2253     foreach ($newcaps as $capname => $capdef) {
 2254         $capability = new stdClass();
 2255         $capability->name         = $capname;
 2256         $capability->captype      = $capdef['captype'];
 2257         $capability->contextlevel = $capdef['contextlevel'];
 2258         $capability->component    = $component;
 2259         $capability->riskbitmask  = $capdef['riskbitmask'];
 2260 
 2261         $DB->insert_record('capabilities', $capability, false);
 2262 
 2263         // Flush the cached, as we have changed DB.
 2264         cache::make('core', 'capabilities')->delete('core_capabilities');
 2265 
 2266         if (isset($capdef['clonepermissionsfrom']) && in_array($capdef['clonepermissionsfrom'], $existingcaps)){
 2267             if ($rolecapabilities = $DB->get_records('role_capabilities', array('capability'=>$capdef['clonepermissionsfrom']))){
 2268                 foreach ($rolecapabilities as $rolecapability){
 2269                     //assign_capability will update rather than insert if capability exists
 2270                     if (!assign_capability($capname, $rolecapability->permission,
 2271                                             $rolecapability->roleid, $rolecapability->contextid, true)){
 2272                          echo $OUTPUT->notification('Could not clone capabilities for '.$capname);
 2273                     }
 2274                 }
 2275             }
 2276         // we ignore archetype key if we have cloned permissions
 2277         } else if (isset($capdef['archetypes']) && is_array($capdef['archetypes'])) {
 2278             assign_legacy_capabilities($capname, $capdef['archetypes']);
 2279         // 'legacy' is for backward compatibility with 1.9 access.php
 2280         } else if (isset($capdef['legacy']) && is_array($capdef['legacy'])) {
 2281             assign_legacy_capabilities($capname, $capdef['legacy']);
 2282         }
 2283     }
 2284     // Are there any capabilities that have been removed from the file
 2285     // definition that we need to delete from the stored capabilities and
 2286     // role assignments?
 2287     capabilities_cleanup($component, $filecaps);
 2288 
 2289     // reset static caches
 2290     accesslib_reset_role_cache();
 2291 
 2292     // Flush the cached again, as we have changed DB.
 2293     cache::make('core', 'capabilities')->delete('core_capabilities');
 2294 
 2295     return true;
 2296 }
 2297 
 2298 /**
 2299  * Deletes cached capabilities that are no longer needed by the component.
 2300  * Also unassigns these capabilities from any roles that have them.
 2301  * NOTE: this function is called from lib/db/upgrade.php
 2302  *
 2303  * @access private
 2304  * @param string $component examples: 'moodle', 'mod_forum', 'block_quiz_results'
 2305  * @param array $newcapdef array of the new capability definitions that will be
 2306  *                     compared with the cached capabilities
 2307  * @return int number of deprecated capabilities that have been removed
 2308  */
 2309 function capabilities_cleanup($component, $newcapdef = null) {
 2310     global $DB;
 2311 
 2312     $removedcount = 0;
 2313 
 2314     if ($cachedcaps = get_cached_capabilities($component)) {
 2315         foreach ($cachedcaps as $cachedcap) {
 2316             if (empty($newcapdef) ||
 2317                         array_key_exists($cachedcap->name, $newcapdef) === false) {
 2318 
 2319                 // Delete from roles.
 2320                 if ($roles = get_roles_with_capability($cachedcap->name)) {
 2321                     foreach($roles as $role) {
 2322                         if (!unassign_capability($cachedcap->name, $role->id)) {
 2323                             print_error('cannotunassigncap', 'error', '', (object)array('cap'=>$cachedcap->name, 'role'=>$role->name));
 2324                         }
 2325                     }
 2326                 }
 2327 
 2328                 // Remove from role_capabilities for any old ones.
 2329                 $DB->delete_records('role_capabilities', array('capability' => $cachedcap->name));
 2330 
 2331                 // Remove from capabilities cache.
 2332                 $DB->delete_records('capabilities', array('name' => $cachedcap->name));
 2333                 $removedcount++;
 2334             } // End if.
 2335         }
 2336     }
 2337     if ($removedcount) {
 2338         cache::make('core', 'capabilities')->delete('core_capabilities');
 2339     }
 2340     return $removedcount;
 2341 }
 2342 
 2343 /**
 2344  * Returns an array of all the known types of risk
 2345  * The array keys can be used, for example as CSS class names, or in calls to
 2346  * print_risk_icon. The values are the corresponding RISK_ constants.
 2347  *
 2348  * @return array all the known types of risk.
 2349  */
 2350 function get_all_risks() {
 2351     return array(
 2352         'riskmanagetrust' => RISK_MANAGETRUST,
 2353         'riskconfig'      => RISK_CONFIG,
 2354         'riskxss'         => RISK_XSS,
 2355         'riskpersonal'    => RISK_PERSONAL,
 2356         'riskspam'        => RISK_SPAM,
 2357         'riskdataloss'    => RISK_DATALOSS,
 2358     );
 2359 }
 2360 
 2361 /**
 2362  * Return a link to moodle docs for a given capability name
 2363  *
 2364  * @param stdClass $capability a capability - a row from the mdl_capabilities table.
 2365  * @return string the human-readable capability name as a link to Moodle Docs.
 2366  */
 2367 function get_capability_docs_link($capability) {
 2368     $url = get_docs_url('Capabilities/' . $capability->name);
 2369     return '<a onclick="this.target=\'docspopup\'" href="' . $url . '">' . get_capability_string($capability->name) . '</a>';
 2370 }
 2371 
 2372 /**
 2373  * This function pulls out all the resolved capabilities (overrides and
 2374  * defaults) of a role used in capability overrides in contexts at a given
 2375  * context.
 2376  *
 2377  * @param int $roleid
 2378  * @param context $context
 2379  * @param string $cap capability, optional, defaults to ''
 2380  * @return array Array of capabilities
 2381  */
 2382 function role_context_capabilities($roleid, context $context, $cap = '') {
 2383     global $DB;
 2384 
 2385     $contexts = $context->get_parent_context_ids(true);
 2386     $contexts = '('.implode(',', $contexts).')';
 2387 
 2388     $params = array($roleid);
 2389 
 2390     if ($cap) {
 2391         $search = " AND rc.capability = ? ";
 2392         $params[] = $cap;
 2393     } else {
 2394         $search = '';
 2395     }
 2396 
 2397     $sql = "SELECT rc.*
 2398               FROM {role_capabilities} rc
 2399               JOIN {context} c ON rc.contextid = c.id
 2400               JOIN {capabilities} cap ON rc.capability = cap.name
 2401              WHERE rc.contextid in $contexts
 2402                    AND rc.roleid = ?
 2403                    $search
 2404           ORDER BY c.contextlevel DESC, rc.capability DESC";
 2405 
 2406     $capabilities = array();
 2407 
 2408     if ($records = $DB->get_records_sql($sql, $params)) {
 2409         // We are traversing via reverse order.
 2410         foreach ($records as $record) {
 2411             // If not set yet (i.e. inherit or not set at all), or currently we have a prohibit
 2412             if (!isset($capabilities[$record->capability]) || $record->permission<-500) {
 2413                 $capabilities[$record->capability] = $record->permission;
 2414             }
 2415         }
 2416     }
 2417     return $capabilities;
 2418 }
 2419 
 2420 /**
 2421  * Constructs array with contextids as first parameter and context paths,
 2422  * in both cases bottom top including self.
 2423  *
 2424  * @access private
 2425  * @param context $context
 2426  * @return array
 2427  */
 2428 function get_context_info_list(context $context) {
 2429     $contextids = explode('/', ltrim($context->path, '/'));
 2430     $contextpaths = array();
 2431     $contextids2 = $contextids;
 2432     while ($contextids2) {
 2433         $contextpaths[] = '/' . implode('/', $contextids2);
 2434         array_pop($contextids2);
 2435     }
 2436     return array($contextids, $contextpaths);
 2437 }
 2438 
 2439 /**
 2440  * Check if context is the front page context or a context inside it
 2441  *
 2442  * Returns true if this context is the front page context, or a context inside it,
 2443  * otherwise false.
 2444  *
 2445  * @param context $context a context object.
 2446  * @return bool
 2447  */
 2448 function is_inside_frontpage(context $context) {
 2449     $frontpagecontext = context_course::instance(SITEID);
 2450     return strpos($context->path . '/', $frontpagecontext->path . '/') === 0;
 2451 }
 2452 
 2453 /**
 2454  * Returns capability information (cached)
 2455  *
 2456  * @param string $capabilityname
 2457  * @return stdClass or null if capability not found
 2458  */
 2459 function get_capability_info($capabilityname) {
 2460     $caps = get_all_capabilities();
 2461 
 2462     if (!isset($caps[$capabilityname])) {
 2463         return null;
 2464     }
 2465 
 2466     return (object) $caps[$capabilityname];
 2467 }
 2468 
 2469 /**
 2470  * Returns all capabilitiy records, preferably from MUC and not database.
 2471  *
 2472  * @return array All capability records indexed by capability name
 2473  */
 2474 function get_all_capabilities() {
 2475     global $DB;
 2476     $cache = cache::make('core', 'capabilities');
 2477     if (!$allcaps = $cache->get('core_capabilities')) {
 2478         $rs = $DB->get_recordset('capabilities');
 2479         $allcaps = array();
 2480         foreach ($rs as $capability) {
 2481             $capability->riskbitmask = (int) $capability->riskbitmask;
 2482             $allcaps[$capability->name] = (array) $capability;
 2483         }
 2484         $rs->close();
 2485         $cache->set('core_capabilities', $allcaps);
 2486     }
 2487     return $allcaps;
 2488 }
 2489 
 2490 /**
 2491  * Returns the human-readable, translated version of the capability.
 2492  * Basically a big switch statement.
 2493  *
 2494  * @param string $capabilityname e.g. mod/choice:readresponses
 2495  * @return string
 2496  */
 2497 function get_capability_string($capabilityname) {
 2498 
 2499     // Typical capability name is 'plugintype/pluginname:capabilityname'
 2500     list($type, $name, $capname) = preg_split('|[/:]|', $capabilityname);
 2501 
 2502     if ($type === 'moodle') {
 2503         $component = 'core_role';
 2504     } else if ($type === 'quizreport') {
 2505         //ugly hack!!
 2506         $component = 'quiz_'.$name;
 2507     } else {
 2508         $component = $type.'_'.$name;
 2509     }
 2510 
 2511     $stringname = $name.':'.$capname;
 2512 
 2513     if ($component === 'core_role' or get_string_manager()->string_exists($stringname, $component)) {
 2514         return get_string($stringname, $component);
 2515     }
 2516 
 2517     $dir = core_component::get_component_directory($component);
 2518     if (!file_exists($dir)) {
 2519         // plugin broken or does not exist, do not bother with printing of debug message
 2520         return $capabilityname.' ???';
 2521     }
 2522 
 2523     // something is wrong in plugin, better print debug
 2524     return get_string($stringname, $component);
 2525 }
 2526 
 2527 /**
 2528  * This gets the mod/block/course/core etc strings.
 2529  *
 2530  * @param string $component
 2531  * @param int $contextlevel
 2532  * @return string|bool String is success, false if failed
 2533  */
 2534 function get_component_string($component, $contextlevel) {
 2535 
 2536     if ($component === 'moodle' or $component === 'core') {
 2537         switch ($contextlevel) {
 2538             // TODO MDL-46123: this should probably use context level names instead
 2539             case CONTEXT_SYSTEM:    return get_string('coresystem');
 2540             case CONTEXT_USER:      return get_string('users');
 2541             case CONTEXT_COURSECAT: return get_string('categories');
 2542             case CONTEXT_COURSE:    return get_string('course');
 2543             case CONTEXT_MODULE:    return get_string('activities');
 2544             case CONTEXT_BLOCK:     return get_string('block');
 2545             default:                print_error('unknowncontext');
 2546         }
 2547     }
 2548 
 2549     list($type, $name) = core_component::normalize_component($component);
 2550     $dir = core_component::get_plugin_directory($type, $name);
 2551     if (!file_exists($dir)) {
 2552         // plugin not installed, bad luck, there is no way to find the name
 2553         return $component.' ???';
 2554     }
 2555 
 2556     switch ($type) {
 2557         // TODO MDL-46123: this is really hacky and should be improved.
 2558         case 'quiz':         return get_string($name.':componentname', $component);// insane hack!!!
 2559         case 'repository':   return get_string('repository', 'repository').': '.get_string('pluginname', $component);
 2560         case 'gradeimport':  return get_string('gradeimport', 'grades').': '.get_string('pluginname', $component);
 2561         case 'gradeexport':  return get_string('gradeexport', 'grades').': '.get_string('pluginname', $component);
 2562         case 'gradereport':  return get_string('gradereport', 'grades').': '.get_string('pluginname', $component);
 2563         case 'webservice':   return get_string('webservice', 'webservice').': '.get_string('pluginname', $component);
 2564         case 'block':        return get_string('block').': '.get_string('pluginname', basename($component));
 2565         case 'mod':
 2566             if (get_string_manager()->string_exists('pluginname', $component)) {
 2567                 return get_string('activity').': '.get_string('pluginname', $component);
 2568             } else {
 2569                 return get_string('activity').': '.get_string('modulename', $component);
 2570             }
 2571         default: return get_string('pluginname', $component);
 2572     }
 2573 }
 2574 
 2575 /**
 2576  * Gets the list of roles assigned to this context and up (parents)
 2577  * from the aggregation of:
 2578  * a) the list of roles that are visible on user profile page and participants page (profileroles setting) and;
 2579  * b) if applicable, those roles that are assigned in the context.
 2580  *
 2581  * @param context $context
 2582  * @return array
 2583  */
 2584 function get_profile_roles(context $context) {
 2585     global $CFG, $DB;
 2586     // If the current user can assign roles, then they can see all roles on the profile and participants page,
 2587     // provided the roles are assigned to at least 1 user in the context. If not, only the policy-defined roles.
 2588     if (has_capability('moodle/role:assign', $context)) {
 2589         $rolesinscope = array_keys(get_all_roles($context));
 2590     } else {
 2591         $rolesinscope = empty($CFG->profileroles) ? [] : array_map('trim', explode(',', $CFG->profileroles));
 2592     }
 2593 
 2594     if (empty($rolesinscope)) {
 2595         return [];
 2596     }
 2597 
 2598     list($rallowed, $params) = $DB->get_in_or_equal($rolesinscope, SQL_PARAMS_NAMED, 'a');
 2599     list($contextlist, $cparams) = $DB->get_in_or_equal($context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'p');
 2600     $params = array_merge($params, $cparams);
 2601 
 2602     if ($coursecontext = $context->get_course_context(false)) {
 2603         $params['coursecontext'] = $coursecontext->id;
 2604     } else {
 2605         $params['coursecontext'] = 0;
 2606     }
 2607 
 2608     $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder, rn.name AS coursealias
 2609               FROM {role_assignments} ra, {role} r
 2610          LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
 2611              WHERE r.id = ra.roleid
 2612                    AND ra.contextid $contextlist
 2613                    AND r.id $rallowed
 2614           ORDER BY r.sortorder ASC";
 2615 
 2616     return $DB->get_records_sql($sql, $params);
 2617 }
 2618 
 2619 /**
 2620  * Gets the list of roles assigned to this context and up (parents)
 2621  *
 2622  * @param context $context
 2623  * @param boolean $includeparents, false means without parents.
 2624  * @return array
 2625  */
 2626 function get_roles_used_in_context(context $context, $includeparents = true) {
 2627     global $DB;
 2628 
 2629     if ($includeparents === true) {
 2630         list($contextlist, $params) = $DB->get_in_or_equal($context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'cl');
 2631     } else {
 2632         list($contextlist, $params) = $DB->get_in_or_equal($context->id, SQL_PARAMS_NAMED, 'cl');
 2633     }
 2634 
 2635     if ($coursecontext = $context->get_course_context(false)) {
 2636         $params['coursecontext'] = $coursecontext->id;
 2637     } else {
 2638         $params['coursecontext'] = 0;
 2639     }
 2640 
 2641     $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder, rn.name AS coursealias
 2642               FROM {role_assignments} ra, {role} r
 2643          LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
 2644              WHERE r.id = ra.roleid
 2645                    AND ra.contextid $contextlist
 2646           ORDER BY r.sortorder ASC";
 2647 
 2648     return $DB->get_records_sql($sql, $params);
 2649 }
 2650 
 2651 /**
 2652  * This function is used to print roles column in user profile page.
 2653  * It is using the CFG->profileroles to limit the list to only interesting roles.
 2654  * (The permission tab has full details of user role assignments.)
 2655  *
 2656  * @param int $userid
 2657  * @param int $courseid
 2658  * @return string
 2659  */
 2660 function get_user_roles_in_course($userid, $courseid) {
 2661     global $CFG, $DB;
 2662     if ($courseid == SITEID) {
 2663         $context = context_system::instance();
 2664     } else {
 2665         $context = context_course::instance($courseid);
 2666     }
 2667     // If the current user can assign roles, then they can see all roles on the profile and participants page,
 2668     // provided the roles are assigned to at least 1 user in the context. If not, only the policy-defined roles.
 2669     if (has_capability('moodle/role:assign', $context)) {
 2670         $rolesinscope = array_keys(get_all_roles($context));
 2671     } else {
 2672         $rolesinscope = empty($CFG->profileroles) ? [] : array_map('trim', explode(',', $CFG->profileroles));
 2673     }
 2674     if (empty($rolesinscope)) {
 2675         return '';
 2676     }
 2677 
 2678     list($rallowed, $params) = $DB->get_in_or_equal($rolesinscope, SQL_PARAMS_NAMED, 'a');
 2679     list($contextlist, $cparams) = $DB->get_in_or_equal($context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'p');
 2680     $params = array_merge($params, $cparams);
 2681 
 2682     if ($coursecontext = $context->get_course_context(false)) {
 2683         $params['coursecontext'] = $coursecontext->id;
 2684     } else {
 2685         $params['coursecontext'] = 0;
 2686     }
 2687 
 2688     $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder, rn.name AS coursealias
 2689               FROM {role_assignments} ra, {role} r
 2690          LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
 2691              WHERE r.id = ra.roleid
 2692                    AND ra.contextid $contextlist
 2693                    AND r.id $rallowed
 2694                    AND ra.userid = :userid
 2695           ORDER BY r.sortorder ASC";
 2696     $params['userid'] = $userid;
 2697 
 2698     $rolestring = '';
 2699 
 2700     if ($roles = $DB->get_records_sql($sql, $params)) {
 2701         $viewableroles = get_viewable_roles($context, $userid);
 2702 
 2703         $rolenames = array();
 2704         foreach ($roles as $roleid => $unused) {
 2705             if (isset($viewableroles[$roleid])) {
 2706                 $url = new moodle_url('/user/index.php', ['contextid' => $context->id, 'roleid' => $roleid]);
 2707                 $rolenames[] = '<a href="' . $url . '">' . $viewableroles[$roleid] . '</a>';
 2708             }
 2709         }
 2710         $rolestring = implode(',', $rolenames);
 2711     }
 2712 
 2713     return $rolestring;
 2714 }
 2715 
 2716 /**
 2717  * Checks if a user can assign users to a particular role in this context
 2718  *
 2719  * @param context $context
 2720  * @param int $targetroleid - the id of the role you want to assign users to
 2721  * @return boolean
 2722  */
 2723 function user_can_assign(context $context, $targetroleid) {
 2724     global $DB;
 2725 
 2726     // First check to see if the user is a site administrator.
 2727     if (is_siteadmin()) {
 2728         return true;
 2729     }
 2730 
 2731     // Check if user has override capability.
 2732     // If not return false.
 2733     if (!has_capability('moodle/role:assign', $context)) {
 2734         return false;
 2735     }
 2736     // pull out all active roles of this user from this context(or above)
 2737     if ($userroles = get_user_roles($context)) {
 2738         foreach ($userroles as $userrole) {
 2739             // if any in the role_allow_override table, then it's ok
 2740             if ($DB->get_record('role_allow_assign', array('roleid'=>$userrole->roleid, 'allowassign'=>$targetroleid))) {
 2741                 return true;
 2742             }
 2743         }
 2744     }
 2745 
 2746     return false;
 2747 }
 2748 
 2749 /**
 2750  * Returns all site roles in correct sort order.
 2751  *
 2752  * Note: this method does not localise role names or descriptions,
 2753  *       use role_get_names() if you need role names.
 2754  *
 2755  * @param context $context optional context for course role name aliases
 2756  * @return array of role records with optional coursealias property
 2757  */
 2758 function get_all_roles(context $context = null) {
 2759     global $DB;
 2760 
 2761     if (!$context or !$coursecontext = $context->get_course_context(false)) {
 2762         $coursecontext = null;
 2763     }
 2764 
 2765     if ($coursecontext) {
 2766         $sql = "SELECT r.*, rn.name AS coursealias
 2767                   FROM {role} r
 2768              LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
 2769               ORDER BY r.sortorder ASC";
 2770         return $DB->get_records_sql($sql, array('coursecontext'=>$coursecontext->id));
 2771 
 2772     } else {
 2773         return $DB->get_records('role', array(), 'sortorder ASC');
 2774     }
 2775 }
 2776 
 2777 /**
 2778  * Returns roles of a specified archetype
 2779  *
 2780  * @param string $archetype
 2781  * @return array of full role records
 2782  */
 2783 function get_archetype_roles($archetype) {
 2784     global $DB;
 2785     return $DB->get_records('role', array('archetype'=>$archetype), 'sortorder ASC');
 2786 }
 2787 
 2788 /**
 2789  * Gets all the user roles assigned in this context, or higher contexts for a list of users.
 2790  *
 2791  * If you try using the combination $userids = [], $checkparentcontexts = true then this is likely
 2792  * to cause an out-of-memory error on large Moodle sites, so this combination is deprecated and
 2793  * outputs a warning, even though it is the default.
 2794  *
 2795  * @param context $context
 2796  * @param array $userids. An empty list means fetch all role assignments for the context.
 2797  * @param bool $checkparentcontexts defaults to true
 2798  * @param string $order defaults to 'c.contextlevel DESC, r.sortorder ASC'
 2799  * @return array
 2800  */
 2801 function get_users_roles(context $context, $userids = [], $checkparentcontexts = true, $order = 'c.contextlevel DESC, r.sortorder ASC') {
 2802     global $DB;
 2803 
 2804     if (!$userids && $checkparentcontexts) {
 2805         debugging('Please do not call get_users_roles() with $checkparentcontexts = true ' .
 2806                 'and $userids array not set. This combination causes large Moodle sites ' .
 2807                 'with lots of site-wide role assignemnts to run out of memory.', DEBUG_DEVELOPER);
 2808     }
 2809 
 2810     if ($checkparentcontexts) {
 2811         $contextids = $context->get_parent_context_ids();
 2812     } else {
 2813         $contextids = array();
 2814     }
 2815     $contextids[] = $context->id;
 2816 
 2817     list($contextids, $params) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, 'con');
 2818 
 2819     // If userids was passed as an empty array, we fetch all role assignments for the course.
 2820     if (empty($userids)) {
 2821         $useridlist = ' IS NOT NULL ';
 2822         $uparams = [];
 2823     } else {
 2824         list($useridlist, $uparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED, 'uids');
 2825     }
 2826 
 2827     $sql = "SELECT ra.*, r.name, r.shortname, ra.userid
 2828               FROM {role_assignments} ra, {role} r, {context} c
 2829              WHERE ra.userid $useridlist
 2830                    AND ra.roleid = r.id
 2831                    AND ra.contextid = c.id
 2832                    AND ra.contextid $contextids
 2833           ORDER BY $order";
 2834 
 2835     $all = $DB->get_records_sql($sql , array_merge($params, $uparams));
 2836 
 2837     // Return results grouped by userid.
 2838     $result = [];
 2839     foreach ($all as $id => $record) {
 2840         if (!isset($result[$record->userid])) {
 2841             $result[$record->userid] = [];
 2842         }
 2843         $result[$record->userid][$record->id] = $record;
 2844     }
 2845 
 2846     // Make sure all requested users are included in the result, even if they had no role assignments.
 2847     foreach ($userids as $id) {
 2848         if (!isset($result[$id])) {
 2849             $result[$id] = [];
 2850         }
 2851     }
 2852 
 2853     return $result;
 2854 }
 2855 
 2856 
 2857 /**
 2858  * Gets all the user roles assigned in this context, or higher contexts
 2859  * this is mainly used when checking if a user can assign a role, or overriding a role
 2860  * i.e. we need to know what this user holds, in order to verify against allow_assign and
 2861  * allow_override tables
 2862  *
 2863  * @param context $context
 2864  * @param int $userid
 2865  * @param bool $checkparentcontexts defaults to true
 2866  * @param string $order defaults to 'c.contextlevel DESC, r.sortorder ASC'
 2867  * @return array
 2868  */
 2869 function get_user_roles(context $context, $userid = 0, $checkparentcontexts = true, $order = 'c.contextlevel DESC, r.sortorder ASC') {
 2870     global $USER, $DB;
 2871 
 2872     if (empty($userid)) {
 2873         if (empty($USER->id)) {
 2874             return array();
 2875         }
 2876         $userid = $USER->id;
 2877     }
 2878 
 2879     if ($checkparentcontexts) {
 2880         $contextids = $context->get_parent_context_ids();
 2881     } else {
 2882         $contextids = array();
 2883     }
 2884     $contextids[] = $context->id;
 2885 
 2886     list($contextids, $params) = $DB->get_in_or_equal($contextids, SQL_PARAMS_QM);
 2887 
 2888     array_unshift($params, $userid);
 2889 
 2890     $sql = "SELECT ra.*, r.name, r.shortname
 2891               FROM {role_assignments} ra, {role} r, {context} c
 2892              WHERE ra.userid = ?
 2893                    AND ra.roleid = r.id
 2894                    AND ra.contextid = c.id
 2895                    AND ra.contextid $contextids
 2896           ORDER BY $order";
 2897 
 2898     return $DB->get_records_sql($sql ,$params);
 2899 }
 2900 
 2901 /**
 2902  * Like get_user_roles, but adds in the authenticated user role, and the front
 2903  * page roles, if applicable.
 2904  *
 2905  * @param context $context the context.
 2906  * @param int $userid optional. Defaults to $USER->id
 2907  * @return array of objects with fields ->userid, ->contextid and ->roleid.
 2908  */
 2909 function get_user_roles_with_special(context $context, $userid = 0) {
 2910     global $CFG, $USER;
 2911 
 2912     if (empty($userid)) {
 2913         if (empty($USER->id)) {
 2914             return array();
 2915         }
 2916         $userid = $USER->id;
 2917     }
 2918 
 2919     $ras = get_user_roles($context, $userid);
 2920 
 2921     // Add front-page role if relevant.
 2922     $defaultfrontpageroleid = isset($CFG->defaultfrontpageroleid) ? $CFG->defaultfrontpageroleid : 0;
 2923     $isfrontpage = ($context->contextlevel == CONTEXT_COURSE && $context->instanceid == SITEID) ||
 2924             is_inside_frontpage($context);
 2925     if ($defaultfrontpageroleid && $isfrontpage) {
 2926         $frontpagecontext = context_course::instance(SITEID);
 2927         $ra = new stdClass();
 2928         $ra->userid = $userid;
 2929         $ra->contextid = $frontpagecontext->id;
 2930         $ra->roleid = $defaultfrontpageroleid;
 2931         $ras[] = $ra;
 2932     }
 2933 
 2934     // Add authenticated user role if relevant.
 2935     $defaultuserroleid      = isset($CFG->defaultuserroleid) ? $CFG->defaultuserroleid : 0;
 2936     if ($defaultuserroleid && !isguestuser($userid)) {
 2937         $systemcontext = context_system::instance();
 2938         $ra = new stdClass();
 2939         $ra->userid = $userid;
 2940         $ra->contextid = $systemcontext->id;
 2941         $ra->roleid = $defaultuserroleid;
 2942         $ras[] = $ra;
 2943     }
 2944 
 2945     return $ras;
 2946 }
 2947 
 2948 /**
 2949  * Creates a record in the role_allow_override table
 2950  *
 2951  * @param int $fromroleid source roleid
 2952  * @param int $targetroleid target roleid
 2953  * @return void
 2954  */
 2955 function core_role_set_override_allowed($fromroleid, $targetroleid) {
 2956     global $DB;
 2957 
 2958     $record = new stdClass();
 2959     $record->roleid        = $fromroleid;
 2960     $record->allowoverride = $targetroleid;
 2961     $DB->insert_record('role_allow_override', $record);
 2962 }
 2963 
 2964 /**
 2965  * Creates a record in the role_allow_assign table
 2966  *
 2967  * @param int $fromroleid source roleid
 2968  * @param int $targetroleid target roleid
 2969  * @return void
 2970  */
 2971 function core_role_set_assign_allowed($fromroleid, $targetroleid) {
 2972     global $DB;
 2973 
 2974     $record = new stdClass();
 2975     $record->roleid      = $fromroleid;
 2976     $record->allowassign = $targetroleid;
 2977     $DB->insert_record('role_allow_assign', $record);
 2978 }
 2979 
 2980 /**
 2981  * Creates a record in the role_allow_switch table
 2982  *
 2983  * @param int $fromroleid source roleid
 2984  * @param int $targetroleid target roleid
 2985  * @return void
 2986  */
 2987 function core_role_set_switch_allowed($fromroleid, $targetroleid) {
 2988     global $DB;
 2989 
 2990     $record = new stdClass();
 2991     $record->roleid      = $fromroleid;
 2992     $record->allowswitch = $targetroleid;
 2993     $DB->insert_record('role_allow_switch', $record);
 2994 }
 2995 
 2996 /**
 2997  * Creates a record in the role_allow_view table
 2998  *
 2999  * @param int $fromroleid source roleid
 3000  * @param int $targetroleid target roleid
 3001  * @return void
 3002  */
 3003 function core_role_set_view_allowed($fromroleid, $targetroleid) {
 3004     global $DB;
 3005 
 3006     $record = new stdClass();
 3007     $record->roleid      = $fromroleid;
 3008     $record->allowview = $targetroleid;
 3009     $DB->insert_record('role_allow_view', $record);
 3010 }
 3011 
 3012 /**
 3013  * Gets a list of roles that this user can assign in this context
 3014  *
 3015  * @param context $context the context.
 3016  * @param int $rolenamedisplay the type of role name to display. One of the
 3017  *      ROLENAME_X constants. Default ROLENAME_ALIAS.
 3018  * @param bool $withusercounts if true, count the number of users with each role.
 3019  * @param integer|object $user A user id or object. By default (null) checks the permissions of the current user.
 3020  * @return array if $withusercounts is false, then an array $roleid => $rolename.
 3021  *      if $withusercounts is true, returns a list of three arrays,
 3022  *      $rolenames, $rolecounts, and $nameswithcounts.
 3023  */
 3024 function get_assignable_roles(context $context, $rolenamedisplay = ROLENAME_ALIAS, $withusercounts = false, $user = null) {
 3025     global $USER, $DB;
 3026 
 3027     // make sure there is a real user specified
 3028     if ($user === null) {
 3029         $userid = isset($USER->id) ? $USER->id : 0;
 3030     } else {
 3031         $userid = is_object($user) ? $user->id : $user;
 3032     }
 3033 
 3034     if (!has_capability('moodle/role:assign', $context, $userid)) {
 3035         if ($withusercounts) {
 3036             return array(array(), array(), array());
 3037         } else {
 3038             return array();
 3039         }
 3040     }
 3041 
 3042     $params = array();
 3043     $extrafields = '';
 3044 
 3045     if ($withusercounts) {
 3046         $extrafields = ', (SELECT COUNT(DISTINCT u.id)
 3047                              FROM {role_assignments} cra JOIN {user} u ON cra.userid = u.id
 3048                             WHERE cra.roleid = r.id AND cra.contextid = :conid AND u.deleted = 0
 3049                           ) AS usercount';
 3050         $params['conid'] = $context->id;
 3051     }
 3052 
 3053     if (is_siteadmin($userid)) {
 3054         // show all roles allowed in this context to admins
 3055         $assignrestriction = "";
 3056     } else {
 3057         $parents = $context->get_parent_context_ids(true);
 3058         $contexts = implode(',' , $parents);
 3059         $assignrestriction = "JOIN (SELECT DISTINCT raa.allowassign AS id
 3060                                       FROM {role_allow_assign} raa
 3061                                       JOIN {role_assignments} ra ON ra.roleid = raa.roleid
 3062                                      WHERE ra.userid = :userid AND ra.contextid IN ($contexts)
 3063                                    ) ar ON ar.id = r.id";
 3064         $params['userid'] = $userid;
 3065     }
 3066     $params['contextlevel'] = $context->contextlevel;
 3067 
 3068     if ($coursecontext = $context->get_course_context(false)) {
 3069         $params['coursecontext'] = $coursecontext->id;
 3070     } else {
 3071         $params['coursecontext'] = 0; // no course aliases
 3072         $coursecontext = null;
 3073     }
 3074     $sql = "SELECT r.id, r.name, r.shortname, rn.name AS coursealias $extrafields
 3075               FROM {role} r
 3076               $assignrestriction
 3077               JOIN {role_context_levels} rcl ON (rcl.contextlevel = :contextlevel AND r.id = rcl.roleid)
 3078          LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
 3079           ORDER BY r.sortorder ASC";
 3080     $roles = $DB->get_records_sql($sql, $params);
 3081 
 3082     $rolenames = role_fix_names($roles, $coursecontext, $rolenamedisplay, true);
 3083 
 3084     if (!$withusercounts) {
 3085         return $rolenames;
 3086     }
 3087 
 3088     $rolecounts = array();
 3089     $nameswithcounts = array();
 3090     foreach ($roles as $role) {
 3091         $nameswithcounts[$role->id] = $rolenames[$role->id] . ' (' . $roles[$role->id]->usercount . ')';
 3092         $rolecounts[$role->id] = $roles[$role->id]->usercount;
 3093     }
 3094     return array($rolenames, $rolecounts, $nameswithcounts);
 3095 }
 3096 
 3097 /**
 3098  * Gets a list of roles that this user can switch to in a context
 3099  *
 3100  * Gets a list of roles that this user can switch to in a context, for the switchrole menu.
 3101  * This function just process the contents of the role_allow_switch table. You also need to
 3102  * test the moodle/role:switchroles to see if the user is allowed to switch in the first place.
 3103  *
 3104  * @param context $context a context.
 3105  * @return array an array $roleid => $rolename.
 3106  */
 3107 function get_switchable_roles(context $context) {
 3108     global $USER, $DB;
 3109 
 3110     // You can't switch roles without this capability.
 3111     if (!has_capability('moodle/role:switchroles', $context)) {
 3112         return [];
 3113     }
 3114 
 3115     $params = array();
 3116     $extrajoins = '';
 3117     $extrawhere = '';
 3118     if (!is_siteadmin()) {
 3119         // Admins are allowed to switch to any role with.
 3120         // Others are subject to the additional constraint that the switch-to role must be allowed by
 3121         // 'role_allow_switch' for some role they have assigned in this context or any parent.
 3122         $parents = $context->get_parent_context_ids(true);
 3123         $contexts = implode(',' , $parents);
 3124 
 3125         $extrajoins = "JOIN {role_allow_switch} ras ON ras.allowswitch = rc.roleid
 3126         JOIN {role_assignments} ra ON ra.roleid = ras.roleid";
 3127         $extrawhere = "WHERE ra.userid = :userid AND ra.contextid IN ($contexts)";
 3128         $params['userid'] = $USER->id;
 3129     }
 3130 
 3131     if ($coursecontext = $context->get_course_context(false)) {
 3132         $params['coursecontext'] = $coursecontext->id;
 3133     } else {
 3134         $params['coursecontext'] = 0; // no course aliases
 3135         $coursecontext = null;
 3136     }
 3137 
 3138     $query = "
 3139         SELECT r.id, r.name, r.shortname, rn.name AS coursealias
 3140           FROM (SELECT DISTINCT rc.roleid
 3141                   FROM {role_capabilities} rc
 3142 
 3143                   $extrajoins
 3144                   $extrawhere) idlist
 3145           JOIN {role} r ON r.id = idlist.roleid
 3146      LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
 3147       ORDER BY r.sortorder";
 3148     $roles = $DB->get_records_sql($query, $params);
 3149 
 3150     return role_fix_names($roles, $context, ROLENAME_ALIAS, true);
 3151 }
 3152 
 3153 /**
 3154  * Gets a list of roles that this user can view in a context
 3155  *
 3156  * @param context $context a context.
 3157  * @param int $userid id of user.
 3158  * @return array an array $roleid => $rolename.
 3159  */
 3160 function get_viewable_roles(context $context, $userid = null) {
 3161     global $USER, $DB;
 3162 
 3163     if ($userid == null) {
 3164         $userid = $USER->id;
 3165     }
 3166 
 3167     $params = array();
 3168     $extrajoins = '';
 3169     $extrawhere = '';
 3170     if (!is_siteadmin()) {
 3171         // Admins are allowed to view any role.
 3172         // Others are subject to the additional constraint that the view role must be allowed by
 3173         // 'role_allow_view' for some role they have assigned in this context or any parent.
 3174         $contexts = $context->get_parent_context_ids(true);
 3175         list($insql, $inparams) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED);
 3176 
 3177         $extrajoins = "JOIN {role_allow_view} ras ON ras.allowview = r.id
 3178                        JOIN {role_assignments} ra ON ra.roleid = ras.roleid";
 3179         $extrawhere = "WHERE ra.userid = :userid AND ra.contextid $insql";
 3180 
 3181         $params += $inparams;
 3182         $params['userid'] = $userid;
 3183     }
 3184 
 3185     if ($coursecontext = $context->get_course_context(false)) {
 3186         $params['coursecontext'] = $coursecontext->id;
 3187     } else {
 3188         $params['coursecontext'] = 0; // No course aliases.
 3189         $coursecontext = null;
 3190     }
 3191 
 3192     $query = "
 3193         SELECT r.id, r.name, r.shortname, rn.name AS coursealias, r.sortorder
 3194           FROM {role} r
 3195           $extrajoins
 3196      LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
 3197           $extrawhere
 3198       GROUP BY r.id, r.name, r.shortname, rn.name, r.sortorder
 3199       ORDER BY r.sortorder";
 3200     $roles = $DB->get_records_sql($query, $params);
 3201 
 3202     return role_fix_names($roles, $context, ROLENAME_ALIAS, true);
 3203 }
 3204 
 3205 /**
 3206  * Gets a list of roles that this user can override in this context.
 3207  *
 3208  * @param context $context the context.
 3209  * @param int $rolenamedisplay the type of role name to display. One of the
 3210  *      ROLENAME_X constants. Default ROLENAME_ALIAS.
 3211  * @param bool $withcounts if true, count the number of overrides that are set for each role.
 3212  * @return array if $withcounts is false, then an array $roleid => $rolename.
 3213  *      if $withusercounts is true, returns a list of three arrays,
 3214  *      $rolenames, $rolecounts, and $nameswithcounts.
 3215  */
 3216 function get_overridable_roles(context $context, $rolenamedisplay = ROLENAME_ALIAS, $withcounts = false) {
 3217     global $USER, $DB;
 3218 
 3219     if (!has_any_capability(array('moodle/role:safeoverride', 'moodle/role:override'), $context)) {
 3220         if ($withcounts) {
 3221             return array(array(), array(), array());
 3222         } else {
 3223             return array();
 3224         }
 3225     }
 3226 
 3227     $parents = $context->get_parent_context_ids(true);
 3228     $contexts = implode(',' , $parents);
 3229 
 3230     $params = array();
 3231     $extrafields = '';
 3232 
 3233     $params['userid'] = $USER->id;
 3234     if ($withcounts) {
 3235         $extrafields = ', (SELECT COUNT(rc.id) FROM {role_capabilities} rc
 3236                 WHERE rc.roleid = ro.id AND rc.contextid = :conid) AS overridecount';
 3237         $params['conid'] = $context->id;
 3238     }
 3239 
 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;
 3245     }
 3246 
 3247     if (is_siteadmin()) {
 3248         // show all roles to admins
 3249         $roles = $DB->get_records_sql("
 3250             SELECT ro.id, ro.name, ro.shortname, rn.name AS coursealias $extrafields
 3251               FROM {role} ro
 3252          LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = ro.id)
 3253           ORDER BY ro.sortorder ASC", $params);
 3254 
 3255     } else {
 3256         $roles = $DB->get_records_sql("
 3257             SELECT ro.id, ro.name, ro.shortname, rn.name AS coursealias $extrafields
 3258               FROM {role} ro
 3259               JOIN (SELECT DISTINCT r.id
 3260                       FROM {role} r
 3261                       JOIN {role_allow_override} rao ON r.id = rao.allowoverride
 3262                       JOIN {role_assignments} ra ON rao.roleid = ra.roleid
 3263                      WHERE ra.userid = :userid AND ra.contextid IN ($contexts)
 3264                    ) inline_view ON ro.id = inline_view.id
 3265          LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = ro.id)
 3266           ORDER BY ro.sortorder ASC", $params);
 3267     }
 3268 
 3269     $rolenames = role_fix_names($roles, $context, $rolenamedisplay, true);
 3270 
 3271     if (!$withcounts) {
 3272         return $rolenames;
 3273     }
 3274 
 3275     $rolecounts = array();
 3276     $nameswithcounts = array();
 3277     foreach ($roles as $role) {
 3278         $nameswithcounts[$role->id] = $rolenames[$role->id] . ' (' . $roles[$role->id]->overridecount . ')';
 3279         $rolecounts[$role->id] = $roles[$role->id]->overridecount;
 3280     }
 3281     return array($rolenames, $rolecounts, $nameswithcounts);
 3282 }
 3283 
 3284 /**
 3285  * Create a role menu suitable for default role selection in enrol plugins.
 3286  *
 3287  * @package    core_enrol
 3288  *
 3289  * @param context $context
 3290  * @param int $addroleid current or default role - always added to list
 3291  * @return array roleid=>localised role name
 3292  */
 3293 function get_default_enrol_roles(context $context, $addroleid = null) {
 3294     global $DB;
 3295 
 3296     $params = array('contextlevel'=>CONTEXT_COURSE);
 3297 
 3298     if ($coursecontext = $context->get_course_context(false)) {
 3299         $params['coursecontext'] = $coursecontext->id;
 3300     } else {
 3301         $params['coursecontext'] = 0; // no course names
 3302         $coursecontext = null;
 3303     }
 3304 
 3305     if ($addroleid) {
 3306         $addrole = "OR r.id = :addroleid";
 3307         $params['addroleid'] = $addroleid;
 3308     } else {
 3309         $addrole = "";
 3310     }
 3311 
 3312     $sql = "SELECT r.id, r.name, r.shortname, rn.name AS coursealias
 3313               FROM {role} r
 3314          LEFT JOIN {role_context_levels} rcl ON (rcl.roleid = r.id AND rcl.contextlevel = :contextlevel)
 3315          LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
 3316              WHERE rcl.id IS NOT NULL $addrole
 3317           ORDER BY sortorder DESC";
 3318 
 3319     $roles = $DB->get_records_sql($sql, $params);
 3320 
 3321     return role_fix_names($roles, $context, ROLENAME_BOTH, true);
 3322 }
 3323 
 3324 /**
 3325  * Return context levels where this role is assignable.
 3326  *
 3327  * @param integer $roleid the id of a role.
 3328  * @return array list of the context levels at which this role may be assigned.
 3329  */
 3330 function get_role_contextlevels($roleid) {
 3331     global $DB;
 3332     return $DB->get_records_menu('role_context_levels', array('roleid' => $roleid),
 3333             'contextlevel', 'id,contextlevel');
 3334 }
 3335 
 3336 /**
 3337  * Return roles suitable for assignment at the specified context level.
 3338  *
 3339  * NOTE: this function name looks like a typo, should be probably get_roles_for_contextlevel()
 3340  *
 3341  * @param integer $contextlevel a contextlevel.
 3342  * @return array list of role ids that are assignable at this context level.
 3343  */
 3344 function get_roles_for_contextlevels($contextlevel) {
 3345     global $DB;
 3346     return $DB->get_records_menu('role_context_levels', array('contextlevel' => $contextlevel),
 3347             '', 'id,roleid');
 3348 }
 3349 
 3350 /**
 3351  * Returns default context levels where roles can be assigned.
 3352  *
 3353  * @param string $rolearchetype one of the role archetypes - that is, one of the keys
 3354  *      from the array returned by get_role_archetypes();
 3355  * @return array list of the context levels at which this type of role may be assigned by default.
 3356  */
 3357 function get_default_contextlevels($rolearchetype) {
 3358     static $defaults = array(
 3359         'manager'        => array(CONTEXT_SYSTEM, CONTEXT_COURSECAT, CONTEXT_COURSE),
 3360         'coursecreator'  => array(CONTEXT_SYSTEM, CONTEXT_COURSECAT),
 3361         'editingteacher' => array(CONTEXT_COURSE, CONTEXT_MODULE),
 3362         'teacher'        => array(CONTEXT_COURSE, CONTEXT_MODULE),
 3363         'student'        => array(CONTEXT_COURSE, CONTEXT_MODULE),
 3364         'guest'          => array(),
 3365         'user'           => array(),
 3366         'frontpage'      => array());
 3367 
 3368     if (isset($defaults[$rolearchetype])) {
 3369         return $defaults[$rolearchetype];
 3370     } else {
 3371         return array();
 3372     }
 3373 }
 3374 
 3375 /**
 3376  * Set the context levels at which a particular role can be assigned.
 3377  * Throws exceptions in case of error.
 3378  *
 3379  * @param integer $roleid the id of a role.
 3380  * @param array $contextlevels the context levels at which this role should be assignable,
 3381  *      duplicate levels are removed.
 3382  * @return void
 3383  */
 3384 function set_role_contextlevels($roleid, array $contextlevels) {
 3385     global $DB;
 3386     $DB->delete_records('role_context_levels', array('roleid' => $roleid));
 3387     $rcl = new stdClass();
 3388     $rcl->roleid = $roleid;
 3389     $contextlevels = array_unique($contextlevels);
 3390     foreach ($contextlevels as $level) {
 3391         $rcl->contextlevel = $level;
 3392         $DB->insert_record('role_context_levels', $rcl, false, true);
 3393     }
 3394 }
 3395 
 3396 /**
 3397  * Who has this capability in this context?
 3398  *
 3399  * This can be a very expensive call - use sparingly and keep
 3400  * the results if you are going to need them again soon.
 3401  *
 3402  * Note if $fields is empty this function attempts to get u.*
 3403  * which can get rather large - and has a serious perf impact
 3404  * on some DBs.
 3405  *
 3406  * @param context $context
 3407  * @param string|array $capability - capability name(s)
 3408  * @param string $fields - fields to be pulled. The user table is aliased to 'u'. u.id MUST be included.
 3409  * @param string $sort - the sort order. Default is lastaccess time.
 3410  * @param mixed $limitfrom - number of records to skip (offset)
 3411  * @param mixed $limitnum - number of records to fetch
 3412  * @param string|array $groups - single group or array of groups - only return
 3413  *               users who are in one of these group(s).
 3414  * @param string|array $exceptions - list of users to exclude, comma separated or array
 3415  * @param bool $doanything_ignored not used any more, admin accounts are never returned
 3416  * @param bool $view_ignored - use get_enrolled_sql() instead
 3417  * @param bool $useviewallgroups if $groups is set the return users who
 3418  *               have capability both $capability and moodle/site:accessallgroups
 3419  *               in this context, as well as users who have $capability and who are
 3420  *               in $groups.
 3421  * @return array of user records
 3422  */
 3423 function get_users_by_capability(context $context, $capability, $fields = '', $sort = '', $limitfrom = '', $limitnum = '',
 3424                                  $groups = '', $exceptions = '', $doanything_ignored = null, $view_ignored = null, $useviewallgroups = false) {
 3425     global $CFG, $DB;
 3426 
 3427     $defaultuserroleid      = isset($CFG->defaultuserroleid) ? $CFG->defaultuserroleid : 0;
 3428     $defaultfrontpageroleid = isset($CFG->defaultfrontpageroleid) ? $CFG->defaultfrontpageroleid : 0;
 3429 
 3430     $ctxids = trim($context->path, '/');
 3431     $ctxids = str_replace('/', ',', $ctxids);
 3432 
 3433     // Context is the frontpage
 3434     $iscoursepage = false; // coursepage other than fp
 3435     $isfrontpage = false;
 3436     if ($context->contextlevel == CONTEXT_COURSE) {
 3437         if ($context->instanceid == SITEID) {
 3438             $isfrontpage = true;
 3439         } else {
 3440             $iscoursepage = true;
 3441         }
 3442     }
 3443     $isfrontpage = ($isfrontpage || is_inside_frontpage($context));
 3444 
 3445     $caps = (array)$capability;
 3446 
 3447     // construct list of context paths bottom-->top
 3448     list($contextids, $paths) = get_context_info_list($context);
 3449 
 3450     // we need to find out all roles that have these capabilities either in definition or in overrides
 3451     $defs = array();
 3452     list($incontexts, $params) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, 'con');
 3453     list($incaps, $params2) = $DB->get_in_or_equal($caps, SQL_PARAMS_NAMED, 'cap');
 3454 
 3455     // Check whether context locking is enabled.
 3456     // Filter out any write capability if this is the case.
 3457     $excludelockedcaps = '';
 3458     $excludelockedcapsparams = [];
 3459     if (!empty($CFG->contextlocking) && $context->locked) {
 3460         $excludelockedcaps = 'AND (cap.captype = :capread OR cap.name = :managelockscap)';
 3461         $excludelockedcapsparams['capread'] = 'read';
 3462         $excludelockedcapsparams['managelockscap'] = 'moodle/site:managecontextlocks';
 3463     }
 3464 
 3465     $params = array_merge($params, $params2, $excludelockedcapsparams);
 3466     $sql = "SELECT rc.id, rc.roleid, rc.permission, rc.capability, ctx.path
 3467               FROM {role_capabilities} rc
 3468               JOIN {capabilities} cap ON rc.capability = cap.name
 3469               JOIN {context} ctx on rc.contextid = ctx.id
 3470              WHERE rc.contextid $incontexts AND rc.capability $incaps $excludelockedcaps";
 3471 
 3472     $rcs = $DB->get_records_sql($sql, $params);
 3473     foreach ($rcs as $rc) {
 3474         $defs[$rc->capability][$rc->path][$rc->roleid] = $rc->permission;
 3475     }
 3476 
 3477     // go through the permissions bottom-->top direction to evaluate the current permission,
 3478     // first one wins (prohibit is an exception that always wins)
 3479     $access = array();
 3480     foreach ($caps as $cap) {
 3481         foreach ($paths as $path) {
 3482             if (empty($defs[$cap][$path])) {
 3483                 continue;
 3484             }
 3485             foreach($defs[$cap][$path] as $roleid => $perm) {
 3486                 if ($perm == CAP_PROHIBIT) {
 3487                     $access[$cap][$roleid] = CAP_PROHIBIT;
 3488                     continue;
 3489                 }
 3490                 if (!isset($access[$cap][$roleid])) {
 3491                     $access[$cap][$roleid] = (int)$perm;
 3492                 }
 3493             }
 3494         }
 3495     }
 3496 
 3497     // make lists of roles that are needed and prohibited in this context
 3498     $needed = array(); // one of these is enough
 3499     $prohibited = array(); // must not have any of these
 3500     foreach ($caps as $cap) {
 3501         if (empty($access[$cap])) {
 3502             continue;
 3503         }
 3504         foreach ($access[$cap] as $roleid => $perm) {
 3505             if ($perm == CAP_PROHIBIT) {
 3506                 unset($needed[$cap][$roleid]);
 3507                 $prohibited[$cap][$roleid] = true;
 3508             } else if ($perm == CAP_ALLOW and empty($prohibited[$cap][$roleid])) {
 3509                 $needed[$cap][$roleid] = true;
 3510             }
 3511         }
 3512         if (empty($needed[$cap]) or !empty($prohibited[$cap][$defaultuserroleid])) {
 3513             // easy, nobody has the permission
 3514             unset($needed[$cap]);
 3515             unset($prohibited[$cap]);
 3516         } else if ($isfrontpage and !empty($prohibited[$cap][$defaultfrontpageroleid])) {
 3517             // everybody is disqualified on the frontpage
 3518             unset($needed[$cap]);
 3519             unset($prohibited[$cap]);
 3520         }
 3521         if (empty($prohibited[$cap])) {
 3522             unset($prohibited[$cap]);
 3523         }
 3524     }
 3525 
 3526     if (empty($needed)) {
 3527         // there can not be anybody if no roles match this request
 3528         return array();
 3529     }
 3530 
 3531     if (empty($prohibited)) {
 3532         // we can compact the needed roles
 3533         $n = array();
 3534         foreach ($needed as $cap) {
 3535             foreach ($cap as $roleid=>$unused) {
 3536                 $n[$roleid] = true;
 3537             }
 3538         }
 3539         $needed = array('any'=>$n);
 3540         unset($n);
 3541     }
 3542 
 3543     // ***** Set up default fields ******
 3544     if (empty($fields)) {
 3545         if ($iscoursepage) {
 3546             $fields = 'u.*, ul.timeaccess AS lastaccess';
 3547         } else {
 3548             $fields = 'u.*';
 3549         }
 3550     } else {
 3551         if ($CFG->debugdeveloper && strpos($fields, 'u.*') === false && strpos($fields, 'u.id') === false) {
 3552             debugging('u.id must be included in the list of fields passed to get_users_by_capability().', DEBUG_DEVELOPER);
 3553         }
 3554     }
 3555 
 3556     // Set up default sort
 3557     if (empty($sort)) { // default to course lastaccess or just lastaccess
 3558         if ($iscoursepage) {
 3559             $sort = 'ul.timeaccess';
 3560         } else {
 3561             $sort = 'u.lastaccess';
 3562         }
 3563     }
 3564 
 3565     // Prepare query clauses
 3566     $wherecond = array();
 3567     $params    = array();
 3568     $joins     = array();
 3569 
 3570     // User lastaccess JOIN
 3571     if ((strpos($sort, 'ul.timeaccess') === false) and (strpos($fields, 'ul.timeaccess') === false)) {
 3572          // user_lastaccess is not required MDL-13810
 3573     } else {
 3574         if ($iscoursepage) {
 3575             $joins[] = "LEFT OUTER JOIN {user_lastaccess} ul ON (ul.userid = u.id AND ul.courseid = {$context->instanceid})";
 3576         } else {
 3577             throw new coding_exception('Invalid sort in get_users_by_capability(), ul.timeaccess allowed only for course contexts.');
 3578         }
 3579     }
 3580 
 3581     // We never return deleted users or guest account.
 3582     $wherecond[] = "u.deleted = 0 AND u.id <> :guestid";
 3583     $params['guestid'] = $CFG->siteguest;
 3584 
 3585     // Groups
 3586     if ($groups) {
 3587         $groups = (array)$groups;
 3588         list($grouptest, $grpparams) = $DB->get_in_or_equal($groups, SQL_PARAMS_NAMED, 'grp');
 3589         $joins[] = "LEFT OUTER JOIN (SELECT DISTINCT userid
 3590                                        FROM {groups_members}
 3591                                       WHERE groupid $grouptest
 3592                                     ) gm ON gm.userid = u.id";
 3593 
 3594         $params = array_merge($params, $grpparams);
 3595 
 3596         $grouptest = 'gm.userid IS NOT NULL';
 3597         if ($useviewallgroups) {
 3598             $viewallgroupsusers = get_users_by_capability($context, 'moodle/site:accessallgroups', 'u.id, u.id', '', '', '', '', $exceptions);
 3599             if (!empty($viewallgroupsusers)) {
 3600                 $grouptest .= ' OR u.id IN (' . implode(',', array_keys($viewallgroupsusers)) . ')';
 3601             }
 3602         }
 3603         $wherecond[] = "($grouptest)";
 3604     }
 3605 
 3606     // User exceptions
 3607     if (!empty($exceptions)) {
 3608         $exceptions = (array)$exceptions;
 3609         list($exsql, $exparams) = $DB->get_in_or_equal($exceptions, SQL_PARAMS_NAMED, 'exc', false);
 3610         $params = array_merge($params, $exparams);
 3611         $wherecond[] = "u.id $exsql";
 3612     }
 3613 
 3614     // now add the needed and prohibited roles conditions as joins
 3615     if (!empty($needed['any'])) {
 3616         // simple case - there are no prohibits involved
 3617         if (!empty($needed['any'][$defaultuserroleid]) or ($isfrontpage and !empty($needed['any'][$defaultfrontpageroleid]))) {
 3618             // everybody
 3619         } else {
 3620             $joins[] = "JOIN (SELECT DISTINCT userid
 3621                                 FROM {role_assignments}
 3622                                WHERE contextid IN ($ctxids)
 3623                                      AND roleid IN (".implode(',', array_keys($needed['any'])) .")
 3624                              ) ra ON ra.userid = u.id";
 3625         }
 3626     } else {
 3627         $unions = array();
 3628         $everybody = false;
 3629         foreach ($needed as $cap=>$unused) {
 3630             if (empty($prohibited[$cap])) {
 3631                 if (!empty($needed[$cap][$defaultuserroleid]) or ($isfrontpage and !empty($needed[$cap][$defaultfrontpageroleid]))) {
 3632                     $everybody = true;
 3633                     break;
 3634                 } else {
 3635                     $unions[] = "SELECT userid
 3636                                    FROM {role_assignments}
 3637                                   WHERE contextid IN ($ctxids)
 3638                                         AND roleid IN (".implode(',', array_keys($needed[$cap])) .")";
 3639                 }
 3640             } else {
 3641                 if (!empty($prohibited[$cap][$defaultuserroleid]) or ($isfrontpage and !empty($prohibited[$cap][$defaultfrontpageroleid]))) {
 3642                     // nobody can have this cap because it is prevented in default roles
 3643                     continue;
 3644 
 3645                 } else if (!empty($needed[$cap][$defaultuserroleid]) or ($isfrontpage and !empty($needed[$cap][$defaultfrontpageroleid]))) {
 3646                     // everybody except the prohibitted - hiding does not matter
 3647                     $unions[] = "SELECT id AS userid
 3648                                    FROM {user}
 3649                                   WHERE id NOT IN (SELECT userid
 3650                                                      FROM {role_assignments}
 3651                                                     WHERE contextid IN ($ctxids)
 3652                                                           AND roleid IN (".implode(',', array_keys($prohibited[$cap])) ."))";
 3653 
 3654                 } else {
 3655                     $unions[] = "SELECT userid
 3656                                    FROM {role_assignments}
 3657                                   WHERE contextid IN ($ctxids) AND roleid IN (".implode(',', array_keys($needed[$cap])) .")
 3658                                         AND userid NOT IN (
 3659                                             SELECT userid
 3660                                               FROM {role_assignments}
 3661                                              WHERE contextid IN ($ctxids)
 3662                                                     AND roleid IN (" . implode(',', array_keys($prohibited[$cap])) . ")
 3663                                                         )";
 3664                 }
 3665             }
 3666         }
 3667         if (!$everybody) {
 3668             if ($unions) {
 3669                 $joins[] = "JOIN (SELECT DISTINCT userid FROM ( ".implode(' UNION ', $unions)." ) us) ra ON ra.userid = u.id";
 3670             } else {
 3671                 // only prohibits found - nobody can be matched
 3672                 $wherecond[] = "1 = 2";
 3673             }
 3674         }
 3675     }
 3676 
 3677     // Collect WHERE conditions and needed joins
 3678     $where = implode(' AND ', $wherecond);
 3679     if ($where !== '') {
 3680         $where = 'WHERE ' . $where;
 3681     }
 3682     $joins = implode("\n", $joins);
 3683 
 3684     // Ok, let's get the users!
 3685     $sql = "SELECT $fields
 3686               FROM {user} u
 3687             $joins
 3688             $where
 3689           ORDER BY $sort";
 3690 
 3691     return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
 3692 }
 3693 
 3694 /**
 3695  * Re-sort a users array based on a sorting policy
 3696  *
 3697  * Will re-sort a $users results array (from get_users_by_capability(), usually)
 3698  * based on a sorting policy. This is to support the odd practice of
 3699  * sorting teachers by 'authority', where authority was "lowest id of the role
 3700  * assignment".
 3701  *
 3702  * Will execute 1 database query. Only suitable for small numbers of users, as it
 3703  * uses an u.id IN() clause.
 3704  *
 3705  * Notes about the sorting criteria.
 3706  *
 3707  * As a default, we cannot rely on role.sortorder because then
 3708  * admins/coursecreators will always win. That is why the sane
 3709  * rule "is locality matters most", with sortorder as 2nd
 3710  * consideration.
 3711  *
 3712  * If you want role.sortorder, use the 'sortorder' policy, and
 3713  * name explicitly what roles you want to cover. It's probably
 3714  * a good idea to see what roles have the capabilities you want
 3715  * (array_diff() them against roiles that have 'can-do-anything'
 3716  * to weed out admin-ish roles. Or fetch a list of roles from
 3717  * variables like $CFG->coursecontact .
 3718  *
 3719  * @param array $users Users array, keyed on userid
 3720  * @param context $context
 3721  * @param array $roles ids of the roles to include, optional
 3722  * @param string $sortpolicy defaults to locality, more about
 3723  * @return array sorted copy of the array
 3724  */
 3725 function sort_by_roleassignment_authority($users, context $context, $roles = array(), $sortpolicy = 'locality') {
 3726     global $DB;
 3727 
 3728     $userswhere = ' ra.userid IN (' . implode(',',array_keys($users)) . ')';
 3729     $contextwhere = 'AND ra.contextid IN ('.str_replace('/', ',',substr($context->path, 1)).')';
 3730     if (empty($roles)) {
 3731         $roleswhere = '';
 3732     } else {
 3733         $roleswhere = ' AND ra.roleid IN ('.implode(',',$roles).')';
 3734     }
 3735 
 3736     $sql = "SELECT ra.userid
 3737               FROM {role_assignments} ra
 3738               JOIN {role} r
 3739                    ON ra.roleid=r.id
 3740               JOIN {context} ctx
 3741                    ON ra.contextid=ctx.id
 3742              WHERE $userswhere
 3743                    $contextwhere
 3744                    $roleswhere";
 3745 
 3746     // Default 'locality' policy -- read PHPDoc notes
 3747     // about sort policies...
 3748     $orderby = 'ORDER BY '
 3749                     .'ctx.depth DESC, '  /* locality wins */
 3750                     .'r.sortorder ASC, ' /* rolesorting 2nd criteria */
 3751                     .'ra.id';            /* role assignment order tie-breaker */
 3752     if ($sortpolicy === 'sortorder') {
 3753         $orderby = 'ORDER BY '
 3754                         .'r.sortorder ASC, ' /* rolesorting 2nd criteria */
 3755                         .'ra.id';            /* role assignment order tie-breaker */
 3756     }
 3757 
 3758     $sortedids = $DB->get_fieldset_sql($sql . $orderby);
 3759     $sortedusers = array();
 3760     $seen = array();
 3761 
 3762     foreach ($sortedids as $id) {
 3763         // Avoid duplicates
 3764         if (isset($seen[$id])) {
 3765             continue;
 3766         }
 3767         $seen[$id] = true;
 3768 
 3769         // assign
 3770         $sortedusers[$id] = $users[$id];
 3771     }
 3772     return $sortedusers;
 3773 }
 3774 
 3775 /**
 3776  * Gets all the users assigned this role in this context or higher
 3777  *
 3778  * Note that moodle is based on capabilities and it is usually better
 3779  * to check permissions than to check role ids as the capabilities
 3780  * system is more flexible. If you really need, you can to use this
 3781  * function but consider has_capability() as a possible substitute.
 3782  *
 3783  * All $sort fields are added into $fields if not present there yet.
 3784  *
 3785  * If $roleid is an array or is empty (all roles) you need to set $fields
 3786  * (and $sort by extension) params according to it, as the first field
 3787  * returned by the database should be unique (ra.id is the best candidate).
 3788  *
 3789  * @param int $roleid (can also be an array of ints!)
 3790  * @param context $context
 3791  * @param bool $parent if true, get list of users assigned in higher context too
 3792  * @param string $fields fields from user (u.) , role assignment (ra) or role (r.)
 3793  * @param string $sort sort from user (u.) , role assignment (ra.) or role (r.).
 3794  *      null => use default sort from users_order_by_sql.
 3795  * @param bool $all true means all, false means limit to enrolled users
 3796  * @param string $group defaults to ''
 3797  * @param mixed $limitfrom defaults to ''
 3798  * @param mixed $limitnum defaults to ''
 3799  * @param string $extrawheretest defaults to ''
 3800  * @param array $whereorsortparams any paramter values used by $sort or $extrawheretest.
 3801  * @return array
 3802  */
 3803 function get_role_users($roleid, context $context, $parent = false, $fields = '',
 3804         $sort = null, $all = true, $group = '',
 3805         $limitfrom = '', $limitnum = '', $extrawheretest = '', $whereorsortparams = array()) {
 3806     global $DB;
 3807 
 3808     if (empty($fields)) {
 3809         $allnames = get_all_user_name_fields(true, 'u');
 3810         $fields = 'u.id, u.confirmed, u.username, '. $allnames . ', ' .
 3811                   'u.maildisplay, u.mailformat, u.maildigest, u.email, u.emailstop, u.city, '.
 3812                   'u.country, u.picture, u.idnumber, u.department, u.institution, '.
 3813                   'u.lang, u.timezone, u.lastaccess, u.mnethostid, r.name AS rolename, r.sortorder, '.
 3814                   'r.shortname AS roleshortname, rn.name AS rolecoursealias';
 3815     }
 3816 
 3817     // Prevent wrong function uses.
 3818     if ((empty($roleid) || is_array($roleid)) && strpos($fields, 'ra.id') !== 0) {
 3819         debugging('get_role_users() without specifying one single roleid needs to be called prefixing ' .
 3820             'role assignments id (ra.id) as unique field, you can use $fields param for it.');
 3821 
 3822         if (!empty($roleid)) {
 3823             // Solving partially the issue when specifying multiple roles.
 3824             $users = array();
 3825             foreach ($roleid as $id) {
 3826                 // Ignoring duplicated keys keeping the first user appearance.
 3827                 $users = $users + get_role_users($id, $context, $parent, $fields, $sort, $all, $group,
 3828                     $limitfrom, $limitnum, $extrawheretest, $whereorsortparams);
 3829             }
 3830             return $users;
 3831         }
 3832     }
 3833 
 3834     $parentcontexts = '';
 3835     if ($parent) {
 3836         $parentcontexts = substr($context->path, 1); // kill leading slash
 3837         $parentcontexts = str_replace('/', ',', $parentcontexts);
 3838         if ($parentcontexts !== '') {
 3839             $parentcontexts = ' OR ra.contextid IN ('.$parentcontexts.' )';
 3840         }
 3841     }
 3842 
 3843     if ($roleid) {
 3844         list($rids, $params) = $DB->get_in_or_equal($roleid, SQL_PARAMS_NAMED, 'r');
 3845         $roleselect = "AND ra.roleid $rids";
 3846     } else {
 3847         $params = array();
 3848         $roleselect = '';
 3849     }
 3850 
 3851     if ($coursecontext = $context->get_course_context(false)) {
 3852         $params['coursecontext'] = $coursecontext->id;
 3853     } else {
 3854         $params['coursecontext'] = 0;
 3855     }
 3856 
 3857     if ($group) {
 3858         $groupjoin   = "JOIN {groups_members} gm ON gm.userid = u.id";
 3859         $groupselect = " AND gm.groupid = :groupid ";
 3860         $params['groupid'] = $group;
 3861     } else {
 3862         $groupjoin   = '';
 3863         $groupselect = '';
 3864     }
 3865 
 3866     $params['contextid'] = $context->id;
 3867 
 3868     if ($extrawheretest) {
 3869         $extrawheretest = ' AND ' . $extrawheretest;
 3870     }
 3871 
 3872     if ($whereorsortparams) {
 3873         $params = array_merge($params, $whereorsortparams);
 3874     }
 3875 
 3876     if (!$sort) {
 3877         list($sort, $sortparams) = users_order_by_sql('u');
 3878         $params = array_merge($params, $sortparams);
 3879     }
 3880 
 3881     // Adding the fields from $sort that are not present in $fields.
 3882     $sortarray = preg_split('/,\s*/', $sort);
 3883     $fieldsarray = preg_split('/,\s*/', $fields);
 3884 
 3885     // Discarding aliases from the fields.
 3886     $fieldnames = array();
 3887     foreach ($fieldsarray as $key => $field) {
 3888         list($fieldnames[$key]) = explode(' ', $field);
 3889     }
 3890 
 3891     $addedfields = array();
 3892     foreach ($sortarray as $sortfield) {
 3893         // Throw away any additional arguments to the sort (e.g. ASC/DESC).
 3894         list($sortfield) = explode(' ', $sortfield);
 3895         list($tableprefix) = explode('.', $sortfield);
 3896         $fieldpresent = false;
 3897         foreach ($fieldnames as $fieldname) {
 3898             if ($fieldname === $sortfield || $fieldname === $tableprefix.'.*') {
 3899                 $fieldpresent = true;
 3900                 break;
 3901             }
 3902         }
 3903 
 3904         if (!$fieldpresent) {
 3905             $fieldsarray[] = $sortfield;
 3906             $addedfields[] = $sortfield;
 3907         }
 3908     }
 3909 
 3910     $fields = implode(', ', $fieldsarray);
 3911     if (!empty($addedfields)) {
 3912         $addedfields = implode(', ', $addedfields);
 3913         debugging('get_role_users() adding '.$addedfields.' to the query result because they were required by $sort but missing in $fields');
 3914     }
 3915 
 3916     if ($all === null) {
 3917         // Previously null was used to indicate that parameter was not used.
 3918         $all = true;
 3919     }
 3920     if (!$all and $coursecontext) {
 3921         // Do not use get_enrolled_sql() here for performance reasons.
 3922         $ejoin = "JOIN {user_enrolments} ue ON ue.userid = u.id
 3923                   JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :ecourseid)";
 3924         $params['ecourseid'] = $coursecontext->instanceid;
 3925     } else {
 3926         $ejoin = "";
 3927     }
 3928 
 3929     $sql = "SELECT DISTINCT $fields, ra.roleid
 3930               FROM {role_assignments} ra
 3931               JOIN {user} u ON u.id = ra.userid
 3932               JOIN {role} r ON ra.roleid = r.id
 3933             $ejoin
 3934          LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
 3935         $groupjoin
 3936              WHERE (ra.contextid = :contextid $parentcontexts)
 3937                    $roleselect
 3938                    $groupselect
 3939                    $extrawheretest
 3940           ORDER BY $sort";                  // join now so that we can just use fullname() later
 3941 
 3942     return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
 3943 }
 3944 
 3945 /**
 3946  * Counts all the users assigned this role in this context or higher
 3947  *
 3948  * @param int|array $roleid either int or an array of ints
 3949  * @param context $context
 3950  * @param bool $parent if true, get list of users assigned in higher context too
 3951  * @return int Returns the result count
 3952  */
 3953 function count_role_users($roleid, context $context, $parent = false) {
 3954     global $DB;
 3955 
 3956     if ($parent) {
 3957         if ($contexts = $context->get_parent_context_ids()) {
 3958             $parentcontexts = ' OR r.contextid IN ('.implode(',', $contexts).')';
 3959         } else {
 3960             $parentcontexts = '';
 3961         }
 3962     } else {
 3963         $parentcontexts = '';
 3964     }
 3965 
 3966     if ($roleid) {
 3967         list($rids, $params) = $DB->get_in_or_equal($roleid, SQL_PARAMS_QM);
 3968         $roleselect = "AND r.roleid $rids";
 3969     } else {
 3970         $params = array();
 3971         $roleselect = '';
 3972     }
 3973 
 3974     array_unshift($params, $context->id);
 3975 
 3976     $sql = "SELECT COUNT(DISTINCT u.id)
 3977               FROM {role_assignments} r
 3978               JOIN {user} u ON u.id = r.userid
 3979              WHERE (r.contextid = ? $parentcontexts)
 3980                    $roleselect
 3981                    AND u.deleted = 0";
 3982 
 3983     return $DB->count_records_sql($sql, $params);
 3984 }
 3985 
 3986 /**
 3987  * This function gets the list of courses that this user has a particular capability in.
 3988  *
 3989  * It is now reasonably efficient, but bear in mind that if there are users who have the capability
 3990  * everywhere, it may return an array of all courses.
 3991  *
 3992  * @param string $capability Capability in question
 3993  * @param int $userid User ID or null for current user
 3994  * @param bool $doanything True if 'doanything' is permitted (default)
 3995  * @param string $fieldsexceptid Leave blank if you only need 'id' in the course records;
 3996  *   otherwise use a comma-separated list of the fields you require, not including id.
 3997  *   Add ctxid, ctxpath, ctxdepth etc to return course context information for preloading.
 3998  * @param string $orderby If set, use a comma-separated list of fields from course
 3999  *   table with sql modifiers (DESC) if needed
 4000  * @param int $limit Limit the number of courses to return on success. Zero equals all entries.
 4001  * @return array|bool Array of courses, if none found false is returned.
 4002  */
 4003 function get_user_capability_course($capability, $userid = null, $doanything = true, $fieldsexceptid = '', $orderby = '',
 4004         $limit = 0) {
 4005     global $DB, $USER;
 4006 
 4007     // Default to current user.
 4008     if (!$userid) {
 4009         $userid = $USER->id;
 4010     }
 4011 
 4012     if ($doanything && is_siteadmin($userid)) {
 4013         // If the user is a site admin and $doanything is enabled then there is no need to restrict
 4014         // the list of courses.
 4015         $contextlimitsql = '';
 4016         $contextlimitparams = [];
 4017     } else {
 4018         // Gets SQL to limit contexts ('x' table) to those where the user has this capability.
 4019         list ($contextlimitsql, $contextlimitparams) = \core\access\get_user_capability_course_helper::get_sql(
 4020                 $userid, $capability);
 4021         if (!$contextlimitsql) {
 4022             // If the does not have this capability in any context, return false without querying.
 4023             return false;
 4024         }
 4025 
 4026         $contextlimitsql = 'WHERE' . $contextlimitsql;
 4027     }
 4028 
 4029     // Convert fields list and ordering
 4030     $fieldlist = '';
 4031     if ($fieldsexceptid) {
 4032         $fields = array_map('trim', explode(',', $fieldsexceptid));
 4033         foreach($fields as $field) {
 4034             // Context fields have a different alias.
 4035             if (strpos($field, 'ctx') === 0) {
 4036                 switch($field) {
 4037                     case 'ctxlevel' :
 4038                         $realfield = 'contextlevel';
 4039                         break;
 4040                     case 'ctxinstance' :
 4041                         $realfield = 'instanceid';
 4042                         break;
 4043                     default:
 4044                         $realfield = substr($field, 3);
 4045                         break;
 4046                 }
 4047                 $fieldlist .= ',x.' . $realfield . ' AS ' . $field;
 4048             } else {
 4049                 $fieldlist .= ',c.'.$field;
 4050             }
 4051         }
 4052     }
 4053     if ($orderby) {
 4054         $fields = explode(',', $orderby);
 4055         $orderby = '';
 4056         foreach($fields as $field) {
 4057             if ($orderby) {
 4058                 $orderby .= ',';
 4059             }
 4060             $orderby .= 'c.'.$field;
 4061         }
 4062         $orderby = 'ORDER BY '.$orderby;
 4063     }
 4064 
 4065     $courses = array();
 4066     $rs = $DB->get_recordset_sql("
 4067             SELECT c.id $fieldlist
 4068               FROM {course} c
 4069               JOIN {context} x ON c.id = x.instanceid AND x.contextlevel = ?
 4070             $contextlimitsql
 4071             $orderby", array_merge([CONTEXT_COURSE], $contextlimitparams));
 4072     foreach ($rs as $course) {
 4073         $courses[] = $course;
 4074         $limit--;
 4075         if ($limit == 0) {
 4076             break;
 4077         }
 4078     }
 4079     $rs->close();
 4080     return empty($courses) ? false : $courses;
 4081 }
 4082 
 4083 /**
 4084  * Switches the current user to another role for the current session and only
 4085  * in the given context.
 4086  *
 4087  * The caller *must* check
 4088  * - that this op is allowed
 4089  * - that the requested role can be switched to in this context (use get_switchable_roles)
 4090  * - that the requested role is NOT $CFG->defaultuserroleid
 4091  *
 4092  * To "unswitch" pass 0 as the roleid.
 4093  *
 4094  * This function *will* modify $USER->access - beware
 4095  *
 4096  * @param integer $roleid the role to switch to.
 4097  * @param context $context the context in which to perform the switch.
 4098  * @return bool success or failure.
 4099  */
 4100 function role_switch($roleid, context $context) {
 4101     global $USER;
 4102 
 4103     // Add the ghost RA to $USER->access as $USER->access['rsw'][$path] = $roleid.
 4104     // To un-switch just unset($USER->access['rsw'][$path]).
 4105     //
 4106     // Note: it is not possible to switch to roles that do not have course:view
 4107 
 4108     if (!isset($USER->access)) {
 4109         load_all_capabilities();
 4110     }
 4111 
 4112     // Add the switch RA
 4113     if ($roleid == 0) {
 4114         unset($USER->access['rsw'][$context->path]);
 4115         return true;
 4116     }
 4117 
 4118     $USER->access['rsw'][$context->path] = $roleid;
 4119 
 4120     return true;
 4121 }
 4122 
 4123 /**
 4124  * Checks if the user has switched roles within the given course.
 4125  *
 4126  * Note: You can only switch roles within the course, hence it takes a course id
 4127  * rather than a context. On that note Petr volunteered to implement this across
 4128  * all other contexts, all requests for this should be forwarded to him ;)
 4129  *
 4130  * @param int $courseid The id of the course to check
 4131  * @return bool True if the user has switched roles within the course.
 4132  */
 4133 function is_role_switched($courseid) {
 4134     global $USER;
 4135     $context = context_course::instance($courseid, MUST_EXIST);
 4136     return (!empty($USER->access['rsw'][$context->path]));
 4137 }
 4138 
 4139 /**
 4140  * Get any role that has an override on exact context
 4141  *
 4142  * @param context $context The context to check
 4143  * @return array An array of roles
 4144  */
 4145 function get_roles_with_override_on_context(context $context) {
 4146     global $DB;
 4147 
 4148     return $DB->get_records_sql("SELECT r.*
 4149                                    FROM {role_capabilities} rc, {role} r
 4150                                   WHERE rc.roleid = r.id AND rc.contextid = ?",
 4151                                 array($context->id));
 4152 }
 4153 
 4154 /**
 4155  * Get all capabilities for this role on this context (overrides)
 4156  *
 4157  * @param stdClass $role
 4158  * @param context $context
 4159  * @return array
 4160  */
 4161 function get_capabilities_from_role_on_context($role, context $context) {
 4162     global $DB;
 4163 
 4164     return $DB->get_records_sql("SELECT *
 4165                                    FROM {role_capabilities}
 4166                                   WHERE contextid = ? AND roleid = ?",
 4167                                 array($context->id, $role->id));
 4168 }
 4169 
 4170 /**
 4171  * Find all user assignment of users for this role, on this context
 4172  *
 4173  * @param stdClass $role
 4174  * @param context $context
 4175  * @return array
 4176  */
 4177 function get_users_from_role_on_context($role, context $context) {
 4178     global $DB;
 4179 
 4180     return $DB->get_records_sql("SELECT *
 4181                                    FROM {role_assignments}
 4182                                   WHERE contextid = ? AND roleid = ?",
 4183                                 array($context->id, $role->id));
 4184 }
 4185 
 4186 /**
 4187  * Simple function returning a boolean true if user has roles
 4188  * in context or parent contexts, otherwise false.
 4189  *
 4190  * @param int $userid
 4191  * @param int $roleid
 4192  * @param int $contextid empty means any context
 4193  * @return bool
 4194  */
 4195 function user_has_role_assignment($userid, $roleid, $contextid = 0) {
 4196     global $DB;
 4197 
 4198     if ($contextid) {
 4199         if (!$context = context::instance_by_id($contextid, IGNORE_MISSING)) {
 4200             return false;
 4201         }
 4202         $parents = $context->get_parent_context_ids(true);
 4203         list($contexts, $params) = $DB->get_in_or_equal($parents, SQL_PARAMS_NAMED, 'r');
 4204         $params['userid'] = $userid;
 4205         $params['roleid'] = $roleid;
 4206 
 4207         $sql = "SELECT COUNT(ra.id)
 4208                   FROM {role_assignments} ra
 4209                  WHERE ra.userid = :userid AND ra.roleid = :roleid AND ra.contextid $contexts";
 4210 
 4211         $count = $DB->get_field_sql($sql, $params);
 4212         return ($count > 0);
 4213 
 4214     } else {
 4215         return $DB->record_exists('role_assignments', array('userid'=>$userid, 'roleid'=>$roleid));
 4216     }
 4217 }
 4218 
 4219 /**
 4220  * Get localised role name or alias if exists and format the text.
 4221  *
 4222  * @param stdClass $role role object
 4223  *      - optional 'coursealias' property should be included for performance reasons if course context used
 4224  *      - description property is not required here
 4225  * @param context|bool $context empty means system context
 4226  * @param int $rolenamedisplay type of role name
 4227  * @return string localised role name or course role name alias
 4228  */
 4229 function role_get_name(stdClass $role, $context = null, $rolenamedisplay = ROLENAME_ALIAS) {
 4230     global $DB;
 4231 
 4232     if ($rolenamedisplay == ROLENAME_SHORT) {
 4233         return $role->shortname;
 4234     }
 4235 
 4236     if (!$context or !$coursecontext = $context->get_course_context(false)) {
 4237         $coursecontext = null;
 4238     }
 4239 
 4240     if ($coursecontext and !property_exists($role, 'coursealias') and ($rolenamedisplay == ROLENAME_ALIAS or $rolenamedisplay == ROLENAME_BOTH or $rolenamedisplay == ROLENAME_ALIAS_RAW)) {
 4241         $role = clone($role); // Do not modify parameters.
 4242         if ($r = $DB->get_record('role_names', array('roleid'=>$role->id, 'contextid'=>$coursecontext->id))) {
 4243             $role->coursealias = $r->name;
 4244         } else {
 4245             $role->coursealias = null;
 4246         }
 4247     }
 4248 
 4249     if ($rolenamedisplay == ROLENAME_ALIAS_RAW) {
 4250         if ($coursecontext) {
 4251             return $role->coursealias;
 4252         } else {
 4253             return null;
 4254         }
 4255     }
 4256 
 4257     if (trim($role->name) !== '') {
 4258         // For filtering always use context where was the thing defined - system for roles here.
 4259         $original = format_string($role->name, true, array('context'=>context_system::instance()));
 4260 
 4261     } else {
 4262         // Empty role->name means we want to see localised role name based on shortname,
 4263         // only default roles are supposed to be localised.
 4264         switch ($role->shortname) {
 4265             case 'manager':         $original = get_string('manager', 'role'); break;
 4266             case 'coursecreator':   $original = get_string('coursecreators'); break;
 4267             case 'editingteacher':  $original = get_string('defaultcourseteacher'); break;
 4268             case 'teacher':         $original = get_string('noneditingteacher'); break;
 4269             case 'student':         $original = get_string('defaultcoursestudent'); break;
 4270             case 'guest':           $original = get_string('guest'); break;
 4271             case 'user':            $original = get_string('authenticateduser'); break;
 4272             case 'frontpage':       $original = get_string('frontpageuser', 'role'); break;
 4273             // We should not get here, the role UI should require the name for custom roles!
 4274             default:                $original = $role->shortname; break;
 4275         }
 4276     }
 4277 
 4278     if ($rolenamedisplay == ROLENAME_ORIGINAL) {
 4279         return $original;
 4280     }
 4281 
 4282     if ($rolenamedisplay == ROLENAME_ORIGINALANDSHORT) {
 4283         return "$original ($role->shortname)";
 4284     }
 4285 
 4286     if ($rolenamedisplay == ROLENAME_ALIAS) {
 4287         if ($coursecontext and trim($role->coursealias) !== '') {
 4288             return format_string($role->coursealias, true, array('context'=>$coursecontext));
 4289         } else {
 4290             return $original;
 4291         }
 4292     }
 4293 
 4294     if ($rolenamedisplay == ROLENAME_BOTH) {
 4295         if ($coursecontext and trim($role->coursealias) !== '') {
 4296             return format_string($role->coursealias, true, array('context'=>$coursecontext)) . " ($original)";
 4297         } else {
 4298             return $original;
 4299         }
 4300     }
 4301 
 4302     throw new coding_exception('Invalid $rolenamedisplay parameter specified in role_get_name()');
 4303 }
 4304 
 4305 /**
 4306  * Returns localised role description if available.
 4307  * If the name is empty it tries to find the default role name using
 4308  * hardcoded list of default role names or other methods in the future.
 4309  *
 4310  * @param stdClass $role
 4311  * @return string localised role name
 4312  */
 4313 function role_get_description(stdClass $role) {
 4314     if (!html_is_blank($role->description)) {
 4315         return format_text($role->description, FORMAT_HTML, array('context'=>context_system::instance()));
 4316     }
 4317 
 4318     switch ($role->shortname) {
 4319         case 'manager':         return get_string('managerdescription', 'role');
 4320         case 'coursecreator':   return get_string('coursecreatorsdescription');
 4321         case 'editingteacher':  return get_string('defaultcourseteacherdescription');
 4322         case 'teacher':         return get_string('noneditingteacherdescription');
 4323         case 'student':         return get_string('defaultcoursestudentdescription');
 4324         case 'guest':           return get_string('guestdescription');
 4325         case 'user':            return get_string('authenticateduserdescription');
 4326         case 'frontpage':       return get_string('frontpageuserdescription', 'role');
 4327         default:                return '';
 4328     }
 4329 }
 4330 
 4331 /**
 4332  * Get all the localised role names for a context.
 4333  *
 4334  * In new installs default roles have empty names, this function
 4335  * add localised role names using current language pack.
 4336  *
 4337  * @param context $context the context, null means system context
 4338  * @param array of role objects with a ->localname field containing the context-specific role name.
 4339  * @param int $rolenamedisplay
 4340  * @param bool $returnmenu true means id=>localname, false means id=>rolerecord
 4341  * @return array Array of context-specific role names, or role objects with a ->localname field added.
 4342  */
 4343 function role_get_names(context $context = null, $rolenamedisplay = ROLENAME_ALIAS, $returnmenu = null) {
 4344     return role_fix_names(get_all_roles($context), $context, $rolenamedisplay, $returnmenu);
 4345 }
 4346 
 4347 /**
 4348  * Prepare list of roles for display, apply aliases and localise default role names.
 4349  *
 4350  * @param array $roleoptions array roleid => roleobject (with optional coursealias), strings are accepted for backwards compatibility only
 4351  * @param context $context the context, null means system context
 4352  * @param int $rolenamedisplay
 4353  * @param bool $returnmenu null means keep the same format as $roleoptions, true means id=>localname, false means id=>rolerecord
 4354  * @return array Array of context-specific role names, or role objects with a ->localname field added.
 4355  */
 4356 function role_fix_names($roleoptions, context $context = null, $rolenamedisplay = ROLENAME_ALIAS, $returnmenu = null) {
 4357     global $DB;
 4358 
 4359     if (empty($roleoptions)) {
 4360         return array();
 4361     }
 4362 
 4363     if (!$context or !$coursecontext = $context->get_course_context(false)) {
 4364         $coursecontext = null;
 4365     }
 4366 
 4367     // We usually need all role columns...
 4368     $first = reset($roleoptions);
 4369     if ($returnmenu === null) {
 4370         $returnmenu = !is_object($first);
 4371     }
 4372 
 4373     if (!is_object($first) or !property_exists($first, 'shortname')) {
 4374         $allroles = get_all_roles($context);
 4375         foreach ($roleoptions as $rid => $unused) {
 4376             $roleoptions[$rid] = $allroles[$rid];
 4377         }
 4378     }
 4379 
 4380     // Inject coursealias if necessary.
 4381     if ($coursecontext and ($rolenamedisplay == ROLENAME_ALIAS_RAW or $rolenamedisplay == ROLENAME_ALIAS or $rolenamedisplay == ROLENAME_BOTH)) {
 4382         $first = reset($roleoptions);
 4383         if (!property_exists($first, 'coursealias')) {
 4384             $aliasnames = $DB->get_records('role_names', array('contextid'=>$coursecontext->id));
 4385             foreach ($aliasnames as $alias) {
 4386                 if (isset($roleoptions[$alias->roleid])) {
 4387                     $roleoptions[$alias->roleid]->coursealias = $alias->name;
 4388                 }
 4389             }
 4390         }
 4391     }
 4392 
 4393     // Add localname property.
 4394     foreach ($roleoptions as $rid => $role) {
 4395         $roleoptions[$rid]->localname = role_get_name($role, $coursecontext, $rolenamedisplay);
 4396     }
 4397 
 4398     if (!$returnmenu) {
 4399         return $roleoptions;
 4400     }
 4401 
 4402     $menu = array();
 4403     foreach ($roleoptions as $rid => $role) {
 4404         $menu[$rid] = $role->localname;
 4405     }
 4406 
 4407     return $menu;
 4408 }
 4409 
 4410 /**
 4411  * Aids in detecting if a new line is required when reading a new capability
 4412  *
 4413  * This function helps admin/roles/manage.php etc to detect if a new line should be printed
 4414  * when we read in a new capability.
 4415  * Most of the time, if the 2 components are different we should print a new line, (e.g. course system->rss client)
 4416  * but when we are in grade, all reports/import/export capabilities should be together
 4417  *
 4418  * @param string $cap component string a
 4419  * @param string $comp component string b
 4420  * @param int $contextlevel
 4421  * @return bool whether 2 component are in different "sections"
 4422  */
 4423 function component_level_changed($cap, $comp, $contextlevel) {
 4424 
 4425     if (strstr($cap->component, '/') && strstr($comp, '/')) {
 4426         $compsa = explode('/', $cap->component);
 4427         $compsb = explode('/', $comp);
 4428 
 4429         // list of system reports
 4430         if (($compsa[0] == 'report') && ($compsb[0] == 'report')) {
 4431             return false;
 4432         }
 4433 
 4434         // we are in gradebook, still
 4435         if (($compsa[0] == 'gradeexport' || $compsa[0] == 'gradeimport' || $compsa[0] == 'gradereport') &&
 4436             ($compsb[0] == 'gradeexport' || $compsb[0] == 'gradeimport' || $compsb[0] == 'gradereport')) {
 4437             return false;
 4438         }
 4439 
 4440         if (($compsa[0] == 'coursereport') && ($compsb[0] == 'coursereport')) {
 4441             return false;
 4442         }
 4443     }
 4444 
 4445     return ($cap->component != $comp || $cap->contextlevel != $contextlevel);
 4446 }
 4447 
 4448 /**
 4449  * Fix the roles.sortorder field in the database, so it contains sequential integers,
 4450  * and return an array of roleids in order.
 4451  *
 4452  * @param array $allroles array of roles, as returned by get_all_roles();
 4453  * @return array $role->sortorder =-> $role->id with the keys in ascending order.
 4454  */
 4455 function fix_role_sortorder($allroles) {
 4456     global $DB;
 4457 
 4458     $rolesort = array();
 4459     $i = 0;
 4460     foreach ($allroles as $role) {
 4461         $rolesort[$i] = $role->id;
 4462         if ($role->sortorder != $i) {
 4463             $r = new stdClass();
 4464             $r->id = $role->id;
 4465             $r->sortorder = $i;
 4466             $DB->update_record('role', $r);
 4467             $allroles[$role->id]->sortorder = $i;
 4468         }
 4469         $i++;
 4470     }
 4471     return $rolesort;
 4472 }
 4473 
 4474 /**
 4475  * Switch the sort order of two roles (used in admin/roles/manage.php).
 4476  *
 4477  * @param stdClass $first The first role. Actually, only ->sortorder is used.
 4478  * @param stdClass $second The second role. Actually, only ->sortorder is used.
 4479  * @return boolean success or failure
 4480  */
 4481 function switch_roles($first, $second) {
 4482     global $DB;
 4483     $temp = $DB->get_field('role', 'MAX(sortorder) + 1', array());
 4484     $result = $DB->set_field('role', 'sortorder', $temp, array('sortorder' => $first->sortorder));
 4485     $result = $result && $DB->set_field('role', 'sortorder', $first->sortorder, array('sortorder' => $second->sortorder));
 4486     $result = $result && $DB->set_field('role', 'sortorder', $second->sortorder, array('sortorder' => $temp));
 4487     return $result;
 4488 }
 4489 
 4490 /**
 4491  * Duplicates all the base definitions of a role
 4492  *
 4493  * @param stdClass $sourcerole role to copy from
 4494  * @param int $targetrole id of role to copy to
 4495  */
 4496 function role_cap_duplicate($sourcerole, $targetrole) {
 4497     global $DB;
 4498 
 4499     $systemcontext = context_system::instance();
 4500     $caps = $DB->get_records_sql("SELECT *
 4501                                     FROM {role_capabilities}
 4502                                    WHERE roleid = ? AND contextid = ?",
 4503                                  array($sourcerole->id, $systemcontext->id));
 4504     // adding capabilities
 4505     foreach ($caps as $cap) {
 4506         unset($cap->id);
 4507         $cap->roleid = $targetrole;
 4508         $DB->insert_record('role_capabilities', $cap);
 4509     }
 4510 
 4511     // Reset any cache of this role, including MUC.
 4512     accesslib_clear_role_cache($targetrole);
 4513 }
 4514 
 4515 /**
 4516  * Returns two lists, this can be used to find out if user has capability.
 4517  * Having any needed role and no forbidden role in this context means
 4518  * user has this capability in this context.
 4519  * Use get_role_names_with_cap_in_context() if you need role names to display in the UI
 4520  *
 4521  * @param stdClass $context
 4522  * @param string $capability
 4523  * @return array($neededroles, $forbiddenroles)
 4524  */
 4525 function get_roles_with_cap_in_context($context, $capability) {
 4526     global $DB;
 4527 
 4528     $ctxids = trim($context->path, '/'); // kill leading slash
 4529     $ctxids = str_replace('/', ',', $ctxids);
 4530 
 4531     $sql = "SELECT rc.id, rc.roleid, rc.permission, ctx.depth
 4532               FROM {role_capabilities} rc
 4533               JOIN {context} ctx ON ctx.id = rc.contextid
 4534               JOIN {capabilities} cap ON rc.capability = cap.name
 4535              WHERE rc.capability = :cap AND ctx.id IN ($ctxids)
 4536           ORDER BY rc.roleid ASC, ctx.depth DESC";
 4537     $params = array('cap'=>$capability);
 4538 
 4539     if (!$capdefs = $DB->get_records_sql($sql, $params)) {
 4540         // no cap definitions --> no capability
 4541         return array(array(), array());
 4542     }
 4543 
 4544     $forbidden = array();
 4545     $needed    = array();
 4546     foreach($capdefs as $def) {
 4547         if (isset($forbidden[$def->roleid])) {
 4548             continue;
 4549         }
 4550         if ($def->permission == CAP_PROHIBIT) {
 4551             $forbidden[$def->roleid] = $def->roleid;
 4552             unset($needed[$def->roleid]);
 4553             continue;
 4554         }
 4555         if (!isset($needed[$def->roleid])) {
 4556             if ($def->permission == CAP_ALLOW) {
 4557                 $needed[$def->roleid] = true;
 4558             } else if ($def->permission == CAP_PREVENT) {
 4559                 $needed[$def->roleid] = false;
 4560             }
 4561         }
 4562     }
 4563     unset($capdefs);
 4564 
 4565     // remove all those roles not allowing
 4566     foreach($needed as $key=>$value) {
 4567         if (!$value) {
 4568             unset($needed[$key]);
 4569         } else {
 4570             $needed[$key] = $key;
 4571         }
 4572     }
 4573 
 4574     return array($needed, $forbidden);
 4575 }
 4576 
 4577 /**
 4578  * Returns an array of role IDs that have ALL of the the supplied capabilities
 4579  * Uses get_roles_with_cap_in_context(). Returns $allowed minus $forbidden
 4580  *
 4581  * @param stdClass $context
 4582  * @param array $capabilities An array of capabilities
 4583  * @return array of roles with all of the required capabilities
 4584  */
 4585 function get_roles_with_caps_in_context($context, $capabilities) {
 4586     $neededarr = array();
 4587     $forbiddenarr = array();
 4588     foreach($capabilities as $caprequired) {
 4589         list($neededarr[], $forbiddenarr[]) = get_roles_with_cap_in_context($context, $caprequired);
 4590     }
 4591 
 4592     $rolesthatcanrate = array();
 4593     if (!empty($neededarr)) {
 4594         foreach ($neededarr as $needed) {
 4595             if (empty($rolesthatcanrate)) {
 4596                 $rolesthatcanrate = $needed;
 4597             } else {
 4598                 //only want roles that have all caps
 4599                 $rolesthatcanrate = array_intersect_key($rolesthatcanrate,$needed);
 4600             }
 4601         }
 4602     }
 4603     if (!empty($forbiddenarr) && !empty($rolesthatcanrate)) {
 4604         foreach ($forbiddenarr as $forbidden) {
 4605            //remove any roles that are forbidden any of the caps
 4606            $rolesthatcanrate = array_diff($rolesthatcanrate, $forbidden);
 4607         }
 4608     }
 4609     return $rolesthatcanrate;
 4610 }
 4611 
 4612 /**
 4613  * Returns an array of role names that have ALL of the the supplied capabilities
 4614  * Uses get_roles_with_caps_in_context(). Returns $allowed minus $forbidden
 4615  *
 4616  * @param stdClass $context
 4617  * @param array $capabilities An array of capabilities
 4618  * @return array of roles with all of the required capabilities
 4619  */
 4620 function get_role_names_with_caps_in_context($context, $capabilities) {
 4621     global $DB;
 4622 
 4623     $rolesthatcanrate = get_roles_with_caps_in_context($context, $capabilities);
 4624     $allroles = $DB->get_records('role', null, 'sortorder DESC');
 4625 
 4626     $roles = array();
 4627     foreach ($rolesthatcanrate as $r) {
 4628         $roles[$r] = $allroles[$r];
 4629     }
 4630 
 4631     return role_fix_names($roles, $context, ROLENAME_ALIAS, true);
 4632 }
 4633 
 4634 /**
 4635  * This function verifies the prohibit comes from this context
 4636  * and there are no more prohibits in parent contexts.
 4637  *
 4638  * @param int $roleid
 4639  * @param context $context
 4640  * @param string $capability name
 4641  * @return bool
 4642  */
 4643 function prohibit_is_removable($roleid, context $context, $capability) {
 4644     global $DB;
 4645 
 4646     $ctxids = trim($context->path, '/'); // kill leading slash
 4647     $ctxids = str_replace('/', ',', $ctxids);
 4648 
 4649     $params = array('roleid'=>$roleid, 'cap'=>$capability, 'prohibit'=>CAP_PROHIBIT);
 4650 
 4651     $sql = "SELECT ctx.id
 4652               FROM {role_capabilities} rc
 4653               JOIN {context} ctx ON ctx.id = rc.contextid
 4654               JOIN {capabilities} cap ON rc.capability = cap.name
 4655              WHERE rc.roleid = :roleid AND rc.permission = :prohibit AND rc.capability = :cap AND ctx.id IN ($ctxids)
 4656           ORDER BY ctx.depth DESC";
 4657 
 4658     if (!$prohibits = $DB->get_records_sql($sql, $params)) {
 4659         // no prohibits == nothing to remove
 4660         return true;
 4661     }
 4662 
 4663     if (count($prohibits) > 1) {
 4664         // more prohibits can not be removed
 4665         return false;
 4666     }
 4667 
 4668     return !empty($prohibits[$context->id]);
 4669 }
 4670 
 4671 /**
 4672  * More user friendly role permission changing,
 4673  * it should produce as few overrides as possible.
 4674  *
 4675  * @param int $roleid
 4676  * @param stdClass $context
 4677  * @param string $capname capability name
 4678  * @param int $permission
 4679  * @return void
 4680  */
 4681 function role_change_permission($roleid, $context, $capname, $permission) {
 4682     global $DB;
 4683 
 4684     if ($permission == CAP_INHERIT) {
 4685         unassign_capability($capname, $roleid, $context->id);
 4686         return;
 4687     }
 4688 
 4689     $ctxids = trim($context->path, '/'); // kill leading slash
 4690     $ctxids = str_replace('/', ',', $ctxids);
 4691 
 4692     $params = array('roleid'=>$roleid, 'cap'=>$capname);
 4693 
 4694     $sql = "SELECT ctx.id, rc.permission, ctx.depth
 4695               FROM {role_capabilities} rc
 4696               JOIN {context} ctx ON ctx.id = rc.contextid
 4697               JOIN {capabilities} cap ON rc.capability = cap.name
 4698              WHERE rc.roleid = :roleid AND rc.capability = :cap AND ctx.id IN ($ctxids)
 4699           ORDER BY ctx.depth DESC";
 4700 
 4701     if ($existing = $DB->get_records_sql($sql, $params)) {
 4702         foreach($existing as $e) {
 4703             if ($e->permission == CAP_PROHIBIT) {
 4704                 // prohibit can not be overridden, no point in changing anything
 4705                 return;
 4706             }
 4707         }
 4708         $lowest = array_shift($existing);
 4709         if ($lowest->permission == $permission) {
 4710             // permission already set in this context or parent - nothing to do
 4711             return;
 4712         }
 4713         if ($existing) {
 4714             $parent = array_shift($existing);
 4715             if ($parent->permission == $permission) {
 4716                 // permission already set in parent context or parent - just unset in this context
 4717                 // we do this because we want as few overrides as possible for performance reasons
 4718                 unassign_capability($capname, $roleid, $context->id);
 4719                 return;
 4720             }
 4721         }
 4722 
 4723     } else {
 4724         if ($permission == CAP_PREVENT) {
 4725             // nothing means role does not have permission
 4726             return;
 4727         }
 4728     }
 4729 
 4730     // assign the needed capability
 4731     assign_capability($capname, $permission, $roleid, $context->id, true);
 4732 }
 4733 
 4734 
 4735 /**
 4736  * Basic moodle context abstraction class.
 4737  *
 4738  * Google confirms that no other important framework is using "context" class,
 4739  * we could use something else like mcontext or moodle_context, but we need to type
 4740  * this very often which would be annoying and it would take too much space...
 4741  *
 4742  * This class is derived from stdClass for backwards compatibility with
 4743  * odl $context record that was returned from DML $DB->get_record()
 4744  *
 4745  * @package   core_access
 4746  * @category  access
 4747  * @copyright Petr Skoda {@link http://skodak.org}
 4748  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 4749  * @since     Moodle 2.2
 4750  *
 4751  * @property-read int $id context id
 4752  * @property-read int $contextlevel CONTEXT_SYSTEM, CONTEXT_COURSE, etc.
 4753  * @property-read int $instanceid id of related instance in each context
 4754  * @property-read string $path path to context, starts with system context
 4755  * @property-read int $depth
 4756  */
 4757 abstract class context extends stdClass implements IteratorAggregate {
 4758 
 4759     /**
 4760      * The context id
 4761      * Can be accessed publicly through $context->id
 4762      * @var int
 4763      */
 4764     protected $_id;
 4765 
 4766     /**
 4767      * The context level
 4768      * Can be accessed publicly through $context->contextlevel
 4769      * @var int One of CONTEXT_* e.g. CONTEXT_COURSE, CONTEXT_MODULE
 4770      */
 4771     protected $_contextlevel;
 4772 
 4773     /**
 4774      * Id of the item this context is related to e.g. COURSE_CONTEXT => course.id
 4775      * Can be accessed publicly through $context->instanceid
 4776      * @var int
 4777      */
 4778     protected $_instanceid;
 4779 
 4780     /**
 4781      * The path to the context always starting from the system context
 4782      * Can be accessed publicly through $context->path
 4783      * @var string
 4784      */
 4785     protected $_path;
 4786 
 4787     /**
 4788      * The depth of the context in relation to parent contexts
 4789      * Can be accessed publicly through $context->depth
 4790      * @var int
 4791      */
 4792     protected $_depth;
 4793 
 4794     /**
 4795      * Whether this context is locked or not.
 4796      *
 4797      * Can be accessed publicly through $context->locked.
 4798      *
 4799      * @var int
 4800      */
 4801     protected $_locked;
 4802 
 4803     /**
 4804      * @var array Context caching info
 4805      */
 4806     private static $cache_contextsbyid = array();
 4807 
 4808     /**
 4809      * @var array Context caching info
 4810      */
 4811     private static $cache_contexts     = array();
 4812 
 4813     /**
 4814      * Context count
 4815      * Why do we do count contexts? Because count($array) is horribly slow for large arrays
 4816      * @var int
 4817      */
 4818     protected static $cache_count      = 0;
 4819 
 4820     /**
 4821      * @var array Context caching info
 4822      */
 4823     protected static $cache_preloaded  = array();
 4824 
 4825     /**
 4826      * @var context_system The system context once initialised
 4827      */
 4828     protected static $systemcontext    = null;
 4829 
 4830     /**
 4831      * Resets the cache to remove all data.
 4832      * @static
 4833      */
 4834     protected static function reset_caches() {
 4835         self::$cache_contextsbyid = array();
 4836         self::$cache_contexts     = array();
 4837         self::$cache_count        = 0;
 4838         self::$cache_preloaded    = array();
 4839 
 4840         self::$systemcontext = null;
 4841     }
 4842 
 4843     /**
 4844      * Adds a context to the cache. If the cache is full, discards a batch of
 4845      * older entries.
 4846      *
 4847      * @static
 4848      * @param context $context New context to add
 4849      * @return void
 4850      */
 4851     protected static function cache_add(context $context) {
 4852         if (isset(self::$cache_contextsbyid[$context->id])) {
 4853             // already cached, no need to do anything - this is relatively cheap, we do all this because count() is slow
 4854             return;
 4855         }
 4856 
 4857         if (self::$cache_count >= CONTEXT_CACHE_MAX_SIZE) {
 4858             $i = 0;
 4859             foreach(self::$cache_contextsbyid as $ctx) {
 4860                 $i++;
 4861                 if ($i <= 100) {
 4862                     // we want to keep the first contexts to be loaded on this page, hopefully they will be needed again later
 4863                     continue;
 4864                 }
 4865                 if ($i > (CONTEXT_CACHE_MAX_SIZE / 3)) {
 4866                     // we remove oldest third of the contexts to make room for more contexts
 4867                     break;
 4868                 }
 4869                 unset(self::$cache_contextsbyid[$ctx->id]);
 4870                 unset(self::$cache_contexts[$ctx->contextlevel][$ctx->instanceid]);
 4871                 self::$cache_count--;
 4872             }
 4873         }
 4874 
 4875         self::$cache_contexts[$context->contextlevel][$context->instanceid] = $context;
 4876         self::$cache_contextsbyid[$context->id] = $context;
 4877         self::$cache_count++;
 4878     }
 4879 
 4880     /**
 4881      * Removes a context from the cache.
 4882      *
 4883      * @static
 4884      * @param context $context Context object to remove
 4885      * @return void
 4886      */
 4887     protected static function cache_remove(context $context) {
 4888         if (!isset(self::$cache_contextsbyid[$context->id])) {
 4889             // not cached, no need to do anything - this is relatively cheap, we do all this because count() is slow
 4890             return;
 4891         }
 4892         unset(self::$cache_contexts[$context->contextlevel][$context->instanceid]);
 4893         unset(self::$cache_contextsbyid[$context->id]);
 4894 
 4895         self::$cache_count--;
 4896 
 4897         if (self::$cache_count < 0) {
 4898             self::$cache_count = 0;
 4899         }
 4900     }
 4901 
 4902     /**
 4903      * Gets a context from the cache.
 4904      *
 4905      * @static
 4906      * @param int $contextlevel Context level
 4907      * @param int $instance Instance ID
 4908      * @return context|bool Context or false if not in cache
 4909      */
 4910     protected static function cache_get($contextlevel, $instance) {
 4911         if (isset(self::$cache_contexts[$contextlevel][$instance])) {
 4912             return self::$cache_contexts[$contextlevel][$instance];
 4913         }
 4914         return false;
 4915     }
 4916 
 4917     /**
 4918      * Gets a context from the cache based on its id.
 4919      *
 4920      * @static
 4921      * @param int $id Context ID
 4922      * @return context|bool Context or false if not in cache
 4923      */
 4924     protected static function cache_get_by_id($id) {
 4925         if (isset(self::$cache_contextsbyid[$id])) {
 4926             return self::$cache_contextsbyid[$id];
 4927         }
 4928         return false;
 4929     }
 4930 
 4931     /**
 4932      * Preloads context information from db record and strips the cached info.
 4933      *
 4934      * @static
 4935      * @param stdClass $rec
 4936      * @return void (modifies $rec)
 4937      */
 4938     protected static function preload_from_record(stdClass $rec) {
 4939         $notenoughdata = false;
 4940         $notenoughdata = $notenoughdata || empty($rec->ctxid);
 4941         $notenoughdata = $notenoughdata || empty($rec->ctxlevel);
 4942         $notenoughdata = $notenoughdata || !isset($rec->ctxinstance);
 4943         $notenoughdata = $notenoughdata || empty($rec->ctxpath);
 4944         $notenoughdata = $notenoughdata || empty($rec->ctxdepth);
 4945         $notenoughdata = $notenoughdata || !isset($rec->ctxlocked);
 4946         if ($notenoughdata) {
 4947             // The record does not have enough data, passed here repeatedly or context does not exist yet.
 4948             if (isset($rec->ctxid) && !isset($rec->ctxlocked)) {
 4949                 debugging('Locked value missing. Code is possibly not usings the getter properly.', DEBUG_DEVELOPER);
 4950             }
 4951             return;
 4952         }
 4953 
 4954         $record = (object) [
 4955             'id'            => $rec->ctxid,
 4956             'contextlevel'  => $rec->ctxlevel,
 4957             'instanceid'    => $rec->ctxinstance,
 4958             'path'          => $rec->ctxpath,
 4959             'depth'         => $rec->ctxdepth,
 4960             'locked'        => $rec->ctxlocked,
 4961         ];
 4962 
 4963         unset($rec->ctxid);
 4964         unset($rec->ctxlevel);
 4965         unset($rec->ctxinstance);
 4966         unset($rec->ctxpath);
 4967         unset($rec->ctxdepth);
 4968         unset($rec->ctxlocked);
 4969 
 4970         return context::create_instance_from_record($record);
 4971     }
 4972 
 4973 
 4974     // ====== magic methods =======
 4975 
 4976     /**
 4977      * Magic setter method, we do not want anybody to modify properties from the outside
 4978      * @param string $name
 4979      * @param mixed $value
 4980      */
 4981     public function __set($name, $value) {
 4982         debugging('Can not change context instance properties!');
 4983     }
 4984 
 4985     /**
 4986      * Magic method getter, redirects to read only values.
 4987      * @param string $name
 4988      * @return mixed
 4989      */
 4990     public function __get($name) {
 4991         switch ($name) {
 4992             case 'id':
 4993                 return $this->_id;
 4994             case 'contextlevel':
 4995                 return $this->_contextlevel;
 4996             case 'instanceid':
 4997                 return $this->_instanceid;
 4998             case 'path':
 4999                 return $this->_path;
 5000             case 'depth':
 5001                 return $this->_depth;
 5002             case 'locked':
 5003                 return $this->is_locked();
 5004 
 5005             default:
 5006                 debugging('Invalid context property accessed! '.$name);
 5007                 return null;
 5008         }
 5009     }
 5010 
 5011     /**
 5012      * Full support for isset on our magic read only properties.
 5013      * @param string $name
 5014      * @return bool
 5015      */
 5016     public function __isset($name) {
 5017         switch ($name) {
 5018             case 'id':
 5019                 return isset($this->_id);
 5020             case 'contextlevel':
 5021                 return isset($this->_contextlevel);
 5022             case 'instanceid':
 5023                 return isset($this->_instanceid);
 5024             case 'path':
 5025                 return isset($this->_path);
 5026             case 'depth':
 5027                 return isset($this->_depth);
 5028             case 'locked':
 5029                 // Locked is always set.
 5030                 return true;
 5031             default:
 5032                 return false;
 5033         }
 5034     }
 5035 
 5036     /**
 5037      * All properties are read only, sorry.
 5038      * @param string $name
 5039      */
 5040     public function __unset($name) {
 5041         debugging('Can not unset context instance properties!');
 5042     }
 5043 
 5044     // ====== implementing method from interface IteratorAggregate ======
 5045 
 5046     /**
 5047      * Create an iterator because magic vars can't be seen by 'foreach'.
 5048      *
 5049      * Now we can convert context object to array using convert_to_array(),
 5050      * and feed it properly to json_encode().
 5051      */
 5052     public function getIterator() {
 5053         $ret = array(
 5054             'id'           => $this->id,
 5055             'contextlevel' => $this->contextlevel,
 5056             'instanceid'   => $this->instanceid,
 5057             'path'         => $this->path,
 5058             'depth'        => $this->depth,
 5059             'locked'       => $this->locked,
 5060         );
 5061         return new ArrayIterator($ret);
 5062     }
 5063 
 5064     // ====== general context methods ======
 5065 
 5066     /**
 5067      * Constructor is protected so that devs are forced to
 5068      * use context_xxx::instance() or context::instance_by_id().
 5069      *
 5070      * @param stdClass $record
 5071      */
 5072     protected function __construct(stdClass $record) {
 5073         $this->_id           = (int)$record->id;
 5074         $this->_contextlevel = (int)$record->contextlevel;
 5075         $this->_instanceid   = $record->instanceid;
 5076         $this->_path         = $record->path;
 5077         $this->_depth        = $record->depth;
 5078 
 5079         if (isset($record->locked)) {
 5080             $this->_locked = $record->locked;
 5081         } else if (!during_initial_install() && !moodle_needs_upgrading()) {
 5082             debugging('Locked value missing. Code is possibly not usings the getter properly.', DEBUG_DEVELOPER);
 5083         }
 5084     }
 5085 
 5086     /**
 5087      * This function is also used to work around 'protected' keyword problems in context_helper.
 5088      * @static
 5089      * @param stdClass $record
 5090      * @return context instance
 5091      */
 5092     protected static function create_instance_from_record(stdClass $record) {
 5093         $classname = context_helper::get_class_for_level($record->contextlevel);
 5094 
 5095         if ($context = context::cache_get_by_id($record->id)) {
 5096             return $context;
 5097         }
 5098 
 5099         $context = new $classname($record);
 5100         context::cache_add($context);
 5101 
 5102         return $context;
 5103     }
 5104 
 5105     /**
 5106      * Copy prepared new contexts from temp table to context table,
 5107      * we do this in db specific way for perf reasons only.
 5108      * @static
 5109      */
 5110     protected static function merge_context_temp_table() {
 5111         global $DB;
 5112 
 5113         /* MDL-11347:
 5114          *  - mysql does not allow to use FROM in UPDATE statements
 5115          *  - using two tables after UPDATE works in mysql, but might give unexpected
 5116          *    results in pg 8 (depends on configuration)
 5117          *  - using table alias in UPDATE does not work in pg < 8.2
 5118          *
 5119          * Different code for each database - mostly for performance reasons
 5120          */
 5121 
 5122         $dbfamily = $DB->get_dbfamily();
 5123         if ($dbfamily == 'mysql') {
 5124             $updatesql = "UPDATE {context} ct, {context_temp} temp
 5125                              SET ct.path     = temp.path,
 5126                                  ct.depth    = temp.depth,
 5127                                  ct.locked   = temp.locked
 5128                            WHERE ct.id = temp.id";
 5129         } else if ($dbfamily == 'oracle') {
 5130             $updatesql = "UPDATE {context} ct
 5131                              SET (ct.path, ct.depth, ct.locked) =
 5132                                  (SELECT temp.path, temp.depth, temp.locked
 5133                                     FROM {context_temp} temp
 5134                                    WHERE temp.id=ct.id)
 5135                            WHERE EXISTS (SELECT 'x'
 5136                                            FROM {context_temp} temp
 5137                                            WHERE temp.id = ct.id)";
 5138         } else if ($dbfamily == 'postgres' or $dbfamily == 'mssql') {
 5139             $updatesql = "UPDATE {context}
 5140                              SET path     = temp.path,
 5141                                  depth    = temp.depth,
 5142                                  locked   = temp.locked
 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                                  locked   = (SELECT locked FROM {context_temp} WHERE id = {context}.id)
 5151                              WHERE id IN (SELECT id FROM {context_temp})";
 5152         }
 5153 
 5154         $DB->execute($updatesql);
 5155     }
 5156 
 5157    /**
 5158     * Get a context instance as an object, from a given context id.
 5159     *
 5160     * @static
 5161     * @param int $id context id
 5162     * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found;
 5163     *                        MUST_EXIST means throw exception if no record found
 5164     * @return context|bool the context object or false if not found
 5165     */
 5166     public static function instance_by_id($id, $strictness = MUST_EXIST) {
 5167         global $DB;
 5168 
 5169         if (get_called_class() !== 'context' and get_called_class() !== 'context_helper') {
 5170             // some devs might confuse context->id and instanceid, better prevent these mistakes completely
 5171             throw new coding_exception('use only context::instance_by_id() for real context levels use ::instance() methods');
 5172         }
 5173 
 5174         if ($id == SYSCONTEXTID) {
 5175             return context_system::instance(0, $strictness);
 5176         }
 5177 
 5178         if (is_array($id) or is_object($id) or empty($id)) {
 5179             throw new coding_exception('Invalid context id specified context::instance_by_id()');
 5180         }
 5181 
 5182         if ($context = context::cache_get_by_id($id)) {
 5183             return $context;
 5184         }
 5185 
 5186         if ($record = $DB->get_record('context', array('id'=>$id), '*', $strictness)) {
 5187             return context::create_instance_from_record($record);
 5188         }
 5189 
 5190         return false;
 5191     }
 5192 
 5193     /**
 5194      * Update context info after moving context in the tree structure.
 5195      *
 5196      * @param context $newparent
 5197      * @return void
 5198      */
 5199     public function update_moved(context $newparent) {
 5200         global $DB;
 5201 
 5202         $frompath = $this->_path;
 5203         $newpath  = $newparent->path . '/' . $this->_id;
 5204 
 5205         $trans = $DB->start_delegated_transaction();
 5206 
 5207         $setdepth = '';
 5208         if (($newparent->depth +1) != $this->_depth) {
 5209             $diff = $newparent->depth - $this->_depth + 1;
 5210             $setdepth = ", depth = depth + $diff";
 5211         }
 5212         $sql = "UPDATE {context}
 5213                    SET path = ?
 5214                        $setdepth
 5215                  WHERE id = ?";
 5216         $params = array($newpath, $this->_id);
 5217         $DB->execute($sql, $params);
 5218 
 5219         $this->_path  = $newpath;
 5220         $this->_depth = $newparent->depth + 1;
 5221 
 5222         $sql = "UPDATE {context}
 5223                    SET path = ".$DB->sql_concat("?", $DB->sql_substr("path", strlen($frompath)+1))."
 5224                        $setdepth
 5225                  WHERE path LIKE ?";
 5226         $params = array($newpath, "{$frompath}/%");
 5227         $DB->execute($sql, $params);
 5228 
 5229         $this->mark_dirty();
 5230 
 5231         context::reset_caches();
 5232 
 5233         $trans->allow_commit();
 5234     }
 5235 
 5236     /**
 5237      * Set whether this context has been locked or not.
 5238      *
 5239      * @param   bool    $locked
 5240      * @return  $this
 5241      */
 5242     public function set_locked(bool $locked) {
 5243         global $DB;
 5244 
 5245         if ($this->_locked == $locked) {
 5246             return $this;
 5247         }
 5248 
 5249         $this->_locked = $locked;
 5250         $DB->set_field('context', 'locked', (int) $locked, ['id' => $this->id]);
 5251         $this->mark_dirty();
 5252 
 5253         if ($locked) {
 5254             $eventname = '\\core\\event\\context_locked';
 5255         } else {
 5256             $eventname = '\\core\\event\\context_unlocked';
 5257         }
 5258         $event = $eventname::create(['context' => $this, 'objectid' => $this->id]);
 5259         $event->trigger();
 5260 
 5261         self::reset_caches();
 5262 
 5263         return $this;
 5264     }
 5265 
 5266     /**
 5267      * Remove all context path info and optionally rebuild it.
 5268      *
 5269      * @param bool $rebuild
 5270      * @return void
 5271      */
 5272     public function reset_paths($rebuild = true) {
 5273         global $DB;
 5274 
 5275         if ($this->_path) {
 5276             $this->mark_dirty();
 5277         }
 5278         $DB->set_field_select('context', 'depth', 0, "path LIKE '%/$this->_id/%'");
 5279         $DB->set_field_select('context', 'path', NULL, "path LIKE '%/$this->_id/%'");
 5280         if ($this->_contextlevel != CONTEXT_SYSTEM) {
 5281             $DB->set_field('context', 'depth', 0, array('id'=>$this->_id));
 5282             $DB->set_field('context', 'path', NULL, array('id'=>$this->_id));
 5283             $this->_depth = 0;
 5284             $this->_path = null;
 5285         }
 5286 
 5287         if ($rebuild) {
 5288             context_helper::build_all_paths(false);
 5289         }
 5290 
 5291         context::reset_caches();
 5292     }
 5293 
 5294     /**
 5295      * Delete all data linked to content, do not delete the context record itself
 5296      */
 5297     public function delete_content() {
 5298         global $CFG, $DB;
 5299 
 5300         blocks_delete_all_for_context($this->_id);
 5301         filter_delete_all_for_context($this->_id);
 5302 
 5303         require_once($CFG->dirroot . '/comment/lib.php');
 5304         comment::delete_comments(array('contextid'=>$this->_id));
 5305 
 5306         require_once($CFG->dirroot.'/rating/lib.php');
 5307         $delopt = new stdclass();
 5308         $delopt->contextid = $this->_id;
 5309         $rm = new rating_manager();
 5310         $rm->delete_ratings($delopt);
 5311 
 5312         // delete all files attached to this context
 5313         $fs = get_file_storage();
 5314         $fs->delete_area_files($this->_id);
 5315 
 5316         // Delete all repository instances attached to this context.
 5317         require_once($CFG->dirroot . '/repository/lib.php');
 5318         repository::delete_all_for_context($this->_id);
 5319 
 5320         // delete all advanced grading data attached to this context
 5321         require_once($CFG->dirroot.'/grade/grading/lib.php');
 5322         grading_manager::delete_all_for_context($this->_id);
 5323 
 5324         // now delete stuff from role related tables, role_unassign_all
 5325         // and unenrol should be called earlier to do proper cleanup
 5326         $DB->delete_records('role_assignments', array('contextid'=>$this->_id));
 5327         $DB->delete_records('role_names', array('contextid'=>$this->_id));
 5328         $this->delete_capabilities();
 5329     }
 5330 
 5331     /**
 5332      * Unassign all capabilities from a context.
 5333      */
 5334     public function delete_capabilities() {
 5335         global $DB;
 5336 
 5337         $ids = $DB->get_fieldset_select('role_capabilities', 'DISTINCT roleid', 'contextid = ?', array($this->_id));
 5338         if ($ids) {
 5339             $DB->delete_records('role_capabilities', array('contextid' => $this->_id));
 5340 
 5341             // Reset any cache of these roles, including MUC.
 5342             accesslib_clear_role_cache($ids);
 5343         }
 5344     }
 5345 
 5346     /**
 5347      * Delete the context content and the context record itself
 5348      */
 5349     public function delete() {
 5350         global $DB;
 5351 
 5352         if ($this->_contextlevel <= CONTEXT_SYSTEM) {
 5353             throw new coding_exception('Cannot delete system context');
 5354         }
 5355 
 5356         // double check the context still exists
 5357         if (!$DB->record_exists('context', array('id'=>$this->_id))) {
 5358             context::cache_remove($this);
 5359             return;
 5360         }
 5361 
 5362         $this->delete_content();
 5363         $DB->delete_records('context', array('id'=>$this->_id));
 5364         // purge static context cache if entry present
 5365         context::cache_remove($this);
 5366     }
 5367 
 5368     // ====== context level related methods ======
 5369 
 5370     /**
 5371      * Utility method for context creation
 5372      *
 5373      * @static
 5374      * @param int $contextlevel
 5375      * @param int $instanceid
 5376      * @param string $parentpath
 5377      * @return stdClass context record
 5378      */
 5379     protected static function insert_context_record($contextlevel, $instanceid, $parentpath) {
 5380         global $DB;
 5381 
 5382         $record = new stdClass();
 5383         $record->contextlevel = $contextlevel;
 5384         $record->instanceid   = $instanceid;
 5385         $record->depth        = 0;
 5386         $record->path         = null; //not known before insert
 5387         $record->locked       = 0;
 5388 
 5389         $record->id = $DB->insert_record('context', $record);
 5390 
 5391         // now add path if known - it can be added later
 5392         if (!is_null($parentpath)) {
 5393             $record->path = $parentpath.'/'.$record->id;
 5394             $record->depth = substr_count($record->path, '/');
 5395             $DB->update_record('context', $record);
 5396         }
 5397 
 5398         return $record;
 5399     }
 5400 
 5401     /**
 5402      * Returns human readable context identifier.
 5403      *
 5404      * @param boolean $withprefix whether to prefix the name of the context with the
 5405      *      type of context, e.g. User, Course, Forum, etc.
 5406      * @param boolean $short whether to use the short name of the thing. Only applies
 5407      *      to course contexts
 5408      * @return string the human readable context name.
 5409      */
 5410     public function get_context_name($withprefix = true, $short = false) {
 5411         // must be implemented in all context levels
 5412         throw new coding_exception('can not get name of abstract context');
 5413     }
 5414 
 5415     /**
 5416      * Whether the current context is locked.
 5417      *
 5418      * @return  bool
 5419      */
 5420     public function is_locked() {
 5421         if ($this->_locked) {
 5422             return true;
 5423         }
 5424 
 5425         if ($parent = $this->get_parent_context()) {
 5426             return $parent->is_locked();
 5427         }
 5428 
 5429         return false;
 5430     }
 5431 
 5432     /**
 5433      * Returns the most relevant URL for this context.
 5434      *
 5435      * @return moodle_url
 5436      */
 5437     public abstract function get_url();
 5438 
 5439     /**
 5440      * Returns array of relevant context capability records.
 5441      *
 5442      * @return array
 5443      */
 5444     public abstract function get_capabilities();
 5445 
 5446     /**
 5447      * Recursive function which, given a context, find all its children context ids.
 5448      *
 5449      * For course category contexts it will return immediate children and all subcategory contexts.
 5450      * It will NOT recurse into courses or subcategories categories.
 5451      * If you want to do that, call it on the r