forked from leftypol/leftypol
Compare commits
56 commits
Author | SHA1 | Date | |
---|---|---|---|
92fc2daa9c | |||
d224c0af23 | |||
c1c20bdab2 | |||
42e850091a | |||
87029580b6 | |||
cf74272806 | |||
336c40b0f7 | |||
81c02be563 | |||
fa56876c36 | |||
8da10af101 | |||
9431536112 | |||
025f1c4221 | |||
501e696891 | |||
557e43e38f | |||
6ee8670401 | |||
c4d7bc39de | |||
b2029d2533 | |||
5b4d1b7f4c | |||
665e3d339a | |||
6be3f4bbff | |||
8f7db3bdef | |||
cca8d88d91 | |||
![]() |
268bd84128 | ||
2c0c003b2c | |||
fe4813867b | |||
6132084b4b | |||
79523f8251 | |||
707fb62c04 | |||
6f9ea52212 | |||
9ea7bf5938 | |||
c45fbd5ec9 | |||
77ae0b101e | |||
94b5c82517 | |||
e753588aeb | |||
c3a1960427 | |||
8754eda6c7 | |||
b61653230d | |||
041958e20f | |||
218dc42aca | |||
5d52f6b32a | |||
583b50af56 | |||
e5ba5feb25 | |||
9256c15c46 | |||
0819c509fa | |||
9a4ce56d86 | |||
230dc4760b | |||
c73a893e54 | |||
2da6c95aa5 | |||
9a5b79452f | |||
![]() |
bf060ce174 | ||
b1989a69b0 | |||
9746293fed | |||
3a44b68c41 | |||
aed49a6188 | |||
e3252306a5 | |||
9bb7f0d2f2 |
29 changed files with 536 additions and 241 deletions
28
inc/Data/Driver/ErrorLogLogDriver.php
Normal file
28
inc/Data/Driver/ErrorLogLogDriver.php
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
namespace Vichan\Data\Driver;
|
||||
|
||||
defined('TINYBOARD') or exit;
|
||||
|
||||
|
||||
/**
|
||||
* Log via the php function error_log.
|
||||
*/
|
||||
class ErrorLogLogDriver implements LogDriver {
|
||||
use LogTrait;
|
||||
|
||||
private string $name;
|
||||
private int $level;
|
||||
|
||||
public function __construct(string $name, int $level) {
|
||||
$this->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);
|
||||
}
|
||||
}
|
||||
}
|
61
inc/Data/Driver/FileLogDriver.php
Normal file
61
inc/Data/Driver/FileLogDriver.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
namespace Vichan\Data\Driver;
|
||||
|
||||
defined('TINYBOARD') or exit;
|
||||
|
||||
|
||||
/**
|
||||
* Log to a file.
|
||||
*/
|
||||
class FileLogDriver implements LogDriver {
|
||||
use LogTrait;
|
||||
|
||||
private string $name;
|
||||
private int $level;
|
||||
private mixed $fd;
|
||||
|
||||
public function __construct(string $name, int $level, string $file_path) {
|
||||
/*
|
||||
* error_log is slow as hell in it's 3rd mode, so use fopen + file locking instead.
|
||||
* https://grobmeier.solutions/performance-ofnonblocking-write-to-files-via-php-21082009.html
|
||||
*
|
||||
* Whatever file appending is atomic is contentious:
|
||||
* - There are no POSIX guarantees: https://stackoverflow.com/a/7237901
|
||||
* - But linus suggested they are on linux, on some filesystems: https://web.archive.org/web/20151201111541/http://article.gmane.org/gmane.linux.kernel/43445
|
||||
* - But it doesn't seem to be always the case: https://www.notthewizard.com/2014/06/17/are-files-appends-really-atomic/
|
||||
*
|
||||
* So we just use file locking to be sure.
|
||||
*/
|
||||
|
||||
$this->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);
|
||||
}
|
||||
}
|
22
inc/Data/Driver/LogDriver.php
Normal file
22
inc/Data/Driver/LogDriver.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
namespace Vichan\Data\Driver;
|
||||
|
||||
defined('TINYBOARD') or exit;
|
||||
|
||||
|
||||
interface LogDriver {
|
||||
public const EMERG = \LOG_EMERG;
|
||||
public const ERROR = \LOG_ERR;
|
||||
public const WARNING = \LOG_WARNING;
|
||||
public const NOTICE = \LOG_NOTICE;
|
||||
public const INFO = \LOG_INFO;
|
||||
public const DEBUG = \LOG_DEBUG;
|
||||
|
||||
/**
|
||||
* Log a message if the level of relevancy is at least the minimum.
|
||||
*
|
||||
* @param int $level Message level. Use Log interface constants.
|
||||
* @param string $message The message to log.
|
||||
*/
|
||||
public function log(int $level, string $message): void;
|
||||
}
|
26
inc/Data/Driver/LogTrait.php
Normal file
26
inc/Data/Driver/LogTrait.php
Normal file
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
namespace Vichan\Data\Driver;
|
||||
|
||||
defined('TINYBOARD') or exit;
|
||||
|
||||
|
||||
trait LogTrait {
|
||||
public static function levelToString(int $level): string {
|
||||
switch ($level) {
|
||||
case LogDriver::EMERG:
|
||||
return 'EMERG';
|
||||
case LogDriver::ERROR:
|
||||
return 'ERROR';
|
||||
case LogDriver::WARNING:
|
||||
return 'WARNING';
|
||||
case LogDriver::NOTICE:
|
||||
return 'NOTICE';
|
||||
case LogDriver::INFO:
|
||||
return 'INFO';
|
||||
case LogDriver::DEBUG:
|
||||
return 'DEBUG';
|
||||
default:
|
||||
throw new \InvalidArgumentException('Not a logging level');
|
||||
}
|
||||
}
|
||||
}
|
27
inc/Data/Driver/StderrLogDriver.php
Normal file
27
inc/Data/Driver/StderrLogDriver.php
Normal file
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
namespace Vichan\Data\Driver;
|
||||
|
||||
defined('TINYBOARD') or exit;
|
||||
|
||||
|
||||
/**
|
||||
* Log to php's standard error file stream.
|
||||
*/
|
||||
class StderrLogDriver implements LogDriver {
|
||||
use LogTrait;
|
||||
|
||||
private string $name;
|
||||
private int $level;
|
||||
|
||||
public function __construct(string $name, int $level) {
|
||||
$this->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");
|
||||
}
|
||||
}
|
||||
}
|
35
inc/Data/Driver/SyslogLogDriver.php
Normal file
35
inc/Data/Driver/SyslogLogDriver.php
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
namespace Vichan\Data\Driver;
|
||||
|
||||
defined('TINYBOARD') or exit;
|
||||
|
||||
/**
|
||||
* Log to syslog.
|
||||
*/
|
||||
class SyslogLogDriver implements LogDriver {
|
||||
private int $level;
|
||||
|
||||
public function __construct(string $name, int $level, bool $print_stderr) {
|
||||
$flags = \LOG_ODELAY;
|
||||
if ($print_stderr) {
|
||||
$flags |= \LOG_PERROR;
|
||||
}
|
||||
|
||||
if (!\openlog($name, $flags, \LOG_USER)) {
|
||||
throw new \RuntimeException('Unable to open syslog');
|
||||
}
|
||||
|
||||
$this->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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
76
inc/Data/IpNoteQueries.php
Normal file
76
inc/Data/IpNoteQueries.php
Normal file
|
@ -0,0 +1,76 @@
|
|||
<?php
|
||||
namespace Vichan\Data;
|
||||
|
||||
use Vichan\Data\Driver\CacheDriver;
|
||||
|
||||
|
||||
class IpNoteQueries {
|
||||
private \PDO $pdo;
|
||||
private CacheDriver $cache;
|
||||
|
||||
|
||||
public function __construct(\PDO $pdo, CacheDriver $cache) {
|
||||
$this->pdo = $pdo;
|
||||
$this->cache = $cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the notes relative to an IP.
|
||||
*
|
||||
* @param string $ip The IP of the notes. THE STRING IS NOT VALIDATED.
|
||||
* @return array Returns an array of notes sorted by the most recent. Includes the username of the mods.
|
||||
*/
|
||||
public function getByIp(string $ip) {
|
||||
$ret = $this->cache->get("ip_note_queries_$ip");
|
||||
if ($ret !== null) {
|
||||
return $ret;
|
||||
}
|
||||
|
||||
$query = $this->pdo->prepare('SELECT `ip_notes`.*, `username` FROM `ip_notes` LEFT JOIN `mods` ON `mod` = `mods`.`id` WHERE `ip` = :ip ORDER BY `time` DESC');
|
||||
$query->bindValue(':ip', $ip);
|
||||
$query->execute();
|
||||
$ret = $query->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
$this->cache->set("ip_note_queries_$ip", $ret);
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new note relative to the given ip.
|
||||
*
|
||||
* @param string $ip The IP of the note. THE STRING IS NOT VALIDATED.
|
||||
* @param int $mod_id The id of the mod who created the note.
|
||||
* @param string $body The text of the note.
|
||||
* @return void
|
||||
*/
|
||||
public function add(string $ip, int $mod_id, string $body) {
|
||||
$query = $this->pdo->prepare('INSERT INTO `ip_notes` (`ip`, `mod`, `time`, `body`) VALUES (:ip, :mod, :time, :body)');
|
||||
$query->bindValue(':ip', $ip);
|
||||
$query->bindValue(':mod', $mod_id);
|
||||
$query->bindValue(':time', time());
|
||||
$query->bindValue(':body', $body);
|
||||
$query->execute();
|
||||
|
||||
$this->cache->delete("ip_note_queries_$ip");
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a note only if it's of a particular IP address.
|
||||
*
|
||||
* @param int $id The id of the note.
|
||||
* @param int $ip The expected IP of the note. THE STRING IS NOT VALIDATED.
|
||||
* @return bool True if any note was deleted.
|
||||
*/
|
||||
public function deleteWhereIp(int $id, string $ip): bool {
|
||||
$query = $this->pdo->prepare('DELETE FROM `ip_notes` WHERE `ip` = :ip AND `id` = :id');
|
||||
$query->bindValue(':ip', $ip);
|
||||
$query->bindValue(':id', $id);
|
||||
$query->execute();
|
||||
$any = $query->rowCount() != 0;
|
||||
|
||||
if ($any) {
|
||||
$this->cache->delete("ip_note_queries_$ip");
|
||||
}
|
||||
return $any;
|
||||
}
|
||||
}
|
|
@ -44,19 +44,20 @@ class UserPostQueries {
|
|||
|
||||
$posts_count = \count($posts);
|
||||
|
||||
// By fetching one extra post bellow and/or above the limit, we know if there are any posts beside the current page.
|
||||
if ($posts_count === $page_size + 2) {
|
||||
$has_extra_prev_post = true;
|
||||
$has_extra_end_post = true;
|
||||
} elseif ($posts_count === $page_size + 1) {
|
||||
$has_extra_prev_post = $start_id !== null && $start_id === (int)$posts[0]['id'];
|
||||
$has_extra_end_post = !$has_extra_prev_post;
|
||||
} else {
|
||||
$has_extra_prev_post = false;
|
||||
$has_extra_end_post = false;
|
||||
/*
|
||||
* If the id we start fetching from is also the first id fetched from the DB, then we exclude it from
|
||||
* the results, noting that we fetched 1 more posts than we needed, and it was before the current page.
|
||||
* Hence, we have no extra post at the end and no next page.
|
||||
*/
|
||||
$has_extra_prev_post = $start_id !== null && $start_id === (int)$posts[0]['id'];
|
||||
$has_extra_end_post = !$has_extra_prev_post && $posts_count > $page_size;
|
||||
}
|
||||
|
||||
// Since we fetched one post bellow and/or above the limit, we always know if there are any posts after the current page.
|
||||
|
||||
// Get the previous cursor, if any.
|
||||
if ($has_extra_prev_post) {
|
||||
\array_shift($posts);
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
namespace Vichan;
|
||||
|
||||
use Vichan\Data\Driver\CacheDriver;
|
||||
use Vichan\Data\{ReportQueries, UserPostQueries};
|
||||
use Vichan\Data\{IpNoteQueries, ReportQueries, UserPostQueries};
|
||||
use Vichan\Data\Driver\{CacheDriver, ErrorLogLogDriver, FileLogDriver, LogDriver, StderrLogDriver, SyslogLogDriver};
|
||||
|
||||
defined('TINYBOARD') or exit;
|
||||
|
||||
|
@ -31,6 +31,34 @@ 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'];
|
||||
|
||||
$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();
|
||||
|
@ -48,6 +76,7 @@ function build_context(array $config): Context {
|
|||
},
|
||||
UserPostQueries::class => function($c) {
|
||||
return new UserPostQueries($c->get(\PDO::class));
|
||||
}
|
||||
},
|
||||
IpNoteQueries::class => fn($c) => new IpNoteQueries($c->get(\PDO::class), $c->get(CacheDriver::class)),
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
* Copyright (c) 2010-2013 Tinyboard Development Group
|
||||
*/
|
||||
|
||||
use Vichan\Context;
|
||||
use Vichan\Data\IpNoteQueries;
|
||||
|
||||
defined('TINYBOARD') or exit;
|
||||
|
||||
class Filter {
|
||||
|
@ -136,17 +139,13 @@ class Filter {
|
|||
}
|
||||
}
|
||||
|
||||
public function action() {
|
||||
public function action(Context $ctx) {
|
||||
global $board;
|
||||
|
||||
$this->add_note = isset($this->add_note) ? $this->add_note : false;
|
||||
if ($this->add_note) {
|
||||
$query = prepare('INSERT INTO ``ip_notes`` VALUES (NULL, :ip, :mod, :time, :body)');
|
||||
$query->bindValue(':ip', $_SERVER['REMOTE_ADDR']);
|
||||
$query->bindValue(':mod', -1);
|
||||
$query->bindValue(':time', time());
|
||||
$query->bindValue(':body', "Autoban message: ".$this->post['body']);
|
||||
$query->execute() or error(db_error($query));
|
||||
$note_queries = $ctx->get(IpNoteQueries::class);
|
||||
$note_queries->add($_SERVER['REMOTE_ADDR'], -1, 'Autoban message: ' . $this->post['body']);
|
||||
}
|
||||
if (isset ($this->action)) switch($this->action) {
|
||||
case 'reject':
|
||||
|
@ -214,7 +213,7 @@ function purge_flood_table() {
|
|||
query("DELETE FROM ``flood`` WHERE `time` < $time") or error(db_error());
|
||||
}
|
||||
|
||||
function do_filters(array $post) {
|
||||
function do_filters(Context $ctx, array $post) {
|
||||
global $config;
|
||||
|
||||
if (!isset($config['filters']) || empty($config['filters']))
|
||||
|
@ -237,7 +236,7 @@ function do_filters(array $post) {
|
|||
$filter = new Filter($filter_array);
|
||||
$filter->flood_check = $flood_check;
|
||||
if ($filter->check($post)) {
|
||||
$filter->action();
|
||||
$filter->action($ctx);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2069,7 +2069,7 @@ function remove_modifiers($body) {
|
|||
return preg_replace('@<tinyboard ([\w\s]+)>(.+?)</tinyboard>@usm', '', $body);
|
||||
}
|
||||
|
||||
function markup(&$body, $track_cites = false, $op = false) {
|
||||
function markup(&$body, $track_cites = false) {
|
||||
global $board, $config, $markup_urls;
|
||||
|
||||
$modifiers = extract_modifiers($body);
|
||||
|
@ -2168,12 +2168,15 @@ function markup(&$body, $track_cites = false, $op = false) {
|
|||
link_for(array('id' => $cite, 'thread' => $cited_posts[$cite])) . '#' . $cite . '">' .
|
||||
'>>' . $cite .
|
||||
'</a>';
|
||||
} else {
|
||||
$replacement = "<s>>>$cite</s>";
|
||||
}
|
||||
|
||||
$body = mb_substr_replace($body, $matches[1][0] . $replacement . $matches[3][0], $matches[0][1] + $skip_chars, mb_strlen($matches[0][0]));
|
||||
$skip_chars += mb_strlen($matches[1][0] . $replacement . $matches[3][0]) - mb_strlen($matches[0][0]);
|
||||
$body = mb_substr_replace($body, $matches[1][0] . $replacement . $matches[3][0], $matches[0][1] + $skip_chars, mb_strlen($matches[0][0]));
|
||||
$skip_chars += mb_strlen($matches[1][0] . $replacement . $matches[3][0]) - mb_strlen($matches[0][0]);
|
||||
|
||||
if ($track_cites && $config['track_cites'])
|
||||
$tracked_cites[] = array($board['uri'], $cite);
|
||||
if ($track_cites && $config['track_cites']) {
|
||||
$tracked_cites[] = array($board['uri'], $cite);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,17 +3,20 @@
|
|||
* Copyright (c) 2010-2013 Tinyboard Development Group
|
||||
*/
|
||||
use Vichan\Context;
|
||||
use Vichan\Data\{UserPostQueries, ReportQueries};
|
||||
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 {
|
||||
|
@ -851,18 +853,26 @@ function mod_view_thread50(Context $ctx, $boardName, $thread) {
|
|||
}
|
||||
|
||||
function mod_ip_remove_note(Context $ctx, $ip, $id) {
|
||||
global $config;
|
||||
$config = $ctx->get('config');
|
||||
|
||||
if (!hasPermission($config['mod']['remove_notes']))
|
||||
error($config['error']['noaccess']);
|
||||
if (!hasPermission($config['mod']['remove_notes'])) {
|
||||
error($config['error']['noaccess']);
|
||||
}
|
||||
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP) === false)
|
||||
if (filter_var($ip, \FILTER_VALIDATE_IP) === false) {
|
||||
error('Invalid IP address');
|
||||
}
|
||||
|
||||
$query = prepare('DELETE FROM ``ip_notes`` WHERE `ip` = :ip AND `id` = :id');
|
||||
$query->bindValue(':ip', $ip);
|
||||
$query->bindValue(':id', $id);
|
||||
$query->execute() or error(db_error($query));
|
||||
if (!is_numeric($id)) {
|
||||
error('Invalid note ID');
|
||||
}
|
||||
|
||||
$queries = $ctx->get(IpNoteQueries::class);
|
||||
$deleted = $queries->deleteWhereIp((int)$id, $ip);
|
||||
|
||||
if (!$deleted) {
|
||||
error("Note $id does not exist for $ip");
|
||||
}
|
||||
|
||||
modLog("Removed a note for <a href=\"?/user_posts/ip/{$ip}\">{$ip}</a>");
|
||||
|
||||
|
@ -870,14 +880,17 @@ function mod_ip_remove_note(Context $ctx, $ip, $id) {
|
|||
}
|
||||
|
||||
function mod_ip(Context $ctx, $ip, string $encoded_cursor = null) {
|
||||
global $config, $mod;
|
||||
global $mod;
|
||||
$config = $ctx->get('config');
|
||||
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP) === false)
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP) === false) {
|
||||
error('Invalid IP address');
|
||||
}
|
||||
|
||||
if (isset($_POST['ban_id'], $_POST['unban'])) {
|
||||
if (!hasPermission($config['mod']['unban']))
|
||||
if (!hasPermission($config['mod']['unban'])) {
|
||||
error($config['error']['noaccess']);
|
||||
}
|
||||
|
||||
Bans::delete($_POST['ban_id'], true, $mod['boards']);
|
||||
|
||||
|
@ -890,17 +903,15 @@ function mod_ip(Context $ctx, $ip, string $encoded_cursor = null) {
|
|||
}
|
||||
|
||||
if (isset($_POST['note'])) {
|
||||
if (!hasPermission($config['mod']['create_notes']))
|
||||
if (!hasPermission($config['mod']['create_notes'])) {
|
||||
error($config['error']['noaccess']);
|
||||
}
|
||||
|
||||
$_POST['note'] = escape_markup_modifiers($_POST['note']);
|
||||
markup($_POST['note']);
|
||||
$query = prepare('INSERT INTO ``ip_notes`` VALUES (NULL, :ip, :mod, :time, :body)');
|
||||
$query->bindValue(':ip', $ip);
|
||||
$query->bindValue(':mod', $mod['id']);
|
||||
$query->bindValue(':time', time());
|
||||
$query->bindValue(':body', $_POST['note']);
|
||||
$query->execute() or error(db_error($query));
|
||||
|
||||
$note_queries = $ctx->get(IpNoteQueries::class);
|
||||
$note_queries->add($ip, $mod['id'], $_POST['note']);
|
||||
|
||||
Cache::delete("mod_page_ip_view_notes_$ip");
|
||||
|
||||
|
@ -952,15 +963,8 @@ function mod_user_posts_by_ip(Context $ctx, string $ip, string $encoded_cursor =
|
|||
}
|
||||
|
||||
if (hasPermission($config['mod']['view_notes'])) {
|
||||
$ret = Cache::get("mod_page_ip_view_notes_$ip");
|
||||
if (!$ret) {
|
||||
$query = prepare("SELECT ``ip_notes``.*, `username` FROM ``ip_notes`` LEFT JOIN ``mods`` ON `mod` = ``mods``.`id` WHERE `ip` = :ip ORDER BY `time` DESC");
|
||||
$query->bindValue(':ip', $ip);
|
||||
$query->execute() or error(db_error($query));
|
||||
$ret = $query->fetchAll(PDO::FETCH_ASSOC);
|
||||
Cache::set("mod_page_ip_view_notes_$ip", $ret, 900);
|
||||
}
|
||||
$args['notes'] = $ret;
|
||||
$note_queries = $ctx->get(IpNoteQueries::class);
|
||||
$args['notes'] = $note_queries->getByIp($ip);
|
||||
}
|
||||
|
||||
if (hasPermission($config['mod']['modlog_ip'])) {
|
||||
|
@ -1087,12 +1091,6 @@ function mod_user_posts_by_passwd(Context $ctx, string $passwd, string $encoded_
|
|||
$args['boards'] = $boards;
|
||||
$args['token'] = make_secure_link_token('ban');
|
||||
|
||||
if (empty($encoded_cursor)) {
|
||||
$args['security_token'] = make_secure_link_token("user_posts/passwd/$passwd");
|
||||
} else {
|
||||
$args['security_token'] = make_secure_link_token("user_posts/passwd/$passwd/cursor/$encoded_cursor");
|
||||
}
|
||||
|
||||
mod_page(\sprintf('%s: %s', _('Password'), \htmlspecialchars($passwd)), 'mod/view_passwd.html', $args);
|
||||
}
|
||||
|
||||
|
@ -1493,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;
|
||||
|
@ -1788,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
|
||||
|
@ -2004,13 +2004,9 @@ function mod_ban_post(Context $ctx, $board, $delete, $post, $token = false) {
|
|||
$autotag .= "/${board}/" . " " . $filehash . " " . $filename ."\r\n";
|
||||
$autotag .= $body . "\r\n";
|
||||
$autotag = escape_markup_modifiers($autotag);
|
||||
markup($autotag);
|
||||
$query = prepare('INSERT INTO ``ip_notes`` VALUES (NULL, :ip, :mod, :time, :body)');
|
||||
$query->bindValue(':ip', $ip);
|
||||
$query->bindValue(':mod', $mod['id']);
|
||||
$query->bindValue(':time', time());
|
||||
$query->bindValue(':body', $autotag);
|
||||
$query->execute() or error(db_error($query));
|
||||
|
||||
$note_queries = $ctx->get(IpNoteQueries::class);
|
||||
$note_queries->add($ip, $mod['id'], $autotag);
|
||||
modLog("Added a note for <a href=\"?/user_posts/ip/{$ip}\">{$ip}</a>");
|
||||
}
|
||||
}
|
||||
|
@ -2116,12 +2112,9 @@ function mod_warning_post(Context $ctx, $board, $post, $token = false) {
|
|||
$autotag .= $body . "\r\n";
|
||||
$autotag = escape_markup_modifiers($autotag);
|
||||
markup($autotag);
|
||||
$query = prepare('INSERT INTO ``ip_notes`` VALUES (NULL, :ip, :mod, :time, :body)');
|
||||
$query->bindValue(':ip', $ip);
|
||||
$query->bindValue(':mod', $mod['id']);
|
||||
$query->bindValue(':time', time());
|
||||
$query->bindValue(':body', $autotag);
|
||||
$query->execute() or error(db_error($query));
|
||||
|
||||
$note_queries = $ctx->get(IpNoteQueries::class);
|
||||
$note_queries->add($ip, $mod['id'], $autotag);
|
||||
modLog("Added a note for <a href=\"?/user_posts/ip/{$ip}\">{$ip}</a>");
|
||||
}
|
||||
}
|
||||
|
@ -2227,7 +2220,7 @@ function mod_edit_post(Context $ctx, $board, $edit_raw_html, $postID) {
|
|||
}
|
||||
|
||||
function mod_delete(Context $ctx, $board, $post) {
|
||||
global $config;
|
||||
global $config, $mod;
|
||||
|
||||
if (!openBoard($board))
|
||||
error($config['error']['noboard']);
|
||||
|
@ -2268,12 +2261,9 @@ function mod_delete(Context $ctx, $board, $post) {
|
|||
$autotag .= $body . "\r\n";
|
||||
$autotag = escape_markup_modifiers($autotag);
|
||||
markup($autotag);
|
||||
$query = prepare('INSERT INTO ``ip_notes`` VALUES (NULL, :ip, :mod, :time, :body)');
|
||||
$query->bindValue(':ip', $ip);
|
||||
$query->bindValue(':mod', $mod['id']);
|
||||
$query->bindValue(':time', time());
|
||||
$query->bindValue(':body', $autotag);
|
||||
$query->execute() or error(db_error($query));
|
||||
|
||||
$note_queries = $ctx->get(IpNoteQueries::class);
|
||||
$note_queries->add($ip, $mod['id'], $autotag);
|
||||
modLog("Added a note for <a href=\"?/user_posts/ip/{$ip}\">{$ip}</a>");
|
||||
}
|
||||
}
|
||||
|
@ -2361,7 +2351,7 @@ function mod_spoiler_image(Context $ctx, $board, $post, $file) {
|
|||
}
|
||||
|
||||
function mod_deletebyip(Context $ctx, $boardName, $post, $global = false) {
|
||||
global $config, $board;
|
||||
global $config, $board, $mod;
|
||||
|
||||
$global = (bool)$global;
|
||||
|
||||
|
@ -2439,12 +2429,9 @@ function mod_deletebyip(Context $ctx, $boardName, $post, $global = false) {
|
|||
$autotag .= $body . "\r\n";
|
||||
$autotag = escape_markup_modifiers($autotag);
|
||||
markup($autotag);
|
||||
$query2 = prepare('INSERT INTO ``ip_notes`` VALUES (NULL, :ip, :mod, :time, :body)');
|
||||
$query2->bindValue(':ip', $ip);
|
||||
$query2->bindValue(':mod', $mod['id']);
|
||||
$query2->bindValue(':time', time());
|
||||
$query2->bindValue(':body', $autotag);
|
||||
$query2->execute() or error(db_error($query2));
|
||||
|
||||
$note_queries = $ctx->get(IpNoteQueries::class);
|
||||
$note_queries->add($ip, $mod['id'], $autotag);
|
||||
modLog("Added a note for <a href=\"?/user_posts/ip/{$ip}\">{$ip}</a>");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -111,7 +111,7 @@ $(window).ready(function() {
|
|||
$(form).find('input[type="submit"]').val(submit_txt);
|
||||
$(form).find('input[type="submit"]').removeAttr('disabled');
|
||||
$(form).find('input[name="subject"],input[name="file_url"],\
|
||||
textarea[name="body"],input[type="file"]').val('').change();
|
||||
textarea[name="body"],input[type="file"],input[name="embed"]').val('').change();
|
||||
},
|
||||
cache: false,
|
||||
contentType: false,
|
||||
|
@ -123,7 +123,7 @@ $(window).ready(function() {
|
|||
$(form).find('input[type="submit"]').val(submit_txt);
|
||||
$(form).find('input[type="submit"]').removeAttr('disabled');
|
||||
$(form).find('input[name="subject"],input[name="file_url"],\
|
||||
textarea[name="body"],input[type="file"]').val('').change();
|
||||
textarea[name="body"],input[type="file"],input[name="embed"]').val('').change();
|
||||
} else {
|
||||
alert(_('An unknown error occured when posting!'));
|
||||
$(form).find('input[type="submit"]').val(submit_txt);
|
||||
|
|
|
@ -17,6 +17,10 @@ $(document).ready(function() {
|
|||
// Default maximum image loads.
|
||||
const DEFAULT_MAX = 5;
|
||||
|
||||
if (localStorage.inline_expand_fit_height !== 'false') {
|
||||
$('<style id="expand-fit-height-style">.full-image { max-height: ' + window.innerHeight + 'px; }</style>').appendTo($('head'));
|
||||
}
|
||||
|
||||
let inline_expand_post = function() {
|
||||
let link = this.getElementsByTagName('a');
|
||||
|
||||
|
@ -56,12 +60,12 @@ $(document).ready(function() {
|
|||
},
|
||||
add: function(ele) {
|
||||
ele.deferred = $.Deferred();
|
||||
ele.deferred.done(function () {
|
||||
ele.deferred.done(function() {
|
||||
let $loadstart = $.Deferred();
|
||||
let thumb = ele.childNodes[0];
|
||||
let img = ele.childNodes[1];
|
||||
|
||||
let onLoadStart = function (img) {
|
||||
let onLoadStart = function(img) {
|
||||
if (img.naturalWidth) {
|
||||
$loadstart.resolve(img, thumb);
|
||||
} else {
|
||||
|
@ -69,15 +73,15 @@ $(document).ready(function() {
|
|||
}
|
||||
};
|
||||
|
||||
$(img).one('load', function () {
|
||||
$.when($loadstart).done(function () {
|
||||
// Once fully loaded, update the waiting queue.
|
||||
$(img).one('load', function() {
|
||||
$.when($loadstart).done(function() {
|
||||
// once fully loaded, update the waiting queue
|
||||
--loading;
|
||||
$(ele).data('imageLoading', 'false');
|
||||
update();
|
||||
});
|
||||
});
|
||||
$loadstart.done(function (img, thumb) {
|
||||
$loadstart.done(function(img, thumb) {
|
||||
thumb.style.display = 'none';
|
||||
img.style.display = '';
|
||||
});
|
||||
|
@ -202,6 +206,8 @@ $(document).ready(function() {
|
|||
Options.extend_tab('general', '<span id="inline-expand-max">' +
|
||||
_('Number of simultaneous image downloads (0 to disable): ') +
|
||||
'<input type="number" step="1" min="0" size="4"></span>');
|
||||
Options.extend_tab('general', '<label id="inline-expand-fit-height"><input type="checkbox">' + _('Fit expanded images into screen height') + '</label>');
|
||||
|
||||
$('#inline-expand-max input')
|
||||
.css('width', '50px')
|
||||
.val(localStorage.inline_expand_max || DEFAULT_MAX)
|
||||
|
@ -212,6 +218,21 @@ $(document).ready(function() {
|
|||
|
||||
localStorage.inline_expand_max = val;
|
||||
});
|
||||
|
||||
$('#inline-expand-fit-height input').on('change', function() {
|
||||
if (localStorage.inline_expand_fit_height !== 'false') {
|
||||
localStorage.inline_expand_fit_height = 'false';
|
||||
$('#expand-fit-height-style').remove();
|
||||
}
|
||||
else {
|
||||
localStorage.inline_expand_fit_height = 'true';
|
||||
$('<style id="expand-fit-height-style">.full-image { max-height: ' + window.innerHeight + 'px; }</style>').appendTo($('head'));
|
||||
}
|
||||
});
|
||||
|
||||
if (localStorage.inline_expand_fit_height !== 'false') {
|
||||
$('#inline-expand-fit-height input').prop('checked', true);
|
||||
}
|
||||
}
|
||||
|
||||
if (window.jQuery) {
|
||||
|
|
|
@ -43,9 +43,6 @@ $(function(){
|
|||
document.location.reload();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$("#style-select").detach().css({float:"none","margin-bottom":0}).appendTo(tab.content);
|
||||
});
|
||||
|
||||
}();
|
||||
|
|
|
@ -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(
|
||||
$('<a>', {href: '#', class: 'post-btn', title: 'Post menu'}).text('►')
|
||||
$('<a>', {href: '#', class: 'post-btn', title: 'Post menu'}).text('\u{25B6}\u{fe0e}')
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
$(document).ready(function() {
|
||||
let showBackLinks = function() {
|
||||
let replyId = $(this).attr('id').replace(/^reply_/, '');
|
||||
let replyId = $(this).attr('id').split('_')[1];
|
||||
|
||||
$(this).find('div.body a:not([rel="nofollow"])').each(function() {
|
||||
let id = $(this).text().match(/^>>(\d+)$/);
|
||||
|
|
|
@ -22,7 +22,7 @@ $(document).ready(function() {
|
|||
|
||||
function makeEmbedNode(embedHost, videoId, width, height) {
|
||||
return $(`<iframe type="text/html" width="${width}" height="${height}" class="full-image"
|
||||
src="https://${embedHost}/embed/${videoId}?autoplay=1" allow="fullscreen" frameborder="0"/>`);
|
||||
src="https://${embedHost}/embed/${videoId}?autoplay=1" allow="fullscreen" frameborder="0" referrerpolicy="strict-origin"/>`);
|
||||
}
|
||||
|
||||
// Adds Options panel item.
|
||||
|
|
82
post.php
82
post.php
|
@ -5,6 +5,7 @@
|
|||
|
||||
use Vichan\Context;
|
||||
use Vichan\Data\ReportQueries;
|
||||
use Vichan\Data\Driver\LogDriver;
|
||||
|
||||
require_once 'inc/bootstrap.php';
|
||||
|
||||
|
@ -221,7 +222,7 @@ function send_matrix_report(
|
|||
|
||||
$end = strlen($post['body_nomarkup']) > $max_msg_len ? ' [...]' : '';
|
||||
$post_content = mb_substr($post['body_nomarkup'], 0, $max_msg_len) . $end;
|
||||
$text_body = $reported_post_url . ($post['thread'] ? "#$post_id" : '') . " \nReason:\n" . $report_reason . " \nPost:\n" . $post_content;
|
||||
$text_body = $reported_post_url . ($post['thread'] ? "#$id" : '') . " \nReason:\n" . $report_reason . " \nPost:\n" . $post_content;
|
||||
|
||||
$random_transaction_id = mt_rand();
|
||||
$json_body = json_encode([
|
||||
|
@ -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);
|
||||
|
||||
|
@ -1315,7 +1313,7 @@ function handle_post(Context $ctx)
|
|||
|
||||
if (!hasPermission($config['mod']['bypass_filters'], $board['uri']) && !$dropped_post) {
|
||||
require_once 'inc/filters.php';
|
||||
do_filters($post);
|
||||
do_filters($ctx, $post);
|
||||
}
|
||||
|
||||
if ($post['has_file']) {
|
||||
|
@ -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'] .= "<tinyboard ocr image $key>" . htmlspecialchars($value) . "</tinyboard>";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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($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'])) {
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 26 KiB |
BIN
static/flags/alunya.png
Normal file
BIN
static/flags/alunya.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 399 B |
|
@ -5,7 +5,7 @@
|
|||
/*dark.css has been prepended (2021-11-11) instead of @import'd for performance*/
|
||||
body {
|
||||
background: #1E1E1E;
|
||||
color: #A7A7A7;
|
||||
color: #C0C0C0;
|
||||
font-family: Verdana, sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
@ -31,13 +31,13 @@ div.title p {
|
|||
font-size: 10px;
|
||||
}
|
||||
a, a:link, a:visited, .intro a.email span.name {
|
||||
color: #CCC;
|
||||
color: #EEE;
|
||||
text-decoration: none;
|
||||
font-family: sans-serif;
|
||||
font-family: Verdana, sans-serif;
|
||||
}
|
||||
a:link:hover, a:visited:hover {
|
||||
color: #fff;
|
||||
font-family: sans-serif;
|
||||
font-family: Verdana, sans-serif;
|
||||
text-decoration: none;
|
||||
}
|
||||
a.post_no {
|
||||
|
@ -48,11 +48,11 @@ a.post_no:hover {
|
|||
text-decoration: underline overline;
|
||||
}
|
||||
.intro a.post_no {
|
||||
color: #CCC;
|
||||
color: #EEE;
|
||||
}
|
||||
div.post.reply {
|
||||
background: #333333;
|
||||
border: #555555 1px solid;
|
||||
border: #4f4f4f 1px solid;
|
||||
|
||||
@media (max-width: 48em) {
|
||||
border-left-style: none;
|
||||
|
@ -60,7 +60,7 @@ div.post.reply {
|
|||
}
|
||||
}
|
||||
div.post.reply.highlighted {
|
||||
background: #555;
|
||||
background: #4f4f4f;
|
||||
border: transparent 1px solid;
|
||||
|
||||
@media (max-width: 48em) {
|
||||
|
@ -69,17 +69,17 @@ div.post.reply.highlighted {
|
|||
}
|
||||
}
|
||||
div.post.reply div.body a:link, div.post.reply div.body a:visited {
|
||||
color: #CCCCCC;
|
||||
color: #EEE;
|
||||
}
|
||||
div.post.reply div.body a:link:hover, div.post.reply div.body a:visited:hover {
|
||||
color: #32DD72;
|
||||
}
|
||||
div.post.inline {
|
||||
border: #555555 1px solid;
|
||||
border: #4f4f4f 1px solid;
|
||||
}
|
||||
.intro span.subject {
|
||||
font-size: 12px;
|
||||
font-family: sans-serif;
|
||||
font-family: Verdana, sans-serif;
|
||||
color: #446655;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
@ -96,16 +96,16 @@ div.post.inline {
|
|||
}
|
||||
input[type="text"], textarea, select {
|
||||
background: #333333;
|
||||
color: #CCCCCC;
|
||||
color: #EEE;
|
||||
border: #666666 1px solid;
|
||||
padding-left: 5px;
|
||||
padding-right: -5px;
|
||||
font-family: sans-serif;
|
||||
font-family: Verdana, sans-serif;
|
||||
font-size: 10pt;
|
||||
}
|
||||
input[type="password"] {
|
||||
background: #333333;
|
||||
color: #CCCCCC;
|
||||
color: #EEE;
|
||||
border: #666666 1px solid;
|
||||
}
|
||||
form table tr th {
|
||||
|
@ -133,10 +133,10 @@ div.banner a {
|
|||
input[type="submit"] {
|
||||
background: #333333;
|
||||
border: #888888 1px solid;
|
||||
color: #CCCCCC;
|
||||
color: #EEE;
|
||||
}
|
||||
input[type="submit"]:hover {
|
||||
background: #555555;
|
||||
background: #4f4f4f;
|
||||
border: #888888 1px solid;
|
||||
color: #32DD72;
|
||||
}
|
||||
|
@ -151,7 +151,7 @@ span.trip {
|
|||
}
|
||||
div.pages {
|
||||
background: #1E1E1E;
|
||||
font-family: sans-serif;
|
||||
font-family: Verdana, sans-serif;
|
||||
}
|
||||
.bar.bottom {
|
||||
bottom: 0px;
|
||||
|
@ -159,7 +159,7 @@ div.pages {
|
|||
background-color: #1E1E1E;
|
||||
}
|
||||
div.pages a.selected {
|
||||
color: #CCCCCC;
|
||||
color: #EEE;
|
||||
}
|
||||
hr {
|
||||
height: 1px;
|
||||
|
@ -167,7 +167,7 @@ hr {
|
|||
}
|
||||
div.boardlist {
|
||||
text-align: center;
|
||||
color: #A7A7A7;
|
||||
color: #C0C0C0;
|
||||
}
|
||||
div.ban {
|
||||
background-color: transparent;
|
||||
|
@ -188,7 +188,7 @@ div.boardlist:not(.bottom) {
|
|||
}
|
||||
.desktop-style div.boardlist:not(.bottom) {
|
||||
text-shadow: black 1px 1px 1px, black -1px -1px 1px, black -1px 1px 1px, black 1px -1px 1px;
|
||||
color: #A7A7A7;
|
||||
color: #C0C0C0;
|
||||
background-color: #1E1E1E;
|
||||
}
|
||||
div.report {
|
||||
|
@ -211,7 +211,7 @@ div.report {
|
|||
}
|
||||
.box {
|
||||
background: #333333;
|
||||
border-color: #555555;
|
||||
border-color: #4f4f4f;
|
||||
color: #C5C8C6;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
@ -221,7 +221,7 @@ div.report {
|
|||
}
|
||||
table thead th {
|
||||
background: #333333;
|
||||
border-color: #555555;
|
||||
border-color: #4f4f4f;
|
||||
color: #C5C8C6;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
@ -229,11 +229,11 @@ table tbody tr:nth-of-type( even ) {
|
|||
background-color: #333333;
|
||||
}
|
||||
table.board-list-table .board-uri .board-sfw {
|
||||
color: #CCCCCC;
|
||||
color: #EEE;
|
||||
}
|
||||
tbody.board-list-omitted td {
|
||||
background: #333333;
|
||||
border-color: #555555;
|
||||
border-color: #4f4f4f;
|
||||
}
|
||||
table.board-list-table .board-tags .board-cell:hover {
|
||||
background: #1e1e1e;
|
||||
|
|
|
@ -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%;
|
||||
|
|
|
@ -36,7 +36,7 @@ Opening posts with liberalism or reactionary topics will be treated with far mor
|
|||
|
||||
<p>9) Due to derailing, COVID denialism outside the COVID-19 thread will be deleted.</p>
|
||||
|
||||
<p>10) All boards except for /siberia/ (and potentially /roulette/) are 'Safe For Work' boards. Pornography should not be posted on them without good reason, and any pornography on these boards should be hidden using the Spoiler Image option. New threads on /siberia/ with pornographic topics should have a Spoiler Image on the opening post.</p>
|
||||
<p>10) All boards except for /siberia/ (and potentially /roulette/) are 'Safe For Work' boards. Pornography should not be posted on them without good reason, and any pornography on these boards should be hidden using the Spoiler Image option. New threads on /siberia/ with pornographic topics should have a Spoiler Image on the opening post. Some kinds of pornographic content are always banned on every board, including /siberia/: cp/loli/jailbait/anything that could possibly interpreted a child, "feral" furry, zoophilia, murder/gore (photographic) and other suitably extreme fetishes.</p>
|
||||
|
||||
<p>11) Posts should, overall, be conductive to an informed and productive discussion. /leftypol/ is not an academic journal, but it also should not be a cesspit of back and forth bickering and pointless insults. Users should attempt to argue for the point they are presenting in an honest and open way and should be receptive to information or arguments that do, in fact, challenge their views.</p>
|
||||
|
||||
|
|
|
@ -96,6 +96,7 @@
|
|||
grid-column: 1;
|
||||
grid-row: 3;
|
||||
width: 100%;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.modlog {
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
.home-description {
|
||||
margin: 20px auto 0 auto;
|
||||
text-align: center;
|
||||
max-width: 700px;"
|
||||
max-width: 700px;
|
||||
}
|
||||
</style>
|
||||
{{ boardlist.top }}
|
||||
|
|
|
@ -88,7 +88,7 @@
|
|||
<h2 id="9">
|
||||
How can I suggest or submit fixes to the site ?
|
||||
</h2>
|
||||
<p>There is a /meta/ thread for this, and our <a href="https://gitlab.leftypol.org/leftypol/leftypol/">Gitlab repo</a>.</p>
|
||||
<p>There is a /meta/ thread for this, and our <a href="https://forgejo.leftypol.org/leftypol/leftypol/">Forgejo repo</a>.</p>
|
||||
<h2 id="10">
|
||||
I don't trust Tor exit nodes. Do you have an .onion site ?
|
||||
</h2>
|
||||
|
@ -129,6 +129,10 @@
|
|||
What are the maximum filesize for attachments ?
|
||||
</h2>
|
||||
<p>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. </p>
|
||||
<h2 id="16">
|
||||
Can I have an account on your git instance?
|
||||
</h2>
|
||||
<p>Create the account on <a href="https://forgejo.leftypol.org/">Forgejo</a>, then contact the staff via the Tech Team General thread on /meta/ to get your account approved.</p>
|
||||
</div>
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue