Product Group and Add-on Hooks

1 views Markdown

The seven hooks over groups, categories, add-ons and requirements.

Overview

The structures around products live here: the groups that hold them, the add-ons sold beside them and the requirements asked for during an order.

Two shapes surprise. Group options arrive as an array or as text depending on the context. And the category gate receives not one record but a batch, which may include deletions.

Reference

Changing the group data

filterproduct.group_save_data
AdminProducts options in two shapes

Runs before a product group or category is saved.

Parameters 3
$set_dataarrayby linkThe data to be written: status, visibility, type and options. ? The shape of the options field depends on the context: an array on a constant group, text elsewhere. Reading it as an array without checking fails.
$categorystringThe category context; empty for a top group, filled for a category kind.
$detailarrayThe existing record; empty on a new one.
Return 1
voidThe return is ignored; you write over the data.
Listener
Hook::add('filter:product.group_save_data', 10, function (&$set_data, $category, $detail) {
    // Options may be an array or text: check the type first.
    if (!is_array($set_data['options'] ?? null)) return;

    $set_data['options']['acme_tag'] = Acme::tagFor($category);
});

Following a group being saved

actionproduct.group_saved
AdminProducts zero on a constant record

Runs after a product group or category is saved.

Parameters 3
$idintThe id of the saved record. On a constant category record it can arrive as zero.
$is_newboolTrue when newly created.
$categorystringThe category kind; empty means a top group.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:product.group_saved', 10, function ($id, $is_new, $category) {
    // On a constant category the id can be ZERO.
    if ($id) Acme::refreshGroupMenu($id);
});

Stopping a group deletion

gateproduct.group_delete
AdminProducts before the write

Runs before a product group or category is deleted.

Parameters 3
$idintThe id to be deleted.
$categorystringThe category context.
$detailarrayThe language record of what goes, title included.
Return 1
string|nullA non-empty text blocks the operation and is shown to the administrator as the error. An empty return lets it carry on.
Listener
Hook::add('gate:product.group_delete', 10, function ($id, $category, $detail) {
    if (Acme::groupHasProducts($id)) return 'A group holding products cannot be deleted.';

    return null;
});

Stopping a batch category operation

gateproduct.category_save
AdminProducts a batch of operations

Runs before add-on or requirement categories are saved. What arrives is not one record but a batch: one request can hold creations, updates and deletions together.

Parameters 2
$typestringThe category kind: addon or requirement.
$categoriesarrayThe operations to apply, each carrying its kind and an id or a title. A deletion can sit in this list too: a check that only looks at creations misses it.
Return 1
string|nullA non-empty text blocks the operation and is shown to the administrator as the error. An empty return lets it carry on.
Listener
Hook::add('gate:product.category_save', 10, function ($type, $categories) {
    // The batch may hold DELETIONS too: walk all of them.
    foreach ($categories as $op)
        if (($op['action'] ?? '') === 'delete' && Acme::categoryInUse((int) ($op['id'] ?? 0)))
            return 'A category in use cannot be deleted.';

    return null;
});

Changing the add-on data

filterproduct.addon_save_data
AdminProducts passed by link

Runs before a product add-on is saved.

Parameters 2
$addonDataarrayby linkThe add-on data to be written: category, status, rank, tax exemption and the product it is tied to.
$detailarrayThe existing record; empty on a new one.
Return 1
voidThe return is ignored; you write over the data.
Listener
Hook::add('filter:product.addon_save_data', 10, function (&$addonData, $detail) {
    $addonData['status'] = Acme::allowedAddon($addonData) ? ($addonData['status'] ?? 0) : 0;
});

Stopping an add-on deletion

gateproduct.addon_delete
AdminProducts before the write

Runs before a product add-on is deleted.

Parameters 2
$idintThe id of the add-on to be deleted.
$detailarrayThe add-on record.
Return 1
string|nullA non-empty text blocks the operation and is shown to the administrator as the error. An empty return lets it carry on.
Listener
Hook::add('gate:product.addon_delete', 10, function ($id, $detail) {
    if (Acme::addonInUse($id)) return 'An add-on live on a customer cannot be deleted.';

    return null;
});

Stopping a requirement deletion

gateproduct.requirement_delete
AdminProducts before the write

Runs before a product requirement is deleted. Requirements are what a customer is asked for during an order, so removing one touches orders already waiting.

Parameters 2
$idintThe id of the requirement to be deleted.
$detailarrayThe requirement record.
Return 1
string|nullA non-empty text blocks the operation and is shown to the administrator as the error. An empty return lets it carry on.
Listener
Hook::add('gate:product.requirement_delete', 10, function ($id, $detail) {
    if (Acme::pendingOrdersNeed($id)) return 'Orders still waiting use this requirement.';

    return null;
});

Pitfalls

The shape of group options depends on context

The options field arrives as an array on a constant group record and as text otherwise. Reading it as an array without checking raises an error; test the type on the first line.

The category gate gets a batch, not one record

One request can hold creations, updates and deletions together. A check that only looks at creations misses the deletion sitting in the same request.

Was this helpful?

Thanks for your feedback!

Still Need Help?

Our support team is here around the clock for anything you can't find above.