[ Index ]

PHP Cross Reference of Unnamed Project

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


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1