diff --git a/.gitignore b/.gitignore
index 3205c64b..93cac6d4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -70,6 +70,9 @@ tf/
/mod/
/random/
+# Banners
+static/banners/*
+
#Fonts
stylesheets/fonts
diff --git a/compose.yml b/compose.yml
index 526e18c6..c04c85f9 100644
--- a/compose.yml
+++ b/compose.yml
@@ -28,6 +28,8 @@ services:
#MySQL Service
db:
image: mysql:8.0.35
+ restart: unless-stopped
+ tty: true
ports:
- "3306:3306"
environment:
diff --git a/inc/Data/Driver/ErrorLogLogDriver.php b/inc/Data/Driver/ErrorLogLogDriver.php
deleted file mode 100644
index e2050606..00000000
--- a/inc/Data/Driver/ErrorLogLogDriver.php
+++ /dev/null
@@ -1,28 +0,0 @@
-name = $name;
- $this->level = $level;
- }
-
- public function log(int $level, string $message): void {
- if ($level <= $this->level) {
- $lv = $this->levelToString($level);
- $line = "{$this->name} $lv: $message";
- \error_log($line, 0, null, null);
- }
- }
-}
diff --git a/inc/Data/Driver/FileLogDriver.php b/inc/Data/Driver/FileLogDriver.php
deleted file mode 100644
index 2c9f14a0..00000000
--- a/inc/Data/Driver/FileLogDriver.php
+++ /dev/null
@@ -1,61 +0,0 @@
-fd = \fopen($file_path, 'a');
- if ($this->fd === false) {
- throw new \RuntimeException("Unable to open log file at $file_path");
- }
-
- $this->name = $name;
- $this->level = $level;
-
- // In some cases PHP does not run the destructor.
- \register_shutdown_function([$this, 'close']);
- }
-
- public function __destruct() {
- $this->close();
- }
-
- public function log(int $level, string $message): void {
- if ($level <= $this->level) {
- $lv = $this->levelToString($level);
- $line = "{$this->name} $lv: $message\n";
- \flock($this->fd, LOCK_EX);
- \fwrite($this->fd, $line);
- \fflush($this->fd);
- \flock($this->fd, LOCK_UN);
- }
- }
-
- public function close() {
- \flock($this->fd, LOCK_UN);
- \fclose($this->fd);
- }
-}
diff --git a/inc/Data/Driver/LogDriver.php b/inc/Data/Driver/LogDriver.php
deleted file mode 100644
index fddc3f27..00000000
--- a/inc/Data/Driver/LogDriver.php
+++ /dev/null
@@ -1,22 +0,0 @@
-name = $name;
- $this->level = $level;
- }
-
- public function log(int $level, string $message): void {
- if ($level <= $this->level) {
- $lv = $this->levelToString($level);
- \fwrite(\STDERR, "{$this->name} $lv: $message\n");
- }
- }
-}
diff --git a/inc/Data/Driver/SyslogLogDriver.php b/inc/Data/Driver/SyslogLogDriver.php
deleted file mode 100644
index c0df5304..00000000
--- a/inc/Data/Driver/SyslogLogDriver.php
+++ /dev/null
@@ -1,35 +0,0 @@
-level = $level;
- }
-
- public function log(int $level, string $message): void {
- if ($level <= $this->level) {
- if (isset($_SERVER['REMOTE_ADDR'], $_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'])) {
- // CGI
- \syslog($level, "$message - client: {$_SERVER['REMOTE_ADDR']}, request: \"{$_SERVER['REQUEST_METHOD']} {$_SERVER['REQUEST_URI']}\"");
- } else {
- \syslog($level, $message);
- }
- }
- }
-}
diff --git a/inc/Data/IpNoteQueries.php b/inc/Data/IpNoteQueries.php
deleted file mode 100644
index ba6fdb15..00000000
--- a/inc/Data/IpNoteQueries.php
+++ /dev/null
@@ -1,76 +0,0 @@
-pdo = $pdo;
- $this->cache = $cache;
- }
-
- /**
- * Get all the notes relative to an IP.
- *
- * @param string $ip The IP of the notes. THE STRING IS NOT VALIDATED.
- * @return array Returns an array of notes sorted by the most recent. Includes the username of the mods.
- */
- public function getByIp(string $ip) {
- $ret = $this->cache->get("ip_note_queries_$ip");
- if ($ret !== null) {
- return $ret;
- }
-
- $query = $this->pdo->prepare('SELECT `ip_notes`.*, `username` FROM `ip_notes` LEFT JOIN `mods` ON `mod` = `mods`.`id` WHERE `ip` = :ip ORDER BY `time` DESC');
- $query->bindValue(':ip', $ip);
- $query->execute();
- $ret = $query->fetchAll(\PDO::FETCH_ASSOC);
-
- $this->cache->set("ip_note_queries_$ip", $ret);
- return $ret;
- }
-
- /**
- * Creates a new note relative to the given ip.
- *
- * @param string $ip The IP of the note. THE STRING IS NOT VALIDATED.
- * @param int $mod_id The id of the mod who created the note.
- * @param string $body The text of the note.
- * @return void
- */
- public function add(string $ip, int $mod_id, string $body) {
- $query = $this->pdo->prepare('INSERT INTO `ip_notes` (`ip`, `mod`, `time`, `body`) VALUES (:ip, :mod, :time, :body)');
- $query->bindValue(':ip', $ip);
- $query->bindValue(':mod', $mod_id);
- $query->bindValue(':time', time());
- $query->bindValue(':body', $body);
- $query->execute();
-
- $this->cache->delete("ip_note_queries_$ip");
- }
-
- /**
- * Delete a note only if it's of a particular IP address.
- *
- * @param int $id The id of the note.
- * @param int $ip The expected IP of the note. THE STRING IS NOT VALIDATED.
- * @return bool True if any note was deleted.
- */
- public function deleteWhereIp(int $id, string $ip): bool {
- $query = $this->pdo->prepare('DELETE FROM `ip_notes` WHERE `ip` = :ip AND `id` = :id');
- $query->bindValue(':ip', $ip);
- $query->bindValue(':id', $id);
- $query->execute();
- $any = $query->rowCount() != 0;
-
- if ($any) {
- $this->cache->delete("ip_note_queries_$ip");
- }
- return $any;
- }
-}
diff --git a/inc/Data/PageFetchResult.php b/inc/Data/PageFetchResult.php
deleted file mode 100644
index b33e7ac2..00000000
--- a/inc/Data/PageFetchResult.php
+++ /dev/null
@@ -1,15 +0,0 @@
-pdo = $pdo;
- }
-
- private function paginate(array $board_uris, int $page_size, ?string $cursor, callable $callback): PageFetchResult {
- // Decode the cursor.
- if ($cursor !== null) {
- list($cursor_type, $uri_id_cursor_map) = Net\decode_cursor($cursor);
- } else {
- // Defaults if $cursor is an invalid string.
- $cursor_type = null;
- $uri_id_cursor_map = [];
- }
- $next_cursor_map = [];
- $prev_cursor_map = [];
- $rows = [];
-
- foreach ($board_uris as $uri) {
- // Extract the cursor relative to the board.
- $start_id = null;
- if ($cursor_type !== null && isset($uri_id_cursor_map[$uri])) {
- $value = $uri_id_cursor_map[$uri];
- if (\is_numeric($value)) {
- $start_id = (int)$value;
- }
- }
-
- $posts = $callback($uri, $cursor_type, $start_id, $page_size);
-
- $posts_count = \count($posts);
-
- // By fetching one extra post bellow and/or above the limit, we know if there are any posts beside the current page.
- if ($posts_count === $page_size + 2) {
- $has_extra_prev_post = true;
- $has_extra_end_post = true;
- } else {
- /*
- * If the id we start fetching from is also the first id fetched from the DB, then we exclude it from
- * the results, noting that we fetched 1 more posts than we needed, and it was before the current page.
- * Hence, we have no extra post at the end and no next page.
- */
- $has_extra_prev_post = $start_id !== null && $start_id === (int)$posts[0]['id'];
- $has_extra_end_post = !$has_extra_prev_post && $posts_count > $page_size;
- }
-
- // Get the previous cursor, if any.
- if ($has_extra_prev_post) {
- \array_shift($posts);
- $posts_count--;
- // Select the most recent post.
- $prev_cursor_map[$uri] = $posts[0]['id'];
- }
- // Get the next cursor, if any.
- if ($has_extra_end_post) {
- \array_pop($posts);
- // Select the oldest post.
- $next_cursor_map[$uri] = $posts[$posts_count - 2]['id'];
- }
-
- $rows[$uri] = $posts;
- }
-
- $res = new PageFetchResult();
- $res->by_uri = $rows;
- $res->cursor_prev = !empty($prev_cursor_map) ? Net\encode_cursor(self::CURSOR_TYPE_PREV, $prev_cursor_map) : null;
- $res->cursor_next = !empty($next_cursor_map) ? Net\encode_cursor(self::CURSOR_TYPE_NEXT, $next_cursor_map) : null;
-
- return $res;
- }
-
- /**
- * Fetch a page of user posts.
- *
- * @param array $board_uris The uris of the boards that should be included.
- * @param string $ip The IP of the target user.
- * @param integer $page_size The Number of posts that should be fetched.
- * @param string|null $cursor The directional cursor to fetch the next or previous page. Null to start from the beginning.
- * @return PageFetchResult
- */
- public function fetchPaginatedByIp(array $board_uris, string $ip, int $page_size, ?string $cursor = null): PageFetchResult {
- return $this->paginate($board_uris, $page_size, $cursor, function($uri, $cursor_type, $start_id, $page_size) use ($ip) {
- if ($cursor_type === null) {
- $query = $this->pdo->prepare(sprintf('SELECT * FROM `posts_%s` WHERE `ip` = :ip ORDER BY `sticky` DESC, `id` DESC LIMIT :limit', $uri));
- $query->bindValue(':ip', $ip);
- $query->bindValue(':limit', $page_size + 1, \PDO::PARAM_INT); // Always fetch more.
- $query->execute();
- return $query->fetchAll(\PDO::FETCH_ASSOC);
- } elseif ($cursor_type === self::CURSOR_TYPE_NEXT) {
- $query = $this->pdo->prepare(sprintf('SELECT * FROM `posts_%s` WHERE `ip` = :ip AND `id` <= :start_id ORDER BY `sticky` DESC, `id` DESC LIMIT :limit', $uri));
- $query->bindValue(':ip', $ip);
- $query->bindValue(':start_id', $start_id, \PDO::PARAM_INT);
- $query->bindValue(':limit', $page_size + 2, \PDO::PARAM_INT); // Always fetch more.
- $query->execute();
- return $query->fetchAll(\PDO::FETCH_ASSOC);
- } elseif ($cursor_type === self::CURSOR_TYPE_PREV) {
- $query = $this->pdo->prepare(sprintf('SELECT * FROM `posts_%s` WHERE `ip` = :ip AND `id` >= :start_id ORDER BY `sticky` ASC, `id` ASC LIMIT :limit', $uri));
- $query->bindValue(':ip', $ip);
- $query->bindValue(':start_id', $start_id, \PDO::PARAM_INT);
- $query->bindValue(':limit', $page_size + 2, \PDO::PARAM_INT); // Always fetch more.
- $query->execute();
- return \array_reverse($query->fetchAll(\PDO::FETCH_ASSOC));
- } else {
- throw new \RuntimeException("Unknown cursor type '$cursor_type'");
- }
- });
- }
-
- /**
- * Fetch a page of user posts.
- *
- * @param array $board_uris The uris of the boards that should be included.
- * @param string $password The password of the target user.
- * @param integer $page_size The Number of posts that should be fetched.
- * @param string|null $cursor The directional cursor to fetch the next or previous page. Null to start from the beginning.
- * @return PageFetchResult
- */
- public function fetchPaginateByPassword(array $board_uris, string $password, int $page_size, ?string $cursor = null): PageFetchResult {
- return $this->paginate($board_uris, $page_size, $cursor, function($uri, $cursor_type, $start_id, $page_size) use ($password) {
- if ($cursor_type === null) {
- $query = $this->pdo->prepare(sprintf('SELECT * FROM `posts_%s` WHERE `password` = :password ORDER BY `sticky` DESC, `id` DESC LIMIT :limit', $uri));
- $query->bindValue(':password', $password);
- $query->bindValue(':limit', $page_size + 1, \PDO::PARAM_INT); // Always fetch more.
- $query->execute();
- return $query->fetchAll(\PDO::FETCH_ASSOC);
- } elseif ($cursor_type === self::CURSOR_TYPE_NEXT) {
- $query = $this->pdo->prepare(sprintf('SELECT * FROM `posts_%s` WHERE `password` = :password AND `id` <= :start_id ORDER BY `sticky` DESC, `id` DESC LIMIT :limit', $uri));
- $query->bindValue(':password', $password);
- $query->bindValue(':start_id', $start_id, \PDO::PARAM_INT);
- $query->bindValue(':limit', $page_size + 2, \PDO::PARAM_INT); // Always fetch more.
- $query->execute();
- return $query->fetchAll(\PDO::FETCH_ASSOC);
- } elseif ($cursor_type === self::CURSOR_TYPE_PREV) {
- $query = $this->pdo->prepare(sprintf('SELECT * FROM `posts_%s` WHERE `password` = :password AND `id` >= :start_id ORDER BY `sticky` ASC, `id` ASC LIMIT :limit', $uri));
- $query->bindValue(':password', $password);
- $query->bindValue(':start_id', $start_id, \PDO::PARAM_INT);
- $query->bindValue(':limit', $page_size + 2, \PDO::PARAM_INT); // Always fetch more.
- $query->execute();
- return \array_reverse($query->fetchAll(\PDO::FETCH_ASSOC));
- } else {
- throw new \RuntimeException("Unknown cursor type '$cursor_type'");
- }
- });
- }
-}
diff --git a/inc/anti-bot.php b/inc/anti-bot.php
index cf82dcc8..1f1c885b 100644
--- a/inc/anti-bot.php
+++ b/inc/anti-bot.php
@@ -196,56 +196,49 @@ function _create_antibot($pdo, $board, $thread) {
$antibot = new AntiBot(array($board, $thread));
try {
- retry_on_deadlock(3, function() use ($config, $pdo, $thread, $board, $antibot, $purged_old_antispam) {
- try {
- $pdo->beginTransaction();
+ $pdo->beginTransaction();
- // Delete old expired antispam, skipping those with NULL expiration timestamps (infinite lifetime).
- if (!isset($purged_old_antispam) && $config['auto_maintenance']) {
- $purged_old_antispam = true;
- purge_old_antispam();
- }
+ // Delete old expired antispam, skipping those with NULL expiration timestamps (infinite lifetime).
+ if (!isset($purged_old_antispam) && $config['auto_maintenance']) {
+ $purged_old_antispam = true;
+ purge_old_antispam();
+ }
- // Keep the now invalid timestamps around for a bit to enable users to post if they're still on an old version of
- // the HTML page.
- // By virtue of existing, we know that we're making a new version of the page, and the user from now on may just reload.
- if ($thread) {
- $query = prepare('UPDATE ``antispam`` SET `expires` = UNIX_TIMESTAMP() + :expires WHERE `board` = :board AND `thread` = :thread AND `expires` IS NULL');
- } else {
- $query = prepare('UPDATE ``antispam`` SET `expires` = UNIX_TIMESTAMP() + :expires WHERE `board` = :board AND `thread` IS NULL AND `expires` IS NULL');
- }
+ // Keep the now invalid timestamps around for a bit to enable users to post if they're still on an old version of
+ // the HTML page.
+ // By virtue of existing, we know that we're making a new version of the page, and the user from now on may just reload.
+ if ($thread) {
+ $query = prepare('UPDATE ``antispam`` SET `expires` = UNIX_TIMESTAMP() + :expires WHERE `board` = :board AND `thread` = :thread AND `expires` IS NULL');
+ } else {
+ $query = prepare('UPDATE ``antispam`` SET `expires` = UNIX_TIMESTAMP() + :expires WHERE `board` = :board AND `thread` IS NULL AND `expires` IS NULL');
+ }
- $query->bindValue(':board', $board);
- if ($thread) {
- $query->bindValue(':thread', $thread);
- }
- $query->bindValue(':expires', $config['spam']['hidden_inputs_expire']);
- // Throws on error.
- $query->execute();
+ $query->bindValue(':board', $board);
+ if ($thread) {
+ $query->bindValue(':thread', $thread);
+ }
+ $query->bindValue(':expires', $config['spam']['hidden_inputs_expire']);
+ // Throws on error.
+ $query->execute();
- $hash = $antibot->hash();
+ $hash = $antibot->hash();
- // Insert an antispam with infinite life as the HTML page of a thread might last well beyond the expiry date.
- $query = prepare('INSERT INTO ``antispam`` VALUES (:board, :thread, :hash, UNIX_TIMESTAMP(), NULL, 0)');
- $query->bindValue(':board', $board);
- $query->bindValue(':thread', $thread);
- $query->bindValue(':hash', $hash);
- // Throws on error.
- $query->execute();
+ // Insert an antispam with infinite life as the HTML page of a thread might last well beyond the expiry date.
+ $query = prepare('INSERT INTO ``antispam`` VALUES (:board, :thread, :hash, UNIX_TIMESTAMP(), NULL, 0)');
+ $query->bindValue(':board', $board);
+ $query->bindValue(':thread', $thread);
+ $query->bindValue(':hash', $hash);
+ // Throws on error.
+ $query->execute();
- $pdo->commit();
- } catch (\Exception $e) {
- $pdo->rollBack();
- throw $e;
- }
- });
+ $pdo->commit();
} catch (\PDOException $e) {
$pdo->rollBack();
if ($e->errorInfo === null || $e->errorInfo[1] != MYSQL_ER_LOCK_DEADLOCK) {
throw $e;
} else {
- \error_log('5 or more deadlocks on _create_antibot while inserting, skipping');
+ error_log('Deadlock on _create_antibot while inserting, skipping');
}
}
diff --git a/inc/config.php b/inc/config.php
index 71b0fbf4..633cdd33 100644
--- a/inc/config.php
+++ b/inc/config.php
@@ -63,29 +63,9 @@
// been generated. This keeps the script from querying the database and causing strain when not needed.
$config['has_installed'] = '.installed';
- // Deprecated, use 'log_system'.
+ // Use syslog() for logging all error messages and unauthorized login attempts.
$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;
@@ -220,9 +200,6 @@
// Used to salt secure tripcodes ("##trip") and poster IDs (if enabled).
$config['secure_trip_salt'] = ')(*&^%$#@!98765432190zyxwvutsrqponmlkjihgfedcba';
- // Used to salt poster passwords.
- $config['secure_password_salt'] = 'wKJSb7M5SyzMcFWD2gPO3j2RYUSO9B789!@#$%^&*()';
-
/*
* ====================
* Flood/spam settings
@@ -943,6 +920,10 @@
// 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.
@@ -981,6 +962,15 @@
// 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.
@@ -1202,22 +1192,10 @@
// Custom embedding (YouTube, vimeo, etc.)
// It's very important that you match the entire input (with ^ and $) or things will not work correctly.
$config['embedding'] = array(
- [
- '/^(?:(?:https?:)?\/\/)?((?:www|m)\.)?(?:(?:youtube(?:-nocookie)?\.com|youtu\.be))(?:\/(?:[\w\-]+\?v=|embed\/|live\/|v\/)?)([\w\-]{11})((?:\?|\&)\S+)?$/i',
- '
'
- ],
- [
- '/^https?:\/\/(\w+\.)?youtube\.com\/shorts\/([a-zA-Z0-9\-_]{10,11})(\?.*)?$/i',
- ''
- ],
+ array(
+ '/^https?:\/\/(\w+\.)?youtube\.com\/watch\?v=([a-zA-Z0-9\-_]{10,11})(&.+)?$/i',
+ 'VIDEO '
+ ),
array(
'/^https?:\/\/(\w+\.)?vimeo\.com\/(\d{2,10})(\?.+)?$/i',
''
@@ -1234,18 +1212,10 @@
'/^https?:\/\/video\.google\.com\/videoplay\?docid=(\d+)([](.+)?)?$/i',
' '
),
- [
+ array(
'/^https?:\/\/(\w+\.)?vocaroo\.com\/i\/([a-zA-Z0-9]{2,15})$/i',
- ''
- ],
- [
- '/^https?:\/\/(\w+\.)?voca\.ro\/([a-zA-Z0-9]{2,15})$/i',
- ''
- ],
- [
- '/^https?:\/\/(\w+\.)?vocaroo\.com\/([a-zA-Z0-9]{2,15})#?$/i',
- ''
- ]
+ ' '
+ )
);
// Embedding width and height.
@@ -1551,8 +1521,8 @@
// Do DNS lookups on IP addresses to get their hostname for the moderator IP pages (?/IP/x.x.x.x).
$config['mod']['dns_lookup'] = true;
- // How many recent posts, per board, to show in ?/user_posts/ip/x.x.x.x. and ?/user_posts/passwd/xxxxxxxx
- $config['mod']['recent_user_posts'] = 5;
+ // How many recent posts, per board, to show in each page of ?/IP/x.x.x.x.
+ $config['mod']['ip_recentposts'] = 5;
// Number of posts to display on the reports page.
$config['mod']['recent_reports'] = 10;
@@ -2030,6 +2000,12 @@
// is the absolute maximum, because MySQL cannot handle table names greater than 64 characters.
$config['board_regex'] = '[0-9a-zA-Z$_\x{0080}-\x{FFFF}]{1,58}';
+ // Youtube.js embed HTML code
+ $config['youtube_js_html'] = '';
+
// Slack Report Notification
$config['slack'] = false;
$config['slack_channel'] = "";
diff --git a/inc/context.php b/inc/context.php
index 11a153ec..b9e768e1 100644
--- a/inc/context.php
+++ b/inc/context.php
@@ -1,8 +1,8 @@
$config,
- LogDriver::class => function($c) {
- $config = $c->get('config');
-
- $name = $config['log_system']['name'];
- $level = $config['debug'] ? LogDriver::DEBUG : LogDriver::NOTICE;
- $backend = $config['log_system']['type'];
-
- $legacy_syslog = isset($config['syslog']) && $config['syslog'];
-
- // Check 'syslog' for backwards compatibility.
- if ($legacy_syslog || $backend === 'syslog') {
- $log_driver = new SyslogLogDriver($name, $level, $config['log_system']['syslog_stderr']);
- if ($legacy_syslog) {
- $log_driver->log(LogDriver::NOTICE, 'The configuration setting \'syslog\' is deprecated. Please use \'log_system\' instead');
- }
- return $log_driver;
- } elseif ($backend === 'file') {
- return new FileLogDriver($name, $level, $config['log_system']['file_path']);
- } elseif ($backend === 'stderr') {
- return new StderrLogDriver($name, $level);
- } elseif ($backend === 'error_log') {
- return new ErrorLogLogDriver($name, $level);
- } else {
- $log_driver = new ErrorLogLogDriver($name, $level);
- $log_driver->log(LogDriver::ERROR, "Unknown 'log_system' value '$backend', using 'error_log' default");
- return $log_driver;
- }
- },
CacheDriver::class => function($c) {
// Use the global for backwards compatibility.
return \cache::getCache();
@@ -73,10 +45,6 @@ function build_context(array $config): Context {
$auto_maintenance = (bool)$c->get('config')['auto_maintenance'];
$pdo = $c->get(\PDO::class);
return new ReportQueries($pdo, $auto_maintenance);
- },
- UserPostQueries::class => function($c) {
- return new UserPostQueries($c->get(\PDO::class));
- },
- IpNoteQueries::class => fn($c) => new IpNoteQueries($c->get(\PDO::class), $c->get(CacheDriver::class)),
+ }
]);
}
diff --git a/inc/filters.php b/inc/filters.php
index 97cbc524..2a66cd2a 100644
--- a/inc/filters.php
+++ b/inc/filters.php
@@ -4,26 +4,23 @@
* Copyright (c) 2010-2013 Tinyboard Development Group
*/
-use Vichan\Context;
-use Vichan\Data\IpNoteQueries;
-
defined('TINYBOARD') or exit;
class Filter {
public $flood_check;
private $condition;
private $post;
-
+
public function __construct(array $arr) {
foreach ($arr as $key => $value)
- $this->$key = $value;
+ $this->$key = $value;
}
-
+
public function match($condition, $match) {
$condition = strtolower($condition);
$post = &$this->post;
-
+
switch($condition) {
case 'custom':
if (!is_callable($match))
@@ -32,11 +29,11 @@ class Filter {
case 'flood-match':
if (!is_array($match))
error('Filter condition "flood-match" must be an array.');
-
+
// Filter out "flood" table entries which do not match this filter.
-
+
$flood_check_matched = array();
-
+
foreach ($this->flood_check as $flood_post) {
foreach ($match as $flood_match_arg) {
switch ($flood_match_arg) {
@@ -72,10 +69,10 @@ class Filter {
}
$flood_check_matched[] = $flood_post;
}
-
+
// is there any reason for this assignment?
$this->flood_check = $flood_check_matched;
-
+
return !empty($this->flood_check);
case 'flood-time':
foreach ($this->flood_check as $flood_post) {
@@ -138,42 +135,46 @@ class Filter {
error('Unknown filter condition: ' . $condition);
}
}
-
- public function action(Context $ctx) {
+
+ public function action() {
global $board;
$this->add_note = isset($this->add_note) ? $this->add_note : false;
if ($this->add_note) {
- $note_queries = $ctx->get(IpNoteQueries::class);
- $note_queries->add($_SERVER['REMOTE_ADDR'], -1, 'Autoban message: ' . $this->post['body']);
- }
+ $query = prepare('INSERT INTO ``ip_notes`` VALUES (NULL, :ip, :mod, :time, :body)');
+ $query->bindValue(':ip', $_SERVER['REMOTE_ADDR']);
+ $query->bindValue(':mod', -1);
+ $query->bindValue(':time', time());
+ $query->bindValue(':body', "Autoban message: ".$this->post['body']);
+ $query->execute() or error(db_error($query));
+ }
if (isset ($this->action)) switch($this->action) {
case 'reject':
error(isset($this->message) ? $this->message : 'Posting blocked by filter.');
case 'ban':
if (!isset($this->reason))
error('The ban action requires a reason.');
-
+
$this->expires = isset($this->expires) ? $this->expires : false;
$this->reject = isset($this->reject) ? $this->reject : true;
$this->all_boards = isset($this->all_boards) ? $this->all_boards : false;
-
+
Bans::new_ban($_SERVER['REMOTE_ADDR'], $this->reason, $this->expires, $this->all_boards ? false : $board['uri'], -1);
if ($this->reject) {
if (isset($this->message))
error($message);
-
+
checkBan($board['uri']);
exit;
}
-
+
break;
default:
error('Unknown filter action: ' . $this->action);
}
}
-
+
public function check(array $post) {
$this->post = $post;
foreach ($this->condition as $condition => $value) {
@@ -183,7 +184,7 @@ class Filter {
} else {
$NOT = false;
}
-
+
if ($this->match($condition, $value) == $NOT)
return false;
}
@@ -193,11 +194,11 @@ class Filter {
function purge_flood_table() {
global $config;
-
+
// Determine how long we need to keep a cache of posts for flood prevention. Unfortunately, it is not
// aware of flood filters in other board configurations. You can solve this problem by settings the
// config variable $config['flood_cache'] (seconds).
-
+
if (isset($config['flood_cache'])) {
$max_time = &$config['flood_cache'];
} else {
@@ -207,18 +208,18 @@ function purge_flood_table() {
$max_time = max($max_time, $filter['condition']['flood-time']);
}
}
-
+
$time = time() - $max_time;
-
+
query("DELETE FROM ``flood`` WHERE `time` < $time") or error(db_error());
}
-function do_filters(Context $ctx, array $post) {
+function do_filters(array $post) {
global $config;
if (!isset($config['filters']) || empty($config['filters']))
return;
-
+
foreach ($config['filters'] as $filter) {
if (isset($filter['condition']['flood-match'])) {
$has_flood = true;
@@ -231,15 +232,15 @@ function do_filters(Context $ctx, array $post) {
} else {
$flood_check = false;
}
-
+
foreach ($config['filters'] as $filter_array) {
$filter = new Filter($filter_array);
$filter->flood_check = $flood_check;
if ($filter->check($post)) {
- $filter->action($ctx);
+ $filter->action();
}
}
-
+
purge_flood_table();
}
diff --git a/inc/functions.php b/inc/functions.php
index def00287..a355e53c 100644
--- a/inc/functions.php
+++ b/inc/functions.php
@@ -745,23 +745,24 @@ function hasPermission($action = null, $board = null, $_mod = null) {
function listBoards($just_uri = false) {
global $config;
- $cache_name = $just_uri ? 'all_boards_uri' : 'all_boards';
+ $just_uri ? $cache_name = 'all_boards_uri' : $cache_name = 'all_boards';
- if ($config['cache']['enabled'] && ($boards = cache::get($cache_name))) {
+ if ($config['cache']['enabled'] && ($boards = cache::get($cache_name)))
return $boards;
- }
if (!$just_uri) {
- $query = query('SELECT * FROM ``boards`` ORDER BY `uri`');
+ $query = query("SELECT * FROM ``boards`` ORDER BY `uri`") or error(db_error());
$boards = $query->fetchAll();
} else {
- $query = query('SELECT `uri` FROM ``boards``');
- $boards = $query->fetchAll(\PDO::FETCH_COLUMN);
+ $boards = array();
+ $query = query("SELECT `uri` FROM ``boards``") or error(db_error());
+ while ($board = $query->fetchColumn()) {
+ $boards[] = $board;
+ }
}
- if ($config['cache']['enabled']) {
+ if ($config['cache']['enabled'])
cache::set($cache_name, $boards);
- }
return $boards;
}
@@ -2069,7 +2070,7 @@ function remove_modifiers($body) {
return preg_replace('@(.+?) @usm', '', $body);
}
-function markup(&$body, $track_cites = false) {
+function markup(&$body, $track_cites = false, $op = false) {
global $board, $config, $markup_urls;
$modifiers = extract_modifiers($body);
@@ -2168,15 +2169,12 @@ function markup(&$body, $track_cites = false) {
link_for(array('id' => $cite, 'thread' => $cited_posts[$cite])) . '#' . $cite . '">' .
'>>' . $cite .
'';
- } else {
- $replacement = ">>$cite ";
- }
- $body = mb_substr_replace($body, $matches[1][0] . $replacement . $matches[3][0], $matches[0][1] + $skip_chars, mb_strlen($matches[0][0]));
- $skip_chars += mb_strlen($matches[1][0] . $replacement . $matches[3][0]) - mb_strlen($matches[0][0]);
+ $body = mb_substr_replace($body, $matches[1][0] . $replacement . $matches[3][0], $matches[0][1] + $skip_chars, mb_strlen($matches[0][0]));
+ $skip_chars += mb_strlen($matches[1][0] . $replacement . $matches[3][0]) - mb_strlen($matches[0][0]);
- if ($track_cites && $config['track_cites']) {
- $tracked_cites[] = array($board['uri'], $cite);
+ if ($track_cites && $config['track_cites'])
+ $tracked_cites[] = array($board['uri'], $cite);
}
}
}
@@ -3085,8 +3083,3 @@ function strategy_first($fun, $array) {
return array('defer');
}
}
-
-function hashPassword($password) {
- global $config;
- return hash('sha3-256', $password . $config['secure_password_salt']);
-}
diff --git a/inc/lib/IP/LICENSE b/inc/lib/IP/LICENSE
new file mode 100755
index 00000000..fb315548
--- /dev/null
+++ b/inc/lib/IP/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2013 Jason Morriss
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
diff --git a/inc/lib/IP/Lifo/IP/BC.php b/inc/lib/IP/Lifo/IP/BC.php
new file mode 100755
index 00000000..26a2c2b7
--- /dev/null
+++ b/inc/lib/IP/Lifo/IP/BC.php
@@ -0,0 +1,293 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Lifo\IP;
+
+/**
+ * BCMath helper class.
+ *
+ * Provides a handful of BCMath routines that are not included in the native
+ * PHP library.
+ *
+ * Note: The Bitwise functions operate on fixed byte boundaries. For example,
+ * comparing the following numbers uses X number of bits:
+ * 0xFFFF and 0xFF will result in comparison of 16 bits.
+ * 0xFFFFFFFF and 0xF will result in comparison of 32 bits.
+ * etc...
+ *
+ */
+abstract class BC
+{
+ // Some common (maybe useless) constants
+ const MAX_INT_32 = '2147483647'; // 7FFFFFFF
+ const MAX_UINT_32 = '4294967295'; // FFFFFFFF
+ const MAX_INT_64 = '9223372036854775807'; // 7FFFFFFFFFFFFFFF
+ const MAX_UINT_64 = '18446744073709551615'; // FFFFFFFFFFFFFFFF
+ const MAX_INT_96 = '39614081257132168796771975167'; // 7FFFFFFFFFFFFFFFFFFFFFFF
+ const MAX_UINT_96 = '79228162514264337593543950335'; // FFFFFFFFFFFFFFFFFFFFFFFF
+ const MAX_INT_128 = '170141183460469231731687303715884105727'; // 7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+ const MAX_UINT_128 = '340282366920938463463374607431768211455'; // FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+
+ /**
+ * BC Math function to convert a HEX string into a DECIMAL
+ */
+ public static function bchexdec($hex)
+ {
+ if (strlen($hex) == 1) {
+ return hexdec($hex);
+ }
+
+ $remain = substr($hex, 0, -1);
+ $last = substr($hex, -1);
+ return bcadd(bcmul(16, self::bchexdec($remain), 0), hexdec($last), 0);
+ }
+
+ /**
+ * BC Math function to convert a DECIMAL string into a BINARY string
+ */
+ public static function bcdecbin($dec, $pad = null)
+ {
+ $bin = '';
+ while ($dec) {
+ $m = bcmod($dec, 2);
+ $dec = bcdiv($dec, 2, 0);
+ $bin = abs($m) . $bin;
+ }
+ return $pad ? sprintf("%0{$pad}s", $bin) : $bin;
+ }
+
+ /**
+ * BC Math function to convert a BINARY string into a DECIMAL string
+ */
+ public static function bcbindec($bin)
+ {
+ $dec = '0';
+ for ($i=0, $j=strlen($bin); $i<$j; $i++) {
+ $dec = bcmul($dec, '2', 0);
+ $dec = bcadd($dec, $bin[$i], 0);
+ }
+ return $dec;
+ }
+
+ /**
+ * BC Math function to convert a BINARY string into a HEX string
+ */
+ public static function bcbinhex($bin, $pad = 0)
+ {
+ return self::bcdechex(self::bcbindec($bin));
+ }
+
+ /**
+ * BC Math function to convert a DECIMAL into a HEX string
+ */
+ public static function bcdechex($dec)
+ {
+ $last = bcmod($dec, 16);
+ $remain = bcdiv(bcsub($dec, $last, 0), 16, 0);
+ return $remain == 0 ? dechex($last) : self::bcdechex($remain) . dechex($last);
+ }
+
+ /**
+ * Bitwise AND two arbitrarily large numbers together.
+ */
+ public static function bcand($left, $right)
+ {
+ $len = self::_bitwise($left, $right);
+
+ $value = '';
+ for ($i=0; $i<$len; $i++) {
+ $value .= (($left[$i] + 0) & ($right[$i] + 0)) ? '1' : '0';
+ }
+ return self::bcbindec($value != '' ? $value : '0');
+ }
+
+ /**
+ * Bitwise OR two arbitrarily large numbers together.
+ */
+ public static function bcor($left, $right)
+ {
+ $len = self::_bitwise($left, $right);
+
+ $value = '';
+ for ($i=0; $i<$len; $i++) {
+ $value .= (($left[$i] + 0) | ($right[$i] + 0)) ? '1' : '0';
+ }
+ return self::bcbindec($value != '' ? $value : '0');
+ }
+
+ /**
+ * Bitwise XOR two arbitrarily large numbers together.
+ */
+ public static function bcxor($left, $right)
+ {
+ $len = self::_bitwise($left, $right);
+
+ $value = '';
+ for ($i=0; $i<$len; $i++) {
+ $value .= (($left[$i] + 0) ^ ($right[$i] + 0)) ? '1' : '0';
+ }
+ return self::bcbindec($value != '' ? $value : '0');
+ }
+
+ /**
+ * Bitwise NOT two arbitrarily large numbers together.
+ */
+ public static function bcnot($left, $bits = null)
+ {
+ $right = 0;
+ $len = self::_bitwise($left, $right, $bits);
+ $value = '';
+ for ($i=0; $i<$len; $i++) {
+ $value .= $left[$i] == '1' ? '0' : '1';
+ }
+ return self::bcbindec($value);
+ }
+
+ /**
+ * Shift number to the left
+ *
+ * @param integer $bits Total bits to shift
+ */
+ public static function bcleft($num, $bits) {
+ return bcmul($num, bcpow('2', $bits));
+ }
+
+ /**
+ * Shift number to the right
+ *
+ * @param integer $bits Total bits to shift
+ */
+ public static function bcright($num, $bits) {
+ return bcdiv($num, bcpow('2', $bits));
+ }
+
+ /**
+ * Determine how many bits are needed to store the number rounded to the
+ * nearest bit boundary.
+ */
+ public static function bits_needed($num, $boundary = 4)
+ {
+ $bits = 0;
+ while ($num > 0) {
+ $num = bcdiv($num, '2', 0);
+ $bits++;
+ }
+ // round to nearest boundrary
+ return $boundary ? ceil($bits / $boundary) * $boundary : $bits;
+ }
+
+ /**
+ * BC Math function to return an arbitrarily large random number.
+ */
+ public static function bcrand($min, $max = null)
+ {
+ if ($max === null) {
+ $max = $min;
+ $min = 0;
+ }
+
+ // swap values if $min > $max
+ if (bccomp($min, $max) == 1) {
+ list($min,$max) = array($max,$min);
+ }
+
+ return bcadd(
+ bcmul(
+ bcdiv(
+ mt_rand(0, mt_getrandmax()),
+ mt_getrandmax(),
+ strlen($max)
+ ),
+ bcsub(
+ bcadd($max, '1'),
+ $min
+ )
+ ),
+ $min
+ );
+ }
+
+ /**
+ * Computes the natural logarithm using a series.
+ * @author Thomas Oldbury.
+ * @license Public domain.
+ */
+ public static function bclog($num, $iter = 10, $scale = 100)
+ {
+ $log = "0.0";
+ for($i = 0; $i < $iter; $i++) {
+ $pow = 1 + (2 * $i);
+ $mul = bcdiv("1.0", $pow, $scale);
+ $fraction = bcmul($mul, bcpow(bcsub($num, "1.0", $scale) / bcadd($num, "1.0", $scale), $pow, $scale), $scale);
+ $log = bcadd($fraction, $log, $scale);
+ }
+ return bcmul("2.0", $log, $scale);
+ }
+
+ /**
+ * Computes the base2 log using baseN log.
+ */
+ public static function bclog2($num, $iter = 10, $scale = 100)
+ {
+ return bcdiv(self::bclog($num, $iter, $scale), self::bclog("2", $iter, $scale), $scale);
+ }
+
+ public static function bcfloor($num)
+ {
+ if (substr($num, 0, 1) == '-') {
+ return bcsub($num, 1, 0);
+ }
+ return bcadd($num, 0, 0);
+ }
+
+ public static function bcceil($num)
+ {
+ if (substr($num, 0, 1) == '-') {
+ return bcsub($num, 0, 0);
+ }
+ return bcadd($num, 1, 0);
+ }
+
+ /**
+ * Compare two numbers and return -1, 0, 1 depending if the LEFT number is
+ * < = > the RIGHT.
+ *
+ * @param string|integer $left Left side operand
+ * @param string|integer $right Right side operand
+ * @return integer Return -1,0,1 for <=> comparison
+ */
+ public static function cmp($left, $right)
+ {
+ // @todo could an optimization be done to determine if a normal 32bit
+ // comparison could be done instead of using bccomp? But would
+ // the number verification cause too much overhead to be useful?
+ return bccomp($left, $right, 0);
+ }
+
+ /**
+ * Internal function to prepare for bitwise operations
+ */
+ private static function _bitwise(&$left, &$right, $bits = null)
+ {
+ if ($bits === null) {
+ $bits = max(self::bits_needed($left), self::bits_needed($right));
+ }
+
+ $left = self::bcdecbin($left);
+ $right = self::bcdecbin($right);
+
+ $len = max(strlen($left), strlen($right), (int)$bits);
+
+ $left = sprintf("%0{$len}s", $left);
+ $right = sprintf("%0{$len}s", $right);
+
+ return $len;
+ }
+
+}
diff --git a/inc/lib/IP/Lifo/IP/CIDR.php b/inc/lib/IP/Lifo/IP/CIDR.php
new file mode 100755
index 00000000..e8fe32ce
--- /dev/null
+++ b/inc/lib/IP/Lifo/IP/CIDR.php
@@ -0,0 +1,706 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Lifo\IP;
+
+/**
+ * CIDR Block helper class.
+ *
+ * Most routines can be used statically or by instantiating an object and
+ * calling its methods.
+ *
+ * Provides routines to do various calculations on IP addresses and ranges.
+ * Convert to/from CIDR to ranges, etc.
+ */
+class CIDR
+{
+ const INTERSECT_NO = 0;
+ const INTERSECT_YES = 1;
+ const INTERSECT_LOW = 2;
+ const INTERSECT_HIGH = 3;
+
+ protected $start;
+ protected $end;
+ protected $prefix;
+ protected $version;
+ protected $istart;
+ protected $iend;
+
+ private $cache;
+
+ /**
+ * Create a new CIDR object.
+ *
+ * The IP range can be arbitrary and does not have to fall on a valid CIDR
+ * range. Some methods will return different values depending if you ignore
+ * the prefix or not. By default all prefix sensitive methods will assume
+ * the prefix is used.
+ *
+ * @param string $cidr An IP address (1.2.3.4), CIDR block (1.2.3.4/24),
+ * or range "1.2.3.4-1.2.3.10"
+ * @param string $end Ending IP in range if no cidr/prefix is given
+ */
+ public function __construct($cidr, $end = null)
+ {
+ if ($end !== null) {
+ $this->setRange($cidr, $end);
+ } else {
+ $this->setCidr($cidr);
+ }
+ }
+
+ /**
+ * Returns the string representation of the CIDR block.
+ */
+ public function __toString()
+ {
+ // do not include the prefix if its a single IP
+ try {
+ if ($this->isTrueCidr() && (
+ ($this->version == 4 and $this->prefix != 32) ||
+ ($this->version == 6 and $this->prefix != 128)
+ )
+ ) {
+ return $this->start . '/' . $this->prefix;
+ }
+ } catch (\Exception $e) {
+ // isTrueCidr() calls getRange which can throw an exception
+ }
+ if (strcmp($this->start, $this->end) == 0) {
+ return $this->start;
+ }
+ return $this->start . ' - ' . $this->end;
+ }
+
+ public function __clone()
+ {
+ // do not clone the cache. No real reason why. I just want to keep the
+ // memory foot print as low as possible, even though this is trivial.
+ $this->cache = array();
+ }
+
+ /**
+ * Set an arbitrary IP range.
+ * The closest matching prefix will be calculated but the actual range
+ * stored in the object can be arbitrary.
+ * @param string $start Starting IP or combination "start-end" string.
+ * @param string $end Ending IP or null.
+ */
+ public function setRange($ip, $end = null)
+ {
+ if (strpos($ip, '-') !== false) {
+ list($ip, $end) = array_map('trim', explode('-', $ip, 2));
+ }
+
+ if (false === filter_var($ip, FILTER_VALIDATE_IP) ||
+ false === filter_var($end, FILTER_VALIDATE_IP)) {
+ throw new \InvalidArgumentException("Invalid IP range \"$ip-$end\"");
+ }
+
+ // determine version (4 or 6)
+ $this->version = (false === filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) ? 6 : 4;
+
+ $this->istart = IP::inet_ptod($ip);
+ $this->iend = IP::inet_ptod($end);
+
+ // fix order
+ if (bccomp($this->istart, $this->iend) == 1) {
+ list($this->istart, $this->iend) = array($this->iend, $this->istart);
+ list($ip, $end) = array($end, $ip);
+ }
+
+ $this->start = $ip;
+ $this->end = $end;
+
+ // calculate real prefix
+ $len = $this->version == 4 ? 32 : 128;
+ $this->prefix = $len - strlen(BC::bcdecbin(BC::bcxor($this->istart, $this->iend)));
+ }
+
+ /**
+ * Returns true if the current IP is a true cidr block
+ */
+ public function isTrueCidr()
+ {
+ return $this->start == $this->getNetwork() && $this->end == $this->getBroadcast();
+ }
+
+ /**
+ * Set the CIDR block.
+ *
+ * The prefix length is optional and will default to 32 ot 128 depending on
+ * the version detected.
+ *
+ * @param string $cidr CIDR block string, eg: "192.168.0.0/24" or "2001::1/64"
+ * @throws \InvalidArgumentException If the CIDR block is invalid
+ */
+ public function setCidr($cidr)
+ {
+ if (strpos($cidr, '-') !== false) {
+ return $this->setRange($cidr);
+ }
+
+ list($ip, $bits) = array_pad(array_map('trim', explode('/', $cidr, 2)), 2, null);
+ if (false === filter_var($ip, FILTER_VALIDATE_IP)) {
+ throw new \InvalidArgumentException("Invalid IP address \"$cidr\"");
+ }
+
+ // determine version (4 or 6)
+ $this->version = (false === filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) ? 6 : 4;
+
+ $this->start = $ip;
+ $this->istart = IP::inet_ptod($ip);
+
+ if ($bits !== null and $bits !== '') {
+ $this->prefix = $bits;
+ } else {
+ $this->prefix = $this->version == 4 ? 32 : 128;
+ }
+
+ if (($this->prefix < 0)
+ || ($this->prefix > 32 and $this->version == 4)
+ || ($this->prefix > 128 and $this->version == 6)) {
+ throw new \InvalidArgumentException("Invalid IP address \"$cidr\"");
+ }
+
+ $this->end = $this->getBroadcast();
+ $this->iend = IP::inet_ptod($this->end);
+
+ $this->cache = array();
+ }
+
+ /**
+ * Get the IP version. 4 or 6.
+ *
+ * @return integer
+ */
+ public function getVersion()
+ {
+ return $this->version;
+ }
+
+ /**
+ * Get the prefix.
+ *
+ * Always returns the "proper" prefix, even if the IP range is arbitrary.
+ *
+ * @return integer
+ */
+ public function getPrefix()
+ {
+ return $this->prefix;
+ }
+
+ /**
+ * Return the starting presentational IP or Decimal value.
+ *
+ * Ignores prefix
+ */
+ public function getStart($decimal = false)
+ {
+ return $decimal ? $this->istart : $this->start;
+ }
+
+ /**
+ * Return the ending presentational IP or Decimal value.
+ *
+ * Ignores prefix
+ */
+ public function getEnd($decimal = false)
+ {
+ return $decimal ? $this->iend : $this->end;
+ }
+
+ /**
+ * Return the next presentational IP or Decimal value (following the
+ * broadcast address of the current CIDR block).
+ */
+ public function getNext($decimal = false)
+ {
+ $next = bcadd($this->getEnd(true), '1');
+ return $decimal ? $next : new self(IP::inet_dtop($next));
+ }
+
+ /**
+ * Returns true if the IP is an IPv4
+ *
+ * @return boolean
+ */
+ public function isIPv4()
+ {
+ return $this->version == 4;
+ }
+
+ /**
+ * Returns true if the IP is an IPv6
+ *
+ * @return boolean
+ */
+ public function isIPv6()
+ {
+ return $this->version == 6;
+ }
+
+ /**
+ * Get the cidr notation for the subnet block.
+ *
+ * This is useful for when you want a string representation of the IP/prefix
+ * and the starting IP is not on a valid network boundrary (eg: Displaying
+ * an IP from an interface).
+ *
+ * @return string IP in CIDR notation "ipaddr/prefix"
+ */
+ public function getCidr()
+ {
+ return $this->start . '/' . $this->prefix;
+ }
+
+ /**
+ * Get the [low,high] range of the CIDR block
+ *
+ * Prefix sensitive.
+ *
+ * @param boolean $ignorePrefix If true the arbitrary start-end range is
+ * returned. default=false.
+ */
+ public function getRange($ignorePrefix = false)
+ {
+ $range = $ignorePrefix
+ ? array($this->start, $this->end)
+ : self::cidr_to_range($this->start, $this->prefix);
+ // watch out for IP '0' being converted to IPv6 '::'
+ if ($range[0] == '::' and strpos($range[1], ':') == false) {
+ $range[0] = '0.0.0.0';
+ }
+ return $range;
+ }
+
+ /**
+ * Return the IP in its fully expanded form.
+ *
+ * For example: 2001::1 == 2007:0000:0000:0000:0000:0000:0000:0001
+ *
+ * @see IP::inet_expand
+ */
+ public function getExpanded()
+ {
+ return IP::inet_expand($this->start);
+ }
+
+ /**
+ * Get network IP of the CIDR block
+ *
+ * Prefix sensitive.
+ *
+ * @param boolean $ignorePrefix If true the arbitrary start-end range is
+ * returned. default=false.
+ */
+ public function getNetwork($ignorePrefix = false)
+ {
+ // micro-optimization to prevent calling getRange repeatedly
+ $k = $ignorePrefix ? 1 : 0;
+ if (!isset($this->cache['range'][$k])) {
+ $this->cache['range'][$k] = $this->getRange($ignorePrefix);
+ }
+ return $this->cache['range'][$k][0];
+ }
+
+ /**
+ * Get broadcast IP of the CIDR block
+ *
+ * Prefix sensitive.
+ *
+ * @param boolean $ignorePrefix If true the arbitrary start-end range is
+ * returned. default=false.
+ */
+ public function getBroadcast($ignorePrefix = false)
+ {
+ // micro-optimization to prevent calling getRange repeatedly
+ $k = $ignorePrefix ? 1 : 0;
+ if (!isset($this->cache['range'][$k])) {
+ $this->cache['range'][$k] = $this->getRange($ignorePrefix);
+ }
+ return $this->cache['range'][$k][1];
+ }
+
+ /**
+ * Get the network mask based on the prefix.
+ *
+ */
+ public function getMask()
+ {
+ return self::prefix_to_mask($this->prefix, $this->version);
+ }
+
+ /**
+ * Get total hosts within CIDR range
+ *
+ * Prefix sensitive.
+ *
+ * @param boolean $ignorePrefix If true the arbitrary start-end range is
+ * returned. default=false.
+ */
+ public function getTotal($ignorePrefix = false)
+ {
+ // micro-optimization to prevent calling getRange repeatedly
+ $k = $ignorePrefix ? 1 : 0;
+ if (!isset($this->cache['range'][$k])) {
+ $this->cache['range'][$k] = $this->getRange($ignorePrefix);
+ }
+ return bcadd(bcsub(IP::inet_ptod($this->cache['range'][$k][1]),
+ IP::inet_ptod($this->cache['range'][$k][0])), '1');
+ }
+
+ public function intersects($cidr)
+ {
+ return self::cidr_intersect((string)$this, $cidr);
+ }
+
+ /**
+ * Determines the intersection between an IP (with optional prefix) and a
+ * CIDR block.
+ *
+ * The IP will be checked against the CIDR block given and will either be
+ * inside or outside the CIDR completely, or partially.
+ *
+ * NOTE: The caller should explicitly check against the INTERSECT_*
+ * constants because this method will return a value > 1 even for partial
+ * matches.
+ *
+ * @param mixed $ip The IP/cidr to match
+ * @param mixed $cidr The CIDR block to match within
+ * @return integer Returns an INTERSECT_* constant
+ * @throws \InvalidArgumentException if either $ip or $cidr is invalid
+ */
+ public static function cidr_intersect($ip, $cidr)
+ {
+ // use fixed length HEX strings so we can easily do STRING comparisons
+ // instead of using slower bccomp() math.
+ list($lo,$hi) = array_map(function($v){ return sprintf("%032s", IP::inet_ptoh($v)); }, CIDR::cidr_to_range($ip));
+ list($min,$max) = array_map(function($v){ return sprintf("%032s", IP::inet_ptoh($v)); }, CIDR::cidr_to_range($cidr));
+
+ /** visualization of logic used below
+ lo-hi = $ip to check
+ min-max = $cidr block being checked against
+ --- --- --- lo --- --- hi --- --- --- --- --- IP/prefix to check
+ --- min --- --- max --- --- --- --- --- --- --- Partial "LOW" match
+ --- --- --- --- --- min --- --- max --- --- --- Partial "HIGH" match
+ --- --- --- --- min max --- --- --- --- --- --- No match "NO"
+ --- --- --- --- --- --- --- --- min --- max --- No match "NO"
+ min --- max --- --- --- --- --- --- --- --- --- No match "NO"
+ --- --- min --- --- --- --- max --- --- --- --- Full match "YES"
+ */
+
+ // IP is exact match or completely inside the CIDR block
+ if ($lo >= $min and $hi <= $max) {
+ return self::INTERSECT_YES;
+ }
+
+ // IP is completely outside the CIDR block
+ if ($max < $lo or $min > $hi) {
+ return self::INTERSECT_NO;
+ }
+
+ // @todo is it useful to return LOW/HIGH partial matches?
+
+ // IP matches the lower end
+ if ($max <= $hi and $min <= $lo) {
+ return self::INTERSECT_LOW;
+ }
+
+ // IP matches the higher end
+ if ($min >= $lo and $max >= $hi) {
+ return self::INTERSECT_HIGH;
+ }
+
+ return self::INTERSECT_NO;
+ }
+
+ /**
+ * Converts an IPv4 or IPv6 CIDR block into its range.
+ *
+ * @todo May not be the fastest way to do this.
+ *
+ * @static
+ * @param string $cidr CIDR block or IP address string.
+ * @param integer|null $bits If /bits is not specified on string they can be
+ * passed via this parameter instead.
+ * @return array A 2 element array with the low, high range
+ */
+ public static function cidr_to_range($cidr, $bits = null)
+ {
+ if (strpos($cidr, '/') !== false) {
+ list($ip, $_bits) = array_pad(explode('/', $cidr, 2), 2, null);
+ } else {
+ $ip = $cidr;
+ $_bits = $bits;
+ }
+
+ if (false === filter_var($ip, FILTER_VALIDATE_IP)) {
+ throw new \InvalidArgumentException("IP address \"$cidr\" is invalid");
+ }
+
+ // force bit length to 32 or 128 depending on type of IP
+ $bitlen = (false === filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) ? 128 : 32;
+
+ if ($bits === null) {
+ // if no prefix is given use the length of the binary string which
+ // will give us 32 or 128 and result in a single IP being returned.
+ $bits = $_bits !== null ? $_bits : $bitlen;
+ }
+
+ if ($bits > $bitlen) {
+ throw new \InvalidArgumentException("IP address \"$cidr\" is invalid");
+ }
+
+ $ipdec = IP::inet_ptod($ip);
+ $ipbin = BC::bcdecbin($ipdec, $bitlen);
+
+ // calculate network
+ $netmask = BC::bcbindec(str_pad(str_repeat('1',$bits), $bitlen, '0'));
+ $ip1 = BC::bcand($ipdec, $netmask);
+
+ // calculate "broadcast" (not technically a broadcast in IPv6)
+ $ip2 = BC::bcor($ip1, BC::bcnot($netmask));
+
+ return array(IP::inet_dtop($ip1), IP::inet_dtop($ip2));
+ }
+
+ /**
+ * Return the CIDR string from the range given
+ */
+ public static function range_to_cidr($start, $end)
+ {
+ $cidr = new CIDR($start, $end);
+ return (string)$cidr;
+ }
+
+ /**
+ * Return the maximum prefix length that would fit the IP address given.
+ *
+ * This is useful to determine how my bit would be needed to store the IP
+ * address when you don't already have a prefix for the IP.
+ *
+ * @example 216.240.32.0 would return 27
+ *
+ * @param string $ip IP address without prefix
+ * @param integer $bits Maximum bits to check; defaults to 32 for IPv4 and 128 for IPv6
+ */
+ public static function max_prefix($ip, $bits = null)
+ {
+ static $mask = array();
+
+ $ver = (false === filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) ? 6 : 4;
+ $max = $ver == 6 ? 128 : 32;
+ if ($bits === null) {
+ $bits = $max;
+
+ }
+
+ $int = IP::inet_ptod($ip);
+ while ($bits > 0) {
+ // micro-optimization; calculate mask once ...
+ if (!isset($mask[$ver][$bits-1])) {
+ // 2^$max - 2^($max - $bits);
+ if ($ver == 4) {
+ $mask[$ver][$bits-1] = pow(2, $max) - pow(2, $max - ($bits-1));
+ } else {
+ $mask[$ver][$bits-1] = bcsub(bcpow(2, $max), bcpow(2, $max - ($bits-1)));
+ }
+ }
+
+ $m = $mask[$ver][$bits-1];
+ //printf("%s/%d: %s & %s == %s\n", $ip, $bits-1, BC::bcdecbin($m, 32), BC::bcdecbin($int, 32), BC::bcdecbin(BC::bcand($int, $m)));
+ //echo "$ip/", $bits-1, ": ", IP::inet_dtop($m), " ($m) & $int == ", BC::bcand($int, $m), "\n";
+ if (bccomp(BC::bcand($int, $m), $int) != 0) {
+ return $bits;
+ }
+ $bits--;
+ }
+ return $bits;
+ }
+
+ /**
+ * Return a contiguous list of true CIDR blocks that span the range given.
+ *
+ * Note: It's not a good idea to call this with IPv6 addresses. While it may
+ * work for certain ranges this can be very slow. Also an IPv6 list won't be
+ * as accurate as an IPv4 list.
+ *
+ * @example
+ * range_to_cidrlist(192.168.0.0, 192.168.0.15) ==
+ * 192.168.0.0/28
+ * range_to_cidrlist(192.168.0.0, 192.168.0.20) ==
+ * 192.168.0.0/28
+ * 192.168.0.16/30
+ * 192.168.0.20/32
+ */
+ public static function range_to_cidrlist($start, $end)
+ {
+ $ver = (false === filter_var($start, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) ? 6 : 4;
+ $start = IP::inet_ptod($start);
+ $end = IP::inet_ptod($end);
+
+ $len = $ver == 4 ? 32 : 128;
+ $log2 = $ver == 4 ? log(2) : BC::bclog(2);
+
+ $list = array();
+ while (BC::cmp($end, $start) >= 0) { // $end >= $start
+ $prefix = self::max_prefix(IP::inet_dtop($start), $len);
+ if ($ver == 4) {
+ $diff = $len - floor( log($end - $start + 1) / $log2 );
+ } else {
+ // this is not as accurate due to the bclog function
+ $diff = bcsub($len, BC::bcfloor(bcdiv(BC::bclog(bcadd(bcsub($end, $start), '1')), $log2)));
+ }
+
+ if ($prefix < $diff) {
+ $prefix = $diff;
+ }
+
+ $list[] = IP::inet_dtop($start) . "/" . $prefix;
+
+ if ($ver == 4) {
+ $start += pow(2, $len - $prefix);
+ } else {
+ $start = bcadd($start, bcpow(2, $len - $prefix));
+ }
+ }
+ return $list;
+ }
+
+ /**
+ * Return an list of optimized CIDR blocks by collapsing adjacent CIDR
+ * blocks into larger blocks.
+ *
+ * @param array $cidrs List of CIDR block strings or objects
+ * @param integer $maxPrefix Maximum prefix to allow
+ * @return array Optimized list of CIDR objects
+ */
+ public static function optimize_cidrlist($cidrs, $maxPrefix = 32)
+ {
+ // all indexes must be a CIDR object
+ $cidrs = array_map(function($o){ return $o instanceof CIDR ? $o : new CIDR($o); }, $cidrs);
+ // sort CIDR blocks in proper order so we can easily loop over them
+ $cidrs = self::cidr_sort($cidrs);
+
+ $list = array();
+ while ($cidrs) {
+ $c = array_shift($cidrs);
+ $start = $c->getStart();
+
+ $max = bcadd($c->getStart(true), $c->getTotal());
+
+ // loop through each cidr block until its ending range is more than
+ // the current maximum.
+ while (!empty($cidrs) and $cidrs[0]->getStart(true) <= $max) {
+ $b = array_shift($cidrs);
+ $newmax = bcadd($b->getStart(true), $b->getTotal());
+ if ($newmax > $max) {
+ $max = $newmax;
+ }
+ }
+
+ // add the new cidr range to the optimized list
+ $list = array_merge($list, self::range_to_cidrlist($start, IP::inet_dtop(bcsub($max, '1'))));
+ }
+
+ return $list;
+ }
+
+ /**
+ * Sort the list of CIDR blocks, optionally with a custom callback function.
+ *
+ * @param array $cidrs A list of CIDR blocks (strings or objects)
+ * @param Closure $callback Optional callback to perform the sorting.
+ * See PHP usort documentation for more details.
+ */
+ public static function cidr_sort($cidrs, $callback = null)
+ {
+ // all indexes must be a CIDR object
+ $cidrs = array_map(function($o){ return $o instanceof CIDR ? $o : new CIDR($o); }, $cidrs);
+
+ if ($callback === null) {
+ $callback = function($a, $b) {
+ if (0 != ($o = BC::cmp($a->getStart(true), $b->getStart(true)))) {
+ return $o; // < or >
+ }
+ if ($a->getPrefix() == $b->getPrefix()) {
+ return 0;
+ }
+ return $a->getPrefix() < $b->getPrefix() ? -1 : 1;
+ };
+ } elseif (!($callback instanceof \Closure) or !is_callable($callback)) {
+ throw new \InvalidArgumentException("Invalid callback in CIDR::cidr_sort, expected Closure, got " . gettype($callback));
+ }
+
+ usort($cidrs, $callback);
+ return $cidrs;
+ }
+
+ /**
+ * Return the Prefix bits from the IPv4 mask given.
+ *
+ * This is only valid for IPv4 addresses since IPv6 addressing does not
+ * have a concept of network masks.
+ *
+ * Example: 255.255.255.0 == 24
+ *
+ * @param string $mask IPv4 network mask.
+ */
+ public static function mask_to_prefix($mask)
+ {
+ if (false === filter_var($mask, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
+ throw new \InvalidArgumentException("Invalid IP netmask \"$mask\"");
+ }
+ return strrpos(IP::inet_ptob($mask, 32), '1') + 1;
+ }
+
+ /**
+ * Return the network mask for the prefix given.
+ *
+ * Normally this is only useful for IPv4 addresses but you can generate a
+ * mask for IPv6 addresses as well, only because its mathematically
+ * possible.
+ *
+ * @param integer $prefix CIDR prefix bits (0-128)
+ * @param integer $version IP version. If null the version will be detected
+ * based on the prefix length given.
+ */
+ public static function prefix_to_mask($prefix, $version = null)
+ {
+ if ($version === null) {
+ $version = $prefix > 32 ? 6 : 4;
+ }
+ if ($prefix < 0 or $prefix > 128) {
+ throw new \InvalidArgumentException("Invalid prefix length \"$prefix\"");
+ }
+ if ($version != 4 and $version != 6) {
+ throw new \InvalidArgumentException("Invalid version \"$version\". Must be 4 or 6");
+ }
+
+ if ($version == 4) {
+ return long2ip($prefix == 0 ? 0 : (0xFFFFFFFF >> (32 - $prefix)) << (32 - $prefix));
+ } else {
+ return IP::inet_dtop($prefix == 0 ? 0 : BC::bcleft(BC::bcright(BC::MAX_UINT_128, 128-$prefix), 128-$prefix));
+ }
+ }
+
+ /**
+ * Return true if the $ip given is a true CIDR block.
+ *
+ * A true CIDR block is one where the $ip given is the actual Network
+ * address and broadcast matches the prefix appropriately.
+ */
+ public static function cidr_is_true($ip)
+ {
+ $ip = new CIDR($ip);
+ return $ip->isTrueCidr();
+ }
+}
diff --git a/inc/lib/IP/Lifo/IP/IP.php b/inc/lib/IP/Lifo/IP/IP.php
new file mode 100755
index 00000000..4d22aa76
--- /dev/null
+++ b/inc/lib/IP/Lifo/IP/IP.php
@@ -0,0 +1,207 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+namespace Lifo\IP;
+
+/**
+ * IP Address helper class.
+ *
+ * Provides routines to translate IPv4 and IPv6 addresses between human readable
+ * strings, decimal, hexidecimal and binary.
+ *
+ * Requires BCmath extension and IPv6 PHP support
+ */
+abstract class IP
+{
+ /**
+ * Convert a human readable (presentational) IP address string into a decimal string.
+ */
+ public static function inet_ptod($ip)
+ {
+ // shortcut for IPv4 addresses
+ if (strpos($ip, ':') === false && strpos($ip, '.') !== false) {
+ return sprintf('%u', ip2long($ip));
+ }
+
+ // remove any cidr block notation
+ if (($o = strpos($ip, '/')) !== false) {
+ $ip = substr($ip, 0, $o);
+ }
+
+ // unpack into 4 32bit integers
+ $parts = unpack('N*', inet_pton($ip));
+ foreach ($parts as &$part) {
+ if ($part < 0) {
+ // convert signed int into unsigned
+ $part = sprintf('%u', $part);
+ //$part = bcadd($part, '4294967296');
+ }
+ }
+
+ // add each 32bit integer to the proper bit location in our big decimal
+ $decimal = $parts[4]; // << 0
+ $decimal = bcadd($decimal, bcmul($parts[3], '4294967296')); // << 32
+ $decimal = bcadd($decimal, bcmul($parts[2], '18446744073709551616')); // << 64
+ $decimal = bcadd($decimal, bcmul($parts[1], '79228162514264337593543950336')); // << 96
+
+ return $decimal;
+ }
+
+ /**
+ * Convert a decimal string into a human readable IP address.
+ */
+ public static function inet_dtop($decimal, $expand = false)
+ {
+ $parts = array();
+ $parts[1] = bcdiv($decimal, '79228162514264337593543950336', 0); // >> 96
+ $decimal = bcsub($decimal, bcmul($parts[1], '79228162514264337593543950336'));
+ $parts[2] = bcdiv($decimal, '18446744073709551616', 0); // >> 64
+ $decimal = bcsub($decimal, bcmul($parts[2], '18446744073709551616'));
+ $parts[3] = bcdiv($decimal, '4294967296', 0); // >> 32
+ $decimal = bcsub($decimal, bcmul($parts[3], '4294967296'));
+ $parts[4] = $decimal; // >> 0
+
+ foreach ($parts as &$part) {
+ if (bccomp($part, '2147483647') == 1) {
+ $part = bcsub($part, '4294967296');
+ }
+ $part = (int) $part;
+ }
+
+ // if the first 96bits is all zeros then we can safely assume we
+ // actually have an IPv4 address. Even though it's technically possible
+ // you're not really ever going to see an IPv6 address in the range:
+ // ::0 - ::ffff
+ // It's feasible to see an IPv6 address of "::", in which case the
+ // caller is going to have to account for that on their own.
+ if (($parts[1] | $parts[2] | $parts[3]) == 0) {
+ $ip = long2ip($parts[4]);
+ } else {
+ $packed = pack('N4', $parts[1], $parts[2], $parts[3], $parts[4]);
+ $ip = inet_ntop($packed);
+ }
+
+ // Turn IPv6 to IPv4 if it's IPv4
+ if (preg_match('/^::\d+\./', $ip)) {
+ return substr($ip, 2);
+ }
+
+ return $expand ? self::inet_expand($ip) : $ip;
+ }
+
+ /**
+ * Convert a human readable (presentational) IP address into a HEX string.
+ */
+ public static function inet_ptoh($ip)
+ {
+ return bin2hex(inet_pton($ip));
+ //return BC::bcdechex(self::inet_ptod($ip));
+ }
+
+ /**
+ * Convert a human readable (presentational) IP address into a BINARY string.
+ */
+ public static function inet_ptob($ip, $bits = 128)
+ {
+ return BC::bcdecbin(self::inet_ptod($ip), $bits);
+ }
+
+ /**
+ * Convert a binary string into an IP address (presentational) string.
+ */
+ public static function inet_btop($bin)
+ {
+ return self::inet_dtop(BC::bcbindec($bin));
+ }
+
+ /**
+ * Convert a HEX string into a human readable (presentational) IP address
+ */
+ public static function inet_htop($hex)
+ {
+ return self::inet_dtop(BC::bchexdec($hex));
+ }
+
+ /**
+ * Expand an IP address. IPv4 addresses are returned as-is.
+ *
+ * Example:
+ * 2001::1 expands to 2001:0000:0000:0000:0000:0000:0000:0001
+ * ::127.0.0.1 expands to 0000:0000:0000:0000:0000:0000:7f00:0001
+ * 127.0.0.1 expands to 127.0.0.1
+ */
+ public static function inet_expand($ip)
+ {
+ // strip possible cidr notation off
+ if (($pos = strpos($ip, '/')) !== false) {
+ $ip = substr($ip, 0, $pos);
+ }
+ $bytes = unpack('n*', inet_pton($ip));
+ if (count($bytes) > 2) {
+ return implode(':', array_map(function ($b) {
+ return sprintf("%04x", $b);
+ }, $bytes));
+ }
+ return $ip;
+ }
+
+ /**
+ * Convert an IPv4 address into an IPv6 address.
+ *
+ * One use-case for this is IP 6to4 tunnels used in networking.
+ *
+ * @example
+ * to_ipv4("10.10.10.10") == a0a:a0a
+ *
+ * @param string $ip IPv4 address.
+ * @param boolean $mapped If true a Full IPv6 address is returned within the
+ * official ipv4to6 mapped space "0:0:0:0:0:ffff:x:x"
+ */
+ public static function to_ipv6($ip, $mapped = false)
+ {
+ if (!self::isIPv4($ip)) {
+ throw new \InvalidArgumentException("Invalid IPv4 address \"$ip\"");
+ }
+
+ $num = IP::inet_ptod($ip);
+ $o1 = dechex($num >> 16);
+ $o2 = dechex($num & 0x0000FFFF);
+
+ return $mapped ? "0:0:0:0:0:ffff:$o1:$o2" : "$o1:$o2";
+ }
+
+ /**
+ * Returns true if the IP address is a valid IPv4 address
+ */
+ public static function isIPv4($ip)
+ {
+ return $ip === filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
+ }
+
+ /**
+ * Returns true if the IP address is a valid IPv6 address
+ */
+ public static function isIPv6($ip)
+ {
+ return $ip === filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6);
+ }
+
+ /**
+ * Compare two IP's (v4 or v6) and return -1, 0, 1 if the first is < = >
+ * the second.
+ *
+ * @param string $ip1 IP address
+ * @param string $ip2 IP address to compare against
+ * @return integer Return -1,0,1 depending if $ip1 is <=> $ip2
+ */
+ public static function cmp($ip1, $ip2)
+ {
+ return bccomp(self::inet_ptod($ip1), self::inet_ptod($ip2), 0);
+ }
+}
diff --git a/inc/lib/twig/extensions/Extension/Tinyboard.php b/inc/lib/twig/extensions/Extension/Tinyboard.php
index 5fb99b11..97fecb20 100644
--- a/inc/lib/twig/extensions/Extension/Tinyboard.php
+++ b/inc/lib/twig/extensions/Extension/Tinyboard.php
@@ -32,7 +32,7 @@ class Twig_Extensions_Extension_Tinyboard extends Twig_Extension
new Twig_SimpleFilter('addslashes', 'addslashes'),
);
}
-
+
/**
* Returns a list of functions to add to the existing list.
*
@@ -52,7 +52,7 @@ class Twig_Extensions_Extension_Tinyboard extends Twig_Extension
new Twig_SimpleFunction('link_for', 'link_for')
);
}
-
+
/**
* Returns the name of the extension.
*
@@ -88,7 +88,7 @@ function twig_hasPermission_filter($mod, $permission, $board = null) {
function twig_extension_filter($value, $case_insensitive = true) {
$ext = mb_substr($value, mb_strrpos($value, '.') + 1);
if($case_insensitive)
- $ext = mb_strtolower($ext);
+ $ext = mb_strtolower($ext);
return $ext;
}
@@ -113,7 +113,7 @@ function twig_filename_truncate_filter($value, $length = 30, $separator = '…')
$value = strrev($value);
$array = array_reverse(explode(".", $value, 2));
$array = array_map("strrev", $array);
-
+
$filename = &$array[0];
$extension = isset($array[1]) ? $array[1] : false;
@@ -127,11 +127,11 @@ function twig_filename_truncate_filter($value, $length = 30, $separator = '…')
function twig_ratio_function($w, $h) {
return fraction($w, $h, ':');
}
-
function twig_secure_link_confirm($text, $title, $confirm_message, $href) {
+ global $config;
+
return '' . $text . ' ';
}
-
function twig_secure_link($href) {
return $href . '/' . make_secure_link_token($href);
}
diff --git a/inc/mod/pages.php b/inc/mod/pages.php
index 155c19a6..45f6db8b 100644
--- a/inc/mod/pages.php
+++ b/inc/mod/pages.php
@@ -3,20 +3,21 @@
* Copyright (c) 2010-2013 Tinyboard Development Group
*/
use Vichan\Context;
-use Vichan\Data\{IpNoteQueries, UserPostQueries, ReportQueries};
-use Vichan\Data\Driver\LogDriver;
+use Vichan\Data\ReportQueries;
+use Vichan\Data\Driver\CacheDriver;
use Vichan\Functions\Net;
+use function Vichan\Functions\Net\decode_cursor;
+use function Vichan\Functions\Net\encode_cursor;
+
defined('TINYBOARD') or exit;
-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 _link_or_copy(string $target, string $link): bool {
+ if (!link($target, $link)) {
+ error_log("Failed to link() $target to $link. FAlling back to copy()");
+ return copy($target, $link);
+ }
+ return true;
}
function mod_page($title, $template, $args, $subtitle = false) {
@@ -57,7 +58,8 @@ 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'])) {
- $ctx->get(LogDriver::class)->log(LogDriver::INFO, 'Unauthorized login attempt!');
+ if ($config['syslog'])
+ _syslog(LOG_WARNING, 'Unauthorized login attempt!');
$args['error'] = $config['error']['invalid'];
} else {
@@ -767,10 +769,7 @@ function mod_board_log(Context $ctx, $board, $page_no = 1, $hide_names = false,
}
function mod_view_catalog(Context $ctx, $boardName) {
- global $mod;
-
- $config = $ctx->get('config');
-
+ global $config;
require_once($config['dir']['themes'].'/catalog/theme.php');
$settings = [];
$settings['boards'] = $boardName;
@@ -853,245 +852,218 @@ function mod_view_thread50(Context $ctx, $boardName, $thread) {
}
function mod_ip_remove_note(Context $ctx, $ip, $id) {
- $config = $ctx->get('config');
+ global $config;
- if (!hasPermission($config['mod']['remove_notes'])) {
- error($config['error']['noaccess']);
- }
+ if (!hasPermission($config['mod']['remove_notes']))
+ error($config['error']['noaccess']);
- if (filter_var($ip, \FILTER_VALIDATE_IP) === false) {
- error('Invalid IP address');
- }
+ if (filter_var($ip, FILTER_VALIDATE_IP) === false)
+ error("Invalid IP address.");
- if (!is_numeric($id)) {
- error('Invalid note ID');
- }
+ $cache = $ctx->get(CacheDriver::class);
- $queries = $ctx->get(IpNoteQueries::class);
- $deleted = $queries->deleteWhereIp((int)$id, $ip);
+ $query = prepare('DELETE FROM ``ip_notes`` WHERE `ip` = :ip AND `id` = :id');
+ $query->bindValue(':ip', $ip);
+ $query->bindValue(':id', $id);
+ $query->execute() or error(db_error($query));
- if (!$deleted) {
- error("Note $id does not exist for $ip");
- }
+ $cache->delete("mod_page_ip_view_notes_$ip");
- modLog("Removed a note for {$ip} ");
+ modLog("Removed a note for {$ip} ");
- \header("Location: ?/user_posts/ip/$ip#notes", true, $config['redirect_http']);
+ header('Location: ?/IP/' . $ip . '#notes', true, $config['redirect_http']);
}
-function mod_ip(Context $ctx, $ip, string $encoded_cursor = null) {
- global $mod;
- $config = $ctx->get('config');
+function mod_ip(Context $ctx, $ip, string $encoded_cursor = '') {
+ global $config, $mod;
- if (filter_var($ip, FILTER_VALIDATE_IP) === false) {
- error('Invalid IP address');
- }
+ if (filter_var($ip, FILTER_VALIDATE_IP) === false)
+ error("Invalid IP address.");
if (isset($_POST['ban_id'], $_POST['unban'])) {
- if (!hasPermission($config['mod']['unban'])) {
+ if (!hasPermission($config['mod']['unban']))
error($config['error']['noaccess']);
- }
Bans::delete($_POST['ban_id'], true, $mod['boards']);
if (empty($encoded_cursor)) {
- \header("Location: ?/user_posts/ip/$ip#bans", true, $config['redirect_http']);
+ header("Location: ?/IP/$ip#bans", true, $config['redirect_http']);
} else {
- \header("Location: ?/user_posts/ip/$ip/cursor/$encoded_cursor#bans", true, $config['redirect_http']);
+ header("Location: ?/IP/$ip/cursor/$encoded_cursor#bans", true, $config['redirect_http']);
}
return;
}
if (isset($_POST['note'])) {
- if (!hasPermission($config['mod']['create_notes'])) {
+ if (!hasPermission($config['mod']['create_notes']))
error($config['error']['noaccess']);
- }
$_POST['note'] = escape_markup_modifiers($_POST['note']);
markup($_POST['note']);
+ $query = prepare('INSERT INTO ``ip_notes`` VALUES (NULL, :ip, :mod, :time, :body)');
+ $query->bindValue(':ip', $ip);
+ $query->bindValue(':mod', $mod['id']);
+ $query->bindValue(':time', time());
+ $query->bindValue(':body', $_POST['note']);
+ $query->execute() or error(db_error($query));
- $note_queries = $ctx->get(IpNoteQueries::class);
- $note_queries->add($ip, $mod['id'], $_POST['note']);
+ $cache = $ctx->get(CacheDriver::class);
+ $cache->delete("mod_page_ip_view_notes_$ip");
- Cache::delete("mod_page_ip_view_notes_$ip");
-
- modLog("Added a note for {$ip} ");
+ modLog("Added a note for {$ip} ");
if (empty($encoded_cursor)) {
- \header("Location: ?/user_posts/ip/$ip#notes", true, $config['redirect_http']);
+ header("Location: ?/IP/$ip#notes", true, $config['redirect_http']);
} else {
- \header("Location: ?/user_posts/ip/$ip/cursor/$encoded_cursor#notes", true, $config['redirect_http']);
+ header("Location: ?/IP/$ip/cursor/$encoded_cursor#notes", true, $config['redirect_http']);
}
return;
}
- // Temporary Redirect so to not to break the note and unban system.
- if (empty($encoded_cursor)) {
- \header("Location: ?/user_posts/ip/$ip", true, 307);
- } else {
- \header("Location: ?/user_posts/ip/$ip/cursor/$encoded_cursor", true, 307);
- }
-}
-
-function mod_user_posts_by_ip(Context $ctx, string $ip, string $encoded_cursor = null) {
- global $mod;
-
- if (\filter_var($ip, \FILTER_VALIDATE_IP) === false){
- error('Invalid IP address');
- }
-
- $config = $ctx->get('config');
-
$args = [
'ip' => $ip,
'posts' => []
];
- if (isset($config['mod']['ip_recentposts'])) {
- // TODO log to migrate.
- $page_size = $config['mod']['ip_recentposts'];
- } else {
- $page_size = $config['mod']['recent_user_posts'];
- }
-
if ($config['mod']['dns_lookup']) {
$args['hostname'] = rDNS($ip);
}
+ // Decode the cursor.
+ list($cursor_type, $board_id_cursor_map) = decode_cursor($encoded_cursor);
+ $post_per_page = $config['mod']['ip_recentposts'];
+ $next_cursor_map = [];
+ $prev_cursor_map = [];
+
+ $boards = listBoards();
+ foreach ($boards as $board) {
+ $uri = $board['uri'];
+ openBoard($uri);
+ if (hasPermission($config['mod']['show_ip'], $uri)) {
+ // Extract the cursor relative to the board.
+ $id_cursor = false;
+ if (isset($board_id_cursor_map[$uri])) {
+ $value = $board_id_cursor_map[$uri];
+ if (is_numeric($value)) {
+ $id_cursor = (int)$value;
+ }
+ }
+
+ if ($id_cursor === false) {
+ $query = prepare(sprintf('SELECT * FROM `posts_%s` WHERE `ip` = :ip ORDER BY `sticky` DESC, `id` DESC LIMIT :limit', $uri));
+ $query->bindValue(':ip', $ip);
+ $query->bindValue(':limit', $post_per_page + 1, PDO::PARAM_INT); // Always fetch more.
+ $query->execute();
+ $posts = $query->fetchAll(PDO::FETCH_ASSOC);
+ } elseif ($cursor_type === 'n') {
+ $query = prepare(sprintf('SELECT * FROM `posts_%s` WHERE `ip` = :ip AND `id` <= :start_id ORDER BY `sticky` DESC, `id` DESC LIMIT :limit', $uri));
+ $query->bindValue(':ip', $ip);
+ $query->bindValue(':start_id', $id_cursor, PDO::PARAM_INT);
+ $query->bindValue(':limit', $post_per_page + 2, PDO::PARAM_INT); // Always fetch more.
+ $query->execute();
+ $posts = $query->fetchAll(PDO::FETCH_ASSOC);
+ } elseif ($cursor_type === 'p') {
+ // FIXME
+ $query = prepare(sprintf('SELECT * FROM `posts_%s` WHERE `ip` = :ip AND `id` >= :start_id ORDER BY `sticky` ASC, `id` ASC LIMIT :limit', $uri));
+ $query->bindValue(':ip', $ip);
+ $query->bindValue(':start_id', $id_cursor, PDO::PARAM_INT);
+ $query->bindValue(':limit', $post_per_page + 2, PDO::PARAM_INT); // Always fetch more.
+ $query->execute();
+ $posts = array_reverse($query->fetchAll(PDO::FETCH_ASSOC));
+ } else {
+ throw new RuntimeException("Unknown cursor type '$cursor_type'");
+ }
+
+ $posts_count = count($posts);
+
+ if ($posts_count === $post_per_page + 2) {
+ $has_extra_prev_post = true;
+ $has_extra_end_post = true;
+ } elseif ($posts_count === $post_per_page + 1) {
+ $has_extra_prev_post = $id_cursor !== false && $posts[0]['id'] == $id_cursor;
+ $has_extra_end_post = !$has_extra_prev_post;
+ } else {
+ $has_extra_prev_post = false;
+ $has_extra_end_post = false;
+ }
+
+ // Get the previous cursor, if any.
+ if ($has_extra_prev_post) {
+ // Select the most recent post.
+ $prev_cursor_map[$uri] = $posts[1]['id'];
+ array_shift($posts);
+ $posts_count--;
+ }
+ // Get the next cursor, if any.
+ if ($has_extra_end_post) {
+ // Since we fetched 1 above the limit, we always know if there are any posts after the current page.
+ // Query orders by DESC, so the SECOND last post has the lowest ID.
+ array_pop($posts);
+ $next_cursor_map[$uri] = $posts[$posts_count - 2]['id'];
+ }
+
+ // Finally load the post contents and build them.
+ foreach ($posts as $post) {
+ if (!$post['thread']) {
+ $po = new Thread($post, '?/', $mod, false);
+ } else {
+ $po = new Post($post, '?/', $mod);
+ }
+
+ if (!isset($args['posts'][$uri])) {
+ $args['posts'][$uri] = [ 'board' => $board, 'posts' => [] ];
+ }
+ $args['posts'][$uri]['posts'][] = $po->build(true);
+ }
+ }
+ }
+
+ // Build the cursors.
+ $args['cursor_prev'] = !empty($encoded_cursor) ? encode_cursor('p', $prev_cursor_map) : false;
+ $args['cursor_next'] = !empty($next_cursor_map) ? encode_cursor('n', $next_cursor_map) : false;
+
+ $args['boards'] = $boards;
+ $args['token'] = make_secure_link_token('ban');
+
if (hasPermission($config['mod']['view_ban'])) {
$args['bans'] = Bans::find($ip, false, true, $config['auto_maintenance']);
}
if (hasPermission($config['mod']['view_notes'])) {
- $note_queries = $ctx->get(IpNoteQueries::class);
- $args['notes'] = $note_queries->getByIp($ip);
+ $cache = $ctx->get(CacheDriver::class);
+ $ret = $cache->get("mod_page_ip_view_notes_$ip");
+ if ($ret === null) {
+ $query = prepare("SELECT ``ip_notes``.*, `username` FROM ``ip_notes`` LEFT JOIN ``mods`` ON `mod` = ``mods``.`id` WHERE `ip` = :ip ORDER BY `time` DESC");
+ $query->bindValue(':ip', $ip);
+ $query->execute() or error(db_error($query));
+ $ret = $query->fetchAll(PDO::FETCH_ASSOC);
+ $cache->set("mod_page_ip_view_notes_$ip", $ret, 900);
+ }
+ $args['notes'] = $ret;
}
if (hasPermission($config['mod']['modlog_ip'])) {
- $ret = Cache::get("mod_page_ip_modlog_ip_$ip");
- if (!$ret) {
+ $cache = $ctx->get(CacheDriver::class);
+ $ret = $cache->get("mod_page_ip_modlog_ip_$ip");
+ if ($ret === null) {
$query = prepare("SELECT `username`, `mod`, `ip`, `board`, `time`, `text` FROM ``modlogs`` LEFT JOIN ``mods`` ON `mod` = ``mods``.`id` WHERE `text` LIKE :search ORDER BY `time` DESC LIMIT 50");
$query->bindValue(':search', '%' . $ip . '%');
$query->execute() or error(db_error($query));
$ret = $query->fetchAll(PDO::FETCH_ASSOC);
- Cache::set("mod_page_ip_modlog_ip_$ip", $ret, 900);
+ $cache->set("mod_page_ip_modlog_ip_$ip", $ret, 900);
}
$args['logs'] = $ret;
} else {
$args['logs'] = [];
}
- $boards = listBoards();
-
- $queryable_uris = [];
- foreach ($boards as $board) {
- $uri = $board['uri'];
- if (hasPermission($config['mod']['show_ip'], $uri)) {
- $queryable_uris[] = $uri;
- }
- }
-
- $queries = $ctx->get(UserPostQueries::class);
- $result = $queries->fetchPaginatedByIp($queryable_uris, $ip, $page_size, $encoded_cursor);
-
- $args['cursor_prev'] = $result->cursor_prev;
- $args['cursor_next'] = $result->cursor_next;
-
- foreach($boards as $board) {
- $uri = $board['uri'];
- // The Thread and Post classes rely on some implicit board parameter set by openBoard.
- openBoard($uri);
-
- // Finally load the post contents and build them.
- foreach ($result->by_uri[$uri] as $post) {
- if (!$post['thread']) {
- $po = new Thread($post, '?/', $mod, false);
- } else {
- $po = new Post($post, '?/', $mod);
- }
-
- if (!isset($args['posts'][$uri])) {
- $args['posts'][$uri] = [ 'board' => $board, 'posts' => [] ];
- }
- $args['posts'][$uri]['posts'][] = $po->build(true);
- }
- }
-
- $args['boards'] = $boards;
- $args['token'] = make_secure_link_token('ban');
-
- // Since the security token is only used to send requests to create notes and remove bans, use "?/IP/" as the url.
if (empty($encoded_cursor)) {
$args['security_token'] = make_secure_link_token("IP/$ip");
} else {
$args['security_token'] = make_secure_link_token("IP/$ip/cursor/$encoded_cursor");
}
- mod_page(\sprintf('%s: %s', _('IP'), \htmlspecialchars($ip)), 'mod/view_ip.html', $args, $args['hostname']);
-}
-
-function mod_user_posts_by_passwd(Context $ctx, string $passwd, string $encoded_cursor = null) {
- global $mod;
-
- // The current hashPassword implementation uses sha3-256, which has a 64 character output in non-binary mode.
- if (\strlen($passwd) != 64) {
- error('Invalid password');
- }
-
- $config = $ctx->get('config');
-
- $args = [
- 'passwd' => $passwd,
- 'posts' => []
- ];
-
- if (isset($config['mod']['ip_recentposts'])) {
- // TODO log to migrate.
- $page_size = $config['mod']['ip_recentposts'];
- } else {
- $page_size = $config['mod']['recent_user_posts'];
- }
-
- $boards = listBoards();
-
- $queryable_uris = [];
- foreach ($boards as $board) {
- $uri = $board['uri'];
- if (hasPermission($config['mod']['show_ip'], $uri)) {
- $queryable_uris[] = $uri;
- }
- }
-
- $queries = $ctx->get(UserPostQueries::class);
- $result = $queries->fetchPaginateByPassword($queryable_uris, $passwd, $page_size, $encoded_cursor);
-
- $args['cursor_prev'] = $result->cursor_prev;
- $args['cursor_next'] = $result->cursor_next;
-
- foreach($boards as $board) {
- $uri = $board['uri'];
- // The Thread and Post classes rely on some implicit board parameter set by openBoard.
- openBoard($uri);
-
- // Finally load the post contents and build them.
- foreach ($result->by_uri[$uri] as $post) {
- if (!$post['thread']) {
- $po = new Thread($post, '?/', $mod, false);
- } else {
- $po = new Post($post, '?/', $mod);
- }
-
- if (!isset($args['posts'][$uri])) {
- $args['posts'][$uri] = [ 'board' => $board, 'posts' => [] ];
- }
- $args['posts'][$uri]['posts'][] = $po->build(true);
- }
- }
-
- $args['boards'] = $boards;
- $args['token'] = make_secure_link_token('ban');
-
- mod_page(\sprintf('%s: %s', _('Password'), \htmlspecialchars($passwd)), 'mod/view_passwd.html', $args);
+ mod_page(sprintf('%s: %s', _('IP'), htmlspecialchars($ip)), 'mod/view_ip.html', $args, $args['hostname']);
}
function mod_ban(Context $ctx) {
@@ -1491,9 +1463,8 @@ 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;
@@ -1787,8 +1758,7 @@ function mod_merge(Context $ctx, $originBoard, $postID) {
$op = $post;
$op['id'] = $newID;
- $_link_or_copy = _link_or_copy_factory($ctx);
- $clone = $shadow ? $_link_or_copy : 'rename';
+ $clone = $shadow ? '_link_or_copy' : 'rename';
if ($post['has_file']) {
// copy image
@@ -2004,10 +1974,14 @@ function mod_ban_post(Context $ctx, $board, $delete, $post, $token = false) {
$autotag .= "/${board}/" . " " . $filehash . " " . $filename ."\r\n";
$autotag .= $body . "\r\n";
$autotag = escape_markup_modifiers($autotag);
-
- $note_queries = $ctx->get(IpNoteQueries::class);
- $note_queries->add($ip, $mod['id'], $autotag);
- modLog("Added a note for {$ip} ");
+ markup($autotag);
+ $query = prepare('INSERT INTO ``ip_notes`` VALUES (NULL, :ip, :mod, :time, :body)');
+ $query->bindValue(':ip', $ip);
+ $query->bindValue(':mod', $mod['id']);
+ $query->bindValue(':time', time());
+ $query->bindValue(':body', $autotag);
+ $query->execute() or error(db_error($query));
+ modLog("Added a note for {$ip} ");
}
}
deletePost($post);
@@ -2112,10 +2086,13 @@ function mod_warning_post(Context $ctx, $board, $post, $token = false) {
$autotag .= $body . "\r\n";
$autotag = escape_markup_modifiers($autotag);
markup($autotag);
-
- $note_queries = $ctx->get(IpNoteQueries::class);
- $note_queries->add($ip, $mod['id'], $autotag);
- modLog("Added a note for {$ip} ");
+ $query = prepare('INSERT INTO ``ip_notes`` VALUES (NULL, :ip, :mod, :time, :body)');
+ $query->bindValue(':ip', $ip);
+ $query->bindValue(':mod', $mod['id']);
+ $query->bindValue(':time', time());
+ $query->bindValue(':body', $autotag);
+ $query->execute() or error(db_error($query));
+ modLog("Added a note for {$ip} ");
}
}
}
@@ -2220,7 +2197,7 @@ function mod_edit_post(Context $ctx, $board, $edit_raw_html, $postID) {
}
function mod_delete(Context $ctx, $board, $post) {
- global $config, $mod;
+ global $config;
if (!openBoard($board))
error($config['error']['noboard']);
@@ -2261,10 +2238,13 @@ function mod_delete(Context $ctx, $board, $post) {
$autotag .= $body . "\r\n";
$autotag = escape_markup_modifiers($autotag);
markup($autotag);
-
- $note_queries = $ctx->get(IpNoteQueries::class);
- $note_queries->add($ip, $mod['id'], $autotag);
- modLog("Added a note for {$ip} ");
+ $query = prepare('INSERT INTO ``ip_notes`` VALUES (NULL, :ip, :mod, :time, :body)');
+ $query->bindValue(':ip', $ip);
+ $query->bindValue(':mod', $mod['id']);
+ $query->bindValue(':time', time());
+ $query->bindValue(':body', $autotag);
+ $query->execute() or error(db_error($query));
+ modLog("Added a note for {$ip} ");
}
}
deletePost($post);
@@ -2351,7 +2331,7 @@ function mod_spoiler_image(Context $ctx, $board, $post, $file) {
}
function mod_deletebyip(Context $ctx, $boardName, $post, $global = false) {
- global $config, $board, $mod;
+ global $config, $board;
$global = (bool)$global;
@@ -2429,10 +2409,13 @@ function mod_deletebyip(Context $ctx, $boardName, $post, $global = false) {
$autotag .= $body . "\r\n";
$autotag = escape_markup_modifiers($autotag);
markup($autotag);
-
- $note_queries = $ctx->get(IpNoteQueries::class);
- $note_queries->add($ip, $mod['id'], $autotag);
- modLog("Added a note for {$ip} ");
+ $query2 = prepare('INSERT INTO ``ip_notes`` VALUES (NULL, :ip, :mod, :time, :body)');
+ $query2->bindValue(':ip', $ip);
+ $query2->bindValue(':mod', $mod['id']);
+ $query2->bindValue(':time', time());
+ $query2->bindValue(':body', $autotag);
+ $query2->execute() or error(db_error($query2));
+ modLog("Added a note for {$ip} ");
}
}
@@ -2463,7 +2446,7 @@ function mod_deletebyip(Context $ctx, $boardName, $post, $global = false) {
}
// Record the action
- modLog("Deleted all posts by IP address: $ip ");
+ modLog("Deleted all posts by IP address: $ip ");
// Redirect
header('Location: ?/' . sprintf($config['board_path'], $boardName) . $config['file_index'], true, $config['redirect_http']);
@@ -3000,7 +2983,7 @@ function mod_report_dismiss(Context $ctx, $id, $all = false) {
if ($all) {
$report_queries->deleteByIp($ip);
- modLog("Dismissed all reports by $ip ");
+ modLog("Dismissed all reports by $ip ");
} else {
$report_queries->deleteById($id);
modLog("Dismissed a report for post #{$id}", $board);
diff --git a/install.php b/install.php
index 7663a503..bc3ba7d4 100644
--- a/install.php
+++ b/install.php
@@ -881,7 +881,6 @@ if ($step == 0) {
$config['cookies']['salt'] = substr(base64_encode(sha1(rand())), 0, 30);
$config['secure_trip_salt'] = substr(base64_encode(sha1(rand())), 0, 30);
- $config['secure_password_salt'] = substr(base64_encode(sha1(rand())), 0, 30);
echo Element('page.html', array(
'body' => Element('installer/config.html', array(
diff --git a/js/ajax.js b/js/ajax.js
index 3cb06bf1..af795a57 100644
--- a/js/ajax.js
+++ b/js/ajax.js
@@ -18,25 +18,16 @@ $(window).ready(function() {
// Enable submit button if disabled (cache problem)
$('input[type="submit"]').removeAttr('disabled');
-
+
var setup_form = function($form) {
$form.submit(function() {
if (do_not_ajax)
return true;
-
- // If the captcha is present, halt if it does not have a response.
- if (captchaMode === 'static' || (captchaMode === 'dynamic' && isDynamicCaptchaEnabled())) {
- if (captcha_renderer && postCaptchaId && !captcha_renderer.hasResponse(postCaptchaId)) {
- captcha_renderer.execute(postCaptchaId);
- return false;
- }
- }
-
var form = this;
var submit_txt = $(this).find('input[type="submit"]').val();
if (window.FormData === undefined)
return true;
-
+
var formData = new FormData(this);
formData.append('json_response', '1');
formData.append('post', submit_txt);
@@ -103,15 +94,15 @@ $(window).ready(function() {
setTimeout(function() { $(window).trigger("scroll"); }, 100);
}
});
-
+
highlightReply(post_response.id);
window.location.hash = post_response.id;
$(window).scrollTop($(document).height());
-
+
$(form).find('input[type="submit"]').val(submit_txt);
$(form).find('input[type="submit"]').removeAttr('disabled');
$(form).find('input[name="subject"],input[name="file_url"],\
- textarea[name="body"],input[type="file"],input[name="embed"]').val('').change();
+ textarea[name="body"],input[type="file"]').val('').change();
},
cache: false,
contentType: false,
@@ -123,7 +114,7 @@ $(window).ready(function() {
$(form).find('input[type="submit"]').val(submit_txt);
$(form).find('input[type="submit"]').removeAttr('disabled');
$(form).find('input[name="subject"],input[name="file_url"],\
- textarea[name="body"],input[type="file"],input[name="embed"]').val('').change();
+ textarea[name="body"],input[type="file"]').val('').change();
} else {
alert(_('An unknown error occured when posting!'));
$(form).find('input[type="submit"]').val(submit_txt);
@@ -141,10 +132,10 @@ $(window).ready(function() {
contentType: false,
processData: false
}, 'json');
-
+
$(form).find('input[type="submit"]').val(_('Posting...'));
$(form).find('input[type="submit"]').attr('disabled', true);
-
+
return false;
});
};
diff --git a/js/inline-expanding.js b/js/inline-expanding.js
index c44843b0..41625d2d 100644
--- a/js/inline-expanding.js
+++ b/js/inline-expanding.js
@@ -17,10 +17,6 @@ $(document).ready(function() {
// Default maximum image loads.
const DEFAULT_MAX = 5;
- if (localStorage.inline_expand_fit_height !== 'false') {
- $('').appendTo($('head'));
- }
-
let inline_expand_post = function() {
let link = this.getElementsByTagName('a');
@@ -60,12 +56,12 @@ $(document).ready(function() {
},
add: function(ele) {
ele.deferred = $.Deferred();
- ele.deferred.done(function() {
+ ele.deferred.done(function () {
let $loadstart = $.Deferred();
let thumb = ele.childNodes[0];
let img = ele.childNodes[1];
- let onLoadStart = function(img) {
+ let onLoadStart = function (img) {
if (img.naturalWidth) {
$loadstart.resolve(img, thumb);
} else {
@@ -73,15 +69,15 @@ $(document).ready(function() {
}
};
- $(img).one('load', function() {
- $.when($loadstart).done(function() {
- // once fully loaded, update the waiting queue
+ $(img).one('load', function () {
+ $.when($loadstart).done(function () {
+ // Once fully loaded, update the waiting queue.
--loading;
$(ele).data('imageLoading', 'false');
update();
});
});
- $loadstart.done(function(img, thumb) {
+ $loadstart.done(function (img, thumb) {
thumb.style.display = 'none';
img.style.display = '';
});
@@ -206,8 +202,6 @@ $(document).ready(function() {
Options.extend_tab('general', '' +
_('Number of simultaneous image downloads (0 to disable): ') +
' ');
- Options.extend_tab('general', ' ' + _('Fit expanded images into screen height') + ' ');
-
$('#inline-expand-max input')
.css('width', '50px')
.val(localStorage.inline_expand_max || DEFAULT_MAX)
@@ -218,21 +212,6 @@ $(document).ready(function() {
localStorage.inline_expand_max = val;
});
-
- $('#inline-expand-fit-height input').on('change', function() {
- if (localStorage.inline_expand_fit_height !== 'false') {
- localStorage.inline_expand_fit_height = 'false';
- $('#expand-fit-height-style').remove();
- }
- else {
- localStorage.inline_expand_fit_height = 'true';
- $('').appendTo($('head'));
- }
- });
-
- if (localStorage.inline_expand_fit_height !== 'false') {
- $('#inline-expand-fit-height input').prop('checked', true);
- }
}
if (window.jQuery) {
diff --git a/js/options/general.js b/js/options/general.js
index 6715ae1d..c0652269 100644
--- a/js/options/general.js
+++ b/js/options/general.js
@@ -43,6 +43,9 @@ $(function(){
document.location.reload();
}
});
+
+
+ $("#style-select").detach().css({float:"none","margin-bottom":0}).appendTo(tab.content);
});
}();
diff --git a/js/post-filter.js b/js/post-filter.js
index 78fd35ce..f3f161c6 100644
--- a/js/post-filter.js
+++ b/js/post-filter.js
@@ -237,8 +237,12 @@ if (active_page === 'thread' || active_page === 'index' || active_page === 'cata
var postUid = $ele.find('.poster_id').text();
}
- let postName = (typeof $ele.find('.name').contents()[0] == 'undefined') ? '' : nameSpanToString($ele.find('.name')[0]);
- let postTrip = $ele.find('.trip').text();
+ let postName;
+ let postTrip = '';
+ if (!pageData.forcedAnon) {
+ postName = (typeof $ele.find('.name').contents()[0] == 'undefined') ? '' : nameSpanToString($ele.find('.name')[0]);
+ postTrip = $ele.find('.trip').text();
+ }
/* display logic and bind click handlers
*/
@@ -293,34 +297,39 @@ if (active_page === 'thread' || active_page === 'index' || active_page === 'cata
}
// name
- if (!$ele.data('hiddenByName')) {
+ if (!pageData.forcedAnon && !$ele.data('hiddenByName')) {
$buffer.find('#filter-add-name').click(function () {
addFilter('name', postName, false);
});
$buffer.find('#filter-remove-name').addClass('hidden');
- } else {
+ } else if (!pageData.forcedAnon) {
$buffer.find('#filter-remove-name').click(function () {
removeFilter('name', postName, false);
});
+ $buffer.find('#filter-add-name').addClass('hidden');
+ } else {
+ // board has forced anon
+ $buffer.find('#filter-remove-name').addClass('hidden');
$buffer.find('#filter-add-name').addClass('hidden');
}
// tripcode
- if (!$ele.data('hiddenByTrip') && postTrip !== '') {
+ if (!pageData.forcedAnon && !$ele.data('hiddenByTrip') && postTrip !== '') {
$buffer.find('#filter-add-trip').click(function () {
addFilter('trip', postTrip, false);
});
$buffer.find('#filter-remove-trip').addClass('hidden');
- } else if (postTrip !== '') {
+ } else if (!pageData.forcedAnon && postTrip !== '') {
$buffer.find('#filter-remove-trip').click(function () {
removeFilter('trip', postTrip, false);
});
$buffer.find('#filter-add-trip').addClass('hidden');
} else {
+ // board has forced anon
$buffer.find('#filter-remove-trip').addClass('hidden');
$buffer.find('#filter-add-trip').addClass('hidden');
}
@@ -382,6 +391,7 @@ if (active_page === 'thread' || active_page === 'index' || active_page === 'cata
var localList = pageData.localList;
var noReplyList = pageData.noReplyList;
var hasUID = pageData.hasUID;
+ var forcedAnon = pageData.forcedAnon;
var hasTrip = ($post.find('.trip').length > 0);
var hasSub = ($post.find('.subject').length > 0);
@@ -422,8 +432,9 @@ if (active_page === 'thread' || active_page === 'index' || active_page === 'cata
}
// matches generalFilter
- name = (typeof $post.find('.name').contents()[0] == 'undefined') ? '' : nameSpanToString($post.find('.name')[0]);
- if (hasTrip)
+ if (!forcedAnon)
+ name = (typeof $post.find('.name').contents()[0] == 'undefined') ? '' : nameSpanToString($post.find('.name')[0]);
+ if (!forcedAnon && hasTrip)
trip = $post.find('.trip').text();
if (hasSub)
subject = $post.find('.subject').text();
@@ -444,13 +455,13 @@ if (active_page === 'thread' || active_page === 'index' || active_page === 'cata
pattern = new RegExp(rule.value);
switch (rule.type) {
case 'name':
- if (pattern.test(name)) {
+ if (!forcedAnon && pattern.test(name)) {
$post.data('hiddenByName', true);
hide(post);
}
break;
case 'trip':
- if (hasTrip && pattern.test(trip)) {
+ if (!forcedAnon && hasTrip && pattern.test(trip)) {
$post.data('hiddenByTrip', true);
hide(post);
}
@@ -477,13 +488,13 @@ if (active_page === 'thread' || active_page === 'index' || active_page === 'cata
} else {
switch (rule.type) {
case 'name':
- if (rule.value == name) {
+ if (!forcedAnon && rule.value == name) {
$post.data('hiddenByName', true);
hide(post);
}
break;
case 'trip':
- if (hasTrip && rule.value == trip) {
+ if (!forcedAnon && hasTrip && rule.value == trip) {
$post.data('hiddenByTrip', true);
hide(post);
}
@@ -816,7 +827,8 @@ if (active_page === 'thread' || active_page === 'index' || active_page === 'cata
boardId: board_name, // get the id from the global variable
localList: [], // all the blacklisted post IDs or UIDs that apply to the current page
noReplyList: [], // any posts that replies to the contents of this list shall be hidden
- hasUID: (document.getElementsByClassName('poster_id').length > 0)
+ hasUID: (document.getElementsByClassName('poster_id').length > 0),
+ forcedAnon: ($('th:contains(Name)').length === 0) // tests by looking for the Name label on the reply form
};
initStyle();
diff --git a/js/post-menu.js b/js/post-menu.js
index c2155c00..79cfd868 100644
--- a/js/post-menu.js
+++ b/js/post-menu.js
@@ -104,10 +104,8 @@ function buildMenu(e) {
function addButton(post) {
var $ele = $(post);
- // Use unicode code with ascii variant selector
- // https://stackoverflow.com/questions/37906969/how-to-prevent-ios-from-converting-ascii-into-emoji
$ele.find('input.delete').after(
- $('', {href: '#', class: 'post-btn', title: 'Post menu'}).text('\u{25B6}\u{fe0e}')
+ $(' ', {href: '#', class: 'post-btn', title: 'Post menu'}).text('►')
);
}
diff --git a/js/show-backlinks.js b/js/show-backlinks.js
index 607c24ab..5924124e 100644
--- a/js/show-backlinks.js
+++ b/js/show-backlinks.js
@@ -15,7 +15,7 @@
$(document).ready(function() {
let showBackLinks = function() {
- let replyId = $(this).attr('id').split('_')[1];
+ let replyId = $(this).attr('id').replace(/^reply_/, '');
$(this).find('div.body a:not([rel="nofollow"])').each(function() {
let id = $(this).text().match(/^>>(\d+)$/);
@@ -25,15 +25,13 @@ $(document).ready(function() {
return;
}
- let post = $('#reply_' + id + ', #op_' + id);
- if (post.length == 0) {
+ let post = $('#reply_' + id);
+ if(post.length == 0)
return;
- }
let mentioned = post.find('.head div.mentioned');
if (mentioned.length === 0) {
- // The op has two "head"s divs, use the second.
- mentioned = $('
').prependTo(post.find('.head').last());
+ mentioned = $('
').prependTo(post.find('.head'));
}
if (mentioned.find('a.mentioned-' + replyId).length !== 0) {
@@ -50,13 +48,13 @@ $(document).ready(function() {
});
};
- $('div.post').each(showBackLinks);
+ $('div.post.reply').each(showBackLinks);
$(document).on('new_post', function(e, post) {
- if ($(post).hasClass('reply') || $(post).hasClass('op')) {
+ if ($(post).hasClass('reply')) {
showBackLinks.call(post);
} else {
- $(post).find('div.post').each(showBackLinks);
+ $(post).find('div.post.reply').each(showBackLinks);
}
});
});
diff --git a/js/style-select-simple.js b/js/style-select-simple.js
deleted file mode 100644
index 8b59fa0a..00000000
--- a/js/style-select-simple.js
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * style-select-simple.js
- *
- * Changes the stylesheet chooser links to a
- *
- * Released under the MIT license
- * Copyright (c) 2025 Zankaria Auxa
- *
- * Usage:
- * $config['additional_javascript'][] = 'js/jquery.min.js';
- * // $config['additional_javascript'][] = 'js/style-select.js'; // Conflicts with this file.
- * $config['additional_javascript'][] = 'js/style-select-simple.js';
- */
-
-$(document).ready(function() {
- let newElement = document.createElement('div');
- newElement.className = 'styles';
-
- // styles is defined in main.js.
- for (styleName in styles) {
- if (styleName) {
- let style = document.createElement('a');
- style.innerHTML = '[' + styleName + ']';
- style.onclick = function() {
- changeStyle(this.innerHTML.substring(1, this.innerHTML.length - 1), this);
- };
- if (styleName == selectedstyle) {
- style.className = 'selected';
- }
- style.href = 'javascript:void(0);';
- newElement.appendChild(style);
- }
- }
-
- document.getElementById('bottom-hud').before(newElement);
-});
diff --git a/js/style-select.js b/js/style-select.js
index 16bbae72..485da735 100644
--- a/js/style-select.js
+++ b/js/style-select.js
@@ -6,53 +6,48 @@
*
* Released under the MIT license
* Copyright (c) 2013 Michael Save
- * Copyright (c) 2013-2014 Marcin Łabanowski
+ * Copyright (c) 2013-2014 Marcin Łabanowski
*
* Usage:
* $config['additional_javascript'][] = 'js/jquery.min.js';
* $config['additional_javascript'][] = 'js/style-select.js';
+ *
*/
$(document).ready(function() {
- let pages = $('div.pages');
- let stylesSelect = $(' ').css({float:"none"});
- let options = [];
+ var stylesDiv = $('div.styles');
+ var pages = $('div.pages');
+ var stylesSelect = $(' ').css({float:"none"});
+ var options = [];
+
+ var i = 1;
+ stylesDiv.children().each(function() {
+ var name = this.innerHTML.replace(/(^\[|\]$)/g, '');
+ var opt = $(' ')
+ .html(name)
+ .val(i);
+ if ($(this).hasClass('selected'))
+ opt.attr('selected', true);
+ options.push ([name.toUpperCase (), opt]);
+ $(this).attr('id', 'style-select-' + i);
+ i++;
+ });
- let i = 1;
- for (styleName in styles) {
- if (styleName) {
- let opt = $(' ')
- .html(styleName)
- .val(i);
- if (selectedstyle == styleName) {
- opt.attr('selected', true);
- }
- opt.attr('id', 'style-select-' + i);
- options.push([styleName.toUpperCase (), opt]);
- i++;
- }
- }
-
- options.sort((a, b) => {
+ options.sort ((a, b) => {
const keya = a [0];
const keyb = b [0];
- if (keya < keyb) {
- return -1;
- }
- if (keya > keyb) {
- return 1;
- }
+ if (keya < keyb) { return -1; }
+ if (keya > keyb) { return 1; }
return 0;
- }).forEach(([key, opt]) => {
+ }).forEach (([key, opt]) => {
stylesSelect.append(opt);
});
-
+
stylesSelect.change(function() {
- let sel = $(this).find(":selected")[0];
- let styleName = sel.innerHTML;
- changeStyle(styleName, sel);
+ $('#style-select-' + $(this).val()).click();
});
-
+
+ stylesDiv.hide()
pages.after(
$('
')
.append(_('Select theme: '), stylesSelect)
diff --git a/js/youtube.js b/js/youtube.js
index 4a5a5afe..4c31ed09 100644
--- a/js/youtube.js
+++ b/js/youtube.js
@@ -1,41 +1,41 @@
/*
- * Don't load the 3rd party embedded content player unless the image is clicked.
- * This increases performance issues when many videos are embedded on the same page.
- *
- * Released under the MIT license
- * Copyright (c) 2013 Michael Save
- * Copyright (c) 2013-2014 Marcin Łabanowski
- * Copyright (c) 2025 Zankaria Auxa
- *
- * Usage:
- * $config['embedding'] = array();
- * $config['embedding'][0] = array(
- * '/^https?:\/\/(\w+\.)?(?:youtube\.com\/watch\?v=|youtu\.be\/)([a-zA-Z0-9\-_]{10,11})(&.+)?$/i',
- * $config['youtube_js_html']);
- * $config['additional_javascript'][] = 'js/jquery.min.js';
- * $config['additional_javascript'][] = 'js/youtube.js';
- */
+* youtube
+* https://github.com/savetheinternet/Tinyboard/blob/master/js/youtube.js
+*
+* Don't load the YouTube player unless the video image is clicked.
+* This increases performance issues when many videos are embedded on the same page.
+* Currently only compatiable with YouTube.
+*
+* Proof of concept.
+*
+* Released under the MIT license
+* Copyright (c) 2013 Michael Save
+* Copyright (c) 2013-2014 Marcin Łabanowski
+*
+* Usage:
+* $config['embedding'] = array();
+* $config['embedding'][0] = array(
+* '/^https?:\/\/(\w+\.)?(?:youtube\.com\/watch\?v=|youtu\.be\/)([a-zA-Z0-9\-_]{10,11})(&.+)?$/i',
+* $config['youtube_js_html']);
+* $config['additional_javascript'][] = 'js/jquery.min.js';
+* $config['additional_javascript'][] = 'js/youtube.js';
+*
+*/
-$(document).ready(function() {
- const ON = '[Remove]';
- const YOUTUBE = 'www.youtube.com';
-
- function makeEmbedNode(embedHost, videoId, width, height) {
- return $(``);
- }
-
- // Adds Options panel item.
+$(document).ready(function(){
+ // Adds Options panel item
if (typeof localStorage.youtube_embed_proxy === 'undefined') {
- localStorage.youtube_embed_proxy = 'incogtube.com'; // Default value.
+ if (location.hostname.includes(".onion")){
+ localStorage.youtube_embed_proxy = 'tuberyps2pn6dor6h47brof3w2asmauahhk4ei42krugybzzzo55klad.onion';
+ } else {
+ localStorage.youtube_embed_proxy = 'incogtube.com'; //default value
+ }
}
if (window.Options && Options.get_tab('general')) {
- Options.extend_tab("general",
- "" + _("Media Proxy (requires refresh)") + " "
- + '' + _('YouTube embed proxy url ')
- + ' '
- + ' ');
+ Options.extend_tab("general", ""+_("Media Proxy (requires refresh)")+" "
+ + ('' + _('YouTube embed proxy url ')+' ')
+ + ' ');
$('#youtube-embed-proxy-url>input').val(localStorage.youtube_embed_proxy);
$('#youtube-embed-proxy-url>input').on('input', function() {
@@ -43,65 +43,51 @@ $(document).ready(function() {
});
}
- const proxy = localStorage.youtube_embed_proxy;
+ const ON = "[Remove]";
+ const OFF = "[Embed]";
+ const YOUTUBE = 'www.youtube.com';
+ const PROXY = localStorage.youtube_embed_proxy;
+ function addEmbedButton(index, videoNode) {
+ videoNode = $(videoNode);
+ var contents = videoNode.contents();
+ var videoId = videoNode.data('video');
+ var span = $("[Embed] ");
+ var spanProxy = $("[Proxy] ");
- function addEmbedButton(_i, node) {
- node = $(node);
- const contents = node.contents();
- const embedUrl = node.data('video-id');
- const embedWidth = node.data('iframe-width');
- const embedHeight = node.data('iframe-height');
- const span = $('[Embed] ');
- const spanProxy = $("[Proxy] ");
-
- let iframeDefault = null;
- let iframeProxy = null;
-
- node.click(function(e) {
+ var makeEmbedNode = function(embedHost) {
+ return $('');
+ }
+ var defaultEmbed = makeEmbedNode(location.hostname.includes(".onion") ? PROXY : YOUTUBE);
+ var proxyEmbed = makeEmbedNode(PROXY);
+ videoNode.click(function(e) {
e.preventDefault();
- if (span.text() == ON) {
- contents.css('display', '');
- spanProxy.css('display', '');
-
- if (iframeDefault !== null) {
- iframeDefault.remove();
- }
- if (iframeProxy !== null) {
- iframeProxy.remove();
- }
-
- span.text('[Embed]');
+ if (span.text() == ON){
+ videoNode.append(spanProxy);
+ videoNode.append(contents);
+ defaultEmbed.remove();
+ proxyEmbed.remove();
+ span.text(OFF);
} else {
- let useProxy = e.target == spanProxy[0];
-
- // Lazily create the iframes.
- if (useProxy) {
- if (iframeProxy === null) {
- iframeProxy = makeEmbedNode(proxy, embedUrl, embedWidth, embedHeight);
- }
- node.prepend(iframeProxy);
- } else {
- if (iframeDefault === null) {
- iframeDefault = makeEmbedNode(YOUTUBE, embedUrl, embedWidth, embedHeight);
- }
- node.prepend(iframeDefault);
- }
-
- contents.css('display', 'none');
- spanProxy.css('display', 'none');
+ contents.detach();
span.text(ON);
+ spanProxy.remove();
+ videoNode.append(e.target == spanProxy[0] ? proxyEmbed : defaultEmbed);
}
});
- node.append(span);
- node.append(spanProxy);
+ videoNode.append(span);
+ videoNode.append(spanProxy);
}
$('div.video-container', document).each(addEmbedButton);
+
- // Allow to work with auto-reload.js, etc.
+ // allow to work with auto-reload.js, etc.
$(document).on('new_post', function(e, post) {
$('div.video-container', post).each(addEmbedButton);
});
});
+
diff --git a/mod.php b/mod.php
index f08a465b..bb260251 100644
--- a/mod.php
+++ b/mod.php
@@ -68,15 +68,9 @@ $pages = [
'/reports/(\d+)/dismiss(all)?' => 'secure report_dismiss', // dismiss a report
'/IP/([\w.:]+)' => 'secure_POST ip', // view ip address
- '/IP/([\w.:]+)/cursor/([\w|-|_]+)' => 'secure_POST ip', // view ip address
+ '/IP/([\w.:]+)/cursor/([\w|-|_|.]+)' => 'secure_POST ip', // view ip address
'/IP/([\w.:]+)/remove_note/(\d+)' => 'secure ip_remove_note', // remove note from ip address
- '/user_posts/ip/([\w.:]+)' => 'secure_POST user_posts_by_ip', // view user posts by ip address
- '/user_posts/ip/([\w.:]+)/cursor/([\w|-|_]+)' => 'secure_POST user_posts_by_ip', // remove note from ip address
-
- '/user_posts/passwd/(\w+)' => 'secure_POST user_posts_by_passwd', // view user posts by ip address
- '/user_posts/passwd/(\w+)/cursor/([\w|-|_]+)' => 'secure_POST user_posts_by_passwd', // remove note from ip address
-
'/ban' => 'secure_POST ban', // new ban
'/bans' => 'secure_POST bans', // ban list
'/bans.json' => 'secure bans_json', // ban list JSON
diff --git a/post.php b/post.php
index 5483600d..642e8057 100644
--- a/post.php
+++ b/post.php
@@ -5,7 +5,6 @@
use Vichan\Context;
use Vichan\Data\ReportQueries;
-use Vichan\Data\Driver\LogDriver;
require_once 'inc/bootstrap.php';
@@ -222,7 +221,7 @@ function send_matrix_report(
$end = strlen($post['body_nomarkup']) > $max_msg_len ? ' [...]' : '';
$post_content = mb_substr($post['body_nomarkup'], 0, $max_msg_len) . $end;
- $text_body = $reported_post_url . ($post['thread'] ? "#$id" : '') . " \nReason:\n" . $report_reason . " \nPost:\n" . $post_content;
+ $text_body = $reported_post_url . ($post['thread'] ? "#$post_id" : '') . " \nReason:\n" . $report_reason . " \nPost:\n" . $post_content;
$random_transaction_id = mt_rand();
$json_body = json_encode([
@@ -355,7 +354,7 @@ function db_select_ban_appeals($ban_id)
$dropped_post = false;
-function handle_nntpchan(Context $ctx)
+function handle_nntpchan()
{
global $config;
if ($_SERVER['REMOTE_ADDR'] != $config['nntpchan']['trusted_peer']) {
@@ -434,7 +433,7 @@ function handle_nntpchan(Context $ctx)
if ($ct == 'text/plain') {
$content = file_get_contents("php://input");
} elseif ($ct == 'multipart/mixed' || $ct == 'multipart/form-data') {
- $ctx->get(LogDriver::class)->log(LogDriver::DEBUG, 'MM: Files: ' . print_r($GLOBALS, true));
+ _syslog(LOG_INFO, "MM: Files: " . print_r($GLOBALS, true)); // Debug
$content = '';
@@ -531,12 +530,10 @@ function handle_delete(Context $ctx)
$password = &$_POST['password'];
- if (empty($password)) {
+ if ($password == '') {
error($config['error']['invalidpassword']);
}
- $password = hashPassword($_POST['password']);
-
$delete = [];
foreach ($_POST as $post => $value) {
if (preg_match('/^delete_(\d+)$/', $post, $m)) {
@@ -611,8 +608,8 @@ function handle_delete(Context $ctx)
modLog("User at $ip deleted his own post #$id");
}
- $ctx->get(LogDriver::class)->log(
- LogDriver::INFO,
+ _syslog(
+ LOG_INFO,
'Deleted post: ' .
'/' . $board['dir'] . $config['dir']['res'] . sprintf($config['file_page'], $post['thread'] ? $post['thread'] : $id) . ($post['thread'] ? '#' . $id : '')
);
@@ -700,7 +697,9 @@ function handle_report(Context $ctx)
foreach ($report as $id) {
$post = db_select_post_minimal($board['uri'], $id);
if ($post === false) {
- $ctx->get(LogDriver::class)->log(LogDriver::INFO, "Failed to report non-existing post #{$id} in {$board['dir']}");
+ if ($config['syslog']) {
+ _syslog(LOG_INFO, "Failed to report non-existing post #{$id} in {$board['dir']}");
+ }
error($config['error']['nopost']);
}
@@ -715,12 +714,13 @@ function handle_report(Context $ctx)
error($error);
}
- $ctx->get(LogDriver::class)->log(
- LogDriver::INFO,
- 'Reported post: ' .
- '/' . $board['dir'] . $config['dir']['res'] . link_for($post) . ($post['thread'] ? '#' . $id : '') .
- ' for "' . $reason . '"'
- );
+ if ($config['syslog'])
+ _syslog(
+ LOG_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);
@@ -1009,16 +1009,11 @@ function handle_post(Context $ctx)
}
}
- // We must do this check now before the passowrd is hashed and overwritten.
- if (\mb_strlen($_POST['password']) > 20) {
- error(\sprintf($config['error']['toolong'], 'password'));
- }
-
$post['name'] = $_POST['name'] != '' ? $_POST['name'] : $config['anonymous'];
$post['subject'] = $_POST['subject'];
$post['email'] = str_replace(' ', '%20', htmlspecialchars($_POST['email']));
$post['body'] = $_POST['body'];
- $post['password'] = hashPassword($_POST['password']);
+ $post['password'] = $_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) {
@@ -1209,6 +1204,9 @@ function handle_post(Context $ctx)
error($config['error']['toolong_body']);
}
}
+ if (mb_strlen($post['password']) > 20) {
+ error(sprintf($config['error']['toolong'], 'password'));
+ }
}
wordfilters($post['body']);
@@ -1313,7 +1311,7 @@ function handle_post(Context $ctx)
if (!hasPermission($config['mod']['bypass_filters'], $board['uri']) && !$dropped_post) {
require_once 'inc/filters.php';
- do_filters($ctx, $post);
+ do_filters($post);
}
if ($post['has_file']) {
@@ -1402,13 +1400,13 @@ function handle_post(Context $ctx)
$file['thumbwidth'] = $size[0];
$file['thumbheight'] = $size[1];
} elseif (
- (($config['strip_exif'] && isset($file['exif_stripped']) && $file['exif_stripped']) || !$config['strip_exif']) &&
+ $config['minimum_copy_resize'] &&
$image->size->width <= $config['thumb_width'] &&
$image->size->height <= $config['thumb_height'] &&
$file['extension'] == ($config['thumb_ext'] ? $config['thumb_ext'] : $file['extension'])
) {
// Copy, because there's nothing to resize
- copy($file['tmp_name'], $file['thumb']);
+ coopy($file['tmp_name'], $file['thumb']);
$file['thumbwidth'] = $image->size->width;
$file['thumbheight'] = $image->size->height;
@@ -1551,6 +1549,35 @@ function handle_post(Context $ctx)
}
}
+ if ($config['tesseract_ocr'] && $file['thumb'] != 'file') {
+ // Let's OCR it!
+ $fname = $file['tmp_name'];
+
+ if ($file['height'] > 500 || $file['width'] > 500) {
+ $fname = $file['thumb'];
+ }
+
+ if ($fname == 'spoiler') {
+ // We don't have that much CPU time, do we?
+ } else {
+ $tmpname = __DIR__ . "/tmp/tesseract/" . rand(0, 10000000);
+
+ // Preprocess command is an ImageMagick b/w quantization
+ $error = shell_exec_error(sprintf($config['tesseract_preprocess_command'], escapeshellarg($fname)) . " | " .
+ 'tesseract stdin ' . escapeshellarg($tmpname) . ' ' . $config['tesseract_params']);
+ $tmpname .= ".txt";
+
+ $value = @file_get_contents($tmpname);
+ @unlink($tmpname);
+
+ if ($value && trim($value)) {
+ // This one has an effect, that the body is appended to a post body. So you can write a correct
+ // spamfilter.
+ $post['body_nomarkup'] .= "" . htmlspecialchars($value) . " ";
+ }
+ }
+ }
+
if (!isset($dont_copy_file) || !$dont_copy_file) {
if (isset($file['file_tmp'])) {
if (!@rename($file['tmp_name'], $file['file'])) {
@@ -1598,6 +1625,11 @@ function handle_post(Context $ctx)
}
}
+ // Do filters again if OCRing
+ if ($config['tesseract_ocr'] && !hasPermission($config['mod']['bypass_filters'], $board['uri']) && !$dropped_post) {
+ do_filters($post);
+ }
+
if (!hasPermission($config['mod']['postunoriginal'], $board['uri']) && $config['robot_enable'] && checkRobot($post['body_nomarkup']) && !$dropped_post) {
undoImage($post);
if ($config['robot_mute']) {
@@ -1747,10 +1779,10 @@ function handle_post(Context $ctx)
buildThread($post['op'] ? $id : $post['thread']);
- $ctx->get(LogDriver::class)->log(
- LogDriver::INFO,
- 'New post: /' . $board['dir'] . $config['dir']['res'] . link_for($post) . (!$post['op'] ? '#' . $id : '')
- );
+ if ($config['syslog']) {
+ _syslog(LOG_INFO, 'New post: /' . $board['dir'] . $config['dir']['res'] .
+ link_for($post) . (!$post['op'] ? '#' . $id : ''));
+ }
if (!$post['mod']) {
header('X-Associated-Content: "' . $redirect . '"');
@@ -1843,17 +1875,17 @@ function handle_appeal(Context $ctx)
displayBan($ban);
}
-$ctx = Vichan\build_context($config);
-
// Is it a post coming from NNTP? Let's extract it and pretend it's a normal post.
if (isset($_GET['Newsgroups'])) {
if ($config['nntpchan']['enabled']) {
- handle_nntpchan($ctx);
+ 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'])) {
diff --git a/static/banners/deny-defend-depose-opt.webp b/static/banners/deny-defend-depose-opt.webp
deleted file mode 100644
index 584a4ae2..00000000
Binary files a/static/banners/deny-defend-depose-opt.webp and /dev/null differ
diff --git a/static/flags/alunya.png b/static/flags/alunya.png
deleted file mode 100644
index 799fd509..00000000
Binary files a/static/flags/alunya.png and /dev/null differ
diff --git a/stylesheets/500px.css b/stylesheets/500px.css
index 5f733005..89b1fa64 100644
--- a/stylesheets/500px.css
+++ b/stylesheets/500px.css
@@ -152,11 +152,6 @@ div.post.reply.highlighted
box-shadow: 3px 5px #5c8c8e;
margin-left: auto;
margin-right: auto;
-
- @media (max-width: 48em) {
- border-left-style: none;
- border-right-style: none;
- }
}
div.post.reply div.body a:link, div.post.reply div.body a:visited
{
diff --git a/stylesheets/8ch.cyber.css b/stylesheets/8ch.cyber.css
index 330eadd3..c94c50e8 100644
--- a/stylesheets/8ch.cyber.css
+++ b/stylesheets/8ch.cyber.css
@@ -219,11 +219,6 @@ background-color: #2b2b2b;
background-color: #2b2b2b;
border: solid 1px #93e0e3;
box-shadow: 3px 5px #93e0e3;
-
- @media (max-width: 48em) {
- border-left-style: none;
- border-right-style: none;
- }
}
/*dont touch this*/
diff --git a/stylesheets/beta.css b/stylesheets/beta.css
index 6a2c771e..e32353cc 100644
--- a/stylesheets/beta.css
+++ b/stylesheets/beta.css
@@ -161,11 +161,6 @@ div.post.reply.highlighted
box-shadow: 3px 5px #5c8c8e;
margin-left: auto;
margin-right: auto;
-
- @media (max-width: 48em) {
- border-left-style: none;
- border-right-style: none;
- }
}
div.post.reply div.body a:link, div.post.reply div.body a:visited
{
diff --git a/stylesheets/bunker_like.css b/stylesheets/bunker_like.css
index 507003ce..a69591a1 100644
--- a/stylesheets/bunker_like.css
+++ b/stylesheets/bunker_like.css
@@ -120,11 +120,6 @@ div.post.reply.highlighted {
background: rgba(59, 22, 43, 0.4);
border: 1px solid #117743;
border-radius: 5px;
-
- @media (max-width: 48em) {
- border-left-style: none;
- border-right-style: none;
- }
}
/* POST CONTENT */
diff --git a/stylesheets/cyberpunk.css b/stylesheets/cyberpunk.css
index 15b68ed5..35fdbbb7 100644
--- a/stylesheets/cyberpunk.css
+++ b/stylesheets/cyberpunk.css
@@ -1,7 +1,7 @@
/*cyberpunk mod of ferus by kalyx
B332E6 = dark purp
-33cccc = teal
+33cccc = teal
00ff00 = green
FF46D2 = pink
dark blue = 00080C
@@ -37,7 +37,7 @@ div.boardlist{
background-color: #0C0001;
}
-@font-face
+@font-face
{
font-family: 'lain';
src: url('./fonts/nrdyyh.woff') format('woff'),
@@ -84,7 +84,7 @@ a:link, a:visited, p.intro a.email span.name
-ms-transition: 0.15s text-shadow, 0.15s color;
transition: 0.15s text-shadow, 0.15s color;
}
-input[type="text"], textarea
+input[type="text"], textarea
{
-moz-transition: 0.15s border-color;
-webkit-transition: 0.15s border-color;
@@ -93,7 +93,7 @@ input[type="text"], textarea
-ms-transition: 0.15s border-color;
transition: 0.15s border-color;
}
-input[type="submit"]
+input[type="submit"]
{
-moz-transition: 0.15s border-color, 0.15s background-color, 0.15s color;
-webkit-transition: 0.15s border-color, 0.15s background-color, 0.15s color;
@@ -117,7 +117,7 @@ a.post_no {
color: #33cccc;
text-decoration: none;
}
-span.quote
+span.quote
{
color:#00ff00;
}
@@ -136,11 +136,6 @@ div.post.reply {
div.post.reply.highlighted {
background: transparent;
border: #1A6666 2px solid;
-
- @media (max-width: 48em) {
- border-left-style: none;
- border-right-style: none;
- }
}
div.post.reply div.body a:link, div.post.reply div.body a:visited {
color: #646464;
diff --git a/stylesheets/cyberpunk2.css b/stylesheets/cyberpunk2.css
index 4754a882..2e907966 100644
--- a/stylesheets/cyberpunk2.css
+++ b/stylesheets/cyberpunk2.css
@@ -1,7 +1,7 @@
/*cyberpunk mod of ferus by kalyx
B332E6 = dark purp
-33cccc = teal
+33cccc = teal
00ff00 = green
FF46D2 = pink
dark blue = 00080C
@@ -14,7 +14,7 @@ body {
font-size: 11px;
/*text-shadow: 0px 0px 3px #FF46D2;*/
}
-@font-face
+@font-face
{
font-family: 'lain';
src: url('./fonts/nrdyyh.woff') format('woff'),
@@ -61,7 +61,7 @@ a:link, a:visited, p.intro a.email span.name
-ms-transition: 0.15s text-shadow, 0.15s color;
transition: 0.15s text-shadow, 0.15s color;
}
-input[type="text"], textarea
+input[type="text"], textarea
{
-moz-transition: 0.15s border-color;
-webkit-transition: 0.15s border-color;
@@ -70,7 +70,7 @@ input[type="text"], textarea
-ms-transition: 0.15s border-color;
transition: 0.15s border-color;
}
-input[type="submit"]
+input[type="submit"]
{
-moz-transition: 0.15s border-color, 0.15s background-color, 0.15s color;
-webkit-transition: 0.15s border-color, 0.15s background-color, 0.15s color;
@@ -94,7 +94,7 @@ a.post_no {
color: #33cccc;
text-decoration: none;
}
-span.quote
+span.quote
{
color:#00ff00;
}
@@ -113,11 +113,6 @@ div.post.reply {
div.post.reply.highlighted {
background: transparent;
border: #33cccc 2px solid;
-
- @media (max-width: 48em) {
- border-left-style: none;
- border-right-style: none;
- }
}
div.post.reply div.body a:link, div.post.reply div.body a:visited {
color: #646464;
diff --git a/stylesheets/dark.css b/stylesheets/dark.css
index 9f3df691..dd7e9bfd 100644
--- a/stylesheets/dark.css
+++ b/stylesheets/dark.css
@@ -63,11 +63,6 @@ div.post.reply {
div.post.reply.highlighted {
background: #555;
border: transparent 1px solid;
-
- @media (max-width: 48em) {
- border-left-style: none;
- border-right-style: none;
- }
}
div.post.reply div.body a:link, div.post.reply div.body a:visited {
color: #CCCCCC;
diff --git a/stylesheets/dark_red.css b/stylesheets/dark_red.css
index 4ffe4eeb..8593ead9 100644
--- a/stylesheets/dark_red.css
+++ b/stylesheets/dark_red.css
@@ -5,7 +5,7 @@
/*dark.css has been prepended (2021-11-11) instead of @import'd for performance*/
body {
background: #1E1E1E;
- color: #C0C0C0;
+ color: #A7A7A7;
font-family: Verdana, sans-serif;
font-size: 14px;
}
@@ -31,28 +31,26 @@ div.title p {
font-size: 10px;
}
a, a:link, a:visited, .intro a.email span.name {
- color: #EEE;
+ color: #CCCCCC;
text-decoration: none;
- font-family: Verdana, sans-serif;
+ font-family: sans-serif;
}
a:link:hover, a:visited:hover {
color: #fff;
- font-family: Verdana, sans-serif;
+ font-family: sans-serif;
text-decoration: none;
}
a.post_no {
+ color: #AAAAAA;
text-decoration: none;
}
a.post_no:hover {
color: #32DD72 !important;
text-decoration: underline overline;
}
-.intro a.post_no {
- color: #EEE;
-}
div.post.reply {
background: #333333;
- border: #4f4f4f 1px solid;
+ border: #555555 1px solid;
@media (max-width: 48em) {
border-left-style: none;
@@ -60,26 +58,21 @@ div.post.reply {
}
}
div.post.reply.highlighted {
- background: #4f4f4f;
+ background: #555;
border: transparent 1px solid;
-
- @media (max-width: 48em) {
- border-left-style: none;
- border-right-style: none;
- }
}
div.post.reply div.body a:link, div.post.reply div.body a:visited {
- color: #EEE;
+ color: #CCCCCC;
}
div.post.reply div.body a:link:hover, div.post.reply div.body a:visited:hover {
color: #32DD72;
}
div.post.inline {
- border: #4f4f4f 1px solid;
+ border: #555555 1px solid;
}
.intro span.subject {
font-size: 12px;
- font-family: Verdana, sans-serif;
+ font-family: sans-serif;
color: #446655;
font-weight: 800;
}
@@ -96,16 +89,16 @@ div.post.inline {
}
input[type="text"], textarea, select {
background: #333333;
- color: #EEE;
+ color: #CCCCCC;
border: #666666 1px solid;
padding-left: 5px;
padding-right: -5px;
- font-family: Verdana, sans-serif;
+ font-family: sans-serif;
font-size: 10pt;
}
input[type="password"] {
background: #333333;
- color: #EEE;
+ color: #CCCCCC;
border: #666666 1px solid;
}
form table tr th {
@@ -133,10 +126,10 @@ div.banner a {
input[type="submit"] {
background: #333333;
border: #888888 1px solid;
- color: #EEE;
+ color: #CCCCCC;
}
input[type="submit"]:hover {
- background: #4f4f4f;
+ background: #555555;
border: #888888 1px solid;
color: #32DD72;
}
@@ -151,7 +144,7 @@ span.trip {
}
div.pages {
background: #1E1E1E;
- font-family: Verdana, sans-serif;
+ font-family: sans-serif;
}
.bar.bottom {
bottom: 0px;
@@ -159,7 +152,7 @@ div.pages {
background-color: #1E1E1E;
}
div.pages a.selected {
- color: #EEE;
+ color: #CCCCCC;
}
hr {
height: 1px;
@@ -167,7 +160,7 @@ hr {
}
div.boardlist {
text-align: center;
- color: #C0C0C0;
+ color: #A7A7A7;
}
div.ban {
background-color: transparent;
@@ -188,7 +181,7 @@ div.boardlist:not(.bottom) {
}
.desktop-style div.boardlist:not(.bottom) {
text-shadow: black 1px 1px 1px, black -1px -1px 1px, black -1px 1px 1px, black 1px -1px 1px;
- color: #C0C0C0;
+ color: #A7A7A7;
background-color: #1E1E1E;
}
div.report {
@@ -211,7 +204,7 @@ div.report {
}
.box {
background: #333333;
- border-color: #4f4f4f;
+ border-color: #555555;
color: #C5C8C6;
border-radius: 10px;
}
@@ -221,7 +214,7 @@ div.report {
}
table thead th {
background: #333333;
- border-color: #4f4f4f;
+ border-color: #555555;
color: #C5C8C6;
border-radius: 4px;
}
@@ -229,11 +222,11 @@ table tbody tr:nth-of-type( even ) {
background-color: #333333;
}
table.board-list-table .board-uri .board-sfw {
- color: #EEE;
+ color: #CCCCCC;
}
tbody.board-list-omitted td {
background: #333333;
- border-color: #4f4f4f;
+ border-color: #555555;
}
table.board-list-table .board-tags .board-cell:hover {
background: #1e1e1e;
diff --git a/stylesheets/dark_spook.css b/stylesheets/dark_spook.css
index 2ca55a20..5e9bf837 100644
--- a/stylesheets/dark_spook.css
+++ b/stylesheets/dark_spook.css
@@ -61,11 +61,6 @@ div.post.reply {
div.post.reply.highlighted {
background: #555;
border: transparent 1px solid;
-
- @media (max-width: 48em) {
- border-left-style: none;
- border-right-style: none;
- }
}
div.post.reply div.body a:link, div.post.reply div.body a:visited {
diff --git a/stylesheets/darkroot_garden.css b/stylesheets/darkroot_garden.css
index cd27da0a..22ee517d 100644
--- a/stylesheets/darkroot_garden.css
+++ b/stylesheets/darkroot_garden.css
@@ -1,7 +1,7 @@
/*cyberpunk mod of ferus by kalyx
B332E6 = dark purp
-293728 = teal
+293728 = teal
59B451 = green
FF46D2 = pink
dark blue = 293728
@@ -19,13 +19,13 @@ div.boardlist{
}
-@font-face
+@font-face
{
font-family: 'lain';
src: url('./fonts/nrdyyh.woff') format('woff'),
url('./fonts/tojcxo.TTF') format('truetype');
}
- @font-face
+ @font-face
{
font-family: 'DejaVuSansMono';
src: url('./fonts/DejaVuSansMono.ttf') format('truetype');
@@ -79,7 +79,7 @@ a:link, a:visited, p.intro a.email span.name
-ms-transition: 0.15s text-shadow, 0.15s color;
transition: 0.15s text-shadow, 0.15s color;
}
-input[type="text"], textarea
+input[type="text"], textarea
{
-moz-transition: 0.15s border-color;
-webkit-transition: 0.15s border-color;
@@ -88,7 +88,7 @@ input[type="text"], textarea
-ms-transition: 0.15s border-color;
transition: 0.15s border-color;
}
-input[type="submit"]
+input[type="submit"]
{
-moz-transition: 0.15s border-color, 0.15s background-color, 0.15s color;
-webkit-transition: 0.15s border-color, 0.15s background-color, 0.15s color;
@@ -112,7 +112,7 @@ a.post_no {
color: #293728;
text-decoration: none;
}
-span.quote
+span.quote
{
color:#4CADA7;
}
@@ -131,11 +131,6 @@ div.post.reply {
div.post.reply.highlighted {
background: transparent;
border: #293728 1px solid;
-
- @media (max-width: 48em) {
- border-left-style: none;
- border-right-style: none;
- }
}
div.post.reply div.body a:link, div.post.reply div.body a:visited {
color: #646464;
diff --git a/stylesheets/delete.css b/stylesheets/delete.css
index 7d7a6876..32bc764d 100644
Binary files a/stylesheets/delete.css and b/stylesheets/delete.css differ
diff --git a/stylesheets/fauux.css b/stylesheets/fauux.css
index 56fe4a82..3cb1538a 100644
--- a/stylesheets/fauux.css
+++ b/stylesheets/fauux.css
@@ -25,7 +25,7 @@ div.boardlist{
background-color: #000!important;
}
-@font-face
+@font-face
{
font-family: 'lain';
src: url('./fonts/nrdyyh.woff') format('woff'),
@@ -74,7 +74,7 @@ a.post_no {
color: #d2738a;
text-decoration: none;
}
-span.quote
+span.quote
{
color:#d2738a;
}
@@ -93,11 +93,6 @@ div.post.reply {
div.post.reply.highlighted {
background: transparent;
border: #EDC7D0 1px solid;
-
- @media (max-width: 48em) {
- border-left-style: none;
- border-right-style: none;
- }
}
div.post.reply div.body a:link, div.post.reply div.body a:visited {
color: #646464;
@@ -221,11 +216,11 @@ table.modlog tr th {
#options_div {
background-color: #000000;
}
-
+
.options_tab_icon {
color: #c1b492;
}
-
+
.options_tab_icon.active {
color: #d2738a;
}
diff --git a/stylesheets/ferus.css b/stylesheets/ferus.css
index 94d66bfd..cdb724c1 100644
--- a/stylesheets/ferus.css
+++ b/stylesheets/ferus.css
@@ -4,7 +4,7 @@ body {
font-family: monospace;
font-size: 11px;
}
-@font-face
+@font-face
{
font-family: 'lain';
src: url('./fonts/nrdyyh.woff') format('woff'),
@@ -41,7 +41,7 @@ a.post_no {
color: #B332E6;
text-decoration: none;
}
-span.quote
+span.quote
{
color:#00ff00;
}
@@ -59,11 +59,6 @@ div.post.reply {
div.post.reply.highlighted {
background: transparent;
border: #B332E6 2px solid;
-
- @media (max-width: 48em) {
- border-left-style: none;
- border-right-style: none;
- }
}
div.post.reply div.body a:link, div.post.reply div.body a:visited {
color: #646464;
diff --git a/stylesheets/forest.css b/stylesheets/forest.css
index ed54f8f3..dfc1bc58 100644
--- a/stylesheets/forest.css
+++ b/stylesheets/forest.css
@@ -1,7 +1,7 @@
/*cyberpunk mod of ferus by kalyx
B332E6 = dark purp
-293728 = teal
+293728 = teal
59B451 = green
FF46D2 = pink
dark blue = 293728
@@ -19,13 +19,13 @@ div.boardlist{
}
-@font-face
+@font-face
{
font-family: 'lain';
src: url('./fonts/nrdyyh.woff') format('woff'),
url('./fonts/tojcxo.TTF') format('truetype');
}
- @font-face
+ @font-face
{
font-family: 'DejaVuSansMono';
src: url('./fonts/DejaVuSansMono.ttf') format('truetype');
@@ -79,7 +79,7 @@ a:link, a:visited, p.intro a.email span.name
-ms-transition: 0.15s text-shadow, 0.15s color;
transition: 0.15s text-shadow, 0.15s color;
}
-input[type="text"], textarea
+input[type="text"], textarea
{
-moz-transition: 0.15s border-color;
-webkit-transition: 0.15s border-color;
@@ -88,7 +88,7 @@ input[type="text"], textarea
-ms-transition: 0.15s border-color;
transition: 0.15s border-color;
}
-input[type="submit"]
+input[type="submit"]
{
-moz-transition: 0.15s border-color, 0.15s background-color, 0.15s color;
-webkit-transition: 0.15s border-color, 0.15s background-color, 0.15s color;
@@ -112,7 +112,7 @@ a.post_no {
color: #293728;
text-decoration: none;
}
-span.quote
+span.quote
{
color:#4CADA7;
}
@@ -131,11 +131,6 @@ div.post.reply {
div.post.reply.highlighted {
background: transparent;
border: #293728 1px solid;
-
- @media (max-width: 48em) {
- border-left-style: none;
- border-right-style: none;
- }
}
div.post.reply div.body a:link, div.post.reply div.body a:visited {
color: #646464;
diff --git a/stylesheets/futaba-light.css b/stylesheets/futaba-light.css
index 296ca053..19166adf 100644
--- a/stylesheets/futaba-light.css
+++ b/stylesheets/futaba-light.css
@@ -34,11 +34,6 @@ div.post.reply {
border: 0px;
background: #FAE8D4;
border: 1px solid #E2C5B1;
-
- @media (max-width: 48em) {
- border-left-style: none;
- border-right-style: none;
- }
}
div.post.reply.highlighted {
background: #f0c0b0;
@@ -67,7 +62,7 @@ div.pages {
padding: 7px 5px;
color: maroon;
font-size: 12pt;
-
+
background: none;
border-width: 1px;
border-style: inset;
@@ -106,3 +101,4 @@ table.modlog tr th {
#options_div, #alert_div {
background: rgb(240, 224, 214);
}
+
diff --git a/stylesheets/gentoochan.css b/stylesheets/gentoochan.css
index 85204d32..49fa8a77 100644
--- a/stylesheets/gentoochan.css
+++ b/stylesheets/gentoochan.css
@@ -23,14 +23,6 @@ div.post.reply, input, textarea, select {
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 2px;
}
-
-@media (max-width: 48em) {
- div.post.reply {
- border-left-style: none;
- border-right-style: none;
- }
-}
-
div.post.reply.post-hover {
background: rgba(200, 200, 200, 0.85);
}
@@ -80,3 +72,4 @@ table.modlog tr th {
#quick-reply table {
background: #0E0E0E url(data:image/gif;base64,R0lGODlhGAAMAKEEAOXl5ebm5vDw8PHx8SH+EUNyZWF0ZWQgd2l0aCBHSU1QACwAAAAAGAAMAAACRpQiY6cLa146MyY1EJQKjG81lNGRUPOIkgMJHtquBgIO7xwvpbrpduUSuXq8ntEC0bBEylYitdDAdM1ViaobkgKgZwyDLAAAOw==) repeat 0 0 !important;
}
+
diff --git a/stylesheets/gorby.css b/stylesheets/gorby.css
index bc52f1c9..cacfc676 100644
--- a/stylesheets/gorby.css
+++ b/stylesheets/gorby.css
@@ -56,10 +56,6 @@ div.post.reply {
border-top: none;
border-radius: 5px;
box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.35);
-
- @media (max-width: 48em) {
- border-left-style: none;
- }
}
div.post.reply.highlighted {
@@ -69,10 +65,6 @@ div.post.reply.highlighted {
border-top: none;
border-radius: 5px;
box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.35);
-
- @media (max-width: 48em) {
- border-right-style: none;
- }
}
div.post.reply div.body a {
diff --git a/stylesheets/heavy_ice.css b/stylesheets/heavy_ice.css
index c0403c40..b5ef39aa 100644
--- a/stylesheets/heavy_ice.css
+++ b/stylesheets/heavy_ice.css
@@ -164,11 +164,6 @@ div.post.reply.highlighted
box-shadow: 3px 5px #5c8c8e;
margin-left: auto;
margin-right: auto;
-
- @media (max-width: 48em) {
- border-left-style: none;
- border-right-style: none;
- }
}
div.post.reply div.body a:link, div.post.reply div.body a:visited
{
diff --git a/stylesheets/jungle.css b/stylesheets/jungle.css
index 7486c5a8..a77573dc 100644
--- a/stylesheets/jungle.css
+++ b/stylesheets/jungle.css
@@ -53,10 +53,6 @@ div.post.reply {
border-top: none;
border-radius: 5px;
box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.35);
-
- @media (max-width: 48em) {
- border-right-style: none;
- }
}
div.post.reply.highlighted {
@@ -66,10 +62,6 @@ div.post.reply.highlighted {
border-top: none;
border-radius: 5px;
box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.35);
-
- @media (max-width: 48em) {
- border-right-style: none;
- }
}
div.post.reply div.body a {
diff --git a/stylesheets/kalyx_theme.css b/stylesheets/kalyx_theme.css
index 1a1ccfc2..71210cff 100644
--- a/stylesheets/kalyx_theme.css
+++ b/stylesheets/kalyx_theme.css
@@ -2,27 +2,27 @@
* dark.css
* Stolen from circlepuller who stole it from derpcat
*/
-body
+body
{
background: #1E1E1E;
color: #999999;
font-family: sans-serif;
font-size: 11px;
-
-
+
+
}
-span.quote
+span.quote
{
color:#B8D962;
}
-@font-face
+@font-face
{
font-family: 'lain';
src: url('./fonts/nrdyyh.woff') format('woff'),
url('./fonts/tojcxo.TTF') format('truetype');
}
-h1
+h1
{
font-family: 'lain', tahoma;
letter-spacing: -2px;
@@ -30,20 +30,20 @@ h1
text-align: center;
color: #32DD72;
}
-header div.subtitle
+header div.subtitle
{
color: #32DD72;
}
-div.title
+div.title
{
color: #32DD72;
font-family: Arial, Helvetica, sans-serif;
}
-div.title p
+div.title p
{
font-size: 10px;
}
-a:link, a:visited, p.intro a.email span.name
+a:link, a:visited, p.intro a.email span.name
{
color: #CCCCCC;
text-decoration: none;
@@ -58,7 +58,7 @@ a:link, a:visited, p.intro a.email span.name
-ms-transition: 0.15s text-shadow, 0.15s color;
transition: 0.15s text-shadow, 0.15s color;
}
-input[type="text"], textarea
+input[type="text"], textarea
{
-moz-transition: 0.15s border-color;
-webkit-transition: 0.15s border-color;
@@ -67,7 +67,7 @@ input[type="text"], textarea
-ms-transition: 0.15s border-color;
transition: 0.15s border-color;
}
-input[type="submit"]
+input[type="submit"]
{
-moz-transition: 0.15s border-color, 0.15s background-color, 0.15s color;
-webkit-transition: 0.15s border-color, 0.15s background-color, 0.15s color;
@@ -76,23 +76,23 @@ input[type="submit"]
-ms-transition: 0.15s border-color, 0.15s background-color, 0.15s color;
transition: 0.15s border-color, 0.15s background-color, 0.15s color;
}
-a:link:hover, a:visited:hover
+a:link:hover, a:visited:hover
{
color: #32DD72;
font-family: sans-serif;
text-decoration: none;
text-shadow: 0px 0px 5px #fff;
}
-a.post_no
+a.post_no
{
color: #AAA;
text-decoration: none;
}
-p.intro a.post_no:hover
+p.intro a.post_no:hover
{
color: #32DD72!important;
}
-div.post.reply
+div.post.reply
{
background: #181818;
border: #555555 0px solid;
@@ -117,64 +117,59 @@ div.sidearrows
display:none;
}
-div.post.reply.highlighted
+div.post.reply.highlighted
{
background: #555;
border: transparent 1px solid;
-
- @media (max-width: 48em) {
- border-left-style: none;
- border-right-style: none;
- }
}
-div.post.reply div.body a:link, div.post.reply div.body a:visited
+div.post.reply div.body a:link, div.post.reply div.body a:visited
{
color: #CCCCCC;
}
-div.post.reply div.body a:link:hover, div.post.reply div.body a:visited:hover
+div.post.reply div.body a:link:hover, div.post.reply div.body a:visited:hover
{
color: #32DD72;
}
-p.intro span.subject
+p.intro span.subject
{
font-size: 12px;
font-family: sans-serif;
color: #446655;
font-weight: 800;
}
-p.intro span.name
+p.intro span.name
{
color: #32DD72;
font-weight: 800;
}
-p.intro a.capcode, p.intro a.nametag
+p.intro a.capcode, p.intro a.nametag
{
color: magenta;
margin-left: 0;
}
-p.intro a.email, p.intro a.email span.name, p.intro a.email:hover, p.intro a.email:hover span.name
+p.intro a.email, p.intro a.email span.name, p.intro a.email:hover, p.intro a.email:hover span.name
{
color: #32ddaf;
}
-input[type="text"], textarea, select
+input[type="text"], textarea, select
{
background: #333333!important;
color: #CCCCCC!important;
border: #666666 1px solid!important;
}
-input[type="password"]
+input[type="password"]
{
background: #333333!important;
color: #CCCCCC!important;
border: #666666 1px solid!important;
}
-form table tr th
+form table tr th
{
background: #333333!important;
color: #AAAAAA!important;
border: #333333 1px solid!important;;
}
-div.banner
+div.banner
{
background: #E04000;
border: 1px solid hsl(17, 100%, 60%);
@@ -185,31 +180,31 @@ div.banner
margin-left: auto;
margin-right: auto;
}
-div.banner a
+div.banner a
{
color:#000;
}
-input[type="submit"]
+input[type="submit"]
{
background: #333333;
border: #666 1px solid;
color: #CCCCCC;
}
-input[type="submit"]:hover
+input[type="submit"]:hover
{
background: #555;
border: #888 1px solid;
color: #32DD72;
}
-input[type="text"]:focus, textarea:focus
+input[type="text"]:focus, textarea:focus
{
border:#888 1px solid!important;
}
-p.fileinfo a:hover
+p.fileinfo a:hover
{
text-decoration: underline;
}
-span.trip
+span.trip
{
color: #AAA;
}
@@ -219,7 +214,7 @@ span.trip
border-bottom: 0px solid #666;
widh:100%;
}
-div.pages
+div.pages
{
color: #AAA;
background: #333;
@@ -227,21 +222,21 @@ div.pages
font-family: sans-serif;
font-size: 10pt;
}
-div.pages a.selected
+div.pages a.selected
{
color: #CCC;
}
-hr
+hr
{
height: 0px;
border: #333 1px solid;
}
-div.boardlist
+div.boardlist
{
color: #999;
}
-div.ban
+div.ban
{
background-color: #1e1e1e;
border: 1px solid #555;
@@ -250,7 +245,7 @@ div.ban
border-radius: 2px;
text-align: left!important;
}
-div.ban h2
+div.ban h2
{
background: #333;
color: #32DD72;
@@ -258,17 +253,17 @@ div.ban h2
font-size: 12pt;
border-bottom: 1px solid #555;
}
-div.ban h2:not(:nth-child(1))
+div.ban h2:not(:nth-child(1))
{
border-top: 1px solid #555;
}
-table.modlog tr th
+table.modlog tr th
{
background: #333;
color: #AAA;
}
-div.report
+div.report
{
color: #666;
}
@@ -281,7 +276,7 @@ div.report
-ms-border-radius: 2px;
border-radius: 2px;
}
-.blur
+.blur
{
filter: blur(20px);
-webkit-filter: blur(23px);
@@ -292,15 +287,17 @@ div.report
}
/* options.js */
-#options_div
+#options_div
{
background: #333333;
}
-.options_tab_icon
+.options_tab_icon
{
color: #AAAAAA;
}
-.options_tab_icon.active
+.options_tab_icon.active
{
color: #FFFFFF;
}
+
+
diff --git a/stylesheets/midnight.css b/stylesheets/midnight.css
index 339479ec..5cfe8060 100644
--- a/stylesheets/midnight.css
+++ b/stylesheets/midnight.css
@@ -265,11 +265,6 @@ div.post.reply,
background: #220022;
border: #555555 1px solid;
border-radius: 10px;
-
- @media (max-width: 48em) {
- border-left-style: none;
- border-right-style: none;
- }
}
.post.highlighted {
@@ -280,11 +275,6 @@ div.post.reply,
div.post.reply.highlighted {
background: #3A003A;
border: transparent 1px solid;
-
- @media (max-width: 48em) {
- border-left-style: none;
- border-right-style: none;
- }
}
.post.highlighted {
@@ -462,10 +452,10 @@ table.modlog tr th {
/* leftypol edits */
-.bar,
-.bar.top,
-.bar.bottom,
-div.boardlist,
+.bar,
+.bar.top,
+.bar.bottom,
+div.boardlist,
div.boardlist:not(.bottom) {
background-color: rgba(30%, 0%, 30%, 1.0);
}
diff --git a/stylesheets/mono.e.lain.css b/stylesheets/mono.e.lain.css
index de073f0c..f09e93b9 100644
--- a/stylesheets/mono.e.lain.css
+++ b/stylesheets/mono.e.lain.css
@@ -215,11 +215,6 @@ margin-left: 10px;
margin-top: 20px;
border: double 3px #000;
background-color: rgb(194, 194, 194);
-
- @media (max-width: 48em) {
- border-left-style: none;
- border-right-style: none;
- }
}
/*unfucks highlighting replies and gives border/shadow*/
diff --git a/stylesheets/muted.css b/stylesheets/muted.css
index bfd47e70..c0d00ecb 100644
--- a/stylesheets/muted.css
+++ b/stylesheets/muted.css
@@ -32,7 +32,7 @@ a:hover,
header div.subtitle,
h1 {
color: #d93f42;
- font-size: 20pt;
+ font-size: 20pt;
font-family: "Open Sans", sans-serif;
}
header div.subtitle {
@@ -48,7 +48,7 @@ p.intro {
border-color: #cccccc;
border-style: solid;
border-width: 0.8px;
- border-radius: 5px;
+ border-radius: 5px;
}
/* Replies */
/* Background color and border */
@@ -58,11 +58,6 @@ div.post.reply {
border-style: solid;
border-width: 0.8px;
border-radius: 5px;
-
- @media (max-width: 48em) {
- border-left-style: none;
- border-right-style: none;
- }
}
div.post.reply.highlighted {
background: #d5dada;
@@ -70,11 +65,6 @@ div.post.reply.highlighted {
border-style: solid;
border-width: 0.8px;
border-radius: 5px;
-
- @media (max-width: 48em) {
- border-left-style: none;
- border-right-style: none;
- }
}
div.post.reply div.body a {
color: #477085;
@@ -106,7 +96,7 @@ orangeText {
background-color: #e9eced;
border: 1px solid #cccccc;
}
-.thread.grid-li.grid-size-vsmall:hover,
+.thread.grid-li.grid-size-vsmall:hover,
.thread.grid-li.grid-size-small:hover,
.thread.grid-li.grid-size-large:hover {
background: #d5dada;
@@ -204,7 +194,7 @@ span.heading {
}
/* Fix OP file bleeding out of the border*/
.post-image {
- margin: 37px
+ margin: 37px
}
/* Quick reply */
/* Quick reply banner */
diff --git a/stylesheets/northboard_cb.css b/stylesheets/northboard_cb.css
index e1eedaf6..b665c569 100644
--- a/stylesheets/northboard_cb.css
+++ b/stylesheets/northboard_cb.css
@@ -28,22 +28,14 @@ div.post.reply {
background: #343439;
border-color: #3070A9;
border-top: 1px solid #3070A9;
+ border-left: 1px solid #3070A9;
border-radius: 3px;
padding: 0px;
-
- @media (min-width: 48em) {
- border-left: 1px solid #3070A9;
- }
}
div.post.reply.highlighted {
background: #44444f;
border: 3px dashed #3070a9;
-
- @media (max-width: 48em) {
- border-left-style: none;
- border-right-style: none;
- }
}
div.post.reply div.body a, .mentioned {
@@ -228,30 +220,30 @@ span.heading {
right: 1em !important;
position: absolute !important;
}
-
+
#expand-all-images{
margin-top: 4em !important;
}
-
+
#treeview{
margin-top: 5em !important;
}
-
+
#shrink-all-images{
margin-top: 6em !important;
}
-
+
#expand-all-images + hr,
#shrink-all-images + hr{
opacity: 0 !important;
margin: 0 !important;
}
-
+
#treeview + hr{
opacity: 0 !important;
clear: both !important;
}
-
+
#options_handler{
margin-top: 3em !important;
}
diff --git a/stylesheets/style.css b/stylesheets/style.css
index 4a090f33..db434302 100644
--- a/stylesheets/style.css
+++ b/stylesheets/style.css
@@ -380,7 +380,6 @@ form table tr td div.center {
.file {
float: left;
- min-width: 100px;
}
.file:not(.multifile) .post-image {
@@ -391,10 +390,6 @@ form table tr td div.center {
float: none;
}
-.file.multifile {
- margin: 0 10px 0 0;
-}
-
.file.multifile > p {
width: 0px;
min-width: 100%;
@@ -434,18 +429,19 @@ img.banner,img.board_image {
.post-image {
display: block;
float: left;
+ margin: 5px 20px 10px 20px;
border: none;
}
.full-image {
float: left;
- padding: 0.2em 0.2em 0.8em 0.2em;
+ padding: 5px;
margin: 0 20px 0 0;
max-width: 98%;
}
div.post .post-image {
- padding: 0.2em 0.2em 0.8em 0.2em;
+ padding: 0.2em;
margin: 0 20px 0 0;
}
@@ -542,8 +538,8 @@ div.post {
}
}
-div.post div.head {
- margin: 0.1em 1em 0.8em 1.4em;
+div.post > div.head {
+ margin: 0.1em 1em;
clear: both;
line-height: 1.3em;
}
@@ -568,11 +564,17 @@ div.post.op > p {
}
div.post div.body {
- margin-left: 1.4em;
+ margin-top: 0.8em;
padding-right: 3em;
padding-bottom: 0.3em;
+}
- white-space: pre-wrap;
+div.post.op div.body {
+ margin-left: 0.8em;
+}
+
+div.post.reply div.body {
+ margin-left: 1.8em;
}
div.post.reply.highlighted {
@@ -583,9 +585,19 @@ div.post.reply div.body a {
color: #D00;
}
+div.post div.body {
+ white-space: pre-wrap;
+}
+
div.post.op {
padding-top: 0px;
vertical-align: top;
+
+ /* Add back in the padding that is provided by body on large screens */
+ @media (max-width: 48em) {
+ padding-left: 4px;
+ padding-right: 4px;
+ }
}
div.post.reply {
@@ -636,7 +648,6 @@ span.trip {
span.omitted {
display: block;
margin-top: 1em;
- margin-left: 0.4em;
}
br.clear {
@@ -821,7 +832,7 @@ span.public_ban {
span.public_warning {
display: block;
- color: orange;
+ color: steelblue;
font-weight: bold;
margin-top: 15px;
}
diff --git a/stylesheets/terminal2.css b/stylesheets/terminal2.css
index 9add30ea..d4d52847 100644
--- a/stylesheets/terminal2.css
+++ b/stylesheets/terminal2.css
@@ -48,11 +48,6 @@ div.post.reply.highlighted {
background: transparent;
border: transparent 1px dashed;
border-color:#00FF00;
-
- @media (max-width: 48em) {
- border-left-style: none;
- border-right-style: none;
- }
}
div.post.reply div.body a:link, div.post.reply div.body a:visited {
color: #00FF00;
diff --git a/stylesheets/terminal_common.css b/stylesheets/terminal_common.css
index 53d04ca2..c7f34f51 100644
--- a/stylesheets/terminal_common.css
+++ b/stylesheets/terminal_common.css
@@ -69,11 +69,6 @@ div.post.reply {
div.post.reply.highlighted {
background: transparent;
border: transparent 1px dotted;
-
- @media (max-width: 48em) {
- border-left-style: none;
- border-right-style: none;
- }
}
p.intro span.subject {
font-size: 12px;
diff --git a/stylesheets/test.css b/stylesheets/test.css
index 6835b23f..162e9c26 100644
--- a/stylesheets/test.css
+++ b/stylesheets/test.css
@@ -132,11 +132,6 @@ line-height: 1.4;
div.post.reply.highlighted {
background: #555;
border: transparent 1px solid;
-
- @media (max-width: 48em) {
- border-left-style: none;
- border-right-style: none;
- }
}
div.post.reply div.body a:link, div.post.reply div.body a:visited {
color: #CCCCCC;
diff --git a/stylesheets/tsuki.css b/stylesheets/tsuki.css
index 523bdb71..bffa78c3 100644
--- a/stylesheets/tsuki.css
+++ b/stylesheets/tsuki.css
@@ -5,15 +5,15 @@ color change by kalyx
@import url("/stylesheets/dark.css");
@font-face
{
- font-family: "DejaVuSansMono";
- src: url("/stylesheets/fonts/DejaVuSansMono.ttf") format("truetype");
+ font-family: "DejaVuSansMono";
+ src: url("/stylesheets/fonts/DejaVuSansMono.ttf") format("truetype");
}
@font-face
{
- font-family: 'lain';
- src: url('./fonts/nrdyyh.woff') format('woff'),
- url('./fonts/tojcxo.TTF') format('truetype');
+ font-family: 'lain';
+ src: url('./fonts/nrdyyh.woff') format('woff'),
+ url('./fonts/tojcxo.TTF') format('truetype');
}
/**
* volafile.css fuck you
@@ -22,15 +22,15 @@ color change by kalyx
@import url("/stylesheets/dark.css");
@font-face
{
- font-family: "DejaVuSansMono";
- src: url("/stylesheets/fonts/DejaVuSansMono.ttf") format("truetype");
+ font-family: "DejaVuSansMono";
+ src: url("/stylesheets/fonts/DejaVuSansMono.ttf") format("truetype");
}
@font-face
{
- font-family: 'lain';
- src: url('./fonts/nrdyyh.woff') format('woff'),
- url('./fonts/tojcxo.TTF') format('truetype');
+ font-family: 'lain';
+ src: url('./fonts/nrdyyh.woff') format('woff'),
+ url('./fonts/tojcxo.TTF') format('truetype');
}
@@ -40,419 +40,419 @@ color change by kalyx
}
.hidden {
- display:none;
+ display:none;
}
a,a:visited {
- text-decoration: underline;
- color: #34345C;
+ text-decoration: underline;
+ color: #34345C;
}
a:hover,.intro a.post_no:hover {
- color: #ff0000;
+ color: #ff0000;
}
a.post_no {
- text-decoration: none;
- margin: 0;
- padding: 0;
+ text-decoration: none;
+ margin: 0;
+ padding: 0;
}
.intro a.post_no {
- color: inherit;
+ color: inherit;
}
.intro a.post_no,p.intro a.email,p.intro a.post_anchor {
- margin: 0;
+ margin: 0;
}
.intro a.email span.name {
- color: #34345C;
+ color: #34345C;
}
.intro a.email:hover span.name {
- color: #ff0000;
+ color: #ff0000;
}
.intro label {
- display: inline;
+ display: inline;
}
.intro time,p.intro a.ip-link,p.intro a.capcode {
- direction: ltr;
- unicode-bidi: embed;
+ direction: ltr;
+ unicode-bidi: embed;
}
h2 {
- color: #AF0A0F;
- font-size: 11pt;
- margin: 0;
- padding: 0;
+ color: #AF0A0F;
+ font-size: 11pt;
+ margin: 0;
+ padding: 0;
}
header {
- margin: 1em 0;
+ margin: 1em 0;
}
h1 {
- font-family: tahoma;
- letter-spacing: -2px;
- font-size: 20pt;
- margin: 0;
+ font-family: tahoma;
+ letter-spacing: -2px;
+ font-size: 20pt;
+ margin: 0;
}
header div.subtitle,h1 {
- color: #888;
- text-align: center;
+ color: #888;
+ text-align: center;
}
header div.subtitle {
- font-size: 16px;
+ font-size: 16px;
}
form {
- margin-bottom: 4em;
+ margin-bottom: 4em;
}
form table {
- margin: auto;
+ margin: auto;
}
form table input {
- height: auto;
+ height: auto;
}
input[type="text"],input[type="password"],textarea {
- border: 1px solid #a9a9a9;
- text-indent: 0;
- text-shadow: none;
- text-transform: none;
- word-spacing: normal;
- max-width: 100%;
+ border: 1px solid #a9a9a9;
+ text-indent: 0;
+ text-shadow: none;
+ text-transform: none;
+ word-spacing: normal;
+ max-width: 100%;
}
#quick-reply input[type="text"], input[type="password"], #quick-reply textarea {
- max-width: 100%;
+ max-width: 100%;
}
textarea {
- width: 100%;
+ width: 100%;
}
form table tr td {
- text-align: left;
- margin: 0;
- padding: 0;
+ text-align: left;
+ margin: 0;
+ padding: 0;
}
form table.mod tr td {
- padding: 2px;
+ padding: 2px;
}
form table tr th {
- text-align: left;
- padding: 4px;
+ text-align: left;
+ padding: 4px;
}
form table tr th {
- background: #98E;
+ background: #98E;
}
form table tr td div.center {
- text-align: center;
- float: left;
- padding-left: 3px;
+ text-align: center;
+ float: left;
+ padding-left: 3px;
}
form table tr td div input {
- display: block;
- margin: 2px auto 0 auto;
+ display: block;
+ margin: 2px auto 0 auto;
}
form table tr td div label {
- font-size: 16px;
+ font-size: 16px;
}
.unimportant,.unimportant * {
- font-size: 16px;
+ font-size: 16px;
}
.file {
- float: left;
- margin-right: 2px;
+ float: left;
+ margin-right: 2px;
}
.file:not(.multifile) .post-image {
- float: left;
+ float: left;
}
.file:not(.multifile) {
- float: none;
+ float: none;
}
p.fileinfo {
- display: block;
- margin: 0 0 0 20px;
+ display: block;
+ margin: 0 0 0 20px;
}
div.post p.fileinfo {
- padding-left: 5px;
+ padding-left: 5px;
}
div.banner {
- background-color: #E04000;
- font-size: 16pt;
- font-weight: bold;
- text-align: center;
- margin: 1em 0;
+ background-color: #E04000;
+ font-size: 16pt;
+ font-weight: bold;
+ text-align: center;
+ margin: 1em 0;
}
div.banner,div.banner a {
- color: white;
+ color: white;
}
div.banner a:hover {
- color: #EEF2FF;
- text-decoration: none;
+ color: #EEF2FF;
+ text-decoration: none;
}
img.banner,img.board_image {
- display: block;
- border: 1px solid #a9a9a9;
- margin: 12px auto 0 auto;
+ display: block;
+ border: 1px solid #a9a9a9;
+ margin: 12px auto 0 auto;
}
.post-image {
- display: block;
- float: left;
- margin: 5px 20px 10px 20px;
- border: none;
+ display: block;
+ float: left;
+ margin: 5px 20px 10px 20px;
+ border: none;
}
.full-image {
- max-width: 98%;
+ max-width: 98%;
}
div.post .post-image {
- padding: 5px;
- margin: 0 20px 0 0;
+ padding: 5px;
+ margin: 0 20px 0 0;
}
div.post img.icon {
- display: inline;
- margin: 0 5px;
- padding: 0;
+ display: inline;
+ margin: 0 5px;
+ padding: 0;
}
div.post i.fa {
- margin: 0 4px;
- font-size: 16px;
+ margin: 0 4px;
+ font-size: 16px;
}
div.post.op {
- margin-right: 20px;
- margin-bottom: 5px;
+ margin-right: 20px;
+ margin-bottom: 5px;
}
div.post.op hr {
- border-color: #D9BFB7;
+ border-color: #D9BFB7;
}
.intro {
- margin: 0.5em 0;
- padding: 0;
- padding-bottom: 0.2em;
+ margin: 0.5em 0;
+ padding: 0;
+ padding-bottom: 0.2em;
}
input.delete {
- float: left;
- margin: 1px 6px 0 0;
+ float: left;
+ margin: 1px 6px 0 0;
}
.intro span.subject {
- color: #0F0C5D;
- font-weight: bold;
+ color: #0F0C5D;
+ font-weight: bold;
}
.intro span.name {
- color: #117743;
- font-weight: bold;
+ color: #117743;
+ font-weight: bold;
}
.intro span.capcode,p.intro a.capcode,p.intro a.nametag {
- color: #F00000;
- margin-left: 0;
+ color: #F00000;
+ margin-left: 0;
}
.intro a {
- margin-left: 8px;
+ margin-left: 8px;
}
div.delete {
- float: right;
+ float: right;
}
div.post.reply p {
- margin: 0.3em 0 0 0;
+ margin: 0.3em 0 0 0;
}
div.post.reply div.body {
- margin-left: 1.8em;
- margin-top: 0.8em;
- padding-right: 3em;
- padding-bottom: 0.3em;
+ margin-left: 1.8em;
+ margin-top: 0.8em;
+ padding-right: 3em;
+ padding-bottom: 0.3em;
}
div.post.reply.highlighted {
- background: #D6BAD0;
+ background: #D6BAD0;
}
div.post.reply div.body a {
- color: #D00;
+ color: #D00;
}
div.post {
- padding-left: 20px;
+ padding-left: 20px;
}
div.post div.body {
- word-wrap: break-word;
- white-space: pre-wrap;
+ word-wrap: break-word;
+ white-space: pre-wrap;
}
div.post.reply {
- background: #D6DAF0;
- margin: 0.2em 4px;
- padding: 0.2em 0.3em 0.5em 0.6em;
- border-width: 1px;
- border-style: none solid solid none;
- border-color: #B7C5D9;
- display: inline-block;
- max-width: 94%!important;
+ background: #D6DAF0;
+ margin: 0.2em 4px;
+ padding: 0.2em 0.3em 0.5em 0.6em;
+ border-width: 1px;
+ border-style: none solid solid none;
+ border-color: #B7C5D9;
+ display: inline-block;
+ max-width: 94%!important;
- @media (max-width: 48em) {
- border-left-style: none;
- border-right-style: none;
- }
+ @media (max-width: 48em) {
+ border-left-style: none;
+ border-right-style: none;
+ }
}
span.trip {
- color: #228854;
+ color: #228854;
}
span.quote {
- color: #789922;
+ color: #789922;
}
span.omitted {
- display: block;
- margin-top: 1em;
+ display: block;
+ margin-top: 1em;
}
br.clear {
- clear: left;
- display: block;
+ clear: left;
+ display: block;
}
div.controls {
- float: right;
- margin: 0;
- padding: 0;
- font-size: 80%;
+ float: right;
+ margin: 0;
+ padding: 0;
+ font-size: 80%;
}
div.controls.op {
- float: none;
- margin-left: 10px;
+ float: none;
+ margin-left: 10px;
}
div.controls a {
- margin: 0;
+ margin: 0;
}
div#wrap {
- width: 900px;
- margin: 0 auto;
+ width: 900px;
+ margin: 0 auto;
}
div.ban {
- background: white;
- border: 1px solid #98E;
- max-width: 700px;
- margin: 30px auto;
+ background: white;
+ border: 1px solid #98E;
+ max-width: 700px;
+ margin: 30px auto;
}
div.ban p,div.ban h2 {
- padding: 3px 7px;
+ padding: 3px 7px;
}
div.ban h2 {
- background: #98E;
- color: black;
- font-size: 16pt;
+ background: #98E;
+ color: black;
+ font-size: 16pt;
}
div.ban p {
- font-size: 16px;
- margin-bottom: 12px;
+ font-size: 16px;
+ margin-bottom: 12px;
}
div.ban p.reason {
- font-weight: bold;
+ font-weight: bold;
}
span.heading {
- color: #AF0A0F;
- font-size: 11pt;
- font-weight: bold;
+ color: #AF0A0F;
+ font-size: 11pt;
+ font-weight: bold;
}
span.spoiler {
- background: black;
- color: black;
- padding: 0 1px;
+ background: black;
+ color: black;
+ padding: 0 1px;
}
div.post.reply div.body span.spoiler a {
- color: black;
+ color: black;
}
span.spoiler:hover,div.post.reply div.body span.spoiler:hover a {
- color: white;
+ color: white;
}
div.styles {
- float: right;
- padding-bottom: 20px;
+ float: right;
+ padding-bottom: 20px;
}
div.styles a {
- margin: 0 10px;
+ margin: 0 10px;
}
div.styles a.selected {
- text-decoration: none;
+ text-decoration: none;
}
table.test {
- width: 100%;
+ width: 100%;
}
table.test td,table.test th {
- text-align: left;
- padding: 5px;
+ text-align: left;
+ padding: 5px;
}
table.test tr.h th {
- background: #98E;
+ background: #98E;
}
table.test td img {
- margin: 0;
+ margin: 0;
}
fieldset label {
- display: block;
+ display: block;
}
div.pages {
@@ -466,524 +466,524 @@ div.pages {
}
div.pages.top {
- display: block;
- padding: 5px 8px;
- margin-bottom: 5px;
- position: fixed;
- top: 0;
- right: 0;
- opacity: 0.9;
+ display: block;
+ padding: 5px 8px;
+ margin-bottom: 5px;
+ position: fixed;
+ top: 0;
+ right: 0;
+ opacity: 0.9;
}
@media screen and (max-width: 800px) {
- div.pages.top {
- display: none!important;
- }
+ div.pages.top {
+ display: none!important;
+ }
}
div.pages a.selected {
- color: black;
- font-weight: bolder;
+ color: black;
+ font-weight: bolder;
}
div.pages a {
- text-decoration: none;
+ text-decoration: none;
}
div.pages form {
- margin: 0;
- padding: 0;
- display: inline;
+ margin: 0;
+ padding: 0;
+ display: inline;
}
div.pages form input {
- margin: 0 5px;
- display: inline;
+ margin: 0 5px;
+ display: inline;
}
hr {
- border: none;
- border-top: 1px solid #B7C5D9;
- height: 0;
- clear: left;
+ border: none;
+ border-top: 1px solid #B7C5D9;
+ height: 0;
+ clear: left;
}
div.report {
- color: #333;
+ color: #333;
}
table.modlog {
- margin: auto;
- width: 100%;
+ margin: auto;
+ width: 100%;
}
table.modlog tr td {
- text-align: left;
- margin: 0;
- padding: 4px 15px 0 0;
+ text-align: left;
+ margin: 0;
+ padding: 4px 15px 0 0;
}
table.modlog tr th {
- text-align: left;
- padding: 4px 15px 5px 5px;
- white-space: nowrap;
+ text-align: left;
+ padding: 4px 15px 5px 5px;
+ white-space: nowrap;
}
table.modlog tr th {
- background: #98E;
+ background: #98E;
}
td.minimal,th.minimal {
- width: 1%;
- white-space: nowrap;
+ width: 1%;
+ white-space: nowrap;
}
div.top_notice {
- text-align: center;
- margin: 5px auto;
+ text-align: center;
+ margin: 5px auto;
}
span.public_ban {
- display: block;
- color: red;
- font-weight: bold;
- margin-top: 15px;
+ display: block;
+ color: red;
+ font-weight: bold;
+ margin-top: 15px;
}
span.toolong {
- display: block;
- margin-top: 15px;
+ display: block;
+ margin-top: 15px;
}
div.blotter {
- color: red;
- font-weight: bold;
- text-align: center;
+ color: red;
+ font-weight: bold;
+ text-align: center;
}
table.mod.config-editor {
- font-size: 9pt;
- width: 100%;
+ font-size: 9pt;
+ width: 100%;
}
table.mod.config-editor td {
- text-align: left;
- padding: 5px;
- border-bottom: 1px solid #98e;
+ text-align: left;
+ padding: 5px;
+ border-bottom: 1px solid #98e;
}
table.mod.config-editor input[type="text"] {
- width: 98%;
+ width: 98%;
}
.desktop-style div.boardlist:nth-child(1) {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- margin-top: 0;
- z-index: 30;
- box-shadow: 0 1px 2px rgba(0, 0, 0, .15);
- border-bottom: 1px solid;
- background-color: #D6DAF0;
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ margin-top: 0;
+ z-index: 30;
+ box-shadow: 0 1px 2px rgba(0, 0, 0, .15);
+ border-bottom: 1px solid;
+ background-color: #D6DAF0;
}
.desktop-style body {
- padding-top: 20px;
+ padding-top: 20px;
}
.desktop-style .sub {
- background: inherit;
+ background: inherit;
}
.desktop-style .sub .sub {
- display: inline-block;
- text-indent: -9000px;
- width: 7px;
+ display: inline-block;
+ text-indent: -9000px;
+ width: 7px;
}
.desktop-style .sub .sub:hover,.desktop-style .sub .sub.hover {
- display: inline;
- text-indent: 0;
- background: inherit;
+ display: inline;
+ text-indent: 0;
+ background: inherit;
}
#attention_bar {
- height: 1.5em;
- max-height: 1.5em;
- width: 100%;
- max-width: 100%;
- text-align: center;
- overflow: hidden;
+ height: 1.5em;
+ max-height: 1.5em;
+ width: 100%;
+ max-width: 100%;
+ text-align: center;
+ overflow: hidden;
}
#attention_bar_form {
- display: none;
- padding: 0;
- margin: 0;
+ display: none;
+ padding: 0;
+ margin: 0;
}
#attention_bar_input {
- width: 100%;
- padding: 0;
- margin: 0;
- text-align: center;
+ width: 100%;
+ padding: 0;
+ margin: 0;
+ text-align: center;
}
#attention_bar:hover {
- background-color: rgba(100%,100%,100%,0.2);
+ background-color: rgba(100%,100%,100%,0.2);
}
.intro.thread-hidden {
- margin: 0;
- padding: 0;
+ margin: 0;
+ padding: 0;
}
form.ban-appeal {
- margin: 9px 20px;
+ margin: 9px 20px;
}
form.ban-appeal textarea {
- display: block;
+ display: block;
}
.MathJax_Display {
- display: inline!important;
+ display: inline!important;
}
pre {
- margin: 0;
+ margin: 0;
}
.theme-catalog div.thread img {
- float: none!important;
- margin: auto;
- max-height: 150px;
- max-width: 200px;
- box-shadow: 0 0 4px rgba(0,0,0,0.55);
- border: 2px solid rgba(153,153,153,0);
+ float: none!important;
+ margin: auto;
+ max-height: 150px;
+ max-width: 200px;
+ box-shadow: 0 0 4px rgba(0,0,0,0.55);
+ border: 2px solid rgba(153,153,153,0);
}
.theme-catalog div.thread {
- display: inline-block;
- vertical-align: top;
- text-align: center;
- font-weight: normal;
- margin-top: 2px;
- margin-bottom: 2px;
- padding: 2px;
- height: 300px;
- width: 205px;
- overflow: hidden;
- position: relative;
- font-size: 11px;
- max-height: 300px;
- background: rgba(182,182,182,0.12);
- border: 2px solid rgba(111,111,111,0.34);
+ display: inline-block;
+ vertical-align: top;
+ text-align: center;
+ font-weight: normal;
+ margin-top: 2px;
+ margin-bottom: 2px;
+ padding: 2px;
+ height: 300px;
+ width: 205px;
+ overflow: hidden;
+ position: relative;
+ font-size: 11px;
+ max-height: 300px;
+ background: rgba(182,182,182,0.12);
+ border: 2px solid rgba(111,111,111,0.34);
}
.theme-catalog div.thread strong {
- display: block;
+ display: block;
}
.theme-catalog div.threads {
- text-align: center;
- margin-left: -20px;
+ text-align: center;
+ margin-left: -20px;
}
.theme-catalog div.thread:hover {
- background: #D6DAF0;
- border-color: #B7C5D9;
+ background: #D6DAF0;
+ border-color: #B7C5D9;
}
.theme-catalog div.grid-size-vsmall img {
- max-height: 33%;
- max-width: 95%
+ max-height: 33%;
+ max-width: 95%
}
.theme-catalog div.grid-size-vsmall {
- min-width:90px; max-width: 90px;
- max-height: 148px;
+ min-width:90px; max-width: 90px;
+ max-height: 148px;
}
.theme-catalog div.grid-size-small img {
- max-height: 33%;
- max-width: 95%
+ max-height: 33%;
+ max-width: 95%
}
.theme-catalog div.grid-size-small {
- min-width:140px; max-width: 140px;
- max-height: 192px;
+ min-width:140px; max-width: 140px;
+ max-height: 192px;
}
.theme-catalog div.grid-size-large img {
- max-height: 40%;
- max-width: 95%
+ max-height: 40%;
+ max-width: 95%
}
.theme-catalog div.grid-size-large {
- min-width: 256px; max-width: 256px;
- max-height: 384px;
+ min-width: 256px; max-width: 256px;
+ max-height: 384px;
}
.theme-catalog img.thread-image {
- height: auto;
- max-width: 100%;
+ height: auto;
+ max-width: 100%;
}
@media (max-width: 420px) {
- .theme-catalog ul#Grid {
- padding-left: 18px;
- }
+ .theme-catalog ul#Grid {
+ padding-left: 18px;
+ }
- .theme-catalog div.thread {
- width: auto;
- margin-left: 0;
- margin-right: 0;
- }
+ .theme-catalog div.thread {
+ width: auto;
+ margin-left: 0;
+ margin-right: 0;
+ }
- .theme-catalog div.threads {
- overflow: hidden;
- }
+ .theme-catalog div.threads {
+ overflow: hidden;
+ }
}
.compact-boardlist {
- padding: 3px;
- padding-bottom: 0;
+ padding: 3px;
+ padding-bottom: 0;
}
.compact-boardlist .cb-item {
- display: inline-block;
- vertical-align: middle;
+ display: inline-block;
+ vertical-align: middle;
}
.compact-boardlist .cb-icon {
- padding-bottom: 1px;
+ padding-bottom: 1px;
}
.compact-boardlist .cb-fa {
- font-size: 21px;
- padding: 2px;
- padding-top: 0;
+ font-size: 21px;
+ padding: 2px;
+ padding-top: 0;
}
.compact-boardlist .cb-cat {
- padding: 5px 6px 8px 6px;
+ padding: 5px 6px 8px 6px;
}
.cb-menuitem {
- display: table-row;
+ display: table-row;
}
.cb-menuitem span {
- padding: 5px;
- display: table-cell;
- text-align: left;
- border-top: 1px solid rgba(0,0,0,0.5);
+ padding: 5px;
+ display: table-cell;
+ text-align: left;
+ border-top: 1px solid rgba(0,0,0,0.5);
}
.cb-menuitem span.cb-uri {
- text-align: right;
+ text-align: right;
}
.boardlist:not(.compact-boardlist) #watch-pinned::before {
- content: " [ ";
+ content: " [ ";
}
.boardlist:not(.compact-boardlist) #watch-pinned::after {
- content: " ] ";
+ content: " ] ";
}
.boardlist:not(.compact-boardlist) #watch-pinned {
- display: inline;
+ display: inline;
}
.boardlist:not(.compact-boardlist) #watch-pinned a {
- margin-left: 3pt;
+ margin-left: 3pt;
}
.boardlist:not(.compact-boardlist) #watch-pinned a:first-child {
- margin-left: 0pt;
+ margin-left: 0pt;
}
.compact-boardlist #watch-pinned {
- display: inline-block;
- vertical-align: middle;
+ display: inline-block;
+ vertical-align: middle;
}
.new-posts {
- opacity: 0.6;
- margin-top: 1em;
+ opacity: 0.6;
+ margin-top: 1em;
}
.new-threads {
- text-align: center;
+ text-align: center;
}
#options_handler, #alert_handler {
- position: fixed;
- top: 0px;
- left: 0px;
- right: 0px;
- bottom: 0px;
- width: 100%;
- height: 100%;
- text-align: center;
- z-index: 9900;
+ position: fixed;
+ top: 0px;
+ left: 0px;
+ right: 0px;
+ bottom: 0px;
+ width: 100%;
+ height: 100%;
+ text-align: center;
+ z-index: 9900;
}
#options_background, #alert_background {
- background: black;
- opacity: 0.5;
- position: absolute;
- top: 0px;
- left: 0px;
- right: 0px;
- bottom: 0px;
- width: 100%;
- height: 100%;
- z-index: -1;
+ background: black;
+ opacity: 0.5;
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ right: 0px;
+ bottom: 0px;
+ width: 100%;
+ height: 100%;
+ z-index: -1;
}
#options_div, #alert_div {
- background-color: #d6daf0;
- border: 1px solid black;
- display: inline-block;
- position: relative;
- margin-top: 20px;
+ background-color: #d6daf0;
+ border: 1px solid black;
+ display: inline-block;
+ position: relative;
+ margin-top: 20px;
}
#options_div {
- width: 620px;
- height: 400px;
- resize: both;
- overflow: auto;
+ width: 620px;
+ height: 400px;
+ resize: both;
+ overflow: auto;
}
#alert_div {
- width: 500px;
+ width: 500px;
}
#alert_message {
- text-align: center;
- margin: 13px;
- font-size: 110%;
+ text-align: center;
+ margin: 13px;
+ font-size: 110%;
}
.alert_button {
- margin-bottom: 13px;
+ margin-bottom: 13px;
}
#options_div textarea {
- max-width: 100%;
+ max-width: 100%;
}
#options_close, #alert_close {
- top: 0px;
- right: 0px;
- position: absolute;
- margin-right: 3px;
- font-size: 20px;
- z-index: 100;
+ top: 0px;
+ right: 0px;
+ position: absolute;
+ margin-right: 3px;
+ font-size: 20px;
+ z-index: 100;
}
#options_tablist {
- padding: 0px 5px;
- left: 0px;
- width: 90px;
- top: 0px;
- bottom: 0px;
- height: 100%;
- border-right: 1px solid black;
+ padding: 0px 5px;
+ left: 0px;
+ width: 90px;
+ top: 0px;
+ bottom: 0px;
+ height: 100%;
+ border-right: 1px solid black;
}
.options_tab_icon {
- padding: 5px;
- color: black;
- cursor: pointer;
+ padding: 5px;
+ color: black;
+ cursor: pointer;
}
.options_tab_icon.active {
- color: red;
+ color: red;
}
.options_tab_icon i {
- font-size: 20px;
+ font-size: 20px;
}
.options_tab_icon div {
- font-size: 11px;
+ font-size: 11px;
}
.options_tab {
- padding: 10px;
- position: absolute;
- top: 0px;
- bottom: 10px;
- left: 101px;
- right: 0px;
- text-align: left;
- font-size: 16px;
- overflow-y: auto;
+ padding: 10px;
+ position: absolute;
+ top: 0px;
+ bottom: 10px;
+ left: 101px;
+ right: 0px;
+ text-align: left;
+ font-size: 16px;
+ overflow-y: auto;
}
.options_tab h2 {
- text-align: center;
- margin-bottom: 5px;
+ text-align: center;
+ margin-bottom: 5px;
}
.mobile-style #options_div, .mobile-style #alert_div {
- display: block;
- width: 100%;
- height: 100%;
- margin-top: 0px;
+ display: block;
+ width: 100%;
+ height: 100%;
+ margin-top: 0px;
}
.mentioned {
- word-wrap: break-word;
+ word-wrap: break-word;
}
.poster_id {
- cursor: pointer;
- display: inline-block;
- -webkit-user-select: none;
- -moz-user-select: none;
- -ms-user-select: none;
- -o-user-select: none;
- user-select: none;
+ cursor: pointer;
+ display: inline-block;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ -o-user-select: none;
+ user-select: none;
}
.poster_id:hover {
- color: #800000!important;
+ color: #800000!important;
}
.poster_id::before {
- content: " ID: ";
+ content: " ID: ";
}
pre {
/* Better code tags */
- max-width:inherit;
- word-wrap:normal;
- overflow:auto;
- display: block!important;
- font-size:9pt;
- font-family:monospace;
+ max-width:inherit;
+ word-wrap:normal;
+ overflow:auto;
+ display: block!important;
+ font-size:9pt;
+ font-family:monospace;
}
span.pln {
- color:grey;
+ color:grey;
}
@media screen and (min-width: 768px) {
- .intro {
- clear: none;
- }
+ .intro {
+ clear: none;
+ }
- div.post div.body {
- clear: none;
- }
+ div.post div.body {
+ clear: none;
+ }
}
.clearfix {
@@ -1088,150 +1088,150 @@ div.boardlist a {
/* File selector */
.dropzone {
- color: #000;
- cursor: default;
- margin: auto;
- padding: 0px 4px;
- text-align: center;
- min-height: 50px;
- max-height: 140px;
- transition: 0.2s;
- background-color: rgba(200, 200, 200, 0.5);
- overflow-y: auto;
+ color: #000;
+ cursor: default;
+ margin: auto;
+ padding: 0px 4px;
+ text-align: center;
+ min-height: 50px;
+ max-height: 140px;
+ transition: 0.2s;
+ background-color: rgba(200, 200, 200, 0.5);
+ overflow-y: auto;
}
.dropzone-wrap {
- width: 100%;
+ width: 100%;
}
.dropzone .file-hint {
- color: rgba(0, 0, 0, 0.5);
- cursor: pointer;
- position: relative;
- margin-bottom: 5px;
- padding: 10px 0px;
- top: 5px;
- transition: 0.2s;
- border: 2px dashed rgba(125, 125, 125, 0.4);
+ color: rgba(0, 0, 0, 0.5);
+ cursor: pointer;
+ position: relative;
+ margin-bottom: 5px;
+ padding: 10px 0px;
+ top: 5px;
+ transition: 0.2s;
+ border: 2px dashed rgba(125, 125, 125, 0.4);
}
.file-hint:hover, .dropzone.dragover .file-hint {
- color: rgba(0, 0, 0, 1);
- border-color: rgba(125, 125, 125, 0.8);
+ color: rgba(0, 0, 0, 1);
+ border-color: rgba(125, 125, 125, 0.8);
}
.dropzone.dragover {
- background-color: rgba(200, 200, 200, 1);
+ background-color: rgba(200, 200, 200, 1);
}
.dropzone .file-thumbs {
- text-align: left;
- width: 100%;
+ text-align: left;
+ width: 100%;
}
.dropzone .tmb-container {
- padding: 3px;
- overflow-x: hidden;
- white-space: nowrap;
+ padding: 3px;
+ overflow-x: hidden;
+ white-space: nowrap;
}
.dropzone .file-tmb {
- height: 40px;
- width: 70px;
- cursor: pointer;
- display: inline-block;
- text-align: center;
- background-color: rgba(187, 187, 187, 0.5);
- background-size: cover;
- background-position: center;
+ height: 40px;
+ width: 70px;
+ cursor: pointer;
+ display: inline-block;
+ text-align: center;
+ background-color: rgba(187, 187, 187, 0.5);
+ background-size: cover;
+ background-position: center;
}
.dropzone .file-tmb span {
- font-weight: 600;
- position: relative;
- top: 13px;
+ font-weight: 600;
+ position: relative;
+ top: 13px;
}
.dropzone .tmb-filename {
- display: inline-block;
- vertical-align: bottom;
- bottom: 16px;
- position: relative;
- margin-left: 5px;
+ display: inline-block;
+ vertical-align: bottom;
+ bottom: 16px;
+ position: relative;
+ margin-left: 5px;
}
.dropzone .remove-btn {
- cursor: pointer;
- color: rgba(125, 125, 125, 0.5);
- display: inline-block;
- vertical-align: bottom;
- bottom: 10px;
- position: relative;
- margin-right: 5px;
- font-size: 20px
+ cursor: pointer;
+ color: rgba(125, 125, 125, 0.5);
+ display: inline-block;
+ vertical-align: bottom;
+ bottom: 10px;
+ position: relative;
+ margin-right: 5px;
+ font-size: 20px
}
.dropzone .remove-btn:hover {
- color: rgba(125, 125, 125, 1);
+ color: rgba(125, 125, 125, 1);
}
#thread_stats {
- display: inline;
- margin-left: 10px;
- margin-right: 10px;
+ display: inline;
+ margin-left: 10px;
+ margin-right: 10px;
}
/* Fileboard */
table.fileboard th, table.fileboard td {
- padding: 2px;
- text-align: center;
+ padding: 2px;
+ text-align: center;
}
table.fileboard .intro a {
- margin-left: 0px;
+ margin-left: 0px;
}
/* Gallery view */
#gallery_images {
- position: absolute;
- right: 0px;
- bottom: 0px;
- top: 0px;
- width: 12%;
- background-color: rgba(0, 0, 0, 0.4);
- overflow: auto;
+ position: absolute;
+ right: 0px;
+ bottom: 0px;
+ top: 0px;
+ width: 12%;
+ background-color: rgba(0, 0, 0, 0.4);
+ overflow: auto;
}
#gallery_toolbar {
- position: absolute;
- right: 12%;
- left: 0px;
- bottom: 0px;
- height: 32px;
- background-color: rgba(0, 0, 0, 0.4);
- text-align: right;
+ position: absolute;
+ right: 12%;
+ left: 0px;
+ bottom: 0px;
+ height: 32px;
+ background-color: rgba(0, 0, 0, 0.4);
+ text-align: right;
}
#gallery_images img {
- width: 100%;
+ width: 100%;
}
#gallery_toolbar a {
- font-size: 28px;
- padding-right: 5px;
+ font-size: 28px;
+ padding-right: 5px;
}
#gallery_main {
- position: absolute;
- left: 0px;
- right: 12%;
- bottom: 32px;
- top: 0px;
- padding: 10px;
+ position: absolute;
+ left: 0px;
+ right: 12%;
+ bottom: 32px;
+ top: 0px;
+ padding: 10px;
}
#gallery_images img {
- opacity: 0.6;
- -webkit-transition: all 0.5s;
- transition: all 0.5s;
+ opacity: 0.6;
+ -webkit-transition: all 0.5s;
+ transition: all 0.5s;
}
#gallery_images img:hover, #gallery_images img.active {
- opacity: 1;
+ opacity: 1;
}
#gallery_images img.active {
- -webkit-box-shadow: 0px 0px 29px 2px rgba(255,255,255,1);
- -moz-box-shadow: 0px 0px 29px 2px rgba(255,255,255,1);
- box-shadow: 0px 0px 29px 2px rgba(255,255,255,1);
- z-index: 1;
+ -webkit-box-shadow: 0px 0px 29px 2px rgba(255,255,255,1);
+ -moz-box-shadow: 0px 0px 29px 2px rgba(255,255,255,1);
+ box-shadow: 0px 0px 29px 2px rgba(255,255,255,1);
+ z-index: 1;
}
#gallery_main img, #gallery_main video {
- max-width: 100%;
- max-height: 100%;
- position: absolute;
+ max-width: 100%;
+ max-height: 100%;
+ position: absolute;
}
.own_post {
font-style: italic;
@@ -1243,80 +1243,80 @@ div.mix {
}
body {
- background: #0A0C11 !important;
+ background: #0A0C11 !important;
}
fieldset {
- margin: 10px 0 !important;
+ margin: 10px 0 !important;
}
legend {
- background:#222 !important;
- border:1px solid #888 !important;
- color:#FFF !important;
+ background:#222 !important;
+ border:1px solid #888 !important;
+ color:#FFF !important;
}
h1 {
- color: #FFF !important;
- font-family: Heading !important;
+ color: #FFF !important;
+ font-family: Heading !important;
}
fieldset ul li a {
- color: #FFF !important;
+ color: #FFF !important;
}
p.unimportant {
- color: #888 !important;
+ color: #888 !important;
}
.ban {
- background-color: #111 !important;
- color: #AAA;
+ background-color: #111 !important;
+ color: #AAA;
}
.boardlinks {
- text-align: center !important;
+ text-align: center !important;
}
#overlay {
- position: fixed;
- left: 0;
- top: 0;
- width: 100%;
- height: 100%;
- pointer-events: none;
- z-index: 1000;
- background-repeat: all;
- background-position: 0px 0px;
+ position: fixed;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+ z-index: 1000;
+ background-repeat: all;
+ background-position: 0px 0px;
- animation-name: Static;
- animation-duration: 0.5s;
- animation-iteration-count: infinite;
- animation-timing-function: steps(32);
+ animation-name: Static;
+ animation-duration: 0.5s;
+ animation-iteration-count: infinite;
+ animation-timing-function: steps(32);
- box-shadow: inset 0px 0px 10em rgba(0,0,0,0.4);
+ box-shadow: inset 0px 0px 10em rgba(0,0,0,0.4);
}
@keyframes Static {
- 0% { background-position: 0px 0px; }
- 100% { background-position: 0px 32px; }
+ 0% { background-position: 0px 0px; }
+ 100% { background-position: 0px 32px; }
}
#overlay2 {
- position: fixed;
- left: 0;
- top: 0;
- width: 100%;
- height: 100%;
- pointer-events: none;
- z-index: 1000;
- background-repeat: all;
- background-position: 0px 0px;
+ position: fixed;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+ z-index: 1000;
+ background-repeat: all;
+ background-position: 0px 0px;
- animation-name: Static;
- animation-duration: 0.2s;
- animation-iteration-count: infinite;
- animation-timing-function: steps(32);
+ animation-name: Static;
+ animation-duration: 0.2s;
+ animation-iteration-count: infinite;
+ animation-timing-function: steps(32);
}
body {
@@ -1326,7 +1326,7 @@ body {
font-size: 16px;
}
span.quote {
- color:#B8D962;
+ color:#B8D962;
}
h1 {
font-size: 20pt;
@@ -1364,7 +1364,7 @@ div.post.reply {
border: #555555 1px solid;
box-shadow: 4px 4px #555;
- @media (max-width: 48em) {
+ @media (max-width: 48em) {
border-left-style: none;
border-right-style: none;
}
@@ -1372,11 +1372,6 @@ div.post.reply {
div.post.reply.highlighted {
background: #555;
border: transparent 1px solid;
-
- @media (max-width: 48em) {
- border-left-style: none;
- border-right-style: none;
- }
}
div.post.reply div.body a:link, div.post.reply div.body a:visited {
color: #CCCCCC;
@@ -1436,7 +1431,7 @@ div.banner {
font-size: 16px;
}
div.banner a {
- color:#000;
+ color:#000;
}
input[type="submit"] {
background: #333333;
@@ -1449,7 +1444,7 @@ input[type="submit"]:hover {
color: #32DD72;
}
input[type="text"]:focus {
- border:#aaa 1px solid;
+ border:#aaa 1px solid;
}
p.fileinfo a:hover {
text-decoration: underline;
@@ -1458,9 +1453,9 @@ span.trip {
color: #AAAAAA;
}
.bar.bottom {
- bottom: 0px;
- border-top: 1px solid #333333;
- background-color: #666666;
+ bottom: 0px;
+ border-top: 1px solid #333333;
+ background-color: #666666;
}
div.pages {
@@ -1479,7 +1474,7 @@ hr {
}
div.boardlist {
color: #999999;
- background-color: rgba(12%, 12%, 12%, 0.10);
+ background-color: rgba(12%, 12%, 12%, 0.10);
}
div.ban {
@@ -1497,13 +1492,13 @@ table.modlog tr th {
}
.desktop-style div.boardlist:not(.bottom) {
- text-shadow: black 1px 1px 1px, black -1px -1px 1px, black -1px 1px 1px, black 1px -1px 1px;
- background-color: #666666;
+ text-shadow: black 1px 1px 1px, black -1px -1px 1px, black -1px 1px 1px, black 1px -1px 1px;
+ background-color: #666666;
}
.desktop-style div.boardlist:not(.bottom):hover, .desktop-style div.boardlist:not(.bottom).cb-menu {
- background-color: rgba(30%, 30%, 30%, 0.65);
+ background-color: rgba(30%, 30%, 30%, 0.65);
}
div.report {
diff --git a/templates/captcha_script.html b/templates/captcha_script.html
index c2f5d87f..79b0b230 100644
--- a/templates/captcha_script.html
+++ b/templates/captcha_script.html
@@ -1,6 +1,6 @@
-{% if config.captcha.mode == 'hcaptcha' %}
+{% if config.hcaptcha %}
{% endif %}
-{% if config.captcha.mode == 'turnstile' %}
+{% if config.turnstile %}
{% endif %}
diff --git a/templates/header.html b/templates/header.html
index 27b1a31f..7e3f9c54 100644
--- a/templates/header.html
+++ b/templates/header.html
@@ -28,6 +28,6 @@
{% endif %}
{% endif %}
- {% if config.captcha.mode == 'recaptcha' %}
+ {% if config.recaptcha %}
{% endif %}
diff --git a/templates/installer/config.html b/templates/installer/config.html
index 00a5b241..973328f5 100644
--- a/templates/installer/config.html
+++ b/templates/installer/config.html
@@ -88,9 +88,6 @@
Secure trip (##) salt:
- Poster password salt:
-
-
Additional configuration:
diff --git a/templates/main.js b/templates/main.js
index c4025646..d86d5b70 100755
--- a/templates/main.js
+++ b/templates/main.js
@@ -231,6 +231,28 @@ var resourceVersion = document.currentScript.getAttribute('data-resource-version
{% endif %}
{% raw %}
+function initStyleChooser() {
+ var newElement = document.createElement('div');
+ newElement.className = 'styles';
+
+ for (styleName in styles) {
+ if (styleName) {
+ var style = document.createElement('a');
+ style.innerHTML = '[' + styleName + ']';
+ style.onclick = function() {
+ changeStyle(this.innerHTML.substring(1, this.innerHTML.length - 1), this);
+ };
+ if (styleName == selectedstyle) {
+ style.className = 'selected';
+ }
+ style.href = 'javascript:void(0);';
+ newElement.appendChild(style);
+ }
+ }
+
+ document.getElementById('bottom-hud').before(newElement);
+}
+
function getCookie(cookie_name) {
let results = document.cookie.match('(^|;) ?' + cookie_name + '=([^;]*)(;|$)');
if (results) {
@@ -243,48 +265,27 @@ function getCookie(cookie_name) {
{% endraw %}
/* BEGIN CAPTCHA REGION */
-{% if config.captcha.mode == 'hcaptcha' or config.captcha.mode == 'turnstile' %} // If any captcha
+{% if config.hcaptcha or config.turnstile %} // If any captcha
// Global captcha object. Assigned by `onCaptchaLoad()`.
var captcha_renderer = null;
-// Captcha widget id of the post form.
-var postCaptchaId = null;
-{% if config.captcha.mode == 'hcaptcha' %} // If hcaptcha
+{% if config.hcaptcha %} // If hcaptcha
function onCaptchaLoadHcaptcha() {
- if ((captchaMode === 'static' || (captchaMode === 'dynamic' && isDynamicCaptchaEnabled()))
- && captcha_renderer === null
- && (active_page === 'index' || active_page === 'catalog' || active_page === 'thread')) {
+ if (captcha_renderer === null && (active_page === 'index' || active_page === 'catalog' || active_page === 'thread')) {
let renderer = {
- /**
- * @returns {object} Opaque widget id.
- */
applyOn: (container, params) => hcaptcha.render(container, {
- sitekey: "{{ config.captcha.hcaptcha.public }}",
+ sitekey: "{{ config.hcaptcha_public }}",
callback: params['on-success'],
}),
- /**
- * @returns {void}
- */
remove: (widgetId) => { /* Not supported */ },
- /**
- * @returns {void}
- */
- reset: (widgetId) => hcaptcha.reset(widgetId),
- /**
- * @returns {bool}
- */
- hasResponse: (widgetId) => !!hcaptcha.getResponse(widgetId),
- /**
- * @returns {void}
- */
- execute: (widgetId) => hcaptcha.execute(widgetId)
+ reset: (widgetId) => hcaptcha.reset(widgetId)
};
onCaptchaLoad(renderer);
}
}
{% endif %} // End if hcaptcha
-{% if config.captcha.mode == 'turnstile' %} // If turnstile
+{% if config.turnstile %} // If turnstile
// Wrapper function to be called from thread.html
window.onCaptchaLoadTurnstile_post_reply = function() {
@@ -298,16 +299,11 @@ window.onCaptchaLoadTurnstile_post_thread = function() {
// Should be called by the captcha API when it's ready. Ugly I know... D:
function onCaptchaLoadTurnstile(action) {
- if ((captchaMode === 'static' || (captchaMode === 'dynamic' && isDynamicCaptchaEnabled()))
- && captcha_renderer === null
- && (active_page === 'index' || active_page === 'catalog' || active_page === 'thread')) {
+ if (captcha_renderer === null && (active_page === 'index' || active_page === 'catalog' || active_page === 'thread')) {
let renderer = {
- /**
- * @returns {object} Opaque widget id.
- */
applyOn: function(container, params) {
let widgetId = turnstile.render('#' + container, {
- sitekey: "{{ config.captcha.turnstile.public }}",
+ sitekey: "{{ config.turnstile_public }}",
action: action,
callback: params['on-success'],
});
@@ -316,22 +312,8 @@ function onCaptchaLoadTurnstile(action) {
}
return widgetId;
},
- /**
- * @returns {void}
- */
remove: (widgetId) => turnstile.remove(widgetId),
- /**
- * @returns {void}
- */
- reset: (widgetId) => turnstile.reset(widgetId),
- /**
- * @returns {bool}
- */
- hasResponse: (widgetId) => !!turnstile.getResponse(widgetId),
- /**
- * @returns {void}
- */
- execute: (widgetId) => turnstile.execute(widgetId)
+ reset: (widgetId) => turnstile.reset(widgetId)
};
onCaptchaLoad(renderer);
@@ -353,7 +335,6 @@ function onCaptchaLoad(renderer) {
if (widgetId === null) {
console.error('Could not render captcha!');
}
- postCaptchaId = widgetId;
document.addEventListener('afterdopost', function(e) {
// User posted! Reset the captcha.
renderer.reset(widgetId);
@@ -361,8 +342,6 @@ function onCaptchaLoad(renderer) {
}
{% if config.dynamic_captcha %} // If dynamic captcha
-var captchaMode = 'dynamic';
-
function isDynamicCaptchaEnabled() {
let cookie = getCookie('captcha-required');
return cookie === '1';
@@ -376,15 +355,8 @@ function initCaptcha() {
}
}
}
-{% else %}
-var captchaMode = 'static';
{% endif %} // End if dynamic captcha
{% else %} // Else if any captcha
-var captchaMode = 'none';
-
-function isDynamicCaptchaEnabled() {
- return false;
-}
// No-op for `init()`.
function initCaptcha() {}
{% endif %} // End if any captcha
@@ -440,13 +412,6 @@ function doPost(form) {
saved[document.location] = form.elements['body'].value;
sessionStorage.body = JSON.stringify(saved);
- if (captchaMode === 'static' || (captchaMode === 'dynamic' && isDynamicCaptchaEnabled())) {
- if (captcha_renderer && postCaptchaId && !captcha_renderer.hasResponse(postCaptchaId)) {
- captcha_renderer.execute(postCaptchaId);
- return false;
- }
- }
-
// Needs to be delayed by at least 1 frame, otherwise it may reset the form (read captcha) fields before they're sent.
setTimeout(() => document.dispatchEvent(new Event('afterdopost')));
return form.elements['body'].value != "" || (form.elements['file'] && form.elements['file'].value != "") || (form.elements.file_url && form.elements['file_url'].value != "");
@@ -563,6 +528,7 @@ var script_settings = function(script_name) {
};
function init() {
+ initStyleChooser();
initCaptcha();
{% endraw %}
diff --git a/templates/mod/dashboard.html b/templates/mod/dashboard.html
index b765ce5f..e36e3c66 100644
--- a/templates/mod/dashboard.html
+++ b/templates/mod/dashboard.html
@@ -1,4 +1,3 @@
-{% if config.url_banner %} {% endif %}
{% for board in boards %}
diff --git a/templates/mod/login.html b/templates/mod/login.html
index 6ed8dc64..32869872 100644
--- a/templates/mod/login.html
+++ b/templates/mod/login.html
@@ -1,5 +1,4 @@
{% if error %}{{ error }} {% endif %}
-{% if config.url_banner %} {% endif %}