Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401]

   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 administration helper.
  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   * @author     Peter Burnett <peterburnett@catalyst-au.net>
  26   * @copyright  2020 Catalyst IT
  27   * @copyright  2012 Sam Hemelryk
  28   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  29   */
  30  
  31  namespace core_cache;
  32  
  33  defined('MOODLE_INTERNAL') || die();
  34  use cache_helper, cache_store, cache_config, cache_factory, cache_definition;
  35  
  36  /**
  37   * Administration helper base class.
  38   *
  39   * Defines abstract methods for a subclass to define the admin page.
  40   *
  41   * @package     core
  42   * @category    cache
  43   * @author      Peter Burnett <peterburnett@catalyst-au.net>
  44   * @copyright   2020 Catalyst IT
  45   * @copyright  2012 Sam Hemelryk
  46   * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  47   */
  48  abstract class administration_helper extends cache_helper {
  49  
  50      /**
  51       * Returns an array containing all of the information about stores a renderer needs.
  52       * @return array
  53       */
  54      public static function get_store_instance_summaries(): array {
  55          $return = array();
  56          $default = array();
  57          $instance = \cache_config::instance();
  58          $stores = $instance->get_all_stores();
  59          $locks = $instance->get_locks();
  60          foreach ($stores as $name => $details) {
  61              $class = $details['class'];
  62              $store = false;
  63              if ($class::are_requirements_met()) {
  64                  $store = new $class($details['name'], $details['configuration']);
  65              }
  66              $lock = (isset($details['lock'])) ? $locks[$details['lock']] : $instance->get_default_lock();
  67              $record = array(
  68                  'name' => $name,
  69                  'plugin' => $details['plugin'],
  70                  'default' => $details['default'],
  71                  'isready' => $store ? $store->is_ready() : false,
  72                  'requirementsmet' => $class::are_requirements_met(),
  73                  'mappings' => 0,
  74                  'lock' => $lock,
  75                  'modes' => array(
  76                      cache_store::MODE_APPLICATION =>
  77                          ($class::get_supported_modes($return) & cache_store::MODE_APPLICATION) == cache_store::MODE_APPLICATION,
  78                      cache_store::MODE_SESSION =>
  79                          ($class::get_supported_modes($return) & cache_store::MODE_SESSION) == cache_store::MODE_SESSION,
  80                      cache_store::MODE_REQUEST =>
  81                          ($class::get_supported_modes($return) & cache_store::MODE_REQUEST) == cache_store::MODE_REQUEST,
  82                  ),
  83                  'supports' => array(
  84                      'multipleidentifiers' => $store ? $store->supports_multiple_identifiers() : false,
  85                      'dataguarantee' => $store ? $store->supports_data_guarantee() : false,
  86                      'nativettl' => $store ? $store->supports_native_ttl() : false,
  87                      'nativelocking' => ($store instanceof \cache_is_lockable),
  88                      'keyawareness' => ($store instanceof \cache_is_key_aware),
  89                      'searchable' => ($store instanceof \cache_is_searchable)
  90                  ),
  91                  'warnings' => $store ? $store->get_warnings() : array()
  92              );
  93              if (empty($details['default'])) {
  94                  $return[$name] = $record;
  95              } else {
  96                  $default[$name] = $record;
  97              }
  98          }
  99  
 100          ksort($return);
 101          ksort($default);
 102          $return = $return + $default;
 103  
 104          $mappings = $instance->get_definition_mappings();
 105          foreach ($mappings as $mapping) {
 106              if (!array_key_exists($mapping['store'], $return)) {
 107                  continue;
 108              }
 109              $return[$mapping['store']]['mappings']++;
 110          }
 111  
 112          // Now get all definitions, and if not mapped, increment the defaults for the mode.
 113          $modemappings = $instance->get_mode_mappings();
 114          foreach ($instance->get_definitions() as $definition) {
 115              // Construct the definition name to search for.
 116              $defname = $definition['component'] . '/' . $definition['area'];
 117              // Skip if definition is already mapped.
 118              if (array_search($defname, array_column($mappings, 'definition')) !== false) {
 119                  continue;
 120              }
 121  
 122              $mode = $definition['mode'];
 123              // Get the store name of the default mapping from the mode.
 124              $index = array_search($mode, array_column($modemappings, 'mode'));
 125              $store = $modemappings[$index]['store'];
 126              $return[$store]['mappings']++;
 127          }
 128  
 129          return $return;
 130      }
 131  
 132      /**
 133       * Returns an array of information about plugins, everything a renderer needs.
 134       *
 135       * @return array for each store, an array containing various information about each store.
 136       *     See the code below for details
 137       */
 138      public static function get_store_plugin_summaries(): array {
 139          $return = array();
 140          $plugins = \core_component::get_plugin_list_with_file('cachestore', 'lib.php', true);
 141          foreach ($plugins as $plugin => $path) {
 142              $class = 'cachestore_'.$plugin;
 143              $return[$plugin] = array(
 144                  'name' => get_string('pluginname', 'cachestore_'.$plugin),
 145                  'requirementsmet' => $class::are_requirements_met(),
 146                  'instances' => 0,
 147                  'modes' => array(
 148                      cache_store::MODE_APPLICATION => ($class::get_supported_modes() & cache_store::MODE_APPLICATION),
 149                      cache_store::MODE_SESSION => ($class::get_supported_modes() & cache_store::MODE_SESSION),
 150                      cache_store::MODE_REQUEST => ($class::get_supported_modes() & cache_store::MODE_REQUEST),
 151                  ),
 152                  'supports' => array(
 153                      'multipleidentifiers' => ($class::get_supported_features() & cache_store::SUPPORTS_MULTIPLE_IDENTIFIERS),
 154                      'dataguarantee' => ($class::get_supported_features() & cache_store::SUPPORTS_DATA_GUARANTEE),
 155                      'nativettl' => ($class::get_supported_features() & cache_store::SUPPORTS_NATIVE_TTL),
 156                      'nativelocking' => (in_array('cache_is_lockable', class_implements($class))),
 157                      'keyawareness' => (array_key_exists('cache_is_key_aware', class_implements($class))),
 158                  ),
 159                  'canaddinstance' => ($class::can_add_instance() && $class::are_requirements_met())
 160              );
 161          }
 162  
 163          $instance = cache_config::instance();
 164          $stores = $instance->get_all_stores();
 165          foreach ($stores as $store) {
 166              $plugin = $store['plugin'];
 167              if (array_key_exists($plugin, $return)) {
 168                  $return[$plugin]['instances']++;
 169              }
 170          }
 171  
 172          return $return;
 173      }
 174  
 175      /**
 176       * Returns an array about the definitions. All the information a renderer needs.
 177       *
 178       * @return array for each store, an array containing various information about each store.
 179       *     See the code below for details
 180       */
 181      public static function get_definition_summaries(): array {
 182          $factory = cache_factory::instance();
 183          $config = $factory->create_config_instance();
 184          $storenames = array();
 185          foreach ($config->get_all_stores() as $key => $store) {
 186              if (!empty($store['default'])) {
 187                  $storenames[$key] = new \lang_string('store_'.$key, 'cache');
 188              } else {
 189                  $storenames[$store['name']] = $store['name'];
 190              }
 191          }
 192          /* @var cache_definition[] $definitions */
 193          $definitions = [];
 194          $return = [];
 195          foreach ($config->get_definitions() as $key => $definition) {
 196              $definitions[$key] = cache_definition::load($definition['component'].'/'.$definition['area'], $definition);
 197          }
 198          foreach ($definitions as $id => $definition) {
 199              $mappings = array();
 200              foreach (cache_helper::get_stores_suitable_for_definition($definition) as $store) {
 201                  $mappings[] = $storenames[$store->my_name()];
 202              }
 203              $return[$id] = array(
 204                  'id' => $id,
 205                  'name' => $definition->get_name(),
 206                  'mode' => $definition->get_mode(),
 207                  'component' => $definition->get_component(),
 208                  'area' => $definition->get_area(),
 209                  'mappings' => $mappings,
 210                  'canuselocalstore' => $definition->can_use_localstore(),
 211                  'sharingoptions' => self::get_definition_sharing_options($definition->get_sharing_options(), false),
 212                  'selectedsharingoption' => self::get_definition_sharing_options($definition->get_selected_sharing_option(), true),
 213                  'userinputsharingkey' => $definition->get_user_input_sharing_key()
 214              );
 215          }
 216          return $return;
 217      }
 218  
 219      /**
 220       * Get the default stores for all modes.
 221       *
 222       * @return array An array containing sub-arrays, one for each mode.
 223       */
 224      public static function get_default_mode_stores(): array {
 225          global $OUTPUT;
 226          $instance = cache_config::instance();
 227          $adequatestores = cache_helper::get_stores_suitable_for_mode_default();
 228          $icon = new \pix_icon('i/warning', new \lang_string('inadequatestoreformapping', 'cache'));
 229          $storenames = array();
 230          foreach ($instance->get_all_stores() as $key => $store) {
 231              if (!empty($store['default'])) {
 232                  $storenames[$key] = new \lang_string('store_'.$key, 'cache');
 233              }
 234          }
 235          $modemappings = array(
 236              cache_store::MODE_APPLICATION => array(),
 237              cache_store::MODE_SESSION => array(),
 238              cache_store::MODE_REQUEST => array(),
 239          );
 240          foreach ($instance->get_mode_mappings() as $mapping) {
 241              $mode = $mapping['mode'];
 242              if (!array_key_exists($mode, $modemappings)) {
 243                  debugging('Unknown mode in cache store mode mappings', DEBUG_DEVELOPER);
 244                  continue;
 245              }
 246              if (array_key_exists($mapping['store'], $storenames)) {
 247                  $modemappings[$mode][$mapping['store']] = $storenames[$mapping['store']];
 248              } else {
 249                  $modemappings[$mode][$mapping['store']] = $mapping['store'];
 250              }
 251              if (!array_key_exists($mapping['store'], $adequatestores)) {
 252                  $modemappings[$mode][$mapping['store']] = $modemappings[$mode][$mapping['store']].' '.$OUTPUT->render($icon);
 253              }
 254          }
 255          return $modemappings;
 256      }
 257  
 258      /**
 259       * Returns an array summarising the locks available in the system.
 260       *
 261       * @return array array of lock summaries.
 262       */
 263      public static function get_lock_summaries(): array {
 264          $locks = array();
 265          $instance = cache_config::instance();
 266          $stores = $instance->get_all_stores();
 267          foreach ($instance->get_locks() as $lock) {
 268              $default = !empty($lock['default']);
 269              if ($default) {
 270                  $name = new \lang_string($lock['name'], 'cache');
 271              } else {
 272                  $name = $lock['name'];
 273              }
 274              $uses = 0;
 275              foreach ($stores as $store) {
 276                  if (!empty($store['lock']) && $store['lock'] === $lock['name']) {
 277                      $uses++;
 278                  }
 279              }
 280              $lockdata = array(
 281                  'name' => $name,
 282                  'default' => $default,
 283                  'uses' => $uses,
 284                  'type' => get_string('pluginname', $lock['type'])
 285              );
 286              $locks[$lock['name']] = $lockdata;
 287          }
 288          return $locks;
 289      }
 290  
 291      /**
 292       * Given a sharing option hash this function returns an array of strings that can be used to describe it.
 293       *
 294       * @param int $sharingoption The sharing option hash to get strings for.
 295       * @param bool $isselectedoptions Set to true if the strings will be used to view the selected options.
 296       * @return array An array of lang_string's.
 297       */
 298      public static function get_definition_sharing_options(int $sharingoption, bool $isselectedoptions = true): array {
 299          $options = array();
 300          $prefix = ($isselectedoptions) ? 'sharingselected' : 'sharing';
 301          if ($sharingoption & cache_definition::SHARING_ALL) {
 302              $options[cache_definition::SHARING_ALL] = new \lang_string($prefix.'_all', 'cache');
 303          }
 304          if ($sharingoption & cache_definition::SHARING_SITEID) {
 305              $options[cache_definition::SHARING_SITEID] = new \lang_string($prefix.'_siteid', 'cache');
 306          }
 307          if ($sharingoption & cache_definition::SHARING_VERSION) {
 308              $options[cache_definition::SHARING_VERSION] = new \lang_string($prefix.'_version', 'cache');
 309          }
 310          if ($sharingoption & cache_definition::SHARING_INPUT) {
 311              $options[cache_definition::SHARING_INPUT] = new \lang_string($prefix.'_input', 'cache');
 312          }
 313          return $options;
 314      }
 315  
 316      /**
 317       * Get an array of stores that are suitable to be used for a given definition.
 318       *
 319       * @param string $component
 320       * @param string $area
 321       * @return array Array containing 3 elements
 322       *      1. An array of currently used stores
 323       *      2. An array of suitable stores
 324       *      3. An array of default stores
 325       */
 326      public static function get_definition_store_options(string $component, string $area): array {
 327          $factory = cache_factory::instance();
 328          $definition = $factory->create_definition($component, $area);
 329          $config = cache_config::instance();
 330          $currentstores = $config->get_stores_for_definition($definition);
 331          $possiblestores = $config->get_stores($definition->get_mode(), $definition->get_requirements_bin());
 332  
 333          $defaults = array();
 334          foreach ($currentstores as $key => $store) {
 335              if (!empty($store['default'])) {
 336                  $defaults[] = $key;
 337                  unset($currentstores[$key]);
 338              }
 339          }
 340          foreach ($possiblestores as $key => $store) {
 341              if ($store['default']) {
 342                  unset($possiblestores[$key]);
 343                  $possiblestores[$key] = $store;
 344              }
 345          }
 346          return array($currentstores, $possiblestores, $defaults);
 347      }
 348  
 349      /**
 350       * This function must be implemented to display options for store plugins.
 351       *
 352       * @param string $name the name of the store plugin.
 353       * @param array $plugindetails array of store plugin details.
 354       * @return array array of actions.
 355       */
 356      public function get_store_plugin_actions(string $name, array $plugindetails): array {
 357          return array();
 358      }
 359  
 360      /**
 361       * This function must be implemented to display options for store instances.
 362       *
 363       * @param string $name the store instance name.
 364       * @param array $storedetails array of store instance details.
 365       * @return array array of actions.
 366       */
 367      public function get_store_instance_actions(string $name, array $storedetails): array {
 368          return array();
 369      }
 370  
 371      /**
 372       * This function must be implemented to display options for definition mappings.
 373       *
 374       * @param context $context the context for the definition.
 375       * @param array $definitionsummary the definition summary.
 376       * @return array array of actions.
 377       */
 378      public function get_definition_actions(\context $context, array $definitionsummary): array {
 379          return array();
 380      }
 381  
 382      /**
 383       * This function must be implemented to get addable locks.
 384       *
 385       * @return array array of locks that are addable.
 386       */
 387      public function get_addable_lock_options(): array {
 388          return array();
 389      }
 390  
 391      /**
 392       * This function must be implemented to perform any page actions by a child class.
 393       *
 394       * @param string $action the action to perform.
 395       * @param array $forminfo empty array to be set by actions.
 396       * @return array array of form info.
 397       */
 398      public abstract function perform_cache_actions(string $action, array $forminfo): array;
 399  
 400      /**
 401       * This function must be implemented to display the cache admin page.
 402       *
 403       * @param \core_cache\output\renderer $renderer the renderer used to generate the page.
 404       * @return string the HTML for the page.
 405       */
 406      abstract public function generate_admin_page(\core_cache\output\renderer $renderer): string;
 407  
 408      /**
 409       * Gets usage information about the whole cache system.
 410       *
 411       * This is a slow function and should only be used on an admin information page.
 412       *
 413       * The returned array lists all cache definitions with fields 'cacheid' and 'stores'. For
 414       * each store, the following fields are available:
 415       *
 416       * - name (store name)
 417       * - class (e.g. cachestore_redis)
 418       * - supported (true if we have any information)
 419       * - items (number of items stored)
 420       * - mean (mean size of item)
 421       * - sd (standard deviation for item sizes)
 422       * - margin (margin of error for mean at 95% confidence)
 423       * - storetotal (total usage for store if known, otherwise null)
 424       *
 425       * The storetotal field will be the same for every cache that uses the same store.
 426       *
 427       * @param int $samplekeys Number of keys to sample when checking size of large caches
 428       * @return array Details of cache usage
 429       */
 430      abstract public function get_usage(int $samplekeys): array;
 431  }