diff --git a/CHANGELOG.md b/CHANGELOG.md index d35ccc2f..10370d33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,102 @@ This file documents all notable changes made to ITFlow. +## [25.11] Changelog + +### Deprecation Notice: +- **Outdated CRON Scripts**: The following scripts are removed. + - `/scripts/cron_mail_queue.php` + - `/scripts/cron_ticket_email_parser.php` + - `/scripts/cron.php` + - `/scripts/cron_domain_refresher.php` + - `/scripts/cron_certificate_refresher.php` + + **Action Required**: Transition to the new versions: + - `/cron/mail_queue.php` + - `/cron/ticket_email_parser.php` + - `/cron/cron.php` + - `/cron/domain_refresher.php` + - `/cron/certificate_refresher.php` + +- PHP Extensions php-imap and php-mime-mail-parser are no longer required. +--- + +### Fixes +- **Ticket Listing**: Resolved issue where the “Check All” checkbox was visible even when ticket status wasn’t set. Now hidden for closed tickets only. +- **Timer Auto-Start**: Show H/M/S placeholders when timer auto-start is disabled. +- **Ticket Guest URL**: Fixed email not including the ticket guest URL key. +- **EML Generation**: Resolved issue with EML not being generated in the new ticket parser. +- **New Ticket Mail Notification**: Included message when notifying the tech of a reply in the new ticket mail parser. +- **Advanced Filter Collapse**: Added clause to prevent collapse of advanced filters when the “from” date is set to the default (1970-01-01). +- **Recurring Invoice**: Fixed issue where email was marked as sent but not actually sent when forcing a recurring invoice to an invoice. +- **CSRF Token**: Fixed issue with deleting recurring ticket from asset details page due to missing CSRF check token. +- **Vendor Website Link**: Fixed missing `https://` prefix in the vendor website link on the vendor details modal. +- **Agent Select Box**: Resolved issue where agents sometimes didn’t appear in the agent select boxes. +- **TinyMCE**: Fixed TinyMCE editor issue on Bulk Create Ticket in Assets. +- **Ticket Timer**: Fixed ticket timer initialization after reload and when the tab is put to sleep (background tab). +- **Client Deletion**: Fixed issue with client deletion. +- **Domain Records**: Added flag for missing SOA record when adding a domain (prevents subdomain creation). +- **Domain Fetching**: Quits domain record fetching if no SOA record exists (prevents subdomains). +- **Domain Expiry**: Only show time to expiry when there’s an expiry date set; otherwise, display a dash. +- **Certificates**: Improved handling of empty date in the agent UI. +- **Certificates API**: Fixed bug with missing JS to fetch certificate details. +- **API Updates**: + - Clients API: Added support for archiving/un-archiving clients, updating client data, and abbreviation support. + - Contacts API: Added archiving/un-archiving and restriction to only allow one primary contact per client. + - Mail Queue: Added recipient domain MX validation before sending emails. + +--- + +### Added / Changed +- **Backup / Restore**: Improved backup and restore by streaming data to disk (to prevent memory issues), setting unlimited timeouts, checking for bad backup contents, and using PHP for DB import instead of shell exec. Added `.htaccess` to prevent PHP execution in `/uploads/` directory. +- **Ajax Modals**: Migrated all Add and Bulk modals to the new Ajax Modal for improved performance. +- **Recurring Ticket Sorting**: Default sorting of recurring tickets by `RunDate` instead of subject. +- **Recurring Ticket Enhancements**: + - Added Billable column. + - Added bulk actions for setting priority, agent, billable status, and next run dates. + - Added filters for category, assigned agent, and billable status. + - Added new frequency options: 3-day and biweekly. +- **Asset Select**: Updated asset select dropdown to separate asset types using opt groups (planned for wider use). +- **Expiring Domains & Certificates**: Added "30 Day" warning for expiring domains and certificates in the dashboard. +- **Ticket Search**: Allowed search using both ticket prefix and number. +- **Recurring Invoice**: Cancel recurring invoices when the associated client is archived. +- **Credentials Import/Export**: Now includes TOTP secrets when importing/exporting credentials. +- **Asset Notes Import**: Allowed importing of asset notes. +- **Ticket View**: Added a "View HTML Code" button in all ticket views for TinyMCE. +- **Date Range Picker**: Updated all date filters to use the improved DateRangePicker JS. +- **Bulk Ticket Creation**: Added bulk ticket creation for clients. +- **Sidebar Updates**: Updated all sidebars to use absolute paths for easier integration with custom code. +- **Document Actions**: Added Archive and Delete buttons to the Document Details view with improved redirect behavior. +- **Ticket Template Sorting**: Allowed sorting by task count in ticket templates. +- **Contact Modal UI**: Updated contact details modal to display contact information at the top. +- **API & Code Updates**: + - Separated out post files for recurring tickets, invoices, expenses, and payments. + - Removed unused budget code. +- **Invoice Product Autocomplete**: Now allows searching for product codes as well as names. +- **Client Duplicate Check**: Flags duplicate clients or leads when using the client add modal. +- **Recurring Invoice Reference**: Added a column to invoices indicating if they were created from a recurring invoice. +- **Global Search Enhancements**: + - Allowed ticket details to be searchable in global search. + - Allowed searching for quotes in global search. +- **UI/UX Improvements**: + - Spruced up the ticket details page UI. + - Added contact email validation to flag duplicates or invalid addresses. +- **API Debugging**: Log API endpoint/URL path for authentication failures to aid in debugging. +- **Image Upload Optimization**: Removed image optimization from uploads (this will be handled by a cron job in the future). +- **View Behavior Change**: Updated ticket/invoice/quote views to always be in the Client section, showing client-side navigation and top info bar. + +--- + +### Library Updates: +- **DataTable**: Bumped from 2.3.3 to 2.3.4. +- **TinyMCE**: Bumped from 8.0.2 to 8.2.0. +- **Stripe-PHP**: Bumped from 17.6.0 to 18.1.0. +- **PHPMailer**: Bumped from 6.10.0 to 7.0.0. +- **Chart.js**: Bumped from 4.5.0 to 4.5.1. + + + + ## [25.10.1] - Deprecation Notice: `/scripts/cron_mail_queue.php` , `/scripts/cron_ticket_email_parser.php` , `/scripts/cron.php` `/scripts/cron_domain_refresher.php`, `/scripts/cron_certificate_refresher.php` are being phased out. Please transition to `/cron/mail_queue.php` , `/cron/ticket_email_parser.php`, `/cron/cron.php`, `/cron/domain_refresher.php`, `/cron/certificate_refresher.php` These older scripts will be removed in the November 25.11 release—update accordingly. 25.10.1 installs have the script already configured. diff --git a/admin/ai_model.php b/admin/ai_model.php index cca1f863..a231ce5c 100644 --- a/admin/ai_model.php +++ b/admin/ai_model.php @@ -16,7 +16,7 @@ $num_rows = mysqli_num_rows($sql);

AI Models

- +
@@ -104,5 +104,4 @@ $num_rows = mysqli_num_rows($sql);

AI Providers

- +
@@ -105,5 +105,4 @@ $num_rows = mysqli_num_rows($sql);
-
-
-

API Keys

-
- -
-
- -
- -
-
- -
-
- -
- -
-
-
- -
-
- -
-
- -
- -
-
- -
- -
- - - - "> - - - - - - - - - - - - All Clients"; - } else { - $api_key_client = nullable_htmlentities($row['client_name']); - } - - ?> - - - - - - - - - - - - - - - - - - - - -
-
- -
-
- - Name - - - - Client - - - - Secret - - - - Created - - - - Expires - - Action
-
- -
-
- -
- -
- -
- +
+
+

API Keys

+
+
- +
+ +
+
+ +
+
+ +
+ +
+
+
+ +
+
+ +
+
+ +
+ +
+
+ +
+ +
+ + + + "> + + + + + + + + + + + + All Clients"; + } else { + $api_key_client = nullable_htmlentities($row['client_name']); + } + + ?> + + + + + + + + + + + + + + +
+
+ +
+
+ + Name + + + + Client + + + + Secret + + + + Created + + + + Expires + + Action
+
+ +
+
+ +
+ +
+ +
+ +
+
+ +
-
" id="advancedFilter"> +
" id="advancedFilter">
-
+
- - -
-
-
-
- - -
-
-
-
- - + + + + +
diff --git a/admin/audit_log.php b/admin/audit_log.php index 33b58ac0..06a11e13 100644 --- a/admin/audit_log.php +++ b/admin/audit_log.php @@ -159,34 +159,15 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
-
" id="advancedFilter"> +
" id="advancedFilter">
-
+
- - -
-
-
-
- - -
-
-
-
- - + + + + +
diff --git a/admin/contract_templates.php b/admin/contract_templates.php new file mode 100644 index 00000000..a3ab44d5 --- /dev/null +++ b/admin/contract_templates.php @@ -0,0 +1,125 @@ + + +
+
+

Contract Templates

+
+ +
+
+
+ +
+
+ +
+ +
+
+
+
+ +
+ + "> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Template NameTypeUpdate FrequencySLA (L/M/H Response)SLA (L/M/H Resolution)Hourly RateAfter Hours RateSupport HoursNet TermsCreatedUpdatedAction
+ + + +
+
+ +
+
+
+ +
+
+ + diff --git a/admin/custom_link.php b/admin/custom_link.php index 77a42072..eebc217d 100644 --- a/admin/custom_link.php +++ b/admin/custom_link.php @@ -21,7 +21,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));

Custom Links

- +
@@ -145,5 +145,4 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
CURRENT_DATABASE_VERSION) { mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.3.6'"); } - // if (CURRENT_DATABASE_VERSION == '2.3.5') { - // // Insert queries here required to update to DB version 2.3.5 + if (CURRENT_DATABASE_VERSION == '2.3.6') { + // Create New Contract Templates Table + mysqli_query($mysqli, "CREATE TABLE `contract_templates` ( + `contract_template_id` INT(11) AUTO_INCREMENT PRIMARY KEY, + `contract_template_name` VARCHAR(255) NOT NULL, + `contract_template_description` TEXT NULL DEFAULT NULL, + `contract_template_type` VARCHAR(50) NULL DEFAULT NULL, + + `contract_template_sla_low_response_time` INT(11) NULL DEFAULT NULL, + `contract_template_sla_low_resolution_time` INT(11) NULL DEFAULT NULL, + `contract_template_sla_medium_response_time` INT(11) NULL DEFAULT NULL, + `contract_template_sla_medium_resolution_time` INT(11) NULL DEFAULT NULL, + `contract_template_sla_high_response_time` INT(11) NULL DEFAULT NULL, + `contract_template_sla_high_resolution_time` INT(11) NULL DEFAULT NULL, + + `contract_template_rate_standard` DECIMAL(10,2) NULL DEFAULT NULL, + `contract_template_rate_after_hours` DECIMAL(10,2) NULL DEFAULT NULL, + + `contract_template_net_terms` VARCHAR(50) NULL DEFAULT NULL, + `contract_template_support_hours` VARCHAR(100) NULL DEFAULT NULL, + `contract_template_renewal_frequency` VARCHAR(50) NULL DEFAULT NULL, + + `contract_template_details` TEXT NULL DEFAULT NULL, + + `contract_template_created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, + `contract_template_updated_at` DATETIME NULL ON UPDATE CURRENT_TIMESTAMP, + `contract_template_archived_at` DATETIME NULL DEFAULT NULL, + + `company_id` INT(11) NOT NULL + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;"); + + + // Create New Contracts Table + mysqli_query($mysqli, "CREATE TABLE `contracts` ( + `contract_id` INT(11) AUTO_INCREMENT PRIMARY KEY, + `contract_name` VARCHAR(255) NOT NULL, + `contract_status` VARCHAR(50) NOT NULL, + `contract_type` VARCHAR(50) NOT NULL, + + `contract_sla_low_response_time` INT(11) NULL DEFAULT NULL, + `contract_sla_low_resolution_time` INT(11) NULL DEFAULT NULL, + `contract_sla_medium_response_time` INT(11) NULL DEFAULT NULL, + `contract_sla_medium_resolution_time` INT(11) NULL DEFAULT NULL, + `contract_sla_high_response_time` INT(11) NULL DEFAULT NULL, + `contract_sla_high_resolution_time` INT(11) NULL DEFAULT NULL, + + `contract_details` TEXT NULL DEFAULT NULL, + + `contract_client_id` INT(11) NULL DEFAULT NULL, + `contract_client_name` VARCHAR(255) NULL DEFAULT NULL, + `contract_client_address` TEXT NULL DEFAULT NULL, + `contract_client_email` VARCHAR(255) NULL DEFAULT NULL, + `contract_client_phone` VARCHAR(100) NULL DEFAULT NULL, + + `contract_contact_name` VARCHAR(255) NULL DEFAULT NULL, + `contract_contact_signature` TEXT NULL DEFAULT NULL, + `contract_contact_signature_date` DATETIME NULL DEFAULT NULL, + + `contract_agent_name` VARCHAR(255) NULL DEFAULT NULL, + `contract_agent_signature` TEXT NULL DEFAULT NULL, + `contract_agent_signature_date` DATETIME NULL DEFAULT NULL, + + `contract_rate_standard` DECIMAL(10,2) NULL DEFAULT NULL, + `contract_rate_after_hours` DECIMAL(10,2) NULL DEFAULT NULL, + + `contract_net_terms` VARCHAR(50) NULL DEFAULT NULL, + `contract_support_hours` VARCHAR(100) NULL DEFAULT NULL, + + `contract_start_date` DATE NULL DEFAULT NULL, + `contract_end_date` DATE NULL DEFAULT NULL, + `contract_renewal_frequency` VARCHAR(50) NULL DEFAULT NULL, + + `contract_created_at` DATETIME DEFAULT CURRENT_TIMESTAMP, + `contract_updated_at` DATETIME NULL ON UPDATE CURRENT_TIMESTAMP, + `contract_archived_at` DATETIME NULL DEFAULT NULL, + + FOREIGN KEY (`contract_client_id`) REFERENCES `clients`(`client_id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;"); + + mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.3.7'"); + } + + // if (CURRENT_DATABASE_VERSION == '2.3.7') { + // // Insert queries here required to update to DB version 2.3.8 // // Then, update the database to the next sequential version - // mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.3.6'"); + // mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.3.8'"); // } } else { diff --git a/admin/debug.php b/admin/debug.php index bab12f91..f40f7881 100644 --- a/admin/debug.php +++ b/admin/debug.php @@ -46,8 +46,6 @@ $systemInfo[] = [ // Section: PHP Extensions $phpExtensions = []; $extensions = [ - 'php-mailparse' => 'mailparse', - 'php-imap' => 'imap', 'php-mysqli' => 'mysqli', 'php-intl' => 'intl', 'php-curl' => 'curl', diff --git a/admin/document_template.php b/admin/document_template.php index 7516abf1..afb6c811 100644 --- a/admin/document_template.php +++ b/admin/document_template.php @@ -22,7 +22,7 @@

Document Templates

-
@@ -121,38 +121,4 @@
- - - - +

Nothing to see here

Go Back"; + require_once "../includes/footer.php"; + exit(); +} $row = mysqli_fetch_array($sql_document); diff --git a/admin/includes/side_nav.php b/admin/includes/side_nav.php index 89b06517..230f9fdd 100644 --- a/admin/includes/side_nav.php +++ b/admin/includes/side_nav.php @@ -14,21 +14,27 @@
-
" id="advancedFilter"> +
" id="advancedFilter">
-
+
- - -
-
-
-
- - -
-
-
-
- - + + + + +
diff --git a/admin/modals/ai/ai_model_add.php b/admin/modals/ai/ai_model_add.php index 32c3880d..aa40a2fb 100644 --- a/admin/modals/ai/ai_model_add.php +++ b/admin/modals/ai/ai_model_add.php @@ -1,73 +1,77 @@ -
- +$sql = mysqli_query($mysqli, "SELECT * FROM project_templates WHERE project_template_id = $project_template_id LIMIT 1"); +$row = mysqli_fetch_array($sql); +$project_template_name = nullable_htmlentities($row['project_template_name']); +$project_template_description = nullable_htmlentities($row['project_template_description']); + +ob_start(); + +?> + + +
+ + + + + + +
+ + - -
+ +
+ + + + +
+ + - -
+?> + +
+ + + +
+ + - -
+ +
+ + +
+ + diff --git a/admin/modals/tax/tax_add.php b/admin/modals/tax/tax_add.php index d2ac58b2..57bceba7 100644 --- a/admin/modals/tax/tax_add.php +++ b/admin/modals/tax/tax_add.php @@ -1,30 +1,35 @@ - - -
"> -
-

Recurring Tickets

-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
SubjectPriorityFrequencyNext Run
- - - - - -
-
-
-
- -
"> -
-

Tickets

-
-
-
- - - - - - - - - - - - - - Never

"; + while ($row = mysqli_fetch_array($sql_related_tickets)) { + $ticket_id = intval($row['ticket_id']); + $ticket_prefix = nullable_htmlentities($row['ticket_prefix']); + $ticket_number = intval($row['ticket_number']); + $ticket_subject = nullable_htmlentities($row['ticket_subject']); + $ticket_priority = nullable_htmlentities($row['ticket_priority']); + $ticket_status_id = intval($row['ticket_status_id']); + $ticket_status_name = nullable_htmlentities($row['ticket_status_name']); + $ticket_status_color = nullable_htmlentities($row['ticket_status_color']); + $ticket_created_at = nullable_htmlentities($row['ticket_created_at']); + $ticket_updated_at = nullable_htmlentities($row['ticket_updated_at']); + if (empty($ticket_updated_at)) { + if ($ticket_status_name == "Closed") { + $ticket_updated_at_display = "

Never

"; + } else { + $ticket_updated_at_display = "

Never

"; + } } else { - $ticket_updated_at_display = "

Never

"; + $ticket_updated_at_display = $ticket_updated_at; } - } else { - $ticket_updated_at_display = $ticket_updated_at; - } - $ticket_closed_at = nullable_htmlentities($row['ticket_closed_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_id == 5) { - $ticket_assigned_to_display = "

Not Assigned

"; + 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_assigned_to_display = "

Not Assigned

"; + $ticket_priority_display = "-"; } - } else { - $ticket_assigned_to_display = nullable_htmlentities($row['user_name']); + $ticket_assigned_to = intval($row['ticket_assigned_to']); + if (empty($ticket_assigned_to)) { + if ($ticket_status_id == 5) { + $ticket_assigned_to_display = "

Not Assigned

"; + } else { + $ticket_assigned_to_display = "

Not Assigned

"; + } + } else { + $ticket_assigned_to_display = nullable_htmlentities($row['user_name']); + } + + ?> + + + + + + + + + + + + - - - - - - - - - + +
NumberSubjectPriorityStatusAssignedLast ResponseCreated
+ +
- -
+
+
+
+
"> +
+

Linked Services

+
+ +
+
+
+
+ + + + + + + + + + + $linked_services[] = $service_id; - -
ServiceCategoryImportanceAction
-
-
-
+ ?> -
"> -
-

Linked Services

-
- -
-
-
-
- - - - - - - - - - - + + + + + - while ($row = mysqli_fetch_array($sql_linked_services)) { - $service_id = intval($row['service_id']); - $service_name = nullable_htmlentities($row['service_name']); - $service_description = nullable_htmlentities($row['service_description']); - $service_category = nullable_htmlentities($row['service_category']); - $service_importance = nullable_htmlentities($row['service_importance']); + - - - - - - - - - - -
ServiceCategoryImportanceAction
+
+
+
+ +
-
-
-
- -
+ + +
+
- + + + + + + + + + + + - - - - - - - - - - - - 0) { - $os_arr = []; - while ($row = mysqli_fetch_assoc($os_sql)) { - // jQuery UI Autocomplete expects {label: "...", value: "..."} - $label = $row['label']; - $os_arr[] = ['label' => $label, 'value' => $label]; - } - $json_os = json_encode($os_arr); -} - ?>
@@ -185,8 +178,8 @@ if ($os_sql && mysqli_num_rows($os_sql) > 0) {
= 2) { ?>
- @@ -716,24 +712,7 @@ if ($os_sql && mysqli_num_rows($os_sql) > 0) { - - - - - -
-
-

Budget for

- -
-
-
-
- -
-
- - - - - - - - - - - - - - - $month): - $amount = getBudgetAmount($budgets, $category['category_id'], $index + 1); - $rowTotal += $amount; - $columnTotals[$index] += $amount; - ?> - - - - - - - - - - - - - - - -
ExpenseTotal
Total
-
- - diff --git a/agent/budget_edit.php b/agent/budget_edit.php deleted file mode 100644 index 26af60ce..00000000 --- a/agent/budget_edit.php +++ /dev/null @@ -1,114 +0,0 @@ - -
-
-

Editing Budget for

-
- - View Budget - - - -
-
-
- -
-
- -
-
-
- - - - - - - - - - - - - - - - - - $month): - $amount = getBudgetAmount($budgets, $category['category_id'], $index + 1); - $rowTotal += $amount; - $columnTotals[$index] += $amount; - ?> - - - - - - - - - - - - - - - -
ExpenseTotal
Total
-
-
- -
- - diff --git a/agent/calendar.php b/agent/calendar.php index c8891783..e7924190 100644 --- a/agent/calendar.php +++ b/agent/calendar.php @@ -33,7 +33,7 @@ if (isset($_GET['calendar_id'])) {

Calendars

- +
@@ -82,8 +82,6 @@ if (isset($_GET['calendar_id'])) { Certificates
- + 0) { ?> - - = 2) { ?>
- @@ -137,23 +137,41 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); Action (0)