From 46e17f04c7975e8bddcb25cd663d0f74076a50fa Mon Sep 17 00:00:00 2001 From: Sam Hemelryk Date: Thu, 7 Feb 2013 10:59:52 +1300 Subject: [PATCH 1/2] MDL-37500 cache: implemented sharing options and gui --- cache/README.md | 10 ++++- cache/admin.php | 28 ++++++++++++ cache/classes/config.php | 15 +++++++ cache/classes/definition.php | 85 ++++++++++++++++++++++++++++++++++- cache/forms.php | 49 ++++++++++++++++++++ cache/locallib.php | 74 +++++++++++++++++++++++++++++- cache/renderer.php | 3 ++ cache/tests/fixtures/lib.php | 8 ++++ cache/tests/locallib_test.php | 17 +++---- lang/en/cache.php | 13 ++++++ 10 files changed, 290 insertions(+), 12 deletions(-) diff --git a/cache/README.md b/cache/README.md index 3868c90fdba..ca7815a159b 100644 --- a/cache/README.md +++ b/cache/README.md @@ -31,6 +31,7 @@ A definition: 'invalidationevents' => array( // Optional 'contextmarkeddirty' ), + 'sharingoptions' => null // Optional ) ); @@ -147,11 +148,12 @@ The following optional settings can also be defined: * ttl - Can be used to set a ttl value for data being set for this cache. * mappingsonly - This definition can only be used if there is a store mapping for it. More on this later. * invalidationevents - An array of events that should trigger this cache to invalidate. +* sharingoptions - The sum of the possible sharing options that are applicable to the definition. An advanced setting. It's important to note that internally the definition is also aware of the component. This is picked up when the definition is read, based upon the location of the caches.php file. -The persist option. -As noted the persist option causes the loader generated for this definition to be stored when first created. Subsequent requests for this definition will be given the original loader instance. +The persistent option. +As noted the persistent option causes the loader generated for this definition to be stored when first created. Subsequent requests for this definition will be given the original loader instance. Data passed to or retrieved from the loader and its chained loaders gets cached by the instance. This option should be used when you know you will require the loader several times and perhaps in different areas of code. Because it caches key=>value data it avoids the need to re-fetch things from stores after the first request. Its good for performance, bad for memory. @@ -162,6 +164,10 @@ The administrator of a site can create mappings between stores and definitions. Setting this option to true means that the definition can only be used if a mapping has been made for it. Normally if no mappings exist then the default store for the definition mode is used. +Sharing options. +This controls the options available to the user when configuring the sharing of a definitions cached data. +By default all sharing options are available to select. This particular option allows the developer to limit the options available to the admin configuring the cache. + ### Data source Data sources allow cache _misses_ (requests for a key that doesn't exist) to be handled and loaded internally. The loader gets used as the last resort if provided and means that code using the cache doesn't need to handle the situation that information isn't cached. diff --git a/cache/admin.php b/cache/admin.php index 52b09b33b30..d3991890dc8 100644 --- a/cache/admin.php +++ b/cache/admin.php @@ -131,6 +131,9 @@ if (!empty($action) && confirm_sesskey()) { break; case 'editdefinitionmapping' : // Edit definition mappings. $definition = required_param('definition', PARAM_SAFEPATH); + if (!array_key_exists($definition, $definitions)) { + throw new cache_exception('Invalid cache definition requested'); + } $title = get_string('editdefinitionmappings', 'cache', $definition); $mform = new cache_definition_mappings_form($PAGE->url, array('definition' => $definition)); if ($mform->is_cancelled()) { @@ -147,6 +150,31 @@ if (!empty($action) && confirm_sesskey()) { redirect($PAGE->url); } break; + case 'editdefinitionsharing' : + $definition = required_param('definition', PARAM_SAFEPATH); + if (!array_key_exists($definition, $definitions)) { + throw new cache_exception('Invalid cache definition requested'); + } + $title = get_string('editdefinitionmappings', 'cache', $definition); + $sharingoptions = $definitions[$definition]['sharingoptions']; + $mform = new cache_definition_sharing_form($PAGE->url, array('definition' => $definition, 'sharingoptions' => $sharingoptions)); + $mform->set_data(array( + 'sharing' => $definitions[$definition]['selectedsharingoption'], + 'userinputsharingkey' => $definitions[$definition]['userinputsharingkey'] + )); + if ($mform->is_cancelled()) { + redirect($PAGE->url); + } else if ($data = $mform->get_data()) { + $component = $definitions[$definition]['component']; + $area = $definitions[$definition]['area']; + cache_helper::purge_by_definition($component, $area); + $writer = cache_config_writer::instance(); + $sharing = array_sum(array_keys($data->sharing)); + $userinputsharingkey = $data->userinputsharingkey; + $writer->set_definition_sharing($definition, $sharing, $userinputsharingkey); + redirect($PAGE->url); + } + break; case 'editmodemappings': // Edit default mode mappings. $mform = new cache_mode_mappings_form(null, $stores); $mform->set_data(array( diff --git a/cache/classes/config.php b/cache/classes/config.php index 8d3fbc04682..91833559736 100644 --- a/cache/classes/config.php +++ b/cache/classes/config.php @@ -231,6 +231,21 @@ class cache_config { // Invalid cache mode used for the definition. continue; } + // Default the sharing option as it was added for 2.5. + // This can be removed sometime after the release of 2.6. + if (!array_key_exists('sharingoptions', $conf)) { + $conf['sharingoptions'] = cache_definition::SHARING_DEFAULTOPTIONS; + } + // Default the selected sharing option as it was added for 2.5. + // This can be removed sometime after the release of 2.6. + if (!array_key_exists('selectedsharingoption', $conf)) { + $conf['selectedsharingoption'] = cache_definition::SHARING_DEFAULT; + } + // Default the user input sharing key as it was added for 2.5. + // This can be removed sometime after the release of 2.6. + if (!array_key_exists('userinputsharingkey', $conf)) { + $conf['userinputsharingkey'] = ''; + } $this->configdefinitions[$id] = $conf; } diff --git a/cache/classes/definition.php b/cache/classes/definition.php index 62732f737ce..59f5d7f1d60 100644 --- a/cache/classes/definition.php +++ b/cache/classes/definition.php @@ -100,6 +100,8 @@ defined('MOODLE_INTERNAL') || die(); * reason or another. * + invalidationevents * [array] An array of events that should cause this cache to invalidate some or all of the items within it. + * + sharingoptions + * [int] The sharing options that are appropriate for this definition. Should be the sum of the possible options. * * For examples take a look at lib/db/caches.php * @@ -110,6 +112,26 @@ defined('MOODLE_INTERNAL') || die(); */ class cache_definition { + /** The cache can be shared with everyone */ + const SHARING_ALL = 1; + /** The cache can be shared with other sites using the same siteid. */ + const SHARING_SITEID = 2; + /** The cache can be shared with other sites of the same version. */ + const SHARING_VERSION = 4; + /** The cache can be shared with other sites using the same key */ + const SHARING_INPUT = 8; + + /** + * The default sharing options available. + * All + SiteID + Version + Input. + */ + const SHARING_DEFAULTOPTIONS = 15; + /** + * The default sharing option that gets used if none have been selected. + * SiteID. It is the most restrictive. + */ + const SHARING_DEFAULT = 2; + /** * The identifier for the definition * @var string @@ -281,6 +303,24 @@ class cache_definition { */ protected $definitionhash = null; + /** + * The selected sharing mode for this definition. + * @var int + */ + protected $sharingoptions; + + /** + * The selected sharing option. + * @var int One of self::SHARING_* + */ + protected $selectedsharingoption = self::SHARING_DEFAULT; + + /** + * The user input key to use if the SHARING_INPUT option has been selected. + * @var string Must be ALPHANUMEXT + */ + protected $userinputsharingkey = ''; + /** * Creates a cache definition given a definition from the cache configuration or from a caches.php file. * @@ -325,6 +365,9 @@ class cache_definition { $ttl = 0; $mappingsonly = false; $invalidationevents = array(); + $sharingoptions = self::SHARING_DEFAULT; + $selectedsharingoption = self::SHARING_DEFAULT; + $userinputsharingkey = ''; if (array_key_exists('simplekeys', $definition)) { $simplekeys = (bool)$definition['simplekeys']; @@ -387,6 +430,26 @@ class cache_definition { if (array_key_exists('invalidationevents', $definition)) { $invalidationevents = (array)$definition['invalidationevents']; } + if (array_key_exists('sharingoptions', $definition)) { + $sharingoptions = (int)$definition['sharingoptions']; + } + if (array_key_exists('selectedsharingoption', $definition)) { + $selectedsharingoption = (int)$definition['selectedsharingoption']; + } else if (array_key_exists('defaultsharing', $definition)) { + $selectedsharingoption = (int)$definition['defaultsharing']; + } else if ($sharingoptions ^ $selectedsharingoption) { + if ($sharingoptions & self::SHARING_SITEID) { + $selectedsharingoption = self::SHARING_SITEID; + } else if ($sharingoptions & self::SHARING_VERSION) { + $selectedsharingoption = self::SHARING_VERSION; + } else { + $selectedsharingoption = self::SHARING_ALL; + } + } + + if (array_key_exists('userinputsharingkey', $definition) && !empty($definition['userinputsharingkey'])) { + $userinputsharingkey = (string)$definition['userinputsharingkey']; + } if (!is_null($overrideclass)) { if (!is_null($overrideclassfile)) { @@ -457,6 +520,9 @@ class cache_definition { $cachedefinition->ttl = $ttl; $cachedefinition->mappingsonly = $mappingsonly; $cachedefinition->invalidationevents = $invalidationevents; + $cachedefinition->sharingoptions = $sharingoptions; + $cachedefinition->selectedsharingoption = $selectedsharingoption; + $cachedefinition->userinputsharingkey = $userinputsharingkey; return $cachedefinition; } @@ -496,6 +562,9 @@ class cache_definition { if (!empty($options['overrideclass'])) { $definition['overrideclass'] = $options['overrideclass']; } + if (!empty($options['sharingoptions'])) { + $definition['sharingoptions'] = $options['sharingoptions']; + } return self::load($id, $definition, null); } @@ -819,6 +888,20 @@ class cache_definition { * @return string A string to be used as part of keys. */ protected function get_cache_identifier() { - return cache_helper::get_site_identifier(); + $identifiers = array(); + if ($this->selectedsharingoption & self::SHARING_ALL) { + // Nothing to do here. + } else { + if ($this->selectedsharingoption & self::SHARING_SITEID) { + $identifiers[] = cache_helper::get_site_identifier(); + } + if ($this->selectedsharingoption & self::SHARING_VERSION) { + $identifiers[] = cache_helper::get_site_version(); + } + if ($this->selectedsharingoption & self::SHARING_INPUT && !empty($this->userinputsharingkey)) { + $identifiers[] = $this->userinputsharingkey; + } + } + return join('/', $identifiers); } } \ No newline at end of file diff --git a/cache/forms.php b/cache/forms.php index b50927355d3..c97f821c8e0 100644 --- a/cache/forms.php +++ b/cache/forms.php @@ -180,6 +180,55 @@ class cache_definition_mappings_form extends moodleform { } } +/** + * Form to set definition sharing option + * + * @package core + * @category cache + * @copyright 2013 Sam Hemelryk + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class cache_definition_sharing_form extends moodleform { + /** + * The definition of the form + */ + protected final function definition() { + $definition = $this->_customdata['definition']; + $sharingoptions = $this->_customdata['sharingoptions']; + $form = $this->_form; + + $form->addElement('hidden', 'definition', $definition); + $form->setType('sharing', PARAM_SAFEPATH); + $form->addElement('hidden', 'action', 'editdefinitionsharing'); + $form->setType('sharing', PARAM_ALPHA); + + $count = 0; + foreach ($sharingoptions as $value => $text) { + $count++; + $name = ($count == 1) ? get_string('sharing', 'cache') : null; + $form->addElement('checkbox', 'sharing['.$value.']', $name, $text); + } + $form->setType('sharing', PARAM_INT); + $form->setDefault('sharing', cache_administration_helper::get_definition_sharing_options(cache_definition::SHARING_DEFAULT)); + + $form->addElement('text', 'userinputsharingkey', get_string('userinputsharingkey', 'cache')); + $form->addHelpButton('userinputsharingkey', 'userinputsharingkey', 'cache'); + $form->disabledIf('userinputsharingkey', 'sharing['.cache_definition::SHARING_INPUT.']', 'notchecked'); + $form->setType('userinputsharingkey', PARAM_ALPHANUMEXT); + + $values = array_keys($sharingoptions); + if (in_array(cache_definition::SHARING_ALL, $values)) { + // If you share with all thenthe other options don't really make sense. + foreach ($values as $value) { + $form->disabledIf('sharing['.$value.']', 'sharing['.cache_definition::SHARING_ALL.']', 'checked'); + } + $form->disabledIf('userinputsharingkey', 'sharing['.cache_definition::SHARING_ALL.']', 'checked'); + } + + $this->add_action_buttons(); + } +} + /** * Form to set the mappings for a mode. * diff --git a/cache/locallib.php b/cache/locallib.php index 617ac7dbedc..f31ce3e7154 100644 --- a/cache/locallib.php +++ b/cache/locallib.php @@ -551,6 +551,20 @@ class cache_config_writer extends cache_config { * @param array $definitions */ private function write_definitions_to_cache(array $definitions) { + + // Preserve the selected sharing option when updating the definitions. + // This is set by the user and should never come from caches.php. + foreach ($definitions as $key => $definition) { + unset($definitions[$key]['selectedsharingoption']); + unset($definitions[$key]['userinputsharingkey']); + if (isset($this->configdefinitions[$key]) && isset($this->configdefinitions[$key]['selectedsharingoption'])) { + $definitions[$key]['selectedsharingoption'] = $this->configdefinitions[$key]['selectedsharingoption']; + } + if (isset($this->configdefinitions[$key]) && isset($this->configdefinitions[$key]['userinputsharingkey'])) { + $definitions[$key]['userinputsharingkey'] = $this->configdefinitions[$key]['userinputsharingkey']; + } + } + $this->configdefinitions = $definitions; foreach ($this->configdefinitionmappings as $key => $mapping) { if (!array_key_exists($mapping['definition'], $definitions)) { @@ -619,6 +633,29 @@ class cache_config_writer extends cache_config { $this->config_save(); return $this->siteidentifier; } + + /** + * Sets the selected sharing options and key for a definition. + * + * @param string $definition The name of the definition to set for. + * @param int $sharingoption The sharing option to set. + * @param string|null $userinputsharingkey The user input key or null. + * @throws coding_exception + */ + public function set_definition_sharing($definition, $sharingoption, $userinputsharingkey = null) { + if (!array_key_exists($definition, $this->configdefinitions)) { + throw new coding_exception('Invalid definition name passed when updating sharing options.'); + } + if (!($this->configdefinitions[$definition]['sharingoptions'] & $sharingoption)) { + throw new coding_exception('Invalid sharing option passed when updating definition.'); + } + $this->configdefinitions[$definition]['selectedsharingoption'] = (int)$sharingoption; + if (!empty($userinputsharingkey)) { + $this->configdefinitions[$definition]['userinputsharingkey'] = (string)$userinputsharingkey; + } + $this->config_save(); + } + } /** @@ -790,12 +827,40 @@ abstract class cache_administration_helper extends cache_helper { 'mode' => $definition['mode'], 'component' => $definition['component'], 'area' => $definition['area'], - 'mappings' => $mappings + 'mappings' => $mappings, + 'sharingoptions' => self::get_definition_sharing_options($definition['sharingoptions'], false), + 'selectedsharingoption' => self::get_definition_sharing_options($definition['selectedsharingoption'], true), + 'userinputsharingkey' => $definition['userinputsharingkey'] ); } return $return; } + /** + * Given a sharing option hash this function returns an array of strings that can be used to describe it. + * + * @param int $sharingoption The sharing option hash to get strings for. + * @param bool $isselectedoptions Set to true if the strings will be used to view the selected options. + * @return array An array of lang_string's. + */ + public static function get_definition_sharing_options($sharingoption, $isselectedoptions = true) { + $options = array(); + $prefix = ($isselectedoptions) ? 'sharingselected' : 'sharing'; + if ($sharingoption & cache_definition::SHARING_ALL) { + $options[cache_definition::SHARING_ALL] = new lang_string($prefix.'_all', 'cache'); + } + if ($sharingoption & cache_definition::SHARING_SITEID) { + $options[cache_definition::SHARING_SITEID] = new lang_string($prefix.'_siteid', 'cache'); + } + if ($sharingoption & cache_definition::SHARING_VERSION) { + $options[cache_definition::SHARING_VERSION] = new lang_string($prefix.'_version', 'cache'); + } + if ($sharingoption & cache_definition::SHARING_INPUT) { + $options[cache_definition::SHARING_INPUT] = new lang_string($prefix.'_input', 'cache'); + } + return $options; + } + /** * Returns all of the actions that can be performed on a definition. * @param context $context @@ -804,10 +869,17 @@ abstract class cache_administration_helper extends cache_helper { public static function get_definition_actions(context $context) { if (has_capability('moodle/site:config', $context)) { return array( + // Edit mappings. array( 'text' => get_string('editmappings', 'cache'), 'url' => new moodle_url('/cache/admin.php', array('action' => 'editdefinitionmapping', 'sesskey' => sesskey())) ), + // Edit sharing. + array( + 'text' => get_string('editsharing', 'cache'), + 'url' => new moodle_url('/cache/admin.php', array('action' => 'editdefinitionsharing', 'sesskey' => sesskey())) + ), + // Purge. array( 'text' => get_string('purge', 'cache'), 'url' => new moodle_url('/cache/admin.php', array('action' => 'purgedefinition', 'sesskey' => sesskey())) diff --git a/cache/renderer.php b/cache/renderer.php index 98a20c5b6b5..25566f01809 100644 --- a/cache/renderer.php +++ b/cache/renderer.php @@ -222,6 +222,7 @@ class core_cache_renderer extends plugin_renderer_base { get_string('component', 'cache'), get_string('area', 'cache'), get_string('mappings', 'cache'), + get_string('sharing', 'cache'), get_string('actions', 'cache'), ); $table->colclasses = array( @@ -230,6 +231,7 @@ class core_cache_renderer extends plugin_renderer_base { 'component', 'area', 'mappings', + 'sharing', 'actions' ); $table->data = array(); @@ -253,6 +255,7 @@ class core_cache_renderer extends plugin_renderer_base { $definition['component'], $definition['area'], $mapping, + join(', ', $definition['selectedsharingoption']), join(', ', $htmlactions) )); $row->attributes['class'] = 'definition-'.$definition['component'].'-'.$definition['area']; diff --git a/cache/tests/fixtures/lib.php b/cache/tests/fixtures/lib.php index b4de9d05da2..bdb229143f3 100644 --- a/cache/tests/fixtures/lib.php +++ b/cache/tests/fixtures/lib.php @@ -58,6 +58,14 @@ class cache_config_phpunittest extends cache_config_writer { $this->configdefinitions[$area] = $properties; } + /** + * Removes a definition. + * @param string $name + */ + public function phpunit_remove_definition($name) { + unset($this->configdefinitions[$name]); + } + /** * Removes the configured stores so that there are none available. */ diff --git a/cache/tests/locallib_test.php b/cache/tests/locallib_test.php index 004a5378086..56397f212c4 100644 --- a/cache/tests/locallib_test.php +++ b/cache/tests/locallib_test.php @@ -154,15 +154,16 @@ class cache_config_writer_phpunit_tests extends advanced_testcase { */ public function test_update_definitions() { $config = cache_config_writer::instance(); - $earlydefinitions = $config->get_definitions(); - unset($config); - cache_factory::reset(); + // Remove the definition. + $config->phpunit_remove_definition('core/string'); + $definitions = $config->get_definitions(); + // Check it is gone. + $this->assertFalse(array_key_exists('core/string', $definitions)); + // Update definitions. This should re-add it. cache_config_writer::update_definitions(); - - $config = cache_config_writer::instance(); - $latedefinitions = $config->get_definitions(); - - $this->assertSame($latedefinitions, $earlydefinitions); + $definitions = $config->get_definitions(); + // Check it is back again. + $this->assertTrue(array_key_exists('core/string', $definitions)); } /** diff --git a/lang/en/cache.php b/lang/en/cache.php index 0c9686d152c..a0ffc1ea6bc 100644 --- a/lang/en/cache.php +++ b/lang/en/cache.php @@ -81,6 +81,7 @@ $string['deletestoreconfirmation'] = 'Are you sure you want to delete the "{$a}" $string['deletestorehasmappings'] = 'You cannot delete this store because it has mappings. Please delete all mappings before deleting the store'; $string['deletestoresuccess'] = 'Successfully deleted the cache store'; $string['editmappings'] = 'Edit mappings'; +$string['editsharing'] = 'Edit sharing'; $string['editstore'] = 'Edit store'; $string['editstoresuccess'] = 'Succesfully edited the cache store.'; $string['editdefinitionmappings'] = '{$a} definition store mappings'; @@ -123,6 +124,16 @@ $string['requestcount'] = 'Test with {$a} requests'; $string['rescandefinitions'] = 'Rescan definitions'; $string['result'] = 'Result'; $string['set'] = 'Set'; +$string['sharing'] = 'Sharing'; +$string['sharing_all'] = 'Everyone.'; +$string['sharing_input'] = 'Custom key (entered below)'; +$string['sharing_help'] = 'This allows you to determine how the cache data can be shared if you have a clustered setup, or if you have multiple sites all set up with the same store and wish to share the data. This is an advanced setting please make sure you understand its purpose before changing it.'; +$string['sharing_siteid'] = 'Sites with the same site id.'; +$string['sharing_version'] = 'Sites running the same version.'; +$string['sharingselected_all'] = 'Everyone'; +$string['sharingselected_input'] = 'Custom key'; +$string['sharingselected_siteid'] = 'Site identifier'; +$string['sharingselected_version'] = 'Version'; $string['storeconfiguration'] = 'Store configuration'; $string['storename'] = 'Store name'; $string['storename_help'] = 'This sets the store name. It is used to identify the store within the system and can only consist of a-z A-Z 0-9 -_ and spaces. It also must be unique. If you attempt to use a name that has already been used you will receive an error.'; @@ -152,3 +163,5 @@ $string['tested'] = 'Tested'; $string['testperformance'] = 'Test performance'; $string['unsupportedmode'] = 'Unsupported mode'; $string['untestable'] = 'Untestable'; +$string['userinputsharingkey'] = 'Custom key for sharing'; +$string['userinputsharingkey_help'] = 'Enter your own private key here. When you set up other stores on other sites you wish to share data with make sure you set the exact same key there.'; From 5f5776c1e658a416c2218e303ce32415bc684d4b Mon Sep 17 00:00:00 2001 From: Sam Hemelryk Date: Tue, 7 May 2013 12:18:42 +1200 Subject: [PATCH 2/2] MDL-37500 cache: integration review touchups. Issues: 1. Fix setType calls 2. Changing from default to all and editing again, default is still selected. 3. Uncheck all options causes error. 4. Doesn't seem to be restricting based upon option. 5. Picked all and got coding error with the database thing. 6. Bad title: https://github.com/samhemelryk/moodle/commit/wip-MDL-37500-m25#L1R157 7. Amend comments - should only be removed once 2.5 is the minimum version for an upgrade. 8. Document the defaultsharing option. Outcomes: 1. Fixed - copy paste error. 2. Fixed - mforms was applying the default value despite a value being provided. A quirk of elements with array names. 3. Fixed - validation now requires at least one option to be selected. 4. Fixed - issue rose from definitions not being re-parsed. cache/admin.php now reparses the first time a user visits the page. 5. Fixed - better purging of definitions when working with them anonymously. Unit test added. 6. Fixed - new string added and used. 7. Fixed - comments amended. New issue to address parsing of definitions during upgrade. New issue to add debugging notice if definition sets only one possible sharing option and that option is user input. --- cache/README.md | 2 ++ cache/admin.php | 17 ++++++++++++--- cache/classes/config.php | 12 +++++------ cache/classes/definition.php | 3 +++ cache/classes/helper.php | 26 +++++++++++++++++++++++ cache/forms.php | 40 +++++++++++++++++++++++++++++++----- cache/tests/cache_test.php | 22 ++++++++++++++++++++ lang/en/cache.php | 2 ++ 8 files changed, 110 insertions(+), 14 deletions(-) diff --git a/cache/README.md b/cache/README.md index ca7815a159b..c1f67c2510f 100644 --- a/cache/README.md +++ b/cache/README.md @@ -32,6 +32,7 @@ A definition: 'contextmarkeddirty' ), 'sharingoptions' => null // Optional + 'defaultsharing' => null // Optional ) ); @@ -149,6 +150,7 @@ The following optional settings can also be defined: * mappingsonly - This definition can only be used if there is a store mapping for it. More on this later. * invalidationevents - An array of events that should trigger this cache to invalidate. * sharingoptions - The sum of the possible sharing options that are applicable to the definition. An advanced setting. +* defaultsharing - The default sharing option to use. It's highly recommended that you don't set this unless there is a very specific reason not to use the system default. It's important to note that internally the definition is also aware of the component. This is picked up when the definition is read, based upon the location of the caches.php file. diff --git a/cache/admin.php b/cache/admin.php index d3991890dc8..50a818853c8 100644 --- a/cache/admin.php +++ b/cache/admin.php @@ -30,6 +30,15 @@ require_once($CFG->dirroot.'/lib/adminlib.php'); require_once($CFG->dirroot.'/cache/locallib.php'); require_once($CFG->dirroot.'/cache/forms.php'); +// The first time the user visits this page we are going to reparse the definitions. +// Just ensures that everything is up to date. +// We flag is session so that this only happens once as people are likely to hit +// this page several times if making changes. +if (empty($SESSION->cacheadminreparsedefinitions)) { + cache_helper::update_definitions(); + $SESSION->cacheadminreparsedefinitions = true; +} + $action = optional_param('action', null, PARAM_ALPHA); admin_externalpage_setup('cacheconfig'); @@ -155,9 +164,10 @@ if (!empty($action) && confirm_sesskey()) { if (!array_key_exists($definition, $definitions)) { throw new cache_exception('Invalid cache definition requested'); } - $title = get_string('editdefinitionmappings', 'cache', $definition); + $title = get_string('editdefinitionsharing', 'cache', $definition); $sharingoptions = $definitions[$definition]['sharingoptions']; - $mform = new cache_definition_sharing_form($PAGE->url, array('definition' => $definition, 'sharingoptions' => $sharingoptions)); + $customdata = array('definition' => $definition, 'sharingoptions' => $sharingoptions); + $mform = new cache_definition_sharing_form($PAGE->url, $customdata); $mform->set_data(array( 'sharing' => $definitions[$definition]['selectedsharingoption'], 'userinputsharingkey' => $definitions[$definition]['userinputsharingkey'] @@ -167,7 +177,8 @@ if (!empty($action) && confirm_sesskey()) { } else if ($data = $mform->get_data()) { $component = $definitions[$definition]['component']; $area = $definitions[$definition]['area']; - cache_helper::purge_by_definition($component, $area); + // Purge the stores removing stale data before we alter the sharing option. + cache_helper::purge_stores_used_by_definition($component, $area); $writer = cache_config_writer::instance(); $sharing = array_sum(array_keys($data->sharing)); $userinputsharingkey = $data->userinputsharingkey; diff --git a/cache/classes/config.php b/cache/classes/config.php index 91833559736..e97ac43decd 100644 --- a/cache/classes/config.php +++ b/cache/classes/config.php @@ -232,18 +232,18 @@ class cache_config { continue; } // Default the sharing option as it was added for 2.5. - // This can be removed sometime after the release of 2.6. - if (!array_key_exists('sharingoptions', $conf)) { + // This can be removed sometime after 2.5 is the minimum version someone can upgrade from. + if (!isset($conf['sharingoptions'])) { $conf['sharingoptions'] = cache_definition::SHARING_DEFAULTOPTIONS; } // Default the selected sharing option as it was added for 2.5. - // This can be removed sometime after the release of 2.6. - if (!array_key_exists('selectedsharingoption', $conf)) { + // This can be removed sometime after 2.5 is the minimum version someone can upgrade from. + if (!isset($conf['selectedsharingoption'])) { $conf['selectedsharingoption'] = cache_definition::SHARING_DEFAULT; } // Default the user input sharing key as it was added for 2.5. - // This can be removed sometime after the release of 2.6. - if (!array_key_exists('userinputsharingkey', $conf)) { + // This can be removed sometime after 2.5 is the minimum version someone can upgrade from. + if (!isset($conf['userinputsharingkey'])) { $conf['userinputsharingkey'] = ''; } $this->configdefinitions[$id] = $conf; diff --git a/cache/classes/definition.php b/cache/classes/definition.php index 59f5d7f1d60..d4b3dc5d82a 100644 --- a/cache/classes/definition.php +++ b/cache/classes/definition.php @@ -102,6 +102,9 @@ defined('MOODLE_INTERNAL') || die(); * [array] An array of events that should cause this cache to invalidate some or all of the items within it. * + sharingoptions * [int] The sharing options that are appropriate for this definition. Should be the sum of the possible options. + * + defaultsharing + * [int] The default sharing option to use. It's highly recommended that you don't set this unless there is a very + * specific reason not to use the system default. * * For examples take a look at lib/db/caches.php * diff --git a/cache/classes/helper.php b/cache/classes/helper.php index be329f77cde..7636ce39238 100644 --- a/cache/classes/helper.php +++ b/cache/classes/helper.php @@ -276,6 +276,11 @@ class cache_helper { /** * Purges the cache for a specific definition. * + * If you need to purge a definition that requires identifiers or an aggregate and you don't + * know the details of those please use cache_helper::purge_stores_used_by_definition instead. + * It is a more aggressive purge and will purge all data within the store, not just the data + * belonging to the given definition. + * * @todo MDL-36660: Change the signature: $aggregate must be added. * * @param string $component @@ -455,6 +460,27 @@ class cache_helper { return true; } + /** + * Purges all of the stores used by a definition. + * + * Unlike cache_helper::purge_by_definition this purges all of the data from the stores not + * just the data relating to the definition. + * This function is useful when you must purge a definition that requires setup but you don't + * want to set it up. + * + * @param string $component + * @param string $area + */ + public static function purge_stores_used_by_definition($component, $area) { + $factory = cache_factory::instance(); + $config = $factory->create_config_instance(); + $definition = $factory->create_definition($component, $area); + $stores = $config->get_stores_for_definition($definition); + foreach ($stores as $store) { + self::purge_store($store['name']); + } + } + /** * Returns the translated name of the definition. * diff --git a/cache/forms.php b/cache/forms.php index c97f821c8e0..2abfabce2b9 100644 --- a/cache/forms.php +++ b/cache/forms.php @@ -198,18 +198,19 @@ class cache_definition_sharing_form extends moodleform { $form = $this->_form; $form->addElement('hidden', 'definition', $definition); - $form->setType('sharing', PARAM_SAFEPATH); + $form->setType('definition', PARAM_SAFEPATH); $form->addElement('hidden', 'action', 'editdefinitionsharing'); - $form->setType('sharing', PARAM_ALPHA); + $form->setType('action', PARAM_ALPHA); + // We use a group here for validation. $count = 0; + $group = array(); foreach ($sharingoptions as $value => $text) { $count++; - $name = ($count == 1) ? get_string('sharing', 'cache') : null; - $form->addElement('checkbox', 'sharing['.$value.']', $name, $text); + $group[] = $form->createElement('checkbox', $value, null, $text); } + $form->addGroup($group, 'sharing', get_string('sharing', 'cache'), '
'); $form->setType('sharing', PARAM_INT); - $form->setDefault('sharing', cache_administration_helper::get_definition_sharing_options(cache_definition::SHARING_DEFAULT)); $form->addElement('text', 'userinputsharingkey', get_string('userinputsharingkey', 'cache')); $form->addHelpButton('userinputsharingkey', 'userinputsharingkey', 'cache'); @@ -227,6 +228,35 @@ class cache_definition_sharing_form extends moodleform { $this->add_action_buttons(); } + + /** + * Sets the data for this form. + * + * @param array $data + */ + public function set_data($data) { + if (!isset($data['sharing'])) { + // Set the default value here. mforms doesn't handle defaults very nicely. + $data['sharing'] = cache_administration_helper::get_definition_sharing_options(cache_definition::SHARING_DEFAULT); + } + parent::set_data($data); + } + + /** + * Validates this form + * + * @param array $data + * @param array $files + * @return array + */ + public function validation($data, $files) { + $errors = parent::validation($data, $files); + if (count($errors) === 0 && !isset($data['sharing'])) { + // They must select at least one sharing option. + $errors['sharing'] = get_string('sharingrequired', 'cache'); + } + return $errors; + } } /** diff --git a/cache/tests/cache_test.php b/cache/tests/cache_test.php index b13a4b570eb..cff510dde1f 100644 --- a/cache/tests/cache_test.php +++ b/cache/tests/cache_test.php @@ -1305,4 +1305,26 @@ class cache_phpunit_tests extends advanced_testcase { $this->assertTrue($cache->delete('a')); $this->assertFalse($cache->has('a')); } + + /** + * Test the static cache_helper method purge_stores_used_by_definition. + */ + public function test_purge_stores_used_by_definition() { + $instance = cache_config_phpunittest::instance(true); + $instance->phpunit_add_definition('phpunit/test_purge_stores_used_by_definition', array( + 'mode' => cache_store::MODE_APPLICATION, + 'component' => 'phpunit', + 'area' => 'test_purge_stores_used_by_definition' + )); + $cache = cache::make('phpunit', 'test_purge_stores_used_by_definition'); + $this->assertInstanceOf('cache_application', $cache); + $this->assertTrue($cache->set('test', 'test')); + unset($cache); + + cache_helper::purge_stores_used_by_definition('phpunit', 'test_purge_stores_used_by_definition'); + + $cache = cache::make('phpunit', 'test_purge_stores_used_by_definition'); + $this->assertInstanceOf('cache_application', $cache); + $this->assertFalse($cache->get('test')); + } } diff --git a/lang/en/cache.php b/lang/en/cache.php index a0ffc1ea6bc..1dd16963c28 100644 --- a/lang/en/cache.php +++ b/lang/en/cache.php @@ -85,6 +85,7 @@ $string['editsharing'] = 'Edit sharing'; $string['editstore'] = 'Edit store'; $string['editstoresuccess'] = 'Succesfully edited the cache store.'; $string['editdefinitionmappings'] = '{$a} definition store mappings'; +$string['editdefinitionsharing'] = 'Edit definition sharing for {$a}'; $string['ex_configcannotsave'] = 'Unable to save the cache config to file.'; $string['ex_nodefaultlock'] = 'Unable to find a default lock instance.'; $string['ex_unabletolock'] = 'Unable to acquire a lock for caching.'; @@ -130,6 +131,7 @@ $string['sharing_input'] = 'Custom key (entered below)'; $string['sharing_help'] = 'This allows you to determine how the cache data can be shared if you have a clustered setup, or if you have multiple sites all set up with the same store and wish to share the data. This is an advanced setting please make sure you understand its purpose before changing it.'; $string['sharing_siteid'] = 'Sites with the same site id.'; $string['sharing_version'] = 'Sites running the same version.'; +$string['sharingrequired'] = 'You must select at least one sharing option.'; $string['sharingselected_all'] = 'Everyone'; $string['sharingselected_input'] = 'Custom key'; $string['sharingselected_siteid'] = 'Site identifier';