# Ticket View Hooks

https://dev.wisecp.com/es/ticket-view-hooks

The nine hooks over the lists, the conversation and the pickers on screen.

## Overview

Everything visible on screen passes through these hooks: the lists, the conversation thread, the department and service pickers, the data of the detail page.

One works differently from the rest: the list query hands you not ready rows but **the query itself**, and it is called twice for one listing.

## Reference

### Changing the department list

filterticket.departments

`ClientTickets` passed by link

Runs once the department list shown to a customer is built. Hiding a department also stops tickets being opened there.

Parameters 2

$departmentsarrayby linkThe visible departments.

$langstringThe active language.

Return 1

voidThe return is ignored; you write over the data.

Listener PHP

```php
Hook::add('filter:ticket.departments', 10, function (&$departments, $lang) {
    // Hiding one also stops tickets being opened there.
    $departments = array_values(array_filter($departments,
        fn ($d) => Acme::departmentOpen((int) ($d['id'] ?? 0))));
});
```

### Changing the priority options

filterticket.priorities

`ClientTickets` selected flag included

Runs once the priorities a customer may pick are prepared.

Parameters 1

$prioritiesarrayby linkThe options, each carrying a value, a label and whether it is selected. Drop the selected one and the form is left with nothing marked.

Return 1

voidThe return is ignored; you write over the data.

Listener PHP

```php
Hook::add('filter:ticket.priorities', 10, function (&$priorities) {
    // Drop the selected one and nothing stays marked on the form.
    $priorities = array_values(array_filter($priorities,
        fn ($p) => (int) ($p['value'] ?? 0) < 4));
});
```

### Changing the service picker

filterticket.related_services

`ClientTickets` grouped structure

Runs once the service picker shown while opening a ticket is prepared.

Parameters 2

$serviceGroupsarrayby linkThe grouped picker data; each group has a label and items. Do not flatten it: the template expects the group layer.

$uidintThe **active account** opening the ticket. Not the login: a sub-user may be listing another account’s services.

Return 1

voidThe return is ignored; you write over the data.

Listener PHP

```php
Hook::add('filter:ticket.related_services', 10, function (&$serviceGroups, $uid) {
    // Do not flatten it: the template expects the group layer.
    $serviceGroups[] = ['label' => 'Acme', 'items' => Acme::servicesFor($uid)];
});
```

### Changing the conversation thread

filterticket.thread

`ClientTickets` newest first

Runs before the reply thread of a ticket reaches the screen.

Parameters 2

$threadarrayby linkThe reply rows, **newest first**. Each carries its author, date and whether it came from staff.

$ctxarrayContext: the ticket id, the login id and whether it is a guest. For a guest the login id is zero.

Return 1

voidThe return is ignored; you write over the data.

Listener PHP

```php
Hook::add('filter:ticket.thread', 10, function (&$thread, $ctx) {
    // The order is newest first.
    foreach ($thread as $i => $row)
        if ($row['is_staff'] ?? false) $thread[$i]['author'] = Acme::displayName($row);
});
```

### Changing the detail page data

filterticket.detail_data

`AdminTickets` all the page data

Runs once the ticket detail page is prepared. What you hold is **all** the data going to the template.

Parameters 2

$dataarrayby linkAll the page data. Take care not to overwrite existing keys: a value lost here leaves a blank area on the page.

$ticketarrayThe active ticket.

Return 1

voidThe return is ignored; you write over the data.

Listener PHP

```php
Hook::add('filter:ticket.detail_data', 10, function (&$data, $ticket) {
    // Do not overwrite existing keys.
    $data['acme_risk'] = Acme::riskScore((int) ($ticket['user_id'] ?? 0));
});
```

### Narrowing the list query

filterticket.list_query

`AdminTickets` the query builder

Runs before the ticket list query is executed. What you hold is not ready rows but **the query itself**.

Parameters 3

$stmtobjectby linkThe query builder; you may chain conditions onto it.

$filtersarrayThe resolved filters: status, department, customer, assignment and priority.

$rCountboolWhether this is the **counting** query. ? The hook runs **twice** for one listing: once to count, once for the data. Add your condition to only one and the paging stops matching the rows.

Return 1

voidThe return is ignored; you chain the condition onto the builder.

Listener PHP

```php
Hook::add('filter:ticket.list_query', 10, function (&$stmt, $filters, $rCount) {
    // The hook runs TWICE: to count and for the data. Add it to both.
    $stmt->where('t.did', '!=', Acme::INTERNAL_DEPT);
});
```

### Changing the list rows

filterticket.list_rows

`ClientTickets` per page

Runs before the customer ticket list reaches the screen.

Parameters 2

$rowsarrayby linkThe rows of that page: reference, subject, status, rating and department. Only the **page being viewed** arrives, not the whole list.

$uidintThe active account; the rows are already scoped to it.

Return 1

voidThe return is ignored; you write over the data.

Listener PHP

```php
Hook::add('filter:ticket.list_rows', 10, function (&$rows, $uid) {
    // Only the page being viewed arrives.
    foreach ($rows as $i => $r) $rows[$i]['subject'] = Acme::shorten($r['subject'] ?? '');
});
```

### Narrowing a bulk action

filterticket.bulk_action_ids

`AdminTickets` narrowing only

Runs before a bulk action is applied. This is how you keep certain tickets out of it.

Parameters 2

$idsarrayby linkThe tickets to be handled. This list is for **narrowing**: remove entries. Adding one closes or deletes a ticket the administrator never picked.

$actionstringThe action applied: closing, deleting and their blocking variants.

Return 1

voidThe return is ignored; you write over the data.

Listener PHP

```php
Hook::add('filter:ticket.bulk_action_ids', 10, function (&$ids, $action) {
    // It is for NARROWING: do not add.
    $ids = array_values(array_filter($ids, fn ($id) => !Acme::underLegalHold((int) $id)));
});
```

### Changing the access groups

filterticket.access_groups

`ClientTickets` passed by link

Runs once the access groups shown while opening a ticket are prepared.

Parameters 2

$groupsarrayby linkThe visible groups: id, name and icon.

$langstringThe active language of the page.

Return 1

voidThe return is ignored; you write over the data.

Listener PHP

```php
Hook::add('filter:ticket.access_groups', 10, function (&$groups, $lang) {
    $groups = array_values(array_filter($groups,
        fn ($g) => Acme::groupVisible((int) ($g['id'] ?? 0))));
});
```

## Pitfalls

> **The list query runs twice for one listing**
> 
> The hook is called once for the **count** and once for the **data**. Add your condition to only one and the paging stops matching the real row count: the user sees empty pages. Unless you branch on the counting flag, add it to both.

> **The bulk list is for narrowing**
> 
> Entries are **removed** through this filter. Adding one closes or deletes a ticket the administrator never picked, and the list they approved on screen parts ways with the list actually handled.

## Related Articles

- Support Ticket Hooks
- [Customer Account Hooks](https://dev.wisecp.com/en/customer-account-hooks)
- [How Hooks Work](https://dev.wisecp.com/en/how-hooks-work)
