From 55a31865d075d3b564d2c9b2163ba3619ba8f91b Mon Sep 17 00:00:00 2001 From: johnnyq Date: Fri, 30 May 2025 16:15:12 -0400 Subject: [PATCH 01/67] Updated Client Overview Entities to not show Archived client's Entities even though the entity may not be archived, also added Archive Searching to network and certificates also added unarchive capabilities to them as well --- assets.php | 20 ++++++++++++++++++-- certificates.php | 36 +++++++++++++++++++++++++++++++++--- contacts.php | 18 +++++++++++++++++- credentials.php | 20 +++++++++++++++++++- locations.php | 18 +++++++++++++++++- networks.php | 35 ++++++++++++++++++++++++++++++++--- post/user/certificate.php | 23 +++++++++++++++++++++++ post/user/network.php | 23 +++++++++++++++++++++++ software.php | 18 +++++++++++++++++- 9 files changed, 199 insertions(+), 12 deletions(-) diff --git a/assets.php b/assets.php index f6d0f1cb..bdbb6112 100644 --- a/assets.php +++ b/assets.php @@ -9,10 +9,26 @@ if (isset($_GET['client_id'])) { require_once "includes/inc_all_client.php"; $client_query = "AND asset_client_id = $client_id"; $client_url = "client_id=$client_id&"; + // Overide Filter Header Archived + if (isset($_GET['archived']) && $_GET['archived'] == 1) { + $archived = 1; + $archive_query = "asset_archived_at IS NOT NULL"; + } else { + $archived = 0; + $archive_query = "asset_archived_at IS NULL"; + } } else { require_once "includes/inc_client_overview_all.php"; $client_query = ''; $client_url = ''; + // Overide Filter Header Archived + if (isset($_GET['archived']) && $_GET['archived'] == 1) { + $archived = 1; + $archive_query = "(client_archived_at IS NOT NULL OR asset_archived_at IS NOT NULL)"; + } else { + $archived = 0; + $archive_query = "(client_archived_at IS NULL AND asset_archived_at IS NULL)"; + } } // Perms @@ -71,7 +87,7 @@ $row = mysqli_fetch_assoc(mysqli_query($mysqli, " LEFT JOIN contacts ON asset_contact_id = contact_id LEFT JOIN locations ON asset_location_id = location_id LEFT JOIN asset_interfaces ON interface_asset_id = asset_id AND interface_primary = 1 - WHERE asset_$archive_query + WHERE $archive_query $access_permission_query $client_query ) AS filtered_assets; @@ -105,7 +121,7 @@ $sql = mysqli_query( LEFT JOIN contacts ON asset_contact_id = contact_id LEFT JOIN locations ON asset_location_id = location_id LEFT JOIN asset_interfaces ON interface_asset_id = asset_id AND interface_primary = 1 - WHERE asset_$archive_query + WHERE $archive_query AND (asset_name LIKE '%$q%' OR asset_description LIKE '%$q%' OR asset_type LIKE '%$q%' OR interface_ip LIKE '%$q%' OR interface_ipv6 LIKE '%$q%' OR asset_make LIKE '%$q%' OR asset_model LIKE '%$q%' OR asset_serial LIKE '%$q%' OR asset_os LIKE '%$q%' OR contact_name LIKE '%$q%' OR location_name LIKE '%$q%' OR client_name LIKE '%$q%') AND ($type_query) $access_permission_query diff --git a/certificates.php b/certificates.php index 7e7bfc04..97d80525 100644 --- a/certificates.php +++ b/certificates.php @@ -9,10 +9,26 @@ if (isset($_GET['client_id'])) { require_once "includes/inc_all_client.php"; $client_query = "AND certificate_client_id = $client_id"; $client_url = "client_id=$client_id&"; + // Overide Filter Header Archived + if (isset($_GET['archived']) && $_GET['archived'] == 1) { + $archived = 1; + $archive_query = "certificate_archived_at IS NOT NULL"; + } else { + $archived = 0; + $archive_query = "certificate_archived_at IS NULL"; + } } else { require_once "includes/inc_client_overview_all.php"; $client_query = ''; $client_url = ''; + // Overide Filter Header Archived + if (isset($_GET['archived']) && $_GET['archived'] == 1) { + $archived = 1; + $archive_query = "(client_archived_at IS NOT NULL OR certificate_archived_at IS NOT NULL)"; + } else { + $archived = 0; + $archive_query = "(client_archived_at IS NULL AND certificate_archived_at IS NULL)"; + } } // Perms @@ -32,7 +48,7 @@ if (!$client_url) { $sql = mysqli_query($mysqli, "SELECT SQL_CALC_FOUND_ROWS * FROM certificates LEFT JOIN clients ON client_id = certificate_client_id - WHERE certificate_archived_at IS NULL + WHERE $archive_query AND (certificate_name LIKE '%$q%' OR certificate_domain LIKE '%$q%' OR certificate_issued_by LIKE '%$q%' OR client_name LIKE '%$q%') $access_permission_query $client_query @@ -65,6 +81,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); +
@@ -109,6 +126,10 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
+ "> + Archived +
diff --git a/contacts.php b/contacts.php index 9da09fe4..65fbda07 100644 --- a/contacts.php +++ b/contacts.php @@ -9,10 +9,26 @@ if (isset($_GET['client_id'])) { require_once "includes/inc_all_client.php"; $client_query = "AND contact_client_id = $client_id"; $client_url = "client_id=$client_id&"; + // Overide Filter Header Archived + if (isset($_GET['archived']) && $_GET['archived'] == 1) { + $archived = 1; + $archive_query = "contact_archived_at IS NOT NULL"; + } else { + $archived = 0; + $archive_query = "contact_archived_at IS NULL"; + } } else { require_once "includes/inc_client_overview_all.php"; $client_query = ''; $client_url = ''; + // Overide Filter Header Archived + if (isset($_GET['archived']) && $_GET['archived'] == 1) { + $archived = 1; + $archive_query = "(client_archived_at IS NOT NULL OR contact_archived_at IS NOT NULL)"; + } else { + $archived = 0; + $archive_query = "(client_archived_at IS NULL AND contact_archived_at IS NULL)"; + } } // Tags Filter @@ -55,7 +71,7 @@ $sql = mysqli_query($mysqli, "SELECT SQL_CALC_FOUND_ROWS contacts.*, clients.*, LEFT JOIN users ON user_id = contact_user_id LEFT JOIN contact_tags ON contact_tags.contact_id = contacts.contact_id LEFT JOIN tags ON tags.tag_id = contact_tags.tag_id - WHERE contact_$archive_query + WHERE $archive_query $tag_query AND (contact_name LIKE '%$q%' OR contact_title LIKE '%$q%' OR location_name LIKE '%$q%' OR contact_email LIKE '%$q%' OR contact_department LIKE '%$q%' OR contact_phone LIKE '%$phone_query%' OR contact_extension LIKE '%$q%' OR contact_mobile LIKE '%$phone_query%' OR tag_name LIKE '%$q%' OR client_name LIKE '%$q%') $access_permission_query diff --git a/credentials.php b/credentials.php index ebad77b1..5a88e058 100644 --- a/credentials.php +++ b/credentials.php @@ -9,12 +9,30 @@ if (isset($_GET['client_id'])) { require_once "includes/inc_all_client.php"; $client_query = "AND credential_client_id = $client_id"; $client_url = "client_id=$client_id&"; + // Overide Filter Header Archived + if (isset($_GET['archived']) && $_GET['archived'] == 1) { + $archived = 1; + $archive_query = "c.credential_archived_at IS NOT NULL"; + } else { + $archived = 0; + $archive_query = "c.credential_archived_at IS NULL"; + } + // Log when users load the Credentials page logAction("Credential", "View", "$session_name viewed the Credentials page for client", $client_id); + } else { require_once "includes/inc_client_overview_all.php"; $client_query = ''; $client_url = ''; + // Overide Filter Header Archived + if (isset($_GET['archived']) && $_GET['archived'] == 1) { + $archived = 1; + $archive_query = "(client_archived_at IS NOT NULL OR c.credential_archived_at IS NOT NULL)"; + } else { + $archived = 0; + $archive_query = "(client_archived_at IS NULL AND c.credential_archived_at IS NULL)"; + } // Log when users load the Credentials page logAction("Credential", "View", "$session_name viewed the All Credentials page"); } @@ -68,7 +86,7 @@ $sql = mysqli_query( LEFT JOIN contacts ON contact_id = credential_contact_id LEFT JOIN assets ON asset_id = credential_asset_id $location_query_innerjoin - WHERE c.credential_$archive_query + WHERE $archive_query $tag_query AND (c.credential_name LIKE '%$q%' OR c.credential_description LIKE '%$q%' OR c.credential_uri LIKE '%$q%' OR tag_name LIKE '%$q%' OR client_name LIKE '%$q%') $location_query diff --git a/locations.php b/locations.php index dbdf2dc3..165245d2 100644 --- a/locations.php +++ b/locations.php @@ -9,6 +9,14 @@ if (isset($_GET['client_id'])) { require_once "includes/inc_all_client.php"; $client_query = "AND location_client_id = $client_id"; $client_url = "client_id=$client_id&"; + // Overide Filter Header Archived + if (isset($_GET['archived']) && $_GET['archived'] == 1) { + $archived = 1; + $archive_query = "location_archived_at IS NOT NULL"; + } else { + $archived = 0; + $archive_query = "location_archived_at IS NULL"; + } } else { require_once "includes/inc_client_overview_all.php"; $client_query = ''; @@ -25,6 +33,14 @@ if (!$client_url) { $client_query = ''; $client = ''; } + // Overide Filter Header Archived + if (isset($_GET['archived']) && $_GET['archived'] == 1) { + $archived = 1; + $archive_query = "(client_archived_at IS NOT NULL OR location_archived_at IS NOT NULL)"; + } else { + $archived = 0; + $archive_query = "(client_archived_at IS NULL AND location_archived_at IS NULL)"; + } } // Tags Filter @@ -45,7 +61,7 @@ $sql = mysqli_query( LEFT JOIN clients ON client_id = location_client_id LEFT JOIN location_tags ON location_tags.location_id = locations.location_id LEFT JOIN tags ON tags.tag_id = location_tags.tag_id - WHERE location_$archive_query + WHERE $archive_query $tag_query AND (location_name LIKE '%$q%' OR location_description LIKE '%$q%' OR location_address LIKE '%$q%' OR location_city LIKE '%$q%' OR location_state LIKE '%$q%' OR location_zip LIKE '%$q%' OR location_country LIKE '%$q%' OR location_phone LIKE '%$phone_query%' OR tag_name LIKE '%$q%' OR client_name LIKE '%$q%') $access_permission_query diff --git a/networks.php b/networks.php index 416e3f3b..96437c38 100644 --- a/networks.php +++ b/networks.php @@ -9,10 +9,26 @@ if (isset($_GET['client_id'])) { require_once "includes/inc_all_client.php"; $client_query = "AND network_client_id = $client_id"; $client_url = "client_id=$client_id&"; + // Overide Filter Header Archived + if (isset($_GET['archived']) && $_GET['archived'] == 1) { + $archived = 1; + $archive_query = "network_archived_at IS NOT NULL"; + } else { + $archived = 0; + $archive_query = "network_archived_at IS NULL"; + } } else { require_once "includes/inc_client_overview_all.php"; $client_query = ''; $client_url = ''; + // Overide Filter Header Archived + if (isset($_GET['archived']) && $_GET['archived'] == 1) { + $archived = 1; + $archive_query = "(client_archived_at IS NOT NULL OR network_archived_at IS NOT NULL)"; + } else { + $archived = 0; + $archive_query = "(client_archived_at IS NULL AND network_archived_at IS NULL)"; + } } // Perms @@ -45,7 +61,7 @@ $sql = mysqli_query( "SELECT SQL_CALC_FOUND_ROWS * FROM networks LEFT JOIN clients ON client_id = network_client_id LEFT JOIN locations ON location_id = network_location_id - WHERE network_$archive_query + WHERE $archive_query AND (network_name LIKE '%$q%' OR network_description LIKE '%$q%' OR network_vlan LIKE '%$q%' OR network LIKE '%$q%' OR network_gateway LIKE '%$q%' OR network_subnet LIKE '%$q%' OR network_primary_dns LIKE '%$q%' OR network_secondary_dns LIKE '%$q%' OR network_dhcp_range LIKE '%$q%' OR location_name LIKE '%$q%' OR client_name LIKE '%$q%') $access_permission_query $location_query @@ -80,6 +96,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); +
@@ -150,6 +167,10 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
+ "> + Archived +
diff --git a/post/user/certificate.php b/post/user/certificate.php index f2960904..22292c1b 100644 --- a/post/user/certificate.php +++ b/post/user/certificate.php @@ -132,6 +132,29 @@ if (isset($_GET['archive_certificate'])) { } +if (isset($_GET['unarchive_certificate'])) { + + enforceUserPermission('module_support', 2); + + $certificate_id = intval($_GET['unarchive_certificate']); + + // Get Certificate Name and Client ID for logging and alert message + $sql = mysqli_query($mysqli,"SELECT certificate_name, certificate_client_id FROM certificates WHERE certificate_id = $certificate_id"); + $row = mysqli_fetch_array($sql); + $certificate_name = sanitizeInput($row['certificate_name']); + $client_id = intval($row['certificate_client_id']); + + mysqli_query($mysqli,"UPDATE certificates SET certificate_archived_at = NULL WHERE certificate_id = $certificate_id"); + + // logging + logAction("Certificate", "Unarchive", "$session_name restored certificate $certificate_name", $client_id, $certificate_id); + + $_SESSION['alert_message'] = "Certificate $certificate_name restored"; + + header("Location: " . $_SERVER["HTTP_REFERER"]); + +} + if (isset($_GET['delete_certificate'])) { enforceUserPermission('module_support', 3); diff --git a/post/user/network.php b/post/user/network.php index 661753a3..b4ba0c19 100644 --- a/post/user/network.php +++ b/post/user/network.php @@ -67,6 +67,29 @@ if (isset($_GET['archive_network'])) { } +if (isset($_GET['unarchive_network'])) { + + enforceUserPermission('module_support', 2); + + $network_id = intval($_GET['unarchive_network']); + + // Get Network Name and Client ID for logging and alert message + $sql = mysqli_query($mysqli,"SELECT network_name, network_client_id FROM networks WHERE network_id = $network_id"); + $row = mysqli_fetch_array($sql); + $network_name = sanitizeInput($row['network_name']); + $client_id = intval($row['network_client_id']); + + mysqli_query($mysqli,"UPDATE networks SET network_archived_at = NULL WHERE network_id = $network_id"); + + // logging + logAction("Network", "Unarchive", "$session_name restored contact $contact_name", $client_id, $network_id); + + $_SESSION['alert_message'] = "Network $network_name restored"; + + header("Location: " . $_SERVER["HTTP_REFERER"]); + +} + if (isset($_GET['delete_network'])) { enforceUserPermission('module_support', 3); diff --git a/software.php b/software.php index 56d6d57c..653d99e7 100644 --- a/software.php +++ b/software.php @@ -9,10 +9,26 @@ if (isset($_GET['client_id'])) { require_once "includes/inc_all_client.php"; $client_query = "AND software_client_id = $client_id"; $client_url = "client_id=$client_id&"; + // Overide Filter Header Archived + if (isset($_GET['archived']) && $_GET['archived'] == 1) { + $archived = 1; + $archive_query = "software_archived_at IS NOT NULL"; + } else { + $archived = 0; + $archive_query = "software_archived_at IS NULL"; + } } else { require_once "includes/inc_client_overview_all.php"; $client_query = ''; $client_url = ''; + // Overide Filter Header Archived + if (isset($_GET['archived']) && $_GET['archived'] == 1) { + $archived = 1; + $archive_query = "(client_archived_at IS NOT NULL OR software_archived_at IS NOT NULL)"; + } else { + $archived = 0; + $archive_query = "(client_archived_at IS NULL AND software_archived_at IS NULL)"; + } } // Perms @@ -36,7 +52,7 @@ $sql = mysqli_query( LEFT JOIN clients ON client_id = software_client_id LEFT JOIN vendors ON vendor_id = software_vendor_id WHERE software_template = 0 - AND software_$archive_query + AND $archive_query AND (software_name LIKE '%$q%' OR software_type LIKE '%$q%' OR software_key LIKE '%$q%' OR client_name LIKE '%$q%') $access_permission_query $client_query From 73da203dbb1c3021fcfcf34b6a8d10592b397766 Mon Sep 17 00:00:00 2001 From: johnnyq Date: Sat, 31 May 2025 17:47:08 -0400 Subject: [PATCH 02/67] Added Cards for Account Balance, Recurring Monthly for Billing Contacts and Domains Expiring for Technical Contacts to Client Portal --- client/index.php | 228 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 228 insertions(+) diff --git a/client/index.php b/client/index.php index 856a36bf..219f8ed2 100644 --- a/client/index.php +++ b/client/index.php @@ -8,7 +8,235 @@ header("Content-Security-Policy: default-src 'self'"); require_once "includes/inc_all.php"; + +// Billing Card Queries + //Add up all the payments for the invoice and get the total amount paid to the invoice +$sql_invoice_amounts = mysqli_query($mysqli, "SELECT SUM(invoice_amount) AS invoice_amounts FROM invoices WHERE invoice_client_id = $session_client_id AND invoice_status NOT LIKE 'Draft' AND invoice_status NOT LIKE 'Cancelled'"); +$row = mysqli_fetch_array($sql_invoice_amounts); + +$invoice_amounts = floatval($row['invoice_amounts']); + +$sql_amount_paid = mysqli_query($mysqli, "SELECT SUM(payment_amount) AS amount_paid FROM payments, invoices WHERE payment_invoice_id = invoice_id AND invoice_client_id = $session_client_id"); +$row = mysqli_fetch_array($sql_amount_paid); + +$amount_paid = floatval($row['amount_paid']); + +$balance = $invoice_amounts - $amount_paid; + +//Get Monthly Recurring Total +$sql_recurring_monthly_total = mysqli_query($mysqli, "SELECT SUM(recurring_invoice_amount) AS recurring_monthly_total FROM recurring_invoices WHERE recurring_invoice_status = 1 AND recurring_invoice_frequency = 'month' AND recurring_invoice_client_id = $session_client_id"); +$row = mysqli_fetch_array($sql_recurring_monthly_total); + +$recurring_monthly_total = floatval($row['recurring_monthly_total']); + +//Get Yearly Recurring Total +$sql_recurring_yearly_total = mysqli_query($mysqli, "SELECT SUM(recurring_invoice_amount) AS recurring_yearly_total FROM recurring_invoices WHERE recurring_invoice_status = 1 AND recurring_invoice_frequency = 'year' AND recurring_invoice_client_id = $session_client_id"); +$row = mysqli_fetch_array($sql_recurring_yearly_total); + +$recurring_yearly_total = floatval($row['recurring_yearly_total']) / 12; + +$recurring_monthly = $recurring_monthly_total + $recurring_yearly_total; + +// Technical Card Queries +// 8 - 45 Day Warning + +// Get Domains Expiring +$sql_domains_expiring = mysqli_query( + $mysqli, + "SELECT * FROM domains + WHERE domain_client_id = $session_client_id + AND domain_expire IS NOT NULL + AND domain_archived_at IS NULL + AND domain_expire > CURRENT_DATE + AND domain_expire < CURRENT_DATE + INTERVAL 45 DAY + ORDER BY domain_expire ASC" +); + +// Get Certificates Expiring +$sql_certificates_expiring = mysqli_query( + $mysqli, + "SELECT * FROM certificates + WHERE certificate_client_id = $session_client_id + AND certificate_expire IS NOT NULL + AND certificate_archived_at IS NULL + AND certificate_expire > CURRENT_DATE + AND certificate_expire < CURRENT_DATE + INTERVAL 45 DAY + ORDER BY certificate_expire ASC" +); + +// Get Licenses Expiring +$sql_licenses_expiring = mysqli_query( + $mysqli, + "SELECT * FROM software + WHERE software_client_id = $session_client_id + AND software_expire IS NOT NULL + AND software_archived_at IS NULL + AND software_expire > CURRENT_DATE + AND software_expire < CURRENT_DATE + INTERVAL 45 DAY + ORDER BY software_expire ASC" +); + +// Get Asset Warranties Expiring +$sql_asset_warranties_expiring = mysqli_query( + $mysqli, + "SELECT * FROM assets + WHERE asset_client_id = $session_client_id + AND asset_warranty_expire IS NOT NULL + AND asset_archived_at IS NULL + AND asset_warranty_expire > CURRENT_DATE + AND asset_warranty_expire < CURRENT_DATE + INTERVAL 45 DAY + ORDER BY asset_warranty_expire ASC" +); + +// Get Assets Retiring 7 Year +$sql_asset_retire = mysqli_query( + $mysqli, + "SELECT * FROM assets + WHERE asset_client_id = $session_client_id + AND asset_install_date IS NOT NULL + AND asset_archived_at IS NULL + AND asset_install_date + INTERVAL 7 YEAR > CURRENT_DATE + AND asset_install_date + INTERVAL 7 YEAR <= CURRENT_DATE + INTERVAL 45 DAY + ORDER BY asset_install_date ASC" +); + +/* + * EXPIRED ITEMS + */ + +// Get Domains Expired +$sql_domains_expired = mysqli_query( + $mysqli, + "SELECT * FROM domains + WHERE domain_client_id = $session_client_id + AND domain_expire IS NOT NULL + AND domain_archived_at IS NULL + AND domain_expire < CURRENT_DATE + ORDER BY domain_expire ASC" +); + +// Get Certificates Expired +$sql_certificates_expired = mysqli_query( + $mysqli, + "SELECT * FROM certificates + WHERE certificate_client_id = $session_client_id + AND certificate_expire IS NOT NULL + AND certificate_archived_at IS NULL + AND certificate_expire < CURRENT_DATE + ORDER BY certificate_expire ASC" +); + +// Get Licenses Expired +$sql_licenses_expired = mysqli_query( + $mysqli, + "SELECT * FROM software + WHERE software_client_id = $session_client_id + AND software_expire IS NOT NULL + AND software_archived_at IS NULL + AND software_expire < CURRENT_DATE + ORDER BY software_expire ASC" +); + +// Get Asset Warranties Expired +$sql_asset_warranties_expired = mysqli_query( + $mysqli, + "SELECT * FROM assets + WHERE asset_client_id = $session_client_id + AND asset_warranty_expire IS NOT NULL + AND asset_archived_at IS NULL + AND asset_warranty_expire < CURRENT_DATE + ORDER BY asset_warranty_expire ASC" +); + +// Get Retired Assets +$sql_asset_retired = mysqli_query( + $mysqli, + "SELECT * FROM assets + WHERE asset_client_id = $session_client_id + AND asset_install_date IS NOT NULL + AND asset_archived_at IS NULL + AND asset_install_date + INTERVAL 7 YEAR < CURRENT_DATE -- Assets retired (installed more than 7 years ago) + ORDER BY asset_install_date ASC" +); + ?> + + + +
+ + 0) { ?> +
+
+
+

Account Balance

+
+
+
+
+
+
+ + + 0) { ?> +
+
+
+

Recurring Monthly

+
+
+
+
+
+
+ + +
+ + + + + +
+ + 0) { ?> +
+
+
+

Domains Expiring

+
+
+ +

+ + Domain: + -- () +

+ +
+
+
+ + +
+ + + From b777f8024998eaf7eead4d8af091bdcd9b36f55a Mon Sep 17 00:00:00 2001 From: johnnyq Date: Mon, 2 Jun 2025 14:19:04 -0400 Subject: [PATCH 03/67] Increment Minor Version and update Changelog --- CHANGELOG.md | 10 ++++++++++ includes/app_version.php | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 38642ef2..f48a1111 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ This file documents all notable changes made to ITFlow. +## [25.05.1] + +### Added / Changed +- Added Domain Expiring Card to Client Portal Dashboard for Primary and Technical Users. +- Added Balance and Monthly Recurring Amount to Client Portal Dahboard for Primary and Technical Users. + +### Fixed +- Add Payment not showing in Invoice. + + ## [25.05] ### Added / Changed diff --git a/includes/app_version.php b/includes/app_version.php index 3cbd4bb9..036a39ee 100644 --- a/includes/app_version.php +++ b/includes/app_version.php @@ -5,4 +5,4 @@ * Update this file each time we merge develop into master. Format is YY.MM (add a .v if there is more than one release a month. */ -DEFINE("APP_VERSION", "25.05"); +DEFINE("APP_VERSION", "25.05.1"); From 84e8a459c88612fe43a8857a6751e3e6b9a65461 Mon Sep 17 00:00:00 2001 From: johnnyq Date: Mon, 2 Jun 2025 14:24:52 -0400 Subject: [PATCH 04/67] update Changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f48a1111..06a75c8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,11 @@ This file documents all notable changes made to ITFlow. ### Added / Changed - Added Domain Expiring Card to Client Portal Dashboard for Primary and Technical Users. - Added Balance and Monthly Recurring Amount to Client Portal Dahboard for Primary and Technical Users. +- Added Archive Searching to network and certificates also added unarchive capabilities to them as well. ### Fixed - Add Payment not showing in Invoice. +- Updated Client Overview Entities to not show archived client's Entities even though the entity may not be archived. ## [25.05] From 08e467baa9711af3fcb3ad92ba504b4b4dc5f57c Mon Sep 17 00:00:00 2001 From: johnnyq Date: Fri, 6 Jun 2025 21:48:24 -0400 Subject: [PATCH 05/67] 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 06/67] 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 07/67] 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 08/67] 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 09/67] 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 10/67] 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 23/67] 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 24/67] 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 11/67] 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 12/67] 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 13/67] 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 14/67] 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 15/67] 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 16/67] 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 19/67] 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 20/67] 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 21/67] 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 22/67] 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 25/67] 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 26/67] 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 27/67] 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 28/67] 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 30/67] 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'])) {
    +
    + +
    +
    + +
    + +
    +
    +