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(); } }