forked from leftypol/leftypol
Merge branch 'user-browse-refactor' into 'config'
User browse wrapper class See merge request leftypol/leftypol!16
This commit is contained in:
commit
da1970d16f
4 changed files with 164 additions and 92 deletions
15
inc/Data/PageFetchResult.php
Normal file
15
inc/Data/PageFetchResult.php
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<?php
|
||||||
|
namespace Vichan\Data;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A page of user posts.
|
||||||
|
*/
|
||||||
|
class PageFetchResult {
|
||||||
|
/**
|
||||||
|
* @var array[array] Posts grouped by board uri.
|
||||||
|
*/
|
||||||
|
public array $by_uri;
|
||||||
|
public ?string $cursor_prev;
|
||||||
|
public ?string $cursor_next;
|
||||||
|
}
|
115
inc/Data/UserPostQueries.php
Normal file
115
inc/Data/UserPostQueries.php
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
<?php
|
||||||
|
namespace Vichan\Data;
|
||||||
|
|
||||||
|
use Vichan\Functions\Net;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Browse user posts
|
||||||
|
*/
|
||||||
|
class UserPostQueries {
|
||||||
|
private const CURSOR_TYPE_PREV = 'p';
|
||||||
|
private const CURSOR_TYPE_NEXT = 'n';
|
||||||
|
|
||||||
|
private \PDO $pdo;
|
||||||
|
|
||||||
|
public function __construct(\PDO $pdo) {
|
||||||
|
$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 {
|
||||||
|
// 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.
|
||||||
|
$id_cursor = false;
|
||||||
|
if (isset($uri_id_cursor_map[$uri])) {
|
||||||
|
$value = $uri_id_cursor_map[$uri];
|
||||||
|
if (\is_numeric($value)) {
|
||||||
|
$id_cursor = (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_count = \count($posts);
|
||||||
|
|
||||||
|
if ($posts_count === $page_size + 2) {
|
||||||
|
$has_extra_prev_post = true;
|
||||||
|
$has_extra_end_post = true;
|
||||||
|
} elseif ($posts_count === $page_size + 1) {
|
||||||
|
$has_extra_prev_post = $id_cursor !== false && $id_cursor === (int)$posts[0]['id'];
|
||||||
|
$has_extra_end_post = !$has_extra_prev_post;
|
||||||
|
} else {
|
||||||
|
$has_extra_prev_post = false;
|
||||||
|
$has_extra_end_post = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since we fetched one post bellow and/or above the limit, we always know if there are any posts after the current page.
|
||||||
|
|
||||||
|
// Get the previous cursor, if any.
|
||||||
|
if ($has_extra_prev_post) {
|
||||||
|
\array_shift($posts);
|
||||||
|
$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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,7 @@
|
||||||
namespace Vichan;
|
namespace Vichan;
|
||||||
|
|
||||||
use Vichan\Data\Driver\CacheDriver;
|
use Vichan\Data\Driver\CacheDriver;
|
||||||
use Vichan\Data\ReportQueries;
|
use Vichan\Data\{ReportQueries, UserPostQueries};
|
||||||
|
|
||||||
defined('TINYBOARD') or exit;
|
defined('TINYBOARD') or exit;
|
||||||
|
|
||||||
|
@ -45,6 +45,9 @@ function build_context(array $config): Context {
|
||||||
$auto_maintenance = (bool)$c->get('config')['auto_maintenance'];
|
$auto_maintenance = (bool)$c->get('config')['auto_maintenance'];
|
||||||
$pdo = $c->get(\PDO::class);
|
$pdo = $c->get(\PDO::class);
|
||||||
return new ReportQueries($pdo, $auto_maintenance);
|
return new ReportQueries($pdo, $auto_maintenance);
|
||||||
|
},
|
||||||
|
UserPostQueries::class => function($c) {
|
||||||
|
return new UserPostQueries($c->get(\PDO::class));
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,9 @@
|
||||||
* Copyright (c) 2010-2013 Tinyboard Development Group
|
* Copyright (c) 2010-2013 Tinyboard Development Group
|
||||||
*/
|
*/
|
||||||
use Vichan\Context;
|
use Vichan\Context;
|
||||||
use Vichan\Data\ReportQueries;
|
use Vichan\Data\{UserPostQueries, ReportQueries};
|
||||||
use Vichan\Functions\Format;
|
|
||||||
use Vichan\Functions\Net;
|
use Vichan\Functions\Net;
|
||||||
|
|
||||||
use function Vichan\Functions\Net\decode_cursor;
|
|
||||||
use function Vichan\Functions\Net\encode_cursor;
|
|
||||||
|
|
||||||
defined('TINYBOARD') or exit;
|
defined('TINYBOARD') or exit;
|
||||||
|
|
||||||
function _link_or_copy(string $target, string $link): bool {
|
function _link_or_copy(string $target, string $link): bool {
|
||||||
|
@ -870,7 +866,7 @@ function mod_ip_remove_note(Context $ctx, $ip, $id) {
|
||||||
header('Location: ?/IP/' . $ip . '#notes', true, $config['redirect_http']);
|
header('Location: ?/IP/' . $ip . '#notes', true, $config['redirect_http']);
|
||||||
}
|
}
|
||||||
|
|
||||||
function mod_ip(Context $ctx, $ip, string $encoded_cursor = '') {
|
function mod_ip(Context $ctx, $ip, string $encoded_cursor = null) {
|
||||||
global $config, $mod;
|
global $config, $mod;
|
||||||
|
|
||||||
if (filter_var($ip, FILTER_VALIDATE_IP) === false)
|
if (filter_var($ip, FILTER_VALIDATE_IP) === false)
|
||||||
|
@ -924,98 +920,41 @@ function mod_ip(Context $ctx, $ip, string $encoded_cursor = '') {
|
||||||
$args['hostname'] = rDNS($ip);
|
$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();
|
$boards = listBoards();
|
||||||
|
|
||||||
|
$queryable_uris = [];
|
||||||
foreach ($boards as $board) {
|
foreach ($boards as $board) {
|
||||||
$uri = $board['uri'];
|
$uri = $board['uri'];
|
||||||
openBoard($uri);
|
|
||||||
if (hasPermission($config['mod']['show_ip'], $uri)) {
|
if (hasPermission($config['mod']['show_ip'], $uri)) {
|
||||||
// Extract the cursor relative to the board.
|
$queryable_uris[] = $uri;
|
||||||
$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.
|
$queries = $ctx->get(UserPostQueries::class);
|
||||||
$args['cursor_prev'] = !empty($encoded_cursor) ? encode_cursor('p', $prev_cursor_map) : false;
|
$result = $queries->fetchPaginatedByIp($queryable_uris, $ip, $config['mod']['ip_recentposts'], $encoded_cursor);
|
||||||
$args['cursor_next'] = !empty($next_cursor_map) ? encode_cursor('n', $next_cursor_map) : false;
|
|
||||||
|
$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['boards'] = $boards;
|
||||||
$args['token'] = make_secure_link_token('ban');
|
$args['token'] = make_secure_link_token('ban');
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue