# Product Group and Add-on Hooks

https://dev.wisecp.com/es/product-group-hooks

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 PHP

```php
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 PHP

```php
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|null**A non-empty text blocks the operation** and is shown to the administrator as the error. An empty return lets it carry on.

Listener PHP

```php
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|null**A non-empty text blocks the operation** and is shown to the administrator as the error. An empty return lets it carry on.

Listener PHP

```php
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 PHP

```php
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|null**A non-empty text blocks the operation** and is shown to the administrator as the error. An empty return lets it carry on.

Listener PHP

```php
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|null**A non-empty text blocks the operation** and is shown to the administrator as the error. An empty return lets it carry on.

Listener PHP

```php
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.

## Related Articles

- Product Hooks
- [Service Hooks](https://dev.wisecp.com/en/service-lifecycle-hooks)
- [How Hooks Work](https://dev.wisecp.com/en/how-hooks-work)
