diff --git a/cache/admin.php b/cache/admin.php index f8931b60601..86b730e0c7e 100644 --- a/cache/admin.php +++ b/cache/admin.php @@ -42,263 +42,27 @@ if (empty($SESSION->cacheadminreparsedefinitions)) { $action = optional_param('action', null, PARAM_ALPHA); admin_externalpage_setup('cacheconfig'); -$context = context_system::instance(); +$adminhelper = cache_factory::instance()->get_administration_display_helper(); -$storeinstancesummaries = cache_administration_helper::get_store_instance_summaries(); -$storepluginsummaries = cache_administration_helper::get_store_plugin_summaries(); -$definitionsummaries = cache_administration_helper::get_definition_summaries(); -$defaultmodestores = cache_administration_helper::get_default_mode_stores(); -$locks = cache_administration_helper::get_lock_summaries(); - -$title = new lang_string('cacheadmin', 'cache'); -$mform = null; $notifications = array(); -$notifysuccess = true; +// Empty array to hold any form information returned from actions. +$forminfo = []; +// Handle page actions in admin helper class. if (!empty($action) && confirm_sesskey()) { - switch ($action) { - case 'rescandefinitions' : // Rescan definitions. - cache_config_writer::update_definitions(); - redirect($PAGE->url); - break; - case 'addstore' : // Add the requested store. - $plugin = required_param('plugin', PARAM_PLUGIN); - if (!$storepluginsummaries[$plugin]['canaddinstance']) { - print_error('ex_unmetstorerequirements', 'cache'); - } - $mform = cache_administration_helper::get_add_store_form($plugin); - $title = get_string('addstore', 'cache', $storepluginsummaries[$plugin]['name']); - if ($mform->is_cancelled()) { - redirect($PAGE->url); - } else if ($data = $mform->get_data()) { - $config = cache_administration_helper::get_store_configuration_from_data($data); - $writer = cache_config_writer::instance(); - unset($config['lock']); - foreach ($writer->get_locks() as $lock => $lockconfig) { - if ($lock == $data->lock) { - $config['lock'] = $data->lock; - } - } - $writer->add_store_instance($data->name, $data->plugin, $config); - redirect($PAGE->url, get_string('addstoresuccess', 'cache', $storepluginsummaries[$plugin]['name']), 5); - } - break; - case 'editstore' : // Edit the requested store. - $plugin = required_param('plugin', PARAM_PLUGIN); - $store = required_param('store', PARAM_TEXT); - $mform = cache_administration_helper::get_edit_store_form($plugin, $store); - $title = get_string('addstore', 'cache', $storepluginsummaries[$plugin]['name']); - if ($mform->is_cancelled()) { - redirect($PAGE->url); - } else if ($data = $mform->get_data()) { - $config = cache_administration_helper::get_store_configuration_from_data($data); - $writer = cache_config_writer::instance(); - - unset($config['lock']); - foreach ($writer->get_locks() as $lock => $lockconfig) { - if ($lock == $data->lock) { - $config['lock'] = $data->lock; - } - } - $writer->edit_store_instance($data->name, $data->plugin, $config); - redirect($PAGE->url, get_string('editstoresuccess', 'cache', $storepluginsummaries[$plugin]['name']), 5); - } - break; - case 'deletestore' : // Delete a given store. - $store = required_param('store', PARAM_TEXT); - $confirm = optional_param('confirm', false, PARAM_BOOL); - - if (!array_key_exists($store, $storeinstancesummaries)) { - $notifysuccess = false; - $notifications[] = array(get_string('invalidstore', 'cache'), false); - } else if ($storeinstancesummaries[$store]['mappings'] > 0) { - $notifysuccess = false; - $notifications[] = array(get_string('deletestorehasmappings', 'cache'), false); - } - - if ($notifysuccess) { - if (!$confirm) { - $title = get_string('confirmstoredeletion', 'cache'); - $params = array('store' => $store, 'confirm' => 1, 'action' => $action, 'sesskey' => sesskey()); - $url = new moodle_url($PAGE->url, $params); - $button = new single_button($url, get_string('deletestore', 'cache')); - - $PAGE->set_title($title); - $PAGE->set_heading($SITE->fullname); - echo $OUTPUT->header(); - echo $OUTPUT->heading($title); - $confirmation = get_string('deletestoreconfirmation', 'cache', $storeinstancesummaries[$store]['name']); - echo $OUTPUT->confirm($confirmation, $button, $PAGE->url); - echo $OUTPUT->footer(); - exit; - } else { - $writer = cache_config_writer::instance(); - $writer->delete_store_instance($store); - redirect($PAGE->url, get_string('deletestoresuccess', 'cache'), 5); - } - } - break; - case 'editdefinitionmapping' : // Edit definition mappings. - $definition = required_param('definition', PARAM_SAFEPATH); - if (!array_key_exists($definition, $definitionsummaries)) { - throw new cache_exception('Invalid cache definition requested'); - } - $title = get_string('editdefinitionmappings', 'cache', $definition); - $mform = new cache_definition_mappings_form($PAGE->url, array('definition' => $definition)); - if ($mform->is_cancelled()) { - redirect($PAGE->url); - } else if ($data = $mform->get_data()) { - $writer = cache_config_writer::instance(); - $mappings = array(); - foreach ($data->mappings as $mapping) { - if (!empty($mapping)) { - $mappings[] = $mapping; - } - } - $writer->set_definition_mappings($definition, $mappings); - redirect($PAGE->url); - } - break; - case 'editdefinitionsharing' : - $definition = required_param('definition', PARAM_SAFEPATH); - if (!array_key_exists($definition, $definitionsummaries)) { - throw new cache_exception('Invalid cache definition requested'); - } - $title = get_string('editdefinitionsharing', 'cache', $definition); - $sharingoptions = $definitionsummaries[$definition]['sharingoptions']; - $customdata = array('definition' => $definition, 'sharingoptions' => $sharingoptions); - $mform = new cache_definition_sharing_form($PAGE->url, $customdata); - $mform->set_data(array( - 'sharing' => $definitionsummaries[$definition]['selectedsharingoption'], - 'userinputsharingkey' => $definitionsummaries[$definition]['userinputsharingkey'] - )); - if ($mform->is_cancelled()) { - redirect($PAGE->url); - } else if ($data = $mform->get_data()) { - $component = $definitionsummaries[$definition]['component']; - $area = $definitionsummaries[$definition]['area']; - // Purge the stores removing stale data before we alter the sharing option. - cache_helper::purge_stores_used_by_definition($component, $area); - $writer = cache_config_writer::instance(); - $sharing = array_sum(array_keys($data->sharing)); - $userinputsharingkey = $data->userinputsharingkey; - $writer->set_definition_sharing($definition, $sharing, $userinputsharingkey); - redirect($PAGE->url); - } - break; - case 'editmodemappings': // Edit default mode mappings. - $mform = new cache_mode_mappings_form(null, $storeinstancesummaries); - $mform->set_data(array( - 'mode_'.cache_store::MODE_APPLICATION => key($defaultmodestores[cache_store::MODE_APPLICATION]), - 'mode_'.cache_store::MODE_SESSION => key($defaultmodestores[cache_store::MODE_SESSION]), - 'mode_'.cache_store::MODE_REQUEST => key($defaultmodestores[cache_store::MODE_REQUEST]), - )); - if ($mform->is_cancelled()) { - redirect($PAGE->url); - } else if ($data = $mform->get_data()) { - $mappings = array( - cache_store::MODE_APPLICATION => array($data->{'mode_'.cache_store::MODE_APPLICATION}), - cache_store::MODE_SESSION => array($data->{'mode_'.cache_store::MODE_SESSION}), - cache_store::MODE_REQUEST => array($data->{'mode_'.cache_store::MODE_REQUEST}), - ); - $writer = cache_config_writer::instance(); - $writer->set_mode_mappings($mappings); - redirect($PAGE->url); - } - break; - - case 'purgedefinition': // Purge a specific definition. - $id = required_param('definition', PARAM_SAFEPATH); - list($component, $area) = explode('/', $id, 2); - $factory = cache_factory::instance(); - $definition = $factory->create_definition($component, $area); - if ($definition->has_required_identifiers()) { - // We will have to purge the stores used by this definition. - cache_helper::purge_stores_used_by_definition($component, $area); - } else { - // Alrighty we can purge just the data belonging to this definition. - cache_helper::purge_by_definition($component, $area); - } - - $message = get_string('purgexdefinitionsuccess', 'cache', [ - 'name' => $definition->get_name(), - 'component' => $component, - 'area' => $area, - ]); - $purgeagainlink = html_writer::link(new moodle_url('/cache/admin.php', [ - 'action' => 'purgedefinition', 'sesskey' => sesskey(), 'definition' => $id]), - get_string('purgeagain', 'cache')); - redirect($PAGE->url, $message . ' ' . $purgeagainlink, 5); - break; - - case 'purgestore': - case 'purge': // Purge a store cache. - $store = required_param('store', PARAM_TEXT); - cache_helper::purge_store($store); - $message = get_string('purgexstoresuccess', 'cache', ['store' => $store]); - $purgeagainlink = html_writer::link(new moodle_url('/cache/admin.php', [ - 'action' => 'purgestore', 'sesskey' => sesskey(), 'store' => $store]), - get_string('purgeagain', 'cache')); - redirect($PAGE->url, $message . ' ' . $purgeagainlink, 5); - break; - - case 'newlockinstance': - // Adds a new lock instance. - $lock = required_param('lock', PARAM_ALPHANUMEXT); - $mform = cache_administration_helper::get_add_lock_form($lock); - if ($mform->is_cancelled()) { - redirect($PAGE->url); - } else if ($data = $mform->get_data()) { - $factory = cache_factory::instance(); - $config = $factory->create_config_instance(true); - $name = $data->name; - $data = cache_administration_helper::get_lock_configuration_from_data($lock, $data); - $config->add_lock_instance($name, $lock, $data); - redirect($PAGE->url, get_string('addlocksuccess', 'cache', $name), 5); - } - break; - case 'deletelock': - // Deletes a lock instance. - $lock = required_param('lock', PARAM_ALPHANUMEXT); - $confirm = optional_param('confirm', false, PARAM_BOOL); - if (!array_key_exists($lock, $locks)) { - $notifysuccess = false; - $notifications[] = array(get_string('invalidlock', 'cache'), false); - } else if ($locks[$lock]['uses'] > 0) { - $notifysuccess = false; - $notifications[] = array(get_string('deletelockhasuses', 'cache'), false); - } - if ($notifysuccess) { - if (!$confirm) { - $title = get_string('confirmlockdeletion', 'cache'); - $params = array('lock' => $lock, 'confirm' => 1, 'action' => $action, 'sesskey' => sesskey()); - $url = new moodle_url($PAGE->url, $params); - $button = new single_button($url, get_string('deletelock', 'cache')); - - $PAGE->set_title($title); - $PAGE->set_heading($SITE->fullname); - echo $OUTPUT->header(); - echo $OUTPUT->heading($title); - $confirmation = get_string('deletelockconfirmation', 'cache', $lock); - echo $OUTPUT->confirm($confirmation, $button, $PAGE->url); - echo $OUTPUT->footer(); - exit; - } else { - $writer = cache_config_writer::instance(); - $writer->delete_lock_instance($lock); - redirect($PAGE->url, get_string('deletelocksuccess', 'cache'), 5); - } - } - break; - } + $forminfo = $adminhelper->perform_cache_actions($action, $forminfo); } // Add cache store warnings to the list of notifications. // Obviously as these are warnings they are show as failures. -foreach (cache_helper::warnings($storeinstancesummaries) as $warning) { +foreach (cache_helper::warnings(core_cache\administration_helper::get_store_instance_summaries()) as $warning) { $notifications[] = array($warning, false); } +// Decide on display mode based on returned forminfo. +$mform = array_key_exists('form', $forminfo) ? $forminfo['form'] : null; +$title = array_key_exists('title', $forminfo) ? $forminfo['title'] : new lang_string('cacheadmin', 'cache'); + $PAGE->set_title($title); $PAGE->set_heading($SITE->fullname); /* @var core_cache_renderer $renderer */ @@ -311,16 +75,8 @@ echo $renderer->notifications($notifications); if ($mform instanceof moodleform) { $mform->display(); } else { - echo $renderer->store_plugin_summaries($storepluginsummaries); - echo $renderer->store_instance_summariers($storeinstancesummaries, $storepluginsummaries); - echo $renderer->definition_summaries($definitionsummaries, $context); - echo $renderer->lock_summaries($locks); - - $applicationstore = join(', ', $defaultmodestores[cache_store::MODE_APPLICATION]); - $sessionstore = join(', ', $defaultmodestores[cache_store::MODE_SESSION]); - $requeststore = join(', ', $defaultmodestores[cache_store::MODE_REQUEST]); - $editurl = new moodle_url('/cache/admin.php', array('action' => 'editmodemappings', 'sesskey' => sesskey())); - echo $renderer->mode_mappings($applicationstore, $sessionstore, $requeststore, $editurl); + // Handle main page definition in admin helper class. + echo $adminhelper->generate_admin_page($renderer); } echo $renderer->footer(); diff --git a/cache/classes/administration_helper.php b/cache/classes/administration_helper.php new file mode 100644 index 00000000000..551e62c1695 --- /dev/null +++ b/cache/classes/administration_helper.php @@ -0,0 +1,389 @@ +<?php +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see <http://www.gnu.org/licenses/>. + +/** + * Cache administration helper. + * + * This file is part of Moodle's cache API, affectionately called MUC. + * It contains the components that are requried in order to use caching. + * + * @package core + * @category cache + * @author Peter Burnett <peterburnett@catalyst-au.net> + * @copyright 2020 Catalyst IT + * @copyright 2012 Sam Hemelryk + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core_cache; + +defined('MOODLE_INTERNAL') || die(); +use cache_helper, cache_store, cache_config, cache_factory, cache_definition; + +/** + * Administration helper base class. + * + * Defines abstract methods for a subclass to define the admin page. + * + * @package core + * @category cache + * @author Peter Burnett <peterburnett@catalyst-au.net> + * @copyright 2020 Catalyst IT + * @copyright 2012 Sam Hemelryk + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +abstract class administration_helper extends cache_helper { + + /** + * Returns an array containing all of the information about stores a renderer needs. + * @return array + */ + public static function get_store_instance_summaries(): array { + $return = array(); + $default = array(); + $instance = \cache_config::instance(); + $stores = $instance->get_all_stores(); + $locks = $instance->get_locks(); + foreach ($stores as $name => $details) { + $class = $details['class']; + $store = false; + if ($class::are_requirements_met()) { + $store = new $class($details['name'], $details['configuration']); + } + $lock = (isset($details['lock'])) ? $locks[$details['lock']] : $instance->get_default_lock(); + $record = array( + 'name' => $name, + 'plugin' => $details['plugin'], + 'default' => $details['default'], + 'isready' => $store ? $store->is_ready() : false, + 'requirementsmet' => $class::are_requirements_met(), + 'mappings' => 0, + 'lock' => $lock, + 'modes' => array( + cache_store::MODE_APPLICATION => + ($class::get_supported_modes($return) & cache_store::MODE_APPLICATION) == cache_store::MODE_APPLICATION, + cache_store::MODE_SESSION => + ($class::get_supported_modes($return) & cache_store::MODE_SESSION) == cache_store::MODE_SESSION, + cache_store::MODE_REQUEST => + ($class::get_supported_modes($return) & cache_store::MODE_REQUEST) == cache_store::MODE_REQUEST, + ), + 'supports' => array( + 'multipleidentifiers' => $store ? $store->supports_multiple_identifiers() : false, + 'dataguarantee' => $store ? $store->supports_data_guarantee() : false, + 'nativettl' => $store ? $store->supports_native_ttl() : false, + 'nativelocking' => ($store instanceof \cache_is_lockable), + 'keyawareness' => ($store instanceof \cache_is_key_aware), + 'searchable' => ($store instanceof \cache_is_searchable) + ), + 'warnings' => $store ? $store->get_warnings() : array() + ); + if (empty($details['default'])) { + $return[$name] = $record; + } else { + $default[$name] = $record; + } + } + + ksort($return); + ksort($default); + $return = $return + $default; + + foreach ($instance->get_definition_mappings() as $mapping) { + if (!array_key_exists($mapping['store'], $return)) { + continue; + } + $return[$mapping['store']]['mappings']++; + } + + return $return; + } + + /** + * Returns an array of information about plugins, everything a renderer needs. + * + * @return array for each store, an array containing various information about each store. + * See the code below for details + */ + public static function get_store_plugin_summaries(): array { + $return = array(); + $plugins = \core_component::get_plugin_list_with_file('cachestore', 'lib.php', true); + foreach ($plugins as $plugin => $path) { + $class = 'cachestore_'.$plugin; + $return[$plugin] = array( + 'name' => get_string('pluginname', 'cachestore_'.$plugin), + 'requirementsmet' => $class::are_requirements_met(), + 'instances' => 0, + 'modes' => array( + cache_store::MODE_APPLICATION => ($class::get_supported_modes() & cache_store::MODE_APPLICATION), + cache_store::MODE_SESSION => ($class::get_supported_modes() & cache_store::MODE_SESSION), + cache_store::MODE_REQUEST => ($class::get_supported_modes() & cache_store::MODE_REQUEST), + ), + 'supports' => array( + 'multipleidentifiers' => ($class::get_supported_features() & cache_store::SUPPORTS_MULTIPLE_IDENTIFIERS), + 'dataguarantee' => ($class::get_supported_features() & cache_store::SUPPORTS_DATA_GUARANTEE), + 'nativettl' => ($class::get_supported_features() & cache_store::SUPPORTS_NATIVE_TTL), + 'nativelocking' => (in_array('cache_is_lockable', class_implements($class))), + 'keyawareness' => (array_key_exists('cache_is_key_aware', class_implements($class))), + ), + 'canaddinstance' => ($class::can_add_instance() && $class::are_requirements_met()) + ); + } + + $instance = cache_config::instance(); + $stores = $instance->get_all_stores(); + foreach ($stores as $store) { + $plugin = $store['plugin']; + if (array_key_exists($plugin, $return)) { + $return[$plugin]['instances']++; + } + } + + return $return; + } + + /** + * Returns an array about the definitions. All the information a renderer needs. + * + * @return array for each store, an array containing various information about each store. + * See the code below for details + */ + public static function get_definition_summaries(): array { + $factory = cache_factory::instance(); + $config = $factory->create_config_instance(); + $storenames = array(); + foreach ($config->get_all_stores() as $key => $store) { + if (!empty($store['default'])) { + $storenames[$key] = new \lang_string('store_'.$key, 'cache'); + } else { + $storenames[$store['name']] = $store['name']; + } + } + /* @var cache_definition[] $definitions */ + $definitions = []; + $return = []; + foreach ($config->get_definitions() as $key => $definition) { + $definitions[$key] = cache_definition::load($definition['component'].'/'.$definition['area'], $definition); + } + foreach ($definitions as $id => $definition) { + $mappings = array(); + foreach (cache_helper::get_stores_suitable_for_definition($definition) as $store) { + $mappings[] = $storenames[$store->my_name()]; + } + $return[$id] = array( + 'id' => $id, + 'name' => $definition->get_name(), + 'mode' => $definition->get_mode(), + 'component' => $definition->get_component(), + 'area' => $definition->get_area(), + 'mappings' => $mappings, + 'canuselocalstore' => $definition->can_use_localstore(), + 'sharingoptions' => self::get_definition_sharing_options($definition->get_sharing_options(), false), + 'selectedsharingoption' => self::get_definition_sharing_options($definition->get_selected_sharing_option(), true), + 'userinputsharingkey' => $definition->get_user_input_sharing_key() + ); + } + return $return; + } + + /** + * Get the default stores for all modes. + * + * @return array An array containing sub-arrays, one for each mode. + */ + public static function get_default_mode_stores(): array { + global $OUTPUT; + $instance = cache_config::instance(); + $adequatestores = cache_helper::get_stores_suitable_for_mode_default(); + $icon = new \pix_icon('i/warning', new \lang_string('inadequatestoreformapping', 'cache')); + $storenames = array(); + foreach ($instance->get_all_stores() as $key => $store) { + if (!empty($store['default'])) { + $storenames[$key] = new \lang_string('store_'.$key, 'cache'); + } + } + $modemappings = array( + cache_store::MODE_APPLICATION => array(), + cache_store::MODE_SESSION => array(), + cache_store::MODE_REQUEST => array(), + ); + foreach ($instance->get_mode_mappings() as $mapping) { + $mode = $mapping['mode']; + if (!array_key_exists($mode, $modemappings)) { + debugging('Unknown mode in cache store mode mappings', DEBUG_DEVELOPER); + continue; + } + if (array_key_exists($mapping['store'], $storenames)) { + $modemappings[$mode][$mapping['store']] = $storenames[$mapping['store']]; + } else { + $modemappings[$mode][$mapping['store']] = $mapping['store']; + } + if (!array_key_exists($mapping['store'], $adequatestores)) { + $modemappings[$mode][$mapping['store']] = $modemappings[$mode][$mapping['store']].' '.$OUTPUT->render($icon); + } + } + return $modemappings; + } + + /** + * Returns an array summarising the locks available in the system. + * + * @return array array of lock summaries. + */ + public static function get_lock_summaries(): array { + $locks = array(); + $instance = cache_config::instance(); + $stores = $instance->get_all_stores(); + foreach ($instance->get_locks() as $lock) { + $default = !empty($lock['default']); + if ($default) { + $name = new \lang_string($lock['name'], 'cache'); + } else { + $name = $lock['name']; + } + $uses = 0; + foreach ($stores as $store) { + if (!empty($store['lock']) && $store['lock'] === $lock['name']) { + $uses++; + } + } + $lockdata = array( + 'name' => $name, + 'default' => $default, + 'uses' => $uses, + 'type' => get_string('pluginname', $lock['type']) + ); + $locks[$lock['name']] = $lockdata; + } + return $locks; + } + + /** + * Given a sharing option hash this function returns an array of strings that can be used to describe it. + * + * @param int $sharingoption The sharing option hash to get strings for. + * @param bool $isselectedoptions Set to true if the strings will be used to view the selected options. + * @return array An array of lang_string's. + */ + public static function get_definition_sharing_options(int $sharingoption, bool $isselectedoptions = true): array { + $options = array(); + $prefix = ($isselectedoptions) ? 'sharingselected' : 'sharing'; + if ($sharingoption & cache_definition::SHARING_ALL) { + $options[cache_definition::SHARING_ALL] = new \lang_string($prefix.'_all', 'cache'); + } + if ($sharingoption & cache_definition::SHARING_SITEID) { + $options[cache_definition::SHARING_SITEID] = new \lang_string($prefix.'_siteid', 'cache'); + } + if ($sharingoption & cache_definition::SHARING_VERSION) { + $options[cache_definition::SHARING_VERSION] = new \lang_string($prefix.'_version', 'cache'); + } + if ($sharingoption & cache_definition::SHARING_INPUT) { + $options[cache_definition::SHARING_INPUT] = new \lang_string($prefix.'_input', 'cache'); + } + return $options; + } + + /** + * Get an array of stores that are suitable to be used for a given definition. + * + * @param string $component + * @param string $area + * @return array Array containing 3 elements + * 1. An array of currently used stores + * 2. An array of suitable stores + * 3. An array of default stores + */ + public static function get_definition_store_options(string $component, string $area): array { + $factory = cache_factory::instance(); + $definition = $factory->create_definition($component, $area); + $config = cache_config::instance(); + $currentstores = $config->get_stores_for_definition($definition); + $possiblestores = $config->get_stores($definition->get_mode(), $definition->get_requirements_bin()); + + $defaults = array(); + foreach ($currentstores as $key => $store) { + if (!empty($store['default'])) { + $defaults[] = $key; + unset($currentstores[$key]); + } + } + foreach ($possiblestores as $key => $store) { + if ($store['default']) { + unset($possiblestores[$key]); + $possiblestores[$key] = $store; + } + } + return array($currentstores, $possiblestores, $defaults); + } + + /** + * This function must be implemented to display options for store plugins. + * + * @param string $name the name of the store plugin. + * @param array $plugindetails array of store plugin details. + * @return array array of actions. + */ + public function get_store_plugin_actions(string $name, array $plugindetails): array { + return array(); + } + + /** + * This function must be implemented to display options for store instances. + * + * @param string $name the store instance name. + * @param array $storedetails array of store instance details. + * @return array array of actions. + */ + public function get_store_instance_actions(string $name, array $storedetails): array { + return array(); + } + + /** + * This function must be implemented to display options for definition mappings. + * + * @param context $context the context for the definition. + * @param array $definitionsummary the definition summary. + * @return array array of actions. + */ + public function get_definition_actions(\context $context, array $definitionsummary): array { + return array(); + } + + /** + * This function must be implemented to get addable locks. + * + * @return array array of locks that are addable. + */ + public function get_addable_lock_options(): array { + return array(); + } + + /** + * This function must be implemented to perform any page actions by a child class. + * + * @param string $action the action to perform. + * @param array $forminfo empty array to be set by actions. + * @return array array of form info. + */ + public abstract function perform_cache_actions(string $action, array $forminfo): array; + + /** + * This function must be implemented to display the cache admin page. + * + * @param core_cache_renderer $renderer the renderer used to generate the page. + * @return string the HTML for the page. + */ + public abstract function generate_admin_page(\core_cache_renderer $renderer): string; +} diff --git a/cache/classes/factory.php b/cache/classes/factory.php index a974377932d..9791c4715d2 100644 --- a/cache/classes/factory.php +++ b/cache/classes/factory.php @@ -112,7 +112,13 @@ class cache_factory { protected $state = 0; /** - * Returns an instance of the cache_factor method. + * The current cache display helper. + * @var core_cache\local\administration_display_helper + */ + protected static $displayhelper = null; + + /** + * Returns an instance of the cache_factory class. * * @param bool $forcereload If set to true a new cache_factory instance will be created and used. * @return cache_factory @@ -134,6 +140,10 @@ class cache_factory { // The cache stores have been disabled. self::$instance->set_state(self::STATE_STORES_DISABLED); } + + } else if (!empty($CFG->alternative_cache_factory_class)) { + $factoryclass = $CFG->alternative_cache_factory_class; + self::$instance = new $factoryclass(); } else { // We're using the regular factory. self::$instance = new cache_factory(); @@ -636,4 +646,16 @@ class cache_factory { $factory->reset_cache_instances(); $factory->set_state(self::STATE_STORES_DISABLED); } + + /** + * Returns an instance of the current display_helper. + * + * @return core_cache\administration_helper + */ + public static function get_administration_display_helper() : core_cache\administration_helper { + if (is_null(self::$displayhelper)) { + self::$displayhelper = new \core_cache\local\administration_display_helper(); + } + return self::$displayhelper; + } } diff --git a/cache/classes/helper.php b/cache/classes/helper.php index dc4821b2558..50643fe7f80 100644 --- a/cache/classes/helper.php +++ b/cache/classes/helper.php @@ -829,7 +829,7 @@ class cache_helper { global $CFG; if ($stores === null) { require_once($CFG->dirroot.'/cache/locallib.php'); - $stores = cache_administration_helper::get_store_instance_summaries(); + $stores = core_cache\administration_helper::get_store_instance_summaries(); } $warnings = array(); foreach ($stores as $store) { diff --git a/cache/classes/local/administration_display_helper.php b/cache/classes/local/administration_display_helper.php new file mode 100644 index 00000000000..1e2aff7c263 --- /dev/null +++ b/cache/classes/local/administration_display_helper.php @@ -0,0 +1,795 @@ +<?php +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see <http://www.gnu.org/licenses/>. + +/** + * Cache display administration helper. + * + * This file is part of Moodle's cache API, affectionately called MUC. + * It contains the components that are requried in order to use caching. + * + * @package core + * @category cache + * @author Peter Burnett <peterburnett@catalyst-au.net> + * @copyright 2020 Catalyst IT + * @copyright 2012 Sam Hemelryk + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace core_cache\local; + +defined('MOODLE_INTERNAL') || die(); +use cache_store, cache_factory, cache_config_writer, cache_helper, core_cache_renderer; + +/** + * A cache helper for administration tasks + * + * @package core + * @category cache + * @copyright 2020 Peter Burnett <peterburnett@catalyst-au.net> + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class administration_display_helper extends \core_cache\administration_helper { + + /** + * Please do not call constructor directly. Use cache_factory::get_administration_display_helper() instead. + */ + public function __construct() { + // Nothing to do here. + } + + /** + * Returns all of the actions that can be performed on a definition. + * + * @param context $context the system context. + * @param array $definitionsummary information about this cache, from the array returned by + * core_cache\administration_helper::get_definition_summaries(). Currently only 'sharingoptions' + * element is used. + * @return array of actions. Each action is an action_url. + */ + public function get_definition_actions(\context $context, array $definitionsummary): array { + global $OUTPUT; + if (has_capability('moodle/site:config', $context)) { + $actions = array(); + // Edit mappings. + $actions[] = $OUTPUT->action_link( + new \moodle_url('/cache/admin.php', array('action' => 'editdefinitionmapping', + 'definition' => $definitionsummary['id'], 'sesskey' => sesskey())), + get_string('editmappings', 'cache') + ); + // Edit sharing. + if (count($definitionsummary['sharingoptions']) > 1) { + $actions[] = $OUTPUT->action_link( + new \moodle_url('/cache/admin.php', array('action' => 'editdefinitionsharing', + 'definition' => $definitionsummary['id'], 'sesskey' => sesskey())), + get_string('editsharing', 'cache') + ); + } + // Purge. + $actions[] = $OUTPUT->action_link( + new \moodle_url('/cache/admin.php', array('action' => 'purgedefinition', + 'definition' => $definitionsummary['id'], 'sesskey' => sesskey())), + get_string('purge', 'cache') + ); + return $actions; + } + return array(); + } + + /** + * Returns all of the actions that can be performed on a store. + * + * @param string $name The name of the store + * @param array $storedetails information about this store, from the array returned by + * core_cache\administration_helper::get_store_instance_summaries(). + * @return array of actions. Each action is an action_url. + */ + public function get_store_instance_actions(string $name, array $storedetails): array { + global $OUTPUT; + $actions = array(); + if (has_capability('moodle/site:config', \context_system::instance())) { + $baseurl = new \moodle_url('/cache/admin.php', array('store' => $name, 'sesskey' => sesskey())); + if (empty($storedetails['default'])) { + $actions[] = $OUTPUT->action_link( + new \moodle_url($baseurl, array('action' => 'editstore', 'plugin' => $storedetails['plugin'])), + get_string('editstore', 'cache') + ); + + $actions[] = $OUTPUT->action_link( + new \moodle_url($baseurl, array('action' => 'deletestore')), + get_string('deletestore', 'cache') + ); + } + + $actions[] = $OUTPUT->action_link( + new \moodle_url($baseurl, array('action' => 'purgestore')), + get_string('purge', 'cache') + ); + } + return $actions; + } + + /** + * Returns all of the actions that can be performed on a plugin. + * + * @param string $name The name of the plugin + * @param array $plugindetails information about this store, from the array returned by + * core_cache\administration_helper::get_store_plugin_summaries(). + * @return array of actions. Each action is an action_url. + */ + public function get_store_plugin_actions(string $name, array $plugindetails): array { + global $OUTPUT; + $actions = array(); + if (has_capability('moodle/site:config', \context_system::instance())) { + if (!empty($plugindetails['canaddinstance'])) { + $url = new \moodle_url('/cache/admin.php', + array('action' => 'addstore', 'plugin' => $name, 'sesskey' => sesskey())); + $actions[] = $OUTPUT->action_link( + $url, + get_string('addinstance', 'cache') + ); + } + } + return $actions; + } + + /** + * Returns a form that can be used to add a store instance. + * + * @param string $plugin The plugin to add an instance of + * @return cachestore_addinstance_form + * @throws coding_exception + */ + public function get_add_store_form(string $plugin): \cachestore_addinstance_form { + global $CFG; // Needed for includes. + $plugins = \core_component::get_plugin_list('cachestore'); + if (!array_key_exists($plugin, $plugins)) { + throw new \coding_exception('Invalid cache plugin used when trying to create an edit form.'); + } + $plugindir = $plugins[$plugin]; + $class = 'cachestore_addinstance_form'; + if (file_exists($plugindir.'/addinstanceform.php')) { + require_once($plugindir.'/addinstanceform.php'); + if (class_exists('cachestore_'.$plugin.'_addinstance_form')) { + $class = 'cachestore_'.$plugin.'_addinstance_form'; + if (!array_key_exists('cachestore_addinstance_form', class_parents($class))) { + throw new \coding_exception('Cache plugin add instance forms must extend cachestore_addinstance_form'); + } + } + } + + $locks = $this->get_possible_locks_for_stores($plugindir, $plugin); + + $url = new \moodle_url('/cache/admin.php', array('action' => 'addstore')); + return new $class($url, array('plugin' => $plugin, 'store' => null, 'locks' => $locks)); + } + + /** + * Returns a form that can be used to edit a store instance. + * + * @param string $plugin + * @param string $store + * @return cachestore_addinstance_form + * @throws coding_exception + */ + public function get_edit_store_form(string $plugin, string $store): \cachestore_addinstance_form { + global $CFG; // Needed for includes. + $plugins = \core_component::get_plugin_list('cachestore'); + if (!array_key_exists($plugin, $plugins)) { + throw new \coding_exception('Invalid cache plugin used when trying to create an edit form.'); + } + $factory = \cache_factory::instance(); + $config = $factory->create_config_instance(); + $stores = $config->get_all_stores(); + if (!array_key_exists($store, $stores)) { + throw new \coding_exception('Invalid store name given when trying to create an edit form.'); + } + $plugindir = $plugins[$plugin]; + $class = 'cachestore_addinstance_form'; + if (file_exists($plugindir.'/addinstanceform.php')) { + require_once($plugindir.'/addinstanceform.php'); + if (class_exists('cachestore_'.$plugin.'_addinstance_form')) { + $class = 'cachestore_'.$plugin.'_addinstance_form'; + if (!array_key_exists('cachestore_addinstance_form', class_parents($class))) { + throw new \coding_exception('Cache plugin add instance forms must extend cachestore_addinstance_form'); + } + } + } + + $locks = $this->get_possible_locks_for_stores($plugindir, $plugin); + + $url = new \moodle_url('/cache/admin.php', array('action' => 'editstore', 'plugin' => $plugin, 'store' => $store)); + $editform = new $class($url, array('plugin' => $plugin, 'store' => $store, 'locks' => $locks)); + if (isset($stores[$store]['lock'])) { + $editform->set_data(array('lock' => $stores[$store]['lock'])); + } + // See if the cachestore is going to want to load data for the form. + // If it has a customised add instance form then it is going to want to. + $storeclass = 'cachestore_'.$plugin; + $storedata = $stores[$store]; + if (array_key_exists('configuration', $storedata) && + array_key_exists('cache_is_configurable', class_implements($storeclass))) { + $storeclass::config_set_edit_form_data($editform, $storedata['configuration']); + } + return $editform; + } + + /** + * Returns an array of suitable lock instances for use with this plugin, or false if the plugin handles locking itself. + * + * @param string $plugindir + * @param string $plugin + * @return array|false + */ + protected function get_possible_locks_for_stores(string $plugindir, string $plugin) { + global $CFG; // Needed for includes. + $supportsnativelocking = false; + if (file_exists($plugindir.'/lib.php')) { + require_once($plugindir.'/lib.php'); + $pluginclass = 'cachestore_'.$plugin; + if (class_exists($pluginclass)) { + $supportsnativelocking = array_key_exists('cache_is_lockable', class_implements($pluginclass)); + } + } + + if (!$supportsnativelocking) { + $config = \cache_config::instance(); + $locks = array(); + foreach ($config->get_locks() as $lock => $conf) { + if (!empty($conf['default'])) { + $name = get_string($lock, 'cache'); + } else { + $name = $lock; + } + $locks[$lock] = $name; + } + } else { + $locks = false; + } + + return $locks; + } + + /** + * Processes the results of the add/edit instance form data for a plugin returning an array of config information suitable to + * store in configuration. + * + * @param stdClass $data The mform data. + * @return array + * @throws coding_exception + */ + public function get_store_configuration_from_data(\stdClass $data): array { + global $CFG; + $file = $CFG->dirroot.'/cache/stores/'.$data->plugin.'/lib.php'; + if (!file_exists($file)) { + throw new \coding_exception('Invalid cache plugin provided. '.$file); + } + require_once($file); + $class = 'cachestore_'.$data->plugin; + if (!class_exists($class)) { + throw new \coding_exception('Invalid cache plugin provided.'); + } + if (array_key_exists('cache_is_configurable', class_implements($class))) { + return $class::config_get_configuration_array($data); + } + return array(); + } + + /** + * Returns an array of lock plugins for which we can add an instance. + * + * Suitable for use within an mform select element. + * + * @return array + */ + public function get_addable_lock_options(): array { + $plugins = \core_component::get_plugin_list_with_class('cachelock', '', 'lib.php'); + $options = array(); + $len = strlen('cachelock_'); + foreach ($plugins as $plugin => $class) { + $method = "$class::can_add_instance"; + if (is_callable($method) && !call_user_func($method)) { + // Can't add an instance of this plugin. + continue; + } + $options[substr($plugin, $len)] = get_string('pluginname', $plugin); + } + return $options; + } + + /** + * Gets the form to use when adding a lock instance. + * + * @param string $plugin + * @param array $lockplugin + * @return cache_lock_form + * @throws coding_exception + */ + public function get_add_lock_form(string $plugin, array $lockplugin = null): \cache_lock_form { + global $CFG; // Needed for includes. + $plugins = \core_component::get_plugin_list('cachelock'); + if (!array_key_exists($plugin, $plugins)) { + throw new \coding_exception('Invalid cache lock plugin requested when trying to create a form.'); + } + $plugindir = $plugins[$plugin]; + $class = 'cache_lock_form'; + if (file_exists($plugindir.'/addinstanceform.php') && in_array('cache_is_configurable', class_implements($class))) { + require_once($plugindir.'/addinstanceform.php'); + if (class_exists('cachelock_'.$plugin.'_addinstance_form')) { + $class = 'cachelock_'.$plugin.'_addinstance_form'; + if (!array_key_exists('cache_lock_form', class_parents($class))) { + throw new \coding_exception('Cache lock plugin add instance forms must extend cache_lock_form'); + } + } + } + return new $class(null, array('lock' => $plugin)); + } + + /** + * Gets configuration data from a new lock instance form. + * + * @param string $plugin + * @param stdClass $data + * @return array + * @throws coding_exception + */ + public function get_lock_configuration_from_data(string $plugin, \stdClass $data): array { + global $CFG; + $file = $CFG->dirroot.'/cache/locks/'.$plugin.'/lib.php'; + if (!file_exists($file)) { + throw new \coding_exception('Invalid cache plugin provided. '.$file); + } + require_once($file); + $class = 'cachelock_'.$plugin; + if (!class_exists($class)) { + throw new \coding_exception('Invalid cache plugin provided.'); + } + if (array_key_exists('cache_is_configurable', class_implements($class))) { + return $class::config_get_configuration_array($data); + } + return array(); + } + + /** + * Handles the page actions, based on the parameter. + * + * @param string $action the action to handle. + * @param array $forminfo an empty array to be overridden and set. + * @return array the empty or overridden forminfo array. + */ + public function perform_cache_actions(string $action, array $forminfo): array { + switch ($action) { + case 'rescandefinitions' : // Rescan definitions. + $this->action_rescan_definition(); + break; + + case 'addstore' : // Add the requested store. + $forminfo = $this->action_addstore(); + break; + + case 'editstore' : // Edit the requested store. + $forminfo = $this->action_editstore(); + break; + + case 'deletestore' : // Delete a given store. + $this->action_deletestore($action); + break; + + case 'editdefinitionmapping' : // Edit definition mappings. + $forminfo = $this->action_editdefinitionmapping(); + break; + + case 'editdefinitionsharing' : // Edit definition sharing. + $forminfo = $this->action_editdefinitionsharing(); + break; + + case 'editmodemappings': // Edit default mode mappings. + $forminfo = $this->action_editmodemappings(); + break; + + case 'purgedefinition': // Purge a specific definition. + $this->action_purgedefinition(); + break; + + case 'purgestore': + case 'purge': // Purge a store cache. + $this->action_purge(); + break; + + case 'newlockinstance': + $forminfo = $this->action_newlockinstance(); + break; + + case 'deletelock': + // Deletes a lock instance. + $this->action_deletelock($action); + break; + } + + return $forminfo; + } + + /** + * Performs the rescan definition action. + * + * @return void + */ + public function action_rescan_definition() { + global $PAGE; + + \cache_config_writer::update_definitions(); + redirect($PAGE->url); + } + + /** + * Performs the add store action. + * + * @return array an array of the form to display to the user, and the page title. + */ + public function action_addstore() : array { + global $PAGE; + $storepluginsummaries = $this->get_store_plugin_summaries(); + + $plugin = required_param('plugin', PARAM_PLUGIN); + if (!$storepluginsummaries[$plugin]['canaddinstance']) { + print_error('ex_unmetstorerequirements', 'cache'); + } + $mform = $this->get_add_store_form($plugin); + $title = get_string('addstore', 'cache', $storepluginsummaries[$plugin]['name']); + if ($mform->is_cancelled()) { + redirect($PAGE->url); + } else if ($data = $mform->get_data()) { + $config = $this->get_store_configuration_from_data($data); + $writer = \cache_config_writer::instance(); + unset($config['lock']); + foreach ($writer->get_locks() as $lock => $lockconfig) { + if ($lock == $data->lock) { + $config['lock'] = $data->lock; + } + } + $writer->add_store_instance($data->name, $data->plugin, $config); + redirect($PAGE->url, get_string('addstoresuccess', 'cache', $storepluginsummaries[$plugin]['name']), 5); + } + + return array('form' => $mform, 'title' => $title); + } + + /** + * Performs the edit store action. + * + * @return array an array of the form to display, and the page title. + */ + public function action_editstore(): array { + global $PAGE; + $storepluginsummaries = $this->get_store_plugin_summaries(); + + $plugin = required_param('plugin', PARAM_PLUGIN); + $store = required_param('store', PARAM_TEXT); + $mform = $this->get_edit_store_form($plugin, $store); + $title = get_string('addstore', 'cache', $storepluginsummaries[$plugin]['name']); + if ($mform->is_cancelled()) { + redirect($PAGE->url); + } else if ($data = $mform->get_data()) { + $config = $this->get_store_configuration_from_data($data); + $writer = \cache_config_writer::instance(); + + unset($config['lock']); + foreach ($writer->get_locks() as $lock => $lockconfig) { + if ($lock == $data->lock) { + $config['lock'] = $data->lock; + } + } + $writer->edit_store_instance($data->name, $data->plugin, $config); + redirect($PAGE->url, get_string('editstoresuccess', 'cache', $storepluginsummaries[$plugin]['name']), 5); + } + + return array('form' => $mform, 'title' => $title); + } + + /** + * Performs the deletestore action. + * + * @param string $action the action calling to this function. + * @return void + */ + public function action_deletestore(string $action) { + global $OUTPUT, $PAGE, $SITE; + $notifysuccess = true; + $storeinstancesummaries = $this->get_store_instance_summaries(); + + $store = required_param('store', PARAM_TEXT); + $confirm = optional_param('confirm', false, PARAM_BOOL); + + if (!array_key_exists($store, $storeinstancesummaries)) { + $notifysuccess = false; + $notifications[] = array(get_string('invalidstore', 'cache'), false); + } else if ($storeinstancesummaries[$store]['mappings'] > 0) { + $notifysuccess = false; + $notifications[] = array(get_string('deletestorehasmappings', 'cache'), false); + } + + if ($notifysuccess) { + if (!$confirm) { + $title = get_string('confirmstoredeletion', 'cache'); + $params = array('store' => $store, 'confirm' => 1, 'action' => $action, 'sesskey' => sesskey()); + $url = new \moodle_url($PAGE->url, $params); + $button = new \single_button($url, get_string('deletestore', 'cache')); + + $PAGE->set_title($title); + $PAGE->set_heading($SITE->fullname); + echo $OUTPUT->header(); + echo $OUTPUT->heading($title); + $confirmation = get_string('deletestoreconfirmation', 'cache', $storeinstancesummaries[$store]['name']); + echo $OUTPUT->confirm($confirmation, $button, $PAGE->url); + echo $OUTPUT->footer(); + exit; + } else { + $writer = \cache_config_writer::instance(); + $writer->delete_store_instance($store); + redirect($PAGE->url, get_string('deletestoresuccess', 'cache'), 5); + } + } + } + + /** + * Performs the edit definition mapping action. + * + * @return array an array of the form to display, and the page title. + * @throws cache_exception + */ + public function action_editdefinitionmapping(): array { + global $PAGE; + $definitionsummaries = $this->get_definition_summaries(); + + $definition = required_param('definition', PARAM_SAFEPATH); + if (!array_key_exists($definition, $definitionsummaries)) { + throw new \cache_exception('Invalid cache definition requested'); + } + $title = get_string('editdefinitionmappings', 'cache', $definition); + $mform = new \cache_definition_mappings_form($PAGE->url, array('definition' => $definition)); + if ($mform->is_cancelled()) { + redirect($PAGE->url); + } else if ($data = $mform->get_data()) { + $writer = \cache_config_writer::instance(); + $mappings = array(); + foreach ($data->mappings as $mapping) { + if (!empty($mapping)) { + $mappings[] = $mapping; + } + } + $writer->set_definition_mappings($definition, $mappings); + redirect($PAGE->url); + } + + return array('form' => $mform, 'title' => $title); + } + + /** + * Performs the edit definition sharing action. + * + * @return array an array of the edit definition sharing form, and the page title. + */ + public function action_editdefinitionsharing(): array { + global $PAGE; + $definitionsummaries = $this->get_definition_summaries(); + + $definition = required_param('definition', PARAM_SAFEPATH); + if (!array_key_exists($definition, $definitionsummaries)) { + throw new \cache_exception('Invalid cache definition requested'); + } + $title = get_string('editdefinitionsharing', 'cache', $definition); + $sharingoptions = $definitionsummaries[$definition]['sharingoptions']; + $customdata = array('definition' => $definition, 'sharingoptions' => $sharingoptions); + $mform = new \cache_definition_sharing_form($PAGE->url, $customdata); + $mform->set_data(array( + 'sharing' => $definitionsummaries[$definition]['selectedsharingoption'], + 'userinputsharingkey' => $definitionsummaries[$definition]['userinputsharingkey'] + )); + if ($mform->is_cancelled()) { + redirect($PAGE->url); + } else if ($data = $mform->get_data()) { + $component = $definitionsummaries[$definition]['component']; + $area = $definitionsummaries[$definition]['area']; + // Purge the stores removing stale data before we alter the sharing option. + \cache_helper::purge_stores_used_by_definition($component, $area); + $writer = \cache_config_writer::instance(); + $sharing = array_sum(array_keys($data->sharing)); + $userinputsharingkey = $data->userinputsharingkey; + $writer->set_definition_sharing($definition, $sharing, $userinputsharingkey); + redirect($PAGE->url); + } + + return array('form' => $mform, 'title' => $title); + } + + /** + * Performs the edit mode mappings action. + * + * @return array an array of the edit mode mappings form. + */ + public function action_editmodemappings(): array { + global $PAGE; + $storeinstancesummaries = $this->get_store_instance_summaries(); + $defaultmodestores = $this->get_default_mode_stores(); + + $mform = new \cache_mode_mappings_form(null, $storeinstancesummaries); + $mform->set_data(array( + 'mode_'.cache_store::MODE_APPLICATION => key($defaultmodestores[cache_store::MODE_APPLICATION]), + 'mode_'.cache_store::MODE_SESSION => key($defaultmodestores[cache_store::MODE_SESSION]), + 'mode_'.cache_store::MODE_REQUEST => key($defaultmodestores[cache_store::MODE_REQUEST]), + )); + if ($mform->is_cancelled()) { + redirect($PAGE->url); + } else if ($data = $mform->get_data()) { + $mappings = array( + cache_store::MODE_APPLICATION => array($data->{'mode_'.cache_store::MODE_APPLICATION}), + cache_store::MODE_SESSION => array($data->{'mode_'.cache_store::MODE_SESSION}), + cache_store::MODE_REQUEST => array($data->{'mode_'.cache_store::MODE_REQUEST}), + ); + $writer = cache_config_writer::instance(); + $writer->set_mode_mappings($mappings); + redirect($PAGE->url); + } + + return array('form' => $mform); + } + + /** + * Performs the purge definition action. + * + * @return void + */ + public function action_purgedefinition() { + global $PAGE; + + $id = required_param('definition', PARAM_SAFEPATH); + list($component, $area) = explode('/', $id, 2); + $factory = cache_factory::instance(); + $definition = $factory->create_definition($component, $area); + if ($definition->has_required_identifiers()) { + // We will have to purge the stores used by this definition. + cache_helper::purge_stores_used_by_definition($component, $area); + } else { + // Alrighty we can purge just the data belonging to this definition. + cache_helper::purge_by_definition($component, $area); + } + + $message = get_string('purgexdefinitionsuccess', 'cache', [ + 'name' => $definition->get_name(), + 'component' => $component, + 'area' => $area, + ]); + $purgeagainlink = \html_writer::link(new \moodle_url('/cache/admin.php', [ + 'action' => 'purgedefinition', 'sesskey' => sesskey(), 'definition' => $id]), + get_string('purgeagain', 'cache')); + redirect($PAGE->url, $message . ' ' . $purgeagainlink, 5); + } + + /** + * Performs the purge action. + * + * @return void + */ + public function action_purge() { + global $PAGE; + + $store = required_param('store', PARAM_TEXT); + cache_helper::purge_store($store); + $message = get_string('purgexstoresuccess', 'cache', ['store' => $store]); + $purgeagainlink = \html_writer::link(new \moodle_url('/cache/admin.php', [ + 'action' => 'purgestore', 'sesskey' => sesskey(), 'store' => $store]), + get_string('purgeagain', 'cache')); + redirect($PAGE->url, $message . ' ' . $purgeagainlink, 5); + } + + /** + * Performs the new lock instance action. + * + * @return array An array containing the new lock instance form. + */ + public function action_newlockinstance(): array { + global $PAGE; + + // Adds a new lock instance. + $lock = required_param('lock', PARAM_ALPHANUMEXT); + $mform = $this->get_add_lock_form($lock); + if ($mform->is_cancelled()) { + redirect($PAGE->url); + } else if ($data = $mform->get_data()) { + $factory = cache_factory::instance(); + $config = $factory->create_config_instance(true); + $name = $data->name; + $data = $this->get_lock_configuration_from_data($lock, $data); + $config->add_lock_instance($name, $lock, $data); + redirect($PAGE->url, get_string('addlocksuccess', 'cache', $name), 5); + } + + return array('form' => $mform); + } + + /** + * Performs the delete lock action. + * + * @param string $action the action calling this function. + * @return void + */ + public function action_deletelock(string $action) { + global $OUTPUT, $PAGE, $SITE; + $notifysuccess = true; + $locks = $this->get_lock_summaries(); + + $lock = required_param('lock', PARAM_ALPHANUMEXT); + $confirm = optional_param('confirm', false, PARAM_BOOL); + if (!array_key_exists($lock, $locks)) { + $notifysuccess = false; + $notifications[] = array(get_string('invalidlock', 'cache'), false); + } else if ($locks[$lock]['uses'] > 0) { + $notifysuccess = false; + $notifications[] = array(get_string('deletelockhasuses', 'cache'), false); + } + if ($notifysuccess) { + if (!$confirm) { + $title = get_string('confirmlockdeletion', 'cache'); + $params = array('lock' => $lock, 'confirm' => 1, 'action' => $action, 'sesskey' => sesskey()); + $url = new \moodle_url($PAGE->url, $params); + $button = new \single_button($url, get_string('deletelock', 'cache')); + + $PAGE->set_title($title); + $PAGE->set_heading($SITE->fullname); + echo $OUTPUT->header(); + echo $OUTPUT->heading($title); + $confirmation = get_string('deletelockconfirmation', 'cache', $lock); + echo $OUTPUT->confirm($confirmation, $button, $PAGE->url); + echo $OUTPUT->footer(); + exit; + } else { + $writer = cache_config_writer::instance(); + $writer->delete_lock_instance($lock); + redirect($PAGE->url, get_string('deletelocksuccess', 'cache'), 5); + } + } + } + + /** + * Outputs the main admin page by generating it through the renderer. + * + * @param core_cache_renderer $renderer the renderer to use to generate the page. + * @return string the HTML for the admin page. + */ + public function generate_admin_page(core_cache_renderer $renderer): string { + $context = \context_system::instance(); + $html = ''; + + $storepluginsummaries = $this->get_store_plugin_summaries(); + $storeinstancesummaries = $this->get_store_instance_summaries(); + $definitionsummaries = $this->get_definition_summaries(); + $defaultmodestores = $this->get_default_mode_stores(); + $locks = $this->get_lock_summaries(); + + $html .= $renderer->store_plugin_summaries($storepluginsummaries); + $html .= $renderer->store_instance_summariers($storeinstancesummaries, $storepluginsummaries); + $html .= $renderer->definition_summaries($definitionsummaries, $context); + $html .= $renderer->lock_summaries($locks); + $html .= $renderer->additional_lock_actions(); + + $applicationstore = join(', ', $defaultmodestores[cache_store::MODE_APPLICATION]); + $sessionstore = join(', ', $defaultmodestores[cache_store::MODE_SESSION]); + $requeststore = join(', ', $defaultmodestores[cache_store::MODE_REQUEST]); + $editurl = new \moodle_url('/cache/admin.php', array('action' => 'editmodemappings', 'sesskey' => sesskey())); + $html .= $renderer->mode_mappings($applicationstore, $sessionstore, $requeststore, $editurl); + + return $html; + } +} \ No newline at end of file diff --git a/cache/forms.php b/cache/forms.php index e482702c424..16435705e23 100644 --- a/cache/forms.php +++ b/cache/forms.php @@ -97,7 +97,7 @@ class cachestore_addinstance_form extends moodleform { if (!preg_match('#^[a-zA-Z0-9\-_ ]+$#', $data['name'])) { $errors['name'] = get_string('storenameinvalid', 'cache'); } else if (empty($this->_customdata['store'])) { - $stores = cache_administration_helper::get_store_instance_summaries(); + $stores = core_cache\administration_helper::get_store_instance_summaries(); if (array_key_exists($data['name'], $stores)) { $errors['name'] = get_string('storenamealreadyused', 'cache'); } @@ -139,9 +139,9 @@ class cache_definition_mappings_form extends moodleform { list($component, $area) = explode('/', $definition, 2); list($currentstores, $storeoptions, $defaults) = - cache_administration_helper::get_definition_store_options($component, $area); + core_cache\administration_helper::get_definition_store_options($component, $area); - $storedata = cache_administration_helper::get_definition_summaries(); + $storedata = core_cache\administration_helper::get_definition_summaries(); if ($storedata[$definition]['mode'] != cache_store::MODE_REQUEST) { if (isset($storedata[$definition]['canuselocalstore']) && $storedata[$definition]['canuselocalstore']) { $form->addElement('html', $OUTPUT->notification(get_string('localstorenotification', 'cache'), 'notifymessage')); @@ -247,7 +247,7 @@ class cache_definition_sharing_form extends moodleform { public function set_data($data) { if (!isset($data['sharing'])) { // Set the default value here. mforms doesn't handle defaults very nicely. - $data['sharing'] = cache_administration_helper::get_definition_sharing_options(cache_definition::SHARING_DEFAULT); + $data['sharing'] = core_cache\administration_helper::get_definition_sharing_options(cache_definition::SHARING_DEFAULT); } parent::set_data($data); } diff --git a/cache/locallib.php b/cache/locallib.php index 62ead7473f4..b8509cedc27 100644 --- a/cache/locallib.php +++ b/cache/locallib.php @@ -659,597 +659,4 @@ class cache_config_writer extends cache_config { } $this->config_save(); } - -} - -/** - * A cache helper for administration tasks - * - * @package core - * @category cache - * @copyright 2012 Sam Hemelryk - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -abstract class cache_administration_helper extends cache_helper { - - /** - * Returns an array containing all of the information about stores a renderer needs. - * @return array - */ - public static function get_store_instance_summaries() { - $return = array(); - $default = array(); - $instance = cache_config::instance(); - $stores = $instance->get_all_stores(); - $locks = $instance->get_locks(); - foreach ($stores as $name => $details) { - $class = $details['class']; - $store = false; - if ($class::are_requirements_met()) { - $store = new $class($details['name'], $details['configuration']); - } - $lock = (isset($details['lock'])) ? $locks[$details['lock']] : $instance->get_default_lock(); - $record = array( - 'name' => $name, - 'plugin' => $details['plugin'], - 'default' => $details['default'], - 'isready' => $store ? $store->is_ready() : false, - 'requirementsmet' => $class::are_requirements_met(), - 'mappings' => 0, - 'lock' => $lock, - 'modes' => array( - cache_store::MODE_APPLICATION => - ($class::get_supported_modes($return) & cache_store::MODE_APPLICATION) == cache_store::MODE_APPLICATION, - cache_store::MODE_SESSION => - ($class::get_supported_modes($return) & cache_store::MODE_SESSION) == cache_store::MODE_SESSION, - cache_store::MODE_REQUEST => - ($class::get_supported_modes($return) & cache_store::MODE_REQUEST) == cache_store::MODE_REQUEST, - ), - 'supports' => array( - 'multipleidentifiers' => $store ? $store->supports_multiple_identifiers() : false, - 'dataguarantee' => $store ? $store->supports_data_guarantee() : false, - 'nativettl' => $store ? $store->supports_native_ttl() : false, - 'nativelocking' => ($store instanceof cache_is_lockable), - 'keyawareness' => ($store instanceof cache_is_key_aware), - 'searchable' => ($store instanceof cache_is_searchable) - ), - 'warnings' => $store ? $store->get_warnings() : array() - ); - if (empty($details['default'])) { - $return[$name] = $record; - } else { - $default[$name] = $record; - } - } - - ksort($return); - ksort($default); - $return = $return + $default; - - foreach ($instance->get_definition_mappings() as $mapping) { - if (!array_key_exists($mapping['store'], $return)) { - continue; - } - $return[$mapping['store']]['mappings']++; - } - - return $return; - } - - /** - * Returns an array of information about plugins, everything a renderer needs. - * - * @return array for each store, an array containing various information about each store. - * See the code below for details - */ - public static function get_store_plugin_summaries() { - $return = array(); - $plugins = core_component::get_plugin_list_with_file('cachestore', 'lib.php', true); - foreach ($plugins as $plugin => $path) { - $class = 'cachestore_'.$plugin; - $return[$plugin] = array( - 'name' => get_string('pluginname', 'cachestore_'.$plugin), - 'requirementsmet' => $class::are_requirements_met(), - 'instances' => 0, - 'modes' => array( - cache_store::MODE_APPLICATION => ($class::get_supported_modes() & cache_store::MODE_APPLICATION), - cache_store::MODE_SESSION => ($class::get_supported_modes() & cache_store::MODE_SESSION), - cache_store::MODE_REQUEST => ($class::get_supported_modes() & cache_store::MODE_REQUEST), - ), - 'supports' => array( - 'multipleidentifiers' => ($class::get_supported_features() & cache_store::SUPPORTS_MULTIPLE_IDENTIFIERS), - 'dataguarantee' => ($class::get_supported_features() & cache_store::SUPPORTS_DATA_GUARANTEE), - 'nativettl' => ($class::get_supported_features() & cache_store::SUPPORTS_NATIVE_TTL), - 'nativelocking' => (in_array('cache_is_lockable', class_implements($class))), - 'keyawareness' => (array_key_exists('cache_is_key_aware', class_implements($class))), - ), - 'canaddinstance' => ($class::can_add_instance() && $class::are_requirements_met()) - ); - } - - $instance = cache_config::instance(); - $stores = $instance->get_all_stores(); - foreach ($stores as $store) { - $plugin = $store['plugin']; - if (array_key_exists($plugin, $return)) { - $return[$plugin]['instances']++; - } - } - - return $return; - } - - /** - * Returns an array about the definitions. All the information a renderer needs. - * - * @return array for each store, an array containing various information about each store. - * See the code below for details - */ - public static function get_definition_summaries() { - $factory = cache_factory::instance(); - $config = $factory->create_config_instance(); - $storenames = array(); - foreach ($config->get_all_stores() as $key => $store) { - if (!empty($store['default'])) { - $storenames[$key] = new lang_string('store_'.$key, 'cache'); - } else { - $storenames[$store['name']] = $store['name']; - } - } - /* @var cache_definition[] $definitions */ - $definitions = array(); - foreach ($config->get_definitions() as $key => $definition) { - $definitions[$key] = cache_definition::load($definition['component'].'/'.$definition['area'], $definition); - } - foreach ($definitions as $id => $definition) { - $mappings = array(); - foreach (cache_helper::get_stores_suitable_for_definition($definition) as $store) { - $mappings[] = $storenames[$store->my_name()]; - } - $return[$id] = array( - 'id' => $id, - 'name' => $definition->get_name(), - 'mode' => $definition->get_mode(), - 'component' => $definition->get_component(), - 'area' => $definition->get_area(), - 'mappings' => $mappings, - 'canuselocalstore' => $definition->can_use_localstore(), - 'sharingoptions' => self::get_definition_sharing_options($definition->get_sharing_options(), false), - 'selectedsharingoption' => self::get_definition_sharing_options($definition->get_selected_sharing_option(), true), - 'userinputsharingkey' => $definition->get_user_input_sharing_key() - ); - } - return $return; - } - - /** - * Given a sharing option hash this function returns an array of strings that can be used to describe it. - * - * @param int $sharingoption The sharing option hash to get strings for. - * @param bool $isselectedoptions Set to true if the strings will be used to view the selected options. - * @return array An array of lang_string's. - */ - public static function get_definition_sharing_options($sharingoption, $isselectedoptions = true) { - $options = array(); - $prefix = ($isselectedoptions) ? 'sharingselected' : 'sharing'; - if ($sharingoption & cache_definition::SHARING_ALL) { - $options[cache_definition::SHARING_ALL] = new lang_string($prefix.'_all', 'cache'); - } - if ($sharingoption & cache_definition::SHARING_SITEID) { - $options[cache_definition::SHARING_SITEID] = new lang_string($prefix.'_siteid', 'cache'); - } - if ($sharingoption & cache_definition::SHARING_VERSION) { - $options[cache_definition::SHARING_VERSION] = new lang_string($prefix.'_version', 'cache'); - } - if ($sharingoption & cache_definition::SHARING_INPUT) { - $options[cache_definition::SHARING_INPUT] = new lang_string($prefix.'_input', 'cache'); - } - return $options; - } - - /** - * Returns all of the actions that can be performed on a definition. - * - * @param context $context the system context. - * @param array $definitionsummary information about this cache, from the array returned by - * cache_administration_helper::get_definition_summaries(). Currently only 'sharingoptions' - * element is used. - * @return array of actions. Each action is an array with two elements, 'text' and 'url'. - */ - public static function get_definition_actions(context $context, array $definitionsummary) { - if (has_capability('moodle/site:config', $context)) { - $actions = array(); - // Edit mappings. - $actions[] = array( - 'text' => get_string('editmappings', 'cache'), - 'url' => new moodle_url('/cache/admin.php', array('action' => 'editdefinitionmapping', 'sesskey' => sesskey())) - ); - // Edit sharing. - if (count($definitionsummary['sharingoptions']) > 1) { - $actions[] = array( - 'text' => get_string('editsharing', 'cache'), - 'url' => new moodle_url('/cache/admin.php', array('action' => 'editdefinitionsharing', 'sesskey' => sesskey())) - ); - } - // Purge. - $actions[] = array( - 'text' => get_string('purge', 'cache'), - 'url' => new moodle_url('/cache/admin.php', array('action' => 'purgedefinition', 'sesskey' => sesskey())) - ); - return $actions; - } - return array(); - } - - /** - * Returns all of the actions that can be performed on a store. - * - * @param string $name The name of the store - * @param array $storedetails information about this store, from the array returned by - * cache_administration_helper::get_store_instance_summaries(). - * @return array of actions. Each action is an array with two elements, 'text' and 'url'. - */ - public static function get_store_instance_actions($name, array $storedetails) { - $actions = array(); - if (has_capability('moodle/site:config', context_system::instance())) { - $baseurl = new moodle_url('/cache/admin.php', array('store' => $name, 'sesskey' => sesskey())); - if (empty($storedetails['default'])) { - $actions[] = array( - 'text' => get_string('editstore', 'cache'), - 'url' => new moodle_url($baseurl, array('action' => 'editstore', 'plugin' => $storedetails['plugin'])) - ); - $actions[] = array( - 'text' => get_string('deletestore', 'cache'), - 'url' => new moodle_url($baseurl, array('action' => 'deletestore')) - ); - } - $actions[] = array( - 'text' => get_string('purge', 'cache'), - 'url' => new moodle_url($baseurl, array('action' => 'purgestore')) - ); - } - return $actions; - } - - /** - * Returns all of the actions that can be performed on a plugin. - * - * @param string $name The name of the plugin - * @param array $plugindetails information about this store, from the array returned by - * cache_administration_helper::get_store_plugin_summaries(). - * @param array $plugindetails - * @return array - */ - public static function get_store_plugin_actions($name, array $plugindetails) { - $actions = array(); - if (has_capability('moodle/site:config', context_system::instance())) { - if (!empty($plugindetails['canaddinstance'])) { - $url = new moodle_url('/cache/admin.php', array('action' => 'addstore', 'plugin' => $name, 'sesskey' => sesskey())); - $actions[] = array( - 'text' => get_string('addinstance', 'cache'), - 'url' => $url - ); - } - } - return $actions; - } - - /** - * Returns a form that can be used to add a store instance. - * - * @param string $plugin The plugin to add an instance of - * @return cachestore_addinstance_form - * @throws coding_exception - */ - public static function get_add_store_form($plugin) { - global $CFG; // Needed for includes. - $plugins = core_component::get_plugin_list('cachestore'); - if (!array_key_exists($plugin, $plugins)) { - throw new coding_exception('Invalid cache plugin used when trying to create an edit form.'); - } - $plugindir = $plugins[$plugin]; - $class = 'cachestore_addinstance_form'; - if (file_exists($plugindir.'/addinstanceform.php')) { - require_once($plugindir.'/addinstanceform.php'); - if (class_exists('cachestore_'.$plugin.'_addinstance_form')) { - $class = 'cachestore_'.$plugin.'_addinstance_form'; - if (!array_key_exists('cachestore_addinstance_form', class_parents($class))) { - throw new coding_exception('Cache plugin add instance forms must extend cachestore_addinstance_form'); - } - } - } - - $locks = self::get_possible_locks_for_stores($plugindir, $plugin); - - $url = new moodle_url('/cache/admin.php', array('action' => 'addstore')); - return new $class($url, array('plugin' => $plugin, 'store' => null, 'locks' => $locks)); - } - - /** - * Returns a form that can be used to edit a store instance. - * - * @param string $plugin - * @param string $store - * @return cachestore_addinstance_form - * @throws coding_exception - */ - public static function get_edit_store_form($plugin, $store) { - global $CFG; // Needed for includes. - $plugins = core_component::get_plugin_list('cachestore'); - if (!array_key_exists($plugin, $plugins)) { - throw new coding_exception('Invalid cache plugin used when trying to create an edit form.'); - } - $factory = cache_factory::instance(); - $config = $factory->create_config_instance(); - $stores = $config->get_all_stores(); - if (!array_key_exists($store, $stores)) { - throw new coding_exception('Invalid store name given when trying to create an edit form.'); - } - $plugindir = $plugins[$plugin]; - $class = 'cachestore_addinstance_form'; - if (file_exists($plugindir.'/addinstanceform.php')) { - require_once($plugindir.'/addinstanceform.php'); - if (class_exists('cachestore_'.$plugin.'_addinstance_form')) { - $class = 'cachestore_'.$plugin.'_addinstance_form'; - if (!array_key_exists('cachestore_addinstance_form', class_parents($class))) { - throw new coding_exception('Cache plugin add instance forms must extend cachestore_addinstance_form'); - } - } - } - - $locks = self::get_possible_locks_for_stores($plugindir, $plugin); - - $url = new moodle_url('/cache/admin.php', array('action' => 'editstore', 'plugin' => $plugin, 'store' => $store)); - $editform = new $class($url, array('plugin' => $plugin, 'store' => $store, 'locks' => $locks)); - if (isset($stores[$store]['lock'])) { - $editform->set_data(array('lock' => $stores[$store]['lock'])); - } - // See if the cachestore is going to want to load data for the form. - // If it has a customised add instance form then it is going to want to. - $storeclass = 'cachestore_'.$plugin; - $storedata = $stores[$store]; - if (array_key_exists('configuration', $storedata) && array_key_exists('cache_is_configurable', class_implements($storeclass))) { - $storeclass::config_set_edit_form_data($editform, $storedata['configuration']); - } - return $editform; - } - - /** - * Returns an array of suitable lock instances for use with this plugin, or false if the plugin handles locking itself. - * - * @param string $plugindir - * @param string $plugin - * @return array|false - */ - protected static function get_possible_locks_for_stores($plugindir, $plugin) { - global $CFG; // Needed for includes. - $supportsnativelocking = false; - if (file_exists($plugindir.'/lib.php')) { - require_once($plugindir.'/lib.php'); - $pluginclass = 'cachestore_'.$plugin; - if (class_exists($pluginclass)) { - $supportsnativelocking = array_key_exists('cache_is_lockable', class_implements($pluginclass)); - } - } - - if (!$supportsnativelocking) { - $config = cache_config::instance(); - $locks = array(); - foreach ($config->get_locks() as $lock => $conf) { - if (!empty($conf['default'])) { - $name = get_string($lock, 'cache'); - } else { - $name = $lock; - } - $locks[$lock] = $name; - } - } else { - $locks = false; - } - - return $locks; - } - - /** - * Processes the results of the add/edit instance form data for a plugin returning an array of config information suitable to - * store in configuration. - * - * @param stdClass $data The mform data. - * @return array - * @throws coding_exception - */ - public static function get_store_configuration_from_data(stdClass $data) { - global $CFG; - $file = $CFG->dirroot.'/cache/stores/'.$data->plugin.'/lib.php'; - if (!file_exists($file)) { - throw new coding_exception('Invalid cache plugin provided. '.$file); - } - require_once($file); - $class = 'cachestore_'.$data->plugin; - if (!class_exists($class)) { - throw new coding_exception('Invalid cache plugin provided.'); - } - if (array_key_exists('cache_is_configurable', class_implements($class))) { - return $class::config_get_configuration_array($data); - } - return array(); - } - - /** - * Get an array of stores that are suitable to be used for a given definition. - * - * @param string $component - * @param string $area - * @return array Array containing 3 elements - * 1. An array of currently used stores - * 2. An array of suitable stores - * 3. An array of default stores - */ - public static function get_definition_store_options($component, $area) { - $factory = cache_factory::instance(); - $definition = $factory->create_definition($component, $area); - $config = cache_config::instance(); - $currentstores = $config->get_stores_for_definition($definition); - $possiblestores = $config->get_stores($definition->get_mode(), $definition->get_requirements_bin()); - - $defaults = array(); - foreach ($currentstores as $key => $store) { - if (!empty($store['default'])) { - $defaults[] = $key; - unset($currentstores[$key]); - } - } - foreach ($possiblestores as $key => $store) { - if ($store['default']) { - unset($possiblestores[$key]); - $possiblestores[$key] = $store; - } - } - return array($currentstores, $possiblestores, $defaults); - } - - /** - * Get the default stores for all modes. - * - * @return array An array containing sub-arrays, one for each mode. - */ - public static function get_default_mode_stores() { - global $OUTPUT; - $instance = cache_config::instance(); - $adequatestores = cache_helper::get_stores_suitable_for_mode_default(); - $icon = new pix_icon('i/warning', new lang_string('inadequatestoreformapping', 'cache')); - $storenames = array(); - foreach ($instance->get_all_stores() as $key => $store) { - if (!empty($store['default'])) { - $storenames[$key] = new lang_string('store_'.$key, 'cache'); - } - } - $modemappings = array( - cache_store::MODE_APPLICATION => array(), - cache_store::MODE_SESSION => array(), - cache_store::MODE_REQUEST => array(), - ); - foreach ($instance->get_mode_mappings() as $mapping) { - $mode = $mapping['mode']; - if (!array_key_exists($mode, $modemappings)) { - debugging('Unknown mode in cache store mode mappings', DEBUG_DEVELOPER); - continue; - } - if (array_key_exists($mapping['store'], $storenames)) { - $modemappings[$mode][$mapping['store']] = $storenames[$mapping['store']]; - } else { - $modemappings[$mode][$mapping['store']] = $mapping['store']; - } - if (!array_key_exists($mapping['store'], $adequatestores)) { - $modemappings[$mode][$mapping['store']] = $modemappings[$mode][$mapping['store']].' '.$OUTPUT->render($icon); - } - } - return $modemappings; - } - - /** - * Returns an array summarising the locks available in the system - */ - public static function get_lock_summaries() { - $locks = array(); - $instance = cache_config::instance(); - $stores = $instance->get_all_stores(); - foreach ($instance->get_locks() as $lock) { - $default = !empty($lock['default']); - if ($default) { - $name = new lang_string($lock['name'], 'cache'); - } else { - $name = $lock['name']; - } - $uses = 0; - foreach ($stores as $store) { - if (!empty($store['lock']) && $store['lock'] === $lock['name']) { - $uses++; - } - } - $lockdata = array( - 'name' => $name, - 'default' => $default, - 'uses' => $uses, - 'type' => get_string('pluginname', $lock['type']) - ); - $locks[$lock['name']] = $lockdata; - } - return $locks; - } - - /** - * Returns an array of lock plugins for which we can add an instance. - * - * Suitable for use within an mform select element. - * - * @return array - */ - public static function get_addable_lock_options() { - $plugins = core_component::get_plugin_list_with_class('cachelock', '', 'lib.php'); - $options = array(); - $len = strlen('cachelock_'); - foreach ($plugins as $plugin => $class) { - $method = "$class::can_add_instance"; - if (is_callable($method) && !call_user_func($method)) { - // Can't add an instance of this plugin. - continue; - } - $options[substr($plugin, $len)] = get_string('pluginname', $plugin); - } - return $options; - } - - /** - * Gets the form to use when adding a lock instance. - * - * @param string $plugin - * @param array $lockplugin - * @return cache_lock_form - * @throws coding_exception - */ - public static function get_add_lock_form($plugin, array $lockplugin = null) { - global $CFG; // Needed for includes. - $plugins = core_component::get_plugin_list('cachelock'); - if (!array_key_exists($plugin, $plugins)) { - throw new coding_exception('Invalid cache lock plugin requested when trying to create a form.'); - } - $plugindir = $plugins[$plugin]; - $class = 'cache_lock_form'; - if (file_exists($plugindir.'/addinstanceform.php') && in_array('cache_is_configurable', class_implements($class))) { - require_once($plugindir.'/addinstanceform.php'); - if (class_exists('cachelock_'.$plugin.'_addinstance_form')) { - $class = 'cachelock_'.$plugin.'_addinstance_form'; - if (!array_key_exists('cache_lock_form', class_parents($class))) { - throw new coding_exception('Cache lock plugin add instance forms must extend cache_lock_form'); - } - } - } - return new $class(null, array('lock' => $plugin)); - } - - /** - * Gets configuration data from a new lock instance form. - * - * @param string $plugin - * @param stdClass $data - * @return array - * @throws coding_exception - */ - public static function get_lock_configuration_from_data($plugin, $data) { - global $CFG; - $file = $CFG->dirroot.'/cache/locks/'.$plugin.'/lib.php'; - if (!file_exists($file)) { - throw new coding_exception('Invalid cache plugin provided. '.$file); - } - require_once($file); - $class = 'cachelock_'.$plugin; - if (!class_exists($class)) { - throw new coding_exception('Invalid cache plugin provided.'); - } - if (array_key_exists('cache_is_configurable', class_implements($class))) { - return $class::config_get_configuration_array($data); - } - return array(); - } } diff --git a/cache/renderer.php b/cache/renderer.php index 38ef769901b..93b3655b5af 100644 --- a/cache/renderer.php +++ b/cache/renderer.php @@ -41,9 +41,9 @@ class core_cache_renderer extends plugin_renderer_base { * Displays store summaries. * * @param array $storeinstancesummaries information about each store instance, - * as returned by cache_administration_helper::get_store_instance_summaries(). + * as returned by core_cache\administration_helper::get_store_instance_summaries(). * @param array $storepluginsummaries information about each store plugin as - * returned by cache_administration_helper::get_store_plugin_summaries(). + * returned by core_cache\administration_helper::get_store_plugin_summaries(). * @return string HTML */ public function store_instance_summariers(array $storeinstancesummaries, array $storepluginsummaries) { @@ -73,7 +73,7 @@ class core_cache_renderer extends plugin_renderer_base { $defaultstoreactions = get_string('defaultstoreactions', 'cache'); foreach ($storeinstancesummaries as $name => $storesummary) { - $actions = cache_administration_helper::get_store_instance_actions($name, $storesummary); + $htmlactions = cache_factory::get_administration_display_helper()->get_store_instance_actions($name, $storesummary); $modes = array(); foreach ($storesummary['modes'] as $mode => $enabled) { if ($enabled) { @@ -92,10 +92,6 @@ class core_cache_renderer extends plugin_renderer_base { if (!empty($storesummary['default'])) { $info = $this->output->pix_icon('i/info', $defaultstoreactions, '', array('class' => 'icon')); } - $htmlactions = array(); - foreach ($actions as $action) { - $htmlactions[] = $this->output->action_link($action['url'], $action['text']); - } $isready = $storesummary['isready'] && $storesummary['requirementsmet']; $readycell = new html_table_cell; @@ -145,7 +141,7 @@ class core_cache_renderer extends plugin_renderer_base { * Displays plugin summaries. * * @param array $storepluginsummaries information about each store plugin as - * returned by cache_administration_helper::get_store_plugin_summaries(). + * returned by core_cache\administration_helper::get_store_plugin_summaries(). * @return string HTML */ public function store_plugin_summaries(array $storepluginsummaries) { @@ -169,7 +165,7 @@ class core_cache_renderer extends plugin_renderer_base { $table->data = array(); foreach ($storepluginsummaries as $name => $plugin) { - $actions = cache_administration_helper::get_store_plugin_actions($name, $plugin); + $htmlactions = cache_factory::get_administration_display_helper()->get_store_plugin_actions($name, $plugin); $modes = array(); foreach ($plugin['modes'] as $mode => $enabled) { @@ -185,11 +181,6 @@ class core_cache_renderer extends plugin_renderer_base { } } - $htmlactions = array(); - foreach ($actions as $action) { - $htmlactions[] = $this->output->action_link($action['url'], $action['text']); - } - $row = new html_table_row(array( $plugin['name'], ($plugin['requirementsmet']) ? $this->output->pix_icon('i/valid', '1') : '', @@ -214,7 +205,7 @@ class core_cache_renderer extends plugin_renderer_base { * Displays definition summaries. * * @param array $definitionsummaries information about each definition, as returned by - * cache_administration_helper::get_definition_summaries(). + * core_cache\administration_helper::get_definition_summaries(). * @param context $context the system context. * * @return string HTML. @@ -247,12 +238,7 @@ class core_cache_renderer extends plugin_renderer_base { $none = new lang_string('none', 'cache'); foreach ($definitionsummaries as $id => $definition) { - $actions = cache_administration_helper::get_definition_actions($context, $definition); - $htmlactions = array(); - foreach ($actions as $action) { - $action['url']->param('definition', $id); - $htmlactions[] = $this->output->action_link($action['url'], $action['text']); - } + $htmlactions = cache_factory::get_administration_display_helper()->get_definition_actions($context, $definition); if (!empty($definition['mappings'])) { $mapping = join(', ', $definition['mappings']); } else { @@ -379,13 +365,24 @@ class core_cache_renderer extends plugin_renderer_base { )); } - $url = new moodle_url('/cache/admin.php', array('action' => 'newlockinstance', 'sesskey' => sesskey())); - $select = new single_select($url, 'lock', cache_administration_helper::get_addable_lock_options()); - $select->label = get_string('addnewlockinstance', 'cache'); - $html = html_writer::start_tag('div', array('id' => 'core-cache-lock-summary')); $html .= $this->output->heading(get_string('locksummary', 'cache'), 3); $html .= html_writer::table($table); + $html .= html_writer::end_tag('div'); + return $html; + } + + /** + * Renders additional actions for locks, such as Add. + * + * @return string + */ + public function additional_lock_actions() : string { + $url = new moodle_url('/cache/admin.php', array('action' => 'newlockinstance', 'sesskey' => sesskey())); + $select = new single_select($url, 'lock', cache_factory::get_administration_display_helper()->get_addable_lock_options()); + $select->label = get_string('addnewlockinstance', 'cache'); + + $html = html_writer::start_tag('div', array('id' => 'core-cache-lock-additional-actions')); $html .= html_writer::tag('div', $this->output->render($select), array('class' => 'new-instance')); $html .= html_writer::end_tag('div'); return $html; diff --git a/cache/tests/administration_helper_test.php b/cache/tests/administration_helper_test.php index 95c70de7515..865539f51d3 100644 --- a/cache/tests/administration_helper_test.php +++ b/cache/tests/administration_helper_test.php @@ -35,7 +35,7 @@ require_once($CFG->dirroot.'/cache/tests/fixtures/lib.php'); /** - * PHPunit tests for the cache API and in particular the cache_administration_helper + * PHPunit tests for the cache API and in particular the core_cache\administration_helper * * @copyright 2012 Sam Hemelryk * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later @@ -73,7 +73,7 @@ class core_cache_administration_helper_testcase extends advanced_testcase { cache_store::MODE_REQUEST => array('default_request'), ))); - $storesummaries = cache_administration_helper::get_store_instance_summaries(); + $storesummaries = core_cache\administration_helper::get_store_instance_summaries(); $this->assertInternalType('array', $storesummaries); $this->assertArrayHasKey('summariesstore', $storesummaries); $summary = $storesummaries['summariesstore']; @@ -94,7 +94,7 @@ class core_cache_administration_helper_testcase extends advanced_testcase { $this->assertEquals(1, $summary['requirementsmet']); $this->assertEquals(1, $summary['mappings']); - $definitionsummaries = cache_administration_helper::get_definition_summaries(); + $definitionsummaries = core_cache\administration_helper::get_definition_summaries(); $this->assertInternalType('array', $definitionsummaries); $this->assertArrayHasKey('core/eventinvalidation', $definitionsummaries); $summary = $definitionsummaries['core/eventinvalidation']; @@ -114,7 +114,7 @@ class core_cache_administration_helper_testcase extends advanced_testcase { $this->assertInternalType('array', $summary['mappings']); $this->assertContains('summariesstore', $summary['mappings']); - $pluginsummaries = cache_administration_helper::get_store_plugin_summaries(); + $pluginsummaries = core_cache\administration_helper::get_store_plugin_summaries(); $this->assertInternalType('array', $pluginsummaries); $this->assertArrayHasKey('file', $pluginsummaries); $summary = $pluginsummaries['file']; @@ -126,18 +126,18 @@ class core_cache_administration_helper_testcase extends advanced_testcase { $this->assertArrayHasKey('supports', $summary); $this->assertArrayHasKey('canaddinstance', $summary); - $locksummaries = cache_administration_helper::get_lock_summaries(); + $locksummaries = core_cache\administration_helper::get_lock_summaries(); $this->assertInternalType('array', $locksummaries); $this->assertTrue(count($locksummaries) > 0); - $mappings = cache_administration_helper::get_default_mode_stores(); + $mappings = core_cache\administration_helper::get_default_mode_stores(); $this->assertInternalType('array', $mappings); $this->assertCount(3, $mappings); $this->assertArrayHasKey(cache_store::MODE_APPLICATION, $mappings); $this->assertInternalType('array', $mappings[cache_store::MODE_APPLICATION]); $this->assertContains('summariesstore', $mappings[cache_store::MODE_APPLICATION]); - $potentials = cache_administration_helper::get_definition_store_options('core', 'eventinvalidation'); + $potentials = core_cache\administration_helper::get_definition_store_options('core', 'eventinvalidation'); $this->assertInternalType('array', $potentials); // Currently used, suitable, default $this->assertCount(3, $potentials); $this->assertArrayHasKey('summariesstore', $potentials[0]); @@ -149,11 +149,11 @@ class core_cache_administration_helper_testcase extends advanced_testcase { * Test instantiating an add store form. */ public function test_get_add_store_form() { - $form = cache_administration_helper::get_add_store_form('file'); + $form = cache_factory::get_administration_display_helper()->get_add_store_form('file'); $this->assertInstanceOf('moodleform', $form); try { - $form = cache_administration_helper::get_add_store_form('somethingstupid'); + $form = cache_factory::get_administration_display_helper()->get_add_store_form('somethingstupid'); $this->fail('You should not be able to create an add form for a store plugin that does not exist.'); } catch (moodle_exception $e) { $this->assertInstanceOf('coding_exception', $e, 'Needs to be: ' .get_class($e)." ::: ".$e->getMessage()); @@ -164,21 +164,23 @@ class core_cache_administration_helper_testcase extends advanced_testcase { * Test instantiating a form to edit a store instance. */ public function test_get_edit_store_form() { + // Always instantiate a new core display helper here. + $administrationhelper = new core_cache\local\administration_display_helper; $config = cache_config_writer::instance(); $this->assertTrue($config->add_store_instance('test_get_edit_store_form', 'file')); - $form = cache_administration_helper::get_edit_store_form('file', 'test_get_edit_store_form'); + $form = $administrationhelper->get_edit_store_form('file', 'test_get_edit_store_form'); $this->assertInstanceOf('moodleform', $form); try { - $form = cache_administration_helper::get_edit_store_form('somethingstupid', 'moron'); + $form = $administrationhelper->get_edit_store_form('somethingstupid', 'moron'); $this->fail('You should not be able to create an edit form for a store plugin that does not exist.'); } catch (moodle_exception $e) { $this->assertInstanceOf('coding_exception', $e); } try { - $form = cache_administration_helper::get_edit_store_form('file', 'blisters'); + $form = $administrationhelper->get_edit_store_form('file', 'blisters'); $this->fail('You should not be able to create an edit form for a store plugin that does not exist.'); } catch (moodle_exception $e) { $this->assertInstanceOf('coding_exception', $e); diff --git a/cache/upgrade.txt b/cache/upgrade.txt index d92a97e4952..1890d306076 100644 --- a/cache/upgrade.txt +++ b/cache/upgrade.txt @@ -6,6 +6,8 @@ Information provided here is intended especially for developers. * The function extend_lock() from the lock_factory interface has been deprecated without replacement including the related implementations. * The function extend() from the lock class has been deprecated without replacement. +* The cache_factory class can now be overridden by an alternative cache config class, which can + also now control the frontend display of the cache/admin.php page (see MDL-41492). === 3.9 === * The record_cache_hit/miss/set methods now take a cache_store instead of a cache_definition object diff --git a/config-dist.php b/config-dist.php index ecd56b6b6fa..97fae22b8b0 100644 --- a/config-dist.php +++ b/config-dist.php @@ -1057,6 +1057,18 @@ $CFG->admin = 'admin'; // $CFG->showcampaigncontent = true; // //========================================================================= +// 16. ALTERNATIVE CACHE CONFIG SETTINGS +//========================================================================= +// +// Alternative cache config. +// Since 3.10 it is possible to override the cache_factory class with an alternative caching factory. +// This overridden factory can provide alternative classes for caching such as cache_config, +// cache_config_writer and core_cache\local\administration_display_helper. +// The autoloaded factory class name can be specified to use. +// +// $CFG->alternative_cache_factory_class = 'tool_alternativecache_cache_factory'; +// +//========================================================================= // ALL DONE! To continue installation, visit your main page with a browser //========================================================================= diff --git a/theme/boost/scss/moodle/admin.scss b/theme/boost/scss/moodle/admin.scss index 65b5cd09b47..0b545e34d04 100644 --- a/theme/boost/scss/moodle/admin.scss +++ b/theme/boost/scss/moodle/admin.scss @@ -603,7 +603,7 @@ #core-cache-rescan-definitions, #core-cache-mode-mappings .edit-link, -#core-cache-lock-summary .new-instance { +#core-cache-lock-additional-actions .new-instance { margin-top: 0.5em; text-align: center; } diff --git a/theme/boost/style/moodle.css b/theme/boost/style/moodle.css index 6a60c9a243f..916d24ecddf 100644 --- a/theme/boost/style/moodle.css +++ b/theme/boost/style/moodle.css @@ -12420,7 +12420,7 @@ input[disabled] { #core-cache-rescan-definitions, #core-cache-mode-mappings .edit-link, -#core-cache-lock-summary .new-instance { +#core-cache-lock-additional-actions .new-instance { margin-top: 0.5em; text-align: center; } diff --git a/theme/classic/style/moodle.css b/theme/classic/style/moodle.css index 37f2d5b77e9..72bfb01f99a 100644 --- a/theme/classic/style/moodle.css +++ b/theme/classic/style/moodle.css @@ -12634,7 +12634,7 @@ input[disabled] { #core-cache-rescan-definitions, #core-cache-mode-mappings .edit-link, -#core-cache-lock-summary .new-instance { +#core-cache-lock-additional-actions .new-instance { margin-top: 0.5em; text-align: center; }