first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
+85
View File
@@ -0,0 +1,85 @@
<?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/>.
/**
* The library file for the apcu cache store.
*
* This file is part of the apcu cache store, it contains the API for interacting with an instance of the store.
*
* @package cachestore_apcu
* @copyright 2014 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot.'/cache/forms.php');
/**
* Form for adding a apcu instance.
*
* @copyright 2014 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cachestore_apcu_addinstance_form extends cachestore_addinstance_form {
/**
* Add the desired form elements.
*/
protected function configuration_definition() {
global $CFG;
$form = $this->_form;
$form->addElement('text', 'prefix', get_string('prefix', 'cachestore_apcu'),
array('maxlength' => 5, 'size' => 5));
$form->addHelpButton('prefix', 'prefix', 'cachestore_apcu');
$form->setType('prefix', PARAM_TEXT); // We set to text but we have a rule to limit to alphanumext.
$form->setDefault('prefix', $CFG->prefix);
$form->addRule('prefix', get_string('prefixinvalid', 'cachestore_apcu'), 'regex', '#^[a-zA-Z0-9\-_]+$#');
$form->addElement('header', 'apc_notice', get_string('notice', 'cachestore_apcu'));
$form->setExpanded('apc_notice');
$link = get_docs_url('Caching#APC');
$form->addElement('html', nl2br(get_string('clusternotice', 'cachestore_apcu', $link)));
}
/**
* Validates the configuration data.
*
* We need to check that prefix is unique.
*
* @param array $data
* @param array $files
* @param array $errors
* @return array
* @throws coding_exception
*/
public function configuration_validation($data, $files, array $errors) {
if (empty($errors['prefix'])) {
$factory = cache_factory::instance();
$config = $factory->create_config_instance();
foreach ($config->get_all_stores() as $store) {
if ($store['plugin'] === 'apcu') {
if (isset($store['configuration']['prefix'])) {
if ($data['prefix'] === $store['configuration']['prefix']) {
// The new store has the same prefix as an existing store, thats a problem.
$errors['prefix'] = get_string('prefixnotunique', 'cachestore_apcu');
break;
}
} else if (empty($data['prefix'])) {
// The existing store hasn't got a prefix and neither does the new store, that's a problem.
$errors['prefix'] = get_string('prefixnotunique', 'cachestore_apcu');
break;
}
}
}
}
return $errors;
}
}
+46
View File
@@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for cachestore_apcu.
*
* @package cachestore_apcu
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace cachestore_apcu\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for cachestore_apcu implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason(): string {
return 'privacy:metadata';
}
}
+35
View File
@@ -0,0 +1,35 @@
<?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/>.
/**
* APCu cache store language strings.
*
* @package cachestore_apcu
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['clusternotice'] = 'Please be aware that APCu is only a suitable choice for single node sites or caches that can be stored locally.
For more information, see the <a href="{$a}">APC user cache documentation</a>.';
$string['notice'] = 'Notice';
$string['pluginname'] = 'APC user cache (APCu)';
$string['prefix'] = 'Prefix';
$string['prefix_help'] = 'The above prefix gets used for all keys being stored in this APC store instance. By default the database prefix is used.';
$string['prefixinvalid'] = 'The prefix you have selected is invalid. You can only use a-z A-Z 0-9-_.';
$string['prefixnotunique'] = 'The prefix you have selected is not unique. Please choose a unique prefix.';
$string['privacy:metadata'] = 'The APC user cache (APCu) plugin stores data briefly as part of its caching functionality but this data is regularly cleared and is not sent externally in any way.';
$string['testperformance'] = 'Test performance';
$string['testperformance_desc'] = 'If enabled, APCu performance will be included when viewing the Test performance page. Enabling this on a production site is not recommended.';
+412
View File
@@ -0,0 +1,412 @@
<?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/>.
/**
* APCu cache store main library.
*
* @package cachestore_apcu
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* The APCu cache store class.
*
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cachestore_apcu extends cache_store implements cache_is_key_aware, cache_is_configurable {
/**
* The required version of APCu for this extension.
*/
const REQUIRED_VERSION = '4.0.0';
/**
* The name of this store instance.
* @var string
*/
protected $name;
/**
* The definition used when this instance was initialised.
* @var cache_definition
*/
protected $definition = null;
/**
* The storeprefix to use on all instances of this store. Configured as part store setup.
* @var string
*/
protected $storeprefix = null;
/**
* The prefix added specifically for this cache.
* @var string
*/
protected $cacheprefix = null;
/**
* Static method to check that the APCu stores requirements have been met.
*
* It checks that the APCu extension has been loaded and that it has been enabled.
*
* @return bool True if the stores software/hardware requirements have been met and it can be used. False otherwise.
*/
public static function are_requirements_met() {
$enabled = ini_get('apc.enabled') && (php_sapi_name() != "cli" || ini_get('apc.enable_cli'));
if (!extension_loaded('apcu') || !$enabled) {
return false;
}
$version = phpversion('apcu');
return $version && version_compare($version, self::REQUIRED_VERSION, '>=');
}
/**
* Static method to check if a store is usable with the given mode.
*
* @param int $mode One of cache_store::MODE_*
* @return bool True if the mode is supported.
*/
public static function is_supported_mode($mode) {
return ($mode === self::MODE_APPLICATION || $mode === self::MODE_SESSION);
}
/**
* Returns the supported features as a binary flag.
*
* @param array $configuration The configuration of a store to consider specifically.
* @return int The supported features.
*/
public static function get_supported_features(array $configuration = array()) {
return self::SUPPORTS_NATIVE_TTL;
}
/**
* Returns the supported modes as a binary flag.
*
* @param array $configuration The configuration of a store to consider specifically.
* @return int The supported modes.
*/
public static function get_supported_modes(array $configuration = array()) {
return self::MODE_APPLICATION + self::MODE_SESSION;
}
/**
* Constructs an instance of the cache store.
*
* This method should not create connections or perform and processing, it should be used
*
* @param string $name The name of the cache store
* @param array $configuration The configuration for this store instance.
*/
public function __construct($name, array $configuration = array()) {
global $CFG;
$this->name = $name;
$this->storeprefix = $CFG->prefix;
if (isset($configuration['prefix'])) {
$this->storeprefix = $configuration['prefix'];
}
}
/**
* Returns the name of this store instance.
* @return string
*/
public function my_name() {
return $this->name;
}
/**
* Initialises a new instance of the cache store given the definition the instance is to be used for.
*
* This function should prepare any given connections etc.
*
* @param cache_definition $definition
* @return bool
*/
public function initialise(cache_definition $definition) {
$this->definition = $definition;
$this->cacheprefix = $this->storeprefix.$definition->generate_definition_hash().'__';
return true;
}
/**
* Returns true if this cache store instance has been initialised.
* @return bool
*/
public function is_initialised() {
return ($this->definition !== null);
}
/**
* Prepares the given key for use.
*
* Should be called before all interaction.
*
* @param string $key The key to prepare for storing in APCu.
*
* @return string
*/
protected function prepare_key($key) {
return $this->cacheprefix . $key;
}
/**
* Retrieves an item from the cache store given its key.
*
* @param string $key The key to retrieve
* @return mixed The data that was associated with the key, or false if the key did not exist.
*/
public function get($key) {
$key = $this->prepare_key($key);
$success = false;
$outcome = apcu_fetch($key, $success);
if ($success) {
return $outcome;
}
return $success;
}
/**
* Retrieves several items from the cache store in a single transaction.
*
* If not all of the items are available in the cache then the data value for those that are missing will be set to false.
*
* @param array $keys The array of keys to retrieve
* @return array An array of items from the cache. There will be an item for each key, those that were not in the store will
* be set to false.
*/
public function get_many($keys) {
$map = array();
foreach ($keys as $key) {
$map[$key] = $this->prepare_key($key);
}
$outcomes = array();
$success = false;
$results = apcu_fetch($map, $success);
if ($success) {
foreach ($map as $key => $used) {
if (array_key_exists($used, $results)) {
$outcomes[$key] = $results[$used];
} else {
$outcomes[$key] = false;
}
}
} else {
$outcomes = array_fill_keys($keys, false);
}
return $outcomes;
}
/**
* Sets an item in the cache given its key and data value.
*
* @param string $key The key to use.
* @param mixed $data The data to set.
* @return bool True if the operation was a success false otherwise.
*/
public function set($key, $data) {
$key = $this->prepare_key($key);
return apcu_store($key, $data, $this->definition->get_ttl());
}
/**
* Sets many items in the cache in a single transaction.
*
* @param array $keyvaluearray An array of key value pairs. Each item in the array will be an associative array with two
* keys, 'key' and 'value'.
* @return int The number of items successfully set. It is up to the developer to check this matches the number of items
* sent ... if they care that is.
*/
public function set_many(array $keyvaluearray) {
$map = array();
foreach ($keyvaluearray as $pair) {
$key = $this->prepare_key($pair['key']);
$map[$key] = $pair['value'];
}
$result = apcu_store($map, null, $this->definition->get_ttl());
return count($map) - count($result);
}
/**
* Deletes an item from the cache store.
*
* @param string $key The key to delete.
* @return bool Returns true if the operation was a success, false otherwise.
*/
public function delete($key) {
$key = $this->prepare_key($key);
return apcu_delete($key);
}
/**
* Deletes several keys from the cache in a single action.
*
* @param array $keys The keys to delete
* @return int The number of items successfully deleted.
*/
public function delete_many(array $keys) {
$count = 0;
foreach ($keys as $key) {
if ($this->delete($key)) {
$count++;
}
}
return $count;
}
/**
* Purges the cache deleting all items within it.
*
* @return boolean True on success. False otherwise.
*/
public function purge() {
if (class_exists('APCUIterator', false)) {
$iterator = new APCUIterator('#^' . preg_quote($this->cacheprefix, '#') . '#');
} else {
$iterator = new APCIterator('user', '#^' . preg_quote($this->cacheprefix, '#') . '#');
}
return apcu_delete($iterator);
}
/**
* Performs any necessary clean up when the store instance is being deleted.
*/
public function instance_deleted() {
if (class_exists('APCUIterator', false)) {
$iterator = new APCUIterator('#^' . preg_quote($this->storeprefix, '#') . '#');
} else {
$iterator = new APCIterator('user', '#^' . preg_quote($this->storeprefix, '#') . '#');
}
return apcu_delete($iterator);
}
/**
* Generates an instance of the cache store that can be used for testing.
*
* Returns an instance of the cache store, or false if one cannot be created.
*
* @param cache_definition $definition
* @return cache_store
*/
public static function initialise_test_instance(cache_definition $definition) {
$testperformance = get_config('cachestore_apcu', 'testperformance');
if (empty($testperformance)) {
return false;
}
if (!self::are_requirements_met()) {
return false;
}
$name = 'APCu test';
$cache = new cachestore_apcu($name);
// No need to check if is_ready() as this has already being done by requirement check.
$cache->initialise($definition);
return $cache;
}
/**
* Test is a cache has a key.
*
* @param string|int $key
* @return bool True if the cache has the requested key, false otherwise.
*/
public function has($key) {
$key = $this->prepare_key($key);
return apcu_exists($key);
}
/**
* Test if a cache has at least one of the given keys.
*
* @param array $keys
* @return bool True if the cache has at least one of the given keys
*/
public function has_any(array $keys) {
foreach ($keys as $arraykey => $key) {
$keys[$arraykey] = $this->prepare_key($key);
}
$result = apcu_exists($keys);
return count($result) > 0;
}
/**
* Test is a cache has all of the given keys.
*
* @param array $keys
* @return bool True if the cache has all of the given keys, false otherwise.
*/
public function has_all(array $keys) {
foreach ($keys as $arraykey => $key) {
$keys[$arraykey] = $this->prepare_key($key);
}
$result = apcu_exists($keys);
return count($result) === count($keys);
}
/**
* Generates the appropriate configuration required for unit testing.
*
* @return array Array of unit test configuration data to be used by initialise().
*/
public static function unit_test_configuration() {
return array('prefix' => 'phpunit');
}
/**
* Given the data from the add instance form this function creates a configuration array.
*
* @param stdClass $data
* @return array
*/
public static function config_get_configuration_array($data) {
$config = array();
if (isset($data->prefix)) {
$config['prefix'] = $data->prefix;
}
return $config;
}
/**
* Allows the cache store to set its data against the edit form before it is shown to the user.
*
* @param moodleform $editform
* @param array $config
*/
public static function config_set_edit_form_data(moodleform $editform, array $config) {
if (isset($config['prefix'])) {
$data['prefix'] = $config['prefix'];
} else {
$data['prefix'] = '';
}
$editform->set_data($data);
}
/**
* Returns true if this cache store instance is both suitable for testing, and ready for testing.
*
* Cache stores that support being used as the default store for unit and acceptance testing should
* override this function and return true if there requirements have been met.
*
* @return bool
*/
public static function ready_to_be_used_for_testing() {
return true;
}
}
+36
View File
@@ -0,0 +1,36 @@
<?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/>.
/**
* The settings for the APCu store.
*
* This file is part of the APCu cache store, it contains the API for interacting with an instance of the store.
*
* @package cachestore_apcu
* @copyright 2014 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
$settings->add(
new admin_setting_configcheckbox(
'cachestore_apcu/testperformance',
new lang_string('testperformance', 'cachestore_apcu'),
new lang_string('testperformance_desc', 'cachestore_apcu'),
false
)
);
+89
View File
@@ -0,0 +1,89 @@
<?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/>.
namespace cachestore_apcu;
use cache_store;
use cache_definition;
use cachestore_apcu;
defined('MOODLE_INTERNAL') || die();
// Include the necessary evils.
global $CFG;
require_once($CFG->dirroot.'/cache/tests/fixtures/stores.php');
require_once($CFG->dirroot.'/cache/stores/apcu/lib.php');
/**
* APC unit test class.
*
* @package cachestore_apcu
* @copyright 2014 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class store_test extends \cachestore_tests {
/**
* Returns the apcu class name
* @return string
*/
protected function get_class_name() {
return 'cachestore_apcu';
}
public function setUp(): void {
if (!cachestore_apcu::are_requirements_met()) {
$this->markTestSkipped('Could not test cachestore_apcu. Requirements are not met.');
}
parent::setUp();
}
/**
* Test that the Moodle APCu store doesn't cross paths with other code using APCu as well.
*/
public function test_cross_application_interaction(): void {
$definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, 'cachestore_apcu', 'phpunit_test');
$instance = new cachestore_apcu('Test', cachestore_apcu::unit_test_configuration());
$instance->initialise($definition);
// Test purge with custom data.
$this->assertTrue($instance->set('test', 'monster'));
$this->assertSame('monster', $instance->get('test'));
$this->assertTrue(apcu_store('test', 'pirate', 180));
$this->assertSame('monster', $instance->get('test'));
$this->assertTrue(apcu_exists('test'));
$this->assertSame('pirate', apcu_fetch('test'));
// Purge and check that our data is gone but the the custom data is still there.
$this->assertTrue($instance->purge());
$this->assertFalse($instance->get('test'));
$this->assertTrue(apcu_exists('test'));
$this->assertSame('pirate', apcu_fetch('test'));
}
public function test_different_caches_have_different_prefixes(): void {
$definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, 'cachestore_apcu', 'phpunit_test');
$instance = new cachestore_apcu('Test', cachestore_apcu::unit_test_configuration());
$instance->initialise($definition);
$definition2 = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, 'cachestore_apcu', 'phpunit_test2');
$instance2 = new cachestore_apcu('Test', cachestore_apcu::unit_test_configuration());
$instance2->initialise($definition2);
$instance->set('test1', 1);
$this->assertFalse($instance2->get('test1'));
$instance2->purge();
$this->assertSame(1, $instance->get('test1'));
}
}
+30
View File
@@ -0,0 +1,30 @@
<?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/>.
/**
* APCu cache store version information.
*
* @package cachestore_apcu
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
$plugin->version = 2024042200;
$plugin->requires = 2024041600;
$plugin->maturity = MATURITY_STABLE;
$plugin->component = 'cachestore_apcu';
+71
View File
@@ -0,0 +1,71 @@
<?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/>.
/**
* The library file for the file cache store.
*
* This file is part of the file cache store, it contains the API for interacting with an instance of the store.
* This is used as a default cache store within the Cache API. It should never be deleted.
*
* @package cachestore_file
* @category cache
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once($CFG->dirroot.'/cache/forms.php');
/**
* Form for adding a file instance.
*
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cachestore_file_addinstance_form extends cachestore_addinstance_form {
/**
* Adds the desired form elements.
*/
protected function configuration_definition() {
$form = $this->_form;
$form->addElement('text', 'path', get_string('path', 'cachestore_file'));
$form->setType('path', PARAM_RAW);
$form->addHelpButton('path', 'path', 'cachestore_file');
$form->addElement('checkbox', 'autocreate', get_string('autocreate', 'cachestore_file'));
$form->setType('autocreate', PARAM_BOOL);
$form->addHelpButton('autocreate', 'autocreate', 'cachestore_file');
$form->disabledIf('autocreate', 'path', 'eq', '');
$form->addElement('checkbox', 'singledirectory', get_string('singledirectory', 'cachestore_file'));
$form->setType('singledirectory', PARAM_BOOL);
$form->addHelpButton('singledirectory', 'singledirectory', 'cachestore_file');
$form->addElement('checkbox', 'prescan', get_string('prescan', 'cachestore_file'));
$form->setType('prescan', PARAM_BOOL);
$form->addHelpButton('prescan', 'prescan', 'cachestore_file');
$form->addElement('checkbox', 'asyncpurge', get_string('asyncpurge', 'cachestore_file'));
$form->setType('asyncpurge', PARAM_BOOL);
$form->addHelpButton('asyncpurge', 'asyncpurge', 'cachestore_file');
$form->addElement('text', 'lockwait', get_string('lockwait', 'cachestore_file'));
$form->setDefault('lockwait', 60);
$form->setType('lockwait', PARAM_INT);
$form->addHelpButton('lockwait', 'lockwait', 'cachestore_file');
}
}
+46
View File
@@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for cachestore_file.
*
* @package cachestore_file
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace cachestore_file\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for cachestore_file implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason(): string {
return 'privacy:metadata';
}
}
+53
View File
@@ -0,0 +1,53 @@
<?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/>.
namespace cachestore_file\task;
/**
* Task deletes old cache revision directory.
*
* @package cachestore_file
* @copyright Catalyst IT Europe Ltd 2021
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Jackson D'Souza <jackson.dsouza@catalyst-eu.net>
*/
class asyncpurge extends \core\task\adhoc_task {
/**
* Executes the scheduled task.
*
* @return boolean True if old cache revision directory exists and is deleted. False otherwise.
*/
public function execute(): bool {
$returnvar = true;
$output = 'Cleaning up file store old cache revision directory:' . PHP_EOL;
$data = $this->get_custom_data();
if (is_dir($data->path)) {
remove_dir($data->path);
$output .= 'Directory deleted: ' . $data->path;
} else {
$output .= 'Directory not found: ' . $data->path;
$returnvar = false;
}
if (!PHPUNIT_TEST) {
mtrace($output);
}
return $returnvar;
}
}
+61
View File
@@ -0,0 +1,61 @@
<?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/>.
/**
* The library file for the file cache store.
*
* This file is part of the file cache store, it contains the API for interacting with an instance of the store.
* This is used as a default cache store within the Cache API. It should never be deleted.
*
* @package cachestore_file
* @category cache
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$string['asyncpurge'] = 'Asynchronously purge directory';
$string['asyncpurge_help'] = 'If enabled, the new directory is created with cache revision and the old directory will be deleted asynchronously via a scheduled task.';
$string['autocreate'] = 'Auto create directory';
$string['autocreate_help'] = 'If enabled the directory specified in path will be automatically created if it does not already exist.';
$string['lockwait'] = 'Maximum lock wait time';
$string['lockwait_help'] = 'The maximum amount of time in seconds to wait for an exclusive lock before reading or writing a cache key. This is only used for cache definitions that have read or write locking required.';
$string['path'] = 'Cache path';
$string['path_help'] = 'The directory that should be used to store files for this cache store. If left blank (default) a directory will be automatically created in the moodledata directory. This can be used to point a file store towards a directory on a better performing drive (such as one in memory).';
$string['pluginname'] = 'File cache';
$string['privacy:metadata'] = 'The File cache cachestore plugin stores data briefly as part of its caching functionality but this data is regularly cleared.';
$string['prescan'] = 'Prescan directory';
$string['prescan_help'] = 'If enabled the directory is scanned when the cache is first used and requests for files are first checked against the scan data. This can help if you have a slow file system and are finding that file operations are causing you a bottle neck.';
$string['singledirectory'] = 'Single directory store';
$string['singledirectory_help'] = 'If enabled files (cached items) will be stored in a single directory rather than being broken up into multiple directories.
Enabling this will speed up file interactions but comes at the cost of increased risk of hitting file system limitations.
It is advisable to only turn this on if the following is true:
* If you know the number of items in the cache is going to be small enough that it won\'t cause issues on the file system you are running with.
* The data being cached is not expensive to generate. If it is then sticking with the default may still be the better option as it reduces the chance of issues.';
$string['task_asyncpurge'] = 'Asynchronously purge file store old cache revision directories';
/**
* This is is like the file store, but designed for siutations where:
* - many more things are likely to be stored in the cache, so CRC hashing is
* too likely to give collisions, and storing everything in a completely flat
* directory structure is inadvisable.
* - the things we are caching are more expensive to calculate, so the extra
* time to computer a better hash is a worthwhile trade-off.
*/
+1042
View File
File diff suppressed because it is too large Load Diff
+97
View File
@@ -0,0 +1,97 @@
<?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/>.
namespace cachestore_file;
use cache_definition;
use cache_store;
use cachestore_file;
/**
* Async purge support test for File cache.
*
* @package cachestore_file
* @copyright Catalyst IT Europe Ltd 2021
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Jackson D'Souza <jackson.dsouza@catalyst-eu.net>
* @coversDefaultClass \cachestore_file
*/
class asyncpurge_test extends \advanced_testcase {
/**
* Testing Asynchronous file store cache purge
*
* @covers ::initialise
* @covers ::set
* @covers ::get
* @covers ::purge
*/
public function test_cache_async_purge(): void {
$this->resetAfterTest(true);
// Cache definition.
$definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, 'cachestore_file', 'phpunit_test');
// Extra config, set async purge = true.
$extraconfig = ['asyncpurge' => true, 'filecacherev' => time()];
$configuration = array_merge(cachestore_file::unit_test_configuration(), $extraconfig);
$name = 'File async test';
// Create file cache store.
$cache = new cachestore_file($name, $configuration);
// Initialise file cache store.
$cache->initialise($definition);
$cache->set('foo', 'bar');
$this->assertSame('bar', $cache->get('foo'));
// Purge this file cache store.
$cache->purge();
// Purging file cache store shouldn't purge the data but create a new cache revision directory.
$this->assertSame('bar', $cache->get('foo'));
$cache->set('foo', 'bar 2');
$this->assertSame('bar 2', $cache->get('foo'));
}
/**
* Testing Adhoc Cron - deletes old cache revision directory
*
* @covers \cachestore_file\task
*/
public function test_cache_async_purge_cron(): void {
global $CFG, $USER;
$this->resetAfterTest(true);
$tmpdir = realpath($CFG->tempdir);
$directorypath = '/cachefile_store';
$cacherevdir = $tmpdir . $directorypath;
// Create cache revision directory.
mkdir($cacherevdir, $CFG->directorypermissions, true);
// Create / execute adhoc task to delete cache revision directory.
$asynctask = new cachestore_file\task\asyncpurge();
$asynctask->set_custom_data(['path' => $cacherevdir]);
$asynctask->set_userid($USER->id);
\core\task\manager::queue_adhoc_task($asynctask);
$asynctask->execute();
// Check if cache revision directory has been deleted.
$this->assertDirectoryDoesNotExist($cacherevdir);
}
}
+114
View File
@@ -0,0 +1,114 @@
<?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/>.
namespace cachestore_file;
use cache_definition;
use cache_store;
use cachestore_file;
defined('MOODLE_INTERNAL') || die();
// Include the necessary evils.
global $CFG;
require_once($CFG->dirroot.'/cache/tests/fixtures/stores.php');
require_once($CFG->dirroot.'/cache/stores/file/lib.php');
/**
* File unit test class.
*
* @package cachestore_file
* @copyright 2013 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \cachestore_file
*/
class store_test extends \cachestore_tests {
/**
* Returns the file class name
* @return string
*/
protected function get_class_name() {
return 'cachestore_file';
}
/**
* Testing cachestore_file::get with prescan enabled and with
* deleting the cache between the prescan and the call to get.
*
* The deleting of cache simulates some other process purging
* the cache.
*/
public function test_cache_get_with_prescan_and_purge(): void {
global $CFG;
$definition = cache_definition::load_adhoc(cache_store::MODE_REQUEST, 'cachestore_file', 'phpunit_test');
$name = 'File test';
$path = make_cache_directory('cachestore_file_test');
$cache = new cachestore_file($name, array('path' => $path, 'prescan' => true));
$cache->initialise($definition);
$cache->set('testing', 'value');
$path = make_cache_directory('cachestore_file_test');
$cache = new cachestore_file($name, array('path' => $path, 'prescan' => true));
$cache->initialise($definition);
// Let's pretend that some other process purged caches.
remove_dir($CFG->cachedir.'/cachestore_file_test', true);
make_cache_directory('cachestore_file_test');
$cache->get('testing');
}
/**
* Tests the get_last_read byte count.
*/
public function test_get_last_io_bytes(): void {
$definition = cache_definition::load_adhoc(cache_store::MODE_REQUEST, 'cachestore_file', 'phpunit_test');
$store = new \cachestore_file('Test');
$store->initialise($definition);
$store->set('foo', 'bar');
$store->set('frog', 'ribbit');
$store->get('foo');
// It's not 3 bytes, because the data is stored serialized.
$this->assertEquals(10, $store->get_last_io_bytes());
$store->get('frog');
$this->assertEquals(13, $store->get_last_io_bytes());
$store->get_many(['foo', 'frog']);
$this->assertEquals(23, $store->get_last_io_bytes());
$store->set('foo', 'goo');
$this->assertEquals(10, $store->get_last_io_bytes());
$store->set_many([
['key' => 'foo', 'value' => 'bar'],
['key' => 'frog', 'value' => 'jump']
]);
$this->assertEquals(21, $store->get_last_io_bytes());
}
public function test_lock(): void {
$store = new \cachestore_file('Test');
$this->assertTrue($store->acquire_lock('lock', '123'));
$this->assertTrue($store->check_lock_state('lock', '123'));
$this->assertFalse($store->check_lock_state('lock', '321'));
$this->assertNull($store->check_lock_state('notalock', '123'));
$this->assertFalse($store->release_lock('lock', '321'));
$this->assertTrue($store->release_lock('lock', '123'));
}
}
+32
View File
@@ -0,0 +1,32 @@
<?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 file store version information.
*
* This is used as a default cache store within the Cache API. It should never be deleted.
*
* @package cachestore_file
* @category cache
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
$plugin->version = 2024042200; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2024041600; // Requires this Moodle version.
$plugin->component = 'cachestore_file'; // Full name of the plugin.
+6
View File
@@ -0,0 +1,6 @@
Redis Cache Store for Moodle
============================
A Moodle cache store plugin for [Redis](http://redis.io).
This plugin requires the [PhpRedis](https://github.com/phpredis/phpredis) extension. The PhpRedis extension can be installed via PECL with `pecl install redis`.
+81
View File
@@ -0,0 +1,81 @@
<?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/>.
/**
* Redis Cache Store - Add instance form
*
* @package cachestore_redis
* @copyright 2013 Adam Durana
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot . '/cache/forms.php');
/**
* Form for adding instance of Redis Cache Store.
*
* @copyright 2013 Adam Durana
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cachestore_redis_addinstance_form extends cachestore_addinstance_form {
/**
* Builds the form for creating an instance.
*/
protected function configuration_definition() {
$form = $this->_form;
$form->addElement('advcheckbox', 'clustermode', get_string('clustermode', 'cachestore_redis'), '',
cache_helper::is_cluster_available() ? '' : 'disabled');
$form->addHelpButton('clustermode', 'clustermode', 'cachestore_redis');
$form->setType('clustermode', PARAM_BOOL);
$form->addElement('textarea', 'server', get_string('server', 'cachestore_redis'), ['cols' => 6, 'rows' => 10]);
$form->setType('server', PARAM_TEXT);
$form->addHelpButton('server', 'server', 'cachestore_redis');
$form->addRule('server', get_string('required'), 'required');
$form->addElement('advcheckbox', 'encryption', get_string('encrypt_connection', 'cachestore_redis'));
$form->setType('encryption', PARAM_BOOL);
$form->addHelpButton('encryption', 'encrypt_connection', 'cachestore_redis');
$form->addElement('text', 'cafile', get_string('ca_file', 'cachestore_redis'));
$form->setType('cafile', PARAM_TEXT);
$form->addHelpButton('cafile', 'ca_file', 'cachestore_redis');
$form->addElement('passwordunmask', 'password', get_string('password', 'cachestore_redis'));
$form->setType('password', PARAM_RAW);
$form->addHelpButton('password', 'password', 'cachestore_redis');
$form->addElement('text', 'prefix', get_string('prefix', 'cachestore_redis'), array('size' => 16));
$form->setType('prefix', PARAM_TEXT); // We set to text but we have a rule to limit to alphanumext.
$form->addHelpButton('prefix', 'prefix', 'cachestore_redis');
$form->addRule('prefix', get_string('prefixinvalid', 'cachestore_redis'), 'regex', '#^[a-zA-Z0-9\-_]+$#');
$serializeroptions = cachestore_redis::config_get_serializer_options();
$form->addElement('select', 'serializer', get_string('useserializer', 'cachestore_redis'), $serializeroptions);
$form->addHelpButton('serializer', 'useserializer', 'cachestore_redis');
$form->setDefault('serializer', Redis::SERIALIZER_PHP);
$form->setType('serializer', PARAM_INT);
$compressoroptions = cachestore_redis::config_get_compressor_options();
$form->addElement('select', 'compressor', get_string('usecompressor', 'cachestore_redis'), $compressoroptions);
$form->addHelpButton('compressor', 'usecompressor', 'cachestore_redis');
$form->setDefault('compressor', cachestore_redis::COMPRESSOR_NONE);
$form->setType('compressor', PARAM_INT);
}
}
+109
View File
@@ -0,0 +1,109 @@
<?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/>.
/**
* Privacy Subsystem implementation for cachestore_redis.
*
* @package cachestore_redis
* @category privacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace cachestore_redis\privacy;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\approved_userlist;
use core_privacy\local\request\contextlist;
use core_privacy\local\request\userlist;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for cachestore_redis.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
\core_privacy\local\metadata\provider,
\core_privacy\local\request\plugin\provider,
\core_privacy\local\request\core_userlist_provider {
/**
* Returns meta data about this system.
*
* @param collection $collection The initialised collection to add items to.
* @return collection A listing of user data stored through this system.
*/
public static function get_metadata(collection $collection): collection {
$collection->add_external_location_link('redis', [
'data' => 'privacy:metadata:redis:data',
], 'privacy:metadata:redis');
return $collection;
}
/**
* Get the list of contexts that contain user information for the specified user.
*
* @param int $userid The user to search.
* @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
*/
public static function get_contexts_for_userid(int $userid): contextlist {
return new contextlist();
}
/**
* Get the list of users who have data within a context.
*
* @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
*/
public static function get_users_in_context(userlist $userlist) {
}
/**
* Export all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts to export information for.
*/
public static function export_user_data(approved_contextlist $contextlist) {
}
/**
* Delete all use data which matches the specified deletion_criteria.
*
* @param \context $context A user context.
*/
public static function delete_data_for_all_users_in_context(\context $context) {
}
/**
* Delete all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
*/
public static function delete_data_for_user(approved_contextlist $contextlist) {
}
/**
* Delete multiple users within a single context.
*
* @param approved_userlist $userlist The approved context and user information to delete information for.
*/
public static function delete_data_for_users(approved_userlist $userlist) {
}
}
+105
View File
@@ -0,0 +1,105 @@
<?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/>.
namespace cachestore_redis\task;
/**
* Task deletes old data from Redis caches with TTL set.
*
* @package cachestore_redis
* @copyright 2021 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class ttl extends \core\task\scheduled_task {
/** @var int Only display memory savings of at least 100 KB */
const MIN_MEMORY_SIZE = 100 * 1024;
/**
* Gets the name of this task.
*
* @return string Task name
*/
public function get_name(): string {
return get_string('task_ttl', 'cachestore_redis');
}
/**
* Executes the scheduled task.
*/
public function execute(): void {
// Find all Redis cache stores.
$factory = \cache_factory::instance();
$config = $factory->create_config_instance();
$stores = $config->get_all_stores();
$doneanything = false;
foreach ($stores as $storename => $storeconfig) {
if ($storeconfig['plugin'] !== 'redis') {
continue;
}
// For each definition in the cache store, do TTL expiry if needed.
$definitions = $config->get_definitions_by_store($storename);
foreach ($definitions as $definition) {
if (empty($definition['ttl'])) {
continue;
}
if (!empty($definition['requireidentifiers'])) {
// We can't make cache below if it requires identifiers.
continue;
}
$doneanything = true;
$definitionname = $definition['component'] . '/' . $definition['area'];
mtrace($definitionname, ': ');
\cache::make($definition['component'], $definition['area']);
$definition = $factory->create_definition($definition['component'], $definition['area']);
$stores = $factory->get_store_instances_in_use($definition);
foreach ($stores as $store) {
// These were all definitions using a Redis store but one definition may
// potentially have multiple stores, we need to process the Redis ones only.
if (!($store instanceof \cachestore_redis)) {
continue;
}
$info = $store->expire_ttl();
$infotext = 'Deleted ' . $info['keys'] . ' key(s) in ' .
sprintf('%0.2f', $info['time']) . 's';
// Only report memory information if available, positive, and reasonably large.
// Otherwise the real information is hard to see amongst random variation etc.
if (!empty($info['memory']) && $info['memory'] > self::MIN_MEMORY_SIZE) {
$infotext .= ' - reported saving ' . display_size($info['memory']);
}
mtrace($infotext);
}
}
}
if (!$doneanything) {
mtrace('No TTL caches assigned to a Redis store; nothing to do.');
}
}
/**
* Checks if this task is allowed to run - this makes it show the 'Run now' link (or not).
*
* @return bool True if task can run
*/
public function can_run(): bool {
// The default implementation of this function checks the plugin is enabled, which doesn't
// seem to work (probably because cachestore plugins can't be enabled).
// We could check if there is a Redis store configured, but it would have to do the exact
// same logic as already in the first part of 'execute', so it's probably OK to just return
// true.
return true;
}
}
+38
View File
@@ -0,0 +1,38 @@
<?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/>.
/**
* Scheduled tasks.
*
* @copyright 2021 The Open University
* @package cachestore_redis
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$tasks = [
[
'classname' => '\cachestore_redis\task\ttl',
'blocking' => 0,
'minute' => 'R',
'hour' => '*',
'day' => '*',
'month' => '*',
'dayofweek' => '*',
'disabled' => 0
]
];
+99
View File
@@ -0,0 +1,99 @@
<?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/>.
/**
* Redis Cache Store - English language strings
*
* @package cachestore_redis
* @copyright 2013 Adam Durana
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$string['ca_file'] = 'CA file path';
$string['ca_file_help'] = 'Location of Certificate Authority file on local filesystem';
$string['clustermode'] = 'Cluster mode';
$string['clustermode_help'] = 'Enabling cluster mode will run the Redis Cluster function, allowing your server to serve multiple servers to handle concurrent requests simultaneously.';
$string['clustermodeunavailable'] = 'Redis Cluster is currently unavailable. Please ensure that the PHP Redis extension supports Redis Cluster functionality.';
$string['compressor_none'] = 'No compression.';
$string['compressor_php_gzip'] = 'Use gzip compression.';
$string['compressor_php_zstd'] = 'Use Zstandard compression.';
$string['encrypt_connection'] = 'Use TLS encryption.';
$string['encrypt_connection_help'] = 'Use TLS to connect to Redis. Do not use \'tls://\' in the hostname for Redis, use this option instead.';
$string['password'] = 'Password';
$string['password_help'] = 'This sets the password of the Redis server.';
$string['pluginname'] = 'Redis';
$string['prefix'] = 'Key prefix';
$string['prefix_help'] = 'This prefix is used for all key names on the Redis server.
* If you only have one Moodle instance using this server, you can leave this value default.
* Due to key length restrictions, a maximum of 5 characters is permitted.';
$string['prefixinvalid'] = 'Invalid prefix. You can only use a-z A-Z 0-9-_.';
$string['privacy:metadata:redis'] = 'The Redis cachestore plugin stores data briefly as part of its caching functionality. This data is stored on an Redis server where data is regularly removed.';
$string['privacy:metadata:redis:data'] = 'The various data stored in the cache';
$string['serializer_igbinary'] = 'The igbinary serializer.';
$string['serializer_php'] = 'The default PHP serializer.';
$string['server'] = 'Server(s)';
$string['server_help'] = 'Redis server to use for testing.
Some example values:
* testredis.abc.com - To connect to a Redis server by hostname (Port 6379 by default).
* testredis.abc.com:1234 - To connect to a Redis server by hostname with a specific port.
* 1.2.3.4 - To connect to a Redis server by IP address (Port 6379 by default).
* 1.2.3.4:1234 - To connect to a Redis server by IP address with a specific port.
* unix:///var/redis.sock - To connect to a Redis server using a Unix socket.
* /var/redis.sock - To connect to a Redis server using a Unix socket (alternative format).
If cluster mode is enabled, specify servers separated by a new line, for example:<br>
172.23.0.11<br>
172.23.0.12<br>
172.23.0.13<br>
For further information, see <a href="https://redis.io/docs/reference/clients/#accepting-client-connections">Accepting Client Connections</a> and <a href="https://redis.io/resources/clients/#php">Redis PHP clients</a>.';
$string['task_ttl'] = 'Free up memory used by expired entries in Redis caches';
$string['test_clustermode'] = 'Cluster mode';
$string['test_clustermode_desc'] = 'Enable Test in Redis Cluster mode.';
$string['test_password'] = 'Test server password';
$string['test_password_desc'] = 'Redis test server password.';
$string['test_serializer'] = 'Serializer';
$string['test_serializer_desc'] = 'Serializer to use for testing.';
$string['test_server'] = 'Test server';
$string['test_server_desc'] = 'Redis server to use for testing.
Some example values:
* testredis.abc.com - To connect to a Redis server by hostname (Port 6379 by default).
* testredis.abc.com:1234 - To connect to a Redis server by hostname with a specific port.
* 1.2.3.4 - To connect to a Redis server by IP address (Port 6379 by default).
* 1.2.3.4:1234 - To connect to a Redis server by IP address with a specific port.
* unix:///var/redis.sock - To connect to a Redis server using a Unix socket.
* /var/redis.sock - To connect to a Redis server using a Unix socket (alternative format).
If cluster mode is enabled, specify servers separated by a new line, for example:<br>
172.23.0.11<br>
172.23.0.12<br>
172.23.0.13<br>
For further information, see <a href="https://redis.io/docs/reference/clients/#accepting-client-connections">Accepting Client Connections</a> and <a href="https://redis.io/resources/clients/#php">Redis PHP clients</a>.';
$string['test_ttl'] = 'Testing TTL';
$string['test_ttl_desc'] = 'Run the performance test using a cache that requires TTL (slower sets).';
$string['usecompressor'] = 'Use compressor';
$string['usecompressor_help'] = 'Specifies the compressor to use after serializing. It is done at Moodle Cache API level, not at php-redis level.';
$string['useserializer'] = 'Use serializer';
$string['useserializer_help'] = 'Specifies the serializer to use for serializing.
The valid serializers are Redis::SERIALIZER_PHP or Redis::SERIALIZER_IGBINARY.
The latter is supported only when phpredis is configured with --enable-redis-igbinary option and the igbinary extension is loaded.';
+1070
View File
File diff suppressed because it is too large Load Diff
+94
View File
@@ -0,0 +1,94 @@
<?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/>.
/**
* Redis Cache Store - Settings
*
* @package cachestore_redis
* @copyright 2013 Adam Durana
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$settings->add(
new admin_setting_configcheckbox(
name: 'cachestore_redis/test_clustermode',
visiblename: get_string('clustermode', 'cachestore_redis'),
description: cache_helper::is_cluster_available() ?
get_string('clustermode_help', 'cachestore_redis') :
get_string('clustermodeunavailable', 'cachestore_redis'),
defaultsetting: 0,
)
);
$settings->add(
new admin_setting_configtextarea(
name: 'cachestore_redis/test_server',
visiblename: get_string('test_server', 'cachestore_redis'),
description: get_string('test_server_desc', 'cachestore_redis'),
defaultsetting: '',
paramtype: PARAM_TEXT,
)
);
$settings->add(new admin_setting_configcheckbox(
'cachestore_redis/test_encryption',
get_string('encrypt_connection', 'cachestore_redis'),
get_string('encrypt_connection', 'cachestore_redis'),
false));
$settings->add(
new admin_setting_configtext(
'cachestore_redis/test_cafile',
get_string('ca_file', 'cachestore_redis'),
get_string('ca_file', 'cachestore_redis'),
'',
PARAM_TEXT,
16
)
);
$settings->add(
new admin_setting_configpasswordunmask(
'cachestore_redis/test_password',
get_string('test_password', 'cachestore_redis'),
get_string('test_password_desc', 'cachestore_redis'),
''
)
);
if (class_exists('Redis')) { // Only if Redis is available.
$options = array(Redis::SERIALIZER_PHP => get_string('serializer_php', 'cachestore_redis'));
if (defined('Redis::SERIALIZER_IGBINARY')) {
$options[Redis::SERIALIZER_IGBINARY] = get_string('serializer_igbinary', 'cachestore_redis');
}
$settings->add(new admin_setting_configselect(
'cachestore_redis/test_serializer',
get_string('test_serializer', 'cachestore_redis'),
get_string('test_serializer_desc', 'cachestore_redis'),
Redis::SERIALIZER_PHP,
$options
)
);
}
$settings->add(new admin_setting_configcheckbox(
'cachestore_redis/test_ttl',
get_string('test_ttl', 'cachestore_redis'),
get_string('test_ttl_desc', 'cachestore_redis'),
false));
@@ -0,0 +1,193 @@
<?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/>.
namespace cachestore_redis;
use cache_definition;
use cache_store;
use cachestore_redis;
defined('MOODLE_INTERNAL') || die();
require_once(__DIR__ . '/../../../tests/fixtures/stores.php');
require_once(__DIR__ . '/../lib.php');
/**
* Redis cluster test.
*
* If you wish to use these unit tests all you need to do is add the following definition to
* your config.php file:
*
* define('TEST_CACHESTORE_REDIS_SERVERSCLUSTER', 'localhost:7000,localhost:7001');
* define('TEST_CACHESTORE_REDIS_ENCRYPTCLUSTER', true);
* define('TEST_CACHESTORE_REDIS_AUTHCLUSTER', 'foobared');
* define('TEST_CACHESTORE_REDIS_CASCLUSTER', '/cafile/dir/ca.crt');
*
* @package cachestore_redis
* @author Daniel Thee Roperto <daniel.roperto@catalyst-au.net>
* @copyright 2017 Catalyst IT Australia {@link http://www.catalyst-au.net}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
* @coversDefaultClass \cachestore_redis
*/
class cachestore_cluster_redis_test extends \advanced_testcase {
/**
* Create a cache store for testing the Redis cluster.
*
* @param string|null $seed The redis cluster servers.
* @return cachestore_redis The created cache store instance.
*/
public function create_store(?string $seed = null): cachestore_redis {
global $DB;
$definition = cache_definition::load_adhoc(
mode: cache_store::MODE_APPLICATION,
component: 'cachestore_redis',
area: 'phpunit_test',
);
$servers = $seed ?? str_replace(",", "\n", TEST_CACHESTORE_REDIS_SERVERSCLUSTER);
$config = [
'server' => $servers,
'prefix' => $DB->get_prefix(),
'clustermode' => true,
];
if (defined('TEST_CACHESTORE_REDIS_ENCRYPTCLUSTER') && TEST_CACHESTORE_REDIS_ENCRYPTCLUSTER === true) {
$config['encryption'] = true;
}
if (defined('TEST_CACHESTORE_REDIS_AUTHCLUSTER') && TEST_CACHESTORE_REDIS_AUTHCLUSTER) {
$config['password'] = TEST_CACHESTORE_REDIS_AUTHCLUSTER;
}
if (defined('TEST_CACHESTORE_REDIS_CASCLUSTER') && TEST_CACHESTORE_REDIS_CASCLUSTER) {
$config['cafile'] = TEST_CACHESTORE_REDIS_CASCLUSTER;
}
$store = new cachestore_redis('TestCluster', $config);
$store->initialise($definition);
$store->purge();
return $store;
}
/**
* Set up the test environment.
*/
public function setUp(): void {
if (!cachestore_redis::are_requirements_met()) {
$this->markTestSkipped('Could not test cachestore_redis with cluster, missing requirements.');
} else if (!\cache_helper::is_cluster_available()) {
$this->markTestSkipped('Could not test cachestore_redis with cluster, class RedisCluster is not available.');
} else if (!defined('TEST_CACHESTORE_REDIS_SERVERSCLUSTER')) {
$this->markTestSkipped('Could not test cachestore_redis with cluster, missing configuration. ' .
"Example: define('TEST_CACHESTORE_REDIS_SERVERSCLUSTER', " .
"'localhost:7000,localhost:7001,localhost:7002');");
}
}
/**
* Test if the cache store can be created successfully.
*
* @covers ::is_ready
*/
public function test_it_can_create(): void {
$store = $this->create_store();
$this->assertNotNull($store);
$this->assertTrue($store->is_ready());
}
/**
* Test if the cache store trims server names correctly.
*
* @covers ::new_redis
*/
public function test_it_trims_server_names(): void {
// Add a time before and spaces after the first server. Also adds a blank line before second server.
$servers = explode(',', TEST_CACHESTORE_REDIS_SERVERSCLUSTER);
$servers[0] = "\t" . $servers[0] . " \n";
$servers = implode("\n", $servers);
$store = $this->create_store($servers);
$this->assertTrue($store->is_ready());
}
/**
* Test if the cache store can successfully set and get a value.
*
* @covers ::set
* @covers ::get
*/
public function test_it_can_setget(): void {
$store = $this->create_store();
$store->set('the key', 'the value');
$actual = $store->get('the key');
$this->assertSame('the value', $actual);
}
/**
* Test if the cache store can successfully set and get multiple values.
*
* @covers ::set_many
* @covers ::get_many
*/
public function test_it_can_setget_many(): void {
$store = $this->create_store();
// Create values.
$values = [];
$keys = [];
$expected = [];
for ($i = 0; $i < 10; $i++) {
$key = "getkey_{$i}";
$value = "getvalue #{$i}";
$keys[] = $key;
$values[] = [
'key' => $key,
'value' => $value,
];
$expected[$key] = $value;
}
$store->set_many($values);
$actual = $store->get_many($keys);
$this->assertSame($expected, $actual);
}
/**
* Test if the cache store is marked as not ready if it fails to connect.
*
* @covers ::is_ready
*/
public function test_it_is_marked_not_ready_if_failed_to_connect(): void {
global $DB;
$config = [
'server' => "abc:123",
'prefix' => $DB->get_prefix(),
'clustermode' => true,
];
$store = new cachestore_redis('TestCluster', $config);
$debugging = $this->getDebuggingMessages();
// Failed to connect should show a debugging message.
$this->assertCount(1, \phpunit_util::get_debugging_messages() );
$this->assertStringContainsString('Couldn\'t map cluster keyspace using any provided seed', $debugging[0]->message);
$this->resetDebugging();
$this->assertFalse($store->is_ready());
}
}
+154
View File
@@ -0,0 +1,154 @@
<?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/>.
namespace cachestore_redis;
use cache_definition;
use cache_store;
use cachestore_redis;
defined('MOODLE_INTERNAL') || die();
require_once(__DIR__.'/../../../tests/fixtures/stores.php');
require_once(__DIR__.'/../lib.php');
/**
* Redis cache store test.
*
* If you wish to use these unit tests all you need to do is add the following definition to
* your config.php file.
*
* define('TEST_CACHESTORE_REDIS_TESTSERVERS', '127.0.0.1');
*
* @package cachestore_redis
* @copyright (c) 2015 Moodlerooms Inc. (http://www.moodlerooms.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*
* @coversDefaultClass \cachestore_redis
*/
class cachestore_redis_test extends \cachestore_tests {
/** @var cachestore_redis $store Redis Cache Store. */
protected $store;
/**
* Returns the class name.
*
* @return string
*/
protected function get_class_name(): string {
return 'cachestore_redis';
}
public function setUp(): void {
if (!cachestore_redis::are_requirements_met() || !defined('TEST_CACHESTORE_REDIS_TESTSERVERS')) {
$this->markTestSkipped('Could not test cachestore_redis. Requirements are not met.');
}
parent::setUp();
}
protected function tearDown(): void {
parent::tearDown();
if ($this->store instanceof cachestore_redis) {
$this->store->purge();
}
}
/**
* Creates the required cachestore for the tests to run against Redis.
*
* @return cachestore_redis
*/
protected function create_cachestore_redis(): cachestore_redis {
$definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, 'cachestore_redis', 'phpunit_test');
$store = new cachestore_redis('Test', cachestore_redis::unit_test_configuration());
$store->initialise($definition);
$this->store = $store;
$store->purge();
return $store;
}
/**
* Test methods for various operations (set and has) in the cachestore_redis class.
*
* @covers ::set
* @covers ::has
*/
public function test_has(): void {
$store = $this->create_cachestore_redis();
$this->assertTrue($store->set('foo', 'bar'));
$this->assertTrue($store->has('foo'));
$this->assertFalse($store->has('bat'));
}
/**
* Test methods for the 'has_any' operation in the cachestore_redis class.
*
* @covers ::set
* @covers ::has_any
*/
public function test_has_any(): void {
$store = $this->create_cachestore_redis();
$this->assertTrue($store->set('foo', 'bar'));
$this->assertTrue($store->has_any(['bat', 'foo']));
$this->assertFalse($store->has_any(['bat', 'baz']));
}
/**
* PHPUnit test methods for the 'has_all' operation in the cachestore_redis class.
*
* @covers ::set
* @covers ::has_all
*/
public function test_has_all(): void {
$store = $this->create_cachestore_redis();
$this->assertTrue($store->set('foo', 'bar'));
$this->assertTrue($store->set('bat', 'baz'));
$this->assertTrue($store->has_all(['foo', 'bat']));
$this->assertFalse($store->has_all(['foo', 'bat', 'this']));
}
/**
* Test methods for the 'lock' operations in the cachestore_redis class.
*
* @covers ::acquire_lock
* @covers ::check_lock_state
* @covers ::release_lock
*/
public function test_lock(): void {
$store = $this->create_cachestore_redis();
$this->assertTrue($store->acquire_lock('lock', '123'));
$this->assertTrue($store->check_lock_state('lock', '123'));
$this->assertFalse($store->check_lock_state('lock', '321'));
$this->assertNull($store->check_lock_state('notalock', '123'));
$this->assertFalse($store->release_lock('lock', '321'));
$this->assertTrue($store->release_lock('lock', '123'));
}
/**
* Test method to check if the cachestore_redis instance is ready after connecting.
*
* @covers ::is_ready
*/
public function test_it_is_ready_after_connecting(): void {
$store = $this->create_cachestore_redis();
$this::assertTrue($store->is_ready());
}
}
+288
View File
@@ -0,0 +1,288 @@
<?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/>.
namespace cachestore_redis;
use cache_definition;
use cache_store;
use cachestore_redis;
require_once(__DIR__.'/../../../tests/fixtures/stores.php');
require_once(__DIR__.'/../lib.php');
/**
* Redis cache test - compressor settings.
*
* If you wish to use these unit tests all you need to do is add the following definition to
* your config.php file.
*
* define('TEST_CACHESTORE_REDIS_TESTSERVERS', '127.0.0.1');
*
* @package cachestore_redis
* @author Daniel Thee Roperto <daniel.roperto@catalyst-au.net>
* @copyright 2018 Catalyst IT Australia {@link http://www.catalyst-au.net}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class compressor_test extends \advanced_testcase {
/**
* Test set up
*/
public function setUp(): void {
if (!cachestore_redis::are_requirements_met() || !defined('TEST_CACHESTORE_REDIS_TESTSERVERS')) {
$this->markTestSkipped('Could not test cachestore_redis. Requirements are not met.');
}
parent::setUp();
}
/**
* Create a cachestore.
*
* @param int $compressor
* @param int $serializer
* @return cachestore_redis
*/
public function create_store($compressor, $serializer) {
/** @var cache_definition $definition */
$definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, 'cachestore_redis', 'phpunit_test');
$config = cachestore_redis::unit_test_configuration();
$config['compressor'] = $compressor;
$config['serializer'] = $serializer;
$store = new cachestore_redis('Test', $config);
$store->initialise($definition);
return $store;
}
/**
* It misses a value.
*/
public function test_it_can_miss_one(): void {
$store = $this->create_store(cachestore_redis::COMPRESSOR_PHP_GZIP, \Redis::SERIALIZER_PHP);
self::assertFalse($store->get('missme'));
}
/**
* It misses many values.
*/
public function test_it_can_miss_many(): void {
$store = $this->create_store(cachestore_redis::COMPRESSOR_PHP_GZIP, \Redis::SERIALIZER_PHP);
$expected = ['missme' => false, 'missmetoo' => false];
$actual = $store->get_many(array_keys($expected));
self::assertSame($expected, $actual);
}
/**
* It misses some values.
*/
public function test_it_can_miss_some(): void {
$store = $this->create_store(cachestore_redis::COMPRESSOR_PHP_GZIP, \Redis::SERIALIZER_PHP);
$store->set('iamhere', 'youfoundme');
$expected = ['missme' => false, 'missmetoo' => false, 'iamhere' => 'youfoundme'];
$actual = $store->get_many(array_keys($expected));
self::assertSame($expected, $actual);
}
/**
* A provider for test_works_with_different_types
*
* @return array
*/
public function provider_for_test_it_works_with_different_types() {
$object = new \stdClass();
$object->field = 'value';
return [
['string', 'Abc Def'],
['string_empty', ''],
['string_binary', gzencode('some binary data')],
['int', 123],
['int_zero', 0],
['int_negative', -100],
['int_huge', PHP_INT_MAX],
['float', 3.14],
['boolean_true', true],
// Boolean 'false' is not tested as it is not allowed in Moodle.
['array', [1, 'b', 3.4]],
['array_map', ['a' => 'b', 'c' => 'd']],
['object_stdClass', $object],
['null', null],
];
}
/**
* It works with different types.
*
* @dataProvider provider_for_test_it_works_with_different_types
* @param string $key
* @param mixed $value
*/
public function test_it_works_with_different_types($key, $value): void {
$store = $this->create_store(cachestore_redis::COMPRESSOR_PHP_GZIP, \Redis::SERIALIZER_PHP);
$store->set($key, $value);
self::assertEquals($value, $store->get($key), "Failed set/get for: {$key}");
}
/**
* Test it works with different types for many.
*/
public function test_it_works_with_different_types_for_many(): void {
$store = $this->create_store(cachestore_redis::COMPRESSOR_PHP_GZIP, \Redis::SERIALIZER_PHP);
$provider = $this->provider_for_test_it_works_with_different_types();
$keys = [];
$values = [];
$expected = [];
foreach ($provider as $item) {
$keys[] = $item[0];
$values[] = ['key' => $item[0], 'value' => $item[1]];
$expected[$item[0]] = $item[1];
}
$store->set_many($values);
$actual = $store->get_many($keys);
self::assertEquals($expected, $actual);
}
/**
* Provider for set/get combination tests.
*
* @return array
*/
public function provider_for_tests_setget() {
if (!cachestore_redis::are_requirements_met()) {
// Even though we skip all tests in this case, this provider can still show warnings about non-existing class.
return [];
}
$data = [
['none, none',
\Redis::SERIALIZER_NONE, cachestore_redis::COMPRESSOR_NONE,
'value1', 'value2'],
['none, gzip',
\Redis::SERIALIZER_NONE, cachestore_redis::COMPRESSOR_PHP_GZIP,
gzencode('value1'), gzencode('value2')],
['php, none',
\Redis::SERIALIZER_PHP, cachestore_redis::COMPRESSOR_NONE,
serialize('value1'), serialize('value2')],
['php, gzip',
\Redis::SERIALIZER_PHP, cachestore_redis::COMPRESSOR_PHP_GZIP,
gzencode(serialize('value1')), gzencode(serialize('value2'))],
];
if (defined('Redis::SERIALIZER_IGBINARY')) {
$data[] = [
'igbinary, none',
\Redis::SERIALIZER_IGBINARY, cachestore_redis::COMPRESSOR_NONE,
igbinary_serialize('value1'), igbinary_serialize('value2'),
];
$data[] = [
'igbinary, gzip',
\Redis::SERIALIZER_IGBINARY, cachestore_redis::COMPRESSOR_PHP_GZIP,
gzencode(igbinary_serialize('value1')), gzencode(igbinary_serialize('value2')),
];
}
if (extension_loaded('zstd')) {
$data[] = [
'none, zstd',
\Redis::SERIALIZER_NONE, cachestore_redis::COMPRESSOR_PHP_ZSTD,
zstd_compress('value1'), zstd_compress('value2'),
];
$data[] = [
'php, zstd',
\Redis::SERIALIZER_PHP, cachestore_redis::COMPRESSOR_PHP_ZSTD,
zstd_compress(serialize('value1')), zstd_compress(serialize('value2')),
];
if (defined('\Redis::SERIALIZER_IGBINARY')) {
$data[] = [
'igbinary, zstd',
\Redis::SERIALIZER_IGBINARY, cachestore_redis::COMPRESSOR_PHP_ZSTD,
zstd_compress(igbinary_serialize('value1')), zstd_compress(igbinary_serialize('value2')),
];
}
}
return $data;
}
/**
* Test we can use get and set with all combinations.
*
* @dataProvider provider_for_tests_setget
* @param string $name
* @param int $serializer
* @param int $compressor
* @param string $rawexpected1
* @param string $rawexpected2
*/
public function test_it_can_use_getset($name, $serializer, $compressor, $rawexpected1, $rawexpected2): void {
// Create a connection with the desired serialisation.
$store = $this->create_store($compressor, $serializer);
$store->set('key', 'value1');
// Disable compressor and serializer to check the actual stored value.
$rawstore = $this->create_store(cachestore_redis::COMPRESSOR_NONE, \Redis::SERIALIZER_NONE);
$data = $store->get('key');
$rawdata = $rawstore->get('key');
self::assertSame('value1', $data, "Invalid serialisation/unserialisation for: {$name}");
self::assertSame($rawexpected1, $rawdata, "Invalid rawdata for: {$name}");
}
/**
* Test we can use get and set many with all combinations.
*
* @dataProvider provider_for_tests_setget
* @param string $name
* @param int $serializer
* @param int $compressor
* @param string $rawexpected1
* @param string $rawexpected2
*/
public function test_it_can_use_getsetmany($name, $serializer, $compressor, $rawexpected1, $rawexpected2): void {
$many = [
['key' => 'key1', 'value' => 'value1'],
['key' => 'key2', 'value' => 'value2'],
];
$keys = ['key1', 'key2'];
$expectations = ['key1' => 'value1', 'key2' => 'value2'];
$rawexpectations = ['key1' => $rawexpected1, 'key2' => $rawexpected2];
// Create a connection with the desired serialisation.
$store = $this->create_store($compressor, $serializer);
$store->set_many($many);
// Disable compressor and serializer to check the actual stored value.
$rawstore = $this->create_store(cachestore_redis::COMPRESSOR_NONE, \Redis::SERIALIZER_NONE);
$data = $store->get_many($keys);
$rawdata = $rawstore->get_many($keys);
foreach ($keys as $key) {
self::assertSame($expectations[$key],
$data[$key],
"Invalid serialisation/unserialisation for {$key} with serializer {$name}");
self::assertSame($rawexpectations[$key],
$rawdata[$key],
"Invalid rawdata for {$key} with serializer {$name}");
}
}
}
+275
View File
@@ -0,0 +1,275 @@
<?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/>.
namespace cachestore_redis;
use cache_store;
use cache_definition;
use cachestore_redis;
defined('MOODLE_INTERNAL') || die();
require_once(__DIR__.'/../../../tests/fixtures/stores.php');
require_once(__DIR__.'/../lib.php');
/**
* Redis cache test.
*
* If you wish to use these unit tests all you need to do is add the following definition to
* your config.php file.
*
* define('TEST_CACHESTORE_REDIS_TESTSERVERS', '127.0.0.1');
*
* @package cachestore_redis
* @covers \cachestore_redis
* @copyright Copyright (c) 2015 Moodlerooms Inc. (http://www.moodlerooms.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class store_test extends \cachestore_tests {
/**
* @var cachestore_redis
*/
protected $store;
/**
* Returns the MongoDB class name
*
* @return string
*/
protected function get_class_name() {
return 'cachestore_redis';
}
public function setUp(): void {
if (!cachestore_redis::are_requirements_met() || !defined('TEST_CACHESTORE_REDIS_TESTSERVERS')) {
$this->markTestSkipped('Could not test cachestore_redis. Requirements are not met.');
}
parent::setUp();
}
protected function tearDown(): void {
parent::tearDown();
if ($this->store instanceof cachestore_redis) {
$this->store->purge();
}
}
/**
* Creates the required cachestore for the tests to run against Redis.
*
* @param array $extraconfig Extra configuration options for Redis instance, if any
* @param bool $ttl True to use a cache definition with TTL enabled
* @return cachestore_redis
*/
protected function create_cachestore_redis(array $extraconfig = [], bool $ttl = false): cachestore_redis {
if ($ttl) {
/** @var cache_definition $definition */
$definition = cache_definition::load('core/wibble', [
'mode' => 1,
'simplekeys' => true,
'simpledata' => true,
'ttl' => 10,
'component' => 'core',
'area' => 'wibble',
'selectedsharingoption' => 2,
'userinputsharingkey' => '',
'sharingoptions' => 15,
]);
} else {
/** @var cache_definition $definition */
$definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, 'cachestore_redis', 'phpunit_test');
}
$configuration = array_merge(cachestore_redis::unit_test_configuration(), $extraconfig);
$store = new cachestore_redis('Test', $configuration);
$store->initialise($definition);
$this->store = $store;
if (!$store) {
$this->markTestSkipped();
}
return $store;
}
public function test_has(): void {
$store = $this->create_cachestore_redis();
$this->assertTrue($store->set('foo', 'bar'));
$this->assertTrue($store->has('foo'));
$this->assertFalse($store->has('bat'));
}
public function test_has_any(): void {
$store = $this->create_cachestore_redis();
$this->assertTrue($store->set('foo', 'bar'));
$this->assertTrue($store->has_any(array('bat', 'foo')));
$this->assertFalse($store->has_any(array('bat', 'baz')));
}
public function test_has_all(): void {
$store = $this->create_cachestore_redis();
$this->assertTrue($store->set('foo', 'bar'));
$this->assertTrue($store->set('bat', 'baz'));
$this->assertTrue($store->has_all(array('foo', 'bat')));
$this->assertFalse($store->has_all(array('foo', 'bat', 'this')));
}
public function test_lock(): void {
$store = $this->create_cachestore_redis();
$this->assertTrue($store->acquire_lock('lock', '123'));
$this->assertTrue($store->check_lock_state('lock', '123'));
$this->assertFalse($store->check_lock_state('lock', '321'));
$this->assertNull($store->check_lock_state('notalock', '123'));
$this->assertFalse($store->release_lock('lock', '321'));
$this->assertTrue($store->release_lock('lock', '123'));
}
/**
* Checks the timeout features of locking.
*/
public function test_lock_timeouts(): void {
$store = $this->create_cachestore_redis(['lockwait' => 2, 'locktimeout' => 4]);
// User 123 acquires lock.
$this->assertTrue($store->acquire_lock('lock', '123'));
$this->assertTrue($store->check_lock_state('lock', '123'));
// User 456 tries to acquire lock - should fail after about 2 seconds.
$before = microtime(true);
$this->assertFalse($store->acquire_lock('lock', '456'));
$after = microtime(true);
$this->assertEqualsWithDelta(2, $after - $before, 0.5);
// Wait another 2 seconds and then it should be able to get the lock because of timeout.
sleep(2);
$this->assertTrue($store->acquire_lock('lock', '456'));
$this->assertTrue($store->check_lock_state('lock', '456'));
// The first user doesn't have the lock any more.
$this->assertFalse($store->check_lock_state('lock', '123'));
// Releasing the lock from the first user does nothing.
$this->assertFalse($store->release_lock('lock', '123'));
$this->assertTrue($store->check_lock_state('lock', '456'));
$this->assertTrue($store->release_lock('lock', '456'));
}
/**
* Tests the shutdown function that is supposed to free any remaining locks.
*/
public function test_lock_shutdown(): void {
$store = $this->create_cachestore_redis();
try {
$this->assertTrue($store->acquire_lock('a', '123'));
$this->assertTrue($store->acquire_lock('b', '123'));
$this->assertTrue($store->acquire_lock('c', '123'));
$this->assertTrue($store->check_lock_state('a', '123'));
$this->assertTrue($store->check_lock_state('b', '123'));
$this->assertTrue($store->check_lock_state('c', '123'));
} finally {
$store->shutdown_release_locks();
$this->assertDebuggingCalledCount(3);
}
$this->assertNull($store->check_lock_state('a', '123'));
$this->assertNull($store->check_lock_state('b', '123'));
$this->assertNull($store->check_lock_state('c', '123'));
}
/**
* Tests the get_last_io_bytes function when not using compression (just returns unknown).
*/
public function test_get_last_io_bytes(): void {
$store = $this->create_cachestore_redis();
$store->set('foo', [1, 2, 3, 4]);
$this->assertEquals(\cache_store::IO_BYTES_NOT_SUPPORTED, $store->get_last_io_bytes());
$store->get('foo');
$this->assertEquals(\cache_store::IO_BYTES_NOT_SUPPORTED, $store->get_last_io_bytes());
}
/**
* Tests the get_last_io_bytes byte count when using compression.
*/
public function test_get_last_io_bytes_compressed(): void {
$store = $this->create_cachestore_redis(['compressor' => cachestore_redis::COMPRESSOR_PHP_GZIP]);
$alphabet = 'abcdefghijklmnopqrstuvwxyz';
$store->set('small', $alphabet);
$store->set('large', str_repeat($alphabet, 10));
$store->get('small');
// Interesting 'compression'.
$this->assertEquals(54, $store->get_last_io_bytes());
$store->get('large');
// This one is actually smaller than uncompressed value!
$this->assertEquals(57, $store->get_last_io_bytes());
$store->get_many(['small', 'large']);
$this->assertEquals(111, $store->get_last_io_bytes());
$store->set('small', str_repeat($alphabet, 2));
$this->assertEquals(56, $store->get_last_io_bytes());
$store->set_many([
['key' => 'small', 'value' => $alphabet],
['key' => 'large', 'value' => str_repeat($alphabet, 10)]
]);
$this->assertEquals(111, $store->get_last_io_bytes());
}
/**
* Data provider for whether cache uses TTL or not.
*
* @return array Array with true and false options
*/
public static function ttl_or_not(): array {
return [
[false],
[true]
];
}
/**
* Tests the delete_many function.
*
* The behaviour is different with TTL enabled so we need to test with that kind of definition
* as well as a 'normal' one.
*
* @param bool $ttl True to test using a TTL definition
* @dataProvider ttl_or_not
*/
public function test_delete_many(bool $ttl): void {
$store = $this->create_cachestore_redis([], $ttl);
// Check it works to delete selected items.
$store->set('foo', 'frog');
$store->set('bar', 'amphibian');
$store->set('hmm', 'undead');
$this->store->delete_many(['foo', 'bar']);
$this->assertFalse($store->get('foo'));
$this->assertFalse($store->get('bar'));
$this->assertEquals('undead', $store->get('hmm'));
// If called with no keys it should do nothing.
$store->delete_many([]);
$this->assertEquals('undead', $store->get('hmm'));
}
}
+122
View File
@@ -0,0 +1,122 @@
<?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/>.
namespace cachestore_redis;
/**
* TTL support test for Redis cache.
*
* If you wish to use these unit tests all you need to do is add the following definition to
* your config.php file.
*
* define('TEST_CACHESTORE_REDIS_TESTSERVERS', '127.0.0.1');
*
* @package cachestore_redis
* @copyright 2021 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \cachestore_redis
*/
final class ttl_test extends \advanced_testcase {
/** @var \cachestore_redis|null Cache store */
protected $store = null;
public function setUp(): void {
// Make sure cachestore_redis is available.
require_once(__DIR__ . '/../lib.php');
if (!\cachestore_redis::are_requirements_met() || !defined('TEST_CACHESTORE_REDIS_TESTSERVERS')) {
$this->markTestSkipped('Could not test cachestore_redis. Requirements are not met.');
}
// Set up a Redis store with a fake definition that has TTL set to 10 seconds.
$definition = \cache_definition::load('core/wibble', [
'mode' => 1,
'simplekeys' => true,
'simpledata' => true,
'ttl' => 10,
'component' => 'core',
'area' => 'wibble',
'selectedsharingoption' => 2,
'userinputsharingkey' => '',
'sharingoptions' => 15,
]);
$this->store = new \cachestore_redis('Test', \cachestore_redis::unit_test_configuration());
$this->store->initialise($definition);
parent::setUp();
}
protected function tearDown(): void {
parent::tearDown();
if ($this->store instanceof \cachestore_redis) {
$this->store->purge();
}
}
/**
* Test calling set_many with an empty array
*
* Trivial test to ensure we don't trigger an ArgumentCountError when calling zAdd with invalid parameters
*/
public function test_set_many_empty(): void {
$this->assertEquals(0, $this->store->set_many([]));
}
/**
* Tests expiring data.
*/
public function test_expire_ttl(): void {
$this->resetAfterTest();
// Set some data at time 100.
\cachestore_redis::set_phpunit_time(100);
$this->store->set('a', 1);
$this->store->set('b', 2);
$this->store->set_many([['key' => 'c', 'value' => 3], ['key' => 'd', 'value' => 4],
['key' => 'e', 'value' => 5], ['key' => 'f', 'value' => 6],
['key' => 'g', 'value' => 7], ['key' => 'h', 'value' => 8]]);
// Set some other data at time 110, including some of the existing values. Whether the
// value changes or not, its TTL should update.
\cachestore_redis::set_phpunit_time(110);
$this->store->set('b', 2);
$this->store->set_many([['key' => 'c', 'value' => 99], ['key' => 'd', 'value' => 4]]);
// Check all the data is still set.
$this->assertEqualsCanonicalizing(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'],
$this->store->find_all());
// Delete some data (to check deletion doesn't confuse expiry).
$this->store->delete('f');
$this->store->delete_many(['g', 'h']);
// Set time to 115 and expire data.
\cachestore_redis::set_phpunit_time(115);
$info = $this->store->expire_ttl();
// We are expecting keys a and e to be deleted.
$this->assertEquals(2, $info['keys']);
$this->assertEquals(1, $info['batches']);
// Check the keys are as expected.
$this->assertEqualsCanonicalizing(['b', 'c', 'd'], $this->store->find_all());
// Might as well check the values of the surviving keys.
$this->assertEquals(2, $this->store->get('b'));
$this->assertEquals(99, $this->store->get('c'));
$this->assertEquals(4, $this->store->get('d'));
}
}
+30
View File
@@ -0,0 +1,30 @@
<?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/>.
/**
* Redis Cache Store - Version information
*
* @package cachestore_redis
* @copyright 2013 Adam Durana
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2024042200;
$plugin->requires = 2024041600; // Requires this Moodle version.
$plugin->maturity = MATURITY_STABLE;
$plugin->component = 'cachestore_redis';
+107
View File
@@ -0,0 +1,107 @@
<?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/>.
/**
* Privacy Subsystem implementation for cachestore_session.
*
* @package cachestore_session
* @category privacy
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace cachestore_session\privacy;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\approved_userlist;
use core_privacy\local\request\contextlist;
use core_privacy\local\request\userlist;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for cachestore_session.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements
\core_privacy\local\metadata\provider,
\core_privacy\local\request\plugin\provider,
\core_privacy\local\request\core_userlist_provider {
/**
* Returns meta data about this system.
*
* @param collection $collection The initialised collection to add items to.
* @return collection A listing of user data stored through this system.
*/
public static function get_metadata(collection $collection): collection {
$collection->add_subsystem_link('core_user', [], 'privacy:metadata:core_user');
return $collection;
}
/**
* Get the list of contexts that contain user information for the specified user.
*
* @param int $userid The user to search.
* @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin.
*/
public static function get_contexts_for_userid(int $userid): contextlist {
return new contextlist();
}
/**
* Get the list of users who have data within a context.
*
* @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination.
*/
public static function get_users_in_context(userlist $userlist) {
}
/**
* Export all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts to export information for.
*/
public static function export_user_data(approved_contextlist $contextlist) {
}
/**
* Delete all use data which matches the specified deletion_criteria.
*
* @param \context $context A user context.
*/
public static function delete_data_for_all_users_in_context(\context $context) {
}
/**
* Delete all user data for the specified user, in the specified contexts.
*
* @param approved_contextlist $contextlist The approved contexts and user information to delete information for.
*/
public static function delete_data_for_user(approved_contextlist $contextlist) {
}
/**
* Delete multiple users within a single context.
*
* @param approved_userlist $userlist The approved context and user information to delete information for.
*/
public static function delete_data_for_users(approved_userlist $userlist) {
}
}
+32
View File
@@ -0,0 +1,32 @@
<?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/>.
/**
* The library file for the session cache store.
*
* This file is part of the session cache store, it contains the API for interacting with an instance of the store.
* This is used as a default cache store within the Cache API. It should never be deleted.
*
* @package cachestore_session
* @category cache
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$string['pluginname'] = 'Session cache';
$string['privacy:metadata:core_user'] = 'The Session cachestore plugin stores data briefly as part of its caching functionality. This data is stored in the short-lived user session.';
+595
View File
@@ -0,0 +1,595 @@
<?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/>.
/**
* The library file for the session cache store.
*
* This file is part of the session cache store, it contains the API for interacting with an instance of the store.
* This is used as a default cache store within the Cache API. It should never be deleted.
*
* @package cachestore_session
* @category cache
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* The session data store class.
*
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class session_data_store extends cache_store {
/**
* Used for the actual storage.
* @var array
*/
private static $sessionstore = null;
/**
* Returns a static store by reference... REFERENCE SUPER IMPORTANT.
*
* @param string $id
* @return array
*/
protected static function &register_store_id($id) {
if (is_null(self::$sessionstore)) {
global $SESSION;
if (!isset($SESSION->cachestore_session)) {
$SESSION->cachestore_session = array();
}
self::$sessionstore =& $SESSION->cachestore_session;
}
if (!array_key_exists($id, self::$sessionstore)) {
self::$sessionstore[$id] = array();
}
return self::$sessionstore[$id];
}
/**
* Flushes the data belong to the given store id.
* @param string $id
*/
protected static function flush_store_by_id($id) {
unset(self::$sessionstore[$id]);
self::$sessionstore[$id] = array();
}
/**
* Flushes the store of all data.
*/
protected static function flush_store() {
$ids = array_keys(self::$sessionstore);
unset(self::$sessionstore);
self::$sessionstore = array();
foreach ($ids as $id) {
self::$sessionstore[$id] = array();
}
}
}
/**
* The Session store class.
*
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cachestore_session extends session_data_store implements cache_is_key_aware, cache_is_searchable {
/**
* The name of the store
* @var store
*/
protected $name;
/**
* The store id (should be unique)
* @var string
*/
protected $storeid;
/**
* The store we use for data.
* @var array
*/
protected $store;
/**
* The ttl if there is one. Hopefully not.
* @var int
*/
protected $ttl = 0;
/**
* The maximum size for the store, or false if there isn't one.
* @var bool|int
*/
protected $maxsize = false;
/**
* The number of items currently being stored.
* @var int
*/
protected $storecount = 0;
/**
* Constructs the store instance.
*
* Noting that this function is not an initialisation. It is used to prepare the store for use.
* The store will be initialised when required and will be provided with a cache_definition at that time.
*
* @param string $name
* @param array $configuration
*/
public function __construct($name, array $configuration = array()) {
$this->name = $name;
}
/**
* Returns the supported features as a combined int.
*
* @param array $configuration
* @return int
*/
public static function get_supported_features(array $configuration = array()) {
return self::SUPPORTS_DATA_GUARANTEE +
self::SUPPORTS_NATIVE_TTL +
self::IS_SEARCHABLE;
}
/**
* Returns false as this store does not support multiple identifiers.
* (This optional function is a performance optimisation; it must be
* consistent with the value from get_supported_features.)
*
* @return bool False
*/
public function supports_multiple_identifiers() {
return false;
}
/**
* Returns the supported modes as a combined int.
*
* @param array $configuration
* @return int
*/
public static function get_supported_modes(array $configuration = array()) {
return self::MODE_SESSION;
}
/**
* Returns true if the store requirements are met.
*
* @return bool
*/
public static function are_requirements_met() {
return true;
}
/**
* Returns true if the given mode is supported by this store.
*
* @param int $mode One of cache_store::MODE_*
* @return bool
*/
public static function is_supported_mode($mode) {
return ($mode === self::MODE_SESSION);
}
/**
* Initialises the cache.
*
* Once this has been done the cache is all set to be used.
*
* @param cache_definition $definition
*/
public function initialise(cache_definition $definition) {
$this->storeid = $definition->generate_definition_hash();
$this->store = &self::register_store_id($this->name.'-'.$definition->get_id());
$this->ttl = $definition->get_ttl();
$maxsize = $definition->get_maxsize();
if ($maxsize !== null) {
// Must be a positive int.
$this->maxsize = abs((int)$maxsize);
$this->storecount = count($this->store);
}
$this->check_ttl();
}
/**
* Returns true once this instance has been initialised.
*
* @return bool
*/
public function is_initialised() {
return (is_array($this->store));
}
/**
* Retrieves an item from the cache store given its key.
*
* @param string $key The key to retrieve
* @return mixed The data that was associated with the key, or false if the key did not exist.
*/
public function get($key) {
if (isset($this->store[$key])) {
if ($this->ttl == 0) {
$value = $this->store[$key][0];
if ($this->maxsize !== false) {
// Make sure the element is now in the end of array.
$this->set($key, $value);
}
return $value;
} else if ($this->store[$key][1] >= (cache::now() - $this->ttl)) {
return $this->store[$key][0];
} else {
// Element is present but has expired.
$this->check_ttl();
}
}
return false;
}
/**
* Retrieves several items from the cache store in a single transaction.
*
* If not all of the items are available in the cache then the data value for those that are missing will be set to false.
*
* @param array $keys The array of keys to retrieve
* @return array An array of items from the cache. There will be an item for each key, those that were not in the store will
* be set to false.
*/
public function get_many($keys) {
$return = array();
$maxtime = 0;
if ($this->ttl != 0) {
$maxtime = cache::now() - $this->ttl;
}
$hasexpiredelements = false;
foreach ($keys as $key) {
$return[$key] = false;
if (isset($this->store[$key])) {
if ($this->ttl == 0) {
$return[$key] = $this->store[$key][0];
if ($this->maxsize !== false) {
// Make sure the element is now in the end of array.
$this->set($key, $return[$key], false);
}
} else if ($this->store[$key][1] >= $maxtime) {
$return[$key] = $this->store[$key][0];
} else {
$hasexpiredelements = true;
}
}
}
if ($hasexpiredelements) {
// There are some elements that are present but have expired.
$this->check_ttl();
}
return $return;
}
/**
* Sets an item in the cache given its key and data value.
*
* @param string $key The key to use.
* @param mixed $data The data to set.
* @param bool $testmaxsize If set to true then we test the maxsize arg and reduce if required. If this is set to false you will
* need to perform these checks yourself. This allows for bulk set's to be performed and maxsize tests performed once.
* @return bool True if the operation was a success false otherwise.
*/
public function set($key, $data, $testmaxsize = true) {
$testmaxsize = ($testmaxsize && $this->maxsize !== false);
$increment = $this->maxsize !== false && !isset($this->store[$key]);
if (($this->maxsize !== false && !$increment) || $this->ttl != 0) {
// Make sure the element is added to the end of $this->store array.
unset($this->store[$key]);
}
if ($this->ttl === 0) {
$this->store[$key] = array($data, 0);
} else {
$this->store[$key] = array($data, cache::now());
}
if ($increment) {
$this->storecount++;
}
if ($testmaxsize && $this->storecount > $this->maxsize) {
$this->reduce_for_maxsize();
}
return true;
}
/**
* Sets many items in the cache in a single transaction.
*
* @param array $keyvaluearray An array of key value pairs. Each item in the array will be an associative array with two
* keys, 'key' and 'value'.
* @return int The number of items successfully set. It is up to the developer to check this matches the number of items
* sent ... if they care that is.
*/
public function set_many(array $keyvaluearray) {
$count = 0;
$increment = 0;
foreach ($keyvaluearray as $pair) {
$key = $pair['key'];
$data = $pair['value'];
$count++;
if ($this->maxsize !== false || $this->ttl !== 0) {
// Make sure the element is added to the end of $this->store array.
$this->delete($key);
$increment++;
} else if (!isset($this->store[$key])) {
$increment++;
}
if ($this->ttl === 0) {
$this->store[$key] = array($data, 0);
} else {
$this->store[$key] = array($data, cache::now());
}
}
if ($this->maxsize !== false) {
$this->storecount += $increment;
if ($this->storecount > $this->maxsize) {
$this->reduce_for_maxsize();
}
}
return $count;
}
/**
* Checks if the store has a record for the given key and returns true if so.
*
* @param string $key
* @return bool
*/
public function has($key) {
if (isset($this->store[$key])) {
if ($this->ttl == 0) {
return true;
} else if ($this->store[$key][1] >= (cache::now() - $this->ttl)) {
return true;
}
}
return false;
}
/**
* Returns true if the store contains records for all of the given keys.
*
* @param array $keys
* @return bool
*/
public function has_all(array $keys) {
$maxtime = 0;
if ($this->ttl != 0) {
$maxtime = cache::now() - $this->ttl;
}
foreach ($keys as $key) {
if (!isset($this->store[$key])) {
return false;
}
if ($this->ttl != 0 && $this->store[$key][1] < $maxtime) {
return false;
}
}
return true;
}
/**
* Returns true if the store contains records for any of the given keys.
*
* @param array $keys
* @return bool
*/
public function has_any(array $keys) {
$maxtime = 0;
if ($this->ttl != 0) {
$maxtime = cache::now() - $this->ttl;
}
foreach ($keys as $key) {
if (isset($this->store[$key]) && ($this->ttl == 0 || $this->store[$key][1] >= $maxtime)) {
return true;
}
}
return false;
}
/**
* Deletes an item from the cache store.
*
* @param string $key The key to delete.
* @return bool Returns true if the operation was a success, false otherwise.
*/
public function delete($key) {
if (!isset($this->store[$key])) {
return false;
}
unset($this->store[$key]);
if ($this->maxsize !== false) {
$this->storecount--;
}
return true;
}
/**
* Deletes several keys from the cache in a single action.
*
* @param array $keys The keys to delete
* @return int The number of items successfully deleted.
*/
public function delete_many(array $keys) {
// The number of items that have actually being removed.
$reduction = 0;
foreach ($keys as $key) {
if (isset($this->store[$key])) {
$reduction++;
}
unset($this->store[$key]);
}
if ($this->maxsize !== false) {
$this->storecount -= $reduction;
}
return $reduction;
}
/**
* Purges the cache deleting all items within it.
*
* @return boolean True on success. False otherwise.
*/
public function purge() {
$this->store = array();
// Don't worry about checking if we're using max size just set it as thats as fast as the check.
$this->storecount = 0;
return true;
}
/**
* Reduces the size of the array if maxsize has been hit.
*
* This function reduces the size of the store reducing it by 10% of its maxsize.
* It removes the oldest items in the store when doing this.
* The reason it does this an doesn't use a least recently used system is purely the overhead such a system
* requires. The current approach is focused on speed, MUC already adds enough overhead to static/session caches
* and avoiding more is of benefit.
*
* @return int
*/
protected function reduce_for_maxsize() {
$diff = $this->storecount - $this->maxsize;
if ($diff < 1) {
return 0;
}
// Reduce it by an extra 10% to avoid calling this repetitively if we are in a loop.
$diff += floor($this->maxsize / 10);
$this->store = array_slice($this->store, $diff, null, true);
$this->storecount -= $diff;
return $diff;
}
/**
* Returns true if the user can add an instance of the store plugin.
*
* @return bool
*/
public static function can_add_instance() {
return false;
}
/**
* Performs any necessary clean up when the store instance is being deleted.
*/
public function instance_deleted() {
$this->purge();
}
/**
* Generates an instance of the cache store that can be used for testing.
*
* @param cache_definition $definition
* @return cachestore_session
*/
public static function initialise_test_instance(cache_definition $definition) {
// Do something here perhaps.
$cache = new cachestore_session('Session test');
$cache->initialise($definition);
return $cache;
}
/**
* Generates the appropriate configuration required for unit testing.
*
* @return array Array of unit test configuration data to be used by initialise().
*/
public static function unit_test_configuration() {
return array();
}
/**
* Returns the name of this instance.
* @return string
*/
public function my_name() {
return $this->name;
}
/**
* Removes expired elements.
* @return int number of removed elements
*/
protected function check_ttl() {
if ($this->ttl === 0) {
return 0;
}
$maxtime = cache::now() - $this->ttl;
$count = 0;
for ($value = reset($this->store); $value !== false; $value = next($this->store)) {
if ($value[1] >= $maxtime) {
// We know that elements are sorted by ttl so no need to continue.
break;
}
$count++;
}
if ($count) {
// Remove first $count elements as they are expired.
$this->store = array_slice($this->store, $count, null, true);
if ($this->maxsize !== false) {
$this->storecount -= $count;
}
}
return $count;
}
/**
* Finds all of the keys being stored in the cache store instance.
*
* @return array
*/
public function find_all() {
$this->check_ttl();
return array_keys($this->store);
}
/**
* Finds all of the keys whose keys start with the given prefix.
*
* @param string $prefix
* @return array An array of keys.
*/
public function find_by_prefix($prefix) {
$return = array();
foreach ($this->find_all() as $key) {
if (strpos($key, $prefix) === 0) {
$return[] = $key;
}
}
return $return;
}
/**
* This store supports native TTL handling.
* @return bool
*/
public function store_supports_native_ttl() {
return true;
}
}
+195
View File
@@ -0,0 +1,195 @@
<?php
// This session 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/>.
namespace cachestore_session;
use cache_store;
defined('MOODLE_INTERNAL') || die();
// Include the necessary evils.
global $CFG;
require_once($CFG->dirroot.'/cache/tests/fixtures/stores.php');
require_once($CFG->dirroot.'/cache/stores/session/lib.php');
/**
* Session unit test class.
*
* @package cachestore_session
* @copyright 2013 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class store_test extends \cachestore_tests {
/**
* Returns the session class name
* @return string
*/
protected function get_class_name() {
return 'cachestore_session';
}
/**
* Test the maxsize option.
*/
public function test_maxsize(): void {
$config = \cache_config_testing::instance();
$config->phpunit_add_definition('phpunit/one', array(
'mode' => cache_store::MODE_SESSION,
'component' => 'phpunit',
'area' => 'one',
'maxsize' => 3
));
$config->phpunit_add_definition('phpunit/two', array(
'mode' => cache_store::MODE_SESSION,
'component' => 'phpunit',
'area' => 'two',
'maxsize' => 3
));
$cacheone = \cache::make('phpunit', 'one');
$this->assertTrue($cacheone->set('key1', 'value1'));
$this->assertTrue($cacheone->set('key2', 'value2'));
$this->assertTrue($cacheone->set('key3', 'value3'));
$this->assertTrue($cacheone->has('key1'));
$this->assertTrue($cacheone->has('key2'));
$this->assertTrue($cacheone->has('key3'));
$this->assertTrue($cacheone->set('key4', 'value4'));
$this->assertTrue($cacheone->set('key5', 'value5'));
$this->assertFalse($cacheone->has('key1'));
$this->assertFalse($cacheone->has('key2'));
$this->assertTrue($cacheone->has('key3'));
$this->assertTrue($cacheone->has('key4'));
$this->assertTrue($cacheone->has('key5'));
$this->assertFalse($cacheone->get('key1'));
$this->assertFalse($cacheone->get('key2'));
$this->assertEquals('value3', $cacheone->get('key3'));
$this->assertEquals('value4', $cacheone->get('key4'));
$this->assertEquals('value5', $cacheone->get('key5'));
// Test adding one more.
$this->assertTrue($cacheone->set('key6', 'value6'));
$this->assertFalse($cacheone->get('key3'));
// Test reducing and then adding to make sure we don't lost one.
$this->assertTrue($cacheone->delete('key6'));
$this->assertTrue($cacheone->set('key7', 'value7'));
$this->assertEquals('value4', $cacheone->get('key4'));
// Set the same key three times to make sure it doesn't count overrides.
for ($i = 0; $i < 3; $i++) {
$this->assertTrue($cacheone->set('key8', 'value8'));
}
$this->assertEquals('value7', $cacheone->get('key7'), 'Overrides are incorrectly incrementing size');
// Test adding many.
$this->assertEquals(3, $cacheone->set_many(array(
'keyA' => 'valueA',
'keyB' => 'valueB',
'keyC' => 'valueC'
)));
$this->assertEquals(array(
'key4' => false,
'key5' => false,
'key6' => false,
'key7' => false,
'keyA' => 'valueA',
'keyB' => 'valueB',
'keyC' => 'valueC'
), $cacheone->get_many(array(
'key4', 'key5', 'key6', 'key7', 'keyA', 'keyB', 'keyC'
)));
$cachetwo = \cache::make('phpunit', 'two');
// Test adding many.
$this->assertEquals(3, $cacheone->set_many(array(
'keyA' => 'valueA',
'keyB' => 'valueB',
'keyC' => 'valueC'
)));
$this->assertEquals(3, $cachetwo->set_many(array(
'key1' => 'value1',
'key2' => 'value2',
'key3' => 'value3'
)));
$this->assertEquals(array(
'keyA' => 'valueA',
'keyB' => 'valueB',
'keyC' => 'valueC'
), $cacheone->get_many(array(
'keyA', 'keyB', 'keyC'
)));
$this->assertEquals(array(
'key1' => 'value1',
'key2' => 'value2',
'key3' => 'value3'
), $cachetwo->get_many(array(
'key1', 'key2', 'key3'
)));
// Test that that cache deletes element that was least recently accessed.
$this->assertEquals('valueA', $cacheone->get('keyA'));
$cacheone->set('keyD', 'valueD');
$this->assertEquals('valueA', $cacheone->get('keyA'));
$this->assertFalse($cacheone->get('keyB'));
$this->assertEquals(array('keyD' => 'valueD', 'keyC' => 'valueC'), $cacheone->get_many(array('keyD', 'keyC')));
$cacheone->set('keyE', 'valueE');
$this->assertFalse($cacheone->get('keyB'));
$this->assertFalse($cacheone->get('keyA'));
$this->assertEquals(array('keyA' => false, 'keyE' => 'valueE', 'keyD' => 'valueD', 'keyC' => 'valueC'),
$cacheone->get_many(array('keyA', 'keyE', 'keyD', 'keyC')));
// Overwrite keyE (moves it to the end of array), and set keyF.
$cacheone->set_many(array('keyE' => 'valueE', 'keyF' => 'valueF'));
$this->assertEquals(array('keyC' => 'valueC', 'keyE' => 'valueE', 'keyD' => false, 'keyF' => 'valueF'),
$cacheone->get_many(array('keyC', 'keyE', 'keyD', 'keyF')));
}
public function test_ttl(): void {
$config = \cache_config_testing::instance();
$config->phpunit_add_definition('phpunit/three', array(
'mode' => cache_store::MODE_SESSION,
'component' => 'phpunit',
'area' => 'three',
'maxsize' => 3,
'ttl' => 3
));
$cachethree = \cache::make('phpunit', 'three');
// Make sure that when cache with ttl is full the elements that were added first are deleted first regardless of access time.
$cachethree->set('key1', 'value1');
$cachethree->set('key2', 'value2');
$cachethree->set('key3', 'value3');
$cachethree->set('key4', 'value4');
$this->assertFalse($cachethree->get('key1'));
$this->assertEquals('value4', $cachethree->get('key4'));
$cachethree->set('key5', 'value5');
$this->assertFalse($cachethree->get('key2'));
$this->assertEquals('value4', $cachethree->get('key4'));
$cachethree->set_many(array('key6' => 'value6', 'key7' => 'value7'));
$this->assertEquals(array('key3' => false, 'key4' => false, 'key5' => 'value5', 'key6' => 'value6', 'key7' => 'value7'),
$cachethree->get_many(array('key3', 'key4', 'key5', 'key6', 'key7')));
}
}
+32
View File
@@ -0,0 +1,32 @@
<?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 session store version information.
*
* This is used as a default cache store within the Cache API. It should never be deleted.
*
* @package cachestore_session
* @category cache
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
$plugin->version = 2024042200; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2024041600; // Requires this Moodle version.
$plugin->component = 'cachestore_session'; // Full name of the plugin.
+46
View File
@@ -0,0 +1,46 @@
<?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/>.
/**
* Privacy Subsystem implementation for cachestore_static.
*
* @package cachestore_static
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace cachestore_static\privacy;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy Subsystem for cachestore_static implementing null_provider.
*
* @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider implements \core_privacy\local\metadata\null_provider {
/**
* Get the language string identifier with the component's language
* file to explain why this plugin stores no data.
*
* @return string
*/
public static function get_reason(): string {
return 'privacy:metadata';
}
}
+32
View File
@@ -0,0 +1,32 @@
<?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/>.
/**
* The library file for the static cache store.
*
* This file is part of the static cache store, it contains the API for interacting with an instance of the store.
* This is used as a default cache store within the Cache API. It should never be deleted.
*
* @package cachestore_static
* @category cache
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
$string['pluginname'] = 'Static request cache';
$string['privacy:metadata'] = 'The Static request cachestore plugin stores some data, but this is only present for the lifetime of a single HTTP request.';
+578
View File
@@ -0,0 +1,578 @@
<?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/>.
/**
* The library file for the static cache store.
*
* This file is part of the static cache store, it contains the API for interacting with an instance of the store.
* This is used as a default cache store within the Cache API. It should never be deleted.
*
* @package cachestore_static
* @category cache
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
/**
* The static data store class
*
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class static_data_store extends cache_store {
/**
* An array for storage.
* @var array
*/
private static $staticstore = array();
/**
* Returns a static store by reference... REFERENCE SUPER IMPORTANT.
*
* @param string $id
* @return array
*/
protected static function &register_store_id($id) {
if (!array_key_exists($id, self::$staticstore)) {
self::$staticstore[$id] = array();
}
return self::$staticstore[$id];
}
/**
* Flushes the store of all values for belonging to the store with the given id.
* @param string $id
*/
protected static function flush_store_by_id($id) {
unset(self::$staticstore[$id]);
self::$staticstore[$id] = array();
}
/**
* Flushes all of the values from all stores.
*
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
protected static function flush_store() {
$ids = array_keys(self::$staticstore);
unset(self::$staticstore);
self::$staticstore = array();
foreach ($ids as $id) {
self::$staticstore[$id] = array();
}
}
}
/**
* The static store class.
*
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cachestore_static extends static_data_store implements cache_is_key_aware, cache_is_searchable {
/**
* The name of the store
* @var store
*/
protected $name;
/**
* The store id (should be unique)
* @var string
*/
protected $storeid;
/**
* The store we use for data.
* @var array
*/
protected $store;
/**
* The maximum size for the store, or false if there isn't one.
* @var bool
*/
protected $maxsize = false;
/**
* Where this cache uses simpledata and we don't need to serialize it.
* @var bool
*/
protected $simpledata = false;
/**
* The number of items currently being stored.
* @var int
*/
protected $storecount = 0;
/**
* igbinary extension available.
* @var bool
*/
protected $igbinaryfound = false;
/**
* Constructs the store instance.
*
* Noting that this function is not an initialisation. It is used to prepare the store for use.
* The store will be initialised when required and will be provided with a cache_definition at that time.
*
* @param string $name
* @param array $configuration
*/
public function __construct($name, array $configuration = array()) {
$this->name = $name;
}
/**
* Returns the supported features as a combined int.
*
* @param array $configuration
* @return int
*/
public static function get_supported_features(array $configuration = array()) {
return self::SUPPORTS_DATA_GUARANTEE +
self::SUPPORTS_NATIVE_TTL +
self::IS_SEARCHABLE +
self::SUPPORTS_MULTIPLE_IDENTIFIERS +
self::DEREFERENCES_OBJECTS;
}
/**
* Returns true as this store does support multiple identifiers.
* (This optional function is a performance optimisation; it must be
* consistent with the value from get_supported_features.)
*
* @return bool true
*/
public function supports_multiple_identifiers() {
return true;
}
/**
* Returns the supported modes as a combined int.
*
* @param array $configuration
* @return int
*/
public static function get_supported_modes(array $configuration = array()) {
return self::MODE_REQUEST;
}
/**
* Returns true if the store requirements are met.
*
* @return bool
*/
public static function are_requirements_met() {
return true;
}
/**
* Returns true if the given mode is supported by this store.
*
* @param int $mode One of cache_store::MODE_*
* @return bool
*/
public static function is_supported_mode($mode) {
return ($mode === self::MODE_REQUEST);
}
/**
* Initialises the cache.
*
* Once this has been done the cache is all set to be used.
*
* @param cache_definition $definition
*/
public function initialise(cache_definition $definition) {
$keyarray = $definition->generate_multi_key_parts();
$this->storeid = $keyarray['mode'].'/'.$keyarray['component'].'/'.$keyarray['area'].'/'.$keyarray['siteidentifier'];
$this->store = &self::register_store_id($this->storeid);
$maxsize = $definition->get_maxsize();
$this->simpledata = $definition->uses_simple_data();
$this->igbinaryfound = extension_loaded('igbinary');
if ($maxsize !== null) {
// Must be a positive int.
$this->maxsize = abs((int)$maxsize);
$this->storecount = count($this->store);
}
}
/**
* Returns true once this instance has been initialised.
*
* @return bool
*/
public function is_initialised() {
return (is_array($this->store));
}
/**
* Uses igbinary serializer if igbinary extension is loaded.
* Fallback to PHP serializer.
*
* @param mixed $data
* The value to be serialized.
* @return string a string containing a byte-stream representation of
* value that can be stored anywhere.
*/
protected function serialize($data) {
if ($this->igbinaryfound) {
return igbinary_serialize($data);
} else {
return serialize($data);
}
}
/**
* Uses igbinary unserializer if igbinary extension is loaded.
* Fallback to PHP unserializer.
*
* @param string $str
* The serialized string.
* @return mixed The converted value is returned, and can be a boolean,
* integer, float, string,
* array or object.
*/
protected function unserialize($str) {
if ($this->igbinaryfound) {
return igbinary_unserialize($str);
} else {
return unserialize($str);
}
}
/**
* Retrieves an item from the cache store given its key.
*
* @param string $key The key to retrieve
* @return mixed The data that was associated with the key, or false if the key did not exist.
*/
public function get($key) {
if (!is_array($key)) {
$key = array('key' => $key);
}
$key = $key['key'];
if (isset($this->store[$key])) {
if ($this->store[$key]['serialized']) {
return $this->unserialize($this->store[$key]['data']);
} else {
return $this->store[$key]['data'];
}
}
return false;
}
/**
* Retrieves several items from the cache store in a single transaction.
*
* If not all of the items are available in the cache then the data value for those that are missing will be set to false.
*
* @param array $keys The array of keys to retrieve
* @return array An array of items from the cache. There will be an item for each key, those that were not in the store will
* be set to false.
*/
public function get_many($keys) {
$return = array();
foreach ($keys as $key) {
if (!is_array($key)) {
$key = array('key' => $key);
}
$key = $key['key'];
$return[$key] = false;
if (isset($this->store[$key])) {
if ($this->store[$key]['serialized']) {
$return[$key] = $this->unserialize($this->store[$key]['data']);
} else {
$return[$key] = $this->store[$key]['data'];
}
}
}
return $return;
}
/**
* Sets an item in the cache given its key and data value.
*
* @param string $key The key to use.
* @param mixed $data The data to set.
* @param bool $testmaxsize If set to true then we test the maxsize arg and reduce if required.
* @return bool True if the operation was a success false otherwise.
*/
public function set($key, $data, $testmaxsize = true) {
if (!is_array($key)) {
$key = array('key' => $key);
}
$key = $key['key'];
$testmaxsize = ($testmaxsize && $this->maxsize !== false);
if ($testmaxsize) {
$increment = (!isset($this->store[$key]));
}
if ($this->simpledata || is_scalar($data)) {
$this->store[$key]['data'] = $data;
$this->store[$key]['serialized'] = false;
} else {
$this->store[$key]['data'] = $this->serialize($data);
$this->store[$key]['serialized'] = true;
}
if ($testmaxsize && $increment) {
$this->storecount++;
if ($this->storecount > $this->maxsize) {
$this->reduce_for_maxsize();
}
}
return true;
}
/**
* Sets many items in the cache in a single transaction.
*
* @param array $keyvaluearray An array of key value pairs. Each item in the array will be an associative array with two
* keys, 'key' and 'value'.
* @return int The number of items successfully set. It is up to the developer to check this matches the number of items
* sent ... if they care that is.
*/
public function set_many(array $keyvaluearray) {
$count = 0;
foreach ($keyvaluearray as $pair) {
if (!is_array($pair['key'])) {
$pair['key'] = array('key' => $pair['key']);
}
// Don't test the maxsize here. We'll do it once when we are done.
$this->set($pair['key']['key'], $pair['value'], false);
$count++;
}
if ($this->maxsize !== false) {
$this->storecount += $count;
if ($this->storecount > $this->maxsize) {
$this->reduce_for_maxsize();
}
}
return $count;
}
/**
* Checks if the store has a record for the given key and returns true if so.
*
* @param string $key
* @return bool
*/
public function has($key) {
if (is_array($key)) {
$key = $key['key'];
}
return isset($this->store[$key]);
}
/**
* Returns true if the store contains records for all of the given keys.
*
* @param array $keys
* @return bool
*/
public function has_all(array $keys) {
foreach ($keys as $key) {
if (!is_array($key)) {
$key = array('key' => $key);
}
$key = $key['key'];
if (!isset($this->store[$key])) {
return false;
}
}
return true;
}
/**
* Returns true if the store contains records for any of the given keys.
*
* @param array $keys
* @return bool
*/
public function has_any(array $keys) {
foreach ($keys as $key) {
if (!is_array($key)) {
$key = array('key' => $key);
}
$key = $key['key'];
if (isset($this->store[$key])) {
return true;
}
}
return false;
}
/**
* Deletes an item from the cache store.
*
* @param string $key The key to delete.
* @return bool Returns true if the operation was a success, false otherwise.
*/
public function delete($key) {
if (!is_array($key)) {
$key = array('key' => $key);
}
$key = $key['key'];
$result = isset($this->store[$key]);
unset($this->store[$key]);
if ($this->maxsize !== false) {
$this->storecount--;
}
return $result;
}
/**
* Deletes several keys from the cache in a single action.
*
* @param array $keys The keys to delete
* @return int The number of items successfully deleted.
*/
public function delete_many(array $keys) {
$count = 0;
foreach ($keys as $key) {
if (!is_array($key)) {
$key = array('key' => $key);
}
$key = $key['key'];
if (isset($this->store[$key])) {
$count++;
}
unset($this->store[$key]);
}
if ($this->maxsize !== false) {
$this->storecount -= $count;
}
return $count;
}
/**
* Purges the cache deleting all items within it.
*
* @return boolean True on success. False otherwise.
*/
public function purge() {
$this->flush_store_by_id($this->storeid);
$this->store = &self::register_store_id($this->storeid);
// Don't worry about checking if we're using max size just set it as thats as fast as the check.
$this->storecount = 0;
return true;
}
/**
* Reduces the size of the array if maxsize has been hit.
*
* This function reduces the size of the store reducing it by 10% of its maxsize.
* It removes the oldest items in the store when doing this.
* The reason it does this an doesn't use a least recently used system is purely the overhead such a system
* requires. The current approach is focused on speed, MUC already adds enough overhead to static/session caches
* and avoiding more is of benefit.
*
* @return int
*/
protected function reduce_for_maxsize() {
$diff = $this->storecount - $this->maxsize;
if ($diff < 1) {
return 0;
}
// Reduce it by an extra 10% to avoid calling this repetitively if we are in a loop.
$diff += floor($this->maxsize / 10);
$this->store = array_slice($this->store, $diff, null, true);
$this->storecount -= $diff;
return $diff;
}
/**
* Returns true if the user can add an instance of the store plugin.
*
* @return bool
*/
public static function can_add_instance() {
return false;
}
/**
* Performs any necessary clean up when the store instance is being deleted.
*/
public function instance_deleted() {
$this->purge();
}
/**
* Generates an instance of the cache store that can be used for testing.
*
* @param cache_definition $definition
* @return cachestore_static
*/
public static function initialise_test_instance(cache_definition $definition) {
// Do something here perhaps.
$cache = new cachestore_static('Static store');
$cache->initialise($definition);
return $cache;
}
/**
* Generates the appropriate configuration required for unit testing.
*
* @return array Array of unit test configuration data to be used by initialise().
*/
public static function unit_test_configuration() {
return array();
}
/**
* Returns the name of this instance.
* @return string
*/
public function my_name() {
return $this->name;
}
/**
* Finds all of the keys being stored in the cache store instance.
*
* @return array
*/
public function find_all() {
return array_keys($this->store);
}
/**
* Finds all of the keys whose keys start with the given prefix.
*
* @param string $prefix
*/
public function find_by_prefix($prefix) {
$return = array();
foreach ($this->find_all() as $key) {
if (strpos($key, $prefix) === 0) {
$return[] = $key;
}
}
return $return;
}
}
+153
View File
@@ -0,0 +1,153 @@
<?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/>.
namespace cachestore_static;
use cache_definition;
use cache_store;
use cachestore_static;
defined('MOODLE_INTERNAL') || die();
// Include the necessary evils.
global $CFG;
require_once($CFG->dirroot.'/cache/tests/fixtures/stores.php');
require_once($CFG->dirroot.'/cache/stores/static/lib.php');
/**
* Static unit test class.
*
* @package cachestore_static
* @copyright 2013 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class store_test extends \cachestore_tests {
/**
* Returns the static class name
* @return string
*/
protected function get_class_name() {
return 'cachestore_static';
}
/**
* Test the maxsize option.
*/
public function test_maxsize(): void {
$defid = 'phpunit/testmaxsize';
$config = \cache_config_testing::instance();
$config->phpunit_add_definition($defid, array(
'mode' => cache_store::MODE_REQUEST,
'component' => 'phpunit',
'area' => 'testmaxsize',
'maxsize' => 3
));
$definition = cache_definition::load($defid, $config->get_definition_by_id($defid));
$instance = cachestore_static::initialise_test_instance($definition);
$this->assertTrue($instance->set('key1', 'value1'));
$this->assertTrue($instance->set('key2', 'value2'));
$this->assertTrue($instance->set('key3', 'value3'));
$this->assertTrue($instance->has('key1'));
$this->assertTrue($instance->has('key2'));
$this->assertTrue($instance->has('key3'));
$this->assertTrue($instance->set('key4', 'value4'));
$this->assertTrue($instance->set('key5', 'value5'));
$this->assertFalse($instance->has('key1'));
$this->assertFalse($instance->has('key2'));
$this->assertTrue($instance->has('key3'));
$this->assertTrue($instance->has('key4'));
$this->assertTrue($instance->has('key5'));
$this->assertFalse($instance->get('key1'));
$this->assertFalse($instance->get('key2'));
$this->assertEquals('value3', $instance->get('key3'));
$this->assertEquals('value4', $instance->get('key4'));
$this->assertEquals('value5', $instance->get('key5'));
// Test adding one more.
$this->assertTrue($instance->set('key6', 'value6'));
$this->assertFalse($instance->get('key3'));
// Test reducing and then adding to make sure we don't lost one.
$this->assertTrue($instance->delete('key6'));
$this->assertTrue($instance->set('key7', 'value7'));
$this->assertEquals('value4', $instance->get('key4'));
// Set the same key three times to make sure it doesn't count overrides.
for ($i = 0; $i < 3; $i++) {
$this->assertTrue($instance->set('key8', 'value8'));
}
$this->assertEquals('value7', $instance->get('key7'), 'Overrides are incorrectly incrementing size');
// Test adding many.
$this->assertEquals(3, $instance->set_many(array(
array('key' => 'keyA', 'value' => 'valueA'),
array('key' => 'keyB', 'value' => 'valueB'),
array('key' => 'keyC', 'value' => 'valueC')
)));
$this->assertEquals(array(
'key4' => false,
'key5' => false,
'key6' => false,
'key7' => false,
'keyA' => 'valueA',
'keyB' => 'valueB',
'keyC' => 'valueC'
), $instance->get_many(array(
'key4', 'key5', 'key6', 'key7', 'keyA', 'keyB', 'keyC'
)));
}
/**
* Simple test to verify igbinary availability and check basic serialization is working ok.
*/
public function test_igbinary_serializer(): void {
// Skip if igbinary is not available.
if (!extension_loaded('igbinary')) {
$this->markTestSkipped('Cannot test igbinary serializer. Extension missing');
}
// Prepare the static instance.
$defid = 'phpunit/igbinary';
$config = \cache_config_testing::instance();
$config->phpunit_add_definition($defid, array(
'mode' => cache_store::MODE_REQUEST,
'component' => 'phpunit',
'area' => 'testigbinary'
));
$definition = cache_definition::load($defid, $config->get_definition_by_id($defid));
$instance = cachestore_static::initialise_test_instance($definition);
// Prepare an object.
$obj = new \stdClass();
$obj->someint = 9;
$obj->somestring = '99';
$obj->somearray = [9 => 999, '99' => '9999'];
// Serialize and set.
$objser = igbinary_serialize($obj);
$instance->set('testigbinary', $objser);
// Get and unserialize.
$res = $instance->get('testigbinary');
$resunser = igbinary_unserialize($res);
// Check expectations.
$this->assertSame($objser, $res); // Ok from cache (ig-serialized, 100% same string).
$this->assertEquals($obj, $resunser); // Ok ig-unserialized (equal
$this->assertNotSame($obj, $resunser);// but different objects, obviously).
}
}
+32
View File
@@ -0,0 +1,32 @@
<?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 static store version information.
*
* This is used as a default cache store within the Cache API. It should never be deleted.
*
* @package cachestore_static
* @category cache
* @copyright 2012 Sam Hemelryk
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die;
$plugin->version = 2024042200; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2024041600; // Requires this Moodle version.
$plugin->component = 'cachestore_static'; // Full name of the plugin.