Rewrote the news backend.
This commit is contained in:
parent
69c6b6f2ac
commit
bff42c26ab
25 changed files with 1113 additions and 634 deletions
|
@ -1,7 +1,6 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
use Misuzu\News\NewsCategory;
|
||||
use Misuzu\Users\User;
|
||||
|
||||
require_once '../../../misuzu.php';
|
||||
|
@ -11,16 +10,17 @@ if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_NEWS, User::getCurrent()->
|
|||
return;
|
||||
}
|
||||
|
||||
$categoriesPagination = new Pagination(NewsCategory::countAll(true), 15);
|
||||
$news = $msz->getNews();
|
||||
$pagination = new Pagination($news->countAllCategories(true), 15);
|
||||
|
||||
if(!$categoriesPagination->hasValidOffset()) {
|
||||
if(!$pagination->hasValidOffset()) {
|
||||
echo render_error(404);
|
||||
return;
|
||||
}
|
||||
|
||||
$categories = NewsCategory::all($categoriesPagination, true);
|
||||
$categories = $news->getAllCategories(true, $pagination);
|
||||
|
||||
Template::render('manage.news.categories', [
|
||||
'news_categories' => $categories,
|
||||
'categories_pagination' => $categoriesPagination,
|
||||
'categories_pagination' => $pagination,
|
||||
]);
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
use RuntimeException;
|
||||
use Misuzu\AuditLog;
|
||||
use Misuzu\News\NewsCategory;
|
||||
use Misuzu\News\NewsCategoryNotFoundException;
|
||||
use Misuzu\Users\User;
|
||||
|
||||
require_once '../../../misuzu.php';
|
||||
|
@ -13,39 +12,62 @@ if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_NEWS, User::getCurrent()->
|
|||
return;
|
||||
}
|
||||
|
||||
$categoryId = (int)filter_input(INPUT_GET, 'c', FILTER_SANITIZE_NUMBER_INT);
|
||||
$news = $msz->getNews();
|
||||
$categoryId = (string)filter_input(INPUT_GET, 'c', FILTER_SANITIZE_NUMBER_INT);
|
||||
$loadCategoryInfo = fn() => $news->getCategoryById($categoryId);
|
||||
|
||||
if($categoryId > 0)
|
||||
if(empty($categoryId))
|
||||
$isNew = true;
|
||||
else
|
||||
try {
|
||||
$categoryInfo = NewsCategory::byId($categoryId);
|
||||
Template::set('category_info', $categoryInfo);
|
||||
} catch(NewsCategoryNotFoundException $ex) {
|
||||
$isNew = false;
|
||||
$categoryInfo = $loadCategoryInfo();
|
||||
} catch(RuntimeException $ex) {
|
||||
echo render_error(404);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!empty($_POST['category']) && CSRF::validateRequest()) {
|
||||
if(!isset($categoryInfo)) {
|
||||
$categoryInfo = new NewsCategory;
|
||||
$isNew = true;
|
||||
if($_SERVER['REQUEST_METHOD'] === 'GET' && !empty($_GET['delete'])) {
|
||||
if(CSRF::validateRequest()) {
|
||||
$news->deleteCategory($categoryInfo);
|
||||
AuditLog::create(AuditLog::NEWS_CATEGORY_DELETE, [$categoryInfo->getId()]);
|
||||
url_redirect('manage-news-categories');
|
||||
} else render_error(403);
|
||||
return;
|
||||
}
|
||||
|
||||
$categoryInfo->setName($_POST['category']['name'])
|
||||
->setDescription($_POST['category']['description'])
|
||||
->setHidden(!empty($_POST['category']['hidden']))
|
||||
->save();
|
||||
while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
||||
$name = trim((string)filter_input(INPUT_POST, 'nc_name'));
|
||||
$description = trim((string)filter_input(INPUT_POST, 'nc_desc'));
|
||||
$hidden = !empty($_POST['nc_hidden']);
|
||||
|
||||
if($isNew) {
|
||||
$categoryInfo = $news->createCategory($name, $description, $hidden);
|
||||
} else {
|
||||
if($name === $categoryInfo->getName())
|
||||
$name = null;
|
||||
if($description === $categoryInfo->getDescription())
|
||||
$description = null;
|
||||
if($hidden === $categoryInfo->isHidden())
|
||||
$hidden = null;
|
||||
|
||||
if($name !== null || $description !== null || $hidden !== null)
|
||||
$news->updateCategory($categoryInfo, $name, $description, $hidden);
|
||||
}
|
||||
|
||||
AuditLog::create(
|
||||
empty($isNew)
|
||||
? AuditLog::NEWS_CATEGORY_EDIT
|
||||
: AuditLog::NEWS_CATEGORY_CREATE,
|
||||
$isNew ? AuditLog::NEWS_CATEGORY_CREATE : AuditLog::NEWS_CATEGORY_EDIT,
|
||||
[$categoryInfo->getId()]
|
||||
);
|
||||
|
||||
if(!empty($isNew)) {
|
||||
header('Location: ' . url('manage-news-category', ['category' => $categoryInfo->getId()]));
|
||||
if($isNew) {
|
||||
url_redirect('manage-news-category', ['category' => $categoryInfo->getId()]);
|
||||
return;
|
||||
}
|
||||
} else $categoryInfo = $loadCategoryInfo();
|
||||
break;
|
||||
}
|
||||
|
||||
Template::render('manage.news.category');
|
||||
Template::render('manage.news.category', [
|
||||
'category_new' => $isNew,
|
||||
'category_info' => $categoryInfo ?? null,
|
||||
]);
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
use RuntimeException;
|
||||
use Misuzu\AuditLog;
|
||||
use Misuzu\News\NewsCategory;
|
||||
use Misuzu\News\NewsPost;
|
||||
use Misuzu\News\NewsPostNotFoundException;
|
||||
use Misuzu\Users\User;
|
||||
|
||||
require_once '../../../misuzu.php';
|
||||
|
@ -14,52 +12,74 @@ if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_NEWS, User::getCurrent()->
|
|||
return;
|
||||
}
|
||||
|
||||
$postId = (int)filter_input(INPUT_GET, 'p', FILTER_SANITIZE_NUMBER_INT);
|
||||
if($postId > 0)
|
||||
$news = $msz->getNews();
|
||||
$postId = (string)filter_input(INPUT_GET, 'p', FILTER_SANITIZE_NUMBER_INT);
|
||||
$loadPostInfo = fn() => $news->getPostById($postId);
|
||||
|
||||
if(empty($postId))
|
||||
$isNew = true;
|
||||
else
|
||||
try {
|
||||
$postInfo = NewsPost::byId($postId);
|
||||
Template::set('post_info', $postInfo);
|
||||
} catch(NewsPostNotFoundException $ex) {
|
||||
$isNew = false;
|
||||
$postInfo = $loadPostInfo();
|
||||
} catch(RuntimeException $ex) {
|
||||
echo render_error(404);
|
||||
return;
|
||||
}
|
||||
|
||||
$categories = NewsCategory::all(null, true);
|
||||
|
||||
if(!empty($_POST['post']) && CSRF::validateRequest()) {
|
||||
if(!isset($postInfo)) {
|
||||
$postInfo = new NewsPost;
|
||||
$isNew = true;
|
||||
if($_SERVER['REQUEST_METHOD'] === 'GET' && !empty($_GET['delete'])) {
|
||||
if(CSRF::validateRequest()) {
|
||||
$news->deletePost($postInfo);
|
||||
AuditLog::create(AuditLog::NEWS_POST_DELETE, [$postInfo->getId()]);
|
||||
url_redirect('manage-news-posts');
|
||||
} else render_error(403);
|
||||
return;
|
||||
}
|
||||
|
||||
$currentUserId = User::getCurrent()->getId();
|
||||
$postInfo->setTitle( $_POST['post']['title'])
|
||||
->setText($_POST['post']['text'])
|
||||
->setCategoryId($_POST['post']['category'])
|
||||
->setFeatured(!empty($_POST['post']['featured']));
|
||||
while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
||||
$title = trim((string)filter_input(INPUT_POST, 'np_title'));
|
||||
$category = (string)filter_input(INPUT_POST, 'np_category', FILTER_SANITIZE_NUMBER_INT);
|
||||
$featured = !empty($_POST['np_featured']);
|
||||
$body = trim((string)filter_input(INPUT_POST, 'np_body'));
|
||||
|
||||
if(!empty($isNew))
|
||||
$postInfo->setUserId($currentUserId);
|
||||
if($isNew) {
|
||||
$postInfo = $news->createPost($category, $title, $body, $featured, User::getCurrent());
|
||||
} else {
|
||||
if($category === $postInfo->getCategoryId())
|
||||
$category = null;
|
||||
if($title === $postInfo->getTitle())
|
||||
$title = null;
|
||||
if($body === $postInfo->getBody())
|
||||
$body = null;
|
||||
if($featured === $postInfo->isFeatured())
|
||||
$featured = null;
|
||||
|
||||
$postInfo->save();
|
||||
if($category !== null || $title !== null || $body !== null || $featured !== null)
|
||||
$news->updatePost($postInfo, $category, $title, $body, $featured);
|
||||
}
|
||||
|
||||
AuditLog::create(
|
||||
empty($isNew)
|
||||
? AuditLog::NEWS_POST_EDIT
|
||||
: AuditLog::NEWS_POST_CREATE,
|
||||
$isNew ? AuditLog::NEWS_POST_CREATE : AuditLog::NEWS_POST_EDIT,
|
||||
[$postInfo->getId()]
|
||||
);
|
||||
|
||||
if(!empty($isNew)) {
|
||||
if($isNew) {
|
||||
if($postInfo->isFeatured()) {
|
||||
// Twitter integration used to be here, replace with Railgun Pulse integration
|
||||
}
|
||||
|
||||
header('Location: ' . url('manage-news-post', ['post' => $postInfo->getId()]));
|
||||
url_redirect('manage-news-post', ['post' => $postInfo->getId()]);
|
||||
return;
|
||||
} else $postInfo = $loadPostInfo();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$categories = [];
|
||||
foreach($news->getAllCategories(true) as $categoryInfo)
|
||||
$categories[$categoryInfo->getId()] = $categoryInfo->getName();
|
||||
|
||||
Template::render('manage.news.post', [
|
||||
'categories' => $categories,
|
||||
'post_new' => $isNew,
|
||||
'post_info' => $postInfo ?? null,
|
||||
]);
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
use Misuzu\News\NewsPost;
|
||||
use Misuzu\Users\User;
|
||||
|
||||
require_once '../../../misuzu.php';
|
||||
|
@ -11,16 +10,24 @@ if(!User::hasCurrent() || !perms_check_user(MSZ_PERMS_NEWS, User::getCurrent()->
|
|||
return;
|
||||
}
|
||||
|
||||
$postsPagination = new Pagination(NewsPost::countAll(false, true, true), 15);
|
||||
$news = $msz->getNews();
|
||||
$pagination = new Pagination($news->countAllPosts(
|
||||
includeScheduled: true,
|
||||
includeDeleted: true
|
||||
), 15);
|
||||
|
||||
if(!$postsPagination->hasValidOffset()) {
|
||||
if(!$pagination->hasValidOffset()) {
|
||||
echo render_error(404);
|
||||
return;
|
||||
}
|
||||
|
||||
$posts = NewsPost::all($postsPagination, false, true, true);
|
||||
$posts = $news->getAllPosts(
|
||||
includeScheduled: true,
|
||||
includeDeleted: true,
|
||||
pagination: $pagination
|
||||
);
|
||||
|
||||
Template::render('manage.news.posts', [
|
||||
'news_posts' => $posts,
|
||||
'posts_pagination' => $postsPagination,
|
||||
'posts_pagination' => $pagination,
|
||||
]);
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
use Misuzu\News\NewsPost;
|
||||
use Misuzu\Comments\CommentsCategory;
|
||||
use Misuzu\Comments\CommentsCategoryNotFoundException;
|
||||
use Misuzu\Users\User;
|
||||
|
||||
require_once '../misuzu.php';
|
||||
|
@ -11,7 +12,48 @@ $searchQuery = !empty($_GET['q']) && is_string($_GET['q']) ? $_GET['q'] : '';
|
|||
if(!empty($searchQuery)) {
|
||||
$forumTopics = forum_topic_listing_search($searchQuery, User::hasCurrent() ? User::getCurrent()->getId() : 0);
|
||||
$forumPosts = forum_post_search($searchQuery);
|
||||
$newsPosts = NewsPost::bySearchQuery($searchQuery);
|
||||
|
||||
// this sure is an expansion
|
||||
$news = $msz->getNews();
|
||||
$newsPosts = [];
|
||||
$newsPostInfos = $news->getPostsBySearchQuery($searchQuery);
|
||||
$newsUserInfos = [];
|
||||
$newsCategoryInfos = [];
|
||||
|
||||
foreach($newsPostInfos as $postInfo) {
|
||||
$userId = $postInfo->getUserId();
|
||||
$categoryId = $postInfo->getCategoryId();
|
||||
|
||||
if(array_key_exists($userId, $newsUserInfos)) {
|
||||
$userInfo = $newsUserInfos[$userId];
|
||||
} else {
|
||||
try {
|
||||
$userInfo = User::byId($userId);
|
||||
} catch(UserNotFoundException $ex) {
|
||||
$userInfo = null;
|
||||
}
|
||||
|
||||
$newsUserInfos[$userId] = $userInfo;
|
||||
}
|
||||
|
||||
if(array_key_exists($categoryId, $newsCategoryInfos))
|
||||
$categoryInfo = $newsCategoryInfos[$categoryId];
|
||||
else
|
||||
$newsCategoryInfos[$categoryId] = $categoryInfo = $news->getCategoryByPost($postInfo);
|
||||
|
||||
$commentsCount = 0;
|
||||
if($postInfo->hasCommentsCategoryId())
|
||||
try {
|
||||
$commentsCount = CommentsCategory::byId($postInfo->getCommentsCategoryId())->getPostCount();
|
||||
} catch(CommentsCategoryNotFoundException $ex) {}
|
||||
|
||||
$newsPosts[] = [
|
||||
'post' => $postInfo,
|
||||
'category' => $categoryInfo,
|
||||
'user' => $userInfo,
|
||||
'comments_count' => $commentsCount,
|
||||
];
|
||||
}
|
||||
|
||||
$findUsers = DB::prepare('
|
||||
SELECT u.`user_id`, u.`username`, u.`user_country`,
|
||||
|
|
|
@ -31,8 +31,10 @@ class AuditLog {
|
|||
|
||||
public const NEWS_POST_CREATE = 'NEWS_POST_CREATE';
|
||||
public const NEWS_POST_EDIT = 'NEWS_POST_EDIT';
|
||||
public const NEWS_POST_DELETE = 'NEWS_POST_DELETE';
|
||||
public const NEWS_CATEGORY_CREATE = 'NEWS_CATEGORY_CREATE';
|
||||
public const NEWS_CATEGORY_EDIT = 'NEWS_CATEGORY_EDIT';
|
||||
public const NEWS_CATEGORY_DELETE = 'NEWS_CATEGORY_DELETE';
|
||||
|
||||
public const FORUM_TOPIC_DELETE = 'FORUM_TOPIC_DELETE';
|
||||
public const FORUM_TOPIC_RESTORE = 'FORUM_TOPIC_RESTORE';
|
||||
|
@ -83,8 +85,10 @@ class AuditLog {
|
|||
|
||||
self::NEWS_POST_CREATE => 'Created news post #%d.',
|
||||
self::NEWS_POST_EDIT => 'Edited news post #%d.',
|
||||
self::NEWS_POST_DELETE => 'Deleted news post #%d.',
|
||||
self::NEWS_CATEGORY_CREATE => 'Created news category #%d.',
|
||||
self::NEWS_CATEGORY_EDIT => 'Edited news category #%d.',
|
||||
self::NEWS_CATEGORY_DELETE => 'Deleted news category #%d.',
|
||||
|
||||
self::FORUM_POST_EDIT => 'Edited forum post #%d.',
|
||||
self::FORUM_POST_DELETE => 'Deleted forum post #%d.',
|
||||
|
|
|
@ -6,9 +6,11 @@ use Misuzu\Config\IConfig;
|
|||
use Misuzu\DB;
|
||||
use Misuzu\Pagination;
|
||||
use Misuzu\Template;
|
||||
use Misuzu\News\NewsPost;
|
||||
use Misuzu\Comments\CommentsCategory;
|
||||
use Misuzu\Comments\CommentsCategoryNotFoundException;
|
||||
use Misuzu\Users\User;
|
||||
use Misuzu\Users\UserSession;
|
||||
use Misuzu\Users\UserNotFoundException;
|
||||
|
||||
final class HomeHandler extends Handler {
|
||||
public function index($response, $request): void {
|
||||
|
@ -27,8 +29,10 @@ final class HomeHandler extends Handler {
|
|||
'same_as' => Config::get('social.linked', IConfig::T_ARR),
|
||||
] : null;
|
||||
|
||||
|
||||
$featuredNews = NewsPost::all(new Pagination(3), true);
|
||||
$featuredNews = $this->context->getNews()->getAllPosts(
|
||||
onlyFeatured: true,
|
||||
pagination: new Pagination(3)
|
||||
);
|
||||
|
||||
$stats = DB::query(
|
||||
'SELECT'
|
||||
|
@ -102,7 +106,49 @@ final class HomeHandler extends Handler {
|
|||
}
|
||||
|
||||
public function home($response, $request): void {
|
||||
$featuredNews = NewsPost::all(new Pagination(5), true);
|
||||
$news = $this->context->getNews();
|
||||
$featuredNews = [];
|
||||
$userInfos = [];
|
||||
$categoryInfos = [];
|
||||
$featuredNewsInfos = $news->getAllPosts(
|
||||
onlyFeatured: true,
|
||||
pagination: new Pagination(5)
|
||||
);
|
||||
|
||||
foreach($featuredNewsInfos as $postInfo) {
|
||||
$userId = $postInfo->getUserId();
|
||||
$categoryId = $postInfo->getCategoryId();
|
||||
|
||||
if(array_key_exists($userId, $userInfos)) {
|
||||
$userInfo = $userInfos[$userId];
|
||||
} else {
|
||||
try {
|
||||
$userInfo = User::byId($userId);
|
||||
} catch(UserNotFoundException $ex) {
|
||||
$userInfo = null;
|
||||
}
|
||||
|
||||
$userInfos[$userId] = $userInfo;
|
||||
}
|
||||
|
||||
if(array_key_exists($categoryId, $categoryInfos))
|
||||
$categoryInfo = $categoryInfos[$categoryId];
|
||||
else
|
||||
$categoryInfos[$categoryId] = $categoryInfo = $news->getCategoryByPost($postInfo);
|
||||
|
||||
$commentsCount = 0;
|
||||
if($postInfo->hasCommentsCategoryId())
|
||||
try {
|
||||
$commentsCount = CommentsCategory::byId($postInfo->getCommentsCategoryId())->getPostCount();
|
||||
} catch(CommentsCategoryNotFoundException $ex) {}
|
||||
|
||||
$featuredNews[] = [
|
||||
'post' => $postInfo,
|
||||
'category' => $categoryInfo,
|
||||
'user' => $userInfo,
|
||||
'comments_count' => $commentsCount,
|
||||
];
|
||||
}
|
||||
|
||||
$stats = DB::query(
|
||||
'SELECT'
|
||||
|
|
|
@ -1,44 +1,95 @@
|
|||
<?php
|
||||
namespace Misuzu\Http\Handlers;
|
||||
|
||||
use RuntimeException;
|
||||
use Misuzu\Config;
|
||||
use Misuzu\Config\IConfig;
|
||||
use Misuzu\DB;
|
||||
use Misuzu\Pagination;
|
||||
use Misuzu\Template;
|
||||
use Misuzu\Comments\CommentsCategory;
|
||||
use Misuzu\Comments\CommentsCategoryNotFoundException;
|
||||
use Misuzu\Config\IConfig;
|
||||
use Misuzu\Feeds\Feed;
|
||||
use Misuzu\Feeds\FeedItem;
|
||||
use Misuzu\Feeds\AtomFeedSerializer;
|
||||
use Misuzu\Feeds\RssFeedSerializer;
|
||||
use Misuzu\News\NewsCategory;
|
||||
use Misuzu\News\NewsPost;
|
||||
use Misuzu\News\NewsCategoryNotFoundException;
|
||||
use Misuzu\News\NewsPostNotFoundException;
|
||||
use Misuzu\News\NewsCategoryInfo;
|
||||
use Misuzu\Parsers\Parser;
|
||||
use Misuzu\Users\User;
|
||||
use Misuzu\Users\UserNotFoundException;
|
||||
|
||||
final class NewsHandler extends Handler {
|
||||
public function index($response, $request) {
|
||||
$categories = NewsCategory::all();
|
||||
$newsPagination = new Pagination(NewsPost::countAll(true), 5);
|
||||
private function fetchPostInfo(array $postInfos, array $categoryInfos = []): array {
|
||||
$news = $this->context->getNews();
|
||||
$posts = [];
|
||||
$userInfos = [];
|
||||
|
||||
if(!$newsPagination->hasValidOffset())
|
||||
foreach($postInfos as $postInfo) {
|
||||
$userId = $postInfo->getUserId();
|
||||
$categoryId = $postInfo->getCategoryId();
|
||||
|
||||
if(array_key_exists($userId, $userInfos)) {
|
||||
$userInfo = $userInfos[$userId];
|
||||
} else {
|
||||
try {
|
||||
$userInfo = User::byId($userId);
|
||||
} catch(UserNotFoundException $ex) {
|
||||
$userInfo = null;
|
||||
}
|
||||
|
||||
$userInfos[$userId] = $userInfo;
|
||||
}
|
||||
|
||||
if(array_key_exists($categoryId, $categoryInfos))
|
||||
$categoryInfo = $categoryInfos[$categoryId];
|
||||
else
|
||||
$categoryInfos[$categoryId] = $categoryInfo = $news->getCategoryByPost($postInfo);
|
||||
|
||||
$commentsCount = 0;
|
||||
if($postInfo->hasCommentsCategoryId())
|
||||
try {
|
||||
$commentsCount = CommentsCategory::byId($postInfo->getCommentsCategoryId())->getPostCount();
|
||||
} catch(CommentsCategoryNotFoundException $ex) {}
|
||||
|
||||
$posts[] = [
|
||||
'post' => $postInfo,
|
||||
'category' => $categoryInfo,
|
||||
'user' => $userInfo,
|
||||
'comments_count' => $commentsCount,
|
||||
];
|
||||
}
|
||||
|
||||
return $posts;
|
||||
}
|
||||
|
||||
public function index($response, $request) {
|
||||
$news = $this->context->getNews();
|
||||
|
||||
$categories = $news->getAllCategories();
|
||||
$pagination = new Pagination($news->countAllPosts(onlyFeatured: true), 5);
|
||||
|
||||
if(!$pagination->hasValidOffset())
|
||||
return 404;
|
||||
|
||||
$postInfos = $news->getAllPosts(onlyFeatured: true, pagination: $pagination);
|
||||
$posts = $this->fetchPostInfo($postInfos);
|
||||
|
||||
$response->setContent(Template::renderRaw('news.index', [
|
||||
'categories' => $categories,
|
||||
'posts' => NewsPost::all($newsPagination, true),
|
||||
'news_pagination' => $newsPagination,
|
||||
'news_categories' => $categories,
|
||||
'news_posts' => $posts,
|
||||
'news_pagination' => $pagination,
|
||||
]));
|
||||
}
|
||||
|
||||
public function viewCategory($response, $request, string $fileName) {
|
||||
$categoryId = (int)pathinfo($fileName, PATHINFO_FILENAME);
|
||||
$news = $this->context->getNews();
|
||||
|
||||
$categoryId = pathinfo($fileName, PATHINFO_FILENAME);
|
||||
$type = pathinfo($fileName, PATHINFO_EXTENSION);
|
||||
|
||||
try {
|
||||
$categoryInfo = NewsCategory::byId($categoryId);
|
||||
} catch(NewsCategoryNotFoundException $ex) {
|
||||
$categoryInfo = $news->getCategoryById($categoryId);
|
||||
} catch(RuntimeException $ex) {
|
||||
return 404;
|
||||
}
|
||||
|
||||
|
@ -49,41 +100,64 @@ final class NewsHandler extends Handler {
|
|||
elseif($type !== '')
|
||||
return 404;
|
||||
|
||||
$categoryPagination = new Pagination(NewsPost::countByCategory($categoryInfo), 5);
|
||||
if(!$categoryPagination->hasValidOffset())
|
||||
$pagination = new Pagination($news->countPostsByCategory($categoryInfo), 5);
|
||||
if(!$pagination->hasValidOffset())
|
||||
return 404;
|
||||
|
||||
$postInfos = $news->getPostsByCategory($categoryInfo, pagination: $pagination);
|
||||
$posts = $this->fetchPostInfo($postInfos, [$categoryInfo->getId() => $categoryInfo]);
|
||||
|
||||
$response->setContent(Template::renderRaw('news.category', [
|
||||
'category_info' => $categoryInfo,
|
||||
'posts' => $categoryInfo->posts($categoryPagination),
|
||||
'news_pagination' => $categoryPagination,
|
||||
'news_category' => $categoryInfo,
|
||||
'news_posts' => $posts,
|
||||
'news_pagination' => $pagination,
|
||||
]));
|
||||
}
|
||||
|
||||
public function viewPost($response, $request, int $postId) {
|
||||
public function viewPost($response, $request, string $postId) {
|
||||
$news = $this->context->getNews();
|
||||
|
||||
try {
|
||||
$postInfo = NewsPost::byId($postId);
|
||||
} catch(NewsPostNotFoundException $ex) {
|
||||
$postInfo = $news->getPostById($postId);
|
||||
} catch(RuntimeException $ex) {
|
||||
return 404;
|
||||
}
|
||||
|
||||
if(!$postInfo->isPublished() || $postInfo->isDeleted())
|
||||
return 404;
|
||||
|
||||
$postInfo->ensureCommentsCategory();
|
||||
$commentsInfo = $postInfo->getCommentsCategory();
|
||||
$categoryInfo = $news->getCategoryByPost($postInfo);
|
||||
|
||||
if($postInfo->hasCommentsCategoryId()) {
|
||||
$commentsCategory = CommentsCategory::byId($postInfo->getCommentsCategoryId());
|
||||
} else {
|
||||
$commentsCategoryName = $postInfo->getCommentsCategoryName();
|
||||
try {
|
||||
$commentsCategory = CommentsCategory::byName($commentsCategoryName);
|
||||
} catch(CommentsCategoryNotFoundException $ex) {
|
||||
$commentsCategory = new CommentsCategory($commentsCategoryName);
|
||||
$commentsCategory->save();
|
||||
$news->updatePostCommentCategory($postInfo, $commentsCategory);
|
||||
}
|
||||
}
|
||||
|
||||
$userInfo = null;
|
||||
if($postInfo->hasUserId())
|
||||
try {
|
||||
$userInfo = User::byId($postInfo->getUserId());
|
||||
} catch(UserNotFoundException $ex) {}
|
||||
|
||||
$response->setContent(Template::renderRaw('news.post', [
|
||||
'post_info' => $postInfo,
|
||||
'comments_info' => $commentsInfo,
|
||||
'post_category_info' => $categoryInfo,
|
||||
'post_user_info' => $userInfo,
|
||||
'comments_info' => $commentsCategory,
|
||||
'comments_user' => User::getCurrent(),
|
||||
]));
|
||||
}
|
||||
|
||||
private function createFeed(string $feedMode, ?NewsCategory $categoryInfo, array $posts): Feed {
|
||||
$hasCategory = !empty($categoryInfo);
|
||||
$pagination = new Pagination(10);
|
||||
$posts = $hasCategory ? $categoryInfo->posts($pagination) : NewsPost::all($pagination, true);
|
||||
private function createFeed(string $feedMode, ?NewsCategoryInfo $categoryInfo, array $posts): Feed {
|
||||
$hasCategory = $categoryInfo !== null;
|
||||
|
||||
$feed = (new Feed)
|
||||
->setTitle(Config::get('site.name', IConfig::T_STR, 'Misuzu') . ' » ' . ($hasCategory ? $categoryInfo->getName() : 'Featured News'))
|
||||
|
@ -92,19 +166,29 @@ final class NewsHandler extends Handler {
|
|||
->setFeedUrl(url_prefix(false) . ($hasCategory ? url("news-category-feed-{$feedMode}", ['category' => $categoryInfo->getId()]) : url("news-feed-{$feedMode}")));
|
||||
|
||||
foreach($posts as $post) {
|
||||
$postUrl = url_prefix(false) . url('news-post', ['post' => $post->getId()]);
|
||||
$commentsUrl = url_prefix(false) . url('news-post-comments', ['post' => $post->getId()]);
|
||||
$authorUrl = url_prefix(false) . url('user-profile', ['user' => $post->getUser()->getId()]);
|
||||
$postInfo = $post['post'];
|
||||
$userInfo = $post['user'];
|
||||
|
||||
$userId = 0;
|
||||
$userName = 'Author';
|
||||
if($userInfo !== null) {
|
||||
$userId = $userInfo->getId();
|
||||
$userName = $userInfo->getUsername();
|
||||
}
|
||||
|
||||
$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($post->getTitle())
|
||||
->setSummary($post->getFirstParagraph())
|
||||
->setContent(Parser::instance(Parser::MARKDOWN)->parseText($post->getText()))
|
||||
->setCreationDate($post->getCreatedTime())
|
||||
->setTitle($postInfo->getTitle())
|
||||
->setSummary($postInfo->getFirstParagraph())
|
||||
->setContent(Parser::instance(Parser::MARKDOWN)->parseText($postInfo->getBody()))
|
||||
->setCreationDate($postInfo->getCreatedTime())
|
||||
->setUniqueId($postUrl)
|
||||
->setContentUrl($postUrl)
|
||||
->setCommentsUrl($commentsUrl)
|
||||
->setAuthorName($post->getUser()->getUsername())
|
||||
->setAuthorName($userName)
|
||||
->setAuthorUrl($authorUrl);
|
||||
|
||||
if(!$feed->hasLastUpdate() || $feed->getLastUpdate() < $feedItem->getCreationDate())
|
||||
|
@ -116,31 +200,75 @@ final class NewsHandler extends Handler {
|
|||
return $feed;
|
||||
}
|
||||
|
||||
private function fetchPostInfoForFeed(array $postInfos): array {
|
||||
$news = $this->context->getNews();
|
||||
$posts = [];
|
||||
$userInfos = [];
|
||||
|
||||
foreach($postInfos as $postInfo) {
|
||||
$userId = $postInfo->getUserId();
|
||||
|
||||
if(array_key_exists($userId, $userInfos)) {
|
||||
$userInfo = $userInfos[$userId];
|
||||
} else {
|
||||
try {
|
||||
$userInfo = User::byId($userId);
|
||||
} catch(UserNotFoundException $ex) {
|
||||
$userInfo = null;
|
||||
}
|
||||
|
||||
$userInfos[$userId] = $userInfo;
|
||||
}
|
||||
|
||||
$posts[] = [
|
||||
'post' => $postInfo,
|
||||
'user' => $userInfo,
|
||||
];
|
||||
}
|
||||
|
||||
return $posts;
|
||||
}
|
||||
|
||||
private function getFeaturedPostsForFeed(): array {
|
||||
return $this->fetchPostInfoForFeed(
|
||||
$this->context->getNews()->getAllPosts(
|
||||
onlyFeatured: true,
|
||||
pagination: new Pagination(10)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function feedIndexAtom($response, $request) {
|
||||
$response->setContentType('application/atom+xml; charset=utf-8');
|
||||
return (new AtomFeedSerializer)->serializeFeed(
|
||||
self::createFeed('atom', null, NewsPost::all(new Pagination(10), true))
|
||||
self::createFeed('atom', null, $this->getFeaturedPostsForFeed())
|
||||
);
|
||||
}
|
||||
|
||||
public function feedIndexRss($response, $request) {
|
||||
$response->setContentType('application/rss+xml; charset=utf-8');
|
||||
return (new RssFeedSerializer)->serializeFeed(
|
||||
self::createFeed('rss', null, NewsPost::all(new Pagination(10), true))
|
||||
self::createFeed('rss', null, $this->getFeaturedPostsForFeed())
|
||||
);
|
||||
}
|
||||
|
||||
public function feedCategoryAtom($response, $request, NewsCategory $categoryInfo) {
|
||||
private function getCategoryPostsForFeed(NewsCategoryInfo $categoryInfo): array {
|
||||
return $this->fetchPostInfoForFeed(
|
||||
$this->context->getNews()->getPostsByCategory($categoryInfo, pagination: new Pagination(10))
|
||||
);
|
||||
}
|
||||
|
||||
public function feedCategoryAtom($response, $request, NewsCategoryInfo $categoryInfo) {
|
||||
$response->setContentType('application/atom+xml; charset=utf-8');
|
||||
return (new AtomFeedSerializer)->serializeFeed(
|
||||
self::createFeed('atom', $categoryInfo, $categoryInfo->posts(new Pagination(10)))
|
||||
self::createFeed('atom', $categoryInfo, $this->getCategoryPostsForFeed($categoryInfo))
|
||||
);
|
||||
}
|
||||
|
||||
public function feedCategoryRss($response, $request, NewsCategory $categoryInfo) {
|
||||
public function feedCategoryRss($response, $request, NewsCategoryInfo $categoryInfo) {
|
||||
$response->setContentType('application/rss+xml; charset=utf-8');
|
||||
return (new RssFeedSerializer)->serializeFeed(
|
||||
self::createFeed('rss', $categoryInfo, $categoryInfo->posts(new Pagination(10)))
|
||||
self::createFeed('rss', $categoryInfo, $this->getCategoryPostsForFeed($categoryInfo))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ use Misuzu\Template;
|
|||
use Misuzu\Changelog\Changelog;
|
||||
use Misuzu\Config\IConfig;
|
||||
use Misuzu\Emoticons\Emotes;
|
||||
use Misuzu\News\News;
|
||||
use Misuzu\SharpChat\SharpChatRoutes;
|
||||
use Misuzu\Users\Users;
|
||||
use Index\Data\IDbConnection;
|
||||
|
@ -25,6 +26,7 @@ class MisuzuContext {
|
|||
private HttpFx $router;
|
||||
private Emotes $emotes;
|
||||
private Changelog $changelog;
|
||||
private News $news;
|
||||
|
||||
public function __construct(IDbConnection $dbConn, IConfig $config) {
|
||||
$this->dbConn = $dbConn;
|
||||
|
@ -32,6 +34,7 @@ class MisuzuContext {
|
|||
$this->users = new Users($this->dbConn);
|
||||
$this->emotes = new Emotes($this->dbConn);
|
||||
$this->changelog = new Changelog($this->dbConn);
|
||||
$this->news = new News($this->dbConn);
|
||||
}
|
||||
|
||||
public function getDbConn(): IDbConnection {
|
||||
|
@ -71,6 +74,10 @@ class MisuzuContext {
|
|||
return $this->changelog;
|
||||
}
|
||||
|
||||
public function getNews(): News {
|
||||
return $this->news;
|
||||
}
|
||||
|
||||
public function setUpHttp(bool $legacy = false): void {
|
||||
$this->router = new HttpFx;
|
||||
$this->router->use('/', function($response) {
|
||||
|
|
481
src/News/News.php
Normal file
481
src/News/News.php
Normal file
|
@ -0,0 +1,481 @@
|
|||
<?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();
|
||||
}
|
||||
}
|
|
@ -1,148 +0,0 @@
|
|||
<?php
|
||||
namespace Misuzu\News;
|
||||
|
||||
use ArrayAccess;
|
||||
use Misuzu\DB;
|
||||
use Misuzu\Pagination;
|
||||
|
||||
class NewsCategoryException extends NewsException {};
|
||||
class NewsCategoryNotFoundException extends NewsCategoryException {};
|
||||
|
||||
class NewsCategory implements ArrayAccess {
|
||||
// Database fields
|
||||
private $category_id = -1;
|
||||
private $category_name = '';
|
||||
private $category_description = '';
|
||||
private $category_is_hidden = false;
|
||||
private $category_created = null;
|
||||
|
||||
private $postCount = -1;
|
||||
|
||||
public const TABLE = 'news_categories';
|
||||
private const QUERY_SELECT = 'SELECT %1$s FROM `' . DB::PREFIX . self::TABLE . '` AS '. self::TABLE;
|
||||
private const SELECT = '%1$s.`category_id`, %1$s.`category_name`, %1$s.`category_description`, %1$s.`category_is_hidden`'
|
||||
. ', UNIX_TIMESTAMP(%1$s.`category_created`) AS `category_created`';
|
||||
|
||||
public function __construct() {}
|
||||
|
||||
public function getId(): int {
|
||||
return $this->category_id < 1 ? -1 : $this->category_id;
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return $this->category_name ?? '';
|
||||
}
|
||||
public function setName(string $name): self {
|
||||
$this->category_name = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDescription(): string {
|
||||
return $this->category_description ?? '';
|
||||
}
|
||||
public function setDescription(string $description): self {
|
||||
$this->category_description = $description;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isHidden(): bool {
|
||||
return $this->category_is_hidden !== 0;
|
||||
}
|
||||
public function setHidden(bool $hide): self {
|
||||
$this->category_is_hidden = $hide ? 1 : 0;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCreatedTime(): int {
|
||||
return $this->category_created === null ? -1 : $this->category_created;
|
||||
}
|
||||
|
||||
// Purely cosmetic, use ::countAll for pagination
|
||||
public function getPostCount(): int {
|
||||
if($this->postCount < 0)
|
||||
$this->postCount = (int)DB::prepare('
|
||||
SELECT COUNT(`post_id`)
|
||||
FROM `msz_news_posts`
|
||||
WHERE `category_id` = :cat_id
|
||||
AND `post_scheduled` <= NOW()
|
||||
AND `post_deleted` IS NULL
|
||||
')->bind('cat_id', $this->getId())->fetchColumn();
|
||||
|
||||
return $this->postCount;
|
||||
}
|
||||
|
||||
public function save(): void {
|
||||
$isInsert = $this->getId() < 1;
|
||||
if($isInsert) {
|
||||
$query = 'INSERT INTO `%1$s%2$s` (`category_name`, `category_description`, `category_is_hidden`) VALUES'
|
||||
. ' (:name, :description, :hidden)';
|
||||
} else {
|
||||
$query = 'UPDATE `%1$s%2$s` SET `category_name` = :name, `category_description` = :description, `category_is_hidden` = :hidden'
|
||||
. ' WHERE `category_id` = :category';
|
||||
}
|
||||
|
||||
$savePost = DB::prepare(sprintf($query, DB::PREFIX, self::TABLE))
|
||||
->bind('name', $this->category_name)
|
||||
->bind('description', $this->category_description)
|
||||
->bind('hidden', $this->category_is_hidden);
|
||||
|
||||
if($isInsert) {
|
||||
$this->category_id = $savePost->executeGetId();
|
||||
$this->category_created = time();
|
||||
} else {
|
||||
$savePost->bind('category', $this->getId())
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
|
||||
public function posts(?Pagination $pagination = null, bool $includeScheduled = false, bool $includeDeleted = false): array {
|
||||
return NewsPost::byCategory($this, $pagination, $includeScheduled, $includeDeleted);
|
||||
}
|
||||
|
||||
private static function countQueryBase(): string {
|
||||
return sprintf(self::QUERY_SELECT, sprintf('COUNT(%s.`category_id`)', self::TABLE));
|
||||
}
|
||||
public static function countAll(bool $showHidden = false): int {
|
||||
return (int)DB::prepare(self::countQueryBase()
|
||||
. ($showHidden ? '' : ' WHERE `category_is_hidden` = 0'))
|
||||
->fetchColumn();
|
||||
}
|
||||
|
||||
private static function byQueryBase(): string {
|
||||
return sprintf(self::QUERY_SELECT, sprintf(self::SELECT, self::TABLE));
|
||||
}
|
||||
public static function byId(int $categoryId): self {
|
||||
$getCat = DB::prepare(self::byQueryBase() . ' WHERE `category_id` = :cat_id');
|
||||
$getCat->bind('cat_id', $categoryId);
|
||||
$cat = $getCat->fetchObject(self::class);
|
||||
if(!$cat)
|
||||
throw new NewsCategoryNotFoundException;
|
||||
return $cat;
|
||||
}
|
||||
public static function all(?Pagination $pagination = null, bool $showHidden = false): array {
|
||||
$catsQuery = self::byQueryBase()
|
||||
. ($showHidden ? '' : ' WHERE `category_is_hidden` = 0')
|
||||
. ' ORDER BY `category_id` ASC';
|
||||
|
||||
if($pagination !== null)
|
||||
$catsQuery .= ' LIMIT :range OFFSET :offset';
|
||||
|
||||
$getCats = DB::prepare($catsQuery);
|
||||
|
||||
if($pagination !== null)
|
||||
$getCats->bind('range', $pagination->getRange())
|
||||
->bind('offset', $pagination->getOffset());
|
||||
|
||||
return $getCats->fetchObjects(self::class);
|
||||
}
|
||||
|
||||
// Twig shim for the news category list in manage, don't use this class as an array normally.
|
||||
public function offsetExists($offset): bool {
|
||||
return $offset === 'name' || $offset === 'id';
|
||||
}
|
||||
public function offsetGet($offset): mixed {
|
||||
return $this->{'get' . ucfirst($offset)}();
|
||||
}
|
||||
public function offsetSet($offset, $value): void {}
|
||||
public function offsetUnset($offset): void {}
|
||||
}
|
51
src/News/NewsCategoryInfo.php
Normal file
51
src/News/NewsCategoryInfo.php
Normal file
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
namespace Misuzu\News;
|
||||
|
||||
use Index\DateTime;
|
||||
use Index\Data\IDbResult;
|
||||
|
||||
class NewsCategoryInfo {
|
||||
private string $id;
|
||||
private string $name;
|
||||
private string $description;
|
||||
private bool $hidden;
|
||||
private int $created;
|
||||
private int $posts;
|
||||
|
||||
public function __construct(IDbResult $result) {
|
||||
$this->id = (string)$result->getInteger(0);
|
||||
$this->name = $result->getString(1);
|
||||
$this->description = $result->getString(2);
|
||||
$this->hidden = $result->getInteger(3) !== 0;
|
||||
$this->created = $result->getInteger(4);
|
||||
$this->posts = $result->getInteger(5);
|
||||
}
|
||||
|
||||
public function getId(): string {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getName(): string {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getDescription(): string {
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
public function isHidden(): bool {
|
||||
return $this->hidden;
|
||||
}
|
||||
|
||||
public function getCreatedTime(): int {
|
||||
return $this->created;
|
||||
}
|
||||
|
||||
public function getCreatedAt(): DateTime {
|
||||
return DateTime::fromUnixTimeSeconds($this->created);
|
||||
}
|
||||
|
||||
public function getPostsCount(): int {
|
||||
return $this->posts;
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
<?php
|
||||
namespace Misuzu\News;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class NewsException extends RuntimeException {}
|
|
@ -1,299 +0,0 @@
|
|||
<?php
|
||||
namespace Misuzu\News;
|
||||
|
||||
use Misuzu\DB;
|
||||
use Misuzu\Pagination;
|
||||
use Misuzu\Comments\CommentsCategory;
|
||||
use Misuzu\Comments\CommentsCategoryNotFoundException;
|
||||
use Misuzu\Parsers\Parser;
|
||||
use Misuzu\Users\User;
|
||||
use Misuzu\Users\UserNotFoundException;
|
||||
|
||||
class NewsPostException extends NewsException {};
|
||||
class NewsPostNotFoundException extends NewsPostException {};
|
||||
|
||||
class NewsPost {
|
||||
// Database fields
|
||||
private $post_id = -1;
|
||||
private $category_id = -1;
|
||||
private $user_id = null;
|
||||
private $comment_section_id = null;
|
||||
private $post_is_featured = false;
|
||||
private $post_title = '';
|
||||
private $post_text = '';
|
||||
private $post_scheduled = null;
|
||||
private $post_created = null;
|
||||
private $post_updated = null;
|
||||
private $post_deleted = null;
|
||||
|
||||
private $category = null;
|
||||
private $user = null;
|
||||
private $userLookedUp = false;
|
||||
private $comments = null;
|
||||
|
||||
public const TABLE = 'news_posts';
|
||||
private const QUERY_SELECT = 'SELECT %1$s FROM `' . DB::PREFIX . self::TABLE . '` AS '. self::TABLE;
|
||||
private const SELECT = '%1$s.`post_id`, %1$s.`category_id`, %1$s.`user_id`, %1$s.`comment_section_id`'
|
||||
. ', %1$s.`post_is_featured`, %1$s.`post_title`, %1$s.`post_text`'
|
||||
. ', UNIX_TIMESTAMP(%1$s.`post_scheduled`) AS `post_scheduled`'
|
||||
. ', UNIX_TIMESTAMP(%1$s.`post_created`) AS `post_created`'
|
||||
. ', UNIX_TIMESTAMP(%1$s.`post_updated`) AS `post_updated`'
|
||||
. ', UNIX_TIMESTAMP(%1$s.`post_deleted`) AS `post_deleted`';
|
||||
|
||||
public function getId(): int {
|
||||
return $this->post_id < 1 ? -1 : $this->post_id;
|
||||
}
|
||||
|
||||
public function getCategoryId(): int {
|
||||
return $this->category_id < 1 ? -1 : $this->category_id;
|
||||
}
|
||||
public function setCategoryId(int $categoryId): self {
|
||||
$this->category_id = max(1, $categoryId);
|
||||
$this->category = null;
|
||||
return $this;
|
||||
}
|
||||
public function getCategory(): NewsCategory {
|
||||
if($this->category === null)
|
||||
$this->category = NewsCategory::byId($this->getCategoryId());
|
||||
return $this->category;
|
||||
}
|
||||
public function setCategory(NewsCategory $category): self {
|
||||
$this->category_id = $category->getId();
|
||||
$this->category = $category;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUserId(): int {
|
||||
return $this->user_id < 1 ? -1 : $this->user_id;
|
||||
}
|
||||
public function setUserId(int $userId): self {
|
||||
$this->user_id = $userId < 1 ? null : $userId;
|
||||
$this->userLookedUp = false;
|
||||
$this->user = null;
|
||||
return $this;
|
||||
}
|
||||
public function getUser(): ?User {
|
||||
if(!$this->userLookedUp && ($userId = $this->getUserId()) > 0) {
|
||||
$this->userLookedUp = true;
|
||||
try {
|
||||
$this->user = User::byId($userId);
|
||||
} catch(UserNotFoundException $ex) {}
|
||||
}
|
||||
return $this->user;
|
||||
}
|
||||
public function setUser(?User $user): self {
|
||||
$this->user_id = $user === null ? null : $user->getId();
|
||||
$this->userLookedUp = true;
|
||||
$this->user = $user;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCommentsCategoryId(): int {
|
||||
return $this->comment_section_id < 1 ? -1 : $this->comment_section_id;
|
||||
}
|
||||
public function hasCommentsCategory(): bool {
|
||||
return $this->getCommentsCategoryId() > 0;
|
||||
}
|
||||
public function getCommentsCategory(): CommentsCategory {
|
||||
if($this->comments === null)
|
||||
$this->comments = CommentsCategory::byId($this->getCommentsCategoryId());
|
||||
return $this->comments;
|
||||
}
|
||||
|
||||
public function isFeatured(): bool {
|
||||
return $this->post_is_featured !== 0;
|
||||
}
|
||||
public function setFeatured(bool $featured): self {
|
||||
$this->post_is_featured = $featured ? 1 : 0;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTitle(): string {
|
||||
return $this->post_title;
|
||||
}
|
||||
public function setTitle(string $title): self {
|
||||
$this->post_title = $title;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getText(): string {
|
||||
return $this->post_text;
|
||||
}
|
||||
public function setText(string $text): self {
|
||||
$this->post_text = $text;
|
||||
return $this;
|
||||
}
|
||||
public function getParsedText(): string {
|
||||
return Parser::instance(Parser::MARKDOWN)->parseText($this->getText());
|
||||
}
|
||||
public function getFirstParagraph(): string {
|
||||
$text = $this->getText();
|
||||
$index = mb_strpos($text, "\n");
|
||||
return $index === false ? $text : mb_substr($text, 0, $index);
|
||||
}
|
||||
public function getParsedFirstParagraph(): string {
|
||||
return Parser::instance(Parser::MARKDOWN)->parseText($this->getFirstParagraph());
|
||||
}
|
||||
|
||||
public function getScheduledTime(): int {
|
||||
return $this->post_scheduled === null ? -1 : $this->post_scheduled;
|
||||
}
|
||||
public function setScheduledTime(int $scheduled): self {
|
||||
$time = ($time = $this->getCreatedTime()) < 0 ? time() : $time;
|
||||
$this->post_scheduled = $scheduled < $time ? $time : $scheduled;
|
||||
return $this;
|
||||
}
|
||||
public function isPublished(): bool {
|
||||
return $this->getScheduledTime() < time();
|
||||
}
|
||||
|
||||
public function getCreatedTime(): int {
|
||||
return $this->post_created === null ? -1 : $this->post_created;
|
||||
}
|
||||
|
||||
public function getUpdatedTime(): int {
|
||||
return $this->post_updated === null ? -1 : $this->post_updated;
|
||||
}
|
||||
public function isEdited(): bool {
|
||||
return $this->getUpdatedTime() >= 0;
|
||||
}
|
||||
|
||||
public function getDeletedTime(): int {
|
||||
return $this->post_deleted === null ? -1 : $this->post_deleted;
|
||||
}
|
||||
public function isDeleted(): bool {
|
||||
return $this->getDeletedTime() >= 0;
|
||||
}
|
||||
public function setDeleted(bool $isDeleted): self {
|
||||
if($this->isDeleted() !== $isDeleted)
|
||||
$this->post_deleted = $isDeleted ? time() : null;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function ensureCommentsCategory(): void {
|
||||
if($this->hasCommentsCategory())
|
||||
return;
|
||||
|
||||
$this->comments = new CommentsCategory("news-{$this->getId()}");
|
||||
$this->comments->save();
|
||||
|
||||
$this->comment_section_id = $this->comments->getId();
|
||||
DB::prepare('UPDATE `msz_news_posts` SET `comment_section_id` = :comment_section_id WHERE `post_id` = :post_id')
|
||||
->execute([
|
||||
'comment_section_id' => $this->getCommentsCategoryId(),
|
||||
'post_id' => $this->getId(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function save(): void {
|
||||
$isInsert = $this->getId() < 1;
|
||||
if($isInsert) {
|
||||
$query = 'INSERT INTO `%1$s%2$s` (`category_id`, `user_id`, `post_is_featured`, `post_title`'
|
||||
. ', `post_text`, `post_scheduled`, `post_deleted`) VALUES'
|
||||
. ' (:category, :user, :featured, :title, :text, FROM_UNIXTIME(:scheduled), FROM_UNIXTIME(:deleted))';
|
||||
} else {
|
||||
$query = 'UPDATE `%1$s%2$s` SET `category_id` = :category, `user_id` = :user, `post_is_featured` = :featured'
|
||||
. ', `post_title` = :title, `post_text` = :text, `post_scheduled` = FROM_UNIXTIME(:scheduled)'
|
||||
. ', `post_deleted` = FROM_UNIXTIME(:deleted)'
|
||||
. ' WHERE `post_id` = :post';
|
||||
}
|
||||
|
||||
$savePost = DB::prepare(sprintf($query, DB::PREFIX, self::TABLE))
|
||||
->bind('category', $this->category_id)
|
||||
->bind('user', $this->user_id)
|
||||
->bind('featured', $this->post_is_featured)
|
||||
->bind('title', $this->post_title)
|
||||
->bind('text', $this->post_text)
|
||||
->bind('scheduled', $this->post_scheduled)
|
||||
->bind('deleted', $this->post_deleted);
|
||||
|
||||
if($isInsert) {
|
||||
$this->post_id = $savePost->executeGetId();
|
||||
$this->post_created = time();
|
||||
} else {
|
||||
$this->post_updated = time();
|
||||
$savePost->bind('post', $this->getId())
|
||||
->execute();
|
||||
}
|
||||
}
|
||||
|
||||
private static function countQueryBase(): string {
|
||||
return sprintf(self::QUERY_SELECT, sprintf('COUNT(%s.`post_id`)', self::TABLE));
|
||||
}
|
||||
public static function countAll(bool $onlyFeatured = false, bool $includeScheduled = false, bool $includeDeleted = false): int {
|
||||
return (int)DB::prepare(self::countQueryBase()
|
||||
. ' WHERE IF(:only_featured, `post_is_featured` <> 0, 1)'
|
||||
. ($includeScheduled ? '' : ' AND `post_scheduled` < NOW()')
|
||||
. ($includeDeleted ? '' : ' AND `post_deleted` IS NULL'))
|
||||
->bind('only_featured', $onlyFeatured ? 1 : 0)
|
||||
->fetchColumn();
|
||||
}
|
||||
public static function countByCategory(NewsCategory $category, bool $includeScheduled = false, bool $includeDeleted = false): int {
|
||||
return (int)DB::prepare(self::countQueryBase()
|
||||
. ' WHERE `category_id` = :cat_id'
|
||||
. ($includeScheduled ? '' : ' AND `post_scheduled` < NOW()')
|
||||
. ($includeDeleted ? '' : ' AND `post_deleted` IS NULL'))
|
||||
->bind('cat_id', $category->getId())
|
||||
->fetchColumn();
|
||||
}
|
||||
|
||||
private static function byQueryBase(): string {
|
||||
return sprintf(self::QUERY_SELECT, sprintf(self::SELECT, self::TABLE));
|
||||
}
|
||||
public static function byId(int $postId): self {
|
||||
$post = DB::prepare(self::byQueryBase() . ' WHERE `post_id` = :post_id')
|
||||
->bind('post_id', $postId)
|
||||
->fetchObject(self::class);
|
||||
if(!$post)
|
||||
throw new NewsPostNotFoundException;
|
||||
return $post;
|
||||
}
|
||||
public static function bySearchQuery(string $query, bool $includeScheduled = false, bool $includeDeleted = false): array {
|
||||
return DB::prepare(
|
||||
self::byQueryBase()
|
||||
. ' WHERE MATCH(`post_title`, `post_text`) AGAINST (:query IN NATURAL LANGUAGE MODE)'
|
||||
. ($includeScheduled ? '' : ' AND `post_scheduled` < NOW()')
|
||||
. ($includeDeleted ? '' : ' AND `post_deleted` IS NULL')
|
||||
. ' ORDER BY `post_id` DESC'
|
||||
) ->bind('query', $query)
|
||||
->fetchObjects(self::class);
|
||||
}
|
||||
public static function byCategory(NewsCategory $category, ?Pagination $pagination = null, bool $includeScheduled = false, bool $includeDeleted = false): array {
|
||||
$postsQuery = self::byQueryBase()
|
||||
. ' WHERE `category_id` = :cat_id'
|
||||
. ($includeScheduled ? '' : ' AND `post_scheduled` < NOW()')
|
||||
. ($includeDeleted ? '' : ' AND `post_deleted` IS NULL')
|
||||
. ' ORDER BY `post_id` DESC';
|
||||
|
||||
if($pagination !== null)
|
||||
$postsQuery .= ' LIMIT :range OFFSET :offset';
|
||||
|
||||
$getPosts = DB::prepare($postsQuery)
|
||||
->bind('cat_id', $category->getId());
|
||||
|
||||
if($pagination !== null)
|
||||
$getPosts->bind('range', $pagination->getRange())
|
||||
->bind('offset', $pagination->getOffset());
|
||||
|
||||
return $getPosts->fetchObjects(self::class);
|
||||
}
|
||||
public static function all(?Pagination $pagination = null, bool $onlyFeatured = false, bool $includeScheduled = false, bool $includeDeleted = false): array {
|
||||
$postsQuery = self::byQueryBase()
|
||||
. ' WHERE IF(:only_featured, `post_is_featured` <> 0, 1)'
|
||||
. ($includeScheduled ? '' : ' AND `post_scheduled` < NOW()')
|
||||
. ($includeDeleted ? '' : ' AND `post_deleted` IS NULL')
|
||||
. ' ORDER BY `post_id` DESC';
|
||||
|
||||
if($pagination !== null)
|
||||
$postsQuery .= ' LIMIT :range OFFSET :offset';
|
||||
|
||||
$getPosts = DB::prepare($postsQuery)
|
||||
->bind('only_featured', $onlyFeatured ? 1 : 0);
|
||||
|
||||
if($pagination !== null)
|
||||
$getPosts->bind('range', $pagination->getRange())
|
||||
->bind('offset', $pagination->getOffset());
|
||||
|
||||
return $getPosts->fetchObjects(self::class);
|
||||
}
|
||||
}
|
122
src/News/NewsPostInfo.php
Normal file
122
src/News/NewsPostInfo.php
Normal file
|
@ -0,0 +1,122 @@
|
|||
<?php
|
||||
namespace Misuzu\News;
|
||||
|
||||
use Index\DateTime;
|
||||
use Index\Data\IDbResult;
|
||||
|
||||
class NewsPostInfo {
|
||||
private string $id;
|
||||
private string $categoryId;
|
||||
private ?string $userId;
|
||||
private ?string $commentsSectionId;
|
||||
private bool $featured;
|
||||
private string $title;
|
||||
private string $body;
|
||||
private int $scheduled;
|
||||
private int $created;
|
||||
private int $updated;
|
||||
private ?int $deleted;
|
||||
|
||||
public function __construct(IDbResult $result) {
|
||||
$this->id = (string)$result->getInteger(0);
|
||||
$this->categoryId = (string)$result->getInteger(1);
|
||||
$this->userId = $result->isNull(2) ? null : (string)$result->getInteger(2);
|
||||
$this->commentsSectionId = $result->isNull(3) ? null : (string)$result->getInteger(3);
|
||||
$this->featured = $result->getInteger(4) !== 0;
|
||||
$this->title = $result->getString(5);
|
||||
$this->body = $result->getString(6);
|
||||
$this->scheduled = $result->getInteger(7);
|
||||
$this->created = $result->getInteger(8);
|
||||
$this->updated = $result->getInteger(9);
|
||||
$this->deleted = $result->isNull(10) ? null : $result->getInteger(10);
|
||||
}
|
||||
|
||||
public function getId(): string {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getCategoryId(): string {
|
||||
return $this->categoryId;
|
||||
}
|
||||
|
||||
public function hasUserId(): bool {
|
||||
return $this->userId !== null;
|
||||
}
|
||||
|
||||
public function getUserId(): ?string {
|
||||
return $this->userId;
|
||||
}
|
||||
|
||||
public function hasCommentsCategoryId(): bool {
|
||||
return $this->commentsSectionId !== null;
|
||||
}
|
||||
|
||||
public function getCommentsCategoryId(): string {
|
||||
return $this->commentsSectionId;
|
||||
}
|
||||
|
||||
public function getCommentsCategoryName(): string {
|
||||
return sprintf('news-%s', $this->id);
|
||||
}
|
||||
|
||||
public function isFeatured(): bool {
|
||||
return $this->featured;
|
||||
}
|
||||
|
||||
public function getTitle(): string {
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function getBody(): string {
|
||||
return $this->body;
|
||||
}
|
||||
|
||||
public function getFirstParagraph(): string {
|
||||
$index = mb_strpos($this->body, "\n");
|
||||
return $index === false ? $this->body : mb_substr($this->body, 0, $index);
|
||||
}
|
||||
|
||||
public function getScheduledTime(): int {
|
||||
return $this->scheduled;
|
||||
}
|
||||
|
||||
public function getScheduledAt(): DateTime {
|
||||
return DateTime::fromUnixTimeSeconds($this->scheduled);
|
||||
}
|
||||
|
||||
public function isPublished(): bool {
|
||||
return $this->scheduled <= time();
|
||||
}
|
||||
|
||||
public function getCreatedTime(): int {
|
||||
return $this->created;
|
||||
}
|
||||
|
||||
public function getCreatedAt(): DateTime {
|
||||
return DateTime::fromUnixTimeSeconds($this->created);
|
||||
}
|
||||
|
||||
public function getUpdatedTime(): int {
|
||||
return $this->updated;
|
||||
}
|
||||
|
||||
public function getUpdatedAt(): DateTime {
|
||||
return DateTime::fromUnixTimeSeconds($this->updated);
|
||||
}
|
||||
|
||||
public function isEdited(): bool {
|
||||
return $this->updated > $this->created;
|
||||
}
|
||||
|
||||
public function isDeleted(): bool {
|
||||
return $this->deleted !== null;
|
||||
}
|
||||
|
||||
public function getDeletedTime(): ?int {
|
||||
return $this->deleted;
|
||||
}
|
||||
|
||||
public function getDeletedAt(): ?DateTime {
|
||||
return $this->deleted === null ? null : DateTime::fromUnixTimeSeconds($this->deleted);
|
||||
}
|
||||
}
|
|
@ -117,8 +117,10 @@ define('MSZ_URLS', [
|
|||
|
||||
'manage-news-categories' => ['/manage/news/categories.php'],
|
||||
'manage-news-category' => ['/manage/news/category.php', ['c' => '<category>']],
|
||||
'manage-news-category-delete' => ['/manage/news/category.php', ['c' => '<category>', 'delete' => '1', 'csrf' => '{token}']],
|
||||
'manage-news-posts' => ['/manage/news/posts.php'],
|
||||
'manage-news-post' => ['/manage/news/post.php', ['p' => '<post>']],
|
||||
'manage-news-post-delete' => ['/manage/news/post.php', ['p' => '<post>', 'delete' => '1', 'csrf' => '{token}']],
|
||||
|
||||
'manage-users' => ['/manage/users'],
|
||||
'manage-user' => ['/manage/users/user.php', ['u' => '<user>']],
|
||||
|
|
|
@ -171,11 +171,9 @@
|
|||
{% for post in featured_news %}
|
||||
<div class="landingv2-news-post markdown">
|
||||
<h1>{{ post.title }}</h1>
|
||||
<p>{{ post.parsedFirstParagraph|raw }}</p>
|
||||
<p>{{ post.firstParagraph|parse_text(2)|raw }}</p>
|
||||
<div class="landingv2-news-post-options">
|
||||
<a href="{{ url('news-post', {'post': post.id}) }}" class="landingv2-news-post-option">Continue reading</a>
|
||||
| <a href="{{ url('news-post-comments', {'post': post.id}) }}" class="landingv2-news-post-option">
|
||||
{{ not post.hasCommentsCategory or post.commentsCategory.postCount < 1 ? 'No' : post.commentsCategory.postCount|number_format }} comment{{ not post.hasCommentsCategory or post.commentsCategory.postCount != 1 ? 's' : '' }}</a>
|
||||
| {{ post.createdTime|time_diff }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -9,10 +9,10 @@
|
|||
|
||||
{% for cat in news_categories %}
|
||||
<p>
|
||||
<a href="{{ url('manage-news-category', {'category': cat.id}) }}" class="input__button">{{ cat.id }}</a>
|
||||
{{ cat.name }},
|
||||
{{ cat.isHidden }},
|
||||
{{ cat.createdTime|date('r') }}
|
||||
<a href="{{ url('manage-news-category', {'category': cat.id}) }}" class="input__button">#{{ cat.id }}</a>
|
||||
{{ cat.name }} |
|
||||
{{ cat.isHidden ? 'Unlisted' : 'Public' }} |
|
||||
{{ cat.createdAt }}
|
||||
</p>
|
||||
{% endfor %}
|
||||
|
||||
|
|
|
@ -2,32 +2,33 @@
|
|||
{% from 'macros.twig' import container_title %}
|
||||
{% from '_layout/input.twig' import input_hidden, input_csrf, input_text, input_checkbox %}
|
||||
|
||||
{% set is_new = category is not defined %}
|
||||
|
||||
{% block manage_content %}
|
||||
<form method="post" action="{{ url('manage-news-category', {'category': category_info.id|default(0)}) }}" class="container">
|
||||
{{ container_title(is_new ? 'New Category' : 'Editing ' ~ category_info.name) }}
|
||||
|
||||
{{ container_title(category_new ? 'New Category' : 'Editing ' ~ category_info.name) }}
|
||||
{{ input_csrf() }}
|
||||
{{ input_hidden('category[id]', category_info.id|default(0)) }}
|
||||
|
||||
<table style="color:inherit">
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td>{{ input_text('category[name]', '', category_info.name|default(), 'text', '', true) }}</td>
|
||||
<td>{{ input_text('nc_name', '', category_info.name|default(), 'text', '', true) }}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Description</td>
|
||||
<td><textarea name="category[description]" required class="input__textarea">{{ category_info.description|default() }}</textarea></td>
|
||||
<td><textarea name="nc_desc" required class="input__textarea">{{ category_info.description|default() }}</textarea></td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Is Hidden</td>
|
||||
<td>{{ input_checkbox('category[hidden]', '', category_info.isHidden|default(false)) }}</td>
|
||||
<td>{{ input_checkbox('nc_hidden', '', category_info.isHidden|default(false)) }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div>
|
||||
<button class="input__button">Save</button>
|
||||
{% if not category_new %}
|
||||
<a href="{{ url('manage-news-category-delete', {'category': category_info.id}) }}" class="input__button input__button--destroy" onclick="return confirm('Are you sure?');">Delete</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
|
|
@ -2,36 +2,37 @@
|
|||
{% from 'macros.twig' import container_title %}
|
||||
{% from '_layout/input.twig' import input_hidden, input_csrf, input_text, input_checkbox, input_select %}
|
||||
|
||||
{% set is_new = post_info is not defined %}
|
||||
|
||||
{% block manage_content %}
|
||||
<form method="post" action="{{ url('manage-news-post', {'post': post_info.id|default(0)}) }}" class="container">
|
||||
{{ container_title(is_new ? 'New Post' : 'Editing ' ~ post_info.title) }}
|
||||
|
||||
{{ container_title(post_new ? 'New Post' : 'Editing ' ~ post_info.title) }}
|
||||
{{ input_csrf() }}
|
||||
{{ input_hidden('post[id]', post_info.id|default(0)) }}
|
||||
|
||||
<table style="color:inherit">
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td>{{ input_text('post[title]', '', post_info.title|default(), 'text', '', true) }}</td>
|
||||
<td>{{ input_text('np_title', '', post_info.title|default(), 'text', '', true) }}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Category</td>
|
||||
<td>{{ input_select('post[category]', categories, post_info.categoryId|default(0), 'name', 'id') }}</td>
|
||||
<td>{{ input_select('np_category', categories, post_info.categoryId|default(0)) }}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>Is Featured</td>
|
||||
<td>{{ input_checkbox('post[featured]', '', post_info.isFeatured|default(false)) }}</td>
|
||||
<td>{{ input_checkbox('np_featured', '', post_info.isFeatured|default(false)) }}</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td colspan="2"><textarea name="post[text]" required class="input__textarea">{{ post_info.text|default() }}</textarea></td>
|
||||
<td colspan="2"><textarea name="np_body" required class="input__textarea">{{ post_info.body|default() }}</textarea></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div>
|
||||
<button class="input__button">Save</button>
|
||||
{% if not post_new %}
|
||||
<a href="{{ url('manage-news-post-delete', {'post': post_info.id}) }}" class="input__button input__button--destroy" onclick="return confirm('Are you sure?');">Delete</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
|
|
@ -9,16 +9,16 @@
|
|||
|
||||
{% for post in news_posts %}
|
||||
<p>
|
||||
<a href="{{ url('manage-news-post', {'post': post.id}) }}" class="input__button">{{ post.id }}</a>
|
||||
<a href="{{ url('manage-news-category', {'category': post.categoryId}) }}" class="input__button">Cat: {{ post.categoryId }}</a>
|
||||
{{ post.isFeatured }},
|
||||
{{ post.user.id }},
|
||||
{{ post.title }},
|
||||
{{ post.scheduledTime|date('r') }},
|
||||
{{ post.createdTime|date('r') }},
|
||||
{{ post.updatedTime|date('r') }},
|
||||
{{ post.deletedTime|date('r') }},
|
||||
{{ post.commentsCategoryId }}
|
||||
<a href="{{ url('manage-news-post', {'post': post.id}) }}" class="input__button">#{{ post.id }}</a>
|
||||
<a href="{{ url('manage-news-category', {'category': post.categoryId}) }}" class="input__button">Category #{{ post.categoryId }}</a>
|
||||
{{ post.title }} |
|
||||
{{ post.isFeatured ? 'Featured' : 'Normal' }} |
|
||||
User #{{ post.userId }} |
|
||||
{% if post.hasCommentsCategoryId %}Comments category #{{ post.commentsCategoryId }}{% else %}No comments category{% endif %} |
|
||||
Created {{ post.createdAt }} |
|
||||
{{ post.isPublished ? 'published' : 'Published ' ~ post.scheduledAt }} |
|
||||
{{ post.isEdited ? 'Edited ' ~ post.updatedAt : 'not edited' }} |
|
||||
{{ post.isDeleted ? 'Deleted ' ~ post.deletedAt : 'not deleted' }}
|
||||
</p>
|
||||
{% endfor %}
|
||||
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
{% from 'macros.twig' import pagination, container_title %}
|
||||
{% from 'news/macros.twig' import news_preview %}
|
||||
|
||||
{% set title = category_info.name ~ ' :: News' %}
|
||||
{% set manage_link = url('manage-news-category', {'category': category_info.id}) %}
|
||||
{% set title = news_category.name ~ ' :: News' %}
|
||||
{% set manage_link = url('manage-news-category', {'category': news_category.id}) %}
|
||||
{% set canonical_url = url('news-category', {
|
||||
'category': category_info.id,
|
||||
'category': news_category.id,
|
||||
'page': news_pagination.page > 2 ? news_pagination.page : 0,
|
||||
}) %}
|
||||
|
||||
|
@ -13,33 +13,33 @@
|
|||
{
|
||||
'type': 'rss',
|
||||
'title': '',
|
||||
'url': url('news-category-feed-rss', {'category': category_info.id}),
|
||||
'url': url('news-category-feed-rss', {'category': news_category.id}),
|
||||
},
|
||||
{
|
||||
'type': 'atom',
|
||||
'title': '',
|
||||
'url': url('news-category-feed-atom', {'category': category_info.id}),
|
||||
'url': url('news-category-feed-atom', {'category': news_category.id}),
|
||||
},
|
||||
] %}
|
||||
|
||||
{% block content %}
|
||||
<div class="news__container">
|
||||
<div class="news__preview__listing">
|
||||
{% for post in posts %}
|
||||
{% for post in news_posts %}
|
||||
{{ news_preview(post) }}
|
||||
{% endfor %}
|
||||
|
||||
<div class="container" style="padding: 4px; display: {{ news_pagination.pages > 1 ? 'block' : 'none' }}">
|
||||
{{ pagination(news_pagination, url('news-category', {'category':category_info.id})) }}
|
||||
{{ pagination(news_pagination, url('news-category', {'category': news_category.id})) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="news__sidebar">
|
||||
<div class="container news__list">
|
||||
{{ container_title('News » ' ~ category_info.name) }}
|
||||
{{ container_title('News » ' ~ news_category.name) }}
|
||||
|
||||
<div class="container__content">
|
||||
{{ category_info.description }}
|
||||
{{ news_category.description }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -47,7 +47,7 @@
|
|||
{{ container_title('Feeds') }}
|
||||
|
||||
<div class="news__feeds">
|
||||
<a href="{{ url('news-category-feed-atom', {'category': category_info.id}) }}" class="news__feed">
|
||||
<a href="{{ url('news-category-feed-atom', {'category': news_category.id}) }}" class="news__feed">
|
||||
<div class="news__feed__icon">
|
||||
<i class="fas fa-rss"></i>
|
||||
</div>
|
||||
|
@ -55,7 +55,7 @@
|
|||
Atom
|
||||
</div>
|
||||
</a>
|
||||
<a href="{{ url('news-category-feed-rss', {'category': category_info.id}) }}" class="news__feed">
|
||||
<a href="{{ url('news-category-feed-rss', {'category': news_category.id}) }}" class="news__feed">
|
||||
<div class="news__feed__icon">
|
||||
<i class="fas fa-rss"></i>
|
||||
</div>
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
{% block content %}
|
||||
<div class="news__container">
|
||||
<div class="news__preview__listing">
|
||||
{% for post in posts %}
|
||||
{% for post in news_posts %}
|
||||
{{ news_preview(post) }}
|
||||
{% endfor %}
|
||||
|
||||
|
@ -38,13 +38,13 @@
|
|||
{{ container_title('Categories') }}
|
||||
|
||||
<div class="container__content">
|
||||
{% for category in categories %}
|
||||
{% for category in news_categories %}
|
||||
<a class="news__list__item news__list__item--kvp" href="{{ url('news-category', {'category': category.id}) }}">
|
||||
<div class="news__list__name">
|
||||
{{ category.name }}
|
||||
</div>
|
||||
<div class="news__list__value">
|
||||
{{ category.postCount }} post{{ category.postCount == 1 ? '' : 's' }}
|
||||
{{ category.postsCount }} post{{ category.postsCount == 1 ? '' : 's' }}
|
||||
</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
|
|
|
@ -23,49 +23,49 @@
|
|||
|
||||
<div class="news__preview__date">
|
||||
Posted
|
||||
<time datetime="{{ post.createdTime|date('c') }}" title="{{ post.createdTime|date('r') }}">
|
||||
{{ post.createdTime|time_diff }}
|
||||
<time datetime="{{ post.post.createdTime|date('c') }}" title="{{ post.post.createdTime|date('r') }}">
|
||||
{{ post.post.createdTime|time_diff }}
|
||||
</time>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="news__preview__content markdown">
|
||||
<h1>{{ post.title }}</h1>
|
||||
<h1>{{ post.post.title }}</h1>
|
||||
<div class="news__preview__text">
|
||||
{{ post.parsedFirstParagraph|raw }}
|
||||
{{ post.post.firstParagraph|parse_text(2)|raw }}
|
||||
</div>
|
||||
<div class="news__preview__links">
|
||||
<a href="{{ url('news-post', {'post': post.id}) }}" class="news__preview__link">Continue reading</a>
|
||||
<a href="{{ url('news-post-comments', {'post': post.id}) }}" class="news__preview__link">
|
||||
{{ not post.hasCommentsCategory or post.commentsCategory.postCount < 1 ? 'No' : post.commentsCategory.postCount|number_format }} comment{{ not post.hasCommentsCategory or post.commentsCategory.postCount != 1 ? 's' : '' }}
|
||||
<a href="{{ url('news-post', {'post': post.post.id}) }}" class="news__preview__link">Continue reading</a>
|
||||
<a href="{{ url('news-post-comments', {'post': post.post.id}) }}" class="news__preview__link">
|
||||
{{ post.comments_count < 1 ? 'No' : post.comments_count|number_format }} comment{{ post.comments_count != 1 ? 's' : '' }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro news_post(post) %}
|
||||
{% macro news_post(post, category, user) %}
|
||||
{% from 'macros.twig' import avatar %}
|
||||
|
||||
<div class="container news__post" style="{% if post.user is not null %}--accent-colour: {{ post.user.colour }}{% endif %}">
|
||||
<div class="container news__post" style="{% if user is not null %}--accent-colour: {{ user.colour }}{% endif %}">
|
||||
<div class="news__post__info">
|
||||
<div class="news__post__info__background"></div>
|
||||
<div class="news__post__info__content">
|
||||
{% if post.user is not null %}
|
||||
{% if user is not null %}
|
||||
<div class="news__post__user">
|
||||
<a class="news__post__avatar" href="{{ url('user-profile', {'user': post.user.id}) }}">
|
||||
{{ avatar(post.user.id, 60, post.user.username) }}
|
||||
<a class="news__post__avatar" href="{{ url('user-profile', {'user': user.id}) }}">
|
||||
{{ avatar(user.id, 60, user.username) }}
|
||||
</a>
|
||||
|
||||
<div class="news__post__user__details">
|
||||
<a class="news__post__username" href="{{ url('user-profile', {'user': post.user.id}) }}">{{ post.user.username }}</a>
|
||||
<a class="news__post__username" href="{{ url('user-profile', {'user': user.id}) }}">{{ user.username }}</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<a class="news__post__category" href="{{ url('news-category', {'category': post.category.id}) }}">
|
||||
{{ post.category.name }}
|
||||
<a class="news__post__category" href="{{ url('news-category', {'category': category.id}) }}">
|
||||
{{ category.name }}
|
||||
</a>
|
||||
|
||||
<div class="news__post__date">
|
||||
|
@ -88,7 +88,7 @@
|
|||
|
||||
<div class="news__post__text markdown">
|
||||
<h1>{{ post.title }}</h1>
|
||||
{{ post.parsedText|raw }}
|
||||
{{ post.body|parse_text(2)|raw }}
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
{% set manage_link = url('manage-news-post', {'post': post_info.id}) %}
|
||||
|
||||
{% block content %}
|
||||
{{ news_post(post_info) }}
|
||||
{{ news_post(post_info, post_category_info, post_user_info) }}
|
||||
|
||||
{% if comments_info is defined %}
|
||||
<div class="container">
|
||||
|
|
Loading…
Reference in a new issue