Rewrote most of the comments backend.g
This commit is contained in:
parent
6274f7f8d3
commit
f24f811acc
22 changed files with 1012 additions and 888 deletions
|
@ -1,12 +1,10 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
use RuntimeException;
|
||||
use Misuzu\AuditLog;
|
||||
use Misuzu\Comments\CommentsCategory;
|
||||
use Misuzu\Comments\CommentsCategoryNotFoundException;
|
||||
use Misuzu\Comments\CommentsPost;
|
||||
use Misuzu\Comments\CommentsPostNotFoundException;
|
||||
use Misuzu\Comments\CommentsPostSaveFailedException;
|
||||
use Misuzu\Comments\CommentsVote;
|
||||
use Misuzu\Users\User;
|
||||
use Misuzu\Users\UserNotFoundException;
|
||||
|
@ -42,82 +40,97 @@ if($currentUserInfo->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(),
|
||||
'<username>',
|
||||
]);
|
||||
} 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(),
|
||||
'<username>',
|
||||
]);
|
||||
|
||||
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:
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
use RuntimeException;
|
||||
use Misuzu\Comments\CommentsCategory;
|
||||
use Misuzu\Comments\CommentsCategoryNotFoundException;
|
||||
use Misuzu\Users\User;
|
||||
|
||||
require_once '../misuzu.php';
|
||||
|
@ -15,6 +15,7 @@ if(!empty($searchQuery)) {
|
|||
|
||||
// this sure is an expansion
|
||||
$news = $msz->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,
|
||||
|
|
524
src/Comments/Comments.php
Normal file
524
src/Comments/Comments.php
Normal file
|
@ -0,0 +1,524 @@
|
|||
<?php
|
||||
namespace Misuzu\Comments;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use Index\Data\IDbConnection;
|
||||
use Index\Data\IDbResult;
|
||||
use Misuzu\DbStatementCache;
|
||||
use Misuzu\Pagination;
|
||||
use Misuzu\Comments\CommentsCategory;
|
||||
use Misuzu\Users\User;
|
||||
|
||||
class Comments {
|
||||
private IDbConnection $dbConn;
|
||||
private DbStatementCache $cache;
|
||||
|
||||
public function __construct(IDbConnection $dbConn) {
|
||||
$this->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();
|
||||
}
|
||||
}
|
|
@ -1,167 +0,0 @@
|
|||
<?php
|
||||
namespace Misuzu\Comments;
|
||||
|
||||
use Misuzu\DB;
|
||||
use Misuzu\Memoizer;
|
||||
use Misuzu\Pagination;
|
||||
use Misuzu\Users\User;
|
||||
|
||||
class CommentsCategoryException extends CommentsException {};
|
||||
class CommentsCategoryNotFoundException extends CommentsCategoryException {};
|
||||
|
||||
class CommentsCategory {
|
||||
// Database fields
|
||||
private $category_id = -1;
|
||||
private $category_name = '';
|
||||
private $owner_id = null;
|
||||
private $category_created = null;
|
||||
private $category_locked = null;
|
||||
|
||||
private $postCount = -1;
|
||||
private $owner = null;
|
||||
|
||||
public const TABLE = 'comments_categories';
|
||||
private const QUERY_SELECT = 'SELECT %1$s FROM `' . DB::PREFIX . self::TABLE . '` AS '. self::TABLE;
|
||||
private const SELECT = '%1$s.`category_id`, %1$s.`category_name`, %1$s.`owner_id`'
|
||||
. ', UNIX_TIMESTAMP(%1$s.`category_created`) AS `category_created`'
|
||||
. ', UNIX_TIMESTAMP(%1$s.`category_locked`) AS `category_locked`';
|
||||
|
||||
public function __construct(?string $name = null) {
|
||||
if($name !== null)
|
||||
$this->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);
|
||||
}
|
||||
}
|
72
src/Comments/CommentsCategoryInfo.php
Normal file
72
src/Comments/CommentsCategoryInfo.php
Normal file
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
namespace Misuzu\Comments;
|
||||
|
||||
use Index\DateTime;
|
||||
use Index\Data\IDbResult;
|
||||
use Misuzu\Users\User;
|
||||
|
||||
class CommentsCategoryInfo {
|
||||
private string $id;
|
||||
private string $name;
|
||||
private ?string $ownerId;
|
||||
private int $created;
|
||||
private ?int $locked;
|
||||
private int $comments;
|
||||
|
||||
public function __construct(IDbResult $result) {
|
||||
$this->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;
|
||||
}
|
||||
}
|
61
src/Comments/CommentsEx.php
Normal file
61
src/Comments/CommentsEx.php
Normal file
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
namespace Misuzu\Comments;
|
||||
|
||||
use stdClass;
|
||||
use Misuzu\Users\User;
|
||||
use Misuzu\Users\UserNotFoundException;
|
||||
|
||||
class CommentsEx {
|
||||
private Comments $comments;
|
||||
private array $userInfos;
|
||||
|
||||
public function __construct(Comments $comments, array $userInfos = []) {
|
||||
$this->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;
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
<?php
|
||||
namespace Misuzu\Comments;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class CommentsException extends RuntimeException {}
|
|
@ -1,326 +0,0 @@
|
|||
<?php
|
||||
namespace Misuzu\Comments;
|
||||
|
||||
use Misuzu\DB;
|
||||
use Misuzu\Pagination;
|
||||
use Misuzu\Users\User;
|
||||
use Misuzu\Users\UserNotFoundException;
|
||||
|
||||
class CommentsPostException extends CommentsException {}
|
||||
class CommentsPostNotFoundException extends CommentsPostException {}
|
||||
class CommentsPostHasNoParentException extends CommentsPostException {}
|
||||
class CommentsPostSaveFailedException extends CommentsPostException {}
|
||||
|
||||
class CommentsPost {
|
||||
// Database fields
|
||||
private $comment_id = -1;
|
||||
private $category_id = -1;
|
||||
private $user_id = null;
|
||||
private $comment_reply_to = null;
|
||||
private $comment_text = '';
|
||||
private $comment_created = null;
|
||||
private $comment_pinned = null;
|
||||
private $comment_edited = null;
|
||||
private $comment_deleted = null;
|
||||
|
||||
// Virtual fields
|
||||
private $comment_likes = -1;
|
||||
private $comment_dislikes = -1;
|
||||
private $user_vote = null;
|
||||
|
||||
private $category = null;
|
||||
private $user = null;
|
||||
private $userLookedUp = false;
|
||||
private $parentPost = null;
|
||||
|
||||
public const TABLE = 'comments_posts';
|
||||
private const QUERY_SELECT = 'SELECT %1$s FROM `' . DB::PREFIX . self::TABLE . '` AS '. self::TABLE;
|
||||
private const SELECT = '%1$s.`comment_id`, %1$s.`category_id`, %1$s.`user_id`, %1$s.`comment_reply_to`, %1$s.`comment_text`'
|
||||
. ', UNIX_TIMESTAMP(%1$s.`comment_created`) AS `comment_created`'
|
||||
. ', UNIX_TIMESTAMP(%1$s.`comment_pinned`) AS `comment_pinned`'
|
||||
. ', UNIX_TIMESTAMP(%1$s.`comment_edited`) AS `comment_edited`'
|
||||
. ', UNIX_TIMESTAMP(%1$s.`comment_deleted`) AS `comment_deleted`';
|
||||
private const LIKE_VOTE_SELECT = '(SELECT COUNT(`comment_id`) FROM `' . DB::PREFIX . CommentsVote::TABLE . '` WHERE `comment_id` = %1$s.`comment_id` AND `comment_vote` = ' . CommentsVote::LIKE . ') AS `comment_likes`';
|
||||
private const DISLIKE_VOTE_SELECT = '(SELECT COUNT(`comment_id`) FROM `' . DB::PREFIX . CommentsVote::TABLE . '` WHERE `comment_id` = %1$s.`comment_id` AND `comment_vote` = ' . CommentsVote::DISLIKE . ') AS `comment_dislikes`';
|
||||
private const USER_VOTE_SELECT = '(SELECT `comment_vote` FROM `' . DB::PREFIX . CommentsVote::TABLE . '` WHERE `comment_id` = %1$s.`comment_id` AND `user_id` = :user) AS `user_vote`';
|
||||
|
||||
public function getId(): int {
|
||||
return $this->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);
|
||||
}
|
||||
}
|
146
src/Comments/CommentsPostInfo.php
Normal file
146
src/Comments/CommentsPostInfo.php
Normal file
|
@ -0,0 +1,146 @@
|
|||
<?php
|
||||
namespace Misuzu\Comments;
|
||||
|
||||
use Index\DateTime;
|
||||
use Index\Data\IDbResult;
|
||||
|
||||
class CommentsPostInfo {
|
||||
private string $id;
|
||||
private string $categoryId;
|
||||
private ?string $userId;
|
||||
private ?string $replyingTo;
|
||||
private string $body;
|
||||
private int $created;
|
||||
private ?int $pinned;
|
||||
private ?int $updated;
|
||||
private ?int $deleted;
|
||||
private int $replies;
|
||||
private int $votesTotal;
|
||||
private int $votesPositive;
|
||||
private int $votesNegative;
|
||||
|
||||
public function __construct(
|
||||
IDbResult $result,
|
||||
bool $includeRepliesCount = false,
|
||||
bool $includeVotesCount = false
|
||||
) {
|
||||
$args = 0;
|
||||
$this->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;
|
||||
}
|
||||
}
|
28
src/Comments/CommentsPostVoteInfo.php
Normal file
28
src/Comments/CommentsPostVoteInfo.php
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
namespace Misuzu\Comments;
|
||||
|
||||
use Index\Data\IDbResult;
|
||||
|
||||
class CommentsPostVoteInfo {
|
||||
private string $commentId;
|
||||
private string $userId;
|
||||
private int $weight;
|
||||
|
||||
public function __construct(IDbResult $result) {
|
||||
$this->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;
|
||||
}
|
||||
}
|
|
@ -1,228 +0,0 @@
|
|||
<?php
|
||||
namespace Misuzu\Comments;
|
||||
|
||||
use Misuzu\DB;
|
||||
use Misuzu\Pagination;
|
||||
use Misuzu\Users\User;
|
||||
|
||||
class CommentsVoteException extends CommentsException {}
|
||||
class CommentsVoteCountFailedException extends CommentsVoteException {}
|
||||
class CommentsVoteCreateFailedException extends CommentsVoteException {}
|
||||
|
||||
class CommentsVoteCount {
|
||||
private $comment_id = -1;
|
||||
private $likes = 0;
|
||||
private $dislikes = 0;
|
||||
private $total = 0;
|
||||
|
||||
public function getPostId(): int {
|
||||
return $this->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);
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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()),
|
||||
]));
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<?php
|
||||
namespace Misuzu\Http\Handlers;
|
||||
|
||||
use RuntimeException;
|
||||
use Misuzu\Config;
|
||||
use Misuzu\Config\IConfig;
|
||||
use Misuzu\DB;
|
||||
use Misuzu\Pagination;
|
||||
use Misuzu\Template;
|
||||
use Misuzu\Comments\CommentsCategory;
|
||||
use Misuzu\Comments\CommentsCategoryNotFoundException;
|
||||
use Misuzu\Users\User;
|
||||
use Misuzu\Users\UserSession;
|
||||
use Misuzu\Users\UserNotFoundException;
|
||||
|
@ -107,6 +107,7 @@ final class HomeHandler extends Handler {
|
|||
|
||||
public function home($response, $request): void {
|
||||
$news = $this->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,
|
||||
|
|
|
@ -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),
|
||||
]));
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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]),
|
||||
];
|
||||
|
|
|
@ -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) %}
|
||||
<div class="comment{% if comment.deleted %} comment--deleted{% endif %}" id="comment-{{ comment.id }}">
|
||||
<div class="comment__container">
|
||||
{% if hide_details %}
|
||||
|
@ -53,16 +71,16 @@
|
|||
{{ avatar(0, indent > 1 ? 40 : 50) }}
|
||||
</div>
|
||||
{% else %}
|
||||
<a class="comment__avatar" href="{{ url('user-profile', {'user':comment.user.id}) }}">
|
||||
{{ avatar(comment.user.id, indent > 1 ? 40 : 50, comment.user.username) }}
|
||||
<a class="comment__avatar" href="{{ url('user-profile', {'user': poster.id}) }}">
|
||||
{{ avatar(poster.id, indent > 1 ? 40 : 50, poster.username) }}
|
||||
</a>
|
||||
{% endif %}
|
||||
<div class="comment__content">
|
||||
<div class="comment__info">
|
||||
{% if not hide_details %}
|
||||
<a class="comment__user comment__user--link"
|
||||
href="{{ url('user-profile', {'user':comment.user.id}) }}"
|
||||
style="--user-colour: {{ comment.user.colour}}">{{ comment.user.username }}</a>
|
||||
href="{{ url('user-profile', {'user': poster.id}) }}"
|
||||
style="--user-colour: {{ poster.colour}}">{{ poster.username }}</a>
|
||||
{% endif %}
|
||||
<a class="comment__link" href="#comment-{{ comment.id }}">
|
||||
<time class="comment__date"
|
||||
|
@ -84,39 +102,39 @@
|
|||
{% endif %}
|
||||
</div>
|
||||
<div class="comment__text">
|
||||
{{ hide_details ? '(deleted)' : comment.parsedText|raw }}
|
||||
{{ hide_details ? '(deleted)' : body|parse_comment|raw }}
|
||||
</div>
|
||||
<div class="comment__actions">
|
||||
{% 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 %}
|
||||
|
||||
<a class="comment__action comment__action--link comment__action--vote comment__action--like{% if comment.userVote > 0 %} comment__action--voted{% endif %}" data-comment-id="{{ comment.id }}" data-comment-vote="{{ like_vote_state }}"
|
||||
<a class="comment__action comment__action--link comment__action--vote comment__action--like{% if userVote > 0 %} comment__action--voted{% endif %}" data-comment-id="{{ comment.id }}" data-comment-vote="{{ like_vote_state }}"
|
||||
href="{{ url('comment-vote', {'comment':comment.id,'vote':like_vote_state}) }}">
|
||||
Like
|
||||
{% if comment.likes > 0 %}
|
||||
({{ comment.likes|number_format }})
|
||||
{% if likes > 0 %}
|
||||
({{ likes|number_format }})
|
||||
{% endif %}
|
||||
</a>
|
||||
<a class="comment__action comment__action--link comment__action--vote comment__action--dislike{% if comment.userVote < 0 %} comment__action--voted{% endif %}" data-comment-id="{{ comment.id }}" data-comment-vote="{{ dislike_vote_state }}"
|
||||
<a class="comment__action comment__action--link comment__action--vote comment__action--dislike{% if userVote < 0 %} comment__action--voted{% endif %}" data-comment-id="{{ comment.id }}" data-comment-vote="{{ dislike_vote_state }}"
|
||||
href="{{ url('comment-vote', {'comment':comment.id,'vote':dislike_vote_state}) }}">
|
||||
Dislike
|
||||
{% if comment.dislikes > 0 %}
|
||||
({{ comment.dislikes|number_format }})
|
||||
{% if dislikes > 0 %}
|
||||
({{ dislikes|number_format }})
|
||||
{% endif %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if user.commentPerms.can_comment|default(false) %}
|
||||
<label class="comment__action comment__action--link" for="comment-reply-toggle-{{ comment.id }}">Reply</label>
|
||||
{% 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)) %}
|
||||
<a class="comment__action comment__action--link comment__action--hide comment__action--delete" data-comment-id="{{ comment.id }}" href="{{ url('comment-delete', {'comment':comment.id}) }}">Delete</a>
|
||||
{% endif %}
|
||||
{# if user is not null %}
|
||||
<a class="comment__action comment__action--link comment__action--hide" href="#">Report</a>
|
||||
{% endif #}
|
||||
{% if not comment.hasParent and user.commentPerms.can_pin|default(false) %}
|
||||
{% if not isReply and user.commentPerms.can_pin|default(false) %}
|
||||
<a class="comment__action comment__action--link comment__action--hide comment__action--pin" data-comment-id="{{ comment.id }}" data-comment-pinned="{{ comment.pinned ? '1' : '0' }}" href="{{ url('comment-' ~ (comment.pinned ? 'unpin' : 'pin'), {'comment':comment.id}) }}">{{ comment.pinned ? 'Unpin' : 'Pin' }}</a>
|
||||
{% 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 %}
|
||||
|
||||
<div class="comments" id="comments">
|
||||
<div class="comments__input">
|
||||
{% if user|default(null) is null %}
|
||||
|
@ -180,9 +206,9 @@
|
|||
</noscript>#}
|
||||
|
||||
<div class="comments__listing">
|
||||
{% 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 %}
|
||||
|
|
|
@ -69,6 +69,6 @@
|
|||
|
||||
<div class="container">
|
||||
{{ container_title('<i class="fas fa-comments fa-fw"></i> Comments for ' ~ change_info.date) }}
|
||||
{{ comments_section(comments_category, comments_user) }}
|
||||
{{ comments_section(comments_info) }}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -58,7 +58,7 @@
|
|||
{% if is_date %}
|
||||
<div class="container">
|
||||
{{ container_title('<i class="fas fa-comments fa-fw"></i> Comments') }}
|
||||
{{ comments_section(comments_category, comments_user) }}
|
||||
{{ comments_section(comments_info) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
{% if comments_info is defined %}
|
||||
<div class="container">
|
||||
{{ container_title('<i class="fas fa-comments fa-fw"></i> Comments') }}
|
||||
{{ comments_section(comments_info, comments_user) }}
|
||||
{{ comments_section(comments_info) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
Loading…
Reference in a new issue