From a3fbad19c9bf86b883fbf9a74f328c5c14589519 Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Sat, 11 Apr 2026 12:43:18 -0400 Subject: [PATCH] Fix leading-zero ticket ID handling across API and UI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - dashboard.js: use String(cb.value) instead of parseInt() in getSelectedTicketIds() so zero-padded IDs like 000123456 are preserved when sent to bulk_operation.php - DashboardView.php: remove (int) cast on data-ticket-id attribute for quick-status button; was stripping leading zeros - TicketView.php: remove (int) cast on export URL ticket_id param - update_ticket.php: preserve ticket_id as string via trim((string)...) - add_comment.php: preserve ticket_id as string; validate with ctype_digit instead of (int) cast so comments are stored with the canonical zero-padded ID matching the tickets table - export_tickets.php: validate singleId as string to avoid stripping leading zeros in the export endpoint - notifications.php: preserve ticket_id strings in URLs and ticket ownership checks; index myTicketIds by both int and string forms for robust lookup regardless of how audit_log stored the ID - TicketController.php: fix inline dependency insert — column was wrong (depends_on_ticket_id → depends_on_id) and bind types were wrong ("iii" → "ssi"); feature was silently broken Co-Authored-By: Claude Sonnet 4.6 --- api/add_comment.php | 4 ++-- api/export_tickets.php | 3 ++- api/notifications.php | 13 +++++++------ api/update_ticket.php | 2 +- assets/js/dashboard.js | 2 +- controllers/TicketController.php | 8 ++++---- views/DashboardView.php | 2 +- views/TicketView.php | 2 +- 8 files changed, 19 insertions(+), 17 deletions(-) diff --git a/api/add_comment.php b/api/add_comment.php index 8737dae..cc306c5 100644 --- a/api/add_comment.php +++ b/api/add_comment.php @@ -65,8 +65,8 @@ try { throw new Exception("Invalid JSON data received"); } - $ticketId = isset($data['ticket_id']) ? (int)$data['ticket_id'] : 0; - if ($ticketId <= 0) { + $ticketId = isset($data['ticket_id']) ? trim((string)$data['ticket_id']) : ''; + if (!ctype_digit($ticketId) || (int)$ticketId <= 0) { http_response_code(400); ob_end_clean(); header('Content-Type: application/json'); diff --git a/api/export_tickets.php b/api/export_tickets.php index 5c8d1b7..e6eec31 100644 --- a/api/export_tickets.php +++ b/api/export_tickets.php @@ -43,7 +43,8 @@ try { $search = isset($_GET['search']) ? trim($_GET['search']) : null; $format = isset($_GET['format']) ? $_GET['format'] : 'csv'; $ticketIds = isset($_GET['ticket_ids']) ? $_GET['ticket_ids'] : null; - $singleId = isset($_GET['ticket_id']) ? (int)$_GET['ticket_id'] : null; + $singleIdRaw = isset($_GET['ticket_id']) ? trim($_GET['ticket_id']) : null; + $singleId = ($singleIdRaw !== null && ctype_digit($singleIdRaw) && (int)$singleIdRaw > 0) ? $singleIdRaw : null; // Initialize model $ticketModel = new TicketModel($conn); diff --git a/api/notifications.php b/api/notifications.php index f3afd22..b32a21e 100644 --- a/api/notifications.php +++ b/api/notifications.php @@ -75,7 +75,7 @@ $stmt = $conn->prepare($myTicketsSql); $stmt->bind_param('ii', $userId, $userId); $stmt->execute(); $mtResult = $stmt->get_result(); -while ($mtRow = $mtResult->fetch_assoc()) { $myTicketIds[(int)$mtRow['ticket_id']] = true; } +while ($mtRow = $mtResult->fetch_assoc()) { $myTicketIds[(int)$mtRow['ticket_id']] = true; $myTicketIds[$mtRow['ticket_id']] = true; } $stmt->close(); $watchedSql = "SELECT ticket_id FROM ticket_watchers WHERE user_id = ?"; @@ -83,7 +83,7 @@ $stmt = $conn->prepare($watchedSql); $stmt->bind_param('i', $userId); $stmt->execute(); $wResult = $stmt->get_result(); -while ($wRow = $wResult->fetch_assoc()) { $myTicketIds[(int)$wRow['ticket_id']] = true; } +while ($wRow = $wResult->fetch_assoc()) { $myTicketIds[(int)$wRow['ticket_id']] = true; $myTicketIds[$wRow['ticket_id']] = true; } $stmt->close(); // Step B: fetch recent comment audit events not by the current user @@ -109,8 +109,9 @@ $stmt->close(); $commentRows = []; foreach ($rawCommentRows as $rawRow) { $d = json_decode($rawRow['details'] ?? '{}', true) ?? []; - $tid = (int)($d['ticket_id'] ?? 0); - if ($tid > 0 && isset($myTicketIds[$tid])) { + $tidRaw = $d['ticket_id'] ?? 0; + $tid = (int)$tidRaw; + if ($tid > 0 && (isset($myTicketIds[$tid]) || isset($myTicketIds[$tidRaw]))) { $commentRows[] = $rawRow; if (count($commentRows) >= 15) break; } @@ -158,8 +159,8 @@ foreach ($all as $row) { ? 'comment' : $row['action_type']; $ticketId = ($actionType === 'comment') - ? (int)($details['ticket_id'] ?? 0) - : (int)$row['entity_id']; + ? ($details['ticket_id'] ?? 0) + : $row['entity_id']; $isRead = $lastSeen && $row['created_at'] <= $lastSeen; // Build human-readable title diff --git a/api/update_ticket.php b/api/update_ticket.php index ccc76bb..29c32c7 100644 --- a/api/update_ticket.php +++ b/api/update_ticket.php @@ -252,7 +252,7 @@ try { throw new Exception("Missing ticket_id parameter"); } - $ticketId = (int)$data['ticket_id']; + $ticketId = trim((string)$data['ticket_id']); // Initialize controller $controller = new ApiTicketController($conn, $userId, $isAdmin, $currentUser); diff --git a/assets/js/dashboard.js b/assets/js/dashboard.js index b81fcae..06887e5 100644 --- a/assets/js/dashboard.js +++ b/assets/js/dashboard.js @@ -497,7 +497,7 @@ function updateSelectionCount() { function getSelectedTicketIds() { const checkboxes = document.querySelectorAll('.ticket-checkbox:checked'); - return Array.from(checkboxes).map(cb => parseInt(cb.value)); + return Array.from(checkboxes).map(cb => String(cb.value)); } function clearSelection() { diff --git a/controllers/TicketController.php b/controllers/TicketController.php index 9405f89..5172336 100644 --- a/controllers/TicketController.php +++ b/controllers/TicketController.php @@ -126,12 +126,12 @@ class TicketController { } // Auto-link as duplicate if requested from create form - $linkDupOf = isset($_POST['link_duplicate_of']) ? (int)$_POST['link_duplicate_of'] : 0; - if ($linkDupOf > 0) { - $depSql = "INSERT IGNORE INTO ticket_dependencies (ticket_id, depends_on_ticket_id, dependency_type, created_by) + $linkDupOfRaw = trim($_POST['link_duplicate_of'] ?? ''); + if ($linkDupOfRaw !== '' && ctype_digit($linkDupOfRaw)) { + $depSql = "INSERT IGNORE INTO ticket_dependencies (ticket_id, depends_on_id, dependency_type, created_by) VALUES (?, ?, 'duplicates', ?)"; $depStmt = $this->conn->prepare($depSql); - $depStmt->bind_param("iii", $result['ticket_id'], $linkDupOf, $userId); + $depStmt->bind_param("ssi", $result['ticket_id'], $linkDupOfRaw, $userId); $depStmt->execute(); $depStmt->close(); } diff --git a/views/DashboardView.php b/views/DashboardView.php index 79a3251..50a7991 100644 --- a/views/DashboardView.php +++ b/views/DashboardView.php @@ -762,7 +762,7 @@ include __DIR__ . '/layout_header.php'; aria-label="View ticket ">View EXPORT