diff --git a/public-legacy/auth/register.php b/public-legacy/auth/register.php index 7a921b8..3e57bd6 100644 --- a/public-legacy/auth/register.php +++ b/public-legacy/auth/register.php @@ -104,7 +104,10 @@ while(!$restricted && !empty($register)) { $users->addRoles($userInfo, $defaultRoleInfo); $config->setString('users.newest', $userInfo->getId()); - $msz->getPerms()->precalculatePermissions($msz->getForum(), [$userInfo->getId()]); + $msz->getPerms()->precalculatePermissions( + $msz->getForumContext()->getCategories(), + [$userInfo->getId()] + ); url_redirect('auth-login-welcome', ['username' => $userInfo->getName()]); return; diff --git a/public-legacy/forum/forum.php b/public-legacy/forum/forum.php index 834cf7d..fed93f8 100644 --- a/public-legacy/forum/forum.php +++ b/public-legacy/forum/forum.php @@ -4,13 +4,16 @@ namespace Misuzu; use stdClass; use RuntimeException; -$forum = $msz->getForum(); +$forumCtx = $msz->getForumContext(); +$forumCategories = $forumCtx->getCategories(); +$forumTopics = $forumCtx->getTopics(); +$forumPosts = $forumCtx->getPosts(); $usersCtx = $msz->getUsersContext(); $categoryId = (int)filter_input(INPUT_GET, 'f', FILTER_SANITIZE_NUMBER_INT); try { - $categoryInfo = $forum->getCategory(categoryId: $categoryId); + $categoryInfo = $forumCategories->getCategory(categoryId: $categoryId); } catch(RuntimeException $ex) { Template::throwError(404); } @@ -29,7 +32,7 @@ if($usersCtx->hasActiveBan($currentUser)) if($categoryInfo->isLink()) { if($categoryInfo->hasLinkTarget()) { - $forum->incrementCategoryClicks($categoryInfo); + $forumCategories->incrementCategoryClicks($categoryInfo); redirect($categoryInfo->getLinkTarget()); return; } @@ -37,7 +40,7 @@ if($categoryInfo->isLink()) { Template::throwError(404); } -$forumPagination = new Pagination($forum->countTopics( +$forumPagination = new Pagination($forumTopics->countTopics( categoryInfo: $categoryInfo, global: true, deleted: $perms->check(Perm::F_POST_DELETE_ANY) ? null : false @@ -50,7 +53,7 @@ $children = []; $topics = []; if($categoryInfo->mayHaveChildren()) { - $children = $forum->getCategoryChildren($categoryInfo, hidden: false, asTree: true); + $children = $forumCategories->getCategoryChildren($categoryInfo, hidden: false, asTree: true); foreach($children as $childId => $child) { $childPerms = $authInfo->getPerms('forum', $child->info); @@ -79,7 +82,7 @@ if($categoryInfo->mayHaveChildren()) { $catIds[] = $greatGrandChildId; } - $grandChildUnread = $forum->checkCategoryUnread($catIds, $currentUser); + $grandChildUnread = $forumCategories->checkCategoryUnread($catIds, $currentUser); if($grandChildUnread) $childUnread = true; } @@ -98,7 +101,7 @@ if($categoryInfo->mayHaveChildren()) { } try { - $lastPostInfo = $forum->getPost(categoryInfos: $catIds, getLast: true, deleted: false); + $lastPostInfo = $forumPosts->getPost(categoryInfos: $catIds, getLast: true, deleted: false); } catch(RuntimeException $ex) { $lastPostInfo = null; } @@ -106,7 +109,7 @@ if($categoryInfo->mayHaveChildren()) { if($lastPostInfo !== null) { $child->lastPost = new stdClass; $child->lastPost->info = $lastPostInfo; - $child->lastPost->topicInfo = $forum->getTopic(postInfo: $lastPostInfo); + $child->lastPost->topicInfo = $forumTopics->getTopic(postInfo: $lastPostInfo); if($lastPostInfo->hasUserId()) { $child->lastPost->user = $usersCtx->getUserInfo($lastPostInfo->getUserId()); @@ -116,7 +119,7 @@ if($categoryInfo->mayHaveChildren()) { } if($child->info->mayHaveTopics() && !$childUnread) - $childUnread = $forum->checkCategoryUnread($child->info, $currentUser); + $childUnread = $forumCategories->checkCategoryUnread($child->info, $currentUser); $child->perms = $childPerms; $child->unread = $childUnread; @@ -124,7 +127,7 @@ if($categoryInfo->mayHaveChildren()) { } if($categoryInfo->mayHaveTopics()) { - $topicInfos = $forum->getTopics( + $topicInfos = $forumTopics->getTopics( categoryInfo: $categoryInfo, global: true, deleted: $perms->check(Perm::F_POST_DELETE_ANY) ? null : false, @@ -134,8 +137,8 @@ if($categoryInfo->mayHaveTopics()) { foreach($topicInfos as $topicInfo) { $topics[] = $topic = new stdClass; $topic->info = $topicInfo; - $topic->unread = $forum->checkTopicUnread($topicInfo, $currentUser); - $topic->participated = $forum->checkTopicParticipated($topicInfo, $currentUser); + $topic->unread = $forumTopics->checkTopicUnread($topicInfo, $currentUser); + $topic->participated = $forumTopics->checkTopicParticipated($topicInfo, $currentUser); $topic->lastPost = new stdClass; if($topicInfo->hasUserId()) { @@ -144,7 +147,7 @@ if($categoryInfo->mayHaveTopics()) { } try { - $topic->lastPost->info = $lastPostInfo = $forum->getPost( + $topic->lastPost->info = $lastPostInfo = $forumPosts->getPost( topicInfo: $topicInfo, getLast: true, deleted: $topicInfo->isDeleted() ? null : false, @@ -163,8 +166,8 @@ $perms = $perms->checkMany([ ]); Template::render('forum.forum', [ - 'forum_breadcrumbs' => $forum->getCategoryAncestry($categoryInfo), - 'global_accent_colour' => $forum->getCategoryColour($categoryInfo), + 'forum_breadcrumbs' => $forumCategories->getCategoryAncestry($categoryInfo), + 'global_accent_colour' => $forumCategories->getCategoryColour($categoryInfo), 'forum_info' => $categoryInfo, 'forum_children' => $children, 'forum_topics' => $topics, diff --git a/public-legacy/forum/index.php b/public-legacy/forum/index.php index ab5531a..d12cce9 100644 --- a/public-legacy/forum/index.php +++ b/public-legacy/forum/index.php @@ -4,7 +4,10 @@ namespace Misuzu; use stdClass; use RuntimeException; -$forum = $msz->getForum(); +$forumCtx = $msz->getForumContext(); +$forumCategories = $forumCtx->getCategories(); +$forumTopics = $forumCtx->getTopics(); +$forumPosts = $forumCtx->getPosts(); $usersCtx = $msz->getUsersContext(); $mode = (string)filter_input(INPUT_GET, 'm'); @@ -20,13 +23,13 @@ if($mode === 'mark') { if($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) { $categoryInfos = $categoryId === null - ? $forum->getCategories() - : $forum->getCategoryChildren(parentInfo: $categoryId, includeSelf: true); + ? $forumCategories->getCategories() + : $forumCategories->getCategoryChildren(parentInfo: $categoryId, includeSelf: true); foreach($categoryInfos as $categoryInfo) { $perms = $authInfo->getPerms('forum', $categoryInfo); if($perms->check(Perm::F_CATEGORY_LIST)) - $forum->updateUserReadCategory($userInfo, $categoryInfo); + $forumCategories->updateUserReadCategory($userInfo, $categoryInfo); } url_redirect($categoryId ? 'forum-category' : 'forum-index', ['forum' => $categoryId]); @@ -47,7 +50,7 @@ if($mode === 'mark') { if($mode !== '') Template::throwError(404); -$categories = $forum->getCategories(hidden: false, asTree: true); +$categories = $forumCategories->getCategories(hidden: false, asTree: true); foreach($categories as $categoryId => $category) { $perms = $authInfo->getPerms('forum', $category->info); @@ -87,7 +90,7 @@ foreach($categories as $categoryId => $category) { $catIds[] = $greatGrandChildId; } - $grandChildUnread = $forum->checkCategoryUnread($catIds, $currentUser); + $grandChildUnread = $forumCategories->checkCategoryUnread($catIds, $currentUser); if($grandChildUnread) $childUnread = true; } @@ -106,7 +109,7 @@ foreach($categories as $categoryId => $category) { } try { - $lastPostInfo = $forum->getPost(categoryInfos: $catIds, getLast: true, deleted: false); + $lastPostInfo = $forumPosts->getPost(categoryInfos: $catIds, getLast: true, deleted: false); } catch(RuntimeException $ex) { $lastPostInfo = null; } @@ -114,7 +117,7 @@ foreach($categories as $categoryId => $category) { if($lastPostInfo !== null) { $child->lastPost = new stdClass; $child->lastPost->info = $lastPostInfo; - $child->lastPost->topicInfo = $forum->getTopic(postInfo: $lastPostInfo); + $child->lastPost->topicInfo = $forumTopics->getTopic(postInfo: $lastPostInfo); if($lastPostInfo->hasUserId()) { $child->lastPost->user = $usersCtx->getUserInfo($lastPostInfo->getUserId()); @@ -125,7 +128,7 @@ foreach($categories as $categoryId => $category) { } if($child->info->mayHaveTopics() && !$childUnread) { - $childUnread = $forum->checkCategoryUnread($child->info, $currentUser); + $childUnread = $forumCategories->checkCategoryUnread($child->info, $currentUser); if($childUnread) $unread = true; } @@ -135,7 +138,7 @@ foreach($categories as $categoryId => $category) { } if($category->info->mayHaveTopics() && !$unread) - $unread = $forum->checkCategoryUnread($category->info, $currentUser); + $unread = $forumCategories->checkCategoryUnread($category->info, $currentUser); if(!$category->info->isListing()) { if(!array_key_exists('0', $categories)) { @@ -159,7 +162,7 @@ foreach($categories as $categoryId => $category) { } try { - $lastPostInfo = $forum->getPost(categoryInfos: $catIds, getLast: true, deleted: false); + $lastPostInfo = $forumPosts->getPost(categoryInfos: $catIds, getLast: true, deleted: false); } catch(RuntimeException $ex) { $lastPostInfo = null; } @@ -167,7 +170,7 @@ foreach($categories as $categoryId => $category) { if($lastPostInfo !== null) { $category->lastPost = new stdClass; $category->lastPost->info = $lastPostInfo; - $category->lastPost->topicInfo = $forum->getTopic(postInfo: $lastPostInfo); + $category->lastPost->topicInfo = $forumTopics->getTopic(postInfo: $lastPostInfo); if($lastPostInfo->hasUserId()) { $category->lastPost->user = $usersCtx->getUserInfo($lastPostInfo->getUserId()); diff --git a/public-legacy/forum/leaderboard.php b/public-legacy/forum/leaderboard.php index 6caa01c..d8f6e26 100644 --- a/public-legacy/forum/leaderboard.php +++ b/public-legacy/forum/leaderboard.php @@ -6,7 +6,7 @@ use RuntimeException; if(!$msz->getAuthInfo()->getPerms('global')->check(Perm::G_FORUM_LEADERBOARD_VIEW)) Template::throwError(403); -$forum = $msz->getForum(); +$forumCtx = $msz->getForumContext(); $usersCtx = $msz->getUsersContext(); $config = $cfg->getValues([ ['forum_leader.first_year:i', 2018], @@ -61,7 +61,7 @@ for($i = $currentYear, $j = $currentMonth;;) { break; } -$rankings = $forum->generatePostRankings($year, $month, $unrankedForums, $unrankedTopics); +$rankings = $forumCtx->getPosts()->generatePostRankings($year, $month, $unrankedForums, $unrankedTopics); foreach($rankings as $ranking) { $ranking->user = $ranking->colour = null; diff --git a/public-legacy/forum/post.php b/public-legacy/forum/post.php index 55fe74f..667f37e 100644 --- a/public-legacy/forum/post.php +++ b/public-legacy/forum/post.php @@ -3,7 +3,8 @@ namespace Misuzu; use RuntimeException; -$forum = $msz->getForum(); +$forumCtx = $msz->getForumContext(); +$forumPosts = $forumCtx->getPosts(); $usersCtx = $msz->getUsersContext(); $postId = !empty($_GET['p']) && is_string($_GET['p']) ? (int)$_GET['p'] : 0; @@ -23,7 +24,7 @@ if($postMode !== '' && $usersCtx->hasActiveBan($currentUser)) Template::displayInfo('You have been banned, check your profile for more information.', 403); try { - $postInfo = $forum->getPost(postId: $postId); + $postInfo = $forumPosts->getPost(postId: $postId); } catch(RuntimeException $ex) { Template::throwError(404); } @@ -56,7 +57,7 @@ switch($postMode) { Template::displayInfo('This post has existed for too long. Ask a moderator to remove if it absolutely necessary.', 403); } - $originalPostInfo = $forum->getPost(topicInfo: $postInfo->getTopicId()); + $originalPostInfo = $forumPosts->getPost(topicInfo: $postInfo->getTopicId()); if($originalPostInfo->getId() === $postInfo->getId()) Template::displayInfo('This is the opening post of the topic it belongs to, it may not be deleted without deleting the entire topic as well.', 403); @@ -79,7 +80,7 @@ switch($postMode) { break; } - $forum->deletePost($postInfo); + $forumPosts->deletePost($postInfo); $msz->createAuditLog('FORUM_POST_DELETE', [$postInfo->getId()]); url_redirect('forum-topic', ['topic' => $postInfo->getTopicId()]); @@ -108,7 +109,7 @@ switch($postMode) { break; } - $forum->nukePost($postInfo->getId()); + $forumPosts->nukePost($postInfo->getId()); $msz->createAuditLog('FORUM_POST_NUKE', [$postInfo->getId()]); url_redirect('forum-topic', ['topic' => $postInfo->getTopicId()]); @@ -137,7 +138,7 @@ switch($postMode) { break; } - $forum->restorePost($postInfo->getId()); + $forumPosts->restorePost($postInfo->getId()); $msz->createAuditLog('FORUM_POST_RESTORE', [$postInfo->getId()]); url_redirect('forum-topic', ['topic' => $postInfo->getTopicId()]); diff --git a/public-legacy/forum/posting.php b/public-legacy/forum/posting.php index 71c546a..bf2a0b7 100644 --- a/public-legacy/forum/posting.php +++ b/public-legacy/forum/posting.php @@ -11,7 +11,10 @@ $authInfo = $msz->getAuthInfo(); if(!$authInfo->isLoggedIn()) Template::throwError(401); -$forum = $msz->getForum(); +$forumCtx = $msz->getForumContext(); +$forumCategories = $forumCtx->getCategories(); +$forumTopics = $forumCtx->getTopics(); +$forumPosts = $forumCtx->getPosts(); $usersCtx = $msz->getUsersContext(); $currentUser = $authInfo->getUserInfo(); @@ -19,8 +22,6 @@ $currentUserId = $currentUser->getId(); if($usersCtx->hasActiveBan($currentUser)) Template::throwError(403); -$userPostsCounts = []; - $forumPostingModes = [ 'create', 'edit', 'quote', 'preview', ]; @@ -63,7 +64,7 @@ if(empty($postId)) { $hasPostInfo = false; } else { try { - $postInfo = $forum->getPost(postId: $postId); + $postInfo = $forumPosts->getPost(postId: $postId); } catch(RuntimeException $ex) { Template::throwError(404); } @@ -80,7 +81,7 @@ if(empty($topicId)) { $hasTopicInfo = false; } else { try { - $topicInfo = $forum->getTopic(topicId: $topicId); + $topicInfo = $forumTopics->getTopic(topicId: $topicId); } catch(RuntimeException $ex) { Template::throwError(404); } @@ -89,7 +90,7 @@ if(empty($topicId)) { Template::throwError(404); $forumId = $topicInfo->getCategoryId(); - $originalPostInfo = $forum->getPost(topicInfo: $topicInfo); + $originalPostInfo = $forumPosts->getPost(topicInfo: $topicInfo); $hasTopicInfo = true; } @@ -97,7 +98,7 @@ if(empty($forumId)) { $hasCategoryInfo = false; } else { try { - $categoryInfo = $forum->getCategory(categoryId: $forumId); + $categoryInfo = $forumCategories->getCategory(categoryId: $forumId); } catch(RuntimeException $ex) { Template::throwError(404); } @@ -152,7 +153,7 @@ if(!empty($_POST)) { $postTimeout = $cfg->getInteger('forum.posting.timeout', 5); if($postTimeout > 0) { $postTimeoutThreshold = DateTime::now()->modify(sprintf('-%d seconds', $postTimeout)); - $lastPostCreatedAt = $forum->getUserLastPostCreatedAt($currentUser); + $lastPostCreatedAt = $forumPosts->getUserLastPostCreatedAt($currentUser); if($lastPostCreatedAt->isMoreThan($postTimeoutThreshold)) { $waitSeconds = $postTimeout + ($lastPostCreatedAt->getUnixTimeSeconds() - time()); @@ -205,7 +206,7 @@ if(!empty($_POST)) { switch($mode) { case 'create': if(empty($topicInfo)) { - $topicInfo = $forum->createTopic( + $topicInfo = $forumTopics->createTopic( $categoryInfo, $currentUser, $topicTitle, @@ -213,11 +214,11 @@ if(!empty($_POST)) { ); $topicId = $topicInfo->getId(); - $forum->incrementCategoryTopics($categoryInfo); + $forumTopics->incrementCategoryTopics($categoryInfo); } else - $forum->bumpTopic($topicInfo); + $forumTopics->bumpTopic($topicInfo); - $postInfo = $forum->createPost( + $postInfo = $forumPosts->createPost( $topicId, $currentUser, $_SERVER['REMOTE_ADDR'], @@ -228,7 +229,7 @@ if(!empty($_POST)) { ); $postId = $postInfo->getId(); - $forum->incrementCategoryPosts($categoryInfo); + $forumCategories->incrementCategoryPosts($categoryInfo); break; case 'edit': @@ -236,7 +237,7 @@ if(!empty($_POST)) { && $postInfo->shouldMarkAsEdited() && $postText !== $postInfo->getBody(); - $forum->updatePost( + $forumPosts->updatePost( $postId, remoteAddr: $_SERVER['REMOTE_ADDR'], body: $postText, @@ -246,7 +247,7 @@ if(!empty($_POST)) { ); if($isEditingTopic && ($topicTitleChanged || $topicTypeChanged)) - $forum->updateTopic( + $forumTopics->updateTopic( $topicId, title: $topicTitle, type: $topicType @@ -278,11 +279,7 @@ if($mode === 'edit') { // $post is pretty much sure to be populated at this poin if($postInfo->hasUserId()) { $post->user = $usersCtx->getUserInfo($postInfo->getUserId()); $post->colour = $usersCtx->getUserColour($post->user); - - $postUserId = $postInfo->getUserId(); - if(!array_key_exists($postUserId, $userPostsCounts)) - $userPostsCounts[$postUserId] = $forum->countPosts(userInfo: $post->user, deleted: false); - $post->postsCount = $userPostsCounts[$postUserId]; + $post->postsCount = $forumCtx->countTotalUserPosts($post->user); } $post->isOriginalPost = $originalPostInfo->getId() == $postInfo->getId(); @@ -293,18 +290,18 @@ if($mode === 'edit') { // $post is pretty much sure to be populated at this poin } try { - $lastPostInfo = $forum->getPost(userInfo: $currentUser, getLast: true, deleted: false); + $lastPostInfo = $forumPosts->getPost(userInfo: $currentUser, getLast: true, deleted: false); $selectedParser = $lastPostInfo->getParser(); } catch(RuntimeException $ex) { $selectedParser = Parser::BBCODE; } Template::render('forum.posting', [ - 'posting_breadcrumbs' => $forum->getCategoryAncestry($categoryInfo), - 'global_accent_colour' => $forum->getCategoryColour($categoryInfo), + 'posting_breadcrumbs' => $forumCategories->getCategoryAncestry($categoryInfo), + 'global_accent_colour' => $forumCategories->getCategoryColour($categoryInfo), 'posting_user' => $currentUser, 'posting_user_colour' => $usersCtx->getUserColour($currentUser), - 'posting_user_posts_count' => $userPostsCounts[$currentUser->getId()] ?? $forum->countPosts(userInfo: $currentUser, deleted: false), + 'posting_user_posts_count' => $forumCtx->countTotalUserPosts($currentUser), 'posting_user_preferred_parser' => $selectedParser, 'posting_forum' => $categoryInfo, 'posting_notices' => $notices, diff --git a/public-legacy/forum/topic.php b/public-legacy/forum/topic.php index 1deedce..c214c7d 100644 --- a/public-legacy/forum/topic.php +++ b/public-legacy/forum/topic.php @@ -4,7 +4,11 @@ namespace Misuzu; use stdClass; use RuntimeException; -$forum = $msz->getForum(); +$forumCtx = $msz->getForumContext(); +$forumCategories = $forumCtx->getCategories(); +$forumTopics = $forumCtx->getTopics(); +$forumTopicRedirects = $forumCtx->getTopicRedirects(); +$forumPosts = $forumCtx->getPosts(); $usersCtx = $msz->getUsersContext(); $postId = !empty($_GET['p']) && is_string($_GET['p']) ? (int)$_GET['p'] : 0; @@ -19,7 +23,7 @@ $currentUserId = $currentUser === null ? '0' : $currentUser->getId(); if($topicId < 1 && $postId > 0) { try { - $postInfo = $forum->getPost(postId: $postId); + $postInfo = $forumPosts->getPost(postId: $postId); } catch(RuntimeException $ex) { Template::throwError(404); } @@ -32,7 +36,7 @@ if($topicId < 1 && $postId > 0) { Template::throwError(404); $topicId = $postInfo->getTopicId(); - $preceedingPostCount = $forum->countPosts( + $preceedingPostCount = $forumPosts->countPosts( topicInfo: $topicId, upToPostInfo: $postInfo, deleted: $canDeleteAny ? null : false @@ -41,7 +45,7 @@ if($topicId < 1 && $postId > 0) { try { $topicIsNuked = $topicIsDeleted = $canDeleteAny = false; - $topicInfo = $forum->getTopic(topicId: $topicId); + $topicInfo = $forumTopics->getTopic(topicId: $topicId); } catch(RuntimeException $ex) { $topicIsNuked = true; } @@ -60,8 +64,8 @@ if(!$topicIsNuked) { $canDeleteAny = $perms->check(Perm::F_POST_DELETE_ANY); } -if(($topicIsNuked || $topicIsDeleted) && $forum->hasTopicRedirect($topicId)) { - $topicRedirectInfo = $forum->getTopicRedirect($topicId); +if(($topicIsNuked || $topicIsDeleted) && $forumTopicRedirects->hasTopicRedirect($topicId)) { + $topicRedirectInfo = $forumTopicRedirects->getTopicRedirect($topicId); Template::set('topic_redir_info', $topicRedirectInfo); if($topicIsNuked || !$canDeleteAny) { @@ -80,7 +84,7 @@ if(!$perms->check(Perm::F_CATEGORY_VIEW)) // this should be in the config $deletePostThreshold = 1; -$categoryInfo = $forum->getCategory(topicInfo: $topicInfo); +$categoryInfo = $forumCategories->getCategory(topicInfo: $topicInfo); $topicIsLocked = $topicInfo->isLocked(); $topicIsArchived = $categoryInfo->isArchived(); $topicPostsTotal = $topicInfo->getTotalPostsCount(); @@ -135,7 +139,7 @@ if(in_array($moderationMode, $validModerationModes, true)) { Template::displayInfo('This topic has existed for too long. Ask a moderator to remove if it absolutely necessary.', 403); // deleted posts are intentionally included - $topicPostCount = $forum->countPosts(topicInfo: $topicInfo); + $topicPostCount = $forumPosts->countPosts(topicInfo: $topicInfo); if($topicPostCount > $deletePostThreshold) Template::displayInfo('This topic already has replies, you may no longer delete it. Ask a moderator to remove if it absolutely necessary.', 403); } @@ -159,7 +163,7 @@ if(in_array($moderationMode, $validModerationModes, true)) { break; } - $forum->deleteTopic($topicInfo->getId()); + $forumTopics->deleteTopic($topicInfo->getId()); $msz->createAuditLog('FORUM_TOPIC_DELETE', [$topicInfo->getId()]); url_redirect('forum-category', [ @@ -189,7 +193,7 @@ if(in_array($moderationMode, $validModerationModes, true)) { break; } - $forum->restoreTopic($topicInfo->getId()); + $forumTopics->restoreTopic($topicInfo->getId()); $msz->createAuditLog('FORUM_TOPIC_RESTORE', [$topicInfo->getId()]); url_redirect('forum-category', [ @@ -219,7 +223,7 @@ if(in_array($moderationMode, $validModerationModes, true)) { break; } - $forum->nukeTopic($topicInfo->getId()); + $forumTopics->nukeTopic($topicInfo->getId()); $msz->createAuditLog('FORUM_TOPIC_NUKE', [$topicInfo->getId()]); url_redirect('forum-category', [ @@ -229,7 +233,7 @@ if(in_array($moderationMode, $validModerationModes, true)) { case 'bump': if($canBumpTopic) { - $forum->bumpTopic($topicInfo->getId()); + $forumTopics->bumpTopic($topicInfo->getId()); $msz->createAuditLog('FORUM_TOPIC_BUMP', [$topicInfo->getId()]); } @@ -240,7 +244,7 @@ if(in_array($moderationMode, $validModerationModes, true)) { case 'lock': if($canLockTopic && !$topicIsLocked) { - $forum->lockTopic($topicInfo->getId()); + $forumTopics->lockTopic($topicInfo->getId()); $msz->createAuditLog('FORUM_TOPIC_LOCK', [$topicInfo->getId()]); } @@ -251,7 +255,7 @@ if(in_array($moderationMode, $validModerationModes, true)) { case 'unlock': if($canLockTopic && $topicIsLocked) { - $forum->unlockTopic($topicInfo->getId()); + $forumTopics->unlockTopic($topicInfo->getId()); $msz->createAuditLog('FORUM_TOPIC_UNLOCK', [$topicInfo->getId()]); } @@ -275,7 +279,7 @@ if(isset($preceedingPostCount)) if(!$topicPagination->hasValidOffset()) Template::throwError(404); -$postInfos = $forum->getPosts( +$postInfos = $forumPosts->getPosts( topicInfo: $topicInfo, deleted: $perms->check(Perm::F_POST_DELETE_ANY) ? null : false, pagination: $topicPagination, @@ -284,9 +288,8 @@ $postInfos = $forum->getPosts( if(empty($postInfos)) Template::throwError(404); -$originalPostInfo = $forum->getPost(topicInfo: $topicInfo); +$originalPostInfo = $forumPosts->getPost(topicInfo: $topicInfo); -$userPostsCounts = []; $posts = []; foreach($postInfos as $postInfo) { @@ -296,11 +299,7 @@ foreach($postInfos as $postInfo) { if($postInfo->hasUserId()) { $post->user = $usersCtx->getUserInfo($postInfo->getUserId()); $post->colour = $usersCtx->getUserColour($post->user); - - $postUserId = $postInfo->getUserId(); - if(!array_key_exists($postUserId, $userPostsCounts)) - $userPostsCounts[$postUserId] = $forum->countPosts(userInfo: $post->user, deleted: false); - $post->postsCount = $userPostsCounts[$postUserId]; + $post->postsCount = $forumCtx->countTotalUserPosts($post->user); } $post->isOriginalPost = $originalPostInfo->getId() == $postInfo->getId(); @@ -310,10 +309,10 @@ foreach($postInfos as $postInfo) { $canReply = !$topicIsArchived && !$topicIsLocked && !$topicIsDeleted && $perms->check(Perm::F_POST_CREATE); -if(!$forum->checkUserHasReadTopic($currentUser, $topicInfo)) - $forum->incrementTopicViews($topicInfo); +if(!$forumTopics->checkUserHasReadTopic($currentUser, $topicInfo)) + $forumTopics->incrementTopicViews($topicInfo); -$forum->updateUserReadTopic($currentUser, $topicInfo); +$forumTopics->updateUserReadTopic($currentUser, $topicInfo); $perms = $perms->checkMany([ 'can_create_post' => Perm::F_POST_CREATE, @@ -324,8 +323,8 @@ $perms = $perms->checkMany([ ]); Template::render('forum.topic', [ - 'topic_breadcrumbs' => $forum->getCategoryAncestry($topicInfo), - 'global_accent_colour' => $forum->getCategoryColour($topicInfo), + 'topic_breadcrumbs' => $forumCategories->getCategoryAncestry($topicInfo), + 'global_accent_colour' => $forumCategories->getCategoryColour($topicInfo), 'topic_info' => $topicInfo, 'category_info' => $categoryInfo, 'topic_posts' => $posts, diff --git a/public-legacy/manage/forum/redirs.php b/public-legacy/manage/forum/redirs.php index 8db1f28..9993c9f 100644 --- a/public-legacy/manage/forum/redirs.php +++ b/public-legacy/manage/forum/redirs.php @@ -5,7 +5,8 @@ $authInfo = $msz->getAuthInfo(); if(!$authInfo->getPerms('global')->check(Perm::G_FORUM_TOPIC_REDIRS_MANAGE)) Template::throwError(403); -$forum = $msz->getForum(); +$forumCtx = $msz->getForumContext(); +$forumTopicRedirects = $forumCtx->getTopicRedirects(); if($_SERVER['REQUEST_METHOD'] === 'POST') { if(!CSRF::validateRequest()) @@ -15,7 +16,7 @@ if($_SERVER['REQUEST_METHOD'] === 'POST') { $rTopicURL = trim((string)filter_input(INPUT_POST, 'topic_redir_url')); $msz->createAuditLog('FORUM_TOPIC_REDIR_CREATE', [$rTopicId]); - $forum->createTopicRedirect($rTopicId, $authInfo->getUserInfo(), $rTopicURL); + $forumTopicRedirects->createTopicRedirect($rTopicId, $authInfo->getUserInfo(), $rTopicURL); url_redirect('manage-forum-topic-redirs'); return; } @@ -26,16 +27,16 @@ if(filter_input(INPUT_GET, 'm') === 'explode') { $rTopicId = (string)filter_input(INPUT_GET, 't'); $msz->createAuditLog('FORUM_TOPIC_REDIR_REMOVE', [$rTopicId]); - $forum->deleteTopicRedirect($rTopicId); + $forumTopicRedirects->deleteTopicRedirect($rTopicId); url_redirect('manage-forum-topic-redirs'); return; } -$pagination = new Pagination($forum->countTopicRedirects(), 20); +$pagination = new Pagination($forumTopicRedirects->countTopicRedirects(), 20); if(!$pagination->hasValidOffset()) Template::throwError(404); -$redirs = $forum->getTopicRedirects(pagination: $pagination); +$redirs = $forumTopicRedirects->getTopicRedirects(pagination: $pagination); Template::render('manage.forum.redirs', [ 'manage_redirs' => $redirs, diff --git a/public-legacy/manage/users/user.php b/public-legacy/manage/users/user.php index 7f52bba..cad3847 100644 --- a/public-legacy/manage/users/user.php +++ b/public-legacy/manage/users/user.php @@ -216,7 +216,10 @@ if(CSRF::validateRequest() && $canEdit) { } if($permsNeedRecalc) - $perms->precalculatePermissions($msz->getForum(), [$userInfo->getId()]); + $perms->precalculatePermissions( + $msz->getForumContext()->getCategories(), + [$userInfo->getId()] + ); url_redirect('manage-user', ['user' => $userInfo->getId()]); return; diff --git a/public-legacy/members.php b/public-legacy/members.php index 9dc17ec..b01117a 100644 --- a/public-legacy/members.php +++ b/public-legacy/members.php @@ -9,7 +9,7 @@ if(!$authInfo->isLoggedIn()) // TODO: restore forum-topics and forum-posts orderings -$forum = $msz->getForum(); +$forumCtx = $msz->getForumContext(); $usersCtx = $msz->getUsersContext(); $users = $usersCtx->getUsers(); $roles = $usersCtx->getRoles(); @@ -94,8 +94,8 @@ foreach($userInfos as $userInfo) $userList[] = [ 'info' => $userInfo, 'colour' => $usersCtx->getUserColour($userInfo), - 'ftopics' => $forum->countTopics(userInfo: $userInfo, deleted: false), - 'fposts' => $forum->countPosts(userInfo: $userInfo, deleted: false), + 'ftopics' => $forumCtx->countTotalUserTopics($userInfo), + 'fposts' => $forumCtx->countTotalUserPosts($userInfo), ]; if(empty($userList)) diff --git a/public-legacy/profile.php b/public-legacy/profile.php index 4969765..bf81317 100644 --- a/public-legacy/profile.php +++ b/public-legacy/profile.php @@ -17,7 +17,10 @@ $isEditing = !empty($_GET['edit']) && is_string($_GET['edit']) ? (bool)$_GET['ed $usersCtx = $msz->getUsersContext(); $users = $usersCtx->getUsers(); -$forum = $msz->getForum(); +$forumCtx = $msz->getForumContext(); +$forumCategories = $forumCtx->getCategories(); +$forumTopics = $forumCtx->getTopics(); +$forumPosts = $forumCtx->getPosts(); $authInfo = $msz->getAuthInfo(); $viewerInfo = $authInfo->getUserInfo(); @@ -289,8 +292,8 @@ if($isEditing) { // TODO: create user counters so these can be statically kept $profileStats = new stdClass; -$profileStats->forum_topic_count = $forum->countTopics(userInfo: $userInfo, deleted: false); -$profileStats->forum_post_count = $forum->countPosts(userInfo: $userInfo, deleted: false); +$profileStats->forum_topic_count = $forumCtx->countTotalUserTopics($userInfo); +$profileStats->forum_post_count = $forumCtx->countTotalUserPosts($userInfo); $profileStats->comments_count = $msz->getComments()->countPosts(userInfo: $userInfo, deleted: false); if(!$viewingAsGuest) { @@ -302,21 +305,21 @@ if(!$viewingAsGuest) { 'forum_leader.unranked.topic:a', ]); - $activeCategoryStats = $forum->getMostActiveCategoryInfo( + $activeCategoryStats = $forumCategories->getMostActiveCategoryInfo( $userInfo, $unranked['forum_leader.unranked.forum'], $unranked['forum_leader.unranked.topic'], deleted: false ); - $activeCategoryInfo = $activeCategoryStats->success ? $forum->getCategory(categoryId: $activeCategoryStats->categoryId) : null; + $activeCategoryInfo = $activeCategoryStats->success ? $forumCategories->getCategory(categoryId: $activeCategoryStats->categoryId) : null; - $activeTopicStats = $forum->getMostActiveTopicInfo( + $activeTopicStats = $forumTopics->getMostActiveTopicInfo( $userInfo, $unranked['forum_leader.unranked.forum'], $unranked['forum_leader.unranked.topic'], deleted: false ); - $activeTopicInfo = $activeTopicStats->success ? $forum->getTopic(topicId: $activeTopicStats->topicId) : null; + $activeTopicInfo = $activeTopicStats->success ? $forumTopics->getTopic(topicId: $activeTopicStats->topicId) : null; $profileFieldValues = $profileFields->getFieldValues($userInfo); $profileFieldInfos = $profileFieldInfos ?? $profileFields->getFields(fieldValueInfos: $isEditing ? null : $profileFieldValues); diff --git a/public-legacy/search.php b/public-legacy/search.php index 9d73164..eb99538 100644 --- a/public-legacy/search.php +++ b/public-legacy/search.php @@ -41,7 +41,10 @@ Template::addFunction('search_merge_query', function($attrs) use (&$searchQueryE if(!empty($searchQuery)) { $usersCtx = $msz->getUsersContext(); $users = $usersCtx->getUsers(); - $forum = $msz->getForum(); + $forumCtx = $msz->getForumContext(); + $forumCategories = $forumCtx->getCategories(); + $forumTopics = $forumCtx->getTopics(); + $forumPosts = $forumCtx->getPosts(); $news = $msz->getNews(); $comments = $msz->getComments(); @@ -79,18 +82,18 @@ if(!empty($searchQuery)) { $currentUserId = $currentUser === null ? 0 : (int)$currentUser->getId(); $forumCategoryIds = XArray::where( - $forum->getCategories(hidden: false), + $forumCategories->getCategories(hidden: false), fn($categoryInfo) => $categoryInfo->mayHaveTopics() && $authInfo->getPerms('forum', $categoryInfo)->check(Perm::F_CATEGORY_VIEW) ); - $forumTopicInfos = $forum->getTopics(categoryInfo: $forumCategoryIds, deleted: false, searchQuery: $searchQueryEvaluated); - $forumTopics = []; + $forumTopicInfos = $forumTopics->getTopics(categoryInfo: $forumCategoryIds, deleted: false, searchQuery: $searchQueryEvaluated); + $ftopics = []; foreach($forumTopicInfos as $topicInfo) { - $forumTopics[] = $topic = new stdClass; + $ftopics[] = $topic = new stdClass; $topic->info = $topicInfo; - $topic->unread = $forum->checkTopicUnread($topicInfo, $currentUser); - $topic->participated = $forum->checkTopicParticipated($topicInfo, $currentUser); + $topic->unread = $forumTopics->checkTopicUnread($topicInfo, $currentUser); + $topic->participated = $forumTopics->checkTopicParticipated($topicInfo, $currentUser); $topic->lastPost = new stdClass; if($topicInfo->hasUserId()) { @@ -99,7 +102,7 @@ if(!empty($searchQuery)) { } try { - $topic->lastPost->info = $lastPostInfo = $forum->getPost( + $topic->lastPost->info = $lastPostInfo = $forumPosts->getPost( topicInfo: $topicInfo, getLast: true, deleted: $topicInfo->isDeleted() ? null : false, @@ -112,17 +115,17 @@ if(!empty($searchQuery)) { } catch(RuntimeException $ex) {} } - $forumPostInfos = $forum->getPosts(categoryInfo: $forumCategoryIds, searchQuery: $searchQueryEvaluated); - $forumPosts = []; + $forumPostInfos = $forumPosts->getPosts(categoryInfo: $forumCategoryIds, searchQuery: $searchQueryEvaluated); + $fposts = []; foreach($forumPostInfos as $postInfo) { - $forumPosts[] = $post = new stdClass; + $fposts[] = $post = new stdClass; $post->info = $postInfo; if($postInfo->hasUserId()) { $post->user = $usersCtx->getUserInfo($postInfo->getUserId(), 'id'); $post->colour = $usersCtx->getUserColour($post->user); - $post->postsCount = $forum->countPosts(userInfo: $post->user, deleted: false); + $post->postsCount = $forumCtx->countTotalUserPosts($post->user); } // can't be bothered sorry @@ -165,15 +168,15 @@ if(!empty($searchQuery)) { $members[] = $member = new stdClass; $member->info = $memberInfo; $member->colour = $usersCtx->getUserColour($memberInfo); - $member->ftopics = $forum->countTopics(userInfo: $memberInfo, deleted: false); - $member->fposts = $forum->countPosts(userInfo: $memberInfo, deleted: false); + $member->ftopics = $forumCtx->countTotalUserTopics($memberInfo); + $member->fposts = $forumCtx->countTotalUserPosts($memberInfo); } } Template::render('home.search', [ 'search_query' => $searchQuery, - 'forum_topics' => $forumTopics ?? [], - 'forum_posts' => $forumPosts ?? [], + 'forum_topics' => $ftopics ?? [], + 'forum_posts' => $fposts ?? [], 'members' => $members ?? [], 'news_posts' => $newsPosts ?? [], ]); diff --git a/public-legacy/settings/account.php b/public-legacy/settings/account.php index ae79b4e..839eacd 100644 --- a/public-legacy/settings/account.php +++ b/public-legacy/settings/account.php @@ -37,7 +37,10 @@ if(!$isRestricted && $isVerifiedRequest && !empty($_POST['role'])) { case 'leave': if($roleInfo->isLeavable()) { $users->removeRoles($userInfo, $roleInfo); - $msz->getPerms()->precalculatePermissions($msz->getForum(), [$userInfo->getId()]); + $msz->getPerms()->precalculatePermissions( + $msz->getForumContext()->getCategories(), + [$userInfo->getId()] + ); } else $errors[] = "You're not allow to leave this role, an administrator has to remove it for you."; break; diff --git a/src/Forum/Forum.php b/src/Forum/Forum.php deleted file mode 100644 index c6a376e..0000000 --- a/src/Forum/Forum.php +++ /dev/null @@ -1,1571 +0,0 @@ -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 - ): int { - if($parentInfo instanceof ForumCategoryInfo) - $parentInfo = $parentInfo->getId(); - - $isRootParent = false; - $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); - 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 { - $isRootParent = false; - $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; - $afterTopicId = null; - $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 incrementTopicViews(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, - ForumPostInfo|string|null $afterPostInfo = null, - ?int $newerThanDays = null, - ?array $searchQuery = null, - ?bool $deleted = null, - ?Pagination $pagination = null - ): array { - // remove this hack when search server - $hasSearchQuery = $searchQuery !== null; - $doSearchOrder = false; - if($hasSearchQuery) { - if(!empty($searchQuery['type']) - && $searchQuery['type'] !== 'forum' - && $searchQuery['type'] !== 'forum:post') - return []; - - $userInfo = null; - $deleted = false; - $pagination = null; - $doSearchOrder = true; - $afterPostInfo = null; - $newerThanDays = null; - - if(!empty($searchQuery['author'])) - $userInfo = $searchQuery['author']; - - if(!empty($searchQuery['after'])) - $afterPostInfo = $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(); - if($afterPostInfo instanceof ForumPostInfo) - $afterPostInfo = $afterPostInfo->getId(); - - $hasCategoryInfo = $categoryInfo !== null; - $hasTopicInfo = $topicInfo !== null; - $hasUserInfo = $userInfo !== null; - $hasUpToPostInfo = $upToPostInfo !== null; - $hasAfterPostInfo = $afterPostInfo !== null; - $hasNewerThanDays = $newerThanDays !== 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($hasAfterPostInfo) - $query .= sprintf(' %s post_id > ?', ++$args > 1 ? 'AND' : 'WHERE'); - if($hasNewerThanDays) - $query .= sprintf(' %s post_created > NOW() - INTERVAL ? DAY', ++$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($hasAfterPostInfo) - $stmt->addParameter(++$args, $afterPostInfo); - if($hasNewerThanDays) - $stmt->addParameter(++$args, $newerThanDays); - 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; - } -} diff --git a/src/Forum/ForumCategories.php b/src/Forum/ForumCategories.php new file mode 100644 index 0000000..c18c624 --- /dev/null +++ b/src/Forum/ForumCategories.php @@ -0,0 +1,507 @@ +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 + ): int { + if($parentInfo instanceof ForumCategoryInfo) + $parentInfo = $parentInfo->getId(); + + $isRootParent = false; + $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); + 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 { + $isRootParent = false; + $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; + } +} diff --git a/src/Forum/ForumContext.php b/src/Forum/ForumContext.php new file mode 100644 index 0000000..1427248 --- /dev/null +++ b/src/Forum/ForumContext.php @@ -0,0 +1,63 @@ +categories = new ForumCategories($dbConn); + $this->topics = new ForumTopics($dbConn); + $this->topicRedirects = new ForumTopicRedirects($dbConn); + $this->posts = new ForumPosts($dbConn); + } + + public function getCategories(): ForumCategories { + return $this->categories; + } + + public function getTopics(): ForumTopics { + return $this->topics; + } + + public function getTopicRedirects(): ForumTopicRedirects { + return $this->topicRedirects; + } + + public function getPosts(): ForumPosts { + return $this->posts; + } + + // should be replaced by a static counter + public function countTotalUserTopics(UserInfo|string|null $userInfo): int { + if($userInfo === null) + return 0; + + $userId = $userInfo instanceof UserInfo ? $userInfo->getId() : $userInfo; + if(array_key_exists($userId, $this->totalUserTopics)) + return $this->totalUserTopics[$userId]; + + return $this->totalUserTopics[$userId] = $this->topics->countTopics(userInfo: $userInfo, deleted: false); + } + + // should be replaced by a static counter + public function countTotalUserPosts(UserInfo|string|null $userInfo): int { + if($userInfo === null) + return 0; + + $userId = $userInfo instanceof UserInfo ? $userInfo->getId() : $userInfo; + if(array_key_exists($userId, $this->totalUserPosts)) + return $this->totalUserPosts[$userId]; + + return $this->totalUserPosts[$userId] = $this->posts->countPosts(userInfo: $userInfo, deleted: false); + } +} diff --git a/src/Forum/ForumPosts.php b/src/Forum/ForumPosts.php new file mode 100644 index 0000000..5040acd --- /dev/null +++ b/src/Forum/ForumPosts.php @@ -0,0 +1,461 @@ +dbConn = $dbConn; + $this->cache = new DbStatementCache($dbConn); + } + + 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, + ForumPostInfo|string|null $afterPostInfo = null, + ?int $newerThanDays = null, + ?array $searchQuery = null, + ?bool $deleted = null, + ?Pagination $pagination = null + ): array { + // remove this hack when search server + $hasSearchQuery = $searchQuery !== null; + $doSearchOrder = false; + if($hasSearchQuery) { + if(!empty($searchQuery['type']) + && $searchQuery['type'] !== 'forum' + && $searchQuery['type'] !== 'forum:post') + return []; + + $userInfo = null; + $deleted = false; + $pagination = null; + $doSearchOrder = true; + $afterPostInfo = null; + $newerThanDays = null; + + if(!empty($searchQuery['author'])) + $userInfo = $searchQuery['author']; + + if(!empty($searchQuery['after'])) + $afterPostInfo = $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(); + if($afterPostInfo instanceof ForumPostInfo) + $afterPostInfo = $afterPostInfo->getId(); + + $hasCategoryInfo = $categoryInfo !== null; + $hasTopicInfo = $topicInfo !== null; + $hasUserInfo = $userInfo !== null; + $hasUpToPostInfo = $upToPostInfo !== null; + $hasAfterPostInfo = $afterPostInfo !== null; + $hasNewerThanDays = $newerThanDays !== 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($hasAfterPostInfo) + $query .= sprintf(' %s post_id > ?', ++$args > 1 ? 'AND' : 'WHERE'); + if($hasNewerThanDays) + $query .= sprintf(' %s post_created > NOW() - INTERVAL ? DAY', ++$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($hasAfterPostInfo) + $stmt->addParameter(++$args, $afterPostInfo); + if($hasNewerThanDays) + $stmt->addParameter(++$args, $newerThanDays); + 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; + } +} diff --git a/src/Forum/ForumTopicRedirects.php b/src/Forum/ForumTopicRedirects.php new file mode 100644 index 0000000..c76a642 --- /dev/null +++ b/src/Forum/ForumTopicRedirects.php @@ -0,0 +1,132 @@ +cache = new DbStatementCache($dbConn); + } + + 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(); + } +} diff --git a/src/Forum/ForumTopics.php b/src/Forum/ForumTopics.php new file mode 100644 index 0000000..1859eeb --- /dev/null +++ b/src/Forum/ForumTopics.php @@ -0,0 +1,529 @@ +dbConn = $dbConn; + $this->cache = new DbStatementCache($dbConn); + } + + 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; + $afterTopicId = null; + $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 incrementTopicViews(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(); + } +} diff --git a/src/MisuzuContext.php b/src/MisuzuContext.php index 65ea9cb..3840a8a 100644 --- a/src/MisuzuContext.php +++ b/src/MisuzuContext.php @@ -20,7 +20,7 @@ use Misuzu\Comments\Comments; use Misuzu\Config\IConfig; use Misuzu\Counters\Counters; use Misuzu\Emoticons\Emotes; -use Misuzu\Forum\Forum; +use Misuzu\Forum\ForumContext; use Misuzu\Home\HomeRoutes; use Misuzu\Info\InfoRoutes; use Misuzu\News\News; @@ -30,7 +30,6 @@ use Misuzu\Profile\ProfileFields; use Misuzu\Satori\SatoriRoutes; use Misuzu\SharpChat\SharpChatRoutes; use Misuzu\Users\UsersContext; -use Misuzu\Users\BanInfo; use Misuzu\Users\UserInfo; use Misuzu\Users\Assets\AssetsRoutes; @@ -58,9 +57,9 @@ class MisuzuContext { private AuthContext $authCtx; private UsersContext $usersCtx; + private ForumContext $forumCtx; private ProfileFields $profileFields; - private Forum $forum; private Permissions $perms; private AuthInfo $authInfo; @@ -74,13 +73,13 @@ class MisuzuContext { $this->authCtx = new AuthContext($dbConn, $config->scopeTo('auth')); $this->usersCtx = new UsersContext($dbConn); + $this->forumCtx = new ForumContext($dbConn); $this->auditLog = new AuditLog($dbConn); $this->changelog = new Changelog($dbConn); $this->comments = new Comments($dbConn); $this->counters = new Counters($dbConn); $this->emotes = new Emotes($dbConn); - $this->forum = new Forum($dbConn); $this->news = new News($dbConn); $this->profileFields = new ProfileFields($dbConn); } @@ -138,10 +137,6 @@ class MisuzuContext { return $this->profileFields; } - public function getForum(): Forum { - return $this->forum; - } - public function getPerms(): Permissions { return $this->perms; } @@ -154,6 +149,10 @@ class MisuzuContext { return $this->usersCtx; } + public function getForumContext(): ForumContext { + return $this->forumCtx; + } + public function getAuthInfo(): AuthInfo { return $this->authInfo; } @@ -294,8 +293,8 @@ class MisuzuContext { $this->router->register(new SatoriRoutes( $this->config->scopeTo('satori'), $this->usersCtx, - $this->profileFields, - $this->forum + $this->forumCtx, + $this->profileFields )); // below is still only otherwise available as stinky php files diff --git a/src/Perms/Permissions.php b/src/Perms/Permissions.php index 3981b2c..8d6a16c 100644 --- a/src/Perms/Permissions.php +++ b/src/Perms/Permissions.php @@ -10,7 +10,7 @@ use Index\Data\DbStatementCache; use Index\Data\DbTools; use Index\Data\IDbConnection; use Index\Data\IDbStatement; -use Misuzu\Forum\Forum; +use Misuzu\Forum\ForumCategories; use Misuzu\Forum\ForumCategoryInfo; use Misuzu\Users\RoleInfo; use Misuzu\Users\UserInfo; @@ -227,7 +227,7 @@ class Permissions { } // precalculates all permissions for fast lookups - public function precalculatePermissions(Forum $forum, array $userIds = []): void { + public function precalculatePermissions(ForumCategories $forumCategories, array $userIds = []): void { $suppliedUsers = !empty($userIds); $doGuest = !$suppliedUsers; @@ -296,7 +296,7 @@ class Permissions { } self::precalculatePermissionsLog('Loading list of forum categories...'); - $forumCats = $forum->getCategories(asTree: true); + $forumCats = $forumCategories->getCategories(asTree: true); foreach($forumCats as $forumCat) $this->precalculatePermissionsForForumCategory($insert, $userIds, $forumCat, $doGuest); diff --git a/src/Satori/SatoriRoutes.php b/src/Satori/SatoriRoutes.php index ecb8d31..c82942a 100644 --- a/src/Satori/SatoriRoutes.php +++ b/src/Satori/SatoriRoutes.php @@ -9,7 +9,7 @@ use Index\Routing\Route; use Misuzu\Pagination; use Misuzu\Tools; use Misuzu\Config\IConfig; -use Misuzu\Forum\Forum; +use Misuzu\Forum\ForumContext; use Misuzu\Profile\ProfileFields; use Misuzu\Users\UsersContext; @@ -17,8 +17,8 @@ final class SatoriRoutes implements IRouteHandler { public function __construct( private IConfig $config, private UsersContext $usersCtx, - private ProfileFields $profileFields, - private Forum $forum + private ForumContext $forumCtx, + private ProfileFields $profileFields ) {} public function registerRoutes(IRouter $router): void { @@ -73,7 +73,7 @@ final class SatoriRoutes implements IRouteHandler { $startId = (string)$request->getParam('start', FILTER_SANITIZE_NUMBER_INT); $posts = []; - $postInfos = $this->forum->getPosts( + $postInfos = $this->forumCtx->getPosts()->getPosts( categoryInfo: $categoryIds, afterPostInfo: $startId, newerThanDays: $backlogDays, @@ -82,9 +82,9 @@ final class SatoriRoutes implements IRouteHandler { ); foreach($postInfos as $postInfo) { - $topicInfo = $this->forum->getTopic(postInfo: $postInfo); - $firstPostInfo = $this->forum->getPost(topicInfo: $topicInfo); - $categoryInfo = $this->forum->getCategory(topicInfo: $topicInfo); + $topicInfo = $this->forumCtx->getTopics()->getTopic(postInfo: $postInfo); + $firstPostInfo = $this->forumCtx->getPosts()->getPost(topicInfo: $topicInfo); + $categoryInfo = $this->forumCtx->getCategories()->getCategory(topicInfo: $topicInfo); $userInfo = $postInfo->hasUserId() ? $this->usersCtx->getUserInfo($postInfo->getUserId()) : null; $userColour = $this->usersCtx->getUserColour($userInfo); diff --git a/tools/cron b/tools/cron index 1a83020..a712c1c 100755 --- a/tools/cron +++ b/tools/cron @@ -75,7 +75,7 @@ msz_sched_task_sql('Synchronise forum_id.', true, 'UPDATE msz_forum_posts AS p INNER JOIN msz_forum_topics AS t ON t.topic_id = p.topic_id SET p.forum_id = t.forum_id'); msz_sched_task_func('Recount forum topics and posts.', true, function() use ($msz) { - $msz->getForum()->syncForumCounters(); + $msz->getForumContext()->getCategories()->syncForumCounters(); }); msz_sched_task_sql('Clean up expired 2fa tokens.', false, @@ -146,7 +146,7 @@ msz_sched_task_func('Recalculate permissions (maybe)...', false, function() use return; $msz->getConfig()->removeValues('perms.needsRecalc'); - $msz->getPerms()->precalculatePermissions($msz->getForum()); + $msz->getPerms()->precalculatePermissions($msz->getForumContext()->getCategories()); }); echo 'Running ' . count($schedTasks) . ' tasks...' . PHP_EOL; diff --git a/tools/recalc-perms b/tools/recalc-perms index ce94adf..390e38e 100755 --- a/tools/recalc-perms +++ b/tools/recalc-perms @@ -5,4 +5,4 @@ namespace Misuzu; require_once __DIR__ . '/../misuzu.php'; $msz->getConfig()->removeValues('perms.needsRecalc'); -$msz->getPerms()->precalculatePermissions($msz->getForum()); +$msz->getPerms()->precalculatePermissions($msz->getForumContext()->getCategories());