523 lines
20 KiB
PHP
523 lines
20 KiB
PHP
<?php
|
|
namespace Misuzu\Comments;
|
|
|
|
use InvalidArgumentException;
|
|
use RuntimeException;
|
|
use Index\Data\DbStatementCache;
|
|
use Index\Data\IDbConnection;
|
|
use Index\Data\IDbResult;
|
|
use Misuzu\Pagination;
|
|
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) {
|
|
++$args;
|
|
$query .= ' WHERE comment_reply_to = ?';
|
|
} else {
|
|
if($hasCategory) {
|
|
++$args;
|
|
$query .= ' WHERE 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) {
|
|
++$args;
|
|
$query .= ' WHERE comment_reply_to = ?';
|
|
} else {
|
|
if($hasCategory) {
|
|
++$args;
|
|
$query .= ' WHERE 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();
|
|
}
|
|
}
|