diff --git a/assets/less/mio/classes/forum/listing.less b/assets/less/mio/classes/forum/listing.less index aa005a27..fe0c4cf3 100644 --- a/assets/less/mio/classes/forum/listing.less +++ b/assets/less/mio/classes/forum/listing.less @@ -63,6 +63,10 @@ &:hover { text-decoration: underline; } + + &--unread { + font-weight: 700; + } } &__stats { @@ -88,6 +92,7 @@ &__activity { min-width: 270px; + min-height: 50px; align-items: center; @media (max-width: @mio-forum-listing-mobile) { diff --git a/database/2018_05_17_000055_forum_structure.php b/database/2018_05_17_000055_forum_structure.php index da401567..4b90de14 100644 --- a/database/2018_05_17_000055_forum_structure.php +++ b/database/2018_05_17_000055_forum_structure.php @@ -34,7 +34,6 @@ function migrate_up(PDO $conn): void `topic_bumped` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, `topic_deleted` TIMESTAMP NULL DEFAULT NULL, `topic_locked` TIMESTAMP NULL DEFAULT NULL, - `topic_view_count` INT(10) NOT NULL DEFAULT '0', PRIMARY KEY (`topic_id`), INDEX `topics_forum_id_foreign` (`forum_id`), INDEX `topics_user_id_foreign` (`user_id`), @@ -86,33 +85,16 @@ function migrate_up(PDO $conn): void ) "); - $conn->exec(" - CREATE TABLE `msz_forum_categories_track` ( - `user_id` INT(10) UNSIGNED NOT NULL, - `forum_id` INT(10) UNSIGNED NOT NULL, - `track_last_read` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - INDEX `categories_track_forum_id_foreign` (`forum_id`), - INDEX `categories_track_user_id_foreign` (`user_id`), - CONSTRAINT `categories_track_forum_id_foreign` - FOREIGN KEY (`forum_id`) - REFERENCES `msz_forum_categories` (`forum_id`) - ON UPDATE CASCADE - ON DELETE CASCADE, - CONSTRAINT `categories_track_user_id_foreign` - FOREIGN KEY (`user_id`) - REFERENCES `msz_users` (`user_id`) - ON UPDATE CASCADE - ON DELETE CASCADE - ) - "); - $conn->exec(" CREATE TABLE `msz_forum_topics_track` ( `user_id` INT(10) UNSIGNED NOT NULL, `topic_id` INT(10) UNSIGNED NOT NULL, + `forum_id` INT(10) UNSIGNED NOT NULL, `track_last_read` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - INDEX `topics_track_topic_id_foreign` (`topic_id`), - INDEX `topics_track_user_id_foreign` (`user_id`), + UNIQUE INDEX `topics_track_unique` (`user_id`, `topic_id`), + INDEX `topics_track_topic_id_foreign` (`topic_id`), + INDEX `topics_track_user_id_foreign` (`user_id`), + INDEX `topics_track_forum_id_foreign` (`forum_id`), CONSTRAINT `topics_track_topic_id_foreign` FOREIGN KEY (`topic_id`) REFERENCES `msz_forum_topics` (`topic_id`) @@ -122,6 +104,11 @@ function migrate_up(PDO $conn): void FOREIGN KEY (`user_id`) REFERENCES `msz_users` (`user_id`) ON UPDATE CASCADE + ON DELETE CASCADE, + CONSTRAINT `topics_track_forum_id_foreign` + FOREIGN KEY (`forum_id`) + REFERENCES `msz_forum_categories` (`forum_id`) + ON UPDATE CASCADE ON DELETE CASCADE ) "); diff --git a/public/forum/forum.php b/public/forum/forum.php index 499e6a92..218a586b 100644 --- a/public/forum/forum.php +++ b/public/forum/forum.php @@ -12,22 +12,8 @@ if ($forumId === 0) { exit; } -$db = Database::connection(); $templating = $app->getTemplating(); - -$getForum = $db->prepare(' - SELECT - `forum_id`, `forum_name`, `forum_type`, `forum_link`, `forum_link_clicks`, `forum_parent`, - ( - SELECT COUNT(`topic_id`) - FROM `msz_forum_topics` - WHERE `forum_id` = f.`forum_id` - ) as `forum_topic_count` - FROM `msz_forum_categories` as f - WHERE `forum_id` = :forum_id -'); -$getForum->bindValue('forum_id', $forumId); -$forum = $getForum->execute() ? $getForum->fetch() : []; +$forum = forum_fetch($forumId); if (empty($forum) || ($forum['forum_type'] == MSZ_FORUM_TYPE_LINK && empty($forum['forum_link']))) { http_response_code(404); @@ -41,114 +27,15 @@ if ($forum['forum_type'] == MSZ_FORUM_TYPE_LINK) { return; } -// declare this, templating engine assumes it exists -$topics = []; +$topics = forum_may_have_topics($forum['forum_type']) + ? forum_topic_listing($forum['forum_id'], $app->getUserId(), $topicsOffset, $topicsRange) + : []; -// no need to fetch topics for categories (or links but we're already done with those at this point) -if ($forum['forum_type'] == MSZ_FORUM_TYPE_DISCUSSION) { - $getTopics = $db->prepare(' - SELECT - t.`topic_id`, t.`topic_title`, t.`topic_view_count`, t.`topic_locked`, t.`topic_type`, t.`topic_created`, - au.`user_id` as `author_id`, au.`username` as `author_name`, - COALESCE(ar.`role_colour`, CAST(0x40000000 AS UNSIGNED)) as `author_colour`, - lp.`post_id` as `response_id`, - lp.`post_created` as `response_created`, - lu.`user_id` as `respondent_id`, - lu.`username` as `respondent_name`, - COALESCE(lr.`role_colour`, CAST(0x40000000 AS UNSIGNED)) as `respondent_colour`, - ( - SELECT COUNT(`post_id`) - FROM `msz_forum_posts` - WHERE `topic_id` = t.`topic_id` - ) as `topic_post_count` - FROM `msz_forum_topics` as t - LEFT JOIN `msz_users` as au - ON t.`user_id` = au.`user_id` - LEFT JOIN `msz_roles` as ar - ON ar.`role_id` = au.`display_role` - LEFT JOIN `msz_forum_posts` as lp - ON lp.`post_id` = ( - SELECT `post_id` - FROM `msz_forum_posts` - WHERE `topic_id` = t.`topic_id` - ORDER BY `post_id` DESC - LIMIT 1 - ) - LEFT JOIN `msz_users` as lu - ON lu.`user_id` = lp.`user_id` - LEFT JOIN `msz_roles` as lr - ON lr.`role_id` = lu.`display_role` - WHERE t.`forum_id` = :forum_id - AND t.`topic_deleted` IS NULL - ORDER BY t.`topic_type` DESC, t.`topic_bumped` DESC - LIMIT :offset, :take - '); - $getTopics->bindValue('forum_id', $forum['forum_id']); - $getTopics->bindValue('offset', $topicsOffset); - $getTopics->bindValue('take', $topicsRange); - $topics = $getTopics->execute() ? $getTopics->fetchAll() : $topics; -} +$forum['forum_subforums'] = forum_get_children($forum['forum_id'], $app->getUserId()); -$getSubforums = $db->prepare(' - SELECT - f.`forum_id`, f.`forum_name`, f.`forum_description`, f.`forum_type`, f.`forum_link`, f.`forum_archived`, - t.`topic_id` as `recent_topic_id`, p.`post_id` as `recent_post_id`, - t.`topic_title` as `recent_topic_title`, - p.`post_created` as `recent_post_created`, - u.`user_id` as `recent_post_user_id`, - u.`username` as `recent_post_username`, - COALESCE(r.`role_colour`, CAST(0x40000000 AS UNSIGNED)) as `recent_post_user_colour`, - ( - SELECT COUNT(t.`topic_id`) - FROM `msz_forum_topics` as t - WHERE t.`forum_id` = f.`forum_id` - ) as `forum_topic_count`, - ( - SELECT COUNT(p.`post_id`) - FROM `msz_forum_posts` as p - WHERE p.`forum_id` = f.`forum_id` - ) as `forum_post_count` - FROM `msz_forum_categories` as f - LEFT JOIN `msz_forum_topics` as t - ON t.`topic_id` = ( - SELECT `topic_id` - FROM `msz_forum_topics` - WHERE `forum_id` = f.`forum_id` - AND `topic_deleted` IS NULL - ORDER BY `topic_bumped` DESC - LIMIT 1 - ) - LEFT JOIN `msz_forum_posts` as p - ON p.`post_id` = ( - SELECT `post_id` - FROM `msz_forum_posts` - WHERE `topic_id` = t.`topic_id` - ORDER BY `post_id` DESC - LIMIT 1 - ) - LEFT JOIN `msz_users` as u - ON u.`user_id` = p.`user_id` - LEFT JOIN `msz_roles` as r - ON r.`role_id` = u.`display_role` - WHERE `forum_parent` = :forum_id - AND `forum_hidden` = false -'); -$getSubforums->bindValue('forum_id', $forum['forum_id']); -$forum['forum_subforums'] = $getSubforums->execute() ? $getSubforums->fetchAll() : []; - -if (count($forum['forum_subforums']) > 0) { - // this really, really needs a better name - $getSubSubs = $db->prepare(' - SELECT `forum_id`, `forum_name` - FROM `msz_forum_categories` - WHERE `forum_parent` = :forum_id - AND `forum_hidden` = false - '); - - foreach ($forum['forum_subforums'] as $skey => $subforum) { - $getSubSubs->bindValue('forum_id', $subforum['forum_id']); - $forum['forum_subforums'][$skey]['forum_subforums'] = $getSubSubs->execute() ? $getSubSubs->fetchAll() : []; - } +foreach ($forum['forum_subforums'] as $skey => $subforum) { + $forum['forum_subforums'][$skey]['forum_subforums'] + = forum_get_children($subforum['forum_id'], $app->getUserId(), true); } echo $app->getTemplating()->render('forum.forum', [ diff --git a/public/forum/index.php b/public/forum/index.php index 73d6705d..ca5e7a39 100644 --- a/public/forum/index.php +++ b/public/forum/index.php @@ -3,100 +3,21 @@ use Misuzu\Database; require_once __DIR__ . '/../../misuzu.php'; -$db = Database::connection(); - -$categories = $db->query(' - SELECT - f.`forum_id`, f.`forum_name`, f.`forum_type`, - ( - SELECT COUNT(`forum_id`) - FROM `msz_forum_categories` as sf - WHERE sf.`forum_parent` = f.`forum_id` - ) as `forum_children` - FROM `msz_forum_categories` as f - WHERE f.`forum_parent` = 0 - AND f.`forum_type` = 1 - AND f.`forum_hidden` = false - ORDER BY f.`forum_order` -')->fetchAll(); - -$categories = array_merge([ - [ - 'forum_id' => 0, - 'forum_name' => 'Forums', - 'forum_children' => 0, - 'forum_type' => 1, - ], -], $categories); - -$getSubCategories = $db->prepare(' - SELECT - f.`forum_id`, f.`forum_name`, f.`forum_description`, f.`forum_type`, - f.`forum_link`, f.`forum_link_clicks`, f.`forum_archived`, - t.`topic_id` as `recent_topic_id`, p.`post_id` as `recent_post_id`, - t.`topic_title` as `recent_topic_title`, - p.`post_created` as `recent_post_created`, - u.`user_id` as `recent_post_user_id`, - u.`username` as `recent_post_username`, - COALESCE(r.`role_colour`, CAST(0x40000000 AS UNSIGNED)) as `recent_post_user_colour`, - ( - SELECT COUNT(`topic_id`) - FROM `msz_forum_topics` - WHERE `forum_id` = f.`forum_id` - ) as `forum_topic_count`, - ( - SELECT COUNT(`post_id`) - FROM `msz_forum_posts` - WHERE `forum_id` = f.`forum_id` - ) as `forum_post_count` - FROM `msz_forum_categories` as f - LEFT JOIN `msz_forum_topics` as t - ON t.`topic_id` = ( - SELECT `topic_id` - FROM `msz_forum_topics` - WHERE `forum_id` = f.`forum_id` - AND `topic_deleted` IS NULL - ORDER BY `topic_bumped` DESC - LIMIT 1 - ) - LEFT JOIN `msz_forum_posts` as p - ON p.`post_id` = ( - SELECT `post_id` - FROM `msz_forum_posts` - WHERE `topic_id` = t.`topic_id` - ORDER BY `post_id` DESC - LIMIT 1 - ) - LEFT JOIN `msz_users` as u - ON u.`user_id` = p.`user_id` - LEFT JOIN `msz_roles` as r - ON r.`role_id` = u.`display_role` - WHERE f.`forum_parent` = :forum_id - AND f.`forum_hidden` = false - AND ((f.`forum_parent` = 0 AND f.`forum_type` != 1) OR f.`forum_parent` != 0) - ORDER BY f.`forum_order` -'); +$categories = forum_get_root_categories(); foreach ($categories as $key => $category) { - // replace these magic numbers with a constant later, only categories and discussion forums may have subs - if (!in_array($category['forum_type'], [0, 1]) - && ($category['forum_id'] === 0 || $category['forum_children'] > 0)) { - continue; - } + $categories[$key]['forum_subforums'] = forum_get_children($category['forum_id'], $app->getUserId()); - $getSubCategories->bindValue('forum_id', $category['forum_id']); - $categories[$key]['forum_subforums'] = $getSubCategories->execute() ? $getSubCategories->fetchAll() : []; - - // one level down more! foreach ($categories[$key]['forum_subforums'] as $skey => $sub) { - $getSubCategories->bindValue('forum_id', $sub['forum_id']); + if (!forum_may_have_children($sub['forum_type'])) { + continue; + } + $categories[$key]['forum_subforums'][$skey]['forum_subforums'] - = $getSubCategories->execute() ? $getSubCategories->fetchAll() : []; + = forum_get_children($sub['forum_id'], $app->getUserId(), true); } } -$categories[0]['forum_children'] = count($categories[0]['forum_subforums']); - echo $app->getTemplating()->render('forum.index', [ 'forum_categories' => $categories, ]); diff --git a/public/forum/posting.php b/public/forum/posting.php index 6a94f6d8..6d4608eb 100644 --- a/public/forum/posting.php +++ b/public/forum/posting.php @@ -125,6 +125,7 @@ if ($postRequest) { IPAddress::remote()->getString(), $postText ); + forum_topic_mark_read($app->getUserId(), $topicId, $forum['forum_id']); header("Location: /forum/topic.php?p={$postId}#p{$postId}"); return; diff --git a/public/forum/topic.php b/public/forum/topic.php index 3b3ff61f..d0d0dc0d 100644 --- a/public/forum/topic.php +++ b/public/forum/topic.php @@ -1,9 +1,6 @@ getTemplating(); $postId = (int)($_GET['p'] ?? 0); @@ -11,7 +8,6 @@ $topicId = (int)($_GET['t'] ?? 0); $postsOffset = max((int)($_GET['o'] ?? 0), 0); $postsRange = max(min((int)($_GET['r'] ?? 10), 25), 5); -// find topic id if ($topicId < 1 && $postId > 0) { $postInfo = forum_post_find($postId); @@ -21,28 +17,7 @@ if ($topicId < 1 && $postId > 0) { } } -$getTopic = $db->prepare(' - SELECT - t.`topic_id`, t.`forum_id`, t.`topic_title`, t.`topic_type`, t.`topic_locked`, - f.`forum_archived` as `topic_archived`, - ( - SELECT MIN(`post_id`) - FROM `msz_forum_posts` - WHERE `topic_id` = t.`topic_id` - ) as `topic_first_post_id`, - ( - SELECT COUNT(`post_id`) - FROM `msz_forum_posts` - WHERE `topic_id` = t.`topic_id` - ) as `topic_post_count` - FROM `msz_forum_topics` as t - LEFT JOIN `msz_forum_categories` as f - ON f.`forum_id` = t.`forum_id` - WHERE t.`topic_id` = :topic_id - AND t.`topic_deleted` IS NULL -'); -$getTopic->bindValue('topic_id', $topicId); -$topic = $getTopic->execute() ? $getTopic->fetch() : false; +$topic = forum_topic_fetch($topicId); if (!$topic) { http_response_code(404); @@ -50,28 +25,7 @@ if (!$topic) { return; } -$getPosts = $db->prepare(' - SELECT - p.`post_id`, p.`post_text`, p.`post_created`, - p.`topic_id`, - u.`user_id` as `poster_id`, - u.`username` as `poster_name`, - u.`created_at` as `poster_joined`, - COALESCE(r.`role_colour`, CAST(0x40000000 AS UNSIGNED)) as `poster_colour` - FROM `msz_forum_posts` as p - LEFT JOIN `msz_users` as u - ON u.`user_id` = p.`user_id` - LEFT JOIN `msz_roles` as r - ON r.`role_id` = u.`display_role` - WHERE `topic_id` = :topic_id - AND `post_deleted` IS NULL - ORDER BY `post_id` - LIMIT :offset, :take -'); -$getPosts->bindValue('topic_id', $topic['topic_id']); -$getPosts->bindValue('offset', $postsOffset); -$getPosts->bindValue('take', $postsRange); -$posts = $getPosts->execute() ? $getPosts->fetchAll() : []; +$posts = forum_post_listing($topic['topic_id'], $postsOffset, $postsRange); if (!$posts) { http_response_code(404); @@ -79,6 +33,8 @@ if (!$posts) { return; } +forum_topic_mark_read($app->getUserId(), $topic['topic_id'], $topic['forum_id']); + echo $templating->render('forum.topic', [ 'topic_breadcrumbs' => forum_get_breadcrumbs($topic['forum_id']), 'topic_info' => $topic, diff --git a/src/Forum/forum.php b/src/Forum/forum.php index d15c4359..aa811fba 100644 --- a/src/Forum/forum.php +++ b/src/Forum/forum.php @@ -10,6 +10,81 @@ define('MSZ_FORUM_TYPES', [ MSZ_FORUM_TYPE_LINK, ]); +define('MSZ_FORUM_MAY_HAVE_CHILDREN', [ + MSZ_FORUM_TYPE_DISCUSSION, + MSZ_FORUM_TYPE_CATEGORY, +]); + +define('MSZ_FORUM_MAY_HAVE_TOPICS', [ + MSZ_FORUM_TYPE_DISCUSSION, +]); + +define('MSZ_FORUM_ROOT', 0); +define('MSZ_FORUM_ROOT_DATA', [ // should be compatible with the data fetched in forum_get_root_categories + 'forum_id' => 0, + 'forum_name' => 'Forums', + 'forum_children' => 0, + 'forum_type' => MSZ_FORUM_TYPE_CATEGORY, +]); + +function forum_may_have_children(int $forumType): bool +{ + return in_array($forumType, MSZ_FORUM_MAY_HAVE_CHILDREN); +} + +function forum_may_have_topics(int $forumType): bool +{ + return in_array($forumType, MSZ_FORUM_MAY_HAVE_TOPICS); +} + +function forum_fetch(int $forumId): array +{ + $getForum = Database::connection()->prepare(' + SELECT + `forum_id`, `forum_name`, `forum_type`, `forum_link`, `forum_link_clicks`, `forum_parent`, + ( + SELECT COUNT(`topic_id`) + FROM `msz_forum_topics` + WHERE `forum_id` = f.`forum_id` + ) as `forum_topic_count` + FROM `msz_forum_categories` as f + WHERE `forum_id` = :forum_id + '); + $getForum->bindValue('forum_id', $forumId); + + return $getForum->execute() ? $getForum->fetch() : []; +} + +function forum_get_root_categories(): array +{ + $dbc = Database::connection(); + + $categories = $dbc->query(' + SELECT + f.`forum_id`, f.`forum_name`, f.`forum_type`, + ( + SELECT COUNT(`forum_id`) + FROM `msz_forum_categories` as sf + WHERE sf.`forum_parent` = f.`forum_id` + ) as `forum_children` + FROM `msz_forum_categories` as f + WHERE f.`forum_parent` = 0 + AND f.`forum_type` = 1 + AND f.`forum_hidden` = false + ORDER BY f.`forum_order` + ')->fetchAll(); + + $categories = array_merge([MSZ_FORUM_ROOT_DATA], $categories); + + $categories[0]['forum_children'] = (int)$dbc->query(' + SELECT COUNT(`forum_id`) + FROM `msz_forum_categories` + WHERE `forum_parent` = ' . MSZ_FORUM_ROOT . ' + ')->fetchColumn(); + + return $categories; +} + function forum_get_breadcrumbs( int $forumId, string $linkFormat = '/forum/forum.php?f=%d', @@ -22,7 +97,7 @@ function forum_get_breadcrumbs( WHERE `forum_id` = :forum_id '); - while ($forumId > 0) { + while ($forumId > MSZ_FORUM_ROOT) { $getBreadcrumb->bindValue('forum_id', $forumId); $breadcrumb = $getBreadcrumb->execute() ? $getBreadcrumb->fetch() : []; @@ -43,9 +118,138 @@ function forum_increment_clicks(int $forumId): void UPDATE `msz_forum_categories` SET `forum_link_clicks` = `forum_link_clicks` + 1 WHERE `forum_id` = :forum_id - AND `forum_type` = 2 + AND `forum_type` = ' . MSZ_FORUM_TYPE_LINK . ' AND `forum_link_clicks` IS NOT NULL '); $incrementLinkClicks->bindValue('forum_id', $forumId); $incrementLinkClicks->execute(); } + +define('MSZ_FORUM_GET_CHILDREN_QUERY_SMALL', ' + SELECT + :user_id as `target_user_id`, + f.`forum_id`, f.`forum_name`, + ( + SELECT + `target_user_id` > 0 + AND + t.`topic_id` IS NOT NULL + AND + t.`topic_bumped` >= NOW() - INTERVAL 1 MONTH + AND ( + SELECT COUNT(ti.`topic_id`) < ( + SELECT COUNT(`topic_id`) + FROM `msz_forum_topics` + WHERE `forum_id` = f.`forum_id` + AND `topic_bumped` >= NOW() - INTERVAL 1 MONTH + AND `topic_deleted` IS NULL + ) + FROM `msz_forum_topics_track` as tt + RIGHT JOIN `msz_forum_topics` as ti + ON ti.`topic_id` = tt.`topic_id` + WHERE ti.`forum_id` = f.`forum_id` + AND tt.`user_id` = `target_user_id` + AND `track_last_read` >= `topic_bumped` + ) + ) as `forum_unread` + FROM `msz_forum_categories` as f + LEFT JOIN `msz_forum_topics` as t + ON t.`topic_id` = ( + SELECT `topic_id` + FROM `msz_forum_topics` + WHERE `forum_id` = f.`forum_id` + AND `topic_deleted` IS NULL + ORDER BY `topic_bumped` DESC + LIMIT 1 + ) + WHERE `forum_parent` = :parent_id + AND `forum_hidden` = false + ORDER BY f.`forum_order` +'); +define('MSZ_FORUM_GET_CHILDREN_QUERY_STANDARD', ' + SELECT + :user_id as `target_user_id`, + f.`forum_id`, f.`forum_name`, f.`forum_description`, f.`forum_type`, + f.`forum_link`, f.`forum_link_clicks`, f.`forum_archived`, + t.`topic_id` as `recent_topic_id`, p.`post_id` as `recent_post_id`, + t.`topic_title` as `recent_topic_title`, t.`topic_bumped` as `recent_topic_bumped`, + p.`post_created` as `recent_post_created`, + u.`user_id` as `recent_post_user_id`, + u.`username` as `recent_post_username`, + COALESCE(r.`role_colour`, CAST(0x40000000 AS UNSIGNED)) as `recent_post_user_colour`, + ( + SELECT COUNT(`topic_id`) + FROM `msz_forum_topics` + WHERE `forum_id` = f.`forum_id` + ) as `forum_topic_count`, + ( + SELECT COUNT(`post_id`) + FROM `msz_forum_posts` + WHERE `forum_id` = f.`forum_id` + ) as `forum_post_count`, + ( + SELECT + `target_user_id` > 0 + AND + `recent_topic_id` IS NOT NULL + AND + `recent_topic_bumped` >= NOW() - INTERVAL 1 MONTH + AND ( + SELECT COUNT(ti.`topic_id`) < ( + SELECT COUNT(`topic_id`) + FROM `msz_forum_topics` + WHERE `forum_id` = f.`forum_id` + AND `topic_bumped` >= NOW() - INTERVAL 1 MONTH + AND `topic_deleted` IS NULL + ) + FROM `msz_forum_topics_track` as tt + RIGHT JOIN `msz_forum_topics` as ti + ON ti.`topic_id` = tt.`topic_id` + WHERE ti.`forum_id` = f.`forum_id` + AND tt.`user_id` = `target_user_id` + AND `track_last_read` >= `topic_bumped` + ) + ) as `forum_unread` + FROM `msz_forum_categories` as f + LEFT JOIN `msz_forum_topics` as t + ON t.`topic_id` = ( + SELECT `topic_id` + FROM `msz_forum_topics` + WHERE `forum_id` = f.`forum_id` + AND `topic_deleted` IS NULL + ORDER BY `topic_bumped` DESC + LIMIT 1 + ) + LEFT JOIN `msz_forum_posts` as p + ON p.`post_id` = ( + SELECT `post_id` + FROM `msz_forum_posts` + WHERE `topic_id` = t.`topic_id` + ORDER BY `post_id` DESC + LIMIT 1 + ) + LEFT JOIN `msz_users` as u + ON u.`user_id` = p.`user_id` + LEFT JOIN `msz_roles` as r + ON r.`role_id` = u.`display_role` + WHERE f.`forum_parent` = :parent_id + AND f.`forum_hidden` = false + AND ( + (f.`forum_parent` = ' . MSZ_FORUM_ROOT . ' AND f.`forum_type` != ' . MSZ_FORUM_TYPE_CATEGORY . ') + OR f.`forum_parent` != ' . MSZ_FORUM_ROOT . ' + ) + ORDER BY f.`forum_order` +'); + +function forum_get_children(int $parentId, int $userId, bool $small = false): array +{ + $getListing = Database::connection()->prepare( + $small + ? MSZ_FORUM_GET_CHILDREN_QUERY_SMALL + : MSZ_FORUM_GET_CHILDREN_QUERY_STANDARD + ); + $getListing->bindValue('user_id', $userId); + $getListing->bindValue('parent_id', $parentId); + + return $getListing->execute() ? $getListing->fetchAll() : []; +} diff --git a/src/Forum/post.php b/src/Forum/post.php index 14336b3c..3ac50bc9 100644 --- a/src/Forum/post.php +++ b/src/Forum/post.php @@ -47,3 +47,40 @@ function forum_post_find(int $postId): array return $getPostInfo->execute() ? $getPostInfo->fetch() : false; } + +define('MSZ_FORUM_POST_LISTING_QUERY_STANDARD', ' + SELECT + p.`post_id`, p.`post_text`, p.`post_created`, + p.`topic_id`, + u.`user_id` as `poster_id`, + u.`username` as `poster_name`, + u.`created_at` as `poster_joined`, + COALESCE(r.`role_colour`, CAST(0x40000000 AS UNSIGNED)) as `poster_colour` + FROM `msz_forum_posts` as p + LEFT JOIN `msz_users` as u + ON u.`user_id` = p.`user_id` + LEFT JOIN `msz_roles` as r + ON r.`role_id` = u.`display_role` + WHERE `topic_id` = :topic_id + AND `post_deleted` IS NULL + ORDER BY `post_id` +'); +define('MSZ_FORUM_POST_LISTING_QUERY_PAGINATED', MSZ_FORUM_POST_LISTING_QUERY_STANDARD . ' LIMIT :offset, :take'); + +function forum_post_listing(int $topicId, int $offset = 0, int $take = 0): array +{ + $hasPagination = $offset >= 0 && $take > 0; + $getPosts = Database::connection()->prepare( + $hasPagination + ? MSZ_FORUM_POST_LISTING_QUERY_PAGINATED + : MSZ_FORUM_POST_LISTING_QUERY_STANDARD + ); + $getPosts->bindValue('topic_id', $topicId); + + if ($hasPagination) { + $getPosts->bindValue('offset', $offset); + $getPosts->bindValue('take', $take); + } + + return $getPosts->execute() ? $getPosts->fetchAll() : []; +} diff --git a/src/Forum/topic.php b/src/Forum/topic.php index 67a8e4f3..23ac680f 100644 --- a/src/Forum/topic.php +++ b/src/Forum/topic.php @@ -27,6 +27,33 @@ function forum_topic_create(int $forumId, int $userId, string $title): int return $createTopic->execute() ? (int)$dbc->lastInsertId() : 0; } +function forum_topic_fetch(int $topicId): array +{ + $getTopic = Database::connection()->prepare(' + SELECT + t.`topic_id`, t.`forum_id`, t.`topic_title`, t.`topic_type`, t.`topic_locked`, + f.`forum_archived` as `topic_archived`, + ( + SELECT MIN(`post_id`) + FROM `msz_forum_posts` + WHERE `topic_id` = t.`topic_id` + ) as `topic_first_post_id`, + ( + SELECT COUNT(`post_id`) + FROM `msz_forum_posts` + WHERE `topic_id` = t.`topic_id` + ) as `topic_post_count` + FROM `msz_forum_topics` as t + LEFT JOIN `msz_forum_categories` as f + ON f.`forum_id` = t.`forum_id` + WHERE t.`topic_id` = :topic_id + AND t.`topic_deleted` IS NULL + '); + $getTopic->bindValue('topic_id', $topicId); + + return $getTopic->execute() ? $getTopic->fetch() : []; +} + function forum_topic_bump(int $topicId): bool { $bumpTopic = Database::connection()->prepare(' @@ -37,3 +64,99 @@ function forum_topic_bump(int $topicId): bool $bumpTopic->bindValue('topic_id', $topicId); return $bumpTopic->execute(); } + +function forum_topic_mark_read(int $userId, int $topicId, int $forumId): void +{ + if ($userId < 1) { + return; + } + + $markAsRead = Database::connection()->prepare(' + REPLACE INTO `msz_forum_topics_track` + (`user_id`, `topic_id`, `forum_id`, `track_last_read`) + VALUES + (:user_id, :topic_id, :forum_id, NOW()) + '); + $markAsRead->bindValue('user_id', $userId); + $markAsRead->bindValue('topic_id', $topicId); + $markAsRead->bindValue('forum_id', $forumId); + $markAsRead->execute(); +} + +define('MSZ_TOPIC_LISTING_QUERY_STANDARD', ' + SELECT + :user_id as `target_user_id`, + t.`topic_id`, t.`topic_title`, t.`topic_locked`, t.`topic_type`, t.`topic_created`, + au.`user_id` as `author_id`, au.`username` as `author_name`, + COALESCE(ar.`role_colour`, CAST(0x40000000 AS UNSIGNED)) as `author_colour`, + lp.`post_id` as `response_id`, + lp.`post_created` as `response_created`, + lu.`user_id` as `respondent_id`, + lu.`username` as `respondent_name`, + COALESCE(lr.`role_colour`, CAST(0x40000000 AS UNSIGNED)) as `respondent_colour`, + ( + SELECT COUNT(`post_id`) + FROM `msz_forum_posts` + WHERE `topic_id` = t.`topic_id` + ) as `topic_post_count`, + ( + SELECT COUNT(`user_id`) + FROM `msz_forum_topics_track` + WHERE `topic_id` = t.`topic_id` + ) as `topic_view_count`, + ( + SELECT + `target_user_id` > 0 + AND + t.`topic_bumped` > NOW() - INTERVAL 1 MONTH + AND ( + SELECT COUNT(ti.`topic_id`) < 1 + FROM `msz_forum_topics_track` as tt + RIGHT JOIN `msz_forum_topics` as ti + ON ti.`topic_id` = tt.`topic_id` + WHERE ti.`topic_id` = t.`topic_id` + AND tt.`user_id` = `target_user_id` + AND `track_last_read` >= `topic_bumped` + ) + ) as `topic_unread` + FROM `msz_forum_topics` as t + LEFT JOIN `msz_users` as au + ON t.`user_id` = au.`user_id` + LEFT JOIN `msz_roles` as ar + ON ar.`role_id` = au.`display_role` + LEFT JOIN `msz_forum_posts` as lp + ON lp.`post_id` = ( + SELECT `post_id` + FROM `msz_forum_posts` + WHERE `topic_id` = t.`topic_id` + ORDER BY `post_id` DESC + LIMIT 1 + ) + LEFT JOIN `msz_users` as lu + ON lu.`user_id` = lp.`user_id` + LEFT JOIN `msz_roles` as lr + ON lr.`role_id` = lu.`display_role` + WHERE t.`forum_id` = :forum_id + AND t.`topic_deleted` IS NULL + ORDER BY t.`topic_type` DESC, t.`topic_bumped` DESC +'); +define('MSZ_TOPIC_LISTING_QUERY_PAGINATED', MSZ_TOPIC_LISTING_QUERY_STANDARD . ' LIMIT :offset, :take'); + +function forum_topic_listing(int $forumId, int $userId, int $offset = 0, int $take = 0): array +{ + $hasPagination = $offset >= 0 && $take > 0; + $getTopics = Database::connection()->prepare( + $hasPagination + ? MSZ_TOPIC_LISTING_QUERY_PAGINATED + : MSZ_TOPIC_LISTING_QUERY_STANDARD + ); + $getTopics->bindValue('forum_id', $forumId); + $getTopics->bindValue('user_id', $userId); + + if ($hasPagination) { + $getTopics->bindValue('offset', $offset); + $getTopics->bindValue('take', $take); + } + + return $getTopics->execute() ? $getTopics->fetchAll() : []; +} diff --git a/views/mio/forum/macros.twig b/views/mio/forum/macros.twig index 7aab63f5..0736a361 100644 --- a/views/mio/forum/macros.twig +++ b/views/mio/forum/macros.twig @@ -23,9 +23,9 @@ {% endmacro %} -{% macro forum_category_entry(forum, forum_type, forum_read, forum_icon) %} +{% macro forum_category_entry(forum, forum_unread, forum_type, forum_icon) %} {% set forum_type = forum_type|default(null) %} - {% set forum_read = forum_read|default(true) ? 'read' : 'unread' %} + {% set forum_unread = forum_unread|default(forum.forum_unread|default(false)) ? 'unread' : 'read' %} {% set forum_icon = forum_icon|default('https://static.flash.moe/images/forum-icons/forum-%s-%s.png') %} {% if forum_type is null %} @@ -43,7 +43,7 @@ {% endif %}
- read + {{ forum_unread }}
@@ -57,7 +57,10 @@ {% if forum.forum_subforums is defined and forum.forum_subforums|length > 0 %}
{% for subforum in forum.forum_subforums %} - {{ subforum.forum_name }} + + {{ subforum.forum_name }} + {% endfor %}
{% endif %} @@ -158,9 +161,9 @@
{% endmacro %} -{% macro forum_topic_entry(topic, topic_type, topic_read, topic_icon) %} +{% macro forum_topic_entry(topic, topic_type, topic_unread, topic_icon) %} {% set topic_type = topic_type|default(null) %} - {% set topic_read = topic_read|default(true) ? 'read' : 'unread' %} + {% set topic_unread = topic_unread|default(topic.topic_unread|default(false)) ? 'unread' : 'read' %} {% set topic_icon = topic_icon|default('https://static.flash.moe/images/topic-icons/topic-%s-%s.png') %} {% if topic_type is null %} @@ -180,10 +183,10 @@ {% endif %}
- read + {{ topic_unread }}