diff --git a/assets/less/mio/classes/comment.less b/assets/less/mio/classes/comment.less index c814e0e3..b751f14a 100644 --- a/assets/less/mio/classes/comment.less +++ b/assets/less/mio/classes/comment.less @@ -67,6 +67,10 @@ transition: opacity .2s; } + &--voted { + font-weight: 700; + } + &__checkbox { vertical-align: text-top; margin-right: 2px; diff --git a/database/2018_07_25_194106_add_global_comments_stuff.php b/database/2018_07_25_194106_add_global_comments_stuff.php index 8aff4023..95db40a5 100644 --- a/database/2018_07_25_194106_add_global_comments_stuff.php +++ b/database/2018_07_25_194106_add_global_comments_stuff.php @@ -70,10 +70,40 @@ function migrate_up(PDO $conn): void ON DELETE CASCADE ); "); + + $conn->exec(" + ALTER TABLE `msz_news_posts` + ADD COLUMN `comment_section_id` INT UNSIGNED NULL DEFAULT NULL AFTER `deleted_at`, + ADD INDEX `news_posts_comment_section` (`comment_section_id`), + ADD CONSTRAINT `news_posts_comment_section` + FOREIGN KEY (`comment_section_id`) + REFERENCES `msz_comments_categories` (`category_id`) + ON UPDATE CASCADE + ON DELETE SET NULL; + "); + + // create a comment section for all news posts + $getNews = $conn->query('SELECT `post_id` FROM `msz_news_posts` WHERE `comment_section_id` IS NULL') + ->fetchAll(PDO::FETCH_ASSOC); + $setNews = $conn->prepare('UPDATE `msz_news_posts` SET `comment_section_id` = :c WHERE `post_id` = :p'); + + foreach ($getNews as $post) { + $info = comments_category_create("news-{$post['post_id']}"); + $setNews->execute([ + 'p' => $post['post_id'], + 'c' => $info['category_id'], + ]); + } } function migrate_down(PDO $conn): void { + $conn->exec(' + ALTER TABLE `msz_news_posts` + DROP COLUMN `comment_section_id`, + DROP INDEX `news_posts_comment_section`, + DROP FOREIGN KEY `news_posts_comment_section`; + '); $conn->exec('DROP TABLE `msz_comments_votes`'); $conn->exec('DROP TABLE `msz_comments_posts`'); $conn->exec('DROP TABLE `msz_comments_categories`'); diff --git a/public/changelog.php b/public/changelog.php index 7fb7e6da..d73c5549 100644 --- a/public/changelog.php +++ b/public/changelog.php @@ -64,7 +64,7 @@ if ($changelogChange > 0) { "changelog-date-{$change['change_date']}", true ), - 'comments' => comments_category_get($commentsCategory['category_id']), + 'comments' => comments_category_get($commentsCategory['category_id'], $app->getUserId()), ]); return; } @@ -90,7 +90,7 @@ if (!$changes) { if (!empty($changelogDate)) { $tpl->vars([ 'comments_category' => $commentsCategory = comments_category_info("changelog-date-{$changelogDate}", true), - 'comments' => comments_category_get($commentsCategory['category_id']), + 'comments' => comments_category_get($commentsCategory['category_id'], $app->getUserId()), ]); } diff --git a/public/comments.php b/public/comments.php index 0a019ccb..432844ff 100644 --- a/public/comments.php +++ b/public/comments.php @@ -3,11 +3,16 @@ use Misuzu\Database; require_once __DIR__ . '/../misuzu.php'; -// if false, display informational pages instead of outputting json. -$isXHR = !empty($_SERVER['HTTP_MISUZU_XHR_REQUEST']); +// basing whether or not this is an xhr request on whether a referrer header is present +// this page is never directy accessed, under normal circumstances +$redirect = !empty($_SERVER['HTTP_REFERER']) && empty($_SERVER['HTTP_X_MISUZU_XHR']) ? $_SERVER['HTTP_REFERER'] : ''; +$isXHR = !$redirect; -if ($isXHR || $_SERVER['REQUEST_METHOD'] === 'GET') { +if ($isXHR) { header('Content-Type: application/json; charset=utf-8'); +} elseif (!is_local_url($redirect)) { + echo render_info('Possible request forgery detected.', 403); + return; } if ($app->getUserId() < 1) { @@ -15,38 +20,95 @@ if ($app->getUserId() < 1) { return; } -$redirect = !$isXHR && !empty($_SERVER['HTTP_REFERER']) - && is_local_url($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : ''; $commentPerms = comments_get_perms($app->getUserId()); -$commentId = (int)($_REQUEST['comment_id'] ?? 0); -if (isset($_POST['vote']) && array_key_exists((int)$_POST['vote'], MSZ_COMMENTS_VOTE_TYPES)) { - echo comments_vote_add( - $commentId, - $app->getUserId(), - MSZ_COMMENTS_VOTE_TYPES[(int)$_POST['vote']] - ); - return; -} +switch ($_GET['m'] ?? null) { + case 'vote': + $comment = (int)($_GET['c'] ?? 0); -switch ($_SERVER['REQUEST_METHOD']) { - case 'GET': - if ($commentId < 1) { - echo render_info_or_json(true, 'Missing data.', 400); + if ($comment < 1) { + echo render_info_or_json($isXHR, 'Missing data.', 400); break; } - switch ($_GET['fetch'] ?? '') { - case 'replies': - echo json_encode(comments_post_replies($commentId)); - break; + $vote = (int)($_GET['v'] ?? 0); - default: - echo json_encode(comments_post_get($commentId)); + if (!array_key_exists($vote, MSZ_COMMENTS_VOTE_TYPES)) { + echo render_info_or_json($isXHR, 'Invalid vote action.', 400); + break; } + + $vote = MSZ_COMMENTS_VOTE_TYPES[(int)($_GET['v'] ?? 0)]; + $voteResult = comments_vote_add( + $comment, + $app->getUserId(), + $vote + ); + + if (!$isXHR) { + header('Location: ' . $redirect . '#comment-' . $comment); + break; + } + + echo '[]'; // we don't really need a answer for this, the client implicitly does all + break; +/* + case 'delete': + if ($commentId < 1) { + echo render_info_or_json($isXHR, 'Missing data.', 400); + break; + } + + if (!$commentPerms['can_delete']) { + echo render_info_or_json($isXHR, "You're not allowed to delete comments.", 403); + break; + } + + if (!$commentPerms['can_delete_any'] + && !comments_post_check_ownership($commentId, $app->getUserId())) { + echo render_info_or_json($isXHR, "You're not allowed to delete comments made by others.", 403); + break; + } + + if (!comments_post_delete($commentId)) { + echo render_info_or_json($isXHR, 'Failed to delete comment.', 500); + break; + } + + if ($redirect) { + header('Location: ' . $redirect); + break; + } + + echo render_info_or_json($isXHR, 'Comment deleted.'); break; - case 'POST': + case 'edit': + if ($commentId < 1) { + echo render_info_or_json($isXHR, 'Missing data.', 400); + break; + } + + if (!$commentPerms['can_edit']) { + echo render_info_or_json($isXHR, "You're not allowed to edit comments.", 403); + break; + } + + if (!$commentPerms['can_edit_any'] + && !comments_post_check_ownership($commentId, $app->getUserId())) { + echo render_info_or_json($isXHR, "You're not allowed to delete comments made by others.", 403); + break; + } + + if ($redirect) { + header('Location: ' . $redirect . '#comment-' . $commentId); + break; + } + + var_dump($_POST); + break; +*/ + case 'create': if (!$commentPerms['can_comment']) { echo render_info_or_json($isXHR, "You're not allowed to post comments.", 403); break; @@ -119,63 +181,6 @@ switch ($_SERVER['REQUEST_METHOD']) { echo json_encode(comments_post_get($commentId)); break; - case 'PATCH': - method_patch: - if ($commentId < 1) { - echo render_info_or_json($isXHR, 'Missing data.', 400); - break; - } - - if (!$commentPerms['can_edit']) { - echo render_info_or_json($isXHR, "You're not allowed to edit comments.", 403); - break; - } - - if (!$commentPerms['can_edit_any'] - && !comments_post_check_ownership($commentId, $app->getUserId())) { - echo render_info_or_json($isXHR, "You're not allowed to delete comments made by others.", 403); - break; - } - - if ($redirect) { - header('Location: ' . $redirect . '#comment-' . $commentId); - break; - } - - var_dump($_POST); - break; - - case 'DELETE': - method_delete: - if ($commentId < 1) { - echo render_info_or_json($isXHR, 'Missing data.', 400); - break; - } - - if (!$commentPerms['can_delete']) { - echo render_info_or_json($isXHR, "You're not allowed to delete comments.", 403); - break; - } - - if (!$commentPerms['can_delete_any'] - && !comments_post_check_ownership($commentId, $app->getUserId())) { - echo render_info_or_json($isXHR, "You're not allowed to delete comments made by others.", 403); - break; - } - - if (!comments_post_delete($commentId)) { - echo render_info_or_json($isXHR, 'Failed to delete comment.', 500); - break; - } - - if ($redirect) { - header('Location: ' . $redirect); - break; - } - - echo render_info_or_json($isXHR, 'Comment deleted.'); - break; - default: - echo render_info_or_json($isXHR, 'Invalid request method.', 405); + echo render_info_or_json($isXHR, 'Not found.', 404); } diff --git a/public/index.php b/public/index.php index 1aebba31..8812eb89 100644 --- a/public/index.php +++ b/public/index.php @@ -22,7 +22,12 @@ $news = Database::query(' SELECT p.`post_id`, p.`post_title`, p.`post_text`, p.`created_at`, u.`user_id`, u.`username`, - COALESCE(u.`user_colour`, r.`role_colour`) as `user_colour` + COALESCE(u.`user_colour`, r.`role_colour`) as `user_colour`, + ( + SELECT COUNT(`comment_id`) + FROM `msz_comments_posts` + WHERE `category_id` = `comment_section_id` + ) as `post_comments` FROM `msz_news_posts` as p LEFT JOIN `msz_users` as u ON p.`user_id` = u.`user_id` diff --git a/public/news.php b/public/news.php index 01371f1e..36666af8 100644 --- a/public/news.php +++ b/public/news.php @@ -18,7 +18,7 @@ $templating->vars([ if ($postId !== null) { $getPost = Database::prepare(' SELECT - p.`post_id`, p.`post_title`, p.`post_text`, p.`created_at`, + p.`post_id`, p.`post_title`, p.`post_text`, p.`created_at`, p.`comment_section_id`, c.`category_id`, c.`category_name`, u.`user_id`, u.`username`, COALESCE(u.`user_colour`, r.`role_colour`) as `user_colour` @@ -39,7 +39,30 @@ if ($postId !== null) { return; } - echo $templating->render('news.post', compact('post')); + if ($post['comment_section_id'] === null) { + $commentsInfo = comments_category_create("news-{$post['post_id']}"); + + if ($commentsInfo) { + $post['comment_section_id'] = $commentsInfo['category_id']; + Database::prepare(' + UPDATE `msz_news_posts` + SET `comment_section_id` = :comment_section_id + WHERE `post_id` = :post_id + ')->execute([ + 'comment_section_id' => $post['comment_section_id'], + 'post_id' => $post['post_id'], + ]); + } + } else { + $commentsInfo = comments_category_info($post['comment_section_id']); + } + + echo $templating->render('news.post', [ + 'post' => $post, + 'comments_perms' => comments_get_perms($app->getUserId()), + 'comments_category' => $commentsInfo, + 'comments' => comments_category_get($commentsInfo['category_id'], $app->getUserId()), + ]); return; } diff --git a/src/comments.php b/src/comments.php index d375ede7..d0686159 100644 --- a/src/comments.php +++ b/src/comments.php @@ -125,7 +125,25 @@ define('MSZ_COMMENTS_CATEGORY_QUERY', ' p.`comment_id`, p.`comment_text`, p.`comment_reply_to`, p.`comment_created`, p.`comment_pinned`, u.`user_id`, u.`username`, - COALESCE(u.`user_colour`, r.`role_colour`) as `user_colour` + COALESCE(u.`user_colour`, r.`role_colour`) as `user_colour`, + ( + SELECT COUNT(`comment_id`) + FROM `msz_comments_votes` + WHERE `comment_id` = p.`comment_id` + AND `comment_vote` = \'Like\' + ) as `comment_likes`, + ( + SELECT COUNT(`comment_id`) + FROM `msz_comments_votes` + WHERE `comment_id` = p.`comment_id` + AND `comment_vote` = \'Dislike\' + ) as `comment_dislikes`, + ( + SELECT `comment_vote` + FROM `msz_comments_votes` + WHERE `comment_id` = p.`comment_id` + AND `user_id` = :user + ) as `comment_user_vote` FROM `msz_comments_posts` as p LEFT JOIN `msz_users` as u ON u.`user_id` = p.`user_id` @@ -147,7 +165,7 @@ define('MSZ_COMMENTS_CATEGORY_QUERY_REPLIES', sprintf( )); // heavily recursive -function comments_category_get(int $category, ?int $parent = null): array +function comments_category_get(int $category, int $user, ?int $parent = null): array { if ($parent !== null) { $getComments = Database::prepare(MSZ_COMMENTS_CATEGORY_QUERY_REPLIES); @@ -156,12 +174,13 @@ function comments_category_get(int $category, ?int $parent = null): array $getComments = Database::prepare(MSZ_COMMENTS_CATEGORY_QUERY_ROOT); } + $getComments->bindValue('user', $user); $getComments->bindValue('category', $category); $comments = $getComments->execute() ? $getComments->fetchAll(PDO::FETCH_ASSOC) : []; $commentsCount = count($comments); for ($i = 0; $i < $commentsCount; $i++) { - $comments[$i]['comment_replies'] = comments_category_get($category, $comments[$i]['comment_id']); + $comments[$i]['comment_replies'] = comments_category_get($category, $user, $comments[$i]['comment_id']); } return $comments; diff --git a/views/mio/_layout/comments.twig b/views/mio/_layout/comments.twig index d647f5f5..5678f287 100644 --- a/views/mio/_layout/comments.twig +++ b/views/mio/_layout/comments.twig @@ -2,7 +2,7 @@ {% set reply_mode = reply_to is not null %}
@@ -85,6 +85,23 @@ {% endif %} + + {% if perms.can_comment %} {% endif %} @@ -167,15 +229,27 @@
{% if perms.can_vote %} - Like - Dislike + + Like + {% if comment.comment_likes > 0 %} + ({{ comment.comment_likes|number_format }}) + {% endif %} + + + Dislike + {% if comment.comment_dislikes > 0 %} + ({{ comment.comment_dislikes|number_format }}) + {% endif %} + {% endif %} {% if perms.can_comment %} {% endif %} - {% if user is not null %} + {# if user is not null %} Report - {% endif %} + {% endif #}
diff --git a/views/mio/news/macros.twig b/views/mio/news/macros.twig index 3cfdbb2a..6b79409a 100644 --- a/views/mio/news/macros.twig +++ b/views/mio/news/macros.twig @@ -22,6 +22,16 @@
{% endif %} + + + {% if post.post_comments < 1 %} + No comments + {% elseif post.post_comments == 1 %} + 1 comment + {% else %} + {{ post.post_comments }} comments + {% endif %} + diff --git a/views/mio/news/post.twig b/views/mio/news/post.twig index bb44af42..309d2a0e 100644 --- a/views/mio/news/post.twig +++ b/views/mio/news/post.twig @@ -1,4 +1,5 @@ {% extends '@mio/news/master.twig' %} +{% from '@mio/_layout/comments.twig' import comments_section %} {% set title = post.post_title ~ ' :: News' %} {% set canonical_url = '/news.php?p=' ~ post.post_id %} @@ -54,10 +55,12 @@ -
-
Comments
-
- Eventually™ + {% if comments is defined %} +
+
+ Comments +
+ {{ comments_section(comments, comments_category, current_user|default(null), comments_perms) }}
-
+ {% endif %} {% endblock %}