Staff Reply Hooks

329 visualizzazioni Markdown

The eight hooks over staff replies, internal notes and assignment.

Overview

Every point where staff touch a ticket lives here: writing a reply, editing it, deleting it, adding an internal note and assigning.

There are three reply hooks and they should not be confused: the one staff write, the one a customer writes and the one a scheduled task writes. The last carries its parameters in a single array.

Reference

Stopping a staff reply

gateticket.reply
AdminTickets signature attached

Runs before a staff reply is sent.

Parameters 3
$ticketarrayThe ticket being replied to.
$messagestringThe final text. It carries markup and the signature is already attached: allow for that if you measure length.
$admin_idintThe staff member writing it.
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', 10, function ($ticket, $message, $admin_id) {
    // The signature is already in: allow for it when measuring length.
    if (Acme::containsSecret($message)) return 'The reply holds a value that must not be shared.';

    return null;
});

Following a staff reply

actionticket.reply_added
AdminTickets hydrated reply

Runs after a staff reply is added.

Parameters 3
$ticketarrayThe fresh ticket after the reply.
$reply_idintThe id of the reply added.
$replyarrayThe whole reply: its text, author, attachments and address.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:ticket.reply_added', 10, function ($ticket, $reply_id, $reply) {
    Acme::stopSlaClock((int) ($ticket['id'] ?? 0));
});

Following an automatic reply

actionticket.replied
cronjobs one context array

Runs when a scheduled task adds a reply to a ticket. Unlike its siblings the parameters arrive in a single array.

Parameters 1
$contextarrayEverything is here: the source, the current ticket and the reply added. Expect no separate parameters; this hook carries one array.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:ticket.replied', 10, function ($context) {
    // One array: different from its siblings.
    $ticket = $context['request'] ?? [];
    Acme::noteAutoReply((int) ($ticket['id'] ?? 0));
});

Following a reply being edited

actionticket.reply_updated
AdminTickets after the edit

Runs after a reply is edited.

Parameters 3
$ticketIdintThe ticket id.
$replyIdintThe id of the edited reply.
$replyarrayThe updated reply. The previous version is not passed: to compare, you must have kept a copy yourself.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:ticket.reply_updated', 10, function ($ticketId, $replyId, $reply) {
    // The previous version is not passed.
    Acme::reindexReply($replyId, $reply['message'] ?? '');
});

Following a reply being deleted

actionticket.reply_deleted
AdminTickets after deletion

Runs after a reply is deleted.

Parameters 3
$ticketIdintThe ticket id.
$replyIdintThe id of the deleted reply.
$replyarrayThe deleted reply record: its owner and whether it was from staff.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:ticket.reply_deleted', 10, function ($ticketId, $replyId, $reply) {
    Acme::dropFromIndex('reply', $replyId);
});

Stopping an internal note

gateticket.note_add
AdminTickets the customer never sees it

Runs before staff add an internal note. Notes are never shown to the customer.

Parameters 3
$ticketarrayThe ticket the note goes on.
$messagestringThe note content.
$admin_idintThe staff member adding it.
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.note_add', 10, function ($ticket, $message, $admin_id) {
    if (Acme::tooLong($message)) return 'The note is too long.';

    return null;
});

Following an internal note

actionticket.note_added
AdminTickets the note may be empty

Runs after an internal note is added.

Parameters 3
$ticketarrayThe ticket the note went on.
$admin_idintThe staff member who added it.
$notearrayThe note data: its text, whether it is pinned, and attachments. ? It can arrive empty when the note could not be read. Test before reaching into it.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:ticket.note_added', 10, function ($ticket, $admin_id, $note) {
    // The note CAN be empty.
    if (!$note) return;

    Acme::mirrorNote((int) ($ticket['id'] ?? 0), $note['message'] ?? '');
});

Following an assignment

actionticket.assigned
AdminTickets zero means unassigned

Runs when a ticket is assigned to staff or unassigned.

Parameters 3
$ticketarrayThe ticket before the assignment.
$old_assigned_idintThe previous assignee; a zero means none.
$new_assigned_idintThe new assignee; a zero means it was unassigned.
Return 1
voidThe return is ignored.
Listener
Hook::add('action:ticket.assigned', 10,
    function ($ticket, $old_assigned_id, $new_assigned_id) {
        // Zero means it was unassigned.
        if (!$new_assigned_id) { Acme::backToPool((int) ($ticket['id'] ?? 0)); return; }

        Acme::notifyAgent($new_assigned_id, (int) ($ticket['id'] ?? 0));
    });

Pitfalls

The note data can be empty

The third parameter of the internal note hook is empty when the note could not be read. A listener reaching straight into it fails there; test on the first line.

The automatic reply hook carries one array

Its siblings give the ticket, the reply id and the reply as separate parameters; the scheduled-task reply carries all of it in a single context array. A listener expecting the same signature works with empty values here.

Ti è stato utile?

Grazie per il tuo feedback!

Hai ancora bisogno di aiuto?

Il nostro team di assistenza è disponibile 24 ore su 24 per aiutarti a trovare le risposte che cerchi.