misuzu/src/Comments/Comments.php
flash 383e2ed0e0 Rewrote the user information class.
This one took multiple days and it pretty invasive into the core of Misuzu so issue might (will) arise, there's also some features that have gone temporarily missing in the mean time and some inefficiencies introduced that will be fixed again at a later time.
The old class isn't gone entirely because I still have to figure out what I'm gonna do about validation, but for the most part this knocks out one of the "layers of backwards compatibility", as I've been referring to it, and is moving us closer to a future where Flashii actually gets real updates.
If you run into anything that's broken and you're inhibited from reporting it through the forum, do it through chat or mail me at flashii-issues@flash.moe.
2023-08-02 22:12:47 +00:00

524 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\UserInfo;
class Comments {
private IDbConnection $dbConn;
private DbStatementCache $cache;
public function __construct(IDbConnection $dbConn) {
$this->dbConn = $dbConn;
$this->cache = new DbStatementCache($dbConn);
}
public function countAllCategories(UserInfo|string|null $owner = null): int {
if($owner instanceof UserInfo)
$owner = $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(
UserInfo|string|null $owner = null,
?Pagination $pagination = null
): array {
if($owner instanceof UserInfo)
$owner = $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, UserInfo|string|null $owner = null): CommentsCategoryInfo {
if($this->checkCategoryNameExists($name))
return $this->getCategoryByName($name);
return $this->createCategory($name, $owner);
}
public function createCategory(string $name, UserInfo|string|null $owner = null): CommentsCategoryInfo {
if($owner instanceof UserInfo)
$owner = $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,
UserInfo|string|null $owner = null
): void {
if($category instanceof CommentsCategoryInfo)
$category = $category->getId();
if($owner instanceof UserInfo)
$owner = $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,
UserInfo|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 UserInfo)
$user = $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,
UserInfo|string|null $user
): CommentsPostVoteInfo {
if($post instanceof CommentsPostInfo)
$post = $post->getId();
if($user instanceof UserInfo)
$user = $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,
UserInfo|string $user,
int $weight
): void {
if($weight === 0)
return;
if($post instanceof CommentsPostInfo)
$post = $post->getId();
if($user instanceof UserInfo)
$user = $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, UserInfo|string $user): void {
$this->addPostVote($post, $user, 1);
}
public function addPostNegativeVote(CommentsPostInfo|string $post, UserInfo|string $user): void {
$this->addPostVote($post, $user, -1);
}
public function removePostVote(
CommentsPostInfo|string $post,
UserInfo|string $user
): void {
if($post instanceof CommentsPostInfo)
$post = $post->getId();
if($user instanceof UserInfo)
$user = $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();
}
}