diff --git a/.gitignore b/.gitignore
index 93cac6d4..3205c64b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -70,9 +70,6 @@ tf/
/mod/
/random/
-# Banners
-static/banners/*
-
#Fonts
stylesheets/fonts
diff --git a/compose.yml b/compose.yml
index c04c85f9..526e18c6 100644
--- a/compose.yml
+++ b/compose.yml
@@ -28,8 +28,6 @@ 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
new file mode 100644
index 00000000..e2050606
--- /dev/null
+++ b/inc/Data/Driver/ErrorLogLogDriver.php
@@ -0,0 +1,28 @@
+name = $name;
+ $this->level = $level;
+ }
+
+ public function log(int $level, string $message): void {
+ if ($level <= $this->level) {
+ $lv = $this->levelToString($level);
+ $line = "{$this->name} $lv: $message";
+ \error_log($line, 0, null, null);
+ }
+ }
+}
diff --git a/inc/Data/Driver/FileLogDriver.php b/inc/Data/Driver/FileLogDriver.php
new file mode 100644
index 00000000..2c9f14a0
--- /dev/null
+++ b/inc/Data/Driver/FileLogDriver.php
@@ -0,0 +1,61 @@
+fd = \fopen($file_path, 'a');
+ if ($this->fd === false) {
+ throw new \RuntimeException("Unable to open log file at $file_path");
+ }
+
+ $this->name = $name;
+ $this->level = $level;
+
+ // In some cases PHP does not run the destructor.
+ \register_shutdown_function([$this, 'close']);
+ }
+
+ public function __destruct() {
+ $this->close();
+ }
+
+ public function log(int $level, string $message): void {
+ if ($level <= $this->level) {
+ $lv = $this->levelToString($level);
+ $line = "{$this->name} $lv: $message\n";
+ \flock($this->fd, LOCK_EX);
+ \fwrite($this->fd, $line);
+ \fflush($this->fd);
+ \flock($this->fd, LOCK_UN);
+ }
+ }
+
+ public function close() {
+ \flock($this->fd, LOCK_UN);
+ \fclose($this->fd);
+ }
+}
diff --git a/inc/Data/Driver/LogDriver.php b/inc/Data/Driver/LogDriver.php
new file mode 100644
index 00000000..fddc3f27
--- /dev/null
+++ b/inc/Data/Driver/LogDriver.php
@@ -0,0 +1,22 @@
+name = $name;
+ $this->level = $level;
+ }
+
+ public function log(int $level, string $message): void {
+ if ($level <= $this->level) {
+ $lv = $this->levelToString($level);
+ \fwrite(\STDERR, "{$this->name} $lv: $message\n");
+ }
+ }
+}
diff --git a/inc/Data/Driver/SyslogLogDriver.php b/inc/Data/Driver/SyslogLogDriver.php
new file mode 100644
index 00000000..c0df5304
--- /dev/null
+++ b/inc/Data/Driver/SyslogLogDriver.php
@@ -0,0 +1,35 @@
+level = $level;
+ }
+
+ public function log(int $level, string $message): void {
+ if ($level <= $this->level) {
+ if (isset($_SERVER['REMOTE_ADDR'], $_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'])) {
+ // CGI
+ \syslog($level, "$message - client: {$_SERVER['REMOTE_ADDR']}, request: \"{$_SERVER['REQUEST_METHOD']} {$_SERVER['REQUEST_URI']}\"");
+ } else {
+ \syslog($level, $message);
+ }
+ }
+ }
+}
diff --git a/inc/Data/IpNoteQueries.php b/inc/Data/IpNoteQueries.php
new file mode 100644
index 00000000..ba6fdb15
--- /dev/null
+++ b/inc/Data/IpNoteQueries.php
@@ -0,0 +1,76 @@
+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
new file mode 100644
index 00000000..b33e7ac2
--- /dev/null
+++ b/inc/Data/PageFetchResult.php
@@ -0,0 +1,15 @@
+pdo->prepare('DELETE FROM `reports` WHERE `post` = :id AND `board` = :board');
$query->bindValue(':id', $post_id, \PDO::PARAM_INT);
$query->bindValue(':board', $board);
$query->execute();
@@ -93,8 +88,10 @@ class ReportQueries {
if ($get_invalid) {
// Get the reports without a post.
$invalid = [];
- if (isset($report_posts[$report['board']][$report['post']])) {
- $invalid[] = $report;
+ foreach ($raw_reports as $report) {
+ if (isset($report_posts[$report['board']][$report['post']])) {
+ $invalid[] = $report;
+ }
}
return $invalid;
} else {
@@ -116,12 +113,10 @@ class ReportQueries {
/**
* @param \PDO $pdo PDO connection.
- * @param CacheDriver $cache Cache driver.
* @param bool $auto_maintenance If the auto maintenance should be enabled.
*/
- public function __construct(\PDO $pdo, CacheDriver $cache, bool $auto_maintenance) {
+ public function __construct(\PDO $pdo, bool $auto_maintenance) {
$this->pdo = $pdo;
- $this->cache = $cache;
$this->auto_maintenance = $auto_maintenance;
}
@@ -131,18 +126,13 @@ class ReportQueries {
* @return int The number of reports.
*/
public function getCount(): int {
- $ret = $this->cache->get(self::CACHE_KEY);
- if ($ret === null) {
- $query = $this->pdo->prepare("SELECT `board`, `post`, `id` FROM `reports`");
- $query->execute();
- $raw_reports = $query->fetchAll(\PDO::FETCH_ASSOC);
- $valid_reports = $this->filterReports($raw_reports, false, null);
- $count = \count($valid_reports);
+ $query = $this->pdo->prepare('SELECT `board`, `post`, `id` FROM `reports`');
+ $query->execute();
+ $raw_reports = $query->fetchAll(\PDO::FETCH_ASSOC);
+ $valid_reports = $this->filterReports($raw_reports, false, null);
+ $count = \count($valid_reports);
- $this->cache->set(self::CACHE_KEY, $count);
- return $count;
- }
- return $ret;
+ return $count;
}
/**
@@ -151,8 +141,8 @@ class ReportQueries {
* @param int $id The id of the report to fetch.
* @return ?array An array of the given report with the `board` and `ip` fields. Null if no such report exists.
*/
- public function getReportById(int $id): array {
- $query = prepare("SELECT `board`, `ip` FROM ``reports`` WHERE `id` = :id");
+ public function getReportById(int $id): ?array {
+ $query = prepare('SELECT `board`, `ip` FROM ``reports`` WHERE `id` = :id');
$query->bindValue(':id', $id);
$query->execute();
@@ -171,7 +161,7 @@ class ReportQueries {
* @return array The reports with the associated post data.
*/
public function getReportsWithPosts(int $count): array {
- $query = $this->pdo->prepare("SELECT * FROM `reports` ORDER BY `time`");
+ $query = $this->pdo->prepare('SELECT * FROM `reports` ORDER BY `time`');
$query->execute();
$raw_reports = $query->fetchAll(\PDO::FETCH_ASSOC);
return $this->joinReportPosts($raw_reports, $count);
@@ -183,7 +173,7 @@ class ReportQueries {
* @return int The number of reports deleted.
*/
public function purge(): int {
- $query = $this->pdo->prepare("SELECT `board`, `post`, `id` FROM `reports`");
+ $query = $this->pdo->prepare('SELECT `board`, `post`, `id` FROM `reports`');
$query->execute();
$raw_reports = $query->fetchAll(\PDO::FETCH_ASSOC);
$invalid_reports = $this->filterReports($raw_reports, true, null);
@@ -200,11 +190,9 @@ class ReportQueries {
* @param int $id The report id.
*/
public function deleteById(int $id) {
- $query = $this->pdo->prepare("DELETE FROM `reports` WHERE `id` = :id");
+ $query = $this->pdo->prepare('DELETE FROM `reports` WHERE `id` = :id');
$query->bindValue(':id', $id, \PDO::PARAM_INT);
$query->execute();
- // The caller may actually delete a valid post, so we need to invalidate the cache.
- $this->cache->delete(self::CACHE_KEY);
}
/**
@@ -213,11 +201,9 @@ class ReportQueries {
* @param string $ip The reporter ip.
*/
public function deleteByIp(string $ip) {
- $query = $this->pdo->prepare("DELETE FROM `reports` WHERE `ip` = :ip");
+ $query = $this->pdo->prepare('DELETE FROM `reports` WHERE `ip` = :ip');
$query->bindValue(':ip', $ip);
$query->execute();
- // The caller may actually delete a valid post, so we need to invalidate the cache.
- $this->cache->delete(self::CACHE_KEY);
}
/**
@@ -237,7 +223,5 @@ class ReportQueries {
$query->bindValue(':post', $post_id, \PDO::PARAM_INT);
$query->bindValue(':reason', $reason);
$query->execute();
-
- $this->cache->delete(self::CACHE_KEY);
}
}
diff --git a/inc/Data/UserPostQueries.php b/inc/Data/UserPostQueries.php
new file mode 100644
index 00000000..1c203431
--- /dev/null
+++ b/inc/Data/UserPostQueries.php
@@ -0,0 +1,159 @@
+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 e9fedc75..cf82dcc8 100644
--- a/inc/anti-bot.php
+++ b/inc/anti-bot.php
@@ -196,37 +196,36 @@ function _create_antibot($pdo, $board, $thread) {
$antibot = new AntiBot(array($board, $thread));
try {
- $pdo->beginTransaction();
+ retry_on_deadlock(3, function() use ($config, $pdo, $thread, $board, $antibot, $purged_old_antispam) {
+ try {
+ $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();
+ }
- retry_on_deadlock(4, function() use($config, $board, $thread) {
- // 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();
- try {
- $hash = $antibot->hash();
- retry_on_deadlock(2, function() use($board, $thread, $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);
@@ -234,19 +233,21 @@ function _create_antibot($pdo, $board, $thread) {
$query->bindValue(':hash', $hash);
// Throws on error.
$query->execute();
- });
- } catch(\PDOException $e) {
- if ($e->errorInfo === null || $e->errorInfo[1] != MYSQL_ER_LOCK_DEADLOCK) {
+
+ $pdo->commit();
+ } catch (\Exception $e) {
+ $pdo->rollBack();
throw $e;
- } else {
- error_log('Multiple deadlocks on _create_antibot while inserting, skipping');
}
- }
+ });
} catch (\PDOException $e) {
$pdo->rollBack();
- throw $e;
+ 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');
+ }
}
- $pdo->commit();
return $antibot;
}
diff --git a/inc/config.php b/inc/config.php
index 633cdd33..71b0fbf4 100644
--- a/inc/config.php
+++ b/inc/config.php
@@ -63,9 +63,29 @@
// been generated. This keeps the script from querying the database and causing strain when not needed.
$config['has_installed'] = '.installed';
- // Use syslog() for logging all error messages and unauthorized login attempts.
+ // Deprecated, use 'log_system'.
$config['syslog'] = false;
+ $config['log_system'] = [
+ /*
+ * Log all error messages and unauthorized login attempts.
+ * Can be "syslog", "error_log" (default), "file", or "stderr".
+ */
+ 'type' => 'error_log',
+ // The application name used by the logging system. Defaults to "tinyboard" for backwards compatibility.
+ 'name' => 'tinyboard',
+ /*
+ * Only relevant if 'log_system' is set to "syslog". If true, double print the logs also in stderr. Defaults to
+ * false.
+ */
+ 'syslog_stderr' => false,
+ /*
+ * Only relevant if "log_system" is set to `file`. Sets the file that vichan will log to. Defaults to
+ * '/var/log/vichan.log'.
+ */
+ 'file_path' => '/var/log/vichan.log',
+ ];
+
// Use `host` via shell_exec() to lookup hostnames, avoiding query timeouts. May not work on your system.
// Requires safe_mode to be disabled.
$config['dns_system'] = false;
@@ -200,6 +220,9 @@
// 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
@@ -920,10 +943,6 @@
// Location of thumbnail to use for deleted images.
$config['image_deleted'] = 'static/deleted.png';
- // When a thumbnailed image is going to be the same (in dimension), just copy the entire file and use
- // that as a thumbnail instead of resizing/redrawing.
- $config['minimum_copy_resize'] = false;
-
// Maximum image upload size in bytes.
$config['max_filesize'] = 10 * 1024 * 1024; // 10MB
// Maximum image dimensions.
@@ -962,15 +981,6 @@
// Set this to true if you're using Linux and you can execute `md5sum` binary.
$config['gnu_md5'] = false;
- // Use Tesseract OCR to retrieve text from images, so you can use it as a spamfilter.
- $config['tesseract_ocr'] = false;
-
- // Tesseract parameters
- $config['tesseract_params'] = '';
-
- // Tesseract preprocess command
- $config['tesseract_preprocess_command'] = 'convert -monochrome %s -';
-
// Number of posts in a "View Last X Posts" page
$config['noko50_count'] = 50;
// Number of posts a thread needs before it gets a "View Last X Posts" page.
@@ -1192,10 +1202,22 @@
// 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(
- array(
- '/^https?:\/\/(\w+\.)?youtube\.com\/watch\?v=([a-zA-Z0-9\-_]{10,11})(&.+)?$/i',
- 'VIDEO '
- ),
+ [
+ '/^(?:(?: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+\.)?vimeo\.com\/(\d{2,10})(\?.+)?$/i',
''
@@ -1212,10 +1234,18 @@
'/^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.
@@ -1521,8 +1551,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 each page of ?/IP/x.x.x.x.
- $config['mod']['ip_recentposts'] = 5;
+ // 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;
// Number of posts to display on the reports page.
$config['mod']['recent_reports'] = 10;
@@ -2000,12 +2030,6 @@
// 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 6ff656fe..11a153ec 100644
--- a/inc/context.php
+++ b/inc/context.php
@@ -1,8 +1,8 @@
$config,
+ LogDriver::class => function($c) {
+ $config = $c->get('config');
+
+ $name = $config['log_system']['name'];
+ $level = $config['debug'] ? LogDriver::DEBUG : LogDriver::NOTICE;
+ $backend = $config['log_system']['type'];
+
+ $legacy_syslog = isset($config['syslog']) && $config['syslog'];
+
+ // Check 'syslog' for backwards compatibility.
+ if ($legacy_syslog || $backend === 'syslog') {
+ $log_driver = new SyslogLogDriver($name, $level, $config['log_system']['syslog_stderr']);
+ if ($legacy_syslog) {
+ $log_driver->log(LogDriver::NOTICE, 'The configuration setting \'syslog\' is deprecated. Please use \'log_system\' instead');
+ }
+ return $log_driver;
+ } elseif ($backend === 'file') {
+ return new FileLogDriver($name, $level, $config['log_system']['file_path']);
+ } elseif ($backend === 'stderr') {
+ return new StderrLogDriver($name, $level);
+ } elseif ($backend === 'error_log') {
+ return new ErrorLogLogDriver($name, $level);
+ } else {
+ $log_driver = new ErrorLogLogDriver($name, $level);
+ $log_driver->log(LogDriver::ERROR, "Unknown 'log_system' value '$backend', using 'error_log' default");
+ return $log_driver;
+ }
+ },
CacheDriver::class => function($c) {
// Use the global for backwards compatibility.
return \cache::getCache();
@@ -44,8 +72,11 @@ function build_context(array $config): Context {
ReportQueries::class => function($c) {
$auto_maintenance = (bool)$c->get('config')['auto_maintenance'];
$pdo = $c->get(\PDO::class);
- $cache = $c->get(CacheDriver::class);
- return new ReportQueries($pdo, $cache, $auto_maintenance);
- }
+ 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/database.php b/inc/database.php
index 385a3135..f5ea73bd 100644
--- a/inc/database.php
+++ b/inc/database.php
@@ -66,7 +66,7 @@ function sql_open() {
$dsn = $config['db']['type'] . ':' .
($unix_socket ? 'unix_socket=' . $unix_socket : 'host=' . $config['db']['server']) .
- ';dbname=' . $config['db']['database'] . ';charset=utf8mb4';
+ ';dbname=' . $config['db']['database'];
if (!empty($config['db']['dsn']))
$dsn .= ';' . $config['db']['dsn'];
try {
@@ -84,6 +84,9 @@ function sql_open() {
if ($config['debug']) {
$debug['time']['db_connect'] = '~' . round((microtime(true) - $start) * 1000, 2) . 'ms';
+ if ($config['db']['type'] == "mysql") {
+ query('SET NAMES utf8') or error(db_error());
+ }
}
return $pdo;
} catch(PDOException $e) {
diff --git a/inc/filters.php b/inc/filters.php
index 2a66cd2a..97cbc524 100644
--- a/inc/filters.php
+++ b/inc/filters.php
@@ -4,23 +4,26 @@
* 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))
@@ -29,11 +32,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) {
@@ -69,10 +72,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) {
@@ -135,46 +138,42 @@ class Filter {
error('Unknown filter condition: ' . $condition);
}
}
-
- public function action() {
+
+ public function action(Context $ctx) {
global $board;
$this->add_note = isset($this->add_note) ? $this->add_note : false;
if ($this->add_note) {
- $query = prepare('INSERT INTO ``ip_notes`` VALUES (NULL, :ip, :mod, :time, :body)');
- $query->bindValue(':ip', $_SERVER['REMOTE_ADDR']);
- $query->bindValue(':mod', -1);
- $query->bindValue(':time', time());
- $query->bindValue(':body', "Autoban message: ".$this->post['body']);
- $query->execute() or error(db_error($query));
- }
+ $note_queries = $ctx->get(IpNoteQueries::class);
+ $note_queries->add($_SERVER['REMOTE_ADDR'], -1, 'Autoban message: ' . $this->post['body']);
+ }
if (isset ($this->action)) switch($this->action) {
case 'reject':
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) {
@@ -184,7 +183,7 @@ class Filter {
} else {
$NOT = false;
}
-
+
if ($this->match($condition, $value) == $NOT)
return false;
}
@@ -194,11 +193,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 {
@@ -208,18 +207,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(array $post) {
+function do_filters(Context $ctx, 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;
@@ -232,15 +231,15 @@ function do_filters(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();
+ $filter->action($ctx);
}
}
-
+
purge_flood_table();
}
diff --git a/inc/functions.php b/inc/functions.php
index a355e53c..def00287 100644
--- a/inc/functions.php
+++ b/inc/functions.php
@@ -745,24 +745,23 @@ function hasPermission($action = null, $board = null, $_mod = null) {
function listBoards($just_uri = false) {
global $config;
- $just_uri ? $cache_name = 'all_boards_uri' : $cache_name = 'all_boards';
+ $cache_name = $just_uri ? 'all_boards_uri' : '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`") or error(db_error());
- $boards = $query->fetchAll();
- } else {
- $boards = array();
- $query = query("SELECT `uri` FROM ``boards``") or error(db_error());
- while ($board = $query->fetchColumn()) {
- $boards[] = $board;
- }
}
- if ($config['cache']['enabled'])
+ if (!$just_uri) {
+ $query = query('SELECT * FROM ``boards`` ORDER BY `uri`');
+ $boards = $query->fetchAll();
+ } else {
+ $query = query('SELECT `uri` FROM ``boards``');
+ $boards = $query->fetchAll(\PDO::FETCH_COLUMN);
+ }
+
+ if ($config['cache']['enabled']) {
cache::set($cache_name, $boards);
+ }
return $boards;
}
@@ -2070,7 +2069,7 @@ function remove_modifiers($body) {
return preg_replace('@(.+?) @usm', '', $body);
}
-function markup(&$body, $track_cites = false, $op = false) {
+function markup(&$body, $track_cites = false) {
global $board, $config, $markup_urls;
$modifiers = extract_modifiers($body);
@@ -2169,12 +2168,15 @@ function markup(&$body, $track_cites = false, $op = 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);
}
}
}
@@ -3083,3 +3085,8 @@ 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
deleted file mode 100755
index fb315548..00000000
--- a/inc/lib/IP/LICENSE
+++ /dev/null
@@ -1,20 +0,0 @@
-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
deleted file mode 100755
index 26a2c2b7..00000000
--- a/inc/lib/IP/Lifo/IP/BC.php
+++ /dev/null
@@ -1,293 +0,0 @@
-
- *
- * 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
deleted file mode 100755
index e8fe32ce..00000000
--- a/inc/lib/IP/Lifo/IP/CIDR.php
+++ /dev/null
@@ -1,706 +0,0 @@
-
- *
- * 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
deleted file mode 100755
index 4d22aa76..00000000
--- a/inc/lib/IP/Lifo/IP/IP.php
+++ /dev/null
@@ -1,207 +0,0 @@
-
- *
- * 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 97fecb20..5fb99b11 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;
+function twig_secure_link_confirm($text, $title, $confirm_message, $href) {
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 9f569c77..155c19a6 100644
--- a/inc/mod/pages.php
+++ b/inc/mod/pages.php
@@ -3,21 +3,20 @@
* Copyright (c) 2010-2013 Tinyboard Development Group
*/
use Vichan\Context;
-use Vichan\Data\ReportQueries;
-use Vichan\Functions\Format;
+use Vichan\Data\{IpNoteQueries, UserPostQueries, ReportQueries};
+use Vichan\Data\Driver\LogDriver;
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(string $target, string $link): bool {
- if (!link($target, $link)) {
- error_log("Failed to link() $target to $link. FAlling back to copy()");
- return copy($target, $link);
- }
- return true;
+function _link_or_copy_factory(Context $ctx): callable {
+ return function(string $target, string $link) use ($ctx) {
+ if (!\link($target, $link)) {
+ $ctx->get(LogDriver::class)->log(LogDriver::NOTICE, "Failed to link() $target to $link. FAlling back to copy()");
+ return \copy($target, $link);
+ }
+ return true;
+ };
}
function mod_page($title, $template, $args, $subtitle = false) {
@@ -58,8 +57,7 @@ function mod_login(Context $ctx, $redirect = false) {
if (!isset($_POST['username'], $_POST['password']) || $_POST['username'] == '' || $_POST['password'] == '') {
$args['error'] = $config['error']['invalid'];
} elseif (!login($_POST['username'], $_POST['password'])) {
- if ($config['syslog'])
- _syslog(LOG_WARNING, 'Unauthorized login attempt!');
+ $ctx->get(LogDriver::class)->log(LogDriver::INFO, 'Unauthorized login attempt!');
$args['error'] = $config['error']['invalid'];
} else {
@@ -100,6 +98,8 @@ function mod_logout(Context $ctx) {
function mod_dashboard(Context $ctx) {
global $config, $mod;
+ $report_queries = $ctx->get(ReportQueries::class);
+
$args = [];
$args['boards'] = listBoards();
@@ -126,8 +126,7 @@ function mod_dashboard(Context $ctx) {
cache::set('pm_unreadcount_' . $mod['id'], $args['unread_pms']);
}
- $query = query('SELECT COUNT(*) FROM ``reports``') or error(db_error($query));
- $args['reports'] = $query->fetchColumn();
+ $args['reports'] = $report_queries->getCount();
$query = query('SELECT COUNT(*) FROM ``ban_appeals`` WHERE denied = 0') or error(db_error($query));
$args['appeals'] = $query->fetchColumn();
@@ -768,7 +767,10 @@ function mod_board_log(Context $ctx, $board, $page_no = 1, $hide_names = false,
}
function mod_view_catalog(Context $ctx, $boardName) {
- global $config;
+ global $mod;
+
+ $config = $ctx->get('config');
+
require_once($config['dir']['themes'].'/catalog/theme.php');
$settings = [];
$settings['boards'] = $boardName;
@@ -851,188 +853,118 @@ function mod_view_thread50(Context $ctx, $boardName, $thread) {
}
function mod_ip_remove_note(Context $ctx, $ip, $id) {
- global $config;
+ $config = $ctx->get('config');
- if (!hasPermission($config['mod']['remove_notes']))
- error($config['error']['noaccess']);
+ if (!hasPermission($config['mod']['remove_notes'])) {
+ error($config['error']['noaccess']);
+ }
- if (filter_var($ip, FILTER_VALIDATE_IP) === false)
- error("Invalid IP address.");
+ if (filter_var($ip, \FILTER_VALIDATE_IP) === false) {
+ error('Invalid IP address');
+ }
- $query = prepare('DELETE FROM ``ip_notes`` WHERE `ip` = :ip AND `id` = :id');
- $query->bindValue(':ip', $ip);
- $query->bindValue(':id', $id);
- $query->execute() or error(db_error($query));
+ if (!is_numeric($id)) {
+ error('Invalid note ID');
+ }
- modLog("Removed a note for {$ip} ");
+ $queries = $ctx->get(IpNoteQueries::class);
+ $deleted = $queries->deleteWhereIp((int)$id, $ip);
- header('Location: ?/IP/' . $ip . '#notes', true, $config['redirect_http']);
+ if (!$deleted) {
+ error("Note $id does not exist for $ip");
+ }
+
+ modLog("Removed a note for {$ip} ");
+
+ \header("Location: ?/user_posts/ip/$ip#notes", true, $config['redirect_http']);
}
-function mod_ip(Context $ctx, $ip, string $encoded_cursor = '') {
- global $config, $mod;
+function mod_ip(Context $ctx, $ip, string $encoded_cursor = null) {
+ global $mod;
+ $config = $ctx->get('config');
- 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: ?/IP/$ip#bans", true, $config['redirect_http']);
+ \header("Location: ?/user_posts/ip/$ip#bans", true, $config['redirect_http']);
} else {
- header("Location: ?/IP/$ip/cursor/$encoded_cursor#bans", true, $config['redirect_http']);
+ \header("Location: ?/user_posts/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::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: ?/IP/$ip#notes", true, $config['redirect_http']);
+ \header("Location: ?/user_posts/ip/$ip#notes", true, $config['redirect_http']);
} else {
- header("Location: ?/IP/$ip/cursor/$encoded_cursor#notes", true, $config['redirect_http']);
+ \header("Location: ?/user_posts/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'])) {
- $ret = Cache::get("mod_page_ip_view_notes_$ip");
- if (!$ret) {
- $query = prepare("SELECT ``ip_notes``.*, `username` FROM ``ip_notes`` LEFT JOIN ``mods`` ON `mod` = ``mods``.`id` WHERE `ip` = :ip ORDER BY `time` DESC");
- $query->bindValue(':ip', $ip);
- $query->execute() or error(db_error($query));
- $ret = $query->fetchAll(PDO::FETCH_ASSOC);
- Cache::set("mod_page_ip_view_notes_$ip", $ret, 900);
- }
- $args['notes'] = $ret;
+ $note_queries = $ctx->get(IpNoteQueries::class);
+ $args['notes'] = $note_queries->getByIp($ip);
}
if (hasPermission($config['mod']['modlog_ip'])) {
@@ -1049,13 +981,117 @@ function mod_ip(Context $ctx, $ip, string $encoded_cursor = '') {
$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']);
+ 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);
}
function mod_ban(Context $ctx) {
@@ -1455,8 +1491,9 @@ function mod_move(Context $ctx, $originBoard, $postID) {
if ($targetBoard === $originBoard)
error(_('Target and source board are the same.'));
+ $_link_or_copy = _link_or_copy_factory($ctx);
// link() if leaving a shadow thread behind; else, rename().
- $clone = $shadow ? '_link_or_copy' : 'rename';
+ $clone = $shadow ? $_link_or_copy : 'rename';
// indicate that the post is a thread
$post['op'] = true;
@@ -1750,7 +1787,8 @@ function mod_merge(Context $ctx, $originBoard, $postID) {
$op = $post;
$op['id'] = $newID;
- $clone = $shadow ? '_link_or_copy' : 'rename';
+ $_link_or_copy = _link_or_copy_factory($ctx);
+ $clone = $shadow ? $_link_or_copy : 'rename';
if ($post['has_file']) {
// copy image
@@ -1966,14 +2004,10 @@ function mod_ban_post(Context $ctx, $board, $delete, $post, $token = false) {
$autotag .= "/${board}/" . " " . $filehash . " " . $filename ."\r\n";
$autotag .= $body . "\r\n";
$autotag = escape_markup_modifiers($autotag);
- markup($autotag);
- $query = prepare('INSERT INTO ``ip_notes`` VALUES (NULL, :ip, :mod, :time, :body)');
- $query->bindValue(':ip', $ip);
- $query->bindValue(':mod', $mod['id']);
- $query->bindValue(':time', time());
- $query->bindValue(':body', $autotag);
- $query->execute() or error(db_error($query));
- modLog("Added a note for {$ip} ");
+
+ $note_queries = $ctx->get(IpNoteQueries::class);
+ $note_queries->add($ip, $mod['id'], $autotag);
+ modLog("Added a note for {$ip} ");
}
}
deletePost($post);
@@ -2078,13 +2112,10 @@ function mod_warning_post(Context $ctx, $board, $post, $token = false) {
$autotag .= $body . "\r\n";
$autotag = escape_markup_modifiers($autotag);
markup($autotag);
- $query = prepare('INSERT INTO ``ip_notes`` VALUES (NULL, :ip, :mod, :time, :body)');
- $query->bindValue(':ip', $ip);
- $query->bindValue(':mod', $mod['id']);
- $query->bindValue(':time', time());
- $query->bindValue(':body', $autotag);
- $query->execute() or error(db_error($query));
- modLog("Added a note for {$ip} ");
+
+ $note_queries = $ctx->get(IpNoteQueries::class);
+ $note_queries->add($ip, $mod['id'], $autotag);
+ modLog("Added a note for {$ip} ");
}
}
}
@@ -2189,7 +2220,7 @@ function mod_edit_post(Context $ctx, $board, $edit_raw_html, $postID) {
}
function mod_delete(Context $ctx, $board, $post) {
- global $config;
+ global $config, $mod;
if (!openBoard($board))
error($config['error']['noboard']);
@@ -2230,13 +2261,10 @@ function mod_delete(Context $ctx, $board, $post) {
$autotag .= $body . "\r\n";
$autotag = escape_markup_modifiers($autotag);
markup($autotag);
- $query = prepare('INSERT INTO ``ip_notes`` VALUES (NULL, :ip, :mod, :time, :body)');
- $query->bindValue(':ip', $ip);
- $query->bindValue(':mod', $mod['id']);
- $query->bindValue(':time', time());
- $query->bindValue(':body', $autotag);
- $query->execute() or error(db_error($query));
- modLog("Added a note for {$ip} ");
+
+ $note_queries = $ctx->get(IpNoteQueries::class);
+ $note_queries->add($ip, $mod['id'], $autotag);
+ modLog("Added a note for {$ip} ");
}
}
deletePost($post);
@@ -2323,7 +2351,7 @@ function mod_spoiler_image(Context $ctx, $board, $post, $file) {
}
function mod_deletebyip(Context $ctx, $boardName, $post, $global = false) {
- global $config, $board;
+ global $config, $board, $mod;
$global = (bool)$global;
@@ -2401,13 +2429,10 @@ function mod_deletebyip(Context $ctx, $boardName, $post, $global = false) {
$autotag .= $body . "\r\n";
$autotag = escape_markup_modifiers($autotag);
markup($autotag);
- $query2 = prepare('INSERT INTO ``ip_notes`` VALUES (NULL, :ip, :mod, :time, :body)');
- $query2->bindValue(':ip', $ip);
- $query2->bindValue(':mod', $mod['id']);
- $query2->bindValue(':time', time());
- $query2->bindValue(':body', $autotag);
- $query2->execute() or error(db_error($query2));
- modLog("Added a note for {$ip} ");
+
+ $note_queries = $ctx->get(IpNoteQueries::class);
+ $note_queries->add($ip, $mod['id'], $autotag);
+ modLog("Added a note for {$ip} ");
}
}
@@ -2438,7 +2463,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']);
@@ -2975,7 +3000,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 bc3ba7d4..7663a503 100644
--- a/install.php
+++ b/install.php
@@ -881,6 +881,7 @@ 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 af795a57..3cb06bf1 100644
--- a/js/ajax.js
+++ b/js/ajax.js
@@ -18,16 +18,25 @@ $(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);
@@ -94,15 +103,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"]').val('').change();
+ textarea[name="body"],input[type="file"],input[name="embed"]').val('').change();
},
cache: false,
contentType: false,
@@ -114,7 +123,7 @@ $(window).ready(function() {
$(form).find('input[type="submit"]').val(submit_txt);
$(form).find('input[type="submit"]').removeAttr('disabled');
$(form).find('input[name="subject"],input[name="file_url"],\
- textarea[name="body"],input[type="file"]').val('').change();
+ textarea[name="body"],input[type="file"],input[name="embed"]').val('').change();
} else {
alert(_('An unknown error occured when posting!'));
$(form).find('input[type="submit"]').val(submit_txt);
@@ -132,10 +141,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 41625d2d..c44843b0 100644
--- a/js/inline-expanding.js
+++ b/js/inline-expanding.js
@@ -17,6 +17,10 @@ $(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');
@@ -56,12 +60,12 @@ $(document).ready(function() {
},
add: function(ele) {
ele.deferred = $.Deferred();
- ele.deferred.done(function () {
+ ele.deferred.done(function() {
let $loadstart = $.Deferred();
let thumb = ele.childNodes[0];
let img = ele.childNodes[1];
- let onLoadStart = function (img) {
+ let onLoadStart = function(img) {
if (img.naturalWidth) {
$loadstart.resolve(img, thumb);
} else {
@@ -69,15 +73,15 @@ $(document).ready(function() {
}
};
- $(img).one('load', function () {
- $.when($loadstart).done(function () {
- // Once fully loaded, update the waiting queue.
+ $(img).one('load', function() {
+ $.when($loadstart).done(function() {
+ // once fully loaded, update the waiting queue
--loading;
$(ele).data('imageLoading', 'false');
update();
});
});
- $loadstart.done(function (img, thumb) {
+ $loadstart.done(function(img, thumb) {
thumb.style.display = 'none';
img.style.display = '';
});
@@ -202,6 +206,8 @@ $(document).ready(function() {
Options.extend_tab('general', '' +
_('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)
@@ -212,6 +218,21 @@ $(document).ready(function() {
localStorage.inline_expand_max = val;
});
+
+ $('#inline-expand-fit-height input').on('change', function() {
+ if (localStorage.inline_expand_fit_height !== 'false') {
+ localStorage.inline_expand_fit_height = 'false';
+ $('#expand-fit-height-style').remove();
+ }
+ else {
+ localStorage.inline_expand_fit_height = 'true';
+ $('').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 c0652269..6715ae1d 100644
--- a/js/options/general.js
+++ b/js/options/general.js
@@ -43,9 +43,6 @@ $(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 f3f161c6..78fd35ce 100644
--- a/js/post-filter.js
+++ b/js/post-filter.js
@@ -237,12 +237,8 @@ if (active_page === 'thread' || active_page === 'index' || active_page === 'cata
var postUid = $ele.find('.poster_id').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();
- }
+ let postName = (typeof $ele.find('.name').contents()[0] == 'undefined') ? '' : nameSpanToString($ele.find('.name')[0]);
+ let postTrip = $ele.find('.trip').text();
/* display logic and bind click handlers
*/
@@ -297,39 +293,34 @@ if (active_page === 'thread' || active_page === 'index' || active_page === 'cata
}
// name
- if (!pageData.forcedAnon && !$ele.data('hiddenByName')) {
+ if (!$ele.data('hiddenByName')) {
$buffer.find('#filter-add-name').click(function () {
addFilter('name', postName, false);
});
$buffer.find('#filter-remove-name').addClass('hidden');
- } else if (!pageData.forcedAnon) {
+ } else {
$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 (!pageData.forcedAnon && !$ele.data('hiddenByTrip') && postTrip !== '') {
+ if (!$ele.data('hiddenByTrip') && postTrip !== '') {
$buffer.find('#filter-add-trip').click(function () {
addFilter('trip', postTrip, false);
});
$buffer.find('#filter-remove-trip').addClass('hidden');
- } else if (!pageData.forcedAnon && postTrip !== '') {
+ } else if (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');
}
@@ -391,7 +382,6 @@ 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);
@@ -432,9 +422,8 @@ if (active_page === 'thread' || active_page === 'index' || active_page === 'cata
}
// matches generalFilter
- if (!forcedAnon)
- name = (typeof $post.find('.name').contents()[0] == 'undefined') ? '' : nameSpanToString($post.find('.name')[0]);
- if (!forcedAnon && hasTrip)
+ name = (typeof $post.find('.name').contents()[0] == 'undefined') ? '' : nameSpanToString($post.find('.name')[0]);
+ if (hasTrip)
trip = $post.find('.trip').text();
if (hasSub)
subject = $post.find('.subject').text();
@@ -455,13 +444,13 @@ if (active_page === 'thread' || active_page === 'index' || active_page === 'cata
pattern = new RegExp(rule.value);
switch (rule.type) {
case 'name':
- if (!forcedAnon && pattern.test(name)) {
+ if (pattern.test(name)) {
$post.data('hiddenByName', true);
hide(post);
}
break;
case 'trip':
- if (!forcedAnon && hasTrip && pattern.test(trip)) {
+ if (hasTrip && pattern.test(trip)) {
$post.data('hiddenByTrip', true);
hide(post);
}
@@ -488,13 +477,13 @@ if (active_page === 'thread' || active_page === 'index' || active_page === 'cata
} else {
switch (rule.type) {
case 'name':
- if (!forcedAnon && rule.value == name) {
+ if (rule.value == name) {
$post.data('hiddenByName', true);
hide(post);
}
break;
case 'trip':
- if (!forcedAnon && hasTrip && rule.value == trip) {
+ if (hasTrip && rule.value == trip) {
$post.data('hiddenByTrip', true);
hide(post);
}
@@ -827,8 +816,7 @@ 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),
- forcedAnon: ($('th:contains(Name)').length === 0) // tests by looking for the Name label on the reply form
+ hasUID: (document.getElementsByClassName('poster_id').length > 0)
};
initStyle();
diff --git a/js/post-menu.js b/js/post-menu.js
index 79cfd868..c2155c00 100644
--- a/js/post-menu.js
+++ b/js/post-menu.js
@@ -104,8 +104,10 @@ function buildMenu(e) {
function addButton(post) {
var $ele = $(post);
+ // Use unicode code with ascii variant selector
+ // https://stackoverflow.com/questions/37906969/how-to-prevent-ios-from-converting-ascii-into-emoji
$ele.find('input.delete').after(
- $('', {href: '#', class: 'post-btn', title: 'Post menu'}).text('►')
+ $(' ', {href: '#', class: 'post-btn', title: 'Post menu'}).text('\u{25B6}\u{fe0e}')
);
}
diff --git a/js/show-backlinks.js b/js/show-backlinks.js
index 5924124e..607c24ab 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').replace(/^reply_/, '');
+ let replyId = $(this).attr('id').split('_')[1];
$(this).find('div.body a:not([rel="nofollow"])').each(function() {
let id = $(this).text().match(/^>>(\d+)$/);
@@ -25,13 +25,15 @@ $(document).ready(function() {
return;
}
- let post = $('#reply_' + id);
- if(post.length == 0)
+ let post = $('#reply_' + id + ', #op_' + id);
+ if (post.length == 0) {
return;
+ }
let mentioned = post.find('.head div.mentioned');
if (mentioned.length === 0) {
- mentioned = $('
').prependTo(post.find('.head'));
+ // The op has two "head"s divs, use the second.
+ mentioned = $('
').prependTo(post.find('.head').last());
}
if (mentioned.find('a.mentioned-' + replyId).length !== 0) {
@@ -48,13 +50,13 @@ $(document).ready(function() {
});
};
- $('div.post.reply').each(showBackLinks);
+ $('div.post').each(showBackLinks);
$(document).on('new_post', function(e, post) {
- if ($(post).hasClass('reply')) {
+ if ($(post).hasClass('reply') || $(post).hasClass('op')) {
showBackLinks.call(post);
} else {
- $(post).find('div.post.reply').each(showBackLinks);
+ $(post).find('div.post').each(showBackLinks);
}
});
});
diff --git a/js/style-select-simple.js b/js/style-select-simple.js
new file mode 100644
index 00000000..8b59fa0a
--- /dev/null
+++ b/js/style-select-simple.js
@@ -0,0 +1,36 @@
+/*
+ * 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 485da735..16bbae72 100644
--- a/js/style-select.js
+++ b/js/style-select.js
@@ -6,48 +6,53 @@
*
* 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() {
- 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 pages = $('div.pages');
+ let stylesSelect = $(' ').css({float:"none"});
+ let options = [];
- options.sort ((a, b) => {
+ 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) => {
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() {
- $('#style-select-' + $(this).val()).click();
+ let sel = $(this).find(":selected")[0];
+ let styleName = sel.innerHTML;
+ changeStyle(styleName, sel);
});
-
- stylesDiv.hide()
+
pages.after(
$('
')
.append(_('Select theme: '), stylesSelect)
diff --git a/js/youtube.js b/js/youtube.js
index 4c31ed09..4a5a5afe 100644
--- a/js/youtube.js
+++ b/js/youtube.js
@@ -1,41 +1,41 @@
/*
-* 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';
-*
-*/
+ * 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';
+ */
-$(document).ready(function(){
- // Adds Options panel item
+$(document).ready(function() {
+ const ON = '[Remove]';
+ const YOUTUBE = 'www.youtube.com';
+
+ function makeEmbedNode(embedHost, videoId, width, height) {
+ return $(``);
+ }
+
+ // Adds Options panel item.
if (typeof localStorage.youtube_embed_proxy === 'undefined') {
- if (location.hostname.includes(".onion")){
- localStorage.youtube_embed_proxy = 'tuberyps2pn6dor6h47brof3w2asmauahhk4ei42krugybzzzo55klad.onion';
- } else {
- localStorage.youtube_embed_proxy = 'incogtube.com'; //default value
- }
+ 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,51 +43,65 @@ $(document).ready(function(){
});
}
- 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] ");
+ const proxy = localStorage.youtube_embed_proxy;
- var makeEmbedNode = function(embedHost) {
- return $('');
- }
- var defaultEmbed = makeEmbedNode(location.hostname.includes(".onion") ? PROXY : YOUTUBE);
- var proxyEmbed = makeEmbedNode(PROXY);
- videoNode.click(function(e) {
+ 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) {
e.preventDefault();
- if (span.text() == ON){
- videoNode.append(spanProxy);
- videoNode.append(contents);
- defaultEmbed.remove();
- proxyEmbed.remove();
- span.text(OFF);
+ if (span.text() == ON) {
+ contents.css('display', '');
+ spanProxy.css('display', '');
+
+ if (iframeDefault !== null) {
+ iframeDefault.remove();
+ }
+ if (iframeProxy !== null) {
+ iframeProxy.remove();
+ }
+
+ span.text('[Embed]');
} else {
- contents.detach();
+ 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');
span.text(ON);
- spanProxy.remove();
- videoNode.append(e.target == spanProxy[0] ? proxyEmbed : defaultEmbed);
}
});
- videoNode.append(span);
- videoNode.append(spanProxy);
+ node.append(span);
+ node.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 bb260251..f08a465b 100644
--- a/mod.php
+++ b/mod.php
@@ -68,9 +68,15 @@ $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 642e8057..5483600d 100644
--- a/post.php
+++ b/post.php
@@ -5,6 +5,7 @@
use Vichan\Context;
use Vichan\Data\ReportQueries;
+use Vichan\Data\Driver\LogDriver;
require_once 'inc/bootstrap.php';
@@ -221,7 +222,7 @@ function send_matrix_report(
$end = strlen($post['body_nomarkup']) > $max_msg_len ? ' [...]' : '';
$post_content = mb_substr($post['body_nomarkup'], 0, $max_msg_len) . $end;
- $text_body = $reported_post_url . ($post['thread'] ? "#$post_id" : '') . " \nReason:\n" . $report_reason . " \nPost:\n" . $post_content;
+ $text_body = $reported_post_url . ($post['thread'] ? "#$id" : '') . " \nReason:\n" . $report_reason . " \nPost:\n" . $post_content;
$random_transaction_id = mt_rand();
$json_body = json_encode([
@@ -354,7 +355,7 @@ function db_select_ban_appeals($ban_id)
$dropped_post = false;
-function handle_nntpchan()
+function handle_nntpchan(Context $ctx)
{
global $config;
if ($_SERVER['REMOTE_ADDR'] != $config['nntpchan']['trusted_peer']) {
@@ -433,7 +434,7 @@ function handle_nntpchan()
if ($ct == 'text/plain') {
$content = file_get_contents("php://input");
} elseif ($ct == 'multipart/mixed' || $ct == 'multipart/form-data') {
- _syslog(LOG_INFO, "MM: Files: " . print_r($GLOBALS, true)); // Debug
+ $ctx->get(LogDriver::class)->log(LogDriver::DEBUG, 'MM: Files: ' . print_r($GLOBALS, true));
$content = '';
@@ -530,10 +531,12 @@ function handle_delete(Context $ctx)
$password = &$_POST['password'];
- if ($password == '') {
+ if (empty($password)) {
error($config['error']['invalidpassword']);
}
+ $password = hashPassword($_POST['password']);
+
$delete = [];
foreach ($_POST as $post => $value) {
if (preg_match('/^delete_(\d+)$/', $post, $m)) {
@@ -608,8 +611,8 @@ function handle_delete(Context $ctx)
modLog("User at $ip deleted his own post #$id");
}
- _syslog(
- LOG_INFO,
+ $ctx->get(LogDriver::class)->log(
+ LogDriver::INFO,
'Deleted post: ' .
'/' . $board['dir'] . $config['dir']['res'] . sprintf($config['file_page'], $post['thread'] ? $post['thread'] : $id) . ($post['thread'] ? '#' . $id : '')
);
@@ -697,9 +700,7 @@ function handle_report(Context $ctx)
foreach ($report as $id) {
$post = db_select_post_minimal($board['uri'], $id);
if ($post === false) {
- if ($config['syslog']) {
- _syslog(LOG_INFO, "Failed to report non-existing post #{$id} in {$board['dir']}");
- }
+ $ctx->get(LogDriver::class)->log(LogDriver::INFO, "Failed to report non-existing post #{$id} in {$board['dir']}");
error($config['error']['nopost']);
}
@@ -714,13 +715,12 @@ function handle_report(Context $ctx)
error($error);
}
- if ($config['syslog'])
- _syslog(
- LOG_INFO,
- 'Reported post: ' .
- '/' . $board['dir'] . $config['dir']['res'] . link_for($post) . ($post['thread'] ? '#' . $id : '') .
- ' for "' . $reason . '"'
- );
+ $ctx->get(LogDriver::class)->log(
+ LogDriver::INFO,
+ 'Reported post: ' .
+ '/' . $board['dir'] . $config['dir']['res'] . link_for($post) . ($post['thread'] ? '#' . $id : '') .
+ ' for "' . $reason . '"'
+ );
$report_queries->add($_SERVER['REMOTE_ADDR'], $board['uri'], $id, $reason);
@@ -1009,11 +1009,16 @@ 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'] = $_POST['password'];
+ $post['password'] = hashPassword($_POST['password']);
$post['has_file'] = (!isset($post['embed']) && (($post['op'] && !isset($post['no_longer_require_an_image_for_op']) && $config['force_image_op']) || count($_FILES) > 0));
if (!$dropped_post) {
@@ -1204,9 +1209,6 @@ 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']);
@@ -1311,7 +1313,7 @@ function handle_post(Context $ctx)
if (!hasPermission($config['mod']['bypass_filters'], $board['uri']) && !$dropped_post) {
require_once 'inc/filters.php';
- do_filters($post);
+ do_filters($ctx, $post);
}
if ($post['has_file']) {
@@ -1400,13 +1402,13 @@ function handle_post(Context $ctx)
$file['thumbwidth'] = $size[0];
$file['thumbheight'] = $size[1];
} elseif (
- $config['minimum_copy_resize'] &&
+ (($config['strip_exif'] && isset($file['exif_stripped']) && $file['exif_stripped']) || !$config['strip_exif']) &&
$image->size->width <= $config['thumb_width'] &&
$image->size->height <= $config['thumb_height'] &&
$file['extension'] == ($config['thumb_ext'] ? $config['thumb_ext'] : $file['extension'])
) {
// Copy, because there's nothing to resize
- coopy($file['tmp_name'], $file['thumb']);
+ copy($file['tmp_name'], $file['thumb']);
$file['thumbwidth'] = $image->size->width;
$file['thumbheight'] = $image->size->height;
@@ -1549,35 +1551,6 @@ function handle_post(Context $ctx)
}
}
- if ($config['tesseract_ocr'] && $file['thumb'] != 'file') {
- // Let's OCR it!
- $fname = $file['tmp_name'];
-
- if ($file['height'] > 500 || $file['width'] > 500) {
- $fname = $file['thumb'];
- }
-
- if ($fname == 'spoiler') {
- // We don't have that much CPU time, do we?
- } else {
- $tmpname = __DIR__ . "/tmp/tesseract/" . rand(0, 10000000);
-
- // Preprocess command is an ImageMagick b/w quantization
- $error = shell_exec_error(sprintf($config['tesseract_preprocess_command'], escapeshellarg($fname)) . " | " .
- 'tesseract stdin ' . escapeshellarg($tmpname) . ' ' . $config['tesseract_params']);
- $tmpname .= ".txt";
-
- $value = @file_get_contents($tmpname);
- @unlink($tmpname);
-
- if ($value && trim($value)) {
- // This one has an effect, that the body is appended to a post body. So you can write a correct
- // spamfilter.
- $post['body_nomarkup'] .= "" . htmlspecialchars($value) . " ";
- }
- }
- }
-
if (!isset($dont_copy_file) || !$dont_copy_file) {
if (isset($file['file_tmp'])) {
if (!@rename($file['tmp_name'], $file['file'])) {
@@ -1625,11 +1598,6 @@ function handle_post(Context $ctx)
}
}
- // Do filters again if OCRing
- if ($config['tesseract_ocr'] && !hasPermission($config['mod']['bypass_filters'], $board['uri']) && !$dropped_post) {
- do_filters($post);
- }
-
if (!hasPermission($config['mod']['postunoriginal'], $board['uri']) && $config['robot_enable'] && checkRobot($post['body_nomarkup']) && !$dropped_post) {
undoImage($post);
if ($config['robot_mute']) {
@@ -1779,10 +1747,10 @@ function handle_post(Context $ctx)
buildThread($post['op'] ? $id : $post['thread']);
- if ($config['syslog']) {
- _syslog(LOG_INFO, 'New post: /' . $board['dir'] . $config['dir']['res'] .
- link_for($post) . (!$post['op'] ? '#' . $id : ''));
- }
+ $ctx->get(LogDriver::class)->log(
+ LogDriver::INFO,
+ 'New post: /' . $board['dir'] . $config['dir']['res'] . link_for($post) . (!$post['op'] ? '#' . $id : '')
+ );
if (!$post['mod']) {
header('X-Associated-Content: "' . $redirect . '"');
@@ -1875,17 +1843,17 @@ function handle_appeal(Context $ctx)
displayBan($ban);
}
+$ctx = Vichan\build_context($config);
+
// Is it a post coming from NNTP? Let's extract it and pretend it's a normal post.
if (isset($_GET['Newsgroups'])) {
if ($config['nntpchan']['enabled']) {
- handle_nntpchan();
+ handle_nntpchan($ctx);
} else {
error("NNTPChan: NNTPChan support is disabled");
}
}
-$ctx = Vichan\build_context($config);
-
if (isset($_POST['delete'])) {
handle_delete($ctx);
} elseif (isset($_POST['report'])) {
diff --git a/static/banners/deny-defend-depose-opt.webp b/static/banners/deny-defend-depose-opt.webp
new file mode 100644
index 00000000..584a4ae2
Binary files /dev/null and b/static/banners/deny-defend-depose-opt.webp differ
diff --git a/static/flags/alunya.png b/static/flags/alunya.png
new file mode 100644
index 00000000..799fd509
Binary files /dev/null and b/static/flags/alunya.png differ
diff --git a/static/flags/libsoc.png b/static/flags/libsoc.png
new file mode 100644
index 00000000..c8eb3e36
Binary files /dev/null and b/static/flags/libsoc.png differ
diff --git a/stylesheets/500px.css b/stylesheets/500px.css
index 89b1fa64..5f733005 100644
--- a/stylesheets/500px.css
+++ b/stylesheets/500px.css
@@ -152,6 +152,11 @@ 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 c94c50e8..330eadd3 100644
--- a/stylesheets/8ch.cyber.css
+++ b/stylesheets/8ch.cyber.css
@@ -219,6 +219,11 @@ 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 e32353cc..6a2c771e 100644
--- a/stylesheets/beta.css
+++ b/stylesheets/beta.css
@@ -161,6 +161,11 @@ 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 a69591a1..507003ce 100644
--- a/stylesheets/bunker_like.css
+++ b/stylesheets/bunker_like.css
@@ -120,6 +120,11 @@ 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 35fdbbb7..15b68ed5 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,6 +136,11 @@ 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 2e907966..4754a882 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,6 +113,11 @@ 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 dd7e9bfd..9f3df691 100644
--- a/stylesheets/dark.css
+++ b/stylesheets/dark.css
@@ -63,6 +63,11 @@ 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 8593ead9..4ffe4eeb 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: #A7A7A7;
+ color: #C0C0C0;
font-family: Verdana, sans-serif;
font-size: 14px;
}
@@ -31,26 +31,28 @@ div.title p {
font-size: 10px;
}
a, a:link, a:visited, .intro a.email span.name {
- color: #CCCCCC;
+ color: #EEE;
text-decoration: none;
- font-family: sans-serif;
+ font-family: Verdana, sans-serif;
}
a:link:hover, a:visited:hover {
color: #fff;
- font-family: sans-serif;
+ font-family: Verdana, sans-serif;
text-decoration: none;
}
a.post_no {
- 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: #555555 1px solid;
+ border: #4f4f4f 1px solid;
@media (max-width: 48em) {
border-left-style: none;
@@ -58,21 +60,26 @@ div.post.reply {
}
}
div.post.reply.highlighted {
- background: #555;
+ background: #4f4f4f;
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;
+ color: #EEE;
}
div.post.reply div.body a:link:hover, div.post.reply div.body a:visited:hover {
color: #32DD72;
}
div.post.inline {
- border: #555555 1px solid;
+ border: #4f4f4f 1px solid;
}
.intro span.subject {
font-size: 12px;
- font-family: sans-serif;
+ font-family: Verdana, sans-serif;
color: #446655;
font-weight: 800;
}
@@ -89,16 +96,16 @@ div.post.inline {
}
input[type="text"], textarea, select {
background: #333333;
- color: #CCCCCC;
+ color: #EEE;
border: #666666 1px solid;
padding-left: 5px;
padding-right: -5px;
- font-family: sans-serif;
+ font-family: Verdana, sans-serif;
font-size: 10pt;
}
input[type="password"] {
background: #333333;
- color: #CCCCCC;
+ color: #EEE;
border: #666666 1px solid;
}
form table tr th {
@@ -126,10 +133,10 @@ div.banner a {
input[type="submit"] {
background: #333333;
border: #888888 1px solid;
- color: #CCCCCC;
+ color: #EEE;
}
input[type="submit"]:hover {
- background: #555555;
+ background: #4f4f4f;
border: #888888 1px solid;
color: #32DD72;
}
@@ -144,7 +151,7 @@ span.trip {
}
div.pages {
background: #1E1E1E;
- font-family: sans-serif;
+ font-family: Verdana, sans-serif;
}
.bar.bottom {
bottom: 0px;
@@ -152,7 +159,7 @@ div.pages {
background-color: #1E1E1E;
}
div.pages a.selected {
- color: #CCCCCC;
+ color: #EEE;
}
hr {
height: 1px;
@@ -160,7 +167,7 @@ hr {
}
div.boardlist {
text-align: center;
- color: #A7A7A7;
+ color: #C0C0C0;
}
div.ban {
background-color: transparent;
@@ -181,7 +188,7 @@ div.boardlist:not(.bottom) {
}
.desktop-style div.boardlist:not(.bottom) {
text-shadow: black 1px 1px 1px, black -1px -1px 1px, black -1px 1px 1px, black 1px -1px 1px;
- color: #A7A7A7;
+ color: #C0C0C0;
background-color: #1E1E1E;
}
div.report {
@@ -204,7 +211,7 @@ div.report {
}
.box {
background: #333333;
- border-color: #555555;
+ border-color: #4f4f4f;
color: #C5C8C6;
border-radius: 10px;
}
@@ -214,7 +221,7 @@ div.report {
}
table thead th {
background: #333333;
- border-color: #555555;
+ border-color: #4f4f4f;
color: #C5C8C6;
border-radius: 4px;
}
@@ -222,11 +229,11 @@ table tbody tr:nth-of-type( even ) {
background-color: #333333;
}
table.board-list-table .board-uri .board-sfw {
- color: #CCCCCC;
+ color: #EEE;
}
tbody.board-list-omitted td {
background: #333333;
- border-color: #555555;
+ border-color: #4f4f4f;
}
table.board-list-table .board-tags .board-cell:hover {
background: #1e1e1e;
diff --git a/stylesheets/dark_spook.css b/stylesheets/dark_spook.css
index 5e9bf837..2ca55a20 100644
--- a/stylesheets/dark_spook.css
+++ b/stylesheets/dark_spook.css
@@ -61,6 +61,11 @@ 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 22ee517d..cd27da0a 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,6 +131,11 @@ 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 32bc764d..7d7a6876 100644
Binary files a/stylesheets/delete.css and b/stylesheets/delete.css differ
diff --git a/stylesheets/fauux.css b/stylesheets/fauux.css
index 3cb1538a..56fe4a82 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,6 +93,11 @@ 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;
@@ -216,11 +221,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 cdb724c1..94d66bfd 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,6 +59,11 @@ 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 dfc1bc58..ed54f8f3 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,6 +131,11 @@ 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 19166adf..296ca053 100644
--- a/stylesheets/futaba-light.css
+++ b/stylesheets/futaba-light.css
@@ -34,6 +34,11 @@ 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;
@@ -62,7 +67,7 @@ div.pages {
padding: 7px 5px;
color: maroon;
font-size: 12pt;
-
+
background: none;
border-width: 1px;
border-style: inset;
@@ -101,4 +106,3 @@ table.modlog tr th {
#options_div, #alert_div {
background: rgb(240, 224, 214);
}
-
diff --git a/stylesheets/gentoochan.css b/stylesheets/gentoochan.css
index 49fa8a77..85204d32 100644
--- a/stylesheets/gentoochan.css
+++ b/stylesheets/gentoochan.css
@@ -23,6 +23,14 @@ 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);
}
@@ -72,4 +80,3 @@ 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 cacfc676..bc52f1c9 100644
--- a/stylesheets/gorby.css
+++ b/stylesheets/gorby.css
@@ -56,6 +56,10 @@ 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 {
@@ -65,6 +69,10 @@ 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 b5ef39aa..c0403c40 100644
--- a/stylesheets/heavy_ice.css
+++ b/stylesheets/heavy_ice.css
@@ -164,6 +164,11 @@ 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 a77573dc..7486c5a8 100644
--- a/stylesheets/jungle.css
+++ b/stylesheets/jungle.css
@@ -53,6 +53,10 @@ 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 {
@@ -62,6 +66,10 @@ 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 71210cff..1a1ccfc2 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,59 +117,64 @@ 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%);
@@ -180,31 +185,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;
}
@@ -214,7 +219,7 @@ span.trip
border-bottom: 0px solid #666;
widh:100%;
}
-div.pages
+div.pages
{
color: #AAA;
background: #333;
@@ -222,21 +227,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;
@@ -245,7 +250,7 @@ div.ban
border-radius: 2px;
text-align: left!important;
}
-div.ban h2
+div.ban h2
{
background: #333;
color: #32DD72;
@@ -253,17 +258,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;
}
@@ -276,7 +281,7 @@ div.report
-ms-border-radius: 2px;
border-radius: 2px;
}
-.blur
+.blur
{
filter: blur(20px);
-webkit-filter: blur(23px);
@@ -287,17 +292,15 @@ 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 5cfe8060..339479ec 100644
--- a/stylesheets/midnight.css
+++ b/stylesheets/midnight.css
@@ -265,6 +265,11 @@ 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 {
@@ -275,6 +280,11 @@ 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 {
@@ -452,10 +462,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 f09e93b9..de073f0c 100644
--- a/stylesheets/mono.e.lain.css
+++ b/stylesheets/mono.e.lain.css
@@ -215,6 +215,11 @@ 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 c0d00ecb..bfd47e70 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,6 +58,11 @@ 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;
@@ -65,6 +70,11 @@ 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;
@@ -96,7 +106,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;
@@ -194,7 +204,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 b665c569..e1eedaf6 100644
--- a/stylesheets/northboard_cb.css
+++ b/stylesheets/northboard_cb.css
@@ -28,14 +28,22 @@ 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 {
@@ -220,30 +228,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 db434302..4a090f33 100644
--- a/stylesheets/style.css
+++ b/stylesheets/style.css
@@ -380,6 +380,7 @@ form table tr td div.center {
.file {
float: left;
+ min-width: 100px;
}
.file:not(.multifile) .post-image {
@@ -390,6 +391,10 @@ form table tr td div.center {
float: none;
}
+.file.multifile {
+ margin: 0 10px 0 0;
+}
+
.file.multifile > p {
width: 0px;
min-width: 100%;
@@ -429,19 +434,18 @@ img.banner,img.board_image {
.post-image {
display: block;
float: left;
- margin: 5px 20px 10px 20px;
border: none;
}
.full-image {
float: left;
- padding: 5px;
+ padding: 0.2em 0.2em 0.8em 0.2em;
margin: 0 20px 0 0;
max-width: 98%;
}
div.post .post-image {
- padding: 0.2em;
+ padding: 0.2em 0.2em 0.8em 0.2em;
margin: 0 20px 0 0;
}
@@ -538,8 +542,8 @@ div.post {
}
}
-div.post > div.head {
- margin: 0.1em 1em;
+div.post div.head {
+ margin: 0.1em 1em 0.8em 1.4em;
clear: both;
line-height: 1.3em;
}
@@ -564,17 +568,11 @@ div.post.op > p {
}
div.post div.body {
- margin-top: 0.8em;
+ margin-left: 1.4em;
padding-right: 3em;
padding-bottom: 0.3em;
-}
-div.post.op div.body {
- margin-left: 0.8em;
-}
-
-div.post.reply div.body {
- margin-left: 1.8em;
+ white-space: pre-wrap;
}
div.post.reply.highlighted {
@@ -585,19 +583,9 @@ 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 {
@@ -648,6 +636,7 @@ span.trip {
span.omitted {
display: block;
margin-top: 1em;
+ margin-left: 0.4em;
}
br.clear {
@@ -832,7 +821,7 @@ span.public_ban {
span.public_warning {
display: block;
- color: steelblue;
+ color: orange;
font-weight: bold;
margin-top: 15px;
}
diff --git a/stylesheets/terminal2.css b/stylesheets/terminal2.css
index d4d52847..9add30ea 100644
--- a/stylesheets/terminal2.css
+++ b/stylesheets/terminal2.css
@@ -48,6 +48,11 @@ 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 c7f34f51..53d04ca2 100644
--- a/stylesheets/terminal_common.css
+++ b/stylesheets/terminal_common.css
@@ -69,6 +69,11 @@ 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 162e9c26..6835b23f 100644
--- a/stylesheets/test.css
+++ b/stylesheets/test.css
@@ -132,6 +132,11 @@ 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 bffa78c3..523bdb71 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,6 +1372,11 @@ 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;
@@ -1431,7 +1436,7 @@ div.banner {
font-size: 16px;
}
div.banner a {
- color:#000;
+ color:#000;
}
input[type="submit"] {
background: #333333;
@@ -1444,7 +1449,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;
@@ -1453,9 +1458,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 {
@@ -1474,7 +1479,7 @@ hr {
}
div.boardlist {
color: #999999;
- background-color: rgba(12%, 12%, 12%, 0.10);
+ background-color: rgba(12%, 12%, 12%, 0.10);
}
div.ban {
@@ -1492,13 +1497,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 79b0b230..c2f5d87f 100644
--- a/templates/captcha_script.html
+++ b/templates/captcha_script.html
@@ -1,6 +1,6 @@
-{% if config.hcaptcha %}
+{% if config.captcha.mode == 'hcaptcha' %}
{% endif %}
-{% if config.turnstile %}
+{% if config.captcha.mode == 'turnstile' %}
{% endif %}
diff --git a/templates/header.html b/templates/header.html
index 7e3f9c54..27b1a31f 100644
--- a/templates/header.html
+++ b/templates/header.html
@@ -28,6 +28,6 @@
{% endif %}
{% endif %}
- {% if config.recaptcha %}
+ {% if config.captcha.mode == 'recaptcha' %}
{% endif %}
diff --git a/templates/installer/config.html b/templates/installer/config.html
index 973328f5..00a5b241 100644
--- a/templates/installer/config.html
+++ b/templates/installer/config.html
@@ -88,6 +88,9 @@
Secure trip (##) salt:
+ Poster password salt:
+
+
Additional configuration:
diff --git a/templates/main.js b/templates/main.js
index d86d5b70..c4025646 100755
--- a/templates/main.js
+++ b/templates/main.js
@@ -231,28 +231,6 @@ 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) {
@@ -265,27 +243,48 @@ function getCookie(cookie_name) {
{% endraw %}
/* BEGIN CAPTCHA REGION */
-{% if config.hcaptcha or config.turnstile %} // If any captcha
+{% if config.captcha.mode == 'hcaptcha' or config.captcha.mode == '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.hcaptcha %} // If hcaptcha
+{% if config.captcha.mode == 'hcaptcha' %} // If hcaptcha
function onCaptchaLoadHcaptcha() {
- if (captcha_renderer === null && (active_page === 'index' || active_page === 'catalog' || active_page === 'thread')) {
+ if ((captchaMode === 'static' || (captchaMode === 'dynamic' && isDynamicCaptchaEnabled()))
+ && 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.hcaptcha_public }}",
+ sitekey: "{{ config.captcha.hcaptcha.public }}",
callback: params['on-success'],
}),
+ /**
+ * @returns {void}
+ */
remove: (widgetId) => { /* Not supported */ },
- reset: (widgetId) => hcaptcha.reset(widgetId)
+ /**
+ * @returns {void}
+ */
+ reset: (widgetId) => hcaptcha.reset(widgetId),
+ /**
+ * @returns {bool}
+ */
+ hasResponse: (widgetId) => !!hcaptcha.getResponse(widgetId),
+ /**
+ * @returns {void}
+ */
+ execute: (widgetId) => hcaptcha.execute(widgetId)
};
onCaptchaLoad(renderer);
}
}
{% endif %} // End if hcaptcha
-{% if config.turnstile %} // If turnstile
+{% if config.captcha.mode == 'turnstile' %} // If turnstile
// Wrapper function to be called from thread.html
window.onCaptchaLoadTurnstile_post_reply = function() {
@@ -299,11 +298,16 @@ window.onCaptchaLoadTurnstile_post_thread = function() {
// Should be called by the captcha API when it's ready. Ugly I know... D:
function onCaptchaLoadTurnstile(action) {
- if (captcha_renderer === null && (active_page === 'index' || active_page === 'catalog' || active_page === 'thread')) {
+ if ((captchaMode === 'static' || (captchaMode === 'dynamic' && isDynamicCaptchaEnabled()))
+ && 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.turnstile_public }}",
+ sitekey: "{{ config.captcha.turnstile.public }}",
action: action,
callback: params['on-success'],
});
@@ -312,8 +316,22 @@ function onCaptchaLoadTurnstile(action) {
}
return widgetId;
},
+ /**
+ * @returns {void}
+ */
remove: (widgetId) => turnstile.remove(widgetId),
- reset: (widgetId) => turnstile.reset(widgetId)
+ /**
+ * @returns {void}
+ */
+ reset: (widgetId) => turnstile.reset(widgetId),
+ /**
+ * @returns {bool}
+ */
+ hasResponse: (widgetId) => !!turnstile.getResponse(widgetId),
+ /**
+ * @returns {void}
+ */
+ execute: (widgetId) => turnstile.execute(widgetId)
};
onCaptchaLoad(renderer);
@@ -335,6 +353,7 @@ 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);
@@ -342,6 +361,8 @@ function onCaptchaLoad(renderer) {
}
{% if config.dynamic_captcha %} // If dynamic captcha
+var captchaMode = 'dynamic';
+
function isDynamicCaptchaEnabled() {
let cookie = getCookie('captcha-required');
return cookie === '1';
@@ -355,8 +376,15 @@ 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
@@ -412,6 +440,13 @@ 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 != "");
@@ -528,7 +563,6 @@ var script_settings = function(script_name) {
};
function init() {
- initStyleChooser();
initCaptcha();
{% endraw %}
diff --git a/templates/mod/dashboard.html b/templates/mod/dashboard.html
index e36e3c66..b765ce5f 100644
--- a/templates/mod/dashboard.html
+++ b/templates/mod/dashboard.html
@@ -1,3 +1,4 @@
+{% if config.url_banner %} {% endif %}
{% for board in boards %}
diff --git a/templates/mod/login.html b/templates/mod/login.html
index 32869872..6ed8dc64 100644
--- a/templates/mod/login.html
+++ b/templates/mod/login.html
@@ -1,4 +1,5 @@
{% if error %}{{ error }} {% endif %}
+{% if config.url_banner %} {% endif %}