Skip to content

feat(cms): F21.1 — RSS feed per page category with homepage and header links #627

@jbourdin

Description

@jbourdin

Summary

Expose an RSS feed for each page category so readers can subscribe to updates, and surface the feed URL in the two natural discovery points: the homepage "latest pages" block and the header of the category list / single-page views.

This is greenfield — the project currently ships an iCal feed for events (EventAgendaController::feed()) but no RSS/Atom for CMS pages.

Proposed approach

1. New route + controller action

In src/Controller/PageController.php, add a feed() action mirroring the structure of category():

  • Route name: app_page_category_feed
  • URL: /{_locale}/pages/category/{id}/feed.xml
  • Returns RSS 2.0 XML with Content-Type: application/rss+xml; charset=UTF-8
  • Items: published pages in that category, newest first, capped (e.g. 20 items) — reuse the published-pages repository query used by category()
  • Each <item> carries <title>, <link> (absolute URL to app_page_show), <guid isPermaLink="true">, <pubDate> (RFC 822 from publishedAt / lastUpdatedAt), <description> (page excerpt or first paragraph)
  • Channel-level metadata: category name (translated), site URL, language matching {_locale}, <lastBuildDate>

Follow the iCal precedent: short HTTP cache (public, max-age=300) since pages publish infrequently.

2. Rendering

Use a dedicated Twig template templates/page/feed.xml.twig rather than building XML strings in PHP. Mirror the iCal pattern — keeps escaping in Twig (|escape('html') or a CDATA wrap) and the action thin.

3. Discovery links

Per the request, expose the feed URL in three template locations:

  • templates/home/blocks/_latest_pages.html.twig — small RSS icon/link next to the block heading, pointing to the configured category's feed (the block already surfaces a category from HomepageRenderer::resolveLatestPages()).
  • templates/page/category.html.twig (around the <h1> near line 14) — RSS link in the category header.
  • templates/page/show.html.twig (header region around lines 41–42) — RSS link to the page's own category feed (since pages belong to a category via Page::menuCategory).

Also add the standard <link rel="alternate" type="application/rss+xml" …> to the <head> of both the category list and the single-page view so browser readers can autodiscover.

4. Translations

New keys in translations/messages.{en,fr}.xlf:

  • app.page.feed.subscribe → "Subscribe via RSS" / "S'abonner au flux RSS"
  • app.page.feed.title_for_category → "%category% — latest pages" / "%category% — derniers articles" (channel title)

Acceptance criteria

  • GET /en/pages/category/{id}/feed.xml returns valid RSS 2.0 (validates via W3C feed validator) with Content-Type: application/rss+xml.
  • The same URL exists for /fr/… and the channel <language> reflects the locale.
  • Only published pages of the requested category appear; unpublished and other-category pages are excluded.
  • Item ordering is newest-first by publication / last-update date.
  • A 404 is returned for an unknown category id.
  • The homepage "latest pages" block shows a small RSS link/icon pointing to the configured category's feed.
  • The category list header (category.html.twig) shows a "Subscribe via RSS" link.
  • Each single-page view (show.html.twig) shows a "Subscribe via RSS" link pointing to that page's category feed.
  • Both views include a <link rel="alternate" type="application/rss+xml"> autodiscovery tag in <head>.
  • PHPUnit functional test covers: feed responds 200, returns RSS content-type, includes only published pages of the category, escapes special characters in titles/descriptions.

Files likely touched

  • src/Controller/PageController.php — new feed() action + route
  • templates/page/feed.xml.twig — new feed template
  • templates/page/category.html.twig — header link + <link rel="alternate">
  • templates/page/show.html.twig — header link + <link rel="alternate">
  • templates/home/blocks/_latest_pages.html.twig — RSS link near block heading
  • translations/messages.{en,fr}.xlf — two new keys
  • docs/features.md — claim F21 (Content syndication) and F21.1 entry
  • tests/Functional/Controller/PageControllerTest.php (or equivalent) — feed coverage

Out of scope (possible follow-ups)

  • A global "all pages" RSS feed (this issue is strictly per-category, as requested).
  • Atom 1.0 alongside RSS 2.0 — only one format ships here.
  • Per-author or per-tag feeds.
  • WebSub / PubSubHubbub push notifications.

Notes

  • MenuCategory has no slug, only numeric id — URL pattern follows the existing app_page_category route shape.
  • Feature ID: allocates F21 as a new top-level family ("Content syndication"). F19 is left as a deliberate gap; F20 is Theming. Update docs/features.md in the same PR.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    Status

    Next

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions