Site Content Hooks

2 vues Markdown

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

gateclient.content_delete
ClientContent five content types

Runs before a piece of site content is deleted. Pages, contracts, news, articles and references share this gate.

Parameters 2
$pagearrayThe record about to be deleted.
$page_typestringThe content type. Deleting a contract can affect the order flow tied to it: be stricter for some types.
Return 1
string|nullA non-empty text blocks the operation and is shown as the error. An empty return lets it carry on.
Listener
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

actionclient.content.deleted
ClientContent after deletion

Runs after a piece of site content is deleted.

Parameters 3
$idintThe id of the deleted record.
$page_typestringThe content type.
$pagearrayThe record as read before deletion.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:client.content.deleted', 10, function ($id, $page_type, $page) {
    Acme::dropFromIndex('page', $id);
});

Following content being viewed

actionclient.content.viewed
ClientContent on every visit

Runs when a piece of site content is visited. The visitor may not be logged in.

Parameters 3
$idintThe id of the record viewed.
$typestringThe content type.
$recordarrayThe record data, with the visit counter already increased. Its shape follows the type: a blog record carries a category, a plain page does not.
Return 1
voidThe return is ignored.
Listener
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

filterclient.content_detail.data
ClientContent passed by link

Runs before the content reaches the screen. The place to resolve your own shortcodes or adjust the search headings.

Parameters 3
$recordarrayby linkThe record data: title, body, link and search headings.
$idintby linkThe record id.
$typestringby linkThe content type.
Return 1
voidThe return is ignored; you write over the data.
Listener
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

filterclient.content_save_data
ClientContent options are text

Runs before the main record of a piece of content is written.

Parameters 3
$set_dataarrayby linkThe data to be written. ? The options field is text, not an array: to change it you must decode, edit and encode again. Writing to it as an array corrupts the record.
$page_typestringThe content type.
$idintThe record id; zero on a new record.
Return 1
voidThe return is ignored; you write over the data.
Listener
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

filterclient.content_lang_data
ClientContent per language

Runs separately for each language of the content. The body is raw markup, which makes this the right place to clean it.

Parameters 3
$set_lang_dataarrayby linkThe data of that language: title, address, body and search headings. Leave the address empty and it falls back to the record id.
$lkeystringThe language key.
$page_typestringThe content type.
Return 1
voidThe return is ignored; you write over the data. The hook runs once per language: on a two-language record you are called twice.
Listener
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

actionclient.page_saved
ClientContent create and edit

Runs after a page is saved.

Parameters 3
$idintThe record id; on a new record the real id is passed.
$page_typestringThe page type.
$is_newboolTrue when newly created.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:client.page_saved', 10, function ($id, $page_type, $is_new) {
    Acme::reindex('page', $id);
});

Following a blog record being saved

actionclient.blog.saved
ClientContent create and edit

Runs after a blog record is saved.

Parameters 2
$idintThe record id; on a new record the real id is passed.
$is_newboolTrue when newly created.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:client.blog.saved', 10, function ($id, $is_new) {
    Acme::reindex('page', $id);
});

Following a news item being saved

actionclient.news.saved
ClientContent create and edit

Runs after a news item is saved.

Parameters 2
$idintThe record id; on a new record the real id is passed.
$is_newboolTrue when newly created.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:client.news.saved', 10, function ($id, $is_new) {
    Acme::reindex('page', $id);
});

Following a slide being saved

actionclient.slide.saved
ClientContent create and edit

Runs after a slide is saved.

Parameters 2
$idintThe record id; on a new record the real id is passed.
$is_newboolTrue when newly created.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:client.slide.saved', 10, function ($id, $is_new) {
    Acme::reindex('page', $id);
});

Following a testimonial being saved

actionclient.feedback.saved
ClientContent the approval state

Runs after a customer testimonial on the site is saved.

Parameters 3
$idintThe testimonial id.
$statusstringIts state: approved or awaiting approval. One awaiting approval does not appear on the site: check before doing anything tied to publication.
$is_newboolTrue when newly created.
Return 1
voidThe return is ignored.
Listener
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

filterclient.menu_save_changes
ClientContent a batch of operations

Runs before changes to the site menu are applied.

Parameters 1
$changesarrayby linkThe changes to apply. It is not one record but a batch: each entry carries an operation type and its data. Adding, editing, deleting and reordering can sit together in one request.
Return 1
voidThe return is ignored; you write over the data.
Listener
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

The options field is text, not an array

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 filter runs once per language

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.

Cet article vous a-t-il été utile ?

Merci pour votre retour !

Besoin d'aide supplémentaire ?

Notre équipe d'assistance est disponible 24h/24 pour tout ce que vous ne trouvez pas ci-dessus.