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
144 lines
7.3 KiB
Markdown
144 lines
7.3 KiB
Markdown
# Architecture
|
|
|
|
## Directory Layout
|
|
|
|
```
|
|
app/ # Framework core — stable API surface
|
|
router.php # Entry point: all requests route here
|
|
constants.php # CONTENT_EXTENSIONS, COVER_IMAGE_EXTENSIONS
|
|
hooks.php # Hook enum + Hooks class
|
|
context.php # Context + Templates classes
|
|
config.php # createContext(): config merge + bootstrap
|
|
helpers.php # Utility functions (template resolution, extraction)
|
|
content.php # Content discovery, slug resolution, navigation
|
|
rendering.php # Markdown/HTML/PHP rendering, template wrapping
|
|
cache.php # Markdown render cache (/tmp/folderweb_cache)
|
|
plugins.php # PluginManager class
|
|
static.php # /app/* asset serving with traversal protection
|
|
vendor/ # Parsedown + ParsedownExtra (Markdown→HTML)
|
|
plugins/global/ # Built-in plugins (languages.php)
|
|
default/ # Demo/fallback theme (NOT for production use)
|
|
config.ini # Default configuration
|
|
templates/ # base.php, page.php, list.php, list-compact.php, list-grid.php
|
|
styles/ # Default stylesheet
|
|
languages/ # en.ini, no.ini
|
|
content/ # Demo content shown when custom/ has no content
|
|
custom/ # User layer — all site-specific work goes here
|
|
config.ini # Config overrides (merged over app/default/config.ini)
|
|
templates/ # Override any template by matching filename
|
|
styles/ # Stylesheets served via /app/styles/*
|
|
fonts/ # Fonts served via /app/fonts/*
|
|
assets/ # Static files served at document root (favicon, robots.txt)
|
|
languages/ # Translation overrides/additions (*.ini)
|
|
plugins/global/ # Custom global plugins
|
|
plugins/page/ # Custom page plugins (reserved, not yet active)
|
|
content/ # Website content (= document root in production)
|
|
devel/ # Dev environment (Containerfile, compose, Apache, perf tools)
|
|
docs/ # Documentation (01-03 human-facing, 04 machine-facing)
|
|
```
|
|
|
|
## Stable Contracts
|
|
|
|
**`app/` is the framework.** When developing `custom/` sites, never modify `app/`. When developing the framework itself, preserve these contracts:
|
|
|
|
| Contract | Guaranteed Behavior |
|
|
|---|---|
|
|
| Override chain | `custom/*` always takes priority over `app/default/*` for templates, styles, languages, config |
|
|
| Template names | `base.php`, `page.php`, `list.php` are the three core template names |
|
|
| Hook enum | `Hook::CONTEXT_READY`, `Hook::PROCESS_CONTENT`, `Hook::TEMPLATE_VARS` — signatures documented in `05-hooks-plugins.md` |
|
|
| Context API | `$ctx->set()`, `$ctx->get()`, `$ctx->has()` — stable key/value store |
|
|
| Content extensions | `md`, `html`, `php` — defined in `CONTENT_EXTENSIONS` |
|
|
| Config format | INI with sections, merged via `array_replace_recursive` |
|
|
| Metadata format | INI file named `metadata.ini` in content directories |
|
|
| URL structure | Folder path = URL path, with slug overrides via metadata |
|
|
| Plugin locations | `app/plugins/{scope}/` and `custom/plugins/{scope}/` |
|
|
| Asset routes | `/app/styles/*` → `custom/styles/`, `/app/fonts/*` → `custom/fonts/`, `/app/default-styles/*` → `app/default/styles/` |
|
|
| Trailing slash | Pages and lists enforce trailing slash via 301 redirect |
|
|
|
|
## Request Flow
|
|
|
|
```
|
|
Browser request
|
|
│
|
|
├─ Apache rewrite: all non-/app/ requests → app/router.php
|
|
│
|
|
▼
|
|
router.php
|
|
│
|
|
├─ 1. Load modules (constants, hooks, context, helpers, plugins, config, content, rendering)
|
|
├─ 2. createContext()
|
|
│ ├─ Parse + merge config (default ← custom)
|
|
│ ├─ loadGlobalPlugins() → fires Hook::CONTEXT_READY
|
|
│ ├─ Determine contentDir (custom content or demo fallback)
|
|
│ ├─ Parse REQUEST_URI → requestPath
|
|
│ └─ Resolve template paths (custom fallback to default)
|
|
│
|
|
├─ 3. Check custom/assets/{path} → serve static file + exit
|
|
├─ 4. Check content/{path} for static asset (css/img/pdf/font) → serve + exit
|
|
│
|
|
├─ 5. Path ends with feed.xml? → Atom feed generation
|
|
│ ├─ Strip feed.xml, resolve parent as list directory
|
|
│ ├─ Check feed = true in metadata, otherwise 404
|
|
│ ├─ buildListItems() + renderContentFile() for full content
|
|
│ └─ Output Atom XML + exit
|
|
│
|
|
├─ 6. Empty path? → frontpage: findAllContentFiles + renderMultipleFiles
|
|
│
|
|
└─ 7. parseRequestPath() → {type, path}
|
|
│
|
|
├─ "page": trailing slash redirect → findAllContentFiles → renderMultipleFiles
|
|
│ (fires Hook::PROCESS_CONTENT for file filtering)
|
|
│ (fires Hook::TEMPLATE_VARS before template render)
|
|
│ Template chain: content → page.php → base.php
|
|
│
|
|
├─ "list": trailing slash redirect → buildListItems() from helpers.php
|
|
│ ├─ Check hide_list metadata → treat as page if true
|
|
│ ├─ Select list template from metadata page_template
|
|
│ ├─ buildListItems(): metadata, titles, dates, covers for each subdir
|
|
│ ├─ Sort items by date (metadata `order` = ascending|descending)
|
|
│ ├─ Store pageTitle, metaDescription, feedUrl etc. on context
|
|
│ ├─ Fire Hook::TEMPLATE_VARS
|
|
│ └─ Template chain: items → list-*.php → base.php (via renderTemplate)
|
|
│
|
|
└─ "not_found": 404 response
|
|
```
|
|
|
|
## Module Dependency Order
|
|
|
|
Loaded sequentially in `router.php`:
|
|
|
|
```
|
|
constants.php → hooks.php → context.php → helpers.php → plugins.php → config.php → content.php → rendering.php
|
|
```
|
|
|
|
`config.php` calls `createContext()` which triggers plugin loading, so `hooks.php` and `plugins.php` must be loaded before it.
|
|
|
|
## Page vs List Detection
|
|
|
|
A resolved directory becomes a **list** if it contains subdirectories, otherwise a **page**. Override with `hide_list = true` in metadata to force page rendering on directories with children.
|
|
|
|
## Deployment Models
|
|
|
|
### Framework development (this repo)
|
|
|
|
Both `app/` and `custom/` live in the same repository. `custom/` holds demo/test overrides.
|
|
|
|
### Site development (separate repo)
|
|
|
|
The site is its own git repository containing `custom/`, content, and deployment config. `app/` is included as a symlink, git submodule, or copied directory pointing to a specific framework version. The site repo never modifies `app/`.
|
|
|
|
Typical site repo layout:
|
|
|
|
```
|
|
my-site/ # Site git repo
|
|
app/ → ../folderweb/app # Symlink to framework (or submodule, or copy)
|
|
custom/ # Site-specific templates, styles, plugins, config
|
|
content/ # Website content (often the document root)
|
|
devel/ # Site's own dev environment config (optional)
|
|
```
|
|
|
|
Either direction works: `app/` symlinked into a site repo, or `custom/` symlinked into the framework repo during development.
|
|
|
|
## Demo Fallback
|
|
|
|
When `content/` (document root) has no files, `app/default/content/` is used automatically. This is **demo mode only** — production sites always provide their own content via the document root.
|