MDL-37684 - phpunit add missing definitions to phpunit.xml.dist
[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         $config = self::instance();
405         $config->write_definitions_to_cache(self::locate_definitions($coreonly));
406     }
408     /**
409      * Locates all of the definition files.
410      *
411      * @param bool $coreonly If set to true only core definitions will be updated.
412      * @return array
413      */
414     protected static function locate_definitions($coreonly = false) {
415         global $CFG;
417         $files = array();
418         if (file_exists($CFG->dirroot.'/lib/db/caches.php')) {
419             $files['core'] = $CFG->dirroot.'/lib/db/caches.php';
420         }
422         if (!$coreonly) {
423             $plugintypes = get_plugin_types();
424             foreach ($plugintypes as $type => $location) {
425                 $plugins = get_plugin_list_with_file($type, 'db/caches.php');
426                 foreach ($plugins as $plugin => $filepath) {
427                     $component = clean_param($type.'_'.$plugin, PARAM_COMPONENT); // Standardised plugin name.
428                     $files[$component] = $filepath;
429                 }
430             }
431         }
433         $definitions = array();
434         foreach ($files as $component => $file) {
435             $filedefs = self::load_caches_file($file);
436             foreach ($filedefs as $area => $definition) {
437                 $area = clean_param($area, PARAM_AREA);
438                 $id = $component.'/'.$area;
439                 $definition['component'] = $component;
440                 $definition['area'] = $area;
441                 if (array_key_exists($id, $definitions)) {
442                     debugging('Error: duplicate cache definition found with id: '.$id, DEBUG_DEVELOPER);
443                     continue;
444                 }
445                 $definitions[$id] = $definition;
446             }
447         }
449         return $definitions;
450     }
452     /**
453      * Writes the updated definitions for the config file.
454      * @param array $definitions
455      */
456     private function write_definitions_to_cache(array $definitions) {
457         $this->configdefinitions = $definitions;
458         foreach ($this->configdefinitionmappings as $key => $mapping) {
459             if (!array_key_exists($mapping['definition'], $definitions)) {
460                 unset($this->configdefinitionmappings[$key]);
461             }
462         }
463         $this->config_save();
464     }
466     /**
467      * Loads the caches file if it exists.
468      * @param string $file Absolute path to the file.
469      * @return array
470      */
471     private static function load_caches_file($file) {
472         if (!file_exists($file)) {
473             return array();
474         }
475         $definitions = array();
476         include($file);
477         return $definitions;
478     }
480     /**
481      * Sets the mappings for a given definition.
482      *
483      * @param string $definition
484      * @param array $mappings
485      * @throws coding_exception
486      */
487     public function set_definition_mappings($definition, $mappings) {
488         if (!array_key_exists($definition, $this->configdefinitions)) {
489             throw new coding_exception('Invalid definition name passed when updating mappings.');
490         }
491         foreach ($mappings as $store) {
492             if (!array_key_exists($store, $this->configstores)) {
493                 throw new coding_exception('Invalid store name passed when updating definition mappings.');
494             }
495         }
496         foreach ($this->configdefinitionmappings as $key => $mapping) {
497             if ($mapping['definition'] == $definition) {
498                 unset($this->configdefinitionmappings[$key]);
499             }
500         }
501         $sort = count($mappings);
502         foreach ($mappings as $store) {
503             $this->configdefinitionmappings[] = array(
504                 'store' => $store,
505                 'definition' => $definition,
506                 'sort' => $sort
507             );
508             $sort--;
509         }
511         $this->config_save();
512     }
516 /**
517  * A cache helper for administration tasks
518  *
519  * @package    core
520  * @category   cache
521  * @copyright  2012 Sam Hemelryk
522  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
523  */
524 abstract class cache_administration_helper extends cache_helper {
526     /**
527      * Returns an array containing all of the information about stores a renderer needs.
528      * @return array
529      */
530     public static function get_store_instance_summaries() {
531         $return = array();
532         $default = array();
533         $instance = cache_config::instance();
534         $stores = $instance->get_all_stores();
535         foreach ($stores as $name => $details) {
536             $class = $details['class'];
537             $store = new $class($details['name'], $details['configuration']);
538             $record = array(
539                 'name' => $name,
540                 'plugin' => $details['plugin'],
541                 'default' => $details['default'],
542                 'isready' => $store->is_ready(),
543                 'requirementsmet' => $store->are_requirements_met(),
544                 'mappings' => 0,
545                 'modes' => array(
546                     cache_store::MODE_APPLICATION =>
547                         ($store->get_supported_modes($return) & cache_store::MODE_APPLICATION) == cache_store::MODE_APPLICATION,
548                     cache_store::MODE_SESSION =>
549                         ($store->get_supported_modes($return) & cache_store::MODE_SESSION) == cache_store::MODE_SESSION,
550                     cache_store::MODE_REQUEST =>
551                         ($store->get_supported_modes($return) & cache_store::MODE_REQUEST) == cache_store::MODE_REQUEST,
552                 ),
553                 'supports' => array(
554                     'multipleidentifiers' => $store->supports_multiple_identifiers(),
555                     'dataguarantee' => $store->supports_data_guarantee(),
556                     'nativettl' => $store->supports_native_ttl(),
557                     'nativelocking' => ($store instanceof cache_is_lockable),
558                     'keyawareness' => ($store instanceof cache_is_key_aware),
559                 )
560             );
561             if (empty($details['default'])) {
562                 $return[$name] = $record;
563             } else {
564                 $default[$name] = $record;
565             }
566         }
568         ksort($return);
569         ksort($default);
570         $return = $return + $default;
572         foreach ($instance->get_definition_mappings() as $mapping) {
573             if (!array_key_exists($mapping['store'], $return)) {
574                 continue;
575             }
576             $return[$mapping['store']]['mappings']++;
577         }
579         return $return;
580     }
582     /**
583      * Returns an array of information about plugins, everything a renderer needs.
584      * @return array
585      */
586     public static function get_store_plugin_summaries() {
587         $return = array();
588         $plugins = get_plugin_list_with_file('cachestore', 'lib.php', true);
589         foreach ($plugins as $plugin => $path) {
590             $class = 'cachestore_'.$plugin;
591             $return[$plugin] = array(
592                 'name' => get_string('pluginname', 'cachestore_'.$plugin),
593                 'requirementsmet' => $class::are_requirements_met(),
594                 'instances' => 0,
595                 'modes' => array(
596                     cache_store::MODE_APPLICATION => ($class::get_supported_modes() & cache_store::MODE_APPLICATION),
597                     cache_store::MODE_SESSION => ($class::get_supported_modes() & cache_store::MODE_SESSION),
598                     cache_store::MODE_REQUEST => ($class::get_supported_modes() & cache_store::MODE_REQUEST),
599                 ),
600                 'supports' => array(
601                     'multipleidentifiers' => ($class::get_supported_features() & cache_store::SUPPORTS_MULTIPLE_IDENTIFIERS),
602                     'dataguarantee' => ($class::get_supported_features() & cache_store::SUPPORTS_DATA_GUARANTEE),
603                     'nativettl' => ($class::get_supported_features() & cache_store::SUPPORTS_NATIVE_TTL),
604                     'nativelocking' => (in_array('cache_is_lockable', class_implements($class))),
605                     'keyawareness' => (array_key_exists('cache_is_key_aware', class_implements($class))),
606                 ),
607                 'canaddinstance' => ($class::can_add_instance() && $class::are_requirements_met())
608             );
609         }
611         $instance = cache_config::instance();
612         $stores = $instance->get_all_stores();
613         foreach ($stores as $store) {
614             $plugin = $store['plugin'];
615             if (array_key_exists($plugin, $return)) {
616                 $return[$plugin]['instances']++;
617             }
618         }
620         return $return;
621     }
623     /**
624      * Returns an array about the definitions. All the information a renderer needs.
625      * @return array
626      */
627     public static function get_definition_summaries() {
628         $instance = cache_config::instance();
629         $definitions = $instance->get_definitions();
631         $storenames = array();
632         foreach ($instance->get_all_stores() as $key => $store) {
633             if (!empty($store['default'])) {
634                 $storenames[$key] = new lang_string('store_'.$key, 'cache');
635             }
636         }
638         $modemappings = array();
639         foreach ($instance->get_mode_mappings() as $mapping) {
640             $mode = $mapping['mode'];
641             if (!array_key_exists($mode, $modemappings)) {
642                 $modemappings[$mode] = array();
643             }
644             if (array_key_exists($mapping['store'], $storenames)) {
645                 $modemappings[$mode][] = $storenames[$mapping['store']];
646             } else {
647                 $modemappings[$mode][] = $mapping['store'];
648             }
649         }
651         $definitionmappings = array();
652         foreach ($instance->get_definition_mappings() as $mapping) {
653             $definition = $mapping['definition'];
654             if (!array_key_exists($definition, $definitionmappings)) {
655                 $definitionmappings[$definition] = array();
656             }
657             if (array_key_exists($mapping['store'], $storenames)) {
658                 $definitionmappings[$definition][] = $storenames[$mapping['store']];
659             } else {
660                 $definitionmappings[$definition][] = $mapping['store'];
661             }
662         }
664         $return = array();
666         foreach ($definitions as $id => $definition) {
668             $mappings = array();
669             if (array_key_exists($id, $definitionmappings)) {
670                 $mappings = $definitionmappings[$id];
671             } else if (empty($definition['mappingsonly'])) {
672                 $mappings = $modemappings[$definition['mode']];
673             }
675             $return[$id] = array(
676                 'id' => $id,
677                 'name' => cache_helper::get_definition_name($definition),
678                 'mode' => $definition['mode'],
679                 'component' => $definition['component'],
680                 'area' => $definition['area'],
681                 'mappings' => $mappings
682             );
683         }
684         return $return;
685     }
687     /**
688      * Returns all of the actions that can be performed on a definition.
689      * @param context $context
690      * @return array
691      */
692     public static function get_definition_actions(context $context) {
693         if (has_capability('moodle/site:config', $context)) {
694             return array(
695                 array(
696                     'text' => get_string('editmappings', 'cache'),
697                     'url' => new moodle_url('/cache/admin.php', array('action' => 'editdefinitionmapping', 'sesskey' => sesskey()))
698                 )
699             );
700         }
701         return array();
702     }
704     /**
705      * Returns all of the actions that can be performed on a store.
706      *
707      * @param string $name The name of the store
708      * @param array $storedetails
709      * @return array
710      */
711     public static function get_store_instance_actions($name, array $storedetails) {
712         $actions = array();
713         if (has_capability('moodle/site:config', get_system_context())) {
714             $baseurl = new moodle_url('/cache/admin.php', array('store' => $name, 'sesskey' => sesskey()));
715             if (empty($storedetails['default'])) {
716                 $actions[] = array(
717                     'text' => get_string('editstore', 'cache'),
718                     'url' => new moodle_url($baseurl, array('action' => 'editstore', 'plugin' => $storedetails['plugin']))
719                 );
720                 $actions[] = array(
721                     'text' => get_string('deletestore', 'cache'),
722                     'url' => new moodle_url($baseurl, array('action' => 'deletestore'))
723                 );
724             }
725             $actions[] = array(
726                 'text' => get_string('purge', 'cache'),
727                 'url' => new moodle_url($baseurl, array('action' => 'purge'))
728             );
729         }
730         return $actions;
731     }
734     /**
735      * Returns all of the actions that can be performed on a plugin.
736      *
737      * @param string $name The name of the plugin
738      * @param array $plugindetails
739      * @return array
740      */
741     public static function get_store_plugin_actions($name, array $plugindetails) {
742         $actions = array();
743         if (has_capability('moodle/site:config', context_system::instance())) {
744             if (!empty($plugindetails['canaddinstance'])) {
745                 $url = new moodle_url('/cache/admin.php', array('action' => 'addstore', 'plugin' => $name, 'sesskey' => sesskey()));
746                 $actions[] = array(
747                     'text' => get_string('addinstance', 'cache'),
748                     'url' => $url
749                 );
750             }
751         }
752         return $actions;
753     }
755     /**
756      * Returns a form that can be used to add a store instance.
757      *
758      * @param string $plugin The plugin to add an instance of
759      * @return cachestore_addinstance_form
760      * @throws coding_exception
761      */
762     public static function get_add_store_form($plugin) {
763         global $CFG; // Needed for includes.
764         $plugins = get_plugin_list('cachestore');
765         if (!array_key_exists($plugin, $plugins)) {
766             throw new coding_exception('Invalid cache plugin used when trying to create an edit form.');
767         }
768         $plugindir = $plugins[$plugin];
769         $class = 'cachestore_addinstance_form';
770         if (file_exists($plugindir.'/addinstanceform.php')) {
771             require_once($plugindir.'/addinstanceform.php');
772             if (class_exists('cachestore_'.$plugin.'_addinstance_form')) {
773                 $class = 'cachestore_'.$plugin.'_addinstance_form';
774                 if (!array_key_exists('cachestore_addinstance_form', class_parents($class))) {
775                     throw new coding_exception('Cache plugin add instance forms must extend cachestore_addinstance_form');
776                 }
777             }
778         }
780         $locks = self::get_possible_locks_for_stores($plugindir, $plugin);
782         $url = new moodle_url('/cache/admin.php', array('action' => 'addstore'));
783         return new $class($url, array('plugin' => $plugin, 'store' => null, 'locks' => $locks));
784     }
786     /**
787      * Returns a form that can be used to edit a store instance.
788      *
789      * @param string $plugin
790      * @param string $store
791      * @return cachestore_addinstance_form
792      * @throws coding_exception
793      */
794     public static function get_edit_store_form($plugin, $store) {
795         global $CFG; // Needed for includes.
796         $plugins = get_plugin_list('cachestore');
797         if (!array_key_exists($plugin, $plugins)) {
798             throw new coding_exception('Invalid cache plugin used when trying to create an edit form.');
799         }
800         $factory = cache_factory::instance();
801         $config = $factory->create_config_instance();
802         $stores = $config->get_all_stores();
803         if (!array_key_exists($store, $stores)) {
804             throw new coding_exception('Invalid store name given when trying to create an edit form.');
805         }
806         $plugindir = $plugins[$plugin];
807         $class = 'cachestore_addinstance_form';
808         if (file_exists($plugindir.'/addinstanceform.php')) {
809             require_once($plugindir.'/addinstanceform.php');
810             if (class_exists('cachestore_'.$plugin.'_addinstance_form')) {
811                 $class = 'cachestore_'.$plugin.'_addinstance_form';
812                 if (!array_key_exists('cachestore_addinstance_form', class_parents($class))) {
813                     throw new coding_exception('Cache plugin add instance forms must extend cachestore_addinstance_form');
814                 }
815             }
816         }
818         $locks = self::get_possible_locks_for_stores($plugindir, $plugin);
820         $url = new moodle_url('/cache/admin.php', array('action' => 'editstore', 'plugin' => $plugin, 'store' => $store));
821         $editform = new $class($url, array('plugin' => $plugin, 'store' => $store, 'locks' => $locks));
822         // See if the cachestore is going to want to load data for the form.
823         // If it has a customised add instance form then it is going to want to.
824         $storeclass = 'cachestore_'.$plugin;
825         $storedata = $stores[$store];
826         if (array_key_exists('configuration', $storedata) && array_key_exists('cache_is_configurable', class_implements($storeclass))) {
827             $storeclass::config_set_edit_form_data($editform, $storedata['configuration']);
828         }
829         return $editform;
830     }
832     /**
833      * Returns an array of suitable lock instances for use with this plugin, or false if the plugin handles locking itself.
834      *
835      * @param string $plugindir
836      * @param string $plugin
837      * @return array|false
838      */
839     protected static function get_possible_locks_for_stores($plugindir, $plugin) {
840         global $CFG; // Needed for includes.
841         $supportsnativelocking = false;
842         if (file_exists($plugindir.'/lib.php')) {
843             require_once($plugindir.'/lib.php');
844             $pluginclass = 'cachestore_'.$plugin;
845             if (class_exists($pluginclass)) {
846                 $supportsnativelocking = array_key_exists('cache_is_lockable', class_implements($pluginclass));
847             }
848         }
850         if (!$supportsnativelocking) {
851             $config = cache_config::instance();
852             $locks = array();
853             foreach ($config->get_locks() as $lock => $conf) {
854                 if (!empty($conf['default'])) {
855                     $name = get_string($lock, 'cache');
856                 } else {
857                     $name = $lock;
858                 }
859                 $locks[$lock] = $name;
860             }
861         } else {
862             $locks = false;
863         }
865         return $locks;
866     }
868     /**
869      * Processes the results of the add/edit instance form data for a plugin returning an array of config information suitable to
870      * store in configuration.
871      *
872      * @param stdClass $data The mform data.
873      * @return array
874      * @throws coding_exception
875      */
876     public static function get_store_configuration_from_data(stdClass $data) {
877         global $CFG;
878         $file = $CFG->dirroot.'/cache/stores/'.$data->plugin.'/lib.php';
879         if (!file_exists($file)) {
880             throw new coding_exception('Invalid cache plugin provided. '.$file);
881         }
882         require_once($file);
883         $class = 'cachestore_'.$data->plugin;
884         if (!class_exists($class)) {
885             throw new coding_exception('Invalid cache plugin provided.');
886         }
887         if (array_key_exists('cache_is_configurable', class_implements($class))) {
888             return $class::config_get_configuration_array($data);
889         }
890         return array();
891     }
893     /**
894      * Get an array of stores that are suitable to be used for a given definition.
895      *
896      * @param string $component
897      * @param string $area
898      * @return array Array containing 3 elements
899      *      1. An array of currently used stores
900      *      2. An array of suitable stores
901      *      3. An array of default stores
902      */
903     public static function get_definition_store_options($component, $area) {
904         $factory = cache_factory::instance();
905         $definition = $factory->create_definition($component, $area);
906         $config = cache_config::instance();
907         $currentstores = $config->get_stores_for_definition($definition);
908         $possiblestores = $config->get_stores($definition->get_mode(), $definition->get_requirements_bin());
910         $defaults = array();
911         foreach ($currentstores as $key => $store) {
912             if (!empty($store['default'])) {
913                 $defaults[] = $key;
914                 unset($currentstores[$key]);
915             }
916         }
917         foreach ($possiblestores as $key => $store) {
918             if ($store['default']) {
919                 unset($possiblestores[$key]);
920                 $possiblestores[$key] = $store;
921             }
922         }
923         return array($currentstores, $possiblestores, $defaults);
924     }
926     /**
927      * Get the default stores for all modes.
928      *
929      * @return array An array containing sub-arrays, one for each mode.
930      */
931     public static function get_default_mode_stores() {
932         $instance = cache_config::instance();
933         $storenames = array();
934         foreach ($instance->get_all_stores() as $key => $store) {
935             if (!empty($store['default'])) {
936                 $storenames[$key] = new lang_string('store_'.$key, 'cache');
937             }
938         }
939         $modemappings = array(
940             cache_store::MODE_APPLICATION => array(),
941             cache_store::MODE_SESSION => array(),
942             cache_store::MODE_REQUEST => array(),
943         );
944         foreach ($instance->get_mode_mappings() as $mapping) {
945             $mode = $mapping['mode'];
946             if (!array_key_exists($mode, $modemappings)) {
947                 debugging('Unknown mode in cache store mode mappings', DEBUG_DEVELOPER);
948                 continue;
949             }
950             if (array_key_exists($mapping['store'], $storenames)) {
951                 $modemappings[$mode][$mapping['store']] = $storenames[$mapping['store']];
952             } else {
953                 $modemappings[$mode][$mapping['store']] = $mapping['store'];
954             }
955         }
956         return $modemappings;
957     }
959     /**
960      * Returns an array summarising the locks available in the system
961      */
962     public static function get_lock_summaries() {
963         $locks = array();
964         $instance = cache_config::instance();
965         $stores = $instance->get_all_stores();
966         foreach ($instance->get_locks() as $lock) {
967             $default = !empty($lock['default']);
968             if ($default) {
969                 $name = new lang_string($lock['name'], 'cache');
970             } else {
971                 $name = $lock['name'];
972             }
973             $uses = 0;
974             foreach ($stores as $store) {
975                 if (!empty($store['lock']) && $store['lock'] === $lock['name']) {
976                     $uses++;
977                 }
978             }
979             $lockdata = array(
980                 'name' => $name,
981                 'default' => $default,
982                 'uses' => $uses
983             );
984             $locks[] = $lockdata;
985         }
986         return $locks;
987     }