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 @@
diff --git a/agent/assets.php b/agent/assets.php
index c03d4eb4..237ed926 100644
--- a/agent/assets.php
+++ b/agent/assets.php
@@ -78,6 +78,18 @@ if ($client_url && isset($_GET['location']) && !empty($_GET['location'])) {
$location_filter = 0;
}
+// Tags Filter
+if (isset($_GET['tags']) && is_array($_GET['tags']) && !empty($_GET['tags'])) {
+ // Sanitize each element of the tags array
+ $sanitizedTags = array_map('intval', $_GET['tags']);
+ // Convert the sanitized tags into a comma-separated string
+ $tag_filter = implode(",", $sanitizedTags);
+ $tag_query = "AND tag_id IN ($tag_filter)";
+} else {
+ $tag_filter = 0;
+ $tag_query = '';
+}
+
//Get Asset Counts
$row = mysqli_fetch_assoc(mysqli_query($mysqli, "
SELECT
@@ -93,9 +105,13 @@ $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
+ LEFT JOIN asset_tags ON asset_tag_asset_id = asset_id
+ LEFT JOIN tags ON tag_id = asset_tag_tag_id
WHERE $archive_query
+ $tag_query
$access_permission_query
$client_query
+ GROUP BY asset_id
) AS filtered_assets;
"));
@@ -127,13 +143,16 @@ $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
+ LEFT JOIN asset_tags ON asset_tag_asset_id = asset_id
+ LEFT JOIN tags ON tag_id = asset_tag_tag_id
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 interface_mac 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%')
+ $tag_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 interface_mac 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%' OR tag_name LIKE '%$q%')
AND ($type_query)
$access_permission_query
$location_query
$client_query
-
+ GROUP BY asset_id
ORDER BY $sort $order LIMIT $record_from, $record_to"
);
@@ -270,7 +289,32 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
";
}
diff --git a/agent/post/quote.php b/agent/post/quote.php
index 0b9e2a4c..6a8695a0 100644
--- a/agent/post/quote.php
+++ b/agent/post/quote.php
@@ -497,7 +497,7 @@ if (isset($_GET['email_quote'])) {
logAction("Quote", "Email", "$session_name emailed quote $quote_prefix$quote_number to $contact_email", $client_id, $quote_id);
- flash_alert("Quote has been queued successfully! See Mail Queue");
+ flash_alert("Quote sent!");
//Don't change the status to sent if the status is anything but draft
if ($quote_status == 'Draft') {
diff --git a/agent/post/ticket.php b/agent/post/ticket.php
index 422f7113..4f17790d 100644
--- a/agent/post/ticket.php
+++ b/agent/post/ticket.php
@@ -1814,14 +1814,19 @@ if (isset($_POST['merge_ticket'])) {
// NEW PARENT ticket details
// Get merge into ticket id (as it may differ from the number)
- $sql = mysqli_query($mysqli, "SELECT ticket_id FROM tickets WHERE ticket_number = $merge_into_ticket_number");
+ $sql = mysqli_query($mysqli, "SELECT ticket_id, ticket_client_id FROM tickets WHERE ticket_number = $merge_into_ticket_number");
if (mysqli_num_rows($sql) == 0) {
flash_alert("Cannot merge into that ticket.", 'error');
redirect();
}
$merge_row = mysqli_fetch_array($sql);
$merge_into_ticket_id = intval($merge_row['ticket_id']);
-
+ $client_id = intval($merge_row['ticket_client_id']);
+ if ($client_id) {
+ $has_client = "&client_id=$client_id";
+ } else {
+ $has_client = "";
+ }
// Sanity check
if ($ticket_number == $merge_into_ticket_number) {
flash_alert("Cannot merge into the same ticket.", 'error');
@@ -1853,7 +1858,7 @@ if (isset($_POST['merge_ticket'])) {
flash_alert("Ticket merged into $ticket_prefix$merge_into_ticket_number");
- redirect();
+ redirect("ticket.php?ticket_id=$merge_into_ticket_id$has_client");
}
diff --git a/agent/ticket.php b/agent/ticket.php
index 6b8ce285..7564ddf9 100644
--- a/agent/ticket.php
+++ b/agent/ticket.php
@@ -39,7 +39,6 @@ if (isset($_GET['ticket_id'])) {
LEFT JOIN ticket_statuses ON ticket_status = ticket_status_id
LEFT JOIN categories ON ticket_category = category_id
WHERE ticket_id = $ticket_id
- $access_permission_query
LIMIT 1"
);
@@ -358,9 +357,11 @@ if (isset($_GET['ticket_id'])) {
This is a notification that a new ticket has been raised in ITFlow. Client: $client_name Priority: $priority Link: https://$config_base_url/ticket.php?ticket_id=$ticket_id
$subject $details";
+ $email_body = "Hello,
This is a notification that a new ticket has been raised in ITFlow. Client: $client_name Priority: $priority Link: https://$config_base_url/agent/ticket.php?ticket_id=$ticket_id&client_id=$session_client_id