1
0
mirror of https://github.com/vichan-devel/vichan.git synced 2024-11-27 17:00:52 +01:00

Merge pull request #818 from Zankaria/dep-inj-cache-wrap

Dependency injected cache
This commit is contained in:
Lorenzo Yario 2024-10-15 20:40:34 -07:00 committed by GitHub
commit c1307feeb5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 492 additions and 171 deletions

View File

@ -0,0 +1,28 @@
<?php
namespace Vichan\Data\Driver;
defined('TINYBOARD') or exit;
class ApcuCacheDriver implements CacheDriver {
public function get(string $key): mixed {
$success = false;
$ret = \apcu_fetch($key, $success);
if ($success === false) {
return null;
}
return $ret;
}
public function set(string $key, mixed $value, mixed $expires = false): void {
\apcu_store($key, $value, (int)$expires);
}
public function delete(string $key): void {
\apcu_delete($key);
}
public function flush(): void {
\apcu_clear_cache();
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace Vichan\Data\Driver;
defined('TINYBOARD') or exit;
/**
* A simple process-wide PHP array.
*/
class ArrayCacheDriver implements CacheDriver {
private static array $inner = [];
public function get(string $key): mixed {
return isset(self::$inner[$key]) ? self::$inner[$key] : null;
}
public function set(string $key, mixed $value, mixed $expires = false): void {
self::$inner[$key] = $value;
}
public function delete(string $key): void {
unset(self::$inner[$key]);
}
public function flush(): void {
self::$inner = [];
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace Vichan\Data\Driver;
defined('TINYBOARD') or exit;
interface CacheDriver {
/**
* Get the value of associated with the key.
*
* @param string $key The key of the value.
* @return mixed|null The value associated with the key, or null if there is none.
*/
public function get(string $key): mixed;
/**
* Set a key-value pair.
*
* @param string $key The key.
* @param mixed $value The value.
* @param int|false $expires After how many seconds the pair will expire. Use false or ignore this parameter to keep
* the value until it gets evicted to make space for more items. Some drivers will always
* ignore this parameter and store the pair until it's removed.
*/
public function set(string $key, mixed $value, mixed $expires = false): void;
/**
* Delete a key-value pair.
*
* @param string $key The key.
*/
public function delete(string $key): void;
/**
* Delete all the key-value pairs.
*/
public function flush(): void;
}

View File

@ -0,0 +1,155 @@
<?php
namespace Vichan\Data\Driver;
defined('TINYBOARD') or exit;
class FsCacheDriver implements CacheDriver {
private string $prefix;
private string $base_path;
private mixed $lock_fd;
private int|false $collect_chance_den;
private function prepareKey(string $key): string {
$key = \str_replace('/', '::', $key);
$key = \str_replace("\0", '', $key);
return $this->prefix . $key;
}
private function sharedLockCache(): void {
\flock($this->lock_fd, LOCK_SH);
}
private function exclusiveLockCache(): void {
\flock($this->lock_fd, LOCK_EX);
}
private function unlockCache(): void {
\flock($this->lock_fd, LOCK_UN);
}
private function collectImpl(): int {
/*
* A read lock is ok, since it's alright if we delete expired items from under the feet of other processes, and
* no other process add new cache items or refresh existing ones.
*/
$files = \glob($this->base_path . $this->prefix . '*', \GLOB_NOSORT);
$count = 0;
foreach ($files as $file) {
$data = \file_get_contents($file);
$wrapped = \json_decode($data, true, 512, \JSON_THROW_ON_ERROR);
if ($wrapped['expires'] !== false && $wrapped['expires'] <= \time()) {
if (@\unlink($file)) {
$count++;
}
}
}
return $count;
}
private function maybeCollect(): void {
if ($this->collect_chance_den !== false && \mt_rand(0, $this->collect_chance_den - 1) === 0) {
$this->collect_chance_den = false; // Collect only once per instance (aka process).
$this->collectImpl();
}
}
public function __construct(string $prefix, string $base_path, string $lock_file, int|false $collect_chance_den) {
if ($base_path[\strlen($base_path) - 1] !== '/') {
$base_path = "$base_path/";
}
if (!\is_dir($base_path)) {
throw new \RuntimeException("$base_path is not a directory!");
}
if (!\is_writable($base_path)) {
throw new \RuntimeException("$base_path is not writable!");
}
$this->lock_fd = \fopen($base_path . $lock_file, 'w');
if ($this->lock_fd === false) {
throw new \RuntimeException('Unable to open the lock file!');
}
$this->prefix = $prefix;
$this->base_path = $base_path;
$this->collect_chance_den = $collect_chance_den;
}
public function __destruct() {
$this->close();
}
public function get(string $key): mixed {
$key = $this->prepareKey($key);
$this->sharedLockCache();
// Collect expired items first so if the target key is expired we shortcut to failure in the next lines.
$this->maybeCollect();
$fd = \fopen($this->base_path . $key, 'r');
if ($fd === false) {
$this->unlockCache();
return null;
}
$data = \stream_get_contents($fd);
\fclose($fd);
$this->unlockCache();
$wrapped = \json_decode($data, true, 512, \JSON_THROW_ON_ERROR);
if ($wrapped['expires'] !== false && $wrapped['expires'] <= \time()) {
// Already expired, leave it there since we already released the lock and pretend it doesn't exist.
return null;
} else {
return $wrapped['inner'];
}
}
public function set(string $key, mixed $value, mixed $expires = false): void {
$key = $this->prepareKey($key);
$wrapped = [
'expires' => $expires ? \time() + $expires : false,
'inner' => $value
];
$data = \json_encode($wrapped);
$this->exclusiveLockCache();
$this->maybeCollect();
\file_put_contents($this->base_path . $key, $data);
$this->unlockCache();
}
public function delete(string $key): void {
$key = $this->prepareKey($key);
$this->exclusiveLockCache();
@\unlink($this->base_path . $key);
$this->maybeCollect();
$this->unlockCache();
}
public function collect(): int {
$this->sharedLockCache();
$count = $this->collectImpl();
$this->unlockCache();
return $count;
}
public function flush(): void {
$this->exclusiveLockCache();
$files = \glob($this->base_path . $this->prefix . '*', \GLOB_NOSORT);
foreach ($files as $file) {
@\unlink($file);
}
$this->unlockCache();
}
public function close(): void {
\fclose($this->lock_fd);
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace Vichan\Data\Driver;
defined('TINYBOARD') or exit;
class MemcachedCacheDriver implements CacheDriver {
private \Memcached $inner;
public function __construct(string $prefix, string $memcached_server) {
$this->inner = new \Memcached();
if (!$this->inner->setOption(\Memcached::OPT_BINARY_PROTOCOL, true)) {
throw new \RuntimeException('Unable to set the memcached protocol!');
}
if (!$this->inner->setOption(\Memcached::OPT_PREFIX_KEY, $prefix)) {
throw new \RuntimeException('Unable to set the memcached prefix!');
}
if (!$this->inner->addServers($memcached_server)) {
throw new \RuntimeException('Unable to add the memcached server!');
}
}
public function get(string $key): mixed {
$ret = $this->inner->get($key);
// If the returned value is false but the retrival was a success, then the value stored was a boolean false.
if ($ret === false && $this->inner->getResultCode() !== \Memcached::RES_SUCCESS) {
return null;
}
return $ret;
}
public function set(string $key, mixed $value, mixed $expires = false): void {
$this->inner->set($key, $value, (int)$expires);
}
public function delete(string $key): void {
$this->inner->delete($key);
}
public function flush(): void {
$this->inner->flush();
}
}

View File

@ -0,0 +1,26 @@
<?php
namespace Vichan\Data\Driver;
defined('TINYBOARD') or exit;
/**
* No-op cache. Useful for testing.
*/
class NoneCacheDriver implements CacheDriver {
public function get(string $key): mixed {
return null;
}
public function set(string $key, mixed $value, mixed $expires = false): void {
// No-op.
}
public function delete(string $key): void {
// No-op.
}
public function flush(): void {
// No-op.
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace Vichan\Data\Driver;
defined('TINYBOARD') or exit;
class RedisCacheDriver implements CacheDriver {
private string $prefix;
private \Redis $inner;
public function __construct(string $prefix, string $host, int $port, ?string $password, string $database) {
$this->inner = new \Redis();
$this->inner->connect($host, $port);
if ($password) {
$this->inner->auth($password);
}
if (!$this->inner->select($database)) {
throw new \RuntimeException('Unable to connect to Redis!');
}
$$this->prefix = $prefix;
}
public function get(string $key): mixed {
$ret = $this->inner->get($this->prefix . $key);
if ($ret === false) {
return null;
}
return \json_decode($ret, true);
}
public function set(string $key, mixed $value, mixed $expires = false): void {
if ($expires === false) {
$this->inner->set($this->prefix . $key, \json_encode($value));
} else {
$expires = $expires * 1000; // Seconds to milliseconds.
$this->inner->setex($this->prefix . $key, $expires, \json_encode($value));
}
}
public function delete(string $key): void {
$this->inner->del($this->prefix . $key);
}
public function flush(): void {
$this->inner->flushDB();
}
}

View File

@ -4,164 +4,89 @@
* Copyright (c) 2010-2013 Tinyboard Development Group
*/
use Vichan\Data\Driver\{CacheDriver, ApcuCacheDriver, ArrayCacheDriver, FsCacheDriver, MemcachedCacheDriver, NoneCacheDriver, RedisCacheDriver};
defined('TINYBOARD') or exit;
class Cache {
private static $cache;
public static function init() {
private static function buildCache(): CacheDriver {
global $config;
switch ($config['cache']['enabled']) {
case 'memcached':
self::$cache = new Memcached();
self::$cache->addServers($config['cache']['memcached']);
break;
return new MemcachedCacheDriver(
$config['cache']['prefix'],
$config['cache']['memcached']
);
case 'redis':
self::$cache = new Redis();
self::$cache->connect($config['cache']['redis'][0], $config['cache']['redis'][1]);
if ($config['cache']['redis'][2]) {
self::$cache->auth($config['cache']['redis'][2]);
}
self::$cache->select($config['cache']['redis'][3]) or die('cache select failure');
break;
return new RedisCacheDriver(
$config['cache']['prefix'],
$config['cache']['redis'][0],
$config['cache']['redis'][1],
$config['cache']['redis'][2],
$config['cache']['redis'][3]
);
case 'apcu':
return new ApcuCacheDriver;
case 'fs':
return new FsCacheDriver(
$config['cache']['prefix'],
"tmp/cache/{$config['cache']['prefix']}",
'.lock',
$config['auto_maintenance'] ? 1000 : false
);
case 'none':
return new NoneCacheDriver();
case 'php':
self::$cache = array();
break;
default:
return new ArrayCacheDriver();
}
}
public static function getCache(): CacheDriver {
static $cache;
return $cache ??= self::buildCache();
}
public static function get($key) {
global $config, $debug;
$key = $config['cache']['prefix'] . $key;
$data = false;
switch ($config['cache']['enabled']) {
case 'memcached':
if (!self::$cache)
self::init();
$data = self::$cache->get($key);
break;
case 'apcu':
$data = apcu_fetch($key);
break;
case 'php':
$data = isset(self::$cache[$key]) ? self::$cache[$key] : false;
break;
case 'fs':
$key = str_replace('/', '::', $key);
$key = str_replace("\0", '', $key);
if (!file_exists('tmp/cache/'.$key)) {
$data = false;
}
else {
$data = file_get_contents('tmp/cache/'.$key);
$data = json_decode($data, true);
}
break;
case 'redis':
if (!self::$cache)
self::init();
$data = json_decode(self::$cache->get($key), true);
break;
$ret = self::getCache()->get($key);
if ($ret === null) {
$ret = false;
}
if ($config['debug'])
$debug['cached'][] = $key . ($data === false ? ' (miss)' : ' (hit)');
if ($config['debug']) {
$debug['cached'][] = $config['cache']['prefix'] . $key . ($ret === false ? ' (miss)' : ' (hit)');
}
return $data;
return $ret;
}
public static function set($key, $value, $expires = false) {
global $config, $debug;
$key = $config['cache']['prefix'] . $key;
if (!$expires)
if (!$expires) {
$expires = $config['cache']['timeout'];
switch ($config['cache']['enabled']) {
case 'memcached':
if (!self::$cache)
self::init();
self::$cache->set($key, $value, $expires);
break;
case 'redis':
if (!self::$cache)
self::init();
self::$cache->setex($key, $expires, json_encode($value));
break;
case 'apcu':
apcu_store($key, $value, $expires);
break;
case 'fs':
$key = str_replace('/', '::', $key);
$key = str_replace("\0", '', $key);
file_put_contents('tmp/cache/'.$key, json_encode($value));
break;
case 'php':
self::$cache[$key] = $value;
break;
}
if ($config['debug'])
$debug['cached'][] = $key . ' (set)';
self::getCache()->set($key, $value, $expires);
if ($config['debug']) {
$debug['cached'][] = $config['cache']['prefix'] . $key . ' (set)';
}
}
public static function delete($key) {
global $config, $debug;
$key = $config['cache']['prefix'] . $key;
self::getCache()->delete($key);
switch ($config['cache']['enabled']) {
case 'memcached':
if (!self::$cache)
self::init();
self::$cache->delete($key);
break;
case 'redis':
if (!self::$cache)
self::init();
self::$cache->del($key);
break;
case 'apcu':
apcu_delete($key);
break;
case 'fs':
$key = str_replace('/', '::', $key);
$key = str_replace("\0", '', $key);
@unlink('tmp/cache/'.$key);
break;
case 'php':
unset(self::$cache[$key]);
break;
if ($config['debug']) {
$debug['cached'][] = $config['cache']['prefix'] . $key . ' (deleted)';
}
if ($config['debug'])
$debug['cached'][] = $key . ' (deleted)';
}
public static function flush() {
global $config;
switch ($config['cache']['enabled']) {
case 'memcached':
if (!self::$cache)
self::init();
return self::$cache->flush();
case 'apcu':
return apcu_clear_cache('user');
case 'php':
self::$cache = array();
break;
case 'fs':
$files = glob('tmp/cache/*');
foreach ($files as $file) {
unlink($file);
}
break;
case 'redis':
if (!self::$cache)
self::init();
return self::$cache->flushDB();
}
self::getCache()->flush();
return false;
}
}

View File

@ -139,17 +139,26 @@
/*
* On top of the static file caching system, you can enable the additional caching system which is
* designed to minimize SQL queries and can significantly increase speed when posting or using the
* moderator interface. APC is the recommended method of caching.
* designed to minimize request processing can significantly increase speed when posting or using
* the moderator interface.
*
* https://github.com/vichan-devel/vichan/wiki/cache
*/
// Uses a PHP array. MUST NOT be used in multiprocess environments.
$config['cache']['enabled'] = 'php';
// The recommended in-memory method of caching. Requires the extension. Due to how APCu works, this should be
// disabled when you run tools from the cli.
// $config['cache']['enabled'] = 'apcu';
// The Memcache server. Requires the memcached extension, with a final D.
// $config['cache']['enabled'] = 'memcached';
// The Redis server. Requires the extension.
// $config['cache']['enabled'] = 'redis';
// Use the local cache folder. Slower than native but available out of the box and compatible with multiprocess
// environments. You can mount a ram-based filesystem in the cache directory to improve performance.
// $config['cache']['enabled'] = 'fs';
// Technically available, offers a no-op fake cache. Don't use this outside of testing or debugging.
// $config['cache']['enabled'] = 'none';
// Timeout for cached objects such as posts and HTML.
$config['cache']['timeout'] = 60 * 60 * 48; // 48 hours

View File

@ -83,6 +83,10 @@ function build_context(array $config): Context {
$config['captcha']['native']['provider_check'],
$config['captcha']['native']['extra']
);
},
CacheDriver::class => function($c) {
// Use the global for backwards compatibility.
return \cache::getCache();
}
]);
}

View File

@ -4,8 +4,8 @@
*/
use Vichan\Context;
use Vichan\Functions\Format;
use Vichan\Functions\Net;
use Vichan\Data\Driver\CacheDriver;
defined('TINYBOARD') or exit;
@ -112,25 +112,23 @@ function mod_dashboard(Context $ctx) {
$args['boards'] = listBoards();
if (hasPermission($config['mod']['noticeboard'])) {
if (!$config['cache']['enabled'] || !$args['noticeboard'] = cache::get('noticeboard_preview')) {
if (!$args['noticeboard'] = $ctx->get(CacheDriver::class)->get('noticeboard_preview')) {
$query = prepare("SELECT ``noticeboard``.*, `username` FROM ``noticeboard`` LEFT JOIN ``mods`` ON ``mods``.`id` = `mod` ORDER BY `id` DESC LIMIT :limit");
$query->bindValue(':limit', $config['mod']['noticeboard_dashboard'], PDO::PARAM_INT);
$query->execute() or error(db_error($query));
$args['noticeboard'] = $query->fetchAll(PDO::FETCH_ASSOC);
if ($config['cache']['enabled'])
cache::set('noticeboard_preview', $args['noticeboard']);
$ctx->get(CacheDriver::class)->set('noticeboard_preview', $args['noticeboard']);
}
}
if (!$config['cache']['enabled'] || ($args['unread_pms'] = cache::get('pm_unreadcount_' . $mod['id'])) === false) {
if ($args['unread_pms'] = $ctx->get(CacheDriver::class)->get('pm_unreadcount_' . $mod['id']) === false) {
$query = prepare('SELECT COUNT(*) FROM ``pms`` WHERE `to` = :id AND `unread` = 1');
$query->bindValue(':id', $mod['id']);
$query->execute() or error(db_error($query));
$args['unread_pms'] = $query->fetchColumn();
if ($config['cache']['enabled'])
cache::set('pm_unreadcount_' . $mod['id'], $args['unread_pms']);
$ctx->get(CacheDriver::class)->set('pm_unreadcount_' . $mod['id'], $args['unread_pms']);
}
$query = query('SELECT COUNT(*) FROM ``reports``') or error(db_error($query));
@ -384,6 +382,8 @@ function mod_search(Context $ctx, $type, $search_query_escaped, $page_no = 1) {
function mod_edit_board(Context $ctx, $boardName) {
global $board, $config, $mod;
$cache = $ctx->get(CacheDriver::class);
if (!openBoard($boardName))
error($config['error']['noboard']);
@ -399,10 +399,8 @@ function mod_edit_board(Context $ctx, $boardName) {
$query->bindValue(':uri', $board['uri']);
$query->execute() or error(db_error($query));
if ($config['cache']['enabled']) {
cache::delete('board_' . $board['uri']);
cache::delete('all_boards');
}
$cache->delete('board_' . $board['uri']);
$cache->delete('all_boards');
modLog('Deleted board: ' . sprintf($config['board_abbreviation'], $board['uri']), false);
@ -467,10 +465,9 @@ function mod_edit_board(Context $ctx, $boardName) {
modLog('Edited board information for ' . sprintf($config['board_abbreviation'], $board['uri']), false);
}
if ($config['cache']['enabled']) {
cache::delete('board_' . $board['uri']);
cache::delete('all_boards');
}
$cache->delete('board_' . $board['uri']);
$cache->delete('all_boards');
Vichan\Functions\Theme\rebuild_themes('boards');
@ -505,6 +502,8 @@ function mod_new_board(Context $ctx) {
if (!preg_match('/^' . $config['board_regex'] . '$/u', $_POST['uri']))
error(sprintf($config['error']['invalidfield'], 'URI'));
$cache = $ctx->get(CacheDriver::class);
$bytes = 0;
$chars = preg_split('//u', $_POST['uri'], -1, PREG_SPLIT_NO_EMPTY);
foreach ($chars as $char) {
@ -544,8 +543,8 @@ function mod_new_board(Context $ctx) {
query($query) or error(db_error());
if ($config['cache']['enabled'])
cache::delete('all_boards');
$cache = $ctx->get(CacheDriver::class);
$cache->delete('all_boards');
// Build the board
buildIndex();
@ -590,8 +589,8 @@ function mod_noticeboard(Context $ctx, $page_no = 1) {
$query->bindValue(':body', $_POST['body']);
$query->execute() or error(db_error($query));
if ($config['cache']['enabled'])
cache::delete('noticeboard_preview');
$cache = $ctx->get(CacheDriver::class);
$cache->delete('noticeboard_preview');
modLog('Posted a noticeboard entry');
@ -631,7 +630,7 @@ function mod_noticeboard_delete(Context $ctx, $id) {
$config = $ctx->get('config');
if (!hasPermission($config['mod']['noticeboard_delete']))
error($config['error']['noaccess']);
error($config['error']['noaccess']);
$query = prepare('DELETE FROM ``noticeboard`` WHERE `id` = :id');
$query->bindValue(':id', $id);
@ -639,8 +638,8 @@ function mod_noticeboard_delete(Context $ctx, $id) {
modLog('Deleted a noticeboard entry');
if ($config['cache']['enabled'])
cache::delete('noticeboard_preview');
$cache = $ctx->get(CacheDriver::class);
$cache->delete('noticeboard_preview');
header('Location: ?/noticeboard', true, $config['redirect_http']);
}
@ -706,7 +705,7 @@ function mod_news_delete(Context $ctx, $id) {
$config = $ctx->get('config');
if (!hasPermission($config['mod']['news_delete']))
error($config['error']['noaccess']);
error($config['error']['noaccess']);
$query = prepare('DELETE FROM ``news`` WHERE `id` = :id');
$query->bindValue(':id', $id);
@ -843,7 +842,7 @@ function mod_view_board(Context $ctx, $boardName, $page_no = 1) {
}
$page['pages'] = getPages(true);
$page['pages'][$page_no-1]['selected'] = true;
$page['pages'][$page_no - 1]['selected'] = true;
$page['btn'] = getPageButtons($page['pages'], true);
$page['mod'] = true;
$page['config'] = $config;
@ -1042,7 +1041,6 @@ function mod_edit_ban(Context $ctx, $ban_id) {
Bans::delete($ban_id);
header('Location: ?/', true, $config['redirect_http']);
}
$args['token'] = make_secure_link_token('edit_ban/' . $ban_id);
@ -2235,10 +2233,9 @@ function mod_pm(Context $ctx, $id, $reply = false) {
$query->bindValue(':id', $id);
$query->execute() or error(db_error($query));
if ($config['cache']['enabled']) {
cache::delete('pm_unread_' . $mod['id']);
cache::delete('pm_unreadcount_' . $mod['id']);
}
$cache = $ctx->get(CacheDriver::class);
$cache->delete('pm_unread_' . $mod['id']);
$cache->delete('pm_unreadcount_' . $mod['id']);
header('Location: ?/', true, $config['redirect_http']);
return;
@ -2249,10 +2246,9 @@ function mod_pm(Context $ctx, $id, $reply = false) {
$query->bindValue(':id', $id);
$query->execute() or error(db_error($query));
if ($config['cache']['enabled']) {
cache::delete('pm_unread_' . $mod['id']);
cache::delete('pm_unreadcount_' . $mod['id']);
}
$cache = $ctx->get(CacheDriver::class);
$cache->delete('pm_unread_' . $mod['id']);
$cache->delete('pm_unreadcount_' . $mod['id']);
modLog('Read a PM');
}
@ -2339,10 +2335,10 @@ function mod_new_pm(Context $ctx, $username) {
$query->bindValue(':time', time());
$query->execute() or error(db_error($query));
if ($config['cache']['enabled']) {
cache::delete('pm_unread_' . $id);
cache::delete('pm_unreadcount_' . $id);
}
$cache = $ctx->get(CacheDriver::class);
$cache->delete('pm_unread_' . $id);
$cache->delete('pm_unreadcount_' . $id);
modLog('Sent a PM to ' . utf8tohtml($username));
@ -2368,6 +2364,8 @@ function mod_rebuild(Context $ctx) {
if (!hasPermission($config['mod']['rebuild']))
error($config['error']['noaccess']);
$cache = $ctx->get(CacheDriver::class);
if (isset($_POST['rebuild'])) {
@set_time_limit($config['mod']['rebuild_timelimit']);
@ -2378,7 +2376,7 @@ function mod_rebuild(Context $ctx) {
if (isset($_POST['rebuild_cache'])) {
if ($config['cache']['enabled']) {
$log[] = 'Flushing cache';
Cache::flush();
$cache->flush();
}
$log[] = 'Clearing template cache';
@ -2840,6 +2838,8 @@ function mod_theme_configure(Context $ctx, $theme_name) {
error($config['error']['invalidtheme']);
}
$cache = $ctx->get(CacheDriver::class);
if (isset($_POST['install'])) {
// Check if everything is submitted
foreach ($theme['config'] as &$conf) {
@ -2868,8 +2868,8 @@ function mod_theme_configure(Context $ctx, $theme_name) {
$query->execute() or error(db_error($query));
// Clean cache
Cache::delete("themes");
Cache::delete("theme_settings_".$theme_name);
$cache->delete("themes");
$cache->delete("theme_settings_$theme_name");
$result = true;
$message = false;
@ -2928,13 +2928,15 @@ function mod_theme_uninstall(Context $ctx, $theme_name) {
if (!hasPermission($config['mod']['themes']))
error($config['error']['noaccess']);
$cache = $ctx->get(CacheDriver::class);
$query = prepare("DELETE FROM ``theme_settings`` WHERE `theme` = :theme");
$query->bindValue(':theme', $theme_name);
$query->execute() or error(db_error($query));
// Clean cache
Cache::delete("themes");
Cache::delete("theme_settings_".$theme_name);
$cache->delete("themes");
$cache->delete("theme_settings_$theme_name");
header('Location: ?/themes', true, $config['redirect_http']);
}
@ -2959,7 +2961,7 @@ function mod_theme_rebuild(Context $ctx, $theme_name) {
}
// This needs to be done for `secure` CSRF prevention compatibility, otherwise the $board will be read in as the token if editing global pages.
function delete_page_base($page = '', $board = false) {
function delete_page_base(Context $ctx, $page = '', $board = false) {
global $config, $mod;
if (empty($board))

View File

@ -21,5 +21,20 @@ echo "Deleted $deleted_count expired antispam in $delta seconds!\n";
$time_tot = $delta;
$deleted_tot = $deleted_count;
if ($config['cache']['enabled'] === 'fs') {
$fs_cache = new Vichan\Data\Driver\FsCacheDriver(
$config['cache']['prefix'],
"tmp/cache/{$config['cache']['prefix']}",
'.lock',
false
);
$start = microtime(true);
$fs_cache->collect();
$delta = microtime(true) - $start;
echo "Deleted $deleted_count expired filesystem cache items in $delta seconds!\n";
$time_tot = $delta;
$deleted_tot = $deleted_count;
}
$time_tot = number_format((float)$time_tot, 4, '.', '');
modLog("Deleted $deleted_tot expired entries in {$time_tot}s with maintenance tool");