set('Cache.DefinitionImpl', null); // Disable cache by setting a non-existent directory or an invalid one $purifier_config->set('URI.AllowedSchemes', ['data' => true, 'src' => true, 'http' => true, 'https' => true]); $purifier = new HTMLPurifier($purifier_config); if (isset($_GET['ticket_id'])) { $ticket_id = intval($_GET['ticket_id']); $sql = mysqli_query( $mysqli, "SELECT * FROM tickets LEFT JOIN clients ON ticket_client_id = client_id LEFT JOIN contacts ON ticket_contact_id = contact_id LEFT JOIN users ON ticket_assigned_to = user_id LEFT JOIN locations ON ticket_location_id = location_id LEFT JOIN assets ON ticket_asset_id = asset_id LEFT JOIN asset_interfaces ON interface_asset_id = asset_id AND interface_primary = 1 LEFT JOIN vendors ON ticket_vendor_id = vendor_id LEFT JOIN projects ON ticket_project_id = project_id LEFT JOIN quotes ON ticket_quote_id = quote_id LEFT JOIN invoices ON ticket_invoice_id = invoice_id LEFT JOIN ticket_statuses ON ticket_status = ticket_status_id LEFT JOIN categories ON ticket_category = category_id WHERE ticket_id = $ticket_id $access_permission_query_overide LIMIT 1" ); if (mysqli_num_rows($sql) == 0) { echo "

Nothing to see here

Go Back
"; require_once "../includes/footer.php"; } else { $ticket = mysqli_fetch_assoc($sql); $client_id = intval($ticket['client_id']); $client_name = escapeHtml($ticket['client_name']); $client_net_terms = intval($ticket['client_net_terms']); if ($client_net_terms == 0) { $client_net_terms = $config_default_net_terms; } $ticket_prefix = escapeHtml($ticket['ticket_prefix']); $ticket_number = intval($ticket['ticket_number']); $ticket_reference = "$ticket_prefix$ticket_number"; $ticket_source = escapeHtml($ticket['ticket_source']); $ticket_category = intval($ticket['ticket_category']); $ticket_category_display = escapeHtml($ticket['category_name']); $ticket_subject = escapeHtml($ticket['ticket_subject']); $ticket_details = $purifier->purify($ticket['ticket_details']); $ticket_priority = escapeHtml($ticket['ticket_priority']); $ticket_billable = intval($ticket['ticket_billable']); $ticket_scheduled_for = escapeHtml($ticket['ticket_schedule']); $ticket_onsite = intval($ticket['ticket_onsite']); if ($ticket_scheduled_for) { $ticket_scheduled_wording = date('M d, Y • g:i A', strtotime($ticket_scheduled_for)); } else { $ticket_scheduled_wording = "Not scheduled"; } // Set Ticket Badge Color based of priority if ($ticket_priority == "Urgent") { $ticket_priority_color = "dark"; } elseif ($ticket_priority == "High") { $ticket_priority_color = "danger"; } elseif ($ticket_priority == "Medium") { $ticket_priority_color = "warning"; } else { $ticket_priority_color = "info"; } $ticket_feedback = escapeHtml($ticket['ticket_feedback']); $ticket_status = intval($ticket['ticket_status_id']); $ticket_status_id = intval($ticket['ticket_status_id']); $ticket_status_name = escapeHtml($ticket['ticket_status_name']); $ticket_status_color = escapeHtml($ticket['ticket_status_color']); $ticket_vendor_ticket_number = escapeHtml($ticket['ticket_vendor_ticket_number']); $ticket_created_at = escapeHtml($ticket['ticket_created_at']); $ticket_created_at_ago = timeAgo($ticket['ticket_created_at']); $ticket_created_by = intval($ticket['ticket_created_by']); $ticket_date = date('Y-m-d', strtotime($ticket_created_at)); $ticket_updated_at = escapeHtml($ticket['ticket_updated_at']); $ticket_updated_at_ago = timeAgo($ticket['ticket_updated_at']); $ticket_first_response_at = escapeHtml($ticket['ticket_first_response_at']); $ticket_sla_id = intval($ticket['ticket_sla_id']); $ticket_response_due_at = escapeHtml($ticket['ticket_response_due_at']); $ticket_resolution_due_at = escapeHtml($ticket['ticket_resolution_due_at']); $ticket_response_sla_met = $ticket['ticket_response_sla_met']; $ticket_resolution_sla_met = $ticket['ticket_resolution_sla_met']; $ticket_sla_paused = intval($ticket['ticket_status_pauses_sla']); $ticket_sla_name = "None"; if ($ticket_sla_id) { $sla_name_sql = mysqli_query($mysqli, "SELECT sla_name FROM slas WHERE sla_id = $ticket_sla_id"); if (mysqli_num_rows($sla_name_sql)) { $ticket_sla_name = escapeHtml(mysqli_fetch_assoc($sla_name_sql)['sla_name']); } } $ticket_resolved_at = escapeHtml($ticket['ticket_resolved_at']); $ticket_resolved_at_ago = timeAgo($ticket['ticket_resolved_at']); $ticket_closed_at = escapeHtml($ticket['ticket_closed_at']); $ticket_closed_at_ago = timeAgo($ticket['ticket_closed_at']); $ticket_closed_by = intval($ticket['ticket_closed_by']); // The three states the page keys its controls off $ticket_is_closed = !empty($ticket_closed_at); $ticket_is_resolved = !empty($ticket_resolved_at); $ticket_is_open = !$ticket_is_resolved && !$ticket_is_closed; $can_edit_ticket = lookupUserPermission("module_support") >= 2; $ticket_assigned_to = intval($ticket['ticket_assigned_to']); if (empty($ticket_assigned_to)) { $ticket_assigned_to_display = "Unassigned"; } else { $ticket_assigned_to_display = escapeHtml($ticket['user_name']); } // Tab Title // No Sanitizing needed $page_title = $ticket['ticket_subject']; $tab_title = "{$ticket['ticket_prefix']}{$ticket['ticket_number']}"; $contact_id = intval($ticket['contact_id']); $contact_name = escapeHtml($ticket['contact_name']); $contact_title = escapeHtml($ticket['contact_title']); $contact_email = escapeHtml($ticket['contact_email']); $contact_phone_country_code = escapeHtml($ticket['contact_phone_country_code']); $contact_phone = escapeHtml(formatPhoneNumber($ticket['contact_phone'], $contact_phone_country_code)); $contact_extension = escapeHtml($ticket['contact_extension']); $contact_mobile_country_code = escapeHtml($ticket['contact_mobile_country_code']); $contact_mobile = escapeHtml(formatPhoneNumber($ticket['contact_mobile'], $contact_mobile_country_code)); $asset_id = intval($ticket['asset_id']); $asset_name = escapeHtml($ticket['asset_name']); $asset_type = escapeHtml($ticket['asset_type']); $asset_icon = getAssetIcon($asset_type); $vendor_id = intval($ticket['ticket_vendor_id']); $vendor_name = escapeHtml($ticket['vendor_name']); $vendor_contact_name = escapeHtml($ticket['vendor_contact_name']); $vendor_phone_country_code = escapeHtml($ticket['vendor_phone_country_code']); $vendor_phone = escapeHtml(formatPhoneNumber($ticket['vendor_phone'], $vendor_phone_country_code)); $vendor_email = escapeHtml($ticket['vendor_email']); $vendor_website = escapeHtml($ticket['vendor_website']); $location_id = intval($ticket['location_id']); $location_name = escapeHtml($ticket['location_name']); $location_phone = formatPhoneNumber($ticket['location_phone']); $quote_id = intval($ticket['ticket_quote_id']); $quote_prefix = escapeHtml($ticket['quote_prefix']); $quote_number = intval($ticket['quote_number']); // The quote row may have been deleted out from under the ticket $quote_reference = $quote_number ? "$quote_prefix$quote_number" : "#$quote_id"; $invoice_id = intval($ticket['ticket_invoice_id']); $invoice_prefix = escapeHtml($ticket['invoice_prefix']); $invoice_number = intval($ticket['invoice_number']); // Same for the invoice - fall back to the id rather than rendering a bare 0 $invoice_reference = $invoice_number ? "$invoice_prefix$invoice_number" : "#$invoice_id"; $project_id = intval($ticket['project_id']); $project_name = escapeHtml($ticket['project_name']); $project_manager = intval($ticket['project_manager']); $project_manager_name = ''; if ($project_manager) { $project_manager_row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT user_name FROM users WHERE user_id = $project_manager")); $project_manager_name = escapeHtml($project_manager_row['user_name']); } $ticket_created_by_display = ''; if ($ticket_created_by) { $created_by_row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT user_name FROM users WHERE user_id = $ticket_created_by")); $ticket_created_by_display = escapeHtml($created_by_row['user_name']); } $ticket_closed_by_display = ''; if ($ticket_is_closed) { $ticket_closed_by_display = 'User'; if (!empty($ticket_closed_by)) { $closed_by_row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT user_name FROM users WHERE user_id = $ticket_closed_by")); $ticket_closed_by_display = escapeHtml($closed_by_row['user_name']); } } /* * How much else this contact has open. The old queries compared the * integer ticket_status column against the string 'Closed', which MySQL * casts to 0 - so "open" matched every ticket and "closed" matched none. * Resolution state lives in ticket_resolved_at, same as the ticket list. */ $contact_open_tickets = 0; if ($contact_id) { $contact_open_tickets = intval(mysqli_fetch_row(mysqli_query( $mysqli, "SELECT COUNT(ticket_id) FROM tickets WHERE ticket_contact_id = $contact_id AND ticket_id != $ticket_id AND ticket_resolved_at IS NULL" ))[0]); } // Reply counts, one pass - these drive the conversation filter $reply_counts = array('total' => 0, 'public' => 0, 'internal' => 0); $sql_reply_counts = mysqli_query( $mysqli, "SELECT ticket_reply_type, COUNT(ticket_reply_id) AS reply_count FROM ticket_replies WHERE ticket_reply_archived_at IS NULL AND ticket_reply_ticket_id = $ticket_id GROUP BY ticket_reply_type" ); while ($reply_count_row = mysqli_fetch_assoc($sql_reply_counts)) { $reply_count = intval($reply_count_row['reply_count']); $reply_counts['total'] += $reply_count; if ($reply_count_row['ticket_reply_type'] == 'Internal') { $reply_counts['internal'] += $reply_count; } else { $reply_counts['public'] += $reply_count; } } // Get Total Ticket Time $ticket_total_reply_time = escapeHtml(mysqli_fetch_assoc(mysqli_query( $mysqli, "SELECT SEC_TO_TIME(SUM(TIME_TO_SEC(ticket_reply_time_worked))) AS ticket_total_reply_time FROM ticket_replies WHERE ticket_reply_archived_at IS NULL AND ticket_reply_ticket_id = $ticket_id" ))['ticket_total_reply_time']); /* * Agents who have worked this ticket. The old query joined every reply's * ticket_reply_by against users, but a Client reply stores a CONTACT id * there - so any contact whose id collided with a user id showed up as a * collaborator. That is the bug the old commented-out block ran into. */ $ticket_collaborators = escapeHtml(mysqli_fetch_assoc(mysqli_query( $mysqli, "SELECT GROUP_CONCAT(DISTINCT user_name ORDER BY user_name SEPARATOR ', ') AS user_names FROM ticket_replies INNER JOIN users ON ticket_reply_by = user_id WHERE ticket_reply_archived_at IS NULL AND ticket_reply_ticket_id = $ticket_id AND ticket_reply_type != 'Client'" ))['user_names']); // Get ticket replies $sql_ticket_replies = mysqli_query($mysqli, "SELECT * FROM ticket_replies LEFT JOIN users ON ticket_reply_by = user_id LEFT JOIN contacts ON ticket_reply_by = contact_id WHERE ticket_reply_ticket_id = $ticket_id AND ticket_reply_archived_at IS NULL ORDER BY ticket_reply_id DESC" ); /* * Every attachment on the ticket in one query, split by reply. The page * used to fire one attachment query per reply as it rendered. */ $ticket_attachments = array(); // attachments on the ticket itself $reply_attachments = array(); // attachments keyed by reply id $sql_all_attachments = mysqli_query( $mysqli, "SELECT ticket_attachment_id, ticket_attachment_name, ticket_attachment_reply_id FROM ticket_attachments WHERE ticket_attachment_ticket_id = $ticket_id" ); while ($attachment_row = mysqli_fetch_assoc($sql_all_attachments)) { $attachment_reply_id = intval($attachment_row['ticket_attachment_reply_id']); if ($attachment_reply_id) { $reply_attachments[$attachment_reply_id][] = $attachment_row; } else { $ticket_attachments[] = $attachment_row; } } // Get Watchers $sql_ticket_watchers = mysqli_query($mysqli, "SELECT * FROM ticket_watchers WHERE watcher_ticket_id = $ticket_id ORDER BY watcher_email DESC"); $watcher_count = mysqli_num_rows($sql_ticket_watchers); // Get Additional Assets $sql_additional_assets = mysqli_query($mysqli, "SELECT * FROM assets, ticket_assets WHERE assets.asset_id = ticket_assets.asset_id AND ticket_id = $ticket_id AND assets.asset_id != $asset_id" ); // Get Tasks $sql_tasks = mysqli_query($mysqli, "SELECT * FROM tasks WHERE task_ticket_id = $ticket_id ORDER BY task_order ASC, task_id ASC"); $task_count = mysqli_num_rows($sql_tasks); $completed_task_count = intval(mysqli_fetch_row(mysqli_query( $mysqli, "SELECT COUNT(task_id) FROM tasks WHERE task_ticket_id = $ticket_id AND task_completed_at IS NOT NULL" ))[0]); // Tasks Completed Percent $tasks_completed_percent = 0; if ($task_count) { $tasks_completed_percent = round(($completed_task_count / $task_count) * 100); } // Tasks still open block resolving the ticket - the page says so rather than just hiding the button $tasks_block_resolve = $task_count !== $completed_task_count; /* * All approvals for this ticket's tasks in one query. The task loop used * to run two approval queries per row. */ $task_approvals = array(); if ($task_count) { $sql_task_approvals = mysqli_query( $mysqli, "SELECT approval_id, approval_task_id, approval_scope, approval_type, approval_required_user_id, approval_created_by, approval_status FROM task_approvals INNER JOIN tasks ON approval_task_id = task_id WHERE task_ticket_id = $ticket_id AND approval_status IN ('pending','declined')" ); while ($approval_row = mysqli_fetch_assoc($sql_task_approvals)) { $task_approvals[intval($approval_row['approval_task_id'])][] = $approval_row; } } /* * Ticket history. agent/post/ticket.php has been writing priority, SLA, * contact, watcher and asset changes here all along - the page fetched * the rows and then never rendered them. */ $sql_ticket_history = mysqli_query($mysqli, "SELECT ticket_history_description, ticket_history_created_at FROM ticket_history WHERE ticket_history_ticket_id = $ticket_id ORDER BY ticket_history_id DESC" ); $ticket_history_count = mysqli_num_rows($sql_ticket_history); /* * The single most useful thing on the page: which clock is running and * how long is left. Everything else about the SLA is detail. */ $sla_state = ''; // breached | at_risk | paused | met | running $sla_label = ''; $sla_class = ''; $sla_icon = 'fa-stopwatch'; if ($ticket_sla_id) { $sla_next_due = empty($ticket_first_response_at) ? $ticket_response_due_at : $ticket_resolution_due_at; $sla_next_label = empty($ticket_first_response_at) ? 'First response due' : 'Resolution due'; $ticket_sla_alert_stage = max(intval($ticket['ticket_response_sla_alert_stage']), intval($ticket['ticket_resolution_sla_alert_stage'])); $sla_breached = (!is_null($ticket_response_sla_met) && !$ticket_response_sla_met) || (!is_null($ticket_resolution_sla_met) && !$ticket_resolution_sla_met) || $ticket_sla_alert_stage == 2; if ($sla_breached) { $sla_state = 'breached'; $sla_label = 'SLA breached'; $sla_class = 'badge-danger'; $sla_icon = 'fa-exclamation-triangle'; } elseif (!$ticket_is_open) { $sla_state = 'met'; $sla_label = 'SLA met'; $sla_class = 'badge-success'; $sla_icon = 'fa-check'; } elseif ($ticket_sla_paused) { $sla_state = 'paused'; $sla_label = 'SLA paused'; $sla_class = 'badge-secondary'; $sla_icon = 'fa-pause'; } elseif (!empty($sla_next_due)) { $sla_overdue = strtotime($sla_next_due) < time(); $sla_state = $sla_overdue ? 'breached' : 'running'; $sla_label = $sla_next_label . ' ' . timeAgo($sla_next_due); $sla_class = $sla_overdue ? 'badge-danger' : 'badge-light border'; } } ?>

No client · ·Opened ·via ·Updated
= 2; $show_invoice_button = $config_module_enable_accounting && $ticket_billable == 1 && empty($invoice_id) && lookupUserPermission("module_sales") >= 2; $has_action_buttons = $show_quote_button || $show_invoice_button || !$ticket_is_closed; ?>
Status
Assigned to
Scheduled
Not scheduled Not scheduled' ?>
= 1) { ?>
Billing
Time logged
Tasks
Description
View Download = 3) { ?> Delete
Conversation
purify($reply_row['ticket_reply']); $ticket_reply_type = escapeHtml($reply_row['ticket_reply_type']); $ticket_reply_created_at = escapeHtml($reply_row['ticket_reply_created_at']); $ticket_reply_created_at_ago = timeAgo($reply_row['ticket_reply_created_at']); $ticket_reply_updated_at = escapeHtml($reply_row['ticket_reply_updated_at']); $ticket_reply_time_worked = ''; if ($ticket_reply_type == "Client") { $ticket_reply_by_display = escapeHtml($reply_row['contact_name']); $user_initials = initials($reply_row['contact_name']); $user_avatar = escapeHtml($reply_row['contact_photo']); $avatar_link = "../uploads/clients/$client_id/$user_avatar"; } else { $ticket_reply_by_display = escapeHtml($reply_row['user_name']); $user_id = intval($reply_row['user_id']); $user_avatar = escapeHtml($reply_row['user_avatar']); $user_initials = initials($reply_row['user_name']); $avatar_link = "../uploads/users/$user_id/$user_avatar"; $ticket_reply_time_worked = $reply_row['ticket_reply_time_worked']; } // Internal notes are the one thing that must never be mistaken for // something the client saw, so they get a label, not just a colour if ($ticket_reply_type == 'Internal') { $reply_border = 'dark'; $reply_badge = "Internal note"; $reply_group = 'internal'; } elseif ($ticket_reply_type == 'Client') { $reply_border = 'warning'; $reply_badge = "From client"; $reply_group = 'public'; } else { $reply_border = 'info'; $reply_badge = "Public reply"; $reply_group = 'public'; } ?>
(edited)'; } ?> ·
View Download = 3) { ?> Delete
Contact
No contact set on this ticket.
ext.
Nothing linked yet. Use to attach an asset, vendor, project or watcher.
Assets
Vendor
Project
Watchers
History