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