# Publieke shortcodes

Data, variabelen, flow, content, URL's en publieke hulp-shortcodes.

## Data

### `[#data resource#]`
Laadt alle records van een resource in `data.<resource>`.

```
[#data articles#]
[#data articles sort=date|desc#]
[#data articles filter=published:1#]
[#data articles search=nimbly#]
[#data articles var=featured filter=featured:yes#]
[#data users.abc123#]           → loads single record into data.users.abc123
[#data users uuid=abc123#]      → same
[#data articles filter=published:1 sort=date|text|desc limit=20 offset=40#]   → the third page of 20
```

Parameters:
- `sort` - `field|asc`, `field|desc`, meerdere: `date|desc,title|asc`
- `filter` - `field:value`, meerdere: `published:1,status:new`, omkeren: `status:!draft`, of: `status:new||todo`
- `search` - zoeken in de volledige tekst van alle velden
- `var` - eigen naam voor de variabele in plaats van `data.<resource>`
- `op` - `read` (standaard) of `list` (alleen UUID's)
- `limit`, `offset` - één pagina van de lijst: hooguit `limit` records, na het overslaan van `offset`. Het aantal records vóór het afknippen staat in `data.<resource>-total` (of `<var>-total`)

Met `limit` of `offset` wordt de lijst beantwoord zonder elk record te lezen. Voor elk veld dat `filter` en `sort` noemen, staan de waarden van alle records samen in een cachebestand (`ext/data/.tmp/cache/_data/list/<resource>/<field>`), gemaakt de eerste keer dat om dat veld wordt gevraagd; alleen de records van de pagina worden gelezen. Een schrijfactie voegt een regel aan die bestanden toe, een Git-pull laat ze opnieuw opbouwen, en `./nimbly data:cache:clear` verwijdert ze. Gebruik dit voor elke lijst van een grote resource; een sortering met een type (`date|text|desc`, `price|num|asc`) is de snelle. `search` leest nog steeds elk record. Vanuit PHP: `data_query($resource, ['filter' => …, 'sort' => …, 'limit' => 20, 'offset' => 40])` in `core/lib/data-list.php` geeft `['records' => …, 'total' => …]`.

### `[#repeat data.articles#]`
Loopt een datavariabele langs en toont voor elk item de template met dezelfde naam.

```
[#repeat data.articles#]
[#repeat data.articles tpl=article-card#]
[#repeat data.articles limit=3#]
[#repeat data.articles filter=published:1#]
[#repeat record.related_items csv tpl=related-item#]
[#repeat data.articles var=post#]   → item variable named "post" instead of "item"
```

In de template zijn de velden van elk record beschikbaar als `item.<field>`:

```html
<h2>[#get item.title#]</h2>
<p>[#get item.intro#]</p>
```

Ook beschikbaar per herhaling:
- `item.ix` - numerieke index (vanaf 0)
- `item.x` - sleutel van het record
- `item.key` - sleutel van het record (string)

Maak voor verwijzingsvelden met komma-gescheiden UUID's geen library alleen om een deel te laden. Laad de doelresource één keer met `[#data#]`, herhaal dan de UUID-lijst met `csv` en lees uit de geladen resourcevariabele:

```html
[#data participants#]
[#repeat stories.participants csv tpl=story-participant-item#]
```

```html
<!-- ext/tpl/story-participant-item/index.tpl -->
<a href="[#get data.participants.[#item.key#].link#]">
    [#get data.participants.[#item.key#].name#]
</a>
```

Dit gebruikt de datalaag van core en zijn caching, houdt de volgorde van de UUID's in het bronrecord aan, en maakt eigen backendcode overbodig voor het eenvoudig tonen van relaties.

### `[#data-sort data.articles sort=title|string|asc#]`
Sorteert een bestaande datavariabele ter plekke.

```
[#data-sort data.articles sort=date|desc#]
[#data-sort data.articles sort=title|string|asc#]
[#data-sort data.articles sort=price|numeric|asc#]
```

### `[#data-join a b#]`
Voegt `data.a` en `data.b` samen in `data.join`. Elk record krijgt een veld `resource_type` met de naam van de bronresource.

### `[#data-count resource#]`
Toont het aantal records in een resource.

---

## Variabelen

### `[#set key=value#]`
Zet een templatevariabele.

```
[#set page-title="About us"#]
[#set active-nav=about#]
[#set body-classes=editor-role append#]    → appends with a space separator
[#set tags=news append=,#]                 → appends with a custom separator
[#set key=value session#]                  → persists in session
[#set key=value overwrite#]                → replaces value even if already set
```

De parameter `append` voegt toe in plaats van te zetten. Zonder scheidingsteken zet hij een spatie tussen de bestaande waarde en de nieuwe; geef een teken mee om een ander scheidingsteken te gebruiken. Handig om lijsten met CSS-classes of komma-gescheiden teksten op te bouwen:

```
[#set body-classes=site-body#]
[#set body-classes=editor-role append#]    → "site-body editor-role"
[#set body-classes=dark-mode append#]      → "site-body editor-role dark-mode"
```

**`[#set#]` overschrijft standaard niet.** Heeft de variabele al een waarde, dan doet een gewone `[#set#]` niets. Zo kun je makkelijk terugvalwaarden zetten in gedeelde of core-templates, terwijl routetemplates ze eerst kunnen vervangen. Voeg `overwrite` alleen toe als je een bestaande waarde bewust wilt vervangen (bijvoorbeeld om data aan een herbruikbare template mee te geven).

### `[#get varname#]`
Toont een waarde. `[#get#]` is de ene manier om waarden te lezen.

Volgorde van opzoeken:
1. Platte variabele: sessievariabelen, daarna systeemvariabelen. De URL en cookies worden alleen gelezen als de template daarom vraagt met `from` (zie hieronder)
2. Een pad met punten in geneste systeemvariabelen, met het langste passende begin van een variabele
3. Terugval op data voor paden `resource.uuid.field`
4. Automatisch de juiste taal als het resultaat een object is met de ingestelde taalcodes als sleutels

Invoer uit het request is opt-in: `from=url` leest de queryparameter met dezelfde naam, `from=cookie` een cookie, `from=url,cookie` (of `url|cookie`) beide in de opgegeven volgorde, en `from=(any)` de cookie en daarna de URL. Een paginavariabele met die naam wint altijd. `[#if#]` accepteert dezelfde `from` voor zijn voorwaarden.

```
[#get page-title#]
[#get item.title default="Untitled"#]
[#get q from=url#]
[#get language#]
[#get data.settings.theme#]
[#get users.abc123.name empty="Unknown"#]
[#get item.title lang=nl#]
[#get item json#]
```

Oudere patronen met `[#get-key#]`, `[#jget#]`, `[#get-i18n#]` en `[#lookup#]` moeten worden omgezet naar `[#get#]`.

### `[#fmt var=data.articles json#]`
Maakt een variabele op voor de uitvoer.

```
[#fmt var=data.records json#]              → JSON encode
[#fmt var=myvar empty={} json#]            → JSON with fallback
[#fmt var=item.date type=date fmt="d-m-Y"#]
[#fmt var=item.body type=html#]            → strips all tags
[#fmt var=item.body type=plain#]           → strips tags and decodes entities
[#fmt var=item.size type=bytes#]           → human-readable bytes
[#fmt var=item.hours type=hours#]          → 3h 30m, 6m, 37h (whole minutes)
[#fmt var=item.created type=ago#]          → "3 days ago"
[#fmt val=[#data-count users#] type=number round=-2 round_mode=floor#]
[#fmt var=item.invoice type=file#]         → file download markup
[#fmt var=item.order type=pad length=4#]   → zero-padded number
```

Typen: `text`, `plain`, `html`, `date`, `ago`, `json`, `bytes`, `hours`, `number`, `boolean`, `image`, `file`, `pad`

`var=` leest een variabele; `val=` neemt de waarde zelf, meestal een geneste shortcode. Het type is `type=`, of een los woord als laatste parameter: `[#fmt var=list empty={} json#]` codeert, `[#fmt var=list json empty={}#]` niet.

### `[#count varname#]`
Toont het aantal items in een arrayvariabele.

```
[#count data.articles#]
```

---

## Verloop

### `[#if condition action#]`

```
[#if user=(empty) redirect=login#]
[#if language=en tpl=intro-en#]
[#if status=published echo=yes#]
[#if user=(not-empty) tpl=dashboard tpl_else=login#]
[#if score=0 echo_else=[#get score#]#]
[#if not role=admin redirect=home#]
[#if lang=en or lang=nl tpl=western-layout#]
```

Voorwaarden: `key=value`, `key=(empty)`, `key=(not-empty)`
Acties: `tpl=`, `tpl_else=`, `echo=`, `echo_else=`, `redirect=`
Aanpassers: `not`, `or`, `and`

**`[#if#]` is altijd één zelfsluitende tag; er is geen blokvorm (`[#if#]...[#/if#]`), en dat is bewust.** Templates bevatten geen bedrijfslogica. Voorwaardelijke inhoud staat altijd in een aparte template die je met `tpl=` aanroept. Schrijf nooit een blokvorm `[#if#]…[#/if#]` in een template.

### `[#redirect url#]`
Stuurt meteen door.

```
[#redirect login#]
[#redirect [#base-url#]/dashboard#]
```

---

## URL en pad

### `[#base-url#]`
Geeft het basispad in de URL van de installatie. Gebruik dit voor alle interne links.

```html
<a href="[#base-url#]/about">About</a>
<link href="[#base-url#]/ext/static/app.css">
```

**In JavaScript** is `[#base-url#]` niet beschikbaar als templatetag. Gebruik in plaats daarvan `nb.base_url`; core zet die voordat scripts van de pagina draaien. Dat is belangrijk als je URL's voor afbeeldingen of API-paden in code opbouwt, vooral als de site onder een alias of submap draait (zoals staging). Schrijf `/img/` of `/api/` in JS nooit als los pad.

```js
// wrong - breaks on staging aliases
return '/img/' + uuid + '/240w';

// correct
return nb.base_url + '/img/' + uuid + '/240w';
```

### `[#base-path#]`
Geeft het pad in het bestandssysteem naar de hoofdmap van de installatie. Voor het invoegen van bestanden op de server.

### `[#url#]`
Geeft de volledige huidige URL.

### `[#is-url segment#]`
Zet `is-url` op `true` als de huidige URL met het opgegeven segment begint. Gebruik dit voor de actieve stand in de navigatie.

```
[#is-url about#]
[#if is-url=true echo=active#]

[#is-url (home) =#]   → exact match for homepage
```

## Content

### `[#get-html field#]`
Toont een bewerkbaar HTML-contentveld. Ondersteunt inline bewerken door beheerders.

```
[#get-html main_text#]
[#get-html content.home.intro default="<p>Edit this text</p>"#]
```

Links in content schrijf je vanaf de root van de site, `/contact` of `/en/article/deep-space`, zonder `[#base-url#]`. Draait de site in een submap (staging onder een alias), dan zet `get-html` het basispad ervoor als de pagina wordt getoond, en haalt de editor het er bij het opslaan weer af, zodat de bewaarde link op elke omgeving werkt. Een link die al met het basispad begint, en een link naar een andere site, blijven zoals ze zijn.

I18n-velden worden automatisch opgelost door `[#get#]` en `[#fmt#]` als de waarde een object is met de ingestelde taalcodes als sleutels. Gebruik `lang=` om een taal af te dwingen.

```
[#get item.title#]
[#get item.title lang=en#]
[#fmt var=item.title type=plain lang=nl#]
```

### `[#text Label_key#]`
Toont een vertaald label uit `.po`-bestanden. Gebruik underscores voor spaties in de sleutel.

```
[#text Search#]
[#text Save_changes#]
[#text No_results_found#]
```

Vertalingen staan in `ext/data/.i18n/text.<lang>.po`.

### `[#cfield field#]`
Bepaalt het volledige pad met punten van een contentveld in de huidige templatecontext. Wordt samen met `[#get-html#]` gebruikt voor vaste, inline bewerkbare paginacontent in de resource `.content`.

De resource `.content` bewaart bewerkbare HTML-blokken die niet bij een datarecord horen, per sleutel. Elk blok heet `content.<page>.<field>`. Wijst `[#get-html#]` naar een `.content`-pad dat nog niet bestaat, dan maakt hij het record vanzelf aan en toont hij de waarde `default`.

```html
<!-- In ext/tpl/about-intro/index.tpl -->
[#get-html content.about.intro default="<p>Edit this text</p>"#]
[#get-html content.about.body default="<p>Body text here</p>"#]
```

`[#cfield field#]` bepaalt het veldpad vanuit de huidige templatecontext; handig als dezelfde template op meerdere plekken wordt gebruikt:

```html
[#get-html [#cfield intro#] default="<p>Edit this</p>"#]
```

Dit patroon vereist dat de user-module actief is (wordt vanzelf geladen).

### `[#get-img-html uuid sizes="..." mode=w#]`
**De aanbevolen manier om afbeeldingen te tonen.** Maakt een responsieve `<img>` met `srcset`, `sizes`, lazy loading en grenzen die opschalen voorkomen. Gebruik dit altijd in plaats van een handgeschreven `<img>`-tag.

```
[#get-img-html [#record.main_img#] sizes="sm-100,md-50,lg-33"#]
```

**Parameter `sizes`**: komma-gescheiden paren van breakpoint en percentage. Elk deel is `breakpoint-vw`, waarbij `vw` het percentage van de schermbreedte is dat de afbeelding vanaf dat breakpoint inneemt. Weggelaten breakpoints vallen terug op `100vw`.

| Voorbeeld | Betekenis |
|---|---|
| `sm-100,md-50` | Volle breedte op mobiel, de helft vanaf md |
| `sm-100,md-33` | Volle breedte op mobiel, een derde vanaf md |
| `sm-100,md-50,lg-25` | Vol → half → een kwart |

**Parameter `mode`**: bepaalt hoe de server de afbeelding op maat maakt:

| Mode | Vorm van de URL | Resultaat |
|---|---|---|
| `w` (standaard) | `480w` | Op die breedte gebracht, hoogte in verhouding |
| `c` | `480c` | Op die hoogte gebracht, breedte in verhouding; goed voor layouts met gelijke hoogtes |
| `w?ratio=1.0` | `480w?ratio=1.0` | Vierkante uitsnede (midden); voor avatars en thumbnails |

Voorbeeld van een vierkante uitsnede:
```
[#get-img-html [#record.avatar#] sizes="sm-50,md-25" mode=w?ratio=1.0#]
```

De inline stijlen `max-width`/`max-height` op de gemaakte `<img>` voorkomen alleen opschalen; ze zitten CSS `object-fit: cover` of gridlayouts niet in de weg als afbeeldingen groter zijn dan hun weergave (en dat zijn ze bij echte content altijd).

---

## HTML-pagina

### `[#html#]`
Toont de volledige HTML-shell van de pagina (doctype, head, body). Zet hem aan het eind van routetemplates.

Deze variabelen beïnvloeden de HTML-shell als ze vóór `[#html#]` worden gezet:

| Variabele | Beschrijving |
|---|---|
| `page-title` | Zet de tag `<title>` en de titel voor OG/Twitter |
| `body-classes` | Voegt CSS-classes toe aan de tag `<body>` |
| `page-settings-link` | Laat de instellingenknop in het actiemenu van de pagina naar de bewerkpagina in de admin van het huidige record wijzen, in plaats van naar de modal met pagina-instellingen. Gebruik dit op detailpagina's voor snelle toegang tot de admin vanaf de site. Voorbeeld: `[#base-url#]/nb-admin/articles/[#record.uuid#]`. Het label wordt "Edit <resource>" (bijvoorbeeld "Edit article"). |
| `page-settings-label` | Vervangt dat label. |
| `page-actions` | Zet op `off` om het actiemenu te verbergen op een pagina zonder paginacontext. In de admin verschijnt het nooit. |
| `page-description` | Vervangt de meta-description van de hele site voor deze pagina. Valt terug op de description uit de siteconfiguratie. |
| `og-image` | Afbeelding om te delen op sociale media. Accepteert een **UUID** (uitgebreid tot een absolute URL `/img/UUID/1200w`), een **relatief pad** (`img/og-card.png`) of een **absolute URL**. Valt terug op de standaard van het project in `ext/tpl/meta/index.tpl`. |
| `og-type` | OG-type van deze pagina. Standaard `website`. Gebruik `article` voor detailpagina's met content. |
| `page-canonical-url` | Vervangt de genormaliseerde canonieke URL en de bijbehorende `og:url`. Zet hem vóór `[#html#]`. |

```
[#set page-title="[#get record.title#]"#]
[#set og-type=article#]
[#set og-image=[#record.main_img#]#]
[#set page-description="[#fmt var=record.main_text type=html max_length=160#]"#]
[#set page-settings-link="[#base-url#]/nb-admin/event/[#record.uuid#]"#]
[#set body-classes=site-body#]
[#html#]
```

De standaard OG-afbeelding van het project wordt één keer gezet in `ext/tpl/meta/index.tpl`, zonder `overwrite`, zodat waarden per pagina altijd voorgaan. Er zijn twee hulpmiddelen voor afbeeldingen als shortcode:

- `[#img-url UUID-or-path-or-URL#]` - maakt van elke verwijzing naar een afbeelding een absolute URL (handig buiten de meta, bijvoorbeeld voor JSON-LD)
- `[#first-img-uuid var=record.main_text#]` - haalt de UUID van de eerste ingevoegde afbeelding uit een HTML-veld; geeft `(empty)` als er geen is

## Resourcerecords importeren uit DOCX

Formulieren om records toe te voegen kunnen de core-actie **Import document** tonen. Die accepteert een `.docx`, haalt de alinea's en hyperlinks eruit, en stuurt de begrensde tekst van het document naar het ingestelde OpenAI-importendpoint om waarden voor de velden van de resource voor te stellen. Redacteuren bekijken het ingevulde formulier voordat ze opslaan; importeren maakt zelf geen record aan.

Het uitlezen van DOCX vereist de ZIP-extensie van PHP, omdat een Office Open XML-document een ZIP-archief is. Elke CLI- en web/FPM-runtime die de import of de tests ervan kan draaien, moet `ZipArchive` hebben. Op Debian en Ubuntu installeer je die meestal met het pakket `php-zip` van de passende versie; de Docker-images van Nimbly voor ontwikkeling en productie bevatten `php-zip` uitdrukkelijk. Controleer een handmatig ingerichte server met:

```bash
php -r 'echo class_exists("ZipArchive") ? "available\n" : "missing\n";'
php core/tests/docx-test.php
```

Leid de beschikbaarheid op het web niet af uit alleen de CLI als CLI en PHP-FPM aparte configuraties laden. Controleer de extensie na de installatie in beide runtimes en test de documentimport via de webapplicatie.

## PDF-documenten

Installeer de afhankelijkheid van de PDF-module van core met `php core/cli/nimbly.php module:install pdf`. Applicatieroutes leveren hun eigen HTML en gebruiken de PDF-library van core voor het maken, cachen op inhoud en veilig downloaden in de browser:

```php
load_library('pdf');
$pdf_bytes = pdf_render_cached_document([
    'paper_size' => 'a4',
    'orientation' => 'portrait',
    'margins' => ['top' => 20, 'right' => 18, 'bottom' => 20, 'left' => 18],
    'pages' => [$document_html],
]);
pdf_send_download($pdf_bytes, 'Document-123.pdf');
exit();
```

`pdf_render_document()` maakt altijd nieuwe bytes. Gebruik voor downloads bij voorkeur `pdf_render_cached_document()`: de cachesleutel bevat de volledige documentinstellingen en de bestanden van de renderer in core, dus gewijzigde content of rendercode maakt de cache vanzelf ongeldig. Bouw geen tijdstempels van resources of lijsten met templateafhankelijkheden in losse routes. `pdf_send_download()` maakt de bestandsnaam veilig en stuurt headers voor een privé PDF-download die de browser niet cachet.

---

## Hulpmiddelen

### `[#include file=path#]`
Voegt een bestand in op de server. Het pad moet binnen het project liggen; anders toont hij niets.

```
[#include file=[#base-path#]ext/tpl/head-scripts/index.tpl#]
```

Normaliseren van UUID, salt, slug, MD5 en resourcesleutels, en het weghalen van opmaak tot platte tekst, zijn PHP-hulpfuncties in `core/lib/util.php`, geen shortcodes. Gebruik ze vanuit PHP-libraries, routebestanden, installatiescripts en CLI-scripts:

```php
load_library('util');

$uuid = generate_uuid();
$salt = generate_salt();
$slug = make_slug($title);
$hash = md5_uuid($slug);
$text = plain_text($html);
```

### `[#env KEY default=value#]`
Geeft een waarde uit `.env`, met `default` als de sleutel ontbreekt. De instellingen die core geheim houdt (`PEPPER`, `STATS_KEY`, `ADMIN_PASSWORD`, `SMTP_PASSWORD`, `AGENT_REMOTE_PASSWORD`, `OPENAI_API_KEY`, `RESEND_API_KEY`, `GIT_TOKEN`) worden nooit getoond: lees die in PHP met `env()`.

```
[#env STRIPE_LINK_ANNUAL#]
[#env MAIL_SERVICE default=resend#]
```

### `[#logged-in#]`
Geeft `"logged-in"` als er een gebruikerssessie actief is.

```
[#if logged-in=(empty) redirect=login#]
```

### `[#feature-cond features=name#]`
Toont inhoud afhankelijk van of de huidige gebruiker een bepaald recht heeft.

```
[#feature-cond features=edit-articles tpl=edit-button#]
[#feature-cond features=view-admin-dashboard echo="<a href='/nb-admin'>Admin</a>"#]
[#feature-cond features=view-admin-dashboard tpl=admin-panel tpl_else=access-denied#]
[#feature-cond features=edit-articles,view-reports tpl=dashboard#]
```

Parameters:
- `features` - komma-gescheiden lijst van rechten; toegang wordt gegeven als de gebruiker **een** daarvan heeft, of `(all)` (admin)
- `tpl` - template die wordt getoond bij toegang
- `tpl_else` - template die wordt getoond zonder toegang
- `echo` - tekst die wordt getoond bij toegang
- `echo_else` - tekst die wordt getoond zonder toegang

Rechten hebben de vorm `<operation>-<resource_id>`, bijvoorbeeld `view-articles`, `create-products`, `edit-.content` of `delete-users`. Verborgen resource-id's houden hun punt vooraan in bewaarde rechten. `manage-<resource_id>` geeft alle standaardhandelingen voor die ene resource. De rol admin heeft altijd `(all)`, die alle rechtencontroles overslaat.

Rechten worden aan rollen toegekend in `/nb-admin/roles/`. Gebruik het rechtenscherm van de rol om rechten met vinkjes te bewerken; eigen tokens kunnen nog steeds worden bewaard voor controles die alleen dit project nodig heeft. Oudere tokens zoals `manage-content`, `(any)`, `get_<resource>`, `add-<resource>` en `(any)_<resource>` worden bij het laden van de sessie en bij de upgrade-migratie omgezet, maar nieuwe code gebruikt de standaardvorm.

### `[#date input fmt=Y-m-d#]`
Maakt een datum op. De invoer kan een Unix-tijdstempel zijn, een datumtekst, of weggelaten (dan vandaag). Gebruik `fmt=full`, `long`, `medium` of `short` voor leesbare uitvoer in de taal van de pagina. Elke andere waarde van `fmt` gebruikt de datumnotatie van PHP voor een exacte technische vorm. Geef `lang=` mee om de gevonden taal te vervangen.

```
[#date fmt=d-m-Y#]                        → today in d-m-Y format
[#date item.date fmt="j F Y"#]            → formats item.date
[#date 1741265734 fmt=Y-m-d#]             → formats Unix timestamp
[#date item.date fmt=long#]               → locale-aware long date
```

`[#fmt var=item.date type=date fmt=long#]` gebruikt dezelfde datumopmaak. Benoemde formaten gebruiken de extensie `intl` van PHP als die er is, en vallen zonder de pagina te breken terug op vaste Engelse PHP-formaten.

### `[#get-first dataset#]`
Zet het eerste item van een dataset in `first` (of een eigen variabele).

```
[#get-first data.articles#]
[#get first.title#]

[#get-first data.articles var=latest#]
[#get latest.date#]
```

### `[#last-update#]`
Geeft een Unix-tijdstempel van het laatst gewijzigde bronbestand in `ext/` en `core/`. Handig met `[#fmt type=ago#]` om te tonen wanneer de site voor het laatst is bijgewerkt.

```
[#fmt val=[#last-update#] type=ago#]   → "3 days ago"
[#fmt val=[#last-update#] type=date fmt=d-m-Y#]
```

### `[#obfuscate text#]`
Toont tekst met onzichtbare Unicode-tekens en Alpine.js, zodat bots en scrapers hem niet zien maar gebruikers hem wel kunnen lezen. Gebruik dit voor e-mailadressen en telefoonnummers.

```
[#obfuscate info@example.com#]
```

### `[#uri-path#]`
Geeft het pad in het bestandssysteem van de map van de huidige route.

### `[#url-key#]`
Geeft een genormaliseerde sleutel voor de huidige URL, handig voor body-classes of om in JS de pagina te herkennen.

```
[#set body-classes=[#url-key#]#]
```

`/admin/users/` → `admin_users`, homepage → `_home`.

### `[#http-header type#]`
Stuurt een Content-Type-header voor het antwoord. Gebruik dit bovenaan routes die geen HTML teruggeven.

```
[#http-header json#]
[#http-header css#]
[#http-header csv#]
```

Typen: `css`, `js`, `json`, `xml`, `txt`, `woff`, `csv`, `403`, `404`, `500`.

Een tweede parameter stuurt ook headers voor lang cachen mee (`Cache-Control: private`, `Expires`, `Last-Modified`) en antwoordt `304` op een voorwaardelijk request: `[#http-header css cached#]`.

### `[#system-messages#]`
Toont systeemmeldingen die klaarstaan (op de server gezet via de sessie). Gebruik dit in de HTML-template of layout om korte meldingen te tonen.

### `[#empty-img#]`
Geeft een transparante GIF van 1×1 als data-URI. Gebruik dit als tijdelijke `src` voordat er een echte afbeelding is.

### `[#max-upload-size#]`
Toont de PHP-waarde `upload_max_filesize` in leesbare vorm. Handig als hint bij uploadformulieren.

### `[#unquote varname#]`
Toont de waarde van een variabele met aanhalingstekens als HTML ge-escapet (`"` → `&quot;`, `'` → `&apos;`). Veilig om waarden in HTML-attributen te zetten.

```html
<div x-data='{"title": "[#unquote item.title#]"}'></div>
```

### `[#collect-script path#]`
Stelt het invoegen van een script uit, zodat alle scripts samen aan het eind van de pagina komen, zonder dubbele. Roep hem zonder argumenten aan om alle verzamelde scripts te tonen.

```
[#collect-script [#base-url#]/ext/static/chart.js#]
...
[#collect-script#]   ← renders all collected scripts here
```

### `[#is-dev-env#]`
Toont `DEV` of `PROD`. Handig voor debuguitvoer of gedrag per omgeving.

```
[#if [#is-dev-env#]=DEV tpl=debug-panel#]
```

### `[#detect-language#]`
Geeft de code van de actieve taal. Volgorde van herkennen:
1. Begin van de URL (`/en/`, `/nl/`)
2. Voorkeur van de gebruiker (`?lang=en`)
3. TLD van het domein (`.en`, `.nl`)
4. Taalheader van de browser
5. Terugval: de eerste taal in de siteconfiguratie

### `[#debug#]`
Toont debuginformatie. Alleen tijdens ontwikkeling gebruiken.

```
[#debug#]
[#debug variables session#]
```

### `[#log message#]`
Schrijft naar `ext/data/.tmp/logs/system.log`.

### `[#nop#]`
Doet niets. Gebruik dit om shortcodes tijdelijk uit te zetten.

## Indeling van de shortcode-API

De lijst hieronder is gemaakt uit de implementaties `*_sc()` in `core/lib` en `core/modules/*/lib`, en vergeleken met het gebruik in lokale `ext/`-code. Publieke shortcodes zijn stabiel voor projecttemplates. Interne en admin-shortcodes worden ondersteund voor core-templates en modules, maar zijn geen API voor projecten.

| Indeling | Shortcodes |
|---|---|
| Publiek core | `base-path`, `base-url`, `cfield`, `collect-script`, `count`, `data`, `data-count`, `data-join`, `data-last-update`, `data-sort`, `date`, `debug`, `detect-language`, `empty-img`, `env`, `fmt`, `get`, `get-first`, `get-html`, `http-header`, `if`, `include`, `is-url`, `last-update`, `log`, `logged-in`, `max-upload-size`, `module`, `nop`, `obfuscate`, `redirect`, `repeat`, `set`, `system-messages`, `text`, `unquote`, `uri-path`, `url`, `url-key` |
| Publiek module | `access`, `api-allow`, `build-form`, `feature-cond`, `first-img-uuid`, `form-key`, `get-form-errors`, `get-img-html`, `get-user`, `honeypot-field`, `img-url`, `json2post`, `logout`, `post`, `role-cond`, `role-switch`, `sanitize`, `sticky`, `userfield`, `username`, `validate` |
| Intern/admin | `api-session`, `api-token`, `disk-space-resource`, `exif`, `field-name`, `files`, `files-unused`, `files-usage`, `find`, `get-resource-meta`, `get-resource-record`, `get-resource-records`, `get-system-log`, `get-user-resources`, `json`, `openai-complete`, `openai-translate`, `pages`, `render-field`, `resource-name`, `resources`, `sys-messages`, `thumbnail` |

---

## Meer publieke interfaces

Deze notities beschrijven publieke shortcodes uit de lijst van het framework die hierboven niet zijn uitgewerkt. Gebruik de aparte gidsen voor complete werkwijzen.

| Shortcode | Gedrag |
|---|---|
| `[#module forms#]` | Doet niets: modules worden bij elk request gevonden. Oudere templates bevatten hem nog; hij mag weg. |
| `[#data-last-update events#]` | Geeft de tijdstempel van het laatst gewijzigde bestand van de resource, of nul als de resource niet bestaat. |
| `[#access feature=view-events#]` | Dwingt een recht af vóór beschermde uitvoer; zonder toegang volgt een doorverwijzing naar het ingestelde weigeradres. |
| `[#api-allow method=post resource=applications#]` | Staat één beperkte handeling op een resource toe voor een route en voert die uit, met de publieke controles op de requestsleutel van het framework; gebruik hem niet om rechten op beschermde resources te omzeilen. |
| `[#role-cond roles=editor tpl=editor-tools tpl_else=visitor-tools#]` | Toont de gekozen template als een van de genoemde rollen actief is, anders de optionele terugval. |
| `[#role-switch tpl=account-panel#]` | Toont een template met het achtervoegsel van de eerste actieve rol, of `.anonymous` zonder sessie. |
| `[#get-user var=user#]` | Laadt het record van de huidige gebruiker in een variabele; `uuid=` kiest uitdrukkelijk een record. Controleer de rechten voordat je velden van een andere gebruiker toont. |
| `[#userfield name#]` | Geeft één veld van de huidige gebruiker; een expliciete UUID kan een ander record kiezen en vraagt passende rechtencontrole in de aanroepende flow. |
| `[#username#]` | Geeft de naam van de ingelogde gebruiker, of `anonymous`. |
| `[#logout#]` | Wist de sessie van de huidige gebruiker en stuurt door; `redirect=` kiest de bestemming. |
| `[#form-key contact#]` | Toont verborgen velden voor de requestsleutel en het formulier; `plain` geeft de sleutel, en `x-init` zet de status van het formulier in de browser klaar. |
| `[#honeypot-field#]` | Toont het ingestelde verborgen honeypotveld voor een publiek formulier. |
| `[#post#]` | Verwerkt een POST met sleutel via de validatie- en verwerkingsbestanden van de route. Validatie is verplicht vóór de verwerking. |
| `[#get-form-errors email#]` | Laadt de validatiemeldingen van het veld in `validation_errors`; zonder argument de algemene fouten. |
| `[#sticky email default=you@example.com#]` | Haalt een formulierwaarde terug uit de verstuurde of bewaarde status, om opnieuw te tonen. |
| `[#json2post#]` | Zet een verstuurd JSON-object om in POST-waarden van PHP voor een bestaande verwerker. |
| `[#first-img-uuid var=record.body#]` | Vindt de UUID van de eerste afbeelding in opgemaakte content. |
| `[#img-url image-reference#]` | Maakt van een UUID, pad of afbeeldings-URL een absolute afbeeldings-URL. |

### Grenzen van validatie en opschonen

De lijst bevat nu `validate` en `sanitize`, maar die shortcodes valideren of schonen niets op. Vertrouw er niet op dat het aanroepen van die tags invoer beschermt. Gebruik de echte validatiefuncties in PHP en de verwerking van velden en API door het framework, zoals beschreven in [Formulieren](/nl/docs/guides/forms/) en [PHP-uitbreidingspunten](/nl/docs/reference/php/).

`feature-cond` en presentatie op basis van rollen kiezen welke templates worden getoond. Backendverwerkers controleren nog steeds de rechten voor elke beschermde handeling.
