Update documentation for new JS variables and rendering pipeline Add script.js file support to content directories Implement cache-busting for JavaScript files Update static file serving to include JavaScript files Document page-specific script loading in base.php
6.9 KiB
Content System
Content Discovery
findAllContentFiles(string $dir): array — Returns sorted file paths from $dir.
- Scans for files with extensions in
CONTENT_EXTENSIONS(md,html,php) - Skips
index.php(reserved for server entry points) - Fires
Hook::PROCESS_CONTENT($files, $dir)— plugins filter files (e.g., language variants) - Sorts by filename via
strnatcmp(natural sort:00-hero,01-intro,02-body) - Returns flat array of absolute paths
File ordering convention: Prefix filenames with NN- for explicit ordering. Files without prefix sort after numbered files.
URL Routing
Trailing slash enforcement: All page and list URLs are canonicalized with a trailing slash. Requests without one receive a 301 redirect (e.g., /about → /about/). The frontpage (/) is exempt.
Frontpage handling: Empty request path is handled before parseRequestPath() — it renders the content root directly via renderMultipleFiles(). The "frontpage" type from parseRequestPath() is never reached in the router's switch statement.
parseRequestPath(Context $ctx): array — Returns ['type' => string, 'path' => string].
Types:
"frontpage"— empty request path (handled before this function is called)"page"— resolved directory with no subdirectories"list"— resolved directory with subdirectories"not_found"— slug resolution failed
resolveSlugToFolder(string $parentDir, string $slug): ?string — Matches URL slug to directory name.
Resolution order:
- Exact folder name match (
$slug === $item) - Metadata slug match (
metadata.inislugfield)
Each URL segment is resolved independently, walking the directory tree. This enables language-specific slugs (e.g., /no/om/ → content/about/ via [no] slug = "om").
Metadata
loadMetadata(string $dirPath): ?array — Parses metadata.ini if present.
Returns flat key-value array with a special _raw key containing the full parsed INI structure (including sections). The _raw key is an internal contract — the language plugin reads $data['_raw'][$lang] to merge language-specific overrides. Plugins processing metadata must preserve _raw if present. Fires Hook::PROCESS_CONTENT($metadata, $dirPath, 'metadata').
Core Fields
| Field | Type | Default | Purpose |
|---|---|---|---|
title |
string | First # heading or folder name |
Page title, list item title, <title> tag |
summary |
string | — | List item description, fallback meta description |
date |
string (YYYY-MM-DD) | Folder date prefix or file mtime | Sorting, display |
search_description |
string | Falls back to summary |
<meta name="description"> |
slug |
string | Folder name | URL override |
menu |
bool/int | 0 | Include in navigation |
menu_order |
int | 999 | Navigation sort order (ascending) |
order |
string | "descending" |
List sort direction (ascending|descending) |
redirect |
string | — | External URL (list items can redirect) |
feed |
bool | false |
Enable Atom feed on list pages (feed.xml) |
plugins |
string | — | Comma-separated page-level plugin names |
Settings Section
[settings]
page_template = "list-grid" # List template override (without .php)
show_date = true # Show date in list items (default: true)
hide_list = false # Force page rendering even with subdirectories
Language Sections
title = "About"
slug = "about"
[no]
title = "Om oss"
slug = "om"
Supported language-overridable fields: title, summary, search_description, slug.
Custom Fields
Any key not listed above is passed through to templates/plugins unchanged. Add whatever fields your templates need.
Date Extraction
extractRawDateFromFolder(string $folderName): ?string — Extracts raw YYYY-MM-DD string from folder name prefix. Returns null if no date prefix. No hook processing.
extractDateFromFolder(string $folderName): ?string — Calls extractRawDateFromFolder() then passes the result through Hook::PROCESS_CONTENT($date, 'date_format') for plugin formatting (e.g., "1. January 2025").
If no date prefix exists and no date metadata is set, falls back to file modification time (filemtime). All dates pass through Hook::PROCESS_CONTENT($date, 'date_format') for plugin formatting.
Note: Date extraction uses regex only — 2025-13-45-slug would extract 2025-13-45 without validation. Invalid dates pass through to templates as-is.
Sorting with null dates: Items without any date are sorted as empty strings via strcmp. Their relative order among other dateless items is undefined.
List Item Building
buildListItems(string $dir, Context $ctx, ?array $parentMetadata): array — Builds and sorts the items array for list views. Defined in helpers.php.
For each subdirectory: loads metadata, extracts title/date/cover/PDF, builds URL with lang prefix and slug. Returns sorted array — direction controlled by order metadata on parent (descending default).
Each item contains both a formatted date (hook-processed for display) and a rawDate (ISO YYYY-MM-DD for Atom feeds and <time> elements). Also includes dirPath (filesystem path) used by the feed generator to render full content.
Used by both the list case in router.php and the Atom feed generator.
Navigation
buildNavigation(Context $ctx): array — Scans top-level content directories.
Returns items with menu = 1 metadata, sorted by menu_order. Each item: ['title' => string, 'url' => string, 'order' => int]. URLs include language prefix if applicable.
Cover Images
findCoverImage(string $dirPath): ?string — Finds cover.* file.
Checks extensions in COVER_IMAGE_EXTENSIONS order: jpg, jpeg, png, webp, gif. Returns filename (not full path) or null.
PDF Discovery
findPdfFile(string $dirPath): ?string — Returns basename of first *.pdf found, or null.
Page-Specific CSS
findPageCss(string $dirPath, string $contentDir): ?array — Checks for styles.css in content directory.
Returns ['url' => string, 'hash' => string] or null. Hash is MD5 of file content for cache busting.
Page-Specific JavaScript
findPageJs(string $dirPath, string $contentDir): ?array — Checks for script.js in content directory.
Returns ['url' => string, 'hash' => string] or null. Hash is MD5 of file content for cache busting. The script is loaded with the defer attribute in base.php, placed before </body> for non-blocking progressive enhancement.
Meta Description Extraction
extractMetaDescription(string $dirPath, ?array $metadata): ?string
Priority: search_description → summary → first paragraph from content files (>20 chars).
Title Extraction
extractTitle(string $filePath): ?string — Reads first content file in directory.
Markdown: first # heading. HTML/PHP: first <h1> tag. Returns null if no heading found.