misuzu/src/News/News.php

481 lines
17 KiB
PHP

<?php
namespace Misuzu\News;
use InvalidArgumentException;
use RuntimeException;
use Index\DateTime;
use Index\Data\IDbConnection;
use Index\Data\IDbResult;
use Misuzu\DbStatementCache;
use Misuzu\Pagination;
use Misuzu\Comments\CommentsCategoryInfo;
use Misuzu\Users\User;
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) {
$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';
}
$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) {
$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 .= 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,
User|string|null $userInfo = null,
DateTime|int|null $schedule = null
): NewsPostInfo {
if($categoryInfo instanceof NewsCategoryInfo)
$categoryInfo = $categoryInfo->getId();
if($userInfo instanceof User)
$userInfo = (string)$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,
User|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 User)
$userInfo = (string)$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();
}
}