Reply Subject Developer Guide - edmozley/freeitsm GitHub Wiki
Reply subject, and replying to one message — Developer Guide
5.2.0 · #306 · User guide: Tickets § The subject
🔑 The one-line version
The Reply / Forward dialog's subject used to be a hidden field built from the ticket's subject. That lost an outside provider's own number (#TCK89633), so every follow-up opened a new ticket on their side. Now:
- the subject is a visible box;
- each message in the thread shows its subject and has its own Reply / Forward, which start from that subject;
- the server's
ticketOutboundSubject()makes sure the ticket's[SDREF:...]survives whatever is typed.
📁 Files
| File | What |
|---|---|
includes/email_threading.php |
ticketOutboundSubject($subject, $ticketNumber) |
api/tickets/send_email.php |
Calls it after the access check, before anything is sent or saved |
api/tickets/get_ticket_thread.php |
Returns each email's subject |
tickets/index.php |
#emailSubject is a text box (.reply-subject, maxlength 255); inbox.js v=198, inbox.css v=87 |
assets/js/inbox.js |
composeSubject(), openReplyModal(fromEmail) / openForwardModal(fromEmail), replyFromThread(), the thread's subject line and per-message buttons |
assets/css/inbox.css |
.reply-subject, .thread-subject, .thread-split-btn + .thread-split-btn |
lang/en/tickets.php, tickets/help.php |
reply_modal.subject, thread.*, help.comments.subject_note |
🔁 The flow
// The dialog's starting subject - prefix not doubled, reference added if missing
function composeSubject(prefix, base, ticketNumber) // ('RE', 'RE: Printer', 'ABC-1') -> 'RE: Printer [SDREF:ABC-1]'
// From the thread: the message's own subject; Reply answers its sender if they wrote to us
replyFromThread(emailId, 'reply' | 'forward') // looks the message up in window.__threadEmails
openReplyModal(fromEmail) // To = fromEmail.from_address when inbound,
// else the ticket's reply address (as before)
// api/tickets/send_email.php - whatever the browser sent
$subject = ticketOutboundSubject((string)$subject, $ticketNumber);
ticketOutboundSubject():
- Replaces control characters (CR, LF) with a space. A typed line break must never reach a mail header.
- Walks every
[SDREF:...](TicketNumbering::REF_PATTERN). It keeps this ticket's and removes any other ticket's, which would otherwise thread replies into that ticket. - Adds this ticket's reference at the end when it was missing. The words are cut so the whole thing fits
emails.subject(255), and the reference is never cut. - An empty subject becomes just the reference. The dialog still refuses to send an empty one.
Inbound threading needed no change: REF_PATTERN finds [SDREF:...] anywhere in a subject, so a provider putting its own number first does not matter.
⚠️ Traps
- TRAP: never send an analyst-typed subject without
ticketOutboundSubject(). A removed or foreign[SDREF]silently breaks threading, and the provider's reply lands as a new ticket. - TRAP: the thread's buttons work only for email messages (
channelempty oremail). A WhatsApp or web-chat message has no subject to carry, and its replies go through the channel composer.
🧪 Tests
tests/ticket-outbound-subject.php(8, no database):- the provider's number kept;
- a removed reference put back;
- the reference found whatever its case and spacing;
- another ticket's reference swapped for this one;
- a CR LF +
Bcc:injection flattened to text; - an empty subject;
- a long subject kept within 255 with the reference whole;
- multi-byte text cut on a character.
- Checked in Chrome on the test install:
inbox.jsloads;- every message shows its subject and its two buttons;
- Reply from a message we sent goes to the requester;
- Forward from a message starts from its subject with an empty To;
- the toolbar's Reply is unchanged.
See also: Tickets § Email threading · Ticket numbering