1f5a7edac30eaab5b29e5a82fef733f545b01579
[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_plugin_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         if (!is_subclass_of($class, 'cache_store')) {
140             throw new cache_exception('Invalid cache plugin specified. The plugin does not extend the required class.');
141         }
142         if (!$class::are_requirements_met()) {
143             throw new cache_exception('Unable to add new cache plugin instance. The requested plugin type is not supported.');
144         }
145         $this->configstores[$name] = array(
146             'name' => $name,
147             'plugin' => $plugin,
148             'configuration' => $configuration,
149             'features' => $class::get_supported_features($configuration),
150             'modes' => $class::get_supported_modes($configuration),
151             'mappingsonly' => !empty($configuration['mappingsonly'])
152         );
153         if (array_key_exists('lock', $configuration)) {
154             $this->configstores[$name]['lock'] = $configuration['lock'];
155             unset($this->configstores[$name]['configuration']['lock']);
156         }
157         $this->config_save();
158         return true;
159     }
161     /**
162      * Sets the mode mappings.
163      *
164      * These determine the default caches for the different modes.
165      * This function also calls save so you should redirect immediately, or at least very shortly after
166      * calling this method.
167      *
168      * @param array $modemappings
169      * @return bool
170      * @throws cache_exception
171      */
172     public function set_mode_mappings(array $modemappings) {
173         $mappings = array(
174             cache_store::MODE_APPLICATION => array(),
175             cache_store::MODE_SESSION => array(),
176             cache_store::MODE_REQUEST => array(),
177         );
178         foreach ($modemappings as $mode => $stores) {
179             if (!array_key_exists($mode, $mappings)) {
180                 throw new cache_exception('The cache mode for the new mapping does not exist');
181             }
182             $sort = 0;
183             foreach ($stores as $store) {
184                 if (!array_key_exists($store, $this->configstores)) {
185                     throw new cache_exception('The instance name for the new mapping does not exist');
186                 }
187                 if (array_key_exists($store, $mappings[$mode])) {
188                     throw new cache_exception('This cache mapping already exists');
189                 }
190                 $mappings[$mode][] = array(
191                     'store' => $store,
192                     'mode' => $mode,
193                     'sort' => $sort++
194                 );
195             }
196         }
197         $this->configmodemappings = array_merge(
198             $mappings[cache_store::MODE_APPLICATION],
199             $mappings[cache_store::MODE_SESSION],
200             $mappings[cache_store::MODE_REQUEST]
201         );
203         $this->config_save();
204         return true;
205     }
207     /**
208      * Edits a give plugin instance.
209      *
210      * The plugin instance if determined by its name, hence you cannot rename plugins.
211      * This function also calls save so you should redirect immediately, or at least very shortly after
212      * calling this method.
213      *
214      * @param string $name
215      * @param string $plugin
216      * @param array $configuration
217      * @return bool
218      * @throws cache_exception
219      */
220     public function edit_plugin_instance($name, $plugin, $configuration) {
221         if (!array_key_exists($name, $this->configstores)) {
222             throw new cache_exception('The requested instance does not exist.');
223         }
224         $plugins = get_plugin_list_with_file('cachestore', 'lib.php');
225         if (!array_key_exists($plugin, $plugins)) {
226             throw new cache_exception('Invalid plugin name specified. The plugin either does not exist or is not valid.');
227         }
228         $class = 'cachestore_.'.$plugin;
229         $file = $plugins[$plugin];
230         if (!class_exists($class)) {
231             if (file_exists($file)) {
232                 require_once($file);
233             }
234             if (!class_exists($class)) {
235                 throw new cache_exception('Invalid cache plugin specified. The plugin does not contain the require class.');
236             }
237         }
238         $this->configstores[$name] = array(
239             'name' => $name,
240             'plugin' => $plugin,
241             'configuration' => $configuration,
242             'features' => $class::get_supported_features($configuration),
243             'modes' => $class::get_supported_modes($configuration),
244             'mappingsonly' => !empty($configuration['mappingsonly'])
245         );
246         if (array_key_exists('lock', $configuration)) {
247             $this->configstores[$name]['lock'] = $configuration['lock'];
248             unset($this->configstores[$name]['configuration']['lock']);
249         }
250         $this->config_save();
251         return true;
252     }
254     /**
255      * Deletes a store instance.
256      *
257      * This function also calls save so you should redirect immediately, or at least very shortly after
258      * calling this method.
259      *
260      * @param string $name The name of the instance to delete.
261      * @return bool
262      * @throws cache_exception
263      */
264     public function delete_store($name) {
265         if (!array_key_exists($name, $this->configstores)) {
266             throw new cache_exception('The requested store does not exist.');
267         }
268         if ($this->configstores[$name]['default']) {
269             throw new cache_exception('The can not delete the default stores.');
270         }
271         foreach ($this->configmodemappings as $mapping) {
272             if ($mapping['store'] === $name) {
273                 throw new cache_exception('You cannot delete a cache store that has mode mappings.');
274             }
275         }
276         foreach ($this->configdefinitionmappings as $mapping) {
277             if ($mapping['store'] === $name) {
278                 throw new cache_exception('You cannot delete a cache store that has definition mappings.');
279             }
280         }
281         unset($this->configstores[$name]);
282         $this->config_save();
283         return true;
284     }
286     /**
287      * Creates the default configuration and saves it.
288      *
289      * This function calls config_save, however it is safe to continue using it afterwards as this function should only ever
290      * be called when there is no configuration file already.
291      */
292     public static function create_default_configuration() {
293         global $CFG;
295         // HACK ALERT.
296         // We probably need to come up with a better way to create the default stores, or at least ensure 100% that the
297         // default store plugins are protected from deletion.
298         require_once($CFG->dirroot.'/cache/stores/file/lib.php');
299         require_once($CFG->dirroot.'/cache/stores/session/lib.php');
300         require_once($CFG->dirroot.'/cache/stores/static/lib.php');
302         $writer = new self;
303         $writer->configstores = array(
304             'default_application' => array(
305                 'name' => 'default_application',
306                 'plugin' => 'file',
307                 'configuration' => array(),
308                 'features' => cachestore_file::get_supported_features(),
309                 'modes' => cache_store::MODE_APPLICATION,
310                 'default' => true,
311             ),
312             'default_session' => array(
313                 'name' => 'default_session',
314                 'plugin' => 'session',
315                 'configuration' => array(),
316                 'features' => cachestore_session::get_supported_features(),
317                 'modes' => cache_store::MODE_SESSION,
318                 'default' => true,
319             ),
320             'default_request' => array(
321                 'name' => 'default_request',
322                 'plugin' => 'static',
323                 'configuration' => array(),
324                 'features' => cachestore_static::get_supported_features(),
325                 'modes' => cache_store::MODE_REQUEST,
326                 'default' => true,
327             )
328         );
329         $writer->configdefinitions = self::locate_definitions();
330         $writer->configmodemappings = array(
331             array(
332                 'mode' => cache_store::MODE_APPLICATION,
333                 'store' => 'default_application',
334                 'sort' => -1
335             ),
336             array(
337                 'mode' => cache_store::MODE_SESSION,
338                 'store' => 'default_session',
339                 'sort' => -1
340             ),
341             array(
342                 'mode' => cache_store::MODE_REQUEST,
343                 'store' => 'default_request',
344                 'sort' => -1
345             )
346         );
347         $writer->configlocks = array(
348             'default_file_lock' => array(
349                 'name' => 'cachelock_file_default',
350                 'type' => 'cachelock_file',
351                 'dir' => 'filelocks',
352                 'default' => true
353             )
354         );
355         $writer->config_save();
356     }
358     /**
359      * Updates the definition in the configuration from those found in the cache files.
360      *
361      * Calls config_save further down, you should redirect immediately or asap after calling this method.
362      */
363     public static function update_definitions() {
364         $config = self::instance();
365         $config->write_definitions_to_cache(self::locate_definitions());
366     }
368     /**
369      * Locates all of the definition files.
370      *
371      * @return array
372      */
373     protected static function locate_definitions() {
374         global $CFG;
376         $files = array();
377         if (file_exists($CFG->dirroot.'/lib/db/caches.php')) {
378             $files['core'] = $CFG->dirroot.'/lib/db/caches.php';
379         }
381         $plugintypes = get_plugin_types();
382         foreach ($plugintypes as $type => $location) {
383             $plugins = get_plugin_list_with_file($type, 'db/caches.php');
384             foreach ($plugins as $plugin => $filepath) {
385                 $component = clean_param($type.'_'.$plugin, PARAM_COMPONENT); // Standardised plugin name.
386                 $files[$component] = $filepath;
387             }
388         }
390         $definitions = array();
391         foreach ($files as $component => $file) {
392             $filedefs = self::load_caches_file($file);
393             foreach ($filedefs as $area => $definition) {
394                 $area = clean_param($area, PARAM_AREA);
395                 $id = $component.'/'.$area;
396                 $definition['component'] = $component;
397                 $definition['area'] = $area;
398                 if (array_key_exists($id, $definitions)) {
399                     debugging('Error: duplicate cache definition found with name '.$name, DEBUG_DEVELOPER);
400                     continue;
401                 }
402                 $definitions[$id] = $definition;
403             }
404         }
406         return $definitions;
407     }
409     /**
410      * Writes the updated definitions for the config file.
411      * @param array $definitions
412      */
413     private function write_definitions_to_cache(array $definitions) {
414         $this->configdefinitions = $definitions;
415         foreach ($this->configdefinitionmappings as $key => $mapping) {
416             if (!array_key_exists($mapping['definition'], $definitions)) {
417                 unset($this->configdefinitionmappings[$key]);
418             }
419         }
420         $this->config_save();
421     }
423     /**
424      * Loads the caches file if it exists.
425      * @param string $file Absolute path to the file.
426      * @return array
427      */
428     private static function load_caches_file($file) {
429         if (!file_exists($file)) {
430             return array();
431         }
432         $definitions = array();
433         include($file);
434         return $definitions;
435     }
437     /**
438      * Sets the mappings for a given definition.
439      *
440      * @param string $definition
441      * @param array $mappings
442      * @throws coding_exception
443      */
444     public function set_definition_mappings($definition, $mappings) {
445         if (!array_key_exists($definition, $this->configdefinitions)) {
446             throw new coding_exception('Invalid definition name passed when updating mappings.');
447         }
448         foreach ($mappings as $store) {
449             if (!array_key_exists($store, $this->configstores)) {
450                 throw new coding_exception('Invalid store name passed when updating definition mappings.');
451             }
452         }
453         foreach ($this->configdefinitionmappings as $key => $mapping) {
454             if ($mapping['definition'] == $definition) {
455                 unset($this->configdefinitionmappings[$key]);
456             }
457         }
458         $sort = count($mappings);
459         foreach ($mappings as $store) {
460             $this->configdefinitionmappings[] = array(
461                 'store' => $store,
462                 'definition' => $definition,
463                 'sort' => $sort
464             );
465             $sort--;
466         }
468         $this->config_save();
469     }
473 /**
474  * A cache helper for administration tasks
475  *
476  * @package    core
477  * @category   cache
478  * @copyright  2012 Sam Hemelryk
479  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
480  */
481 abstract class cache_administration_helper extends cache_helper {
483     /**
484      * Returns an array containing all of the information about stores a renderer needs.
485      * @return array
486      */
487     public static function get_store_summaries() {
488         $return = array();
489         $default = array();
490         $instance = cache_config::instance();
491         $stores = $instance->get_all_stores();
492         foreach ($stores as $name => $details) {
493             $class = $details['class'];
494             $store = new $class($details['name'], $details['configuration']);
495             $record = array(
496                 'name' => $name,
497                 'plugin' => $details['plugin'],
498                 'default' => $details['default'],
499                 'isready' => $store->is_ready(),
500                 'requirementsmet' => $store->are_requirements_met(),
501                 'mappings' => 0,
502                 'modes' => array(
503                     cache_store::MODE_APPLICATION =>
504                         ($store->get_supported_modes($return) & cache_store::MODE_APPLICATION) == cache_store::MODE_APPLICATION,
505                     cache_store::MODE_SESSION =>
506                         ($store->get_supported_modes($return) & cache_store::MODE_SESSION) == cache_store::MODE_SESSION,
507                     cache_store::MODE_REQUEST =>
508                         ($store->get_supported_modes($return) & cache_store::MODE_REQUEST) == cache_store::MODE_REQUEST,
509                 ),
510                 'supports' => array(
511                     'multipleidentifiers' => $store->supports_multiple_indentifiers(),
512                     'dataguarantee' => $store->supports_data_guarantee(),
513                     'nativettl' => $store->supports_native_ttl(),
514                     'nativelocking' => ($store instanceof cache_is_lockable),
515                     'keyawareness' => ($store instanceof cache_is_key_aware),
516                 )
517             );
518             if (empty($details['default'])) {
519                 $return[$name] = $record;
520             } else {
521                 $default[$name] = $record;
522             }
523         }
525         ksort($return);
526         ksort($default);
527         $return = $return + $default;
529         foreach ($instance->get_definition_mappings() as $mapping) {
530             if (!array_key_exists($mapping['store'], $return)) {
531                 continue;
532             }
533             $return[$mapping['store']]['mappings']++;
534         }
536         return $return;
537     }
539     /**
540      * Returns an array of information about plugins, everything a renderer needs.
541      * @return array
542      */
543     public static function get_plugin_summaries() {
544         $return = array();
545         $plugins = get_plugin_list_with_file('cachestore', 'lib.php', true);
546         foreach ($plugins as $plugin => $path) {
547             $class = 'cachestore_'.$plugin;
548             $return[$plugin] = array(
549                 'name' => get_string('pluginname', 'cachestore_'.$plugin),
550                 'requirementsmet' => $class::are_requirements_met(),
551                 'instances' => 0,
552                 'modes' => array(
553                     cache_store::MODE_APPLICATION => ($class::get_supported_modes() & cache_store::MODE_APPLICATION),
554                     cache_store::MODE_SESSION => ($class::get_supported_modes() & cache_store::MODE_SESSION),
555                     cache_store::MODE_REQUEST => ($class::get_supported_modes() & cache_store::MODE_REQUEST),
556                 ),
557                 'supports' => array(
558                     'multipleidentifiers' => ($class::get_supported_features() & cache_store::SUPPORTS_MULTIPLE_IDENTIFIERS),
559                     'dataguarantee' => ($class::get_supported_features() & cache_store::SUPPORTS_DATA_GUARANTEE),
560                     'nativettl' => ($class::get_supported_features() & cache_store::SUPPORTS_NATIVE_TTL),
561                     'nativelocking' => (in_array('cache_is_lockable', class_implements($class))),
562                     'keyawareness' => (array_key_exists('cache_is_key_aware', class_implements($class))),
563                 ),
564                 'canaddinstance' => ($class::can_add_instance())
565             );
566         }
568         $instance = cache_config::instance();
569         $stores = $instance->get_all_stores();
570         foreach ($stores as $store) {
571             $plugin = $store['plugin'];
572             if (array_key_exists($plugin, $return)) {
573                 $return[$plugin]['instances']++;
574             }
575         }
577         return $return;
578     }
580     /**
581      * Returns an array about the definitions. All the information a renderer needs.
582      * @return array
583      */
584     public static function get_definition_summaries() {
585         $instance = cache_config::instance();
586         $definitions = $instance->get_definitions();
588         $storenames = array();
589         foreach ($instance->get_all_stores() as $key => $store) {
590             if (!empty($store['default'])) {
591                 $storenames[$key] = new lang_string('store_'.$key, 'cache');
592             }
593         }
595         $modemappings = array();
596         foreach ($instance->get_mode_mappings() as $mapping) {
597             $mode = $mapping['mode'];
598             if (!array_key_exists($mode, $modemappings)) {
599                 $modemappings[$mode] = array();
600             }
601             if (array_key_exists($mapping['store'], $storenames)) {
602                 $modemappings[$mode][] = $storenames[$mapping['store']];
603             } else {
604                 $modemappings[$mode][] = $mapping['store'];
605             }
606         }
608         $definitionmappings = array();
609         foreach ($instance->get_definition_mappings() as $mapping) {
610             $definition = $mapping['definition'];
611             if (!array_key_exists($definition, $definitionmappings)) {
612                 $definitionmappings[$definition] = array();
613             }
614             if (array_key_exists($mapping['store'], $storenames)) {
615                 $definitionmappings[$definition][] = $storenames[$mapping['store']];
616             } else {
617                 $definitionmappings[$definition][] = $mapping['store'];
618             }
619         }
621         $return = array();
623         foreach ($definitions as $id => $definition) {
625             $mappings = array();
626             if (array_key_exists($id, $definitionmappings)) {
627                 $mappings = $definitionmappings[$id];
628             } else if (empty($definition['mappingsonly'])) {
629                 $mappings = $modemappings[$definition['mode']];
630             }
632             $return[$id] = array(
633                 'id' => $id,
634                 'name' => cache_helper::get_definition_name($definition),
635                 'mode' => $definition['mode'],
636                 'component' => $definition['component'],
637                 'area' => $definition['area'],
638                 'mappings' => $mappings
639             );
640         }
641         return $return;
642     }
644     /**
645      * Returns all of the actions that can be performed on a definition.
646      * @param context $context
647      * @return array
648      */
649     public static function get_definition_actions(context $context) {
650         if (has_capability('moodle/site:config', $context)) {
651             return array(
652                 array(
653                     'text' => get_string('editmappings', 'cache'),
654                     'url' => new moodle_url('/cache/admin.php', array('action' => 'editdefinitionmapping', 'sesskey' => sesskey()))
655                 )
656             );
657         }
658         return array();
659     }
661     /**
662      * Returns all of the actions that can be performed on a store.
663      *
664      * @param string $name The name of the store
665      * @param array $storedetails
666      * @return array
667      */
668     public static function get_store_actions($name, array $storedetails) {
669         $actions = array();
670         if (has_capability('moodle/site:config', get_system_context())) {
671             $baseurl = new moodle_url('/cache/admin.php', array('store' => $name, 'sesskey' => sesskey()));
672             if (empty($storedetails['default'])) {
673                 $actions[] = array(
674                     'text' => get_string('editstore', 'cache'),
675                     'url' => new moodle_url($baseurl, array('action' => 'editstore', 'plugin' => $storedetails['plugin']))
676                 );
677                 $actions[] = array(
678                     'text' => get_string('deletestore', 'cache'),
679                     'url' => new moodle_url($baseurl, array('action' => 'deletestore'))
680                 );
681             }
682             $actions[] = array(
683                 'text' => get_string('purge', 'cache'),
684                 'url' => new moodle_url($baseurl, array('action' => 'purge'))
685             );
686         }
687         return $actions;
688     }
691     /**
692      * Returns all of the actions that can be performed on a plugin.
693      *
694      * @param string $name The name of the plugin
695      * @param array $plugindetails
696      * @return array
697      */
698     public static function get_plugin_actions($name, array $plugindetails) {
699         $actions = array();
700         if (has_capability('moodle/site:config', get_system_context())) {
701             if (!empty($plugindetails['canaddinstance'])) {
702                 $url = new moodle_url('/cache/admin.php', array('action' => 'addstore', 'plugin' => $name, 'sesskey' => sesskey()));
703                 $actions[] = array(
704                     'text' => get_string('addinstance', 'cache'),
705                     'url' => $url
706                 );
707             }
708         }
709         return $actions;
710     }
712     /**
713      * Returns a form that can be used to add a store instance.
714      *
715      * @param string $plugin The plugin to add an instance of
716      * @return cachestore_addinstance_form
717      * @throws coding_exception
718      */
719     public static function get_add_store_form($plugin) {
720         global $CFG; // Needed for includes.
721         $plugindir = get_plugin_directory('cachestore', $plugin);
722         $class = 'cachestore_addinstance_form';
723         if (file_exists($plugindir.'/addinstanceform.php')) {
724             require_once($plugindir.'/addinstanceform.php');
725             if (class_exists('cachestore_'.$plugin.'_addinstance_form')) {
726                 $class = 'cachestore_'.$plugin.'_addinstance_form';
727                 if (!array_key_exists('cachestore_addinstance_form', class_parents($class))) {
728                     throw new coding_exception('Cache plugin add instance forms must extend cachestore_addinstance_form');
729                 }
730             }
731         }
733         $supportsnativelocking = false;
734         if (file_exists($plugindir.'/lib.php')) {
735             require_once($plugindir.'/lib.php');
736             $pluginclass = 'cachestore_'.$plugin;
737             if (class_exists($pluginclass)) {
738                 $supportsnativelocking = array_key_exists('cache_is_lockable', class_implements($pluginclass));
739             }
740         }
742         if (!$supportsnativelocking) {
743             $config = cache_config::instance();
744             $locks = array();
745             foreach ($config->get_locks() as $lock => $conf) {
746                 if (!empty($conf['default'])) {
747                     $name = get_string($lock, 'cache');
748                 } else {
749                     $name = $lock;
750                 }
751                 $locks[$lock] = $name;
752             }
753         } else {
754             $locks = false;
755         }
757         $url = new moodle_url('/cache/admin.php', array('action' => 'addstore'));
758         return new $class($url, array('plugin' => $plugin, 'store' => null, 'locks' => $locks));
759     }
761     /**
762      * Returns a form that can be used to edit a store instance.
763      *
764      * @param string $plugin
765      * @param string $store
766      * @return cachestore_addinstance_form
767      * @throws coding_exception
768      */
769     public static function get_edit_store_form($plugin, $store) {
770         global $CFG; // Needed for includes.
771         $plugindir = get_plugin_directory('cachestore', $plugin);
772         $class = 'cachestore_addinstance_form';
773         if (file_exists($plugindir.'/addinstanceform.php')) {
774             require_once($plugindir.'/addinstanceform.php');
775             if (class_exists('cachestore_'.$plugin.'_addinstance_form')) {
776                 $class = 'cachestore_'.$plugin.'_addinstance_form';
777                 if (!array_key_exists('cachestore_addinstance_form', class_parents($class))) {
778                     throw new coding_exception('Cache plugin add instance forms must extend cachestore_addinstance_form');
779                 }
780             }
781         }
783         $url = new moodle_url('/cache/admin.php', array('action' => 'editstore'));
784         return new $class($url, array('plugin' => $plugin, 'store' => $store));
785     }
787     /**
788      * Processes the results of the add/edit instance form data for a plugin returning an array of config information suitable to
789      * store in configuration.
790      *
791      * @param stdClass $data The mform data.
792      * @return array
793      * @throws coding_exception
794      */
795     public static function get_store_configuration_from_data(stdClass $data) {
796         global $CFG;
797         $file = $CFG->dirroot.'/cache/stores/'.$data->plugin.'/lib.php';
798         if (!file_exists($file)) {
799             throw new coding_exception('Invalid cache plugin provided. '.$file);
800         }
801         require_once($file);
802         $class = 'cachestore_'.$data->plugin;
803         $method = 'config_get_configuration_array';
804         if (!class_exists($class)) {
805             throw new coding_exception('Invalid cache plugin provided.');
806         }
807         if (method_exists($class, $method)) {
808             return call_user_func(array($class, $method), $data);
809         }
810         return array();
811     }
813     /**
814      * Get an array of stores that are suitable to be used for a given definition.
815      *
816      * @param string $component
817      * @param string $area
818      * @return array Array containing 3 elements
819      *      1. An array of currently used stores
820      *      2. An array of suitable stores
821      *      3. An array of default stores
822      */
823     public static function get_definition_store_options($component, $area) {
824         $factory = cache_factory::instance();
825         $definition = $factory->create_definition($component, $area);
826         $config = cache_config::instance();
827         $currentstores = $config->get_stores_for_definition($definition);
828         $possiblestores = $config->get_stores($definition->get_mode(), $definition->get_requirements_bin());
830         $defaults = array();
831         foreach ($currentstores as $key => $store) {
832             if (!empty($store['default'])) {
833                 $defaults[] = $key;
834                 unset($currentstores[$key]);
835             }
836         }
837         foreach ($possiblestores as $key => $store) {
838             if ($store['default']) {
839                 unset($possiblestores[$key]);
840                 $possiblestores[$key] = $store;
841             }
842         }
843         return array($currentstores, $possiblestores, $defaults);
844     }
846     /**
847      * Get the default stores for all modes.
848      *
849      * @return array An array containing sub-arrays, one for each mode.
850      */
851     public static function get_default_mode_stores() {
852         $instance = cache_config::instance();
853         $storenames = array();
854         foreach ($instance->get_all_stores() as $key => $store) {
855             if (!empty($store['default'])) {
856                 $storenames[$key] = new lang_string('store_'.$key, 'cache');
857             }
858         }
859         $modemappings = array(
860             cache_store::MODE_APPLICATION => array(),
861             cache_store::MODE_SESSION => array(),
862             cache_store::MODE_REQUEST => array(),
863         );
864         foreach ($instance->get_mode_mappings() as $mapping) {
865             $mode = $mapping['mode'];
866             if (!array_key_exists($mode, $modemappings)) {
867                 debugging('Unknown mode in cache store mode mappings', DEBUG_DEVELOPER);
868                 continue;
869             }
870             if (array_key_exists($mapping['store'], $storenames)) {
871                 $modemappings[$mode][$mapping['store']] = $storenames[$mapping['store']];
872             } else {
873                 $modemappings[$mode][$mapping['store']] = $mapping['store'];
874             }
875         }
876         return $modemappings;
877     }
879     /**
880      * Returns an array summarising the locks available in the system
881      */
882     public static function get_lock_summaries() {
883         $locks = array();
884         $instance = cache_config::instance();
885         $stores = $instance->get_all_stores();
886         foreach ($instance->get_locks() as $lock) {
887             $default = !empty($lock['default']);
888             if ($default) {
889                 $name = new lang_string($lock['name'], 'cache');
890             } else {
891                 $name = $lock['name'];
892             }
893             $uses = 0;
894             foreach ($stores as $store) {
895                 if (!empty($store['lock']) && $store['lock'] === $lock['name']) {
896                     $uses++;
897                 }
898             }
899             $lockdata = array(
900                 'name' => $name,
901                 'default' => $default,
902                 'uses' => $uses
903             );
904             $locks[] = $lockdata;
905         }
906         return $locks;
907     }