14 KiB
Template Reference
Complete reference for all templates and available variables in FolderWeb.
Template System Overview
FolderWeb uses a fallback template system:
- Check
/custom/templates/{name}.php - 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:
<!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:
<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:
[
'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 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:
<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:
<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 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
<!-- 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:
[
[
'title' => 'Blog',
'url' => '/blog/',
'order' => 1
],
[
'title' => 'About',
'url' => '/about/',
'order' => 2
],
// ...
]
Already sorted by menu_order field.
Translation Array
Structure of $translations:
[
'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:
$templatePath = resolveTemplate('base', 'templates');
$cssPath = resolveTemplate('base.css', 'styles');
htmlspecialchars()
Escape output (always use for user content):
<?= 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
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:
page_template = "my-custom-list"
Note: Omit .php extension.
Template Best Practices
Always Escape Output
<!-- Good -->
<?= htmlspecialchars($item['title']) ?>
<!-- Bad (XSS vulnerability) -->
<?= $item['title'] ?>
Check Variables Before Use
<?php if (isset($item['cover']) && $item['cover']): ?>
<img src="<?= $item['cover'] ?>" alt="">
<?php endif; ?>
Use Null Coalescing
$author = $metadata['author'] ?? 'Anonymous';
Semantic HTML
<!-- 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
<!-- 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
<!-- Good (modern PHP) -->
<?= $variable ?>
<!-- Verbose -->
<?php echo $variable; ?>
Keep Logic Minimal
Prepare data in functions, not templates:
<!-- 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
<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 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
<!-- 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
<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
$templatePath = resolveTemplate('base', 'templates');
echo "Using template: $templatePath";
?>
PHP Error Reporting
Enable in development:
<?php
ini_set('display_errors', 1);
error_reporting(E_ALL);
?>