Proyectos de Subversion Moodle

Rev

Rev 11 | | Comparar con el anterior | 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
 
1441 ariadna 17
namespace core_cache;
1 efrain 18
 
1441 ariadna 19
use core\lang_string;
20
use core\exception\coding_exception;
21
use DirectoryIterator;
1 efrain 22
 
23
/**
24
 * The cache helper class.
25
 *
26
 * The cache helper class provides common functionality to the cache API and is useful to developers within to interact with
27
 * the cache API in a general way.
28
 *
1441 ariadna 29
 * @package    core_cache
1 efrain 30
 * @category   cache
31
 * @copyright  2012 Sam Hemelryk
32
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
33
 */
1441 ariadna 34
class helper {
1 efrain 35
    /**
36
     * Statistics gathered by the cache API during its operation will be used here.
37
     * @static
38
     * @var array
39
     */
1441 ariadna 40
    protected static $stats = [];
1 efrain 41
 
42
    /**
43
     * The instance of the cache helper.
1441 ariadna 44
     * @var self
1 efrain 45
     */
46
    protected static $instance;
47
 
48
    /**
49
     * The site identifier used by the cache.
50
     * Set the first time get_site_identifier is called.
51
     * @var string
52
     */
53
    protected static $siteidentifier = null;
54
 
55
    /**
56
     * Returns true if the cache API can be initialised before Moodle has finished initialising itself.
57
     *
58
     * This check is essential when trying to cache the likes of configuration information. It checks to make sure that the cache
59
     * configuration file has been created which allows use to set up caching when ever is required.
60
     *
61
     * @return bool
62
     */
63
    public static function ready_for_early_init() {
1441 ariadna 64
        return config::config_file_exists();
1 efrain 65
    }
66
 
67
    /**
1441 ariadna 68
     * Returns an instance of the helper.
1 efrain 69
     *
70
     * This is designed for internal use only and acts as a static store.
71
     * @staticvar null $instance
1441 ariadna 72
     * @return self
1 efrain 73
     */
74
    protected static function instance() {
75
        if (is_null(self::$instance)) {
1441 ariadna 76
            self::$instance = new self();
1 efrain 77
        }
78
        return self::$instance;
79
    }
80
 
81
    /**
1441 ariadna 82
     * Constructs an instance of the helper class. Again for internal use only.
1 efrain 83
     */
84
    protected function __construct() {
85
        // Nothing to do here, just making sure you can't get an instance of this.
86
    }
87
 
88
    /**
89
     * Used as a data store for initialised definitions.
90
     * @var array
91
     */
1441 ariadna 92
    protected $definitions = [];
1 efrain 93
 
94
    /**
95
     * Used as a data store for initialised cache stores
96
     * We use this because we want to avoid establishing multiple instances of a single store.
97
     * @var array
98
     */
1441 ariadna 99
    protected $stores = [];
1 efrain 100
 
101
    /**
102
     * Returns the class for use as a cache loader for the given mode.
103
     *
1441 ariadna 104
     * @param int $mode One of store::MODE_
1 efrain 105
     * @return string
106
     * @throws coding_exception
107
     */
108
    public static function get_class_for_mode($mode) {
109
        switch ($mode) {
1441 ariadna 110
            case store::MODE_APPLICATION:
111
                return application_cache::class;
112
            case store::MODE_REQUEST:
113
                return request_cache::class;
114
            case store::MODE_SESSION:
115
                return session_cache::class;
1 efrain 116
        }
1441 ariadna 117
        throw new coding_exception('Unknown cache mode passed. Must be one of store::MODE_*');
1 efrain 118
    }
119
 
120
    /**
121
     * Returns the cache stores to be used with the given definition.
1441 ariadna 122
     * @param definition $definition
1 efrain 123
     * @return array
124
     */
1441 ariadna 125
    public static function get_cache_stores(definition $definition) {
126
        $instance = config::instance();
1 efrain 127
        $stores = $instance->get_stores_for_definition($definition);
128
        $stores = self::initialise_cachestore_instances($stores, $definition);
129
        return $stores;
130
    }
131
 
132
    /**
133
     * Internal function for initialising an array of stores against a given cache definition.
134
     *
135
     * @param array $stores
1441 ariadna 136
     * @param definition $definition
137
     * @return store[]
1 efrain 138
     */
1441 ariadna 139
    protected static function initialise_cachestore_instances(array $stores, definition $definition) {
140
        $return = [];
141
        $factory = factory::instance();
1 efrain 142
        foreach ($stores as $name => $details) {
143
            $store = $factory->create_store_from_config($name, $details, $definition);
144
            if ($store !== false) {
145
                $return[] = $store;
146
            }
147
        }
148
        return $return;
149
    }
150
 
151
    /**
1441 ariadna 152
     * Returns a locakable_cache_interface instance suitable for use with the store.
1 efrain 153
     *
1441 ariadna 154
     * @param store $store
155
     * @return lockable_cache_interface
1 efrain 156
     */
1441 ariadna 157
    public static function get_cachelock_for_store(store $store) {
158
        $instance = config::instance();
1 efrain 159
        $lockconf = $instance->get_lock_for_store($store->my_name());
1441 ariadna 160
        $factory = factory::instance();
1 efrain 161
        return $factory->create_lock_instance($lockconf);
162
    }
163
 
164
    /**
165
     * Returns an array of plugins without using core methods.
166
     *
167
     * This function explicitly does NOT use core functions as it will in some circumstances be called before Moodle has
168
     * finished initialising. This happens when loading configuration for instance.
169
     *
170
     * @return array
171
     */
172
    public static function early_get_cache_plugins() {
173
        global $CFG;
1441 ariadna 174
        $result = [];
175
        $ignored = ['CVS', '_vti_cnf', 'simpletest', 'db', 'yui', 'tests'];
176
        $fulldir = $CFG->dirroot . '/cache/stores';
1 efrain 177
        $items = new DirectoryIterator($fulldir);
178
        foreach ($items as $item) {
179
            if ($item->isDot() or !$item->isDir()) {
180
                continue;
181
            }
182
            $pluginname = $item->getFilename();
183
            if (in_array($pluginname, $ignored)) {
184
                continue;
185
            }
186
            if (!is_valid_plugin_name($pluginname)) {
187
                // Better ignore plugins with problematic names here.
188
                continue;
189
            }
1441 ariadna 190
            $result[$pluginname] = $fulldir . '/' . $pluginname;
1 efrain 191
            unset($item);
192
        }
193
        unset($items);
194
        return $result;
195
    }
196
 
197
    /**
198
     * Invalidates a given set of keys from a given definition.
199
     *
200
     * @todo Invalidating by definition should also add to the event cache so that sessions can be invalidated (when required).
201
     *
202
     * @param string $component
203
     * @param string $area
204
     * @param array $identifiers
205
     * @param array|string|int $keys
206
     * @return boolean
207
     * @throws coding_exception
208
     */
1441 ariadna 209
    public static function invalidate_by_definition($component, $area, array $identifiers = [], $keys = []) {
1 efrain 210
        $cache = cache::make($component, $area, $identifiers);
211
        if (is_array($keys)) {
212
            $cache->delete_many($keys);
213
        } else if (is_scalar($keys)) {
214
            $cache->delete($keys);
215
        } else {
1441 ariadna 216
            throw new coding_exception('helper::invalidate_by_definition only accepts $keys as array, or scalar.');
1 efrain 217
        }
218
        return true;
219
    }
220
 
221
    /**
222
     * Invalidates a given set of keys by means of an event.
223
     *
224
     * Events cannot determine what identifiers might need to be cleared. Event based purge and invalidation
225
     * are only supported on caches without identifiers.
226
     *
227
     * @param string $event
228
     * @param array $keys
229
     */
230
    public static function invalidate_by_event($event, array $keys) {
1441 ariadna 231
        $instance = config::instance();
1 efrain 232
        $invalidationeventset = false;
1441 ariadna 233
        $factory = factory::instance();
1 efrain 234
        $inuse = $factory->get_caches_in_use();
235
        $purgetoken = null;
236
        foreach ($instance->get_definitions() as $name => $definitionarr) {
1441 ariadna 237
            $definition = definition::load($name, $definitionarr);
1 efrain 238
            if ($definition->invalidates_on_event($event)) {
239
                // First up check if there is a cache loader for this definition already.
240
                // If there is we need to invalidate the keys from there.
1441 ariadna 241
                $definitionkey = $definition->get_component() . '/' . $definition->get_area();
1 efrain 242
                if (isset($inuse[$definitionkey])) {
243
                    $inuse[$definitionkey]->delete_many($keys);
244
                }
245
 
246
                // We should only log events for application and session caches.
247
                // Request caches shouldn't have events as all data is lost at the end of the request.
248
                // Events should only be logged once of course and likely several definitions are watching so we
249
                // track its logging with $invalidationeventset.
1441 ariadna 250
                $logevent = ($invalidationeventset === false && $definition->get_mode() !== store::MODE_REQUEST);
1 efrain 251
 
252
                if ($logevent) {
253
                    // Get the event invalidation cache.
254
                    $cache = cache::make('core', 'eventinvalidation');
255
                    // Get any existing invalidated keys for this cache.
256
                    $data = $cache->get($event);
257
                    if ($data === false) {
258
                        // There are none.
1441 ariadna 259
                        $data = [];
1 efrain 260
                    }
261
                    // Add our keys to them with the current cache timestamp.
262
                    if (null === $purgetoken) {
263
                        $purgetoken = cache::get_purge_token(true);
264
                    }
265
                    foreach ($keys as $key) {
266
                        $data[$key] = $purgetoken;
267
                    }
268
                    // Set that data back to the cache.
269
                    $cache->set($event, $data);
270
                    // This only needs to occur once.
271
                    $invalidationeventset = true;
272
                }
273
            }
274
        }
275
    }
276
 
277
    /**
278
     * Purges the cache for a specific definition.
279
     *
280
     * @param string $component
281
     * @param string $area
282
     * @param array $identifiers
283
     * @return bool
284
     */
1441 ariadna 285
    public static function purge_by_definition($component, $area, array $identifiers = []) {
1 efrain 286
        // Create the cache.
287
        $cache = cache::make($component, $area, $identifiers);
288
        // Initialise, in case of a store.
1441 ariadna 289
        if ($cache instanceof store) {
290
            $factory = factory::instance();
1 efrain 291
            $definition = $factory->create_definition($component, $area, null);
292
            $cacheddefinition = clone $definition;
293
            $cacheddefinition->set_identifiers($identifiers);
294
            $cache->initialise($cacheddefinition);
295
        }
296
        // Purge baby, purge.
297
        $cache->purge();
298
        return true;
299
    }
300
 
301
    /**
302
     * Purges a cache of all information on a given event.
303
     *
304
     * Events cannot determine what identifiers might need to be cleared. Event based purge and invalidation
305
     * are only supported on caches without identifiers.
306
     *
307
     * @param string $event
308
     */
309
    public static function purge_by_event($event) {
1441 ariadna 310
        $instance = config::instance();
1 efrain 311
        $invalidationeventset = false;
1441 ariadna 312
        $factory = factory::instance();
1 efrain 313
        $inuse = $factory->get_caches_in_use();
314
        $purgetoken = null;
315
        foreach ($instance->get_definitions() as $name => $definitionarr) {
1441 ariadna 316
            $definition = definition::load($name, $definitionarr);
1 efrain 317
            if ($definition->invalidates_on_event($event)) {
318
                // First up check if there is a cache loader for this definition already.
319
                // If there is we need to invalidate the keys from there.
1441 ariadna 320
                $definitionkey = $definition->get_component() . '/' . $definition->get_area();
1 efrain 321
                if (isset($inuse[$definitionkey])) {
322
                    $inuse[$definitionkey]->purge();
323
                } else {
324
                    cache::make($definition->get_component(), $definition->get_area())->purge();
325
                }
326
 
327
                // We should only log events for application and session caches.
328
                // Request caches shouldn't have events as all data is lost at the end of the request.
329
                // Events should only be logged once of course and likely several definitions are watching so we
330
                // track its logging with $invalidationeventset.
1441 ariadna 331
                $logevent = ($invalidationeventset === false && $definition->get_mode() !== store::MODE_REQUEST);
1 efrain 332
 
333
                // We need to flag the event in the "Event invalidation" cache if it hasn't already happened.
334
                if ($logevent && $invalidationeventset === false) {
335
                    // Get the event invalidation cache.
336
                    $cache = cache::make('core', 'eventinvalidation');
337
                    // Create a key to invalidate all.
338
                    if (null === $purgetoken) {
339
                        $purgetoken = cache::get_purge_token(true);
340
                    }
1441 ariadna 341
                    $data = [
1 efrain 342
                        'purged' => $purgetoken,
1441 ariadna 343
                    ];
1 efrain 344
                    // Set that data back to the cache.
345
                    $cache->set($event, $data);
346
                    // This only needs to occur once.
347
                    $invalidationeventset = true;
348
                }
349
            }
350
        }
351
    }
352
 
353
    /**
354
     * Ensure that the stats array is ready to collect information for the given store and definition.
355
     * @param string $store
356
     * @param string $storeclass
357
     * @param string $definition A string that identifies the definition.
1441 ariadna 358
     * @param int $mode One of store::MODE_*. Since 2.9.
1 efrain 359
     */
1441 ariadna 360
    protected static function ensure_ready_for_stats($store, $storeclass, $definition, $mode = store::MODE_APPLICATION) {
1 efrain 361
        // This function is performance-sensitive, so exit as quickly as possible
362
        // if we do not need to do anything.
363
        if (isset(self::$stats[$definition]['stores'][$store])) {
364
            return;
365
        }
366
 
367
        if (!array_key_exists($definition, self::$stats)) {
1441 ariadna 368
            self::$stats[$definition] = [
1 efrain 369
                'mode' => $mode,
1441 ariadna 370
                'stores' => [
371
                    $store => [
1 efrain 372
                        'class' => $storeclass,
373
                        'hits' => 0,
374
                        'misses' => 0,
375
                        'sets' => 0,
1441 ariadna 376
                        'iobytes' => store::IO_BYTES_NOT_SUPPORTED,
1 efrain 377
                        'locks' => 0,
1441 ariadna 378
                    ],
379
                ],
380
            ];
1 efrain 381
        } else if (!array_key_exists($store, self::$stats[$definition]['stores'])) {
1441 ariadna 382
            self::$stats[$definition]['stores'][$store] = [
1 efrain 383
                'class' => $storeclass,
384
                'hits' => 0,
385
                'misses' => 0,
386
                'sets' => 0,
1441 ariadna 387
                'iobytes' => store::IO_BYTES_NOT_SUPPORTED,
1 efrain 388
                'locks' => 0,
1441 ariadna 389
            ];
1 efrain 390
        }
391
    }
392
 
393
    /**
394
     * Returns a string to describe the definition.
395
     *
396
     * This method supports the definition as a string due to legacy requirements.
397
     * It is backwards compatible when a string is passed but is not accurate.
398
     *
399
     * @since 2.9
1441 ariadna 400
     * @param definition|string $definition
1 efrain 401
     * @return string
402
     */
403
    protected static function get_definition_stat_id_and_mode($definition) {
1441 ariadna 404
        if (!($definition instanceof definition)) {
1 efrain 405
            // All core calls to this method have been updated, this is the legacy state.
406
            // We'll use application as the default as that is the most common, really this is not accurate of course but
407
            // at this point we can only guess and as it only affects calls to cache stat outside of core (of which there should
408
            // be none) I think that is fine.
409
            debugging('Please update you cache stat calls to pass the definition rather than just its ID.', DEBUG_DEVELOPER);
1441 ariadna 410
            return [(string)$definition, store::MODE_APPLICATION];
1 efrain 411
        }
1441 ariadna 412
        return [$definition->get_id(), $definition->get_mode()];
1 efrain 413
    }
414
 
415
    /**
416
     * Record a cache hit in the stats for the given store and definition.
417
     *
418
     * In Moodle 2.9 the $definition argument changed from accepting only a string to accepting a string or a
1441 ariadna 419
     * definition instance. It is preferable to pass a cache definition instance.
1 efrain 420
     *
1441 ariadna 421
     * In Moodle 3.9 the first argument changed to also accept a store.
1 efrain 422
     *
423
     * @internal
1441 ariadna 424
     * @param string|store $store
425
     * @param definition $definition You used to be able to pass a string here, however that is deprecated please pass the
426
     *      actual definition object now.
1 efrain 427
     * @param int $hits The number of hits to record (by default 1)
1441 ariadna 428
     * @param int $readbytes Number of bytes read from the cache or store::IO_BYTES_NOT_SUPPORTED
1 efrain 429
     */
1441 ariadna 430
    public static function record_cache_hit(
431
        $store,
432
        $definition,
433
        int $hits = 1,
434
        int $readbytes = store::IO_BYTES_NOT_SUPPORTED,
435
    ): void {
1 efrain 436
        $storeclass = '';
1441 ariadna 437
        if ($store instanceof store) {
1 efrain 438
            $storeclass = get_class($store);
439
            $store = $store->my_name();
440
        }
1441 ariadna 441
        [$definitionstr, $mode] = self::get_definition_stat_id_and_mode($definition);
1 efrain 442
        self::ensure_ready_for_stats($store, $storeclass, $definitionstr, $mode);
443
        self::$stats[$definitionstr]['stores'][$store]['hits'] += $hits;
1441 ariadna 444
        if ($readbytes !== store::IO_BYTES_NOT_SUPPORTED) {
445
            if (self::$stats[$definitionstr]['stores'][$store]['iobytes'] === store::IO_BYTES_NOT_SUPPORTED) {
1 efrain 446
                self::$stats[$definitionstr]['stores'][$store]['iobytes'] = $readbytes;
447
            } else {
448
                self::$stats[$definitionstr]['stores'][$store]['iobytes'] += $readbytes;
449
            }
450
        }
451
    }
452
 
453
    /**
454
     * Record a cache miss in the stats for the given store and definition.
455
     *
456
     * In Moodle 2.9 the $definition argument changed from accepting only a string to accepting a string or a
1441 ariadna 457
     * definition instance. It is preferable to pass a cache definition instance.
1 efrain 458
     *
1441 ariadna 459
     * In Moodle 3.9 the first argument changed to also accept a store.
1 efrain 460
     *
461
     * @internal
1441 ariadna 462
     * @param string|store $store
463
     * @param definition $definition You used to be able to pass a string here, however that is deprecated please pass the
464
     *      actual definition object now.
1 efrain 465
     * @param int $misses The number of misses to record (by default 1)
466
     */
467
    public static function record_cache_miss($store, $definition, $misses = 1) {
468
        $storeclass = '';
1441 ariadna 469
        if ($store instanceof store) {
1 efrain 470
            $storeclass = get_class($store);
471
            $store = $store->my_name();
472
        }
1441 ariadna 473
        [$definitionstr, $mode] = self::get_definition_stat_id_and_mode($definition);
1 efrain 474
        self::ensure_ready_for_stats($store, $storeclass, $definitionstr, $mode);
475
        self::$stats[$definitionstr]['stores'][$store]['misses'] += $misses;
476
    }
477
 
478
    /**
479
     * Record a cache set in the stats for the given store and definition.
480
     *
481
     * In Moodle 2.9 the $definition argument changed from accepting only a string to accepting a string or a
1441 ariadna 482
     * definition instance. It is preferable to pass a cache definition instance.
1 efrain 483
     *
1441 ariadna 484
     * In Moodle 3.9 the first argument changed to also accept a store.
1 efrain 485
     *
486
     * @internal
1441 ariadna 487
     * @param string|store $store
488
     * @param definition $definition You used to be able to pass a string here, however that is deprecated please pass the
489
     *      actual definition object now.
1 efrain 490
     * @param int $sets The number of sets to record (by default 1)
1441 ariadna 491
     * @param int $writebytes Number of bytes written to the cache or store::IO_BYTES_NOT_SUPPORTED
1 efrain 492
     */
1441 ariadna 493
    public static function record_cache_set(
494
        $store,
495
        $definition,
496
        int $sets = 1,
497
        int $writebytes = store::IO_BYTES_NOT_SUPPORTED
498
    ) {
1 efrain 499
        $storeclass = '';
1441 ariadna 500
        if ($store instanceof store) {
1 efrain 501
            $storeclass = get_class($store);
502
            $store = $store->my_name();
503
        }
1441 ariadna 504
        [$definitionstr, $mode] = self::get_definition_stat_id_and_mode($definition);
1 efrain 505
        self::ensure_ready_for_stats($store, $storeclass, $definitionstr, $mode);
506
        self::$stats[$definitionstr]['stores'][$store]['sets'] += $sets;
1441 ariadna 507
        if ($writebytes !== store::IO_BYTES_NOT_SUPPORTED) {
508
            if (self::$stats[$definitionstr]['stores'][$store]['iobytes'] === store::IO_BYTES_NOT_SUPPORTED) {
1 efrain 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
     *
1441 ariadna 531
     * @param bool $usewriter If set to true the config_writer class is used. This class is special as it avoids
1 efrain 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) {
1441 ariadna 537
        $factory = factory::instance();
1 efrain 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
1441 ariadna 551
     * @param config|null $config
1 efrain 552
     * @return bool
553
     */
1441 ariadna 554
    public static function purge_store($storename, ?config $config = null) {
1 efrain 555
        if ($config === null) {
1441 ariadna 556
            $config = config::instance();
1 efrain 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?
1441 ariadna 574
        /* @var store $instance */
1 efrain 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) {
1441 ariadna 581
            $definition = definition::load($id, $definition);
1 efrain 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
     *
1441 ariadna 594
     * Unlike helper::purge_by_definition this purges all of the data from the stores not
1 efrain 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) {
1441 ariadna 603
        $factory = factory::instance();
1 efrain 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
     *
1441 ariadna 615
     * @param definition $definition
1 efrain 616
     * @return lang_string
617
     */
618
    public static function get_definition_name($definition) {
1441 ariadna 619
        if ($definition instanceof definition) {
1 efrain 620
            return $definition->get_name();
621
        }
1441 ariadna 622
        $identifier = 'cachedef_' . clean_param($definition['area'], PARAM_STRINGID);
1 efrain 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
1441 ariadna 633
     * @param definition $definition
1 efrain 634
     * @return string
635
     */
1441 ariadna 636
    public static function hash_key($key, definition $definition) {
1 efrain 637
        if ($definition->uses_simple_keys()) {
638
            if (debugging() && preg_match('#[^a-zA-Z0-9_]#', $key ?? '')) {
1441 ariadna 639
                throw new coding_exception(
640
                    'Cache definition ' . $definition->get_id() . ' requires simple keys. Invalid key provided.',
641
                    $key,
642
                );
1 efrain 643
            }
644
            // We put the key first so that we can be sure the start of the key changes.
645
            return (string)$key . '-' . $definition->generate_single_key_prefix();
646
        }
647
        $key = $definition->generate_single_key_prefix() . '-' . $key;
648
        return sha1($key);
649
    }
650
 
651
    /**
652
     * Finds all definitions and updates them within the cache config file.
653
     *
654
     * @param bool $coreonly If set to true only core definitions will be updated.
655
     */
656
    public static function update_definitions($coreonly = false) {
657
        // First update definitions
1441 ariadna 658
        config_writer::update_definitions($coreonly);
1 efrain 659
        // Second reset anything we have already initialised to ensure we're all up to date.
1441 ariadna 660
        factory::reset();
1 efrain 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) {
1441 ariadna 670
        $factory = factory::instance();
1 efrain 671
        $factory->updating_started();
672
        $config = $factory->create_config_instance(true);
673
        $siteidentifier = $config->update_site_identifier($siteidentifier);
674
        $factory->updating_finished();
1441 ariadna 675
        factory::reset();
1 efrain 676
        return $siteidentifier;
677
    }
678
 
679
    /**
680
     * Returns the site identifier.
681
     *
682
     * @return string
683
     */
684
    public static function get_site_identifier() {
685
        global $CFG;
686
        if (!is_null(self::$siteidentifier)) {
687
            return self::$siteidentifier;
688
        }
689
        // If site identifier hasn't been collected yet attempt to get it from the cache config.
1441 ariadna 690
        $factory = factory::instance();
1 efrain 691
        // If the factory is initialising then we don't want to try to get it from the config or we risk
692
        // causing the cache to enter an infinite initialisation loop.
693
        if (!$factory->is_initialising()) {
694
            $config = $factory->create_config_instance();
695
            self::$siteidentifier = $config->get_site_identifier();
696
        }
697
        if (is_null(self::$siteidentifier)) {
698
            // If the site identifier is still null then config isn't aware of it yet.
699
            // We'll see if the CFG is loaded, and if not we will just use unknown.
700
            // It's very important here that we don't use get_config. We don't want an endless cache loop!
701
            if (!empty($CFG->siteidentifier)) {
702
                self::$siteidentifier = self::update_site_identifier($CFG->siteidentifier);
703
            } else {
704
                // It's not being recorded in MUC's config and the config data hasn't been loaded yet.
705
                // Likely we are initialising.
706
                return 'unknown';
707
            }
708
        }
709
        return self::$siteidentifier;
710
    }
711
 
712
    /**
713
     * Returns the site version.
714
     *
715
     * @return string
716
     */
717
    public static function get_site_version() {
718
        global $CFG;
719
        return (string)$CFG->version;
720
    }
721
 
722
    /**
723
     * Runs cron routines for MUC.
724
     */
725
    public static function cron() {
726
        self::clean_old_session_data(true);
727
    }
728
 
729
    /**
730
     * Cleans old session data from cache stores used for session based definitions.
731
     *
732
     * @param bool $output If set to true output will be given.
733
     */
734
    public static function clean_old_session_data($output = false) {
735
        global $CFG;
736
        if ($output) {
737
            mtrace('Cleaning up stale session data from cache stores.');
738
        }
1441 ariadna 739
        $factory = factory::instance();
1 efrain 740
        $config = $factory->create_config_instance();
741
        $definitions = $config->get_definitions();
742
        $purgetime = time() - $CFG->sessiontimeout;
743
        foreach ($definitions as $definitionarray) {
744
            // We are only interested in session caches.
1441 ariadna 745
            if (!($definitionarray['mode'] & store::MODE_SESSION)) {
1 efrain 746
                continue;
747
            }
748
            $definition = $factory->create_definition($definitionarray['component'], $definitionarray['area']);
749
            $stores = $config->get_stores_for_definition($definition);
750
            // Turn them into store instances.
751
            $stores = self::initialise_cachestore_instances($stores, $definition);
752
            // Initialise all of the stores used for that definition.
753
            foreach ($stores as $store) {
754
                // If the store doesn't support searching we can skip it.
1441 ariadna 755
                if (!($store instanceof searchable_cache_interface)) {
1 efrain 756
                    debugging('Cache stores used for session definitions should ideally be searchable.', DEBUG_DEVELOPER);
757
                    continue;
758
                }
11 efrain 759
                // Get all of the last access keys.
1441 ariadna 760
                $keys = $store->find_by_prefix(session_cache::LASTACCESS);
11 efrain 761
                $todelete = [];
1 efrain 762
                foreach ($store->get_many($keys) as $key => $value) {
11 efrain 763
                    $expiresvalue = 0;
1441 ariadna 764
                    if ($value instanceof ttl_wrapper) {
11 efrain 765
                        $expiresvalue = $value->data;
1441 ariadna 766
                    } else if ($value instanceof cached_object) {
11 efrain 767
                        $expiresvalue = $value->restore_object();
768
                    } else {
769
                        $expiresvalue = $value;
1 efrain 770
                    }
11 efrain 771
                    $expires = (int) $expiresvalue;
772
 
773
                    if ($expires > 0 && $expires < $purgetime) {
1441 ariadna 774
                        $prefix = substr($key, strlen(session_cache::LASTACCESS));
11 efrain 775
                        $foundbyprefix = $store->find_by_prefix($prefix);
776
                        $todelete = array_merge($todelete, [$key], $foundbyprefix);
1 efrain 777
                    }
778
                }
11 efrain 779
                if ($todelete) {
1441 ariadna 780
                    $outcome = (int) $store->delete_many($todelete);
1 efrain 781
                    if ($output) {
782
                        $strdef = s($definition->get_id());
783
                        $strstore = s($store->my_name());
784
                        mtrace("- Removed {$outcome} old {$strdef} sessions from the '{$strstore}' cache store.");
785
                    }
786
                }
787
            }
788
        }
789
    }
790
 
791
    /**
792
     * Returns an array of stores that would meet the requirements for every definition.
793
     *
794
     * These stores would be 100% suitable to map as defaults for cache modes.
795
     *
796
     * @return array[] An array of stores, keys are the store names.
797
     */
798
    public static function get_stores_suitable_for_mode_default() {
1441 ariadna 799
        $factory = factory::instance();
1 efrain 800
        $config = $factory->create_config_instance();
801
        $requirements = 0;
802
        foreach ($config->get_definitions() as $definition) {
1441 ariadna 803
            $definition = definition::load($definition['component'] . '/' . $definition['area'], $definition);
1 efrain 804
            $requirements = $requirements | $definition->get_requirements_bin();
805
        }
1441 ariadna 806
        $stores = [];
1 efrain 807
        foreach ($config->get_all_stores() as $name => $store) {
808
            if (!empty($store['features']) && ($store['features'] & $requirements)) {
809
                $stores[$name] = $store;
810
            }
811
        }
812
        return $stores;
813
    }
814
 
815
    /**
816
     * Returns stores suitable for use with a given definition.
817
     *
1441 ariadna 818
     * @param definition $definition
819
     * @return store[]
1 efrain 820
     */
1441 ariadna 821
    public static function get_stores_suitable_for_definition(definition $definition) {
822
        $factory = factory::instance();
823
        $stores = [];
1 efrain 824
        if ($factory->is_initialising() || $factory->stores_disabled()) {
825
            // No suitable stores here.
826
            return $stores;
827
        } else {
828
            $stores = self::get_cache_stores($definition);
829
            // If mappingsonly is set, having 0 stores is ok.
830
            if ((count($stores) === 0) && (!$definition->is_for_mappings_only())) {
831
                // No suitable stores we found for the definition. We need to come up with a sensible default.
832
                // If this has happened we can be sure that the user has mapped custom stores to either the
833
                // mode of the definition. The first alternative to try is the system default for the mode.
834
                // e.g. the default file store instance for application definitions.
835
                $config = $factory->create_config_instance();
836
                foreach ($config->get_stores($definition->get_mode()) as $name => $details) {
837
                    if (!empty($details['default'])) {
838
                        $stores[] = $factory->create_store_from_config($name, $details, $definition);
839
                        break;
840
                    }
841
                }
842
            }
843
        }
844
        return $stores;
845
    }
846
 
847
    /**
848
     * Returns an array of warnings from the cache API.
849
     *
850
     * The warning returned here are for things like conflicting store instance configurations etc.
851
     * These get shown on the admin notifications page for example.
852
     *
853
     * @param array|null $stores An array of stores to get warnings for, or null for all.
854
     * @return string[]
855
     */
1441 ariadna 856
    public static function warnings(?array $stores = null) {
1 efrain 857
        if ($stores === null) {
1441 ariadna 858
            $stores = administration_helper::get_store_instance_summaries();
1 efrain 859
        }
1441 ariadna 860
        $warnings = [];
1 efrain 861
        foreach ($stores as $store) {
862
            if (!empty($store['warnings'])) {
863
                $warnings = array_merge($warnings, $store['warnings']);
864
            }
865
        }
866
        return $warnings;
867
    }
868
 
869
    /**
870
     * A helper to determine whether a result was found.
871
     *
872
     * This has been deemed required after people have been confused by the fact that [] == false.
873
     *
874
     * @param mixed $value
875
     * @return bool
876
     */
877
    public static function result_found($value): bool {
878
        return $value !== false;
879
    }
880
 
881
    /**
882
     * Checks whether the cluster mode is available in PHP.
883
     *
884
     * @return bool Return true if the PHP supports redis cluster, otherwise false.
885
     */
886
    public static function is_cluster_available(): bool {
887
        return class_exists('RedisCluster');
888
    }
889
}
1441 ariadna 890
 
891
// Alias this class to the old name.
892
// This file will be autoloaded by the legacyclasses autoload system.
893
// In future all uses of this class will be corrected and the legacy references will be removed.
894
class_alias(helper::class, \cache_helper::class);