Product Data and Catalogue Hooks

1 views Markdown

The eight hooks over product data, the catalogue, the card lists and stock.

Overview

Product data flows two ways: through the save filter on the way in, and through the read filter on the way out. The second is a hot path and its result goes into the cache.

The rest build the lists a customer sees: catalogue plans, related products, software cards. The stock hook stands apart; it is moved not by an administrator but by an order.

Reference

Changing the product data before it is saved

filterproduct.save_data
AdminProducts passed by link

Runs before the product data is written. This is how you hold one pricing policy in one place.

Parameters 2
$inputarrayby linkThe data to be written: type, options, module data, languages, pricing and tax.
$detailarrayThe existing record before the change.
Return 1
voidThe return is ignored; you write over the data.
Listener
Hook::add('filter:product.save_data', 10, function (&$input, $detail) {
    // Hold the pricing policy in one place.
    $input['pricing'] = Acme::applyMargin($input['pricing'] ?? []);
});

Changing a product as it is read

filterproduct.get
Products::get the result is cached

Runs after a product is read from the database. Every product read passes here: the catalogue, the basket, the service detail.

Parameters 2
$productarrayby linkThe resolved product row with its options and module data unpacked. When no product was found an empty array arrives; look before you reach into it.
$contextarrayThe id asked for and the language.
Return 1
voidThe return is ignored; you write over the product. ? The array you change goes into the cache: a mistake here shows up not on one read but on every read that follows.
Listener
Hook::add('filter:product.get', 10, function (&$product, $context) {
    // A product that was not found arrives as an EMPTY array.
    if (!$product) return;

    $product['acme_badge'] = Acme::badgeFor((int) ($context['id'] ?? 0));
});

Changing the product configuration fields

filterproduct.config_options
AdminProducts empty means no form

Runs before the configuration fields offered by the module appear.

Parameters 3
$config_optionsarrayby linkThe field definitions to be shown.
$moduleobjectThe product module instance.
$module_dataarrayThe current module data of the product.
Return 1
voidThe return is ignored; you write over the definitions. If the array is left empty after the filter, no form appears at all: clearing the fields removes the form.
Listener
Hook::add('filter:product.config_options', 10, function (&$config_options, $module, $module_data) {
    // Emptying the array removes the form entirely.
    unset($config_options['legacy_option']);
});

Changing the catalogue plans

filterproduct.catalog_plans
website/products passed by link

Runs once the plan list on the catalogue page is prepared. Ordering, hiding and badging belong here.

Parameters 2
$plansarrayby linkThe plan list.
$ctxarrayContext: the category, the product kind and the layout.
Return 1
voidThe return is ignored; you write over the data.
Listener
Hook::add('filter:product.catalog_plans', 10, function (&$plans, $ctx) {
    $plans = Acme::sortByPopularity($plans);
});
filterproduct.related_products
website/products passed by link

Runs once the related product list under a product detail is built. The core default is up to four products from the same catalogue.

Parameters 2
$relatedarrayby linkThe card list.
$idintby linkThe id of the product on screen; this is the "related to what" context.
Return 1
voidThe return is ignored; you write over the data.
Listener
Hook::add('filter:product.related_products', 10, function (&$related, &$id) {
    $related = Acme::recommendFor($id) ?: $related;
});

Changing the software card list

filterproduct.software_list
website/products passed by link

Runs once the card list of the software store is prepared.

Parameters 2
$outarrayby linkThe card list; each card carries a title, category, image and tags.
$ctxarrayContext: the currency the cards are priced in. The currency varies by visitor: if you produce a price, use this value rather than the default.
Return 1
voidThe return is ignored; you write over the data.
Listener
Hook::add('filter:product.software_list', 10, function (&$out, $ctx) {
    // If you produce a price, use the currency from the context.
    $out = Acme::attachOffers($out, (int) ($ctx['currency'] ?? 0));
});

Following a stock change

actionproduct.stock_changed
Orders driven by an order

Runs when the stock of a product moves. The move is not manual: an order becoming active, or ceasing to be, drives it.

Parameters 4
$product_idintThe product id.
$deltaintWhich way it moved: minus one means an order became active and stock came down, plus one means an order left and stock returned.
$new_stockintThe new stock written. It never goes below zero: expect no negative value.
$order_idintThe order that drove the change.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:product.stock_changed', 10,
    function ($product_id, $delta, $new_stock, $order_id) {
        // Stock never goes below zero.
        if ($new_stock === 0) Acme::alertOutOfStock($product_id);
    });

Changing per-country message pricing

filterproduct.intl_sms_country_pricing_save
AdminProducts passed by link

Runs before international text message prices are saved.

Parameters 1
$current_listarrayby linkCountry code against price: cost, selling amount, currency and status.
Return 1
voidThe return is ignored; you write over the data.
Listener
Hook::add('filter:product.intl_sms_country_pricing_save', 10, function (&$current_list) {
    foreach ($current_list as $code => $row)
        $current_list[$code]['amount'] = Acme::markup((float) ($row['cost'] ?? 0));
});

Pitfalls

The read filter result goes into the cache

A change you make in the product read filter is kept and comes back on later reads. A mistake here shows up not on one page but everywhere until the cache is cleared.

An emptied configuration removes the form

If the array is empty after the configuration fields filter, no form appears at all. Clearing everything while meaning to hide one field leaves the administrator unable to configure the product.

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.