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