ContentAPI voor PHP content bestanden + handleiding in admin
- Nieuwe ContentAPI class beschikbaar als $api in PHP content bestanden met methodes: getAllPages, getPage, getMenu, getConfig, buildUrl, etc. - Admin handleiding pagina op /admin/guide met taalwisselaar - Zijbalk link naar handleiding in admin menu - Dubbele alert in config pagina verwijderd - Handleidingen (nl/en) uitgebreid met Content API referentie
This commit is contained in:
@@ -76,6 +76,11 @@ $layoutSidebarColor = $layoutThemeConfig['header_color'] ?? '#0a369d';
|
|||||||
<i class="bi bi-people"></i> Gebruikers
|
<i class="bi bi-people"></i> Gebruikers
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link <?= ($route ?? '') === 'guide' ? 'active' : '' ?>" href="/admin/guide">
|
||||||
|
<i class="bi bi-book"></i> Handleiding
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
<li class="nav-item mt-3">
|
<li class="nav-item mt-3">
|
||||||
<a class="nav-link" href="/" target="_blank">
|
<a class="nav-link" href="/" target="_blank">
|
||||||
<i class="bi bi-box-arrow-up-right"></i> Website bekijken
|
<i class="bi bi-box-arrow-up-right"></i> Website bekijken
|
||||||
@@ -152,6 +157,9 @@ $layoutSidebarColor = $layoutThemeConfig['header_color'] ?? '#0a369d';
|
|||||||
case 'users':
|
case 'users':
|
||||||
require __DIR__ . '/pages/users.php';
|
require __DIR__ . '/pages/users.php';
|
||||||
break;
|
break;
|
||||||
|
case 'guide':
|
||||||
|
require __DIR__ . '/pages/guide.php';
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -1,12 +1,5 @@
|
|||||||
<h2 class="mb-4"><i class="bi bi-sliders"></i> Configuratie</h2>
|
<h2 class="mb-4"><i class="bi bi-sliders"></i> Configuratie</h2>
|
||||||
|
|
||||||
<?php if ($message): ?>
|
|
||||||
<div class="alert alert-<?= $messageType ?> alert-dismissible fade show">
|
|
||||||
<?= htmlspecialchars($message) ?>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
||||||
</div>
|
|
||||||
<?php endif; ?>
|
|
||||||
|
|
||||||
<form method="POST" action="/admin/config">
|
<form method="POST" action="/admin/config">
|
||||||
<input type="hidden" name="csrf_token" value="<?= $csrf ?>">
|
<input type="hidden" name="csrf_token" value="<?= $csrf ?>">
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<h2 class="mb-4"><i class="bi bi-book"></i> Handleiding</h2>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="btn-group" role="group">
|
||||||
|
<a href="/admin/guide?lang=nl" class="btn btn-sm <?= $lang === 'nl' ? 'btn-primary' : 'btn-outline-primary' ?>">Nederlands</a>
|
||||||
|
<a href="/admin/guide?lang=en" class="btn btn-sm <?= $lang === 'en' ? 'btn-primary' : 'btn-outline-primary' ?>">English</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-body guide-content">
|
||||||
|
<?= $content ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.guide-content h2 { margin-top: 1.5rem; }
|
||||||
|
.guide-content h3 { margin-top: 1.25rem; }
|
||||||
|
.guide-content pre { background: #f4f4f4; padding: 1rem; border-radius: 4px; overflow-x: auto; }
|
||||||
|
.guide-content code { background: #f0f0f0; padding: 0.15rem 0.4rem; border-radius: 3px; font-size: 0.9em; }
|
||||||
|
.guide-content table { width: 100%; margin-bottom: 1rem; }
|
||||||
|
.guide-content table th, .guide-content table td { padding: 0.5rem; border: 1px solid #dee2e6; }
|
||||||
|
.guide-content blockquote { border-left: 3px solid #ccc; padding-left: 1rem; color: #666; margin-left: 0; }
|
||||||
|
</style>
|
||||||
@@ -783,7 +783,8 @@ class CodePressCMS {
|
|||||||
$title = $metadata['title'] ?? '';
|
$title = $metadata['title'] ?? '';
|
||||||
|
|
||||||
ob_start();
|
ob_start();
|
||||||
// Make metadata available to the included file
|
// Make API and metadata available to the included file
|
||||||
|
$api = new ContentAPI($this);
|
||||||
$pageMetadata = $metadata;
|
$pageMetadata = $metadata;
|
||||||
include $filePath;
|
include $filePath;
|
||||||
$content = ob_get_clean();
|
$content = ob_get_clean();
|
||||||
@@ -1011,7 +1012,6 @@ class CodePressCMS {
|
|||||||
$this->pluginManager->doAction('onBeforeRender');
|
$this->pluginManager->doAction('onBeforeRender');
|
||||||
|
|
||||||
$menu = $this->getMenu();
|
$menu = $this->getMenu();
|
||||||
$breadcrumb = $this->generateBreadcrumb();
|
|
||||||
|
|
||||||
// Get homepage title
|
// Get homepage title
|
||||||
$homepageTitle = $this->getHomepageTitle();
|
$homepageTitle = $this->getHomepageTitle();
|
||||||
@@ -1026,6 +1026,10 @@ class CodePressCMS {
|
|||||||
// Get layout from page metadata
|
// Get layout from page metadata
|
||||||
$layout = $page['layout'] ?? 'sidebar-content';
|
$layout = $page['layout'] ?? 'sidebar-content';
|
||||||
|
|
||||||
|
// Determine if sidebar toggle should be shown
|
||||||
|
$hasSidebar = $layout !== 'content' && !empty(trim($sidebarContent));
|
||||||
|
$breadcrumb = $this->generateBreadcrumb($hasSidebar);
|
||||||
|
|
||||||
// Prepare template data
|
// Prepare template data
|
||||||
$templateData = [
|
$templateData = [
|
||||||
'site_title' => $this->config['site_title'],
|
'site_title' => $this->config['site_title'],
|
||||||
@@ -1154,11 +1158,15 @@ class CodePressCMS {
|
|||||||
/**
|
/**
|
||||||
* Generate breadcrumb navigation HTML
|
* Generate breadcrumb navigation HTML
|
||||||
*
|
*
|
||||||
|
* @param bool $hasSidebar Whether sidebar content exists and should show toggle
|
||||||
* @return string Breadcrumb HTML
|
* @return string Breadcrumb HTML
|
||||||
*/
|
*/
|
||||||
public function generateBreadcrumb() {
|
public function generateBreadcrumb($hasSidebar = true) {
|
||||||
// Sidebar toggle button (shown before home icon in breadcrumb)
|
// Sidebar toggle button (shown before home icon in breadcrumb)
|
||||||
$sidebarToggle = '<li class="breadcrumb-item sidebar-toggle-item"><button type="button" class="sidebar-toggle-btn" onclick="toggleSidebar()" title="Toggle Sidebar" aria-label="Toggle Sidebar" aria-expanded="true"><i class="bi bi-layout-sidebar-inset"></i></button></li>';
|
$sidebarToggle = '';
|
||||||
|
if ($hasSidebar) {
|
||||||
|
$sidebarToggle = '<li class="breadcrumb-item sidebar-toggle-item"><button type="button" class="sidebar-toggle-btn" onclick="toggleSidebar()" title="Toggle Sidebar" aria-label="Toggle Sidebar" aria-expanded="true"><i class="bi bi-layout-sidebar-inset"></i></button></li>';
|
||||||
|
}
|
||||||
|
|
||||||
if (isset($_GET['search'])) {
|
if (isset($_GET['search'])) {
|
||||||
return '<nav aria-label="breadcrumb"><ol class="breadcrumb">' . $sidebarToggle . '<li class="breadcrumb-item"><a href="/' . $this->currentLanguage . '"><i class="bi bi-house"></i></a></li><li class="breadcrumb-item"> > </li><li class="breadcrumb-item active">' . $this->t('search') . '</li></ol></nav>';
|
return '<nav aria-label="breadcrumb"><ol class="breadcrumb">' . $sidebarToggle . '<li class="breadcrumb-item"><a href="/' . $this->currentLanguage . '"><i class="bi bi-house"></i></a></li><li class="breadcrumb-item"> > </li><li class="breadcrumb-item active">' . $this->t('search') . '</li></ol></nav>';
|
||||||
|
|||||||
@@ -0,0 +1,154 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class ContentAPI
|
||||||
|
{
|
||||||
|
private CodePressCMS $cms;
|
||||||
|
|
||||||
|
public function __construct(CodePressCMS $cms)
|
||||||
|
{
|
||||||
|
$this->cms = $cms;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAllPages(): array
|
||||||
|
{
|
||||||
|
return $this->cms->getAllPageTitles();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPage(string $path): ?array
|
||||||
|
{
|
||||||
|
$contentDir = $this->cms->config['content_dir'];
|
||||||
|
$path = preg_replace('/\.(md|php|html)$/', '', $path);
|
||||||
|
$filePath = $contentDir . '/' . $path;
|
||||||
|
|
||||||
|
$extensions = ['md', 'php', 'html'];
|
||||||
|
$actualPath = null;
|
||||||
|
foreach ($extensions as $ext) {
|
||||||
|
if (file_exists($filePath . '.' . $ext)) {
|
||||||
|
$actualPath = $filePath . '.' . $ext;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$actualPath || !file_exists($actualPath)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$content = file_get_contents($actualPath);
|
||||||
|
$extension = pathinfo($actualPath, PATHINFO_EXTENSION);
|
||||||
|
|
||||||
|
switch ($extension) {
|
||||||
|
case 'md':
|
||||||
|
$result = $this->cms->parseMarkdown($content, $actualPath);
|
||||||
|
break;
|
||||||
|
case 'php':
|
||||||
|
$result = $this->cms->parsePHP($actualPath);
|
||||||
|
$result['content'] = $this->cms->processContent($result['content']);
|
||||||
|
break;
|
||||||
|
case 'html':
|
||||||
|
$result = $this->cms->parseHTML($content, $actualPath);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'title' => $result['title'] ?? '',
|
||||||
|
'content' => $result['content'] ?? '',
|
||||||
|
'path' => $path,
|
||||||
|
'layout' => $result['layout'] ?? 'sidebar-content',
|
||||||
|
'metadata' => $result['metadata'] ?? [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getMenu(): array
|
||||||
|
{
|
||||||
|
return $this->cms->getMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getConfig(string $key, $default = null)
|
||||||
|
{
|
||||||
|
$keys = explode('.', $key);
|
||||||
|
$value = $this->cms->config;
|
||||||
|
|
||||||
|
foreach ($keys as $k) {
|
||||||
|
if (!isset($value[$k])) {
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
$value = $value[$k];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCurrentLanguage(): string
|
||||||
|
{
|
||||||
|
return $this->cms->currentLanguage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function buildUrl(string $page = 'index', ?string $lang = null, array $params = []): string
|
||||||
|
{
|
||||||
|
$lang = $lang ?? $this->getCurrentLanguage();
|
||||||
|
$query = 'page=' . urlencode($page) . '&lang=' . urlencode($lang);
|
||||||
|
if (!empty($params)) {
|
||||||
|
$query .= '&' . http_build_query($params);
|
||||||
|
}
|
||||||
|
return '?' . $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function pageExists(string $path): bool
|
||||||
|
{
|
||||||
|
$contentDir = $this->cms->config['content_dir'];
|
||||||
|
$path = preg_replace('/\.(md|php|html)$/', '', $path);
|
||||||
|
$basePath = $contentDir . '/' . $path;
|
||||||
|
|
||||||
|
return file_exists($basePath . '.md')
|
||||||
|
|| file_exists($basePath . '.php')
|
||||||
|
|| file_exists($basePath . '.html');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCurrentPageTitle(): string
|
||||||
|
{
|
||||||
|
$page = $this->cms->getPage();
|
||||||
|
return $page['title'] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCurrentPagePath(): string
|
||||||
|
{
|
||||||
|
return $_GET['page'] ?? $this->cms->config['default_page'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isHomepage(): bool
|
||||||
|
{
|
||||||
|
$defaultPage = $this->cms->config['default_page'] ?? 'index';
|
||||||
|
$currentPage = $_GET['page'] ?? $defaultPage;
|
||||||
|
return $currentPage === $defaultPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function t(string $key): string
|
||||||
|
{
|
||||||
|
return $this->cms->t($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSiteTitle(): string
|
||||||
|
{
|
||||||
|
return $this->cms->config['site_title'] ?? 'CodePress';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAvailableLanguages(): array
|
||||||
|
{
|
||||||
|
return $this->cms->getAvailableLanguages();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSearchResults(): array
|
||||||
|
{
|
||||||
|
if (isset($_GET['search'])) {
|
||||||
|
return $this->cms->searchResults;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isSearching(): bool
|
||||||
|
{
|
||||||
|
return isset($_GET['search']);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,6 +42,9 @@ require_once 'class/Logger.php';
|
|||||||
require_once 'plugin/CMSAPI.php';
|
require_once 'plugin/CMSAPI.php';
|
||||||
require_once 'plugin/PluginManager.php';
|
require_once 'plugin/PluginManager.php';
|
||||||
|
|
||||||
|
// Load ContentAPI class - provides CMS data access for PHP content files
|
||||||
|
require_once 'class/ContentAPI.php';
|
||||||
|
|
||||||
// Load main CMS class - handles content parsing, navigation, search, and page rendering
|
// Load main CMS class - handles content parsing, navigation, search, and page rendering
|
||||||
require_once 'class/CodePressCMS.php';
|
require_once 'class/CodePressCMS.php';
|
||||||
|
|
||||||
|
|||||||
+99
-1
@@ -18,7 +18,7 @@ CodePress CMS is a lightweight, file-based content management system built with
|
|||||||
|
|
||||||
### Content Types
|
### Content Types
|
||||||
- **Markdown (.md)** - CommonMark support via `league/commonmark`
|
- **Markdown (.md)** - CommonMark support via `league/commonmark`
|
||||||
- **PHP (.php)** - Dynamic content
|
- **PHP (.php)** - Dynamic content with **Content API** (`$api` variable)
|
||||||
- **HTML (.html)** - Static HTML pages
|
- **HTML (.html)** - Static HTML pages
|
||||||
- **Directory listings** - Automatic directory overviews
|
- **Directory listings** - Automatic directory overviews
|
||||||
- **Language-specific content** - `en.` and `nl.` prefixes
|
- **Language-specific content** - `en.` and `nl.` prefixes
|
||||||
@@ -63,6 +63,7 @@ CodePress CMS is a lightweight, file-based content management system built with
|
|||||||
- **Plugin management** - Overview, create, edit, configure, enable/disable and delete
|
- **Plugin management** - Overview, create, edit, configure, enable/disable and delete
|
||||||
- **Media management** - Upload and delete media files in `content/-assets/`
|
- **Media management** - Upload and delete media files in `content/-assets/`
|
||||||
- **User management** - Add, remove users, change passwords
|
- **User management** - Add, remove users, change passwords
|
||||||
|
- **Guide** - Built-in documentation with API reference
|
||||||
- Session-based authentication with bcrypt hashing
|
- Session-based authentication with bcrypt hashing
|
||||||
- CSRF protection, brute-force lockout (5 attempts, 15 min)
|
- CSRF protection, brute-force lockout (5 attempts, 15 min)
|
||||||
- Default login: `admin` / `admin` (change immediately after installation)
|
- Default login: `admin` / `admin` (change immediately after installation)
|
||||||
@@ -286,6 +287,7 @@ Media files (images, PDFs, video, audio) can be placed in any `content/` subdire
|
|||||||
| `media` | Media management (upload, delete) |
|
| `media` | Media management (upload, delete) |
|
||||||
| `media-list` | JSON list of all media (for editor modal) |
|
| `media-list` | JSON list of all media (for editor modal) |
|
||||||
| `users` | User management |
|
| `users` | User management |
|
||||||
|
| `guide` | Guide / documentation |
|
||||||
|
|
||||||
### Editor Features
|
### Editor Features
|
||||||
|
|
||||||
@@ -491,6 +493,102 @@ class MyPlugin
|
|||||||
|
|
||||||
The MQTTTracker plugin stores `broker_host`, `broker_port`, `client_id`, `username` and `password` in plain text in `plugins/MQTTTracker/config.json`. This is a known open security issue - in a production environment it is recommended to externalize these credentials to environment variables or a separate credential manager.
|
The MQTTTracker plugin stores `broker_host`, `broker_port`, `client_id`, `username` and `password` in plain text in `plugins/MQTTTracker/config.json`. This is a known open security issue - in a production environment it is recommended to externalize these credentials to environment variables or a separate credential manager.
|
||||||
|
|
||||||
|
## Content API (for PHP content files)
|
||||||
|
|
||||||
|
PHP content files (`.php` in the `content/` directory) have access to an `$api` variable with the following methods:
|
||||||
|
|
||||||
|
### Getting pages
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Get all pages with titles
|
||||||
|
$pages = $api->getAllPages();
|
||||||
|
// Result: ['index' => 'Home', 'about' => 'About Us', ...]
|
||||||
|
|
||||||
|
// Get a specific page's content
|
||||||
|
$page = $api->getPage('about');
|
||||||
|
// $page['title'], $page['content'], $page['path'], $page['layout'], $page['metadata']
|
||||||
|
|
||||||
|
// Check if a page exists
|
||||||
|
if ($api->pageExists('contact')) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Navigation
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Get menu structure
|
||||||
|
$menu = $api->getMenu();
|
||||||
|
// Nested array with 'title', 'path', 'url', 'children'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Get config value (dot notation)
|
||||||
|
$title = $api->getConfig('site_title');
|
||||||
|
$lang = $api->getConfig('language.default');
|
||||||
|
$seoDesc = $api->getConfig('seo.description', 'Default description');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Current page
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Current page title
|
||||||
|
$pageTitle = $api->getCurrentPageTitle();
|
||||||
|
|
||||||
|
// Current page path
|
||||||
|
$pagePath = $api->getCurrentPagePath();
|
||||||
|
|
||||||
|
// Check if this is the homepage
|
||||||
|
if ($api->isHomepage()) {
|
||||||
|
echo 'Welcome!';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### URLs and language
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Build URL for a page
|
||||||
|
$url = $api->buildUrl('about', 'en');
|
||||||
|
|
||||||
|
// Current language
|
||||||
|
$lang = $api->getCurrentLanguage();
|
||||||
|
|
||||||
|
// Available languages
|
||||||
|
$languages = $api->getAvailableLanguages();
|
||||||
|
|
||||||
|
// Site title
|
||||||
|
$title = $api->getSiteTitle();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Translations and search
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Get translation
|
||||||
|
$label = $api->t('home');
|
||||||
|
|
||||||
|
// Search results (if searching)
|
||||||
|
if ($api->isSearching()) {
|
||||||
|
$results = $api->getSearchResults();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example PHP content file
|
||||||
|
|
||||||
|
```php
|
||||||
|
---
|
||||||
|
title: Page Overview
|
||||||
|
layout: content
|
||||||
|
---
|
||||||
|
<h1>All Pages</h1>
|
||||||
|
<ul>
|
||||||
|
<?php foreach ($api->getAllPages() as $path => $title): ?>
|
||||||
|
<li><a href="<?= $api->buildUrl($path) ?>"><?= htmlspecialchars($title) ?></a></li>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</ul>
|
||||||
|
```
|
||||||
|
|
||||||
## Analytics & Tracking
|
## Analytics & Tracking
|
||||||
|
|
||||||
### MQTT Tracker
|
### MQTT Tracker
|
||||||
|
|||||||
+99
-1
@@ -18,7 +18,7 @@ CodePress CMS is een lichtgewicht, file-based content management systeem gebouwd
|
|||||||
|
|
||||||
### Content Types
|
### Content Types
|
||||||
- **Markdown (.md)** - CommonMark ondersteuning via `league/commonmark`
|
- **Markdown (.md)** - CommonMark ondersteuning via `league/commonmark`
|
||||||
- **PHP (.php)** - Dynamische content
|
- **PHP (.php)** - Dynamische content met **Content API** (`$api` variabele)
|
||||||
- **HTML (.html)** - Statische HTML pagina's
|
- **HTML (.html)** - Statische HTML pagina's
|
||||||
- **Directory listings** - Automatische directory overzichten
|
- **Directory listings** - Automatische directory overzichten
|
||||||
- **Language-specific content** - `nl.` en `en.` prefix
|
- **Language-specific content** - `nl.` en `en.` prefix
|
||||||
@@ -63,6 +63,7 @@ CodePress CMS is een lichtgewicht, file-based content management systeem gebouwd
|
|||||||
- **Plugin beheer** - Overzicht, aanmaken, bewerken, configureren, in-/uitschakelen en verwijderen
|
- **Plugin beheer** - Overzicht, aanmaken, bewerken, configureren, in-/uitschakelen en verwijderen
|
||||||
- **Media beheer** - Uploaden en verwijderen van mediabestanden in `content/-assets/`
|
- **Media beheer** - Uploaden en verwijderen van mediabestanden in `content/-assets/`
|
||||||
- **Gebruikersbeheer** - Gebruikers toevoegen, verwijderen, wachtwoorden wijzigen
|
- **Gebruikersbeheer** - Gebruikers toevoegen, verwijderen, wachtwoorden wijzigen
|
||||||
|
- **Handleiding** - Ingebouwde documentatie met API referentie
|
||||||
- Session-based authenticatie met bcrypt hashing
|
- Session-based authenticatie met bcrypt hashing
|
||||||
- CSRF-bescherming, brute-force lockout (5 pogingen, 15 min)
|
- CSRF-bescherming, brute-force lockout (5 pogingen, 15 min)
|
||||||
- Standaard login: `admin` / `admin` (wijzig direct na installatie)
|
- Standaard login: `admin` / `admin` (wijzig direct na installatie)
|
||||||
@@ -287,6 +288,7 @@ Media bestanden (afbeeldingen, PDFs, video, audio) kunnen in elke `content/` sub
|
|||||||
| `media` | Media beheer (uploaden, verwijderen) |
|
| `media` | Media beheer (uploaden, verwijderen) |
|
||||||
| `media-list` | JSON lijst van alle media (voor editor modal) |
|
| `media-list` | JSON lijst van alle media (voor editor modal) |
|
||||||
| `users` | Gebruikersbeheer |
|
| `users` | Gebruikersbeheer |
|
||||||
|
| `guide` | Handleiding (deze documentatie) |
|
||||||
|
|
||||||
### Editor Functionaliteit
|
### Editor Functionaliteit
|
||||||
|
|
||||||
@@ -492,6 +494,102 @@ class MijnPlugin
|
|||||||
|
|
||||||
De MQTTTracker plugin slaat `broker_host`, `broker_port`, `client_id`, `username` en `password` op in plain text in `plugins/MQTTTracker/config.json`. Dit is een bekend openstaand security punt - bij een productieomgeving wordt aangeraden deze gegevens te externaliseren naar omgevingsvariabelen of een aparte credentials manager.
|
De MQTTTracker plugin slaat `broker_host`, `broker_port`, `client_id`, `username` en `password` op in plain text in `plugins/MQTTTracker/config.json`. Dit is een bekend openstaand security punt - bij een productieomgeving wordt aangeraden deze gegevens te externaliseren naar omgevingsvariabelen of een aparte credentials manager.
|
||||||
|
|
||||||
|
## Content API (voor PHP content bestanden)
|
||||||
|
|
||||||
|
PHP content bestanden (`.php` in de `content/` map) hebben toegang tot een `$api` variabele met de volgende methodes:
|
||||||
|
|
||||||
|
### Pagina's opvragen
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Alle pagina's met titels ophalen
|
||||||
|
$pages = $api->getAllPages();
|
||||||
|
// Resultaat: ['index' => 'Home', 'over-ons' => 'Over ons', ...]
|
||||||
|
|
||||||
|
// specifieke pagina inhoud ophalen
|
||||||
|
$page = $api->getPage('over-ons');
|
||||||
|
// $page['title'], $page['content'], $page['path'], $page['layout'], $page['metadata']
|
||||||
|
|
||||||
|
// Controleren of een pagina bestaat
|
||||||
|
if ($api->pageExists('contact')) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Navigatie
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Menu structuur ophalen
|
||||||
|
$menu = $api->getMenu();
|
||||||
|
// Bevat geneste array met 'title', 'path', 'url', 'children'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuratie
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Configuratie waarde opvragen (punt-notatie)
|
||||||
|
$title = $api->getConfig('site_title');
|
||||||
|
$lang = $api->getConfig('language.default');
|
||||||
|
$seoDesc = $api->getConfig('seo.description', 'Standaard beschrijving');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Huidige pagina
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Huidige pagina titel
|
||||||
|
$pageTitle = $api->getCurrentPageTitle();
|
||||||
|
|
||||||
|
// Huidige pagina pad
|
||||||
|
$pagePath = $api->getCurrentPagePath();
|
||||||
|
|
||||||
|
// Check of dit de homepage is
|
||||||
|
if ($api->isHomepage()) {
|
||||||
|
echo 'Welkom!';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### URLs en taal
|
||||||
|
|
||||||
|
```php
|
||||||
|
// URL bouwen voor een pagina
|
||||||
|
$url = $api->buildUrl('over-ons', 'nl');
|
||||||
|
|
||||||
|
// Huidige taal
|
||||||
|
$lang = $api->getCurrentLanguage();
|
||||||
|
|
||||||
|
// Beschikbare talen
|
||||||
|
$languages = $api->getAvailableLanguages();
|
||||||
|
|
||||||
|
// Site titel
|
||||||
|
$title = $api->getSiteTitle();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Vertalingen en zoeken
|
||||||
|
|
||||||
|
```php
|
||||||
|
// Vertaling ophalen
|
||||||
|
$label = $api->t('home');
|
||||||
|
|
||||||
|
// Zoekresultaten (als er gezocht wordt)
|
||||||
|
if ($api->isSearching()) {
|
||||||
|
$results = $api->getSearchResults();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Voorbeeld PHP content bestand
|
||||||
|
|
||||||
|
```php
|
||||||
|
---
|
||||||
|
title: Pagina Overzicht
|
||||||
|
layout: content
|
||||||
|
---
|
||||||
|
<h1>Alle Pagina's</h1>
|
||||||
|
<ul>
|
||||||
|
<?php foreach ($api->getAllPages() as $path => $title): ?>
|
||||||
|
<li><a href="<?= $api->buildUrl($path) ?>"><?= htmlspecialchars($title) ?></a></li>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</ul>
|
||||||
|
```
|
||||||
|
|
||||||
## Analytics & Tracking
|
## Analytics & Tracking
|
||||||
|
|
||||||
### MQTT Tracker
|
### MQTT Tracker
|
||||||
|
|||||||
+53
-12
@@ -121,6 +121,10 @@ switch ($route) {
|
|||||||
handleMediaList($auth, $appConfig);
|
handleMediaList($auth, $appConfig);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 'guide':
|
||||||
|
handleGuide($auth, $appConfig);
|
||||||
|
break;
|
||||||
|
|
||||||
case 'users':
|
case 'users':
|
||||||
handleUsers($auth, $appConfig);
|
handleUsers($auth, $appConfig);
|
||||||
break;
|
break;
|
||||||
@@ -662,24 +666,28 @@ function handleConfig(AdminAuth $auth, array $config): void
|
|||||||
$configData = file_exists($configJson) ? json_decode(file_get_contents($configJson), true) : [];
|
$configData = file_exists($configJson) ? json_decode(file_get_contents($configJson), true) : [];
|
||||||
if (!is_array($configData)) $configData = [];
|
if (!is_array($configData)) $configData = [];
|
||||||
|
|
||||||
// Get available root-level pages
|
// Get available pages (including subdirectories)
|
||||||
$contentDir = $config['content_dir'];
|
$contentDir = $config['content_dir'];
|
||||||
$availablePages = [];
|
$availablePages = [];
|
||||||
$availableLangs = $configData['language']['available'] ?? ['nl', 'en'];
|
$availableLangs = $configData['language']['available'] ?? ['nl', 'en'];
|
||||||
if (is_dir($contentDir)) {
|
if (is_dir($contentDir)) {
|
||||||
$pageMap = [];
|
$pageMap = [];
|
||||||
foreach (scandir($contentDir) as $file) {
|
$realContentDir = realpath($contentDir);
|
||||||
if ($file[0] === '.') continue;
|
$iterator = new RecursiveIteratorIterator(
|
||||||
$filePath = $contentDir . '/' . $file;
|
new RecursiveDirectoryIterator($contentDir, RecursiveDirectoryIterator::SKIP_DOTS)
|
||||||
if (is_file($filePath) && preg_match('/\.(md|php|html)$/', $file)) {
|
);
|
||||||
$base = $file;
|
foreach ($iterator as $fileInfo) {
|
||||||
$langPattern = '/^(' . implode('|', $availableLangs) . ')\.(.+)$/';
|
if (!$fileInfo->isFile()) continue;
|
||||||
if (preg_match($langPattern, $base, $m)) {
|
$ext = $fileInfo->getExtension();
|
||||||
$base = $m[2];
|
if (!in_array($ext, ['md', 'php', 'html'])) continue;
|
||||||
}
|
$relative = substr($fileInfo->getRealPath(), strlen($realContentDir) + 1);
|
||||||
$pageKey = preg_replace('/\.(md|php|html)$/', '', $base);
|
$base = $relative;
|
||||||
$pageMap[$pageKey] = true;
|
$langPattern = '/^(' . implode('|', $availableLangs) . ')\.(.+)$/';
|
||||||
|
if (preg_match($langPattern, $base, $m)) {
|
||||||
|
$base = $m[2];
|
||||||
}
|
}
|
||||||
|
$pageKey = preg_replace('/\.(md|php|html)$/', '', $base);
|
||||||
|
$pageMap[$pageKey] = true;
|
||||||
}
|
}
|
||||||
$availablePages = array_keys($pageMap);
|
$availablePages = array_keys($pageMap);
|
||||||
sort($availablePages);
|
sort($availablePages);
|
||||||
@@ -1360,6 +1368,39 @@ function handleUsers(AdminAuth $auth, array $config): void
|
|||||||
require __DIR__ . '/../admin/templates/layout.php';
|
require __DIR__ . '/../admin/templates/layout.php';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleGuide(AdminAuth $auth, array $config): void
|
||||||
|
{
|
||||||
|
$user = $auth->getCurrentUser();
|
||||||
|
$csrf = $auth->getCsrfToken();
|
||||||
|
|
||||||
|
// Determine language for guide
|
||||||
|
$lang = $_GET['lang'] ?? 'nl';
|
||||||
|
if (!in_array($lang, ['nl', 'en'])) $lang = 'nl';
|
||||||
|
|
||||||
|
$guideFile = __DIR__ . '/../guide/' . $lang . '.codepress.md';
|
||||||
|
if (!file_exists($guideFile)) {
|
||||||
|
$guideFile = __DIR__ . '/../guide/en.codepress.md';
|
||||||
|
$lang = 'en';
|
||||||
|
}
|
||||||
|
|
||||||
|
$rawContent = file_get_contents($guideFile);
|
||||||
|
|
||||||
|
// Parse markdown using CommonMark if available
|
||||||
|
$content = '';
|
||||||
|
if (class_exists('League\CommonMark\CommonMarkConverter')) {
|
||||||
|
$converter = new League\CommonMark\CommonMarkConverter([
|
||||||
|
'html_input' => 'allow',
|
||||||
|
'allow_unsafe_links' => false,
|
||||||
|
]);
|
||||||
|
$content = $converter->convert($rawContent)->getContent();
|
||||||
|
} else {
|
||||||
|
$content = '<pre>' . htmlspecialchars($rawContent) . '</pre>';
|
||||||
|
}
|
||||||
|
|
||||||
|
$route = 'guide';
|
||||||
|
require __DIR__ . '/../admin/templates/layout.php';
|
||||||
|
}
|
||||||
|
|
||||||
// --- Frontmatter helpers ---
|
// --- Frontmatter helpers ---
|
||||||
|
|
||||||
function parseFrontmatterField(string $content, string $key, string $default = ''): string
|
function parseFrontmatterField(string $content, string $key, string $default = ''): string
|
||||||
|
|||||||
Reference in New Issue
Block a user