Roll SLA email targets up to business days, escape priority, validate priority in the tickets API

This commit is contained in:
johnnyq
2026-08-27 15:15:40 -04:00
parent ac175e7dc0
commit 4e1d72e06d
2 changed files with 46 additions and 11 deletions

View File

@@ -27,7 +27,15 @@ if (isset($_POST['ticket_subject'])) {
}
if (isset($_POST['ticket_priority'])) {
// Priority has to be one of the four the rest of the app is built around:
// sla_assignments are keyed on these exact strings, and the ticket list and
// kanban both fall through to the Low styling for anything else, so an
// unrecognised value silently reads as the lowest priority everywhere.
// Anything else is normalised away rather than rejected, matching how this
// model already handles a non-integer client_id.
$allowed_priorities = ['Low', 'Medium', 'High', 'Urgent'];
if (isset($_POST['ticket_priority']) && in_array($_POST['ticket_priority'], $allowed_priorities, true)) {
$priority = escapeSql($_POST['ticket_priority']);
} elseif ($ticket_row) {
$priority = mysqli_real_escape_string($mysqli, $ticket_row['ticket_priority']);

View File

@@ -425,10 +425,18 @@ function applyTicketSla($ticket_id, $forced_sla_id = null)
syncTicketSlaClock($ticket_id);
}
// Human-readable SLA target, e.g. "2 business hours" or "45 minutes". The
// "business" qualifier is dropped when no business calendar is configured,
// because addBusinessMinutes runs 24x7 in that case and the promise would
// otherwise be misleading. The condition mirrors that function's own guard.
// Human-readable SLA target, e.g. "45 minutes", "4 business hours" or
// "3 business days". The "business" qualifier is dropped when no business
// calendar is configured, because addBusinessMinutes runs 24x7 in that case
// and the promise would otherwise be misleading. The condition mirrors that
// function's own guard.
//
// Anything at or over one business day rolls up to days: with 9-5 hours a
// 1440-minute target is three working days, but "24 business hours" reads to
// a client as tomorrow. The exact deadline always travels next to this text
// in the email, so the wording only has to give the right impression - a
// remainder under five minutes is dropped rather than rendering the likes of
// "1 business hour 1 minute".
function formatSlaMinutes($minutes)
{
$minutes = intval($minutes);
@@ -438,20 +446,37 @@ function formatSlaMinutes($minutes)
$day_end = $sla_settings['business_hours_end'];
$qualifier = '';
$day_minutes = 1440;
if (!empty($sla_settings['business_days']) && !empty($day_start) && !empty($day_end) && $day_start < $day_end) {
$qualifier = 'business ';
$working_minutes = intval((strtotime($day_end) - strtotime($day_start)) / 60);
if ($working_minutes > 0) {
$day_minutes = $working_minutes;
}
}
if ($minutes < 60) {
return $minutes . ' minute' . ($minutes == 1 ? '' : 's');
}
$hours = intdiv($minutes, 60);
$remainder = $minutes % 60;
if ($minutes < $day_minutes) {
$hours = intdiv($minutes, 60);
$remainder = $minutes % 60;
$text = $hours . ' ' . $qualifier . 'hour' . ($hours == 1 ? '' : 's');
if ($remainder > 0) {
$text .= ' ' . $remainder . ' minute' . ($remainder == 1 ? '' : 's');
$text = $hours . ' ' . $qualifier . 'hour' . ($hours == 1 ? '' : 's');
if ($remainder >= 5) {
$text .= ' ' . $remainder . ' minutes';
}
return $text;
}
$days = intdiv($minutes, $day_minutes);
$hours = intdiv($minutes % $day_minutes, 60);
$text = $days . ' ' . $qualifier . 'day' . ($days == 1 ? '' : 's');
if ($hours > 0) {
$text .= ' ' . $hours . ' hour' . ($hours == 1 ? '' : 's');
}
return $text;
@@ -488,7 +513,9 @@ function getTicketSlaEmailNotice($ticket_id, $company_phone = '')
return '';
}
$priority = $row['ticket_priority'];
// The only value in this notice that comes from the database rather than a
// literal - escaped so the quote-free guarantee above holds for it too
$priority = escapeHtml($row['ticket_priority']);
$target = formatSlaMinutes($response_minutes);
$due = date('D j M, g:i A', strtotime($row['ticket_response_due_at']));