PageRenderTime 65ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 1ms

/lib/accesslib.php

http://github.com/moodle/moodle
PHP | 7630 lines | 4256 code | 953 blank | 2421 comment | 816 complexity | 6a179ac9a9da8f1ad2194a8d84323484 MD5 | raw file
Possible License(s): MIT, AGPL-3.0, MPL-2.0-no-copyleft-exception, LGPL-3.0, GPL-3.0, Apache-2.0, LGPL-2.1, BSD-3-Clause

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

  1. <?php
  2. // This file is part of Moodle - http://moodle.org/
  3. //
  4. // Moodle is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // Moodle is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  16. /**
  17. * This file contains functions for managing user access
  18. *
  19. * <b>Public API vs internals</b>
  20. *
  21. * General users probably only care about
  22. *
  23. * Context handling
  24. * - context_course::instance($courseid), context_module::instance($cm->id), context_coursecat::instance($catid)
  25. * - context::instance_by_id($contextid)
  26. * - $context->get_parent_contexts();
  27. * - $context->get_child_contexts();
  28. *
  29. * Whether the user can do something...
  30. * - has_capability()
  31. * - has_any_capability()
  32. * - has_all_capabilities()
  33. * - require_capability()
  34. * - require_login() (from moodlelib)
  35. * - is_enrolled()
  36. * - is_viewing()
  37. * - is_guest()
  38. * - is_siteadmin()
  39. * - isguestuser()
  40. * - isloggedin()
  41. *
  42. * What courses has this user access to?
  43. * - get_enrolled_users()
  44. *
  45. * What users can do X in this context?
  46. * - get_enrolled_users() - at and bellow course context
  47. * - get_users_by_capability() - above course context
  48. *
  49. * Modify roles
  50. * - role_assign()
  51. * - role_unassign()
  52. * - role_unassign_all()
  53. *
  54. * Advanced - for internal use only
  55. * - load_all_capabilities()
  56. * - reload_all_capabilities()
  57. * - has_capability_in_accessdata()
  58. * - get_user_roles_sitewide_accessdata()
  59. * - etc.
  60. *
  61. * <b>Name conventions</b>
  62. *
  63. * "ctx" means context
  64. * "ra" means role assignment
  65. * "rdef" means role definition
  66. *
  67. * <b>accessdata</b>
  68. *
  69. * Access control data is held in the "accessdata" array
  70. * which - for the logged-in user, will be in $USER->access
  71. *
  72. * For other users can be generated and passed around (but may also be cached
  73. * against userid in $ACCESSLIB_PRIVATE->accessdatabyuser).
  74. *
  75. * $accessdata is a multidimensional array, holding
  76. * role assignments (RAs), role switches and initialization time.
  77. *
  78. * Things are keyed on "contextpaths" (the path field of
  79. * the context table) for fast walking up/down the tree.
  80. * <code>
  81. * $accessdata['ra'][$contextpath] = array($roleid=>$roleid)
  82. * [$contextpath] = array($roleid=>$roleid)
  83. * [$contextpath] = array($roleid=>$roleid)
  84. * </code>
  85. *
  86. * <b>Stale accessdata</b>
  87. *
  88. * For the logged-in user, accessdata is long-lived.
  89. *
  90. * On each pageload we load $ACCESSLIB_PRIVATE->dirtycontexts which lists
  91. * context paths affected by changes. Any check at-or-below
  92. * a dirty context will trigger a transparent reload of accessdata.
  93. *
  94. * Changes at the system level will force the reload for everyone.
  95. *
  96. * <b>Default role caps</b>
  97. * The default role assignment is not in the DB, so we
  98. * add it manually to accessdata.
  99. *
  100. * This means that functions that work directly off the
  101. * DB need to ensure that the default role caps
  102. * are dealt with appropriately.
  103. *
  104. * @package core_access
  105. * @copyright 1999 onwards Martin Dougiamas http://dougiamas.com
  106. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  107. */
  108. defined('MOODLE_INTERNAL') || die();
  109. /** No capability change */
  110. define('CAP_INHERIT', 0);
  111. /** Allow permission, overrides CAP_PREVENT defined in parent contexts */
  112. define('CAP_ALLOW', 1);
  113. /** Prevent permission, overrides CAP_ALLOW defined in parent contexts */
  114. define('CAP_PREVENT', -1);
  115. /** Prohibit permission, overrides everything in current and child contexts */
  116. define('CAP_PROHIBIT', -1000);
  117. /** System context level - only one instance in every system */
  118. define('CONTEXT_SYSTEM', 10);
  119. /** User context level - one instance for each user describing what others can do to user */
  120. define('CONTEXT_USER', 30);
  121. /** Course category context level - one instance for each category */
  122. define('CONTEXT_COURSECAT', 40);
  123. /** Course context level - one instances for each course */
  124. define('CONTEXT_COURSE', 50);
  125. /** Course module context level - one instance for each course module */
  126. define('CONTEXT_MODULE', 70);
  127. /**
  128. * Block context level - one instance for each block, sticky blocks are tricky
  129. * because ppl think they should be able to override them at lower contexts.
  130. * Any other context level instance can be parent of block context.
  131. */
  132. define('CONTEXT_BLOCK', 80);
  133. /** Capability allow management of trusts - NOT IMPLEMENTED YET - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
  134. define('RISK_MANAGETRUST', 0x0001);
  135. /** Capability allows changes in system configuration - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
  136. define('RISK_CONFIG', 0x0002);
  137. /** Capability allows user to add scripted content - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
  138. define('RISK_XSS', 0x0004);
  139. /** Capability allows access to personal user information - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
  140. define('RISK_PERSONAL', 0x0008);
  141. /** Capability allows users to add content others may see - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
  142. define('RISK_SPAM', 0x0010);
  143. /** capability allows mass delete of data belonging to other users - see {@link http://docs.moodle.org/dev/Hardening_new_Roles_system} */
  144. define('RISK_DATALOSS', 0x0020);
  145. /** rolename displays - the name as defined in the role definition, localised if name empty */
  146. define('ROLENAME_ORIGINAL', 0);
  147. /** rolename displays - the name as defined by a role alias at the course level, falls back to ROLENAME_ORIGINAL if alias not present */
  148. define('ROLENAME_ALIAS', 1);
  149. /** rolename displays - Both, like this: Role alias (Original) */
  150. define('ROLENAME_BOTH', 2);
  151. /** rolename displays - the name as defined in the role definition and the shortname in brackets */
  152. define('ROLENAME_ORIGINALANDSHORT', 3);
  153. /** rolename displays - the name as defined by a role alias, in raw form suitable for editing */
  154. define('ROLENAME_ALIAS_RAW', 4);
  155. /** rolename displays - the name is simply short role name */
  156. define('ROLENAME_SHORT', 5);
  157. if (!defined('CONTEXT_CACHE_MAX_SIZE')) {
  158. /** maximum size of context cache - it is possible to tweak this config.php or in any script before inclusion of context.php */
  159. define('CONTEXT_CACHE_MAX_SIZE', 2500);
  160. }
  161. /**
  162. * Although this looks like a global variable, it isn't really.
  163. *
  164. * It is just a private implementation detail to accesslib that MUST NOT be used elsewhere.
  165. * It is used to cache various bits of data between function calls for performance reasons.
  166. * Sadly, a PHP global variable is the only way to implement this, without rewriting everything
  167. * as methods of a class, instead of functions.
  168. *
  169. * @access private
  170. * @global stdClass $ACCESSLIB_PRIVATE
  171. * @name $ACCESSLIB_PRIVATE
  172. */
  173. global $ACCESSLIB_PRIVATE;
  174. $ACCESSLIB_PRIVATE = new stdClass();
  175. $ACCESSLIB_PRIVATE->cacheroledefs = array(); // Holds site-wide role definitions.
  176. $ACCESSLIB_PRIVATE->dirtycontexts = null; // Dirty contexts cache, loaded from DB once per page
  177. $ACCESSLIB_PRIVATE->dirtyusers = null; // Dirty users cache, loaded from DB once per $USER->id
  178. $ACCESSLIB_PRIVATE->accessdatabyuser = array(); // Holds the cache of $accessdata structure for users (including $USER)
  179. /**
  180. * Clears accesslib's private caches. ONLY BE USED BY UNIT TESTS
  181. *
  182. * This method should ONLY BE USED BY UNIT TESTS. It clears all of
  183. * accesslib's private caches. You need to do this before setting up test data,
  184. * and also at the end of the tests.
  185. *
  186. * @access private
  187. * @return void
  188. */
  189. function accesslib_clear_all_caches_for_unit_testing() {
  190. global $USER;
  191. if (!PHPUNIT_TEST) {
  192. throw new coding_exception('You must not call clear_all_caches outside of unit tests.');
  193. }
  194. accesslib_clear_all_caches(true);
  195. accesslib_reset_role_cache();
  196. unset($USER->access);
  197. }
  198. /**
  199. * Clears accesslib's private caches. ONLY BE USED FROM THIS LIBRARY FILE!
  200. *
  201. * This reset does not touch global $USER.
  202. *
  203. * @access private
  204. * @param bool $resetcontexts
  205. * @return void
  206. */
  207. function accesslib_clear_all_caches($resetcontexts) {
  208. global $ACCESSLIB_PRIVATE;
  209. $ACCESSLIB_PRIVATE->dirtycontexts = null;
  210. $ACCESSLIB_PRIVATE->dirtyusers = null;
  211. $ACCESSLIB_PRIVATE->accessdatabyuser = array();
  212. if ($resetcontexts) {
  213. context_helper::reset_caches();
  214. }
  215. }
  216. /**
  217. * Full reset of accesslib's private role cache. ONLY TO BE USED FROM THIS LIBRARY FILE!
  218. *
  219. * This reset does not touch global $USER.
  220. *
  221. * Note: Only use this when the roles that need a refresh are unknown.
  222. *
  223. * @see accesslib_clear_role_cache()
  224. *
  225. * @access private
  226. * @return void
  227. */
  228. function accesslib_reset_role_cache() {
  229. global $ACCESSLIB_PRIVATE;
  230. $ACCESSLIB_PRIVATE->cacheroledefs = array();
  231. $cache = cache::make('core', 'roledefs');
  232. $cache->purge();
  233. }
  234. /**
  235. * Clears accesslib's private cache of a specific role or roles. ONLY BE USED FROM THIS LIBRARY FILE!
  236. *
  237. * This reset does not touch global $USER.
  238. *
  239. * @access private
  240. * @param int|array $roles
  241. * @return void
  242. */
  243. function accesslib_clear_role_cache($roles) {
  244. global $ACCESSLIB_PRIVATE;
  245. if (!is_array($roles)) {
  246. $roles = [$roles];
  247. }
  248. foreach ($roles as $role) {
  249. if (isset($ACCESSLIB_PRIVATE->cacheroledefs[$role])) {
  250. unset($ACCESSLIB_PRIVATE->cacheroledefs[$role]);
  251. }
  252. }
  253. $cache = cache::make('core', 'roledefs');
  254. $cache->delete_many($roles);
  255. }
  256. /**
  257. * Role is assigned at system context.
  258. *
  259. * @access private
  260. * @param int $roleid
  261. * @return array
  262. */
  263. function get_role_access($roleid) {
  264. $accessdata = get_empty_accessdata();
  265. $accessdata['ra']['/'.SYSCONTEXTID] = array((int)$roleid => (int)$roleid);
  266. return $accessdata;
  267. }
  268. /**
  269. * Fetch raw "site wide" role definitions.
  270. * Even MUC static acceleration cache appears a bit slow for this.
  271. * Important as can be hit hundreds of times per page.
  272. *
  273. * @param array $roleids List of role ids to fetch definitions for.
  274. * @return array Complete definition for each requested role.
  275. */
  276. function get_role_definitions(array $roleids) {
  277. global $ACCESSLIB_PRIVATE;
  278. if (empty($roleids)) {
  279. return array();
  280. }
  281. // Grab all keys we have not yet got in our static cache.
  282. if ($uncached = array_diff($roleids, array_keys($ACCESSLIB_PRIVATE->cacheroledefs))) {
  283. $cache = cache::make('core', 'roledefs');
  284. foreach ($cache->get_many($uncached) as $roleid => $cachedroledef) {
  285. if (is_array($cachedroledef)) {
  286. $ACCESSLIB_PRIVATE->cacheroledefs[$roleid] = $cachedroledef;
  287. }
  288. }
  289. // Check we have the remaining keys from the MUC.
  290. if ($uncached = array_diff($roleids, array_keys($ACCESSLIB_PRIVATE->cacheroledefs))) {
  291. $uncached = get_role_definitions_uncached($uncached);
  292. $ACCESSLIB_PRIVATE->cacheroledefs += $uncached;
  293. $cache->set_many($uncached);
  294. }
  295. }
  296. // Return just the roles we need.
  297. return array_intersect_key($ACCESSLIB_PRIVATE->cacheroledefs, array_flip($roleids));
  298. }
  299. /**
  300. * Query raw "site wide" role definitions.
  301. *
  302. * @param array $roleids List of role ids to fetch definitions for.
  303. * @return array Complete definition for each requested role.
  304. */
  305. function get_role_definitions_uncached(array $roleids) {
  306. global $DB;
  307. if (empty($roleids)) {
  308. return array();
  309. }
  310. // Create a blank results array: even if a role has no capabilities,
  311. // we need to ensure it is included in the results to show we have
  312. // loaded all the capabilities that there are.
  313. $rdefs = array();
  314. foreach ($roleids as $roleid) {
  315. $rdefs[$roleid] = array();
  316. }
  317. // Load all the capabilities for these roles in all contexts.
  318. list($sql, $params) = $DB->get_in_or_equal($roleids);
  319. $sql = "SELECT ctx.path, rc.roleid, rc.capability, rc.permission
  320. FROM {role_capabilities} rc
  321. JOIN {context} ctx ON rc.contextid = ctx.id
  322. JOIN {capabilities} cap ON rc.capability = cap.name
  323. WHERE rc.roleid $sql";
  324. $rs = $DB->get_recordset_sql($sql, $params);
  325. // Store the capabilities into the expected data structure.
  326. foreach ($rs as $rd) {
  327. if (!isset($rdefs[$rd->roleid][$rd->path])) {
  328. $rdefs[$rd->roleid][$rd->path] = array();
  329. }
  330. $rdefs[$rd->roleid][$rd->path][$rd->capability] = (int) $rd->permission;
  331. }
  332. $rs->close();
  333. // Sometimes (e.g. get_user_capability_course_helper::get_capability_info_at_each_context)
  334. // we process role definitinons in a way that requires we see parent contexts
  335. // before child contexts. This sort ensures that works (and is faster than
  336. // sorting in the SQL query).
  337. foreach ($rdefs as $roleid => $rdef) {
  338. ksort($rdefs[$roleid]);
  339. }
  340. return $rdefs;
  341. }
  342. /**
  343. * Get the default guest role, this is used for guest account,
  344. * search engine spiders, etc.
  345. *
  346. * @return stdClass role record
  347. */
  348. function get_guest_role() {
  349. global $CFG, $DB;
  350. if (empty($CFG->guestroleid)) {
  351. if ($roles = $DB->get_records('role', array('archetype'=>'guest'))) {
  352. $guestrole = array_shift($roles); // Pick the first one
  353. set_config('guestroleid', $guestrole->id);
  354. return $guestrole;
  355. } else {
  356. debugging('Can not find any guest role!');
  357. return false;
  358. }
  359. } else {
  360. if ($guestrole = $DB->get_record('role', array('id'=>$CFG->guestroleid))) {
  361. return $guestrole;
  362. } else {
  363. // somebody is messing with guest roles, remove incorrect setting and try to find a new one
  364. set_config('guestroleid', '');
  365. return get_guest_role();
  366. }
  367. }
  368. }
  369. /**
  370. * Check whether a user has a particular capability in a given context.
  371. *
  372. * For example:
  373. * $context = context_module::instance($cm->id);
  374. * has_capability('mod/forum:replypost', $context)
  375. *
  376. * By default checks the capabilities of the current user, but you can pass a
  377. * different userid. By default will return true for admin users, but you can override that with the fourth argument.
  378. *
  379. * Guest and not-logged-in users can never get any dangerous capability - that is any write capability
  380. * or capabilities with XSS, config or data loss risks.
  381. *
  382. * @category access
  383. *
  384. * @param string $capability the name of the capability to check. For example mod/forum:view
  385. * @param context $context the context to check the capability in. You normally get this with instance method of a context class.
  386. * @param integer|stdClass $user A user id or object. By default (null) checks the permissions of the current user.
  387. * @param boolean $doanything If false, ignores effect of admin role assignment
  388. * @return boolean true if the user has this capability. Otherwise false.
  389. */
  390. function has_capability($capability, context $context, $user = null, $doanything = true) {
  391. global $USER, $CFG, $SCRIPT, $ACCESSLIB_PRIVATE;
  392. if (during_initial_install()) {
  393. if ($SCRIPT === "/$CFG->admin/index.php"
  394. or $SCRIPT === "/$CFG->admin/cli/install.php"
  395. or $SCRIPT === "/$CFG->admin/cli/install_database.php"
  396. or (defined('BEHAT_UTIL') and BEHAT_UTIL)
  397. or (defined('PHPUNIT_UTIL') and PHPUNIT_UTIL)) {
  398. // we are in an installer - roles can not work yet
  399. return true;
  400. } else {
  401. return false;
  402. }
  403. }
  404. if (strpos($capability, 'moodle/legacy:') === 0) {
  405. throw new coding_exception('Legacy capabilities can not be used any more!');
  406. }
  407. if (!is_bool($doanything)) {
  408. throw new coding_exception('Capability parameter "doanything" is wierd, only true or false is allowed. This has to be fixed in code.');
  409. }
  410. // capability must exist
  411. if (!$capinfo = get_capability_info($capability)) {
  412. debugging('Capability "'.$capability.'" was not found! This has to be fixed in code.');
  413. return false;
  414. }
  415. if (!isset($USER->id)) {
  416. // should never happen
  417. $USER->id = 0;
  418. debugging('Capability check being performed on a user with no ID.', DEBUG_DEVELOPER);
  419. }
  420. // make sure there is a real user specified
  421. if ($user === null) {
  422. $userid = $USER->id;
  423. } else {
  424. $userid = is_object($user) ? $user->id : $user;
  425. }
  426. // make sure forcelogin cuts off not-logged-in users if enabled
  427. if (!empty($CFG->forcelogin) and $userid == 0) {
  428. return false;
  429. }
  430. // make sure the guest account and not-logged-in users never get any risky caps no matter what the actual settings are.
  431. if (($capinfo->captype === 'write') or ($capinfo->riskbitmask & (RISK_XSS | RISK_CONFIG | RISK_DATALOSS))) {
  432. if (isguestuser($userid) or $userid == 0) {
  433. return false;
  434. }
  435. }
  436. // Check whether context locking is enabled.
  437. if (!empty($CFG->contextlocking)) {
  438. if ($capinfo->captype === 'write' && $context->locked) {
  439. // Context locking applies to any write capability in a locked context.
  440. // It does not apply to moodle/site:managecontextlocks - this is to allow context locking to be unlocked.
  441. if ($capinfo->name !== 'moodle/site:managecontextlocks') {
  442. // It applies to all users who are not site admins.
  443. // It also applies to site admins when contextlockappliestoadmin is set.
  444. if (!is_siteadmin($userid) || !empty($CFG->contextlockappliestoadmin)) {
  445. return false;
  446. }
  447. }
  448. }
  449. }
  450. // somehow make sure the user is not deleted and actually exists
  451. if ($userid != 0) {
  452. if ($userid == $USER->id and isset($USER->deleted)) {
  453. // this prevents one query per page, it is a bit of cheating,
  454. // but hopefully session is terminated properly once user is deleted
  455. if ($USER->deleted) {
  456. return false;
  457. }
  458. } else {
  459. if (!context_user::instance($userid, IGNORE_MISSING)) {
  460. // no user context == invalid userid
  461. return false;
  462. }
  463. }
  464. }
  465. // context path/depth must be valid
  466. if (empty($context->path) or $context->depth == 0) {
  467. // this should not happen often, each upgrade tries to rebuild the context paths
  468. debugging('Context id '.$context->id.' does not have valid path, please use context_helper::build_all_paths()');
  469. if (is_siteadmin($userid)) {
  470. return true;
  471. } else {
  472. return false;
  473. }
  474. }
  475. // Find out if user is admin - it is not possible to override the doanything in any way
  476. // and it is not possible to switch to admin role either.
  477. if ($doanything) {
  478. if (is_siteadmin($userid)) {
  479. if ($userid != $USER->id) {
  480. return true;
  481. }
  482. // make sure switchrole is not used in this context
  483. if (empty($USER->access['rsw'])) {
  484. return true;
  485. }
  486. $parts = explode('/', trim($context->path, '/'));
  487. $path = '';
  488. $switched = false;
  489. foreach ($parts as $part) {
  490. $path .= '/' . $part;
  491. if (!empty($USER->access['rsw'][$path])) {
  492. $switched = true;
  493. break;
  494. }
  495. }
  496. if (!$switched) {
  497. return true;
  498. }
  499. //ok, admin switched role in this context, let's use normal access control rules
  500. }
  501. }
  502. // Careful check for staleness...
  503. $context->reload_if_dirty();
  504. if ($USER->id == $userid) {
  505. if (!isset($USER->access)) {
  506. load_all_capabilities();
  507. }
  508. $access =& $USER->access;
  509. } else {
  510. // make sure user accessdata is really loaded
  511. get_user_accessdata($userid, true);
  512. $access =& $ACCESSLIB_PRIVATE->accessdatabyuser[$userid];
  513. }
  514. return has_capability_in_accessdata($capability, $context, $access);
  515. }
  516. /**
  517. * Check if the user has any one of several capabilities from a list.
  518. *
  519. * This is just a utility method that calls has_capability in a loop. Try to put
  520. * the capabilities that most users are likely to have first in the list for best
  521. * performance.
  522. *
  523. * @category access
  524. * @see has_capability()
  525. *
  526. * @param array $capabilities an array of capability names.
  527. * @param context $context the context to check the capability in. You normally get this with instance method of a context class.
  528. * @param integer|stdClass $user A user id or object. By default (null) checks the permissions of the current user.
  529. * @param boolean $doanything If false, ignore effect of admin role assignment
  530. * @return boolean true if the user has any of these capabilities. Otherwise false.
  531. */
  532. function has_any_capability(array $capabilities, context $context, $user = null, $doanything = true) {
  533. foreach ($capabilities as $capability) {
  534. if (has_capability($capability, $context, $user, $doanything)) {
  535. return true;
  536. }
  537. }
  538. return false;
  539. }
  540. /**
  541. * Check if the user has all the capabilities in a list.
  542. *
  543. * This is just a utility method that calls has_capability in a loop. Try to put
  544. * the capabilities that fewest users are likely to have first in the list for best
  545. * performance.
  546. *
  547. * @category access
  548. * @see has_capability()
  549. *
  550. * @param array $capabilities an array of capability names.
  551. * @param context $context the context to check the capability in. You normally get this with instance method of a context class.
  552. * @param integer|stdClass $user A user id or object. By default (null) checks the permissions of the current user.
  553. * @param boolean $doanything If false, ignore effect of admin role assignment
  554. * @return boolean true if the user has all of these capabilities. Otherwise false.
  555. */
  556. function has_all_capabilities(array $capabilities, context $context, $user = null, $doanything = true) {
  557. foreach ($capabilities as $capability) {
  558. if (!has_capability($capability, $context, $user, $doanything)) {
  559. return false;
  560. }
  561. }
  562. return true;
  563. }
  564. /**
  565. * Is course creator going to have capability in a new course?
  566. *
  567. * This is intended to be used in enrolment plugins before or during course creation,
  568. * do not use after the course is fully created.
  569. *
  570. * @category access
  571. *
  572. * @param string $capability the name of the capability to check.
  573. * @param context $context course or category context where is course going to be created
  574. * @param integer|stdClass $user A user id or object. By default (null) checks the permissions of the current user.
  575. * @return boolean true if the user will have this capability.
  576. *
  577. * @throws coding_exception if different type of context submitted
  578. */
  579. function guess_if_creator_will_have_course_capability($capability, context $context, $user = null) {
  580. global $CFG;
  581. if ($context->contextlevel != CONTEXT_COURSE and $context->contextlevel != CONTEXT_COURSECAT) {
  582. throw new coding_exception('Only course or course category context expected');
  583. }
  584. if (has_capability($capability, $context, $user)) {
  585. // User already has the capability, it could be only removed if CAP_PROHIBIT
  586. // was involved here, but we ignore that.
  587. return true;
  588. }
  589. if (!has_capability('moodle/course:create', $context, $user)) {
  590. return false;
  591. }
  592. if (!enrol_is_enabled('manual')) {
  593. return false;
  594. }
  595. if (empty($CFG->creatornewroleid)) {
  596. return false;
  597. }
  598. if ($context->contextlevel == CONTEXT_COURSE) {
  599. if (is_viewing($context, $user, 'moodle/role:assign') or is_enrolled($context, $user, 'moodle/role:assign')) {
  600. return false;
  601. }
  602. } else {
  603. if (has_capability('moodle/course:view', $context, $user) and has_capability('moodle/role:assign', $context, $user)) {
  604. return false;
  605. }
  606. }
  607. // Most likely they will be enrolled after the course creation is finished,
  608. // does the new role have the required capability?
  609. list($neededroles, $forbiddenroles) = get_roles_with_cap_in_context($context, $capability);
  610. return isset($neededroles[$CFG->creatornewroleid]);
  611. }
  612. /**
  613. * Check if the user is an admin at the site level.
  614. *
  615. * Please note that use of proper capabilities is always encouraged,
  616. * this function is supposed to be used from core or for temporary hacks.
  617. *
  618. * @category access
  619. *
  620. * @param int|stdClass $user_or_id user id or user object
  621. * @return bool true if user is one of the administrators, false otherwise
  622. */
  623. function is_siteadmin($user_or_id = null) {
  624. global $CFG, $USER;
  625. if ($user_or_id === null) {
  626. $user_or_id = $USER;
  627. }
  628. if (empty($user_or_id)) {
  629. return false;
  630. }
  631. if (!empty($user_or_id->id)) {
  632. $userid = $user_or_id->id;
  633. } else {
  634. $userid = $user_or_id;
  635. }
  636. // Because this script is called many times (150+ for course page) with
  637. // the same parameters, it is worth doing minor optimisations. This static
  638. // cache stores the value for a single userid, saving about 2ms from course
  639. // page load time without using significant memory. As the static cache
  640. // also includes the value it depends on, this cannot break unit tests.
  641. static $knownid, $knownresult, $knownsiteadmins;
  642. if ($knownid === $userid && $knownsiteadmins === $CFG->siteadmins) {
  643. return $knownresult;
  644. }
  645. $knownid = $userid;
  646. $knownsiteadmins = $CFG->siteadmins;
  647. $siteadmins = explode(',', $CFG->siteadmins);
  648. $knownresult = in_array($userid, $siteadmins);
  649. return $knownresult;
  650. }
  651. /**
  652. * Returns true if user has at least one role assign
  653. * of 'coursecontact' role (is potentially listed in some course descriptions).
  654. *
  655. * @param int $userid
  656. * @return bool
  657. */
  658. function has_coursecontact_role($userid) {
  659. global $DB, $CFG;
  660. if (empty($CFG->coursecontact)) {
  661. return false;
  662. }
  663. $sql = "SELECT 1
  664. FROM {role_assignments}
  665. WHERE userid = :userid AND roleid IN ($CFG->coursecontact)";
  666. return $DB->record_exists_sql($sql, array('userid'=>$userid));
  667. }
  668. /**
  669. * Does the user have a capability to do something?
  670. *
  671. * Walk the accessdata array and return true/false.
  672. * Deals with prohibits, role switching, aggregating
  673. * capabilities, etc.
  674. *
  675. * The main feature of here is being FAST and with no
  676. * side effects.
  677. *
  678. * Notes:
  679. *
  680. * Switch Role merges with default role
  681. * ------------------------------------
  682. * If you are a teacher in course X, you have at least
  683. * teacher-in-X + defaultloggedinuser-sitewide. So in the
  684. * course you'll have techer+defaultloggedinuser.
  685. * We try to mimic that in switchrole.
  686. *
  687. * Permission evaluation
  688. * ---------------------
  689. * Originally there was an extremely complicated way
  690. * to determine the user access that dealt with
  691. * "locality" or role assignments and role overrides.
  692. * Now we simply evaluate access for each role separately
  693. * and then verify if user has at least one role with allow
  694. * and at the same time no role with prohibit.
  695. *
  696. * @access private
  697. * @param string $capability
  698. * @param context $context
  699. * @param array $accessdata
  700. * @return bool
  701. */
  702. function has_capability_in_accessdata($capability, context $context, array &$accessdata) {
  703. global $CFG;
  704. // Build $paths as a list of current + all parent "paths" with order bottom-to-top
  705. $path = $context->path;
  706. $paths = array($path);
  707. while ($path = rtrim($path, '0123456789')) {
  708. $path = rtrim($path, '/');
  709. if ($path === '') {
  710. break;
  711. }
  712. $paths[] = $path;
  713. }
  714. $roles = array();
  715. $switchedrole = false;
  716. // Find out if role switched
  717. if (!empty($accessdata['rsw'])) {
  718. // From the bottom up...
  719. foreach ($paths as $path) {
  720. if (isset($accessdata['rsw'][$path])) {
  721. // Found a switchrole assignment - check for that role _plus_ the default user role
  722. $roles = array($accessdata['rsw'][$path]=>null, $CFG->defaultuserroleid=>null);
  723. $switchedrole = true;
  724. break;
  725. }
  726. }
  727. }
  728. if (!$switchedrole) {
  729. // get all users roles in this context and above
  730. foreach ($paths as $path) {
  731. if (isset($accessdata['ra'][$path])) {
  732. foreach ($accessdata['ra'][$path] as $roleid) {
  733. $roles[$roleid] = null;
  734. }
  735. }
  736. }
  737. }
  738. // Now find out what access is given to each role, going bottom-->up direction
  739. $rdefs = get_role_definitions(array_keys($roles));
  740. $allowed = false;
  741. foreach ($roles as $roleid => $ignored) {
  742. foreach ($paths as $path) {
  743. if (isset($rdefs[$roleid][$path][$capability])) {
  744. $perm = (int)$rdefs[$roleid][$path][$capability];
  745. if ($perm === CAP_PROHIBIT) {
  746. // any CAP_PROHIBIT found means no permission for the user
  747. return false;
  748. }
  749. if (is_null($roles[$roleid])) {
  750. $roles[$roleid] = $perm;
  751. }
  752. }
  753. }
  754. // CAP_ALLOW in any role means the user has a permission, we continue only to detect prohibits
  755. $allowed = ($allowed or $roles[$roleid] === CAP_ALLOW);
  756. }
  757. return $allowed;
  758. }
  759. /**
  760. * A convenience function that tests has_capability, and displays an error if
  761. * the user does not have that capability.
  762. *
  763. * NOTE before Moodle 2.0, this function attempted to make an appropriate
  764. * require_login call before checking the capability. This is no longer the case.
  765. * You must call require_login (or one of its variants) if you want to check the
  766. * user is logged in, before you call this function.
  767. *
  768. * @see has_capability()
  769. *
  770. * @param string $capability the name of the capability to check. For example mod/forum:view
  771. * @param context $context the context to check the capability in. You normally get this with context_xxxx::instance().
  772. * @param int $userid A user id. By default (null) checks the permissions of the current user.
  773. * @param bool $doanything If false, ignore effect of admin role assignment
  774. * @param string $errormessage The error string to to user. Defaults to 'nopermissions'.
  775. * @param string $stringfile The language file to load the error string from. Defaults to 'error'.
  776. * @return void terminates with an error if the user does not have the given capability.
  777. */
  778. function require_capability($capability, context $context, $userid = null, $doanything = true,
  779. $errormessage = 'nopermissions', $stringfile = '') {
  780. if (!has_capability($capability, $context, $userid, $doanything)) {
  781. throw new required_capability_exception($context, $capability, $errormessage, $stringfile);
  782. }
  783. }
  784. /**
  785. * Return a nested array showing all role assignments for the user.
  786. * [ra] => [contextpath][roleid] = roleid
  787. *
  788. * @access private
  789. * @param int $userid - the id of the user
  790. * @return array access info array
  791. */
  792. function get_user_roles_sitewide_accessdata($userid) {
  793. global $CFG, $DB;
  794. $accessdata = get_empty_accessdata();
  795. // start with the default role
  796. if (!empty($CFG->defaultuserroleid)) {
  797. $syscontext = context_system::instance();
  798. $accessdata['ra'][$syscontext->path][(int)$CFG->defaultuserroleid] = (int)$CFG->defaultuserroleid;
  799. }
  800. // load the "default frontpage role"
  801. if (!empty($CFG->defaultfrontpageroleid)) {
  802. $frontpagecontext = context_course::instance(get_site()->id);
  803. if ($frontpagecontext->path) {
  804. $accessdata['ra'][$frontpagecontext->path][(int)$CFG->defaultfrontpageroleid] = (int)$CFG->defaultfrontpageroleid;
  805. }
  806. }
  807. // Preload every assigned role.
  808. $sql = "SELECT ctx.path, ra.roleid, ra.contextid
  809. FROM {role_assignments} ra
  810. JOIN {context} ctx ON ctx.id = ra.contextid
  811. WHERE ra.userid = :userid";
  812. $rs = $DB->get_recordset_sql($sql, array('userid' => $userid));
  813. foreach ($rs as $ra) {
  814. // RAs leafs are arrays to support multi-role assignments...
  815. $accessdata['ra'][$ra->path][(int)$ra->roleid] = (int)$ra->roleid;
  816. }
  817. $rs->close();
  818. return $accessdata;
  819. }
  820. /**
  821. * Returns empty accessdata structure.
  822. *
  823. * @access private
  824. * @return array empt accessdata
  825. */
  826. function get_empty_accessdata() {
  827. $accessdata = array(); // named list
  828. $accessdata['ra'] = array();
  829. $accessdata['time'] = time();
  830. $accessdata['rsw'] = array();
  831. return $accessdata;
  832. }
  833. /**
  834. * Get accessdata for a given user.
  835. *
  836. * @access private
  837. * @param int $userid
  838. * @param bool $preloadonly true means do not return access array
  839. * @return array accessdata
  840. */
  841. function get_user_accessdata($userid, $preloadonly=false) {
  842. global $CFG, $ACCESSLIB_PRIVATE, $USER;
  843. if (isset($USER->access)) {
  844. $ACCESSLIB_PRIVATE->accessdatabyuser[$USER->id] = $USER->access;
  845. }
  846. if (!isset($ACCESSLIB_PRIVATE->accessdatabyuser[$userid])) {
  847. if (empty($userid)) {
  848. if (!empty($CFG->notloggedinroleid)) {
  849. $accessdata = get_role_access($CFG->notloggedinroleid);
  850. } else {
  851. // weird
  852. return get_empty_accessdata();
  853. }
  854. } else if (isguestuser($userid)) {
  855. if ($guestrole = get_guest_role()) {
  856. $accessdata = get_role_access($guestrole->id);
  857. } else {
  858. //weird
  859. return get_empty_accessdata();
  860. }
  861. } else {
  862. // Includes default role and frontpage role.
  863. $accessdata = get_user_roles_sitewide_accessdata($userid);
  864. }
  865. $ACCESSLIB_PRIVATE->accessdatabyuser[$userid] = $accessdata;
  866. }
  867. if ($preloadonly) {
  868. return;
  869. } else {
  870. return $ACCESSLIB_PRIVATE->accessdatabyuser[$userid];
  871. }
  872. }
  873. /**
  874. * A convenience function to completely load all the capabilities
  875. * for the current user. It is called from has_capability() and functions change permissions.
  876. *
  877. * Call it only _after_ you've setup $USER and called check_enrolment_plugins();
  878. * @see check_enrolment_plugins()
  879. *
  880. * @access private
  881. * @return void
  882. */
  883. function load_all_capabilities() {
  884. global $USER;
  885. // roles not installed yet - we are in the middle of installation
  886. if (during_initial_install()) {
  887. return;
  888. }
  889. if (!isset($USER->id)) {
  890. // this should not happen
  891. $USER->id = 0;
  892. }
  893. unset($USER->access);
  894. $USER->access = get_user_accessdata($USER->id);
  895. // Clear to force a refresh
  896. unset($USER->mycourses);
  897. // init/reset internal enrol caches - active course enrolments and temp access
  898. $USER->enrol = array('enrolled'=>array(), 'tempguest'=>array());
  899. }
  900. /**
  901. * A convenience function to completely reload all the capabilities
  902. * for the current user when roles have been updated in a relevant
  903. * context -- but PRESERVING switchroles and loginas.
  904. * This function resets all accesslib and context caches.
  905. *
  906. * That is - completely transparent to the user.
  907. *
  908. * Note: reloads $USER->access completely.
  909. *
  910. * @access private
  911. * @return void
  912. */
  913. function reload_all_capabilities() {
  914. global $USER, $DB, $ACCESSLIB_PRIVATE;
  915. // copy switchroles
  916. $sw = array();
  917. if (!empty($USER->access['rsw'])) {
  918. $sw = $USER->access['rsw'];
  919. }
  920. accesslib_clear_all_caches(true);
  921. unset($USER->access);
  922. // Prevent dirty flags refetching on this page.
  923. $ACCESSLIB_PRIVATE->dirtycontexts = array();
  924. $ACCESSLIB_PRIVATE->dirtyusers = array($USER->id => false);
  925. load_all_capabilities();
  926. foreach ($sw as $path => $roleid) {
  927. if ($record = $DB->get_record('context', array('path'=>$path))) {
  928. $context = context::instance_by_id($record->id);
  929. if (has_capability('moodle/role:switchroles', $context)) {
  930. role_switch($roleid, $context);
  931. }
  932. }
  933. }
  934. }
  935. /**
  936. * Adds a temp role to current USER->access array.
  937. *
  938. * Useful for the "temporary guest" access we grant to logged-in users.
  939. * This is useful for enrol plugins only.
  940. *
  941. * @since Moodle 2.2
  942. * @param context_course $coursecontext
  943. * @param int $roleid
  944. * @return void
  945. */
  946. function load_temp_course_role(context_course $coursecontext, $roleid) {
  947. global $USER, $SITE;
  948. if (empty($roleid)) {
  949. debugging('invalid role specified in load_temp_course_role()');
  950. return;
  951. }
  952. if ($coursecontext->instanceid == $SITE->id) {
  953. debugging('Can not use temp roles on the frontpage');
  954. return;
  955. }
  956. if (!isset($USER->access)) {
  957. load_all_capabilities();
  958. }
  959. $coursecontext->reload_if_dirty();
  960. if (isset($USER->access['ra'][$coursecontext->path][$roleid])) {
  961. return;
  962. }
  963. $USER->access['ra'][$coursecontext->path][(int)$roleid] = (int)$roleid;
  964. }
  965. /**
  966. * Removes any extra guest roles from current USER->access array.
  967. * This is useful for enrol plugins only.
  968. *
  969. * @since Moodle 2.2
  970. * @param context_course $coursecontext
  971. * @return void
  972. */
  973. function remove_temp_course_roles(context_course $coursecontext) {
  974. global $DB, $USER, $SITE;
  975. if ($coursecontext->instanceid == $SITE->id) {
  976. debugging('Can not use temp roles on the frontpage');
  977. return;
  978. }
  979. if (empty($USER->access['ra'][$coursecontext->path])) {
  980. //no roles here, weird
  981. return;
  982. }
  983. $sql = "SELECT DISTINCT ra.roleid AS id
  984. FROM {role_assignments} ra
  985. WHERE ra.contextid = :contextid AND ra.userid = :userid";
  986. $ras = $DB->get_records_sql($sql, array('contextid'=>$coursecontext->id, 'userid'=>$USER->id));
  987. $USER->access['ra'][$coursecontext->path] = array();
  988. foreach ($ras as $r) {
  989. $USER->access['ra'][$coursecontext->path][(int)$r->id] = (int)$r->id;
  990. }
  991. }
  992. /**
  993. * Returns array of all role archetypes.
  994. *
  995. * @return array
  996. */
  997. function get_role_archetypes() {
  998. return array(
  999. 'manager' => 'manager',
  1000. 'coursecreator' => 'coursecreator',
  1001. 'editingteacher' => 'editingteacher',
  1002. 'teacher' => 'teacher',
  1003. 'student' => 'student',
  1004. 'guest' => 'guest',
  1005. 'user' => 'user',
  1006. 'frontpage' => 'frontpage'
  1007. );
  1008. }
  1009. /**
  1010. * Assign the defaults found in this capability definition to roles that have
  1011. * the corresponding legacy capabilities assigned to them.
  1012. *
  1013. * @param string $capability
  1014. * @param array $legacyperms an array in the format (example):
  1015. * 'guest' => CAP_PREVENT,
  1016. * 'student' => CAP_ALLOW,
  1017. * 'teacher' => CAP_ALLOW,
  1018. * 'editingteacher' => CAP_ALLOW,
  1019. * 'coursecreator' => CAP_ALLOW,
  1020. * 'manager' => CAP_ALLOW
  1021. * @return boolean success or failure.
  1022. */
  1023. function assign_legacy_capabilities($capability, $legacyperms) {
  1024. $archetypes = get_role_archetypes();
  1025. foreach ($legacyperms as $type => $perm) {
  1026. $systemcontext = context_system::instance();
  1027. if ($type === 'admin') {
  1028. debugging('Legacy type admin in access.php was renamed to manager, please update the code.');
  1029. $type = 'manager';
  1030. }
  1031. if (!array_key_exists($type, $archetypes)) {
  1032. print_error('invalidlegacy', '', '', $type);
  1033. }
  1034. if ($roles = get_archetype_roles($type)) {
  1035. foreach ($roles as $role) {
  1036. // Assign a site level capability.
  1037. if (!assign_capability($capability, $perm, $role->id, $systemcontext->id)) {
  1038. return false;
  1039. }
  1040. }
  1041. }
  1042. }
  1043. return true;
  1044. }
  1045. /**
  1046. * Verify capability risks.
  1047. *
  1048. * @param stdClass $capability a capability - a row from the capabilities table.
  1049. * @return boolean whether this capability is safe - that is, whether people with the
  1050. * safeoverrides capability should be allowed to change it.
  1051. */
  1052. function is_safe_capability($capability) {
  1053. return !((RISK_DATALOSS | RISK_MANAGETRUST | RISK_CONFIG | RISK_XSS | RISK_PERSONAL) & $capability->riskbitmask);
  1054. }
  1055. /**
  1056. * Get the local override (if any) for a given capability in a role in a context
  1057. *
  1058. * @param int $roleid
  1059. * @param int $contextid
  1060. * @param string $capability
  1061. * @return stdClass local capability override
  1062. */
  1063. function get_local_override($roleid, $contextid, $capability) {
  1064. global $DB;
  1065. return $DB->get_record_sql("
  1066. SELECT rc.*
  1067. FROM {role_capabilities} rc
  1068. JOIN {capability} cap ON rc.capability = cap.name
  1069. WHERE rc.roleid = :roleid AND rc.capability = :capability AND rc.contextid = :contextid", [
  1070. 'roleid' => $roleid,
  1071. 'contextid' => $contextid,
  1072. 'capability' => $capability,
  1073. ]);
  1074. }
  1075. /**
  1076. * Returns context instance plus related course and cm instances
  1077. *
  1078. * @param int $contextid
  1079. * @return array of ($context, $course, $cm)
  1080. */
  1081. function get_context_info_array($contextid) {
  1082. global $DB;
  1083. $context = context::instance_by_id($contextid, MUST_EXIST);
  1084. $course = null;
  1085. $cm = null;
  1086. if ($context->contextlevel == CONTEXT_COURSE) {
  1087. $course = $DB->get_record('course', array('id'=>$context->instanceid), '*', MUST_EXIST);
  1088. } else if ($context->contextlevel == CONTEXT_MODULE) {
  1089. $cm = get_coursemodule_from_id('', $context->instanceid, 0, false, MUST_EXIST);
  1090. $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
  1091. } else if ($context->contextlevel == CONTEXT_BLOCK) {
  1092. $parent = $context->get_parent_context();
  1093. if ($parent->contextlevel == CONTEXT_COURSE) {
  1094. $course = $DB->get_record('course', array('id'=>$parent->instanceid), '*', MUST_EXIST);
  1095. } else if ($parent->contextlevel == CONTEXT_MODULE) {
  1096. $cm = get_coursemodule_from_id('', $parent->instanceid, 0, false, MUST_EXIST);
  1097. $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
  1098. }
  1099. }
  1100. return array($context, $course, $cm);
  1101. }
  1102. /**
  1103. * Function that creates a role
  1104. *
  1105. * @param string $name role name
  1106. * @param string $shortname role short name
  1107. * @param string $description role description
  1108. * @param string $archetype
  1109. * @return int id or dml_exception
  1110. */
  1111. function create_role($name, $shortname, $description, $archetype = '') {
  1112. global $DB;
  1113. if (strpos($archetype, 'moodle/legacy:') !== false) {
  1114. throw new coding_exception('Use new role archetype parameter in create_role() instead of old legacy capabilities.');
  1115. }
  1116. // verify role archetype actually exists
  1117. $archetypes = get_role_archetypes();
  1118. if (empty($archetypes[$archetype])) {
  1119. $archetype = '';
  1120. }
  1121. // Insert the role record.
  1122. $role = new stdClass();
  1123. $role->name = $name;
  1124. $role->shortname = $shortname;
  1125. $role->description = $description;
  1126. $role->archetype = $archetype;
  1127. //find free sortorder number
  1128. $role->sortorder = $DB->get_field('role', 'MAX(sortorder) + 1', array());
  1129. if (empty($role->sortorder)) {
  1130. $role->sortorder = 1;
  1131. }
  1132. $id = $DB->insert_record('role', $role);
  1133. return $id;
  1134. }
  1135. /**
  1136. * Function that deletes a role and cleanups up after it
  1137. *
  1138. * @param int $roleid id of role to delete
  1139. * @return bool always true
  1140. */
  1141. function delete_role($roleid) {
  1142. global $DB;
  1143. // first unssign all users
  1144. role_unassign_all(array('roleid'=>$roleid));
  1145. // cleanup all references to this role, ignore errors
  1146. $DB->delete_records('role_capabilities', array('roleid'=>$roleid));
  1147. $DB->delete_records('role_allow_assign', array('roleid'=>$roleid));
  1148. $DB->delete_records('role_allow_assign', array('allowassign'=>$roleid));
  1149. $DB->delete_records('role_allow_override', array('roleid'=>$roleid));
  1150. $DB->delete_records('role_allow_override', array('allowoverride'=>$roleid));
  1151. $DB->delete_records('role_names', array('roleid'=>$roleid));
  1152. $DB->delete_records('role_context_levels', array('roleid'=>$roleid));
  1153. // Get role record before it's deleted.
  1154. $role = $DB->get_record('role', array('id'=>$roleid));
  1155. // Finally delete the role itself.
  1156. $DB->delete_records('role', array('id'=>$roleid));
  1157. // Trigger event.
  1158. $event = \core\event\role_deleted::create(
  1159. array(
  1160. 'context' => context_system::instance(),
  1161. 'objectid' => $roleid,
  1162. 'other' =>
  1163. array(
  1164. 'shortname' => $role->shortname,
  1165. 'description' => $role->description,
  1166. 'archetype' => $role->archetype
  1167. )
  1168. )
  1169. );
  1170. $event->add_record_snapshot('role', $role);
  1171. $event->trigger();
  1172. // Reset any cache of this role, including MUC.
  1173. accesslib_clear_role_cache($roleid);
  1174. return true;
  1175. }
  1176. /**
  1177. * Function to write context specific overrides, or default capabilities.
  1178. *
  1179. * @param string $capability string name
  1180. * @param int $permission CAP_ constants
  1181. * @param int $roleid role id
  1182. * @param int|context $contextid context id
  1183. * @param bool $overwrite
  1184. * @return bool always true or exception
  1185. */
  1186. function assign_capability($capability, $permission, $roleid, $contextid, $overwrite = false) {
  1187. global $USER, $DB;
  1188. if ($contextid instanceof context) {
  1189. $context = $contextid;
  1190. } else {
  1191. $context = context::instance_by_id($contextid);
  1192. }
  1193. // Capability must exist.
  1194. if (!$capinfo = get_capability_info($capability)) {
  1195. throw new coding_exception("Capability '{$capability}' was not found! This has to be fixed in code.");
  1196. }
  1197. if (empty($permission) || $permission == CAP_INHERIT) { // if permission is not set
  1198. unassign_capability($capability, $roleid, $context->id);
  1199. return true;
  1200. }
  1201. $existing = $DB->get_record('role_capabilities', array('contextid'=>$context->id, 'roleid'=>$roleid, 'capability'=>$capability));
  1202. if ($existing and !$overwrite) { // We want to keep whatever is there already
  1203. return true;
  1204. }
  1205. $cap = new stdClass();
  1206. $cap->contextid = $context->id;
  1207. $cap->roleid = $roleid;
  1208. $cap->capability = $capability;
  1209. $cap->permission = $permission;
  1210. $cap->timemodified = time();
  1211. $cap->modifierid = empty($USER->id) ? 0 : $USER->id;
  1212. if ($existing) {
  1213. $cap->id = $existing->id;
  1214. $DB->update_record('role_capabilities', $cap);
  1215. } else {
  1216. if ($DB->record_exists('context', array('id'=>$context->id))) {
  1217. $DB->insert_record('role_capabilities', $cap);
  1218. }
  1219. }
  1220. // Trigger capability_assigned event.
  1221. \core\event\capability_assigned::create([
  1222. 'userid' => $cap->modifierid,
  1223. 'context' => $context,
  1224. 'objectid' => $roleid,
  1225. 'other' => [
  1226. 'capability' => $capability,
  1227. 'oldpermission' => $existing->permission ?? CAP_INHERIT,
  1228. 'permission' => $permission
  1229. ]
  1230. ])->trigger();
  1231. // Reset any cache of this role, including MUC.
  1232. accesslib_clear_role_cache($roleid);
  1233. return true;
  1234. }
  1235. /**
  1236. * Unassign a capability from a role.
  1237. *
  1238. * @param string $capability the name of the capability
  1239. * @param int $roleid the role id
  1240. * @param int|context $contextid null means all contexts
  1241. * @return boolean true or exception
  1242. */
  1243. function unassign_capability($capability, $roleid, $contextid = null) {
  1244. global $DB, $USER;
  1245. // Capability must exist.
  1246. if (!$capinfo = get_capability_info($capability)) {
  1247. throw new coding_exception("Capability '{$capability}' was not found! This has to be fixed in code.");
  1248. }
  1249. if (!empty($contextid)) {
  1250. if ($contextid instanceof context) {
  1251. $context = $contextid;
  1252. } else {
  1253. $context = context::instance_by_id($contextid);
  1254. }
  1255. // delete from context rel, if this is the last override in this context
  1256. $DB->delete_records('role_capabilities', array('capability'=>$capability, 'roleid'=>$roleid, 'contextid'=>$context->id));
  1257. } else {
  1258. $DB->delete_records('role_capabilities', array('capability'=>$capability, 'roleid'=>$roleid));
  1259. }
  1260. // Trigger capability_assigned event.
  1261. \core\event\capability_unassigned::create([
  1262. 'userid' => $USER->id,
  1263. 'context' => $context ?? context_system::instance(),
  1264. 'objectid' => $roleid,
  1265. 'other' => [
  1266. 'capability' => $capability,
  1267. ]
  1268. ])->trigger();
  1269. // Reset any cache of this role, including MUC.
  1270. accesslib_clear_role_cache($roleid);
  1271. return true;
  1272. }
  1273. /**
  1274. * Get the roles that have a given capability assigned to it
  1275. *
  1276. * This function does not resolve the actual permission of the capability.
  1277. * It just checks for permissions and overrides.
  1278. * Use get_roles_with_cap_in_context() if resolution is required.
  1279. *
  1280. * @param string $capability capability name (string)
  1281. * @param string $permission optional, the permission defined for this capability
  1282. * either CAP_ALLOW, CAP_PREVENT or CAP_PROHIBIT. Defaults to null which means any.
  1283. * @param stdClass $context null means any
  1284. * @return array of role records
  1285. */
  1286. function get_roles_with_capability($capability, $permission = null, $context = null) {
  1287. global $DB;
  1288. if ($context) {
  1289. $contexts = $context->get_parent_context_ids(true);
  1290. l

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