UserPostQueries.php: rewrite of searchPosts

This commit is contained in:
Zankaria 2025-05-21 22:55:25 +02:00
parent 73c62b1088
commit 20bf8b542e

View file

@ -13,6 +13,36 @@ class UserPostQueries {
private \PDO $pdo;
/**
* Escapes wildcards from LIKE operators using the default escape character.
*/
private static function escapeLike(string $str): string {
// Escape any existing escape characters.
$str = \str_replace('\\', '\\\\', $str);
// Escape wildcard characters.
$str = \str_replace('%', '\\%', $str);
$str = \str_replace('_', '\\_', $str);
return $str;
}
/**
* Joins the fragments of filter into a single LIKE operand string.
* Given prefix = cat and fragments_count = 3, we get "%:cat0%:cat1%:cat2%"
*
* @param string $prefix The prefix for the parameter binding
* @param int $fragments_count MUST BE >= 1.
* @return string
*/
private static function joinFragments(string $prefix, int $fragments_count): string {
// With prefix = cat and fragments = 2 it becomes: "%:cat0%:cat1%"
$s = '%';
for ($i = 0; $i < $fragments_count; $i++) {
$s .= ":$prefix$i%";
}
return $s;
}
public function __construct(\PDO $pdo) {
$this->pdo = $pdo;
}
@ -157,63 +187,69 @@ class UserPostQueries {
});
}
public function searchPosts(string $board, ?string $subject, ?string $name, ?array $flags, ?int $id, ?int $thread, array $text_chunks, int $limit): array {
$filters_acc = [];
if ($subject !== null) {
$acc[] = " WHERE subject LIKE '%?%'";
}
if ($name !== null) {
$acc[] = " WHERE name LIKE '%?%'";
}
if ($id !== null) {
$acc[] = " WHERE id = ?";
}
if ($thread !== null) {
$acc[] = " WHERE thread = ?";
}
if ($flags !== null) {
$f_acc = [];
for ($i = 0; $i < \count($flags); $i++) {
// Yes, vichan stores the flag inside the generated HTML.
// English lacks the words to express my feelings about it in a satisfying manner.
$f_acc[] = " WHERE body_nomarkup LIKE = '%<tinyboard>?</tinyboard>%'";
}
$acc[] = '(' . \implode(' OR ', $f_acc) . ')';
}
for ($i = 0; $i < \count($text_chunks); $i++) {
$filters_acc = " WHERE body LIKE = '%?%'";
}
$sql = "'SELECT * FROM `posts_$board` " . \implode(' AND ', $filters_acc) . ' LIMIT ?';
public function searchPosts(string $board, array $subject, array $name, array $flags, ?int $id, ?int $thread, array $bodies, int $limit): array {
$where_acc = [];
$query = $this->pdo->prepare($sql);
$bound_i = 0;
if ($subject !== null) {
$query->bindValue($bound_i, $subject);
$bound_i++;
if (!empty($subject)) {
$like_op = self::joinFragments('subj', \count($subject));
$where_acc[] = "WHERE subject LIKE '$like_op'";
}
if ($name !== null) {
$query->bindValue($bound_i, $name);
$bound_i++;
if (!empty($name)) {
$like_op = self::joinFragments('name', \count($name));
$where_acc[] = "WHERE name LIKE '$like_op'";
}
if (!empty($flags)) {
$flag_acc = [];
for ($i = 0; $i < \count($flags); $i++) {
// Yes, vichan stores the flag inside the generated HTML. Now you know why it's slow as shit.
// English lacks the words to express my feelings about it in a satisfying manner.
$flag_acc[] = "LIKE = '%<tinyboard>:flag$i</tinyboard>%'";
}
$where_acc[] = 'WHERE body_nomarkup (' . \implode(' OR ', $flag_acc) . ')';
}
if ($id !== null) {
$query->bindValue($bound_i, $id, \PDO::PARAM_INT);
$bound_i++;
$where_acc[] = 'WHERE id = :id';
}
if ($thread !== null) {
$query->bindValue($bound_i, $thread, \PDO::PARAM_INT);
$bound_i++;
$where_acc[] = 'WHERE thread = :thread';
}
if ($flags !== null) {
foreach ($flags as $flag) {
$query->bindValue($bound_i, $flag);
$bound_i++;
for ($i = 0; $i < \count($bodies); $i++) {
$body = $bodies[$i];
$like_op = self::joinFragments("body_{$i}_", \count($body));
$where_acc[] = "WHERE body LIKE '$like_op'";
}
if (empty($where_acc)) {
return [];
}
$sql = "SELECT * FROM `posts_$board` " . \implode(' AND ', $where_acc) . ' LIMIT ?';
$query = $this->pdo->prepare($sql);
for ($i = 0; $i < \count($subject); $i++) {
$query->bindValue(":subj$i", $subject[$i]);
}
for ($i = 0; $i < \count($name); $i++) {
$query->bindValue(":name$i", $name[$i]);
}
for ($i = 0; $i < \count($flags); $i++) {
$query->bindValue(":flag$i", $flags[$i]);
}
if ($id !== null) {
$query->bindValue(':id', $id, \PDO::PARAM_INT);
}
if ($thread !== null) {
$query->bindValue(':thread', $thread, \PDO::PARAM_INT);
}
for ($body_i = 0; $body_i < \count($bodies); $body_i++) {
$body = $bodies[$body_i];
for ($i = 0; $i < \count($body); $i++) {
$query->bindValue(":body_{$body_i}_{$i}", $body[$i]);
}
}
foreach ($text_chunks as $chunk) {
$query->bindValue($bound_i, $chunk);
$bound_i++;
}
$query->bindValue($bound_i, $limit, \PDO::PARAM_INT);
$query->bindValue(':limit', $limit, \PDO::PARAM_INT);
$query->execute();
return $query->fetchAll(\PDO::FETCH_ASSOC);