PageRenderTime 64ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 2ms

/lib/accesslib.php

https://bitbucket.org/moodle/moodle
PHP | 7784 lines | 4316 code | 952 blank | 2516 comment | 829 complexity | 0faea7bbcec8f4e35f89afe381d5329f MD5 | raw file
Possible License(s): Apache-2.0, LGPL-2.1, BSD-3-Clause, MIT, GPL-3.0

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. if (!empty($USER->loginascontext)) {
  476. // The current user is logged in as another user and can assume their identity at or below the `loginascontext`
  477. // defined in the USER session.
  478. // The user may not assume their identity at any other location.
  479. if (!$USER->loginascontext->is_parent_of($context, true)) {
  480. // The context being checked is not the specified context, or one of its children.
  481. return false;
  482. }
  483. }
  484. // Find out if user is admin - it is not possible to override the doanything in any way
  485. // and it is not possible to switch to admin role either.
  486. if ($doanything) {
  487. if (is_siteadmin($userid)) {
  488. if ($userid != $USER->id) {
  489. return true;
  490. }
  491. // make sure switchrole is not used in this context
  492. if (empty($USER->access['rsw'])) {
  493. return true;
  494. }
  495. $parts = explode('/', trim($context->path, '/'));
  496. $path = '';
  497. $switched = false;
  498. foreach ($parts as $part) {
  499. $path .= '/' . $part;
  500. if (!empty($USER->access['rsw'][$path])) {
  501. $switched = true;
  502. break;
  503. }
  504. }
  505. if (!$switched) {
  506. return true;
  507. }
  508. //ok, admin switched role in this context, let's use normal access control rules
  509. }
  510. }
  511. // Careful check for staleness...
  512. $context->reload_if_dirty();
  513. if ($USER->id == $userid) {
  514. if (!isset($USER->access)) {
  515. load_all_capabilities();
  516. }
  517. $access =& $USER->access;
  518. } else {
  519. // make sure user accessdata is really loaded
  520. get_user_accessdata($userid, true);
  521. $access =& $ACCESSLIB_PRIVATE->accessdatabyuser[$userid];
  522. }
  523. return has_capability_in_accessdata($capability, $context, $access);
  524. }
  525. /**
  526. * Check if the user has any one of several capabilities from a list.
  527. *
  528. * This is just a utility method that calls has_capability in a loop. Try to put
  529. * the capabilities that most users are likely to have first in the list for best
  530. * performance.
  531. *
  532. * @category access
  533. * @see has_capability()
  534. *
  535. * @param array $capabilities an array of capability names.
  536. * @param context $context the context to check the capability in. You normally get this with instance method of a context class.
  537. * @param integer|stdClass $user A user id or object. By default (null) checks the permissions of the current user.
  538. * @param boolean $doanything If false, ignore effect of admin role assignment
  539. * @return boolean true if the user has any of these capabilities. Otherwise false.
  540. */
  541. function has_any_capability(array $capabilities, context $context, $user = null, $doanything = true) {
  542. foreach ($capabilities as $capability) {
  543. if (has_capability($capability, $context, $user, $doanything)) {
  544. return true;
  545. }
  546. }
  547. return false;
  548. }
  549. /**
  550. * Check if the user has all the capabilities in a list.
  551. *
  552. * This is just a utility method that calls has_capability in a loop. Try to put
  553. * the capabilities that fewest users are likely to have first in the list for best
  554. * performance.
  555. *
  556. * @category access
  557. * @see has_capability()
  558. *
  559. * @param array $capabilities an array of capability names.
  560. * @param context $context the context to check the capability in. You normally get this with instance method of a context class.
  561. * @param integer|stdClass $user A user id or object. By default (null) checks the permissions of the current user.
  562. * @param boolean $doanything If false, ignore effect of admin role assignment
  563. * @return boolean true if the user has all of these capabilities. Otherwise false.
  564. */
  565. function has_all_capabilities(array $capabilities, context $context, $user = null, $doanything = true) {
  566. foreach ($capabilities as $capability) {
  567. if (!has_capability($capability, $context, $user, $doanything)) {
  568. return false;
  569. }
  570. }
  571. return true;
  572. }
  573. /**
  574. * Is course creator going to have capability in a new course?
  575. *
  576. * This is intended to be used in enrolment plugins before or during course creation,
  577. * do not use after the course is fully created.
  578. *
  579. * @category access
  580. *
  581. * @param string $capability the name of the capability to check.
  582. * @param context $context course or category context where is course going to be created
  583. * @param integer|stdClass $user A user id or object. By default (null) checks the permissions of the current user.
  584. * @return boolean true if the user will have this capability.
  585. *
  586. * @throws coding_exception if different type of context submitted
  587. */
  588. function guess_if_creator_will_have_course_capability($capability, context $context, $user = null) {
  589. global $CFG;
  590. if ($context->contextlevel != CONTEXT_COURSE and $context->contextlevel != CONTEXT_COURSECAT) {
  591. throw new coding_exception('Only course or course category context expected');
  592. }
  593. if (has_capability($capability, $context, $user)) {
  594. // User already has the capability, it could be only removed if CAP_PROHIBIT
  595. // was involved here, but we ignore that.
  596. return true;
  597. }
  598. if (!has_capability('moodle/course:create', $context, $user)) {
  599. return false;
  600. }
  601. if (!enrol_is_enabled('manual')) {
  602. return false;
  603. }
  604. if (empty($CFG->creatornewroleid)) {
  605. return false;
  606. }
  607. if ($context->contextlevel == CONTEXT_COURSE) {
  608. if (is_viewing($context, $user, 'moodle/role:assign') or is_enrolled($context, $user, 'moodle/role:assign')) {
  609. return false;
  610. }
  611. } else {
  612. if (has_capability('moodle/course:view', $context, $user) and has_capability('moodle/role:assign', $context, $user)) {
  613. return false;
  614. }
  615. }
  616. // Most likely they will be enrolled after the course creation is finished,
  617. // does the new role have the required capability?
  618. list($neededroles, $forbiddenroles) = get_roles_with_cap_in_context($context, $capability);
  619. return isset($neededroles[$CFG->creatornewroleid]);
  620. }
  621. /**
  622. * Check if the user is an admin at the site level.
  623. *
  624. * Please note that use of proper capabilities is always encouraged,
  625. * this function is supposed to be used from core or for temporary hacks.
  626. *
  627. * @category access
  628. *
  629. * @param int|stdClass $user_or_id user id or user object
  630. * @return bool true if user is one of the administrators, false otherwise
  631. */
  632. function is_siteadmin($user_or_id = null) {
  633. global $CFG, $USER;
  634. if ($user_or_id === null) {
  635. $user_or_id = $USER;
  636. }
  637. if (empty($user_or_id)) {
  638. return false;
  639. }
  640. if (!empty($user_or_id->id)) {
  641. $userid = $user_or_id->id;
  642. } else {
  643. $userid = $user_or_id;
  644. }
  645. // Because this script is called many times (150+ for course page) with
  646. // the same parameters, it is worth doing minor optimisations. This static
  647. // cache stores the value for a single userid, saving about 2ms from course
  648. // page load time without using significant memory. As the static cache
  649. // also includes the value it depends on, this cannot break unit tests.
  650. static $knownid, $knownresult, $knownsiteadmins;
  651. if ($knownid === $userid && $knownsiteadmins === $CFG->siteadmins) {
  652. return $knownresult;
  653. }
  654. $knownid = $userid;
  655. $knownsiteadmins = $CFG->siteadmins;
  656. $siteadmins = explode(',', $CFG->siteadmins);
  657. $knownresult = in_array($userid, $siteadmins);
  658. return $knownresult;
  659. }
  660. /**
  661. * Returns true if user has at least one role assign
  662. * of 'coursecontact' role (is potentially listed in some course descriptions).
  663. *
  664. * @param int $userid
  665. * @return bool
  666. */
  667. function has_coursecontact_role($userid) {
  668. global $DB, $CFG;
  669. if (empty($CFG->coursecontact)) {
  670. return false;
  671. }
  672. $sql = "SELECT 1
  673. FROM {role_assignments}
  674. WHERE userid = :userid AND roleid IN ($CFG->coursecontact)";
  675. return $DB->record_exists_sql($sql, array('userid'=>$userid));
  676. }
  677. /**
  678. * Does the user have a capability to do something?
  679. *
  680. * Walk the accessdata array and return true/false.
  681. * Deals with prohibits, role switching, aggregating
  682. * capabilities, etc.
  683. *
  684. * The main feature of here is being FAST and with no
  685. * side effects.
  686. *
  687. * Notes:
  688. *
  689. * Switch Role merges with default role
  690. * ------------------------------------
  691. * If you are a teacher in course X, you have at least
  692. * teacher-in-X + defaultloggedinuser-sitewide. So in the
  693. * course you'll have techer+defaultloggedinuser.
  694. * We try to mimic that in switchrole.
  695. *
  696. * Permission evaluation
  697. * ---------------------
  698. * Originally there was an extremely complicated way
  699. * to determine the user access that dealt with
  700. * "locality" or role assignments and role overrides.
  701. * Now we simply evaluate access for each role separately
  702. * and then verify if user has at least one role with allow
  703. * and at the same time no role with prohibit.
  704. *
  705. * @access private
  706. * @param string $capability
  707. * @param context $context
  708. * @param array $accessdata
  709. * @return bool
  710. */
  711. function has_capability_in_accessdata($capability, context $context, array &$accessdata) {
  712. global $CFG;
  713. // Build $paths as a list of current + all parent "paths" with order bottom-to-top
  714. $path = $context->path;
  715. $paths = array($path);
  716. while ($path = rtrim($path, '0123456789')) {
  717. $path = rtrim($path, '/');
  718. if ($path === '') {
  719. break;
  720. }
  721. $paths[] = $path;
  722. }
  723. $roles = array();
  724. $switchedrole = false;
  725. // Find out if role switched
  726. if (!empty($accessdata['rsw'])) {
  727. // From the bottom up...
  728. foreach ($paths as $path) {
  729. if (isset($accessdata['rsw'][$path])) {
  730. // Found a switchrole assignment - check for that role _plus_ the default user role
  731. $roles = array($accessdata['rsw'][$path]=>null, $CFG->defaultuserroleid=>null);
  732. $switchedrole = true;
  733. break;
  734. }
  735. }
  736. }
  737. if (!$switchedrole) {
  738. // get all users roles in this context and above
  739. foreach ($paths as $path) {
  740. if (isset($accessdata['ra'][$path])) {
  741. foreach ($accessdata['ra'][$path] as $roleid) {
  742. $roles[$roleid] = null;
  743. }
  744. }
  745. }
  746. }
  747. // Now find out what access is given to each role, going bottom-->up direction
  748. $rdefs = get_role_definitions(array_keys($roles));
  749. $allowed = false;
  750. foreach ($roles as $roleid => $ignored) {
  751. foreach ($paths as $path) {
  752. if (isset($rdefs[$roleid][$path][$capability])) {
  753. $perm = (int)$rdefs[$roleid][$path][$capability];
  754. if ($perm === CAP_PROHIBIT) {
  755. // any CAP_PROHIBIT found means no permission for the user
  756. return false;
  757. }
  758. if (is_null($roles[$roleid])) {
  759. $roles[$roleid] = $perm;
  760. }
  761. }
  762. }
  763. // CAP_ALLOW in any role means the user has a permission, we continue only to detect prohibits
  764. $allowed = ($allowed or $roles[$roleid] === CAP_ALLOW);
  765. }
  766. return $allowed;
  767. }
  768. /**
  769. * A convenience function that tests has_capability, and displays an error if
  770. * the user does not have that capability.
  771. *
  772. * NOTE before Moodle 2.0, this function attempted to make an appropriate
  773. * require_login call before checking the capability. This is no longer the case.
  774. * You must call require_login (or one of its variants) if you want to check the
  775. * user is logged in, before you call this function.
  776. *
  777. * @see has_capability()
  778. *
  779. * @param string $capability the name of the capability to check. For example mod/forum:view
  780. * @param context $context the context to check the capability in. You normally get this with context_xxxx::instance().
  781. * @param int $userid A user id. By default (null) checks the permissions of the current user.
  782. * @param bool $doanything If false, ignore effect of admin role assignment
  783. * @param string $errormessage The error string to to user. Defaults to 'nopermissions'.
  784. * @param string $stringfile The language file to load the error string from. Defaults to 'error'.
  785. * @return void terminates with an error if the user does not have the given capability.
  786. */
  787. function require_capability($capability, context $context, $userid = null, $doanything = true,
  788. $errormessage = 'nopermissions', $stringfile = '') {
  789. if (!has_capability($capability, $context, $userid, $doanything)) {
  790. throw new required_capability_exception($context, $capability, $errormessage, $stringfile);
  791. }
  792. }
  793. /**
  794. * A convenience function that tests has_capability for a list of capabilities, and displays an error if
  795. * the user does not have that capability.
  796. *
  797. * This is just a utility method that calls has_capability in a loop. Try to put
  798. * the capabilities that fewest users are likely to have first in the list for best
  799. * performance.
  800. *
  801. * @category access
  802. * @see has_capability()
  803. *
  804. * @param array $capabilities an array of capability names.
  805. * @param context $context the context to check the capability in. You normally get this with context_xxxx::instance().
  806. * @param int $userid A user id. By default (null) checks the permissions of the current user.
  807. * @param bool $doanything If false, ignore effect of admin role assignment
  808. * @param string $errormessage The error string to to user. Defaults to 'nopermissions'.
  809. * @param string $stringfile The language file to load the error string from. Defaults to 'error'.
  810. * @return void terminates with an error if the user does not have the given capability.
  811. */
  812. function require_all_capabilities(array $capabilities, context $context, $userid = null, $doanything = true,
  813. $errormessage = 'nopermissions', $stringfile = ''): void {
  814. foreach ($capabilities as $capability) {
  815. if (!has_capability($capability, $context, $userid, $doanything)) {
  816. throw new required_capability_exception($context, $capability, $errormessage, $stringfile);
  817. }
  818. }
  819. }
  820. /**
  821. * Return a nested array showing all role assignments for the user.
  822. * [ra] => [contextpath][roleid] = roleid
  823. *
  824. * @access private
  825. * @param int $userid - the id of the user
  826. * @return array access info array
  827. */
  828. function get_user_roles_sitewide_accessdata($userid) {
  829. global $CFG, $DB;
  830. $accessdata = get_empty_accessdata();
  831. // start with the default role
  832. if (!empty($CFG->defaultuserroleid)) {
  833. $syscontext = context_system::instance();
  834. $accessdata['ra'][$syscontext->path][(int)$CFG->defaultuserroleid] = (int)$CFG->defaultuserroleid;
  835. }
  836. // load the "default frontpage role"
  837. if (!empty($CFG->defaultfrontpageroleid)) {
  838. $frontpagecontext = context_course::instance(get_site()->id);
  839. if ($frontpagecontext->path) {
  840. $accessdata['ra'][$frontpagecontext->path][(int)$CFG->defaultfrontpageroleid] = (int)$CFG->defaultfrontpageroleid;
  841. }
  842. }
  843. // Preload every assigned role.
  844. $sql = "SELECT ctx.path, ra.roleid, ra.contextid
  845. FROM {role_assignments} ra
  846. JOIN {context} ctx ON ctx.id = ra.contextid
  847. WHERE ra.userid = :userid";
  848. $rs = $DB->get_recordset_sql($sql, array('userid' => $userid));
  849. foreach ($rs as $ra) {
  850. // RAs leafs are arrays to support multi-role assignments...
  851. $accessdata['ra'][$ra->path][(int)$ra->roleid] = (int)$ra->roleid;
  852. }
  853. $rs->close();
  854. return $accessdata;
  855. }
  856. /**
  857. * Returns empty accessdata structure.
  858. *
  859. * @access private
  860. * @return array empt accessdata
  861. */
  862. function get_empty_accessdata() {
  863. $accessdata = array(); // named list
  864. $accessdata['ra'] = array();
  865. $accessdata['time'] = time();
  866. $accessdata['rsw'] = array();
  867. return $accessdata;
  868. }
  869. /**
  870. * Get accessdata for a given user.
  871. *
  872. * @access private
  873. * @param int $userid
  874. * @param bool $preloadonly true means do not return access array
  875. * @return array accessdata
  876. */
  877. function get_user_accessdata($userid, $preloadonly=false) {
  878. global $CFG, $ACCESSLIB_PRIVATE, $USER;
  879. if (isset($USER->access)) {
  880. $ACCESSLIB_PRIVATE->accessdatabyuser[$USER->id] = $USER->access;
  881. }
  882. if (!isset($ACCESSLIB_PRIVATE->accessdatabyuser[$userid])) {
  883. if (empty($userid)) {
  884. if (!empty($CFG->notloggedinroleid)) {
  885. $accessdata = get_role_access($CFG->notloggedinroleid);
  886. } else {
  887. // weird
  888. return get_empty_accessdata();
  889. }
  890. } else if (isguestuser($userid)) {
  891. if ($guestrole = get_guest_role()) {
  892. $accessdata = get_role_access($guestrole->id);
  893. } else {
  894. //weird
  895. return get_empty_accessdata();
  896. }
  897. } else {
  898. // Includes default role and frontpage role.
  899. $accessdata = get_user_roles_sitewide_accessdata($userid);
  900. }
  901. $ACCESSLIB_PRIVATE->accessdatabyuser[$userid] = $accessdata;
  902. }
  903. if ($preloadonly) {
  904. return;
  905. } else {
  906. return $ACCESSLIB_PRIVATE->accessdatabyuser[$userid];
  907. }
  908. }
  909. /**
  910. * A convenience function to completely load all the capabilities
  911. * for the current user. It is called from has_capability() and functions change permissions.
  912. *
  913. * Call it only _after_ you've setup $USER and called check_enrolment_plugins();
  914. * @see check_enrolment_plugins()
  915. *
  916. * @access private
  917. * @return void
  918. */
  919. function load_all_capabilities() {
  920. global $USER;
  921. // roles not installed yet - we are in the middle of installation
  922. if (during_initial_install()) {
  923. return;
  924. }
  925. if (!isset($USER->id)) {
  926. // this should not happen
  927. $USER->id = 0;
  928. }
  929. unset($USER->access);
  930. $USER->access = get_user_accessdata($USER->id);
  931. // Clear to force a refresh
  932. unset($USER->mycourses);
  933. // init/reset internal enrol caches - active course enrolments and temp access
  934. $USER->enrol = array('enrolled'=>array(), 'tempguest'=>array());
  935. }
  936. /**
  937. * A convenience function to completely reload all the capabilities
  938. * for the current user when roles have been updated in a relevant
  939. * context -- but PRESERVING switchroles and loginas.
  940. * This function resets all accesslib and context caches.
  941. *
  942. * That is - completely transparent to the user.
  943. *
  944. * Note: reloads $USER->access completely.
  945. *
  946. * @access private
  947. * @return void
  948. */
  949. function reload_all_capabilities() {
  950. global $USER, $DB, $ACCESSLIB_PRIVATE;
  951. // copy switchroles
  952. $sw = array();
  953. if (!empty($USER->access['rsw'])) {
  954. $sw = $USER->access['rsw'];
  955. }
  956. accesslib_clear_all_caches(true);
  957. unset($USER->access);
  958. // Prevent dirty flags refetching on this page.
  959. $ACCESSLIB_PRIVATE->dirtycontexts = array();
  960. $ACCESSLIB_PRIVATE->dirtyusers = array($USER->id => false);
  961. load_all_capabilities();
  962. foreach ($sw as $path => $roleid) {
  963. if ($record = $DB->get_record('context', array('path'=>$path))) {
  964. $context = context::instance_by_id($record->id);
  965. if (has_capability('moodle/role:switchroles', $context)) {
  966. role_switch($roleid, $context);
  967. }
  968. }
  969. }
  970. }
  971. /**
  972. * Adds a temp role to current USER->access array.
  973. *
  974. * Useful for the "temporary guest" access we grant to logged-in users.
  975. * This is useful for enrol plugins only.
  976. *
  977. * @since Moodle 2.2
  978. * @param context_course $coursecontext
  979. * @param int $roleid
  980. * @return void
  981. */
  982. function load_temp_course_role(context_course $coursecontext, $roleid) {
  983. global $USER, $SITE;
  984. if (empty($roleid)) {
  985. debugging('invalid role specified in load_temp_course_role()');
  986. return;
  987. }
  988. if ($coursecontext->instanceid == $SITE->id) {
  989. debugging('Can not use temp roles on the frontpage');
  990. return;
  991. }
  992. if (!isset($USER->access)) {
  993. load_all_capabilities();
  994. }
  995. $coursecontext->reload_if_dirty();
  996. if (isset($USER->access['ra'][$coursecontext->path][$roleid])) {
  997. return;
  998. }
  999. $USER->access['ra'][$coursecontext->path][(int)$roleid] = (int)$roleid;
  1000. }
  1001. /**
  1002. * Removes any extra guest roles from current USER->access array.
  1003. * This is useful for enrol plugins only.
  1004. *
  1005. * @since Moodle 2.2
  1006. * @param context_course $coursecontext
  1007. * @return void
  1008. */
  1009. function remove_temp_course_roles(context_course $coursecontext) {
  1010. global $DB, $USER, $SITE;
  1011. if ($coursecontext->instanceid == $SITE->id) {
  1012. debugging('Can not use temp roles on the frontpage');
  1013. return;
  1014. }
  1015. if (empty($USER->access['ra'][$coursecontext->path])) {
  1016. //no roles here, weird
  1017. return;
  1018. }
  1019. $sql = "SELECT DISTINCT ra.roleid AS id
  1020. FROM {role_assignments} ra
  1021. WHERE ra.contextid = :contextid AND ra.userid = :userid";
  1022. $ras = $DB->get_records_sql($sql, array('contextid'=>$coursecontext->id, 'userid'=>$USER->id));
  1023. $USER->access['ra'][$coursecontext->path] = array();
  1024. foreach ($ras as $r) {
  1025. $USER->access['ra'][$coursecontext->path][(int)$r->id] = (int)$r->id;
  1026. }
  1027. }
  1028. /**
  1029. * Returns array of all role archetypes.
  1030. *
  1031. * @return array
  1032. */
  1033. function get_role_archetypes() {
  1034. return array(
  1035. 'manager' => 'manager',
  1036. 'coursecreator' => 'coursecreator',
  1037. 'editingteacher' => 'editingteacher',
  1038. 'teacher' => 'teacher',
  1039. 'student' => 'student',
  1040. 'guest' => 'guest',
  1041. 'user' => 'user',
  1042. 'frontpage' => 'frontpage'
  1043. );
  1044. }
  1045. /**
  1046. * Assign the defaults found in this capability definition to roles that have
  1047. * the corresponding legacy capabilities assigned to them.
  1048. *
  1049. * @param string $capability
  1050. * @param array $legacyperms an array in the format (example):
  1051. * 'guest' => CAP_PREVENT,
  1052. * 'student' => CAP_ALLOW,
  1053. * 'teacher' => CAP_ALLOW,
  1054. * 'editingteacher' => CAP_ALLOW,
  1055. * 'coursecreator' => CAP_ALLOW,
  1056. * 'manager' => CAP_ALLOW
  1057. * @return boolean success or failure.
  1058. */
  1059. function assign_legacy_capabilities($capability, $legacyperms) {
  1060. $archetypes = get_role_archetypes();
  1061. foreach ($legacyperms as $type => $perm) {
  1062. $systemcontext = context_system::instance();
  1063. if ($type === 'admin') {
  1064. debugging('Legacy type admin in access.php was renamed to manager, please update the code.');
  1065. $type = 'manager';
  1066. }
  1067. if (!array_key_exists($type, $archetypes)) {
  1068. print_error('invalidlegacy', '', '', $type);
  1069. }
  1070. if ($roles = get_archetype_roles($type)) {
  1071. foreach ($roles as $role) {
  1072. // Assign a site level capability.
  1073. if (!assign_capability($capability, $perm, $role->id, $systemcontext->id)) {
  1074. return false;
  1075. }
  1076. }
  1077. }
  1078. }
  1079. return true;
  1080. }
  1081. /**
  1082. * Verify capability risks.
  1083. *
  1084. * @param stdClass $capability a capability - a row from the capabilities table.
  1085. * @return boolean whether this capability is safe - that is, whether people with the
  1086. * safeoverrides capability should be allowed to change it.
  1087. */
  1088. function is_safe_capability($capability) {
  1089. return !((RISK_DATALOSS | RISK_MANAGETRUST | RISK_CONFIG | RISK_XSS | RISK_PERSONAL) & $capability->riskbitmask);
  1090. }
  1091. /**
  1092. * Get the local override (if any) for a given capability in a role in a context
  1093. *
  1094. * @param int $roleid
  1095. * @param int $contextid
  1096. * @param string $capability
  1097. * @return stdClass local capability override
  1098. */
  1099. function get_local_override($roleid, $contextid, $capability) {
  1100. global $DB;
  1101. return $DB->get_record_sql("
  1102. SELECT rc.*
  1103. FROM {role_capabilities} rc
  1104. JOIN {capability} cap ON rc.capability = cap.name
  1105. WHERE rc.roleid = :roleid AND rc.capability = :capability AND rc.contextid = :contextid", [
  1106. 'roleid' => $roleid,
  1107. 'contextid' => $contextid,
  1108. 'capability' => $capability,
  1109. ]);
  1110. }
  1111. /**
  1112. * Returns context instance plus related course and cm instances
  1113. *
  1114. * @param int $contextid
  1115. * @return array of ($context, $course, $cm)
  1116. */
  1117. function get_context_info_array($contextid) {
  1118. global $DB;
  1119. $context = context::instance_by_id($contextid, MUST_EXIST);
  1120. $course = null;
  1121. $cm = null;
  1122. if ($context->contextlevel == CONTEXT_COURSE) {
  1123. $course = $DB->get_record('course', array('id'=>$context->instanceid), '*', MUST_EXIST);
  1124. } else if ($context->contextlevel == CONTEXT_MODULE) {
  1125. $cm = get_coursemodule_from_id('', $context->instanceid, 0, false, MUST_EXIST);
  1126. $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
  1127. } else if ($context->contextlevel == CONTEXT_BLOCK) {
  1128. $parent = $context->get_parent_context();
  1129. if ($parent->contextlevel == CONTEXT_COURSE) {
  1130. $course = $DB->get_record('course', array('id'=>$parent->instanceid), '*', MUST_EXIST);
  1131. } else if ($parent->contextlevel == CONTEXT_MODULE) {
  1132. $cm = get_coursemodule_from_id('', $parent->instanceid, 0, false, MUST_EXIST);
  1133. $course = $DB->get_record('course', array('id'=>$cm->course), '*', MUST_EXIST);
  1134. }
  1135. }
  1136. return array($context, $course, $cm);
  1137. }
  1138. /**
  1139. * Function that creates a role
  1140. *
  1141. * @param string $name role name
  1142. * @param string $shortname role short name
  1143. * @param string $description role description
  1144. * @param string $archetype
  1145. * @return int id or dml_exception
  1146. */
  1147. function create_role($name, $shortname, $description, $archetype = '') {
  1148. global $DB;
  1149. if (strpos($archetype, 'moodle/legacy:') !== false) {
  1150. throw new coding_exception('Use new role archetype parameter in create_role() instead of old legacy capabilities.');
  1151. }
  1152. // verify role archetype actually exists
  1153. $archetypes = get_role_archetypes();
  1154. if (empty($archetypes[$archetype])) {
  1155. $archetype = '';
  1156. }
  1157. // Insert the role record.
  1158. $role = new stdClass();
  1159. $role->name = $name;
  1160. $role->shortname = $shortname;
  1161. $role->description = $description;
  1162. $role->archetype = $archetype;
  1163. //find free sortorder number
  1164. $role->sortorder = $DB->get_field('role', 'MAX(sortorder) + 1', array());
  1165. if (empty($role->sortorder)) {
  1166. $role->sortorder = 1;
  1167. }
  1168. $id = $DB->insert_record('role', $role);
  1169. return $id;
  1170. }
  1171. /**
  1172. * Function that deletes a role and cleanups up after it
  1173. *
  1174. * @param int $roleid id of role to delete
  1175. * @return bool always true
  1176. */
  1177. function delete_role($roleid) {
  1178. global $DB;
  1179. // first unssign all users
  1180. role_unassign_all(array('roleid'=>$roleid));
  1181. // cleanup all references to this role, ignore errors
  1182. $DB->delete_records('role_capabilities', array('roleid'=>$roleid));
  1183. $DB->delete_records('role_allow_assign', array('roleid'=>$roleid));
  1184. $DB->delete_records('role_allow_assign', array('allowassign'=>$roleid));
  1185. $DB->delete_records('role_allow_override', array('roleid'=>$roleid));
  1186. $DB->delete_records('role_allow_override', array('allowoverride'=>$roleid));
  1187. $DB->delete_records('role_names', array('roleid'=>$roleid));
  1188. $DB->delete_records('role_context_levels', array('roleid'=>$roleid));
  1189. // Get role record before it's deleted.
  1190. $role = $DB->get_record('role', array('id'=>$roleid));
  1191. // Finally delete the role itself.
  1192. $DB->delete_records('role', array('id'=>$roleid));
  1193. // Trigger event.
  1194. $event = \core\event\role_deleted::create(
  1195. array(
  1196. 'context' => context_system::instance(),
  1197. 'objectid' => $roleid,
  1198. 'other' =>
  1199. array(
  1200. 'shortname' => $role->shortname,
  1201. 'description' => $role->description,
  1202. 'archetype' => $role->archetype
  1203. )
  1204. )
  1205. );
  1206. $event->add_record_snapshot('role', $role);
  1207. $event->trigger();
  1208. // Reset any cache of this role, including MUC.
  1209. accesslib_clear_role_cache($roleid);
  1210. return true;
  1211. }
  1212. /**
  1213. * Function to write context specific overrides, or default capabilities.
  1214. *
  1215. * @param string $capability string name
  1216. * @param int $permission CAP_ constants
  1217. * @param int $roleid role id
  1218. * @param int|context $contextid context id
  1219. * @param bool $overwrite
  1220. * @return bool always true or exception
  1221. */
  1222. function assign_capability($capability, $permission, $roleid, $contextid, $overwrite = false) {
  1223. global $USER, $DB;
  1224. if ($contextid instanceof context) {
  1225. $context = $contextid;
  1226. } else {
  1227. $context = context::instance_by_id($contextid);
  1228. }
  1229. // Capability must exist.
  1230. if (!$capinfo = get_capability_info($capability)) {
  1231. throw new coding_exception("Capability '{$capability}' was not found! This has to be fixed in code.");
  1232. }
  1233. if (empty($permission) || $permission == CAP_INHERIT) { // if permission is not set
  1234. unassign_capability($capability, $roleid, $context->id);
  1235. return true;
  1236. }
  1237. $existing = $DB->get_record('role_capabilities', array('contextid'=>$context->id, 'roleid'=>$roleid, 'capability'=>$capability));
  1238. if ($existing and !$overwrite) { // We want to keep whatever is there already
  1239. return true;
  1240. }
  1241. $cap = new stdClass();
  1242. $cap->contextid = $context->id;
  1243. $cap->roleid = $roleid;
  1244. $cap->capability = $capability;
  1245. $cap->permission = $permission;
  1246. $cap->timemodified = time();
  1247. $cap->modifierid = empty($USER->id) ? 0 : $USER->id;
  1248. if ($existing) {
  1249. $cap->id = $existing->id;
  1250. $DB->update_record('role_capabilities', $cap);
  1251. } else {
  1252. if ($DB->record_exists('context', array('id'=>$context->id))) {
  1253. $DB->insert_record('role_capabilities', $cap);
  1254. }
  1255. }
  1256. // Trigger capability_assigned event.
  1257. \core\event\capability_assigned::create([
  1258. 'userid' => $cap->modifierid,
  1259. 'context' => $context,
  1260. 'objectid' => $roleid,
  1261. 'other' => [
  1262. 'capability' => $capability,
  1263. 'oldpermission' => $existing->permission ?? CAP_INHERIT,
  1264. 'permission' => $permission
  1265. ]
  1266. ])->trigger();
  1267. // Reset any cache of this role, including MUC.
  1268. accesslib_clear_role_cache($roleid);
  1269. return true;
  1270. }
  1271. /**
  1272. * Unassign a capability from a role.
  1273. *
  1274. * @param string $capability the name of the capability
  1275. * @param int $roleid the role id
  1276. * @param int|context $contextid null means all contexts
  1277. * @return boolean true or exception
  1278. */
  1279. function unassign_capability($capability, $roleid, $contextid = null) {
  1280. glob

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