# Templatesyntax

Routetemplates, herbruikbare blokken, waarden en voorwaardelijke compositie.

Templates gebruiken shortcodes tussen `[#` en `#]`.

**Templates zijn bewust zonder logica.** Een template is schone, eenvoudige markup: hij toont waarden, herhaalt een template voor een lijst en kiest hooguit welke template volgt (`[#if … tpl=…#]`). Beslissingen, berekeningen en datavoorbereiding staan in libraries, waar je ze op één plek kunt lezen en testen. Er zijn geen logicablokken, lussen met voorwaarden of expressies in templates, zoals engines als Twig of Blade die toestaan; logica in markup mengen is precies wat Nimbly vermijdt.

```
[#shortcode#]
[#shortcode param#]
[#shortcode key=value key2=value2#]
```

Shortcodes kunnen genest worden: elke parameterwaarde of shortcodenaam kan zelf een shortcode bevatten:

```
[#get data.users.[#get selected-id#]#]
```

Dat werkt ook in `tpl=`-parameters en shortcodenamen, waarmee je **templatenamen dynamisch samenstelt**:

```
[#if current_image=(not-empty) tpl=card-image[#if current_image=(empty) echo=-placeholder#]#]
```

Dit wordt `tpl=card-image` als er een afbeelding is, en `tpl=card-image-placeholder` als die er niet is.

Een bijzonder krachtige toepassing is **kiezen op contenttype**: zet een typeveld direct in de naam van de shortcode om zonder vertakkingen naar een template voor dat type te gaan:

```
[#card-[#get item.type#]#]
```

Dit wordt `[#card-news#]`, `[#card-event#]`, `[#card-download#]` enzovoort, afhankelijk van het type van het item. Dit patroon maakt ketens van `[#if item.type=X tpl=Y#]` overbodig en groeit netjes mee als er types bijkomen.

Gebruik `default=` om een geldige shortcodenaam te garanderen als de waarde kan ontbreken of onbekend kan zijn; een shortcode die niet gevonden wordt, verschijnt als letterlijke tekst in de uitvoer:

```
[#card-[#get item.type default=unknown#]#]
```

Maak `[#card-unknown#]` als algemene terugvaloptie (of laat hem leeg om niets te tonen).

Templates zijn gewone bestanden in het beoogde uitvoerformaat (HTML, JSON, CSS, enzovoort). Ze hebben de extensie `.tpl`.

## Routetemplates

Elke URL hoort bij een bestand in `ext/uri/`:

```
ext/uri/index.tpl              → /
ext/uri/about/index.tpl        → /about/
ext/uri/blog/(slug)/index.tpl  → /blog/<anything>/
```

Een routetemplate maakt de pagina:

```
[#html#]
```

De `main.tpl` in dezelfde map bevat de body van de pagina, getoond binnen de HTML-shell. Die hoort altijd bij die route; zelfs `ext/uri/main.tpl` is alleen de body van de homepage (`/`), geen algemene layout.

## Herbruikbare templates

Staan in `ext/tpl/<name>/index.tpl`. Je roept ze aan met hun naam als shortcode:

```
[#hero-section#]
```

Templates krijgen **geen** parameters rechtstreeks mee. Om data aan een template te geven, gebruik je `[#set ... overwrite#]` vóór de aanroep:

```
[#set back_url=/agenda overwrite#]
[#back-button#]
```

Lees de variabele in de template met `[#get back_url default=/#]`. Library-shortcodes (`ext/lib/<name>/`) werken anders: die krijgen `$params` en kun je met parameters aanroepen.

## Naamgeving van templates

Geef elke herbruikbare template een naam met **eerst het type**, gevolgd door de route of het onderwerp waar hij bij hoort:

```
{type}-{route-or-subject}
```

Types en voorbeelden:

| Type | Gebruikt voor | Voorbeeld |
|------|----------|---------|
| `page-` | Template voor een hele pagina (één per route) | `page-about`, `page-projects` |
| `section-` | Een sectie of blok binnen een pagina | `section-home-header`, `section-page-header` |
| `card-` | Een herhaalbare kaart | `card-project`, `card-team-member` |
| `logo-` | Een component die een logo toont | `logo-partner`, `logo-partner-image` |
| `icon-` | Een inline icoon | `icon-arrow-right`, `icon-chevron-right` |
| `form-` | Een formuliercomponent | `form-registration` |

Subtemplates in een paginamap volgen dezelfde regel, eerst het type:

```
ext/tpl/page-projects/
  section-project-list.tpl
  card-project.tpl
  card-project-image.tpl
```

Zet het type **nooit** aan het eind (`project-card`, `about-page`). Met het type vooraan staan alle kaarten, alle secties en alle paginatemplates bij elkaar in het bestandssysteem.

## Templates binnen een route

Templates kunnen ook los in een URI-map staan (niet in een submap). Dat zijn geen routes, maar deeltemplates die vanuit de `index.tpl` of `main.tpl` van die route worden gebruikt.

```
ext/uri/new/page-settings-fields.tpl   ← partial, not a route
ext/uri/new/index.tpl                  ← route
ext/uri/new/main.tpl                   ← route body
```

Belangrijk: een submap in `ext/uri/` maakt **altijd** een nieuwe route. Een los `.tpl`-bestand in een URI-map is een deeltemplate voor die route.

## route.inc

`route.inc` wordt alleen gebruikt voor **dynamische routes**: routes met `(param)`-segmenten in de URL die gematcht, gevalideerd en geaccepteerd of geweigerd moeten worden. Statische routes (gewone URL-paden zonder parameters) gebruiken geen `route.inc`; hun `index.tpl` wordt direct geladen.

Dynamische routes worden **niet vanzelf gevonden**. Elke route met een `(param)`-segment heeft ook een record in de resource `.routes` nodig, anders bereikt het request de `route.inc` nooit en volgt een 404. Zo heeft `ext/uri/en/blog/(slug)/route.inc` een `.routes`-record nodig met `route: "en/blog/(slug)"` en een passende `order`, meestal `200`. Draai `php core/cli/nimbly.php routes:sync` om de bestanden `ext/uri/**/route.inc` te doorzoeken en ontbrekende `.routes`-records aan te maken.

Een `route.inc` staat naast `index.tpl` en bepaalt of het request bij deze route hoort:

```php
<?php
$parts = router_match(__FILE__);
if ($parts === false) return;

$slug = $parts[0];
load_library('data');
load_library('util');

$records = data_read_index('articles', 'title_slug', md5_uuid($slug));
if (empty($records)) return;

set_variable_dot('record', reset($records));
router_accept();
```

### Routerfuncties

| Functie | Beschrijving |
|---|---|
| `router_match(__FILE__)` | Vergelijkt de huidige URL met de dynamische segmenten van deze route. Geeft een array met de gevonden waarden (één per `(param)` in het pad), of `false` als de URL niet past. Roep deze altijd eerst aan. |
| `router_accept()` | Geeft aan dat deze route.inc het request accepteert. De template-engine gaat verder met `index.tpl`. |
| `router_deny()` | Geeft een weigering aan; de router zoekt verder naar een andere passende route. `return` zonder `router_accept()` heeft hetzelfde effect. |

Het standaardpatroon: roep `router_match()` aan, valideer het resultaat, laad en valideer de data, en roep alleen `router_accept()` aan als alles klopt. Mislukt er iets, doe dan gewoon `return`; het request valt door naar de volgende route of een 404.

### Valideer alle invoer van de route aan de grens

Vertrouw geen enkele waarde die via een URL binnenkomt. Dat geldt voor dynamische padsegmenten, GET- en queryparameters, headers die de route gebruikt, en elke waarde uit het request die via het variabelensysteem van de templates binnenkomt. Geautomatiseerde scanners sturen standaard SQL-fragmenten, shellsyntax, padtraversals, te grote getallen en andere fuzzing naar elke publieke parameter, ook al gebruikt Nimbly geen SQL.

Valideer en normaliseer deze waarden in de route voordat je ze aan een library geeft of ze gebruikt in berekeningen, bij het opzoeken van resources, in bestandspaden, cachesleutels, redirects of commando's:

- Gebruik bij voorkeur een allowlist voor de exacte syntax die je accepteert; probeer niet bekende slechte fragmenten weg te halen.
- Controleer type, lengte, bereik en toegestane waarden waar dat van toepassing is.
- Zet gevalideerde getallen om naar `int` of `float` voordat je ermee rekent.
- Houd ruwe tekst uit het request buiten bestands- en cachepaden, tenzij de volledige syntax met een allowlist is gecontroleerd en bewust deel uitmaakt van de cachesleutel.
- Weiger ongeldige invoer meteen met het normale foutantwoord van de route. Routes voor assets die op een API lijken, mogen een lege HTTP 400 geven als foute invoer zichtbaar moet zijn voor rate limiting of botdetectie.
- Valideer zelf in PHP, ook als Apache, een proxy of een CDN dezelfde parameter al filtert; filtering aan de rand is een optimalisatie, niet de vertrouwensgrens.

Een queryparameter `ratio` voor een thumbnail moet bijvoorbeeld als begrensd positief decimaal getal worden gevalideerd en naar float worden omgezet voordat er met de afbeelding wordt gerekend. Een tekst als `ratio=1' OR 1=1` mag nooit bij de berekening van een thumbnail of het aanmaken van een cachebestand komen.

Houd `route.inc` beperkt tot routinglogica: matchen, valideren, een variabele of twee zetten, accepteren of weigeren. Bedrijfslogica hoort in een library die vanuit `index.tpl` via een shortcode wordt geladen.

> **Voeg nooit een `route.inc` toe aan een statische route.** Een statische route (zoals `login/`, `about/`) heeft geen `(param)`-segmenten en wordt altijd geaccepteerd; een `route.inc` zonder `router_accept()` geeft een 404. Moet er logica draaien op een statische pagina, zet die dan in een shortcode die vanuit `index.tpl` wordt aangeroepen, niet in `route.inc`.

> **`route` is een gereserveerde shortcodenaam.** Er is geen shortcode `[#route#]` en die kan ook niet worden gemaakt. De bestandsnaam `route.inc` hoort alleen bij het routingsysteem.

---
