Customer-side Ticket Hooks

2 Aufrufe Markdown

The eight hooks over what a customer does: opening, replying, closing and rating.

Overview

Everything a customer can do on the support side lives here: opening a ticket, replying, closing, reopening and rating.

The difficulty of this branch is the question who did it. A sub-user may act on somebody else’s account and a guest may belong to no account at all, so the hooks carry the account and the login separately.

Reference

Following a customer opening a ticket

actionticket.opened_by_client
ClientTickets zero for a guest

Runs when a ticket is opened by a customer or through email.

Parameters 4
$ticketAfterarrayThe freshly created ticket.
$messagestringThe first message with its normalised body.
$clientIdintThe owner. It is zero for an unrecognised sender arriving by email: do not use it without looking the account up.
$replyIdintThe id of the first reply row.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:ticket.opened_by_client', 10,
    function ($ticketAfter, $message, $clientId, $replyId) {
        // It arrives as ZERO for an unrecognised sender.
        if (!$clientId) { Acme::queueIdentify($ticketAfter); return; }

        Acme::classify((int) ($ticketAfter['id'] ?? 0), $message);
    });

Stopping a customer reply

gateticket.reply_by_client
ClientTickets shape varies by source

Runs before a customer replies to a ticket. Replies from the panel, from email and from a guest link all pass here.

Parameters 5
$ticketarrayThe ticket being replied to. ? Its shape varies by source: what arrives from the panel and what arrives by email do not carry the same fields. Test a field before reaching for it.
$messagestringThe text to be sent, as plain text.
$ownerIdintThe account that owns the ticket. Zero for a guest.
$loginIdintThe login doing it. It differs from the owner when a sub-user replies on another account; zero for a guest.
$isGuestboolWhether it came through a guest link.
Return 1
string|nullA non-empty text blocks the operation and is shown as the error. An empty return lets it carry on.
Listener
Hook::add('gate:ticket.reply_by_client', 10,
    function ($ticket, $message, $ownerId, $loginId, $isGuest) {
        // For a guest both ids arrive as ZERO.
        if ($isGuest && Acme::guestRepliesClosed()) return 'Guest replies are closed.';

        return null;
    });

Following a customer reply

actionticket.reply_added_by_client
ClientTickets zero for a guest

Runs after a customer replies to a ticket.

Parameters 4
$ticketAfterarrayThe fresh ticket after the reply.
$messagestringThe message added.
$clientIdintWho wrote it; it can be zero on an anonymous email reply.
$replyIdintThe id of the reply added.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:ticket.reply_added_by_client', 10,
    function ($ticketAfter, $message, $clientId, $replyId) {
        Acme::restartSlaClock((int) ($ticketAfter['id'] ?? 0));
    });

Following a customer closing a ticket

actionticket.closed_by_client
ClientTickets two separate ids

Runs when a customer closes their own ticket.

Parameters 3
$ticketarrayThe snapshot from before closing.
$ownerIdintThe account the ticket belongs to.
$loginIdintThe identity that actually did it. When a sub-user closes another account’s ticket the two differ: this is the one that belongs in an audit record.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:ticket.closed_by_client', 10, function ($ticket, $ownerId, $loginId) {
    // The LOGIN id is the one that belongs in the audit.
    Acme::audit('ticket-closed', $loginId, $ownerId);
});

Following a customer reopening a ticket

actionticket.reopened_by_client
ClientTickets two separate ids

Runs when a customer reopens a closed ticket.

Parameters 3
$ticketarrayThe snapshot from before reopening.
$ownerIdintThe account the ticket belongs to.
$loginIdintThe login that did it.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:ticket.reopened_by_client', 10, function ($ticket, $ownerId, $loginId) {
    Acme::restartSlaClock((int) ($ticket['id'] ?? 0));
});

Following a rating

actionticket.rated
ClientTickets two scopes

Runs when a customer rates a ticket or a single staff reply. Two separate scopes land on the same hook.

Parameters 5
$scopestringWhat was rated: ticket or reply. Check this first: the two scopes fill their parameters differently.
$ratingintThe score, one to five. A value outside that range never reaches the hook.
$ticketIdintThe ticket id; filled in both scopes.
$replyIdintThe reply rated. It is zero in the ticket scope.
$ownerIdintThe account the ticket belongs to.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:ticket.rated', 10,
    function ($scope, $rating, $ticketId, $replyId, $ownerId) {
        // Check the scope first: the reply id is ZERO in the ticket scope.
        if ($scope === 'reply') { Acme::scoreAgent($replyId, $rating); return; }

        Acme::scoreTicket($ticketId, $rating);
    });

Following a guest viewing

actionticket.guest.viewed
ClientTickets carries the access value

Runs when somebody not signed in views a ticket through a link.

Parameters 2
$ticketarrayThe resolved ticket. ? It holds the sender’s name, email and access value. Whoever sees that value can open the ticket: keep it out of your records and logs.
$tokenstringThe access value from the address, already verified.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:ticket.guest.viewed', 10, function ($ticket, $token) {
    // Write the access value NOWHERE.
    Acme::countGuestView((int) ($ticket['id'] ?? 0));
});

Changing a customer message

filterticket.client_message
ClientTickets passed by link

Runs before the text a customer wrote is saved. New tickets and replies both pass here.

Parameters 2
$messagestringby linkThe message body, as plain text.
$ctxarrayContext: the source, the ticket id, the account and whether it is a guest. On a new ticket the id is zero: the ticket does not exist yet.
Return 1
voidThe return is ignored; you write over the data.
Listener
Hook::add('filter:ticket.client_message', 10, function (&$message, $ctx) {
    // On a new ticket the ticket id is ZERO.
    $message = Acme::stripSecrets($message);
});

Pitfalls

The account and the login are not the same thing

The closing, reopening and reply hooks carry two ids: the account the ticket belongs to and the login that actually did it. When a sub-user works on somebody else’s account they differ. The login belongs in the audit record; the permission decision follows the account.

The guest view hook carries the access value

The ticket data holds the sender’s name, email and access value. Whoever sees that value can open the ticket: writing it into your records, your logs or an outside service copies the link to a third place.

War das hilfreich?

Vielen Dank für Ihre Rückmeldung!

Brauchen Sie weitere Hilfe?

Unser Support-Team ist rund um die Uhr für Sie da, wenn Sie oben nicht fündig werden.