Merge development v2.6.0 into main

Resolved conflicts by taking development (v2.6.0) version for all files.
Removed statistics.twig (replaced by Statistics plugin).
This commit is contained in:
2026-08-15 19:32:47 +02:00
208 changed files with 3757 additions and 23879 deletions
+20 -80
View File
@@ -1,84 +1,24 @@
# CMS Architecture
CodePress is a file-based CMS without a database. Content, configuration and users are stored in files.
## Folder structure
```
codepress/
├── cms/ # Core CMS engine
│ ├── core/
│ ├── class/
│ │ ├── CodePressCMS.php # Main CMS class (routing, rendering, breadcrumb)
│ │ ├── ThemeManager.php # Theme resolver + Twig render + SCSS compile
├── ContentAPI.php # Read-only API for PHP content
├── ContentSecurityPolicy.php # CSP header management
├── Analytics.php # Visitor statistics
│ │ ├── BotGuard.php # Bot/AI detection
│ │ │ ├── Cache.php # Cache system
│ │ │ ├── GeoIP.php # GeoIP lookup (country, flag)
│ │ │ ├── Logger.php # Basic logging
│ │ │ ├── LogManager.php # Dynamic logging (SQLite/syslog)
│ │ │ ├── RateLimiter.php # Rate limiting per IP
│ │ │ ├── RequestLogger.php # Request logging + visitor info
│ │ ├── SearchEngine.php # Full text search
│ │ └── AccessibilityManager.php # Accessibility features
│ │ ├── plugin/
│ │ │ ├── PluginManager.php # Plugin loader (hooks, filters, sidebar, admin routes)
│ │ │ └── CMSAPI.php # API for plugins (getPage, getConfig, etc.)
│ │ ├── config.php # Config loader (reads config.json)
│ │ └── index.php # Bootstrap (autoloader, requires)
│ ├── lang/ # Language files (nl.php, en.php)
│ └── router.php # PHP dev server router
├── themes/ # Dynamic themes
│ └── default/ # Default theme (views + assets)
│ ├── theme.json # { title, config.default_template, template: layout→.twig }
│ ├── base.twig # Main layout
│ ├── *.twig # Layout templates
│ ├── partials/ # header.twig, navigation.twig, footer.twig
│ └── assets/
│ ├── scss/theme.scss # SCSS source (only CSS source)
│ ├── css_compiled/ # Generated by scssphp (read-only)
│ ├── css/ # External CSS (bootstrap.min.css, etc.)
│ ├── js/ # JavaScript
│ └── img/ # Images
├── admin/ # Admin panel
│ ├── config/
│ │ ├── app.php # Admin app configuration
│ │ └── admin.json # Users & security (file-based, .gitignore'd)
│ ├── src/
│ │ └── AdminAuth.php # Authentication + RBAC
│ ├── theme/default/ # Admin theme (views + assets)
│ │ ├── theme.json
│ │ ├── assets/ # CSS, JS, CodeMirror, fonts
│ │ └── views/ # login.twig, layouts/, pages/
│ └── storage/ # Logs, cache, geoip
├── plugins/ # CMS plugins
│ ├── Navigation/ # Essential navigation plugin (protected)
│ │ ├── Navigation.php # Plugin code (NOT plugin.php)
│ │ ├── plugin.json # Metadata with type field
│ │ └── assets/ # SCSS + CSS
│ └── HTMLBlock/ # Example sidebar plugin
├── content/ # Website content (.md, .php, .html) — .gitignore'd
├── guide/ # Guides (nl/en)
├── public/ # Web root
│ ├── index.php # Website entry point
│ ├── admin.php # Admin entry point + routing
│ ├── asset.php # Asset server for Apache (themes/, admin/assets/, plugins/)
│ ├── .htaccess # Forwards asset URLs to asset.php
│ ├── favicon.ico
│ └── robots.txt
├── var/ # Cache (twig) — .gitignore'd
├── config.json # Site configuration — .gitignore'd
├── composer.json # PHP dependencies (CommonMark, Twig, scssphp)
└── version.php # Version information (2.5.2)
```
## Principles
- **No database** — Everything in files (content in `content/`, users in `admin/config/admin.json`, config in `config.json`)
- **File-based auth** — `AdminAuth` uses bcrypt hashes in `admin.json`, sessions for login
- **Twig templating** — Themes use Twig; `ThemeManager` renders via Twig
- **SCSS compilation** — `assets/scss/theme.scss``assets/css_compiled/theme.css` via scssphp (read-only output)
- **Plugin system** — `PluginManager` loads content and system plugins with hooks/filters
- **Asset serving** — PHP dev router (`cms/router.php`) or Apache (`.htaccess``public/asset.php`)
├── cms/core/ # Core engine
│ ├── class/ # CodePressCMS, ThemeManager, Logger, Analytics
│ ├── plugin/ # PluginManager, CMSAPI, AdminPluginAPI
├── config.php # Config loader
└── index.php # Bootstrap
├── admin/ # Admin console
├── config/ # app.php, admin.json
├── src/ # AdminAuth
└── theme/default/views/ # Twig templates
├── themes/ # Themes (default, demo, ...)
├── plugins/ # Plugins (Dashboard, HTMLBlock, Navigation, ...)
├── content/ # Content files (.md, .php, .html)
├── language/ # Language files (nl/, en/, de/)
├── guide/ # Guides (nl/, en/)
├── public/ # Web root
├── index.php # Website entry point
└── admin.php # Admin entry point + router
├── config.json # Site configuration
└── version.php # Version info
```
+29 -72
View File
@@ -5,17 +5,11 @@
Main CMS class in `cms/core/class/CodePressCMS.php`:
```php
$cms = new CodePressCMS();
$cms->init();
$cms = new CodePressCMS($config);
$cms->renderPage($pagePath);
```
Responsibilities:
- Routing and page rendering
- Breadcrumb generation (dynamic: Home > [subfolders] > [page])
- Loading guide pages (`getGuidePage()`)
- Title extraction from H1 (`getTitleFromFile()`)
- Display name processing (`formatDisplayName()`)
Key methods: `renderPage()`, `getMenu()`, `getPage()`, `parseMarkdown()`, `generateBreadcrumb()`, `getAvailableLanguages()`.
## ThemeManager.php
@@ -23,20 +17,10 @@ Theme management in `cms/core/class/ThemeManager.php`:
```php
$themeManager = new ThemeManager($config);
$themeManager->getActiveTheme();
$themeManager->renderTwig($template, $data);
$themeManager->compileCss($force);
$themeManager->getCssUrl();
```
Responsibilities:
- Resolving the active theme from `config.json`
- Loading `theme.json` (title, config.default_template, template mapping)
- Building the Twig environment (rooted in the theme folder)
- **SCSS compilation** — compiles `assets/scss/theme.scss` to `assets/css_compiled/theme.css` via scssphp
- Resolving the layout to a `.twig` template (fallback to `config.default_template`)
`getCssUrl()` priority: 1) `assets/css/theme.css` (manual), 2) `assets/css_compiled/theme.css` (compiled).
Compiles `assets/scss/theme.scss` at runtime to CSS and renders Twig templates.
## PluginManager.php
@@ -44,66 +28,39 @@ Plugin system in `cms/core/plugin/PluginManager.php`:
```php
$pluginManager = new PluginManager($pluginsPath, $enabledPlugins);
$pluginManager->getSidebarContent($allowedPlugins);
$pluginManager->getPluginCssUrls();
$pluginManager->executeHook($name, $params);
$pluginManager->setAPI($api);
$pluginManager->doAction('onPageLoad', $args);
$result = $pluginManager->applyFilters('onContentFilter', $content);
$pluginManager->getAdminMenuItems();
$pluginManager->handleAdminRoute($route);
$pluginManager->getPluginType($pluginName);
```
Responsibilities:
- Loading plugins from `plugins/` folders (only enabled plugins)
- Plugin file: `<PluginName>.php` (NOT `plugin.php`)
- Hooks and filters system (`doAction`, `applyFilters`)
- Collecting sidebar content from content plugins (`getSidebarContent`)
- Collecting plugin CSS URLs (`getPluginCssUrls`)
- **Admin menu items** from system plugins (`getAdminMenuItems`)
- **Admin routes** handled via system plugins (`handleAdminRoute`)
- Determining the **plugin type** (`getPluginType``content` or `system`)
- `isPluginViewable` — system plugins do not appear in the sidebar
## AdminAuth.php
Authentication and RBAC in `admin/src/AdminAuth.php`:
```php
$auth = new AdminAuth($appConfig);
$auth->login($username, $password);
$auth->logout();
$auth->hasPermission($route);
$auth->verifyCsrf($token);
```
Constants:
- `ROLE_PERMISSIONS` — Mapping of role → allowed route prefixes. `admin` has wildcard `*`.
- `ROLE_LABELS` — Human-readable labels per role.
Roles:
| Role | Label | Permissions |
|------|-------|-------------|
| `admin` | Admin | Everything (`*`) |
| `content-manager` | Content Manager | Content management, guide |
| `bi-manager` | BI Manager | Statistics, logs, guide |
| `site-admin` | Site Admin | Theme, plugins, statistics, logs, update, guide |
Responsibilities:
- Session-based authentication
- bcrypt password hashing
- CSRF tokens (`verifyCsrf`)
- Brute-force lockout (login attempts tracking)
- RBAC via `hasPermission($route)` — checks the route against `ROLE_PERMISSIONS`
Key methods: `setAPI()`, `doAction()`, `applyFilters()`, `getPlugin()`, `getAllPlugins()`, `getEnabledPlugins()`, `isEnabled()`, `getSidebarContent()`, `getPluginCssUrls()`, `getAdminMenuItems()`, `dispatchAdminRoute()`, `resolveAdminRoute()`.
## CMSAPI.php
Plugin API in `cms/core/plugin/CMSAPI.php`:
Front-end plugin API in `cms/core/plugin/CMSAPI.php`. Implements `PluginAPIInterface`. Provides access to the CMS instance for plugins in the front-end context.
```php
$api = PluginManager::getAPI();
$config = $api->getConfig();
$page = $api->getPage($path);
$content = $api->getContent();
$api->getCurrentPageTitle();
$api->getMenu();
$api->getConfig('site_title');
$api->createUrl('about-us');
```
Provides read-only access for plugins to CMS data (config, pages, content).
## AdminPluginAPI.php
Admin plugin API in `cms/core/plugin/AdminPluginAPI.php`. Implements `PluginAPIInterface`. Lightweight wrapper with config-only access (no CMS instance needed).
```php
$api->getConfig('analytics.enabled');
$api->getContentDir();
$api->getEnabledPlugins();
```
## AdminAuth.php
Authentication in `admin/src/AdminAuth.php`. Manages sessions, bcrypt passwords, CSRF tokens, brute-force lockout, and role-based permissions.
## LogManager.php
Logging system in `cms/core/class/LogManager.php`. Supports SQLite, syslog, and file-based logging. Events: admin, requests, errors, security, content, system.
@@ -1,144 +1,146 @@
# Plugin Development
## Plugin types
CodePress has two kinds of plugins, determined by the `type` field in `plugin.json`:
- **Content plugins** (`type: "content"`) — Appear in the sidebar and in the plugin selection on content-edit pages. Provide sidebar content via `getSidebarContent()`.
- **System plugins** (`type: "system"`) — Are loaded by PluginManager but do NOT appear in the sidebar. Provide functionality via admin menu, routes and the CMSAPI.
When no `type` field is present, `content` is assumed.
## Plugin structure
```
plugins/MyPlugin/
├── MyPlugin.php # Plugin code (NOT plugin.php)
├── plugin.json # Metadata with type field
├── config.json # Optional configuration
└── assets/
├── scss/myplugin.scss # SCSS source (optional)
└── css/myplugin.css # CSS (manually maintained)
├── MyPlugin.php # Main plugin class (name = folder name)
├── plugin.json # Plugin metadata
├── config.json # Optional configuration
└── assets/ # Optional CSS/JS
├── css/
└── scss/
```
Plugin files are named `<PluginName>.php` (e.g. `Navigation.php`, `HTMLBlock.php`). `PluginManager` loads `$pluginDir . '/' . $pluginName . '.php'`.
## plugin.json
```json
{
"name": "My Plugin",
"version": "1.0.0",
"author": "Your Name",
"description": "Description",
"type": "content"
"name": "My Plugin",
"version": "1.0.0",
"author": "Your Name",
"description": "Description",
"type": "content",
"essential": false,
"hasConfig": false
}
```
- `name` — Display name
- `type``"content"` or `"system"` (default: `content`)
### Fields
## Content plugin example
| Field | Value | Description |
|-------|-------|-------------|
| `name` | string | Display name in admin |
| `version` | string | Version number |
| `author` | string | Author |
| `description` | string | Short description |
| `type` | `"system"` or `"content"` | System (blue badge) or content (green badge) |
| `essential` | boolean | Essential plugins cannot be edited/deleted |
| `hasConfig` | boolean | Shows a Config button in admin |
## Plugin class example
```php
<?php
/**
* Plugin: MyPlugin (content)
*/
class MyPlugin
{
private ?PluginAPIInterface $api = null;
public function setAPI(PluginAPIInterface $api): void
{
$this->api = $api;
}
public function getConfig(): array
{
return [
'title' => 'My Plugin',
'type' => 'content',
'viewable' => true,
];
}
public function getSidebarContent(): string
{
return '<p>Hello World</p>';
$title = $this->api ? $this->api->getCurrentPageTitle() : '';
return '<p>Current page: ' . htmlspecialchars($title) . '</p>';
}
}
```
## System plugin example
The plugin class is automatically loaded by `PluginManager` when the plugin is listed in `enabled_plugins` in `config.json`.
## Using the API
The API is injected via `setAPI()`, not via a static method. In the front-end context this is a `CMSAPI` instance, in the admin context an `AdminPluginAPI` instance. Both implement `PluginAPIInterface`.
```php
<?php
/**
* Plugin: MySystem (system)
*/
class MySystem
// Front-end API (CMSAPI)
$this->api->getCurrentPageTitle();
$this->api->getMenu();
$this->api->getConfig('site_title');
$this->api->getCurrentLanguage();
$this->api->isHomepage();
$this->api->createUrl('about-us');
// Admin API (AdminPluginAPI)
$this->api->getConfig('analytics.enabled');
$this->api->getContentDir();
$this->api->getEnabledPlugins();
```
## Hooks
Plugins can implement the following methods for automatic hook registration:
**Actions** (no return value):
- `onPageLoad` - On page load
- `onBeforeRender` - Before rendering
- `onAfterRender` - After rendering
- `onSearch` - On search
- `onMenuBuild` - On menu build
**Filters** (return modified value):
- `onContentFilter` - Filter content
- `onTitleFilter` - Filter title
- `onMenuFilter` - Filter menu
## Admin integration
Plugins can add custom admin pages via `getAdminMenu()` and `handleAdminRoute()`:
```php
public function getAdminMenu(): array
{
public function getConfig(): array
{
return [
'title' => 'My System',
'type' => 'system',
];
}
return [
[
'plugin' => 'MyPlugin',
'route' => 'my-plugin',
'label' => 'My Plugin',
'icon' => 'bi-puzzle',
'section' => 'general', // or 'system'
],
];
}
public function getAdminMenu(): array
{
return [
['label' => 'My System', 'route' => 'my-system', 'icon' => 'bi-gear'],
];
}
public function getAdminRoutes(): array
{
return ['my-system'];
}
public function handleAdminRoute(string $route): void
{
echo '<h1>My System page</h1>';
}
public function handleAdminRoute(string $action): ?string
{
return '<h2>My Plugin admin page</h2>';
}
```
System plugins are shown in the admin sidebar via `PluginManager::getAdminMenuItems()` and handled via `PluginManager::handleAdminRoute()`.
Only plugins listed in `enabled_plugins` are shown in the admin sidebar.
## Using CMSAPI
## Adding CSS
```php
<?php
$api = PluginManager::getAPI();
$config = $api->getConfig();
$page = $api->getPage($path);
```
`setAPI()` is called automatically by `PluginManager` if the plugin has the method.
## Plugin CSS
- Plugin CSS in `assets/css/` is manually maintained (SCSS compilation for plugins is not yet automatic)
- Plugin CSS is automatically loaded after theme CSS (in `base.twig`), so themes can override plugin styling
- Plugin assets are served via `cms/router.php` at URL `/plugins/<Name>/assets/...`
- Implement `getCssUrl()` in the plugin class to return the CSS URL
Plugins can provide a CSS URL via `getCssUrl()`:
```php
public function getCssUrl(): string
{
return '/plugins/MyPlugin/assets/css/myplugin.css';
return '/plugins/MyPlugin/assets/css/style.css';
}
```
## Hooks and filters
PluginManager auto-registers these methods as hooks/filters:
- **Hooks**: `onPageLoad`, `onBeforeRender`, `onAfterRender`, `onSearch`, `onMenuBuild`
- **Filters**: `onContentFilter`, `onTitleFilter`, `onMenuFilter`
```php
public function onPageLoad($page) { /* ... */ }
public function onContentFilter($content) { return $content; }
```
## Essential plugins
Essential plugins are defined in `getProtectedPlugins()` in `public/admin.php`. Current essential plugin: **Navigation**.
These plugins cannot be deactivated, edited or deleted. Always add the `isProtectedPlugin()` check to new plugin handlers.
```