[ Index ] |
PHP Cross Reference of moodle-2.8 |
[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 * Cache helper class 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 * The cache helper class. 33 * 34 * The cache helper class provides common functionality to the cache API and is useful to developers within to interact with 35 * the cache API in a general way. 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_helper { 43 44 /** 45 * Statistics gathered by the cache API during its operation will be used here. 46 * @static 47 * @var array 48 */ 49 protected static $stats = array(); 50 51 /** 52 * The instance of the cache helper. 53 * @var cache_helper 54 */ 55 protected static $instance; 56 57 /** 58 * The site identifier used by the cache. 59 * Set the first time get_site_identifier is called. 60 * @var string 61 */ 62 protected static $siteidentifier = null; 63 64 /** 65 * Returns true if the cache API can be initialised before Moodle has finished initialising itself. 66 * 67 * This check is essential when trying to cache the likes of configuration information. It checks to make sure that the cache 68 * configuration file has been created which allows use to set up caching when ever is required. 69 * 70 * @return bool 71 */ 72 public static function ready_for_early_init() { 73 return cache_config::config_file_exists(); 74 } 75 76 /** 77 * Returns an instance of the cache_helper. 78 * 79 * This is designed for internal use only and acts as a static store. 80 * @staticvar null $instance 81 * @return cache_helper 82 */ 83 protected static function instance() { 84 if (is_null(self::$instance)) { 85 self::$instance = new cache_helper(); 86 } 87 return self::$instance; 88 } 89 90 /** 91 * Constructs an instance of the cache_helper class. Again for internal use only. 92 */ 93 protected function __construct() { 94 // Nothing to do here, just making sure you can't get an instance of this. 95 } 96 97 /** 98 * Used as a data store for initialised definitions. 99 * @var array 100 */ 101 protected $definitions = array(); 102 103 /** 104 * Used as a data store for initialised cache stores 105 * We use this because we want to avoid establishing multiple instances of a single store. 106 * @var array 107 */ 108 protected $stores = array(); 109 110 /** 111 * Returns the class for use as a cache loader for the given mode. 112 * 113 * @param int $mode One of cache_store::MODE_ 114 * @return string 115 * @throws coding_exception 116 */ 117 public static function get_class_for_mode($mode) { 118 switch ($mode) { 119 case cache_store::MODE_APPLICATION : 120 return 'cache_application'; 121 case cache_store::MODE_REQUEST : 122 return 'cache_request'; 123 case cache_store::MODE_SESSION : 124 return 'cache_session'; 125 } 126 throw new coding_exception('Unknown cache mode passed. Must be one of cache_store::MODE_*'); 127 } 128 129 /** 130 * Returns the cache stores to be used with the given definition. 131 * @param cache_definition $definition 132 * @return array 133 */ 134 public static function get_cache_stores(cache_definition $definition) { 135 $instance = cache_config::instance(); 136 $stores = $instance->get_stores_for_definition($definition); 137 $stores = self::initialise_cachestore_instances($stores, $definition); 138 return $stores; 139 } 140 141 /** 142 * Internal function for initialising an array of stores against a given cache definition. 143 * 144 * @param array $stores 145 * @param cache_definition $definition 146 * @return cache_store[] 147 */ 148 protected static function initialise_cachestore_instances(array $stores, cache_definition $definition) { 149 $return = array(); 150 $factory = cache_factory::instance(); 151 foreach ($stores as $name => $details) { 152 $store = $factory->create_store_from_config($name, $details, $definition); 153 if ($store !== false) { 154 $return[] = $store; 155 } 156 } 157 return $return; 158 } 159 160 /** 161 * Returns a cache_lock instance suitable for use with the store. 162 * 163 * @param cache_store $store 164 * @return cache_lock_interface 165 */ 166 public static function get_cachelock_for_store(cache_store $store) { 167 $instance = cache_config::instance(); 168 $lockconf = $instance->get_lock_for_store($store->my_name()); 169 $factory = cache_factory::instance(); 170 return $factory->create_lock_instance($lockconf); 171 } 172 173 /** 174 * Returns an array of plugins without using core methods. 175 * 176 * This function explicitly does NOT use core functions as it will in some circumstances be called before Moodle has 177 * finished initialising. This happens when loading configuration for instance. 178 * 179 * @return string 180 */ 181 public static function early_get_cache_plugins() { 182 global $CFG; 183 $result = array(); 184 $ignored = array('CVS', '_vti_cnf', 'simpletest', 'db', 'yui', 'tests'); 185 $fulldir = $CFG->dirroot.'/cache/stores'; 186 $items = new DirectoryIterator($fulldir); 187 foreach ($items as $item) { 188 if ($item->isDot() or !$item->isDir()) { 189 continue; 190 } 191 $pluginname = $item->getFilename(); 192 if (in_array($pluginname, $ignored)) { 193 continue; 194 } 195 if (!is_valid_plugin_name($pluginname)) { 196 // Better ignore plugins with problematic names here. 197 continue; 198 } 199 $result[$pluginname] = $fulldir.'/'.$pluginname; 200 unset($item); 201 } 202 unset($items); 203 return $result; 204 } 205 206 /** 207 * Invalidates a given set of keys from a given definition. 208 * 209 * @todo Invalidating by definition should also add to the event cache so that sessions can be invalidated (when required). 210 * 211 * @param string $component 212 * @param string $area 213 * @param array $identifiers 214 * @param array $keys 215 * @return boolean 216 */ 217 public static function invalidate_by_definition($component, $area, array $identifiers = array(), $keys = array()) { 218 $cache = cache::make($component, $area, $identifiers); 219 if (is_array($keys)) { 220 $cache->delete_many($keys); 221 } else if (is_scalar($keys)) { 222 $cache->delete($keys); 223 } else { 224 throw new coding_exception('cache_helper::invalidate_by_definition only accepts $keys as array, or scalar.'); 225 } 226 return true; 227 } 228 229 /** 230 * Invalidates a given set of keys by means of an event. 231 * 232 * @todo add support for identifiers to be supplied and utilised. 233 * 234 * @param string $event 235 * @param array $keys 236 */ 237 public static function invalidate_by_event($event, array $keys) { 238 $instance = cache_config::instance(); 239 $invalidationeventset = false; 240 $factory = cache_factory::instance(); 241 $inuse = $factory->get_caches_in_use(); 242 foreach ($instance->get_definitions() as $name => $definitionarr) { 243 $definition = cache_definition::load($name, $definitionarr); 244 if ($definition->invalidates_on_event($event)) { 245 // First up check if there is a cache loader for this definition already. 246 // If there is we need to invalidate the keys from there. 247 $definitionkey = $definition->get_component().'/'.$definition->get_area(); 248 if (isset($inuse[$definitionkey])) { 249 $inuse[$definitionkey]->delete_many($keys); 250 } 251 252 // We should only log events for application and session caches. 253 // Request caches shouldn't have events as all data is lost at the end of the request. 254 // Events should only be logged once of course and likely several definitions are watching so we 255 // track its logging with $invalidationeventset. 256 $logevent = ($invalidationeventset === false && $definition->get_mode() !== cache_store::MODE_REQUEST); 257 258 if ($logevent) { 259 // Get the event invalidation cache. 260 $cache = cache::make('core', 'eventinvalidation'); 261 // Get any existing invalidated keys for this cache. 262 $data = $cache->get($event); 263 if ($data === false) { 264 // There are none. 265 $data = array(); 266 } 267 // Add our keys to them with the current cache timestamp. 268 foreach ($keys as $key) { 269 $data[$key] = cache::now(); 270 } 271 // Set that data back to the cache. 272 $cache->set($event, $data); 273 // This only needs to occur once. 274 $invalidationeventset = true; 275 } 276 } 277 } 278 } 279 280 /** 281 * Purges the cache for a specific definition. 282 * 283 * If you need to purge a definition that requires identifiers or an aggregate and you don't 284 * know the details of those please use cache_helper::purge_stores_used_by_definition instead. 285 * It is a more aggressive purge and will purge all data within the store, not just the data 286 * belonging to the given definition. 287 * 288 * @todo MDL-36660: Change the signature: $aggregate must be added. 289 * 290 * @param string $component 291 * @param string $area 292 * @param array $identifiers 293 * @return bool 294 */ 295 public static function purge_by_definition($component, $area, array $identifiers = array()) { 296 // Create the cache. 297 $cache = cache::make($component, $area, $identifiers); 298 // Initialise, in case of a store. 299 if ($cache instanceof cache_store) { 300 $factory = cache_factory::instance(); 301 // TODO MDL-36660: Providing $aggregate is required for purging purposes: $definition->get_id() 302 $definition = $factory->create_definition($component, $area, null); 303 $definition->set_identifiers($identifiers); 304 $cache->initialise($definition); 305 } 306 // Purge baby, purge. 307 $cache->purge(); 308 return true; 309 } 310 311 /** 312 * Purges a cache of all information on a given event. 313 * 314 * @param string $event 315 */ 316 public static function purge_by_event($event) { 317 $instance = cache_config::instance(); 318 $invalidationeventset = false; 319 $factory = cache_factory::instance(); 320 $inuse = $factory->get_caches_in_use(); 321 foreach ($instance->get_definitions() as $name => $definitionarr) { 322 $definition = cache_definition::load($name, $definitionarr); 323 if ($definition->invalidates_on_event($event)) { 324 // First up check if there is a cache loader for this definition already. 325 // If there is we need to invalidate the keys from there. 326 $definitionkey = $definition->get_component().'/'.$definition->get_area(); 327 if (isset($inuse[$definitionkey])) { 328 $inuse[$definitionkey]->purge(); 329 } 330 331 // We should only log events for application and session caches. 332 // Request caches shouldn't have events as all data is lost at the end of the request. 333 // Events should only be logged once of course and likely several definitions are watching so we 334 // track its logging with $invalidationeventset. 335 $logevent = ($invalidationeventset === false && $definition->get_mode() !== cache_store::MODE_REQUEST); 336 337 // We need to flag the event in the "Event invalidation" cache if it hasn't already happened. 338 if ($logevent && $invalidationeventset === false) { 339 // Get the event invalidation cache. 340 $cache = cache::make('core', 'eventinvalidation'); 341 // Create a key to invalidate all. 342 $data = array( 343 'purged' => cache::now() 344 ); 345 // Set that data back to the cache. 346 $cache->set($event, $data); 347 // This only needs to occur once. 348 $invalidationeventset = true; 349 } 350 } 351 } 352 } 353 354 /** 355 * Ensure that the stats array is ready to collect information for the given store and definition. 356 * @param string $store 357 * @param string $definition 358 */ 359 protected static function ensure_ready_for_stats($store, $definition) { 360 // This function is performance-sensitive, so exit as quickly as possible 361 // if we do not need to do anything. 362 if (isset(self::$stats[$definition][$store])) { 363 return; 364 } 365 if (!array_key_exists($definition, self::$stats)) { 366 self::$stats[$definition] = array( 367 $store => array( 368 'hits' => 0, 369 'misses' => 0, 370 'sets' => 0, 371 ) 372 ); 373 } else if (!array_key_exists($store, self::$stats[$definition])) { 374 self::$stats[$definition][$store] = array( 375 'hits' => 0, 376 'misses' => 0, 377 'sets' => 0, 378 ); 379 } 380 } 381 382 /** 383 * Record a cache hit in the stats for the given store and definition. 384 * 385 * @internal 386 * @param string $store 387 * @param string $definition 388 * @param int $hits The number of hits to record (by default 1) 389 */ 390 public static function record_cache_hit($store, $definition, $hits = 1) { 391 self::ensure_ready_for_stats($store, $definition); 392 self::$stats[$definition][$store]['hits'] += $hits; 393 } 394 395 /** 396 * Record a cache miss in the stats for the given store and definition. 397 * 398 * @internal 399 * @param string $store 400 * @param string $definition 401 * @param int $misses The number of misses to record (by default 1) 402 */ 403 public static function record_cache_miss($store, $definition, $misses = 1) { 404 self::ensure_ready_for_stats($store, $definition); 405 self::$stats[$definition][$store]['misses'] += $misses; 406 } 407 408 /** 409 * Record a cache set in the stats for the given store and definition. 410 * 411 * @internal 412 * @param string $store 413 * @param string $definition 414 * @param int $sets The number of sets to record (by default 1) 415 */ 416 public static function record_cache_set($store, $definition, $sets = 1) { 417 self::ensure_ready_for_stats($store, $definition); 418 self::$stats[$definition][$store]['sets'] += $sets; 419 } 420 421 /** 422 * Return the stats collected so far. 423 * @return array 424 */ 425 public static function get_stats() { 426 return self::$stats; 427 } 428 429 /** 430 * Purge all of the cache stores of all of their data. 431 * 432 * Think twice before calling this method. It will purge **ALL** caches regardless of whether they have been used recently or 433 * anything. This will involve full setup of the cache + the purge operation. On a site using caching heavily this WILL be 434 * painful. 435 * 436 * @param bool $usewriter If set to true the cache_config_writer class is used. This class is special as it avoids 437 * it is still usable when caches have been disabled. 438 * Please use this option only if you really must. It's purpose is to allow the cache to be purged when it would be 439 * otherwise impossible. 440 */ 441 public static function purge_all($usewriter = false) { 442 $factory = cache_factory::instance(); 443 $config = $factory->create_config_instance($usewriter); 444 foreach ($config->get_all_stores() as $store) { 445 self::purge_store($store['name'], $config); 446 } 447 } 448 449 /** 450 * Purges a store given its name. 451 * 452 * @param string $storename 453 * @param cache_config $config 454 * @return bool 455 */ 456 public static function purge_store($storename, cache_config $config = null) { 457 if ($config === null) { 458 $config = cache_config::instance(); 459 } 460 461 $stores = $config->get_all_stores(); 462 if (!array_key_exists($storename, $stores)) { 463 // The store does not exist. 464 return false; 465 } 466 467 $store = $stores[$storename]; 468 $class = $store['class']; 469 470 // Found the store: is it ready? 471 /* @var cache_store $instance */ 472 $instance = new $class($store['name'], $store['configuration']); 473 // We check are_requirements_met although we expect is_ready is going to check as well. 474 if (!$instance::are_requirements_met() || !$instance->is_ready()) { 475 unset($instance); 476 return false; 477 } 478 479 foreach ($config->get_definitions_by_store($storename) as $id => $definition) { 480 $definition = cache_definition::load($id, $definition); 481 $definitioninstance = clone($instance); 482 $definitioninstance->initialise($definition); 483 $definitioninstance->purge(); 484 unset($definitioninstance); 485 } 486 487 return true; 488 } 489 490 /** 491 * Purges all of the stores used by a definition. 492 * 493 * Unlike cache_helper::purge_by_definition this purges all of the data from the stores not 494 * just the data relating to the definition. 495 * This function is useful when you must purge a definition that requires setup but you don't 496 * want to set it up. 497 * 498 * @param string $component 499 * @param string $area 500 */ 501 public static function purge_stores_used_by_definition($component, $area) { 502 $factory = cache_factory::instance(); 503 $config = $factory->create_config_instance(); 504 $definition = $factory->create_definition($component, $area); 505 $stores = $config->get_stores_for_definition($definition); 506 foreach ($stores as $store) { 507 self::purge_store($store['name']); 508 } 509 } 510 511 /** 512 * Returns the translated name of the definition. 513 * 514 * @param cache_definition $definition 515 * @return lang_string 516 */ 517 public static function get_definition_name($definition) { 518 if ($definition instanceof cache_definition) { 519 return $definition->get_name(); 520 } 521 $identifier = 'cachedef_'.clean_param($definition['area'], PARAM_STRINGID); 522 $component = $definition['component']; 523 if ($component === 'core') { 524 $component = 'cache'; 525 } 526 return new lang_string($identifier, $component); 527 } 528 529 /** 530 * Hashes a descriptive key to make it shorter and still unique. 531 * @param string|int $key 532 * @param cache_definition $definition 533 * @return string 534 */ 535 public static function hash_key($key, cache_definition $definition) { 536 if ($definition->uses_simple_keys()) { 537 if (debugging() && preg_match('#[^a-zA-Z0-9_]#', $key)) { 538 throw new coding_exception('Cache definition '.$definition->get_id().' requires simple keys. Invalid key provided.', $key); 539 } 540 // We put the key first so that we can be sure the start of the key changes. 541 return (string)$key . '-' . $definition->generate_single_key_prefix(); 542 } 543 $key = $definition->generate_single_key_prefix() . '-' . $key; 544 return sha1($key); 545 } 546 547 /** 548 * Finds all definitions and updates them within the cache config file. 549 * 550 * @param bool $coreonly If set to true only core definitions will be updated. 551 */ 552 public static function update_definitions($coreonly = false) { 553 global $CFG; 554 // Include locallib. 555 require_once($CFG->dirroot.'/cache/locallib.php'); 556 // First update definitions 557 cache_config_writer::update_definitions($coreonly); 558 // Second reset anything we have already initialised to ensure we're all up to date. 559 cache_factory::reset(); 560 } 561 562 /** 563 * Update the site identifier stored by the cache API. 564 * 565 * @param string $siteidentifier 566 * @return string The new site identifier. 567 */ 568 public static function update_site_identifier($siteidentifier) { 569 global $CFG; 570 // Include locallib. 571 require_once($CFG->dirroot.'/cache/locallib.php'); 572 $factory = cache_factory::instance(); 573 $factory->updating_started(); 574 $config = $factory->create_config_instance(true); 575 $siteidentifier = $config->update_site_identifier($siteidentifier); 576 $factory->updating_finished(); 577 cache_factory::reset(); 578 return $siteidentifier; 579 } 580 581 /** 582 * Returns the site identifier. 583 * 584 * @return string 585 */ 586 public static function get_site_identifier() { 587 global $CFG; 588 if (!is_null(self::$siteidentifier)) { 589 return self::$siteidentifier; 590 } 591 // If site identifier hasn't been collected yet attempt to get it from the cache config. 592 $factory = cache_factory::instance(); 593 // If the factory is initialising then we don't want to try to get it from the config or we risk 594 // causing the cache to enter an infinite initialisation loop. 595 if (!$factory->is_initialising()) { 596 $config = $factory->create_config_instance(); 597 self::$siteidentifier = $config->get_site_identifier(); 598 } 599 if (is_null(self::$siteidentifier)) { 600 // If the site identifier is still null then config isn't aware of it yet. 601 // We'll see if the CFG is loaded, and if not we will just use unknown. 602 // It's very important here that we don't use get_config. We don't want an endless cache loop! 603 if (!empty($CFG->siteidentifier)) { 604 self::$siteidentifier = self::update_site_identifier($CFG->siteidentifier); 605 } else { 606 // It's not being recorded in MUC's config and the config data hasn't been loaded yet. 607 // Likely we are initialising. 608 return 'unknown'; 609 } 610 } 611 return self::$siteidentifier; 612 } 613 614 /** 615 * Returns the site version. 616 * 617 * @return string 618 */ 619 public static function get_site_version() { 620 global $CFG; 621 return (string)$CFG->version; 622 } 623 624 /** 625 * Runs cron routines for MUC. 626 */ 627 public static function cron() { 628 self::clean_old_session_data(true); 629 } 630 631 /** 632 * Cleans old session data from cache stores used for session based definitions. 633 * 634 * @param bool $output If set to true output will be given. 635 */ 636 public static function clean_old_session_data($output = false) { 637 global $CFG; 638 if ($output) { 639 mtrace('Cleaning up stale session data from cache stores.'); 640 } 641 $factory = cache_factory::instance(); 642 $config = $factory->create_config_instance(); 643 $definitions = $config->get_definitions(); 644 $purgetime = time() - $CFG->sessiontimeout; 645 foreach ($definitions as $definitionarray) { 646 // We are only interested in session caches. 647 if (!($definitionarray['mode'] & cache_store::MODE_SESSION)) { 648 continue; 649 } 650 $definition = $factory->create_definition($definitionarray['component'], $definitionarray['area']); 651 $stores = $config->get_stores_for_definition($definition); 652 // Turn them into store instances. 653 $stores = self::initialise_cachestore_instances($stores, $definition); 654 // Initialise all of the stores used for that definition. 655 foreach ($stores as $store) { 656 // If the store doesn't support searching we can skip it. 657 if (!($store instanceof cache_is_searchable)) { 658 debugging('Cache stores used for session definitions should ideally be searchable.', DEBUG_DEVELOPER); 659 continue; 660 } 661 // Get all of the keys. 662 $keys = $store->find_by_prefix(cache_session::KEY_PREFIX); 663 $todelete = array(); 664 foreach ($store->get_many($keys) as $key => $value) { 665 if (strpos($key, cache_session::KEY_PREFIX) !== 0 || !is_array($value) || !isset($value['lastaccess'])) { 666 continue; 667 } 668 if ((int)$value['lastaccess'] < $purgetime || true) { 669 $todelete[] = $key; 670 } 671 } 672 if (count($todelete)) { 673 $outcome = (int)$store->delete_many($todelete); 674 if ($output) { 675 $strdef = s($definition->get_id()); 676 $strstore = s($store->my_name()); 677 mtrace("- Removed {$outcome} old {$strdef} sessions from the '{$strstore}' cache store."); 678 } 679 } 680 } 681 } 682 } 683 684 /** 685 * Returns an array of stores that would meet the requirements for every definition. 686 * 687 * These stores would be 100% suitable to map as defaults for cache modes. 688 * 689 * @return array[] An array of stores, keys are the store names. 690 */ 691 public static function get_stores_suitable_for_mode_default() { 692 $factory = cache_factory::instance(); 693 $config = $factory->create_config_instance(); 694 $requirements = 0; 695 foreach ($config->get_definitions() as $definition) { 696 $definition = cache_definition::load($definition['component'].'/'.$definition['area'], $definition); 697 $requirements = $requirements | $definition->get_requirements_bin(); 698 } 699 $stores = array(); 700 foreach ($config->get_all_stores() as $name => $store) { 701 if (!empty($store['features']) && ($store['features'] & $requirements)) { 702 $stores[$name] = $store; 703 } 704 } 705 return $stores; 706 } 707 708 /** 709 * Returns stores suitable for use with a given definition. 710 * 711 * @param cache_definition $definition 712 * @return cache_store[] 713 */ 714 public static function get_stores_suitable_for_definition(cache_definition $definition) { 715 $factory = cache_factory::instance(); 716 $stores = array(); 717 if ($factory->is_initialising() || $factory->stores_disabled()) { 718 // No suitable stores here. 719 return $stores; 720 } else { 721 $stores = self::get_cache_stores($definition); 722 // If mappingsonly is set, having 0 stores is ok. 723 if ((count($stores) === 0) && (!$definition->is_for_mappings_only())) { 724 // No suitable stores we found for the definition. We need to come up with a sensible default. 725 // If this has happened we can be sure that the user has mapped custom stores to either the 726 // mode of the definition. The first alternative to try is the system default for the mode. 727 // e.g. the default file store instance for application definitions. 728 $config = $factory->create_config_instance(); 729 foreach ($config->get_stores($definition->get_mode()) as $name => $details) { 730 if (!empty($details['default'])) { 731 $stores[] = $factory->create_store_from_config($name, $details, $definition); 732 break; 733 } 734 } 735 } 736 } 737 return $stores; 738 } 739 740 /** 741 * Returns an array of warnings from the cache API. 742 * 743 * The warning returned here are for things like conflicting store instance configurations etc. 744 * These get shown on the admin notifications page for example. 745 * 746 * @param array|null $stores An array of stores to get warnings for, or null for all. 747 * @return string[] 748 */ 749 public static function warnings(array $stores = null) { 750 global $CFG; 751 if ($stores === null) { 752 require_once($CFG->dirroot.'/cache/locallib.php'); 753 $stores = cache_administration_helper::get_store_instance_summaries(); 754 } 755 $warnings = array(); 756 foreach ($stores as $store) { 757 if (!empty($store['warnings'])) { 758 $warnings = array_merge($warnings, $store['warnings']); 759 } 760 } 761 return $warnings; 762 } 763 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 20:29:05 2014 | Cross-referenced by PHPXref 0.7.1 |