From 3016d694289e308f2b289dcee196952e0f336f90 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Mon, 1 Apr 2024 19:36:03 +0200 Subject: [PATCH 001/336] Share REST call code and separate components via dependency injection --- composer.json | 4 +- inc/config.php | 2 + inc/context.php | 28 +++++ inc/driver/http-driver.php | 151 +++++++++++++++++++++++ inc/service/captcha-queries.php | 102 ++++++++++++++++ post.php | 208 ++++++++++++++++---------------- 6 files changed, 392 insertions(+), 103 deletions(-) create mode 100644 inc/context.php create mode 100644 inc/driver/http-driver.php create mode 100644 inc/service/captcha-queries.php diff --git a/composer.json b/composer.json index ec4a090d..e1951679 100644 --- a/composer.json +++ b/composer.json @@ -32,7 +32,9 @@ "inc/mod/auth.php", "inc/lock.php", "inc/queue.php", - "inc/functions.php" + "inc/functions.php", + "inc/driver/http-driver.php", + "inc/service/captcha-queries.php" ] }, "license": "Tinyboard + vichan", diff --git a/inc/config.php b/inc/config.php index 0cece593..ffd1b544 100644 --- a/inc/config.php +++ b/inc/config.php @@ -1232,6 +1232,8 @@ $config['error']['captcha'] = _('You seem to have mistyped the verification.'); $config['error']['flag_undefined'] = _('The flag %s is undefined, your PHP version is too old!'); $config['error']['flag_wrongtype'] = _('defined_flags_accumulate(): The flag %s is of the wrong type!'); + $config['error']['remote_io_error'] = _('IO error while interacting with a remote service.'); + $config['error']['local_io_error'] = _('IO error while interacting with a local resource or service.'); // Moderator errors diff --git a/inc/context.php b/inc/context.php new file mode 100644 index 00000000..5e540bab --- /dev/null +++ b/inc/context.php @@ -0,0 +1,28 @@ +config = $config; + } + + public function getHttpDriver(): HttpDriver { + if (is_null($this->http)) { + $this->http = HttpDrivers::getHttpDriver($this->config['upload_by_url_timeout'], $this->config['max_filesize']); + } + return $this->http; + } +} diff --git a/inc/driver/http-driver.php b/inc/driver/http-driver.php new file mode 100644 index 00000000..cfbedfad --- /dev/null +++ b/inc/driver/http-driver.php @@ -0,0 +1,151 @@ +inner); + curl_setopt_array($this->inner, array( + CURLOPT_URL => $url, + CURLOPT_TIMEOUT => $this->timeout, + CURLOPT_USERAGENT => $this->user_agent, + CURLOPT_PROTOCOLS => CURLPROTO_HTTP | CURLPROTO_HTTPS, + )); + } + + private function setSizeLimit(): void { + // Adapted from: https://stackoverflow.com/a/17642638 + curl_setopt($this->inner, CURLOPT_NOPROGRESS, false); + + if (PHP_MAJOR_VERSION >= 8 && PHP_MINOR_VERSION >= 2) { + curl_setopt($this->inner, CURLOPT_XFERINFOFUNCTION, function($res, $next_dl, $dl, $next_up, $up) { + return (int)($dl <= $this->max_file_size); + }); + } else { + curl_setopt($this->inner, CURLOPT_PROGRESSFUNCTION, function($res, $next_dl, $dl, $next_up, $up) { + return (int)($dl <= $this->max_file_size); + }); + } + } + + function __construct($timeout, $user_agent, $max_file_size) { + $this->inner = curl_init(); + $this->timeout = $timeout; + $this->user_agent = $user_agent; + $this->max_file_size = $max_file_size; + } + + function __destruct() { + curl_close($this->inner); + } + + /** + * Execute a GET request. + * + * @param string $endpoint Uri endpoint. + * @param ?array $data Optional GET parameters. + * @param int $timeout Optional request timeout in seconds. Use the default timeout if 0. + * @return string Returns the body of the response. + * @throws RuntimeException Throws on IO error. + */ + public function requestGet(string $endpoint, ?array $data, int $timeout = 0): string { + if (!empty($data)) { + $endpoint .= '?' . http_build_query($data); + } + if ($timeout == 0) { + $timeout = $this->timeout; + } + + $this->resetTowards($endpoint, $timeout); + curl_setopt($this->inner, CURLOPT_RETURNTRANSFER, true); + $ret = curl_exec($this->inner); + + if ($ret === false) { + throw new \RuntimeException(curl_error($this->inner)); + } + return $ret; + } + + /** + * Execute a POST request. + * + * @param string $endpoint Uri endpoint. + * @param ?array $data Optional POST parameters. + * @param int $timeout Optional request timeout in seconds. Use the default timeout if 0. + * @return string Returns the body of the response. + * @throws RuntimeException Throws on IO error. + */ + public function requestPost(string $endpoint, ?array $data, int $timeout = 0): string { + if ($timeout == 0) { + $timeout = $this->timeout; + } + + $this->resetTowards($endpoint, $timeout); + curl_setopt($this->inner, CURLOPT_POST, true); + if (!empty($data)) { + curl_setopt($this->inner, CURLOPT_POSTFIELDS, http_build_query($data)); + } + curl_setopt($this->inner, CURLOPT_RETURNTRANSFER, true); + $ret = curl_exec($this->inner); + + if ($ret === false) { + throw new \RuntimeException(curl_error($this->inner)); + } + return $ret; + } + + /** + * Download the url's target with curl. + * + * @param string $url Url to the file to download. + * @param ?array $data Optional GET parameters. + * @param resource $fd File descriptor to save the content to. + * @param int $timeout Optional request timeout in seconds. Use the default timeout if 0. + * @return bool Returns true on success, false if the file was too large. + * @throws RuntimeException Throws on IO error. + */ + public function requestGetInto(string $endpoint, ?array $data, mixed $fd, int $timeout = 0): bool { + if (!empty($data)) { + $endpoint .= '?' . http_build_query($data); + } + if ($timeout == 0) { + $timeout = $this->timeout; + } + + $this->resetTowards($endpoint, $timeout); + curl_setopt($this->inner, CURLOPT_FAILONERROR, true); + curl_setopt($this->inner, CURLOPT_FOLLOWLOCATION, false); + curl_setopt($this->inner, CURLOPT_FILE, $fd); + curl_setopt($this->inner, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); + $this->setSizeLimit(); + $ret = curl_exec($this->inner); + + if ($ret === false) { + if (curl_errno($this->inner) === CURLE_ABORTED_BY_CALLBACK) { + return false; + } + + throw new \RuntimeException(curl_error($this->inner)); + } + return true; + } +} diff --git a/inc/service/captcha-queries.php b/inc/service/captcha-queries.php new file mode 100644 index 00000000..d7966501 --- /dev/null +++ b/inc/service/captcha-queries.php @@ -0,0 +1,102 @@ +http = $http; + $this->secret = $secret; + $this->endpoint = $endpoint; + } + + /** + * Checks if the user at the remote ip passed the captcha. + * + * @param string $response User provided response. + * @param string $remote_ip User ip. + * @return bool Returns true if the user passed the captcha. + * @throws RuntimeException|JsonException Throws on IO errors or if it fails to decode the answer. + */ + public function verify(string $response, string $remote_ip): bool { + $data = array( + 'secret' => $this->secret, + 'response' => $response, + 'remoteip' => $remote_ip + ); + + $ret = $this->http->requestGet($this->endpoint, $data); + $resp = json_decode($ret, true, 16, JSON_THROW_ON_ERROR); + + return isset($resp['success']) && $resp['success']; + } +} + +class NativeCaptchaQuery { + private HttpDriver $http; + private string $domain; + private string $provider_check; + + + /** + * @param HttpDriver $http The http client. + * @param string $domain The server's domain. + * @param string $provider_check Path to the endpoint. + */ + function __construct(HttpDriver $http, string $domain, string $provider_check) { + $this->http = $http; + $this->domain = $domain; + $this->provider_check = $provider_check; + } + + /** + * Checks if the user at the remote ip passed the native vichan captcha. + * + * @param string $extra Extra http parameters. + * @param string $user_text Remote user's text input. + * @param string $user_cookie Remote user cookie. + * @return bool Returns true if the user passed the check. + * @throws RuntimeException Throws on IO errors. + */ + public function verify(string $extra, string $user_text, string $user_cookie): bool { + $data = array( + 'mode' => 'check', + 'text' => $user_text, + 'extra' => $extra, + 'cookie' => $user_cookie + ); + + $ret = $this->http->requestGet($this->domain . '/' . $this->provider_check, $data); + return $ret === '1'; + } +} diff --git a/post.php b/post.php index 2cc99071..d43f37a5 100644 --- a/post.php +++ b/post.php @@ -5,6 +5,10 @@ require_once 'inc/bootstrap.php'; +use Vichan\AppContext; +use Vichan\Driver\HttpDriver; +use Vichan\Service\{RemoteCaptchaQuery, NativeCaptchaQuery}; + /** * Utility functions */ @@ -61,54 +65,27 @@ function strip_symbols($input) { } } -/** - * Download the url's target with curl. - * - * @param string $url Url to the file to download. - * @param int $timeout Request timeout in seconds. - * @param File $fd File descriptor to save the content to. - * @return null|string Returns a string on error. - */ -function download_file_into($url, $timeout, $fd) { - $err = null; - $curl = curl_init(); - curl_setopt($curl, CURLOPT_URL, $url); - curl_setopt($curl, CURLOPT_FAILONERROR, true); - curl_setopt($curl, CURLOPT_FOLLOWLOCATION, false); - curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 5); - curl_setopt($curl, CURLOPT_TIMEOUT, $timeout); - curl_setopt($curl, CURLOPT_USERAGENT, 'Tinyboard'); - curl_setopt($curl, CURLOPT_FILE, $fd); - curl_setopt($curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); - curl_setopt($curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); - - if (curl_exec($curl) === false) { - $err = curl_error($curl); - } - - curl_close($curl); - return $err; -} - /** * Download a remote file from the given url. * The file is deleted at shutdown. * + * @param HttpDriver $http The http client. * @param string $file_url The url to download the file from. * @param int $request_timeout Timeout to retrieve the file. * @param array $extra_extensions Allowed file extensions. * @param string $tmp_dir Temporary directory to save the file into. * @param array $error_array An array with error codes, used to create exceptions on failure. - * @return array Returns an array describing the file on success. - * @throws Exception on error. + * @return array|false Returns an array describing the file on success, or false if the file was too large + * @throws InvalidArgumentException|RuntimeException Throws on invalid arguments and IO errors. */ -function download_file_from_url($file_url, $request_timeout, $allowed_extensions, $tmp_dir, &$error_array) { +function download_file_from_url(HttpDriver $http, $file_url, $request_timeout, $allowed_extensions, $tmp_dir, &$error_array) { if (!preg_match('@^https?://@', $file_url)) { throw new InvalidArgumentException($error_array['invalidimg']); } - if (mb_strpos($file_url, '?') !== false) { - $url_without_params = mb_substr($file_url, 0, mb_strpos($file_url, '?')); + $param_idx = mb_strpos($file_url, '?'); + if ($param_idx !== false) { + $url_without_params = mb_substr($file_url, 0, $param_idx); } else { $url_without_params = $file_url; } @@ -128,10 +105,13 @@ function download_file_from_url($file_url, $request_timeout, $allowed_extensions $fd = fopen($tmp_file, 'w'); - $dl_err = download_file_into($fd, $request_timeout, $fd); - fclose($fd); - if ($dl_err !== null) { - throw new Exception($error_array['nomove'] . '
Curl says: ' . $dl_err); + try { + $success = $http->requestGetInto($url_without_params, null, $fd, $request_timeout); + if (!$success) { + return false; + } + } finally { + fclose($fd); } return array( @@ -165,6 +145,7 @@ function ocr_image(array $config, string $img_path): string { return trim($ret); } + /** * Trim an image's EXIF metadata * @@ -190,6 +171,7 @@ function strip_image_metadata(string $img_path): int { */ $dropped_post = false; +$context = new AppContext($config); // Is it a post coming from NNTP? Let's extract it and pretend it's a normal post. if (isset($_GET['Newsgroups']) && $config['nntpchan']['enabled']) { @@ -487,22 +469,32 @@ if (isset($_POST['delete'])) { if (count($report) > $config['report_limit']) error($config['error']['toomanyreports']); - if ($config['report_captcha'] && !isset($_POST['captcha_text'], $_POST['captcha_cookie'])) { - error($config['error']['bot']); - } if ($config['report_captcha']) { - $ch = curl_init($config['domain'].'/'.$config['captcha']['provider_check'] . "?" . http_build_query([ - 'mode' => 'check', - 'text' => $_POST['captcha_text'], - 'extra' => $config['captcha']['extra'], - 'cookie' => $_POST['captcha_cookie'] - ])); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - $resp = curl_exec($ch); + if (!isset($_POST['captcha_text'], $_POST['captcha_cookie'])) { + error($config['error']['bot']); + } - if ($resp !== '1') { - error($config['error']['captcha']); + try { + $query = new NativeCaptchaQuery( + $context->getHttpDriver(), + $config['domain'], + $config['captcha']['provider_check'] + ); + $success = $query->verify( + $config['captcha']['extra'], + $_POST['captcha_text'], + $_POST['captcha_cookie'] + ); + + if (!$success) { + error($config['error']['captcha']); + } + } catch (RuntimeException $e) { + if ($config['syslog']) { + _syslog(LOG_ERR, "Native captcha IO exception: {$e->getMessage()}"); + } + error($config['error']['local_io_error']); } } @@ -598,62 +590,60 @@ if (isset($_POST['delete'])) { // Check if banned checkBan($board['uri']); - // Check for CAPTCHA right after opening the board so the "return" link is in there - if ($config['recaptcha']) { - if (!isset($_POST['g-recaptcha-response'])) - error($config['error']['bot']); + // Check for CAPTCHA right after opening the board so the "return" link is in there. + try { + // With our custom captcha provider + if ($config['captcha']['enabled'] || ($post['op'] && $config['new_thread_capt'])) { + $query = new NativeCaptchaQuery($context->getHttpDriver(), $config['domain'], $config['captcha']['provider_check']); + $success = $query->verify($config['captcha']['extra'], $_POST['captcha_text'], $_POST['captcha_cookie']); - // Check what reCAPTCHA has to say... - $resp = json_decode(file_get_contents(sprintf('https://www.recaptcha.net/recaptcha/api/siteverify?secret=%s&response=%s&remoteip=%s', - $config['recaptcha_private'], - urlencode($_POST['g-recaptcha-response']), - $_SERVER['REMOTE_ADDR'])), true); - - if (!$resp['success']) { - error($config['error']['captcha']); + if (!$success) { + error( + $config['error']['captcha'] + . '' + ); + } } - } - // hCaptcha - if ($config['hcaptcha']) { - if (!isset($_POST['h-captcha-response'])) { - error($config['error']['bot']); + // Remote 3rd party captchas. + else { + // recaptcha + if ($config['recaptcha']) { + if (!isset($_POST['g-recaptcha-response'])) { + error($config['error']['bot']); + } + $response = $_POST['g-recaptcha-response']; + $query = RemoteCaptchaQuery::withRecaptcha($context->getHttpDriver(), $config['recaptcha_private']); + } + // hCaptcha + elseif ($config['hcaptcha']) { + if (!isset($_POST['h-captcha-response'])) { + error($config['error']['bot']); + } + $response = $_POST['g-recaptcha-response']; + $query = RemoteCaptchaQuery::withHCaptcha($context->getHttpDriver(), $config['hcaptcha_private']); + } + + $success = $query->verify($response, $_SERVER['REMOTE_ADDR']); + if (!$success) { + error($config['error']['captcha']); + } } - - $data = array( - 'secret' => $config['hcaptcha_private'], - 'response' => $_POST['h-captcha-response'], - 'remoteip' => $_SERVER['REMOTE_ADDR'] - ); - - $hcaptchaverify = curl_init(); - curl_setopt($hcaptchaverify, CURLOPT_URL, "https://hcaptcha.com/siteverify"); - curl_setopt($hcaptchaverify, CURLOPT_POST, true); - curl_setopt($hcaptchaverify, CURLOPT_POSTFIELDS, http_build_query($data)); - curl_setopt($hcaptchaverify, CURLOPT_RETURNTRANSFER, true); - $hcaptcharesponse = curl_exec($hcaptchaverify); - - $resp = json_decode($hcaptcharesponse, true); // Decoding $hcaptcharesponse instead of $response - - if (!$resp['success']) { - error($config['error']['captcha']); + } catch (RuntimeException $e) { + if ($config['syslog']) { + _syslog(LOG_ERR, "Captcha IO exception: {$e->getMessage()}"); } + error($config['error']['remote_io_error']); + } catch (JsonException $e) { + if ($config['syslog']) { + _syslog(LOG_ERR, "Bad JSON reply to captcha: {$e->getMessage()}"); + } + error($config['error']['remote_io_error']); } - // Same, but now with our custom captcha provider - if (($config['captcha']['enabled']) || (($post['op']) && ($config['new_thread_capt'])) ) { - $ch = curl_init($config['domain'].'/'.$config['captcha']['provider_check'] . "?" . http_build_query([ - 'mode' => 'check', - 'text' => $_POST['captcha_text'], - 'extra' => $config['captcha']['extra'], - 'cookie' => $_POST['captcha_cookie'] - ])); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - $resp = curl_exec($ch); - if ($resp !== '1') { - error($config['error']['captcha'] . - ''); - } - } if (!(($post['op'] && $_POST['post'] == $config['button_newtopic']) || (!$post['op'] && $_POST['post'] == $config['button_reply']))) @@ -757,7 +747,21 @@ if (isset($_POST['delete'])) { } try { - $_FILES['file'] = download_file_from_url($_POST['file_url'], $config['upload_by_url_timeout'], $allowed_extensions, $config['tmp'], $config['error']); + $ret = download_file_from_url( + $context->getHttpDriver(), + $_POST['file_url'], + $config['upload_by_url_timeout'], + $allowed_extensions, + $config['tmp'], + $config['error'] + ); + if ($ret === false) { + error(sprintf3($config['error']['filesize'], array( + 'filesz' => 'more than that', + 'maxsz' => number_format($config['max_filesize']) + ))); + } + $_FILES['file'] = $ret; } catch (Exception $e) { error($e->getMessage()); } From 650ef8bcc28f7d364fc55f30ea19c6985d54fe30 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Mon, 1 Apr 2024 19:36:37 +0200 Subject: [PATCH 002/336] Fix crash if no captcha is enabled --- post.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/post.php b/post.php index d43f37a5..954b2ed5 100644 --- a/post.php +++ b/post.php @@ -627,9 +627,11 @@ if (isset($_POST['delete'])) { $query = RemoteCaptchaQuery::withHCaptcha($context->getHttpDriver(), $config['hcaptcha_private']); } - $success = $query->verify($response, $_SERVER['REMOTE_ADDR']); - if (!$success) { - error($config['error']['captcha']); + if (isset($query, $response)) { + $success = $query->verify($response, $_SERVER['REMOTE_ADDR']); + if (!$success) { + error($config['error']['captcha']); + } } } } catch (RuntimeException $e) { From 8120c42440ad6e74017b0c5b0cdf07355228c3b3 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Mon, 1 Apr 2024 19:46:04 +0200 Subject: [PATCH 003/336] Format injected javascript on failed native captcha --- post.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/post.php b/post.php index 954b2ed5..6fdbfc0b 100644 --- a/post.php +++ b/post.php @@ -599,12 +599,14 @@ if (isset($_POST['delete'])) { if (!$success) { error( - $config['error']['captcha'] - . '' + "{$config['error']['captcha']} + " ); } } From 00b05099f33257bd33ebb1548bec3664b653e235 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Fri, 16 Feb 2024 14:18:55 +0100 Subject: [PATCH 004/336] Format lock.php --- inc/lock.php | 68 +++++++++++++++++++++++++++------------------------- 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/inc/lock.php b/inc/lock.php index 4fb2f5df..23c0bce4 100644 --- a/inc/lock.php +++ b/inc/lock.php @@ -1,39 +1,43 @@ f = fopen("tmp/locks/$key", "w"); - } - } + $this->f = fopen("tmp/locks/$key", "w"); + } + } - // Get a shared lock - function get($nonblock = false) { global $config; - if ($config['lock']['enabled'] == 'fs') { - $wouldblock = false; - flock($this->f, LOCK_SH | ($nonblock ? LOCK_NB : 0), $wouldblock); - if ($nonblock && $wouldblock) return false; - } - return $this; - } + // Get a shared lock + function get($nonblock = false) { + global $config; + if ($config['lock']['enabled'] == 'fs') { + $wouldblock = false; + flock($this->f, LOCK_SH | ($nonblock ? LOCK_NB : 0), $wouldblock); + if ($nonblock && $wouldblock) return false; + } + return $this; + } - // Get an exclusive lock - function get_ex($nonblock = false) { global $config; - if ($config['lock']['enabled'] == 'fs') { - $wouldblock = false; - flock($this->f, LOCK_EX | ($nonblock ? LOCK_NB : 0), $wouldblock); - if ($nonblock && $wouldblock) return false; - } - return $this; - } + // Get an exclusive lock + function get_ex($nonblock = false) { + global $config; + if ($config['lock']['enabled'] == 'fs') { + $wouldblock = false; + flock($this->f, LOCK_EX | ($nonblock ? LOCK_NB : 0), $wouldblock); + if ($nonblock && $wouldblock) return false; + } + return $this; + } - // Free a lock - function free() { global $config; - if ($config['lock']['enabled'] == 'fs') { - flock($this->f, LOCK_UN); - } - return $this; - } + // Free a lock + function free() { + global $config; + if ($config['lock']['enabled'] == 'fs') { + flock($this->f, LOCK_UN); + } + return $this; + } } From 760431606d1309180929eda8f203d65a42039bf6 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Fri, 16 Feb 2024 14:47:20 +0100 Subject: [PATCH 005/336] Refactor lock.php --- inc/lock.php | 105 +++++++++++++++++++++++++++++++++++--------------- inc/queue.php | 2 +- 2 files changed, 74 insertions(+), 33 deletions(-) diff --git a/inc/lock.php b/inc/lock.php index 23c0bce4..5a83562a 100644 --- a/inc/lock.php +++ b/inc/lock.php @@ -1,43 +1,84 @@ f = fopen("tmp/locks/$key", "w"); +class Locks { + private static function filesystem(string $key): Lock|false { + $key = str_replace('/', '::', $key); + $key = str_replace("\0", '', $key); + + $fd = fopen("tmp/locks/$key", "w"); + if ($fd === false) { + return false; } + + return new class($fd) implements Lock { + // Resources have no type in php. + private mixed $f; + + + function __construct($fd) { + $this->f = $fd; + } + + public function get(bool $nonblock = false): Lock|false { + $wouldblock = false; + flock($this->f, LOCK_SH | ($nonblock ? LOCK_NB : 0), $wouldblock); + if ($nonblock && $wouldblock) { + return false; + } + return $this; + } + + public function get_ex(bool $nonblock = false): Lock|false { + $wouldblock = false; + flock($this->f, LOCK_EX | ($nonblock ? LOCK_NB : 0), $wouldblock); + if ($nonblock && $wouldblock) { + return false; + } + return $this; + } + + public function free(): Lock { + flock($this->f, LOCK_UN); + return $this; + } + }; } - // Get a shared lock - function get($nonblock = false) { - global $config; - if ($config['lock']['enabled'] == 'fs') { - $wouldblock = false; - flock($this->f, LOCK_SH | ($nonblock ? LOCK_NB : 0), $wouldblock); - if ($nonblock && $wouldblock) return false; - } - return $this; + /** + * No-op. Can be used for mocking. + */ + public static function none(): Lock|false { + return new class() implements Lock { + public function get(bool $nonblock = false): Lock|false { + return $this; + } + + public function get_ex(bool $nonblock = false): Lock|false { + return $this; + } + + public function free(): Lock { + return $this; + } + }; } - // Get an exclusive lock - function get_ex($nonblock = false) { - global $config; + public static function get_lock(array $config, string $key): Lock|false { if ($config['lock']['enabled'] == 'fs') { - $wouldblock = false; - flock($this->f, LOCK_EX | ($nonblock ? LOCK_NB : 0), $wouldblock); - if ($nonblock && $wouldblock) return false; + return self::filesystem($key); + } else { + return self::none(); } - return $this; - } - - // Free a lock - function free() { - global $config; - if ($config['lock']['enabled'] == 'fs') { - flock($this->f, LOCK_UN); - } - return $this; } } + +interface Lock { + // Get a shared lock + public function get(bool $nonblock = false): Lock|false; + + // Get an exclusive lock + public function get_ex(bool $nonblock = false): Lock|false; + + // Free a lock + public function free(): Lock; +} diff --git a/inc/queue.php b/inc/queue.php index 66305b3b..a3873491 100644 --- a/inc/queue.php +++ b/inc/queue.php @@ -3,7 +3,7 @@ class Queue { function __construct($key) { global $config; if ($config['queue']['enabled'] == 'fs') { - $this->lock = new Lock($key); + $this->lock = Locks::get_lock($config, $key); $key = str_replace('/', '::', $key); $key = str_replace("\0", '', $key); $this->key = "tmp/queue/$key/"; From 55034762b07cdcb143c4daa42ba26c52260094a9 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Fri, 16 Feb 2024 14:48:42 +0100 Subject: [PATCH 006/336] Format queue.php --- inc/queue.php | 74 +++++++++++++++++++++++++-------------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/inc/queue.php b/inc/queue.php index a3873491..2801170e 100644 --- a/inc/queue.php +++ b/inc/queue.php @@ -1,49 +1,49 @@ lock = Locks::get_lock($config, $key); - $key = str_replace('/', '::', $key); - $key = str_replace("\0", '', $key); - $this->key = "tmp/queue/$key/"; - } - } + function __construct($key) { global $config; + if ($config['queue']['enabled'] == 'fs') { + $this->lock = Locks::get_lock($config, $key); + $key = str_replace('/', '::', $key); + $key = str_replace("\0", '', $key); + $this->key = "tmp/queue/$key/"; + } + } - function push($str) { global $config; - if ($config['queue']['enabled'] == 'fs') { - $this->lock->get_ex(); - file_put_contents($this->key.microtime(true), $str); - $this->lock->free(); - } - return $this; - } + function push($str) { global $config; + if ($config['queue']['enabled'] == 'fs') { + $this->lock->get_ex(); + file_put_contents($this->key.microtime(true), $str); + $this->lock->free(); + } + return $this; + } - function pop($n = 1) { global $config; - if ($config['queue']['enabled'] == 'fs') { - $this->lock->get_ex(); - $dir = opendir($this->key); - $paths = array(); - while ($n > 0) { - $path = readdir($dir); - if ($path === FALSE) break; - elseif ($path == '.' || $path == '..') continue; - else { $paths[] = $path; $n--; } - } - $out = array(); - foreach ($paths as $v) { - $out []= file_get_contents($this->key.$v); - unlink($this->key.$v); - } - $this->lock->free(); - return $out; - } - } + function pop($n = 1) { global $config; + if ($config['queue']['enabled'] == 'fs') { + $this->lock->get_ex(); + $dir = opendir($this->key); + $paths = array(); + while ($n > 0) { + $path = readdir($dir); + if ($path === FALSE) break; + elseif ($path == '.' || $path == '..') continue; + else { $paths[] = $path; $n--; } + } + $out = array(); + foreach ($paths as $v) { + $out []= file_get_contents($this->key.$v); + unlink($this->key.$v); + } + $this->lock->free(); + return $out; + } + } } // Don't use the constructor. Use the get_queue function. $queues = array(); function get_queue($name) { global $queues; - return $queues[$name] = isset ($queues[$name]) ? $queues[$name] : new Queue($name); + return $queues[$name] = isset ($queues[$name]) ? $queues[$name] : new Queue($name); } From e61ed35aa06c633db93903d64244a3f82221586f Mon Sep 17 00:00:00 2001 From: Zankaria Date: Fri, 16 Feb 2024 15:18:17 +0100 Subject: [PATCH 007/336] Refactor queue.php --- inc/functions.php | 12 ++++- inc/queue.php | 121 ++++++++++++++++++++++++++++++++-------------- 2 files changed, 95 insertions(+), 38 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index 50e0ca38..5890da8a 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -2918,8 +2918,16 @@ function generation_strategy($fun, $array=array()) { global $config; return 'rebuild'; case 'defer': // Ok, it gets interesting here :) - get_queue('generate')->push(serialize(array('build', $fun, $array, $action))); - return 'ignore'; + $queue = Queues::get_queue($config, 'generate'); + if ($queue === false) { + if ($config['syslog']) { + _syslog(LOG_ERR, "Could not initialize generate queue, falling back to immediate rebuild strategy"); + } + return 'rebuild'; + } else { + $queue->push(serialize(array('build', $fun, $array, $action))); + return 'ignore'; + } case 'build_on_load': return 'delete'; } diff --git a/inc/queue.php b/inc/queue.php index 2801170e..0ecd1e3c 100644 --- a/inc/queue.php +++ b/inc/queue.php @@ -1,49 +1,98 @@ lock = Locks::get_lock($config, $key); - $key = str_replace('/', '::', $key); - $key = str_replace("\0", '', $key); - $this->key = "tmp/queue/$key/"; - } +class Queues { + private static $queues = array(); + + + /** + * This queue implementation isn't actually ordered, so it works more as a "bag". + */ + private static function filesystem(string $key, Lock $lock): Queue { + $key = str_replace('/', '::', $key); + $key = str_replace("\0", '', $key); + $key = "tmp/queue/$key/"; + + return new class($key, $lock) implements Queue { + private Lock $lock; + private string $key; + + + function __construct(string $key, Lock $lock) { + $this->lock = $lock; + $this->key = $key; + } + + public function push(string $str): Queue { + $this->lock->get_ex(); + file_put_contents($this->key . microtime(true), $str); + $this->lock->free(); + return $this; + } + + public function pop(int $n = 1): array { + $this->lock->get_ex(); + $dir = opendir($this->key); + $paths = array(); + + while ($n > 0) { + $path = readdir($dir); + if ($path === false) { + break; + } elseif ($path == '.' || $path == '..') { + continue; + } else { + $paths[] = $path; + $n--; + } + } + + $out = array(); + foreach ($paths as $v) { + $out[] = file_get_contents($this->key . $v); + unlink($this->key . $v); + } + + $this->lock->free(); + return $out; + } + }; } - function push($str) { global $config; - if ($config['queue']['enabled'] == 'fs') { - $this->lock->get_ex(); - file_put_contents($this->key.microtime(true), $str); - $this->lock->free(); - } - return $this; + /** + * No-op. Can be used for mocking. + */ + public static function none(): Queue { + return new class() implements Queue { + public function push(string $str): Queue { + return $this; + } + + public function pop(int $n = 1): array { + return array(); + } + }; } - function pop($n = 1) { global $config; - if ($config['queue']['enabled'] == 'fs') { - $this->lock->get_ex(); - $dir = opendir($this->key); - $paths = array(); - while ($n > 0) { - $path = readdir($dir); - if ($path === FALSE) break; - elseif ($path == '.' || $path == '..') continue; - else { $paths[] = $path; $n--; } + public static function get_queue(array $config, string $name): Queue|false { + if (!isset(self::$queues[$name])) { + if ($config['queue']['enabled'] == 'fs') { + $lock = Locks::get_lock($config, $name); + if ($lock === false) { + return false; + } + self::$queues[$name] = self::filesystem($name, $lock); + } else { + self::$queues[$name] = self::none(); } - $out = array(); - foreach ($paths as $v) { - $out []= file_get_contents($this->key.$v); - unlink($this->key.$v); - } - $this->lock->free(); - return $out; } + return self::$queues[$name]; } } -// Don't use the constructor. Use the get_queue function. -$queues = array(); +interface Queue { + // Push a string in the queue. + public function push(string $str): Queue; -function get_queue($name) { global $queues; - return $queues[$name] = isset ($queues[$name]) ? $queues[$name] : new Queue($name); + // Get a string from the queue. + public function pop(int $n = 1): array; } From 09dc44ec406cdfc12d4d9a0be7f37caa1e5a7ba8 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Mon, 11 Mar 2024 10:31:19 +0100 Subject: [PATCH 008/336] functions.php: trim --- inc/functions.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index 5890da8a..ad69e55a 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -428,10 +428,10 @@ function rebuildThemes($action, $boardname = false) { $board = $_board; // Reload the locale - if ($config['locale'] != $current_locale) { - $current_locale = $config['locale']; - init_locale($config['locale']); - } + if ($config['locale'] != $current_locale) { + $current_locale = $config['locale']; + init_locale($config['locale']); + } if (PHP_SAPI === 'cli') { echo "Rebuilding theme ".$theme['theme']."... "; @@ -450,8 +450,8 @@ function rebuildThemes($action, $boardname = false) { // Reload the locale if ($config['locale'] != $current_locale) { - $current_locale = $config['locale']; - init_locale($config['locale']); + $current_locale = $config['locale']; + init_locale($config['locale']); } } From f47332cdffb00cfe33cb5371de6e0a3d300e25f7 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Fri, 16 Feb 2024 15:48:18 +0100 Subject: [PATCH 009/336] Allow queue push to fail gracefully --- inc/functions.php | 11 ++++++++--- inc/queue.php | 12 ++++++------ 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index ad69e55a..71231203 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -2924,10 +2924,15 @@ function generation_strategy($fun, $array=array()) { global $config; _syslog(LOG_ERR, "Could not initialize generate queue, falling back to immediate rebuild strategy"); } return 'rebuild'; - } else { - $queue->push(serialize(array('build', $fun, $array, $action))); - return 'ignore'; } + $ret = $queue->push(serialize(array('build', $fun, $array, $action))); + if ($ret === false) { + if ($config['syslog']) { + _syslog(LOG_ERR, "Could not push item in the queue, falling back to immediate rebuild strategy"); + } + return 'rebuild'; + } + return 'ignore'; case 'build_on_load': return 'delete'; } diff --git a/inc/queue.php b/inc/queue.php index 0ecd1e3c..a5905c84 100644 --- a/inc/queue.php +++ b/inc/queue.php @@ -22,11 +22,11 @@ class Queues { $this->key = $key; } - public function push(string $str): Queue { + public function push(string $str): bool { $this->lock->get_ex(); - file_put_contents($this->key . microtime(true), $str); + $ret = file_put_contents($this->key . microtime(true), $str); $this->lock->free(); - return $this; + return $ret !== false; } public function pop(int $n = 1): array { @@ -63,8 +63,8 @@ class Queues { */ public static function none(): Queue { return new class() implements Queue { - public function push(string $str): Queue { - return $this; + public function push(string $str): bool { + return true; } public function pop(int $n = 1): array { @@ -91,7 +91,7 @@ class Queues { interface Queue { // Push a string in the queue. - public function push(string $str): Queue; + public function push(string $str): bool; // Get a string from the queue. public function pop(int $n = 1): array; From 542c9f3342ed712c2758765ebb9677afa6bc8edf Mon Sep 17 00:00:00 2001 From: Zankaria Date: Mon, 12 Feb 2024 12:45:47 +0100 Subject: [PATCH 010/336] Remove check-updates functionality. It relies on the defunct vichan.net website, no point keeping it around. --- inc/config.php | 5 ----- inc/mod/pages.php | 54 ----------------------------------------------- 2 files changed, 59 deletions(-) diff --git a/inc/config.php b/inc/config.php index 0cece593..c42328fa 100644 --- a/inc/config.php +++ b/inc/config.php @@ -36,11 +36,6 @@ // $config['global_message'] = 'This is an important announcement!'; $config['blotter'] = &$config['global_message']; - // Automatically check if a newer version of vichan is available when an administrator logs in. - $config['check_updates'] = false; - // How often to check for updates - $config['check_updates_time'] = 43200; // 12 hours - // Shows some extra information at the bottom of pages. Good for development/debugging. $config['debug'] = false; // For development purposes. Displays (and "dies" on) all errors and warnings. Turn on with the above. diff --git a/inc/mod/pages.php b/inc/mod/pages.php index 1e803424..16072b9d 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -107,60 +107,6 @@ function mod_dashboard() { $query = query('SELECT COUNT(*) FROM ``ban_appeals``') or error(db_error($query)); $args['appeals'] = $query->fetchColumn(); - if ($mod['type'] >= ADMIN && $config['check_updates']) { - if (!$config['version']) - error(_('Could not find current version! (Check .installed)')); - - if (isset($_COOKIE['update'])) { - $latest = unserialize($_COOKIE['update']); - } else { - $ctx = stream_context_create(array('http' => array('timeout' => 5))); - if ($code = @file_get_contents('http://engine.vichan.info/version.txt', 0, $ctx)) { - $ver = strtok($code, "\n"); - - if (preg_match('@^// v(\d+)\.(\d+)\.(\d+)\s*?$@', $ver, $matches)) { - $latest = array( - 'massive' => $matches[1], - 'major' => $matches[2], - 'minor' => $matches[3] - ); - if (preg_match('/(\d+)\.(\d)\.(\d+)(-dev.+)?$/', $config['version'], $matches)) { - $current = array( - 'massive' => (int) $matches[1], - 'major' => (int) $matches[2], - 'minor' => (int) $matches[3] - ); - if (isset($m[4])) { - // Development versions are always ahead in the versioning numbers - $current['minor'] --; - } - // Check if it's newer - if (!( $latest['massive'] > $current['massive'] || - $latest['major'] > $current['major'] || - ($latest['massive'] == $current['massive'] && - $latest['major'] == $current['major'] && - $latest['minor'] > $current['minor'] - ))) - $latest = false; - } else { - $latest = false; - } - } else { - // Couldn't get latest version - $latest = false; - } - } else { - // Couldn't get latest version - $latest = false; - } - - setcookie('update', serialize($latest), time() + $config['check_updates_time'], $config['cookies']['jail'] ? $config['cookies']['path'] : '/', null, !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off', true); - } - - if ($latest) - $args['newer_release'] = $latest; - } - $args['logout_token'] = make_secure_link_token('logout'); mod_page(_('Dashboard'), $config['file_mod_dashboard'], $args); From 9a014e855741405ad2f33047f6a27d83736dd7da Mon Sep 17 00:00:00 2001 From: Zankaria Date: Wed, 3 Apr 2024 16:11:54 +0200 Subject: [PATCH 011/336] template.php: fix deprecated string interning syntax --- inc/template.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inc/template.php b/inc/template.php index 0362111c..fb75aaf6 100644 --- a/inc/template.php +++ b/inc/template.php @@ -58,7 +58,7 @@ function Element($templateFile, array $options) { } // Read the template file - if (@file_get_contents("{$config['dir']['template']}/${templateFile}")) { + if (@file_get_contents("{$config['dir']['template']}/{$templateFile}")) { $body = $twig->render($templateFile, $options); if ($config['minify_html'] && preg_match('/\.html$/', $templateFile)) { @@ -67,7 +67,7 @@ function Element($templateFile, array $options) { return $body; } else { - throw new Exception("Template file '${templateFile}' does not exist or is empty in '{$config['dir']['template']}'!"); + throw new Exception("Template file '{$templateFile}' does not exist or is empty in '{$config['dir']['template']}'!"); } } From 7479360aad2764eaf34d2dd4399535b70bebb718 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Wed, 3 Apr 2024 17:18:52 +0200 Subject: [PATCH 012/336] catalog.html: format template --- templates/themes/catalog/catalog.html | 68 +++++++++++++-------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/templates/themes/catalog/catalog.html b/templates/themes/catalog/catalog.html index 34202907..de7d1461 100644 --- a/templates/themes/catalog/catalog.html +++ b/templates/themes/catalog/catalog.html @@ -19,24 +19,24 @@ - {% trans 'Sort by' %}: - - - {% trans 'Image size' %}: - -
-
- {% for post in recent_posts %} -
{% trans 'Sort by' %}: + + + {% trans 'Image size' %}: + +
+
+ {% for post in recent_posts %} +
-
- +
+ {% if post.youtube %} - - -
- R: {{ post.reply_count }} / I: {{ post.image_count }}{% if post.sticky %} (sticky){% endif %} - {% if post.subject %} + id="img-{{ post.id }}" data-subject="{% if post.subject %}{{ post.subject|e }}{% endif %}" data-name="{{ post.name|e }}" data-muhdifference="{{ post.muhdifference }}" class="{{post.board}} thread-image" title="{{post.bump|date('%b %d %H:%M')}}"> + +
+ R: {{ post.reply_count }} / I: {{ post.image_count }}{% if post.sticky %} (sticky){% endif %} + {% if post.subject %}

{{ post.subject|e }} @@ -66,13 +66,13 @@ {% endif %} {{ post.body }} -

-
-
- {% endfor %} -
-
- +
+
+
+ {% endfor %} +
+
+
{% include 'footer.html' %} - + From 735180cf542a755c9c6e39297aba36751b23f289 Mon Sep 17 00:00:00 2001 From: discomrade Date: Fri, 12 Nov 2021 04:50:47 -0100 Subject: [PATCH 080/336] Improve social media cards --- templates/index.html | 12 ++++++++++++ templates/themes/categories/frames.html | 9 ++++++++- templates/thread.html | 3 +++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/templates/index.html b/templates/index.html index e0fad639..685dc0a9 100644 --- a/templates/index.html +++ b/templates/index.html @@ -14,6 +14,18 @@ {% include 'header.html' %} + + {% set meta_subject %}{% if config.thread_subject_in_title and thread.subject %}{{ thread.subject|e }}{% else %}{{ thread.body_nomarkup|remove_modifiers[:256]|e }}{% endif %}{% endset %} + + + + + + + + + + {{ board.url }} - {{ board.title|e }} diff --git a/templates/themes/categories/frames.html b/templates/themes/categories/frames.html index 48066bcd..291ca59f 100644 --- a/templates/themes/categories/frames.html +++ b/templates/themes/categories/frames.html @@ -1,7 +1,14 @@ - + + + + + + + + {% endif %} - {% if config.hcaptcha %} + {% if config.captcha.provider.hcaptcha %} {% endif %} diff --git a/templates/main.js b/templates/main.js index e0819622..f956f5cf 100644 --- a/templates/main.js +++ b/templates/main.js @@ -223,15 +223,15 @@ function getCookie(cookie_name) { } {% endraw %} -{% if config.dynamic_captcha %} +{% if config.captcha.dynamic %} function is_dynamic_captcha_enabled() { let cookie = get_cookie('require-captcha'); return cookie === '1'; } function get_captcha_pub_key() { -{% if config.recaptcha %} - return "{{ config.recaptcha_public }}"; +{% if config.captcha.provider === 'recaptcha' %} + return "{{ config.captcha.recaptcha.sitekey }}"; {% else %} return null; {% endif %} diff --git a/templates/post_form.html b/templates/post_form.html index 8220107b..19c69cfb 100644 --- a/templates/post_form.html +++ b/templates/post_form.html @@ -72,8 +72,8 @@ {% endif %} - {% if config.recaptcha %} - {% if config.dynamic_captcha %} + {% if config.captcha.provider == 'recaptcha' %} + {% if config.captcha.dynamic %} {% else %} @@ -83,19 +83,19 @@ {{ antibot.html() }} -
+
{{ antibot.html() }} {% endif %} - {% if config.hcaptcha %} + {% if config.captcha.provider == 'hcaptcha' %} {% trans %}Verification{% endtrans %} {{ antibot.html() }} -
+
{{ antibot.html() }} @@ -106,11 +106,11 @@ {% trans %}Verification{% endtrans %} - + @@ -122,11 +122,11 @@ {% trans %}Verification{% endtrans %} - + From cb5fb68c5e2c9d28452de72b0817b56d6e7782a2 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Thu, 15 Aug 2024 16:12:32 +0200 Subject: [PATCH 219/336] header.html: format --- templates/header.html | 110 +++++++++++++++++++++--------------------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/templates/header.html b/templates/header.html index f72b752a..14a42952 100644 --- a/templates/header.html +++ b/templates/header.html @@ -1,55 +1,55 @@ - - {% if config.url_favicon %}{% endif %} - - - {% if config.meta_keywords %}{% endif %} - {% if config.default_stylesheet.1 != '' %}{% endif %} - {% if config.font_awesome %}{% endif %} - {% if config.country_flags_condensed %}{% endif %} - - {% if not nojavascript %} - - {% if not config.additional_javascript_compile %} - {% for javascript in config.additional_javascript %}{% endfor %} - {% endif %} - {% if mod %} - - {% endif %} - {% endif %} - {% if config.captcha.provider == 'recaptcha' %} - {% endif %} - {% if config.captcha.provider.hcaptcha %} - - {% endif %} + +{% if config.url_favicon %}{% endif %} + + +{% if config.meta_keywords %}{% endif %} +{% if config.default_stylesheet.1 != '' %}{% endif %} +{% if config.font_awesome %}{% endif %} +{% if config.country_flags_condensed %}{% endif %} + +{% if not nojavascript %} + + {% if not config.additional_javascript_compile %} + {% for javascript in config.additional_javascript %}{% endfor %} + {% endif %} + {% if mod %} + + {% endif %} +{% endif %} +{% if config.captcha.provider == 'recaptcha' %} +{% endif %} +{% if config.captcha.provider.hcaptcha %} + +{% endif %} From f7073d5d7ef6c923437ad36df0cb1b90b092d91f Mon Sep 17 00:00:00 2001 From: Zankaria Date: Thu, 15 Aug 2024 16:22:58 +0200 Subject: [PATCH 220/336] post.php: do not verify the poster IP if the captcha is dynamic --- post.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/post.php b/post.php index f937f06c..b461c47e 100644 --- a/post.php +++ b/post.php @@ -661,8 +661,13 @@ if (isset($_POST['delete'])) { error($config['error']['bot']); } $response = $_POST[$field]; + /* + * Do not query with the IP if the mode is dynamic. This config is meant for proxies and internal + * loopback addresses. + */ + $ip = $dynamic ? null : $_SERVER['REMOTE_ADDR']; - $success = $query->verify($response, $_SERVER['REMOTE_ADDR']); + $success = $query->verify($response, $ip); if (!$success) { error($config['error']['captcha']); } From 165ea5308ac7400e785e8322999d316aea43ff5a Mon Sep 17 00:00:00 2001 From: Zankaria Date: Thu, 15 Aug 2024 16:33:56 +0200 Subject: [PATCH 221/336] config.php: ops, wrong name for the native captcha --- inc/config.php | 6 +++--- inc/context.php | 6 +++--- post.php | 6 +++--- templates/post_form.html | 8 ++++---- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/inc/config.php b/inc/config.php index 89ea333a..b4fc27d8 100644 --- a/inc/config.php +++ b/inc/config.php @@ -352,7 +352,7 @@ $config['simple_spam'] = false; $config['captcha'] = [ - // Can be false, 'recaptcha', 'hcaptcha' or 'secureimage'. + // Can be false, 'recaptcha', 'hcaptcha' or 'native'. 'provider' => false, /* * If not false, the captcha is dynamically injected on the client if the web server set the `captcha-required` @@ -372,8 +372,8 @@ 'sitekey' => '10000000-ffff-ffff-ffff-000000000001', 'secret' => '0x0000000000000000000000000000000000000000', ], - // Enable the secureimage captcha you need to change a couple of settings. Read more at: /inc/captcha/readme.md - 'secureimage' => [ + // To enable the native captcha you need to change a couple of settings. Read more at: /inc/captcha/readme.md + 'native' => [ // Custom captcha get provider path (if not working get the absolute path aka your url). 'provider_get' => '../inc/captcha/entrypoint.php', // Custom captcha check provider path diff --git a/inc/context.php b/inc/context.php index 23165377..3f8a75d6 100644 --- a/inc/context.php +++ b/inc/context.php @@ -76,14 +76,14 @@ function build_context(array $config): Context { }, NativeCaptchaQuery::class => function($c) { $config = $c->get('config'); - if ($config['captcha']['provider'] !== 'secureimage') { + if ($config['captcha']['provider'] !== 'native') { throw new RuntimeException('No native captcha service available'); } return new NativeCaptchaQuery( $c->get(HttpDriver::class), $config['domain'], - $config['captcha']['secureimage']['provider_check'], - $config['captcha']['secureimage']['extra'] + $config['captcha']['native']['provider_check'], + $config['captcha']['native']['extra'] ); } ]); diff --git a/post.php b/post.php index b461c47e..eafb1130 100644 --- a/post.php +++ b/post.php @@ -630,12 +630,12 @@ if (isset($_POST['delete'])) { // Check for CAPTCHA right after opening the board so the "return" link is in there. try { $provider = $config['captcha']['provider']; - $new_thread_capt = $config['captcha']['secureimage']['new_thread_capt']; + $new_thread_capt = $config['captcha']['native']['new_thread_capt']; $dynamic = $config['captcha']['dynamic']; // With our custom captcha provider - if (($provider === 'secureimage' && !$new_thread_capt) - || ($provider === 'secureimage' && $new_thread_capt && $post['op'])) { + if (($provider === 'native' && !$new_thread_capt) + || ($provider === 'native' && $new_thread_capt && $post['op'])) { $query = $context->get(NativeCaptchaQuery::class); $success = $query->verify($_POST['captcha_text'], $_POST['captcha_cookie']); diff --git a/templates/post_form.html b/templates/post_form.html index 19c69cfb..038395fa 100644 --- a/templates/post_form.html +++ b/templates/post_form.html @@ -106,11 +106,11 @@ {% trans %}Verification{% endtrans %} - + @@ -122,11 +122,11 @@ {% trans %}Verification{% endtrans %} - + From e640217a8fc9d7faed57aa5827de6a97c8178eeb Mon Sep 17 00:00:00 2001 From: Zankaria Date: Thu, 15 Aug 2024 16:39:12 +0200 Subject: [PATCH 222/336] post.php: fix DI method call --- post.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/post.php b/post.php index 644ff111..a80c9d8e 100644 --- a/post.php +++ b/post.php @@ -674,10 +674,10 @@ if (isset($_POST['delete'])) { } } } catch (RuntimeException $e) { - $context->getLog()->log(Log::ERROR, "Captcha IO exception: {$e->getMessage()}"); + $context->get(Log::class)->log(Log::ERROR, "Captcha IO exception: {$e->getMessage()}"); error($config['error']['remote_io_error']); } catch (JsonException $e) { - $context->getLog()->log(Log::ERROR, "Bad JSON reply to captcha: {$e->getMessage()}"); + $context->get(Log::class)->log(Log::ERROR, "Bad JSON reply to captcha: {$e->getMessage()}"); error($config['error']['remote_io_error']); } From 19082aec56e4ae677575d362ebb273a68b05f5b8 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Thu, 15 Aug 2024 16:52:29 +0200 Subject: [PATCH 223/336] mod.php: use modern array syntax --- mod.php | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/mod.php b/mod.php index 20cff8e5..f1e1245c 100644 --- a/mod.php +++ b/mod.php @@ -1,13 +1,13 @@ ':?/', // redirect to dashboard '/' => 'dashboard', // dashboard '/confirm/(.+)' => 'confirm', // confirm action (if javascript didn't work) @@ -109,14 +109,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; @@ -126,10 +126,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)) + 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[(!empty($key) and $key[0] == '!') ? $key : '!^' . $key . '(?:&[^&=]+=[^&]*)*$!u'] = $callback; } @@ -172,11 +173,11 @@ 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'; } @@ -204,4 +205,3 @@ foreach ($pages as $uri => $handler) { } error($config['error']['404']); - From 524ae9462411e39140bc5463631fc03eb964cc3a Mon Sep 17 00:00:00 2001 From: Zankaria Date: Thu, 15 Aug 2024 17:05:43 +0200 Subject: [PATCH 224/336] mod.php: pass the context to the mod pages --- inc/mod/pages.php | 156 +++++++++++++++++++++++----------------------- mod.php | 4 +- 2 files changed, 80 insertions(+), 80 deletions(-) diff --git a/inc/mod/pages.php b/inc/mod/pages.php index 46e8fc7e..bd5c7c1b 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -1,8 +1,8 @@ $request, 'token' => make_secure_link_token($request))); } -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 = array(); @@ -173,7 +173,7 @@ function mod_dashboard() { mod_page(_('Dashboard'), $config['file_mod_dashboard'], $args); } -function mod_search_redirect() { +function mod_search_redirect(Context $ctx) { global $config; if (!hasPermission($config['mod']['search'])) @@ -196,7 +196,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'])) @@ -351,7 +351,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)) @@ -453,7 +453,7 @@ function mod_edit_board($boardName) { } } -function mod_new_board() { +function mod_new_board(Context $ctx) { global $config, $board; if (!hasPermission($config['mod']['newboard'])) @@ -522,7 +522,7 @@ function mod_new_board() { mod_page(_('New board'), $config['file_mod_board'], 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) @@ -577,7 +577,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'])) @@ -595,7 +595,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) @@ -642,7 +642,7 @@ function mod_news($page_no = 1) { mod_page(_('News'), $config['file_mod_news'], 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'])) @@ -657,7 +657,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) @@ -682,7 +682,7 @@ function mod_log($page_no = 1) { mod_page(_('Moderation log'), $config['file_mod_log'], 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) @@ -709,7 +709,7 @@ function mod_user_log($username, $page_no = 1) { mod_page(_('Moderation log'), $config['file_mod_log'], array('logs' => $logs, 'count' => $count, 'username' => $username)); } -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) @@ -745,8 +745,8 @@ function mod_board_log($board, $page_no = 1, $hide_names = false, $public = fals mod_page(_('Board log'), $config['file_mod_log'], 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 = array(); $settings['boards'] = $boardName; @@ -757,7 +757,7 @@ function mod_view_catalog($boardName) { echo $catalog->build($settings, $boardName, true); } -function mod_view_board($boardName, $page_no = 1) { +function mod_view_board(Context $ctx, $boardName, $page_no = 1) { global $config, $mod; if (!openBoard($boardName)) @@ -776,7 +776,7 @@ function mod_view_board($boardName, $page_no = 1) { echo Element($config['file_board_index'], $page); } -function mod_view_thread($boardName, $thread) { +function mod_view_thread(Context $ctx, $boardName, $thread) { global $config, $mod; if (!openBoard($boardName)) @@ -786,7 +786,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)) @@ -796,7 +796,7 @@ function mod_view_thread50($boardName, $thread) { echo $page; } -function mod_ip_remove_note($cloaked_ip, $id) { +function mod_ip_remove_note(Context $ctx, $cloaked_ip, $id) { $ip = uncloak_ip($cloaked_ip); global $config, $mod; @@ -818,7 +818,7 @@ function mod_ip_remove_note($cloaked_ip, $id) { -function mod_ip($cip) { +function mod_ip(Context $ctx, $cip) { $ip = uncloak_ip($cip); global $config, $mod; @@ -921,8 +921,8 @@ function mod_ip($cip) { mod_page(sprintf('%s: %s', _('IP'), htmlspecialchars($cip)), $config['file_mod_view_ip'], $args, $args['hostname']); } -function mod_edit_ban($ban_id) { - global $mod, $config; +function mod_edit_ban(Context $ctx, $ban_id) { + global $config; if (!hasPermission($config['mod']['edit_ban'])) error($config['error']['noaccess']); @@ -971,8 +971,7 @@ function mod_edit_ban($ban_id) { } - -function mod_ban() { +function mod_ban(Context $ctx) { global $config; if (!hasPermission($config['mod']['ban'])) @@ -991,9 +990,8 @@ function mod_ban() { header('Location: ?/', true, $config['redirect_http']); } -function mod_bans() { - global $config; - global $mod; +function mod_bans(Context $ctx) { + global $config, $mod; if (!hasPermission($config['mod']['view_banlist'])) error($config['error']['noaccess']); @@ -1026,11 +1024,11 @@ function mod_bans() { )); } -function mod_bans_json() { - global $config, $mod; +function mod_bans_json(Context $ctx) { + global $config, $mod; - if (!hasPermission($config['mod']['ban'])) - error($config['error']['noaccess']); + if (!hasPermission($config['mod']['ban'])) + error($config['error']['noaccess']); // Compress the json for faster loads if (substr_count($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip')) ob_start("ob_gzhandler"); @@ -1038,7 +1036,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'])) @@ -1114,7 +1112,7 @@ function mod_ban_appeals() { )); } -function mod_lock($board, $unlock, $post) { +function mod_lock(Context $ctx, $board, $unlock, $post) { global $config; if (!openBoard($board)) @@ -1148,7 +1146,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)) @@ -1170,7 +1168,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)) @@ -1192,7 +1190,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)) @@ -1214,8 +1212,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']); @@ -1316,8 +1314,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']); @@ -1526,7 +1524,7 @@ function mod_move($originBoard, $postID) { mod_page(_('Move thread'), $config['file_mod_move'], 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)) @@ -1596,8 +1594,8 @@ function mod_ban_post($board, $delete, $post, $token = false) { mod_page(_('New ban'), $config['file_mod_ban_form'], $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']); @@ -1673,8 +1671,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']); @@ -1694,8 +1692,8 @@ function mod_delete($board, $post) { header('Location: ?/' . sprintf($config['board_path'], $board) . $config['file_index'], true, $config['redirect_http']); } -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']); @@ -1717,8 +1715,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']); @@ -1762,8 +1760,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; @@ -1838,7 +1836,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'])) @@ -1963,7 +1961,7 @@ function mod_user($uid) { )); } -function mod_user_new() { +function mod_user_new(Context $ctx) { global $pdo, $config; if (!hasPermission($config['mod']['createusers'])) @@ -2016,7 +2014,7 @@ function mod_user_new() { } -function mod_users() { +function mod_users(Context $ctx) { global $config; if (!hasPermission($config['mod']['manageusers'])) @@ -2037,7 +2035,7 @@ function mod_users() { mod_page(sprintf('%s (%d)', _('Manage users'), count($users)), $config['file_mod_users'], array('users' => $users)); } -function mod_user_promote($uid, $action) { +function mod_user_promote(Context $ctx, $uid, $action) { global $config; if (!hasPermission($config['mod']['promoteusers'])) @@ -2080,7 +2078,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'])) @@ -2135,7 +2133,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'); @@ -2159,7 +2157,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'])) @@ -2207,7 +2205,7 @@ function mod_new_pm($username) { )); } -function mod_rebuild() { +function mod_rebuild(Context $ctx) { global $config, $twig; if (!hasPermission($config['mod']['rebuild'])) @@ -2279,7 +2277,7 @@ function mod_rebuild() { )); } -function mod_reports() { +function mod_reports(Context $ctx) { global $config, $mod; if (!hasPermission($config['mod']['reports'])) @@ -2362,7 +2360,7 @@ function mod_reports() { mod_page(sprintf('%s (%d)', _('Report queue'), $count), $config['file_mod_reports'], array('reports' => $body, 'count' => $count)); } -function mod_report_dismiss($id, $action) { +function mod_report_dismiss(Context $ctx, $id, $action) { global $config; $query = prepare("SELECT `post`, `board`, `ip` FROM ``reports`` WHERE `id` = :id"); @@ -2408,7 +2406,7 @@ function mod_report_dismiss($id, $action) { header('Location: ?/reports', true, $config['redirect_http']); } -function mod_recent_posts($lim) { +function mod_recent_posts(Context $ctx, $lim) { global $config, $mod, $pdo; if (!hasPermission($config['mod']['recent'])) @@ -2464,7 +2462,7 @@ function mod_recent_posts($lim) { } -function mod_config($board_config = false) { +function mod_config(Context $ctx, $board_config = false) { global $config, $mod, $board; if ($board_config && !openBoard($board_config)) @@ -2604,7 +2602,7 @@ function mod_config($board_config = false) { )); } -function mod_themes_list() { +function mod_themes_list(Context $ctx) { global $config; if (!hasPermission($config['mod']['themes'])) @@ -2638,7 +2636,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'])) @@ -2720,7 +2718,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'])) @@ -2737,7 +2735,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'])) @@ -2778,15 +2776,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'); @@ -2857,7 +2855,7 @@ function mod_edit_page($id) { mod_page(sprintf(_('Editing static page: %s'), $page['name']), $config['file_mod_edit_page'], 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)) @@ -2911,7 +2909,7 @@ function mod_pages($board = false) { mod_page(_('Pages'), $config['file_mod_pages'], 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 = array(); @@ -2948,7 +2946,7 @@ function mod_debug_antispam() { mod_page(_('Debug: Anti-spam'), $config['file_mod_debug_antispam'], $args); } -function mod_debug_recent_posts() { +function mod_debug_recent_posts(Context $ctx) { global $pdo, $config; $limit = 500; @@ -2982,7 +2980,7 @@ function mod_debug_recent_posts() { mod_page(_('Debug: Recent posts'), $config['file_mod_debug_recent_posts'], 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'])) diff --git a/mod.php b/mod.php index f1e1245c..474210cd 100644 --- a/mod.php +++ b/mod.php @@ -136,9 +136,11 @@ foreach ($pages as $key => $callback) { } $pages = $new_pages; +$ctx = Vichan\build_context($config); + 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']; From b64bac5eb8ea65f8043855566068391e88557c4e Mon Sep 17 00:00:00 2001 From: Zankaria Date: Thu, 15 Aug 2024 17:12:14 +0200 Subject: [PATCH 225/336] pages.php: use the context to access the configuration array --- inc/mod/pages.php | 125 +++++++++++++++++++++++++++------------------- 1 file changed, 73 insertions(+), 52 deletions(-) diff --git a/inc/mod/pages.php b/inc/mod/pages.php index bd5c7c1b..0793ccb4 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -31,7 +31,7 @@ function mod_page($title, $template, $args, $subtitle = false) { } function mod_login(Context $ctx, $redirect = false) { - global $config; + $config = $ctx->get('config'); $args = []; @@ -68,19 +68,20 @@ function mod_login(Context $ctx, $redirect = false) { } function mod_confirm(Context $ctx, $request) { - global $config; + $config = $ctx->get('config'); mod_page(_('Confirm action'), $config['file_mod_confim'], array('request' => $request, 'token' => make_secure_link_token($request))); } function mod_logout(Context $ctx) { - global $config; + $config = $ctx->get('config'); destroyCookies(); header('Location: ?/', true, $config['redirect_http']); } function mod_dashboard(Context $ctx) { - global $config, $mod; + global $mod; + $config = $ctx->get('config'); $args = array(); @@ -174,7 +175,7 @@ function mod_dashboard(Context $ctx) { } function mod_search_redirect(Context $ctx) { - global $config; + $config = $ctx->get('config'); if (!hasPermission($config['mod']['search'])) error($config['error']['noaccess']); @@ -454,7 +455,8 @@ function mod_edit_board(Context $ctx, $boardName) { } function mod_new_board(Context $ctx) { - global $config, $board; + global $board; + $config = $ctx->get('config'); if (!hasPermission($config['mod']['newboard'])) error($config['error']['noaccess']); @@ -523,7 +525,8 @@ function mod_new_board(Context $ctx) { } function mod_noticeboard(Context $ctx, $page_no = 1) { - global $config, $pdo, $mod; + global $pdo, $mod; + $config = $ctx->get('config'); if ($page_no < 1) error($config['error']['404']); @@ -578,7 +581,7 @@ function mod_noticeboard(Context $ctx, $page_no = 1) { } function mod_noticeboard_delete(Context $ctx, $id) { - global $config; + $config = $ctx->get('config'); if (!hasPermission($config['mod']['noticeboard_delete'])) error($config['error']['noaccess']); @@ -596,7 +599,8 @@ function mod_noticeboard_delete(Context $ctx, $id) { } function mod_news(Context $ctx, $page_no = 1) { - global $config, $pdo, $mod; + global $pdo, $mod; + $config = $ctx->get('config'); if ($page_no < 1) error($config['error']['404']); @@ -643,7 +647,7 @@ function mod_news(Context $ctx, $page_no = 1) { } function mod_news_delete(Context $ctx, $id) { - global $config; + $config = $ctx->get('config'); if (!hasPermission($config['mod']['news_delete'])) error($config['error']['noaccess']); @@ -658,7 +662,7 @@ function mod_news_delete(Context $ctx, $id) { } function mod_log(Context $ctx, $page_no = 1) { - global $config; + $config = $ctx->get('config'); if ($page_no < 1) error($config['error']['404']); @@ -683,7 +687,7 @@ function mod_log(Context $ctx, $page_no = 1) { } function mod_user_log(Context $ctx, $username, $page_no = 1) { - global $config; + $config = $ctx->get('config'); if ($page_no < 1) error($config['error']['404']); @@ -710,7 +714,7 @@ function mod_user_log(Context $ctx, $username, $page_no = 1) { } function mod_board_log(Context $ctx, $board, $page_no = 1, $hide_names = false, $public = false) { - global $config; + $config = $ctx->get('config'); if ($page_no < 1) error($config['error']['404']); @@ -746,7 +750,7 @@ function mod_board_log(Context $ctx, $board, $page_no = 1, $hide_names = false, } function mod_view_catalog(Context $ctx, $boardName) { - global $config; + $config = $ctx->get('config'); require_once($config['dir']['themes'].'/catalog/theme.php'); $settings = array(); $settings['boards'] = $boardName; @@ -758,7 +762,8 @@ function mod_view_catalog(Context $ctx, $boardName) { } function mod_view_board(Context $ctx, $boardName, $page_no = 1) { - global $config, $mod; + global $mod; + $config = $ctx->get('config'); if (!openBoard($boardName)) error($config['error']['noboard']); @@ -777,7 +782,8 @@ function mod_view_board(Context $ctx, $boardName, $page_no = 1) { } function mod_view_thread(Context $ctx, $boardName, $thread) { - global $config, $mod; + global $mod; + $config = $ctx->get('config'); if (!openBoard($boardName)) error($config['error']['noboard']); @@ -787,7 +793,8 @@ function mod_view_thread(Context $ctx, $boardName, $thread) { } function mod_view_thread50(Context $ctx, $boardName, $thread) { - global $config, $mod; + global $mod; + $config = $ctx->get('config'); if (!openBoard($boardName)) error($config['error']['noboard']); @@ -798,10 +805,10 @@ function mod_view_thread50(Context $ctx, $boardName, $thread) { function mod_ip_remove_note(Context $ctx, $cloaked_ip, $id) { $ip = uncloak_ip($cloaked_ip); - global $config, $mod; + $config = $ctx->get('config'); if (!hasPermission($config['mod']['remove_notes'])) - error($config['error']['noaccess']); + error($config['error']['noaccess']); if (filter_var($ip, FILTER_VALIDATE_IP) === false) error("Invalid IP address."); @@ -820,7 +827,8 @@ function mod_ip_remove_note(Context $ctx, $cloaked_ip, $id) { function mod_ip(Context $ctx, $cip) { $ip = uncloak_ip($cip); - global $config, $mod; + global $mod; + $config = $ctx->get('config'); if (filter_var($ip, FILTER_VALIDATE_IP) === false) error("Invalid IP address."); @@ -922,7 +930,7 @@ function mod_ip(Context $ctx, $cip) { } function mod_edit_ban(Context $ctx, $ban_id) { - global $config; + $config = $ctx->get('config'); if (!hasPermission($config['mod']['edit_ban'])) error($config['error']['noaccess']); @@ -972,7 +980,7 @@ function mod_edit_ban(Context $ctx, $ban_id) { } function mod_ban(Context $ctx) { - global $config; + $config = $ctx->get('config'); if (!hasPermission($config['mod']['ban'])) error($config['error']['noaccess']); @@ -991,7 +999,8 @@ function mod_ban(Context $ctx) { } function mod_bans(Context $ctx) { - global $config, $mod; + global $mod; + $config = $ctx->get('config'); if (!hasPermission($config['mod']['view_banlist'])) error($config['error']['noaccess']); @@ -1025,7 +1034,8 @@ function mod_bans(Context $ctx) { } function mod_bans_json(Context $ctx) { - global $config, $mod; + global $mod; + $config = $ctx->get('config'); if (!hasPermission($config['mod']['ban'])) error($config['error']['noaccess']); @@ -1037,7 +1047,8 @@ function mod_bans_json(Context $ctx) { } function mod_ban_appeals(Context $ctx) { - global $config, $board; + global $board; + $config = $ctx->get('config'); if (!hasPermission($config['mod']['view_ban_appeals'])) error($config['error']['noaccess']); @@ -1113,7 +1124,7 @@ function mod_ban_appeals(Context $ctx) { } function mod_lock(Context $ctx, $board, $unlock, $post) { - global $config; + $config = $ctx->get('config'); if (!openBoard($board)) error($config['error']['noboard']); @@ -1147,7 +1158,7 @@ function mod_lock(Context $ctx, $board, $unlock, $post) { } function mod_sticky(Context $ctx, $board, $unsticky, $post) { - global $config; + $config = $ctx->get('config'); if (!openBoard($board)) error($config['error']['noboard']); @@ -1169,7 +1180,7 @@ function mod_sticky(Context $ctx, $board, $unsticky, $post) { } function mod_cycle(Context $ctx, $board, $uncycle, $post) { - global $config; + $config = $ctx->get('config'); if (!openBoard($board)) error($config['error']['noboard']); @@ -1191,7 +1202,7 @@ function mod_cycle(Context $ctx, $board, $uncycle, $post) { } function mod_bumplock(Context $ctx, $board, $unbumplock, $post) { - global $config; + $config = $ctx->get('config'); if (!openBoard($board)) error($config['error']['noboard']); @@ -1525,7 +1536,7 @@ function mod_move(Context $ctx, $originBoard, $postID) { } function mod_ban_post(Context $ctx, $board, $delete, $post, $token = false) { - global $config, $mod; + $config = $ctx->get('config'); if (!openBoard($board)) error($config['error']['noboard']); @@ -1595,7 +1606,7 @@ function mod_ban_post(Context $ctx, $board, $delete, $post, $token = false) { } function mod_edit_post(Context $ctx, $board, $edit_raw_html, $postID) { - global $config; + $config = $ctx->get('config'); if (!openBoard($board)) error($config['error']['noboard']); @@ -1672,7 +1683,7 @@ function mod_edit_post(Context $ctx, $board, $edit_raw_html, $postID) { } function mod_delete(Context $ctx, $board, $post) { - global $config; + $config = $ctx->get('config'); if (!openBoard($board)) error($config['error']['noboard']); @@ -1693,7 +1704,7 @@ function mod_delete(Context $ctx, $board, $post) { } function mod_deletefile(Context $ctx, $board, $post, $file) { - global $config; + $config = $ctx->get('config'); if (!openBoard($board)) error($config['error']['noboard']); @@ -1716,7 +1727,7 @@ function mod_deletefile(Context $ctx, $board, $post, $file) { } function mod_spoiler_image(Context $ctx, $board, $post, $file) { - global $config; + $config = $ctx->get('config'); if (!openBoard($board)) error($config['error']['noboard']); @@ -1761,7 +1772,8 @@ function mod_spoiler_image(Context $ctx, $board, $post, $file) { } function mod_deletebyip(Context $ctx, $boardName, $post, $global = false) { - global $config, $board; + global $board; + $config = $ctx->get('config'); $global = (bool)$global; @@ -1837,7 +1849,8 @@ function mod_deletebyip(Context $ctx, $boardName, $post, $global = false) { } function mod_user(Context $ctx, $uid) { - global $config, $mod; + global $mod; + $config = $ctx->get('config'); if (!hasPermission($config['mod']['editusers']) && !(hasPermission($config['mod']['change_password']) && $uid == $mod['id'])) error($config['error']['noaccess']); @@ -2015,7 +2028,7 @@ function mod_user_new(Context $ctx) { function mod_users(Context $ctx) { - global $config; + $config = $ctx->get('config'); if (!hasPermission($config['mod']['manageusers'])) error($config['error']['noaccess']); @@ -2036,7 +2049,7 @@ function mod_users(Context $ctx) { } function mod_user_promote(Context $ctx, $uid, $action) { - global $config; + $config = $ctx->get('config'); if (!hasPermission($config['mod']['promoteusers'])) error($config['error']['noaccess']); @@ -2134,7 +2147,8 @@ function mod_pm(Context $ctx, $id, $reply = false) { } function mod_inbox(Context $ctx) { - global $config, $mod; + global $mod; + $config = $ctx->get('config'); $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'); $query->bindValue(':mod', $mod['id']); @@ -2158,7 +2172,8 @@ function mod_inbox(Context $ctx) { function mod_new_pm(Context $ctx, $username) { - global $config, $mod; + global $mod; + $config = $ctx->get('config'); if (!hasPermission($config['mod']['create_pm'])) error($config['error']['noaccess']); @@ -2206,7 +2221,8 @@ function mod_new_pm(Context $ctx, $username) { } function mod_rebuild(Context $ctx) { - global $config, $twig; + global $twig; + $config = $ctx->get('config'); if (!hasPermission($config['mod']['rebuild'])) error($config['error']['noaccess']); @@ -2278,7 +2294,8 @@ function mod_rebuild(Context $ctx) { } function mod_reports(Context $ctx) { - global $config, $mod; + global $mod; + $config = $ctx->get('config'); if (!hasPermission($config['mod']['reports'])) error($config['error']['noaccess']); @@ -2361,7 +2378,7 @@ function mod_reports(Context $ctx) { } function mod_report_dismiss(Context $ctx, $id, $action) { - global $config; + $config = $ctx->get('config'); $query = prepare("SELECT `post`, `board`, `ip` FROM ``reports`` WHERE `id` = :id"); $query->bindValue(':id', $id); @@ -2407,7 +2424,8 @@ function mod_report_dismiss(Context $ctx, $id, $action) { } function mod_recent_posts(Context $ctx, $lim) { - global $config, $mod, $pdo; + global $mod, $pdo; + $config = $ctx->get('config'); if (!hasPermission($config['mod']['recent'])) error($config['error']['noaccess']); @@ -2463,7 +2481,8 @@ function mod_recent_posts(Context $ctx, $lim) { } function mod_config(Context $ctx, $board_config = false) { - global $config, $mod, $board; + global $mod, $board; + $config = $ctx->get('config'); if ($board_config && !openBoard($board_config)) error($config['error']['noboard']); @@ -2603,7 +2622,7 @@ function mod_config(Context $ctx, $board_config = false) { } function mod_themes_list(Context $ctx) { - global $config; + $config = $ctx->get('config'); if (!hasPermission($config['mod']['themes'])) error($config['error']['noaccess']); @@ -2637,7 +2656,7 @@ function mod_themes_list(Context $ctx) { } function mod_theme_configure(Context $ctx, $theme_name) { - global $config; + $config = $ctx->get('config'); if (!hasPermission($config['mod']['themes'])) error($config['error']['noaccess']); @@ -2719,7 +2738,7 @@ function mod_theme_configure(Context $ctx, $theme_name) { } function mod_theme_uninstall(Context $ctx, $theme_name) { - global $config; + $config = $ctx->get('config'); if (!hasPermission($config['mod']['themes'])) error($config['error']['noaccess']); @@ -2736,7 +2755,7 @@ function mod_theme_uninstall(Context $ctx, $theme_name) { } function mod_theme_rebuild(Context $ctx, $theme_name) { - global $config; + $config = $ctx->get('config'); if (!hasPermission($config['mod']['themes'])) error($config['error']['noaccess']); @@ -2785,7 +2804,8 @@ function mod_delete_page_board(Context $ctx, $page = '', $board = false) { } function mod_edit_page(Context $ctx, $id) { - global $config, $mod, $board; + global $mod, $board; + $config = $ctx->get('config'); $query = prepare('SELECT * FROM ``pages`` WHERE `id` = :id'); $query->bindValue(':id', $id); @@ -2856,7 +2876,8 @@ function mod_edit_page(Context $ctx, $id) { } function mod_pages(Context $ctx, $board = false) { - global $config, $mod, $pdo; + global $mod, $pdo; + $config = $ctx->get('config'); if (empty($board)) $board = false; @@ -2981,7 +3002,7 @@ function mod_debug_recent_posts(Context $ctx) { } function mod_debug_sql(Context $ctx) { - global $config; + $config = $ctx->get('config'); if (!hasPermission($config['mod']['debug_sql'])) error($config['error']['noaccess']); From 81aebef2f4c89dbc7351e2434888a210df7a6623 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Thu, 15 Aug 2024 17:21:41 +0200 Subject: [PATCH 226/336] pages.php: use modern array syntax for new empty arrays --- inc/mod/pages.php | 56 +++++++++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/inc/mod/pages.php b/inc/mod/pages.php index 0793ccb4..95183246 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -83,7 +83,7 @@ function mod_dashboard(Context $ctx) { global $mod; $config = $ctx->get('config'); - $args = array(); + $args = []; $args['boards'] = listBoards(); @@ -223,7 +223,7 @@ function mod_search(Context $ctx, $type, $search_query_escaped, $page_no = 1) { $query = str_replace('`', '!`', $query); // Array of phrases to match - $match = array(); + $match = []; // Exact phrases ("like this") if (preg_match_all('/"(.+?)"/', $query, $exact_phrases)) { @@ -752,7 +752,7 @@ function mod_board_log(Context $ctx, $board, $page_no = 1, $hide_names = false, function mod_view_catalog(Context $ctx, $boardName) { $config = $ctx->get('config'); require_once($config['dir']['themes'].'/catalog/theme.php'); - $settings = array(); + $settings = []; $settings['boards'] = $boardName; $settings['update_on_posts'] = true; $settings['title'] = 'Catalog'; @@ -871,9 +871,9 @@ function mod_ip(Context $ctx, $cip) { } - $args = array(); + $args = []; $args['ip'] = $ip; - $args['posts'] = array(); + $args['posts'] = []; if ($config['mod']['dns_lookup'] && empty($config['ipcrypt_key'])) $args['hostname'] = rDNS($ip); @@ -896,7 +896,7 @@ function mod_ip(Context $ctx, $cip) { } if (!isset($args['posts'][$board['uri']])) - $args['posts'][$board['uri']] = array('board' => $board, 'posts' => array()); + $args['posts'][$board['uri']] = array('board' => $board, 'posts' => []); $args['posts'][$board['uri']]['posts'][] = $po->build(true); } } @@ -921,7 +921,7 @@ function mod_ip(Context $ctx, $cip) { $query->execute() or error(db_error($query)); $args['logs'] = $query->fetchAll(PDO::FETCH_ASSOC); } else { - $args['logs'] = array(); + $args['logs'] = []; } $args['security_token'] = make_secure_link_token('IP/' . $cip); @@ -1009,7 +1009,7 @@ function mod_bans(Context $ctx) { if (!hasPermission($config['mod']['unban'])) error($config['error']['noaccess']); - $unban = array(); + $unban = []; foreach ($_POST as $name => $unused) { if (preg_match('/^ban_(\d+)$/', $name, $match)) $unban[] = $match[1]; @@ -1094,16 +1094,16 @@ function mod_ban_appeals(Context $ctx) { $query = query(sprintf("SELECT `num_files`, `files` FROM ``posts_%s`` WHERE `id` = " . (int)$ban['post']['id'], $board['uri'])); if ($_post = $query->fetch(PDO::FETCH_ASSOC)) { - $_post['files'] = $_post['files'] ? json_decode($_post['files']) : array(); + $_post['files'] = $_post['files'] ? json_decode($_post['files']) : []; $ban['post'] = array_merge($ban['post'], $_post); } else { - $ban['post']['files'] = array(array()); + $ban['post']['files'] = array([]); $ban['post']['files'][0]['file'] = 'deleted'; $ban['post']['files'][0]['thumb'] = false; $ban['post']['num_files'] = 1; } } else { - $ban['post']['files'] = array(array()); + $ban['post']['files'] = array([]); $ban['post']['files'][0]['file'] = 'deleted'; $ban['post']['files'][0]['thumb'] = false; $ban['post']['num_files'] = 1; @@ -1395,7 +1395,7 @@ function mod_move(Context $ctx, $originBoard, $postID) { $query->bindValue(':id', $postID, PDO::PARAM_INT); $query->execute() or error(db_error($query)); - $replies = array(); + $replies = []; while ($post = $query->fetch(PDO::FETCH_ASSOC)) { $post['mod'] = true; @@ -1459,7 +1459,7 @@ function mod_move(Context $ctx, $originBoard, $postID) { if (!empty($post['tracked_cites'])) { - $insert_rows = array(); + $insert_rows = []; foreach ($post['tracked_cites'] as $cite) { $insert_rows[] = '(' . $pdo->quote($board['uri']) . ', ' . $newPostID . ', ' . @@ -1810,8 +1810,8 @@ function mod_deletebyip(Context $ctx, $boardName, $post, $global = false) { @set_time_limit($config['mod']['rebuild_timelimit']); - $threads_to_rebuild = array(); - $threads_deleted = array(); + $threads_to_rebuild = []; + $threads_deleted = []; while ($post = $query->fetch(PDO::FETCH_ASSOC)) { openBoard($post['board']); @@ -1870,7 +1870,7 @@ function mod_user(Context $ctx, $uid) { $board = $board['uri']; } - $boards = array(); + $boards = []; foreach ($_POST as $name => $value) { if (preg_match('/^board_(' . $config['board_regex'] . ')$/u', $name, $matches) && in_array($matches[1], $_boards)) $boards[] = $matches[1]; @@ -1961,7 +1961,7 @@ function mod_user(Context $ctx, $uid) { $query->execute() or error(db_error($query)); $log = $query->fetchAll(PDO::FETCH_ASSOC); } else { - $log = array(); + $log = []; } $user['boards'] = explode(',', $user['boards']); @@ -1994,7 +1994,7 @@ function mod_user_new(Context $ctx) { $board = $board['uri']; } - $boards = array(); + $boards = []; foreach ($_POST as $name => $value) { if (preg_match('/^board_(' . $config['board_regex'] . ')$/u', $name, $matches) && in_array($matches[1], $_boards)) $boards[] = $matches[1]; @@ -2230,9 +2230,9 @@ function mod_rebuild(Context $ctx) { if (isset($_POST['rebuild'])) { @set_time_limit($config['mod']['rebuild_timelimit']); - $log = array(); + $log = []; $boards = listBoards(); - $rebuilt_scripts = array(); + $rebuilt_scripts = []; if (isset($_POST['rebuild_cache'])) { if ($config['cache']['enabled']) { @@ -2305,16 +2305,16 @@ function mod_reports(Context $ctx) { $query->execute() or error(db_error($query)); $reports = $query->fetchAll(PDO::FETCH_ASSOC); - $report_queries = array(); + $report_queries = []; foreach ($reports as $report) { if (!isset($report_queries[$report['board']])) - $report_queries[$report['board']] = array(); + $report_queries[$report['board']] = []; $report_queries[$report['board']][] = $report['post']; } - $report_posts = array(); + $report_posts = []; foreach ($report_queries as $board => $posts) { - $report_posts[$board] = array(); + $report_posts[$board] = []; $query = query(sprintf('SELECT * FROM ``posts_%s`` WHERE `id` = ' . implode(' OR `id` = ', $posts), $board)) or error(db_error()); while ($post = $query->fetch(PDO::FETCH_ASSOC)) { @@ -2433,7 +2433,7 @@ function mod_recent_posts(Context $ctx, $lim) { $limit = (is_numeric($lim))? $lim : 25; $last_time = (isset($_GET['last']) && is_numeric($_GET['last'])) ? $_GET['last'] : 0; - $mod_boards = array(); + $mod_boards = []; $boards = listBoards(); //if not all boards @@ -2593,7 +2593,7 @@ function mod_config(Context $ctx, $board_config = false) { if ($config['minify_html']) $config_append = str_replace("\n", ' ', $config_append); - $page = array(); + $page = []; $page['title'] = 'Cannot write to file!'; $page['config'] = $config; $page['body'] = ' @@ -2636,7 +2636,7 @@ function mod_themes_list(Context $ctx) { $themes_in_use = $query->fetchAll(PDO::FETCH_COLUMN); // Scan directory for themes - $themes = array(); + $themes = []; while ($file = readdir($dir)) { if ($file[0] != '.' && is_dir($config['dir']['themes'] . '/' . $file)) { $themes[$file] = loadThemeConfig($file); @@ -2933,7 +2933,7 @@ function mod_pages(Context $ctx, $board = false) { function mod_debug_antispam(Context $ctx) { global $pdo, $config; - $args = array(); + $args = []; if (isset($_POST['board'], $_POST['thread'])) { $where = '`board` = ' . $pdo->quote($_POST['board']); From b2df2ab2a560ca5818bc347630279b72ed9c9a99 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Fri, 16 Aug 2024 00:46:35 +0200 Subject: [PATCH 227/336] theme.php: extract theme functions from functions.php --- inc/functions.php | 102 ---------------------------------------- inc/functions/theme.php | 98 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 102 deletions(-) create mode 100644 inc/functions/theme.php diff --git a/inc/functions.php b/inc/functions.php index 2cb97c50..e529a955 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -397,108 +397,6 @@ function create_antibot($board, $thread = null) { return _create_antibot($board, $thread); } -function rebuildThemes($action, $boardname = false) { - global $config, $board, $current_locale; - - // Save the global variables - $_config = $config; - $_board = $board; - - // List themes - if ($themes = Cache::get("themes")) { - // OK, we already have themes loaded - } - else { - $query = query("SELECT `theme` FROM ``theme_settings`` WHERE `name` IS NULL AND `value` IS NULL") or error(db_error()); - - $themes = array(); - - while ($theme = $query->fetch(PDO::FETCH_ASSOC)) { - $themes[] = $theme; - } - - Cache::set("themes", $themes); - } - - foreach ($themes as $theme) { - // Restore them - $config = $_config; - $board = $_board; - - // Reload the locale - if ($config['locale'] != $current_locale) { - $current_locale = $config['locale']; - init_locale($config['locale']); - } - - if (PHP_SAPI === 'cli') { - echo "Rebuilding theme ".$theme['theme']."... "; - } - - rebuildTheme($theme['theme'], $action, $boardname); - - if (PHP_SAPI === 'cli') { - echo "done\n"; - } - } - - // Restore them again - $config = $_config; - $board = $_board; - - // Reload the locale - if ($config['locale'] != $current_locale) { - $current_locale = $config['locale']; - init_locale($config['locale']); - } -} - - -function loadThemeConfig($_theme) { - global $config; - - if (!file_exists($config['dir']['themes'] . '/' . $_theme . '/info.php')) - return false; - - // Load theme information into $theme - include $config['dir']['themes'] . '/' . $_theme . '/info.php'; - - return $theme; -} - -function rebuildTheme($theme, $action, $board = false) { - global $config, $_theme; - $_theme = $theme; - - $theme = loadThemeConfig($_theme); - - if (file_exists($config['dir']['themes'] . '/' . $_theme . '/theme.php')) { - require_once $config['dir']['themes'] . '/' . $_theme . '/theme.php'; - - $theme['build_function']($action, themeSettings($_theme), $board); - } -} - - -function themeSettings($theme) { - if ($settings = Cache::get("theme_settings_".$theme)) { - return $settings; - } - - $query = prepare("SELECT `name`, `value` FROM ``theme_settings`` WHERE `theme` = :theme AND `name` IS NOT NULL"); - $query->bindValue(':theme', $theme); - $query->execute() or error(db_error($query)); - - $settings = array(); - while ($s = $query->fetch(PDO::FETCH_ASSOC)) { - $settings[$s['name']] = $s['value']; - } - - Cache::set("theme_settings_".$theme, $settings); - - return $settings; -} - function sprintf3($str, $vars, $delim = '%') { $replaces = array(); foreach ($vars as $k => $v) { diff --git a/inc/functions/theme.php b/inc/functions/theme.php new file mode 100644 index 00000000..840f6b29 --- /dev/null +++ b/inc/functions/theme.php @@ -0,0 +1,98 @@ +fetchAll(\PDO::FETCH_NUM); + + \Cache::set("themes", $themes); + } + + foreach ($themes as $theme) { + // Restore them + $config = $_config; + $board = $_board; + + // Reload the locale + if ($config['locale'] != $current_locale) { + $current_locale = $config['locale']; + init_locale($config['locale']); + } + + if (PHP_SAPI === 'cli') { + echo "Rebuilding theme ".$theme['theme']."... "; + } + + rebuildTheme($theme['theme'], $action, $boardname); + + if (PHP_SAPI === 'cli') { + echo "done\n"; + } + } + + // Restore them again + $config = $_config; + $board = $_board; + + // Reload the locale + if ($config['locale'] != $current_locale) { + $current_locale = $config['locale']; + init_locale($config['locale']); + } +} + +function loadThemeConfig($_theme) { + global $config; + + if (!file_exists($config['dir']['themes'] . '/' . $_theme . '/info.php')) { + return false; + } + + // Load theme information into $theme + include $config['dir']['themes'] . '/' . $_theme . '/info.php'; + + return $theme; +} + +function rebuildTheme($theme, string $action, $board = false) { + global $config, $_theme; + $_theme = $theme; + + $theme = loadThemeConfig($_theme); + + if (file_exists($config['dir']['themes'] . '/' . $_theme . '/theme.php')) { + require_once $config['dir']['themes'] . '/' . $_theme . '/theme.php'; + + $theme['build_function']($action, themeSettings($_theme), $board); + } +} + +function themeSettings($theme): array { + if ($settings = \Cache::get("theme_settings_" . $theme)) { + return $settings; + } + + $query = prepare("SELECT `name`, `value` FROM ``theme_settings`` WHERE `theme` = :theme AND `name` IS NOT NULL"); + $query->bindValue(':theme', $theme); + $query->execute() or error(db_error($query)); + + $settings = []; + while ($s = $query->fetch(\PDO::FETCH_ASSOC)) { + $settings[$s['name']] = $s['value']; + } + + \Cache::set("theme_settings_".$theme, $settings); + + return $settings; +} From 453ae795f5647a41163896dec9d67137f0a674aa Mon Sep 17 00:00:00 2001 From: Zankaria Date: Fri, 16 Aug 2024 18:24:48 +0200 Subject: [PATCH 228/336] composer.json: autoload theme.php --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 80eaf918..92f9c76e 100644 --- a/composer.json +++ b/composer.json @@ -37,6 +37,7 @@ "inc/functions/net.php", "inc/functions/num.php", "inc/functions/format.php", + "inc/functions/theme.php", "inc/driver/http-driver.php", "inc/driver/log-driver.php", "inc/service/captcha-queries.php" From d408ed04135b365f7d8bb3e3e80cc36940731f2d Mon Sep 17 00:00:00 2001 From: Zankaria Date: Fri, 16 Aug 2024 18:32:32 +0200 Subject: [PATCH 229/336] theme.php: rename functions to snake_case --- inc/bans.php | 12 ++++++------ inc/controller.php | 8 ++++---- inc/functions/theme.php | 14 +++++++------- inc/mod/pages.php | 38 +++++++++++++++++++------------------- post.php | 6 +++--- 5 files changed, 39 insertions(+), 39 deletions(-) diff --git a/inc/bans.php b/inc/bans.php index a0ea5c76..58b054ec 100644 --- a/inc/bans.php +++ b/inc/bans.php @@ -15,7 +15,7 @@ class Bans { $query->bindValue(':id', $ban_ids[0], PDO::PARAM_INT); $query->execute() or error(db_error()); - rebuildThemes('bans'); + Vichan\Functions\Theme\rebuild_themes('bans'); } elseif ($len >= 1) { // Build the query. $query = 'DELETE FROM ``bans`` WHERE `id` IN ('; @@ -33,7 +33,7 @@ class Bans { $query->execute() or error(db_error()); - rebuildThemes('bans'); + Vichan\Functions\Theme\rebuild_themes('bans'); } } @@ -343,7 +343,7 @@ class Bans { static public function seen($ban_id) { $query = query("UPDATE ``bans`` SET `seen` = 1 WHERE `id` = " . (int)$ban_id) or error(db_error()); - rebuildThemes('bans'); + Vichan\Functions\Theme\rebuild_themes('bans'); } static public function purge($require_seen, $moratorium) { @@ -358,7 +358,7 @@ class Bans { $affected = $query->rowCount(); if ($affected > 0) { - rebuildThemes('bans'); + Vichan\Functions\Theme\rebuild_themes('bans'); } return $affected; } @@ -386,7 +386,7 @@ class Bans { query("DELETE FROM ``bans`` WHERE `id` = " . (int)$ban_id) or error(db_error()); - if (!$dont_rebuild) rebuildThemes('bans'); + if (!$dont_rebuild) Vichan\Functions\Theme\rebuild_themes('bans'); return true; } @@ -465,7 +465,7 @@ class Bans { modLog("Created a new $ban_len ban on $ban_board for $ban_ip (# $ban_id ) with $ban_reason"); - rebuildThemes('bans'); + Vichan\Functions\Theme\rebuild_themes('bans'); return $pdo->lastInsertId(); } diff --git a/inc/controller.php b/inc/controller.php index 02e33443..0b4e2886 100644 --- a/inc/controller.php +++ b/inc/controller.php @@ -85,24 +85,24 @@ function sb_api($b) { global $config, $build_pages; } function sb_ukko() { - rebuildTheme("ukko", "post-thread"); + Vichan\Functions\Theme\rebuild_theme("ukko", "post-thread"); return true; } function sb_catalog($b) { if (!openBoard($b)) return false; - rebuildTheme("catalog", "post-thread", $b); + Vichan\Functions\Theme\rebuild_theme("catalog", "post-thread", $b); return true; } function sb_recent() { - rebuildTheme("recent", "post-thread"); + Vichan\Functions\Theme\rebuild_theme("recent", "post-thread"); return true; } function sb_sitemap() { - rebuildTheme("sitemap", "all"); + Vichan\Functions\Theme\rebuild_theme("sitemap", "all"); return true; } diff --git a/inc/functions/theme.php b/inc/functions/theme.php index 840f6b29..6ac3a95b 100644 --- a/inc/functions/theme.php +++ b/inc/functions/theme.php @@ -2,7 +2,7 @@ namespace Vichan\Functions\Theme; -function rebuildThemes(string $action, $boardname = false): void { +function rebuild_themes(string $action, $boardname = false): void { global $config, $board, $current_locale; // Save the global variables @@ -34,7 +34,7 @@ function rebuildThemes(string $action, $boardname = false): void { echo "Rebuilding theme ".$theme['theme']."... "; } - rebuildTheme($theme['theme'], $action, $boardname); + rebuild_theme($theme['theme'], $action, $boardname); if (PHP_SAPI === 'cli') { echo "done\n"; @@ -52,7 +52,7 @@ function rebuildThemes(string $action, $boardname = false): void { } } -function loadThemeConfig($_theme) { +function load_theme_config($_theme) { global $config; if (!file_exists($config['dir']['themes'] . '/' . $_theme . '/info.php')) { @@ -65,20 +65,20 @@ function loadThemeConfig($_theme) { return $theme; } -function rebuildTheme($theme, string $action, $board = false) { +function rebuild_theme($theme, string $action, $board = false) { global $config, $_theme; $_theme = $theme; - $theme = loadThemeConfig($_theme); + $theme = load_theme_config($_theme); if (file_exists($config['dir']['themes'] . '/' . $_theme . '/theme.php')) { require_once $config['dir']['themes'] . '/' . $_theme . '/theme.php'; - $theme['build_function']($action, themeSettings($_theme), $board); + $theme['build_function']($action, theme_settings($_theme), $board); } } -function themeSettings($theme): array { +function theme_settings($theme): array { if ($settings = \Cache::get("theme_settings_" . $theme)) { return $settings; } diff --git a/inc/mod/pages.php b/inc/mod/pages.php index 46e8fc7e..f2caff32 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -442,7 +442,7 @@ function mod_edit_board($boardName) { cache::delete('all_boards'); } - rebuildThemes('boards'); + Vichan\Functions\Theme\rebuild_themes('boards'); header('Location: ?/', true, $config['redirect_http']); } else { @@ -514,7 +514,7 @@ function mod_new_board() { // Build the board buildIndex(); - rebuildThemes('boards'); + Vichan\Functions\Theme\rebuild_themes('boards'); header('Location: ?/' . $board['uri'] . '/' . $config['file_index'], true, $config['redirect_http']); } @@ -617,7 +617,7 @@ function mod_news($page_no = 1) { modLog('Posted a news entry'); - rebuildThemes('news'); + Vichan\Functions\Theme\rebuild_themes('news'); header('Location: ?/edit_news#' . $pdo->lastInsertId(), true, $config['redirect_http']); } @@ -1013,7 +1013,7 @@ function mod_bans() { foreach ($unban as $id) { Bans::delete($id, true, $mod['boards'], true); } - rebuildThemes('bans'); + Vichan\Functions\Theme\rebuild_themes('bans'); header('Location: ?/bans', true, $config['redirect_http']); return; } @@ -1281,7 +1281,7 @@ function mod_move_reply($originBoard, $postID) { buildThread($newID); // trigger themes - rebuildThemes('post', $targetBoard); + Vichan\Functions\Theme\rebuild_themes('post', $targetBoard); // mod log modLog("Moved post #{$postID} to " . sprintf($config['board_abbreviation'], $targetBoard) . " (#{$newID})", $originBoard); @@ -1469,7 +1469,7 @@ function mod_move($originBoard, $postID) { buildIndex(); // trigger themes - rebuildThemes('post', $targetBoard); + Vichan\Functions\Theme\rebuild_themes('post', $targetBoard); $newboard = $board; @@ -1576,7 +1576,7 @@ function mod_ban_post($board, $delete, $post, $token = false) { // Rebuild board buildIndex(); // Rebuild themes - rebuildThemes('post-delete', $board); + Vichan\Functions\Theme\rebuild_themes('post-delete', $board); } header('Location: ?/' . sprintf($config['board_path'], $board) . $config['file_index'], true, $config['redirect_http']); @@ -1651,7 +1651,7 @@ function mod_edit_post($board, $edit_raw_html, $postID) { buildIndex(); - rebuildThemes('post', $board); + Vichan\Functions\Theme\rebuild_themes('post', $board); header('Location: ?/' . sprintf($config['board_path'], $board) . $config['dir']['res'] . link_for($post) . '#' . $postID, true, $config['redirect_http']); } else { @@ -1689,7 +1689,7 @@ function mod_delete($board, $post) { // Rebuild board buildIndex(); // Rebuild themes - rebuildThemes('post-delete', $board); + Vichan\Functions\Theme\rebuild_themes('post-delete', $board); // Redirect header('Location: ?/' . sprintf($config['board_path'], $board) . $config['file_index'], true, $config['redirect_http']); } @@ -1711,7 +1711,7 @@ function mod_deletefile($board, $post, $file) { // Rebuild board buildIndex(); // Rebuild themes - rebuildThemes('post-delete', $board); + Vichan\Functions\Theme\rebuild_themes('post-delete', $board); // Redirect header('Location: ?/' . sprintf($config['board_path'], $board) . $config['file_index'], true, $config['redirect_http']); @@ -1756,7 +1756,7 @@ function mod_spoiler_image($board, $post, $file) { buildIndex(); // Rebuild themes - rebuildThemes('post-delete', $board); + Vichan\Functions\Theme\rebuild_themes('post-delete', $board); // Redirect header('Location: ?/' . sprintf($config['board_path'], $board) . $config['file_index'], true, $config['redirect_http']); @@ -1807,7 +1807,7 @@ function mod_deletebyip($boardName, $post, $global = false) { deletePost($post['id'], false, false); - rebuildThemes('post-delete', $board['uri']); + Vichan\Functions\Theme\rebuild_themes('post-delete', $board['uri']); buildIndex(); @@ -2233,7 +2233,7 @@ function mod_rebuild() { if (isset($_POST['rebuild_themes'])) { $log[] = 'Regenerating theme files'; - rebuildThemes('all'); + Vichan\Functions\Theme\rebuild_themes('all'); } if (isset($_POST['rebuild_javascript'])) { @@ -2622,7 +2622,7 @@ function mod_themes_list() { $themes = array(); while ($file = readdir($dir)) { if ($file[0] != '.' && is_dir($config['dir']['themes'] . '/' . $file)) { - $themes[$file] = loadThemeConfig($file); + $themes[$file] = Vichan\Functions\Theme\load_theme_config($file); } } closedir($dir); @@ -2644,7 +2644,7 @@ function mod_theme_configure($theme_name) { if (!hasPermission($config['mod']['themes'])) error($config['error']['noaccess']); - if (!$theme = loadThemeConfig($theme_name)) { + if (!$theme = Vichan\Functions\Theme\load_theme_config($theme_name)) { error($config['error']['invalidtheme']); } @@ -2682,7 +2682,7 @@ function mod_theme_configure($theme_name) { $result = true; $message = false; if (isset($theme['install_callback'])) { - $ret = $theme['install_callback'](themeSettings($theme_name)); + $ret = $theme['install_callback'](Vichan\Functions\Theme\theme_settings($theme_name)); if ($ret && !empty($ret)) { if (is_array($ret) && count($ret) == 2) { $result = $ret[0]; @@ -2699,7 +2699,7 @@ function mod_theme_configure($theme_name) { } // Build themes - rebuildThemes('all'); + Vichan\Functions\Theme\rebuild_themes('all'); mod_page(sprintf(_($result ? 'Installed theme: %s' : 'Installation failed: %s'), $theme['name']), $config['file_mod_theme_installed'], array( 'theme_name' => $theme_name, @@ -2710,7 +2710,7 @@ function mod_theme_configure($theme_name) { return; } - $settings = themeSettings($theme_name); + $settings = Vichan\Functions\Theme\theme_settings($theme_name); mod_page(sprintf(_('Configuring theme: %s'), $theme['name']), $config['file_mod_theme_config'], array( 'theme_name' => $theme_name, @@ -2743,7 +2743,7 @@ function mod_theme_rebuild($theme_name) { if (!hasPermission($config['mod']['themes'])) error($config['error']['noaccess']); - rebuildTheme($theme_name, 'all'); + Vichan\Functions\Theme\rebuild_theme($theme_name, 'all'); mod_page(sprintf(_('Rebuilt theme: %s'), $theme_name), $config['file_mod_theme_rebuilt'], array( 'theme_name' => $theme_name, diff --git a/post.php b/post.php index 644ff111..18022047 100644 --- a/post.php +++ b/post.php @@ -477,7 +477,7 @@ if (isset($_POST['delete'])) { if (function_exists('fastcgi_finish_request')) @fastcgi_finish_request(); - rebuildThemes('post-delete', $board['uri']); + Vichan\Functions\Theme\rebuild_themes('post-delete', $board['uri']); } elseif (isset($_POST['report'])) { if (!isset($_POST['board'], $_POST['reason'])) @@ -1429,9 +1429,9 @@ if (isset($_POST['delete'])) { @fastcgi_finish_request(); if ($post['op']) - rebuildThemes('post-thread', $board['uri']); + Vichan\Functions\Theme\rebuild_themes('post-thread', $board['uri']); else - rebuildThemes('post', $board['uri']); + Vichan\Functions\Theme\rebuild_themes('post', $board['uri']); } elseif (isset($_POST['appeal'])) { if (!isset($_POST['ban_id'])) From 4f68166870f3e86b064189e56cd8a7e7cedce550 Mon Sep 17 00:00:00 2001 From: fowr Date: Fri, 5 Aug 2022 11:16:53 -0300 Subject: [PATCH 230/336] api.php: using depedency injection instead of globals --- inc/api.php | 10 ++++------ inc/functions.php | 12 ++++++++++-- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/inc/api.php b/inc/api.php index 11f51fe4..16844d32 100644 --- a/inc/api.php +++ b/inc/api.php @@ -45,13 +45,11 @@ class Api { 'size' => 'fsize' ]; - public function __construct() { - global $config; - + public function __construct(bool $show_filename, bool $hide_email, bool $country_flags) { // Translation from local fields to fields in 4chan-style API - $this->show_filename = $config['show_filename']; - $this->hide_email = $config['hide_email']; - $this->country_flags = $config['country_flags']; + $this->show_filename = $show_filename; + $this->hide_email = $hide_email; + $this->country_flags = $country_flags; $this->postFields = [ 'id' => 'no', diff --git a/inc/functions.php b/inc/functions.php index 2cb97c50..c76ca10b 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -1730,7 +1730,11 @@ function buildIndex($global_api = "yes") { $antibot = null; if ($config['api']['enabled']) { - $api = new Api(); + $api = new Api( + $config['show_filename'], + $config['hide_email'], + $config['country_flags'] + ); $catalog = array(); } @@ -2388,7 +2392,11 @@ function buildThread($id, $return = false, $mod = false) { // json api if ($config['api']['enabled'] && !$mod) { - $api = new Api(); + $api = new Api( + $config['show_filename'], + $config['hide_email'], + $config['country_flags'] + ); $json = json_encode($api->translateThread($thread)); $jsonFilename = $board['dir'] . $config['dir']['res'] . $id . '.json'; file_write($jsonFilename, $json); From 1191dfb193ea36f18ba84ad1b546ed0517286c95 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sun, 18 Aug 2024 01:06:45 +0200 Subject: [PATCH 231/336] pages.php: add copy fallback in case of already existing file when moving thread. --- inc/mod/pages.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/inc/mod/pages.php b/inc/mod/pages.php index ac5cf35f..a713d8fe 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -10,6 +10,14 @@ use Vichan\Functions\Net; defined('TINYBOARD') or exit; +function _link_or_copy(string $target, string $link): bool { + if (!link($target, $link)) { + error_log("Failed to link() $target to $link. Falling back to copy()"); + return copy($target, $link); + } + return true; +} + function mod_page($title, $template, $args, $subtitle = false) { global $config, $mod; @@ -1348,7 +1356,7 @@ function mod_move(Context $ctx, $originBoard, $postID) { error(_('Target and source board are the same.')); // link() if leaving a shadow thread behind; else, rename(). - $clone = $shadow ? 'link' : 'rename'; + $clone = $shadow ? '_link_or_copy' : 'rename'; // indicate that the post is a thread $post['op'] = true; From 13b587cf62aa8caf8826bbf85c9846e287a2e4af Mon Sep 17 00:00:00 2001 From: fowr <89118232+perdedora@users.noreply.github.com> Date: Tue, 20 Aug 2024 21:27:22 -0300 Subject: [PATCH 232/336] add resource_version to avoid cache. --- inc/secrets.php | 6 ++++++ templates/header.html | 6 +++--- templates/mod/ban_list.html | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/inc/secrets.php b/inc/secrets.php index b3d9bbc7..e7c4efc1 100644 --- a/inc/secrets.php +++ b/inc/secrets.php @@ -1 +1,7 @@ + {% if config.url_favicon %}{% endif %} @@ -12,9 +12,9 @@ var modRoot = "{{ config.root }}" + (inMod ? "mod.php?/" : ""); {% if not nojavascript %} - + {% if not config.additional_javascript_compile %} - {% for javascript in config.additional_javascript %}{% endfor %} + {% for javascript in config.additional_javascript %}{% endfor %} {% endif %} {% if mod %} diff --git a/templates/mod/ban_list.html b/templates/mod/ban_list.html index 0c67f269..f039350b 100644 --- a/templates/mod/ban_list.html +++ b/templates/mod/ban_list.html @@ -1,4 +1,4 @@ - + From 6ea8fd5bf32d83ac1af9e0a54fdce2524ec239c6 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Wed, 21 Aug 2024 14:00:59 +0200 Subject: [PATCH 233/336] config.php: add missing default configuration, remove db settings --- inc/config.php | 4 ++++ inc/secrets.php | 6 ------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/inc/config.php b/inc/config.php index b4fc27d8..77c3ed57 100644 --- a/inc/config.php +++ b/inc/config.php @@ -1187,6 +1187,10 @@ // Minify Javascript using http://code.google.com/p/minify/. $config['minify_js'] = false; + // Version number for main.js (or $config['url_javascript']). + // You can use this to bypass the user's browsers and CDN caches. + $config['resource_version'] = 0; + // Dispatch thumbnail loading and image configuration with JavaScript. It will need a certain javascript // code to work. $config['javascript_image_dispatch'] = false; diff --git a/inc/secrets.php b/inc/secrets.php index e7c4efc1..b3d9bbc7 100644 --- a/inc/secrets.php +++ b/inc/secrets.php @@ -1,7 +1 @@ Date: Sun, 25 Aug 2024 17:01:06 -0300 Subject: [PATCH 234/336] fix oversights introduced in captcha; --- templates/main.js | 6 +++--- templates/post_form.html | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/templates/main.js b/templates/main.js index f956f5cf..13c80c33 100644 --- a/templates/main.js +++ b/templates/main.js @@ -222,7 +222,7 @@ function getCookie(cookie_name) { } } -{% endraw %} +{% endverbatim %} {% if config.captcha.dynamic %} function is_dynamic_captcha_enabled() { let cookie = get_cookie('require-captcha'); @@ -230,7 +230,7 @@ function is_dynamic_captcha_enabled() { } function get_captcha_pub_key() { -{% if config.captcha.provider === 'recaptcha' %} +{% if config.captcha.provider == 'recaptcha' %} return "{{ config.captcha.recaptcha.sitekey }}"; {% else %} return null; @@ -250,7 +250,7 @@ function init_dynamic_captcha() { } } {% endif %} -{% raw %} +{% verbatim %} function highlightReply(id) { if (typeof window.event != "undefined" && event.which == 2) { diff --git a/templates/post_form.html b/templates/post_form.html index 038395fa..64aff382 100644 --- a/templates/post_form.html +++ b/templates/post_form.html @@ -100,7 +100,7 @@ {% endif %} - {% if config.captcha.enabled %} + {% if config.captcha.provider == 'native' %} {% trans %}Verification{% endtrans %} @@ -115,7 +115,7 @@ - {% elseif config.new_thread_capt %} + {% elseif config.captcha.native.new_thread_capt %} {% if not id %} From eeb55133eb22ded86419692bc8070ea9d9aa8599 Mon Sep 17 00:00:00 2001 From: fowr <89118232+perdedora@users.noreply.github.com> Date: Sun, 25 Aug 2024 17:01:31 -0300 Subject: [PATCH 235/336] add missing context file to composer autoload --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 92f9c76e..a2ddce03 100644 --- a/composer.json +++ b/composer.json @@ -40,7 +40,8 @@ "inc/functions/theme.php", "inc/driver/http-driver.php", "inc/driver/log-driver.php", - "inc/service/captcha-queries.php" + "inc/service/captcha-queries.php", + "inc/context.php" ] }, "license": "Tinyboard + vichan", From 43b926c41bd5f9d8ec8df9c50e8f9fa700c42cd9 Mon Sep 17 00:00:00 2001 From: seisatsu Date: Fri, 6 Sep 2024 15:23:37 -0500 Subject: [PATCH 236/336] Fix typo in post.php that breaks Tesseract OCR Fixes a typo in post.php that causes an uninitialized variable "$value" to be referenced instead of the correct variable "$txt". This caused all attempts to use the Tesseract OCR feature to fail with an error. --- post.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/post.php b/post.php index 1f3a2b97..6f29f50c 100644 --- a/post.php +++ b/post.php @@ -1197,7 +1197,7 @@ if (isset($_POST['delete'])) { if ($txt !== '') { // This one has an effect, that the body is appended to a post body. So you can write a correct // spamfilter. - $post['body_nomarkup'] .= "" . htmlspecialchars($value) . ""; + $post['body_nomarkup'] .= "" . htmlspecialchars($txt) . ""; } } catch (RuntimeException $e) { $context->get(Log::class)->log(Log::ERROR, "Could not OCR image: {$e->getMessage()}"); From 1db5c788dd8b1f9322eea9700a45a8d7562ac756 Mon Sep 17 00:00:00 2001 From: Lorenzo Yario Date: Tue, 17 Sep 2024 21:03:05 -0700 Subject: [PATCH 237/336] lazy loading --- inc/config.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/inc/config.php b/inc/config.php index 77c3ed57..3e183dc1 100644 --- a/inc/config.php +++ b/inc/config.php @@ -1155,6 +1155,10 @@ // . $config['flag_style'] = 'width:16px;height:11px;'; + // Lazy loading + // https://developer.mozilla.org/en-US/docs/Web/Performance/Lazy_loading + $config['content_lazy_loading'] = false; + /* * ==================== * Javascript From 16bb7041544e31ca94030ee1f97b829af6c2d2cf Mon Sep 17 00:00:00 2001 From: Lorenzo Yario Date: Tue, 17 Sep 2024 21:15:43 -0700 Subject: [PATCH 238/336] lazy loading commit --- templates/post/image.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/post/image.html b/templates/post/image.html index 8b845729..6e416502 100644 --- a/templates/post/image.html +++ b/templates/post/image.html @@ -20,7 +20,7 @@ {{ config.file_thumb|sprintf(config.file_icons.default) }} {% endif %} " - style="width:{{ post.thumbwidth }}px;height:{{ post.thumbheight }}px" + style="width:{{ post.thumbwidth }}px;height:{{ post.thumbheight }}px" {% if config.content_lazy_loading %}loading="lazy"{% endif %} > {% else %} @@ -39,7 +39,7 @@ {{ config.uri_thumb }}{{ post.thumb }} {% endif %} " - style="width:{{ post.thumbwidth }}px;height:{{ post.thumbheight }}px" alt="" + style="width:{{ post.thumbwidth }}px;height:{{ post.thumbheight }}px" {% if config.content_lazy_loading %}loading="lazy"{% endif %} alt="" /> {% endif %} From 8c27b5261ca386f60165fc71d621cc1feb50bf0e Mon Sep 17 00:00:00 2001 From: Lorenzo Yario Date: Tue, 17 Sep 2024 21:18:09 -0700 Subject: [PATCH 239/336] lazy loading commit --- templates/themes/catalog/catalog.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/themes/catalog/catalog.html b/templates/themes/catalog/catalog.html index 493f47d1..62ca7147 100644 --- a/templates/themes/catalog/catalog.html +++ b/templates/themes/catalog/catalog.html @@ -51,7 +51,7 @@ {% else %} + id="img-{{ post.id }}" data-subject="{% if post.subject %}{{ post.subject|e }}{% endif %}" data-name="{{ post.name|e }}" data-muhdifference="{{ post.muhdifference }}" class="{{post.board}} thread-image" title="{{post.bump|date('%b %d %H:%M')}} {% if config.content_lazy_loading %}loading="lazy"{% endif %}">
R: {{ post.reply_count }} / I: {{ post.image_count }}{% if post.sticky %} (sticky){% endif %} From a457b905bf378cbb05b3b9c205d58881ca04b851 Mon Sep 17 00:00:00 2001 From: Lorenzo Yario Date: Tue, 17 Sep 2024 21:44:06 -0700 Subject: [PATCH 240/336] from master branch --- js/show-backlinks.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/show-backlinks.js b/js/show-backlinks.js index 19a3d5dd..d80eebaa 100644 --- a/js/show-backlinks.js +++ b/js/show-backlinks.js @@ -57,9 +57,10 @@ onReady(function() { $('div.post.op').each(showBackLinks); $(document).on('new_post', function(e, post) { - showBackLinks.call(post); if ($(post).hasClass("op")) { $(post).find('div.post.reply').each(showBackLinks); + } else { + $(post).parent().find('div.post.reply').each(showBackLinks); } }); }); From 36737b77a832f1f36610db5bcebd280c9f9ef105 Mon Sep 17 00:00:00 2001 From: Lorenzo Yario Date: Tue, 17 Sep 2024 22:20:36 -0700 Subject: [PATCH 241/336] made https flag make sense --- install.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install.php b/install.php index 90d8af57..36151c87 100644 --- a/install.php +++ b/install.php @@ -871,7 +871,7 @@ if ($step == 0) { ), array( 'category' => 'Misc', - 'name' => 'HTTPS not being used', + 'name' => 'HTTPS being used', 'result' => $httpsvalue, 'required' => false, 'message' => 'You are not currently using https for vichan, or at least for your backend server. If this intentional, add "$config[\'cookies\'][\'secure_login_only\'] = 0;" (or 1 if using a proxy) on a new line under "Additional configuration" on the next page.' From 1effe1648b3fdb3dbadc8be28ba42f2943f9e4c8 Mon Sep 17 00:00:00 2001 From: Lorenzo Yario Date: Tue, 17 Sep 2024 23:28:21 -0700 Subject: [PATCH 242/336] add some spaces --- templates/mod/dashboard.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/mod/dashboard.html b/templates/mod/dashboard.html index a5e11f82..9d16c159 100644 --- a/templates/mod/dashboard.html +++ b/templates/mod/dashboard.html @@ -6,7 +6,7 @@ {% for board in boards %}
  • {{ config.board_abbreviation|sprintf(board.uri) }} - - + - {{ board.title|e }} {% if board.subtitle %} — @@ -70,7 +70,7 @@
  • {% trans 'PM inbox' %} - {% if unread_pms > 0 %}{%endif %}({{ unread_pms }} unread){% if unread_pms > 0 %}{%endif %} + {% if unread_pms > 0 %}{%endif %} ({{ unread_pms }} unread){% if unread_pms > 0 %}{%endif %}
  • From e1a4ae5336fa13e455e9cf0bffe42dd1667cc3ad Mon Sep 17 00:00:00 2001 From: Lorenzo Yario Date: Thu, 19 Sep 2024 01:26:50 -0700 Subject: [PATCH 243/336] revert trimming and this somehow fixes the antibot issues?????? idk but it works somehow?????? --- inc/anti-bot.php | 70 ++++++++++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/inc/anti-bot.php b/inc/anti-bot.php index aa0eeb99..48150328 100644 --- a/inc/anti-bot.php +++ b/inc/anti-bot.php @@ -10,7 +10,7 @@ $hidden_inputs_twig = array(); class AntiBot { public $salt, $inputs = array(), $index = 0; - + public static function randomString($length, $uppercase = false, $special_chars = false, $unicode_chars = false) { $chars = 'abcdefghijklmnopqrstuvwxyz0123456789'; if ($uppercase) @@ -22,11 +22,11 @@ class AntiBot { for ($n = 0; $n < $len; $n++) $chars .= mb_convert_encoding('&#' . mt_rand(0x2600, 0x26FF) . ';', 'UTF-8', 'HTML-ENTITIES'); } - + $chars = preg_split('//u', $chars, -1, PREG_SPLIT_NO_EMPTY); - + $ch = array(); - + // fill up $ch until we reach $length while (count($ch) < $length) { $n = $length - count($ch); @@ -39,40 +39,40 @@ class AntiBot { foreach ($keys as $key) $ch[] = $chars[$key]; } - + $chars = $ch; - + return implode('', $chars); } - + public static function make_confusing($string) { $chars = preg_split('//u', $string, -1, PREG_SPLIT_NO_EMPTY); - + foreach ($chars as &$c) { if (mt_rand(0, 3) != 0) $c = utf8tohtml($c); else $c = mb_encode_numericentity($c, array(0, 0xffff, 0, 0xffff), 'UTF-8'); } - + return implode('', $chars); } - + public function __construct(array $salt = array()) { global $config; - + if (!empty($salt)) { // create a salted hash of the "extra salt" $this->salt = implode(':', $salt); - } else { + } else { $this->salt = ''; } - + shuffle($config['spam']['hidden_input_names']); - + $input_count = mt_rand($config['spam']['hidden_inputs_min'], $config['spam']['hidden_inputs_max']); $hidden_input_names_x = 0; - + for ($x = 0; $x < $input_count ; $x++) { if ($hidden_input_names_x === false || mt_rand(0, 2) == 0) { // Use an obscure name @@ -83,7 +83,7 @@ class AntiBot { if ($hidden_input_names_x >= count($config['spam']['hidden_input_names'])) $hidden_input_names_x = false; } - + if (mt_rand(0, 2) == 0) { // Value must be null $this->inputs[$name] = ''; @@ -96,16 +96,16 @@ class AntiBot { } } } - + public static function space() { if (mt_rand(0, 3) != 0) return ' '; return str_repeat(' ', mt_rand(1, 3)); } - + public function html($count = false) { global $config; - + $elements = array( '', '', @@ -119,13 +119,13 @@ class AntiBot { '', '' ); - + $html = ''; - + if ($count === false) { - $count = mt_rand(1, (int)abs(count($this->inputs) / 15) + 1); + $count = mt_rand(1, abs(count($this->inputs) / 15) + 1); } - + if ($count === true) { // all elements $inputs = array_slice($this->inputs, $this->index); @@ -133,7 +133,7 @@ class AntiBot { $inputs = array_slice($this->inputs, $this->index, $count); } $this->index += count($inputs); - + foreach ($inputs as $name => $value) { $element = false; while (!$element) { @@ -146,37 +146,37 @@ class AntiBot { $element = false; } } - + $element = str_replace('%name%', utf8tohtml($name), $element); - + if (mt_rand(0, 2) == 0) $value = $this->make_confusing($value); else $value = utf8tohtml($value); - + if (strpos($element, 'textarea') === false) $value = str_replace('"', '"', $value); - + $element = str_replace('%value%', $value, $element); - + $html .= $element; } - + return $html; } - + public function reset() { $this->index = 0; } - + public function hash() { global $config; - + // This is the tricky part: create a hash to validate it after // First, sort the keys in alphabetical order (A-Z) $inputs = $this->inputs; ksort($inputs); - + $hash = ''; // Iterate through each input foreach ($inputs as $name => $value) { @@ -184,7 +184,7 @@ class AntiBot { } // Add a salt to the hash $hash .= $config['cookies']['salt']; - + // Use SHA1 for the hash return sha1($hash . $this->salt); } From c1788d079298989bbd17828e147a53ca6ea3bd80 Mon Sep 17 00:00:00 2001 From: Lorenzo Yario Date: Thu, 19 Sep 2024 01:32:49 -0700 Subject: [PATCH 244/336] Revert "revert trimming and this somehow fixes the antibot issues??????" --- inc/anti-bot.php | 70 ++++++++++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/inc/anti-bot.php b/inc/anti-bot.php index 48150328..aa0eeb99 100644 --- a/inc/anti-bot.php +++ b/inc/anti-bot.php @@ -10,7 +10,7 @@ $hidden_inputs_twig = array(); class AntiBot { public $salt, $inputs = array(), $index = 0; - + public static function randomString($length, $uppercase = false, $special_chars = false, $unicode_chars = false) { $chars = 'abcdefghijklmnopqrstuvwxyz0123456789'; if ($uppercase) @@ -22,11 +22,11 @@ class AntiBot { for ($n = 0; $n < $len; $n++) $chars .= mb_convert_encoding('&#' . mt_rand(0x2600, 0x26FF) . ';', 'UTF-8', 'HTML-ENTITIES'); } - + $chars = preg_split('//u', $chars, -1, PREG_SPLIT_NO_EMPTY); - + $ch = array(); - + // fill up $ch until we reach $length while (count($ch) < $length) { $n = $length - count($ch); @@ -39,40 +39,40 @@ class AntiBot { foreach ($keys as $key) $ch[] = $chars[$key]; } - + $chars = $ch; - + return implode('', $chars); } - + public static function make_confusing($string) { $chars = preg_split('//u', $string, -1, PREG_SPLIT_NO_EMPTY); - + foreach ($chars as &$c) { if (mt_rand(0, 3) != 0) $c = utf8tohtml($c); else $c = mb_encode_numericentity($c, array(0, 0xffff, 0, 0xffff), 'UTF-8'); } - + return implode('', $chars); } - + public function __construct(array $salt = array()) { global $config; - + if (!empty($salt)) { // create a salted hash of the "extra salt" $this->salt = implode(':', $salt); - } else { + } else { $this->salt = ''; } - + shuffle($config['spam']['hidden_input_names']); - + $input_count = mt_rand($config['spam']['hidden_inputs_min'], $config['spam']['hidden_inputs_max']); $hidden_input_names_x = 0; - + for ($x = 0; $x < $input_count ; $x++) { if ($hidden_input_names_x === false || mt_rand(0, 2) == 0) { // Use an obscure name @@ -83,7 +83,7 @@ class AntiBot { if ($hidden_input_names_x >= count($config['spam']['hidden_input_names'])) $hidden_input_names_x = false; } - + if (mt_rand(0, 2) == 0) { // Value must be null $this->inputs[$name] = ''; @@ -96,16 +96,16 @@ class AntiBot { } } } - + public static function space() { if (mt_rand(0, 3) != 0) return ' '; return str_repeat(' ', mt_rand(1, 3)); } - + public function html($count = false) { global $config; - + $elements = array( '', '', @@ -119,13 +119,13 @@ class AntiBot { '', '' ); - + $html = ''; - + if ($count === false) { - $count = mt_rand(1, abs(count($this->inputs) / 15) + 1); + $count = mt_rand(1, (int)abs(count($this->inputs) / 15) + 1); } - + if ($count === true) { // all elements $inputs = array_slice($this->inputs, $this->index); @@ -133,7 +133,7 @@ class AntiBot { $inputs = array_slice($this->inputs, $this->index, $count); } $this->index += count($inputs); - + foreach ($inputs as $name => $value) { $element = false; while (!$element) { @@ -146,37 +146,37 @@ class AntiBot { $element = false; } } - + $element = str_replace('%name%', utf8tohtml($name), $element); - + if (mt_rand(0, 2) == 0) $value = $this->make_confusing($value); else $value = utf8tohtml($value); - + if (strpos($element, 'textarea') === false) $value = str_replace('"', '"', $value); - + $element = str_replace('%value%', $value, $element); - + $html .= $element; } - + return $html; } - + public function reset() { $this->index = 0; } - + public function hash() { global $config; - + // This is the tricky part: create a hash to validate it after // First, sort the keys in alphabetical order (A-Z) $inputs = $this->inputs; ksort($inputs); - + $hash = ''; // Iterate through each input foreach ($inputs as $name => $value) { @@ -184,7 +184,7 @@ class AntiBot { } // Add a salt to the hash $hash .= $config['cookies']['salt']; - + // Use SHA1 for the hash return sha1($hash . $this->salt); } From 9fbc816205af457773b0f42ea18f6fb9605885be Mon Sep 17 00:00:00 2001 From: Zankaria Date: Thu, 22 Aug 2024 00:09:28 +0200 Subject: [PATCH 245/336] templates: bust all js caches with resource_version --- templates/header.html | 2 +- templates/mod/ban_list.html | 12 ++++++------ templates/mod/pages.html | 4 ++-- templates/mod/recent_posts.html | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/templates/header.html b/templates/header.html index b2c0f054..b928ff4a 100644 --- a/templates/header.html +++ b/templates/header.html @@ -17,7 +17,7 @@ {% for javascript in config.additional_javascript %}{% endfor %} {% endif %} {% if mod %} - + {% endif %} {% endif %} {% if config.captcha.provider == 'recaptcha' %} diff --git a/templates/mod/ban_list.html b/templates/mod/ban_list.html index f039350b..4a4586f7 100644 --- a/templates/mod/ban_list.html +++ b/templates/mod/ban_list.html @@ -1,9 +1,9 @@ - - - - - + + + + + @@ -32,7 +32,7 @@ - + {% if token_json %} diff --git a/templates/mod/pages.html b/templates/mod/pages.html index c2395c02..f6be9487 100644 --- a/templates/mod/pages.html +++ b/templates/mod/pages.html @@ -1,4 +1,4 @@ - +

    {% if board %} @@ -27,7 +27,7 @@ -
    {% trans %}URL{% endtrans %}{% trans %}Title{% endtrans %}
    + diff --git a/templates/mod/recent_posts.html b/templates/mod/recent_posts.html index d5e5d83c..55f854b5 100644 --- a/templates/mod/recent_posts.html +++ b/templates/mod/recent_posts.html @@ -1,4 +1,4 @@ - + {% if not posts|length %}

    ({% trans 'There are no active posts.' %})

    {% else %} From fcf5c3d73afaddc24197b2353718be18f0bb462a Mon Sep 17 00:00:00 2001 From: Zankaria Date: Thu, 22 Aug 2024 00:15:51 +0200 Subject: [PATCH 246/336] templates: bust all css caches with resource_version --- templates/header.html | 6 +++--- templates/mod/ban_list.html | 4 ++-- templates/themes/recent/recent.html | 16 ++++++++-------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/templates/header.html b/templates/header.html index b928ff4a..335f572e 100644 --- a/templates/header.html +++ b/templates/header.html @@ -3,9 +3,9 @@ {% if config.meta_keywords %}{% endif %} -{% if config.default_stylesheet.1 != '' %}{% endif %} -{% if config.font_awesome %}{% endif %} -{% if config.country_flags_condensed %}{% endif %} +{% if config.default_stylesheet.1 != '' %}{% endif %} +{% if config.font_awesome %}{% endif %} +{% if config.country_flags_condensed %}{% endif %} - - + +
    {% if token %} diff --git a/templates/themes/recent/recent.html b/templates/themes/recent/recent.html index 2a4510f8..9fce7ab8 100644 --- a/templates/themes/recent/recent.html +++ b/templates/themes/recent/recent.html @@ -4,11 +4,11 @@ {{ settings.title }} - - + + {% if config.url_favicon %}{% endif %} - {% if config.default_stylesheet.1 != '' %}{% endif %} - {% if config.font_awesome %}{% endif %} + {% if config.default_stylesheet.1 != '' %}{% endif %} + {% if config.font_awesome %}{% endif %} {% include 'header.html' %} @@ -17,7 +17,7 @@

    {{ settings.title }}

    {{ settings.subtitle }}
    - +

    Recent Images

    @@ -36,7 +36,7 @@
      {% for post in recent_posts %}
    • - {{ post.board_name }}: + {{ post.board_name }}: {{ post.snippet }} @@ -53,11 +53,11 @@
    - +
    {% include 'footer.html' %} - + {% endapply %} From 3c5484a7c273aaffdf025fc293383ce5f960d3d0 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sat, 24 Aug 2024 01:36:49 +0200 Subject: [PATCH 247/336] main.js: load styles with dyanamically provided resource version. This is done because: - If the version is updated before the rebuild, someone might cache the old version in the meanwhile. - One could not rebuild javascript before updating the version. Now it's possible. --- templates/main.js | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/templates/main.js b/templates/main.js index 13c80c33..82c87094 100644 --- a/templates/main.js +++ b/templates/main.js @@ -141,7 +141,33 @@ function changeStyle(styleName, link) { x.appendChild(s); } - document.getElementById('stylesheet').href = styles[styleName]; + let mainStylesheetElement = document.getElementById('stylesheet'); + let userStylesheetElement = document.getElementById('stylesheet-user'); + + // Override main stylesheet with the user selected one. + if (!userStylesheetElement) { + userStylesheetElement = document.createElement('link'); + userStylesheetElement.rel = 'stylesheet'; + userStylesheetElement.media = 'none'; + userStylesheetElement.type = 'text/css'; + userStylesheetElement.id = 'stylesheet'; + let x = document.getElementsByTagName('head')[0]; + x.appendChild(userStylesheetElement); + } + + // When the new one is loaded, disable the old one + userStylesheetElement.onload = function() { + this.media = 'all'; + mainStylesheetElement.media = 'none'; + } + + let style = styles[styleName]; + if (style !== '') { + // Add the version of the resource if the style is not the embedded one. + style += `?v=${resourceVersion}`; + } + + document.getElementById('stylesheet').href = style; selectedstyle = styleName; if (document.getElementsByClassName('styles').length != 0) { @@ -162,6 +188,7 @@ function changeStyle(styleName, link) { {% endverbatim %} +var resourceVersion = document.currentScript.getAttribute('data-resource-version'); {% if config.stylesheets_board %} {% verbatim %} From 6b60f841d4cde117431cd285644d896866af2568 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sat, 24 Aug 2024 01:43:31 +0200 Subject: [PATCH 248/336] template: supply data-resource-version to main.js --- templates/header.html | 2 +- templates/mod/ban_list.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/header.html b/templates/header.html index 335f572e..51a9593c 100644 --- a/templates/header.html +++ b/templates/header.html @@ -12,7 +12,7 @@ var modRoot = "{{ config.root }}" + (inMod ? "mod.php?/" : ""); {% if not nojavascript %} - + {% if not config.additional_javascript_compile %} {% for javascript in config.additional_javascript %}{% endfor %} {% endif %} diff --git a/templates/mod/ban_list.html b/templates/mod/ban_list.html index 9115ddb3..ee8753f7 100644 --- a/templates/mod/ban_list.html +++ b/templates/mod/ban_list.html @@ -1,4 +1,4 @@ - + From cb6d6f13dde4a511bfdcfe9c3326c077788dd008 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Thu, 19 Sep 2024 22:51:01 +0200 Subject: [PATCH 249/336] main.js: use let --- templates/main.js | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/templates/main.js b/templates/main.js index 13c80c33..53150e2f 100644 --- a/templates/main.js +++ b/templates/main.js @@ -71,7 +71,7 @@ var datelocale = function alert(a, do_confirm, confirm_ok_action, confirm_cancel_action) { - var handler, div, bg, closebtn, okbtn; + let handler, div, bg, closebtn, okbtn; let close = function() { handler.fadeOut(400, function() { handler.remove(); }); return false; @@ -133,11 +133,11 @@ function changeStyle(styleName, link) { {% verbatim %} if (!document.getElementById('stylesheet')) { - var s = document.createElement('link'); + let s = document.createElement('link'); s.rel = 'stylesheet'; s.type = 'text/css'; s.id = 'stylesheet'; - var x = document.getElementsByTagName('head')[0]; + let x = document.getElementsByTagName('head')[0]; x.appendChild(s); } @@ -145,8 +145,8 @@ function changeStyle(styleName, link) { selectedstyle = styleName; if (document.getElementsByClassName('styles').length != 0) { - var styleLinks = document.getElementsByClassName('styles')[0].childNodes; - for (var i = 0; i < styleLinks.length; i++) { + let styleLinks = document.getElementsByClassName('styles')[0].childNodes; + for (let i = 0; i < styleLinks.length; i++) { styleLinks[i].className = ''; } } @@ -171,7 +171,7 @@ function changeStyle(styleName, link) { var stylesheet_choices = JSON.parse(localStorage.board_stylesheets); if (board_name && stylesheet_choices[board_name]) { - for (var styleName in styles) { + for (let styleName in styles) { if (styleName == stylesheet_choices[board_name]) { changeStyle(styleName); break; @@ -182,7 +182,7 @@ function changeStyle(styleName, link) { {% else %} {% verbatim %} if (localStorage.stylesheet) { - for (var styleName in styles) { + for (let styleName in styles) { if (styleName == localStorage.stylesheet) { changeStyle(styleName); break; @@ -194,11 +194,11 @@ function changeStyle(styleName, link) { {% verbatim %} function initStyleChooser() { - var newElement = document.createElement('div'); + let newElement = document.createElement('div'); newElement.className = 'styles'; for (styleName in styles) { - var style = document.createElement('a'); + let style = document.createElement('a'); style.innerHTML = '[' + styleName + ']'; style.onclick = function() { changeStyle(this.innerHTML.substring(1, this.innerHTML.length - 1), this); @@ -259,8 +259,7 @@ function highlightReply(id) { } let divs = document.getElementsByTagName('div'); - for (var i = 0; i < divs.length; i++) - { + for (let i = 0; i < divs.length; i++) { if (divs[i].className.indexOf('post') != -1) { divs[i].className = divs[i].className.replace(/highlighted/, ''); } @@ -435,7 +434,7 @@ onReady(init); {% if config.google_analytics %}{% verbatim %} -var _gaq = _gaq || [];_gaq.push(['_setAccount', '{% endverbatim %}{{ config.google_analytics }}{% verbatim %}']);{% endverbatim %}{% if config.google_analytics_domain %}{% verbatim %}_gaq.push(['_setDomainName', '{% endverbatim %}{{ config.google_analytics_domain }}{% verbatim %}']){% endverbatim %}{% endif %}{% if not config.google_analytics_domain %}{% verbatim %}_gaq.push(['_setDomainName', 'none']){% endverbatim %}{% endif %}{% verbatim %};_gaq.push(['_trackPageview']);(function() {var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;ga.src = ('https:' == document.location.protocol ? 'https://' : 'http://') + 'stats.g.doubleclick.net/dc.js';var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);})();{% endverbatim %}{% endif %} +var _gaq = _gaq || [];_gaq.push(['_setAccount', '{% endverbatim %}{{ config.google_analytics }}{% verbatim %}']);{% endverbatim %}{% if config.google_analytics_domain %}{% verbatim %}_gaq.push(['_setDomainName', '{% endverbatim %}{{ config.google_analytics_domain }}{% verbatim %}']){% endverbatim %}{% endif %}{% if not config.google_analytics_domain %}{% verbatim %}_gaq.push(['_setDomainName', 'none']){% endverbatim %}{% endif %}{% verbatim %};_gaq.push(['_trackPageview']);(function() {var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;ga.src = ('https:' == document.location.protocol ? 'https://' : 'http://') + 'stats.g.doubleclick.net/dc.js';let s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);})();{% endverbatim %}{% endif %} {% if config.statcounter_project and config.statcounter_security %} var sc = document.createElement('script'); From 4332b703630ab73a6e789861dcfa9f02fe64055f Mon Sep 17 00:00:00 2001 From: Zankaria Date: Thu, 19 Sep 2024 22:51:31 +0200 Subject: [PATCH 250/336] quote-selection.js: trim --- js/quote-selection.js | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/js/quote-selection.js b/js/quote-selection.js index 0722c0b1..6f609319 100644 --- a/js/quote-selection.js +++ b/js/quote-selection.js @@ -16,7 +16,7 @@ $(document).ready(function(){ if (!window.getSelection) return; - + $.fn.selectRange = function(start, end) { return this.each(function() { if (this.setSelectionRange) { @@ -31,11 +31,11 @@ $(document).ready(function(){ } }); }; - + var altKey = false; var ctrlKey = false; var metaKey = false; - + $(document).keyup(function(e) { if (e.keyCode == 18) altKey = false; @@ -44,7 +44,7 @@ $(document).ready(function(){ else if (e.keyCode == 91) metaKey = false; }); - + $(document).keydown(function(e) { if (e.altKey) altKey = true; @@ -52,51 +52,51 @@ $(document).ready(function(){ ctrlKey = true; else if (e.metaKey) metaKey = true; - + if (altKey || ctrlKey || metaKey) { // console.log('CTRL/ALT/Something used. Ignoring'); return; } - + if (e.keyCode < 48 || e.keyCode > 90) return; - + var selection = window.getSelection(); var $post = $(selection.anchorNode).parents('.post'); if ($post.length == 0) { // console.log('Start of selection was not post div', $(selection.anchorNode).parent()); return; } - + var postID = $post.find('.post_no:eq(1)').text(); - + if (postID != $(selection.focusNode).parents('.post').find('.post_no:eq(1)').text()) { // console.log('Selection left post div', $(selection.focusNode).parent()); return; } - + ; var selectedText = selection.toString(); // console.log('Selected text: ' + selectedText.replace(/\n/g, '\\n').replace(/\r/g, '\\r')); - + if ($('body').hasClass('debug')) alert(selectedText); - + if (selectedText.length == 0) return; - + var body = $('textarea#body')[0]; - + var last_quote = body.value.match(/[\S.]*(^|[\S\s]*)>>(\d+)/); if (last_quote) last_quote = last_quote[2]; - + /* to solve some bugs on weird browsers, we need to replace \r\n with \n and then undo that after */ var quote = (last_quote != postID ? '>>' + postID + '\r\n' : '') + $.trim(selectedText).replace(/\r\n/g, '\n').replace(/^/mg, '>').replace(/\n/g, '\r\n') + '\r\n'; - + // console.log('Deselecting text'); selection.removeAllRanges(); - + if (document.selection) { // IE body.focus(); @@ -107,12 +107,12 @@ $(document).ready(function(){ // Mozilla var start = body.selectionStart; var end = body.selectionEnd; - + if (!body.value.substring(0, start).match(/(^|\n)$/)) { quote = '\r\n\r\n' + quote; } - - body.value = body.value.substring(0, start) + quote + body.value.substring(end, body.value.length); + + body.value = body.value.substring(0, start) + quote + body.value.substring(end, body.value.length); $(body).selectRange(start + quote.length, start + quote.length); } else { // ??? @@ -121,4 +121,3 @@ $(document).ready(function(){ } }); }); - From fd309443ea0a4e4e37033dbf042aa9f7f445dd7a Mon Sep 17 00:00:00 2001 From: Zankaria Date: Thu, 19 Sep 2024 22:54:04 +0200 Subject: [PATCH 251/336] js: drop IE support --- js/quote-selection.js | 9 +-------- templates/main.js | 7 +------ 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/js/quote-selection.js b/js/quote-selection.js index 6f609319..ad7a76ed 100644 --- a/js/quote-selection.js +++ b/js/quote-selection.js @@ -97,14 +97,7 @@ $(document).ready(function(){ // console.log('Deselecting text'); selection.removeAllRanges(); - if (document.selection) { - // IE - body.focus(); - var sel = document.selection.createRange(); - sel.text = quote; - body.focus(); - } else if (body.selectionStart || body.selectionStart == '0') { - // Mozilla + if (body.selectionStart || body.selectionStart == '0') { var start = body.selectionStart; var end = body.selectionEnd; diff --git a/templates/main.js b/templates/main.js index 53150e2f..96d2fb3f 100644 --- a/templates/main.js +++ b/templates/main.js @@ -307,12 +307,7 @@ function citeReply(id, with_link) { return false; } - if (document.selection) { - // IE - textarea.focus(); - let sel = document.selection.createRange(); - sel.text = '>>' + id + '\n'; - } else if (textarea.selectionStart || textarea.selectionStart == '0') { + if (textarea.selectionStart || textarea.selectionStart == '0') { let start = textarea.selectionStart; let end = textarea.selectionEnd; textarea.value = textarea.value.substring(0, start) + '>>' + id + '\n' + textarea.value.substring(end, textarea.value.length); From bdd7090e7588f9b62919e876817dc442455b192d Mon Sep 17 00:00:00 2001 From: Zankaria Date: Thu, 19 Sep 2024 22:56:23 +0200 Subject: [PATCH 252/336] quote-selection.js: format --- js/quote-selection.js | 70 +++++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/js/quote-selection.js b/js/quote-selection.js index ad7a76ed..ba67fa7c 100644 --- a/js/quote-selection.js +++ b/js/quote-selection.js @@ -10,12 +10,12 @@ * Usage: * $config['additional_javascript'][] = 'js/jquery.min.js'; * $config['additional_javascript'][] = 'js/quote-selection.js'; - * */ -$(document).ready(function(){ - if (!window.getSelection) +$(document).ready(function() { + if (!window.getSelection) { return; + } $.fn.selectRange = function(start, end) { return this.each(function() { @@ -23,7 +23,7 @@ $(document).ready(function(){ this.focus(); this.setSelectionRange(start, end); } else if (this.createTextRange) { - var range = this.createTextRange(); + let range = this.createTextRange(); range.collapse(true); range.moveEnd('character', end); range.moveStart('character', start); @@ -32,74 +32,74 @@ $(document).ready(function(){ }); }; - var altKey = false; - var ctrlKey = false; - var metaKey = false; + let altKey = false; + let ctrlKey = false; + let metaKey = false; $(document).keyup(function(e) { - if (e.keyCode == 18) + if (e.keyCode == 18) { altKey = false; - else if (e.keyCode == 17) + } else if (e.keyCode == 17) { ctrlKey = false; - else if (e.keyCode == 91) + } else if (e.keyCode == 91) { metaKey = false; + } }); $(document).keydown(function(e) { - if (e.altKey) + if (e.altKey) { altKey = true; - else if (e.ctrlKey) + } else if (e.ctrlKey) { ctrlKey = true; - else if (e.metaKey) + } else if (e.metaKey) { metaKey = true; + } if (altKey || ctrlKey || metaKey) { - // console.log('CTRL/ALT/Something used. Ignoring'); return; } - if (e.keyCode < 48 || e.keyCode > 90) - return; - - var selection = window.getSelection(); - var $post = $(selection.anchorNode).parents('.post'); - if ($post.length == 0) { - // console.log('Start of selection was not post div', $(selection.anchorNode).parent()); + if (e.keyCode < 48 || e.keyCode > 90) { return; } - var postID = $post.find('.post_no:eq(1)').text(); + let selection = window.getSelection(); + let post = $(selection.anchorNode).parents('.post'); + if (post.length == 0) { + return; + } + + let postID = post.find('.post_no:eq(1)').text(); if (postID != $(selection.focusNode).parents('.post').find('.post_no:eq(1)').text()) { - // console.log('Selection left post div', $(selection.focusNode).parent()); return; } - ; - var selectedText = selection.toString(); - // console.log('Selected text: ' + selectedText.replace(/\n/g, '\\n').replace(/\r/g, '\\r')); + let selectedText = selection.toString(); - if ($('body').hasClass('debug')) + if ($('body').hasClass('debug')) { alert(selectedText); + } - if (selectedText.length == 0) + if (selectedText.length == 0) { return; + } - var body = $('textarea#body')[0]; + let body = $('textarea#body')[0]; - var last_quote = body.value.match(/[\S.]*(^|[\S\s]*)>>(\d+)/); - if (last_quote) + let last_quote = body.value.match(/[\S.]*(^|[\S\s]*)>>(\d+)/); + if (last_quote) { last_quote = last_quote[2]; + } /* to solve some bugs on weird browsers, we need to replace \r\n with \n and then undo that after */ - var quote = (last_quote != postID ? '>>' + postID + '\r\n' : '') + $.trim(selectedText).replace(/\r\n/g, '\n').replace(/^/mg, '>').replace(/\n/g, '\r\n') + '\r\n'; + let quote = (last_quote != postID ? '>>' + postID + '\r\n' : '') + $.trim(selectedText).replace(/\r\n/g, '\n').replace(/^/mg, '>').replace(/\n/g, '\r\n') + '\r\n'; - // console.log('Deselecting text'); selection.removeAllRanges(); if (body.selectionStart || body.selectionStart == '0') { - var start = body.selectionStart; - var end = body.selectionEnd; + let start = body.selectionStart; + let end = body.selectionEnd; if (!body.value.substring(0, start).match(/(^|\n)$/)) { quote = '\r\n\r\n' + quote; From 0e8aeca4af75661ca07b813ee1ee7053ea88a8c0 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Thu, 19 Sep 2024 23:09:02 +0200 Subject: [PATCH 253/336] anti-bot.php: format --- inc/anti-bot.php | 56 ++++++++++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/inc/anti-bot.php b/inc/anti-bot.php index aa0eeb99..cfa05a32 100644 --- a/inc/anti-bot.php +++ b/inc/anti-bot.php @@ -6,26 +6,31 @@ defined('TINYBOARD') or exit; -$hidden_inputs_twig = array(); +$hidden_inputs_twig = []; class AntiBot { - public $salt, $inputs = array(), $index = 0; + public $salt; + public $inputs = []; + public $index = 0; public static function randomString($length, $uppercase = false, $special_chars = false, $unicode_chars = false) { $chars = 'abcdefghijklmnopqrstuvwxyz0123456789'; - if ($uppercase) + if ($uppercase) { $chars .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; - if ($special_chars) + } + if ($special_chars) { $chars .= ' ~!@#$%^&*()_+,./;\'[]\\{}|:<>?=-` '; + } if ($unicode_chars) { $len = strlen($chars) / 10; - for ($n = 0; $n < $len; $n++) + for ($n = 0; $n < $len; $n++) { $chars .= mb_convert_encoding('&#' . mt_rand(0x2600, 0x26FF) . ';', 'UTF-8', 'HTML-ENTITIES'); + } } $chars = preg_split('//u', $chars, -1, PREG_SPLIT_NO_EMPTY); - $ch = array(); + $ch = []; // fill up $ch until we reach $length while (count($ch) < $length) { @@ -36,8 +41,9 @@ class AntiBot { break; } shuffle($keys); - foreach ($keys as $key) + foreach ($keys as $key) { $ch[] = $chars[$key]; + } } $chars = $ch; @@ -49,20 +55,21 @@ class AntiBot { $chars = preg_split('//u', $string, -1, PREG_SPLIT_NO_EMPTY); foreach ($chars as &$c) { - if (mt_rand(0, 3) != 0) + if (mt_rand(0, 3) != 0) { $c = utf8tohtml($c); - else - $c = mb_encode_numericentity($c, array(0, 0xffff, 0, 0xffff), 'UTF-8'); + } else { + $c = mb_encode_numericentity($c, [ 0, 0xffff, 0, 0xffff ], 'UTF-8'); + } } return implode('', $chars); } - public function __construct(array $salt = array()) { + public function __construct(array $salt = []) { global $config; if (!empty($salt)) { - // create a salted hash of the "extra salt" + // Create a salted hash of the "extra salt" $this->salt = implode(':', $salt); } else { $this->salt = ''; @@ -80,8 +87,9 @@ class AntiBot { } else { // Use a pre-defined confusing name $name = $config['spam']['hidden_input_names'][$hidden_input_names_x++]; - if ($hidden_input_names_x >= count($config['spam']['hidden_input_names'])) + if ($hidden_input_names_x >= count($config['spam']['hidden_input_names'])) { $hidden_input_names_x = false; + } } if (mt_rand(0, 2) == 0) { @@ -98,15 +106,14 @@ class AntiBot { } public static function space() { - if (mt_rand(0, 3) != 0) + if (mt_rand(0, 3) != 0) { return ' '; + } return str_repeat(' ', mt_rand(1, 3)); } public function html($count = false) { - global $config; - - $elements = array( + $elements = [ '', '', '', @@ -118,7 +125,7 @@ class AntiBot { '
    ', '', '' - ); + ]; $html = ''; @@ -127,7 +134,7 @@ class AntiBot { } if ($count === true) { - // all elements + // All elements $inputs = array_slice($this->inputs, $this->index); } else { $inputs = array_slice($this->inputs, $this->index, $count); @@ -139,8 +146,9 @@ class AntiBot { while (!$element) { $element = $elements[array_rand($elements)]; $element = str_replace(' ', self::space(), $element); - if (mt_rand(0, 5) == 0) + if (mt_rand(0, 5) == 0) { $element = str_replace('>', self::space() . '>', $element); + } if (strpos($element, 'textarea') !== false && $value == '') { // There have been some issues with mobile web browsers and empty '; + $page['pm'] = create_pm_header(); echo Element($config['file_page_template'], $page); exit; } @@ -2869,7 +2873,13 @@ function mod_edit_page(Context $ctx, $id) { $fn = (isset($board['uri']) ? ($board['uri'] . '/') : '') . $page['name'] . '.html'; $body = "
    $write
    "; - $html = Element($config['file_page_template'], array('config' => $config, 'boardlist' => createBoardlist(), 'body' => $body, 'title' => utf8tohtml($page['title']))); + $html = Element($config['file_page_template'], [ + 'config' => $config, + 'boardlist' => createBoardlist(), + 'body' => $body, + 'title' => utf8tohtml($page['title']), + 'pm' => create_pm_header() + ]); file_write($fn, $html); } diff --git a/inc/template.php b/inc/template.php index 4f30e00c..17df316b 100644 --- a/inc/template.php +++ b/inc/template.php @@ -34,10 +34,6 @@ function Element($templateFile, array $options) { if (!$twig) load_twig(); - if (function_exists('create_pm_header') && ((isset($options['mod']) && $options['mod']) || isset($options['__mod'])) && !preg_match('!^mod/!', $templateFile)) { - $options['pm'] = create_pm_header(); - } - if (isset($options['body']) && $config['debug']) { $_debug = $debug; From 6c6ec65b021188f56212246686c871ff26b298fe Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sun, 6 Oct 2024 10:40:54 +0200 Subject: [PATCH 275/336] pages.php: extract $mod array --- inc/mod/pages.php | 465 +++++++++++++++++++++++++++++++++------------- 1 file changed, 332 insertions(+), 133 deletions(-) diff --git a/inc/mod/pages.php b/inc/mod/pages.php index b9ea20ae..c03e1a5e 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -18,28 +18,34 @@ function _link_or_copy(string $target, string $link): bool { return true; } -function mod_page($title, $template, $args, $subtitle = false) { - global $config, $mod; +function mod_page($title, $template, $args, $mod, $subtitle = false) { + global $config; - echo Element($config['file_page_template'], array( + $options = [ 'config' => $config, 'mod' => $mod, 'hide_dashboard_link' => $template == $config['file_mod_dashboard'], 'title' => $title, 'subtitle' => $subtitle, 'boardlist' => createBoardlist($mod), - 'pm' => create_pm_header(), - 'body' => Element($template, - array_merge( - array('config' => $config, 'mod' => $mod), - $args - ) + 'body' => Element( + $template, + array_merge( + [ 'config' => $config, 'mod' => $mod ], + $args ) ) - ); + ]; + + if ($mod) { + $options['pm'] = create_pm_header(); + } + + echo Element($config['file_page_template'], $options); } function mod_login(Context $ctx, $redirect = false) { + global $mod; $config = $ctx->get('config'); $args = []; @@ -73,12 +79,21 @@ function mod_login(Context $ctx, $redirect = false) { if (isset($_POST['username'])) $args['username'] = $_POST['username']; - mod_page(_('Login'), $config['file_mod_login'], $args); + mod_page(_('Login'), $config['file_mod_login'], $args, $mod); } function mod_confirm(Context $ctx, $request) { + global $mod; $config = $ctx->get('config'); - mod_page(_('Confirm action'), $config['file_mod_confim'], array('request' => $request, 'token' => make_secure_link_token($request))); + mod_page( + _('Confirm action'), + $config['file_mod_confim'], + [ + 'request' => $request, + 'token' => make_secure_link_token($request) + ], + $mod + ); } function mod_logout(Context $ctx) { @@ -180,7 +195,7 @@ function mod_dashboard(Context $ctx) { $args['logout_token'] = make_secure_link_token('logout'); - mod_page(_('Dashboard'), $config['file_mod_dashboard'], $args); + mod_page(_('Dashboard'), $config['file_mod_dashboard'], $args, $mod); } function mod_search_redirect(Context $ctx) { @@ -207,7 +222,7 @@ function mod_search_redirect(Context $ctx) { } function mod_search(Context $ctx, $type, $search_query_escaped, $page_no = 1) { - global $pdo, $config; + global $pdo, $config, $mod; if (!hasPermission($config['mod']['search'])) error($config['error']['noaccess']); @@ -352,17 +367,22 @@ function mod_search(Context $ctx, $type, $search_query_escaped, $page_no = 1) { // $results now contains the search results - mod_page(_('Search results'), $config['file_mod_search_results'], array( - 'search_type' => $type, - 'search_query' => $search_query, - 'search_query_escaped' => $search_query_escaped, - 'result_count' => $result_count, - 'results' => $results - )); + mod_page( + _('Search results'), + $config['file_mod_search_results'], + [ + 'search_type' => $type, + 'search_query' => $search_query, + 'search_query_escaped' => $search_query_escaped, + 'result_count' => $result_count, + 'results' => $results + ], + $mod + ); } function mod_edit_board(Context $ctx, $boardName) { - global $board, $config; + global $board, $config, $mod; if (!openBoard($boardName)) error($config['error']['noboard']); @@ -456,15 +476,20 @@ function mod_edit_board(Context $ctx, $boardName) { header('Location: ?/', true, $config['redirect_http']); } else { - mod_page(sprintf('%s: ' . $config['board_abbreviation'], _('Edit board'), $board['uri']), $config['file_mod_board'], array( - 'board' => $board, - 'token' => make_secure_link_token('edit/' . $board['uri']) - )); + mod_page( + sprintf('%s: ' . $config['board_abbreviation'], _('Edit board'), $board['uri']), + $config['file_mod_board'], + [ + 'board' => $board, + 'token' => make_secure_link_token('edit/' . $board['uri']) + ], + $mod + ); } } function mod_new_board(Context $ctx) { - global $board; + global $board, $mod; $config = $ctx->get('config'); if (!hasPermission($config['mod']['newboard'])) @@ -530,7 +555,15 @@ function mod_new_board(Context $ctx) { header('Location: ?/' . $board['uri'] . '/' . $config['file_index'], true, $config['redirect_http']); } - mod_page(_('New board'), $config['file_mod_board'], array('new' => true, 'token' => make_secure_link_token('new-board'))); + mod_page( + _('New board'), + $config['file_mod_board'], + [ + 'new' => true, + 'token' => make_secure_link_token('new-board') + ], + $mod + ); } function mod_noticeboard(Context $ctx, $page_no = 1) { @@ -582,11 +615,16 @@ function mod_noticeboard(Context $ctx, $page_no = 1) { $query->execute() or error(db_error($query)); $count = $query->fetchColumn(); - mod_page(_('Noticeboard'), $config['file_mod_noticeboard'], array( - 'noticeboard' => $noticeboard, - 'count' => $count, - 'token' => make_secure_link_token('noticeboard') - )); + mod_page( + _('Noticeboard'), + $config['file_mod_noticeboard'], + [ + 'noticeboard' => $noticeboard, + 'count' => $count, + 'token' => make_secure_link_token('noticeboard') + ], + $mod + ); } function mod_noticeboard_delete(Context $ctx, $id) { @@ -652,7 +690,16 @@ function mod_news(Context $ctx, $page_no = 1) { $query->execute() or error(db_error($query)); $count = $query->fetchColumn(); - mod_page(_('News'), $config['file_mod_news'], array('news' => $news, 'count' => $count, 'token' => make_secure_link_token('edit_news'))); + mod_page( + _('News'), + $config['file_mod_news'], + [ + 'news' => $news, + 'count' => $count, + 'token' => make_secure_link_token('edit_news') + ], + $mod + ); } function mod_news_delete(Context $ctx, $id) { @@ -671,6 +718,7 @@ function mod_news_delete(Context $ctx, $id) { } function mod_log(Context $ctx, $page_no = 1) { + global $mod; $config = $ctx->get('config'); if ($page_no < 1) @@ -692,10 +740,11 @@ function mod_log(Context $ctx, $page_no = 1) { $query->execute() or error(db_error($query)); $count = $query->fetchColumn(); - mod_page(_('Moderation log'), $config['file_mod_log'], array('logs' => $logs, 'count' => $count)); + mod_page(_('Moderation log'), $config['file_mod_log'], [ 'logs' => $logs, 'count' => $count ], $mod); } function mod_user_log(Context $ctx, $username, $page_no = 1) { + global $mod; $config = $ctx->get('config'); if ($page_no < 1) @@ -719,10 +768,11 @@ function mod_user_log(Context $ctx, $username, $page_no = 1) { $query->execute() or error(db_error($query)); $count = $query->fetchColumn(); - mod_page(_('Moderation log'), $config['file_mod_log'], array('logs' => $logs, 'count' => $count, 'username' => $username)); + mod_page(_('Moderation log'), $config['file_mod_log'], [ 'logs' => $logs, 'count' => $count, 'username' => $username ], $mod); } function mod_board_log(Context $ctx, $board, $page_no = 1, $hide_names = false, $public = false) { + global $mod; $config = $ctx->get('config'); if ($page_no < 1) @@ -755,7 +805,18 @@ function mod_board_log(Context $ctx, $board, $page_no = 1, $hide_names = false, $query->execute() or error(db_error($query)); $count = $query->fetchColumn(); - mod_page(_('Board log'), $config['file_mod_log'], array('logs' => $logs, 'count' => $count, 'board' => $board, 'hide_names' => $hide_names, 'public' => $public)); + mod_page( + _('Board log'), + $config['file_mod_log'], + [ + 'logs' => $logs, + 'count' => $count, + 'board' => $board, + 'hide_names' => $hide_names, + 'public' => $public + ], + $mod + ); } function mod_view_catalog(Context $ctx, $boardName) { @@ -936,10 +997,11 @@ function mod_ip(Context $ctx, $cip) { $args['security_token'] = make_secure_link_token('IP/' . $cip); - mod_page(sprintf('%s: %s', _('IP'), htmlspecialchars($cip)), $config['file_mod_view_ip'], $args, $args['hostname']); + mod_page(sprintf('%s: %s', _('IP'), htmlspecialchars($cip)), $config['file_mod_view_ip'], $args, $mod, $args['hostname']); } function mod_edit_ban(Context $ctx, $ban_id) { + global $mod; $config = $ctx->get('config'); if (!hasPermission($config['mod']['edit_ban'])) @@ -985,18 +1047,18 @@ function mod_edit_ban(Context $ctx, $ban_id) { $args['token'] = make_secure_link_token('edit_ban/' . $ban_id); - mod_page(_('Edit ban'), 'mod/edit_ban.html', $args); - + mod_page(_('Edit ban'), 'mod/edit_ban.html', $args, $mod); } function mod_ban(Context $ctx) { + global $mod; $config = $ctx->get('config'); if (!hasPermission($config['mod']['ban'])) error($config['error']['noaccess']); if (!isset($_POST['ip'], $_POST['reason'], $_POST['length'], $_POST['board'])) { - mod_page(_('New ban'), $config['file_mod_ban_form'], array('token' => make_secure_link_token('ban'))); + mod_page(_('New ban'), $config['file_mod_ban_form'], [ 'token' => make_secure_link_token('ban') ], $mod); return; } @@ -1035,12 +1097,17 @@ function mod_bans(Context $ctx) { return; } - mod_page(_('Ban list'), $config['file_mod_ban_list'], array( - 'mod' => $mod, - 'boards' => json_encode($mod['boards']), - 'token' => make_secure_link_token('bans'), - 'token_json' => make_secure_link_token('bans.json') - )); + mod_page( + _('Ban list'), + $config['file_mod_ban_list'], + [ + 'mod' => $mod, + 'boards' => json_encode($mod['boards']), + 'token' => make_secure_link_token('bans'), + 'token_json' => make_secure_link_token('bans.json') + ], + $mod + ); } function mod_bans_json(Context $ctx) { @@ -1057,7 +1124,7 @@ function mod_bans_json(Context $ctx) { } function mod_ban_appeals(Context $ctx) { - global $board; + global $board, $mod; $config = $ctx->get('config'); if (!hasPermission($config['mod']['view_ban_appeals'])) @@ -1127,10 +1194,15 @@ function mod_ban_appeals(Context $ctx) { } } - mod_page(_('Ban appeals'), $config['file_mod_ban_appeals'], array( - 'ban_appeals' => $ban_appeals, - 'token' => make_secure_link_token('ban-appeals') - )); + mod_page( + _('Ban appeals'), + $config['file_mod_ban_appeals'], + [ + 'ban_appeals' => $ban_appeals, + 'token' => make_secure_link_token('ban-appeals') + ], + $mod + ); } function mod_lock(Context $ctx, $board, $unlock, $post) { @@ -1234,7 +1306,7 @@ function mod_bumplock(Context $ctx, $board, $unbumplock, $post) { } function mod_move_reply(Context $ctx, $originBoard, $postID) { - global $board, $config; + global $board, $config, $mod; if (!openBoard($originBoard)) error($config['error']['noboard']); @@ -1323,20 +1395,27 @@ function mod_move_reply(Context $ctx, $originBoard, $postID) { // redirect header('Location: ?/' . sprintf($config['board_path'], $board['uri']) . $config['dir']['res'] . link_for($post) . '#' . $newID, true, $config['redirect_http']); } - else { $boards = listBoards(); $security_token = make_secure_link_token($originBoard . '/move_reply/' . $postID); - mod_page(_('Move reply'), $config['file_mod_move_reply'], array('post' => $postID, 'board' => $originBoard, 'boards' => $boards, 'token' => $security_token)); - + mod_page( + _('Move reply'), + $config['file_mod_move_reply'], + [ + 'post' => $postID, + 'board' => $originBoard, + 'boards' => $boards, + 'token' => $security_token + ], + $mod + ); } - } function mod_move(Context $ctx, $originBoard, $postID) { - global $board, $config, $pdo; + global $board, $config, $pdo, $mod; if (!openBoard($originBoard)) error($config['error']['noboard']); @@ -1542,10 +1621,21 @@ function mod_move(Context $ctx, $originBoard, $postID) { $security_token = make_secure_link_token($originBoard . '/move/' . $postID); - mod_page(_('Move thread'), $config['file_mod_move'], array('post' => $postID, 'board' => $originBoard, 'boards' => $boards, 'token' => $security_token)); + mod_page( + _('Move thread'), + $config['file_mod_move'], + [ + 'post' => $postID, + 'board' => $originBoard, + 'boards' => $boards, + 'token' => $security_token + ], + $mod + ); } function mod_ban_post(Context $ctx, $board, $delete, $post, $token = false) { + global $mod; $config = $ctx->get('config'); if (!openBoard($board)) @@ -1612,10 +1702,11 @@ function mod_ban_post(Context $ctx, $board, $delete, $post, $token = false) { 'token' => $security_token ); - mod_page(_('New ban'), $config['file_mod_ban_form'], $args); + mod_page(_('New ban'), $config['file_mod_ban_form'], $args, $mod); } function mod_edit_post(Context $ctx, $board, $edit_raw_html, $postID) { + global $mod; $config = $ctx->get('config'); if (!openBoard($board)) @@ -1688,7 +1779,17 @@ function mod_edit_post(Context $ctx, $board, $edit_raw_html, $postID) { $post['body'] = str_replace("\t", ' ', $post['body']); } - mod_page(_('Edit post'), $config['file_mod_edit_post_form'], array('token' => $security_token, 'board' => $board, 'raw' => $edit_raw_html, 'post' => $post)); + mod_page( + _('Edit post'), + $config['file_mod_edit_post_form'], + [ + 'token' => $security_token, + 'board' => $board, + 'raw' => $edit_raw_html, + 'post' => $post + ], + $mod + ); } } @@ -1976,16 +2077,21 @@ function mod_user(Context $ctx, $uid) { $user['boards'] = explode(',', $user['boards']); - mod_page(_('Edit user'), $config['file_mod_user'], array( - 'user' => $user, - 'logs' => $log, - 'boards' => listBoards(), - 'token' => make_secure_link_token('users/' . $user['id']) - )); + mod_page( + _('Edit user'), + $config['file_mod_user'], + [ + 'user' => $user, + 'logs' => $log, + 'boards' => listBoards(), + 'token' => make_secure_link_token('users/' . $user['id']) + ], + $mod + ); } function mod_user_new(Context $ctx) { - global $pdo, $config; + global $pdo, $config, $mod; if (!hasPermission($config['mod']['createusers'])) error($config['error']['noaccess']); @@ -2033,11 +2139,21 @@ function mod_user_new(Context $ctx) { return; } - mod_page(_('New user'), $config['file_mod_user'], array('new' => true, 'boards' => listBoards(), 'token' => make_secure_link_token('users/new'))); + mod_page( + _('New user'), + $config['file_mod_user'], + [ + 'new' => true, + 'boards' => listBoards(), + 'token' => make_secure_link_token('users/new') + ], + $mod + ); } function mod_users(Context $ctx) { + global $mod; $config = $ctx->get('config'); if (!hasPermission($config['mod']['manageusers'])) @@ -2055,7 +2171,7 @@ function mod_users(Context $ctx) { $user['demote_token'] = make_secure_link_token("users/{$user['id']}/demote"); } - mod_page(sprintf('%s (%d)', _('Manage users'), count($users)), $config['file_mod_users'], array('users' => $users)); + mod_page(sprintf('%s (%d)', _('Manage users'), count($users)), $config['file_mod_users'], [ 'users' => $users ], $mod); } function mod_user_promote(Context $ctx, $uid, $action) { @@ -2145,14 +2261,19 @@ function mod_pm(Context $ctx, $id, $reply = false) { if (!$pm['to_username']) error($config['error']['404']); // deleted? - mod_page(sprintf('%s %s', _('New PM for'), $pm['to_username']), $config['file_mod_new_pm'], array( - 'username' => $pm['username'], - 'id' => $pm['sender'], - 'message' => quote($pm['message']), - 'token' => make_secure_link_token('new_PM/' . $pm['username']) - )); + mod_page( + sprintf('%s %s', _('New PM for'), $pm['to_username']), + $config['file_mod_new_pm'], + [ + 'username' => $pm['username'], + 'id' => $pm['sender'], + 'message' => quote($pm['message']), + 'token' => make_secure_link_token('new_PM/' . $pm['username']) + ], + $mod + ); } else { - mod_page(sprintf('%s – #%d', _('Private message'), $id), $config['file_mod_pm'], $pm); + mod_page(sprintf('%s – #%d', _('Private message'), $id), $config['file_mod_pm'], $pm, $mod); } } @@ -2174,10 +2295,15 @@ function mod_inbox(Context $ctx) { $message['snippet'] = pm_snippet($message['message']); } - mod_page(sprintf('%s (%s)', _('PM inbox'), count($messages) > 0 ? $unread . ' unread' : 'empty'), $config['file_mod_inbox'], array( - 'messages' => $messages, - 'unread' => $unread - )); + mod_page( + sprintf('%s (%s)', _('PM inbox'), count($messages) > 0 ? $unread . ' unread' : 'empty'), + $config['file_mod_inbox'], + [ + 'messages' => $messages, + 'unread' => $unread + ], + $mod + ); } @@ -2223,15 +2349,20 @@ function mod_new_pm(Context $ctx, $username) { header('Location: ?/', true, $config['redirect_http']); } - mod_page(sprintf('%s %s', _('New PM for'), $username), $config['file_mod_new_pm'], array( - 'username' => $username, - 'id' => $id, - 'token' => make_secure_link_token('new_PM/' . $username) - )); + mod_page( + sprintf('%s %s', _('New PM for'), $username), + $config['file_mod_new_pm'], + [ + 'username' => $username, + 'id' => $id, + 'token' => make_secure_link_token('new_PM/' . $username) + ], + $mod + ); } function mod_rebuild(Context $ctx) { - global $twig; + global $twig, $mod; $config = $ctx->get('config'); if (!hasPermission($config['mod']['rebuild'])) @@ -2293,14 +2424,19 @@ function mod_rebuild(Context $ctx) { } } - mod_page(_('Rebuild'), $config['file_mod_rebuilt'], array('logs' => $log)); + mod_page(_('Rebuild'), $config['file_mod_rebuilt'], [ 'logs' => $log ], $mod); return; } - mod_page(_('Rebuild'), $config['file_mod_rebuild'], array( - 'boards' => listBoards(), - 'token' => make_secure_link_token('rebuild') - )); + mod_page( + _('Rebuild'), + $config['file_mod_rebuild'], + [ + 'boards' => listBoards(), + 'token' => make_secure_link_token('rebuild') + ], + $mod + ); } function mod_reports(Context $ctx) { @@ -2385,7 +2521,15 @@ function mod_reports(Context $ctx) { $count++; } - mod_page(sprintf('%s (%d)', _('Report queue'), $count), $config['file_mod_reports'], array('reports' => $body, 'count' => $count)); + mod_page( + sprintf('%s (%d)', _('Report queue'), $count), + $config['file_mod_reports'], + [ + 'reports' => $body, + 'count' => $count + ], + $mod + ); } function mod_report_dismiss(Context $ctx, $id, $action) { @@ -2482,13 +2626,16 @@ function mod_recent_posts(Context $ctx, $lim) { $last_time = $post['time']; } - echo mod_page(_('Recent posts'), $config['file_mod_recent_posts'], array( + echo mod_page( + _('Recent posts'), + $config['file_mod_recent_posts'], + [ 'posts' => $posts, 'limit' => $limit, 'last_time' => $last_time - ) + ], + $mod ); - } function mod_config(Context $ctx, $board_config = false) { @@ -2528,14 +2675,19 @@ function mod_config(Context $ctx, $board_config = false) { } $instance_config = str_replace("\n", ' ', utf8tohtml($instance_config)); - mod_page(_('Config editor'), $config['file_mod_config_editor_php'], array( - 'php' => $instance_config, - 'readonly' => $readonly, - 'boards' => listBoards(), - 'board' => $board_config, - 'file' => $config_file, - 'token' => make_secure_link_token('config' . ($board_config ? '/' . $board_config : '')) - )); + mod_page( + _('Config editor'), + $config['file_mod_config_editor_php'], + [ + 'php' => $instance_config, + 'readonly' => $readonly, + 'boards' => listBoards(), + 'board' => $board_config, + 'file' => $config_file, + 'token' => make_secure_link_token('config' . ($board_config ? '/' . $board_config : '')) + ], + $mod + ); return; } @@ -2623,17 +2775,22 @@ function mod_config(Context $ctx, $board_config = false) { exit; } - mod_page(_('Config editor') . ($board_config ? ': ' . sprintf($config['board_abbreviation'], $board_config) : ''), - $config['file_mod_config_editor'], array( + mod_page( + _('Config editor') . ($board_config ? ': ' . sprintf($config['board_abbreviation'], $board_config) : ''), + $config['file_mod_config_editor'], + [ 'boards' => listBoards(), 'board' => $board_config, 'conf' => $conf, 'file' => $config_file, 'token' => make_secure_link_token('config' . ($board_config ? '/' . $board_config : '')) - )); + ], + $mod + ); } function mod_themes_list(Context $ctx) { + global $mod; $config = $ctx->get('config'); if (!hasPermission($config['mod']['themes'])) @@ -2661,13 +2818,19 @@ function mod_themes_list(Context $ctx) { $theme['uninstall_token'] = make_secure_link_token('themes/' . $theme_name . '/uninstall'); } - mod_page(_('Manage themes'), $config['file_mod_themes'], array( - 'themes' => $themes, - 'themes_in_use' => $themes_in_use, - )); + mod_page( + _('Manage themes'), + $config['file_mod_themes'], + [ + 'themes' => $themes, + 'themes_in_use' => $themes_in_use, + ], + $mod + ); } function mod_theme_configure(Context $ctx, $theme_name) { + global $mod; $config = $ctx->get('config'); if (!hasPermission($config['mod']['themes'])) @@ -2730,23 +2893,33 @@ function mod_theme_configure(Context $ctx, $theme_name) { // Build themes Vichan\Functions\Theme\rebuild_themes('all'); - mod_page(sprintf(_($result ? 'Installed theme: %s' : 'Installation failed: %s'), $theme['name']), $config['file_mod_theme_installed'], array( - 'theme_name' => $theme_name, - 'theme' => $theme, - 'result' => $result, - 'message' => $message - )); + mod_page( + sprintf(_($result ? 'Installed theme: %s' : 'Installation failed: %s'), $theme['name']), + $config['file_mod_theme_installed'], + [ + 'theme_name' => $theme_name, + 'theme' => $theme, + 'result' => $result, + 'message' => $message + ], + $mod + ); return; } $settings = Vichan\Functions\Theme\theme_settings($theme_name); - mod_page(sprintf(_('Configuring theme: %s'), $theme['name']), $config['file_mod_theme_config'], array( - 'theme_name' => $theme_name, - 'theme' => $theme, - 'settings' => $settings, - 'token' => make_secure_link_token('themes/' . $theme_name) - )); + mod_page( + sprintf(_('Configuring theme: %s'), $theme['name']), + $config['file_mod_theme_config'], + [ + 'theme_name' => $theme_name, + 'theme' => $theme, + 'settings' => $settings, + 'token' => make_secure_link_token('themes/' . $theme_name) + ], + $mod + ); } function mod_theme_uninstall(Context $ctx, $theme_name) { @@ -2767,6 +2940,7 @@ function mod_theme_uninstall(Context $ctx, $theme_name) { } function mod_theme_rebuild(Context $ctx, $theme_name) { + global $mod; $config = $ctx->get('config'); if (!hasPermission($config['mod']['themes'])) @@ -2774,9 +2948,14 @@ function mod_theme_rebuild(Context $ctx, $theme_name) { Vichan\Functions\Theme\rebuild_theme($theme_name, 'all'); - mod_page(sprintf(_('Rebuilt theme: %s'), $theme_name), $config['file_mod_theme_rebuilt'], array( - 'theme_name' => $theme_name, - )); + mod_page( + sprintf(_('Rebuilt theme: %s'), $theme_name), + $config['file_mod_theme_rebuilt'], + [ + 'theme_name' => $theme_name, + ], + $mod + ); } // This needs to be done for `secure` CSRF prevention compatibility, otherwise the $board will be read in as the token if editing global pages. @@ -2890,7 +3069,17 @@ function mod_edit_page(Context $ctx, $id) { $content = $query->fetchColumn(); } - mod_page(sprintf(_('Editing static page: %s'), $page['name']), $config['file_mod_edit_page'], array('page' => $page, 'token' => make_secure_link_token("edit_page/$id"), 'content' => prettify_textarea($content), 'board' => $board)); + mod_page( + sprintf(_('Editing static page: %s'), $page['name']), + $config['file_mod_edit_page'], + [ + 'page' => $page, + 'token' => make_secure_link_token("edit_page/$id"), + 'content' => prettify_textarea($content), + 'board' => $board + ], + $mod + ); } function mod_pages(Context $ctx, $board = false) { @@ -2945,11 +3134,20 @@ function mod_pages(Context $ctx, $board = false) { $p['delete_token'] = make_secure_link_token('edit_pages/delete/' . $p['name'] . ($board ? ('/' . $board) : '')); } - mod_page(_('Pages'), $config['file_mod_pages'], array('pages' => $pages, 'token' => make_secure_link_token('edit_pages' . ($board ? ('/' . $board) : '')), 'board' => $board)); + mod_page( + _('Pages'), + $config['file_mod_pages'], + [ + 'pages' => $pages, + 'token' => make_secure_link_token('edit_pages' . ($board ? ('/' . $board) : '')), + 'board' => $board + ], + $mod + ); } function mod_debug_antispam(Context $ctx) { - global $pdo, $config; + global $pdo, $config, $mod; $args = []; @@ -2982,11 +3180,11 @@ function mod_debug_antispam(Context $ctx) { $query = query('SELECT * FROM ``antispam`` ' . ($where ? "WHERE $where" : '') . ' ORDER BY `created` DESC LIMIT 20') or error(db_error()); $args['recent'] = $query->fetchAll(PDO::FETCH_ASSOC); - mod_page(_('Debug: Anti-spam'), $config['file_mod_debug_antispam'], $args); + mod_page(_('Debug: Anti-spam'), $config['file_mod_debug_antispam'], $args, $mod); } function mod_debug_recent_posts(Context $ctx) { - global $pdo, $config; + global $pdo, $config, $mod; $limit = 500; @@ -3016,10 +3214,11 @@ function mod_debug_recent_posts(Context $ctx) { } } - mod_page(_('Debug: Recent posts'), $config['file_mod_debug_recent_posts'], array('posts' => $posts, 'flood_posts' => $flood_posts)); + mod_page(_('Debug: Recent posts'), $config['file_mod_debug_recent_posts'], [ 'posts' => $posts, 'flood_posts' => $flood_posts ], $mod); } function mod_debug_sql(Context $ctx) { + global $mod; $config = $ctx->get('config'); if (!hasPermission($config['mod']['debug_sql'])) @@ -3040,5 +3239,5 @@ function mod_debug_sql(Context $ctx) { } } - mod_page(_('Debug: SQL'), $config['file_mod_debug_sql'], $args); + mod_page(_('Debug: SQL'), $config['file_mod_debug_sql'], $args, $mod); } From f9127dd478f2f28b5e79e205c8a6ab93a005aa1e Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sat, 5 Oct 2024 18:17:36 +0200 Subject: [PATCH 276/336] mod.php: $matches should always be an array --- mod.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/mod.php b/mod.php index c9e891ee..9adf1eb5 100644 --- a/mod.php +++ b/mod.php @@ -184,10 +184,8 @@ foreach ($pages as $uri => $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] == ':') { From 767b8fd8c32ff8e4f3c6e33117d7696309cf6741 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sun, 6 Oct 2024 11:25:57 +0200 Subject: [PATCH 277/336] mod.php: add missing context parameter --- mod.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mod.php b/mod.php index 9adf1eb5..b23c75de 100644 --- a/mod.php +++ b/mod.php @@ -161,7 +161,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; } } From 0e8909ac4e331b529cadd5c46f49f93a66d005c2 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Fri, 20 Sep 2024 22:48:08 +0200 Subject: [PATCH 278/336] pages.php: fix whitespace indentation --- inc/mod/pages.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/inc/mod/pages.php b/inc/mod/pages.php index c03e1a5e..ef41da70 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -11,11 +11,11 @@ defined('TINYBOARD') or exit; function _link_or_copy(string $target, string $link): bool { - if (!link($target, $link)) { - error_log("Failed to link() $target to $link. Falling back to copy()"); - return copy($target, $link); - } - return true; + if (!link($target, $link)) { + error_log("Failed to link() $target to $link. Falling back to copy()"); + return copy($target, $link); + } + return true; } function mod_page($title, $template, $args, $mod, $subtitle = false) { @@ -1092,7 +1092,7 @@ function mod_bans(Context $ctx) { foreach ($unban as $id) { Bans::delete($id, true, $mod['boards'], true); } - Vichan\Functions\Theme\rebuild_themes('bans'); + Vichan\Functions\Theme\rebuild_themes('bans'); header('Location: ?/bans', true, $config['redirect_http']); return; } @@ -1595,7 +1595,7 @@ function mod_move(Context $ctx, $originBoard, $postID) { 'op' => false ); - $spost['body'] = $spost['body_nomarkup'] = sprintf($config['mod']['shadow_mesage'], '>>>/' . $targetBoard . '/' . $newID); + $spost['body'] = $spost['body_nomarkup'] = sprintf($config['mod']['shadow_mesage'], '>>>/' . $targetBoard . '/' . $newID); markup($spost['body']); From 3fe44653f27fe824c29018aadf64c0b12b677ded Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sun, 6 Oct 2024 13:08:29 +0200 Subject: [PATCH 279/336] post.php: fix invocation of native captcha --- post.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/post.php b/post.php index bfd41639..3658ed52 100644 --- a/post.php +++ b/post.php @@ -519,10 +519,10 @@ if (isset($_POST['delete'])) { $query = new NativeCaptchaQuery( $context->get(HttpDriver::class), $config['domain'], - $config['captcha']['provider_check'] + $config['captcha']['provider_check'], + $config['captcha']['extra'] ); $success = $query->verify( - $config['captcha']['extra'], $_POST['captcha_text'], $_POST['captcha_cookie'] ); From 82ea1815fd2c24414e207597207d185c3c646c10 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sun, 4 Feb 2024 14:45:32 +0100 Subject: [PATCH 280/336] Refactor cache driver --- inc/cache.php | 2 +- inc/driver/cache-driver.php | 266 ++++++++++++++++++++++++++++++++++++ 2 files changed, 267 insertions(+), 1 deletion(-) create mode 100644 inc/driver/cache-driver.php diff --git a/inc/cache.php b/inc/cache.php index c4053610..ae788bc1 100644 --- a/inc/cache.php +++ b/inc/cache.php @@ -25,7 +25,7 @@ class Cache { self::$cache->select($config['cache']['redis'][3]) or die('cache select failure'); break; case 'php': - self::$cache = array(); + self::$cache = []; break; } } diff --git a/inc/driver/cache-driver.php b/inc/driver/cache-driver.php new file mode 100644 index 00000000..b5279d82 --- /dev/null +++ b/inc/driver/cache-driver.php @@ -0,0 +1,266 @@ +setOption(Memcached::OPT_BINARY_PROTOCOL, true)) { + throw new RuntimeException('Unable to set the memcached protocol!'); + } + if (!$memcached->setOption(Memcached::OPT_PREFIX_KEY, $prefix)) { + throw new RuntimeException('Unable to set the memcached prefix!'); + } + if (!$memcached->addServers($memcached_server)) { + throw new RuntimeException('Unable to add the memcached server!'); + } + + return new class($memcached) implements CacheDriver { + private Memcached $inner; + + public function __construct(Memcached $inner) { + $this->inner = $inner; + } + + 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(); + } + }; + } + + public static function redis(string $prefix, string $host, int $port, string $password, string $database) { + $redis = new Redis(); + $redis->connect($host, $port); + if ($password) { + $redis->auth($password); + } + if (!$redis->select($database)) { + throw new RuntimeException('Unable to connect to Redis!'); + } + + return new class($prefix, $redis) implements CacheDriver { + private string $prefix; + private Redis $inner; + + public function __construct(string $prefix, Redis $inner) { + $$this->prefix = $prefix; + $this->inner = $inner; + } + + 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(); + } + }; + } + + public static function apcu() { + return new class 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(); + } + }; + } + + public static function filesystem(string $prefix, string $base_path) { + 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!"); + } + + return new class($prefix, $base_path) implements CacheDriver { + private string $prefix; + private string $base_path; + + + private function prepareKey(string $key): string { + $key = str_replace('/', '::', $key); + $key = str_replace("\0", '', $key); + return $this->prefix . $key; + } + + public function __construct(string $prefix, string $base_path) { + $this->prefix = $prefix; + $this->base_path = $base_path; + } + + public function get(string $key): mixed { + $key = $this->prepareKey($key); + + $fd = fopen("$this->base_path/$key", 'r'); + if ($fd === false) { + return null; + } + + $data = stream_get_contents("$this->base_path/$key"); + fclose($fd); + return json_decode($data, true); + } + + public function set(string $key, mixed $value, mixed $expires = false): void { + $key = $this->prepareKey($key); + + $data = json_encode($value); + file_put_contents("$this->base_path/$key", $data); + } + + public function delete(string $key): void { + $key = $this->prepareKey($key); + + @unlink("$this->base_path/$key"); + } + + public function flush(): void { + $files = glob("$this->base_path/$this->prefix*"); + foreach ($files as $file) { + @unlink($file); + } + } + }; + } + + public static function phpArray() { + return new class 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 = []; + } + }; + } + + /** + * No-op cache. Useful for testing. + */ + public static function none() { + return new class 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. + } + }; + } +} + +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; +} From b57d9bfbb3b581cf63f34c831c760b632d903c8c Mon Sep 17 00:00:00 2001 From: Zankaria Date: Thu, 11 Apr 2024 19:20:19 +0200 Subject: [PATCH 281/336] cache: implement cache locking for filesystem cache and give it multiprocess support --- inc/driver/cache-driver.php | 55 +++++++++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 8 deletions(-) diff --git a/inc/driver/cache-driver.php b/inc/driver/cache-driver.php index b5279d82..0718ad86 100644 --- a/inc/driver/cache-driver.php +++ b/inc/driver/cache-driver.php @@ -122,7 +122,7 @@ class CacheDrivers { }; } - public static function filesystem(string $prefix, string $base_path) { + public static function filesystem(string $prefix, string $base_path, string $lock_file) { if ($base_path[strlen($base_path) - 1] !== '/') { $base_path = "$base_path/"; } @@ -135,9 +135,17 @@ class CacheDrivers { throw new RuntimeException("$base_path is not writable!"); } - return new class($prefix, $base_path) implements CacheDriver { + $lock_file = $base_path . $lock_file; + + $lock_fd = fopen($lock_file, 'w'); + if ($lock_fd === false) { + throw new RuntimeException('Unable to open the lock file!'); + } + + $ret = new class($prefix, $base_path, $lock_file) implements CacheDriver { private string $prefix; private string $base_path; + private mixed $lock_fd; private function prepareKey(string $key): string { @@ -146,21 +154,38 @@ class CacheDrivers { return $this->prefix . $key; } - public function __construct(string $prefix, string $base_path) { + 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); + } + + public function __construct(string $prefix, string $base_path, mixed $lock_fd) { $this->prefix = $prefix; $this->base_path = $base_path; + $this->lock_fd = $lock_fd; } public function get(string $key): mixed { $key = $this->prepareKey($key); - $fd = fopen("$this->base_path/$key", 'r'); + $this->sharedLockCache(); + + $fd = fopen($this->base_path . $key, 'r'); if ($fd === false) { + $this->unlockCache(); return null; } - $data = stream_get_contents("$this->base_path/$key"); + $data = stream_get_contents($fd); fclose($fd); + $this->unlockCache(); return json_decode($data, true); } @@ -168,22 +193,36 @@ class CacheDrivers { $key = $this->prepareKey($key); $data = json_encode($value); - file_put_contents("$this->base_path/$key", $data); + $this->exclusiveLockCache(); + file_put_contents($this->base_path . $key, $data); + $this->unlockCache(); } public function delete(string $key): void { $key = $this->prepareKey($key); - @unlink("$this->base_path/$key"); + $this->exclusiveLockCache(); + @unlink($this->base_path . $key); + $this->unlockCache(); } public function flush(): void { - $files = glob("$this->base_path/$this->prefix*"); + $this->exclusiveLockCache(); + $files = glob($this->base_path . $this->prefix . '*', GLOB_NOSORT); foreach ($files as $file) { @unlink($file); } + $this->unlockCache(); + } + + public function close() { + fclose($this->lock_fd); } }; + + register_shutdown_function([$ret, 'close']); + + return $ret; } public static function phpArray() { From 5ea42fa0e2bb3c61c711a0461774e5d3b1b8f875 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Thu, 11 Apr 2024 18:19:18 +0200 Subject: [PATCH 282/336] config.php: update cache documentation --- inc/config.php | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/inc/config.php b/inc/config.php index 11ac8809..d958d617 100644 --- a/inc/config.php +++ b/inc/config.php @@ -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 From 66d2f901718dcdb3e88cb9683b3cd02e5f9a3100 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Fri, 20 Sep 2024 19:44:50 +0200 Subject: [PATCH 283/336] cache-driver.php: filesystem handle expired values. --- inc/driver/cache-driver.php | 55 ++++++++++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/inc/driver/cache-driver.php b/inc/driver/cache-driver.php index 0718ad86..672d7b92 100644 --- a/inc/driver/cache-driver.php +++ b/inc/driver/cache-driver.php @@ -122,7 +122,7 @@ class CacheDrivers { }; } - public static function filesystem(string $prefix, string $base_path, string $lock_file) { + public static function filesystem(string $prefix, string $base_path, string $lock_file, int|false $collect_chance_den) { if ($base_path[strlen($base_path) - 1] !== '/') { $base_path = "$base_path/"; } @@ -146,6 +146,7 @@ class CacheDrivers { private string $prefix; private string $base_path; private mixed $lock_fd; + private int|false $collect_chance_den; private function prepareKey(string $key): string { @@ -166,10 +167,34 @@ class CacheDrivers { flock($this->lock_fd, LOCK_UN); } - public function __construct(string $prefix, string $base_path, mixed $lock_fd) { + 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. + $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() { + if ($this->collect_chance_den !== false && rand(0, $this->collect_chance_den) === 0) { + $this->collect_chance_den = false; // Collect only once per instance (aka process$this->collect_chance_den !== false$this->collect_chance_den !== false). + $this->collectImpl(); + } + } + + public function __construct(string $prefix, string $base_path, mixed $lock_fd, int|false $collect_chance_den) { $this->prefix = $prefix; $this->base_path = $base_path; $this->lock_fd = $lock_fd; + $this->collect_chance_den = $collect_chance_den; } public function get(string $key): mixed { @@ -185,16 +210,30 @@ class CacheDrivers { $data = stream_get_contents($fd); fclose($fd); + $this->maybeCollect(); $this->unlockCache(); - return json_decode($data, true); + $wrapped = json_decode($data, true, 512, JSON_THROW_ON_ERROR); + + if ($wrapped['expires'] !== false && $wrapped['expires'] <= time()) { + // Already, expired, 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); - $data = json_encode($value); + $wrapped = [ + 'expires' => $expires ? time() + $expires : false, + 'inner' => $value + ]; + + $data = json_encode($wrapped); $this->exclusiveLockCache(); file_put_contents($this->base_path . $key, $data); + $this->maybeCollect(); $this->unlockCache(); } @@ -203,9 +242,17 @@ class CacheDrivers { $this->exclusiveLockCache(); @unlink($this->base_path . $key); + $this->maybeCollect(); $this->unlockCache(); } + public function collect() { + $this->sharedLockCache(); + $count = $this->collectImpl(); + $this->unlockCache(); + return $count; + } + public function flush(): void { $this->exclusiveLockCache(); $files = glob($this->base_path . $this->prefix . '*', GLOB_NOSORT); From 3d406aeab2343c23b694d5d84b20fc359a7254e5 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Wed, 2 Oct 2024 21:36:28 +0200 Subject: [PATCH 284/336] cache-driver.php: move to Data --- inc/{driver => Data/Driver}/cache-driver.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename inc/{driver => Data/Driver}/cache-driver.php (99%) diff --git a/inc/driver/cache-driver.php b/inc/Data/Driver/cache-driver.php similarity index 99% rename from inc/driver/cache-driver.php rename to inc/Data/Driver/cache-driver.php index 672d7b92..b943ee71 100644 --- a/inc/driver/cache-driver.php +++ b/inc/Data/Driver/cache-driver.php @@ -1,5 +1,5 @@ Date: Wed, 2 Oct 2024 21:49:51 +0200 Subject: [PATCH 285/336] driver: break up cache drivers --- inc/Data/Driver/ApcuCacheDriver.php | 28 ++ inc/Data/Driver/ArrayCacheDriver.php | 28 ++ inc/Data/Driver/CacheDriver.php | 38 +++ inc/Data/Driver/FsCachedriver.php | 150 ++++++++++ inc/Data/Driver/MemcacheCacheDriver.php | 43 +++ inc/Data/Driver/NoneCacheDriver.php | 26 ++ inc/Data/Driver/RedisCacheDriver.php | 48 ++++ inc/Data/Driver/cache-driver.php | 352 ------------------------ 8 files changed, 361 insertions(+), 352 deletions(-) create mode 100644 inc/Data/Driver/ApcuCacheDriver.php create mode 100644 inc/Data/Driver/ArrayCacheDriver.php create mode 100644 inc/Data/Driver/CacheDriver.php create mode 100644 inc/Data/Driver/FsCachedriver.php create mode 100644 inc/Data/Driver/MemcacheCacheDriver.php create mode 100644 inc/Data/Driver/NoneCacheDriver.php create mode 100644 inc/Data/Driver/RedisCacheDriver.php delete mode 100644 inc/Data/Driver/cache-driver.php 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. + $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(); + + $fd = \fopen($this->base_path . $key, 'r'); + if ($fd === false) { + $this->unlockCache(); + return null; + } + + $data = \stream_get_contents($fd); + \fclose($fd); + $this->maybeCollect(); + $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(); + \file_put_contents($this->base_path . $key, $data); + $this->maybeCollect(); + $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(); + $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(); + } +} diff --git a/inc/Data/Driver/cache-driver.php b/inc/Data/Driver/cache-driver.php deleted file mode 100644 index b943ee71..00000000 --- a/inc/Data/Driver/cache-driver.php +++ /dev/null @@ -1,352 +0,0 @@ -setOption(Memcached::OPT_BINARY_PROTOCOL, true)) { - throw new RuntimeException('Unable to set the memcached protocol!'); - } - if (!$memcached->setOption(Memcached::OPT_PREFIX_KEY, $prefix)) { - throw new RuntimeException('Unable to set the memcached prefix!'); - } - if (!$memcached->addServers($memcached_server)) { - throw new RuntimeException('Unable to add the memcached server!'); - } - - return new class($memcached) implements CacheDriver { - private Memcached $inner; - - public function __construct(Memcached $inner) { - $this->inner = $inner; - } - - 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(); - } - }; - } - - public static function redis(string $prefix, string $host, int $port, string $password, string $database) { - $redis = new Redis(); - $redis->connect($host, $port); - if ($password) { - $redis->auth($password); - } - if (!$redis->select($database)) { - throw new RuntimeException('Unable to connect to Redis!'); - } - - return new class($prefix, $redis) implements CacheDriver { - private string $prefix; - private Redis $inner; - - public function __construct(string $prefix, Redis $inner) { - $$this->prefix = $prefix; - $this->inner = $inner; - } - - 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(); - } - }; - } - - public static function apcu() { - return new class 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(); - } - }; - } - - public static function filesystem(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!"); - } - - $lock_file = $base_path . $lock_file; - - $lock_fd = fopen($lock_file, 'w'); - if ($lock_fd === false) { - throw new RuntimeException('Unable to open the lock file!'); - } - - $ret = new class($prefix, $base_path, $lock_file) 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. - $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() { - if ($this->collect_chance_den !== false && rand(0, $this->collect_chance_den) === 0) { - $this->collect_chance_den = false; // Collect only once per instance (aka process$this->collect_chance_den !== false$this->collect_chance_den !== false). - $this->collectImpl(); - } - } - - public function __construct(string $prefix, string $base_path, mixed $lock_fd, int|false $collect_chance_den) { - $this->prefix = $prefix; - $this->base_path = $base_path; - $this->lock_fd = $lock_fd; - $this->collect_chance_den = $collect_chance_den; - } - - public function get(string $key): mixed { - $key = $this->prepareKey($key); - - $this->sharedLockCache(); - - $fd = fopen($this->base_path . $key, 'r'); - if ($fd === false) { - $this->unlockCache(); - return null; - } - - $data = stream_get_contents($fd); - fclose($fd); - $this->maybeCollect(); - $this->unlockCache(); - $wrapped = json_decode($data, true, 512, JSON_THROW_ON_ERROR); - - if ($wrapped['expires'] !== false && $wrapped['expires'] <= time()) { - // Already, expired, 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(); - file_put_contents($this->base_path . $key, $data); - $this->maybeCollect(); - $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() { - $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() { - fclose($this->lock_fd); - } - }; - - register_shutdown_function([$ret, 'close']); - - return $ret; - } - - public static function phpArray() { - return new class 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 = []; - } - }; - } - - /** - * No-op cache. Useful for testing. - */ - public static function none() { - return new class 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. - } - }; - } -} - -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; -} From f138b4b88799f051a5a60bc70c806d600ed25263 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Fri, 26 Apr 2024 13:55:58 +0200 Subject: [PATCH 286/336] cache.php: wrap new cache drivers --- inc/cache.php | 177 +++++++++++++++----------------------------------- 1 file changed, 51 insertions(+), 126 deletions(-) diff --git a/inc/cache.php b/inc/cache.php index ae788bc1..293660fd 100644 --- a/inc/cache.php +++ b/inc/cache.php @@ -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 = []; - 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; } } From 589435b667626a43c9f79df45621ed23f0b919a8 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sun, 7 Apr 2024 21:10:39 +0200 Subject: [PATCH 287/336] context.php: use shared cache driver --- inc/context.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/inc/context.php b/inc/context.php index c3ebef04..e747a68d 100644 --- a/inc/context.php +++ b/inc/context.php @@ -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(); } ]); } From 243e4894fa61917bce70dd58409f2a6aa61d09b8 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sun, 7 Apr 2024 23:14:31 +0200 Subject: [PATCH 288/336] Use CacheDriver and Context for mod.php and mod pages --- inc/mod/pages.php | 88 ++++++++++++++++++++++++----------------------- 1 file changed, 45 insertions(+), 43 deletions(-) diff --git a/inc/mod/pages.php b/inc/mod/pages.php index ef41da70..8a549481 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -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)) From 003e8f6d3b0a645ccaa4b131fee31e7dc8b91278 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Fri, 20 Sep 2024 22:41:51 +0200 Subject: [PATCH 289/336] maintenance.php: delete expired filesystem cache --- tools/maintenance.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tools/maintenance.php b/tools/maintenance.php index 33c0a4d4..a869e2fa 100644 --- a/tools/maintenance.php +++ b/tools/maintenance.php @@ -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"); From 115f28807a614b816a6f519a1bfa5160ff95cc38 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sat, 5 Oct 2024 17:31:41 +0200 Subject: [PATCH 290/336] FsCacheDriver.php: collect expired cache items before operating on the cache --- inc/Data/Driver/FsCachedriver.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/inc/Data/Driver/FsCachedriver.php b/inc/Data/Driver/FsCachedriver.php index f7f0e028..b543cfa6 100644 --- a/inc/Data/Driver/FsCachedriver.php +++ b/inc/Data/Driver/FsCachedriver.php @@ -30,7 +30,10 @@ class FsCacheDriver implements CacheDriver { } 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. + /* + * 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) { @@ -84,6 +87,9 @@ class FsCacheDriver implements CacheDriver { $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(); @@ -92,7 +98,6 @@ class FsCacheDriver implements CacheDriver { $data = \stream_get_contents($fd); \fclose($fd); - $this->maybeCollect(); $this->unlockCache(); $wrapped = \json_decode($data, true, 512, \JSON_THROW_ON_ERROR); @@ -114,8 +119,8 @@ class FsCacheDriver implements CacheDriver { $data = \json_encode($wrapped); $this->exclusiveLockCache(); - \file_put_contents($this->base_path . $key, $data); $this->maybeCollect(); + \file_put_contents($this->base_path . $key, $data); $this->unlockCache(); } From fe7a667441df1806c4131b38e95bd14da935baa9 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sat, 12 Oct 2024 16:16:37 +0200 Subject: [PATCH 291/336] style.css: add diceroll styling --- stylesheets/style.css | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/stylesheets/style.css b/stylesheets/style.css index b7c39b26..ef1abf8b 100644 --- a/stylesheets/style.css +++ b/stylesheets/style.css @@ -1042,6 +1042,20 @@ div.boardlist a { cursor: pointer; } +/* Inline dice */ +.dice-option table { + border: 1px dotted black; + margin: 0; + border-collapse: collapse; +} +.dice-option table td { + text-align: center; + border-left: 1px dotted black; + padding-left: 2px; + padding-right: 2px; + padding-bottom: 2px; +} + #youtube-size input { width: 50px; } From 27e4bd833a1ee5dda75188ca2cbb124a38105692 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Mon, 14 Oct 2024 12:18:04 +0200 Subject: [PATCH 292/336] config.php: use op-cache friend array syntax for markup config --- inc/config.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/inc/config.php b/inc/config.php index 11ac8809..fbca7447 100644 --- a/inc/config.php +++ b/inc/config.php @@ -783,11 +783,13 @@ * ==================== */ - // "Wiki" markup syntax ($config['wiki_markup'] in pervious versions): - $config['markup'][] = array("/'''(.+?)'''/", "\$1"); - $config['markup'][] = array("/''(.+?)''/", "\$1"); - $config['markup'][] = array("/\*\*(.+?)\*\*/", "\$1"); - $config['markup'][] = array("/^[ |\t]*==(.+?)==[ |\t]*$/m", "\$1"); + $config['markup'] = [ + // "Wiki" markup syntax ($config['wiki_markup'] in pervious versions): + [ "/'''(.+?)'''/", "\$1" ], + [ "/''(.+?)''/", "\$1" ], + [ "/\*\*(.+?)\*\*/", "\$1" ], + [ "/^[ |\t]*==(.+?)==[ |\t]*$/m", "\$1" ], + ]; // Code markup. This should be set to a regular expression, using tags you want to use. Examples: // "/\[code\](.*?)\[\/code\]/is" From b8c53fbbcdffe50e1548c38ff764cf4cb11e2c50 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Mon, 14 Oct 2024 12:21:42 +0200 Subject: [PATCH 293/336] dice.php: extract email dice function from functions.php --- inc/functions.php | 61 +----------------------------------------- inc/functions/dice.php | 61 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 60 deletions(-) create mode 100644 inc/functions/dice.php diff --git a/inc/functions.php b/inc/functions.php index 30994b5d..15aa2c0e 100755 --- a/inc/functions.php +++ b/inc/functions.php @@ -241,7 +241,7 @@ function loadConfig() { $config['version'] = $__version; if ($config['allow_roll']) { - event_handler('post', 'diceRoller'); + event_handler('post', 'email_dice_roll'); } if (in_array('webm', $config['allowed_ext_files']) || in_array('mp4', $config['allowed_ext_files'])) { @@ -2600,65 +2600,6 @@ function shell_exec_error($command, $suppress_stdout = false) { return $return === 'TB_SUCCESS' ? false : $return; } -/* Die rolling: - * If "dice XdY+/-Z" is in the email field (where X or +/-Z may be - * missing), X Y-sided dice are rolled and summed, with the modifier Z - * added on. The result is displayed at the top of the post. - */ -function diceRoller($post) { - global $config; - if(strpos(strtolower($post->email), 'dice%20') === 0) { - $dicestr = str_split(substr($post->email, strlen('dice%20'))); - - // Get params - $diceX = ''; - $diceY = ''; - $diceZ = ''; - - $curd = 'diceX'; - for($i = 0; $i < count($dicestr); $i ++) { - if(is_numeric($dicestr[$i])) { - $$curd .= $dicestr[$i]; - } else if($dicestr[$i] == 'd') { - $curd = 'diceY'; - } else if($dicestr[$i] == '-' || $dicestr[$i] == '+') { - $curd = 'diceZ'; - $$curd = $dicestr[$i]; - } - } - - // Default values for X and Z - if($diceX == '') { - $diceX = '1'; - } - - if($diceZ == '') { - $diceZ = '+0'; - } - - // Intify them - $diceX = intval($diceX); - $diceY = intval($diceY); - $diceZ = intval($diceZ); - - // Continue only if we have valid values - if($diceX > 0 && $diceY > 0) { - $dicerolls = array(); - $dicesum = $diceZ; - for($i = 0; $i < $diceX; $i++) { - $roll = rand(1, $diceY); - $dicerolls[] = $roll; - $dicesum += $roll; - } - - // Prepend the result to the post body - $modifier = ($diceZ != 0) ? ((($diceZ < 0) ? ' - ' : ' + ') . abs($diceZ)) : ''; - $dicesum = ($diceX > 1) ? ' = ' . $dicesum : ''; - $post->body = '
    Dice rollRolled ' . implode(', ', $dicerolls) . $modifier . $dicesum . '

    ' . $post->body; - } - } -} - function slugify($post) { global $config; diff --git a/inc/functions/dice.php b/inc/functions/dice.php new file mode 100644 index 00000000..f3208b33 --- /dev/null +++ b/inc/functions/dice.php @@ -0,0 +1,61 @@ +email), 'dice%20') === 0) { + $dicestr = str_split(substr($post->email, strlen('dice%20'))); + + // Get params + $diceX = ''; + $diceY = ''; + $diceZ = ''; + + $curd = 'diceX'; + for($i = 0; $i < count($dicestr); $i ++) { + if(is_numeric($dicestr[$i])) { + $$curd .= $dicestr[$i]; + } else if($dicestr[$i] == 'd') { + $curd = 'diceY'; + } else if($dicestr[$i] == '-' || $dicestr[$i] == '+') { + $curd = 'diceZ'; + $$curd = $dicestr[$i]; + } + } + + // Default values for X and Z + if($diceX == '') { + $diceX = '1'; + } + + if($diceZ == '') { + $diceZ = '+0'; + } + + // Intify them + $diceX = intval($diceX); + $diceY = intval($diceY); + $diceZ = intval($diceZ); + + // Continue only if we have valid values + if($diceX > 0 && $diceY > 0) { + $dicerolls = array(); + $dicesum = $diceZ; + for($i = 0; $i < $diceX; $i++) { + $roll = rand(1, $diceY); + $dicerolls[] = $roll; + $dicesum += $roll; + } + + // Prepend the result to the post body + $modifier = ($diceZ != 0) ? ((($diceZ < 0) ? ' - ' : ' + ') . abs($diceZ)) : ''; + $dicesum = ($diceX > 1) ? ' = ' . $dicesum : ''; + $post->body = '
    Dice rollRolled ' . implode(', ', $dicerolls) . $modifier . $dicesum . '

    ' . $post->body; + } + } +} From ceccbfc5b79cdc0851d48877301aed167ded4083 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sat, 12 Oct 2024 16:20:01 +0200 Subject: [PATCH 294/336] config.php: limit the number of dicerolls --- inc/config.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/inc/config.php b/inc/config.php index fbca7447..2e14555d 100644 --- a/inc/config.php +++ b/inc/config.php @@ -712,6 +712,9 @@ ); */ + // Maximum number inline of dice rolls per markup. + $config['max_roll_count'] = 50; + // Allow dice rolling: an email field of the form "dice XdY+/-Z" will result in X Y-sided dice rolled and summed, // with the modifier Z added, with the result displayed at the top of the post body. $config['allow_roll'] = false; From ad1d56d0927f22f924d788ad744eb09b3990ae76 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Mon, 14 Oct 2024 12:23:38 +0200 Subject: [PATCH 295/336] dice.php: handle inline dice rolling markup --- inc/functions/dice.php | 53 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/inc/functions/dice.php b/inc/functions/dice.php index f3208b33..558257bf 100644 --- a/inc/functions/dice.php +++ b/inc/functions/dice.php @@ -1,6 +1,11 @@ The number of dices to roll. + * 3 -> The number faces of the dices. + * 4 -> The offset to apply to the dice. + * @param string $img_path Path to the image to use relative to the root. Null if none. + * @return string The html to replace the original markup with. + */ +function inline_dice_roll_markup(array $matches, ?string $img_path): string { + global $config; + + $dice_count = _get_or_default_int($matches, 1, 1); + $dice_faces = _get_or_default_int($matches, 3, 6); + $dice_offset = _get_or_default_int($matches, 4, 0); + + // Clamp between 1 and max_roll_count. + $dice_count = max(min($dice_count, $config['max_roll_count']), 1); + // Must be at least 2. + if ($dice_faces < 2) { + $dice_faces = 6; + } + + $tot = 0; + for ($i = 0; $i < $dice_count; $i++) { + $tot += mt_rand(1, $dice_faces); + } + // Ensure that final result is at least an integer. + $tot = abs((int)($dice_offset + $tot)); + + + if ($img_path !== null) { + $img_text = "dice"; + } else { + $img_text = ''; + } + + if ($dice_offset === 0) { + $dice_offset_text = ''; + } elseif ($dice_offset > 0) { + $dice_offset_text = "+{$dice_offset}"; + } else { + $dice_offset_text = (string)$dice_offset; + } + + return "$img_text {$dice_count}d{$dice_faces}{$dice_offset_text} = $tot"; +} From fba88643ecda698ca3be13be3ca1b7adf4b691a6 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Mon, 14 Oct 2024 12:24:44 +0200 Subject: [PATCH 296/336] config.php: add inline dice rolling markup support to the default configuration --- inc/config.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/inc/config.php b/inc/config.php index 2e14555d..067bc715 100644 --- a/inc/config.php +++ b/inc/config.php @@ -787,6 +787,8 @@ */ $config['markup'] = [ + // Inline dice roll markup. + [ "/!([-+]?\d+)?([d])([-+]?\d+)([-+]\d+)?/iu", fn($m) => inline_dice_roll_markup($m, 'static/d10.svg') ], // "Wiki" markup syntax ($config['wiki_markup'] in pervious versions): [ "/'''(.+?)'''/", "\$1" ], [ "/''(.+?)''/", "\$1" ], From b67ff982e29759d1349b6f21acc45471d9da158e Mon Sep 17 00:00:00 2001 From: Zankaria Date: Mon, 14 Oct 2024 13:55:58 +0200 Subject: [PATCH 297/336] composer: add dice.php to autoload --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 88ee789d..a2e906fe 100644 --- a/composer.json +++ b/composer.json @@ -34,9 +34,10 @@ "inc/lock.php", "inc/queue.php", "inc/functions.php", + "inc/functions/dice.php", + "inc/functions/format.php", "inc/functions/net.php", "inc/functions/num.php", - "inc/functions/format.php", "inc/functions/theme.php", "inc/service/captcha-queries.php", "inc/context.php" From 76f6c721e9679ec1c2f58c147f1b06bbf72509cb Mon Sep 17 00:00:00 2001 From: Zankaria Date: Wed, 16 Oct 2024 22:49:19 +0200 Subject: [PATCH 298/336] context.php: fix missing include file --- inc/context.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/inc/context.php b/inc/context.php index e747a68d..9e5caef0 100644 --- a/inc/context.php +++ b/inc/context.php @@ -1,8 +1,7 @@ definitions[$name])) { - throw new RuntimeException("Could not find a dependency named $name"); + throw new \RuntimeException("Could not find a dependency named $name"); } $ret = $this->definitions[$name]; @@ -69,13 +68,13 @@ function build_context(array $config): Context { $config['captcha']['hcaptcha']['sitekey'] ); default: - throw new RuntimeException('No remote captcha service available'); + throw new \RuntimeException('No remote captcha service available'); } }, NativeCaptchaQuery::class => function($c) { $config = $c->get('config'); if ($config['captcha']['provider'] !== 'native') { - throw new RuntimeException('No native captcha service available'); + throw new \RuntimeException('No native captcha service available'); } return new NativeCaptchaQuery( $c->get(HttpDriver::class), From 58f730293650e1a2282a08010c100ae55d601e57 Mon Sep 17 00:00:00 2001 From: Lorenzo Yario Date: Sun, 20 Oct 2024 12:03:15 -0700 Subject: [PATCH 299/336] minor bugfix relating to auth when changing your own username --- inc/mod/auth.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/mod/auth.php b/inc/mod/auth.php index 70cf23ff..f6a65db2 100644 --- a/inc/mod/auth.php +++ b/inc/mod/auth.php @@ -10,7 +10,7 @@ use Vichan\Functions\Net; defined('TINYBOARD') or exit; // create a hash/salt pair for validate logins -function mkhash(string $username, string $password, mixed $salt = false): array|string { +function mkhash(string $username, ?string $password, mixed $salt = false): array|string { global $config; if (!$salt) { From b5a9dc4d1a1ce3173745e2e8e14ce37a25f689a3 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Thu, 31 Oct 2024 14:47:11 +0100 Subject: [PATCH 300/336] bans.php: remove multiple return types for PHP 7.4 support --- inc/bans.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/bans.php b/inc/bans.php index 8ea3b921..931777a4 100644 --- a/inc/bans.php +++ b/inc/bans.php @@ -142,7 +142,7 @@ class Bans { return array($ipstart, $ipend); } - static public function findSingle(string $ip, int $ban_id, bool $require_ban_view): array|null { + static public function findSingle(string $ip, int $ban_id, bool $require_ban_view): ?array { /** * Use OR in the query to also garbage collect bans. Ideally we should move the whole GC procedure to a separate * script, but it will require a more important restructuring. From 9dacdf59b1db3f38aed0018a0ee6caec5cdfc0d3 Mon Sep 17 00:00:00 2001 From: Lorenzo Yario Date: Mon, 16 Dec 2024 23:15:23 -0600 Subject: [PATCH 301/336] skip captcha or log settings in mod.php config relates to multidimensional arrays --- inc/mod/config-editor.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/inc/mod/config-editor.php b/inc/mod/config-editor.php index 0f6dbc9a..688b0910 100644 --- a/inc/mod/config-editor.php +++ b/inc/mod/config-editor.php @@ -64,6 +64,10 @@ function config_vars() { $var['comment'][] = $temp_comment; $temp_comment = false; } + + if (preg_match('!^\s*\$config\[(\'log_system\'|\'captcha\')\]!', $line)) { + continue; + } if (preg_match('!^\s*// ([^$].*)$!', $line, $matches)) { if ($var['default'] !== false) { From 2e91c1ed3d9cb3091d4c38722be27b529e862d77 Mon Sep 17 00:00:00 2001 From: Lorenzo Yario Date: Fri, 20 Dec 2024 00:24:46 -0600 Subject: [PATCH 302/336] Update FileLogDriver.php --- inc/Data/Driver/FileLogDriver.php | 1 + 1 file changed, 1 insertion(+) diff --git a/inc/Data/Driver/FileLogDriver.php b/inc/Data/Driver/FileLogDriver.php index 18423cf1..2c9f14a0 100644 --- a/inc/Data/Driver/FileLogDriver.php +++ b/inc/Data/Driver/FileLogDriver.php @@ -49,6 +49,7 @@ class FileLogDriver implements LogDriver { $line = "{$this->name} $lv: $message\n"; \flock($this->fd, LOCK_EX); \fwrite($this->fd, $line); + \fflush($this->fd); \flock($this->fd, LOCK_UN); } } From 26ffd1aa72aefc309e7074750f450155819996c4 Mon Sep 17 00:00:00 2001 From: Lorenzo Yario Date: Fri, 20 Dec 2024 01:08:41 -0600 Subject: [PATCH 303/336] remove anti-bot functions --- inc/anti-bot.php | 196 +---------------------------------------------- 1 file changed, 1 insertion(+), 195 deletions(-) diff --git a/inc/anti-bot.php b/inc/anti-bot.php index 326628af..6f684c55 100644 --- a/inc/anti-bot.php +++ b/inc/anti-bot.php @@ -1,199 +1,5 @@ ?=-` '; - } - if ($unicode_chars) { - $len = strlen($chars) / 10; - for ($n = 0; $n < $len; $n++) { - $chars .= mb_convert_encoding('&#' . mt_rand(0x2600, 0x26FF) . ';', 'UTF-8', 'HTML-ENTITIES'); - } - } - - $chars = preg_split('//u', $chars, -1, PREG_SPLIT_NO_EMPTY); - - $ch = []; - - // fill up $ch until we reach $length - while (count($ch) < $length) { - $n = $length - count($ch); - $keys = array_rand($chars, $n > count($chars) ? count($chars) : $n); - if ($n == 1) { - $ch[] = $chars[$keys]; - break; - } - shuffle($keys); - foreach ($keys as $key) { - $ch[] = $chars[$key]; - } - } - - $chars = $ch; - - return implode('', $chars); - } - - public static function make_confusing($string) { - $chars = preg_split('//u', $string, -1, PREG_SPLIT_NO_EMPTY); - - foreach ($chars as &$c) { - if (mt_rand(0, 3) != 0) { - $c = utf8tohtml($c); - } else { - $c = mb_encode_numericentity($c, [ 0, 0xffff, 0, 0xffff ], 'UTF-8'); - } - } - - return implode('', $chars); - } - - public function __construct(array $salt = []) { - global $config; - - if (!empty($salt)) { - // Create a salted hash of the "extra salt" - $this->salt = implode(':', $salt); - } else { - $this->salt = ''; - } - - shuffle($config['spam']['hidden_input_names']); - - $input_count = mt_rand($config['spam']['hidden_inputs_min'], $config['spam']['hidden_inputs_max']); - $hidden_input_names_x = 0; - - for ($x = 0; $x < $input_count ; $x++) { - if ($hidden_input_names_x === false || mt_rand(0, 2) == 0) { - // Use an obscure name - $name = $this->randomString(mt_rand(10, 40), false, false, $config['spam']['unicode']); - } else { - // Use a pre-defined confusing name - $name = $config['spam']['hidden_input_names'][$hidden_input_names_x++]; - if ($hidden_input_names_x >= count($config['spam']['hidden_input_names'])) { - $hidden_input_names_x = false; - } - } - - if (mt_rand(0, 2) == 0) { - // Value must be null - $this->inputs[$name] = ''; - } elseif (mt_rand(0, 4) == 0) { - // Numeric value - $this->inputs[$name] = (string)mt_rand(0, 100000); - } else { - // Obscure value - $this->inputs[$name] = $this->randomString(mt_rand(5, 100), true, true, $config['spam']['unicode']); - } - } - } - - public static function space() { - if (mt_rand(0, 3) != 0) { - return ' '; - } - return str_repeat(' ', mt_rand(1, 3)); - } - - public function html($count = false) { - $elements = [ - '', - '', - '', - '', - '', - '', - '', - '
    ', - '
    ', - '', - '' - ]; - - $html = ''; - - if ($count === false) { - $count = mt_rand(1, (int)abs(count($this->inputs) / 15) + 1); - } - - if ($count === true) { - // All elements - $inputs = array_slice($this->inputs, $this->index); - } else { - $inputs = array_slice($this->inputs, $this->index, $count); - } - $this->index += count($inputs); - - foreach ($inputs as $name => $value) { - $element = false; - while (!$element) { - $element = $elements[array_rand($elements)]; - $element = str_replace(' ', self::space(), $element); - if (mt_rand(0, 5) == 0) { - $element = str_replace('>', self::space() . '>', $element); - } - if (strpos($element, 'textarea') !== false && $value == '') { - // There have been some issues with mobile web browsers and empty - {{ antibot.html() }} {% if not (not (config.field_disable_subject or (id and config.field_disable_reply_subject)) or (mod and post.mod|hasPermission(config.mod.bypass_field_disable, board.uri))) %} {% if not (not config.field_disable_email or (mod and post.mod|hasPermission(config.mod.bypass_field_disable, board.uri))) %} {% if config.spoiler_images %} {% endif %} @@ -80,11 +69,9 @@ {% endif %} {% trans %}Verification{% endtrans %} - {{ antibot.html() }}
    - {{ antibot.html() }} {% endif %} @@ -92,11 +79,9 @@ {% trans %}Verification{% endtrans %} - {{ antibot.html() }}
    - {{ antibot.html() }} {% endif %} @@ -173,14 +158,12 @@
    {% endif %} - {{ antibot.html() }} {% if config.enable_embedding %} {% trans %}Embed{% endtrans %} - {{ antibot.html() }} @@ -211,27 +194,21 @@ {% if not config.field_disable_password or (mod and post.mod|hasPermission(config.mod.bypass_field_disable, board.uri)) %} {% trans %}Password{% endtrans %} - {{ antibot.html() }} {% trans %}(For file deletion.){% endtrans %} - {{ antibot.html() }} {% endif %} {% if config.simple_spam and not id %} {{ config.simple_spam.question }} - {{ antibot.html() }} - {{ antibot.html() }} {% endif %} -{{ antibot.html(true) }} - +