Merge branch 'wip-MDL-36363-m25' of https://github.com/samhemelryk/moodle
[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         // Call instance_created()
182         $store = new $class($name, $this->configstores[$name]['configuration']);
183         $store->instance_created();
185         $this->config_save();
186         return true;
187     }
189     /**
190      * Sets the mode mappings.
191      *
192      * These determine the default caches for the different modes.
193      * This function also calls save so you should redirect immediately, or at least very shortly after
194      * calling this method.
195      *
196      * @param array $modemappings
197      * @return bool
198      * @throws cache_exception
199      */
200     public function set_mode_mappings(array $modemappings) {
201         $mappings = array(
202             cache_store::MODE_APPLICATION => array(),
203             cache_store::MODE_SESSION => array(),
204             cache_store::MODE_REQUEST => array(),
205         );
206         foreach ($modemappings as $mode => $stores) {
207             if (!array_key_exists($mode, $mappings)) {
208                 throw new cache_exception('The cache mode for the new mapping does not exist');
209             }
210             $sort = 0;
211             foreach ($stores as $store) {
212                 if (!array_key_exists($store, $this->configstores)) {
213                     throw new cache_exception('The instance name for the new mapping does not exist');
214                 }
215                 if (array_key_exists($store, $mappings[$mode])) {
216                     throw new cache_exception('This cache mapping already exists');
217                 }
218                 $mappings[$mode][] = array(
219                     'store' => $store,
220                     'mode' => $mode,
221                     'sort' => $sort++
222                 );
223             }
224         }
225         $this->configmodemappings = array_merge(
226             $mappings[cache_store::MODE_APPLICATION],
227             $mappings[cache_store::MODE_SESSION],
228             $mappings[cache_store::MODE_REQUEST]
229         );
231         $this->config_save();
232         return true;
233     }
235     /**
236      * Edits a give plugin instance.
237      *
238      * The plugin instance is determined by its name, hence you cannot rename plugins.
239      * This function also calls save so you should redirect immediately, or at least very shortly after
240      * calling this method.
241      *
242      * @param string $name
243      * @param string $plugin
244      * @param array $configuration
245      * @return bool
246      * @throws cache_exception
247      */
248     public function edit_store_instance($name, $plugin, $configuration) {
249         if (!array_key_exists($name, $this->configstores)) {
250             throw new cache_exception('The requested instance does not exist.');
251         }
252         $plugins = get_plugin_list_with_file('cachestore', 'lib.php');
253         if (!array_key_exists($plugin, $plugins)) {
254             throw new cache_exception('Invalid plugin name specified. The plugin either does not exist or is not valid.');
255         }
256         $class = 'cachestore_'.$plugin;
257         $file = $plugins[$plugin];
258         if (!class_exists($class)) {
259             if (file_exists($file)) {
260                 require_once($file);
261             }
262             if (!class_exists($class)) {
263                 throw new cache_exception('Invalid cache plugin specified. The plugin does not contain the required class.'.$class);
264             }
265         }
266         $this->configstores[$name] = array(
267             'name' => $name,
268             'plugin' => $plugin,
269             'configuration' => $configuration,
270             'features' => $class::get_supported_features($configuration),
271             'modes' => $class::get_supported_modes($configuration),
272             'mappingsonly' => !empty($configuration['mappingsonly']),
273             'class' => $class,
274             'default' => $this->configstores[$name]['default'] // Can't change the default.
275         );
276         if (array_key_exists('lock', $configuration)) {
277             $this->configstores[$name]['lock'] = $configuration['lock'];
278             unset($this->configstores[$name]['configuration']['lock']);
279         }
280         $this->config_save();
281         return true;
282     }
284     /**
285      * Deletes a store instance.
286      *
287      * This function also calls save so you should redirect immediately, or at least very shortly after
288      * calling this method.
289      *
290      * @param string $name The name of the instance to delete.
291      * @return bool
292      * @throws cache_exception
293      */
294     public function delete_store_instance($name) {
295         if (!array_key_exists($name, $this->configstores)) {
296             throw new cache_exception('The requested store does not exist.');
297         }
298         if ($this->configstores[$name]['default']) {
299             throw new cache_exception('The can not delete the default stores.');
300         }
301         foreach ($this->configmodemappings as $mapping) {
302             if ($mapping['store'] === $name) {
303                 throw new cache_exception('You cannot delete a cache store that has mode mappings.');
304             }
305         }
306         foreach ($this->configdefinitionmappings as $mapping) {
307             if ($mapping['store'] === $name) {
308                 throw new cache_exception('You cannot delete a cache store that has definition mappings.');
309             }
310         }
312         // Call instance_deleted()
313         $class = 'cachestore_'.$this->configstores[$name]['plugin'];
314         $store = new $class($name, $this->configstores[$name]['configuration']);
315         $store->instance_deleted();
317         unset($this->configstores[$name]);
318         $this->config_save();
319         return true;
320     }
322     /**
323      * Creates the default configuration and saves it.
324      *
325      * This function calls config_save, however it is safe to continue using it afterwards as this function should only ever
326      * be called when there is no configuration file already.
327      *
328      * @return true|array Returns true if the default configuration was successfully created.
329      *     Returns a configuration array if it could not be saved. This is a bad situation. Check your error logs.
330      */
331     public static function create_default_configuration() {
332         global $CFG;
334         // HACK ALERT.
335         // We probably need to come up with a better way to create the default stores, or at least ensure 100% that the
336         // default store plugins are protected from deletion.
337         require_once($CFG->dirroot.'/cache/stores/file/lib.php');
338         require_once($CFG->dirroot.'/cache/stores/session/lib.php');
339         require_once($CFG->dirroot.'/cache/stores/static/lib.php');
341         $writer = new self;
342         $writer->configstores = array(
343             'default_application' => array(
344                 'name' => 'default_application',
345                 'plugin' => 'file',
346                 'configuration' => array(),
347                 'features' => cachestore_file::get_supported_features(),
348                 'modes' => cache_store::MODE_APPLICATION,
349                 'default' => true,
350             ),
351             'default_session' => array(
352                 'name' => 'default_session',
353                 'plugin' => 'session',
354                 'configuration' => array(),
355                 'features' => cachestore_session::get_supported_features(),
356                 'modes' => cache_store::MODE_SESSION,
357                 'default' => true,
358             ),
359             'default_request' => array(
360                 'name' => 'default_request',
361                 'plugin' => 'static',
362                 'configuration' => array(),
363                 'features' => cachestore_static::get_supported_features(),
364                 'modes' => cache_store::MODE_REQUEST,
365                 'default' => true,
366             )
367         );
368         $writer->configdefinitions = self::locate_definitions();
369         $writer->configmodemappings = array(
370             array(
371                 'mode' => cache_store::MODE_APPLICATION,
372                 'store' => 'default_application',
373                 'sort' => -1
374             ),
375             array(
376                 'mode' => cache_store::MODE_SESSION,
377                 'store' => 'default_session',
378                 'sort' => -1
379             ),
380             array(
381                 'mode' => cache_store::MODE_REQUEST,
382                 'store' => 'default_request',
383                 'sort' => -1
384             )
385         );
386         $writer->configlocks = array(
387             'default_file_lock' => array(
388                 'name' => 'cachelock_file_default',
389                 'type' => 'cachelock_file',
390                 'dir' => 'filelocks',
391                 'default' => true
392             )
393         );
395         $factory = cache_factory::instance();
396         // We expect the cache to be initialising presently. If its not then something has gone wrong and likely
397         // we are now in a loop.
398         if ($factory->get_state() !== cache_factory::STATE_INITIALISING) {
399             return $writer->generate_configuration_array();
400         }
401         $factory->set_state(cache_factory::STATE_SAVING);
402         $writer->config_save();
403         return true;
404     }
406     /**
407      * Updates the definition in the configuration from those found in the cache files.
408      *
409      * Calls config_save further down, you should redirect immediately or asap after calling this method.
410      *
411      * @param bool $coreonly If set to true only core definitions will be updated.
412      */
413     public static function update_definitions($coreonly = false) {
414         $factory = cache_factory::instance();
415         $factory->updating_started();
416         $config = $factory->create_config_instance(true);
417         $config->write_definitions_to_cache(self::locate_definitions($coreonly));
418         $factory->updating_finished();
419     }
421     /**
422      * Locates all of the definition files.
423      *
424      * @param bool $coreonly If set to true only core definitions will be updated.
425      * @return array
426      */
427     protected static function locate_definitions($coreonly = false) {
428         global $CFG;
430         $files = array();
431         if (file_exists($CFG->dirroot.'/lib/db/caches.php')) {
432             $files['core'] = $CFG->dirroot.'/lib/db/caches.php';
433         }
435         if (!$coreonly) {
436             $plugintypes = get_plugin_types();
437             foreach ($plugintypes as $type => $location) {
438                 $plugins = get_plugin_list_with_file($type, 'db/caches.php');
439                 foreach ($plugins as $plugin => $filepath) {
440                     $component = clean_param($type.'_'.$plugin, PARAM_COMPONENT); // Standardised plugin name.
441                     $files[$component] = $filepath;
442                 }
443             }
444         }
446         $definitions = array();
447         foreach ($files as $component => $file) {
448             $filedefs = self::load_caches_file($file);
449             foreach ($filedefs as $area => $definition) {
450                 $area = clean_param($area, PARAM_AREA);
451                 $id = $component.'/'.$area;
452                 $definition['component'] = $component;
453                 $definition['area'] = $area;
454                 if (array_key_exists($id, $definitions)) {
455                     debugging('Error: duplicate cache definition found with id: '.$id, DEBUG_DEVELOPER);
456                     continue;
457                 }
458                 $definitions[$id] = $definition;
459             }
460         }
462         return $definitions;
463     }
465     /**
466      * Writes the updated definitions for the config file.
467      * @param array $definitions
468      */
469     private function write_definitions_to_cache(array $definitions) {
470         $this->configdefinitions = $definitions;
471         foreach ($this->configdefinitionmappings as $key => $mapping) {
472             if (!array_key_exists($mapping['definition'], $definitions)) {
473                 unset($this->configdefinitionmappings[$key]);
474             }
475         }
476         $this->config_save();
477     }
479     /**
480      * Loads the caches file if it exists.
481      * @param string $file Absolute path to the file.
482      * @return array
483      */
484     private static function load_caches_file($file) {
485         if (!file_exists($file)) {
486             return array();
487         }
488         $definitions = array();
489         include($file);
490         return $definitions;
491     }
493     /**
494      * Sets the mappings for a given definition.
495      *
496      * @param string $definition
497      * @param array $mappings
498      * @throws coding_exception
499      */
500     public function set_definition_mappings($definition, $mappings) {
501         if (!array_key_exists($definition, $this->configdefinitions)) {
502             throw new coding_exception('Invalid definition name passed when updating mappings.');
503         }
504         foreach ($mappings as $store) {
505             if (!array_key_exists($store, $this->configstores)) {
506                 throw new coding_exception('Invalid store name passed when updating definition mappings.');
507             }
508         }
509         foreach ($this->configdefinitionmappings as $key => $mapping) {
510             if ($mapping['definition'] == $definition) {
511                 unset($this->configdefinitionmappings[$key]);
512             }
513         }
514         $sort = count($mappings);
515         foreach ($mappings as $store) {
516             $this->configdefinitionmappings[] = array(
517                 'store' => $store,
518                 'definition' => $definition,
519                 'sort' => $sort
520             );
521             $sort--;
522         }
524         $this->config_save();
525     }
529 /**
530  * A cache helper for administration tasks
531  *
532  * @package    core
533  * @category   cache
534  * @copyright  2012 Sam Hemelryk
535  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
536  */
537 abstract class cache_administration_helper extends cache_helper {
539     /**
540      * Returns an array containing all of the information about stores a renderer needs.
541      * @return array
542      */
543     public static function get_store_instance_summaries() {
544         $return = array();
545         $default = array();
546         $instance = cache_config::instance();
547         $stores = $instance->get_all_stores();
548         foreach ($stores as $name => $details) {
549             $class = $details['class'];
550             $store = new $class($details['name'], $details['configuration']);
551             $record = array(
552                 'name' => $name,
553                 'plugin' => $details['plugin'],
554                 'default' => $details['default'],
555                 'isready' => $store->is_ready(),
556                 'requirementsmet' => $store->are_requirements_met(),
557                 'mappings' => 0,
558                 'modes' => array(
559                     cache_store::MODE_APPLICATION =>
560                         ($store->get_supported_modes($return) & cache_store::MODE_APPLICATION) == cache_store::MODE_APPLICATION,
561                     cache_store::MODE_SESSION =>
562                         ($store->get_supported_modes($return) & cache_store::MODE_SESSION) == cache_store::MODE_SESSION,
563                     cache_store::MODE_REQUEST =>
564                         ($store->get_supported_modes($return) & cache_store::MODE_REQUEST) == cache_store::MODE_REQUEST,
565                 ),
566                 'supports' => array(
567                     'multipleidentifiers' => $store->supports_multiple_identifiers(),
568                     'dataguarantee' => $store->supports_data_guarantee(),
569                     'nativettl' => $store->supports_native_ttl(),
570                     'nativelocking' => ($store instanceof cache_is_lockable),
571                     'keyawareness' => ($store instanceof cache_is_key_aware),
572                 )
573             );
574             if (empty($details['default'])) {
575                 $return[$name] = $record;
576             } else {
577                 $default[$name] = $record;
578             }
579         }
581         ksort($return);
582         ksort($default);
583         $return = $return + $default;
585         foreach ($instance->get_definition_mappings() as $mapping) {
586             if (!array_key_exists($mapping['store'], $return)) {
587                 continue;
588             }
589             $return[$mapping['store']]['mappings']++;
590         }
592         return $return;
593     }
595     /**
596      * Returns an array of information about plugins, everything a renderer needs.
597      * @return array
598      */
599     public static function get_store_plugin_summaries() {
600         $return = array();
601         $plugins = get_plugin_list_with_file('cachestore', 'lib.php', true);
602         foreach ($plugins as $plugin => $path) {
603             $class = 'cachestore_'.$plugin;
604             $return[$plugin] = array(
605                 'name' => get_string('pluginname', 'cachestore_'.$plugin),
606                 'requirementsmet' => $class::are_requirements_met(),
607                 'instances' => 0,
608                 'modes' => array(
609                     cache_store::MODE_APPLICATION => ($class::get_supported_modes() & cache_store::MODE_APPLICATION),
610                     cache_store::MODE_SESSION => ($class::get_supported_modes() & cache_store::MODE_SESSION),
611                     cache_store::MODE_REQUEST => ($class::get_supported_modes() & cache_store::MODE_REQUEST),
612                 ),
613                 'supports' => array(
614                     'multipleidentifiers' => ($class::get_supported_features() & cache_store::SUPPORTS_MULTIPLE_IDENTIFIERS),
615                     'dataguarantee' => ($class::get_supported_features() & cache_store::SUPPORTS_DATA_GUARANTEE),
616                     'nativettl' => ($class::get_supported_features() & cache_store::SUPPORTS_NATIVE_TTL),
617                     'nativelocking' => (in_array('cache_is_lockable', class_implements($class))),
618                     'keyawareness' => (array_key_exists('cache_is_key_aware', class_implements($class))),
619                 ),
620                 'canaddinstance' => ($class::can_add_instance() && $class::are_requirements_met())
621             );
622         }
624         $instance = cache_config::instance();
625         $stores = $instance->get_all_stores();
626         foreach ($stores as $store) {
627             $plugin = $store['plugin'];
628             if (array_key_exists($plugin, $return)) {
629                 $return[$plugin]['instances']++;
630             }
631         }
633         return $return;
634     }
636     /**
637      * Returns an array about the definitions. All the information a renderer needs.
638      * @return array
639      */
640     public static function get_definition_summaries() {
641         $instance = cache_config::instance();
642         $definitions = $instance->get_definitions();
644         $storenames = array();
645         foreach ($instance->get_all_stores() as $key => $store) {
646             if (!empty($store['default'])) {
647                 $storenames[$key] = new lang_string('store_'.$key, 'cache');
648             }
649         }
651         $modemappings = array();
652         foreach ($instance->get_mode_mappings() as $mapping) {
653             $mode = $mapping['mode'];
654             if (!array_key_exists($mode, $modemappings)) {
655                 $modemappings[$mode] = array();
656             }
657             if (array_key_exists($mapping['store'], $storenames)) {
658                 $modemappings[$mode][] = $storenames[$mapping['store']];
659             } else {
660                 $modemappings[$mode][] = $mapping['store'];
661             }
662         }
664         $definitionmappings = array();
665         foreach ($instance->get_definition_mappings() as $mapping) {
666             $definition = $mapping['definition'];
667             if (!array_key_exists($definition, $definitionmappings)) {
668                 $definitionmappings[$definition] = array();
669             }
670             if (array_key_exists($mapping['store'], $storenames)) {
671                 $definitionmappings[$definition][] = $storenames[$mapping['store']];
672             } else {
673                 $definitionmappings[$definition][] = $mapping['store'];
674             }
675         }
677         $return = array();
679         foreach ($definitions as $id => $definition) {
681             $mappings = array();
682             if (array_key_exists($id, $definitionmappings)) {
683                 $mappings = $definitionmappings[$id];
684             } else if (empty($definition['mappingsonly'])) {
685                 $mappings = $modemappings[$definition['mode']];
686             }
688             $return[$id] = array(
689                 'id' => $id,
690                 'name' => cache_helper::get_definition_name($definition),
691                 'mode' => $definition['mode'],
692                 'component' => $definition['component'],
693                 'area' => $definition['area'],
694                 'mappings' => $mappings
695             );
696         }
697         return $return;
698     }
700     /**
701      * Returns all of the actions that can be performed on a definition.
702      * @param context $context
703      * @return array
704      */
705     public static function get_definition_actions(context $context) {
706         if (has_capability('moodle/site:config', $context)) {
707             return array(
708                 array(
709                     'text' => get_string('editmappings', 'cache'),
710                     'url' => new moodle_url('/cache/admin.php', array('action' => 'editdefinitionmapping', 'sesskey' => sesskey()))
711                 ),
712                 array(
713                     'text' => get_string('purge', 'cache'),
714                     'url' => new moodle_url('/cache/admin.php', array('action' => 'purgedefinition', 'sesskey' => sesskey()))
715                 )
716             );
717         }
718         return array();
719     }
721     /**
722      * Returns all of the actions that can be performed on a store.
723      *
724      * @param string $name The name of the store
725      * @param array $storedetails
726      * @return array
727      */
728     public static function get_store_instance_actions($name, array $storedetails) {
729         $actions = array();
730         if (has_capability('moodle/site:config', get_system_context())) {
731             $baseurl = new moodle_url('/cache/admin.php', array('store' => $name, 'sesskey' => sesskey()));
732             if (empty($storedetails['default'])) {
733                 $actions[] = array(
734                     'text' => get_string('editstore', 'cache'),
735                     'url' => new moodle_url($baseurl, array('action' => 'editstore', 'plugin' => $storedetails['plugin']))
736                 );
737                 $actions[] = array(
738                     'text' => get_string('deletestore', 'cache'),
739                     'url' => new moodle_url($baseurl, array('action' => 'deletestore'))
740                 );
741             }
742             $actions[] = array(
743                 'text' => get_string('purge', 'cache'),
744                 'url' => new moodle_url($baseurl, array('action' => 'purgestore'))
745             );
746         }
747         return $actions;
748     }
751     /**
752      * Returns all of the actions that can be performed on a plugin.
753      *
754      * @param string $name The name of the plugin
755      * @param array $plugindetails
756      * @return array
757      */
758     public static function get_store_plugin_actions($name, array $plugindetails) {
759         $actions = array();
760         if (has_capability('moodle/site:config', context_system::instance())) {
761             if (!empty($plugindetails['canaddinstance'])) {
762                 $url = new moodle_url('/cache/admin.php', array('action' => 'addstore', 'plugin' => $name, 'sesskey' => sesskey()));
763                 $actions[] = array(
764                     'text' => get_string('addinstance', 'cache'),
765                     'url' => $url
766                 );
767             }
768         }
769         return $actions;
770     }
772     /**
773      * Returns a form that can be used to add a store instance.
774      *
775      * @param string $plugin The plugin to add an instance of
776      * @return cachestore_addinstance_form
777      * @throws coding_exception
778      */
779     public static function get_add_store_form($plugin) {
780         global $CFG; // Needed for includes.
781         $plugins = get_plugin_list('cachestore');
782         if (!array_key_exists($plugin, $plugins)) {
783             throw new coding_exception('Invalid cache plugin used when trying to create an edit form.');
784         }
785         $plugindir = $plugins[$plugin];
786         $class = 'cachestore_addinstance_form';
787         if (file_exists($plugindir.'/addinstanceform.php')) {
788             require_once($plugindir.'/addinstanceform.php');
789             if (class_exists('cachestore_'.$plugin.'_addinstance_form')) {
790                 $class = 'cachestore_'.$plugin.'_addinstance_form';
791                 if (!array_key_exists('cachestore_addinstance_form', class_parents($class))) {
792                     throw new coding_exception('Cache plugin add instance forms must extend cachestore_addinstance_form');
793                 }
794             }
795         }
797         $locks = self::get_possible_locks_for_stores($plugindir, $plugin);
799         $url = new moodle_url('/cache/admin.php', array('action' => 'addstore'));
800         return new $class($url, array('plugin' => $plugin, 'store' => null, 'locks' => $locks));
801     }
803     /**
804      * Returns a form that can be used to edit a store instance.
805      *
806      * @param string $plugin
807      * @param string $store
808      * @return cachestore_addinstance_form
809      * @throws coding_exception
810      */
811     public static function get_edit_store_form($plugin, $store) {
812         global $CFG; // Needed for includes.
813         $plugins = get_plugin_list('cachestore');
814         if (!array_key_exists($plugin, $plugins)) {
815             throw new coding_exception('Invalid cache plugin used when trying to create an edit form.');
816         }
817         $factory = cache_factory::instance();
818         $config = $factory->create_config_instance();
819         $stores = $config->get_all_stores();
820         if (!array_key_exists($store, $stores)) {
821             throw new coding_exception('Invalid store name given when trying to create an edit form.');
822         }
823         $plugindir = $plugins[$plugin];
824         $class = 'cachestore_addinstance_form';
825         if (file_exists($plugindir.'/addinstanceform.php')) {
826             require_once($plugindir.'/addinstanceform.php');
827             if (class_exists('cachestore_'.$plugin.'_addinstance_form')) {
828                 $class = 'cachestore_'.$plugin.'_addinstance_form';
829                 if (!array_key_exists('cachestore_addinstance_form', class_parents($class))) {
830                     throw new coding_exception('Cache plugin add instance forms must extend cachestore_addinstance_form');
831                 }
832             }
833         }
835         $locks = self::get_possible_locks_for_stores($plugindir, $plugin);
837         $url = new moodle_url('/cache/admin.php', array('action' => 'editstore', 'plugin' => $plugin, 'store' => $store));
838         $editform = new $class($url, array('plugin' => $plugin, 'store' => $store, 'locks' => $locks));
839         // See if the cachestore is going to want to load data for the form.
840         // If it has a customised add instance form then it is going to want to.
841         $storeclass = 'cachestore_'.$plugin;
842         $storedata = $stores[$store];
843         if (array_key_exists('configuration', $storedata) && array_key_exists('cache_is_configurable', class_implements($storeclass))) {
844             $storeclass::config_set_edit_form_data($editform, $storedata['configuration']);
845         }
846         return $editform;
847     }
849     /**
850      * Returns an array of suitable lock instances for use with this plugin, or false if the plugin handles locking itself.
851      *
852      * @param string $plugindir
853      * @param string $plugin
854      * @return array|false
855      */
856     protected static function get_possible_locks_for_stores($plugindir, $plugin) {
857         global $CFG; // Needed for includes.
858         $supportsnativelocking = false;
859         if (file_exists($plugindir.'/lib.php')) {
860             require_once($plugindir.'/lib.php');
861             $pluginclass = 'cachestore_'.$plugin;
862             if (class_exists($pluginclass)) {
863                 $supportsnativelocking = array_key_exists('cache_is_lockable', class_implements($pluginclass));
864             }
865         }
867         if (!$supportsnativelocking) {
868             $config = cache_config::instance();
869             $locks = array();
870             foreach ($config->get_locks() as $lock => $conf) {
871                 if (!empty($conf['default'])) {
872                     $name = get_string($lock, 'cache');
873                 } else {
874                     $name = $lock;
875                 }
876                 $locks[$lock] = $name;
877             }
878         } else {
879             $locks = false;
880         }
882         return $locks;
883     }
885     /**
886      * Processes the results of the add/edit instance form data for a plugin returning an array of config information suitable to
887      * store in configuration.
888      *
889      * @param stdClass $data The mform data.
890      * @return array
891      * @throws coding_exception
892      */
893     public static function get_store_configuration_from_data(stdClass $data) {
894         global $CFG;
895         $file = $CFG->dirroot.'/cache/stores/'.$data->plugin.'/lib.php';
896         if (!file_exists($file)) {
897             throw new coding_exception('Invalid cache plugin provided. '.$file);
898         }
899         require_once($file);
900         $class = 'cachestore_'.$data->plugin;
901         if (!class_exists($class)) {
902             throw new coding_exception('Invalid cache plugin provided.');
903         }
904         if (array_key_exists('cache_is_configurable', class_implements($class))) {
905             return $class::config_get_configuration_array($data);
906         }
907         return array();
908     }
910     /**
911      * Get an array of stores that are suitable to be used for a given definition.
912      *
913      * @param string $component
914      * @param string $area
915      * @return array Array containing 3 elements
916      *      1. An array of currently used stores
917      *      2. An array of suitable stores
918      *      3. An array of default stores
919      */
920     public static function get_definition_store_options($component, $area) {
921         $factory = cache_factory::instance();
922         $definition = $factory->create_definition($component, $area);
923         $config = cache_config::instance();
924         $currentstores = $config->get_stores_for_definition($definition);
925         $possiblestores = $config->get_stores($definition->get_mode(), $definition->get_requirements_bin());
927         $defaults = array();
928         foreach ($currentstores as $key => $store) {
929             if (!empty($store['default'])) {
930                 $defaults[] = $key;
931                 unset($currentstores[$key]);
932             }
933         }
934         foreach ($possiblestores as $key => $store) {
935             if ($store['default']) {
936                 unset($possiblestores[$key]);
937                 $possiblestores[$key] = $store;
938             }
939         }
940         return array($currentstores, $possiblestores, $defaults);
941     }
943     /**
944      * Get the default stores for all modes.
945      *
946      * @return array An array containing sub-arrays, one for each mode.
947      */
948     public static function get_default_mode_stores() {
949         $instance = cache_config::instance();
950         $storenames = array();
951         foreach ($instance->get_all_stores() as $key => $store) {
952             if (!empty($store['default'])) {
953                 $storenames[$key] = new lang_string('store_'.$key, 'cache');
954             }
955         }
956         $modemappings = array(
957             cache_store::MODE_APPLICATION => array(),
958             cache_store::MODE_SESSION => array(),
959             cache_store::MODE_REQUEST => array(),
960         );
961         foreach ($instance->get_mode_mappings() as $mapping) {
962             $mode = $mapping['mode'];
963             if (!array_key_exists($mode, $modemappings)) {
964                 debugging('Unknown mode in cache store mode mappings', DEBUG_DEVELOPER);
965                 continue;
966             }
967             if (array_key_exists($mapping['store'], $storenames)) {
968                 $modemappings[$mode][$mapping['store']] = $storenames[$mapping['store']];
969             } else {
970                 $modemappings[$mode][$mapping['store']] = $mapping['store'];
971             }
972         }
973         return $modemappings;
974     }
976     /**
977      * Returns an array summarising the locks available in the system
978      */
979     public static function get_lock_summaries() {
980         $locks = array();
981         $instance = cache_config::instance();
982         $stores = $instance->get_all_stores();
983         foreach ($instance->get_locks() as $lock) {
984             $default = !empty($lock['default']);
985             if ($default) {
986                 $name = new lang_string($lock['name'], 'cache');
987             } else {
988                 $name = $lock['name'];
989             }
990             $uses = 0;
991             foreach ($stores as $store) {
992                 if (!empty($store['lock']) && $store['lock'] === $lock['name']) {
993                     $uses++;
994                 }
995             }
996             $lockdata = array(
997                 'name' => $name,
998                 'default' => $default,
999                 'uses' => $uses
1000             );
1001             $locks[] = $lockdata;
1002         }
1003         return $locks;
1004     }