folderweb/app/docs/plugin-system.md
Ruben a205f2cbd7 Remove language-specific content handling
Refactor to use plugin system for language support

Remove hardcoded language features from core

Move language handling to plugin system

Improve content file discovery

Simplify context creation

Add plugin system documentation

Implement hook system for extensibility

Add template variable hook

Add context storage for plugins

Improve error handling

Refactor rendering logic

Improve list view sorting

Add support for custom list templates

Improve metadata handling

Add plugin system reference documentation
2025-11-25 20:19:12 +01:00

262 lines
6.1 KiB
Markdown

# Plugin System Reference
## Overview
The framework uses a minimal 3-hook plugin system that allows plugins to modify behavior without the core knowing about them. Plugins are PHP files that register callbacks to specific hooks.
## Hook System
### Available Hooks
```php
enum Hook: string {
case CONTEXT_READY = 'context_ready'; // After context created, before routing
case PROCESS_CONTENT = 'process_content'; // When loading/processing content
case TEMPLATE_VARS = 'template_vars'; // When building template variables
}
```
### Core Functions
```php
// Register a filter
Hooks::add(Hook $hook, callable $callback): void
// Apply filters (modify and return data)
Hooks::apply(Hook $hook, mixed $value, mixed ...$args): mixed
```
## Creating a Plugin
### Plugin Location
```
custom/plugins/global/your-plugin.php # Custom plugins
app/plugins/global/your-plugin.php # Default plugins
```
### Plugin Structure
```php
<?php
// custom/plugins/global/my-plugin.php
// Hook 1: Modify context after creation
Hooks::add(Hook::CONTEXT_READY, function(Context $ctx, array $config) {
// Modify context, add data
$ctx->set('myData', 'value');
return $ctx;
});
// Hook 2: Process content/metadata
Hooks::add(Hook::PROCESS_CONTENT, function(mixed $data, string $context) {
// Modify content files, metadata, etc.
return $data;
});
// Hook 3: Add template variables
Hooks::add(Hook::TEMPLATE_VARS, function(array $vars, Context $ctx) {
// Add variables for templates
$vars['myVar'] = 'value';
return $vars;
});
// Helper functions for your plugin
function myHelper() {
// Plugin-specific logic
}
```
### Enable Plugin
Add to `custom/config.ini`:
```ini
[plugins]
enabled = "my-plugin,another-plugin"
```
## Hook Details
### 1. CONTEXT_READY
**When:** After context created, before routing starts
**Purpose:** Modify request handling, extract data from URL, inject context properties
**Signature:** `function(Context $ctx, array $config): Context`
**Example:**
```php
Hooks::add(Hook::CONTEXT_READY, function(Context $ctx, array $config) {
// Extract custom parameter from URL
$parts = explode('/', $ctx->requestPath);
if ($parts[0] === 'api') {
$ctx->set('isApi', true);
array_shift($parts);
// Update request path
$reflection = new ReflectionProperty($ctx, 'requestPath');
$reflection->setValue($ctx, implode('/', $parts));
}
return $ctx;
});
```
### 2. PROCESS_CONTENT
**When:** During content loading and processing
**Purpose:** Filter content files, modify metadata, transform data
**Signature:** `function(mixed $data, string $context, ...): mixed`
**Use Cases:**
**Filter content files:**
```php
Hooks::add(Hook::PROCESS_CONTENT, function(mixed $data, string $dir) {
if (is_array($data) && isset($data[0]['path'])) {
// $data is array of content files
return array_filter($data, fn($file) =>
!str_contains($file['name'], 'draft')
);
}
return $data;
});
```
**Modify metadata:**
```php
Hooks::add(Hook::PROCESS_CONTENT, function(mixed $data, string $dir, string $type = '') {
if ($type === 'metadata') {
// $data is metadata array
$data['processed'] = true;
}
return $data;
});
```
**Format dates:**
```php
Hooks::add(Hook::PROCESS_CONTENT, function(mixed $data, string $type) {
if ($type === 'date_format') {
// $data is date string
return date('F j, Y', strtotime($data));
}
return $data;
});
```
### 3. TEMPLATE_VARS
**When:** Before rendering templates
**Purpose:** Add variables for use in templates
**Signature:** `function(array $vars, Context $ctx): array`
**Example:**
```php
Hooks::add(Hook::TEMPLATE_VARS, function(array $vars, Context $ctx) {
$vars['siteName'] = 'My Site';
$vars['year'] = date('Y');
$vars['customData'] = $ctx->get('myData');
return $vars;
});
```
## Context Storage
Plugins can store data in the context using generic storage:
```php
// Set data
$ctx->set('key', $value);
// Get data
$value = $ctx->get('key', $default);
// Check if exists
if ($ctx->has('key')) { }
// Magic property access
$ctx->myKey = 'value';
$value = $ctx->myKey;
```
## Best Practices
1. **Keep plugins self-contained** - All logic in one file
2. **Use namespaced helper functions** - Prefix with plugin name
3. **Store plugin data in context** - Use `$ctx->set()` for plugin state
4. **Return modified data** - Always return from hooks
5. **Use global context when needed** - `$GLOBALS['ctx']` for cross-hook access
6. **Document your hooks** - Comment what each hook does
## Plugin Loading Order
1. Config loaded
2. Global plugins loaded (from config)
3. Context created
4. `CONTEXT_READY` hooks run
5. Routing happens
6. Content loaded
7. `PROCESS_CONTENT` hooks run (multiple times)
8. Template variables prepared
9. `TEMPLATE_VARS` hooks run
10. Template rendered
## Example: Complete Plugin
```php
<?php
// custom/plugins/global/analytics.php
// Add analytics configuration to context
Hooks::add(Hook::CONTEXT_READY, function(Context $ctx, array $config) {
$analyticsId = $config['analytics']['id'] ?? null;
$ctx->set('analyticsId', $analyticsId);
return $ctx;
});
// Add analytics variables to templates
Hooks::add(Hook::TEMPLATE_VARS, function(array $vars, Context $ctx) {
$vars['analyticsId'] = $ctx->get('analyticsId');
$vars['analyticsEnabled'] = !empty($vars['analyticsId']);
return $vars;
});
```
Config:
```ini
[analytics]
id = "G-XXXXXXXXXX"
[plugins]
enabled = "analytics"
```
Template usage:
```php
<?php if ($analyticsEnabled): ?>
<script async src="https://www.googletagmanager.com/gtag/js?id=<?= $analyticsId ?>"></script>
<?php endif; ?>
```
## Debugging
Check loaded plugins:
```php
$plugins = getPluginManager()->getLoadedPlugins();
var_dump($plugins);
```
Check if plugin loaded:
```php
if (getPluginManager()->isLoaded('my-plugin')) {
// Plugin is active
}
```
## Limitations
- No priorities (hooks run in registration order)
- No actions (only filters that return values)
- No unhooking (once registered, always runs)
- Plugins load once per request
For advanced needs, consider multiple plugins or extending the hook system.