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
262 lines
6.1 KiB
Markdown
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.
|