misuzu/src/News/News.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

481 lines
17 KiB
PHP

<?php
namespace Misuzu\News;
use InvalidArgumentException;
use RuntimeException;
use Index\DateTime;
use Index\Data\DbStatementCache;
use Index\Data\IDbConnection;
use Index\Data\IDbResult;
use Misuzu\Pagination;
use Misuzu\Comments\CommentsCategoryInfo;
use Misuzu\Users\UserInfo;
class News {
private IDbConnection $dbConn;
private DbStatementCache $cache;
public function __construct(IDbConnection $dbConn) {
$this->dbConn = $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 {
$posts = [];
while($result->next())
$posts[] = new NewsPostInfo($result);
return $posts;
}
public function countAllCategories(bool $includeHidden = false): int {
$query = 'SELECT COUNT(*) FROM msz_news_categories';
if($includeHidden)
$query .= ' WHERE category_is_hidden = 0';
$result = $this->dbConn->query($query);
$count = 0;
if($result->next())
$count = $result->getInteger(0);
return $count;
}
public function getAllCategories(
bool $includeHidden = false,
?Pagination $pagination = null
): array {
$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';
if(!$includeHidden)
$query .= ' WHERE category_is_hidden = 0';
$query .= ' ORDER BY category_created ASC';
if($hasPagination)
$query .= ' LIMIT ? OFFSET ?';
$stmt = $this->cache->get($query);
$args = 0;
if($hasPagination) {
$stmt->addParameter(++$args, $pagination->getRange());
$stmt->addParameter(++$args, $pagination->getOffset());
}
$stmt->execute();
return self::readCategories($stmt->getResult());
}
public function getCategoryByPost(NewsPostInfo|string $postInfo): NewsCategoryInfo {
$query = 'SELECT category_id, category_name, category_description, category_is_hidden, UNIX_TIMESTAMP(category_created) FROM msz_news_categories WHERE category_id = ';
if($postInfo instanceof NewsPostInfo) {
$query .= '?';
$param = $postInfo->getCategoryId();
} else {
$query .= '(SELECT category_id FROM msz_news_posts WHERE post_id = ?)';
$param = $postInfo;
}
$stmt = $this->cache->get($query);
$stmt->addParameter(1, $param);
$stmt->execute();
$result = $stmt->getResult();
if(!$result->next())
throw new RuntimeException('No news category associated with that ID exists.');
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);
}
public function createCategory(
string $name,
string $description,
bool $hidden
): NewsCategoryInfo {
$name = trim($name);
if(empty($name))
throw new InvalidArgumentException('$name may not be empty');
$description = trim($description);
if(empty($description))
throw new InvalidArgumentException('$description may not be empty');
$stmt = $this->cache->get('INSERT INTO msz_news_categories (category_name, category_description, category_is_hidden) VALUES (?, ?, ?)');
$stmt->addParameter(1, $name);
$stmt->addParameter(2, $description);
$stmt->addParameter(3, $hidden ? 1 : 0);
$stmt->execute();
return $this->getCategoryById((string)$this->dbConn->getLastInsertId());
}
public function deleteCategory(NewsCategoryInfo|string $infoOrId): void {
if($infoOrId instanceof NewsCategoryInfo)
$infoOrId = $infoOrId->getId();
$stmt = $this->cache->get('DELETE FROM msz_news_categories WHERE category_id = ?');
$stmt->addParameter(1, $infoOrId);
$stmt->execute();
}
public function updateCategory(
NewsCategoryInfo|string $infoOrId,
?string $name = null,
?string $description = null,
?bool $hidden = null
): void {
if($infoOrId instanceof NewsCategoryInfo)
$infoOrId = $infoOrId->getId();
if($name !== null) {
$name = trim($name);
if(empty($name))
throw new InvalidArgumentException('$name may not be empty');
}
if($description !== null) {
$description = trim($description);
if(empty($description))
throw new InvalidArgumentException('$description may not be empty');
}
$hasHidden = $hidden !== null;
$stmt = $this->cache->get('UPDATE msz_news_categories SET category_name = COALESCE(?, category_name), category_description = COALESCE(?, category_description), category_is_hidden = IF(?, ?, category_is_hidden) WHERE category_id = ?');
$stmt->addParameter(1, $name);
$stmt->addParameter(2, $description);
$stmt->addParameter(3, $hasHidden ? 1 : 0);
$stmt->addParameter(4, $hidden ? 1 : 0);
$stmt->addParameter(5, $infoOrId);
$stmt->execute();
}
public function countAllPosts(
bool $onlyFeatured = false,
bool $includeScheduled = false,
bool $includeDeleted = false
): int {
$args = 0;
$query = 'SELECT COUNT(*) FROM msz_news_posts';
if($onlyFeatured) {
++$args;
$query .= ' WHERE post_is_featured = 1';
}
if(!$includeScheduled) {
$query .= (++$args > 1 ? ' AND' : ' WHERE');
$query .= ' post_scheduled <= NOW()';
}
if(!$includeDeleted) {
$query .= (++$args > 1 ? ' AND' : ' WHERE');
$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->addParameter(1, $categoryInfo);
$stmt->execute();
$result = $stmt->getResult();
$count = 0;
if($result->next())
$count = $result->getInteger(0);
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';
private const POSTS_SELECT_ORDER = ' ORDER BY post_scheduled DESC';
public function getAllPosts(
bool $onlyFeatured = false,
bool $includeScheduled = false,
bool $includeDeleted = false,
?Pagination $pagination = null
): array {
$args = 0;
$hasPagination = $pagination !== null;
$query = self::POSTS_SELECT_QUERY;
if($onlyFeatured) {
++$args;
$query .= ' WHERE post_is_featured = 1';
}
if(!$includeScheduled) {
$query .= (++$args > 1 ? ' AND' : ' WHERE');
$query .= ' post_scheduled <= NOW()';
}
if(!$includeDeleted) {
$query .= (++$args > 1 ? ' AND' : ' WHERE');
$query .= ' post_deleted IS NULL';
}
$query .= self::POSTS_SELECT_ORDER;
if($hasPagination)
$query .= ' LIMIT ? OFFSET ?';
$stmt = $this->cache->get($query);
$args = 0;
if($hasPagination) {
$stmt->addParameter(++$args, $pagination->getRange());
$stmt->addParameter(++$args, $pagination->getOffset());
}
$stmt->execute();
return self::readPosts($stmt->getResult());
}
public function getPostsByCategory(
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->addParameter(1, $postId);
$stmt->execute();
$result = $stmt->getResult();
if(!$result->next())
throw new RuntimeException('No news post with that ID exists.');
return new NewsPostInfo($result);
}
public function createPost(
NewsCategoryInfo|string $categoryInfo,
string $title,
string $body,
bool $featured = false,
UserInfo|string|null $userInfo = null,
DateTime|int|null $schedule = null
): NewsPostInfo {
if($categoryInfo instanceof NewsCategoryInfo)
$categoryInfo = $categoryInfo->getId();
if($userInfo instanceof UserInfo)
$userInfo = $userInfo->getId();
if($schedule instanceof DateTime)
$schedule = $schedule->getUnixTimeSeconds();
$title = trim($title);
if(empty($title))
throw new InvalidArgumentException('$title may not be empty');
$body = trim($body);
if(empty($body))
throw new InvalidArgumentException('$body may not be empty');
$stmt = $this->cache->get('INSERT INTO msz_news_posts (category_id, user_id, post_is_featured, post_title, post_text, post_scheduled) VALUES (?, ?, ?, ?, ?, ?)');
$stmt->addParameter(1, $categoryInfo);
$stmt->addParameter(2, $userInfo);
$stmt->addParameter(3, $featured ? 1 : 0);
$stmt->addParameter(4, $title);
$stmt->addParameter(5, $body);
$stmt->addParameter(6, $schedule);
$stmt->execute();
return $this->getPostById((string)$this->dbConn->getLastInsertId());
}
public function deletePost(NewsPostInfo|string $postInfo): void {
if($postInfo instanceof NewsPostInfo)
$postInfo = $postInfo->getId();
$stmt = $this->cache->get('UPDATE msz_news_posts SET post_deleted = COALESCE(post_deleted, NOW()) WHERE post_id = ?');
$stmt->addParameter(1, $postInfo);
$stmt->execute();
}
public function restorePost(NewsPostInfo|string $postInfo): void {
if($postInfo instanceof NewsPostInfo)
$postInfo = $postInfo->getId();
$stmt = $this->cache->get('UPDATE msz_news_posts SET post_deleted = NULL WHERE post_id = ?');
$stmt->addParameter(1, $postInfo);
$stmt->execute();
}
public function nukePost(NewsPostInfo|string $postInfo): void {
if($postInfo instanceof NewsPostInfo)
$postInfo = $postInfo->getId();
// should this enforce a soft delete first? (AND post_deleted IS NOT NULL)
$stmt = $this->cache->get('DELETE FROM msz_news_posts WHERE post_id = ?');
$stmt->addParameter(1, $postInfo);
$stmt->execute();
}
public function updatePost(
NewsPostInfo|string $postInfo,
NewsCategoryInfo|string|null $categoryInfo = null,
?string $title = null,
?string $body = null,
?bool $featured = null,
bool $updateUserInfo = false,
UserInfo|string|null $userInfo = null,
DateTime|int|null $schedule = null
): void {
if($postInfo instanceof NewsPostInfo)
$postInfo = $postInfo->getId();
if($categoryInfo instanceof NewsCategoryInfo)
$categoryInfo = $categoryInfo->getId();
if($userInfo instanceof UserInfo)
$userInfo = $userInfo->getId();
if($schedule instanceof DateTime)
$schedule = $schedule->getUnixTimeSeconds();
if($title !== null) {
$title = trim($title);
if(empty($title))
throw new InvalidArgumentException('$title may not be empty');
}
if($body !== null) {
$body = trim($body);
if(empty($body))
throw new InvalidArgumentException('$body may not be empty');
}
$hasFeatured = $featured !== null;
$stmt = $this->cache->get('UPDATE msz_news_posts SET category_id = COALESCE(?, category_id), user_id = IF(?, ?, user_id), post_is_featured = IF(?, ?, post_is_featured), post_title = COALESCE(?, post_title), post_text = COALESCE(?, post_text), post_scheduled = COALESCE(?, post_scheduled) WHERE post_id = ?');
$stmt->addParameter(1, $categoryInfo);
$stmt->addParameter(2, $updateUserInfo ? 1 : 0);
$stmt->addParameter(3, $userInfo);
$stmt->addParameter(4, $hasFeatured ? 1 : 0);
$stmt->addParameter(5, $featured ? 1 : 0);
$stmt->addParameter(6, $title);
$stmt->addParameter(7, $body);
$stmt->addParameter(8, $schedule);
$stmt->addParameter(9, $postInfo);
$stmt->execute();
}
public function updatePostCommentCategory(
NewsPostInfo|string $postInfo,
CommentsCategoryInfo|string $commentsCategory
): void {
if($postInfo instanceof NewsPostInfo)
$postInfo = $postInfo->getId();
if($commentsCategory instanceof CommentsCategoryInfo)
$commentsCategory = $commentsCategory->getId();
$stmt = $this->cache->get('UPDATE msz_news_posts SET comment_section_id = ? WHERE post_id = ?');
$stmt->addParameter(1, $commentsCategory);
$stmt->addParameter(2, $postInfo);
$stmt->execute();
}
}