folderweb/docs/04-development/06-templates.md
Ruben 069ce389ea Add Atom feed support to list pages
Introduce `feed` metadata option to enable Atom feeds
Update list item structure with standardized fields
Add `$feedUrl` template variable for autodiscovery
Improve date handling with raw/processed date separation
Document feed generation in architecture and rendering docs
Update template examples to use new item structure
2026-02-06 18:24:39 +01:00

5 KiB

Templates

Template Hierarchy

Three levels, rendered inside-out:

Content (Markdown/HTML/PHP rendered to HTML string)
  → page.php or list-*.php (wraps content in semantic markup)
    → base.php (HTML scaffold: doctype, head, header, nav, main, footer)

Template Resolution (canonical reference)

resolveTemplate(string $name): string — in helpers.php. This is the single resolution function used throughout the framework.

  1. Check custom/templates/{name}.php
  2. Fall back to app/default/templates/{name}.php

The three core templates (base, page, list) are resolved at context creation and stored in $ctx->templates. Other docs reference this section for resolution behavior.

List Template Override

In metadata.ini:

[settings]
page_template = "list-grid"

Resolution for list template override (in router.php, at render time):

  1. custom/templates/{page_template}.php
  2. app/default/templates/{page_template}.php
  3. Fall back to $ctx->templates->list (the default resolved at context creation)

Default Templates Provided

Template Purpose
base.php HTML document scaffold
page.php Single page wrapper (<article><?= $content ?></article>)
list.php Default list with cover images, dates, summaries
list-compact.php Minimal list variant
list-grid.php Card grid layout

Template Variables

Variables are injected via extract() — each array key becomes a local variable.

base.php Variables

Variable Type Always Set Description
$content string (HTML) yes Rendered output from page.php or list template
$pageTitle ?string yes Page title (null if unset)
$metaDescription ?string no SEO description
$socialImageUrl ?string no Cover image URL for og:image
$navigation array yes Menu items: ['title','url','order']
$homeLabel string yes Home link text
$pageCssUrl ?string no Page-specific CSS URL
$pageCssHash ?string no CSS cache-bust hash
$feedUrl ?string no Atom feed URL (only on lists with feed = true)
$currentLang string plugin Language code (from languages plugin)
$langPrefix string plugin URL language prefix
$languageUrls array plugin [lang => url] for language switcher
$translations array plugin UI strings for current language

page.php Variables

All base.php variables plus:

Variable Type Description
$content string (HTML) Rendered page content
$metadata ?array Page metadata from metadata.ini

list-*.php Variables

All base.php variables plus:

Variable Type Description
$items array List items (see schema below)
$pageContent ?string (HTML) Rendered content from the list directory itself
$metadata ?array List directory metadata

List Item Schema

Each entry in $items:

Key Type Always Description
title string yes From metadata, first heading, or folder name
url string yes Full URL path with trailing slash and lang prefix
date ?string no Formatted date string (plugin-processed)
rawDate ?string no ISO YYYY-MM-DD date (for feeds, <time> elements)
summary ?string no From metadata
cover ?string no URL to cover image
pdf ?string no URL to first PDF file
redirect ?string no External redirect URL
dirPath string yes Filesystem path to item directory (internal use)

Items sorted by date — direction controlled by order metadata on parent (descending default, ascending available).

Asset URLs vs page URLs: Item url uses the translated slug. Asset paths (cover, pdf) use the actual folder name, ensuring assets resolve regardless of language.

Adding Custom Template Variables

Via Hook::TEMPLATE_VARS:

Hooks::add(Hook::TEMPLATE_VARS, function(array $vars, Context $ctx): array {
    $vars['myVar'] = 'value';
    return $vars;
});

Then in any template: <?= $myVar ?>.

Template Conventions

  • Escape all user-derived output: <?= htmlspecialchars($var) ?>
  • Exception: $content is pre-rendered HTML — output raw: <?= $content ?>
  • Check optional vars: <?php if (!empty($var)): ?>
  • Use null coalescing for defaults: <?= $var ?? 'fallback' ?>
  • Use short echo tags: <?= $expr ?>
  • Semantic HTML5: <article>, <nav>, <header>, <footer>, <time>, <main>
  • ARIA labels on navigation elements
  • Classless defaults — the default theme uses semantic HTML without classes where possible

Partials

Not a framework feature — just PHP includes. Convention:

custom/templates/partials/post-card.php

Include from templates:

<?php foreach ($items as $post): ?>
    <?php include __DIR__ . '/partials/post-card.php'; ?>
<?php endforeach; ?>

Variables from the parent scope are available in the included file.