diff --git a/assets/less/mio/classes/forum/listing.less b/assets/less/mio/classes/forum/listing.less index 7c0a96c1..2a99de33 100644 --- a/assets/less/mio/classes/forum/listing.less +++ b/assets/less/mio/classes/forum/listing.less @@ -5,6 +5,10 @@ margin: 0; } + &__none { + padding: 2px 5px; + } + &__entry { display: flex; padding: 2px 0; @@ -16,8 +20,6 @@ &__icon { width: 50px; - line-height: 50px; - font-size: 2.5em; text-align: center; flex-grow: 0; flex-shrink: 0; @@ -26,6 +28,7 @@ &__info { flex-grow: 1; flex-shrink: 1; + padding-left: 6px; } &__title { diff --git a/assets/less/mio/classes/forum/topics.less b/assets/less/mio/classes/forum/topics.less new file mode 100644 index 00000000..efb03206 --- /dev/null +++ b/assets/less/mio/classes/forum/topics.less @@ -0,0 +1,3 @@ +.forum__topics { + // +} diff --git a/assets/less/mio/classes/navigation.less b/assets/less/mio/classes/navigation.less index 0fb10cb4..5fc05b2e 100644 --- a/assets/less/mio/classes/navigation.less +++ b/assets/less/mio/classes/navigation.less @@ -17,6 +17,16 @@ flex-direction: column; } + &--left { + justify-content: left; + padding-left: 25px; + } + + &--right { + justify-content: right; + padding-right: 25px; + } + &--top { border-top-width: 0; border-bottom-width: 1px; diff --git a/database/2018_05_16_155840_initial_structure.php b/database/2018_05_16_155840_initial_structure.php index 873311b3..519867aa 100644 --- a/database/2018_05_16_155840_initial_structure.php +++ b/database/2018_05_16_155840_initial_structure.php @@ -2,7 +2,6 @@ namespace Misuzu\DatabaseMigrations\InitialStructure; use PDO; -use Misuzu\Database; function migrate_up(PDO $conn): void { diff --git a/database/2018_05_17_000055_forum_structure.php b/database/2018_05_17_000055_forum_structure.php new file mode 100644 index 00000000..1bc51d9b --- /dev/null +++ b/database/2018_05_17_000055_forum_structure.php @@ -0,0 +1,135 @@ +exec(" + CREATE TABLE `msz_forum_categories` ( + `forum_id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `forum_order` INT(10) UNSIGNED NOT NULL DEFAULT '1', + `forum_parent` INT(10) UNSIGNED NOT NULL DEFAULT '0', + `forum_name` VARCHAR(255) NOT NULL, + `forum_type` TINYINT(4) NOT NULL DEFAULT '0', + `forum_description` TEXT NULL, + `forum_link` VARCHAR(255) NULL DEFAULT NULL, + `forum_created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `forum_archived` TINYINT(1) NOT NULL DEFAULT '0', + `forum_hidden` TINYINT(1) NOT NULL DEFAULT '0', + PRIMARY KEY (`forum_id`), + INDEX `forums_indices` (`forum_order`, `forum_parent`, `forum_type`) + ) + "); + + $conn->exec(" + CREATE TABLE `msz_forum_topics` ( + `topic_id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `forum_id` INT(10) UNSIGNED NOT NULL, + `user_id` INT(10) UNSIGNED NULL DEFAULT NULL, + `topic_title` VARCHAR(255) NOT NULL, + `topic_created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `topic_bumped` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `topic_deleted` TIMESTAMP NULL DEFAULT NULL, + `topic_view_count` INT(10) NOT NULL DEFAULT '0', + `topic_type` TINYINT(4) NOT NULL DEFAULT '0', + `topic_status` TINYINT(4) NOT NULL DEFAULT '0', + PRIMARY KEY (`topic_id`), + INDEX `topics_forum_id_foreign` (`forum_id`), + INDEX `topics_user_id_foreign` (`user_id`), + INDEX `topics_indices` (`topic_bumped`, `topic_type`), + CONSTRAINT `topics_forum_id_foreign` + FOREIGN KEY (`forum_id`) + REFERENCES `msz_forum_categories` (`forum_id`) + ON UPDATE CASCADE + ON DELETE CASCADE, + CONSTRAINT `topics_user_id_foreign` + FOREIGN KEY (`user_id`) + REFERENCES `msz_users` (`user_id`) + ON UPDATE CASCADE + ON DELETE CASCADE + ) + "); + + $conn->exec(" + CREATE TABLE `msz_forum_posts` ( + `post_id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT, + `topic_id` INT(10) UNSIGNED NOT NULL, + `forum_id` INT(10) UNSIGNED NOT NULL, + `user_id` INT(10) UNSIGNED NULL DEFAULT NULL, + `post_title` VARCHAR(255) NOT NULL, + `post_ip` BLOB NOT NULL, + `post_created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `post_edited` TIMESTAMP NULL DEFAULT NULL, + `post_deleted` TIMESTAMP NULL DEFAULT NULL, + PRIMARY KEY (`post_id`), + INDEX `posts_topic_id_foreign` (`topic_id`), + INDEX `posts_forum_id_foreign` (`forum_id`), + INDEX `posts_user_id_foreign` (`user_id`), + INDEX `posts_indices` (`post_created`), + CONSTRAINT `posts_topic_id_foreign` + FOREIGN KEY (`topic_id`) + REFERENCES `msz_forum_topics` (`topic_id`) + ON UPDATE CASCADE + ON DELETE CASCADE, + CONSTRAINT `posts_forum_id_foreign` + FOREIGN KEY (`forum_id`) + REFERENCES `msz_forum_categories` (`forum_id`) + ON UPDATE CASCADE + ON DELETE CASCADE, + CONSTRAINT `posts_user_id_foreign` + FOREIGN KEY (`user_id`) + REFERENCES `msz_users` (`user_id`) + ON UPDATE CASCADE + ON DELETE SET NULL + ) + "); + + $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, + `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`), + CONSTRAINT `topics_track_topic_id_foreign` + FOREIGN KEY (`topic_id`) + REFERENCES `msz_forum_topics` (`topic_id`) + ON UPDATE CASCADE + ON DELETE CASCADE, + CONSTRAINT `topics_track_user_id_foreign` + FOREIGN KEY (`user_id`) + REFERENCES `msz_users` (`user_id`) + ON UPDATE CASCADE + ON DELETE CASCADE + ) + "); +} + +function migrate_down(PDO $conn): void +{ + $conn->exec('DROP TABLE `msz_forum_topics_track`'); + $conn->exec('DROP TABLE `msz_forum_posts`'); + $conn->exec('DROP TABLE `msz_forum_topics`'); + $conn->exec('DROP TABLE `msz_forum_categories`'); +} diff --git a/public/forum/forum.php b/public/forum/forum.php new file mode 100644 index 00000000..eb4e848b --- /dev/null +++ b/public/forum/forum.php @@ -0,0 +1,130 @@ +getTemplating(); + +if ($forumId > 0) { + $getForum = $db->prepare(' + SELECT + `forum_id`, `forum_name`, `forum_type`, `forum_link`, `forum_parent` + FROM `msz_forum_categories` + WHERE `forum_id` = :forum_id + '); + $getForum->bindValue('forum_id', $forumId); + $forum = $getForum->execute() ? $getForum->fetch() : []; +} + +if (empty($forum) || ($forum['forum_type'] == 2 && empty($forum['forum_link']))) { + http_response_code(404); + echo $templating->render('errors.404'); + return; +} + +if ($forum['forum_type'] == 2) { + header('Location: ' . $forum['forum_link']); + return; +} + +// declare this, templating engine assumes it exists +$topics = []; + +// no need to fetch topics for categories (or links but we're already done with those at this point) +if ($forum['forum_type'] == 0) { + $getTopics = $db->prepare(' + SELECT + t.`topic_id`, t.`topic_title`, t.`topic_view_count`, + au.`user_id` as `author_id`, au.`username` as `author_name`, + COUNT(p.`post_id`) as `topic_post_count`, + MIN(p.`post_id`) as `topic_first_post_id`, + MAX(p.`post_id`) as `topic_last_post_id`, + COALESCE(ar.`role_colour`, CAST(0x40000000 AS UNSIGNED)) as `author_colour` + 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 p + ON t.`topic_id` = p.`topic_id` + WHERE t.`forum_id` = :forum_id + AND t.`topic_deleted` IS NULL + GROUP BY t.`topic_id` + ORDER BY t.`topic_type`, t.`topic_bumped` + '); + $getTopics->bindValue('forum_id', $forum['forum_id']); + $topics = $getTopics->execute() ? $getTopics->fetchAll() : $topics; +} + +$getSubforums = $db->prepare(' + SELECT + `forum_id`, `forum_name`, `forum_description`, `forum_type`, `forum_link`, + ( + 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 + 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() : []; + } +} + +$lastParent = $forum['forum_parent']; +$breadcrumbs = [$forum['forum_name'] => '/forum/forum.php?f=' . $forum['forum_id']]; +$getBreadcrumb = $db->prepare(' + SELECT `forum_id`, `forum_name`, `forum_parent` + FROM `msz_forum_categories` + WHERE `forum_id` = :forum_id +'); + +while ($lastParent > 0) { + $getBreadcrumb->bindValue('forum_id', $lastParent); + + if (!$getBreadcrumb->execute()) { + break; + } + + $parentForum = $getBreadcrumb->fetch(); + + $breadcrumbs[$parentForum['forum_name']] = '/forum/forum.php?f=' . $parentForum['forum_id']; + $lastParent = $parentForum['forum_parent']; +} + +$breadcrumbs['Forums'] = '/forum/'; +$breadcrumbs = array_reverse($breadcrumbs); + +echo $app->getTemplating()->render('forum.forum', [ + 'forum_info' => $forum, + 'forum_breadcrumbs' => $breadcrumbs, + 'forum_topics' => $topics, +]); diff --git a/public/forum/index.php b/public/forum/index.php new file mode 100644 index 00000000..083f9e87 --- /dev/null +++ b/public/forum/index.php @@ -0,0 +1,73 @@ +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 + GROUP BY f.`forum_id` + 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`, + ( + 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 + 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` +'); + +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; + } + + $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']); + $categories[$key]['forum_subforums'][$skey]['forum_subforums'] + = $getSubCategories->execute() ? $getSubCategories->fetchAll() : []; + } +} + +$categories[0]['forum_children'] = count($categories[0]['forum_subforums']); + +echo $app->getTemplating()->render('forum.index', [ + 'forum_categories' => $categories, +]); diff --git a/public/forum/topic.php b/public/forum/topic.php new file mode 100644 index 00000000..cedc4248 --- /dev/null +++ b/public/forum/topic.php @@ -0,0 +1,6 @@ +getTemplating()->render('forum.topic'); diff --git a/public/index.php b/public/index.php index 07f08830..c1921365 100644 --- a/public/index.php +++ b/public/index.php @@ -1,6 +1,5 @@ - Create new Role + Create new Role