folderweb/docs/reference/templates.md
2025-11-02 13:46:47 +01:00

608 lines
14 KiB
Markdown

# Template Reference
Complete reference for all templates and available variables in FolderWeb.
## Template System Overview
FolderWeb uses a fallback template system:
1. Check `/custom/templates/{name}.php`
2. Fall back to `/app/default/templates/{name}.php`
Templates are plain PHP files with access to specific variables and the context object.
## Core Templates
### base.php
**Purpose**: HTML wrapper for all pages (header, navigation, footer)
**Used**: On every page render
**Customizable**: Yes
**Available Variables**:
| Variable | Type | Description |
|----------|------|-------------|
| `$content` | string | Rendered page content (HTML) |
| `$ctx` | Context | Full context object |
| `$currentLang` | string | Current language code (e.g., "en", "no") |
| `$navigation` | array | Navigation menu items |
| `$homeLabel` | string | Site title |
| `$translations` | array | UI translation strings |
| `$pageTitle` | string | Current page title |
| `$dirName` | string | Parent directory name |
| `$pageName` | string | Current page filename |
**Example**:
```php
<!DOCTYPE html>
<html lang="<?= $ctx->currentLang ?>">
<head>
<meta charset="UTF-8">
<title><?= htmlspecialchars($pageTitle) ?> | <?= htmlspecialchars($homeLabel) ?></title>
<link rel="stylesheet" href="/app/styles/base.css?v=<?= md5_file(resolveTemplate('base.css', 'styles')) ?>">
</head>
<body class="section-<?= $dirName ?> page-<?= $pageName ?>">
<header>
<nav>
<a href="<?= $ctx->langPrefix ?>/"><?= $homeLabel ?></a>
<?php foreach ($navigation as $item): ?>
<a href="<?= $item['url'] ?>"><?= htmlspecialchars($item['title']) ?></a>
<?php endforeach; ?>
</nav>
</header>
<main>
<?= $content ?>
</main>
<footer>
<p><?= $translations['footer_text'] ?></p>
</footer>
</body>
</html>
```
### page.php
**Purpose**: Wrapper for single pages and articles
**Used**: For file and multi-file pages
**Customizable**: Yes
**Available Variables**:
| Variable | Type | Description |
|----------|------|-------------|
| `$content` | string | Rendered content (HTML) |
| `$pageMetadata` | array | Page metadata from metadata.ini |
| `$translations` | array | UI translation strings |
**Example**:
```php
<article>
<?= $content ?>
<?php if (!empty($pageMetadata['tags'])): ?>
<footer class="tags">
<strong><?= $translations['tags'] ?>:</strong>
<?php foreach ($pageMetadata['tags'] as $tag): ?>
<span class="tag"><?= htmlspecialchars($tag) ?></span>
<?php endforeach; ?>
</footer>
<?php endif; ?>
</article>
```
## List Templates
### list.php
**Purpose**: Simple list view (default)
**Used**: Directories with subdirectories
**Customizable**: Yes
**Available Variables**:
| Variable | Type | Description |
|----------|------|-------------|
| `$items` | array | Array of subdirectory items |
| `$metadata` | array | Directory metadata |
| `$pageContent` | string | Optional intro content (HTML) |
| `$translations` | array | UI translation strings |
**Item Structure**:
Each item in `$items` has:
```php
[
'title' => 'Item Title', // From metadata or H1
'date' => '2. november 2025', // Formatted date
'url' => '/blog/post-slug/', // Full URL with language prefix
'cover' => '/path/to/cover.jpg', // Cover image path or null
'summary' => 'Brief description', // From metadata or null
'pdf' => '/path/to/file.pdf', // PDF file path or null
'redirect' => 'https://...', // External URL or null
]
```
**Example**:
```php
<?php if (!empty($pageContent)): ?>
<div class="page-intro">
<?= $pageContent ?>
</div>
<?php endif; ?>
<div class="list">
<?php foreach ($items as $item): ?>
<article>
<h2>
<a href="<?= $item['url'] ?>">
<?= htmlspecialchars($item['title']) ?>
</a>
</h2>
<?php if ($item['date']): ?>
<time><?= $item['date'] ?></time>
<?php endif; ?>
<?php if ($item['summary']): ?>
<p><?= htmlspecialchars($item['summary']) ?></p>
<?php endif; ?>
</article>
<?php endforeach; ?>
</div>
```
### list-grid.php
**Purpose**: Grid layout with cover images
**Used**: Visual blog/portfolio listings
**Customizable**: Yes
**Same variables as list.php**
Features:
- Grid layout
- Cover images
- PDF download links
- "Read more" buttons
**Example**:
```php
<div class="list-grid">
<?php foreach ($items as $item): ?>
<article>
<?php if ($item['cover']): ?>
<img src="<?= $item['cover'] ?>" alt="">
<?php endif; ?>
<h2>
<a href="<?= $item['url'] ?>">
<?= htmlspecialchars($item['title']) ?>
</a>
</h2>
<?php if ($item['date']): ?>
<time><?= $item['date'] ?></time>
<?php endif; ?>
<?php if ($item['summary']): ?>
<p><?= htmlspecialchars($item['summary']) ?></p>
<?php endif; ?>
<div class="actions">
<a href="<?= $item['url'] ?>" class="button">
<?= $translations['read_more'] ?>
</a>
<?php if ($item['pdf']): ?>
<a href="<?= $item['pdf'] ?>" download class="button secondary">
Download PDF
</a>
<?php endif; ?>
</div>
</article>
<?php endforeach; ?>
</div>
```
### list-card-grid.php
**Purpose**: Card-style grid with external link support
**Used**: Portfolios, resource lists
**Customizable**: Yes
**Same variables as list.php**
Features:
- Card-style layout
- PDF download support
- External redirect support
- Cover images
**Example**:
```php
<div class="card-grid">
<?php foreach ($items as $item): ?>
<article class="card">
<?php if ($item['cover']): ?>
<img src="<?= $item['cover'] ?>" alt="">
<?php endif; ?>
<h2><?= htmlspecialchars($item['title']) ?></h2>
<?php if ($item['summary']): ?>
<p><?= htmlspecialchars($item['summary']) ?></p>
<?php endif; ?>
<?php if ($item['redirect']): ?>
<a href="<?= $item['redirect'] ?>"
target="_blank"
rel="noopener"
class="button">
Visit Site
</a>
<?php elseif ($item['pdf']): ?>
<a href="<?= $item['pdf'] ?>" download class="button">
Download PDF
</a>
<?php else: ?>
<a href="<?= $item['url'] ?>" class="button">
View Details
</a>
<?php endif; ?>
</article>
<?php endforeach; ?>
</div>
```
### list-faq.php
**Purpose**: Expandable FAQ/Q&A format
**Used**: FAQ sections, documentation
**Customizable**: Yes
**Same variables as list.php**
Features:
- Collapsible `<details>` elements
- Semantic HTML
- Keyboard accessible
**Example**:
```php
<?php if (!empty($pageContent)): ?>
<div class="page-intro">
<?= $pageContent ?>
</div>
<?php endif; ?>
<div class="faq">
<?php foreach ($items as $item): ?>
<details>
<summary><?= htmlspecialchars($item['title']) ?></summary>
<?php if ($item['summary']): ?>
<p><?= htmlspecialchars($item['summary']) ?></p>
<?php endif; ?>
<a href="<?= $item['url'] ?>">
<?= $translations['read_more'] ?>
</a>
</details>
<?php endforeach; ?>
</div>
```
## Context Object
All templates have access to `$ctx` (Context object):
### Properties
| Property | Type | Description |
|----------|------|-------------|
| `$ctx->contentDir` | string | Path to content directory |
| `$ctx->currentLang` | string | Current language code |
| `$ctx->defaultLang` | string | Default language code |
| `$ctx->availableLangs` | array | Available language codes |
| `$ctx->langPrefix` | string | URL language prefix (e.g., "/en" or "") |
| `$ctx->requestPath` | string | Current request path |
| `$ctx->hasTrailingSlash` | bool | Whether path has trailing slash |
| `$ctx->navigation` | array | Navigation menu items (computed) |
| `$ctx->homeLabel` | string | Site title (computed) |
| `$ctx->translations` | array | UI translations (computed) |
### Example Usage
```php
<!-- Language switcher -->
<?php foreach ($ctx->availableLangs as $lang): ?>
<?php
$url = $lang === $ctx->defaultLang
? '/' . trim($ctx->requestPath, '/')
: '/' . $lang . '/' . trim($ctx->requestPath, '/');
?>
<a href="<?= $url ?>" <?= $lang === $ctx->currentLang ? 'aria-current="true"' : '' ?>>
<?= strtoupper($lang) ?>
</a>
<?php endforeach; ?>
<!-- Breadcrumbs -->
<nav aria-label="Breadcrumb">
<ol>
<li><a href="<?= $ctx->langPrefix ?>/"><?= $ctx->homeLabel ?></a></li>
<?php
$parts = array_filter(explode('/', trim($ctx->requestPath, '/')));
$path = '';
foreach ($parts as $i => $part):
$path .= '/' . $part;
$isLast = ($i === count($parts) - 1);
?>
<li<?= $isLast ? ' aria-current="page"' : '' ?>>
<?php if ($isLast): ?>
<?= htmlspecialchars($part) ?>
<?php else: ?>
<a href="<?= $ctx->langPrefix . $path ?>/">
<?= htmlspecialchars($part) ?>
</a>
<?php endif; ?>
</li>
<?php endforeach; ?>
</ol>
</nav>
```
## Navigation Array
Structure of `$navigation` items:
```php
[
[
'title' => 'Blog',
'url' => '/blog/',
'order' => 1
],
[
'title' => 'About',
'url' => '/about/',
'order' => 2
],
// ...
]
```
Already sorted by `menu_order` field.
## Translation Array
Structure of `$translations`:
```php
[
'home' => 'Home',
'read_more' => 'Read more',
'categories' => 'Categories',
'tags' => 'Tags',
'footer_text' => 'Made with FolderWeb',
'footer_handcoded' => 'Generated in',
'footer_page_time' => 'ms',
// ... custom translations
]
```
## Helper Functions Available in Templates
### resolveTemplate()
Find custom or default template:
```php
$templatePath = resolveTemplate('base', 'templates');
$cssPath = resolveTemplate('base.css', 'styles');
```
### htmlspecialchars()
Escape output (always use for user content):
```php
<?= htmlspecialchars($variable) ?>
```
### Other PHP Functions
All standard PHP functions are available:
- `isset()`, `empty()`
- `count()`, `array_filter()`
- `date()`, `time()`
- String functions
- etc.
## Creating Custom Templates
### Step 1: Create Template File
```bash
touch custom/templates/my-custom-list.php
```
### Step 2: Use Standard Variables
Custom list templates receive `$items`, `$metadata`, `$pageContent`, `$translations`.
### Step 3: Apply in Metadata
**content/my-section/metadata.ini**:
```ini
page_template = "my-custom-list"
```
Note: Omit `.php` extension.
## Template Best Practices
### Always Escape Output
```php
<!-- Good -->
<?= htmlspecialchars($item['title']) ?>
<!-- Bad (XSS vulnerability) -->
<?= $item['title'] ?>
```
### Check Variables Before Use
```php
<?php if (isset($item['cover']) && $item['cover']): ?>
<img src="<?= $item['cover'] ?>" alt="">
<?php endif; ?>
```
### Use Null Coalescing
```php
$author = $metadata['author'] ?? 'Anonymous';
```
### Semantic HTML
```php
<!-- Good -->
<article>
<h2><a href="<?= $item['url'] ?>"><?= htmlspecialchars($item['title']) ?></a></h2>
<time datetime="2025-11-02"><?= $item['date'] ?></time>
</article>
<!-- Avoid -->
<div>
<span><a href="<?= $item['url'] ?>"><?= htmlspecialchars($item['title']) ?></a></span>
<span><?= $item['date'] ?></span>
</div>
```
### Accessibility
```php
<!-- Proper alt text -->
<img src="<?= $item['cover'] ?>" alt="<?= htmlspecialchars($item['title']) ?>">
<!-- ARIA labels -->
<nav aria-label="Main navigation">
<!-- Semantic elements -->
<main>
<header>
<footer>
<article>
<aside>
```
### Short Echo Tags
```php
<!-- Good (modern PHP) -->
<?= $variable ?>
<!-- Verbose -->
<?php echo $variable; ?>
```
### Keep Logic Minimal
Prepare data in functions, not templates:
```php
<!-- Avoid complex logic in templates -->
<?php
// Don't do heavy processing here
$processedData = someComplexFunction($items);
?>
<!-- Keep templates simple -->
<?php foreach ($items as $item): ?>
<?= htmlspecialchars($item['title']) ?>
<?php endforeach; ?>
```
## Common Patterns
### Card with Fallback Content
```php
<article class="card">
<?php if ($item['cover']): ?>
<img src="<?= $item['cover'] ?>" alt="">
<?php else: ?>
<div class="placeholder">No image</div>
<?php endif; ?>
<h2><?= htmlspecialchars($item['title'] ?? 'Untitled') ?></h2>
<p><?= htmlspecialchars($item['summary'] ?? 'No description available.') ?></p>
</article>
```
### Conditional Links
```php
<?php if ($item['redirect']): ?>
<a href="<?= $item['redirect'] ?>" target="_blank" rel="noopener">
External Link
</a>
<?php else: ?>
<a href="<?= $item['url'] ?>">
Read More
</a>
<?php endif; ?>
```
### Date Formatting
```php
<!-- Use provided formatted date -->
<?php if ($item['date']): ?>
<time><?= $item['date'] ?></time>
<?php endif; ?>
```
Date is already formatted in Norwegian style by FolderWeb.
## Debugging Templates
### Dump Variables
```php
<pre><?php var_dump($items); ?></pre>
<pre><?php var_dump($metadata); ?></pre>
<pre><?php var_dump($ctx); ?></pre>
```
### Check Template Resolution
Verify which template is used:
```php
<?php
$templatePath = resolveTemplate('base', 'templates');
echo "Using template: $templatePath";
?>
```
### PHP Error Reporting
Enable in development:
```php
<?php
ini_set('display_errors', 1);
error_reporting(E_ALL);
?>
```
## Related
- [Custom Templates Guide](../how-to/custom-templates.md)
- [Metadata Reference](metadata.md)
- [File Structure Reference](file-structure.md)
- [CSS Variables Reference](css-variables.md)