99) { $warning_percent = 0; // Out of range = warnings disabled, breach alerts only } $sla_notification_email = trim(strval($sla_settings['notification_email'])); $from_email = $sla_settings['ticket_from_email']; $from_name = $sla_settings['ticket_from_name']; $now = time(); // Queue in-app + email notifications for an SLA event function sendSlaAlert($ticket, $subject_line, $body_line) { global $mysqli, $sla_notification_email, $from_email, $from_name, $config_base_url; $ticket_id = intval($ticket['ticket_id']); $client_id = intval($ticket['ticket_client_id']); $ticket_ref = "{$ticket['ticket_prefix']}{$ticket['ticket_number']}"; $ticket_subject = strval($ticket['ticket_subject']); // appNotify inserts what it is given - escape at the boundary appNotify("Ticket SLA", escapeSql("$subject_line - $ticket_ref - $ticket_subject"), "/agent/ticket.php?ticket_id=$ticket_id", $client_id, $ticket_id); // addToMailQueue also inserts raw, and the body's link markup contains // single quotes - escape whole strings once here. The body keeps its HTML, // so it gets mysqli_real_escape_string directly (escapeSql strips tags), // same as the ticket email parser does for its bodies. $email_subject = escapeSql("$subject_line: $ticket_ref - $ticket_subject"); $email_body = mysqli_real_escape_string($mysqli, "Hello,

$body_line

Ticket: $ticket_ref
Subject: $ticket_subject

View ticket"); $email_data = []; if (!empty($sla_notification_email)) { $email_data[] = [ 'from' => $from_email, 'from_name' => escapeSql($from_name), 'recipient' => $sla_notification_email, 'recipient_name' => 'SLA Notifications', 'subject' => $email_subject, 'body' => $email_body, ]; } // Assigned agent (skip a duplicate if they are also the notification address) if (!empty($ticket['user_email']) && strtolower($ticket['user_email']) != strtolower($sla_notification_email)) { $email_data[] = [ 'from' => $from_email, 'from_name' => escapeSql($from_name), 'recipient' => $ticket['user_email'], 'recipient_name' => escapeSql(strval($ticket['user_name'])), 'subject' => $email_subject, 'body' => $email_body, ]; } if (!empty($email_data)) { addToMailQueue($email_data); } } // --- Response SLA track --- // Open tickets awaiting a first response, not yet marked breached $sql_response = mysqli_query($mysqli, "SELECT ticket_id, ticket_prefix, ticket_number, ticket_subject, ticket_client_id, ticket_created_at, ticket_response_due_at, ticket_response_sla_alert_stage, sla_response_minutes, user_email, user_name FROM tickets LEFT JOIN slas ON ticket_sla_id = sla_id LEFT JOIN users ON ticket_assigned_to = user_id WHERE ticket_sla_id > 0 AND ticket_response_due_at IS NOT NULL AND ticket_first_response_at IS NULL AND ticket_resolved_at IS NULL AND ticket_closed_at IS NULL AND ticket_archived_at IS NULL AND ticket_response_sla_alert_stage < 2" ); while ($ticket = mysqli_fetch_assoc($sql_response)) { $ticket_id = intval($ticket['ticket_id']); $stage = intval($ticket['ticket_response_sla_alert_stage']); $due = strtotime($ticket['ticket_response_due_at']); if ($now >= $due) { // Breached without a response - the verdict is final, record the miss mysqli_query($mysqli, "UPDATE tickets SET ticket_response_sla_alert_stage = 2, ticket_response_sla_met = 0 WHERE ticket_id = $ticket_id"); sendSlaAlert($ticket, "Response SLA breached", "The response SLA on this ticket was missed (due {$ticket['ticket_response_due_at']})."); } elseif ($stage < 1 && $warning_percent) { $warn_at = strtotime(addBusinessMinutes($ticket['ticket_created_at'], floor(intval($ticket['sla_response_minutes']) * $warning_percent / 100))); if ($now >= $warn_at) { mysqli_query($mysqli, "UPDATE tickets SET ticket_response_sla_alert_stage = 1 WHERE ticket_id = $ticket_id"); sendSlaAlert($ticket, "Response SLA at risk", "This ticket is approaching its response SLA (due {$ticket['ticket_response_due_at']})."); } } } // --- Resolution SLA track --- // Open tickets with a resolution target, not yet resolved or marked breached $sql_resolution = mysqli_query($mysqli, "SELECT ticket_id, ticket_prefix, ticket_number, ticket_subject, ticket_client_id, ticket_created_at, ticket_resolution_due_at, ticket_resolution_sla_alert_stage, sla_resolution_minutes, user_email, user_name FROM tickets LEFT JOIN slas ON ticket_sla_id = sla_id LEFT JOIN users ON ticket_assigned_to = user_id WHERE ticket_sla_id > 0 AND ticket_resolution_due_at IS NOT NULL AND ticket_resolved_at IS NULL AND ticket_closed_at IS NULL AND ticket_archived_at IS NULL AND ticket_resolution_sla_alert_stage < 2" ); while ($ticket = mysqli_fetch_assoc($sql_resolution)) { $ticket_id = intval($ticket['ticket_id']); $stage = intval($ticket['ticket_resolution_sla_alert_stage']); $due = strtotime($ticket['ticket_resolution_due_at']); if ($now >= $due) { mysqli_query($mysqli, "UPDATE tickets SET ticket_resolution_sla_alert_stage = 2, ticket_resolution_sla_met = 0 WHERE ticket_id = $ticket_id"); sendSlaAlert($ticket, "Resolution SLA breached", "The resolution SLA on this ticket was missed (due {$ticket['ticket_resolution_due_at']})."); } elseif ($stage < 1 && $warning_percent) { $warn_at = strtotime(addBusinessMinutes($ticket['ticket_created_at'], floor(intval($ticket['sla_resolution_minutes']) * $warning_percent / 100))); if ($now >= $warn_at) { mysqli_query($mysqli, "UPDATE tickets SET ticket_resolution_sla_alert_stage = 1 WHERE ticket_id = $ticket_id"); sendSlaAlert($ticket, "Resolution SLA at risk", "This ticket is approaching its resolution SLA (due {$ticket['ticket_resolution_due_at']})."); } } }