[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
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 * The supplementary cache API. 19 * 20 * This file is part of Moodle's cache API, affectionately called MUC. 21 * It contains elements of the API that are not required in order to use caching. 22 * Things in here are more in line with administration and management of the cache setup and configuration. 23 * 24 * @package core 25 * @category cache 26 * @copyright 2012 Sam Hemelryk 27 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 28 */ 29 30 defined('MOODLE_INTERNAL') || die(); 31 32 /** 33 * Cache configuration writer. 34 * 35 * This class should only be used when you need to write to the config, all read operations exist within the cache_config. 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_writer extends cache_config { 43 44 /** 45 * Switch that gets set to true when ever a cache_config_writer instance is saving the cache configuration file. 46 * If this is set to true when save is next called we must avoid the trying to save and instead return the 47 * generated config so that is may be used instead of the file. 48 * @var bool 49 */ 50 protected static $creatingconfig = false; 51 52 /** 53 * Returns an instance of the configuration writer. 54 * 55 * @return cache_config_writer 56 */ 57 public static function instance() { 58 $factory = cache_factory::instance(); 59 return $factory->create_config_instance(true); 60 } 61 62 /** 63 * Saves the current configuration. 64 * 65 * Exceptions within this function are tolerated but must be of type cache_exception. 66 * They are caught during initialisation and written to the error log. This is required in order to avoid 67 * infinite loop situations caused by the cache throwing exceptions during its initialisation. 68 */ 69 protected function config_save() { 70 global $CFG; 71 $cachefile = static::get_config_file_path(); 72 $directory = dirname($cachefile); 73 if ($directory !== $CFG->dataroot && !file_exists($directory)) { 74 $result = make_writable_directory($directory, false); 75 if (!$result) { 76 throw new cache_exception('ex_configcannotsave', 'cache', '', null, 'Cannot create config directory. Check the permissions on your moodledata directory.'); 77 } 78 } 79 if (!file_exists($directory) || !is_writable($directory)) { 80 throw new cache_exception('ex_configcannotsave', 'cache', '', null, 'Config directory is not writable. Check the permissions on the moodledata/muc directory.'); 81 } 82 83 // Prepare a configuration array to store. 84 $configuration = $this->generate_configuration_array(); 85 86 // Prepare the file content. 87 $content = "<?php defined('MOODLE_INTERNAL') || die();\n \$configuration = ".var_export($configuration, true).";"; 88 89 // We need to create a temporary cache lock instance for use here. Remember we are generating the config file 90 // it doesn't exist and thus we can't use the normal API for this (it'll just try to use config). 91 $lockconf = reset($this->configlocks); 92 if ($lockconf === false) { 93 debugging('Your cache configuration file is out of date and needs to be refreshed.', DEBUG_DEVELOPER); 94 // Use the default 95 $lockconf = array( 96 'name' => 'cachelock_file_default', 97 'type' => 'cachelock_file', 98 'dir' => 'filelocks', 99 'default' => true 100 ); 101 } 102 $factory = cache_factory::instance(); 103 $locking = $factory->create_lock_instance($lockconf); 104 if ($locking->lock('configwrite', 'config', true)) { 105 // Its safe to use w mode here because we have already acquired the lock. 106 $handle = fopen($cachefile, 'w'); 107 fwrite($handle, $content); 108 fflush($handle); 109 fclose($handle); 110 $locking->unlock('configwrite', 'config'); 111 @chmod($cachefile, $CFG->filepermissions); 112 // Tell PHP to recompile the script. 113 core_component::invalidate_opcode_php_cache($cachefile); 114 } else { 115 throw new cache_exception('ex_configcannotsave', 'cache', '', null, 'Unable to open the cache config file.'); 116 } 117 } 118 119 /** 120 * Generates a configuration array suitable to be written to the config file. 121 * @return array 122 */ 123 protected function generate_configuration_array() { 124 $configuration = array(); 125 $configuration['siteidentifier'] = $this->siteidentifier; 126 $configuration['stores'] = $this->configstores; 127 $configuration['modemappings'] = $this->configmodemappings; 128 $configuration['definitions'] = $this->configdefinitions; 129 $configuration['definitionmappings'] = $this->configdefinitionmappings; 130 $configuration['locks'] = $this->configlocks; 131 return $configuration; 132 } 133 134 /** 135 * Adds a plugin instance. 136 * 137 * This function also calls save so you should redirect immediately, or at least very shortly after 138 * calling this method. 139 * 140 * @param string $name The name for the instance (must be unique) 141 * @param string $plugin The name of the plugin. 142 * @param array $configuration The configuration data for the plugin instance. 143 * @return bool 144 * @throws cache_exception 145 */ 146 public function add_store_instance($name, $plugin, array $configuration = array()) { 147 if (array_key_exists($name, $this->configstores)) { 148 throw new cache_exception('Duplicate name specificed for cache plugin instance. You must provide a unique name.'); 149 } 150 $class = 'cachestore_'.$plugin; 151 if (!class_exists($class)) { 152 $plugins = core_component::get_plugin_list_with_file('cachestore', 'lib.php'); 153 if (!array_key_exists($plugin, $plugins)) { 154 throw new cache_exception('Invalid plugin name specified. The plugin does not exist or is not valid.'); 155 } 156 $file = $plugins[$plugin]; 157 if (file_exists($file)) { 158 require_once($file); 159 } 160 if (!class_exists($class)) { 161 throw new cache_exception('Invalid cache plugin specified. The plugin does not contain the required class.'); 162 } 163 } 164 $reflection = new ReflectionClass($class); 165 if (!$reflection->isSubclassOf('cache_store')) { 166 throw new cache_exception('Invalid cache plugin specified. The plugin does not extend the required class.'); 167 } 168 if (!$class::are_requirements_met()) { 169 throw new cache_exception('Unable to add new cache plugin instance. The requested plugin type is not supported.'); 170 } 171 $this->configstores[$name] = array( 172 'name' => $name, 173 'plugin' => $plugin, 174 'configuration' => $configuration, 175 'features' => $class::get_supported_features($configuration), 176 'modes' => $class::get_supported_modes($configuration), 177 'mappingsonly' => !empty($configuration['mappingsonly']), 178 'class' => $class, 179 'default' => false 180 ); 181 if (array_key_exists('lock', $configuration)) { 182 $this->configstores[$name]['lock'] = $configuration['lock']; 183 unset($this->configstores[$name]['configuration']['lock']); 184 } 185 // Call instance_created() 186 $store = new $class($name, $this->configstores[$name]['configuration']); 187 $store->instance_created(); 188 189 $this->config_save(); 190 return true; 191 } 192 193 /** 194 * Adds a new lock instance to the config file. 195 * 196 * @param string $name The name the user gave the instance. PARAM_ALHPANUMEXT 197 * @param string $plugin The plugin we are creating an instance of. 198 * @param string $configuration Configuration data from the config instance. 199 * @throws cache_exception 200 */ 201 public function add_lock_instance($name, $plugin, $configuration = array()) { 202 if (array_key_exists($name, $this->configlocks)) { 203 throw new cache_exception('Duplicate name specificed for cache lock instance. You must provide a unique name.'); 204 } 205 $class = 'cachelock_'.$plugin; 206 if (!class_exists($class)) { 207 $plugins = core_component::get_plugin_list_with_file('cachelock', 'lib.php'); 208 if (!array_key_exists($plugin, $plugins)) { 209 throw new cache_exception('Invalid lock name specified. The plugin does not exist or is not valid.'); 210 } 211 $file = $plugins[$plugin]; 212 if (file_exists($file)) { 213 require_once($file); 214 } 215 if (!class_exists($class)) { 216 throw new cache_exception('Invalid lock plugin specified. The plugin does not contain the required class.'); 217 } 218 } 219 $reflection = new ReflectionClass($class); 220 if (!$reflection->implementsInterface('cache_lock_interface')) { 221 throw new cache_exception('Invalid lock plugin specified. The plugin does not implement the required interface.'); 222 } 223 $this->configlocks[$name] = array_merge($configuration, array( 224 'name' => $name, 225 'type' => 'cachelock_'.$plugin, 226 'default' => false 227 )); 228 $this->config_save(); 229 } 230 231 /** 232 * Deletes a lock instance given its name. 233 * 234 * @param string $name The name of the plugin, PARAM_ALPHANUMEXT. 235 * @return bool 236 * @throws cache_exception 237 */ 238 public function delete_lock_instance($name) { 239 if (!array_key_exists($name, $this->configlocks)) { 240 throw new cache_exception('The requested store does not exist.'); 241 } 242 if ($this->configlocks[$name]['default']) { 243 throw new cache_exception('You can not delete the default lock.'); 244 } 245 foreach ($this->configstores as $store) { 246 if (isset($store['lock']) && $store['lock'] === $name) { 247 throw new cache_exception('You cannot delete a cache lock that is being used by a store.'); 248 } 249 } 250 unset($this->configlocks[$name]); 251 $this->config_save(); 252 return true; 253 } 254 255 /** 256 * Sets the mode mappings. 257 * 258 * These determine the default caches for the different modes. 259 * This function also calls save so you should redirect immediately, or at least very shortly after 260 * calling this method. 261 * 262 * @param array $modemappings 263 * @return bool 264 * @throws cache_exception 265 */ 266 public function set_mode_mappings(array $modemappings) { 267 $mappings = array( 268 cache_store::MODE_APPLICATION => array(), 269 cache_store::MODE_SESSION => array(), 270 cache_store::MODE_REQUEST => array(), 271 ); 272 foreach ($modemappings as $mode => $stores) { 273 if (!array_key_exists($mode, $mappings)) { 274 throw new cache_exception('The cache mode for the new mapping does not exist'); 275 } 276 $sort = 0; 277 foreach ($stores as $store) { 278 if (!array_key_exists($store, $this->configstores)) { 279 throw new cache_exception('The instance name for the new mapping does not exist'); 280 } 281 if (array_key_exists($store, $mappings[$mode])) { 282 throw new cache_exception('This cache mapping already exists'); 283 } 284 $mappings[$mode][] = array( 285 'store' => $store, 286 'mode' => $mode, 287 'sort' => $sort++ 288 ); 289 } 290 } 291 $this->configmodemappings = array_merge( 292 $mappings[cache_store::MODE_APPLICATION], 293 $mappings[cache_store::MODE_SESSION], 294 $mappings[cache_store::MODE_REQUEST] 295 ); 296 297 $this->config_save(); 298 return true; 299 } 300 301 /** 302 * Edits a give plugin instance. 303 * 304 * The plugin instance is determined by its name, hence you cannot rename plugins. 305 * This function also calls save so you should redirect immediately, or at least very shortly after 306 * calling this method. 307 * 308 * @param string $name 309 * @param string $plugin 310 * @param array $configuration 311 * @return bool 312 * @throws cache_exception 313 */ 314 public function edit_store_instance($name, $plugin, $configuration) { 315 if (!array_key_exists($name, $this->configstores)) { 316 throw new cache_exception('The requested instance does not exist.'); 317 } 318 $plugins = core_component::get_plugin_list_with_file('cachestore', 'lib.php'); 319 if (!array_key_exists($plugin, $plugins)) { 320 throw new cache_exception('Invalid plugin name specified. The plugin either does not exist or is not valid.'); 321 } 322 $class = 'cachestore_'.$plugin; 323 $file = $plugins[$plugin]; 324 if (!class_exists($class)) { 325 if (file_exists($file)) { 326 require_once($file); 327 } 328 if (!class_exists($class)) { 329 throw new cache_exception('Invalid cache plugin specified. The plugin does not contain the required class.'.$class); 330 } 331 } 332 $this->configstores[$name] = array( 333 'name' => $name, 334 'plugin' => $plugin, 335 'configuration' => $configuration, 336 'features' => $class::get_supported_features($configuration), 337 'modes' => $class::get_supported_modes($configuration), 338 'mappingsonly' => !empty($configuration['mappingsonly']), 339 'class' => $class, 340 'default' => $this->configstores[$name]['default'] // Can't change the default. 341 ); 342 if (array_key_exists('lock', $configuration)) { 343 $this->configstores[$name]['lock'] = $configuration['lock']; 344 unset($this->configstores[$name]['configuration']['lock']); 345 } 346 $this->config_save(); 347 return true; 348 } 349 350 /** 351 * Deletes a store instance. 352 * 353 * This function also calls save so you should redirect immediately, or at least very shortly after 354 * calling this method. 355 * 356 * @param string $name The name of the instance to delete. 357 * @return bool 358 * @throws cache_exception 359 */ 360 public function delete_store_instance($name) { 361 if (!array_key_exists($name, $this->configstores)) { 362 throw new cache_exception('The requested store does not exist.'); 363 } 364 if ($this->configstores[$name]['default']) { 365 throw new cache_exception('The can not delete the default stores.'); 366 } 367 foreach ($this->configmodemappings as $mapping) { 368 if ($mapping['store'] === $name) { 369 throw new cache_exception('You cannot delete a cache store that has mode mappings.'); 370 } 371 } 372 foreach ($this->configdefinitionmappings as $mapping) { 373 if ($mapping['store'] === $name) { 374 throw new cache_exception('You cannot delete a cache store that has definition mappings.'); 375 } 376 } 377 378 // Call instance_deleted() 379 $class = 'cachestore_'.$this->configstores[$name]['plugin']; 380 $store = new $class($name, $this->configstores[$name]['configuration']); 381 $store->instance_deleted(); 382 383 unset($this->configstores[$name]); 384 $this->config_save(); 385 return true; 386 } 387 388 /** 389 * Creates the default configuration and saves it. 390 * 391 * This function calls config_save, however it is safe to continue using it afterwards as this function should only ever 392 * be called when there is no configuration file already. 393 * 394 * @param bool $forcesave If set to true then we will forcefully save the default configuration file. 395 * @return true|array Returns true if the default configuration was successfully created. 396 * Returns a configuration array if it could not be saved. This is a bad situation. Check your error logs. 397 */ 398 public static function create_default_configuration($forcesave = false) { 399 // HACK ALERT. 400 // We probably need to come up with a better way to create the default stores, or at least ensure 100% that the 401 // default store plugins are protected from deletion. 402 $writer = new self; 403 $writer->configstores = self::get_default_stores(); 404 $writer->configdefinitions = self::locate_definitions(); 405 $writer->configmodemappings = array( 406 array( 407 'mode' => cache_store::MODE_APPLICATION, 408 'store' => 'default_application', 409 'sort' => -1 410 ), 411 array( 412 'mode' => cache_store::MODE_SESSION, 413 'store' => 'default_session', 414 'sort' => -1 415 ), 416 array( 417 'mode' => cache_store::MODE_REQUEST, 418 'store' => 'default_request', 419 'sort' => -1 420 ) 421 ); 422 $writer->configlocks = array( 423 'default_file_lock' => array( 424 'name' => 'cachelock_file_default', 425 'type' => 'cachelock_file', 426 'dir' => 'filelocks', 427 'default' => true 428 ) 429 ); 430 431 $factory = cache_factory::instance(); 432 // We expect the cache to be initialising presently. If its not then something has gone wrong and likely 433 // we are now in a loop. 434 if (!$forcesave && $factory->get_state() !== cache_factory::STATE_INITIALISING) { 435 return $writer->generate_configuration_array(); 436 } 437 $factory->set_state(cache_factory::STATE_SAVING); 438 $writer->config_save(); 439 return true; 440 } 441 442 /** 443 * Returns an array of default stores for use. 444 * 445 * @return array 446 */ 447 protected static function get_default_stores() { 448 global $CFG; 449 450 require_once($CFG->dirroot.'/cache/stores/file/lib.php'); 451 require_once($CFG->dirroot.'/cache/stores/session/lib.php'); 452 require_once($CFG->dirroot.'/cache/stores/static/lib.php'); 453 454 return array( 455 'default_application' => array( 456 'name' => 'default_application', 457 'plugin' => 'file', 458 'configuration' => array(), 459 'features' => cachestore_file::get_supported_features(), 460 'modes' => cachestore_file::get_supported_modes(), 461 'default' => true, 462 ), 463 'default_session' => array( 464 'name' => 'default_session', 465 'plugin' => 'session', 466 'configuration' => array(), 467 'features' => cachestore_session::get_supported_features(), 468 'modes' => cachestore_session::get_supported_modes(), 469 'default' => true, 470 ), 471 'default_request' => array( 472 'name' => 'default_request', 473 'plugin' => 'static', 474 'configuration' => array(), 475 'features' => cachestore_static::get_supported_features(), 476 'modes' => cachestore_static::get_supported_modes(), 477 'default' => true, 478 ) 479 ); 480 } 481 482 /** 483 * Updates the default stores within the MUC config file. 484 */ 485 public static function update_default_config_stores() { 486 $factory = cache_factory::instance(); 487 $factory->updating_started(); 488 $config = $factory->create_config_instance(true); 489 $config->configstores = array_merge($config->configstores, self::get_default_stores()); 490 $config->config_save(); 491 $factory->updating_finished(); 492 } 493 494 /** 495 * Updates the definition in the configuration from those found in the cache files. 496 * 497 * Calls config_save further down, you should redirect immediately or asap after calling this method. 498 * 499 * @param bool $coreonly If set to true only core definitions will be updated. 500 */ 501 public static function update_definitions($coreonly = false) { 502 $factory = cache_factory::instance(); 503 $factory->updating_started(); 504 $config = $factory->create_config_instance(true); 505 $config->write_definitions_to_cache(self::locate_definitions($coreonly)); 506 $factory->updating_finished(); 507 } 508 509 /** 510 * Locates all of the definition files. 511 * 512 * @param bool $coreonly If set to true only core definitions will be updated. 513 * @return array 514 */ 515 protected static function locate_definitions($coreonly = false) { 516 global $CFG; 517 518 $files = array(); 519 if (file_exists($CFG->dirroot.'/lib/db/caches.php')) { 520 $files['core'] = $CFG->dirroot.'/lib/db/caches.php'; 521 } 522 523 if (!$coreonly) { 524 $plugintypes = core_component::get_plugin_types(); 525 foreach ($plugintypes as $type => $location) { 526 $plugins = core_component::get_plugin_list_with_file($type, 'db/caches.php'); 527 foreach ($plugins as $plugin => $filepath) { 528 $component = clean_param($type.'_'.$plugin, PARAM_COMPONENT); // Standardised plugin name. 529 $files[$component] = $filepath; 530 } 531 } 532 } 533 534 $definitions = array(); 535 foreach ($files as $component => $file) { 536 $filedefs = self::load_caches_file($file); 537 foreach ($filedefs as $area => $definition) { 538 $area = clean_param($area, PARAM_AREA); 539 $id = $component.'/'.$area; 540 $definition['component'] = $component; 541 $definition['area'] = $area; 542 if (array_key_exists($id, $definitions)) { 543 debugging('Error: duplicate cache definition found with id: '.$id, DEBUG_DEVELOPER); 544 continue; 545 } 546 $definitions[$id] = $definition; 547 } 548 } 549 550 return $definitions; 551 } 552 553 /** 554 * Writes the updated definitions for the config file. 555 * @param array $definitions 556 */ 557 private function write_definitions_to_cache(array $definitions) { 558 559 // Preserve the selected sharing option when updating the definitions. 560 // This is set by the user and should never come from caches.php. 561 foreach ($definitions as $key => $definition) { 562 unset($definitions[$key]['selectedsharingoption']); 563 unset($definitions[$key]['userinputsharingkey']); 564 if (isset($this->configdefinitions[$key]) && isset($this->configdefinitions[$key]['selectedsharingoption'])) { 565 $definitions[$key]['selectedsharingoption'] = $this->configdefinitions[$key]['selectedsharingoption']; 566 } 567 if (isset($this->configdefinitions[$key]) && isset($this->configdefinitions[$key]['userinputsharingkey'])) { 568 $definitions[$key]['userinputsharingkey'] = $this->configdefinitions[$key]['userinputsharingkey']; 569 } 570 } 571 572 $this->configdefinitions = $definitions; 573 foreach ($this->configdefinitionmappings as $key => $mapping) { 574 if (!array_key_exists($mapping['definition'], $definitions)) { 575 unset($this->configdefinitionmappings[$key]); 576 } 577 } 578 $this->config_save(); 579 } 580 581 /** 582 * Loads the caches file if it exists. 583 * @param string $file Absolute path to the file. 584 * @return array 585 */ 586 private static function load_caches_file($file) { 587 if (!file_exists($file)) { 588 return array(); 589 } 590 $definitions = array(); 591 include($file); 592 return $definitions; 593 } 594 595 /** 596 * Sets the mappings for a given definition. 597 * 598 * @param string $definition 599 * @param array $mappings 600 * @throws coding_exception 601 */ 602 public function set_definition_mappings($definition, $mappings) { 603 if (!array_key_exists($definition, $this->configdefinitions)) { 604 throw new coding_exception('Invalid definition name passed when updating mappings.'); 605 } 606 foreach ($mappings as $store) { 607 if (!array_key_exists($store, $this->configstores)) { 608 throw new coding_exception('Invalid store name passed when updating definition mappings.'); 609 } 610 } 611 foreach ($this->configdefinitionmappings as $key => $mapping) { 612 if ($mapping['definition'] == $definition) { 613 unset($this->configdefinitionmappings[$key]); 614 } 615 } 616 $sort = count($mappings); 617 foreach ($mappings as $store) { 618 $this->configdefinitionmappings[] = array( 619 'store' => $store, 620 'definition' => $definition, 621 'sort' => $sort 622 ); 623 $sort--; 624 } 625 626 $this->config_save(); 627 } 628 629 /** 630 * Update the site identifier stored by the cache API. 631 * 632 * @param string $siteidentifier 633 * @return string The new site identifier. 634 */ 635 public function update_site_identifier($siteidentifier) { 636 $this->siteidentifier = md5((string)$siteidentifier); 637 $this->config_save(); 638 return $this->siteidentifier; 639 } 640 641 /** 642 * Sets the selected sharing options and key for a definition. 643 * 644 * @param string $definition The name of the definition to set for. 645 * @param int $sharingoption The sharing option to set. 646 * @param string|null $userinputsharingkey The user input key or null. 647 * @throws coding_exception 648 */ 649 public function set_definition_sharing($definition, $sharingoption, $userinputsharingkey = null) { 650 if (!array_key_exists($definition, $this->configdefinitions)) { 651 throw new coding_exception('Invalid definition name passed when updating sharing options.'); 652 } 653 if (!($this->configdefinitions[$definition]['sharingoptions'] & $sharingoption)) { 654 throw new coding_exception('Invalid sharing option passed when updating definition.'); 655 } 656 $this->configdefinitions[$definition]['selectedsharingoption'] = (int)$sharingoption; 657 if (!empty($userinputsharingkey)) { 658 $this->configdefinitions[$definition]['userinputsharingkey'] = (string)$userinputsharingkey; 659 } 660 $this->config_save(); 661 } 662 663 } 664 665 /** 666 * A cache helper for administration tasks 667 * 668 * @package core 669 * @category cache 670 * @copyright 2012 Sam Hemelryk 671 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 672 */ 673 abstract class cache_administration_helper extends cache_helper { 674 675 /** 676 * Returns an array containing all of the information about stores a renderer needs. 677 * @return array 678 */ 679 public static function get_store_instance_summaries() { 680 $return = array(); 681 $default = array(); 682 $instance = cache_config::instance(); 683 $stores = $instance->get_all_stores(); 684 $locks = $instance->get_locks(); 685 foreach ($stores as $name => $details) { 686 $class = $details['class']; 687 $store = new $class($details['name'], $details['configuration']); 688 $lock = (isset($details['lock'])) ? $locks[$details['lock']] : $instance->get_default_lock(); 689 $record = array( 690 'name' => $name, 691 'plugin' => $details['plugin'], 692 'default' => $details['default'], 693 'isready' => $store->is_ready(), 694 'requirementsmet' => $store->are_requirements_met(), 695 'mappings' => 0, 696 'lock' => $lock, 697 'modes' => array( 698 cache_store::MODE_APPLICATION => 699 ($store->get_supported_modes($return) & cache_store::MODE_APPLICATION) == cache_store::MODE_APPLICATION, 700 cache_store::MODE_SESSION => 701 ($store->get_supported_modes($return) & cache_store::MODE_SESSION) == cache_store::MODE_SESSION, 702 cache_store::MODE_REQUEST => 703 ($store->get_supported_modes($return) & cache_store::MODE_REQUEST) == cache_store::MODE_REQUEST, 704 ), 705 'supports' => array( 706 'multipleidentifiers' => $store->supports_multiple_identifiers(), 707 'dataguarantee' => $store->supports_data_guarantee(), 708 'nativettl' => $store->supports_native_ttl(), 709 'nativelocking' => ($store instanceof cache_is_lockable), 710 'keyawareness' => ($store instanceof cache_is_key_aware), 711 'searchable' => ($store instanceof cache_is_searchable) 712 ), 713 'warnings' => $store->get_warnings() 714 ); 715 if (empty($details['default'])) { 716 $return[$name] = $record; 717 } else { 718 $default[$name] = $record; 719 } 720 } 721 722 ksort($return); 723 ksort($default); 724 $return = $return + $default; 725 726 foreach ($instance->get_definition_mappings() as $mapping) { 727 if (!array_key_exists($mapping['store'], $return)) { 728 continue; 729 } 730 $return[$mapping['store']]['mappings']++; 731 } 732 733 return $return; 734 } 735 736 /** 737 * Returns an array of information about plugins, everything a renderer needs. 738 * @return array 739 */ 740 public static function get_store_plugin_summaries() { 741 $return = array(); 742 $plugins = core_component::get_plugin_list_with_file('cachestore', 'lib.php', true); 743 foreach ($plugins as $plugin => $path) { 744 $class = 'cachestore_'.$plugin; 745 $return[$plugin] = array( 746 'name' => get_string('pluginname', 'cachestore_'.$plugin), 747 'requirementsmet' => $class::are_requirements_met(), 748 'instances' => 0, 749 'modes' => array( 750 cache_store::MODE_APPLICATION => ($class::get_supported_modes() & cache_store::MODE_APPLICATION), 751 cache_store::MODE_SESSION => ($class::get_supported_modes() & cache_store::MODE_SESSION), 752 cache_store::MODE_REQUEST => ($class::get_supported_modes() & cache_store::MODE_REQUEST), 753 ), 754 'supports' => array( 755 'multipleidentifiers' => ($class::get_supported_features() & cache_store::SUPPORTS_MULTIPLE_IDENTIFIERS), 756 'dataguarantee' => ($class::get_supported_features() & cache_store::SUPPORTS_DATA_GUARANTEE), 757 'nativettl' => ($class::get_supported_features() & cache_store::SUPPORTS_NATIVE_TTL), 758 'nativelocking' => (in_array('cache_is_lockable', class_implements($class))), 759 'keyawareness' => (array_key_exists('cache_is_key_aware', class_implements($class))), 760 ), 761 'canaddinstance' => ($class::can_add_instance() && $class::are_requirements_met()) 762 ); 763 } 764 765 $instance = cache_config::instance(); 766 $stores = $instance->get_all_stores(); 767 foreach ($stores as $store) { 768 $plugin = $store['plugin']; 769 if (array_key_exists($plugin, $return)) { 770 $return[$plugin]['instances']++; 771 } 772 } 773 774 return $return; 775 } 776 777 /** 778 * Returns an array about the definitions. All the information a renderer needs. 779 * @return array 780 */ 781 public static function get_definition_summaries() { 782 $factory = cache_factory::instance(); 783 $config = $factory->create_config_instance(); 784 $storenames = array(); 785 foreach ($config->get_all_stores() as $key => $store) { 786 if (!empty($store['default'])) { 787 $storenames[$key] = new lang_string('store_'.$key, 'cache'); 788 } else { 789 $storenames[$store['name']] = $store['name']; 790 } 791 } 792 /* @var cache_definition[] $definitions */ 793 $definitions = array(); 794 foreach ($config->get_definitions() as $key => $definition) { 795 $definitions[$key] = cache_definition::load($definition['component'].'/'.$definition['area'], $definition); 796 } 797 foreach ($definitions as $id => $definition) { 798 $mappings = array(); 799 foreach (cache_helper::get_stores_suitable_for_definition($definition) as $store) { 800 $mappings[] = $storenames[$store->my_name()]; 801 } 802 $return[$id] = array( 803 'id' => $id, 804 'name' => $definition->get_name(), 805 'mode' => $definition->get_mode(), 806 'component' => $definition->get_component(), 807 'area' => $definition->get_area(), 808 'mappings' => $mappings, 809 'canuselocalstore' => $definition->can_use_localstore(), 810 'sharingoptions' => self::get_definition_sharing_options($definition->get_sharing_options(), false), 811 'selectedsharingoption' => self::get_definition_sharing_options($definition->get_selected_sharing_option(), true), 812 'userinputsharingkey' => $definition->get_user_input_sharing_key() 813 ); 814 } 815 return $return; 816 } 817 818 /** 819 * Given a sharing option hash this function returns an array of strings that can be used to describe it. 820 * 821 * @param int $sharingoption The sharing option hash to get strings for. 822 * @param bool $isselectedoptions Set to true if the strings will be used to view the selected options. 823 * @return array An array of lang_string's. 824 */ 825 public static function get_definition_sharing_options($sharingoption, $isselectedoptions = true) { 826 $options = array(); 827 $prefix = ($isselectedoptions) ? 'sharingselected' : 'sharing'; 828 if ($sharingoption & cache_definition::SHARING_ALL) { 829 $options[cache_definition::SHARING_ALL] = new lang_string($prefix.'_all', 'cache'); 830 } 831 if ($sharingoption & cache_definition::SHARING_SITEID) { 832 $options[cache_definition::SHARING_SITEID] = new lang_string($prefix.'_siteid', 'cache'); 833 } 834 if ($sharingoption & cache_definition::SHARING_VERSION) { 835 $options[cache_definition::SHARING_VERSION] = new lang_string($prefix.'_version', 'cache'); 836 } 837 if ($sharingoption & cache_definition::SHARING_INPUT) { 838 $options[cache_definition::SHARING_INPUT] = new lang_string($prefix.'_input', 'cache'); 839 } 840 return $options; 841 } 842 843 /** 844 * Returns all of the actions that can be performed on a definition. 845 * @param context $context 846 * @return array 847 */ 848 public static function get_definition_actions(context $context, array $definition) { 849 if (has_capability('moodle/site:config', $context)) { 850 $actions = array(); 851 // Edit mappings. 852 $actions[] = array( 853 'text' => get_string('editmappings', 'cache'), 854 'url' => new moodle_url('/cache/admin.php', array('action' => 'editdefinitionmapping', 'sesskey' => sesskey())) 855 ); 856 // Edit sharing. 857 if (count($definition['sharingoptions']) > 1) { 858 $actions[] = array( 859 'text' => get_string('editsharing', 'cache'), 860 'url' => new moodle_url('/cache/admin.php', array('action' => 'editdefinitionsharing', 'sesskey' => sesskey())) 861 ); 862 } 863 // Purge. 864 $actions[] = array( 865 'text' => get_string('purge', 'cache'), 866 'url' => new moodle_url('/cache/admin.php', array('action' => 'purgedefinition', 'sesskey' => sesskey())) 867 ); 868 return $actions; 869 } 870 return array(); 871 } 872 873 /** 874 * Returns all of the actions that can be performed on a store. 875 * 876 * @param string $name The name of the store 877 * @param array $storedetails 878 * @return array 879 */ 880 public static function get_store_instance_actions($name, array $storedetails) { 881 $actions = array(); 882 if (has_capability('moodle/site:config', context_system::instance())) { 883 $baseurl = new moodle_url('/cache/admin.php', array('store' => $name, 'sesskey' => sesskey())); 884 if (empty($storedetails['default'])) { 885 $actions[] = array( 886 'text' => get_string('editstore', 'cache'), 887 'url' => new moodle_url($baseurl, array('action' => 'editstore', 'plugin' => $storedetails['plugin'])) 888 ); 889 $actions[] = array( 890 'text' => get_string('deletestore', 'cache'), 891 'url' => new moodle_url($baseurl, array('action' => 'deletestore')) 892 ); 893 } 894 $actions[] = array( 895 'text' => get_string('purge', 'cache'), 896 'url' => new moodle_url($baseurl, array('action' => 'purgestore')) 897 ); 898 } 899 return $actions; 900 } 901 902 903 /** 904 * Returns all of the actions that can be performed on a plugin. 905 * 906 * @param string $name The name of the plugin 907 * @param array $plugindetails 908 * @return array 909 */ 910 public static function get_store_plugin_actions($name, array $plugindetails) { 911 $actions = array(); 912 if (has_capability('moodle/site:config', context_system::instance())) { 913 if (!empty($plugindetails['canaddinstance'])) { 914 $url = new moodle_url('/cache/admin.php', array('action' => 'addstore', 'plugin' => $name, 'sesskey' => sesskey())); 915 $actions[] = array( 916 'text' => get_string('addinstance', 'cache'), 917 'url' => $url 918 ); 919 } 920 } 921 return $actions; 922 } 923 924 /** 925 * Returns a form that can be used to add a store instance. 926 * 927 * @param string $plugin The plugin to add an instance of 928 * @return cachestore_addinstance_form 929 * @throws coding_exception 930 */ 931 public static function get_add_store_form($plugin) { 932 global $CFG; // Needed for includes. 933 $plugins = core_component::get_plugin_list('cachestore'); 934 if (!array_key_exists($plugin, $plugins)) { 935 throw new coding_exception('Invalid cache plugin used when trying to create an edit form.'); 936 } 937 $plugindir = $plugins[$plugin]; 938 $class = 'cachestore_addinstance_form'; 939 if (file_exists($plugindir.'/addinstanceform.php')) { 940 require_once($plugindir.'/addinstanceform.php'); 941 if (class_exists('cachestore_'.$plugin.'_addinstance_form')) { 942 $class = 'cachestore_'.$plugin.'_addinstance_form'; 943 if (!array_key_exists('cachestore_addinstance_form', class_parents($class))) { 944 throw new coding_exception('Cache plugin add instance forms must extend cachestore_addinstance_form'); 945 } 946 } 947 } 948 949 $locks = self::get_possible_locks_for_stores($plugindir, $plugin); 950 951 $url = new moodle_url('/cache/admin.php', array('action' => 'addstore')); 952 return new $class($url, array('plugin' => $plugin, 'store' => null, 'locks' => $locks)); 953 } 954 955 /** 956 * Returns a form that can be used to edit a store instance. 957 * 958 * @param string $plugin 959 * @param string $store 960 * @return cachestore_addinstance_form 961 * @throws coding_exception 962 */ 963 public static function get_edit_store_form($plugin, $store) { 964 global $CFG; // Needed for includes. 965 $plugins = core_component::get_plugin_list('cachestore'); 966 if (!array_key_exists($plugin, $plugins)) { 967 throw new coding_exception('Invalid cache plugin used when trying to create an edit form.'); 968 } 969 $factory = cache_factory::instance(); 970 $config = $factory->create_config_instance(); 971 $stores = $config->get_all_stores(); 972 if (!array_key_exists($store, $stores)) { 973 throw new coding_exception('Invalid store name given when trying to create an edit form.'); 974 } 975 $plugindir = $plugins[$plugin]; 976 $class = 'cachestore_addinstance_form'; 977 if (file_exists($plugindir.'/addinstanceform.php')) { 978 require_once($plugindir.'/addinstanceform.php'); 979 if (class_exists('cachestore_'.$plugin.'_addinstance_form')) { 980 $class = 'cachestore_'.$plugin.'_addinstance_form'; 981 if (!array_key_exists('cachestore_addinstance_form', class_parents($class))) { 982 throw new coding_exception('Cache plugin add instance forms must extend cachestore_addinstance_form'); 983 } 984 } 985 } 986 987 $locks = self::get_possible_locks_for_stores($plugindir, $plugin); 988 989 $url = new moodle_url('/cache/admin.php', array('action' => 'editstore', 'plugin' => $plugin, 'store' => $store)); 990 $editform = new $class($url, array('plugin' => $plugin, 'store' => $store, 'locks' => $locks)); 991 if (isset($stores[$store]['lock'])) { 992 $editform->set_data(array('lock' => $stores[$store]['lock'])); 993 } 994 // See if the cachestore is going to want to load data for the form. 995 // If it has a customised add instance form then it is going to want to. 996 $storeclass = 'cachestore_'.$plugin; 997 $storedata = $stores[$store]; 998 if (array_key_exists('configuration', $storedata) && array_key_exists('cache_is_configurable', class_implements($storeclass))) { 999 $storeclass::config_set_edit_form_data($editform, $storedata['configuration']); 1000 } 1001 return $editform; 1002 } 1003 1004 /** 1005 * Returns an array of suitable lock instances for use with this plugin, or false if the plugin handles locking itself. 1006 * 1007 * @param string $plugindir 1008 * @param string $plugin 1009 * @return array|false 1010 */ 1011 protected static function get_possible_locks_for_stores($plugindir, $plugin) { 1012 global $CFG; // Needed for includes. 1013 $supportsnativelocking = false; 1014 if (file_exists($plugindir.'/lib.php')) { 1015 require_once ($plugindir.'/lib.php'); 1016 $pluginclass = 'cachestore_'.$plugin; 1017 if (class_exists($pluginclass)) { 1018 $supportsnativelocking = array_key_exists('cache_is_lockable', class_implements($pluginclass)); 1019 } 1020 } 1021 1022 if (!$supportsnativelocking) { 1023 $config = cache_config::instance(); 1024 $locks = array(); 1025 foreach ($config->get_locks() as $lock => $conf) { 1026 if (!empty($conf['default'])) { 1027 $name = get_string($lock, 'cache'); 1028 } else { 1029 $name = $lock; 1030 } 1031 $locks[$lock] = $name; 1032 } 1033 } else { 1034 $locks = false; 1035 } 1036 1037 return $locks; 1038 } 1039 1040 /** 1041 * Processes the results of the add/edit instance form data for a plugin returning an array of config information suitable to 1042 * store in configuration. 1043 * 1044 * @param stdClass $data The mform data. 1045 * @return array 1046 * @throws coding_exception 1047 */ 1048 public static function get_store_configuration_from_data(stdClass $data) { 1049 global $CFG; 1050 $file = $CFG->dirroot.'/cache/stores/'.$data->plugin.'/lib.php'; 1051 if (!file_exists($file)) { 1052 throw new coding_exception('Invalid cache plugin provided. '.$file); 1053 } 1054 require_once($file); 1055 $class = 'cachestore_'.$data->plugin; 1056 if (!class_exists($class)) { 1057 throw new coding_exception('Invalid cache plugin provided.'); 1058 } 1059 if (array_key_exists('cache_is_configurable', class_implements($class))) { 1060 return $class::config_get_configuration_array($data); 1061 } 1062 return array(); 1063 } 1064 1065 /** 1066 * Get an array of stores that are suitable to be used for a given definition. 1067 * 1068 * @param string $component 1069 * @param string $area 1070 * @return array Array containing 3 elements 1071 * 1. An array of currently used stores 1072 * 2. An array of suitable stores 1073 * 3. An array of default stores 1074 */ 1075 public static function get_definition_store_options($component, $area) { 1076 $factory = cache_factory::instance(); 1077 $definition = $factory->create_definition($component, $area); 1078 $config = cache_config::instance(); 1079 $currentstores = $config->get_stores_for_definition($definition); 1080 $possiblestores = $config->get_stores($definition->get_mode(), $definition->get_requirements_bin()); 1081 1082 $defaults = array(); 1083 foreach ($currentstores as $key => $store) { 1084 if (!empty($store['default'])) { 1085 $defaults[] = $key; 1086 unset($currentstores[$key]); 1087 } 1088 } 1089 foreach ($possiblestores as $key => $store) { 1090 if ($store['default']) { 1091 unset($possiblestores[$key]); 1092 $possiblestores[$key] = $store; 1093 } 1094 } 1095 return array($currentstores, $possiblestores, $defaults); 1096 } 1097 1098 /** 1099 * Get the default stores for all modes. 1100 * 1101 * @return array An array containing sub-arrays, one for each mode. 1102 */ 1103 public static function get_default_mode_stores() { 1104 global $OUTPUT; 1105 $instance = cache_config::instance(); 1106 $adequatestores = cache_helper::get_stores_suitable_for_mode_default(); 1107 $icon = new pix_icon('i/warning', new lang_string('inadequatestoreformapping', 'cache')); 1108 $storenames = array(); 1109 foreach ($instance->get_all_stores() as $key => $store) { 1110 if (!empty($store['default'])) { 1111 $storenames[$key] = new lang_string('store_'.$key, 'cache'); 1112 } 1113 } 1114 $modemappings = array( 1115 cache_store::MODE_APPLICATION => array(), 1116 cache_store::MODE_SESSION => array(), 1117 cache_store::MODE_REQUEST => array(), 1118 ); 1119 foreach ($instance->get_mode_mappings() as $mapping) { 1120 $mode = $mapping['mode']; 1121 if (!array_key_exists($mode, $modemappings)) { 1122 debugging('Unknown mode in cache store mode mappings', DEBUG_DEVELOPER); 1123 continue; 1124 } 1125 if (array_key_exists($mapping['store'], $storenames)) { 1126 $modemappings[$mode][$mapping['store']] = $storenames[$mapping['store']]; 1127 } else { 1128 $modemappings[$mode][$mapping['store']] = $mapping['store']; 1129 } 1130 if (!array_key_exists($mapping['store'], $adequatestores)) { 1131 $modemappings[$mode][$mapping['store']] = $modemappings[$mode][$mapping['store']].' '.$OUTPUT->render($icon); 1132 } 1133 } 1134 return $modemappings; 1135 } 1136 1137 /** 1138 * Returns an array summarising the locks available in the system 1139 */ 1140 public static function get_lock_summaries() { 1141 $locks = array(); 1142 $instance = cache_config::instance(); 1143 $stores = $instance->get_all_stores(); 1144 foreach ($instance->get_locks() as $lock) { 1145 $default = !empty($lock['default']); 1146 if ($default) { 1147 $name = new lang_string($lock['name'], 'cache'); 1148 } else { 1149 $name = $lock['name']; 1150 } 1151 $uses = 0; 1152 foreach ($stores as $store) { 1153 if (!empty($store['lock']) && $store['lock'] === $lock['name']) { 1154 $uses++; 1155 } 1156 } 1157 $lockdata = array( 1158 'name' => $name, 1159 'default' => $default, 1160 'uses' => $uses, 1161 'type' => get_string('pluginname', $lock['type']) 1162 ); 1163 $locks[$lock['name']] = $lockdata; 1164 } 1165 return $locks; 1166 } 1167 1168 /** 1169 * Returns an array of lock plugins for which we can add an instance. 1170 * 1171 * Suitable for use within an mform select element. 1172 * 1173 * @return array 1174 */ 1175 public static function get_addable_lock_options() { 1176 $plugins = core_component::get_plugin_list_with_class('cachelock', '', 'lib.php'); 1177 $options = array(); 1178 $len = strlen('cachelock_'); 1179 foreach ($plugins as $plugin => $class) { 1180 $method = "$class::can_add_instance"; 1181 if (is_callable($method) && !call_user_func($method)) { 1182 // Can't add an instance of this plugin. 1183 continue; 1184 } 1185 $options[substr($plugin, $len)] = get_string('pluginname', $plugin); 1186 } 1187 return $options; 1188 } 1189 1190 /** 1191 * Gets the form to use when adding a lock instance. 1192 * 1193 * @param string $plugin 1194 * @param array $lockplugin 1195 * @return cache_lock_form 1196 * @throws coding_exception 1197 */ 1198 public static function get_add_lock_form($plugin, array $lockplugin = null) { 1199 global $CFG; // Needed for includes. 1200 $plugins = core_component::get_plugin_list('cachelock'); 1201 if (!array_key_exists($plugin, $plugins)) { 1202 throw new coding_exception('Invalid cache lock plugin requested when trying to create a form.'); 1203 } 1204 $plugindir = $plugins[$plugin]; 1205 $class = 'cache_lock_form'; 1206 if (file_exists($plugindir.'/addinstanceform.php') && in_array('cache_is_configurable', class_implements($class))) { 1207 require_once($plugindir.'/addinstanceform.php'); 1208 if (class_exists('cachelock_'.$plugin.'_addinstance_form')) { 1209 $class = 'cachelock_'.$plugin.'_addinstance_form'; 1210 if (!array_key_exists('cache_lock_form', class_parents($class))) { 1211 throw new coding_exception('Cache lock plugin add instance forms must extend cache_lock_form'); 1212 } 1213 } 1214 } 1215 return new $class(null, array('lock' => $plugin)); 1216 } 1217 1218 /** 1219 * Gets configuration data from a new lock instance form. 1220 * 1221 * @param string $plugin 1222 * @param stdClass $data 1223 * @return array 1224 * @throws coding_exception 1225 */ 1226 public static function get_lock_configuration_from_data($plugin, $data) { 1227 global $CFG; 1228 $file = $CFG->dirroot.'/cache/locks/'.$plugin.'/lib.php'; 1229 if (!file_exists($file)) { 1230 throw new coding_exception('Invalid cache plugin provided. '.$file); 1231 } 1232 require_once($file); 1233 $class = 'cachelock_'.$plugin; 1234 if (!class_exists($class)) { 1235 throw new coding_exception('Invalid cache plugin provided.'); 1236 } 1237 if (array_key_exists('cache_is_configurable', class_implements($class))) { 1238 return $class::config_get_configuration_array($data); 1239 } 1240 return array(); 1241 } 1242 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |