config = $config; $this->authInfo = $authInfo; $this->news = $news; $this->users = $users; $this->comments = $comments; } public function registerRoutes(IRouter $router): void { $router->get('/news', $this->getIndex(...)); $router->get('/news.rss', $this->getFeedRss(...)); $router->get('/news.atom', $this->getFeedAtom(...)); $router->get('/news/:category', $this->getCategory(...)); $router->get('/news/post/:id', $this->getPost(...)); $router->get('/news.php', function($response, $request) { $postId = $request->getParam('n', FILTER_SANITIZE_NUMBER_INT) ?? $request->getParam('p', FILTER_SANITIZE_NUMBER_INT); if($postId > 0) $location = url('news-post', ['post' => $postId]); else { $catId = $request->getParam('c', FILTER_SANITIZE_NUMBER_INT); $pageId = $request->getParam('page', FILTER_SANITIZE_NUMBER_INT); $location = url($catId > 0 ? 'news-category' : 'news-index', ['category' => $catId, 'page' => $pageId]); } $response->redirect($location, true); }); $router->get('/news.php/rss', function($response, $request) { $catId = $request->getParam('c', FILTER_SANITIZE_NUMBER_INT); $location = url($catId > 0 ? 'news-category-feed-rss' : 'news-feed-rss', ['category' => $catId]); $response->redirect($location, true); }); $router->get('/news.php/atom', function($response, $request) { $catId = $request->getParam('c', FILTER_SANITIZE_NUMBER_INT); $location = url($catId > 0 ? 'news-category-feed-atom' : 'news-feed-atom', ['category' => $catId]); $response->redirect($location, true); }); $router->get('/news/index.php', function($response, $request) { $response->redirect(url('news-index', [ 'page' => $request->getParam('page', FILTER_SANITIZE_NUMBER_INT), ]), true); }); $router->get('/news/category.php', function($response, $request) { $response->redirect(url('news-category', [ 'category' => $request->getParam('c', FILTER_SANITIZE_NUMBER_INT), 'page' => $request->getParam('p', FILTER_SANITIZE_NUMBER_INT), ]), true); }); $router->get('/news/post.php', function($response, $request) { $response->redirect(url('news-post', [ 'post' => $request->getParam('p', FILTER_SANITIZE_NUMBER_INT), ]), true); }); $router->get('/news/feed.php/rss', function($response, $request) { $catId = (int)$request->getParam('c', FILTER_SANITIZE_NUMBER_INT); $response->redirect(url( $catId > 0 ? 'news-category-feed-rss' : 'news-feed-rss', ['category' => $catId] ), true); }); $router->get('/news/feed.php/atom', function($response, $request) { $catId = (int)$request->getParam('c', FILTER_SANITIZE_NUMBER_INT); $response->redirect(url( $catId > 0 ? 'news-category-feed-atom' : 'news-feed-atom', ['category' => $catId] ), true); }); } private array $userInfos = []; private array $userColours = []; 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) { $userId = $postInfo->getUserId(); $categoryId = $postInfo->getCategoryId(); if(array_key_exists($userId, $this->userInfos)) { $userInfo = $this->userInfos[$userId]; } else { try { $userInfo = $this->users->getUser($userId, 'id'); } catch(RuntimeException $ex) { $userInfo = null; } $this->userInfos[$userId] = $userInfo; } if(array_key_exists($userId, $this->userColours)) { $userColour = $this->userColours[$userId]; } else { try { $userColour = $this->users->getUserColour($userInfo); } catch(RuntimeException $ex) { $userColour = null; } $this->userColours[$userId] = $userColour; } 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' => $userColour, '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(); if(array_key_exists($userId, $this->userInfos)) { $userInfo = $this->userInfos[$userId]; } else { try { $userInfo = $this->users->getUser($userId, 'id'); } catch(RuntimeException $ex) { $userInfo = null; } $this->userInfos[$userId] = $userInfo; } $posts[] = [ 'post' => $postInfo, 'category' => $categoryInfo, 'user' => $userInfo, ]; } return $posts; } 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, ]); } public function getFeedRss($response) { return $this->getFeed($response, 'rss'); } public function getFeedAtom($response) { return $this->getFeed($response, 'atom'); } public function getCategory($response, $request, string $fileName) { $categoryId = pathinfo($fileName, PATHINFO_FILENAME); $type = pathinfo($fileName, PATHINFO_EXTENSION); 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, ]); } private function getCategoryFeedRss($response, $request, NewsCategoryInfo $categoryInfo) { return $this->getFeed($response, 'rss', $categoryInfo); } private function getCategoryFeedAtom($response, $request, NewsCategoryInfo $categoryInfo) { return $this->getFeed($response, 'atom', $categoryInfo); } 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 = null; $userColour = null; if($postInfo->hasUserId()) try { $userInfo = $this->users->getUser($postInfo->getUserId(), 'id'); $userColour = $this->users->getUserColour($userInfo); } catch(RuntimeException $ex) {} $comments = new CommentsEx($this->authInfo, $this->comments, $this->users); return Template::renderRaw('news.post', [ 'post_info' => $postInfo, 'post_category_info' => $categoryInfo, 'post_user_info' => $userInfo, 'post_user_colour' => $userColour, 'comments_info' => $comments->getCommentsForLayout($commentsCategory), ]); } private function getFeed($response, string $feedType, ?NewsCategoryInfo $categoryInfo = null) { $hasCategory = $categoryInfo !== null; $siteName = $this->config->getString('site.name', 'Misuzu'); $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(url_prefix(false) . ($hasCategory ? url('news-category', ['category' => $categoryInfo->getId()]) : url('news-index'))) ->setFeedUrl(url_prefix(false) . ($hasCategory ? url("news-category-feed-{$feedType}", ['category' => $categoryInfo->getId()]) : url("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 = url_prefix(false) . url('news-post', ['post' => $postInfo->getId()]); $commentsUrl = url_prefix(false) . url('news-post-comments', ['post' => $postInfo->getId()]); $authorUrl = url_prefix(false) . url('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); } }