Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403]

   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 display 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\local;
  32  
  33  defined('MOODLE_INTERNAL') || die();
  34  use cache_store, cache_factory, cache_config_writer, cache_helper, core_cache_renderer;
  35  
  36  /**
  37   * A cache helper for administration tasks
  38   *
  39   * @package    core
  40   * @category   cache
  41   * @copyright  2020 Peter Burnett <peterburnett@catalyst-au.net>
  42   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  43   */
  44  class administration_display_helper extends \core_cache\administration_helper {
  45  
  46      /**
  47       * Please do not call constructor directly. Use cache_factory::get_administration_display_helper() instead.
  48       */
  49      public function __construct() {
  50          // Nothing to do here.
  51      }
  52  
  53      /**
  54       * Returns all of the actions that can be performed on a definition.
  55       *
  56       * @param context $context the system context.
  57       * @param array $definitionsummary information about this cache, from the array returned by
  58       *      core_cache\administration_helper::get_definition_summaries(). Currently only 'sharingoptions'
  59       *      element is used.
  60       * @return array of actions. Each action is an action_url.
  61       */
  62      public function get_definition_actions(\context $context, array $definitionsummary): array {
  63          global $OUTPUT;
  64          if (has_capability('moodle/site:config', $context)) {
  65              $actions = array();
  66              // Edit mappings.
  67              $actions[] = $OUTPUT->action_link(
  68                  new \moodle_url('/cache/admin.php', array('action' => 'editdefinitionmapping',
  69                      'definition' => $definitionsummary['id'], 'sesskey' => sesskey())),
  70                  get_string('editmappings', 'cache')
  71              );
  72              // Edit sharing.
  73              if (count($definitionsummary['sharingoptions']) > 1) {
  74                  $actions[] = $OUTPUT->action_link(
  75                      new \moodle_url('/cache/admin.php', array('action' => 'editdefinitionsharing',
  76                          'definition' => $definitionsummary['id'], 'sesskey' => sesskey())),
  77                      get_string('editsharing', 'cache')
  78                  );
  79              }
  80              // Purge.
  81              $actions[] = $OUTPUT->action_link(
  82                  new \moodle_url('/cache/admin.php', array('action' => 'purgedefinition',
  83                      'definition' => $definitionsummary['id'], 'sesskey' => sesskey())),
  84                  get_string('purge', 'cache')
  85              );
  86              return $actions;
  87          }
  88          return array();
  89      }
  90  
  91      /**
  92       * Returns all of the actions that can be performed on a store.
  93       *
  94       * @param string $name The name of the store
  95       * @param array $storedetails information about this store, from the array returned by
  96       *      core_cache\administration_helper::get_store_instance_summaries().
  97       * @return array of actions. Each action is an action_url.
  98       */
  99      public function get_store_instance_actions(string $name, array $storedetails): array {
 100          global $OUTPUT;
 101          $actions = array();
 102          if (has_capability('moodle/site:config', \context_system::instance())) {
 103              $baseurl = new \moodle_url('/cache/admin.php', array('store' => $name, 'sesskey' => sesskey()));
 104              if (empty($storedetails['default'])) {
 105                  $actions[] = $OUTPUT->action_link(
 106                      new \moodle_url($baseurl, array('action' => 'editstore', 'plugin' => $storedetails['plugin'])),
 107                      get_string('editstore', 'cache')
 108                  );
 109  
 110                  $actions[] = $OUTPUT->action_link(
 111                      new \moodle_url($baseurl, array('action' => 'deletestore')),
 112                      get_string('deletestore', 'cache')
 113                  );
 114              }
 115  
 116              $actions[] = $OUTPUT->action_link(
 117                  new \moodle_url($baseurl, array('action' => 'purgestore')),
 118                  get_string('purge', 'cache')
 119              );
 120          }
 121          return $actions;
 122      }
 123  
 124      /**
 125       * Returns all of the actions that can be performed on a plugin.
 126       *
 127       * @param string $name The name of the plugin
 128       * @param array $plugindetails information about this store, from the array returned by
 129       *      core_cache\administration_helper::get_store_plugin_summaries().
 130       * @return array of actions. Each action is an action_url.
 131       */
 132      public function get_store_plugin_actions(string $name, array $plugindetails): array {
 133          global $OUTPUT;
 134          $actions = array();
 135          if (has_capability('moodle/site:config', \context_system::instance())) {
 136              if (!empty($plugindetails['canaddinstance'])) {
 137                  $url = new \moodle_url('/cache/admin.php',
 138                      array('action' => 'addstore', 'plugin' => $name, 'sesskey' => sesskey()));
 139                  $actions[] = $OUTPUT->action_link(
 140                      $url,
 141                      get_string('addinstance', 'cache')
 142                  );
 143              }
 144          }
 145          return $actions;
 146      }
 147  
 148      /**
 149       * Returns a form that can be used to add a store instance.
 150       *
 151       * @param string $plugin The plugin to add an instance of
 152       * @return cachestore_addinstance_form
 153       * @throws coding_exception
 154       */
 155      public function get_add_store_form(string $plugin): \cachestore_addinstance_form {
 156          global $CFG; // Needed for includes.
 157          $plugins = \core_component::get_plugin_list('cachestore');
 158          if (!array_key_exists($plugin, $plugins)) {
 159              throw new \coding_exception('Invalid cache plugin used when trying to create an edit form.');
 160          }
 161          $plugindir = $plugins[$plugin];
 162          $class = 'cachestore_addinstance_form';
 163          if (file_exists($plugindir.'/addinstanceform.php')) {
 164              require_once($plugindir.'/addinstanceform.php');
 165              if (class_exists('cachestore_'.$plugin.'_addinstance_form')) {
 166                  $class = 'cachestore_'.$plugin.'_addinstance_form';
 167                  if (!array_key_exists('cachestore_addinstance_form', class_parents($class))) {
 168                      throw new \coding_exception('Cache plugin add instance forms must extend cachestore_addinstance_form');
 169                  }
 170              }
 171          }
 172  
 173          $locks = $this->get_possible_locks_for_stores($plugindir, $plugin);
 174  
 175          $url = new \moodle_url('/cache/admin.php', array('action' => 'addstore'));
 176          return new $class($url, array('plugin' => $plugin, 'store' => null, 'locks' => $locks));
 177      }
 178  
 179      /**
 180       * Returns a form that can be used to edit a store instance.
 181       *
 182       * @param string $plugin
 183       * @param string $store
 184       * @return cachestore_addinstance_form
 185       * @throws coding_exception
 186       */
 187      public function get_edit_store_form(string $plugin, string $store): \cachestore_addinstance_form {
 188          global $CFG; // Needed for includes.
 189          $plugins = \core_component::get_plugin_list('cachestore');
 190          if (!array_key_exists($plugin, $plugins)) {
 191              throw new \coding_exception('Invalid cache plugin used when trying to create an edit form.');
 192          }
 193          $factory = \cache_factory::instance();
 194          $config = $factory->create_config_instance();
 195          $stores = $config->get_all_stores();
 196          if (!array_key_exists($store, $stores)) {
 197              throw new \coding_exception('Invalid store name given when trying to create an edit form.');
 198          }
 199          $plugindir = $plugins[$plugin];
 200          $class = 'cachestore_addinstance_form';
 201          if (file_exists($plugindir.'/addinstanceform.php')) {
 202              require_once($plugindir.'/addinstanceform.php');
 203              if (class_exists('cachestore_'.$plugin.'_addinstance_form')) {
 204                  $class = 'cachestore_'.$plugin.'_addinstance_form';
 205                  if (!array_key_exists('cachestore_addinstance_form', class_parents($class))) {
 206                      throw new \coding_exception('Cache plugin add instance forms must extend cachestore_addinstance_form');
 207                  }
 208              }
 209          }
 210  
 211          $locks = $this->get_possible_locks_for_stores($plugindir, $plugin);
 212  
 213          $url = new \moodle_url('/cache/admin.php', array('action' => 'editstore', 'plugin' => $plugin, 'store' => $store));
 214          $editform = new $class($url, array('plugin' => $plugin, 'store' => $store, 'locks' => $locks));
 215          if (isset($stores[$store]['lock'])) {
 216              $editform->set_data(array('lock' => $stores[$store]['lock']));
 217          }
 218          // See if the cachestore is going to want to load data for the form.
 219          // If it has a customised add instance form then it is going to want to.
 220          $storeclass = 'cachestore_'.$plugin;
 221          $storedata = $stores[$store];
 222          if (array_key_exists('configuration', $storedata) &&
 223              array_key_exists('cache_is_configurable', class_implements($storeclass))) {
 224              $storeclass::config_set_edit_form_data($editform, $storedata['configuration']);
 225          }
 226          return $editform;
 227      }
 228  
 229      /**
 230       * Returns an array of suitable lock instances for use with this plugin, or false if the plugin handles locking itself.
 231       *
 232       * @param string $plugindir
 233       * @param string $plugin
 234       * @return array|false
 235       */
 236      protected function get_possible_locks_for_stores(string $plugindir, string $plugin) {
 237          global $CFG; // Needed for includes.
 238          $supportsnativelocking = false;
 239          if (file_exists($plugindir.'/lib.php')) {
 240              require_once($plugindir.'/lib.php');
 241              $pluginclass = 'cachestore_'.$plugin;
 242              if (class_exists($pluginclass)) {
 243                  $supportsnativelocking = array_key_exists('cache_is_lockable', class_implements($pluginclass));
 244              }
 245          }
 246  
 247          if (!$supportsnativelocking) {
 248              $config = \cache_config::instance();
 249              $locks = array();
 250              foreach ($config->get_locks() as $lock => $conf) {
 251                  if (!empty($conf['default'])) {
 252                      $name = get_string($lock, 'cache');
 253                  } else {
 254                      $name = $lock;
 255                  }
 256                  $locks[$lock] = $name;
 257              }
 258          } else {
 259              $locks = false;
 260          }
 261  
 262          return $locks;
 263      }
 264  
 265      /**
 266       * Processes the results of the add/edit instance form data for a plugin returning an array of config information suitable to
 267       * store in configuration.
 268       *
 269       * @param stdClass $data The mform data.
 270       * @return array
 271       * @throws coding_exception
 272       */
 273      public function get_store_configuration_from_data(\stdClass $data): array {
 274          global $CFG;
 275          $file = $CFG->dirroot.'/cache/stores/'.$data->plugin.'/lib.php';
 276          if (!file_exists($file)) {
 277              throw new \coding_exception('Invalid cache plugin provided. '.$file);
 278          }
 279          require_once($file);
 280          $class = 'cachestore_'.$data->plugin;
 281          if (!class_exists($class)) {
 282              throw new \coding_exception('Invalid cache plugin provided.');
 283          }
 284          if (array_key_exists('cache_is_configurable', class_implements($class))) {
 285              return $class::config_get_configuration_array($data);
 286          }
 287          return array();
 288      }
 289  
 290      /**
 291       * Returns an array of lock plugins for which we can add an instance.
 292       *
 293       * Suitable for use within an mform select element.
 294       *
 295       * @return array
 296       */
 297      public function get_addable_lock_options(): array {
 298          $plugins = \core_component::get_plugin_list_with_class('cachelock', '', 'lib.php');
 299          $options = array();
 300          $len = strlen('cachelock_');
 301          foreach ($plugins as $plugin => $class) {
 302              $method = "$class::can_add_instance";
 303              if (is_callable($method) && !call_user_func($method)) {
 304                  // Can't add an instance of this plugin.
 305                  continue;
 306              }
 307              $options[substr($plugin, $len)] = get_string('pluginname', $plugin);
 308          }
 309          return $options;
 310      }
 311  
 312      /**
 313       * Gets the form to use when adding a lock instance.
 314       *
 315       * @param string $plugin
 316       * @param array $lockplugin
 317       * @return cache_lock_form
 318       * @throws coding_exception
 319       */
 320      public function get_add_lock_form(string $plugin, array $lockplugin = null): \cache_lock_form {
 321          global $CFG; // Needed for includes.
 322          $plugins = \core_component::get_plugin_list('cachelock');
 323          if (!array_key_exists($plugin, $plugins)) {
 324              throw new \coding_exception('Invalid cache lock plugin requested when trying to create a form.');
 325          }
 326          $plugindir = $plugins[$plugin];
 327          $class = 'cache_lock_form';
 328          if (file_exists($plugindir.'/addinstanceform.php') && in_array('cache_is_configurable', class_implements($class))) {
 329              require_once($plugindir.'/addinstanceform.php');
 330              if (class_exists('cachelock_'.$plugin.'_addinstance_form')) {
 331                  $class = 'cachelock_'.$plugin.'_addinstance_form';
 332                  if (!array_key_exists('cache_lock_form', class_parents($class))) {
 333                      throw new \coding_exception('Cache lock plugin add instance forms must extend cache_lock_form');
 334                  }
 335              }
 336          }
 337          return new $class(null, array('lock' => $plugin));
 338      }
 339  
 340      /**
 341       * Gets configuration data from a new lock instance form.
 342       *
 343       * @param string $plugin
 344       * @param stdClass $data
 345       * @return array
 346       * @throws coding_exception
 347       */
 348      public function get_lock_configuration_from_data(string $plugin, \stdClass $data): array {
 349          global $CFG;
 350          $file = $CFG->dirroot.'/cache/locks/'.$plugin.'/lib.php';
 351          if (!file_exists($file)) {
 352              throw new \coding_exception('Invalid cache plugin provided. '.$file);
 353          }
 354          require_once($file);
 355          $class = 'cachelock_'.$plugin;
 356          if (!class_exists($class)) {
 357              throw new \coding_exception('Invalid cache plugin provided.');
 358          }
 359          if (array_key_exists('cache_is_configurable', class_implements($class))) {
 360              return $class::config_get_configuration_array($data);
 361          }
 362          return array();
 363      }
 364  
 365      /**
 366       * Handles the page actions, based on the parameter.
 367       *
 368       * @param string $action the action to handle.
 369       * @param array $forminfo an empty array to be overridden and set.
 370       * @return array the empty or overridden forminfo array.
 371       */
 372      public function perform_cache_actions(string $action, array $forminfo): array {
 373          switch ($action) {
 374              case 'rescandefinitions' : // Rescan definitions.
 375                  $this->action_rescan_definition();
 376                  break;
 377  
 378              case 'addstore' : // Add the requested store.
 379                  $forminfo = $this->action_addstore();
 380                  break;
 381  
 382              case 'editstore' : // Edit the requested store.
 383                  $forminfo = $this->action_editstore();
 384                  break;
 385  
 386              case 'deletestore' : // Delete a given store.
 387                  $this->action_deletestore($action);
 388                  break;
 389  
 390              case 'editdefinitionmapping' : // Edit definition mappings.
 391                  $forminfo = $this->action_editdefinitionmapping();
 392                  break;
 393  
 394              case 'editdefinitionsharing' : // Edit definition sharing.
 395                  $forminfo = $this->action_editdefinitionsharing();
 396                  break;
 397  
 398              case 'editmodemappings': // Edit default mode mappings.
 399                  $forminfo = $this->action_editmodemappings();
 400                  break;
 401  
 402              case 'purgedefinition': // Purge a specific definition.
 403                  $this->action_purgedefinition();
 404                  break;
 405  
 406              case 'purgestore':
 407              case 'purge': // Purge a store cache.
 408                  $this->action_purge();
 409                  break;
 410  
 411              case 'newlockinstance':
 412                  $forminfo = $this->action_newlockinstance();
 413                  break;
 414  
 415              case 'deletelock':
 416                  // Deletes a lock instance.
 417                  $this->action_deletelock($action);
 418                  break;
 419          }
 420  
 421          return $forminfo;
 422      }
 423  
 424      /**
 425       * Performs the rescan definition action.
 426       *
 427       * @return void
 428       */
 429      public function action_rescan_definition() {
 430          global $PAGE;
 431  
 432          \cache_config_writer::update_definitions();
 433          redirect($PAGE->url);
 434      }
 435  
 436      /**
 437       * Performs the add store action.
 438       *
 439       * @return array an array of the form to display to the user, and the page title.
 440       */
 441      public function action_addstore() : array {
 442          global $PAGE;
 443          $storepluginsummaries = $this->get_store_plugin_summaries();
 444  
 445          $plugin = required_param('plugin', PARAM_PLUGIN);
 446          if (!$storepluginsummaries[$plugin]['canaddinstance']) {
 447              print_error('ex_unmetstorerequirements', 'cache');
 448          }
 449          $mform = $this->get_add_store_form($plugin);
 450          $title = get_string('addstore', 'cache', $storepluginsummaries[$plugin]['name']);
 451          if ($mform->is_cancelled()) {
 452              redirect($PAGE->url);
 453          } else if ($data = $mform->get_data()) {
 454              $config = $this->get_store_configuration_from_data($data);
 455              $writer = \cache_config_writer::instance();
 456              unset($config['lock']);
 457              foreach ($writer->get_locks() as $lock => $lockconfig) {
 458                  if ($lock == $data->lock) {
 459                      $config['lock'] = $data->lock;
 460                  }
 461              }
 462              $writer->add_store_instance($data->name, $data->plugin, $config);
 463              redirect($PAGE->url, get_string('addstoresuccess', 'cache', $storepluginsummaries[$plugin]['name']), 5);
 464          }
 465  
 466          return array('form' => $mform, 'title' => $title);
 467      }
 468  
 469      /**
 470       * Performs the edit store action.
 471       *
 472       * @return array an array of the form to display, and the page title.
 473       */
 474      public function action_editstore(): array {
 475          global $PAGE;
 476          $storepluginsummaries = $this->get_store_plugin_summaries();
 477  
 478          $plugin = required_param('plugin', PARAM_PLUGIN);
 479          $store = required_param('store', PARAM_TEXT);
 480          $mform = $this->get_edit_store_form($plugin, $store);
 481          $title = get_string('addstore', 'cache', $storepluginsummaries[$plugin]['name']);
 482          if ($mform->is_cancelled()) {
 483              redirect($PAGE->url);
 484          } else if ($data = $mform->get_data()) {
 485              $config = $this->get_store_configuration_from_data($data);
 486              $writer = \cache_config_writer::instance();
 487  
 488              unset($config['lock']);
 489              foreach ($writer->get_locks() as $lock => $lockconfig) {
 490                  if ($lock == $data->lock) {
 491                      $config['lock'] = $data->lock;
 492                  }
 493              }
 494              $writer->edit_store_instance($data->name, $data->plugin, $config);
 495              redirect($PAGE->url, get_string('editstoresuccess', 'cache', $storepluginsummaries[$plugin]['name']), 5);
 496          }
 497  
 498          return array('form' => $mform, 'title' => $title);
 499      }
 500  
 501      /**
 502       * Performs the deletestore action.
 503       *
 504       * @param string $action the action calling to this function.
 505       * @return void
 506       */
 507      public function action_deletestore(string $action) {
 508          global $OUTPUT, $PAGE, $SITE;
 509          $notifysuccess = true;
 510          $storeinstancesummaries = $this->get_store_instance_summaries();
 511  
 512          $store = required_param('store', PARAM_TEXT);
 513          $confirm = optional_param('confirm', false, PARAM_BOOL);
 514  
 515          if (!array_key_exists($store, $storeinstancesummaries)) {
 516              $notifysuccess = false;
 517              $notifications[] = array(get_string('invalidstore', 'cache'), false);
 518          } else if ($storeinstancesummaries[$store]['mappings'] > 0) {
 519              $notifysuccess = false;
 520              $notifications[] = array(get_string('deletestorehasmappings', 'cache'), false);
 521          }
 522  
 523          if ($notifysuccess) {
 524              if (!$confirm) {
 525                  $title = get_string('confirmstoredeletion', 'cache');
 526                  $params = array('store' => $store, 'confirm' => 1, 'action' => $action, 'sesskey' => sesskey());
 527                  $url = new \moodle_url($PAGE->url, $params);
 528                  $button = new \single_button($url, get_string('deletestore', 'cache'));
 529  
 530                  $PAGE->set_title($title);
 531                  $PAGE->set_heading($SITE->fullname);
 532                  echo $OUTPUT->header();
 533                  echo $OUTPUT->heading($title);
 534                  $confirmation = get_string('deletestoreconfirmation', 'cache', $storeinstancesummaries[$store]['name']);
 535                  echo $OUTPUT->confirm($confirmation, $button, $PAGE->url);
 536                  echo $OUTPUT->footer();
 537                  exit;
 538              } else {
 539                  $writer = \cache_config_writer::instance();
 540                  $writer->delete_store_instance($store);
 541                  redirect($PAGE->url, get_string('deletestoresuccess', 'cache'), 5);
 542              }
 543          }
 544      }
 545  
 546      /**
 547       * Performs the edit definition mapping action.
 548       *
 549       * @return array an array of the form to display, and the page title.
 550       * @throws cache_exception
 551       */
 552      public function action_editdefinitionmapping(): array {
 553          global $PAGE;
 554          $definitionsummaries = $this->get_definition_summaries();
 555  
 556          $definition = required_param('definition', PARAM_SAFEPATH);
 557          if (!array_key_exists($definition, $definitionsummaries)) {
 558              throw new \cache_exception('Invalid cache definition requested');
 559          }
 560          $title = get_string('editdefinitionmappings', 'cache', $definition);
 561          $mform = new \cache_definition_mappings_form($PAGE->url, array('definition' => $definition));
 562          if ($mform->is_cancelled()) {
 563              redirect($PAGE->url);
 564          } else if ($data = $mform->get_data()) {
 565              $writer = \cache_config_writer::instance();
 566              $mappings = array();
 567              foreach ($data->mappings as $mapping) {
 568                  if (!empty($mapping)) {
 569                      $mappings[] = $mapping;
 570                  }
 571              }
 572              $writer->set_definition_mappings($definition, $mappings);
 573              redirect($PAGE->url);
 574          }
 575  
 576          return array('form' => $mform, 'title' => $title);
 577      }
 578  
 579      /**
 580       * Performs the edit definition sharing action.
 581       *
 582       * @return array an array of the edit definition sharing form, and the page title.
 583       */
 584      public function action_editdefinitionsharing(): array {
 585          global $PAGE;
 586          $definitionsummaries = $this->get_definition_summaries();
 587  
 588          $definition = required_param('definition', PARAM_SAFEPATH);
 589          if (!array_key_exists($definition, $definitionsummaries)) {
 590              throw new \cache_exception('Invalid cache definition requested');
 591          }
 592          $title = get_string('editdefinitionsharing', 'cache', $definition);
 593          $sharingoptions = $definitionsummaries[$definition]['sharingoptions'];
 594          $customdata = array('definition' => $definition, 'sharingoptions' => $sharingoptions);
 595          $mform = new \cache_definition_sharing_form($PAGE->url, $customdata);
 596          $mform->set_data(array(
 597              'sharing' => $definitionsummaries[$definition]['selectedsharingoption'],
 598              'userinputsharingkey' => $definitionsummaries[$definition]['userinputsharingkey']
 599          ));
 600          if ($mform->is_cancelled()) {
 601              redirect($PAGE->url);
 602          } else if ($data = $mform->get_data()) {
 603              $component = $definitionsummaries[$definition]['component'];
 604              $area = $definitionsummaries[$definition]['area'];
 605              // Purge the stores removing stale data before we alter the sharing option.
 606              \cache_helper::purge_stores_used_by_definition($component, $area);
 607              $writer = \cache_config_writer::instance();
 608              $sharing = array_sum(array_keys($data->sharing));
 609              $userinputsharingkey = $data->userinputsharingkey;
 610              $writer->set_definition_sharing($definition, $sharing, $userinputsharingkey);
 611              redirect($PAGE->url);
 612          }
 613  
 614          return array('form' => $mform, 'title' => $title);
 615      }
 616  
 617      /**
 618       * Performs the edit mode mappings action.
 619       *
 620       * @return array an array of the edit mode mappings form.
 621       */
 622      public function action_editmodemappings(): array {
 623          global $PAGE;
 624          $storeinstancesummaries = $this->get_store_instance_summaries();
 625          $defaultmodestores = $this->get_default_mode_stores();
 626  
 627          $mform = new \cache_mode_mappings_form(null, $storeinstancesummaries);
 628          $mform->set_data(array(
 629              'mode_'.cache_store::MODE_APPLICATION => key($defaultmodestores[cache_store::MODE_APPLICATION]),
 630              'mode_'.cache_store::MODE_SESSION => key($defaultmodestores[cache_store::MODE_SESSION]),
 631              'mode_'.cache_store::MODE_REQUEST => key($defaultmodestores[cache_store::MODE_REQUEST]),
 632          ));
 633          if ($mform->is_cancelled()) {
 634              redirect($PAGE->url);
 635          } else if ($data = $mform->get_data()) {
 636              $mappings = array(
 637                  cache_store::MODE_APPLICATION => array($data->{'mode_'.cache_store::MODE_APPLICATION}),
 638                  cache_store::MODE_SESSION => array($data->{'mode_'.cache_store::MODE_SESSION}),
 639                  cache_store::MODE_REQUEST => array($data->{'mode_'.cache_store::MODE_REQUEST}),
 640              );
 641              $writer = cache_config_writer::instance();
 642              $writer->set_mode_mappings($mappings);
 643              redirect($PAGE->url);
 644          }
 645  
 646          return array('form' => $mform);
 647      }
 648  
 649      /**
 650       * Performs the purge definition action.
 651       *
 652       * @return void
 653       */
 654      public function action_purgedefinition() {
 655          global $PAGE;
 656  
 657          $id = required_param('definition', PARAM_SAFEPATH);
 658          list($component, $area) = explode('/', $id, 2);
 659          $factory = cache_factory::instance();
 660          $definition = $factory->create_definition($component, $area);
 661          if ($definition->has_required_identifiers()) {
 662              // We will have to purge the stores used by this definition.
 663              cache_helper::purge_stores_used_by_definition($component, $area);
 664          } else {
 665              // Alrighty we can purge just the data belonging to this definition.
 666              cache_helper::purge_by_definition($component, $area);
 667          }
 668  
 669          $message = get_string('purgexdefinitionsuccess', 'cache', [
 670                      'name' => $definition->get_name(),
 671                      'component' => $component,
 672                      'area' => $area,
 673                  ]);
 674          $purgeagainlink = \html_writer::link(new \moodle_url('/cache/admin.php', [
 675                  'action' => 'purgedefinition', 'sesskey' => sesskey(), 'definition' => $id]),
 676                  get_string('purgeagain', 'cache'));
 677          redirect($PAGE->url, $message . ' ' . $purgeagainlink, 5);
 678      }
 679  
 680      /**
 681       * Performs the purge action.
 682       *
 683       * @return void
 684       */
 685      public function action_purge() {
 686          global $PAGE;
 687  
 688          $store = required_param('store', PARAM_TEXT);
 689          cache_helper::purge_store($store);
 690          $message = get_string('purgexstoresuccess', 'cache', ['store' => $store]);
 691          $purgeagainlink = \html_writer::link(new \moodle_url('/cache/admin.php', [
 692                  'action' => 'purgestore', 'sesskey' => sesskey(), 'store' => $store]),
 693                  get_string('purgeagain', 'cache'));
 694          redirect($PAGE->url, $message . ' ' . $purgeagainlink, 5);
 695      }
 696  
 697      /**
 698       * Performs the new lock instance action.
 699       *
 700       * @return array An array containing the new lock instance form.
 701       */
 702      public function action_newlockinstance(): array {
 703          global $PAGE;
 704  
 705          // Adds a new lock instance.
 706          $lock = required_param('lock', PARAM_ALPHANUMEXT);
 707          $mform = $this->get_add_lock_form($lock);
 708          if ($mform->is_cancelled()) {
 709              redirect($PAGE->url);
 710          } else if ($data = $mform->get_data()) {
 711              $factory = cache_factory::instance();
 712              $config = $factory->create_config_instance(true);
 713              $name = $data->name;
 714              $data = $this->get_lock_configuration_from_data($lock, $data);
 715              $config->add_lock_instance($name, $lock, $data);
 716              redirect($PAGE->url, get_string('addlocksuccess', 'cache', $name), 5);
 717          }
 718  
 719          return array('form' => $mform);
 720      }
 721  
 722      /**
 723       * Performs the delete lock action.
 724       *
 725       * @param string $action the action calling this function.
 726       * @return void
 727       */
 728      public function action_deletelock(string $action) {
 729          global $OUTPUT, $PAGE, $SITE;
 730          $notifysuccess = true;
 731          $locks = $this->get_lock_summaries();
 732  
 733          $lock = required_param('lock', PARAM_ALPHANUMEXT);
 734          $confirm = optional_param('confirm', false, PARAM_BOOL);
 735          if (!array_key_exists($lock, $locks)) {
 736              $notifysuccess = false;
 737              $notifications[] = array(get_string('invalidlock', 'cache'), false);
 738          } else if ($locks[$lock]['uses'] > 0) {
 739              $notifysuccess = false;
 740              $notifications[] = array(get_string('deletelockhasuses', 'cache'), false);
 741          }
 742          if ($notifysuccess) {
 743              if (!$confirm) {
 744                  $title = get_string('confirmlockdeletion', 'cache');
 745                  $params = array('lock' => $lock, 'confirm' => 1, 'action' => $action, 'sesskey' => sesskey());
 746                  $url = new \moodle_url($PAGE->url, $params);
 747                  $button = new \single_button($url, get_string('deletelock', 'cache'));
 748  
 749                  $PAGE->set_title($title);
 750                  $PAGE->set_heading($SITE->fullname);
 751                  echo $OUTPUT->header();
 752                  echo $OUTPUT->heading($title);
 753                  $confirmation = get_string('deletelockconfirmation', 'cache', $lock);
 754                  echo $OUTPUT->confirm($confirmation, $button, $PAGE->url);
 755                  echo $OUTPUT->footer();
 756                  exit;
 757              } else {
 758                  $writer = cache_config_writer::instance();
 759                  $writer->delete_lock_instance($lock);
 760                  redirect($PAGE->url, get_string('deletelocksuccess', 'cache'), 5);
 761              }
 762          }
 763      }
 764  
 765      /**
 766       * Outputs the main admin page by generating it through the renderer.
 767       *
 768       * @param core_cache_renderer $renderer the renderer to use to generate the page.
 769       * @return string the HTML for the admin page.
 770       */
 771      public function generate_admin_page(core_cache_renderer $renderer): string {
 772          $context = \context_system::instance();
 773          $html = '';
 774  
 775          $storepluginsummaries = $this->get_store_plugin_summaries();
 776          $storeinstancesummaries = $this->get_store_instance_summaries();
 777          $definitionsummaries = $this->get_definition_summaries();
 778          $defaultmodestores = $this->get_default_mode_stores();
 779          $locks = $this->get_lock_summaries();
 780  
 781          $html .= $renderer->store_plugin_summaries($storepluginsummaries);
 782          $html .= $renderer->store_instance_summariers($storeinstancesummaries, $storepluginsummaries);
 783          $html .= $renderer->definition_summaries($definitionsummaries, $context);
 784          $html .= $renderer->lock_summaries($locks);
 785          $html .= $renderer->additional_lock_actions();
 786  
 787          $applicationstore = join(', ', $defaultmodestores[cache_store::MODE_APPLICATION]);
 788          $sessionstore = join(', ', $defaultmodestores[cache_store::MODE_SESSION]);
 789          $requeststore = join(', ', $defaultmodestores[cache_store::MODE_REQUEST]);
 790          $editurl = new \moodle_url('/cache/admin.php', array('action' => 'editmodemappings', 'sesskey' => sesskey()));
 791          $html .= $renderer->mode_mappings($applicationstore, $sessionstore, $requeststore, $editurl);
 792  
 793          return $html;
 794      }
 795  }