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..2c9f14a0 --- /dev/null +++ b/inc/Data/Driver/FileLogDriver.php @@ -0,0 +1,61 @@ +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); + \fflush($this->fd); + \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); + } + } + } +} diff --git a/inc/config.php b/inc/config.php index def2cd27..71b0fbf4 100644 --- a/inc/config.php +++ b/inc/config.php @@ -63,9 +63,29 @@ // 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", 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. $config['dns_system'] = false; @@ -923,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. @@ -965,15 +981,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/inc/context.php b/inc/context.php index 3207d7ea..11a153ec 100644 --- a/inc/context.php +++ b/inc/context.php @@ -1,8 +1,8 @@ $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']; + + $legacy_syslog = isset($config['syslog']) && $config['syslog']; + + // Check 'syslog' for backwards compatibility. + 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') { + return new StderrLogDriver($name, $level); + } 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) { // Use the global for backwards compatibility. return \cache::getCache(); 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 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}') ); } diff --git a/post.php b/post.php index 6e7c6ab6..5483600d 100644 --- a/post.php +++ b/post.php @@ -5,6 +5,7 @@ use Vichan\Context; use Vichan\Data\ReportQueries; +use Vichan\Data\Driver\LogDriver; require_once 'inc/bootstrap.php'; @@ -354,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']) { @@ -433,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 = ''; @@ -610,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 : '') ); @@ -699,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']); } @@ -716,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); @@ -1404,13 +1402,13 @@ function handle_post(Context $ctx) $file['thumbwidth'] = $size[0]; $file['thumbheight'] = $size[1]; } elseif ( - $config['minimum_copy_resize'] && + (($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']) ) { // 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; @@ -1553,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'])) { @@ -1629,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']) { @@ -1783,10 +1747,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 . '"'); @@ -1879,17 +1843,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'])) { diff --git a/static/banners/just-monika-opt.webp b/static/banners/just-monika-opt.webp deleted file mode 100644 index 731f985a..00000000 Binary files a/static/banners/just-monika-opt.webp and /dev/null differ diff --git a/stylesheets/style.css b/stylesheets/style.css index 815e1853..4a090f33 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 10px 0 0; +} + .file.multifile > p { width: 0px; min-width: 100%; diff --git a/templates/themes/categories/frames.html b/templates/themes/categories/frames.html index 6b2fac38..1c4673cc 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; @@ -96,17 +96,18 @@ grid-column: 1; grid-row: 3; width: 100%; + word-break: break-all; } - + .modlog { width: 100%; text-align: center; } - + table { table-layout: fixed; } - + table.modlog tr th { white-space: normal; word-wrap: break-word; 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 }} diff --git a/tmp/tesseract/.gitkeep b/tmp/tesseract/.gitkeep deleted file mode 100644 index e69de29b..00000000