Search moodle.org's
Developer Documentation

  • Bug fixes for general core bugs in 3.11.x will end 14 Nov 2022 (12 months plus 6 months extension).
  • Bug fixes for security issues in 3.11.x will end 13 Nov 2023 (18 months plus 12 months extension).
  • PHP version: minimum PHP 7.3.0 Note: minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is supported too.
  • Differences Between: [Versions 311 and 400] [Versions 37 and 311] [Versions 38 and 311] [Versions 39 and 311]

       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       * Events cannot determine what identifiers might need to be cleared. Event based purge and invalidation
     233       * are only supported on caches without identifiers.
     234       *
     235       * @param string $event
     236       * @param array $keys
     237       */
     238      public static function invalidate_by_event($event, array $keys) {
     239          $instance = cache_config::instance();
     240          $invalidationeventset = false;
     241          $factory = cache_factory::instance();
     242          $inuse = $factory->get_caches_in_use();
     243          $purgetoken = null;
     244          foreach ($instance->get_definitions() as $name => $definitionarr) {
     245              $definition = cache_definition::load($name, $definitionarr);
     246              if ($definition->invalidates_on_event($event)) {
     247                  // First up check if there is a cache loader for this definition already.
     248                  // If there is we need to invalidate the keys from there.
     249                  $definitionkey = $definition->get_component().'/'.$definition->get_area();
     250                  if (isset($inuse[$definitionkey])) {
     251                      $inuse[$definitionkey]->delete_many($keys);
     252                  }
     253  
     254                  // We should only log events for application and session caches.
     255                  // Request caches shouldn't have events as all data is lost at the end of the request.
     256                  // Events should only be logged once of course and likely several definitions are watching so we
     257                  // track its logging with $invalidationeventset.
     258                  $logevent = ($invalidationeventset === false && $definition->get_mode() !== cache_store::MODE_REQUEST);
     259  
     260                  if ($logevent) {
     261                      // Get the event invalidation cache.
     262                      $cache = cache::make('core', 'eventinvalidation');
     263                      // Get any existing invalidated keys for this cache.
     264                      $data = $cache->get($event);
     265                      if ($data === false) {
     266                          // There are none.
     267                          $data = array();
     268                      }
     269                      // Add our keys to them with the current cache timestamp.
     270                      if (null === $purgetoken) {
     271                          $purgetoken = cache::get_purge_token(true);
     272                      }
     273                      foreach ($keys as $key) {
     274                          $data[$key] = $purgetoken;
     275                      }
     276                      // Set that data back to the cache.
     277                      $cache->set($event, $data);
     278                      // This only needs to occur once.
     279                      $invalidationeventset = true;
     280                  }
     281              }
     282          }
     283      }
     284  
     285      /**
     286       * Purges the cache for a specific definition.
     287       *
     288       * @param string $component
     289       * @param string $area
     290       * @param array $identifiers
     291       * @return bool
     292       */
     293      public static function purge_by_definition($component, $area, array $identifiers = array()) {
     294          // Create the cache.
     295          $cache = cache::make($component, $area, $identifiers);
     296          // Initialise, in case of a store.
     297          if ($cache instanceof cache_store) {
     298              $factory = cache_factory::instance();
     299              $definition = $factory->create_definition($component, $area, null);
     300              $cacheddefinition = clone $definition;
     301              $cacheddefinition->set_identifiers($identifiers);
     302              $cache->initialise($cacheddefinition);
     303          }
     304          // Purge baby, purge.
     305          $cache->purge();
     306          return true;
     307      }
     308  
     309      /**
     310       * Purges a cache of all information on a given event.
     311       *
     312       * Events cannot determine what identifiers might need to be cleared. Event based purge and invalidation
     313       * are only supported on caches without identifiers.
     314       *
     315       * @param string $event
     316       */
     317      public static function purge_by_event($event) {
     318          $instance = cache_config::instance();
     319          $invalidationeventset = false;
     320          $factory = cache_factory::instance();
     321          $inuse = $factory->get_caches_in_use();
     322          $purgetoken = null;
     323          foreach ($instance->get_definitions() as $name => $definitionarr) {
     324              $definition = cache_definition::load($name, $definitionarr);
     325              if ($definition->invalidates_on_event($event)) {
     326                  // First up check if there is a cache loader for this definition already.
     327                  // If there is we need to invalidate the keys from there.
     328                  $definitionkey = $definition->get_component().'/'.$definition->get_area();
     329                  if (isset($inuse[$definitionkey])) {
     330                      $inuse[$definitionkey]->purge();
     331                  } else {
     332                      cache::make($definition->get_component(), $definition->get_area())->purge();
     333                  }
     334  
     335                  // We should only log events for application and session caches.
     336                  // Request caches shouldn't have events as all data is lost at the end of the request.
     337                  // Events should only be logged once of course and likely several definitions are watching so we
     338                  // track its logging with $invalidationeventset.
     339                  $logevent = ($invalidationeventset === false && $definition->get_mode() !== cache_store::MODE_REQUEST);
     340  
     341                  // We need to flag the event in the "Event invalidation" cache if it hasn't already happened.
     342                  if ($logevent && $invalidationeventset === false) {
     343                      // Get the event invalidation cache.
     344                      $cache = cache::make('core', 'eventinvalidation');
     345                      // Create a key to invalidate all.
     346                      if (null === $purgetoken) {
     347                          $purgetoken = cache::get_purge_token(true);
     348                      }
     349                      $data = array(
     350                          'purged' => $purgetoken,
     351                      );
     352                      // Set that data back to the cache.
     353                      $cache->set($event, $data);
     354                      // This only needs to occur once.
     355                      $invalidationeventset = true;
     356                  }
     357              }
     358          }
     359      }
     360  
     361      /**
     362       * Ensure that the stats array is ready to collect information for the given store and definition.
     363       * @param string $store
     364       * @param string $storeclass
     365       * @param string $definition A string that identifies the definition.
     366       * @param int $mode One of cache_store::MODE_*. Since 2.9.
     367       */
     368      protected static function ensure_ready_for_stats($store, $storeclass, $definition, $mode = cache_store::MODE_APPLICATION) {
     369          // This function is performance-sensitive, so exit as quickly as possible
     370          // if we do not need to do anything.
     371          if (isset(self::$stats[$definition]['stores'][$store])) {
     372              return;
     373          }
     374  
     375          if (!array_key_exists($definition, self::$stats)) {
     376              self::$stats[$definition] = array(
     377                  'mode' => $mode,
     378                  'stores' => array(
     379                      $store => array(
     380                          'class' => $storeclass,
     381                          'hits' => 0,
     382                          'misses' => 0,
     383                          'sets' => 0,
     384                      )
     385                  )
     386              );
     387          } else if (!array_key_exists($store, self::$stats[$definition]['stores'])) {
     388              self::$stats[$definition]['stores'][$store] = array(
     389                  'class' => $storeclass,
     390                  'hits' => 0,
     391                  'misses' => 0,
     392                  'sets' => 0,
     393              );
     394          }
     395      }
     396  
     397      /**
     398       * Returns a string to describe the definition.
     399       *
     400       * This method supports the definition as a string due to legacy requirements.
     401       * It is backwards compatible when a string is passed but is not accurate.
     402       *
     403       * @since 2.9
     404       * @param cache_definition|string $definition
     405       * @return string
     406       */
     407      protected static function get_definition_stat_id_and_mode($definition) {
     408          if (!($definition instanceof cache_definition)) {
     409              // All core calls to this method have been updated, this is the legacy state.
     410              // We'll use application as the default as that is the most common, really this is not accurate of course but
     411              // at this point we can only guess and as it only affects calls to cache stat outside of core (of which there should
     412              // be none) I think that is fine.
     413              debugging('Please update you cache stat calls to pass the definition rather than just its ID.', DEBUG_DEVELOPER);
     414              return array((string)$definition, cache_store::MODE_APPLICATION);
     415          }
     416          return array($definition->get_id(), $definition->get_mode());
     417      }
     418  
     419      /**
     420       * Record a cache hit in the stats for the given store and definition.
     421       *
     422       * In Moodle 2.9 the $definition argument changed from accepting only a string to accepting a string or a
     423       * cache_definition instance. It is preferable to pass a cache definition instance.
     424       *
     425       * In Moodle 3.9 the first argument changed to also accept a cache_store.
     426       *
     427       * @internal
     428       * @param string|cache_store $store
     429       * @param cache_definition $definition You used to be able to pass a string here, however that is deprecated please pass the
     430       *      actual cache_definition object now.
     431       * @param int $hits The number of hits to record (by default 1)
     432       */
     433      public static function record_cache_hit($store, $definition, $hits = 1) {
     434          $storeclass = '';
     435          if ($store instanceof cache_store) {
     436              $storeclass = get_class($store);
     437              $store = $store->my_name();
     438          }
     439          list($definitionstr, $mode) = self::get_definition_stat_id_and_mode($definition);
     440          self::ensure_ready_for_stats($store, $storeclass, $definitionstr, $mode);
     441          self::$stats[$definitionstr]['stores'][$store]['hits'] += $hits;
     442      }
     443  
     444      /**
     445       * Record a cache miss in the stats for the given store and definition.
     446       *
     447       * In Moodle 2.9 the $definition argument changed from accepting only a string to accepting a string or a
     448       * cache_definition instance. It is preferable to pass a cache definition instance.
     449       *
     450       * In Moodle 3.9 the first argument changed to also accept a cache_store.
     451       *
     452       * @internal
     453       * @param string|cache_store $store
     454       * @param cache_definition $definition You used to be able to pass a string here, however that is deprecated please pass the
     455       *      actual cache_definition object now.
     456       * @param int $misses The number of misses to record (by default 1)
     457       */
     458      public static function record_cache_miss($store, $definition, $misses = 1) {
     459          $storeclass = '';
     460          if ($store instanceof cache_store) {
     461              $storeclass = get_class($store);
     462              $store = $store->my_name();
     463          }
     464          list($definitionstr, $mode) = self::get_definition_stat_id_and_mode($definition);
     465          self::ensure_ready_for_stats($store, $storeclass, $definitionstr, $mode);
     466          self::$stats[$definitionstr]['stores'][$store]['misses'] += $misses;
     467      }
     468  
     469      /**
     470       * Record a cache set in the stats for the given store and definition.
     471       *
     472       * In Moodle 2.9 the $definition argument changed from accepting only a string to accepting a string or a
     473       * cache_definition instance. It is preferable to pass a cache definition instance.
     474       *
     475       * In Moodle 3.9 the first argument changed to also accept a cache_store.
     476       *
     477       * @internal
     478       * @param string|cache_store $store
     479       * @param cache_definition $definition You used to be able to pass a string here, however that is deprecated please pass the
     480       *      actual cache_definition object now.
     481       * @param int $sets The number of sets to record (by default 1)
     482       */
     483      public static function record_cache_set($store, $definition, $sets = 1) {
     484          $storeclass = '';
     485          if ($store instanceof cache_store) {
     486              $storeclass = get_class($store);
     487              $store = $store->my_name();
     488          }
     489          list($definitionstr, $mode) = self::get_definition_stat_id_and_mode($definition);
     490          self::ensure_ready_for_stats($store, $storeclass, $definitionstr, $mode);
     491          self::$stats[$definitionstr]['stores'][$store]['sets'] += $sets;
     492      }
     493  
     494      /**
     495       * Return the stats collected so far.
     496       * @return array
     497       */
     498      public static function get_stats() {
     499          return self::$stats;
     500      }
     501  
     502      /**
     503       * Purge all of the cache stores of all of their data.
     504       *
     505       * Think twice before calling this method. It will purge **ALL** caches regardless of whether they have been used recently or
     506       * anything. This will involve full setup of the cache + the purge operation. On a site using caching heavily this WILL be
     507       * painful.
     508       *
     509       * @param bool $usewriter If set to true the cache_config_writer class is used. This class is special as it avoids
     510       *      it is still usable when caches have been disabled.
     511       *      Please use this option only if you really must. It's purpose is to allow the cache to be purged when it would be
     512       *      otherwise impossible.
     513       */
     514      public static function purge_all($usewriter = false) {
     515          $factory = cache_factory::instance();
     516          $config = $factory->create_config_instance($usewriter);
     517          foreach ($config->get_all_stores() as $store) {
     518              self::purge_store($store['name'], $config);
     519          }
     520          foreach ($factory->get_adhoc_caches_in_use() as $cache) {
     521              $cache->purge();
     522          }
     523      }
     524  
     525      /**
     526       * Purges a store given its name.
     527       *
     528       * @param string $storename
     529       * @param cache_config $config
     530       * @return bool
     531       */
     532      public static function purge_store($storename, cache_config $config = null) {
     533          if ($config === null) {
     534              $config = cache_config::instance();
     535          }
     536  
     537          $stores = $config->get_all_stores();
     538          if (!array_key_exists($storename, $stores)) {
     539              // The store does not exist.
     540              return false;
     541          }
     542  
     543          $store = $stores[$storename];
     544          $class = $store['class'];
     545  
     546  
     547          // We check are_requirements_met although we expect is_ready is going to check as well.
     548          if (!$class::are_requirements_met()) {
     549              return false;
     550          }
     551          // Found the store: is it ready?
     552          /* @var cache_store $instance */
     553          $instance = new $class($store['name'], $store['configuration']);
     554          if (!$instance->is_ready()) {
     555              unset($instance);
     556              return false;
     557          }
     558          foreach ($config->get_definitions_by_store($storename) as $id => $definition) {
     559              $definition = cache_definition::load($id, $definition);
     560              $definitioninstance = clone($instance);
     561              $definitioninstance->initialise($definition);
     562              $definitioninstance->purge();
     563              unset($definitioninstance);
     564          }
     565  
     566          return true;
     567      }
     568  
     569      /**
     570       * Purges all of the stores used by a definition.
     571       *
     572       * Unlike cache_helper::purge_by_definition this purges all of the data from the stores not
     573       * just the data relating to the definition.
     574       * This function is useful when you must purge a definition that requires setup but you don't
     575       * want to set it up.
     576       *
     577       * @param string $component
     578       * @param string $area
     579       */
     580      public static function purge_stores_used_by_definition($component, $area) {
     581          $factory = cache_factory::instance();
     582          $config = $factory->create_config_instance();
     583          $definition = $factory->create_definition($component, $area);
     584          $stores = $config->get_stores_for_definition($definition);
     585          foreach ($stores as $store) {
     586              self::purge_store($store['name']);
     587          }
     588      }
     589  
     590      /**
     591       * Returns the translated name of the definition.
     592       *
     593       * @param cache_definition $definition
     594       * @return lang_string
     595       */
     596      public static function get_definition_name($definition) {
     597          if ($definition instanceof cache_definition) {
     598              return $definition->get_name();
     599          }
     600          $identifier = 'cachedef_'.clean_param($definition['area'], PARAM_STRINGID);
     601          $component = $definition['component'];
     602          if ($component === 'core') {
     603              $component = 'cache';
     604          }
     605          return new lang_string($identifier, $component);
     606      }
     607  
     608      /**
     609       * Hashes a descriptive key to make it shorter and still unique.
     610       * @param string|int $key
     611       * @param cache_definition $definition
     612       * @return string
     613       */
     614      public static function hash_key($key, cache_definition $definition) {
     615          if ($definition->uses_simple_keys()) {
     616              if (debugging() && preg_match('#[^a-zA-Z0-9_]#', $key)) {
     617                  throw new coding_exception('Cache definition '.$definition->get_id().' requires simple keys. Invalid key provided.', $key);
     618              }
     619              // We put the key first so that we can be sure the start of the key changes.
     620              return (string)$key . '-' . $definition->generate_single_key_prefix();
     621          }
     622          $key = $definition->generate_single_key_prefix() . '-' . $key;
     623          return sha1($key);
     624      }
     625  
     626      /**
     627       * Finds all definitions and updates them within the cache config file.
     628       *
     629       * @param bool $coreonly If set to true only core definitions will be updated.
     630       */
     631      public static function update_definitions($coreonly = false) {
     632          global $CFG;
     633          // Include locallib.
     634          require_once($CFG->dirroot.'/cache/locallib.php');
     635          // First update definitions
     636          cache_config_writer::update_definitions($coreonly);
     637          // Second reset anything we have already initialised to ensure we're all up to date.
     638          cache_factory::reset();
     639      }
     640  
     641      /**
     642       * Update the site identifier stored by the cache API.
     643       *
     644       * @param string $siteidentifier
     645       * @return string The new site identifier.
     646       */
     647      public static function update_site_identifier($siteidentifier) {
     648          global $CFG;
     649          // Include locallib.
     650          require_once($CFG->dirroot.'/cache/locallib.php');
     651          $factory = cache_factory::instance();
     652          $factory->updating_started();
     653          $config = $factory->create_config_instance(true);
     654          $siteidentifier = $config->update_site_identifier($siteidentifier);
     655          $factory->updating_finished();
     656          cache_factory::reset();
     657          return $siteidentifier;
     658      }
     659  
     660      /**
     661       * Returns the site identifier.
     662       *
     663       * @return string
     664       */
     665      public static function get_site_identifier() {
     666          global $CFG;
     667          if (!is_null(self::$siteidentifier)) {
     668              return self::$siteidentifier;
     669          }
     670          // If site identifier hasn't been collected yet attempt to get it from the cache config.
     671          $factory = cache_factory::instance();
     672          // If the factory is initialising then we don't want to try to get it from the config or we risk
     673          // causing the cache to enter an infinite initialisation loop.
     674          if (!$factory->is_initialising()) {
     675              $config = $factory->create_config_instance();
     676              self::$siteidentifier = $config->get_site_identifier();
     677          }
     678          if (is_null(self::$siteidentifier)) {
     679              // If the site identifier is still null then config isn't aware of it yet.
     680              // We'll see if the CFG is loaded, and if not we will just use unknown.
     681              // It's very important here that we don't use get_config. We don't want an endless cache loop!
     682              if (!empty($CFG->siteidentifier)) {
     683                  self::$siteidentifier = self::update_site_identifier($CFG->siteidentifier);
     684              } else {
     685                  // It's not being recorded in MUC's config and the config data hasn't been loaded yet.
     686                  // Likely we are initialising.
     687                  return 'unknown';
     688              }
     689          }
     690          return self::$siteidentifier;
     691      }
     692  
     693      /**
     694       * Returns the site version.
     695       *
     696       * @return string
     697       */
     698      public static function get_site_version() {
     699          global $CFG;
     700          return (string)$CFG->version;
     701      }
     702  
     703      /**
     704       * Runs cron routines for MUC.
     705       */
     706      public static function cron() {
     707          self::clean_old_session_data(true);
     708      }
     709  
     710      /**
     711       * Cleans old session data from cache stores used for session based definitions.
     712       *
     713       * @param bool $output If set to true output will be given.
     714       */
     715      public static function clean_old_session_data($output = false) {
     716          global $CFG;
     717          if ($output) {
     718              mtrace('Cleaning up stale session data from cache stores.');
     719          }
     720          $factory = cache_factory::instance();
     721          $config = $factory->create_config_instance();
     722          $definitions = $config->get_definitions();
     723          $purgetime = time() - $CFG->sessiontimeout;
     724          foreach ($definitions as $definitionarray) {
     725              // We are only interested in session caches.
     726              if (!($definitionarray['mode'] & cache_store::MODE_SESSION)) {
     727                  continue;
     728              }
     729              $definition = $factory->create_definition($definitionarray['component'], $definitionarray['area']);
     730              $stores = $config->get_stores_for_definition($definition);
     731              // Turn them into store instances.
     732              $stores = self::initialise_cachestore_instances($stores, $definition);
     733              // Initialise all of the stores used for that definition.
     734              foreach ($stores as $store) {
     735                  // If the store doesn't support searching we can skip it.
     736                  if (!($store instanceof cache_is_searchable)) {
     737                      debugging('Cache stores used for session definitions should ideally be searchable.', DEBUG_DEVELOPER);
     738                      continue;
     739                  }
     740                  // Get all of the keys.
     741                  $keys = $store->find_by_prefix(cache_session::KEY_PREFIX);
     742                  $todelete = array();
     743                  foreach ($store->get_many($keys) as $key => $value) {
     744                      if (strpos($key, cache_session::KEY_PREFIX) !== 0 || !is_array($value) || !isset($value['lastaccess'])) {
     745                          continue;
     746                      }
     747                      if ((int)$value['lastaccess'] < $purgetime || true) {
     748                          $todelete[] = $key;
     749                      }
     750                  }
     751                  if (count($todelete)) {
     752                      $outcome = (int)$store->delete_many($todelete);
     753                      if ($output) {
     754                          $strdef = s($definition->get_id());
     755                          $strstore = s($store->my_name());
     756                          mtrace("- Removed {$outcome} old {$strdef} sessions from the '{$strstore}' cache store.");
     757                      }
     758                  }
     759              }
     760          }
     761      }
     762  
     763      /**
     764       * Returns an array of stores that would meet the requirements for every definition.
     765       *
     766       * These stores would be 100% suitable to map as defaults for cache modes.
     767       *
     768       * @return array[] An array of stores, keys are the store names.
     769       */
     770      public static function get_stores_suitable_for_mode_default() {
     771          $factory = cache_factory::instance();
     772          $config = $factory->create_config_instance();
     773          $requirements = 0;
     774          foreach ($config->get_definitions() as $definition) {
     775              $definition = cache_definition::load($definition['component'].'/'.$definition['area'], $definition);
     776              $requirements = $requirements | $definition->get_requirements_bin();
     777          }
     778          $stores = array();
     779          foreach ($config->get_all_stores() as $name => $store) {
     780              if (!empty($store['features']) && ($store['features'] & $requirements)) {
     781                  $stores[$name] = $store;
     782              }
     783          }
     784          return $stores;
     785      }
     786  
     787      /**
     788       * Returns stores suitable for use with a given definition.
     789       *
     790       * @param cache_definition $definition
     791       * @return cache_store[]
     792       */
     793      public static function get_stores_suitable_for_definition(cache_definition $definition) {
     794          $factory = cache_factory::instance();
     795          $stores = array();
     796          if ($factory->is_initialising() || $factory->stores_disabled()) {
     797              // No suitable stores here.
     798              return $stores;
     799          } else {
     800              $stores = self::get_cache_stores($definition);
     801              // If mappingsonly is set, having 0 stores is ok.
     802              if ((count($stores) === 0) && (!$definition->is_for_mappings_only())) {
     803                  // No suitable stores we found for the definition. We need to come up with a sensible default.
     804                  // If this has happened we can be sure that the user has mapped custom stores to either the
     805                  // mode of the definition. The first alternative to try is the system default for the mode.
     806                  // e.g. the default file store instance for application definitions.
     807                  $config = $factory->create_config_instance();
     808                  foreach ($config->get_stores($definition->get_mode()) as $name => $details) {
     809                      if (!empty($details['default'])) {
     810                          $stores[] = $factory->create_store_from_config($name, $details, $definition);
     811                          break;
     812                      }
     813                  }
     814              }
     815          }
     816          return $stores;
     817      }
     818  
     819      /**
     820       * Returns an array of warnings from the cache API.
     821       *
     822       * The warning returned here are for things like conflicting store instance configurations etc.
     823       * These get shown on the admin notifications page for example.
     824       *
     825       * @param array|null $stores An array of stores to get warnings for, or null for all.
     826       * @return string[]
     827       */
     828      public static function warnings(array $stores = null) {
     829          global $CFG;
     830          if ($stores === null) {
     831              require_once($CFG->dirroot.'/cache/locallib.php');
     832              $stores = core_cache\administration_helper::get_store_instance_summaries();
     833          }
     834          $warnings = array();
     835          foreach ($stores as $store) {
     836              if (!empty($store['warnings'])) {
     837                  $warnings = array_merge($warnings, $store['warnings']);
     838              }
     839          }
     840          return $warnings;
     841      }
     842  }