Site Content Hooks
The twelve hooks over pages, news, slides, testimonials and the menu.
Overview
The written content of the site lives here: pages, contracts, news, articles, references, slides, testimonials and the menu.
The save path splits into two filters: the main record passes once, the language data once per language. If you clean the body, the second is the right place.
Reference
Stopping content being deleted
Runs before a piece of site content is deleted. Pages, contracts, news, articles and references share this gate.
Hook::add('gate:client.content_delete', 10, function ($page, $page_type) {
// Deleting a contract can affect the order flow.
if ($page_type === 'contract') return 'Contracts cannot be deleted; switch them off instead.';
return null;
});Following content being deleted
Runs after a piece of site content is deleted.
Hook::add('action:client.content.deleted', 10, function ($id, $page_type, $page) {
Acme::dropFromIndex('page', $id);
});Following content being viewed
Runs when a piece of site content is visited. The visitor may not be logged in.
Hook::add('action:client.content.viewed', 10, function ($id, $type, $record) {
// It runs on every visit: keep it light.
Acme::trackView($type, $id);
});Changing the content detail data
Runs before the content reaches the screen. The place to resolve your own shortcodes or adjust the search headings.
Hook::add('filter:client.content_detail.data', 10, function (&$record, &$id, &$type) {
if ($type !== 'articles') return;
$record['content'] = Acme::resolveShortcodes($record['content'] ?? '');
});Changing the content data before it is saved
Runs before the main record of a piece of content is written.
Hook::add('filter:client.content_save_data', 10, function (&$set_data, $page_type, $id) {
// The options are TEXT: decode, edit, encode again.
$opt = Utility::jdecode($set_data['options'] ?? '', true) ?: [];
$opt['acme_reviewed'] = 1;
$set_data['options'] = Utility::jencode($opt);
});Changing the language data before it is saved
Runs separately for each language of the content. The body is raw markup, which makes this the right place to clean it.
Hook::add('filter:client.content_lang_data', 10,
function (&$set_lang_data, $lkey, $page_type) {
// The body is raw markup: this is the right place to clean it.
$set_lang_data['content'] = Acme::sanitize($set_lang_data['content'] ?? '');
});Following a page being saved
Runs after a page is saved.
Hook::add('action:client.page_saved', 10, function ($id, $page_type, $is_new) {
Acme::reindex('page', $id);
});Following a blog record being saved
Runs after a blog record is saved.
Hook::add('action:client.blog.saved', 10, function ($id, $is_new) {
Acme::reindex('page', $id);
});Following a news item being saved
Runs after a news item is saved.
Hook::add('action:client.news.saved', 10, function ($id, $is_new) {
Acme::reindex('page', $id);
});Following a slide being saved
Runs after a slide is saved.
Hook::add('action:client.slide.saved', 10, function ($id, $is_new) {
Acme::reindex('page', $id);
});Following a testimonial being saved
Runs after a customer testimonial on the site is saved.
Hook::add('action:client.feedback.saved', 10, function ($id, $status, $is_new) {
// One awaiting approval does not appear on the site.
if ($status === 'approved') Acme::publishTestimonial($id);
});Changing menu edits before they are saved
Runs before changes to the site menu are applied.
Hook::add('filter:client.menu_save_changes', 10, function (&$changes) {
// A batch: adding, editing, deleting and reordering can sit together.
$changes = array_values(array_filter($changes,
fn ($c) => ($c['type'] ?? '') !== 'delete' || Acme::menuDeletable($c['data'] ?? [])));
});Pitfalls
In the content save filter the options field arrives as encoded text. Writing a key into it as though it were an array corrupts the field and leaves the record unreadable. The fix: decode, edit, encode again.
The language data filter is called per language: three times on a three-language record. A listener keeping a counter or doing one-off work runs three times here. Use the main record filter for anything that should happen once.
Related Articles
Thanks for your feedback!
Our support team is here around the clock for anything you can't find above.