# 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 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 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 ``` ## 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.