diff --git a/public/comments.php b/public/comments.php index 93a480d..e7064b8 100644 --- a/public/comments.php +++ b/public/comments.php @@ -1,12 +1,10 @@ isSilenced()) { return; } +$comments = $msz->getComments(); + $commentPerms = $currentUserInfo->commentPerms(); -$commentId = (int)filter_input(INPUT_GET, 'c', FILTER_SANITIZE_NUMBER_INT); -$commentMode = filter_input(INPUT_GET, 'm'); +$commentId = (string)filter_input(INPUT_GET, 'c', FILTER_SANITIZE_NUMBER_INT); +$commentMode = (string)filter_input(INPUT_GET, 'm'); $commentVote = (int)filter_input(INPUT_GET, 'v', FILTER_SANITIZE_NUMBER_INT); -if($commentId > 0) +if(!empty($commentId)) { try { - $commentInfo2 = CommentsPost::byId($commentId); - } catch(CommentsPostNotFoundException $ex) { + $commentInfo = $comments->getPostById($commentId); + } catch(RuntimeException $ex) { echo render_info('Post not found.', 404); return; } + $categoryInfo = $comments->getCategoryByPost($commentInfo); +} + +if($commentMode !== 'create' && empty($commentInfo)) { + echo render_error(400); + return; +} + switch($commentMode) { case 'pin': case 'unpin': - if(!$commentPerms['can_pin'] && !$commentInfo2->isOwner($currentUserInfo)) { + if(!$commentPerms['can_pin'] && !$categoryInfo->isOwner($currentUserInfo)) { echo render_info("You're not allowed to pin comments.", 403); break; } - if($commentInfo2->isDeleted()) { + if($commentInfo->isDeleted()) { echo render_info("This comment doesn't exist!", 400); break; } - if($commentInfo2->hasParent()) { + if($commentInfo->isReply()) { echo render_info("You can't pin replies!", 400); break; } $isPinning = $commentMode === 'pin'; - if($isPinning && $commentInfo2->isPinned()) { - echo render_info('This comment is already pinned.', 400); - break; - } elseif(!$isPinning && !$commentInfo2->isPinned()) { - echo render_info("This comment isn't pinned yet.", 400); - break; + if($isPinning) { + if($commentInfo->isPinned()) { + echo render_info('This comment is already pinned.', 400); + break; + } + + $comments->pinPost($commentInfo); + } else { + if(!$commentInfo->isPinned()) { + echo render_info("This comment isn't pinned yet.", 400); + break; + } + + $comments->unpinPost($commentInfo); } - $commentInfo2->setPinned($isPinning); - $commentInfo2->save(); - - redirect($redirect . '#comment-' . $commentInfo2->getId()); + redirect($redirect . '#comment-' . $commentInfo->getId()); break; case 'vote': - if(!$commentPerms['can_vote'] && !$commentInfo2->isOwner($currentUserInfo)) { + if(!$commentPerms['can_vote'] && !$categoryInfo->isOwner($currentUserInfo)) { echo render_info("You're not allowed to vote on comments.", 403); break; } - if($commentInfo2->isDeleted()) { + if($commentInfo->isDeleted()) { echo render_info("This comment doesn't exist!", 400); break; } if($commentVote > 0) - $commentInfo2->addPositiveVote($currentUserInfo); + $comments->addPostPositiveVote($commentInfo, $currentUserInfo); elseif($commentVote < 0) - $commentInfo2->addNegativeVote($currentUserInfo); + $comments->addPostNegativeVote($commentInfo, $currentUserInfo); else - $commentInfo2->removeVote($currentUserInfo); + $comments->removePostVote($commentInfo, $currentUserInfo); - redirect($redirect . '#comment-' . $commentInfo2->getId()); + redirect($redirect . '#comment-' . $commentInfo->getId()); break; case 'delete': - if(!$commentPerms['can_delete'] && !$commentInfo2->isOwner($currentUserInfo)) { + if(!$commentPerms['can_delete'] && !$categoryInfo->isOwner($currentUserInfo)) { echo render_info("You're not allowed to delete comments.", 403); break; } - if($commentInfo2->isDeleted()) { + if($commentInfo->isDeleted()) { echo render_info( $commentPerms['can_delete_any'] ? 'This comment is already marked for deletion.' : "This comment doesn't exist.", 400 @@ -125,7 +138,7 @@ switch($commentMode) { break; } - $isOwnComment = $commentInfo2->getUserId() === $currentUserInfo->getId(); + $isOwnComment = $commentInfo->getUserId() === (string)$currentUserInfo->getId(); $isModAction = $commentPerms['can_delete_any'] && !$isOwnComment; if(!$isModAction && !$isOwnComment) { @@ -133,17 +146,16 @@ switch($commentMode) { break; } - $commentInfo2->setDeleted(true); - $commentInfo2->save(); + $comments->deletePost($commentInfo); if($isModAction) { AuditLog::create(AuditLog::COMMENT_ENTRY_DELETE_MOD, [ - $commentInfo2->getId(), - $commentUserId = $commentInfo2->getUserId(), - ($commentUserId < 1 ? '(Deleted User)' : $commentInfo2->getUser()->getUsername()), + $commentInfo->getId(), + $commentUserId = $commentInfo->getUserId(), + '', ]); } else { - AuditLog::create(AuditLog::COMMENT_ENTRY_DELETE, [$commentInfo2->getId()]); + AuditLog::create(AuditLog::COMMENT_ENTRY_DELETE, [$commentInfo->getId()]); } redirect($redirect); @@ -155,25 +167,24 @@ switch($commentMode) { break; } - if(!$commentInfo2->isDeleted()) { + if(!$commentInfo->isDeleted()) { echo render_info("This comment isn't in a deleted state.", 400); break; } - $commentInfo2->setDeleted(false); - $commentInfo2->save(); + $comments->restorePost($commentInfo); AuditLog::create(AuditLog::COMMENT_ENTRY_RESTORE, [ - $commentInfo2->getId(), - $commentUserId = $commentInfo2->getUserId(), - ($commentUserId < 1 ? '(Deleted User)' : $commentInfo2->getUser()->getUsername()), + $commentInfo->getId(), + $commentUserId = $commentInfo->getUserId(), + '', ]); - redirect($redirect . '#comment-' . $commentInfo2->getId()); + redirect($redirect . '#comment-' . $commentInfo->getId()); break; case 'create': - if(!$commentPerms['can_comment'] && !$commentInfo2->isOwner($currentUserInfo)) { + if(!$commentPerms['can_comment'] && !$categoryInfo->isOwner($currentUserInfo)) { echo render_info("You're not allowed to post comments.", 403); break; } @@ -184,12 +195,11 @@ switch($commentMode) { } try { - $categoryInfo = CommentsCategory::byId( - isset($_POST['comment']['category']) && is_string($_POST['comment']['category']) - ? (int)$_POST['comment']['category'] - : 0 - ); - } catch(CommentsCategoryNotFoundException $ex) { + $categoryId = isset($_POST['comment']['category']) && is_string($_POST['comment']['category']) + ? (int)$_POST['comment']['category'] + : 0; + $categoryInfo = $comments->getCategoryById($categoryId); + } catch(RuntimeException $ex) { echo render_info('This comment category doesn\'t exist.', 404); break; } @@ -199,21 +209,23 @@ switch($commentMode) { break; } - $commentText = !empty($_POST['comment']['text']) && is_string($_POST['comment']['text']) ? $_POST['comment']['text'] : ''; - $commentReply = !empty($_POST['comment']['reply']) && is_string($_POST['comment']['reply']) ? (int)$_POST['comment']['reply'] : 0; - $commentLock = !empty($_POST['comment']['lock']) && $commentPerms['can_lock']; - $commentPin = !empty($_POST['comment']['pin']) && $commentPerms['can_pin']; + $commentText = !empty($_POST['comment']['text']) && is_string($_POST['comment']['text']) ? $_POST['comment']['text'] : ''; + $commentReply = (string)(!empty($_POST['comment']['reply']) && is_string($_POST['comment']['reply']) ? (int)$_POST['comment']['reply'] : 0); + $commentLock = !empty($_POST['comment']['lock']) && $commentPerms['can_lock']; + $commentPin = !empty($_POST['comment']['pin']) && $commentPerms['can_pin']; if($commentLock) { - $categoryInfo->setLocked(!$categoryInfo->isLocked()); - $categoryInfo->save(); + if($categoryInfo->isLocked()) + $comments->unlockCategory($categoryInfo); + else + $comments->lockCategory($categoryInfo); } if(strlen($commentText) > 0) { $commentText = preg_replace("/[\r\n]{2,}/", "\n", $commentText); } else { if($commentPerms['can_lock']) { - echo render_info('The action has been processed.'); + echo render_info('The action has been processed.', 400); } else { echo render_info('Your comment is too short.', 400); } @@ -227,34 +239,24 @@ switch($commentMode) { if($commentReply > 0) { try { - $parentCommentInfo = CommentsPost::byId($commentReply); - } catch(CommentsPostNotFoundException $ex) { - unset($parentCommentInfo); - } + $parentInfo = $comments->getPostById($commentReply); + } catch(RuntimeException $ex) {} - if(!isset($parentCommentInfo) || $parentCommentInfo->isDeleted()) { + if(!isset($parentInfo) || $parentInfo->isDeleted()) { echo render_info('The comment you tried to reply to does not exist.', 404); break; } } - $commentInfo2 = (new CommentsPost) - ->setUser($currentUserInfo) - ->setCategory($categoryInfo) - ->setParsedText($commentText) - ->setPinned($commentPin); + $commentInfo = $comments->createPost( + $categoryInfo, + $parentInfo ?? null, + $currentUserInfo, + $commentText, + $commentPin + ); - if(isset($parentCommentInfo)) - $commentInfo2->setParent($parentCommentInfo); - - try { - $commentInfo2->save(); - } catch(CommentsPostSaveFailedException $ex) { - echo render_info('Something went horribly wrong.', 500); - break; - } - - redirect($redirect . '#comment-' . $commentInfo2->getId()); + redirect($redirect . '#comment-' . $commentInfo->getId()); break; default: diff --git a/public/search.php b/public/search.php index 022f16e..81c484f 100644 --- a/public/search.php +++ b/public/search.php @@ -1,8 +1,8 @@ getNews(); + $comments = $msz->getComments(); $newsPosts = []; $newsPostInfos = $news->getPostsBySearchQuery($searchQuery); $newsUserInfos = []; @@ -41,11 +42,8 @@ if(!empty($searchQuery)) { else $newsCategoryInfos[$categoryId] = $categoryInfo = $news->getCategoryByPost($postInfo); - $commentsCount = 0; - if($postInfo->hasCommentsCategoryId()) - try { - $commentsCount = CommentsCategory::byId($postInfo->getCommentsCategoryId())->getPostCount(); - } catch(CommentsCategoryNotFoundException $ex) {} + $commentsCount = $postInfo->hasCommentsCategoryId() + ? $comments->countPosts($postInfo->getCommentsCategoryId(), includeReplies: true) : 0; $newsPosts[] = [ 'post' => $postInfo, diff --git a/src/Comments/Comments.php b/src/Comments/Comments.php new file mode 100644 index 0000000..18e2c91 --- /dev/null +++ b/src/Comments/Comments.php @@ -0,0 +1,524 @@ +dbConn = $dbConn; + $this->cache = new DbStatementCache($dbConn); + } + + public function countAllCategories(User|string|null $owner = null): int { + if($owner instanceof User) + $owner = (string)$owner->getId(); + + $hasOwner = $owner !== null; + + $query = 'SELECT COUNT(*) FROM msz_comments_categories'; + if($hasOwner) + $query .= ' WHERE owner_id = ?'; + + $stmt = $this->cache->get($query); + $stmt->addParameter(1, $owner); + $stmt->execute(); + + $count = 0; + $result = $stmt->getResult(); + + if($result->next()) + $count = $result->getInteger(0); + + return $count; + } + + public function getCategories( + User|string|null $owner = null, + ?Pagination $pagination = null + ): array { + if($owner instanceof User) + $owner = (string)$owner->getId(); + + $hasOwner = $owner !== null; + $hasPagination = $pagination !== null; + + $query = 'SELECT category_id, category_name, owner_id, UNIX_TIMESTAMP(category_created), UNIX_TIMESTAMP(category_locked), (SELECT COUNT(*) FROM msz_comments_posts AS cp WHERE cp.category_id = cc.category_id AND comment_deleted IS NULL) AS `category_comments` FROM msz_comments_categories AS cc'; + if($hasOwner) + $query .= ' WHERE owner_id = ?'; + $query .= ' ORDER BY category_id ASC'; // should order by date but no index on + if($hasPagination) + $query .= ' LIMIT ? RANGE ?'; + + $stmt = $this->cache->get($query); + + $args = 0; + if($hasOwner) + $stmt->addParameter(++$args, $owner); + if($hasPagination) { + $stmt->addParameter(++$args, $pagination->getRange()); + $stmt->addParameter(++$args, $pagination->getOffset()); + } + + $stmt->execute(); + + $result = $stmt->getResult(); + $categories = []; + + while($result->next()) + $categories[] = new CommentsCategoryInfo($result); + + return $categories; + } + + public function getCategoryByName(string $name): CommentsCategoryInfo { + $stmt = $this->cache->get('SELECT category_id, category_name, owner_id, UNIX_TIMESTAMP(category_created), UNIX_TIMESTAMP(category_locked), (SELECT COUNT(*) FROM msz_comments_posts AS cp WHERE cp.category_id = cc.category_id AND comment_deleted IS NULL) AS `category_comments` FROM msz_comments_categories AS cc WHERE category_name = ?'); + $stmt->addParameter(1, $name); + $stmt->execute(); + $result = $stmt->getResult(); + + if(!$result->next()) + throw new RuntimeException('No category with this name found.'); + + return new CommentsCategoryInfo($result); + } + + public function getCategoryById(string $id): CommentsCategoryInfo { + $stmt = $this->cache->get('SELECT category_id, category_name, owner_id, UNIX_TIMESTAMP(category_created), UNIX_TIMESTAMP(category_locked), (SELECT COUNT(*) FROM msz_comments_posts AS cp WHERE cp.category_id = cc.category_id AND comment_deleted IS NULL) AS `category_comments` FROM msz_comments_categories AS cc WHERE category_id = ?'); + $stmt->addParameter(1, $id); + $stmt->execute(); + $result = $stmt->getResult(); + + if(!$result->next()) + throw new RuntimeException('No category with this ID found.'); + + return new CommentsCategoryInfo($result); + } + + public function getCategoryByPost(CommentsPostInfo|string $infoOrId): CommentsCategoryInfo { + $query = 'SELECT category_id, category_name, owner_id, UNIX_TIMESTAMP(category_created), UNIX_TIMESTAMP(category_locked), (SELECT COUNT(*) FROM msz_comments_posts AS cp WHERE cp.category_id = cc.category_id AND comment_deleted IS NULL) AS `category_comments` FROM msz_comments_categories AS cc WHERE category_id = '; + + if($infoOrId instanceof CommentsPostInfo) { + $query .= '?'; + $param = $infoOrId->getCategoryId(); + } else { + $query .= '(SELECT category_id FROM msz_comments_posts WHERE comment_id = ?)'; + $param = $infoOrId; + } + + $stmt = $this->cache->get($query); + $stmt->addParameter(1, $param); + $stmt->execute(); + $result = $stmt->getResult(); + + if(!$result->next()) + throw new RuntimeException('No category belonging to this post found.'); + + return new CommentsCategoryInfo($result); + } + + public function checkCategoryNameExists(string $name): bool { + $stmt = $this->cache->get('SELECT COUNT(*) FROM msz_comments_categories WHERE category_name = ?'); + $stmt->addParameter(1, $name); + $stmt->execute(); + + $count = 0; + $result = $stmt->getResult(); + + if($result->next()) + $count = $result->getInteger(0); + + return $count > 0; + } + + public function ensureCategory(string $name, User|string|null $owner = null): CommentsCategoryInfo { + if($this->checkCategoryNameExists($name)) + return $this->getCategoryByName($name); + return $this->createCategory($name, $owner); + } + + public function createCategory(string $name, User|string|null $owner = null): CommentsCategoryInfo { + if($owner instanceof User) + $owner = (string)$owner->getId(); + + $name = trim($name); + if(empty($name)) + throw new InvalidArgumentException('$name may not be empty.'); + + $stmt = $this->cache->get('INSERT INTO msz_comments_categories (category_name, owner_id) VALUES (?, ?)'); + $stmt->addParameter(1, $name); + $stmt->addParameter(2, $owner); + $stmt->execute(); + + return $this->getCategoryById((string)$this->dbConn->getLastInsertId()); + } + + public function deleteCategory(CommentsCategoryInfo|string $category): void { + if($category instanceof CommentsCategoryInfo) + $category = $category->getId(); + + $stmt = $this->cache->get('DELETE FROM msz_comments_categories WHERE category_id = ?'); + $stmt->addParameter(1, $category); + $stmt->execute(); + } + + public function updateCategory( + CommentsCategoryInfo|string $category, + ?string $name = null, + bool $updateOwner = false, + User|string|null $owner = null + ): void { + if($category instanceof CommentsCategoryInfo) + $category = $category->getId(); + if($owner instanceof User) + $owner = (string)$owner->getId(); + + if($name !== null) { + $name = trim($name); + if(empty($name)) + throw new InvalidArgumentException('$name may not be empty.'); + } + + $stmt = $this->cache->get('UPDATE msz_comments_categories SET category_name = COALESCE(?, category_name), owner_id = IF(?, ?, owner_id) WHERE category_id = ?'); + $stmt->addParameter(1, $name); + $stmt->addParameter(2, $updateOwner ? 1 : 0); + $stmt->addParameter(3, $owner ? 1 : 0); + $stmt->addParameter(4, $category); + $stmt->execute(); + } + + public function lockCategory(CommentsCategoryInfo|string $category): void { + if($category instanceof CommentsCategoryInfo) + $category = $category->getId(); + + $stmt = $this->cache->get('UPDATE msz_comments_categories SET category_locked = COALESCE(category_locked, NOW()) WHERE category_id = ?'); + $stmt->addParameter(1, $category); + $stmt->execute(); + } + + public function unlockCategory(CommentsCategoryInfo|string $category): void { + if($category instanceof CommentsCategoryInfo) + $category = $category->getId(); + + $stmt = $this->cache->get('UPDATE msz_comments_categories SET category_locked = NULL WHERE category_id = ?'); + $stmt->addParameter(1, $category); + $stmt->execute(); + } + + public function countPosts( + CommentsCategoryInfo|string|null $category = null, + CommentsPostInfo|string|null $parent = null, + bool $includeReplies = false, + bool $includeDeleted = false + ): int { + if($category instanceof CommentsCategoryInfo) + $category = $category->getId(); + if($parent instanceof CommentsPostInfo) + $parent = $parent->getId(); + + $hasCategory = $category !== null; + $hasParent = $parent !== null; + + $args = 0; + $query = 'SELECT COUNT(*) FROM msz_comments_posts'; + + if($hasParent) { + $query .= (++$args > 1 ? ' AND' : ' WHERE'); + $query .= ' comment_reply_to = ?'; + } else { + if($hasCategory) { + $query .= (++$args > 1 ? ' AND' : ' WHERE'); + $query .= ' category_id = ?'; + } + + if(!$includeReplies) { + $query .= (++$args > 1 ? ' AND' : ' WHERE'); + $query .= ' comment_reply_to IS NULL'; + } + } + + if(!$includeDeleted) { + $query .= (++$args > 1 ? ' AND' : ' WHERE'); + $query .= ' comment_deleted IS NULL'; + } + + $args = 0; + $stmt = $this->cache->get($query); + if($hasParent) + $stmt->addParameter(++$args, $parent); + elseif($hasCategory) + $stmt->addParameter(++$args, $category); + $stmt->execute(); + + $result = $stmt->getResult(); + $count = 0; + + if($result->next()) + $count = $result->getInteger(0); + + return $count; + } + + public function getPosts( + CommentsCategoryInfo|string|null $category = null, + CommentsPostInfo|string|null $parent = null, + bool $includeReplies = false, + bool $includeDeleted = false, + bool $includeRepliesCount = false, + bool $includeVotesCount = false + ): array { + if($category instanceof CommentsCategoryInfo) + $category = $category->getId(); + if($parent instanceof CommentsPostInfo) + $parent = $parent->getId(); + + $hasCategory = $category !== null; + $hasParent = $parent !== null; + + $args = 0; + $query = 'SELECT comment_id, category_id, user_id, comment_reply_to, comment_text, UNIX_TIMESTAMP(comment_created), UNIX_TIMESTAMP(comment_pinned), UNIX_TIMESTAMP(comment_edited), UNIX_TIMESTAMP(comment_deleted)'; + if($includeRepliesCount) + $query .= ', (SELECT COUNT(*) FROM msz_comments_posts AS ccr WHERE ccr.comment_reply_to = cpp.comment_id AND comment_deleted IS NULL) AS `comment_replies`'; + if($includeVotesCount) { + $query .= ', (SELECT COUNT(*) FROM msz_comments_votes AS cvc WHERE cvc.comment_id = cpp.comment_id) AS `comment_votes_total`'; + $query .= ', (SELECT COUNT(*) FROM msz_comments_votes AS cvc WHERE cvc.comment_id = cpp.comment_id AND comment_vote > 0) AS `comment_votes_positive`'; + $query .= ', (SELECT COUNT(*) FROM msz_comments_votes AS cvc WHERE cvc.comment_id = cpp.comment_id AND comment_vote < 0) AS `comment_votes_negative`'; + } + $query .= ' FROM msz_comments_posts AS cpp'; + + if($hasParent) { + $query .= (++$args > 1 ? ' AND' : ' WHERE'); + $query .= ' comment_reply_to = ?'; + } else { + if($hasCategory) { + $query .= (++$args > 1 ? ' AND' : ' WHERE'); + $query .= ' category_id = ?'; + } + + if(!$includeReplies) { + $query .= (++$args > 1 ? ' AND' : ' WHERE'); + $query .= ' comment_reply_to IS NULL'; + } + } + + if(!$includeDeleted) { + $query .= (++$args > 1 ? ' AND' : ' WHERE'); + $query .= ' comment_deleted IS NULL'; + } + + // this should probably not be implicit like this + if($hasParent) + $query .= ' ORDER BY comment_deleted ASC, comment_pinned DESC, comment_created ASC'; + elseif($hasCategory) + $query .= ' ORDER BY comment_deleted ASC, comment_pinned DESC, comment_created DESC'; + else + $query .= ' ORDER BY comment_created DESC'; + + $args = 0; + $stmt = $this->cache->get($query); + if($hasParent) + $stmt->addParameter(++$args, $parent); + elseif($hasCategory) + $stmt->addParameter(++$args, $category); + $stmt->execute(); + + $posts = []; + $result = $stmt->getResult(); + + while($result->next()) + $posts[] = new CommentsPostInfo($result, $includeRepliesCount, $includeVotesCount); + + return $posts; + } + + public function getPostById( + string $postId, + bool $includeRepliesCount = false, + bool $includeVotesCount = false + ): CommentsPostInfo { + $query = 'SELECT comment_id, category_id, user_id, comment_reply_to, comment_text, UNIX_TIMESTAMP(comment_created), UNIX_TIMESTAMP(comment_pinned), UNIX_TIMESTAMP(comment_edited), UNIX_TIMESTAMP(comment_deleted)'; + if($includeRepliesCount) + $query .= ', (SELECT COUNT(*) FROM msz_comments_posts AS ccr WHERE ccr.comment_reply_to = cpp.comment_id AND comment_deleted IS NULL) AS `comment_replies`'; + if($includeVotesCount) { + $query .= ', (SELECT COUNT(*) FROM msz_comments_votes AS cvc WHERE cvc.comment_id = cpp.comment_id) AS `comment_votes_total`'; + $query .= ', (SELECT COUNT(*) FROM msz_comments_votes AS cvc WHERE cvc.comment_id = cpp.comment_id AND comment_vote > 0) AS `comment_votes_positive`'; + $query .= ', (SELECT COUNT(*) FROM msz_comments_votes AS cvc WHERE cvc.comment_id = cpp.comment_id AND comment_vote < 0) AS `comment_votes_negative`'; + } + $query .= ' FROM msz_comments_posts AS cpp WHERE comment_id = ?'; + + $stmt = $this->cache->get($query); + $stmt->addParameter(1, $postId); + $stmt->execute(); + + $result = $stmt->getResult(); + if(!$result->next()) + throw new RuntimeException('No comment with that ID exists.'); + + return new CommentsPostInfo($result, $includeRepliesCount, $includeVotesCount); + } + + public function createPost( + CommentsCategoryInfo|string|null $category, + CommentsPostInfo|string|null $parent, + User|string|null $user, + string $body, + bool $pin = false + ): CommentsPostInfo { + if($category instanceof CommentsCategoryInfo) + $category = $category->getId(); + if($parent instanceof CommentsPostInfo) { + if($category === null) + $category = $parent->getCategoryId(); + elseif($category !== $parent->getCategoryId()) + throw new InvalidArgumentException('$parent belongs to a different category than where this post is attempted to be created.'); + $parent = $parent->getId(); + } + if($category === null) + throw new InvalidArgumentException('$category is null; at least a $category or $parent must be specified.'); + if($user instanceof User) + $user = (string)$user->getId(); + if(empty(trim($body))) + throw new InvalidArgumentException('$body may not be empty.'); + + $stmt = $this->cache->get('INSERT INTO msz_comments_posts (category_id, user_id, comment_reply_to, comment_text, comment_pinned) VALUES (?, ?, ?, ?, IF(?, NOW(), NULL))'); + $stmt->addParameter(1, $category); + $stmt->addParameter(2, $user); + $stmt->addParameter(3, $parent); + $stmt->addParameter(4, $body); + $stmt->addParameter(5, $pin ? 1 : 0); + $stmt->execute(); + + return $this->getPostById((string)$this->dbConn->getLastInsertId()); + } + + public function deletePost(CommentsPostInfo|string $infoOrId): void { + if($infoOrId instanceof CommentsPostInfo) + $infoOrId = $infoOrId->getId(); + + $stmt = $this->cache->get('UPDATE msz_comments_posts SET comment_deleted = COALESCE(comment_deleted, NOW()) WHERE comment_id = ?'); + $stmt->addParameter(1, $infoOrId); + $stmt->execute(); + } + + public function nukePost(CommentsPostInfo|string $infoOrId): void { + if($infoOrId instanceof CommentsPostInfo) + $infoOrId = $infoOrId->getId(); + + $stmt = $this->cache->get('DELETE FROM msz_comments_posts WHERE comment_id = ?'); + $stmt->addParameter(1, $infoOrId); + $stmt->execute(); + } + + public function restorePost(CommentsPostInfo|string $infoOrId): void { + if($infoOrId instanceof CommentsPostInfo) + $infoOrId = $infoOrId->getId(); + + $stmt = $this->cache->get('UPDATE msz_comments_posts SET comment_deleted = NULL WHERE comment_id = ?'); + $stmt->addParameter(1, $infoOrId); + $stmt->execute(); + } + + public function editPost(CommentsPostInfo|string $infoOrId, string $body): void { + if($infoOrId instanceof CommentsPostInfo) + $infoOrId = $infoOrId->getId(); + + if(empty(trim($body))) + throw new InvalidArgumentException('$body may not be empty.'); + + $stmt = $this->cache->get('UPDATE msz_comments_posts SET comment_text = ?, comment_edited = NOW() WHERE comment_id = ?'); + $stmt->addParameter(1, $body); + $stmt->addParameter(2, $infoOrId); + $stmt->execute(); + } + + public function pinPost(CommentsPostInfo|string $infoOrId): void { + if($infoOrId instanceof CommentsPostInfo) + $infoOrId = $infoOrId->getId(); + + $stmt = $this->cache->get('UPDATE msz_comments_posts SET comment_pinned = COALESCE(comment_pinned, NOW()) WHERE comment_id = ?'); + $stmt->addParameter(1, $infoOrId); + $stmt->execute(); + } + + public function unpinPost(CommentsPostInfo|string $infoOrId): void { + if($infoOrId instanceof CommentsPostInfo) + $infoOrId = $infoOrId->getId(); + + $stmt = $this->cache->get('UPDATE msz_comments_posts SET comment_pinned = NULL WHERE comment_id = ?'); + $stmt->addParameter(1, $infoOrId); + $stmt->execute(); + } + + public function getPostVote( + CommentsPostInfo|string $post, + User|string|null $user + ): CommentsPostVoteInfo { + if($post instanceof CommentsPostInfo) + $post = $post->getId(); + if($user instanceof User) + $user = (string)$user->getId(); + + // SUM() here makes it so a result row is always returned, albeit with just NULLs + $stmt = $this->cache->get('SELECT comment_id, user_id, SUM(comment_vote) FROM msz_comments_votes WHERE comment_id = ? AND user_id = ?'); + $stmt->addParameter(1, $post); + $stmt->addParameter(2, $user); + $stmt->execute(); + + $result = $stmt->getResult(); + if(!$result->next()) + throw new RuntimeException('Failed to fetch vote info.'); + + return new CommentsPostVoteInfo($result); + } + + public function addPostVote( + CommentsPostInfo|string $post, + User|string $user, + int $weight + ): void { + if($weight === 0) + return; + if($post instanceof CommentsPostInfo) + $post = $post->getId(); + if($user instanceof User) + $user = (string)$user->getId(); + + $stmt = $this->cache->get('REPLACE INTO msz_comments_votes (comment_id, user_id, comment_vote) VALUES (?, ?, ?)'); + $stmt->addParameter(1, $post); + $stmt->addParameter(2, $user); + $stmt->addParameter(3, $weight); + $stmt->execute(); + } + + public function addPostPositiveVote(CommentsPostInfo|string $post, User|string $user): void { + $this->addPostVote($post, $user, 1); + } + + public function addPostNegativeVote(CommentsPostInfo|string $post, User|string $user): void { + $this->addPostVote($post, $user, -1); + } + + public function removePostVote( + CommentsPostInfo|string $post, + User|string $user + ): void { + if($post instanceof CommentsPostInfo) + $post = $post->getId(); + if($user instanceof User) + $user = (string)$user->getId(); + + $stmt = $this->cache->get('DELETE FROM msz_comments_votes WHERE comment_id = ? AND user_id = ?'); + $stmt->addParameter(1, $post); + $stmt->addParameter(2, $user); + $stmt->execute(); + } +} diff --git a/src/Comments/CommentsCategory.php b/src/Comments/CommentsCategory.php deleted file mode 100644 index 165c01b..0000000 --- a/src/Comments/CommentsCategory.php +++ /dev/null @@ -1,167 +0,0 @@ -setName($name); - } - - public function getId(): int { - return $this->category_id < 1 ? -1 : $this->category_id; - } - - public function getName(): string { - return $this->category_name; - } - public function setName(string $name): self { - $this->category_name = $name; - return $this; - } - - public function getOwnerId(): int { - return $this->owner_id < 1 ? -1 : $this->owner_id; - } - public function hasOwner(): bool { - return $this->owner_id !== null; - } - public function getOwner(): User { - if($this->owner === null && ($ownerId = $this->getOwnerId()) >= 1) - $this->owner = User::byId($ownerId); - return $this->owner; - } - public function isOwner(User $user): bool { - return $this->hasOwner() && $user->getId() === $this->getOwnerId(); - } - - public function getCreatedTime(): int { - return $this->category_created === null ? -1 : $this->category_created; - } - - public function getLockedTime(): int { - return $this->category_locked === null ? -1 : $this->category_locked; - } - public function isLocked(): bool { - return $this->getLockedTime() >= 0; - } - public function setLocked(bool $locked): self { - if($locked !== $this->isLocked()) - $this->category_locked = $locked ? time() : null; - return $this; - } - - // Purely cosmetic, do not use for anything other than displaying - public function getPostCount(): int { - if($this->postCount < 0) - $this->postCount = (int)DB::prepare(' - SELECT COUNT(`comment_id`) - FROM `msz_comments_posts` - WHERE `category_id` = :cat_id - AND `comment_deleted` IS NULL - ')->bind('cat_id', $this->getId())->fetchColumn(); - - return $this->postCount; - } - - public function save(): void { - $isInsert = $this->getId() < 1; - if($isInsert) { - $query = 'INSERT INTO `%1$s%2$s` (`category_name`, `category_locked`) VALUES' - . ' (:name, :locked)'; - } else { - $query = 'UPDATE `%1$s%2$s` SET `category_name` = :name, `category_locked` = FROM_UNIXTIME(:locked)' - . ' WHERE `category_id` = :category'; - } - - $saveCategory = DB::prepare(sprintf($query, DB::PREFIX, self::TABLE)) - ->bind('name', $this->category_name) - ->bind('locked', $this->category_locked); - - if($isInsert) { - $this->category_id = $saveCategory->executeGetId(); - $this->category_created = time(); - } else { - $saveCategory->bind('category', $this->getId()) - ->execute(); - } - } - - public function posts(?User $voteUser = null, bool $includeVotes = true, ?Pagination $pagination = null, bool $rootOnly = true, bool $includeDeleted = true): array { - return CommentsPost::byCategory($this, $voteUser, $includeVotes, $pagination, $rootOnly, $includeDeleted); - } - public function votes(?User $user = null, bool $rootOnly = true, ?Pagination $pagination = null): array { - return CommentsVote::byCategory($this, $user, $rootOnly, $pagination); - } - - private static function memoizer() { - static $memoizer = null; - if($memoizer === null) - $memoizer = new Memoizer; - return $memoizer; - } - - private static function byQueryBase(): string { - return sprintf(self::QUERY_SELECT, sprintf(self::SELECT, self::TABLE)); - } - public static function byId(int $categoryId): self { - return self::memoizer()->find($categoryId, function() use ($categoryId) { - $cat = DB::prepare(self::byQueryBase() . ' WHERE `category_id` = :cat_id') - ->bind('cat_id', $categoryId) - ->fetchObject(self::class); - if(!$cat) - throw new CommentsCategoryNotFoundException; - return $cat; - }); - } - public static function byName(string $categoryName): self { - return self::memoizer()->find(function($category) use ($categoryName) { - return $category->getName() === $categoryName; - }, function() use ($categoryName) { - $cat = DB::prepare(self::byQueryBase() . ' WHERE `category_name` = :name') - ->bind('name', $categoryName) - ->fetchObject(self::class); - if(!$cat) - throw new CommentsCategoryNotFoundException; - return $cat; - }); - } - public static function all(?Pagination $pagination = null): array { - $catsQuery = self::byQueryBase() - . ' ORDER BY `category_id` ASC'; - - if($pagination !== null) - $catsQuery .= ' LIMIT :range OFFSET :offset'; - - $getCats = DB::prepare($catsQuery); - - if($pagination !== null) - $getCats->bind('range', $pagination->getRange()) - ->bind('offset', $pagination->getOffset()); - - return $getCats->fetchObjects(self::class); - } -} diff --git a/src/Comments/CommentsCategoryInfo.php b/src/Comments/CommentsCategoryInfo.php new file mode 100644 index 0000000..71631d9 --- /dev/null +++ b/src/Comments/CommentsCategoryInfo.php @@ -0,0 +1,72 @@ +id = (string)$result->getInteger(0); + $this->name = $result->getString(1); + $this->ownerId = $result->isNull(2) ? null : (string)$result->getInteger(2); + $this->created = $result->getInteger(3); + $this->locked = $result->isNull(4) ? null : $result->getInteger(4); + $this->comments = $result->getInteger(5); + } + + public function getId(): string { + return $this->id; + } + + public function getName(): string { + return $this->name; + } + + public function hasOwnerId(): bool { + return $this->ownerId !== null; + } + + public function getOwnerId(): ?string { + return $this->ownerId; + } + + public function isOwner(User|string $user): bool { + if($this->ownerId === null) + return false; + if($user instanceof User) + $user = (string)$user->getId(); + return $user === $this->ownerId; + } + + public function getCreatedTime(): int { + return $this->created; + } + + public function getCreatedAt(): DateTime { + return DateTime::fromUnixTimeSeconds($this->created); + } + + public function getLockedTime(): ?int { + return $this->locked; + } + + public function getLockedAt(): ?DateTime { + return $this->locked === null ? null : DateTime::fromUnixTimeSeconds($this->locked); + } + + public function isLocked(): bool { + return $this->locked !== null; + } + + public function getCommentsCount(): int { + return $this->comments; + } +} diff --git a/src/Comments/CommentsEx.php b/src/Comments/CommentsEx.php new file mode 100644 index 0000000..ff8dcdb --- /dev/null +++ b/src/Comments/CommentsEx.php @@ -0,0 +1,61 @@ +comments = $comments; + $this->userInfos = $userInfos; + } + + public function getCommentsForLayout(CommentsCategoryInfo|string $category): object { + $info = new stdClass; + if(is_string($category)) + $category = $this->comments->ensureCategory($category); + + $info->user = User::getCurrent(); + $info->category = $category; + $info->posts = []; + + $root = $this->comments->getPosts($category, includeRepliesCount: true, includeVotesCount: true, includeDeleted: true); + foreach($root as $postInfo) + $info->posts[] = $this->decorateComment($postInfo); + + return $info; + } + + public function decorateComment(CommentsPostInfo $postInfo): object { + if($postInfo->hasUserId()) { + $userId = $postInfo->getUserId(); + if(array_key_exists($userId, $this->userInfos)) { + $userInfo = $this->userInfos[$userId]; + } else { + try { + $userInfo = User::byId($userId); + } catch(UserNotFoundException $ex) { + $userInfo = null; + } + + $this->userInfos[$userId] = $userInfo; + } + } else $userInfo = null; + + $info = new stdClass; + $info->post = $postInfo; + $info->user = $userInfo; + $info->vote = $this->comments->getPostVote($postInfo, $userInfo); + $info->replies = []; + + $root = $this->comments->getPosts(parent: $postInfo, includeRepliesCount: true, includeVotesCount: true, includeDeleted: true); + foreach($root as $childInfo) + $info->replies[] = $this->decorateComment($childInfo); + + return $info; + } +} diff --git a/src/Comments/CommentsException.php b/src/Comments/CommentsException.php deleted file mode 100644 index adaa76d..0000000 --- a/src/Comments/CommentsException.php +++ /dev/null @@ -1,6 +0,0 @@ -comment_id < 1 ? -1 : $this->comment_id; - } - - public function getCategoryId(): int { - return $this->category_id < 1 ? -1 : $this->category_id; - } - public function setCategoryId(int $categoryId): self { - $this->category_id = $categoryId; - $this->category = null; - return $this; - } - public function getCategory(): CommentsCategory { - if($this->category === null) - $this->category = CommentsCategory::byId($this->getCategoryId()); - return $this->category; - } - public function setCategory(CommentsCategory $category): self { - $this->category_id = $category->getId(); - $this->category = null; - return $this; - } - - public function getUserId(): int { - return $this->user_id < 1 ? -1 : $this->user_id; - } - public function setUserId(int $userId): self { - $this->user_id = $userId < 1 ? null : $userId; - $this->userLookedUp = false; - $this->user = null; - return $this; - } - public function getUser(): ?User { - if(!$this->userLookedUp && ($userId = $this->getUserId()) > 0) { - $this->userLookedUp = true; - try { - $this->user = User::byId($userId); - } catch(UserNotFoundException $ex) {} - } - return $this->user; - } - public function setUser(?User $user): self { - $this->user_id = $user === null ? null : $user->getId(); - $this->userLookedUp = true; - $this->user = $user; - return $this; - } - - public function getParentId(): int { - return $this->comment_reply_to < 1 ? -1 : $this->comment_reply_to; - } - public function setParentId(int $parentId): self { - $this->comment_reply_to = $parentId < 1 ? null : $parentId; - $this->parentPost = null; - return $this; - } - public function hasParent(): bool { - return $this->getParentId() > 0; - } - public function getParent(): CommentsPost { - if(!$this->hasParent()) - throw new CommentsPostHasNoParentException; - if($this->parentPost === null) - $this->parentPost = CommentsPost::byId($this->getParentId()); - return $this->parentPost; - } - public function setParent(?CommentsPost $parent): self { - $this->comment_reply_to = $parent === null ? null : $parent->getId(); - $this->parentPost = $parent; - return $this; - } - - public function getText(): string { - return $this->comment_text; - } - public function setText(string $text): self { - $this->comment_text = $text; - return $this; - } - public function getParsedText(): string { - return CommentsParser::parseForDisplay($this->getText()); - } - public function setParsedText(string $text): self { - return $this->setText(CommentsParser::parseForStorage($text)); - } - - public function getCreatedTime(): int { - return $this->comment_created === null ? -1 : $this->comment_created; - } - - public function getPinnedTime(): int { - return $this->comment_pinned === null ? -1 : $this->comment_pinned; - } - public function isPinned(): bool { - return $this->getPinnedTime() >= 0; - } - public function setPinned(bool $pinned): self { - if($this->isPinned() !== $pinned) - $this->comment_pinned = $pinned ? time() : null; - return $this; - } - - public function getEditedTime(): int { - return $this->comment_edited === null ? -1 : $this->comment_edited; - } - public function isEdited(): bool { - return $this->getEditedTime() >= 0; - } - - public function getDeletedTime(): int { - return $this->comment_deleted === null ? -1 : $this->comment_deleted; - } - public function isDeleted(): bool { - return $this->getDeletedTime() >= 0; - } - public function setDeleted(bool $deleted): self { - if($this->isDeleted() !== $deleted) - $this->comment_deleted = $deleted ? time() : null; - return $this; - } - - public function getLikes(): int { - return $this->comment_likes; - } - public function getDislikes(): int { - return $this->comment_dislikes; - } - - public function hasUserVote(): bool { - return $this->user_vote !== null; - } - public function getUserVote(): int { - return $this->user_vote ?? 0; - } - - public function save(): void { - $isInsert = $this->getId() < 1; - if($isInsert) { - $query = 'INSERT INTO `%1$s%2$s` (`category_id`, `user_id`, `comment_reply_to`, `comment_text`' - . ', `comment_pinned`, `comment_deleted`) VALUES' - . ' (:category, :user, :parent, :text, FROM_UNIXTIME(:pinned), FROM_UNIXTIME(:deleted))'; - } else { - $query = 'UPDATE `%1$s%2$s` SET `category_id` = :category, `user_id` = :user, `comment_reply_to` = :parent' - . ', `comment_text` = :text, `comment_pinned` = FROM_UNIXTIME(:pinned), `comment_deleted` = FROM_UNIXTIME(:deleted)' - . ' WHERE `comment_id` = :post'; - } - - $savePost = DB::prepare(sprintf($query, DB::PREFIX, self::TABLE)) - ->bind('category', $this->category_id) - ->bind('user', $this->user_id) - ->bind('parent', $this->comment_reply_to) - ->bind('text', $this->comment_text) - ->bind('pinned', $this->comment_pinned) - ->bind('deleted', $this->comment_deleted); - - if($isInsert) { - $this->comment_id = $savePost->executeGetId(); - if($this->comment_id < 1) - throw new CommentsPostSaveFailedException; - $this->comment_created = time(); - } else { - $this->comment_edited = time(); - $savePost->bind('post', $this->getId()); - if(!$savePost->execute()) - throw new CommentsPostSaveFailedException; - } - } - - public function nuke(): void { - $replies = $this->replies(null, true); - foreach($replies as $reply) - $reply->nuke(); - DB::prepare('DELETE FROM `' . DB::PREFIX . self::TABLE . '` WHERE `comment_id` = :comment') - ->bind('comment_id', $this->getId()) - ->execute(); - } - - public function replies(?User $voteUser = null, bool $includeVotes = true, ?Pagination $pagination = null, bool $includeDeleted = true): array { - return CommentsPost::byParent($this, $voteUser, $includeVotes, $pagination, $includeDeleted); - } - public function votes(): CommentsVoteCount { - return CommentsVote::countByPost($this); - } - public function childVotes(?User $user = null, ?Pagination $pagination = null): array { - return CommentsVote::byParent($this, $user, $pagination); - } - - public function addPositiveVote(User $user): void { - CommentsVote::create($this, $user, CommentsVote::LIKE); - } - public function addNegativeVote(User $user): void { - CommentsVote::create($this, $user, CommentsVote::DISLIKE); - } - public function removeVote(User $user): void { - CommentsVote::delete($this, $user); - } - - public function getVoteFromUser(User $user): CommentsVote { - return CommentsVote::byExact($this, $user); - } - - private static function byQueryBase(bool $includeVotes = true, bool $includeUserVote = false): string { - $select = self::SELECT; - if($includeVotes) - $select .= ', ' . self::LIKE_VOTE_SELECT - . ', ' . self::DISLIKE_VOTE_SELECT; - if($includeUserVote) - $select .= ', ' . self::USER_VOTE_SELECT; - return sprintf(self::QUERY_SELECT, sprintf($select, self::TABLE)); - } - public static function byId(int $postId): self { - $getPost = DB::prepare(self::byQueryBase() . ' WHERE `comment_id` = :post_id'); - $getPost->bind('post_id', $postId); - $post = $getPost->fetchObject(self::class); - if(!$post) - throw new CommentsPostNotFoundException; - return $post; - } - public static function byCategory(CommentsCategory $category, ?User $voteUser = null, bool $includeVotes = true, ?Pagination $pagination = null, bool $rootOnly = true, bool $includeDeleted = true): array { - $postsQuery = self::byQueryBase($includeVotes, $voteUser !== null) - . ' WHERE `category_id` = :category' - . (!$rootOnly ? '' : ' AND `comment_reply_to` IS NULL') - . ($includeDeleted ? '' : ' AND `comment_deleted` IS NULL') - . ' ORDER BY `comment_deleted` ASC, `comment_pinned` DESC, `comment_id` DESC'; - - if($pagination !== null) - $postsQuery .= ' LIMIT :range OFFSET :offset'; - - $getPosts = DB::prepare($postsQuery) - ->bind('category', $category->getId()); - - if($voteUser !== null) - $getPosts->bind('user', $voteUser->getId()); - - if($pagination !== null) - $getPosts->bind('range', $pagination->getRange()) - ->bind('offset', $pagination->getOffset()); - - return $getPosts->fetchObjects(self::class); - } - public static function byParent(CommentsPost $parent, ?User $voteUser = null, bool $includeVotes = true, ?Pagination $pagination = null, bool $includeDeleted = true): array { - $postsQuery = self::byQueryBase($includeVotes, $voteUser !== null) - . ' WHERE `comment_reply_to` = :parent' - . ($includeDeleted ? '' : ' AND `comment_deleted` IS NULL') - . ' ORDER BY `comment_deleted` ASC, `comment_pinned` DESC, `comment_id` ASC'; - - if($pagination !== null) - $postsQuery .= ' LIMIT :range OFFSET :offset'; - - $getPosts = DB::prepare($postsQuery) - ->bind('parent', $parent->getId()); - - if($voteUser !== null) - $getPosts->bind('user', $voteUser->getId()); - - if($pagination !== null) - $getPosts->bind('range', $pagination->getRange()) - ->bind('offset', $pagination->getOffset()); - - return $getPosts->fetchObjects(self::class); - } - public static function all(?Pagination $pagination = null, bool $rootOnly = true, bool $includeDeleted = false): array { - $postsQuery = self::byQueryBase() - . ' WHERE 1' // this is disgusting - . (!$rootOnly ? '' : ' AND `comment_reply_to` IS NULL') - . ($includeDeleted ? '' : ' AND `comment_deleted` IS NULL') - . ' ORDER BY `comment_id` DESC'; - - if($pagination !== null) - $postsQuery .= ' LIMIT :range OFFSET :offset'; - - $getPosts = DB::prepare($postsQuery); - - if($pagination !== null) - $getPosts->bind('range', $pagination->getRange()) - ->bind('offset', $pagination->getOffset()); - - return $getPosts->fetchObjects(self::class); - } -} diff --git a/src/Comments/CommentsPostInfo.php b/src/Comments/CommentsPostInfo.php new file mode 100644 index 0000000..51a3f7c --- /dev/null +++ b/src/Comments/CommentsPostInfo.php @@ -0,0 +1,146 @@ +id = (string)$result->getInteger($args); + $this->categoryId = (string)$result->getInteger(++$args); + $this->userId = $result->isNull(++$args) ? null : (string)$result->getInteger($args); + $this->replyingTo = $result->isNull(++$args) ? null : (string)$result->getInteger($args); + $this->body = $result->getString(++$args); + $this->created = $result->getInteger(++$args); + $this->pinned = $result->isNull(++$args) ? null : $result->getInteger($args); + $this->updated = $result->isNull(++$args) ? null : $result->getInteger($args); + $this->deleted = $result->isNull(++$args) ? null : $result->getInteger($args); + + $this->replies = $includeRepliesCount ? $result->getInteger(++$args) : 0; + + if($includeVotesCount) { + $this->votesTotal = $result->getInteger(++$args); + $this->votesPositive = $result->getInteger(++$args); + $this->votesNegative = $result->getInteger(++$args); + } else { + $this->votesTotal = 0; + $this->votesPositive = 0; + $this->votesNegative = 0; + } + } + + public function getId(): string { + return $this->id; + } + + public function getCategoryId(): string { + return $this->categoryId; + } + + public function hasUserId(): bool { + return $this->userId !== null; + } + + public function getUserId(): ?string { + return $this->userId; + } + + public function isReply(): bool { + return $this->replyingTo !== null; + } + + public function getReplyingTo(): ?string { + return $this->replyingTo; + } + + public function getBody(): string { + return $this->body; + } + + public function getCreatedTime(): int { + return $this->created; + } + + public function getCreatedAt(): DateTime { + return DateTime::fromUnixTimeSeconds($this->created); + } + + public function getPinnedTime(): ?int { + return $this->pinned; + } + + public function getPinnedAt(): DateTime { + return $this->pinned === null ? null : DateTime::fromUnixTimeSeconds($this->pinned); + } + + public function isPinned(): bool { + return $this->pinned !== null; + } + + public function getUpdatedTime(): ?int { + return $this->updated; + } + + public function getUpdatedAt(): DateTime { + return $this->updated === null ? null : DateTime::fromUnixTimeSeconds($this->updated); + } + + public function isEdited(): bool { + return $this->updated !== null; + } + + public function getDeletedTime(): ?int { + return $this->deleted; + } + + public function getDeletedAt(): DateTime { + return $this->deleted === null ? null : DateTime::fromUnixTimeSeconds($this->deleted); + } + + public function isDeleted(): bool { + return $this->deleted !== null; + } + + public function hasRepliesCount(): bool { + return $this->replies > 0; + } + + public function getRepliesCount(): int { + return $this->replies; + } + + public function hasVotesCount(): bool { + return $this->votesTotal > 0; + } + + public function getVotesTotal(): int { + return $this->votesTotal; + } + + public function getVotesPositive(): int { + return $this->votesPositive; + } + + public function getVotesNegative(): int { + return $this->votesNegative; + } +} diff --git a/src/Comments/CommentsPostVoteInfo.php b/src/Comments/CommentsPostVoteInfo.php new file mode 100644 index 0000000..4a48a45 --- /dev/null +++ b/src/Comments/CommentsPostVoteInfo.php @@ -0,0 +1,28 @@ +commentId = (string)$result->getInteger(0); + $this->userId = (string)$result->getInteger(1); + $this->weight = $result->getInteger(2); + } + + public function getCommentId(): string { + return $this->commentId; + } + + public function getUserId(): string { + return $this->userId; + } + + public function getWeight(): int { + return $this->weight; + } +} diff --git a/src/Comments/CommentsVote.php b/src/Comments/CommentsVote.php deleted file mode 100644 index 947e03d..0000000 --- a/src/Comments/CommentsVote.php +++ /dev/null @@ -1,228 +0,0 @@ -comment_id < 1 ? -1 : $this->comment_id; - } - public function getLikes(): int { - return $this->likes; - } - public function getDislikes(): int { - return $this->dislikes; - } - public function getTotal(): int { - return $this->total; - } -} - -class CommentsVote { - // Database fields - private $comment_id = -1; - private $user_id = -1; - private $comment_vote = 0; - - private $comment = null; - private $user = null; - - public const LIKE = 1; - public const NONE = 0; - public const DISLIKE = -1; - - public const TABLE = 'comments_votes'; - private const QUERY_SELECT = 'SELECT %1$s FROM `' . DB::PREFIX . self::TABLE . '` AS '. self::TABLE; - private const SELECT = '%1$s.`comment_id`, %1$s.`user_id`, %1$s.`comment_vote`'; - - private const QUERY_COUNT = 'SELECT %3$d AS `comment_id`' - . ', (SELECT COUNT(`comment_id`) FROM `%1$s%2$s` WHERE %6$s) AS `total`' - . ', (SELECT COUNT(`comment_id`) FROM `%1$s%2$s` WHERE %6$s AND `comment_vote` = %4$d) AS `likes`' - . ', (SELECT COUNT(`comment_id`) FROM `%1$s%2$s` WHERE %6$s AND `comment_vote` = %5$d) AS `dislikes`'; - - public function getPostId(): int { - return $this->comment_id < 1 ? -1 : $this->comment_id; - } - public function getPost(): CommentsPost { - if($this->comment === null) - $this->comment = CommentsPost::byId($this->comment_id); - return $this->comment; - } - - public function getUserId(): int { - return $this->user_id < 1 ? -1 : $this->user_id; - } - public function getUser(): User { - if($this->user === null) - $this->user = User::byId($this->user_id); - return $this->user; - } - - public function getVote(): int { - return $this->comment_vote; - } - - public static function create(CommentsPost $post, User $user, int $vote, bool $return = false): ?self { - $createVote = DB::prepare(' - REPLACE INTO `msz_comments_votes` - (`comment_id`, `user_id`, `comment_vote`) - VALUES - (:post, :user, :vote) - ') ->bind('post', $post->getId()) - ->bind('user', $user->getId()) - ->bind('vote', $vote); - - if(!$createVote->execute()) - throw new CommentsVoteCreateFailedException; - if(!$return) - return null; - - return self::byExact($post, $user); - } - - public static function delete(CommentsPost $post, User $user): void { - DB::prepare('DELETE FROM `msz_comments_votes` WHERE `comment_id` = :post AND `user_id` = :user') - ->bind('post', $post->getId()) - ->bind('user', $user->getId()) - ->execute(); - } - - private static function countQueryBase(int $id, string $condition = '1'): string { - return sprintf(self::QUERY_COUNT, DB::PREFIX, self::TABLE, $id, self::LIKE, self::DISLIKE, $condition); - } - public static function countByPost(CommentsPost $post): CommentsVoteCount { - $count = DB::prepare(self::countQueryBase($post->getId(), sprintf('`comment_id` = %d', $post->getId()))) - ->fetchObject(CommentsVoteCount::class); - if(!$count) - throw new CommentsVoteCountFailedException; - return $count; - } - - private static function fake(CommentsPost $post, User $user, int $vote): CommentsVote { - $fake = new CommentsVote; - $fake->comment_id = $post->getId(); - $fake->comment = $post; - $fake->user_id = $user->getId(); - $fake->user = $user; - $fake->comment_vote = $vote; - return $fake; - } - - private static function byQueryBase(): string { - return sprintf(self::QUERY_SELECT, sprintf(self::SELECT, self::TABLE)); - } - public static function byExact(CommentsPost $post, User $user): self { - $vote = DB::prepare(self::byQueryBase() . ' WHERE `comment_id` = :post_id AND `user_id` = :user_id') - ->bind('post_id', $post->getId()) - ->bind('user_id', $user->getId()) - ->fetchObject(self::class); - if(!$vote) - return self::fake($post, $user, self::NONE); - return $vote; - } - public static function byPost(CommentsPost $post, ?User $user = null, ?Pagination $pagination = null): array { - $votesQuery = self::byQueryBase() - . ' WHERE `comment_id` = :post' - . ($user === null ? '' : ' AND `user_id` = :user'); - - if($pagination !== null) - $votesQuery .= ' LIMIT :range OFFSET :offset'; - - $getVotes = DB::prepare($votesQuery) - ->bind('post', $post->getId()); - - if($user !== null) - $getVotes->bind('user', $user->getId()); - - if($pagination !== null) - $getVotes->bind('range', $pagination->getRange()) - ->bind('offset', $pagination->getOffset()); - - return $getVotes->fetchObjects(self::class); - } - public static function byUser(User $user, ?Pagination $pagination = null): array { - $votesQuery = self::byQueryBase() - . ' WHERE `user_id` = :user'; - - if($pagination !== null) - $votesQuery .= ' LIMIT :range OFFSET :offset'; - - $getVotes = DB::prepare($votesQuery) - ->bind('user', $user->getId()); - - if($pagination !== null) - $getVotes->bind('range', $pagination->getRange()) - ->bind('offset', $pagination->getOffset()); - - return $getVotes->fetchObjects(self::class); - } - public static function byCategory(CommentsCategory $category, ?User $user = null, bool $rootOnly = true, ?Pagination $pagination = null): array { - $votesQuery = self::byQueryBase() - . ' WHERE `comment_id` IN' - . ' (SELECT `comment_id` FROM `' . DB::PREFIX . CommentsPost::TABLE . '` WHERE `category_id` = :category' - . (!$rootOnly ? '' : ' AND `comment_reply_to` IS NULL') - . ')' - . ($user === null ? '' : ' AND `user_id` = :user'); - - if($pagination !== null) - $votesQuery .= ' LIMIT :range OFFSET :offset'; - - $getVotes = DB::prepare($votesQuery) - ->bind('category', $category->getId()); - - if($user !== null) - $getVotes->bind('user', $user->getId()); - - if($pagination !== null) - $getVotes->bind('range', $pagination->getRange()) - ->bind('offset', $pagination->getOffset()); - - return $getVotes->fetchObjects(self::class); - } - public static function byParent(CommentsPost $parent, ?User $user = null, ?Pagination $pagination = null): array { - $votesQuery = self::byQueryBase() - . ' WHERE `comment_id` IN' - . ' (SELECT `comment_id` FROM `' . DB::PREFIX . CommentsPost::TABLE . '` WHERE `comment_reply_to` = :parent)' - . ($user === null ? '' : ' AND `user_id` = :user'); - - if($pagination !== null) - $votesQuery .= ' LIMIT :range OFFSET :offset'; - - $getVotes = DB::prepare($votesQuery) - ->bind('parent', $parent->getId()); - - if($user !== null) - $getVotes->bind('user', $user->getId()); - - if($pagination !== null) - $getVotes->bind('range', $pagination->getRange()) - ->bind('offset', $pagination->getOffset()); - - return $getVotes->fetchObjects(self::class); - } - public static function all(?Pagination $pagination = null): array { - $votesQuery = self::byQueryBase(); - - if($pagination !== null) - $votesQuery .= ' LIMIT :range OFFSET :offset'; - - $getVotes = DB::prepare($votesQuery); - - if($pagination !== null) - $getVotes->bind('range', $pagination->getRange()) - ->bind('offset', $pagination->getOffset()); - - return $getVotes->fetchObjects(self::class); - } -} diff --git a/src/Database/Database.php b/src/Database/Database.php index c86b54e..8928df8 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -12,7 +12,7 @@ class Database { } public function queries(): int { - return (int)$this->query('SHOW SESSION STATUS LIKE "Questions"')->fetchColumn(1); + return ((int)$this->query('SHOW SESSION STATUS LIKE "Questions"')->fetchColumn(1)); } public function exec(string $stmt): int { diff --git a/src/Http/Handlers/ChangelogHandler.php b/src/Http/Handlers/ChangelogHandler.php index 68de81e..c0a63d5 100644 --- a/src/Http/Handlers/ChangelogHandler.php +++ b/src/Http/Handlers/ChangelogHandler.php @@ -7,8 +7,7 @@ use Misuzu\Config; use Misuzu\Config\IConfig; use Misuzu\Pagination; use Misuzu\Template; -use Misuzu\Comments\CommentsCategory; -use Misuzu\Comments\CommentsCategoryNotFoundException; +use Misuzu\Comments\CommentsEx; use Misuzu\Feeds\Feed; use Misuzu\Feeds\FeedItem; use Misuzu\Feeds\AtomFeedSerializer; @@ -17,6 +16,8 @@ use Misuzu\Users\User; use Misuzu\Users\UserNotFoundException; class ChangelogHandler extends Handler { + private array $userInfos = []; + public function index($response, $request) { $filterDate = (string)$request->getParam('date'); $filterUser = (int)$request->getParam('user', FILTER_SANITIZE_NUMBER_INT); @@ -60,13 +61,12 @@ class ChangelogHandler extends Handler { return 404; $changes = []; - $userInfos = []; foreach($changeInfos as $changeInfo) { $userId = $changeInfo->getUserId(); - if(array_key_exists($userId, $userInfos)) { - $userInfo = $userInfos[$userId]; + if(array_key_exists($userId, $this->userInfos)) { + $userInfo = $this->userInfos[$userId]; } else { try { $userInfo = User::byId($userId); @@ -74,7 +74,7 @@ class ChangelogHandler extends Handler { $userInfo = null; } - $userInfos[$userId] = $userInfo; + $this->userInfos[$userId] = $userInfo; } $changes[] = [ @@ -89,20 +89,13 @@ class ChangelogHandler extends Handler { 'changelog_user' => $filterUser, 'changelog_tags' => $filterTags, 'changelog_pagination' => $pagination, - 'comments_user' => User::getCurrent(), - 'comments_category' => empty($filterDate) ? null : self::getCommentsCategory($changeInfos[0]->getCommentsCategoryName()), + 'comments_info' => empty($filterDate) ? null : $this->getCommentsInfo($changeInfos[0]->getCommentsCategoryName()), ])); } - private static function getCommentsCategory(string $categoryName): CommentsCategory { - try { - $category = CommentsCategory::byName($categoryName); - } catch(CommentsCategoryNotFoundException $ex) { - $category = new CommentsCategory($categoryName); - $category->save(); - } - - return $category; + private function getCommentsInfo(string $categoryName): object { + $comments = new CommentsEx($this->context->getComments(), $this->userInfos); + return $comments->getCommentsForLayout($categoryName); } public function change($response, $request, string $changeId) { @@ -121,8 +114,7 @@ class ChangelogHandler extends Handler { $response->setContent(Template::renderRaw('changelog.change', [ 'change_info' => $changeInfo, 'change_user_info' => $userInfo, - 'comments_user' => User::getCurrent(), - 'comments_category' => self::getCommentsCategory($changeInfo->getCommentsCategoryName()), + 'comments_info' => $this->getCommentsInfo($changeInfo->getCommentsCategoryName()), ])); } diff --git a/src/Http/Handlers/HomeHandler.php b/src/Http/Handlers/HomeHandler.php index 229039d..c662b77 100644 --- a/src/Http/Handlers/HomeHandler.php +++ b/src/Http/Handlers/HomeHandler.php @@ -1,13 +1,13 @@ context->getNews(); + $comments = $this->context->getComments(); $featuredNews = []; $userInfos = []; $categoryInfos = []; @@ -136,11 +137,8 @@ final class HomeHandler extends Handler { else $categoryInfos[$categoryId] = $categoryInfo = $news->getCategoryByPost($postInfo); - $commentsCount = 0; - if($postInfo->hasCommentsCategoryId()) - try { - $commentsCount = CommentsCategory::byId($postInfo->getCommentsCategoryId())->getPostCount(); - } catch(CommentsCategoryNotFoundException $ex) {} + $commentsCount = $postInfo->hasCommentsCategoryId() + ? $comments->countPosts($postInfo->getCommentsCategoryId(), includeReplies: true) : 0; $featuredNews[] = [ 'post' => $postInfo, diff --git a/src/Http/Handlers/NewsHandler.php b/src/Http/Handlers/NewsHandler.php index 56686e2..25d129f 100644 --- a/src/Http/Handlers/NewsHandler.php +++ b/src/Http/Handlers/NewsHandler.php @@ -7,7 +7,7 @@ use Misuzu\DB; use Misuzu\Pagination; use Misuzu\Template; use Misuzu\Comments\CommentsCategory; -use Misuzu\Comments\CommentsCategoryNotFoundException; +use Misuzu\Comments\CommentsEx; use Misuzu\Config\IConfig; use Misuzu\Feeds\Feed; use Misuzu\Feeds\FeedItem; @@ -21,6 +21,7 @@ use Misuzu\Users\UserNotFoundException; final class NewsHandler extends Handler { private function fetchPostInfo(array $postInfos, array $categoryInfos = []): array { $news = $this->context->getNews(); + $comments = $this->context->getComments(); $posts = []; $userInfos = []; @@ -45,11 +46,8 @@ final class NewsHandler extends Handler { else $categoryInfos[$categoryId] = $categoryInfo = $news->getCategoryByPost($postInfo); - $commentsCount = 0; - if($postInfo->hasCommentsCategoryId()) - try { - $commentsCount = CommentsCategory::byId($postInfo->getCommentsCategoryId())->getPostCount(); - } catch(CommentsCategoryNotFoundException $ex) {} + $commentsCount = $postInfo->hasCommentsCategoryId() + ? $comments->countPosts($postInfo->getCommentsCategoryId(), includeReplies: true) : 0; $posts[] = [ 'post' => $postInfo, @@ -116,6 +114,7 @@ final class NewsHandler extends Handler { public function viewPost($response, $request, string $postId) { $news = $this->context->getNews(); + $comments = $this->context->getComments(); try { $postInfo = $news->getPostById($postId); @@ -128,17 +127,13 @@ final class NewsHandler extends Handler { $categoryInfo = $news->getCategoryByPost($postInfo); + $comments = $this->context->getComments(); + if($postInfo->hasCommentsCategoryId()) { - $commentsCategory = CommentsCategory::byId($postInfo->getCommentsCategoryId()); + $commentsCategory = $comments->getCategoryById($postInfo->getCommentsCategoryId()); } else { - $commentsCategoryName = $postInfo->getCommentsCategoryName(); - try { - $commentsCategory = CommentsCategory::byName($commentsCategoryName); - } catch(CommentsCategoryNotFoundException $ex) { - $commentsCategory = new CommentsCategory($commentsCategoryName); - $commentsCategory->save(); - $news->updatePostCommentCategory($postInfo, $commentsCategory); - } + $commentsCategory = $comments->ensureCategory($postInfo->getCommentsCategoryName()); + $news->updatePostCommentCategory($postInfo, $commentsCategory); } $userInfo = null; @@ -147,12 +142,13 @@ final class NewsHandler extends Handler { $userInfo = User::byId($postInfo->getUserId()); } catch(UserNotFoundException $ex) {} + $comments = new CommentsEx($comments); + $response->setContent(Template::renderRaw('news.post', [ 'post_info' => $postInfo, 'post_category_info' => $categoryInfo, 'post_user_info' => $userInfo, - 'comments_info' => $commentsCategory, - 'comments_user' => User::getCurrent(), + 'comments_info' => $comments->getCommentsForLayout($commentsCategory), ])); } diff --git a/src/MisuzuContext.php b/src/MisuzuContext.php index b50c4a8..3efe72b 100644 --- a/src/MisuzuContext.php +++ b/src/MisuzuContext.php @@ -3,6 +3,7 @@ namespace Misuzu; use Misuzu\Template; use Misuzu\Changelog\Changelog; +use Misuzu\Comments\Comments; use Misuzu\Config\IConfig; use Misuzu\Emoticons\Emotes; use Misuzu\News\News; @@ -27,6 +28,7 @@ class MisuzuContext { private Emotes $emotes; private Changelog $changelog; private News $news; + private Comments $comments; public function __construct(IDbConnection $dbConn, IConfig $config) { $this->dbConn = $dbConn; @@ -35,6 +37,7 @@ class MisuzuContext { $this->emotes = new Emotes($this->dbConn); $this->changelog = new Changelog($this->dbConn); $this->news = new News($this->dbConn); + $this->comments = new Comments($this->dbConn); } public function getDbConn(): IDbConnection { @@ -78,6 +81,10 @@ class MisuzuContext { return $this->news; } + public function getComments(): Comments { + return $this->comments; + } + public function setUpHttp(bool $legacy = false): void { $this->router = new HttpFx; $this->router->use('/', function($response) { diff --git a/src/News/News.php b/src/News/News.php index f3a04da..5c87861 100644 --- a/src/News/News.php +++ b/src/News/News.php @@ -8,7 +8,7 @@ use Index\Data\IDbConnection; use Index\Data\IDbResult; use Misuzu\DbStatementCache; use Misuzu\Pagination; -use Misuzu\Comments\CommentsCategory; +use Misuzu\Comments\CommentsCategoryInfo; use Misuzu\Users\User; class News { @@ -465,17 +465,16 @@ class News { public function updatePostCommentCategory( NewsPostInfo|string $postInfo, - CommentsCategory|string $commentsCategory + CommentsCategoryInfo|string $commentsCategory ): void { if($postInfo instanceof NewsPostInfo) $postInfo = $postInfo->getId(); - if($commentsCategory instanceof CommentsCategory) - $commentsCategory = (string)$commentsCategory->getId(); + if($commentsCategory instanceof CommentsCategoryInfo) + $commentsCategory = $commentsCategory->getId(); - // "post_updated = post_updated" is an Attempt at making this not bump post_updated ON UPDATE - $stmt = $this->cache->get('UPDATE msz_news_posts SET comment_section_id = ?, post_updated = post_updated WHERE post_id = ?'); - $stmt->addParameter(1, $postInfo); - $stmt->addParameter(2, $commentsCategory); + $stmt = $this->cache->get('UPDATE msz_news_posts SET comment_section_id = ? WHERE post_id = ?'); + $stmt->addParameter(1, $commentsCategory); + $stmt->addParameter(2, $postInfo); $stmt->execute(); } } diff --git a/src/TwigMisuzu.php b/src/TwigMisuzu.php index e38b6a6..0cf2b10 100644 --- a/src/TwigMisuzu.php +++ b/src/TwigMisuzu.php @@ -5,10 +5,11 @@ use Twig\Extension\AbstractExtension; use Twig\TwigFilter; use Twig\TwigFunction; use Twig\Environment as TwigEnvironment; -use Misuzu\Parsers\Parser; -use Misuzu\MisuzuContext; use Index\ByteFormat; use Index\Environment; +use Misuzu\MisuzuContext; +use Misuzu\Comments\CommentsParser; +use Misuzu\Parsers\Parser; final class TwigMisuzu extends AbstractExtension { private MisuzuContext $ctx; @@ -22,6 +23,7 @@ final class TwigMisuzu extends AbstractExtension { new TwigFilter('html_colour', 'html_colour'), new TwigFilter('country_name', 'get_country_name'), new TwigFilter('parse_text', fn(string $text, int $parser): string => Parser::instance($parser)->parseText($text)), + new TwigFilter('parse_comment', fn(string $text): string => CommentsParser::parseForDisplay($text)), new TwigFilter('perms_check', 'perms_check'), new TwigFilter('time_diff', [$this, 'timeDiff'], ['needs_environment' => true]), ]; diff --git a/templates/_layout/comments.twig b/templates/_layout/comments.twig index 89c05e1..ee01b42 100644 --- a/templates/_layout/comments.twig +++ b/templates/_layout/comments.twig @@ -43,9 +43,27 @@ {% macro comments_entry(comment, indent, category, user) %} {% from 'macros.twig' import avatar %} {% from '_layout/input.twig' import input_checkbox_raw %} - {% set hide_details = comment.userId < 1 or comment.deleted and not user.commentPerms.can_delete_any|default(false) %} - {% if user.commentPerms.can_delete_any|default(false) or (not comment.deleted or comment.replies(user)|length > 0) %} + {% set replies = comment.replies %} + {% set poster = comment.user|default(null) %} + {% if comment.post is defined %} + {% set userVote = comment.vote.weight %} + {% set comment = comment.post %} + {% set body = comment.body %} + {% set likes = comment.votesPositive %} + {% set dislikes = comment.votesNegative %} + {% set isReply = comment.isReply %} + {% else %} + {% set body = comment.text %} + {% set userVote = comment.userVote %} + {% set likes = comment.likes %} + {% set dislikes = comment.dislikes %} + {% set isReply = comment.hasParent %} + {% endif %} + + {% set hide_details = poster is null or comment.deleted and not user.commentPerms.can_delete_any|default(false) %} + + {% if user.commentPerms.can_delete_any|default(false) or (not comment.deleted or replies|length > 0) %}
{% if hide_details %} @@ -53,16 +71,16 @@ {{ avatar(0, indent > 1 ? 40 : 50) }}
{% else %} - - {{ avatar(comment.user.id, indent > 1 ? 40 : 50, comment.user.username) }} + + {{ avatar(poster.id, indent > 1 ? 40 : 50, poster.username) }} {% endif %}
{% if not hide_details %} {{ comment.user.username }} + href="{{ url('user-profile', {'user': poster.id}) }}" + style="--user-colour: {{ poster.colour}}">{{ poster.username }} {% endif %}
{% if not comment.deleted and user is not null %} {% if user.commentPerms.can_vote|default(false) %} - {% set like_vote_state = comment.userVote > 0 ? 0 : 1 %} - {% set dislike_vote_state = comment.userVote < 0 ? 0 : -1 %} + {% set like_vote_state = userVote > 0 ? 0 : 1 %} + {% set dislike_vote_state = userVote < 0 ? 0 : -1 %} - Like - {% if comment.likes > 0 %} - ({{ comment.likes|number_format }}) + {% if likes > 0 %} + ({{ likes|number_format }}) {% endif %} - Dislike - {% if comment.dislikes > 0 %} - ({{ comment.dislikes|number_format }}) + {% if dislikes > 0 %} + ({{ dislikes|number_format }}) {% endif %} {% endif %} {% if user.commentPerms.can_comment|default(false) %} {% endif %} - {% if user.commentPerms.can_delete_any|default(false) or (comment.user.id|default(0) == user.id and user.commentPerms.can_delete|default(false)) %} + {% if user.commentPerms.can_delete_any|default(false) or (poster.id|default(0) == user.id and user.commentPerms.can_delete|default(false)) %} Delete {% endif %} {# if user is not null %} Report {% endif #} - {% if not comment.hasParent and user.commentPerms.can_pin|default(false) %} + {% if not isReply and user.commentPerms.can_pin|default(false) %} {{ comment.pinned ? 'Unpin' : 'Pin' }} {% endif %} {% elseif user.commentPerms.can_delete_any|default(false) %} @@ -132,8 +150,8 @@ {{ input_checkbox_raw('', false, 'comment__reply-toggle', '', false, {'id':'comment-reply-toggle-' ~ comment.id}) }} {{ comments_input(category, user, comment) }} {% endif %} - {% if comment.replies|length > 0 %} - {% for reply in comment.replies %} + {% if replies|length > 0 %} + {% for reply in replies %} {{ comments_entry(reply, indent + 1, category, user) }} {% endfor %} {% endif %} @@ -143,6 +161,14 @@ {% endmacro %} {% macro comments_section(category, user) %} + {% if category.category is defined %} + {% set user = category.user %} + {% set posts = category.posts %} + {% set category = category.category %} + {% else %} + {% set posts = category.posts %} + {% endif %} +
{% if user|default(null) is null %} @@ -180,9 +206,9 @@ #}
- {% if category.posts|length > 0 %} + {% if posts|length > 0 %} {% from _self import comments_entry %} - {% for comment in category.posts(user) %} + {% for comment in posts %} {{ comments_entry(comment, 1, category, user) }} {% endfor %} {% else %} diff --git a/templates/changelog/change.twig b/templates/changelog/change.twig index 16eeb20..dcc26b2 100644 --- a/templates/changelog/change.twig +++ b/templates/changelog/change.twig @@ -69,6 +69,6 @@
{{ container_title(' Comments for ' ~ change_info.date) }} - {{ comments_section(comments_category, comments_user) }} + {{ comments_section(comments_info) }}
{% endblock %} diff --git a/templates/changelog/index.twig b/templates/changelog/index.twig index 9d59ba7..3cbdf13 100644 --- a/templates/changelog/index.twig +++ b/templates/changelog/index.twig @@ -58,7 +58,7 @@ {% if is_date %}
{{ container_title(' Comments') }} - {{ comments_section(comments_category, comments_user) }} + {{ comments_section(comments_info) }}
{% endif %} {% endblock %} diff --git a/templates/news/post.twig b/templates/news/post.twig index 7bfe87d..3c90573 100644 --- a/templates/news/post.twig +++ b/templates/news/post.twig @@ -13,7 +13,7 @@ {% if comments_info is defined %}
{{ container_title(' Comments') }} - {{ comments_section(comments_info, comments_user) }} + {{ comments_section(comments_info) }}
{% endif %} {% endblock %}