# Rendering & Caching ## Content Rendering **`renderContentFile(string $filePath, ?Context $ctx = null): string`** Renders a single content file to HTML string based on extension: | Extension | Rendering | |---|---| | `md` | Parsedown + ParsedownExtra → HTML. Cached. Language prefix injected into internal links. | | `html` | Included directly (output buffered) | | `php` | Included with `Hook::TEMPLATE_VARS` variables extracted into scope | PHP content files receive variables from `Hook::TEMPLATE_VARS` (starting with an empty array). This includes plugin-provided variables like `$translations`, `$currentLang`, etc. However, page-level context like `$metadata` and `$pageTitle` is **not** included — those are only available in the wrapping template (page.php/list.php), not in content files. ## Page Rendering **`renderMultipleFiles(Context $ctx, array $files, string $pageDir): void`** Used for both frontpage and page views: 1. Load metadata for `$pageDir` 2. Load page plugins (from metadata `plugins` field) 3. Render all content files, concatenate HTML 4. Compute: `$pageTitle`, `$metaDescription`, `$pageCssUrl`/`$pageCssHash`, `$socialImageUrl` 5. Fire `Hook::TEMPLATE_VARS` with all variables 6. `extract()` variables → render `page.php` → capture output as `$content` 7. Render `base.php` with `$content` + base variables 8. `exit` ## List Rendering Handled directly in `router.php` (not a separate function): 1. Render directory's own content files as `$pageContent` 2. Load metadata, check `hide_list` 3. Select list template from `page_template` metadata 4. Build `$items` array from subdirectories (metadata + extraction) 5. Sort items by date 6. Fire `Hook::TEMPLATE_VARS` 7. Render list template → capture as `$content` 8. Pass to `renderTemplate()` which renders `base.php` **`renderTemplate(Context $ctx, string $content, int $statusCode = 200): void`** — Wraps content in base template. Used for list views and error pages. ## Markdown Caching Defined in `app/cache.php`. File-based cache in `/tmp/folderweb_cache/`. **Cache key:** `md5($filePath . $mtime . $langPrefix)` - Invalidates when file is modified (mtime changes) - Invalidates per-language (different link rewriting) - No explicit TTL — entries persist until temp directory cleanup - **Does not track plugin state** — if a plugin modifies Markdown output (e.g., via PROCESS_CONTENT on files), changing plugin config won't bust the cache. Clear `/tmp/folderweb_cache/` manually after plugin changes that affect rendered Markdown. ```php getCachedMarkdown(string $filePath, string $langPrefix = ''): ?string setCachedMarkdown(string $filePath, string $html, string $langPrefix = ''): void ``` ## Static File Serving ### Content Assets (router.php) Before content routing, the router serves static files from content directory with an explicit MIME type allowlist: `css`, `jpg`, `jpeg`, `png`, `gif`, `webp`, `svg`, `pdf`, `woff`, `woff2`, `ttf`, `otf` Files not in this list are not served as static assets. Notably, `.js` files are excluded — JavaScript must be placed in `custom/assets/` to be served (at the document root URL), or linked from an external source. ### Custom Assets (router.php) Files in `custom/assets/` are served at the document root URL. Example: `custom/assets/favicon.ico` → `/favicon.ico`. Uses `mime_content_type()` for MIME detection. ### Framework Assets (static.php) `/app/*` requests are handled by `static.php` with directory traversal protection (`../` stripped): | URL Path | Filesystem Path | |---|---| | `/app/styles/*` | `custom/styles/*` | | `/app/fonts/*` | `custom/fonts/*` | | `/app/assets/*` | `custom/assets/*` | | `/app/default-styles/*` | `app/default/styles/*` | MIME types resolved from extension map, falling back to `mime_content_type()`. ## CSS Cache Busting Page-specific CSS gets an MD5 hash appended: `?v={hash}`. Computed by `findPageCss()`. The default theme's CSS is linked directly without hash (uses browser caching). ## Parsedown Markdown rendering uses `Parsedown` + `ParsedownExtra` from `app/vendor/`. These are the only third-party dependencies. Loaded lazily on first Markdown render. **Internal link rewriting:** After Markdown→HTML conversion, `href="/..."` links are prefixed with the current language prefix (e.g., `/no`). This ensures Markdown links work correctly in multilingual sites. ## Error Responses - **403:** Invalid path (outside content directory or unreadable) - **404:** Slug resolution failed or unknown route type - Both render via `renderTemplate()` with appropriate status code