Hooks
Pragma CMS follows a "Pluggable Architecture" philosophy. Instead of modifying core files, you use Hooks to inject custom logic or transform data. This ensures your site remains compatible with future core updates.
PHP Hooks (Actions & Filters)
The PHP hook system is the backbone of server-side extensibility. It is managed by the Hooks class and uses the Hook Enum for type-safe event names.
1. Actions (addAction / doAction)
Concept: "When this event happens, execute this code."
Actions are triggers. They allow you to run additional code at specific moments (e.g., sending a Slack notification after a form is submitted) without expecting anything in return.
- Analogy: An Announcement. The core shouts: "I just saved an entry!", and your code listens and reacts.
- Example:
// Listen to a core event using the Enum
Hooks::addAction(Hook::ON_ENTRY_SAVE, function($entryId, $entryData) {
// Custom logic: e.g., clearing a specific cache
MyCacheManager::clear($entryId);
});
2. Filters (addFilter / applyFilters)
Concept: "I’m about to use this data. Does anyone want to modify it first?"
Filters are used to transform data. They are sequential; each function in the chain receives the value from the previous one.
- Analogy: An Assembly Line. Data enters the line, and each hooked function (worker) can inspect it, modify it, and pass it to the next one.
- Crucial Rule: A filter must return the value, even if it wasn't modified.
- Example:
// Modify a CSS class list before rendering
Hooks::addFilter(Hook::ENQUEUE_CSS, function(array $cssFiles) {
$cssFiles[] = getAssetsPath('css/custom-extension.css');
return $cssFiles; // Always return the value
});
Type Safety & Custom Hooks
While core hooks are accessed via Hook::ENUM_NAME, you can create your own hooks for your extensions by passing a string:
// Triggering a custom hook in your extension
Hooks::doAction('my_extension_custom_event', $data);
JS Hooks (UI & Editor)
Pragma CMS exposes a global window.CMS namespace. This allows your scripts to register components or logic before the core UI initializes, ensuring a seamless integration.
1. Global Namespace
All core APIs and tools are exposed through window.CMS.
window.CMS.tools: Registry for Editor.js block classes.window.CMS.media/window.CMS.icons: APIs to programmatically open system modals and handle selections.window.CMS.fieldRenderers: A registry used to define how custom fields should render their configuration UI in the Content Type builder.
2. Extending the Visual Builder (Editor.js)
To keep the UI fast and prevent crashes, Pragma CMS handles Editor.js tools through two distinct registries:
Root Tools (editorExtraTools)
Used to add a new block type to the main + menu of the editor.
// Registering a custom "Alert" block
window.CMS.editorExtraTools = window.CMS.editorExtraTools || {};
window.CMS.editorExtraTools.alert = {
class: AlertTool, // Your Editor.js class
tunes: ['containerTune'], // Enable responsive width settings
inlineToolbar: true,
config: {
placeholder: 'Enter alert text...'
}
};
Sub-Blocks (subBlockRegistry)
To make a component available specifically inside the Columns tool, you must register the class in the window.CMS.subBlockRegistry. This registry allows the Columns component to instantiate sub-tools without duplicating core logic.
// Making the "Alert" block available inside Columns
window.CMS.subBlockRegistry = window.CMS.subBlockRegistry || {};
window.CMS.subBlockRegistry.alert = AlertTool;
3. Text Handling & Paste Rules
To maintain data integrity, all custom tools using contentEditable should use the core utility handleCmsPaste(e). This forces plain-text input and prevents "block escaping" (where Editor.js creates a new root paragraph when you paste text into a sub-field).
// Example in a custom render() method
myInputElement.addEventListener('paste', handleCmsPaste);
4. Custom Field Renderers
f you create a new field type for Content Types, you can hook into the Builder UI to define its settings panel:
window.CMS.fieldRenderers.my_custom_field = function(fieldId, data) {
return `
<div class="form-container">
<label>Custom Option</label>
<input type="text" name="fields[${fieldId}][options][my_key]" value="${data.options?.my_key || ''}">
</div>
`;
};
Best Practices
- Priority: Both PHP and JS hooks support priority (default is 10). Lower numbers execute first.
- Naming: Prefix your custom string hooks with your extension handle (e.g., shop:after_checkout) to avoid collisions.
- Performance: Keep hook callbacks lightweight. For heavy PHP tasks (like image processing), use the action to trigger a background job or a deferred task.