diff --git a/compose.yml b/compose.yml index 6ae60976..fe967be2 100644 --- a/compose.yml +++ b/compose.yml @@ -23,6 +23,7 @@ services: volumes: - ./local-instances/${INSTANCE:-0}/www:/var/www - ./docker/php/www.conf:/usr/local/etc/php-fpm.d/www.conf + - redis-sock:/var/run/redis #MySQL Service db: @@ -36,3 +37,13 @@ services: MYSQL_ROOT_PASSWORD: password volumes: - ./local-instances/${INSTANCE:-0}/mysql:/var/lib/mysql + + redis: + build: + context: ./ + dockerfile: ./docker/redis/Dockerfile + volumes: + - redis-sock:/var/run/redis + +volumes: + redis-sock: diff --git a/composer.json b/composer.json index 86692db4..d0345e3b 100644 --- a/composer.json +++ b/composer.json @@ -13,6 +13,7 @@ "files": [ "inc/anti-bot.php", "inc/bootstrap.php", + "inc/context.php", "inc/display.php", "inc/template.php", "inc/database.php", diff --git a/docker/redis/Dockerfile b/docker/redis/Dockerfile new file mode 100644 index 00000000..9ab7f7fc --- /dev/null +++ b/docker/redis/Dockerfile @@ -0,0 +1,6 @@ +FROM redis:7.4-alpine + +RUN mkdir -p /var/run/redis && chmod 777 /var/run/redis +COPY ./docker/redis/redis.conf /etc/redis.conf + +ENTRYPOINT [ "docker-entrypoint.sh", "/etc/redis.conf" ] diff --git a/docker/redis/redis.conf b/docker/redis/redis.conf new file mode 100644 index 00000000..609d57ff --- /dev/null +++ b/docker/redis/redis.conf @@ -0,0 +1,16 @@ +# Accept connections on the specified port, default is 6379 (IANA #815344). +# If port 0 is specified Redis will not listen on a TCP socket. +#port 6379 +port 0 + +# Unix socket. +# +# Specify the path for the Unix socket that will be used to listen for +# incoming connections. There is no default, so Redis will not listen +# on a unix socket when not specified. +# +unixsocket /var/run/redis/redis-server.sock +# Executig a socket is a no-op, and we need to share acces to other programs. +# Shared the connection only with programs in the redis group for security. +#unixsocketperm 700 +unixsocketperm 666 diff --git a/inc/Data/Driver/ApcuCacheDriver.php b/inc/Data/Driver/ApcuCacheDriver.php new file mode 100644 index 00000000..a39bb656 --- /dev/null +++ b/inc/Data/Driver/ApcuCacheDriver.php @@ -0,0 +1,28 @@ +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); + } +} diff --git a/inc/Data/Driver/MemcacheCacheDriver.php b/inc/Data/Driver/MemcacheCacheDriver.php new file mode 100644 index 00000000..04f62895 --- /dev/null +++ b/inc/Data/Driver/MemcacheCacheDriver.php @@ -0,0 +1,43 @@ +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(); + } +} diff --git a/inc/Data/Driver/NoneCacheDriver.php b/inc/Data/Driver/NoneCacheDriver.php new file mode 100644 index 00000000..8b260a50 --- /dev/null +++ b/inc/Data/Driver/NoneCacheDriver.php @@ -0,0 +1,26 @@ +inner = new \Redis(); + if (str_starts_with($host, 'unix:') || str_starts_with($host, ':')) { + $ret = \explode(':', $host); + if (count($ret) < 2) { + throw new \RuntimeException("Invalid unix socket path $host"); + } + // Unix socket. + $this->inner->connect($ret[1]); + } elseif ($port === null) { + $this->inner->connect($host); + } else { + // IP + port. + $this->inner->connect($host, $port); + } + if ($password) { + $this->inner->auth($password); + } + if (!$this->inner->setOption(\Redis::OPT_SERIALIZER, \Redis::SERIALIZER_JSON)) { + throw new \RuntimeException('Unable to configure Redis serializer'); + } + if (!$this->inner->select($database)) { + throw new \RuntimeException('Unable to connect to Redis database!'); + } + + $this->prefix = $prefix; + } + + public function get(string $key): mixed { + $ret = $this->inner->get($this->prefix . $key); + if ($ret === false) { + return null; + } + if ($ret === null) { + return false; + } + return $ret; + } + + public function set(string $key, mixed $value, mixed $expires = false): void { + $value = $value === false ? null : $value; + if ($expires === false) { + $this->inner->set($this->prefix . $key, $value); + } else { + $this->inner->setEx($this->prefix . $key, $expires, $value); + } + } + + public function delete(string $key): void { + $this->inner->del($this->prefix . $key); + } + + public function flush(): void { + if (empty($this->prefix)) { + $this->inner->flushDB(); + } else { + $this->inner->unlink($this->inner->keys("{$this->prefix}*")); + } + } +} diff --git a/inc/cache.php b/inc/cache.php index 3f1b8cb1..293660fd 100644 --- a/inc/cache.php +++ b/inc/cache.php @@ -4,183 +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(); - - $ret = explode(':', $config['cache']['redis'][0]); - if (count($ret) > 0) { - // Unix socket. - self::$cache->connect($ret[1]); - } else { - // IP + port. - self::$cache->connect($ret[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 'apc': - $data = apc_fetch($key); - break; - case 'xcache': - $data = xcache_get($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(); - $ret = self::$cache->get($key); - $data = $ret !== false ? json_decode($ret, true) : false; - 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 'apc': - apc_store($key, $value, $expires); - break; - case 'xcache': - xcache_set($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 'apc': - apc_delete($key); - break; - case 'xcache': - xcache_unset($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 'apc': - return apc_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; } } diff --git a/inc/config.php b/inc/config.php index 92184fc8..633cdd33 100644 --- a/inc/config.php +++ b/inc/config.php @@ -117,18 +117,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. * - * http://tinyboard.org/docs/index.php?p=Config/Cache + * https://github.com/vichan-devel/vichan/wiki/cache */ + // Uses a PHP array. MUST NOT be used in multiprocess environments. $config['cache']['enabled'] = 'php'; - // $config['cache']['enabled'] = 'xcache'; - // $config['cache']['enabled'] = 'apc'; + // 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 @@ -144,7 +152,7 @@ // Redis server to use. Location, port, password, database id. // Note that Tinyboard may clear the database at times, so you may want to pick a database id just for // Tinyboard to use. - $config['cache']['redis'] = array('localhost', 6379, '', 1); + $config['cache']['redis'] = [ 'localhost', 6379, null, 1 ]; // EXPERIMENTAL: Should we cache configs? Warning: this changes board behaviour, i'd say, a lot. // If you have any lambdas/includes present in your config, you should move them to instance-functions.php @@ -722,18 +730,18 @@ // a link to an email address or IRC chat room to appeal the ban. $config['ban_page_extra'] = ''; - // Pre-configured ban reasons that pre-fill the ban form when clicked. - // To disable, set $config['ban_reasons'] = false; - $config['ban_reasons'] = array( - array( 'reason' => 'Low-quality posting', - 'length' => '1d'), - array( 'reason' => 'Off-topic', - 'length' => '1d'), - array( 'reason' => 'Ban evasion', - 'length' => '30d'), - array( 'reason' => 'Illegal content', - 'length' => ''), - ); + // Pre-configured ban reasons that pre-fill the ban form when clicked. + // To disable, set $config['ban_reasons'] = false; + $config['ban_reasons'] = array( + array( 'reason' => 'Low-quality posting', + 'length' => '1d'), + array( 'reason' => 'Off-topic', + 'length' => '1d'), + array( 'reason' => 'Ban evasion', + 'length' => '30d'), + array( 'reason' => 'Illegal content', + 'length' => ''), + ); // How often (minimum) to purge the ban list of expired bans (which have been seen). $config['purge_bans'] = 60 * 60 * 12; // 12 hours @@ -1912,15 +1920,15 @@ 'max_message_length' => 240 ]; - //Securimage captcha - //Note from lainchan PR: "TODO move a bunch of things here" + //Securimage captcha + //Note from lainchan PR: "TODO move a bunch of things here" - $config['spam']['valid_inputs'][]='captcha'; - $config['error']['securimage']=array( - 'missing'=>'The captcha field was missing. Please try again', - 'empty'=>'Please fill out the captcha', - 'bad'=>'Incorrect or expired captcha', - ); + $config['spam']['valid_inputs'][]='captcha'; + $config['error']['securimage']=array( + 'missing'=>'The captcha field was missing. Please try again', + 'empty'=>'Please fill out the captcha', + 'bad'=>'Incorrect or expired captcha', + ); // Meta keywords. It's probably best to include these in per-board configurations. // $config['meta_keywords'] = 'chan,anonymous discussion,imageboard,tinyboard'; diff --git a/inc/context.php b/inc/context.php new file mode 100644 index 00000000..137f6ebd --- /dev/null +++ b/inc/context.php @@ -0,0 +1,38 @@ +definitions = $definitions; + } + + public function get(string $name): mixed { + if (!isset($this->definitions[$name])) { + throw new \RuntimeException("Could not find a dependency named $name"); + } + + $ret = $this->definitions[$name]; + if (is_callable($ret) && !is_string($ret) && !is_array($ret)) { + $ret = $ret($this); + $this->definitions[$name] = $ret; + } + return $ret; + } +} + +function build_context(array $config): Context { + return new Context([ + 'config' => $config, + CacheDriver::class => function($c) { + // Use the global for backwards compatibility. + return \cache::getCache(); + } + ]); +} diff --git a/inc/mod/auth.php b/inc/mod/auth.php index 958b7ba5..01b234a1 100644 --- a/inc/mod/auth.php +++ b/inc/mod/auth.php @@ -4,6 +4,7 @@ * Copyright (c) 2010-2013 Tinyboard Development Group */ +use Vichan\Context; use Vichan\Functions\Net; defined('TINYBOARD') or exit; @@ -177,7 +178,7 @@ function make_secure_link_token($uri) { return substr(sha1($config['cookies']['salt'] . '-' . $uri . '-' . $mod['id']), 0, 8); } -function check_login($prompt = false) { +function check_login(Context $ctx, $prompt = false) { global $config, $mod; // Validate session @@ -187,7 +188,7 @@ function check_login($prompt = false) { if (count($cookie) != 3) { // Malformed cookies destroyCookies(); - if ($prompt) mod_login(); + if ($prompt) mod_login($ctx); exit; } @@ -200,7 +201,7 @@ function check_login($prompt = false) { if ($cookie[1] !== mkhash($cookie[0], $user['password'], $cookie[2])) { // Malformed cookies destroyCookies(); - if ($prompt) mod_login(); + if ($prompt) mod_login($ctx); exit; } diff --git a/inc/mod/pages.php b/inc/mod/pages.php index b980c844..050e1216 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -1,9 +1,9 @@ $request, 'token' => make_secure_link_token($request)); +function mod_confirm(Context $ctx, $request) { + $args = [ 'request' => $request, 'token' => make_secure_link_token($request) ]; if(isset($_GET['thread'])) { $args['rest'] = 'thread=' . $_GET['thread']; } mod_page(_('Confirm action'), 'mod/confirm.html', $args); } -function mod_logout() { +function mod_logout(Context $ctx) { global $config; destroyCookies(); header('Location: ?/', true, $config['redirect_http']); } -function mod_dashboard() { +function mod_dashboard(Context $ctx) { global $config, $mod; $args = []; @@ -190,7 +190,7 @@ function mod_dashboard() { mod_page(_('Dashboard'), 'mod/dashboard.html', $args); } -function mod_search_redirect() { +function mod_search_redirect(Context $ctx) { global $config; if (!hasPermission($config['mod']['search'])) @@ -213,7 +213,7 @@ function mod_search_redirect() { } } -function mod_search($type, $search_query_escaped, $page_no = 1) { +function mod_search(Context $ctx, $type, $search_query_escaped, $page_no = 1) { global $pdo, $config; if (!hasPermission($config['mod']['search'])) @@ -368,7 +368,7 @@ function mod_search($type, $search_query_escaped, $page_no = 1) { )); } -function mod_edit_board($boardName) { +function mod_edit_board(Context $ctx, $boardName) { global $board, $config; if (!openBoard($boardName)) @@ -470,7 +470,7 @@ function mod_edit_board($boardName) { } } -function mod_new_board() { +function mod_new_board(Context $ctx) { global $config, $board; if (!hasPermission($config['mod']['newboard'])) @@ -536,7 +536,7 @@ function mod_new_board() { mod_page(_('New board'), 'mod/board.html', array('new' => true, 'token' => make_secure_link_token('new-board'))); } -function mod_noticeboard($page_no = 1) { +function mod_noticeboard(Context $ctx, $page_no = 1) { global $config, $pdo, $mod; if ($page_no < 1) @@ -591,7 +591,7 @@ function mod_noticeboard($page_no = 1) { )); } -function mod_noticeboard_delete($id) { +function mod_noticeboard_delete(Context $ctx, $id) { global $config; if (!hasPermission($config['mod']['noticeboard_delete'])) @@ -609,7 +609,7 @@ function mod_noticeboard_delete($id) { header('Location: ?/noticeboard', true, $config['redirect_http']); } -function mod_news($page_no = 1) { +function mod_news(Context $ctx, $page_no = 1) { global $config, $pdo, $mod; if ($page_no < 1) @@ -656,7 +656,7 @@ function mod_news($page_no = 1) { mod_page(_('News'), 'mod/news.html', array('news' => $news, 'count' => $count, 'token' => make_secure_link_token('edit_news'))); } -function mod_news_delete($id) { +function mod_news_delete(Context $ctx, $id) { global $config; if (!hasPermission($config['mod']['news_delete'])) @@ -671,7 +671,7 @@ function mod_news_delete($id) { header('Location: ?/edit_news', true, $config['redirect_http']); } -function mod_log($page_no = 1) { +function mod_log(Context $ctx, $page_no = 1) { global $config; if ($page_no < 1) @@ -696,7 +696,7 @@ function mod_log($page_no = 1) { mod_page(_('Moderation log'), 'mod/log.html', array('logs' => $logs, 'count' => $count)); } -function mod_user_log($username, $page_no = 1) { +function mod_user_log(Context $ctx, $username, $page_no = 1) { global $config; if ($page_no < 1) @@ -733,7 +733,7 @@ function protect_ip($entry) { return preg_replace(array($ipv4_link_regex, $ipv6_link_regex), "xxxx", $entry); } -function mod_board_log($board, $page_no = 1, $hide_names = false, $public = false) { +function mod_board_log(Context $ctx, $board, $page_no = 1, $hide_names = false, $public = false) { global $config; if ($page_no < 1) @@ -766,8 +766,8 @@ function mod_board_log($board, $page_no = 1, $hide_names = false, $public = fals mod_page(_('Board log'), 'mod/log.html', array('logs' => $logs, 'count' => $count, 'board' => $board, 'hide_names' => $hide_names, 'public' => $public)); } -function mod_view_catalog($boardName) { - global $config, $mod; +function mod_view_catalog(Context $ctx, $boardName) { + global $config; require_once($config['dir']['themes'].'/catalog/theme.php'); $settings = []; $settings['boards'] = $boardName; @@ -798,7 +798,7 @@ function mod_view_catalog($boardName) { } } -function mod_view_board($boardName, $page_no = 1) { +function mod_view_board(Context $ctx, $boardName, $page_no = 1) { global $config, $mod; if (!openBoard($boardName)){ @@ -829,7 +829,7 @@ function mod_view_board($boardName, $page_no = 1) { echo Element('index.html', $page); } -function mod_view_thread($boardName, $thread) { +function mod_view_thread(Context $ctx, $boardName, $thread) { global $config, $mod; if (!openBoard($boardName)) @@ -839,7 +839,7 @@ function mod_view_thread($boardName, $thread) { echo $page; } -function mod_view_thread50($boardName, $thread) { +function mod_view_thread50(Context $ctx, $boardName, $thread) { global $config, $mod; if (!openBoard($boardName)) @@ -849,8 +849,8 @@ function mod_view_thread50($boardName, $thread) { echo $page; } -function mod_ip_remove_note($ip, $id) { - global $config, $mod; +function mod_ip_remove_note(Context $ctx, $ip, $id) { + global $config; if (!hasPermission($config['mod']['remove_notes'])) error($config['error']['noaccess']); @@ -868,7 +868,7 @@ function mod_ip_remove_note($ip, $id) { header('Location: ?/IP/' . $ip . '#notes', true, $config['redirect_http']); } -function mod_page_ip($ip, string $encoded_cursor = '') { +function mod_ip(Context $ctx, $ip, string $encoded_cursor = '') { global $config, $mod; if (filter_var($ip, FILTER_VALIDATE_IP) === false) @@ -1057,7 +1057,7 @@ function mod_page_ip($ip, string $encoded_cursor = '') { mod_page(sprintf('%s: %s', _('IP'), htmlspecialchars($ip)), 'mod/view_ip.html', $args, $args['hostname']); } -function mod_ban() { +function mod_ban(Context $ctx) { global $config; if (!hasPermission($config['mod']['ban'])) @@ -1078,7 +1078,7 @@ function mod_ban() { header('Location: ?/', true, $config['redirect_http']); } -function mod_warning() { +function mod_warning(Context $ctx) { global $config; if (!hasPermission($config['mod']['warning'])) @@ -1095,7 +1095,7 @@ function mod_warning() { header('Location: ?/', true, $config['redirect_http']); } -function mod_bans() { +function mod_bans(Context $ctx) { global $config; global $mod; @@ -1130,7 +1130,7 @@ function mod_bans() { )); } -function mod_bans_json() { +function mod_bans_json(Context $ctx) { global $config, $mod; if (!hasPermission($config['mod']['ban'])) @@ -1142,7 +1142,7 @@ function mod_bans_json() { Bans::stream_json(false, false, !hasPermission($config['mod']['view_banstaff']), $mod['boards']); } -function mod_ban_appeals() { +function mod_ban_appeals(Context $ctx) { global $config, $board; if (!hasPermission($config['mod']['view_ban_appeals'])) @@ -1224,7 +1224,7 @@ function mod_ban_appeals() { )); } -function mod_lock($board, $unlock, $post) { +function mod_lock(Context $ctx, $board, $unlock, $post) { global $config; if (!openBoard($board)) @@ -1260,7 +1260,7 @@ function mod_lock($board, $unlock, $post) { event('lock', $post); } -function mod_sticky($board, $unsticky, $post) { +function mod_sticky(Context $ctx, $board, $unsticky, $post) { global $config; if (!openBoard($board)) @@ -1284,7 +1284,7 @@ function mod_sticky($board, $unsticky, $post) { header('Location: ?/' . sprintf($config['board_path'], $board) . $config['file_index'], true, $config['redirect_http']); } -function mod_cycle($board, $uncycle, $post) { +function mod_cycle(Context $ctx, $board, $uncycle, $post) { global $config; if (!openBoard($board)) @@ -1306,7 +1306,7 @@ function mod_cycle($board, $uncycle, $post) { header('Location: ?/' . sprintf($config['board_path'], $board) . $config['file_index'], true, $config['redirect_http']); } -function mod_bumplock($board, $unbumplock, $post) { +function mod_bumplock(Context $ctx, $board, $unbumplock, $post) { global $config; if (!openBoard($board)) @@ -1328,8 +1328,8 @@ function mod_bumplock($board, $unbumplock, $post) { header('Location: ?/' . sprintf($config['board_path'], $board) . $config['file_index'], true, $config['redirect_http']); } -function mod_move_reply($originBoard, $postID) { - global $board, $config, $mod; +function mod_move_reply(Context $ctx, $originBoard, $postID) { + global $board, $config; if (!openBoard($originBoard)) error($config['error']['noboard']); @@ -1432,8 +1432,8 @@ function mod_move_reply($originBoard, $postID) { } -function mod_move($originBoard, $postID) { - global $board, $config, $mod, $pdo; +function mod_move(Context $ctx, $originBoard, $postID) { + global $board, $config, $pdo; if (!openBoard($originBoard)) error($config['error']['noboard']); @@ -1645,8 +1645,8 @@ function mod_move($originBoard, $postID) { mod_page(_('Move thread'), 'mod/move.html', array('post' => $postID, 'board' => $originBoard, 'boards' => $boards, 'token' => $security_token)); } -function mod_merge($originBoard, $postID) { - global $board, $config, $mod, $pdo; +function mod_merge(Context $ctx, $originBoard, $postID) { + global $board, $config, $pdo; if (!openBoard($originBoard)) error($config['error']['noboard']); @@ -1655,10 +1655,11 @@ function mod_merge($originBoard, $postID) { error($config['error']['noaccess']); $query = prepare(sprintf('SELECT * FROM ``posts_%s`` WHERE `id` = :id AND `thread` IS NULL', $originBoard)); - $query->bindValue(':id', $postID); + $query->bindValue(':id', (int)$postID, \PDO::PARAM_INT); $query->execute() or error(db_error($query)); - if (!$post = $query->fetch(PDO::FETCH_ASSOC)) + if (!$post = $query->fetch(PDO::FETCH_ASSOC)) { error($config['error']['404']); + } $sourceOp = ""; if ($post['thread']){ $sourceOp = $post['thread']; @@ -1886,7 +1887,7 @@ function mod_merge($originBoard, $postID) { mod_page(_('Merge thread'), 'mod/merge.html', array('post' => $postID, 'board' => $originBoard, 'boards' => $boards, 'token' => $security_token)); } -function mod_ban_post($board, $delete, $post, $token = false) { +function mod_ban_post(Context $ctx, $board, $delete, $post, $token = false) { global $config, $mod; if (!openBoard($board)) @@ -1998,7 +1999,7 @@ function mod_ban_post($board, $delete, $post, $token = false) { 'board' => $board, 'delete' => (bool)$delete, 'boards' => listBoards(), - 'reasons' => $config['ban_reasons'], + 'reasons' => $config['ban_reasons'], 'token' => $security_token ); @@ -2009,7 +2010,7 @@ function mod_ban_post($board, $delete, $post, $token = false) { mod_page(_('New ban'), 'mod/ban_form.html', $args); } -function mod_warning_post($board,$post, $token = false) { +function mod_warning_post(Context $ctx, $board, $post, $token = false) { global $config, $mod; if (!openBoard($board)) @@ -2109,8 +2110,8 @@ function mod_warning_post($board,$post, $token = false) { mod_page(_('New warning'), 'mod/warning_form.html', $args); } -function mod_edit_post($board, $edit_raw_html, $postID) { - global $config, $mod; +function mod_edit_post(Context $ctx, $board, $edit_raw_html, $postID) { + global $config; if (!openBoard($board)) error($config['error']['noboard']); @@ -2186,8 +2187,8 @@ function mod_edit_post($board, $edit_raw_html, $postID) { } } -function mod_delete($board, $post) { - global $config, $mod; +function mod_delete(Context $ctx, $board, $post) { + global $config; if (!openBoard($board)) error($config['error']['noboard']); @@ -2252,8 +2253,8 @@ function mod_delete($board, $post) { } } -function mod_deletefile($board, $post, $file) { - global $config, $mod; +function mod_deletefile(Context $ctx, $board, $post, $file) { + global $config; if (!openBoard($board)) error($config['error']['noboard']); @@ -2275,8 +2276,8 @@ function mod_deletefile($board, $post, $file) { header('Location: ?/' . sprintf($config['board_path'], $board) . $config['file_index'], true, $config['redirect_http']); } -function mod_spoiler_image($board, $post, $file) { - global $config, $mod; +function mod_spoiler_image(Context $ctx, $board, $post, $file) { + global $config; if (!openBoard($board)) error($config['error']['noboard']); @@ -2320,8 +2321,8 @@ function mod_spoiler_image($board, $post, $file) { header('Location: ?/' . sprintf($config['board_path'], $board) . $config['file_index'], true, $config['redirect_http']); } -function mod_deletebyip($boardName, $post, $global = false) { - global $config, $mod, $board; +function mod_deletebyip(Context $ctx, $boardName, $post, $global = false) { + global $config, $board; $global = (bool)$global; @@ -2442,7 +2443,7 @@ function mod_deletebyip($boardName, $post, $global = false) { header('Location: ?/' . sprintf($config['board_path'], $boardName) . $config['file_index'], true, $config['redirect_http']); } -function mod_user($uid) { +function mod_user(Context $ctx, $uid) { global $config, $mod; if (!hasPermission($config['mod']['editusers']) && !(hasPermission($config['mod']['change_password']) && $uid == $mod['id'])) @@ -2567,7 +2568,7 @@ function mod_user($uid) { )); } -function mod_user_new() { +function mod_user_new(Context $ctx) { global $pdo, $config; if (!hasPermission($config['mod']['createusers'])) @@ -2620,7 +2621,7 @@ function mod_user_new() { } -function mod_users() { +function mod_users(Context $ctx) { global $config; if (!hasPermission($config['mod']['manageusers'])) @@ -2641,7 +2642,7 @@ function mod_users() { mod_page(sprintf('%s (%d)', _('Manage users'), count($users)), 'mod/users.html', array('users' => $users)); } -function mod_user_promote($uid, $action) { +function mod_user_promote(Context $ctx, $uid, $action) { global $config; if (!hasPermission($config['mod']['promoteusers'])) @@ -2684,7 +2685,7 @@ function mod_user_promote($uid, $action) { header('Location: ?/users', true, $config['redirect_http']); } -function mod_pm($id, $reply = false) { +function mod_pm(Context $ctx, $id, $reply = false) { global $mod, $config; if ($reply && !hasPermission($config['mod']['create_pm'])) @@ -2739,7 +2740,7 @@ function mod_pm($id, $reply = false) { } } -function mod_inbox() { +function mod_inbox(Context $ctx) { global $config, $mod; $query = prepare('SELECT `unread`,``pms``.`id`, `time`, `sender`, `to`, `message`, `username` FROM ``pms`` LEFT JOIN ``mods`` ON ``mods``.`id` = `sender` WHERE `to` = :mod ORDER BY `unread` DESC, `time` DESC'); @@ -2763,7 +2764,7 @@ function mod_inbox() { } -function mod_new_pm($username) { +function mod_new_pm(Context $ctx, $username) { global $config, $mod; if (!hasPermission($config['mod']['create_pm'])) @@ -2811,7 +2812,7 @@ function mod_new_pm($username) { )); } -function mod_rebuild() { +function mod_rebuild(Context $ctx) { global $config, $twig; if (!hasPermission($config['mod']['rebuild'])) @@ -2883,7 +2884,7 @@ function mod_rebuild() { )); } -function mod_reports() { +function mod_reports(Context $ctx) { global $config, $mod; if (!hasPermission($config['mod']['reports'])) @@ -2987,7 +2988,7 @@ function mod_reports() { ); } -function mod_report_dismiss($id, $all = false) { +function mod_report_dismiss(Context $ctx, $id, $all = false) { global $config; $query = prepare("SELECT `post`, `board`, `ip` FROM ``reports`` WHERE `id` = :id"); @@ -3024,7 +3025,7 @@ function mod_report_dismiss($id, $all = false) { header('Location: ?/reports', true, $config['redirect_http']); } -function mod_recent_posts($lim,$board_list = false,$json=false) { +function mod_recent_posts(Context $ctx, $lim, $board_list = false, $json = false) { global $config, $mod, $pdo; if (!hasPermission($config['mod']['recent'])) @@ -3122,7 +3123,7 @@ function mod_recent_posts($lim,$board_list = false,$json=false) { } } -function mod_config($board_config = false) { +function mod_config(Context $ctx, $board_config = false) { global $config, $mod, $board; if ($board_config && !openBoard($board_config)) @@ -3262,7 +3263,7 @@ function mod_config($board_config = false) { )); } -function mod_themes_list() { +function mod_themes_list(Context $ctx) { global $config; if (!hasPermission($config['mod']['themes'])) @@ -3296,7 +3297,7 @@ function mod_themes_list() { )); } -function mod_theme_configure($theme_name) { +function mod_theme_configure(Context $ctx, $theme_name) { global $config; if (!hasPermission($config['mod']['themes'])) @@ -3378,7 +3379,7 @@ function mod_theme_configure($theme_name) { )); } -function mod_theme_uninstall($theme_name) { +function mod_theme_uninstall(Context $ctx, $theme_name) { global $config; if (!hasPermission($config['mod']['themes'])) @@ -3395,7 +3396,7 @@ function mod_theme_uninstall($theme_name) { header('Location: ?/themes', true, $config['redirect_http']); } -function mod_theme_rebuild($theme_name) { +function mod_theme_rebuild(Context $ctx, $theme_name) { global $config; if (!hasPermission($config['mod']['themes'])) @@ -3436,15 +3437,15 @@ function delete_page_base($page = '', $board = false) { header('Location: ?/edit_pages' . ($board ? ('/' . $board) : ''), true, $config['redirect_http']); } -function mod_delete_page($page = '') { - delete_page_base($page); +function mod_delete_page(Context $ctx, $page = '') { + delete_page_base($ctx, $page); } -function mod_delete_page_board($page = '', $board = false) { - delete_page_base($page, $board); +function mod_delete_page_board(Context $ctx, $page = '', $board = false) { + delete_page_base($ctx, $page, $board); } -function mod_edit_page($id) { +function mod_edit_page(Context $ctx, $id) { global $config, $mod, $board; $query = prepare('SELECT * FROM ``pages`` WHERE `id` = :id'); @@ -3515,7 +3516,7 @@ function mod_edit_page($id) { mod_page(sprintf(_('Editing static page: %s'), $page['name']), 'mod/edit_page.html', array('page' => $page, 'token' => make_secure_link_token("edit_page/$id"), 'content' => prettify_textarea($content), 'board' => $board)); } -function mod_pages($board = false) { +function mod_pages(Context $ctx, $board = false) { global $config, $mod, $pdo; if (empty($board)) @@ -3569,7 +3570,7 @@ function mod_pages($board = false) { mod_page(_('Pages'), 'mod/pages.html', array('pages' => $pages, 'token' => make_secure_link_token('edit_pages' . ($board ? ('/' . $board) : '')), 'board' => $board)); } -function mod_debug_antispam() { +function mod_debug_antispam(Context $ctx) { global $pdo, $config; $args = []; @@ -3606,7 +3607,7 @@ function mod_debug_antispam() { mod_page(_('Debug: Anti-spam'), 'mod/debug/antispam.html', $args); } -function mod_debug_recent_posts() { +function mod_debug_recent_posts(Context $ctx) { global $pdo, $config; $limit = 500; @@ -3640,7 +3641,7 @@ function mod_debug_recent_posts() { mod_page(_('Debug: Recent posts'), 'mod/debug/recent_posts.html', array('posts' => $posts, 'flood_posts' => $flood_posts)); } -function mod_debug_sql() { +function mod_debug_sql(Context $ctx) { global $config; if (!hasPermission($config['mod']['debug_sql'])) @@ -3663,25 +3664,3 @@ function mod_debug_sql() { mod_page(_('Debug: SQL'), 'mod/debug/sql.html', $args); } - -function mod_debug_apc() { - global $config; - - if (!hasPermission($config['mod']['debug_apc'])) - error($config['error']['noaccess']); - - if ($config['cache']['enabled'] != 'apc') - error('APC is not enabled.'); - - $cache_info = apc_cache_info('user'); - - // $cached_vars = new APCIterator('user', '/^' . $config['cache']['prefix'] . '/'); - $cached_vars = []; - foreach ($cache_info['cache_list'] as $var) { - if ($config['cache']['prefix'] != '' && strpos(isset($var['key']) ? $var['key'] : $var['info'], $config['cache']['prefix']) !== 0) - continue; - $cached_vars[] = $var; - } - - mod_page(_('Debug: APC'), 'mod/debug/apc.html', array('cached_vars' => $cached_vars)); -} diff --git a/inc/polyfill.php b/inc/polyfill.php index 2b14c4dc..4368c8e8 100644 --- a/inc/polyfill.php +++ b/inc/polyfill.php @@ -1,187 +1,10 @@ $i ? $i : 0]) ^ ord($theirs[$i]); - } - - return $answer === 0 && $olen === $tlen; - } -} - -if (!function_exists('imagecreatefrombmp')) { - /*********************************************/ - /* Fonction: imagecreatefrombmp */ - /* Author: DHKold */ - /* Contact: admin@dhkold.com */ - /* Date: The 15th of June 2005 */ - /* Version: 2.0B */ - /*********************************************/ - function imagecreatefrombmp($filename) { - if (! $f1 = fopen($filename,"rb")) return FALSE; - $FILE = unpack("vfile_type/Vfile_size/Vreserved/Vbitmap_offset", fread($f1,14)); - if ($FILE['file_type'] != 19778) return FALSE; - $BMP = unpack('Vheader_size/Vwidth/Vheight/vplanes/vbits_per_pixel'. - '/Vcompression/Vsize_bitmap/Vhoriz_resolution'. - '/Vvert_resolution/Vcolors_used/Vcolors_important', fread($f1,40)); - $BMP['colors'] = pow(2,$BMP['bits_per_pixel']); - if ($BMP['size_bitmap'] == 0) $BMP['size_bitmap'] = $FILE['file_size'] - $FILE['bitmap_offset']; - $BMP['bytes_per_pixel'] = $BMP['bits_per_pixel']/8; - $BMP['bytes_per_pixel2'] = ceil($BMP['bytes_per_pixel']); - $BMP['decal'] = ($BMP['width']*$BMP['bytes_per_pixel']/4); - $BMP['decal'] -= floor($BMP['width']*$BMP['bytes_per_pixel']/4); - $BMP['decal'] = 4-(4*$BMP['decal']); - if ($BMP['decal'] == 4) $BMP['decal'] = 0; - $PALETTE = array(); - if ($BMP['colors'] < 16777216) - { - $PALETTE = unpack('V'.$BMP['colors'], fread($f1,$BMP['colors']*4)); - } - $IMG = fread($f1,$BMP['size_bitmap']); - $VIDE = chr(0); - $res = imagecreatetruecolor($BMP['width'],$BMP['height']); - $P = 0; - $Y = $BMP['height']-1; - while ($Y >= 0) - { - $X=0; - while ($X < $BMP['width']) - { - if ($BMP['bits_per_pixel'] == 24) - $COLOR = unpack("V",substr($IMG,$P,3).$VIDE); - elseif ($BMP['bits_per_pixel'] == 16) - { - $COLOR = unpack("n",substr($IMG,$P,2)); - $COLOR[1] = $PALETTE[$COLOR[1]+1]; - } - elseif ($BMP['bits_per_pixel'] == 8) - { - $COLOR = unpack("n",$VIDE.substr($IMG,$P,1)); - $COLOR[1] = $PALETTE[$COLOR[1]+1]; - } - elseif ($BMP['bits_per_pixel'] == 4) - { - $COLOR = unpack("n",$VIDE.substr($IMG,floor($P),1)); - if (($P*2)%2 == 0) $COLOR[1] = ($COLOR[1] >> 4) ; else $COLOR[1] = ($COLOR[1] & 0x0F); - $COLOR[1] = $PALETTE[$COLOR[1]+1]; - } - elseif ($BMP['bits_per_pixel'] == 1) - { - $COLOR = unpack("n",$VIDE.substr($IMG,floor($P),1)); - if (($P*8)%8 == 0) $COLOR[1] = $COLOR[1] >>7; - elseif (($P*8)%8 == 1) $COLOR[1] = ($COLOR[1] & 0x40)>>6; - elseif (($P*8)%8 == 2) $COLOR[1] = ($COLOR[1] & 0x20)>>5; - elseif (($P*8)%8 == 3) $COLOR[1] = ($COLOR[1] & 0x10)>>4; - elseif (($P*8)%8 == 4) $COLOR[1] = ($COLOR[1] & 0x8)>>3; - elseif (($P*8)%8 == 5) $COLOR[1] = ($COLOR[1] & 0x4)>>2; - elseif (($P*8)%8 == 6) $COLOR[1] = ($COLOR[1] & 0x2)>>1; - elseif (($P*8)%8 == 7) $COLOR[1] = ($COLOR[1] & 0x1); - $COLOR[1] = $PALETTE[$COLOR[1]+1]; - } - else - return FALSE; - imagesetpixel($res,$X,$Y,$COLOR[1]); - $X++; - $P += $BMP['bytes_per_pixel']; - } - $Y--; - $P+=$BMP['decal']; - } - fclose($f1); - return $res; - } -} - -if (!function_exists('imagebmp')) { - function imagebmp(&$img, $filename='') { - $widthOrig = imagesx($img); - $widthFloor = ((floor($widthOrig/16))*16); - $widthCeil = ((ceil($widthOrig/16))*16); - $height = imagesy($img); - $size = ($widthCeil*$height*3)+54; - // Bitmap File Header - $result = 'BM'; // header (2b) - $result .= int_to_dword($size); // size of file (4b) - $result .= int_to_dword(0); // reserved (4b) - $result .= int_to_dword(54); // byte location in the file which is first byte of IMAGE (4b) - // Bitmap Info Header - $result .= int_to_dword(40); // Size of BITMAPINFOHEADER (4b) - $result .= int_to_dword($widthCeil); // width of bitmap (4b) - $result .= int_to_dword($height); // height of bitmap (4b) - $result .= int_to_word(1); // biPlanes = 1 (2b) - $result .= int_to_word(24); // biBitCount = {1 (mono) or 4 (16 clr ) or 8 (256 clr) or 24 (16 Mil)} (2b - $result .= int_to_dword(0); // RLE COMPRESSION (4b) - $result .= int_to_dword(0); // width x height (4b) - $result .= int_to_dword(0); // biXPelsPerMeter (4b) - $result .= int_to_dword(0); // biYPelsPerMeter (4b) - $result .= int_to_dword(0); // Number of palettes used (4b) - $result .= int_to_dword(0); // Number of important colour (4b) - // is faster than chr() - $arrChr = array(); - for ($i=0; $i<256; $i++){ - $arrChr[$i] = chr($i); - } - // creates image data - $bgfillcolor = array('red'=>0, 'green'=>0, 'blue'=>0); - // bottom to top - left to right - attention blue green red !!! - $y=$height-1; - for ($y2=0; $y2<$height; $y2++) { - for ($x=0; $x<$widthFloor; ) { - $rgb = imagecolorsforindex($img, imagecolorat($img, $x++, $y)); - $result .= $arrChr[$rgb['blue']].$arrChr[$rgb['green']].$arrChr[$rgb['red']]; - $rgb = imagecolorsforindex($img, imagecolorat($img, $x++, $y)); - $result .= $arrChr[$rgb['blue']].$arrChr[$rgb['green']].$arrChr[$rgb['red']]; - $rgb = imagecolorsforindex($img, imagecolorat($img, $x++, $y)); - $result .= $arrChr[$rgb['blue']].$arrChr[$rgb['green']].$arrChr[$rgb['red']]; - $rgb = imagecolorsforindex($img, imagecolorat($img, $x++, $y)); - $result .= $arrChr[$rgb['blue']].$arrChr[$rgb['green']].$arrChr[$rgb['red']]; - $rgb = imagecolorsforindex($img, imagecolorat($img, $x++, $y)); - $result .= $arrChr[$rgb['blue']].$arrChr[$rgb['green']].$arrChr[$rgb['red']]; - $rgb = imagecolorsforindex($img, imagecolorat($img, $x++, $y)); - $result .= $arrChr[$rgb['blue']].$arrChr[$rgb['green']].$arrChr[$rgb['red']]; - $rgb = imagecolorsforindex($img, imagecolorat($img, $x++, $y)); - $result .= $arrChr[$rgb['blue']].$arrChr[$rgb['green']].$arrChr[$rgb['red']]; - $rgb = imagecolorsforindex($img, imagecolorat($img, $x++, $y)); - $result .= $arrChr[$rgb['blue']].$arrChr[$rgb['green']].$arrChr[$rgb['red']]; - } - for ($x=$widthFloor; $x<$widthCeil; $x++) { - $rgb = ($x<$widthOrig) ? imagecolorsforindex($img, imagecolorat($img, $x, $y)) : $bgfillcolor; - $result .= $arrChr[$rgb['blue']].$arrChr[$rgb['green']].$arrChr[$rgb['red']]; - } - $y--; - } - // see imagegif - if ($filename == '') { - echo $result; - } else { - $file = fopen($filename, 'wb'); - fwrite($file, $result); - fclose($file); - } - } - // imagebmp helpers - function int_to_dword($n) { - return chr($n & 255).chr(($n >> 8) & 255).chr(($n >> 16) & 255).chr(($n >> 24) & 255); - } - function int_to_word($n) { - return chr($n & 255).chr(($n >> 8) & 255); +if (!function_exists('str_starts_with')) { + function str_starts_with(string $haystack, string $needle): bool { + // https://wiki.php.net/rfc/add_str_starts_with_and_ends_with_functions#str_starts_with + return \strncmp($haystack, $needle, \strlen($needle)) === 0; } } diff --git a/mod.php b/mod.php index 9587c8e1..bb260251 100644 --- a/mod.php +++ b/mod.php @@ -1,18 +1,21 @@ ':?/', // redirect to dashboard '/' => 'dashboard', // dashboard '/confirm/(.+)' => 'confirm', // confirm action (if javascript didn't work) @@ -107,7 +110,6 @@ $pages = array( // these pages aren't listed in the dashboard without $config['debug'] '/debug/antispam' => 'debug_antispam', '/debug/recent' => 'debug_recent_posts', - '/debug/apc' => 'debug_apc', '/debug/sql' => 'secure_POST debug_sql', // This should always be at the end: @@ -121,14 +123,14 @@ $pages = array( str_replace('%d', '(\d+)', preg_quote($config['file_page'], '!')) => 'view_thread', '/(\%b)/' . preg_quote($config['dir']['res'], '!') . - str_replace(array('%d','%s'), array('(\d+)', '[a-z0-9-]+'), preg_quote($config['file_page50_slug'], '!')) => 'view_thread50', + str_replace([ '%d','%s' ], [ '(\d+)', '[a-z0-9-]+' ], preg_quote($config['file_page50_slug'], '!')) => 'view_thread50', '/(\%b)/' . preg_quote($config['dir']['res'], '!') . - str_replace(array('%d','%s'), array('(\d+)', '[a-z0-9-]+'), preg_quote($config['file_page_slug'], '!')) => 'view_thread', -); + str_replace([ '%d','%s' ], [ '(\d+)', '[a-z0-9-]+' ], preg_quote($config['file_page_slug'], '!')) => 'view_thread', +]; if (!$mod) { - $pages = array('!^(.+)?$!' => 'login'); + $pages = [ '!^(.+)?$!' => 'login' ]; } elseif (isset($_GET['status'], $_GET['r'])) { header('Location: ' . $_GET['r'], true, (int)$_GET['status']); exit; @@ -138,12 +140,11 @@ if (isset($config['mod']['custom_pages'])) { $pages = array_merge($pages, $config['mod']['custom_pages']); } -$new_pages = array(); +$new_pages = []; foreach ($pages as $key => $callback) { if (is_string($callback) && preg_match('/^secure /', $callback)) { $key .= '(/(?P[a-f0-9]{8}))?'; - } - + } $key = str_replace('\%b', '?P' . sprintf(substr($config['board_path'], 0, -1), $config['board_regex']), $key); $new_pages[@$key[0] == '!' ? $key : '!^' . $key . '(?:&[^&=]+=[^&]*)*$!u'] = $callback; } @@ -151,7 +152,7 @@ $pages = $new_pages; foreach ($pages as $uri => $handler) { if (preg_match($uri, $query, $matches)) { - $matches = array_slice($matches, 1); + $matches[0] = $ctx; // Replace the text captured by the full pattern with a reference to the context. if (isset($matches['board'])) { $board_match = $matches['board']; @@ -171,7 +172,7 @@ foreach ($pages as $uri => $handler) { if ($secure_post_only) error($config['error']['csrf']); else { - mod_confirm(substr($query, 1)); + mod_confirm($ctx, substr($query, 1)); exit; } } @@ -186,24 +187,20 @@ foreach ($pages as $uri => $handler) { } if ($config['debug']) { - $debug['mod_page'] = array( + $debug['mod_page'] = [ 'req' => $query, 'match' => $uri, 'handler' => $handler, - ); + ]; $debug['time']['parse_mod_req'] = '~' . round((microtime(true) - $parse_start_time) * 1000, 2) . 'ms'; } - if (is_array($matches)) { - // we don't want to call named parameters (PHP 8) - $matches = array_values($matches); - } + // We don't want to call named parameters (PHP 8). + $matches = array_values($matches); if (is_string($handler)) { if ($handler[0] == ':') { header('Location: ' . substr($handler, 1), true, $config['redirect_http']); - } elseif (is_callable("mod_page_$handler")) { - call_user_func_array("mod_page_$handler", $matches); } elseif (is_callable("mod_$handler")) { call_user_func_array("mod_$handler", $matches); } else { diff --git a/post.php b/post.php index 9a2a219b..78a1bb90 100644 --- a/post.php +++ b/post.php @@ -3,6 +3,8 @@ * Copyright (c) 2010-2014 Tinyboard Development Group */ +use Vichan\Context; + require_once 'inc/bootstrap.php'; /** @@ -529,7 +531,7 @@ function handle_nntpchan() ); } -function handle_delete() +function handle_delete(Context $ctx) { // Delete global $config, $board, $mod; @@ -537,7 +539,7 @@ function handle_delete() error($config['error']['bot']); } - check_login(false); + check_login($ctx, false); $is_mod = !!$mod; if (isset($_POST['mod']) && $_POST['mod'] && !$mod) { @@ -653,7 +655,7 @@ function handle_delete() rebuildThemes('post-delete', $board['uri']); } -function handle_report() +function handle_report(Context $ctx) { global $config, $board; if (!isset($_POST['board'], $_POST['reason'])) @@ -802,7 +804,7 @@ function handle_report() } } -function handle_post() +function handle_post(Context $ctx) { global $config, $dropped_post, $board, $mod, $pdo; @@ -911,7 +913,7 @@ function handle_post() } if ($post['mod'] = isset($_POST['mod']) && $_POST['mod']) { - check_login(false); + check_login($ctx, false); if (!$mod) { // Liar. You're not a mod >:-[ error($config['error']['notamod']); @@ -1846,7 +1848,7 @@ function handle_post() } } -function handle_appeal() +function handle_appeal(Context $ctx) { global $config; if (!isset($_POST['ban_id'])) { @@ -1899,14 +1901,16 @@ if (isset($_GET['Newsgroups'])) { } } +$ctx = Vichan\build_context($config); + if (isset($_POST['delete'])) { - handle_delete(); + handle_delete($ctx); } elseif (isset($_POST['report'])) { - handle_report(); + handle_report($ctx); } elseif (isset($_POST['post']) || $dropped_post) { - handle_post(); + handle_post($ctx); } elseif (isset($_POST['appeal'])) { - handle_appeal(); + handle_appeal($ctx); } else { if (!file_exists($config['has_installed'])) { header('Location: install.php', true, $config['redirect_http']); diff --git a/tools/maintenance.php b/tools/maintenance.php index 734d2d45..075fe5ce 100644 --- a/tools/maintenance.php +++ b/tools/maintenance.php @@ -87,5 +87,20 @@ echo "Deleted $deleted_count invalid reports 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");