Search moodle.org's
Developer Documentation

See Release Notes

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

Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402] [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      $event = \core\event\role_assigned::create(array(
1649          'context' => $context,
1650          'objectid' => $ra->roleid,
1651          'relateduserid' => $ra->userid,
1652          'other' => array(
1653              'id' => $ra->id,
1654              'component' => $ra->component,
1655              'itemid' => $ra->itemid
1656          )
1657      ));
1658      $event->add_record_snapshot('role_assignments', $ra);
1659      $event->trigger();
1660  
1661      return $ra->id;
1662  }
1663  
1664  /**
1665   * Removes one role assignment
1666   *
1667   * @param int $roleid
1668   * @param int  $userid
1669   * @param int  $contextid
1670   * @param string $component
1671   * @param int  $itemid
1672   * @return void
1673   */
1674  function role_unassign($roleid, $userid, $contextid, $component = '', $itemid = 0) {
1675      // first make sure the params make sense
1676      if ($roleid == 0 or $userid == 0 or $contextid == 0) {
1677          throw new coding_exception('Invalid call to role_unassign(), please use role_unassign_all() when removing multiple role assignments');
1678      }
1679  
1680      if ($itemid) {
1681          if (strpos($component, '_') === false) {
1682              throw new coding_exception('Invalid call to role_assign(), component must start with plugin type such as "enrol_" when itemid specified', 'component:'.$component);
1683          }
1684      } else {
1685          $itemid = 0;
1686          if ($component !== '' and strpos($component, '_') === false) {
1687              throw new coding_exception('Invalid call to role_assign(), invalid component string', 'component:'.$component);
1688          }
1689      }
1690  
1691      role_unassign_all(array('roleid'=>$roleid, 'userid'=>$userid, 'contextid'=>$contextid, 'component'=>$component, 'itemid'=>$itemid), false, false);
1692  }
1693  
1694  /**
1695   * Removes multiple role assignments, parameters may contain:
1696   *   'roleid', 'userid', 'contextid', 'component', 'enrolid'.
1697   *
1698   * @param array $params role assignment parameters
1699   * @param bool $subcontexts unassign in subcontexts too
1700   * @param bool $includemanual include manual role assignments too
1701   * @return void
1702   */
1703  function role_unassign_all(array $params, $subcontexts = false, $includemanual = false) {
1704      global $USER, $CFG, $DB;
1705  
1706      if (!$params) {
1707          throw new coding_exception('Missing parameters in role_unsassign_all() call');
1708      }
1709  
1710      $allowed = array('roleid', 'userid', 'contextid', 'component', 'itemid');
1711      foreach ($params as $key=>$value) {
1712          if (!in_array($key, $allowed)) {
1713              throw new coding_exception('Unknown role_unsassign_all() parameter key', 'key:'.$key);
1714          }
1715      }
1716  
1717      if (isset($params['component']) and $params['component'] !== '' and strpos($params['component'], '_') === false) {
1718          throw new coding_exception('Invalid component paramter in role_unsassign_all() call', 'component:'.$params['component']);
1719      }
1720  
1721      if ($includemanual) {
1722          if (!isset($params['component']) or $params['component'] === '') {
1723              throw new coding_exception('include manual parameter requires component parameter in role_unsassign_all() call');
1724          }
1725      }
1726  
1727      if ($subcontexts) {
1728          if (empty($params['contextid'])) {
1729              throw new coding_exception('subcontexts paramtere requires component parameter in role_unsassign_all() call');
1730          }
1731      }
1732  
1733      $ras = $DB->get_records('role_assignments', $params);
1734      foreach ($ras as $ra) {
1735          $DB->delete_records('role_assignments', array('id'=>$ra->id));
1736          if ($context = context::instance_by_id($ra->contextid, IGNORE_MISSING)) {
1737              // Role assignments have changed, so mark user as dirty.
1738              mark_user_dirty($ra->userid);
1739  
1740              $event = \core\event\role_unassigned::create(array(
1741                  'context' => $context,
1742                  'objectid' => $ra->roleid,
1743                  'relateduserid' => $ra->userid,
1744                  'other' => array(
1745                      'id' => $ra->id,
1746                      'component' => $ra->component,
1747                      'itemid' => $ra->itemid
1748                  )
1749              ));
1750              $event->add_record_snapshot('role_assignments', $ra);
1751              $event->trigger();
1752              core_course_category::role_assignment_changed($ra->roleid, $context);
1753          }
1754      }
1755      unset($ras);
1756  
1757      // process subcontexts
1758      if ($subcontexts and $context = context::instance_by_id($params['contextid'], IGNORE_MISSING)) {
1759          if ($params['contextid'] instanceof context) {
1760              $context = $params['contextid'];
1761          } else {
1762              $context = context::instance_by_id($params['contextid'], IGNORE_MISSING);
1763          }
1764  
1765          if ($context) {
1766              $contexts = $context->get_child_contexts();
1767              $mparams = $params;
1768              foreach ($contexts as $context) {
1769                  $mparams['contextid'] = $context->id;
1770                  $ras = $DB->get_records('role_assignments', $mparams);
1771                  foreach ($ras as $ra) {
1772                      $DB->delete_records('role_assignments', array('id'=>$ra->id));
1773                      // Role assignments have changed, so mark user as dirty.
1774                      mark_user_dirty($ra->userid);
1775  
1776                      $event = \core\event\role_unassigned::create(
1777                          array('context'=>$context, 'objectid'=>$ra->roleid, 'relateduserid'=>$ra->userid,
1778                              'other'=>array('id'=>$ra->id, 'component'=>$ra->component, 'itemid'=>$ra->itemid)));
1779                      $event->add_record_snapshot('role_assignments', $ra);
1780                      $event->trigger();
1781                      core_course_category::role_assignment_changed($ra->roleid, $context);
1782                  }
1783              }
1784          }
1785      }
1786  
1787      // do this once more for all manual role assignments
1788      if ($includemanual) {
1789          $params['component'] = '';
1790          role_unassign_all($params, $subcontexts, false);
1791      }
1792  }
1793  
1794  /**
1795   * Mark a user as dirty (with timestamp) so as to force reloading of the user session.
1796   *
1797   * @param int $userid
1798   * @return void
1799   */
1800  function mark_user_dirty($userid) {
1801      global $CFG, $ACCESSLIB_PRIVATE;
1802  
1803      if (during_initial_install()) {
1804          return;
1805      }
1806  
1807      // Throw exception if invalid userid is provided.
1808      if (empty($userid)) {
1809          throw new coding_exception('Invalid user parameter supplied for mark_user_dirty() function!');
1810      }
1811  
1812      // Set dirty flag in database, set dirty field locally, and clear local accessdata cache.
1813      set_cache_flag('accesslib/dirtyusers', $userid, 1, time() + $CFG->sessiontimeout);
1814      $ACCESSLIB_PRIVATE->dirtyusers[$userid] = 1;
1815      unset($ACCESSLIB_PRIVATE->accessdatabyuser[$userid]);
1816  }
1817  
1818  /**
1819   * Determines if a user is currently logged in
1820   *
1821   * @category   access
1822   *
1823   * @return bool
1824   */
1825  function isloggedin() {
1826      global $USER;
1827  
1828      return (!empty($USER->id));
1829  }
1830  
1831  /**
1832   * Determines if a user is logged in as real guest user with username 'guest'.
1833   *
1834   * @category   access
1835   *
1836   * @param int|object $user mixed user object or id, $USER if not specified
1837   * @return bool true if user is the real guest user, false if not logged in or other user
1838   */
1839  function isguestuser($user = null) {
1840      global $USER, $DB, $CFG;
1841  
1842      // make sure we have the user id cached in config table, because we are going to use it a lot
1843      if (empty($CFG->siteguest)) {
1844          if (!$guestid = $DB->get_field('user', 'id', array('username'=>'guest', 'mnethostid'=>$CFG->mnet_localhost_id))) {
1845              // guest does not exist yet, weird
1846              return false;
1847          }
1848          set_config('siteguest', $guestid);
1849      }
1850      if ($user === null) {
1851          $user = $USER;
1852      }
1853  
1854      if ($user === null) {
1855          // happens when setting the $USER
1856          return false;
1857  
1858      } else if (is_numeric($user)) {
1859          return ($CFG->siteguest == $user);
1860  
1861      } else if (is_object($user)) {
1862          if (empty($user->id)) {
1863              return false; // not logged in means is not be guest
1864          } else {
1865              return ($CFG->siteguest == $user->id);
1866          }
1867  
1868      } else {
1869          throw new coding_exception('Invalid user parameter supplied for isguestuser() function!');
1870      }
1871  }
1872  
1873  /**
1874   * Does user have a (temporary or real) guest access to course?
1875   *
1876   * @category   access
1877   *
1878   * @param context $context
1879   * @param stdClass|int $user
1880   * @return bool
1881   */
1882  function is_guest(context $context, $user = null) {
1883      global $USER;
1884  
1885      // first find the course context
1886      $coursecontext = $context->get_course_context();
1887  
1888      // make sure there is a real user specified
1889      if ($user === null) {
1890          $userid = isset($USER->id) ? $USER->id : 0;
1891      } else {
1892          $userid = is_object($user) ? $user->id : $user;
1893      }
1894  
1895      if (isguestuser($userid)) {
1896          // can not inspect or be enrolled
1897          return true;
1898      }
1899  
1900      if (has_capability('moodle/course:view', $coursecontext, $user)) {
1901          // viewing users appear out of nowhere, they are neither guests nor participants
1902          return false;
1903      }
1904  
1905      // consider only real active enrolments here
1906      if (is_enrolled($coursecontext, $user, '', true)) {
1907          return false;
1908      }
1909  
1910      return true;
1911  }
1912  
1913  /**
1914   * Returns true if the user has moodle/course:view capability in the course,
1915   * this is intended for admins, managers (aka small admins), inspectors, etc.
1916   *
1917   * @category   access
1918   *
1919   * @param context $context
1920   * @param int|stdClass $user if null $USER is used
1921   * @param string $withcapability extra capability name
1922   * @return bool
1923   */
1924  function is_viewing(context $context, $user = null, $withcapability = '') {
1925      // first find the course context
1926      $coursecontext = $context->get_course_context();
1927  
1928      if (isguestuser($user)) {
1929          // can not inspect
1930          return false;
1931      }
1932  
1933      if (!has_capability('moodle/course:view', $coursecontext, $user)) {
1934          // admins are allowed to inspect courses
1935          return false;
1936      }
1937  
1938      if ($withcapability and !has_capability($withcapability, $context, $user)) {
1939          // site admins always have the capability, but the enrolment above blocks
1940          return false;
1941      }
1942  
1943      return true;
1944  }
1945  
1946  /**
1947   * Returns true if the user is able to access the course.
1948   *
1949   * This function is in no way, shape, or form a substitute for require_login.
1950   * It should only be used in circumstances where it is not possible to call require_login
1951   * such as the navigation.
1952   *
1953   * This function checks many of the methods of access to a course such as the view
1954   * capability, enrollments, and guest access. It also makes use of the cache
1955   * generated by require_login for guest access.
1956   *
1957   * The flags within the $USER object that are used here should NEVER be used outside
1958   * of this function can_access_course and require_login. Doing so WILL break future
1959   * versions.
1960   *
1961   * @param stdClass $course record
1962   * @param stdClass|int|null $user user record or id, current user if null
1963   * @param string $withcapability Check for this capability as well.
1964   * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
1965   * @return boolean Returns true if the user is able to access the course
1966   */
1967  function can_access_course(stdClass $course, $user = null, $withcapability = '', $onlyactive = false) {
1968      global $DB, $USER;
1969  
1970      // this function originally accepted $coursecontext parameter
1971      if ($course instanceof context) {
1972          if ($course instanceof context_course) {
1973              debugging('deprecated context parameter, please use $course record');
1974              $coursecontext = $course;
1975              $course = $DB->get_record('course', array('id'=>$coursecontext->instanceid));
1976          } else {
1977              debugging('Invalid context parameter, please use $course record');
1978              return false;
1979          }
1980      } else {
1981          $coursecontext = context_course::instance($course->id);
1982      }
1983  
1984      if (!isset($USER->id)) {
1985          // should never happen
1986          $USER->id = 0;
1987          debugging('Course access check being performed on a user with no ID.', DEBUG_DEVELOPER);
1988      }
1989  
1990      // make sure there is a user specified
1991      if ($user === null) {
1992          $userid = $USER->id;
1993      } else {
1994          $userid = is_object($user) ? $user->id : $user;
1995      }
1996      unset($user);
1997  
1998      if ($withcapability and !has_capability($withcapability, $coursecontext, $userid)) {
1999          return false;
2000      }
2001  
2002      if ($userid == $USER->id) {
2003          if (!empty($USER->access['rsw'][$coursecontext->path])) {
2004              // the fact that somebody switched role means they can access the course no matter to what role they switched
2005              return true;
2006          }
2007      }
2008  
2009      if (!$course->visible and !has_capability('moodle/course:viewhiddencourses', $coursecontext, $userid)) {
2010          return false;
2011      }
2012  
2013      if (is_viewing($coursecontext, $userid)) {
2014          return true;
2015      }
2016  
2017      if ($userid != $USER->id) {
2018          // for performance reasons we do not verify temporary guest access for other users, sorry...
2019          return is_enrolled($coursecontext, $userid, '', $onlyactive);
2020      }
2021  
2022      // === from here we deal only with $USER ===
2023  
2024      $coursecontext->reload_if_dirty();
2025  
2026      if (isset($USER->enrol['enrolled'][$course->id])) {
2027          if ($USER->enrol['enrolled'][$course->id] > time()) {
2028              return true;
2029          }
2030      }
2031      if (isset($USER->enrol['tempguest'][$course->id])) {
2032          if ($USER->enrol['tempguest'][$course->id] > time()) {
2033              return true;
2034          }
2035      }
2036  
2037      if (is_enrolled($coursecontext, $USER, '', $onlyactive)) {
2038          return true;
2039      }
2040  
2041      if (!core_course_category::can_view_course_info($course)) {
2042          // No guest access if user does not have capability to browse courses.
2043          return false;
2044      }
2045  
2046      // if not enrolled try to gain temporary guest access
2047      $instances = $DB->get_records('enrol', array('courseid'=>$course->id, 'status'=>ENROL_INSTANCE_ENABLED), 'sortorder, id ASC');
2048      $enrols = enrol_get_plugins(true);
2049      foreach ($instances as $instance) {
2050          if (!isset($enrols[$instance->enrol])) {
2051              continue;
2052          }
2053          // Get a duration for the guest access, a timestamp in the future, 0 (always) or false.
2054          $until = $enrols[$instance->enrol]->try_guestaccess($instance);
2055          if ($until !== false and $until > time()) {
2056              $USER->enrol['tempguest'][$course->id] = $until;
2057              return true;
2058          }
2059      }
2060      if (isset($USER->enrol['tempguest'][$course->id])) {
2061          unset($USER->enrol['tempguest'][$course->id]);
2062          remove_temp_course_roles($coursecontext);
2063      }
2064  
2065      return false;
2066  }
2067  
2068  /**
2069   * Loads the capability definitions for the component (from file).
2070   *
2071   * Loads the capability definitions for the component (from file). If no
2072   * capabilities are defined for the component, we simply return an empty array.
2073   *
2074   * @access private
2075   * @param string $component full plugin name, examples: 'moodle', 'mod_forum'
2076   * @return array array of capabilities
2077   */
2078  function load_capability_def($component) {
2079      $defpath = core_component::get_component_directory($component).'/db/access.php';
2080  
2081      $capabilities = array();
2082      if (file_exists($defpath)) {
2083          require($defpath);
2084          if (!empty(${$component.'_capabilities'})) {
2085              // BC capability array name
2086              // since 2.0 we prefer $capabilities instead - it is easier to use and matches db/* files
2087              debugging('componentname_capabilities array is deprecated, please use $capabilities array only in access.php files');
2088              $capabilities = ${$component.'_capabilities'};
2089          }
2090      }
2091  
2092      return $capabilities;
2093  }
2094  
2095  /**
2096   * Gets the capabilities that have been cached in the database for this component.
2097   *
2098   * @access private
2099   * @param string $component - examples: 'moodle', 'mod_forum'
2100   * @return array array of capabilities
2101   */
2102  function get_cached_capabilities($component = 'moodle') {
2103      global $DB;
2104      $caps = get_all_capabilities();
2105      $componentcaps = array();
2106      foreach ($caps as $cap) {
2107          if ($cap['component'] == $component) {
2108              $componentcaps[] = (object) $cap;
2109          }
2110      }
2111      return $componentcaps;
2112  }
2113  
2114  /**
2115   * Returns default capabilities for given role archetype.
2116   *
2117   * @param string $archetype role archetype
2118   * @return array
2119   */
2120  function get_default_capabilities($archetype) {
2121      global $DB;
2122  
2123      if (!$archetype) {
2124          return array();
2125      }
2126  
2127      $alldefs = array();
2128      $defaults = array();
2129      $components = array();
2130      $allcaps = get_all_capabilities();
2131  
2132      foreach ($allcaps as $cap) {
2133          if (!in_array($cap['component'], $components)) {
2134              $components[] = $cap['component'];
2135              $alldefs = array_merge($alldefs, load_capability_def($cap['component']));
2136          }
2137      }
2138      foreach ($alldefs as $name=>$def) {
2139          // Use array 'archetypes if available. Only if not specified, use 'legacy'.
2140          if (isset($def['archetypes'])) {
2141              if (isset($def['archetypes'][$archetype])) {
2142                  $defaults[$name] = $def['archetypes'][$archetype];
2143              }
2144          // 'legacy' is for backward compatibility with 1.9 access.php
2145          } else {
2146              if (isset($def['legacy'][$archetype])) {
2147                  $defaults[$name] = $def['legacy'][$archetype];
2148              }
2149          }
2150      }
2151  
2152      return $defaults;
2153  }
2154  
2155  /**
2156   * Return default roles that can be assigned, overridden or switched
2157   * by give role archetype.
2158   *
2159   * @param string $type  assign|override|switch|view
2160   * @param string $archetype
2161   * @return array of role ids
2162   */
2163  function get_default_role_archetype_allows($type, $archetype) {
2164      global $DB;
2165  
2166      if (empty($archetype)) {
2167          return array();
2168      }
2169  
2170      $roles = $DB->get_records('role');
2171      $archetypemap = array();
2172      foreach ($roles as $role) {
2173          if ($role->archetype) {
2174              $archetypemap[$role->archetype][$role->id] = $role->id;
2175          }
2176      }
2177  
2178      $defaults = array(
2179          'assign' => array(
2180              'manager'        => array('manager', 'coursecreator', 'editingteacher', 'teacher', 'student'),
2181              'coursecreator'  => array(),
2182              'editingteacher' => array('teacher', 'student'),
2183              'teacher'        => array(),
2184              'student'        => array(),
2185              'guest'          => array(),
2186              'user'           => array(),
2187              'frontpage'      => array(),
2188          ),
2189          'override' => array(
2190              'manager'        => array('manager', 'coursecreator', 'editingteacher', 'teacher', 'student', 'guest', 'user', 'frontpage'),
2191              'coursecreator'  => array(),
2192              'editingteacher' => array('teacher', 'student', 'guest'),
2193              'teacher'        => array(),
2194              'student'        => array(),
2195              'guest'          => array(),
2196              'user'           => array(),
2197              'frontpage'      => array(),
2198          ),
2199          'switch' => array(
2200              'manager'        => array('editingteacher', 'teacher', 'student', 'guest'),
2201              'coursecreator'  => array(),
2202              'editingteacher' => array('teacher', 'student', 'guest'),
2203              'teacher'        => array('student', 'guest'),
2204              'student'        => array(),
2205              'guest'          => array(),
2206              'user'           => array(),
2207              'frontpage'      => array(),
2208          ),
2209          'view' => array(
2210              'manager'        => array('manager', 'coursecreator', 'editingteacher', 'teacher', 'student', 'guest', 'user', 'frontpage'),
2211              'coursecreator'  => array('coursecreator', 'editingteacher', 'teacher', 'student'),
2212              'editingteacher' => array('coursecreator', 'editingteacher', 'teacher', 'student'),
2213              'teacher'        => array('coursecreator', 'editingteacher', 'teacher', 'student'),
2214              'student'        => array('coursecreator', 'editingteacher', 'teacher', 'student'),
2215              'guest'          => array(),
2216              'user'           => array(),
2217              'frontpage'      => array(),
2218          ),
2219      );
2220  
2221      if (!isset($defaults[$type][$archetype])) {
2222          debugging("Unknown type '$type'' or archetype '$archetype''");
2223          return array();
2224      }
2225  
2226      $return = array();
2227      foreach ($defaults[$type][$archetype] as $at) {
2228          if (isset($archetypemap[$at])) {
2229              foreach ($archetypemap[$at] as $roleid) {
2230                  $return[$roleid] = $roleid;
2231              }
2232          }
2233      }
2234  
2235      return $return;
2236  }
2237  
2238  /**
2239   * Reset role capabilities to default according to selected role archetype.
2240   * If no archetype selected, removes all capabilities.
2241   *
2242   * This applies to capabilities that are assigned to the role (that you could
2243   * edit in the 'define roles' interface), and not to any capability overrides
2244   * in different locations.
2245   *
2246   * @param int $roleid ID of role to reset capabilities for
2247   */
2248  function reset_role_capabilities($roleid) {
2249      global $DB;
2250  
2251      $role = $DB->get_record('role', array('id'=>$roleid), '*', MUST_EXIST);
2252      $defaultcaps = get_default_capabilities($role->archetype);
2253  
2254      $systemcontext = context_system::instance();
2255  
2256      $DB->delete_records('role_capabilities',
2257              array('roleid' => $roleid, 'contextid' => $systemcontext->id));
2258  
2259      foreach ($defaultcaps as $cap=>$permission) {
2260          assign_capability($cap, $permission, $roleid, $systemcontext->id);
2261      }
2262  
2263      // Reset any cache of this role, including MUC.
2264      accesslib_clear_role_cache($roleid);
2265  }
2266  
2267  /**
2268   * Updates the capabilities table with the component capability definitions.
2269   * If no parameters are given, the function updates the core moodle
2270   * capabilities.
2271   *
2272   * Note that the absence of the db/access.php capabilities definition file
2273   * will cause any stored capabilities for the component to be removed from
2274   * the database.
2275   *
2276   * @access private
2277   * @param string $component examples: 'moodle', 'mod_forum', 'block_activity_results'
2278   * @return boolean true if success, exception in case of any problems
2279   */
2280  function update_capabilities($component = 'moodle') {
2281      global $DB, $OUTPUT;
2282  
2283      // Allow temporary caches to be used during install, dramatically boosting performance.
2284      $token = new \core_cache\allow_temporary_caches();
2285  
2286      $storedcaps = array();
2287  
2288      $filecaps = load_capability_def($component);
2289      foreach ($filecaps as $capname=>$unused) {
2290          if (!preg_match('|^[a-z]+/[a-z_0-9]+:[a-z_0-9]+$|', $capname)) {
2291              debugging("Coding problem: Invalid capability name '$capname', use 'clonepermissionsfrom' field for migration.");
2292          }
2293      }
2294  
2295      // It is possible somebody directly modified the DB (according to accesslib_test anyway).
2296      // So ensure our updating is based on fresh data.
2297      cache::make('core', 'capabilities')->delete('core_capabilities');
2298  
2299      $cachedcaps = get_cached_capabilities($component);
2300      if ($cachedcaps) {
2301          foreach ($cachedcaps as $cachedcap) {
2302              array_push($storedcaps, $cachedcap->name);
2303              // update risk bitmasks and context levels in existing capabilities if needed
2304              if (array_key_exists($cachedcap->name, $filecaps)) {
2305                  if (!array_key_exists('riskbitmask', $filecaps[$cachedcap->name])) {
2306                      $filecaps[$cachedcap->name]['riskbitmask'] = 0; // no risk if not specified
2307                  }
2308                  if ($cachedcap->captype != $filecaps[$cachedcap->name]['captype']) {
2309                      $updatecap = new stdClass();
2310                      $updatecap->id = $cachedcap->id;
2311                      $updatecap->captype = $filecaps[$cachedcap->name]['captype'];
2312                      $DB->update_record('capabilities', $updatecap);
2313                  }
2314                  if ($cachedcap->riskbitmask != $filecaps[$cachedcap->name]['riskbitmask']) {
2315                      $updatecap = new stdClass();
2316                      $updatecap->id = $cachedcap->id;
2317                      $updatecap->riskbitmask = $filecaps[$cachedcap->name]['riskbitmask'];
2318                      $DB->update_record('capabilities', $updatecap);
2319                  }
2320  
2321                  if (!array_key_exists('contextlevel', $filecaps[$cachedcap->name])) {
2322                      $filecaps[$cachedcap->name]['contextlevel'] = 0; // no context level defined
2323                  }
2324                  if ($cachedcap->contextlevel != $filecaps[$cachedcap->name]['contextlevel']) {
2325                      $updatecap = new stdClass();
2326                      $updatecap->id = $cachedcap->id;
2327                      $updatecap->contextlevel = $filecaps[$cachedcap->name]['contextlevel'];
2328                      $DB->update_record('capabilities', $updatecap);
2329                  }
2330              }
2331          }
2332      }
2333  
2334      // Flush the cached again, as we have changed DB.
2335      cache::make('core', 'capabilities')->delete('core_capabilities');
2336  
2337      // Are there new capabilities in the file definition?
2338      $newcaps = array();
2339  
2340      foreach ($filecaps as $filecap => $def) {
2341          if (!$storedcaps ||
2342                  ($storedcaps && in_array($filecap, $storedcaps) === false)) {
2343              if (!array_key_exists('riskbitmask', $def)) {
2344                  $def['riskbitmask'] = 0; // no risk if not specified
2345              }
2346              $newcaps[$filecap] = $def;
2347          }
2348      }
2349      // Add new capabilities to the stored definition.
2350      $existingcaps = $DB->get_records_menu('capabilities', array(), 'id', 'id, name');
2351      $capabilityobjects = [];
2352      foreach ($newcaps as $capname => $capdef) {
2353          $capability = new stdClass();
2354          $capability->name         = $capname;
2355          $capability->captype      = $capdef['captype'];
2356          $capability->contextlevel = $capdef['contextlevel'];
2357          $capability->component    = $component;
2358          $capability->riskbitmask  = $capdef['riskbitmask'];
2359          $capabilityobjects[] = $capability;
2360      }
2361      $DB->insert_records('capabilities', $capabilityobjects);
2362  
2363      // Flush the cache, as we have changed DB.
2364      cache::make('core', 'capabilities')->delete('core_capabilities');
2365  
2366      foreach ($newcaps as $capname => $capdef) {
2367          if (isset($capdef['clonepermissionsfrom']) && in_array($capdef['clonepermissionsfrom'], $existingcaps)){
2368              if ($rolecapabilities = $DB->get_records_sql('
2369                      SELECT rc.*,
2370                             CASE WHEN EXISTS(SELECT 1
2371                                      FROM {role_capabilities} rc2
2372                                     WHERE rc2.capability = ?
2373                                           AND rc2.contextid = rc.contextid
2374                                           AND rc2.roleid = rc.roleid) THEN 1 ELSE 0 END AS entryexists,
2375                              ' . context_helper::get_preload_record_columns_sql('x') .'
2376                        FROM {role_capabilities} rc
2377                        JOIN {context} x ON x.id = rc.contextid
2378                       WHERE rc.capability = ?',
2379                      [$capname, $capdef['clonepermissionsfrom']])) {
2380                  foreach ($rolecapabilities as $rolecapability) {
2381                      // Preload the context and add performance hints based on the SQL query above.
2382                      context_helper::preload_from_record($rolecapability);
2383                      $performancehints = [ACCESSLIB_HINT_CONTEXT_EXISTS];
2384                      if (!$rolecapability->entryexists) {
2385                          $performancehints[] = ACCESSLIB_HINT_NO_EXISTING;
2386                      }
2387                      //assign_capability will update rather than insert if capability exists
2388                      if (!assign_capability($capname, $rolecapability->permission,
2389                              $rolecapability->roleid, $rolecapability->contextid, true, $performancehints)) {
2390                           echo $OUTPUT->notification('Could not clone capabilities for '.$capname);
2391                      }
2392                  }
2393              }
2394          // we ignore archetype key if we have cloned permissions
2395          } else if (isset($capdef['archetypes']) && is_array($capdef['archetypes'])) {
2396              assign_legacy_capabilities($capname, $capdef['archetypes']);
2397          // 'legacy' is for backward compatibility with 1.9 access.php
2398          } else if (isset($capdef['legacy']) && is_array($capdef['legacy'])) {
2399              assign_legacy_capabilities($capname, $capdef['legacy']);
2400          }
2401      }
2402      // Are there any capabilities that have been removed from the file
2403      // definition that we need to delete from the stored capabilities and
2404      // role assignments?
2405      capabilities_cleanup($component, $filecaps);
2406  
2407      // reset static caches
2408      accesslib_reset_role_cache();
2409  
2410      // Flush the cached again, as we have changed DB.
2411      cache::make('core', 'capabilities')->delete('core_capabilities');
2412  
2413      return true;
2414  }
2415  
2416  /**
2417   * Deletes cached capabilities that are no longer needed by the component.
2418   * Also unassigns these capabilities from any roles that have them.
2419   * NOTE: this function is called from lib/db/upgrade.php
2420   *
2421   * @access private
2422   * @param string $component examples: 'moodle', 'mod_forum', 'block_activity_results'
2423   * @param array $newcapdef array of the new capability definitions that will be
2424   *                     compared with the cached capabilities
2425   * @return int number of deprecated capabilities that have been removed
2426   */
2427  function capabilities_cleanup($component, $newcapdef = null) {
2428      global $DB;
2429  
2430      $removedcount = 0;
2431  
2432      if ($cachedcaps = get_cached_capabilities($component)) {
2433          foreach ($cachedcaps as $cachedcap) {
2434              if (empty($newcapdef) ||
2435                          array_key_exists($cachedcap->name, $newcapdef) === false) {
2436  
2437                  // Delete from roles.
2438                  if ($roles = get_roles_with_capability($cachedcap->name)) {
2439                      foreach ($roles as $role) {
2440                          if (!unassign_capability($cachedcap->name, $role->id)) {
2441                              throw new \moodle_exception('cannotunassigncap', 'error', '',
2442                                  (object)array('cap' => $cachedcap->name, 'role' => $role->name));
2443                          }
2444                      }
2445                  }
2446  
2447                  // Remove from role_capabilities for any old ones.
2448                  $DB->delete_records('role_capabilities', array('capability' => $cachedcap->name));
2449  
2450                  // Remove from capabilities cache.
2451                  $DB->delete_records('capabilities', array('name' => $cachedcap->name));
2452                  $removedcount++;
2453              } // End if.
2454          }
2455      }
2456      if ($removedcount) {
2457          cache::make('core', 'capabilities')->delete('core_capabilities');
2458      }
2459      return $removedcount;
2460  }
2461  
2462  /**
2463   * Returns an array of all the known types of risk
2464   * The array keys can be used, for example as CSS class names, or in calls to
2465   * print_risk_icon. The values are the corresponding RISK_ constants.
2466   *
2467   * @return array all the known types of risk.
2468   */
2469  function get_all_risks() {
2470      return array(
2471          'riskmanagetrust' => RISK_MANAGETRUST,
2472          'riskconfig'      => RISK_CONFIG,
2473          'riskxss'         => RISK_XSS,
2474          'riskpersonal'    => RISK_PERSONAL,
2475          'riskspam'        => RISK_SPAM,
2476          'riskdataloss'    => RISK_DATALOSS,
2477      );
2478  }
2479  
2480  /**
2481   * Return a link to moodle docs for a given capability name
2482   *
2483   * @param stdClass $capability a capability - a row from the mdl_capabilities table.
2484   * @return string the human-readable capability name as a link to Moodle Docs.
2485   */
2486  function get_capability_docs_link($capability) {
2487      $url = get_docs_url('Capabilities/' . $capability->name);
2488      return '<a onclick="this.target=\'docspopup\'" href="' . $url . '">' . get_capability_string($capability->name) . '</a>';
2489  }
2490  
2491  /**
2492   * This function pulls out all the resolved capabilities (overrides and
2493   * defaults) of a role used in capability overrides in contexts at a given
2494   * context.
2495   *
2496   * @param int $roleid
2497   * @param context $context
2498   * @param string $cap capability, optional, defaults to ''
2499   * @return array Array of capabilities
2500   */
2501  function role_context_capabilities($roleid, context $context, $cap = '') {
2502      global $DB;
2503  
2504      $contexts = $context->get_parent_context_ids(true);
2505      $contexts = '('.implode(',', $contexts).')';
2506  
2507      $params = array($roleid);
2508  
2509      if ($cap) {
2510          $search = " AND rc.capability = ? ";
2511          $params[] = $cap;
2512      } else {
2513          $search = '';
2514      }
2515  
2516      $sql = "SELECT rc.*
2517                FROM {role_capabilities} rc
2518                JOIN {context} c ON rc.contextid = c.id
2519                JOIN {capabilities} cap ON rc.capability = cap.name
2520               WHERE rc.contextid in $contexts
2521                     AND rc.roleid = ?
2522                     $search
2523            ORDER BY c.contextlevel DESC, rc.capability DESC";
2524  
2525      $capabilities = array();
2526  
2527      if ($records = $DB->get_records_sql($sql, $params)) {
2528          // We are traversing via reverse order.
2529          foreach ($records as $record) {
2530              // If not set yet (i.e. inherit or not set at all), or currently we have a prohibit
2531              if (!isset($capabilities[$record->capability]) || $record->permission<-500) {
2532                  $capabilities[$record->capability] = $record->permission;
2533              }
2534          }
2535      }
2536      return $capabilities;
2537  }
2538  
2539  /**
2540   * Constructs array with contextids as first parameter and context paths,
2541   * in both cases bottom top including self.
2542   *
2543   * @access private
2544   * @param context $context
2545   * @return array
2546   */
2547  function get_context_info_list(context $context) {
2548      $contextids = explode('/', ltrim($context->path, '/'));
2549      $contextpaths = array();
2550      $contextids2 = $contextids;
2551      while ($contextids2) {
2552          $contextpaths[] = '/' . implode('/', $contextids2);
2553          array_pop($contextids2);
2554      }
2555      return array($contextids, $contextpaths);
2556  }
2557  
2558  /**
2559   * Check if context is the front page context or a context inside it
2560   *
2561   * Returns true if this context is the front page context, or a context inside it,
2562   * otherwise false.
2563   *
2564   * @param context $context a context object.
2565   * @return bool
2566   */
2567  function is_inside_frontpage(context $context) {
2568      $frontpagecontext = context_course::instance(SITEID);
2569      return strpos($context->path . '/', $frontpagecontext->path . '/') === 0;
2570  }
2571  
2572  /**
2573   * Returns capability information (cached)
2574   *
2575   * @param string $capabilityname
2576   * @return stdClass or null if capability not found
2577   */
2578  function get_capability_info($capabilityname) {
2579      $caps = get_all_capabilities();
2580  
2581      // Check for deprecated capability.
2582      if ($deprecatedinfo = get_deprecated_capability_info($capabilityname)) {
2583          if (!empty($deprecatedinfo['replacement'])) {
2584              // Let's try again with this capability if it exists.
2585              if (isset($caps[$deprecatedinfo['replacement']])) {
2586                  $capabilityname = $deprecatedinfo['replacement'];
2587              } else {
2588                  debugging("Capability '{$capabilityname}' was supposed to be replaced with ".
2589                      "'{$deprecatedinfo['replacement']}', which does not exist !");
2590              }
2591          }
2592          $fullmessage = $deprecatedinfo['fullmessage'];
2593          debugging($fullmessage, DEBUG_DEVELOPER);
2594      }
2595      if (!isset($caps[$capabilityname])) {
2596          return null;
2597      }
2598  
2599      return (object) $caps[$capabilityname];
2600  }
2601  
2602  /**
2603   * Returns deprecation info for this particular capabilty (cached)
2604   *
2605   * Do not use this function except in the get_capability_info
2606   *
2607   * @param string $capabilityname
2608   * @return stdClass|null with deprecation message and potential replacement if not null
2609   */
2610  function get_deprecated_capability_info($capabilityname) {
2611      $cache = cache::make('core', 'capabilities');
2612      $alldeprecatedcaps = $cache->get('deprecated_capabilities');
2613      if ($alldeprecatedcaps === false) {
2614          // Look for deprecated capabilities in each component.
2615          $allcaps = get_all_capabilities();
2616          $components = [];
2617          $alldeprecatedcaps = [];
2618          foreach ($allcaps as $cap) {
2619              if (!in_array($cap['component'], $components)) {
2620                  $components[] = $cap['component'];
2621                  $defpath = core_component::get_component_directory($cap['component']).'/db/access.php';
2622                  if (file_exists($defpath)) {
2623                      $deprecatedcapabilities = [];
2624                      require($defpath);
2625                      if (!empty($deprecatedcapabilities)) {
2626                          foreach ($deprecatedcapabilities as $cname => $cdef) {
2627                              $alldeprecatedcaps[$cname] = $cdef;
2628                          }
2629                      }
2630                  }
2631              }
2632          }
2633          $cache->set('deprecated_capabilities', $alldeprecatedcaps);
2634      }
2635  
2636      if (!isset($alldeprecatedcaps[$capabilityname])) {
2637          return null;
2638      }
2639      $deprecatedinfo = $alldeprecatedcaps[$capabilityname];
2640      $deprecatedinfo['fullmessage'] = "The capability '{$capabilityname}' is deprecated.";
2641      if (!empty($deprecatedinfo['message'])) {
2642          $deprecatedinfo['fullmessage'] .= $deprecatedinfo['message'];
2643      }
2644      if (!empty($deprecatedinfo['replacement'])) {
2645          $deprecatedinfo['fullmessage'] .=
2646              "It will be replaced by '{$deprecatedinfo['replacement']}'.";
2647      }
2648      return $deprecatedinfo;
2649  }
2650  
2651  /**
2652   * Returns all capabilitiy records, preferably from MUC and not database.
2653   *
2654   * @return array All capability records indexed by capability name
2655   */
2656  function get_all_capabilities() {
2657      global $DB;
2658      $cache = cache::make('core', 'capabilities');
2659      if (!$allcaps = $cache->get('core_capabilities')) {
2660          $rs = $DB->get_recordset('capabilities');
2661          $allcaps = array();
2662          foreach ($rs as $capability) {
2663              $capability->riskbitmask = (int) $capability->riskbitmask;
2664              $allcaps[$capability->name] = (array) $capability;
2665          }
2666          $rs->close();
2667          $cache->set('core_capabilities', $allcaps);
2668      }
2669      return $allcaps;
2670  }
2671  
2672  /**
2673   * Returns the human-readable, translated version of the capability.
2674   * Basically a big switch statement.
2675   *
2676   * @param string $capabilityname e.g. mod/choice:readresponses
2677   * @return string
2678   */
2679  function get_capability_string($capabilityname) {
2680  
2681      // Typical capability name is 'plugintype/pluginname:capabilityname'
2682      list($type, $name, $capname) = preg_split('|[/:]|', $capabilityname);
2683  
2684      if ($type === 'moodle') {
2685          $component = 'core_role';
2686      } else if ($type === 'quizreport') {
2687          //ugly hack!!
2688          $component = 'quiz_'.$name;
2689      } else {
2690          $component = $type.'_'.$name;
2691      }
2692  
2693      $stringname = $name.':'.$capname;
2694  
2695      if ($component === 'core_role' or get_string_manager()->string_exists($stringname, $component)) {
2696          return get_string($stringname, $component);
2697      }
2698  
2699      $dir = core_component::get_component_directory($component);
2700      if (!isset($dir) || !file_exists($dir)) {
2701          // plugin broken or does not exist, do not bother with printing of debug message
2702          return $capabilityname.' ???';
2703      }
2704  
2705      // something is wrong in plugin, better print debug
2706      return get_string($stringname, $component);
2707  }
2708  
2709  /**
2710   * This gets the mod/block/course/core etc strings.
2711   *
2712   * @param string $component
2713   * @param int $contextlevel
2714   * @return string|bool String is success, false if failed
2715   */
2716  function get_component_string($component, $contextlevel) {
2717  
2718      if ($component === 'moodle' || $component === 'core') {
2719          return context_helper::get_level_name($contextlevel);
2720      }
2721  
2722      list($type, $name) = core_component::normalize_component($component);
2723      $dir = core_component::get_plugin_directory($type, $name);
2724      if (!isset($dir) || !file_exists($dir)) {
2725          // plugin not installed, bad luck, there is no way to find the name
2726          return $component . ' ???';
2727      }
2728  
2729      // Some plugin types need an extra prefix to make the name easy to understand.
2730      switch ($type) {
2731          case 'quiz':
2732              $prefix = get_string('quizreport', 'quiz') . ': ';
2733              break;
2734          case 'repository':
2735              $prefix = get_string('repository', 'repository') . ': ';
2736              break;
2737          case 'gradeimport':
2738              $prefix = get_string('gradeimport', 'grades') . ': ';
2739              break;
2740          case 'gradeexport':
2741              $prefix = get_string('gradeexport', 'grades') . ': ';
2742              break;
2743          case 'gradereport':
2744              $prefix = get_string('gradereport', 'grades') . ': ';
2745              break;
2746          case 'webservice':
2747              $prefix = get_string('webservice', 'webservice') . ': ';
2748              break;
2749          case 'block':
2750              $prefix = get_string('block') . ': ';
2751              break;
2752          case 'mod':
2753              $prefix = get_string('activity') . ': ';
2754              break;
2755  
2756          // Default case, just use the plugin name.
2757          default:
2758              $prefix = '';
2759      }
2760      return $prefix . get_string('pluginname', $component);
2761  }
2762  
2763  /**
2764   * Gets the list of roles assigned to this context and up (parents)
2765   * from the aggregation of:
2766   * a) the list of roles that are visible on user profile page and participants page (profileroles setting) and;
2767   * b) if applicable, those roles that are assigned in the context.
2768   *
2769   * @param context $context
2770   * @return array
2771   */
2772  function get_profile_roles(context $context) {
2773      global $CFG, $DB;
2774      // If the current user can assign roles, then they can see all roles on the profile and participants page,
2775      // provided the roles are assigned to at least 1 user in the context. If not, only the policy-defined roles.
2776      if (has_capability('moodle/role:assign', $context)) {
2777          $rolesinscope = array_keys(get_all_roles($context));
2778      } else {
2779          $rolesinscope = empty($CFG->profileroles) ? [] : array_map('trim', explode(',', $CFG->profileroles));
2780      }
2781  
2782      if (empty($rolesinscope)) {
2783          return [];
2784      }
2785  
2786      list($rallowed, $params) = $DB->get_in_or_equal($rolesinscope, SQL_PARAMS_NAMED, 'a');
2787      list($contextlist, $cparams) = $DB->get_in_or_equal($context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'p');
2788      $params = array_merge($params, $cparams);
2789  
2790      if ($coursecontext = $context->get_course_context(false)) {
2791          $params['coursecontext'] = $coursecontext->id;
2792      } else {
2793          $params['coursecontext'] = 0;
2794      }
2795  
2796      $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder, rn.name AS coursealias
2797                FROM {role_assignments} ra, {role} r
2798           LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
2799               WHERE r.id = ra.roleid
2800                     AND ra.contextid $contextlist
2801                     AND r.id $rallowed
2802            ORDER BY r.sortorder ASC";
2803  
2804      return $DB->get_records_sql($sql, $params);
2805  }
2806  
2807  /**
2808   * Gets the list of roles assigned to this context and up (parents)
2809   *
2810   * @param context $context
2811   * @param boolean $includeparents, false means without parents.
2812   * @return array
2813   */
2814  function get_roles_used_in_context(context $context, $includeparents = true) {
2815      global $DB;
2816  
2817      if ($includeparents === true) {
2818          list($contextlist, $params) = $DB->get_in_or_equal($context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'cl');
2819      } else {
2820          list($contextlist, $params) = $DB->get_in_or_equal($context->id, SQL_PARAMS_NAMED, 'cl');
2821      }
2822  
2823      if ($coursecontext = $context->get_course_context(false)) {
2824          $params['coursecontext'] = $coursecontext->id;
2825      } else {
2826          $params['coursecontext'] = 0;
2827      }
2828  
2829      $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder, rn.name AS coursealias
2830                FROM {role_assignments} ra, {role} r
2831           LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
2832               WHERE r.id = ra.roleid
2833                     AND ra.contextid $contextlist
2834            ORDER BY r.sortorder ASC";
2835  
2836      return $DB->get_records_sql($sql, $params);
2837  }
2838  
2839  /**
2840   * This function is used to print roles column in user profile page.
2841   * It is using the CFG->profileroles to limit the list to only interesting roles.
2842   * (The permission tab has full details of user role assignments.)
2843   *
2844   * @param int $userid
2845   * @param int $courseid
2846   * @return string
2847   */
2848  function get_user_roles_in_course($userid, $courseid) {
2849      global $CFG, $DB;
2850      if ($courseid == SITEID) {
2851          $context = context_system::instance();
2852      } else {
2853          $context = context_course::instance($courseid);
2854      }
2855      // If the current user can assign roles, then they can see all roles on the profile and participants page,
2856      // provided the roles are assigned to at least 1 user in the context. If not, only the policy-defined roles.
2857      if (has_capability('moodle/role:assign', $context)) {
2858          $rolesinscope = array_keys(get_all_roles($context));
2859      } else {
2860          $rolesinscope = empty($CFG->profileroles) ? [] : array_map('trim', explode(',', $CFG->profileroles));
2861      }
2862      if (empty($rolesinscope)) {
2863          return '';
2864      }
2865  
2866      list($rallowed, $params) = $DB->get_in_or_equal($rolesinscope, SQL_PARAMS_NAMED, 'a');
2867      list($contextlist, $cparams) = $DB->get_in_or_equal($context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'p');
2868      $params = array_merge($params, $cparams);
2869  
2870      if ($coursecontext = $context->get_course_context(false)) {
2871          $params['coursecontext'] = $coursecontext->id;
2872      } else {
2873          $params['coursecontext'] = 0;
2874      }
2875  
2876      $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder, rn.name AS coursealias
2877                FROM {role_assignments} ra, {role} r
2878           LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
2879               WHERE r.id = ra.roleid
2880                     AND ra.contextid $contextlist
2881                     AND r.id $rallowed
2882                     AND ra.userid = :userid
2883            ORDER BY r.sortorder ASC";
2884      $params['userid'] = $userid;
2885  
2886      $rolestring = '';
2887  
2888      if ($roles = $DB->get_records_sql($sql, $params)) {
2889          $viewableroles = get_viewable_roles($context, $userid);
2890  
2891          $rolenames = array();
2892          foreach ($roles as $roleid => $unused) {
2893              if (isset($viewableroles[$roleid])) {
2894                  $url = new moodle_url('/user/index.php', ['contextid' => $context->id, 'roleid' => $roleid]);
2895                  $rolenames[] = '<a href="' . $url . '">' . $viewableroles[$roleid] . '</a>';
2896              }
2897          }
2898          $rolestring = implode(', ', $rolenames);
2899      }
2900  
2901      return $rolestring;
2902  }
2903  
2904  /**
2905   * Checks if a user can assign users to a particular role in this context
2906   *
2907   * @param context $context
2908   * @param int $targetroleid - the id of the role you want to assign users to
2909   * @return boolean
2910   */
2911  function user_can_assign(context $context, $targetroleid) {
2912      global $DB;
2913  
2914      // First check to see if the user is a site administrator.
2915      if (is_siteadmin()) {
2916          return true;
2917      }
2918  
2919      // Check if user has override capability.
2920      // If not return false.
2921      if (!has_capability('moodle/role:assign', $context)) {
2922          return false;
2923      }
2924      // pull out all active roles of this user from this context(or above)
2925      if ($userroles = get_user_roles($context)) {
2926          foreach ($userroles as $userrole) {
2927              // if any in the role_allow_override table, then it's ok
2928              if ($DB->get_record('role_allow_assign', array('roleid'=>$userrole->roleid, 'allowassign'=>$targetroleid))) {
2929                  return true;
2930              }
2931          }
2932      }
2933  
2934      return false;
2935  }
2936  
2937  /**
2938   * Returns all site roles in correct sort order.
2939   *
2940   * Note: this method does not localise role names or descriptions,
2941   *       use role_get_names() if you need role names.
2942   *
2943   * @param context $context optional context for course role name aliases
2944   * @return array of role records with optional coursealias property
2945   */
2946  function get_all_roles(context $context = null) {
2947      global $DB;
2948  
2949      if (!$context or !$coursecontext = $context->get_course_context(false)) {
2950          $coursecontext = null;
2951      }
2952  
2953      if ($coursecontext) {
2954          $sql = "SELECT r.*, rn.name AS coursealias
2955                    FROM {role} r
2956               LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
2957                ORDER BY r.sortorder ASC";
2958          return $DB->get_records_sql($sql, array('coursecontext'=>$coursecontext->id));
2959  
2960      } else {
2961          return $DB->get_records('role', array(), 'sortorder ASC');
2962      }
2963  }
2964  
2965  /**
2966   * Returns roles of a specified archetype
2967   *
2968   * @param string $archetype
2969   * @return array of full role records
2970   */
2971  function get_archetype_roles($archetype) {
2972      global $DB;
2973      return $DB->get_records('role', array('archetype'=>$archetype), 'sortorder ASC');
2974  }
2975  
2976  /**
2977   * Gets all the user roles assigned in this context, or higher contexts for a list of users.
2978   *
2979   * If you try using the combination $userids = [], $checkparentcontexts = true then this is likely
2980   * to cause an out-of-memory error on large Moodle sites, so this combination is deprecated and
2981   * outputs a warning, even though it is the default.
2982   *
2983   * @param context $context
2984   * @param array $userids. An empty list means fetch all role assignments for the context.
2985   * @param bool $checkparentcontexts defaults to true
2986   * @param string $order defaults to 'c.contextlevel DESC, r.sortorder ASC'
2987   * @return array
2988   */
2989  function get_users_roles(context $context, $userids = [], $checkparentcontexts = true, $order = 'c.contextlevel DESC, r.sortorder ASC') {
2990      global $DB;
2991  
2992      if (!$userids && $checkparentcontexts) {
2993          debugging('Please do not call get_users_roles() with $checkparentcontexts = true ' .
2994                  'and $userids array not set. This combination causes large Moodle sites ' .
2995                  'with lots of site-wide role assignemnts to run out of memory.', DEBUG_DEVELOPER);
2996      }
2997  
2998      if ($checkparentcontexts) {
2999          $contextids = $context->get_parent_context_ids();
3000      } else {
3001          $contextids = array();
3002      }
3003      $contextids[] = $context->id;
3004  
3005      list($contextids, $params) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, 'con');
3006  
3007      // If userids was passed as an empty array, we fetch all role assignments for the course.
3008      if (empty($userids)) {
3009          $useridlist = ' IS NOT NULL ';
3010          $uparams = [];
3011      } else {
3012          list($useridlist, $uparams) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED, 'uids');
3013      }
3014  
3015      $sql = "SELECT ra.*, r.name, r.shortname, ra.userid
3016                FROM {role_assignments} ra, {role} r, {context} c
3017               WHERE ra.userid $useridlist
3018                     AND ra.roleid = r.id
3019                     AND ra.contextid = c.id
3020                     AND ra.contextid $contextids
3021            ORDER BY $order";
3022  
3023      $all = $DB->get_records_sql($sql , array_merge($params, $uparams));
3024  
3025      // Return results grouped by userid.
3026      $result = [];
3027      foreach ($all as $id => $record) {
3028          if (!isset($result[$record->userid])) {
3029              $result[$record->userid] = [];
3030          }
3031          $result[$record->userid][$record->id] = $record;
3032      }
3033  
3034      // Make sure all requested users are included in the result, even if they had no role assignments.
3035      foreach ($userids as $id) {
3036          if (!isset($result[$id])) {
3037              $result[$id] = [];
3038          }
3039      }
3040  
3041      return $result;
3042  }
3043  
3044  
3045  /**
3046   * Gets all the user roles assigned in this context, or higher contexts
3047   * this is mainly used when checking if a user can assign a role, or overriding a role
3048   * i.e. we need to know what this user holds, in order to verify against allow_assign and
3049   * allow_override tables
3050   *
3051   * @param context $context
3052   * @param int $userid
3053   * @param bool $checkparentcontexts defaults to true
3054   * @param string $order defaults to 'c.contextlevel DESC, r.sortorder ASC'
3055   * @return array
3056   */
3057  function get_user_roles(context $context, $userid = 0, $checkparentcontexts = true, $order = 'c.contextlevel DESC, r.sortorder ASC') {
3058      global $USER, $DB;
3059  
3060      if (empty($userid)) {
3061          if (empty($USER->id)) {
3062              return array();
3063          }
3064          $userid = $USER->id;
3065      }
3066  
3067      if ($checkparentcontexts) {
3068          $contextids = $context->get_parent_context_ids();
3069      } else {
3070          $contextids = array();
3071      }
3072      $contextids[] = $context->id;
3073  
3074      list($contextids, $params) = $DB->get_in_or_equal($contextids, SQL_PARAMS_QM);
3075  
3076      array_unshift($params, $userid);
3077  
3078      $sql = "SELECT ra.*, r.name, r.shortname
3079                FROM {role_assignments} ra, {role} r, {context} c
3080               WHERE ra.userid = ?
3081                     AND ra.roleid = r.id
3082                     AND ra.contextid = c.id
3083                     AND ra.contextid $contextids
3084            ORDER BY $order";
3085  
3086      return $DB->get_records_sql($sql ,$params);
3087  }
3088  
3089  /**
3090   * Like get_user_roles, but adds in the authenticated user role, and the front
3091   * page roles, if applicable.
3092   *
3093   * @param context $context the context.
3094   * @param int $userid optional. Defaults to $USER->id
3095   * @return array of objects with fields ->userid, ->contextid and ->roleid.
3096   */
3097  function get_user_roles_with_special(context $context, $userid = 0) {
3098      global $CFG, $USER;
3099  
3100      if (empty($userid)) {
3101          if (empty($USER->id)) {
3102              return array();
3103          }
3104          $userid = $USER->id;
3105      }
3106  
3107      $ras = get_user_roles($context, $userid);
3108  
3109      // Add front-page role if relevant.
3110      $defaultfrontpageroleid = isset($CFG->defaultfrontpageroleid) ? $CFG->defaultfrontpageroleid : 0;
3111      $isfrontpage = ($context->contextlevel == CONTEXT_COURSE && $context->instanceid == SITEID) ||
3112              is_inside_frontpage($context);
3113      if ($defaultfrontpageroleid && $isfrontpage) {
3114          $frontpagecontext = context_course::instance(SITEID);
3115          $ra = new stdClass();
3116          $ra->userid = $userid;
3117          $ra->contextid = $frontpagecontext->id;
3118          $ra->roleid = $defaultfrontpageroleid;
3119          $ras[] = $ra;
3120      }
3121  
3122      // Add authenticated user role if relevant.
3123      $defaultuserroleid      = isset($CFG->defaultuserroleid) ? $CFG->defaultuserroleid : 0;
3124      if ($defaultuserroleid && !isguestuser($userid)) {
3125          $systemcontext = context_system::instance();
3126          $ra = new stdClass();
3127          $ra->userid = $userid;
3128          $ra->contextid = $systemcontext->id;
3129          $ra->roleid = $defaultuserroleid;
3130          $ras[] = $ra;
3131      }
3132  
3133      return $ras;
3134  }
3135  
3136  /**
3137   * Creates a record in the role_allow_override table
3138   *
3139   * @param int $fromroleid source roleid
3140   * @param int $targetroleid target roleid
3141   * @return void
3142   */
3143  function core_role_set_override_allowed($fromroleid, $targetroleid) {
3144      global $DB;
3145  
3146      $record = new stdClass();
3147      $record->roleid        = $fromroleid;
3148      $record->allowoverride = $targetroleid;
3149      $DB->insert_record('role_allow_override', $record);
3150  }
3151  
3152  /**
3153   * Creates a record in the role_allow_assign table
3154   *
3155   * @param int $fromroleid source roleid
3156   * @param int $targetroleid target roleid
3157   * @return void
3158   */
3159  function core_role_set_assign_allowed($fromroleid, $targetroleid) {
3160      global $DB;
3161  
3162      $record = new stdClass();
3163      $record->roleid      = $fromroleid;
3164      $record->allowassign = $targetroleid;
3165      $DB->insert_record('role_allow_assign', $record);
3166  }
3167  
3168  /**
3169   * Creates a record in the role_allow_switch table
3170   *
3171   * @param int $fromroleid source roleid
3172   * @param int $targetroleid target roleid
3173   * @return void
3174   */
3175  function core_role_set_switch_allowed($fromroleid, $targetroleid) {
3176      global $DB;
3177  
3178      $record = new stdClass();
3179      $record->roleid      = $fromroleid;
3180      $record->allowswitch = $targetroleid;
3181      $DB->insert_record('role_allow_switch', $record);
3182  }
3183  
3184  /**
3185   * Creates a record in the role_allow_view table
3186   *
3187   * @param int $fromroleid source roleid
3188   * @param int $targetroleid target roleid
3189   * @return void
3190   */
3191  function core_role_set_view_allowed($fromroleid, $targetroleid) {
3192      global $DB;
3193  
3194      $record = new stdClass();
3195      $record->roleid      = $fromroleid;
3196      $record->allowview = $targetroleid;
3197      $DB->insert_record('role_allow_view', $record);
3198  }
3199  
3200  /**
3201   * Gets a list of roles that this user can assign in this context
3202   *
3203   * @param context $context the context.
3204   * @param int $rolenamedisplay the type of role name to display. One of the
3205   *      ROLENAME_X constants. Default ROLENAME_ALIAS.
3206   * @param bool $withusercounts if true, count the number of users with each role.
3207   * @param integer|object $user A user id or object. By default (null) checks the permissions of the current user.
3208   * @return array if $withusercounts is false, then an array $roleid => $rolename.
3209   *      if $withusercounts is true, returns a list of three arrays,
3210   *      $rolenames, $rolecounts, and $nameswithcounts.
3211   */
3212  function get_assignable_roles(context $context, $rolenamedisplay = ROLENAME_ALIAS, $withusercounts = false, $user = null) {
3213      global $USER, $DB;
3214  
3215      // make sure there is a real user specified
3216      if ($user === null) {
3217          $userid = isset($USER->id) ? $USER->id : 0;
3218      } else {
3219          $userid = is_object($user) ? $user->id : $user;
3220      }
3221  
3222      if (!has_capability('moodle/role:assign', $context, $userid)) {
3223          if ($withusercounts) {
3224              return array(array(), array(), array());
3225          } else {
3226              return array();
3227          }
3228      }
3229  
3230      $params = array();
3231      $extrafields = '';
3232  
3233      if ($withusercounts) {
3234          $extrafields = ', (SELECT COUNT(DISTINCT u.id)
3235                               FROM {role_assignments} cra JOIN {user} u ON cra.userid = u.id
3236                              WHERE cra.roleid = r.id AND cra.contextid = :conid AND u.deleted = 0
3237                            ) AS usercount';
3238          $params['conid'] = $context->id;
3239      }
3240  
3241      if (is_siteadmin($userid)) {
3242          // show all roles allowed in this context to admins
3243          $assignrestriction = "";
3244      } else {
3245          $parents = $context->get_parent_context_ids(true);
3246          $contexts = implode(',' , $parents);
3247          $assignrestriction = "JOIN (SELECT DISTINCT raa.allowassign AS id
3248                                        FROM {role_allow_assign} raa
3249                                        JOIN {role_assignments} ra ON ra.roleid = raa.roleid
3250                                       WHERE ra.userid = :userid AND ra.contextid IN ($contexts)
3251                                     ) ar ON ar.id = r.id";
3252          $params['userid'] = $userid;
3253      }
3254      $params['contextlevel'] = $context->contextlevel;
3255  
3256      if ($coursecontext = $context->get_course_context(false)) {
3257          $params['coursecontext'] = $coursecontext->id;
3258      } else {
3259          $params['coursecontext'] = 0; // no course aliases
3260          $coursecontext = null;
3261      }
3262      $sql = "SELECT r.id, r.name, r.shortname, rn.name AS coursealias $extrafields
3263                FROM {role} r
3264                $assignrestriction
3265                JOIN {role_context_levels} rcl ON (rcl.contextlevel = :contextlevel AND r.id = rcl.roleid)
3266           LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
3267            ORDER BY r.sortorder ASC";
3268      $roles = $DB->get_records_sql($sql, $params);
3269  
3270      $rolenames = role_fix_names($roles, $coursecontext, $rolenamedisplay, true);
3271  
3272      if (!$withusercounts) {
3273          return $rolenames;
3274      }
3275  
3276      $rolecounts = array();
3277      $nameswithcounts = array();
3278      foreach ($roles as $role) {
3279          $nameswithcounts[$role->id] = $rolenames[$role->id] . ' (' . $roles[$role->id]->usercount . ')';
3280          $rolecounts[$role->id] = $roles[$role->id]->usercount;
3281      }
3282      return array($rolenames, $rolecounts, $nameswithcounts);
3283  }
3284  
3285  /**
3286   * Gets a list of roles that this user can switch to in a context
3287   *
3288   * Gets a list of roles that this user can switch to in a context, for the switchrole menu.
3289   * This function just process the contents of the role_allow_switch table. You also need to
3290   * test the moodle/role:switchroles to see if the user is allowed to switch in the first place.
3291   *
3292   * @param context $context a context.
3293   * @param int $rolenamedisplay the type of role name to display. One of the
3294   *      ROLENAME_X constants. Default ROLENAME_ALIAS.
3295   * @return array an array $roleid => $rolename.
3296   */
3297  function get_switchable_roles(context $context, $rolenamedisplay = ROLENAME_ALIAS) {
3298      global $USER, $DB;
3299  
3300      // You can't switch roles without this capability.
3301      if (!has_capability('moodle/role:switchroles', $context)) {
3302          return [];
3303      }
3304  
3305      $params = array();
3306      $extrajoins = '';
3307      $extrawhere = '';
3308      if (!is_siteadmin()) {
3309          // Admins are allowed to switch to any role with.
3310          // Others are subject to the additional constraint that the switch-to role must be allowed by
3311          // 'role_allow_switch' for some role they have assigned in this context or any parent.
3312          $parents = $context->get_parent_context_ids(true);
3313          $contexts = implode(',' , $parents);
3314  
3315          $extrajoins = "JOIN {role_allow_switch} ras ON ras.allowswitch = rc.roleid
3316          JOIN {role_assignments} ra ON ra.roleid = ras.roleid";
3317          $extrawhere = "WHERE ra.userid = :userid AND ra.contextid IN ($contexts)";
3318          $params['userid'] = $USER->id;
3319      }
3320  
3321      if ($coursecontext = $context->get_course_context(false)) {
3322          $params['coursecontext'] = $coursecontext->id;
3323      } else {
3324          $params['coursecontext'] = 0; // no course aliases
3325          $coursecontext = null;
3326      }
3327  
3328      $query = "
3329          SELECT r.id, r.name, r.shortname, rn.name AS coursealias
3330            FROM (SELECT DISTINCT rc.roleid
3331                    FROM {role_capabilities} rc
3332  
3333                    $extrajoins
3334                    $extrawhere) idlist
3335            JOIN {role} r ON r.id = idlist.roleid
3336       LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
3337        ORDER BY r.sortorder";
3338      $roles = $DB->get_records_sql($query, $params);
3339  
3340      return role_fix_names($roles, $context, $rolenamedisplay, true);
3341  }
3342  
3343  /**
3344   * Gets a list of roles that this user can view in a context
3345   *
3346   * @param context $context a context.
3347   * @param int $userid id of user.
3348   * @param int $rolenamedisplay the type of role name to display. One of the
3349   *      ROLENAME_X constants. Default ROLENAME_ALIAS.
3350   * @return array an array $roleid => $rolename.
3351   */
3352  function get_viewable_roles(context $context, $userid = null, $rolenamedisplay = ROLENAME_ALIAS) {
3353      global $USER, $DB;
3354  
3355      if ($userid == null) {
3356          $userid = $USER->id;
3357      }
3358  
3359      $params = array();
3360      $extrajoins = '';
3361      $extrawhere = '';
3362      if (!is_siteadmin()) {
3363          // Admins are allowed to view any role.
3364          // Others are subject to the additional constraint that the view role must be allowed by
3365          // 'role_allow_view' for some role they have assigned in this context or any parent.
3366          $contexts = $context->get_parent_context_ids(true);
3367          list($insql, $inparams) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED);
3368  
3369          $extrajoins = "JOIN {role_allow_view} ras ON ras.allowview = r.id
3370                         JOIN {role_assignments} ra ON ra.roleid = ras.roleid";
3371          $extrawhere = "WHERE ra.userid = :userid AND ra.contextid $insql";
3372  
3373          $params += $inparams;
3374          $params['userid'] = $userid;
3375      }
3376  
3377      if ($coursecontext = $context->get_course_context(false)) {
3378          $params['coursecontext'] = $coursecontext->id;
3379      } else {
3380          $params['coursecontext'] = 0; // No course aliases.
3381          $coursecontext = null;
3382      }
3383  
3384      $query = "
3385          SELECT r.id, r.name, r.shortname, rn.name AS coursealias, r.sortorder
3386            FROM {role} r
3387            $extrajoins
3388       LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
3389            $extrawhere
3390        GROUP BY r.id, r.name, r.shortname, rn.name, r.sortorder
3391        ORDER BY r.sortorder";
3392      $roles = $DB->get_records_sql($query, $params);
3393  
3394      return role_fix_names($roles, $context, $rolenamedisplay, true);
3395  }
3396  
3397  /**
3398   * Gets a list of roles that this user can override in this context.
3399   *
3400   * @param context $context the context.
3401   * @param int $rolenamedisplay the type of role name to display. One of the
3402   *      ROLENAME_X constants. Default ROLENAME_ALIAS.
3403   * @param bool $withcounts if true, count the number of overrides that are set for each role.
3404   * @return array if $withcounts is false, then an array $roleid => $rolename.
3405   *      if $withusercounts is true, returns a list of three arrays,
3406   *      $rolenames, $rolecounts, and $nameswithcounts.
3407   */
3408  function get_overridable_roles(context $context, $rolenamedisplay = ROLENAME_ALIAS, $withcounts = false) {
3409      global $USER, $DB;
3410  
3411      if (!has_any_capability(array('moodle/role:safeoverride', 'moodle/role:override'), $context)) {
3412          if ($withcounts) {
3413              return array(array(), array(), array());
3414          } else {
3415              return array();
3416          }
3417      }
3418  
3419      $parents = $context->get_parent_context_ids(true);
3420      $contexts = implode(',' , $parents);
3421  
3422      $params = array();
3423      $extrafields = '';
3424  
3425      $params['userid'] = $USER->id;
3426      if ($withcounts) {
3427          $extrafields = ', (SELECT COUNT(rc.id) FROM {role_capabilities} rc
3428                  WHERE rc.roleid = ro.id AND rc.contextid = :conid) AS overridecount';
3429          $params['conid'] = $context->id;
3430      }
3431  
3432      if ($coursecontext = $context->get_course_context(false)) {
3433          $params['coursecontext'] = $coursecontext->id;
3434      } else {
3435          $params['coursecontext'] = 0; // no course aliases
3436          $coursecontext = null;
3437      }
3438  
3439      if (is_siteadmin()) {
3440          // show all roles to admins
3441          $roles = $DB->get_records_sql("
3442              SELECT ro.id, ro.name, ro.shortname, rn.name AS coursealias $extrafields
3443                FROM {role} ro
3444           LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = ro.id)
3445            ORDER BY ro.sortorder ASC", $params);
3446  
3447      } else {
3448          $roles = $DB->get_records_sql("
3449              SELECT ro.id, ro.name, ro.shortname, rn.name AS coursealias $extrafields
3450                FROM {role} ro
3451                JOIN (SELECT DISTINCT r.id
3452                        FROM {role} r
3453                        JOIN {role_allow_override} rao ON r.id = rao.allowoverride
3454                        JOIN {role_assignments} ra ON rao.roleid = ra.roleid
3455                       WHERE ra.userid = :userid AND ra.contextid IN ($contexts)
3456                     ) inline_view ON ro.id = inline_view.id
3457           LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = ro.id)
3458            ORDER BY ro.sortorder ASC", $params);
3459      }
3460  
3461      $rolenames = role_fix_names($roles, $context, $rolenamedisplay, true);
3462  
3463      if (!$withcounts) {
3464          return $rolenames;
3465      }
3466  
3467      $rolecounts = array();
3468      $nameswithcounts = array();
3469      foreach ($roles as $role) {
3470          $nameswithcounts[$role->id] = $rolenames[$role->id] . ' (' . $roles[$role->id]->overridecount . ')';
3471          $rolecounts[$role->id] = $roles[$role->id]->overridecount;
3472      }
3473      return array($rolenames, $rolecounts, $nameswithcounts);
3474  }
3475  
3476  /**
3477   * Create a role menu suitable for default role selection in enrol plugins.
3478   *
3479   * @package    core_enrol
3480   *
3481   * @param context $context
3482   * @param int $addroleid current or default role - always added to list
3483   * @return array roleid=>localised role name
3484   */
3485  function get_default_enrol_roles(context $context, $addroleid = null) {
3486      global $DB;
3487  
3488      $params = array('contextlevel'=>CONTEXT_COURSE);
3489  
3490      if ($coursecontext = $context->get_course_context(false)) {
3491          $params['coursecontext'] = $coursecontext->id;
3492      } else {
3493          $params['coursecontext'] = 0; // no course names
3494          $coursecontext = null;
3495      }
3496  
3497      if ($addroleid) {
3498          $addrole = "OR r.id = :addroleid";
3499          $params['addroleid'] = $addroleid;
3500      } else {
3501          $addrole = "";
3502      }
3503  
3504      $sql = "SELECT r.id, r.name, r.shortname, rn.name AS coursealias
3505                FROM {role} r
3506           LEFT JOIN {role_context_levels} rcl ON (rcl.roleid = r.id AND rcl.contextlevel = :contextlevel)
3507           LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
3508               WHERE rcl.id IS NOT NULL $addrole
3509            ORDER BY sortorder DESC";
3510  
3511      $roles = $DB->get_records_sql($sql, $params);
3512  
3513      return role_fix_names($roles, $context, ROLENAME_BOTH, true);
3514  }
3515  
3516  /**
3517   * Return context levels where this role is assignable.
3518   *
3519   * @param integer $roleid the id of a role.
3520   * @return array list of the context levels at which this role may be assigned.
3521   */
3522  function get_role_contextlevels($roleid) {
3523      global $DB;
3524      return $DB->get_records_menu('role_context_levels', array('roleid' => $roleid),
3525              'contextlevel', 'id,contextlevel');
3526  }
3527  
3528  /**
3529   * Return roles suitable for assignment at the specified context level.
3530   *
3531   * NOTE: this function name looks like a typo, should be probably get_roles_for_contextlevel()
3532   *
3533   * @param integer $contextlevel a contextlevel.
3534   * @return array list of role ids that are assignable at this context level.
3535   */
3536  function get_roles_for_contextlevels($contextlevel) {
3537      global $DB;
3538      return $DB->get_records_menu('role_context_levels', array('contextlevel' => $contextlevel),
3539              '', 'id,roleid');
3540  }
3541  
3542  /**
3543   * Returns default context levels where roles can be assigned.
3544   *
3545   * @param string $rolearchetype one of the role archetypes - that is, one of the keys
3546   *      from the array returned by get_role_archetypes();
3547   * @return array list of the context levels at which this type of role may be assigned by default.
3548   */
3549  function get_default_contextlevels($rolearchetype) {
3550      return \context_helper::get_compatible_levels($rolearchetype);
3551  }
3552  
3553  /**
3554   * Set the context levels at which a particular role can be assigned.
3555   * Throws exceptions in case of error.
3556   *
3557   * @param integer $roleid the id of a role.
3558   * @param array $contextlevels the context levels at which this role should be assignable,
3559   *      duplicate levels are removed.
3560   * @return void
3561   */
3562  function set_role_contextlevels($roleid, array $contextlevels) {
3563      global $DB;
3564      $DB->delete_records('role_context_levels', array('roleid' => $roleid));
3565      $rcl = new stdClass();
3566      $rcl->roleid = $roleid;
3567      $contextlevels = array_unique($contextlevels);
3568      foreach ($contextlevels as $level) {
3569          $rcl->contextlevel = $level;
3570          $DB->insert_record('role_context_levels', $rcl, false, true);
3571      }
3572  }
3573  
3574  /**
3575   * Gets sql joins for finding users with capability in the given context.
3576   *
3577   * @param context $context Context for the join.
3578   * @param string|array $capability Capability name or array of names.
3579   *      If an array is provided then this is the equivalent of a logical 'OR',
3580   *      i.e. the user needs to have one of these capabilities.
3581   * @param string $useridcolumn e.g. 'u.id'.
3582   * @return \core\dml\sql_join Contains joins, wheres, params.
3583   *      This function will set ->cannotmatchanyrows if applicable.
3584   *      This may let you skip doing a DB query.
3585   */
3586  function get_with_capability_join(context $context, $capability, $useridcolumn) {
3587      global $CFG, $DB;
3588  
3589      // Add a unique prefix to param names to ensure they are unique.
3590      static $i = 0;
3591      $i++;
3592      $paramprefix = 'eu' . $i . '_';
3593  
3594      $defaultuserroleid      = isset($CFG->defaultuserroleid) ? $CFG->defaultuserroleid : 0;
3595      $defaultfrontpageroleid = isset($CFG->defaultfrontpageroleid) ? $CFG->defaultfrontpageroleid : 0;
3596  
3597      $ctxids = trim($context->path, '/');
3598      $ctxids = str_replace('/', ',', $ctxids);
3599  
3600      // Context is the frontpage
3601      $isfrontpage = $context->contextlevel == CONTEXT_COURSE && $context->instanceid == SITEID;
3602      $isfrontpage = $isfrontpage || is_inside_frontpage($context);
3603  
3604      $caps = (array) $capability;
3605  
3606      // Construct list of context paths bottom --> top.
3607      list($contextids, $paths) = get_context_info_list($context);
3608  
3609      // We need to find out all roles that have these capabilities either in definition or in overrides.
3610      $defs = [];
3611      list($incontexts, $params) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, $paramprefix . 'con');
3612      list($incaps, $params2) = $DB->get_in_or_equal($caps, SQL_PARAMS_NAMED, $paramprefix . 'cap');
3613  
3614      // Check whether context locking is enabled.
3615      // Filter out any write capability if this is the case.
3616      $excludelockedcaps = '';
3617      $excludelockedcapsparams = [];
3618      if (!empty($CFG->contextlocking) && $context->locked) {
3619          $excludelockedcaps = 'AND (cap.captype = :capread OR cap.name = :managelockscap)';
3620          $excludelockedcapsparams['capread'] = 'read';
3621          $excludelockedcapsparams['managelockscap'] = 'moodle/site:managecontextlocks';
3622      }
3623  
3624      $params = array_merge($params, $params2, $excludelockedcapsparams);
3625      $sql = "SELECT rc.id, rc.roleid, rc.permission, rc.capability, ctx.path
3626                FROM {role_capabilities} rc
3627                JOIN {capabilities} cap ON rc.capability = cap.name
3628                JOIN {context} ctx on rc.contextid = ctx.id
3629               WHERE rc.contextid $incontexts AND rc.capability $incaps $excludelockedcaps";
3630  
3631      $rcs = $DB->get_records_sql($sql, $params);
3632      foreach ($rcs as $rc) {
3633          $defs[$rc->capability][$rc->path][$rc->roleid] = $rc->permission;
3634      }
3635  
3636      // Go through the permissions bottom-->top direction to evaluate the current permission,
3637      // first one wins (prohibit is an exception that always wins).
3638      $access = [];
3639      foreach ($caps as $cap) {
3640          foreach ($paths as $path) {
3641              if (empty($defs[$cap][$path])) {
3642                  continue;
3643              }
3644              foreach ($defs[$cap][$path] as $roleid => $perm) {
3645                  if ($perm == CAP_PROHIBIT) {
3646                      $access[$cap][$roleid] = CAP_PROHIBIT;
3647                      continue;
3648                  }
3649                  if (!isset($access[$cap][$roleid])) {
3650                      $access[$cap][$roleid] = (int)$perm;
3651                  }
3652              }
3653          }
3654      }
3655  
3656      // Make lists of roles that are needed and prohibited in this context.
3657      $needed = []; // One of these is enough.
3658      $prohibited = []; // Must not have any of these.
3659      foreach ($caps as $cap) {
3660          if (empty($access[$cap])) {
3661              continue;
3662          }
3663          foreach ($access[$cap] as $roleid => $perm) {
3664              if ($perm == CAP_PROHIBIT) {
3665                  unset($needed[$cap][$roleid]);
3666                  $prohibited[$cap][$roleid] = true;
3667              } else if ($perm == CAP_ALLOW and empty($prohibited[$cap][$roleid])) {
3668                  $needed[$cap][$roleid] = true;
3669              }
3670          }
3671          if (empty($needed[$cap]) or !empty($prohibited[$cap][$defaultuserroleid])) {
3672              // Easy, nobody has the permission.
3673              unset($needed[$cap]);
3674              unset($prohibited[$cap]);
3675          } else if ($isfrontpage and !empty($prohibited[$cap][$defaultfrontpageroleid])) {
3676              // Everybody is disqualified on the frontpage.
3677              unset($needed[$cap]);
3678              unset($prohibited[$cap]);
3679          }
3680          if (empty($prohibited[$cap])) {
3681              unset($prohibited[$cap]);
3682          }
3683      }
3684  
3685      if (empty($needed)) {
3686          // There can not be anybody if no roles match this request.
3687          return new \core\dml\sql_join('', '1 = 2', [], true);
3688      }
3689  
3690      if (empty($prohibited)) {
3691          // We can compact the needed roles.
3692          $n = [];
3693          foreach ($needed as $cap) {
3694              foreach ($cap as $roleid => $unused) {
3695                  $n[$roleid] = true;
3696              }
3697          }
3698          $needed = ['any' => $n];
3699          unset($n);
3700      }
3701  
3702      // Prepare query clauses.
3703      $wherecond = [];
3704      $params    = [];
3705      $joins     = [];
3706      $cannotmatchanyrows = false;
3707  
3708      // We never return deleted users or guest account.
3709      // Use a hack to get the deleted user column without an API change.
3710      $deletedusercolumn = substr($useridcolumn, 0, -2) . 'deleted';
3711      $wherecond[] = "$deletedusercolumn = 0 AND $useridcolumn <> :{$paramprefix}guestid";
3712      $params[$paramprefix . 'guestid'] = $CFG->siteguest;
3713  
3714      // Now add the needed and prohibited roles conditions as joins.
3715      if (!empty($needed['any'])) {
3716          // Simple case - there are no prohibits involved.
3717          if (!empty($needed['any'][$defaultuserroleid]) ||
3718                  ($isfrontpage && !empty($needed['any'][$defaultfrontpageroleid]))) {
3719              // Everybody.
3720          } else {
3721              $joins[] = "JOIN (SELECT DISTINCT userid
3722                                  FROM {role_assignments}
3723                                 WHERE contextid IN ($ctxids)
3724                                       AND roleid IN (" . implode(',', array_keys($needed['any'])) . ")
3725                               ) ra ON ra.userid = $useridcolumn";
3726          }
3727      } else {
3728          $unions = [];
3729          $everybody = false;
3730          foreach ($needed as $cap => $unused) {
3731              if (empty($prohibited[$cap])) {
3732                  if (!empty($needed[$cap][$defaultuserroleid]) ||
3733                          ($isfrontpage && !empty($needed[$cap][$defaultfrontpageroleid]))) {
3734                      $everybody = true;
3735                      break;
3736                  } else {
3737                      $unions[] = "SELECT userid
3738                                     FROM {role_assignments}
3739                                    WHERE contextid IN ($ctxids)
3740                                          AND roleid IN (".implode(',', array_keys($needed[$cap])) .")";
3741                  }
3742              } else {
3743                  if (!empty($prohibited[$cap][$defaultuserroleid]) ||
3744                          ($isfrontpage && !empty($prohibited[$cap][$defaultfrontpageroleid]))) {
3745                      // Nobody can have this cap because it is prohibited in default roles.
3746                      continue;
3747  
3748                  } else if (!empty($needed[$cap][$defaultuserroleid]) ||
3749                          ($isfrontpage && !empty($needed[$cap][$defaultfrontpageroleid]))) {
3750                      // Everybody except the prohibited - hiding does not matter.
3751                      $unions[] = "SELECT id AS userid
3752                                     FROM {user}
3753                                    WHERE id NOT IN (SELECT userid
3754                                                       FROM {role_assignments}
3755                                                      WHERE contextid IN ($ctxids)
3756                                                            AND roleid IN (" . implode(',', array_keys($prohibited[$cap])) . "))";
3757  
3758                  } else {
3759                      $unions[] = "SELECT userid
3760                                     FROM {role_assignments}
3761                                    WHERE contextid IN ($ctxids) AND roleid IN (" . implode(',', array_keys($needed[$cap])) . ")
3762                                          AND userid NOT IN (
3763                                              SELECT userid
3764                                                FROM {role_assignments}
3765                                               WHERE contextid IN ($ctxids)
3766                                                     AND roleid IN (" . implode(',', array_keys($prohibited[$cap])) . "))";
3767                  }
3768              }
3769          }
3770  
3771          if (!$everybody) {
3772              if ($unions) {
3773                  $joins[] = "JOIN (
3774                                    SELECT DISTINCT userid
3775                                      FROM (
3776                                              " . implode("\n UNION \n", $unions) . "
3777                                           ) us
3778                                   ) ra ON ra.userid = $useridcolumn";
3779              } else {
3780                  // Only prohibits found - nobody can be matched.
3781                  $wherecond[] = "1 = 2";
3782                  $cannotmatchanyrows = true;
3783              }
3784          }
3785      }
3786  
3787      return new \core\dml\sql_join(implode("\n", $joins), implode(" AND ", $wherecond), $params, $cannotmatchanyrows);
3788  }
3789  
3790  /**
3791   * Who has this capability in this context?
3792   *
3793   * This can be a very expensive call - use sparingly and keep
3794   * the results if you are going to need them again soon.
3795   *
3796   * Note if $fields is empty this function attempts to get u.*
3797   * which can get rather large - and has a serious perf impact
3798   * on some DBs.
3799   *
3800   * @param context $context
3801   * @param string|array $capability - capability name(s)
3802   * @param string $fields - fields to be pulled. The user table is aliased to 'u'. u.id MUST be included.
3803   * @param string $sort - the sort order. Default is lastaccess time.
3804   * @param mixed $limitfrom - number of records to skip (offset)
3805   * @param mixed $limitnum - number of records to fetch
3806   * @param string|array $groups - single group or array of groups - only return
3807   *               users who are in one of these group(s).
3808   * @param string|array $exceptions - list of users to exclude, comma separated or array
3809   * @param bool $notuseddoanything not used any more, admin accounts are never returned
3810   * @param bool $notusedview - use get_enrolled_sql() instead
3811   * @param bool $useviewallgroups if $groups is set the return users who
3812   *               have capability both $capability and moodle/site:accessallgroups
3813   *               in this context, as well as users who have $capability and who are
3814   *               in $groups.
3815   * @return array of user records
3816   */
3817  function get_users_by_capability(context $context, $capability, $fields = '', $sort = '', $limitfrom = '', $limitnum = '',
3818          $groups = '', $exceptions = '', $notuseddoanything = null, $notusedview = null, $useviewallgroups = false) {
3819      global $CFG, $DB;
3820  
3821      // Context is a course page other than the frontpage.
3822      $iscoursepage = $context->contextlevel == CONTEXT_COURSE && $context->instanceid != SITEID;
3823  
3824      // Set up default fields list if necessary.
3825      if (empty($fields)) {
3826          if ($iscoursepage) {
3827              $fields = 'u.*, ul.timeaccess AS lastaccess';
3828          } else {
3829              $fields = 'u.*';
3830          }
3831      } else {
3832          if ($CFG->debugdeveloper && strpos($fields, 'u.*') === false && strpos($fields, 'u.id') === false) {
3833              debugging('u.id must be included in the list of fields passed to get_users_by_capability().', DEBUG_DEVELOPER);
3834          }
3835      }
3836  
3837      // Set up default sort if necessary.
3838      if (empty($sort)) { // default to course lastaccess or just lastaccess
3839          if ($iscoursepage) {
3840              $sort = 'ul.timeaccess';
3841          } else {
3842              $sort = 'u.lastaccess';
3843          }
3844      }
3845  
3846      // Get the bits of SQL relating to capabilities.
3847      $sqljoin = get_with_capability_join($context, $capability, 'u.id');
3848      if ($sqljoin->cannotmatchanyrows) {
3849          return [];
3850      }
3851  
3852      // Prepare query clauses.
3853      $wherecond = [$sqljoin->wheres];
3854      $params    = $sqljoin->params;
3855      $joins     = [$sqljoin->joins];
3856  
3857      // Add user lastaccess JOIN, if required.
3858      if ((strpos($sort, 'ul.timeaccess') === false) and (strpos($fields, 'ul.timeaccess') === false)) {
3859           // Here user_lastaccess is not required MDL-13810.
3860      } else {
3861          if ($iscoursepage) {
3862              $joins[] = "LEFT OUTER JOIN {user_lastaccess} ul ON (ul.userid = u.id AND ul.courseid = {$context->instanceid})";
3863          } else {
3864              throw new coding_exception('Invalid sort in get_users_by_capability(), ul.timeaccess allowed only for course contexts.');
3865          }
3866      }
3867  
3868      // Groups.
3869      if ($groups) {
3870          $groups = (array)$groups;
3871          list($grouptest, $grpparams) = $DB->get_in_or_equal($groups, SQL_PARAMS_NAMED, 'grp');
3872          $joins[] = "LEFT OUTER JOIN (SELECT DISTINCT userid
3873                                         FROM {groups_members}
3874                                        WHERE groupid $grouptest
3875                                      ) gm ON gm.userid = u.id";
3876  
3877          $params = array_merge($params, $grpparams);
3878  
3879          $grouptest = 'gm.userid IS NOT NULL';
3880          if ($useviewallgroups) {
3881              $viewallgroupsusers = get_users_by_capability($context, 'moodle/site:accessallgroups', 'u.id, u.id', '', '', '', '', $exceptions);
3882              if (!empty($viewallgroupsusers)) {
3883                  $grouptest .= ' OR u.id IN (' . implode(',', array_keys($viewallgroupsusers)) . ')';
3884              }
3885          }
3886          $wherecond[] = "($grouptest)";
3887      }
3888  
3889      // User exceptions.
3890      if (!empty($exceptions)) {
3891          $exceptions = (array)$exceptions;
3892          list($exsql, $exparams) = $DB->get_in_or_equal($exceptions, SQL_PARAMS_NAMED, 'exc', false);
3893          $params = array_merge($params, $exparams);
3894          $wherecond[] = "u.id $exsql";
3895      }
3896  
3897      // Collect WHERE conditions and needed joins.
3898      $where = implode(' AND ', $wherecond);
3899      if ($where !== '') {
3900          $where = 'WHERE ' . $where;
3901      }
3902      $joins = implode("\n", $joins);
3903  
3904      // Finally! we have all the bits, run the query.
3905      $sql = "SELECT $fields
3906                FROM {user} u
3907              $joins
3908              $where
3909            ORDER BY $sort";
3910  
3911      return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
3912  }
3913  
3914  /**
3915   * Re-sort a users array based on a sorting policy
3916   *
3917   * Will re-sort a $users results array (from get_users_by_capability(), usually)
3918   * based on a sorting policy. This is to support the odd practice of
3919   * sorting teachers by 'authority', where authority was "lowest id of the role
3920   * assignment".
3921   *
3922   * Will execute 1 database query. Only suitable for small numbers of users, as it
3923   * uses an u.id IN() clause.
3924   *
3925   * Notes about the sorting criteria.
3926   *
3927   * As a default, we cannot rely on role.sortorder because then
3928   * admins/coursecreators will always win. That is why the sane
3929   * rule "is locality matters most", with sortorder as 2nd
3930   * consideration.
3931   *
3932   * If you want role.sortorder, use the 'sortorder' policy, and
3933   * name explicitly what roles you want to cover. It's probably
3934   * a good idea to see what roles have the capabilities you want
3935   * (array_diff() them against roiles that have 'can-do-anything'
3936   * to weed out admin-ish roles. Or fetch a list of roles from
3937   * variables like $CFG->coursecontact .
3938   *
3939   * @param array $users Users array, keyed on userid
3940   * @param context $context
3941   * @param array $roles ids of the roles to include, optional
3942   * @param string $sortpolicy defaults to locality, more about
3943   * @return array sorted copy of the array
3944   */
3945  function sort_by_roleassignment_authority($users, context $context, $roles = array(), $sortpolicy = 'locality') {
3946      global $DB;
3947  
3948      $userswhere = ' ra.userid IN (' . implode(',',array_keys($users)) . ')';
3949      $contextwhere = 'AND ra.contextid IN ('.str_replace('/', ',',substr($context->path, 1)).')';
3950      if (empty($roles)) {
3951          $roleswhere = '';
3952      } else {
3953          $roleswhere = ' AND ra.roleid IN ('.implode(',',$roles).')';
3954      }
3955  
3956      $sql = "SELECT ra.userid
3957                FROM {role_assignments} ra
3958                JOIN {role} r
3959                     ON ra.roleid=r.id
3960                JOIN {context} ctx
3961                     ON ra.contextid=ctx.id
3962               WHERE $userswhere
3963                     $contextwhere
3964                     $roleswhere";
3965  
3966      // Default 'locality' policy -- read PHPDoc notes
3967      // about sort policies...
3968      $orderby = 'ORDER BY '
3969                      .'ctx.depth DESC, '  /* locality wins */
3970                      .'r.sortorder ASC, ' /* rolesorting 2nd criteria */
3971                      .'ra.id';            /* role assignment order tie-breaker */
3972      if ($sortpolicy === 'sortorder') {
3973          $orderby = 'ORDER BY '
3974                          .'r.sortorder ASC, ' /* rolesorting 2nd criteria */
3975                          .'ra.id';            /* role assignment order tie-breaker */
3976      }
3977  
3978      $sortedids = $DB->get_fieldset_sql($sql . $orderby);
3979      $sortedusers = array();
3980      $seen = array();
3981  
3982      foreach ($sortedids as $id) {
3983          // Avoid duplicates
3984          if (isset($seen[$id])) {
3985              continue;
3986          }
3987          $seen[$id] = true;
3988  
3989          // assign
3990          $sortedusers[$id] = $users[$id];
3991      }
3992      return $sortedusers;
3993  }
3994  
3995  /**
3996   * Gets all the users assigned this role in this context or higher
3997   *
3998   * Note that moodle is based on capabilities and it is usually better
3999   * to check permissions than to check role ids as the capabilities
4000   * system is more flexible. If you really need, you can to use this
4001   * function but consider has_capability() as a possible substitute.
4002   *
4003   * All $sort fields are added into $fields if not present there yet.
4004   *
4005   * If $roleid is an array or is empty (all roles) you need to set $fields
4006   * (and $sort by extension) params according to it, as the first field
4007   * returned by the database should be unique (ra.id is the best candidate).
4008   *
4009   * @param int|array $roleid (can also be an array of ints!)
4010   * @param context $context
4011   * @param bool $parent if true, get list of users assigned in higher context too
4012   * @param string $fields fields from user (u.) , role assignment (ra) or role (r.)
4013   * @param string $sort sort from user (u.) , role assignment (ra.) or role (r.).
4014   *      null => use default sort from users_order_by_sql.
4015   * @param bool $all true means all, false means limit to enrolled users
4016   * @param string $group defaults to ''
4017   * @param mixed $limitfrom defaults to ''
4018   * @param mixed $limitnum defaults to ''
4019   * @param string $extrawheretest defaults to ''
4020   * @param array $whereorsortparams any paramter values used by $sort or $extrawheretest.
4021   * @return array
4022   */
4023  function get_role_users($roleid, context $context, $parent = false, $fields = '',
4024          $sort = null, $all = true, $group = '',
4025          $limitfrom = '', $limitnum = '', $extrawheretest = '', $whereorsortparams = array()) {
4026      global $DB;
4027  
4028      if (empty($fields)) {
4029          $userfieldsapi = \core_user\fields::for_name();
4030          $allnames = $userfieldsapi->get_sql('u', false, '', '', false)->selects;
4031          $fields = 'u.id, u.confirmed, u.username, '. $allnames . ', ' .
4032                    'u.maildisplay, u.mailformat, u.maildigest, u.email, u.emailstop, u.city, '.
4033                    'u.country, u.picture, u.idnumber, u.department, u.institution, '.
4034                    'u.lang, u.timezone, u.lastaccess, u.mnethostid, r.name AS rolename, r.sortorder, '.
4035                    'r.shortname AS roleshortname, rn.name AS rolecoursealias';
4036      }
4037  
4038      // Prevent wrong function uses.
4039      if ((empty($roleid) || is_array($roleid)) && strpos($fields, 'ra.id') !== 0) {
4040          debugging('get_role_users() without specifying one single roleid needs to be called prefixing ' .
4041              'role assignments id (ra.id) as unique field, you can use $fields param for it.');
4042  
4043          if (!empty($roleid)) {
4044              // Solving partially the issue when specifying multiple roles.
4045              $users = array();
4046              foreach ($roleid as $id) {
4047                  // Ignoring duplicated keys keeping the first user appearance.
4048                  $users = $users + get_role_users($id, $context, $parent, $fields, $sort, $all, $group,
4049                      $limitfrom, $limitnum, $extrawheretest, $whereorsortparams);
4050              }
4051              return $users;
4052          }
4053      }
4054  
4055      $parentcontexts = '';
4056      if ($parent) {
4057          $parentcontexts = substr($context->path, 1); // kill leading slash
4058          $parentcontexts = str_replace('/', ',', $parentcontexts);
4059          if ($parentcontexts !== '') {
4060              $parentcontexts = ' OR ra.contextid IN ('.$parentcontexts.' )';
4061          }
4062      }
4063  
4064      if ($roleid) {
4065          list($rids, $params) = $DB->get_in_or_equal($roleid, SQL_PARAMS_NAMED, 'r');
4066          $roleselect = "AND ra.roleid $rids";
4067      } else {
4068          $params = array();
4069          $roleselect = '';
4070      }
4071  
4072      if ($coursecontext = $context->get_course_context(false)) {
4073          $params['coursecontext'] = $coursecontext->id;
4074      } else {
4075          $params['coursecontext'] = 0;
4076      }
4077  
4078      if ($group) {
4079          $groupjoin   = "JOIN {groups_members} gm ON gm.userid = u.id";
4080          $groupselect = " AND gm.groupid = :groupid ";
4081          $params['groupid'] = $group;
4082      } else {
4083          $groupjoin   = '';
4084          $groupselect = '';
4085      }
4086  
4087      $params['contextid'] = $context->id;
4088  
4089      if ($extrawheretest) {
4090          $extrawheretest = ' AND ' . $extrawheretest;
4091      }
4092  
4093      if ($whereorsortparams) {
4094          $params = array_merge($params, $whereorsortparams);
4095      }
4096  
4097      if (!$sort) {
4098          list($sort, $sortparams) = users_order_by_sql('u');
4099          $params = array_merge($params, $sortparams);
4100      }
4101  
4102      // Adding the fields from $sort that are not present in $fields.
4103      $sortarray = preg_split('/,\s*/', $sort);
4104      $fieldsarray = preg_split('/,\s*/', $fields);
4105  
4106      // Discarding aliases from the fields.
4107      $fieldnames = array();
4108      foreach ($fieldsarray as $key => $field) {
4109          list($fieldnames[$key]) = explode(' ', $field);
4110      }
4111  
4112      $addedfields = array();
4113      foreach ($sortarray as $sortfield) {
4114          // Throw away any additional arguments to the sort (e.g. ASC/DESC).
4115          list($sortfield) = explode(' ', $sortfield);
4116          list($tableprefix) = explode('.', $sortfield);
4117          $fieldpresent = false;
4118          foreach ($fieldnames as $fieldname) {
4119              if ($fieldname === $sortfield || $fieldname === $tableprefix.'.*') {
4120                  $fieldpresent = true;
4121                  break;
4122              }
4123          }
4124  
4125          if (!$fieldpresent) {
4126              $fieldsarray[] = $sortfield;
4127              $addedfields[] = $sortfield;
4128          }
4129      }
4130  
4131      $fields = implode(', ', $fieldsarray);
4132      if (!empty($addedfields)) {
4133          $addedfields = implode(', ', $addedfields);
4134          debugging('get_role_users() adding '.$addedfields.' to the query result because they were required by $sort but missing in $fields');
4135      }
4136  
4137      if ($all === null) {
4138          // Previously null was used to indicate that parameter was not used.
4139          $all = true;
4140      }
4141      if (!$all and $coursecontext) {
4142          // Do not use get_enrolled_sql() here for performance reasons.
4143          $ejoin = "JOIN {user_enrolments} ue ON ue.userid = u.id
4144                    JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :ecourseid)";
4145          $params['ecourseid'] = $coursecontext->instanceid;
4146      } else {
4147          $ejoin = "";
4148      }
4149  
4150      $sql = "SELECT DISTINCT $fields, ra.roleid
4151                FROM {role_assignments} ra
4152                JOIN {user} u ON u.id = ra.userid
4153                JOIN {role} r ON ra.roleid = r.id
4154              $ejoin
4155           LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
4156          $groupjoin
4157               WHERE (ra.contextid = :contextid $parentcontexts)
4158                     $roleselect
4159                     $groupselect
4160                     $extrawheretest
4161            ORDER BY $sort";                  // join now so that we can just use fullname() later
4162  
4163      return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
4164  }
4165  
4166  /**
4167   * Counts all the users assigned this role in this context or higher
4168   *
4169   * @param int|array $roleid either int or an array of ints
4170   * @param context $context
4171   * @param bool $parent if true, get list of users assigned in higher context too
4172   * @return int Returns the result count
4173   */
4174  function count_role_users($roleid, context $context, $parent = false) {
4175      global $DB;
4176  
4177      if ($parent) {
4178          if ($contexts = $context->get_parent_context_ids()) {
4179              $parentcontexts = ' OR r.contextid IN ('.implode(',', $contexts).')';
4180          } else {
4181              $parentcontexts = '';
4182          }
4183      } else {
4184          $parentcontexts = '';
4185      }
4186  
4187      if ($roleid) {
4188          list($rids, $params) = $DB->get_in_or_equal($roleid, SQL_PARAMS_QM);
4189          $roleselect = "AND r.roleid $rids";
4190      } else {
4191          $params = array();
4192          $roleselect = '';
4193      }
4194  
4195      array_unshift($params, $context->id);
4196  
4197      $sql = "SELECT COUNT(DISTINCT u.id)
4198                FROM {role_assignments} r
4199                JOIN {user} u ON u.id = r.userid
4200               WHERE (r.contextid = ? $parentcontexts)
4201                     $roleselect
4202                     AND u.deleted = 0";
4203  
4204      return $DB->count_records_sql($sql, $params);
4205  }
4206  
4207  /**
4208   * This function gets the list of course and course category contexts that this user has a particular capability in.
4209   *
4210   * It is now reasonably efficient, but bear in mind that if there are users who have the capability
4211   * everywhere, it may return an array of all contexts.
4212   *
4213   * @param string $capability Capability in question
4214   * @param int $userid User ID or null for current user
4215   * @param bool $getcategories Wether to return also course_categories
4216   * @param bool $doanything True if 'doanything' is permitted (default)
4217   * @param string $coursefieldsexceptid Leave blank if you only need 'id' in the course records;
4218   *   otherwise use a comma-separated list of the fields you require, not including id.
4219   *   Add ctxid, ctxpath, ctxdepth etc to return course context information for preloading.
4220   * @param string $categoryfieldsexceptid Leave blank if you only need 'id' in the course records;
4221   *   otherwise use a comma-separated list of the fields you require, not including id.
4222   *   Add ctxid, ctxpath, ctxdepth etc to return course context information for preloading.
4223   * @param string $courseorderby If set, use a comma-separated list of fields from course
4224   *   table with sql modifiers (DESC) if needed
4225   * @param string $categoryorderby If set, use a comma-separated list of fields from course_category
4226   *   table with sql modifiers (DESC) if needed
4227   * @param int $limit Limit the number of courses to return on success. Zero equals all entries.
4228   * @return array Array of categories and courses.
4229   */
4230  function get_user_capability_contexts(string $capability, bool $getcategories, $userid = null, $doanything = true,
4231                                        $coursefieldsexceptid = '', $categoryfieldsexceptid = '', $courseorderby = '',
4232                                        $categoryorderby = '', $limit = 0): array {
4233      global $DB, $USER;
4234  
4235      // Default to current user.
4236      if (!$userid) {
4237          $userid = $USER->id;
4238      }
4239  
4240      if (!$capinfo = get_capability_info($capability)) {
4241          debugging('Capability "'.$capability.'" was not found! This has to be fixed in code.');
4242          return [false, false];
4243      }
4244  
4245      if ($doanything && is_siteadmin($userid)) {
4246          // If the user is a site admin and $doanything is enabled then there is no need to restrict
4247          // the list of courses.
4248          $contextlimitsql = '';
4249          $contextlimitparams = [];
4250      } else {
4251          // Gets SQL to limit contexts ('x' table) to those where the user has this capability.
4252          list ($contextlimitsql, $contextlimitparams) = \core\access\get_user_capability_course_helper::get_sql(
4253              $userid, $capinfo->name);
4254          if (!$contextlimitsql) {
4255              // If the does not have this capability in any context, return false without querying.
4256              return [false, false];
4257          }
4258  
4259          $contextlimitsql = 'WHERE' . $contextlimitsql;
4260      }
4261  
4262      $categories = [];
4263      if ($getcategories) {
4264          $fieldlist = \core\access\get_user_capability_course_helper::map_fieldnames($categoryfieldsexceptid);
4265          if ($categoryorderby) {
4266              $fields = explode(',', $categoryorderby);
4267              $categoryorderby = '';
4268              foreach ($fields as $field) {
4269                  if ($categoryorderby) {
4270                      $categoryorderby .= ',';
4271                  }
4272                  $categoryorderby .= 'c.'.$field;
4273              }
4274              $categoryorderby = 'ORDER BY '.$categoryorderby;
4275          }
4276          $rs = $DB->get_recordset_sql("
4277              SELECT c.id $fieldlist
4278                FROM {course_categories} c
4279                 JOIN {context} x ON c.id = x.instanceid AND x.contextlevel = ?
4280              $contextlimitsql
4281              $categoryorderby", array_merge([CONTEXT_COURSECAT], $contextlimitparams));
4282          $basedlimit = $limit;
4283          foreach ($rs as $category) {
4284              $categories[] = $category;
4285              $basedlimit--;
4286              if ($basedlimit == 0) {
4287                  break;
4288              }
4289          }
4290          $rs->close();
4291      }
4292  
4293      $courses = [];
4294      $fieldlist = \core\access\get_user_capability_course_helper::map_fieldnames($coursefieldsexceptid);
4295      if ($courseorderby) {
4296          $fields = explode(',', $courseorderby);
4297          $courseorderby = '';
4298          foreach ($fields as $field) {
4299              if ($courseorderby) {
4300                  $courseorderby .= ',';
4301              }
4302              $courseorderby .= 'c.'.$field;
4303          }
4304          $courseorderby = 'ORDER BY '.$courseorderby;
4305      }
4306      $rs = $DB->get_recordset_sql("
4307              SELECT c.id $fieldlist
4308                FROM {course} c
4309                 JOIN {context} x ON c.id = x.instanceid AND x.contextlevel = ?
4310              $contextlimitsql
4311              $courseorderby", array_merge([CONTEXT_COURSE], $contextlimitparams));
4312      foreach ($rs as $course) {
4313          $courses[] = $course;
4314          $limit--;
4315          if ($limit == 0) {
4316              break;
4317          }
4318      }
4319      $rs->close();
4320      return [$categories, $courses];
4321  }
4322  
4323  /**
4324   * This function gets the list of courses that this user has a particular capability in.
4325   *
4326   * It is now reasonably efficient, but bear in mind that if there are users who have the capability
4327   * everywhere, it may return an array of all courses.
4328   *
4329   * @param string $capability Capability in question
4330   * @param int $userid User ID or null for current user
4331   * @param bool $doanything True if 'doanything' is permitted (default)
4332   * @param string $fieldsexceptid Leave blank if you only need 'id' in the course records;
4333   *   otherwise use a comma-separated list of the fields you require, not including id.
4334   *   Add ctxid, ctxpath, ctxdepth etc to return course context information for preloading.
4335   * @param string $orderby If set, use a comma-separated list of fields from course
4336   *   table with sql modifiers (DESC) if needed
4337   * @param int $limit Limit the number of courses to return on success. Zero equals all entries.
4338   * @return array|bool Array of courses, if none found false is returned.
4339   */
4340  function get_user_capability_course($capability, $userid = null, $doanything = true, $fieldsexceptid = '',
4341                                      $orderby = '', $limit = 0) {
4342      list($categories, $courses) = get_user_capability_contexts(
4343          $capability,
4344          false,
4345          $userid,
4346          $doanything,
4347          $fieldsexceptid,
4348          '',
4349          $orderby,
4350          '',
4351          $limit
4352      );
4353      return $courses;
4354  }
4355  
4356  /**
4357   * Switches the current user to another role for the current session and only
4358   * in the given context.
4359   *
4360   * The caller *must* check
4361   * - that this op is allowed
4362   * - that the requested role can be switched to in this context (use get_switchable_roles)
4363   * - that the requested role is NOT $CFG->defaultuserroleid
4364   *
4365   * To "unswitch" pass 0 as the roleid.
4366   *
4367   * This function *will* modify $USER->access - beware
4368   *
4369   * @param integer $roleid the role to switch to.
4370   * @param context $context the context in which to perform the switch.
4371   * @return bool success or failure.
4372   */
4373  function role_switch($roleid, context $context) {
4374      global $USER;
4375  
4376      // Add the ghost RA to $USER->access as $USER->access['rsw'][$path] = $roleid.
4377      // To un-switch just unset($USER->access['rsw'][$path]).
4378      //
4379      // Note: it is not possible to switch to roles that do not have course:view
4380  
4381      if (!isset($USER->access)) {
4382          load_all_capabilities();
4383      }
4384  
4385      // Make sure that course index is refreshed.
4386      if ($coursecontext = $context->get_course_context()) {
4387          core_courseformat\base::session_cache_reset(get_course($coursecontext->instanceid));
4388      }
4389  
4390      // Add the switch RA
4391      if ($roleid == 0) {
4392          unset($USER->access['rsw'][$context->path]);
4393          return true;
4394      }
4395  
4396      $USER->access['rsw'][$context->path] = $roleid;
4397  
4398      return true;
4399  }
4400  
4401  /**
4402   * Checks if the user has switched roles within the given course.
4403   *
4404   * Note: You can only switch roles within the course, hence it takes a course id
4405   * rather than a context. On that note Petr volunteered to implement this across
4406   * all other contexts, all requests for this should be forwarded to him ;)
4407   *
4408   * @param int $courseid The id of the course to check
4409   * @return bool True if the user has switched roles within the course.
4410   */
4411  function is_role_switched($courseid) {
4412      global $USER;
4413      $context = context_course::instance($courseid, MUST_EXIST);
4414      return (!empty($USER->access['rsw'][$context->path]));
4415  }
4416  
4417  /**
4418   * Get any role that has an override on exact context
4419   *
4420   * @param context $context The context to check
4421   * @return array An array of roles
4422   */
4423  function get_roles_with_override_on_context(context $context) {
4424      global $DB;
4425  
4426      return $DB->get_records_sql("SELECT r.*
4427                                     FROM {role_capabilities} rc, {role} r
4428                                    WHERE rc.roleid = r.id AND rc.contextid = ?",
4429                                  array($context->id));
4430  }
4431  
4432  /**
4433   * Get all capabilities for this role on this context (overrides)
4434   *
4435   * @param stdClass $role
4436   * @param context $context
4437   * @return array
4438   */
4439  function get_capabilities_from_role_on_context($role, context $context) {
4440      global $DB;
4441  
4442      return $DB->get_records_sql("SELECT *
4443                                     FROM {role_capabilities}
4444                                    WHERE contextid = ? AND roleid = ?",
4445                                  array($context->id, $role->id));
4446  }
4447  
4448  /**
4449   * Find all user assignment of users for this role, on this context
4450   *
4451   * @param stdClass $role
4452   * @param context $context
4453   * @return array
4454   */
4455  function get_users_from_role_on_context($role, context $context) {
4456      global $DB;
4457  
4458      return $DB->get_records_sql("SELECT *
4459                                     FROM {role_assignments}
4460                                    WHERE contextid = ? AND roleid = ?",
4461                                  array($context->id, $role->id));
4462  }
4463  
4464  /**
4465   * Simple function returning a boolean true if user has roles
4466   * in context or parent contexts, otherwise false.
4467   *
4468   * @param int $userid
4469   * @param int $roleid
4470   * @param int $contextid empty means any context
4471   * @return bool
4472   */
4473  function user_has_role_assignment($userid, $roleid, $contextid = 0) {
4474      global $DB;
4475  
4476      if ($contextid) {
4477          if (!$context = context::instance_by_id($contextid, IGNORE_MISSING)) {
4478              return false;
4479          }
4480          $parents = $context->get_parent_context_ids(true);
4481          list($contexts, $params) = $DB->get_in_or_equal($parents, SQL_PARAMS_NAMED, 'r');
4482          $params['userid'] = $userid;
4483          $params['roleid'] = $roleid;
4484  
4485          $sql = "SELECT COUNT(ra.id)
4486                    FROM {role_assignments} ra
4487                   WHERE ra.userid = :userid AND ra.roleid = :roleid AND ra.contextid $contexts";
4488  
4489          $count = $DB->get_field_sql($sql, $params);
4490          return ($count > 0);
4491  
4492      } else {
4493          return $DB->record_exists('role_assignments', array('userid'=>$userid, 'roleid'=>$roleid));
4494      }
4495  }
4496  
4497  /**
4498   * Get localised role name or alias if exists and format the text.
4499   *
4500   * @param stdClass $role role object
4501   *      - optional 'coursealias' property should be included for performance reasons if course context used
4502   *      - description property is not required here
4503   * @param context|bool $context empty means system context
4504   * @param int $rolenamedisplay type of role name
4505   * @return string localised role name or course role name alias
4506   */
4507  function role_get_name(stdClass $role, $context = null, $rolenamedisplay = ROLENAME_ALIAS) {
4508      global $DB;
4509  
4510      if ($rolenamedisplay == ROLENAME_SHORT) {
4511          return $role->shortname;
4512      }
4513  
4514      if (!$context or !$coursecontext = $context->get_course_context(false)) {
4515          $coursecontext = null;
4516      }
4517  
4518      if ($coursecontext and !property_exists($role, 'coursealias') and ($rolenamedisplay == ROLENAME_ALIAS or $rolenamedisplay == ROLENAME_BOTH or $rolenamedisplay == ROLENAME_ALIAS_RAW)) {
4519          $role = clone($role); // Do not modify parameters.
4520          if ($r = $DB->get_record('role_names', array('roleid'=>$role->id, 'contextid'=>$coursecontext->id))) {
4521              $role->coursealias = $r->name;
4522          } else {
4523              $role->coursealias = null;
4524          }
4525      }
4526  
4527      if ($rolenamedisplay == ROLENAME_ALIAS_RAW) {
4528          if ($coursecontext) {
4529              return $role->coursealias;
4530          } else {
4531              return null;
4532          }
4533      }
4534  
4535      if (trim($role->name) !== '') {
4536          // For filtering always use context where was the thing defined - system for roles here.
4537          $original = format_string($role->name, true, array('context'=>context_system::instance()));
4538  
4539      } else {
4540          // Empty role->name means we want to see localised role name based on shortname,
4541          // only default roles are supposed to be localised.
4542          switch ($role->shortname) {
4543              case 'manager':         $original = get_string('manager', 'role'); break;
4544              case 'coursecreator':   $original = get_string('coursecreators'); break;
4545              case 'editingteacher':  $original = get_string('defaultcourseteacher'); break;
4546              case 'teacher':         $original = get_string('noneditingteacher'); break;
4547              case 'student':         $original = get_string('defaultcoursestudent'); break;
4548              case 'guest':           $original = get_string('guest'); break;
4549              case 'user':            $original = get_string('authenticateduser'); break;
4550              case 'frontpage':       $original = get_string('frontpageuser', 'role'); break;
4551              // We should not get here, the role UI should require the name for custom roles!
4552              default:                $original = $role->shortname; break;
4553          }
4554      }
4555  
4556      if ($rolenamedisplay == ROLENAME_ORIGINAL) {
4557          return $original;
4558      }
4559  
4560      if ($rolenamedisplay == ROLENAME_ORIGINALANDSHORT) {
4561          return "$original ($role->shortname)";
4562      }
4563  
4564      if ($rolenamedisplay == ROLENAME_ALIAS) {
4565          if ($coursecontext && $role->coursealias && trim($role->coursealias) !== '') {
4566              return format_string($role->coursealias, true, array('context'=>$coursecontext));
4567          } else {
4568              return $original;
4569          }
4570      }
4571  
4572      if ($rolenamedisplay == ROLENAME_BOTH) {
4573          if ($coursecontext && $role->coursealias && trim($role->coursealias) !== '') {
4574              return format_string($role->coursealias, true, array('context'=>$coursecontext)) . " ($original)";
4575          } else {
4576              return $original;
4577          }
4578      }
4579  
4580      throw new coding_exception('Invalid $rolenamedisplay parameter specified in role_get_name()');
4581  }
4582  
4583  /**
4584   * Returns localised role description if available.
4585   * If the name is empty it tries to find the default role name using
4586   * hardcoded list of default role names or other methods in the future.
4587   *
4588   * @param stdClass $role
4589   * @return string localised role name
4590   */
4591  function role_get_description(stdClass $role) {
4592      if (!html_is_blank($role->description)) {
4593          return format_text($role->description, FORMAT_HTML, array('context'=>context_system::instance()));
4594      }
4595  
4596      switch ($role->shortname) {
4597          case 'manager':         return get_string('managerdescription', 'role');
4598          case 'coursecreator':   return get_string('coursecreatorsdescription');
4599          case 'editingteacher':  return get_string('defaultcourseteacherdescription');
4600          case 'teacher':         return get_string('noneditingteacherdescription');
4601          case 'student':         return get_string('defaultcoursestudentdescription');
4602          case 'guest':           return get_string('guestdescription');
4603          case 'user':            return get_string('authenticateduserdescription');
4604          case 'frontpage':       return get_string('frontpageuserdescription', 'role');
4605          default:                return '';
4606      }
4607  }
4608  
4609  /**
4610   * Get all the localised role names for a context.
4611   *
4612   * In new installs default roles have empty names, this function
4613   * add localised role names using current language pack.
4614   *
4615   * @param context $context the context, null means system context
4616   * @param array of role objects with a ->localname field containing the context-specific role name.
4617   * @param int $rolenamedisplay
4618   * @param bool $returnmenu true means id=>localname, false means id=>rolerecord
4619   * @return array Array of context-specific role names, or role objects with a ->localname field added.
4620   */
4621  function role_get_names(context $context = null, $rolenamedisplay = ROLENAME_ALIAS, $returnmenu = null) {
4622      return role_fix_names(get_all_roles($context), $context, $rolenamedisplay, $returnmenu);
4623  }
4624  
4625  /**
4626   * Prepare list of roles for display, apply aliases and localise default role names.
4627   *
4628   * @param array $roleoptions array roleid => roleobject (with optional coursealias), strings are accepted for backwards compatibility only
4629   * @param context $context the context, null means system context
4630   * @param int $rolenamedisplay
4631   * @param bool $returnmenu null means keep the same format as $roleoptions, true means id=>localname, false means id=>rolerecord
4632   * @return array Array of context-specific role names, or role objects with a ->localname field added.
4633   */
4634  function role_fix_names($roleoptions, context $context = null, $rolenamedisplay = ROLENAME_ALIAS, $returnmenu = null) {
4635      global $DB;
4636  
4637      if (empty($roleoptions)) {
4638          return array();
4639      }
4640  
4641      if (!$context or !$coursecontext = $context->get_course_context(false)) {
4642          $coursecontext = null;
4643      }
4644  
4645      // We usually need all role columns...
4646      $first = reset($roleoptions);
4647      if ($returnmenu === null) {
4648          $returnmenu = !is_object($first);
4649      }
4650  
4651      if (!is_object($first) or !property_exists($first, 'shortname')) {
4652          $allroles = get_all_roles($context);
4653          foreach ($roleoptions as $rid => $unused) {
4654              $roleoptions[$rid] = $allroles[$rid];
4655          }
4656      }
4657  
4658      // Inject coursealias if necessary.
4659      if ($coursecontext and ($rolenamedisplay == ROLENAME_ALIAS_RAW or $rolenamedisplay == ROLENAME_ALIAS or $rolenamedisplay == ROLENAME_BOTH)) {
4660          $first = reset($roleoptions);
4661          if (!property_exists($first, 'coursealias')) {
4662              $aliasnames = $DB->get_records('role_names', array('contextid'=>$coursecontext->id));
4663              foreach ($aliasnames as $alias) {
4664                  if (isset($roleoptions[$alias->roleid])) {
4665                      $roleoptions[$alias->roleid]->coursealias = $alias->name;
4666                  }
4667              }
4668          }
4669      }
4670  
4671      // Add localname property.
4672      foreach ($roleoptions as $rid => $role) {
4673          $roleoptions[$rid]->localname = role_get_name($role, $coursecontext, $rolenamedisplay);
4674      }
4675  
4676      if (!$returnmenu) {
4677          return $roleoptions;
4678      }
4679  
4680      $menu = array();
4681      foreach ($roleoptions as $rid => $role) {
4682          $menu[$rid] = $role->localname;
4683      }
4684  
4685      return $menu;
4686  }
4687  
4688  /**
4689   * Aids in detecting if a new line is required when reading a new capability
4690   *
4691   * This function helps admin/roles/manage.php etc to detect if a new line should be printed
4692   * when we read in a new capability.
4693   * Most of the time, if the 2 components are different we should print a new line, (e.g. course system->rss client)
4694   * but when we are in grade, all reports/import/export capabilities should be together
4695   *
4696   * @param stdClass $cap component string a
4697   * @param string $comp component string b
4698   * @param int $contextlevel
4699   * @return bool whether 2 component are in different "sections"
4700   */
4701  function component_level_changed($cap, $comp, $contextlevel) {
4702  
4703      if (strstr($cap->component, '/') && strstr($comp, '/')) {
4704          $compsa = explode('/', $cap->component);
4705          $compsb = explode('/', $comp);
4706  
4707          // list of system reports
4708          if (($compsa[0] == 'report') && ($compsb[0] == 'report')) {
4709              return false;
4710          }
4711  
4712          // we are in gradebook, still
4713          if (($compsa[0] == 'gradeexport' || $compsa[0] == 'gradeimport' || $compsa[0] == 'gradereport') &&
4714              ($compsb[0] == 'gradeexport' || $compsb[0] == 'gradeimport' || $compsb[0] == 'gradereport')) {
4715              return false;
4716          }
4717  
4718          if (($compsa[0] == 'coursereport') && ($compsb[0] == 'coursereport')) {
4719              return false;
4720          }
4721      }
4722  
4723      return ($cap->component != $comp || $cap->contextlevel != $contextlevel);
4724  }
4725  
4726  /**
4727   * Fix the roles.sortorder field in the database, so it contains sequential integers,
4728   * and return an array of roleids in order.
4729   *
4730   * @param array $allroles array of roles, as returned by get_all_roles();
4731   * @return array $role->sortorder =-> $role->id with the keys in ascending order.
4732   */
4733  function fix_role_sortorder($allroles) {
4734      global $DB;
4735  
4736      $rolesort = array();
4737      $i = 0;
4738      foreach ($allroles as $role) {
4739          $rolesort[$i] = $role->id;
4740          if ($role->sortorder != $i) {
4741              $r = new stdClass();
4742              $r->id = $role->id;
4743              $r->sortorder = $i;
4744              $DB->update_record('role', $r);
4745              $allroles[$role->id]->sortorder = $i;
4746          }
4747          $i++;
4748      }
4749      return $rolesort;
4750  }
4751  
4752  /**
4753   * Switch the sort order of two roles (used in admin/roles/manage.php).
4754   *
4755   * @param stdClass $first The first role. Actually, only ->sortorder is used.
4756   * @param stdClass $second The second role. Actually, only ->sortorder is used.
4757   * @return boolean success or failure
4758   */
4759  function switch_roles($first, $second) {
4760      global $DB;
4761      $temp = $DB->get_field('role', 'MAX(sortorder) + 1', array());
4762      $result = $DB->set_field('role', 'sortorder', $temp, array('sortorder' => $first->sortorder));
4763      $result = $result && $DB->set_field('role', 'sortorder', $first->sortorder, array('sortorder' => $second->sortorder));
4764      $result = $result && $DB->set_field('role', 'sortorder', $second->sortorder, array('sortorder' => $temp));
4765      return $result;
4766  }
4767  
4768  /**
4769   * Duplicates all the base definitions of a role
4770   *
4771   * @param stdClass $sourcerole role to copy from
4772   * @param int $targetrole id of role to copy to
4773   */
4774  function role_cap_duplicate($sourcerole, $targetrole) {
4775      global $DB;
4776  
4777      $systemcontext = context_system::instance();
4778      $caps = $DB->get_records_sql("SELECT *
4779                                      FROM {role_capabilities}
4780                                     WHERE roleid = ? AND contextid = ?",
4781                                   array($sourcerole->id, $systemcontext->id));
4782      // adding capabilities
4783      foreach ($caps as $cap) {
4784          unset($cap->id);
4785          $cap->roleid = $targetrole;
4786          $DB->insert_record('role_capabilities', $cap);
4787      }
4788  
4789      // Reset any cache of this role, including MUC.
4790      accesslib_clear_role_cache($targetrole);
4791  }
4792  
4793  /**
4794   * Returns two lists, this can be used to find out if user has capability.
4795   * Having any needed role and no forbidden role in this context means
4796   * user has this capability in this context.
4797   * Use get_role_names_with_cap_in_context() if you need role names to display in the UI
4798   *
4799   * @param stdClass $context
4800   * @param string $capability
4801   * @return array($neededroles, $forbiddenroles)
4802   */
4803  function get_roles_with_cap_in_context($context, $capability) {
4804      global $DB;
4805  
4806      $ctxids = trim($context->path, '/'); // kill leading slash
4807      $ctxids = str_replace('/', ',', $ctxids);
4808  
4809      $sql = "SELECT rc.id, rc.roleid, rc.permission, ctx.depth
4810                FROM {role_capabilities} rc
4811                JOIN {context} ctx ON ctx.id = rc.contextid
4812                JOIN {capabilities} cap ON rc.capability = cap.name
4813               WHERE rc.capability = :cap AND ctx.id IN ($ctxids)
4814            ORDER BY rc.roleid ASC, ctx.depth DESC";
4815      $params = array('cap'=>$capability);
4816  
4817      if (!$capdefs = $DB->get_records_sql($sql, $params)) {
4818          // no cap definitions --> no capability
4819          return array(array(), array());
4820      }
4821  
4822      $forbidden = array();
4823      $needed    = array();
4824      foreach ($capdefs as $def) {
4825          if (isset($forbidden[$def->roleid])) {
4826              continue;
4827          }
4828          if ($def->permission == CAP_PROHIBIT) {
4829              $forbidden[$def->roleid] = $def->roleid;
4830              unset($needed[$def->roleid]);
4831              continue;
4832          }
4833          if (!isset($needed[$def->roleid])) {
4834              if ($def->permission == CAP_ALLOW) {
4835                  $needed[$def->roleid] = true;
4836              } else if ($def->permission == CAP_PREVENT) {
4837                  $needed[$def->roleid] = false;
4838              }
4839          }
4840      }
4841      unset($capdefs);
4842  
4843      // remove all those roles not allowing
4844      foreach ($needed as $key=>$value) {
4845          if (!$value) {
4846              unset($needed[$key]);
4847          } else {
4848              $needed[$key] = $key;
4849          }
4850      }
4851  
4852      return array($needed, $forbidden);
4853  }
4854  
4855  /**
4856   * Returns an array of role IDs that have ALL of the the supplied capabilities
4857   * Uses get_roles_with_cap_in_context(). Returns $allowed minus $forbidden
4858   *
4859   * @param stdClass $context
4860   * @param array $capabilities An array of capabilities
4861   * @return array of roles with all of the required capabilities
4862   */
4863  function get_roles_with_caps_in_context($context, $capabilities) {
4864      $neededarr = array();
4865      $forbiddenarr = array();
4866      foreach ($capabilities as $caprequired) {
4867          list($neededarr[], $forbiddenarr[]) = get_roles_with_cap_in_context($context, $caprequired);
4868      }
4869  
4870      $rolesthatcanrate = array();
4871      if (!empty($neededarr)) {
4872          foreach ($neededarr as $needed) {
4873              if (empty($rolesthatcanrate)) {
4874                  $rolesthatcanrate = $needed;
4875              } else {
4876                  //only want roles that have all caps
4877                  $rolesthatcanrate = array_intersect_key($rolesthatcanrate,$needed);
4878              }
4879          }
4880      }
4881      if (!empty($forbiddenarr) && !empty($rolesthatcanrate)) {
4882          foreach ($forbiddenarr as $forbidden) {
4883             //remove any roles that are forbidden any of the caps
4884             $rolesthatcanrate = array_diff($rolesthatcanrate, $forbidden);
4885          }
4886      }
4887      return $rolesthatcanrate;
4888  }
4889  
4890  /**
4891   * Returns an array of role names that have ALL of the the supplied capabilities
4892   * Uses get_roles_with_caps_in_context(). Returns $allowed minus $forbidden
4893   *
4894   * @param stdClass $context
4895   * @param array $capabilities An array of capabilities
4896   * @return array of roles with all of the required capabilities
4897   */
4898  function get_role_names_with_caps_in_context($context, $capabilities) {
4899      global $DB;
4900  
4901      $rolesthatcanrate = get_roles_with_caps_in_context($context, $capabilities);
4902      $allroles = $DB->get_records('role', null, 'sortorder DESC');
4903  
4904      $roles = array();
4905      foreach ($rolesthatcanrate as $r) {
4906          $roles[$r] = $allroles[$r];
4907      }
4908  
4909      return role_fix_names($roles, $context, ROLENAME_ALIAS, true);
4910  }
4911  
4912  /**
4913   * This function verifies the prohibit comes from this context
4914   * and there are no more prohibits in parent contexts.
4915   *
4916   * @param int $roleid
4917   * @param context $context
4918   * @param string $capability name
4919   * @return bool
4920   */
4921  function prohibit_is_removable($roleid, context $context, $capability) {
4922      global $DB;
4923  
4924      $ctxids = trim($context->path, '/'); // kill leading slash
4925      $ctxids = str_replace('/', ',', $ctxids);
4926  
4927      $params = array('roleid'=>$roleid, 'cap'=>$capability, 'prohibit'=>CAP_PROHIBIT);
4928  
4929      $sql = "SELECT ctx.id
4930                FROM {role_capabilities} rc
4931                JOIN {context} ctx ON ctx.id = rc.contextid
4932                JOIN {capabilities} cap ON rc.capability = cap.name
4933               WHERE rc.roleid = :roleid AND rc.permission = :prohibit AND rc.capability = :cap AND ctx.id IN ($ctxids)
4934            ORDER BY ctx.depth DESC";
4935  
4936      if (!$prohibits = $DB->get_records_sql($sql, $params)) {
4937          // no prohibits == nothing to remove
4938          return true;
4939      }
4940  
4941      if (count($prohibits) > 1) {
4942          // more prohibits can not be removed
4943          return false;
4944      }
4945  
4946      return !empty($prohibits[$context->id]);
4947  }
4948  
4949  /**
4950   * More user friendly role permission changing,
4951   * it should produce as few overrides as possible.
4952   *
4953   * @param int $roleid
4954   * @param stdClass|context $context
4955   * @param string $capname capability name
4956   * @param int $permission
4957   * @return void
4958   */
4959  function role_change_permission($roleid, $context, $capname, $permission) {
4960      global $DB;
4961  
4962      if ($permission == CAP_INHERIT) {
4963          unassign_capability($capname, $roleid, $context->id);
4964          return;
4965      }
4966  
4967      $ctxids = trim($context->path, '/'); // kill leading slash
4968      $ctxids = str_replace('/', ',', $ctxids);
4969  
4970      $params = array('roleid'=>$roleid, 'cap'=>$capname);
4971  
4972      $sql = "SELECT ctx.id, rc.permission, ctx.depth
4973                FROM {role_capabilities} rc
4974                JOIN {context} ctx ON ctx.id = rc.contextid
4975                JOIN {capabilities} cap ON rc.capability = cap.name
4976               WHERE rc.roleid = :roleid AND rc.capability = :cap AND ctx.id IN ($ctxids)
4977            ORDER BY ctx.depth DESC";
4978  
4979      if ($existing = $DB->get_records_sql($sql, $params)) {
4980          foreach ($existing as $e) {
4981              if ($e->permission == CAP_PROHIBIT) {
4982                  // prohibit can not be overridden, no point in changing anything
4983                  return;
4984              }
4985          }
4986          $lowest = array_shift($existing);
4987          if ($lowest->permission == $permission) {
4988              // permission already set in this context or parent - nothing to do
4989              return;
4990          }
4991          if ($existing) {
4992              $parent = array_shift($existing);
4993              if ($parent->permission == $permission) {
4994                  // permission already set in parent context or parent - just unset in this context
4995                  // we do this because we want as few overrides as possible for performance reasons
4996                  unassign_capability($capname, $roleid, $context->id);
4997                  return;
4998              }
4999          }
5000  
5001      } else {
5002          if ($permission == CAP_PREVENT) {
5003              // nothing means role does not have permission
5004              return;
5005          }
5006      }
5007  
5008      // assign the needed capability
5009      assign_capability($capname, $permission, $roleid, $context->id, true);
5010  }
5011  
5012  /* ============== DEPRECATED FUNCTIONS ========================================== */
5013  // Old context related functions were deprecated in 2.0, it is recommended
5014  // to use context classes in new code. Old function can be used when
5015  // creating patches that are supposed to be backported to older stable branches.
5016  // These deprecated functions will not be removed in near future,
5017  // before removing devs will be warned with a debugging message first,
5018  // then we will add error message and only after that we can remove the functions
5019  // completely.
5020  
5021  // Renamed context class do not use lib/db/renamedclasses.php because we cannot
5022  // ask everybody to update all code, so let's keep this here for the next few decades.
5023  // Another benefit is that PHPStorm understands this and stops complaining.
5024  class_alias(core\context_helper::class, 'context_helper', true);
5025  class_alias(core\context::class, 'context', true);
5026  class_alias(core\context\block::class, 'context_block');
5027  class_alias(core\context\course::class, 'context_course', true);
5028  class_alias(core\context\coursecat::class, 'context_coursecat');
5029  class_alias(core\context\module::class, 'context_module', true);
5030  class_alias(core\context\system::class, 'context_system', true);
5031  class_alias(core\context\user::class, 'context_user', true);
5032  
5033  /**
5034   * Runs get_records select on context table and returns the result
5035   * Does get_records_select on the context table, and returns the results ordered
5036   * by contextlevel, and then the natural sort order within each level.
5037   * for the purpose of $select, you need to know that the context table has been
5038   * aliased to ctx, so for example, you can call get_sorted_contexts('ctx.depth = 3');
5039   *
5040   * @param string $select the contents of the WHERE clause. Remember to do ctx.fieldname.
5041   * @param array $params any parameters required by $select.
5042   * @return array the requested context records.
5043   */
5044  function get_sorted_contexts($select, $params = array()) {
5045  
5046      //TODO: we should probably rewrite all the code that is using this thing, the trouble is we MUST NOT modify the context instances...
5047  
5048      global $DB;
5049      if ($select) {
5050          $select = 'WHERE ' . $select;
5051      }
5052      return $DB->get_records_sql("
5053              SELECT ctx.*
5054                FROM {context} ctx
5055                LEFT JOIN {user} u ON ctx.contextlevel = " . CONTEXT_USER . " AND u.id = ctx.instanceid
5056                LEFT JOIN {course_categories} cat ON ctx.contextlevel = " . CONTEXT_COURSECAT . " AND cat.id = ctx.instanceid
5057                LEFT JOIN {course} c ON ctx.contextlevel = " . CONTEXT_COURSE . " AND c.id = ctx.instanceid
5058                LEFT JOIN {course_modules} cm ON ctx.contextlevel = " . CONTEXT_MODULE . " AND cm.id = ctx.instanceid
5059                LEFT JOIN {block_instances} bi ON ctx.contextlevel = " . CONTEXT_BLOCK . " AND bi.id = ctx.instanceid
5060             $select
5061            ORDER BY ctx.contextlevel, bi.defaultregion, COALESCE(cat.sortorder, c.sortorder, cm.section, bi.defaultweight), u.lastname, u.firstname, cm.id
5062              ", $params);
5063  }
5064  
5065  /**
5066   * Given context and array of users, returns array of users whose enrolment status is suspended,
5067   * or enrolment has expired or has not started. Also removes those users from the given array
5068   *
5069   * @param context $context context in which suspended users should be extracted.
5070   * @param array $users list of users.
5071   * @param array $ignoreusers array of user ids to ignore, e.g. guest
5072   * @return array list of suspended users.
5073   */
5074  function extract_suspended_users($context, &$users, $ignoreusers=array()) {
5075      global $DB;
5076  
5077      // Get active enrolled users.
5078      list($sql, $params) = get_enrolled_sql($context, null, null, true);
5079      $activeusers = $DB->get_records_sql($sql, $params);
5080  
5081      // Move suspended users to a separate array & remove from the initial one.
5082      $susers = array();
5083      if (sizeof($activeusers)) {
5084          foreach ($users as $userid => $user) {
5085              if (!array_key_exists($userid, $activeusers) && !in_array($userid, $ignoreusers)) {
5086                  $susers[$userid] = $user;
5087                  unset($users[$userid]);
5088              }
5089          }
5090      }
5091      return $susers;
5092  }
5093  
5094  /**
5095   * Given context and array of users, returns array of user ids whose enrolment status is suspended,
5096   * or enrolment has expired or not started.
5097   *
5098   * @param context $context context in which user enrolment is checked.
5099   * @param bool $usecache Enable or disable (default) the request cache
5100   * @return array list of suspended user id's.
5101   */
5102  function get_suspended_userids(context $context, $usecache = false) {
5103      global $DB;
5104  
5105      if ($usecache) {
5106          $cache = cache::make('core', 'suspended_userids');
5107          $susers = $cache->get($context->id);
5108          if ($susers !== false) {
5109              return $susers;
5110          }
5111      }
5112  
5113      $coursecontext = $context->get_course_context();
5114      $susers = array();
5115  
5116      // Front page users are always enrolled, so suspended list is empty.
5117      if ($coursecontext->instanceid != SITEID) {
5118          list($sql, $params) = get_enrolled_sql($context, null, null, false, true);
5119          $susers = $DB->get_fieldset_sql($sql, $params);
5120          $susers = array_combine($susers, $susers);
5121      }
5122  
5123      // Cache results for the remainder of this request.
5124      if ($usecache) {
5125          $cache->set($context->id, $susers);
5126      }
5127  
5128      return $susers;
5129  }
5130  
5131  /**
5132   * Gets sql for finding users with capability in the given context
5133   *
5134   * @param context $context
5135   * @param string|array $capability Capability name or array of names.
5136   *      If an array is provided then this is the equivalent of a logical 'OR',
5137   *      i.e. the user needs to have one of these capabilities.
5138   * @return array($sql, $params)
5139   */
5140  function get_with_capability_sql(context $context, $capability) {
5141      static $i = 0;
5142      $i++;
5143      $prefix = 'cu' . $i . '_';
5144  
5145      $capjoin = get_with_capability_join($context, $capability, $prefix . 'u.id');
5146  
5147      $sql = "SELECT DISTINCT {$prefix}u.id
5148                FROM {user} {$prefix}u
5149              $capjoin->joins
5150               WHERE {$prefix}u.deleted = 0 AND $capjoin->wheres";
5151  
5152      return array($sql, $capjoin->params);
5153  }