dc4821b25588e45a13f6846a9350df4132bdf08a
[moodle.git] / cache / classes / helper.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  * Cache helper class
19  *
20  * This file is part of Moodle's cache API, affectionately called MUC.
21  * It contains the components that are requried in order to use caching.
22  *
23  * @package    core
24  * @category   cache
25  * @copyright  2012 Sam Hemelryk
26  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27  */
29 defined('MOODLE_INTERNAL') || die();
31 /**
32  * The cache helper class.
33  *
34  * The cache helper class provides common functionality to the cache API and is useful to developers within to interact with
35  * the cache API in a general way.
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_helper {
44     /**
45      * Statistics gathered by the cache API during its operation will be used here.
46      * @static
47      * @var array
48      */
49     protected static $stats = array();
51     /**
52      * The instance of the cache helper.
53      * @var cache_helper
54      */
55     protected static $instance;
57     /**
58      * The site identifier used by the cache.
59      * Set the first time get_site_identifier is called.
60      * @var string
61      */
62     protected static $siteidentifier = null;
64     /**
65      * Returns true if the cache API can be initialised before Moodle has finished initialising itself.
66      *
67      * This check is essential when trying to cache the likes of configuration information. It checks to make sure that the cache
68      * configuration file has been created which allows use to set up caching when ever is required.
69      *
70      * @return bool
71      */
72     public static function ready_for_early_init() {
73         return cache_config::config_file_exists();
74     }
76     /**
77      * Returns an instance of the cache_helper.
78      *
79      * This is designed for internal use only and acts as a static store.
80      * @staticvar null $instance
81      * @return cache_helper
82      */
83     protected static function instance() {
84         if (is_null(self::$instance)) {
85             self::$instance = new cache_helper();
86         }
87         return self::$instance;
88     }
90     /**
91      * Constructs an instance of the cache_helper class. Again for internal use only.
92      */
93     protected function __construct() {
94         // Nothing to do here, just making sure you can't get an instance of this.
95     }
97     /**
98      * Used as a data store for initialised definitions.
99      * @var array
100      */
101     protected $definitions = array();
103     /**
104      * Used as a data store for initialised cache stores
105      * We use this because we want to avoid establishing multiple instances of a single store.
106      * @var array
107      */
108     protected $stores = array();
110     /**
111      * Returns the class for use as a cache loader for the given mode.
112      *
113      * @param int $mode One of cache_store::MODE_
114      * @return string
115      * @throws coding_exception
116      */
117     public static function get_class_for_mode($mode) {
118         switch ($mode) {
119             case cache_store::MODE_APPLICATION :
120                 return 'cache_application';
121             case cache_store::MODE_REQUEST :
122                 return 'cache_request';
123             case cache_store::MODE_SESSION :
124                 return 'cache_session';
125         }
126         throw new coding_exception('Unknown cache mode passed. Must be one of cache_store::MODE_*');
127     }
129     /**
130      * Returns the cache stores to be used with the given definition.
131      * @param cache_definition $definition
132      * @return array
133      */
134     public static function get_cache_stores(cache_definition $definition) {
135         $instance = cache_config::instance();
136         $stores = $instance->get_stores_for_definition($definition);
137         $stores = self::initialise_cachestore_instances($stores, $definition);
138         return $stores;
139     }
141     /**
142      * Internal function for initialising an array of stores against a given cache definition.
143      *
144      * @param array $stores
145      * @param cache_definition $definition
146      * @return cache_store[]
147      */
148     protected static function initialise_cachestore_instances(array $stores, cache_definition $definition) {
149         $return = array();
150         $factory = cache_factory::instance();
151         foreach ($stores as $name => $details) {
152             $store = $factory->create_store_from_config($name, $details, $definition);
153             if ($store !== false) {
154                 $return[] = $store;
155             }
156         }
157         return $return;
158     }
160     /**
161      * Returns a cache_lock instance suitable for use with the store.
162      *
163      * @param cache_store $store
164      * @return cache_lock_interface
165      */
166     public static function get_cachelock_for_store(cache_store $store) {
167         $instance = cache_config::instance();
168         $lockconf = $instance->get_lock_for_store($store->my_name());
169         $factory = cache_factory::instance();
170         return $factory->create_lock_instance($lockconf);
171     }
173     /**
174      * Returns an array of plugins without using core methods.
175      *
176      * This function explicitly does NOT use core functions as it will in some circumstances be called before Moodle has
177      * finished initialising. This happens when loading configuration for instance.
178      *
179      * @return string
180      */
181     public static function early_get_cache_plugins() {
182         global $CFG;
183         $result = array();
184         $ignored = array('CVS', '_vti_cnf', 'simpletest', 'db', 'yui', 'tests');
185         $fulldir = $CFG->dirroot.'/cache/stores';
186         $items = new DirectoryIterator($fulldir);
187         foreach ($items as $item) {
188             if ($item->isDot() or !$item->isDir()) {
189                 continue;
190             }
191             $pluginname = $item->getFilename();
192             if (in_array($pluginname, $ignored)) {
193                 continue;
194             }
195             if (!is_valid_plugin_name($pluginname)) {
196                 // Better ignore plugins with problematic names here.
197                 continue;
198             }
199             $result[$pluginname] = $fulldir.'/'.$pluginname;
200             unset($item);
201         }
202         unset($items);
203         return $result;
204     }
206     /**
207      * Invalidates a given set of keys from a given definition.
208      *
209      * @todo Invalidating by definition should also add to the event cache so that sessions can be invalidated (when required).
210      *
211      * @param string $component
212      * @param string $area
213      * @param array $identifiers
214      * @param array $keys
215      * @return boolean
216      */
217     public static function invalidate_by_definition($component, $area, array $identifiers = array(), $keys = array()) {
218         $cache = cache::make($component, $area, $identifiers);
219         if (is_array($keys)) {
220             $cache->delete_many($keys);
221         } else if (is_scalar($keys)) {
222             $cache->delete($keys);
223         } else {
224             throw new coding_exception('cache_helper::invalidate_by_definition only accepts $keys as array, or scalar.');
225         }
226         return true;
227     }
229     /**
230      * Invalidates a given set of keys by means of an event.
231      *
232      * Events cannot determine what identifiers might need to be cleared. Event based purge and invalidation
233      * are only supported on caches without identifiers.
234      *
235      * @param string $event
236      * @param array $keys
237      */
238     public static function invalidate_by_event($event, array $keys) {
239         $instance = cache_config::instance();
240         $invalidationeventset = false;
241         $factory = cache_factory::instance();
242         $inuse = $factory->get_caches_in_use();
243         $purgetoken = null;
244         foreach ($instance->get_definitions() as $name => $definitionarr) {
245             $definition = cache_definition::load($name, $definitionarr);
246             if ($definition->invalidates_on_event($event)) {
247                 // First up check if there is a cache loader for this definition already.
248                 // If there is we need to invalidate the keys from there.
249                 $definitionkey = $definition->get_component().'/'.$definition->get_area();
250                 if (isset($inuse[$definitionkey])) {
251                     $inuse[$definitionkey]->delete_many($keys);
252                 }
254                 // We should only log events for application and session caches.
255                 // Request caches shouldn't have events as all data is lost at the end of the request.
256                 // Events should only be logged once of course and likely several definitions are watching so we
257                 // track its logging with $invalidationeventset.
258                 $logevent = ($invalidationeventset === false && $definition->get_mode() !== cache_store::MODE_REQUEST);
260                 if ($logevent) {
261                     // Get the event invalidation cache.
262                     $cache = cache::make('core', 'eventinvalidation');
263                     // Get any existing invalidated keys for this cache.
264                     $data = $cache->get($event);
265                     if ($data === false) {
266                         // There are none.
267                         $data = array();
268                     }
269                     // Add our keys to them with the current cache timestamp.
270                     if (null === $purgetoken) {
271                         $purgetoken = cache::get_purge_token(true);
272                     }
273                     foreach ($keys as $key) {
274                         $data[$key] = $purgetoken;
275                     }
276                     // Set that data back to the cache.
277                     $cache->set($event, $data);
278                     // This only needs to occur once.
279                     $invalidationeventset = true;
280                 }
281             }
282         }
283     }
285     /**
286      * Purges the cache for a specific definition.
287      *
288      * @param string $component
289      * @param string $area
290      * @param array $identifiers
291      * @return bool
292      */
293     public static function purge_by_definition($component, $area, array $identifiers = array()) {
294         // Create the cache.
295         $cache = cache::make($component, $area, $identifiers);
296         // Initialise, in case of a store.
297         if ($cache instanceof cache_store) {
298             $factory = cache_factory::instance();
299             $definition = $factory->create_definition($component, $area, null);
300             $cacheddefinition = clone $definition;
301             $cacheddefinition->set_identifiers($identifiers);
302             $cache->initialise($cacheddefinition);
303         }
304         // Purge baby, purge.
305         $cache->purge();
306         return true;
307     }
309     /**
310      * Purges a cache of all information on a given event.
311      *
312      * Events cannot determine what identifiers might need to be cleared. Event based purge and invalidation
313      * are only supported on caches without identifiers.
314      *
315      * @param string $event
316      */
317     public static function purge_by_event($event) {
318         $instance = cache_config::instance();
319         $invalidationeventset = false;
320         $factory = cache_factory::instance();
321         $inuse = $factory->get_caches_in_use();
322         $purgetoken = null;
323         foreach ($instance->get_definitions() as $name => $definitionarr) {
324             $definition = cache_definition::load($name, $definitionarr);
325             if ($definition->invalidates_on_event($event)) {
326                 // First up check if there is a cache loader for this definition already.
327                 // If there is we need to invalidate the keys from there.
328                 $definitionkey = $definition->get_component().'/'.$definition->get_area();
329                 if (isset($inuse[$definitionkey])) {
330                     $inuse[$definitionkey]->purge();
331                 } else {
332                     cache::make($definition->get_component(), $definition->get_area())->purge();
333                 }
335                 // We should only log events for application and session caches.
336                 // Request caches shouldn't have events as all data is lost at the end of the request.
337                 // Events should only be logged once of course and likely several definitions are watching so we
338                 // track its logging with $invalidationeventset.
339                 $logevent = ($invalidationeventset === false && $definition->get_mode() !== cache_store::MODE_REQUEST);
341                 // We need to flag the event in the "Event invalidation" cache if it hasn't already happened.
342                 if ($logevent && $invalidationeventset === false) {
343                     // Get the event invalidation cache.
344                     $cache = cache::make('core', 'eventinvalidation');
345                     // Create a key to invalidate all.
346                     if (null === $purgetoken) {
347                         $purgetoken = cache::get_purge_token(true);
348                     }
349                     $data = array(
350                         'purged' => $purgetoken,
351                     );
352                     // Set that data back to the cache.
353                     $cache->set($event, $data);
354                     // This only needs to occur once.
355                     $invalidationeventset = true;
356                 }
357             }
358         }
359     }
361     /**
362      * Ensure that the stats array is ready to collect information for the given store and definition.
363      * @param string $store
364      * @param string $storeclass
365      * @param string $definition A string that identifies the definition.
366      * @param int $mode One of cache_store::MODE_*. Since 2.9.
367      */
368     protected static function ensure_ready_for_stats($store, $storeclass, $definition, $mode = cache_store::MODE_APPLICATION) {
369         // This function is performance-sensitive, so exit as quickly as possible
370         // if we do not need to do anything.
371         if (isset(self::$stats[$definition]['stores'][$store])) {
372             return;
373         }
375         if (!array_key_exists($definition, self::$stats)) {
376             self::$stats[$definition] = array(
377                 'mode' => $mode,
378                 'stores' => array(
379                     $store => array(
380                         'class' => $storeclass,
381                         'hits' => 0,
382                         'misses' => 0,
383                         'sets' => 0,
384                     )
385                 )
386             );
387         } else if (!array_key_exists($store, self::$stats[$definition]['stores'])) {
388             self::$stats[$definition]['stores'][$store] = array(
389                 'class' => $storeclass,
390                 'hits' => 0,
391                 'misses' => 0,
392                 'sets' => 0,
393             );
394         }
395     }
397     /**
398      * Returns a string to describe the definition.
399      *
400      * This method supports the definition as a string due to legacy requirements.
401      * It is backwards compatible when a string is passed but is not accurate.
402      *
403      * @since 2.9
404      * @param cache_definition|string $definition
405      * @return string
406      */
407     protected static function get_definition_stat_id_and_mode($definition) {
408         if (!($definition instanceof cache_definition)) {
409             // All core calls to this method have been updated, this is the legacy state.
410             // We'll use application as the default as that is the most common, really this is not accurate of course but
411             // at this point we can only guess and as it only affects calls to cache stat outside of core (of which there should
412             // be none) I think that is fine.
413             debugging('Please update you cache stat calls to pass the definition rather than just its ID.', DEBUG_DEVELOPER);
414             return array((string)$definition, cache_store::MODE_APPLICATION);
415         }
416         return array($definition->get_id(), $definition->get_mode());
417     }
419     /**
420      * Record a cache hit in the stats for the given store and definition.
421      *
422      * In Moodle 2.9 the $definition argument changed from accepting only a string to accepting a string or a
423      * cache_definition instance. It is preferable to pass a cache definition instance.
424      *
425      * In Moodle 3.9 the first argument changed to also accept a cache_store.
426      *
427      * @internal
428      * @param string|cache_store $store
429      * @param cache_definition $definition You used to be able to pass a string here, however that is deprecated please pass the
430      *      actual cache_definition object now.
431      * @param int $hits The number of hits to record (by default 1)
432      */
433     public static function record_cache_hit($store, $definition, $hits = 1) {
434         $storeclass = '';
435         if ($store instanceof cache_store) {
436             $storeclass = get_class($store);
437             $store = $store->my_name();
438         }
439         list($definitionstr, $mode) = self::get_definition_stat_id_and_mode($definition);
440         self::ensure_ready_for_stats($store, $storeclass, $definitionstr, $mode);
441         self::$stats[$definitionstr]['stores'][$store]['hits'] += $hits;
442     }
444     /**
445      * Record a cache miss in the stats for the given store and definition.
446      *
447      * In Moodle 2.9 the $definition argument changed from accepting only a string to accepting a string or a
448      * cache_definition instance. It is preferable to pass a cache definition instance.
449      *
450      * In Moodle 3.9 the first argument changed to also accept a cache_store.
451      *
452      * @internal
453      * @param string|cache_store $store
454      * @param cache_definition $definition You used to be able to pass a string here, however that is deprecated please pass the
455      *      actual cache_definition object now.
456      * @param int $misses The number of misses to record (by default 1)
457      */
458     public static function record_cache_miss($store, $definition, $misses = 1) {
459         $storeclass = '';
460         if ($store instanceof cache_store) {
461             $storeclass = get_class($store);
462             $store = $store->my_name();
463         }
464         list($definitionstr, $mode) = self::get_definition_stat_id_and_mode($definition);
465         self::ensure_ready_for_stats($store, $storeclass, $definitionstr, $mode);
466         self::$stats[$definitionstr]['stores'][$store]['misses'] += $misses;
467     }
469     /**
470      * Record a cache set in the stats for the given store and definition.
471      *
472      * In Moodle 2.9 the $definition argument changed from accepting only a string to accepting a string or a
473      * cache_definition instance. It is preferable to pass a cache definition instance.
474      *
475      * In Moodle 3.9 the first argument changed to also accept a cache_store.
476      *
477      * @internal
478      * @param string|cache_store $store
479      * @param cache_definition $definition You used to be able to pass a string here, however that is deprecated please pass the
480      *      actual cache_definition object now.
481      * @param int $sets The number of sets to record (by default 1)
482      */
483     public static function record_cache_set($store, $definition, $sets = 1) {
484         $storeclass = '';
485         if ($store instanceof cache_store) {
486             $storeclass = get_class($store);
487             $store = $store->my_name();
488         }
489         list($definitionstr, $mode) = self::get_definition_stat_id_and_mode($definition);
490         self::ensure_ready_for_stats($store, $storeclass, $definitionstr, $mode);
491         self::$stats[$definitionstr]['stores'][$store]['sets'] += $sets;
492     }
494     /**
495      * Return the stats collected so far.
496      * @return array
497      */
498     public static function get_stats() {
499         return self::$stats;
500     }
502     /**
503      * Purge all of the cache stores of all of their data.
504      *
505      * Think twice before calling this method. It will purge **ALL** caches regardless of whether they have been used recently or
506      * anything. This will involve full setup of the cache + the purge operation. On a site using caching heavily this WILL be
507      * painful.
508      *
509      * @param bool $usewriter If set to true the cache_config_writer class is used. This class is special as it avoids
510      *      it is still usable when caches have been disabled.
511      *      Please use this option only if you really must. It's purpose is to allow the cache to be purged when it would be
512      *      otherwise impossible.
513      */
514     public static function purge_all($usewriter = false) {
515         $factory = cache_factory::instance();
516         $config = $factory->create_config_instance($usewriter);
517         foreach ($config->get_all_stores() as $store) {
518             self::purge_store($store['name'], $config);
519         }
520         foreach ($factory->get_adhoc_caches_in_use() as $cache) {
521             $cache->purge();
522         }
523     }
525     /**
526      * Purges a store given its name.
527      *
528      * @param string $storename
529      * @param cache_config $config
530      * @return bool
531      */
532     public static function purge_store($storename, cache_config $config = null) {
533         if ($config === null) {
534             $config = cache_config::instance();
535         }
537         $stores = $config->get_all_stores();
538         if (!array_key_exists($storename, $stores)) {
539             // The store does not exist.
540             return false;
541         }
543         $store = $stores[$storename];
544         $class = $store['class'];
547         // We check are_requirements_met although we expect is_ready is going to check as well.
548         if (!$class::are_requirements_met()) {
549             return false;
550         }
551         // Found the store: is it ready?
552         /* @var cache_store $instance */
553         $instance = new $class($store['name'], $store['configuration']);
554         if (!$instance->is_ready()) {
555             unset($instance);
556             return false;
557         }
558         foreach ($config->get_definitions_by_store($storename) as $id => $definition) {
559             $definition = cache_definition::load($id, $definition);
560             $definitioninstance = clone($instance);
561             $definitioninstance->initialise($definition);
562             $definitioninstance->purge();
563             unset($definitioninstance);
564         }
566         return true;
567     }
569     /**
570      * Purges all of the stores used by a definition.
571      *
572      * Unlike cache_helper::purge_by_definition this purges all of the data from the stores not
573      * just the data relating to the definition.
574      * This function is useful when you must purge a definition that requires setup but you don't
575      * want to set it up.
576      *
577      * @param string $component
578      * @param string $area
579      */
580     public static function purge_stores_used_by_definition($component, $area) {
581         $factory = cache_factory::instance();
582         $config = $factory->create_config_instance();
583         $definition = $factory->create_definition($component, $area);
584         $stores = $config->get_stores_for_definition($definition);
585         foreach ($stores as $store) {
586             self::purge_store($store['name']);
587         }
588     }
590     /**
591      * Returns the translated name of the definition.
592      *
593      * @param cache_definition $definition
594      * @return lang_string
595      */
596     public static function get_definition_name($definition) {
597         if ($definition instanceof cache_definition) {
598             return $definition->get_name();
599         }
600         $identifier = 'cachedef_'.clean_param($definition['area'], PARAM_STRINGID);
601         $component = $definition['component'];
602         if ($component === 'core') {
603             $component = 'cache';
604         }
605         return new lang_string($identifier, $component);
606     }
608     /**
609      * Hashes a descriptive key to make it shorter and still unique.
610      * @param string|int $key
611      * @param cache_definition $definition
612      * @return string
613      */
614     public static function hash_key($key, cache_definition $definition) {
615         if ($definition->uses_simple_keys()) {
616             if (debugging() && preg_match('#[^a-zA-Z0-9_]#', $key)) {
617                 throw new coding_exception('Cache definition '.$definition->get_id().' requires simple keys. Invalid key provided.', $key);
618             }
619             // We put the key first so that we can be sure the start of the key changes.
620             return (string)$key . '-' . $definition->generate_single_key_prefix();
621         }
622         $key = $definition->generate_single_key_prefix() . '-' . $key;
623         return sha1($key);
624     }
626     /**
627      * Finds all definitions and updates them within the cache config file.
628      *
629      * @param bool $coreonly If set to true only core definitions will be updated.
630      */
631     public static function update_definitions($coreonly = false) {
632         global $CFG;
633         // Include locallib.
634         require_once($CFG->dirroot.'/cache/locallib.php');
635         // First update definitions
636         cache_config_writer::update_definitions($coreonly);
637         // Second reset anything we have already initialised to ensure we're all up to date.
638         cache_factory::reset();
639     }
641     /**
642      * Update the site identifier stored by the cache API.
643      *
644      * @param string $siteidentifier
645      * @return string The new site identifier.
646      */
647     public static function update_site_identifier($siteidentifier) {
648         global $CFG;
649         // Include locallib.
650         require_once($CFG->dirroot.'/cache/locallib.php');
651         $factory = cache_factory::instance();
652         $factory->updating_started();
653         $config = $factory->create_config_instance(true);
654         $siteidentifier = $config->update_site_identifier($siteidentifier);
655         $factory->updating_finished();
656         cache_factory::reset();
657         return $siteidentifier;
658     }
660     /**
661      * Returns the site identifier.
662      *
663      * @return string
664      */
665     public static function get_site_identifier() {
666         global $CFG;
667         if (!is_null(self::$siteidentifier)) {
668             return self::$siteidentifier;
669         }
670         // If site identifier hasn't been collected yet attempt to get it from the cache config.
671         $factory = cache_factory::instance();
672         // If the factory is initialising then we don't want to try to get it from the config or we risk
673         // causing the cache to enter an infinite initialisation loop.
674         if (!$factory->is_initialising()) {
675             $config = $factory->create_config_instance();
676             self::$siteidentifier = $config->get_site_identifier();
677         }
678         if (is_null(self::$siteidentifier)) {
679             // If the site identifier is still null then config isn't aware of it yet.
680             // We'll see if the CFG is loaded, and if not we will just use unknown.
681             // It's very important here that we don't use get_config. We don't want an endless cache loop!
682             if (!empty($CFG->siteidentifier)) {
683                 self::$siteidentifier = self::update_site_identifier($CFG->siteidentifier);
684             } else {
685                 // It's not being recorded in MUC's config and the config data hasn't been loaded yet.
686                 // Likely we are initialising.
687                 return 'unknown';
688             }
689         }
690         return self::$siteidentifier;
691     }
693     /**
694      * Returns the site version.
695      *
696      * @return string
697      */
698     public static function get_site_version() {
699         global $CFG;
700         return (string)$CFG->version;
701     }
703     /**
704      * Runs cron routines for MUC.
705      */
706     public static function cron() {
707         self::clean_old_session_data(true);
708     }
710     /**
711      * Cleans old session data from cache stores used for session based definitions.
712      *
713      * @param bool $output If set to true output will be given.
714      */
715     public static function clean_old_session_data($output = false) {
716         global $CFG;
717         if ($output) {
718             mtrace('Cleaning up stale session data from cache stores.');
719         }
720         $factory = cache_factory::instance();
721         $config = $factory->create_config_instance();
722         $definitions = $config->get_definitions();
723         $purgetime = time() - $CFG->sessiontimeout;
724         foreach ($definitions as $definitionarray) {
725             // We are only interested in session caches.
726             if (!($definitionarray['mode'] & cache_store::MODE_SESSION)) {
727                 continue;
728             }
729             $definition = $factory->create_definition($definitionarray['component'], $definitionarray['area']);
730             $stores = $config->get_stores_for_definition($definition);
731             // Turn them into store instances.
732             $stores = self::initialise_cachestore_instances($stores, $definition);
733             // Initialise all of the stores used for that definition.
734             foreach ($stores as $store) {
735                 // If the store doesn't support searching we can skip it.
736                 if (!($store instanceof cache_is_searchable)) {
737                     debugging('Cache stores used for session definitions should ideally be searchable.', DEBUG_DEVELOPER);
738                     continue;
739                 }
740                 // Get all of the keys.
741                 $keys = $store->find_by_prefix(cache_session::KEY_PREFIX);
742                 $todelete = array();
743                 foreach ($store->get_many($keys) as $key => $value) {
744                     if (strpos($key, cache_session::KEY_PREFIX) !== 0 || !is_array($value) || !isset($value['lastaccess'])) {
745                         continue;
746                     }
747                     if ((int)$value['lastaccess'] < $purgetime || true) {
748                         $todelete[] = $key;
749                     }
750                 }
751                 if (count($todelete)) {
752                     $outcome = (int)$store->delete_many($todelete);
753                     if ($output) {
754                         $strdef = s($definition->get_id());
755                         $strstore = s($store->my_name());
756                         mtrace("- Removed {$outcome} old {$strdef} sessions from the '{$strstore}' cache store.");
757                     }
758                 }
759             }
760         }
761     }
763     /**
764      * Returns an array of stores that would meet the requirements for every definition.
765      *
766      * These stores would be 100% suitable to map as defaults for cache modes.
767      *
768      * @return array[] An array of stores, keys are the store names.
769      */
770     public static function get_stores_suitable_for_mode_default() {
771         $factory = cache_factory::instance();
772         $config = $factory->create_config_instance();
773         $requirements = 0;
774         foreach ($config->get_definitions() as $definition) {
775             $definition = cache_definition::load($definition['component'].'/'.$definition['area'], $definition);
776             $requirements = $requirements | $definition->get_requirements_bin();
777         }
778         $stores = array();
779         foreach ($config->get_all_stores() as $name => $store) {
780             if (!empty($store['features']) && ($store['features'] & $requirements)) {
781                 $stores[$name] = $store;
782             }
783         }
784         return $stores;
785     }
787     /**
788      * Returns stores suitable for use with a given definition.
789      *
790      * @param cache_definition $definition
791      * @return cache_store[]
792      */
793     public static function get_stores_suitable_for_definition(cache_definition $definition) {
794         $factory = cache_factory::instance();
795         $stores = array();
796         if ($factory->is_initialising() || $factory->stores_disabled()) {
797             // No suitable stores here.
798             return $stores;
799         } else {
800             $stores = self::get_cache_stores($definition);
801             // If mappingsonly is set, having 0 stores is ok.
802             if ((count($stores) === 0) && (!$definition->is_for_mappings_only())) {
803                 // No suitable stores we found for the definition. We need to come up with a sensible default.
804                 // If this has happened we can be sure that the user has mapped custom stores to either the
805                 // mode of the definition. The first alternative to try is the system default for the mode.
806                 // e.g. the default file store instance for application definitions.
807                 $config = $factory->create_config_instance();
808                 foreach ($config->get_stores($definition->get_mode()) as $name => $details) {
809                     if (!empty($details['default'])) {
810                         $stores[] = $factory->create_store_from_config($name, $details, $definition);
811                         break;
812                     }
813                 }
814             }
815         }
816         return $stores;
817     }
819     /**
820      * Returns an array of warnings from the cache API.
821      *
822      * The warning returned here are for things like conflicting store instance configurations etc.
823      * These get shown on the admin notifications page for example.
824      *
825      * @param array|null $stores An array of stores to get warnings for, or null for all.
826      * @return string[]
827      */
828     public static function warnings(array $stores = null) {
829         global $CFG;
830         if ($stores === null) {
831             require_once($CFG->dirroot.'/cache/locallib.php');
832             $stores = cache_administration_helper::get_store_instance_summaries();
833         }
834         $warnings = array();
835         foreach ($stores as $store) {
836             if (!empty($store['warnings'])) {
837                 $warnings = array_merge($warnings, $store['warnings']);
838             }
839         }
840         return $warnings;
841     }