# Customer Panel Data Hooks

https://dev.wisecp.com/es/client-panel-data-hooks

The fifteen data filters of the customer panel: account tabs, home page panels, invoice lists, and the partner and reseller screens.

## Overview

The filters over the data the customer panel carries to the screen live here: the account tabs, the home page panels, the invoice lists, and the partner and reseller screens.

Two things recur in this group. First the **active account**: when a sub-user looks at another account, the id you hold belongs to that account rather than to whoever signed in. Second, amounts arrive **formatted**: read the raw value separately if you compare.

## Reference

### Hiding account tabs

filterclient.account.tabs

`ClientAccount` a visibility map

Runs before the tabs of a customer account appear. Closing a tab takes the work behind it out of sight too.

Parameters 3

$tabsarrayby linkTab against visibility: profile, contacts, activity, messages, notifications, security and verification. ? This is **visibility only**: closing a tab does not close the operation behind it. To really block access, use the matching gate as well.

$uidintby linkThe id of the **active account**. It is not the id of whoever signed in: when a sub-user is looking at another account, that account’s id arrives.

$selfboolby linkWhether the active account is the person’s own. A false means a sub-user is looking at another account.

Return 1

voidThe return is ignored; you write over the data.

Listener PHP

```php
Hook::add('filter:client.account.tabs', 10, function (&$tabs, &$uid, &$self) {
    // Visibility ONLY: it does not close the operation.
    if (!$self) $tabs['security'] = false;
});
```

### Widening the data that reaches the template

filterclient.predefined_data

`Controllers` on every page

Runs before **every page** of the customer site appears. Every key you add here becomes a template variable.

Parameters 2

$dataarrayby linkThe data bag of the page. ? Overwrite an existing key and **the page loses its own data**. Give your keys a prefix.

$ctxarrayby linkContext: the active route and controller name. The hook runs on every page, so check which one you are on before doing anything heavy.

Return 1

voidThe return is ignored; you write over the data.

Listener PHP

```php
Hook::add('filter:client.predefined_data', 10, function (&$data, &$ctx) {
    // It runs on EVERY page: check the route first.
    if (($ctx['route'] ?? '') !== 'my-account') return;

    $data['acme_banner'] = Acme::accountBanner();
});
```

### Changing the home page panels

filterclient.homepage_panels

`website/home` four panels

Runs once the panels of the home page are prepared.

Parameters 1

$panelsarrayby linkThe data of four panels: spotlight extensions, product cards, feature blocks and announcements. All four arrive in one array: change it without breaking the shape, since the template expects all four keys.

Return 1

voidThe return is ignored; you write over the data.

Listener PHP

```php
Hook::add('filter:client.homepage_panels', 10, function (&$panels) {
    // All four keys must stay.
    $panels['product_cards'] = Acme::reorderCards($panels['product_cards'] ?? []);
});
```

### Changing the maintenance decision

filterclient.maintenance

`Kernel` reads backwards

Runs after the maintenance decision is made and before the page is served. This is how you keep certain addresses out of maintenance.

Parameters 3

$passive_maintenanceboolby linkWhat the core decided. ? It **reads backwards**: true means maintenance is **skipped**, false means the maintenance page is shown. Setting it true because the name sounds right takes the site out of maintenance.

$controllerstringThe name of the requested controller.

$foundAdminboolWhether the request is on the admin address.

Return 1

voidThe return is ignored; you write over the data.

Listener PHP

```php
Hook::add('filter:client.maintenance', 10,
    function (&$passive_maintenance, $controller, $foundAdmin) {
        // It reads BACKWARDS: true = maintenance is SKIPPED.
        if ($controller === 'status') $passive_maintenance = true;
    });
```

### Changing the invoice list rows

filterclient.invoice_list_data

`ClientInvoices` formatted rows

Runs before the customer invoice list reaches the screen.

Parameters 2

$rowsarrayby linkThe invoice rows: number, badge, payment link, amount and due line. The amounts are formatted text: read the invoice separately if you need to compare.

$uidintThe customer whose list is on screen.

Return 1

voidThe return is ignored; you write over the data.

Listener PHP

```php
Hook::add('filter:client.invoice_list_data', 10, function (&$rows, $uid) {
    foreach ($rows as $i => $r) $rows[$i]['acme_note'] = Acme::noteFor($r['id'] ?? 0);
});
```

### Changing the invoice summary

filterclient.invoice_list_summary

`ClientInvoices` tiles and the notice

Runs once the summary above the invoice list is prepared.

Parameters 2

$summaryarrayby linkTwo parts: the summary tiles (all, paid, unpaid, overdue) and the outstanding notice. If you change the numbers, update the notice text too: the two are read from different places and can part ways.

$uidintThe customer whose summary is on screen.

Return 1

voidThe return is ignored; you write over the data.

Listener PHP

```php
Hook::add('filter:client.invoice_list_summary', 10, function (&$summary, $uid) {
    // If you change the numbers, update the notice text too.
    $summary['outstanding']['show'] = Acme::hideOutstanding($uid) ? false : ($summary['outstanding']['show'] ?? false);
});
```

### Changing the invoice share address

filterclient.invoice_share_url

`ClientInvoices` access without login

Runs after the address is produced when a customer shares an invoice.

Parameters 3

$shareUrlstringby linkThe share address produced. ? That address **needs no sign-in**: whoever sees it can open the invoice and the payment page. Writing it into your own store copies the access too.

$idintThe invoice id.

$ownerIdintThe owner of the invoice.

Return 1

voidThe return is ignored; you write over the data.

Listener PHP

```php
Hook::add('filter:client.invoice_share_url', 10, function (&$shareUrl, $id, $ownerId) {
    // The address needs no sign-in: keep it out of your own store.
    $shareUrl = Acme::shorten($shareUrl);
});
```

### Changing the invoice transaction list

filterclient.invoice_transactions

`ClientInvoices` refunds included

Runs before the payment movements of an invoice reach the screen.

Parameters 3

$rowsarrayby linkThe movement rows: method, reference, date, whether it is a refund, and the amount. Refunds are in this list too: mind the flag when totalling.

$idintThe invoice id.

$invCidintThe currency of the invoice.

Return 1

voidThe return is ignored; you write over the data.

Listener PHP

```php
Hook::add('filter:client.invoice_transactions', 10, function (&$rows, $id, $invCid) {
    // Refunds are in this list too.
    foreach ($rows as $i => $r)
        if ($r['refund'] ?? false) $rows[$i]['method'] = 'Acme refund';
});
```

### Changing the bulk payment list

filterclient.bulk_pay_rows

`ClientInvoices` the active account

Runs on the screen where a customer pays several invoices together.

Parameters 2

$rowsarrayby linkThe invoice rows that can be paid. An invoice you remove becomes unpayable here: the customer has to pay it separately.

$uidintby linkThe id of the **active account**. It is not the id of whoever signed in: when a sub-user is looking at another account, that account’s id arrives.

Return 1

voidThe return is ignored; you write over the data.

Listener PHP

```php
Hook::add('filter:client.bulk_pay_rows', 10, function (&$rows, &$uid) {
    // An invoice you remove becomes unpayable here.
    $rows = array_values(array_filter($rows, fn ($r) => !Acme::onHold($r['id'] ?? 0)));
});
```

### Changing the commission list

filterclient.affiliate_commissions

`ClientAffiliate` three states

Runs before a partner’s commission list reaches the screen.

Parameters 2

$commissionsarrayby linkThe commission rows; each carries a **state**: available, clearing or rejected. Keep the three apart when totalling: adding them all shows the partner money they will never receive.

$commissionsCtxarrayContext: the account id, the partner record and the currency.

Return 1

voidThe return is ignored; you write over the data.

Listener PHP

```php
Hook::add('filter:client.affiliate_commissions', 10,
    function (&$commissions, $commissionsCtx) {
        // Three states: do not total them all.
        $commissions = array_values(array_filter($commissions,
            fn ($c) => ($c['flag'] ?? '') !== 'rejected'));
    });
```

### Changing the payout channel list

filterclient.affiliate_gateways

`ClientAffiliate` a list of labels

Runs once the payout channels a partner may pick are prepared.

Parameters 2

$outarrayby linkThe channel **labels**. It is a list of plain text, not ids, and the choice is stored as that text. Changing a label can break the match with older records.

$gatewaysCtxarrayContext: the language the labels were resolved in.

Return 1

voidThe return is ignored; you write over the data.

Listener PHP

```php
Hook::add('filter:client.affiliate_gateways', 10, function (&$out, $gatewaysCtx) {
    // Changing a label can break the match with older records.
    $out[] = 'Acme Wallet';
});
```

### Changing the referral destination

filterclient.affiliate_referral_redirect

`ClientAffiliate` invalid codes arrive too

Runs when a visitor clicks a partner link, before the redirect happens.

Parameters 2

$redirectstringby linkThe redirect destination.

$redirectCtxarrayContext: the raw code in the address, the resolved partner and whether the code is valid. ? The hook **runs on an invalid code too**: a listener using the partner id without checking the valid flag works with a zero.

Return 1

voidThe return is ignored; you write over the data.

Listener PHP

```php
Hook::add('filter:client.affiliate_referral_redirect', 10,
    function (&$redirect, $redirectCtx) {
        // It runs on an invalid code too: check the flag first.
        if (!($redirectCtx['valid'] ?? false)) return;

        $redirect = Acme::landingFor((int) ($redirectCtx['owner_id'] ?? 0)) ?: $redirect;
    });
```

### Changing the payout request amount

filterclient.affiliate_withdraw_amount

`ClientAffiliate` the partner currency

Runs while a partner requests a payout, before the amount is saved. What you write is what gets recorded.

Parameters 4

$amountfloatby linkThe amount requested.

$uidintThe account requesting.

$cidintThe **partner’s** currency. It is not the currency selected in the store: use this one if you convert.

$amountCtxarrayContext information.

Return 1

voidThe return is ignored; you write over the data.

Listener PHP

```php
Hook::add('filter:client.affiliate_withdraw_amount', 10,
    function (&$amount, $uid, $cid, $amountCtx) {
        // The currency is the PARTNER'S, not the store's.
        $amount = Acme::roundToPayoutStep($amount, $cid);
    });
```

### Changing the reseller statistics

filterclient.reseller_stats

`ClientReseller` worked out from invoices

Runs once the figures on the reseller board are worked out.

Parameters 2

$statsarrayby linkThe reseller figures: total sales, turnover and discounts, each with today’s counterpart.

$statsCtxarrayContext: the account id of the reseller.

Return 1

voidThe return is ignored; you write over the data.

Listener PHP

```php
Hook::add('filter:client.reseller_stats', 10, function (&$stats, $statsCtx) {
    $stats['acme_target'] = Acme::monthlyTarget((int) ($statsCtx['uid'] ?? 0));
});
```

### Changing the reseller tiers

filterclient.reseller_tiers

`ClientReseller` two separate surfaces

Runs once the reseller tier list is prepared. The hook is used on **two separate screens**.

Parameters 2

$tiersarrayby linkThe tier rows.

$tiersCtxarrayContext: which screen and the account id. ? On the programme page the account id is **zero** (the visitor may not be signed in); on the board the reseller’s id arrives. A rule depending on the id must behave differently on the two.

Return 1

voidThe return is ignored; you write over the data.

Listener PHP

```php
Hook::add('filter:client.reseller_tiers', 10, function (&$tiers, $tiersCtx) {
    // On the programme page the account id arrives as ZERO.
    if (($tiersCtx['scope'] ?? '') === 'program') return;

    $tiers = Acme::highlightCurrent($tiers, (int) ($tiersCtx['uid'] ?? 0));
});
```

## Pitfalls

> **The maintenance flag reads backwards**
> 
> The value in the maintenance filter answers not "is maintenance on" but **"will maintenance be skipped"**. A listener setting it true because the name sounds right **opens the site to visitors** while you meant it closed. Confirm the direction before changing it.

> **Hiding a tab does not close access**
> 
> The account tab filter governs **visibility** only. Closing a tab does not close the operation behind it: somebody who knows the address can still request it directly. Use the matching gate to really block access.

## Related Articles

- [Customer Site Hooks](https://dev.wisecp.com/en/hooks-on-the-customer-site)
- [Partner Programme Hooks](https://dev.wisecp.com/en/affiliate-hooks)
- [Invoice Lifecycle Hooks](https://dev.wisecp.com/en/invoice-lifecycle-hooks)
