misuzu/src/News/NewsRoutes.php

243 lines
9.4 KiB
PHP

<?php
namespace Misuzu\News;
use RuntimeException;
use Index\DateTime;
use Index\Data\{DbTools,IDbConnection};
use Index\Http\Routing\{HttpGet,RouteHandler};
use Misuzu\{Pagination,SiteInfo,Template};
use Misuzu\Auth\AuthInfo;
use Misuzu\Comments\{Comments,CommentsCategory,CommentsEx};
use Misuzu\Feeds\{Feed,FeedItem,AtomFeedSerializer,RssFeedSerializer};
use Misuzu\Parsers\Parser;
use Misuzu\URLs\{URLInfo,URLRegistry};
use Misuzu\Users\UsersContext;
class NewsRoutes extends RouteHandler {
public function __construct(
private SiteInfo $siteInfo,
private AuthInfo $authInfo,
private URLRegistry $urls,
private News $news,
private UsersContext $usersCtx,
private Comments $comments
) {}
private array $categoryInfos = [];
private function getNewsPostsForView(Pagination $pagination, ?NewsCategoryInfo $categoryInfo = null): array {
$posts = [];
$postInfos = $this->news->getPosts(
categoryInfo: $categoryInfo,
onlyFeatured: $categoryInfo === null,
pagination: $pagination
);
foreach($postInfos as $postInfo) {
$categoryId = $postInfo->getCategoryId();
$userInfo = $postInfo->hasUserId() ? $this->usersCtx->getUserInfo($postInfo->getUserId()) : null;
if(array_key_exists($categoryId, $this->categoryInfos))
$categoryInfo = $this->categoryInfos[$categoryId];
else
$this->categoryInfos[$categoryId] = $categoryInfo = $this->news->getCategory(postInfo: $postInfo);
$commentsCount = $postInfo->hasCommentsCategoryId()
? $this->comments->countPosts(categoryInfo: $postInfo->getCommentsCategoryId(), deleted: false)
: 0;
$posts[] = [
'post' => $postInfo,
'category' => $categoryInfo,
'user' => $userInfo,
'user_colour' => $this->usersCtx->getUserColour($userInfo),
'comments_count' => $commentsCount,
];
}
return $posts;
}
private function getNewsPostsForFeed(?NewsCategoryInfo $categoryInfo = null): array {
$posts = [];
$postInfos = $this->news->getPosts(
categoryInfo: $categoryInfo,
onlyFeatured: $categoryInfo === null,
pagination: new Pagination(10)
);
foreach($postInfos as $postInfo) {
$userId = $postInfo->getUserId();
$categoryId = $postInfo->getCategoryId();
$userInfo = $postInfo->hasUserId() ? $this->usersCtx->getUserInfo($postInfo->getUserId()) : null;
$posts[] = [
'post' => $postInfo,
'category' => $categoryInfo,
'user' => $userInfo,
];
}
return $posts;
}
#[HttpGet('/news')]
#[URLInfo('news-index', '/news', ['p' => '<page>'])]
public function getIndex() {
$categories = $this->news->getCategories(hidden: false);
$pagination = new Pagination($this->news->countPosts(onlyFeatured: true), 5);
if(!$pagination->hasValidOffset())
return 404;
$posts = $this->getNewsPostsForView($pagination);
return Template::renderRaw('news.index', [
'news_categories' => $categories,
'news_posts' => $posts,
'news_pagination' => $pagination,
]);
}
#[HttpGet('/news.rss')]
#[URLInfo('news-feed-rss', '/news.rss')]
public function getFeedRss($response) {
return $this->getFeed($response, 'rss');
}
#[HttpGet('/news.atom')]
#[URLInfo('news-feed-atom', '/news.atom')]
public function getFeedAtom($response) {
return $this->getFeed($response, 'atom');
}
#[HttpGet('/news/([0-9]+)(?:\.(rss|atom))?')]
#[URLInfo('news-category', '/news/<category>', ['p' => '<page>'])]
public function getCategory($response, $request, string $categoryId, string $type = '') {
try {
$categoryInfo = $this->news->getCategory(categoryId: $categoryId);
} catch(RuntimeException $ex) {
return 404;
}
if($type === 'rss')
return $this->getCategoryFeedRss($response, $request, $categoryInfo);
elseif($type === 'atom')
return $this->getCategoryFeedAtom($response, $request, $categoryInfo);
elseif($type !== '')
return 404;
$pagination = new Pagination($this->news->countPosts(categoryInfo: $categoryInfo), 5);
if(!$pagination->hasValidOffset())
return 404;
$posts = $this->getNewsPostsForView($pagination, $categoryInfo);
return Template::renderRaw('news.category', [
'news_category' => $categoryInfo,
'news_posts' => $posts,
'news_pagination' => $pagination,
]);
}
#[URLInfo('news-category-feed-rss', '/news/<category>.rss')]
private function getCategoryFeedRss($response, $request, NewsCategoryInfo $categoryInfo) {
return $this->getFeed($response, 'rss', $categoryInfo);
}
#[URLInfo('news-category-feed-atom', '/news/<category>.atom')]
private function getCategoryFeedAtom($response, $request, NewsCategoryInfo $categoryInfo) {
return $this->getFeed($response, 'atom', $categoryInfo);
}
#[HttpGet('/news/post/([0-9]+)')]
#[URLInfo('news-post', '/news/post/<post>')]
#[URLInfo('news-post-comments', '/news/post/<post>', fragment: 'comments')]
public function getPost($response, $request, string $postId) {
try {
$postInfo = $this->news->getPost($postId);
} catch(RuntimeException $ex) {
return 404;
}
if(!$postInfo->isPublished() || $postInfo->isDeleted())
return 404;
$categoryInfo = $this->news->getCategory(postInfo: $postInfo);
if($postInfo->hasCommentsCategoryId())
try {
$commentsCategory = $this->comments->getCategory(categoryId: $postInfo->getCommentsCategoryId());
} catch(RuntimeException $ex) {}
if(!isset($commentsCategory)) {
$commentsCategory = $this->comments->ensureCategory($postInfo->getCommentsCategoryName());
$this->news->updatePostCommentCategory($postInfo, $commentsCategory);
}
$userInfo = $postInfo->hasUserId() ? $this->usersCtx->getUserInfo($postInfo->getUserId()) : null;
$comments = new CommentsEx($this->authInfo, $this->comments, $this->usersCtx);
return Template::renderRaw('news.post', [
'post_info' => $postInfo,
'post_category_info' => $categoryInfo,
'post_user_info' => $userInfo,
'post_user_colour' => $this->usersCtx->getUserColour($userInfo),
'comments_info' => $comments->getCommentsForLayout($commentsCategory),
]);
}
private function getFeed($response, string $feedType, ?NewsCategoryInfo $categoryInfo = null) {
$hasCategory = $categoryInfo !== null;
$siteName = $this->siteInfo->getName();
$posts = $this->getNewsPostsForFeed($categoryInfo);
$serialiser = match($feedType) {
'rss' => new RssFeedSerializer,
'atom' => new AtomFeedSerializer,
default => throw new RuntimeException('Invalid $feedType specified.'),
};
$response->setContentType(sprintf('application/%s+xml; charset=utf-8', $feedType));
$feed = (new Feed)
->setTitle($siteName . ' » ' . ($hasCategory ? $categoryInfo->getName() : 'Featured News'))
->setDescription($hasCategory ? $categoryInfo->getDescription() : 'A live featured news feed.')
->setContentUrl($this->siteInfo->getURL() . ($hasCategory ? $this->urls->format('news-category', ['category' => $categoryInfo->getId()]) : $this->urls->format('news-index')))
->setFeedUrl($this->siteInfo->getURL() . ($hasCategory ? $this->urls->format("news-category-feed-{$feedType}", ['category' => $categoryInfo->getId()]) : $this->urls->format("news-feed-{$feedType}")));
foreach($posts as $post) {
$postInfo = $post['post'];
$userInfo = $post['user'];
$userId = 0;
$userName = 'Author';
if($userInfo !== null) {
$userId = $userInfo->getId();
$userName = $userInfo->getName();
}
$postUrl = $this->siteInfo->getURL() . $this->urls->format('news-post', ['post' => $postInfo->getId()]);
$commentsUrl = $this->siteInfo->getURL() . $this->urls->format('news-post-comments', ['post' => $postInfo->getId()]);
$authorUrl = $this->siteInfo->getURL() . $this->urls->format('user-profile', ['user' => $userId]);
$feedItem = (new FeedItem)
->setTitle($postInfo->getTitle())
->setSummary($postInfo->getFirstParagraph())
->setContent(Parser::instance(Parser::MARKDOWN)->parseText($postInfo->getBody()))
->setCreationDate($postInfo->getCreatedTime())
->setUniqueId($postUrl)
->setContentUrl($postUrl)
->setCommentsUrl($commentsUrl)
->setAuthorName($userName)
->setAuthorUrl($authorUrl);
if(!$feed->hasLastUpdate() || $feed->getLastUpdate() < $feedItem->getCreationDate())
$feed->setLastUpdate($feedItem->getCreationDate());
$feed->addItem($feedItem);
}
return $serialiser->serializeFeed($feed);
}
}