diff --git a/lang/en/cache.php b/lang/en/cache.php index 3e633792143..f4e9312a8e3 100644 --- a/lang/en/cache.php +++ b/lang/en/cache.php @@ -37,6 +37,7 @@ $string['caching'] = 'Caching'; $string['cacheadmin'] = 'Cache administration'; $string['cacheconfig'] = 'Configuration'; $string['cachedef_calendar_subscriptions'] = 'Calendar subscriptions'; +$string['cachedef_capabilities'] = 'System capabilities list'; $string['cachedef_config'] = 'Config settings'; $string['cachedef_coursecat'] = 'Course categories lists for particular user'; $string['cachedef_coursecatrecords'] = 'Course categories records'; diff --git a/lib/accesslib.php b/lib/accesslib.php index b60be06abc5..369e3eb2183 100644 --- a/lib/accesslib.php +++ b/lib/accesslib.php @@ -203,7 +203,6 @@ $ACCESSLIB_PRIVATE = new stdClass(); $ACCESSLIB_PRIVATE->dirtycontexts = null; // Dirty contexts cache, loaded from DB once per page $ACCESSLIB_PRIVATE->accessdatabyuser = array(); // Holds the cache of $accessdata structure for users (including $USER) $ACCESSLIB_PRIVATE->rolepermissions = array(); // role permissions cache - helps a lot with mem usage -$ACCESSLIB_PRIVATE->capabilities = null; // detailed information about the capabilities /** * Clears accesslib's private caches. ONLY BE USED BY UNIT TESTS @@ -241,7 +240,6 @@ function accesslib_clear_all_caches($resetcontexts) { $ACCESSLIB_PRIVATE->dirtycontexts = null; $ACCESSLIB_PRIVATE->accessdatabyuser = array(); $ACCESSLIB_PRIVATE->rolepermissions = array(); - $ACCESSLIB_PRIVATE->capabilities = null; if ($resetcontexts) { context_helper::reset_caches(); @@ -2532,7 +2530,14 @@ function load_capability_def($component) { */ function get_cached_capabilities($component = 'moodle') { global $DB; - return $DB->get_records('capabilities', array('component'=>$component)); + $caps = get_all_capabilities(); + $componentcaps = array(); + foreach ($caps as $cap) { + if ($cap['component'] == $component) { + $componentcaps[] = (object) $cap; + } + } + return $componentcaps; } /** @@ -2551,12 +2556,12 @@ function get_default_capabilities($archetype) { $alldefs = array(); $defaults = array(); $components = array(); - $allcaps = $DB->get_records('capabilities'); + $allcaps = get_all_capabilities(); foreach ($allcaps as $cap) { - if (!in_array($cap->component, $components)) { - $components[] = $cap->component; - $alldefs = array_merge($alldefs, load_capability_def($cap->component)); + if (!in_array($cap['component'], $components)) { + $components[] = $cap['component']; + $alldefs = array_merge($alldefs, load_capability_def($cap['component'])); } } foreach($alldefs as $name=>$def) { @@ -2703,6 +2708,10 @@ function update_capabilities($component = 'moodle') { } } + // It is possible somebody directly modified the DB (according to accesslib_test anyway). + // So ensure our updating is based on fresh data. + cache::make('core', 'capabilities')->delete('core_capabilities'); + $cachedcaps = get_cached_capabilities($component); if ($cachedcaps) { foreach ($cachedcaps as $cachedcap) { @@ -2738,6 +2747,9 @@ function update_capabilities($component = 'moodle') { } } + // Flush the cached again, as we have changed DB. + cache::make('core', 'capabilities')->delete('core_capabilities'); + // Are there new capabilities in the file definition? $newcaps = array(); @@ -2788,6 +2800,9 @@ function update_capabilities($component = 'moodle') { // reset static caches accesslib_clear_all_caches(false); + // Flush the cached again, as we have changed DB. + cache::make('core', 'capabilities')->delete('core_capabilities'); + return true; } @@ -2826,6 +2841,9 @@ function capabilities_cleanup($component, $newcapdef = null) { } // End if. } } + if ($removedcount) { + cache::make('core', 'capabilities')->delete('core_capabilities'); + } return $removedcount; } @@ -2946,21 +2964,33 @@ function is_inside_frontpage(context $context) { function get_capability_info($capabilityname) { global $ACCESSLIB_PRIVATE, $DB; // one request per page only - //TODO: MUC - this could be cached in shared memory, it would eliminate 1 query per page + $caps = get_all_capabilities(); - if (empty($ACCESSLIB_PRIVATE->capabilities)) { - $ACCESSLIB_PRIVATE->capabilities = array(); - $caps = $DB->get_records('capabilities', array(), '', 'id, name, captype, riskbitmask'); - foreach ($caps as $cap) { - $capname = $cap->name; - unset($cap->id); - unset($cap->name); - $cap->riskbitmask = (int)$cap->riskbitmask; - $ACCESSLIB_PRIVATE->capabilities[$capname] = $cap; - } + if (!isset($caps[$capabilityname])) { + return null; } - return isset($ACCESSLIB_PRIVATE->capabilities[$capabilityname]) ? $ACCESSLIB_PRIVATE->capabilities[$capabilityname] : null; + return (object) $caps[$capabilityname]; +} + +/** + * Returns all capabilitiy records, preferably from MUC and not database. + * + * @return array All capability records + */ +function get_all_capabilities() { + global $DB; + $cache = cache::make('core', 'capabilities'); + if (!$allcaps = $cache->get('core_capabilities')) { + $allcaps = $DB->get_records('capabilities', null, '', 'name as uniquename, *'); + foreach ($allcaps as $k => $v) { + unset($v->uniquename); + $v->riskbitmask = (int) $v->riskbitmask; + $allcaps[$k] = (array) $v; + } + $cache->set('core_capabilities', $allcaps); + } + return $allcaps; } /** diff --git a/lib/db/caches.php b/lib/db/caches.php index 98dadff4601..6701e125067 100644 --- a/lib/db/caches.php +++ b/lib/db/caches.php @@ -126,6 +126,16 @@ $definitions = array( 'staticacceleration' => true, ), + // Cache the capabilities list DB table. See get_all_capabilities in accesslib. + 'capabilities' => array( + 'mode' => cache_store::MODE_APPLICATION, + 'simplekeys' => true, + 'simpledata' => true, + 'staticacceleration' => true, + 'staticaccelerationsize' => 1, + 'ttl' => 3600, // Just in case. + ), + // YUI Module cache. // This stores the YUI module metadata for Shifted YUI modules in Moodle. 'yuimodules' => array( diff --git a/lib/tests/accesslib_test.php b/lib/tests/accesslib_test.php index 9b036498173..91922d5f9ef 100644 --- a/lib/tests/accesslib_test.php +++ b/lib/tests/accesslib_test.php @@ -67,6 +67,20 @@ class core_accesslib_testcase extends advanced_testcase { $this->assertEmpty($ACCESSLIB_PRIVATE->accessdatabyuser); } + /** + * Check modifying capability record is not exposed to other code. + */ + public function test_capabilities_mutation() { + $oldcap = get_capability_info('moodle/site:config'); + $cap = get_capability_info('moodle/site:config'); + unset($cap->name); + $newcap = get_capability_info('moodle/site:config'); + + $this->assertFalse(isset($cap->name)); + $this->assertTrue(isset($newcap->name)); + $this->assertTrue(isset($oldcap->name)); + } + /** * Test getting of role access */