Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.
/lib/ -> accesslib.php (source)

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403] [Versions 402 and 403]

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