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