MDL-37545 cache: fixed bug during initialisation and updating of cache config
[moodle.git] / cache / locallib.php
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/>.
17 /**
18  * The supplementary cache API.
19  *
20  * This file is part of Moodle's cache API, affectionately called MUC.
21  * It contains elements of the API that are not required in order to use caching.
22  * Things in here are more in line with administration and management of the cache setup and configuration.
23  *
24  * @package    core
25  * @category   cache
26  * @copyright  2012 Sam Hemelryk
27  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
28  */
30 defined('MOODLE_INTERNAL') || die();
32 /**
33  * Cache configuration writer.
34  *
35  * This class should only be used when you need to write to the config, all read operations exist within the cache_config.
36  *
37  * @package    core
38  * @category   cache
39  * @copyright  2012 Sam Hemelryk
40  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
41  */
42 class cache_config_writer extends cache_config {
44     /**
45      * Switch that gets set to true when ever a cache_config_writer instance is saving the cache configuration file.
46      * If this is set to true when save is next called we must avoid the trying to save and instead return the
47      * generated config so that is may be used instead of the file.
48      * @var bool
49      */
50     protected static $creatingconfig = false;
52     /**
53      * Returns an instance of the configuration writer.
54      *
55      * @return cache_config_writer
56      */
57     public static function instance() {
58         $factory = cache_factory::instance();
59         return $factory->create_config_instance(true);
60     }
62     /**
63      * Saves the current configuration.
64      *
65      * Exceptions within this function are tolerated but must be of type cache_exception.
66      * They are caught during initialisation and written to the error log. This is required in order to avoid
67      * infinite loop situations caused by the cache throwing exceptions during its initialisation.
68      */
69     protected function config_save() {
70         global $CFG;
71         $cachefile = self::get_config_file_path();
72         $directory = dirname($cachefile);
73         if ($directory !== $CFG->dataroot && !file_exists($directory)) {
74             $result = make_writable_directory($directory, false);
75             if (!$result) {
76                 throw new cache_exception('ex_configcannotsave', 'cache', '', null, 'Cannot create config directory. Check the permissions on your moodledata directory.');
77             }
78         }
79         if (!file_exists($directory) || !is_writable($directory)) {
80             throw new cache_exception('ex_configcannotsave', 'cache', '', null, 'Config directory is not writable. Check the permissions on the moodledata/muc directory.');
81         }
83         // Prepare a configuration array to store.
84         $configuration = $this->generate_configuration_array();
86         // Prepare the file content.
87         $content = "<?php defined('MOODLE_INTERNAL') || die();\n \$configuration = ".var_export($configuration, true).";";
89         // We need to create a temporary cache lock instance for use here. Remember we are generating the config file
90         // it doesn't exist and thus we can't use the normal API for this (it'll just try to use config).
91         $lockconf = reset($this->configlocks);
92         if ($lockconf === false) {
93             debugging('Your cache configuration file is out of date and needs to be refreshed.', DEBUG_DEVELOPER);
94             // Use the default
95             $lockconf = array(
96                 'name' => 'cachelock_file_default',
97                 'type' => 'cachelock_file',
98                 'dir' => 'filelocks',
99                 'default' => true
100             );
101         }
102         $factory = cache_factory::instance();
103         $locking = $factory->create_lock_instance($lockconf);
104         if ($locking->lock('configwrite', 'config', true)) {
105             // Its safe to use w mode here because we have already acquired the lock.
106             $handle = fopen($cachefile, 'w');
107             fwrite($handle, $content);
108             fflush($handle);
109             fclose($handle);
110             $locking->unlock('configwrite', 'config');
111         } else {
112             throw new cache_exception('ex_configcannotsave', 'cache', '', null, 'Unable to open the cache config file.');
113         }
114     }
116     /**
117      * Generates a configuration array suitable to be written to the config file.
118      * @return array
119      */
120     protected function generate_configuration_array() {
121         $configuration = array();
122         $configuration['stores'] = $this->configstores;
123         $configuration['modemappings'] = $this->configmodemappings;
124         $configuration['definitions'] = $this->configdefinitions;
125         $configuration['definitionmappings'] = $this->configdefinitionmappings;
126         $configuration['locks'] = $this->configlocks;
127         return $configuration;
128     }
130     /**
131      * Adds a plugin instance.
132      *
133      * This function also calls save so you should redirect immediately, or at least very shortly after
134      * calling this method.
135      *
136      * @param string $name The name for the instance (must be unique)
137      * @param string $plugin The name of the plugin.
138      * @param array $configuration The configuration data for the plugin instance.
139      * @return bool
140      * @throws cache_exception
141      */
142     public function add_store_instance($name, $plugin, array $configuration = array()) {
143         if (array_key_exists($name, $this->configstores)) {
144             throw new cache_exception('Duplicate name specificed for cache plugin instance. You must provide a unique name.');
145         }
146         $class = 'cachestore_'.$plugin;
147         if (!class_exists($class)) {
148             $plugins = get_plugin_list_with_file('cachestore', 'lib.php');
149             if (!array_key_exists($plugin, $plugins)) {
150                 throw new cache_exception('Invalid plugin name specified. The plugin does not exist or is not valid.');
151             }
152             $file = $plugins[$plugin];
153             if (file_exists($file)) {
154                 require_once($file);
155             }
156             if (!class_exists($class)) {
157                 throw new cache_exception('Invalid cache plugin specified. The plugin does not contain the required class.');
158             }
159         }
160         $reflection = new ReflectionClass($class);
161         if (!$reflection->isSubclassOf('cache_store')) {
162             throw new cache_exception('Invalid cache plugin specified. The plugin does not extend the required class.');
163         }
164         if (!$class::are_requirements_met()) {
165             throw new cache_exception('Unable to add new cache plugin instance. The requested plugin type is not supported.');
166         }
167         $this->configstores[$name] = array(
168             'name' => $name,
169             'plugin' => $plugin,
170             'configuration' => $configuration,
171             'features' => $class::get_supported_features($configuration),
172             'modes' => $class::get_supported_modes($configuration),
173             'mappingsonly' => !empty($configuration['mappingsonly']),
174             'class' => $class,
175             'default' => false
176         );
177         if (array_key_exists('lock', $configuration)) {
178             $this->configstores[$name]['lock'] = $configuration['lock'];
179             unset($this->configstores[$name]['configuration']['lock']);
180         }
181         $this->config_save();
182         return true;
183     }
185     /**
186      * Sets the mode mappings.
187      *
188      * These determine the default caches for the different modes.
189      * This function also calls save so you should redirect immediately, or at least very shortly after
190      * calling this method.
191      *
192      * @param array $modemappings
193      * @return bool
194      * @throws cache_exception
195      */
196     public function set_mode_mappings(array $modemappings) {
197         $mappings = array(
198             cache_store::MODE_APPLICATION => array(),
199             cache_store::MODE_SESSION => array(),
200             cache_store::MODE_REQUEST => array(),
201         );
202         foreach ($modemappings as $mode => $stores) {
203             if (!array_key_exists($mode, $mappings)) {
204                 throw new cache_exception('The cache mode for the new mapping does not exist');
205             }
206             $sort = 0;
207             foreach ($stores as $store) {
208                 if (!array_key_exists($store, $this->configstores)) {
209                     throw new cache_exception('The instance name for the new mapping does not exist');
210                 }
211                 if (array_key_exists($store, $mappings[$mode])) {
212                     throw new cache_exception('This cache mapping already exists');
213                 }
214                 $mappings[$mode][] = array(
215                     'store' => $store,
216                     'mode' => $mode,
217                     'sort' => $sort++
218                 );
219             }
220         }
221         $this->configmodemappings = array_merge(
222             $mappings[cache_store::MODE_APPLICATION],
223             $mappings[cache_store::MODE_SESSION],
224             $mappings[cache_store::MODE_REQUEST]
225         );
227         $this->config_save();
228         return true;
229     }
231     /**
232      * Edits a give plugin instance.
233      *
234      * The plugin instance is determined by its name, hence you cannot rename plugins.
235      * This function also calls save so you should redirect immediately, or at least very shortly after
236      * calling this method.
237      *
238      * @param string $name
239      * @param string $plugin
240      * @param array $configuration
241      * @return bool
242      * @throws cache_exception
243      */
244     public function edit_store_instance($name, $plugin, $configuration) {
245         if (!array_key_exists($name, $this->configstores)) {
246             throw new cache_exception('The requested instance does not exist.');
247         }
248         $plugins = get_plugin_list_with_file('cachestore', 'lib.php');
249         if (!array_key_exists($plugin, $plugins)) {
250             throw new cache_exception('Invalid plugin name specified. The plugin either does not exist or is not valid.');
251         }
252         $class = 'cachestore_'.$plugin;
253         $file = $plugins[$plugin];
254         if (!class_exists($class)) {
255             if (file_exists($file)) {
256                 require_once($file);
257             }
258             if (!class_exists($class)) {
259                 throw new cache_exception('Invalid cache plugin specified. The plugin does not contain the required class.'.$class);
260             }
261         }
262         $this->configstores[$name] = array(
263             'name' => $name,
264             'plugin' => $plugin,
265             'configuration' => $configuration,
266             'features' => $class::get_supported_features($configuration),
267             'modes' => $class::get_supported_modes($configuration),
268             'mappingsonly' => !empty($configuration['mappingsonly']),
269             'class' => $class,
270             'default' => $this->configstores[$name]['default'] // Can't change the default.
271         );
272         if (array_key_exists('lock', $configuration)) {
273             $this->configstores[$name]['lock'] = $configuration['lock'];
274             unset($this->configstores[$name]['configuration']['lock']);
275         }
276         $this->config_save();
277         return true;
278     }
280     /**
281      * Deletes a store instance.
282      *
283      * This function also calls save so you should redirect immediately, or at least very shortly after
284      * calling this method.
285      *
286      * @param string $name The name of the instance to delete.
287      * @return bool
288      * @throws cache_exception
289      */
290     public function delete_store_instance($name) {
291         if (!array_key_exists($name, $this->configstores)) {
292             throw new cache_exception('The requested store does not exist.');
293         }
294         if ($this->configstores[$name]['default']) {
295             throw new cache_exception('The can not delete the default stores.');
296         }
297         foreach ($this->configmodemappings as $mapping) {
298             if ($mapping['store'] === $name) {
299                 throw new cache_exception('You cannot delete a cache store that has mode mappings.');
300             }
301         }
302         foreach ($this->configdefinitionmappings as $mapping) {
303             if ($mapping['store'] === $name) {
304                 throw new cache_exception('You cannot delete a cache store that has definition mappings.');
305             }
306         }
307         unset($this->configstores[$name]);
308         $this->config_save();
309         return true;
310     }
312     /**
313      * Creates the default configuration and saves it.
314      *
315      * This function calls config_save, however it is safe to continue using it afterwards as this function should only ever
316      * be called when there is no configuration file already.
317      *
318      * @return true|array Returns true if the default configuration was successfully created.
319      *     Returns a configuration array if it could not be saved. This is a bad situation. Check your error logs.
320      */
321     public static function create_default_configuration() {
322         global $CFG;
324         // HACK ALERT.
325         // We probably need to come up with a better way to create the default stores, or at least ensure 100% that the
326         // default store plugins are protected from deletion.
327         require_once($CFG->dirroot.'/cache/stores/file/lib.php');
328         require_once($CFG->dirroot.'/cache/stores/session/lib.php');
329         require_once($CFG->dirroot.'/cache/stores/static/lib.php');
331         $writer = new self;
332         $writer->configstores = array(
333             'default_application' => array(
334                 'name' => 'default_application',
335                 'plugin' => 'file',
336                 'configuration' => array(),
337                 'features' => cachestore_file::get_supported_features(),
338                 'modes' => cache_store::MODE_APPLICATION,
339                 'default' => true,
340             ),
341             'default_session' => array(
342                 'name' => 'default_session',
343                 'plugin' => 'session',
344                 'configuration' => array(),
345                 'features' => cachestore_session::get_supported_features(),
346                 'modes' => cache_store::MODE_SESSION,
347                 'default' => true,
348             ),
349             'default_request' => array(
350                 'name' => 'default_request',
351                 'plugin' => 'static',
352                 'configuration' => array(),
353                 'features' => cachestore_static::get_supported_features(),
354                 'modes' => cache_store::MODE_REQUEST,
355                 'default' => true,
356             )
357         );
358         $writer->configdefinitions = self::locate_definitions();
359         $writer->configmodemappings = array(
360             array(
361                 'mode' => cache_store::MODE_APPLICATION,
362                 'store' => 'default_application',
363                 'sort' => -1
364             ),
365             array(
366                 'mode' => cache_store::MODE_SESSION,
367                 'store' => 'default_session',
368                 'sort' => -1
369             ),
370             array(
371                 'mode' => cache_store::MODE_REQUEST,
372                 'store' => 'default_request',
373                 'sort' => -1
374             )
375         );
376         $writer->configlocks = array(
377             'default_file_lock' => array(
378                 'name' => 'cachelock_file_default',
379                 'type' => 'cachelock_file',
380                 'dir' => 'filelocks',
381                 'default' => true
382             )
383         );
385         $factory = cache_factory::instance();
386         // We expect the cache to be initialising presently. If its not then something has gone wrong and likely
387         // we are now in a loop.
388         if ($factory->get_state() !== cache_factory::STATE_INITIALISING) {
389             return $writer->generate_configuration_array();
390         }
391         $factory->set_state(cache_factory::STATE_SAVING);
392         $writer->config_save();
393         return true;
394     }
396     /**
397      * Updates the definition in the configuration from those found in the cache files.
398      *
399      * Calls config_save further down, you should redirect immediately or asap after calling this method.
400      *
401      * @param bool $coreonly If set to true only core definitions will be updated.
402      */
403     public static function update_definitions($coreonly = false) {
404         $factory = cache_factory::instance();
405         $factory->updating_started();
406         $config = $factory->create_config_instance(true);
407         $config->write_definitions_to_cache(self::locate_definitions($coreonly));
408         $factory->updating_finished();
409     }
411     /**
412      * Locates all of the definition files.
413      *
414      * @param bool $coreonly If set to true only core definitions will be updated.
415      * @return array
416      */
417     protected static function locate_definitions($coreonly = false) {
418         global $CFG;
420         $files = array();
421         if (file_exists($CFG->dirroot.'/lib/db/caches.php')) {
422             $files['core'] = $CFG->dirroot.'/lib/db/caches.php';
423         }
425         if (!$coreonly) {
426             $plugintypes = get_plugin_types();
427             foreach ($plugintypes as $type => $location) {
428                 $plugins = get_plugin_list_with_file($type, 'db/caches.php');
429                 foreach ($plugins as $plugin => $filepath) {
430                     $component = clean_param($type.'_'.$plugin, PARAM_COMPONENT); // Standardised plugin name.
431                     $files[$component] = $filepath;
432                 }
433             }
434         }
436         $definitions = array();
437         foreach ($files as $component => $file) {
438             $filedefs = self::load_caches_file($file);
439             foreach ($filedefs as $area => $definition) {
440                 $area = clean_param($area, PARAM_AREA);
441                 $id = $component.'/'.$area;
442                 $definition['component'] = $component;
443                 $definition['area'] = $area;
444                 if (array_key_exists($id, $definitions)) {
445                     debugging('Error: duplicate cache definition found with id: '.$id, DEBUG_DEVELOPER);
446                     continue;
447                 }
448                 $definitions[$id] = $definition;
449             }
450         }
452         return $definitions;
453     }
455     /**
456      * Writes the updated definitions for the config file.
457      * @param array $definitions
458      */
459     private function write_definitions_to_cache(array $definitions) {
460         $this->configdefinitions = $definitions;
461         foreach ($this->configdefinitionmappings as $key => $mapping) {
462             if (!array_key_exists($mapping['definition'], $definitions)) {
463                 unset($this->configdefinitionmappings[$key]);
464             }
465         }
466         $this->config_save();
467     }
469     /**
470      * Loads the caches file if it exists.
471      * @param string $file Absolute path to the file.
472      * @return array
473      */
474     private static function load_caches_file($file) {
475         if (!file_exists($file)) {
476             return array();
477         }
478         $definitions = array();
479         include($file);
480         return $definitions;
481     }
483     /**
484      * Sets the mappings for a given definition.
485      *
486      * @param string $definition
487      * @param array $mappings
488      * @throws coding_exception
489      */
490     public function set_definition_mappings($definition, $mappings) {
491         if (!array_key_exists($definition, $this->configdefinitions)) {
492             throw new coding_exception('Invalid definition name passed when updating mappings.');
493         }
494         foreach ($mappings as $store) {
495             if (!array_key_exists($store, $this->configstores)) {
496                 throw new coding_exception('Invalid store name passed when updating definition mappings.');
497             }
498         }
499         foreach ($this->configdefinitionmappings as $key => $mapping) {
500             if ($mapping['definition'] == $definition) {
501                 unset($this->configdefinitionmappings[$key]);
502             }
503         }
504         $sort = count($mappings);
505         foreach ($mappings as $store) {
506             $this->configdefinitionmappings[] = array(
507                 'store' => $store,
508                 'definition' => $definition,
509                 'sort' => $sort
510             );
511             $sort--;
512         }
514         $this->config_save();
515     }
519 /**
520  * A cache helper for administration tasks
521  *
522  * @package    core
523  * @category   cache
524  * @copyright  2012 Sam Hemelryk
525  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
526  */
527 abstract class cache_administration_helper extends cache_helper {
529     /**
530      * Returns an array containing all of the information about stores a renderer needs.
531      * @return array
532      */
533     public static function get_store_instance_summaries() {
534         $return = array();
535         $default = array();
536         $instance = cache_config::instance();
537         $stores = $instance->get_all_stores();
538         foreach ($stores as $name => $details) {
539             $class = $details['class'];
540             $store = new $class($details['name'], $details['configuration']);
541             $record = array(
542                 'name' => $name,
543                 'plugin' => $details['plugin'],
544                 'default' => $details['default'],
545                 'isready' => $store->is_ready(),
546                 'requirementsmet' => $store->are_requirements_met(),
547                 'mappings' => 0,
548                 'modes' => array(
549                     cache_store::MODE_APPLICATION =>
550                         ($store->get_supported_modes($return) & cache_store::MODE_APPLICATION) == cache_store::MODE_APPLICATION,
551                     cache_store::MODE_SESSION =>
552                         ($store->get_supported_modes($return) & cache_store::MODE_SESSION) == cache_store::MODE_SESSION,
553                     cache_store::MODE_REQUEST =>
554                         ($store->get_supported_modes($return) & cache_store::MODE_REQUEST) == cache_store::MODE_REQUEST,
555                 ),
556                 'supports' => array(
557                     'multipleidentifiers' => $store->supports_multiple_identifiers(),
558                     'dataguarantee' => $store->supports_data_guarantee(),
559                     'nativettl' => $store->supports_native_ttl(),
560                     'nativelocking' => ($store instanceof cache_is_lockable),
561                     'keyawareness' => ($store instanceof cache_is_key_aware),
562                 )
563             );
564             if (empty($details['default'])) {
565                 $return[$name] = $record;
566             } else {
567                 $default[$name] = $record;
568             }
569         }
571         ksort($return);
572         ksort($default);
573         $return = $return + $default;
575         foreach ($instance->get_definition_mappings() as $mapping) {
576             if (!array_key_exists($mapping['store'], $return)) {
577                 continue;
578             }
579             $return[$mapping['store']]['mappings']++;
580         }
582         return $return;
583     }
585     /**
586      * Returns an array of information about plugins, everything a renderer needs.
587      * @return array
588      */
589     public static function get_store_plugin_summaries() {
590         $return = array();
591         $plugins = get_plugin_list_with_file('cachestore', 'lib.php', true);
592         foreach ($plugins as $plugin => $path) {
593             $class = 'cachestore_'.$plugin;
594             $return[$plugin] = array(
595                 'name' => get_string('pluginname', 'cachestore_'.$plugin),
596                 'requirementsmet' => $class::are_requirements_met(),
597                 'instances' => 0,
598                 'modes' => array(
599                     cache_store::MODE_APPLICATION => ($class::get_supported_modes() & cache_store::MODE_APPLICATION),
600                     cache_store::MODE_SESSION => ($class::get_supported_modes() & cache_store::MODE_SESSION),
601                     cache_store::MODE_REQUEST => ($class::get_supported_modes() & cache_store::MODE_REQUEST),
602                 ),
603                 'supports' => array(
604                     'multipleidentifiers' => ($class::get_supported_features() & cache_store::SUPPORTS_MULTIPLE_IDENTIFIERS),
605                     'dataguarantee' => ($class::get_supported_features() & cache_store::SUPPORTS_DATA_GUARANTEE),
606                     'nativettl' => ($class::get_supported_features() & cache_store::SUPPORTS_NATIVE_TTL),
607                     'nativelocking' => (in_array('cache_is_lockable', class_implements($class))),
608                     'keyawareness' => (array_key_exists('cache_is_key_aware', class_implements($class))),
609                 ),
610                 'canaddinstance' => ($class::can_add_instance() && $class::are_requirements_met())
611             );
612         }
614         $instance = cache_config::instance();
615         $stores = $instance->get_all_stores();
616         foreach ($stores as $store) {
617             $plugin = $store['plugin'];
618             if (array_key_exists($plugin, $return)) {
619                 $return[$plugin]['instances']++;
620             }
621         }
623         return $return;
624     }
626     /**
627      * Returns an array about the definitions. All the information a renderer needs.
628      * @return array
629      */
630     public static function get_definition_summaries() {
631         $instance = cache_config::instance();
632         $definitions = $instance->get_definitions();
634         $storenames = array();
635         foreach ($instance->get_all_stores() as $key => $store) {
636             if (!empty($store['default'])) {
637                 $storenames[$key] = new lang_string('store_'.$key, 'cache');
638             }
639         }
641         $modemappings = array();
642         foreach ($instance->get_mode_mappings() as $mapping) {
643             $mode = $mapping['mode'];
644             if (!array_key_exists($mode, $modemappings)) {
645                 $modemappings[$mode] = array();
646             }
647             if (array_key_exists($mapping['store'], $storenames)) {
648                 $modemappings[$mode][] = $storenames[$mapping['store']];
649             } else {
650                 $modemappings[$mode][] = $mapping['store'];
651             }
652         }
654         $definitionmappings = array();
655         foreach ($instance->get_definition_mappings() as $mapping) {
656             $definition = $mapping['definition'];
657             if (!array_key_exists($definition, $definitionmappings)) {
658                 $definitionmappings[$definition] = array();
659             }
660             if (array_key_exists($mapping['store'], $storenames)) {
661                 $definitionmappings[$definition][] = $storenames[$mapping['store']];
662             } else {
663                 $definitionmappings[$definition][] = $mapping['store'];
664             }
665         }
667         $return = array();
669         foreach ($definitions as $id => $definition) {
671             $mappings = array();
672             if (array_key_exists($id, $definitionmappings)) {
673                 $mappings = $definitionmappings[$id];
674             } else if (empty($definition['mappingsonly'])) {
675                 $mappings = $modemappings[$definition['mode']];
676             }
678             $return[$id] = array(
679                 'id' => $id,
680                 'name' => cache_helper::get_definition_name($definition),
681                 'mode' => $definition['mode'],
682                 'component' => $definition['component'],
683                 'area' => $definition['area'],
684                 'mappings' => $mappings
685             );
686         }
687         return $return;
688     }
690     /**
691      * Returns all of the actions that can be performed on a definition.
692      * @param context $context
693      * @return array
694      */
695     public static function get_definition_actions(context $context) {
696         if (has_capability('moodle/site:config', $context)) {
697             return array(
698                 array(
699                     'text' => get_string('editmappings', 'cache'),
700                     'url' => new moodle_url('/cache/admin.php', array('action' => 'editdefinitionmapping', 'sesskey' => sesskey()))
701                 )
702             );
703         }
704         return array();
705     }
707     /**
708      * Returns all of the actions that can be performed on a store.
709      *
710      * @param string $name The name of the store
711      * @param array $storedetails
712      * @return array
713      */
714     public static function get_store_instance_actions($name, array $storedetails) {
715         $actions = array();
716         if (has_capability('moodle/site:config', get_system_context())) {
717             $baseurl = new moodle_url('/cache/admin.php', array('store' => $name, 'sesskey' => sesskey()));
718             if (empty($storedetails['default'])) {
719                 $actions[] = array(
720                     'text' => get_string('editstore', 'cache'),
721                     'url' => new moodle_url($baseurl, array('action' => 'editstore', 'plugin' => $storedetails['plugin']))
722                 );
723                 $actions[] = array(
724                     'text' => get_string('deletestore', 'cache'),
725                     'url' => new moodle_url($baseurl, array('action' => 'deletestore'))
726                 );
727             }
728             $actions[] = array(
729                 'text' => get_string('purge', 'cache'),
730                 'url' => new moodle_url($baseurl, array('action' => 'purge'))
731             );
732         }
733         return $actions;
734     }
737     /**
738      * Returns all of the actions that can be performed on a plugin.
739      *
740      * @param string $name The name of the plugin
741      * @param array $plugindetails
742      * @return array
743      */
744     public static function get_store_plugin_actions($name, array $plugindetails) {
745         $actions = array();
746         if (has_capability('moodle/site:config', context_system::instance())) {
747             if (!empty($plugindetails['canaddinstance'])) {
748                 $url = new moodle_url('/cache/admin.php', array('action' => 'addstore', 'plugin' => $name, 'sesskey' => sesskey()));
749                 $actions[] = array(
750                     'text' => get_string('addinstance', 'cache'),
751                     'url' => $url
752                 );
753             }
754         }
755         return $actions;
756     }
758     /**
759      * Returns a form that can be used to add a store instance.
760      *
761      * @param string $plugin The plugin to add an instance of
762      * @return cachestore_addinstance_form
763      * @throws coding_exception
764      */
765     public static function get_add_store_form($plugin) {
766         global $CFG; // Needed for includes.
767         $plugins = get_plugin_list('cachestore');
768         if (!array_key_exists($plugin, $plugins)) {
769             throw new coding_exception('Invalid cache plugin used when trying to create an edit form.');
770         }
771         $plugindir = $plugins[$plugin];
772         $class = 'cachestore_addinstance_form';
773         if (file_exists($plugindir.'/addinstanceform.php')) {
774             require_once($plugindir.'/addinstanceform.php');
775             if (class_exists('cachestore_'.$plugin.'_addinstance_form')) {
776                 $class = 'cachestore_'.$plugin.'_addinstance_form';
777                 if (!array_key_exists('cachestore_addinstance_form', class_parents($class))) {
778                     throw new coding_exception('Cache plugin add instance forms must extend cachestore_addinstance_form');
779                 }
780             }
781         }
783         $locks = self::get_possible_locks_for_stores($plugindir, $plugin);
785         $url = new moodle_url('/cache/admin.php', array('action' => 'addstore'));
786         return new $class($url, array('plugin' => $plugin, 'store' => null, 'locks' => $locks));
787     }
789     /**
790      * Returns a form that can be used to edit a store instance.
791      *
792      * @param string $plugin
793      * @param string $store
794      * @return cachestore_addinstance_form
795      * @throws coding_exception
796      */
797     public static function get_edit_store_form($plugin, $store) {
798         global $CFG; // Needed for includes.
799         $plugins = get_plugin_list('cachestore');
800         if (!array_key_exists($plugin, $plugins)) {
801             throw new coding_exception('Invalid cache plugin used when trying to create an edit form.');
802         }
803         $factory = cache_factory::instance();
804         $config = $factory->create_config_instance();
805         $stores = $config->get_all_stores();
806         if (!array_key_exists($store, $stores)) {
807             throw new coding_exception('Invalid store name given when trying to create an edit form.');
808         }
809         $plugindir = $plugins[$plugin];
810         $class = 'cachestore_addinstance_form';
811         if (file_exists($plugindir.'/addinstanceform.php')) {
812             require_once($plugindir.'/addinstanceform.php');
813             if (class_exists('cachestore_'.$plugin.'_addinstance_form')) {
814                 $class = 'cachestore_'.$plugin.'_addinstance_form';
815                 if (!array_key_exists('cachestore_addinstance_form', class_parents($class))) {
816                     throw new coding_exception('Cache plugin add instance forms must extend cachestore_addinstance_form');
817                 }
818             }
819         }
821         $locks = self::get_possible_locks_for_stores($plugindir, $plugin);
823         $url = new moodle_url('/cache/admin.php', array('action' => 'editstore', 'plugin' => $plugin, 'store' => $store));
824         $editform = new $class($url, array('plugin' => $plugin, 'store' => $store, 'locks' => $locks));
825         // See if the cachestore is going to want to load data for the form.
826         // If it has a customised add instance form then it is going to want to.
827         $storeclass = 'cachestore_'.$plugin;
828         $storedata = $stores[$store];
829         if (array_key_exists('configuration', $storedata) && array_key_exists('cache_is_configurable', class_implements($storeclass))) {
830             $storeclass::config_set_edit_form_data($editform, $storedata['configuration']);
831         }
832         return $editform;
833     }
835     /**
836      * Returns an array of suitable lock instances for use with this plugin, or false if the plugin handles locking itself.
837      *
838      * @param string $plugindir
839      * @param string $plugin
840      * @return array|false
841      */
842     protected static function get_possible_locks_for_stores($plugindir, $plugin) {
843         global $CFG; // Needed for includes.
844         $supportsnativelocking = false;
845         if (file_exists($plugindir.'/lib.php')) {
846             require_once($plugindir.'/lib.php');
847             $pluginclass = 'cachestore_'.$plugin;
848             if (class_exists($pluginclass)) {
849                 $supportsnativelocking = array_key_exists('cache_is_lockable', class_implements($pluginclass));
850             }
851         }
853         if (!$supportsnativelocking) {
854             $config = cache_config::instance();
855             $locks = array();
856             foreach ($config->get_locks() as $lock => $conf) {
857                 if (!empty($conf['default'])) {
858                     $name = get_string($lock, 'cache');
859                 } else {
860                     $name = $lock;
861                 }
862                 $locks[$lock] = $name;
863             }
864         } else {
865             $locks = false;
866         }
868         return $locks;
869     }
871     /**
872      * Processes the results of the add/edit instance form data for a plugin returning an array of config information suitable to
873      * store in configuration.
874      *
875      * @param stdClass $data The mform data.
876      * @return array
877      * @throws coding_exception
878      */
879     public static function get_store_configuration_from_data(stdClass $data) {
880         global $CFG;
881         $file = $CFG->dirroot.'/cache/stores/'.$data->plugin.'/lib.php';
882         if (!file_exists($file)) {
883             throw new coding_exception('Invalid cache plugin provided. '.$file);
884         }
885         require_once($file);
886         $class = 'cachestore_'.$data->plugin;
887         if (!class_exists($class)) {
888             throw new coding_exception('Invalid cache plugin provided.');
889         }
890         if (array_key_exists('cache_is_configurable', class_implements($class))) {
891             return $class::config_get_configuration_array($data);
892         }
893         return array();
894     }
896     /**
897      * Get an array of stores that are suitable to be used for a given definition.
898      *
899      * @param string $component
900      * @param string $area
901      * @return array Array containing 3 elements
902      *      1. An array of currently used stores
903      *      2. An array of suitable stores
904      *      3. An array of default stores
905      */
906     public static function get_definition_store_options($component, $area) {
907         $factory = cache_factory::instance();
908         $definition = $factory->create_definition($component, $area);
909         $config = cache_config::instance();
910         $currentstores = $config->get_stores_for_definition($definition);
911         $possiblestores = $config->get_stores($definition->get_mode(), $definition->get_requirements_bin());
913         $defaults = array();
914         foreach ($currentstores as $key => $store) {
915             if (!empty($store['default'])) {
916                 $defaults[] = $key;
917                 unset($currentstores[$key]);
918             }
919         }
920         foreach ($possiblestores as $key => $store) {
921             if ($store['default']) {
922                 unset($possiblestores[$key]);
923                 $possiblestores[$key] = $store;
924             }
925         }
926         return array($currentstores, $possiblestores, $defaults);
927     }
929     /**
930      * Get the default stores for all modes.
931      *
932      * @return array An array containing sub-arrays, one for each mode.
933      */
934     public static function get_default_mode_stores() {
935         $instance = cache_config::instance();
936         $storenames = array();
937         foreach ($instance->get_all_stores() as $key => $store) {
938             if (!empty($store['default'])) {
939                 $storenames[$key] = new lang_string('store_'.$key, 'cache');
940             }
941         }
942         $modemappings = array(
943             cache_store::MODE_APPLICATION => array(),
944             cache_store::MODE_SESSION => array(),
945             cache_store::MODE_REQUEST => array(),
946         );
947         foreach ($instance->get_mode_mappings() as $mapping) {
948             $mode = $mapping['mode'];
949             if (!array_key_exists($mode, $modemappings)) {
950                 debugging('Unknown mode in cache store mode mappings', DEBUG_DEVELOPER);
951                 continue;
952             }
953             if (array_key_exists($mapping['store'], $storenames)) {
954                 $modemappings[$mode][$mapping['store']] = $storenames[$mapping['store']];
955             } else {
956                 $modemappings[$mode][$mapping['store']] = $mapping['store'];
957             }
958         }
959         return $modemappings;
960     }
962     /**
963      * Returns an array summarising the locks available in the system
964      */
965     public static function get_lock_summaries() {
966         $locks = array();
967         $instance = cache_config::instance();
968         $stores = $instance->get_all_stores();
969         foreach ($instance->get_locks() as $lock) {
970             $default = !empty($lock['default']);
971             if ($default) {
972                 $name = new lang_string($lock['name'], 'cache');
973             } else {
974                 $name = $lock['name'];
975             }
976             $uses = 0;
977             foreach ($stores as $store) {
978                 if (!empty($store['lock']) && $store['lock'] === $lock['name']) {
979                     $uses++;
980                 }
981             }
982             $lockdata = array(
983                 'name' => $name,
984                 'default' => $default,
985                 'uses' => $uses
986             );
987             $locks[] = $lockdata;
988         }
989         return $locks;
990     }