[ Index ]

PHP Cross Reference of moodle-2.8

title

Body

[close]

/lib/ -> accesslib.php (source)

   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_access_sitewide()
  60   * - load_course_context()
  61   * - load_role_access_by_context()
  62   * - etc.
  63   *
  64   * <b>Name conventions</b>
  65   *
  66   * "ctx" means context
  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-capabilities-perm sets
  78   * (role defs) and a list of courses we have loaded
  79   * data for.
  80   *
  81   * Things are keyed on "contextpaths" (the path field of
  82   * the context table) for fast walking up/down the tree.
  83   * <code>
  84   * $accessdata['ra'][$contextpath] = array($roleid=>$roleid)
  85   *                  [$contextpath] = array($roleid=>$roleid)
  86   *                  [$contextpath] = array($roleid=>$roleid)
  87   * </code>
  88   *
  89   * Role definitions are stored like this
  90   * (no cap merge is done - so it's compact)
  91   *
  92   * <code>
  93   * $accessdata['rdef']["$contextpath:$roleid"]['mod/forum:viewpost'] = 1
  94   *                                            ['mod/forum:editallpost'] = -1
  95   *                                            ['mod/forum:startdiscussion'] = -1000
  96   * </code>
  97   *
  98   * See how has_capability_in_accessdata() walks up the tree.
  99   *
 100   * First we only load rdef and ra down to the course level, but not below.
 101   * This keeps accessdata small and compact. Below-the-course ra/rdef
 102   * are loaded as needed. We keep track of which courses we have loaded ra/rdef in
 103   * <code>
 104   * $accessdata['loaded'] = array($courseid1=>1, $courseid2=>1)
 105   * </code>
 106   *
 107   * <b>Stale accessdata</b>
 108   *
 109   * For the logged-in user, accessdata is long-lived.
 110   *
 111   * On each pageload we load $ACCESSLIB_PRIVATE->dirtycontexts which lists
 112   * context paths affected by changes. Any check at-or-below
 113   * a dirty context will trigger a transparent reload of accessdata.
 114   *
 115   * Changes at the system level will force the reload for everyone.
 116   *
 117   * <b>Default role caps</b>
 118   * The default role assignment is not in the DB, so we
 119   * add it manually to accessdata.
 120   *
 121   * This means that functions that work directly off the
 122   * DB need to ensure that the default role caps
 123   * are dealt with appropriately.
 124   *
 125   * @package    core_access
 126   * @copyright  1999 onwards Martin Dougiamas  http://dougiamas.com
 127   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 128   */
 129  
 130  defined('MOODLE_INTERNAL') || die();
 131  
 132  /** No capability change */
 133  define('CAP_INHERIT', 0);
 134  /** Allow permission, overrides CAP_PREVENT defined in parent contexts */
 135  define('CAP_ALLOW', 1);
 136  /** Prevent permission, overrides CAP_ALLOW defined in parent contexts */
 137  define('CAP_PREVENT', -1);
 138  /** Prohibit permission, overrides everything in current and child contexts */
 139  define('CAP_PROHIBIT', -1000);
 140  
 141  /** System context level - only one instance in every system */
 142  define('CONTEXT_SYSTEM', 10);
 143  /** User context level -  one instance for each user describing what others can do to user */
 144  define('CONTEXT_USER', 30);
 145  /** Course category context level - one instance for each category */
 146  define('CONTEXT_COURSECAT', 40);
 147  /** Course context level - one instances for each course */
 148  define('CONTEXT_COURSE', 50);
 149  /** Course module context level - one instance for each course module */
 150  define('CONTEXT_MODULE', 70);
 151  /**
 152   * Block context level - one instance for each block, sticky blocks are tricky
 153   * because ppl think they should be able to override them at lower contexts.
 154   * Any other context level instance can be parent of block context.
 155   */
 156  define('CONTEXT_BLOCK', 80);
 157  
 158  /** Capability allow management of trusts - NOT IMPLEMENTED YET - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
 159  define('RISK_MANAGETRUST', 0x0001);
 160  /** Capability allows changes in system configuration - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
 161  define('RISK_CONFIG',      0x0002);
 162  /** Capability allows user to add scripted content - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
 163  define('RISK_XSS',         0x0004);
 164  /** Capability allows access to personal user information - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
 165  define('RISK_PERSONAL',    0x0008);
 166  /** Capability allows users to add content others may see - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
 167  define('RISK_SPAM',        0x0010);
 168  /** capability allows mass delete of data belonging to other users - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
 169  define('RISK_DATALOSS',    0x0020);
 170  
 171  /** rolename displays - the name as defined in the role definition, localised if name empty */
 172  define('ROLENAME_ORIGINAL', 0);
 173  /** rolename displays - the name as defined by a role alias at the course level, falls back to ROLENAME_ORIGINAL if alias not present */
 174  define('ROLENAME_ALIAS', 1);
 175  /** rolename displays - Both, like this:  Role alias (Original) */
 176  define('ROLENAME_BOTH', 2);
 177  /** rolename displays - the name as defined in the role definition and the shortname in brackets */
 178  define('ROLENAME_ORIGINALANDSHORT', 3);
 179  /** rolename displays - the name as defined by a role alias, in raw form suitable for editing */
 180  define('ROLENAME_ALIAS_RAW', 4);
 181  /** rolename displays - the name is simply short role name */
 182  define('ROLENAME_SHORT', 5);
 183  
 184  if (!defined('CONTEXT_CACHE_MAX_SIZE')) {
 185      /** maximum size of context cache - it is possible to tweak this config.php or in any script before inclusion of context.php */
 186      define('CONTEXT_CACHE_MAX_SIZE', 2500);
 187  }
 188  
 189  /**
 190   * Although this looks like a global variable, it isn't really.
 191   *
 192   * It is just a private implementation detail to accesslib that MUST NOT be used elsewhere.
 193   * It is used to cache various bits of data between function calls for performance reasons.
 194   * Sadly, a PHP global variable is the only way to implement this, without rewriting everything
 195   * as methods of a class, instead of functions.
 196   *
 197   * @access private
 198   * @global stdClass $ACCESSLIB_PRIVATE
 199   * @name $ACCESSLIB_PRIVATE
 200   */
 201  global $ACCESSLIB_PRIVATE;
 202  $ACCESSLIB_PRIVATE = new stdClass();
 203  $ACCESSLIB_PRIVATE->dirtycontexts    = null;    // Dirty contexts cache, loaded from DB once per page
 204  $ACCESSLIB_PRIVATE->accessdatabyuser = array(); // Holds the cache of $accessdata structure for users (including $USER)
 205  $ACCESSLIB_PRIVATE->rolepermissions  = array(); // role permissions cache - helps a lot with mem usage
 206  $ACCESSLIB_PRIVATE->capabilities     = null;    // detailed information about the capabilities
 207  
 208  /**
 209   * Clears accesslib's private caches. ONLY BE USED BY UNIT TESTS
 210   *
 211   * This method should ONLY BE USED BY UNIT TESTS. It clears all of
 212   * accesslib's private caches. You need to do this before setting up test data,
 213   * and also at the end of the tests.
 214   *
 215   * @access private
 216   * @return void
 217   */
 218  function accesslib_clear_all_caches_for_unit_testing() {
 219      global $USER;
 220      if (!PHPUNIT_TEST) {
 221          throw new coding_exception('You must not call clear_all_caches outside of unit tests.');
 222      }
 223  
 224      accesslib_clear_all_caches(true);
 225  
 226      unset($USER->access);
 227  }
 228  
 229  /**
 230   * Clears accesslib's private caches. ONLY BE USED FROM THIS LIBRARY FILE!
 231   *
 232   * This reset does not touch global $USER.
 233   *
 234   * @access private
 235   * @param bool $resetcontexts
 236   * @return void
 237   */
 238  function accesslib_clear_all_caches($resetcontexts) {
 239      global $ACCESSLIB_PRIVATE;
 240  
 241      $ACCESSLIB_PRIVATE->dirtycontexts    = null;
 242      $ACCESSLIB_PRIVATE->accessdatabyuser = array();
 243      $ACCESSLIB_PRIVATE->rolepermissions  = array();
 244      $ACCESSLIB_PRIVATE->capabilities     = null;
 245  
 246      if ($resetcontexts) {
 247          context_helper::reset_caches();
 248      }
 249  }
 250  
 251  /**
 252   * Gets the accessdata for role "sitewide" (system down to course)
 253   *
 254   * @access private
 255   * @param int $roleid
 256   * @return array
 257   */
 258  function get_role_access($roleid) {
 259      global $DB, $ACCESSLIB_PRIVATE;
 260  
 261      /* Get it in 1 DB query...
 262       * - relevant role caps at the root and down
 263       *   to the course level - but not below
 264       */
 265  
 266      //TODO: MUC - this could be cached in shared memory to speed up first page loading, web crawlers, etc.
 267  
 268      $accessdata = get_empty_accessdata();
 269  
 270      $accessdata['ra']['/'.SYSCONTEXTID] = array((int)$roleid => (int)$roleid);
 271  
 272      // Overrides for the role IN ANY CONTEXTS down to COURSE - not below -.
 273  
 274      /*
 275      $sql = "SELECT ctx.path,
 276                     rc.capability, rc.permission
 277                FROM {context} ctx
 278                JOIN {role_capabilities} rc ON rc.contextid = ctx.id
 279           LEFT JOIN {context} cctx
 280                     ON (cctx.contextlevel = ".CONTEXT_COURSE." AND ctx.path LIKE ".$DB->sql_concat('cctx.path',"'/%'").")
 281               WHERE rc.roleid = ? AND cctx.id IS NULL";
 282      $params = array($roleid);
 283      */
 284  
 285      // Note: the commented out query is 100% accurate but slow, so let's cheat instead by hardcoding the blocks mess directly.
 286  
 287      $sql = "SELECT COALESCE(ctx.path, bctx.path) AS path, rc.capability, rc.permission
 288                FROM {role_capabilities} rc
 289           LEFT JOIN {context} ctx ON (ctx.id = rc.contextid AND ctx.contextlevel <= ".CONTEXT_COURSE.")
 290           LEFT JOIN ({context} bctx
 291                      JOIN {block_instances} bi ON (bi.id = bctx.instanceid)
 292                      JOIN {context} pctx ON (pctx.id = bi.parentcontextid AND pctx.contextlevel < ".CONTEXT_COURSE.")
 293                     ) ON (bctx.id = rc.contextid AND bctx.contextlevel = ".CONTEXT_BLOCK.")
 294               WHERE rc.roleid = :roleid AND (ctx.id IS NOT NULL OR bctx.id IS NOT NULL)";
 295      $params = array('roleid'=>$roleid);
 296  
 297      // we need extra caching in CLI scripts and cron
 298      $rs = $DB->get_recordset_sql($sql, $params);
 299      foreach ($rs as $rd) {
 300          $k = "{$rd->path}:{$roleid}";
 301          $accessdata['rdef'][$k][$rd->capability] = (int)$rd->permission;
 302      }
 303      $rs->close();
 304  
 305      // share the role definitions
 306      foreach ($accessdata['rdef'] as $k=>$unused) {
 307          if (!isset($ACCESSLIB_PRIVATE->rolepermissions[$k])) {
 308              $ACCESSLIB_PRIVATE->rolepermissions[$k] = $accessdata['rdef'][$k];
 309          }
 310          $accessdata['rdef_count']++;
 311          $accessdata['rdef'][$k] =& $ACCESSLIB_PRIVATE->rolepermissions[$k];
 312      }
 313  
 314      return $accessdata;
 315  }
 316  
 317  /**
 318   * Get the default guest role, this is used for guest account,
 319   * search engine spiders, etc.
 320   *
 321   * @return stdClass role record
 322   */
 323  function get_guest_role() {
 324      global $CFG, $DB;
 325  
 326      if (empty($CFG->guestroleid)) {
 327          if ($roles = $DB->get_records('role', array('archetype'=>'guest'))) {
 328              $guestrole = array_shift($roles);   // Pick the first one
 329              set_config('guestroleid', $guestrole->id);
 330              return $guestrole;
 331          } else {
 332              debugging('Can not find any guest role!');
 333              return false;
 334          }
 335      } else {
 336          if ($guestrole = $DB->get_record('role', array('id'=>$CFG->guestroleid))) {
 337              return $guestrole;
 338          } else {
 339              // somebody is messing with guest roles, remove incorrect setting and try to find a new one
 340              set_config('guestroleid', '');
 341              return get_guest_role();
 342          }
 343      }
 344  }
 345  
 346  /**
 347   * Check whether a user has a particular capability in a given context.
 348   *
 349   * For example:
 350   *      $context = context_module::instance($cm->id);
 351   *      has_capability('mod/forum:replypost', $context)
 352   *
 353   * By default checks the capabilities of the current user, but you can pass a
 354   * different userid. By default will return true for admin users, but you can override that with the fourth argument.
 355   *
 356   * Guest and not-logged-in users can never get any dangerous capability - that is any write capability
 357   * or capabilities with XSS, config or data loss risks.
 358   *
 359   * @category access
 360   *
 361   * @param string $capability the name of the capability to check. For example mod/forum:view
 362   * @param context $context the context to check the capability in. You normally get this with instance method of a context class.
 363   * @param integer|stdClass $user A user id or object. By default (null) checks the permissions of the current user.
 364   * @param boolean $doanything If false, ignores effect of admin role assignment
 365   * @return boolean true if the user has this capability. Otherwise false.
 366   */
 367  function has_capability($capability, context $context, $user = null, $doanything = true) {
 368      global $USER, $CFG, $SCRIPT, $ACCESSLIB_PRIVATE;
 369  
 370      if (during_initial_install()) {
 371          if ($SCRIPT === "/$CFG->admin/index.php" or $SCRIPT === "/$CFG->admin/cli/install.php" or $SCRIPT === "/$CFG->admin/cli/install_database.php") {
 372              // we are in an installer - roles can not work yet
 373              return true;
 374          } else {
 375              return false;
 376          }
 377      }
 378  
 379      if (strpos($capability, 'moodle/legacy:') === 0) {
 380          throw new coding_exception('Legacy capabilities can not be used any more!');
 381      }
 382  
 383      if (!is_bool($doanything)) {
 384          throw new coding_exception('Capability parameter "doanything" is wierd, only true or false is allowed. This has to be fixed in code.');
 385      }
 386  
 387      // capability must exist
 388      if (!$capinfo = get_capability_info($capability)) {
 389          debugging('Capability "'.$capability.'" was not found! This has to be fixed in code.');
 390          return false;
 391      }
 392  
 393      if (!isset($USER->id)) {
 394          // should never happen
 395          $USER->id = 0;
 396          debugging('Capability check being performed on a user with no ID.', DEBUG_DEVELOPER);
 397      }
 398  
 399      // make sure there is a real user specified
 400      if ($user === null) {
 401          $userid = $USER->id;
 402      } else {
 403          $userid = is_object($user) ? $user->id : $user;
 404      }
 405  
 406      // make sure forcelogin cuts off not-logged-in users if enabled
 407      if (!empty($CFG->forcelogin) and $userid == 0) {
 408          return false;
 409      }
 410  
 411      // make sure the guest account and not-logged-in users never get any risky caps no matter what the actual settings are.
 412      if (($capinfo->captype === 'write') or ($capinfo->riskbitmask & (RISK_XSS | RISK_CONFIG | RISK_DATALOSS))) {
 413          if (isguestuser($userid) or $userid == 0) {
 414              return false;
 415          }
 416      }
 417  
 418      // somehow make sure the user is not deleted and actually exists
 419      if ($userid != 0) {
 420          if ($userid == $USER->id and isset($USER->deleted)) {
 421              // this prevents one query per page, it is a bit of cheating,
 422              // but hopefully session is terminated properly once user is deleted
 423              if ($USER->deleted) {
 424                  return false;
 425              }
 426          } else {
 427              if (!context_user::instance($userid, IGNORE_MISSING)) {
 428                  // no user context == invalid userid
 429                  return false;
 430              }
 431          }
 432      }
 433  
 434      // context path/depth must be valid
 435      if (empty($context->path) or $context->depth == 0) {
 436          // this should not happen often, each upgrade tries to rebuild the context paths
 437          debugging('Context id '.$context->id.' does not have valid path, please use build_context_path()');
 438          if (is_siteadmin($userid)) {
 439              return true;
 440          } else {
 441              return false;
 442          }
 443      }
 444  
 445      // Find out if user is admin - it is not possible to override the doanything in any way
 446      // and it is not possible to switch to admin role either.
 447      if ($doanything) {
 448          if (is_siteadmin($userid)) {
 449              if ($userid != $USER->id) {
 450                  return true;
 451              }
 452              // make sure switchrole is not used in this context
 453              if (empty($USER->access['rsw'])) {
 454                  return true;
 455              }
 456              $parts = explode('/', trim($context->path, '/'));
 457              $path = '';
 458              $switched = false;
 459              foreach ($parts as $part) {
 460                  $path .= '/' . $part;
 461                  if (!empty($USER->access['rsw'][$path])) {
 462                      $switched = true;
 463                      break;
 464                  }
 465              }
 466              if (!$switched) {
 467                  return true;
 468              }
 469              //ok, admin switched role in this context, let's use normal access control rules
 470          }
 471      }
 472  
 473      // Careful check for staleness...
 474      $context->reload_if_dirty();
 475  
 476      if ($USER->id == $userid) {
 477          if (!isset($USER->access)) {
 478              load_all_capabilities();
 479          }
 480          $access =& $USER->access;
 481  
 482      } else {
 483          // make sure user accessdata is really loaded
 484          get_user_accessdata($userid, true);
 485          $access =& $ACCESSLIB_PRIVATE->accessdatabyuser[$userid];
 486      }
 487  
 488  
 489      // Load accessdata for below-the-course context if necessary,
 490      // all contexts at and above all courses are already loaded
 491      if ($context->contextlevel != CONTEXT_COURSE and $coursecontext = $context->get_course_context(false)) {
 492          load_course_context($userid, $coursecontext, $access);
 493      }
 494  
 495      return has_capability_in_accessdata($capability, $context, $access);
 496  }
 497  
 498  /**
 499   * Check if the user has any one of several capabilities from a list.
 500   *
 501   * This is just a utility method that calls has_capability in a loop. Try to put
 502   * the capabilities that most users are likely to have first in the list for best
 503   * performance.
 504   *
 505   * @category access
 506   * @see has_capability()
 507   *
 508   * @param array $capabilities an array of capability names.
 509   * @param context $context the context to check the capability in. You normally get this with instance method of a context class.
 510   * @param integer|stdClass $user A user id or object. By default (null) checks the permissions of the current user.
 511   * @param boolean $doanything If false, ignore effect of admin role assignment
 512   * @return boolean true if the user has any of these capabilities. Otherwise false.
 513   */
 514  function has_any_capability(array $capabilities, context $context, $user = null, $doanything = true) {
 515      foreach ($capabilities as $capability) {
 516          if (has_capability($capability, $context, $user, $doanything)) {
 517              return true;
 518          }
 519      }
 520      return false;
 521  }
 522  
 523  /**
 524   * Check if the user has all the capabilities in a list.
 525   *
 526   * This is just a utility method that calls has_capability in a loop. Try to put
 527   * the capabilities that fewest users are likely to have first in the list for best
 528   * performance.
 529   *
 530   * @category access
 531   * @see has_capability()
 532   *
 533   * @param array $capabilities an array of capability names.
 534   * @param context $context the context to check the capability in. You normally get this with instance method of a context class.
 535   * @param integer|stdClass $user A user id or object. By default (null) checks the permissions of the current user.
 536   * @param boolean $doanything If false, ignore effect of admin role assignment
 537   * @return boolean true if the user has all of these capabilities. Otherwise false.
 538   */
 539  function has_all_capabilities(array $capabilities, context $context, $user = null, $doanything = true) {
 540      foreach ($capabilities as $capability) {
 541          if (!has_capability($capability, $context, $user, $doanything)) {
 542              return false;
 543          }
 544      }
 545      return true;
 546  }
 547  
 548  /**
 549   * Is course creator going to have capability in a new course?
 550   *
 551   * This is intended to be used in enrolment plugins before or during course creation,
 552   * do not use after the course is fully created.
 553   *
 554   * @category access
 555   *
 556   * @param string $capability the name of the capability to check.
 557   * @param context $context course or category context where is course going to be created
 558   * @param integer|stdClass $user A user id or object. By default (null) checks the permissions of the current user.
 559   * @return boolean true if the user will have this capability.
 560   *
 561   * @throws coding_exception if different type of context submitted
 562   */
 563  function guess_if_creator_will_have_course_capability($capability, context $context, $user = null) {
 564      global $CFG;
 565  
 566      if ($context->contextlevel != CONTEXT_COURSE and $context->contextlevel != CONTEXT_COURSECAT) {
 567          throw new coding_exception('Only course or course category context expected');
 568      }
 569  
 570      if (has_capability($capability, $context, $user)) {
 571          // User already has the capability, it could be only removed if CAP_PROHIBIT
 572          // was involved here, but we ignore that.
 573          return true;
 574      }
 575  
 576      if (!has_capability('moodle/course:create', $context, $user)) {
 577          return false;
 578      }
 579  
 580      if (!enrol_is_enabled('manual')) {
 581          return false;
 582      }
 583  
 584      if (empty($CFG->creatornewroleid)) {
 585          return false;
 586      }
 587  
 588      if ($context->contextlevel == CONTEXT_COURSE) {
 589          if (is_viewing($context, $user, 'moodle/role:assign') or is_enrolled($context, $user, 'moodle/role:assign')) {
 590              return false;
 591          }
 592      } else {
 593          if (has_capability('moodle/course:view', $context, $user) and has_capability('moodle/role:assign', $context, $user)) {
 594              return false;
 595          }
 596      }
 597  
 598      // Most likely they will be enrolled after the course creation is finished,
 599      // does the new role have the required capability?
 600      list($neededroles, $forbiddenroles) = get_roles_with_cap_in_context($context, $capability);
 601      return isset($neededroles[$CFG->creatornewroleid]);
 602  }
 603  
 604  /**
 605   * Check if the user is an admin at the site level.
 606   *
 607   * Please note that use of proper capabilities is always encouraged,
 608   * this function is supposed to be used from core or for temporary hacks.
 609   *
 610   * @category access
 611   *
 612   * @param  int|stdClass  $user_or_id user id or user object
 613   * @return bool true if user is one of the administrators, false otherwise
 614   */
 615  function is_siteadmin($user_or_id = null) {
 616      global $CFG, $USER;
 617  
 618      if ($user_or_id === null) {
 619          $user_or_id = $USER;
 620      }
 621  
 622      if (empty($user_or_id)) {
 623          return false;
 624      }
 625      if (!empty($user_or_id->id)) {
 626          $userid = $user_or_id->id;
 627      } else {
 628          $userid = $user_or_id;
 629      }
 630  
 631      // Because this script is called many times (150+ for course page) with
 632      // the same parameters, it is worth doing minor optimisations. This static
 633      // cache stores the value for a single userid, saving about 2ms from course
 634      // page load time without using significant memory. As the static cache
 635      // also includes the value it depends on, this cannot break unit tests.
 636      static $knownid, $knownresult, $knownsiteadmins;
 637      if ($knownid === $userid && $knownsiteadmins === $CFG->siteadmins) {
 638          return $knownresult;
 639      }
 640      $knownid = $userid;
 641      $knownsiteadmins = $CFG->siteadmins;
 642  
 643      $siteadmins = explode(',', $CFG->siteadmins);
 644      $knownresult = in_array($userid, $siteadmins);
 645      return $knownresult;
 646  }
 647  
 648  /**
 649   * Returns true if user has at least one role assign
 650   * of 'coursecontact' role (is potentially listed in some course descriptions).
 651   *
 652   * @param int $userid
 653   * @return bool
 654   */
 655  function has_coursecontact_role($userid) {
 656      global $DB, $CFG;
 657  
 658      if (empty($CFG->coursecontact)) {
 659          return false;
 660      }
 661      $sql = "SELECT 1
 662                FROM {role_assignments}
 663               WHERE userid = :userid AND roleid IN ($CFG->coursecontact)";
 664      return $DB->record_exists_sql($sql, array('userid'=>$userid));
 665  }
 666  
 667  /**
 668   * Does the user have a capability to do something?
 669   *
 670   * Walk the accessdata array and return true/false.
 671   * Deals with prohibits, role switching, aggregating
 672   * capabilities, etc.
 673   *
 674   * The main feature of here is being FAST and with no
 675   * side effects.
 676   *
 677   * Notes:
 678   *
 679   * Switch Role merges with default role
 680   * ------------------------------------
 681   * If you are a teacher in course X, you have at least
 682   * teacher-in-X + defaultloggedinuser-sitewide. So in the
 683   * course you'll have techer+defaultloggedinuser.
 684   * We try to mimic that in switchrole.
 685   *
 686   * Permission evaluation
 687   * ---------------------
 688   * Originally there was an extremely complicated way
 689   * to determine the user access that dealt with
 690   * "locality" or role assignments and role overrides.
 691   * Now we simply evaluate access for each role separately
 692   * and then verify if user has at least one role with allow
 693   * and at the same time no role with prohibit.
 694   *
 695   * @access private
 696   * @param string $capability
 697   * @param context $context
 698   * @param array $accessdata
 699   * @return bool
 700   */
 701  function has_capability_in_accessdata($capability, context $context, array &$accessdata) {
 702      global $CFG;
 703  
 704      // Build $paths as a list of current + all parent "paths" with order bottom-to-top
 705      $path = $context->path;
 706      $paths = array($path);
 707      while($path = rtrim($path, '0123456789')) {
 708          $path = rtrim($path, '/');
 709          if ($path === '') {
 710              break;
 711          }
 712          $paths[] = $path;
 713      }
 714  
 715      $roles = array();
 716      $switchedrole = false;
 717  
 718      // Find out if role switched
 719      if (!empty($accessdata['rsw'])) {
 720          // From the bottom up...
 721          foreach ($paths as $path) {
 722              if (isset($accessdata['rsw'][$path])) {
 723                  // Found a switchrole assignment - check for that role _plus_ the default user role
 724                  $roles = array($accessdata['rsw'][$path]=>null, $CFG->defaultuserroleid=>null);
 725                  $switchedrole = true;
 726                  break;
 727              }
 728          }
 729      }
 730  
 731      if (!$switchedrole) {
 732          // get all users roles in this context and above
 733          foreach ($paths as $path) {
 734              if (isset($accessdata['ra'][$path])) {
 735                  foreach ($accessdata['ra'][$path] as $roleid) {
 736                      $roles[$roleid] = null;
 737                  }
 738              }
 739          }
 740      }
 741  
 742      // Now find out what access is given to each role, going bottom-->up direction
 743      $allowed = false;
 744      foreach ($roles as $roleid => $ignored) {
 745          foreach ($paths as $path) {
 746              if (isset($accessdata['rdef']["{$path}:$roleid"][$capability])) {
 747                  $perm = (int)$accessdata['rdef']["{$path}:$roleid"][$capability];
 748                  if ($perm === CAP_PROHIBIT) {
 749                      // any CAP_PROHIBIT found means no permission for the user
 750                      return false;
 751                  }
 752                  if (is_null($roles[$roleid])) {
 753                      $roles[$roleid] = $perm;
 754                  }
 755              }
 756          }
 757          // CAP_ALLOW in any role means the user has a permission, we continue only to detect prohibits
 758          $allowed = ($allowed or $roles[$roleid] === CAP_ALLOW);
 759      }
 760  
 761      return $allowed;
 762  }
 763  
 764  /**
 765   * A convenience function that tests has_capability, and displays an error if
 766   * the user does not have that capability.
 767   *
 768   * NOTE before Moodle 2.0, this function attempted to make an appropriate
 769   * require_login call before checking the capability. This is no longer the case.
 770   * You must call require_login (or one of its variants) if you want to check the
 771   * user is logged in, before you call this function.
 772   *
 773   * @see has_capability()
 774   *
 775   * @param string $capability the name of the capability to check. For example mod/forum:view
 776   * @param context $context the context to check the capability in. You normally get this with context_xxxx::instance().
 777   * @param int $userid A user id. By default (null) checks the permissions of the current user.
 778   * @param bool $doanything If false, ignore effect of admin role assignment
 779   * @param string $errormessage The error string to to user. Defaults to 'nopermissions'.
 780   * @param string $stringfile The language file to load the error string from. Defaults to 'error'.
 781   * @return void terminates with an error if the user does not have the given capability.
 782   */
 783  function require_capability($capability, context $context, $userid = null, $doanything = true,
 784                              $errormessage = 'nopermissions', $stringfile = '') {
 785      if (!has_capability($capability, $context, $userid, $doanything)) {
 786          throw new required_capability_exception($context, $capability, $errormessage, $stringfile);
 787      }
 788  }
 789  
 790  /**
 791   * Return a nested array showing role assignments
 792   * all relevant role capabilities for the user at
 793   * site/course_category/course levels
 794   *
 795   * We do _not_ delve deeper than courses because the number of
 796   * overrides at the module/block levels can be HUGE.
 797   *
 798   * [ra]   => [/path][roleid]=roleid
 799   * [rdef] => [/path:roleid][capability]=permission
 800   *
 801   * @access private
 802   * @param int $userid - the id of the user
 803   * @return array access info array
 804   */
 805  function get_user_access_sitewide($userid) {
 806      global $CFG, $DB, $ACCESSLIB_PRIVATE;
 807  
 808      /* Get in a few cheap DB queries...
 809       * - role assignments
 810       * - relevant role caps
 811       *   - above and within this user's RAs
 812       *   - below this user's RAs - limited to course level
 813       */
 814  
 815      // raparents collects paths & roles we need to walk up the parenthood to build the minimal rdef
 816      $raparents = array();
 817      $accessdata = get_empty_accessdata();
 818  
 819      // start with the default role
 820      if (!empty($CFG->defaultuserroleid)) {
 821          $syscontext = context_system::instance();
 822          $accessdata['ra'][$syscontext->path][(int)$CFG->defaultuserroleid] = (int)$CFG->defaultuserroleid;
 823          $raparents[$CFG->defaultuserroleid][$syscontext->id] = $syscontext->id;
 824      }
 825  
 826      // load the "default frontpage role"
 827      if (!empty($CFG->defaultfrontpageroleid)) {
 828          $frontpagecontext = context_course::instance(get_site()->id);
 829          if ($frontpagecontext->path) {
 830              $accessdata['ra'][$frontpagecontext->path][(int)$CFG->defaultfrontpageroleid] = (int)$CFG->defaultfrontpageroleid;
 831              $raparents[$CFG->defaultfrontpageroleid][$frontpagecontext->id] = $frontpagecontext->id;
 832          }
 833      }
 834  
 835      // preload every assigned role at and above course context
 836      $sql = "SELECT ctx.path, ra.roleid, ra.contextid
 837                FROM {role_assignments} ra
 838                JOIN {context} ctx
 839                     ON ctx.id = ra.contextid
 840           LEFT JOIN {block_instances} bi
 841                     ON (ctx.contextlevel = ".CONTEXT_BLOCK." AND bi.id = ctx.instanceid)
 842           LEFT JOIN {context} bpctx
 843                     ON (bpctx.id = bi.parentcontextid)
 844               WHERE ra.userid = :userid
 845                     AND (ctx.contextlevel <= ".CONTEXT_COURSE." OR bpctx.contextlevel < ".CONTEXT_COURSE.")";
 846      $params = array('userid'=>$userid);
 847      $rs = $DB->get_recordset_sql($sql, $params);
 848      foreach ($rs as $ra) {
 849          // RAs leafs are arrays to support multi-role assignments...
 850          $accessdata['ra'][$ra->path][(int)$ra->roleid] = (int)$ra->roleid;
 851          $raparents[$ra->roleid][$ra->contextid] = $ra->contextid;
 852      }
 853      $rs->close();
 854  
 855      if (empty($raparents)) {
 856          return $accessdata;
 857      }
 858  
 859      // now get overrides of interesting roles in all interesting child contexts
 860      // hopefully we will not run out of SQL limits here,
 861      // users would have to have very many roles at/above course context...
 862      $sqls = array();
 863      $params = array();
 864  
 865      static $cp = 0;
 866      foreach ($raparents as $roleid=>$ras) {
 867          $cp++;
 868          list($sqlcids, $cids) = $DB->get_in_or_equal($ras, SQL_PARAMS_NAMED, 'c'.$cp.'_');
 869          $params = array_merge($params, $cids);
 870          $params['r'.$cp] = $roleid;
 871          $sqls[] = "(SELECT ctx.path, rc.roleid, rc.capability, rc.permission
 872                       FROM {role_capabilities} rc
 873                       JOIN {context} ctx
 874                            ON (ctx.id = rc.contextid)
 875                       JOIN {context} pctx
 876                            ON (pctx.id $sqlcids
 877                                AND (ctx.id = pctx.id
 878                                     OR ctx.path LIKE ".$DB->sql_concat('pctx.path',"'/%'")."
 879                                     OR pctx.path LIKE ".$DB->sql_concat('ctx.path',"'/%'")."))
 880                  LEFT JOIN {block_instances} bi
 881                            ON (ctx.contextlevel = ".CONTEXT_BLOCK." AND bi.id = ctx.instanceid)
 882                  LEFT JOIN {context} bpctx
 883                            ON (bpctx.id = bi.parentcontextid)
 884                      WHERE rc.roleid = :r{$cp}
 885                            AND (ctx.contextlevel <= ".CONTEXT_COURSE." OR bpctx.contextlevel < ".CONTEXT_COURSE.")
 886                     )";
 887      }
 888  
 889      // fixed capability order is necessary for rdef dedupe
 890      $rs = $DB->get_recordset_sql(implode("\nUNION\n", $sqls). "ORDER BY capability", $params);
 891  
 892      foreach ($rs as $rd) {
 893          $k = $rd->path.':'.$rd->roleid;
 894          $accessdata['rdef'][$k][$rd->capability] = (int)$rd->permission;
 895      }
 896      $rs->close();
 897  
 898      // share the role definitions
 899      foreach ($accessdata['rdef'] as $k=>$unused) {
 900          if (!isset($ACCESSLIB_PRIVATE->rolepermissions[$k])) {
 901              $ACCESSLIB_PRIVATE->rolepermissions[$k] = $accessdata['rdef'][$k];
 902          }
 903          $accessdata['rdef_count']++;
 904          $accessdata['rdef'][$k] =& $ACCESSLIB_PRIVATE->rolepermissions[$k];
 905      }
 906  
 907      return $accessdata;
 908  }
 909  
 910  /**
 911   * Add to the access ctrl array the data needed by a user for a given course.
 912   *
 913   * This function injects all course related access info into the accessdata array.
 914   *
 915   * @access private
 916   * @param int $userid the id of the user
 917   * @param context_course $coursecontext course context
 918   * @param array $accessdata accessdata array (modified)
 919   * @return void modifies $accessdata parameter
 920   */
 921  function load_course_context($userid, context_course $coursecontext, &$accessdata) {
 922      global $DB, $CFG, $ACCESSLIB_PRIVATE;
 923  
 924      if (empty($coursecontext->path)) {
 925          // weird, this should not happen
 926          return;
 927      }
 928  
 929      if (isset($accessdata['loaded'][$coursecontext->instanceid])) {
 930          // already loaded, great!
 931          return;
 932      }
 933  
 934      $roles = array();
 935  
 936      if (empty($userid)) {
 937          if (!empty($CFG->notloggedinroleid)) {
 938              $roles[$CFG->notloggedinroleid] = $CFG->notloggedinroleid;
 939          }
 940  
 941      } else if (isguestuser($userid)) {
 942          if ($guestrole = get_guest_role()) {
 943              $roles[$guestrole->id] = $guestrole->id;
 944          }
 945  
 946      } else {
 947          // Interesting role assignments at, above and below the course context
 948          list($parentsaself, $params) = $DB->get_in_or_equal($coursecontext->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'pc_');
 949          $params['userid'] = $userid;
 950          $params['children'] = $coursecontext->path."/%";
 951          $sql = "SELECT ra.*, ctx.path
 952                    FROM {role_assignments} ra
 953                    JOIN {context} ctx ON ra.contextid = ctx.id
 954                   WHERE ra.userid = :userid AND (ctx.id $parentsaself OR ctx.path LIKE :children)";
 955          $rs = $DB->get_recordset_sql($sql, $params);
 956  
 957          // add missing role definitions
 958          foreach ($rs as $ra) {
 959              $accessdata['ra'][$ra->path][(int)$ra->roleid] = (int)$ra->roleid;
 960              $roles[$ra->roleid] = $ra->roleid;
 961          }
 962          $rs->close();
 963  
 964          // add the "default frontpage role" when on the frontpage
 965          if (!empty($CFG->defaultfrontpageroleid)) {
 966              $frontpagecontext = context_course::instance(get_site()->id);
 967              if ($frontpagecontext->id == $coursecontext->id) {
 968                  $roles[$CFG->defaultfrontpageroleid] = $CFG->defaultfrontpageroleid;
 969              }
 970          }
 971  
 972          // do not forget the default role
 973          if (!empty($CFG->defaultuserroleid)) {
 974              $roles[$CFG->defaultuserroleid] = $CFG->defaultuserroleid;
 975          }
 976      }
 977  
 978      if (!$roles) {
 979          // weird, default roles must be missing...
 980          $accessdata['loaded'][$coursecontext->instanceid] = 1;
 981          return;
 982      }
 983  
 984      // now get overrides of interesting roles in all interesting contexts (this course + children + parents)
 985      $params = array('c'=>$coursecontext->id);
 986      list($parentsaself, $rparams) = $DB->get_in_or_equal($coursecontext->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'pc_');
 987      $params = array_merge($params, $rparams);
 988      list($roleids, $rparams) = $DB->get_in_or_equal($roles, SQL_PARAMS_NAMED, 'r_');
 989      $params = array_merge($params, $rparams);
 990  
 991      $sql = "SELECT ctx.path, rc.roleid, rc.capability, rc.permission
 992                   FROM {role_capabilities} rc
 993                   JOIN {context} ctx
 994                        ON (ctx.id = rc.contextid)
 995                   JOIN {context} cctx
 996                        ON (cctx.id = :c
 997                            AND (ctx.id $parentsaself OR ctx.path LIKE ".$DB->sql_concat('cctx.path',"'/%'")."))
 998                  WHERE rc.roleid $roleids
 999               ORDER BY rc.capability"; // fixed capability order is necessary for rdef dedupe
1000      $rs = $DB->get_recordset_sql($sql, $params);
1001  
1002      $newrdefs = array();
1003      foreach ($rs as $rd) {
1004          $k = $rd->path.':'.$rd->roleid;
1005          if (isset($accessdata['rdef'][$k])) {
1006              continue;
1007          }
1008          $newrdefs[$k][$rd->capability] = (int)$rd->permission;
1009      }
1010      $rs->close();
1011  
1012      // share new role definitions
1013      foreach ($newrdefs as $k=>$unused) {
1014          if (!isset($ACCESSLIB_PRIVATE->rolepermissions[$k])) {
1015              $ACCESSLIB_PRIVATE->rolepermissions[$k] = $newrdefs[$k];
1016          }
1017          $accessdata['rdef_count']++;
1018          $accessdata['rdef'][$k] =& $ACCESSLIB_PRIVATE->rolepermissions[$k];
1019      }
1020  
1021      $accessdata['loaded'][$coursecontext->instanceid] = 1;
1022  
1023      // we want to deduplicate the USER->access from time to time, this looks like a good place,
1024      // because we have to do it before the end of session
1025      dedupe_user_access();
1026  }
1027  
1028  /**
1029   * Add to the access ctrl array the data needed by a role for a given context.
1030   *
1031   * The data is added in the rdef key.
1032   * This role-centric function is useful for role_switching
1033   * and temporary course roles.
1034   *
1035   * @access private
1036   * @param int $roleid the id of the user
1037   * @param context $context needs path!
1038   * @param array $accessdata accessdata array (is modified)
1039   * @return array
1040   */
1041  function load_role_access_by_context($roleid, context $context, &$accessdata) {
1042      global $DB, $ACCESSLIB_PRIVATE;
1043  
1044      /* Get the relevant rolecaps into rdef
1045       * - relevant role caps
1046       *   - at ctx and above
1047       *   - below this ctx
1048       */
1049  
1050      if (empty($context->path)) {
1051          // weird, this should not happen
1052          return;
1053      }
1054  
1055      list($parentsaself, $params) = $DB->get_in_or_equal($context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'pc_');
1056      $params['roleid'] = $roleid;
1057      $params['childpath'] = $context->path.'/%';
1058  
1059      $sql = "SELECT ctx.path, rc.capability, rc.permission
1060                FROM {role_capabilities} rc
1061                JOIN {context} ctx ON (rc.contextid = ctx.id)
1062               WHERE rc.roleid = :roleid AND (ctx.id $parentsaself OR ctx.path LIKE :childpath)
1063            ORDER BY rc.capability"; // fixed capability order is necessary for rdef dedupe
1064      $rs = $DB->get_recordset_sql($sql, $params);
1065  
1066      $newrdefs = array();
1067      foreach ($rs as $rd) {
1068          $k = $rd->path.':'.$roleid;
1069          if (isset($accessdata['rdef'][$k])) {
1070              continue;
1071          }
1072          $newrdefs[$k][$rd->capability] = (int)$rd->permission;
1073      }
1074      $rs->close();
1075  
1076      // share new role definitions
1077      foreach ($newrdefs as $k=>$unused) {
1078          if (!isset($ACCESSLIB_PRIVATE->rolepermissions[$k])) {
1079              $ACCESSLIB_PRIVATE->rolepermissions[$k] = $newrdefs[$k];
1080          }
1081          $accessdata['rdef_count']++;
1082          $accessdata['rdef'][$k] =& $ACCESSLIB_PRIVATE->rolepermissions[$k];
1083      }
1084  }
1085  
1086  /**
1087   * Returns empty accessdata structure.
1088   *
1089   * @access private
1090   * @return array empt accessdata
1091   */
1092  function get_empty_accessdata() {
1093      $accessdata               = array(); // named list
1094      $accessdata['ra']         = array();
1095      $accessdata['rdef']       = array();
1096      $accessdata['rdef_count'] = 0;       // this bloody hack is necessary because count($array) is slooooowwww in PHP
1097      $accessdata['rdef_lcc']   = 0;       // rdef_count during the last compression
1098      $accessdata['loaded']     = array(); // loaded course contexts
1099      $accessdata['time']       = time();
1100      $accessdata['rsw']        = array();
1101  
1102      return $accessdata;
1103  }
1104  
1105  /**
1106   * Get accessdata for a given user.
1107   *
1108   * @access private
1109   * @param int $userid
1110   * @param bool $preloadonly true means do not return access array
1111   * @return array accessdata
1112   */
1113  function get_user_accessdata($userid, $preloadonly=false) {
1114      global $CFG, $ACCESSLIB_PRIVATE, $USER;
1115  
1116      if (!empty($USER->access['rdef']) and empty($ACCESSLIB_PRIVATE->rolepermissions)) {
1117          // share rdef from USER session with rolepermissions cache in order to conserve memory
1118          foreach ($USER->access['rdef'] as $k=>$v) {
1119              $ACCESSLIB_PRIVATE->rolepermissions[$k] =& $USER->access['rdef'][$k];
1120          }
1121          $ACCESSLIB_PRIVATE->accessdatabyuser[$USER->id] = $USER->access;
1122      }
1123  
1124      if (!isset($ACCESSLIB_PRIVATE->accessdatabyuser[$userid])) {
1125          if (empty($userid)) {
1126              if (!empty($CFG->notloggedinroleid)) {
1127                  $accessdata = get_role_access($CFG->notloggedinroleid);
1128              } else {
1129                  // weird
1130                  return get_empty_accessdata();
1131              }
1132  
1133          } else if (isguestuser($userid)) {
1134              if ($guestrole = get_guest_role()) {
1135                  $accessdata = get_role_access($guestrole->id);
1136              } else {
1137                  //weird
1138                  return get_empty_accessdata();
1139              }
1140  
1141          } else {
1142              $accessdata = get_user_access_sitewide($userid); // includes default role and frontpage role
1143          }
1144  
1145          $ACCESSLIB_PRIVATE->accessdatabyuser[$userid] = $accessdata;
1146      }
1147  
1148      if ($preloadonly) {
1149          return;
1150      } else {
1151          return $ACCESSLIB_PRIVATE->accessdatabyuser[$userid];
1152      }
1153  }
1154  
1155  /**
1156   * Try to minimise the size of $USER->access by eliminating duplicate override storage,
1157   * this function looks for contexts with the same overrides and shares them.
1158   *
1159   * @access private
1160   * @return void
1161   */
1162  function dedupe_user_access() {
1163      global $USER;
1164  
1165      if (CLI_SCRIPT) {
1166          // no session in CLI --> no compression necessary
1167          return;
1168      }
1169  
1170      if (empty($USER->access['rdef_count'])) {
1171          // weird, this should not happen
1172          return;
1173      }
1174  
1175      // the rdef is growing only, we never remove stuff from it, the rdef_lcc helps us to detect new stuff in rdef
1176      if ($USER->access['rdef_count'] - $USER->access['rdef_lcc'] > 10) {
1177          // do not compress after each change, wait till there is more stuff to be done
1178          return;
1179      }
1180  
1181      $hashmap = array();
1182      foreach ($USER->access['rdef'] as $k=>$def) {
1183          $hash = sha1(serialize($def));
1184          if (isset($hashmap[$hash])) {
1185              $USER->access['rdef'][$k] =& $hashmap[$hash];
1186          } else {
1187              $hashmap[$hash] =& $USER->access['rdef'][$k];
1188          }
1189      }
1190  
1191      $USER->access['rdef_lcc'] = $USER->access['rdef_count'];
1192  }
1193  
1194  /**
1195   * A convenience function to completely load all the capabilities
1196   * for the current user. It is called from has_capability() and functions change permissions.
1197   *
1198   * Call it only _after_ you've setup $USER and called check_enrolment_plugins();
1199   * @see check_enrolment_plugins()
1200   *
1201   * @access private
1202   * @return void
1203   */
1204  function load_all_capabilities() {
1205      global $USER;
1206  
1207      // roles not installed yet - we are in the middle of installation
1208      if (during_initial_install()) {
1209          return;
1210      }
1211  
1212      if (!isset($USER->id)) {
1213          // this should not happen
1214          $USER->id = 0;
1215      }
1216  
1217      unset($USER->access);
1218      $USER->access = get_user_accessdata($USER->id);
1219  
1220      // deduplicate the overrides to minimize session size
1221      dedupe_user_access();
1222  
1223      // Clear to force a refresh
1224      unset($USER->mycourses);
1225  
1226      // init/reset internal enrol caches - active course enrolments and temp access
1227      $USER->enrol = array('enrolled'=>array(), 'tempguest'=>array());
1228  }
1229  
1230  /**
1231   * A convenience function to completely reload all the capabilities
1232   * for the current user when roles have been updated in a relevant
1233   * context -- but PRESERVING switchroles and loginas.
1234   * This function resets all accesslib and context caches.
1235   *
1236   * That is - completely transparent to the user.
1237   *
1238   * Note: reloads $USER->access completely.
1239   *
1240   * @access private
1241   * @return void
1242   */
1243  function reload_all_capabilities() {
1244      global $USER, $DB, $ACCESSLIB_PRIVATE;
1245  
1246      // copy switchroles
1247      $sw = array();
1248      if (!empty($USER->access['rsw'])) {
1249          $sw = $USER->access['rsw'];
1250      }
1251  
1252      accesslib_clear_all_caches(true);
1253      unset($USER->access);
1254      $ACCESSLIB_PRIVATE->dirtycontexts = array(); // prevent dirty flags refetching on this page
1255  
1256      load_all_capabilities();
1257  
1258      foreach ($sw as $path => $roleid) {
1259          if ($record = $DB->get_record('context', array('path'=>$path))) {
1260              $context = context::instance_by_id($record->id);
1261              role_switch($roleid, $context);
1262          }
1263      }
1264  }
1265  
1266  /**
1267   * Adds a temp role to current USER->access array.
1268   *
1269   * Useful for the "temporary guest" access we grant to logged-in users.
1270   * This is useful for enrol plugins only.
1271   *
1272   * @since Moodle 2.2
1273   * @param context_course $coursecontext
1274   * @param int $roleid
1275   * @return void
1276   */
1277  function load_temp_course_role(context_course $coursecontext, $roleid) {
1278      global $USER, $SITE;
1279  
1280      if (empty($roleid)) {
1281          debugging('invalid role specified in load_temp_course_role()');
1282          return;
1283      }
1284  
1285      if ($coursecontext->instanceid == $SITE->id) {
1286          debugging('Can not use temp roles on the frontpage');
1287          return;
1288      }
1289  
1290      if (!isset($USER->access)) {
1291          load_all_capabilities();
1292      }
1293  
1294      $coursecontext->reload_if_dirty();
1295  
1296      if (isset($USER->access['ra'][$coursecontext->path][$roleid])) {
1297          return;
1298      }
1299  
1300      // load course stuff first
1301      load_course_context($USER->id, $coursecontext, $USER->access);
1302  
1303      $USER->access['ra'][$coursecontext->path][(int)$roleid] = (int)$roleid;
1304  
1305      load_role_access_by_context($roleid, $coursecontext, $USER->access);
1306  }
1307  
1308  /**
1309   * Removes any extra guest roles from current USER->access array.
1310   * This is useful for enrol plugins only.
1311   *
1312   * @since Moodle 2.2
1313   * @param context_course $coursecontext
1314   * @return void
1315   */
1316  function remove_temp_course_roles(context_course $coursecontext) {
1317      global $DB, $USER, $SITE;
1318  
1319      if ($coursecontext->instanceid == $SITE->id) {
1320          debugging('Can not use temp roles on the frontpage');
1321          return;
1322      }
1323  
1324      if (empty($USER->access['ra'][$coursecontext->path])) {
1325          //no roles here, weird
1326          return;
1327      }
1328  
1329      $sql = "SELECT DISTINCT ra.roleid AS id
1330                FROM {role_assignments} ra
1331               WHERE ra.contextid = :contextid AND ra.userid = :userid";
1332      $ras = $DB->get_records_sql($sql, array('contextid'=>$coursecontext->id, 'userid'=>$USER->id));
1333  
1334      $USER->access['ra'][$coursecontext->path] = array();
1335      foreach($ras as $r) {
1336          $USER->access['ra'][$coursecontext->path][(int)$r->id] = (int)$r->id;
1337      }
1338  }
1339  
1340  /**
1341   * Returns array of all role archetypes.
1342   *
1343   * @return array
1344   */
1345  function get_role_archetypes() {
1346      return array(
1347          'manager'        => 'manager',
1348          'coursecreator'  => 'coursecreator',
1349          'editingteacher' => 'editingteacher',
1350          'teacher'        => 'teacher',
1351          'student'        => 'student',
1352          'guest'          => 'guest',
1353          'user'           => 'user',
1354          'frontpage'      => 'frontpage'
1355      );
1356  }
1357  
1358  /**
1359   * Assign the defaults found in this capability definition to roles that have
1360   * the corresponding legacy capabilities assigned to them.
1361   *
1362   * @param string $capability
1363   * @param array $legacyperms an array in the format (example):
1364   *                      'guest' => CAP_PREVENT,
1365   *                      'student' => CAP_ALLOW,
1366   *                      'teacher' => CAP_ALLOW,
1367   *                      'editingteacher' => CAP_ALLOW,
1368   *                      'coursecreator' => CAP_ALLOW,
1369   *                      'manager' => CAP_ALLOW
1370   * @return boolean success or failure.
1371   */
1372  function assign_legacy_capabilities($capability, $legacyperms) {
1373  
1374      $archetypes = get_role_archetypes();
1375  
1376      foreach ($legacyperms as $type => $perm) {
1377  
1378          $systemcontext = context_system::instance();
1379          if ($type === 'admin') {
1380              debugging('Legacy type admin in access.php was renamed to manager, please update the code.');
1381              $type = 'manager';
1382          }
1383  
1384          if (!array_key_exists($type, $archetypes)) {
1385              print_error('invalidlegacy', '', '', $type);
1386          }
1387  
1388          if ($roles = get_archetype_roles($type)) {
1389              foreach ($roles as $role) {
1390                  // Assign a site level capability.
1391                  if (!assign_capability($capability, $perm, $role->id, $systemcontext->id)) {
1392                      return false;
1393                  }
1394              }
1395          }
1396      }
1397      return true;
1398  }
1399  
1400  /**
1401   * Verify capability risks.
1402   *
1403   * @param stdClass $capability a capability - a row from the capabilities table.
1404   * @return boolean whether this capability is safe - that is, whether people with the
1405   *      safeoverrides capability should be allowed to change it.
1406   */
1407  function is_safe_capability($capability) {
1408      return !((RISK_DATALOSS | RISK_MANAGETRUST | RISK_CONFIG | RISK_XSS | RISK_PERSONAL) & $capability->riskbitmask);
1409  }
1410  
1411  /**
1412   * Get the local override (if any) for a given capability in a role in a context
1413   *
1414   * @param int $roleid
1415   * @param int $contextid
1416   * @param string $capability
1417   * @return stdClass local capability override
1418   */
1419  function get_local_override($roleid, $contextid, $capability) {
1420      global $DB;
1421      return $DB->get_record('role_capabilities', array('roleid'=>$roleid, 'capability'=>$capability, 'contextid'=>$contextid));
1422  }
1423  
1424  /**
1425   * Returns context instance plus related course and cm instances
1426   *
1427   * @param int $contextid
1428   * @return array of ($context, $course, $cm)
1429   */
1430  function get_context_info_array($contextid) {
1431      global $DB;
1432  
1433      $context = context::instance_by_id($contextid, MUST_EXIST);
1434      $course  = null;
1435      $cm      = null;
1436  
1437      if ($context->contextlevel == CONTEXT_COURSE) {
1438          $course = $DB->get_record('course', array('id'=>$context->instanceid), '*', MUST_EXIST);
1439  
1440      } else if ($context->contextlevel == CONTEXT_MODULE) {
1441          $cm = get_coursemodule_from_id('', $context->instanceid, 0, false, MUST_EXIST);
1442          $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
1443  
1444      } else if ($context->contextlevel == CONTEXT_BLOCK) {
1445          $parent = $context->get_parent_context();
1446  
1447          if ($parent->contextlevel == CONTEXT_COURSE) {
1448              $course = $DB->get_record('course', array('id'=>$parent->instanceid), '*', MUST_EXIST);
1449          } else if ($parent->contextlevel == CONTEXT_MODULE) {
1450              $cm = get_coursemodule_from_id('', $parent->instanceid, 0, false, MUST_EXIST);
1451              $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
1452          }
1453      }
1454  
1455      return array($context, $course, $cm);
1456  }
1457  
1458  /**
1459   * Function that creates a role
1460   *
1461   * @param string $name role name
1462   * @param string $shortname role short name
1463   * @param string $description role description
1464   * @param string $archetype
1465   * @return int id or dml_exception
1466   */
1467  function create_role($name, $shortname, $description, $archetype = '') {
1468      global $DB;
1469  
1470      if (strpos($archetype, 'moodle/legacy:') !== false) {
1471          throw new coding_exception('Use new role archetype parameter in create_role() instead of old legacy capabilities.');
1472      }
1473  
1474      // verify role archetype actually exists
1475      $archetypes = get_role_archetypes();
1476      if (empty($archetypes[$archetype])) {
1477          $archetype = '';
1478      }
1479  
1480      // Insert the role record.
1481      $role = new stdClass();
1482      $role->name        = $name;
1483      $role->shortname   = $shortname;
1484      $role->description = $description;
1485      $role->archetype   = $archetype;
1486  
1487      //find free sortorder number
1488      $role->sortorder = $DB->get_field('role', 'MAX(sortorder) + 1', array());
1489      if (empty($role->sortorder)) {
1490          $role->sortorder = 1;
1491      }
1492      $id = $DB->insert_record('role', $role);
1493  
1494      return $id;
1495  }
1496  
1497  /**
1498   * Function that deletes a role and cleanups up after it
1499   *
1500   * @param int $roleid id of role to delete
1501   * @return bool always true
1502   */
1503  function delete_role($roleid) {
1504      global $DB;
1505  
1506      // first unssign all users
1507      role_unassign_all(array('roleid'=>$roleid));
1508  
1509      // cleanup all references to this role, ignore errors
1510      $DB->delete_records('role_capabilities',   array('roleid'=>$roleid));
1511      $DB->delete_records('role_allow_assign',   array('roleid'=>$roleid));
1512      $DB->delete_records('role_allow_assign',   array('allowassign'=>$roleid));
1513      $DB->delete_records('role_allow_override', array('roleid'=>$roleid));
1514      $DB->delete_records('role_allow_override', array('allowoverride'=>$roleid));
1515      $DB->delete_records('role_names',          array('roleid'=>$roleid));
1516      $DB->delete_records('role_context_levels', array('roleid'=>$roleid));
1517  
1518      // Get role record before it's deleted.
1519      $role = $DB->get_record('role', array('id'=>$roleid));
1520  
1521      // Finally delete the role itself.
1522      $DB->delete_records('role', array('id'=>$roleid));
1523  
1524      // Trigger event.
1525      $event = \core\event\role_deleted::create(
1526          array(
1527              'context' => context_system::instance(),
1528              'objectid' => $roleid,
1529              'other' =>
1530                  array(
1531                      'shortname' => $role->shortname,
1532                      'description' => $role->description,
1533                      'archetype' => $role->archetype
1534                  )
1535              )
1536          );
1537      $event->add_record_snapshot('role', $role);
1538      $event->trigger();
1539  
1540      return true;
1541  }
1542  
1543  /**
1544   * Function to write context specific overrides, or default capabilities.
1545   *
1546   * NOTE: use $context->mark_dirty() after this
1547   *
1548   * @param string $capability string name
1549   * @param int $permission CAP_ constants
1550   * @param int $roleid role id
1551   * @param int|context $contextid context id
1552   * @param bool $overwrite
1553   * @return bool always true or exception
1554   */
1555  function assign_capability($capability, $permission, $roleid, $contextid, $overwrite = false) {
1556      global $USER, $DB;
1557  
1558      if ($contextid instanceof context) {
1559          $context = $contextid;
1560      } else {
1561          $context = context::instance_by_id($contextid);
1562      }
1563  
1564      if (empty($permission) || $permission == CAP_INHERIT) { // if permission is not set
1565          unassign_capability($capability, $roleid, $context->id);
1566          return true;
1567      }
1568  
1569      $existing = $DB->get_record('role_capabilities', array('contextid'=>$context->id, 'roleid'=>$roleid, 'capability'=>$capability));
1570  
1571      if ($existing and !$overwrite) {   // We want to keep whatever is there already
1572          return true;
1573      }
1574  
1575      $cap = new stdClass();
1576      $cap->contextid    = $context->id;
1577      $cap->roleid       = $roleid;
1578      $cap->capability   = $capability;
1579      $cap->permission   = $permission;
1580      $cap->timemodified = time();
1581      $cap->modifierid   = empty($USER->id) ? 0 : $USER->id;
1582  
1583      if ($existing) {
1584          $cap->id = $existing->id;
1585          $DB->update_record('role_capabilities', $cap);
1586      } else {
1587          if ($DB->record_exists('context', array('id'=>$context->id))) {
1588              $DB->insert_record('role_capabilities', $cap);
1589          }
1590      }
1591      return true;
1592  }
1593  
1594  /**
1595   * Unassign a capability from a role.
1596   *
1597   * NOTE: use $context->mark_dirty() after this
1598   *
1599   * @param string $capability the name of the capability
1600   * @param int $roleid the role id
1601   * @param int|context $contextid null means all contexts
1602   * @return boolean true or exception
1603   */
1604  function unassign_capability($capability, $roleid, $contextid = null) {
1605      global $DB;
1606  
1607      if (!empty($contextid)) {
1608          if ($contextid instanceof context) {
1609              $context = $contextid;
1610          } else {
1611              $context = context::instance_by_id($contextid);
1612          }
1613          // delete from context rel, if this is the last override in this context
1614          $DB->delete_records('role_capabilities', array('capability'=>$capability, 'roleid'=>$roleid, 'contextid'=>$context->id));
1615      } else {
1616          $DB->delete_records('role_capabilities', array('capability'=>$capability, 'roleid'=>$roleid));
1617      }
1618      return true;
1619  }
1620  
1621  /**
1622   * Get the roles that have a given capability assigned to it
1623   *
1624   * This function does not resolve the actual permission of the capability.
1625   * It just checks for permissions and overrides.
1626   * Use get_roles_with_cap_in_context() if resolution is required.
1627   *
1628   * @param string $capability capability name (string)
1629   * @param string $permission optional, the permission defined for this capability
1630   *                      either CAP_ALLOW, CAP_PREVENT or CAP_PROHIBIT. Defaults to null which means any.
1631   * @param stdClass $context null means any
1632   * @return array of role records
1633   */
1634  function get_roles_with_capability($capability, $permission = null, $context = null) {
1635      global $DB;
1636  
1637      if ($context) {
1638          $contexts = $context->get_parent_context_ids(true);
1639          list($insql, $params) = $DB->get_in_or_equal($contexts, SQL_PARAMS_NAMED, 'ctx');
1640          $contextsql = "AND rc.contextid $insql";
1641      } else {
1642          $params = array();
1643          $contextsql = '';
1644      }
1645  
1646      if ($permission) {
1647          $permissionsql = " AND rc.permission = :permission";
1648          $params['permission'] = $permission;
1649      } else {
1650          $permissionsql = '';
1651      }
1652  
1653      $sql = "SELECT r.*
1654                FROM {role} r
1655               WHERE r.id IN (SELECT rc.roleid
1656                                FROM {role_capabilities} rc
1657                               WHERE rc.capability = :capname
1658                                     $contextsql
1659                                     $permissionsql)";
1660      $params['capname'] = $capability;
1661  
1662  
1663      return $DB->get_records_sql($sql, $params);
1664  }
1665  
1666  /**
1667   * This function makes a role-assignment (a role for a user in a particular context)
1668   *
1669   * @param int $roleid the role of the id
1670   * @param int $userid userid
1671   * @param int|context $contextid id of the context
1672   * @param string $component example 'enrol_ldap', defaults to '' which means manual assignment,
1673   * @param int $itemid id of enrolment/auth plugin
1674   * @param string $timemodified defaults to current time
1675   * @return int new/existing id of the assignment
1676   */
1677  function role_assign($roleid, $userid, $contextid, $component = '', $itemid = 0, $timemodified = '') {
1678      global $USER, $DB;
1679  
1680      // first of all detect if somebody is using old style parameters
1681      if ($contextid === 0 or is_numeric($component)) {
1682          throw new coding_exception('Invalid call to role_assign(), code needs to be updated to use new order of parameters');
1683      }
1684  
1685      // now validate all parameters
1686      if (empty($roleid)) {
1687          throw new coding_exception('Invalid call to role_assign(), roleid can not be empty');
1688      }
1689  
1690      if (empty($userid)) {
1691          throw new coding_exception('Invalid call to role_assign(), userid can not be empty');
1692      }
1693  
1694      if ($itemid) {
1695          if (strpos($component, '_') === false) {
1696              throw new coding_exception('Invalid call to role_assign(), component must start with plugin type such as"enrol_" when itemid specified', 'component:'.$component);
1697          }
1698      } else {
1699          $itemid = 0;
1700          if ($component !== '' and strpos($component, '_') === false) {
1701              throw new coding_exception('Invalid call to role_assign(), invalid component string', 'component:'.$component);
1702          }
1703      }
1704  
1705      if (!$DB->record_exists('user', array('id'=>$userid, 'deleted'=>0))) {
1706          throw new coding_exception('User ID does not exist or is deleted!', 'userid:'.$userid);
1707      }
1708  
1709      if ($contextid instanceof context) {
1710          $context = $contextid;
1711      } else {
1712          $context = context::instance_by_id($contextid, MUST_EXIST);
1713      }
1714  
1715      if (!$timemodified) {
1716          $timemodified = time();
1717      }
1718  
1719      // Check for existing entry
1720      $ras = $DB->get_records('role_assignments', array('roleid'=>$roleid, 'contextid'=>$context->id, 'userid'=>$userid, 'component'=>$component, 'itemid'=>$itemid), 'id');
1721  
1722      if ($ras) {
1723          // role already assigned - this should not happen
1724          if (count($ras) > 1) {
1725              // very weird - remove all duplicates!
1726              $ra = array_shift($ras);
1727              foreach ($ras as $r) {
1728                  $DB->delete_records('role_assignments', array('id'=>$r->id));
1729              }
1730          } else {
1731              $ra = reset($ras);
1732          }
1733  
1734          // actually there is no need to update, reset anything or trigger any event, so just return
1735          return $ra->id;
1736      }
1737  
1738      // Create a new entry
1739      $ra = new stdClass();
1740      $ra->roleid       = $roleid;
1741      $ra->contextid    = $context->id;
1742      $ra->userid       = $userid;
1743      $ra->component    = $component;
1744      $ra->itemid       = $itemid;
1745      $ra->timemodified = $timemodified;
1746      $ra->modifierid   = empty($USER->id) ? 0 : $USER->id;
1747      $ra->sortorder    = 0;
1748  
1749      $ra->id = $DB->insert_record('role_assignments', $ra);
1750  
1751      // mark context as dirty - again expensive, but needed
1752      $context->mark_dirty();
1753  
1754      if (!empty($USER->id) && $USER->id == $userid) {
1755          // If the user is the current user, then do full reload of capabilities too.
1756          reload_all_capabilities();
1757      }
1758  
1759      $event = \core\event\role_assigned::create(array(
1760          'context' => $context,
1761          'objectid' => $ra->roleid,
1762          'relateduserid' => $ra->userid,
1763          'other' => array(
1764              'id' => $ra->id,
1765              'component' => $ra->component,
1766              'itemid' => $ra->itemid
1767          )
1768      ));
1769      $event->add_record_snapshot('role_assignments', $ra);
1770      $event->trigger();
1771  
1772      return $ra->id;
1773  }
1774  
1775  /**
1776   * Removes one role assignment
1777   *
1778   * @param int $roleid
1779   * @param int  $userid
1780   * @param int  $contextid
1781   * @param string $component
1782   * @param int  $itemid
1783   * @return void
1784   */
1785  function role_unassign($roleid, $userid, $contextid, $component = '', $itemid = 0) {
1786      // first make sure the params make sense
1787      if ($roleid == 0 or $userid == 0 or $contextid == 0) {
1788          throw new coding_exception('Invalid call to role_unassign(), please use role_unassign_all() when removing multiple role assignments');
1789      }
1790  
1791      if ($itemid) {
1792          if (strpos($component, '_') === false) {
1793              throw new coding_exception('Invalid call to role_assign(), component must start with plugin type such as "enrol_" when itemid specified', 'component:'.$component);
1794          }
1795      } else {
1796          $itemid = 0;
1797          if ($component !== '' and strpos($component, '_') === false) {
1798              throw new coding_exception('Invalid call to role_assign(), invalid component string', 'component:'.$component);
1799          }
1800      }
1801  
1802      role_unassign_all(array('roleid'=>$roleid, 'userid'=>$userid, 'contextid'=>$contextid, 'component'=>$component, 'itemid'=>$itemid), false, false);
1803  }
1804  
1805  /**
1806   * Removes multiple role assignments, parameters may contain:
1807   *   'roleid', 'userid', 'contextid', 'component', 'enrolid'.
1808   *
1809   * @param array $params role assignment parameters
1810   * @param bool $subcontexts unassign in subcontexts too
1811   * @param bool $includemanual include manual role assignments too
1812   * @return void
1813   */
1814  function role_unassign_all(array $params, $subcontexts = false, $includemanual = false) {
1815      global $USER, $CFG, $DB;
1816  
1817      if (!$params) {
1818          throw new coding_exception('Missing parameters in role_unsassign_all() call');
1819      }
1820  
1821      $allowed = array('roleid', 'userid', 'contextid', 'component', 'itemid');
1822      foreach ($params as $key=>$value) {
1823          if (!in_array($key, $allowed)) {
1824              throw new coding_exception('Unknown role_unsassign_all() parameter key', 'key:'.$key);
1825          }
1826      }
1827  
1828      if (isset($params['component']) and $params['component'] !== '' and strpos($params['component'], '_') === false) {
1829          throw new coding_exception('Invalid component paramter in role_unsassign_all() call', 'component:'.$params['component']);
1830      }
1831  
1832      if ($includemanual) {
1833          if (!isset($params['component']) or $params['component'] === '') {
1834              throw new coding_exception('include manual parameter requires component parameter in role_unsassign_all() call');
1835          }
1836      }
1837  
1838      if ($subcontexts) {
1839          if (empty($params['contextid'])) {
1840              throw new coding_exception('subcontexts paramtere requires component parameter in role_unsassign_all() call');
1841          }
1842      }
1843  
1844      $ras = $DB->get_records('role_assignments', $params);
1845      foreach($ras as $ra) {
1846          $DB->delete_records('role_assignments', array('id'=>$ra->id));
1847          if ($context = context::instance_by_id($ra->contextid, IGNORE_MISSING)) {
1848              // this is a bit expensive but necessary
1849              $context->mark_dirty();
1850              // If the user is the current user, then do full reload of capabilities too.
1851              if (!empty($USER->id) && $USER->id == $ra->userid) {
1852                  reload_all_capabilities();
1853              }
1854              $event = \core\event\role_unassigned::create(array(
1855                  'context' => $context,
1856                  'objectid' => $ra->roleid,
1857                  'relateduserid' => $ra->userid,
1858                  'other' => array(
1859                      'id' => $ra->id,
1860                      'component' => $ra->component,
1861                      'itemid' => $ra->itemid
1862                  )
1863              ));
1864              $event->add_record_snapshot('role_assignments', $ra);
1865              $event->trigger();
1866          }
1867      }
1868      unset($ras);
1869  
1870      // process subcontexts
1871      if ($subcontexts and $context = context::instance_by_id($params['contextid'], IGNORE_MISSING)) {
1872          if ($params['contextid'] instanceof context) {
1873              $context = $params['contextid'];
1874          } else {
1875              $context = context::instance_by_id($params['contextid'], IGNORE_MISSING);
1876          }
1877  
1878          if ($context) {
1879              $contexts = $context->get_child_contexts();
1880              $mparams = $params;
1881              foreach($contexts as $context) {
1882                  $mparams['contextid'] = $context->id;
1883                  $ras = $DB->get_records('role_assignments', $mparams);
1884                  foreach($ras as $ra) {
1885                      $DB->delete_records('role_assignments', array('id'=>$ra->id));
1886                      // this is a bit expensive but necessary
1887                      $context->mark_dirty();
1888                      // If the user is the current user, then do full reload of capabilities too.
1889                      if (!empty($USER->id) && $USER->id == $ra->userid) {
1890                          reload_all_capabilities();
1891                      }
1892                      $event = \core\event\role_unassigned::create(
1893                          array('context'=>$context, 'objectid'=>$ra->roleid, 'relateduserid'=>$ra->userid,
1894                              'other'=>array('id'=>$ra->id, 'component'=>$ra->component, 'itemid'=>$ra->itemid)));
1895                      $event->add_record_snapshot('role_assignments', $ra);
1896                      $event->trigger();
1897                  }
1898              }
1899          }
1900      }
1901  
1902      // do this once more for all manual role assignments
1903      if ($includemanual) {
1904          $params['component'] = '';
1905          role_unassign_all($params, $subcontexts, false);
1906      }
1907  }
1908  
1909  /**
1910   * Determines if a user is currently logged in
1911   *
1912   * @category   access
1913   *
1914   * @return bool
1915   */
1916  function isloggedin() {
1917      global $USER;
1918  
1919      return (!empty($USER->id));
1920  }
1921  
1922  /**
1923   * Determines if a user is logged in as real guest user with username 'guest'.
1924   *
1925   * @category   access
1926   *
1927   * @param int|object $user mixed user object or id, $USER if not specified
1928   * @return bool true if user is the real guest user, false if not logged in or other user
1929   */
1930  function isguestuser($user = null) {
1931      global $USER, $DB, $CFG;
1932  
1933      // make sure we have the user id cached in config table, because we are going to use it a lot
1934      if (empty($CFG->siteguest)) {
1935          if (!$guestid = $DB->get_field('user', 'id', array('username'=>'guest', 'mnethostid'=>$CFG->mnet_localhost_id))) {
1936              // guest does not exist yet, weird
1937              return false;
1938          }
1939          set_config('siteguest', $guestid);
1940      }
1941      if ($user === null) {
1942          $user = $USER;
1943      }
1944  
1945      if ($user === null) {
1946          // happens when setting the $USER
1947          return false;
1948  
1949      } else if (is_numeric($user)) {
1950          return ($CFG->siteguest == $user);
1951  
1952      } else if (is_object($user)) {
1953          if (empty($user->id)) {
1954              return false; // not logged in means is not be guest
1955          } else {
1956              return ($CFG->siteguest == $user->id);
1957          }
1958  
1959      } else {
1960          throw new coding_exception('Invalid user parameter supplied for isguestuser() function!');
1961      }
1962  }
1963  
1964  /**
1965   * Does user have a (temporary or real) guest access to course?
1966   *
1967   * @category   access
1968   *
1969   * @param context $context
1970   * @param stdClass|int $user
1971   * @return bool
1972   */
1973  function is_guest(context $context, $user = null) {
1974      global $USER;
1975  
1976      // first find the course context
1977      $coursecontext = $context->get_course_context();
1978  
1979      // make sure there is a real user specified
1980      if ($user === null) {
1981          $userid = isset($USER->id) ? $USER->id : 0;
1982      } else {
1983          $userid = is_object($user) ? $user->id : $user;
1984      }
1985  
1986      if (isguestuser($userid)) {
1987          // can not inspect or be enrolled
1988          return true;
1989      }
1990  
1991      if (has_capability('moodle/course:view', $coursecontext, $user)) {
1992          // viewing users appear out of nowhere, they are neither guests nor participants
1993          return false;
1994      }
1995  
1996      // consider only real active enrolments here
1997      if (is_enrolled($coursecontext, $user, '', true)) {
1998          return false;
1999      }
2000  
2001      return true;
2002  }
2003  
2004  /**
2005   * Returns true if the user has moodle/course:view capability in the course,
2006   * this is intended for admins, managers (aka small admins), inspectors, etc.
2007   *
2008   * @category   access
2009   *
2010   * @param context $context
2011   * @param int|stdClass $user if null $USER is used
2012   * @param string $withcapability extra capability name
2013   * @return bool
2014   */
2015  function is_viewing(context $context, $user = null, $withcapability = '') {
2016      // first find the course context
2017      $coursecontext = $context->get_course_context();
2018  
2019      if (isguestuser($user)) {
2020          // can not inspect
2021          return false;
2022      }
2023  
2024      if (!has_capability('moodle/course:view', $coursecontext, $user)) {
2025          // admins are allowed to inspect courses
2026          return false;
2027      }
2028  
2029      if ($withcapability and !has_capability($withcapability, $context, $user)) {
2030          // site admins always have the capability, but the enrolment above blocks
2031          return false;
2032      }
2033  
2034      return true;
2035  }
2036  
2037  /**
2038   * Returns true if user is enrolled (is participating) in course
2039   * this is intended for students and teachers.
2040   *
2041   * Since 2.2 the result for active enrolments and current user are cached.
2042   *
2043   * @package   core_enrol
2044   * @category  access
2045   *
2046   * @param context $context
2047   * @param int|stdClass $user if null $USER is used, otherwise user object or id expected
2048   * @param string $withcapability extra capability name
2049   * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
2050   * @return bool
2051   */
2052  function is_enrolled(context $context, $user = null, $withcapability = '', $onlyactive = false) {
2053      global $USER, $DB;
2054  
2055      // first find the course context
2056      $coursecontext = $context->get_course_context();
2057  
2058      // make sure there is a real user specified
2059      if ($user === null) {
2060          $userid = isset($USER->id) ? $USER->id : 0;
2061      } else {
2062          $userid = is_object($user) ? $user->id : $user;
2063      }
2064  
2065      if (empty($userid)) {
2066          // not-logged-in!
2067          return false;
2068      } else if (isguestuser($userid)) {
2069          // guest account can not be enrolled anywhere
2070          return false;
2071      }
2072  
2073      if ($coursecontext->instanceid == SITEID) {
2074          // everybody participates on frontpage
2075      } else {
2076          // try cached info first - the enrolled flag is set only when active enrolment present
2077          if ($USER->id == $userid) {
2078              $coursecontext->reload_if_dirty();
2079              if (isset($USER->enrol['enrolled'][$coursecontext->instanceid])) {
2080                  if ($USER->enrol['enrolled'][$coursecontext->instanceid] > time()) {
2081                      if ($withcapability and !has_capability($withcapability, $context, $userid)) {
2082                          return false;
2083                      }
2084                      return true;
2085                  }
2086              }
2087          }
2088  
2089          if ($onlyactive) {
2090              // look for active enrolments only
2091              $until = enrol_get_enrolment_end($coursecontext->instanceid, $userid);
2092  
2093              if ($until === false) {
2094                  return false;
2095              }
2096  
2097              if ($USER->id == $userid) {
2098                  if ($until == 0) {
2099                      $until = ENROL_MAX_TIMESTAMP;
2100                  }
2101                  $USER->enrol['enrolled'][$coursecontext->instanceid] = $until;
2102                  if (isset($USER->enrol['tempguest'][$coursecontext->instanceid])) {
2103                      unset($USER->enrol['tempguest'][$coursecontext->instanceid]);
2104                      remove_temp_course_roles($coursecontext);
2105                  }
2106              }
2107  
2108          } else {
2109              // any enrolment is good for us here, even outdated, disabled or inactive
2110              $sql = "SELECT 'x'
2111                        FROM {user_enrolments} ue
2112                        JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :courseid)
2113                        JOIN {user} u ON u.id = ue.userid
2114                       WHERE ue.userid = :userid AND u.deleted = 0";
2115              $params = array('userid'=>$userid, 'courseid'=>$coursecontext->instanceid);
2116              if (!$DB->record_exists_sql($sql, $params)) {
2117                  return false;
2118              }
2119          }
2120      }
2121  
2122      if ($withcapability and !has_capability($withcapability, $context, $userid)) {
2123          return false;
2124      }
2125  
2126      return true;
2127  }
2128  
2129  /**
2130   * Returns true if the user is able to access the course.
2131   *
2132   * This function is in no way, shape, or form a substitute for require_login.
2133   * It should only be used in circumstances where it is not possible to call require_login
2134   * such as the navigation.
2135   *
2136   * This function checks many of the methods of access to a course such as the view
2137   * capability, enrollments, and guest access. It also makes use of the cache
2138   * generated by require_login for guest access.
2139   *
2140   * The flags within the $USER object that are used here should NEVER be used outside
2141   * of this function can_access_course and require_login. Doing so WILL break future
2142   * versions.
2143   *
2144   * @param stdClass $course record
2145   * @param stdClass|int|null $user user record or id, current user if null
2146   * @param string $withcapability Check for this capability as well.
2147   * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
2148   * @return boolean Returns true if the user is able to access the course
2149   */
2150  function can_access_course(stdClass $course, $user = null, $withcapability = '', $onlyactive = false) {
2151      global $DB, $USER;
2152  
2153      // this function originally accepted $coursecontext parameter
2154      if ($course instanceof context) {
2155          if ($course instanceof context_course) {
2156              debugging('deprecated context parameter, please use $course record');
2157              $coursecontext = $course;
2158              $course = $DB->get_record('course', array('id'=>$coursecontext->instanceid));
2159          } else {
2160              debugging('Invalid context parameter, please use $course record');
2161              return false;
2162          }
2163      } else {
2164          $coursecontext = context_course::instance($course->id);
2165      }
2166  
2167      if (!isset($USER->id)) {
2168          // should never happen
2169          $USER->id = 0;
2170          debugging('Course access check being performed on a user with no ID.', DEBUG_DEVELOPER);
2171      }
2172  
2173      // make sure there is a user specified
2174      if ($user === null) {
2175          $userid = $USER->id;
2176      } else {
2177          $userid = is_object($user) ? $user->id : $user;
2178      }
2179      unset($user);
2180  
2181      if ($withcapability and !has_capability($withcapability, $coursecontext, $userid)) {
2182          return false;
2183      }
2184  
2185      if ($userid == $USER->id) {
2186          if (!empty($USER->access['rsw'][$coursecontext->path])) {
2187              // the fact that somebody switched role means they can access the course no matter to what role they switched
2188              return true;
2189          }
2190      }
2191  
2192      if (!$course->visible and !has_capability('moodle/course:viewhiddencourses', $coursecontext, $userid)) {
2193          return false;
2194      }
2195  
2196      if (is_viewing($coursecontext, $userid)) {
2197          return true;
2198      }
2199  
2200      if ($userid != $USER->id) {
2201          // for performance reasons we do not verify temporary guest access for other users, sorry...
2202          return is_enrolled($coursecontext, $userid, '', $onlyactive);
2203      }
2204  
2205      // === from here we deal only with $USER ===
2206  
2207      $coursecontext->reload_if_dirty();
2208  
2209      if (isset($USER->enrol['enrolled'][$course->id])) {
2210          if ($USER->enrol['enrolled'][$course->id] > time()) {
2211              return true;
2212          }
2213      }
2214      if (isset($USER->enrol['tempguest'][$course->id])) {
2215          if ($USER->enrol['tempguest'][$course->id] > time()) {
2216              return true;
2217          }
2218      }
2219  
2220      if (is_enrolled($coursecontext, $USER, '', $onlyactive)) {
2221          return true;
2222      }
2223  
2224      // if not enrolled try to gain temporary guest access
2225      $instances = $DB->get_records('enrol', array('courseid'=>$course->id, 'status'=>ENROL_INSTANCE_ENABLED), 'sortorder, id ASC');
2226      $enrols = enrol_get_plugins(true);
2227      foreach($instances as $instance) {
2228          if (!isset($enrols[$instance->enrol])) {
2229              continue;
2230          }
2231          // Get a duration for the guest access, a timestamp in the future, 0 (always) or false.
2232          $until = $enrols[$instance->enrol]->try_guestaccess($instance);
2233          if ($until !== false and $until > time()) {
2234              $USER->enrol['tempguest'][$course->id] = $until;
2235              return true;
2236          }
2237      }
2238      if (isset($USER->enrol['tempguest'][$course->id])) {
2239          unset($USER->enrol['tempguest'][$course->id]);
2240          remove_temp_course_roles($coursecontext);
2241      }
2242  
2243      return false;
2244  }
2245  
2246  /**
2247   * Returns array with sql code and parameters returning all ids
2248   * of users enrolled into course.
2249   *
2250   * This function is using 'eu[0-9]+_' prefix for table names and parameters.
2251   *
2252   * @package   core_enrol
2253   * @category  access
2254   *
2255   * @param context $context
2256   * @param string $withcapability
2257   * @param int $groupid 0 means ignore groups, any other value limits the result by group id
2258   * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
2259   * @return array list($sql, $params)
2260   */
2261  function get_enrolled_sql(context $context, $withcapability = '', $groupid = 0, $onlyactive = false) {
2262      global $DB, $CFG;
2263  
2264      // use unique prefix just in case somebody makes some SQL magic with the result
2265      static $i = 0;
2266      $i++;
2267      $prefix = 'eu'.$i.'_';
2268  
2269      // first find the course context
2270      $coursecontext = $context->get_course_context();
2271  
2272      $isfrontpage = ($coursecontext->instanceid == SITEID);
2273  
2274      $joins  = array();
2275      $wheres = array();
2276      $params = array();
2277  
2278      list($contextids, $contextpaths) = get_context_info_list($context);
2279  
2280      // get all relevant capability info for all roles
2281      if ($withcapability) {
2282          list($incontexts, $cparams) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, 'ctx');
2283          $cparams['cap'] = $withcapability;
2284  
2285          $defs = array();
2286          $sql = "SELECT rc.id, rc.roleid, rc.permission, ctx.path
2287                    FROM {role_capabilities} rc
2288                    JOIN {context} ctx on rc.contextid = ctx.id
2289                   WHERE rc.contextid $incontexts AND rc.capability = :cap";
2290          $rcs = $DB->get_records_sql($sql, $cparams);
2291          foreach ($rcs as $rc) {
2292              $defs[$rc->path][$rc->roleid] = $rc->permission;
2293          }
2294  
2295          $access = array();
2296          if (!empty($defs)) {
2297              foreach ($contextpaths as $path) {
2298                  if (empty($defs[$path])) {
2299                      continue;
2300                  }
2301                  foreach($defs[$path] as $roleid => $perm) {
2302                      if ($perm == CAP_PROHIBIT) {
2303                          $access[$roleid] = CAP_PROHIBIT;
2304                          continue;
2305                      }
2306                      if (!isset($access[$roleid])) {
2307                          $access[$roleid] = (int)$perm;
2308                      }
2309                  }
2310              }
2311          }
2312  
2313          unset($defs);
2314  
2315          // make lists of roles that are needed and prohibited
2316          $needed     = array(); // one of these is enough
2317          $prohibited = array(); // must not have any of these
2318          foreach ($access as $roleid => $perm) {
2319              if ($perm == CAP_PROHIBIT) {
2320                  unset($needed[$roleid]);
2321                  $prohibited[$roleid] = true;
2322              } else if ($perm == CAP_ALLOW and empty($prohibited[$roleid])) {
2323                  $needed[$roleid] = true;
2324              }
2325          }
2326  
2327          $defaultuserroleid      = isset($CFG->defaultuserroleid) ? $CFG->defaultuserroleid : 0;
2328          $defaultfrontpageroleid = isset($CFG->defaultfrontpageroleid) ? $CFG->defaultfrontpageroleid : 0;
2329  
2330          $nobody = false;
2331  
2332          if ($isfrontpage) {
2333              if (!empty($prohibited[$defaultuserroleid]) or !empty($prohibited[$defaultfrontpageroleid])) {
2334                  $nobody = true;
2335              } else if (!empty($needed[$defaultuserroleid]) or !empty($needed[$defaultfrontpageroleid])) {
2336                  // everybody not having prohibit has the capability
2337                  $needed = array();
2338              } else if (empty($needed)) {
2339                  $nobody = true;
2340              }
2341          } else {
2342              if (!empty($prohibited[$defaultuserroleid])) {
2343                  $nobody = true;
2344              } else if (!empty($needed[$defaultuserroleid])) {
2345                  // everybody not having prohibit has the capability
2346                  $needed = array();
2347              } else if (empty($needed)) {
2348                  $nobody = true;
2349              }
2350          }
2351  
2352          if ($nobody) {
2353              // nobody can match so return some SQL that does not return any results
2354              $wheres[] = "1 = 2";
2355  
2356          } else {
2357  
2358              if ($needed) {
2359                  $ctxids = implode(',', $contextids);
2360                  $roleids = implode(',', array_keys($needed));
2361                  $joins[] = "JOIN {role_assignments} {$prefix}ra3 ON ({$prefix}ra3.userid = {$prefix}u.id AND {$prefix}ra3.roleid IN ($roleids) AND {$prefix}ra3.contextid IN ($ctxids))";
2362              }
2363  
2364              if ($prohibited) {
2365                  $ctxids = implode(',', $contextids);
2366                  $roleids = implode(',', array_keys($prohibited));
2367                  $joins[] = "LEFT JOIN {role_assignments} {$prefix}ra4 ON ({$prefix}ra4.userid = {$prefix}u.id AND {$prefix}ra4.roleid IN ($roleids) AND {$prefix}ra4.contextid IN ($ctxids))";
2368                  $wheres[] = "{$prefix}ra4.id IS NULL";
2369              }
2370  
2371              if ($groupid) {
2372                  $joins[] = "JOIN {groups_members} {$prefix}gm ON ({$prefix}gm.userid = {$prefix}u.id AND {$prefix}gm.groupid = :{$prefix}gmid)";
2373                  $params["{$prefix}gmid"] = $groupid;
2374              }
2375          }
2376  
2377      } else {
2378          if ($groupid) {
2379              $joins[] = "JOIN {groups_members} {$prefix}gm ON ({$prefix}gm.userid = {$prefix}u.id AND {$prefix}gm.groupid = :{$prefix}gmid)";
2380              $params["{$prefix}gmid"] = $groupid;
2381          }
2382      }
2383  
2384      $wheres[] = "{$prefix}u.deleted = 0 AND {$prefix}u.id <> :{$prefix}guestid";
2385      $params["{$prefix}guestid"] = $CFG->siteguest;
2386  
2387      if ($isfrontpage) {
2388          // all users are "enrolled" on the frontpage
2389      } else {
2390          $joins[] = "JOIN {user_enrolments} {$prefix}ue ON {$prefix}ue.userid = {$prefix}u.id";
2391          $joins[] = "JOIN {enrol} {$prefix}e ON ({$prefix}e.id = {$prefix}ue.enrolid AND {$prefix}e.courseid = :{$prefix}courseid)";
2392          $params[$prefix.'courseid'] = $coursecontext->instanceid;
2393  
2394          if ($onlyactive) {
2395              $wheres[] = "{$prefix}ue.status = :{$prefix}active AND {$prefix}e.status = :{$prefix}enabled";
2396              $wheres[] = "{$prefix}ue.timestart < :{$prefix}now1 AND ({$prefix}ue.timeend = 0 OR {$prefix}ue.timeend > :{$prefix}now2)";
2397              $now = round(time(), -2); // rounding helps caching in DB
2398              $params = array_merge($params, array($prefix.'enabled'=>ENROL_INSTANCE_ENABLED,
2399                                                   $prefix.'active'=>ENROL_USER_ACTIVE,
2400                                                   $prefix.'now1'=>$now, $prefix.'now2'=>$now));
2401          }
2402      }
2403  
2404      $joins = implode("\n", $joins);
2405      $wheres = "WHERE ".implode(" AND ", $wheres);
2406  
2407      $sql = "SELECT DISTINCT {$prefix}u.id
2408                FROM {user} {$prefix}u
2409              $joins
2410             $wheres";
2411  
2412      return array($sql, $params);
2413  }
2414  
2415  /**
2416   * Returns list of users enrolled into course.
2417   *
2418   * @package   core_enrol
2419   * @category  access
2420   *
2421   * @param context $context
2422   * @param string $withcapability
2423   * @param int $groupid 0 means ignore groups, any other value limits the result by group id
2424   * @param string $userfields requested user record fields
2425   * @param string $orderby
2426   * @param int $limitfrom return a subset of records, starting at this point (optional, required if $limitnum is set).
2427   * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
2428   * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
2429   * @return array of user records
2430   */
2431  function get_enrolled_users(context $context, $withcapability = '', $groupid = 0, $userfields = 'u.*', $orderby = null,
2432          $limitfrom = 0, $limitnum = 0, $onlyactive = false) {
2433      global $DB;
2434  
2435      list($esql, $params) = get_enrolled_sql($context, $withcapability, $groupid, $onlyactive);
2436      $sql = "SELECT $userfields
2437                FROM {user} u
2438                JOIN ($esql) je ON je.id = u.id
2439               WHERE u.deleted = 0";
2440  
2441      if ($orderby) {
2442          $sql = "$sql ORDER BY $orderby";
2443      } else {
2444          list($sort, $sortparams) = users_order_by_sql('u');
2445          $sql = "$sql ORDER BY $sort";
2446          $params = array_merge($params, $sortparams);
2447      }
2448  
2449      return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
2450  }
2451  
2452  /**
2453   * Counts list of users enrolled into course (as per above function)
2454   *
2455   * @package   core_enrol
2456   * @category  access
2457   *
2458   * @param context $context
2459   * @param string $withcapability
2460   * @param int $groupid 0 means ignore groups, any other value limits the result by group id
2461   * @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
2462   * @return array of user records
2463   */
2464  function count_enrolled_users(context $context, $withcapability = '', $groupid = 0, $onlyactive = false) {
2465      global $DB;
2466  
2467      list($esql, $params) = get_enrolled_sql($context, $withcapability, $groupid, $onlyactive);
2468      $sql = "SELECT count(u.id)
2469                FROM {user} u
2470                JOIN ($esql) je ON je.id = u.id
2471               WHERE u.deleted = 0";
2472  
2473      return $DB->count_records_sql($sql, $params);
2474  }
2475  
2476  /**
2477   * Loads the capability definitions for the component (from file).
2478   *
2479   * Loads the capability definitions for the component (from file). If no
2480   * capabilities are defined for the component, we simply return an empty array.
2481   *
2482   * @access private
2483   * @param string $component full plugin name, examples: 'moodle', 'mod_forum'
2484   * @return array array of capabilities
2485   */
2486  function load_capability_def($component) {
2487      $defpath = core_component::get_component_directory($component).'/db/access.php';
2488  
2489      $capabilities = array();
2490      if (file_exists($defpath)) {
2491          require($defpath);
2492          if (!empty(${$component.'_capabilities'})) {
2493              // BC capability array name
2494              // since 2.0 we prefer $capabilities instead - it is easier to use and matches db/* files
2495              debugging('componentname_capabilities array is deprecated, please use $capabilities array only in access.php files');
2496              $capabilities = ${$component.'_capabilities'};
2497          }
2498      }
2499  
2500      return $capabilities;
2501  }
2502  
2503  /**
2504   * Gets the capabilities that have been cached in the database for this component.
2505   *
2506   * @access private
2507   * @param string $component - examples: 'moodle', 'mod_forum'
2508   * @return array array of capabilities
2509   */
2510  function get_cached_capabilities($component = 'moodle') {
2511      global $DB;
2512      return $DB->get_records('capabilities', array('component'=>$component));
2513  }
2514  
2515  /**
2516   * Returns default capabilities for given role archetype.
2517   *
2518   * @param string $archetype role archetype
2519   * @return array
2520   */
2521  function get_default_capabilities($archetype) {
2522      global $DB;
2523  
2524      if (!$archetype) {
2525          return array();
2526      }
2527  
2528      $alldefs = array();
2529      $defaults = array();
2530      $components = array();
2531      $allcaps = $DB->get_records('capabilities');
2532  
2533      foreach ($allcaps as $cap) {
2534          if (!in_array($cap->component, $components)) {
2535              $components[] = $cap->component;
2536              $alldefs = array_merge($alldefs, load_capability_def($cap->component));
2537          }
2538      }
2539      foreach($alldefs as $name=>$def) {
2540          // Use array 'archetypes if available. Only if not specified, use 'legacy'.
2541          if (isset($def['archetypes'])) {
2542              if (isset($def['archetypes'][$archetype])) {
2543                  $defaults[$name] = $def['archetypes'][$archetype];
2544              }
2545          // 'legacy' is for backward compatibility with 1.9 access.php
2546          } else {
2547              if (isset($def['legacy'][$archetype])) {
2548                  $defaults[$name] = $def['legacy'][$archetype];
2549              }
2550          }
2551      }
2552  
2553      return $defaults;
2554  }
2555  
2556  /**
2557   * Return default roles that can be assigned, overridden or switched
2558   * by give role archetype.
2559   *
2560   * @param string $type  assign|override|switch
2561   * @param string $archetype
2562   * @return array of role ids
2563   */
2564  function get_default_role_archetype_allows($type, $archetype) {
2565      global $DB;
2566  
2567      if (empty($archetype)) {
2568          return array();
2569      }
2570  
2571      $roles = $DB->get_records('role');
2572      $archetypemap = array();
2573      foreach ($roles as $role) {
2574          if ($role->archetype) {
2575              $archetypemap[$role->archetype][$role->id] = $role->id;
2576          }
2577      }
2578  
2579      $defaults = array(
2580          'assign' => array(
2581              'manager'        => array('manager', 'coursecreator', 'editingteacher', 'teacher', 'student'),
2582              'coursecreator'  => array(),
2583              'editingteacher' => array('teacher', 'student'),
2584              'teacher'        => array(),
2585              'student'        => array(),
2586              'guest'          => array(),
2587              'user'           => array(),
2588              'frontpage'      => array(),
2589          ),
2590          'override' => array(
2591              'manager'        => array('manager', 'coursecreator', 'editingteacher', 'teacher', 'student', 'guest', 'user', 'frontpage'),
2592              'coursecreator'  => array(),
2593              'editingteacher' => array('teacher', 'student', 'guest'),
2594              'teacher'        => array(),
2595              'student'        => array(),
2596              'guest'          => array(),
2597              'user'           => array(),
2598              'frontpage'      => array(),
2599          ),
2600          'switch' => array(
2601              'manager'        => array('editingteacher', 'teacher', 'student', 'guest'),
2602              'coursecreator'  => array(),
2603              'editingteacher' => array('teacher', 'student', 'guest'),
2604              'teacher'        => array('student', 'guest'),
2605              'student'        => array(),
2606              'guest'          => array(),
2607              'user'           => array(),
2608              'frontpage'      => array(),
2609          ),
2610      );
2611  
2612      if (!isset($defaults[$type][$archetype])) {
2613          debugging("Unknown type '$type'' or archetype '$archetype''");
2614          return array();
2615      }
2616  
2617      $return = array();
2618      foreach ($defaults[$type][$archetype] as $at) {
2619          if (isset($archetypemap[$at])) {
2620              foreach ($archetypemap[$at] as $roleid) {
2621                  $return[$roleid] = $roleid;
2622              }
2623          }
2624      }
2625  
2626      return $return;
2627  }
2628  
2629  /**
2630   * Reset role capabilities to default according to selected role archetype.
2631   * If no archetype selected, removes all capabilities.
2632   *
2633   * This applies to capabilities that are assigned to the role (that you could
2634   * edit in the 'define roles' interface), and not to any capability overrides
2635   * in different locations.
2636   *
2637   * @param int $roleid ID of role to reset capabilities for
2638   */
2639  function reset_role_capabilities($roleid) {
2640      global $DB;
2641  
2642      $role = $DB->get_record('role', array('id'=>$roleid), '*', MUST_EXIST);
2643      $defaultcaps = get_default_capabilities($role->archetype);
2644  
2645      $systemcontext = context_system::instance();
2646  
2647      $DB->delete_records('role_capabilities',
2648              array('roleid' => $roleid, 'contextid' => $systemcontext->id));
2649  
2650      foreach($defaultcaps as $cap=>$permission) {
2651          assign_capability($cap, $permission, $roleid, $systemcontext->id);
2652      }
2653  
2654      // Mark the system context dirty.
2655      context_system::instance()->mark_dirty();
2656  }
2657  
2658  /**
2659   * Updates the capabilities table with the component capability definitions.
2660   * If no parameters are given, the function updates the core moodle
2661   * capabilities.
2662   *
2663   * Note that the absence of the db/access.php capabilities definition file
2664   * will cause any stored capabilities for the component to be removed from
2665   * the database.
2666   *
2667   * @access private
2668   * @param string $component examples: 'moodle', 'mod/forum', 'block/quiz_results'
2669   * @return boolean true if success, exception in case of any problems
2670   */
2671  function update_capabilities($component = 'moodle') {
2672      global $DB, $OUTPUT;
2673  
2674      $storedcaps = array();
2675  
2676      $filecaps = load_capability_def($component);
2677      foreach($filecaps as $capname=>$unused) {
2678          if (!preg_match('|^[a-z]+/[a-z_0-9]+:[a-z_0-9]+$|', $capname)) {
2679              debugging("Coding problem: Invalid capability name '$capname', use 'clonepermissionsfrom' field for migration.");
2680          }
2681      }
2682  
2683      $cachedcaps = get_cached_capabilities($component);
2684      if ($cachedcaps) {
2685          foreach ($cachedcaps as $cachedcap) {
2686              array_push($storedcaps, $cachedcap->name);
2687              // update risk bitmasks and context levels in existing capabilities if needed
2688              if (array_key_exists($cachedcap->name, $filecaps)) {
2689                  if (!array_key_exists('riskbitmask', $filecaps[$cachedcap->name])) {
2690                      $filecaps[$cachedcap->name]['riskbitmask'] = 0; // no risk if not specified
2691                  }
2692                  if ($cachedcap->captype != $filecaps[$cachedcap->name]['captype']) {
2693                      $updatecap = new stdClass();
2694                      $updatecap->id = $cachedcap->id;
2695                      $updatecap->captype = $filecaps[$cachedcap->name]['captype'];
2696                      $DB->update_record('capabilities', $updatecap);
2697                  }
2698                  if ($cachedcap->riskbitmask != $filecaps[$cachedcap->name]['riskbitmask']) {
2699                      $updatecap = new stdClass();
2700                      $updatecap->id = $cachedcap->id;
2701                      $updatecap->riskbitmask = $filecaps[$cachedcap->name]['riskbitmask'];
2702                      $DB->update_record('capabilities', $updatecap);
2703                  }
2704  
2705                  if (!array_key_exists('contextlevel', $filecaps[$cachedcap->name])) {
2706                      $filecaps[$cachedcap->name]['contextlevel'] = 0; // no context level defined
2707                  }
2708                  if ($cachedcap->contextlevel != $filecaps[$cachedcap->name]['contextlevel']) {
2709                      $updatecap = new stdClass();
2710                      $updatecap->id = $cachedcap->id;
2711                      $updatecap->contextlevel = $filecaps[$cachedcap->name]['contextlevel'];
2712                      $DB->update_record('capabilities', $updatecap);
2713                  }
2714              }
2715          }
2716      }
2717  
2718      // Are there new capabilities in the file definition?
2719      $newcaps = array();
2720  
2721      foreach ($filecaps as $filecap => $def) {
2722          if (!$storedcaps ||
2723                  ($storedcaps && in_array($filecap, $storedcaps) === false)) {
2724              if (!array_key_exists('riskbitmask', $def)) {
2725                  $def['riskbitmask'] = 0; // no risk if not specified
2726              }
2727              $newcaps[$filecap] = $def;
2728          }
2729      }
2730      // Add new capabilities to the stored definition.
2731      $existingcaps = $DB->get_records_menu('capabilities', array(), 'id', 'id, name');
2732      foreach ($newcaps as $capname => $capdef) {
2733          $capability = new stdClass();
2734          $capability->name         = $capname;
2735          $capability->captype      = $capdef['captype'];
2736          $capability->contextlevel = $capdef['contextlevel'];
2737          $capability->component    = $component;
2738          $capability->riskbitmask  = $capdef['riskbitmask'];
2739  
2740          $DB->insert_record('capabilities', $capability, false);
2741  
2742          if (isset($capdef['clonepermissionsfrom']) && in_array($capdef['clonepermissionsfrom'], $existingcaps)){
2743              if ($rolecapabilities = $DB->get_records('role_capabilities', array('capability'=>$capdef['clonepermissionsfrom']))){
2744                  foreach ($rolecapabilities as $rolecapability){
2745                      //assign_capability will update rather than insert if capability exists
2746                      if (!assign_capability($capname, $rolecapability->permission,
2747                                              $rolecapability->roleid, $rolecapability->contextid, true)){
2748                           echo $OUTPUT->notification('Could not clone capabilities for '.$capname);
2749                      }
2750                  }
2751              }
2752          // we ignore archetype key if we have cloned permissions
2753          } else if (isset($capdef['archetypes']) && is_array($capdef['archetypes'])) {
2754              assign_legacy_capabilities($capname, $capdef['archetypes']);
2755          // 'legacy' is for backward compatibility with 1.9 access.php
2756          } else if (isset($capdef['legacy']) && is_array($capdef['legacy'])) {
2757              assign_legacy_capabilities($capname, $capdef['legacy']);
2758          }
2759      }
2760      // Are there any capabilities that have been removed from the file
2761      // definition that we need to delete from the stored capabilities and
2762      // role assignments?
2763      capabilities_cleanup($component, $filecaps);
2764  
2765      // reset static caches
2766      accesslib_clear_all_caches(false);
2767  
2768      return true;
2769  }
2770  
2771  /**
2772   * Deletes cached capabilities that are no longer needed by the component.
2773   * Also unassigns these capabilities from any roles that have them.
2774   * NOTE: this function is called from lib/db/upgrade.php
2775   *
2776   * @access private
2777   * @param string $component examples: 'moodle', 'mod_forum', 'block_quiz_results'
2778   * @param array $newcapdef array of the new capability definitions that will be
2779   *                     compared with the cached capabilities
2780   * @return int number of deprecated capabilities that have been removed
2781   */
2782  function capabilities_cleanup($component, $newcapdef = null) {
2783      global $DB;
2784  
2785      $removedcount = 0;
2786  
2787      if ($cachedcaps = get_cached_capabilities($component)) {
2788          foreach ($cachedcaps as $cachedcap) {
2789              if (empty($newcapdef) ||
2790                          array_key_exists($cachedcap->name, $newcapdef) === false) {
2791  
2792                  // Remove from capabilities cache.
2793                  $DB->delete_records('capabilities', array('name'=>$cachedcap->name));
2794                  $removedcount++;
2795                  // Delete from roles.
2796                  if ($roles = get_roles_with_capability($cachedcap->name)) {
2797                      foreach($roles as $role) {
2798                          if (!unassign_capability($cachedcap->name, $role->id)) {
2799                              print_error('cannotunassigncap', 'error', '', (object)array('cap'=>$cachedcap->name, 'role'=>$role->name));
2800                          }
2801                      }
2802                  }
2803              } // End if.
2804          }
2805      }
2806      return $removedcount;
2807  }
2808  
2809  /**
2810   * Returns an array of all the known types of risk
2811   * The array keys can be used, for example as CSS class names, or in calls to
2812   * print_risk_icon. The values are the corresponding RISK_ constants.
2813   *
2814   * @return array all the known types of risk.
2815   */
2816  function get_all_risks() {
2817      return array(
2818          'riskmanagetrust' => RISK_MANAGETRUST,
2819          'riskconfig'      => RISK_CONFIG,
2820          'riskxss'         => RISK_XSS,
2821          'riskpersonal'    => RISK_PERSONAL,
2822          'riskspam'        => RISK_SPAM,
2823          'riskdataloss'    => RISK_DATALOSS,
2824      );
2825  }
2826  
2827  /**
2828   * Return a link to moodle docs for a given capability name
2829   *
2830   * @param stdClass $capability a capability - a row from the mdl_capabilities table.
2831   * @return string the human-readable capability name as a link to Moodle Docs.
2832   */
2833  function get_capability_docs_link($capability) {
2834      $url = get_docs_url('Capabilities/' . $capability->name);
2835      return '<a onclick="this.target=\'docspopup\'" href="' . $url . '">' . get_capability_string($capability->name) . '</a>';
2836  }
2837  
2838  /**
2839   * This function pulls out all the resolved capabilities (overrides and
2840   * defaults) of a role used in capability overrides in contexts at a given
2841   * context.
2842   *
2843   * @param int $roleid
2844   * @param context $context
2845   * @param string $cap capability, optional, defaults to ''
2846   * @return array Array of capabilities
2847   */
2848  function role_context_capabilities($roleid, context $context, $cap = '') {
2849      global $DB;
2850  
2851      $contexts = $context->get_parent_context_ids(true);
2852      $contexts = '('.implode(',', $contexts).')';
2853  
2854      $params = array($roleid);
2855  
2856      if ($cap) {
2857          $search = " AND rc.capability = ? ";
2858          $params[] = $cap;
2859      } else {
2860          $search = '';
2861      }
2862  
2863      $sql = "SELECT rc.*
2864                FROM {role_capabilities} rc, {context} c
2865               WHERE rc.contextid in $contexts
2866                     AND rc.roleid = ?
2867                     AND rc.contextid = c.id $search
2868            ORDER BY c.contextlevel DESC, rc.capability DESC";
2869  
2870      $capabilities = array();
2871  
2872      if ($records = $DB->get_records_sql($sql, $params)) {
2873          // We are traversing via reverse order.
2874          foreach ($records as $record) {
2875              // If not set yet (i.e. inherit or not set at all), or currently we have a prohibit
2876              if (!isset($capabilities[$record->capability]) || $record->permission<-500) {
2877                  $capabilities[$record->capability] = $record->permission;
2878              }
2879          }
2880      }
2881      return $capabilities;
2882  }
2883  
2884  /**
2885   * Constructs array with contextids as first parameter and context paths,
2886   * in both cases bottom top including self.
2887   *
2888   * @access private
2889   * @param context $context
2890   * @return array
2891   */
2892  function get_context_info_list(context $context) {
2893      $contextids = explode('/', ltrim($context->path, '/'));
2894      $contextpaths = array();
2895      $contextids2 = $contextids;
2896      while ($contextids2) {
2897          $contextpaths[] = '/' . implode('/', $contextids2);
2898          array_pop($contextids2);
2899      }
2900      return array($contextids, $contextpaths);
2901  }
2902  
2903  /**
2904   * Check if context is the front page context or a context inside it
2905   *
2906   * Returns true if this context is the front page context, or a context inside it,
2907   * otherwise false.
2908   *
2909   * @param context $context a context object.
2910   * @return bool
2911   */
2912  function is_inside_frontpage(context $context) {
2913      $frontpagecontext = context_course::instance(SITEID);
2914      return strpos($context->path . '/', $frontpagecontext->path . '/') === 0;
2915  }
2916  
2917  /**
2918   * Returns capability information (cached)
2919   *
2920   * @param string $capabilityname
2921   * @return stdClass or null if capability not found
2922   */
2923  function get_capability_info($capabilityname) {
2924      global $ACCESSLIB_PRIVATE, $DB; // one request per page only
2925  
2926      //TODO: MUC - this could be cached in shared memory, it would eliminate 1 query per page
2927  
2928      if (empty($ACCESSLIB_PRIVATE->capabilities)) {
2929          $ACCESSLIB_PRIVATE->capabilities = array();
2930          $caps = $DB->get_records('capabilities', array(), 'id, name, captype, riskbitmask');
2931          foreach ($caps as $cap) {
2932              $capname = $cap->name;
2933              unset($cap->id);
2934              unset($cap->name);
2935              $cap->riskbitmask = (int)$cap->riskbitmask;
2936              $ACCESSLIB_PRIVATE->capabilities[$capname] = $cap;
2937          }
2938      }
2939  
2940      return isset($ACCESSLIB_PRIVATE->capabilities[$capabilityname]) ? $ACCESSLIB_PRIVATE->capabilities[$capabilityname] : null;
2941  }
2942  
2943  /**
2944   * Returns the human-readable, translated version of the capability.
2945   * Basically a big switch statement.
2946   *
2947   * @param string $capabilityname e.g. mod/choice:readresponses
2948   * @return string
2949   */
2950  function get_capability_string($capabilityname) {
2951  
2952      // Typical capability name is 'plugintype/pluginname:capabilityname'
2953      list($type, $name, $capname) = preg_split('|[/:]|', $capabilityname);
2954  
2955      if ($type === 'moodle') {
2956          $component = 'core_role';
2957      } else if ($type === 'quizreport') {
2958          //ugly hack!!
2959          $component = 'quiz_'.$name;
2960      } else {
2961          $component = $type.'_'.$name;
2962      }
2963  
2964      $stringname = $name.':'.$capname;
2965  
2966      if ($component === 'core_role' or get_string_manager()->string_exists($stringname, $component)) {
2967          return get_string($stringname, $component);
2968      }
2969  
2970      $dir = core_component::get_component_directory($component);
2971      if (!file_exists($dir)) {
2972          // plugin broken or does not exist, do not bother with printing of debug message
2973          return $capabilityname.' ???';
2974      }
2975  
2976      // something is wrong in plugin, better print debug
2977      return get_string($stringname, $component);
2978  }
2979  
2980  /**
2981   * This gets the mod/block/course/core etc strings.
2982   *
2983   * @param string $component
2984   * @param int $contextlevel
2985   * @return string|bool String is success, false if failed
2986   */
2987  function get_component_string($component, $contextlevel) {
2988  
2989      if ($component === 'moodle' or $component === 'core') {
2990          switch ($contextlevel) {
2991              // TODO MDL-46123: this should probably use context level names instead
2992              case CONTEXT_SYSTEM:    return get_string('coresystem');
2993              case CONTEXT_USER:      return get_string('users');
2994              case CONTEXT_COURSECAT: return get_string('categories');
2995              case CONTEXT_COURSE:    return get_string('course');
2996              case CONTEXT_MODULE:    return get_string('activities');
2997              case CONTEXT_BLOCK:     return get_string('block');
2998              default:                print_error('unknowncontext');
2999          }
3000      }
3001  
3002      list($type, $name) = core_component::normalize_component($component);
3003      $dir = core_component::get_plugin_directory($type, $name);
3004      if (!file_exists($dir)) {
3005          // plugin not installed, bad luck, there is no way to find the name
3006          return $component.' ???';
3007      }
3008  
3009      switch ($type) {
3010          // TODO MDL-46123: this is really hacky and should be improved.
3011          case 'quiz':         return get_string($name.':componentname', $component);// insane hack!!!
3012          case 'repository':   return get_string('repository', 'repository').': '.get_string('pluginname', $component);
3013          case 'gradeimport':  return get_string('gradeimport', 'grades').': '.get_string('pluginname', $component);
3014          case 'gradeexport':  return get_string('gradeexport', 'grades').': '.get_string('pluginname', $component);
3015          case 'gradereport':  return get_string('gradereport', 'grades').': '.get_string('pluginname', $component);
3016          case 'webservice':   return get_string('webservice', 'webservice').': '.get_string('pluginname', $component);
3017          case 'block':        return get_string('block').': '.get_string('pluginname', basename($component));
3018          case 'mod':
3019              if (get_string_manager()->string_exists('pluginname', $component)) {
3020                  return get_string('activity').': '.get_string('pluginname', $component);
3021              } else {
3022                  return get_string('activity').': '.get_string('modulename', $component);
3023              }
3024          default: return get_string('pluginname', $component);
3025      }
3026  }
3027  
3028  /**
3029   * Gets the list of roles assigned to this context and up (parents)
3030   * from the list of roles that are visible on user profile page
3031   * and participants page.
3032   *
3033   * @param context $context
3034   * @return array
3035   */
3036  function get_profile_roles(context $context) {
3037      global $CFG, $DB;
3038  
3039      if (empty($CFG->profileroles)) {
3040          return array();
3041      }
3042  
3043      list($rallowed, $params) = $DB->get_in_or_equal(explode(',', $CFG->profileroles), SQL_PARAMS_NAMED, 'a');
3044      list($contextlist, $cparams) = $DB->get_in_or_equal($context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'p');
3045      $params = array_merge($params, $cparams);
3046  
3047      if ($coursecontext = $context->get_course_context(false)) {
3048          $params['coursecontext'] = $coursecontext->id;
3049      } else {
3050          $params['coursecontext'] = 0;
3051      }
3052  
3053      $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder, rn.name AS coursealias
3054                FROM {role_assignments} ra, {role} r
3055           LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
3056               WHERE r.id = ra.roleid
3057                     AND ra.contextid $contextlist
3058                     AND r.id $rallowed
3059            ORDER BY r.sortorder ASC";
3060  
3061      return $DB->get_records_sql($sql, $params);
3062  }
3063  
3064  /**
3065   * Gets the list of roles assigned to this context and up (parents)
3066   *
3067   * @param context $context
3068   * @return array
3069   */
3070  function get_roles_used_in_context(context $context) {
3071      global $DB;
3072  
3073      list($contextlist, $params) = $DB->get_in_or_equal($context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'cl');
3074  
3075      if ($coursecontext = $context->get_course_context(false)) {
3076          $params['coursecontext'] = $coursecontext->id;
3077      } else {
3078          $params['coursecontext'] = 0;
3079      }
3080  
3081      $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder, rn.name AS coursealias
3082                FROM {role_assignments} ra, {role} r
3083           LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
3084               WHERE r.id = ra.roleid
3085                     AND ra.contextid $contextlist
3086            ORDER BY r.sortorder ASC";
3087  
3088      return $DB->get_records_sql($sql, $params);
3089  }
3090  
3091  /**
3092   * This function is used to print roles column in user profile page.
3093   * It is using the CFG->profileroles to limit the list to only interesting roles.
3094   * (The permission tab has full details of user role assignments.)
3095   *
3096   * @param int $userid
3097   * @param int $courseid
3098   * @return string
3099   */
3100  function get_user_roles_in_course($userid, $courseid) {
3101      global $CFG, $DB;
3102  
3103      if (empty($CFG->profileroles)) {
3104          return '';
3105      }
3106  
3107      if ($courseid == SITEID) {
3108          $context = context_system::instance();
3109      } else {
3110          $context = context_course::instance($courseid);
3111      }
3112  
3113      list($rallowed, $params) = $DB->get_in_or_equal(explode(',', $CFG->profileroles), SQL_PARAMS_NAMED, 'a');
3114      list($contextlist, $cparams) = $DB->get_in_or_equal($context->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'p');
3115      $params = array_merge($params, $cparams);
3116  
3117      if ($coursecontext = $context->get_course_context(false)) {
3118          $params['coursecontext'] = $coursecontext->id;
3119      } else {
3120          $params['coursecontext'] = 0;
3121      }
3122  
3123      $sql = "SELECT DISTINCT r.id, r.name, r.shortname, r.sortorder, rn.name AS coursealias
3124                FROM {role_assignments} ra, {role} r
3125           LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
3126               WHERE r.id = ra.roleid
3127                     AND ra.contextid $contextlist
3128                     AND r.id $rallowed
3129                     AND ra.userid = :userid
3130            ORDER BY r.sortorder ASC";
3131      $params['userid'] = $userid;
3132  
3133      $rolestring = '';
3134  
3135      if ($roles = $DB->get_records_sql($sql, $params)) {
3136          $rolenames = role_fix_names($roles, $context, ROLENAME_ALIAS, true);   // Substitute aliases
3137  
3138          foreach ($rolenames as $roleid => $rolename) {
3139              $rolenames[$roleid] = '<a href="'.$CFG->wwwroot.'/user/index.php?contextid='.$context->id.'&amp;roleid='.$roleid.'">'.$rolename.'</a>';
3140          }
3141          $rolestring = implode(',', $rolenames);
3142      }
3143  
3144      return $rolestring;
3145  }
3146  
3147  /**
3148   * Checks if a user can assign users to a particular role in this context
3149   *
3150   * @param context $context
3151   * @param int $targetroleid - the id of the role you want to assign users to
3152   * @return boolean
3153   */
3154  function user_can_assign(context $context, $targetroleid) {
3155      global $DB;
3156  
3157      // First check to see if the user is a site administrator.
3158      if (is_siteadmin()) {
3159          return true;
3160      }
3161  
3162      // Check if user has override capability.
3163      // If not return false.
3164      if (!has_capability('moodle/role:assign', $context)) {
3165          return false;
3166      }
3167      // pull out all active roles of this user from this context(or above)
3168      if ($userroles = get_user_roles($context)) {
3169          foreach ($userroles as $userrole) {
3170              // if any in the role_allow_override table, then it's ok
3171              if ($DB->get_record('role_allow_assign', array('roleid'=>$userrole->roleid, 'allowassign'=>$targetroleid))) {
3172                  return true;
3173              }
3174          }
3175      }
3176  
3177      return false;
3178  }
3179  
3180  /**
3181   * Returns all site roles in correct sort order.
3182   *
3183   * Note: this method does not localise role names or descriptions,
3184   *       use role_get_names() if you need role names.
3185   *
3186   * @param context $context optional context for course role name aliases
3187   * @return array of role records with optional coursealias property
3188   */
3189  function get_all_roles(context $context = null) {
3190      global $DB;
3191  
3192      if (!$context or !$coursecontext = $context->get_course_context(false)) {
3193          $coursecontext = null;
3194      }
3195  
3196      if ($coursecontext) {
3197          $sql = "SELECT r.*, rn.name AS coursealias
3198                    FROM {role} r
3199               LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
3200                ORDER BY r.sortorder ASC";
3201          return $DB->get_records_sql($sql, array('coursecontext'=>$coursecontext->id));
3202  
3203      } else {
3204          return $DB->get_records('role', array(), 'sortorder ASC');
3205      }
3206  }
3207  
3208  /**
3209   * Returns roles of a specified archetype
3210   *
3211   * @param string $archetype
3212   * @return array of full role records
3213   */
3214  function get_archetype_roles($archetype) {
3215      global $DB;
3216      return $DB->get_records('role', array('archetype'=>$archetype), 'sortorder ASC');
3217  }
3218  
3219  /**
3220   * Gets all the user roles assigned in this context, or higher contexts
3221   * this is mainly used when checking if a user can assign a role, or overriding a role
3222   * i.e. we need to know what this user holds, in order to verify against allow_assign and
3223   * allow_override tables
3224   *
3225   * @param context $context
3226   * @param int $userid
3227   * @param bool $checkparentcontexts defaults to true
3228   * @param string $order defaults to 'c.contextlevel DESC, r.sortorder ASC'
3229   * @return array
3230   */
3231  function get_user_roles(context $context, $userid = 0, $checkparentcontexts = true, $order = 'c.contextlevel DESC, r.sortorder ASC') {
3232      global $USER, $DB;
3233  
3234      if (empty($userid)) {
3235          if (empty($USER->id)) {
3236              return array();
3237          }
3238          $userid = $USER->id;
3239      }
3240  
3241      if ($checkparentcontexts) {
3242          $contextids = $context->get_parent_context_ids();
3243      } else {
3244          $contextids = array();
3245      }
3246      $contextids[] = $context->id;
3247  
3248      list($contextids, $params) = $DB->get_in_or_equal($contextids, SQL_PARAMS_QM);
3249  
3250      array_unshift($params, $userid);
3251  
3252      $sql = "SELECT ra.*, r.name, r.shortname
3253                FROM {role_assignments} ra, {role} r, {context} c
3254               WHERE ra.userid = ?
3255                     AND ra.roleid = r.id
3256                     AND ra.contextid = c.id
3257                     AND ra.contextid $contextids
3258            ORDER BY $order";
3259  
3260      return $DB->get_records_sql($sql ,$params);
3261  }
3262  
3263  /**
3264   * Like get_user_roles, but adds in the authenticated user role, and the front
3265   * page roles, if applicable.
3266   *
3267   * @param context $context the context.
3268   * @param int $userid optional. Defaults to $USER->id
3269   * @return array of objects with fields ->userid, ->contextid and ->roleid.
3270   */
3271  function get_user_roles_with_special(context $context, $userid = 0) {
3272      global $CFG, $USER;
3273  
3274      if (empty($userid)) {
3275          if (empty($USER->id)) {
3276              return array();
3277          }
3278          $userid = $USER->id;
3279      }
3280  
3281      $ras = get_user_roles($context, $userid);
3282  
3283      // Add front-page role if relevant.
3284      $defaultfrontpageroleid = isset($CFG->defaultfrontpageroleid) ? $CFG->defaultfrontpageroleid : 0;
3285      $isfrontpage = ($context->contextlevel == CONTEXT_COURSE && $context->instanceid == SITEID) ||
3286              is_inside_frontpage($context);
3287      if ($defaultfrontpageroleid && $isfrontpage) {
3288          $frontpagecontext = context_course::instance(SITEID);
3289          $ra = new stdClass();
3290          $ra->userid = $userid;
3291          $ra->contextid = $frontpagecontext->id;
3292          $ra->roleid = $defaultfrontpageroleid;
3293          $ras[] = $ra;
3294      }
3295  
3296      // Add authenticated user role if relevant.
3297      $defaultuserroleid      = isset($CFG->defaultuserroleid) ? $CFG->defaultuserroleid : 0;
3298      if ($defaultuserroleid && !isguestuser($userid)) {
3299          $systemcontext = context_system::instance();
3300          $ra = new stdClass();
3301          $ra->userid = $userid;
3302          $ra->contextid = $systemcontext->id;
3303          $ra->roleid = $defaultuserroleid;
3304          $ras[] = $ra;
3305      }
3306  
3307      return $ras;
3308  }
3309  
3310  /**
3311   * Creates a record in the role_allow_override table
3312   *
3313   * @param int $sroleid source roleid
3314   * @param int $troleid target roleid
3315   * @return void
3316   */
3317  function allow_override($sroleid, $troleid) {
3318      global $DB;
3319  
3320      $record = new stdClass();
3321      $record->roleid        = $sroleid;
3322      $record->allowoverride = $troleid;
3323      $DB->insert_record('role_allow_override', $record);
3324  }
3325  
3326  /**
3327   * Creates a record in the role_allow_assign table
3328   *
3329   * @param int $fromroleid source roleid
3330   * @param int $targetroleid target roleid
3331   * @return void
3332   */
3333  function allow_assign($fromroleid, $targetroleid) {
3334      global $DB;
3335  
3336      $record = new stdClass();
3337      $record->roleid      = $fromroleid;
3338      $record->allowassign = $targetroleid;
3339      $DB->insert_record('role_allow_assign', $record);
3340  }
3341  
3342  /**
3343   * Creates a record in the role_allow_switch table
3344   *
3345   * @param int $fromroleid source roleid
3346   * @param int $targetroleid target roleid
3347   * @return void
3348   */
3349  function allow_switch($fromroleid, $targetroleid) {
3350      global $DB;
3351  
3352      $record = new stdClass();
3353      $record->roleid      = $fromroleid;
3354      $record->allowswitch = $targetroleid;
3355      $DB->insert_record('role_allow_switch', $record);
3356  }
3357  
3358  /**
3359   * Gets a list of roles that this user can assign in this context
3360   *
3361   * @param context $context the context.
3362   * @param int $rolenamedisplay the type of role name to display. One of the
3363   *      ROLENAME_X constants. Default ROLENAME_ALIAS.
3364   * @param bool $withusercounts if true, count the number of users with each role.
3365   * @param integer|object $user A user id or object. By default (null) checks the permissions of the current user.
3366   * @return array if $withusercounts is false, then an array $roleid => $rolename.
3367   *      if $withusercounts is true, returns a list of three arrays,
3368   *      $rolenames, $rolecounts, and $nameswithcounts.
3369   */
3370  function get_assignable_roles(context $context, $rolenamedisplay = ROLENAME_ALIAS, $withusercounts = false, $user = null) {
3371      global $USER, $DB;
3372  
3373      // make sure there is a real user specified
3374      if ($user === null) {
3375          $userid = isset($USER->id) ? $USER->id : 0;
3376      } else {
3377          $userid = is_object($user) ? $user->id : $user;
3378      }
3379  
3380      if (!has_capability('moodle/role:assign', $context, $userid)) {
3381          if ($withusercounts) {
3382              return array(array(), array(), array());
3383          } else {
3384              return array();
3385          }
3386      }
3387  
3388      $params = array();
3389      $extrafields = '';
3390  
3391      if ($withusercounts) {
3392          $extrafields = ', (SELECT count(u.id)
3393                               FROM {role_assignments} cra JOIN {user} u ON cra.userid = u.id
3394                              WHERE cra.roleid = r.id AND cra.contextid = :conid AND u.deleted = 0
3395                            ) AS usercount';
3396          $params['conid'] = $context->id;
3397      }
3398  
3399      if (is_siteadmin($userid)) {
3400          // show all roles allowed in this context to admins
3401          $assignrestriction = "";
3402      } else {
3403          $parents = $context->get_parent_context_ids(true);
3404          $contexts = implode(',' , $parents);
3405          $assignrestriction = "JOIN (SELECT DISTINCT raa.allowassign AS id
3406                                        FROM {role_allow_assign} raa
3407                                        JOIN {role_assignments} ra ON ra.roleid = raa.roleid
3408                                       WHERE ra.userid = :userid AND ra.contextid IN ($contexts)
3409                                     ) ar ON ar.id = r.id";
3410          $params['userid'] = $userid;
3411      }
3412      $params['contextlevel'] = $context->contextlevel;
3413  
3414      if ($coursecontext = $context->get_course_context(false)) {
3415          $params['coursecontext'] = $coursecontext->id;
3416      } else {
3417          $params['coursecontext'] = 0; // no course aliases
3418          $coursecontext = null;
3419      }
3420      $sql = "SELECT r.id, r.name, r.shortname, rn.name AS coursealias $extrafields
3421                FROM {role} r
3422                $assignrestriction
3423                JOIN {role_context_levels} rcl ON (rcl.contextlevel = :contextlevel AND r.id = rcl.roleid)
3424           LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
3425            ORDER BY r.sortorder ASC";
3426      $roles = $DB->get_records_sql($sql, $params);
3427  
3428      $rolenames = role_fix_names($roles, $coursecontext, $rolenamedisplay, true);
3429  
3430      if (!$withusercounts) {
3431          return $rolenames;
3432      }
3433  
3434      $rolecounts = array();
3435      $nameswithcounts = array();
3436      foreach ($roles as $role) {
3437          $nameswithcounts[$role->id] = $rolenames[$role->id] . ' (' . $roles[$role->id]->usercount . ')';
3438          $rolecounts[$role->id] = $roles[$role->id]->usercount;
3439      }
3440      return array($rolenames, $rolecounts, $nameswithcounts);
3441  }
3442  
3443  /**
3444   * Gets a list of roles that this user can switch to in a context
3445   *
3446   * Gets a list of roles that this user can switch to in a context, for the switchrole menu.
3447   * This function just process the contents of the role_allow_switch table. You also need to
3448   * test the moodle/role:switchroles to see if the user is allowed to switch in the first place.
3449   *
3450   * @param context $context a context.
3451   * @return array an array $roleid => $rolename.
3452   */
3453  function get_switchable_roles(context $context) {
3454      global $USER, $DB;
3455  
3456      $params = array();
3457      $extrajoins = '';
3458      $extrawhere = '';
3459      if (!is_siteadmin()) {
3460          // Admins are allowed to switch to any role with.
3461          // Others are subject to the additional constraint that the switch-to role must be allowed by
3462          // 'role_allow_switch' for some role they have assigned in this context or any parent.
3463          $parents = $context->get_parent_context_ids(true);
3464          $contexts = implode(',' , $parents);
3465  
3466          $extrajoins = "JOIN {role_allow_switch} ras ON ras.allowswitch = rc.roleid
3467          JOIN {role_assignments} ra ON ra.roleid = ras.roleid";
3468          $extrawhere = "WHERE ra.userid = :userid AND ra.contextid IN ($contexts)";
3469          $params['userid'] = $USER->id;
3470      }
3471  
3472      if ($coursecontext = $context->get_course_context(false)) {
3473          $params['coursecontext'] = $coursecontext->id;
3474      } else {
3475          $params['coursecontext'] = 0; // no course aliases
3476          $coursecontext = null;
3477      }
3478  
3479      $query = "
3480          SELECT r.id, r.name, r.shortname, rn.name AS coursealias
3481            FROM (SELECT DISTINCT rc.roleid
3482                    FROM {role_capabilities} rc
3483                    $extrajoins
3484                    $extrawhere) idlist
3485            JOIN {role} r ON r.id = idlist.roleid
3486       LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
3487        ORDER BY r.sortorder";
3488      $roles = $DB->get_records_sql($query, $params);
3489  
3490      return role_fix_names($roles, $context, ROLENAME_ALIAS, true);
3491  }
3492  
3493  /**
3494   * Gets a list of roles that this user can override in this context.
3495   *
3496   * @param context $context the context.
3497   * @param int $rolenamedisplay the type of role name to display. One of the
3498   *      ROLENAME_X constants. Default ROLENAME_ALIAS.
3499   * @param bool $withcounts if true, count the number of overrides that are set for each role.
3500   * @return array if $withcounts is false, then an array $roleid => $rolename.
3501   *      if $withusercounts is true, returns a list of three arrays,
3502   *      $rolenames, $rolecounts, and $nameswithcounts.
3503   */
3504  function get_overridable_roles(context $context, $rolenamedisplay = ROLENAME_ALIAS, $withcounts = false) {
3505      global $USER, $DB;
3506  
3507      if (!has_any_capability(array('moodle/role:safeoverride', 'moodle/role:override'), $context)) {
3508          if ($withcounts) {
3509              return array(array(), array(), array());
3510          } else {
3511              return array();
3512          }
3513      }
3514  
3515      $parents = $context->get_parent_context_ids(true);
3516      $contexts = implode(',' , $parents);
3517  
3518      $params = array();
3519      $extrafields = '';
3520  
3521      $params['userid'] = $USER->id;
3522      if ($withcounts) {
3523          $extrafields = ', (SELECT COUNT(rc.id) FROM {role_capabilities} rc
3524                  WHERE rc.roleid = ro.id AND rc.contextid = :conid) AS overridecount';
3525          $params['conid'] = $context->id;
3526      }
3527  
3528      if ($coursecontext = $context->get_course_context(false)) {
3529          $params['coursecontext'] = $coursecontext->id;
3530      } else {
3531          $params['coursecontext'] = 0; // no course aliases
3532          $coursecontext = null;
3533      }
3534  
3535      if (is_siteadmin()) {
3536          // show all roles to admins
3537          $roles = $DB->get_records_sql("
3538              SELECT ro.id, ro.name, ro.shortname, rn.name AS coursealias $extrafields
3539                FROM {role} ro
3540           LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = ro.id)
3541            ORDER BY ro.sortorder ASC", $params);
3542  
3543      } else {
3544          $roles = $DB->get_records_sql("
3545              SELECT ro.id, ro.name, ro.shortname, rn.name AS coursealias $extrafields
3546                FROM {role} ro
3547                JOIN (SELECT DISTINCT r.id
3548                        FROM {role} r
3549                        JOIN {role_allow_override} rao ON r.id = rao.allowoverride
3550                        JOIN {role_assignments} ra ON rao.roleid = ra.roleid
3551                       WHERE ra.userid = :userid AND ra.contextid IN ($contexts)
3552                     ) inline_view ON ro.id = inline_view.id
3553           LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = ro.id)
3554            ORDER BY ro.sortorder ASC", $params);
3555      }
3556  
3557      $rolenames = role_fix_names($roles, $context, $rolenamedisplay, true);
3558  
3559      if (!$withcounts) {
3560          return $rolenames;
3561      }
3562  
3563      $rolecounts = array();
3564      $nameswithcounts = array();
3565      foreach ($roles as $role) {
3566          $nameswithcounts[$role->id] = $rolenames[$role->id] . ' (' . $roles[$role->id]->overridecount . ')';
3567          $rolecounts[$role->id] = $roles[$role->id]->overridecount;
3568      }
3569      return array($rolenames, $rolecounts, $nameswithcounts);
3570  }
3571  
3572  /**
3573   * Create a role menu suitable for default role selection in enrol plugins.
3574   *
3575   * @package    core_enrol
3576   *
3577   * @param context $context
3578   * @param int $addroleid current or default role - always added to list
3579   * @return array roleid=>localised role name
3580   */
3581  function get_default_enrol_roles(context $context, $addroleid = null) {
3582      global $DB;
3583  
3584      $params = array('contextlevel'=>CONTEXT_COURSE);
3585  
3586      if ($coursecontext = $context->get_course_context(false)) {
3587          $params['coursecontext'] = $coursecontext->id;
3588      } else {
3589          $params['coursecontext'] = 0; // no course names
3590          $coursecontext = null;
3591      }
3592  
3593      if ($addroleid) {
3594          $addrole = "OR r.id = :addroleid";
3595          $params['addroleid'] = $addroleid;
3596      } else {
3597          $addrole = "";
3598      }
3599  
3600      $sql = "SELECT r.id, r.name, r.shortname, rn.name AS coursealias
3601                FROM {role} r
3602           LEFT JOIN {role_context_levels} rcl ON (rcl.roleid = r.id AND rcl.contextlevel = :contextlevel)
3603           LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
3604               WHERE rcl.id IS NOT NULL $addrole
3605            ORDER BY sortorder DESC";
3606  
3607      $roles = $DB->get_records_sql($sql, $params);
3608  
3609      return role_fix_names($roles, $context, ROLENAME_BOTH, true);
3610  }
3611  
3612  /**
3613   * Return context levels where this role is assignable.
3614   *
3615   * @param integer $roleid the id of a role.
3616   * @return array list of the context levels at which this role may be assigned.
3617   */
3618  function get_role_contextlevels($roleid) {
3619      global $DB;
3620      return $DB->get_records_menu('role_context_levels', array('roleid' => $roleid),
3621              'contextlevel', 'id,contextlevel');
3622  }
3623  
3624  /**
3625   * Return roles suitable for assignment at the specified context level.
3626   *
3627   * NOTE: this function name looks like a typo, should be probably get_roles_for_contextlevel()
3628   *
3629   * @param integer $contextlevel a contextlevel.
3630   * @return array list of role ids that are assignable at this context level.
3631   */
3632  function get_roles_for_contextlevels($contextlevel) {
3633      global $DB;
3634      return $DB->get_records_menu('role_context_levels', array('contextlevel' => $contextlevel),
3635              '', 'id,roleid');
3636  }
3637  
3638  /**
3639   * Returns default context levels where roles can be assigned.
3640   *
3641   * @param string $rolearchetype one of the role archetypes - that is, one of the keys
3642   *      from the array returned by get_role_archetypes();
3643   * @return array list of the context levels at which this type of role may be assigned by default.
3644   */
3645  function get_default_contextlevels($rolearchetype) {
3646      static $defaults = array(
3647          'manager'        => array(CONTEXT_SYSTEM, CONTEXT_COURSECAT, CONTEXT_COURSE),
3648          'coursecreator'  => array(CONTEXT_SYSTEM, CONTEXT_COURSECAT),
3649          'editingteacher' => array(CONTEXT_COURSE, CONTEXT_MODULE),
3650          'teacher'        => array(CONTEXT_COURSE, CONTEXT_MODULE),
3651          'student'        => array(CONTEXT_COURSE, CONTEXT_MODULE),
3652          'guest'          => array(),
3653          'user'           => array(),
3654          'frontpage'      => array());
3655  
3656      if (isset($defaults[$rolearchetype])) {
3657          return $defaults[$rolearchetype];
3658      } else {
3659          return array();
3660      }
3661  }
3662  
3663  /**
3664   * Set the context levels at which a particular role can be assigned.
3665   * Throws exceptions in case of error.
3666   *
3667   * @param integer $roleid the id of a role.
3668   * @param array $contextlevels the context levels at which this role should be assignable,
3669   *      duplicate levels are removed.
3670   * @return void
3671   */
3672  function set_role_contextlevels($roleid, array $contextlevels) {
3673      global $DB;
3674      $DB->delete_records('role_context_levels', array('roleid' => $roleid));
3675      $rcl = new stdClass();
3676      $rcl->roleid = $roleid;
3677      $contextlevels = array_unique($contextlevels);
3678      foreach ($contextlevels as $level) {
3679          $rcl->contextlevel = $level;
3680          $DB->insert_record('role_context_levels', $rcl, false, true);
3681      }
3682  }
3683  
3684  /**
3685   * Who has this capability in this context?
3686   *
3687   * This can be a very expensive call - use sparingly and keep
3688   * the results if you are going to need them again soon.
3689   *
3690   * Note if $fields is empty this function attempts to get u.*
3691   * which can get rather large - and has a serious perf impact
3692   * on some DBs.
3693   *
3694   * @param context $context
3695   * @param string|array $capability - capability name(s)
3696   * @param string $fields - fields to be pulled. The user table is aliased to 'u'. u.id MUST be included.
3697   * @param string $sort - the sort order. Default is lastaccess time.
3698   * @param mixed $limitfrom - number of records to skip (offset)
3699   * @param mixed $limitnum - number of records to fetch
3700   * @param string|array $groups - single group or array of groups - only return
3701   *               users who are in one of these group(s).
3702   * @param string|array $exceptions - list of users to exclude, comma separated or array
3703   * @param bool $doanything_ignored not used any more, admin accounts are never returned
3704   * @param bool $view_ignored - use get_enrolled_sql() instead
3705   * @param bool $useviewallgroups if $groups is set the return users who
3706   *               have capability both $capability and moodle/site:accessallgroups
3707   *               in this context, as well as users who have $capability and who are
3708   *               in $groups.
3709   * @return array of user records
3710   */
3711  function get_users_by_capability(context $context, $capability, $fields = '', $sort = '', $limitfrom = '', $limitnum = '',
3712                                   $groups = '', $exceptions = '', $doanything_ignored = null, $view_ignored = null, $useviewallgroups = false) {
3713      global $CFG, $DB;
3714  
3715      $defaultuserroleid      = isset($CFG->defaultuserroleid) ? $CFG->defaultuserroleid : 0;
3716      $defaultfrontpageroleid = isset($CFG->defaultfrontpageroleid) ? $CFG->defaultfrontpageroleid : 0;
3717  
3718      $ctxids = trim($context->path, '/');
3719      $ctxids = str_replace('/', ',', $ctxids);
3720  
3721      // Context is the frontpage
3722      $iscoursepage = false; // coursepage other than fp
3723      $isfrontpage = false;
3724      if ($context->contextlevel == CONTEXT_COURSE) {
3725          if ($context->instanceid == SITEID) {
3726              $isfrontpage = true;
3727          } else {
3728              $iscoursepage = true;
3729          }
3730      }
3731      $isfrontpage = ($isfrontpage || is_inside_frontpage($context));
3732  
3733      $caps = (array)$capability;
3734  
3735      // construct list of context paths bottom-->top
3736      list($contextids, $paths) = get_context_info_list($context);
3737  
3738      // we need to find out all roles that have these capabilities either in definition or in overrides
3739      $defs = array();
3740      list($incontexts, $params) = $DB->get_in_or_equal($contextids, SQL_PARAMS_NAMED, 'con');
3741      list($incaps, $params2) = $DB->get_in_or_equal($caps, SQL_PARAMS_NAMED, 'cap');
3742      $params = array_merge($params, $params2);
3743      $sql = "SELECT rc.id, rc.roleid, rc.permission, rc.capability, ctx.path
3744                FROM {role_capabilities} rc
3745                JOIN {context} ctx on rc.contextid = ctx.id
3746               WHERE rc.contextid $incontexts AND rc.capability $incaps";
3747  
3748      $rcs = $DB->get_records_sql($sql, $params);
3749      foreach ($rcs as $rc) {
3750          $defs[$rc->capability][$rc->path][$rc->roleid] = $rc->permission;
3751      }
3752  
3753      // go through the permissions bottom-->top direction to evaluate the current permission,
3754      // first one wins (prohibit is an exception that always wins)
3755      $access = array();
3756      foreach ($caps as $cap) {
3757          foreach ($paths as $path) {
3758              if (empty($defs[$cap][$path])) {
3759                  continue;
3760              }
3761              foreach($defs[$cap][$path] as $roleid => $perm) {
3762                  if ($perm == CAP_PROHIBIT) {
3763                      $access[$cap][$roleid] = CAP_PROHIBIT;
3764                      continue;
3765                  }
3766                  if (!isset($access[$cap][$roleid])) {
3767                      $access[$cap][$roleid] = (int)$perm;
3768                  }
3769              }
3770          }
3771      }
3772  
3773      // make lists of roles that are needed and prohibited in this context
3774      $needed = array(); // one of these is enough
3775      $prohibited = array(); // must not have any of these
3776      foreach ($caps as $cap) {
3777          if (empty($access[$cap])) {
3778              continue;
3779          }
3780          foreach ($access[$cap] as $roleid => $perm) {
3781              if ($perm == CAP_PROHIBIT) {
3782                  unset($needed[$cap][$roleid]);
3783                  $prohibited[$cap][$roleid] = true;
3784              } else if ($perm == CAP_ALLOW and empty($prohibited[$cap][$roleid])) {
3785                  $needed[$cap][$roleid] = true;
3786              }
3787          }
3788          if (empty($needed[$cap]) or !empty($prohibited[$cap][$defaultuserroleid])) {
3789              // easy, nobody has the permission
3790              unset($needed[$cap]);
3791              unset($prohibited[$cap]);
3792          } else if ($isfrontpage and !empty($prohibited[$cap][$defaultfrontpageroleid])) {
3793              // everybody is disqualified on the frontpage
3794              unset($needed[$cap]);
3795              unset($prohibited[$cap]);
3796          }
3797          if (empty($prohibited[$cap])) {
3798              unset($prohibited[$cap]);
3799          }
3800      }
3801  
3802      if (empty($needed)) {
3803          // there can not be anybody if no roles match this request
3804          return array();
3805      }
3806  
3807      if (empty($prohibited)) {
3808          // we can compact the needed roles
3809          $n = array();
3810          foreach ($needed as $cap) {
3811              foreach ($cap as $roleid=>$unused) {
3812                  $n[$roleid] = true;
3813              }
3814          }
3815          $needed = array('any'=>$n);
3816          unset($n);
3817      }
3818  
3819      // ***** Set up default fields ******
3820      if (empty($fields)) {
3821          if ($iscoursepage) {
3822              $fields = 'u.*, ul.timeaccess AS lastaccess';
3823          } else {
3824              $fields = 'u.*';
3825          }
3826      } else {
3827          if ($CFG->debugdeveloper && strpos($fields, 'u.*') === false && strpos($fields, 'u.id') === false) {
3828              debugging('u.id must be included in the list of fields passed to get_users_by_capability().', DEBUG_DEVELOPER);
3829          }
3830      }
3831  
3832      // Set up default sort
3833      if (empty($sort)) { // default to course lastaccess or just lastaccess
3834          if ($iscoursepage) {
3835              $sort = 'ul.timeaccess';
3836          } else {
3837              $sort = 'u.lastaccess';
3838          }
3839      }
3840  
3841      // Prepare query clauses
3842      $wherecond = array();
3843      $params    = array();
3844      $joins     = array();
3845  
3846      // User lastaccess JOIN
3847      if ((strpos($sort, 'ul.timeaccess') === false) and (strpos($fields, 'ul.timeaccess') === false)) {
3848           // user_lastaccess is not required MDL-13810
3849      } else {
3850          if ($iscoursepage) {
3851              $joins[] = "LEFT OUTER JOIN {user_lastaccess} ul ON (ul.userid = u.id AND ul.courseid = {$context->instanceid})";
3852          } else {
3853              throw new coding_exception('Invalid sort in get_users_by_capability(), ul.timeaccess allowed only for course contexts.');
3854          }
3855      }
3856  
3857      // We never return deleted users or guest account.
3858      $wherecond[] = "u.deleted = 0 AND u.id <> :guestid";
3859      $params['guestid'] = $CFG->siteguest;
3860  
3861      // Groups
3862      if ($groups) {
3863          $groups = (array)$groups;
3864          list($grouptest, $grpparams) = $DB->get_in_or_equal($groups, SQL_PARAMS_NAMED, 'grp');
3865          $grouptest = "u.id IN (SELECT userid FROM {groups_members} gm WHERE gm.groupid $grouptest)";
3866          $params = array_merge($params, $grpparams);
3867  
3868          if ($useviewallgroups) {
3869              $viewallgroupsusers = get_users_by_capability($context, 'moodle/site:accessallgroups', 'u.id, u.id', '', '', '', '', $exceptions);
3870              if (!empty($viewallgroupsusers)) {
3871                  $wherecond[] =  "($grouptest OR u.id IN (" . implode(',', array_keys($viewallgroupsusers)) . '))';
3872              } else {
3873                  $wherecond[] =  "($grouptest)";
3874              }
3875          } else {
3876              $wherecond[] =  "($grouptest)";
3877          }
3878      }
3879  
3880      // User exceptions
3881      if (!empty($exceptions)) {
3882          $exceptions = (array)$exceptions;
3883          list($exsql, $exparams) = $DB->get_in_or_equal($exceptions, SQL_PARAMS_NAMED, 'exc', false);
3884          $params = array_merge($params, $exparams);
3885          $wherecond[] = "u.id $exsql";
3886      }
3887  
3888      // now add the needed and prohibited roles conditions as joins
3889      if (!empty($needed['any'])) {
3890          // simple case - there are no prohibits involved
3891          if (!empty($needed['any'][$defaultuserroleid]) or ($isfrontpage and !empty($needed['any'][$defaultfrontpageroleid]))) {
3892              // everybody
3893          } else {
3894              $joins[] = "JOIN (SELECT DISTINCT userid
3895                                  FROM {role_assignments}
3896                                 WHERE contextid IN ($ctxids)
3897                                       AND roleid IN (".implode(',', array_keys($needed['any'])) .")
3898                               ) ra ON ra.userid = u.id";
3899          }
3900      } else {
3901          $unions = array();
3902          $everybody = false;
3903          foreach ($needed as $cap=>$unused) {
3904              if (empty($prohibited[$cap])) {
3905                  if (!empty($needed[$cap][$defaultuserroleid]) or ($isfrontpage and !empty($needed[$cap][$defaultfrontpageroleid]))) {
3906                      $everybody = true;
3907                      break;
3908                  } else {
3909                      $unions[] = "SELECT userid
3910                                     FROM {role_assignments}
3911                                    WHERE contextid IN ($ctxids)
3912                                          AND roleid IN (".implode(',', array_keys($needed[$cap])) .")";
3913                  }
3914              } else {
3915                  if (!empty($prohibited[$cap][$defaultuserroleid]) or ($isfrontpage and !empty($prohibited[$cap][$defaultfrontpageroleid]))) {
3916                      // nobody can have this cap because it is prevented in default roles
3917                      continue;
3918  
3919                  } else if (!empty($needed[$cap][$defaultuserroleid]) or ($isfrontpage and !empty($needed[$cap][$defaultfrontpageroleid]))) {
3920                      // everybody except the prohibitted - hiding does not matter
3921                      $unions[] = "SELECT id AS userid
3922                                     FROM {user}
3923                                    WHERE id NOT IN (SELECT userid
3924                                                       FROM {role_assignments}
3925                                                      WHERE contextid IN ($ctxids)
3926                                                            AND roleid IN (".implode(',', array_keys($prohibited[$cap])) ."))";
3927  
3928                  } else {
3929                      $unions[] = "SELECT userid
3930                                     FROM {role_assignments}
3931                                    WHERE contextid IN ($ctxids)
3932                                          AND roleid IN (".implode(',', array_keys($needed[$cap])) .")
3933                                          AND roleid NOT IN (".implode(',', array_keys($prohibited[$cap])) .")";
3934                  }
3935              }
3936          }
3937          if (!$everybody) {
3938              if ($unions) {
3939                  $joins[] = "JOIN (SELECT DISTINCT userid FROM ( ".implode(' UNION ', $unions)." ) us) ra ON ra.userid = u.id";
3940              } else {
3941                  // only prohibits found - nobody can be matched
3942                  $wherecond[] = "1 = 2";
3943              }
3944          }
3945      }
3946  
3947      // Collect WHERE conditions and needed joins
3948      $where = implode(' AND ', $wherecond);
3949      if ($where !== '') {
3950          $where = 'WHERE ' . $where;
3951      }
3952      $joins = implode("\n", $joins);
3953  
3954      // Ok, let's get the users!
3955      $sql = "SELECT $fields
3956                FROM {user} u
3957              $joins
3958              $where
3959            ORDER BY $sort";
3960  
3961      return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
3962  }
3963  
3964  /**
3965   * Re-sort a users array based on a sorting policy
3966   *
3967   * Will re-sort a $users results array (from get_users_by_capability(), usually)
3968   * based on a sorting policy. This is to support the odd practice of
3969   * sorting teachers by 'authority', where authority was "lowest id of the role
3970   * assignment".
3971   *
3972   * Will execute 1 database query. Only suitable for small numbers of users, as it
3973   * uses an u.id IN() clause.
3974   *
3975   * Notes about the sorting criteria.
3976   *
3977   * As a default, we cannot rely on role.sortorder because then
3978   * admins/coursecreators will always win. That is why the sane
3979   * rule "is locality matters most", with sortorder as 2nd
3980   * consideration.
3981   *
3982   * If you want role.sortorder, use the 'sortorder' policy, and
3983   * name explicitly what roles you want to cover. It's probably
3984   * a good idea to see what roles have the capabilities you want
3985   * (array_diff() them against roiles that have 'can-do-anything'
3986   * to weed out admin-ish roles. Or fetch a list of roles from
3987   * variables like $CFG->coursecontact .
3988   *
3989   * @param array $users Users array, keyed on userid
3990   * @param context $context
3991   * @param array $roles ids of the roles to include, optional
3992   * @param string $sortpolicy defaults to locality, more about
3993   * @return array sorted copy of the array
3994   */
3995  function sort_by_roleassignment_authority($users, context $context, $roles = array(), $sortpolicy = 'locality') {
3996      global $DB;
3997  
3998      $userswhere = ' ra.userid IN (' . implode(',',array_keys($users)) . ')';
3999      $contextwhere = 'AND ra.contextid IN ('.str_replace('/', ',',substr($context->path, 1)).')';
4000      if (empty($roles)) {
4001          $roleswhere = '';
4002      } else {
4003          $roleswhere = ' AND ra.roleid IN ('.implode(',',$roles).')';
4004      }
4005  
4006      $sql = "SELECT ra.userid
4007                FROM {role_assignments} ra
4008                JOIN {role} r
4009                     ON ra.roleid=r.id
4010                JOIN {context} ctx
4011                     ON ra.contextid=ctx.id
4012               WHERE $userswhere
4013                     $contextwhere
4014                     $roleswhere";
4015  
4016      // Default 'locality' policy -- read PHPDoc notes
4017      // about sort policies...
4018      $orderby = 'ORDER BY '
4019                      .'ctx.depth DESC, '  /* locality wins */
4020                      .'r.sortorder ASC, ' /* rolesorting 2nd criteria */
4021                      .'ra.id';            /* role assignment order tie-breaker */
4022      if ($sortpolicy === 'sortorder') {
4023          $orderby = 'ORDER BY '
4024                          .'r.sortorder ASC, ' /* rolesorting 2nd criteria */
4025                          .'ra.id';            /* role assignment order tie-breaker */
4026      }
4027  
4028      $sortedids = $DB->get_fieldset_sql($sql . $orderby);
4029      $sortedusers = array();
4030      $seen = array();
4031  
4032      foreach ($sortedids as $id) {
4033          // Avoid duplicates
4034          if (isset($seen[$id])) {
4035              continue;
4036          }
4037          $seen[$id] = true;
4038  
4039          // assign
4040          $sortedusers[$id] = $users[$id];
4041      }
4042      return $sortedusers;
4043  }
4044  
4045  /**
4046   * Gets all the users assigned this role in this context or higher
4047   *
4048   * @param int $roleid (can also be an array of ints!)
4049   * @param context $context
4050   * @param bool $parent if true, get list of users assigned in higher context too
4051   * @param string $fields fields from user (u.) , role assignment (ra) or role (r.)
4052   * @param string $sort sort from user (u.) , role assignment (ra.) or role (r.).
4053   *      null => use default sort from users_order_by_sql.
4054   * @param bool $all true means all, false means limit to enrolled users
4055   * @param string $group defaults to ''
4056   * @param mixed $limitfrom defaults to ''
4057   * @param mixed $limitnum defaults to ''
4058   * @param string $extrawheretest defaults to ''
4059   * @param array $whereorsortparams any paramter values used by $sort or $extrawheretest.
4060   * @return array
4061   */
4062  function get_role_users($roleid, context $context, $parent = false, $fields = '',
4063          $sort = null, $all = true, $group = '',
4064          $limitfrom = '', $limitnum = '', $extrawheretest = '', $whereorsortparams = array()) {
4065      global $DB;
4066  
4067      if (empty($fields)) {
4068          $allnames = get_all_user_name_fields(true, 'u');
4069          $fields = 'u.id, u.confirmed, u.username, '. $allnames . ', ' .
4070                    'u.maildisplay, u.mailformat, u.maildigest, u.email, u.emailstop, u.city, '.
4071                    'u.country, u.picture, u.idnumber, u.department, u.institution, '.
4072                    'u.lang, u.timezone, u.lastaccess, u.mnethostid, r.name AS rolename, r.sortorder, '.
4073                    'r.shortname AS roleshortname, rn.name AS rolecoursealias';
4074      }
4075  
4076      $parentcontexts = '';
4077      if ($parent) {
4078          $parentcontexts = substr($context->path, 1); // kill leading slash
4079          $parentcontexts = str_replace('/', ',', $parentcontexts);
4080          if ($parentcontexts !== '') {
4081              $parentcontexts = ' OR ra.contextid IN ('.$parentcontexts.' )';
4082          }
4083      }
4084  
4085      if ($roleid) {
4086          list($rids, $params) = $DB->get_in_or_equal($roleid, SQL_PARAMS_NAMED, 'r');
4087          $roleselect = "AND ra.roleid $rids";
4088      } else {
4089          $params = array();
4090          $roleselect = '';
4091      }
4092  
4093      if ($coursecontext = $context->get_course_context(false)) {
4094          $params['coursecontext'] = $coursecontext->id;
4095      } else {
4096          $params['coursecontext'] = 0;
4097      }
4098  
4099      if ($group) {
4100          $groupjoin   = "JOIN {groups_members} gm ON gm.userid = u.id";
4101          $groupselect = " AND gm.groupid = :groupid ";
4102          $params['groupid'] = $group;
4103      } else {
4104          $groupjoin   = '';
4105          $groupselect = '';
4106      }
4107  
4108      $params['contextid'] = $context->id;
4109  
4110      if ($extrawheretest) {
4111          $extrawheretest = ' AND ' . $extrawheretest;
4112      }
4113  
4114      if ($whereorsortparams) {
4115          $params = array_merge($params, $whereorsortparams);
4116      }
4117  
4118      if (!$sort) {
4119          list($sort, $sortparams) = users_order_by_sql('u');
4120          $params = array_merge($params, $sortparams);
4121      }
4122  
4123      if ($all === null) {
4124          // Previously null was used to indicate that parameter was not used.
4125          $all = true;
4126      }
4127      if (!$all and $coursecontext) {
4128          // Do not use get_enrolled_sql() here for performance reasons.
4129          $ejoin = "JOIN {user_enrolments} ue ON ue.userid = u.id
4130                    JOIN {enrol} e ON (e.id = ue.enrolid AND e.courseid = :ecourseid)";
4131          $params['ecourseid'] = $coursecontext->instanceid;
4132      } else {
4133          $ejoin = "";
4134      }
4135  
4136      $sql = "SELECT DISTINCT $fields, ra.roleid
4137                FROM {role_assignments} ra
4138                JOIN {user} u ON u.id = ra.userid
4139                JOIN {role} r ON ra.roleid = r.id
4140              $ejoin
4141           LEFT JOIN {role_names} rn ON (rn.contextid = :coursecontext AND rn.roleid = r.id)
4142          $groupjoin
4143               WHERE (ra.contextid = :contextid $parentcontexts)
4144                     $roleselect
4145                     $groupselect
4146                     $extrawheretest
4147            ORDER BY $sort";                  // join now so that we can just use fullname() later
4148  
4149      return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
4150  }
4151  
4152  /**
4153   * Counts all the users assigned this role in this context or higher
4154   *
4155   * @param int|array $roleid either int or an array of ints
4156   * @param context $context
4157   * @param bool $parent if true, get list of users assigned in higher context too
4158   * @return int Returns the result count
4159   */
4160  function count_role_users($roleid, context $context, $parent = false) {
4161      global $DB;
4162  
4163      if ($parent) {
4164          if ($contexts = $context->get_parent_context_ids()) {
4165              $parentcontexts = ' OR r.contextid IN ('.implode(',', $contexts).')';
4166          } else {
4167              $parentcontexts = '';
4168          }
4169      } else {
4170          $parentcontexts = '';
4171      }
4172  
4173      if ($roleid) {
4174          list($rids, $params) = $DB->get_in_or_equal($roleid, SQL_PARAMS_QM);
4175          $roleselect = "AND r.roleid $rids";
4176      } else {
4177          $params = array();
4178          $roleselect = '';
4179      }
4180  
4181      array_unshift($params, $context->id);
4182  
4183      $sql = "SELECT COUNT(u.id)
4184                FROM {role_assignments} r
4185                JOIN {user} u ON u.id = r.userid
4186               WHERE (r.contextid = ? $parentcontexts)
4187                     $roleselect
4188                     AND u.deleted = 0";
4189  
4190      return $DB->count_records_sql($sql, $params);
4191  }
4192  
4193  /**
4194   * This function gets the list of courses that this user has a particular capability in.
4195   * It is still not very efficient.
4196   *
4197   * @param string $capability Capability in question
4198   * @param int $userid User ID or null for current user
4199   * @param bool $doanything True if 'doanything' is permitted (default)
4200   * @param string $fieldsexceptid Leave blank if you only need 'id' in the course records;
4201   *   otherwise use a comma-separated list of the fields you require, not including id
4202   * @param string $orderby If set, use a comma-separated list of fields from course
4203   *   table with sql modifiers (DESC) if needed
4204   * @return array|bool Array of courses, if none found false is returned.
4205   */
4206  function get_user_capability_course($capability, $userid = null, $doanything = true, $fieldsexceptid = '', $orderby = '') {
4207      global $DB;
4208  
4209      // Convert fields list and ordering
4210      $fieldlist = '';
4211      if ($fieldsexceptid) {
4212          $fields = explode(',', $fieldsexceptid);
4213          foreach($fields as $field) {
4214              $fieldlist .= ',c.'.$field;
4215          }
4216      }
4217      if ($orderby) {
4218          $fields = explode(',', $orderby);
4219          $orderby = '';
4220          foreach($fields as $field) {
4221              if ($orderby) {
4222                  $orderby .= ',';
4223              }
4224              $orderby .= 'c.'.$field;
4225          }
4226          $orderby = 'ORDER BY '.$orderby;
4227      }
4228  
4229      // Obtain a list of everything relevant about all courses including context.
4230      // Note the result can be used directly as a context (we are going to), the course
4231      // fields are just appended.
4232  
4233      $contextpreload = context_helper::get_preload_record_columns_sql('x');
4234  
4235      $courses = array();
4236      $rs = $DB->get_recordset_sql("SELECT c.id $fieldlist, $contextpreload
4237                                      FROM {course} c
4238                                      JOIN {context} x ON (c.id=x.instanceid AND x.contextlevel=".CONTEXT_COURSE.")
4239                                  $orderby");
4240      // Check capability for each course in turn
4241      foreach ($rs as $course) {
4242          context_helper::preload_from_record($course);
4243          $context = context_course::instance($course->id);
4244          if (has_capability($capability, $context, $userid, $doanything)) {
4245              // We've got the capability. Make the record look like a course record
4246              // and store it
4247              $courses[] = $course;
4248          }
4249      }
4250      $rs->close();
4251      return empty($courses) ? false : $courses;
4252  }
4253  
4254  /**
4255   * This function finds the roles assigned directly to this context only
4256   * i.e. no roles in parent contexts
4257   *
4258   * @param context $context
4259   * @return array
4260   */
4261  function get_roles_on_exact_context(context $context) {
4262      global $DB;
4263  
4264      return $DB->get_records_sql("SELECT r.*
4265                                     FROM {role_assignments} ra, {role} r
4266                                    WHERE ra.roleid = r.id AND ra.contextid = ?",
4267                                  array($context->id));
4268  }
4269  
4270  /**
4271   * Switches the current user to another role for the current session and only
4272   * in the given context.
4273   *
4274   * The caller *must* check
4275   * - that this op is allowed
4276   * - that the requested role can be switched to in this context (use get_switchable_roles)
4277   * - that the requested role is NOT $CFG->defaultuserroleid
4278   *
4279   * To "unswitch" pass 0 as the roleid.
4280   *
4281   * This function *will* modify $USER->access - beware
4282   *
4283   * @param integer $roleid the role to switch to.
4284   * @param context $context the context in which to perform the switch.
4285   * @return bool success or failure.
4286   */
4287  function role_switch($roleid, context $context) {
4288      global $USER;
4289  
4290      //
4291      // Plan of action
4292      //
4293      // - Add the ghost RA to $USER->access
4294      //   as $USER->access['rsw'][$path] = $roleid
4295      //
4296      // - Make sure $USER->access['rdef'] has the roledefs
4297      //   it needs to honour the switcherole
4298      //
4299      // Roledefs will get loaded "deep" here - down to the last child
4300      // context. Note that
4301      //
4302      // - When visiting subcontexts, our selective accessdata loading
4303      //   will still work fine - though those ra/rdefs will be ignored
4304      //   appropriately while the switch is in place
4305      //
4306      // - If a switcherole happens at a category with tons of courses
4307      //   (that have many overrides for switched-to role), the session
4308      //   will get... quite large. Sometimes you just can't win.
4309      //
4310      // To un-switch just unset($USER->access['rsw'][$path])
4311      //
4312      // Note: it is not possible to switch to roles that do not have course:view
4313  
4314      if (!isset($USER->access)) {
4315          load_all_capabilities();
4316      }
4317  
4318  
4319      // Add the switch RA
4320      if ($roleid == 0) {
4321          unset($USER->access['rsw'][$context->path]);
4322          return true;
4323      }
4324  
4325      $USER->access['rsw'][$context->path] = $roleid;
4326  
4327      // Load roledefs
4328      load_role_access_by_context($roleid, $context, $USER->access);
4329  
4330      return true;
4331  }
4332  
4333  /**
4334   * Checks if the user has switched roles within the given course.
4335   *
4336   * Note: You can only switch roles within the course, hence it takes a course id
4337   * rather than a context. On that note Petr volunteered to implement this across
4338   * all other contexts, all requests for this should be forwarded to him ;)
4339   *
4340   * @param int $courseid The id of the course to check
4341   * @return bool True if the user has switched roles within the course.
4342   */
4343  function is_role_switched($courseid) {
4344      global $USER;
4345      $context = context_course::instance($courseid, MUST_EXIST);
4346      return (!empty($USER->access['rsw'][$context->path]));
4347  }
4348  
4349  /**
4350   * Get any role that has an override on exact context
4351   *
4352   * @param context $context The context to check
4353   * @return array An array of roles
4354   */
4355  function get_roles_with_override_on_context(context $context) {
4356      global $DB;
4357  
4358      return $DB->get_records_sql("SELECT r.*
4359                                     FROM {role_capabilities} rc, {role} r
4360                                    WHERE rc.roleid = r.id AND rc.contextid = ?",
4361                                  array($context->id));
4362  }
4363  
4364  /**
4365   * Get all capabilities for this role on this context (overrides)
4366   *
4367   * @param stdClass $role
4368   * @param context $context
4369   * @return array
4370   */
4371  function get_capabilities_from_role_on_context($role, context $context) {
4372      global $DB;
4373  
4374      return $DB->get_records_sql("SELECT *
4375                                     FROM {role_capabilities}
4376                                    WHERE contextid = ? AND roleid = ?",
4377                                  array($context->id, $role->id));
4378  }
4379  
4380  /**
4381   * Find out which roles has assignment on this context
4382   *
4383   * @param context $context
4384   * @return array
4385   *
4386   */
4387  function get_roles_with_assignment_on_context(context $context) {
4388      global $DB;
4389  
4390      return $DB->get_records_sql("SELECT r.*
4391                                     FROM {role_assignments} ra, {role} r
4392                                    WHERE ra.roleid = r.id AND ra.contextid = ?",
4393                                  array($context->id));
4394  }
4395  
4396  /**
4397   * Find all user assignment of users for this role, on this context
4398   *
4399   * @param stdClass $role
4400   * @param context $context
4401   * @return array
4402   */
4403  function get_users_from_role_on_context($role, context $context) {
4404      global $DB;
4405  
4406      return $DB->get_records_sql("SELECT *
4407                                     FROM {role_assignments}
4408                                    WHERE contextid = ? AND roleid = ?",
4409                                  array($context->id, $role->id));
4410  }
4411  
4412  /**
4413   * Simple function returning a boolean true if user has roles
4414   * in context or parent contexts, otherwise false.
4415   *
4416   * @param int $userid
4417   * @param int $roleid
4418   * @param int $contextid empty means any context
4419   * @return bool
4420   */
4421  function user_has_role_assignment($userid, $roleid, $contextid = 0) {
4422      global $DB;
4423  
4424      if ($contextid) {
4425          if (!$context = context::instance_by_id($contextid, IGNORE_MISSING)) {
4426              return false;
4427          }
4428          $parents = $context->get_parent_context_ids(true);
4429          list($contexts, $params) = $DB->get_in_or_equal($parents, SQL_PARAMS_NAMED, 'r');
4430          $params['userid'] = $userid;
4431          $params['roleid'] = $roleid;
4432  
4433          $sql = "SELECT COUNT(ra.id)
4434                    FROM {role_assignments} ra
4435                   WHERE ra.userid = :userid AND ra.roleid = :roleid AND ra.contextid $contexts";
4436  
4437          $count = $DB->get_field_sql($sql, $params);
4438          return ($count > 0);
4439  
4440      } else {
4441          return $DB->record_exists('role_assignments', array('userid'=>$userid, 'roleid'=>$roleid));
4442      }
4443  }
4444  
4445  /**
4446   * Get localised role name or alias if exists and format the text.
4447   *
4448   * @param stdClass $role role object
4449   *      - optional 'coursealias' property should be included for performance reasons if course context used
4450   *      - description property is not required here
4451   * @param context|bool $context empty means system context
4452   * @param int $rolenamedisplay type of role name
4453   * @return string localised role name or course role name alias
4454   */
4455  function role_get_name(stdClass $role, $context = null, $rolenamedisplay = ROLENAME_ALIAS) {
4456      global $DB;
4457  
4458      if ($rolenamedisplay == ROLENAME_SHORT) {
4459          return $role->shortname;
4460      }
4461  
4462      if (!$context or !$coursecontext = $context->get_course_context(false)) {
4463          $coursecontext = null;
4464      }
4465  
4466      if ($coursecontext and !property_exists($role, 'coursealias') and ($rolenamedisplay == ROLENAME_ALIAS or $rolenamedisplay == ROLENAME_BOTH or $rolenamedisplay == ROLENAME_ALIAS_RAW)) {
4467          $role = clone($role); // Do not modify parameters.
4468          if ($r = $DB->get_record('role_names', array('roleid'=>$role->id, 'contextid'=>$coursecontext->id))) {
4469              $role->coursealias = $r->name;
4470          } else {
4471              $role->coursealias = null;
4472          }
4473      }
4474  
4475      if ($rolenamedisplay == ROLENAME_ALIAS_RAW) {
4476          if ($coursecontext) {
4477              return $role->coursealias;
4478          } else {
4479              return null;
4480          }
4481      }
4482  
4483      if (trim($role->name) !== '') {
4484          // For filtering always use context where was the thing defined - system for roles here.
4485          $original = format_string($role->name, true, array('context'=>context_system::instance()));
4486  
4487      } else {
4488          // Empty role->name means we want to see localised role name based on shortname,
4489          // only default roles are supposed to be localised.
4490          switch ($role->shortname) {
4491              case 'manager':         $original = get_string('manager', 'role'); break;
4492              case 'coursecreator':   $original = get_string('coursecreators'); break;
4493              case 'editingteacher':  $original = get_string('defaultcourseteacher'); break;
4494              case 'teacher':         $original = get_string('noneditingteacher'); break;
4495              case 'student':         $original = get_string('defaultcoursestudent'); break;
4496              case 'guest':           $original = get_string('guest'); break;
4497              case 'user':            $original = get_string('authenticateduser'); break;
4498              case 'frontpage':       $original = get_string('frontpageuser', 'role'); break;
4499              // We should not get here, the role UI should require the name for custom roles!
4500              default:                $original = $role->shortname; break;
4501          }
4502      }
4503  
4504      if ($rolenamedisplay == ROLENAME_ORIGINAL) {
4505          return $original;
4506      }
4507  
4508      if ($rolenamedisplay == ROLENAME_ORIGINALANDSHORT) {
4509          return "$original ($role->shortname)";
4510      }
4511  
4512      if ($rolenamedisplay == ROLENAME_ALIAS) {
4513          if ($coursecontext and trim($role->coursealias) !== '') {
4514              return format_string($role->coursealias, true, array('context'=>$coursecontext));
4515          } else {
4516              return $original;
4517          }
4518      }
4519  
4520      if ($rolenamedisplay == ROLENAME_BOTH) {
4521          if ($coursecontext and trim($role->coursealias) !== '') {
4522              return format_string($role->coursealias, true, array('context'=>$coursecontext)) . " ($original)";
4523          } else {
4524              return $original;
4525          }
4526      }
4527  
4528      throw new coding_exception('Invalid $rolenamedisplay parameter specified in role_get_name()');
4529  }
4530  
4531  /**
4532   * Returns localised role description if available.
4533   * If the name is empty it tries to find the default role name using
4534   * hardcoded list of default role names or other methods in the future.
4535   *
4536   * @param stdClass $role
4537   * @return string localised role name
4538   */
4539  function role_get_description(stdClass $role) {
4540      if (!html_is_blank($role->description)) {
4541          return format_text($role->description, FORMAT_HTML, array('context'=>context_system::instance()));
4542      }
4543  
4544      switch ($role->shortname) {
4545          case 'manager':         return get_string('managerdescription', 'role');
4546          case 'coursecreator':   return get_string('coursecreatorsdescription');
4547          case 'editingteacher':  return get_string('defaultcourseteacherdescription');
4548          case 'teacher':         return get_string('noneditingteacherdescription');
4549          case 'student':         return get_string('defaultcoursestudentdescription');
4550          case 'guest':           return get_string('guestdescription');
4551          case 'user':            return get_string('authenticateduserdescription');
4552          case 'frontpage':       return get_string('frontpageuserdescription', 'role');
4553          default:                return '';
4554      }
4555  }
4556  
4557  /**
4558   * Get all the localised role names for a context.
4559   *
4560   * In new installs default roles have empty names, this function
4561   * add localised role names using current language pack.
4562   *
4563   * @param context $context the context, null means system context
4564   * @param array of role objects with a ->localname field containing the context-specific role name.
4565   * @param int $rolenamedisplay
4566   * @param bool $returnmenu true means id=>localname, false means id=>rolerecord
4567   * @return array Array of context-specific role names, or role objects with a ->localname field added.
4568   */
4569  function role_get_names(context $context = null, $rolenamedisplay = ROLENAME_ALIAS, $returnmenu = null) {
4570      return role_fix_names(get_all_roles($context), $context, $rolenamedisplay, $returnmenu);
4571  }
4572  
4573  /**
4574   * Prepare list of roles for display, apply aliases and localise default role names.
4575   *
4576   * @param array $roleoptions array roleid => roleobject (with optional coursealias), strings are accepted for backwards compatibility only
4577   * @param context $context the context, null means system context
4578   * @param int $rolenamedisplay
4579   * @param bool $returnmenu null means keep the same format as $roleoptions, true means id=>localname, false means id=>rolerecord
4580   * @return array Array of context-specific role names, or role objects with a ->localname field added.
4581   */
4582  function role_fix_names($roleoptions, context $context = null, $rolenamedisplay = ROLENAME_ALIAS, $returnmenu = null) {
4583      global $DB;
4584  
4585      if (empty($roleoptions)) {
4586          return array();
4587      }
4588  
4589      if (!$context or !$coursecontext = $context->get_course_context(false)) {
4590          $coursecontext = null;
4591      }
4592  
4593      // We usually need all role columns...
4594      $first = reset($roleoptions);
4595      if ($returnmenu === null) {
4596          $returnmenu = !is_object($first);
4597      }
4598  
4599      if (!is_object($first) or !property_exists($first, 'shortname')) {
4600          $allroles = get_all_roles($context);
4601          foreach ($roleoptions as $rid => $unused) {
4602              $roleoptions[$rid] = $allroles[$rid];
4603          }
4604      }
4605  
4606      // Inject coursealias if necessary.
4607      if ($coursecontext and ($rolenamedisplay == ROLENAME_ALIAS_RAW or $rolenamedisplay == ROLENAME_ALIAS or $rolenamedisplay == ROLENAME_BOTH)) {
4608          $first = reset($roleoptions);
4609          if (!property_exists($first, 'coursealias')) {
4610              $aliasnames = $DB->get_records('role_names', array('contextid'=>$coursecontext->id));
4611              foreach ($aliasnames as $alias) {
4612                  if (isset($roleoptions[$alias->roleid])) {
4613                      $roleoptions[$alias->roleid]->coursealias = $alias->name;
4614                  }
4615              }
4616          }
4617      }
4618  
4619      // Add localname property.
4620      foreach ($roleoptions as $rid => $role) {
4621          $roleoptions[$rid]->localname = role_get_name($role, $coursecontext, $rolenamedisplay);
4622      }
4623  
4624      if (!$returnmenu) {
4625          return $roleoptions;
4626      }
4627  
4628      $menu = array();
4629      foreach ($roleoptions as $rid => $role) {
4630          $menu[$rid] = $role->localname;
4631      }
4632  
4633      return $menu;
4634  }
4635  
4636  /**
4637   * Aids in detecting if a new line is required when reading a new capability
4638   *
4639   * This function helps admin/roles/manage.php etc to detect if a new line should be printed
4640   * when we read in a new capability.
4641   * Most of the time, if the 2 components are different we should print a new line, (e.g. course system->rss client)
4642   * but when we are in grade, all reports/import/export capabilities should be together
4643   *
4644   * @param string $cap component string a
4645   * @param string $comp component string b
4646   * @param int $contextlevel
4647   * @return bool whether 2 component are in different "sections"
4648   */
4649  function component_level_changed($cap, $comp, $contextlevel) {
4650  
4651      if (strstr($cap->component, '/') && strstr($comp, '/')) {
4652          $compsa = explode('/', $cap->component);
4653          $compsb = explode('/', $comp);
4654  
4655          // list of system reports
4656          if (($compsa[0] == 'report') && ($compsb[0] == 'report')) {
4657              return false;
4658          }
4659  
4660          // we are in gradebook, still
4661          if (($compsa[0] == 'gradeexport' || $compsa[0] == 'gradeimport' || $compsa[0] == 'gradereport') &&
4662              ($compsb[0] == 'gradeexport' || $compsb[0] == 'gradeimport' || $compsb[0] == 'gradereport')) {
4663              return false;
4664          }
4665  
4666          if (($compsa[0] == 'coursereport') && ($compsb[0] == 'coursereport')) {
4667              return false;
4668          }
4669      }
4670  
4671      return ($cap->component != $comp || $cap->contextlevel != $contextlevel);
4672  }
4673  
4674  /**
4675   * Fix the roles.sortorder field in the database, so it contains sequential integers,
4676   * and return an array of roleids in order.
4677   *
4678   * @param array $allroles array of roles, as returned by get_all_roles();
4679   * @return array $role->sortorder =-> $role->id with the keys in ascending order.
4680   */
4681  function fix_role_sortorder($allroles) {
4682      global $DB;
4683  
4684      $rolesort = array();
4685      $i = 0;
4686      foreach ($allroles as $role) {
4687          $rolesort[$i] = $role->id;
4688          if ($role->sortorder != $i) {
4689              $r = new stdClass();
4690              $r->id = $role->id;
4691              $r->sortorder = $i;
4692              $DB->update_record('role', $r);
4693              $allroles[$role->id]->sortorder = $i;
4694          }
4695          $i++;
4696      }
4697      return $rolesort;
4698  }
4699  
4700  /**
4701   * Switch the sort order of two roles (used in admin/roles/manage.php).
4702   *
4703   * @param stdClass $first The first role. Actually, only ->sortorder is used.
4704   * @param stdClass $second The second role. Actually, only ->sortorder is used.
4705   * @return boolean success or failure
4706   */
4707  function switch_roles($first, $second) {
4708      global $DB;
4709      $temp = $DB->get_field('role', 'MAX(sortorder) + 1', array());
4710      $result = $DB->set_field('role', 'sortorder', $temp, array('sortorder' => $first->sortorder));
4711      $result = $result && $DB->set_field('role', 'sortorder', $first->sortorder, array('sortorder' => $second->sortorder));
4712      $result = $result && $DB->set_field('role', 'sortorder', $second->sortorder, array('sortorder' => $temp));
4713      return $result;
4714  }
4715  
4716  /**
4717   * Duplicates all the base definitions of a role
4718   *
4719   * @param stdClass $sourcerole role to copy from
4720   * @param int $targetrole id of role to copy to
4721   */
4722  function role_cap_duplicate($sourcerole, $targetrole) {
4723      global $DB;
4724  
4725      $systemcontext = context_system::instance();
4726      $caps = $DB->get_records_sql("SELECT *
4727                                      FROM {role_capabilities}
4728                                     WHERE roleid = ? AND contextid = ?",
4729                                   array($sourcerole->id, $systemcontext->id));
4730      // adding capabilities
4731      foreach ($caps as $cap) {
4732          unset($cap->id);
4733          $cap->roleid = $targetrole;
4734          $DB->insert_record('role_capabilities', $cap);
4735      }
4736  }
4737  
4738  /**
4739   * Returns two lists, this can be used to find out if user has capability.
4740   * Having any needed role and no forbidden role in this context means
4741   * user has this capability in this context.
4742   * Use get_role_names_with_cap_in_context() if you need role names to display in the UI
4743   *
4744   * @param stdClass $context
4745   * @param string $capability
4746   * @return array($neededroles, $forbiddenroles)
4747   */
4748  function get_roles_with_cap_in_context($context, $capability) {
4749      global $DB;
4750  
4751      $ctxids = trim($context->path, '/'); // kill leading slash
4752      $ctxids = str_replace('/', ',', $ctxids);
4753  
4754      $sql = "SELECT rc.id, rc.roleid, rc.permission, ctx.depth
4755                FROM {role_capabilities} rc
4756                JOIN {context} ctx ON ctx.id = rc.contextid
4757               WHERE rc.capability = :cap AND ctx.id IN ($ctxids)
4758            ORDER BY rc.roleid ASC, ctx.depth DESC";
4759      $params = array('cap'=>$capability);
4760  
4761      if (!$capdefs = $DB->get_records_sql($sql, $params)) {
4762          // no cap definitions --> no capability
4763          return array(array(), array());
4764      }
4765  
4766      $forbidden = array();
4767      $needed    = array();
4768      foreach($capdefs as $def) {
4769          if (isset($forbidden[$def->roleid])) {
4770              continue;
4771          }
4772          if ($def->permission == CAP_PROHIBIT) {
4773              $forbidden[$def->roleid] = $def->roleid;
4774              unset($needed[$def->roleid]);
4775              continue;
4776          }
4777          if (!isset($needed[$def->roleid])) {
4778              if ($def->permission == CAP_ALLOW) {
4779                  $needed[$def->roleid] = true;
4780              } else if ($def->permission == CAP_PREVENT) {
4781                  $needed[$def->roleid] = false;
4782              }
4783          }
4784      }
4785      unset($capdefs);
4786  
4787      // remove all those roles not allowing
4788      foreach($needed as $key=>$value) {
4789          if (!$value) {
4790              unset($needed[$key]);
4791          } else {
4792              $needed[$key] = $key;
4793          }
4794      }
4795  
4796      return array($needed, $forbidden);
4797  }
4798  
4799  /**
4800   * Returns an array of role IDs that have ALL of the the supplied capabilities
4801   * Uses get_roles_with_cap_in_context(). Returns $allowed minus $forbidden
4802   *
4803   * @param stdClass $context
4804   * @param array $capabilities An array of capabilities
4805   * @return array of roles with all of the required capabilities
4806   */
4807  function get_roles_with_caps_in_context($context, $capabilities) {
4808      $neededarr = array();
4809      $forbiddenarr = array();
4810      foreach($capabilities as $caprequired) {
4811          list($neededarr[], $forbiddenarr[]) = get_roles_with_cap_in_context($context, $caprequired);
4812      }
4813  
4814      $rolesthatcanrate = array();
4815      if (!empty($neededarr)) {
4816          foreach ($neededarr as $needed) {
4817              if (empty($rolesthatcanrate)) {
4818                  $rolesthatcanrate = $needed;
4819              } else {
4820                  //only want roles that have all caps
4821                  $rolesthatcanrate = array_intersect_key($rolesthatcanrate,$needed);
4822              }
4823          }
4824      }
4825      if (!empty($forbiddenarr) && !empty($rolesthatcanrate)) {
4826          foreach ($forbiddenarr as $forbidden) {
4827             //remove any roles that are forbidden any of the caps
4828             $rolesthatcanrate = array_diff($rolesthatcanrate, $forbidden);
4829          }
4830      }
4831      return $rolesthatcanrate;
4832  }
4833  
4834  /**
4835   * Returns an array of role names that have ALL of the the supplied capabilities
4836   * Uses get_roles_with_caps_in_context(). Returns $allowed minus $forbidden
4837   *
4838   * @param stdClass $context
4839   * @param array $capabilities An array of capabilities
4840   * @return array of roles with all of the required capabilities
4841   */
4842  function get_role_names_with_caps_in_context($context, $capabilities) {
4843      global $DB;
4844  
4845      $rolesthatcanrate = get_roles_with_caps_in_context($context, $capabilities);
4846      $allroles = $DB->get_records('role', null, 'sortorder DESC');
4847  
4848      $roles = array();
4849      foreach ($rolesthatcanrate as $r) {
4850          $roles[$r] = $allroles[$r];
4851      }
4852  
4853      return role_fix_names($roles, $context, ROLENAME_ALIAS, true);
4854  }
4855  
4856  /**
4857   * This function verifies the prohibit comes from this context
4858   * and there are no more prohibits in parent contexts.
4859   *
4860   * @param int $roleid
4861   * @param context $context
4862   * @param string $capability name
4863   * @return bool
4864   */
4865  function prohibit_is_removable($roleid, context $context, $capability) {
4866      global $DB;
4867  
4868      $ctxids = trim($context->path, '/'); // kill leading slash
4869      $ctxids = str_replace('/', ',', $ctxids);
4870  
4871      $params = array('roleid'=>$roleid, 'cap'=>$capability, 'prohibit'=>CAP_PROHIBIT);
4872  
4873      $sql = "SELECT ctx.id
4874                FROM {role_capabilities} rc
4875                JOIN {context} ctx ON ctx.id = rc.contextid
4876               WHERE rc.roleid = :roleid AND rc.permission = :prohibit AND rc.capability = :cap AND ctx.id IN ($ctxids)
4877            ORDER BY ctx.depth DESC";
4878  
4879      if (!$prohibits = $DB->get_records_sql($sql, $params)) {
4880          // no prohibits == nothing to remove
4881          return true;
4882      }
4883  
4884      if (count($prohibits) > 1) {
4885          // more prohibits can not be removed
4886          return false;
4887      }
4888  
4889      return !empty($prohibits[$context->id]);
4890  }
4891  
4892  /**
4893   * More user friendly role permission changing,
4894   * it should produce as few overrides as possible.
4895   *
4896   * @param int $roleid
4897   * @param stdClass $context
4898   * @param string $capname capability name
4899   * @param int $permission
4900   * @return void
4901   */
4902  function role_change_permission($roleid, $context, $capname, $permission) {
4903      global $DB;
4904  
4905      if ($permission == CAP_INHERIT) {
4906          unassign_capability($capname, $roleid, $context->id);
4907          $context->mark_dirty();
4908          return;
4909      }
4910  
4911      $ctxids = trim($context->path, '/'); // kill leading slash
4912      $ctxids = str_replace('/', ',', $ctxids);
4913  
4914      $params = array('roleid'=>$roleid, 'cap'=>$capname);
4915  
4916      $sql = "SELECT ctx.id, rc.permission, ctx.depth
4917                FROM {role_capabilities} rc
4918                JOIN {context} ctx ON ctx.id = rc.contextid
4919               WHERE rc.roleid = :roleid AND rc.capability = :cap AND ctx.id IN ($ctxids)
4920            ORDER BY ctx.depth DESC";
4921  
4922      if ($existing = $DB->get_records_sql($sql, $params)) {
4923          foreach($existing as $e) {
4924              if ($e->permission == CAP_PROHIBIT) {
4925                  // prohibit can not be overridden, no point in changing anything
4926                  return;
4927              }
4928          }
4929          $lowest = array_shift($existing);
4930          if ($lowest->permission == $permission) {
4931              // permission already set in this context or parent - nothing to do
4932              return;
4933          }
4934          if ($existing) {
4935              $parent = array_shift($existing);
4936              if ($parent->permission == $permission) {
4937                  // permission already set in parent context or parent - just unset in this context
4938                  // we do this because we want as few overrides as possible for performance reasons
4939                  unassign_capability($capname, $roleid, $context->id);
4940                  $context->mark_dirty();
4941                  return;
4942              }
4943          }
4944  
4945      } else {
4946          if ($permission == CAP_PREVENT) {
4947              // nothing means role does not have permission
4948              return;
4949          }
4950      }
4951  
4952      // assign the needed capability
4953      assign_capability($capname, $permission, $roleid, $context->id, true);
4954  
4955      // force cap reloading
4956      $context->mark_dirty();
4957  }
4958  
4959  
4960  /**
4961   * Basic moodle context abstraction class.
4962   *
4963   * Google confirms that no other important framework is using "context" class,
4964   * we could use something else like mcontext or moodle_context, but we need to type
4965   * this very often which would be annoying and it would take too much space...
4966   *
4967   * This class is derived from stdClass for backwards compatibility with
4968   * odl $context record that was returned from DML $DB->get_record()
4969   *
4970   * @package   core_access
4971   * @category  access
4972   * @copyright Petr Skoda {@link http://skodak.org}
4973   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
4974   * @since     Moodle 2.2
4975   *
4976   * @property-read int $id context id
4977   * @property-read int $contextlevel CONTEXT_SYSTEM, CONTEXT_COURSE, etc.
4978   * @property-read int $instanceid id of related instance in each context
4979   * @property-read string $path path to context, starts with system context
4980   * @property-read int $depth
4981   */
4982  abstract class context extends stdClass implements IteratorAggregate {
4983  
4984      /**
4985       * The context id
4986       * Can be accessed publicly through $context->id
4987       * @var int
4988       */
4989      protected $_id;
4990  
4991      /**
4992       * The context level
4993       * Can be accessed publicly through $context->contextlevel
4994       * @var int One of CONTEXT_* e.g. CONTEXT_COURSE, CONTEXT_MODULE
4995       */
4996      protected $_contextlevel;
4997  
4998      /**
4999       * Id of the item this context is related to e.g. COURSE_CONTEXT => course.id
5000       * Can be accessed publicly through $context->instanceid
5001       * @var int
5002       */
5003      protected $_instanceid;
5004  
5005      /**
5006       * The path to the context always starting from the system context
5007       * Can be accessed publicly through $context->path
5008       * @var string
5009       */
5010      protected $_path;
5011  
5012      /**
5013       * The depth of the context in relation to parent contexts
5014       * Can be accessed publicly through $context->depth
5015       * @var int
5016       */
5017      protected $_depth;
5018  
5019      /**
5020       * @var array Context caching info
5021       */
5022      private static $cache_contextsbyid = array();
5023  
5024      /**
5025       * @var array Context caching info
5026       */
5027      private static $cache_contexts     = array();
5028  
5029      /**
5030       * Context count
5031       * Why do we do count contexts? Because count($array) is horribly slow for large arrays
5032       * @var int
5033       */
5034      protected static $cache_count      = 0;
5035  
5036      /**
5037       * @var array Context caching info
5038       */
5039      protected static $cache_preloaded  = array();
5040  
5041      /**
5042       * @var context_system The system context once initialised
5043       */
5044      protected static $systemcontext    = null;
5045  
5046      /**
5047       * Resets the cache to remove all data.
5048       * @static
5049       */
5050      protected static function reset_caches() {
5051          self::$cache_contextsbyid = array();
5052          self::$cache_contexts     = array();
5053          self::$cache_count        = 0;
5054          self::$cache_preloaded    = array();
5055  
5056          self::$systemcontext = null;
5057      }
5058  
5059      /**
5060       * Adds a context to the cache. If the cache is full, discards a batch of
5061       * older entries.
5062       *
5063       * @static
5064       * @param context $context New context to add
5065       * @return void
5066       */
5067      protected static function cache_add(context $context) {
5068          if (isset(self::$cache_contextsbyid[$context->id])) {
5069              // already cached, no need to do anything - this is relatively cheap, we do all this because count() is slow
5070              return;
5071          }
5072  
5073          if (self::$cache_count >= CONTEXT_CACHE_MAX_SIZE) {
5074              $i = 0;
5075              foreach(self::$cache_contextsbyid as $ctx) {
5076                  $i++;
5077                  if ($i <= 100) {
5078                      // we want to keep the first contexts to be loaded on this page, hopefully they will be needed again later
5079                      continue;
5080                  }
5081                  if ($i > (CONTEXT_CACHE_MAX_SIZE / 3)) {
5082                      // we remove oldest third of the contexts to make room for more contexts
5083                      break;
5084                  }
5085                  unset(self::$cache_contextsbyid[$ctx->id]);
5086                  unset(self::$cache_contexts[$ctx->contextlevel][$ctx->instanceid]);
5087                  self::$cache_count--;
5088              }
5089          }
5090  
5091          self::$cache_contexts[$context->contextlevel][$context->instanceid] = $context;
5092          self::$cache_contextsbyid[$context->id] = $context;
5093          self::$cache_count++;
5094      }
5095  
5096      /**
5097       * Removes a context from the cache.
5098       *
5099       * @static
5100       * @param context $context Context object to remove
5101       * @return void
5102       */
5103      protected static function cache_remove(context $context) {
5104          if (!isset(self::$cache_contextsbyid[$context->id])) {
5105              // not cached, no need to do anything - this is relatively cheap, we do all this because count() is slow
5106              return;
5107          }
5108          unset(self::$cache_contexts[$context->contextlevel][$context->instanceid]);
5109          unset(self::$cache_contextsbyid[$context->id]);
5110  
5111          self::$cache_count--;
5112  
5113          if (self::$cache_count < 0) {
5114              self::$cache_count = 0;
5115          }
5116      }
5117  
5118      /**
5119       * Gets a context from the cache.
5120       *
5121       * @static
5122       * @param int $contextlevel Context level
5123       * @param int $instance Instance ID
5124       * @return context|bool Context or false if not in cache
5125       */
5126      protected static function cache_get($contextlevel, $instance) {
5127          if (isset(self::$cache_contexts[$contextlevel][$instance])) {
5128              return self::$cache_contexts[$contextlevel][$instance];
5129          }
5130          return false;
5131      }
5132  
5133      /**
5134       * Gets a context from the cache based on its id.
5135       *
5136       * @static
5137       * @param int $id Context ID
5138       * @return context|bool Context or false if not in cache
5139       */
5140      protected static function cache_get_by_id($id) {
5141          if (isset(self::$cache_contextsbyid[$id])) {
5142              return self::$cache_contextsbyid[$id];
5143          }
5144          return false;
5145      }
5146  
5147      /**
5148       * Preloads context information from db record and strips the cached info.
5149       *
5150       * @static
5151       * @param stdClass $rec
5152       * @return void (modifies $rec)
5153       */
5154       protected static function preload_from_record(stdClass $rec) {
5155           if (empty($rec->ctxid) or empty($rec->ctxlevel) or !isset($rec->ctxinstance) or empty($rec->ctxpath) or empty($rec->ctxdepth)) {
5156               // $rec does not have enough data, passed here repeatedly or context does not exist yet
5157               return;
5158           }
5159  
5160           // note: in PHP5 the objects are passed by reference, no need to return $rec
5161           $record = new stdClass();
5162           $record->id           = $rec->ctxid;       unset($rec->ctxid);
5163           $record->contextlevel = $rec->ctxlevel;    unset($rec->ctxlevel);
5164           $record->instanceid   = $rec->ctxinstance; unset($rec->ctxinstance);
5165           $record->path         = $rec->ctxpath;     unset($rec->ctxpath);
5166           $record->depth        = $rec->ctxdepth;    unset($rec->ctxdepth);
5167  
5168           return context::create_instance_from_record($record);
5169       }
5170  
5171  
5172      // ====== magic methods =======
5173  
5174      /**
5175       * Magic setter method, we do not want anybody to modify properties from the outside
5176       * @param string $name
5177       * @param mixed $value
5178       */
5179      public function __set($name, $value) {
5180          debugging('Can not change context instance properties!');
5181      }
5182  
5183      /**
5184       * Magic method getter, redirects to read only values.
5185       * @param string $name
5186       * @return mixed
5187       */
5188      public function __get($name) {
5189          switch ($name) {
5190              case 'id':           return $this->_id;
5191              case 'contextlevel': return $this->_contextlevel;
5192              case 'instanceid':   return $this->_instanceid;
5193              case 'path':         return $this->_path;
5194              case 'depth':        return $this->_depth;
5195  
5196              default:
5197                  debugging('Invalid context property accessed! '.$name);
5198                  return null;
5199          }
5200      }
5201  
5202      /**
5203       * Full support for isset on our magic read only properties.
5204       * @param string $name
5205       * @return bool
5206       */
5207      public function __isset($name) {
5208          switch ($name) {
5209              case 'id':           return isset($this->_id);
5210              case 'contextlevel': return isset($this->_contextlevel);
5211              case 'instanceid':   return isset($this->_instanceid);
5212              case 'path':         return isset($this->_path);
5213              case 'depth':        return isset($this->_depth);
5214  
5215              default: return false;
5216          }
5217  
5218      }
5219  
5220      /**
5221       * ALl properties are read only, sorry.
5222       * @param string $name
5223       */
5224      public function __unset($name) {
5225          debugging('Can not unset context instance properties!');
5226      }
5227  
5228      // ====== implementing method from interface IteratorAggregate ======
5229  
5230      /**
5231       * Create an iterator because magic vars can't be seen by 'foreach'.
5232       *
5233       * Now we can convert context object to array using convert_to_array(),
5234       * and feed it properly to json_encode().
5235       */
5236      public function getIterator() {
5237          $ret = array(
5238              'id'           => $this->id,
5239              'contextlevel' => $this->contextlevel,
5240              'instanceid'   => $this->instanceid,
5241              'path'         => $this->path,
5242              'depth'        => $this->depth
5243          );
5244          return new ArrayIterator($ret);
5245      }
5246  
5247      // ====== general context methods ======
5248  
5249      /**
5250       * Constructor is protected so that devs are forced to
5251       * use context_xxx::instance() or context::instance_by_id().
5252       *
5253       * @param stdClass $record
5254       */
5255      protected function __construct(stdClass $record) {
5256          $this->_id           = (int)$record->id;
5257          $this->_contextlevel = (int)$record->contextlevel;
5258          $this->_instanceid   = $record->instanceid;
5259          $this->_path         = $record->path;
5260          $this->_depth        = $record->depth;
5261      }
5262  
5263      /**
5264       * This function is also used to work around 'protected' keyword problems in context_helper.
5265       * @static
5266       * @param stdClass $record
5267       * @return context instance
5268       */
5269      protected static function create_instance_from_record(stdClass $record) {
5270          $classname = context_helper::get_class_for_level($record->contextlevel);
5271  
5272          if ($context = context::cache_get_by_id($record->id)) {
5273              return $context;
5274          }
5275  
5276          $context = new $classname($record);
5277          context::cache_add($context);
5278  
5279          return $context;
5280      }
5281  
5282      /**
5283       * Copy prepared new contexts from temp table to context table,
5284       * we do this in db specific way for perf reasons only.
5285       * @static
5286       */
5287      protected static function merge_context_temp_table() {
5288          global $DB;
5289  
5290          /* MDL-11347:
5291           *  - mysql does not allow to use FROM in UPDATE statements
5292           *  - using two tables after UPDATE works in mysql, but might give unexpected
5293           *    results in pg 8 (depends on configuration)
5294           *  - using table alias in UPDATE does not work in pg < 8.2
5295           *
5296           * Different code for each database - mostly for performance reasons
5297           */
5298  
5299          $dbfamily = $DB->get_dbfamily();
5300          if ($dbfamily == 'mysql') {
5301              $updatesql = "UPDATE {context} ct, {context_temp} temp
5302                               SET ct.path     = temp.path,
5303                                   ct.depth    = temp.depth
5304                             WHERE ct.id = temp.id";
5305          } else if ($dbfamily == 'oracle') {
5306              $updatesql = "UPDATE {context} ct
5307                               SET (ct.path, ct.depth) =
5308                                   (SELECT temp.path, temp.depth
5309                                      FROM {context_temp} temp
5310                                     WHERE temp.id=ct.id)
5311                             WHERE EXISTS (SELECT 'x'
5312                                             FROM {context_temp} temp
5313                                             WHERE temp.id = ct.id)";
5314          } else if ($dbfamily == 'postgres' or $dbfamily == 'mssql') {
5315              $updatesql = "UPDATE {context}
5316                               SET path     = temp.path,
5317                                   depth    = temp.depth
5318                              FROM {context_temp} temp
5319                             WHERE temp.id={context}.id";
5320          } else {
5321              // sqlite and others
5322              $updatesql = "UPDATE {context}
5323                               SET path     = (SELECT path FROM {context_temp} WHERE id = {context}.id),
5324                                   depth    = (SELECT depth FROM {context_temp} WHERE id = {context}.id)
5325                               WHERE id IN (SELECT id FROM {context_temp})";
5326          }
5327  
5328          $DB->execute($updatesql);
5329      }
5330  
5331     /**
5332      * Get a context instance as an object, from a given context id.
5333      *
5334      * @static
5335      * @param int $id context id
5336      * @param int $strictness IGNORE_MISSING means compatible mode, false returned if record not found, debug message if more found;
5337      *                        MUST_EXIST means throw exception if no record found
5338      * @return context|bool the context object or false if not found
5339      */
5340      public static function instance_by_id($id, $strictness = MUST_EXIST) {
5341          global $DB;
5342  
5343          if (get_called_class() !== 'context' and get_called_class() !== 'context_helper') {
5344              // some devs might confuse context->id and instanceid, better prevent these mistakes completely
5345              throw new coding_exception('use only context::instance_by_id() for real context levels use ::instance() methods');
5346          }
5347  
5348          if ($id == SYSCONTEXTID) {
5349              return context_system::instance(0, $strictness);
5350          }
5351  
5352          if (is_array($id) or is_object($id) or empty($id)) {
5353              throw new coding_exception('Invalid context id specified context::instance_by_id()');
5354          }
5355  
5356          if ($context = context::cache_get_by_id($id)) {
5357              return $context;
5358          }
5359  
5360          if ($record = $DB->get_record('context', array('id'=>$id), '*', $strictness)) {
5361              return context::create_instance_from_record($record);
5362          }
5363  
5364          return false;
5365      }
5366  
5367      /**
5368       * Update context info after moving context in the tree structure.
5369       *
5370       * @param context $newparent
5371       * @return void
5372       */
5373      public function update_moved(context $newparent) {
5374          global $DB;
5375  
5376          $frompath = $this->_path;
5377          $newpath  = $newparent->path . '/' . $this->_id;
5378  
5379          $trans = $DB->start_delegated_transaction();
5380  
5381          $this->mark_dirty();
5382  
5383          $setdepth = '';
5384          if (($newparent->depth +1) != $this->_depth) {
5385              $diff = $newparent->depth - $this->_depth + 1;
5386              $setdepth = ", depth = depth + $diff";
5387          }
5388          $sql = "UPDATE {context}
5389                     SET path = ?
5390                         $setdepth
5391                   WHERE id = ?";
5392          $params = array($newpath, $this->_id);
5393          $DB->execute($sql, $params);
5394  
5395          $this->_path  = $newpath;
5396          $this->_depth = $newparent->depth + 1;
5397  
5398          $sql = "UPDATE {context}
5399                     SET path = ".$DB->sql_concat("?", $DB->sql_substr("path", strlen($frompath)+1))."
5400                         $setdepth
5401                   WHERE path LIKE ?";
5402          $params = array($newpath, "{$frompath}/%");
5403          $DB->execute($sql, $params);
5404  
5405          $this->mark_dirty();
5406  
5407          context::reset_caches();
5408  
5409          $trans->allow_commit();
5410      }
5411  
5412      /**
5413       * Remove all context path info and optionally rebuild it.
5414       *
5415       * @param bool $rebuild
5416       * @return void
5417       */
5418      public function reset_paths($rebuild = true) {
5419          global $DB;
5420  
5421          if ($this->_path) {
5422              $this->mark_dirty();
5423          }
5424          $DB->set_field_select('context', 'depth', 0, "path LIKE '%/$this->_id/%'");
5425          $DB->set_field_select('context', 'path', NULL, "path LIKE '%/$this->_id/%'");
5426          if ($this->_contextlevel != CONTEXT_SYSTEM) {
5427              $DB->set_field('context', 'depth', 0, array('id'=>$this->_id));
5428              $DB->set_field('context', 'path', NULL, array('id'=>$this->_id));
5429              $this->_depth = 0;
5430              $this->_path = null;
5431          }
5432  
5433          if ($rebuild) {
5434              context_helper::build_all_paths(false);
5435          }
5436  
5437          context::reset_caches();
5438      }
5439  
5440      /**
5441       * Delete all data linked to content, do not delete the context record itself
5442       */
5443      public function delete_content() {
5444          global $CFG, $DB;
5445  
5446          blocks_delete_all_for_context($this->_id);
5447          filter_delete_all_for_context($this->_id);
5448  
5449          require_once($CFG->dirroot . '/comment/lib.php');
5450          comment::delete_comments(array('contextid'=>$this->_id));
5451  
5452          require_once($CFG->dirroot.'/rating/lib.php');
5453          $delopt = new stdclass();
5454          $delopt->contextid = $this->_id;
5455          $rm = new rating_manager();
5456          $rm->delete_ratings($delopt);
5457  
5458          // delete all files attached to this context
5459          $fs = get_file_storage();
5460          $fs->delete_area_files($this->_id);
5461  
5462          // Delete all repository instances attached to this context.
5463          require_once($CFG->dirroot . '/repository/lib.php');
5464          repository::delete_all_for_context($this->_id);
5465  
5466          // delete all advanced grading data attached to this context
5467          require_once($CFG->dirroot.'/grade/grading/lib.php');
5468          grading_manager::delete_all_for_context($this->_id);
5469  
5470          // now delete stuff from role related tables, role_unassign_all
5471          // and unenrol should be called earlier to do proper cleanup
5472          $DB->delete_records('role_assignments', array('contextid'=>$this->_id));
5473          $DB->delete_records('role_capabilities', array('contextid'=>$this->_id));
5474          $DB->delete_records('role_names', array('contextid'=>$this->_id));
5475      }
5476  
5477      /**
5478       * Delete the context content and the context record itself
5479       */
5480      public function delete() {
5481          global $DB;
5482  
5483          if ($this->_contextlevel <= CONTEXT_SYSTEM) {
5484              throw new coding_exception('Cannot delete system context');
5485          }
5486  
5487          // double check the context still exists
5488          if (!$DB->record_exists('context', array('id'=>$this->_id))) {
5489              context::cache_remove($this);
5490              return;
5491          }
5492  
5493          $this->delete_content();
5494          $DB->delete_records('context', array('id'=>$this->_id));
5495          // purge static context cache if entry present
5496          context::cache_remove($this);
5497  
5498          // do not mark dirty contexts if parents unknown
5499          if (!is_null($this->_path) and $this->_depth > 0) {
5500              $this->mark_dirty();
5501          }
5502      }
5503  
5504      // ====== context level related methods ======
5505  
5506      /**
5507       * Utility method for context creation
5508       *
5509       * @static
5510       * @param int $contextlevel
5511       * @param int $instanceid
5512       * @param string $parentpath
5513       * @return stdClass context record
5514       */
5515      protected static function insert_context_record($contextlevel, $instanceid, $parentpath) {
5516          global $DB;
5517  
5518          $record = new stdClass();
5519          $record->contextlevel = $contextlevel;
5520          $record->instanceid   = $instanceid;
5521          $record->depth        = 0;
5522          $record->path         = null; //not known before insert
5523  
5524          $record->id = $DB->insert_record('context', $record);
5525  
5526          // now add path if known - it can be added later
5527          if (!is_null($parentpath)) {
5528              $record->path = $parentpath.'/'.$record->id;
5529              $record->depth = substr_count($record->path, '/');
5530              $DB->update_record('context', $record);
5531          }
5532  
5533          return $record;
5534      }
5535  
5536      /**
5537       * Returns human readable context identifier.
5538       *
5539       * @param boolean $withprefix whether to prefix the name of the context with the
5540       *      type of context, e.g. User, Course, Forum, etc.
5541       * @param boolean $short whether to use the short name of the thing. Only applies
5542       *      to course contexts
5543       * @return string the human readable context name.
5544       */
5545      public function get_context_name($withprefix = true, $short = false) {
5546          // must be implemented in all context levels
5547          throw new coding_exception('can not get name of abstract context');
5548      }
5549  
5550      /**
5551       * Returns the most relevant URL for this context.
5552       *
5553       * @return moodle_url
5554       */
5555      public abstract function get_url();
5556  
5557      /**
5558       * Returns array of relevant context capability records.
5559       *
5560       * @return array
5561       */
5562      public abstract function get_capabilities();
5563  
5564      /**
5565       * Recursive function which, given a context, find all its children context ids.
5566       *
5567       * For course category contexts it will return immediate children and all subcategory contexts.
5568       * It will NOT recurse into courses or subcategories categories.
5569       * If you want to do that, call it on the returned courses/categories.
5570       *
5571       * When called for a course context, it will return the modules and blocks
5572       * displayed in the course page and blocks displayed on the module pages.
5573       *
5574       * If called on a user/course/module context it _will_ populate the cache with the appropriate
5575       * contexts ;-)
5576       *
5577       * @return array Array of child records
5578       */
5579      public function get_child_contexts() {
5580          global $DB;
5581  
5582          if (empty($this->_path) or empty($this->_depth)) {
5583              debugging('Can not find child contexts of context '.$this->_id.' try rebuilding of context paths');
5584              return array();
5585          }
5586  
5587          $sql = "SELECT ctx.*
5588                    FROM {context} ctx
5589                   WHERE ctx.path LIKE ?";
5590          $params = array($this->_path.'/%');
5591          $records = $DB->get_records_sql($sql, $params);
5592  
5593          $result = array();
5594          foreach ($records as $record) {
5595              $result[$record->id] = context::create_instance_from_record($record);
5596          }
5597  
5598          return $result;
5599      }
5600  
5601      /**
5602       * Returns parent contexts of this context in reversed order, i.e. parent first,
5603       * then grand parent, etc.
5604       *
5605       * @param bool $includeself tre means include self too
5606       * @return array of context instances
5607       */
5608      public function get_parent_contexts($includeself = false) {
5609          if (!$contextids = $this->get_parent_context_ids($includeself)) {
5610              return array();
5611          }
5612  
5613          $result = array();
5614          foreach ($contextids as $contextid) {
5615              $parent = context::instance_by_id($contextid, MUST_EXIST);
5616              $result[$parent->id] = $parent;
5617          }
5618  
5619          return $result;
5620      }
5621  
5622      /**
5623       * Returns parent contexts of this context in reversed order, i.e. parent first,
5624       * then grand parent, etc.
5625       *
5626       * @param bool $includeself tre means include self too
5627       * @return array of context ids
5628       */
5629      public function get_parent_context_ids($includeself = false) {
5630          if (empty($this->_path)) {
5631              return array();
5632          }
5633  
5634          $parentcontexts = trim($this->_path, '/'); // kill leading slash
5635          $parentcontexts = explode('/', $parentcontexts);
5636          if (!$includeself) {
5637              array_pop($parentcontexts); // and remove its own id
5638          }
5639  
5640          return array_reverse($parentcontexts);
5641      }
5642  
5643      /**
5644       * Returns parent context
5645       *
5646       * @return context
5647       */
5648      public function get_parent_context() {
5649          if (empty($this->_path) or $this->_id == SYSCONTEXTID) {
5650              return false;
5651          }
5652  
5653          $parentcontexts = trim($this->_path, '/'); // kill leading slash
5654          $parentcontexts = explode('/', $parentcontexts);
5655          array_pop($parentcontexts); // self
5656          $contextid = array_pop($parentcontexts); // immediate parent
5657  
5658          return context::instance_by_id($contextid, MUST_EXIST);
5659      }
5660  
5661      /**
5662       * Is this context part of any course? If yes return course context.
5663       *
5664       * @param bool $strict true means throw exception if not found, false means return false if not found
5665       * @return context_course context of the enclosing course, null if not found or exception
5666       */
5667      public function get_course_context($strict = true) {
5668          if ($strict) {
5669              throw new coding_exception('Context does not belong to any course.');
5670          } else {
5671              return false;
5672          }
5673      }
5674  
5675      /**
5676       * Returns sql necessary for purging of stale context instances.
5677       *
5678       * @static
5679       * @return string cleanup SQL
5680       */
5681      protected static function get_cleanup_sql() {
5682          throw new coding_exception('get_cleanup_sql() method must be implemented in all context levels');
5683      }
5684  
5685      /**
5686       * Rebuild context paths and depths at context level.
5687       *
5688       * @static
5689       * @param bool $force
5690       * @return void
5691       */
5692      protected static function build_paths($force) {
5693          throw new coding_exception('build_paths() method must be implemented in all context levels');
5694      }
5695  
5696      /**
5697       * Create missing context instances at given level
5698       *
5699       * @static
5700       * @return void
5701       */
5702      protected static function create_level_instances() {
5703          throw new coding_exception('create_level_instances() method must be implemented in all context levels');
5704      }
5705  
5706      /**
5707       * Reset all cached permissions and definitions if the necessary.
5708       * @return void
5709       */
5710      public function reload_if_dirty() {
5711          global $ACCESSLIB_PRIVATE, $USER;
5712  
5713          // Load dirty contexts list if needed
5714          if (CLI_SCRIPT) {
5715              if (!isset($ACCESSLIB_PRIVATE->dirtycontexts)) {
5716                  // we do not load dirty flags in CLI and cron
5717                  $ACCESSLIB_PRIVATE->dirtycontexts = array();
5718              }
5719          } else {
5720              if (!isset($ACCESSLIB_PRIVATE->dirtycontexts)) {
5721                  if (!isset($USER->access['time'])) {
5722                      // nothing was loaded yet, we do not need to check dirty contexts now
5723                      return;
5724                  }
5725                  // no idea why -2 is there, server cluster time difference maybe... (skodak)
5726                  $ACCESSLIB_PRIVATE->dirtycontexts = get_cache_flags('accesslib/dirtycontexts', $USER->access['time']-2);
5727              }
5728          }
5729  
5730          foreach ($ACCESSLIB_PRIVATE->dirtycontexts as $path=>$unused) {
5731              if ($path === $this->_path or strpos($this->_path, $path.'/') === 0) {
5732                  // reload all capabilities of USER and others - preserving loginas, roleswitches, etc
5733                  // and then cleanup any marks of dirtyness... at least from our short term memory! :-)
5734                  reload_all_capabilities();
5735                  break;
5736              }
5737          }
5738      }
5739  
5740      /**
5741       * Mark a context as dirty (with timestamp) so as to force reloading of the context.
5742       */
5743      public function mark_dirty() {
5744          global $CFG, $USER, $ACCESSLIB_PRIVATE;
5745  
5746          if (during_initial_install()) {
5747              return;
5748          }
5749  
5750          // only if it is a non-empty string
5751          if (is_string($this->_path) && $this->_path !== '') {
5752              set_cache_flag('accesslib/dirtycontexts', $this->_path, 1, time()+$CFG->sessiontimeout);
5753              if (isset($ACCESSLIB_PRIVATE->dirtycontexts)) {
5754                  $ACCESSLIB_PRIVATE->dirtycontexts[$this->_path] = 1;
5755              } else {
5756                  if (CLI_SCRIPT) {
5757                      $ACCESSLIB_PRIVATE->dirtycontexts = array($this->_path => 1);
5758                  } else {
5759                      if (isset($USER->access['time'])) {
5760                          $ACCESSLIB_PRIVATE->dirtycontexts = get_cache_flags('accesslib/dirtycontexts', $USER->access['time']-2);
5761                      } else {
5762                          $ACCESSLIB_PRIVATE->dirtycontexts = array($this->_path => 1);
5763                      }
5764                      // flags not loaded yet, it will be done later in $context->reload_if_dirty()
5765                  }
5766              }
5767          }
5768      }
5769  }
5770  
5771  
5772  /**
5773   * Context maintenance and helper methods.
5774   *
5775   * This is "extends context" is a bloody hack that tires to work around the deficiencies
5776   * in the "protected" keyword in PHP, this helps us to hide all the internals of context
5777   * level implementation from the rest of code, the code completion returns what developers need.
5778   *
5779   * Thank you Tim Hunt for helping me with this nasty trick.
5780   *
5781   * @package   core_access
5782   * @category  access
5783   * @copyright Petr Skoda {@link http://skodak.org}
5784   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
5785   * @since     Moodle 2.2
5786   */
5787  class context_helper extends context {
5788  
5789      /**
5790       * @var array An array mapping context levels to classes
5791       */
5792      private static $alllevels;
5793  
5794      /**
5795       * Instance does not make sense here, only static use
5796       */
5797      protected function __construct() {
5798      }
5799  
5800      /**
5801       * Reset internal context levels array.
5802       */
5803      public static function reset_levels() {
5804          self::$alllevels = null;
5805      }
5806  
5807      /**
5808       * Initialise context levels, call before using self::$alllevels.
5809       */
5810      private static function init_levels() {
5811          global $CFG;
5812  
5813          if (isset(self::$alllevels)) {
5814              return;
5815          }
5816          self::$alllevels = array(
5817              CONTEXT_SYSTEM    => 'context_system',
5818              CONTEXT_USER      => 'context_user',
5819              CONTEXT_COURSECAT => 'context_coursecat',
5820              CONTEXT_COURSE    => 'context_course',
5821              CONTEXT_MODULE    => 'context_module',
5822              CONTEXT_BLOCK     => 'context_block',
5823          );
5824  
5825          if (empty($CFG->custom_context_classes)) {
5826              return;
5827          }
5828  
5829          $levels = $CFG->custom_context_classes;
5830          if (!is_array($levels)) {
5831              $levels = @unserialize($levels);
5832          }
5833          if (!is_array($levels)) {
5834              debugging('Invalid $CFG->custom_context_classes detected, value ignored.', DEBUG_DEVELOPER);
5835              return;
5836          }
5837  
5838          // Unsupported custom levels, use with care!!!
5839          foreach ($levels as $level => $classname) {
5840              self::$alllevels[$level] = $classname;
5841          }
5842          ksort(self::$alllevels);
5843      }
5844  
5845      /**
5846       * Returns a class name of the context level class
5847       *
5848       * @static
5849       * @param int $contextlevel (CONTEXT_SYSTEM, etc.)
5850       * @return string class name of the context class
5851       */
5852      public static function get_class_for_level($contextlevel) {
5853          self::init_levels();
5854          if (isset(self::$alllevels[$contextlevel])) {
5855              return self::$alllevels[$contextlevel];
5856          } else {
5857              throw new coding_exception('Invalid context level specified');
5858          }
5859      }
5860  
5861      /**
5862       * Returns a list of all context levels
5863       *
5864       * @static
5865       * @return array int=>string (level=>level class name)
5866       */
5867      public static function get_all_levels() {
5868          self::init_levels();
5869          return self::$alllevels;
5870      }
5871  
5872      /**
5873       * Remove stale contexts that belonged to deleted instances.
5874       * Ideally all code should cleanup contexts properly, unfortunately accidents happen...
5875       *
5876       * @static
5877       * @return void
5878       */
5879      public static function cleanup_instances() {
5880          global $DB;
5881          self::init_levels();
5882  
5883          $sqls = array();
5884          foreach (self::$alllevels as $level=>$classname) {
5885              $sqls[] = $classname::get_cleanup_sql();
5886          }
5887  
5888          $sql = implode(" UNION ", $sqls);
5889  
5890          // it is probably better to use transactions, it might be faster too
5891          $transaction = $DB->start_delegated_transaction();
5892  
5893          $rs = $DB->get_recordset_sql($sql);
5894          foreach ($rs as $record) {
5895              $context = context::create_instance_from_record($record);
5896              $context->delete();
5897          }
5898          $rs->close();
5899  
5900          $transaction->allow_commit();
5901      }
5902  
5903      /**
5904       * Create all context instances at the given level and above.
5905       *
5906       * @static
5907       * @param int $contextlevel null means all levels
5908       * @param bool $buildpaths
5909       * @return void
5910       */
5911      public static function create_instances($contextlevel = null, $buildpaths = true) {
5912          self::init_levels();
5913          foreach (self::$alllevels as $level=>$classname) {
5914              if ($contextlevel and $level > $contextlevel) {
5915                  // skip potential sub-contexts
5916                  continue;
5917              }
5918              $classname::create_level_instances();
5919              if ($buildpaths) {
5920                  $classname::build_paths(false);
5921              }
5922          }
5923      }
5924  
5925      /**
5926       * Rebuild paths and depths in all context levels.
5927       *
5928       * @static
5929       * @param bool $force false means add missing only
5930       * @return void
5931       */
5932      public static function build_all_paths($force = false) {
5933          self::init_levels();
5934          foreach (self::$alllevels as $classname) {
5935              $classname::build_paths($force);
5936          }
5937  
5938          // reset static course cache - it might have incorrect cached data
5939          accesslib_clear_all_caches(true);
5940      }
5941  
5942      /**
5943       * Resets the cache to remove all data.
5944       * @static
5945       */
5946      public static function reset_caches() {
5947          context::reset_caches();
5948      }
5949  
5950      /**
5951       * Returns all fields necessary for context preloading from user $rec.
5952       *
5953       * This helps with performance when dealing with hundreds of contexts.
5954       *
5955       * @static
5956       * @param string $tablealias context table alias in the query
5957       * @return array (table.column=>alias, ...)
5958       */
5959      public static function get_preload_record_columns($tablealias) {
5960          return array("$tablealias.id"=>"ctxid", "$tablealias.path"=>"ctxpath", "$tablealias.depth"=>"ctxdepth", "$tablealias.contextlevel"=>"ctxlevel", "$tablealias.instanceid"=>"ctxinstance");
5961      }
5962  
5963      /**
5964       * Returns all fields necessary for context preloading from user $rec.
5965       *
5966       * This helps with performance when dealing with hundreds of contexts.
5967       *
5968       * @static
5969       * @param string $tablealias context table alias in the query
5970       * @return string
5971       */
5972      public static function get_preload_record_columns_sql($tablealias) {
5973          return "$tablealias.id AS ctxid, $tablealias.path AS ctxpath, $tablealias.depth AS ctxdepth, $tablealias.contextlevel AS ctxlevel, $tablealias.instanceid AS ctxinstance";
5974      }
5975  
5976      /**
5977       * Preloads context information from db record and strips the cached info.
5978       *
5979       * The db request has to contain all columns from context_helper::get_preload_record_columns().
5980       *
5981       * @static
5982       * @param stdClass $rec
5983       * @return void (modifies $rec)
5984       */
5985       public static function preload_from_record(stdClass $rec) {
5986           context::preload_from_record($rec);
5987       }
5988  
5989      /**
5990       * Preload all contexts instances from course.
5991       *
5992       * To be used if you expect multiple queries for course activities...
5993       *
5994       * @static
5995       * @param int $courseid
5996       */
5997      public static function preload_course($courseid) {
5998          // Users can call this multiple times without doing any harm
5999          if (isset(context::$cache_preloaded[$courseid])) {
6000              return;
6001          }
6002          $coursecontext = context_course::instance($courseid);
6003          $coursecontext->get_child_contexts();
6004  
6005          context::$cache_preloaded[$courseid] = true;
6006      }
6007  
6008      /**
6009       * Delete context instance
6010       *
6011       * @static
6012       * @param int $contextlevel
6013       * @param int $instanceid
6014       * @return void
6015       */
6016      public static function delete_instance($contextlevel, $instanceid) {
6017          global $DB;
6018  
6019          // double check the context still exists
6020          if ($record = $DB->get_record('context', array('contextlevel'=>$contextlevel, 'instanceid'=>$instanceid))) {
6021              $context = context::create_instance_from_record($record);
6022              $context->delete();
6023          } else {
6024              // we should try to purge the cache anyway
6025          }
6026      }
6027  
6028      /**
6029       * Returns the name of specified context level
6030       *
6031       * @static
6032       * @param int $contextlevel
6033       * @return string name of the context level
6034       */
6035      public static function get_level_name($contextlevel) {
6036          $classname = context_helper::get_class_for_level($contextlevel);
6037          return $classname::get_level_name();
6038      }
6039  
6040      /**
6041       * not used
6042       */
6043      public function get_url() {
6044      }
6045  
6046      /**
6047       * not used
6048       */
6049      public function get_capabilities() {
6050      }
6051  }
6052  
6053  
6054  /**
6055   * System context class
6056   *
6057   * @package   core_access
6058   * @category  access
6059   * @copyright Petr Skoda {@link http://skodak.org}
6060   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6061   * @since     Moodle 2.2
6062   */
6063  class context_system extends context {
6064      /**
6065       * Please use context_system::instance() if you need the instance of context.
6066       *
6067       * @param stdClass $record
6068       */
6069      protected function __construct(stdClass $record) {
6070          parent::__construct($record);
6071          if ($record->contextlevel != CONTEXT_SYSTEM) {
6072              throw new coding_exception('Invalid $record->contextlevel in context_system constructor.');
6073          }
6074      }
6075  
6076      /**
6077       * Returns human readable context level name.
6078       *
6079       * @static
6080       * @return string the human readable context level name.
6081       */
6082      public static function get_level_name() {
6083          return get_string('coresystem');
6084      }
6085  
6086      /**
6087       * Returns human readable context identifier.
6088       *
6089       * @param boolean $withprefix does not apply to system context
6090       * @param boolean $short does not apply to system context
6091       * @return string the human readable context name.
6092       */
6093      public function get_context_name($withprefix = true, $short = false) {
6094          return self::get_level_name();
6095      }
6096  
6097      /**
6098       * Returns the most relevant URL for this context.
6099       *
6100       * @return moodle_url
6101       */
6102      public function get_url() {
6103          return new moodle_url('/');
6104      }
6105  
6106      /**
6107       * Returns array of relevant context capability records.
6108       *
6109       * @return array
6110       */
6111      public function get_capabilities() {
6112          global $DB;
6113  
6114          $sort = 'ORDER BY contextlevel,component,name';   // To group them sensibly for display
6115  
6116          $params = array();
6117          $sql = "SELECT *
6118                    FROM {capabilities}";
6119  
6120          return $DB->get_records_sql($sql.' '.$sort, $params);
6121      }
6122  
6123      /**
6124       * Create missing context instances at system context
6125       * @static
6126       */
6127      protected static function create_level_instances() {
6128          // nothing to do here, the system context is created automatically in installer
6129          self::instance(0);
6130      }
6131  
6132      /**
6133       * Returns system context instance.
6134       *
6135       * @static
6136       * @param int $instanceid
6137       * @param int $strictness
6138       * @param bool $cache
6139       * @return context_system context instance
6140       */
6141      public static function instance($instanceid = 0, $strictness = MUST_EXIST, $cache = true) {
6142          global $DB;
6143  
6144          if ($instanceid != 0) {
6145              debugging('context_system::instance(): invalid $id parameter detected, should be 0');
6146          }
6147  
6148          if (defined('SYSCONTEXTID') and $cache) { // dangerous: define this in config.php to eliminate 1 query/page
6149              if (!isset(context::$systemcontext)) {
6150                  $record = new stdClass();
6151                  $record->id           = SYSCONTEXTID;
6152                  $record->contextlevel = CONTEXT_SYSTEM;
6153                  $record->instanceid   = 0;
6154                  $record->path         = '/'.SYSCONTEXTID;
6155                  $record->depth        = 1;
6156                  context::$systemcontext = new context_system($record);
6157              }
6158              return context::$systemcontext;
6159          }
6160  
6161  
6162          try {
6163              // We ignore the strictness completely because system context must exist except during install.
6164              $record = $DB->get_record('context', array('contextlevel'=>CONTEXT_SYSTEM), '*', MUST_EXIST);
6165          } catch (dml_exception $e) {
6166              //table or record does not exist
6167              if (!during_initial_install()) {
6168                  // do not mess with system context after install, it simply must exist
6169                  throw $e;
6170              }
6171              $record = null;
6172          }
6173  
6174          if (!$record) {
6175              $record = new stdClass();
6176              $record->contextlevel = CONTEXT_SYSTEM;
6177              $record->instanceid   = 0;
6178              $record->depth        = 1;
6179              $record->path         = null; //not known before insert
6180  
6181              try {
6182                  if ($DB->count_records('context')) {
6183                      // contexts already exist, this is very weird, system must be first!!!
6184                      return null;
6185                  }
6186                  if (defined('SYSCONTEXTID')) {
6187                      // this would happen only in unittest on sites that went through weird 1.7 upgrade
6188                      $record->id = SYSCONTEXTID;
6189                      $DB->import_record('context', $record);
6190                      $DB->get_manager()->reset_sequence('context');
6191                  } else {
6192                      $record->id = $DB->insert_record('context', $record);
6193                  }
6194              } catch (dml_exception $e) {
6195                  // can not create context - table does not exist yet, sorry
6196                  return null;
6197              }
6198          }
6199  
6200          if ($record->instanceid != 0) {
6201              // this is very weird, somebody must be messing with context table
6202              debugging('Invalid system context detected');
6203          }
6204  
6205          if ($record->depth != 1 or $record->path != '/'.$record->id) {
6206              // fix path if necessary, initial install or path reset
6207              $record->depth = 1;
6208              $record->path  = '/'.$record->id;
6209              $DB->update_record('context', $record);
6210          }
6211  
6212          if (!defined('SYSCONTEXTID')) {
6213              define('SYSCONTEXTID', $record->id);
6214          }
6215  
6216          context::$systemcontext = new context_system($record);
6217          return context::$systemcontext;
6218      }
6219  
6220      /**
6221       * Returns all site contexts except the system context, DO NOT call on production servers!!
6222       *
6223       * Contexts are not cached.
6224       *
6225       * @return array
6226       */
6227      public function get_child_contexts() {
6228          global $DB;
6229  
6230          debugging('Fetching of system context child courses is strongly discouraged on production servers (it may eat all available memory)!');
6231  
6232          // Just get all the contexts except for CONTEXT_SYSTEM level
6233          // and hope we don't OOM in the process - don't cache
6234          $sql = "SELECT c.*
6235                    FROM {context} c
6236                   WHERE contextlevel > ".CONTEXT_SYSTEM;
6237          $records = $DB->get_records_sql($sql);
6238  
6239          $result = array();
6240          foreach ($records as $record) {
6241              $result[$record->id] = context::create_instance_from_record($record);
6242          }
6243  
6244          return $result;
6245      }
6246  
6247      /**
6248       * Returns sql necessary for purging of stale context instances.
6249       *
6250       * @static
6251       * @return string cleanup SQL
6252       */
6253      protected static function get_cleanup_sql() {
6254          $sql = "
6255                    SELECT c.*
6256                      FROM {context} c
6257                     WHERE 1=2
6258                 ";
6259  
6260          return $sql;
6261      }
6262  
6263      /**
6264       * Rebuild context paths and depths at system context level.
6265       *
6266       * @static
6267       * @param bool $force
6268       */
6269      protected static function build_paths($force) {
6270          global $DB;
6271  
6272          /* note: ignore $force here, we always do full test of system context */
6273  
6274          // exactly one record must exist
6275          $record = $DB->get_record('context', array('contextlevel'=>CONTEXT_SYSTEM), '*', MUST_EXIST);
6276  
6277          if ($record->instanceid != 0) {
6278              debugging('Invalid system context detected');
6279          }
6280  
6281          if (defined('SYSCONTEXTID') and $record->id != SYSCONTEXTID) {
6282              debugging('Invalid SYSCONTEXTID detected');
6283          }
6284  
6285          if ($record->depth != 1 or $record->path != '/'.$record->id) {
6286              // fix path if necessary, initial install or path reset
6287              $record->depth    = 1;
6288              $record->path     = '/'.$record->id;
6289              $DB->update_record('context', $record);
6290          }
6291      }
6292  }
6293  
6294  
6295  /**
6296   * User context class
6297   *
6298   * @package   core_access
6299   * @category  access
6300   * @copyright Petr Skoda {@link http://skodak.org}
6301   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6302   * @since     Moodle 2.2
6303   */
6304  class context_user extends context {
6305      /**
6306       * Please use context_user::instance($userid) if you need the instance of context.
6307       * Alternatively if you know only the context id use context::instance_by_id($contextid)
6308       *
6309       * @param stdClass $record
6310       */
6311      protected function __construct(stdClass $record) {
6312          parent::__construct($record);
6313          if ($record->contextlevel != CONTEXT_USER) {
6314              throw new coding_exception('Invalid $record->contextlevel in context_user constructor.');
6315          }
6316      }
6317  
6318      /**
6319       * Returns human readable context level name.
6320       *
6321       * @static
6322       * @return string the human readable context level name.
6323       */
6324      public static function get_level_name() {
6325          return get_string('user');
6326      }
6327  
6328      /**
6329       * Returns human readable context identifier.
6330       *
6331       * @param boolean $withprefix whether to prefix the name of the context with User
6332       * @param boolean $short does not apply to user context
6333       * @return string the human readable context name.
6334       */
6335      public function get_context_name($withprefix = true, $short = false) {
6336          global $DB;
6337  
6338          $name = '';
6339          if ($user = $DB->get_record('user', array('id'=>$this->_instanceid, 'deleted'=>0))) {
6340              if ($withprefix){
6341                  $name = get_string('user').': ';
6342              }
6343              $name .= fullname($user);
6344          }
6345          return $name;
6346      }
6347  
6348      /**
6349       * Returns the most relevant URL for this context.
6350       *
6351       * @return moodle_url
6352       */
6353      public function get_url() {
6354          global $COURSE;
6355  
6356          if ($COURSE->id == SITEID) {
6357              $url = new moodle_url('/user/profile.php', array('id'=>$this->_instanceid));
6358          } else {
6359              $url = new moodle_url('/user/view.php', array('id'=>$this->_instanceid, 'courseid'=>$COURSE->id));
6360          }
6361          return $url;
6362      }
6363  
6364      /**
6365       * Returns array of relevant context capability records.
6366       *
6367       * @return array
6368       */
6369      public function get_capabilities() {
6370          global $DB;
6371  
6372          $sort = 'ORDER BY contextlevel,component,name';   // To group them sensibly for display
6373  
6374          $extracaps = array('moodle/grade:viewall');
6375          list($extra, $params) = $DB->get_in_or_equal($extracaps, SQL_PARAMS_NAMED, 'cap');
6376          $sql = "SELECT *
6377                    FROM {capabilities}
6378                   WHERE contextlevel = ".CONTEXT_USER."
6379                         OR name $extra";
6380  
6381          return $records = $DB->get_records_sql($sql.' '.$sort, $params);
6382      }
6383  
6384      /**
6385       * Returns user context instance.
6386       *
6387       * @static
6388       * @param int $instanceid
6389       * @param int $strictness
6390       * @return context_user context instance
6391       */
6392      public static function instance($instanceid, $strictness = MUST_EXIST) {
6393          global $DB;
6394  
6395          if ($context = context::cache_get(CONTEXT_USER, $instanceid)) {
6396              return $context;
6397          }
6398  
6399          if (!$record = $DB->get_record('context', array('contextlevel'=>CONTEXT_USER, 'instanceid'=>$instanceid))) {
6400              if ($user = $DB->get_record('user', array('id'=>$instanceid, 'deleted'=>0), 'id', $strictness)) {
6401                  $record = context::insert_context_record(CONTEXT_USER, $user->id, '/'.SYSCONTEXTID, 0);
6402              }
6403          }
6404  
6405          if ($record) {
6406              $context = new context_user($record);
6407              context::cache_add($context);
6408              return $context;
6409          }
6410  
6411          return false;
6412      }
6413  
6414      /**
6415       * Create missing context instances at user context level
6416       * @static
6417       */
6418      protected static function create_level_instances() {
6419          global $DB;
6420  
6421          $sql = "INSERT INTO {context} (contextlevel, instanceid)
6422                  SELECT ".CONTEXT_USER.", u.id
6423                    FROM {user} u
6424                   WHERE u.deleted = 0
6425                         AND NOT EXISTS (SELECT 'x'
6426                                           FROM {context} cx
6427                                          WHERE u.id = cx.instanceid AND cx.contextlevel=".CONTEXT_USER.")";
6428          $DB->execute($sql);
6429      }
6430  
6431      /**
6432       * Returns sql necessary for purging of stale context instances.
6433       *
6434       * @static
6435       * @return string cleanup SQL
6436       */
6437      protected static function get_cleanup_sql() {
6438          $sql = "
6439                    SELECT c.*
6440                      FROM {context} c
6441           LEFT OUTER JOIN {user} u ON (c.instanceid = u.id AND u.deleted = 0)
6442                     WHERE u.id IS NULL AND c.contextlevel = ".CONTEXT_USER."
6443                 ";
6444  
6445          return $sql;
6446      }
6447  
6448      /**
6449       * Rebuild context paths and depths at user context level.
6450       *
6451       * @static
6452       * @param bool $force
6453       */
6454      protected static function build_paths($force) {
6455          global $DB;
6456  
6457          // First update normal users.
6458          $path = $DB->sql_concat('?', 'id');
6459          $pathstart = '/' . SYSCONTEXTID . '/';
6460          $params = array($pathstart);
6461  
6462          if ($force) {
6463              $where = "depth <> 2 OR path IS NULL OR path <> ({$path})";
6464              $params[] = $pathstart;
6465          } else {
6466              $where = "depth = 0 OR path IS NULL";
6467          }
6468  
6469          $sql = "UPDATE {context}
6470                     SET depth = 2,
6471                         path = {$path}
6472                   WHERE contextlevel = " . CONTEXT_USER . "
6473                     AND ($where)";
6474          $DB->execute($sql, $params);
6475      }
6476  }
6477  
6478  
6479  /**
6480   * Course category context class
6481   *
6482   * @package   core_access
6483   * @category  access
6484   * @copyright Petr Skoda {@link http://skodak.org}
6485   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6486   * @since     Moodle 2.2
6487   */
6488  class context_coursecat extends context {
6489      /**
6490       * Please use context_coursecat::instance($coursecatid) if you need the instance of context.
6491       * Alternatively if you know only the context id use context::instance_by_id($contextid)
6492       *
6493       * @param stdClass $record
6494       */
6495      protected function __construct(stdClass $record) {
6496          parent::__construct($record);
6497          if ($record->contextlevel != CONTEXT_COURSECAT) {
6498              throw new coding_exception('Invalid $record->contextlevel in context_coursecat constructor.');
6499          }
6500      }
6501  
6502      /**
6503       * Returns human readable context level name.
6504       *
6505       * @static
6506       * @return string the human readable context level name.
6507       */
6508      public static function get_level_name() {
6509          return get_string('category');
6510      }
6511  
6512      /**
6513       * Returns human readable context identifier.
6514       *
6515       * @param boolean $withprefix whether to prefix the name of the context with Category
6516       * @param boolean $short does not apply to course categories
6517       * @return string the human readable context name.
6518       */
6519      public function get_context_name($withprefix = true, $short = false) {
6520          global $DB;
6521  
6522          $name = '';
6523          if ($category = $DB->get_record('course_categories', array('id'=>$this->_instanceid))) {
6524              if ($withprefix){
6525                  $name = get_string('category').': ';
6526              }
6527              $name .= format_string($category->name, true, array('context' => $this));
6528          }
6529          return $name;
6530      }
6531  
6532      /**
6533       * Returns the most relevant URL for this context.
6534       *
6535       * @return moodle_url
6536       */
6537      public function get_url() {
6538          return new moodle_url('/course/index.php', array('categoryid' => $this->_instanceid));
6539      }
6540  
6541      /**
6542       * Returns array of relevant context capability records.
6543       *
6544       * @return array
6545       */
6546      public function get_capabilities() {
6547          global $DB;
6548  
6549          $sort = 'ORDER BY contextlevel,component,name';   // To group them sensibly for display
6550  
6551          $params = array();
6552          $sql = "SELECT *
6553                    FROM {capabilities}
6554                   WHERE contextlevel IN (".CONTEXT_COURSECAT.",".CONTEXT_COURSE.",".CONTEXT_MODULE.",".CONTEXT_BLOCK.")";
6555  
6556          return $DB->get_records_sql($sql.' '.$sort, $params);
6557      }
6558  
6559      /**
6560       * Returns course category context instance.
6561       *
6562       * @static
6563       * @param int $instanceid
6564       * @param int $strictness
6565       * @return context_coursecat context instance
6566       */
6567      public static function instance($instanceid, $strictness = MUST_EXIST) {
6568          global $DB;
6569  
6570          if ($context = context::cache_get(CONTEXT_COURSECAT, $instanceid)) {
6571              return $context;
6572          }
6573  
6574          if (!$record = $DB->get_record('context', array('contextlevel'=>CONTEXT_COURSECAT, 'instanceid'=>$instanceid))) {
6575              if ($category = $DB->get_record('course_categories', array('id'=>$instanceid), 'id,parent', $strictness)) {
6576                  if ($category->parent) {
6577                      $parentcontext = context_coursecat::instance($category->parent);
6578                      $record = context::insert_context_record(CONTEXT_COURSECAT, $category->id, $parentcontext->path);
6579                  } else {
6580                      $record = context::insert_context_record(CONTEXT_COURSECAT, $category->id, '/'.SYSCONTEXTID, 0);
6581                  }
6582              }
6583          }
6584  
6585          if ($record) {
6586              $context = new context_coursecat($record);
6587              context::cache_add($context);
6588              return $context;
6589          }
6590  
6591          return false;
6592      }
6593  
6594      /**
6595       * Returns immediate child contexts of category and all subcategories,
6596       * children of subcategories and courses are not returned.
6597       *
6598       * @return array
6599       */
6600      public function get_child_contexts() {
6601          global $DB;
6602  
6603          if (empty($this->_path) or empty($this->_depth)) {
6604              debugging('Can not find child contexts of context '.$this->_id.' try rebuilding of context paths');
6605              return array();
6606          }
6607  
6608          $sql = "SELECT ctx.*
6609                    FROM {context} ctx
6610                   WHERE ctx.path LIKE ? AND (ctx.depth = ? OR ctx.contextlevel = ?)";
6611          $params = array($this->_path.'/%', $this->depth+1, CONTEXT_COURSECAT);
6612          $records = $DB->get_records_sql($sql, $params);
6613  
6614          $result = array();
6615          foreach ($records as $record) {
6616              $result[$record->id] = context::create_instance_from_record($record);
6617          }
6618  
6619          return $result;
6620      }
6621  
6622      /**
6623       * Create missing context instances at course category context level
6624       * @static
6625       */
6626      protected static function create_level_instances() {
6627          global $DB;
6628  
6629          $sql = "INSERT INTO {context} (contextlevel, instanceid)
6630                  SELECT ".CONTEXT_COURSECAT.", cc.id
6631                    FROM {course_categories} cc
6632                   WHERE NOT EXISTS (SELECT 'x'
6633                                       FROM {context} cx
6634                                      WHERE cc.id = cx.instanceid AND cx.contextlevel=".CONTEXT_COURSECAT.")";
6635          $DB->execute($sql);
6636      }
6637  
6638      /**
6639       * Returns sql necessary for purging of stale context instances.
6640       *
6641       * @static
6642       * @return string cleanup SQL
6643       */
6644      protected static function get_cleanup_sql() {
6645          $sql = "
6646                    SELECT c.*
6647                      FROM {context} c
6648           LEFT OUTER JOIN {course_categories} cc ON c.instanceid = cc.id
6649                     WHERE cc.id IS NULL AND c.contextlevel = ".CONTEXT_COURSECAT."
6650                 ";
6651  
6652          return $sql;
6653      }
6654  
6655      /**
6656       * Rebuild context paths and depths at course category context level.
6657       *
6658       * @static
6659       * @param bool $force
6660       */
6661      protected static function build_paths($force) {
6662          global $DB;
6663  
6664          if ($force or $DB->record_exists_select('context', "contextlevel = ".CONTEXT_COURSECAT." AND (depth = 0 OR path IS NULL)")) {
6665              if ($force) {
6666                  $ctxemptyclause = $emptyclause = '';
6667              } else {
6668                  $ctxemptyclause = "AND (ctx.path IS NULL OR ctx.depth = 0)";
6669                  $emptyclause    = "AND ({context}.path IS NULL OR {context}.depth = 0)";
6670              }
6671  
6672              $base = '/'.SYSCONTEXTID;
6673  
6674              // Normal top level categories
6675              $sql = "UPDATE {context}
6676                         SET depth=2,
6677                             path=".$DB->sql_concat("'$base/'", 'id')."
6678                       WHERE contextlevel=".CONTEXT_COURSECAT."
6679                             AND EXISTS (SELECT 'x'
6680                                           FROM {course_categories} cc
6681                                          WHERE cc.id = {context}.instanceid AND cc.depth=1)
6682                             $emptyclause";
6683              $DB->execute($sql);
6684  
6685              // Deeper categories - one query per depthlevel
6686              $maxdepth = $DB->get_field_sql("SELECT MAX(depth) FROM {course_categories}");
6687              for ($n=2; $n<=$maxdepth; $n++) {
6688                  $sql = "INSERT INTO {context_temp} (id, path, depth)
6689                          SELECT ctx.id, ".$DB->sql_concat('pctx.path', "'/'", 'ctx.id').", pctx.depth+1
6690                            FROM {context} ctx
6691                            JOIN {course_categories} cc ON (cc.id = ctx.instanceid AND ctx.contextlevel = ".CONTEXT_COURSECAT." AND cc.depth = $n)
6692                            JOIN {context} pctx ON (pctx.instanceid = cc.parent AND pctx.contextlevel = ".CONTEXT_COURSECAT.")
6693                           WHERE pctx.path IS NOT NULL AND pctx.depth > 0
6694                                 $ctxemptyclause";
6695                  $trans = $DB->start_delegated_transaction();
6696                  $DB->delete_records('context_temp');
6697                  $DB->execute($sql);
6698                  context::merge_context_temp_table();
6699                  $DB->delete_records('context_temp');
6700                  $trans->allow_commit();
6701  
6702              }
6703          }
6704      }
6705  }
6706  
6707  
6708  /**
6709   * Course context class
6710   *
6711   * @package   core_access
6712   * @category  access
6713   * @copyright Petr Skoda {@link http://skodak.org}
6714   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6715   * @since     Moodle 2.2
6716   */
6717  class context_course extends context {
6718      /**
6719       * Please use context_course::instance($courseid) if you need the instance of context.
6720       * Alternatively if you know only the context id use context::instance_by_id($contextid)
6721       *
6722       * @param stdClass $record
6723       */
6724      protected function __construct(stdClass $record) {
6725          parent::__construct($record);
6726          if ($record->contextlevel != CONTEXT_COURSE) {
6727              throw new coding_exception('Invalid $record->contextlevel in context_course constructor.');
6728          }
6729      }
6730  
6731      /**
6732       * Returns human readable context level name.
6733       *
6734       * @static
6735       * @return string the human readable context level name.
6736       */
6737      public static function get_level_name() {
6738          return get_string('course');
6739      }
6740  
6741      /**
6742       * Returns human readable context identifier.
6743       *
6744       * @param boolean $withprefix whether to prefix the name of the context with Course
6745       * @param boolean $short whether to use the short name of the thing.
6746       * @return string the human readable context name.
6747       */
6748      public function get_context_name($withprefix = true, $short = false) {
6749          global $DB;
6750  
6751          $name = '';
6752          if ($this->_instanceid == SITEID) {
6753              $name = get_string('frontpage', 'admin');
6754          } else {
6755              if ($course = $DB->get_record('course', array('id'=>$this->_instanceid))) {
6756                  if ($withprefix){
6757                      $name = get_string('course').': ';
6758                  }
6759                  if ($short){
6760                      $name .= format_string($course->shortname, true, array('context' => $this));
6761                  } else {
6762                      $name .= format_string(get_course_display_name_for_list($course));
6763                 }
6764              }
6765          }
6766          return $name;
6767      }
6768  
6769      /**
6770       * Returns the most relevant URL for this context.
6771       *
6772       * @return moodle_url
6773       */
6774      public function get_url() {
6775          if ($this->_instanceid != SITEID) {
6776              return new moodle_url('/course/view.php', array('id'=>$this->_instanceid));
6777          }
6778  
6779          return new moodle_url('/');
6780      }
6781  
6782      /**
6783       * Returns array of relevant context capability records.
6784       *
6785       * @return array
6786       */
6787      public function get_capabilities() {
6788          global $DB;
6789  
6790          $sort = 'ORDER BY contextlevel,component,name';   // To group them sensibly for display
6791  
6792          $params = array();
6793          $sql = "SELECT *
6794                    FROM {capabilities}
6795                   WHERE contextlevel IN (".CONTEXT_COURSE.",".CONTEXT_MODULE.",".CONTEXT_BLOCK.")";
6796  
6797          return $DB->get_records_sql($sql.' '.$sort, $params);
6798      }
6799  
6800      /**
6801       * Is this context part of any course? If yes return course context.
6802       *
6803       * @param bool $strict true means throw exception if not found, false means return false if not found
6804       * @return context_course context of the enclosing course, null if not found or exception
6805       */
6806      public function get_course_context($strict = true) {
6807          return $this;
6808      }
6809  
6810      /**
6811       * Returns course context instance.
6812       *
6813       * @static
6814       * @param int $instanceid
6815       * @param int $strictness
6816       * @return context_course context instance
6817       */
6818      public static function instance($instanceid, $strictness = MUST_EXIST) {
6819          global $DB;
6820  
6821          if ($context = context::cache_get(CONTEXT_COURSE, $instanceid)) {
6822              return $context;
6823          }
6824  
6825          if (!$record = $DB->get_record('context', array('contextlevel'=>CONTEXT_COURSE, 'instanceid'=>$instanceid))) {
6826              if ($course = $DB->get_record('course', array('id'=>$instanceid), 'id,category', $strictness)) {
6827                  if ($course->category) {
6828                      $parentcontext = context_coursecat::instance($course->category);
6829                      $record = context::insert_context_record(CONTEXT_COURSE, $course->id, $parentcontext->path);
6830                  } else {
6831                      $record = context::insert_context_record(CONTEXT_COURSE, $course->id, '/'.SYSCONTEXTID, 0);
6832                  }
6833              }
6834          }
6835  
6836          if ($record) {
6837              $context = new context_course($record);
6838              context::cache_add($context);
6839              return $context;
6840          }
6841  
6842          return false;
6843      }
6844  
6845      /**
6846       * Create missing context instances at course context level
6847       * @static
6848       */
6849      protected static function create_level_instances() {
6850          global $DB;
6851  
6852          $sql = "INSERT INTO {context} (contextlevel, instanceid)
6853                  SELECT ".CONTEXT_COURSE.", c.id
6854                    FROM {course} c
6855                   WHERE NOT EXISTS (SELECT 'x'
6856                                       FROM {context} cx
6857                                      WHERE c.id = cx.instanceid AND cx.contextlevel=".CONTEXT_COURSE.")";
6858          $DB->execute($sql);
6859      }
6860  
6861      /**
6862       * Returns sql necessary for purging of stale context instances.
6863       *
6864       * @static
6865       * @return string cleanup SQL
6866       */
6867      protected static function get_cleanup_sql() {
6868          $sql = "
6869                    SELECT c.*
6870                      FROM {context} c
6871           LEFT OUTER JOIN {course} co ON c.instanceid = co.id
6872                     WHERE co.id IS NULL AND c.contextlevel = ".CONTEXT_COURSE."
6873                 ";
6874  
6875          return $sql;
6876      }
6877  
6878      /**
6879       * Rebuild context paths and depths at course context level.
6880       *
6881       * @static
6882       * @param bool $force
6883       */
6884      protected static function build_paths($force) {
6885          global $DB;
6886  
6887          if ($force or $DB->record_exists_select('context', "contextlevel = ".CONTEXT_COURSE." AND (depth = 0 OR path IS NULL)")) {
6888              if ($force) {
6889                  $ctxemptyclause = $emptyclause = '';
6890              } else {
6891                  $ctxemptyclause = "AND (ctx.path IS NULL OR ctx.depth = 0)";
6892                  $emptyclause    = "AND ({context}.path IS NULL OR {context}.depth = 0)";
6893              }
6894  
6895              $base = '/'.SYSCONTEXTID;
6896  
6897              // Standard frontpage
6898              $sql = "UPDATE {context}
6899                         SET depth = 2,
6900                             path = ".$DB->sql_concat("'$base/'", 'id')."
6901                       WHERE contextlevel = ".CONTEXT_COURSE."
6902                             AND EXISTS (SELECT 'x'
6903                                           FROM {course} c
6904                                          WHERE c.id = {context}.instanceid AND c.category = 0)
6905                             $emptyclause";
6906              $DB->execute($sql);
6907  
6908              // standard courses
6909              $sql = "INSERT INTO {context_temp} (id, path, depth)
6910                      SELECT ctx.id, ".$DB->sql_concat('pctx.path', "'/'", 'ctx.id').", pctx.depth+1
6911                        FROM {context} ctx
6912                        JOIN {course} c ON (c.id = ctx.instanceid AND ctx.contextlevel = ".CONTEXT_COURSE." AND c.category <> 0)
6913                        JOIN {context} pctx ON (pctx.instanceid = c.category AND pctx.contextlevel = ".CONTEXT_COURSECAT.")
6914                       WHERE pctx.path IS NOT NULL AND pctx.depth > 0
6915                             $ctxemptyclause";
6916              $trans = $DB->start_delegated_transaction();
6917              $DB->delete_records('context_temp');
6918              $DB->execute($sql);
6919              context::merge_context_temp_table();
6920              $DB->delete_records('context_temp');
6921              $trans->allow_commit();
6922          }
6923      }
6924  }
6925  
6926  
6927  /**
6928   * Course module context class
6929   *
6930   * @package   core_access
6931   * @category  access
6932   * @copyright Petr Skoda {@link http://skodak.org}
6933   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
6934   * @since     Moodle 2.2
6935   */
6936  class context_module extends context {
6937      /**
6938       * Please use context_module::instance($cmid) if you need the instance of context.
6939       * Alternatively if you know only the context id use context::instance_by_id($contextid)
6940       *
6941       * @param stdClass $record
6942       */
6943      protected function __construct(stdClass $record) {
6944          parent::__construct($record);
6945          if ($record->contextlevel != CONTEXT_MODULE) {
6946              throw new coding_exception('Invalid $record->contextlevel in context_module constructor.');
6947          }
6948      }
6949  
6950      /**
6951       * Returns human readable context level name.
6952       *
6953       * @static
6954       * @return string the human readable context level name.
6955       */
6956      public static function get_level_name() {
6957          return get_string('activitymodule');
6958      }
6959  
6960      /**
6961       * Returns human readable context identifier.
6962       *
6963       * @param boolean $withprefix whether to prefix the name of the context with the
6964       *      module name, e.g. Forum, Glossary, etc.
6965       * @param boolean $short does not apply to module context
6966       * @return string the human readable context name.
6967       */
6968      public function get_context_name($withprefix = true, $short = false) {
6969          global $DB;
6970  
6971          $name = '';
6972          if ($cm = $DB->get_record_sql("SELECT cm.*, md.name AS modname
6973                                           FROM {course_modules} cm
6974                                           JOIN {modules} md ON md.id = cm.module
6975                                          WHERE cm.id = ?", array($this->_instanceid))) {
6976              if ($mod = $DB->get_record($cm->modname, array('id' => $cm->instance))) {
6977                      if ($withprefix){
6978                          $name = get_string('modulename', $cm->modname).': ';
6979                      }
6980                      $name .= format_string($mod->name, true, array('context' => $this));
6981                  }
6982              }
6983          return $name;
6984      }
6985  
6986      /**
6987       * Returns the most relevant URL for this context.
6988       *
6989       * @return moodle_url
6990       */
6991      public function get_url() {
6992          global $DB;
6993  
6994          if ($modname = $DB->get_field_sql("SELECT md.name AS modname
6995                                               FROM {course_modules} cm
6996                                               JOIN {modules} md ON md.id = cm.module
6997                                              WHERE cm.id = ?", array($this->_instanceid))) {
6998              return new moodle_url('/mod/' . $modname . '/view.php', array('id'=>$this->_instanceid));
6999          }
7000  
7001          return new moodle_url('/');
7002      }
7003  
7004      /**
7005       * Returns array of relevant context capability records.
7006       *
7007       * @return array
7008       */
7009      public function get_capabilities() {
7010          global $DB, $CFG;
7011  
7012          $sort = 'ORDER BY contextlevel,component,name';   // To group them sensibly for display
7013  
7014          $cm = $DB->get_record('course_modules', array('id'=>$this->_instanceid));
7015          $module = $DB->get_record('modules', array('id'=>$cm->module));
7016  
7017          $subcaps = array();
7018          $subpluginsfile = "$CFG->dirroot/mod/$module->name/db/subplugins.php";
7019          if (file_exists($subpluginsfile)) {
7020              $subplugins = array();  // should be redefined in the file
7021              include($subpluginsfile);
7022              if (!empty($subplugins)) {
7023                  foreach (array_keys($subplugins) as $subplugintype) {
7024                      foreach (array_keys(core_component::get_plugin_list($subplugintype)) as $subpluginname) {
7025                          $subcaps = array_merge($subcaps, array_keys(load_capability_def($subplugintype.'_'.$subpluginname)));
7026                      }
7027                  }
7028              }
7029          }
7030  
7031          $modfile = "$CFG->dirroot/mod/$module->name/lib.php";
7032          $extracaps = array();
7033          if (file_exists($modfile)) {
7034              include_once($modfile);
7035              $modfunction = $module->name.'_get_extra_capabilities';
7036              if (function_exists($modfunction)) {
7037                  $extracaps = $modfunction();
7038              }
7039          }
7040  
7041          $extracaps = array_merge($subcaps, $extracaps);
7042          $extra = '';
7043          list($extra, $params) = $DB->get_in_or_equal(
7044              $extracaps, SQL_PARAMS_NAMED, 'cap0', true, '');
7045          if (!empty($extra)) {
7046              $extra = "OR name $extra";
7047          }
7048          $sql = "SELECT *
7049                    FROM {capabilities}
7050                   WHERE (contextlevel = ".CONTEXT_MODULE."
7051                         AND (component = :component OR component = 'moodle'))
7052                         $extra";
7053          $params['component'] = "mod_$module->name";
7054  
7055          return $DB->get_records_sql($sql.' '.$sort, $params);
7056      }
7057  
7058      /**
7059       * Is this context part of any course? If yes return course context.
7060       *
7061       * @param bool $strict true means throw exception if not found, false means return false if not found
7062       * @return context_course context of the enclosing course, null if not found or exception
7063       */
7064      public function get_course_context($strict = true) {
7065          return $this->get_parent_context();
7066      }
7067  
7068      /**
7069       * Returns module context instance.
7070       *
7071       * @static
7072       * @param int $instanceid
7073       * @param int $strictness
7074       * @return context_module context instance
7075       */
7076      public static function instance($instanceid, $strictness = MUST_EXIST) {
7077          global $DB;
7078  
7079          if ($context = context::cache_get(CONTEXT_MODULE, $instanceid)) {
7080              return $context;
7081          }
7082  
7083          if (!$record = $DB->get_record('context', array('contextlevel'=>CONTEXT_MODULE, 'instanceid'=>$instanceid))) {
7084              if ($cm = $DB->get_record('course_modules', array('id'=>$instanceid), 'id,course', $strictness)) {
7085                  $parentcontext = context_course::instance($cm->course);
7086                  $record = context::insert_context_record(CONTEXT_MODULE, $cm->id, $parentcontext->path);
7087              }
7088          }
7089  
7090          if ($record) {
7091              $context = new context_module($record);
7092              context::cache_add($context);
7093              return $context;
7094          }
7095  
7096          return false;
7097      }
7098  
7099      /**
7100       * Create missing context instances at module context level
7101       * @static
7102       */
7103      protected static function create_level_instances() {
7104          global $DB;
7105  
7106          $sql = "INSERT INTO {context} (contextlevel, instanceid)
7107                  SELECT ".CONTEXT_MODULE.", cm.id
7108                    FROM {course_modules} cm
7109                   WHERE NOT EXISTS (SELECT 'x'
7110                                       FROM {context} cx
7111                                      WHERE cm.id = cx.instanceid AND cx.contextlevel=".CONTEXT_MODULE.")";
7112          $DB->execute($sql);
7113      }
7114  
7115      /**
7116       * Returns sql necessary for purging of stale context instances.
7117       *
7118       * @static
7119       * @return string cleanup SQL
7120       */
7121      protected static function get_cleanup_sql() {
7122          $sql = "
7123                    SELECT c.*
7124                      FROM {context} c
7125           LEFT OUTER JOIN {course_modules} cm ON c.instanceid = cm.id
7126                     WHERE cm.id IS NULL AND c.contextlevel = ".CONTEXT_MODULE."
7127                 ";
7128  
7129          return $sql;
7130      }
7131  
7132      /**
7133       * Rebuild context paths and depths at module context level.
7134       *
7135       * @static
7136       * @param bool $force
7137       */
7138      protected static function build_paths($force) {
7139          global $DB;
7140  
7141          if ($force or $DB->record_exists_select('context', "contextlevel = ".CONTEXT_MODULE." AND (depth = 0 OR path IS NULL)")) {
7142              if ($force) {
7143                  $ctxemptyclause = '';
7144              } else {
7145                  $ctxemptyclause = "AND (ctx.path IS NULL OR ctx.depth = 0)";
7146              }
7147  
7148              $sql = "INSERT INTO {context_temp} (id, path, depth)
7149                      SELECT ctx.id, ".$DB->sql_concat('pctx.path', "'/'", 'ctx.id').", pctx.depth+1
7150                        FROM {context} ctx
7151                        JOIN {course_modules} cm ON (cm.id = ctx.instanceid AND ctx.contextlevel = ".CONTEXT_MODULE.")
7152                        JOIN {context} pctx ON (pctx.instanceid = cm.course AND pctx.contextlevel = ".CONTEXT_COURSE.")
7153                       WHERE pctx.path IS NOT NULL AND pctx.depth > 0
7154                             $ctxemptyclause";
7155              $trans = $DB->start_delegated_transaction();
7156              $DB->delete_records('context_temp');
7157              $DB->execute($sql);
7158              context::merge_context_temp_table();
7159              $DB->delete_records('context_temp');
7160              $trans->allow_commit();
7161          }
7162      }
7163  }
7164  
7165  
7166  /**
7167   * Block context class
7168   *
7169   * @package   core_access
7170   * @category  access
7171   * @copyright Petr Skoda {@link http://skodak.org}
7172   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
7173   * @since     Moodle 2.2
7174   */
7175  class context_block extends context {
7176      /**
7177       * Please use context_block::instance($blockinstanceid) if you need the instance of context.
7178       * Alternatively if you know only the context id use context::instance_by_id($contextid)
7179       *
7180       * @param stdClass $record
7181       */
7182      protected function __construct(stdClass $record) {
7183          parent::__construct($record);
7184          if ($record->contextlevel != CONTEXT_BLOCK) {
7185              throw new coding_exception('Invalid $record->contextlevel in context_block constructor');
7186          }
7187      }
7188  
7189      /**
7190       * Returns human readable context level name.
7191       *
7192       * @static
7193       * @return string the human readable context level name.
7194       */
7195      public static function get_level_name() {
7196          return get_string('block');
7197      }
7198  
7199      /**
7200       * Returns human readable context identifier.
7201       *
7202       * @param boolean $withprefix whether to prefix the name of the context with Block
7203       * @param boolean $short does not apply to block context
7204       * @return string the human readable context name.
7205       */
7206      public function get_context_name($withprefix = true, $short = false) {
7207          global $DB, $CFG;
7208  
7209          $name = '';
7210          if ($blockinstance = $DB->get_record('block_instances', array('id'=>$this->_instanceid))) {
7211              global $CFG;
7212              require_once("$CFG->dirroot/blocks/moodleblock.class.php");
7213              require_once("$CFG->dirroot/blocks/$blockinstance->blockname/block_$blockinstance->blockname.php");
7214              $blockname = "block_$blockinstance->blockname";
7215              if ($blockobject = new $blockname()) {
7216                  if ($withprefix){
7217                      $name = get_string('block').': ';
7218                  }
7219                  $name .= $blockobject->title;
7220              }
7221          }
7222  
7223          return $name;
7224      }
7225  
7226      /**
7227       * Returns the most relevant URL for this context.
7228       *
7229       * @return moodle_url
7230       */
7231      public function get_url() {
7232          $parentcontexts = $this->get_parent_context();
7233          return $parentcontexts->get_url();
7234      }
7235  
7236      /**
7237       * Returns array of relevant context capability records.
7238       *
7239       * @return array
7240       */
7241      public function get_capabilities() {
7242          global $DB;
7243  
7244          $sort = 'ORDER BY contextlevel,component,name';   // To group them sensibly for display
7245  
7246          $params = array();
7247          $bi = $DB->get_record('block_instances', array('id' => $this->_instanceid));
7248  
7249          $extra = '';
7250          $extracaps = block_method_result($bi->blockname, 'get_extra_capabilities');
7251          if ($extracaps) {
7252              list($extra, $params) = $DB->get_in_or_equal($extracaps, SQL_PARAMS_NAMED, 'cap');
7253              $extra = "OR name $extra";
7254          }
7255  
7256          $sql = "SELECT *
7257                    FROM {capabilities}
7258                   WHERE (contextlevel = ".CONTEXT_BLOCK."
7259                         AND component = :component)
7260                         $extra";
7261          $params['component'] = 'block_' . $bi->blockname;
7262  
7263          return $DB->get_records_sql($sql.' '.$sort, $params);
7264      }
7265  
7266      /**
7267       * Is this context part of any course? If yes return course context.
7268       *
7269       * @param bool $strict true means throw exception if not found, false means return false if not found
7270       * @return context_course context of the enclosing course, null if not found or exception
7271       */
7272      public function get_course_context($strict = true) {
7273          $parentcontext = $this->get_parent_context();
7274          return $parentcontext->get_course_context($strict);
7275      }
7276  
7277      /**
7278       * Returns block context instance.
7279       *
7280       * @static
7281       * @param int $instanceid
7282       * @param int $strictness
7283       * @return context_block context instance
7284       */
7285      public static function instance($instanceid, $strictness = MUST_EXIST) {
7286          global $DB;
7287  
7288          if ($context = context::cache_get(CONTEXT_BLOCK, $instanceid)) {
7289              return $context;
7290          }
7291  
7292          if (!$record = $DB->get_record('context', array('contextlevel'=>CONTEXT_BLOCK, 'instanceid'=>$instanceid))) {
7293              if ($bi = $DB->get_record('block_instances', array('id'=>$instanceid), 'id,parentcontextid', $strictness)) {
7294                  $parentcontext = context::instance_by_id($bi->parentcontextid);
7295                  $record = context::insert_context_record(CONTEXT_BLOCK, $bi->id, $parentcontext->path);
7296              }
7297          }
7298  
7299          if ($record) {
7300              $context = new context_block($record);
7301              context::cache_add($context);
7302              return $context;
7303          }
7304  
7305          return false;
7306      }
7307  
7308      /**
7309       * Block do not have child contexts...
7310       * @return array
7311       */
7312      public function get_child_contexts() {
7313          return array();
7314      }
7315  
7316      /**
7317       * Create missing context instances at block context level
7318       * @static
7319       */
7320      protected static function create_level_instances() {
7321          global $DB;
7322  
7323          $sql = "INSERT INTO {context} (contextlevel, instanceid)
7324                  SELECT ".CONTEXT_BLOCK.", bi.id
7325                    FROM {block_instances} bi
7326                   WHERE NOT EXISTS (SELECT 'x'
7327                                       FROM {context} cx
7328                                      WHERE bi.id = cx.instanceid AND cx.contextlevel=".CONTEXT_BLOCK.")";
7329          $DB->execute($sql);
7330      }
7331  
7332      /**
7333       * Returns sql necessary for purging of stale context instances.
7334       *
7335       * @static
7336       * @return string cleanup SQL
7337       */
7338      protected static function get_cleanup_sql() {
7339          $sql = "
7340                    SELECT c.*
7341                      FROM {context} c
7342           LEFT OUTER JOIN {block_instances} bi ON c.instanceid = bi.id
7343                     WHERE bi.id IS NULL AND c.contextlevel = ".CONTEXT_BLOCK."
7344                 ";
7345  
7346          return $sql;
7347      }
7348  
7349      /**
7350       * Rebuild context paths and depths at block context level.
7351       *
7352       * @static
7353       * @param bool $force
7354       */
7355      protected static function build_paths($force) {
7356          global $DB;
7357  
7358          if ($force or $DB->record_exists_select('context', "contextlevel = ".CONTEXT_BLOCK." AND (depth = 0 OR path IS NULL)")) {
7359              if ($force) {
7360                  $ctxemptyclause = '';
7361              } else {
7362                  $ctxemptyclause = "AND (ctx.path IS NULL OR ctx.depth = 0)";
7363              }
7364  
7365              // pctx.path IS NOT NULL prevents fatal problems with broken block instances that point to invalid context parent
7366              $sql = "INSERT INTO {context_temp} (id, path, depth)
7367                      SELECT ctx.id, ".$DB->sql_concat('pctx.path', "'/'", 'ctx.id').", pctx.depth+1
7368                        FROM {context} ctx
7369                        JOIN {block_instances} bi ON (bi.id = ctx.instanceid AND ctx.contextlevel = ".CONTEXT_BLOCK.")
7370                        JOIN {context} pctx ON (pctx.id = bi.parentcontextid)
7371                       WHERE (pctx.path IS NOT NULL AND pctx.depth > 0)
7372                             $ctxemptyclause";
7373              $trans = $DB->start_delegated_transaction();
7374              $DB->delete_records('context_temp');
7375              $DB->execute($sql);
7376              context::merge_context_temp_table();
7377              $DB->delete_records('context_temp');
7378              $trans->allow_commit();
7379          }
7380      }
7381  }
7382  
7383  
7384  // ============== DEPRECATED FUNCTIONS ==========================================
7385  // Old context related functions were deprecated in 2.0, it is recommended
7386  // to use context classes in new code. Old function can be used when
7387  // creating patches that are supposed to be backported to older stable branches.
7388  // These deprecated functions will not be removed in near future,
7389  // before removing devs will be warned with a debugging message first,
7390  // then we will add error message and only after that we can remove the functions
7391  // completely.
7392  
7393  /**
7394   * Runs get_records select on context table and returns the result
7395   * Does get_records_select on the context table, and returns the results ordered
7396   * by contextlevel, and then the natural sort order within each level.
7397   * for the purpose of $select, you need to know that the context table has been
7398   * aliased to ctx, so for example, you can call get_sorted_contexts('ctx.depth = 3');
7399   *
7400   * @param string $select the contents of the WHERE clause. Remember to do ctx.fieldname.
7401   * @param array $params any parameters required by $select.
7402   * @return array the requested context records.
7403   */
7404  function get_sorted_contexts($select, $params = array()) {
7405  
7406      //TODO: we should probably rewrite all the code that is using this thing, the trouble is we MUST NOT modify the context instances...
7407  
7408      global $DB;
7409      if ($select) {
7410          $select = 'WHERE ' . $select;
7411      }
7412      return $DB->get_records_sql("
7413              SELECT ctx.*
7414                FROM {context} ctx
7415                LEFT JOIN {user} u ON ctx.contextlevel = " . CONTEXT_USER . " AND u.id = ctx.instanceid
7416                LEFT JOIN {course_categories} cat ON ctx.contextlevel = " . CONTEXT_COURSECAT . " AND cat.id = ctx.instanceid
7417                LEFT JOIN {course} c ON ctx.contextlevel = " . CONTEXT_COURSE . " AND c.id = ctx.instanceid
7418                LEFT JOIN {course_modules} cm ON ctx.contextlevel = " . CONTEXT_MODULE . " AND cm.id = ctx.instanceid
7419                LEFT JOIN {block_instances} bi ON ctx.contextlevel = " . CONTEXT_BLOCK . " AND bi.id = ctx.instanceid
7420             $select
7421            ORDER BY ctx.contextlevel, bi.defaultregion, COALESCE(cat.sortorder, c.sortorder, cm.section, bi.defaultweight), u.lastname, u.firstname, cm.id
7422              ", $params);
7423  }
7424  
7425  /**
7426   * Given context and array of users, returns array of users whose enrolment status is suspended,
7427   * or enrolment has expired or has not started. Also removes those users from the given array
7428   *
7429   * @param context $context context in which suspended users should be extracted.
7430   * @param array $users list of users.
7431   * @param array $ignoreusers array of user ids to ignore, e.g. guest
7432   * @return array list of suspended users.
7433   */
7434  function extract_suspended_users($context, &$users, $ignoreusers=array()) {
7435      global $DB;
7436  
7437      // Get active enrolled users.
7438      list($sql, $params) = get_enrolled_sql($context, null, null, true);
7439      $activeusers = $DB->get_records_sql($sql, $params);
7440  
7441      // Move suspended users to a separate array & remove from the initial one.
7442      $susers = array();
7443      if (sizeof($activeusers)) {
7444          foreach ($users as $userid => $user) {
7445              if (!array_key_exists($userid, $activeusers) && !in_array($userid, $ignoreusers)) {
7446                  $susers[$userid] = $user;
7447                  unset($users[$userid]);
7448              }
7449          }
7450      }
7451      return $susers;
7452  }
7453  
7454  /**
7455   * Given context and array of users, returns array of user ids whose enrolment status is suspended,
7456   * or enrolment has expired or not started.
7457   *
7458   * @param context $context context in which user enrolment is checked.
7459   * @param bool $usecache Enable or disable (default) the request cache
7460   * @return array list of suspended user id's.
7461   */
7462  function get_suspended_userids(context $context, $usecache = false) {
7463      global $DB;
7464  
7465      // Check the cache first for performance reasons if enabled.
7466      if ($usecache) {
7467          $cache = cache::make('core', 'suspended_userids');
7468          $susers = $cache->get($context->id);
7469          if ($susers !== false) {
7470              return $susers;
7471          }
7472      }
7473  
7474      // Get all enrolled users.
7475      list($sql, $params) = get_enrolled_sql($context);
7476      $users = $DB->get_records_sql($sql, $params);
7477  
7478      // Get active enrolled users.
7479      list($sql, $params) = get_enrolled_sql($context, null, null, true);
7480      $activeusers = $DB->get_records_sql($sql, $params);
7481  
7482      $susers = array();
7483      if (sizeof($activeusers) != sizeof($users)) {
7484          foreach ($users as $userid => $user) {
7485              if (!array_key_exists($userid, $activeusers)) {
7486                  $susers[$userid] = $userid;
7487              }
7488          }
7489      }
7490  
7491      // Cache results for the remainder of this request.
7492      if ($usecache) {
7493          $cache->set($context->id, $susers);
7494      }
7495  
7496      // Return.
7497      return $susers;
7498  }


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1