1559 lines
61 KiB
PHP
1559 lines
61 KiB
PHP
|
<?php
|
||
|
namespace Misuzu\Forum;
|
||
|
|
||
|
use InvalidArgumentException;
|
||
|
use RuntimeException;
|
||
|
use stdClass;
|
||
|
use Index\DateTime;
|
||
|
use Index\Colour\Colour;
|
||
|
use Index\Data\DbStatementCache;
|
||
|
use Index\Data\DbTools;
|
||
|
use Index\Data\IDbConnection;
|
||
|
use Index\Net\IPAddress;
|
||
|
use Misuzu\Pagination;
|
||
|
use Misuzu\Users\UserInfo;
|
||
|
|
||
|
class Forum {
|
||
|
private IDbConnection $dbConn;
|
||
|
private DbStatementCache $cache;
|
||
|
|
||
|
public function __construct(IDbConnection $dbConn) {
|
||
|
$this->dbConn = $dbConn;
|
||
|
$this->cache = new DbStatementCache($dbConn);
|
||
|
}
|
||
|
|
||
|
public static function convertCategoryListToTree(
|
||
|
array $catInfos,
|
||
|
ForumCategoryInfo|string|null $parentInfo = null,
|
||
|
?Colour $colour = null
|
||
|
): array {
|
||
|
$colour ??= Colour::none();
|
||
|
$tree = [];
|
||
|
$predicate = $parentInfo
|
||
|
? fn($catInfo) => $catInfo->isDirectChildOf($parentInfo)
|
||
|
: fn($catInfo) => !$catInfo->hasParent();
|
||
|
|
||
|
foreach($catInfos as $catInfo) {
|
||
|
if(!$predicate($catInfo))
|
||
|
continue;
|
||
|
|
||
|
$tree[$catInfo->getId()] = $item = new stdClass;
|
||
|
$item->info = $catInfo;
|
||
|
$item->colour = $catInfo->hasColour() ? $catInfo->getColour() : $colour;
|
||
|
$item->children = self::convertCategoryListToTree($catInfos, $catInfo, $item->colour);
|
||
|
$item->childIds = [];
|
||
|
foreach($item->children as $child) {
|
||
|
$item->childIds[] = $child->info->getId();
|
||
|
$item->childIds += $child->childIds;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $tree;
|
||
|
}
|
||
|
|
||
|
public function countCategories(
|
||
|
ForumCategoryInfo|string|null|false $parentInfo = false,
|
||
|
string|int|null $type = null,
|
||
|
?bool $hidden = null
|
||
|
): array {
|
||
|
if($parentInfo instanceof ForumCategoryInfo)
|
||
|
$parentInfo = $parentInfo->getId();
|
||
|
|
||
|
$hasParentInfo = $parentInfo !== false;
|
||
|
$hasType = $type !== null;
|
||
|
$hasHidden = $hidden !== null;
|
||
|
|
||
|
$args = 0;
|
||
|
$query = 'SELECT COUNT(*) FROM msz_forum_categories';
|
||
|
if($hasParentInfo) {
|
||
|
++$args;
|
||
|
$isRootParent = $parentInfo === null;
|
||
|
if($isRootParent) { // make a migration that makes the field DEFAULT NULL and update all 0s to NULL
|
||
|
$query .= 'WHERE (forum_parent IS NULL OR forum_parent = 0)';
|
||
|
} else {
|
||
|
$query .= 'WHERE forum_parent = ?';
|
||
|
}
|
||
|
}
|
||
|
if($hasType) {
|
||
|
if(is_string($type)) {
|
||
|
if(!array_key_exists($type, ForumCategoryInfo::TYPE_ALIASES))
|
||
|
throw new InvalidArgumentException('$type is not a valid alias.');
|
||
|
$type = ForumCategoryInfo::TYPE_ALIASES[$type];
|
||
|
}
|
||
|
|
||
|
$query .= sprintf(' %s forum_type = ?', ++$args > 1 ? 'AND' : 'WHERE');
|
||
|
}
|
||
|
if($hasHidden)
|
||
|
$query .= sprintf(' %s forum_hidden %s 0', ++$args > 1 ? 'AND' : 'WHERE', $hidden ? '<>' : '=');
|
||
|
|
||
|
$args = 0;
|
||
|
$stmt = $this->cache->get($query);
|
||
|
if($hasParentInfo && !$isRootParent)
|
||
|
$stmt->addParameter(++$args, $parentInfo->getId());
|
||
|
if($hasType)
|
||
|
$stmt->addParameter(++$args, $type);
|
||
|
$stmt->execute();
|
||
|
|
||
|
$result = $stmt->getResult();
|
||
|
return $result->next() ? $result->getInteger(0) : 0;
|
||
|
}
|
||
|
|
||
|
public function getCategories(
|
||
|
ForumCategoryInfo|string|null|false $parentInfo = false,
|
||
|
string|int|null $type = null,
|
||
|
?bool $hidden = null,
|
||
|
bool $asTree = false,
|
||
|
?Pagination $pagination = null
|
||
|
): array {
|
||
|
$hasParentInfo = $parentInfo !== false;
|
||
|
$hasType = $type !== null;
|
||
|
$hasHidden = $hidden !== null;
|
||
|
$hasPagination = $pagination !== null;
|
||
|
|
||
|
if($hasParentInfo && $asTree)
|
||
|
throw new InvalidArgumentException('$asTree can only be used with $parentInfo set to false.');
|
||
|
|
||
|
$args = 0;
|
||
|
$query = 'SELECT forum_id, forum_order, forum_parent, forum_name, forum_type, forum_description, forum_icon, forum_colour, forum_link, forum_link_clicks, UNIX_TIMESTAMP(forum_created), forum_archived, forum_hidden, forum_count_topics, forum_count_posts FROM msz_forum_categories';
|
||
|
if($hasParentInfo) {
|
||
|
++$args;
|
||
|
$isRootParent = $parentInfo === null;
|
||
|
if($isRootParent) { // make a migration that makes the field DEFAULT NULL and update all 0s to NULL
|
||
|
$query .= ' WHERE (forum_parent IS NULL OR forum_parent = 0)';
|
||
|
} else {
|
||
|
$query .= ' WHERE forum_parent = ?';
|
||
|
}
|
||
|
}
|
||
|
if($hasType) {
|
||
|
if(is_string($type)) {
|
||
|
if(!array_key_exists($type, ForumCategoryInfo::TYPE_ALIASES))
|
||
|
throw new InvalidArgumentException('$type is not a valid alias.');
|
||
|
$type = ForumCategoryInfo::TYPE_ALIASES[$type];
|
||
|
}
|
||
|
|
||
|
$query .= sprintf(' %s forum_type = ?', ++$args > 1 ? 'AND' : 'WHERE');
|
||
|
}
|
||
|
if($hasHidden)
|
||
|
$query .= sprintf(' %s forum_hidden %s 0', ++$args > 1 ? 'AND' : 'WHERE', $hidden ? '<>' : '=');
|
||
|
$query .= ' ORDER BY forum_parent, forum_type <> 1, forum_order';
|
||
|
if($hasPagination)
|
||
|
$query .= ' LIMIT ? OFFSET ?';
|
||
|
|
||
|
$args = 0;
|
||
|
$stmt = $this->cache->get($query);
|
||
|
if($hasParentInfo && !$isRootParent) {
|
||
|
if($parentInfo instanceof ForumCategoryInfo)
|
||
|
$stmt->addParameter(++$args, $parentInfo->getId());
|
||
|
else
|
||
|
$stmt->addParameter(++$args, $parentInfo);
|
||
|
}
|
||
|
if($hasType)
|
||
|
$stmt->addParameter(++$args, $type);
|
||
|
if($hasPagination) {
|
||
|
$stmt->addParameter(++$args, $pagination->getRange());
|
||
|
$stmt->addParameter(++$args, $pagination->getOffset());
|
||
|
}
|
||
|
$stmt->execute();
|
||
|
|
||
|
$result = $stmt->getResult();
|
||
|
$cats = [];
|
||
|
|
||
|
while($result->next())
|
||
|
$cats[] = new ForumCategoryInfo($result);
|
||
|
|
||
|
if($asTree)
|
||
|
$cats = self::convertCategoryListToTree($cats);
|
||
|
|
||
|
return $cats;
|
||
|
}
|
||
|
|
||
|
public function getCategory(
|
||
|
?string $categoryId = null,
|
||
|
ForumTopicInfo|string|null $topicInfo = null,
|
||
|
ForumPostInfo|string|null $postInfo = null
|
||
|
): ForumCategoryInfo {
|
||
|
$hasCategoryId = $categoryId !== null;
|
||
|
$hasTopicInfo = $topicInfo !== null;
|
||
|
$hasPostInfo = $postInfo !== null;
|
||
|
|
||
|
if(!$hasCategoryId && !$hasTopicInfo && !$hasPostInfo)
|
||
|
throw new InvalidArgumentException('You must specify an argument.');
|
||
|
if(($hasCategoryId && ($hasTopicInfo || $hasPostInfo))
|
||
|
|| ($hasTopicInfo && ($hasCategoryId || $hasPostInfo))
|
||
|
|| ($hasPostInfo && ($hasCategoryId || $hasTopicInfo)))
|
||
|
throw new InvalidArgumentException('Only one argument may be specified.');
|
||
|
|
||
|
$value = null;
|
||
|
$query = 'SELECT forum_id, forum_order, forum_parent, forum_name, forum_type, forum_description, forum_icon, forum_colour, forum_link, forum_link_clicks, UNIX_TIMESTAMP(forum_created), forum_archived, forum_hidden, forum_count_topics, forum_count_posts FROM msz_forum_categories';
|
||
|
if($hasCategoryId) {
|
||
|
$query .= ' WHERE forum_id = ?';
|
||
|
$value = $categoryId;
|
||
|
}
|
||
|
if($hasTopicInfo) {
|
||
|
if($topicInfo instanceof ForumTopicInfo) {
|
||
|
$query .= ' WHERE forum_id = ?';
|
||
|
$value = $topicInfo->getCategoryId();
|
||
|
} else {
|
||
|
$query .= ' WHERE forum_id = (SELECT forum_id FROM msz_forum_topics WHERE topic_id = ?)';
|
||
|
$value = $topicInfo;
|
||
|
}
|
||
|
}
|
||
|
if($hasPostInfo) {
|
||
|
if($postInfo instanceof ForumPostInfo) {
|
||
|
$query .= ' WHERE forum_id = ?';
|
||
|
$value = $postInfo->getCategoryId();
|
||
|
} else {
|
||
|
$query .= ' WHERE forum_id = (SELECT forum_id FROM msz_forum_posts WHERE post_id = ?)';
|
||
|
$value = $postInfo;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$stmt = $this->cache->get($query);
|
||
|
$stmt->addParameter(1, $value);
|
||
|
$stmt->execute();
|
||
|
|
||
|
$result = $stmt->getResult();
|
||
|
if(!$result->next())
|
||
|
throw new RuntimeException('Forum category info not found.');
|
||
|
|
||
|
return new ForumCategoryInfo($result);
|
||
|
}
|
||
|
|
||
|
public function updateCategory(
|
||
|
ForumCategoryInfo|string $categoryInfo
|
||
|
): void {
|
||
|
if($categoryInfo instanceof ForumCategoryInfo)
|
||
|
$categoryInfo = $categoryInfo->getId();
|
||
|
}
|
||
|
|
||
|
public function deleteCategory(ForumCategoryInfo|string $categoryInfo): void {
|
||
|
if($categoryInfo instanceof ForumCategoryInfo)
|
||
|
$categoryInfo = $categoryInfo->getId();
|
||
|
}
|
||
|
|
||
|
public function incrementCategoryClicks(ForumCategoryInfo|string $categoryInfo): void {
|
||
|
if($categoryInfo instanceof ForumCategoryInfo)
|
||
|
$categoryInfo = $categoryInfo->getId();
|
||
|
|
||
|
// previous implementation also WHERE'd for forum_type = link but i don't think there's any other way to get here anyhow
|
||
|
$stmt = $this->cache->get('UPDATE msz_forum_categories SET forum_link_clicks = forum_link_clicks + 1 WHERE forum_id = ? AND forum_link_clicks IS NOT NULL');
|
||
|
$stmt->addParameter(1, $categoryInfo);
|
||
|
$stmt->execute();
|
||
|
}
|
||
|
|
||
|
public function incrementCategoryTopics(ForumCategoryInfo|string $categoryInfo): void {
|
||
|
if($categoryInfo instanceof ForumCategoryInfo)
|
||
|
$categoryInfo = $categoryInfo->getId();
|
||
|
|
||
|
$stmt = $this->cache->get('UPDATE msz_forum_categories SET forum_count_topics = forum_count_topics + 1 WHERE forum_id = ?');
|
||
|
$stmt->addParameter(1, $categoryInfo);
|
||
|
$stmt->execute();
|
||
|
}
|
||
|
|
||
|
public function incrementCategoryPosts(ForumCategoryInfo|string $categoryInfo): void {
|
||
|
if($categoryInfo instanceof ForumCategoryInfo)
|
||
|
$categoryInfo = $categoryInfo->getId();
|
||
|
|
||
|
$stmt = $this->cache->get('UPDATE msz_forum_categories SET forum_count_posts = forum_count_posts + 1 WHERE forum_id = ?');
|
||
|
$stmt->addParameter(1, $categoryInfo);
|
||
|
$stmt->execute();
|
||
|
}
|
||
|
|
||
|
public function getCategoryAncestry(
|
||
|
ForumCategoryInfo|ForumTopicInfo|ForumPostInfo|string $categoryInfo
|
||
|
): array {
|
||
|
if($categoryInfo instanceof ForumCategoryInfo)
|
||
|
$categoryInfo = $categoryInfo->getId();
|
||
|
elseif($categoryInfo instanceof ForumTopicInfo || $categoryInfo instanceof ForumPostInfo)
|
||
|
$categoryInfo = $categoryInfo->getCategoryId();
|
||
|
|
||
|
$query = 'WITH RECURSIVE msz_cte_ancestry AS ('
|
||
|
. 'SELECT forum_id, forum_order, forum_parent, forum_name, forum_type, forum_description, forum_icon, forum_colour, forum_link, forum_link_clicks, UNIX_TIMESTAMP(forum_created), forum_archived, forum_hidden, forum_count_topics, forum_count_posts FROM msz_forum_categories WHERE forum_id = ?'
|
||
|
. ' UNION ALL'
|
||
|
. ' SELECT fc.forum_id, fc.forum_order, fc.forum_parent, fc.forum_name, fc.forum_type, fc.forum_description, fc.forum_icon, fc.forum_colour, fc.forum_link, fc.forum_link_clicks, UNIX_TIMESTAMP(fc.forum_created), fc.forum_archived, fc.forum_hidden, fc.forum_count_topics, fc.forum_count_posts FROM msz_forum_categories AS fc JOIN msz_cte_ancestry AS ca ON fc.forum_id = ca.forum_parent'
|
||
|
. ') SELECT * FROM msz_cte_ancestry';
|
||
|
|
||
|
$stmt = $this->cache->get($query);
|
||
|
$stmt->addParameter(1, $categoryInfo);
|
||
|
$stmt->execute();
|
||
|
|
||
|
$result = $stmt->getResult();
|
||
|
$cats = [];
|
||
|
|
||
|
while($result->next())
|
||
|
$cats[] = new ForumCategoryInfo($result);
|
||
|
|
||
|
return $cats;
|
||
|
}
|
||
|
|
||
|
public function getCategoryChildren(
|
||
|
ForumCategoryInfo|string $parentInfo,
|
||
|
bool $includeSelf = false,
|
||
|
?bool $hidden = null,
|
||
|
bool $asTree = false
|
||
|
): array {
|
||
|
if($parentInfo instanceof ForumCategoryInfo)
|
||
|
$parentInfo = $parentInfo->getId();
|
||
|
|
||
|
$hasHidden = $hidden !== null;
|
||
|
|
||
|
$query = 'WITH RECURSIVE msz_cte_children AS ('
|
||
|
. 'SELECT forum_id, forum_order, forum_parent, forum_name, forum_type, forum_description, forum_icon, forum_colour, forum_link, forum_link_clicks, UNIX_TIMESTAMP(forum_created), forum_archived, forum_hidden, forum_count_topics, forum_count_posts FROM msz_forum_categories WHERE forum_id = ?'
|
||
|
. ' UNION ALL'
|
||
|
. ' SELECT fc.forum_id, fc.forum_order, fc.forum_parent, fc.forum_name, fc.forum_type, fc.forum_description, fc.forum_icon, fc.forum_colour, fc.forum_link, fc.forum_link_clicks, UNIX_TIMESTAMP(fc.forum_created), fc.forum_archived, fc.forum_hidden, fc.forum_count_topics, fc.forum_count_posts FROM msz_forum_categories AS fc JOIN msz_cte_children AS cc ON fc.forum_parent = cc.forum_id'
|
||
|
. ') SELECT * FROM msz_cte_children';
|
||
|
|
||
|
$args = 0;
|
||
|
if(!$includeSelf) {
|
||
|
++$args;
|
||
|
$query .= ' WHERE forum_id <> ?';
|
||
|
}
|
||
|
if($hasHidden)
|
||
|
$query .= sprintf(' %s forum_hidden %s 0', ++$args > 1 ? 'AND' : 'WHERE', $hidden ? '<>' : '=');
|
||
|
$query .= ' ORDER BY forum_parent, forum_order';
|
||
|
|
||
|
$args = 0;
|
||
|
$stmt = $this->cache->get($query);
|
||
|
$stmt->addParameter(++$args, $parentInfo);
|
||
|
if(!$includeSelf)
|
||
|
$stmt->addParameter(++$args, $parentInfo);
|
||
|
$stmt->execute();
|
||
|
|
||
|
$result = $stmt->getResult();
|
||
|
$cats = [];
|
||
|
|
||
|
while($result->next())
|
||
|
$cats[] = new ForumCategoryInfo($result);
|
||
|
|
||
|
if($asTree)
|
||
|
$cats = self::convertCategoryListToTree($cats, $parentInfo);
|
||
|
|
||
|
return $cats;
|
||
|
}
|
||
|
|
||
|
public function checkCategoryUnread(
|
||
|
ForumCategoryInfo|string|array $categoryInfos,
|
||
|
UserInfo|string|null $userInfo
|
||
|
): bool {
|
||
|
if($userInfo === null)
|
||
|
return false;
|
||
|
|
||
|
if(!is_array($categoryInfos))
|
||
|
$categoryInfos = [$categoryInfos];
|
||
|
if($userInfo instanceof UserInfo)
|
||
|
$userInfo = $userInfo->getId();
|
||
|
|
||
|
$args = 0;
|
||
|
$stmt = $this->cache->get(sprintf(
|
||
|
'SELECT COUNT(*) FROM msz_forum_topics AS ft LEFT JOIN msz_forum_topics_track AS ftt ON ftt.topic_id = ft.topic_id AND ftt.user_id = ? WHERE ft.forum_id IN (%s) AND ft.topic_deleted IS NULL AND ft.topic_bumped >= NOW() - INTERVAL 1 MONTH AND (ftt.track_last_read IS NULL OR ftt.track_last_read < ft.topic_bumped)',
|
||
|
DbTools::prepareListString($categoryInfos)
|
||
|
));
|
||
|
$stmt->addParameter(++$args, $userInfo);
|
||
|
foreach($categoryInfos as $categoryInfo) {
|
||
|
if($categoryInfo instanceof ForumCategoryInfo)
|
||
|
$stmt->addParameter(++$args, $categoryInfo->getId());
|
||
|
elseif(is_string($categoryInfo) || is_int($categoryInfo))
|
||
|
$stmt->addParameter(++$args, $categoryInfo);
|
||
|
else
|
||
|
throw new InvalidArgumentException('Invalid item in $categoryInfos.');
|
||
|
}
|
||
|
$stmt->execute();
|
||
|
|
||
|
$result = $stmt->getResult();
|
||
|
return $result->next() && $result->getInteger(0) > 0;
|
||
|
}
|
||
|
|
||
|
public function updateUserReadCategory(
|
||
|
UserInfo|string|null $userInfo,
|
||
|
ForumCategoryInfo|string $categoryInfo
|
||
|
): void {
|
||
|
if($userInfo === null)
|
||
|
return;
|
||
|
|
||
|
if($userInfo instanceof UserInfo)
|
||
|
$userInfo = $userInfo->getId();
|
||
|
if($categoryInfo instanceof $categoryInfo)
|
||
|
$categoryInfo = $categoryInfo->getId();
|
||
|
|
||
|
$stmt = $this->cache->get('REPLACE INTO msz_forum_topics_track (user_id, topic_id, forum_id, track_last_read) SELECT ?, topic_id, forum_id, NOW() FROM msz_forum_topics WHERE forum_id = ? AND topic_bumped >= NOW() - INTERVAL 1 MONTH');
|
||
|
$stmt->addParameter(1, $userInfo);
|
||
|
$stmt->addParameter(2, $categoryInfo);
|
||
|
$stmt->execute();
|
||
|
}
|
||
|
|
||
|
public function getCategoryColour(
|
||
|
ForumCategoryInfo|ForumTopicInfo|ForumPostInfo|string $categoryInfo
|
||
|
): Colour {
|
||
|
if($categoryInfo instanceof ForumCategoryInfo)
|
||
|
$categoryInfo = $categoryInfo->getId();
|
||
|
elseif($categoryInfo instanceof ForumTopicInfo || $categoryInfo instanceof ForumPostInfo)
|
||
|
$categoryInfo = $categoryInfo->getCategoryId();
|
||
|
|
||
|
$query = 'WITH RECURSIVE msz_cte_colours AS ('
|
||
|
. 'SELECT forum_id, forum_parent, forum_colour FROM msz_forum_categories WHERE forum_id = ?'
|
||
|
. ' UNION ALL'
|
||
|
. ' SELECT fc.forum_id, fc.forum_parent, fc.forum_colour FROM msz_forum_categories AS fc JOIN msz_cte_colours AS cc ON fc.forum_id = cc.forum_parent'
|
||
|
. ') SELECT forum_colour FROM msz_cte_colours WHERE forum_colour IS NOT NULL';
|
||
|
|
||
|
$stmt = $this->cache->get($query);
|
||
|
$stmt->addParameter(1, $categoryInfo);
|
||
|
$stmt->execute();
|
||
|
|
||
|
$result = $stmt->getResult();
|
||
|
return $result->next() ? Colour::fromMisuzu($result->getInteger(0)) : Colour::none();
|
||
|
}
|
||
|
|
||
|
public function getMostActiveCategoryInfo(
|
||
|
UserInfo|string $userInfo,
|
||
|
array $exceptCategoryInfos = [],
|
||
|
array $exceptTopicInfos = [],
|
||
|
?bool $deleted = null
|
||
|
): object {
|
||
|
if($userInfo instanceof UserInfo)
|
||
|
$userInfo = $userInfo->getId();
|
||
|
|
||
|
$hasExceptCategoryInfos = !empty($exceptCategoryInfos);
|
||
|
$hasExceptTopicInfos = !empty($exceptTopicInfos);
|
||
|
$hasDeleted = $deleted !== null;
|
||
|
|
||
|
$query = 'SELECT forum_id, COUNT(*) AS post_count FROM msz_forum_posts WHERE user_id = ?';
|
||
|
if($hasDeleted)
|
||
|
$query .= sprintf(' AND post_deleted %s NULL', $deleted ? 'IS NOT' : 'IS');
|
||
|
if($hasExceptCategoryInfos)
|
||
|
$query .= sprintf(' AND forum_id NOT IN (%s)', DbTools::prepareListString($exceptCategoryInfos));
|
||
|
if($hasExceptTopicInfos)
|
||
|
$query .= sprintf(' AND topic_id NOT IN (%s)', DbTools::prepareListString($exceptTopicInfos));
|
||
|
$query .= ' GROUP BY forum_id ORDER BY post_count DESC LIMIT 1';
|
||
|
|
||
|
$args = 0;
|
||
|
$stmt = $this->cache->get($query);
|
||
|
$stmt->addParameter(++$args, $userInfo);
|
||
|
foreach($exceptCategoryInfos as $categoryInfo) {
|
||
|
if($categoryInfo instanceof ForumCategoryInfo)
|
||
|
$stmt->addParameter(++$args, $categoryInfo->getId());
|
||
|
elseif(is_string($categoryInfo) || is_int($categoryInfo))
|
||
|
$stmt->addParameter(++$args, (string)$categoryInfo);
|
||
|
else
|
||
|
throw new InvalidArgumentException('$exceptCategoryInfos may only contain string ids or instances of ForumCategoryInfo.');
|
||
|
}
|
||
|
foreach($exceptTopicInfos as $topicInfo) {
|
||
|
if($topicInfo instanceof ForumTopicInfo)
|
||
|
$stmt->addParameter(++$args, $topicInfo->getId());
|
||
|
elseif(is_string($topicInfo) || is_int($topicInfo))
|
||
|
$stmt->addParameter(++$args, (string)$topicInfo);
|
||
|
else
|
||
|
throw new InvalidArgumentException('$exceptTopicInfos may only contain string ids or instances of ForumTopicInfo.');
|
||
|
}
|
||
|
$stmt->execute();
|
||
|
|
||
|
$result = $stmt->getResult();
|
||
|
$info = new stdClass;
|
||
|
$info->success = $result->next();
|
||
|
if($info->success) {
|
||
|
$info->categoryId = $result->getString(0);
|
||
|
$info->postCount = $result->getInteger(1);
|
||
|
}
|
||
|
|
||
|
return $info;
|
||
|
}
|
||
|
|
||
|
public function syncForumCounters(
|
||
|
ForumCategoryInfo|string|null $categoryInfo = null,
|
||
|
bool $updateCounters = true
|
||
|
): object {
|
||
|
if($categoryInfo instanceof ForumCategoryInfo)
|
||
|
$categoryInfo = $categoryInfo->getId();
|
||
|
elseif($categoryInfo === null)
|
||
|
$categoryInfo = '0';
|
||
|
|
||
|
$counters = new stdClass;
|
||
|
|
||
|
$stmt = $this->cache->get('SELECT ? AS target_category_id, (SELECT COUNT(*) FROM msz_forum_topics WHERE forum_id = target_category_id AND topic_deleted IS NULL) AS count_topics, (SELECT COUNT(*) FROM msz_forum_posts WHERE forum_id = target_category_id AND post_deleted IS NULL) AS count_posts');
|
||
|
$stmt->addParameter(1, $categoryInfo);
|
||
|
$stmt->execute();
|
||
|
|
||
|
$result = $stmt->getResult();
|
||
|
if(!$result->next())
|
||
|
throw new RuntimeException('Failed to fetch forum category counters.');
|
||
|
|
||
|
$counters->topics = $result->getInteger(1);
|
||
|
$counters->posts = $result->getInteger(2);
|
||
|
|
||
|
$stmt = $this->cache->get('SELECT forum_id FROM msz_forum_categories WHERE forum_parent = ?');
|
||
|
$stmt->addParameter(1, $categoryInfo);
|
||
|
$stmt->execute();
|
||
|
|
||
|
$children = [];
|
||
|
$result = $stmt->getResult();
|
||
|
while($result->next())
|
||
|
$children[] = $result->getString(0);
|
||
|
|
||
|
foreach($children as $childId) {
|
||
|
$childCounters = $this->syncForumCounters($childId, $updateCounters);
|
||
|
$counters->topics += $childCounters->topics;
|
||
|
$counters->posts += $childCounters->posts;
|
||
|
}
|
||
|
|
||
|
if($updateCounters && $categoryInfo !== '0') {
|
||
|
$stmt = $this->cache->get('UPDATE msz_forum_categories SET forum_count_topics = ?, forum_count_posts = ? WHERE forum_id = ?');
|
||
|
$stmt->addParameter(1, $counters->topics);
|
||
|
$stmt->addParameter(2, $counters->posts);
|
||
|
$stmt->addParameter(3, $categoryInfo);
|
||
|
$stmt->execute();
|
||
|
}
|
||
|
|
||
|
return $counters;
|
||
|
}
|
||
|
|
||
|
public function countTopics(
|
||
|
ForumCategoryInfo|string|array|null $categoryInfo = null,
|
||
|
UserInfo|string|null $userInfo = null,
|
||
|
?bool $global = null,
|
||
|
?bool $deleted = null
|
||
|
): int {
|
||
|
if($categoryInfo instanceof ForumCategoryInfo)
|
||
|
$categoryInfo = $categoryInfo->getId();
|
||
|
if($userInfo instanceof UserInfo)
|
||
|
$userInfo = $userInfo->getId();
|
||
|
|
||
|
$hasCategoryInfo = $categoryInfo !== null;
|
||
|
$hasUserInfo = $userInfo !== null;
|
||
|
$hasGlobal = $global !== null;
|
||
|
$hasDeleted = $deleted !== null;
|
||
|
|
||
|
$args = 0;
|
||
|
$query = 'SELECT COUNT(*) FROM msz_forum_topics';
|
||
|
if($hasCategoryInfo || $hasGlobal) {
|
||
|
++$args;
|
||
|
|
||
|
// wow this sucks
|
||
|
$hasGlobalAndCategory = $hasCategoryInfo && $hasGlobal;
|
||
|
$query .= ' WHERE ';
|
||
|
if($hasGlobalAndCategory)
|
||
|
$query .= '(';
|
||
|
|
||
|
if($hasCategoryInfo) {
|
||
|
if(is_array($categoryInfo))
|
||
|
$query .= sprintf('forum_id IN (%s)', DbTools::prepareListString($categoryInfo));
|
||
|
else
|
||
|
$query .= 'forum_id = ?';
|
||
|
}
|
||
|
|
||
|
if($hasGlobalAndCategory)
|
||
|
$query .= ' OR ';
|
||
|
|
||
|
if($hasGlobal) // not sure why you would ever set this to false, but consistency!
|
||
|
$query .= sprintf('topic_type %s %d', $global ? '=' : '<>', ForumTopicInfo::TYPE_GLOBAL);
|
||
|
|
||
|
if($hasGlobalAndCategory)
|
||
|
$query .= ')';
|
||
|
}
|
||
|
if($hasUserInfo)
|
||
|
$query .= sprintf(' %s user_id = ?', ++$args > 1 ? 'AND' : 'WHERE');
|
||
|
if($hasDeleted)
|
||
|
$query .= sprintf(' %s topic_deleted %s NULL', ++$args > 1 ? 'AND' : 'WHERE', $deleted ? 'IS NOT' : 'IS');
|
||
|
|
||
|
$args = 0;
|
||
|
$stmt = $this->cache->get($query);
|
||
|
if($hasCategoryInfo) {
|
||
|
if(is_array($categoryInfo)) {
|
||
|
foreach($categoryInfo as $categoryInfoEntry)
|
||
|
$stmt->addParameter(++$args, $categoryInfoEntry instanceof ForumCategoryInfo ? $categoryInfoEntry->getId() : (string)$categoryInfoEntry);
|
||
|
} else
|
||
|
$stmt->addParameter(++$args, $categoryInfo);
|
||
|
}
|
||
|
if($hasUserInfo)
|
||
|
$stmt->addParameter(++$args, $userInfo);
|
||
|
$stmt->execute();
|
||
|
|
||
|
$result = $stmt->getResult();
|
||
|
return $result->next() ? $result->getInteger(0) : 0;
|
||
|
}
|
||
|
|
||
|
public function getTopics(
|
||
|
ForumCategoryInfo|string|array|null $categoryInfo = null,
|
||
|
UserInfo|string|null $userInfo = null,
|
||
|
?array $searchQuery = null,
|
||
|
?bool $global = null,
|
||
|
?bool $deleted = null,
|
||
|
?Pagination $pagination = null
|
||
|
): array {
|
||
|
// remove this hack when search server
|
||
|
$hasSearchQuery = $searchQuery !== null;
|
||
|
$hasAfterTopicId = false;
|
||
|
$doSearchOrder = false;
|
||
|
if($hasSearchQuery) {
|
||
|
if(!empty($searchQuery['type'])
|
||
|
&& $searchQuery['type'] !== 'forum'
|
||
|
&& $searchQuery['type'] !== 'forum:topic')
|
||
|
return [];
|
||
|
|
||
|
$deleted = false;
|
||
|
$pagination = null;
|
||
|
$doSearchOrder = true;
|
||
|
|
||
|
if(!empty($searchQuery['author']))
|
||
|
$userInfo = $searchQuery['author'];
|
||
|
|
||
|
if(!empty($searchQuery['after'])) {
|
||
|
$hasAfterTopicId = true;
|
||
|
$afterTopicId = $searchQuery['after'];
|
||
|
}
|
||
|
|
||
|
$searchQuery = $searchQuery['query_string'];
|
||
|
$hasSearchQuery = !empty($searchQuery);
|
||
|
}
|
||
|
|
||
|
if($categoryInfo instanceof ForumCategoryInfo)
|
||
|
$categoryInfo = $categoryInfo->getId();
|
||
|
if($userInfo instanceof UserInfo)
|
||
|
$userInfo = $userInfo->getId();
|
||
|
|
||
|
$hasCategoryInfo = $categoryInfo !== null;
|
||
|
$hasUserInfo = $userInfo !== null;
|
||
|
$hasGlobal = $global !== null;
|
||
|
$hasDeleted = $deleted !== null;
|
||
|
$hasPagination = $pagination !== null;
|
||
|
|
||
|
$args = 0;
|
||
|
$query = 'SELECT topic_id, forum_id, user_id, topic_type, topic_title, topic_count_views, UNIX_TIMESTAMP(topic_created), UNIX_TIMESTAMP(topic_bumped), UNIX_TIMESTAMP(topic_deleted), UNIX_TIMESTAMP(topic_locked), (SELECT COUNT(*) FROM msz_forum_posts WHERE topic_id = ft.topic_id AND post_deleted IS NULL) AS topic_count_posts, (SELECT COUNT(*) FROM msz_forum_posts WHERE topic_id = ft.topic_id AND post_deleted IS NOT NULL) AS topic_count_posts_deleted FROM msz_forum_topics AS ft';
|
||
|
if($hasCategoryInfo || $hasGlobal) {
|
||
|
++$args;
|
||
|
|
||
|
// wow this sucks
|
||
|
$hasGlobalAndCategory = $hasCategoryInfo && $hasGlobal;
|
||
|
$query .= ' WHERE ';
|
||
|
if($hasGlobalAndCategory)
|
||
|
$query .= '(';
|
||
|
|
||
|
if($hasCategoryInfo) {
|
||
|
if(is_array($categoryInfo))
|
||
|
$query .= sprintf('forum_id IN (%s)', DbTools::prepareListString($categoryInfo));
|
||
|
else
|
||
|
$query .= 'forum_id = ?';
|
||
|
}
|
||
|
|
||
|
if($hasGlobalAndCategory)
|
||
|
$query .= ' OR ';
|
||
|
|
||
|
if($hasGlobal) // not sure why you would ever set this to false, but consistency!
|
||
|
$query .= sprintf('topic_type %s %d', $global ? '=' : '<>', ForumTopicInfo::TYPE_GLOBAL);
|
||
|
|
||
|
if($hasGlobalAndCategory)
|
||
|
$query .= ')';
|
||
|
}
|
||
|
if($hasUserInfo)
|
||
|
$query .= sprintf(' %s user_id = ?', ++$args > 1 ? 'AND' : 'WHERE');
|
||
|
if($hasAfterTopicId)
|
||
|
$query .= sprintf(' %s topic_id > ?', ++$args > 1 ? 'AND' : 'WHERE');
|
||
|
if($hasSearchQuery)
|
||
|
$query .= sprintf(' %s MATCH(topic_title) AGAINST (? IN NATURAL LANGUAGE MODE)', ++$args > 1 ? 'AND' : 'WHERE');
|
||
|
if($hasDeleted)
|
||
|
$query .= sprintf(' %s topic_deleted %s NULL', ++$args > 1 ? 'AND' : 'WHERE', $deleted ? 'IS NOT' : 'IS');
|
||
|
if($doSearchOrder) {
|
||
|
$query .= ' ORDER BY topic_id ASC LIMIT 20';
|
||
|
} else {
|
||
|
$query .= ' ORDER BY topic_type DESC, topic_bumped DESC';
|
||
|
if($hasPagination)
|
||
|
$query .= ' LIMIT ? OFFSET ?';
|
||
|
}
|
||
|
|
||
|
$args = 0;
|
||
|
$stmt = $this->cache->get($query);
|
||
|
if($hasCategoryInfo) {
|
||
|
if(is_array($categoryInfo)) {
|
||
|
foreach($categoryInfo as $categoryInfoEntry)
|
||
|
$stmt->addParameter(++$args, $categoryInfoEntry instanceof ForumCategoryInfo ? $categoryInfoEntry->getId() : (string)$categoryInfoEntry);
|
||
|
} else
|
||
|
$stmt->addParameter(++$args, $categoryInfo);
|
||
|
}
|
||
|
if($hasUserInfo)
|
||
|
$stmt->addParameter(++$args, $userInfo);
|
||
|
if($hasAfterTopicId)
|
||
|
$stmt->addParameter(++$args, $afterTopicId);
|
||
|
if($hasSearchQuery)
|
||
|
$stmt->addParameter(++$args, $searchQuery);
|
||
|
if($hasPagination) {
|
||
|
$stmt->addParameter(++$args, $pagination->getRange());
|
||
|
$stmt->addParameter(++$args, $pagination->getOffset());
|
||
|
}
|
||
|
$stmt->execute();
|
||
|
|
||
|
$result = $stmt->getResult();
|
||
|
$topics = [];
|
||
|
|
||
|
while($result->next())
|
||
|
$topics[] = new ForumTopicInfo($result);
|
||
|
|
||
|
return $topics;
|
||
|
}
|
||
|
|
||
|
public function getTopic(
|
||
|
?string $topicId = null,
|
||
|
ForumPostInfo|string|null $postInfo = null
|
||
|
): ForumTopicInfo {
|
||
|
$hasTopicId = $topicId !== null;
|
||
|
$hasPostInfo = $postInfo !== null;
|
||
|
|
||
|
if(!$hasTopicId && !$hasPostInfo)
|
||
|
throw new InvalidArgumentException('At least one argument must be specified.');
|
||
|
if($hasTopicId && $hasPostInfo)
|
||
|
throw new InvalidArgumentException('Only one argument may be specified.');
|
||
|
|
||
|
$value = null;
|
||
|
$query = 'SELECT topic_id, forum_id, user_id, topic_type, topic_title, topic_count_views, UNIX_TIMESTAMP(topic_created), UNIX_TIMESTAMP(topic_bumped), UNIX_TIMESTAMP(topic_deleted), UNIX_TIMESTAMP(topic_locked), (SELECT COUNT(*) FROM msz_forum_posts WHERE topic_id = ft.topic_id AND post_deleted IS NULL) AS topic_count_posts, (SELECT COUNT(*) FROM msz_forum_posts WHERE topic_id = ft.topic_id AND post_deleted IS NOT NULL) AS topic_count_posts_deleted FROM msz_forum_topics AS ft';
|
||
|
if($hasTopicId) {
|
||
|
$query .= ' WHERE topic_id = ?';
|
||
|
$value = $topicId;
|
||
|
}
|
||
|
if($hasPostInfo) {
|
||
|
if($postInfo instanceof ForumPostInfo) {
|
||
|
$query .= ' WHERE topic_id = ?';
|
||
|
$value = $postInfo->getTopicId();
|
||
|
} else {
|
||
|
$query .= ' WHERE topic_id = (SELECT topic_id FROM msz_forum_posts WHERE post_id = ?)';
|
||
|
$value = $postInfo;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$stmt = $this->cache->get($query);
|
||
|
$stmt->addParameter(1, $value);
|
||
|
$stmt->execute();
|
||
|
|
||
|
$result = $stmt->getResult();
|
||
|
if(!$result->next())
|
||
|
throw new RuntimeException('Forum topic not found.');
|
||
|
|
||
|
return new ForumTopicInfo($result);
|
||
|
}
|
||
|
|
||
|
public function createTopic(
|
||
|
ForumCategoryInfo|string $categoryInfo,
|
||
|
UserInfo|string|null $userInfo,
|
||
|
string $title,
|
||
|
string|int $type = ForumTopicInfo::TYPE_DISCUSSION
|
||
|
): ForumTopicInfo {
|
||
|
if(is_string($type)) {
|
||
|
if(!array_key_exists($type, ForumTopicInfo::TYPE_ALIASES))
|
||
|
throw new InvalidArgumentException('$type is not a valid alias.');
|
||
|
$type = ForumTopicInfo::TYPE_ALIASES[$type];
|
||
|
}
|
||
|
if($categoryInfo instanceof ForumCategoryInfo)
|
||
|
$categoryInfo = $categoryInfo->getId();
|
||
|
if($userInfo instanceof UserInfo)
|
||
|
$userInfo = $userInfo->getId();
|
||
|
|
||
|
$stmt = $this->cache->get('INSERT INTO msz_forum_topics (forum_id, user_id, topic_type, topic_title) VALUES (?, ?, ?, ?)');
|
||
|
$stmt->addParameter(1, $categoryInfo);
|
||
|
$stmt->addParameter(2, $userInfo);
|
||
|
$stmt->addParameter(3, $type);
|
||
|
$stmt->addParameter(4, $title);
|
||
|
$stmt->execute();
|
||
|
|
||
|
return $this->getTopic(topicId: (string)$this->dbConn->getLastInsertId());
|
||
|
}
|
||
|
|
||
|
public function updateTopic(
|
||
|
ForumTopicInfo|string $topicInfo,
|
||
|
?string $title = null,
|
||
|
string|int|null $type = null
|
||
|
): void {
|
||
|
if($topicInfo instanceof ForumTopicInfo)
|
||
|
$topicInfo = $topicInfo->getId();
|
||
|
|
||
|
$fields = [];
|
||
|
$values = [];
|
||
|
|
||
|
if($title !== null) {
|
||
|
$fields[] = 'topic_title = ?';
|
||
|
$values[] = $title;
|
||
|
}
|
||
|
|
||
|
if($type !== null) {
|
||
|
if(is_string($type)) {
|
||
|
if(!array_key_exists($type, ForumTopicInfo::TYPE_ALIASES))
|
||
|
throw new InvalidArgumentException('$type is not a valid type alias.');
|
||
|
|
||
|
$type = ForumTopicInfo::TYPE_ALIASES[$type];
|
||
|
}
|
||
|
|
||
|
$fields[] = 'topic_type = ?';
|
||
|
$values[] = $type;
|
||
|
}
|
||
|
|
||
|
if(empty($fields))
|
||
|
return;
|
||
|
|
||
|
$args = 0;
|
||
|
$stmt = $this->cache->get(sprintf('UPDATE msz_forum_topics SET %s WHERE topic_id = ?', implode(', ', $fields)));
|
||
|
foreach($values as $value)
|
||
|
$stmt->addParameter(++$args, $value);
|
||
|
$stmt->addParameter(++$args, $topicInfo);
|
||
|
$stmt->execute();
|
||
|
}
|
||
|
|
||
|
public function incrementTopicView(ForumTopicInfo|string $topicInfo): void {
|
||
|
if($topicInfo instanceof ForumTopicInfo)
|
||
|
$topicInfo = $topicInfo->getId();
|
||
|
|
||
|
$stmt = $this->cache->get('UPDATE msz_forum_topics SET topic_count_views = topic_count_views + 1 WHERE topic_id = ?');
|
||
|
$stmt->addParameter(1, $topicInfo);
|
||
|
$stmt->execute();
|
||
|
}
|
||
|
|
||
|
public function bumpTopic(ForumTopicInfo|string $topicInfo): void {
|
||
|
if($topicInfo instanceof ForumTopicInfo)
|
||
|
$topicInfo = $topicInfo->getId();
|
||
|
|
||
|
$stmt = $this->cache->get('UPDATE msz_forum_topics SET topic_bumped = NOW() WHERE topic_id = ?');
|
||
|
$stmt->addParameter(1, $topicInfo);
|
||
|
$stmt->execute();
|
||
|
}
|
||
|
|
||
|
public function lockTopic(ForumTopicInfo|string $topicInfo): void {
|
||
|
if($topicInfo instanceof ForumTopicInfo)
|
||
|
$topicInfo = $topicInfo->getId();
|
||
|
|
||
|
$stmt = $this->cache->get('UPDATE msz_forum_topics SET topic_locked = NOW() WHERE topic_id = ?');
|
||
|
$stmt->addParameter(1, $topicInfo);
|
||
|
$stmt->execute();
|
||
|
}
|
||
|
|
||
|
public function unlockTopic(ForumTopicInfo|string $topicInfo): void {
|
||
|
if($topicInfo instanceof ForumTopicInfo)
|
||
|
$topicInfo = $topicInfo->getId();
|
||
|
|
||
|
$stmt = $this->cache->get('UPDATE msz_forum_topics SET topic_locked = NULL WHERE topic_id = ?');
|
||
|
$stmt->addParameter(1, $topicInfo);
|
||
|
$stmt->execute();
|
||
|
}
|
||
|
|
||
|
public function deleteTopic(ForumTopicInfo|string $topicInfo): void {
|
||
|
if($topicInfo instanceof ForumTopicInfo)
|
||
|
$topicInfo = $topicInfo->getId();
|
||
|
|
||
|
$stmt = $this->cache->get('UPDATE msz_forum_topics SET topic_deleted = COALESCE(topic_deleted, NOW()) WHERE topic_id = ? AND topic_deleted IS NULL');
|
||
|
$stmt->addParameter(1, $topicInfo);
|
||
|
$stmt->execute();
|
||
|
|
||
|
$stmt = $this->cache->get('UPDATE msz_forum_posts AS fp SET post_deleted = (SELECT topic_deleted FROM msz_forum_topics WHERE topic_id = fp.topic_id) WHERE topic_id = ? AND post_deleted = NULL');
|
||
|
$stmt->addParameter(1, $topicInfo);
|
||
|
$stmt->execute();
|
||
|
}
|
||
|
|
||
|
public function restoreTopic(ForumTopicInfo|string $topicInfo): void {
|
||
|
if($topicInfo instanceof ForumTopicInfo)
|
||
|
$topicInfo = $topicInfo->getId();
|
||
|
|
||
|
$stmt = $this->cache->get('UPDATE msz_forum_posts AS fp SET post_deleted = NULL WHERE topic_id = ? AND post_deleted = (SELECT topic_deleted FROM msz_forum_topics WHERE topic_id = fp.topic_id)');
|
||
|
$stmt->addParameter(1, $topicInfo);
|
||
|
$stmt->execute();
|
||
|
|
||
|
$stmt = $this->cache->get('UPDATE msz_forum_topics SET topic_deleted = NULL WHERE topic_id = ?');
|
||
|
$stmt->addParameter(1, $topicInfo);
|
||
|
$stmt->execute();
|
||
|
}
|
||
|
|
||
|
public function nukeTopic(ForumTopicInfo|string $topicInfo): void {
|
||
|
if($topicInfo instanceof ForumTopicInfo)
|
||
|
$topicInfo = $topicInfo->getId();
|
||
|
|
||
|
$stmt = $this->cache->get('DELETE FROM msz_forum_topics WHERE topic_id = ?');
|
||
|
$stmt->addParameter(1, $topicInfo);
|
||
|
$stmt->execute();
|
||
|
}
|
||
|
|
||
|
public function checkTopicParticipated(
|
||
|
ForumTopicInfo|string $topicInfo,
|
||
|
UserInfo|string|null $userInfo
|
||
|
): bool {
|
||
|
if($userInfo === null)
|
||
|
return false;
|
||
|
if($topicInfo instanceof ForumTopicInfo)
|
||
|
$topicInfo = $topicInfo->getId();
|
||
|
if($userInfo instanceof UserInfo)
|
||
|
$userInfo = $userInfo->getId();
|
||
|
|
||
|
$stmt = $this->cache->get('SELECT COUNT(*) FROM msz_forum_posts WHERE topic_id = ? AND user_id = ?');
|
||
|
$stmt->addParameter(1, $topicInfo);
|
||
|
$stmt->addParameter(2, $userInfo);
|
||
|
$stmt->execute();
|
||
|
$result = $stmt->getResult();
|
||
|
|
||
|
return $result->next() && $result->getInteger(0) > 0;
|
||
|
}
|
||
|
|
||
|
public function checkTopicUnread(
|
||
|
ForumTopicInfo|string $topicInfo,
|
||
|
UserInfo|string|null $userInfo
|
||
|
): bool {
|
||
|
if($userInfo === null)
|
||
|
return false;
|
||
|
|
||
|
$topicInfoIsInstance = $topicInfo instanceof ForumTopicInfo;
|
||
|
if($topicInfoIsInstance && !$topicInfo->isActive())
|
||
|
return false;
|
||
|
|
||
|
$query = 'SELECT UNIX_TIMESTAMP(track_last_read) FROM msz_forum_topics_track AS ftt WHERE user_id = ? AND topic_id = ?';
|
||
|
if(!$topicInfoIsInstance)
|
||
|
$query .= ' AND track_last_read = (SELECT topic_bumped FROM msz_forum_topics WHERE topic_id = ftt.topic_id AND topic_bumped >= NOW() - INTERVAL 1 MONTH)';
|
||
|
|
||
|
$stmt = $this->cache->get($query);
|
||
|
$stmt->addParameter(1, $userInfo instanceof UserInfo ? $userInfo->getId() : $userInfo);
|
||
|
$stmt->addParameter(2, $topicInfoIsInstance ? $topicInfo->getId() : $topicInfo);
|
||
|
$stmt->execute();
|
||
|
$result = $stmt->getResult();
|
||
|
|
||
|
// user has never read this topic, return unread
|
||
|
if(!$result->next())
|
||
|
return true;
|
||
|
|
||
|
return $result->getInteger(0) < $topicInfo->getBumpedTime();
|
||
|
}
|
||
|
|
||
|
public function getMostActiveTopicInfo(
|
||
|
UserInfo|string $userInfo,
|
||
|
array $exceptCategoryInfos = [],
|
||
|
array $exceptTopicInfos = [],
|
||
|
?bool $deleted = null
|
||
|
): object {
|
||
|
if($userInfo instanceof UserInfo)
|
||
|
$userInfo = $userInfo->getId();
|
||
|
|
||
|
$hasExceptCategoryInfos = !empty($exceptCategoryInfos);
|
||
|
$hasExceptTopicInfos = !empty($exceptTopicInfos);
|
||
|
$hasDeleted = $deleted !== null;
|
||
|
|
||
|
$query = 'SELECT topic_id, forum_id, COUNT(*) AS post_count FROM msz_forum_posts WHERE user_id = ?';
|
||
|
if($hasDeleted)
|
||
|
$query .= sprintf(' AND post_deleted %s NULL', $deleted ? 'IS NOT' : 'IS');
|
||
|
if($hasExceptCategoryInfos)
|
||
|
$query .= sprintf(' AND forum_id NOT IN (%s)', DbTools::prepareListString($exceptCategoryInfos));
|
||
|
if($hasExceptTopicInfos)
|
||
|
$query .= sprintf(' AND topic_id NOT IN (%s)', DbTools::prepareListString($exceptTopicInfos));
|
||
|
$query .= ' GROUP BY topic_id ORDER BY post_count DESC LIMIT 1';
|
||
|
|
||
|
$args = 0;
|
||
|
$stmt = $this->cache->get($query);
|
||
|
$stmt->addParameter(++$args, $userInfo);
|
||
|
foreach($exceptCategoryInfos as $categoryInfo) {
|
||
|
if($categoryInfo instanceof ForumCategoryInfo)
|
||
|
$stmt->addParameter(++$args, $categoryInfo->getId());
|
||
|
elseif(is_string($categoryInfo) || is_int($categoryInfo))
|
||
|
$stmt->addParameter(++$args, (string)$categoryInfo);
|
||
|
else
|
||
|
throw new InvalidArgumentException('$exceptCategoryInfos may only contain string ids or instances of ForumCategoryInfo.');
|
||
|
}
|
||
|
foreach($exceptTopicInfos as $topicInfo) {
|
||
|
if($topicInfo instanceof ForumTopicInfo)
|
||
|
$stmt->addParameter(++$args, $topicInfo->getId());
|
||
|
elseif(is_string($topicInfo) || is_int($topicInfo))
|
||
|
$stmt->addParameter(++$args, (string)$topicInfo);
|
||
|
else
|
||
|
throw new InvalidArgumentException('$exceptTopicInfos may only contain string ids or instances of ForumTopicInfo.');
|
||
|
}
|
||
|
$stmt->execute();
|
||
|
|
||
|
$result = $stmt->getResult();
|
||
|
$info = new stdClass;
|
||
|
$info->success = $result->next();
|
||
|
if($info->success) {
|
||
|
$info->topicId = $result->getString(0);
|
||
|
$info->categoryId = $result->getString(1);
|
||
|
$info->postCount = $result->getInteger(2);
|
||
|
}
|
||
|
|
||
|
return $info;
|
||
|
}
|
||
|
|
||
|
public function checkUserHasReadTopic(
|
||
|
UserInfo|string|null $userInfo,
|
||
|
ForumTopicInfo|string $topicInfo
|
||
|
): bool {
|
||
|
// this method is primarily used to check if we should increment the view count
|
||
|
// guests shouldn't increment it so we just
|
||
|
if($userInfo === null)
|
||
|
return true;
|
||
|
|
||
|
$stmt = $this->cache->get('SELECT COUNT(*) FROM msz_forum_topics_track WHERE topic_id = ? AND user_id = ?');
|
||
|
$stmt->addParameter(1, $topicInfo instanceof ForumTopicInfo ? $topicInfo->getId() : $topicInfo);
|
||
|
$stmt->addParameter(2, $userInfo instanceof UserInfo ? $userInfo->getId() : $userInfo);
|
||
|
$stmt->execute();
|
||
|
$result = $stmt->getResult();
|
||
|
|
||
|
return $result->next() && $result->getInteger(0) > 0;
|
||
|
}
|
||
|
|
||
|
public function updateUserReadTopic(
|
||
|
UserInfo|string|null $userInfo,
|
||
|
ForumTopicInfo|string $topicInfo,
|
||
|
ForumCategoryInfo|string|null $categoryInfo = null
|
||
|
): void {
|
||
|
if($userInfo === null)
|
||
|
return;
|
||
|
|
||
|
if($userInfo instanceof UserInfo)
|
||
|
$userInfo = $userInfo->getId();
|
||
|
|
||
|
if($topicInfo instanceof ForumTopicInfo) {
|
||
|
$categoryInfo = $topicInfo->getCategoryId();
|
||
|
$topicInfo = $topicInfo->getId();
|
||
|
} else {
|
||
|
if($categoryInfo === null)
|
||
|
throw new InvalidArgumentException('$categoryInfo must be specified if $topicInfo is not an instance of ForumTopicInfo.');
|
||
|
if($categoryInfo instanceof ForumCategoryInfo)
|
||
|
$categoryInfo = $categoryInfo->getId();
|
||
|
}
|
||
|
|
||
|
$stmt = $this->cache->get('REPLACE INTO msz_forum_topics_track (user_id, topic_id, forum_id, track_last_read) VALUES (?, ?, ?, NOW())');
|
||
|
$stmt->addParameter(1, $userInfo);
|
||
|
$stmt->addParameter(2, $topicInfo);
|
||
|
$stmt->addParameter(3, $categoryInfo);
|
||
|
$stmt->execute();
|
||
|
}
|
||
|
|
||
|
public function countTopicRedirects(
|
||
|
UserInfo|string|null $userInfo = null
|
||
|
): int {
|
||
|
if($userInfo instanceof UserInfo)
|
||
|
$userInfo = $userInfo->getId();
|
||
|
|
||
|
$hasUserInfo = $userInfo !== null;
|
||
|
|
||
|
$query = 'SELECT COUNT(*) FROM msz_forum_topics_redirects';
|
||
|
if($hasUserInfo)
|
||
|
$query .= ' WHERE user_id = ?';
|
||
|
|
||
|
$stmt = $this->cache->get($query);
|
||
|
if($hasUserInfo)
|
||
|
$stmt->addParameter(1, $userInfo);
|
||
|
$stmt->execute();
|
||
|
|
||
|
$result = $stmt->getResult();
|
||
|
return $result->next() ? $result->getInteger(0) : 0;
|
||
|
}
|
||
|
|
||
|
public function getTopicRedirects(
|
||
|
UserInfo|string|null $userInfo = null,
|
||
|
?Pagination $pagination = null
|
||
|
): array {
|
||
|
if($userInfo instanceof UserInfo)
|
||
|
$userInfo = $userInfo->getId();
|
||
|
|
||
|
$hasUserInfo = $userInfo !== null;
|
||
|
$hasPagination = $pagination !== null;
|
||
|
|
||
|
$query = 'SELECT topic_id, user_id, topic_redir_url, UNIX_TIMESTAMP(topic_redir_created) FROM msz_forum_topics_redirects';
|
||
|
if($hasUserInfo)
|
||
|
$query .= ' WHERE user_id = ?';
|
||
|
if($hasPagination)
|
||
|
$query .= ' LIMIT ? OFFSET ?';
|
||
|
|
||
|
$args = 0;
|
||
|
$stmt = $this->cache->get($query);
|
||
|
if($hasUserInfo)
|
||
|
$stmt->addParameter(++$args, $userInfo);
|
||
|
if($hasPagination) {
|
||
|
$stmt->addParameter(++$args, $pagination->getRange());
|
||
|
$stmt->addParameter(++$args, $pagination->getOffset());
|
||
|
}
|
||
|
$stmt->execute();
|
||
|
|
||
|
$result = $stmt->getResult();
|
||
|
$redirs = [];
|
||
|
|
||
|
while($result->next())
|
||
|
$redirs[] = new ForumTopicRedirectInfo($result);
|
||
|
|
||
|
return $redirs;
|
||
|
}
|
||
|
|
||
|
public function hasTopicRedirect(ForumTopicInfo|string $topicInfo): bool {
|
||
|
if($topicInfo instanceof ForumTopicInfo)
|
||
|
$topicInfo = $topicInfo->getId();
|
||
|
|
||
|
$stmt = $this->cache->get('SELECT COUNT(*) FROM msz_forum_topics_redirects WHERE topic_id = ?');
|
||
|
$stmt->addParameter(1, $topicInfo);
|
||
|
$stmt->execute();
|
||
|
|
||
|
$result = $stmt->getResult();
|
||
|
if(!$result->next())
|
||
|
throw new RuntimeException('Was unable to check if a redirect exists.');
|
||
|
|
||
|
return $result->getInteger(0) > 0;
|
||
|
}
|
||
|
|
||
|
public function getTopicRedirect(ForumTopicInfo|string $topicInfo): ForumTopicRedirectInfo {
|
||
|
if($topicInfo instanceof ForumTopicInfo)
|
||
|
$topicInfo = $topicInfo->getId();
|
||
|
|
||
|
$stmt = $this->cache->get('SELECT topic_id, user_id, topic_redir_url, UNIX_TIMESTAMP(topic_redir_created) FROM msz_forum_topics_redirects WHERE topic_id = ?');
|
||
|
$stmt->addParameter(1, $topicInfo);
|
||
|
$stmt->execute();
|
||
|
|
||
|
$result = $stmt->getResult();
|
||
|
if(!$result->next())
|
||
|
throw new RuntimeException('Could not find that forum topic redirect.');
|
||
|
|
||
|
return new ForumTopicRedirectInfo($result);
|
||
|
}
|
||
|
|
||
|
public function createTopicRedirect(
|
||
|
ForumTopicInfo|string $topicInfo,
|
||
|
UserInfo|string|null $userInfo,
|
||
|
string $linkTarget
|
||
|
): ForumTopicRedirectInfo {
|
||
|
if($topicInfo instanceof ForumTopicInfo)
|
||
|
$topicInfo = $topicInfo->getId();
|
||
|
if($userInfo instanceof UserInfo)
|
||
|
$userInfo = $userInfo->getId();
|
||
|
|
||
|
$stmt = $this->cache->get('INSERT INTO msz_forum_topics_redirects (topic_id, user_id, topic_redir_url) VALUES (?, ?, ?)');
|
||
|
$stmt->addParameter(1, $topicInfo);
|
||
|
$stmt->addParameter(2, $userInfo);
|
||
|
$stmt->addParameter(3, $linkTarget);
|
||
|
$stmt->execute();
|
||
|
|
||
|
return $this->getTopicRedirect($topicInfo);
|
||
|
}
|
||
|
|
||
|
public function deleteTopicRedirect(ForumTopicRedirectInfo|ForumTopicInfo|string $topicInfo): void {
|
||
|
if($topicInfo instanceof ForumTopicRedirectInfo)
|
||
|
$topicInfo = $topicInfo->getTopicId();
|
||
|
elseif($topicInfo instanceof ForumTopicInfo)
|
||
|
$topicInfo = $topicInfo->getId();
|
||
|
|
||
|
$stmt = $this->cache->get('DELETE FROM msz_forum_topics_redirects WHERE topic_id = ?');
|
||
|
$stmt->addParameter(1, $topicInfo);
|
||
|
$stmt->execute();
|
||
|
}
|
||
|
|
||
|
public function countPosts(
|
||
|
ForumCategoryInfo|string|null $categoryInfo = null,
|
||
|
ForumTopicInfo|string|null $topicInfo = null,
|
||
|
UserInfo|string|null $userInfo = null,
|
||
|
ForumPostInfo|string|null $upToPostInfo = null,
|
||
|
?bool $deleted = null
|
||
|
): int {
|
||
|
if($categoryInfo instanceof ForumCategoryInfo)
|
||
|
$categoryInfo = $categoryInfo->getId();
|
||
|
if($topicInfo instanceof ForumTopicInfo)
|
||
|
$topicInfo = $topicInfo->getId();
|
||
|
if($userInfo instanceof UserInfo)
|
||
|
$userInfo = $userInfo->getId();
|
||
|
if($upToPostInfo instanceof ForumPostInfo)
|
||
|
$upToPostInfo = $upToPostInfo->getId();
|
||
|
|
||
|
$hasCategoryInfo = $categoryInfo !== null;
|
||
|
$hasTopicInfo = $topicInfo !== null;
|
||
|
$hasUserInfo = $userInfo !== null;
|
||
|
$hasUpToPostInfo = $upToPostInfo !== null;
|
||
|
$hasDeleted = $deleted !== null;
|
||
|
|
||
|
$args = 0;
|
||
|
$query = 'SELECT COUNT(*) FROM msz_forum_posts';
|
||
|
if($hasCategoryInfo) {
|
||
|
++$args;
|
||
|
$query .= ' WHERE forum_id = ?';
|
||
|
}
|
||
|
if($hasTopicInfo)
|
||
|
$query .= sprintf(' %s topic_id = ?', ++$args > 1 ? 'AND' : 'WHERE');
|
||
|
if($hasUserInfo)
|
||
|
$query .= sprintf(' %s user_id = ?', ++$args > 1 ? 'AND' : 'WHERE');
|
||
|
if($hasUpToPostInfo)
|
||
|
$query .= sprintf(' %s post_id < ?', ++$args > 1 ? 'AND' : 'WHERE');
|
||
|
if($hasDeleted)
|
||
|
$query .= sprintf(' %s post_deleted %s NULL', ++$args > 1 ? 'AND' : 'WHERE', $deleted ? 'IS NOT' : 'IS');
|
||
|
|
||
|
$args = 0;
|
||
|
$stmt = $this->cache->get($query);
|
||
|
if($hasCategoryInfo)
|
||
|
$stmt->addParameter(++$args, $categoryInfo);
|
||
|
if($hasTopicInfo)
|
||
|
$stmt->addParameter(++$args, $topicInfo);
|
||
|
if($hasUserInfo)
|
||
|
$stmt->addParameter(++$args, $userInfo);
|
||
|
if($hasUpToPostInfo)
|
||
|
$stmt->addParameter(++$args, $upToPostInfo);
|
||
|
$stmt->execute();
|
||
|
|
||
|
$result = $stmt->getResult();
|
||
|
return $result->next() ? $result->getInteger(0) : 0;
|
||
|
}
|
||
|
|
||
|
public function getPosts(
|
||
|
ForumCategoryInfo|string|array|null $categoryInfo = null,
|
||
|
ForumTopicInfo|string|null $topicInfo = null,
|
||
|
UserInfo|string|null $userInfo = null,
|
||
|
ForumPostInfo|string|null $upToPostInfo = null,
|
||
|
?array $searchQuery = null,
|
||
|
?bool $deleted = null,
|
||
|
?Pagination $pagination = null
|
||
|
): array {
|
||
|
// remove this hack when search server
|
||
|
$hasSearchQuery = $searchQuery !== null;
|
||
|
$hasAfterPostId = false;
|
||
|
$doSearchOrder = false;
|
||
|
if($hasSearchQuery) {
|
||
|
if(!empty($searchQuery['type'])
|
||
|
&& $searchQuery['type'] !== 'forum'
|
||
|
&& $searchQuery['type'] !== 'forum:post')
|
||
|
return [];
|
||
|
|
||
|
$deleted = false;
|
||
|
$pagination = null;
|
||
|
$doSearchOrder = true;
|
||
|
|
||
|
if(!empty($searchQuery['author']))
|
||
|
$userInfo = $searchQuery['author'];
|
||
|
|
||
|
if(!empty($searchQuery['after'])) {
|
||
|
$hasAfterPostId = true;
|
||
|
$afterPostId = $searchQuery['after'];
|
||
|
}
|
||
|
|
||
|
$searchQuery = $searchQuery['query_string'];
|
||
|
$hasSearchQuery = !empty($searchQuery);
|
||
|
}
|
||
|
|
||
|
if($categoryInfo instanceof ForumCategoryInfo)
|
||
|
$categoryInfo = $categoryInfo->getId();
|
||
|
if($topicInfo instanceof ForumTopicInfo)
|
||
|
$topicInfo = $topicInfo->getId();
|
||
|
if($userInfo instanceof UserInfo)
|
||
|
$userInfo = $userInfo->getId();
|
||
|
if($upToPostInfo instanceof ForumPostInfo)
|
||
|
$upToPostInfo = $upToPostInfo->getId();
|
||
|
|
||
|
$hasCategoryInfo = $categoryInfo !== null;
|
||
|
$hasTopicInfo = $topicInfo !== null;
|
||
|
$hasUserInfo = $userInfo !== null;
|
||
|
$hasUpToPostInfo = $upToPostInfo !== null;
|
||
|
$hasDeleted = $deleted !== null;
|
||
|
$hasPagination = $pagination !== null;
|
||
|
|
||
|
$args = 0;
|
||
|
$query = 'SELECT post_id, topic_id, forum_id, user_id, INET6_NTOA(post_ip), post_text, post_parse, post_display_signature, UNIX_TIMESTAMP(post_created), UNIX_TIMESTAMP(post_edited), UNIX_TIMESTAMP(post_deleted) FROM msz_forum_posts';
|
||
|
if($hasCategoryInfo) {
|
||
|
++$args;
|
||
|
if(is_array($categoryInfo))
|
||
|
$query .= sprintf(' WHERE forum_id IN (%s)', DbTools::prepareListString($categoryInfo));
|
||
|
else
|
||
|
$query .= ' WHERE forum_id = ?';
|
||
|
}
|
||
|
if($hasTopicInfo)
|
||
|
$query .= sprintf(' %s topic_id = ?', ++$args > 1 ? 'AND' : 'WHERE');
|
||
|
if($hasUserInfo)
|
||
|
$query .= sprintf(' %s user_id = ?', ++$args > 1 ? 'AND' : 'WHERE');
|
||
|
if($hasUpToPostInfo)
|
||
|
$query .= sprintf(' %s post_id < ?', ++$args > 1 ? 'AND' : 'WHERE');
|
||
|
if($hasAfterPostId)
|
||
|
$query .= sprintf(' %s post_id > ?', ++$args > 1 ? 'AND' : 'WHERE');
|
||
|
if($hasSearchQuery)
|
||
|
$query .= sprintf(' %s MATCH(post_text) AGAINST (? IN NATURAL LANGUAGE MODE)', ++$args > 1 ? 'AND' : 'WHERE');
|
||
|
if($hasDeleted)
|
||
|
$query .= sprintf(' %s post_deleted %s NULL', ++$args > 1 ? 'AND' : 'WHERE', $deleted ? 'IS NOT' : 'IS');
|
||
|
if($doSearchOrder) {
|
||
|
$query .= ' ORDER BY post_id ASC LIMIT 20';
|
||
|
} else {
|
||
|
$query .= ' ORDER BY post_id ASC';
|
||
|
if($hasPagination)
|
||
|
$query .= ' LIMIT ? OFFSET ?';
|
||
|
}
|
||
|
|
||
|
$args = 0;
|
||
|
$stmt = $this->cache->get($query);
|
||
|
if($hasCategoryInfo) {
|
||
|
if(is_array($categoryInfo)) {
|
||
|
foreach($categoryInfo as $categoryInfoEntry)
|
||
|
$stmt->addParameter(++$args, $categoryInfoEntry instanceof ForumCategoryInfo ? $categoryInfoEntry->getId() : (string)$categoryInfoEntry);
|
||
|
} else
|
||
|
$stmt->addParameter(++$args, $categoryInfo);
|
||
|
}
|
||
|
if($hasTopicInfo)
|
||
|
$stmt->addParameter(++$args, $topicInfo);
|
||
|
if($hasUserInfo)
|
||
|
$stmt->addParameter(++$args, $userInfo);
|
||
|
if($hasUpToPostInfo)
|
||
|
$stmt->addParameter(++$args, $upToPostInfo);
|
||
|
if($hasAfterPostId)
|
||
|
$stmt->addParameter(++$args, $afterPostId);
|
||
|
if($hasSearchQuery)
|
||
|
$stmt->addParameter(++$args, $searchQuery);
|
||
|
if($hasPagination) {
|
||
|
$stmt->addParameter(++$args, $pagination->getRange());
|
||
|
$stmt->addParameter(++$args, $pagination->getOffset());
|
||
|
}
|
||
|
$stmt->execute();
|
||
|
|
||
|
$result = $stmt->getResult();
|
||
|
$posts = [];
|
||
|
|
||
|
while($result->next())
|
||
|
$posts[] = new ForumPostInfo($result);
|
||
|
|
||
|
return $posts;
|
||
|
}
|
||
|
|
||
|
public function getPost(
|
||
|
?string $postId = null,
|
||
|
ForumTopicInfo|string|null $topicInfo = null,
|
||
|
ForumCategoryInfo|string|array|null $categoryInfos = null,
|
||
|
UserInfo|string|null $userInfo = null,
|
||
|
bool $getLast = false,
|
||
|
?bool $deleted = null
|
||
|
): ForumPostInfo {
|
||
|
$hasPostId = $postId !== null;
|
||
|
$hasTopicInfo = $topicInfo !== null;
|
||
|
$hasCategoryInfos = $categoryInfos !== null;
|
||
|
$hasUserInfo = $userInfo !== null;
|
||
|
$hasDeleted = $deleted !== null;
|
||
|
|
||
|
if(!$hasPostId && !$hasTopicInfo && !$hasCategoryInfos && !$hasUserInfo)
|
||
|
throw new InvalidArgumentException('At least one of the four first arguments must be specified.');
|
||
|
|
||
|
$values = [];
|
||
|
$query = 'SELECT post_id, topic_id, forum_id, user_id, INET6_NTOA(post_ip), post_text, post_parse, post_display_signature, UNIX_TIMESTAMP(post_created), UNIX_TIMESTAMP(post_edited), UNIX_TIMESTAMP(post_deleted) FROM msz_forum_posts';
|
||
|
if($hasPostId) {
|
||
|
$query .= ' WHERE post_id = ?';
|
||
|
$values[] = $postId;
|
||
|
} elseif($hasUserInfo) {
|
||
|
$query .= ' WHERE user_id = ?';
|
||
|
$values[] = $userInfo instanceof UserInfo ? $userInfo->getId() : $userInfo;
|
||
|
$query .= sprintf(' ORDER BY post_id %s', $getLast ? 'DESC' : 'ASC');
|
||
|
} elseif($hasTopicInfo) {
|
||
|
if($topicInfo instanceof ForumTopicInfo)
|
||
|
$topicInfo = $topicInfo->getId();
|
||
|
|
||
|
$query .= sprintf(' WHERE post_id = (SELECT %s(post_id) FROM msz_forum_posts WHERE topic_id = ?', $getLast ? 'MAX' : 'MIN');
|
||
|
if($hasDeleted)
|
||
|
$query .= sprintf(' AND post_deleted %s NULL', $deleted ? 'IS NOT' : 'IS');
|
||
|
$query .= ')';
|
||
|
|
||
|
$values[] = $topicInfo;
|
||
|
} elseif($hasCategoryInfos) {
|
||
|
if(!is_array($categoryInfos))
|
||
|
$categoryInfos = [$categoryInfos];
|
||
|
|
||
|
$query .= sprintf(
|
||
|
' WHERE post_id = (SELECT %s(post_id) FROM msz_forum_posts WHERE forum_id IN (%s)',
|
||
|
$getLast ? 'MAX' : 'MIN',
|
||
|
DbTools::prepareListString($categoryInfos)
|
||
|
);
|
||
|
if($hasDeleted)
|
||
|
$query .= sprintf(' AND post_deleted %s NULL', $deleted ? 'IS NOT' : 'IS');
|
||
|
$query .= ')';
|
||
|
|
||
|
foreach($categoryInfos as $categoryInfo) {
|
||
|
if($categoryInfo instanceof ForumCategoryInfo)
|
||
|
$values[] = $categoryInfo->getId();
|
||
|
elseif(is_string($categoryInfo) || is_int($categoryInfo))
|
||
|
$values[] = (string)$categoryInfo;
|
||
|
else
|
||
|
throw new InvalidArgumentException('$categoryInfos contains an invalid item.');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$args = 0;
|
||
|
$stmt = $this->cache->get($query);
|
||
|
foreach($values as $value)
|
||
|
$stmt->addParameter(++$args, $value);
|
||
|
$stmt->execute();
|
||
|
|
||
|
$result = $stmt->getResult();
|
||
|
if(!$result->next())
|
||
|
throw new RuntimeException('Forum post not found.');
|
||
|
|
||
|
return new ForumPostInfo($result);
|
||
|
}
|
||
|
|
||
|
public function createPost(
|
||
|
ForumTopicInfo|string $topicInfo,
|
||
|
UserInfo|string|null $userInfo,
|
||
|
IPAddress|string $remoteAddr,
|
||
|
string $body,
|
||
|
int $bodyParser,
|
||
|
bool $displaySignature,
|
||
|
ForumCategoryInfo|string|null $categoryInfo = null
|
||
|
): ForumPostInfo {
|
||
|
if($categoryInfo instanceof ForumCategoryInfo)
|
||
|
$categoryInfo = $categoryInfo->getId();
|
||
|
|
||
|
if($topicInfo instanceof ForumTopicInfo) {
|
||
|
$categoryInfo ??= $topicInfo->getCategoryId();
|
||
|
$topicInfo = $topicInfo->getId();
|
||
|
} elseif($categoryInfo === null)
|
||
|
throw new InvalidArgumentException('$categoryInfo may only be null if $topicInfo is an instance of ForumTopicInfo.');
|
||
|
|
||
|
if($userInfo instanceof UserInfo)
|
||
|
$userInfo = $userInfo->getId();
|
||
|
|
||
|
if($remoteAddr instanceof IPAddress)
|
||
|
$remoteAddr = (string)$remoteAddr;
|
||
|
|
||
|
$stmt = $this->cache->get('INSERT INTO msz_forum_posts (topic_id, forum_id, user_id, post_ip, post_text, post_parse, post_display_signature) VALUES (?, ?, ?, INET6_ATON(?), ?, ?, ?)');
|
||
|
$stmt->addParameter(1, $topicInfo);
|
||
|
$stmt->addParameter(2, $categoryInfo);
|
||
|
$stmt->addParameter(3, $userInfo);
|
||
|
$stmt->addParameter(4, $remoteAddr);
|
||
|
$stmt->addParameter(5, $body);
|
||
|
$stmt->addParameter(6, $bodyParser);
|
||
|
$stmt->addParameter(7, $displaySignature ? 1 : 0);
|
||
|
$stmt->execute();
|
||
|
|
||
|
return $this->getPost(postId: (string)$this->dbConn->getLastInsertId());
|
||
|
}
|
||
|
|
||
|
public function updatePost(
|
||
|
ForumPostInfo|string $postInfo,
|
||
|
IPAddress|string|null $remoteAddr = null,
|
||
|
?string $body = null,
|
||
|
?int $bodyParser = null,
|
||
|
?bool $displaySignature = null,
|
||
|
bool $bumpEdited = true
|
||
|
): void {
|
||
|
if($postInfo instanceof ForumPostInfo)
|
||
|
$postInfo = $postInfo->getId();
|
||
|
|
||
|
$fields = [];
|
||
|
$values = [];
|
||
|
|
||
|
if($remoteAddr !== null) {
|
||
|
if($remoteAddr instanceof IPAddress)
|
||
|
$remoteAddr = (string)$remoteAddr;
|
||
|
|
||
|
$fields[] = 'post_ip = INET6_ATON(?)';
|
||
|
$values[] = $remoteAddr;
|
||
|
}
|
||
|
|
||
|
if($body !== null) {
|
||
|
$fields[] = 'post_text = ?';
|
||
|
$values[] = $body;
|
||
|
}
|
||
|
|
||
|
if($bodyParser !== null) {
|
||
|
$fields[] = 'post_parse = ?';
|
||
|
$values[] = $bodyParser;
|
||
|
}
|
||
|
|
||
|
if($displaySignature !== null) {
|
||
|
$fields[] = 'post_display_signature = ?';
|
||
|
$values[] = $displaySignature ? 1 : 0;
|
||
|
}
|
||
|
|
||
|
if(empty($fields))
|
||
|
return;
|
||
|
|
||
|
if($bumpEdited)
|
||
|
$fields[] = 'post_edited = NOW()';
|
||
|
|
||
|
$args = 0;
|
||
|
$stmt = $this->cache->get(sprintf('UPDATE msz_forum_posts SET %s WHERE post_id = ?', implode(', ', $fields)));
|
||
|
foreach($values as $value)
|
||
|
$stmt->addParameter(++$args, $value);
|
||
|
$stmt->addParameter(++$args, $postInfo);
|
||
|
$stmt->execute();
|
||
|
}
|
||
|
|
||
|
public function deletePost(ForumPostInfo|string $postInfo): void {
|
||
|
if($postInfo instanceof ForumPostInfo)
|
||
|
$postInfo = $postInfo->getId();
|
||
|
|
||
|
$stmt = $this->cache->get('UPDATE msz_forum_posts SET post_deleted = COALESCE(post_deleted, NOW()) WHERE post_id = ?');
|
||
|
$stmt->addParameter(1, $postInfo);
|
||
|
$stmt->execute();
|
||
|
}
|
||
|
|
||
|
public function restorePost(ForumPostInfo|string $postInfo): void {
|
||
|
if($postInfo instanceof ForumPostInfo)
|
||
|
$postInfo = $postInfo->getId();
|
||
|
|
||
|
$stmt = $this->cache->get('UPDATE msz_forum_posts SET post_deleted = NULL WHERE post_id = ?');
|
||
|
$stmt->addParameter(1, $postInfo);
|
||
|
$stmt->execute();
|
||
|
}
|
||
|
|
||
|
public function nukePost(ForumPostInfo|string $postInfo): void {
|
||
|
if($postInfo instanceof ForumPostInfo)
|
||
|
$postInfo = $postInfo->getId();
|
||
|
|
||
|
$stmt = $this->cache->get('DELETE FROM msz_forum_posts WHERE post_id = ?');
|
||
|
$stmt->addParameter(1, $postInfo);
|
||
|
$stmt->execute();
|
||
|
}
|
||
|
|
||
|
public function getUserLastPostCreatedTime(UserInfo|string $userInfo): int {
|
||
|
if($userInfo instanceof UserInfo)
|
||
|
$userInfo = $userInfo->getId();
|
||
|
|
||
|
// intentionally including deleted posts
|
||
|
$stmt = $this->cache->get('SELECT UNIX_TIMESTAMP(MAX(post_created)) FROM msz_forum_posts WHERE user_id = ?');
|
||
|
$stmt->addParameter(1, $userInfo);
|
||
|
$stmt->execute();
|
||
|
|
||
|
$result = $stmt->getResult();
|
||
|
if(!$result->next())
|
||
|
return 0;
|
||
|
|
||
|
return $result->getInteger(0);
|
||
|
}
|
||
|
|
||
|
public function getUserLastPostCreatedAt(UserInfo|string $userInfo): DateTime {
|
||
|
return DateTime::fromUnixTimeSeconds($this->getUserLastPostCreatedTime($userInfo));
|
||
|
}
|
||
|
|
||
|
public function generatePostRankings(
|
||
|
int $year = 0,
|
||
|
int $month = 0,
|
||
|
array $exceptCategoryInfos = [],
|
||
|
array $exceptTopicInfos = []
|
||
|
): array {
|
||
|
$hasYear = $year > 0;
|
||
|
$hasMonth = $hasYear && $month > 0;
|
||
|
$hasExcludedCategoryInfos = !empty($exceptCategoryInfos);
|
||
|
$hasExcludedTopicInfos = !empty($exceptTopicInfos);
|
||
|
|
||
|
$query = 'SELECT user_id, COUNT(*) AS posts_count FROM msz_forum_posts WHERE post_deleted IS NULL';
|
||
|
if($hasYear)
|
||
|
$query .= sprintf(
|
||
|
' AND DATE(post_created) BETWEEN "%1$04d-%2$02d-01" AND "%1$04d-%3$02d-31"',
|
||
|
$year,
|
||
|
$hasMonth ? $month : 1,
|
||
|
$hasMonth ? $month : 12
|
||
|
);
|
||
|
if($hasExcludedCategoryInfos)
|
||
|
$query .= sprintf(' AND forum_id NOT IN (%s)', DbTools::prepareListString($exceptCategoryInfos));
|
||
|
if($hasExcludedTopicInfos)
|
||
|
$query .= sprintf(' AND topic_id NOT IN (%s)', DbTools::prepareListString($exceptTopicInfos));
|
||
|
$query .= ' GROUP BY user_id HAVING posts_count > 0 ORDER BY posts_count DESC';
|
||
|
|
||
|
$args = 0;
|
||
|
$stmt = $this->cache->get($query);
|
||
|
foreach($exceptCategoryInfos as $exceptCategoryInfo)
|
||
|
$stmt->addParameter(++$args, $exceptCategoryInfo instanceof ForumCategoryInfo ? $exceptCategoryInfo->getId() : $exceptCategoryInfo);
|
||
|
foreach($exceptTopicInfos as $exceptTopicInfo)
|
||
|
$stmt->addParameter(++$args, $exceptTopicInfo instanceof ForumTopicInfo ? $exceptTopicInfo->getId() : $exceptTopicInfo);
|
||
|
$stmt->execute();
|
||
|
|
||
|
$result = $stmt->getResult();
|
||
|
$rankings = [];
|
||
|
$rankNo = 0;
|
||
|
$lastPostsCount = PHP_INT_MAX;
|
||
|
|
||
|
while($result->next()) {
|
||
|
$rankings[] = $ranking = new stdClass;
|
||
|
$ranking->userId = $result->getString(0);
|
||
|
$ranking->postsCount = $result->getInteger(1);
|
||
|
|
||
|
if($lastPostsCount > $ranking->postsCount) {
|
||
|
++$rankNo;
|
||
|
$lastPostsCount = $ranking->postsCount;
|
||
|
}
|
||
|
|
||
|
$ranking->position = $rankNo;
|
||
|
}
|
||
|
|
||
|
return $rankings;
|
||
|
}
|
||
|
}
|