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

14 KiB

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:

<!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>
<?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);
?>