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