From 08e467baa9711af3fcb3ad92ba504b4b4dc5f57c Mon Sep 17 00:00:00 2001 From: johnnyq Date: Fri, 6 Jun 2025 21:48:24 -0400 Subject: [PATCH 01/63] Encode Page Title --- includes/footer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/footer.php b/includes/footer.php index bb0cf4f6..0028c738 100644 --- a/includes/footer.php +++ b/includes/footer.php @@ -14,7 +14,7 @@ if (str_contains(basename($_SERVER["PHP_SELF"]), "admin_")) { ?> - + From 4e76ceaa0f8451c037929cf87db79144fa0f6317 Mon Sep 17 00:00:00 2001 From: johnnyq Date: Sat, 7 Jun 2025 00:46:11 -0400 Subject: [PATCH 02/63] Simplify Category filter logic in tickets catrgory is an int not a string duh --- tickets.php | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/tickets.php b/tickets.php index 919843fb..7f4c4637 100644 --- a/tickets.php +++ b/tickets.php @@ -56,14 +56,8 @@ if (isset($_GET['billable']) && ($_GET['billable']) == '1') { } if (isset($_GET['category'])) { - $category = sanitizeInput($_GET['category']); - if ($category == 'empty') { - $category_snippet = "AND ticket_category = 0 "; - } elseif ($category == 'all') { - $category_snippet = ''; - } else { - $category_snippet = "AND ticket_category = " . $category; - } + $category = intval($_GET['category']); + $category_snippet = "AND ticket_category = $category"; } else { $category_snippet = ''; } From 5f007029b4f293dbb5f4885ca42021ee370e0d7d Mon Sep 17 00:00:00 2001 From: johnnyq Date: Sat, 7 Jun 2025 00:58:56 -0400 Subject: [PATCH 03/63] Fix Category --- tickets.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tickets.php b/tickets.php index 7f4c4637..5d02e799 100644 --- a/tickets.php +++ b/tickets.php @@ -55,7 +55,7 @@ if (isset($_GET['billable']) && ($_GET['billable']) == '1') { $ticket_billable_snippet = ''; } -if (isset($_GET['category'])) { +if (!empty($_GET['category'])) { $category = intval($_GET['category']); $category_snippet = "AND ticket_category = $category"; } else { @@ -219,7 +219,7 @@ $sql_categories = mysqli_query( Categories
From fec8eaef707aa176d752b02d966cd6b542b5a327 Mon Sep 17 00:00:00 2001 From: johnnyq Date: Sat, 7 Jun 2025 01:28:13 -0400 Subject: [PATCH 04/63] Remove xml from the allowed upload list, if you must zip it up --- modals/client_file_upload_modal.php | 2 +- post/user/file.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modals/client_file_upload_modal.php b/modals/client_file_upload_modal.php index 02f1fd42..65aaaa44 100644 --- a/modals/client_file_upload_modal.php +++ b/modals/client_file_upload_modal.php @@ -44,7 +44,7 @@
- +
Up to 20 files can be uploaded at once by holding down CTRL and selecting files diff --git a/post/user/file.php b/post/user/file.php index 9debb0fe..24b29ce5 100644 --- a/post/user/file.php +++ b/post/user/file.php @@ -28,7 +28,7 @@ if (isset($_POST['upload_files'])) { $allowedExtensions = [ 'jpg', 'jpeg', 'gif', 'png', 'webp', 'pdf', 'txt', 'md', 'doc', 'docx', 'odt', 'csv', 'xls', 'xlsx', 'ods', 'pptx', 'odp', 'zip', 'tar', 'gz', - 'xml', 'msg', 'json', 'wav', 'mp3', 'ogg', 'mov', 'mp4', 'av1', 'ovpn', + 'msg', 'json', 'wav', 'mp3', 'ogg', 'mov', 'mp4', 'av1', 'ovpn', 'cfg', 'ps1', 'vsdx', 'drawio', 'pfx', 'pages', 'numbers', 'unf', 'key', 'bat', 'stk' ]; From 6c8403fa09cbae5c5567570c30479f452bb89f1e Mon Sep 17 00:00:00 2001 From: johnnyq Date: Mon, 9 Jun 2025 12:52:07 -0400 Subject: [PATCH 05/63] Move MFA Modal out of the Password Reset Form --- user_security.php | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/user_security.php b/user_security.php index 233f6c9f..3334f82e 100644 --- a/user_security.php +++ b/user_security.php @@ -30,22 +30,20 @@ $remember_token_count = mysqli_num_rows($sql_remember_tokens); -
- - - - - - - Disable MFA - -
- - +
+ + + + + + + Disable MFA + +
From 680dbb04cefc487d77e33c0baa80375cd263b991 Mon Sep 17 00:00:00 2001 From: johnnyq Date: Mon, 9 Jun 2025 13:30:13 -0400 Subject: [PATCH 06/63] Fix UI Regression with Indenting Columns in Tickets listing when Open and Closed tickets are filters --- tickets_compact.php | 15 ++++++++------- tickets_list.php | 13 ++++++++----- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/tickets_compact.php b/tickets_compact.php index 6a3f8fea..9421a284 100644 --- a/tickets_compact.php +++ b/tickets_compact.php @@ -6,14 +6,15 @@
"> - - + - + "> - - -
+
+
Ticket @@ -167,14 +168,14 @@
+ +
-
+ +
diff --git a/tickets_list.php b/tickets_list.php index 325afd03..aea0428b 100644 --- a/tickets_list.php +++ b/tickets_list.php @@ -7,13 +7,15 @@ text-nowrap"> - + - + "> - - - + - - = 2) { ?> diff --git a/scripts/cron.php b/scripts/cron.php index ec11395f..dc2367fe 100644 --- a/scripts/cron.php +++ b/scripts/cron.php @@ -319,6 +319,7 @@ if (mysqli_num_rows($sql_recurring_tickets) > 0) { $client_id = intval($row['recurring_ticket_client_id']); $contact_id = intval($row['recurring_ticket_contact_id']); $asset_id = intval($row['recurring_ticket_asset_id']); + $category = intval($row['recurring_ticket_category']); $ticket_status = 1; // Default if ($assigned_id > 0) { @@ -334,7 +335,7 @@ if (mysqli_num_rows($sql_recurring_tickets) > 0) { mysqli_query($mysqli, "UPDATE settings SET config_ticket_next_number = $new_config_ticket_next_number WHERE company_id = 1"); // Raise the ticket - mysqli_query($mysqli, "INSERT INTO tickets SET ticket_prefix = '$config_ticket_prefix', ticket_number = $ticket_number, ticket_source = 'Recurring', ticket_subject = '$subject', ticket_details = '$details', ticket_priority = '$priority', ticket_status = '$ticket_status', ticket_billable = $billable, ticket_created_by = $created_id, ticket_assigned_to = $assigned_id, ticket_contact_id = $contact_id, ticket_client_id = $client_id, ticket_asset_id = $asset_id, ticket_recurring_ticket_id = $recurring_ticket_id"); + mysqli_query($mysqli, "INSERT INTO tickets SET ticket_prefix = '$config_ticket_prefix', ticket_number = $ticket_number, ticket_source = 'Recurring', ticket_subject = '$subject', ticket_details = '$details', ticket_priority = '$priority', ticket_status = '$ticket_status', ticket_billable = $billable, ticket_created_by = $created_id, ticket_assigned_to = $assigned_id, ticket_contact_id = $contact_id, ticket_client_id = $client_id, ticket_asset_id = $asset_id, ticket_category = $category, ticket_recurring_ticket_id = $recurring_ticket_id"); $id = mysqli_insert_id($mysqli); // Copy Additional Assets from Recurring ticket to new ticket From bef18c0d72a052a40f6811f762508a2c79902dee Mon Sep 17 00:00:00 2001 From: johnnyq Date: Wed, 11 Jun 2025 18:48:23 -0400 Subject: [PATCH 19/63] Do not Resolved Tickets in Bulk that have Open tasks, display warning and count of ticket not resolved because of open tasks --- post/user/ticket.php | 171 ++++++++++++++++++++++++------------------- 1 file changed, 94 insertions(+), 77 deletions(-) diff --git a/post/user/ticket.php b/post/user/ticket.php index 84d9ad65..e3d36467 100644 --- a/post/user/ticket.php +++ b/post/user/ticket.php @@ -1012,104 +1012,121 @@ if (isset($_POST['bulk_resolve_tickets'])) { // Resolve Selected Tickets if (isset($_POST['ticket_ids'])) { - // Get a Ticket Count - $ticket_count = count($_POST['ticket_ids']); + // Intitialze the counts before the loop + $ticket_count = 0; + $skipped_count = 0; foreach ($_POST['ticket_ids'] as $ticket_id) { $ticket_id = intval($ticket_id); - $sql = mysqli_query($mysqli, "SELECT * FROM tickets WHERE ticket_id = $ticket_id"); - $row = mysqli_fetch_array($sql); + // Check to make sure Tasks are complete before resolving + $row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT COUNT('task_id') AS num FROM tasks WHERE task_completed_at IS NULL AND task_ticket_id = $ticket_id")); + $num_of_open_tasks = $row['num']; + + if ($num_of_open_tasks == 0) { + // Count the Ticket Loop + $ticket_count++; - $ticket_prefix = sanitizeInput($row['ticket_prefix']); - $ticket_number = intval($row['ticket_number']); - $ticket_subject = sanitizeInput($row['ticket_subject']); - $current_ticket_priority = sanitizeInput($row['ticket_priority']); - $url_key = sanitizeInput($row['ticket_url_key']); - $client_id = intval($row['ticket_client_id']); - - // Update ticket & insert reply - mysqli_query($mysqli, "UPDATE tickets SET ticket_status = 4, ticket_resolved_at = NOW() WHERE ticket_id = $ticket_id"); - - mysqli_query($mysqli, "INSERT INTO ticket_replies SET ticket_reply = '$details', ticket_reply_type = '$ticket_reply_type', ticket_reply_time_worked = '$ticket_reply_time_worked', ticket_reply_by = $session_user_id, ticket_reply_ticket_id = $ticket_id"); - - // Logging - logAction("Ticket", "Resolve", "$session_name resolved $ticket_prefix$ticket_number - $ticket_subject", $client_id, $ticket_id); - - customAction('ticket_resolve', $ticket_id); - - // Client notification email - if (!empty($config_smtp_host) && $config_ticket_client_general_notifications == 1 && $private_note == 0) { - - // Get Contact details - $ticket_sql = mysqli_query($mysqli, "SELECT contact_name, contact_email FROM tickets - LEFT JOIN contacts ON ticket_contact_id = contact_id - WHERE ticket_id = $ticket_id - "); - $row = mysqli_fetch_array($ticket_sql); - - $contact_name = sanitizeInput($row['contact_name']); - $contact_email = sanitizeInput($row['contact_email']); - - // Sanitize Config vars from get_settings.php - $from_name = sanitizeInput($config_ticket_from_name); - $from_email = sanitizeInput($config_ticket_from_email); - $base_url = sanitizeInput($config_base_url); - - // Get Company Info - $sql = mysqli_query($mysqli, "SELECT company_name, company_phone, company_phone_country_code FROM companies WHERE company_id = 1"); + $sql = mysqli_query($mysqli, "SELECT * FROM tickets WHERE ticket_id = $ticket_id"); $row = mysqli_fetch_array($sql); - $company_name = sanitizeInput($row['company_name']); - $company_phone = sanitizeInput(formatPhoneNumber($row['company_phone'], $row['company_phone_country_code'])); + + $ticket_prefix = sanitizeInput($row['ticket_prefix']); + $ticket_number = intval($row['ticket_number']); + $ticket_subject = sanitizeInput($row['ticket_subject']); + $current_ticket_priority = sanitizeInput($row['ticket_priority']); + $url_key = sanitizeInput($row['ticket_url_key']); + $client_id = intval($row['ticket_client_id']); + + // Update ticket & insert reply + mysqli_query($mysqli, "UPDATE tickets SET ticket_status = 4, ticket_resolved_at = NOW() WHERE ticket_id = $ticket_id"); + + mysqli_query($mysqli, "INSERT INTO ticket_replies SET ticket_reply = '$details', ticket_reply_type = '$ticket_reply_type', ticket_reply_time_worked = '$ticket_reply_time_worked', ticket_reply_by = $session_user_id, ticket_reply_ticket_id = $ticket_id"); + + // Logging + logAction("Ticket", "Resolve", "$session_name resolved $ticket_prefix$ticket_number - $ticket_subject", $client_id, $ticket_id); + + customAction('ticket_resolve', $ticket_id); + + // Client notification email + if (!empty($config_smtp_host) && $config_ticket_client_general_notifications == 1 && $private_note == 0) { + + // Get Contact details + $ticket_sql = mysqli_query($mysqli, "SELECT contact_name, contact_email FROM tickets + LEFT JOIN contacts ON ticket_contact_id = contact_id + WHERE ticket_id = $ticket_id + "); + $row = mysqli_fetch_array($ticket_sql); + + $contact_name = sanitizeInput($row['contact_name']); + $contact_email = sanitizeInput($row['contact_email']); + + // Sanitize Config vars from get_settings.php + $from_name = sanitizeInput($config_ticket_from_name); + $from_email = sanitizeInput($config_ticket_from_email); + $base_url = sanitizeInput($config_base_url); + + // Get Company Info + $sql = mysqli_query($mysqli, "SELECT company_name, company_phone, company_phone_country_code FROM companies WHERE company_id = 1"); + $row = mysqli_fetch_array($sql); + $company_name = sanitizeInput($row['company_name']); + $company_phone = sanitizeInput(formatPhoneNumber($row['company_phone'], $row['company_phone_country_code'])); - // EMAIL - $subject = "Ticket resolved - [$ticket_prefix$ticket_number] - $ticket_subject | (pending closure)"; - $body = "##- Please type your reply above this line -##

Hello $contact_name,

Your ticket regarding \"$ticket_subject\" has been marked as solved and is pending closure.

$details

If your request/issue is resolved, you can simply ignore this email. If you need further assistance, please reply or re-open to let us know!

Ticket: $ticket_prefix$ticket_number
Subject: $ticket_subject
Portal: https://$base_url/client/ticket.php?id=$ticket_id

--
$company_name - Support
$config_ticket_from_email
$company_phone"; + // EMAIL + $subject = "Ticket resolved - [$ticket_prefix$ticket_number] - $ticket_subject | (pending closure)"; + $body = "##- Please type your reply above this line -##

Hello $contact_name,

Your ticket regarding \"$ticket_subject\" has been marked as solved and is pending closure.

$details

If your request/issue is resolved, you can simply ignore this email. If you need further assistance, please reply or re-open to let us know!

Ticket: $ticket_prefix$ticket_number
Subject: $ticket_subject
Portal: https://$base_url/client/ticket.php?id=$ticket_id

--
$company_name - Support
$config_ticket_from_email
$company_phone"; - // Check email valid - if (filter_var($contact_email, FILTER_VALIDATE_EMAIL)) { + // Check email valid + if (filter_var($contact_email, FILTER_VALIDATE_EMAIL)) { - $data = []; + $data = []; - // Email Ticket Contact - // Queue Mail + // Email Ticket Contact + // Queue Mail - $data[] = [ - 'from' => $from_email, - 'from_name' => $from_name, - 'recipient' => $contact_email, - 'recipient_name' => $contact_name, - 'subject' => $subject, - 'body' => $body - ]; - } + $data[] = [ + 'from' => $from_email, + 'from_name' => $from_name, + 'recipient' => $contact_email, + 'recipient_name' => $contact_name, + 'subject' => $subject, + 'body' => $body + ]; + } - // Also Email all the watchers - $sql_watchers = mysqli_query($mysqli, "SELECT watcher_email FROM ticket_watchers WHERE watcher_ticket_id = $ticket_id"); - $body .= "

----------------------------------------
YOU ARE A COLLABORATOR ON THIS TICKET"; - while ($row = mysqli_fetch_array($sql_watchers)) { - $watcher_email = sanitizeInput($row['watcher_email']); + // Also Email all the watchers + $sql_watchers = mysqli_query($mysqli, "SELECT watcher_email FROM ticket_watchers WHERE watcher_ticket_id = $ticket_id"); + $body .= "

----------------------------------------
YOU ARE A COLLABORATOR ON THIS TICKET"; + while ($row = mysqli_fetch_array($sql_watchers)) { + $watcher_email = sanitizeInput($row['watcher_email']); - // Queue Mail - $data[] = [ - 'from' => $from_email, - 'from_name' => $from_name, - 'recipient' => $watcher_email, - 'recipient_name' => $watcher_email, - 'subject' => $subject, - 'body' => $body - ]; - } - addToMailQueue($data); - } // End Mail IF + // Queue Mail + $data[] = [ + 'from' => $from_email, + 'from_name' => $from_name, + 'recipient' => $watcher_email, + 'recipient_name' => $watcher_email, + 'subject' => $subject, + 'body' => $body + ]; + } + addToMailQueue($data); + } // End Mail IF + } else { + $skipped_count++; + } // End Task Check } // End Loop } // End Array Empty Check $_SESSION['alert_message'] = "Resolved $ticket_count Tickets"; + if ($skipped_count > 0) { + $_SESSION['alert_type'] = "info"; + $_SESSION['alert_message'] .= " $skipped_count ticket(s) could not be resolved because they have open tasks."; + } + header("Location: " . $_SERVER["HTTP_REFERER"]); } From 07726322dff05a68d07f1f81678b5f582dfdceae Mon Sep 17 00:00:00 2001 From: johnnyq Date: Wed, 11 Jun 2025 20:47:27 -0400 Subject: [PATCH 20/63] Project Details: Add Bulk Actions to tickets and allow tickets to be sorted --- project_details.php | 363 ++++++++++++++++++++++++++++---------------- tickets.php | 64 ++++---- 2 files changed, 260 insertions(+), 167 deletions(-) diff --git a/project_details.php b/project_details.php index 4bfc25da..fd5a0353 100644 --- a/project_details.php +++ b/project_details.php @@ -1,5 +1,9 @@ 0) { ?> -
+
+
-
Project Tickets
-
-
+
+
Ticket @@ -181,14 +183,15 @@
+ +
+
From d79a17adb03ec21c599fc6ca3fb2f7354f98faa2 Mon Sep 17 00:00:00 2001 From: johnnyq Date: Mon, 9 Jun 2025 20:28:10 -0400 Subject: [PATCH 07/63] Added sanitize_url function to strip out unsupported URI Schemas schema:// if not on the allow list it will show unsupport://URL --- ajax/ajax_asset_details.php | 38 ++++++++++++++++++------------------- asset_details.php | 24 +++++++++++------------ assets.php | 4 ++-- functions.php | 19 +++++++++++++++++++ includes/side_nav.php | 2 +- includes/top_nav.php | 2 +- 6 files changed, 54 insertions(+), 35 deletions(-) diff --git a/ajax/ajax_asset_details.php b/ajax/ajax_asset_details.php index 3ffa50a2..ec378459 100644 --- a/ajax/ajax_asset_details.php +++ b/ajax/ajax_asset_details.php @@ -24,8 +24,8 @@ $asset_make = nullable_htmlentities($row['asset_make']); $asset_model = nullable_htmlentities($row['asset_model']); $asset_serial = nullable_htmlentities($row['asset_serial']); $asset_os = nullable_htmlentities($row['asset_os']); -$asset_uri = nullable_htmlentities($row['asset_uri']); -$asset_uri_2 = nullable_htmlentities($row['asset_uri_2']); +$asset_uri = sanitize_url($row['asset_uri']); +$asset_uri_2 = sanitize_url($row['asset_uri_2']); $asset_status = nullable_htmlentities($row['asset_status']); $asset_purchase_reference = nullable_htmlentities($row['asset_purchase_reference']); $asset_purchase_date = nullable_htmlentities($row['asset_purchase_date']); @@ -262,25 +262,25 @@ ob_start();
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -291,19 +291,19 @@ ob_start();
@@ -315,19 +315,19 @@ ob_start();
-
+
-
+
-
+
-
+
-
+
diff --git a/asset_details.php b/asset_details.php index ca4eaf2e..632c87b2 100644 --- a/asset_details.php +++ b/asset_details.php @@ -34,8 +34,8 @@ if (isset($_GET['asset_id'])) { $asset_model = nullable_htmlentities($row['asset_model']); $asset_serial = nullable_htmlentities($row['asset_serial']); $asset_os = nullable_htmlentities($row['asset_os']); - $asset_uri = nullable_htmlentities($row['asset_uri']); - $asset_uri_2 = nullable_htmlentities($row['asset_uri_2']); + $asset_uri = sanitize_url($row['asset_uri']); + $asset_uri_2 = sanitize_url($row['asset_uri_2']); $asset_status = nullable_htmlentities($row['asset_status']); $asset_purchase_reference = nullable_htmlentities($row['asset_purchase_reference']); $asset_purchase_date = nullable_htmlentities($row['asset_purchase_date']); @@ -283,19 +283,19 @@ if (isset($_GET['asset_id'])) {
-
+
-
+
-
+
- +
- +
@@ -307,19 +307,19 @@ if (isset($_GET['asset_id'])) {
-
+
-
+
-
+
-
+
-
+
diff --git a/assets.php b/assets.php index bdbb6112..a74df2b8 100644 --- a/assets.php +++ b/assets.php @@ -486,8 +486,8 @@ if (mysqli_num_rows($os_sql) > 0) { $asset_ipv6 = nullable_htmlentities($row['interface_ipv6']); $asset_nat_ip = nullable_htmlentities($row['interface_nat_ip']); $asset_mac = nullable_htmlentities($row['interface_mac']); - $asset_uri = nullable_htmlentities($row['asset_uri']); - $asset_uri_2 = nullable_htmlentities($row['asset_uri_2']); + $asset_uri = sanitize_url($row['asset_uri']); + $asset_uri_2 = sanitize_url($row['asset_uri_2']); $asset_status = nullable_htmlentities($row['asset_status']); $asset_purchase_reference = nullable_htmlentities($row['asset_purchase_reference']); $asset_purchase_date = nullable_htmlentities($row['asset_purchase_date']); diff --git a/functions.php b/functions.php index 1c1e9b6f..b440ef9e 100644 --- a/functions.php +++ b/functions.php @@ -1652,3 +1652,22 @@ function display_folder_options($parent_folder_id, $client_id, $folder_location display_folder_options($folder_id, $client_id, $folder_location, $indent + 1); } } + +function sanitize_url($url) { + $allowed = ['http', 'https', 'file', 'ftp', 'ftps', 'sftp', 'dav', 'webdav', 'caldav', 'carddav', 'ssh', 'telnet', 'smb', 'rdp', 'vnc', 'rustdesk', 'anydesk', 'connectwise', 'splashtop', 'sip', 'sips', 'ldap', 'ldaps']; + $parts = parse_url($url); + if (isset($parts['scheme']) && !in_array(strtolower($parts['scheme']), $allowed)) { + // Remove the scheme and colon + $pos = strpos($url, ':'); + $without_scheme = $url; + if ($pos !== false) { + $without_scheme = substr($url, $pos + 1); // This keeps slashes (e.g. //pizza.com) + } + // Prepend 'unsupported://' (strip any leading slashes from $without_scheme to avoid triple slashes) + $unsupported = 'unsupported://' . ltrim($without_scheme, '/'); + return htmlspecialchars($unsupported, ENT_QUOTES, 'UTF-8'); + } + + // Safe schemes: return escaped original URL + return htmlspecialchars($url, ENT_QUOTES, 'UTF-8'); +} \ No newline at end of file diff --git a/includes/side_nav.php b/includes/side_nav.php index 7d30cc8a..24c1f288 100644 --- a/includes/side_nav.php +++ b/includes/side_nav.php @@ -211,7 +211,7 @@ while ($row = mysqli_fetch_array($sql_custom_links)) { $custom_link_name = nullable_htmlentities($row['custom_link_name']); - $custom_link_uri = nullable_htmlentities($row['custom_link_uri']); + $custom_link_uri = sanitize_url($row['custom_link_uri']); $custom_link_icon = nullable_htmlentities($row['custom_link_icon']); $custom_link_new_tab = intval($row['custom_link_new_tab']); if ($custom_link_new_tab == 1) { diff --git a/includes/top_nav.php b/includes/top_nav.php index 45aeaa88..944cd3c2 100644 --- a/includes/top_nav.php +++ b/includes/top_nav.php @@ -36,7 +36,7 @@ while ($row = mysqli_fetch_array($sql_custom_links)) { $custom_link_name = nullable_htmlentities($row['custom_link_name']); - $custom_link_uri = nullable_htmlentities($row['custom_link_uri']); + $custom_link_uri = sanitize_url($row['custom_link_uri']); $custom_link_icon = nullable_htmlentities($row['custom_link_icon']); $custom_link_new_tab = intval($row['custom_link_new_tab']); if ($custom_link_new_tab == 1) { From f2bbc170da404402c67bfd66cd75f45cea107cbf Mon Sep 17 00:00:00 2001 From: wrongecho Date: Tue, 10 Jun 2025 09:03:00 +0100 Subject: [PATCH 08/63] Update how functions.php gets the remote IP address for logging - Builds on PR #1210 to always get the leftmost IP address - Cloudflare (HTTP_CF_CONNECTING_IP) must now be explicitly defined, otherwise people could add the HTTP_CF_CONNECTING_IP header to a non-Cloudflare host and spoof IPs - Tidy up the if/else logic a little --- functions.php | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/functions.php b/functions.php index b440ef9e..c6575cdc 100644 --- a/functions.php +++ b/functions.php @@ -77,17 +77,21 @@ function getUserAgent() { } function getIP() { - if (defined("CONST_GET_IP_METHOD")) { - if (CONST_GET_IP_METHOD == "HTTP_X_FORWARDED_FOR") { - $ip = getenv('HTTP_X_FORWARDED_FOR'); - } else { - $ip = $_SERVER["HTTP_CF_CONNECTING_IP"] ?? $_SERVER['REMOTE_ADDR']; - } - } else { + + // Default way to get IP + $ip = $_SERVER['REMOTE_ADDR']; + + // Allow overrides via config.php in-case we use a proxy - https://docs.itflow.org/config_php + if (defined("CONST_GET_IP_METHOD") && CONST_GET_IP_METHOD == "HTTP_X_FORWARDED_FOR") { + $ip = explode(',', getenv('HTTP_X_FORWARDED_FOR'))[0] ?? $_SERVER['REMOTE_ADDR'];; + } elseif (defined("CONST_GET_IP_METHOD") && CONST_GET_IP_METHOD == "HTTP_CF_CONNECTING_IP") { $ip = $_SERVER["HTTP_CF_CONNECTING_IP"] ?? $_SERVER['REMOTE_ADDR']; } + // Abort if something isn't right if (!filter_var($ip, FILTER_VALIDATE_IP)) { + error_log("ITFlow - Could not validate remote IP address"); + error_log("ITFlow - IP was [$ip] using method " . CONST_GET_IP_METHOD); exit("Potential Security Violation"); } From ac3a02baea21a3cf1be8d2cf84f59b75f46b1acc Mon Sep 17 00:00:00 2001 From: wrongecho Date: Tue, 10 Jun 2025 09:19:29 +0100 Subject: [PATCH 09/63] Disallow turning on login key without a secret --- post/admin/admin_settings_security.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/post/admin/admin_settings_security.php b/post/admin/admin_settings_security.php index 4862cbf1..7d60c52f 100644 --- a/post/admin/admin_settings_security.php +++ b/post/admin/admin_settings_security.php @@ -12,6 +12,11 @@ if (isset($_POST['edit_security_settings'])) { $config_login_remember_me_expire = intval($_POST['config_login_remember_me_expire']); $config_log_retention = intval($_POST['config_log_retention']); + // Disallow turning on login key without a secret + if (empty($config_login_key_secret)) { + $config_login_key_required = 0; + } + mysqli_query($mysqli,"UPDATE settings SET config_login_message = '$config_login_message', config_login_key_required = '$config_login_key_required', config_login_key_secret = '$config_login_key_secret', config_login_remember_me_expire = $config_login_remember_me_expire, config_log_retention = $config_log_retention WHERE company_id = 1"); // Logging From 8745d098902f76ef7771405a035b82ac0dcafd52 Mon Sep 17 00:00:00 2001 From: johnnyq Date: Tue, 10 Jun 2025 12:11:58 -0400 Subject: [PATCH 10/63] Add sanitize the remaining uris that allow uri type:// refactored service details --- ajax/ajax_asset_details.php | 4 +- ajax/ajax_credential_edit.php | 6 +- ajax/ajax_service_details.php | 338 ++++++++++++---------------------- asset_details.php | 4 +- credentials.php | 6 +- 5 files changed, 128 insertions(+), 230 deletions(-) diff --git a/ajax/ajax_asset_details.php b/ajax/ajax_asset_details.php index ec378459..4accc12d 100644 --- a/ajax/ajax_asset_details.php +++ b/ajax/ajax_asset_details.php @@ -300,10 +300,10 @@ ob_start();
-
+
-
+
diff --git a/ajax/ajax_credential_edit.php b/ajax/ajax_credential_edit.php index ea132dfe..3b25b61e 100644 --- a/ajax/ajax_credential_edit.php +++ b/ajax/ajax_credential_edit.php @@ -12,6 +12,8 @@ $credential_name = nullable_htmlentities($row['credential_name']); $credential_description = nullable_htmlentities($row['credential_description']); $credential_uri = nullable_htmlentities($row['credential_uri']); $credential_uri_2 = nullable_htmlentities($row['credential_uri_2']); +$credential_uri_link = sanitize_url($row['credential_uri']); +$credential_uri_2_link = sanitize_url($row['credential_uri_2']); $credential_username = nullable_htmlentities(decryptCredentialEntry($row['credential_username'])); $credential_password = nullable_htmlentities(decryptCredentialEntry($row['credential_password'])); $credential_otp_secret = nullable_htmlentities($row['credential_otp_secret']); @@ -137,7 +139,7 @@ ob_start();
- +
@@ -153,7 +155,7 @@ ob_start();
- +
diff --git a/ajax/ajax_service_details.php b/ajax/ajax_service_details.php index 3c26234a..8c386fba 100644 --- a/ajax/ajax_service_details.php +++ b/ajax/ajax_service_details.php @@ -5,7 +5,6 @@ require_once '../includes/ajax_header.php'; $service_id = intval($_GET['id']); $sql = mysqli_query($mysqli, "SELECT * FROM services WHERE service_id = $service_id LIMIT 1"); - $row = mysqli_fetch_array($sql); $service_name = nullable_htmlentities($row['service_name']); $service_description = nullable_htmlentities($row['service_description']); @@ -17,6 +16,7 @@ $service_created_at = nullable_htmlentities($row['service_created_at']); $service_updated_at = nullable_htmlentities($row['service_updated_at']); $service_review_due = nullable_htmlentities($row['service_review_due']); $client_id = intval($row['service_client_id']); + // Service Importance if ($service_importance == "High") { $service_importance_display = "$service_importance"; @@ -55,6 +55,7 @@ $sql_domains = mysqli_query( LEFT JOIN domains ON service_domains.domain_id = domains.domain_id WHERE service_id = $service_id" ); + // Associated Certificates $sql_certificates = mysqli_query( $mysqli, @@ -63,10 +64,6 @@ $sql_certificates = mysqli_query( WHERE service_id = $service_id" ); -// Associated URLs ---- REMOVED for now -//$sql_urls = mysqli_query($mysqli, "SELECT * FROM service_urls -//WHERE service_id = '$service_id'"); - // Associated Vendors $sql_vendors = mysqli_query( $mysqli, @@ -116,149 +113,99 @@ ob_start(); 0) { ?> -
Assets
- - 0) { + echo "
Assets
    "; + mysqli_data_seek($sql_assets, 0); + while ($row = mysqli_fetch_array($sql_assets)) { + $asset_id = intval($row['asset_id']); + $asset_name = nullable_htmlentities($row['asset_name']); + $ip = !empty($row['interface_ip']) ? '(' . nullable_htmlentities($row['interface_ip']) . ')' : ''; + echo "
  • $asset_name$ip
  • "; + } + echo "
"; } ?> -
Networks
-
    - Networks
      "; + foreach ($networks as $network) { + $network_parts = explode(":", $network); + $network_name = $network_parts[0]; + $network_vlan = $network_parts[1] ?? ''; + echo "
    • $network_name (VLAN $network_vlan)
    • "; + } + echo "
    "; } - foreach($networks as $network) { - $network = explode(":", $network); - echo "
  • $network[0] (VLAN $network[1])
  • "; - } - - ?> -
- -
Locations
-
    - Locations
      "; + foreach ($location_names as $location) { + echo "
    • $location
    • "; + } + echo "
    "; } - foreach($location_names as $location) { - echo "
  • $location
  • "; - } - ?> -
- 0) { ?> -
Domains
-
    - $row[domain_name]"; - } + if (mysqli_num_rows($sql_domains) > 0) { + echo "
    Domains
      "; + mysqli_data_seek($sql_domains, 0); + while ($row = mysqli_fetch_array($sql_domains)) { + if (!empty($row['domain_name'])) { + $domain_name = nullable_htmlentities($row['domain_name']); + echo "
    • $domain_name
    • "; } - ?> -
    - "; } ?> 0) { ?> -
    Certificates
    -
      - $row[certificate_name] ($row[certificate_domain])"; - } + if (mysqli_num_rows($sql_certificates) > 0) { + echo "
      Certificates
        "; + mysqli_data_seek($sql_certificates, 0); + while ($row = mysqli_fetch_array($sql_certificates)) { + if (!empty($row['certificate_name'])) { + $certificate_name = nullable_htmlentities($row['certificate_name']); + $certificate_domain = nullable_htmlentities($row['certificate_domain']); + echo "
      • $certificate_name ($certificate_domain)
      • "; } - ?> -
      - "; } ?>
-
@@ -267,148 +214,96 @@ ob_start(); 0) { ?> -
Vendors
- - 0) { + echo "
Vendors
    "; + mysqli_data_seek($sql_vendors, 0); + while ($row = mysqli_fetch_array($sql_vendors)) { + $vendor_id = intval($row['vendor_id']); + $vendor_name = nullable_htmlentities($row['vendor_name']); + echo "
  • $vendor_name
  • "; + } + echo "
"; } ?> 0) { ?> -
Contacts
- - 0) { + echo "
Contacts
    "; + mysqli_data_seek($sql_contacts, 0); + while ($row = mysqli_fetch_array($sql_contacts)) { + $contact_id = intval($row['contact_id']); + $contact_name = nullable_htmlentities($row['contact_name']); + echo "
  • $contact_name
  • "; + } + echo "
"; } ?> 0 || mysqli_num_rows($sql_credentials) > 0) { ?> -
Credentials
-
    - $row[credential_name]"; - } + if (mysqli_num_rows($sql_assets) > 0 || mysqli_num_rows($sql_credentials) > 0) { + echo "
    Credentials
      "; + // Credentials linked to assets + mysqli_data_seek($sql_assets, 0); + while ($row = mysqli_fetch_array($sql_assets)) { + $credential_name = nullable_htmlentities($row['credential_name']); + if (!empty($credential_name)) { + echo "
    • $credential_name
    • "; } - - // Showing explicitly linked credentials - while ($row = mysqli_fetch_array($sql_credentials)) { - if (!empty($row['credential_name'])) { - echo "
    • $row[credential_name]
    • "; - } + } + // Explicitly linked credentials + mysqli_data_seek($sql_credentials, 0); + while ($row = mysqli_fetch_array($sql_credentials)) { + $credential_name = nullable_htmlentities($row['credential_name']); + if (!empty($credential_name)) { + echo "
    • $credential_name
    • "; } - ?> -
    - "; } ?> -
    URLs
    -
      - $row[credential_uri]"; - } - } - - // Reset the $sql_assets pointer to the start - mysqli_data_seek($sql_assets, 0); - - // Show URLs linked to assets, that also have credentials - while ($row = mysqli_fetch_array($sql_assets)) { - if (!empty($row['credential_uri'])) { - echo "
    • $row[credential_uri]
    • "; - } - } - ?> -
    - URLs
      "; + foreach ($urls as $url) { + $label = htmlspecialchars(parse_url($url, PHP_URL_HOST) ?: $url); + echo "
    • $label
    • "; + } + echo "
    "; } ?> 0) { ?> -
    Documents
    - - 0) { + echo "
    Documents
      "; + mysqli_data_seek($sql_docs, 0); + while ($row = mysqli_fetch_array($sql_docs)) { + $document_id = intval($row['document_id']); + $document_name = nullable_htmlentities($row['document_name']); + echo "
    • $document_name
    • "; + } + echo "
    "; } ?> - - - - -
@@ -416,3 +311,4 @@ ob_start(); diff --git a/asset_details.php b/asset_details.php index 632c87b2..3e8deaf0 100644 --- a/asset_details.php +++ b/asset_details.php @@ -292,10 +292,10 @@ if (isset($_GET['asset_id'])) {
-
+
-
+
diff --git a/credentials.php b/credentials.php index 5a88e058..bb74060b 100644 --- a/credentials.php +++ b/credentials.php @@ -300,13 +300,13 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); $credential_id = intval($row['c_credential_id']); $credential_name = nullable_htmlentities($row['credential_name']); $credential_description = nullable_htmlentities($row['credential_description']); - $credential_uri = nullable_htmlentities($row['credential_uri']); + $credential_uri = sanitize_url($row['credential_uri']); if (empty($credential_uri)) { $credential_uri_display = "-"; } else { - $credential_uri_display = truncate($credential_uri,40) . ""; + $credential_uri_display = "" . truncate($credential_uri,40) . ""; } - $credential_uri_2 = nullable_htmlentities($row['credential_uri_2']); + $credential_uri_2 = sanitize_url($row['credential_uri_2']); $credential_username = nullable_htmlentities(decryptCredentialEntry($row['credential_username'])); if (empty($credential_username)) { $credential_username_display = "-"; From 4e0252553aa7005ec274ddc6b0057bb68b98f9a4 Mon Sep 17 00:00:00 2001 From: wrongecho Date: Wed, 11 Jun 2025 08:18:32 +0100 Subject: [PATCH 11/63] rm extra ; --- functions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/functions.php b/functions.php index c6575cdc..7c74dea4 100644 --- a/functions.php +++ b/functions.php @@ -83,7 +83,7 @@ function getIP() { // Allow overrides via config.php in-case we use a proxy - https://docs.itflow.org/config_php if (defined("CONST_GET_IP_METHOD") && CONST_GET_IP_METHOD == "HTTP_X_FORWARDED_FOR") { - $ip = explode(',', getenv('HTTP_X_FORWARDED_FOR'))[0] ?? $_SERVER['REMOTE_ADDR'];; + $ip = explode(',', getenv('HTTP_X_FORWARDED_FOR'))[0] ?? $_SERVER['REMOTE_ADDR']; } elseif (defined("CONST_GET_IP_METHOD") && CONST_GET_IP_METHOD == "HTTP_CF_CONNECTING_IP") { $ip = $_SERVER["HTTP_CF_CONNECTING_IP"] ?? $_SERVER['REMOTE_ADDR']; } From a462ab36f83d8a8c17d17969943b18145402f790 Mon Sep 17 00:00:00 2001 From: johnnyq Date: Wed, 11 Jun 2025 14:56:23 -0400 Subject: [PATCH 12/63] Ticket add / edit / builk sort categories alphabeticaly --- ajax/ajax_ticket_edit.php | 2 +- modals/ticket_add_modal.php | 2 +- modals/ticket_bulk_edit_category_modal.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ajax/ajax_ticket_edit.php b/ajax/ajax_ticket_edit.php index ab181869..de022663 100644 --- a/ajax/ajax_ticket_edit.php +++ b/ajax/ajax_ticket_edit.php @@ -105,7 +105,7 @@ ob_start(); + + + + + + + + - - + + Add Payment @@ -701,7 +705,6 @@ if (isset($_GET['invoice_id'])) { \ No newline at end of file diff --git a/report_clients_with_balance.php b/report_clients_with_balance.php index 40f15ce7..cd677d07 100644 --- a/report_clients_with_balance.php +++ b/report_clients_with_balance.php @@ -27,8 +27,9 @@ enforceUserPermission('module_financial'); invoices ON clients.client_id = invoices.invoice_client_id - AND invoices.invoice_status NOT LIKE 'Draft' - AND invoices.invoice_status NOT LIKE 'Cancelled' + AND invoices.invoice_status != 'Draft' + AND invoices.invoice_status != 'Cancelled' + AND invoice_status != 'Non-Billable' LEFT JOIN (SELECT payment_invoice_id, From a3554b3dfdb56d6e1d98ef2f93bd5bc135dd4c26 Mon Sep 17 00:00:00 2001 From: johnnyq Date: Wed, 11 Jun 2025 17:16:03 -0400 Subject: [PATCH 15/63] Add one more Non-Billable Check in invoices --- invoices.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invoices.php b/invoices.php index 45abb016..5f59a92c 100644 --- a/invoices.php +++ b/invoices.php @@ -61,7 +61,7 @@ $sql_total_overdue_partial_amount = mysqli_query($mysqli, "SELECT SUM(payment_am $row = mysqli_fetch_array($sql_total_overdue_partial_amount); $total_overdue_partial_amount = floatval($row['total_overdue_partial_amount']); -$sql_total_overdue_amount = mysqli_query($mysqli, "SELECT SUM(invoice_amount) AS total_overdue_amount FROM invoices WHERE invoice_status NOT LIKE 'Draft' AND invoice_status NOT LIKE 'Paid' AND invoice_status NOT LIKE 'Cancelled' AND invoice_due < CURDATE() $client_query"); +$sql_total_overdue_amount = mysqli_query($mysqli, "SELECT SUM(invoice_amount) AS total_overdue_amount FROM invoices WHERE invoice_status != 'Draft' AND invoice_status != 'Paid' AND invoice_status != 'Cancelled' AND invoice_status != 'Non-Billable' AND invoice_due < CURDATE() $client_query"); $row = mysqli_fetch_array($sql_total_overdue_amount); $total_overdue_amount = floatval($row['total_overdue_amount']); From 5530e89f419a6637b858b9c938838a0be1126bbe Mon Sep 17 00:00:00 2001 From: johnnyq Date: Wed, 11 Jun 2025 17:18:28 -0400 Subject: [PATCH 16/63] Add one more Non-Billable Check in invoices --- invoice.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/invoice.php b/invoice.php index 3ac52b80..49d58455 100644 --- a/invoice.php +++ b/invoice.php @@ -283,7 +283,7 @@ if (isset($_GET['invoice_id'])) { "> Guest URL - + Cancel From a3d4a52188189f4cbf920b5404fdafd2e709762a Mon Sep 17 00:00:00 2001 From: johnnyq Date: Wed, 11 Jun 2025 17:29:38 -0400 Subject: [PATCH 17/63] Add Mark Sent when invoice is draft to Action Dropdown for invoice listings --- invoices.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/invoices.php b/invoices.php index 5f59a92c..824b2d79 100644 --- a/invoices.php +++ b/invoices.php @@ -375,10 +375,16 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); - Send + Send Email + + + Mark Sent + + + Delete From 7c3332570a7e0730047b75c135d09b11f7a5f290 Mon Sep 17 00:00:00 2001 From: johnnyq Date: Wed, 11 Jun 2025 18:11:08 -0400 Subject: [PATCH 18/63] Add Ticket Category UI for Recurring Tickets --- ajax/ajax_recurring_ticket_edit.php | 57 +++++++++++++++++++++----- modals/recurring_ticket_add_modal.php | 58 ++++++++++++++++++++++----- post/user/ticket.php | 7 ++-- post/user/ticket_recurring_model.php | 19 ++------- recurring_tickets.php | 15 ++++--- scripts/cron.php | 3 +- 6 files changed, 115 insertions(+), 44 deletions(-) diff --git a/ajax/ajax_recurring_ticket_edit.php b/ajax/ajax_recurring_ticket_edit.php index bcdaee3b..2893b742 100644 --- a/ajax/ajax_recurring_ticket_edit.php +++ b/ajax/ajax_recurring_ticket_edit.php @@ -16,6 +16,7 @@ $recurring_ticket_next_run = nullable_htmlentities($row['recurring_ticket_next_r $recurring_ticket_assigned_to = intval($row['recurring_ticket_assigned_to']); $recurring_ticket_contact_id = intval($row['recurring_ticket_contact_id']); $recurring_ticket_asset_id = intval($row['recurring_ticket_asset_id']); +$recurring_ticket_category = intval($row['recurring_ticket_category']); $recurring_ticket_billable = intval($row['recurring_ticket_billable']); // Additional Assets Selected @@ -75,17 +76,53 @@ ob_start(); -
- -
-
- +
+
+
+ +
+
+ +
+ +
+
+
+ +
+
+ +
+
+ +
+ +
+ +
+
-
diff --git a/modals/recurring_ticket_add_modal.php b/modals/recurring_ticket_add_modal.php index 3ed5acbb..891c978c 100644 --- a/modals/recurring_ticket_add_modal.php +++ b/modals/recurring_ticket_add_modal.php @@ -50,18 +50,56 @@
-
- -
-
- +
+ +
+
+ +
+
+ +
+ +
-
+ +
+
+ +
+
+ +
+ +
+ +
+
+
+
+
diff --git a/post/user/ticket.php b/post/user/ticket.php index 98c42884..84d9ad65 100644 --- a/post/user/ticket.php +++ b/post/user/ticket.php @@ -2101,7 +2101,7 @@ if (isset($_POST['add_recurring_ticket'])) { $start_date = sanitizeInput($_POST['start_date']); - mysqli_query($mysqli, "INSERT INTO recurring_tickets SET recurring_ticket_subject = '$subject', recurring_ticket_details = '$details', recurring_ticket_priority = '$priority', recurring_ticket_frequency = '$frequency', recurring_ticket_billable = $billable, recurring_ticket_start_date = '$start_date', recurring_ticket_next_run = '$start_date', recurring_ticket_assigned_to = $assigned_to, recurring_ticket_created_by = $session_user_id, recurring_ticket_client_id = $client_id, recurring_ticket_contact_id = $contact_id, recurring_ticket_asset_id = $asset_id"); + mysqli_query($mysqli, "INSERT INTO recurring_tickets SET recurring_ticket_subject = '$subject', recurring_ticket_details = '$details', recurring_ticket_priority = '$priority', recurring_ticket_frequency = '$frequency', recurring_ticket_billable = $billable, recurring_ticket_start_date = '$start_date', recurring_ticket_next_run = '$start_date', recurring_ticket_assigned_to = $assigned_to, recurring_ticket_created_by = $session_user_id, recurring_ticket_client_id = $client_id, recurring_ticket_contact_id = $contact_id, recurring_ticket_asset_id = $asset_id, recurring_ticket_category = $category"); $recurring_ticket_id = mysqli_insert_id($mysqli); @@ -2130,7 +2130,7 @@ if (isset($_POST['edit_recurring_ticket'])) { $recurring_ticket_id = intval($_POST['recurring_ticket_id']); $next_run_date = sanitizeInput($_POST['next_date']); - mysqli_query($mysqli, "UPDATE recurring_tickets SET recurring_ticket_subject = '$subject', recurring_ticket_details = '$details', recurring_ticket_priority = '$priority', recurring_ticket_frequency = '$frequency', recurring_ticket_billable = $billable, recurring_ticket_next_run = '$next_run_date', recurring_ticket_assigned_to = $assigned_to, recurring_ticket_asset_id = $asset_id, recurring_ticket_contact_id = $contact_id WHERE recurring_ticket_id = $recurring_ticket_id"); + mysqli_query($mysqli, "UPDATE recurring_tickets SET recurring_ticket_subject = '$subject', recurring_ticket_details = '$details', recurring_ticket_priority = '$priority', recurring_ticket_frequency = '$frequency', recurring_ticket_billable = $billable, recurring_ticket_next_run = '$next_run_date', recurring_ticket_assigned_to = $assigned_to, recurring_ticket_asset_id = $asset_id, recurring_ticket_contact_id = $contact_id, recurring_ticket_category = $category WHERE recurring_ticket_id = $recurring_ticket_id"); // Add Additional Assets if (isset($_POST['additional_assets'])) { @@ -2171,6 +2171,7 @@ if (isset($_GET['force_recurring_ticket'])) { $contact_id = intval($row['recurring_ticket_contact_id']); $client_id = intval($row['recurring_ticket_client_id']); $asset_id = intval($row['recurring_ticket_asset_id']); + $category = intval($row['recurring_ticket_category']); $url_key = randomString(156); $ticket_status = 1; // Default @@ -2190,7 +2191,7 @@ if (isset($_GET['force_recurring_ticket'])) { mysqli_query($mysqli, "UPDATE settings SET config_ticket_next_number = $new_config_ticket_next_number WHERE company_id = 1"); // Raise the ticket - mysqli_query($mysqli, "INSERT INTO tickets SET ticket_prefix = '$config_ticket_prefix', ticket_number = $ticket_number, ticket_subject = '$subject', ticket_details = '$details', ticket_priority = '$priority', ticket_status = '$ticket_status', ticket_billable = $billable, ticket_url_key = '$url_key', ticket_created_by = $created_id, ticket_assigned_to = $assigned_id, ticket_contact_id = $contact_id, ticket_client_id = $client_id, ticket_asset_id = $asset_id, ticket_recurring_ticket_id = $recurring_ticket_id"); + mysqli_query($mysqli, "INSERT INTO tickets SET ticket_prefix = '$config_ticket_prefix', ticket_number = $ticket_number, ticket_subject = '$subject', ticket_details = '$details', ticket_priority = '$priority', ticket_status = '$ticket_status', ticket_billable = $billable, ticket_url_key = '$url_key', ticket_created_by = $created_id, ticket_assigned_to = $assigned_id, ticket_contact_id = $contact_id, ticket_client_id = $client_id, ticket_asset_id = $asset_id, ticket_category = $category, ticket_recurring_ticket_id = $recurring_ticket_id"); $id = mysqli_insert_id($mysqli); // Copy Additional Assets from Recurring ticket to new ticket diff --git a/post/user/ticket_recurring_model.php b/post/user/ticket_recurring_model.php index 038c7fc3..eae9e116 100644 --- a/post/user/ticket_recurring_model.php +++ b/post/user/ticket_recurring_model.php @@ -7,18 +7,7 @@ $priority = sanitizeInput($_POST['priority']); $details = mysqli_real_escape_string($mysqli, $_POST['details']); $frequency = sanitizeInput($_POST['frequency']); $billable = intval($_POST['billable'] ?? 0); - -$asset_id = "0"; -if (isset($_POST['asset'])) { - $asset_id = intval($_POST['asset']); -} - -$contact_id = "0"; -if (isset($_POST['contact'])) { - $contact_id = intval($_POST['contact']); -} - -$assigned_to = "0"; -if (isset($_POST['assigned_to'])) { - $assigned_to = intval($_POST['assigned_to']); -} +$asset_id = intval($_POST['asset'] ?? 0); +$contact_id = intval($_POST['contact'] ?? 0); +$assigned_to = intval($_POST['assigned_to'] ?? 0); +$category = intval($_POST['category'] ?? 0); \ No newline at end of file diff --git a/recurring_tickets.php b/recurring_tickets.php index af3edf25..56c50705 100644 --- a/recurring_tickets.php +++ b/recurring_tickets.php @@ -31,8 +31,9 @@ $url_query_strings_sort = http_build_query($get_copy); $sql = mysqli_query( $mysqli, "SELECT SQL_CALC_FOUND_ROWS * FROM recurring_tickets - LEFT JOIN clients on recurring_ticket_client_id = client_id - WHERE recurring_tickets.recurring_ticket_subject LIKE '%$q%' + LEFT JOIN clients ON recurring_ticket_client_id = client_id + LEFT JOIN categories ON category_id = recurring_ticket_category + WHERE (recurring_tickets.recurring_ticket_subject LIKE '%$q%' OR category_name LIKE '%$q%') $rec_ticket_permission_snippet $client_query ORDER BY @@ -127,6 +128,11 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); Subject +
+ + Category + + Priority @@ -159,6 +165,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); $recurring_ticket_priority = nullable_htmlentities($row['recurring_ticket_priority']); $recurring_ticket_frequency = nullable_htmlentities($row['recurring_ticket_frequency']); $recurring_ticket_next_run = nullable_htmlentities($row['recurring_ticket_next_run']); + $recurring_ticket_category = getFallBack(nullable_htmlentities($row['category_name'])); $recurring_ticket_client_name = nullable_htmlentities($row['client_name']); ?> @@ -184,11 +191,9 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); - +
- - - - - - - - - - - - Project Tickets - while ($row = mysqli_fetch_array($sql_tickets)) { - $ticket_id = intval($row['ticket_id']); - $ticket_prefix = nullable_htmlentities($row['ticket_prefix']); - $ticket_number = nullable_htmlentities($row['ticket_number']); - $ticket_subject = nullable_htmlentities($row['ticket_subject']); - $ticket_priority = nullable_htmlentities($row['ticket_priority']); - $ticket_status = intval($row['ticket_status']); - $ticket_status_name = nullable_htmlentities($row['ticket_status_name']); - $ticket_status_color = nullable_htmlentities($row['ticket_status_color']); - $ticket_billable = intval($row['ticket_billable']); - $ticket_created_at = nullable_htmlentities($row['ticket_created_at']); - $ticket_created_at_time_ago = timeAgo($row['ticket_created_at']); - $ticket_updated_at = nullable_htmlentities($row['ticket_updated_at']); - $ticket_updated_at_time_ago = timeAgo($row['ticket_updated_at']); - if (empty($ticket_updated_at)) { - if ($ticket_status == 5) { - $ticket_updated_at_display = "

Never

"; - } else { - $ticket_updated_at_display = "

Never

"; - } - } else { - $ticket_updated_at_display = "$ticket_updated_at_time_ago
$ticket_updated_at"; - } - $ticket_closed_at = nullable_htmlentities($row['ticket_closed_at']); - - if ($ticket_priority == "High") { - $ticket_priority_display = "$ticket_priority"; - } elseif ($ticket_priority == "Medium") { - $ticket_priority_display = "$ticket_priority"; - } elseif ($ticket_priority == "Low") { - $ticket_priority_display = "$ticket_priority"; - } else{ - $ticket_priority_display = "-"; - } - - $ticket_assigned_to = intval($row['ticket_assigned_to']); - if (empty($ticket_assigned_to)) { - if ($ticket_status == 5) { - $ticket_assigned_to_display = "

Not Assigned

"; - } else { - $ticket_assigned_to_display = "

Not Assigned

"; - } - } else { - $ticket_assigned_to_display = nullable_htmlentities($row['user_name']); - } - - $project_id = intval($row['ticket_project_id']); - - $client_id = intval($row['client_id']); - $client_name = nullable_htmlentities($row['client_name']); - - $contact_name = nullable_htmlentities($row['contact_name']); - $contact_email = nullable_htmlentities($row['contact_email']); - $contact_archived_at = nullable_htmlentities($row['contact_archived_at']); - if (empty($contact_archived_at)) { - $contact_archived_display = ""; - } else { - $contact_archived_display = "Archived - "; - } - if (empty($contact_name)) { - $contact_display = "-"; - } else { - $contact_display = "$contact_archived_display$contact_name
$contact_email"; - } - - // Get who last updated the ticket - to be shown in the last Response column - $ticket_reply_type = "Client"; // Default to client for unreplied tickets - $ticket_reply_by_display = ""; // Default none - $sql_ticket_reply = mysqli_query($mysqli, "SELECT ticket_reply_type, contact_name, user_name 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 LIMIT 1" - ); - $row = mysqli_fetch_array($sql_ticket_reply); - - if ($row) { - $ticket_reply_type = nullable_htmlentities($row['ticket_reply_type']); - if ($ticket_reply_type == "Client") { - $ticket_reply_by_display = nullable_htmlentities($row['contact_name']); - } else { - $ticket_reply_by_display = nullable_htmlentities($row['user_name']); - } - } - - ?> - - - - - - - - - - - - - - - - - - - - + + + Set Category + + + + Update Priority + + + + Bulk Update/Reply + + + + Merge + + + + Resolve + + + + - -
TicketPriorityStatusAssignedLast ResponseClient
- - - + - - -
-
-
+ + +
+
+ +
+ + + + + + + + + + + + + + Never

"; + } else { + $ticket_updated_at_display = "

Never

"; + } + } else { + $ticket_updated_at_display = "$ticket_updated_at_time_ago
$ticket_updated_at"; + } + $ticket_closed_at = nullable_htmlentities($row['ticket_closed_at']); + + if ($ticket_priority == "High") { + $ticket_priority_display = "$ticket_priority"; + } elseif ($ticket_priority == "Medium") { + $ticket_priority_display = "$ticket_priority"; + } elseif ($ticket_priority == "Low") { + $ticket_priority_display = "$ticket_priority"; + } else{ + $ticket_priority_display = "-"; + } + + $ticket_assigned_to = intval($row['ticket_assigned_to']); + if (empty($ticket_assigned_to)) { + if ($ticket_status == 5) { + $ticket_assigned_to_display = "

Not Assigned

"; + } else { + $ticket_assigned_to_display = "

Not Assigned

"; + } + } else { + $ticket_assigned_to_display = nullable_htmlentities($row['user_name']); + } + + $project_id = intval($row['ticket_project_id']); + + $client_id = intval($row['client_id']); + $client_name = nullable_htmlentities($row['client_name']); + + $contact_name = nullable_htmlentities($row['contact_name']); + $contact_email = nullable_htmlentities($row['contact_email']); + $contact_archived_at = nullable_htmlentities($row['contact_archived_at']); + if (empty($contact_archived_at)) { + $contact_archived_display = ""; + } else { + $contact_archived_display = "Archived - "; + } + if (empty($contact_name)) { + $contact_display = "-"; + } else { + $contact_display = "$contact_archived_display$contact_name
$contact_email"; + } + + // Get who last updated the ticket - to be shown in the last Response column + $ticket_reply_type = "Client"; // Default to client for unreplied tickets + $ticket_reply_by_display = ""; // Default none + $sql_ticket_reply = mysqli_query($mysqli, "SELECT ticket_reply_type, contact_name, user_name 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 LIMIT 1" + ); + $row = mysqli_fetch_array($sql_ticket_reply); + + if ($row) { + $ticket_reply_type = nullable_htmlentities($row['ticket_reply_type']); + if ($ticket_reply_type == "Client") { + $ticket_reply_by_display = nullable_htmlentities($row['contact_name']); + } else { + $ticket_reply_by_display = nullable_htmlentities($row['user_name']); + } + } + + ?> + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+ + Ticket + + + + Priority + + + + Status + + + + Assigned + + + + Last Response + + + + Client + +
+ + +
+ +
+ +
+ + + + + + + +
+
+
+
+ + + + + + +
@@ -465,4 +556,6 @@ require_once "includes/footer.php"; ?> + + diff --git a/tickets.php b/tickets.php index 5d02e799..54f24536 100644 --- a/tickets.php +++ b/tickets.php @@ -251,39 +251,39 @@ $sql_categories = mysqli_query( = 2) { ?> From dba3e895da167f8a89420741030142c7db2332bf Mon Sep 17 00:00:00 2001 From: johnnyq Date: Wed, 11 Jun 2025 22:28:38 -0400 Subject: [PATCH 21/63] UI/UX update in ticket details switch to full card stacks with edit icons for the stackable reference items on the right like asset watchers contact etc --- project_details.php | 52 ++--- ticket.php | 483 +++++++++++++++++++++++++------------------- 2 files changed, 303 insertions(+), 232 deletions(-) diff --git a/project_details.php b/project_details.php index fd5a0353..ca303319 100644 --- a/project_details.php +++ b/project_details.php @@ -326,7 +326,7 @@ if (isset($_GET['project_id'])) {
- +
+ + + + +
@@ -514,29 +514,33 @@ if (isset($_GET['project_id'])) { 0) { ?> -
-
All Tasks
- - - - - - -
- - - - - - - - -
+
+
+
All Tasks
+
+
+ + + + + + +
+ + + + + + + + +
+
diff --git a/ticket.php b/ticket.php index bb64dfb2..55868d1a 100644 --- a/ticket.php +++ b/ticket.php @@ -864,57 +864,53 @@ if (isset($_GET['ticket_id'])) { -
-
Contact
-
- - -
- - -
- -
- -
- -
- -
- -
- -
- -
- - -
- -
-
Contact
-
- - = 2 && empty($ticket_closed_at)) { ?> - data-toggle = "ajax-modal" - data-ajax-url = "ajax/ajax_ticket_contact.php" - data-ajax-id = "" +
+ +
+ +
+ + +
+ + +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
@@ -923,86 +919,113 @@ if (isset($_GET['ticket_id'])) { 0)) { ?> -
- - = 2) { ?> - - -
-
- -
- -
+
+
+
Tasks
+ +
+
- - - - - - + + + + +
- - - = 2) { ?> - - - - - - -
- -
- - - - = 2) { ?> - - - - + = 2) { ?> +
+ +
+
+ +
+
-
-
+ while($row = mysqli_fetch_array($sql_tasks)){ + $task_id = intval($row['task_id']); + $task_name = nullable_htmlentities($row['task_name']); + //$task_description = nullable_htmlentities($row['task_description']); // not in db yet + $task_completion_estimate = intval($row['task_completion_estimate']); + $task_completed_at = nullable_htmlentities($row['task_completed_at']); + ?> +
+ + + = 2) { ?> + + + + + + +
+ +
+ + + + = 2) { ?> + + + + +
+
+
+
@@ -1011,67 +1034,89 @@ if (isset($_GET['ticket_id'])) { 0) { ?> -
-
Watchers
- - -
- - - - + -
-
Asset(s)
-
- - - +
+
+
Primary Asset
+
+ = 2) { ?> + + + + +
- -
+
+ - +
+ + + + + + + + +
+ + } + ?> +
@@ -1079,62 +1124,84 @@ if (isset($_GET['ticket_id'])) { -
-
Vendor
- -
- +
+
+
Vendor
+
+ = 2) { ?> + + + + +
- - if (!empty($vendor_contact_name)) { ?> -
- +
+
- -
- -
- +
+ +
+ -
- -
- +
+ +
+ -
- -
- +
+ +
+ -
- -
- + if (!empty($vendor_phone)) { ?> +
+ +
+ +
+ +
+ + +
-
-
Project
-
- - -
- - -
- +
+
+
Project
+
+ = 2) { ?> + + + +
- +
+
+
+ + +
+ + +
+ +
+ +
From 9c096d1f650ad9c47c897400be029acda2c3d47b Mon Sep 17 00:00:00 2001 From: johnnyq Date: Thu, 12 Jun 2025 13:44:21 -0400 Subject: [PATCH 22/63] Add Setting option to enable or disable ticket autotimer --- admin_settings_ticket.php | 7 ++++++ database_updates.php | 11 +++++---- db.sql | 3 ++- includes/database_version.php | 2 +- includes/get_settings.php | 1 + js/ticket_time_tracking.js | 34 +++++++++++++--------------- post/admin/admin_settings_ticket.php | 3 ++- ticket.php | 5 ++++ 8 files changed, 41 insertions(+), 25 deletions(-) diff --git a/admin_settings_ticket.php b/admin_settings_ticket.php index f91cd4c4..2dcdea6c 100644 --- a/admin_settings_ticket.php +++ b/admin_settings_ticket.php @@ -53,6 +53,13 @@ require_once "includes/inc_all_admin.php";
+
+
+ value="1" id="ticketTimerSwitch"> + +
+
+
diff --git a/database_updates.php b/database_updates.php index e4fba32d..a3c6cf98 100644 --- a/database_updates.php +++ b/database_updates.php @@ -3464,12 +3464,15 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) { mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.1.4'"); } + if (CURRENT_DATABASE_VERSION == '2.1.4') { + mysqli_query($mysqli, "ALTER TABLE `settings` ADD `config_ticket_timer_autostart` TINYINT(1) NOT NULL DEFAULT '1' AFTER `config_ticket_default_billable`"); + mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.1.5'"); + } - - // if (CURRENT_DATABASE_VERSION == '2.1.4') { - // // Insert queries here required to update to DB version 2.1.5 + // if (CURRENT_DATABASE_VERSION == '2.1.5') { + // // Insert queries here required to update to DB version 2.1.6 // // Then, update the database to the next sequential version - // mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.1.5'"); + // mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.1.6'"); // } } else { diff --git a/db.sql b/db.sql index 203f5771..89f581eb 100644 --- a/db.sql +++ b/db.sql @@ -1812,6 +1812,7 @@ CREATE TABLE `settings` ( `config_ticket_autoclose_hours` int(5) NOT NULL DEFAULT 72, `config_ticket_new_ticket_notification_email` varchar(200) DEFAULT NULL, `config_ticket_default_billable` tinyint(1) NOT NULL DEFAULT 0, + `config_ticket_timer_autostart` tinyint(1) NOT NULL DEFAULT 1, `config_enable_cron` tinyint(1) NOT NULL DEFAULT 0, `config_recurring_auto_send_invoice` tinyint(1) NOT NULL DEFAULT 1, `config_enable_alert_domain_expire` tinyint(1) NOT NULL DEFAULT 1, @@ -2500,4 +2501,4 @@ CREATE TABLE `vendors` ( /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; --- Dump completed on 2025-05-24 13:23:21 +-- Dump completed on 2025-06-12 13:43:40 diff --git a/includes/database_version.php b/includes/database_version.php index 72a223bb..6df39190 100644 --- a/includes/database_version.php +++ b/includes/database_version.php @@ -5,4 +5,4 @@ * It is used in conjunction with database_updates.php */ -DEFINE("LATEST_DATABASE_VERSION", "2.1.4"); +DEFINE("LATEST_DATABASE_VERSION", "2.1.5"); diff --git a/includes/get_settings.php b/includes/get_settings.php index 80e6fa7b..65a28aee 100644 --- a/includes/get_settings.php +++ b/includes/get_settings.php @@ -78,6 +78,7 @@ $config_ticket_default_billable = intval($row['config_ticket_default_billable']) $config_ticket_default_view = intval($row['config_ticket_default_view']); $config_ticket_moving_columns = intval($row['config_ticket_moving_columns']); $config_ticket_ordering = intval($row['config_ticket_ordering']); +$config_ticket_timer_autostart = intval($row['config_ticket_timer_autostart']); // Cron $config_enable_cron = intval($row['config_enable_cron']); diff --git a/js/ticket_time_tracking.js b/js/ticket_time_tracking.js index f2a7d6fe..ea1b69f8 100644 --- a/js/ticket_time_tracking.js +++ b/js/ticket_time_tracking.js @@ -1,10 +1,7 @@ -// Description: This file contains the JavaScript for the ticket time tracking feature - (function() { document.addEventListener("DOMContentLoaded", function() { // Initialize variables var timerInterval = null; - var isPaused = false; var ticketID = getCurrentTicketID(); var elapsedSecs = getElapsedSeconds(); @@ -51,10 +48,8 @@ localStorage.setItem(getLocalStorageKey("startTime"), Date.now().toString()); } timerInterval = setInterval(countTime, 1000); - isPaused = false; document.getElementById("startStopTimer").innerHTML = ""; localStorage.setItem("ticket-timer-running-" + ticketID, "true"); - } function pauseTimer() { @@ -65,10 +60,8 @@ let currentElapsed = getElapsedSeconds(); localStorage.setItem(getLocalStorageKey("pausedTime"), currentElapsed.toString()); localStorage.removeItem(getLocalStorageKey("startTime")); - isPaused = true; document.getElementById("startStopTimer").innerHTML = ""; localStorage.setItem("ticket-timer-running-" + ticketID, "false"); - } function clearTimeStorage() { @@ -99,9 +92,7 @@ } function handleInputFocus() { - if (!isPaused) { - pauseTimer(); - } + pauseTimer(); } function updateTimeFromInput() { @@ -110,7 +101,6 @@ const seconds = parseInt(document.getElementById("seconds").value, 10) || 0; elapsedSecs = (hours * 3600) + (minutes * 60) + seconds; - // Update local storage so the manually entered time is retained even if the page is reloaded. if (!timerInterval) { localStorage.setItem(getLocalStorageKey("pausedTime"), elapsedSecs.toString()); } else { @@ -120,7 +110,6 @@ } } - // Function to check status and pause timer function checkStatusAndPauseTimer() { var status = document.querySelector('select[name="status"]').value; if (status.includes("Pending") || status.includes("Close")) { @@ -128,6 +117,7 @@ } } + // Attach input listeners document.getElementById("hours").addEventListener('change', updateTimeFromInput); document.getElementById("minutes").addEventListener('change', updateTimeFromInput); document.getElementById("seconds").addEventListener('change', updateTimeFromInput); @@ -151,25 +141,33 @@ }); document.getElementById("ticket_add_reply").addEventListener('click', function() { - // Wait for other synchronous actions (if any) to complete before resetting the timer. - setTimeout(forceResetTimer, 100); // 100ms delay should suffice, but you can adjust as needed. + setTimeout(forceResetTimer, 100); }); document.getElementById("ticket_close").addEventListener('click', function() { - // Wait for other synchronous actions (if any) to complete before resetting the timer. - setTimeout(clearTimeStorage, 100); // 100ms delay should suffice, but you can adjust as needed. + setTimeout(clearTimeStorage, 100); }); + // Final initialization logic try { displayTime(); + + // If no timer state, respect ticketAutoStart if (!localStorage.getItem(getLocalStorageKey("startTime")) && !localStorage.getItem(getLocalStorageKey("pausedTime"))) { - startTimer(); - } else if (localStorage.getItem(getLocalStorageKey("startTime"))) { + if (ticketAutoStart === 1) { + startTimer(); + } else { + pauseTimer(); + } + } + // If timer already running, resume it + else if (localStorage.getItem(getLocalStorageKey("startTime"))) { startTimer(); } // Check and pause timer if status is pending checkStatusAndPauseTimer(); + } catch (error) { console.error("There was an issue initializing the timer:", error); } diff --git a/post/admin/admin_settings_ticket.php b/post/admin/admin_settings_ticket.php index bc75e518..918a703c 100644 --- a/post/admin/admin_settings_ticket.php +++ b/post/admin/admin_settings_ticket.php @@ -17,8 +17,9 @@ if (isset($_POST['edit_ticket_settings'])) { $config_ticket_default_view = intval($_POST['config_ticket_default_view']); $config_ticket_moving_columns = intval($_POST['config_ticket_moving_columns']); $config_ticket_ordering = intval($_POST['config_ticket_ordering']); + $config_ticket_timer_autostart = intval($_POST['config_ticket_timer_autostart']); - mysqli_query($mysqli,"UPDATE settings SET config_ticket_prefix = '$config_ticket_prefix', config_ticket_next_number = $config_ticket_next_number, config_ticket_email_parse = $config_ticket_email_parse, config_ticket_email_parse_unknown_senders = $config_ticket_email_parse_unknown_senders, config_ticket_autoclose_hours = $config_ticket_autoclose_hours, config_ticket_new_ticket_notification_email = '$config_ticket_new_ticket_notification_email', config_ticket_default_billable = $config_ticket_default_billable, config_ticket_default_view = $config_ticket_default_view, config_ticket_moving_columns = $config_ticket_moving_columns, config_ticket_ordering = $config_ticket_ordering WHERE company_id = 1"); + mysqli_query($mysqli,"UPDATE settings SET config_ticket_prefix = '$config_ticket_prefix', config_ticket_next_number = $config_ticket_next_number, config_ticket_email_parse = $config_ticket_email_parse, config_ticket_email_parse_unknown_senders = $config_ticket_email_parse_unknown_senders, config_ticket_autoclose_hours = $config_ticket_autoclose_hours, config_ticket_new_ticket_notification_email = '$config_ticket_new_ticket_notification_email', config_ticket_default_billable = $config_ticket_default_billable, config_ticket_default_view = $config_ticket_default_view, config_ticket_moving_columns = $config_ticket_moving_columns, config_ticket_ordering = $config_ticket_ordering, config_ticket_timer_autostart = $config_ticket_timer_autostart WHERE company_id = 1"); // Logging logAction("Settings", "Edit", "$session_name edited ticket settings"); diff --git a/ticket.php b/ticket.php index 55868d1a..40265423 100644 --- a/ticket.php +++ b/ticket.php @@ -1252,6 +1252,11 @@ require_once "includes/footer.php"; + + + From ec24ec60c626cf8be836ec5b5fd521de1f9581a9 Mon Sep 17 00:00:00 2001 From: johnnyq Date: Thu, 12 Jun 2025 13:49:11 -0400 Subject: [PATCH 23/63] Disable Ticket Auto Timer by default --- database_updates.php | 2 +- db.sql | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/database_updates.php b/database_updates.php index a3c6cf98..d14d0c5a 100644 --- a/database_updates.php +++ b/database_updates.php @@ -3465,7 +3465,7 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) { } if (CURRENT_DATABASE_VERSION == '2.1.4') { - mysqli_query($mysqli, "ALTER TABLE `settings` ADD `config_ticket_timer_autostart` TINYINT(1) NOT NULL DEFAULT '1' AFTER `config_ticket_default_billable`"); + mysqli_query($mysqli, "ALTER TABLE `settings` ADD `config_ticket_timer_autostart` TINYINT(1) NOT NULL DEFAULT '0' AFTER `config_ticket_default_billable`"); mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.1.5'"); } diff --git a/db.sql b/db.sql index 89f581eb..12549030 100644 --- a/db.sql +++ b/db.sql @@ -1812,7 +1812,7 @@ CREATE TABLE `settings` ( `config_ticket_autoclose_hours` int(5) NOT NULL DEFAULT 72, `config_ticket_new_ticket_notification_email` varchar(200) DEFAULT NULL, `config_ticket_default_billable` tinyint(1) NOT NULL DEFAULT 0, - `config_ticket_timer_autostart` tinyint(1) NOT NULL DEFAULT 1, + `config_ticket_timer_autostart` tinyint(1) NOT NULL DEFAULT 0, `config_enable_cron` tinyint(1) NOT NULL DEFAULT 0, `config_recurring_auto_send_invoice` tinyint(1) NOT NULL DEFAULT 1, `config_enable_alert_domain_expire` tinyint(1) NOT NULL DEFAULT 1, From 878d5444e1377c193a79d46f44cbcc6eff4c8b90 Mon Sep 17 00:00:00 2001 From: johnnyq Date: Thu, 12 Jun 2025 15:00:47 -0400 Subject: [PATCH 24/63] Use Gray Accent color when in client view and use configured theme for Global view, this will help differentiate the view that you are in --- includes/client_side_nav.php | 2 +- includes/header.php | 2 +- includes/top_nav.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/includes/client_side_nav.php b/includes/client_side_nav.php index 17d506e1..7899113d 100644 --- a/includes/client_side_nav.php +++ b/includes/client_side_nav.php @@ -1,5 +1,5 @@ -
+
+
+
+ +
+
+ +
+ +
+
+
+
+
+ +
+
+ +
+ +
+
+
+
+ = 2) { ?>
diff --git a/database_updates.php b/database_updates.php index d14d0c5a..12c38e49 100644 --- a/database_updates.php +++ b/database_updates.php @@ -3465,7 +3465,8 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) { } if (CURRENT_DATABASE_VERSION == '2.1.4') { - mysqli_query($mysqli, "ALTER TABLE `settings` ADD `config_ticket_timer_autostart` TINYINT(1) NOT NULL DEFAULT '0' AFTER `config_ticket_default_billable`"); + mysqli_query($mysqli, "ALTER TABLE `settings` ADD `config_ticket_timer_autostart` TINYINT(1) NOT NULL DEFAULT '0' AFTER `config_ticket_default_billable`"); + mysqli_query($mysqli, "ALTER TABLE `tickets` ADD `ticket_due_at` DATETIME DEFAULT NULL AFTER `ticket_updated_at`"); mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.1.5'"); } diff --git a/db.sql b/db.sql index 12549030..af9ff4db 100644 --- a/db.sql +++ b/db.sql @@ -2245,6 +2245,7 @@ CREATE TABLE `tickets` ( `ticket_url_key` varchar(200) DEFAULT NULL, `ticket_created_at` datetime NOT NULL DEFAULT current_timestamp(), `ticket_updated_at` datetime DEFAULT NULL ON UPDATE current_timestamp(), + `ticket_due_at` datetime DEFAULT NULL, `ticket_resolved_at` datetime DEFAULT NULL, `ticket_archived_at` datetime DEFAULT NULL, `ticket_first_response_at` datetime DEFAULT NULL, @@ -2501,4 +2502,4 @@ CREATE TABLE `vendors` ( /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; --- Dump completed on 2025-06-12 13:43:40 +-- Dump completed on 2025-06-12 17:12:13 diff --git a/modals/ticket_add_modal.php b/modals/ticket_add_modal.php index 2eb9a265..33322e15 100644 --- a/modals/ticket_add_modal.php +++ b/modals/ticket_add_modal.php @@ -172,31 +172,46 @@
-
- -
-
- -
- + + 1 - AND user_type = 1 - AND user_status = 1 - AND user_archived_at IS NULL - ORDER BY user_name ASC" - ); - while ($row = mysqli_fetch_array($sql)) { - $user_id = intval($row['user_id']); - $user_name = nullable_htmlentities($row['user_name']); ?> - - - + $sql = mysqli_query( + $mysqli, + "SELECT user_id, user_name FROM users + WHERE user_role_id > 1 + AND user_type = 1 + AND user_status = 1 + AND user_archived_at IS NULL + ORDER BY user_name ASC" + ); + while ($row = mysqli_fetch_array($sql)) { + $user_id = intval($row['user_id']); + $user_name = nullable_htmlentities($row['user_name']); ?> + + + +
+
+
+
+
+ +
+
+ +
+ +
+
diff --git a/post/user/ticket.php b/post/user/ticket.php index e3d36467..3d8a3bbc 100644 --- a/post/user/ticket.php +++ b/post/user/ticket.php @@ -30,6 +30,18 @@ if (isset($_POST['add_ticket'])) { $use_primary_contact = intval($_POST['use_primary_contact'] ?? 0); $ticket_template_id = intval($_POST['ticket_template_id']); $billable = intval($_POST['billable'] ?? 0); + // Validate/clean due field + $dueInput = $_POST['due'] ?? null; + if ($dueInput === null || trim($dueInput) === '') { + $due = 'NULL'; // prepare as SQL-safe string + } else { + $d = DateTime::createFromFormat('Y-m-d\TH:i', $dueInput); // for + if ($d !== false) { + $due = "'" . $d->format('Y-m-d H:i:s') . "'"; // wrap in quotes for SQL + } else { + $due = 'NULL'; // fallback if invalid + } + } // Add the primary contact as the ticket contact if "Use primary contact" is checked if ($use_primary_contact == 1) { @@ -53,7 +65,7 @@ if (isset($_POST['add_ticket'])) { mysqli_query($mysqli, "UPDATE settings SET config_ticket_next_number = $new_config_ticket_next_number WHERE company_id = 1"); - mysqli_query($mysqli, "INSERT INTO tickets SET ticket_prefix = '$config_ticket_prefix', ticket_number = $ticket_number, ticket_source = 'Agent', ticket_category = $category_id, ticket_subject = '$subject', ticket_details = '$details', ticket_priority = '$priority', ticket_billable = '$billable', ticket_status = '$ticket_status', ticket_vendor_ticket_number = '$vendor_ticket_number', ticket_vendor_id = $vendor_id, ticket_location_id = $location_id, ticket_asset_id = $asset_id, ticket_created_by = $session_user_id, ticket_assigned_to = $assigned_to, ticket_contact_id = $contact, ticket_url_key = '$url_key', ticket_client_id = $client_id, ticket_invoice_id = 0, ticket_project_id = $project_id"); + mysqli_query($mysqli, "INSERT INTO tickets SET ticket_prefix = '$config_ticket_prefix', ticket_number = $ticket_number, ticket_source = 'Agent', ticket_category = $category_id, ticket_subject = '$subject', ticket_details = '$details', ticket_priority = '$priority', ticket_billable = '$billable', ticket_status = '$ticket_status', ticket_vendor_ticket_number = '$vendor_ticket_number', ticket_vendor_id = $vendor_id, ticket_location_id = $location_id, ticket_asset_id = $asset_id, ticket_created_by = $session_user_id, ticket_assigned_to = $assigned_to, ticket_contact_id = $contact, ticket_url_key = '$url_key', ticket_due_at = $due, ticket_client_id = $client_id, ticket_invoice_id = 0, ticket_project_id = $project_id"); $ticket_id = mysqli_insert_id($mysqli); @@ -193,8 +205,20 @@ if (isset($_POST['edit_ticket'])) { $asset_id = intval($_POST['asset']); $location_id = intval($_POST['location']); $project_id = intval($_POST['project']); + // Validate/clean due field + $dueInput = $_POST['due'] ?? null; + if ($dueInput === null || trim($dueInput) === '') { + $due = 'NULL'; // prepare as SQL-safe string + } else { + $d = DateTime::createFromFormat('Y-m-d\TH:i', $dueInput); // for + if ($d !== false) { + $due = "'" . $d->format('Y-m-d H:i:s') . "'"; // wrap in quotes for SQL + } else { + $due = 'NULL'; // fallback if invalid + } + } - mysqli_query($mysqli, "UPDATE tickets SET ticket_category = $category_id, ticket_subject = '$ticket_subject', ticket_priority = '$ticket_priority', ticket_billable = $billable, ticket_details = '$details', ticket_vendor_ticket_number = '$vendor_ticket_number', ticket_contact_id = $contact_id, ticket_vendor_id = $vendor_id, ticket_location_id = $location_id, ticket_asset_id = $asset_id, ticket_project_id = $project_id WHERE ticket_id = $ticket_id"); + mysqli_query($mysqli, "UPDATE tickets SET ticket_category = $category_id, ticket_subject = '$ticket_subject', ticket_priority = '$ticket_priority', ticket_billable = $billable, ticket_details = '$details', ticket_due_at = $due, ticket_vendor_ticket_number = '$vendor_ticket_number', ticket_contact_id = $contact_id, ticket_vendor_id = $vendor_id, ticket_location_id = $location_id, ticket_asset_id = $asset_id, ticket_project_id = $project_id WHERE ticket_id = $ticket_id"); // Add Additional Assets if (isset($_POST['additional_assets'])) { From 4c74351d21a5bfc78349a16504ff36ef08da9020 Mon Sep 17 00:00:00 2001 From: johnnyq Date: Fri, 13 Jun 2025 15:51:09 -0400 Subject: [PATCH 26/63] Added Company Tax ID Field and Option to Show Tax ID on Invoices --- admin_settings_company.php | 11 +++++++++++ admin_settings_invoice.php | 15 ++++++++++++--- database_updates.php | 3 +++ db.sql | 4 +++- guest/guest_view_invoice.php | 11 ++++++++++- guest/includes/guest_header.php | 1 + includes/get_settings.php | 1 + invoice.php | 11 ++++++++++- post/admin/admin_settings_company.php | 3 ++- post/admin/admin_settings_invoice.php | 3 ++- setup.php | 13 ++++++++++++- 11 files changed, 67 insertions(+), 9 deletions(-) diff --git a/admin_settings_company.php b/admin_settings_company.php index 17ba0239..6d5a548f 100644 --- a/admin_settings_company.php +++ b/admin_settings_company.php @@ -19,6 +19,7 @@ $company_website = nullable_htmlentities($row['company_website']); $company_logo = nullable_htmlentities($row['company_logo']); $company_locale = nullable_htmlentities($row['company_locale']); $company_currency = nullable_htmlentities($row['company_currency']); +$company_tax_id = nullable_htmlentities($row['company_tax_id']); $company_initials = nullable_htmlentities(initials($company_name)); @@ -146,6 +147,16 @@ $company_initials = nullable_htmlentities(initials($company_name));
+
+ +
+
+ +
+ +
+
+
diff --git a/admin_settings_invoice.php b/admin_settings_invoice.php index 9633c3eb..cb432452 100644 --- a/admin_settings_invoice.php +++ b/admin_settings_invoice.php @@ -37,12 +37,21 @@ require_once "includes/inc_all_admin.php";
+
Show Tax ID On Invoices
+ +
+
+ value="1" id="customSwitch1"> + +
+
+
Invoice Late Fees
-
+
- value="1" id="customSwitch1"> - + value="1" id="customSwitch2"> +
diff --git a/database_updates.php b/database_updates.php index 12c38e49..467f4623 100644 --- a/database_updates.php +++ b/database_updates.php @@ -3467,6 +3467,9 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) { if (CURRENT_DATABASE_VERSION == '2.1.4') { mysqli_query($mysqli, "ALTER TABLE `settings` ADD `config_ticket_timer_autostart` TINYINT(1) NOT NULL DEFAULT '0' AFTER `config_ticket_default_billable`"); mysqli_query($mysqli, "ALTER TABLE `tickets` ADD `ticket_due_at` DATETIME DEFAULT NULL AFTER `ticket_updated_at`"); + mysqli_query($mysqli, "ALTER TABLE `companies` ADD `company_tax_id` VARCHAR(200) DEFAULT NULL AFTER `company_currency`"); + mysqli_query($mysqli, "ALTER TABLE `settings` ADD `config_invoice_show_tax_id` TINYINT(1) NOT NULL DEFAULT '0' AFTER `config_invoice_paid_notification_email`"); + mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.1.5'"); } diff --git a/db.sql b/db.sql index af9ff4db..6866f217 100644 --- a/db.sql +++ b/db.sql @@ -555,6 +555,7 @@ CREATE TABLE `companies` ( `company_logo` varchar(250) DEFAULT NULL, `company_locale` varchar(200) DEFAULT NULL, `company_currency` varchar(200) NOT NULL, + `company_tax_id` varchar(200) DEFAULT NULL, `company_created_at` datetime NOT NULL DEFAULT current_timestamp(), `company_updated_at` datetime DEFAULT NULL ON UPDATE current_timestamp(), PRIMARY KEY (`company_id`) @@ -1794,6 +1795,7 @@ CREATE TABLE `settings` ( `config_invoice_late_fee_enable` tinyint(1) NOT NULL DEFAULT 0, `config_invoice_late_fee_percent` decimal(5,2) NOT NULL DEFAULT 0.00, `config_invoice_paid_notification_email` varchar(200) DEFAULT NULL, + `config_invoice_show_tax_id` tinyint(1) NOT NULL DEFAULT 0, `config_recurring_invoice_prefix` varchar(200) DEFAULT NULL, `config_recurring_invoice_next_number` int(11) NOT NULL DEFAULT 1, `config_quote_prefix` varchar(200) DEFAULT NULL, @@ -2502,4 +2504,4 @@ CREATE TABLE `vendors` ( /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; --- Dump completed on 2025-06-12 17:12:13 +-- Dump completed on 2025-06-13 15:50:44 diff --git a/guest/guest_view_invoice.php b/guest/guest_view_invoice.php index b6239c7a..ed47a0c1 100644 --- a/guest/guest_view_invoice.php +++ b/guest/guest_view_invoice.php @@ -77,6 +77,12 @@ $company_phone_country_code = nullable_htmlentities($row['company_phone_country_ $company_phone = nullable_htmlentities(formatPhoneNumber($row['company_phone'], $company_phone_country_code)); $company_email = nullable_htmlentities($row['company_email']); $company_website = nullable_htmlentities($row['company_website']); +$company_tax_id = nullable_htmlentities($row['company_tax_id']); +if ($config_invoice_show_tax_id && !empty($company_tax_id)) { + $company_tax_id_display = "Tax ID: $company_tax_id"; +} else { + $company_tax_id_display = ""; +} $company_logo = nullable_htmlentities($row['company_logo']); if (!empty($company_logo)) { $company_logo_base64 = base64_encode(file_get_contents("../uploads/settings/$company_logo")); @@ -203,6 +209,9 @@ if ($balance > 0) {
  • + +
  • +
    @@ -411,7 +420,7 @@ if ($balance > 0) { { columns: [ { - text: , + text: , style: 'invoiceBillingAddress' }, { diff --git a/guest/includes/guest_header.php b/guest/includes/guest_header.php index 615a6766..2dc20aa1 100644 --- a/guest/includes/guest_header.php +++ b/guest/includes/guest_header.php @@ -2,6 +2,7 @@ require_once "../config.php"; require_once "../functions.php"; +require_once "../includes/get_settings.php"; session_start(); diff --git a/includes/get_settings.php b/includes/get_settings.php index 65a28aee..e4b31087 100644 --- a/includes/get_settings.php +++ b/includes/get_settings.php @@ -47,6 +47,7 @@ $config_invoice_from_email = $row['config_invoice_from_email']; $config_invoice_late_fee_enable = intval($row['config_invoice_late_fee_enable']); $config_invoice_late_fee_percent = floatval($row['config_invoice_late_fee_percent']); $config_invoice_paid_notification_email = $row['config_invoice_paid_notification_email']; +$config_invoice_show_tax_id = intval($row['config_invoice_show_tax_id']); // Recurring Invoices $config_recurring_invoice_prefix = $row['config_recurring_invoice_prefix']; diff --git a/invoice.php b/invoice.php index 49d58455..88435116 100644 --- a/invoice.php +++ b/invoice.php @@ -89,6 +89,12 @@ if (isset($_GET['invoice_id'])) { $company_phone = nullable_htmlentities(formatPhoneNumber($row['company_phone'], $company_phone_country_code)); $company_email = nullable_htmlentities($row['company_email']); $company_website = nullable_htmlentities($row['company_website']); + $company_tax_id = nullable_htmlentities($row['company_tax_id']); + if ($config_invoice_show_tax_id && !empty($company_tax_id)) { + $company_tax_id_display = "Tax ID: $company_tax_id"; + } else { + $company_tax_id_display = ""; + } $company_logo = nullable_htmlentities($row['company_logo']); if (!empty($company_logo)) { $company_logo_base64 = base64_encode(file_get_contents("uploads/settings/$company_logo")); @@ -328,6 +334,9 @@ if (isset($_GET['invoice_id'])) {
  • + +
  • +
    @@ -796,7 +805,7 @@ require_once "includes/footer.php"; { columns: [ { - text: , + text: , style: 'invoiceBillingAddress' }, { diff --git a/post/admin/admin_settings_company.php b/post/admin/admin_settings_company.php index 3df3c4a1..4213540f 100644 --- a/post/admin/admin_settings_company.php +++ b/post/admin/admin_settings_company.php @@ -16,6 +16,7 @@ if (isset($_POST['edit_company'])) { $phone = preg_replace("/[^0-9]/", '',$_POST['phone']); $email = sanitizeInput($_POST['email']); $website = sanitizeInput($_POST['website']); + $tax_id = sanitizeInput($_POST['tax_id']); $sql = mysqli_query($mysqli,"SELECT company_logo FROM companies WHERE company_id = 1"); $row = mysqli_fetch_array($sql); @@ -41,7 +42,7 @@ if (isset($_POST['edit_company'])) { } } - mysqli_query($mysqli,"UPDATE companies SET company_name = '$name', company_address = '$address', company_city = '$city', company_state = '$state', company_zip = '$zip', company_country = '$country', company_phone_country_code = '$phone_country_code', company_phone = '$phone', company_email = '$email', company_website = '$website' WHERE company_id = 1"); + mysqli_query($mysqli,"UPDATE companies SET company_name = '$name', company_address = '$address', company_city = '$city', company_state = '$state', company_zip = '$zip', company_country = '$country', company_phone_country_code = '$phone_country_code', company_phone = '$phone', company_email = '$email', company_website = '$website', company_tax_id = '$tax_id' WHERE company_id = 1"); // Logging logAction("Settings", "Edit", "$session_name edited company details"); diff --git a/post/admin/admin_settings_invoice.php b/post/admin/admin_settings_invoice.php index 62cb3088..08262d02 100644 --- a/post/admin/admin_settings_invoice.php +++ b/post/admin/admin_settings_invoice.php @@ -9,6 +9,7 @@ if (isset($_POST['edit_invoice_settings'])) { $config_invoice_prefix = sanitizeInput($_POST['config_invoice_prefix']); $config_invoice_next_number = intval($_POST['config_invoice_next_number']); $config_invoice_footer = sanitizeInput($_POST['config_invoice_footer']); + $config_invoice_show_tax_id = intval($_POST['config_invoice_show_tax_id'] ?? 0); $config_invoice_late_fee_enable = intval($_POST['config_invoice_late_fee_enable'] ?? 0); $config_invoice_late_fee_percent = floatval($_POST['config_invoice_late_fee_percent']); $config_recurring_invoice_prefix = sanitizeInput($_POST['config_recurring_invoice_prefix']); @@ -18,7 +19,7 @@ if (isset($_POST['edit_invoice_settings'])) { $config_invoice_paid_notification_email = sanitizeInput($_POST['config_invoice_paid_notification_email']); } - mysqli_query($mysqli,"UPDATE settings SET config_invoice_prefix = '$config_invoice_prefix', config_invoice_next_number = $config_invoice_next_number, config_invoice_footer = '$config_invoice_footer', config_invoice_late_fee_enable = $config_invoice_late_fee_enable, config_invoice_late_fee_percent = $config_invoice_late_fee_percent, config_invoice_paid_notification_email = '$config_invoice_paid_notification_email', config_recurring_invoice_prefix = '$config_recurring_invoice_prefix', config_recurring_invoice_next_number = $config_recurring_invoice_next_number WHERE company_id = 1"); + mysqli_query($mysqli,"UPDATE settings SET config_invoice_prefix = '$config_invoice_prefix', config_invoice_next_number = $config_invoice_next_number, config_invoice_footer = '$config_invoice_footer', config_invoice_show_tax_id = $config_invoice_show_tax_id, config_invoice_late_fee_enable = $config_invoice_late_fee_enable, config_invoice_late_fee_percent = $config_invoice_late_fee_percent, config_invoice_paid_notification_email = '$config_invoice_paid_notification_email', config_recurring_invoice_prefix = '$config_recurring_invoice_prefix', config_recurring_invoice_next_number = $config_recurring_invoice_next_number WHERE company_id = 1"); // Logging logAction("Settings", "Edit", "$session_name edited invoice settings"); diff --git a/setup.php b/setup.php index db7de36e..fc953e77 100644 --- a/setup.php +++ b/setup.php @@ -323,8 +323,9 @@ if (isset($_POST['add_company_settings'])) { $phone = preg_replace("/[^0-9]/", '',$_POST['phone']); $email = sanitizeInput($_POST['email']); $website = sanitizeInput($_POST['website']); + $tax_id = sanitizeInput($_POST['tax_id']); - mysqli_query($mysqli,"INSERT INTO companies SET company_name = '$name', company_address = '$address', company_city = '$city', company_state = '$state', company_zip = '$zip', company_country = '$country', company_phone = '$phone', company_email = '$email', company_website = '$website', company_locale = '$locale', company_currency = '$currency_code'"); + mysqli_query($mysqli,"INSERT INTO companies SET company_name = '$name', company_address = '$address', company_city = '$city', company_state = '$state', company_zip = '$zip', company_country = '$country', company_phone = '$phone', company_email = '$email', company_website = '$website', company_tax_id = '$tax_id'"); //Check to see if a file is attached if ($_FILES['file']['tmp_name'] != '') { @@ -1262,6 +1263,16 @@ if (isset($_POST['add_telemetry'])) {
    +
    + +
    +
    + +
    + +
    +
    +