Made data source argument lists for News, Changelog, Comments and Emotes consistent with the rest.

This commit is contained in:
flash 2023-08-05 13:50:15 +00:00
parent 87915b6a25
commit d4f6990e8a
27 changed files with 334 additions and 433 deletions

View file

@ -38,13 +38,13 @@ $commentVote = (int)filter_input(INPUT_GET, 'v', FILTER_SANITIZE_NUMBER_INT);
if(!empty($commentId)) { if(!empty($commentId)) {
try { try {
$commentInfo = $comments->getPostById($commentId); $commentInfo = $comments->getPost($commentId);
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
echo render_info('Post not found.', 404); echo render_info('Post not found.', 404);
return; return;
} }
$categoryInfo = $comments->getCategoryByPost($commentInfo); $categoryInfo = $comments->getCategory(postInfo: $commentInfo);
} }
if($commentMode !== 'create' && empty($commentInfo)) { if($commentMode !== 'create' && empty($commentInfo)) {
@ -186,7 +186,7 @@ switch($commentMode) {
$categoryId = isset($_POST['comment']['category']) && is_string($_POST['comment']['category']) $categoryId = isset($_POST['comment']['category']) && is_string($_POST['comment']['category'])
? (int)$_POST['comment']['category'] ? (int)$_POST['comment']['category']
: 0; : 0;
$categoryInfo = $comments->getCategoryById($categoryId); $categoryInfo = $comments->getCategory(categoryId: $categoryId);
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
echo render_info('This comment category doesn\'t exist.', 404); echo render_info('This comment category doesn\'t exist.', 404);
break; break;
@ -227,7 +227,7 @@ switch($commentMode) {
if($commentReply > 0) { if($commentReply > 0) {
try { try {
$parentInfo = $comments->getPostById($commentReply); $parentInfo = $comments->getPost($commentReply);
} catch(RuntimeException $ex) {} } catch(RuntimeException $ex) {}
if(!isset($parentInfo) || $parentInfo->isDeleted()) { if(!isset($parentInfo) || $parentInfo->isDeleted()) {

View file

@ -4,6 +4,7 @@ namespace Misuzu;
use DateTimeInterface; use DateTimeInterface;
use RuntimeException; use RuntimeException;
use Index\DateTime; use Index\DateTime;
use Index\XArray;
use Misuzu\Changelog\Changelog; use Misuzu\Changelog\Changelog;
if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_CHANGELOG, $msz->getActiveUser()->getId(), MSZ_PERM_CHANGELOG_MANAGE_CHANGES)) { if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_CHANGELOG, $msz->getActiveUser()->getId(), MSZ_PERM_CHANGELOG_MANAGE_CHANGES)) {
@ -17,15 +18,17 @@ foreach(Changelog::ACTIONS as $action)
$changelog = $msz->getChangelog(); $changelog = $msz->getChangelog();
$changeId = (string)filter_input(INPUT_GET, 'c', FILTER_SANITIZE_NUMBER_INT); $changeId = (string)filter_input(INPUT_GET, 'c', FILTER_SANITIZE_NUMBER_INT);
$loadChangeInfo = fn() => $changelog->getChangeById($changeId, withTags: true); $changeInfo = null;
$changeTags = $changelog->getAllTags(); $changeTagIds = [];
$tagInfos = $changelog->getTags();
if(empty($changeId)) if(empty($changeId))
$isNew = true; $isNew = true;
else else
try { try {
$isNew = false; $isNew = false;
$changeInfo = $loadChangeInfo(); $changeInfo = $changelog->getChange($changeId);
$changeTagIds = XArray::select($changelog->getTags(changeInfo: $changeInfo), fn($tagInfo) => $tagInfo->getId());
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
echo render_error(404); echo render_error(404);
return; return;
@ -78,7 +81,7 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
} }
if(!empty($tags)) { if(!empty($tags)) {
$tCurrent = $changeInfo->getTagIds(); $tCurrent = $changeTagIds;
$tApply = $tags; $tApply = $tags;
$tRemove = []; $tRemove = [];
@ -102,17 +105,15 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
[$changeInfo->getId()] [$changeInfo->getId()]
); );
if($isNew) { url_redirect('manage-changelog-change', ['change' => $changeInfo->getId()]);
url_redirect('manage-changelog-change', ['change' => $changeInfo->getId()]); return;
return;
} else $changeInfo = $loadChangeInfo();
break;
} }
Template::render('manage.changelog.change', [ Template::render('manage.changelog.change', [
'change_new' => $isNew, 'change_new' => $isNew,
'change_info' => $changeInfo ?? null, 'change_info' => $changeInfo,
'change_tags' => $changeTags, 'change_info_tags' => $changeTagIds,
'change_tags' => $tagInfos,
'change_actions' => $changeActions, 'change_actions' => $changeActions,
'change_author_id' => $msz->getActiveUser()->getId(), 'change_author_id' => $msz->getActiveUser()->getId(),
]); ]);

View file

@ -9,14 +9,14 @@ if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_CHANGELOG, $msz->getActive
} }
$changelog = $msz->getChangelog(); $changelog = $msz->getChangelog();
$changelogPagination = new Pagination($changelog->countAllChanges(), 30); $changelogPagination = new Pagination($changelog->countChanges(), 30);
if(!$changelogPagination->hasValidOffset()) { if(!$changelogPagination->hasValidOffset()) {
echo render_error(404); echo render_error(404);
return; return;
} }
$changeInfos = $changelog->getAllChanges(withTags: true, pagination: $changelogPagination); $changeInfos = $changelog->getChanges(pagination: $changelogPagination);
$changes = []; $changes = [];
$userInfos = []; $userInfos = [];
$userColours = []; $userColours = [];
@ -39,6 +39,7 @@ foreach($changeInfos as $changeInfo) {
$changes[] = [ $changes[] = [
'change' => $changeInfo, 'change' => $changeInfo,
'tags' => $changelog->getTags(changeInfo: $changeInfo),
'user' => $userInfo, 'user' => $userInfo,
'user_colour' => $userColours[$userId] ?? \Index\Colour\Colour::none(), 'user_colour' => $userColours[$userId] ?? \Index\Colour\Colour::none(),
]; ];

View file

@ -10,7 +10,7 @@ if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_CHANGELOG, $msz->getActive
$changelog = $msz->getChangelog(); $changelog = $msz->getChangelog();
$tagId = (string)filter_input(INPUT_GET, 't', FILTER_SANITIZE_NUMBER_INT); $tagId = (string)filter_input(INPUT_GET, 't', FILTER_SANITIZE_NUMBER_INT);
$loadTagInfo = fn() => $changelog->getTagById($tagId); $loadTagInfo = fn() => $changelog->getTag($tagId);
if(empty($tagId)) if(empty($tagId))
$isNew = true; $isNew = true;

View file

@ -7,5 +7,5 @@ if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_CHANGELOG, $msz->getActive
} }
Template::render('manage.changelog.tags', [ Template::render('manage.changelog.tags', [
'changelog_tags' => $msz->getChangelog()->getAllTags(), 'changelog_tags' => $msz->getChangelog()->getTags(),
]); ]);

View file

@ -2,6 +2,7 @@
namespace Misuzu; namespace Misuzu;
use RuntimeException; use RuntimeException;
use Index\XArray;
if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_GENERAL, $msz->getActiveUser()->getId(), MSZ_PERM_GENERAL_MANAGE_EMOTES)) { if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_GENERAL, $msz->getActiveUser()->getId(), MSZ_PERM_GENERAL_MANAGE_EMOTES)) {
echo render_error(403); echo render_error(403);
@ -10,14 +11,14 @@ if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_GENERAL, $msz->getActiveUs
$emotes = $msz->getEmotes(); $emotes = $msz->getEmotes();
$emoteId = (string)filter_input(INPUT_GET, 'e', FILTER_SANITIZE_NUMBER_INT); $emoteId = (string)filter_input(INPUT_GET, 'e', FILTER_SANITIZE_NUMBER_INT);
$loadEmoteInfo = fn() => $emotes->getEmoteById($emoteId, true);
if(empty($emoteId)) if(empty($emoteId))
$isNew = true; $isNew = true;
else else
try { try {
$isNew = false; $isNew = false;
$emoteInfo = $loadEmoteInfo(); $emoteInfo = $emotes->getEmote($emoteId);
$emoteStrings = $emotes->getEmoteStrings($emoteInfo);
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
echo render_error(404); echo render_error(404);
return; return;
@ -60,7 +61,7 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
$emotes->updateEmote($emoteInfo, $order, $minRank, $url); $emotes->updateEmote($emoteInfo, $order, $minRank, $url);
} }
$sCurrent = $emoteInfo->getStringsRaw(); $sCurrent = XArray::select($emoteStrings, fn($stringInfo) => $stringInfo->getString());
$sApply = $strings; $sApply = $strings;
$sRemove = []; $sRemove = [];
@ -97,14 +98,12 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
[$emoteInfo->getId()] [$emoteInfo->getId()]
); );
if($isNew) { url_redirect('manage-general-emoticon', ['emote' => $emoteInfo->getId()]);
url_redirect('manage-general-emoticon', ['emote' => $emoteInfo->getId()]); return;
return;
} else $emoteInfo = $loadEmoteInfo();
break;
} }
Template::render('manage.general.emoticon', [ Template::render('manage.general.emoticon', [
'emote_new' => $isNew, 'emote_new' => $isNew,
'emote_info' => $emoteInfo ?? null, 'emote_info' => $emoteInfo ?? null,
'emote_strings' => $emoteStrings ?? [],
]); ]);

View file

@ -14,7 +14,7 @@ if(CSRF::validateRequest() && !empty($_GET['emote'])) {
$emoteId = (string)filter_input(INPUT_GET, 'emote', FILTER_SANITIZE_NUMBER_INT); $emoteId = (string)filter_input(INPUT_GET, 'emote', FILTER_SANITIZE_NUMBER_INT);
try { try {
$emoteInfo = $emotes->getEmoteById($emoteId); $emoteInfo = $emotes->getEmote($emoteId);
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
echo render_error(404); echo render_error(404);
return; return;
@ -45,5 +45,5 @@ if(CSRF::validateRequest() && !empty($_GET['emote'])) {
} }
Template::render('manage.general.emoticons', [ Template::render('manage.general.emoticons', [
'emotes' => $emotes->getAllEmotes(), 'emotes' => $emotes->getEmotes(),
]); ]);

View file

@ -7,14 +7,14 @@ if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_NEWS, $msz->getActiveUser(
} }
$news = $msz->getNews(); $news = $msz->getNews();
$pagination = new Pagination($news->countAllCategories(true), 15); $pagination = new Pagination($news->countCategories(), 15);
if(!$pagination->hasValidOffset()) { if(!$pagination->hasValidOffset()) {
echo render_error(404); echo render_error(404);
return; return;
} }
$categories = $news->getAllCategories(true, $pagination); $categories = $news->getCategories(pagination: $pagination);
Template::render('manage.news.categories', [ Template::render('manage.news.categories', [
'news_categories' => $categories, 'news_categories' => $categories,

View file

@ -10,7 +10,7 @@ if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_NEWS, $msz->getActiveUser(
$news = $msz->getNews(); $news = $msz->getNews();
$categoryId = (string)filter_input(INPUT_GET, 'c', FILTER_SANITIZE_NUMBER_INT); $categoryId = (string)filter_input(INPUT_GET, 'c', FILTER_SANITIZE_NUMBER_INT);
$loadCategoryInfo = fn() => $news->getCategoryById($categoryId); $loadCategoryInfo = fn() => $news->getCategory(categoryId: $categoryId);
if(empty($categoryId)) if(empty($categoryId))
$isNew = true; $isNew = true;

View file

@ -10,7 +10,7 @@ if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_NEWS, $msz->getActiveUser(
$news = $msz->getNews(); $news = $msz->getNews();
$postId = (string)filter_input(INPUT_GET, 'p', FILTER_SANITIZE_NUMBER_INT); $postId = (string)filter_input(INPUT_GET, 'p', FILTER_SANITIZE_NUMBER_INT);
$loadPostInfo = fn() => $news->getPostById($postId); $loadPostInfo = fn() => $news->getPost($postId);
if(empty($postId)) if(empty($postId))
$isNew = true; $isNew = true;
@ -71,7 +71,7 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
} }
$categories = []; $categories = [];
foreach($news->getAllCategories(true) as $categoryInfo) foreach($news->getCategories() as $categoryInfo)
$categories[$categoryInfo->getId()] = $categoryInfo->getName(); $categories[$categoryInfo->getId()] = $categoryInfo->getName();
Template::render('manage.news.post', [ Template::render('manage.news.post', [

View file

@ -7,7 +7,7 @@ if(!$msz->isLoggedIn() || !perms_check_user(MSZ_PERMS_NEWS, $msz->getActiveUser(
} }
$news = $msz->getNews(); $news = $msz->getNews();
$pagination = new Pagination($news->countAllPosts( $pagination = new Pagination($news->countPosts(
includeScheduled: true, includeScheduled: true,
includeDeleted: true includeDeleted: true
), 15); ), 15);

View file

@ -20,7 +20,7 @@ if(!empty($searchQuery)) {
$users = $msz->getUsers(); $users = $msz->getUsers();
$comments = $msz->getComments(); $comments = $msz->getComments();
$newsPosts = []; $newsPosts = [];
$newsPostInfos = $news->getPostsBySearchQuery($searchQuery); $newsPostInfos = $news->getPosts(searchQuery: $searchQuery);
$newsUserInfos = []; $newsUserInfos = [];
$newsUserColours = []; $newsUserColours = [];
$newsCategoryInfos = []; $newsCategoryInfos = [];
@ -45,10 +45,10 @@ if(!empty($searchQuery)) {
if(array_key_exists($categoryId, $newsCategoryInfos)) if(array_key_exists($categoryId, $newsCategoryInfos))
$categoryInfo = $newsCategoryInfos[$categoryId]; $categoryInfo = $newsCategoryInfos[$categoryId];
else else
$newsCategoryInfos[$categoryId] = $categoryInfo = $news->getCategoryByPost($postInfo); $newsCategoryInfos[$categoryId] = $categoryInfo = $news->getCategory(postInfo: $postInfo);
$commentsCount = $postInfo->hasCommentsCategoryId() $commentsCount = $postInfo->hasCommentsCategoryId()
? $comments->countPosts($postInfo->getCommentsCategoryId(), includeReplies: true) : 0; ? $comments->countPosts(categoryInfo: $postInfo->getCommentsCategoryId(), deleted: false) : 0;
$newsPosts[] = [ $newsPosts[] = [
'post' => $postInfo, 'post' => $postInfo,

View file

@ -11,16 +11,14 @@ class ChangeInfo {
private int $created; private int $created;
private string $summary; private string $summary;
private string $body; private string $body;
private array $tags;
public function __construct(IDbResult $result, array $tags = []) { public function __construct(IDbResult $result) {
$this->id = (string)$result->getInteger(0); $this->id = (string)$result->getInteger(0);
$this->userId = $result->isNull(1) ? null : (string)$result->getInteger(1); $this->userId = $result->isNull(1) ? null : (string)$result->getInteger(1);
$this->action = $result->getInteger(2); $this->action = $result->getInteger(2);
$this->created = $result->getInteger(3); $this->created = $result->getInteger(3);
$this->summary = $result->getString(4); $this->summary = $result->getString(4);
$this->body = $result->getString(5); $this->body = $result->getString(5);
$this->tags = $tags;
} }
public function getId(): string { public function getId(): string {
@ -70,19 +68,4 @@ class ChangeInfo {
public function getCommentsCategoryName(): string { public function getCommentsCategoryName(): string {
return sprintf('changelog-date-%s', $this->getDate()); return sprintf('changelog-date-%s', $this->getDate());
} }
public function getTags(): array {
return $this->tags;
}
public function getTagIds(): array {
$ids = [];
foreach($this->tags as $tagInfo)
$ids[] = $tagInfo->getId();
return $ids;
}
public function hasTag(ChangeTagInfo|string $infoOrId): bool {
return in_array($infoOrId, $this->tags);
}
} }

View file

@ -64,38 +64,7 @@ class Changelog {
}; };
} }
private function readChanges(IDbResult $result, bool $withTags): array { public function countChanges(
$changes = [];
if($withTags) {
while($result->next())
$changes[] = new ChangeInfo(
$result,
$this->getTagsByChange((string)$result->getInteger(0))
);
} else {
while($result->next())
$changes[] = new ChangeInfo($result);
}
return $changes;
}
private function readTags(IDbResult $result): array {
$tags = [];
while($result->next()) {
$tagId = (string)$result->getInteger(0);
if(array_key_exists($tagId, $this->tags))
$tags[] = $this->tags[$tagId];
else
$tags[] = $this->tags[$tagId] = new ChangeTagInfo($result);
}
return $tags;
}
public function countAllChanges(
UserInfo|string|null $userInfo = null, UserInfo|string|null $userInfo = null,
DateTime|int|null $dateTime = null, DateTime|int|null $dateTime = null,
?array $tags = null ?array $tags = null
@ -147,8 +116,7 @@ class Changelog {
return $count; return $count;
} }
public function getAllChanges( public function getChanges(
bool $withTags = false,
UserInfo|string|null $userInfo = null, UserInfo|string|null $userInfo = null,
DateTime|int|null $dateTime = null, DateTime|int|null $dateTime = null,
?array $tags = null, ?array $tags = null,
@ -201,10 +169,16 @@ class Changelog {
$stmt->execute(); $stmt->execute();
return self::readChanges($stmt->getResult(), $withTags); $result = $stmt->getResult();
$changes = [];
while($result->next())
$changes[] = new ChangeInfo($result);
return $changes;
} }
public function getChangeById(string $changeId, bool $withTags = false): ChangeInfo { public function getChange(string $changeId): ChangeInfo {
$stmt = $this->cache->get('SELECT change_id, user_id, change_action, UNIX_TIMESTAMP(change_created), change_log, change_text FROM msz_changelog_changes WHERE change_id = ?'); $stmt = $this->cache->get('SELECT change_id, user_id, change_action, UNIX_TIMESTAMP(change_created), change_log, change_text FROM msz_changelog_changes WHERE change_id = ?');
$stmt->addParameter(1, $changeId); $stmt->addParameter(1, $changeId);
$stmt->execute(); $stmt->execute();
@ -213,11 +187,7 @@ class Changelog {
if(!$result->next()) if(!$result->next())
throw new RuntimeException('No tag with that ID exists.'); throw new RuntimeException('No tag with that ID exists.');
$tags = []; return new ChangeInfo($result);
if($withTags)
$tags = $this->getTagsByChange((string)$result->getInteger(0));
return new ChangeInfo($result, $tags);
} }
public function createChange( public function createChange(
@ -250,7 +220,7 @@ class Changelog {
$stmt->addParameter(5, $body); $stmt->addParameter(5, $body);
$stmt->execute(); $stmt->execute();
return $this->getChangeById((string)$this->dbConn->getLastInsertId()); return $this->getChange((string)$this->dbConn->getLastInsertId());
} }
public function deleteChange(ChangeInfo|string $infoOrId): void { public function deleteChange(ChangeInfo|string $infoOrId): void {
@ -306,25 +276,38 @@ class Changelog {
$stmt->execute(); $stmt->execute();
} }
public function getAllTags(): array { public function getTags(
// only putting the changes count in here for now, it is only used in manage ChangeInfo|string|null $changeInfo = null
return $this->readTags( ): array {
$this->dbConn->query('SELECT tag_id, tag_name, tag_description, UNIX_TIMESTAMP(tag_created), UNIX_TIMESTAMP(tag_archived), (SELECT COUNT(*) FROM msz_changelog_change_tags AS ct WHERE ct.tag_id = t.tag_id) AS `tag_changes` FROM msz_changelog_tags AS t') if($changeInfo instanceof ChangeInfo)
); $changeInfo = $changeInfo->getId();
}
public function getTagsByChange(ChangeInfo|string $infoOrId): array { $hasChangeInfo = $changeInfo !== null;
if($infoOrId instanceof ChangeInfo)
$infoOrId = $infoOrId->getId();
$stmt = $this->cache->get('SELECT tag_id, tag_name, tag_description, UNIX_TIMESTAMP(tag_created), UNIX_TIMESTAMP(tag_archived), 0 AS `tag_changes` FROM msz_changelog_tags WHERE tag_id IN (SELECT tag_id FROM msz_changelog_change_tags WHERE change_id = ?)'); $query = 'SELECT tag_id, tag_name, tag_description, UNIX_TIMESTAMP(tag_created), UNIX_TIMESTAMP(tag_archived), (SELECT COUNT(*) FROM msz_changelog_change_tags AS ct WHERE ct.tag_id = t.tag_id) AS tag_changes FROM msz_changelog_tags AS t';
$stmt->addParameter(1, $infoOrId); if($hasChangeInfo)
$query .= ' WHERE tag_id IN (SELECT tag_id FROM msz_changelog_change_tags WHERE change_id = ?)';
$stmt = $this->cache->get($query);
if($hasChangeInfo)
$stmt->addParameter(1, $changeInfo);
$stmt->execute(); $stmt->execute();
return $this->readTags($stmt->getResult()); $result = $stmt->getResult();
$tags = [];
while($result->next()) {
$tagId = (string)$result->getInteger(0);
if(array_key_exists($tagId, $this->tags))
$tags[] = $this->tags[$tagId];
else
$tags[] = $this->tags[$tagId] = new ChangeTagInfo($result);
}
return $tags;
} }
public function getTagById(string $tagId): ChangeTagInfo { public function getTag(string $tagId): ChangeTagInfo {
$stmt = $this->cache->get('SELECT tag_id, tag_name, tag_description, UNIX_TIMESTAMP(tag_created), UNIX_TIMESTAMP(tag_archived), 0 AS `tag_changes` FROM msz_changelog_tags WHERE tag_id = ?'); $stmt = $this->cache->get('SELECT tag_id, tag_name, tag_description, UNIX_TIMESTAMP(tag_created), UNIX_TIMESTAMP(tag_archived), 0 AS `tag_changes` FROM msz_changelog_tags WHERE tag_id = ?');
$stmt->addParameter(1, $tagId); $stmt->addParameter(1, $tagId);
$stmt->execute(); $stmt->execute();
@ -355,7 +338,7 @@ class Changelog {
$stmt->addParameter(3, $archived ? 1 : 0); $stmt->addParameter(3, $archived ? 1 : 0);
$stmt->execute(); $stmt->execute();
return $this->getTagById((string)$this->dbConn->getLastInsertId()); return $this->getTag((string)$this->dbConn->getLastInsertId());
} }
public function deleteTag(ChangeTagInfo|string $infoOrId): void { public function deleteTag(ChangeTagInfo|string $infoOrId): void {

View file

@ -96,12 +96,12 @@ final class ChangelogRoutes {
$tag = trim($tag); $tag = trim($tag);
} }
$count = $this->changelog->countAllChanges($filterUser, $filterDate, $filterTags); $count = $this->changelog->countChanges($filterUser, $filterDate, $filterTags);
$pagination = new Pagination($count, 30); $pagination = new Pagination($count, 30);
if(!$pagination->hasValidOffset()) if(!$pagination->hasValidOffset())
return 404; return 404;
$changeInfos = $this->changelog->getAllChanges(userInfo: $filterUser, dateTime: $filterDate, tags: $filterTags, pagination: $pagination); $changeInfos = $this->changelog->getChanges(userInfo: $filterUser, dateTime: $filterDate, tags: $filterTags, pagination: $pagination);
if(empty($changeInfos)) if(empty($changeInfos))
return 404; return 404;
@ -145,11 +145,13 @@ final class ChangelogRoutes {
public function getChange($response, $request, string $changeId) { public function getChange($response, $request, string $changeId) {
try { try {
$changeInfo = $this->changelog->getChangeById($changeId, withTags: true); $changeInfo = $this->changelog->getChange($changeId);
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
return 404; return 404;
} }
$tagInfos = $this->changelog->getTags(changeInfo: $changeInfo);
try { try {
$userInfo = $this->users->getUser($changeInfo->getUserId(), 'id'); $userInfo = $this->users->getUser($changeInfo->getUserId(), 'id');
$userColour = $this->users->getUserColour($userInfo); $userColour = $this->users->getUserColour($userInfo);
@ -160,6 +162,7 @@ final class ChangelogRoutes {
return Template::renderRaw('changelog.change', [ return Template::renderRaw('changelog.change', [
'change_info' => $changeInfo, 'change_info' => $changeInfo,
'change_tags' => $tagInfos,
'change_user_info' => $userInfo, 'change_user_info' => $userInfo,
'change_user_colour' => $userColour, 'change_user_colour' => $userColour,
'comments_info' => $this->getCommentsInfo($changeInfo->getCommentsCategoryName()), 'comments_info' => $this->getCommentsInfo($changeInfo->getCommentsCategoryName()),
@ -168,7 +171,7 @@ final class ChangelogRoutes {
private function createFeed(string $feedMode): Feed { private function createFeed(string $feedMode): Feed {
$siteName = $this->config->getString('site.name', 'Misuzu'); $siteName = $this->config->getString('site.name', 'Misuzu');
$changes = $this->changelog->getAllChanges(pagination: new Pagination(10)); $changes = $this->changelog->getChanges(pagination: new Pagination(10));
$feed = (new Feed) $feed = (new Feed)
->setTitle($siteName . ' » Changelog') ->setTitle($siteName . ' » Changelog')

View file

@ -18,7 +18,9 @@ class Comments {
$this->cache = new DbStatementCache($dbConn); $this->cache = new DbStatementCache($dbConn);
} }
public function countAllCategories(UserInfo|string|null $owner = null): int { public function countCategories(
UserInfo|string|null $owner = null
): int {
if($owner instanceof UserInfo) if($owner instanceof UserInfo)
$owner = $owner->getId(); $owner = $owner->getId();
@ -79,48 +81,48 @@ class Comments {
return $categories; return $categories;
} }
public function getCategoryByName(string $name): CommentsCategoryInfo { public function getCategory(
$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 = ?'); ?string $categoryId = null,
$stmt->addParameter(1, $name); ?string $name = null,
$stmt->execute(); CommentsPostInfo|string|null $postInfo = null
$result = $stmt->getResult(); ): CommentsCategoryInfo {
$hasCategoryId = $categoryId !== null;
$hasName = $name !== null;
$hasPostInfo = $postInfo !== null;
if(!$result->next()) if(!$hasCategoryId && !$hasName && !$hasPostInfo)
throw new RuntimeException('No category with this name found.'); throw new InvalidArgumentException('At least one of the arguments must be set.');
// there has got to be a better way to do this
if(($hasCategoryId && ($hasName || $hasPostInfo)) || ($hasName && ($hasCategoryId || $hasPostInfo)) || ($hasPostInfo && ($hasCategoryId || $hasName)))
throw new InvalidArgumentException('Only one of the arguments may be specified.');
return new CommentsCategoryInfo($result); $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';
} $value = null;
if($hasCategoryId) {
public function getCategoryById(string $id): CommentsCategoryInfo { $query .= ' WHERE category_id = ?';
$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 = ?'); $value = $categoryId;
$stmt->addParameter(1, $id); }
$stmt->execute(); if($hasName) {
$result = $stmt->getResult(); $query .= ' WHERE category_name = ?';
$value = $name;
if(!$result->next()) }
throw new RuntimeException('No category with this ID found.'); if($hasPostInfo) {
if($postInfo instanceof CommentsPostInfo) {
return new CommentsCategoryInfo($result); $query .= ' WHERE category_id = ?';
} $value = $postInfo->getCategoryId();
} else {
public function getCategoryByPost(CommentsPostInfo|string $infoOrId): CommentsCategoryInfo { $query .= ' WHERE category_id = (SELECT category_id FROM msz_comments_posts WHERE comment_id = ?)';
$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 = '; $value = $postInfo;
}
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 = $this->cache->get($query);
$stmt->addParameter(1, $param); $stmt->addParameter(1, $value);
$stmt->execute(); $stmt->execute();
$result = $stmt->getResult(); $result = $stmt->getResult();
if(!$result->next()) if(!$result->next())
throw new RuntimeException('No category belonging to this post found.'); throw new RuntimeException('Comments category not found.');
return new CommentsCategoryInfo($result); return new CommentsCategoryInfo($result);
} }
@ -141,7 +143,7 @@ class Comments {
public function ensureCategory(string $name, UserInfo|string|null $owner = null): CommentsCategoryInfo { public function ensureCategory(string $name, UserInfo|string|null $owner = null): CommentsCategoryInfo {
if($this->checkCategoryNameExists($name)) if($this->checkCategoryNameExists($name))
return $this->getCategoryByName($name); return $this->getCategory(name: $name);
return $this->createCategory($name, $owner); return $this->createCategory($name, $owner);
} }
@ -158,7 +160,7 @@ class Comments {
$stmt->addParameter(2, $owner); $stmt->addParameter(2, $owner);
$stmt->execute(); $stmt->execute();
return $this->getCategoryById((string)$this->dbConn->getLastInsertId()); return $this->getCategory(categoryId: (string)$this->dbConn->getLastInsertId());
} }
public function deleteCategory(CommentsCategoryInfo|string $category): void { public function deleteCategory(CommentsCategoryInfo|string $category): void {
@ -214,48 +216,41 @@ class Comments {
} }
public function countPosts( public function countPosts(
CommentsCategoryInfo|string|null $category = null, CommentsCategoryInfo|string|null $categoryInfo = null,
CommentsPostInfo|string|null $parent = null, CommentsPostInfo|string|null $parentInfo = null,
bool $includeReplies = false, ?bool $replies = null,
bool $includeDeleted = false ?bool $deleted = null
): int { ): int {
if($category instanceof CommentsCategoryInfo) if($categoryInfo instanceof CommentsCategoryInfo)
$category = $category->getId(); $categoryInfo = $categoryInfo->getId();
if($parent instanceof CommentsPostInfo) if($parentInfo instanceof CommentsPostInfo)
$parent = $parent->getId(); $parentInfo = $parentInfo->getId();
$hasCategory = $category !== null; $hasCategoryInfo = $categoryInfo !== null;
$hasParent = $parent !== null; $hasParentInfo = $parentInfo !== null;
$hasReplies = $replies !== null;
$hasDeleted = $deleted !== null;
$args = 0; $args = 0;
$query = 'SELECT COUNT(*) FROM msz_comments_posts'; $query = 'SELECT COUNT(*) FROM msz_comments_posts';
if($hasParent) { if($hasParentInfo) {
++$args; ++$args;
$query .= ' WHERE comment_reply_to = ?'; $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';
} }
if($hasCategoryInfo)
$query .= sprintf(' %s category_id = ?', ++$args > 1 ? 'AND' : 'WHERE');
if($hasReplies)
$query .= sprintf(' %s comment_reply_to %s NULL', ++$args > 1 ? 'AND' : 'WHERE', $replies ? 'IS NOT' : 'IS');
if($hasDeleted)
$query .= sprintf(' %s comment_deleted %s NULL', ++$args > 1 ? 'AND' : 'WHERE', $deleted ? 'IS NOT' : 'IS');
$args = 0; $args = 0;
$stmt = $this->cache->get($query); $stmt = $this->cache->get($query);
if($hasParent) if($hasParentInfo)
$stmt->addParameter(++$args, $parent); $stmt->addParameter(++$args, $parentInfo);
elseif($hasCategory) elseif($hasCategoryInfo)
$stmt->addParameter(++$args, $category); $stmt->addParameter(++$args, $categoryInfo);
$stmt->execute(); $stmt->execute();
$result = $stmt->getResult(); $result = $stmt->getResult();
@ -268,20 +263,22 @@ class Comments {
} }
public function getPosts( public function getPosts(
CommentsCategoryInfo|string|null $category = null, CommentsCategoryInfo|string|null $categoryInfo = null,
CommentsPostInfo|string|null $parent = null, CommentsPostInfo|string|null $parentInfo = null,
bool $includeReplies = false, ?bool $replies = null,
bool $includeDeleted = false, ?bool $deleted = null,
bool $includeRepliesCount = false, bool $includeRepliesCount = false,
bool $includeVotesCount = false bool $includeVotesCount = false
): array { ): array {
if($category instanceof CommentsCategoryInfo) if($categoryInfo instanceof CommentsCategoryInfo)
$category = $category->getId(); $categoryInfo = $categoryInfo->getId();
if($parent instanceof CommentsPostInfo) if($parentInfo instanceof CommentsPostInfo)
$parent = $parent->getId(); $parentInfo = $parentInfo->getId();
$hasCategory = $category !== null; $hasCategoryInfo = $categoryInfo !== null;
$hasParent = $parent !== null; $hasParentInfo = $parentInfo !== null;
$hasReplies = $replies !== null;
$hasDeleted = $deleted !== null;
$args = 0; $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)'; $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)';
@ -294,40 +291,31 @@ class Comments {
} }
$query .= ' FROM msz_comments_posts AS cpp'; $query .= ' FROM msz_comments_posts AS cpp';
if($hasParent) { if($hasParentInfo) {
++$args; ++$args;
$query .= ' WHERE comment_reply_to = ?'; $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($hasCategoryInfo)
$query .= sprintf(' %s category_id = ?', ++$args > 1 ? 'AND' : 'WHERE');
if($hasReplies)
$query .= sprintf(' %s comment_reply_to %s NULL', ++$args > 1 ? 'AND' : 'WHERE', $replies ? 'IS NOT' : 'IS');
if($hasDeleted)
$query .= sprintf(' %s comment_deleted %s NULL', ++$args > 1 ? 'AND' : 'WHERE', $deleted ? 'IS NOT' : 'IS');
if(!$includeDeleted) { // this should really not be implicit like this
$query .= (++$args > 1 ? ' AND' : ' WHERE'); if($hasParentInfo)
$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'; $query .= ' ORDER BY comment_deleted ASC, comment_pinned DESC, comment_created ASC';
elseif($hasCategory) elseif($hasCategoryInfo)
$query .= ' ORDER BY comment_deleted ASC, comment_pinned DESC, comment_created DESC'; $query .= ' ORDER BY comment_deleted ASC, comment_pinned DESC, comment_created DESC';
else else
$query .= ' ORDER BY comment_created DESC'; $query .= ' ORDER BY comment_created DESC';
$args = 0; $args = 0;
$stmt = $this->cache->get($query); $stmt = $this->cache->get($query);
if($hasParent) if($hasParentInfo)
$stmt->addParameter(++$args, $parent); $stmt->addParameter(++$args, $parentInfo);
elseif($hasCategory) elseif($hasCategoryInfo)
$stmt->addParameter(++$args, $category); $stmt->addParameter(++$args, $categoryInfo);
$stmt->execute(); $stmt->execute();
$posts = []; $posts = [];
@ -339,7 +327,7 @@ class Comments {
return $posts; return $posts;
} }
public function getPostById( public function getPost(
string $postId, string $postId,
bool $includeRepliesCount = false, bool $includeRepliesCount = false,
bool $includeVotesCount = false bool $includeVotesCount = false
@ -396,7 +384,7 @@ class Comments {
$stmt->addParameter(5, $pin ? 1 : 0); $stmt->addParameter(5, $pin ? 1 : 0);
$stmt->execute(); $stmt->execute();
return $this->getPostById((string)$this->dbConn->getLastInsertId()); return $this->getPost((string)$this->dbConn->getLastInsertId());
} }
public function deletePost(CommentsPostInfo|string $infoOrId): void { public function deletePost(CommentsPostInfo|string $infoOrId): void {

View file

@ -28,7 +28,7 @@ class CommentsEx {
$info->category = $category; $info->category = $category;
$info->posts = []; $info->posts = [];
$root = $this->comments->getPosts($category, includeRepliesCount: true, includeVotesCount: true, includeDeleted: true); $root = $this->comments->getPosts($category, includeRepliesCount: true, includeVotesCount: true, replies: false);
foreach($root as $postInfo) foreach($root as $postInfo)
$info->posts[] = $this->decorateComment($postInfo); $info->posts[] = $this->decorateComment($postInfo);
@ -65,7 +65,7 @@ class CommentsEx {
$info->vote = $this->comments->getPostVote($postInfo, $userInfo); $info->vote = $this->comments->getPostVote($postInfo, $userInfo);
$info->replies = []; $info->replies = [];
$root = $this->comments->getPosts(parent: $postInfo, includeRepliesCount: true, includeVotesCount: true, includeDeleted: true); $root = $this->comments->getPosts(parentInfo: $postInfo, includeRepliesCount: true, includeVotesCount: true);
foreach($root as $childInfo) foreach($root as $childInfo)
$info->replies[] = $this->decorateComment($childInfo); $info->replies[] = $this->decorateComment($childInfo);

View file

@ -9,14 +9,12 @@ class EmoteInfo implements Stringable {
private int $order; private int $order;
private int $rank; private int $rank;
private string $url; private string $url;
private array $strings;
public function __construct(IDbResult $result, array $strings = []) { public function __construct(IDbResult $result) {
$this->id = (string)$result->getInteger(0); $this->id = (string)$result->getInteger(0);
$this->order = $result->getInteger(1); $this->order = $result->getInteger(1);
$this->rank = $result->getInteger(2); $this->rank = $result->getInteger(2);
$this->url = $result->getString(3); $this->url = $result->getString(3);
$this->strings = $strings;
} }
public function getId(): string { public function getId(): string {
@ -35,17 +33,6 @@ class EmoteInfo implements Stringable {
return $this->url; return $this->url;
} }
public function getStrings(): array {
return $this->strings;
}
public function getStringsRaw(): array {
$strings = [];
foreach($this->strings as $info)
$strings[] = $info->getString();
return $strings;
}
public function __toString(): string { public function __toString(): string {
return $this->url; return $this->url;
} }

View file

@ -21,18 +21,17 @@ class Emotes {
$this->cache = new DbStatementCache($dbConn); $this->cache = new DbStatementCache($dbConn);
} }
public function getEmoteById(string $emoteId, bool $withStrings = false): EmoteInfo { public function getEmote(string $emoteId): EmoteInfo {
$stmt = $this->cache->get('SELECT emote_id, emote_order, emote_hierarchy, emote_url FROM msz_emoticons WHERE emote_id = ?'); $stmt = $this->cache->get('SELECT emote_id, emote_order, emote_hierarchy, emote_url FROM msz_emoticons WHERE emote_id = ?');
$stmt->addParameter(1, $emoteId); $stmt->addParameter(1, $emoteId);
$stmt->execute(); $stmt->execute();
$result = $stmt->getResult(); $result = $stmt->getResult();
if(!$result->next()) if(!$result->next())
throw new RuntimeException('No emoticon with that ID exists.'); throw new RuntimeException('No emoticon with that ID exists.');
$strings = $withStrings ? $this->getEmoteStrings($emoteId) : []; return new EmoteInfo($result);
return new EmoteInfo($result, $strings);
} }
public static function emoteOrderOptions(): array { public static function emoteOrderOptions(): array {
@ -40,32 +39,37 @@ class Emotes {
} }
// TODO: pagination // TODO: pagination
public function getAllEmotes( public function getEmotes(
int $minRank = -1, ?int $minRank = null,
string $orderBy = '', ?string $orderBy = null,
bool $desc = false, ?bool $reverse = null
bool $withStrings = false
): array { ): array {
if($minRank < 0) $minRank = PHP_INT_MAX; $hasMinRank = $minRank !== null;
$orderBy = self::EMOTE_ORDER[$orderBy] ?? self::EMOTE_ORDER[array_key_first(self::EMOTE_ORDER)]; $hasOrderBy = $orderBy !== null;
$hasReverse = $reverse !== null;
$stmt = $this->cache->get(sprintf( $query = 'SELECT emote_id, emote_order, emote_hierarchy, emote_url FROM msz_emoticons';
'SELECT emote_id, emote_order, emote_hierarchy, emote_url FROM msz_emoticons WHERE emote_hierarchy <= ? ORDER BY %s %s', if($hasMinRank)
$orderBy, ($desc ? 'DESC' : 'ASC') $query .= ' WHERE emote_hierarchy <= ?';
)); if($hasOrderBy) {
$stmt->addParameter(1, $minRank); if(!array_key_exists($orderBy, self::EMOTE_ORDER))
throw new InvalidArgumentException('Invalid $orderBy specified.');
$query .= sprintf(' ORDER BY %s', self::EMOTE_ORDER[$orderBy]);
if($hasReverse)
$query .= $hasReverse ? ' DESC' : ' ASC';
}
$stmt = $this->cache->get($query);
if($hasMinRank)
$stmt->addParameter(1, $minRank);
$stmt->execute(); $stmt->execute();
$emotes = []; $emotes = [];
$result = $stmt->getResult(); $result = $stmt->getResult();
if($withStrings) { while($result->next())
while($result->next()) $emotes[] = new EmoteInfo($result);
$emotes[] = new EmoteInfo($result, $this->getEmoteStrings((string)$result->getInteger(0)));
} else {
while($result->next())
$emotes[] = new EmoteInfo($result);
}
return $emotes; return $emotes;
} }
@ -106,7 +110,7 @@ class Emotes {
$stmt->addParameter(3, $order); $stmt->addParameter(3, $order);
$stmt->execute(); $stmt->execute();
return $this->getEmoteById((string)$this->dbConn->getLastInsertId()); return $this->getEmote((string)$this->dbConn->getLastInsertId());
} }
public function deleteEmote(EmoteInfo|string $infoOrId): void { public function deleteEmote(EmoteInfo|string $infoOrId): void {

View file

@ -79,7 +79,7 @@ class HomeRoutes {
private array $newsCategoryInfos = []; private array $newsCategoryInfos = [];
private function getFeaturedNewsPosts(int $amount, bool $decorate): array { private function getFeaturedNewsPosts(int $amount, bool $decorate): array {
$postInfos = $this->news->getAllPosts( $postInfos = $this->news->getPosts(
onlyFeatured: true, onlyFeatured: true,
pagination: new Pagination($amount) pagination: new Pagination($amount)
); );
@ -112,10 +112,10 @@ class HomeRoutes {
if(array_key_exists($categoryId, $this->newsCategoryInfos)) if(array_key_exists($categoryId, $this->newsCategoryInfos))
$categoryInfo = $this->newsCategoryInfos[$categoryId]; $categoryInfo = $this->newsCategoryInfos[$categoryId];
else else
$this->newsCategoryInfos[$categoryId] = $categoryInfo = $this->news->getCategoryByPost($postInfo); $this->newsCategoryInfos[$categoryId] = $categoryInfo = $this->news->getCategory(postInfo: $postInfo);
$commentsCount = $postInfo->hasCommentsCategoryId() $commentsCount = $postInfo->hasCommentsCategoryId()
? $this->comments->countPosts($postInfo->getCommentsCategoryId(), includeReplies: true) : 0; ? $this->comments->countPosts(categoryInfo: $postInfo->getCommentsCategoryId(), deleted: false) : 0;
$posts[] = [ $posts[] = [
'post' => $postInfo, 'post' => $postInfo,
@ -205,7 +205,7 @@ class HomeRoutes {
$stats = $this->getStats(); $stats = $this->getStats();
$onlineUserInfos = $this->getOnlineUsers(); $onlineUserInfos = $this->getOnlineUsers();
$featuredNews = $this->getFeaturedNewsPosts(5, true); $featuredNews = $this->getFeaturedNewsPosts(5, true);
$changelog = $this->changelog->getAllChanges(pagination: new Pagination(10)); $changelog = $this->changelog->getChanges(pagination: new Pagination(10));
$stats['users:online:recent'] = count($onlineUserInfos); $stats['users:online:recent'] = count($onlineUserInfos);

View file

@ -20,15 +20,6 @@ class News {
$this->cache = new DbStatementCache($dbConn); $this->cache = new DbStatementCache($dbConn);
} }
private function readCategories(IDbResult $result): array {
$categories = [];
while($result->next())
$categories[] = new NewsCategoryInfo($result);
return $categories;
}
private function readPosts(IDbResult $result): array { private function readPosts(IDbResult $result): array {
$posts = []; $posts = [];
@ -38,10 +29,14 @@ class News {
return $posts; return $posts;
} }
public function countAllCategories(bool $includeHidden = false): int { public function countCategories(
?bool $hidden = null
): int {
$hasHidden = $hidden !== null;
$query = 'SELECT COUNT(*) FROM msz_news_categories'; $query = 'SELECT COUNT(*) FROM msz_news_categories';
if($includeHidden) if($hasHidden)
$query .= ' WHERE category_is_hidden = 0'; $query .= sprintf(' WHERE category_is_hidden %s 0', $hidden ? '<>' : '=');
$result = $this->dbConn->query($query); $result = $this->dbConn->query($query);
$count = 0; $count = 0;
@ -52,15 +47,16 @@ class News {
return $count; return $count;
} }
public function getAllCategories( public function getCategories(
bool $includeHidden = false, ?bool $hidden = null,
?Pagination $pagination = null ?Pagination $pagination = null
): array { ): array {
$hasHidden = $hidden !== null;
$hasPagination = $pagination !== null; $hasPagination = $pagination !== null;
$query = 'SELECT category_id, category_name, category_description, category_is_hidden, UNIX_TIMESTAMP(category_created), (SELECT COUNT(*) FROM msz_news_posts AS np WHERE np.category_id = nc.category_id) AS category_posts_count FROM msz_news_categories AS nc'; $query = 'SELECT category_id, category_name, category_description, category_is_hidden, UNIX_TIMESTAMP(category_created), (SELECT COUNT(*) FROM msz_news_posts AS np WHERE np.category_id = nc.category_id) AS category_posts_count FROM msz_news_categories AS nc';
if(!$includeHidden) if($hasHidden)
$query .= ' WHERE category_is_hidden = 0'; $query .= sprintf(' WHERE category_is_hidden %s 0', $hidden ? '<>' : '=');
$query .= ' ORDER BY category_created ASC'; $query .= ' ORDER BY category_created ASC';
if($hasPagination) if($hasPagination)
$query .= ' LIMIT ? OFFSET ?'; $query .= ' LIMIT ? OFFSET ?';
@ -74,39 +70,51 @@ class News {
$stmt->execute(); $stmt->execute();
return self::readCategories($stmt->getResult()); $result = $stmt->getResult();
$categories = [];
while($result->next())
$categories[] = new NewsCategoryInfo($result);
return $categories;
} }
public function getCategoryByPost(NewsPostInfo|string $postInfo): NewsCategoryInfo { public function getCategory(
$query = 'SELECT category_id, category_name, category_description, category_is_hidden, UNIX_TIMESTAMP(category_created) FROM msz_news_categories WHERE category_id = '; ?string $categoryId = null,
NewsPostInfo|string|null $postInfo = null
): NewsCategoryInfo {
$hasCategoryId = $categoryId !== null;
$hasPostInfo = $postInfo !== null;
if($postInfo instanceof NewsPostInfo) { if(!$hasCategoryId && !$hasPostInfo)
$query .= '?'; throw new InvalidArgumentException('At least one argument must be specified.');
$param = $postInfo->getCategoryId(); if($hasCategoryId && $hasPostInfo)
} else { throw new InvalidArgumentException('Only one argument may be specified.');
$query .= '(SELECT category_id FROM msz_news_posts WHERE post_id = ?)';
$param = $postInfo; $value = null;
$query = 'SELECT category_id, category_name, category_description, category_is_hidden, UNIX_TIMESTAMP(category_created) FROM msz_news_categories';
if($hasCategoryId) {
$query .= ' WHERE category_id = ?';
$value = $categoryId;
}
if($hasPostInfo) {
if($postInfo instanceof NewsPostInfo) {
$query .= ' WHERE category_id = ?';
$value = $postInfo->getCategoryId();
} else {
$query .= ' WHERE category_id = (SELECT category_id FROM msz_news_posts WHERE post_id = ?)';
$value = $postInfo;
}
} }
$stmt = $this->cache->get($query); $stmt = $this->cache->get($query);
$stmt->addParameter(1, $param); $stmt->addParameter(1, $value);
$stmt->execute(); $stmt->execute();
$result = $stmt->getResult(); $result = $stmt->getResult();
if(!$result->next()) if(!$result->next())
throw new RuntimeException('No news category associated with that ID exists.'); throw new RuntimeException('News category not found.');
return new NewsCategoryInfo($result);
}
public function getCategoryById(string $categoryId): NewsCategoryInfo {
$stmt = $this->cache->get('SELECT category_id, category_name, category_description, category_is_hidden, UNIX_TIMESTAMP(category_created) FROM msz_news_categories WHERE category_id = ?');
$stmt->addParameter(1, $categoryId);
$stmt->execute();
$result = $stmt->getResult();
if(!$result->next())
throw new RuntimeException('No news category with that ID exists.');
return new NewsCategoryInfo($result); return new NewsCategoryInfo($result);
} }
@ -130,7 +138,7 @@ class News {
$stmt->addParameter(3, $hidden ? 1 : 0); $stmt->addParameter(3, $hidden ? 1 : 0);
$stmt->execute(); $stmt->execute();
return $this->getCategoryById((string)$this->dbConn->getLastInsertId()); return $this->getCategory(categoryId: (string)$this->dbConn->getLastInsertId());
} }
public function deleteCategory(NewsCategoryInfo|string $infoOrId): void { public function deleteCategory(NewsCategoryInfo|string $infoOrId): void {
@ -174,16 +182,26 @@ class News {
$stmt->execute(); $stmt->execute();
} }
public function countAllPosts( public function countPosts(
NewsCategoryInfo|string|null $categoryInfo = null,
bool $onlyFeatured = false, bool $onlyFeatured = false,
bool $includeScheduled = false, bool $includeScheduled = false,
bool $includeDeleted = false bool $includeDeleted = false
): int { ): int {
if($categoryInfo instanceof NewsCategoryInfo)
$categoryInfo = $categoryInfo->getId();
$hasCategoryInfo = $categoryInfo !== null;
$args = 0; $args = 0;
$query = 'SELECT COUNT(*) FROM msz_news_posts'; $query = 'SELECT COUNT(*) FROM msz_news_posts';
if($onlyFeatured) { if($hasCategoryInfo) {
++$args; ++$args;
$query .= ' WHERE post_is_featured = 1'; $query .= ' WHERE category_id = ?';
}
if($onlyFeatured) {
$query .= (++$args > 1 ? ' AND' : ' WHERE');
$query .= ' post_is_featured = 1';
} }
if(!$includeScheduled) { if(!$includeScheduled) {
$query .= (++$args > 1 ? ' AND' : ' WHERE'); $query .= (++$args > 1 ? ' AND' : ' WHERE');
@ -194,34 +212,9 @@ class News {
$query .= ' post_deleted IS NULL'; $query .= ' post_deleted IS NULL';
} }
$result = $this->dbConn->query($query);
$count = 0;
if($result->next())
$count = $result->getInteger(0);
return $count;
}
public function countPostsByCategory(
NewsCategoryInfo|string $categoryInfo,
bool $onlyFeatured = false,
bool $includeScheduled = false,
bool $includeDeleted = false
): int {
if($categoryInfo instanceof NewsCategoryInfo)
$categoryInfo = $categoryInfo->getId();
$query = 'SELECT COUNT(*) FROM msz_news_posts WHERE category_id = ?';
if($onlyFeatured)
$query .= ' AND post_is_featured = 1';
if(!$includeScheduled)
$query .= ' AND post_scheduled <= NOW()';
if(!$includeDeleted)
$query .= ' AND post_deleted IS NULL';
$stmt = $this->cache->get($query); $stmt = $this->cache->get($query);
$stmt->addParameter(1, $categoryInfo); if($hasCategoryInfo)
$stmt->addParameter(1, $categoryInfo);
$stmt->execute(); $stmt->execute();
$result = $stmt->getResult(); $result = $stmt->getResult();
@ -233,22 +226,34 @@ class News {
return $count; return $count;
} }
private const POSTS_SELECT_QUERY = 'SELECT post_id, category_id, user_id, comment_section_id, post_is_featured, post_title, post_text, UNIX_TIMESTAMP(post_scheduled), UNIX_TIMESTAMP(post_created), UNIX_TIMESTAMP(post_updated), UNIX_TIMESTAMP(post_deleted) FROM msz_news_posts'; public function getPosts(
private const POSTS_SELECT_ORDER = ' ORDER BY post_scheduled DESC'; NewsCategoryInfo|string|null $categoryInfo = null,
string $searchQuery = null,
public function getAllPosts(
bool $onlyFeatured = false, bool $onlyFeatured = false,
bool $includeScheduled = false, bool $includeScheduled = false,
bool $includeDeleted = false, bool $includeDeleted = false,
?Pagination $pagination = null ?Pagination $pagination = null
): array { ): array {
$args = 0; if($categoryInfo instanceof NewsCategoryInfo)
$categoryInfo = $categoryInfo->getId();
$hasCategoryInfo = $categoryInfo !== null;
$hasSearchQuery = $searchQuery !== null;
$hasPagination = $pagination !== null; $hasPagination = $pagination !== null;
$query = self::POSTS_SELECT_QUERY; $args = 0;
if($onlyFeatured) { $query = 'SELECT post_id, category_id, user_id, comment_section_id, post_is_featured, post_title, post_text, UNIX_TIMESTAMP(post_scheduled), UNIX_TIMESTAMP(post_created), UNIX_TIMESTAMP(post_updated), UNIX_TIMESTAMP(post_deleted) FROM msz_news_posts';
if($hasCategoryInfo) {
++$args; ++$args;
$query .= ' WHERE post_is_featured = 1'; $query .= ' WHERE category_id = ?';
}
if($hasSearchQuery) {
$query .= (++$args > 1 ? ' AND' : ' WHERE');
$query .= ' MATCH(post_title, post_text) AGAINST (? IN NATURAL LANGUAGE MODE)';
}
if($onlyFeatured) {
$query .= (++$args > 1 ? ' AND' : ' WHERE');
$query .= ' post_is_featured = 1';
} }
if(!$includeScheduled) { if(!$includeScheduled) {
$query .= (++$args > 1 ? ' AND' : ' WHERE'); $query .= (++$args > 1 ? ' AND' : ' WHERE');
@ -258,12 +263,16 @@ class News {
$query .= (++$args > 1 ? ' AND' : ' WHERE'); $query .= (++$args > 1 ? ' AND' : ' WHERE');
$query .= ' post_deleted IS NULL'; $query .= ' post_deleted IS NULL';
} }
$query .= self::POSTS_SELECT_ORDER; $query .= ' ORDER BY post_scheduled DESC';
if($hasPagination) if($hasPagination)
$query .= ' LIMIT ? OFFSET ?'; $query .= ' LIMIT ? OFFSET ?';
$stmt = $this->cache->get($query); $stmt = $this->cache->get($query);
$args = 0; $args = 0;
if($hasCategoryInfo)
$stmt->addParameter(++$args, $categoryInfo);
if($hasSearchQuery)
$stmt->addParameter(++$args, $searchQuery);
if($hasPagination) { if($hasPagination) {
$stmt->addParameter(++$args, $pagination->getRange()); $stmt->addParameter(++$args, $pagination->getRange());
$stmt->addParameter(++$args, $pagination->getOffset()); $stmt->addParameter(++$args, $pagination->getOffset());
@ -271,78 +280,16 @@ class News {
$stmt->execute(); $stmt->execute();
return self::readPosts($stmt->getResult()); $result = $stmt->getResult();
$posts = [];
while($result->next())
$posts[] = new NewsPostInfo($result);
return $posts;
} }
public function getPostsByCategory( public function getPost(string $postId): NewsPostInfo {
NewsCategoryInfo|string $categoryInfo,
bool $onlyFeatured = false,
bool $includeScheduled = false,
bool $includeDeleted = false,
?Pagination $pagination = null
): array {
if($categoryInfo instanceof NewsCategoryInfo)
$categoryInfo = $categoryInfo->getId();
$hasPagination = $pagination !== null;
$query = self::POSTS_SELECT_QUERY;
$query .= ' WHERE category_id = ?';
if($onlyFeatured)
$query .= ' AND post_is_featured = 1';
if(!$includeScheduled)
$query .= ' AND post_scheduled <= NOW()';
if(!$includeDeleted)
$query .= ' AND post_deleted IS NULL';
$query .= self::POSTS_SELECT_ORDER;
if($hasPagination)
$query .= ' LIMIT ? OFFSET ?';
$stmt = $this->cache->get($query);
$args = 0;
$stmt->addParameter(++$args, $categoryInfo);
if($hasPagination) {
$stmt->addParameter(++$args, $pagination->getRange());
$stmt->addParameter(++$args, $pagination->getOffset());
}
$stmt->execute();
return self::readPosts($stmt->getResult());
}
public function getPostsBySearchQuery(
string $searchQuery,
bool $includeScheduled = false,
bool $includeDeleted = false,
?Pagination $pagination = null
): array {
$hasPagination = $pagination !== null;
$query = self::POSTS_SELECT_QUERY;
$query .= ' WHERE MATCH(post_title, post_text) AGAINST (? IN NATURAL LANGUAGE MODE)';
if(!$includeScheduled)
$query .= ' AND post_scheduled <= NOW()';
if(!$includeDeleted)
$query .= ' AND post_deleted IS NULL';
$query .= self::POSTS_SELECT_ORDER;
if($hasPagination)
$query .= ' LIMIT ? OFFSET ?';
$stmt = $this->cache->get($query);
$args = 0;
$stmt->addParameter(++$args, $searchQuery);
if($hasPagination) {
$stmt->addParameter(++$args, $pagination->getRange());
$stmt->addParameter(++$args, $pagination->getOffset());
}
$stmt->execute();
return self::readPosts($stmt->getResult());
}
public function getPostById(string $postId): NewsPostInfo {
$stmt = $this->cache->get('SELECT post_id, category_id, user_id, comment_section_id, post_is_featured, post_title, post_text, UNIX_TIMESTAMP(post_scheduled), UNIX_TIMESTAMP(post_created), UNIX_TIMESTAMP(post_updated), UNIX_TIMESTAMP(post_deleted) FROM msz_news_posts WHERE post_id = ?'); $stmt = $this->cache->get('SELECT post_id, category_id, user_id, comment_section_id, post_is_featured, post_title, post_text, UNIX_TIMESTAMP(post_scheduled), UNIX_TIMESTAMP(post_created), UNIX_TIMESTAMP(post_updated), UNIX_TIMESTAMP(post_deleted) FROM msz_news_posts WHERE post_id = ?');
$stmt->addParameter(1, $postId); $stmt->addParameter(1, $postId);
$stmt->execute(); $stmt->execute();
@ -386,7 +333,7 @@ class News {
$stmt->addParameter(6, $schedule); $stmt->addParameter(6, $schedule);
$stmt->execute(); $stmt->execute();
return $this->getPostById((string)$this->dbConn->getLastInsertId()); return $this->getPost((string)$this->dbConn->getLastInsertId());
} }
public function deletePost(NewsPostInfo|string $postInfo): void { public function deletePost(NewsPostInfo|string $postInfo): void {

View file

@ -118,9 +118,11 @@ class NewsRoutes {
private function getNewsPostsForView(Pagination $pagination, ?NewsCategoryInfo $categoryInfo = null): array { private function getNewsPostsForView(Pagination $pagination, ?NewsCategoryInfo $categoryInfo = null): array {
$posts = []; $posts = [];
$postInfos = $categoryInfo === null $postInfos = $this->news->getPosts(
? $this->news->getAllPosts(onlyFeatured: true, pagination: $pagination) categoryInfo: $categoryInfo,
: $this->news->getPostsByCategory($categoryInfo, pagination: $pagination); onlyFeatured: $categoryInfo === null,
pagination: $pagination
);
foreach($postInfos as $postInfo) { foreach($postInfos as $postInfo) {
$userId = $postInfo->getUserId(); $userId = $postInfo->getUserId();
@ -153,10 +155,10 @@ class NewsRoutes {
if(array_key_exists($categoryId, $this->categoryInfos)) if(array_key_exists($categoryId, $this->categoryInfos))
$categoryInfo = $this->categoryInfos[$categoryId]; $categoryInfo = $this->categoryInfos[$categoryId];
else else
$this->categoryInfos[$categoryId] = $categoryInfo = $this->news->getCategoryByPost($postInfo); $this->categoryInfos[$categoryId] = $categoryInfo = $this->news->getCategory(postInfo: $postInfo);
$commentsCount = $postInfo->hasCommentsCategoryId() $commentsCount = $postInfo->hasCommentsCategoryId()
? $this->comments->countPosts($postInfo->getCommentsCategoryId(), includeReplies: true) ? $this->comments->countPosts(categoryInfo: $postInfo->getCommentsCategoryId(), deleted: false)
: 0; : 0;
$posts[] = [ $posts[] = [
@ -173,9 +175,11 @@ class NewsRoutes {
private function getNewsPostsForFeed(?NewsCategoryInfo $categoryInfo = null): array { private function getNewsPostsForFeed(?NewsCategoryInfo $categoryInfo = null): array {
$posts = []; $posts = [];
$postInfos = $categoryInfo === null $postInfos = $this->news->getPosts(
? $this->news->getAllPosts(onlyFeatured: true, pagination: new Pagination(10)) categoryInfo: $categoryInfo,
: $this->news->getPostsByCategory($categoryInfo, pagination: new Pagination(10)); onlyFeatured: $categoryInfo === null,
pagination: new Pagination(10)
);
foreach($postInfos as $postInfo) { foreach($postInfos as $postInfo) {
$userId = $postInfo->getUserId(); $userId = $postInfo->getUserId();
@ -204,9 +208,9 @@ class NewsRoutes {
} }
public function getIndex() { public function getIndex() {
$categories = $this->news->getAllCategories(); $categories = $this->news->getCategories(hidden: false);
$pagination = new Pagination($this->news->countAllPosts(onlyFeatured: true), 5); $pagination = new Pagination($this->news->countPosts(onlyFeatured: true), 5);
if(!$pagination->hasValidOffset()) if(!$pagination->hasValidOffset())
return 404; return 404;
@ -232,7 +236,7 @@ class NewsRoutes {
$type = pathinfo($fileName, PATHINFO_EXTENSION); $type = pathinfo($fileName, PATHINFO_EXTENSION);
try { try {
$categoryInfo = $this->news->getCategoryById($categoryId); $categoryInfo = $this->news->getCategory(categoryId: $categoryId);
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
return 404; return 404;
} }
@ -244,7 +248,7 @@ class NewsRoutes {
elseif($type !== '') elseif($type !== '')
return 404; return 404;
$pagination = new Pagination($this->news->countPostsByCategory($categoryInfo), 5); $pagination = new Pagination($this->news->countPosts(categoryInfo: $categoryInfo), 5);
if(!$pagination->hasValidOffset()) if(!$pagination->hasValidOffset())
return 404; return 404;
@ -267,7 +271,7 @@ class NewsRoutes {
public function getPost($response, $request, string $postId) { public function getPost($response, $request, string $postId) {
try { try {
$postInfo = $this->news->getPostById($postId); $postInfo = $this->news->getPost($postId);
} catch(RuntimeException $ex) { } catch(RuntimeException $ex) {
return 404; return 404;
} }
@ -275,11 +279,11 @@ class NewsRoutes {
if(!$postInfo->isPublished() || $postInfo->isDeleted()) if(!$postInfo->isPublished() || $postInfo->isDeleted())
return 404; return 404;
$categoryInfo = $this->news->getCategoryByPost($postInfo); $categoryInfo = $this->news->getCategory(postInfo: $postInfo);
if($postInfo->hasCommentsCategoryId()) if($postInfo->hasCommentsCategoryId())
try { try {
$commentsCategory = $this->comments->getCategoryById($postInfo->getCommentsCategoryId()); $commentsCategory = $this->comments->getCategory(categoryId: $postInfo->getCommentsCategoryId());
} catch(RuntimeException $ex) {} } catch(RuntimeException $ex) {}
if(!isset($commentsCategory)) { if(!isset($commentsCategory)) {

View file

@ -81,13 +81,13 @@ final class SharpChatRoutes {
$response->setHeader('Access-Control-Allow-Origin', '*'); $response->setHeader('Access-Control-Allow-Origin', '*');
$response->setHeader('Access-Control-Allow-Methods', 'GET'); $response->setHeader('Access-Control-Allow-Methods', 'GET');
$emotes = $this->emotes->getAllEmotes(withStrings: true); $emotes = $this->emotes->getEmotes();
$out = []; $out = [];
foreach($emotes as $emoteInfo) { foreach($emotes as $emoteInfo) {
$strings = []; $strings = [];
foreach($emoteInfo->getStrings() as $stringInfo) foreach($this->emotes->getEmoteStrings($emoteInfo) as $stringInfo)
$strings[] = sprintf(':%s:', $stringInfo->getString()); $strings[] = sprintf(':%s:', $stringInfo->getString());
$out[] = [ $out[] = [

View file

@ -42,9 +42,9 @@
</time> </time>
</a> </a>
{% if change_info.tags|length > 0 %} {% if change_tags|length > 0 %}
<ul class="changelog__change__tags"> <ul class="changelog__change__tags">
{% for tag in change_info.tags %} {% for tag in change_tags %}
<li class="changelog__change__tag" title="{{ tag.description }}"> <li class="changelog__change__tag" title="{{ tag.description }}">
<a href="{{ url('changelog-index', {'tags': tag.id}) }}" class="changelog__change__tag__link"> <a href="{{ url('changelog-index', {'tags': tag.id}) }}" class="changelog__change__tag__link">
{{ tag.name }} {{ tag.name }}

View file

@ -27,6 +27,7 @@
{% macro changelog_entry(change, is_small, is_manage) %} {% macro changelog_entry(change, is_small, is_manage) %}
{% set user = change.user|default(null) %} {% set user = change.user|default(null) %}
{% set user_colour = change.user_colour|default(null) %} {% set user_colour = change.user_colour|default(null) %}
{% set tags = change.tags|default([]) %}
{% if change.change is defined %} {% if change.change is defined %}
{% set change = change.change %} {% set change = change.change %}
{% endif %} {% endif %}
@ -74,7 +75,7 @@
{% if is_manage %} {% if is_manage %}
<div class="changelog__entry__tags"> <div class="changelog__entry__tags">
{% for tag in change.tags %} {% for tag in tags %}
<a href="{{ is_manage ? url('manage-changelog-tag', {'tag': tag.id}) : url('changelog-index', {'tags': tag.id}) }}" class="changelog__entry__tag"> <a href="{{ is_manage ? url('manage-changelog-tag', {'tag': tag.id}) : url('changelog-index', {'tags': tag.id}) }}" class="changelog__entry__tag">
{{ tag.name }} {{ tag.name }}
</a> </a>

View file

@ -44,7 +44,7 @@
<label class="manage__tag"> <label class="manage__tag">
<div class="manage__tag__background"></div> <div class="manage__tag__background"></div>
<div class="manage__tag__content"> <div class="manage__tag__content">
{{ input_checkbox('cl_tags[]', '', change_info.hasTag(tag)|default(0), 'manage__tag__checkbox', tag.id) }} {{ input_checkbox('cl_tags[]', '', tag.id in change_info_tags, 'manage__tag__checkbox', tag.id) }}
<div class="manage__tag__title"> <div class="manage__tag__title">
{{ tag.name }} {{ tag.name }}
</div> </div>

View file

@ -28,7 +28,7 @@
<label class="manage__emote__field"> <label class="manage__emote__field">
<div class="manage__emote__field__name">Strings</div> <div class="manage__emote__field__name">Strings</div>
{{ input_text('em_strings', 'manage__emote__field__value', emote_info.strings|default([])|join(' '), 'text', '', true) }} {{ input_text('em_strings', 'manage__emote__field__value', emote_strings|default([])|join(' '), 'text', '', true) }}
</label> </label>
<div class="manage__emote__actions"> <div class="manage__emote__actions">