Proyectos de Subversion Moodle

Rev

Ir a la última revisión | | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
 * 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
 */
28
 
29
defined('MOODLE_INTERNAL') || die();
30
 
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 {
43
 
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();
50
 
51
    /**
52
     * The instance of the cache helper.
53
     * @var cache_helper
54
     */
55
    protected static $instance;
56
 
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;
63
 
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
    }
75
 
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
    }
89
 
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
    }
96
 
97
    /**
98
     * Used as a data store for initialised definitions.
99
     * @var array
100
     */
101
    protected $definitions = array();
102
 
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();
109
 
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
    }
128
 
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
    }
140
 
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
    }
159
 
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
    }
172
 
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 array
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
    }
205
 
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|string|int $keys
215
     * @return boolean
216
     * @throws coding_exception
217
     */
218
    public static function invalidate_by_definition($component, $area, array $identifiers = array(), $keys = array()) {
219
        $cache = cache::make($component, $area, $identifiers);
220
        if (is_array($keys)) {
221
            $cache->delete_many($keys);
222
        } else if (is_scalar($keys)) {
223
            $cache->delete($keys);
224
        } else {
225
            throw new coding_exception('cache_helper::invalidate_by_definition only accepts $keys as array, or scalar.');
226
        }
227
        return true;
228
    }
229
 
230
    /**
231
     * Invalidates a given set of keys by means of an event.
232
     *
233
     * Events cannot determine what identifiers might need to be cleared. Event based purge and invalidation
234
     * are only supported on caches without identifiers.
235
     *
236
     * @param string $event
237
     * @param array $keys
238
     */
239
    public static function invalidate_by_event($event, array $keys) {
240
        $instance = cache_config::instance();
241
        $invalidationeventset = false;
242
        $factory = cache_factory::instance();
243
        $inuse = $factory->get_caches_in_use();
244
        $purgetoken = null;
245
        foreach ($instance->get_definitions() as $name => $definitionarr) {
246
            $definition = cache_definition::load($name, $definitionarr);
247
            if ($definition->invalidates_on_event($event)) {
248
                // First up check if there is a cache loader for this definition already.
249
                // If there is we need to invalidate the keys from there.
250
                $definitionkey = $definition->get_component().'/'.$definition->get_area();
251
                if (isset($inuse[$definitionkey])) {
252
                    $inuse[$definitionkey]->delete_many($keys);
253
                }
254
 
255
                // We should only log events for application and session caches.
256
                // Request caches shouldn't have events as all data is lost at the end of the request.
257
                // Events should only be logged once of course and likely several definitions are watching so we
258
                // track its logging with $invalidationeventset.
259
                $logevent = ($invalidationeventset === false && $definition->get_mode() !== cache_store::MODE_REQUEST);
260
 
261
                if ($logevent) {
262
                    // Get the event invalidation cache.
263
                    $cache = cache::make('core', 'eventinvalidation');
264
                    // Get any existing invalidated keys for this cache.
265
                    $data = $cache->get($event);
266
                    if ($data === false) {
267
                        // There are none.
268
                        $data = array();
269
                    }
270
                    // Add our keys to them with the current cache timestamp.
271
                    if (null === $purgetoken) {
272
                        $purgetoken = cache::get_purge_token(true);
273
                    }
274
                    foreach ($keys as $key) {
275
                        $data[$key] = $purgetoken;
276
                    }
277
                    // Set that data back to the cache.
278
                    $cache->set($event, $data);
279
                    // This only needs to occur once.
280
                    $invalidationeventset = true;
281
                }
282
            }
283
        }
284
    }
285
 
286
    /**
287
     * Purges the cache for a specific definition.
288
     *
289
     * @param string $component
290
     * @param string $area
291
     * @param array $identifiers
292
     * @return bool
293
     */
294
    public static function purge_by_definition($component, $area, array $identifiers = array()) {
295
        // Create the cache.
296
        $cache = cache::make($component, $area, $identifiers);
297
        // Initialise, in case of a store.
298
        if ($cache instanceof cache_store) {
299
            $factory = cache_factory::instance();
300
            $definition = $factory->create_definition($component, $area, null);
301
            $cacheddefinition = clone $definition;
302
            $cacheddefinition->set_identifiers($identifiers);
303
            $cache->initialise($cacheddefinition);
304
        }
305
        // Purge baby, purge.
306
        $cache->purge();
307
        return true;
308
    }
309
 
310
    /**
311
     * Purges a cache of all information on a given event.
312
     *
313
     * Events cannot determine what identifiers might need to be cleared. Event based purge and invalidation
314
     * are only supported on caches without identifiers.
315
     *
316
     * @param string $event
317
     */
318
    public static function purge_by_event($event) {
319
        $instance = cache_config::instance();
320
        $invalidationeventset = false;
321
        $factory = cache_factory::instance();
322
        $inuse = $factory->get_caches_in_use();
323
        $purgetoken = null;
324
        foreach ($instance->get_definitions() as $name => $definitionarr) {
325
            $definition = cache_definition::load($name, $definitionarr);
326
            if ($definition->invalidates_on_event($event)) {
327
                // First up check if there is a cache loader for this definition already.
328
                // If there is we need to invalidate the keys from there.
329
                $definitionkey = $definition->get_component().'/'.$definition->get_area();
330
                if (isset($inuse[$definitionkey])) {
331
                    $inuse[$definitionkey]->purge();
332
                } else {
333
                    cache::make($definition->get_component(), $definition->get_area())->purge();
334
                }
335
 
336
                // We should only log events for application and session caches.
337
                // Request caches shouldn't have events as all data is lost at the end of the request.
338
                // Events should only be logged once of course and likely several definitions are watching so we
339
                // track its logging with $invalidationeventset.
340
                $logevent = ($invalidationeventset === false && $definition->get_mode() !== cache_store::MODE_REQUEST);
341
 
342
                // We need to flag the event in the "Event invalidation" cache if it hasn't already happened.
343
                if ($logevent && $invalidationeventset === false) {
344
                    // Get the event invalidation cache.
345
                    $cache = cache::make('core', 'eventinvalidation');
346
                    // Create a key to invalidate all.
347
                    if (null === $purgetoken) {
348
                        $purgetoken = cache::get_purge_token(true);
349
                    }
350
                    $data = array(
351
                        'purged' => $purgetoken,
352
                    );
353
                    // Set that data back to the cache.
354
                    $cache->set($event, $data);
355
                    // This only needs to occur once.
356
                    $invalidationeventset = true;
357
                }
358
            }
359
        }
360
    }
361
 
362
    /**
363
     * Ensure that the stats array is ready to collect information for the given store and definition.
364
     * @param string $store
365
     * @param string $storeclass
366
     * @param string $definition A string that identifies the definition.
367
     * @param int $mode One of cache_store::MODE_*. Since 2.9.
368
     */
369
    protected static function ensure_ready_for_stats($store, $storeclass, $definition, $mode = cache_store::MODE_APPLICATION) {
370
        // This function is performance-sensitive, so exit as quickly as possible
371
        // if we do not need to do anything.
372
        if (isset(self::$stats[$definition]['stores'][$store])) {
373
            return;
374
        }
375
 
376
        if (!array_key_exists($definition, self::$stats)) {
377
            self::$stats[$definition] = array(
378
                'mode' => $mode,
379
                'stores' => array(
380
                    $store => array(
381
                        'class' => $storeclass,
382
                        'hits' => 0,
383
                        'misses' => 0,
384
                        'sets' => 0,
385
                        'iobytes' => cache_store::IO_BYTES_NOT_SUPPORTED,
386
                        'locks' => 0,
387
                    )
388
                )
389
            );
390
        } else if (!array_key_exists($store, self::$stats[$definition]['stores'])) {
391
            self::$stats[$definition]['stores'][$store] = array(
392
                'class' => $storeclass,
393
                'hits' => 0,
394
                'misses' => 0,
395
                'sets' => 0,
396
                'iobytes' => cache_store::IO_BYTES_NOT_SUPPORTED,
397
                'locks' => 0,
398
            );
399
        }
400
    }
401
 
402
    /**
403
     * Returns a string to describe the definition.
404
     *
405
     * This method supports the definition as a string due to legacy requirements.
406
     * It is backwards compatible when a string is passed but is not accurate.
407
     *
408
     * @since 2.9
409
     * @param cache_definition|string $definition
410
     * @return string
411
     */
412
    protected static function get_definition_stat_id_and_mode($definition) {
413
        if (!($definition instanceof cache_definition)) {
414
            // All core calls to this method have been updated, this is the legacy state.
415
            // We'll use application as the default as that is the most common, really this is not accurate of course but
416
            // at this point we can only guess and as it only affects calls to cache stat outside of core (of which there should
417
            // be none) I think that is fine.
418
            debugging('Please update you cache stat calls to pass the definition rather than just its ID.', DEBUG_DEVELOPER);
419
            return array((string)$definition, cache_store::MODE_APPLICATION);
420
        }
421
        return array($definition->get_id(), $definition->get_mode());
422
    }
423
 
424
    /**
425
     * Record a cache hit in the stats for the given store and definition.
426
     *
427
     * In Moodle 2.9 the $definition argument changed from accepting only a string to accepting a string or a
428
     * cache_definition instance. It is preferable to pass a cache definition instance.
429
     *
430
     * In Moodle 3.9 the first argument changed to also accept a cache_store.
431
     *
432
     * @internal
433
     * @param string|cache_store $store
434
     * @param cache_definition $definition You used to be able to pass a string here, however that is deprecated please pass the
435
     *      actual cache_definition object now.
436
     * @param int $hits The number of hits to record (by default 1)
437
     * @param int $readbytes Number of bytes read from the cache or cache_store::IO_BYTES_NOT_SUPPORTED
438
     */
439
    public static function record_cache_hit($store, $definition, int $hits = 1, int $readbytes = cache_store::IO_BYTES_NOT_SUPPORTED): void {
440
        $storeclass = '';
441
        if ($store instanceof cache_store) {
442
            $storeclass = get_class($store);
443
            $store = $store->my_name();
444
        }
445
        list($definitionstr, $mode) = self::get_definition_stat_id_and_mode($definition);
446
        self::ensure_ready_for_stats($store, $storeclass, $definitionstr, $mode);
447
        self::$stats[$definitionstr]['stores'][$store]['hits'] += $hits;
448
        if ($readbytes !== cache_store::IO_BYTES_NOT_SUPPORTED) {
449
            if (self::$stats[$definitionstr]['stores'][$store]['iobytes'] === cache_store::IO_BYTES_NOT_SUPPORTED) {
450
                self::$stats[$definitionstr]['stores'][$store]['iobytes'] = $readbytes;
451
            } else {
452
                self::$stats[$definitionstr]['stores'][$store]['iobytes'] += $readbytes;
453
            }
454
        }
455
    }
456
 
457
    /**
458
     * Record a cache miss in the stats for the given store and definition.
459
     *
460
     * In Moodle 2.9 the $definition argument changed from accepting only a string to accepting a string or a
461
     * cache_definition instance. It is preferable to pass a cache definition instance.
462
     *
463
     * In Moodle 3.9 the first argument changed to also accept a cache_store.
464
     *
465
     * @internal
466
     * @param string|cache_store $store
467
     * @param cache_definition $definition You used to be able to pass a string here, however that is deprecated please pass the
468
     *      actual cache_definition object now.
469
     * @param int $misses The number of misses to record (by default 1)
470
     */
471
    public static function record_cache_miss($store, $definition, $misses = 1) {
472
        $storeclass = '';
473
        if ($store instanceof cache_store) {
474
            $storeclass = get_class($store);
475
            $store = $store->my_name();
476
        }
477
        list($definitionstr, $mode) = self::get_definition_stat_id_and_mode($definition);
478
        self::ensure_ready_for_stats($store, $storeclass, $definitionstr, $mode);
479
        self::$stats[$definitionstr]['stores'][$store]['misses'] += $misses;
480
    }
481
 
482
    /**
483
     * Record a cache set in the stats for the given store and definition.
484
     *
485
     * In Moodle 2.9 the $definition argument changed from accepting only a string to accepting a string or a
486
     * cache_definition instance. It is preferable to pass a cache definition instance.
487
     *
488
     * In Moodle 3.9 the first argument changed to also accept a cache_store.
489
     *
490
     * @internal
491
     * @param string|cache_store $store
492
     * @param cache_definition $definition You used to be able to pass a string here, however that is deprecated please pass the
493
     *      actual cache_definition object now.
494
     * @param int $sets The number of sets to record (by default 1)
495
     * @param int $writebytes Number of bytes written to the cache or cache_store::IO_BYTES_NOT_SUPPORTED
496
     */
497
    public static function record_cache_set($store, $definition, int $sets = 1,
498
            int $writebytes = cache_store::IO_BYTES_NOT_SUPPORTED) {
499
        $storeclass = '';
500
        if ($store instanceof cache_store) {
501
            $storeclass = get_class($store);
502
            $store = $store->my_name();
503
        }
504
        list($definitionstr, $mode) = self::get_definition_stat_id_and_mode($definition);
505
        self::ensure_ready_for_stats($store, $storeclass, $definitionstr, $mode);
506
        self::$stats[$definitionstr]['stores'][$store]['sets'] += $sets;
507
        if ($writebytes !== cache_store::IO_BYTES_NOT_SUPPORTED) {
508
            if (self::$stats[$definitionstr]['stores'][$store]['iobytes'] === cache_store::IO_BYTES_NOT_SUPPORTED) {
509
                self::$stats[$definitionstr]['stores'][$store]['iobytes'] = $writebytes;
510
            } else {
511
                self::$stats[$definitionstr]['stores'][$store]['iobytes'] += $writebytes;
512
            }
513
        }
514
    }
515
 
516
    /**
517
     * Return the stats collected so far.
518
     * @return array
519
     */
520
    public static function get_stats() {
521
        return self::$stats;
522
    }
523
 
524
    /**
525
     * Purge all of the cache stores of all of their data.
526
     *
527
     * Think twice before calling this method. It will purge **ALL** caches regardless of whether they have been used recently or
528
     * anything. This will involve full setup of the cache + the purge operation. On a site using caching heavily this WILL be
529
     * painful.
530
     *
531
     * @param bool $usewriter If set to true the cache_config_writer class is used. This class is special as it avoids
532
     *      it is still usable when caches have been disabled.
533
     *      Please use this option only if you really must. It's purpose is to allow the cache to be purged when it would be
534
     *      otherwise impossible.
535
     */
536
    public static function purge_all($usewriter = false) {
537
        $factory = cache_factory::instance();
538
        $config = $factory->create_config_instance($usewriter);
539
        foreach ($config->get_all_stores() as $store) {
540
            self::purge_store($store['name'], $config);
541
        }
542
        foreach ($factory->get_adhoc_caches_in_use() as $cache) {
543
            $cache->purge();
544
        }
545
    }
546
 
547
    /**
548
     * Purges a store given its name.
549
     *
550
     * @param string $storename
551
     * @param cache_config $config
552
     * @return bool
553
     */
554
    public static function purge_store($storename, cache_config $config = null) {
555
        if ($config === null) {
556
            $config = cache_config::instance();
557
        }
558
 
559
        $stores = $config->get_all_stores();
560
        if (!array_key_exists($storename, $stores)) {
561
            // The store does not exist.
562
            return false;
563
        }
564
 
565
        $store = $stores[$storename];
566
        $class = $store['class'];
567
 
568
 
569
        // We check are_requirements_met although we expect is_ready is going to check as well.
570
        if (!$class::are_requirements_met()) {
571
            return false;
572
        }
573
        // Found the store: is it ready?
574
        /* @var cache_store $instance */
575
        $instance = new $class($store['name'], $store['configuration']);
576
        if (!$instance->is_ready()) {
577
            unset($instance);
578
            return false;
579
        }
580
        foreach ($config->get_definitions_by_store($storename) as $id => $definition) {
581
            $definition = cache_definition::load($id, $definition);
582
            $definitioninstance = clone($instance);
583
            $definitioninstance->initialise($definition);
584
            $definitioninstance->purge();
585
            unset($definitioninstance);
586
        }
587
 
588
        return true;
589
    }
590
 
591
    /**
592
     * Purges all of the stores used by a definition.
593
     *
594
     * Unlike cache_helper::purge_by_definition this purges all of the data from the stores not
595
     * just the data relating to the definition.
596
     * This function is useful when you must purge a definition that requires setup but you don't
597
     * want to set it up.
598
     *
599
     * @param string $component
600
     * @param string $area
601
     */
602
    public static function purge_stores_used_by_definition($component, $area) {
603
        $factory = cache_factory::instance();
604
        $config = $factory->create_config_instance();
605
        $definition = $factory->create_definition($component, $area);
606
        $stores = $config->get_stores_for_definition($definition);
607
        foreach ($stores as $store) {
608
            self::purge_store($store['name']);
609
        }
610
    }
611
 
612
    /**
613
     * Returns the translated name of the definition.
614
     *
615
     * @param cache_definition $definition
616
     * @return lang_string
617
     */
618
    public static function get_definition_name($definition) {
619
        if ($definition instanceof cache_definition) {
620
            return $definition->get_name();
621
        }
622
        $identifier = 'cachedef_'.clean_param($definition['area'], PARAM_STRINGID);
623
        $component = $definition['component'];
624
        if ($component === 'core') {
625
            $component = 'cache';
626
        }
627
        return new lang_string($identifier, $component);
628
    }
629
 
630
    /**
631
     * Hashes a descriptive key to make it shorter and still unique.
632
     * @param string|int $key
633
     * @param cache_definition $definition
634
     * @return string
635
     */
636
    public static function hash_key($key, cache_definition $definition) {
637
        if ($definition->uses_simple_keys()) {
638
            if (debugging() && preg_match('#[^a-zA-Z0-9_]#', $key ?? '')) {
639
                throw new coding_exception('Cache definition '.$definition->get_id().' requires simple keys. Invalid key provided.', $key);
640
            }
641
            // We put the key first so that we can be sure the start of the key changes.
642
            return (string)$key . '-' . $definition->generate_single_key_prefix();
643
        }
644
        $key = $definition->generate_single_key_prefix() . '-' . $key;
645
        return sha1($key);
646
    }
647
 
648
    /**
649
     * Finds all definitions and updates them within the cache config file.
650
     *
651
     * @param bool $coreonly If set to true only core definitions will be updated.
652
     */
653
    public static function update_definitions($coreonly = false) {
654
        global $CFG;
655
        // Include locallib.
656
        require_once($CFG->dirroot.'/cache/locallib.php');
657
        // First update definitions
658
        cache_config_writer::update_definitions($coreonly);
659
        // Second reset anything we have already initialised to ensure we're all up to date.
660
        cache_factory::reset();
661
    }
662
 
663
    /**
664
     * Update the site identifier stored by the cache API.
665
     *
666
     * @param string $siteidentifier
667
     * @return string The new site identifier.
668
     */
669
    public static function update_site_identifier($siteidentifier) {
670
        global $CFG;
671
        // Include locallib.
672
        require_once($CFG->dirroot.'/cache/locallib.php');
673
        $factory = cache_factory::instance();
674
        $factory->updating_started();
675
        $config = $factory->create_config_instance(true);
676
        $siteidentifier = $config->update_site_identifier($siteidentifier);
677
        $factory->updating_finished();
678
        cache_factory::reset();
679
        return $siteidentifier;
680
    }
681
 
682
    /**
683
     * Returns the site identifier.
684
     *
685
     * @return string
686
     */
687
    public static function get_site_identifier() {
688
        global $CFG;
689
        if (!is_null(self::$siteidentifier)) {
690
            return self::$siteidentifier;
691
        }
692
        // If site identifier hasn't been collected yet attempt to get it from the cache config.
693
        $factory = cache_factory::instance();
694
        // If the factory is initialising then we don't want to try to get it from the config or we risk
695
        // causing the cache to enter an infinite initialisation loop.
696
        if (!$factory->is_initialising()) {
697
            $config = $factory->create_config_instance();
698
            self::$siteidentifier = $config->get_site_identifier();
699
        }
700
        if (is_null(self::$siteidentifier)) {
701
            // If the site identifier is still null then config isn't aware of it yet.
702
            // We'll see if the CFG is loaded, and if not we will just use unknown.
703
            // It's very important here that we don't use get_config. We don't want an endless cache loop!
704
            if (!empty($CFG->siteidentifier)) {
705
                self::$siteidentifier = self::update_site_identifier($CFG->siteidentifier);
706
            } else {
707
                // It's not being recorded in MUC's config and the config data hasn't been loaded yet.
708
                // Likely we are initialising.
709
                return 'unknown';
710
            }
711
        }
712
        return self::$siteidentifier;
713
    }
714
 
715
    /**
716
     * Returns the site version.
717
     *
718
     * @return string
719
     */
720
    public static function get_site_version() {
721
        global $CFG;
722
        return (string)$CFG->version;
723
    }
724
 
725
    /**
726
     * Runs cron routines for MUC.
727
     */
728
    public static function cron() {
729
        self::clean_old_session_data(true);
730
    }
731
 
732
    /**
733
     * Cleans old session data from cache stores used for session based definitions.
734
     *
735
     * @param bool $output If set to true output will be given.
736
     */
737
    public static function clean_old_session_data($output = false) {
738
        global $CFG;
739
        if ($output) {
740
            mtrace('Cleaning up stale session data from cache stores.');
741
        }
742
        $factory = cache_factory::instance();
743
        $config = $factory->create_config_instance();
744
        $definitions = $config->get_definitions();
745
        $purgetime = time() - $CFG->sessiontimeout;
746
        foreach ($definitions as $definitionarray) {
747
            // We are only interested in session caches.
748
            if (!($definitionarray['mode'] & cache_store::MODE_SESSION)) {
749
                continue;
750
            }
751
            $definition = $factory->create_definition($definitionarray['component'], $definitionarray['area']);
752
            $stores = $config->get_stores_for_definition($definition);
753
            // Turn them into store instances.
754
            $stores = self::initialise_cachestore_instances($stores, $definition);
755
            // Initialise all of the stores used for that definition.
756
            foreach ($stores as $store) {
757
                // If the store doesn't support searching we can skip it.
758
                if (!($store instanceof cache_is_searchable)) {
759
                    debugging('Cache stores used for session definitions should ideally be searchable.', DEBUG_DEVELOPER);
760
                    continue;
761
                }
762
                // Get all of the keys.
763
                $keys = $store->find_by_prefix(cache_session::KEY_PREFIX);
764
                $todelete = array();
765
                foreach ($store->get_many($keys) as $key => $value) {
766
                    if (strpos($key, cache_session::KEY_PREFIX) !== 0 || !is_array($value) || !isset($value['lastaccess'])) {
767
                        continue;
768
                    }
769
                    if ((int)$value['lastaccess'] < $purgetime || true) {
770
                        $todelete[] = $key;
771
                    }
772
                }
773
                if (count($todelete)) {
774
                    $outcome = (int)$store->delete_many($todelete);
775
                    if ($output) {
776
                        $strdef = s($definition->get_id());
777
                        $strstore = s($store->my_name());
778
                        mtrace("- Removed {$outcome} old {$strdef} sessions from the '{$strstore}' cache store.");
779
                    }
780
                }
781
            }
782
        }
783
    }
784
 
785
    /**
786
     * Returns an array of stores that would meet the requirements for every definition.
787
     *
788
     * These stores would be 100% suitable to map as defaults for cache modes.
789
     *
790
     * @return array[] An array of stores, keys are the store names.
791
     */
792
    public static function get_stores_suitable_for_mode_default() {
793
        $factory = cache_factory::instance();
794
        $config = $factory->create_config_instance();
795
        $requirements = 0;
796
        foreach ($config->get_definitions() as $definition) {
797
            $definition = cache_definition::load($definition['component'].'/'.$definition['area'], $definition);
798
            $requirements = $requirements | $definition->get_requirements_bin();
799
        }
800
        $stores = array();
801
        foreach ($config->get_all_stores() as $name => $store) {
802
            if (!empty($store['features']) && ($store['features'] & $requirements)) {
803
                $stores[$name] = $store;
804
            }
805
        }
806
        return $stores;
807
    }
808
 
809
    /**
810
     * Returns stores suitable for use with a given definition.
811
     *
812
     * @param cache_definition $definition
813
     * @return cache_store[]
814
     */
815
    public static function get_stores_suitable_for_definition(cache_definition $definition) {
816
        $factory = cache_factory::instance();
817
        $stores = array();
818
        if ($factory->is_initialising() || $factory->stores_disabled()) {
819
            // No suitable stores here.
820
            return $stores;
821
        } else {
822
            $stores = self::get_cache_stores($definition);
823
            // If mappingsonly is set, having 0 stores is ok.
824
            if ((count($stores) === 0) && (!$definition->is_for_mappings_only())) {
825
                // No suitable stores we found for the definition. We need to come up with a sensible default.
826
                // If this has happened we can be sure that the user has mapped custom stores to either the
827
                // mode of the definition. The first alternative to try is the system default for the mode.
828
                // e.g. the default file store instance for application definitions.
829
                $config = $factory->create_config_instance();
830
                foreach ($config->get_stores($definition->get_mode()) as $name => $details) {
831
                    if (!empty($details['default'])) {
832
                        $stores[] = $factory->create_store_from_config($name, $details, $definition);
833
                        break;
834
                    }
835
                }
836
            }
837
        }
838
        return $stores;
839
    }
840
 
841
    /**
842
     * Returns an array of warnings from the cache API.
843
     *
844
     * The warning returned here are for things like conflicting store instance configurations etc.
845
     * These get shown on the admin notifications page for example.
846
     *
847
     * @param array|null $stores An array of stores to get warnings for, or null for all.
848
     * @return string[]
849
     */
850
    public static function warnings(array $stores = null) {
851
        global $CFG;
852
        if ($stores === null) {
853
            require_once($CFG->dirroot.'/cache/locallib.php');
854
            $stores = core_cache\administration_helper::get_store_instance_summaries();
855
        }
856
        $warnings = array();
857
        foreach ($stores as $store) {
858
            if (!empty($store['warnings'])) {
859
                $warnings = array_merge($warnings, $store['warnings']);
860
            }
861
        }
862
        return $warnings;
863
    }
864
 
865
    /**
866
     * A helper to determine whether a result was found.
867
     *
868
     * This has been deemed required after people have been confused by the fact that [] == false.
869
     *
870
     * @param mixed $value
871
     * @return bool
872
     */
873
    public static function result_found($value): bool {
874
        return $value !== false;
875
    }
876
 
877
    /**
878
     * Checks whether the cluster mode is available in PHP.
879
     *
880
     * @return bool Return true if the PHP supports redis cluster, otherwise false.
881
     */
882
    public static function is_cluster_available(): bool {
883
        return class_exists('RedisCluster');
884
    }
885
}