diff --git a/CHANGELOG.md b/CHANGELOG.md index 10370d33..d8af4496 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,37 @@ This file documents all notable changes made to ITFlow. -## [25.11] Changelog +## [25.11.1] Maint Release + +### Fixes +- Fix broken edit Payment Method. +- Fix unable to delete Vendor Template. +- Fix Mail Queue link in flash alert for testing email and sending a quote. +- Add Show Category Type select if not defined. +- Add Show Product Type select if not defined. +- Fix add ticket watcher. +- Fix if Client isn't assigned to a ticket dont show client view. +- Fix missing session client id check when paying an invoice from client portal. +- Update Composer Webklex-IMAP library dependency symfony/http-foundation from 7.3.3 to 7.3.7 to fix security related issues. +- Add back delete Payment provider the database will handle cascade deletes to saved cards, recurring payments and client payment provider reference. +- Don't show Client Tickets Breadcrumb if no client is assigned to a ticket. +- Don't Show Contact or Assignment Tab in edit ticket if no Client is Assigned. +- Don't Show add contact, asset, vendor, watcher if not client is assigned to a ticket. +- Don't Show Public Comment & Email if contact email doesn't exist. +- Fixed IMAP Test whicn now uses RAW TCP Connection instead of the depracated php-imap extension. +- Fix Broken Link in Ticket Updates via Client Portal to agent. + +### Added / Changed +- [Feature] Added Asset Tags. +- [Feature] Added Quick Add Links to most side bar navs example quickly add a client from sidebar. +- Migrate ticket template add to ajax modal. +- Add TOTP secret to Client Export PDF in Credential section. +- Add UserID on hover in users listing. +- Merge ticket now redirects to the new ticket details page. +- [Feature] Add Pay via saved card under invoice Listings. +- Ticket Related Side Items UI Cleanup to use btn-tool class. + +## [25.11] Stable ### Deprecation Notice: - **Outdated CRON Scripts**: The following scripts are removed. diff --git a/admin/contract_templates.php b/admin/contract_template.php similarity index 100% rename from admin/contract_templates.php rename to admin/contract_template.php diff --git a/admin/database_updates.php b/admin/database_updates.php index f1e138af..ee595709 100644 --- a/admin/database_updates.php +++ b/admin/database_updates.php @@ -4059,9 +4059,7 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) { `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 + `contract_template_archived_at` DATETIME NULL DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;"); @@ -4115,10 +4113,31 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) { 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 + if (CURRENT_DATABASE_VERSION == '2.3.7') { + + mysqli_query($mysqli, " + CREATE TABLE `asset_tags` ( + `asset_tag_asset_id` INT(11) NOT NULL, + `asset_tag_tag_id` INT(11) NOT NULL, + PRIMARY KEY (`asset_tag_asset_id`, `asset_tag_tag_id`), + CONSTRAINT `fk_asset` + FOREIGN KEY (`asset_tag_asset_id`) + REFERENCES `assets`(`asset_id`) + ON DELETE CASCADE, + CONSTRAINT `fk_tag` + FOREIGN KEY (`asset_tag_tag_id`) + REFERENCES `tags`(`tag_id`) + ON DELETE CASCADE + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + "); + + mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.3.8'"); + } + + // if (CURRENT_DATABASE_VERSION == '2.3.8') { + // // Insert queries here required to update to DB version 2.3.9 // // Then, update the database to the next sequential version - // mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.3.8'"); + // mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.3.9'"); // } } else { diff --git a/admin/includes/side_nav.php b/admin/includes/side_nav.php index 230f9fdd..e6c00510 100644 --- a/admin/includes/side_nav.php +++ b/admin/includes/side_nav.php @@ -18,25 +18,37 @@ @@ -44,26 +56,38 @@ + diff --git a/admin/modals/category/category_add.php b/admin/modals/category/category_add.php index 52d9769d..8fa6ac74 100644 --- a/admin/modals/category/category_add.php +++ b/admin/modals/category/category_add.php @@ -2,7 +2,9 @@ require_once '../../../includes/modal_header.php'; -$category = nullable_htmlentities($_GET['category']); +$category = nullable_htmlentities($_GET['category'] ?? ''); + +$category_types_array = ['Expense', 'Income', 'Referral', 'Ticket']; ?> @@ -13,10 +15,30 @@ $category = nullable_htmlentities($_GET['category']);
-
diff --git a/admin/modals/tag/tag_edit.php b/admin/modals/tag/tag_edit.php index 8aa3c2ae..312244b4 100644 --- a/admin/modals/tag/tag_edit.php +++ b/admin/modals/tag/tag_edit.php @@ -12,11 +12,24 @@ $tag_type = intval($row['tag_type']); $tag_color = nullable_htmlentities($row['tag_color']); $tag_icon = nullable_htmlentities($row['tag_icon']); -// Generate the HTML form content using output buffering. +if ($tag_type == 1) { + $tag_type_display = "Client"; +} elseif ( $tag_type == 2) { + $tag_type_display = "Location"; +} elseif ( $tag_type == 3) { + $tag_type_display = "Contact"; +} elseif ( $tag_type == 4) { + $tag_type_display = "Credential"; + } elseif ( $tag_type == 5) { + $tag_type_display = "Asset"; +} else { + $tag_type_display = "Unknown"; +} + ob_start(); ?> -
- -
-
- -
- -
-
-
@@ -73,7 +70,7 @@ ob_start();
diff --git a/admin/modals/ticket_template/ticket_template_add.php b/admin/modals/ticket_template/ticket_template_add.php index 1c1c837a..cfccd8d1 100644 --- a/admin/modals/ticket_template/ticket_template_add.php +++ b/admin/modals/ticket_template/ticket_template_add.php @@ -1,75 +1,79 @@ - -
+
+
+ +
+
+
+
- +
- +
-
-
+
+
+ +
+ +
+
diff --git a/agent/contact_details.php b/agent/contact_details.php index 503f319c..1ff6e2af 100644 --- a/agent/contact_details.php +++ b/agent/contact_details.php @@ -64,7 +64,14 @@ if (isset($_GET['contact_id'])) { } // Related Assets Query - 1 to 1 relationship - $sql_related_assets = mysqli_query($mysqli, "SELECT * FROM assets LEFT JOIN asset_interfaces ON interface_asset_id = asset_id AND interface_primary = 1 WHERE asset_contact_id = $contact_id ORDER BY asset_name DESC"); + $sql_related_assets = mysqli_query($mysqli, "SELECT * FROM assets + LEFT JOIN asset_interfaces ON interface_asset_id = asset_id AND interface_primary = 1 + LEFT JOIN asset_tags ON asset_tag_asset_id = asset_id + LEFT JOIN tags ON tag_id = asset_tag_tag_id + WHERE asset_contact_id = $contact_id + GROUP BY asset_id + ORDER BY asset_name ASC" + ); $asset_count = mysqli_num_rows($sql_related_assets); // Linked Software Licenses @@ -90,7 +97,7 @@ if (isset($_GET['contact_id'])) { LEFT JOIN tags ON tags.tag_id = credential_tags.tag_id WHERE credential_contact_id = $contact_id GROUP BY credentials.credential_id - ORDER BY credential_name DESC + ORDER BY credential_name ASC "); $credential_count = mysqli_num_rows($sql_related_credentials); @@ -401,6 +408,28 @@ if (isset($_GET['contact_id'])) { $asset_created_at = nullable_htmlentities($row['asset_created_at']); $device_icon = getAssetIcon($asset_type); + // Tags + $asset_tag_name_display_array = array(); + $asset_tag_id_array = array(); + $sql_asset_tags = mysqli_query($mysqli, "SELECT * FROM asset_tags LEFT JOIN tags ON asset_tag_tag_id = tag_id WHERE asset_tag_asset_id = $asset_id ORDER BY tag_name ASC"); + while ($row = mysqli_fetch_array($sql_asset_tags)) { + + $asset_tag_id = intval($row['tag_id']); + $asset_tag_name = nullable_htmlentities($row['tag_name']); + $asset_tag_color = nullable_htmlentities($row['tag_color']); + if (empty($asset_tag_color)) { + $asset_tag_color = "dark"; + } + $asset_tag_icon = nullable_htmlentities($row['tag_icon']); + if (empty($asset_tag_icon)) { + $asset_tag_icon = "tag"; + } + + $asset_tag_id_array[] = $asset_tag_id; + $asset_tag_name_display_array[] = "$asset_tag_name"; + } + $asset_tags_display = implode('', $asset_tag_name_display_array); + ?> @@ -413,6 +442,12 @@ if (isset($_GET['contact_id'])) {
+ +
+ +
+ diff --git a/agent/dashboard.php b/agent/dashboard.php index 546164f1..9a8da89c 100644 --- a/agent/dashboard.php +++ b/agent/dashboard.php @@ -729,13 +729,22 @@ if ($user_config_dashboard_technical_enable == 1) { $client_name = nullable_htmlentities($row['client_name']); $contact_id = intval($row['ticket_contact_id']); $contact_name = nullable_htmlentities($row['contact_name']); + if ($client_id) { + $has_client = "&client_id=$client_id"; + } else { + $has_client = ""; + } $ticket_priority_color = $ticket_priority == "High" ? "danger" : ($ticket_priority == "Medium" ? "warning" : "info"); $contact_display = empty($contact_name) ? "-" : "$contact_name"; ?> - - + + "> + + + "> diff --git a/agent/includes/client_overview_side_nav.php b/agent/includes/client_overview_side_nav.php index bdd81b8f..429ac5de 100644 --- a/agent/includes/client_overview_side_nav.php +++ b/agent/includes/client_overview_side_nav.php @@ -53,6 +53,7 @@ $num_software = $row['num'];

Contacts + 0) { ?> @@ -65,6 +66,7 @@ $num_software = $row['num'];

Locations + 0) { ?> @@ -77,6 +79,7 @@ $num_software = $row['num'];

Assets + 0) { ?> @@ -89,6 +92,7 @@ $num_software = $row['num'];

Licenses + 0) { ?> @@ -101,6 +105,7 @@ $num_software = $row['num'];

Credentials + 0) { ?> @@ -113,6 +118,7 @@ $num_software = $row['num'];

Networks + 0) { ?> @@ -125,6 +131,7 @@ $num_software = $row['num'];

Certificates + 0) { ?> @@ -137,6 +144,7 @@ $num_software = $row['num'];

Domains + 0) { ?> @@ -149,6 +157,7 @@ $num_software = $row['num'];

Services + 0) { ?> diff --git a/agent/includes/client_side_nav.php b/agent/includes/client_side_nav.php index 1090ebe3..d7c479d5 100644 --- a/agent/includes/client_side_nav.php +++ b/agent/includes/client_side_nav.php @@ -30,6 +30,7 @@

Contacts + 0) { ?> @@ -43,6 +44,7 @@

Locations + 0) { ?> @@ -59,6 +61,7 @@

Tickets + 0) { ?> @@ -73,6 +76,7 @@

Recurring Tickets + @@ -87,6 +91,7 @@

Projects + @@ -101,6 +106,7 @@

Vendors + 0) { ?> @@ -132,6 +138,7 @@

Assets + 0) { ?> @@ -145,6 +152,7 @@

Licenses + 0) { ?> @@ -159,6 +167,7 @@

Credentials + 0) { ?> @@ -173,6 +182,7 @@

Networks + 0) { ?> @@ -186,6 +196,7 @@

Racks + 0) { ?> @@ -199,7 +210,7 @@

Certificates - + 0) { ?> @@ -213,7 +224,7 @@

Domains - + 0) { ?> @@ -227,6 +238,7 @@

Services + 0) { ?> @@ -240,6 +252,7 @@

Documents + 0) { ?> @@ -256,6 +269,7 @@

Files + 0) { ?> @@ -277,6 +291,7 @@

Invoices + 0) { ?> @@ -290,6 +305,7 @@

Recurring Invoices + @@ -303,6 +319,7 @@

Quotes + 0) { ?> @@ -333,6 +350,7 @@

Trips + 0) { ?> diff --git a/agent/includes/inc_all_client.php b/agent/includes/inc_all_client.php index 55e6e3c2..21321c24 100644 --- a/agent/includes/inc_all_client.php +++ b/agent/includes/inc_all_client.php @@ -29,8 +29,8 @@ if (isset($_GET['client_id'])) { $sql = mysqli_query( $mysqli, "SELECT * FROM clients - LEFT JOIN locations ON clients.client_id = locations.location_client_id AND location_primary = 1 - LEFT JOIN contacts ON clients.client_id = contacts.contact_client_id AND contact_primary = 1 + LEFT JOIN locations ON client_id = location_client_id AND location_primary = 1 + LEFT JOIN contacts ON client_id = contact_client_id AND contact_primary = 1 WHERE client_id = $client_id" ); diff --git a/agent/includes/side_nav.php b/agent/includes/side_nav.php index dcc56be7..10f97da8 100644 --- a/agent/includes/side_nav.php +++ b/agent/includes/side_nav.php @@ -23,10 +23,11 @@ ">

- Clients - - - + Clients + + + +

@@ -40,6 +41,7 @@

Tickets + @@ -51,6 +53,7 @@

Recurring Tickets + @@ -62,6 +65,7 @@

Projects + @@ -84,6 +88,7 @@

Quotes + @@ -95,6 +100,7 @@

Invoices + @@ -106,6 +112,7 @@

Recurring Invoices + @@ -116,12 +123,14 @@ ">

Revenues

+ @@ -137,12 +146,14 @@ ">

Vendors

+
diff --git a/agent/invoice.php b/agent/invoice.php index 039da741..710c04d6 100644 --- a/agent/invoice.php +++ b/agent/invoice.php @@ -18,8 +18,8 @@ if (isset($_GET['invoice_id'])) { $mysqli, "SELECT * FROM invoices LEFT JOIN clients ON invoice_client_id = client_id - LEFT JOIN contacts ON clients.client_id = contacts.contact_client_id AND contact_primary = 1 - LEFT JOIN locations ON clients.client_id = locations.location_client_id AND location_primary = 1 + LEFT JOIN contacts ON client_id = contact_client_id AND contact_primary = 1 + LEFT JOIN locations ON client_id = location_client_id AND location_primary = 1 WHERE invoice_id = $invoice_id $access_permission_query LIMIT 1" diff --git a/agent/invoices.php b/agent/invoices.php index b547b47d..9cc3cf7c 100644 --- a/agent/invoices.php +++ b/agent/invoices.php @@ -344,8 +344,6 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); $recurring_invoice_display = "-"; } - - $now = time(); if (($invoice_status == "Sent" || $invoice_status == "Partial" || $invoice_status == "Viewed") && strtotime($invoice_due) + 86400 < $now) { @@ -356,6 +354,15 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); $invoice_badge_color = getInvoiceBadgeColor($invoice_status); + // Saved Payment Methods + $sql_saved_payment_methods = mysqli_query($mysqli, " + SELECT * FROM client_saved_payment_methods + LEFT JOIN payment_providers + ON client_saved_payment_methods.saved_payment_provider_id = payment_providers.payment_provider_id + WHERE saved_payment_client_id = $client_id + AND payment_provider_active = 1; + "); + ?> @@ -395,10 +402,8 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); Add Payment - - - Pay via saved card - + 0 && ($invoice_status === 'Sent' || $invoice_status === 'Viewed')) { ?> + Pay with Saved Card diff --git a/agent/modals/asset/asset_add.php b/agent/modals/asset/asset_add.php index 0b6ebccf..cda60a46 100644 --- a/agent/modals/asset/asset_add.php +++ b/agent/modals/asset/asset_add.php @@ -27,6 +27,8 @@ if ($os_sql && mysqli_num_rows($os_sql) > 0) { $json_os = json_encode($os_arr); } +$sql_tags_select = mysqli_query($mysqli, "SELECT tag_id, tag_name FROM tags WHERE tag_type = 5 ORDER BY tag_name ASC"); + ob_start(); ?> @@ -465,6 +467,33 @@ ob_start();
+
+ +
+
+ +
+ +
+ +
+
+
+ +
diff --git a/agent/modals/asset/asset_bulk_assign_tags.php b/agent/modals/asset/asset_bulk_assign_tags.php new file mode 100644 index 00000000..e5bd8341 --- /dev/null +++ b/agent/modals/asset/asset_bulk_assign_tags.php @@ -0,0 +1,61 @@ + + + +
+ + + + + + +
+ +$asset_tag_name"; +} +$asset_tags_display = implode('', $asset_tag_name_display_array); + // Network Interfaces $sql_related_interfaces = mysqli_query($mysqli, " SELECT @@ -262,8 +284,13 @@ ob_start();
+ +
+ +
+ -
+
diff --git a/agent/modals/asset/asset_edit.php b/agent/modals/asset/asset_edit.php index 49d7cfc8..b154981d 100644 --- a/agent/modals/asset/asset_edit.php +++ b/agent/modals/asset/asset_edit.php @@ -50,7 +50,14 @@ $sql_asset_history = mysqli_query($mysqli, "SELECT * FROM asset_history DESC LIMIT 10" ); -// Generate the HTML form content using output buffering. +// Tags +$asset_tag_id_array = array(); +$sql_asset_tags = mysqli_query($mysqli, "SELECT asset_tag_tag_id FROM asset_tags WHERE asset_tag_asset_id = $asset_id"); +while ($row = mysqli_fetch_array($sql_asset_tags)) { + $asset_tag_tag_id = intval($row['asset_tag_tag_id']); + $asset_tag_id_array[] = $asset_tag_tag_id; +} + ob_start(); ?> @@ -462,6 +469,33 @@ ob_start();
+
+ +
+
+ +
+ +
+ +
+
+
+

Asset ID:

diff --git a/agent/modals/contact/contact_details.php b/agent/modals/contact/contact_details.php index 59e4ff11..a6121544 100644 --- a/agent/modals/contact/contact_details.php +++ b/agent/modals/contact/contact_details.php @@ -51,7 +51,14 @@ $auth_method = nullable_htmlentities($row['user_auth_method']); $contact_client_id = intval($row['contact_client_id']); // Related Assets Query - 1 to 1 relationship -$sql_related_assets = mysqli_query($mysqli, "SELECT * FROM assets LEFT JOIN asset_interfaces ON interface_asset_id = asset_id AND interface_primary = 1 WHERE asset_contact_id = $contact_id ORDER BY asset_name DESC"); +$sql_related_assets = mysqli_query($mysqli, "SELECT * FROM assets + LEFT JOIN asset_interfaces ON interface_asset_id = asset_id AND interface_primary = 1 + LEFT JOIN asset_tags ON asset_tag_asset_id = asset_id + LEFT JOIN tags ON tag_id = asset_tag_tag_id + WHERE asset_contact_id = $contact_id + GROUP BY asset_id + ORDER BY asset_name ASC" +); $asset_count = mysqli_num_rows($sql_related_assets); // Linked Software Licenses @@ -77,7 +84,7 @@ $sql_related_credentials = mysqli_query($mysqli, " LEFT JOIN tags ON tags.tag_id = credential_tags.tag_id WHERE credential_contact_id = $contact_id GROUP BY credentials.credential_id - ORDER BY credential_name DESC + ORDER BY credential_name ASC "); $credential_count = mysqli_num_rows($sql_related_credentials); @@ -376,6 +383,27 @@ ob_start(); $asset_notes = nullable_htmlentities($row['asset_notes']); $asset_created_at = nullable_htmlentities($row['asset_created_at']); $device_icon = getAssetIcon($asset_type); + // Tags + $asset_tag_name_display_array = array(); + $asset_tag_id_array = array(); + $sql_asset_tags = mysqli_query($mysqli, "SELECT * FROM asset_tags LEFT JOIN tags ON asset_tag_tag_id = tag_id WHERE asset_tag_asset_id = $asset_id ORDER BY tag_name ASC"); + while ($row = mysqli_fetch_array($sql_asset_tags)) { + + $asset_tag_id = intval($row['tag_id']); + $asset_tag_name = nullable_htmlentities($row['tag_name']); + $asset_tag_color = nullable_htmlentities($row['tag_color']); + if (empty($asset_tag_color)) { + $asset_tag_color = "dark"; + } + $asset_tag_icon = nullable_htmlentities($row['tag_icon']); + if (empty($asset_tag_icon)) { + $asset_tag_icon = "tag"; + } + + $asset_tag_id_array[] = $asset_tag_id; + $asset_tag_name_display_array[] = "$asset_tag_name"; + } + $asset_tags_display = implode('', $asset_tag_name_display_array); ?> @@ -389,6 +417,12 @@ ob_start();
+ +
+ +
+ diff --git a/agent/modals/product/product_add.php b/agent/modals/product/product_add.php index 51af4f00..c628516c 100644 --- a/agent/modals/product/product_add.php +++ b/agent/modals/product/product_add.php @@ -9,9 +9,12 @@ if ($type == 'product') { $type_icon = "fa-wrench"; } +$product_types_array = ['product', 'service']; + ob_start(); ?> +
- + + + + +
+ +
+
+ +
+ +
+
+ + +
diff --git a/agent/modals/ticket/ticket_add_watcher.php b/agent/modals/ticket/ticket_add_watcher.php index 0fec509d..29b32491 100644 --- a/agent/modals/ticket/ticket_add_watcher.php +++ b/agent/modals/ticket/ticket_add_watcher.php @@ -3,6 +3,7 @@ require_once '../../../includes/modal_header.php'; $ticket_id = intval($_GET['ticket_id']); +$client_id = intval(getFieldById('tickets', $ticket_id, 'ticket_client_id')); ob_start(); diff --git a/agent/modals/ticket/ticket_edit.php b/agent/modals/ticket/ticket_edit.php index cd2e5482..d2d34b30 100644 --- a/agent/modals/ticket/ticket_edit.php +++ b/agent/modals/ticket/ticket_edit.php @@ -38,33 +38,35 @@ while ($row = mysqli_fetch_array($sql_additional_assets)) { ob_start(); ?> - +