v2.6.3 (Lyra): Content multi-type handling, getAllPages() structuur, . verberg-prefix

- 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
This commit is contained in:
2026-08-20 16:45:53 +00:00
parent 97c4d52c78
commit 6485f693dc
28 changed files with 1481 additions and 950 deletions
+135 -49
View File
@@ -1,84 +1,132 @@
# Content API
The Content API is available in PHP content files (`.php`) and provides a safe, read-only interface to CMS data.
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.
## Usage in PHP content files
## 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:
1. **Echo / print** — everything you echo (or that sits outside `<?php ?>` tags)
is captured and placed as `{{ content }}` in the layout.
2. **Return** — if you `return` a string, that string is used as `{{ content }}`
(any echoed output is then ignored).
Example with echo:
```php
---
layout: full_content
---
<?php
/** @var ContentAPI $api */
// Get all pages
$allPages = $api->getAllPages();
// Result: ['index' => 'Home', 'about-us' => 'About Us', ...]
// Get a specific page
$page = $api->getPage('about-us');
// Result: ['title' => 'About Us', 'content' => '...', 'path' => 'about-us', 'layout' => 'full_content', 'metadata' => [...]]
// Get menu structure
$menu = $api->getMenu();
// Result: [['title' => 'Home', 'path' => 'index', 'type' => 'file', 'active' => true], ...]
// Get config value (dot notation)
$siteTitle = $api->getConfig('site_title');
$searchEnabled = $api->getConfig('features.search_enabled', false);
?>
<h1>Hello <?= htmlspecialchars($api->getSiteTitle()) ?></h1>
<p>Welcome to my page.</p>
```
## Available methods
Example with return:
```php
---
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 `.php` file. 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 pages as `['path' => 'title']` pairs
- `getPage(string $path): ?array` - Specific page with `title`, `content`, `path`, `layout`, `metadata`
- `pageExists(string $path): bool` - Check if a page exists
- `getCurrentPageTitle(): string` - Title of current page
- `getCurrentPagePath(): string` - Path of current page
- `isHomepage(): bool` - Whether current page is the homepage
- `getAllPages(): array` All content entries (folders + files) as a list of `['path' => ..., 'title' => ..., 'type' => ...]`. `type` is `md`/`php`/`html` for files, `folder` for directories.
- `getPage(string $path): ?array` — A specific page; returns `title`, `content`, `path`, `layout`, `metadata`. `$path` may include an extension.
- `pageExists(string $path): bool` Check whether a page exists
- `getCurrentPageTitle(): string` Title of the current page
- `getCurrentPagePath(): string` Path of the current page
- `isHomepage(): bool` Whether the current page is the homepage
### Menu & Navigation
### Menu & navigation
- `getMenu(): array` - Hierarchical menu structure with `title`, `path`, `children`, `active`
- `buildUrl(string $page = 'index', ?string $lang = null, array $params = []): string` - Build a URL for a page
- `getMenu(): array` Hierarchical menu structure with `title`, `path`, `children`, `active`
- `buildUrl(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
- `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 languages (e.g. `['nl', 'en']`)
- `t(string $key): string` - Translate a language key
- `getCurrentLanguage(): string` Current language code (e.g. `'nl'`)
- `getAvailableLanguages(): array` Available languages
- `t(string $key): string` Translate a language key
### Search
- `getSearchResults(): array` - Search results (empty if not searching)
- `isSearching(): bool` - Whether a search is currently active
- `getSearchResults(): array` Search results (empty if not searching)
- `isSearching(): bool` Whether a search is currently active
## Example: Show recent pages
### Author
- `getPageAuthor(): array` — Author metadata from frontmatter (`author_name`, `author_email`, `created`)
## Examples
### Show recent pages
```php
---
layout: full_content
---
<?php
/** @var ContentAPI $api */
$pages = $api->getAllPages();
$entries = $api->getAllPages();
$currentLang = $api->getCurrentLanguage();
?>
<ul>
<?php foreach (array_slice($pages, 0, 5, true) as $path => $title): ?>
<?php foreach ($entries as $entry): ?>
<?php if ($entry['type'] === 'folder') continue; // skip folders ?>
<li>
<a href="/<?= $currentLang ?>/<?= htmlspecialchars($path) ?>">
<?= htmlspecialchars($title) ?>
<a href="/<?= htmlspecialchars($currentLang) ?>/<?= htmlspecialchars($entry['path']) ?>">
<?= htmlspecialchars($entry['title']) ?>
<small>(<?= htmlspecialchars($entry['type']) ?>)</small>
</a>
</li>
<?php endforeach; ?>
</ul>
```
## Example: Using config values
### Use a config value
```php
---
layout: full_content
---
<?php
/** @var ContentAPI $api */
if ($api->getConfig('features.search_enabled', false)): ?>
@@ -89,6 +137,44 @@ if ($api->getConfig('features.search_enabled', false)): ?>
<?php endif; ?>
```
### Dynamic greeting based on language
```php
---
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:
```yaml
---
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`, `require` or `file_get_contents` on external paths. Stay inside
the `content/` 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:
@@ -108,12 +194,12 @@ $this->api->createUrl('about-us');
$this->api->translate('home');
```
Additionally, CMSAPI provides:
In addition, CMSAPI has:
- `getCurrentPage(): array` - Full page data
- `getCurrentPageUrl(): string` - URL of current page
- `getCurrentPageFileInfo(): ?array` - File info (created, modified)
- `getBreadcrumb(): string` - Breadcrumb HTML
- `executePhpFile(string $filePath): string` - Execute PHP file and capture output
- `getFileContent(string $filePath): string` - Get content from PHP/HTML/Markdown file
- `contentFileExists(string $filename): bool` - Check if file exists in content directory
- `getCurrentPage(): array` Full page data
- `getCurrentPageUrl(): string` URL of the current page
- `getCurrentPageFileInfo(): ?array` File info (created, modified)
- `getBreadcrumb(): string` Breadcrumb HTML
- `executePhpFile(string $filePath): string` Execute a PHP file and capture output
- `getFileContent(string $filePath): string` Get content from a PHP/HTML/Markdown file
- `contentFileExists(string $filename): bool` Check whether a file exists in the content directory