You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
818 lines
32 KiB
818 lines
32 KiB
2 years ago
|
<?php
|
||
|
// This file is part of Moodle - http://moodle.org/
|
||
|
//
|
||
|
// Moodle is free software: you can redistribute it and/or modify
|
||
|
// it under the terms of the GNU General Public License as published by
|
||
|
// the Free Software Foundation, either version 3 of the License, or
|
||
|
// (at your option) any later version.
|
||
|
//
|
||
|
// Moodle is distributed in the hope that it will be useful,
|
||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
// GNU General Public License for more details.
|
||
|
//
|
||
|
// You should have received a copy of the GNU General Public License
|
||
|
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||
|
|
||
|
/**
|
||
|
* Cache helper class
|
||
|
*
|
||
|
* This file is part of Moodle's cache API, affectionately called MUC.
|
||
|
* It contains the components that are requried in order to use caching.
|
||
|
*
|
||
|
* @package core
|
||
|
* @category cache
|
||
|
* @copyright 2012 Sam Hemelryk
|
||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||
|
*/
|
||
|
|
||
|
defined('MOODLE_INTERNAL') || die();
|
||
|
|
||
|
/**
|
||
|
* The cache helper class.
|
||
|
*
|
||
|
* The cache helper class provides common functionality to the cache API and is useful to developers within to interact with
|
||
|
* the cache API in a general way.
|
||
|
*
|
||
|
* @package core
|
||
|
* @category cache
|
||
|
* @copyright 2012 Sam Hemelryk
|
||
|
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||
|
*/
|
||
|
class cache_helper {
|
||
|
|
||
|
/**
|
||
|
* Statistics gathered by the cache API during its operation will be used here.
|
||
|
* @static
|
||
|
* @var array
|
||
|
*/
|
||
|
protected static $stats = array();
|
||
|
|
||
|
/**
|
||
|
* The instance of the cache helper.
|
||
|
* @var cache_helper
|
||
|
*/
|
||
|
protected static $instance;
|
||
|
|
||
|
/**
|
||
|
* The site identifier used by the cache.
|
||
|
* Set the first time get_site_identifier is called.
|
||
|
* @var string
|
||
|
*/
|
||
|
protected static $siteidentifier = null;
|
||
|
|
||
|
/**
|
||
|
* Returns true if the cache API can be initialised before Moodle has finished initialising itself.
|
||
|
*
|
||
|
* This check is essential when trying to cache the likes of configuration information. It checks to make sure that the cache
|
||
|
* configuration file has been created which allows use to set up caching when ever is required.
|
||
|
*
|
||
|
* @return bool
|
||
|
*/
|
||
|
public static function ready_for_early_init() {
|
||
|
return cache_config::config_file_exists();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns an instance of the cache_helper.
|
||
|
*
|
||
|
* This is designed for internal use only and acts as a static store.
|
||
|
* @staticvar null $instance
|
||
|
* @return cache_helper
|
||
|
*/
|
||
|
protected static function instance() {
|
||
|
if (is_null(self::$instance)) {
|
||
|
self::$instance = new cache_helper();
|
||
|
}
|
||
|
return self::$instance;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Constructs an instance of the cache_helper class. Again for internal use only.
|
||
|
*/
|
||
|
protected function __construct() {
|
||
|
// Nothing to do here, just making sure you can't get an instance of this.
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Used as a data store for initialised definitions.
|
||
|
* @var array
|
||
|
*/
|
||
|
protected $definitions = array();
|
||
|
|
||
|
/**
|
||
|
* Used as a data store for initialised cache stores
|
||
|
* We use this because we want to avoid establishing multiple instances of a single store.
|
||
|
* @var array
|
||
|
*/
|
||
|
protected $stores = array();
|
||
|
|
||
|
/**
|
||
|
* Returns the class for use as a cache loader for the given mode.
|
||
|
*
|
||
|
* @param int $mode One of cache_store::MODE_
|
||
|
* @return string
|
||
|
* @throws coding_exception
|
||
|
*/
|
||
|
public static function get_class_for_mode($mode) {
|
||
|
switch ($mode) {
|
||
|
case cache_store::MODE_APPLICATION :
|
||
|
return 'cache_application';
|
||
|
case cache_store::MODE_REQUEST :
|
||
|
return 'cache_request';
|
||
|
case cache_store::MODE_SESSION :
|
||
|
return 'cache_session';
|
||
|
}
|
||
|
throw new coding_exception('Unknown cache mode passed. Must be one of cache_store::MODE_*');
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the cache stores to be used with the given definition.
|
||
|
* @param cache_definition $definition
|
||
|
* @return array
|
||
|
*/
|
||
|
public static function get_cache_stores(cache_definition $definition) {
|
||
|
$instance = cache_config::instance();
|
||
|
$stores = $instance->get_stores_for_definition($definition);
|
||
|
$stores = self::initialise_cachestore_instances($stores, $definition);
|
||
|
return $stores;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Internal function for initialising an array of stores against a given cache definition.
|
||
|
*
|
||
|
* @param array $stores
|
||
|
* @param cache_definition $definition
|
||
|
* @return cache_store[]
|
||
|
*/
|
||
|
protected static function initialise_cachestore_instances(array $stores, cache_definition $definition) {
|
||
|
$return = array();
|
||
|
$factory = cache_factory::instance();
|
||
|
foreach ($stores as $name => $details) {
|
||
|
$store = $factory->create_store_from_config($name, $details, $definition);
|
||
|
if ($store !== false) {
|
||
|
$return[] = $store;
|
||
|
}
|
||
|
}
|
||
|
return $return;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a cache_lock instance suitable for use with the store.
|
||
|
*
|
||
|
* @param cache_store $store
|
||
|
* @return cache_lock_interface
|
||
|
*/
|
||
|
public static function get_cachelock_for_store(cache_store $store) {
|
||
|
$instance = cache_config::instance();
|
||
|
$lockconf = $instance->get_lock_for_store($store->my_name());
|
||
|
$factory = cache_factory::instance();
|
||
|
return $factory->create_lock_instance($lockconf);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns an array of plugins without using core methods.
|
||
|
*
|
||
|
* This function explicitly does NOT use core functions as it will in some circumstances be called before Moodle has
|
||
|
* finished initialising. This happens when loading configuration for instance.
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
public static function early_get_cache_plugins() {
|
||
|
global $CFG;
|
||
|
$result = array();
|
||
|
$ignored = array('CVS', '_vti_cnf', 'simpletest', 'db', 'yui', 'tests');
|
||
|
$fulldir = $CFG->dirroot.'/cache/stores';
|
||
|
$items = new DirectoryIterator($fulldir);
|
||
|
foreach ($items as $item) {
|
||
|
if ($item->isDot() or !$item->isDir()) {
|
||
|
continue;
|
||
|
}
|
||
|
$pluginname = $item->getFilename();
|
||
|
if (in_array($pluginname, $ignored)) {
|
||
|
continue;
|
||
|
}
|
||
|
if (!is_valid_plugin_name($pluginname)) {
|
||
|
// Better ignore plugins with problematic names here.
|
||
|
continue;
|
||
|
}
|
||
|
$result[$pluginname] = $fulldir.'/'.$pluginname;
|
||
|
unset($item);
|
||
|
}
|
||
|
unset($items);
|
||
|
return $result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Invalidates a given set of keys from a given definition.
|
||
|
*
|
||
|
* @todo Invalidating by definition should also add to the event cache so that sessions can be invalidated (when required).
|
||
|
*
|
||
|
* @param string $component
|
||
|
* @param string $area
|
||
|
* @param array $identifiers
|
||
|
* @param array $keys
|
||
|
* @return boolean
|
||
|
*/
|
||
|
public static function invalidate_by_definition($component, $area, array $identifiers = array(), $keys = array()) {
|
||
|
$cache = cache::make($component, $area, $identifiers);
|
||
|
if (is_array($keys)) {
|
||
|
$cache->delete_many($keys);
|
||
|
} else if (is_scalar($keys)) {
|
||
|
$cache->delete($keys);
|
||
|
} else {
|
||
|
throw new coding_exception('cache_helper::invalidate_by_definition only accepts $keys as array, or scalar.');
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Invalidates a given set of keys by means of an event.
|
||
|
*
|
||
|
* Events cannot determine what identifiers might need to be cleared. Event based purge and invalidation
|
||
|
* are only supported on caches without identifiers.
|
||
|
*
|
||
|
* @param string $event
|
||
|
* @param array $keys
|
||
|
*/
|
||
|
public static function invalidate_by_event($event, array $keys) {
|
||
|
$instance = cache_config::instance();
|
||
|
$invalidationeventset = false;
|
||
|
$factory = cache_factory::instance();
|
||
|
$inuse = $factory->get_caches_in_use();
|
||
|
$purgetoken = null;
|
||
|
foreach ($instance->get_definitions() as $name => $definitionarr) {
|
||
|
$definition = cache_definition::load($name, $definitionarr);
|
||
|
if ($definition->invalidates_on_event($event)) {
|
||
|
// First up check if there is a cache loader for this definition already.
|
||
|
// If there is we need to invalidate the keys from there.
|
||
|
$definitionkey = $definition->get_component().'/'.$definition->get_area();
|
||
|
if (isset($inuse[$definitionkey])) {
|
||
|
$inuse[$definitionkey]->delete_many($keys);
|
||
|
}
|
||
|
|
||
|
// We should only log events for application and session caches.
|
||
|
// Request caches shouldn't have events as all data is lost at the end of the request.
|
||
|
// Events should only be logged once of course and likely several definitions are watching so we
|
||
|
// track its logging with $invalidationeventset.
|
||
|
$logevent = ($invalidationeventset === false && $definition->get_mode() !== cache_store::MODE_REQUEST);
|
||
|
|
||
|
if ($logevent) {
|
||
|
// Get the event invalidation cache.
|
||
|
$cache = cache::make('core', 'eventinvalidation');
|
||
|
// Get any existing invalidated keys for this cache.
|
||
|
$data = $cache->get($event);
|
||
|
if ($data === false) {
|
||
|
// There are none.
|
||
|
$data = array();
|
||
|
}
|
||
|
// Add our keys to them with the current cache timestamp.
|
||
|
if (null === $purgetoken) {
|
||
|
$purgetoken = cache::get_purge_token(true);
|
||
|
}
|
||
|
foreach ($keys as $key) {
|
||
|
$data[$key] = $purgetoken;
|
||
|
}
|
||
|
// Set that data back to the cache.
|
||
|
$cache->set($event, $data);
|
||
|
// This only needs to occur once.
|
||
|
$invalidationeventset = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Purges the cache for a specific definition.
|
||
|
*
|
||
|
* @param string $component
|
||
|
* @param string $area
|
||
|
* @param array $identifiers
|
||
|
* @return bool
|
||
|
*/
|
||
|
public static function purge_by_definition($component, $area, array $identifiers = array()) {
|
||
|
// Create the cache.
|
||
|
$cache = cache::make($component, $area, $identifiers);
|
||
|
// Initialise, in case of a store.
|
||
|
if ($cache instanceof cache_store) {
|
||
|
$factory = cache_factory::instance();
|
||
|
$definition = $factory->create_definition($component, $area, null);
|
||
|
$cacheddefinition = clone $definition;
|
||
|
$cacheddefinition->set_identifiers($identifiers);
|
||
|
$cache->initialise($cacheddefinition);
|
||
|
}
|
||
|
// Purge baby, purge.
|
||
|
$cache->purge();
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Purges a cache of all information on a given event.
|
||
|
*
|
||
|
* Events cannot determine what identifiers might need to be cleared. Event based purge and invalidation
|
||
|
* are only supported on caches without identifiers.
|
||
|
*
|
||
|
* @param string $event
|
||
|
*/
|
||
|
public static function purge_by_event($event) {
|
||
|
$instance = cache_config::instance();
|
||
|
$invalidationeventset = false;
|
||
|
$factory = cache_factory::instance();
|
||
|
$inuse = $factory->get_caches_in_use();
|
||
|
$purgetoken = null;
|
||
|
foreach ($instance->get_definitions() as $name => $definitionarr) {
|
||
|
$definition = cache_definition::load($name, $definitionarr);
|
||
|
if ($definition->invalidates_on_event($event)) {
|
||
|
// First up check if there is a cache loader for this definition already.
|
||
|
// If there is we need to invalidate the keys from there.
|
||
|
$definitionkey = $definition->get_component().'/'.$definition->get_area();
|
||
|
if (isset($inuse[$definitionkey])) {
|
||
|
$inuse[$definitionkey]->purge();
|
||
|
} else {
|
||
|
cache::make($definition->get_component(), $definition->get_area())->purge();
|
||
|
}
|
||
|
|
||
|
// We should only log events for application and session caches.
|
||
|
// Request caches shouldn't have events as all data is lost at the end of the request.
|
||
|
// Events should only be logged once of course and likely several definitions are watching so we
|
||
|
// track its logging with $invalidationeventset.
|
||
|
$logevent = ($invalidationeventset === false && $definition->get_mode() !== cache_store::MODE_REQUEST);
|
||
|
|
||
|
// We need to flag the event in the "Event invalidation" cache if it hasn't already happened.
|
||
|
if ($logevent && $invalidationeventset === false) {
|
||
|
// Get the event invalidation cache.
|
||
|
$cache = cache::make('core', 'eventinvalidation');
|
||
|
// Create a key to invalidate all.
|
||
|
if (null === $purgetoken) {
|
||
|
$purgetoken = cache::get_purge_token(true);
|
||
|
}
|
||
|
$data = array(
|
||
|
'purged' => $purgetoken,
|
||
|
);
|
||
|
// Set that data back to the cache.
|
||
|
$cache->set($event, $data);
|
||
|
// This only needs to occur once.
|
||
|
$invalidationeventset = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Ensure that the stats array is ready to collect information for the given store and definition.
|
||
|
* @param string $store
|
||
|
* @param string $definition A string that identifies the definition.
|
||
|
* @param int $mode One of cache_store::MODE_*. Since 2.9.
|
||
|
*/
|
||
|
protected static function ensure_ready_for_stats($store, $definition, $mode = cache_store::MODE_APPLICATION) {
|
||
|
// This function is performance-sensitive, so exit as quickly as possible
|
||
|
// if we do not need to do anything.
|
||
|
if (isset(self::$stats[$definition]['stores'][$store])) {
|
||
|
return;
|
||
|
}
|
||
|
if (!array_key_exists($definition, self::$stats)) {
|
||
|
self::$stats[$definition] = array(
|
||
|
'mode' => $mode,
|
||
|
'stores' => array(
|
||
|
$store => array(
|
||
|
'hits' => 0,
|
||
|
'misses' => 0,
|
||
|
'sets' => 0,
|
||
|
)
|
||
|
)
|
||
|
);
|
||
|
} else if (!array_key_exists($store, self::$stats[$definition]['stores'])) {
|
||
|
self::$stats[$definition]['stores'][$store] = array(
|
||
|
'hits' => 0,
|
||
|
'misses' => 0,
|
||
|
'sets' => 0,
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns a string to describe the definition.
|
||
|
*
|
||
|
* This method supports the definition as a string due to legacy requirements.
|
||
|
* It is backwards compatible when a string is passed but is not accurate.
|
||
|
*
|
||
|
* @since 2.9
|
||
|
* @param cache_definition|string $definition
|
||
|
* @return string
|
||
|
*/
|
||
|
protected static function get_definition_stat_id_and_mode($definition) {
|
||
|
if (!($definition instanceof cache_definition)) {
|
||
|
// All core calls to this method have been updated, this is the legacy state.
|
||
|
// We'll use application as the default as that is the most common, really this is not accurate of course but
|
||
|
// at this point we can only guess and as it only affects calls to cache stat outside of core (of which there should
|
||
|
// be none) I think that is fine.
|
||
|
debugging('Please update you cache stat calls to pass the definition rather than just its ID.', DEBUG_DEVELOPER);
|
||
|
return array((string)$definition, cache_store::MODE_APPLICATION);
|
||
|
}
|
||
|
return array($definition->get_id(), $definition->get_mode());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Record a cache hit in the stats for the given store and definition.
|
||
|
*
|
||
|
* In Moodle 2.9 the $definition argument changed from accepting only a string to accepting a string or a
|
||
|
* cache_definition instance. It is preferable to pass a cache definition instance.
|
||
|
*
|
||
|
* @internal
|
||
|
* @param cache_definition $store
|
||
|
* @param cache_definition $definition You used to be able to pass a string here, however that is deprecated please pass the
|
||
|
* actual cache_definition object now.
|
||
|
* @param int $hits The number of hits to record (by default 1)
|
||
|
*/
|
||
|
public static function record_cache_hit($store, $definition, $hits = 1) {
|
||
|
list($definitionstr, $mode) = self::get_definition_stat_id_and_mode($definition);
|
||
|
self::ensure_ready_for_stats($store, $definitionstr, $mode);
|
||
|
self::$stats[$definitionstr]['stores'][$store]['hits'] += $hits;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Record a cache miss in the stats for the given store and definition.
|
||
|
*
|
||
|
* In Moodle 2.9 the $definition argument changed from accepting only a string to accepting a string or a
|
||
|
* cache_definition instance. It is preferable to pass a cache definition instance.
|
||
|
*
|
||
|
* @internal
|
||
|
* @param string $store
|
||
|
* @param cache_definition $definition You used to be able to pass a string here, however that is deprecated please pass the
|
||
|
* actual cache_definition object now.
|
||
|
* @param int $misses The number of misses to record (by default 1)
|
||
|
*/
|
||
|
public static function record_cache_miss($store, $definition, $misses = 1) {
|
||
|
list($definitionstr, $mode) = self::get_definition_stat_id_and_mode($definition);
|
||
|
self::ensure_ready_for_stats($store, $definitionstr, $mode);
|
||
|
self::$stats[$definitionstr]['stores'][$store]['misses'] += $misses;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Record a cache set in the stats for the given store and definition.
|
||
|
*
|
||
|
* In Moodle 2.9 the $definition argument changed from accepting only a string to accepting a string or a
|
||
|
* cache_definition instance. It is preferable to pass a cache definition instance.
|
||
|
*
|
||
|
* @internal
|
||
|
* @param string $store
|
||
|
* @param cache_definition $definition You used to be able to pass a string here, however that is deprecated please pass the
|
||
|
* actual cache_definition object now.
|
||
|
* @param int $sets The number of sets to record (by default 1)
|
||
|
*/
|
||
|
public static function record_cache_set($store, $definition, $sets = 1) {
|
||
|
list($definitionstr, $mode) = self::get_definition_stat_id_and_mode($definition);
|
||
|
self::ensure_ready_for_stats($store, $definitionstr, $mode);
|
||
|
self::$stats[$definitionstr]['stores'][$store]['sets'] += $sets;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return the stats collected so far.
|
||
|
* @return array
|
||
|
*/
|
||
|
public static function get_stats() {
|
||
|
return self::$stats;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Purge all of the cache stores of all of their data.
|
||
|
*
|
||
|
* Think twice before calling this method. It will purge **ALL** caches regardless of whether they have been used recently or
|
||
|
* anything. This will involve full setup of the cache + the purge operation. On a site using caching heavily this WILL be
|
||
|
* painful.
|
||
|
*
|
||
|
* @param bool $usewriter If set to true the cache_config_writer class is used. This class is special as it avoids
|
||
|
* it is still usable when caches have been disabled.
|
||
|
* Please use this option only if you really must. It's purpose is to allow the cache to be purged when it would be
|
||
|
* otherwise impossible.
|
||
|
*/
|
||
|
public static function purge_all($usewriter = false) {
|
||
|
$factory = cache_factory::instance();
|
||
|
$config = $factory->create_config_instance($usewriter);
|
||
|
foreach ($config->get_all_stores() as $store) {
|
||
|
self::purge_store($store['name'], $config);
|
||
|
}
|
||
|
foreach ($factory->get_adhoc_caches_in_use() as $cache) {
|
||
|
$cache->purge();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Purges a store given its name.
|
||
|
*
|
||
|
* @param string $storename
|
||
|
* @param cache_config $config
|
||
|
* @return bool
|
||
|
*/
|
||
|
public static function purge_store($storename, cache_config $config = null) {
|
||
|
if ($config === null) {
|
||
|
$config = cache_config::instance();
|
||
|
}
|
||
|
|
||
|
$stores = $config->get_all_stores();
|
||
|
if (!array_key_exists($storename, $stores)) {
|
||
|
// The store does not exist.
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
$store = $stores[$storename];
|
||
|
$class = $store['class'];
|
||
|
|
||
|
|
||
|
// We check are_requirements_met although we expect is_ready is going to check as well.
|
||
|
if (!$class::are_requirements_met()) {
|
||
|
return false;
|
||
|
}
|
||
|
// Found the store: is it ready?
|
||
|
/* @var cache_store $instance */
|
||
|
$instance = new $class($store['name'], $store['configuration']);
|
||
|
if (!$instance->is_ready()) {
|
||
|
unset($instance);
|
||
|
return false;
|
||
|
}
|
||
|
foreach ($config->get_definitions_by_store($storename) as $id => $definition) {
|
||
|
$definition = cache_definition::load($id, $definition);
|
||
|
$definitioninstance = clone($instance);
|
||
|
$definitioninstance->initialise($definition);
|
||
|
$definitioninstance->purge();
|
||
|
unset($definitioninstance);
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Purges all of the stores used by a definition.
|
||
|
*
|
||
|
* Unlike cache_helper::purge_by_definition this purges all of the data from the stores not
|
||
|
* just the data relating to the definition.
|
||
|
* This function is useful when you must purge a definition that requires setup but you don't
|
||
|
* want to set it up.
|
||
|
*
|
||
|
* @param string $component
|
||
|
* @param string $area
|
||
|
*/
|
||
|
public static function purge_stores_used_by_definition($component, $area) {
|
||
|
$factory = cache_factory::instance();
|
||
|
$config = $factory->create_config_instance();
|
||
|
$definition = $factory->create_definition($component, $area);
|
||
|
$stores = $config->get_stores_for_definition($definition);
|
||
|
foreach ($stores as $store) {
|
||
|
self::purge_store($store['name']);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the translated name of the definition.
|
||
|
*
|
||
|
* @param cache_definition $definition
|
||
|
* @return lang_string
|
||
|
*/
|
||
|
public static function get_definition_name($definition) {
|
||
|
if ($definition instanceof cache_definition) {
|
||
|
return $definition->get_name();
|
||
|
}
|
||
|
$identifier = 'cachedef_'.clean_param($definition['area'], PARAM_STRINGID);
|
||
|
$component = $definition['component'];
|
||
|
if ($component === 'core') {
|
||
|
$component = 'cache';
|
||
|
}
|
||
|
return new lang_string($identifier, $component);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Hashes a descriptive key to make it shorter and still unique.
|
||
|
* @param string|int $key
|
||
|
* @param cache_definition $definition
|
||
|
* @return string
|
||
|
*/
|
||
|
public static function hash_key($key, cache_definition $definition) {
|
||
|
if ($definition->uses_simple_keys()) {
|
||
|
if (debugging() && preg_match('#[^a-zA-Z0-9_]#', $key)) {
|
||
|
throw new coding_exception('Cache definition '.$definition->get_id().' requires simple keys. Invalid key provided.', $key);
|
||
|
}
|
||
|
// We put the key first so that we can be sure the start of the key changes.
|
||
|
return (string)$key . '-' . $definition->generate_single_key_prefix();
|
||
|
}
|
||
|
$key = $definition->generate_single_key_prefix() . '-' . $key;
|
||
|
return sha1($key);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Finds all definitions and updates them within the cache config file.
|
||
|
*
|
||
|
* @param bool $coreonly If set to true only core definitions will be updated.
|
||
|
*/
|
||
|
public static function update_definitions($coreonly = false) {
|
||
|
global $CFG;
|
||
|
// Include locallib.
|
||
|
require_once($CFG->dirroot.'/cache/locallib.php');
|
||
|
// First update definitions
|
||
|
cache_config_writer::update_definitions($coreonly);
|
||
|
// Second reset anything we have already initialised to ensure we're all up to date.
|
||
|
cache_factory::reset();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Update the site identifier stored by the cache API.
|
||
|
*
|
||
|
* @param string $siteidentifier
|
||
|
* @return string The new site identifier.
|
||
|
*/
|
||
|
public static function update_site_identifier($siteidentifier) {
|
||
|
global $CFG;
|
||
|
// Include locallib.
|
||
|
require_once($CFG->dirroot.'/cache/locallib.php');
|
||
|
$factory = cache_factory::instance();
|
||
|
$factory->updating_started();
|
||
|
$config = $factory->create_config_instance(true);
|
||
|
$siteidentifier = $config->update_site_identifier($siteidentifier);
|
||
|
$factory->updating_finished();
|
||
|
cache_factory::reset();
|
||
|
return $siteidentifier;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the site identifier.
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
public static function get_site_identifier() {
|
||
|
global $CFG;
|
||
|
if (!is_null(self::$siteidentifier)) {
|
||
|
return self::$siteidentifier;
|
||
|
}
|
||
|
// If site identifier hasn't been collected yet attempt to get it from the cache config.
|
||
|
$factory = cache_factory::instance();
|
||
|
// If the factory is initialising then we don't want to try to get it from the config or we risk
|
||
|
// causing the cache to enter an infinite initialisation loop.
|
||
|
if (!$factory->is_initialising()) {
|
||
|
$config = $factory->create_config_instance();
|
||
|
self::$siteidentifier = $config->get_site_identifier();
|
||
|
}
|
||
|
if (is_null(self::$siteidentifier)) {
|
||
|
// If the site identifier is still null then config isn't aware of it yet.
|
||
|
// We'll see if the CFG is loaded, and if not we will just use unknown.
|
||
|
// It's very important here that we don't use get_config. We don't want an endless cache loop!
|
||
|
if (!empty($CFG->siteidentifier)) {
|
||
|
self::$siteidentifier = self::update_site_identifier($CFG->siteidentifier);
|
||
|
} else {
|
||
|
// It's not being recorded in MUC's config and the config data hasn't been loaded yet.
|
||
|
// Likely we are initialising.
|
||
|
return 'unknown';
|
||
|
}
|
||
|
}
|
||
|
return self::$siteidentifier;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the site version.
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
public static function get_site_version() {
|
||
|
global $CFG;
|
||
|
return (string)$CFG->version;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Runs cron routines for MUC.
|
||
|
*/
|
||
|
public static function cron() {
|
||
|
self::clean_old_session_data(true);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Cleans old session data from cache stores used for session based definitions.
|
||
|
*
|
||
|
* @param bool $output If set to true output will be given.
|
||
|
*/
|
||
|
public static function clean_old_session_data($output = false) {
|
||
|
global $CFG;
|
||
|
if ($output) {
|
||
|
mtrace('Cleaning up stale session data from cache stores.');
|
||
|
}
|
||
|
$factory = cache_factory::instance();
|
||
|
$config = $factory->create_config_instance();
|
||
|
$definitions = $config->get_definitions();
|
||
|
$purgetime = time() - $CFG->sessiontimeout;
|
||
|
foreach ($definitions as $definitionarray) {
|
||
|
// We are only interested in session caches.
|
||
|
if (!($definitionarray['mode'] & cache_store::MODE_SESSION)) {
|
||
|
continue;
|
||
|
}
|
||
|
$definition = $factory->create_definition($definitionarray['component'], $definitionarray['area']);
|
||
|
$stores = $config->get_stores_for_definition($definition);
|
||
|
// Turn them into store instances.
|
||
|
$stores = self::initialise_cachestore_instances($stores, $definition);
|
||
|
// Initialise all of the stores used for that definition.
|
||
|
foreach ($stores as $store) {
|
||
|
// If the store doesn't support searching we can skip it.
|
||
|
if (!($store instanceof cache_is_searchable)) {
|
||
|
debugging('Cache stores used for session definitions should ideally be searchable.', DEBUG_DEVELOPER);
|
||
|
continue;
|
||
|
}
|
||
|
// Get all of the keys.
|
||
|
$keys = $store->find_by_prefix(cache_session::KEY_PREFIX);
|
||
|
$todelete = array();
|
||
|
foreach ($store->get_many($keys) as $key => $value) {
|
||
|
if (strpos($key, cache_session::KEY_PREFIX) !== 0 || !is_array($value) || !isset($value['lastaccess'])) {
|
||
|
continue;
|
||
|
}
|
||
|
if ((int)$value['lastaccess'] < $purgetime || true) {
|
||
|
$todelete[] = $key;
|
||
|
}
|
||
|
}
|
||
|
if (count($todelete)) {
|
||
|
$outcome = (int)$store->delete_many($todelete);
|
||
|
if ($output) {
|
||
|
$strdef = s($definition->get_id());
|
||
|
$strstore = s($store->my_name());
|
||
|
mtrace("- Removed {$outcome} old {$strdef} sessions from the '{$strstore}' cache store.");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns an array of stores that would meet the requirements for every definition.
|
||
|
*
|
||
|
* These stores would be 100% suitable to map as defaults for cache modes.
|
||
|
*
|
||
|
* @return array[] An array of stores, keys are the store names.
|
||
|
*/
|
||
|
public static function get_stores_suitable_for_mode_default() {
|
||
|
$factory = cache_factory::instance();
|
||
|
$config = $factory->create_config_instance();
|
||
|
$requirements = 0;
|
||
|
foreach ($config->get_definitions() as $definition) {
|
||
|
$definition = cache_definition::load($definition['component'].'/'.$definition['area'], $definition);
|
||
|
$requirements = $requirements | $definition->get_requirements_bin();
|
||
|
}
|
||
|
$stores = array();
|
||
|
foreach ($config->get_all_stores() as $name => $store) {
|
||
|
if (!empty($store['features']) && ($store['features'] & $requirements)) {
|
||
|
$stores[$name] = $store;
|
||
|
}
|
||
|
}
|
||
|
return $stores;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns stores suitable for use with a given definition.
|
||
|
*
|
||
|
* @param cache_definition $definition
|
||
|
* @return cache_store[]
|
||
|
*/
|
||
|
public static function get_stores_suitable_for_definition(cache_definition $definition) {
|
||
|
$factory = cache_factory::instance();
|
||
|
$stores = array();
|
||
|
if ($factory->is_initialising() || $factory->stores_disabled()) {
|
||
|
// No suitable stores here.
|
||
|
return $stores;
|
||
|
} else {
|
||
|
$stores = self::get_cache_stores($definition);
|
||
|
// If mappingsonly is set, having 0 stores is ok.
|
||
|
if ((count($stores) === 0) && (!$definition->is_for_mappings_only())) {
|
||
|
// No suitable stores we found for the definition. We need to come up with a sensible default.
|
||
|
// If this has happened we can be sure that the user has mapped custom stores to either the
|
||
|
// mode of the definition. The first alternative to try is the system default for the mode.
|
||
|
// e.g. the default file store instance for application definitions.
|
||
|
$config = $factory->create_config_instance();
|
||
|
foreach ($config->get_stores($definition->get_mode()) as $name => $details) {
|
||
|
if (!empty($details['default'])) {
|
||
|
$stores[] = $factory->create_store_from_config($name, $details, $definition);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return $stores;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns an array of warnings from the cache API.
|
||
|
*
|
||
|
* The warning returned here are for things like conflicting store instance configurations etc.
|
||
|
* These get shown on the admin notifications page for example.
|
||
|
*
|
||
|
* @param array|null $stores An array of stores to get warnings for, or null for all.
|
||
|
* @return string[]
|
||
|
*/
|
||
|
public static function warnings(array $stores = null) {
|
||
|
global $CFG;
|
||
|
if ($stores === null) {
|
||
|
require_once($CFG->dirroot.'/cache/locallib.php');
|
||
|
$stores = cache_administration_helper::get_store_instance_summaries();
|
||
|
}
|
||
|
$warnings = array();
|
||
|
foreach ($stores as $store) {
|
||
|
if (!empty($store['warnings'])) {
|
||
|
$warnings = array_merge($warnings, $store['warnings']);
|
||
|
}
|
||
|
}
|
||
|
return $warnings;
|
||
|
}
|
||
|
}
|