Add Blog RSS feed, footer feed/sitemap links, and expanded docs
Blog RSS feed (novaconium/ISSUES.md): - App/pages/blog/feed/index.php - main feed, built from the same hand-written $posts array App/pages/blog/index.php renders from, so it works with content_index_enabled left at its default false. Added a published date field per post entry. - App/pages/blog/tag/[tag]/feed/index.php - per-tag feed, gated on content_index_enabled the same way blog/tag/[tag]/index.php is. - novaconium/lib/Rss.php - shared RSS 2.0 envelope builder both feeds use, generic (title/link/description/items in, XML string out). - New head_extra block in the root layout (empty by default) so App/pages/blog/_layout/layout.twig can add feed auto-discovery scoped to /blog/* only. Footer: - New content_index_enabled Twig global (Renderer/bootstrap.php), same pattern as admin_auth_enabled. - Right-aligned footer menu linking to /sitemap.xml (only shown when content_index_enabled, so it never links to a 404) and /blog/feed (always shown, no content-index dependency). Docs: - New /admin/docs/rss page: Lib\Rss API, the two shipped feeds, a worked example of adding a feed for another content collection, and how to advertise multiple feeds via head_extra. - New /admin/docs/sitemap page: changefreq/priority blocks, what's included/excluded, an honest callout on the relative-<loc>-URL spec deviation (consistent with how canonical/og:url already work) with an override path for strict compliance, and submitting it to search engines. - /admin/docs (Overview) gains a Requirements section: minimum requirements, then optional requirements for the database/content-index features (pdo_sqlite, optional pdo_mysql, FTS5) - and a Documentation heading separating it from the doc links list. - /admin/docs/getting-started's Requirements line was stale (missing pdo_sqlite entirely, unlike README) - fixed and cross-linked to the Overview page's fuller list. - /admin/docs/content-index trimmed to point at the new RSS/sitemap pages instead of duplicating their detail. Also adds an "In-house comments" entry to novaconium/ISSUES.md's Backlog - a Lib\ class for sidecar-attached comments on any page, depending on the not-yet-built Admin login & user management for real user accounts. Closes the "Blog RSS feed" backlog item.
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Lib;
|
||||
|
||||
/**
|
||||
* A minimal RSS 2.0 envelope builder — plain string concatenation, no
|
||||
* DOMDocument, same style as novaconium/pages/sitemap.xml/index.php.
|
||||
* Generic on purpose (title/link/description/items in, XML string out) —
|
||||
* it doesn't know about blog posts specifically; App/pages/blog/feed/ and
|
||||
* App/pages/blog/tag/[tag]/feed/ are the two call sites that supply
|
||||
* blog-shaped data to it.
|
||||
*
|
||||
* Links/guids are expected to be site-relative paths (e.g.
|
||||
* "/blog/hello-world"), consistent with how this framework already
|
||||
* handles canonical/og:url (see /admin/docs/seo) — there's no site-wide
|
||||
* base-URL config to build absolute URLs from. Every <guid> is emitted
|
||||
* with isPermaLink="false" for exactly this reason: it's a stable
|
||||
* identifier, not a real absolute permalink.
|
||||
*/
|
||||
final class Rss
|
||||
{
|
||||
/**
|
||||
* @param array<int, array{title: string, link: string, guid: string, pubDateTimestamp: int, description: string}> $items
|
||||
*/
|
||||
public static function render(string $channelTitle, string $channelLink, string $channelDescription, array $items): string
|
||||
{
|
||||
$xml = '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
|
||||
$xml .= '<rss version="2.0">' . "\n";
|
||||
$xml .= ' <channel>' . "\n";
|
||||
$xml .= ' <title>' . htmlspecialchars($channelTitle, ENT_XML1) . '</title>' . "\n";
|
||||
$xml .= ' <link>' . htmlspecialchars($channelLink, ENT_XML1) . '</link>' . "\n";
|
||||
$xml .= ' <description>' . htmlspecialchars($channelDescription, ENT_XML1) . '</description>' . "\n";
|
||||
|
||||
foreach ($items as $item) {
|
||||
$xml .= ' <item>' . "\n";
|
||||
$xml .= ' <title>' . htmlspecialchars($item['title'], ENT_XML1) . '</title>' . "\n";
|
||||
$xml .= ' <link>' . htmlspecialchars($item['link'], ENT_XML1) . '</link>' . "\n";
|
||||
$xml .= ' <guid isPermaLink="false">' . htmlspecialchars($item['guid'], ENT_XML1) . '</guid>' . "\n";
|
||||
$xml .= ' <pubDate>' . date(DATE_RSS, $item['pubDateTimestamp']) . '</pubDate>' . "\n";
|
||||
$xml .= ' <description>' . htmlspecialchars($item['description'], ENT_XML1) . '</description>' . "\n";
|
||||
$xml .= ' </item>' . "\n";
|
||||
}
|
||||
|
||||
$xml .= ' </channel>' . "\n";
|
||||
$xml .= '</rss>' . "\n";
|
||||
|
||||
return $xml;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user