diff --git a/assets/less/news/feed.less b/assets/less/news/feed.less new file mode 100644 index 00000000..98d04906 --- /dev/null +++ b/assets/less/news/feed.less @@ -0,0 +1,25 @@ +.news__feed { + display: flex; + color: inherit; + text-decoration: none; + font-size: 1.5em; + line-height: 32px; + height: 32px; + transition: background-color .2s; + border-radius: 4px; + + &:hover, + &:focus { + background-color: fade(#fff, 20%); + } + + &:active { + background-color: fade(#fff, 10%); + } + + &__icon { + width: 32px; + height: 32px; + text-align: center; + } +} diff --git a/assets/less/news/feeds.less b/assets/less/news/feeds.less new file mode 100644 index 00000000..ab13a27d --- /dev/null +++ b/assets/less/news/feeds.less @@ -0,0 +1,6 @@ +.news__feeds { + display: grid; + grid-template-columns: 1fr 1fr; + grid-gap: 2px; + padding: 2px; +} diff --git a/assets/less/news/list.less b/assets/less/news/list.less index 2da03731..efb7f129 100644 --- a/assets/less/news/list.less +++ b/assets/less/news/list.less @@ -1,4 +1,5 @@ .news__list { + margin: 2px 0; &__item { text-decoration: none; diff --git a/assets/less/news/news.less b/assets/less/news/news.less index 73baa34c..9a7f4dba 100644 --- a/assets/less/news/news.less +++ b/assets/less/news/news.less @@ -3,3 +3,5 @@ @import "preview"; @import "sidebar"; @import "post"; +@import "feeds"; +@import "feed"; diff --git a/public/news.php b/public/news.php index b4492464..3e02aa96 100644 --- a/public/news.php +++ b/public/news.php @@ -9,6 +9,7 @@ if (!empty($_GET['n']) && is_string($_GET['n'])) { return; } +$feedMode = trim($_SERVER['PATH_INFO'] ?? '', '/'); $categoryId = !empty($_GET['c']) && is_string($_GET['c']) ? (int)$_GET['c'] : 0; $postId = !empty($_GET['p']) && is_string($_GET['p']) ? (int)$_GET['p'] : 0; @@ -67,8 +68,23 @@ if ($categoryId > 0) { $featured = news_posts_get(0, 10, $category['category_id'], true); - tpl_var('news_pagination', $categoryPagination); - echo tpl_render('news.category', compact('category', 'posts', 'featured')); + if ($feedMode === 'rss' || $feedMode === 'atom') { + header("Content-Type: application/{$feedMode}+xml; charset=utf-8"); + echo news_feed($feedMode, $posts, [ + 'title' => config_get('Site', 'name') . ' » ' . $category['category_name'], + 'subtitle' => $category['category_description'], + 'html-url' => url('news-category', ['category' => $category['category_id']]), + 'feed-url' => url("news-category-feed-{$feedMode}", ['category' => $category['category_id']]), + ]); + return; + } + + echo tpl_render('news.category', [ + 'category' => $category, + 'posts' => $posts, + 'featured' => $featured, + 'news_pagination' => $categoryPagination, + ]); return; } @@ -94,5 +110,19 @@ if (!$posts) { return; } -tpl_var('news_pagination', $newsPagination); -echo tpl_render('news.index', compact('categories', 'posts')); +if ($feedMode === 'rss' || $feedMode === 'atom') { + header("Content-Type: application/{$feedMode}+xml; charset=utf-8"); + echo news_feed($feedMode, $posts, [ + 'title' => config_get('Site', 'name') . ' » Featured News', + 'subtitle' => 'A live featured news feed.', + 'html-url' => url('news-index'), + 'feed-url' => url("news-feed-{$feedMode}"), + ]); + return; +} + +echo tpl_render('news.index', [ + 'categories' => $categories, + 'posts' => $posts, + 'news_pagination' => $newsPagination, +]); diff --git a/src/news.php b/src/news.php index bca5e0e3..d36aea66 100644 --- a/src/news.php +++ b/src/news.php @@ -297,3 +297,85 @@ function news_post_get(int $postId): array $getPost->bindValue(':post_id', $postId); return db_fetch($getPost); } + +function news_feed(string $type, array $posts, array $info): string +{ + $document = new DOMDocument('1.0', 'utf-8'); + $urlPrefix = url_prefix(false); + $htmlUrl = $urlPrefix . $info['html-url']; + $feedUrl = $urlPrefix . $info['feed-url']; + + if ($type === 'rss') { + $dateFormat = 'r'; + $rss = $document->appendChild($document->createElement('rss')); + $rss->setAttribute('version', '2.0'); + $rss->setAttribute('xmlns:atom', 'http://www.w3.org/2005/Atom'); + $feed = $rss->appendChild($document->createElement('channel')); + $feed->appendChild($document->createElement('ttl', '900')); + } else { + $dateFormat = 'c'; + $feed = $document->appendChild($document->createElement('feed')); + $feed->setAttribute('xmlns', 'http://www.w3.org/2005/Atom'); + $link = $feed->appendChild($document->createElement('link')); + $link->setAttribute('href', $htmlUrl); + } + + $feed->appendChild($document->createElement('title', $info['title'])); + $feed->appendChild($document->createElement( + $type === 'rss' ? 'description' : 'subtitle', $info['subtitle'] + )); + $feed->appendChild($document->createElement( + $type === 'rss' ? 'link' : 'id', $htmlUrl + )); + $feed->appendChild($document->createElement( + $type === 'rss' ? 'pubDate' : 'updated', + date($dateFormat, strtotime($posts[0]['post_created'])) + )); + $link = $feed->appendChild($document->createElement($type === 'rss' ? 'atom:link' : 'link')); + $link->setAttribute('rel', 'self'); + $link->setAttribute('href', $feedUrl); + + foreach ($posts as $post) { + $entry = $feed->appendChild($document->createElement($type === 'rss' ? 'item' : 'entry')); + $entry->appendChild($document->createElement('title', $post['post_title'])); + $entry->appendChild($document->createElement( + $type === 'rss' ? 'link' : 'id', + $urlPrefix . url('news-post', ['post' => $post['post_id']]) + )); + $entry->appendChild($document->createElement( + $type === 'rss' ? 'pubDate' : 'updated', + date($dateFormat, strtotime($post['post_created'])) + )); + + $entry->appendChild($document->createElement( + $type === 'rss' ? 'description' : 'summary', + first_paragraph($post['post_text']) + )); + + if ($type === 'rss') { + $entry->appendChild($document->createElement( + 'comments', + $urlPrefix . url('news-post-comments', ['post' => $post['post_id']]) + )); + $guid = $entry->appendChild($document->createElement( + 'guid', + $urlPrefix . url('news-post', ['post' => $post['post_id']]) + )); + $guid->setAttribute('isPermaLink', 'true'); + } else { + $link = $entry->appendChild($document->createElement('link')); + $link->setAttribute('href', $urlPrefix . url('news-post', ['post' => $post['post_id']])); + $link->setAttribute('type', 'text/html'); + $html = $entry->appendChild($document->createElement( + 'content', + htmlentities(parse_text($post['post_text'], MSZ_PARSER_MARKDOWN)) + )); + $html->setAttribute('type', 'html'); + $author = $entry->appendChild($document->createElement('author')); + $author->appendChild($document->createElement('name', $post['username'])); + $author->appendChild($document->createElement('uri', $urlPrefix . url('user-profile', ['user' => $post['user_id']]))); + } + } + + return $document->saveXML(); +} diff --git a/src/url.php b/src/url.php index 8b16c1bb..b7482de2 100644 --- a/src/url.php +++ b/src/url.php @@ -30,6 +30,10 @@ define('MSZ_URLS', [ 'news-post' => ['/news.php', ['p' => '']], 'news-post-comments' => ['/news.php', ['p' => ''], 'comments'], 'news-category' => ['/news.php', ['c' => '', 'page' => '']], + 'news-feed-rss' => ['/news.php/rss'], + 'news-category-feed-rss' => ['/news.php/rss', ['c' => '']], + 'news-feed-atom' => ['/news.php/atom'], + 'news-category-feed-atom' => ['/news.php/atom', ['c' => '']], 'forum-index' => ['/forum'], 'forum-leaderboard' => ['/forum/leaderboard.php', ['id' => '', 'mode' => '']], diff --git a/templates/_layout/meta.twig b/templates/_layout/meta.twig index 49d54a1a..d01e94f6 100644 --- a/templates/_layout/meta.twig +++ b/templates/_layout/meta.twig @@ -56,4 +56,10 @@ {% endif %} {% endif %} + + {% if feeds is defined and feeds is iterable %} + {% for feed in feeds %} + + {% endfor %} + {% endif %} {% endspaceless %} diff --git a/templates/news/category.twig b/templates/news/category.twig index 482fea28..130cac1c 100644 --- a/templates/news/category.twig +++ b/templates/news/category.twig @@ -9,6 +9,19 @@ 'page': news_pagination.page > 2 ? news_pagination.page : 0, }) %} +{% set feeds = [ + { + 'type': 'rss', + 'title': '', + 'url': url('news-category-feed-rss', {'category': category.category_id}), + }, + { + 'type': 'atom', + 'title': '', + 'url': url('news-category-feed-atom', {'category': category.category_id}), + }, +] %} + {% block content %}
@@ -41,6 +54,29 @@
{% endif %} + +
+ {{ container_title('Feeds') }} + + +
{% endblock %} diff --git a/templates/news/index.twig b/templates/news/index.twig index fb6c021c..5d5d26fc 100644 --- a/templates/news/index.twig +++ b/templates/news/index.twig @@ -8,6 +8,19 @@ }) %} {% set manage_link = '/manage/news.php?v=index' %} +{% set feeds = [ + { + 'type': 'rss', + 'title': '', + 'url': url('news-feed-rss'), + }, + { + 'type': 'atom', + 'title': '', + 'url': url('news-feed-atom'), + }, +] %} + {% block content %}
@@ -37,6 +50,29 @@ {% endfor %}
+ +
+ {{ container_title('Feeds') }} + + +
{% endblock %}