Proyectos de Subversion Moodle

Rev

Rev 1 | | 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_cache\exception\cache_exception;
1 efrain 20
 
21
/**
22
 * Cache configuration reader.
23
 *
24
 * This class is used to interact with the cache's configuration.
25
 * The configuration is stored in the Moodle data directory.
26
 *
1441 ariadna 27
 * @package    core_cache
1 efrain 28
 * @category   cache
29
 * @copyright  2012 Sam Hemelryk
30
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
31
 */
1441 ariadna 32
class config {
1 efrain 33
    /**
34
     * The configured stores
35
     * @var array
36
     */
1441 ariadna 37
    protected $configstores = [];
1 efrain 38
 
39
    /**
40
     * The configured mode mappings
41
     * @var array
42
     */
1441 ariadna 43
    protected $configmodemappings = [];
1 efrain 44
 
45
    /**
46
     * The configured definitions as picked up from cache.php files
47
     * @var array
48
     */
1441 ariadna 49
    protected $configdefinitions = [];
1 efrain 50
 
51
    /**
52
     * The definition mappings that have been configured.
53
     * @var array
54
     */
1441 ariadna 55
    protected $configdefinitionmappings = [];
1 efrain 56
 
57
    /**
58
     * An array of configured cache lock instances.
59
     * @var array
60
     */
1441 ariadna 61
    protected $configlocks = [];
1 efrain 62
 
63
    /**
64
     * The site identifier used when the cache config was last saved.
65
     * @var string
66
     */
67
    protected $siteidentifier = null;
68
 
69
    /**
1441 ariadna 70
     * Please use config::instance to get an instance of the cache config that is ready to be used.
1 efrain 71
     */
72
    public function __construct() {
73
        // Nothing to do here but look pretty.
74
    }
75
 
76
    /**
1441 ariadna 77
     * Gets an instance of the cache config class.
1 efrain 78
     *
1441 ariadna 79
     * @return self
1 efrain 80
     */
81
    public static function instance() {
1441 ariadna 82
        $factory = factory::instance();
1 efrain 83
        return $factory->create_config_instance();
84
    }
85
 
86
    /**
87
     * Checks if the configuration file exists.
88
     *
89
     * @return bool True if it exists
90
     */
91
    public static function config_file_exists() {
92
        // Allow for late static binding by using static.
93
        return file_exists(static::get_config_file_path());
94
    }
95
 
96
    /**
97
     * Returns the expected path to the configuration file.
98
     *
99
     * @return string The absolute path
100
     */
101
    protected static function get_config_file_path() {
102
        global $CFG;
103
        if (!empty($CFG->altcacheconfigpath)) {
104
            $path = $CFG->altcacheconfigpath;
105
            if (is_dir($path) && is_writable($path)) {
106
                // Its a writable directory, thats fine.
1441 ariadna 107
                return $path . '/cacheconfig.php';
1 efrain 108
            } else if (is_writable(dirname($path)) && (!file_exists($path) || is_writable($path))) {
109
                // Its a file, either it doesn't exist and the directory is writable or the file exists and is writable.
110
                return $path;
111
            }
112
        }
113
        // Return the default location within dataroot.
1441 ariadna 114
        return $CFG->dataroot . '/muc/config.php';
1 efrain 115
    }
116
 
117
    /**
118
     * Loads the configuration file and parses its contents into the expected structure.
119
     *
120
     * @param array|false $configuration Can be used to force a configuration. Should only be used when truly required.
121
     * @return boolean
122
     */
123
    public function load($configuration = false) {
124
        global $CFG;
125
 
126
        if ($configuration === false) {
127
            $configuration = $this->include_configuration();
128
        }
129
 
1441 ariadna 130
        $this->configstores = [];
131
        $this->configdefinitions = [];
132
        $this->configlocks = [];
133
        $this->configmodemappings = [];
134
        $this->configdefinitionmappings = [];
1 efrain 135
 
136
        $siteidentifier = 'unknown';
137
        if (array_key_exists('siteidentifier', $configuration)) {
138
            $siteidentifier = $configuration['siteidentifier'];
139
        }
140
        $this->siteidentifier = $siteidentifier;
141
 
142
        // Filter the lock instances.
143
        $defaultlock = null;
144
        foreach ($configuration['locks'] as $conf) {
145
            if (!is_array($conf)) {
146
                // Something is very wrong here.
147
                continue;
148
            }
149
            if (!array_key_exists('name', $conf)) {
150
                // Not a valid definition configuration.
151
                continue;
152
            }
153
            $name = $conf['name'];
154
            if (array_key_exists($name, $this->configlocks)) {
155
                debugging('Duplicate cache lock detected. This should never happen.', DEBUG_DEVELOPER);
156
                continue;
157
            }
158
            $conf['default'] = (!empty($conf['default']));
159
            if ($defaultlock === null || $conf['default']) {
160
                $defaultlock = $name;
161
            }
162
            $this->configlocks[$name] = $conf;
163
        }
164
 
165
        // Filter the stores.
1441 ariadna 166
        $availableplugins = helper::early_get_cache_plugins();
1 efrain 167
        foreach ($configuration['stores'] as $store) {
168
            if (!is_array($store) || !array_key_exists('name', $store) || !array_key_exists('plugin', $store)) {
169
                // Not a valid instance configuration.
170
                debugging('Invalid cache store in config. Missing name or plugin.', DEBUG_DEVELOPER);
171
                continue;
172
            }
173
            $plugin = $store['plugin'];
1441 ariadna 174
            $class = 'cachestore_' . $plugin;
1 efrain 175
            $exists = array_key_exists($plugin, $availableplugins);
176
            if (!$exists) {
177
                // Not a valid plugin, or has been uninstalled, just skip it an carry on.
178
                debugging('Invalid cache store in config. Not an available plugin.', DEBUG_DEVELOPER);
179
                continue;
180
            }
1441 ariadna 181
            $file = $CFG->dirroot . '/cache/stores/' . $plugin . '/lib.php';
1 efrain 182
            if (!class_exists($class) && file_exists($file)) {
183
                require_once($file);
184
            }
185
            if (!class_exists($class)) {
186
                continue;
187
            }
1441 ariadna 188
            if (!array_key_exists(store::class, class_parents($class))) {
1 efrain 189
                continue;
190
            }
191
            if (!array_key_exists('configuration', $store) || !is_array($store['configuration'])) {
1441 ariadna 192
                $store['configuration'] = [];
1 efrain 193
            }
194
            $store['class'] = $class;
195
            $store['default'] = !empty($store['default']);
196
            if (!array_key_exists('lock', $store) || !array_key_exists($store['lock'], $this->configlocks)) {
197
                $store['lock'] = $defaultlock;
198
            }
199
 
200
            $this->configstores[$store['name']] = $store;
201
        }
202
 
203
        // Filter the definitions.
204
        foreach ($configuration['definitions'] as $id => $conf) {
205
            if (!is_array($conf)) {
206
                // Something is very wrong here.
207
                continue;
208
            }
209
            if (!array_key_exists('mode', $conf) || !array_key_exists('component', $conf) || !array_key_exists('area', $conf)) {
210
                // Not a valid definition configuration.
211
                continue;
212
            }
213
            if (array_key_exists($id, $this->configdefinitions)) {
214
                debugging('Duplicate cache definition detected. This should never happen.', DEBUG_DEVELOPER);
215
                continue;
216
            }
217
            $conf['mode'] = (int)$conf['mode'];
1441 ariadna 218
            if ($conf['mode'] < store::MODE_APPLICATION || $conf['mode'] > store::MODE_REQUEST) {
1 efrain 219
                // Invalid cache mode used for the definition.
220
                continue;
221
            }
1441 ariadna 222
            if ($conf['mode'] === store::MODE_SESSION || $conf['mode'] === store::MODE_REQUEST) {
1 efrain 223
                // We force this for session and request caches.
224
                // They are only allowed to use the default as we don't want people changing them.
1441 ariadna 225
                $conf['sharingoptions'] = definition::SHARING_DEFAULT;
226
                $conf['selectedsharingoption'] = definition::SHARING_DEFAULT;
1 efrain 227
                $conf['userinputsharingkey'] = '';
228
            } else {
229
                // Default the sharing option as it was added for 2.5.
230
                // This can be removed sometime after 2.5 is the minimum version someone can upgrade from.
231
                if (!isset($conf['sharingoptions'])) {
1441 ariadna 232
                    $conf['sharingoptions'] = definition::SHARING_DEFAULTOPTIONS;
1 efrain 233
                }
234
                // Default the selected sharing option as it was added for 2.5.
235
                // This can be removed sometime after 2.5 is the minimum version someone can upgrade from.
236
                if (!isset($conf['selectedsharingoption'])) {
1441 ariadna 237
                    $conf['selectedsharingoption'] = definition::SHARING_DEFAULT;
1 efrain 238
                }
239
                // Default the user input sharing key as it was added for 2.5.
240
                // This can be removed sometime after 2.5 is the minimum version someone can upgrade from.
241
                if (!isset($conf['userinputsharingkey'])) {
242
                    $conf['userinputsharingkey'] = '';
243
                }
244
            }
245
            $this->configdefinitions[$id] = $conf;
246
        }
247
 
248
        // Filter the mode mappings.
249
        foreach ($configuration['modemappings'] as $mapping) {
250
            if (!is_array($mapping) || !array_key_exists('mode', $mapping) || !array_key_exists('store', $mapping)) {
251
                // Not a valid mapping configuration.
252
                debugging('A cache mode mapping entry is invalid.', DEBUG_DEVELOPER);
253
                continue;
254
            }
255
            if (!array_key_exists($mapping['store'], $this->configstores)) {
256
                // Mapped array instance doesn't exist.
257
                debugging('A cache mode mapping exists for a mode or store that does not exist.', DEBUG_DEVELOPER);
258
                continue;
259
            }
260
            $mapping['mode'] = (int)$mapping['mode'];
261
            if ($mapping['mode'] < 0 || $mapping['mode'] > 4) {
262
                // Invalid cache type used for the mapping.
263
                continue;
264
            }
265
            if (!array_key_exists('sort', $mapping)) {
266
                $mapping['sort'] = 0;
267
            }
268
            $this->configmodemappings[] = $mapping;
269
        }
270
 
271
        // Filter the definition mappings.
272
        foreach ($configuration['definitionmappings'] as $mapping) {
273
            if (!is_array($mapping) || !array_key_exists('definition', $mapping) || !array_key_exists('store', $mapping)) {
274
                // Not a valid mapping configuration.
275
                continue;
276
            }
277
            if (!array_key_exists($mapping['store'], $this->configstores)) {
278
                // Mapped array instance doesn't exist.
279
                continue;
280
            }
281
            if (!array_key_exists($mapping['definition'], $this->configdefinitions)) {
282
                // Mapped array instance doesn't exist.
283
                continue;
284
            }
285
            if (!array_key_exists('sort', $mapping)) {
286
                $mapping['sort'] = 0;
287
            }
288
            $this->configdefinitionmappings[] = $mapping;
289
        }
290
 
1441 ariadna 291
        usort($this->configmodemappings, [$this, 'sort_mappings']);
292
        usort($this->configdefinitionmappings, [$this, 'sort_mappings']);
1 efrain 293
 
294
        return true;
295
    }
296
 
297
    /**
298
     * Returns the site identifier used by the cache API.
299
     * @return string
300
     */
301
    public function get_site_identifier() {
302
        return $this->siteidentifier;
303
    }
304
 
305
    /**
306
     * Includes the configuration file and makes sure it contains the expected bits.
307
     *
308
     * You need to ensure that the config file exists before this is called.
309
     *
310
     * @return array
311
     * @throws cache_exception
312
     */
313
    protected function include_configuration() {
314
        $configuration = null;
315
        // We need to allow for late static bindings to allow for class path mudling happending for unit tests.
316
        $cachefile = static::get_config_file_path();
317
 
318
        if (!file_exists($cachefile)) {
319
            throw new cache_exception('Default cache config could not be found. It should have already been created by now.');
320
        }
321
 
322
        if (!include($cachefile)) {
323
            throw new cache_exception('Unable to load the cache configuration file');
324
        }
325
 
326
        if (!is_array($configuration)) {
327
            throw new cache_exception('Invalid cache configuration file');
328
        }
329
        if (!array_key_exists('stores', $configuration) || !is_array($configuration['stores'])) {
1441 ariadna 330
            $configuration['stores'] = [];
1 efrain 331
        }
332
        if (!array_key_exists('modemappings', $configuration) || !is_array($configuration['modemappings'])) {
1441 ariadna 333
            $configuration['modemappings'] = [];
1 efrain 334
        }
335
        if (!array_key_exists('definitions', $configuration) || !is_array($configuration['definitions'])) {
1441 ariadna 336
            $configuration['definitions'] = [];
1 efrain 337
        }
338
        if (!array_key_exists('definitionmappings', $configuration) || !is_array($configuration['definitionmappings'])) {
1441 ariadna 339
            $configuration['definitionmappings'] = [];
1 efrain 340
        }
341
        if (!array_key_exists('locks', $configuration) || !is_array($configuration['locks'])) {
1441 ariadna 342
            $configuration['locks'] = [];
1 efrain 343
        }
344
 
345
        return $configuration;
346
    }
347
 
348
    /**
349
     * Used to sort cache config arrays based upon a sort key.
350
     *
351
     * Highest number at the top.
352
     *
353
     * @param array $a
354
     * @param array $b
355
     * @return int
356
     */
357
    protected function sort_mappings(array $a, array $b) {
358
        if ($a['sort'] == $b['sort']) {
359
            return 0;
360
        }
361
        return ($a['sort'] < $b['sort']) ? 1 : -1;
362
    }
363
 
364
    /**
365
     * Gets a definition from the config given its name.
366
     *
367
     * @param string $id
368
     * @return bool
369
     */
370
    public function get_definition_by_id($id) {
371
        if (array_key_exists($id, $this->configdefinitions)) {
372
            return $this->configdefinitions[$id];
373
        }
374
        return false;
375
    }
376
 
377
    /**
378
     * Returns all the known definitions.
379
     *
380
     * @return array
381
     */
382
    public function get_definitions() {
383
        return $this->configdefinitions;
384
    }
385
 
386
    /**
387
     * Returns the definitions mapped into the given store name.
388
     *
389
     * @param string $storename
390
     * @return array Associative array of definitions, id=>definition
391
     */
392
    public function get_definitions_by_store($storename) {
1441 ariadna 393
        $definitions = [];
1 efrain 394
 
395
        // This function was accidentally made static at some stage in the past.
396
        // It was converted to an instance method but to be backwards compatible
397
        // we must step around this in code.
398
        if (!isset($this)) {
1441 ariadna 399
            $config = self::instance();
1 efrain 400
        } else {
401
            $config = $this;
402
        }
403
 
404
        $stores = $config->get_all_stores();
405
        if (!array_key_exists($storename, $stores)) {
406
            // The store does not exist.
407
            return false;
408
        }
409
 
410
        $defmappings = $config->get_definition_mappings();
411
        // Create an associative array for the definition mappings.
1441 ariadna 412
        $thedefmappings = [];
1 efrain 413
        foreach ($defmappings as $defmapping) {
414
            $thedefmappings[$defmapping['definition']] = $defmapping;
415
        }
416
 
417
        // Search for matches in default mappings.
418
        $defs = $config->get_definitions();
1441 ariadna 419
        foreach ($config->get_mode_mappings() as $modemapping) {
1 efrain 420
            if ($modemapping['store'] !== $storename) {
421
                continue;
422
            }
1441 ariadna 423
            foreach ($defs as $id => $definition) {
1 efrain 424
                if ($definition['mode'] !== $modemapping['mode']) {
425
                    continue;
426
                }
427
                // Exclude custom definitions mapping: they will be managed few lines below.
428
                if (array_key_exists($id, $thedefmappings)) {
429
                    continue;
430
                }
431
                $definitions[$id] = $definition;
432
            }
433
        }
434
 
1441 ariadna 435
        // Search for matches in the custom definitions mapping.
1 efrain 436
        foreach ($defmappings as $defmapping) {
437
            if ($defmapping['store'] !== $storename) {
438
                continue;
439
            }
440
            $definition = $config->get_definition_by_id($defmapping['definition']);
441
            if ($definition) {
442
                $definitions[$defmapping['definition']] = $definition;
443
            }
444
        }
445
 
446
        return $definitions;
447
    }
448
 
449
    /**
450
     * Returns all of the stores that are suitable for the given mode and requirements.
451
     *
1441 ariadna 452
     * @param int $mode One of store::MODE_*
1 efrain 453
     * @param int $requirements The requirements of the cache as a binary flag
454
     * @return array An array of suitable stores.
455
     */
456
    public function get_stores($mode, $requirements = 0) {
1441 ariadna 457
        $stores = [];
1 efrain 458
        foreach ($this->configstores as $name => $store) {
459
            // If the mode is supported and all of the requirements are provided features.
460
            if (($store['modes'] & $mode) && ($store['features'] & $requirements) === $requirements) {
461
                $stores[$name] = $store;
462
            }
463
        }
464
        return $stores;
465
    }
466
 
467
    /**
468
     * Gets all of the stores that are to be used for the given definition.
469
     *
1441 ariadna 470
     * @param definition $definition
471
     * @return array<store>
1 efrain 472
     */
1441 ariadna 473
    public function get_stores_for_definition(definition $definition) {
1 efrain 474
        // Check if MUC has been disabled.
1441 ariadna 475
        $factory = factory::instance();
1 efrain 476
        if ($factory->stores_disabled()) {
477
            // Yip its been disabled.
478
            // To facilitate this we are going to always return an empty array of stores to use.
479
            // This will force all cache instances to use the cachestore_dummy.
480
            // MUC will still be used essentially so that code using it will still continue to function but because no cache stores
481
            // are being used interaction with MUC will be purely based around a static var.
1441 ariadna 482
            return [];
1 efrain 483
        }
484
 
485
        $availablestores = $this->get_stores($definition->get_mode(), $definition->get_requirements_bin());
1441 ariadna 486
        $stores = [];
1 efrain 487
        $id = $definition->get_id();
488
 
489
        // Now get any mappings and give them priority.
490
        foreach ($this->configdefinitionmappings as $mapping) {
491
            if ($mapping['definition'] !== $id) {
492
                continue;
493
            }
494
            $storename = $mapping['store'];
495
            if (!array_key_exists($storename, $availablestores)) {
496
                continue;
497
            }
498
            if (array_key_exists($storename, $stores)) {
499
                $store = $stores[$storename];
500
                unset($stores[$storename]);
501
                $stores[$storename] = $store;
502
            } else {
503
                $stores[$storename] = $availablestores[$storename];
504
            }
505
        }
506
 
507
        if (empty($stores) && !$definition->is_for_mappings_only()) {
508
            $mode = $definition->get_mode();
509
            // Load the default stores.
510
            foreach ($this->configmodemappings as $mapping) {
511
                if ($mapping['mode'] === $mode && array_key_exists($mapping['store'], $availablestores)) {
512
                    $store = $availablestores[$mapping['store']];
513
                    if (empty($store['mappingsonly'])) {
514
                        $stores[$mapping['store']] = $store;
515
                    }
516
                }
517
            }
518
        }
519
 
520
        return $stores;
521
    }
522
 
523
    /**
524
     * Returns all of the configured stores
525
     * @return array
526
     */
527
    public function get_all_stores() {
528
        return $this->configstores;
529
    }
530
 
531
    /**
532
     * Returns all of the configured mode mappings
533
     * @return array
534
     */
535
    public function get_mode_mappings() {
536
        return $this->configmodemappings;
537
    }
538
 
539
    /**
540
     * Returns all of the known definition mappings.
541
     * @return array
542
     */
543
    public function get_definition_mappings() {
544
        return $this->configdefinitionmappings;
545
    }
546
 
547
    /**
548
     * Returns an array of the configured locks.
549
     * @return array Array of name => config
550
     */
551
    public function get_locks() {
552
        return $this->configlocks;
553
    }
554
 
555
    /**
556
     * Returns the lock store configuration to use with a given store.
557
     * @param string $storename
558
     * @return array
559
     * @throws cache_exception
560
     */
561
    public function get_lock_for_store($storename) {
562
        if (array_key_exists($storename, $this->configstores)) {
563
            if (array_key_exists($this->configstores[$storename]['lock'], $this->configlocks)) {
564
                $lock = $this->configstores[$storename]['lock'];
565
                return $this->configlocks[$lock];
566
            }
567
        }
568
        return $this->get_default_lock();
569
    }
570
 
571
    /**
572
     * Gets the default lock instance.
573
     *
574
     * @return array
575
     * @throws cache_exception
576
     */
577
    public function get_default_lock() {
578
        foreach ($this->configlocks as $lockconf) {
579
            if (!empty($lockconf['default'])) {
580
                return $lockconf;
581
            }
582
        }
583
        throw new cache_exception('ex_nodefaultlock');
584
    }
585
}
1441 ariadna 586
 
587
// Alias this class to the old name.
588
// This file will be autoloaded by the legacyclasses autoload system.
589
// In future all uses of this class will be corrected and the legacy references will be removed.
590
class_alias(config::class, \cache_config::class);