misuzu/src/News/News.php

407 lines
15 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);
}
public function countCategories(
?bool $hidden = null
): int {
$hasHidden = $hidden !== null;
$query = 'SELECT COUNT(*) FROM msz_news_categories';
if($hasHidden)
$query .= sprintf(' WHERE category_is_hidden %s 0', $hidden ? '<>' : '=');
$result = $this->dbConn->query($query);
$count = 0;
if($result->next())
$count = $result->getInteger(0);
return $count;
}
public function getCategories(
?bool $hidden = null,
?Pagination $pagination = null
): iterable {
$hasHidden = $hidden !== 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';
if($hasHidden)
$query .= sprintf(' WHERE category_is_hidden %s 0', $hidden ? '<>' : '=');
$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 $stmt->getResult()->getIterator(NewsCategoryInfo::fromResult(...));
}
public function getCategory(
?string $categoryId = null,
NewsPostInfo|string|null $postInfo = null
): NewsCategoryInfo {
$hasCategoryId = $categoryId !== null;
$hasPostInfo = $postInfo !== null;
if(!$hasCategoryId && !$hasPostInfo)
throw new InvalidArgumentException('At least one argument must be specified.');
if($hasCategoryId && $hasPostInfo)
throw new InvalidArgumentException('Only one argument may be specified.');
$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->addParameter(1, $value);
$stmt->execute();
$result = $stmt->getResult();
if(!$result->next())
throw new RuntimeException('News category not found.');
return NewsCategoryInfo::fromResult($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->getCategory(categoryId: (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 countPosts(
NewsCategoryInfo|string|null $categoryInfo = null,
bool $onlyFeatured = false,
bool $includeScheduled = false,
bool $includeDeleted = false
): int {
if($categoryInfo instanceof NewsCategoryInfo)
$categoryInfo = $categoryInfo->getId();
$hasCategoryInfo = $categoryInfo !== null;
$args = 0;
$query = 'SELECT COUNT(*) FROM msz_news_posts';
if($hasCategoryInfo) {
++$args;
$query .= ' WHERE category_id = ?';
}
if($onlyFeatured) {
$query .= (++$args > 1 ? ' AND' : ' WHERE');
$query .= ' 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';
}
$stmt = $this->cache->get($query);
if($hasCategoryInfo)
$stmt->addParameter(1, $categoryInfo);
$stmt->execute();
$result = $stmt->getResult();
$count = 0;
if($result->next())
$count = $result->getInteger(0);
return $count;
}
public function getPosts(
NewsCategoryInfo|string|null $categoryInfo = null,
string $searchQuery = null,
bool $onlyFeatured = false,
bool $includeScheduled = false,
bool $includeDeleted = false,
?Pagination $pagination = null
): iterable {
if($categoryInfo instanceof NewsCategoryInfo)
$categoryInfo = $categoryInfo->getId();
$hasCategoryInfo = $categoryInfo !== null;
$hasSearchQuery = $searchQuery !== null;
$hasPagination = $pagination !== null;
$args = 0;
$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;
$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) {
$query .= (++$args > 1 ? ' AND' : ' WHERE');
$query .= ' post_scheduled <= NOW()';
}
if(!$includeDeleted) {
$query .= (++$args > 1 ? ' AND' : ' WHERE');
$query .= ' post_deleted IS NULL';
}
$query .= ' ORDER BY post_scheduled DESC';
if($hasPagination)
$query .= ' LIMIT ? OFFSET ?';
$stmt = $this->cache->get($query);
$args = 0;
if($hasCategoryInfo)
$stmt->addParameter(++$args, $categoryInfo);
if($hasSearchQuery)
$stmt->addParameter(++$args, $searchQuery);
if($hasPagination) {
$stmt->addParameter(++$args, $pagination->getRange());
$stmt->addParameter(++$args, $pagination->getOffset());
}
$stmt->execute();
return $stmt->getResult()->getIterator(NewsPostInfo::fromResult(...));
}
public function getPost(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 NewsPostInfo::fromResult($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->getPost((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();
}
}