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