From 4ede43b192575011071c50b035c7cd261100d0aa Mon Sep 17 00:00:00 2001 From: Zankaria Date: Wed, 18 Dec 2024 19:51:41 +0100 Subject: [PATCH 01/14] UserPostQueries.php: refactor implementation --- inc/Data/UserPostQueries.php | 80 +++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 37 deletions(-) diff --git a/inc/Data/UserPostQueries.php b/inc/Data/UserPostQueries.php index e702dd89..bebbd365 100644 --- a/inc/Data/UserPostQueries.php +++ b/inc/Data/UserPostQueries.php @@ -17,16 +17,7 @@ class UserPostQueries { $this->pdo = $pdo; } - /** - * 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 { + 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); @@ -41,37 +32,15 @@ class UserPostQueries { foreach ($board_uris as $uri) { // Extract the cursor relative to the board. - $id_cursor = false; - if (isset($uri_id_cursor_map[$uri])) { + $start_id = null; + if ($cursor_type !== null && isset($uri_id_cursor_map[$uri])) { $value = $uri_id_cursor_map[$uri]; if (\is_numeric($value)) { - $id_cursor = (int)$value; + $start_id = (int)$value; } } - if ($id_cursor === false) { - $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(); - $posts = $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', $id_cursor, \PDO::PARAM_INT); - $query->bindValue(':limit', $page_size + 2, \PDO::PARAM_INT); // Always fetch more. - $query->execute(); - $posts = $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', $id_cursor, \PDO::PARAM_INT); - $query->bindValue(':limit', $page_size + 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 = $callback($uri, $cursor_type, $start_id, $page_size); $posts_count = \count($posts); @@ -79,7 +48,7 @@ class UserPostQueries { $has_extra_prev_post = true; $has_extra_end_post = true; } elseif ($posts_count === $page_size + 1) { - $has_extra_prev_post = $id_cursor !== false && $id_cursor === (int)$posts[0]['id']; + $has_extra_prev_post = $start_id !== null && $start_id === (int)$posts[0]['id']; $has_extra_end_post = !$has_extra_prev_post; } else { $has_extra_prev_post = false; @@ -112,4 +81,41 @@ class UserPostQueries { 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'"); + } + }); + } } \ No newline at end of file From 27b7f1c60d12858f5c0bb35ef003e597c3110c71 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Wed, 18 Dec 2024 21:09:41 +0100 Subject: [PATCH 02/14] UserPostQueries.php: add post fetching by password --- inc/Data/UserPostQueries.php | 39 +++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/inc/Data/UserPostQueries.php b/inc/Data/UserPostQueries.php index bebbd365..ee16b2f5 100644 --- a/inc/Data/UserPostQueries.php +++ b/inc/Data/UserPostQueries.php @@ -118,4 +118,41 @@ class UserPostQueries { } }); } -} \ No newline at end of file + + /** + * 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` = :ip AND `password` >= :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'"); + } + }); + } +} From 11c574888812b7add000ea34ae49977462adcf31 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Wed, 18 Dec 2024 22:10:08 +0100 Subject: [PATCH 03/14] pages.php: refactor mod_ip into mod_user_posts_by_ip --- inc/mod/pages.php | 137 +++++++++++++++++++++++++++------------------- 1 file changed, 80 insertions(+), 57 deletions(-) diff --git a/inc/mod/pages.php b/inc/mod/pages.php index 37b282c4..a057ad25 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -861,9 +861,9 @@ function mod_ip_remove_note(Context $ctx, $ip, $id) { $query->bindValue(':id', $id); $query->execute() or error(db_error($query)); - modLog("Removed a note for {$ip}"); + modLog("Removed a note for {$ip}"); - header('Location: ?/IP/' . $ip . '#notes', true, $config['redirect_http']); + \header("Location: ?/user_posts/ip/$ip#notes", true, $config['redirect_http']); } function mod_ip(Context $ctx, $ip, string $encoded_cursor = null) { @@ -879,9 +879,9 @@ function mod_ip(Context $ctx, $ip, string $encoded_cursor = null) { 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; } @@ -901,64 +901,48 @@ function mod_ip(Context $ctx, $ip, string $encoded_cursor = null) { 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; } + if (empty($encoded_cursor)) { + \header("Location: ?/user_posts/ip/$ip", true, 308); + } else { + \header("Location: ?/user_posts/ip/$ip/cursor/$encoded_cursor", true, 308); + } +} + +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); } - $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, $config['mod']['ip_recentposts'], $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'); - if (hasPermission($config['mod']['view_ban'])) { $args['bans'] = Bans::find($ip, false, true, $config['auto_maintenance']); } @@ -989,13 +973,52 @@ function mod_ip(Context $ctx, $ip, string $encoded_cursor = null) { $args['logs'] = []; } - 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"); + $boards = listBoards(); + + $queryable_uris = []; + foreach ($boards as $board) { + $uri = $board['uri']; + if (hasPermission($config['mod']['show_ip'], $uri)) { + $queryable_uris[] = $uri; + } } - mod_page(sprintf('%s: %s', _('IP'), htmlspecialchars($ip)), 'mod/view_ip.html', $args, $args['hostname']); + $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'); + + if (empty($encoded_cursor)) { + $args['security_token'] = make_secure_link_token("user_posts/ip/$ip"); + } else { + $args['security_token'] = make_secure_link_token("user_posts/ip/$ip/cursor/$encoded_cursor"); + } + + mod_page(\sprintf('%s: %s', _('IP'), \htmlspecialchars($ip)), 'mod/view_ip.html', $args, $args['hostname']); } function mod_ban(Context $ctx) { @@ -1913,7 +1936,7 @@ function mod_ban_post(Context $ctx, $board, $delete, $post, $token = false) { $query->bindValue(':time', time()); $query->bindValue(':body', $autotag); $query->execute() or error(db_error($query)); - modLog("Added a note for {$ip}"); + modLog("Added a note for {$ip}"); } } deletePost($post); @@ -2024,7 +2047,7 @@ function mod_warning_post(Context $ctx, $board, $post, $token = false) { $query->bindValue(':time', time()); $query->bindValue(':body', $autotag); $query->execute() or error(db_error($query)); - modLog("Added a note for {$ip}"); + modLog("Added a note for {$ip}"); } } } @@ -2176,7 +2199,7 @@ function mod_delete(Context $ctx, $board, $post) { $query->bindValue(':time', time()); $query->bindValue(':body', $autotag); $query->execute() or error(db_error($query)); - modLog("Added a note for {$ip}"); + modLog("Added a note for {$ip}"); } } deletePost($post); @@ -2347,7 +2370,7 @@ function mod_deletebyip(Context $ctx, $boardName, $post, $global = false) { $query2->bindValue(':time', time()); $query2->bindValue(':body', $autotag); $query2->execute() or error(db_error($query2)); - modLog("Added a note for {$ip}"); + modLog("Added a note for {$ip}"); } } @@ -2378,7 +2401,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']); @@ -2915,7 +2938,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); From bf25002295dd04ee0701a89c8d28c57aadb25f25 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Wed, 18 Dec 2024 22:26:55 +0100 Subject: [PATCH 04/14] ip.html: use mod_user_posts_by_ip --- templates/post/ip.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/post/ip.html b/templates/post/ip.html index 11581ff9..4894394d 100644 --- a/templates/post/ip.html +++ b/templates/post/ip.html @@ -1,3 +1,3 @@ -{% if post.mod and post.mod|hasPermission(config.mod.show_ip, board.uri) %} - [{{ post.ip }}] +{% if post.mod and post.mod|hasPermission(config.mod.show_ip, board.uri) %} + [{{ post.ip }}] {# Keep this space #} {% endif %} From 720ca7787500a3eadb427425719becc1ddd4c5e2 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Wed, 18 Dec 2024 22:33:02 +0100 Subject: [PATCH 05/14] mod.php: add mod_user_posts_by_ip --- mod.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mod.php b/mod.php index 554e4fd5..d53e3cdc 100644 --- a/mod.php +++ b/mod.php @@ -71,6 +71,9 @@ $pages = [ '/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 + '/ban' => 'secure_POST ban', // new ban '/bans' => 'secure_POST bans', // ban list '/bans.json' => 'secure bans_json', // ban list JSON From d29de1a9efb1c03347a05c6e7683273a71c8e7cc Mon Sep 17 00:00:00 2001 From: Zankaria Date: Wed, 18 Dec 2024 23:48:07 +0100 Subject: [PATCH 06/14] user_posts_list.html: add template to list posts --- templates/mod/user_posts_list.html | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 templates/mod/user_posts_list.html diff --git a/templates/mod/user_posts_list.html b/templates/mod/user_posts_list.html new file mode 100644 index 00000000..cc44bc21 --- /dev/null +++ b/templates/mod/user_posts_list.html @@ -0,0 +1,12 @@ +{% for board_posts in posts %} +
+ + + {{ config.board_abbreviation|sprintf(board_posts.board.uri) }} + - + {{ board_posts.board.title|e }} + + + {{ board_posts.posts|join('
') }} +
+{% endfor %} From 39b6a6025713954d970c70cddf6bbcfa7b130639 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Wed, 18 Dec 2024 23:48:22 +0100 Subject: [PATCH 07/14] view_ip.html: use user_posts_list.html --- templates/mod/view_ip.html | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/templates/mod/view_ip.html b/templates/mod/view_ip.html index 78fce4a9..6cc0c83f 100644 --- a/templates/mod/view_ip.html +++ b/templates/mod/view_ip.html @@ -1,4 +1,4 @@ -{% if mod|hasPermission(config.mod.view_notes) %} +{% if mod|hasPermission(config.mod.view_notes) and notes is not null %}
{% set notes_on_record = 'note' ~ (notes|count != 1 ? 's' : '') ~ ' on record' %} @@ -201,18 +201,7 @@
{% endif %} -{% for board_posts in posts %} -
- - - {{ config.board_abbreviation|sprintf(board_posts.board.uri) }} - - - {{ board_posts.board.title|e }} - - - {{ board_posts.posts|join('
') }} -
-{% endfor %} +{{ include('mod/user_posts_list.html', {posts: posts}) }}
[Page 1] {% if cursor_prev %} From 2ec23083a48e42b49e03de6eacdfcacd16257873 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Wed, 18 Dec 2024 23:50:53 +0100 Subject: [PATCH 08/14] view_ip.html: use mod_user_posts_by_ip --- templates/mod/view_ip.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/mod/view_ip.html b/templates/mod/view_ip.html index 6cc0c83f..5edbeb85 100644 --- a/templates/mod/view_ip.html +++ b/templates/mod/view_ip.html @@ -203,11 +203,11 @@ {{ include('mod/user_posts_list.html', {posts: posts}) }}
- [Page 1] + [Page 1] {% if cursor_prev %} - [Previous Page] + [Previous Page] {% endif %} {% if cursor_next %} - [Next Page] + [Next Page] {% endif %}
From 00ef803950a9e5aa019edaf70d409169a099beee Mon Sep 17 00:00:00 2001 From: Zankaria Date: Wed, 18 Dec 2024 23:55:40 +0100 Subject: [PATCH 09/14] ip.html: add password link --- templates/post/ip.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/post/ip.html b/templates/post/ip.html index 4894394d..84047c99 100644 --- a/templates/post/ip.html +++ b/templates/post/ip.html @@ -1,3 +1,3 @@ {% if post.mod and post.mod|hasPermission(config.mod.show_ip, board.uri) %} - [{{ post.ip }}] {# Keep this space #} + [{{ post.ip }}] [{{ post.password[:15] }}] {# Keep this space #} {% endif %} From e1514784db5aea5509e0b9520b62ebc60c88311b Mon Sep 17 00:00:00 2001 From: Zankaria Date: Thu, 19 Dec 2024 00:08:53 +0100 Subject: [PATCH 10/14] mod.php: add mod_user_posts_by_passwd --- mod.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mod.php b/mod.php index d53e3cdc..f08a465b 100644 --- a/mod.php +++ b/mod.php @@ -74,6 +74,9 @@ $pages = [ '/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 From 0609e36ca4aafbfab875fa4dfdcbabbda60fa2da Mon Sep 17 00:00:00 2001 From: Zankaria Date: Thu, 19 Dec 2024 00:09:18 +0100 Subject: [PATCH 11/14] config.php: deprecate ip_recentposts for recent_user_posts --- inc/config.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inc/config.php b/inc/config.php index 08b01ead..095daefe 100644 --- a/inc/config.php +++ b/inc/config.php @@ -1524,8 +1524,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; From 10401bd094fd2456a07cff4d08f26941d0381174 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Thu, 19 Dec 2024 00:16:10 +0100 Subject: [PATCH 12/14] pages.php: add mod_user_posts_by_passwd implementation --- inc/mod/pages.php | 70 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/inc/mod/pages.php b/inc/mod/pages.php index a057ad25..0bbd7065 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -1021,6 +1021,76 @@ function mod_user_posts_by_ip(Context $ctx, string $ip, string $encoded_cursor = mod_page(\sprintf('%s: %s', _('IP'), \htmlspecialchars($ip)), 'mod/view_ip.html', $args, $args['hostname']); } +function mod_user_posts_by_passwd(Context $ctx, string $passwd, string $encoded_cursor = null) { + global $mod; + + // The current hashPassword implementation uses sha3-256, which has a 64 character output in non-binary mode. + if (\strlen($passwd) != 64) { + error('Invalid password'); + } + + $config = $ctx->get('config'); + + $args = [ + 'passwd' => $passwd, + 'posts' => [] + ]; + + if (isset($config['mod']['ip_recentposts'])) { + // TODO log to migrate. + $page_size = $config['mod']['ip_recentposts']; + } else { + $page_size = $config['mod']['recent_user_posts']; + } + + $boards = listBoards(); + + $queryable_uris = []; + foreach ($boards as $board) { + $uri = $board['uri']; + if (hasPermission($config['mod']['show_ip'], $uri)) { + $queryable_uris[] = $uri; + } + } + + $queries = $ctx->get(UserPostQueries::class); + $result = $queries->fetchPaginateByPassword($queryable_uris, $passwd, $page_size, $encoded_cursor); + + $args['cursor_prev'] = $result->cursor_prev; + $args['cursor_next'] = $result->cursor_next; + + foreach($boards as $board) { + $uri = $board['uri']; + // The Thread and Post classes rely on some implicit board parameter set by openBoard. + openBoard($uri); + + // Finally load the post contents and build them. + foreach ($result->by_uri[$uri] as $post) { + if (!$post['thread']) { + $po = new Thread($post, '?/', $mod, false); + } else { + $po = new Post($post, '?/', $mod); + } + + if (!isset($args['posts'][$uri])) { + $args['posts'][$uri] = [ 'board' => $board, 'posts' => [] ]; + } + $args['posts'][$uri]['posts'][] = $po->build(true); + } + } + + $args['boards'] = $boards; + $args['token'] = make_secure_link_token('ban'); + + if (empty($encoded_cursor)) { + $args['security_token'] = make_secure_link_token("user_posts/passwd/$passwd"); + } else { + $args['security_token'] = make_secure_link_token("user_posts/passwd/$passwd/cursor/$encoded_cursor"); + } + + mod_page(\sprintf('%s: %s', _('Password'), \htmlspecialchars($passwd)), 'mod/view_passwd.html', $args); +} + function mod_ban(Context $ctx) { global $config; From d4781d6f004dd98d14b26563a14b2a7d00816477 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Fri, 27 Dec 2024 19:13:27 +0100 Subject: [PATCH 13/14] view_passwd.html: add view password template --- templates/mod/view_passwd.html | 50 ++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 templates/mod/view_passwd.html diff --git a/templates/mod/view_passwd.html b/templates/mod/view_passwd.html new file mode 100644 index 00000000..a9fd7bdd --- /dev/null +++ b/templates/mod/view_passwd.html @@ -0,0 +1,50 @@ +{% if logs and logs|length > 0 %} +
+ History + + + + + + + + {% for log in logs %} + + + + + + + {% endfor %} +
{% trans 'Staff' %}{% trans 'Time' %}{% trans 'Board' %}{% trans 'Action' %}
+ {% if log.username %} + {{ log.username|e }} + {% elseif log.mod == -1 %} + system + {% else %} + {% trans 'deleted?' %} + {% endif %} + + {{ log.time|ago }} + + {% if log.board %} + {{ config.board_abbreviation|sprintf(log.board) }} + {% else %} + - + {% endif %} + + {{ log.text }} +
+
+{% endif %} + +{{ include('mod/user_posts_list.html', {posts: posts}) }} +
+ [Page 1] + {% if cursor_prev %} + [Previous Page] + {% endif %} + {% if cursor_next %} + [Next Page] + {% endif %} +
From 0c23445d723a88bc14cf71dee3adf099d4b096b3 Mon Sep 17 00:00:00 2001 From: Zankaria Date: Fri, 27 Dec 2024 19:43:22 +0100 Subject: [PATCH 14/14] pages.php: slightly better ip error --- inc/mod/pages.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/inc/mod/pages.php b/inc/mod/pages.php index 0bbd7065..f743528f 100644 --- a/inc/mod/pages.php +++ b/inc/mod/pages.php @@ -854,7 +854,7 @@ function mod_ip_remove_note(Context $ctx, $ip, $id) { error($config['error']['noaccess']); if (filter_var($ip, FILTER_VALIDATE_IP) === false) - error("Invalid IP address."); + error('Invalid IP address'); $query = prepare('DELETE FROM ``ip_notes`` WHERE `ip` = :ip AND `id` = :id'); $query->bindValue(':ip', $ip); @@ -870,7 +870,7 @@ function mod_ip(Context $ctx, $ip, string $encoded_cursor = null) { global $config, $mod; if (filter_var($ip, FILTER_VALIDATE_IP) === false) - error("Invalid IP address."); + error('Invalid IP address'); if (isset($_POST['ban_id'], $_POST['unban'])) { if (!hasPermission($config['mod']['unban'])) @@ -922,7 +922,7 @@ function mod_user_posts_by_ip(Context $ctx, string $ip, string $encoded_cursor = global $mod; if (\filter_var($ip, \FILTER_VALIDATE_IP) === false){ - error("Invalid IP address."); + error('Invalid IP address'); } $config = $ctx->get('config');