608 lines
14 KiB
Markdown
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)
|