[ Index ]

PHP Cross Reference of Moodle 1.9.3 [Build 15-Oct-2008]

title

Body

[close]

/lib/ -> accesslib.php (source)

   1  <?php // $Id: accesslib.php,v 1.421.2.78 2008/09/24 17:26:27 stronk7 Exp $
   2  
   3  ///////////////////////////////////////////////////////////////////////////
   4  //                                                                       //
   5  // NOTICE OF COPYRIGHT                                                   //
   6  //                                                                       //
   7  // Moodle - Modular Object-Oriented Dynamic Learning Environment         //
   8  //          http://moodle.org                                            //
   9  //                                                                       //
  10  // Copyright (C) 1999 onwards Martin Dougiamas  http://dougiamas.com     //
  11  //                                                                       //
  12  // This program is free software; you can redistribute it and/or modify  //
  13  // it under the terms of the GNU General Public License as published by  //
  14  // the Free Software Foundation; either version 2 of the License, or     //
  15  // (at your option) any later version.                                   //
  16  //                                                                       //
  17  // This program is distributed in the hope that it will be useful,       //
  18  // but WITHOUT ANY WARRANTY; without even the implied warranty of        //
  19  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         //
  20  // GNU General Public License for more details:                          //
  21  //                                                                       //
  22  //          http://www.gnu.org/copyleft/gpl.html                         //
  23  //                                                                       //
  24  ///////////////////////////////////////////////////////////////////////////
  25  
  26  /**
  27   * Public API vs internals 
  28   * -----------------------
  29   * 
  30   * General users probably only care about
  31   *
  32   * Context handling
  33   * - get_context_instance()
  34   * - get_context_instance_by_id()
  35   * - get_parent_contexts()
  36   * - get_child_contexts()
  37   * 
  38   * Whether the user can do something...
  39   * - has_capability()
  40   * - require_capability()
  41   * - require_login() (from moodlelib)
  42   *
  43   * What courses has this user access to?
  44   * - get_user_courses_bycap()
  45   *
  46   * What users can do X in this context?
  47   * - get_users_by_capability()
  48   *
  49   * Enrol/unenrol
  50   * - enrol_into_course()
  51   * - role_assign()/role_unassign()
  52   * 
  53   *
  54   * Advanced use
  55   * - load_all_capabilities()
  56   * - reload_all_capabilities()
  57   * - $ACCESS global
  58   * - has_capability_in_accessdata()
  59   * - is_siteadmin()
  60   * - get_user_access_sitewide()
  61   * - load_subcontext()
  62   * - get_role_access_bycontext()
  63   *
  64   * Name conventions
  65   * ----------------
  66   * 
  67   * - "ctx" means context
  68   *
  69   * accessdata
  70   * ----------
  71   *
  72   * Access control data is held in the "accessdata" array
  73   * which - for the logged-in user, will be in $USER->access
  74   * 
  75   * For other users can be generated and passed around (but see
  76   * the $ACCESS global).
  77   *
  78   * $accessdata is a multidimensional array, holding
  79   * role assignments (RAs), role-capabilities-perm sets 
  80   * (role defs) and a list of courses we have loaded
  81   * data for.
  82   *
  83   * Things are keyed on "contextpaths" (the path field of 
  84   * the context table) for fast walking up/down the tree.
  85   * 
  86   * $accessdata[ra][$contextpath]= array($roleid)
  87   *                [$contextpath]= array($roleid)
  88   *                [$contextpath]= array($roleid) 
  89   *
  90   * Role definitions are stored like this
  91   * (no cap merge is done - so it's compact)
  92   *
  93   * $accessdata[rdef][$contextpath:$roleid][mod/forum:viewpost] = 1
  94   *                                        [mod/forum:editallpost] = -1
  95   *                                        [mod/forum:startdiscussion] = -1000
  96   *
  97   * See how has_capability_in_accessdata() walks up/down the tree.
  98   *
  99   * Normally - specially for the logged-in user, we only load
 100   * rdef and ra down to the course level, but not below. This
 101   * keeps accessdata small and compact. Below-the-course ra/rdef
 102   * are loaded as needed. We keep track of which courses we
 103   * have loaded ra/rdef in 
 104   *
 105   * $accessdata[loaded] = array($contextpath, $contextpath) 
 106   *
 107   * Stale accessdata
 108   * ----------------
 109   *
 110   * For the logged-in user, accessdata is long-lived.
 111   *
 112   * On each pageload we load $DIRTYPATHS which lists
 113   * context paths affected by changes. Any check at-or-below
 114   * a dirty context will trigger a transparent reload of accessdata.
 115   * 
 116   * Changes at the sytem level will force the reload for everyone.
 117   *
 118   * Default role caps
 119   * -----------------
 120   * The default role assignment is not in the DB, so we 
 121   * add it manually to accessdata. 
 122   *
 123   * This means that functions that work directly off the
 124   * DB need to ensure that the default role caps
 125   * are dealt with appropriately. 
 126   *
 127   */
 128  
 129  require_once $CFG->dirroot.'/lib/blocklib.php';
 130  
 131  // permission definitions
 132  define('CAP_INHERIT', 0);
 133  define('CAP_ALLOW', 1);
 134  define('CAP_PREVENT', -1);
 135  define('CAP_PROHIBIT', -1000);
 136  
 137  // context definitions
 138  define('CONTEXT_SYSTEM', 10);
 139  define('CONTEXT_USER', 30);
 140  define('CONTEXT_COURSECAT', 40);
 141  define('CONTEXT_COURSE', 50);
 142  define('CONTEXT_GROUP', 60);
 143  define('CONTEXT_MODULE', 70);
 144  define('CONTEXT_BLOCK', 80);
 145  
 146  // capability risks - see http://docs.moodle.org/en/Development:Hardening_new_Roles_system
 147  define('RISK_MANAGETRUST', 0x0001);
 148  define('RISK_CONFIG',      0x0002);
 149  define('RISK_XSS',         0x0004);
 150  define('RISK_PERSONAL',    0x0008);
 151  define('RISK_SPAM',        0x0010);
 152  define('RISK_DATALOSS',    0x0020);
 153  
 154  // rolename displays
 155  define('ROLENAME_ORIGINAL', 0);// the name as defined in the role definition
 156  define('ROLENAME_ALIAS', 1);   // the name as defined by a role alias 
 157  define('ROLENAME_BOTH', 2);    // Both, like this:  Role alias (Original)
 158  
 159  require_once($CFG->dirroot.'/group/lib.php');
 160  
 161  $context_cache    = array();    // Cache of all used context objects for performance (by level and instance)
 162  $context_cache_id = array();    // Index to above cache by id
 163  
 164  $DIRTYCONTEXTS = null; // dirty contexts cache
 165  $ACCESS = array(); // cache of caps for cron user switching and has_capability for other users (==not $USER)
 166  $RDEFS = array(); // role definitions cache - helps a lot with mem usage in cron
 167  
 168  function get_role_context_caps($roleid, $context) {
 169      //this is really slow!!!! - do not use above course context level!
 170      $result = array();
 171      $result[$context->id] = array();
 172  
 173      // first emulate the parent context capabilities merging into context
 174      $searchcontexts = array_reverse(get_parent_contexts($context));
 175      array_push($searchcontexts, $context->id);
 176      foreach ($searchcontexts as $cid) {
 177          if ($capabilities = get_records_select('role_capabilities', "roleid = $roleid AND contextid = $cid")) {
 178              foreach ($capabilities as $cap) {
 179                  if (!array_key_exists($cap->capability, $result[$context->id])) {
 180                      $result[$context->id][$cap->capability] = 0;
 181                  }
 182                  $result[$context->id][$cap->capability] += $cap->permission;
 183              }
 184          }
 185      }
 186  
 187      // now go through the contexts bellow given context
 188      $searchcontexts = array_keys(get_child_contexts($context));
 189      foreach ($searchcontexts as $cid) {
 190          if ($capabilities = get_records_select('role_capabilities', "roleid = $roleid AND contextid = $cid")) {
 191              foreach ($capabilities as $cap) {
 192                  if (!array_key_exists($cap->contextid, $result)) {
 193                      $result[$cap->contextid] = array();
 194                  }
 195                  $result[$cap->contextid][$cap->capability] = $cap->permission;
 196              }
 197          }
 198      }
 199  
 200      return $result;
 201  }
 202  
 203  /**
 204   * Gets the accessdata for role "sitewide" 
 205   * (system down to course)
 206   *
 207   * @return array
 208   */
 209  function get_role_access($roleid, $accessdata=NULL) {
 210  
 211      global $CFG;
 212  
 213      /* Get it in 1 cheap DB query...
 214       * - relevant role caps at the root and down
 215       *   to the course level - but not below
 216       */
 217      if (is_null($accessdata)) {
 218          $accessdata           = array(); // named list
 219          $accessdata['ra']     = array();
 220          $accessdata['rdef']   = array();
 221          $accessdata['loaded'] = array();
 222      }
 223  
 224      //
 225      // Overrides for the role IN ANY CONTEXTS
 226      // down to COURSE - not below -
 227      //
 228      $sql = "SELECT ctx.path,
 229                     rc.capability, rc.permission
 230              FROM {$CFG->prefix}context ctx
 231              JOIN {$CFG->prefix}role_capabilities rc
 232                ON rc.contextid=ctx.id
 233              WHERE rc.roleid = {$roleid}
 234                    AND ctx.contextlevel <= ".CONTEXT_COURSE."
 235              ORDER BY ctx.depth, ctx.path";
 236  
 237      // we need extra caching in cron only
 238      if (defined('FULLME') and FULLME === 'cron') {
 239          static $cron_cache = array();
 240  
 241          if (!isset($cron_cache[$roleid])) {
 242              $cron_cache[$roleid] = array();
 243              if ($rs = get_recordset_sql($sql)) {
 244                  while ($rd = rs_fetch_next_record($rs)) {
 245                      $cron_cache[$roleid][] = $rd;
 246                  }
 247                  rs_close($rs);
 248              }
 249          }
 250  
 251          foreach ($cron_cache[$roleid] as $rd) {
 252              $k = "{$rd->path}:{$roleid}";
 253              $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
 254          }
 255          
 256      } else {
 257          if ($rs = get_recordset_sql($sql)) {
 258              while ($rd = rs_fetch_next_record($rs)) {
 259                  $k = "{$rd->path}:{$roleid}";
 260                  $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
 261              }
 262              unset($rd);
 263              rs_close($rs);
 264          }
 265      }
 266  
 267      return $accessdata;
 268  }
 269  
 270  /**
 271   * Gets the accessdata for role "sitewide" 
 272   * (system down to course)
 273   *
 274   * @return array
 275   */
 276  function get_default_frontpage_role_access($roleid, $accessdata=NULL) {
 277  
 278      global $CFG;
 279      
 280      $frontpagecontext = get_context_instance(CONTEXT_COURSE, SITEID);
 281      $base = '/'. SYSCONTEXTID .'/'. $frontpagecontext->id;
 282   
 283      //
 284      // Overrides for the role in any contexts related to the course
 285      //
 286      $sql = "SELECT ctx.path,
 287                     rc.capability, rc.permission
 288              FROM {$CFG->prefix}context ctx
 289              JOIN {$CFG->prefix}role_capabilities rc
 290                ON rc.contextid=ctx.id
 291              WHERE rc.roleid = {$roleid}
 292                    AND (ctx.id = ".SYSCONTEXTID." OR ctx.path LIKE '$base/%')
 293                    AND ctx.contextlevel <= ".CONTEXT_COURSE."
 294              ORDER BY ctx.depth, ctx.path";             
 295              
 296      if ($rs = get_recordset_sql($sql)) {
 297          while ($rd = rs_fetch_next_record($rs)) {
 298              $k = "{$rd->path}:{$roleid}";
 299              $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
 300          }
 301          unset($rd);
 302          rs_close($rs);
 303      }
 304  
 305      return $accessdata;
 306  }
 307  
 308  
 309  /**
 310   * Get the default guest role
 311   * @return object role
 312   */
 313  function get_guest_role() {
 314      global $CFG;
 315  
 316      if (empty($CFG->guestroleid)) {
 317          if ($roles = get_roles_with_capability('moodle/legacy:guest', CAP_ALLOW)) {
 318              $guestrole = array_shift($roles);   // Pick the first one
 319              set_config('guestroleid', $guestrole->id);
 320              return $guestrole;
 321          } else {
 322              debugging('Can not find any guest role!');
 323              return false;
 324          }
 325      } else {
 326          if ($guestrole = get_record('role','id', $CFG->guestroleid)) {
 327              return $guestrole;
 328          } else {
 329              //somebody is messing with guest roles, remove incorrect setting and try to find a new one
 330              set_config('guestroleid', '');
 331              return get_guest_role();
 332          }
 333      }
 334  }
 335  
 336  /**
 337   * This function returns whether the current user has the capability of performing a function
 338   * For example, we can do has_capability('mod/forum:replypost',$context) in forum
 339   * @param string $capability - name of the capability (or debugcache or clearcache)
 340   * @param object $context - a context object (record from context table)
 341   * @param integer $userid - a userid number, empty if current $USER
 342   * @param bool $doanything - if false, ignore do anything
 343   * @return bool
 344   */
 345  function has_capability($capability, $context, $userid=NULL, $doanything=true) {
 346      global $USER, $ACCESS, $CFG, $DIRTYCONTEXTS;
 347  
 348      // the original $CONTEXT here was hiding serious errors
 349      // for security reasons do not reuse previous context
 350      if (empty($context)) {
 351          debugging('Incorrect context specified');
 352          return false;
 353      }
 354  
 355  /// Some sanity checks
 356      if (debugging('',DEBUG_DEVELOPER)) {
 357          static $capsnames = null; // one request per page only
 358          
 359          if (is_null($capsnames)) {
 360              if ($caps = get_records('capabilities', '', '', '', 'id, name')) {
 361                  $capsnames = array();
 362                  foreach ($caps as $cap) {
 363                      $capsnames[$cap->name] = true;
 364                  }
 365              }
 366          }
 367          if ($capsnames) { // ignore if can not fetch caps
 368              if (!isset($capsnames[$capability])) {
 369                  debugging('Capability "'.$capability.'" was not found! This should be fixed in code.');
 370              }
 371          }
 372          if (!is_bool($doanything)) {
 373              debugging('Capability parameter "doanything" is wierd ("'.$doanything.'"). This should be fixed in code.');
 374          }
 375      }
 376  
 377      if (empty($userid)) { // we must accept null, 0, '0', '' etc. in $userid
 378          $userid = $USER->id;
 379      }
 380  
 381      if (is_null($context->path) or $context->depth == 0) {
 382          //this should not happen
 383          $contexts = array(SYSCONTEXTID, $context->id);
 384          $context->path = '/'.SYSCONTEXTID.'/'.$context->id;
 385          debugging('Context id '.$context->id.' does not have valid path, please use build_context_path()', DEBUG_DEVELOPER);
 386  
 387      } else {
 388          $contexts = explode('/', $context->path);
 389          array_shift($contexts);
 390      }
 391  
 392      if (defined('FULLME') && FULLME === 'cron' && !isset($USER->access)) {
 393          // In cron, some modules setup a 'fake' $USER,
 394          // ensure we load the appropriate accessdata.
 395          if (isset($ACCESS[$userid])) {
 396              $DIRTYCONTEXTS = NULL; //load fresh dirty contexts
 397          } else {
 398              load_user_accessdata($userid);
 399              $DIRTYCONTEXTS = array();
 400          }
 401          $USER->access = $ACCESS[$userid];
 402  
 403      } else if ($USER->id == $userid && !isset($USER->access)) {
 404          // caps not loaded yet - better to load them to keep BC with 1.8
 405          // not-logged-in user or $USER object set up manually first time here
 406          load_all_capabilities();
 407          $ACCESS = array(); // reset the cache for other users too, the dirty contexts are empty now
 408          $RDEFS = array();
 409      }
 410  
 411      // Load dirty contexts list if needed
 412      if (!isset($DIRTYCONTEXTS)) {
 413          if (isset($USER->access['time'])) {
 414              $DIRTYCONTEXTS = get_dirty_contexts($USER->access['time']);
 415          }
 416          else {
 417              $DIRTYCONTEXTS = array();
 418          }
 419      }
 420  
 421      // Careful check for staleness...
 422      if (count($DIRTYCONTEXTS) !== 0 and is_contextpath_dirty($contexts, $DIRTYCONTEXTS)) {
 423          // reload all capabilities - preserving loginas, roleswitches, etc
 424          // and then cleanup any marks of dirtyness... at least from our short
 425          // term memory! :-)
 426          $ACCESS = array();
 427          $RDEFS = array();
 428  
 429          if (defined('FULLME') && FULLME === 'cron') {
 430              load_user_accessdata($userid);
 431              $USER->access = $ACCESS[$userid];
 432              $DIRTYCONTEXTS = array();
 433  
 434          } else {
 435              reload_all_capabilities();
 436          }
 437      }
 438  
 439      // divulge how many times we are called
 440      //// error_log("has_capability: id:{$context->id} path:{$context->path} userid:$userid cap:$capability");
 441  
 442      if ($USER->id == $userid) { // we must accept strings and integers in $userid
 443          //
 444          // For the logged in user, we have $USER->access
 445          // which will have all RAs and caps preloaded for
 446          // course and above contexts.
 447          //
 448          // Contexts below courses && contexts that do not
 449          // hang from courses are loaded into $USER->access
 450          // on demand, and listed in $USER->access[loaded]
 451          //
 452          if ($context->contextlevel <= CONTEXT_COURSE) {
 453              // Course and above are always preloaded
 454              return has_capability_in_accessdata($capability, $context, $USER->access, $doanything);
 455          }
 456          // Load accessdata for below-the-course contexts
 457          if (!path_inaccessdata($context->path,$USER->access)) {
 458              // error_log("loading access for context {$context->path} for $capability at {$context->contextlevel} {$context->id}");
 459              // $bt = debug_backtrace();
 460              // error_log("bt {$bt[0]['file']} {$bt[0]['line']}");
 461              load_subcontext($USER->id, $context, $USER->access);
 462          }
 463          return has_capability_in_accessdata($capability, $context, $USER->access, $doanything);
 464      }
 465  
 466      if (!isset($ACCESS[$userid])) {
 467          load_user_accessdata($userid);
 468      }
 469      if ($context->contextlevel <= CONTEXT_COURSE) {
 470          // Course and above are always preloaded
 471          return has_capability_in_accessdata($capability, $context, $ACCESS[$userid], $doanything);
 472      }
 473      // Load accessdata for below-the-course contexts as needed
 474      if (!path_inaccessdata($context->path, $ACCESS[$userid])) {
 475          // error_log("loading access for context {$context->path} for $capability at {$context->contextlevel} {$context->id}");
 476          // $bt = debug_backtrace();
 477          // error_log("bt {$bt[0]['file']} {$bt[0]['line']}");
 478          load_subcontext($userid, $context, $ACCESS[$userid]);
 479      }
 480      return has_capability_in_accessdata($capability, $context, $ACCESS[$userid], $doanything);
 481  }
 482  
 483  /**
 484   * This function returns whether the current user has any of the capabilities in the
 485   * $capabilities array. This is a simple wrapper around has_capability for convinience.
 486   *
 487   * There are probably tricks that could be done to improve the performance here, for example,
 488   * check the capabilities that are already cached first.
 489   *
 490   * @param array $capabilities - an array of capability names.
 491   * @param object $context - a context object (record from context table)
 492   * @param integer $userid - a userid number, empty if current $USER
 493   * @param bool $doanything - if false, ignore do anything
 494   * @return bool
 495   */
 496  function has_any_capability($capabilities, $context, $userid=NULL, $doanything=true) {
 497      foreach ($capabilities as $capability) {
 498          if (has_capability($capability, $context, $userid, $doanything)) {
 499              return true;
 500          }
 501      }
 502      return false;
 503  }
 504  
 505  /**
 506   * Uses 1 DB query to answer whether a user is an admin at the sitelevel.
 507   * It depends on DB schema >=1.7 but does not depend on the new datastructures
 508   * in v1.9 (context.path, or $USER->access)
 509   *
 510   * Will return true if the userid has any of
 511   *  - moodle/site:config
 512   *  - moodle/legacy:admin
 513   *  - moodle/site:doanything
 514   *
 515   * @param   int  $userid
 516   * @returns bool $isadmin
 517   */
 518  function is_siteadmin($userid) {
 519      global $CFG;
 520  
 521      $sql = "SELECT SUM(rc.permission)
 522              FROM " . $CFG->prefix . "role_capabilities rc
 523              JOIN " . $CFG->prefix . "context ctx 
 524                ON ctx.id=rc.contextid
 525              JOIN " . $CFG->prefix . "role_assignments ra
 526                ON ra.roleid=rc.roleid AND ra.contextid=ctx.id
 527              WHERE ctx.contextlevel=10
 528                AND ra.userid={$userid}
 529                AND rc.capability IN ('moodle/site:config', 'moodle/legacy:admin', 'moodle/site:doanything')       
 530              GROUP BY rc.capability
 531              HAVING SUM(rc.permission) > 0";
 532  
 533      $isadmin = record_exists_sql($sql);
 534      return $isadmin;
 535  }
 536  
 537  function get_course_from_path ($path) {
 538      // assume that nothing is more than 1 course deep
 539      if (preg_match('!^(/.+)/\d+$!', $path, $matches)) {
 540          return $matches[1];
 541      }
 542      return false;
 543  }
 544  
 545  function path_inaccessdata($path, $accessdata) {
 546  
 547      // assume that contexts hang from sys or from a course
 548      // this will only work well with stuff that hangs from a course
 549      if (in_array($path, $accessdata['loaded'], true)) {
 550              // error_log("found it!");
 551          return true;
 552      }
 553      $base = '/' . SYSCONTEXTID;
 554      while (preg_match('!^(/.+)/\d+$!', $path, $matches)) {
 555          $path = $matches[1];
 556          if ($path === $base) {
 557              return false;
 558          }
 559          if (in_array($path, $accessdata['loaded'], true)) {
 560              return true;
 561          }
 562      }
 563      return false;
 564  }
 565  
 566  /**
 567   * Walk the accessdata array and return true/false.
 568   * Deals with prohibits, roleswitching, aggregating
 569   * capabilities, etc.
 570   *
 571   * The main feature of here is being FAST and with no
 572   * side effects. 
 573   *
 574   * Notes:
 575   *
 576   * Switch Roles exits early
 577   * -----------------------
 578   * cap checks within a switchrole need to exit early
 579   * in our bottom up processing so they don't "see" that
 580   * there are real RAs that can do all sorts of things.
 581   *
 582   * Switch Role merges with default role
 583   * ------------------------------------
 584   * If you are a teacher in course X, you have at least
 585   * teacher-in-X + defaultloggedinuser-sitewide. So in the
 586   * course you'll have techer+defaultloggedinuser.
 587   * We try to mimic that in switchrole.
 588   *
 589   * Local-most role definition and role-assignment wins
 590   * ---------------------------------------------------
 591   * So if the local context has said 'allow', it wins
 592   * over a high-level context that says 'deny'.
 593   * This is applied when walking rdefs, and RAs.
 594   * Only at the same context the values are SUM()med.
 595   *
 596   * The exception is CAP_PROHIBIT.
 597   *
 598   * "Guest default role" exception
 599   * ------------------------------
 600   *
 601   * See MDL-7513 and $ignoreguest below for details.
 602   *
 603   * The rule is that
 604   *
 605   *    IF we are being asked about moodle/legacy:guest
 606   *                             OR moodle/course:view
 607   *    FOR a real, logged-in user
 608   *    AND we reached the top of the path in ra and rdef
 609   *    AND that role has moodle/legacy:guest === 1...
 610   *    THEN we act as if we hadn't seen it.
 611   *
 612   *
 613   * To Do:
 614   *
 615   * - Document how it works
 616   * - Rewrite in ASM :-)
 617   *
 618   */
 619  function has_capability_in_accessdata($capability, $context, $accessdata, $doanything) {
 620  
 621      global $CFG;
 622  
 623      $path = $context->path;
 624  
 625      // build $contexts as a list of "paths" of the current
 626      // contexts and parents with the order top-to-bottom
 627      $contexts = array($path);
 628      while (preg_match('!^(/.+)/\d+$!', $path, $matches)) {
 629          $path = $matches[1];
 630          array_unshift($contexts, $path);
 631      }
 632  
 633      $ignoreguest = false;
 634      if (isset($accessdata['dr'])
 635          && ($capability    == 'moodle/course:view'
 636              || $capability == 'moodle/legacy:guest')) {
 637          // At the base, ignore rdefs where moodle/legacy:guest
 638          // is set
 639          $ignoreguest = $accessdata['dr'];
 640      }
 641  
 642      // Coerce it to an int
 643      $CAP_PROHIBIT = (int)CAP_PROHIBIT;
 644  
 645      $cc = count($contexts);
 646  
 647      $can = 0;
 648      $capdepth = 0;
 649  
 650      //
 651      // role-switches loop
 652      //
 653      if (isset($accessdata['rsw'])) {
 654          // check for isset() is fast 
 655          // empty() is slow...
 656          if (empty($accessdata['rsw'])) {
 657              unset($accessdata['rsw']); // keep things fast and unambiguous
 658              break;
 659          }
 660          // From the bottom up...
 661          for ($n=$cc-1;$n>=0;$n--) {
 662              $ctxp = $contexts[$n];
 663              if (isset($accessdata['rsw'][$ctxp])) {
 664                  // Found a switchrole assignment
 665                  // check for that role _plus_ the default user role
 666                  $ras = array($accessdata['rsw'][$ctxp],$CFG->defaultuserroleid);
 667                  for ($rn=0;$rn<2;$rn++) {
 668                      $roleid = (int)$ras[$rn];
 669                      // Walk the path for capabilities
 670                      // from the bottom up...
 671                      for ($m=$cc-1;$m>=0;$m--) {
 672                          $capctxp = $contexts[$m];
 673                          if (isset($accessdata['rdef']["{$capctxp}:$roleid"][$capability])) {
 674                              $perm = (int)$accessdata['rdef']["{$capctxp}:$roleid"][$capability];
 675  
 676                              // The most local permission (first to set) wins
 677                              // the only exception is CAP_PROHIBIT
 678                              if ($can === 0) {
 679                                  $can = $perm;
 680                              } elseif ($perm === $CAP_PROHIBIT) {
 681                                  $can = $perm;
 682                                  break;
 683                              }
 684                          }
 685                      }
 686                  }
 687                  // As we are dealing with a switchrole,
 688                  // we return _here_, do _not_ walk up 
 689                  // the hierarchy any further
 690                  if ($can < 1) {
 691                      if ($doanything) {
 692                          // didn't find it as an explicit cap,
 693                          // but maybe the user candoanything in this context...
 694                          return has_capability_in_accessdata('moodle/site:doanything', $context, $accessdata, false);
 695                      } else {
 696                          return false;
 697                      }
 698                  } else {
 699                      return true;
 700                  }
 701                  
 702              }
 703          }
 704      }
 705  
 706      //
 707      // Main loop for normal RAs
 708      // From the bottom up...
 709      //
 710      for ($n=$cc-1;$n>=0;$n--) {
 711          $ctxp = $contexts[$n];
 712          if (isset($accessdata['ra'][$ctxp])) {
 713              // Found role assignments on this leaf
 714              $ras = $accessdata['ra'][$ctxp];
 715  
 716              $rc          = count($ras);
 717              $ctxcan      = 0;
 718              $ctxcapdepth = 0;
 719              for ($rn=0;$rn<$rc;$rn++) {
 720                  $roleid  = (int)$ras[$rn];
 721                  $rolecan = 0;
 722                  $rolecapdepth = 0;
 723                  // Walk the path for capabilities
 724                  // from the bottom up...
 725                  for ($m=$cc-1;$m>=0;$m--) {
 726                      $capctxp = $contexts[$m];
 727                      // ignore some guest caps
 728                      // at base ra and rdef
 729                      if ($ignoreguest == $roleid
 730                          && $n === 0
 731                          && $m === 0
 732                          && isset($accessdata['rdef']["{$capctxp}:$roleid"]['moodle/legacy:guest'])
 733                          && $accessdata['rdef']["{$capctxp}:$roleid"]['moodle/legacy:guest'] > 0) {
 734                              continue;
 735                      }
 736                      if (isset($accessdata['rdef']["{$capctxp}:$roleid"][$capability])) {
 737                          $perm = (int)$accessdata['rdef']["{$capctxp}:$roleid"][$capability];
 738                          // The most local permission (first to set) wins
 739                          // the only exception is CAP_PROHIBIT
 740                          if ($rolecan === 0) {
 741                              $rolecan      = $perm;
 742                              $rolecapdepth = $m;
 743                          } elseif ($perm === $CAP_PROHIBIT) {
 744                              $rolecan      = $perm;
 745                              $rolecapdepth = $m;
 746                              break;
 747                          }
 748                      }
 749                  }
 750                  // Rules for RAs at the same context...
 751                  // - prohibits always wins
 752                  // - permissions at the same ctxlevel & capdepth are added together
 753                  // - deeper capdepth wins
 754                  if ($ctxcan === $CAP_PROHIBIT || $rolecan === $CAP_PROHIBIT) {
 755                      $ctxcan      = $CAP_PROHIBIT;
 756                      $ctxcapdepth = 0;
 757                  } elseif ($ctxcapdepth === $rolecapdepth) {
 758                      $ctxcan += $rolecan;
 759                  } elseif ($ctxcapdepth < $rolecapdepth) {
 760                      $ctxcan      = $rolecan;
 761                      $ctxcapdepth = $rolecapdepth;
 762                  } else { // ctxcaptdepth is deeper
 763                      // rolecap ignored
 764                  }
 765              }
 766              // The most local RAs with a defined
 767              // permission ($ctxcan) win, except
 768              // for CAP_PROHIBIT
 769              // NOTE: If we want the deepest RDEF to
 770              // win regardless of the depth of the RA,
 771              // change the elseif below to read
 772              // ($can === 0 || $capdepth < $ctxcapdepth) {
 773              if ($ctxcan === $CAP_PROHIBIT) {
 774                  $can = $ctxcan;
 775                  break;
 776              } elseif ($can === 0) { // see note above
 777                  $can      = $ctxcan;
 778                  $capdepth = $ctxcapdepth;
 779              }
 780          }
 781      }
 782  
 783      if ($can < 1) {
 784          if ($doanything) {
 785              // didn't find it as an explicit cap,
 786              // but maybe the user candoanything in this context...
 787              return has_capability_in_accessdata('moodle/site:doanything', $context, $accessdata, false);
 788          } else {
 789              return false;
 790          }
 791      } else {
 792          return true;
 793      }
 794  
 795  }
 796  
 797  function aggregate_roles_from_accessdata($context, $accessdata) {
 798  
 799      $path = $context->path;
 800  
 801      // build $contexts as a list of "paths" of the current
 802      // contexts and parents with the order top-to-bottom
 803      $contexts = array($path);
 804      while (preg_match('!^(/.+)/\d+$!', $path, $matches)) {
 805          $path = $matches[1];
 806          array_unshift($contexts, $path);
 807      }
 808  
 809      $cc = count($contexts);
 810  
 811      $roles = array();
 812      // From the bottom up...
 813      for ($n=$cc-1;$n>=0;$n--) {
 814          $ctxp = $contexts[$n];
 815          if (isset($accessdata['ra'][$ctxp]) && count($accessdata['ra'][$ctxp])) {
 816              // Found assignments on this leaf
 817              $addroles = $accessdata['ra'][$ctxp];
 818              $roles    = array_merge($roles, $addroles);
 819          }
 820      }
 821  
 822      return array_unique($roles);
 823  }
 824  
 825  /**
 826   * This is an easy to use function, combining has_capability() with require_course_login().
 827   * And will call those where needed.
 828   * 
 829   * It checks for a capability assertion being true.  If it isn't
 830   * then the page is terminated neatly with a standard error message.
 831   *
 832   * If the user is not logged in, or is using 'guest' access or other special "users,
 833   * it provides a logon prompt.
 834   *
 835   * @param string $capability - name of the capability
 836   * @param object $context - a context object (record from context table)
 837   * @param integer $userid - a userid number
 838   * @param bool $doanything - if false, ignore do anything
 839   * @param string $errorstring - an errorstring
 840   * @param string $stringfile - which stringfile to get it from
 841   */
 842  function require_capability($capability, $context, $userid=NULL, $doanything=true,
 843                              $errormessage='nopermissions', $stringfile='') {
 844  
 845      global $USER, $CFG;
 846  
 847      /* Empty $userid means current user, if the current user is not logged in,
 848       * then make sure they are (if needed).
 849       * Originally there was a check for loaded permissions - it is not needed here.
 850       * Context is now required parameter, the cached $CONTEXT was only hiding errors.
 851       */
 852      $errorlink = '';
 853  
 854      if (empty($userid)) {
 855          if ($context->contextlevel == CONTEXT_COURSE) {
 856              require_login($context->instanceid);
 857  
 858          } else if ($context->contextlevel == CONTEXT_MODULE) {
 859              if (!$cm = get_record('course_modules', 'id', $context->instanceid)) {
 860                  error('Incorrect module');
 861              }
 862              if (!$course = get_record('course', 'id', $cm->course)) {
 863                  error('Incorrect course.');
 864              }
 865              require_course_login($course, true, $cm);
 866              $errorlink = $CFG->wwwroot.'/course/view.php?id='.$cm->course;
 867  
 868          } else if ($context->contextlevel == CONTEXT_SYSTEM) {
 869              if (!empty($CFG->forcelogin)) {
 870                  require_login();
 871              }
 872  
 873          } else {
 874              require_login();
 875          }
 876      }
 877  
 878  /// OK, if they still don't have the capability then print a nice error message
 879  
 880      if (!has_capability($capability, $context, $userid, $doanything)) {
 881          $capabilityname = get_capability_string($capability);
 882          print_error($errormessage, $stringfile, $errorlink, $capabilityname);
 883      }
 884  }
 885  
 886  /**
 887   * Get an array of courses (with magic extra bits)
 888   * where the accessdata and in DB enrolments show
 889   * that the cap requested is available.
 890   *
 891   * The main use is for get_my_courses().
 892   *
 893   * Notes
 894   *
 895   * - $fields is an array of fieldnames to ADD
 896   *   so name the fields you really need, which will
 897   *   be added and uniq'd
 898   *
 899   * - the course records have $c->context which is a fully
 900   *   valid context object. Saves you a query per course!
 901   *
 902   * - the course records have $c->categorypath to make
 903   *   category lookups cheap
 904   *
 905   * - current implementation is split in -
 906   *
 907   *   - if the user has the cap systemwide, stupidly
 908   *     grab *every* course for a capcheck. This eats
 909   *     a TON of bandwidth, specially on large sites 
 910   *     with separate DBs...
 911   *
 912   *   - otherwise, fetch "likely" courses with a wide net
 913   *     that should get us _cheaply_ at least the courses we need, and some
 914   *     we won't - we get courses that...
 915   *      - are in a category where user has the cap
 916   *      - or where use has a role-assignment (any kind)
 917   *      - or where the course has an override on for this cap
 918   *
 919   *   - walk the courses recordset checking the caps oneach one
 920   *     the checks are all in memory and quite fast
 921   *     (though we could implement a specialised variant of the
 922   *     has_capability_in_accessdata() code to speed it up)
 923   *
 924   * @param string $capability - name of the capability
 925   * @param array  $accessdata - accessdata session array
 926   * @param bool   $doanything - if false, ignore do anything
 927   * @param string $sort - sorting fields - prefix each fieldname with "c."
 928   * @param array  $fields - additional fields you are interested in...
 929   * @param int    $limit  - set if you want to limit the number of courses
 930   * @return array $courses - ordered array of course objects - see notes above
 931   *
 932   */
 933  function get_user_courses_bycap($userid, $cap, $accessdata, $doanything, $sort='c.sortorder ASC', $fields=NULL, $limit=0) {
 934  
 935      global $CFG;
 936  
 937      // Slim base fields, let callers ask for what they need...
 938      $basefields = array('id', 'sortorder', 'shortname', 'idnumber');
 939  
 940      if (!is_null($fields)) {
 941          $fields = array_merge($basefields, $fields);
 942          $fields = array_unique($fields);
 943      } else {
 944          $fields = $basefields;
 945      }
 946      $coursefields = 'c.' .implode(',c.', $fields);
 947  
 948      $sort = trim($sort);
 949      if ($sort !== '') {
 950          $sort = "ORDER BY $sort";
 951      }
 952  
 953      $sysctx = get_context_instance(CONTEXT_SYSTEM);
 954      if (has_capability_in_accessdata($cap, $sysctx, $accessdata, $doanything)) {
 955          //
 956          // Apparently the user has the cap sitewide, so walk *every* course
 957          // (the cap checks are moderately fast, but this moves massive bandwidth w the db)
 958          // Yuck.
 959          //
 960          $sql = "SELECT $coursefields,
 961                         ctx.id AS ctxid, ctx.path AS ctxpath,
 962                         ctx.depth AS ctxdepth, ctx.contextlevel AS ctxlevel,
 963                         cc.path AS categorypath
 964                  FROM {$CFG->prefix}course c
 965                  JOIN {$CFG->prefix}course_categories cc
 966                    ON c.category=cc.id
 967                  JOIN {$CFG->prefix}context ctx 
 968                    ON (c.id=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE.")
 969                  $sort ";
 970          $rs = get_recordset_sql($sql);
 971      } else {
 972          //
 973          // narrow down where we have the caps to a few contexts
 974          // this will be a combination of
 975          // - courses    where user has an explicit enrolment
 976          // - courses    that have an override (any status) on that capability
 977          // - categories where user has the rights (granted status) on that capability
 978          //
 979          $sql = "SELECT ctx.*
 980                  FROM   {$CFG->prefix}context ctx
 981                  WHERE  ctx.contextlevel=".CONTEXT_COURSECAT."
 982                  ORDER BY ctx.depth";
 983          $rs = get_recordset_sql($sql);
 984          $catpaths = array();
 985          while ($catctx = rs_fetch_next_record($rs)) {
 986              if ($catctx->path != '' 
 987                  && has_capability_in_accessdata($cap, $catctx, $accessdata, $doanything)) {
 988                  $catpaths[] = $catctx->path;
 989              }
 990          }
 991          rs_close($rs);
 992          $catclause = '';
 993          if (count($catpaths)) {
 994              $cc = count($catpaths);
 995              for ($n=0;$n<$cc;$n++) {
 996                  $catpaths[$n] = "ctx.path LIKE '{$catpaths[$n]}/%'";
 997              }
 998              $catclause = 'WHERE (' . implode(' OR ', $catpaths) .')';
 999          }
1000          unset($catpaths);
1001  
1002          $capany = '';
1003          if ($doanything) {
1004              $capany = " OR rc.capability='moodle/site:doanything'";
1005          }
1006  
1007          /// UNION 3 queries:
1008          /// - user role assignments in courses
1009          /// - user capability (override - any status) in courses
1010          /// - user right (granted status) in categories (optionally executed)
1011          /// Enclosing the 3-UNION into an inline_view to avoid column names conflict and making the ORDER BY cross-db
1012          /// and to allow selection of TEXT columns in the query (MSSQL and Oracle limitation). MDL-16209
1013          $sql = "
1014              SELECT $coursefields, ctxid, ctxpath, ctxdepth, ctxlevel, categorypath 
1015                FROM (
1016                      SELECT c.id,
1017                             ctx.id AS ctxid, ctx.path AS ctxpath,
1018                             ctx.depth AS ctxdepth, ctx.contextlevel AS ctxlevel,
1019                             cc.path AS categorypath
1020                      FROM {$CFG->prefix}course c
1021                      JOIN {$CFG->prefix}course_categories cc
1022                        ON c.category=cc.id
1023                      JOIN {$CFG->prefix}context ctx
1024                        ON (c.id=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE.")
1025                      JOIN {$CFG->prefix}role_assignments ra
1026                        ON (ra.contextid=ctx.id AND ra.userid=$userid)
1027                      UNION
1028                      SELECT c.id,
1029                             ctx.id AS ctxid, ctx.path AS ctxpath,
1030                             ctx.depth AS ctxdepth, ctx.contextlevel AS ctxlevel,
1031                             cc.path AS categorypath
1032                      FROM {$CFG->prefix}course c
1033                      JOIN {$CFG->prefix}course_categories cc
1034                        ON c.category=cc.id
1035                      JOIN {$CFG->prefix}context ctx
1036                        ON (c.id=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE.")
1037                      JOIN {$CFG->prefix}role_capabilities rc
1038                        ON (rc.contextid=ctx.id AND (rc.capability='$cap' $capany)) ";
1039  
1040          if (!empty($catclause)) { /// If we have found the right in categories, add child courses here too
1041              $sql .= "
1042                      UNION
1043                      SELECT c.id,
1044                             ctx.id AS ctxid, ctx.path AS ctxpath,
1045                             ctx.depth AS ctxdepth, ctx.contextlevel AS ctxlevel,
1046                             cc.path AS categorypath
1047                      FROM {$CFG->prefix}course c
1048                      JOIN {$CFG->prefix}course_categories cc
1049                        ON c.category=cc.id
1050                      JOIN {$CFG->prefix}context ctx
1051                        ON (c.id=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE.")
1052                      $catclause";
1053          }
1054  
1055      /// Close the inline_view and join with courses table to get requested $coursefields
1056          $sql .= "
1057                  ) inline_view
1058                  INNER JOIN {$CFG->prefix}course c
1059                      ON inline_view.id = c.id";
1060  
1061      /// To keep cross-db we need to strip any prefix in the ORDER BY clause for queries using UNION
1062          $sql .= "
1063                  " . preg_replace('/[a-z]+\./i', '', $sort); /// Add ORDER BY clause
1064  
1065          $rs = get_recordset_sql($sql);
1066      }
1067  
1068  /// Confirm rights (granted capability) for each course returned
1069      $courses = array();
1070      $cc = 0; // keep count
1071      while ($c = rs_fetch_next_record($rs)) {
1072          // build the context obj
1073          $c = make_context_subobj($c);
1074  
1075          if (has_capability_in_accessdata($cap, $c->context, $accessdata, $doanything)) {
1076              $courses[] = $c;
1077              if ($limit > 0 && $cc++ > $limit) {
1078                  break;
1079              }
1080          }
1081      }
1082      rs_close($rs);
1083      return $courses;
1084  }
1085  
1086  
1087  /**
1088   * It will return a nested array showing role assignments
1089   * all relevant role capabilities for the user at
1090   * site/metacourse/course_category/course levels
1091   *
1092   * We do _not_ delve deeper than courses because the number of
1093   * overrides at the module/block levels is HUGE.
1094   *
1095   * [ra]   => [/path/] = array(roleid, roleid)
1096   * [rdef] => [/path/:roleid][capability]=permission
1097   * [loaded] => array('/path', '/path')
1098   *
1099   * @param $userid integer - the id of the user
1100   *
1101   */
1102  function get_user_access_sitewide($userid) {
1103  
1104      global $CFG;
1105  
1106      // this flag has not been set!
1107      // (not clean install, or upgraded successfully to 1.7 and up)
1108      if (empty($CFG->rolesactive)) {
1109          return false;
1110      }
1111  
1112      /* Get in 3 cheap DB queries...
1113       * - role assignments - with role_caps
1114       * - relevant role caps
1115       *   - above this user's RAs
1116       *   - below this user's RAs - limited to course level
1117       */
1118  
1119      $accessdata           = array(); // named list
1120      $accessdata['ra']     = array();
1121      $accessdata['rdef']   = array();
1122      $accessdata['loaded'] = array();
1123  
1124      $sitectx = get_system_context();
1125      $base = '/'.$sitectx->id;
1126  
1127      //
1128      // Role assignments - and any rolecaps directly linked
1129      // because it's cheap to read rolecaps here over many
1130      // RAs
1131      //
1132      $sql = "SELECT ctx.path, ra.roleid, rc.capability, rc.permission
1133              FROM {$CFG->prefix}role_assignments ra
1134              JOIN {$CFG->prefix}context ctx
1135                 ON ra.contextid=ctx.id
1136              LEFT OUTER JOIN {$CFG->prefix}role_capabilities rc
1137                 ON (rc.roleid=ra.roleid AND rc.contextid=ra.contextid)
1138              WHERE ra.userid = $userid AND ctx.contextlevel <= ".CONTEXT_COURSE."
1139              ORDER BY ctx.depth, ctx.path, ra.roleid";
1140      $rs = get_recordset_sql($sql);
1141      //
1142      // raparents collects paths & roles we need to walk up
1143      // the parenthood to build the rdef
1144      //
1145      // the array will bulk up a bit with dups
1146      // which we'll later clear up
1147      //
1148      $raparents = array();
1149      $lastseen  = '';
1150      if ($rs) {
1151          while ($ra = rs_fetch_next_record($rs)) {
1152              // RAs leafs are arrays to support multi
1153              // role assignments...
1154              if (!isset($accessdata['ra'][$ra->path])) {
1155                  $accessdata['ra'][$ra->path] = array();
1156              }
1157              // only add if is not a repeat caused
1158              // by capability join...
1159              // (this check is cheaper than in_array())
1160              if ($lastseen !== $ra->path.':'.$ra->roleid) {
1161                  $lastseen = $ra->path.':'.$ra->roleid;
1162                  array_push($accessdata['ra'][$ra->path], $ra->roleid);
1163                  $parentids = explode('/', $ra->path);
1164                  array_shift($parentids); // drop empty leading "context"
1165                  array_pop($parentids);   // drop _this_ context
1166  
1167                  if (isset($raparents[$ra->roleid])) {
1168                      $raparents[$ra->roleid] = array_merge($raparents[$ra->roleid],
1169                                                            $parentids);
1170                  } else {
1171                      $raparents[$ra->roleid] = $parentids;
1172                  }
1173              }
1174              // Always add the roleded
1175              if (!empty($ra->capability)) {
1176                  $k = "{$ra->path}:{$ra->roleid}";
1177                  $accessdata['rdef'][$k][$ra->capability] = $ra->permission;
1178              }
1179          }
1180          unset($ra);
1181          rs_close($rs);
1182      }
1183  
1184      // Walk up the tree to grab all the roledefs
1185      // of interest to our user...
1186      // NOTE: we use a series of IN clauses here - which
1187      // might explode on huge sites with very convoluted nesting of
1188      // categories... - extremely unlikely that the number of categories
1189      // and roletypes is so large that we hit the limits of IN()
1190      $clauses = array();
1191      foreach ($raparents as $roleid=>$contexts) {
1192          $contexts = implode(',', array_unique($contexts));
1193          if ($contexts ==! '') {
1194              $clauses[] = "(roleid=$roleid AND contextid IN ($contexts))";
1195          }
1196      }
1197      $clauses = implode(" OR ", $clauses);
1198      if ($clauses !== '') {
1199          $sql = "SELECT ctx.path, rc.roleid, rc.capability, rc.permission
1200                  FROM {$CFG->prefix}role_capabilities rc
1201                  JOIN {$CFG->prefix}context ctx
1202                    ON rc.contextid=ctx.id
1203                  WHERE $clauses
1204                  ORDER BY ctx.depth ASC, ctx.path DESC, rc.roleid ASC ";
1205  
1206          $rs = get_recordset_sql($sql);
1207          unset($clauses);
1208  
1209          if ($rs) {
1210              while ($rd = rs_fetch_next_record($rs)) {
1211                  $k = "{$rd->path}:{$rd->roleid}";
1212                  $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
1213              }
1214              unset($rd);
1215              rs_close($rs);
1216          }
1217      }
1218  
1219      //
1220      // Overrides for the role assignments IN SUBCONTEXTS
1221      // (though we still do _not_ go below the course level.
1222      //
1223      // NOTE that the JOIN w sctx is with 3-way triangulation to
1224      // catch overrides to the applicable role in any subcontext, based
1225      // on the path field of the parent.
1226      //
1227      $sql = "SELECT sctx.path, ra.roleid,
1228                     ctx.path AS parentpath,
1229                     rco.capability, rco.permission
1230              FROM {$CFG->prefix}role_assignments ra
1231              JOIN {$CFG->prefix}context ctx
1232                ON ra.contextid=ctx.id
1233              JOIN {$CFG->prefix}context sctx
1234                ON (sctx.path LIKE " . sql_concat('ctx.path',"'/%'"). " )
1235              JOIN {$CFG->prefix}role_capabilities rco
1236                ON (rco.roleid=ra.roleid AND rco.contextid=sctx.id)
1237              WHERE ra.userid = $userid
1238                    AND sctx.contextlevel <= ".CONTEXT_COURSE."
1239              ORDER BY sctx.depth, sctx.path, ra.roleid";
1240  
1241      $rs = get_recordset_sql($sql);
1242      if ($rs) {
1243          while ($rd = rs_fetch_next_record($rs)) {
1244              $k = "{$rd->path}:{$rd->roleid}";
1245              $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
1246          }
1247          unset($rd);
1248          rs_close($rs);
1249      }
1250      return $accessdata;
1251  }
1252  
1253  /**
1254   * It add to the access ctrl array the data
1255   * needed by a user for a given context
1256   *
1257   * @param $userid  integer - the id of the user
1258   * @param $context context obj - needs path!
1259   * @param $accessdata array  accessdata array
1260   */
1261  function load_subcontext($userid, $context, &$accessdata) {
1262  
1263      global $CFG;
1264  
1265  
1266  
1267      /* Get the additional RAs and relevant rolecaps
1268       * - role assignments - with role_caps
1269       * - relevant role caps
1270       *   - above this user's RAs
1271       *   - below this user's RAs - limited to course level
1272       */
1273  
1274      $base = "/" . SYSCONTEXTID;
1275  
1276      //
1277      // Replace $context with the target context we will
1278      // load. Normally, this will be a course context, but
1279      // may be a different top-level context.
1280      //
1281      // We have 3 cases
1282      //
1283      // - Course
1284      // - BLOCK/PERSON/USER/COURSE(sitecourse) hanging from SYSTEM
1285      // - BLOCK/MODULE/GROUP hanging from a course
1286      //
1287      // For course contexts, we _already_ have the RAs
1288      // but the cost of re-fetching is minimal so we don't care.
1289      //
1290      if ($context->contextlevel !== CONTEXT_COURSE 
1291          && $context->path !== "$base/{$context->id}") {
1292          // Case BLOCK/MODULE/GROUP hanging from a course
1293          // Assumption: the course _must_ be our parent
1294          // If we ever see stuff nested further this needs to
1295          // change to do 1 query over the exploded path to
1296          // find out which one is the course
1297          $courses = explode('/',get_course_from_path($context->path));
1298          $targetid = array_pop($courses);
1299          $context = get_context_instance_by_id($targetid);
1300                                   
1301      }
1302  
1303      //
1304      // Role assignments in the context and below
1305      //
1306      $sql = "SELECT ctx.path, ra.roleid
1307              FROM {$CFG->prefix}role_assignments ra
1308              JOIN {$CFG->prefix}context ctx
1309                 ON ra.contextid=ctx.id
1310              WHERE ra.userid = $userid
1311                    AND (ctx.path = '{$context->path}' OR ctx.path LIKE '{$context->path}/%')
1312              ORDER BY ctx.depth, ctx.path, ra.roleid";
1313      $rs = get_recordset_sql($sql);
1314  
1315      //
1316      // Read in the RAs, preventing duplicates
1317      //
1318      $localroles = array();
1319      $lastseen  = '';
1320      while ($ra = rs_fetch_next_record($rs)) {
1321          if (!isset($accessdata['ra'][$ra->path])) {
1322              $accessdata['ra'][$ra->path] = array();
1323          }
1324          // only add if is not a repeat caused
1325          // by capability join...
1326          // (this check is cheaper than in_array())
1327          if ($lastseen !== $ra->path.':'.$ra->roleid) {
1328              $lastseen = $ra->path.':'.$ra->roleid;
1329              array_push($accessdata['ra'][$ra->path], $ra->roleid);
1330              array_push($localroles,           $ra->roleid);
1331          }
1332      }
1333      rs_close($rs);
1334  
1335      //
1336      // Walk up and down the tree to grab all the roledefs
1337      // of interest to our user...
1338      //
1339      // NOTES
1340      // - we use IN() but the number of roles is very limited.
1341      //
1342      $courseroles    = aggregate_roles_from_accessdata($context, $accessdata);
1343  
1344      // Do we have any interesting "local" roles?
1345      $localroles = array_diff($localroles,$courseroles); // only "new" local roles
1346      $wherelocalroles='';
1347      if (count($localroles)) {
1348          // Role defs for local roles in 'higher' contexts...
1349          $contexts = substr($context->path, 1); // kill leading slash
1350          $contexts = str_replace('/', ',', $contexts);
1351          $localroleids = implode(',',$localroles);
1352          $wherelocalroles="OR (rc.roleid IN ({$localroleids}) 
1353                                AND ctx.id IN ($contexts))" ;
1354      }
1355  
1356      // We will want overrides for all of them
1357      $whereroles = '';
1358      if ($roleids  = implode(',',array_merge($courseroles,$localroles))) {
1359          $whereroles = "rc.roleid IN ($roleids) AND";
1360      }
1361      $sql = "SELECT ctx.path, rc.roleid, rc.capability, rc.permission
1362              FROM {$CFG->prefix}role_capabilities rc
1363              JOIN {$CFG->prefix}context ctx
1364               ON rc.contextid=ctx.id
1365              WHERE ($whereroles
1366                      (ctx.id={$context->id} OR ctx.path LIKE '{$context->path}/%'))
1367                      $wherelocalroles
1368              ORDER BY ctx.depth ASC, ctx.path DESC, rc.roleid ASC ";
1369  
1370      $newrdefs = array();
1371      if ($rs = get_recordset_sql($sql)) {
1372          while ($rd = rs_fetch_next_record($rs)) {
1373              $k = "{$rd->path}:{$rd->roleid}";
1374              if (!array_key_exists($k, $newrdefs)) {
1375                  $newrdefs[$k] = array();
1376              }
1377              $newrdefs[$k][$rd->capability] = $rd->permission;
1378          }
1379          rs_close($rs);
1380      } else {
1381          debugging('Bad SQL encountered!');
1382      }
1383  
1384      compact_rdefs($newrdefs);
1385      foreach ($newrdefs as $key=>$value) {
1386          $accessdata['rdef'][$key] =& $newrdefs[$key];
1387      }
1388  
1389      // error_log("loaded {$context->path}");
1390      $accessdata['loaded'][] = $context->path;
1391  }
1392  
1393  /**
1394   * It add to the access ctrl array the data
1395   * needed by a role for a given context.
1396   *
1397   * The data is added in the rdef key.
1398   *
1399   * This role-centric function is useful for role_switching
1400   * and to get an overview of what a role gets under a
1401   * given context and below...
1402   *
1403   * @param $roleid  integer - the id of the user
1404   * @param $context context obj - needs path!
1405   * @param $accessdata      accessdata array
1406   *
1407   */
1408  function get_role_access_bycontext($roleid, $context, $accessdata=NULL) {
1409  
1410      global $CFG;
1411  
1412      /* Get the relevant rolecaps into rdef
1413       * - relevant role caps
1414       *   - at ctx and above
1415       *   - below this ctx
1416       */
1417  
1418      if (is_null($accessdata)) {
1419          $accessdata           = array(); // named list
1420          $accessdata['ra']     = array();
1421          $accessdata['rdef']   = array();
1422          $accessdata['loaded'] = array();
1423      }
1424      
1425      $contexts = substr($context->path, 1); // kill leading slash
1426      $contexts = str_replace('/', ',', $contexts);
1427  
1428      //
1429      // Walk up and down the tree to grab all the roledefs
1430      // of interest to our role...
1431      //
1432      // NOTE: we use an IN clauses here - which
1433      // might explode on huge sites with very convoluted nesting of
1434      // categories... - extremely unlikely that the number of nested
1435      // categories is so large that we hit the limits of IN()
1436      //
1437      $sql = "SELECT ctx.path, rc.capability, rc.permission
1438              FROM {$CFG->prefix}role_capabilities rc
1439              JOIN {$CFG->prefix}context ctx
1440                ON rc.contextid=ctx.id
1441              WHERE rc.roleid=$roleid AND
1442                    ( ctx.id IN ($contexts) OR 
1443                      ctx.path LIKE '{$context->path}/%' )
1444              ORDER BY ctx.depth ASC, ctx.path DESC, rc.roleid ASC ";
1445  
1446      $rs = get_recordset_sql($sql);
1447      while ($rd = rs_fetch_next_record($rs)) {
1448          $k = "{$rd->path}:{$roleid}";
1449          $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
1450      }
1451      rs_close($rs);
1452  
1453      return $accessdata;
1454  }
1455  
1456  /**
1457   * Load accessdata for a user
1458   * into the $ACCESS global
1459   *
1460   * Used by has_capability() - but feel free
1461   * to call it if you are about to run a BIG 
1462   * cron run across a bazillion users.
1463   *
1464   */ 
1465  function load_user_accessdata($userid) {
1466      global $ACCESS,$CFG;
1467  
1468      $base = '/'.SYSCONTEXTID;
1469  
1470      $accessdata = get_user_access_sitewide($userid);
1471      $frontpagecontext = get_context_instance(CONTEXT_COURSE, SITEID); 
1472      //
1473      // provide "default role" & set 'dr'
1474      //
1475      if (!empty($CFG->defaultuserroleid)) {
1476          $accessdata = get_role_access($CFG->defaultuserroleid, $accessdata);
1477          if (!isset($accessdata['ra'][$base])) {
1478              $accessdata['ra'][$base] = array($CFG->defaultuserroleid);
1479          } else {
1480              array_push($accessdata['ra'][$base], $CFG->defaultuserroleid);
1481          }
1482          $accessdata['dr'] = $CFG->defaultuserroleid;
1483      }
1484  
1485      //
1486      // provide "default frontpage role"
1487      //
1488      if (!empty($CFG->defaultfrontpageroleid)) {
1489          $base = '/'. SYSCONTEXTID .'/'. $frontpagecontext->id;
1490          $accessdata = get_default_frontpage_role_access($CFG->defaultfrontpageroleid, $accessdata);
1491          if (!isset($accessdata['ra'][$base])) {
1492              $accessdata['ra'][$base] = array($CFG->defaultfrontpageroleid);
1493          } else {
1494              array_push($accessdata['ra'][$base], $CFG->defaultfrontpageroleid);
1495          }
1496      }
1497      // for dirty timestamps in cron
1498      $accessdata['time'] = time();
1499  
1500      $ACCESS[$userid] = $accessdata;
1501      compact_rdefs($ACCESS[$userid]['rdef']);
1502  
1503      return true;
1504  }
1505  
1506  /**
1507   * Use shared copy of role definistions stored in $RDEFS;
1508   * @param array $rdefs array of role definitions in contexts
1509   */
1510  function compact_rdefs(&$rdefs) {
1511      global $RDEFS;
1512  
1513      /*
1514       * This is a basic sharing only, we could also
1515       * use md5 sums of values. The main purpose is to
1516       * reduce mem in cron jobs - many users in $ACCESS array.
1517       */
1518  
1519      foreach ($rdefs as $key => $value) {
1520          if (!array_key_exists($key, $RDEFS)) {
1521              $RDEFS[$key] = $rdefs[$key];
1522          }
1523          $rdefs[$key] =& $RDEFS[$key];
1524      }
1525  }
1526  
1527  /**
1528   *  A convenience function to completely load all the capabilities 
1529   *  for the current user.   This is what gets called from complete_user_login()
1530   *  for example. Call it only _after_ you've setup $USER and called
1531   *  check_enrolment_plugins();
1532   *
1533   */
1534  function load_all_capabilities() {
1535      global $USER, $CFG, $DIRTYCONTEXTS;
1536  
1537      $base = '/'.SYSCONTEXTID;
1538  
1539      if (isguestuser()) {
1540          $guest = get_guest_role();
1541  
1542          // Load the rdefs
1543          $USER->access = get_role_access($guest->id);
1544          // Put the ghost enrolment in place...
1545          $USER->access['ra'][$base] = array($guest->id);
1546  
1547  
1548      } else if (isloggedin()) {
1549  
1550          $accessdata = get_user_access_sitewide($USER->id);
1551  
1552          //
1553          // provide "default role" & set 'dr'
1554          //
1555          if (!empty($CFG->defaultuserroleid)) {
1556              $accessdata = get_role_access($CFG->defaultuserroleid, $accessdata);
1557              if (!isset($accessdata['ra'][$base])) {
1558                  $accessdata['ra'][$base] = array($CFG->defaultuserroleid);
1559              } else {
1560                  array_push($accessdata['ra'][$base], $CFG->defaultuserroleid);
1561              }
1562              $accessdata['dr'] = $CFG->defaultuserroleid;
1563          }
1564  
1565          $frontpagecontext = get_context_instance(CONTEXT_COURSE, SITEID);
1566  
1567          //
1568          // provide "default frontpage role"
1569          //
1570          if (!empty($CFG->defaultfrontpageroleid)) {
1571              $base = '/'. SYSCONTEXTID .'/'. $frontpagecontext->id;
1572              $accessdata = get_default_frontpage_role_access($CFG->defaultfrontpageroleid, $accessdata);
1573              if (!isset($accessdata['ra'][$base])) {
1574                  $accessdata['ra'][$base] = array($CFG->defaultfrontpageroleid);
1575              } else {
1576                  array_push($accessdata['ra'][$base], $CFG->defaultfrontpageroleid);
1577              }
1578          } 
1579          $USER->access = $accessdata;
1580      
1581      } else if (!empty($CFG->notloggedinroleid)) {
1582          $USER->access = get_role_access($CFG->notloggedinroleid);
1583          $USER->access['ra'][$base] = array($CFG->notloggedinroleid);
1584      }
1585  
1586      // Timestamp to read dirty context timestamps later
1587      $USER->access['time'] = time();
1588      $DIRTYCONTEXTS = array();
1589  
1590      // Clear to force a refresh
1591      unset($USER->mycourses);
1592  }
1593  
1594  /**
1595   * A convenience function to completely reload all the capabilities 
1596   * for the current user when roles have been updated in a relevant
1597   * context -- but PRESERVING switchroles and loginas. 
1598   *
1599   * That is - completely transparent to the user.
1600   * 
1601   * Note: rewrites $USER->access completely.
1602   *
1603   */
1604  function reload_all_capabilities() {
1605      global $USER,$CFG;
1606  
1607      // error_log("reloading");
1608      // copy switchroles
1609      $sw = array();
1610      if (isset($USER->access['rsw'])) {
1611          $sw = $USER->access['rsw'];
1612          // error_log(print_r($sw,1));
1613      }
1614  
1615      unset($USER->access);
1616      unset($USER->mycourses);
1617      
1618      load_all_capabilities();
1619  
1620      foreach ($sw as $path => $roleid) {
1621          $context = get_record('context', 'path', $path);
1622          role_switch($roleid, $context);
1623      }
1624  
1625  }
1626  
1627  /*
1628   * Adds a temp role to an accessdata array.
1629   *
1630   * Useful for the "temporary guest" access
1631   * we grant to logged-in users.
1632   *
1633   * Note - assumes a course context!
1634   *
1635   */
1636  function load_temp_role($context, $roleid, $accessdata) {
1637  
1638      global $CFG;
1639  
1640      //
1641      // Load rdefs for the role in -
1642      // - this context
1643      // - all the parents
1644      // - and below - IOWs overrides...
1645      //
1646      
1647      // turn the path into a list of context ids
1648      $contexts = substr($context->path, 1); // kill leading slash
1649      $contexts = str_replace('/', ',', $contexts);
1650  
1651      $sql = "SELECT ctx.path,
1652                     rc.capability, rc.permission
1653              FROM {$CFG->prefix}context ctx
1654              JOIN {$CFG->prefix}role_capabilities rc
1655                ON rc.contextid=ctx.id
1656              WHERE (ctx.id IN ($contexts)
1657                     OR ctx.path LIKE '{$context->path}/%')
1658                    AND rc.roleid = {$roleid}
1659              ORDER BY ctx.depth, ctx.path";
1660      $rs = get_recordset_sql($sql);
1661      while ($rd = rs_fetch_next_record($rs)) {
1662          $k = "{$rd->path}:{$roleid}";
1663          $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
1664      }
1665      rs_close($rs);
1666  
1667      //
1668      // Say we loaded everything for the course context
1669      // - which we just did - if the user gets a proper
1670      // RA in this session, this data will need to be reloaded,
1671      // but that is handled by the complete accessdata reload
1672      //
1673      array_push($accessdata['loaded'], $context->path);
1674  
1675      //
1676      // Add the ghost RA
1677      //
1678      if (isset($accessdata['ra'][$context->path])) {
1679          array_push($accessdata['ra'][$context->path], $roleid);
1680      } else {
1681          $accessdata['ra'][$context->path] = array($roleid);
1682      }
1683  
1684      return $accessdata;
1685  }
1686  
1687  
1688  /**
1689   * Check all the login enrolment information for the given user object
1690   * by querying the enrolment plugins
1691   */
1692  function check_enrolment_plugins(&$user) {
1693      global $CFG;
1694  
1695      static $inprogress;  // To prevent this function being called more than once in an invocation
1696  
1697      if (!empty($inprogress[$user->id])) {
1698          return;
1699      }
1700  
1701      $inprogress[$user->id] = true;  // Set the flag
1702  
1703      require_once($CFG->dirroot .'/enrol/enrol.class.php');
1704  
1705      if (!($plugins = explode(',', $CFG->enrol_plugins_enabled))) {
1706          $plugins = array($CFG->enrol);
1707      }
1708  
1709      foreach ($plugins as $plugin) {
1710          $enrol = enrolment_factory::factory($plugin);
1711          if (method_exists($enrol, 'setup_enrolments')) {  /// Plugin supports Roles (Moodle 1.7 and later)
1712              $enrol->setup_enrolments($user);
1713          } else {                                          /// Run legacy enrolment methods
1714              if (method_exists($enrol, 'get_student_courses')) {
1715                  $enrol->get_student_courses($user);
1716              }
1717              if (method_exists($enrol, 'get_teacher_courses')) {
1718                  $enrol->get_teacher_courses($user);
1719              }
1720  
1721          /// deal with $user->students and $user->teachers stuff
1722              unset($user->student);
1723              unset($user->teacher);
1724          }
1725          unset($enrol);
1726      }
1727  
1728      unset($inprogress[$user->id]);  // Unset the flag
1729  }
1730  
1731  /**
1732   * Installs the roles system.
1733   * This function runs on a fresh install as well as on an upgrade from the old
1734   * hard-coded student/teacher/admin etc. roles to the new roles system.
1735   */
1736  function moodle_install_roles() {
1737  
1738      global $CFG, $db;
1739  
1740  /// Create a system wide context for assignemnt.
1741      $systemcontext = $context = get_context_instance(CONTEXT_SYSTEM);
1742  
1743  
1744  /// Create default/legacy roles and capabilities.
1745  /// (1 legacy capability per legacy role at system level).
1746  
1747      $adminrole          = create_role(addslashes(get_string('administrator')), 'admin',
1748                                        addslashes(get_string('administratordescription')), 'moodle/legacy:admin');
1749      $coursecreatorrole  = create_role(addslashes(get_string('coursecreators')), 'coursecreator',
1750                                        addslashes(get_string('coursecreatorsdescription')), 'moodle/legacy:coursecreator');
1751      $editteacherrole    = create_role(addslashes(get_string('defaultcourseteacher')), 'editingteacher',
1752                                        addslashes(get_string('defaultcourseteacherdescription')), 'moodle/legacy:editingteacher');
1753      $noneditteacherrole = create_role(addslashes(get_string('noneditingteacher')), 'teacher',
1754                                        addslashes(get_string('noneditingteacherdescription')), 'moodle/legacy:teacher');
1755      $studentrole        = create_role(addslashes(get_string('defaultcoursestudent')), 'student',
1756                                        addslashes(get_string('defaultcoursestudentdescription')), 'moodle/legacy:student');
1757      $guestrole          = create_role(addslashes(get_string('guest')), 'guest',
1758                                        addslashes(get_string('guestdescription')), 'moodle/legacy:guest');
1759      $userrole           = create_role(addslashes(get_string('authenticateduser')), 'user',
1760                                        addslashes(get_string('authenticateduserdescription')), 'moodle/legacy:user');
1761  
1762  /// Now is the correct moment to install capabilities - after creation of legacy roles, but before assigning of roles
1763  
1764      if (!assign_capability('moodle/site:doanything', CAP_ALLOW, $adminrole, $systemcontext->id)) {
1765          error('Could not assign moodle/site:doanything to the admin role');
1766      }
1767      if (!update_capabilities()) {
1768          error('Had trouble upgrading the core capabilities for the Roles System');
1769      }
1770  
1771  /// Look inside user_admin, user_creator, user_teachers, user_students and
1772  /// assign above new roles. If a user has both teacher and student role,
1773  /// only teacher role is assigned. The assignment should be system level.
1774  
1775      $dbtables = $db->MetaTables('TABLES');
1776  
1777  /// Set up the progress bar
1778  
1779      $usertables = array('user_admins', 'user_coursecreators', 'user_teachers', 'user_students');
1780  
1781      $totalcount = $progresscount = 0;
1782      foreach ($usertables as $usertable) {
1783          if (in_array($CFG->prefix.$usertable, $dbtables)) {
1784               $totalcount += count_records($usertable);
1785          }
1786      }
1787  
1788      print_progress(0, $totalcount, 5, 1, 'Processing role assignments');
1789  
1790  /// Upgrade the admins.
1791  /// Sort using id ASC, first one is primary admin.
1792  
1793      if (in_array($CFG->prefix.'user_admins', $dbtables)) {
1794          if ($rs = get_recordset_sql('SELECT * from '.$CFG->prefix.'user_admins ORDER BY ID ASC')) {
1795              while ($admin = rs_fetch_next_record($rs)) {
1796                  role_assign($adminrole, $admin->userid, 0, $systemcontext->id);
1797                  $progresscount++;
1798                  print_progress($progresscount, $totalcount, 5, 1, 'Processing role assignments');
1799              }
1800              rs_close($rs);
1801          }
1802      } else {
1803          // This is a fresh install.
1804      }
1805  
1806  
1807  /// Upgrade course creators.
1808      if (in_array($CFG->prefix.'user_coursecreators', $dbtables)) {
1809          if ($rs = get_recordset('user_coursecreators')) {
1810              while ($coursecreator = rs_fetch_next_record($rs)) {
1811                  role_assign($coursecreatorrole, $coursecreator->userid, 0, $systemcontext->id);
1812                  $progresscount++;
1813                  print_progress($progresscount, $totalcount, 5, 1, 'Processing role assignments');
1814              }
1815              rs_close($rs);
1816          }
1817      }
1818  
1819  
1820  /// Upgrade editting teachers and non-editting teachers.
1821      if (in_array($CFG->prefix.'user_teachers', $dbtables)) {
1822          if ($rs = get_recordset('user_teachers')) {
1823              while ($teacher = rs_fetch_next_record($rs)) {
1824  
1825                  // removed code here to ignore site level assignments
1826                  // since the contexts are separated now
1827  
1828                  // populate the user_lastaccess table
1829                  $access = new object();
1830                  $access->timeaccess = $teacher->timeaccess;
1831                  $access->userid = $teacher->userid;
1832                  $access->courseid = $teacher->course;
1833                  insert_record('user_lastaccess', $access);
1834  
1835                  // assign the default student role
1836                  $coursecontext = get_context_instance(CONTEXT_COURSE, $teacher->course); // needs cache
1837                  // hidden teacher
1838                  if ($teacher->authority == 0) {
1839                      $hiddenteacher = 1;
1840                  } else {
1841                      $hiddenteacher = 0;
1842                  }
1843  
1844                  if ($teacher->editall) { // editting teacher
1845                      role_assign($editteacherrole, $teacher->userid, 0, $coursecontext->id, $teacher->timestart, $teacher->timeend, $hiddenteacher, $teacher->enrol, $teacher->timemodified);
1846                  } else {
1847                      role_assign($noneditteacherrole, $teacher->userid, 0, $coursecontext->id, $teacher->timestart, $teacher->timeend, $hiddenteacher, $teacher->enrol, $teacher->timemodified);
1848                  }
1849                  $progresscount++;
1850                  print_progress($progresscount, $totalcount, 5, 1, 'Processing role assignments');
1851              }
1852              rs_close($rs);
1853          }
1854      }
1855  
1856  
1857  /// Upgrade students.
1858      if (in_array($CFG->prefix.'user_students', $dbtables)) {
1859          if ($rs = get_recordset('user_students')) {
1860              while ($student = rs_fetch_next_record($rs)) {
1861  
1862                  // populate the user_lastaccess table
1863                  $access = new object;
1864                  $access->timeaccess = $student->timeaccess;
1865                  $access->userid = $student->userid;
1866                  $access->courseid = $student->course;
1867                  insert_record('user_lastaccess', $access);
1868  
1869                  // assign the default student role
1870                  $coursecontext = get_context_instance(CONTEXT_COURSE, $student->course);
1871                  role_assign($studentrole, $student->userid, 0, $coursecontext->id, $student->timestart, $student->timeend, 0, $student->enrol, $student->time);
1872                  $progresscount++;
1873                  print_progress($progresscount, $totalcount, 5, 1, 'Processing role assignments');
1874              }
1875              rs_close($rs);
1876          }
1877      }
1878  
1879  
1880  /// Upgrade guest (only 1 entry).
1881      if ($guestuser = get_record('user', 'username', 'guest')) {
1882          role_assign($guestrole, $guestuser->id, 0, $systemcontext->id);
1883      }
1884      print_progress($totalcount, $totalcount, 5, 1, 'Processing role assignments');
1885  
1886  
1887  /// Insert the correct records for legacy roles
1888      allow_assign($adminrole, $adminrole);
1889      allow_assign($adminrole, $coursecreatorrole);
1890      allow_assign($adminrole, $noneditteacherrole);
1891      allow_assign($adminrole, $editteacherrole);
1892      allow_assign($adminrole, $studentrole);
1893      allow_assign($adminrole, $guestrole);
1894  
1895      allow_assign($coursecreatorrole, $noneditteacherrole);
1896      allow_assign($coursecreatorrole, $editteacherrole);
1897      allow_assign($coursecreatorrole, $studentrole);
1898      allow_assign($coursecreatorrole, $guestrole);
1899  
1900      allow_assign($editteacherrole, $noneditteacherrole);
1901      allow_assign($editteacherrole, $studentrole);
1902      allow_assign($editteacherrole, $guestrole);
1903  
1904  /// Set up default allow override matrix
1905      allow_override($adminrole, $adminrole);
1906      allow_override($adminrole, $coursecreatorrole);
1907      allow_override($adminrole, $noneditteacherrole);
1908      allow_override($adminrole, $editteacherrole);
1909      allow_override($adminrole, $studentrole);
1910      allow_override($adminrole, $guestrole);
1911      allow_override($adminrole, $userrole);
1912  
1913      //See MDL-15841
1914      //allow_override($editteacherrole, $noneditteacherrole);
1915      //allow_override($editteacherrole, $studentrole);
1916      //allow_override($editteacherrole, $guestrole);
1917  
1918  
1919  /// Delete the old user tables when we are done
1920  
1921      $tables = array('user_students', 'user_teachers', 'user_coursecreators', 'user_admins');  
1922      foreach ($tables as $tablename) {
1923          $table = new XMLDBTable($tablename);
1924          if (table_exists($table)) {
1925              drop_table($table);
1926          }
1927      }
1928  }
1929  
1930  /**
1931   * Returns array of all legacy roles.
1932   */
1933  function get_legacy_roles() {
1934      return array(
1935          'admin'          => 'moodle/legacy:admin',
1936          'coursecreator'  => 'moodle/legacy:coursecreator',
1937          'editingteacher' => 'moodle/legacy:editingteacher',
1938          'teacher'        => 'moodle/legacy:teacher',
1939          'student'        => 'moodle/legacy:student',
1940          'guest'          => 'moodle/legacy:guest',
1941          'user'           => 'moodle/legacy:user'
1942      );
1943  }
1944  
1945  function get_legacy_type($roleid) {
1946      $sitecontext = get_context_instance(CONTEXT_SYSTEM);
1947      $legacyroles = get_legacy_roles();
1948  
1949      $result = '';
1950      foreach($legacyroles as $ltype=>$lcap) {
1951          $localoverride = get_local_override($roleid, $sitecontext->id, $lcap);
1952          if (!empty($localoverride->permission) and $localoverride->permission == CAP_ALLOW) {
1953              //choose first selected legacy capability - reset the rest
1954              if (empty($result)) {
1955                  $result = $ltype;
1956              } else {
1957                  unassign_capability($lcap, $roleid);
1958              }
1959          }
1960      }
1961  
1962      return $result;
1963  }
1964  
1965  /**
1966   * Assign the defaults found in this capabality definition to roles that have
1967   * the corresponding legacy capabilities assigned to them.
1968   * @param $legacyperms - an array in the format (example):
1969   *                      'guest' => CAP_PREVENT,
1970   *                      'student' => CAP_ALLOW,
1971   *                      'teacher' => CAP_ALLOW,
1972   *                      'editingteacher' => CAP_ALLOW,
1973   *                      'coursecreator' => CAP_ALLOW,
1974   *                      'admin' => CAP_ALLOW
1975   * @return boolean - success or failure.
1976   */
1977  function assign_legacy_capabilities($capability, $legacyperms) {
1978  
1979      $legacyroles = get_legacy_roles();
1980  
1981      foreach ($legacyperms as $type => $perm) {
1982  
1983          $systemcontext = get_context_instance(CONTEXT_SYSTEM);
1984  
1985          if (!array_key_exists($type, $legacyroles)) {
1986              error('Incorrect legacy role definition for type: '.$type);
1987          }
1988  
1989          if ($roles = get_roles_with_capability($legacyroles[$type], CAP_ALLOW)) {
1990              foreach ($roles as $role) {
1991                  // Assign a site level capability.
1992                  if (!assign_capability($capability, $perm, $role->id, $systemcontext->id)) {
1993                      return false;
1994                  }
1995              }
1996          }
1997      }
1998      return true;
1999  }
2000  
2001  
2002  /**
2003   * Checks to see if a capability is a legacy capability.
2004   * @param $capabilityname
2005   * @return boolean
2006   */
2007  function islegacy($capabilityname) {
2008      if (strpos($capabilityname, 'moodle/legacy') === 0) {
2009          return true;
2010      } else {
2011          return false;
2012      }
2013  }
2014  
2015  
2016  
2017  /**********************************
2018   * Context Manipulation functions *
2019   **********************************/
2020  
2021  /**
2022   * Create a new context record for use by all roles-related stuff
2023   * assumes that the caller has done the homework.
2024   *
2025   * @param $level
2026   * @param $instanceid
2027   *
2028   * @return object newly created context
2029   */
2030  function create_context($contextlevel, $instanceid) {
2031  
2032      global $CFG;
2033  
2034      if ($contextlevel == CONTEXT_SYSTEM) {
2035          return create_system_context();
2036      }
2037  
2038      $context = new object();
2039      $context->contextlevel = $contextlevel;
2040      $context->instanceid = $instanceid;
2041  
2042      // Define $context->path based on the parent
2043      // context. In other words... Who is your daddy?
2044      $basepath  = '/' . SYSCONTEXTID;
2045      $basedepth = 1;
2046  
2047      $result = true;
2048  
2049      switch ($contextlevel) {
2050          case CONTEXT_COURSECAT:
2051              $sql = "SELECT ctx.path, ctx.depth 
2052                      FROM {$CFG->prefix}context           ctx
2053                      JOIN {$CFG->prefix}course_categories cc
2054                        ON (cc.parent=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSECAT.")
2055                      WHERE cc.id={$instanceid}";
2056              if ($p = get_record_sql($sql)) {
2057                  $basepath  = $p->path;
2058                  $basedepth = $p->depth;
2059              } else if ($category = get_record('course_categories', 'id', $instanceid)) {
2060                  if (empty($category->parent)) {
2061                      // ok - this is a top category
2062                  } else if ($parent = get_context_instance(CONTEXT_COURSECAT, $category->parent)) {
2063                      $basepath  = $parent->path;
2064                      $basedepth = $parent->depth;
2065                  } else {
2066                      // wrong parent category - no big deal, this can be fixed later
2067                      $basepath  = null;
2068                      $basedepth = 0;
2069                  }
2070              } else {
2071                  // incorrect category id
2072                  $result = false;
2073              }
2074              break;
2075  
2076          case CONTEXT_COURSE:
2077              $sql = "SELECT ctx.path, ctx.depth
2078                      FROM {$CFG->prefix}context           ctx
2079                      JOIN {$CFG->prefix}course            c
2080                        ON (c.category=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSECAT.")
2081                      WHERE c.id={$instanceid} AND c.id !=" . SITEID;
2082              if ($p = get_record_sql($sql)) {
2083                  $basepath  = $p->path;
2084                  $basedepth = $p->depth;
2085              } else if ($course = get_record('course', 'id', $instanceid)) {
2086                  if ($course->id == SITEID) {
2087                      //ok - no parent category
2088                  } else if ($parent = get_context_instance(CONTEXT_COURSECAT, $course->category)) {
2089                      $basepath  = $parent->path;
2090                      $basedepth = $parent->depth;
2091                  } else {
2092                      // wrong parent category of course - no big deal, this can be fixed later
2093                      $basepath  = null;
2094                      $basedepth = 0;
2095                  }
2096              } else if ($instanceid == SITEID) {
2097                  // no errors for missing site course during installation
2098                  return false;
2099              } else {
2100                  // incorrect course id
2101                  $result = false;
2102              }
2103              break;
2104  
2105          case CONTEXT_MODULE:
2106              $sql = "SELECT ctx.path, ctx.depth
2107                      FROM {$CFG->prefix}context           ctx
2108                      JOIN {$CFG->prefix}course_modules    cm
2109                        ON (cm.course=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE.")
2110                      WHERE cm.id={$instanceid}";
2111              if ($p = get_record_sql($sql)) {
2112                  $basepath  = $p->path;
2113                  $basedepth = $p->depth;
2114              } else if ($cm = get_record('course_modules', 'id', $instanceid)) {
2115                  if ($parent = get_context_instance(CONTEXT_COURSE, $cm->course)) {
2116                      $basepath  = $parent->path;
2117                      $basedepth = $parent->depth;
2118                  } else {
2119                      // course does not exist - modules can not exist without a course
2120                      $result = false;
2121                  }
2122              } else {
2123                  // cm does not exist
2124                  $result = false;
2125              }
2126              break;
2127  
2128          case CONTEXT_BLOCK:
2129              // Only non-pinned & course-page based
2130              $sql = "SELECT ctx.path, ctx.depth
2131                      FROM {$CFG->prefix}context           ctx
2132                      JOIN {$CFG->prefix}block_instance    bi
2133                        ON (bi.pageid=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE.")
2134                      WHERE bi.id={$instanceid} AND bi.pagetype='course-view'&q