- Content bestanden met dezelfde naam maar ander type (md/php/html) worden correct geserveerd: URL met extensie opent dat bestand, URL zonder extensie valt terug op md > php > html (resolveContentByType helper) - Admin content editor accepteert bestanden met dezelfde naam (ander type); preview-knop linkt per extensie - Frontend navigatie/directory listing/search tonen elk bestandstype apart - getAllPages() array structuur gewijzigd naar list van ['path','title','type'] met type 'md'/'php'/'html'/'folder' - Verberg-prefix logica: _ is geen verberg-prefix meer, alleen . (en -); admin toont wél alle . bestanden/mappen - ContentAPI getPage()/pageExists() respecteren expliciete extensie - Handleiding content-api.md (NL+EN) herschreven - File-tree unificatie: _file-tree.twig + _editor-styles.twig includes - Versie verhoogd naar 2.6.3
6.4 KiB
Content API
PHP content files (.php) run inside the CMS and have access to a safe,
read-only API via the $api variable. The output of such a file is
automatically placed into the active Twig layout at the {{ content }} spot.
How to pass content to the Twig template
There are two ways to send content from a .php file to the Twig template.
The CMS picks the right one automatically:
- Echo / print — everything you echo (or that sits outside
<?php ?>tags) is captured and placed as{{ content }}in the layout. - Return — if you
returna string, that string is used as{{ content }}(any echoed output is then ignored).
Example with echo:
---
layout: full_content
---
<?php
/** @var ContentAPI $api */
?>
<h1>Hello <?= htmlspecialchars($api->getSiteTitle()) ?></h1>
<p>Welcome to my page.</p>
Example with return:
---
layout: full_content
---
<?php
/** @var ContentAPI $api */
return '<h1>Hello ' . htmlspecialchars($api->getSiteTitle()) . '</h1>';
The frontmatter (
---block) is stripped by the CMS before the PHP code runs. The layout key determines which Twig template surrounds the content.
What you cannot do
- You cannot set your own Twig variables from a
.phpfile. PHP content only feeds the{{ content }}placeholder. Other template variables (menu,breadcrumb,page_title, etc.) are set by the CMS based on frontmatter and config — not by your PHP code. - You cannot call a ContentAPI from the outside; the class is only
instantiated inside
parsePHP()and is never reachable via a URL.
Available variables in your PHP file
The following variables are available inside a .php content file:
| Variable | Type | Description |
|---|---|---|
$api |
ContentAPI |
Read-only access to CMS data |
$pageMetadata |
array |
The frontmatter metadata of this file |
ContentAPI methods
Pages
getAllPages(): array— All content entries (folders + files) as a list of['path' => ..., 'title' => ..., 'type' => ...].typeismd/php/htmlfor files,folderfor directories.getPage(string $path): ?array— A specific page; returnstitle,content,path,layout,metadata.$pathmay include an extension.pageExists(string $path): bool— Check whether a page existsgetCurrentPageTitle(): string— Title of the current pagegetCurrentPagePath(): string— Path of the current pageisHomepage(): bool— Whether the current page is the homepage
Menu & navigation
getMenu(): array— Hierarchical menu structure withtitle,path,children,activebuildUrl(string $page = 'index', ?string $lang = null, array $params = []): string— Build a URL
Configuration
getConfig(string $key, mixed $default = null): mixed— Config value via dot notation (e.g.'features.search_enabled')getSiteTitle(): string— Site title from config
Language
getCurrentLanguage(): string— Current language code (e.g.'nl')getAvailableLanguages(): array— Available languagest(string $key): string— Translate a language key
Search
getSearchResults(): array— Search results (empty if not searching)isSearching(): bool— Whether a search is currently active
Author
getPageAuthor(): array— Author metadata from frontmatter (author_name,author_email,created)
Examples
Show recent pages
---
layout: full_content
---
<?php
/** @var ContentAPI $api */
$entries = $api->getAllPages();
$currentLang = $api->getCurrentLanguage();
?>
<ul>
<?php foreach ($entries as $entry): ?>
<?php if ($entry['type'] === 'folder') continue; // skip folders ?>
<li>
<a href="/<?= htmlspecialchars($currentLang) ?>/<?= htmlspecialchars($entry['path']) ?>">
<?= htmlspecialchars($entry['title']) ?>
<small>(<?= htmlspecialchars($entry['type']) ?>)</small>
</a>
</li>
<?php endforeach; ?>
</ul>
Use a config value
---
layout: full_content
---
<?php
/** @var ContentAPI $api */
if ($api->getConfig('features.search_enabled', false)): ?>
<form method="GET" action="">
<input type="search" name="search" placeholder="<?= $api->t('search_placeholder') ?>">
<button type="submit"><?= $api->t('search_button') ?></button>
</form>
<?php endif; ?>
Dynamic greeting based on language
---
layout: full_content
---
<?php
/** @var ContentAPI $api */
$lang = $api->getCurrentLanguage();
$greeting = $lang === 'nl' ? 'Welkom' : 'Welcome';
?>
<h1><?= $greeting ?> to <?= htmlspecialchars($api->getSiteTitle()) ?></h1>
Choosing a layout
The frontmatter layout key determines which Twig template surrounds your
content. Available layouts are defined in themes/<active-theme>/theme.json
under the template section. For example:
---
layout: sidebar-content
---
If you omit a layout, config.default_template is used, falling back to
full_content. The content always lands at the {{ content }} spot in that
template.
Security
- Always use
htmlspecialchars()for output of user-content or API data. - PHP content files have access to the server filesystem — be careful with
include,requireorfile_get_contentson external paths. Stay inside thecontent/directory. - The ContentAPI is read-only; you cannot modify files or config through it.
CMSAPI (for plugins)
Plugins use the CMSAPI class via $this->api. It offers similar methods:
$this->api->getCurrentPageTitle();
$this->api->getCurrentPageContent();
$this->api->getMenu();
$this->api->getConfig('site_title');
$this->api->getCurrentLanguage();
$this->api->isHomepage();
$this->api->hasContent();
$this->api->getSearchResults();
$this->api->isSearching();
$this->api->getAvailableLanguages();
$this->api->createUrl('about-us');
$this->api->translate('home');
In addition, CMSAPI has:
getCurrentPage(): array— Full page datagetCurrentPageUrl(): string— URL of the current pagegetCurrentPageFileInfo(): ?array— File info (created, modified)getBreadcrumb(): string— Breadcrumb HTMLexecutePhpFile(string $filePath): string— Execute a PHP file and capture outputgetFileContent(string $filePath): string— Get content from a PHP/HTML/Markdown filecontentFileExists(string $filename): bool— Check whether a file exists in the content directory