Compare commits
96 commits
11-block-p
...
config
Author | SHA1 | Date | |
---|---|---|---|
9b1f4debad | |||
5bc1009dfb | |||
b55c299842 | |||
ca25c85984 | |||
ee84baf87d | |||
d4bc625c05 | |||
34bf9b2261 | |||
f580100121 | |||
46c5f17db8 | |||
c9926802f7 | |||
e76c4eeed4 | |||
3c9c86901a | |||
7aae8be1ae | |||
2b30929bc9 | |||
eed2b2986a | |||
de9d118390 | |||
37e771f0be | |||
b9a29927f3 | |||
84a22a788e | |||
126314846c | |||
e10cd5fab4 | |||
e5145cd98c | |||
dcb978e31a | |||
0c898abdfe | |||
6e5a56ff0e | |||
a779f12144 | |||
a586025a62 | |||
18624963d5 | |||
2cc7a70e4c | |||
e3693f2d19 | |||
63228d04be | |||
4cf6fd3838 | |||
f7dae74522 | |||
3e3b71211a | |||
2f69af8267 | |||
![]() |
4ef10e26fc | ||
![]() |
715005ec96 | ||
f7bef11ac9 | |||
![]() |
a8a947af65 | ||
![]() |
3510f05fe8 | ||
2cac548b4d | |||
24e43a5aa1 | |||
634f769592 | |||
b9938d9513 | |||
55508e6210 | |||
a28d9a4246 | |||
439730f216 | |||
f2d0ac7341 | |||
6763f7b416 | |||
19c0868320 | |||
8cb6a76f0a | |||
8282d5cd63 | |||
28f75c8aed | |||
ff94e58f2e | |||
181f4ba49a | |||
bac5032b56 | |||
8d189eb9c8 | |||
3c0779992a | |||
8cffb479fa | |||
08c2d6f5d1 | |||
acdf792daf | |||
54dcf79a7f | |||
2e1cb7995f | |||
8ee3f4c81d | |||
68b2911dfd | |||
612d1cfc57 | |||
5613baca05 | |||
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 |
|
@ -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).
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
"inc/polyfill.php",
|
||||
"inc/error.php",
|
||||
"inc/functions.php",
|
||||
"inc/functions/hide.php",
|
||||
"inc/functions/net.php"
|
||||
]
|
||||
},
|
||||
|
|
|
@ -18,6 +18,8 @@ RUN apk add --no-cache \
|
|||
graphicsmagick \
|
||||
gifsicle \
|
||||
ffmpeg \
|
||||
djvulibre \
|
||||
ghostscript \
|
||||
bind-tools \
|
||||
gettext \
|
||||
gettext-dev \
|
||||
|
|
20
inc/Data/Driver/CacheDriverTrait.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
namespace Vichan\Data\Driver;
|
||||
|
||||
|
||||
trait CacheDriverTrait {
|
||||
/**
|
||||
* Tries to interpret the uri as a path to a unix socket.
|
||||
*
|
||||
* @param string $uri
|
||||
* @return ?string The path to the socket, null if it cannot be interpreted as such.
|
||||
*/
|
||||
private static function asUnixSocketPath(string $uri): ?string {
|
||||
if (str_starts_with($uri, 'unix:')) {
|
||||
return \substr($uri, 5);
|
||||
} elseif (str_starts_with($uri, ':')) {
|
||||
return \substr($uri, 1);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
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
|
@ -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
|
@ -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
|
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,18 +5,49 @@ 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!');
|
||||
$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'");
|
||||
}
|
||||
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()) {
|
||||
$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)) {
|
||||
$err = $this->inner->getResultMessage();
|
||||
throw new \RuntimeException("Unable to add memcached servers: '$err'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
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
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
|
@ -1291,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<br>Your file\'s size: %filesz% bytes');
|
||||
$config['error']['maxsize'] = _('The file was too big.');
|
||||
|
@ -1888,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
|
||||
|
@ -2041,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;
|
||||
|
@ -2061,9 +2030,16 @@
|
|||
// 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 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;
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
namespace Vichan;
|
||||
|
||||
use Vichan\Data\Driver\CacheDriver;
|
||||
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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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');
|
||||
|
@ -2227,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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2281,38 +2277,31 @@ function markup(&$body, $track_cites = false) {
|
|||
if ($cite) {
|
||||
if (isset($cited_posts[$_board][$cite])) {
|
||||
$link = $cited_posts[$_board][$cite];
|
||||
if (isset($original_board)){
|
||||
$replacement = '<a ' .
|
||||
$replacement_board = $original_board ?? $_board;
|
||||
|
||||
$replacement = '<a ' .
|
||||
($_board == $board['uri'] ?
|
||||
'onclick="highlightReply(\''.$cite.'\', event);" '
|
||||
: '') . 'href="' . $link . '">' .
|
||||
'>>>/' . $original_board . '/' . $cite .
|
||||
'>>>/' . $replacement_board . '/' . $cite .
|
||||
'</a>';
|
||||
|
||||
if ($track_cites && $config['track_cites']) {
|
||||
$tracked_cites[] = [ $_board, $cite ];
|
||||
}
|
||||
else {
|
||||
$replacement = '<a ' .
|
||||
($_board == $board['uri'] ?
|
||||
'onclick="highlightReply(\''.$cite.'\', event);" '
|
||||
: '') . 'href="' . $link . '">' .
|
||||
'>>>/' . $_board . '/' . $cite .
|
||||
'</a>';
|
||||
|
||||
}
|
||||
|
||||
$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 = "<s>>>>/$_board/$cite</s>";
|
||||
}
|
||||
} elseif(isset($crossboard_indexes[$_board])) {
|
||||
} elseif (isset($crossboard_indexes[$_board])) {
|
||||
$replacement = '<a href="' . $crossboard_indexes[$_board] . '">' .
|
||||
'>>>/' . $_board . '/' .
|
||||
'</a>';
|
||||
$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 = "<s>>>>/$_board/$cite</s>";
|
||||
}
|
||||
|
||||
$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]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2583,11 +2572,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 +2604,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}"];
|
||||
|
|
6
inc/functions/hide.php
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?php
|
||||
namespace Vichan\Functions\Hide;
|
||||
|
||||
function secure_hash(string $data, bool $binary): string {
|
||||
return \hash('tiger160,3', $data, $binary);
|
||||
}
|
137
inc/mod/auth.php
|
@ -5,108 +5,106 @@
|
|||
*/
|
||||
|
||||
use Vichan\Context;
|
||||
use Vichan\Functions\Net;
|
||||
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) {
|
||||
// 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) {
|
||||
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;
|
||||
$new_salt = generate_salt();
|
||||
$password = crypt($password, $config['password_crypt'] . $new_salt . "$");
|
||||
return array($version, $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));
|
||||
$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, [ 'cost' => 12 ]);
|
||||
if ($r === false) {
|
||||
throw new \RuntimeException("Could not hash password");
|
||||
}
|
||||
else {
|
||||
$comp = crypt($test, $password);
|
||||
|
||||
return [ $version, $r ];
|
||||
}
|
||||
|
||||
function test_password(string $db_hash, string|int $version, string $input_password): bool {
|
||||
$version = (int)$version;
|
||||
if ($version < 2) {
|
||||
$ok = \hash_equals($db_hash, \crypt($input_password, $db_hash));
|
||||
} else {
|
||||
$pre_hash = \hash('tiger160,3', $input_password, false);
|
||||
$ok = \password_verify($pre_hash, $db_hash);
|
||||
}
|
||||
return array($version, hash_equals($password, $comp));
|
||||
return $ok;
|
||||
}
|
||||
|
||||
function generate_salt() {
|
||||
return strtr(base64_encode(random_bytes(16)), '+', '.');
|
||||
}
|
||||
|
||||
function login($username, $password) {
|
||||
global $mod, $config;
|
||||
function login(string $username, string $password): array|false {
|
||||
global $mod;
|
||||
|
||||
$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)) {
|
||||
list($version, $ok) = test_password($user['password'], $user['version'], $password);
|
||||
$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");
|
||||
$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(
|
||||
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();
|
||||
|
||||
|
@ -119,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);
|
||||
|
@ -141,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;
|
||||
}
|
||||
|
@ -159,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
|
||||
|
@ -188,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;
|
||||
}
|
||||
|
||||
|
@ -201,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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
@ -43,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 = [];
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -87,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 = [];
|
||||
|
@ -189,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']);
|
||||
|
@ -469,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']);
|
||||
|
@ -535,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']);
|
||||
|
@ -590,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']);
|
||||
|
@ -608,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']);
|
||||
|
@ -655,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']);
|
||||
|
@ -670,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']);
|
||||
|
@ -695,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']);
|
||||
|
@ -732,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']);
|
||||
|
@ -800,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";
|
||||
|
@ -831,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;
|
||||
|
@ -1093,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']);
|
||||
|
@ -1114,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']);
|
||||
|
@ -1131,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']);
|
||||
|
||||
|
@ -1166,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']);
|
||||
|
@ -1178,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']);
|
||||
|
@ -1260,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']);
|
||||
|
@ -1296,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']);
|
||||
|
@ -1320,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']);
|
||||
|
@ -1342,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']);
|
||||
|
@ -1489,8 +1509,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 +1805,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
|
||||
|
@ -1923,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']);
|
||||
|
@ -2042,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']);
|
||||
|
@ -2139,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']);
|
||||
|
@ -2216,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']);
|
||||
|
@ -2279,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']);
|
||||
|
@ -2302,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']);
|
||||
|
@ -2347,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))
|
||||
|
@ -2466,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']);
|
||||
|
@ -2644,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']);
|
||||
|
@ -2665,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']);
|
||||
|
@ -2763,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']);
|
||||
|
@ -2787,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']);
|
||||
|
@ -2835,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']);
|
||||
|
@ -2907,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']);
|
||||
|
@ -2974,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);
|
||||
|
@ -3006,13 +3043,27 @@ 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']);
|
||||
|
||||
$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();
|
||||
|
@ -3104,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']);
|
||||
|
@ -3244,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']);
|
||||
|
@ -3278,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']);
|
||||
|
@ -3360,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']);
|
||||
|
@ -3377,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']);
|
||||
|
@ -3426,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);
|
||||
|
@ -3436,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']);
|
||||
|
||||
|
@ -3497,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']);
|
||||
|
||||
|
@ -3622,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']);
|
||||
|
|
|
@ -1,152 +0,0 @@
|
|||
<?php
|
||||
/*
|
||||
* Copyright (c) 2016 vichan-devel
|
||||
*/
|
||||
|
||||
defined('TINYBOARD') or exit;
|
||||
|
||||
function gen_msgid($board, $id) {
|
||||
global $config;
|
||||
|
||||
$b = preg_replace("/[^0-9a-zA-Z$]/", 'x', $board);
|
||||
$salt = sha1($board . "|" . $id . "|" . $config['nntpchan']['salt']);
|
||||
$salt = substr($salt, 0, 7);
|
||||
$salt = base_convert($salt, 16, 36);
|
||||
|
||||
return "<$b.$id.$salt@".$config['nntpchan']['domain'].">";
|
||||
}
|
||||
|
||||
|
||||
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'] . " <poster@" . $config['nntpchan']['domain'] . ">";
|
||||
|
||||
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);
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
<?php
|
||||
define('TINYBOARD', 'fuck yeah');
|
||||
require_once('nntpchan.php');
|
||||
|
||||
|
||||
die();
|
||||
|
||||
$time = time();
|
||||
echo "\n@@@@ Thread:\n";
|
||||
echo $m0 = gennntp(["From" => "czaks <marcin@6irc.net>", "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 <marcin@6irc.net>", "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 <marcin@6irc.net>", "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 <marcin@6irc.net>", "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>");
|
||||
|
|
@ -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}')
|
||||
);
|
||||
}
|
||||
|
||||
|
|
656
post.php
|
@ -5,6 +5,7 @@
|
|||
|
||||
use Vichan\Context;
|
||||
use Vichan\Data\ReportQueries;
|
||||
use Vichan\Data\Driver\LogDriver;
|
||||
|
||||
require_once 'inc/bootstrap.php';
|
||||
|
||||
|
@ -352,166 +353,6 @@ function db_select_ban_appeals($ban_id)
|
|||
* Method handling functions
|
||||
*/
|
||||
|
||||
$dropped_post = false;
|
||||
|
||||
function handle_nntpchan()
|
||||
{
|
||||
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') {
|
||||
_syslog(LOG_INFO, "MM: Files: " . print_r($GLOBALS, true)); // Debug
|
||||
|
||||
$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
|
||||
|
@ -610,8 +451,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 +540,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 +555,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);
|
||||
|
||||
|
@ -791,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']);
|
||||
}
|
||||
|
||||
|
@ -836,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.
|
||||
|
@ -1023,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;
|
||||
|
@ -1162,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']) {
|
||||
|
@ -1183,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']);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1221,33 +1046,32 @@ function handle_post(Context $ctx)
|
|||
$post['body'] .= "\n<tinyboard raw html>1</tinyboard>";
|
||||
}
|
||||
|
||||
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<tinyboard flag>" . strtolower($country_code) . "</tinyboard>" .
|
||||
"\n<tinyboard flag alt>" . geoip_country_name_by_addr_v6($gi, ipv4to6($_SERVER['REMOTE_ADDR'])) . "</tinyboard>";
|
||||
}
|
||||
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<tinyboard flag>" . strtolower($country_code) . "</tinyboard>" .
|
||||
"\n<tinyboard flag alt>" . geoip_country_name_by_addr_v6($gi, ipv4to6($_SERVER['REMOTE_ADDR'])) . "</tinyboard>";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($config['user_flag'] && isset($_POST['user_flag']))
|
||||
if (!empty($_POST['user_flag'])) {
|
||||
|
@ -1267,11 +1091,9 @@ function handle_post(Context $ctx)
|
|||
$post['body'] .= "\n<tinyboard tag>" . $_POST['tag'] . "</tinyboard>";
|
||||
}
|
||||
|
||||
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<tinyboard proxy>" . $proxy . "</tinyboard>";
|
||||
}
|
||||
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<tinyboard proxy>" . $proxy . "</tinyboard>";
|
||||
}
|
||||
|
||||
$post['body_nomarkup'] = $post['body'];
|
||||
|
@ -1313,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);
|
||||
}
|
||||
|
@ -1404,13 +1226,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;
|
||||
|
@ -1449,17 +1271,35 @@ function handle_post(Context $ctx)
|
|||
}
|
||||
$image->destroy();
|
||||
} else {
|
||||
if (
|
||||
($file['extension'] == "pdf" && $config['pdf_file_thumbnail']) ||
|
||||
($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']));
|
||||
$mime = \mime_content_type($file['tmp_name']);
|
||||
$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.
|
||||
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) {
|
||||
$path = sprintf($config['file_thumb'], isset($config['file_icons'][$file['extension']]) ? $config['file_icons'][$file['extension']] : $config['file_icons']['default']);
|
||||
$log = $ctx->get(LogDriver::class);
|
||||
$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']);
|
||||
|
@ -1553,35 +1393,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,12 +1440,7 @@ 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) {
|
||||
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()));
|
||||
|
@ -1680,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
|
||||
|
@ -1783,10 +1554,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,22 +1650,13 @@ function handle_appeal(Context $ctx)
|
|||
displayBan($ban);
|
||||
}
|
||||
|
||||
// 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();
|
||||
} else {
|
||||
error("NNTPChan: NNTPChan support is disabled");
|
||||
}
|
||||
}
|
||||
|
||||
$ctx = Vichan\build_context($config);
|
||||
|
||||
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);
|
||||
|
|
340
search.php
|
@ -1,174 +1,178 @@
|
|||
<?php
|
||||
require 'inc/bootstrap.php';
|
||||
|
||||
if (!$config['search']['enable']) {
|
||||
die(_("Post search is disabled"));
|
||||
require 'inc/bootstrap.php';
|
||||
|
||||
if (!$config['search']['enable']) {
|
||||
die(_("Post search is disabled"));
|
||||
}
|
||||
|
||||
$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'];
|
||||
} else {
|
||||
$boards = listBoards(TRUE);
|
||||
}
|
||||
|
||||
$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];
|
||||
}
|
||||
|
||||
$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 .= '<p class="unimportant" style="text-align:center">(Query too broad.)</p>';
|
||||
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 .= '<p class="unimportant" style="text-align:center">('._('Query too broad.').')</p>';
|
||||
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) . '<hr/>';
|
||||
}
|
||||
|
||||
if (!empty($temp))
|
||||
$_body .= '<fieldset><legend>' .
|
||||
sprintf(ngettext('%d result in', '%d results in', $query->rowCount()),
|
||||
$query->rowCount()) . ' <a href="/' .
|
||||
sprintf($config['board_path'], $board['uri']) . $config['file_index'] .
|
||||
'">' .
|
||||
sprintf($config['board_abbreviation'], $board['uri']) . ' - ' . $board['title'] .
|
||||
'</a></legend>' . $temp . '</fieldset>';
|
||||
|
||||
$body .= '<hr/>';
|
||||
if (!empty($_body)) {
|
||||
$body .= $_body;
|
||||
} else {
|
||||
$boards = listBoards(TRUE);
|
||||
$body .= '<p style="text-align:center" class="unimportant">('._('No results.').')</p>';
|
||||
}
|
||||
|
||||
$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 .= '<p class="unimportant" style="text-align:center">(Query too broad.)</p>';
|
||||
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 .= '<p class="unimportant" style="text-align:center">('._('Query too broad.').')</p>';
|
||||
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) . '<hr/>';
|
||||
}
|
||||
|
||||
if(!empty($temp))
|
||||
$_body .= '<fieldset><legend>' .
|
||||
sprintf(ngettext('%d result in', '%d results in', $query->rowCount()),
|
||||
$query->rowCount()) . ' <a href="/' .
|
||||
sprintf($config['board_path'], $board['uri']) . $config['file_index'] .
|
||||
'">' .
|
||||
sprintf($config['board_abbreviation'], $board['uri']) . ' - ' . $board['title'] .
|
||||
'</a></legend>' . $temp . '</fieldset>';
|
||||
|
||||
$body .= '<hr/>';
|
||||
if(!empty($_body))
|
||||
$body .= $_body;
|
||||
else
|
||||
$body .= '<p style="text-align:center" class="unimportant">('._('No results.').')</p>';
|
||||
}
|
||||
|
||||
echo Element('page.html', Array(
|
||||
'config'=>$config,
|
||||
'title'=>_('Search'),
|
||||
'body'=>'' . $body
|
||||
));
|
||||
echo Element('page.html', Array(
|
||||
'config'=>$config,
|
||||
'title'=>_('Search'),
|
||||
'body'=>'' . $body
|
||||
));
|
||||
|
|
BIN
static/banners/1734708324675.jpg
Normal file
After Width: | Height: | Size: 6.4 KiB |
BIN
static/banners/closeted-dengoid.jpg
Normal file
After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 26 KiB |
BIN
static/banners/thread-on-leftypol.jpg
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
static/flags/420.png
Normal file
After Width: | Height: | Size: 465 B |
BIN
static/flags/rodina_get.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
static/flags/rodina_lp.png
Normal file
After Width: | Height: | Size: 522 B |
Before Width: | Height: | Size: 898 B After Width: | Height: | Size: 898 B |
BIN
static/flags/tania.png
Normal file
After Width: | Height: | Size: 498 B |
BIN
static/leftypol_logo.png
Normal file
After Width: | Height: | Size: 35 KiB |
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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%;
|
||||
|
@ -570,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;
|
||||
}
|
||||
|
|
|
@ -206,3 +206,8 @@ div.pages {
|
|||
border: 0;
|
||||
background: none !important;
|
||||
}
|
||||
|
||||
.theme-catalog div.thread:hover {
|
||||
background: #583E28;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
@ -692,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 {
|
||||
|
@ -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: #927C8E;
|
||||
border: transparent 1px solid;
|
||||
|
||||
@media (max-width: 48em) {
|
||||
|
|
|
@ -62,9 +62,11 @@ Opening posts with liberalism or reactionary topics will be treated with far mor
|
|||
|
||||
<p>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.</p>
|
||||
|
||||
<p>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.</p>
|
||||
|
||||
<p><b>META:</b></p>
|
||||
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
</div>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
.home-description {
|
||||
margin: 20px auto 0 auto;
|
||||
text-align: center;
|
||||
max-width: 700px;"
|
||||
max-width: 700px;
|
||||
}
|
||||
</style>
|
||||
{{ boardlist.top }}
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
<h2 id="3">
|
||||
I'm new here and <span class="strikethrough">learned politics from memes</span> would like to learn about leftism! Where should I start ?
|
||||
</h2>
|
||||
<p>There are some beginner lists in <a href="/leftypol/res/285223.html">the reading thread</a>. Meanwhile, consider asking your questions in <a href="/edu/">/edu/</a> or in a relevant /leftypol/ thread, such as <a href="https://leftypol.org/search.php?search=qtddtot&board=leftypol">QTDDTOT</a>.</p>
|
||||
<p>Consider asking your questions in <a href="/edu/">/edu/</a> or in a relevant /leftypol/ thread, such as <a href="https://leftypol.org/search.php?search=qtddtot&board=leftypol">QTDDTOT</a>.</p>
|
||||
<h2 id="4">
|
||||
What is the purpose of leftypol.org ?
|
||||
</h2>
|
||||
|
@ -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>
|
||||
|
||||
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|