From 6f9ea522123e1fadb06b124859164bbde56f9201 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Mon, 24 Feb 2025 16:14:53 +0100 Subject: [PATCH 01/85] faq: update git repo (again) --- templates/themes/faq/index.html | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/templates/themes/faq/index.html b/templates/themes/faq/index.html index 6ddf872a..b63a4483 100644 --- a/templates/themes/faq/index.html +++ b/templates/themes/faq/index.html @@ -88,7 +88,7 @@

How can I suggest or submit fixes to the site ?

-

There is a /meta/ thread for this, and our Gitlab repo.

+

There is a /meta/ thread for this, and our Forgejo repo.

I don't trust Tor exit nodes. Do you have an .onion site ?

@@ -129,6 +129,10 @@ What are the maximum filesize for attachments ?

Maximum file size in megabytes for attachments to a single post is 80MB (e.g. 5 * 16MB), as most boards support uploading 5 attachments by default. Maximum file size in pixels for images is currently set to 20000 by 20000.

+

+ Can I have an account on your git instance? +

+

Create the account on Forgejo, then contact the staff via the Tech Team General thread on /meta/ to get your account approved.

From 707fb62c04db12c04f075e170f28879fe23fcd46 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Fri, 4 Oct 2024 12:54:49 +0200 Subject: [PATCH 02/85] log-driver.php: split up log driver --- inc/Data/Driver/ErrorLogLogDriver.php | 28 +++++++++++++ inc/Data/Driver/FileLogDriver.php | 60 +++++++++++++++++++++++++++ inc/Data/Driver/LogDriver.php | 22 ++++++++++ inc/Data/Driver/LogTrait.php | 26 ++++++++++++ inc/Data/Driver/StderrLogDriver.php | 27 ++++++++++++ inc/Data/Driver/SyslogLogDriver.php | 35 ++++++++++++++++ 6 files changed, 198 insertions(+) create mode 100644 inc/Data/Driver/ErrorLogLogDriver.php create mode 100644 inc/Data/Driver/FileLogDriver.php create mode 100644 inc/Data/Driver/LogDriver.php create mode 100644 inc/Data/Driver/LogTrait.php create mode 100644 inc/Data/Driver/StderrLogDriver.php create mode 100644 inc/Data/Driver/SyslogLogDriver.php diff --git a/inc/Data/Driver/ErrorLogLogDriver.php b/inc/Data/Driver/ErrorLogLogDriver.php new file mode 100644 index 00000000..e2050606 --- /dev/null +++ b/inc/Data/Driver/ErrorLogLogDriver.php @@ -0,0 +1,28 @@ +name = $name; + $this->level = $level; + } + + public function log(int $level, string $message): void { + if ($level <= $this->level) { + $lv = $this->levelToString($level); + $line = "{$this->name} $lv: $message"; + \error_log($line, 0, null, null); + } + } +} diff --git a/inc/Data/Driver/FileLogDriver.php b/inc/Data/Driver/FileLogDriver.php new file mode 100644 index 00000000..18423cf1 --- /dev/null +++ b/inc/Data/Driver/FileLogDriver.php @@ -0,0 +1,60 @@ +fd = \fopen($file_path, 'a'); + if ($this->fd === false) { + throw new \RuntimeException("Unable to open log file at $file_path"); + } + + $this->name = $name; + $this->level = $level; + + // In some cases PHP does not run the destructor. + \register_shutdown_function([$this, 'close']); + } + + public function __destruct() { + $this->close(); + } + + public function log(int $level, string $message): void { + if ($level <= $this->level) { + $lv = $this->levelToString($level); + $line = "{$this->name} $lv: $message\n"; + \flock($this->fd, LOCK_EX); + \fwrite($this->fd, $line); + \flock($this->fd, LOCK_UN); + } + } + + public function close() { + \flock($this->fd, LOCK_UN); + \fclose($this->fd); + } +} diff --git a/inc/Data/Driver/LogDriver.php b/inc/Data/Driver/LogDriver.php new file mode 100644 index 00000000..fddc3f27 --- /dev/null +++ b/inc/Data/Driver/LogDriver.php @@ -0,0 +1,22 @@ +name = $name; + $this->level = $level; + } + + public function log(int $level, string $message): void { + if ($level <= $this->level) { + $lv = $this->levelToString($level); + \fwrite(\STDERR, "{$this->name} $lv: $message\n"); + } + } +} diff --git a/inc/Data/Driver/SyslogLogDriver.php b/inc/Data/Driver/SyslogLogDriver.php new file mode 100644 index 00000000..c0df5304 --- /dev/null +++ b/inc/Data/Driver/SyslogLogDriver.php @@ -0,0 +1,35 @@ +level = $level; + } + + public function log(int $level, string $message): void { + if ($level <= $this->level) { + if (isset($_SERVER['REMOTE_ADDR'], $_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'])) { + // CGI + \syslog($level, "$message - client: {$_SERVER['REMOTE_ADDR']}, request: \"{$_SERVER['REQUEST_METHOD']} {$_SERVER['REQUEST_URI']}\""); + } else { + \syslog($level, $message); + } + } + } +} From 79523f8251fa2fde9bbc582ce73893a9cb0def7f Mon Sep 17 00:00:00 2001 From: Zankaria Date: Thu, 19 Dec 2024 00:23:39 +0100 Subject: [PATCH 03/85] context.php: initial add LogDriver --- inc/context.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/inc/context.php b/inc/context.php index 3207d7ea..75c107d0 100644 --- a/inc/context.php +++ b/inc/context.php @@ -3,6 +3,7 @@ namespace Vichan; use Vichan\Data\Driver\CacheDriver; use Vichan\Data\{IpNoteQueries, ReportQueries, UserPostQueries}; +use Vichan\Data\Driver\{LogDriver, LogDrivers}; defined('TINYBOARD') or exit; @@ -31,6 +32,26 @@ class Context { function build_context(array $config): Context { return new Context([ 'config' => $config, + LogDriver::class => function($c) { + $config = $c->get('config'); + + $name = $config['log_system']['name']; + $level = $config['debug'] ? LogDriver::DEBUG : LogDriver::NOTICE; + $backend = $config['log_system']['type']; + + // Check 'syslog' for backwards compatibility. + if ((isset($config['syslog']) && $config['syslog']) || $backend === 'syslog') { + return LogDrivers::syslog($name, $level, $this->config['log_system']['syslog_stderr']); + } elseif ($backend === 'file') { + return LogDrivers::file($name, $level, $this->config['log_system']['file_path']); + } elseif ($backend === 'stderr') { + return LogDrivers::stderr($name, $level); + } elseif ($backend === 'none') { + return LogDrivers::none(); + } else { + return LogDrivers::error_log($name, $level); + } + }, CacheDriver::class => function($c) { // Use the global for backwards compatibility. return \cache::getCache(); From 6132084b4b7a567e746a39eec4f24142b57502c8 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Fri, 4 Oct 2024 12:57:37 +0200 Subject: [PATCH 04/85] context.php: update LogDriver --- inc/context.php | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/inc/context.php b/inc/context.php index 75c107d0..933b5e5f 100644 --- a/inc/context.php +++ b/inc/context.php @@ -1,9 +1,8 @@ config['log_system']['syslog_stderr']); + return new SyslogLogDriver($name, $level, $this->config['log_system']['syslog_stderr']); } elseif ($backend === 'file') { - return LogDrivers::file($name, $level, $this->config['log_system']['file_path']); + return new FileLogDriver($name, $level, $this->config['log_system']['file_path']); } elseif ($backend === 'stderr') { - return LogDrivers::stderr($name, $level); - } elseif ($backend === 'none') { - return LogDrivers::none(); + return new StderrLogDriver($name, $level); } else { - return LogDrivers::error_log($name, $level); + return new ErrorLogLogDriver($name, $level); } }, CacheDriver::class => function($c) { From fe4813867b757eef27279af9eaa5ccc5235147ba Mon Sep 17 00:00:00 2001 From: Zankaria Date: Wed, 11 Dec 2024 15:18:26 +0100 Subject: [PATCH 05/85] context.php: fix log init --- inc/context.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inc/context.php b/inc/context.php index 933b5e5f..f451034d 100644 --- a/inc/context.php +++ b/inc/context.php @@ -40,9 +40,9 @@ function build_context(array $config): Context { // Check 'syslog' for backwards compatibility. if ((isset($config['syslog']) && $config['syslog']) || $backend === 'syslog') { - return new SyslogLogDriver($name, $level, $this->config['log_system']['syslog_stderr']); + return new SyslogLogDriver($name, $level, $config['log_system']['syslog_stderr']); } elseif ($backend === 'file') { - return new FileLogDriver($name, $level, $this->config['log_system']['file_path']); + return new FileLogDriver($name, $level, $config['log_system']['file_path']); } elseif ($backend === 'stderr') { return new StderrLogDriver($name, $level); } else { From 2c0c003b2cbc64a99bf726d1cf5be6f2e51fc0b6 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Wed, 11 Dec 2024 15:34:51 +0100 Subject: [PATCH 06/85] context.php: report deprecation notice on syslog option --- inc/context.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/inc/context.php b/inc/context.php index f451034d..631483e7 100644 --- a/inc/context.php +++ b/inc/context.php @@ -38,9 +38,15 @@ function build_context(array $config): Context { $level = $config['debug'] ? LogDriver::DEBUG : LogDriver::NOTICE; $backend = $config['log_system']['type']; + $legacy_syslog = isset($config['syslog']) && $config['syslog']; + // Check 'syslog' for backwards compatibility. - if ((isset($config['syslog']) && $config['syslog']) || $backend === 'syslog') { - return new SyslogLogDriver($name, $level, $config['log_system']['syslog_stderr']); + if ($legacy_syslog || $backend === 'syslog') { + $log_driver = new SyslogLogDriver($name, $level, $config['log_system']['syslog_stderr']); + if ($legacy_syslog) { + $log_driver->log(LogDriver::NOTICE, 'The configuration setting \'syslog\' is deprecated. Please use \'log_system\' instead'); + } + return $log_driver; } elseif ($backend === 'file') { return new FileLogDriver($name, $level, $config['log_system']['file_path']); } elseif ($backend === 'stderr') { From 268bd84128609a53459f6480d75985733e0c031d Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sun, 4 Feb 2024 12:42:21 +0100 Subject: [PATCH 07/85] Refactor the logging system --- inc/config.php | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/inc/config.php b/inc/config.php index def2cd27..64fd9fe3 100644 --- a/inc/config.php +++ b/inc/config.php @@ -63,9 +63,22 @@ // been generated. This keeps the script from querying the database and causing strain when not needed. $config['has_installed'] = '.installed'; - // Use syslog() for logging all error messages and unauthorized login attempts. + // Deprecated, use 'log_system'. $config['syslog'] = false; + $config['log_system'] = []; + // Log all error messages and unauthorized login attempts. + // Can be "syslog", "error_log" (default), "file", "stderr" or "none". + $config['log_system']['type'] = 'error_log'; + // The application name used by the logging system. Defaults to "tinyboard" for backwards compatibility. + $config['log_system']['name'] = 'tinyboard'; + // Only relevant if 'log_system' is set to "syslog". If true, double print the logs also in stderr. + // Defaults to false. + $config['log_system']['syslog_stderr'] = false; + // Only relevant if "log_system" is set to `file`. Sets the file that vichan will log to. + // Defaults to '/var/log/vichan.log'. + $config['log_system']['file_path'] = '/var/log/vichan.log'; + // Use `host` via shell_exec() to lookup hostnames, avoiding query timeouts. May not work on your system. // Requires safe_mode to be disabled. $config['dns_system'] = false; From cca8d88d91dca6706a9bfd53488ed52de264ffad Mon Sep 17 00:00:00 2001 From: Zankaria Date: Fri, 4 Oct 2024 13:10:38 +0200 Subject: [PATCH 08/85] config.php: update LogDriver configuration --- inc/config.php | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/inc/config.php b/inc/config.php index 64fd9fe3..25031bfb 100644 --- a/inc/config.php +++ b/inc/config.php @@ -66,18 +66,25 @@ // Deprecated, use 'log_system'. $config['syslog'] = false; - $config['log_system'] = []; - // Log all error messages and unauthorized login attempts. - // Can be "syslog", "error_log" (default), "file", "stderr" or "none". - $config['log_system']['type'] = 'error_log'; - // The application name used by the logging system. Defaults to "tinyboard" for backwards compatibility. - $config['log_system']['name'] = 'tinyboard'; - // Only relevant if 'log_system' is set to "syslog". If true, double print the logs also in stderr. - // Defaults to false. - $config['log_system']['syslog_stderr'] = false; - // Only relevant if "log_system" is set to `file`. Sets the file that vichan will log to. - // Defaults to '/var/log/vichan.log'. - $config['log_system']['file_path'] = '/var/log/vichan.log'; + $config['log_system'] = [ + /* + * Log all error messages and unauthorized login attempts. + * Can be "syslog", "error_log" (default), "file", or "stderr". + */ + 'type' => 'error_log', + // The application name used by the logging system. Defaults to "tinyboard" for backwards compatibility. + 'name' => 'tinyboard', + /* + * Only relevant if 'log_system' is set to "syslog". If true, double print the logs also in stderr. Defaults to + * false. + */ + 'syslog_stderr' => false, + /* + * Only relevant if "log_system" is set to `file`. Sets the file that vichan will log to. Defaults to + * '/var/log/vichan.log'. + */ + 'file_path' => '/var/log/vichan.log', + ]; // Use `host` via shell_exec() to lookup hostnames, avoiding query timeouts. May not work on your system. // Requires safe_mode to be disabled. From 8f7db3bdef17003b42da484471821a1f9faf10ab Mon Sep 17 00:00:00 2001 From: Zankaria Date: Thu, 19 Dec 2024 00:33:54 +0100 Subject: [PATCH 09/85] FileLogDriver.php: flush writes to file --- 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 6be3f4bbff8426064671c3945c76375a7b49c1b9 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Fri, 4 Oct 2024 13:01:32 +0200 Subject: [PATCH 10/85] post.php: update LogDriver --- post.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/post.php b/post.php index 6e7c6ab6..d3ba4728 100644 --- a/post.php +++ b/post.php @@ -5,9 +5,11 @@ use Vichan\Context; use Vichan\Data\ReportQueries; +use Vichan\Data\Driver\LogDriver; require_once 'inc/bootstrap.php'; + /** * Utility functions */ From 665e3d339a7a310a9fe2ebc90b30ef4da7f72293 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Thu, 19 Dec 2024 12:13:04 +0100 Subject: [PATCH 11/85] post.php: use LogDriver --- post.php | 40 ++++++++++++++++++---------------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/post.php b/post.php index d3ba4728..27a45413 100644 --- a/post.php +++ b/post.php @@ -9,7 +9,6 @@ use Vichan\Data\Driver\LogDriver; require_once 'inc/bootstrap.php'; - /** * Utility functions */ @@ -356,7 +355,7 @@ function db_select_ban_appeals($ban_id) $dropped_post = false; -function handle_nntpchan() +function handle_nntpchan(Context $ctx) { global $config; if ($_SERVER['REMOTE_ADDR'] != $config['nntpchan']['trusted_peer']) { @@ -435,7 +434,7 @@ function handle_nntpchan() if ($ct == 'text/plain') { $content = file_get_contents("php://input"); } elseif ($ct == 'multipart/mixed' || $ct == 'multipart/form-data') { - _syslog(LOG_INFO, "MM: Files: " . print_r($GLOBALS, true)); // Debug + $ctx->get(LogDriver::class)->log(LogDriver::DEBUG, 'MM: Files: ' . print_r($GLOBALS, true)); $content = ''; @@ -612,8 +611,8 @@ function handle_delete(Context $ctx) modLog("User at $ip deleted his own post #$id"); } - _syslog( - LOG_INFO, + $ctx->get(LogDriver::class)->log( + LogDriver::INFO, 'Deleted post: ' . '/' . $board['dir'] . $config['dir']['res'] . sprintf($config['file_page'], $post['thread'] ? $post['thread'] : $id) . ($post['thread'] ? '#' . $id : '') ); @@ -701,9 +700,7 @@ function handle_report(Context $ctx) foreach ($report as $id) { $post = db_select_post_minimal($board['uri'], $id); if ($post === false) { - if ($config['syslog']) { - _syslog(LOG_INFO, "Failed to report non-existing post #{$id} in {$board['dir']}"); - } + $ctx->get(LogDriver::class)->log(LogDriver::INFO, "Failed to report non-existing post #{$id} in {$board['dir']}"); error($config['error']['nopost']); } @@ -718,13 +715,12 @@ function handle_report(Context $ctx) error($error); } - if ($config['syslog']) - _syslog( - LOG_INFO, - 'Reported post: ' . - '/' . $board['dir'] . $config['dir']['res'] . link_for($post) . ($post['thread'] ? '#' . $id : '') . - ' for "' . $reason . '"' - ); + $ctx->get(LogDriver::class)->log( + LogDriver::INFO, + 'Reported post: ' . + '/' . $board['dir'] . $config['dir']['res'] . link_for($post) . ($post['thread'] ? '#' . $id : '') . + ' for "' . $reason . '"' + ); $report_queries->add($_SERVER['REMOTE_ADDR'], $board['uri'], $id, $reason); @@ -1785,10 +1781,10 @@ function handle_post(Context $ctx) buildThread($post['op'] ? $id : $post['thread']); - if ($config['syslog']) { - _syslog(LOG_INFO, 'New post: /' . $board['dir'] . $config['dir']['res'] . - link_for($post) . (!$post['op'] ? '#' . $id : '')); - } + $ctx->get(LogDriver::class)->log( + LogDriver::INFO, + 'New post: /' . $board['dir'] . $config['dir']['res'] . link_for($post) . (!$post['op'] ? '#' . $id : '') + ); if (!$post['mod']) { header('X-Associated-Content: "' . $redirect . '"'); @@ -1881,17 +1877,17 @@ function handle_appeal(Context $ctx) displayBan($ban); } +$ctx = Vichan\build_context($config); + // Is it a post coming from NNTP? Let's extract it and pretend it's a normal post. if (isset($_GET['Newsgroups'])) { if ($config['nntpchan']['enabled']) { - handle_nntpchan(); + handle_nntpchan($ctx); } else { error("NNTPChan: NNTPChan support is disabled"); } } -$ctx = Vichan\build_context($config); - if (isset($_POST['delete'])) { handle_delete($ctx); } elseif (isset($_POST['report'])) { From 5b4d1b7f4c7b420c5ccbf8a0f6a780e594da83e4 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Thu, 19 Dec 2024 12:22:24 +0100 Subject: [PATCH 12/85] pages.php: use LogDriver --- inc/mod/pages.php | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/inc/mod/pages.php b/inc/mod/pages.php index ec33b128..155c19a6 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -4,16 +4,19 @@ */ use Vichan\Context; use Vichan\Data\{IpNoteQueries, UserPostQueries, ReportQueries}; +use Vichan\Data\Driver\LogDriver; 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 _link_or_copy_factory(Context $ctx): callable { + return function(string $target, string $link) use ($ctx) { + if (!\link($target, $link)) { + $ctx->get(LogDriver::class)->log(LogDriver::NOTICE, "Failed to link() $target to $link. FAlling back to copy()"); + return \copy($target, $link); + } + return true; + }; } function mod_page($title, $template, $args, $subtitle = false) { @@ -54,8 +57,7 @@ function mod_login(Context $ctx, $redirect = false) { if (!isset($_POST['username'], $_POST['password']) || $_POST['username'] == '' || $_POST['password'] == '') { $args['error'] = $config['error']['invalid']; } elseif (!login($_POST['username'], $_POST['password'])) { - if ($config['syslog']) - _syslog(LOG_WARNING, 'Unauthorized login attempt!'); + $ctx->get(LogDriver::class)->log(LogDriver::INFO, 'Unauthorized login attempt!'); $args['error'] = $config['error']['invalid']; } else { @@ -1489,8 +1491,9 @@ function mod_move(Context $ctx, $originBoard, $postID) { if ($targetBoard === $originBoard) error(_('Target and source board are the same.')); + $_link_or_copy = _link_or_copy_factory($ctx); // link() if leaving a shadow thread behind; else, rename(). - $clone = $shadow ? '_link_or_copy' : 'rename'; + $clone = $shadow ? $_link_or_copy : 'rename'; // indicate that the post is a thread $post['op'] = true; @@ -1784,7 +1787,8 @@ function mod_merge(Context $ctx, $originBoard, $postID) { $op = $post; $op['id'] = $newID; - $clone = $shadow ? '_link_or_copy' : 'rename'; + $_link_or_copy = _link_or_copy_factory($ctx); + $clone = $shadow ? $_link_or_copy : 'rename'; if ($post['has_file']) { // copy image From b2029d2533a90be56248434ff310a46ef48aa123 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sun, 16 Mar 2025 18:17:42 +0100 Subject: [PATCH 13/85] context.php: log error if log_system is erroneous --- inc/context.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/inc/context.php b/inc/context.php index 631483e7..11a153ec 100644 --- a/inc/context.php +++ b/inc/context.php @@ -51,8 +51,12 @@ function build_context(array $config): Context { return new FileLogDriver($name, $level, $config['log_system']['file_path']); } elseif ($backend === 'stderr') { return new StderrLogDriver($name, $level); - } else { + } elseif ($backend === 'error_log') { return new ErrorLogLogDriver($name, $level); + } else { + $log_driver = new ErrorLogLogDriver($name, $level); + $log_driver->log(LogDriver::ERROR, "Unknown 'log_system' value '$backend', using 'error_log' default"); + return $log_driver; } }, CacheDriver::class => function($c) { From 6ee867040144d14d5e032f4ad9bdb74a2c4a0922 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Mon, 17 Mar 2025 16:17:42 +0100 Subject: [PATCH 14/85] post-menu.js: use unicode code with variant selector for equilateral triangle --- js/post-menu.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/js/post-menu.js b/js/post-menu.js index 79cfd868..c2155c00 100644 --- a/js/post-menu.js +++ b/js/post-menu.js @@ -104,8 +104,10 @@ function buildMenu(e) { function addButton(post) { var $ele = $(post); + // Use unicode code with ascii variant selector + // https://stackoverflow.com/questions/37906969/how-to-prevent-ios-from-converting-ascii-into-emoji $ele.find('input.delete').after( - $('', {href: '#', class: 'post-btn', title: 'Post menu'}).text('►') + $('', {href: '#', class: 'post-btn', title: 'Post menu'}).text('\u{25B6}\u{fe0e}') ); } From 501e696891c68e38c9897eb70682ab820cd6946d Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sun, 23 Mar 2025 17:05:20 +0100 Subject: [PATCH 15/85] banners: remove just monika banner This reverts commit 6bcf22aa7ed5d341b8f6201977df5d55fc5fdc82. --- static/banners/just-monika-opt.webp | Bin 26620 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 static/banners/just-monika-opt.webp diff --git a/static/banners/just-monika-opt.webp b/static/banners/just-monika-opt.webp deleted file mode 100644 index 731f985a228069545967c42ed4b0a25d131e2307..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26620 zcmaI7WmF!)(k?owf3QIk(p8 z>1RrMs;Ya9)r_K~nAoie0H}!y%d5(Bs>1^SK>FEe!2lxwkQ5P-&jSD41;Dc`jjS9X z1p&ay+ROzo{cbyz=jD1KR4e~u5X{=e|A|6tGm zU@J?fPv8Da|A`Rsx3!AWXHW6j2mx_G5|9DpKhtUmI00sWCEy6qfA-d&90x%0Q!e~} zvB&?fz5J(@;ir|^=Zug7KL8uR3NZMuJ@B77_|*BN|H-X`F$?p5Szw65006cA@o_^5 z05I_Y@D}s&@sj=V@sfXkX#Zd0hW#}7|MK?#tMmWn?{no!)Bgql2HGxM07=4`--6B zm%7yJ{~}%3G~|z?WSkLOzYV-> z=0oYRh>J0EM@-$I%Adj=wPf+ju6vARG~A34wSu;-g=C(S?;o#$apR>SuyK$Xqao^| zg4-v}=R7A(7}pqSX2|FS%s4AdxK0~t9c#(Sygmxk$ZsJMy_?oPAyRW*EGEUwAKR>dP9eYGA!71g2;z(~0AH zZr44oKTKZtcmE~v)Vl}rPz#bg3xI(rG!7vlY!tRAI{baIQrL7bvV41R;&?nuo-3eh zmVv@D3rN9`<3@xa|M`=9n(U4EyQn!1a9C((pLBZ>z?Iu(Jk>`GkJb-W{m5d)%=t>P z&540cHuxwA^6T?+SQsg+g%h;zgE9@Xd4Ewb8E+hJ;NSg~vEJ}1wWX5c5&ik}Oq#l8 zn*`M`G*rb1$_YwgTQC3uHqKVqW#n*@I{Uq9+;Wjwp-z)+$9OeDuODTnd6rWn#Z%>) zCMnJV&X3VhV%R4?g3=?^BuN6Aoq|w`ro`92XjSd{LiiBH+(2&PEQ4Qefg%40Cs9Nt z=?Lwg#P4&X_1)D2E}1vcD1W$`7Zz7p72Ox!hbHQn@=M|b{hwO~Fz{?JzFR0FB=cXm zan;4jYs9>aTGr}{#|A1yZi>f*5Wm@Q-0XZcaoZ?!TVp`+5g6D>wsU*fz=)T+GkCqb zZLj4*X(CY@m*4^6!-G+&O)(;}n$t$j?I`GWDr%s+v}ov+RH+)Vsk3Qg-Gy%~za-jt+mM)-z1P3pG-?-;1#N8(5p+YW zY^cB}trrrrMNZSSz=M8#(fSUX!(ho)C7xPRG~)-JawKhl zl*!%x#z~_@B7g|vYCjw`=>oYTf0|uF+ocA_b_g?vtU1LpGbiy3^Juwg~*I>X0Fv zx{MM&wB51q?12!ax0x6G6g`i2d0@KtdASxuJx#`4tX$bZ6bs#w1tIHxSH(dbBiwk~ zudzfLq%c$}&rg%8Wq$bi!aWo@vY5rePW{co z-fANAH5c3|0x0??H4}D&+&=e^lAg61+8h@(DWMQJ;N=Got!`OSe`0N6p6H~S)ZG92 zyWPG0-jc_dlzotW`1jzoUiKmij=~0FcU80>tco z0e=OVB{(9}He)tzVNn(dIE4}ow()0C3t@SpC25(n~`ev=b5b%X@a6eK>9!N$)6hJ0YYxCQp zHFRsbl(Qr{aiBPvVxB1KI|5~SfUg|#Z5`Gk32mln0CfMU73DlNO=Nj?AEzBDDrLfOdI*Kh*m%&QCFU8P{ zoLAfX7F9?H+@{&kUK%3VIBgcmllNK-q1QJV@3mlt0vtq0sm^K3g*vOgh7y7p0}8kO z`s%V$xvbW%ShK?BL$d_qObMM*su52q?LFp_$iP1^sazNoN0GY6Fe zns|*>5el25qGPzG-&u&#x_=GYBj{Ihd)^U&QT1$d@98;V2rGB&(5-q#87J7`ZZmMJ z5!qBb73bAhSr@Ce)Vg@^XT7<+Jt9^r7|*ZMEy1x!P$Qo&E|)DQE=}mmuY=t}I~N1r zz-5gQ>$Lo}lw`G1Ofgii^72=h#+rB(QI8NWgzkqJm)a;H%@+&j;X135`UL)aw?;}) zPi&;TY)Zu}g&UWNKm}XWma+&lG6_#aigQ(x>ND|_f!Cc=_U&h_g@M-lrlM~R`CB|r4^bqjghzIVn`EEQX=)c-!Ot6z7&FdAig z$e;^1U=ay;b^yNb0fzfidr1B%{AC1RGI-E4OyQs6GjsLZxdS>KpY%PTF zeidghR1-iZnuz){s*WADbF|eZAk}N*v`a89j}_x#FN^RR@R@xtv0wh!FdB)yO2&m# zSoMqF&J&!XX)W?l`$y+esJrZHo}X_mfmO8AMTSAfBH4d1%4KrVrm8ph=k8q# zqTXsGy?^DP?XVm9ZwxQ+1ZU^^Yl7c4g zS2ogG%%EvjikQ+;x{p(@*3Z>GYE@&k;+@90V48=v7f*uf^=hx0jP~CLnbJudfDltSI)>=sYD6 zUVe;VP6&eJNrOB2-t_>UowDQ1huek$us`%eutXTZB=oi!IaUiKraU+Z9F@rumJ^od zkMdL`sVD%VVJxOd93H`^=1YM85(3bt_t?FAxBGx~6#1dfrN=-pMt|URemA~dhS2dj zvV+!RPE-?2MD==H4&5i+GshiF)m(CnFeF#>!v*K+(f|n)GvtWB8V+NaXQDcoZSr~rbC_;6Z|hnocuy4<-);uCH2F7onJg1 zq=aWhIq@O({CXJq=7By1+XabGsvg^XHBH5RYdhoHo!F3;*0Cq1Z|mG5SH*z}20TP> zMT)@H&%0EMQllO|$)_evn~Xit(1%S5^+BK7QLCP8aY;kPKo*vcHbL}d8%(6HXxcq=XujsSWnK`4 zq(~QaewL4YC0nX=(_9dR1*ZfsyAuvJt zWc99Mgc$?N2#pM=kaU&$gGB65OsQG2uVEXV6jBF4?l;5&FGn93Z>PI){>x7e~5$D(sGr6E-2Xh_LF8BTvBnD=RZ?F`l& zismR3%gHRT@OdramAh$&p(1Vd&(GseR>~xfBPq^0C!kX8$>}F0+J#6nVfXRM@EN%k zF4;#_9zhgsk2SbK(kr7U!uXfn2+eD100Ky}AR`KTl9OgOokMzcTVBUUm;uza!zE-{ zylw?KWg$Jr06?f_e`^a40O5M>6`|?29-ZmQO!Npq285b#BzVfWu~8ezD>JzsOWzgF zjIcyq&{lQKk(y!{5z+4Yk)K{ngwblt9$gZ9r81j`LWW(ZOq1 zdnbY5$d_^JLZsUoOD%a2zP~4=4Qgrznf1~1%g zW`m7?v-&OLV}xl~vjWN&^#oh+amd~JY4ZN;)qCZ+@S^QGtM|@nkBVw{gMZ)uB70z5 zU>l3mosfnU%%$D{4Zs3Wu3%=#V87%n0hQm3GDrYeV7a>ze2F(u<+k*P3JVIk84(qL zw=4YZu~C01a8BQZSInpsdiloRp6OFHB&#T-$+Sjhn(OIY~gCJREu~ zEjKaf29IW&4X5e{A*HB1BnUkaKI;cYhQ~$M^SaQH*2^Ob5X@*NJS^Sg6OIfV7uBR> z>zmZ^O_bi!MmAh1&>I>$mnbll7@zu$umv&3K?dRGDNm~(ez34IVpHvSN0Xqf{d;(W z-#g!)NFydzb9HvhPLSU3dm0%!a z65e+~33_TwTgT9>t|_lZzJnU!B`0vwtiQ)5B8Y3-{}2O)xtA(zWqRq9$~AvdgJqyq z!O2-g;y@#P&^!oumUWxDfH$y{o_b4P8!R?H*TrTy&@((+u_GS0W zj<$XMT4gWk+9ScdBixAi8vWAC9ZbCS!xZQ_=s%miJ1*Omm2D<@pu6W(ck?6dJId>^ zInK-Xbje`W9+B_a0b1lC9b)dYC5vha&%p&A_M+oCg3LkXWPFY}Sp`a1A#rFLI4nWQ zC&8^D_bHsf?BQ2U0=uti4byFEcrPny5^Ll@-&o*3LOC@_&4ERE56eQ%myo=&Lj^vs zLQYQtqTSvqq@TDE*Y*-JeJgf><4II||03OO_5Gv%js~YT+z*vwj2+ae8N(`F3kEuy!lAbUA1O^G|j>zFdOT zCl>0~a^|n7_WfafK?Xuq>Erco+6ckHJ|av);uXbz+}jdH ztF;i2!i+mqn~9b01pSf7dPu8~mI_{0275@o?q3+F&F*jTKR6FOM2dC!yC9xCqt>+P zWzB8i={j>@Gy0}V%S!5x!Fz24_2>8>Os}6VjZ*A@u&SEG4_5%Z%HC+v{9{kgYZwW2 zu;Pa-!LIhr_vf@s1_h-Y*MVncod#3mP=Qc1Bv2klK;!Tl{`>f_WpV4mfPxef+wWss(GP~c+OgXFx5aYGbZg<%R5FXWia4dLN_ZpaZEs4U!P$9IFY z{%9hp0|O*zkM&o_e9ZuF)9K_ScOT)(cPnyin(XZogC7vR5)_C#e^PnLf38@H@5@Q* zRFv@J4GN?5Pa7$pvsyIWg`Lq!NvbSjDom0ab%#aJH~A&HSD~ABA+r^(l{n^HrbX5} zL4?3KE4DH759d+Q9M!q=FZ3FmVKIMMy|7PGLDH+3SkLDZzpgE_uzz;nr&;TUR?~~( znF;?bqzop5bZ1sWxS+25i@4VYu>$7W1btH)8fkGnzC>=Z<--smL;DxlNNOpt2)F;!YL4%_e)b!385&`A*-SgO!s_XXG_BMln{CnD&(+ki}b?9<7Kjq(q};&7#C? zbX1@Ir^(~H<<7dmN4k#QlRxGGVSf~!7NLOcev967yiZ5$)HqC`?!`$1cEiZG{^WBT z3kUp%kN&5PmjjA1H@l7-_R7%Yb!YYH`kslW^1oDwKrF7uxJd)mS9s7UiwXA0rG-xc zxN>cbpgaGJ*1=@u#FMT1Q?nb)4|G3MeHE;BB)s|NE!i^XmIn8YR2?5tQ0!t3#@&5y zJb6T!t!goW3ERc5!oJ^P>d{r6>j|{75ew5E!)@_c-_r`3MH`ReTaKL}R^5vt&a7OjEq7?rD&?Vk4`EywXaO6_myLBt@qB>4gAx9vO%H1qL=VOguvf-Ma!cC-k6DMU=+Cv&eG zuHujW@F4-K(YHqqS-y4+3`h~ct;n{dRnEcYYmF>BmldxYe=jPbWzBh=iiqLpcp<7PZ zWqs@TMkml73w`&q9QS4dKX0LO#QA)fu2T_`ZI!ofr~QaW^d{UDo|mJjr9l!UW)H5! zN+@1N>wGtR-*hdbiPljb z``C6*{kRMw)B;2_W~${gL}Mx0U^S`?$`bNV^vvXs(Iw9Lrh;{jhtKjc$|xIj05-h& z1kIv+jCu8LU3gf7gV|!Fm0~P27xfxQ2Cu<)0iIhV>jSycu>BrV5Pmr zA^-|^? zLgYNlGHkc>>wb~E8?~f;DDI5+7BLeTBPwNpZO|oa2YfKdtkY!9ZY}LanZ&=@+LLR| ziP1qBAW3>_G%BNwvgc9LDo8z?wH;Aj(=hhXsT$AO*(SBC+WyVp2yPa(8$%cvVrQ71 zGAx}vE@iighl&=6_t(kXwz&)Kt~}S0Rujt$0LcykAS3D{B<5adN~55)9~^}EKYpSq z+80=jU^{K)jgQIhtAKwbcMJC6T0LUr(YG!y&K{^;OS>ilG2&C)!A{2pjNrk5 zYs>S7PgAv|W1jX6%NS1yQY8Bq%?>zs0M>BJA;;%2K5*%@u5_Aog_+xko5+Ooc&Z1uw7FWTkjgUfYitEoymJ(h5r8X zo+d^Ft;)4}@G<-X{&iLVq{3{N&#EV83+WKwl5r7Igc{5-C(l)$ybT_q1`2!k(0&&c zqyWy=f#!SV-FyFda(DH(w6cxMkCWe14JMV7e`c;=;~A8wZ`k?gFS@#}^B-|J z7k=B>mksxx+zCzF;P1k$ZNN|j2rI@k&nD|_Ep*yT|O$7CM{PBKeI4pJ-notyoxl%9O%o{9I6vCi62V+&6vQ@ zc0>T09mE$Q4fQFw2gl5_k#7NvRL84VR#r}zd!rCC9nO2DsUP}zXn0K#J`j5o) z<+iX+Hla@7?y{0(NJ*SK6MIeyY0Gq()@XSa4q4c#BU697i|UG0y)jE?Sl)?2+Tyg` zz~OFJOw39i0Dg_Au81NYs5+<9e!QUJ8}*yh*Y3Z|Sm}wFZ(u&mk>~gNVIGi&EO*2; z(d!=GH5UIMcNhCvt)BK(I4p@6492NyJabG*teKJXt29rQpePy}8$N!lyr>3PMlBl| zo-F_Ex{Yx?>8x#Nzm!sbJS*z4H{1{P5&aPVP`YUBvZzGeMAi1s?m0Nfe5 z>5HtaEV$OAuqGoupX@Obfp9#i;QSBcksVwi1i?CPG4~95>5yg97j$d`L;po9f=s-t z?MN^ipbisvgj0e?oI7=B5C7HQ^;Rg=0@TRQ9pNJ~X)Au5`F`u7_VGaOsPpIV^;o8W z@(SV6_xCHjps*rnIEDN$)}XW_@j|tKZ)*?SMoSB+U?tAa1|oz|XkVLPCb#N>)Jf)e zY!1#(YU*XDhBdZ}sR=aD^(D<>zhKGL?yX`}Ajql5#HX*R;@Nz2Nn|p6{P(vJ=i)5w zoRS&KZ`#{U^LT2_+0-~+>w|Byv1l2KhQGY$P|p^O+Vk4S-v&}Lldt;A6TI)t?7OiQ z2K#vc;y><$w?Ag{>qO3=h8vcZ`@KM$kxq{rBT_(cAb11wV^z+X-^urh#%)F**vAll z9dqP)VErgWksmWGbe*GrxiE&D!{}ERMC7|xzVu|QS|o76(Aph5V36*W@Q-bw>?xE{ z8{ehl=tq^_7c=%+1TzSVa8>1FVpw0&018ylpdU>rM}mc6>&y4Dt-&T=I7WbD7D-h5 zPSs)|qr1)oo=z-qqVGMZqXpK)@tL?LH2kZOsI^oMy5I*E3F|5&CIsARmWAqqzY;@Q z@(j_2Egl`9um7=0c@%sZVPf*mE72)=HNIM7=Drq33>TooeKN~c^$d9{NV8%5n$^sl zb1ij^27?9i7cF=YA8`x6i;I{D`IyM^vu@)pNhs?Xu`wQp_1&C)T=&aa1Pdqm8*ZZF zHjx$x_dU|Z!|X?f6rJ9dwo07_5i@2vclR|&4Y?Fga?4b>0VZq+K+U2##f%*In~WnP zq5_8(QEcaA^DTAc8_P!dYxL15<-=`BZyuBLSO9F`s6RSbBLX?i^K0AIhDf}M-YmyE z0WPB;_4*8Yf-g8!%4~IaA}QDyLExN8Gb+n>ZfP=U_ATM_m3c;dNLek9%j`jJ{xq@(&DHP;G!wd8W%M#ZMS$e*DP6J0N_xO zCsk>Ff4ULqgQN`H3O`@EHB9Xpvv)z;4xUfPqnp8iGj$gPxt$-Gn&Du^ob@?Wx6$DX z^nEDkLj!zIlPP>3BmPB{c(;RD5a7s(8)3yt>MI~dzcpuovCY=0?3Dw1^GS@my8{%$fA7H3Erg81JITi{74P9?kOQ#to@>gcIjnPsAH+P{ zZ<=N&k8OL}f15)x{wx|AOUDe-k>V5^YFGpMe@wK1Eugnb^DF}C%Fl)trIblXT*c-@Xe)80EBv1=Jyq0 zg0nw^s_VNgBVC%8&ekxPO#gzwODlM#Dq1paghOD*C_NE-y9<(|pF`82#M{hK-p zP0?-7l+LRR-GIUpipP&Ya7aMw{uGBp;1SLyiT;hegT;P9@8LV<3?w;2s=gOOrZ6Mw zdU}Id4K*Hwt`Ki#ots^O|Do46TW2TWOg-h_>f2qqkMz}g*}%oe!={M83&ax-QOt~$ zh(<72iy#euCZE>Yh(PX}47hmbP?xULL<>TNVYZ8h7!&|J97K7EyxQ)MC5*Ud^rkfk?a3>@FZf!TS#PH>U02;D^*;uapvK8{q=HN(;7Ybp~-}5l-hJ($e?@#S& zvsl#ey$d`mO@A3Ow~4+4(wu#rQcIq>FA2hV?Z{_~!U2b{yzVv}cD?ME#D`t~k$%Tw zGa z#*cdw%JriSownwpv|4H9Dz7KYvOBy?c6=%74;(}s<)?Ucawc&*YD;>{@oN&1l)nqhjw>HqbZtXLT1(}>(EtbEE+|gJ`i!vmf!lS`8ZJ9YvS}RYrp9EvNlnK0N zmOsuVl63vS0Iu9At4Jch2hdBgG2?(@n;q2juvfn|9wJ~Bh*hD1+=0k@X zMD@Ea-p&6qX9qU2%VgloWKYVKVo9tnJlPZ1y;&RFLjF0Qe#?T_ja>N&)@Z^CNYK4M ze?-#>gJ)w!H#K)G|Mjr2lX0E5XU>6y1Hi3EoBey-tkv%e9MAi)&V)_!&(OD-vcH;( zWLo*fDd&}5O+^r`3xzi64ry+fEe53T0S};(ejB}hakVwt4l_303#p>Ix&i6-sW@+bIxub*sC8qZ96I*Nho6{}3KR*%0Kkio4pWBI2%0~%qZ@W}6?}Zu@ ze?n?=4uzt+vwzTHls}|#v)yjN3@<~xryUL3oVW8Ff0HvhzlITTB;!@U=P0dz9j|`p z8E}0uHAb~KD0F|n!(4#_^hg2yVyP)!aYMj*EAWq4C=NL6VLvQ5IJI38|3;@~GvzTy z19E08w(-CoQQgicl}rSSZt7Q5n(4}AjRX@(EuL1P;efp7WzMuA$WmW2;M6z63wN$% zBjaO_a{af}f_cx+(Ch>bcu$OEY#Wf_>FSnJVVM$0Y4iJrKF!D^SxEJPr=vgixnF)a zErC?-#aBTHYmfd7(@8bC0t{w&_8vTOGrjFL;=$GG366GzRcIGqkYHxQ{gySJyhhoj z!*H0yhj$Y{QLsG@EZM3>f$P7>d?TB~m*2On&-gi)Y9Uj&Um-??T4A+qla`z55#T-l ze0jYm;|0&CbxepZ_b3(xX1F&U`LvOMeUV1&<@8`I2-#uJ-Yv}FH{Kn30>%suZ2M5l?z=}EA61pX@DkQJ5Lu7TkbvF>b z`7A{>x~+&U^s$crJz0Pls-7>gja8o&`%Z5=g2khk&6&+fzNcK?spU2;6>(Hz!;xTS zZf%{kUaj*kB#1x*z+w_gKVE)gdLTGorPUu@ed3yh1lcKDDX-wVLiRr`O(+g=x=uWR zn%Gwb6aTR5OZF*fbs#Q5$c?1i?WC|g_p&kmj>jdq=)1Odhd>8U$1UZt`vNH!xwFZm zbk48}paOuZvh)5?xy)MajD>CIG#s^gUr@y53op{xNEdGBtSr9!=0cOMqS^x4%A|${5$=8 z(Z^zB@OfZd-t|1x@ zM#03pD%gGNn_7>iXB%r$x$0>ouLZp1UsEJxWQ@1=afMIf^b0vr{Ql-R%F%}^$F{>B2q%+kN^MznkTH&q6OxY-d@JZd@ei0TMy)@#uK>G z4SCGQ(ZIe3MO;lP^gOcgb`oef&l-Bv|UH7zXYx~m^nO5tEonF*QaM1i#%?TwG@&$`aKPH6tQP7)tA@g37CYQ|1sW(7j*&ky?RCL5!Wm-qyN z4>(3Cr5B*W-!DT9A|d2q90JmU1xcZqk;ys*W!>U_LdG0F7eDZgu(*T4R>Z%Md`yj7 zD5#_D3r&n%Z{8vPC0RAE6SFf1l(83|ahVwbnOKKYlDCG3=HXU$TANy?J2Rtb!~=V6 zQodgk=?VQ-t-i}@l^wxE+>qi~08Aj^@PK5!_V{p|YB2uT6Cun0kI^dIxFufLAG96gC zsrTa?NPo-38W2vYw`>IiCp&GKFIpoi^bEi@QR%f&K=D{)G2j&OG_YwYSbaq3Ie!mb zR#=UvoVsJQut*_q{0<=Ur_LuM2MkUivVEzQmaLe0xh(`O%p;OgS?k zGE?;$OP|w(P?DF~F_EW}gbQNbDWdlOe0T@5q;bca!LA01ahgXoWCAcKEe8Xg&Pt7;Ikp$hX44Y!^siwnd!1A zjf23BuB2w?p#oHnW&I{f$SWGWvRA$3KVQ>9){{57(o_O~B?knL_wB3uwkn+G>2me` zpO#Hm${*~6WLZ#6if$dtYI3|{{UP7vvq(vcC7FMTck2x7wKXN&!a%IC{`tkzv)*fL z{_~uhlQaer3XxV2tIRtqjG(0Q3yluu;Gw*W=&_^q>I+lq8+Fzqh!iq=qGRsS8`LV3 z59dO`L z!kGQB#qEWM1P|agj(4Kd<=axnyiV}`C^qAfLZw)~ed_BJTJt0mPeav@oH&u0z{UP9 zR`kR4$vyxBG%NIvIz+_)?Y40}x`MOa3C%%uRVTIX_LAy$2qWZ_(<+9q$B!MQeBSlx@Qd5kx|jDH1#N$N!ZTXn2+QGdO6yWcb;;1PHvK69(^KpO$? z*yk%q0p}(>TflQU!_Ie7rt@HtO0q-#fpjkf`M_0nVBGHWl?ekr>0ZOiR2IdHqb}p< zL}?|n(chv}5U5l?z6-?})OK{SO=+%&ZAPUy{mpSrk5R@=d_&_vlKO~#tLr1a1kt~5 zKm<|s!nCYfcVOwSnFmA)c^9@$Xm}dppWWav2)$RkBli&LCY!C_=gK?Hy&vcpt4RQU z3S$K`YzhjA_!qN6-KDYJ=Av9mP&n$~Xuoi&6*aEjSx?sAr}U8H4r0=w;afJ&Tbm2= z;Y0PGeKqkhOO+oMHy0nWq4*S9UY=YBM7%mql0p_Z2TE9!el<{|%ByenXUkK2-0u+c zmh9VTB^}>fbh5uOLw0teb5br(%yW3s!wXQPRLJA#rMvC=ATWSl!3Xu8ytmgkY@S2q zxa%Idbm1{K;XR;|Az{F|S0G>L?`9hc8d{V5iPM}C`B`0VYwW24WKDD(nC&knYcA^9 zz47d!2(=lo_}HfeMv08qe>j|2#?R3T~m^Kqz~ z2=hn$!1^gcCBLl7)asG=^}grP{n-%a`OroJNY`Ygg~z# zMj&`&LFeVwm2XaOm#?8@|ML2~+RFaO4{|n4>1l@1wY;FrQ=Geo+u6SwETgUmm-Z{R z+7FLNm~lRwCvz{^+bW_z%9Sk4{=wQ{e-ilcIF=qw2_7J{5hD~EAbsz>h; zO@6o00&mPQ{t~BlyA;X0 zfuZrcrs4f(vW=I{pDeE;^PTcYl0yrZ#bqMRCCYp3MF^$JPCvg0`fDo_>zWr2e}ZzN ziV7ASJT$N)5ZatmC-%#W(o=b1g|ha?!SZ|u?S6-A6d!4>STM_R`c~UQ*@O8Rk%_ly z2Ycm#+1Ui~dy#3MV2wB_WbQKfDofI)Mu(V{F=7ySImTuyuCcI6JW0(Zec=8s=N_4>QU{$^!ngk?%_AU+kEeN=ZwXp65M}~nE z1XrA6@P>~$xlB2_G|ptcDU#H4=PCh)uTm&-0*Ga&iJK8hb&N^A<}l)j&s#fIPV@Cv zPnXR5Cnv5T9L&99hvI9Rx$+%ZObysrq?TLk$eHJ=73_1wwsksagRm=Q_>b@R-t{$! zIuz39pBj#95EW26SKGI_#FZzghgg{P=rUQx-iWA+n6`Q2T?RUN&SrV;4qHi*@28C` z_EdS0EDGHng2P%VEZjU%O1k3mRDZx`k$X^JuDn7{Kd)rIxH^V;H0E=cS_uU|2Z&X% z(UT$x7K)%*BWArcBZlxgC}%o}zfJ5-e64-`9)*I0*phepsQ-Zm4#+Q4&rl+GwO#KA zo2>8hFNxB=&#zf^&v{!sY0cS_haO-_tQ@9%)s3B&M>|u&iBip%3X%I6kj!lFS)C!S zuzc%r8U2OWjD&B!!bXJU=aACLxiIT`%i$Y>;HZK7Dy`^mzV(aU^+>c*s#;CUrM80< zzQ4Zl&`Tl68oR=)fh}6Qt$E#dcYjew)N$=epA(5waJ1q?6<&Uy3-Zc;@!*#6^q#IVejm-{eF{G%m44K1q@I%A zv24?h1fqDJxdb;@%jjwpWoal1z0A+_&-xXIyMZgvNTnFUzF391^3=z)L9eY#fL8m$&A5XjG^ALtki`Vys@p9ho zPVYXV=1NejW)A`1ZF=-Rv}%osq&%f;e>n_#;eroC2}kQO{%bn%(Te z#C-JeTSR>9@J@?HzY|FC57uU7px}mN9LH=mgZb{1W_+=?YA@>#xF=3 z<)X>#uAmg=W0thX8fn#)m4V%qHguC|ZuimIPMvQOJ?KVdVzJ~Z|IT$PqAZ&w$tAK$ z>1G*j`K)a_>18>$La=C6F$ptdeeS$y0XRV4=Z3$9=j_FFr7;8&$|r#tLjmF-IF|*2 zgs9R^uq#fC!Y#n3Q# zvH7YeHEE7zArHQBF&M2=1S)T#k>cn}^IWYE&xTF{3P`A_SF2TMKQ=oR0H{qBUPw}i zdnhfJypA20dNf~HdDKp7b)#auty1zw9iJOyRB8bjR4@>z#C0)JWHxL2ukN7O=86p& ztxH?8v%V30JzS~tCX{~kBIIJ2tr6I)ihj+d6rbh9;X2CO2l+Al(UzbdJ$!&1SBvzS zupX^k;csl6~g^rgos5Rg7P#v1FKxcq;zd4cOn317KB>8>`es8fLt__x)Z$La-QF8C?WVjj)rU!S zQ&>D3i<|CSu9t|1x_-`qLS)?8_l^e8Bt^Sh;(b#nOM1v zIomnLnT9UzFm^i3MF2ee^x0hviNhX>ski?Bf3`>0zpkx)Sz7m0S3+U-iHec$()Eg& zHx=~R$X+Vr2Jx~?267P5F*j*McLl9gCmG4sQo1SKHG?Fsi3{)UibBU{i8iYHcntAU z0EBe(zdb83E)ibde3;0I?E-R?St6&HH;Oka7bSZ^YVcx5iVITeX}BQa5eYhYcg(aI zS(*+iPRfdR4SUtl_axHyl!Z5rk!$5>fcT$>0SJIrVTH6e+Raa?q;H*G<)Ek7UL~Mq zCPY;RJarP)8<+rpFMxknT+mJ`1K*wWzAqRQZLlO2p4UZ`s<+A-v$`jytKKk|PxRl# z<&>xOp5Jzzf;1tPt-cB(mI8`_DzwPvAIbk7U0#)1)=JMs!J(Hx>|WM+43#L6QAMo6 zbpI($s9CKys?Uv!?QHI+VYDUf%@S8gZvwE$i>7M$+zkM)6aZxVHZ(sR2mO1rgmz2L z&ZHj^#*@6^z)T3jOY@`cfAYnZ*{bL{T`e|QWEK6nz<3{`s4}#~)HWVktlj3p^!4xf zc-U8rea1;TT+5U^TKVU38VU$0kt*w9MtI$P$u=R+)^i<}L!~9{!4|&j*lO&96j`F3h%m>gOswhCA<+K`r zePm3+T>IkPc^9FeK*CilJOcs7%LBx{d;uq8_m~kZ=g%%%kx=R{D$uWwoj%;xZZAKb zdi|C#=41il0fFQgYK1DA*znQ&O(3bPn-CNDpBQevFn;vT^!{e=@(5@S_=AGON-arR zRO2HC$J!Uyn3OHWC!Qvecy%RNA1b59`Pvg_k;DL00UBl;ziOex;Mr|M!1fT<@ zd{ge({8*l1kSNYhJbMaLvyrI3s z1vp&gKi`ur6k5r5d^+nHZGaL0RP6M~R}EX+x#rmafq%e+3Ga;P-BJIn+TYajmwWj$ z=hafUg4<|S^FZ|%GAlyJ`PsQte5s0K26g>1$$mdjFB1*Qffo>32vURbRU_P?pGO{`409b)VkcX8V@) z#O|>_ZbLE1G+WzbPLYY{>R-EEYiC~0Yt?|8hi`YIsQ2dRZmQ^;!I#O7b+m^B0C~&wNF%=c@h9hVbpSwU*8=jw(4EStUr6e0mwt9KkXeJ$Nf z26TIU+=QG-8)=s21to84jbga}mUGr0T<3k6T~P*pZC2toLA=dV(3;2jc0W{`(6`*D z_x;I2Jp}2OO&DmvJ+gbD@ZyQw2}?ZR-}Vj-fD)iykMa__!>`jqO4ua1fxK zA0R2ob5rIKod^J%Qtap+ay^u1D2Qd%)D>DFHXs->3ls=v9`Tg3Ba~>;6<{?w8khI02mMVg!a2U@0@@6eXE+@V3TKGs>j3~-vrd8&P{NA zD}W&l05||-u&Ltn_W9Gc(!v^7@B8A%_>W7>-{v(ull$JC?=(05?^L{l z2e?H+0QSE(AE>CB0HG;C6ZR;!pKJ}I`qltFn$A=JKzi-^RxZxZ{ndQ^#ntx7L9mT? zc_D)CbJ_sR9)LBB09l}Wv%Dn>obLg?An88#x|+-JuBul~VIUXJ1tKL=Fhx`ZMIAv+ zU2+r&lWXOs*6^NRSy`w8NRL#xTZ$B9#Za=&hR0i$FGVM-l~2z~sKm;m!m+}uJjTOo z;8DbOI3LWOiqxt^nxQ>Rl6lMi{*uIKP%@tV6953?`lL+fjeV^3Uw?5jB&Jx)7rp*J z4bK^$=_SKO7zh-wpkxrihRW}E<~H)rdu9gvr8}}S{BcUi!k&S6EXI{tZd@qmN<2yQyZ8JYmH? zh6tmFfd!th0@5;(RzG}}(@Z4nP4@Nd*(vknfKusx=LzQhhAC`T$2Fsh7mj^v_BGKc z)_rTX;OY=uLKe3S0803mLaNfpF4EJrBU#lLQ9>lt(|bLFsS3c{7GqbrY^oMMdfx17 z*8WALu=jQN{v`j2(~gVawASkT!~Pv#3qJBBEdvkMo=&SgQ0~JH`r_zTdtz9&g-lWAJECo6r+ z>hvvH5FtmA^C4PJ*BAgcy?XXb0f6G|beLTO4IgzcJ*ZJuw!JVUPc^cX42H;-Nyp;P+NzaFju9`;0mQSJcF%(^~28gquREiquA z;40=F2tc9n`Qb;`^Htkj>K;N$AxM%SAxfoz0zur3w;u2eN#>5z6s8PQC}jc(uFB^v zYHJfN(nJPPI%(Fp=ikq|-6KJDjQe1QPd5;}p!QSGPE8HOtER8J+gWdWYuXJLN-BwU z*7ve&I&VN=-{Rr{pi;_lOgS2QXzFWkUa$^eO6p!e<;A2S=f}8-r<~dknS(F0>tjEE z#X#oxJ%PRWc3!P zyDMFAfP#sUj%ce!CetH0{v83kwX!RaGg?*3rBD$A%7F-VN-z;SQXv31QM@1kAa}pa zj*)DZLgnPnRkDOo^yj9akS=(w73m1D_35_zQZ3!=IV%Q^*1iTWo-!8A8YNy1kuBt0 zyIh|8$@7_GcoK1u?+^OkmH(0Ft3cw;0Wi;%T}7p$`+U~&V+T};CuF>k46P}Lxo)o` z4Bqy9Lb9{s#H9s)w^p+A(7XEyUjA=z1I)W%*`j}T`c}zltGFUP6NG}NM2SRE=}6V~ z(M})R?G3)^^Z~173;KZDtRdP;>*RWU-#4GJ)=8oU>ZpHh0o8%dtn_;J|7SagTK9}8UbvN2`3~Njr2pl zBLJ%MQx#D4L=5qxaY$I=ZX`(2>=l}jd9tcg%3A*|LNB*{f#mQ>CI&>JkW?C?Y&|e( zoYq)r=>g*ur^9w33ay$uG-O(rZ>+9M2DPc_iHc8Ju0=Sx7*99un&hLbL$Q!O#>Zqq zaH4jiP2ccq+8e)s*Z&&a1Rbc)-S5BsHn?v>AE=@UqLP;q(_*HWa1^{x))YVyLtlfJ zMgU`t1VaU;5GVu;hO=|#$ZWCstz7%z0vl}b@3j6s((}vu*9JzNG{L~ug+zn+rLl41 zZf-l*xs-lWVtCV2noVRN3j1P%}90){{Fj(UN8?$Pm zh%+?!3QS9Swx`Kx=_C=288yo@9i^jmwD8Ed9ngS|$Jt^pvq-<;g{AboJ1K~}18>5f zPE)yPPLF~vZ#aoF^d6PI4IBZ0;%N7Oh6jZa1OTN5AR!2FfN%h@syQs6KvbFl+D-1H z{{%IFKngi+Y|S>cUwZSV(%;{2x-ZOhPCE1t`lc}7<5H30N$QX+MEcWP*Q~&`f+)c= zsKZZdYcyU#2MGe8`z;iJ9i=t9G(Xgcz?Bkd;0^$w>X~9WFIaTJU;_Y>+r*Gci1eu= zjnvL%tQhg+yh3f_F#>zGxoc5bs^XhuXrr__OH_E_pSm9MG8~trE>#yRdm24nrm_Qot zF)eVQsvR8aULcl=9*WG7sy)*;rx>rTt$GRe;+%c=VrdFN9fybs2j1D=LAo%vF zj2t4B5fMi5>CI|9x;AOFY$z5GS}=9giG_pn948~S%Dx{^nXoEBzRF24~_k+E- zm%K4021LrM*i*->*bVAb$(r*$H?~1fryYIPL#+7j;;p1}n7zqI29PjO?-y5p`nBud z9-o}=W#3qxz!edpMX$De`vD0Dd-snyVg1Q_LIMy7@X`$iz;)i|deb^Te>`OoLV!Ei( z4fRh|z)9Qf{U^+Ok^mod{=qoR*gs^>W!8fw+D+?vv$IKJNb?m5Af5yuL?9O=o)o<| zr+%^~5&*(x3(Hj5&5DlLG?I(GR04x>g^&a3n8{x;nFU(XC z7(Dc9w0`-r+mj+|Fv6%xHaD&9(ux_ps{X=M)oHRetc^9Ti84rbdrCWrsTEA1#fC^t zotU2-HzyTRGD0vUpq4bm6NH3{o_|$|`(YE{;i*auQuI$S>j5TK#XDQ?ceU8ezf|%z z3{s9F4LG3ubSH=cf<#?U14@*^q~CiJceR|?Jr30j$wz2B*Z~p9LZNIGGd_ZrUxy#e z`r*gGk^wfnpp}ufuK<4Oi$6O#MO(lt=KM8Bx9=PQm25t%L?t<(>Z;ZnW0QijRyUx2 zrmt}p$d*h7aFM0678k)CXqD`!4Q~h1DJO?e^DRvz!XQFWMGDcsKVv_5R=5DWuoY_n zKZ3G*0AN6a&>!;mQKJ6gYA2TC0u&6sD+eD9!){IQ`oZUOU(UHVt^7L9amvYazz)L> zZ}jdu`|e);xT}5j6CwG?UUB5&vfl3n6328>x_|vGqu;xGsi zzunFRy`u&XyY#MNVRRq>qEaE%ttymSBi1lI1;>tRGj2qb`C3IwU1D@Q2Nc#I<7a`KQVh_b%Kr(tdL+IC!Lho zGT&c->mx-AY6DjbM6)J3OT3@Ug*$XyfrhK(ISfUOV006kMRcg0z@!AEG%cq7`NUzGkEd-K? z7u>mu0jhQOm%s3XohOd!$z#?G=&0ij^{RY}D!2%eMCrZi+hfNYPIvbcnQ=w*w4`@0@KvwWkh4fvmdmwFf)0wl0*u^pM=E_NMc9jPOYgS2SsPFPYT z%bE8(l&u;m$<65s$y#kWw>%VeDtpgU`tPR4bbhX53Xv$D;8xaDmQNhI{BZrPsGfqX zSKua~Xepg8^mO?z#NzFmlP>X4r|H>Wh6lSg#LyOFkWMW^hd|3s5}BOjQscekFZGYi09ruS#?oAS{kroczff#b1ojoB(BMrr|UAxhttWWTg7Y&(59E z1p0P?GgmEbELIBzUra?1005GY=BM<=RsK(#6b8N{41e$7!#|!9KK{K~dunLpU$_ot zta)r7z4NFNP~lj*$|OVVtX@+{H628j5BJg}b>`wPo28>A;8uc|lyu~K<_Md8(*R%*r}|Xh|iR zPgqj3SPVfx%Q^{e1_?;F1#(X=!PNmTB`rfjPmbwnQ2k-7b--Ec;#LQvv`%wXo53`hI!o_W7Rp*Pc)s=v)LO(RPR%E0RD zsSJHs?)ORAAaz3ks7hkI)b9`N!R*}eW$pZ^vcD#y2T+Juc?(Jb5P&EF5C8=L0dSah zs23;5UYL37mhSIw1jD$aJW~>h`W}xT#hu*WT-Te9V)Yp(K({kfC=4O!s8nA0<|*?2 z@t@vGRNby3bN2zjtLp#0Jz6&=&6j()N|?G5|jWC3h99o&o}@3dC%Oo z^90$t`KH4=NDb|!F6f9BO5}RJ*667`f8s(iWN54w>+k!=JEwTyhJsG$yn`IrW?1T` z?@--%=I8QMJk-)$BGvw#V^Zfaycj;2XQe|G(G z25x8#z#R7IAwf&hg0$*KH!l9c?6m*8y#0nN_=>)OfT_oma*>?CKQ){mA&I~!S`3Vs zAdzUBxI_{FkuV?vm=K8s*XKPqY8yh~s!?yd^nov(uG+V{F z;^0|rr@+xh+qK{Zd9x&mjV81e`jNb{!c_P74{iO9x}{r_`l_&LZc@r zm8>ON*b|QfkTjJl8)`>?bo~i#3t%I`01_~<@Q_sAKlz_G^TRL{f<%{RNP``~a^6%1 zNA+2%6RAs03d~?K%kJYPB#=m)w}TTwA|tSI=gfG$BLqrClt!zS*)cJxo20ZjA&U(0 z4%&txDqX7WdB$UdIJ;H^^{&Ln27RV*T=6s|K}l8Oo2oo&(&lay!t{zMg9dE|-BR99 zGOMX;48Ll5W}y?{HB$Uyu+1=6sG9FVN|5dQALsc#s%aIHVJ39l1xCF@?m*)z21hK1K_1aHl;dq=h^(5pa1{O=gjU+@^!%bZNq2K zq2*%7gE_8rAKXn&>p9^_j#PN+@EG?R7DZqdK|n$T;spx}?Rou~qw&eJO3Z}G{vnUP z+W&u93|Jn$S&)Y0sv_C|5hM_hhHrUuy00EX-0JkJ#;Y~5Bt-+T{+tLzsG0u3clPVWARUPSoSW%Mj-)#nBObGT8^*rA0=l?Gl--^nMuFk zvH9aVy55vQJHBklW|j*DKxY^r0}I%?LC|VXyr}KH3md=xUys8xt3zsmbO3Eo&i3@9 z?cl#~TL-CZTQori;6NZ|R8&*|AfXS&D>HCMV(Z-k0m4#u!ydT{SndR28-Gz$>PTRL z-)iVCBe9U*LBjl&CS31*^X6NA{_FkC-R<|HEjyPeL?u+N*{xhNsfgIp>Zt(utEPU? z(->&dKh)*(eOvh)rU5{J1PT(M&?7&j=1(mZD@I%xxeP$S$dQ2&2nsU;Vnk%N9kzlH zq8+OHmTxM8pB;R3F0VE-Ta8-$)`Y~>LWwUFe3IU>6DX6r7jzCL$Kbm6Y zG*$@Jy0G#PAQ+iQWS{^E$e0X(NsPdH5{1t^bKsHxWdkQG%V*Hf(3Sc`b&r2hdON%w z-T-&WBcNEN>c8H5Hec*`r=>YVFwlPpx6fY`N+56%1o)NmhNs;wqx4BkVm%ju8;X8V zs353JqJS{pwtKO1)F-b9AVDJ0;}v`d;@)K$oyDw=7ZSk>i5UT}B%n}$nX~2xR)}Zb zGZQ@UkrNQY^7&T33xYx-qCve;-R57=r!bMC?c4o@_17!e1osLHfT|JdJ7JCOZw|AL zCOfzIXT-O_x8Vs!(M=wmh=1vI1DN9-P{6XLN1>unfPk2O-H}}B&i?Jul|NW&-x&Q} zJS%;W0BCQB#r|Z_5HTD7!xRt#67ZQ|CUZp0Kp_)kf}qOBm81Bc4;XV`A_IV1ES-Vh z;CE2~00E$o2yvJ7cTo#W_Ohn<(htio)tOl)2~i*qQoJ=urT+c7VRx(i3fzO2VGoN} zI|83NH@ii9q$^OaP-_atWZl;XI!6OeqrmK|M~aNO#Xz0p$wN1cuWB+?gRCULSrw zPgn-f6BZT@AMHMH#DGFoLln-Bgoy!wiNv{zO&k#*YLl;Q^Y(wdqfiLW5;)lG4zL5wF2pD)n!UP2f#I*bB>%RKw^(m%;0L~aF=U~fT zLNAClnH^nm=CBtXtR}Ypn`~@MY6rx?Mgp8A9DoUodXwr@cMMB+OuvQ+;DP}F2)6st zF4K_Ns=6WNSkL3YIr;}0u6D!aKmZ9dbI#s{&lG};f~Qs%g`?5xtMo-Z=}X>HFt8j* zebP4ObeV3+yye7a0;qN*LVL!9+=q?t>W37lq6S0EmeRbK1h59q&Ss(_34^+xphN=1WY*fS?c8>~w&cEEn-JJ}~Qr&%ATa z8VouCJ1~sp1OO=3u#FLnrIo`h@_*B>%bMFy{Se<`>!4oRMys6{b_Re1tO0Iaf4F*- z5B(p5AqblM29_bd^bLLeo5Tl43>ZjMYXScQ+>Fxy8y14# z@d}^N%v6^0C%5~*x$FMpzaNlan*IS!lDm0;WFyEw>VEOuxL*Kpf7Qb+v7jU?)b3hJ n- Date: Wed, 26 Mar 2025 13:40:28 +0100 Subject: [PATCH 16/85] news.html: remove spurious stuff from css --- templates/themes/categories/news.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/themes/categories/news.html b/templates/themes/categories/news.html index 484c4eb0..76722e41 100644 --- a/templates/themes/categories/news.html +++ b/templates/themes/categories/news.html @@ -11,7 +11,7 @@ .home-description { margin: 20px auto 0 auto; text-align: center; - max-width: 700px;" + max-width: 700px; } {{ boardlist.top }} From 9431536112e98b708b1c8fb4ea20f2f0945496b7 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Wed, 26 Mar 2025 13:54:55 +0100 Subject: [PATCH 17/85] frames.html: trim --- templates/themes/categories/frames.html | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/templates/themes/categories/frames.html b/templates/themes/categories/frames.html index 6b2fac38..ec9cdc25 100644 --- a/templates/themes/categories/frames.html +++ b/templates/themes/categories/frames.html @@ -16,13 +16,13 @@ border-width: 2px; margin-right: 15px; } - + .introduction { grid-column: 2 / 9; grid-row: 1; width: 100%; } - + .content { grid-column: 2 / 9; grid-row: 2; @@ -35,7 +35,7 @@ gap: 20px; height: 100vh; } - + .modlog { width: 50%; text-align: left; @@ -69,7 +69,7 @@ li a.system { font-weight: bold; } - + @media (max-width:768px) { body{ display: grid; @@ -78,7 +78,7 @@ height: 100vh; width: 100%; } - + .introduction { grid-column: 1; grid-row: 1; @@ -97,16 +97,16 @@ grid-row: 3; width: 100%; } - + .modlog { width: 100%; text-align: center; } - + table { table-layout: fixed; } - + table.modlog tr th { white-space: normal; word-wrap: break-word; From 8da10af1014699d165b58abced86695242530d81 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Wed, 26 Mar 2025 13:55:04 +0100 Subject: [PATCH 18/85] frames.html: break long words to prevent page getting cut --- templates/themes/categories/frames.html | 1 + 1 file changed, 1 insertion(+) diff --git a/templates/themes/categories/frames.html b/templates/themes/categories/frames.html index ec9cdc25..1c4673cc 100644 --- a/templates/themes/categories/frames.html +++ b/templates/themes/categories/frames.html @@ -96,6 +96,7 @@ grid-column: 1; grid-row: 3; width: 100%; + word-break: break-all; } .modlog { From fa56876c3620f5aa0895fbde70216bf25b113bb7 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Thu, 27 Mar 2025 00:17:17 +0100 Subject: [PATCH 19/85] style.css: set minimum file container width and multifile margin --- stylesheets/style.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/stylesheets/style.css b/stylesheets/style.css index 815e1853..74c455a5 100644 --- a/stylesheets/style.css +++ b/stylesheets/style.css @@ -380,6 +380,7 @@ form table tr td div.center { .file { float: left; + min-width: 100px; } .file:not(.multifile) .post-image { @@ -390,6 +391,10 @@ form table tr td div.center { float: none; } +.file.multifile { + margin: 0 20px 0 0; +} + .file.multifile > p { width: 0px; min-width: 100%; From 81c02be563b65d339c572ccbfbddebff4d719240 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Thu, 27 Mar 2025 01:23:37 +0100 Subject: [PATCH 20/85] style.css: reduce margin between multiple files --- stylesheets/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stylesheets/style.css b/stylesheets/style.css index 74c455a5..4a090f33 100644 --- a/stylesheets/style.css +++ b/stylesheets/style.css @@ -392,7 +392,7 @@ form table tr td div.center { } .file.multifile { - margin: 0 20px 0 0; + margin: 0 10px 0 0; } .file.multifile > p { From 336c40b0f75d4c487a09035e7791a6ea035da624 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Fri, 28 Mar 2025 15:05:01 +0100 Subject: [PATCH 21/85] remove all tesseract traces --- inc/config.php | 9 --------- post.php | 34 ---------------------------------- tmp/tesseract/.gitkeep | 0 3 files changed, 43 deletions(-) delete mode 100644 tmp/tesseract/.gitkeep diff --git a/inc/config.php b/inc/config.php index 25031bfb..61325d10 100644 --- a/inc/config.php +++ b/inc/config.php @@ -985,15 +985,6 @@ // Set this to true if you're using Linux and you can execute `md5sum` binary. $config['gnu_md5'] = false; - // Use Tesseract OCR to retrieve text from images, so you can use it as a spamfilter. - $config['tesseract_ocr'] = false; - - // Tesseract parameters - $config['tesseract_params'] = ''; - - // Tesseract preprocess command - $config['tesseract_preprocess_command'] = 'convert -monochrome %s -'; - // Number of posts in a "View Last X Posts" page $config['noko50_count'] = 50; // Number of posts a thread needs before it gets a "View Last X Posts" page. diff --git a/post.php b/post.php index 27a45413..977bab05 100644 --- a/post.php +++ b/post.php @@ -1551,35 +1551,6 @@ function handle_post(Context $ctx) } } - if ($config['tesseract_ocr'] && $file['thumb'] != 'file') { - // Let's OCR it! - $fname = $file['tmp_name']; - - if ($file['height'] > 500 || $file['width'] > 500) { - $fname = $file['thumb']; - } - - if ($fname == 'spoiler') { - // We don't have that much CPU time, do we? - } else { - $tmpname = __DIR__ . "/tmp/tesseract/" . rand(0, 10000000); - - // Preprocess command is an ImageMagick b/w quantization - $error = shell_exec_error(sprintf($config['tesseract_preprocess_command'], escapeshellarg($fname)) . " | " . - 'tesseract stdin ' . escapeshellarg($tmpname) . ' ' . $config['tesseract_params']); - $tmpname .= ".txt"; - - $value = @file_get_contents($tmpname); - @unlink($tmpname); - - if ($value && trim($value)) { - // 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) . ""; - } - } - } - if (!isset($dont_copy_file) || !$dont_copy_file) { if (isset($file['file_tmp'])) { if (!@rename($file['tmp_name'], $file['file'])) { @@ -1627,11 +1598,6 @@ function handle_post(Context $ctx) } } - // Do filters again if OCRing - if ($config['tesseract_ocr'] && !hasPermission($config['mod']['bypass_filters'], $board['uri']) && !$dropped_post) { - do_filters($ctx, $post); - } - if (!hasPermission($config['mod']['postunoriginal'], $board['uri']) && $config['robot_enable'] && checkRobot($post['body_nomarkup']) && !$dropped_post) { undoImage($post); if ($config['robot_mute']) { diff --git a/tmp/tesseract/.gitkeep b/tmp/tesseract/.gitkeep deleted file mode 100644 index e69de29b..00000000 From 87029580b6080d0e6ebbd1bcc692b87857077f85 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sat, 29 Mar 2025 00:30:08 +0100 Subject: [PATCH 22/85] post.php: default minimum_copy_resize to true by removing it --- post.php | 1 - 1 file changed, 1 deletion(-) diff --git a/post.php b/post.php index 977bab05..0e46e80d 100644 --- a/post.php +++ b/post.php @@ -1402,7 +1402,6 @@ function handle_post(Context $ctx) $file['thumbwidth'] = $size[0]; $file['thumbheight'] = $size[1]; } elseif ( - $config['minimum_copy_resize'] && $image->size->width <= $config['thumb_width'] && $image->size->height <= $config['thumb_height'] && $file['extension'] == ($config['thumb_ext'] ? $config['thumb_ext'] : $file['extension']) From 42e850091ad5acbee0a79b4c13f0afacd4f5c8d3 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sat, 29 Mar 2025 00:29:58 +0100 Subject: [PATCH 23/85] config.php: remove minimum_copy_resize --- inc/config.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/inc/config.php b/inc/config.php index 61325d10..71b0fbf4 100644 --- a/inc/config.php +++ b/inc/config.php @@ -943,10 +943,6 @@ // Location of thumbnail to use for deleted images. $config['image_deleted'] = 'static/deleted.png'; - // When a thumbnailed image is going to be the same (in dimension), just copy the entire file and use - // that as a thumbnail instead of resizing/redrawing. - $config['minimum_copy_resize'] = false; - // Maximum image upload size in bytes. $config['max_filesize'] = 10 * 1024 * 1024; // 10MB // Maximum image dimensions. From c1c20bdab2e663826f898f80316fa7fd16a2fa15 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sat, 29 Mar 2025 00:32:56 +0100 Subject: [PATCH 24/85] post.php: fix typo --- post.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/post.php b/post.php index 0e46e80d..02ccd53e 100644 --- a/post.php +++ b/post.php @@ -1407,7 +1407,7 @@ function handle_post(Context $ctx) $file['extension'] == ($config['thumb_ext'] ? $config['thumb_ext'] : $file['extension']) ) { // Copy, because there's nothing to resize - coopy($file['tmp_name'], $file['thumb']); + copy($file['tmp_name'], $file['thumb']); $file['thumbwidth'] = $image->size->width; $file['thumbheight'] = $image->size->height; From d224c0af23e9f9cfcf00f7505053e58b49f62c3d Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sat, 29 Mar 2025 00:34:37 +0100 Subject: [PATCH 25/85] post.php: skip resize only if already stripped --- post.php | 1 + 1 file changed, 1 insertion(+) diff --git a/post.php b/post.php index 02ccd53e..b5852edb 100644 --- a/post.php +++ b/post.php @@ -1402,6 +1402,7 @@ function handle_post(Context $ctx) $file['thumbwidth'] = $size[0]; $file['thumbheight'] = $size[1]; } elseif ( + (($config['strip_exif'] && $file['exif_stripped']) || !$config['strip_exif']) && $image->size->width <= $config['thumb_width'] && $image->size->height <= $config['thumb_height'] && $file['extension'] == ($config['thumb_ext'] ? $config['thumb_ext'] : $file['extension']) From 92fc2daa9c9c768bb625449005653f8ab030faf8 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sat, 29 Mar 2025 08:55:15 +0100 Subject: [PATCH 26/85] post.php: fix undefined exif_stripped --- post.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/post.php b/post.php index b5852edb..5483600d 100644 --- a/post.php +++ b/post.php @@ -1402,7 +1402,7 @@ function handle_post(Context $ctx) $file['thumbwidth'] = $size[0]; $file['thumbheight'] = $size[1]; } elseif ( - (($config['strip_exif'] && $file['exif_stripped']) || !$config['strip_exif']) && + (($config['strip_exif'] && isset($file['exif_stripped']) && $file['exif_stripped']) || !$config['strip_exif']) && $image->size->width <= $config['thumb_width'] && $image->size->height <= $config['thumb_height'] && $file['extension'] == ($config['thumb_ext'] ? $config['thumb_ext'] : $file['extension']) From 5613baca0521c180142bd80c8cb9baf0e6a43203 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sun, 13 Apr 2025 16:49:13 +0200 Subject: [PATCH 27/85] flags: add grace flag --- static/flags/grace.png | Bin 0 -> 1345 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 static/flags/grace.png diff --git a/static/flags/grace.png b/static/flags/grace.png new file mode 100644 index 0000000000000000000000000000000000000000..7b4cb03ce4a3bfdea31fe783faf878d7ae8b8e95 GIT binary patch literal 1345 zcmaizX;2eZ5XT>CE20QhD}q`TibW(CASeV;jDZjZ#1Jf=6$6$aF(d>c!9a>AqCga? zB!EJwf*c_M1mlgi$`y_f5E4$WilSh(rO367(@tmVm%f?Z|LlAF+nw3jm*ne1GBhwV zKnNLZ8hRw%#qm<^igc7dP-4vm2Rfh;=VIFe!mdc}KXwQJo6woF>cPw$beG~1$@HnQJj{$W8w6mZ&3>pOm z3~M>t7V2W4D;l)1#ax%mY**+=8E+MgHuIq^S;BO_!f}EY4m{!%Ggg%_t)MOr9_cCw zFLJkyw^rmvdgO68=0>_^vp0{n$S*{AL&sU@I033e8PleaZBxv1$>h2xM^J2*&B2(> z`CU~td|xurC>?qzby#BTY-MsLf+~q3mGfNXEUbiQ1sbul8Nyl4ABVRVHuLc229xT$ zqYVnzWwTaT%$|ZF7t4iirU*1y#XQVFFXvsqFf(c=ObtYw+h>GB*5>G)>ee64x3DXa z={m&Epuyy;yr?~6eQCe-a9?UU@48N1;_ZM@%@Ls=E7jY6rQV48_M6&rYZDYL+#Zum zfe$^!ynqi~$#1n$GQI=!W?p7*1daSfYsoWJ=}^7weT{6SR{maDF{mmVR?9#w%|Gl4 zo#)GYglphcz^2K!d3ApySmL z0@RAiSzbaCK}gyL_4UwH2g*8VP^NqMo+lBjm|pNubS-F8RX9-|N=~QlN}>7-{rp9g zU2+<+GGqe)m^XPp(=ELPhvM_`Km6ZOq|#`*9K8cuNyK4={E0*t%;b>0-Dkd>VK~kHxZ#ri%UpFqlE~gVCP?SiGJ6-$2cR pa(x|O^=}6boe> Date: Sun, 13 Apr 2025 16:49:50 +0200 Subject: [PATCH 28/85] flags: add Rodinia's /get/ flag --- static/flags/rodinia_get.png | Bin 0 -> 1379 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 static/flags/rodinia_get.png diff --git a/static/flags/rodinia_get.png b/static/flags/rodinia_get.png new file mode 100644 index 0000000000000000000000000000000000000000..a22169ecaf5673e0d8da8fa20ed784687aab41d7 GIT binary patch literal 1379 zcmaJ=3pCVu6#l6;y%cR8Nhs;Lw(+Rlq>Xl{DJC&q?N%6r!I&`yLnDI*HBF`>npJjX zwc`;A8P5qNZ?Y>rJVG0W@|IV3|LvZ$=j@)dd(XM|JNMr6oqNtb-*>^9cvxYD`U-@Q z0^tbW21CV9xok03x9hF3#Xy^U#KsDtkgW)D&m%O8jkx^?1>g`GbVkTH5g}DZQqc($ zgceO(TawHLyvwN(SDOOkdT5uQQ#j=mwkYUaR9IMQcwANxKQk~aK9Ex%C@S&cbkIbV zPV{bSVx!C1o`6fWK8#L2sK6;}LS6L@ygF%eqwr*FyJ{ARP&cn-Cs7~t*puJ|$JH46Fikm_=>IG@+ zt#WdEhRPj%-N_5B@WPpK6?N=JjEqaLiQ#@12fZeAI+ zX%%n%>S*P4l>|~hJv>5bTd1;Dh|1ErlxXN{OsCq9rQLwGn*1|FsfQz^CO}Iqq$G1S zwKy7DCA1S`={$HNhIiR8A}zW|&hjThcUca@9vaw-UoJ(dht zX01bL9sm=MIzo3i$DuzL=F(v#H_X8lCT9jef-RK|<6{#|($S^@fEhb4-p~TT_&lF% z*;W%ctaTb7F6HicQ}JATxe`Lcr27-IFkIirbKD0XvSBho%rt@i zf}RZSgNt6W=CXn23V0!%Y0RCemq2@ce?y(D@kyC5_;p4o3?;wGb1Lw{zZZu>hnR1> zWuZC~>I$GmvQT?(p{5WTrSPP5zA8s(Z#bUgn@`u3c$-0&ILFsiWOJ}OJQS)bCQ1sX zALc?`X&o;-+0HW0*Am(@%IOvo<^hSng}~BIXzvhrnt09LqLgWK-{W9xS5gz^4NaB} z@sn--C&+`=GHfm9dh9U%R`R{uE3rnNMX)lLA6TNWdY4-KbXx_s-ax>ckwU)iO%8l> zPywwrH9Pbz-rVBw5yH`9mR4(!BJsHO2^(9IoxOvj)5-6Y(Aw`$oj&92;!1X-xO;d~ zKSy5PJ~UrC!;k6D3J45h2cwX)oX~S&-0+CV^Sr3&m<#A4|B~QxtnkX!YjN?{6B2JA z(GN*CZzccuQ_Ah+)SuJt{DQ>k8JSskvvYFq-OtM}cuKhuHnp>W>{`y1yk5~Wz literal 0 HcmV?d00001 From 68b2911dfd2af6f302785fcf213b763f133130d1 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sun, 13 Apr 2025 16:50:49 +0200 Subject: [PATCH 29/85] flags: add Rodinia's /leftypol/ flag --- static/flags/rodinia_lp.png | Bin 0 -> 522 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 static/flags/rodinia_lp.png diff --git a/static/flags/rodinia_lp.png b/static/flags/rodinia_lp.png new file mode 100644 index 0000000000000000000000000000000000000000..7cdd61a8768de592b1b5e58971aa14e9c6e2b9dd GIT binary patch literal 522 zcmV+l0`>igP)rulQzIK1ANP5$b310~SEu`)=RVJS&OK*F!DiMT^=Lwkrwo_5`)_K!=;OoX6i4#x z^t7nw+y@@fiAFdUn#uM8?Zg})}25iN$(ku|{Q~g1&G>{cH z0+~Ny22F~aOh&y@TkzK*mF#i_jTta&LN7DSS`>U^ei16C%Edsq3SrpX4L$TSL!3`& z4#_07orKSSf!FnSnHN(WMmmDh_+cIc)6P8goIq<}U+T4@TbSl8#DIgDJbZ8(Fl!4# zJVbYpOnZ;ih2vcrtf>*x%dDIMuaY?L0q^yhTl2G6QzKe0+=b<74GdPvY5oUhWd~$w zB#6(GJ*n;OLMi!?%7L+_M(Cke<2E4NE)5Nl1=!y%+RA)H0@V199urGWGWmq?avgTh zFMeewNWya=85(-R6Hd*nvjgJ{^a>2IiEc#P)eXN@+g73Ci?=fAXw1tBW)rF#JzA1`T?SUJ%d&fdJ?2mX!lF)g8SeEPx# literal 0 HcmV?d00001 From 8ee3f4c81d7b7b9ba330c16303eae3815a78a3ac Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sun, 13 Apr 2025 17:02:03 +0200 Subject: [PATCH 30/85] flags: add Tania's flag --- static/flags/tania.png | Bin 0 -> 498 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 static/flags/tania.png diff --git a/static/flags/tania.png b/static/flags/tania.png new file mode 100644 index 0000000000000000000000000000000000000000..c711d149b2ae308ccc9dc3e34fa70b46d17e8188 GIT binary patch literal 498 zcmVR~jN2TI2~8A!d$2A0^N|;L=PPgiMBZkhW_FyNE-;C98xG zNcK=fi1O%^MR>1Of;g2x1A&{t+{^n@d#1y8zVqGtopZ0?Icl~o)0tILW|C;k{5fAQ zu0^Z>7^*o}v^1DqIb*ke7@)mmYyDa}-w^;;2W0?u+Lwsv$!^~RAc)QUIn&8K04Jl7 zzRA@=c_EN1TAH-X#xkLVDo!W&FnhJ825%`zkFPHkMdPgZ=V4mr9=pUQE+%1oCD})RRj3;JTtw8Mx}(5GM1~xMx_+X zLjr(SPnk)MtJ@d_2czI{T;0~|Rh*cGe-V@gHJ5r@gt)-sZ8s{V#U>^%IQ+EF|Dx6G zLgugjU?B^xe@m;`W!xLa8ppjMt!5Y3ztz}}qY6U(tkeFT1Sr2TMS5+M8_$zLfw!+W o#P44-E`JUrz=wC1_;dHb69gK%BHWM*H~;_u07*qoM6N<$f*(rXh5!Hn literal 0 HcmV?d00001 From 2e1cb7995f98e6d06ff4290ba4c714e83a0ecb7e Mon Sep 17 00:00:00 2001 From: Zankaria Date: Mon, 14 Apr 2025 17:48:05 +0200 Subject: [PATCH 31/85] pages.php: better input validation in recent_posts page --- inc/mod/pages.php | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/inc/mod/pages.php b/inc/mod/pages.php index 155c19a6..18a7a6fa 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -3015,8 +3015,20 @@ function mod_recent_posts(Context $ctx, $lim, $board_list = false, $json = false if (!hasPermission($config['mod']['recent'])) error($config['error']['noaccess']); - $limit = (is_numeric($lim))? $lim : 25; - $last_time = (isset($_GET['last']) && is_numeric($_GET['last'])) ? $_GET['last'] : 0; + $limit = 25; + if (\is_numeric($lim)) { + $lim = \intval($lim); + if ($lim > 0 && $lim < 1000) { + $limit = $lim; + } + } + $last_time = 0; + if (isset($_GET['last']) && \is_numeric($_GET['last'])) { + $last = \intval($_GET['last']); + if ($last > 0) { + $last_time = $last; + } + } $mod_boards = []; $boards = listBoards(); From 54dcf79a7f2bcbd58d47ee0fa4145dd740d4084b Mon Sep 17 00:00:00 2001 From: Zankaria Date: Tue, 15 Apr 2025 22:31:18 +0200 Subject: [PATCH 32/85] search.php: fix formatting --- search.php | 340 +++++++++++++++++++++++++++-------------------------- 1 file changed, 172 insertions(+), 168 deletions(-) diff --git a/search.php b/search.php index 0d64ed04..bfd4b022 100644 --- a/search.php +++ b/search.php @@ -1,174 +1,178 @@ $boards, 'board' => isset($_GET['board']) ? $_GET['board'] : false, 'search' => isset($_GET['search']) ? str_replace('"', '"', utf8tohtml($_GET['search'])) : false)); + +if (isset($_GET['search']) && !empty($_GET['search']) && isset($_GET['board']) && in_array($_GET['board'], $boards)) { + $phrase = $_GET['search']; + $_body = ''; + + $query = prepare("SELECT COUNT(*) FROM ``search_queries`` WHERE `ip` = :ip AND `time` > :time"); + $query->bindValue(':ip', $_SERVER['REMOTE_ADDR']); + $query->bindValue(':time', time() - ($queries_per_minutes[1] * 60)); + $query->execute() or error(db_error($query)); + if ($query->fetchColumn() > $queries_per_minutes[0]) + error(_('Wait a while before searching again, please.')); + + $query = prepare("SELECT COUNT(*) FROM ``search_queries`` WHERE `time` > :time"); + $query->bindValue(':time', time() - ($queries_per_minutes_all[1] * 60)); + $query->execute() or error(db_error($query)); + if ($query->fetchColumn() > $queries_per_minutes_all[0]) + error(_('Wait a while before searching again, please.')); + + + $query = prepare("INSERT INTO ``search_queries`` VALUES (:ip, :time, :query)"); + $query->bindValue(':ip', $_SERVER['REMOTE_ADDR']); + $query->bindValue(':time', time()); + $query->bindValue(':query', $phrase); + $query->execute() or error(db_error($query)); + + _syslog(LOG_NOTICE, 'Searched /' . $_GET['board'] . '/ for "' . $phrase . '"'); + + // Cleanup search queries table + $query = prepare("DELETE FROM ``search_queries`` WHERE `time` <= :time"); + $query->bindValue(':time', time() - ($queries_per_minutes_all[1] * 60)); + $query->execute() or error(db_error($query)); + + openBoard($_GET['board']); + + $filters = Array(); + + function search_filters($m) { + global $filters; + $name = $m[2]; + $value = isset($m[4]) ? $m[4] : $m[3]; + + if (!in_array($name, array('id', 'thread', 'subject', 'name'))) { + // unknown filter + return $m[0]; + } + + $filters[$name] = $value; + + return $m[1]; } - $queries_per_minutes = $config['search']['queries_per_minutes']; - $queries_per_minutes_all = $config['search']['queries_per_minutes_all']; - $search_limit = $config['search']['search_limit']; - - if (isset($config['search']['boards'])) { - $boards = $config['search']['boards']; + $phrase = trim(preg_replace_callback('/(^|\s)(\w+):("(.*)?"|[^\s]*)/', 'search_filters', $phrase)); + + if (!preg_match('/[^*^\s]/', $phrase) && empty($filters)) { + _syslog(LOG_WARNING, 'Query too broad.'); + $body .= '

(Query too broad.)

'; + echo Element('page.html', Array( + 'config'=>$config, + 'title'=>'Search', + 'body'=>$body, + )); + exit; + } + + // Escape escape character + $phrase = str_replace('!', '!!', $phrase); + + // Remove SQL wildcard + $phrase = str_replace('%', '!%', $phrase); + + // Use asterisk as wildcard to suit convention + $phrase = str_replace('*', '%', $phrase); + + // Remove `, it's used by table prefix magic + $phrase = str_replace('`', '!`', $phrase); + + $like = ''; + $match = Array(); + + // Find exact phrases + if (preg_match_all('/"(.+?)"/', $phrase, $m)) { + foreach($m[1] as &$quote) { + $phrase = str_replace("\"{$quote}\"", '', $phrase); + $match[] = $pdo->quote($quote); + } + } + + $words = explode(' ', $phrase); + foreach($words as &$word) { + if (empty($word)) { + continue; + } + $match[] = $pdo->quote($word); + } + + $like = ''; + foreach($match as &$phrase) { + if (!empty($like)) { + $like .= ' AND '; + } + $phrase = preg_replace('/^\'(.+)\'$/', '\'%$1%\'', $phrase); + $like .= '`body` LIKE ' . $phrase . ' ESCAPE \'!\''; + } + + foreach($filters as $name => $value) { + if (!empty($like)) { + $like .= ' AND '; + } + $like .= '`' . $name . '` = '. $pdo->quote($value); + } + + $like = str_replace('%', '%%', $like); + + $query = prepare(sprintf("SELECT * FROM ``posts_%s`` WHERE " . $like . " ORDER BY `time` DESC LIMIT :limit", $board['uri'])); + $query->bindValue(':limit', $search_limit, PDO::PARAM_INT); + $query->execute() or error(db_error($query)); + + if ($query->rowCount() == $search_limit) { + _syslog(LOG_WARNING, 'Query too broad.'); + $body .= '

('._('Query too broad.').')

'; + echo Element('page.html', Array( + 'config'=>$config, + 'title'=>'Search', + 'body'=>$body, + )); + exit; + } + + $temp = ''; + while ($post = $query->fetch()) { + if (!$post['thread']) { + $po = new Thread($post); + } else { + $po = new Post($post); + } + $temp .= $po->build(true) . '
'; + } + + if (!empty($temp)) + $_body .= '
'; + + $body .= '
'; + if (!empty($_body)) { + $body .= $_body; } else { - $boards = listBoards(TRUE); + $body .= '

('._('No results.').')

'; } - - $body = Element('search_form.html', Array('boards' => $boards, 'board' => isset($_GET['board']) ? $_GET['board'] : false, 'search' => isset($_GET['search']) ? str_replace('"', '"', utf8tohtml($_GET['search'])) : false)); - - if(isset($_GET['search']) && !empty($_GET['search']) && isset($_GET['board']) && in_array($_GET['board'], $boards)) { - $phrase = $_GET['search']; - $_body = ''; - - $query = prepare("SELECT COUNT(*) FROM ``search_queries`` WHERE `ip` = :ip AND `time` > :time"); - $query->bindValue(':ip', $_SERVER['REMOTE_ADDR']); - $query->bindValue(':time', time() - ($queries_per_minutes[1] * 60)); - $query->execute() or error(db_error($query)); - if($query->fetchColumn() > $queries_per_minutes[0]) - error(_('Wait a while before searching again, please.')); - - $query = prepare("SELECT COUNT(*) FROM ``search_queries`` WHERE `time` > :time"); - $query->bindValue(':time', time() - ($queries_per_minutes_all[1] * 60)); - $query->execute() or error(db_error($query)); - if($query->fetchColumn() > $queries_per_minutes_all[0]) - error(_('Wait a while before searching again, please.')); - - - $query = prepare("INSERT INTO ``search_queries`` VALUES (:ip, :time, :query)"); - $query->bindValue(':ip', $_SERVER['REMOTE_ADDR']); - $query->bindValue(':time', time()); - $query->bindValue(':query', $phrase); - $query->execute() or error(db_error($query)); - - _syslog(LOG_NOTICE, 'Searched /' . $_GET['board'] . '/ for "' . $phrase . '"'); +} - // Cleanup search queries table - $query = prepare("DELETE FROM ``search_queries`` WHERE `time` <= :time"); - $query->bindValue(':time', time() - ($queries_per_minutes_all[1] * 60)); - $query->execute() or error(db_error($query)); - - openBoard($_GET['board']); - - $filters = Array(); - - function search_filters($m) { - global $filters; - $name = $m[2]; - $value = isset($m[4]) ? $m[4] : $m[3]; - - if(!in_array($name, array('id', 'thread', 'subject', 'name'))) { - // unknown filter - return $m[0]; - } - - $filters[$name] = $value; - - return $m[1]; - } - - $phrase = trim(preg_replace_callback('/(^|\s)(\w+):("(.*)?"|[^\s]*)/', 'search_filters', $phrase)); - - if(!preg_match('/[^*^\s]/', $phrase) && empty($filters)) { - _syslog(LOG_WARNING, 'Query too broad.'); - $body .= '

(Query too broad.)

'; - echo Element('page.html', Array( - 'config'=>$config, - 'title'=>'Search', - 'body'=>$body, - )); - exit; - } - - // Escape escape character - $phrase = str_replace('!', '!!', $phrase); - - // Remove SQL wildcard - $phrase = str_replace('%', '!%', $phrase); - - // Use asterisk as wildcard to suit convention - $phrase = str_replace('*', '%', $phrase); - - // Remove `, it's used by table prefix magic - $phrase = str_replace('`', '!`', $phrase); - - $like = ''; - $match = Array(); - - // Find exact phrases - if(preg_match_all('/"(.+?)"/', $phrase, $m)) { - foreach($m[1] as &$quote) { - $phrase = str_replace("\"{$quote}\"", '', $phrase); - $match[] = $pdo->quote($quote); - } - } - - $words = explode(' ', $phrase); - foreach($words as &$word) { - if(empty($word)) - continue; - $match[] = $pdo->quote($word); - } - - $like = ''; - foreach($match as &$phrase) { - if(!empty($like)) - $like .= ' AND '; - $phrase = preg_replace('/^\'(.+)\'$/', '\'%$1%\'', $phrase); - $like .= '`body` LIKE ' . $phrase . ' ESCAPE \'!\''; - } - - foreach($filters as $name => $value) { - if(!empty($like)) - $like .= ' AND '; - $like .= '`' . $name . '` = '. $pdo->quote($value); - } - - $like = str_replace('%', '%%', $like); - - $query = prepare(sprintf("SELECT * FROM ``posts_%s`` WHERE " . $like . " ORDER BY `time` DESC LIMIT :limit", $board['uri'])); - $query->bindValue(':limit', $search_limit, PDO::PARAM_INT); - $query->execute() or error(db_error($query)); - - if($query->rowCount() == $search_limit) { - _syslog(LOG_WARNING, 'Query too broad.'); - $body .= '

('._('Query too broad.').')

'; - echo Element('page.html', Array( - 'config'=>$config, - 'title'=>'Search', - 'body'=>$body, - )); - exit; - } - - $temp = ''; - while($post = $query->fetch()) { - if(!$post['thread']) { - $po = new Thread($post); - } else { - $po = new Post($post); - } - $temp .= $po->build(true) . '
'; - } - - if(!empty($temp)) - $_body .= '
' . - sprintf(ngettext('%d result in', '%d results in', $query->rowCount()), - $query->rowCount()) . ' ' . - sprintf($config['board_abbreviation'], $board['uri']) . ' - ' . $board['title'] . - '' . $temp . '
'; - - $body .= '
'; - if(!empty($_body)) - $body .= $_body; - else - $body .= '

('._('No results.').')

'; - } - - echo Element('page.html', Array( - 'config'=>$config, - 'title'=>_('Search'), - 'body'=>'' . $body - )); +echo Element('page.html', Array( + 'config'=>$config, + 'title'=>_('Search'), + 'body'=>'' . $body +)); From acdf792dafea095612a860ac93b4ed923c02f255 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Wed, 16 Apr 2025 14:32:14 +0200 Subject: [PATCH 33/85] hide.php: add hide.php to the functions --- inc/functions/hide.php | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 inc/functions/hide.php diff --git a/inc/functions/hide.php b/inc/functions/hide.php new file mode 100644 index 00000000..bf972751 --- /dev/null +++ b/inc/functions/hide.php @@ -0,0 +1,6 @@ + Date: Wed, 16 Apr 2025 14:32:54 +0200 Subject: [PATCH 34/85] composer: add hide.php --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index d0345e3b..21fff51a 100644 --- a/composer.json +++ b/composer.json @@ -25,6 +25,7 @@ "inc/polyfill.php", "inc/error.php", "inc/functions.php", + "inc/functions/hide.php", "inc/functions/net.php" ] }, From 8cffb479fa52a8e83e8817d792d0ca4020d91598 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Wed, 16 Apr 2025 14:38:07 +0200 Subject: [PATCH 35/85] functions.php: use secure_hash where appropriate --- inc/functions.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index def00287..a37e2001 100644 --- a/inc/functions.php +++ b/inc/functions.php @@ -11,6 +11,7 @@ if (realpath($_SERVER['SCRIPT_FILENAME']) == str_replace('\\', '/', __FILE__)) { $microtime_start = microtime(true); +use Vichan\Functions\Hide; use Lifo\IP\IP; // for expanding IPv6 address in DNSBL() // the user is not currently logged in as a moderator @@ -1691,7 +1692,7 @@ function checkSpam(array $extra_salt = array()) { $_hash = sha1($_hash . $extra_salt); if ($hash != $_hash) { - return true; + return true; } $query = prepare('SELECT `passed` FROM ``antispam`` WHERE `hash` = :hash'); @@ -2583,11 +2584,11 @@ function rrmdir($dir) { function poster_id($ip, $thread) { global $config; - if ($id = event('poster-id', $ip, $thread)) + if ($id = event('poster-id', $ip, $thread)) { return $id; + } - // Confusing, hard to brute-force, but simple algorithm - return substr(sha1(sha1($ip . $config['secure_trip_salt'] . $thread) . $config['secure_trip_salt']), 0, $config['poster_id_length']); + return \substr(Hide\secure_hash($ip . $config['secure_trip_salt'] . $thread . $config['secure_trip_salt'], false), 0, $config['poster_id_length']); } function generate_tripcode($name) { @@ -2615,7 +2616,7 @@ function generate_tripcode($name) { if (isset($config['custom_tripcode']["##{$trip}"])) $trip = $config['custom_tripcode']["##{$trip}"]; else - $trip = '!!' . substr(crypt($trip, str_replace('+', '.', '_..A.' . substr(base64_encode(sha1($trip . $config['secure_trip_salt'], true)), 0, 4))), -10); + $trip = '!!' . substr(crypt($trip, str_replace('+', '.', '_..A.' . substr(Hide\secure_hash($trip . $config['secure_trip_salt'], false), 0, 4))), -10); } else { if (isset($config['custom_tripcode']["#{$trip}"])) $trip = $config['custom_tripcode']["#{$trip}"]; From 3c0779992a764f07f36b54f2bf12a1bb32e6e3a1 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Wed, 16 Apr 2025 14:26:41 +0200 Subject: [PATCH 36/85] auth.php: use secure salt source, use a cryptographically secure hashing algorithm for login tokens --- inc/mod/auth.php | 43 +++++++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/inc/mod/auth.php b/inc/mod/auth.php index 01b234a1..326f6f53 100644 --- a/inc/mod/auth.php +++ b/inc/mod/auth.php @@ -5,7 +5,7 @@ */ use Vichan\Context; -use Vichan\Functions\Net; +use Vichan\Functions\{Hide, Net}; defined('TINYBOARD') or exit; @@ -14,30 +14,32 @@ function mkhash($username, $password, $salt = false) { global $config; if (!$salt) { - // create some sort of salt for the hash - $salt = substr(base64_encode(sha1(rand() . time(), true) . $config['cookies']['salt']), 0, 15); - + // Create some salt for the hash. + $salt = \bin2hex(\random_bytes(15)); // 20 characters. $generated_salt = true; + } else { + $generated_salt = false; } // generate hash (method is not important as long as it's strong) - $hash = substr( - base64_encode( - md5( - $username . $config['cookies']['salt'] . sha1( - $username . $password . $salt . ( - $config['mod']['lock_ip'] ? $_SERVER['REMOTE_ADDR'] : '' - ), true - ) . sha1($config['password_crypt_version']) // Log out users being logged in with older password encryption schema - , true - ) - ), 0, 20 + $hash = \substr( + Hide\secure_hash( + $username . $config['cookies']['salt'] . Hide\secure_hash( + $username . $password . $salt . ( + $config['mod']['lock_ip'] ? $_SERVER['REMOTE_ADDR'] : '' + ), true + ) . Hide\secure_hash($config['password_crypt_version'], true), // Log out users being logged in with older password encryption schema + false + ), + 0, + 40 ); - if (isset($generated_salt)) - return array($hash, $salt); - else + if ($generated_salt) { + return [ $hash, $salt ]; + } else { return $hash; + } } function crypt_password($password) { @@ -50,16 +52,13 @@ function crypt_password($password) { } function test_password($password, $salt, $test) { - global $config; - // Version = 0 denotes an old password hashing schema. In the same column, the // password hash was kept previously $version = (strlen($salt) <= 8) ? (int) $salt : 0; if ($version == 0) { $comp = hash('sha256', $salt . sha1($test)); - } - else { + } else { $comp = crypt($test, $password); } return array($version, hash_equals($password, $comp)); From bac5032b56570b4ac8e69620d9026b84187b1399 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sun, 20 Apr 2025 16:45:18 +0200 Subject: [PATCH 37/85] rules.html: make ordinance 2 into rule 15 --- templates/rules.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/templates/rules.html b/templates/rules.html index 9eaa3c3b..cfb8c8c8 100644 --- a/templates/rules.html +++ b/templates/rules.html @@ -62,9 +62,11 @@ Opening posts with liberalism or reactionary topics will be treated with far mor

These examples are low quality posts that are considered, at best, bait, but are better described as spam. Any poster that violates this rule may be subject to a ban, and any post that violates this is subject to deletion at the discretion of moderators, if they feel that the topic may be an avenue for productive discussion.

+

15) In order to incentivize creativity and develop a genuine culture, non-original Wojaks, Pepes, or Groypers which are not /leftypol/ original content are considered spam and banned.

+

META:

-

15) Volunteers may remove other posts according to their own discretion which they feel do not contribute to the stated mission of /leftypol/, but they should try to adhere to the standards of the community and of their fellow moderators, and to refrain from arbitrary decisions. Where there is disagreement among moderators, the matter will be decided by informal consensus of currently active moderators. If there is still disagreement, the matter should be escalated to a formal vote.

+

16) Volunteers may remove other posts according to their own discretion which they feel do not contribute to the stated mission of /leftypol/, but they should try to adhere to the standards of the community and of their fellow moderators, and to refrain from arbitrary decisions. Where there is disagreement among moderators, the matter will be decided by informal consensus of currently active moderators. If there is still disagreement, the matter should be escalated to a formal vote.

-

16) Users have the right to question and challenge any bans or post removals, or other moderator actions, which they feel are unfair or do not live up to the spirit of the rules. This may be done in the moderation feedback threads on the various boards, on the /meta/ board, through the ban appeal feature, or in the Leftypol Matrix Congress chat, but comments should be considered and constructive, and should not devolve into polemics against the volunteers. Ultimately, the judgement of the moderation team is final.

+

17) Users have the right to question and challenge any bans or post removals, or other moderator actions, which they feel are unfair or do not live up to the spirit of the rules. This may be done in the moderation feedback threads on the various boards, on the /meta/ board, through the ban appeal feature, or in the Leftypol Matrix Congress chat, but comments should be considered and constructive, and should not devolve into polemics against the volunteers. Ultimately, the judgement of the moderation team is final.

From 181f4ba49a619fbda6405339522d5c741901a42a Mon Sep 17 00:00:00 2001 From: Zankaria Date: Mon, 21 Apr 2025 14:53:09 +0200 Subject: [PATCH 38/85] config.php: add generic invalidfile error --- inc/config.php | 1 + 1 file changed, 1 insertion(+) diff --git a/inc/config.php b/inc/config.php index 71b0fbf4..5fc38c8d 100644 --- a/inc/config.php +++ b/inc/config.php @@ -1298,6 +1298,7 @@ $config['error']['pendingappeal'] = _('There is already a pending appeal for this ban.'); $config['error']['invalidpassword'] = _('Wrong password…'); $config['error']['invalidimg'] = _('Invalid image.'); + $config['error']['invalidfile'] = _('Invalid file.'); $config['error']['unknownext'] = _('Unknown file extension.'); $config['error']['filesize'] = _('Maximum file size: %maxsz% bytes
Your file\'s size: %filesz% bytes'); $config['error']['maxsize'] = _('The file was too big.'); From ff94e58f2e7c14868848143f6ee42ecf4f92f90a Mon Sep 17 00:00:00 2001 From: Zankaria Date: Mon, 21 Apr 2025 15:19:34 +0200 Subject: [PATCH 39/85] config.php: update pdf_file_thumbnail documentation --- inc/config.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/inc/config.php b/inc/config.php index 5fc38c8d..9a9de46c 100644 --- a/inc/config.php +++ b/inc/config.php @@ -2069,7 +2069,9 @@ // Enable auto IP note generation of moderator deleted posts $config['autotagging'] = false; - // Enable PDF file thumbnail generation + // Enable PDF thumbnail generation. + // Requires a working installation of ghostscript and imagemagick. + // Imagemagick support of PDF files is not required. $config['pdf_file_thumbnail'] = false; // Enable TXT file thumbnail From 28f75c8aed7d12bb783ffb06e6430420fa6f3b64 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Mon, 21 Apr 2025 15:21:13 +0200 Subject: [PATCH 40/85] config.php: add missing djvu_file_thumbnail option --- inc/config.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/inc/config.php b/inc/config.php index 9a9de46c..55580cec 100644 --- a/inc/config.php +++ b/inc/config.php @@ -2074,6 +2074,11 @@ // Imagemagick support of PDF files is not required. $config['pdf_file_thumbnail'] = false; + // Enable djvu thumbnail generation. + // Requires djvulibre's tools and imagemagick. + // Imagemagick support of djvu files is not required. + $config['djvu_file_thumbnail'] = false; + // Enable TXT file thumbnail $config['txt_file_thumbnail'] = false; From 8282d5cd6361914660f83334ea166d13d76c27ee Mon Sep 17 00:00:00 2001 From: Zankaria Date: Mon, 21 Apr 2025 15:11:32 +0200 Subject: [PATCH 41/85] post.php: implement safe PDF thumbnailing --- post.php | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/post.php b/post.php index 5483600d..e4560bb8 100644 --- a/post.php +++ b/post.php @@ -1447,10 +1447,36 @@ function handle_post(Context $ctx) } $image->destroy(); } else { - if ( - ($file['extension'] == "pdf" && $config['pdf_file_thumbnail']) || - ($file['extension'] == "djvu" && $config['djvu_file_thumbnail']) - ) { + $mime = \mime_content_type($file['tmp_name']); + if ($file['extension'] === "pdf" && $config['pdf_file_thumbnail']) { + if ($mime !== 'application/pdf' && $mime !== 'application/x-pdf') { + error($config['error']['invalidfile']); + } + + $e_thumb_path = \escapeshellarg($file['thumb']); + $e_file_path = \escapeshellarg($file['tmp_name']); + $thumb_width = $config['thumb_width']; + $thumb_height = $config['thumb_height']; + + // Generates a PPM image and pipes it directly into convert for resizing + type conversion. + $error = shell_exec_error("gs -dSAFER -dBATCH -dNOPAUSE -dQUIET + -sDEVICE=ppmraw -r100 -dFirstPage=1 -dLastPage=1 -sOutputFile=- $e_file_path + | convert -thumbnail {$thumb_width}x{$thumb_height} ppm:- $e_thumb_path"); + + if ($error) { + $log = $ctx->get(LogDriver::class); + $log->log(LogDriver::ERROR, 'Could not render thumbnail for PDF file, using static fallback.'); + $path = sprintf($config['file_thumb'], isset($config['file_icons'][$file['extension']]) ? $config['file_icons'][$file['extension']] : $config['file_icons']['default']); + } + + $file['thumb'] = basename($file['thumb']); + $size = @getimagesize($path); + $file['thumbwidth'] = $size[0]; + $file['thumbheight'] = $size[1]; + $file['width'] = $size[0]; + $file['height'] = $size[1]; + } + if ($file['extension'] == "djvu" && $config['djvu_file_thumbnail']) { $path = $file['thumb']; $error = shell_exec_error('convert -size ' . $config['thumb_width'] . 'x' . $config['thumb_height'] . ' -thumbnail ' . $config['thumb_width'] . 'x' . $config['thumb_height'] . ' -background white -alpha remove ' . escapeshellarg($file['tmp_name'] . '[0]') . ' ' . From 8cb6a76f0a92740312ccbfeff61fa5d823f7ad0f Mon Sep 17 00:00:00 2001 From: Zankaria Date: Mon, 21 Apr 2025 15:58:11 +0200 Subject: [PATCH 42/85] post.php: add safe djvu thumbnail generation --- post.php | 44 ++++++++++++++++++-------------------------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/post.php b/post.php index e4560bb8..e6afe5b6 100644 --- a/post.php +++ b/post.php @@ -1448,42 +1448,34 @@ function handle_post(Context $ctx) $image->destroy(); } else { $mime = \mime_content_type($file['tmp_name']); - if ($file['extension'] === "pdf" && $config['pdf_file_thumbnail']) { - if ($mime !== 'application/pdf' && $mime !== 'application/x-pdf') { - error($config['error']['invalidfile']); - } + $pdf = $file['extension'] === "pdf" && $config['pdf_file_thumbnail']; + $djvu = $file['extension'] === "djvu" && $config['djvu_file_thumbnail']; + if ($pdf || $djvu) { $e_thumb_path = \escapeshellarg($file['thumb']); $e_file_path = \escapeshellarg($file['tmp_name']); $thumb_width = $config['thumb_width']; $thumb_height = $config['thumb_height']; // Generates a PPM image and pipes it directly into convert for resizing + type conversion. - $error = shell_exec_error("gs -dSAFER -dBATCH -dNOPAUSE -dQUIET - -sDEVICE=ppmraw -r100 -dFirstPage=1 -dLastPage=1 -sOutputFile=- $e_file_path - | convert -thumbnail {$thumb_width}x{$thumb_height} ppm:- $e_thumb_path"); + if ($pdf && $mime === 'application/pdf') { + $error = shell_exec_error("gs -dSAFER -dBATCH -dNOPAUSE -dQUIET \ + -sDEVICE=ppmraw -r100 -dFirstPage=1 -dLastPage=1 -sOutputFile=- $e_file_path \ + | convert -thumbnail {$thumb_width}x{$thumb_height} ppm:- $e_thumb_path"); + } elseif ($djvu && $mime === 'image/vnd.djvu') { + $error = shell_exec_error("ddjvu -format=ppm -page 1 $e_file_path \ + | convert -thumbnail {$thumb_width}x{$thumb_height} ppm:- $e_thumb_path"); + } else { + // Mime check failed. + error($config['error']['invalidfile']); + } if ($error) { $log = $ctx->get(LogDriver::class); - $log->log(LogDriver::ERROR, 'Could not render thumbnail for PDF file, using static fallback.'); - $path = sprintf($config['file_thumb'], isset($config['file_icons'][$file['extension']]) ? $config['file_icons'][$file['extension']] : $config['file_icons']['default']); - } - - $file['thumb'] = basename($file['thumb']); - $size = @getimagesize($path); - $file['thumbwidth'] = $size[0]; - $file['thumbheight'] = $size[1]; - $file['width'] = $size[0]; - $file['height'] = $size[1]; - } - if ($file['extension'] == "djvu" && $config['djvu_file_thumbnail']) { - $path = $file['thumb']; - $error = shell_exec_error('convert -size ' . $config['thumb_width'] . 'x' . $config['thumb_height'] . ' -thumbnail ' . $config['thumb_width'] . 'x' . $config['thumb_height'] . ' -background white -alpha remove ' . - escapeshellarg($file['tmp_name'] . '[0]') . ' ' . - escapeshellarg($file['thumb'])); - - if ($error) { - $path = sprintf($config['file_thumb'], isset($config['file_icons'][$file['extension']]) ? $config['file_icons'][$file['extension']] : $config['file_icons']['default']); + $log->log(LogDriver::ERROR, 'Could not render thumbnail for PDF/DJVU file, using static fallback.'); + $path = \sprintf($config['file_thumb'], isset($config['file_icons'][$file['extension']]) ? $config['file_icons'][$file['extension']] : $config['file_icons']['default']); + } else { + $path = $file['thumb']; } $file['thumb'] = basename($file['thumb']); From 19c08683207835e95b1b6844f4655f9239209f1f Mon Sep 17 00:00:00 2001 From: Zankaria Date: Mon, 21 Apr 2025 16:37:17 +0200 Subject: [PATCH 43/85] docker: add djvulibre and ghostscript tools --- docker/php/Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker/php/Dockerfile b/docker/php/Dockerfile index a884650c..fffd868d 100644 --- a/docker/php/Dockerfile +++ b/docker/php/Dockerfile @@ -18,6 +18,8 @@ RUN apk add --no-cache \ graphicsmagick \ gifsicle \ ffmpeg \ + djvulibre \ + ghostscript \ bind-tools \ gettext \ gettext-dev \ From f2d0ac7341aaa2ba7522256c728e01a1da1af7f9 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Wed, 23 Apr 2025 19:34:49 +0200 Subject: [PATCH 44/85] functions.php: minor format --- inc/functions.php | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index a37e2001..11a1a40b 100644 --- a/inc/functions.php +++ b/inc/functions.php @@ -2228,20 +2228,15 @@ function markup(&$body, $track_cites = false) { $clauses = array_unique($clauses); if ($board['uri'] != $_board) { - if (!openBoard($_board)){ - if (in_array($_board,array_keys($config['boards_alias']))){ - $_board = $config['boards_alias'][$_board]; - if (openBoard($_board)){ - - } - else { + if (!openBoard($_board)) { + if (\in_array($_board, \array_keys($config['boards_alias']))) { + $_board = $config['boards_alias'][$_board]; + if (!openBoard($_board)) { continue; // Unknown board - } - } - else { + } + } else { continue; // Unknown board } - } } From 439730f216e2fb0b4485b731535e7f7f4e2158d8 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Wed, 23 Apr 2025 19:52:10 +0200 Subject: [PATCH 45/85] functions.php: add crossboard strikethrough for invalid cites --- inc/functions.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index 11a1a40b..6c0c6033 100644 --- a/inc/functions.php +++ b/inc/functions.php @@ -2296,19 +2296,21 @@ function markup(&$body, $track_cites = false) { } - $body = mb_substr_replace($body, $matches[1][0] . $replacement . $matches[4][0], $matches[0][1] + $skip_chars, mb_strlen($matches[0][0])); - $skip_chars += mb_strlen($matches[1][0] . $replacement . $matches[4][0]) - mb_strlen($matches[0][0]); - if ($track_cites && $config['track_cites']) $tracked_cites[] = array($_board, $cite); + } else { + $replacement = ">>>/$_board/$cite"; } } elseif(isset($crossboard_indexes[$_board])) { $replacement = '' . '>>>/' . $_board . '/' . ''; - $body = mb_substr_replace($body, $matches[1][0] . $replacement . $matches[4][0], $matches[0][1] + $skip_chars, mb_strlen($matches[0][0])); - $skip_chars += mb_strlen($matches[1][0] . $replacement . $matches[4][0]) - mb_strlen($matches[0][0]); + } else { + $replacement = ">>>/$_board/$cite"; } + + $body = mb_substr_replace($body, $matches[1][0] . $replacement . $matches[4][0], $matches[0][1] + $skip_chars, mb_strlen($matches[0][0])); + $skip_chars += mb_strlen($matches[1][0] . $replacement . $matches[4][0]) - mb_strlen($matches[0][0]); } } From a28d9a42463c76e646fe4e7b4ed679cc2f5a6311 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Wed, 23 Apr 2025 19:55:19 +0200 Subject: [PATCH 46/85] functions.php: minor code semplification for crossbooard citations --- inc/functions.php | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index 6c0c6033..c9f6657e 100644 --- a/inc/functions.php +++ b/inc/functions.php @@ -2277,31 +2277,22 @@ function markup(&$body, $track_cites = false) { if ($cite) { if (isset($cited_posts[$_board][$cite])) { $link = $cited_posts[$_board][$cite]; - if (isset($original_board)){ - $replacement = '' . - '>>>/' . $original_board . '/' . $cite . + '>>>/' . $replacement_board . '/' . $cite . ''; + if ($track_cites && $config['track_cites']) { + $tracked_cites[] = [ $_board, $cite ]; } - else { - $replacement = '' . - '>>>/' . $_board . '/' . $cite . - ''; - - } - - if ($track_cites && $config['track_cites']) - $tracked_cites[] = array($_board, $cite); } else { $replacement = ">>>/$_board/$cite"; } - } elseif(isset($crossboard_indexes[$_board])) { + } elseif (isset($crossboard_indexes[$_board])) { $replacement = '' . '>>>/' . $_board . '/' . ''; From b9938d9513ccbfc4bf1cb0731d4e9e979fcf02d7 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Wed, 23 Apr 2025 22:01:49 +0200 Subject: [PATCH 47/85] post.php: remove nttpchan --- post.php | 552 +++++++++++++++++-------------------------------------- 1 file changed, 166 insertions(+), 386 deletions(-) diff --git a/post.php b/post.php index e6afe5b6..37af514f 100644 --- a/post.php +++ b/post.php @@ -353,166 +353,6 @@ function db_select_ban_appeals($ban_id) * Method handling functions */ -$dropped_post = false; - -function handle_nntpchan(Context $ctx) -{ - global $config; - if ($_SERVER['REMOTE_ADDR'] != $config['nntpchan']['trusted_peer']) { - error("NNTPChan: Forbidden. $_SERVER[REMOTE_ADDR] is not a trusted peer"); - } - - $_POST = []; - $_POST['json_response'] = true; - - $headers = json_encode($_GET); - - if (!isset($_GET['Message-Id'])) { - if (!isset($_GET['Message-ID'])) { - error("NNTPChan: No message ID"); - } else { - $msgid = $_GET['Message-ID']; - } - } else { - $msgid = $_GET['Message-Id']; - } - - $groups = preg_split("/,\s*/", $_GET['Newsgroups']); - if (count($groups) != 1) { - error("NNTPChan: Messages can go to only one newsgroup"); - } - $group = $groups[0]; - - if (!isset($config['nntpchan']['dispatch'][$group])) { - error("NNTPChan: We don't synchronize $group"); - } - $xboard = $config['nntpchan']['dispatch'][$group]; - - $ref = null; - if (isset($_GET['References'])) { - $refs = preg_split("/,\s*/", $_GET['References']); - - if (count($refs) > 1) { - error("NNTPChan: We don't support multiple references"); - } - - $ref = $refs[0]; - - $query = prepare("SELECT `board`,`id` FROM ``nntp_references`` WHERE `message_id` = :ref"); - $query->bindValue(':ref', $ref); - $query->execute() or error(db_error($query)); - - $ary = $query->fetchAll(PDO::FETCH_ASSOC); - - if (count($ary) == 0) { - error("NNTPChan: We don't have $ref that $msgid references"); - } - - $p_id = $ary[0]['id']; - $p_board = $ary[0]['board']; - - if ($p_board != $xboard) { - error("NNTPChan: Cross board references not allowed. Tried to reference $p_board on $xboard"); - } - - $_POST['thread'] = $p_id; - } - - $date = isset($_GET['Date']) ? strtotime($_GET['Date']) : time(); - - list($ct) = explode('; ', $_GET['Content-Type']); - - $query = prepare("SELECT COUNT(*) AS `c` FROM ``nntp_references`` WHERE `message_id` = :msgid"); - $query->bindValue(":msgid", $msgid); - $query->execute() or error(db_error($query)); - - $a = $query->fetch(PDO::FETCH_ASSOC); - if ($a['c'] > 0) { - error("NNTPChan: We already have this post. Post discarded."); - } - - if ($ct == 'text/plain') { - $content = file_get_contents("php://input"); - } elseif ($ct == 'multipart/mixed' || $ct == 'multipart/form-data') { - $ctx->get(LogDriver::class)->log(LogDriver::DEBUG, 'MM: Files: ' . print_r($GLOBALS, true)); - - $content = ''; - - $newfiles = []; - foreach ($_FILES['attachment']['error'] as $id => $error) { - if ($_FILES['attachment']['type'][$id] == 'text/plain') { - $content .= file_get_contents($_FILES['attachment']['tmp_name'][$id]); - } elseif ($_FILES['attachment']['type'][$id] == 'message/rfc822') { - // Signed message, ignore for now - } else { - // A real attachment :^) - $file = []; - $file['name'] = $_FILES['attachment']['name'][$id]; - $file['type'] = $_FILES['attachment']['type'][$id]; - $file['size'] = $_FILES['attachment']['size'][$id]; - $file['tmp_name'] = $_FILES['attachment']['tmp_name'][$id]; - $file['error'] = $_FILES['attachment']['error'][$id]; - - $newfiles["file$id"] = $file; - } - } - - $_FILES = $newfiles; - } else { - error("NNTPChan: Wrong mime type: $ct"); - } - - $_POST['subject'] = isset($_GET['Subject']) ? ($_GET['Subject'] == 'None' ? '' : $_GET['Subject']) : ''; - $_POST['board'] = $xboard; - - if (isset($_GET['From'])) { - list($name, $mail) = explode(" <", $_GET['From'], 2); - $mail = preg_replace('/>\s+$/', '', $mail); - - $_POST['name'] = $name; - //$_POST['email'] = $mail; - $_POST['email'] = ''; - } - - if (isset($_GET['X_Sage'])) { - $_POST['email'] = 'sage'; - } - - $content = preg_replace_callback('/>>([0-9a-fA-F]{6,})/', function ($id) use ($xboard) { - $id = $id[1]; - - $query = prepare("SELECT `board`,`id` FROM ``nntp_references`` WHERE `message_id_digest` LIKE :rule"); - $idx = $id . "%"; - $query->bindValue(':rule', $idx); - $query->execute() or error(db_error($query)); - - $ary = $query->fetchAll(PDO::FETCH_ASSOC); - if (count($ary) == 0) { - return ">>>>$id"; - } else { - $ret = []; - foreach ($ary as $v) { - if ($v['board'] != $xboard) { - $ret[] = ">>>/" . $v['board'] . "/" . $v['id']; - } else { - $ret[] = ">>" . $v['id']; - } - } - return implode($ret, ", "); - } - }, $content); - - $_POST['body'] = $content; - - $dropped_post = array( - 'date' => $date, - 'board' => $xboard, - 'msgid' => $msgid, - 'headers' => $headers, - 'from_nntp' => true, - ); -} - function handle_delete(Context $ctx) { // Delete @@ -789,9 +629,9 @@ function handle_report(Context $ctx) function handle_post(Context $ctx) { - global $config, $dropped_post, $board, $mod, $pdo; + global $config, $board, $mod, $pdo; - if (!isset($_POST['body'], $_POST['board']) && !$dropped_post) { + if (!isset($_POST['body'], $_POST['board'])) { error($config['error']['bot']); } @@ -834,108 +674,104 @@ function handle_post(Context $ctx) } - if (!$dropped_post) { - // Check for CAPTCHA right after opening the board so the "return" link is in there. - if ($config['captcha']['mode'] !== false) { - if (!isset($_POST['captcha-response'], $_POST['captcha-form-id'])) { - error($config['error']['bot']); - } - - $expected_action = $post['op'] ? 'post-thread' : 'post-reply'; - $ret = check_captcha($config['captcha'], $_POST['captcha-form-id'], $post['board'], $_POST['captcha-response'], $_SERVER['REMOTE_ADDR'], $expected_action); - if (!$ret) { - error($config['error']['captcha']); - } - } - - if (isset($config['simple_spam']) && $post['op']) { - if (!isset($_POST['simple_spam']) || $config['simple_spam']['answer'] != $_POST['simple_spam']) { - error($config['error']['simple_spam']); - } - } - - if (isset($config['securimage']) && $config['securimage']) { - if (!isset($_POST['captcha'])) { - error($config['error']['securimage']['missing']); - } - - if (empty($_POST['captcha'])) { - error($config['error']['securimage']['empty']); - } - - if (!db_delete_captcha($_SERVER['REMOTE_ADDR'], $_POST['captcha'])) { - error($config['error']['securimage']['bad']); - } - } - - if ( - !(($post['op'] && $_POST['post'] == $config['button_newtopic']) || - (!$post['op'] && $_POST['post'] == $config['button_reply'])) - ) { + // Check for CAPTCHA right after opening the board so the "return" link is in there. + if ($config['captcha']['mode'] !== false) { + if (!isset($_POST['captcha-response'], $_POST['captcha-form-id'])) { error($config['error']['bot']); } - // Check the referrer - if ( - $config['referer_match'] !== false && - (!isset($_SERVER['HTTP_REFERER']) || !preg_match($config['referer_match'], rawurldecode($_SERVER['HTTP_REFERER']))) - ) { - error($config['error']['referer']); + $expected_action = $post['op'] ? 'post-thread' : 'post-reply'; + $ret = check_captcha($config['captcha'], $_POST['captcha-form-id'], $post['board'], $_POST['captcha-response'], $_SERVER['REMOTE_ADDR'], $expected_action); + if (!$ret) { + error($config['error']['captcha']); + } + } + + if (isset($config['simple_spam']) && $post['op']) { + if (!isset($_POST['simple_spam']) || $config['simple_spam']['answer'] != $_POST['simple_spam']) { + error($config['error']['simple_spam']); + } + } + + if (isset($config['securimage']) && $config['securimage']) { + if (!isset($_POST['captcha'])) { + error($config['error']['securimage']['missing']); } - checkDNSBL(); - - // Check if banned - checkBan($board['uri']); - - if ($config['op_require_history'] && $post['op'] && !isIPv6()) { - $has_any = has_any_history($_SERVER['REMOTE_ADDR'], $_POST['password']); - if (!$has_any) { - error($config['error']['opnohistory']); - } + if (empty($_POST['captcha'])) { + error($config['error']['securimage']['empty']); } - if ($post['mod'] = isset($_POST['mod']) && $_POST['mod']) { - check_login($ctx, false); - if (!$mod) { - // Liar. You're not a mod >:-[ - error($config['error']['notamod']); - } + if (!db_delete_captcha($_SERVER['REMOTE_ADDR'], $_POST['captcha'])) { + error($config['error']['securimage']['bad']); + } + } - $post['sticky'] = $post['op'] && isset($_POST['sticky']); - $post['locked'] = $post['op'] && isset($_POST['lock']); - $post['raw'] = isset($_POST['raw']); + if ( + !(($post['op'] && $_POST['post'] == $config['button_newtopic']) || + (!$post['op'] && $_POST['post'] == $config['button_reply'])) + ) { + error($config['error']['bot']); + } - if ($post['sticky'] && !hasPermission($config['mod']['sticky'], $board['uri'])) { - error($config['error']['noaccess']); - } - if ($post['locked'] && !hasPermission($config['mod']['lock'], $board['uri'])) { - error($config['error']['noaccess']); - } - if ($post['raw'] && !hasPermission($config['mod']['rawhtml'], $board['uri'])) { - error($config['error']['noaccess']); - } + // Check the referrer + if ( + $config['referer_match'] !== false && + (!isset($_SERVER['HTTP_REFERER']) || !preg_match($config['referer_match'], rawurldecode($_SERVER['HTTP_REFERER']))) + ) { + error($config['error']['referer']); + } + + checkDNSBL(); + + // Check if banned + checkBan($board['uri']); + + if ($config['op_require_history'] && $post['op'] && !isIPv6()) { + $has_any = has_any_history($_SERVER['REMOTE_ADDR'], $_POST['password']); + if (!$has_any) { + error($config['error']['opnohistory']); + } + } + + if ($post['mod'] = isset($_POST['mod']) && $_POST['mod']) { + check_login($ctx, false); + if (!$mod) { + // Liar. You're not a mod >:-[ + error($config['error']['notamod']); } - if (!$post['mod'] && $config['spam']['enabled'] == true) { - $post['antispam_hash'] = checkSpam( - array( - $board['uri'], - isset($post['thread']) ? $post['thread'] : ($config['try_smarter'] && isset($_POST['page']) ? 0 - (int) $_POST['page'] : null) - ) - ); - //$post['antispam_hash'] = checkSpam(); + $post['sticky'] = $post['op'] && isset($_POST['sticky']); + $post['locked'] = $post['op'] && isset($_POST['lock']); + $post['raw'] = isset($_POST['raw']); - if ($post['antispam_hash'] === true) { - error($config['error']['spam']); - } + if ($post['sticky'] && !hasPermission($config['mod']['sticky'], $board['uri'])) { + error($config['error']['noaccess']); } + if ($post['locked'] && !hasPermission($config['mod']['lock'], $board['uri'])) { + error($config['error']['noaccess']); + } + if ($post['raw'] && !hasPermission($config['mod']['rawhtml'], $board['uri'])) { + error($config['error']['noaccess']); + } + } - if ($config['robot_enable'] && $config['robot_mute']) { - checkMute(); + if (!$post['mod'] && $config['spam']['enabled'] == true) { + $post['antispam_hash'] = checkSpam( + array( + $board['uri'], + isset($post['thread']) ? $post['thread'] : ($config['try_smarter'] && isset($_POST['page']) ? 0 - (int) $_POST['page'] : null) + ) + ); + //$post['antispam_hash'] = checkSpam(); + + if ($post['antispam_hash'] === true) { + error($config['error']['spam']); } - } else { - $mod = $post['mod'] = false; + } + + if ($config['robot_enable'] && $config['robot_mute']) { + checkMute(); } // Check if thread exists. @@ -1021,40 +857,35 @@ function handle_post(Context $ctx) $post['password'] = hashPassword($_POST['password']); $post['has_file'] = (!isset($post['embed']) && (($post['op'] && !isset($post['no_longer_require_an_image_for_op']) && $config['force_image_op']) || count($_FILES) > 0)); - if (!$dropped_post) { - if (!($post['has_file'] || isset($post['embed'])) || (($post['op'] && $config['force_body_op']) || (!$post['op'] && $config['force_body']))) { - $stripped_whitespace = preg_replace('/[\s]/u', '', $post['body']); - if ($stripped_whitespace == '') { - error($config['error']['tooshort_body']); - } - } - - if (!$post['op']) { - // Check if thread is locked - // but allow mods to post - if ($thread['locked'] && !hasPermission($config['mod']['postinlocked'], $board['uri'])) { - error($config['error']['locked']); - } - - $numposts = numPosts($post['thread']); - - $replythreshold = isset($thread['cycle']) && $thread['cycle'] ? $numposts['replies'] - 1 : $numposts['replies']; - $imagethreshold = isset($thread['cycle']) && $thread['cycle'] ? $numposts['images'] - 1 : $numposts['images']; - - if ($config['reply_hard_limit'] != 0 && $config['reply_hard_limit'] <= $replythreshold) { - error($config['error']['reply_hard_limit']); - } - - if ($post['has_file'] && $config['image_hard_limit'] != 0 && $config['image_hard_limit'] <= $imagethreshold) { - error($config['error']['image_hard_limit']); - } - } - } else { - if (!$post['op']) { - $numposts = numPosts($post['thread']); + if (!($post['has_file'] || isset($post['embed'])) || (($post['op'] && $config['force_body_op']) || (!$post['op'] && $config['force_body']))) { + $stripped_whitespace = preg_replace('/[\s]/u', '', $post['body']); + if ($stripped_whitespace == '') { + error($config['error']['tooshort_body']); } } + if (!$post['op']) { + // Check if thread is locked + // but allow mods to post + if ($thread['locked'] && !hasPermission($config['mod']['postinlocked'], $board['uri'])) { + error($config['error']['locked']); + } + + $numposts = numPosts($post['thread']); + + $replythreshold = isset($thread['cycle']) && $thread['cycle'] ? $numposts['replies'] - 1 : $numposts['replies']; + $imagethreshold = isset($thread['cycle']) && $thread['cycle'] ? $numposts['images'] - 1 : $numposts['images']; + + if ($config['reply_hard_limit'] != 0 && $config['reply_hard_limit'] <= $replythreshold) { + error($config['error']['reply_hard_limit']); + } + + if ($post['has_file'] && $config['image_hard_limit'] != 0 && $config['image_hard_limit'] <= $imagethreshold) { + error($config['error']['image_hard_limit']); + } + } + + if ($post['has_file']) { // Determine size sanity $size = 0; @@ -1160,18 +991,16 @@ function handle_post(Context $ctx) $post['has_file'] = false; } - if (!$dropped_post) { - // Check for a file - if ($post['op'] && !isset($post['no_longer_require_an_image_for_op'])) { - if (!$post['has_file'] && $config['force_image_op']) { - error($config['error']['noimage']); - } + // Check for a file + if ($post['op'] && !isset($post['no_longer_require_an_image_for_op'])) { + if (!$post['has_file'] && $config['force_image_op']) { + error($config['error']['noimage']); } + } - // Check for too many files - if (sizeof($post['files']) > $config['max_images']) { - error($config['error']['toomanyimages']); - } + // Check for too many files + if (sizeof($post['files']) > $config['max_images']) { + error($config['error']['toomanyimages']); } if ($config['strip_combining_chars']) { @@ -1181,33 +1010,31 @@ function handle_post(Context $ctx) $post['body'] = strip_combining_chars($post['body']); } - if (!$dropped_post) { - // Check string lengths - if (mb_strlen($post['name']) > 35) { - error(sprintf($config['error']['toolong'], 'name')); - } - if (mb_strlen($post['email']) > 40) { - error(sprintf($config['error']['toolong'], 'email')); - } - if (mb_strlen($post['subject']) > 100) { - error(sprintf($config['error']['toolong'], 'subject')); - } - if (!$mod) { - $body_mb_len = mb_strlen($post['body']); - $is_op = $post['op']; + // Check string lengths + if (mb_strlen($post['name']) > 35) { + error(sprintf($config['error']['toolong'], 'name')); + } + if (mb_strlen($post['email']) > 40) { + error(sprintf($config['error']['toolong'], 'email')); + } + if (mb_strlen($post['subject']) > 100) { + error(sprintf($config['error']['toolong'], 'subject')); + } + if (!$mod) { + $body_mb_len = mb_strlen($post['body']); + $is_op = $post['op']; - if (($is_op && $config['force_body_op']) || (!$is_op && $config['force_body'])) { - $min_body = $is_op ? $config['min_body_op'] : $config['min_body']; + if (($is_op && $config['force_body_op']) || (!$is_op && $config['force_body'])) { + $min_body = $is_op ? $config['min_body_op'] : $config['min_body']; - if ($body_mb_len < $min_body) { - error($config['error']['tooshort_body']); - } + if ($body_mb_len < $min_body) { + error($config['error']['tooshort_body']); } + } - $max_body = $is_op ? $config['max_body_op'] : $config['max_body']; - if ($body_mb_len > $max_body) { - error($config['error']['toolong_body']); - } + $max_body = $is_op ? $config['max_body_op'] : $config['max_body']; + if ($body_mb_len > $max_body) { + error($config['error']['toolong_body']); } } @@ -1219,33 +1046,32 @@ function handle_post(Context $ctx) $post['body'] .= "\n1"; } - if (!$dropped_post) - if (($config['country_flags'] && !$config['allow_no_country']) || ($config['country_flags'] && $config['allow_no_country'] && !isset($_POST['no_country']))) { - $gi = geoip_open('inc/lib/geoip/GeoIPv6.dat', GEOIP_STANDARD); + if (($config['country_flags'] && !$config['allow_no_country']) || ($config['country_flags'] && $config['allow_no_country'] && !isset($_POST['no_country']))) { + $gi = geoip_open('inc/lib/geoip/GeoIPv6.dat', GEOIP_STANDARD); - function ipv4to6($ip) - { - if (strpos($ip, ':') !== false) { - if (strpos($ip, '.') > 0) { - $ip = substr($ip, strrpos($ip, ':') + 1); - } else { - // Native ipv6. - return $ip; - } + function ipv4to6($ip) + { + if (strpos($ip, ':') !== false) { + if (strpos($ip, '.') > 0) { + $ip = substr($ip, strrpos($ip, ':') + 1); + } else { + // Native ipv6. + return $ip; } - $iparr = array_pad(explode('.', $ip), 4, 0); - $part7 = base_convert(($iparr[0] * 256) + $iparr[1], 10, 16); - $part8 = base_convert(($iparr[2] * 256) + $iparr[3], 10, 16); - return '::ffff:' . $part7 . ':' . $part8; } + $iparr = array_pad(explode('.', $ip), 4, 0); + $part7 = base_convert(($iparr[0] * 256) + $iparr[1], 10, 16); + $part8 = base_convert(($iparr[2] * 256) + $iparr[3], 10, 16); + return '::ffff:' . $part7 . ':' . $part8; + } - if ($country_code = geoip_country_code_by_addr_v6($gi, ipv4to6($_SERVER['REMOTE_ADDR']))) { - if (!in_array(strtolower($country_code), array('eu', 'ap', 'o1', 'a1', 'a2'))) { - $post['body'] .= "\n" . strtolower($country_code) . "" . - "\n" . geoip_country_name_by_addr_v6($gi, ipv4to6($_SERVER['REMOTE_ADDR'])) . ""; - } + if ($country_code = geoip_country_code_by_addr_v6($gi, ipv4to6($_SERVER['REMOTE_ADDR']))) { + if (!in_array(strtolower($country_code), array('eu', 'ap', 'o1', 'a1', 'a2'))) { + $post['body'] .= "\n" . strtolower($country_code) . "" . + "\n" . geoip_country_name_by_addr_v6($gi, ipv4to6($_SERVER['REMOTE_ADDR'])) . ""; } } + } if ($config['user_flag'] && isset($_POST['user_flag'])) if (!empty($_POST['user_flag'])) { @@ -1265,11 +1091,9 @@ function handle_post(Context $ctx) $post['body'] .= "\n" . $_POST['tag'] . ""; } - if (!$dropped_post) { - if ($config['proxy_save'] && isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { - $proxy = preg_replace("/[^0-9a-fA-F.,: ]/", '', $_SERVER['HTTP_X_FORWARDED_FOR']); - $post['body'] .= "\n" . $proxy . ""; - } + if ($config['proxy_save'] && isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { + $proxy = preg_replace("/[^0-9a-fA-F.,: ]/", '', $_SERVER['HTTP_X_FORWARDED_FOR']); + $post['body'] .= "\n" . $proxy . ""; } $post['body_nomarkup'] = $post['body']; @@ -1311,7 +1135,7 @@ function handle_post(Context $ctx) } } - if (!hasPermission($config['mod']['bypass_filters'], $board['uri']) && !$dropped_post) { + if (!hasPermission($config['mod']['bypass_filters'], $board['uri'])) { require_once 'inc/filters.php'; do_filters($ctx, $post); } @@ -1616,7 +1440,7 @@ function handle_post(Context $ctx) } } - if (!hasPermission($config['mod']['postunoriginal'], $board['uri']) && $config['robot_enable'] && checkRobot($post['body_nomarkup']) && !$dropped_post) { + if (!hasPermission($config['mod']['postunoriginal'], $board['uri']) && $config['robot_enable'] && checkRobot($post['body_nomarkup'])) { undoImage($post); if ($config['robot_mute']) { error(sprintf($config['error']['muted'], mute())); @@ -1662,41 +1486,6 @@ function handle_post(Context $ctx) $post['id'] = $id = post($post); $post['slug'] = slugify($post); - if ($dropped_post && $dropped_post['from_nntp']) { - $query = prepare("INSERT INTO ``nntp_references`` (`board`, `id`, `message_id`, `message_id_digest`, `own`, `headers`) VALUES " . - "(:board , :id , :message_id , :message_id_digest , false, :headers)"); - - $query->bindValue(':board', $dropped_post['board']); - $query->bindValue(':id', $id); - $query->bindValue(':message_id', $dropped_post['msgid']); - $query->bindValue(':message_id_digest', sha1($dropped_post['msgid'])); - $query->bindValue(':headers', $dropped_post['headers']); - $query->execute() or error(db_error($query)); - } // ^^^^^ For inbound posts ^^^^^ - elseif ($config['nntpchan']['enabled'] && $config['nntpchan']['group']) { - // vvvvv For outbound posts vvvvv - - require_once('inc/nntpchan/nntpchan.php'); - $msgid = gen_msgid($post['board'], $post['id']); - - list($headers, $files) = post2nntp($post, $msgid); - - $message = gen_nntp($headers, $files); - - $query = prepare("INSERT INTO ``nntp_references`` (`board`, `id`, `message_id`, `message_id_digest`, `own`, `headers`) VALUES " . - "(:board , :id , :message_id , :message_id_digest , true , :headers)"); - - $query->bindValue(':board', $post['board']); - $query->bindValue(':id', $post['id']); - $query->bindValue(':message_id', $msgid); - $query->bindValue(':message_id_digest', sha1($msgid)); - $query->bindValue(':headers', json_encode($headers)); - $query->execute() or error(db_error($query)); - - // Let's broadcast it! - nntp_publish($message, $msgid); - } - insertFloodPost($post); // Handle cyclical threads @@ -1863,20 +1652,11 @@ function handle_appeal(Context $ctx) $ctx = Vichan\build_context($config); -// Is it a post coming from NNTP? Let's extract it and pretend it's a normal post. -if (isset($_GET['Newsgroups'])) { - if ($config['nntpchan']['enabled']) { - handle_nntpchan($ctx); - } else { - error("NNTPChan: NNTPChan support is disabled"); - } -} - if (isset($_POST['delete'])) { handle_delete($ctx); } elseif (isset($_POST['report'])) { handle_report($ctx); -} elseif (isset($_POST['post']) || $dropped_post) { +} elseif (isset($_POST['post'])) { handle_post($ctx); } elseif (isset($_POST['appeal'])) { handle_appeal($ctx); From 634f76959221a648bb412b80596673fa904c22da Mon Sep 17 00:00:00 2001 From: Zankaria Date: Wed, 23 Apr 2025 22:02:57 +0200 Subject: [PATCH 48/85] config.php: remove nntpchan options --- inc/config.php | 39 --------------------------------------- 1 file changed, 39 deletions(-) diff --git a/inc/config.php b/inc/config.php index 55580cec..8e1f337c 100644 --- a/inc/config.php +++ b/inc/config.php @@ -1896,45 +1896,6 @@ // Example: Adding the pre-markup post body to the API as "com_nomarkup". // $config['api']['extra_fields'] = array('body_nomarkup' => 'com_nomarkup'); -/* - * ================== - * NNTPChan settings - * ================== - */ - -/* - * Please keep in mind that NNTPChan support in vichan isn't finished yet / is in an experimental - * state. Please join #nntpchan on Rizon in order to peer with someone. - */ - - $config['nntpchan'] = array(); - - // Enable NNTPChan integration - $config['nntpchan']['enabled'] = false; - - // NNTP server - $config['nntpchan']['server'] = "localhost:1119"; - - // Global dispatch array. Add your boards to it to enable them. Please make - // sure that this setting is set in a global context. - $config['nntpchan']['dispatch'] = array(); // 'overchan.test' => 'test' - - // Trusted peer - an IP address of your NNTPChan instance. This peer will have - // increased capabilities, eg.: will evade spamfilter. - $config['nntpchan']['trusted_peer'] = '127.0.0.1'; - - // Salt for message ID generation. Keep it long and secure. - $config['nntpchan']['salt'] = 'change_me+please'; - - // A local message ID domain. Make sure to change it. - $config['nntpchan']['domain'] = 'example.vichan.net'; - - // An NNTPChan group name. - // Please set this setting in your board/config.php, not globally. - $config['nntpchan']['group'] = false; // eg. 'overchan.test' - - - /* * ==================== * Other/uncategorized From 24e43a5aa14e78ff47b3d1dcbba4d9f82abf33c8 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Wed, 23 Apr 2025 22:03:32 +0200 Subject: [PATCH 49/85] nntpchan: removed handler code --- inc/nntpchan/nntpchan.php | 152 -------------------------------------- inc/nntpchan/tests.php | 30 -------- 2 files changed, 182 deletions(-) delete mode 100644 inc/nntpchan/nntpchan.php delete mode 100644 inc/nntpchan/tests.php diff --git a/inc/nntpchan/nntpchan.php b/inc/nntpchan/nntpchan.php deleted file mode 100644 index de67a193..00000000 --- a/inc/nntpchan/nntpchan.php +++ /dev/null @@ -1,152 +0,0 @@ -"; -} - - -function gen_nntp($headers, $files) { - if (count($files) == 0) { - } - else if (count($files) == 1 && $files[0]['type'] == 'text/plain') { - $content = $files[0]['text'] . "\r\n"; - $headers['Content-Type'] = "text/plain; charset=UTF-8"; - } - else { - $boundary = sha1($headers['Message-Id']); - $content = ""; - $headers['Content-Type'] = "multipart/mixed; boundary=$boundary"; - foreach ($files as $file) { - $content .= "--$boundary\r\n"; - if (isset($file['name'])) { - $file['name'] = preg_replace('/[\r\n\0"]/', '', $file['name']); - $content .= "Content-Disposition: form-data; filename=\"$file[name]\"; name=\"attachment\"\r\n"; - } - $type = explode('/', $file['type'])[0]; - if ($type == 'text') { - $file['type'] .= '; charset=UTF-8'; - } - $content .= "Content-Type: $file[type]\r\n"; - if ($type != 'text' && $type != 'message') { - $file['text'] = base64_encode($file['text']); - $content .= "Content-Transfer-Encoding: base64\r\n"; - } - $content .= "\r\n"; - $content .= $file['text']; - $content .= "\r\n"; - } - $content .= "--$boundary--\r\n"; - - $headers['Mime-Version'] = '1.0'; - } - //$headers['Content-Length'] = strlen($content); - $headers['Date'] = date('r', $headers['Date']); - $out = ""; - foreach ($headers as $id => $val) { - $val = str_replace("\n", "\n\t", $val); - $out .= "$id: $val\r\n"; - } - $out .= "\r\n"; - $out .= $content; - return $out; -} - -function nntp_publish($msg, $id) { - global $config; - $server = $config["nntpchan"]["server"]; - $s = fsockopen("tcp://$server"); - fgets($s); - fputs($s, "MODE STREAM\r\n"); - fgets($s); - fputs($s, "TAKETHIS $id\r\n"); - fputs($s, $msg); - fputs($s, "\r\n.\r\n"); - fgets($s); - fputs($s, "QUIT\r\n"); - fclose($s); -} - -function post2nntp($post, $msgid) { - global $config; - - $headers = array(); - $files = array(); - - $headers['Message-Id'] = $msgid; - $headers['Newsgroups'] = $config['nntpchan']['group']; - $headers['Date'] = time(); - $headers['Subject'] = $post['subject'] ? $post['subject'] : "None"; - $headers['From'] = $post['name'] . " "; - - if ($post['email'] == 'sage') { - $headers['X-Sage'] = true; - } - - if (!$post['op']) { - // Get muh parent - $query = prepare("SELECT `message_id` FROM ``nntp_references`` WHERE `board` = :board AND `id` = :id"); - $query->bindValue(':board', $post['board']); - $query->bindValue(':id', $post['thread']); - $query->execute() or error(db_error($query)); - - if ($result = $query->fetch(PDO::FETCH_ASSOC)) { - $headers['References'] = $result['message_id']; - } - else { - return false; // We don't have OP. Discarding. - } - } - - // Let's parse the body a bit. - $body = trim($post['body_nomarkup']); - $body = preg_replace('/\r?\n/', "\r\n", $body); - $body = preg_replace_callback('@>>(>/([a-zA-Z0-9_+-]+)/)?([0-9]+)@', function($o) use ($post) { - if ($o[1]) { - $board = $o[2]; - } - else { - $board = $post['board']; - } - $id = $o[3]; - - $query = prepare("SELECT `message_id_digest` FROM ``nntp_references`` WHERE `board` = :board AND `id` = :id"); - $query->bindValue(':board', $board); - $query->bindValue(':id', $id); - $query->execute() or error(db_error($query)); - - if ($result = $query->fetch(PDO::FETCH_ASSOC)) { - return ">>".substr($result['message_id_digest'], 0, 18); - } - else { - return $o[0]; // Should send URL imo - } - }, $body); - $body = preg_replace('/>>>>([0-9a-fA-F])+/', '>>\1', $body); - - - $files[] = array('type' => 'text/plain', 'text' => $body); - - foreach ($post['files'] as $id => $file) { - $fc = array(); - - $fc['type'] = $file['type']; - $fc['text'] = file_get_contents($file['file_path']); - $fc['name'] = $file['name']; - - $files[] = $fc; - } - - return array($headers, $files); -} diff --git a/inc/nntpchan/tests.php b/inc/nntpchan/tests.php deleted file mode 100644 index a63789d7..00000000 --- a/inc/nntpchan/tests.php +++ /dev/null @@ -1,30 +0,0 @@ - "czaks ", "Message-Id" => "<1234.0000.".$time."@example.vichan.net>", "Newsgroups" => "overchan.test", "Date" => time(), "Subject" => "None"], -[['type' => 'text/plain', 'text' => "THIS IS A NEW TEST THREAD"]]); -echo "\n@@@@ Single msg:\n"; -echo $m1 = gennntp(["From" => "czaks ", "Message-Id" => "<1234.1234.".$time."@example.vichan.net>", "Newsgroups" => "overchan.test", "Date" => time(), "Subject" => "None", "References" => "<1234.0000.".$time."@example.vichan.net>"], -[['type' => 'text/plain', 'text' => "hello world, with no image :("]]); -echo "\n@@@@ Single msg and pseudoimage:\n"; -echo $m2 = gennntp(["From" => "czaks ", "Message-Id" => "<1234.2137.".$time."@example.vichan.net>", "Newsgroups" => "overchan.test", "Date" => time(), "Subject" => "None", "References" => "<1234.0000.".$time."@example.vichan.net>"], -[['type' => 'text/plain', 'text' => "hello world, now with an image!"], - ['type' => 'image/gif', 'text' => base64_decode("R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="), 'name' => "urgif.gif"]]); -echo "\n@@@@ Single msg and two pseudoimages:\n"; -echo $m3 = gennntp(["From" => "czaks ", "Message-Id" => "<1234.1488.".$time."@example.vichan.net>", "Newsgroups" => "overchan.test", "Date" => time(), "Subject" => "None", "References" => "<1234.0000.".$time."@example.vichan.net>"], -[['type' => 'text/plain', 'text' => "hello world, now WITH TWO IMAGES!!!"], - ['type' => 'image/gif', 'text' => base64_decode("R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="), 'name' => "urgif.gif"], - ['type' => 'image/gif', 'text' => base64_decode("R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="), 'name' => "urgif2.gif"]]); -shoveitup($m0, "<1234.0000.".$time."@example.vichan.net>"); -sleep(1); -shoveitup($m1, "<1234.1234.".$time."@example.vichan.net>"); -sleep(1); -shoveitup($m2, "<1234.2137.".$time."@example.vichan.net>"); -shoveitup($m3, "<1234.1488.".$time."@example.vichan.net>"); - From 3510f05fe82d633c6142abffaad8ab3252cb1ccf Mon Sep 17 00:00:00 2001 From: fowr <89118232+perdedora@users.noreply.github.com> Date: Wed, 16 Apr 2025 12:31:18 -0300 Subject: [PATCH 50/85] auth.php: use password_hash with bcrypt and password_verify for login --- inc/mod/auth.php | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/inc/mod/auth.php b/inc/mod/auth.php index 326f6f53..743543ef 100644 --- a/inc/mod/auth.php +++ b/inc/mod/auth.php @@ -46,22 +46,20 @@ function crypt_password($password) { global $config; // `salt` database field is reused as a version value. We don't want it to be 0. $version = $config['password_crypt_version'] ? $config['password_crypt_version'] : 1; - $new_salt = generate_salt(); - $password = crypt($password, $config['password_crypt'] . $new_salt . "$"); - return array($version, $password); + $hash = \password_hash($password, \PASSWORD_BCRYPT); + return [$version, $hash]; } -function test_password($password, $salt, $test) { +function test_password(string $db_hash, string|int $version, string $input_password): array { // Version = 0 denotes an old password hashing schema. In the same column, the // password hash was kept previously - $version = (strlen($salt) <= 8) ? (int) $salt : 0; - - if ($version == 0) { - $comp = hash('sha256', $salt . sha1($test)); + $version = (int)$version; + if ($version < 2) { + $ok = \hash_equals($db_hash, \crypt($input_password, $db_hash)); } else { - $comp = crypt($test, $password); + $ok = \password_verify($input_password, $db_hash); } - return array($version, hash_equals($password, $comp)); + return [$version, $ok]; } function generate_salt() { @@ -79,7 +77,7 @@ function login($username, $password) { list($version, $ok) = test_password($user['password'], $user['version'], $password); if ($ok) { - if ($config['password_crypt_version'] > $version) { + if ((int)$user['version'] < 2) { // It's time to upgrade the password hashing method! list ($user['version'], $user['password']) = crypt_password($password); $query = prepare("UPDATE ``mods`` SET `password` = :password, `version` = :version WHERE `id` = :id"); From a8a947af651786ebbd184cfa7a9354b074bbc635 Mon Sep 17 00:00:00 2001 From: fowr <89118232+perdedora@users.noreply.github.com> Date: Wed, 16 Apr 2025 12:31:35 -0300 Subject: [PATCH 51/85] config.php: bump password crypt version --- inc/config.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/config.php b/inc/config.php index 8e1f337c..fb4b4493 100644 --- a/inc/config.php +++ b/inc/config.php @@ -2010,7 +2010,7 @@ // Password hashing method version // If set to 0, it won't upgrade hashes using old password encryption schema, only create new. // You can set it to a higher value, to further migrate to other password hashing function. - $config['password_crypt_version'] = 1; + $config['password_crypt_version'] = 2; // Use CAPTCHA for reports? $config['report_captcha'] = false; From f7bef11ac9873e07c5ba153b38b427294537aca0 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Wed, 16 Apr 2025 19:18:50 +0200 Subject: [PATCH 52/85] auth.php: use pre-hashing for BCRYPT --- inc/mod/auth.php | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/inc/mod/auth.php b/inc/mod/auth.php index 743543ef..c77e1ed2 100644 --- a/inc/mod/auth.php +++ b/inc/mod/auth.php @@ -46,8 +46,13 @@ function crypt_password($password) { global $config; // `salt` database field is reused as a version value. We don't want it to be 0. $version = $config['password_crypt_version'] ? $config['password_crypt_version'] : 1; - $hash = \password_hash($password, \PASSWORD_BCRYPT); - return [$version, $hash]; + $pre_hash = \hash('tiger160,3', $password, false); // Note that it's truncated to 72 in the next line. + $r = \password_hash($pre_hash, \PASSWORD_BCRYPT); + if ($r === false) { + throw new \RuntimeException("Could not hash password"); + } + + return [ $version, $r ]; } function test_password(string $db_hash, string|int $version, string $input_password): array { @@ -57,9 +62,10 @@ function test_password(string $db_hash, string|int $version, string $input_passw if ($version < 2) { $ok = \hash_equals($db_hash, \crypt($input_password, $db_hash)); } else { - $ok = \password_verify($input_password, $db_hash); + $pre_hash = \hash('tiger160,3', $input_password, false); + $ok = \password_verify($pre_hash, $db_hash); } - return [$version, $ok]; + return [ $version, $ok ]; } function generate_salt() { From 715005ec96bc3208c3afb917cca3c3eb2d3aabc7 Mon Sep 17 00:00:00 2001 From: fowr <89118232+perdedora@users.noreply.github.com> Date: Wed, 16 Apr 2025 14:44:37 -0300 Subject: [PATCH 53/85] auth.php: no need to repass version anymore in test_password --- inc/mod/auth.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/inc/mod/auth.php b/inc/mod/auth.php index c77e1ed2..e5bd6548 100644 --- a/inc/mod/auth.php +++ b/inc/mod/auth.php @@ -55,7 +55,7 @@ function crypt_password($password) { return [ $version, $r ]; } -function test_password(string $db_hash, string|int $version, string $input_password): array { +function test_password(string $db_hash, string|int $version, string $input_password): bool { // Version = 0 denotes an old password hashing schema. In the same column, the // password hash was kept previously $version = (int)$version; @@ -65,7 +65,7 @@ function test_password(string $db_hash, string|int $version, string $input_passw $pre_hash = \hash('tiger160,3', $input_password, false); $ok = \password_verify($pre_hash, $db_hash); } - return [ $version, $ok ]; + return $ok; } function generate_salt() { @@ -80,7 +80,7 @@ function login($username, $password) { $query->execute() or error(db_error($query)); if ($user = $query->fetch(PDO::FETCH_ASSOC)) { - list($version, $ok) = test_password($user['password'], $user['version'], $password); + $ok = test_password($user['password'], $user['version'], $password); if ($ok) { if ((int)$user['version'] < 2) { From 4ef10e26fc09909233e6a6e436722e536f7ec315 Mon Sep 17 00:00:00 2001 From: fowr <89118232+perdedora@users.noreply.github.com> Date: Wed, 16 Apr 2025 14:46:11 -0300 Subject: [PATCH 54/85] auth.php: cleanup ununsed function --- inc/mod/auth.php | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/inc/mod/auth.php b/inc/mod/auth.php index e5bd6548..5e3b7c98 100644 --- a/inc/mod/auth.php +++ b/inc/mod/auth.php @@ -68,11 +68,7 @@ function test_password(string $db_hash, string|int $version, string $input_passw return $ok; } -function generate_salt() { - return strtr(base64_encode(random_bytes(16)), '+', '.'); -} - -function login($username, $password) { +function login(string $username, string $password): array|false { global $mod, $config; $query = prepare("SELECT `id`, `type`, `boards`, `password`, `version` FROM ``mods`` WHERE BINARY `username` = :username"); @@ -215,4 +211,4 @@ function check_login(Context $ctx, $prompt = false) { 'boards' => explode(',', $user['boards']) ); } -} +} \ No newline at end of file From 2f69af8267bb29718e2432ab3384d84b5573aff8 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Wed, 16 Apr 2025 21:06:03 +0200 Subject: [PATCH 55/85] auth.php: remove unused global --- 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 5e3b7c98..bcf2d037 100644 --- a/inc/mod/auth.php +++ b/inc/mod/auth.php @@ -69,7 +69,7 @@ function test_password(string $db_hash, string|int $version, string $input_passw } function login(string $username, string $password): array|false { - global $mod, $config; + global $mod; $query = prepare("SELECT `id`, `type`, `boards`, `password`, `version` FROM ``mods`` WHERE BINARY `username` = :username"); $query->bindValue(':username', $username); From 3e3b71211a7247b4c507ae1e6701d48a1f42c962 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Wed, 16 Apr 2025 21:18:30 +0200 Subject: [PATCH 56/85] auth.php: use php 8.4 cost for bcrypt --- inc/mod/auth.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/inc/mod/auth.php b/inc/mod/auth.php index bcf2d037..cdf04abf 100644 --- a/inc/mod/auth.php +++ b/inc/mod/auth.php @@ -47,7 +47,7 @@ function crypt_password($password) { // `salt` database field is reused as a version value. We don't want it to be 0. $version = $config['password_crypt_version'] ? $config['password_crypt_version'] : 1; $pre_hash = \hash('tiger160,3', $password, false); // Note that it's truncated to 72 in the next line. - $r = \password_hash($pre_hash, \PASSWORD_BCRYPT); + $r = \password_hash($pre_hash, \PASSWORD_BCRYPT, [ 'cost' => 12 ]); if ($r === false) { throw new \RuntimeException("Could not hash password"); } @@ -73,7 +73,7 @@ function login(string $username, string $password): array|false { $query = prepare("SELECT `id`, `type`, `boards`, `password`, `version` FROM ``mods`` WHERE BINARY `username` = :username"); $query->bindValue(':username', $username); - $query->execute() or error(db_error($query)); + $query->execute(); if ($user = $query->fetch(PDO::FETCH_ASSOC)) { $ok = test_password($user['password'], $user['version'], $password); @@ -86,7 +86,7 @@ function login(string $username, string $password): array|false { $query->bindValue(':password', $user['password']); $query->bindValue(':version', $user['version']); $query->bindValue(':id', $user['id']); - $query->execute() or error(db_error($query)); + $query->execute(); } return $mod = array( From f7dae74522c9b98847cee2137bfdeddf13142f2a Mon Sep 17 00:00:00 2001 From: Zankaria Date: Wed, 23 Apr 2025 22:29:23 +0200 Subject: [PATCH 57/85] auth.php: format --- inc/mod/auth.php | 54 +++++++++++++++++++++++++++--------------------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/inc/mod/auth.php b/inc/mod/auth.php index cdf04abf..610e2e7c 100644 --- a/inc/mod/auth.php +++ b/inc/mod/auth.php @@ -10,7 +10,7 @@ use Vichan\Functions\{Hide, Net}; defined('TINYBOARD') or exit; // create a hash/salt pair for validate logins -function mkhash($username, $password, $salt = false) { +function mkhash(string $username, ?string $password, mixed $salt = false): array|string { global $config; if (!$salt) { @@ -42,7 +42,7 @@ function mkhash($username, $password, $salt = false) { } } -function crypt_password($password) { +function crypt_password(string $password): array { global $config; // `salt` database field is reused as a version value. We don't want it to be 0. $version = $config['password_crypt_version'] ? $config['password_crypt_version'] : 1; @@ -56,8 +56,6 @@ function crypt_password($password) { } function test_password(string $db_hash, string|int $version, string $input_password): bool { - // Version = 0 denotes an old password hashing schema. In the same column, the - // password hash was kept previously $version = (int)$version; if ($version < 2) { $ok = \hash_equals($db_hash, \crypt($input_password, $db_hash)); @@ -89,23 +87,24 @@ function login(string $username, string $password): array|false { $query->execute(); } - return $mod = array( + return $mod = [ 'id' => $user['id'], 'type' => $user['type'], 'username' => $username, 'hash' => mkhash($username, $user['password']), 'boards' => explode(',', $user['boards']) - ); + ]; } } return false; } -function setCookies() { +function setCookies(): void { global $mod, $config; - if (!$mod) + if (!$mod) { error('setCookies() was called for a non-moderator!'); + } $is_https = Net\is_connection_https(); @@ -118,14 +117,14 @@ function setCookies() { time() + $config['cookies']['expire'], $config['cookies']['jail'] ? $config['cookies']['path'] : '/', null, $is_https, $config['cookies']['httponly']); } -function destroyCookies() { +function destroyCookies(): void { global $config; $is_https = Net\is_connection_https(); // Delete the cookies setcookie($config['cookies']['mod'], 'deleted', time() - $config['cookies']['expire'], $config['cookies']['jail']?$config['cookies']['path'] : '/', null, $is_https, true); } -function modLog($action, $_board=null) { +function modLog(string $action, ?string $_board = null): void { global $mod, $board, $config; $query = prepare("INSERT INTO ``modlogs`` VALUES (:id, :ip, :board, :time, :text)"); $query->bindValue(':id', (isset($mod['id']) ? $mod['id'] : -1), PDO::PARAM_INT); @@ -140,16 +139,18 @@ function modLog($action, $_board=null) { $query->bindValue(':board', null, PDO::PARAM_NULL); $query->execute() or error(db_error($query)); - if ($config['syslog']) + if ($config['syslog']) { _syslog(LOG_INFO, '[mod/' . $mod['username'] . ']: ' . $action); + } } -function create_pm_header() { +function create_pm_header(): mixed { global $mod, $config; if ($config['cache']['enabled'] && ($header = cache::get('pm_unread_' . $mod['id'])) != false) { - if ($header === true) + if ($header === true) { return false; + } return $header; } @@ -158,26 +159,29 @@ function create_pm_header() { $query->bindValue(':id', $mod['id'], PDO::PARAM_INT); $query->execute() or error(db_error($query)); - if ($pm = $query->fetch(PDO::FETCH_ASSOC)) - $header = array('id' => $pm['id'], 'waiting' => $query->rowCount() - 1); - else + if ($pm = $query->fetch(PDO::FETCH_ASSOC)) { + $header = [ 'id' => $pm['id'], 'waiting' => $query->rowCount() - 1 ]; + } else { $header = true; + } - if ($config['cache']['enabled']) + if ($config['cache']['enabled']) { cache::set('pm_unread_' . $mod['id'], $header); + } - if ($header === true) + if ($header === true) { return false; + } return $header; } -function make_secure_link_token($uri) { +function make_secure_link_token(string $uri): string { global $mod, $config; return substr(sha1($config['cookies']['salt'] . '-' . $uri . '-' . $mod['id']), 0, 8); } -function check_login(Context $ctx, $prompt = false) { +function check_login(Context $ctx, bool $prompt = false): void { global $config, $mod; // Validate session @@ -187,7 +191,9 @@ function check_login(Context $ctx, $prompt = false) { if (count($cookie) != 3) { // Malformed cookies destroyCookies(); - if ($prompt) mod_login($ctx); + if ($prompt) { + mod_login($ctx); + } exit; } @@ -200,7 +206,9 @@ function check_login(Context $ctx, $prompt = false) { if ($cookie[1] !== mkhash($cookie[0], $user['password'], $cookie[2])) { // Malformed cookies destroyCookies(); - if ($prompt) mod_login($ctx); + if ($prompt) { + mod_login($ctx); + } exit; } @@ -211,4 +219,4 @@ function check_login(Context $ctx, $prompt = false) { 'boards' => explode(',', $user['boards']) ); } -} \ No newline at end of file +} From 63228d04bedb978b07d78a303205a1c5d14d5f1b Mon Sep 17 00:00:00 2001 From: Zankaria Date: Wed, 23 Apr 2025 23:16:18 +0200 Subject: [PATCH 58/85] pages.php: use Context provided config --- inc/mod/pages.php | 141 ++++++++++++++++++++++++++++++---------------- 1 file changed, 91 insertions(+), 50 deletions(-) diff --git a/inc/mod/pages.php b/inc/mod/pages.php index 18a7a6fa..17d2ef74 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -46,7 +46,7 @@ function clone_wrapped_with_exist_check($clonefn, $src, $dest) { } function mod_login(Context $ctx, $redirect = false) { - global $config; + $config = $ctx->get('config'); $args = []; @@ -89,15 +89,16 @@ function mod_confirm(Context $ctx, $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'); $report_queries = $ctx->get(ReportQueries::class); $args = []; @@ -191,7 +192,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']); @@ -471,7 +472,9 @@ 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']); @@ -537,7 +540,9 @@ 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']); @@ -592,7 +597,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']); @@ -610,7 +615,9 @@ 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']); @@ -657,7 +664,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']); @@ -672,7 +679,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']); @@ -697,7 +704,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']); @@ -734,7 +741,7 @@ function protect_ip($entry) { } 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']); @@ -802,7 +809,9 @@ 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)){ require "templates/themes/overboards/overboards.php"; @@ -833,20 +842,24 @@ function mod_view_board(Context $ctx, $boardName, $page_no = 1) { } function mod_view_thread(Context $ctx, $boardName, $thread) { - global $config, $mod; + global $mod; - if (!openBoard($boardName)) + if (!openBoard($boardName)) { + $config = $ctx->get('config'); error($config['error']['noboard']); + } $page = buildThread($thread, true, $mod); echo $page; } function mod_view_thread50(Context $ctx, $boardName, $thread) { - global $config, $mod; + global $mod; - if (!openBoard($boardName)) + if (!openBoard($boardName)) { + $config = $ctx->get('config'); error($config['error']['noboard']); + } $page = buildThread50($thread, true, $mod); echo $page; @@ -1095,7 +1108,7 @@ function mod_user_posts_by_passwd(Context $ctx, string $passwd, string $encoded_ } function mod_ban(Context $ctx) { - global $config; + $config = $ctx->get('config'); if (!hasPermission($config['mod']['ban'])) error($config['error']['noaccess']); @@ -1116,7 +1129,7 @@ function mod_ban(Context $ctx) { } function mod_warning(Context $ctx) { - global $config; + $config = $ctx->get('config'); if (!hasPermission($config['mod']['warning'])) error($config['error']['noaccess']); @@ -1133,9 +1146,10 @@ function mod_warning(Context $ctx) { } function mod_bans(Context $ctx) { - global $config; global $mod; + $config = $ctx->get('config'); + if (!hasPermission($config['mod']['view_banlist'])) error($config['error']['noaccess']); @@ -1168,7 +1182,9 @@ 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']); @@ -1180,7 +1196,9 @@ 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']); @@ -1262,7 +1280,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']); @@ -1298,7 +1316,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']); @@ -1322,7 +1340,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']); @@ -1344,7 +1362,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']); @@ -1927,7 +1945,9 @@ function mod_merge(Context $ctx, $originBoard, $postID) { } function mod_ban_post(Context $ctx, $board, $delete, $post, $token = false) { - global $config, $mod; + global $mod; + + $config = $ctx->get('config'); if (!openBoard($board)) error($config['error']['noboard']); @@ -2046,7 +2066,9 @@ function mod_ban_post(Context $ctx, $board, $delete, $post, $token = false) { } function mod_warning_post(Context $ctx, $board, $post, $token = false) { - global $config, $mod; + global $mod; + + $config = $ctx->get('config'); if (!openBoard($board)) error($config['error']['noboard']); @@ -2143,7 +2165,7 @@ function mod_warning_post(Context $ctx, $board, $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']); @@ -2220,7 +2242,9 @@ function mod_edit_post(Context $ctx, $board, $edit_raw_html, $postID) { } function mod_delete(Context $ctx, $board, $post) { - global $config, $mod; + global $mod; + + $config = $ctx->get('config'); if (!openBoard($board)) error($config['error']['noboard']); @@ -2283,7 +2307,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']); @@ -2306,7 +2330,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']); @@ -2351,8 +2375,9 @@ function mod_spoiler_image(Context $ctx, $board, $post, $file) { } function mod_deletebyip(Context $ctx, $boardName, $post, $global = false) { - global $config, $board, $mod; + global $board, $mod; + $config = $ctx->get('config'); $global = (bool)$global; if (!openBoard($boardName)) @@ -2470,7 +2495,9 @@ 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']); @@ -2648,7 +2675,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']); @@ -2669,7 +2696,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']); @@ -2767,7 +2794,7 @@ function mod_pm(Context $ctx, $id, $reply = false) { } function mod_inbox(Context $ctx) { - global $config, $mod; + global $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'); $query->bindValue(':mod', $mod['id']); @@ -2791,7 +2818,9 @@ 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']); @@ -2839,7 +2868,9 @@ 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']); @@ -2911,7 +2942,9 @@ 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']); @@ -2978,7 +3011,7 @@ function mod_reports(Context $ctx) { } function mod_report_dismiss(Context $ctx, $id, $all = false) { - global $config; + $config = $ctx->get('config'); $report_queries = $ctx->get(ReportQueries::class); $report = $report_queries->getReportById($id); @@ -3010,7 +3043,9 @@ function mod_report_dismiss(Context $ctx, $id, $all = false) { } function mod_recent_posts(Context $ctx, $lim, $board_list = false, $json = false) { - global $config, $mod, $pdo; + global $mod, $pdo; + + $config = $ctx->get('config'); if (!hasPermission($config['mod']['recent'])) error($config['error']['noaccess']); @@ -3120,7 +3155,9 @@ function mod_recent_posts(Context $ctx, $lim, $board_list = false, $json = false } 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']); @@ -3260,7 +3297,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']); @@ -3294,7 +3331,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']); @@ -3376,7 +3413,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']); @@ -3393,7 +3430,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']); @@ -3442,7 +3479,7 @@ function mod_delete_page_board(Context $ctx, $page = '', $board = false) { } function mod_edit_page(Context $ctx, $id) { - global $config, $mod, $board; + global $mod, $board; $query = prepare('SELECT * FROM ``pages`` WHERE `id` = :id'); $query->bindValue(':id', $id); @@ -3452,6 +3489,8 @@ function mod_edit_page(Context $ctx, $id) { if (!$page) error(_('Could not find the page you are trying to edit.')); + $config = $ctx->get('config'); + if (!$page['board'] && $mod['boards'][0] !== '*') error($config['error']['noaccess']); @@ -3513,11 +3552,13 @@ function mod_edit_page(Context $ctx, $id) { } function mod_pages(Context $ctx, $board = false) { - global $config, $mod, $pdo; + global $mod, $pdo; if (empty($board)) $board = false; + $config = $ctx->get('config'); + if (!$board && $mod['boards'][0] !== '*') error($config['error']['noaccess']); @@ -3638,7 +3679,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 e3693f2d196df497b83de6e58472778d3b64027f Mon Sep 17 00:00:00 2001 From: Zankaria Date: Fri, 25 Apr 2025 14:23:52 +0200 Subject: [PATCH 59/85] flags: add 420-tan flag --- static/flags/420.png | Bin 0 -> 465 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 static/flags/420.png diff --git a/static/flags/420.png b/static/flags/420.png new file mode 100644 index 0000000000000000000000000000000000000000..019f2e7f354a398a3fa573d84efb509e7b42ce19 GIT binary patch literal 465 zcmV;?0WSWDP)&C$!e#AjxelUaSOh$X6 zc=9~leS48a)}XkQUDv1nC_S9oB=QN1c}qW->P!-wfV(N5NxIuGKGO!l1aYwx`t=?L zIpi87O!=mC?rRe}aC5-$)PVTD>MhhdK-t^3m*|%Lgq$?Ca&tm_8!BJ}XUHerAdm%) zACnl>iOFf=K8Ue`^30|0RvgaZcNHCfgalk|#FLv8$e_A2PL z=#ZgefKz-Go2{JhwZx{65RT$fL#=%UqEoNRzM36FOucYwaT4vqk=XPRhGSrd=&E^r z+`J&A?A;Nyo-08>p)Gb*i31L~263Fc$M{SvsnTd__x~V=TxzJ53Tib~0b1C+z2Gm? zpHGiV`C;&8ipg>7j!z9?|4f`XGwKr#7nXyGYUtzvEEo6zMbh1Ko5bCt00000NkvXX Hu0mjfmaWoO literal 0 HcmV?d00001 From 2cc7a70e4caaf834e65a3bf99dc173f39dd21a93 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Fri, 25 Apr 2025 23:31:33 +0200 Subject: [PATCH 60/85] flags: fix typo in Stirner flag name --- static/flags/{stiner.png => stirner.png} | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename static/flags/{stiner.png => stirner.png} (100%) diff --git a/static/flags/stiner.png b/static/flags/stirner.png similarity index 100% rename from static/flags/stiner.png rename to static/flags/stirner.png From 18624963d5c4dea1eadd9dd0e74c211f8ca66372 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Fri, 2 May 2025 22:43:30 +0200 Subject: [PATCH 61/85] style.css: set minimum width for text --- stylesheets/style.css | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/stylesheets/style.css b/stylesheets/style.css index 4a090f33..bef69cca 100644 --- a/stylesheets/style.css +++ b/stylesheets/style.css @@ -575,6 +575,13 @@ div.post div.body { white-space: pre-wrap; } +div.post div.body:before { + content: ""; + width: 18ch; + display: block; + overflow: hidden; +} + div.post.reply.highlighted { background: #D6BAD0; } From a779f121448be4af18525c1caa8c2547c5430e1d Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sat, 3 May 2025 22:11:32 +0200 Subject: [PATCH 62/85] flags: correct rodina naming --- static/flags/rodina_get.png | Bin 0 -> 1379 bytes static/flags/rodina_lp.png | Bin 0 -> 522 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 static/flags/rodina_get.png create mode 100644 static/flags/rodina_lp.png diff --git a/static/flags/rodina_get.png b/static/flags/rodina_get.png new file mode 100644 index 0000000000000000000000000000000000000000..a22169ecaf5673e0d8da8fa20ed784687aab41d7 GIT binary patch literal 1379 zcmaJ=3pCVu6#l6;y%cR8Nhs;Lw(+Rlq>Xl{DJC&q?N%6r!I&`yLnDI*HBF`>npJjX zwc`;A8P5qNZ?Y>rJVG0W@|IV3|LvZ$=j@)dd(XM|JNMr6oqNtb-*>^9cvxYD`U-@Q z0^tbW21CV9xok03x9hF3#Xy^U#KsDtkgW)D&m%O8jkx^?1>g`GbVkTH5g}DZQqc($ zgceO(TawHLyvwN(SDOOkdT5uQQ#j=mwkYUaR9IMQcwANxKQk~aK9Ex%C@S&cbkIbV zPV{bSVx!C1o`6fWK8#L2sK6;}LS6L@ygF%eqwr*FyJ{ARP&cn-Cs7~t*puJ|$JH46Fikm_=>IG@+ zt#WdEhRPj%-N_5B@WPpK6?N=JjEqaLiQ#@12fZeAI+ zX%%n%>S*P4l>|~hJv>5bTd1;Dh|1ErlxXN{OsCq9rQLwGn*1|FsfQz^CO}Iqq$G1S zwKy7DCA1S`={$HNhIiR8A}zW|&hjThcUca@9vaw-UoJ(dht zX01bL9sm=MIzo3i$DuzL=F(v#H_X8lCT9jef-RK|<6{#|($S^@fEhb4-p~TT_&lF% z*;W%ctaTb7F6HicQ}JATxe`Lcr27-IFkIirbKD0XvSBho%rt@i zf}RZSgNt6W=CXn23V0!%Y0RCemq2@ce?y(D@kyC5_;p4o3?;wGb1Lw{zZZu>hnR1> zWuZC~>I$GmvQT?(p{5WTrSPP5zA8s(Z#bUgn@`u3c$-0&ILFsiWOJ}OJQS)bCQ1sX zALc?`X&o;-+0HW0*Am(@%IOvo<^hSng}~BIXzvhrnt09LqLgWK-{W9xS5gz^4NaB} z@sn--C&+`=GHfm9dh9U%R`R{uE3rnNMX)lLA6TNWdY4-KbXx_s-ax>ckwU)iO%8l> zPywwrH9Pbz-rVBw5yH`9mR4(!BJsHO2^(9IoxOvj)5-6Y(Aw`$oj&92;!1X-xO;d~ zKSy5PJ~UrC!;k6D3J45h2cwX)oX~S&-0+CV^Sr3&m<#A4|B~QxtnkX!YjN?{6B2JA z(GN*CZzccuQ_Ah+)SuJt{DQ>k8JSskvvYFq-OtM}cuKhuHnp>W>{`y1yk5~Wz literal 0 HcmV?d00001 diff --git a/static/flags/rodina_lp.png b/static/flags/rodina_lp.png new file mode 100644 index 0000000000000000000000000000000000000000..7cdd61a8768de592b1b5e58971aa14e9c6e2b9dd GIT binary patch literal 522 zcmV+l0`>igP)rulQzIK1ANP5$b310~SEu`)=RVJS&OK*F!DiMT^=Lwkrwo_5`)_K!=;OoX6i4#x z^t7nw+y@@fiAFdUn#uM8?Zg})}25iN$(ku|{Q~g1&G>{cH z0+~Ny22F~aOh&y@TkzK*mF#i_jTta&LN7DSS`>U^ei16C%Edsq3SrpX4L$TSL!3`& z4#_07orKSSf!FnSnHN(WMmmDh_+cIc)6P8goIq<}U+T4@TbSl8#DIgDJbZ8(Fl!4# zJVbYpOnZ;ih2vcrtf>*x%dDIMuaY?L0q^yhTl2G6QzKe0+=b<74GdPvY5oUhWd~$w zB#6(GJ*n;OLMi!?%7L+_M(Cke<2E4NE)5Nl1=!y%+RA)H0@V199urGWGWmq?avgTh zFMeewNWya=85(-R6Hd*nvjgJ{^a>2IiEc#P)eXN@+g73Ci?=fAXw1tBW)rF#JzA1`T?SUJ%d&fdJ?2mX!lF)g8SeEPx# literal 0 HcmV?d00001 From 6e5a56ff0e807cfc14d2ef79580a2972ffe560fe Mon Sep 17 00:00:00 2001 From: Zankaria Date: Thu, 8 May 2025 21:44:20 +0200 Subject: [PATCH 63/85] flags: remove incorrectly named rodina flags --- static/flags/rodinia_get.png | Bin 1379 -> 0 bytes static/flags/rodinia_lp.png | Bin 522 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 static/flags/rodinia_get.png delete mode 100644 static/flags/rodinia_lp.png diff --git a/static/flags/rodinia_get.png b/static/flags/rodinia_get.png deleted file mode 100644 index a22169ecaf5673e0d8da8fa20ed784687aab41d7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1379 zcmaJ=3pCVu6#l6;y%cR8Nhs;Lw(+Rlq>Xl{DJC&q?N%6r!I&`yLnDI*HBF`>npJjX zwc`;A8P5qNZ?Y>rJVG0W@|IV3|LvZ$=j@)dd(XM|JNMr6oqNtb-*>^9cvxYD`U-@Q z0^tbW21CV9xok03x9hF3#Xy^U#KsDtkgW)D&m%O8jkx^?1>g`GbVkTH5g}DZQqc($ zgceO(TawHLyvwN(SDOOkdT5uQQ#j=mwkYUaR9IMQcwANxKQk~aK9Ex%C@S&cbkIbV zPV{bSVx!C1o`6fWK8#L2sK6;}LS6L@ygF%eqwr*FyJ{ARP&cn-Cs7~t*puJ|$JH46Fikm_=>IG@+ zt#WdEhRPj%-N_5B@WPpK6?N=JjEqaLiQ#@12fZeAI+ zX%%n%>S*P4l>|~hJv>5bTd1;Dh|1ErlxXN{OsCq9rQLwGn*1|FsfQz^CO}Iqq$G1S zwKy7DCA1S`={$HNhIiR8A}zW|&hjThcUca@9vaw-UoJ(dht zX01bL9sm=MIzo3i$DuzL=F(v#H_X8lCT9jef-RK|<6{#|($S^@fEhb4-p~TT_&lF% z*;W%ctaTb7F6HicQ}JATxe`Lcr27-IFkIirbKD0XvSBho%rt@i zf}RZSgNt6W=CXn23V0!%Y0RCemq2@ce?y(D@kyC5_;p4o3?;wGb1Lw{zZZu>hnR1> zWuZC~>I$GmvQT?(p{5WTrSPP5zA8s(Z#bUgn@`u3c$-0&ILFsiWOJ}OJQS)bCQ1sX zALc?`X&o;-+0HW0*Am(@%IOvo<^hSng}~BIXzvhrnt09LqLgWK-{W9xS5gz^4NaB} z@sn--C&+`=GHfm9dh9U%R`R{uE3rnNMX)lLA6TNWdY4-KbXx_s-ax>ckwU)iO%8l> zPywwrH9Pbz-rVBw5yH`9mR4(!BJsHO2^(9IoxOvj)5-6Y(Aw`$oj&92;!1X-xO;d~ zKSy5PJ~UrC!;k6D3J45h2cwX)oX~S&-0+CV^Sr3&m<#A4|B~QxtnkX!YjN?{6B2JA z(GN*CZzccuQ_Ah+)SuJt{DQ>k8JSskvvYFq-OtM}cuKhuHnp>W>{`y1yk5~Wz diff --git a/static/flags/rodinia_lp.png b/static/flags/rodinia_lp.png deleted file mode 100644 index 7cdd61a8768de592b1b5e58971aa14e9c6e2b9dd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 522 zcmV+l0`>igP)rulQzIK1ANP5$b310~SEu`)=RVJS&OK*F!DiMT^=Lwkrwo_5`)_K!=;OoX6i4#x z^t7nw+y@@fiAFdUn#uM8?Zg})}25iN$(ku|{Q~g1&G>{cH z0+~Ny22F~aOh&y@TkzK*mF#i_jTta&LN7DSS`>U^ei16C%Edsq3SrpX4L$TSL!3`& z4#_07orKSSf!FnSnHN(WMmmDh_+cIc)6P8goIq<}U+T4@TbSl8#DIgDJbZ8(Fl!4# zJVbYpOnZ;ih2vcrtf>*x%dDIMuaY?L0q^yhTl2G6QzKe0+=b<74GdPvY5oUhWd~$w zB#6(GJ*n;OLMi!?%7L+_M(Cke<2E4NE)5Nl1=!y%+RA)H0@V199urGWGWmq?avgTh zFMeewNWya=85(-R6Hd*nvjgJ{^a>2IiEc#P)eXN@+g73Ci?=fAXw1tBW)rF#JzA1`T?SUJ%d&fdJ?2mX!lF)g8SeEPx# From 0c898abdfe4296eb453241fa56a3fd55c8ce22ba Mon Sep 17 00:00:00 2001 From: Zankaria Date: Thu, 8 May 2025 21:44:56 +0200 Subject: [PATCH 64/85] flags: remove grace flag --- static/flags/grace.png | Bin 1345 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 static/flags/grace.png diff --git a/static/flags/grace.png b/static/flags/grace.png deleted file mode 100644 index 7b4cb03ce4a3bfdea31fe783faf878d7ae8b8e95..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1345 zcmaizX;2eZ5XT>CE20QhD}q`TibW(CASeV;jDZjZ#1Jf=6$6$aF(d>c!9a>AqCga? zB!EJwf*c_M1mlgi$`y_f5E4$WilSh(rO367(@tmVm%f?Z|LlAF+nw3jm*ne1GBhwV zKnNLZ8hRw%#qm<^igc7dP-4vm2Rfh;=VIFe!mdc}KXwQJo6woF>cPw$beG~1$@HnQJj{$W8w6mZ&3>pOm z3~M>t7V2W4D;l)1#ax%mY**+=8E+MgHuIq^S;BO_!f}EY4m{!%Ggg%_t)MOr9_cCw zFLJkyw^rmvdgO68=0>_^vp0{n$S*{AL&sU@I033e8PleaZBxv1$>h2xM^J2*&B2(> z`CU~td|xurC>?qzby#BTY-MsLf+~q3mGfNXEUbiQ1sbul8Nyl4ABVRVHuLc229xT$ zqYVnzWwTaT%$|ZF7t4iirU*1y#XQVFFXvsqFf(c=ObtYw+h>GB*5>G)>ee64x3DXa z={m&Epuyy;yr?~6eQCe-a9?UU@48N1;_ZM@%@Ls=E7jY6rQV48_M6&rYZDYL+#Zum zfe$^!ynqi~$#1n$GQI=!W?p7*1daSfYsoWJ=}^7weT{6SR{maDF{mmVR?9#w%|Gl4 zo#)GYglphcz^2K!d3ApySmL z0@RAiSzbaCK}gyL_4UwH2g*8VP^NqMo+lBjm|pNubS-F8RX9-|N=~QlN}>7-{rp9g zU2+<+GGqe)m^XPp(=ELPhvM_`Km6ZOq|#`*9K8cuNyK4={E0*t%;b>0-Dkd>VK~kHxZ#ri%UpFqlE~gVCP?SiGJ6-$2cR pa(x|O^=}6boe> Date: Sun, 11 May 2025 22:47:19 +0200 Subject: [PATCH 65/85] delete-stray-images.php: handle missing files --- tools/delete-stray-images.php | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/tools/delete-stray-images.php b/tools/delete-stray-images.php index 36962730..baf955f8 100755 --- a/tools/delete-stray-images.php +++ b/tools/delete-stray-images.php @@ -44,20 +44,26 @@ foreach ($boards as $board) { ); foreach ($stray_src as $src) { - $stats['deleted']++; - $stats['size'] += filesize($board['uri'] . "/" . $config['dir']['img'] . $src); - if (!file_unlink($board['uri'] . "/" . $config['dir']['img'] . $src)) { - $er = error_get_last(); - die("error: " . $er['message'] . "\n"); + $p = $board['uri'] . "/" . $config['dir']['img'] . $src; + if (file_exists($p)) { + $stats['deleted']++; + $stats['size'] += filesize($p); + if (!file_unlink($p)) { + $er = error_get_last(); + die("error: " . $er['message'] . "\n"); + } } } foreach ($stray_thumb as $thumb) { - $stats['deleted']++; - $stats['size'] += filesize($board['uri'] . "/" . $config['dir']['thumb'] . $thumb); - if (!file_unlink($board['uri'] . "/" . $config['dir']['thumb'] . $thumb)) { - $er = error_get_last(); - die("error: " . $er['message'] . "\n"); + $p = $board['uri'] . "/" . $config['dir']['thumb'] . $thumb; + if (file_exists($p)) { + $stats['deleted']++; + $stats['size'] += filesize($p); + if (!file_unlink($p)) { + $er = error_get_last(); + die("error: " . $er['message'] . "\n"); + } } } From e5145cd98c3cb8444fdeee057befc4aacaf90783 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Mon, 12 May 2025 22:41:10 +0200 Subject: [PATCH 66/85] banners: add thread on leftypol banner --- static/banners/thread-on-leftypol.jpg | Bin 0 -> 10639 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 static/banners/thread-on-leftypol.jpg diff --git a/static/banners/thread-on-leftypol.jpg b/static/banners/thread-on-leftypol.jpg new file mode 100644 index 0000000000000000000000000000000000000000..df5aadb67c48a6b06bee18bbb3a4801721cfd141 GIT binary patch literal 10639 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 Date: Mon, 12 May 2025 22:41:31 +0200 Subject: [PATCH 67/85] banners: add closeted dengoid flag --- static/banners/closeted-dengoid.jpg | Bin 0 -> 11551 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 static/banners/closeted-dengoid.jpg diff --git a/static/banners/closeted-dengoid.jpg b/static/banners/closeted-dengoid.jpg new file mode 100644 index 0000000000000000000000000000000000000000..91b0f0fcd84a8287dd8a999725d127f2cceddac2 GIT binary patch literal 11551 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 GSN<2nV)wNG literal 0 HcmV?d00001 From 126314846c782edd55a00b2fa43a067aacd5baeb Mon Sep 17 00:00:00 2001 From: Zankaria Date: Tue, 13 May 2025 23:11:55 +0200 Subject: [PATCH 68/85] banners: add new banner --- static/banners/1734708324675.jpg | Bin 0 -> 6554 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 static/banners/1734708324675.jpg diff --git a/static/banners/1734708324675.jpg b/static/banners/1734708324675.jpg new file mode 100644 index 0000000000000000000000000000000000000000..bcb43222b30cd608dede20057d6bb6dd3bfab366 GIT binary patch literal 6554 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 Date: Thu, 29 May 2025 23:53:29 +0200 Subject: [PATCH 69/85] CacheDriverTrait.php: add CacheDriverTrait to share code --- inc/Data/Driver/CacheDriverTrait.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 inc/Data/Driver/CacheDriverTrait.php diff --git a/inc/Data/Driver/CacheDriverTrait.php b/inc/Data/Driver/CacheDriverTrait.php new file mode 100644 index 00000000..16e56ce0 --- /dev/null +++ b/inc/Data/Driver/CacheDriverTrait.php @@ -0,0 +1,20 @@ + Date: Thu, 29 May 2025 23:54:53 +0200 Subject: [PATCH 70/85] RedisCacheDriver.php: use CacheDriverTrait --- inc/Data/Driver/RedisCacheDriver.php | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/inc/Data/Driver/RedisCacheDriver.php b/inc/Data/Driver/RedisCacheDriver.php index 389fff32..c71c8779 100644 --- a/inc/Data/Driver/RedisCacheDriver.php +++ b/inc/Data/Driver/RedisCacheDriver.php @@ -5,18 +5,17 @@ defined('TINYBOARD') or exit; class RedisCacheDriver implements CacheDriver { + use CacheDriverTrait; + private string $prefix; private \Redis $inner; public function __construct(string $prefix, string $host, ?int $port, ?string $password, int $database) { $this->inner = new \Redis(); - if (str_starts_with($host, 'unix:') || str_starts_with($host, ':')) { - $ret = \explode(':', $host); - if (count($ret) < 2) { - throw new \RuntimeException("Invalid unix socket path $host"); - } - // Unix socket. - $this->inner->connect($ret[1]); + $maybe_unix = self::asUnixSocketPath($host); + + if ($maybe_unix !== null) { + $this->inner->connect($maybe_unix); } elseif ($port === null) { $this->inner->connect($host); } else { From 37e771f0bec9d5cd0be80969e2fe84e31e75d2a3 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Thu, 29 May 2025 23:55:04 +0200 Subject: [PATCH 71/85] MemcacheCacheDriver.php: fix configuration --- inc/Data/Driver/MemcacheCacheDriver.php | 34 ++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/inc/Data/Driver/MemcacheCacheDriver.php b/inc/Data/Driver/MemcacheCacheDriver.php index 04f62895..f05c51e7 100644 --- a/inc/Data/Driver/MemcacheCacheDriver.php +++ b/inc/Data/Driver/MemcacheCacheDriver.php @@ -5,9 +5,12 @@ defined('TINYBOARD') or exit; class MemcachedCacheDriver implements CacheDriver { + use CacheDriverTrait; + private \Memcached $inner; - public function __construct(string $prefix, string $memcached_server) { + + public function __construct(string $prefix, string $server_uri, int $server_port, int $server_weight) { $this->inner = new \Memcached(); if (!$this->inner->setOption(\Memcached::OPT_BINARY_PROTOCOL, true)) { throw new \RuntimeException('Unable to set the memcached protocol!'); @@ -15,8 +18,33 @@ class MemcachedCacheDriver implements CacheDriver { 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!'); + + $maybe_unix_path = self::asUnixSocketPath($server_uri); + $is_unix = $maybe_unix_path !== null; + if ($is_unix) { + $server_uri = $maybe_unix_path; + } + + // Memcached keeps the server connections open across requests. + $current_servers = $this->inner->getServerList(); + $found_in_curr = false; + foreach ($current_servers as $curr) { + // Ignore the port if the server is connected with a unix socket. + if ($curr['host'] === $server_uri && ($is_unix || $curr['port'] === $server_port)) { + $found_in_curr = true; + } + } + + if (!$found_in_curr) { + if (!empty($current_servers)) { + if ($this->inner->resetServerList()) { + throw new \RuntimeException('Unable to reset the memcached server list!'); + } + + if ($this->inner->addServer($server_uri, $server_port, $server_weight)) { + throw new \RuntimeException('Unable to add memcached servers!'); + } + } } } From de9d118390178fdb64d0ee8880f1f2087844f60e Mon Sep 17 00:00:00 2001 From: Zankaria Date: Thu, 29 May 2025 23:59:20 +0200 Subject: [PATCH 72/85] cache.php: update MemcachedCacheDriver initialization --- inc/cache.php | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/inc/cache.php b/inc/cache.php index d26f9201..182d6fda 100644 --- a/inc/cache.php +++ b/inc/cache.php @@ -15,10 +15,18 @@ class Cache { switch ($config['cache']['enabled']) { case 'memcached': - return new MemcachedCacheDriver( - $config['cache']['prefix'], - $config['cache']['memcached'] - ); + $prefix = $config['cache']['prefix']; + $uri = $config['cache']['memcached'][0]; + $port = 0; + $weight = 0; + if (isset($config['cache']['memcached'][1]) && $config['cache']['memcached'][1] !== null) { + $port = \intval($config['cache']['memcached'][1]); + } + if (isset($config['cache']['memcached'][2]) && $config['cache']['memcached'][2] !== null) { + $weight = \intval($config['cache']['memcached'][2]); + } + + return new MemcachedCacheDriver($prefix, $uri, $port, $weight); case 'redis': $port = $config['cache']['redis'][1]; $port = empty($port) ? null : intval($port); From 2b30929bc98ccc02b07f773b770b0fd0ce289dcb Mon Sep 17 00:00:00 2001 From: Zankaria Date: Fri, 30 May 2025 00:22:42 +0200 Subject: [PATCH 73/85] MemcacheCacheDriver.php: fix init again --- inc/Data/Driver/MemcacheCacheDriver.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/inc/Data/Driver/MemcacheCacheDriver.php b/inc/Data/Driver/MemcacheCacheDriver.php index f05c51e7..cba0f1a6 100644 --- a/inc/Data/Driver/MemcacheCacheDriver.php +++ b/inc/Data/Driver/MemcacheCacheDriver.php @@ -40,10 +40,9 @@ class MemcachedCacheDriver implements CacheDriver { if ($this->inner->resetServerList()) { throw new \RuntimeException('Unable to reset the memcached server list!'); } - - if ($this->inner->addServer($server_uri, $server_port, $server_weight)) { - throw new \RuntimeException('Unable to add memcached servers!'); - } + } + if ($this->inner->addServer($server_uri, $server_port, $server_weight)) { + throw new \RuntimeException('Unable to add memcached servers!'); } } } From 7aae8be1ae258fc2f0026f6c17a3ab78bf91e7ca Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sat, 31 May 2025 18:08:54 +0200 Subject: [PATCH 74/85] MemcacheCacheDriver.php: add detailed errors --- inc/Data/Driver/MemcacheCacheDriver.php | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/inc/Data/Driver/MemcacheCacheDriver.php b/inc/Data/Driver/MemcacheCacheDriver.php index cba0f1a6..729f5a50 100644 --- a/inc/Data/Driver/MemcacheCacheDriver.php +++ b/inc/Data/Driver/MemcacheCacheDriver.php @@ -13,10 +13,12 @@ class MemcachedCacheDriver implements CacheDriver { public function __construct(string $prefix, string $server_uri, int $server_port, int $server_weight) { $this->inner = new \Memcached(); if (!$this->inner->setOption(\Memcached::OPT_BINARY_PROTOCOL, true)) { - throw new \RuntimeException('Unable to set the memcached protocol!'); + $err = $this->inner->getResultMessage(); + throw new \RuntimeException("Unable to set the memcached protocol: '$err'"); } if (!$this->inner->setOption(\Memcached::OPT_PREFIX_KEY, $prefix)) { - throw new \RuntimeException('Unable to set the memcached prefix!'); + $err = $this->inner->getResultMessage(); + throw new \RuntimeException("Unable to set the memcached prefix: '$err'"); } $maybe_unix_path = self::asUnixSocketPath($server_uri); @@ -38,11 +40,13 @@ class MemcachedCacheDriver implements CacheDriver { if (!$found_in_curr) { if (!empty($current_servers)) { if ($this->inner->resetServerList()) { - throw new \RuntimeException('Unable to reset the memcached server list!'); + $err = $this->inner->getResultMessage(); + throw new \RuntimeException("Unable to reset the memcached server list: '$err'"); } } if ($this->inner->addServer($server_uri, $server_port, $server_weight)) { - throw new \RuntimeException('Unable to add memcached servers!'); + $err = $this->inner->getResultMessage(); + throw new \RuntimeException("Unable to add memcached servers: '$err'"); } } } From 3c9c86901ae428264e10e03eb2f3ae97a3b157f8 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sat, 31 May 2025 18:10:51 +0200 Subject: [PATCH 75/85] MemcacheCacheDriver.php: fix error check --- inc/Data/Driver/MemcacheCacheDriver.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inc/Data/Driver/MemcacheCacheDriver.php b/inc/Data/Driver/MemcacheCacheDriver.php index 729f5a50..22330108 100644 --- a/inc/Data/Driver/MemcacheCacheDriver.php +++ b/inc/Data/Driver/MemcacheCacheDriver.php @@ -39,12 +39,12 @@ class MemcachedCacheDriver implements CacheDriver { if (!$found_in_curr) { if (!empty($current_servers)) { - if ($this->inner->resetServerList()) { + if (!$this->inner->resetServerList()) { $err = $this->inner->getResultMessage(); throw new \RuntimeException("Unable to reset the memcached server list: '$err'"); } } - if ($this->inner->addServer($server_uri, $server_port, $server_weight)) { + if (!$this->inner->addServer($server_uri, $server_port, $server_weight)) { $err = $this->inner->getResultMessage(); throw new \RuntimeException("Unable to add memcached servers: '$err'"); } From e76c4eeed40f5dfc2c6e7d208f57b7c771a0135d Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sat, 31 May 2025 23:04:41 +0200 Subject: [PATCH 76/85] static: add the logo (somehow it wasn't in the repo!) --- static/leftypol_logo.png | Bin 0 -> 35775 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 static/leftypol_logo.png diff --git a/static/leftypol_logo.png b/static/leftypol_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..685a16e6fe2c63783646a126e8bb137933d3e2a0 GIT binary patch literal 35775 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 Date: Sun, 1 Jun 2025 16:05:15 +0200 Subject: [PATCH 77/85] database.php: force utf8mb4 for PDO --- inc/database.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inc/database.php b/inc/database.php index f5ea73bd..4b0bb6c3 100644 --- a/inc/database.php +++ b/inc/database.php @@ -66,7 +66,7 @@ function sql_open() { $dsn = $config['db']['type'] . ':' . ($unix_socket ? 'unix_socket=' . $unix_socket : 'host=' . $config['db']['server']) . - ';dbname=' . $config['db']['database']; + ';charset=utf8mb4;dbname=' . $config['db']['database']; if (!empty($config['db']['dsn'])) $dsn .= ';' . $config['db']['dsn']; try { @@ -85,7 +85,7 @@ function sql_open() { if ($config['debug']) { $debug['time']['db_connect'] = '~' . round((microtime(true) - $start) * 1000, 2) . 'ms'; if ($config['db']['type'] == "mysql") { - query('SET NAMES utf8') or error(db_error()); + query('SET NAMES utf8mb4') or error(db_error()); } } return $pdo; From 46c5f17db8422cad889c80738bb22684ee002772 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sun, 1 Jun 2025 23:08:19 +0200 Subject: [PATCH 78/85] dark_red.css: change catalog hover color --- stylesheets/dark_red.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/stylesheets/dark_red.css b/stylesheets/dark_red.css index 4ffe4eeb..71ca26e0 100644 --- a/stylesheets/dark_red.css +++ b/stylesheets/dark_red.css @@ -194,6 +194,10 @@ div.boardlist:not(.bottom) { div.report { color: #666666; } +.theme-catalog div.thread:hover { + background: #4f4f4f; + border-color: transparent; +} #options_div, #alert_div { background: #333333; } From f58010012177dda7232aa605664dad2677416b84 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sun, 1 Jun 2025 23:10:39 +0200 Subject: [PATCH 79/85] dark.css: change catalog hover color --- stylesheets/dark.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/stylesheets/dark.css b/stylesheets/dark.css index 9f3df691..c6d338f2 100644 --- a/stylesheets/dark.css +++ b/stylesheets/dark.css @@ -203,6 +203,10 @@ div.boardlist:not(.bottom) { div.report { color: #666666; } +theme-catalog div.thread:hover { + background: #555; + border-color: transparent; +} /* options.js */ #options_div, #alert_div { From 34bf9b2261b2f4b3472ec43dd91d01cd8b9b25ef Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sun, 1 Jun 2025 23:12:38 +0200 Subject: [PATCH 80/85] dark_spook.css: change catalog hover color --- stylesheets/dark_spook.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/stylesheets/dark_spook.css b/stylesheets/dark_spook.css index 2ca55a20..e667cc01 100644 --- a/stylesheets/dark_spook.css +++ b/stylesheets/dark_spook.css @@ -209,6 +209,11 @@ div.boardlist:not(.bottom) { background-color: #1E1E1E; } +.theme-catalog div.thread:hover { + background: #555; + border-color: transparent; +} + div.report { color: #666666; } From d4bc625c053c9cbf2e5e28029792d4635e25dd1a Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sun, 1 Jun 2025 23:16:29 +0200 Subject: [PATCH 81/85] szalet.css: change catalog hover color --- stylesheets/szalet.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/stylesheets/szalet.css b/stylesheets/szalet.css index a766416e..6feb4939 100644 --- a/stylesheets/szalet.css +++ b/stylesheets/szalet.css @@ -206,3 +206,8 @@ div.pages { border: 0; background: none !important; } + +.theme-catalog div.thread:hover { + background: #583E28; + border-color: transparent; +} From ee84baf87dcc4b7c8868b19c2985eada89aac279 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sun, 1 Jun 2025 23:19:34 +0200 Subject: [PATCH 82/85] tsuki.css: remove duplicated css --- stylesheets/tsuki.css | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/stylesheets/tsuki.css b/stylesheets/tsuki.css index 523bdb71..17547509 100644 --- a/stylesheets/tsuki.css +++ b/stylesheets/tsuki.css @@ -303,10 +303,6 @@ div.post.reply div.body { padding-bottom: 0.3em; } -div.post.reply.highlighted { - background: #D6BAD0; -} - div.post.reply div.body a { color: #D00; } @@ -322,6 +318,8 @@ div.post div.body { div.post.reply { background: #D6DAF0; + border: #555555 1px solid; + box-shadow: 4px 4px #555; margin: 0.2em 4px; padding: 0.2em 0.3em 0.5em 0.6em; border-width: 1px; @@ -1359,18 +1357,8 @@ a.post_no:hover { color: #32DD72 !important; text-decoration: underline overline; } -div.post.reply { - background: #111; - border: #555555 1px solid; - box-shadow: 4px 4px #555; - - @media (max-width: 48em) { - border-left-style: none; - border-right-style: none; - } -} div.post.reply.highlighted { - background: #555; + background: #D6BAD0; border: transparent 1px solid; @media (max-width: 48em) { From ca25c85984745448156fef6de13119b4b6093e48 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sun, 1 Jun 2025 23:21:13 +0200 Subject: [PATCH 83/85] tsuki.css: change hover color --- stylesheets/tsuki.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stylesheets/tsuki.css b/stylesheets/tsuki.css index 17547509..9995889d 100644 --- a/stylesheets/tsuki.css +++ b/stylesheets/tsuki.css @@ -690,8 +690,8 @@ pre { } .theme-catalog div.thread:hover { - background: #D6DAF0; - border-color: #B7C5D9; + background: #927C8E; + border-color: transparent; } .theme-catalog div.grid-size-vsmall img { @@ -1358,7 +1358,7 @@ a.post_no:hover { text-decoration: underline overline; } div.post.reply.highlighted { - background: #D6BAD0; + background: #927C8E; border: transparent 1px solid; @media (max-width: 48em) { From 5bc1009dfb8bec47ff51c8c88f71d3f7474a8b41 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Sun, 1 Jun 2025 23:38:30 +0200 Subject: [PATCH 84/85] faq: remove broken link to readlist thread --- templates/themes/faq/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/themes/faq/index.html b/templates/themes/faq/index.html index b63a4483..6877edd4 100644 --- a/templates/themes/faq/index.html +++ b/templates/themes/faq/index.html @@ -33,7 +33,7 @@

I'm new here and learned politics from memes would like to learn about leftism! Where should I start ?

-

There are some beginner lists in the reading thread. Meanwhile, consider asking your questions in /edu/ or in a relevant /leftypol/ thread, such as QTDDTOT.

+

Consider asking your questions in /edu/ or in a relevant /leftypol/ thread, such as QTDDTOT.

What is the purpose of leftypol.org ?

From 9b1f4debadb4d752f2a6ff5e7920f82cb54c6f4c Mon Sep 17 00:00:00 2001 From: Zankaria Date: Mon, 2 Jun 2025 00:51:17 +0200 Subject: [PATCH 85/85] README.md: trim --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e5f8ead0..e0f985fe 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Requirements PHP 8.0 is explicitly supported. PHP 7.x should be compatable. 2. MySQL/MariaDB server >= 5.5.3 3. [Composer](https://getcomposer.org/) (To install various packages) -4. [mbstring](http://www.php.net/manual/en/mbstring.installation.php) +4. [mbstring](http://www.php.net/manual/en/mbstring.installation.php) 5. [PHP GD](http://www.php.net/manual/en/intro.image.php) 6. [PHP PDO](http://www.php.net/manual/en/intro.pdo.php) @@ -44,7 +44,7 @@ Installation development version with: git clone git://git.leftypol.org/leftypol/leftypol.git - + 2. run ```composer install``` inside the directory 3. Navigate to ```install.php``` in your web browser and follow the prompts. @@ -80,7 +80,7 @@ find support from a variety of sources: * For support, reply to the sticky on our [/tech/](https://leftypol.org/tech/) board. ### Tinyboard support -vichan, and by extension lainchan and leftypol, is based on a Tinyboard, so both engines have very much in common. These links may be helpful for you as well: +vichan, and by extension lainchan and leftypol, is based on a Tinyboard, so both engines have very much in common. These links may be helpful for you as well: * Tinyboard documentation can be found [here](https://web.archive.org/web/20121016074303/http://tinyboard.org/docs/?p=Main_Page).
' . + sprintf(ngettext('%d result in', '%d results in', $query->rowCount()), + $query->rowCount()) . ' ' . + sprintf($config['board_abbreviation'], $board['uri']) . ' - ' . $board['title'] . + '' . $temp . '