From 40bdfaf4415ff9f46e63435fc5e61049649802f2 Mon Sep 17 00:00:00 2001 From: Moritz Strohm Date: Tue, 14 May 2024 07:22:47 +0000 Subject: made Stud.IP cache compatible with PSR-6, re #3701 Merge request studip/studip!2570 --- app/controllers/admin/cache.php | 6 +- app/controllers/file.php | 12 +- composer.json | 3 +- composer.lock | 51 +++- db/migrations/1.154_recalculate_score.php | 3 +- db/migrations/1.224_db_cache_table.php | 2 +- db/migrations/1.318_step_00353_cache.php | 13 +- .../5.5.26_tic3193_no_unlimited_courses.php | 5 +- db/migrations/6.0.3_adjust_cache_configuration.php | 38 +++ lib/activities/Activity.php | 4 +- lib/bootstrap-autoload.php | 5 + lib/bootstrap-definitions.php | 4 +- lib/bootstrap.php | 4 +- lib/classes/MvvPerm.php | 4 +- lib/classes/Score.class.php | 2 +- lib/classes/SemClass.class.php | 10 +- lib/classes/SemType.class.php | 10 +- lib/classes/Seminar.class.php | 4 +- lib/classes/SimpleORMap.class.php | 12 +- lib/classes/Siteinfo.php | 6 +- lib/classes/StudipCache.class.php | 94 ------- lib/classes/StudipCacheFactory.class.php | 195 --------------- lib/classes/StudipCacheKeyTrait.php | 29 --- lib/classes/StudipCacheProxy.php | 117 --------- lib/classes/StudipCacheWrapper.php | 85 ------- lib/classes/StudipCachedArray.php | 20 +- lib/classes/StudipDbCache.class.php | 123 --------- lib/classes/StudipFileCache.class.php | 276 -------------------- lib/classes/StudipKing.class.php | 2 +- lib/classes/StudipMemcachedCache.php | 157 ------------ lib/classes/StudipMemoryCache.class.php | 84 ------- lib/classes/StudipRedisCache.class.php | 177 ------------- lib/classes/UserLookup.class.php | 2 +- lib/classes/assets/SASSCompiler.php | 4 +- lib/classes/cache/Cache.class.php | 208 ++++++++++++++++ lib/classes/cache/DbCache.class.php | 142 +++++++++++ lib/classes/cache/Exception.php | 27 ++ lib/classes/cache/Factory.class.php | 206 +++++++++++++++ lib/classes/cache/FileCache.class.php | 277 +++++++++++++++++++++ .../cache/InvalidCacheArgumentException.php | 28 +++ lib/classes/cache/Item.class.php | 163 ++++++++++++ lib/classes/cache/KeyTrait.php | 31 +++ lib/classes/cache/MemcachedCache.class.php | 151 +++++++++++ lib/classes/cache/MemoryCache.class.php | 98 ++++++++ lib/classes/cache/Proxy.class.php | 122 +++++++++ lib/classes/cache/RedisCache.class.php | 198 +++++++++++++++ lib/classes/cache/Wrapper.class.php | 95 +++++++ lib/classes/cas/CAS_PGTStorage_Cache.php | 4 +- lib/cronjobs/purge_cache.class.php | 4 +- lib/functions.php | 2 +- lib/models/CourseDate.class.php | 6 +- lib/models/OERMaterial.php | 4 +- lib/models/PersonalNotifications.class.php | 6 +- lib/models/Semester.class.php | 6 +- lib/models/StudipCacheOperation.php | 2 +- lib/phplib/CT_Cache.class.php | 2 +- lib/plugins/db/RolePersistence.class.php | 4 +- lib/plugins/engine/PluginRepository.class.php | 2 +- lib/raumzeit/SingleDate.class.php | 4 +- tests/functional/_bootstrap.php | 4 +- tests/jsonapi/_bootstrap.php | 2 + tests/unit/_bootstrap.php | 4 +- tests/unit/lib/classes/MigrationTest.php | 5 - tests/unit/lib/classes/StudipCachedArrayTest.php | 5 +- 64 files changed, 1943 insertions(+), 1432 deletions(-) create mode 100644 db/migrations/6.0.3_adjust_cache_configuration.php delete mode 100644 lib/classes/StudipCache.class.php delete mode 100644 lib/classes/StudipCacheFactory.class.php delete mode 100644 lib/classes/StudipCacheKeyTrait.php delete mode 100644 lib/classes/StudipCacheProxy.php delete mode 100644 lib/classes/StudipCacheWrapper.php delete mode 100644 lib/classes/StudipDbCache.class.php delete mode 100644 lib/classes/StudipFileCache.class.php delete mode 100644 lib/classes/StudipMemcachedCache.php delete mode 100644 lib/classes/StudipMemoryCache.class.php delete mode 100644 lib/classes/StudipRedisCache.class.php create mode 100644 lib/classes/cache/Cache.class.php create mode 100644 lib/classes/cache/DbCache.class.php create mode 100644 lib/classes/cache/Exception.php create mode 100644 lib/classes/cache/Factory.class.php create mode 100644 lib/classes/cache/FileCache.class.php create mode 100644 lib/classes/cache/InvalidCacheArgumentException.php create mode 100644 lib/classes/cache/Item.class.php create mode 100644 lib/classes/cache/KeyTrait.php create mode 100644 lib/classes/cache/MemcachedCache.class.php create mode 100644 lib/classes/cache/MemoryCache.class.php create mode 100644 lib/classes/cache/Proxy.class.php create mode 100644 lib/classes/cache/RedisCache.class.php create mode 100644 lib/classes/cache/Wrapper.class.php diff --git a/app/controllers/admin/cache.php b/app/controllers/admin/cache.php index 329aab7..3e9d970 100644 --- a/app/controllers/admin/cache.php +++ b/app/controllers/admin/cache.php @@ -111,7 +111,7 @@ class Admin_CacheController extends AuthenticatedController // Store settings to global config. if (Config::get()->store('SYSTEMCACHE', $settings)) { PageLayout::postSuccess(_('Die Einstellungen wurden gespeichert.')); - StudipCacheFactory::unconfigure(); + \Studip\Cache\Factory::unconfigure(); } $this->relocate('admin/cache/settings'); @@ -122,7 +122,7 @@ class Admin_CacheController extends AuthenticatedController */ public function flush_action() { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $cache->flush(); PageLayout::postSuccess(_('Die Inhalte des Caches wurden gelöscht.')); @@ -135,7 +135,7 @@ class Admin_CacheController extends AuthenticatedController */ public function stats_action() { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $this->stats = $cache->getStats(); } diff --git a/app/controllers/file.php b/app/controllers/file.php index 65344ef..bebfe70 100644 --- a/app/controllers/file.php +++ b/app/controllers/file.php @@ -364,7 +364,7 @@ class FileController extends AuthenticatedController //The file system object is a folder. //Calculate the files and the folder size: - list($this->folder_size, $this->folder_file_amount) = $this->getFolderSize($this->folder); + [$this->folder_size, $this->folder_file_amount] = $this->getFolderSize($this->folder); PageLayout::setTitle($this->folder->name); $this->render_action('folder_details'); } @@ -1128,7 +1128,7 @@ class FileController extends AuthenticatedController $this->search_id = md5(json_encode($search_parameters)); - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $merged_results = LibrarySearchManager::search( $search_parameters, @@ -1187,7 +1187,7 @@ class FileController extends AuthenticatedController $this->search_id = Request::get('search_id'); $this->page = Request::get('page'); - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $cache_data = $cache->read($this->search_id); $results = $cache_data['results']; $this->total_results = count($results); @@ -1247,7 +1247,7 @@ class FileController extends AuthenticatedController } if ($item_id) { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $documents = $cache->read($search_id); $document = $documents['results'][$item_id]; if (!($document instanceof LibraryDocument)) { @@ -1255,7 +1255,7 @@ class FileController extends AuthenticatedController } $file = LibraryFile::createFromLibraryDocument($document, $folder_id); } else { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $search = $cache->read($search_id); if (!$search) { throw new Exception('Search not found in cache!'); @@ -2036,7 +2036,7 @@ class FileController extends AuthenticatedController PageLayout::postMessage($result); } } - list($this->folder_size, $this->folder_file_amount) = $this->getFolderSize($folder); + [$this->folder_size, $this->folder_file_amount] = $this->getFolderSize($folder); } public function delete_folder_action($folder_id) diff --git a/composer.json b/composer.json index e4d1902..009a7a0 100644 --- a/composer.json +++ b/composer.json @@ -62,7 +62,8 @@ "psy/psysh": "^0.12.2", "okvpn/clock-lts": "^1.0", "vlucas/phpdotenv": "^5.6", - "edu-sharing/auth-plugin": "8.0.x-dev" + "edu-sharing/auth-plugin": "8.0.x-dev", + "psr/cache": "^1.0" }, "replace": { "symfony/polyfill-php73": "*", diff --git a/composer.lock b/composer.lock index ffde9ba..b93431a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "535218b1c9cca9acd1f2c554c59e5113", + "content-hash": "30db4609d0f74dac659c0fcd53db3d0a", "packages": [ { "name": "algo26-matthias/idna-convert", @@ -2890,6 +2890,55 @@ "time": "2023-02-11T17:10:30+00:00" }, { + "name": "psr/cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/master" + }, + "time": "2016-08-06T20:24:11+00:00" + }, + { "name": "psr/clock", "version": "1.0.0", "source": { diff --git a/db/migrations/1.154_recalculate_score.php b/db/migrations/1.154_recalculate_score.php index 158582e..5568363 100644 --- a/db/migrations/1.154_recalculate_score.php +++ b/db/migrations/1.154_recalculate_score.php @@ -38,7 +38,7 @@ class RecalculateScore extends Migration { private static function getScore($user_id) { $user_id || $user_id = $GLOBALS['user']->id; - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); if ($cache->read("user_score_of_".$user_id)) { return $cache->read("user_score_of_".$user_id); } @@ -142,4 +142,3 @@ class RecalculateScore extends Migration { } } - diff --git a/db/migrations/1.224_db_cache_table.php b/db/migrations/1.224_db_cache_table.php index 66227d0..9abe7e3 100644 --- a/db/migrations/1.224_db_cache_table.php +++ b/db/migrations/1.224_db_cache_table.php @@ -17,7 +17,7 @@ class DbCacheTable extends Migration PRIMARY KEY (cache_key) ) ENGINE=InnoDB ROW_FORMAT=DYNAMIC'); - StudipCacheFactory::getCache()->flush(); + \Studip\Cache\Factory::getCache()->flush(); } public function down() diff --git a/db/migrations/1.318_step_00353_cache.php b/db/migrations/1.318_step_00353_cache.php index 901db60..5959945 100644 --- a/db/migrations/1.318_step_00353_cache.php +++ b/db/migrations/1.318_step_00353_cache.php @@ -1,5 +1,10 @@ exec(" ALTER TABLE `sem_classes` ADD `unlimited_forbidden` TINYINT UNSIGNED NOT NULL DEFAULT 0 AFTER `is_group` "); - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $cache->expire('DB_SEM_CLASSES_ARRAY'); } public function down() { DBManager::get()->exec("ALTER TABLE `sem_classes` DROP `unlimited_forbidden`"); - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $cache->expire('DB_SEM_CLASSES_ARRAY'); } } - diff --git a/db/migrations/6.0.3_adjust_cache_configuration.php b/db/migrations/6.0.3_adjust_cache_configuration.php new file mode 100644 index 0000000..2432e84 --- /dev/null +++ b/db/migrations/6.0.3_adjust_cache_configuration.php @@ -0,0 +1,38 @@ + Studip\Cache\DbCache::class, + StudipFileCache::class => Studip\Cache\FileCache::class, + StudipMemcachedCache::class => Studip\Cache\MemcachedCache::class, + StudipRedisCache::class => Studip\Cache\RedisCache::class, + ]; + + public function description() + { + return 'Replaces the renamed cache classes in system configuration'; + } + + protected function up() + { + foreach (self::MAPPING as $old => $new) { + self::replaceCache($old, $new); + } + } + + protected function down() + { + foreach (self::MAPPING as $old => $new) { + self::replaceCache($new, $old); + } + } + + private function replaceCache(string $old, string $new): void + { + $query = "UPDATE `config_values` + SET `value` = JSON_REPLACE(`value`, '$.type', ?) + WHERE `field` = 'SYSTEMCACHE' + AND JSON_CONTAINS(`value`, JSON_QUOTE(?), '$.type')"; + DBManager::get()->execute($query, [$new, $old]); + } +}; diff --git a/lib/activities/Activity.php b/lib/activities/Activity.php index 3508e5a..e1c918d 100644 --- a/lib/activities/Activity.php +++ b/lib/activities/Activity.php @@ -163,7 +163,7 @@ class Activity extends \SimpleORMap ); //Expire Cache - \StudipCacheFactory::getCache()->expire('activity/oldest_activity'); + \Studip\Cache\Factory::getCache()->expire('activity/oldest_activity'); } /** @@ -173,7 +173,7 @@ class Activity extends \SimpleORMap */ public static function getOldestActivity() { - $cache = \StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $cache_key = 'activity/oldest-activity'; if (!$activity = unserialize($cache->read($cache_key))) { diff --git a/lib/bootstrap-autoload.php b/lib/bootstrap-autoload.php index 4e63115..21219fd 100644 --- a/lib/bootstrap-autoload.php +++ b/lib/bootstrap-autoload.php @@ -20,6 +20,11 @@ StudipAutoloader::addAutoloadPath('lib/classes/admission'); StudipAutoloader::addAutoloadPath('lib/classes/admission/userfilter'); StudipAutoloader::addAutoloadPath('lib/classes/auth_plugins'); StudipAutoloader::addAutoloadPath('lib/classes/calendar'); + +StudipAutoloader::addAutoloadPath('lib/classes/cache', 'Studip\\Cache'); +class_alias(\Studip\Cache\Factory::class, 'StudipCacheFactory'); +class_alias(\Studip\Cache\Cache::class, 'StudipCache'); + StudipAutoloader::addAutoloadPath('lib/classes/exportdocument'); StudipAutoloader::addAutoloadPath('lib/classes/forms'); StudipAutoloader::addAutoloadPath('lib/classes/globalsearch'); diff --git a/lib/bootstrap-definitions.php b/lib/bootstrap-definitions.php index 17f35d1..ee80135 100644 --- a/lib/bootstrap-definitions.php +++ b/lib/bootstrap-definitions.php @@ -14,8 +14,8 @@ return [ ), ]); }), - StudipCache::class => DI\factory(function () { - return StudipCacheFactory::getCache(); + \Studip\Cache\Cache::class => DI\factory(function () { + return \Studip\Cache\Factory::getCache(); }), StudipPDO::class => DI\factory(function () { return DBManager::get(); diff --git a/lib/bootstrap.php b/lib/bootstrap.php index 364f9d9..9afc16a 100644 --- a/lib/bootstrap.php +++ b/lib/bootstrap.php @@ -170,7 +170,7 @@ if (isset($_SERVER['REQUEST_METHOD'])) { // bootstrap because the stud.ip cache needs to have a db conenction) if ($GLOBALS['CACHING_ENABLE']) { $lookup_hash = null; - $cached = StudipCacheFactory::getCache()->read('STUDIP#autoloader-classes'); + $cached = \Studip\Cache\Factory::getCache()->read('STUDIP#autoloader-classes'); if ($cached) { $class_lookup = json_decode($cached, true); if (is_array($class_lookup)) { @@ -182,7 +182,7 @@ if ($GLOBALS['CACHING_ENABLE']) { register_shutdown_function(function () use ($lookup_hash) { $cached = json_encode(StudipAutoloader::$class_lookup, JSON_UNESCAPED_UNICODE); if (md5($cached) !== $lookup_hash) { - StudipCacheFactory::getCache()->write( + \Studip\Cache\Factory::getCache()->write( 'STUDIP#autoloader-classes', $cached, 7 * 24 * 60 * 60 diff --git a/lib/classes/MvvPerm.php b/lib/classes/MvvPerm.php index 93f9f44..91e1e35 100644 --- a/lib/classes/MvvPerm.php +++ b/lib/classes/MvvPerm.php @@ -563,7 +563,7 @@ class MvvPerm { private static function getPrivileges($mvv_table) { if (self::$privileges === null) { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); self::$privileges = unserialize($cache->read(MVV::CACHE_KEY . '/privileges')); } @@ -576,7 +576,7 @@ class MvvPerm { include $config_file; // Defines $privileges self::$privileges[$mvv_table] = $privileges ?? []; } - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $cache->write(MVV::CACHE_KEY . '/privileges', serialize(self::$privileges)); } } diff --git a/lib/classes/Score.class.php b/lib/classes/Score.class.php index 8f038f9..f7d8ea3 100644 --- a/lib/classes/Score.class.php +++ b/lib/classes/Score.class.php @@ -121,7 +121,7 @@ class Score public static function GetMyScore($user_or_id = null) { $user = $user_or_id ? User::toObject($user_or_id) : User::findCurrent(); - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); if ($cache->read("user_score_of_".$user->id)) { return $cache->read("user_score_of_".$user->id); } diff --git a/lib/classes/SemClass.class.php b/lib/classes/SemClass.class.php index 63be920..2a038e2 100644 --- a/lib/classes/SemClass.class.php +++ b/lib/classes/SemClass.class.php @@ -389,7 +389,7 @@ class SemClass implements ArrayAccess "chdate = UNIX_TIMESTAMP() " . "WHERE id = :id ". ""); - StudipCacheFactory::getCache()->expire('DB_SEM_CLASSES_ARRAY'); + \Studip\Cache\Factory::getCache()->expire('DB_SEM_CLASSES_ARRAY'); return $statement->execute([ 'id' => $this->data['id'], 'name' => $this->data['name'], @@ -453,7 +453,7 @@ class SemClass implements ArrayAccess DELETE FROM sem_classes WHERE id = :id "); - StudipCacheFactory::getCache()->expire('DB_SEM_CLASSES_ARRAY'); + \Studip\Cache\Factory::getCache()->expire('DB_SEM_CLASSES_ARRAY'); return $statement->execute([ 'id' => $this->data['id'] ]); @@ -552,7 +552,7 @@ class SemClass implements ArrayAccess $db = DBManager::get(); self::$sem_classes = []; - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $class_array = unserialize($cache->read('DB_SEM_CLASSES_ARRAY')); if (!$class_array) { @@ -564,7 +564,7 @@ class SemClass implements ArrayAccess $class_array = $statement->fetchAll(PDO::FETCH_ASSOC); if ($class_array) { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $cache->write('DB_SEM_CLASSES_ARRAY', serialize($class_array)); } } catch (PDOException $e) { @@ -593,7 +593,7 @@ class SemClass implements ArrayAccess */ static public function refreshClasses() { - StudipCacheFactory::getCache()->expire('DB_SEM_CLASSES_ARRAY'); + \Studip\Cache\Factory::getCache()->expire('DB_SEM_CLASSES_ARRAY'); self::$sem_classes = null; return self::getClasses(); } diff --git a/lib/classes/SemType.class.php b/lib/classes/SemType.class.php index 2365c4e..5be1f19 100644 --- a/lib/classes/SemType.class.php +++ b/lib/classes/SemType.class.php @@ -68,7 +68,7 @@ class SemType implements ArrayAccess "chdate = UNIX_TIMESTAMP() " . "WHERE id = :id ". ""); - StudipCacheFactory::getCache()->expire('DB_SEM_TYPES_ARRAY'); + \Studip\Cache\Factory::getCache()->expire('DB_SEM_TYPES_ARRAY'); return $statement->execute([ 'id' => $this->data['id'], 'name' => $this->data['name'], @@ -89,7 +89,7 @@ class SemType implements ArrayAccess DELETE FROM sem_types WHERE id = :id "); - StudipCacheFactory::getCache()->expire('DB_SEM_TYPES_ARRAY'); + \Studip\Cache\Factory::getCache()->expire('DB_SEM_TYPES_ARRAY'); return $statement->execute([ 'id' => $this->data['id'] ]); @@ -175,7 +175,7 @@ class SemType implements ArrayAccess $db = DBManager::get(); self::$sem_types = []; - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $types_array = unserialize($cache->read('DB_SEM_TYPES_ARRAY')); if (!$types_array) { try { @@ -185,7 +185,7 @@ class SemType implements ArrayAccess $statement->execute(); $types_array = $statement->fetchAll(PDO::FETCH_ASSOC); if ($types_array) { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $cache->write('DB_SEM_TYPES_ARRAY', serialize($types_array)); } } catch (PDOException $e) { @@ -210,7 +210,7 @@ class SemType implements ArrayAccess static public function refreshTypes() { self::$sem_types = null; - StudipCacheFactory::getCache()->expire('DB_SEM_TYPES_ARRAY'); + \Studip\Cache\Factory::getCache()->expire('DB_SEM_TYPES_ARRAY'); return self::getTypes(); } diff --git a/lib/classes/Seminar.class.php b/lib/classes/Seminar.class.php index 054c337..dda25ee 100644 --- a/lib/classes/Seminar.class.php +++ b/lib/classes/Seminar.class.php @@ -303,7 +303,7 @@ class Seminar { // Caching - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $cache_key = 'course/undecorated_data/'. $this->id; if ($filter) { @@ -745,7 +745,7 @@ class Seminar StudipLog::log("SEM_ADD_SINGLEDATE", $this->getId(), $singledate->toString(), 'SingleDateID: '.$singledate->getTerminID()); // logging <<<<<< - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $cache->expire('course/undecorated_data/'. $this->getId()); $this->readSingleDates(); diff --git a/lib/classes/SimpleORMap.class.php b/lib/classes/SimpleORMap.class.php index 2606086..499149c 100644 --- a/lib/classes/SimpleORMap.class.php +++ b/lib/classes/SimpleORMap.class.php @@ -277,7 +277,7 @@ class SimpleORMap implements ArrayAccess, Countable, IteratorAggregate $relation = $a_config[0] ?? ''; $relation_field = $a_config[1] ?? ''; if (!$relation) { - list($relation, $relation_field) = explode('_', $a_field); + [$relation, $relation_field] = explode('_', $a_field); } if (!$relation_field || !$relation) { throw new UnexpectedValueException('no relation found for autoget/set additional field: ' . $a_field); @@ -415,7 +415,7 @@ class SimpleORMap implements ArrayAccess, Countable, IteratorAggregate public static function tableScheme($db_table) { if (self::$schemes === null) { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); self::$schemes = unserialize($cache->read('DB_TABLE_SCHEMES')); } if (!isset(self::$schemes[$db_table])) { @@ -434,7 +434,7 @@ class SimpleORMap implements ArrayAccess, Countable, IteratorAggregate } self::$schemes[$db_table]['db_fields'] = $db_fields; self::$schemes[$db_table]['pk'] = $pk; - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $cache->write('DB_TABLE_SCHEMES', serialize(self::$schemes)); } return isset(self::$schemes[$db_table]); @@ -446,7 +446,7 @@ class SimpleORMap implements ArrayAccess, Countable, IteratorAggregate */ public static function expireTableScheme() { - StudipCacheFactory::getCache()->expire('DB_TABLE_SCHEMES'); + \Studip\Cache\Factory::getCache()->expire('DB_TABLE_SCHEMES'); self::$schemes = null; self::$config = []; } @@ -993,7 +993,7 @@ class SimpleORMap implements ArrayAccess, Countable, IteratorAggregate */ protected function _getAdditionalValueFromRelation($field) { - list($relation, $relation_field) = [$this->additional_fields()[$field]['relation'], + [$relation, $relation_field] = [$this->additional_fields()[$field]['relation'], $this->additional_fields()[$field]['relation_field']]; if (!array_key_exists($field, $this->additional_data)) { $this->_setAdditionalValue($field, $this->getRelationValue($relation, $relation_field)); @@ -1010,7 +1010,7 @@ class SimpleORMap implements ArrayAccess, Countable, IteratorAggregate */ protected function _setAdditionalValueFromRelation($field, $value) { - list($relation, $relation_field) = [$this->additional_fields()[$field]['relation'], + [$relation, $relation_field] = [$this->additional_fields()[$field]['relation'], $this->additional_fields()[$field]['relation_field']]; $this->$relation->$field = $value; unset($this->additional_data[$field]); diff --git a/lib/classes/Siteinfo.php b/lib/classes/Siteinfo.php index ba7386b..247b836 100644 --- a/lib/classes/Siteinfo.php +++ b/lib/classes/Siteinfo.php @@ -419,7 +419,7 @@ class SiteinfoMarkupEngine { } function coregroup() { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); if (!($remotefile = $cache->read('coregroup'))) { $remotefile = file_get_contents('https://develop.studip.de/studip/extern.php?module=Persons&config_id=8d1dafc3afca2bce6125d57d4119b631&range_id=4498a5bc62d7974d0a0ac3e97aca5296', false, get_default_http_stream_context('https://develop.studip.de')); $cache->write('coregroup', $remotefile); @@ -428,7 +428,7 @@ class SiteinfoMarkupEngine { } function toplist($item) { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); if ($found_in_cache = $cache->read(__METHOD__ . $item)) { return $found_in_cache; } @@ -531,7 +531,7 @@ class SiteinfoMarkupEngine { } function indicator($key) { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); if ($found_in_cache = $cache->read(__METHOD__ . $key)) { return $found_in_cache; } diff --git a/lib/classes/StudipCache.class.php b/lib/classes/StudipCache.class.php deleted file mode 100644 index ba929f9..0000000 --- a/lib/classes/StudipCache.class.php +++ /dev/null @@ -1,94 +0,0 @@ -expire('foo'); - * - * @param string $arg a single key - */ - public function expire($arg); - - /**1 - * Expire all items from the cache. - */ - public function flush(); - - /** - * Retrieve item from the server. - * - * Example: - * - * # reads foo - * $foo = $cache->reads('foo'); - * - * @param string $arg a single key - * - * @return mixed the previously stored data if an item with such a key - * exists on the server or FALSE on failure. - */ - public function read($arg); - - /** - * Store data at the server. - * - * @param string $name the item's key. - * @param mixed $content the item's content (will be serialized if necessary). - * @param int $expires the item's expiry time in seconds. Optional, defaults to 12h. - * - * @return bool returns TRUE on success or FALSE on failure. - */ - public function write($name, $content, $expires = self::DEFAULT_EXPIRATION); - - /** - * @return string A translateable display name for this cache class. - */ - public static function getDisplayName(): string; - - /** - * Get some statistics from cache, like number of entries, hit rate or - * whatever the underlying cache provides. - * Results are returned in form of an array like - * "[ - * [ - * 'name' => - * 'value' => - * ] - * ]" - * - * @return array - */ - public function getStats(): array; - - /** - * Return the Vue component name and props that handle configuration. - * The associative array is of the form - * [ - * 'component' => , - * 'props' => - * ] - * - * @return array - */ - public static function getConfig(): array; -} diff --git a/lib/classes/StudipCacheFactory.class.php b/lib/classes/StudipCacheFactory.class.php deleted file mode 100644 index 77c5973..0000000 --- a/lib/classes/StudipCacheFactory.class.php +++ /dev/null @@ -1,195 +0,0 @@ -getMessage()); - PageLayout::addBodyElements(MessageBox::error(__METHOD__ . ': ' . $e->getMessage())); - $class = self::DEFAULT_CACHE_CLASS; - self::$cache = new $class(); - } - } - - // If proxy should be used, inject it. Otherwise apply pending - // operations, if any. - if ($proxied) { - self::$cache = new StudipCacheProxy(self::$cache); - } elseif ($GLOBALS['CACHING_ENABLE'] && $apply_proxied_operations) { - // Even if the above condition will try to eliminate most - // failures, the following operation still needs to be wrapped - // in a try/catch block. Otherwise there are no means to - // execute migration 166 which creates the neccessary tables - // for said operation. - try { - StudipCacheOperation::apply(self::$cache); - } catch (Exception $e) { - } - } - } - - return self::$cache; - } - - - /** - * Load configured cache class and return its name. - * - * @return string the name of the configured cache class - */ - public static function loadCacheClass() - { - $cacheConfig = self::getConfig()->SYSTEMCACHE; - - $cache_class = $cacheConfig['type'] ?: null; - - # default class - if ($cache_class === null) { - $version = new DBSchemaVersion(); - if ($version->get(1) < 224) { - // db cache is not yet available, use StudipMemoryCache - return 'StudipMemoryCache'; - } - - return self::DEFAULT_CACHE_CLASS; - } - - if (!class_exists($cache_class)) { - throw new UnexpectedValueException("Could not find class: '$cache_class'"); - } - - return $cache_class; - } - - /** - * Return an array of arguments required for instantiation of the cache - * class. - * - * @return array the array of arguments - */ - public static function retrieveConstructorArguments() - { - $cacheConfig = self::getConfig()->SYSTEMCACHE; - - return $cacheConfig ?: []; - } - - /** - * Return an instance of a given class using some arguments. Unless the - * memory cache is instantiated, the cache will be wrapped in a wrapper - * class that uses a memory cache to reduce accesses to the cache. - * - * @param string $class the name of the class - * @param array $arguments an array of arguments to be used by the constructor - * - * @return StudipCache an instance of the specified class - */ - public static function instantiateCache($class, $arguments) - { - $reflection_class = new ReflectionClass($class); - $cache = (is_array($arguments['config']) && count($arguments['config']) > 0) - ? $reflection_class->newInstanceArgs($arguments['config']) - : $reflection_class->newInstance(); - - if ($class !== StudipMemoryCache::class) { - return new StudipCacheWrapper($cache); - } - - return $cache; - } -} diff --git a/lib/classes/StudipCacheKeyTrait.php b/lib/classes/StudipCacheKeyTrait.php deleted file mode 100644 index 62eb142..0000000 --- a/lib/classes/StudipCacheKeyTrait.php +++ /dev/null @@ -1,29 +0,0 @@ - - * @license GPL2 or any later version - * @package studip - * @subpackage cache - * @since Stud.IP 5.0 - */ -trait StudipCacheKeyTrait -{ - protected $cache_prefix = null; - - /** - * Returns a prefix cache key based on db configuration. - * - * @param string $offset - * @return string - */ - protected function getCacheKey($offset) - { - if ($this->cache_prefix === null) { - $this->cache_prefix = md5("{$GLOBALS['DB_STUDIP_HOST']}|{$GLOBALS['DB_STUDIP_DATABASE']}"); - } - return "{$this->cache_prefix}/{$offset}"; - } -} diff --git a/lib/classes/StudipCacheProxy.php b/lib/classes/StudipCacheProxy.php deleted file mode 100644 index 686f812..0000000 --- a/lib/classes/StudipCacheProxy.php +++ /dev/null @@ -1,117 +0,0 @@ - - * @license GPL2 or any later version - * @since Stud.IP 3.3 - */ -class StudipCacheProxy implements StudipCache -{ - protected $actual_cache; - protected $proxy_these; - - /** - * @param StudipCache $cache The actual cache object - * @param mixed $proxy_these List of operations to proxy (should be - * an array but a space seperated string - * is also valid) - */ - public function __construct(StudipCache $cache, $proxy_these = ['expire']) - { - if (!is_array($proxy_these)) { - $proxy_these = words($proxy_these); - } - - $this->actual_cache = $cache; - $this->proxy_these = is_array($proxy_these) - ? $proxy_these - : words($proxy_these); - } - - /** - * Expires just a single key. - * - * @param string $key The item's key - */ - public function expire($key) - { - if (in_array('expire', $this->proxy_these)) { - try { - $operation = new StudipCacheOperation([$key, 'expire']); - $operation->parameters = serialize([]); - $operation->store(); - } catch (Exception $e) { - } - } - - return $this->actual_cache->expire($key); - } - - /** - * Expire all items from the cache. - */ - public function flush() - { - if (in_array('flush', $this->proxy_these)) { - try { - $operation = new StudipCacheOperation(['', 'flush']); - $operation->parameters = serialize([]); - $operation->store(); - } catch (Exception $e) { - } - } - - return $this->actual_cache->flush(); - } - - /** - * Reads just a single key from the cache. - * - * @param string $key The item's key - * @return mixed The corresponding value - */ - public function read($key) - { - return $this->actual_cache->read($key); - } - - /** - * Store data at the server. - * - * @param string $key The item's key - * @param string $content The item's conten - * @param int $expires The item's expiry time in seconds, defaults to 12h - * @return bool Returns TRUE on success or FALSE on failure - */ - public function write($key, $content, $expires = self::DEFAULT_EXPIRATION) - { - if (in_array('write', $this->proxy_these)) { - try { - $operation = new StudipCacheOperation([$key, 'write']); - $operation->parameters = serialize([$content, $expires]); - $operation->store(); - } catch (Exception $e) { - } - } - - return $this->actual_cache->write($key, $content, $expires); - } - - public static function getDisplayName(): string - { - return static::class; - } - - public function getStats(): array - { - return $this->actual_cache->getStats(); - } - - public static function getConfig(): array - { - return []; - } -} diff --git a/lib/classes/StudipCacheWrapper.php b/lib/classes/StudipCacheWrapper.php deleted file mode 100644 index 6c75c01..0000000 --- a/lib/classes/StudipCacheWrapper.php +++ /dev/null @@ -1,85 +0,0 @@ - - * @license GPL2 or any later version - * @since Stud.IP 5.4 - */ -class StudipCacheWrapper implements StudipCache -{ - const DEFAULT_MEMORY_EXPIRATION = 60; - - protected $actual_cache; - protected $memory_cache; - - public function __construct(StudipCache $actual_cache) - { - $this->actual_cache = $actual_cache; - $this->memory_cache = new StudipMemoryCache(); - } - - /** - * @inheritdoc - */ - public function expire($arg) - { - $this->memory_cache->expire($arg); - $this->actual_cache->expire($arg); - } - - /** - * @inheritdoc - */ - public function flush() - { - $this->memory_cache->flush(); - $this->actual_cache->flush(); - } - - /** - * @inheritdoc - */ - public function read($arg) - { - $cached = $this->memory_cache->read($arg); - if ($cached !== false) { - return $cached; - } - - $cached = $this->actual_cache->read($arg); - if ($cached !== false) { - $this->memory_cache->write($arg, $cached, self::DEFAULT_MEMORY_EXPIRATION); - } - return $cached; - } - - /** - * @inheritdoc - */ - public function write($name, $content, $expires = self::DEFAULT_EXPIRATION) - { - if ($this->actual_cache->write($name, $content, $expires)) { - return $this->memory_cache->write($name, $content, $expires); - } else { - return false; - } - } - - public static function getDisplayName(): string - { - return static::class; - } - - public function getStats(): array - { - return $this->actual_cache->getStats(); - } - - public static function getConfig(): array - { - return []; - } -} diff --git a/lib/classes/StudipCachedArray.php b/lib/classes/StudipCachedArray.php index 1232d00..46723f4 100644 --- a/lib/classes/StudipCachedArray.php +++ b/lib/classes/StudipCachedArray.php @@ -27,10 +27,10 @@ class StudipCachedArray implements ArrayAccess * @param int $duration Duration in seconds for which the item shall be * stored */ - public function __construct(string $key, int $duration = StudipCache::DEFAULT_EXPIRATION) + public function __construct(string $key, int $duration = \Studip\Cache\Cache::DEFAULT_EXPIRATION) { $this->key = self::class . "/{$key}"; - $this->cache = StudipCacheFactory::getCache(); + $this->cache = \Studip\Cache\Factory::getCache(); $this->duration = $duration; $this->hash = $this->getHash(); @@ -116,11 +116,14 @@ class StudipCachedArray implements ArrayAccess protected function loadData(string $offset) { if (!array_key_exists($offset, $this->data)) { - $cached = $this->cache->read($this->getCacheKey($offset)); - $this->data[$offset] = $this->swapNullAndFalse($cached); + // Get the cache item from the cache: + $item = $this->cache->getItem($this->getCacheKey($offset)); + if ($item->isHit()) { + $this->data[$offset] = $this->swapNullAndFalse($item->get()); + } } - return $this->data[$offset]; + return $this->data[$offset] ?? null; } /** @@ -132,13 +135,12 @@ class StudipCachedArray implements ArrayAccess */ protected function storeData(string $offset): void { - $data = $this->swapNullAndFalse($this->data[$offset]); - - $this->cache->write( + $item = new \Studip\Cache\Item( $this->getCacheKey($offset), - $data, + $this->swapNullAndFalse($this->data[$offset]), $this->duration ); + $this->cache->save($item); } /** diff --git a/lib/classes/StudipDbCache.class.php b/lib/classes/StudipDbCache.class.php deleted file mode 100644 index 865825e..0000000 --- a/lib/classes/StudipDbCache.class.php +++ /dev/null @@ -1,123 +0,0 @@ - - */ -class StudipDbCache implements StudipCache -{ - - /** - * @return string A translateable display name for this cache class. - */ - public static function getDisplayName(): string - { - return _('Datenbank'); - } - - /** - * Expire item from the cache. - * - * @param string $arg a single key - */ - public function expire($arg) - { - $db = DBManager::get(); - - $stmt = $db->prepare('DELETE FROM cache WHERE cache_key = ?'); - $stmt->execute([$arg]); - } - - /** - * Expire all items from the cache. - */ - public function flush() - { - $db = DBManager::get(); - - $db->exec('TRUNCATE TABLE cache'); - } - - /** - * Delete all expired items from the cache. - */ - public function purge() - { - $db = DBManager::get(); - - $stmt = $db->prepare('DELETE FROM cache WHERE expires < ?'); - $stmt->execute([time()]); - } - - /** - * Retrieve item from the server. - * - * @param string $arg a single key - * - * @return mixed the previously stored data if an item with such a key - * exists on the server or FALSE on failure. - */ - public function read($arg) - { - $db = DBManager::get(); - - $stmt = $db->prepare('SELECT content FROM cache WHERE cache_key = ? AND expires > ?'); - $stmt->execute([$arg, time()]); - $result = $stmt->fetchColumn(); - - return $result !== false ? unserialize($result) : false; - } - - /** - * Store data at the server. - * - * @param string $name the item's key. - * @param mixed $content the item's content (will be serialized if necessary). - * @param int $expired the item's expiry time in seconds. Optional, defaults to 12h. - * - * @return bool returns TRUE on success or FALSE on failure. - */ - public function write($name, $content, $expires = self::DEFAULT_EXPIRATION) - { - $db = DBManager::get(); - - $stmt = $db->prepare('REPLACE INTO cache VALUES(?, ?, ?)'); - return $stmt->execute([$name, serialize($content), time() + $expires]); - } - - /** - * Return statistics. - * - * @see StudipCache::getStats() - * - * @return array|array[] - */ - public function getStats(): array - { - return [ - __CLASS__ => [ - 'name' => _('Anzahl Einträge'), - 'value' => DBManager::get()->fetchColumn("SELECT COUNT(*) FROM `cache`") - ] - ]; - } - - /** - * Return the Vue component name and props that handle configuration. - * - * @see StudipCache::getConfig() - * - * @return array - */ - public static function getConfig(): array - { - return [ - 'component' => null, - 'props' => [] - ]; - } - -} diff --git a/lib/classes/StudipFileCache.class.php b/lib/classes/StudipFileCache.class.php deleted file mode 100644 index 9eae66c..0000000 --- a/lib/classes/StudipFileCache.class.php +++ /dev/null @@ -1,276 +0,0 @@ - -// +--------------------------------------------------------------------------+ -// This program 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 2 -// of the License, or any later version. -// +--------------------------------------------------------------------------+ -// This program 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 this program; if not, write to the Free Software -// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. -// +--------------------------------------------------------------------------+ - -/** - * StudipCache implementation using files - * - * @package studip - * @subpackage cache - * - * @author André Noack - * @version 2 - */ -class StudipFileCache implements StudipCache -{ - use StudipCacheKeyTrait; - - /** - * full path to cache directory - * - * @var string - */ - private $dir; - - /** - * @return string A translateable display name for this cache class. - */ - public static function getDisplayName(): string - { - return _('Dateisystem'); - } - - /** - * without the 'dir' argument the cache path is taken from - * $CACHING_FILECACHE_PATH or is set to - * $TMP_PATH/studip_cache - * - * @param string the path to use - * @return void - * @throws exception if the directory does not exist or could not be - * created - */ - public function __construct($path = '') - { - $this->dir = $path - ?: (Config::get()->SYSTEMCACHE['type'] == 'StudipFileCache' ? - Config::get()->SYSTEMCACHE['config']['path'] : '') - ?: $GLOBALS['CACHING_FILECACHE_PATH'] - ?: ($GLOBALS['TMP_PATH'] . '/' . 'studip_cache'); - $this->dir = rtrim($this->dir, '\\/') . '/'; - - if (!is_dir($this->dir) && !@mkdir($this->dir, 0700)) { - throw new Exception('Could not create directory: ' . $this->dir); - } - - if (!is_writable($this->dir)) { - throw new Exception('Can not write to directory: ' . $this->dir); - } - } - - /** - * get path to cache directory - * - * @return string - */ - public function getCacheDir() - { - return $this->dir; - } - - /** - * expire cache item - * - * @see StudipCache::expire() - * @param string $arg - * @return void - */ - public function expire($arg) - { - $key = $this->getCacheKey($arg); - - if ($file = $this->getPathAndFile($key)){ - @unlink($file); - } - } - - /** - * Expire all items from the cache. - */ - public function flush() - { - rmdirr($this->dir); - } - - /** - * retrieve cache item from filesystem - * tests first if item is expired - * - * @see StudipCache::read() - * @param string $arg a cache key - * @return string|bool - */ - public function read($arg) - { - $key = $this->getCacheKey($arg); - - if ($file = $this->check($key)){ - $f = @fopen($file, 'rb'); - if ($f) { - @flock($f, LOCK_SH); - $result = stream_get_contents($f); - @fclose($f); - } - return unserialize($result); - } - return false; - } - - /** - * store data as cache item in filesystem - * - * @see StudipCache::write() - * @param string $arg a cache key - * @param mixed $content data to store - * @param int $expire expiry time in seconds, default 12h - * @return int|bool the number of bytes that were written to the file, - * or false on failure - */ - public function write($arg, $content, $expire = self::DEFAULT_EXPIRATION) - { - $key = $this->getCacheKey($arg); - - $this->expire($key); - $file = $this->getPathAndFile($key, $expire); - return @file_put_contents($file, serialize($content), LOCK_EX); - } - - /** - * checks if specified cache item is expired - * if expired the cache file is deleted - * - * @param string $key a cache key to check - * @return string|bool the path to the cache file or false if expired - */ - private function check($key) - { - if ($file = $this->getPathAndFile($key)){ - list($id, $expire) = explode('-', basename($file)); - if (time() < $expire) { - return $file; - } else { - @unlink($file); - } - } - return false; - } - - /** - * get the full path to a cache file - * - * the cache files are organized in sub-folders named by - * the first two characters of the hashed cache key. - * the filename is constructed from the hashed cache key - * and the timestamp of expiration - * - * @param string $key a cache key - * @param int $expire expiry time in seconds - * @return string|bool full path to cache item or false on failure - */ - private function getPathAndFile($key, $expire = null) - { - $id = hash('md5', $key); - $path = $this->dir . mb_substr($id, 0, 2); - if (!is_dir($path) && !@mkdir($path, 0700)) { - throw new Exception('Could not create directory: ' . $path); - } - if (!is_null($expire)){ - return $path . '/' . $id . '-' . (time() + $expire); - } else { - $files = @glob("{$path}/{$id}*"); - if (count($files) > 0) { - return $files[0]; - } - } - return false; - } - - /** - * purges expired entries from the cache directory - * - * @param bool echo messages if set to false - * @return int the number of deleted files - */ - public function purge($be_quiet = true) - { - $now = time(); - $deleted = 0; - foreach (@glob($this->dir . '*', GLOB_ONLYDIR) as $current_dir){ - foreach (@glob("{$current_dir}/*") as $file){ - list($id, $expire) = explode('-', basename($file)); - if ($expire < $now) { - if (@unlink($file)) { - ++$deleted; - if (!$be_quiet) { - echo "File: {$file} deleted.\n"; - } - } - } else if (!$be_quiet){ - echo "File: {$file} expires on " . strftime('%x %X', $expire) . "\n"; - } - } - } - return $deleted; - } - - /** - * Return statistics. - * - * @return array|array[] - */ - public function getStats(): array - { - return [ - __CLASS__ => [ - 'name' => _('Anzahl Einträge'), - 'value' => DBManager::get()->fetchColumn("SELECT COUNT(*) FROM `cache`") - ] - ]; - } - - /** - * Return the Vue component name and props that handle configuration. - * - * @return array - */ - public static function getConfig(): array - { - $currentCache = Config::get()->SYSTEMCACHE; - - // Set default config for this cache - $currentConfig = [ - 'path' => $GLOBALS['TMP_PATH'] . '/studip_cache' - ]; - - // If this cache is set as system cache, use config from global settings. - if ($currentCache['type'] == __CLASS__) { - $currentConfig = $currentCache['config']; - } - - return [ - 'component' => 'FileCacheConfig', - 'props' => $currentConfig - ]; - } - -} diff --git a/lib/classes/StudipKing.class.php b/lib/classes/StudipKing.class.php index 2d1f15c..3a61c57 100644 --- a/lib/classes/StudipKing.class.php +++ b/lib/classes/StudipKing.class.php @@ -63,7 +63,7 @@ class StudipKing { private static function get_kings() { if (self::$kings === null) { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); # read cache (unserializing a cache miss - FALSE - does not matter) $kings = unserialize($cache->read(self::CACHE_KEY)); diff --git a/lib/classes/StudipMemcachedCache.php b/lib/classes/StudipMemcachedCache.php deleted file mode 100644 index 0e44dd5..0000000 --- a/lib/classes/StudipMemcachedCache.php +++ /dev/null @@ -1,157 +0,0 @@ - - * - * This program 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 2 of - * the License, or (at your option) any later version. - */ - - -/** - * Cache implementation using memcached. - * - * @package studip - * @subpackage cache - * - * @author mlunzena - * @copyright (c) Authors - * @since 5.0 - */ - -class StudipMemcachedCache implements StudipCache -{ - use StudipCacheKeyTrait; - - private $memcache; - - /** - * @return string A translateable display name for this cache class. - */ - public static function getDisplayName(): string - { - return _('Memcached'); - } - - public function __construct($servers) - { - if (!extension_loaded('memcached')) { - throw new Exception('Memcache extension missing.'); - } - - $prefix = Config::get()->STUDIP_INSTALLATION_ID; - $this->memcache = new Memcached('studip' . $prefix ? '-' . $prefix : ''); - - if (count($this->memcache->getServerList()) === 0) { - foreach ($servers as $server) { - $status = $this->memcache->addServer($server['hostname'], (int) $server['port']); - - if (!$status) { - throw new Exception("Could not add server: {$server['hostname']} @ port {$server['port']}"); - } - } - } - } - - /** - * Expire item from the cache. - * - * Example: - * - * # expires foo - * $cache->expire('foo'); - * - * @param string $arg a single key. - * @returns void - */ - public function expire($arg) - { - $key = $this->getCacheKey($arg); - $this->memcache->delete($key); - } - - /** - * Expire all items from the cache. - */ - public function flush() - { - $this->memcache->flush(); - } - - /** - * Retrieve item from the server. - * - * Example: - * - * # reads foo - * $foo = $cache->reads('foo'); - * - * @param string $arg a single key - * @returns mixed the previously stored data if an item with such a key - * exists on the server or FALSE on failure. - */ - public function read($arg) - { - $key = $this->getCacheKey($arg); - return $this->memcache->get($key); - } - - /** - * Store data at the server. - * - * @param string $arg the item's key. - * @param string $content the item's content. - * @param int $expire the item's expiry time in seconds. Defaults to 12h. - * - * @returns mixed returns TRUE on success or FALSE on failure. - * - */ - public function write($arg, $content, $expire = self::DEFAULT_EXPIRATION) - { - $key = $this->getCacheKey($arg); - return $this->memcache->set($key, $content, $expire); - } - - /** - * Return statistics. - * - * @StudipCache::getStats() - * - * @return array|array[] - */ - public function getStats(): array - { - $stats = $this->memcache->getStats(); - return $stats; - } - - /** - * Return the Vue component name and props that handle configuration. - * - * @see StudipCache::getConfig() - * - * @return array - */ - public static function getConfig(): array - { - $currentCache = Config::get()->SYSTEMCACHE; - - // Set default config for this cache - $currentConfig = [ - 'servers' => [] - ]; - - // If this cache is set as system cache, use config from global settings. - if ($currentCache['type'] == __CLASS__) { - $currentConfig = $currentCache['config']; - } - - return [ - 'component' => 'MemcachedCacheConfig', - 'props' => $currentConfig - ]; - } - -} diff --git a/lib/classes/StudipMemoryCache.class.php b/lib/classes/StudipMemoryCache.class.php deleted file mode 100644 index d38385a..0000000 --- a/lib/classes/StudipMemoryCache.class.php +++ /dev/null @@ -1,84 +0,0 @@ - - * @license GPL2 or any later version - * @since Stud.IP 5.0 - */ -class StudipMemoryCache implements StudipCache -{ - protected $memory_cache = []; - - /** - * Expires just a single key. - * - * @param string the key - */ - public function expire($key) - { - unset($this->memory_cache[$key]); - } - - /** - * Expire all items from the cache. - */ - public function flush() - { - $this->memory_cache = []; - } - - /** - * Reads just a single key from the cache. - * - * @param string the key - * - * @return mixed the corresponding value - */ - public function read($key) - { - if (!isset($this->memory_cache[$key])) { - return false; - } - if ($this->memory_cache[$key]['expires'] < time()) { - $this->expire($key); - return false; - } - return $this->memory_cache[$key]['data']; - } - - /** - * Store data at the server. - * - * @param string the item's key. - * @param mixed the item's content (will be serialized if necessary). - * @param int the item's expiry time in seconds. Defaults to 12h. - * - * @returns mixed returns TRUE on success or FALSE on failure. - * - */ - public function write($name, $content, $expires = self::DEFAULT_EXPIRATION) - { - $this->memory_cache[$name] = [ - 'expires' => time() + $expires, - 'data' => $content, - ]; - - return true; - } - - public static function getDisplayName(): string - { - return 'Memory cache'; - } - - public function getStats(): array - { - return []; - } - - public static function getConfig(): array - { - return []; - } -} diff --git a/lib/classes/StudipRedisCache.class.php b/lib/classes/StudipRedisCache.class.php deleted file mode 100644 index 7b9570b..0000000 --- a/lib/classes/StudipRedisCache.class.php +++ /dev/null @@ -1,177 +0,0 @@ - - * @license GPL2 or any later version - * @package studip - * @subpackage cache - * @since Stud.IP 5.0 - */ -class StudipRedisCache implements StudipCache -{ - use StudipCacheKeyTrait; - - private $redis; - - /** - * @return string A translateable display name for this cache class. - */ - public static function getDisplayName(): string - { - return _('Redis'); - } - - /** - * Construct a cache instance. - * - * @param string $hostname Hostname of redis server - * @param int $port Port of redis server - * @param string $auth Optional auth token/password - */ - public function __construct($hostname, $port, string $auth = '') - { - if (!extension_loaded('redis')) { - throw new Exception('Redis extension missing.'); - } - - $this->redis = new Redis(); - $status = $this->redis->connect($hostname, $port, 1); - - if (!$status) { - throw new Exception('Could not add cache.'); - } - - if ($auth !== '') { - $this->redis->auth($auth); - } - } - - /** - * Returns the instance of the redis server connection. - * - * @return Redis instance - */ - public function getRedis() - { - return $this->redis; - } - - /** - * Expire item from the cache. - * - * Example: - * - * # expires foo - * $cache->expire('foo'); - * - * @param string $arg a single key. - */ - public function expire($arg) - { - $key = $this->getCacheKey($arg); - $this->redis->unlink($key); - } - - /** - * Retrieve item from the server. - * - * Example: - * - * # reads foo - * $foo = $cache->reads('foo'); - * - * @param string $arg a single key - * @return mixed the previously stored data if an item with such a key - * exists on the server or FALSE on failure. - */ - public function read($arg) - { - $key = $this->getCacheKey($arg); - - $result = $this->redis->get($key); - - return ($result === null) ? null : unserialize($result); - } - - /** - * Store data at the server. - * - * @param string the item's key. - * @param string the item's content. - * @param int the item's expiry time in seconds. Defaults to 12h. - * @return mixed returns TRUE on success or FALSE on failure. - */ - public function write($name, $content, $expire = self::DEFAULT_EXPIRATION) - { - $key = $this->getCacheKey($name); - return $this->redis->setEx($key, $expire, serialize($content)); - } - - /** - * Expire all items from the cache. - */ - public function flush() - { - $pattern = $this->getCacheKey('*'); - foreach ($this->redis->keys($pattern) as $key) { - $this->redis->unlink($key); - } - } - - /** - * @param string $method Method to call - * @param array $args Arguments to pass - * @return false|mixed - */ - public function __call($method, $args) - { - if (is_callable([$this->redis, $method])) { - return call_user_func_array([$this->redis, $method], $args); - } - throw new BadMethodCallException("Method {$method} does not exist"); - } - - /** - * Return statistics. - * - * @StudipCache::getStats() - * - * @return array|array[] - */ - public function getStats(): array - { - $stats = $this->redis->info(); - $stats['size'] = count($this->redis->keys($this->getCacheKey('*'))); - return ["{$this->redis->getHost()}:{$this->redis->getPort()}" => $stats]; - } - - /* - * Return the Vue component name and props that handle configuration. - * - * @see StudipCache::getConfig() - * - * @return array - */ - public static function getConfig(): array - { - $currentCache = Config::get()->SYSTEMCACHE; - - // Set default config for this cache - $currentConfig = [ - 'hostname' => '', - 'port' => null - ]; - - // If this cache is set as system cache, use config from global settings. - if ($currentCache['type'] == __CLASS__) { - $currentConfig = $currentCache['config']; - $currentConfig['port'] = $currentConfig['port'] ? (int) $currentConfig['port'] : null; - } - - return [ - 'component' => 'RedisCacheConfig', - 'props' => $currentConfig - ]; - } -} diff --git a/lib/classes/UserLookup.class.php b/lib/classes/UserLookup.class.php index ddf9276..bdd9fc3 100644 --- a/lib/classes/UserLookup.class.php +++ b/lib/classes/UserLookup.class.php @@ -239,7 +239,7 @@ class UserLookup return call_user_func(self::$types[$type]['values']); } - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $cache_key = "UserLookup/{$type}/values"; $cached_values = $cache->read($cache_key); if ($cached_values) { diff --git a/lib/classes/assets/SASSCompiler.php b/lib/classes/assets/SASSCompiler.php index 2dcda2d..0b03a8c 100644 --- a/lib/classes/assets/SASSCompiler.php +++ b/lib/classes/assets/SASSCompiler.php @@ -2,7 +2,7 @@ namespace Assets; use Assets; -use StudipCacheFactory; +use Studip\Cache\Factory; use Studip; use ScssPhp\ScssPhp\Compiler as ScssCompiler; @@ -82,7 +82,7 @@ class SASSCompiler implements Compiler */ private function getPrefix() { - $cache = StudipCacheFactory::getCache(); + $cache = Studip\Cache\Factory::getCache(); $prefix = $cache->read(self::CACHE_KEY); diff --git a/lib/classes/cache/Cache.class.php b/lib/classes/cache/Cache.class.php new file mode 100644 index 0000000..366945d --- /dev/null +++ b/lib/classes/cache/Cache.class.php @@ -0,0 +1,208 @@ + + * @copyright (c) Authors + * @since 1.6 + * @license GPL2 or any later version + */ +abstract class Cache implements CacheItemPoolInterface +{ + const DEFAULT_EXPIRATION = 12 * 60 * 60; // 12 hours + + /** + * @return string A translateable display name for this cache class. + */ + abstract public static function getDisplayName(): string; + + /** + * Get some statistics from cache, like number of entries, hit rate or + * whatever the underlying cache provides. + * Results are returned in form of an array like + * "[ + * [ + * 'name' => + * 'value' => + * ] + * ]" + * + * @return array + */ + abstract public function getStats(): array; + + /** + * Return the Vue component name and props that handle configuration. + * The associative array is of the form + * [ + * 'component' => , + * 'props' => + * ] + * + * @return array + */ + abstract public static function getConfig(): array; + + /** + * Expire item from the cache. + * + * Example: + * + * # expires foo + * $cache->expire('foo'); + * + * @param string $arg a single key + */ + abstract public function expire($arg); + + /** + * Expire all items from the cache. + */ + abstract public function flush(); + + /** + * @see CacheItemPoolInterface::getItem + */ + abstract public function getItem($key); + + /** + * @see CacheItemPoolInterface::hasItem + */ + abstract public function hasItem($key); + + /** + * @var array An array of deferred items that shall be saved only + * when commit() is called. This is only used in PSR-6 cache methods. + */ + protected array $deferred_items = []; + + /** + * Retrieve item from the server. + * + * Example: + * + * # reads foo + * $foo = $cache->reads('foo'); + * + * @param string $arg a single key + * + * @return mixed the previously stored data if an item with such a key + * exists on the server or FALSE on failure. + * + * @deprecated To be removed with Stud.IP 7.0. + */ + public function read($arg) + { + $item = $this->getItem($arg); + if ($item->isHit()) { + return $item->get(); + } + return false; + } + + /** + * Store data at the server. + * + * @param string $name the item's key. + * @param mixed $content the item's content (will be serialized if necessary). + * @param int $expires the item's expiry time in seconds. Optional, defaults to 12h. + * + * @return bool returns TRUE on success or FALSE on failure. + + * @deprecated To be removed with Stud.IP 7.0. + */ + public function write($name, $content, $expires = self::DEFAULT_EXPIRATION) + { + $item = new Item($name, $content, $expires); + + return $this->save($item); + } + + /** + * Calculates the expiration by a cache item. If that cannot be determined, + * the default expiration period is returned. + * + * @param Item $item The item from which to get the expiration time. + * + * @return int The time from now until the expiration in seconds. + */ + public function getExpiration(CacheItemInterface $item) : int + { + $expiration = self::DEFAULT_EXPIRATION; + if ($item instanceof Item) { + $expiration = $item->getExpirationInSeconds(); + } + return $expiration; + } + + // PSR-6 CacheItemPoolInterface: + + /** + * @see CacheItemPoolInterface::getItems + */ + public function getItems(array $keys = []) + { + $items = []; + foreach ($keys as $key) { + $item = $this->getItem($key); + if ($item instanceof Item) { + $items[] = $item; + } + } + return $items; + } + + /** + * @see CacheItemPoolInterface::clear + */ + public function clear() + { + $this->deferred_items = []; + $this->flush(); + } + + /** + * @see CacheItemPoolInterface::deleteItem + */ + public function deleteItem($key) + { + $this->expire($key); + } + + /** + * @see CacheItemPoolInterface::deleteItems + */ + public function deleteItems(array $keys) + { + foreach ($keys as $key) { + $this->expire($key); + } + } + + /** + * @see CacheItemPoolInterface::saveDeferred + */ + public function saveDeferred(CacheItemInterface $item) + { + $this->deferred_items[] = $item; + } + + /** + * @see CacheItemPoolInterface::commit + */ + public function commit() + { + foreach ($this->deferred_items as $item) { + $this->save($item); + } + } +} diff --git a/lib/classes/cache/DbCache.class.php b/lib/classes/cache/DbCache.class.php new file mode 100644 index 0000000..3361af0 --- /dev/null +++ b/lib/classes/cache/DbCache.class.php @@ -0,0 +1,142 @@ + + */ +class DbCache extends Cache +{ + /** + * @return string A display name (that can be translated) for this cache class. + */ + public static function getDisplayName(): string + { + return _('Datenbank'); + } + + /** + * Expire item from the cache. + * + * @param string $arg a single key + */ + public function expire($arg) + { + $db = DBManager::get(); + + $stmt = $db->prepare('DELETE FROM cache WHERE cache_key = ?'); + $stmt->execute([$arg]); + } + + /** + * Expire all items from the cache. + */ + public function flush() + { + $db = DBManager::get(); + + $db->exec('TRUNCATE TABLE cache'); + } + + /** + * Delete all expired items from the cache. + */ + public function purge() + { + $db = DBManager::get(); + + $stmt = $db->prepare('DELETE FROM cache WHERE expires < ?'); + $stmt->execute([time()]); + } + + /** + * Return statistics. + * + * @return array|array[] + *@see Cache::getStats() + * + */ + public function getStats(): array + { + return [ + __CLASS__ => [ + 'name' => _('Anzahl Einträge'), + 'value' => DBManager::get()->fetchColumn("SELECT COUNT(*) FROM `cache`") + ] + ]; + } + + /** + * Return the Vue component name and props that handle configuration. + * + * @return array + *@see Cache::getConfig() + * + */ + public static function getConfig(): array + { + return [ + 'component' => null, + 'props' => [] + ]; + } + + /** + * @inheritDoc + */ + public function getItem($key) + { + $query = "SELECT `content`, `expires` + FROM `cache` + WHERE `cache_key` = :key + AND `expires` > UNIX_TIMESTAMP()"; + $result = DBManager::get()->fetchOne($query, [':key' => $key]); + + $item = new Item($key); + if (!empty($result)) { + $item->setHit(); + if ($result['content']) { + $item->set(unserialize($result['content'])); + } + if ($result['expires']) { + $expiration = new \DateTime(); + $expiration->setTimestamp($result['expires']); + $item->expiresAt($expiration); + } + } + return $item; + } + + /** + * @inheritDoc + */ + public function hasItem($key) + { + $query = "SELECT 1 + FROM `cache` + WHERE `cache_key` = :key + AND `expires` > UNIX_TIMESTAMP()"; + return (bool) DBManager::get()->fetchColumn($query, [':key' => $key]); + } + + /** + * @inheritDoc + */ + public function save(\Psr\Cache\CacheItemInterface $item) + { + $expiration = $this->getExpiration($item); + if ($expiration < 1) { + // The item would expire immediately. + return false; + } + + return DBManager::get()->execute( + 'REPLACE INTO `cache` VALUES (?, ?, ?)', + [$item->getKey(), serialize($item->get()), $expiration] + ); + } +} diff --git a/lib/classes/cache/Exception.php b/lib/classes/cache/Exception.php new file mode 100644 index 0000000..64ee364 --- /dev/null +++ b/lib/classes/cache/Exception.php @@ -0,0 +1,27 @@ + + * @copyright 2024 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 6.0 + */ + +namespace Studip\Cache; + +/** + * The CacheException class is an implementation of the CacheException interface + * of PSR-6 that behaves like a StudipException. + */ +class Exception extends \StudipException implements \Psr\Cache\CacheException +{ + //Nothing here, since there is nothing to implement. +} diff --git a/lib/classes/cache/Factory.class.php b/lib/classes/cache/Factory.class.php new file mode 100644 index 0000000..b5c8359 --- /dev/null +++ b/lib/classes/cache/Factory.class.php @@ -0,0 +1,206 @@ +getMessage()); + PageLayout::addBodyElements(MessageBox::error(__METHOD__ . ': ' . $e->getMessage())); + $class = self::DEFAULT_CACHE_CLASS; + self::$cache = new $class(); + } + } + + // If proxy should be used, inject it. Otherwise apply pending + // operations, if any. + if ($proxied) { + self::$cache = new Proxy(self::$cache); + } elseif ($GLOBALS['CACHING_ENABLE'] && $apply_proxied_operations) { + // Even if the above condition will try to eliminate most + // failures, the following operation still needs to be wrapped + // in a try/catch block. Otherwise there are no means to + // execute migration 166 which creates the neccessary tables + // for said operation. + try { + StudipCacheOperation::apply(self::$cache); + } catch (\Exception $e) { + } + } + } + + return self::$cache; + } + + + /** + * Load configured cache class and return its name. + * + * @return string the name of the configured cache class + */ + public static function loadCacheClass() + { + $cacheConfig = self::getConfig()->SYSTEMCACHE; + + $cache_class = $cacheConfig['type'] ?: null; + + # default class + if ($cache_class === null) { + $version = new DBSchemaVersion(); + if ($version->get(1) < 224) { + // db cache is not yet available, use StudipMemoryCache + return 'StudipMemoryCache'; + } + + return self::DEFAULT_CACHE_CLASS; + } + + if (!class_exists($cache_class)) { + throw new UnexpectedValueException("Could not find class: '$cache_class'"); + } + + return $cache_class; + } + + /** + * Return an array of arguments required for instantiation of the cache + * class. + * + * @return array the array of arguments + */ + public static function retrieveConstructorArguments() + { + $cacheConfig = self::getConfig()->SYSTEMCACHE; + + return $cacheConfig ?: []; + } + + /** + * Return an instance of a given class using some arguments. Unless the + * memory cache is instantiated, the cache will be wrapped in a wrapper + * class that uses a memory cache to reduce accesses to the cache. + * + * @param string $class the name of the class + * @param array $arguments an array of arguments to be used by the constructor + * + * @return Cache an instance of the specified class + * @throws \ReflectionException + */ + public static function instantiateCache($class, $arguments) + { + $reflection_class = new ReflectionClass($class); + $cache = (is_array($arguments['config']) && count($arguments['config']) > 0) + ? $reflection_class->newInstanceArgs($arguments['config']) + : $reflection_class->newInstance(); + + if ($class !== MemoryCache::class) { + return new Wrapper($cache); + } + + return $cache; + } +} diff --git a/lib/classes/cache/FileCache.class.php b/lib/classes/cache/FileCache.class.php new file mode 100644 index 0000000..e94395a --- /dev/null +++ b/lib/classes/cache/FileCache.class.php @@ -0,0 +1,277 @@ + + * @copyright 2007 André Noack + * @license GPL2 or any later version + */ +class FileCache extends Cache +{ + use KeyTrait; + + /** + * full path to cache directory + * + * @var string + */ + private string $dir; + + /** + * @return string A translateable display name for this cache class. + */ + public static function getDisplayName(): string + { + return _('Dateisystem'); + } + + /** + * without the 'dir' argument the cache path is taken from + * $CACHING_FILECACHE_PATH or is set to + * $TMP_PATH/studip_cache + * + * @param string $path the path to use + * @throws Exception if the directory does not exist or could not be + * created + */ + public function __construct(string $path = '') + { + $this->dir = $path + ?: ( + Config::get()->SYSTEMCACHE['type'] === self::class + ? Config::get()->SYSTEMCACHE['config']['path'] + : '' + ) + ?: $GLOBALS['CACHING_FILECACHE_PATH'] + ?: ($GLOBALS['TMP_PATH'] . '/' . 'studip_cache'); + $this->dir = rtrim($this->dir, '\\/') . '/'; + + if (!is_dir($this->dir) && !@mkdir($this->dir, 0700)) { + throw new \Exception('Could not create directory: ' . $this->dir); + } + + if (!is_writable($this->dir)) { + throw new \Exception('Can not write to directory: ' . $this->dir); + } + } + + /** + * get path to cache directory + * + * @return string + */ + public function getCacheDir() + { + return $this->dir; + } + + /** + * expire cache item + * + * @param string $arg + * + * @return void + * @throws Exception + * @see Cache::expire() + */ + public function expire($arg) + { + $key = $this->getCacheKey($arg); + + if ($file = $this->getPathAndFile($key)){ + @unlink($file); + } + } + + /** + * Expire all items from the cache. + */ + public function flush() + { + rmdirr($this->dir); + } + + /** + * checks if specified cache item is expired + * if expired the cache file is deleted + * + * @param string $key a cache key to check + * + * @return array|bool the path to the cache file or false if expired + * @throws Exception + */ + private function check($key) + { + if ($file = $this->getPathAndFile($key)){ + [$id, $expire] = explode('-', basename($file)); + if (time() < $expire) { + return [$file, $expire]; + } else { + @unlink($file); + } + } + return false; + } + + /** + * get the full path to a cache file + * + * the cache files are organized in sub-folders named by + * the first two characters of the hashed cache key. + * the filename is constructed from the hashed cache key + * and the timestamp of expiration + * + * @param string $key a cache key + * @param int|null $expire expiry time in seconds + * + * @return string|bool full path to cache item or false on failure + * @throws Exception + */ + private function getPathAndFile(string $key, ?int $expire = null): bool|string + { + $id = hash('md5', $key); + $path = $this->dir . mb_substr($id, 0, 2); + if (!is_dir($path) && !@mkdir($path, 0700)) { + throw new \Exception('Could not create directory: ' . $path); + } + if (!is_null($expire)){ + return $path . '/' . $id . '-' . (time() + $expire); + } else { + $files = @glob("{$path}/{$id}*"); + if (count($files) > 0) { + return $files[0]; + } + } + return false; + } + + /** + * purges expired entries from the cache directory + * + * @param bool $be_quiet echo messages if set to false + * + * @return int the number of deleted files + */ + public function purge(bool $be_quiet = true): int + { + $now = time(); + $deleted = 0; + foreach (@glob($this->dir . '*', GLOB_ONLYDIR) as $current_dir){ + foreach (@glob("{$current_dir}/*") as $file){ + [$id, $expire] = explode('-', basename($file)); + if ($expire < $now) { + if (@unlink($file)) { + ++$deleted; + if (!$be_quiet) { + echo "File: {$file} deleted.\n"; + } + } + } else if (!$be_quiet) { + echo "File: {$file} expires on " . date('Y-m-d H:i:s', $expire) . "\n"; + } + } + } + return $deleted; + } + + /** + * Return statistics. + * + * @return array|array[] + */ + public function getStats(): array + { + return [ + __CLASS__ => [ + 'name' => _('Anzahl Einträge'), + 'value' => \DBManager::get()->fetchColumn("SELECT COUNT(*) FROM `cache`") + ] + ]; + } + + /** + * Return the Vue component name and props that handle configuration. + * + * @return array + */ + public static function getConfig(): array + { + $currentCache = Config::get()->SYSTEMCACHE; + + // Set default config for this cache + $currentConfig = [ + 'path' => $GLOBALS['TMP_PATH'] . '/studip_cache' + ]; + + // If this cache is set as system cache, use config from global settings. + if ($currentCache['type'] == __CLASS__) { + $currentConfig = $currentCache['config']; + } + + return [ + 'component' => 'FileCacheConfig', + 'props' => $currentConfig + ]; + } + + /** + * @inheritDoc + */ + public function getItem($key) + { + $real_key = $this->getCacheKey($key); + + $item = new \Studip\Cache\Item($key); + + $file_data = $this->check($real_key); + if ($file_data) { + $file = $file_data[0]; + $expire = $file_data[1]; + $f = @fopen($file, 'rb'); + if ($f) { + @flock($f, LOCK_SH); + $result = stream_get_contents($f); + @fclose($f); + } + $item->setHit(); + $item->set(unserialize($result)); + $expiration = new \DateTime(); + $expiration->setTimestamp($expire); + $item->expiresAt($expiration); + } + return $item; + } + + /** + * @inheritDoc + */ + public function hasItem($key) + { + $real_key = $this->getCacheKey($key); + $file_data = $this->check($real_key); + return $file_data !== false; + } + + /** + * @inheritDoc + */ + public function save(\Psr\Cache\CacheItemInterface $item) + { + $expiration = $this->getExpiration($item); + if ($expiration < 1) { + //The item would expire immediately. + return false; + } + + $real_key = $this->getCacheKey($item->getKey()); + $this->expire($real_key); + $file = $this->getPathAndFile($real_key, $expiration); + return @file_put_contents($file, serialize($item->get()), LOCK_EX); + } +} diff --git a/lib/classes/cache/InvalidCacheArgumentException.php b/lib/classes/cache/InvalidCacheArgumentException.php new file mode 100644 index 0000000..e85c6b2 --- /dev/null +++ b/lib/classes/cache/InvalidCacheArgumentException.php @@ -0,0 +1,28 @@ + + * @copyright 2024 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 6.0 + */ + +namespace Studip\Cache; + + +/** + * The InvalidCacheArgumentException is an implementation of the InvalidArgumentException interface + * of PSR-6 that behaves like a StudipException. + */ +class InvalidCacheArgumentException extends \StudipException implements \Psr\Cache\InvalidArgumentException +{ + //Nothing here, since there is nothing to implement. +} diff --git a/lib/classes/cache/Item.class.php b/lib/classes/cache/Item.class.php new file mode 100644 index 0000000..1a09f1d --- /dev/null +++ b/lib/classes/cache/Item.class.php @@ -0,0 +1,163 @@ + + * @copyright 2024 + * @license http://www.gnu.org/licenses/gpl-2.0.html GPL version 2 + * @category Stud.IP + * @since 6.0 + */ + +namespace Studip\Cache; + +use DateInterval; +use DateTime; +use Psr\Cache\CacheItemInterface; + +/** + * \Studip\Cache\CacheItem implements the CacheItemInterface of PSR-6. It holds the value and the + * key of a cache item and also provides additional methods to get the expiration of the item. + */ +class Item implements CacheItemInterface +{ + /** + * @var string The key of the item in the cache. + */ + protected string $key; + + /** + * @var mixed The value of the item. + */ + protected mixed $value; + + /** + * @var DateTime|null The expiration as DateTime object or null if the expiration is not defined. + */ + protected ?DateTime $expiration = null; + + /** + * @var bool An indicator whether the item has been found in the cache (true) or not (false). + */ + protected bool $cache_hit = false; + + /** + * The constructor of \Studip\Cache\CacheItem. + * + * @param string $key The key of the item in the cache. + * @param mixed $value The value of the item. + * @param int|null $expiration The expiration of the item in seconds, if applicable. + * @param bool $cache_hit Whether the item shall be constructed as cache hit (true) or not (false). + * + */ + public function __construct( + string $key, + mixed $value = null, + ?int $expiration = null, + bool $cache_hit = false + ) { + $this->key = $key; + $this->value = $value; + $this->cache_hit = $cache_hit; + $this->expiresAfter($expiration); + } + + /** + * @inheritDoc + */ + public function getKey() + { + return $this->key; + } + + /** + * @inheritDoc + */ + public function get() + { + return $this->value; + } + + /** + * @inheritDoc + */ + public function isHit() + { + return $this->cache_hit; + } + + /** + * @inheritDoc + */ + public function set($value) + { + $this->value = $value; + } + + /** + * @inheritDoc + */ + public function expiresAt($expiration) + { + $this->expiration = $expiration; + return $this; + } + + /** + * @inheritDoc + */ + public function expiresAfter($time) + { + $this->expiration = new DateTime(); + if ($time instanceof DateInterval) { + $this->expiration = $this->expiration->add($time); + } elseif (is_integer($time)) { + $this->expiration->setTimestamp(time() + $time); + } else { + $this->expiration->setTimestamp(time() + Cache::DEFAULT_EXPIRATION); + } + return $this; + } + + // \Studip\Cache\CacheItem specific methods: + + /** + * Sets the item to be a cache hit. + * + * @return void + */ + public function setHit() : void + { + $this->cache_hit = true; + } + + /** + * Returns the expiration, if set. + * + * @return DateTime|null A DateTime object with the expiration date and time + * or null if the expiration is not defined. + */ + public function getExpiration() : ?DateTime + { + return $this->expiration; + } + + /** + * Returns the seconds from the current timestamp until the expiration of the item. + * + * @return int The seconds until the item expires + */ + public function getExpirationInSeconds() : int + { + if ($this->expiration) { + return $this->expiration->getTimestamp() - time(); + } + return 0; + } +} diff --git a/lib/classes/cache/KeyTrait.php b/lib/classes/cache/KeyTrait.php new file mode 100644 index 0000000..021aaba --- /dev/null +++ b/lib/classes/cache/KeyTrait.php @@ -0,0 +1,31 @@ + + * @license GPL2 or any later version + * @package studip + * @subpackage cache + * @since Stud.IP 5.0 + */ +trait KeyTrait +{ + protected ?string $cache_prefix = null; + + /** + * Returns a prefix cache key based on db configuration. + * + * @param string $offset + * @return string + */ + protected function getCacheKey(string $offset): string + { + if ($this->cache_prefix === null) { + $this->cache_prefix = md5("{$GLOBALS['DB_STUDIP_HOST']}|{$GLOBALS['DB_STUDIP_DATABASE']}"); + } + return "$this->cache_prefix/$offset"; + } +} diff --git a/lib/classes/cache/MemcachedCache.class.php b/lib/classes/cache/MemcachedCache.class.php new file mode 100644 index 0000000..5d9033b --- /dev/null +++ b/lib/classes/cache/MemcachedCache.class.php @@ -0,0 +1,151 @@ + + * @copyright (c) Authors + * @license GPL2 or any later version + * @since 5.0 + */ +class MemcachedCache extends Cache +{ + use KeyTrait; + + private Memcached $memcache; + + /** + * @return string A translateable display name for this cache class. + */ + public static function getDisplayName(): string + { + return _('Memcached'); + } + + public function __construct($servers) + { + if (!extension_loaded('memcached')) { + throw new \Exception('Memcache extension missing.'); + } + + $prefix = \Config::get()->STUDIP_INSTALLATION_ID; + $this->memcache = new Memcached('studip' . ($prefix ? '-' . $prefix : '')); + + if (count($this->memcache->getServerList()) === 0) { + foreach ($servers as $server) { + $status = $this->memcache->addServer($server['hostname'], (int) $server['port']); + + if (!$status) { + throw new \Exception("Could not add server: {$server['hostname']} @ port {$server['port']}"); + } + } + } + } + + /** + * Expire item from the cache. + * + * Example: + * + * # expires foo + * $cache->expire('foo'); + * + * @param string $arg a single key. + * @returns void + */ + public function expire($arg) + { + $key = $this->getCacheKey($arg); + $this->memcache->delete($key); + } + + /** + * Expire all items from the cache. + */ + public function flush() + { + $this->memcache->flush(); + } + + /** + * Return statistics. + * + * @StudipCache::getStats() + * + * @return array|array[] + */ + public function getStats(): array + { + return $this->memcache->getStats(); + } + + /** + * Return the Vue component name and props that handle configuration. + * + * @see Cache::getConfig() + * + * @return array + */ + public static function getConfig(): array + { + $currentCache = \Config::get()->SYSTEMCACHE; + + // Set default config for this cache + $currentConfig = [ + 'servers' => [] + ]; + + // If this cache is set as system cache, use config from global settings. + if ($currentCache['type'] == __CLASS__) { + $currentConfig = $currentCache['config']; + } + + return [ + 'component' => 'MemcachedCacheConfig', + 'props' => $currentConfig + ]; + } + + /** + * @inheritDoc + */ + public function getItem($key) + { + $item = new Item($key); + $value = $this->memcache->get($this->getCacheKey($key)); + if ($this->memcache->getResultCode() !== Memcached::RES_NOTFOUND) { + // Set the value, even if it is the boolean value false: + $item->setHit(); + $item->set($value); + } + return $item; + } + + /** + * @inheritDoc + */ + public function hasItem($key) + { + return $this->memcache->checkKey($this->getCacheKey($key)); + } + + /** + * @inheritDoc + */ + public function save(CacheItemInterface $item) + { + $expiration = $this->getExpiration($item); + if ($expiration < 1) { + // The item would expire immediately. + return false; + } + + $real_key = $this->getCacheKey($item->getKey()); + return $this->memcache->set($real_key, $item->get(), $expiration); + } +} diff --git a/lib/classes/cache/MemoryCache.class.php b/lib/classes/cache/MemoryCache.class.php new file mode 100644 index 0000000..93f6bbd --- /dev/null +++ b/lib/classes/cache/MemoryCache.class.php @@ -0,0 +1,98 @@ + + * @license GPL2 or any later version + * @since Stud.IP 5.0 + */ +class MemoryCache extends Cache +{ + protected array $memory_cache = []; + + /** + * Expires just a single key. + * + * @param string the key + */ + public function expire($key) + { + unset($this->memory_cache[$key]); + } + + /** + * Expire all items from the cache. + */ + public function flush() + { + $this->memory_cache = []; + } + + public static function getDisplayName(): string + { + return 'Memory cache'; + } + + public function getStats(): array + { + return []; + } + + public static function getConfig(): array + { + return []; + } + + /** + * @inheritDoc + */ + public function getItem($key) + { + $item = new Item($key); + if (!isset($this->memory_cache[$key])) { + return $item; + } + if ($this->memory_cache[$key]['expires'] < time()) { + $this->expire($key); + return $item; + } + $item->setHit(); + $item->set($this->memory_cache[$key]['data']); + if (!empty($this->memory_cache[$key]['expires'])) { + $expiration = new DateTime(); + $expiration->setTimestamp($this->memory_cache[$key]['expires']); + $item->expiresAt($expiration); + } + return $item; + } + + /** + * @inheritDoc + */ + public function hasItem($key) + { + return isset($this->memory_cache[$key]) + && $this->memory_cache[$key]['expires'] < time(); + } + + /** + * @inheritDoc + */ + public function save(CacheItemInterface $item) + { + $expiration = $this->getExpiration($item); + + $this->memory_cache[$item->getKey()] = [ + 'expires' => $expiration + time(), + 'data' => $item->get(), + ]; + + return true; + } +} diff --git a/lib/classes/cache/Proxy.class.php b/lib/classes/cache/Proxy.class.php new file mode 100644 index 0000000..6791fb7 --- /dev/null +++ b/lib/classes/cache/Proxy.class.php @@ -0,0 +1,122 @@ + + * @license GPL2 or any later version + * @since Stud.IP 3.3 + */ +class Proxy extends Cache +{ + protected Cache $actual_cache; + protected array $proxy_these; + + /** + * @param Cache $cache The actual cache object + * @param mixed $proxy_these List of operations to proxy (should be an + * array but a space seperated string is also + * valid) + */ + public function __construct(Cache $cache, $proxy_these = ['expire']) + { + if (!is_array($proxy_these)) { + $proxy_these = words($proxy_these); + } + + $this->actual_cache = $cache; + $this->proxy_these = is_array($proxy_these) + ? $proxy_these + : words($proxy_these); + } + + /** + * Expires just a single key. + * + * @param string $key The item's key + */ + public function expire($key) + { + if (in_array('expire', $this->proxy_these)) { + try { + $operation = new StudipCacheOperation([$key, 'expire']); + $operation->parameters = serialize([]); + $operation->store(); + } catch (\Exception $e) { + } + } + + return $this->actual_cache->expire($key); + } + + /** + * Expire all items from the cache. + */ + public function flush() + { + if (in_array('flush', $this->proxy_these)) { + try { + $operation = new StudipCacheOperation(['', 'flush']); + $operation->parameters = serialize([]); + $operation->store(); + } catch (\Exception $e) { + } + } + + return $this->actual_cache->flush(); + } + + public static function getDisplayName(): string + { + return static::class; + } + + public function getStats(): array + { + return $this->actual_cache->getStats(); + } + + public static function getConfig(): array + { + return []; + } + + /** + * @inheritDoc + */ + public function getItem($key) + { + return $this->actual_cache->getItem($key); + } + + /** + * @inheritDoc + */ + public function hasItem($key) + { + return $this->actual_cache->hasItem($key); + } + + /** + * @inheritDoc + */ + public function save(\Psr\Cache\CacheItemInterface $item) + { + if (in_array('save', $this->proxy_these)) { + try { + $operation = new StudipCacheOperation([$item->getKey(), 'save']); + $operation->parameters = serialize([$item]); + $operation->store(); + } catch (\Exception $e) { + } + } + + return $this->actual_cache->save($item); + } +} diff --git a/lib/classes/cache/RedisCache.class.php b/lib/classes/cache/RedisCache.class.php new file mode 100644 index 0000000..9ea8711 --- /dev/null +++ b/lib/classes/cache/RedisCache.class.php @@ -0,0 +1,198 @@ + + * @license GPL2 or any later version + * @package studip + * @subpackage cache + * @since Stud.IP 5.0 + */ +class RedisCache extends Cache +{ + use KeyTrait; + + private $redis; + + /** + * @return string A translateable display name for this cache class. + */ + public static function getDisplayName(): string + { + return _('Redis'); + } + + /** + * Construct a cache instance. + * + * @param string $hostname Hostname of redis server + * @param int $port Port of redis server + * @param string $auth Optional auth token/password + * + * @throws RedisException + */ + public function __construct($hostname, $port, string $auth = '') + { + if (!extension_loaded('redis')) { + throw new Exception('Redis extension missing.'); + } + + $this->redis = new Redis(); + $status = $this->redis->connect($hostname, $port, 1); + + if (!$status) { + throw new Exception('Could not add cache.'); + } + + if ($auth !== '') { + $this->redis->auth($auth); + } + } + + /** + * Returns the instance of the redis server connection. + * + * @return Redis instance + */ + public function getRedis() + { + return $this->redis; + } + + /** + * Expire item from the cache. + * + * Example: + * + * # expires foo + * $cache->expire('foo'); + * + * @param string $arg a single key. + */ + public function expire($arg) + { + $key = $this->getCacheKey($arg); + $this->redis->unlink($key); + } + + /** + * Expire all items from the cache. + */ + public function flush() + { + $pattern = $this->getCacheKey('*'); + foreach ($this->redis->keys($pattern) as $key) { + $this->redis->unlink($key); + } + } + + /** + * @param string $method Method to call + * @param array $args Arguments to pass + * @return false|mixed + */ + public function __call($method, $args) + { + if (is_callable([$this->redis, $method])) { + return call_user_func_array([$this->redis, $method], $args); + } + throw new BadMethodCallException("Method {$method} does not exist"); + } + + /** + * Return statistics. + * + * @StudipCache::getStats() + * + * @return array|array[] + */ + public function getStats(): array + { + $stats = $this->redis->info(); + $stats['size'] = count($this->redis->keys($this->getCacheKey('*'))); + return ["{$this->redis->getHost()}:{$this->redis->getPort()}" => $stats]; + } + + /* + * Return the Vue component name and props that handle configuration. + * + * @see StudipCache::getConfig() + * + * @return array + */ + public static function getConfig(): array + { + $currentCache = Config::get()->SYSTEMCACHE; + + // Set default config for this cache + $currentConfig = [ + 'hostname' => '', + 'port' => null + ]; + + // If this cache is set as system cache, use config from global settings. + if ($currentCache['type'] == __CLASS__) { + $currentConfig = $currentCache['config']; + $currentConfig['port'] = $currentConfig['port'] ? (int) $currentConfig['port'] : null; + } + + return [ + 'component' => 'RedisCacheConfig', + 'props' => $currentConfig + ]; + } + + /** + * @inheritDoc + */ + public function getItem($key) + { + $item = new Item($key); + $real_key = $this->getCacheKey($key); + $result = $this->redis->get($real_key); + if ($result === null) { + return $item; + } + $item->setHit(); + $item->set(unserialize($result)); + $expiration = new DateTime(); + $expiration->setTimestamp($this->redis->expiretime($real_key)); + $item->expiresAt($expiration); + return $item; + } + + /** + * @inheritDoc + */ + public function hasItem($key) + { + $real_key = $this->getCacheKey($key); + return $this->redis->get($real_key) !== null; + } + + /** + * @inheritDoc + */ + public function save(CacheItemInterface $item) + { + $expiration = $this->getExpiration($item); + if ($expiration < 1) { + // The item would expire immediately. + return false; + } + + $real_key = $this->getCacheKey($item->getKey()); + return $this->redis->setEx($real_key, $expiration, serialize($item->get())); + } +} diff --git a/lib/classes/cache/Wrapper.class.php b/lib/classes/cache/Wrapper.class.php new file mode 100644 index 0000000..7448932 --- /dev/null +++ b/lib/classes/cache/Wrapper.class.php @@ -0,0 +1,95 @@ + + * @license GPL2 or any later version + * @since Stud.IP 5.4 + */ +class Wrapper extends Cache +{ + protected Cache $actual_cache; + protected MemoryCache $memory_cache; + + public function __construct(Cache $actual_cache) + { + $this->actual_cache = $actual_cache; + $this->memory_cache = new MemoryCache(); + } + + /** + * @inheritdoc + */ + public function expire($arg) + { + $this->memory_cache->expire($arg); + $this->actual_cache->expire($arg); + } + + /** + * @inheritdoc + */ + public function flush() + { + $this->memory_cache->flush(); + $this->actual_cache->flush(); + } + + public static function getDisplayName(): string + { + return static::class; + } + + public function getStats(): array + { + return $this->actual_cache->getStats(); + } + + public static function getConfig(): array + { + return []; + } + + /** + * @inheritDoc + */ + public function getItem($key) + { + $cached = $this->memory_cache->getItem($key); + if ($cached->isHit()) { + return $cached; + } + + $cached = $this->actual_cache->getItem($key); + if ($cached->isHit()) { + $this->memory_cache->save($cached); + } + return $cached; + } + + /** + * @inheritDoc + */ + public function hasItem($key) + { + return $this->actual_cache->hasItem($key); + } + + /** + * @inheritDoc + */ + public function save(CacheItemInterface $item) + { + if ($this->actual_cache->save($item)) { + return $this->memory_cache->save($item); + } else { + return false; + } + } +} diff --git a/lib/classes/cas/CAS_PGTStorage_Cache.php b/lib/classes/cas/CAS_PGTStorage_Cache.php index 284b591..61ce9fa 100644 --- a/lib/classes/cas/CAS_PGTStorage_Cache.php +++ b/lib/classes/cas/CAS_PGTStorage_Cache.php @@ -43,7 +43,7 @@ class CAS_PGTStorage_Cache extends CAS_PGTStorage_AbstractStorage */ public function write($pgt, $pgt_iou) { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $cache_key = 'pgtiou/' . $pgt_iou; return $cache->write($cache_key, $pgt); } @@ -58,7 +58,7 @@ class CAS_PGTStorage_Cache extends CAS_PGTStorage_AbstractStorage */ public function read($pgt_iou) { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $cache_key = 'pgtiou/' . $pgt_iou; $pgt = $cache->read($cache_key); $cache->expire($cache_key); diff --git a/lib/cronjobs/purge_cache.class.php b/lib/cronjobs/purge_cache.class.php index 4e5682f..d2e3697 100644 --- a/lib/cronjobs/purge_cache.class.php +++ b/lib/cronjobs/purge_cache.class.php @@ -70,7 +70,7 @@ class PurgeCacheJob extends CronJob */ public function setUp() { - require_once 'lib/classes/StudipFileCache.class.php'; + require_once 'lib/classes/cache/FileCache.class.php'; } /** @@ -86,7 +86,7 @@ class PurgeCacheJob extends CronJob */ public function execute($last_result, $parameters = []) { - $cache = new StudipFileCache(); + $cache = new \Studip\Cache\FileCache(); $cache->purge(empty($parameters['verbose'])); } } diff --git a/lib/functions.php b/lib/functions.php index 0e806aa..7d9f4f2 100644 --- a/lib/functions.php +++ b/lib/functions.php @@ -636,7 +636,7 @@ function get_users_online($active_time = 5, $name_format = 'full_rev') */ function get_users_online_count($active_time = 10) { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $online_count = $cache->read("online_count/{$active_time}"); if ($online_count === false) { $query = "SELECT COUNT(*) FROM user_online diff --git a/lib/models/CourseDate.class.php b/lib/models/CourseDate.class.php index 59fbb9c..eadeb0a 100644 --- a/lib/models/CourseDate.class.php +++ b/lib/models/CourseDate.class.php @@ -364,7 +364,7 @@ class CourseDate extends SimpleORMap implements PrivacyObject, Event // load room-booking, if any $this->room_booking; - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $cache->expire('course/undecorated_data/'. $this->range_id); return parent::store(); } @@ -376,7 +376,7 @@ class CourseDate extends SimpleORMap implements PrivacyObject, Event */ public function delete() { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $cache->expire('course/undecorated_data/'. $this->range_id); return parent::delete(); } @@ -433,7 +433,7 @@ class CourseDate extends SimpleORMap implements PrivacyObject, Event $folders = array_merge($folders, $topic->folders->getArrayCopy()); } foreach ($folders as $folder) { - list($files, $typed_folders) = array_values(FileManager::getFolderFilesRecursive($folder->getTypedFolder(), $user_id)); + [$files, $typed_folders] = array_values(FileManager::getFolderFilesRecursive($folder->getTypedFolder(), $user_id)); foreach ($files as $file) { $all_files[$file->id] = $file; } diff --git a/lib/models/OERMaterial.php b/lib/models/OERMaterial.php index 6ec7de8..8411d14 100644 --- a/lib/models/OERMaterial.php +++ b/lib/models/OERMaterial.php @@ -164,7 +164,7 @@ class OERMaterial extends SimpleORMap public static function fetchRemoteSearch($text, $tag = false) { $cache_name = "oer_remote_searched_for_".md5($text)."_".($tag ? 1 : 0); - $already_searched = (bool) StudipCacheFactory::getCache()->read($cache_name); + $already_searched = (bool) \Studip\Cache\Factory::getCache()->read($cache_name); if (!$already_searched) { $hosts = OERHost::findBySQL("index_server = '1' AND allowed_as_index_server = '1' ORDER BY RAND()"); foreach ($hosts as $host) { @@ -172,7 +172,7 @@ class OERMaterial extends SimpleORMap $host->fetchRemoteSearch($text, $tag); } } - StudipCacheFactory::getCache()->write($cache_name, "1", 60); + \Studip\Cache\Factory::getCache()->write($cache_name, "1", 60); } } diff --git a/lib/models/PersonalNotifications.class.php b/lib/models/PersonalNotifications.class.php index 721038c..12d9c1b 100644 --- a/lib/models/PersonalNotifications.class.php +++ b/lib/models/PersonalNotifications.class.php @@ -262,7 +262,7 @@ class PersonalNotifications extends SimpleORMap */ protected static function getCache($user_id) { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $hash = self::getCacheHash($user_id); $cached = $cache->read($hash); @@ -281,7 +281,7 @@ class PersonalNotifications extends SimpleORMap */ protected static function setCache($user_id, $items) { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $hash = self::getCacheHash($user_id); $cache->write($hash, serialize($items), self::CACHE_DURATION); } @@ -293,7 +293,7 @@ class PersonalNotifications extends SimpleORMap */ protected static function expireCache($user_id) { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $hash = self::getCacheHash($user_id); $cache->expire($hash); } diff --git a/lib/models/Semester.class.php b/lib/models/Semester.class.php index 4eefa3b..03243a5 100644 --- a/lib/models/Semester.class.php +++ b/lib/models/Semester.class.php @@ -181,7 +181,7 @@ class Semester extends SimpleORMap if (!is_array(self::$semester_cache) || $force_reload) { self::$semester_cache = []; if (!$force_reload) { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $semester_data_array = unserialize($cache->read('DB_SEMESTER_DATA')); if ($semester_data_array) { foreach ($semester_data_array as $semester_data) { @@ -202,7 +202,7 @@ class Semester extends SimpleORMap } $semester_data[] = $semester->toRawArray(); } - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $cache->write('DB_SEMESTER_DATA', serialize($semester_data)); } } @@ -471,7 +471,7 @@ class Semester extends SimpleORMap */ public function refreshCache() { - StudipCacheFactory::getCache()->expire('DB_SEMESTER_DATA'); + \Studip\Cache\Factory::getCache()->expire('DB_SEMESTER_DATA'); } /* diff --git a/lib/models/StudipCacheOperation.php b/lib/models/StudipCacheOperation.php index e9c8738..d2287c3 100644 --- a/lib/models/StudipCacheOperation.php +++ b/lib/models/StudipCacheOperation.php @@ -42,7 +42,7 @@ class StudipCacheOperation extends SimpleORMap */ public static function apply(StudipCache $cache) { - self::findEachBySQL(function ($item) use ($cache) { + self::findEachBySQL(function (StudipCacheOperation $item) use ($cache): void { $parameters = unserialize($item->parameters); array_unshift($parameters, $item->cache_key); call_user_func_array([$cache, $item->operation], $parameters); diff --git a/lib/phplib/CT_Cache.class.php b/lib/phplib/CT_Cache.class.php index 311b27c..d4eccfa 100644 --- a/lib/phplib/CT_Cache.class.php +++ b/lib/phplib/CT_Cache.class.php @@ -14,7 +14,7 @@ class CT_Cache public function ac_start() { - $this->cache = StudipCacheFactory::getCache(); + $this->cache = \Studip\Cache\Factory::getCache(); } public function ac_get_lock() diff --git a/lib/plugins/db/RolePersistence.class.php b/lib/plugins/db/RolePersistence.class.php index b11fa4a..df63a77 100644 --- a/lib/plugins/db/RolePersistence.class.php +++ b/lib/plugins/db/RolePersistence.class.php @@ -29,7 +29,7 @@ class RolePersistence { if (self::$all_roles === null) { // read cache - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); // cache miss, retrieve from database self::$all_roles = $cache->read(self::ROLES_CACHE_KEY); @@ -675,7 +675,7 @@ class RolePersistence public static function expireRolesCache() { self::$all_roles = null; - StudipCacheFactory::getCache()->expire(self::ROLES_CACHE_KEY); + \Studip\Cache\Factory::getCache()->expire(self::ROLES_CACHE_KEY); } /** diff --git a/lib/plugins/engine/PluginRepository.class.php b/lib/plugins/engine/PluginRepository.class.php index 14415f4..235e9bd 100644 --- a/lib/plugins/engine/PluginRepository.class.php +++ b/lib/plugins/engine/PluginRepository.class.php @@ -55,7 +55,7 @@ class PluginRepository */ public function readMetadata($url) { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $cache_key = 'plugin_metadata/'.$url; $metadata = $cache->read($cache_key); diff --git a/lib/raumzeit/SingleDate.class.php b/lib/raumzeit/SingleDate.class.php index 82a89db..1a58695 100644 --- a/lib/raumzeit/SingleDate.class.php +++ b/lib/raumzeit/SingleDate.class.php @@ -292,7 +292,7 @@ class SingleDate function delete() { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $cache->expire('course/undecorated_data/' . $this->range_id); $this->chdate = time(); @@ -304,7 +304,7 @@ class SingleDate function store() { - $cache = StudipCacheFactory::getCache(); + $cache = \Studip\Cache\Factory::getCache(); $cache->expire('course/undecorated_data/' . $this->range_id); $this->chdate = time(); diff --git a/tests/functional/_bootstrap.php b/tests/functional/_bootstrap.php index 49f3324..8a9125b 100644 --- a/tests/functional/_bootstrap.php +++ b/tests/functional/_bootstrap.php @@ -30,6 +30,8 @@ StudipAutoloader::register(); StudipAutoloader::addAutoloadPath($STUDIP_BASE_PATH . '/lib/calendar', 'Studip\\Calendar'); StudipAutoloader::addAutoloadPath($STUDIP_BASE_PATH . '/lib/classes'); StudipAutoloader::addAutoloadPath($STUDIP_BASE_PATH . '/lib/classes', 'Studip'); +StudipAutoloader::addAutoloadPath($STUDIP_BASE_PATH . '/lib/classes/cache'); +StudipAutoloader::addAutoloadPath($STUDIP_BASE_PATH . '/lib/classes/cache', 'Studip'); StudipAutoloader::addAutoloadPath($STUDIP_BASE_PATH . '/lib/exceptions'); StudipAutoloader::addAutoloadPath($STUDIP_BASE_PATH . '/lib/exceptions/resources'); StudipAutoloader::addAutoloadPath($STUDIP_BASE_PATH . '/lib/filesystem'); @@ -74,7 +76,7 @@ if (!class_exists('StudipTestHelper')) { { static function set_up_tables($tables) { - $cache = StudipCacheFactory::getCache(false); + $cache = \Studip\Cache\Factory::getCache(false); // second step, expire table scheme SimpleORMap::expireTableScheme(); diff --git a/tests/jsonapi/_bootstrap.php b/tests/jsonapi/_bootstrap.php index 82ae54e..baf3740 100644 --- a/tests/jsonapi/_bootstrap.php +++ b/tests/jsonapi/_bootstrap.php @@ -53,6 +53,8 @@ StudipAutoloader::addAutoloadPath($GLOBALS['STUDIP_BASE_PATH'].'/lib/plugins/eng StudipAutoloader::addAutoloadPath($GLOBALS['STUDIP_BASE_PATH'].'/lib/calendar'); StudipAutoloader::addAutoloadPath($GLOBALS['STUDIP_BASE_PATH'].'/lib/calendar', 'Studip\\Calendar'); +StudipAutoloader::addAutoloadPath($GLOBALS['STUDIP_BASE_PATH'].'lib/classes/cache'); +StudipAutoloader::addAutoloadPath($GLOBALS['STUDIP_BASE_PATH'].'lib/classes/cache', 'Studip'); StudipAutoloader::addAutoloadPath($GLOBALS['STUDIP_BASE_PATH'].'/lib/calendar/lib'); StudipAutoloader::addAutoloadPath($GLOBALS['STUDIP_BASE_PATH'].'/lib/exceptions'); diff --git a/tests/unit/_bootstrap.php b/tests/unit/_bootstrap.php index 3aa1144..af95607 100644 --- a/tests/unit/_bootstrap.php +++ b/tests/unit/_bootstrap.php @@ -54,6 +54,8 @@ StudipAutoloader::addAutoloadPath('lib/models'); StudipAutoloader::addAutoloadPath('lib/classes'); StudipAutoloader::addAutoloadPath('lib/classes', 'Studip'); StudipAutoloader::addAutoloadPath('lib/exTpl', 'exTpl'); +StudipAutoloader::addAutoloadPath('lib/classes/cache'); +StudipAutoloader::addAutoloadPath('lib/classes/cache', 'Studip'); StudipAutoloader::addAutoloadPath('lib/exceptions'); StudipAutoloader::addAutoloadPath('lib/classes/sidebar'); StudipAutoloader::addAutoloadPath('lib/classes/helpbar'); @@ -108,7 +110,7 @@ if (!class_exists('StudipTestHelper')) { static function set_up_tables($tables) { // first step, set fake cache - $cache = StudipCacheFactory::getCache(false); + $cache = \Studip\Cache\Factory::getCache(false); // second step, expire table scheme SimpleORMap::expireTableScheme(); diff --git a/tests/unit/lib/classes/MigrationTest.php b/tests/unit/lib/classes/MigrationTest.php index 4a45e71..12df120 100644 --- a/tests/unit/lib/classes/MigrationTest.php +++ b/tests/unit/lib/classes/MigrationTest.php @@ -16,11 +16,6 @@ class MigrationTest extends \Codeception\Test\Unit $this->before = $GLOBALS['CACHING_ENABLE'] ?? null; $GLOBALS['CACHING_ENABLE'] = false; - require_once 'lib/classes/SimpleORMap.class.php'; - require_once 'lib/classes/StudipCache.class.php'; - require_once 'lib/classes/StudipMemoryCache.class.php'; - require_once 'lib/classes/StudipCacheFactory.class.php'; - require_once 'lib/migrations/Migration.php'; require_once 'lib/migrations/Migrator.php'; require_once 'lib/migrations/SchemaVersion.php'; diff --git a/tests/unit/lib/classes/StudipCachedArrayTest.php b/tests/unit/lib/classes/StudipCachedArrayTest.php index c98c1bd..5fd39f2 100644 --- a/tests/unit/lib/classes/StudipCachedArrayTest.php +++ b/tests/unit/lib/classes/StudipCachedArrayTest.php @@ -1,4 +1,7 @@