PageRenderTime 54ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/accesslib.php

https://bitbucket.org/ceu/moodle_demo
PHP | 5809 lines | 3392 code | 733 blank | 1684 comment | 752 complexity | 80a2cdbb08e7015d6d1d38c21de86bd4 MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.0, LGPL-2.1

Large files files are truncated, but you can click here to view the full file

  1. <?php // $Id: accesslib.php,v 1.421.2.111 2011/08/03 16:28:19 moodlerobot Exp $
  2. ///////////////////////////////////////////////////////////////////////////
  3. // //
  4. // NOTICE OF COPYRIGHT //
  5. // //
  6. // Moodle - Modular Object-Oriented Dynamic Learning Environment //
  7. // http://moodle.org //
  8. // //
  9. // Copyright (C) 1999 onwards Martin Dougiamas http://dougiamas.com //
  10. // //
  11. // This program is free software; you can redistribute it and/or modify //
  12. // it under the terms of the GNU General Public License as published by //
  13. // the Free Software Foundation; either version 2 of the License, or //
  14. // (at your option) any later version. //
  15. // //
  16. // This program is distributed in the hope that it will be useful, //
  17. // but WITHOUT ANY WARRANTY; without even the implied warranty of //
  18. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
  19. // GNU General Public License for more details: //
  20. // //
  21. // http://www.gnu.org/copyleft/gpl.html //
  22. // //
  23. ///////////////////////////////////////////////////////////////////////////
  24. /**
  25. * Public API vs internals
  26. * -----------------------
  27. *
  28. * General users probably only care about
  29. *
  30. * Context handling
  31. * - get_context_instance()
  32. * - get_context_instance_by_id()
  33. * - get_parent_contexts()
  34. * - get_child_contexts()
  35. *
  36. * Whether the user can do something...
  37. * - has_capability()
  38. * - has_any_capability()
  39. * - has_all_capabilities()
  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. require_once $CFG->dirroot.'/lib/blocklib.php';
  129. // permission definitions
  130. define('CAP_INHERIT', 0);
  131. define('CAP_ALLOW', 1);
  132. define('CAP_PREVENT', -1);
  133. define('CAP_PROHIBIT', -1000);
  134. // context definitions
  135. define('CONTEXT_SYSTEM', 10);
  136. define('CONTEXT_USER', 30);
  137. define('CONTEXT_COURSECAT', 40);
  138. define('CONTEXT_COURSE', 50);
  139. define('CONTEXT_MODULE', 70);
  140. define('CONTEXT_BLOCK', 80);
  141. // capability risks - see http://docs.moodle.org/dev/Hardening_new_Roles_system
  142. define('RISK_MANAGETRUST', 0x0001);
  143. define('RISK_CONFIG', 0x0002);
  144. define('RISK_XSS', 0x0004);
  145. define('RISK_PERSONAL', 0x0008);
  146. define('RISK_SPAM', 0x0010);
  147. define('RISK_DATALOSS', 0x0020);
  148. // rolename displays
  149. define('ROLENAME_ORIGINAL', 0);// the name as defined in the role definition
  150. define('ROLENAME_ALIAS', 1); // the name as defined by a role alias
  151. define('ROLENAME_BOTH', 2); // Both, like this: Role alias (Original)
  152. require_once($CFG->dirroot.'/group/lib.php');
  153. if (!defined('MAX_CONTEXT_CACHE_SIZE')) {
  154. define('MAX_CONTEXT_CACHE_SIZE', 5000);
  155. }
  156. $context_cache = array(); // Cache of all used context objects for performance (by level and instance)
  157. $context_cache_id = array(); // Index to above cache by id
  158. $DIRTYCONTEXTS = null; // dirty contexts cache
  159. $ACCESS = array(); // cache of caps for cron user switching and has_capability for other users (==not $USER)
  160. $RDEFS = array(); // role definitions cache - helps a lot with mem usage in cron
  161. /**
  162. * Adds a context to the cache.
  163. * @param object $context Context object to be cached
  164. */
  165. function cache_context($context) {
  166. global $context_cache, $context_cache_id;
  167. // If there are too many items in the cache already, remove items until
  168. // there is space
  169. while (count($context_cache_id) >= MAX_CONTEXT_CACHE_SIZE) {
  170. $first = reset($context_cache_id);
  171. unset($context_cache_id[$first->id]);
  172. unset($context_cache[$first->contextlevel][$first->instanceid]);
  173. }
  174. // Add this context to the cache
  175. $context_cache_id[$context->id] = $context;
  176. $context_cache[$context->contextlevel][$context->instanceid] = $context;
  177. }
  178. function get_role_context_caps($roleid, $context) {
  179. //this is really slow!!!! - do not use above course context level!
  180. $result = array();
  181. $result[$context->id] = array();
  182. // first emulate the parent context capabilities merging into context
  183. $searchcontexts = array_reverse(get_parent_contexts($context));
  184. array_push($searchcontexts, $context->id);
  185. foreach ($searchcontexts as $cid) {
  186. if ($capabilities = get_records_select('role_capabilities', "roleid = $roleid AND contextid = $cid")) {
  187. foreach ($capabilities as $cap) {
  188. if (!array_key_exists($cap->capability, $result[$context->id])) {
  189. $result[$context->id][$cap->capability] = 0;
  190. }
  191. $result[$context->id][$cap->capability] += $cap->permission;
  192. }
  193. }
  194. }
  195. // now go through the contexts bellow given context
  196. $searchcontexts = array_keys(get_child_contexts($context));
  197. foreach ($searchcontexts as $cid) {
  198. if ($capabilities = get_records_select('role_capabilities', "roleid = $roleid AND contextid = $cid")) {
  199. foreach ($capabilities as $cap) {
  200. if (!array_key_exists($cap->contextid, $result)) {
  201. $result[$cap->contextid] = array();
  202. }
  203. $result[$cap->contextid][$cap->capability] = $cap->permission;
  204. }
  205. }
  206. }
  207. return $result;
  208. }
  209. /**
  210. * Gets the accessdata for role "sitewide"
  211. * (system down to course)
  212. *
  213. * @return array
  214. */
  215. function get_role_access($roleid, $accessdata=NULL) {
  216. global $CFG;
  217. /* Get it in 1 cheap DB query...
  218. * - relevant role caps at the root and down
  219. * to the course level - but not below
  220. */
  221. if (is_null($accessdata)) {
  222. $accessdata = array(); // named list
  223. $accessdata['ra'] = array();
  224. $accessdata['rdef'] = array();
  225. $accessdata['loaded'] = array();
  226. }
  227. //
  228. // Overrides for the role IN ANY CONTEXTS
  229. // down to COURSE - not below -
  230. //
  231. $sql = "SELECT ctx.path,
  232. rc.capability, rc.permission
  233. FROM {$CFG->prefix}context ctx
  234. JOIN {$CFG->prefix}role_capabilities rc
  235. ON rc.contextid=ctx.id
  236. WHERE rc.roleid = {$roleid}
  237. AND ctx.contextlevel <= ".CONTEXT_COURSE."
  238. ORDER BY ctx.depth, ctx.path";
  239. // we need extra caching in cron only
  240. if (defined('FULLME') and FULLME === 'cron') {
  241. static $cron_cache = array();
  242. if (!isset($cron_cache[$roleid])) {
  243. $cron_cache[$roleid] = array();
  244. if ($rs = get_recordset_sql($sql)) {
  245. while ($rd = rs_fetch_next_record($rs)) {
  246. $cron_cache[$roleid][] = $rd;
  247. }
  248. rs_close($rs);
  249. }
  250. }
  251. foreach ($cron_cache[$roleid] as $rd) {
  252. $k = "{$rd->path}:{$roleid}";
  253. $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
  254. }
  255. } else {
  256. if ($rs = get_recordset_sql($sql)) {
  257. while ($rd = rs_fetch_next_record($rs)) {
  258. $k = "{$rd->path}:{$roleid}";
  259. $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
  260. }
  261. unset($rd);
  262. rs_close($rs);
  263. }
  264. }
  265. return $accessdata;
  266. }
  267. /**
  268. * Gets the accessdata for role "sitewide"
  269. * (system down to course)
  270. *
  271. * @return array
  272. */
  273. function get_default_frontpage_role_access($roleid, $accessdata=NULL) {
  274. global $CFG;
  275. $frontpagecontext = get_context_instance(CONTEXT_COURSE, SITEID);
  276. $base = '/'. SYSCONTEXTID .'/'. $frontpagecontext->id;
  277. //
  278. // Overrides for the role in any contexts related to the course
  279. //
  280. $sql = "SELECT ctx.path,
  281. rc.capability, rc.permission
  282. FROM {$CFG->prefix}context ctx
  283. JOIN {$CFG->prefix}role_capabilities rc
  284. ON rc.contextid=ctx.id
  285. WHERE rc.roleid = {$roleid}
  286. AND (ctx.id = ".SYSCONTEXTID." OR ctx.path LIKE '$base/%')
  287. AND ctx.contextlevel <= ".CONTEXT_COURSE."
  288. ORDER BY ctx.depth, ctx.path";
  289. if ($rs = get_recordset_sql($sql)) {
  290. while ($rd = rs_fetch_next_record($rs)) {
  291. $k = "{$rd->path}:{$roleid}";
  292. $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
  293. }
  294. unset($rd);
  295. rs_close($rs);
  296. }
  297. return $accessdata;
  298. }
  299. /**
  300. * Get the default guest role
  301. * @return object role
  302. */
  303. function get_guest_role() {
  304. global $CFG;
  305. if (empty($CFG->guestroleid)) {
  306. if ($roles = get_roles_with_capability('moodle/legacy:guest', CAP_ALLOW)) {
  307. $guestrole = array_shift($roles); // Pick the first one
  308. set_config('guestroleid', $guestrole->id);
  309. return $guestrole;
  310. } else {
  311. debugging('Can not find any guest role!');
  312. return false;
  313. }
  314. } else {
  315. if ($guestrole = get_record('role','id', $CFG->guestroleid)) {
  316. return $guestrole;
  317. } else {
  318. //somebody is messing with guest roles, remove incorrect setting and try to find a new one
  319. set_config('guestroleid', '');
  320. return get_guest_role();
  321. }
  322. }
  323. }
  324. /**
  325. * This function returns whether the current user has the capability of performing a function
  326. * For example, we can do has_capability('mod/forum:replypost',$context) in forum
  327. * @param string $capability - name of the capability (or debugcache or clearcache)
  328. * @param object $context - a context object (record from context table)
  329. * @param integer $userid - a userid number, empty if current $USER
  330. * @param bool $doanything - if false, ignore do anything
  331. * @return bool
  332. */
  333. function has_capability($capability, $context, $userid=NULL, $doanything=true) {
  334. global $USER, $ACCESS, $CFG, $DIRTYCONTEXTS;
  335. // the original $CONTEXT here was hiding serious errors
  336. // for security reasons do not reuse previous context
  337. if (empty($context)) {
  338. debugging('Incorrect context specified');
  339. return false;
  340. }
  341. /// Some sanity checks
  342. if (debugging('',DEBUG_DEVELOPER)) {
  343. if (!is_valid_capability($capability)) {
  344. debugging('Capability "'.$capability.'" was not found! This should be fixed in code.');
  345. }
  346. if (!is_bool($doanything)) {
  347. debugging('Capability parameter "doanything" is wierd ("'.$doanything.'"). This should be fixed in code.');
  348. }
  349. }
  350. if (empty($userid)) { // we must accept null, 0, '0', '' etc. in $userid
  351. $userid = $USER->id;
  352. }
  353. if (is_null($context->path) or $context->depth == 0) {
  354. //this should not happen
  355. $contexts = array(SYSCONTEXTID, $context->id);
  356. $context->path = '/'.SYSCONTEXTID.'/'.$context->id;
  357. debugging('Context id '.$context->id.' does not have valid path, please use build_context_path()', DEBUG_DEVELOPER);
  358. } else {
  359. $contexts = explode('/', $context->path);
  360. array_shift($contexts);
  361. }
  362. if (defined('FULLME') && FULLME === 'cron' && !isset($USER->access)) {
  363. // In cron, some modules setup a 'fake' $USER,
  364. // ensure we load the appropriate accessdata.
  365. if (isset($ACCESS[$userid])) {
  366. $DIRTYCONTEXTS = NULL; //load fresh dirty contexts
  367. } else {
  368. load_user_accessdata($userid);
  369. $DIRTYCONTEXTS = array();
  370. }
  371. $USER->access = $ACCESS[$userid];
  372. } else if ($USER->id == $userid && !isset($USER->access)) {
  373. // caps not loaded yet - better to load them to keep BC with 1.8
  374. // not-logged-in user or $USER object set up manually first time here
  375. load_all_capabilities();
  376. $ACCESS = array(); // reset the cache for other users too, the dirty contexts are empty now
  377. $RDEFS = array();
  378. }
  379. // Load dirty contexts list if needed
  380. if (!isset($DIRTYCONTEXTS)) {
  381. if (isset($USER->access['time'])) {
  382. $DIRTYCONTEXTS = get_dirty_contexts($USER->access['time']);
  383. }
  384. else {
  385. $DIRTYCONTEXTS = array();
  386. }
  387. }
  388. // Careful check for staleness...
  389. if (count($DIRTYCONTEXTS) !== 0 and is_contextpath_dirty($contexts, $DIRTYCONTEXTS)) {
  390. // reload all capabilities - preserving loginas, roleswitches, etc
  391. // and then cleanup any marks of dirtyness... at least from our short
  392. // term memory! :-)
  393. $ACCESS = array();
  394. $RDEFS = array();
  395. if (defined('FULLME') && FULLME === 'cron') {
  396. load_user_accessdata($userid);
  397. $USER->access = $ACCESS[$userid];
  398. $DIRTYCONTEXTS = array();
  399. } else {
  400. reload_all_capabilities();
  401. }
  402. }
  403. // divulge how many times we are called
  404. //// error_log("has_capability: id:{$context->id} path:{$context->path} userid:$userid cap:$capability");
  405. if ($USER->id == $userid) { // we must accept strings and integers in $userid
  406. //
  407. // For the logged in user, we have $USER->access
  408. // which will have all RAs and caps preloaded for
  409. // course and above contexts.
  410. //
  411. // Contexts below courses && contexts that do not
  412. // hang from courses are loaded into $USER->access
  413. // on demand, and listed in $USER->access[loaded]
  414. //
  415. if ($context->contextlevel <= CONTEXT_COURSE) {
  416. // Course and above are always preloaded
  417. return has_capability_in_accessdata($capability, $context, $USER->access, $doanything);
  418. }
  419. // Load accessdata for below-the-course contexts
  420. if (!path_inaccessdata($context->path,$USER->access)) {
  421. // error_log("loading access for context {$context->path} for $capability at {$context->contextlevel} {$context->id}");
  422. // $bt = debug_backtrace();
  423. // error_log("bt {$bt[0]['file']} {$bt[0]['line']}");
  424. load_subcontext($USER->id, $context, $USER->access);
  425. }
  426. return has_capability_in_accessdata($capability, $context, $USER->access, $doanything);
  427. }
  428. if (!isset($ACCESS[$userid])) {
  429. load_user_accessdata($userid);
  430. }
  431. if ($context->contextlevel <= CONTEXT_COURSE) {
  432. // Course and above are always preloaded
  433. return has_capability_in_accessdata($capability, $context, $ACCESS[$userid], $doanything);
  434. }
  435. // Load accessdata for below-the-course contexts as needed
  436. if (!path_inaccessdata($context->path, $ACCESS[$userid])) {
  437. // error_log("loading access for context {$context->path} for $capability at {$context->contextlevel} {$context->id}");
  438. // $bt = debug_backtrace();
  439. // error_log("bt {$bt[0]['file']} {$bt[0]['line']}");
  440. load_subcontext($userid, $context, $ACCESS[$userid]);
  441. }
  442. return has_capability_in_accessdata($capability, $context, $ACCESS[$userid], $doanything);
  443. }
  444. /**
  445. * This function returns whether the current user has any of the capabilities in the
  446. * $capabilities array. This is a simple wrapper around has_capability for convinience.
  447. *
  448. * There are probably tricks that could be done to improve the performance here, for example,
  449. * check the capabilities that are already cached first.
  450. *
  451. * @param array $capabilities - an array of capability names.
  452. * @param object $context - a context object (record from context table)
  453. * @param integer $userid - a userid number, empty if current $USER
  454. * @param bool $doanything - if false, ignore do anything
  455. * @return bool
  456. */
  457. function has_any_capability($capabilities, $context, $userid=NULL, $doanything=true) {
  458. foreach ($capabilities as $capability) {
  459. if (has_capability($capability, $context, $userid, $doanything)) {
  460. return true;
  461. }
  462. }
  463. return false;
  464. }
  465. /**
  466. * This function returns whether the current user has all of the capabilities in the
  467. * $capabilities array. This is a simple wrapper around has_capability for convinience.
  468. *
  469. * There are probably tricks that could be done to improve the performance here, for example,
  470. * check the capabilities that are already cached first.
  471. *
  472. * @param array $capabilities - an array of capability names.
  473. * @param object $context - a context object (record from context table)
  474. * @param integer $userid - a userid number, empty if current $USER
  475. * @param bool $doanything - if false, ignore do anything
  476. * @return bool
  477. */
  478. function has_all_capabilities($capabilities, $context, $userid=NULL, $doanything=true) {
  479. if (!is_array($capabilities)) {
  480. debugging('Incorrect $capabilities parameter in has_all_capabilities() call - must be an array');
  481. return false;
  482. }
  483. foreach ($capabilities as $capability) {
  484. if (!has_capability($capability, $context, $userid, $doanything)) {
  485. return false;
  486. }
  487. }
  488. return true;
  489. }
  490. /**
  491. * Uses 1 DB query to answer whether a user is an admin at the sitelevel.
  492. * It depends on DB schema >=1.7 but does not depend on the new datastructures
  493. * in v1.9 (context.path, or $USER->access)
  494. *
  495. * Will return true if the userid has any of
  496. * - moodle/site:config
  497. * - moodle/legacy:admin
  498. * - moodle/site:doanything
  499. *
  500. * @param int $userid
  501. * @returns bool $isadmin
  502. */
  503. function is_siteadmin($userid) {
  504. global $CFG;
  505. $sql = "SELECT SUM(rc.permission)
  506. FROM " . $CFG->prefix . "role_capabilities rc
  507. JOIN " . $CFG->prefix . "context ctx
  508. ON ctx.id=rc.contextid
  509. JOIN " . $CFG->prefix . "role_assignments ra
  510. ON ra.roleid=rc.roleid AND ra.contextid=ctx.id
  511. WHERE ctx.contextlevel=10
  512. AND ra.userid={$userid}
  513. AND rc.capability IN ('moodle/site:config', 'moodle/legacy:admin', 'moodle/site:doanything')
  514. GROUP BY rc.capability
  515. HAVING SUM(rc.permission) > 0";
  516. $isadmin = record_exists_sql($sql);
  517. return $isadmin;
  518. }
  519. function get_course_from_path ($path) {
  520. // assume that nothing is more than 1 course deep
  521. if (preg_match('!^(/.+)/\d+$!', $path, $matches)) {
  522. return $matches[1];
  523. }
  524. return false;
  525. }
  526. function path_inaccessdata($path, $accessdata) {
  527. // assume that contexts hang from sys or from a course
  528. // this will only work well with stuff that hangs from a course
  529. if (in_array($path, $accessdata['loaded'], true)) {
  530. // error_log("found it!");
  531. return true;
  532. }
  533. $base = '/' . SYSCONTEXTID;
  534. while (preg_match('!^(/.+)/\d+$!', $path, $matches)) {
  535. $path = $matches[1];
  536. if ($path === $base) {
  537. return false;
  538. }
  539. if (in_array($path, $accessdata['loaded'], true)) {
  540. return true;
  541. }
  542. }
  543. return false;
  544. }
  545. /**
  546. * Walk the accessdata array and return true/false.
  547. * Deals with prohibits, roleswitching, aggregating
  548. * capabilities, etc.
  549. *
  550. * The main feature of here is being FAST and with no
  551. * side effects.
  552. *
  553. * Notes:
  554. *
  555. * Switch Roles exits early
  556. * -----------------------
  557. * cap checks within a switchrole need to exit early
  558. * in our bottom up processing so they don't "see" that
  559. * there are real RAs that can do all sorts of things.
  560. *
  561. * Switch Role merges with default role
  562. * ------------------------------------
  563. * If you are a teacher in course X, you have at least
  564. * teacher-in-X + defaultloggedinuser-sitewide. So in the
  565. * course you'll have techer+defaultloggedinuser.
  566. * We try to mimic that in switchrole.
  567. *
  568. * Local-most role definition and role-assignment wins
  569. * ---------------------------------------------------
  570. * So if the local context has said 'allow', it wins
  571. * over a high-level context that says 'deny'.
  572. * This is applied when walking rdefs, and RAs.
  573. * Only at the same context the values are SUM()med.
  574. *
  575. * The exception is CAP_PROHIBIT.
  576. *
  577. * "Guest default role" exception
  578. * ------------------------------
  579. *
  580. * See MDL-7513 and $ignoreguest below for details.
  581. *
  582. * The rule is that
  583. *
  584. * IF we are being asked about moodle/legacy:guest
  585. * OR moodle/course:view
  586. * FOR a real, logged-in user
  587. * AND we reached the top of the path in ra and rdef
  588. * AND that role has moodle/legacy:guest === 1...
  589. * THEN we act as if we hadn't seen it.
  590. *
  591. * Note that this function must be kept in synch with has_capability_in_accessdata.
  592. *
  593. * To Do:
  594. *
  595. * - Document how it works
  596. * - Rewrite in ASM :-)
  597. *
  598. */
  599. function has_capability_in_accessdata($capability, $context, $accessdata, $doanything) {
  600. global $CFG;
  601. $path = $context->path;
  602. // build $contexts as a list of "paths" of the current
  603. // contexts and parents with the order top-to-bottom
  604. $contexts = array($path);
  605. while (preg_match('!^(/.+)/\d+$!', $path, $matches)) {
  606. $path = $matches[1];
  607. array_unshift($contexts, $path);
  608. }
  609. $ignoreguest = false;
  610. if (isset($accessdata['dr'])
  611. && ($capability == 'moodle/course:view'
  612. || $capability == 'moodle/legacy:guest')) {
  613. // At the base, ignore rdefs where moodle/legacy:guest
  614. // is set
  615. $ignoreguest = $accessdata['dr'];
  616. }
  617. // Coerce it to an int
  618. $CAP_PROHIBIT = (int)CAP_PROHIBIT;
  619. $cc = count($contexts);
  620. $can = 0;
  621. $capdepth = 0;
  622. //
  623. // role-switches loop
  624. //
  625. if (isset($accessdata['rsw'])) {
  626. // check for isset() is fast
  627. // empty() is slow...
  628. if (empty($accessdata['rsw'])) {
  629. unset($accessdata['rsw']); // keep things fast and unambiguous
  630. break;
  631. }
  632. // From the bottom up...
  633. for ($n=$cc-1;$n>=0;$n--) {
  634. $ctxp = $contexts[$n];
  635. if (isset($accessdata['rsw'][$ctxp])) {
  636. // Found a switchrole assignment
  637. // check for that role _plus_ the default user role
  638. $ras = array($accessdata['rsw'][$ctxp],$CFG->defaultuserroleid);
  639. for ($rn=0;$rn<2;$rn++) {
  640. $roleid = (int)$ras[$rn];
  641. // Walk the path for capabilities
  642. // from the bottom up...
  643. for ($m=$cc-1;$m>=0;$m--) {
  644. $capctxp = $contexts[$m];
  645. if (isset($accessdata['rdef']["{$capctxp}:$roleid"][$capability])) {
  646. $perm = (int)$accessdata['rdef']["{$capctxp}:$roleid"][$capability];
  647. // The most local permission (first to set) wins
  648. // the only exception is CAP_PROHIBIT
  649. if ($can === 0) {
  650. $can = $perm;
  651. } elseif ($perm === $CAP_PROHIBIT) {
  652. $can = $perm;
  653. break;
  654. }
  655. }
  656. }
  657. }
  658. // As we are dealing with a switchrole,
  659. // we return _here_, do _not_ walk up
  660. // the hierarchy any further
  661. if ($can < 1) {
  662. if ($doanything) {
  663. // didn't find it as an explicit cap,
  664. // but maybe the user can doanything in this context...
  665. return has_capability_in_accessdata('moodle/site:doanything', $context, $accessdata, false);
  666. } else {
  667. return false;
  668. }
  669. } else {
  670. return true;
  671. }
  672. }
  673. }
  674. }
  675. //
  676. // Main loop for normal RAs
  677. // From the bottom up...
  678. //
  679. for ($n=$cc-1;$n>=0;$n--) {
  680. $ctxp = $contexts[$n];
  681. if (isset($accessdata['ra'][$ctxp])) {
  682. // Found role assignments on this leaf
  683. $ras = $accessdata['ra'][$ctxp];
  684. $rc = count($ras);
  685. $ctxcan = 0;
  686. $ctxcapdepth = 0;
  687. for ($rn=0;$rn<$rc;$rn++) {
  688. $roleid = (int)$ras[$rn];
  689. $rolecan = 0;
  690. $rolecapdepth = 0;
  691. // Walk the path for capabilities
  692. // from the bottom up...
  693. for ($m=$cc-1;$m>=0;$m--) {
  694. $capctxp = $contexts[$m];
  695. // ignore some guest caps
  696. // at base ra and rdef
  697. if ($ignoreguest == $roleid
  698. && $n === 0
  699. && $m === 0
  700. && isset($accessdata['rdef']["{$capctxp}:$roleid"]['moodle/legacy:guest'])
  701. && $accessdata['rdef']["{$capctxp}:$roleid"]['moodle/legacy:guest'] > 0) {
  702. continue;
  703. }
  704. if (isset($accessdata['rdef']["{$capctxp}:$roleid"][$capability])) {
  705. $perm = (int)$accessdata['rdef']["{$capctxp}:$roleid"][$capability];
  706. // The most local permission (first to set) wins
  707. // the only exception is CAP_PROHIBIT
  708. if ($rolecan === 0) {
  709. $rolecan = $perm;
  710. $rolecapdepth = $m;
  711. } elseif ($perm === $CAP_PROHIBIT) {
  712. $rolecan = $perm;
  713. $rolecapdepth = $m;
  714. break;
  715. }
  716. }
  717. }
  718. // Rules for RAs at the same context...
  719. // - prohibits always wins
  720. // - permissions at the same ctxlevel & capdepth are added together
  721. // - deeper capdepth wins
  722. if ($ctxcan === $CAP_PROHIBIT || $rolecan === $CAP_PROHIBIT) {
  723. $ctxcan = $CAP_PROHIBIT;
  724. $ctxcapdepth = 0;
  725. } elseif ($ctxcapdepth === $rolecapdepth) {
  726. $ctxcan += $rolecan;
  727. } elseif ($ctxcapdepth < $rolecapdepth) {
  728. $ctxcan = $rolecan;
  729. $ctxcapdepth = $rolecapdepth;
  730. } else { // ctxcaptdepth is deeper
  731. // rolecap ignored
  732. }
  733. }
  734. // The most local RAs with a defined
  735. // permission ($ctxcan) win, except
  736. // for CAP_PROHIBIT
  737. // NOTE: If we want the deepest RDEF to
  738. // win regardless of the depth of the RA,
  739. // change the elseif below to read
  740. // ($can === 0 || $capdepth < $ctxcapdepth) {
  741. if ($ctxcan === $CAP_PROHIBIT) {
  742. $can = $ctxcan;
  743. break;
  744. } elseif ($can === 0) { // see note above
  745. $can = $ctxcan;
  746. $capdepth = $ctxcapdepth;
  747. }
  748. }
  749. }
  750. if ($can < 1) {
  751. if ($doanything) {
  752. // didn't find it as an explicit cap,
  753. // but maybe the user can doanything in this context...
  754. return has_capability_in_accessdata('moodle/site:doanything', $context, $accessdata, false);
  755. } else {
  756. return false;
  757. }
  758. } else {
  759. return true;
  760. }
  761. }
  762. function aggregate_roles_from_accessdata($context, $accessdata) {
  763. $path = $context->path;
  764. // build $contexts as a list of "paths" of the current
  765. // contexts and parents with the order top-to-bottom
  766. $contexts = array($path);
  767. while (preg_match('!^(/.+)/\d+$!', $path, $matches)) {
  768. $path = $matches[1];
  769. array_unshift($contexts, $path);
  770. }
  771. $cc = count($contexts);
  772. $roles = array();
  773. // From the bottom up...
  774. for ($n=$cc-1;$n>=0;$n--) {
  775. $ctxp = $contexts[$n];
  776. if (isset($accessdata['ra'][$ctxp]) && count($accessdata['ra'][$ctxp])) {
  777. // Found assignments on this leaf
  778. $addroles = $accessdata['ra'][$ctxp];
  779. $roles = array_merge($roles, $addroles);
  780. }
  781. }
  782. return array_unique($roles);
  783. }
  784. /**
  785. * This is an easy to use function, combining has_capability() with require_course_login().
  786. * And will call those where needed.
  787. *
  788. * It checks for a capability assertion being true. If it isn't
  789. * then the page is terminated neatly with a standard error message.
  790. *
  791. * If the user is not logged in, or is using 'guest' access or other special "users,
  792. * it provides a logon prompt.
  793. *
  794. * @param string $capability - name of the capability
  795. * @param object $context - a context object (record from context table)
  796. * @param integer $userid - a userid number
  797. * @param bool $doanything - if false, ignore do anything
  798. * @param string $errorstring - an errorstring
  799. * @param string $stringfile - which stringfile to get it from
  800. */
  801. function require_capability($capability, $context, $userid=NULL, $doanything=true,
  802. $errormessage='nopermissions', $stringfile='') {
  803. global $USER, $CFG;
  804. /* Empty $userid means current user, if the current user is not logged in,
  805. * then make sure they are (if needed).
  806. * Originally there was a check for loaded permissions - it is not needed here.
  807. * Context is now required parameter, the cached $CONTEXT was only hiding errors.
  808. */
  809. $errorlink = '';
  810. if (empty($userid)) {
  811. if ($context->contextlevel == CONTEXT_COURSE) {
  812. require_login($context->instanceid);
  813. } else if ($context->contextlevel == CONTEXT_MODULE) {
  814. if (!$cm = get_record('course_modules', 'id', $context->instanceid)) {
  815. error('Incorrect module');
  816. }
  817. if (!$course = get_record('course', 'id', $cm->course)) {
  818. error('Incorrect course.');
  819. }
  820. require_course_login($course, true, $cm);
  821. $errorlink = $CFG->wwwroot.'/course/view.php?id='.$cm->course;
  822. } else if ($context->contextlevel == CONTEXT_SYSTEM) {
  823. if (!empty($CFG->forcelogin)) {
  824. require_login();
  825. }
  826. } else {
  827. require_login();
  828. }
  829. }
  830. /// OK, if they still don't have the capability then print a nice error message
  831. if (!has_capability($capability, $context, $userid, $doanything)) {
  832. $capabilityname = get_capability_string($capability);
  833. print_error($errormessage, $stringfile, $errorlink, $capabilityname);
  834. }
  835. }
  836. /**
  837. * Get an array of courses (with magic extra bits)
  838. * where the accessdata and in DB enrolments show
  839. * that the cap requested is available.
  840. *
  841. * The main use is for get_my_courses().
  842. *
  843. * Notes
  844. *
  845. * - $fields is an array of fieldnames to ADD
  846. * so name the fields you really need, which will
  847. * be added and uniq'd
  848. *
  849. * - the course records have $c->context which is a fully
  850. * valid context object. Saves you a query per course!
  851. *
  852. * - the course records have $c->categorypath to make
  853. * category lookups cheap
  854. *
  855. * - current implementation is split in -
  856. *
  857. * - if the user has the cap systemwide, stupidly
  858. * grab *every* course for a capcheck. This eats
  859. * a TON of bandwidth, specially on large sites
  860. * with separate DBs...
  861. *
  862. * - otherwise, fetch "likely" courses with a wide net
  863. * that should get us _cheaply_ at least the courses we need, and some
  864. * we won't - we get courses that...
  865. * - are in a category where user has the cap
  866. * - or where use has a role-assignment (any kind)
  867. * - or where the course has an override on for this cap
  868. *
  869. * - walk the courses recordset checking the caps oneach one
  870. * the checks are all in memory and quite fast
  871. * (though we could implement a specialised variant of the
  872. * has_capability_in_accessdata() code to speed it up)
  873. *
  874. * @param string $capability - name of the capability
  875. * @param array $accessdata - accessdata session array
  876. * @param bool $doanything - if false, ignore do anything
  877. * @param string $sort - sorting fields - prefix each fieldname with "c."
  878. * @param array $fields - additional fields you are interested in...
  879. * @param int $limit - set if you want to limit the number of courses
  880. * @return array $courses - ordered array of course objects - see notes above
  881. *
  882. */
  883. function get_user_courses_bycap($userid, $cap, $accessdata, $doanything, $sort='c.sortorder ASC', $fields=NULL, $limit=0) {
  884. global $CFG;
  885. // Slim base fields, let callers ask for what they need...
  886. $basefields = array('id', 'sortorder', 'shortname', 'idnumber');
  887. if (!is_null($fields)) {
  888. $fields = array_merge($basefields, $fields);
  889. $fields = array_unique($fields);
  890. } else {
  891. $fields = $basefields;
  892. }
  893. // If any of the fields is '*', leave it alone, discarding the rest
  894. // to avoid ambiguous columns under some silly DBs. See MDL-18746 :-D
  895. if (in_array('*', $fields)) {
  896. $fields = array('*');
  897. }
  898. $coursefields = 'c.' .implode(',c.', $fields);
  899. $sort = trim($sort);
  900. if ($sort !== '') {
  901. $sort = "ORDER BY $sort";
  902. }
  903. $sysctx = get_context_instance(CONTEXT_SYSTEM);
  904. if (has_capability_in_accessdata($cap, $sysctx, $accessdata, $doanything)) {
  905. //
  906. // Apparently the user has the cap sitewide, so walk *every* course
  907. // (the cap checks are moderately fast, but this moves massive bandwidth w the db)
  908. // Yuck.
  909. //
  910. $sql = "SELECT $coursefields,
  911. ctx.id AS ctxid, ctx.path AS ctxpath,
  912. ctx.depth AS ctxdepth, ctx.contextlevel AS ctxlevel,
  913. cc.path AS categorypath
  914. FROM {$CFG->prefix}course c
  915. JOIN {$CFG->prefix}course_categories cc
  916. ON c.category=cc.id
  917. JOIN {$CFG->prefix}context ctx
  918. ON (c.id=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE.")
  919. $sort ";
  920. $rs = get_recordset_sql($sql);
  921. } else {
  922. //
  923. // narrow down where we have the caps to a few contexts
  924. // this will be a combination of
  925. // - courses where user has an explicit enrolment
  926. // - courses that have an override (any status) on that capability
  927. // - categories where user has the rights (granted status) on that capability
  928. //
  929. $sql = "SELECT ctx.*
  930. FROM {$CFG->prefix}context ctx
  931. WHERE ctx.contextlevel=".CONTEXT_COURSECAT."
  932. ORDER BY ctx.depth";
  933. $rs = get_recordset_sql($sql);
  934. $catpaths = array();
  935. while ($catctx = rs_fetch_next_record($rs)) {
  936. if ($catctx->path != ''
  937. && has_capability_in_accessdata($cap, $catctx, $accessdata, $doanything)) {
  938. $catpaths[] = $catctx->path;
  939. }
  940. }
  941. rs_close($rs);
  942. $catclause = '';
  943. if (count($catpaths)) {
  944. $cc = count($catpaths);
  945. for ($n=0;$n<$cc;$n++) {
  946. $catpaths[$n] = "ctx.path LIKE '{$catpaths[$n]}/%'";
  947. }
  948. $catclause = 'WHERE (' . implode(' OR ', $catpaths) .')';
  949. }
  950. unset($catpaths);
  951. $capany = '';
  952. if ($doanything) {
  953. $capany = " OR rc.capability='moodle/site:doanything'";
  954. }
  955. /// UNION 3 queries:
  956. /// - user role assignments in courses
  957. /// - user capability (override - any status) in courses
  958. /// - user right (granted status) in categories (optionally executed)
  959. /// Enclosing the 3-UNION into an inline_view to avoid column names conflict and making the ORDER BY cross-db
  960. /// and to allow selection of TEXT columns in the query (MSSQL and Oracle limitation). MDL-16209
  961. $sql = "
  962. SELECT $coursefields, ctxid, ctxpath, ctxdepth, ctxlevel, categorypath
  963. FROM (
  964. SELECT c.id,
  965. ctx.id AS ctxid, ctx.path AS ctxpath,
  966. ctx.depth AS ctxdepth, ctx.contextlevel AS ctxlevel,
  967. cc.path AS categorypath
  968. FROM {$CFG->prefix}course c
  969. JOIN {$CFG->prefix}course_categories cc
  970. ON c.category=cc.id
  971. JOIN {$CFG->prefix}context ctx
  972. ON (c.id=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE.")
  973. JOIN {$CFG->prefix}role_assignments ra
  974. ON (ra.contextid=ctx.id AND ra.userid=$userid)
  975. UNION
  976. SELECT c.id,
  977. ctx.id AS ctxid, ctx.path AS ctxpath,
  978. ctx.depth AS ctxdepth, ctx.contextlevel AS ctxlevel,
  979. cc.path AS categorypath
  980. FROM {$CFG->prefix}course c
  981. JOIN {$CFG->prefix}course_categories cc
  982. ON c.category=cc.id
  983. JOIN {$CFG->prefix}context ctx
  984. ON (c.id=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE.")
  985. JOIN {$CFG->prefix}role_capabilities rc
  986. ON (rc.contextid=ctx.id AND (rc.capability='$cap' $capany)) ";
  987. if (!empty($catclause)) { /// If we have found the right in categories, add child courses here too
  988. $sql .= "
  989. UNION
  990. SELECT c.id,
  991. ctx.id AS ctxid, ctx.path AS ctxpath,
  992. ctx.depth AS ctxdepth, ctx.contextlevel AS ctxlevel,
  993. cc.path AS categorypath
  994. FROM {$CFG->prefix}course c
  995. JOIN {$CFG->prefix}course_categories cc
  996. ON c.category=cc.id
  997. JOIN {$CFG->prefix}context ctx
  998. ON (c.id=ctx.instanceid AND ctx.contextlevel=".CONTEXT_COURSE.")
  999. $catclause";
  1000. }
  1001. /// Close the inline_view and join with courses table to get requested $coursefields
  1002. $sql .= "
  1003. ) inline_view
  1004. INNER JOIN {$CFG->prefix}course c
  1005. ON inline_view.id = c.id";
  1006. /// To keep cross-db we need to strip any prefix in the ORDER BY clause for queries using UNION
  1007. $sql .= "
  1008. " . preg_replace('/[a-z]+\./i', '', $sort); /// Add ORDER BY clause
  1009. $rs = get_recordset_sql($sql);
  1010. }
  1011. /// Confirm rights (granted capability) for each course returned
  1012. $courses = array();
  1013. $cc = 0; // keep count
  1014. while ($c = rs_fetch_next_record($rs)) {
  1015. // build the context obj
  1016. $c = make_context_subobj($c);
  1017. if (has_capability_in_accessdata($cap, $c->context, $accessdata, $doanything)) {
  1018. if ($limit > 0 && $cc >= $limit) {
  1019. break;
  1020. }
  1021. $courses[] = $c;
  1022. $cc++;
  1023. }
  1024. }
  1025. rs_close($rs);
  1026. return $courses;
  1027. }
  1028. /**
  1029. * It will return a nested array showing role assignments
  1030. * all relevant role capabilities for the user at
  1031. * site/metacourse/course_category/course levels
  1032. *
  1033. * We do _not_ delve deeper than courses because the number of
  1034. * overrides at the module/block levels is HUGE.
  1035. *
  1036. * [ra] => [/path/] = array(roleid, roleid)
  1037. * [rdef] => [/path/:roleid][capability]=permission
  1038. * [loaded] => array('/path', '/path')
  1039. *
  1040. * @param $userid integer - the id of the user
  1041. *
  1042. */
  1043. function get_user_access_sitewide($userid) {
  1044. global $CFG;
  1045. // this flag has not been set!
  1046. // (not clean install, or upgraded successfully to 1.7 and up)
  1047. if (empty($CFG->rolesactive)) {
  1048. return false;
  1049. }
  1050. /* Get in 3 cheap DB queries...
  1051. * - role assignments - with role_caps
  1052. * - relevant role caps
  1053. * - above this user's RAs
  1054. * - below this user's RAs - limited to course level
  1055. */
  1056. $accessdata = array(); // named list
  1057. $accessdata['ra'] = array();
  1058. $accessdata['rdef'] = array();
  1059. $accessdata['loaded'] = array();
  1060. $sitectx = get_system_context();
  1061. $base = '/'.$sitectx->id;
  1062. //
  1063. // Role assignments - and any rolecaps directly linked
  1064. // because it's cheap to read rolecaps here over many
  1065. // RAs
  1066. //
  1067. $sql = "SELECT ctx.path, ra.roleid, rc.capability, rc.permission
  1068. FROM {$CFG->prefix}role_assignments ra
  1069. JOIN {$CFG->prefix}context ctx
  1070. ON ra.contextid=ctx.id
  1071. LEFT OUTER JOIN {$CFG->prefix}role_capabilities rc
  1072. ON (rc.roleid=ra.roleid AND rc.contextid=ra.contextid)
  1073. WHERE ra.userid = $userid AND ctx.contextlevel <= ".CONTEXT_COURSE."
  1074. ORDER BY ctx.depth, ctx.path, ra.roleid";
  1075. $rs = get_recordset_sql($sql);
  1076. //
  1077. // raparents collects paths & roles we need to walk up
  1078. // the parenthood to build the rdef
  1079. //
  1080. // the array will bulk up a bit with dups
  1081. // which we'll later clear up
  1082. //
  1083. $raparents = array();
  1084. $lastseen = '';
  1085. if ($rs) {
  1086. while ($ra = rs_fetch_next_record($rs)) {
  1087. // RAs leafs are arrays to support multi
  1088. // role assignments...
  1089. if (!isset($accessdata['ra'][$ra->path])) {
  1090. $accessdata['ra'][$ra->path] = array();
  1091. }
  1092. // only add if is not a repeat caused
  1093. // by capability join...
  1094. // (this check is cheaper than in_array())
  1095. if ($lastseen !== $ra->path.':'.$ra->roleid) {
  1096. $lastseen = $ra->path.':'.$ra->roleid;
  1097. array_push($accessdata['ra'][$ra->path], $ra->roleid);
  1098. $parentids = explode('/', $ra->path);
  1099. array_shift($parentids); // drop empty leading "context"
  1100. array_pop($parentids); // drop _this_ context
  1101. if (isset($raparents[$ra->roleid])) {
  1102. $raparents[$ra->roleid] = array_merge($raparents[$ra->roleid],
  1103. $parentids);
  1104. } else {
  1105. $raparents[$ra->roleid] = $parentids;
  1106. }
  1107. }
  1108. // Always add the roleded
  1109. if (!empty($ra->capability)) {
  1110. $k = "{$ra->path}:{$ra->roleid}";
  1111. $accessdata['rdef'][$k][$ra->capability] = $ra->permission;
  1112. }
  1113. }
  1114. unset($ra);
  1115. rs_close($rs);
  1116. }
  1117. // Walk up the tree to grab all the roledefs
  1118. // of interest to our user...
  1119. // NOTE: we use a series of IN clauses here - which
  1120. // might explode on huge sites with very convoluted nesting of
  1121. // categories... - extremely unlikely that the number of categories
  1122. // and roletypes is so large that we hit the limits of IN()
  1123. $clauses = array();
  1124. foreach ($raparents as $roleid=>$contexts) {
  1125. $contexts = implode(',', array_unique($contexts));
  1126. if ($contexts ==! '') {
  1127. $clauses[] = "(roleid=$roleid AND contextid IN ($contexts))";
  1128. }
  1129. }
  1130. $clauses = implode(" OR ", $clauses);
  1131. if ($clauses !== '') {
  1132. $sql = "SELECT ctx.path, rc.roleid, rc.capability, rc.permission
  1133. FROM {$CFG->prefix}role_capabilities rc
  1134. JOIN {$CFG->prefix}context ctx
  1135. ON rc.contextid=ctx.id
  1136. WHERE $clauses
  1137. ORDER BY ctx.depth ASC, ctx.path DESC, rc.roleid ASC ";
  1138. $rs = get_recordset_sql($sql);
  1139. unset($clauses);
  1140. if ($rs) {
  1141. while ($rd = rs_fetch_next_record($rs)) {
  1142. $k = "{$rd->path}:{$rd->roleid}";
  1143. $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
  1144. }
  1145. unset($rd);
  1146. rs_close($rs);
  1147. }
  1148. }
  1149. //
  1150. // Overrides for the role assignments IN SUBCONTEXTS
  1151. // (though we still do _not_ go below the course level.
  1152. //
  1153. // NOTE that the JOIN w sctx is with 3-way triangulation to
  1154. // catch overrides to the applicable role in any subcontext, based
  1155. // on the path field of the parent.
  1156. //
  1157. $sql = "SELECT sctx.path, ra.roleid,
  1158. ctx.path AS parentpath,
  1159. rco.capability, rco.permission
  1160. FROM {$CFG->prefix}role_assignments ra
  1161. JOIN {$CFG->prefix}context ctx
  1162. ON ra.contextid=ctx.id
  1163. JOIN {$CFG->prefix}context sctx
  1164. ON (sctx.path LIKE " . sql_concat('ctx.path',"'/%'"). " )
  1165. JOIN {$CFG->prefix}role_capabilities rco
  1166. ON (rco.roleid=ra.roleid AND rco.contextid=sctx.id)
  1167. WHERE ra.userid = $userid
  1168. AND ctx.contextlevel <= ".CONTEXT_COURSECAT."
  1169. AND sctx.contextlevel <= ".CONTEXT_COURSE."
  1170. ORDER BY sctx.depth, sctx.path, ra.roleid";
  1171. $rs = get_recordset_sql($sql);
  1172. if ($rs) {
  1173. while ($rd = rs_fetch_next_record($rs)) {
  1174. $k = "{$rd->path}:{$rd->roleid}";
  1175. $accessdata['rdef'][$k][$rd->capability] = $rd->permission;
  1176. }
  1177. unset($rd);
  1178. rs_close($rs);
  1179. }
  1180. return $accessdata;
  1181. }
  1182. /**
  1183. * It add to the access ctrl array the data
  1184. * needed by a user for a given context
  1185. *
  1186. * @param $userid integer - the id of the user
  1187. * @param $context context obj - needs path!
  1188. * @param $accessdata array accessdata array
  1189. */
  1190. function load_subcontext($userid, $context, &$accessdata) {
  1191. global $CFG;
  1192. /* Get the additional RAs and relevant rolecaps
  1193. * - role assignments - with role_caps
  1194. * - relevant role caps
  1195. * - above this user's RAs
  1196. * - below this user's RAs - limited to course level
  1197. */
  1198. $base = "/" . SYSCONTEXTID;
  1199. //
  1200. // Replace $context with the target context we will
  1201. // load. Normally, this will be a course context, but

Large files files are truncated, but you can click here to view the full file