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