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
6.1 KiB
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
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
// 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
// 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:
[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:
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:
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:
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:
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:
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:
// 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
- Keep plugins self-contained - All logic in one file
- Use namespaced helper functions - Prefix with plugin name
- Store plugin data in context - Use
$ctx->set()for plugin state - Return modified data - Always return from hooks
- Use global context when needed -
$GLOBALS['ctx']for cross-hook access - Document your hooks - Comment what each hook does
Plugin Loading Order
- Config loaded
- Global plugins loaded (from config)
- Context created
CONTEXT_READYhooks run- Routing happens
- Content loaded
PROCESS_CONTENThooks run (multiple times)- Template variables prepared
TEMPLATE_VARShooks run- Template rendered
Example: Complete Plugin
<?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:
[analytics]
id = "G-XXXXXXXXXX"
[plugins]
enabled = "analytics"
Template usage:
<?php if ($analyticsEnabled): ?>
<script async src="https://www.googletagmanager.com/gtag/js?id=<?= $analyticsId ?>"></script>
<?php endif; ?>
Debugging
Check loaded plugins:
$plugins = getPluginManager()->getLoadedPlugins();
var_dump($plugins);
Check if plugin loaded:
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.