Site Content Hooks

3 Aufrufe 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.

War das hilfreich?

Vielen Dank für Ihre Rückmeldung!

Brauchen Sie weitere Hilfe?

Unser Support-Team ist rund um die Uhr für Sie da, wenn Sie oben nicht fündig werden.