481 lines
17 KiB
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\CommentsCategory;
|
|
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,
|
|
CommentsCategory|string $commentsCategory
|
|
): void {
|
|
if($postInfo instanceof NewsPostInfo)
|
|
$postInfo = $postInfo->getId();
|
|
if($commentsCategory instanceof CommentsCategory)
|
|
$commentsCategory = (string)$commentsCategory->getId();
|
|
|
|
// "post_updated = post_updated" is an Attempt at making this not bump post_updated ON UPDATE
|
|
$stmt = $this->cache->get('UPDATE msz_news_posts SET comment_section_id = ?, post_updated = post_updated WHERE post_id = ?');
|
|
$stmt->addParameter(1, $postInfo);
|
|
$stmt->addParameter(2, $commentsCategory);
|
|
$stmt->execute();
|
|
}
|
|
}
|