From ec344cbaa722a95a295e1456f3969ee3ecf90c06 Mon Sep 17 00:00:00 2001 From: Marcus Hill Date: Sat, 28 Jun 2025 12:30:42 +0100 Subject: [PATCH 1/6] Allow entering multiple ticket watchers at once --- modals/ticket_add_watcher_modal.php | 2 +- post/user/ticket.php | 121 +++++++++++++++------------- 2 files changed, 65 insertions(+), 58 deletions(-) diff --git a/modals/ticket_add_watcher_modal.php b/modals/ticket_add_watcher_modal.php index ab6a1cf3..d6dfdebb 100644 --- a/modals/ticket_add_watcher_modal.php +++ b/modals/ticket_add_watcher_modal.php @@ -21,7 +21,7 @@ - +
diff --git a/post/admin/admin_settings_company.php b/post/admin/admin_settings_company.php index 4213540f..acaee04d 100644 --- a/post/admin/admin_settings_company.php +++ b/post/admin/admin_settings_company.php @@ -61,6 +61,8 @@ if (isset($_GET['remove_company_logo'])) { unlink("uploads/settings/$company_logo"); + mysqli_query($mysqli,"UPDATE companies SET company_logo = NULL WHERE company_id = 1"); + // Logging logAction("Settings", "Edit", "$session_name deleted company logo"); From 19af05ebeed946f6f4b4794c37b18b31387b0006 Mon Sep 17 00:00:00 2001 From: Marcus Hill Date: Sat, 28 Jun 2025 14:28:25 +0100 Subject: [PATCH 3/6] Client Portal - Add company logo to header banner --- client/includes/check_login.php | 2 +- client/includes/header.php | 5 ++++- client/index.php | 9 ++++----- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/client/includes/check_login.php b/client/includes/check_login.php index 33972c98..d5935f96 100644 --- a/client/includes/check_login.php +++ b/client/includes/check_login.php @@ -49,7 +49,7 @@ $session_company_country = $row['company_country']; $session_company_locale = $row['company_locale']; $session_company_currency = $row['company_currency']; $currency_format = numfmt_create($session_company_locale, NumberFormatter::CURRENCY); - +$session_company_logo = $row['company_logo']; // Get contact info $contact_sql = mysqli_query($mysqli, "SELECT * FROM contacts WHERE contact_id = $session_contact_id AND contact_client_id = $session_client_id"); diff --git a/client/includes/header.php b/client/includes/header.php index 486089b5..edc59799 100644 --- a/client/includes/header.php +++ b/client/includes/header.php @@ -139,10 +139,13 @@ header("X-Frame-Options: DENY"); // Legacy
+ + "> +

Welcome, !

-
+
New ticket -
- + 0) { ?>
@@ -210,9 +209,9 @@ if ($session_contact_primary == 1 || $session_contact_is_billing_contact) { ?> -
From bc9529c4885c41c597e5cdadd538569a23898ed4 Mon Sep 17 00:00:00 2001 From: Marcus Hill Date: Sat, 28 Jun 2025 15:49:17 +0100 Subject: [PATCH 4/6] Client Portal - Allow client choose asset during ticket creation --- client/post.php | 3 ++- client/ticket_add.php | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/client/post.php b/client/post.php index 03ec1773..59381157 100644 --- a/client/post.php +++ b/client/post.php @@ -15,6 +15,7 @@ if (isset($_POST['add_ticket'])) { $subject = sanitizeInput($_POST['subject']); $details = mysqli_real_escape_string($mysqli, ($_POST['details'])); $category = intval($_POST['category']); + $asset = intval($_POST['asset']); // Get settings from get_settings.php $config_ticket_prefix = sanitizeInput($config_ticket_prefix); @@ -38,7 +39,7 @@ if (isset($_POST['add_ticket'])) { $new_config_ticket_next_number = $config_ticket_next_number + 1; 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 = 'Portal', ticket_category = $category, ticket_subject = '$subject', ticket_details = '$details', ticket_priority = '$priority', ticket_status = 1, ticket_billable = $config_ticket_default_billable, ticket_created_by = $session_user_id, ticket_contact_id = $session_contact_id, ticket_url_key = '$url_key', ticket_client_id = $session_client_id"); + mysqli_query($mysqli, "INSERT INTO tickets SET ticket_prefix = '$config_ticket_prefix', ticket_number = $ticket_number, ticket_source = 'Portal', ticket_category = $category, ticket_subject = '$subject', ticket_details = '$details', ticket_priority = '$priority', ticket_status = 1, ticket_billable = $config_ticket_default_billable, ticket_created_by = $session_user_id, ticket_contact_id = $session_contact_id, ticket_asset_id = $asset, ticket_url_key = '$url_key', ticket_client_id = $session_client_id"); $ticket_id = mysqli_insert_id($mysqli); // Notify agent DL of the new ticket, if populated with a valid email diff --git a/client/ticket_add.php b/client/ticket_add.php index ceffca1c..6037f5b1 100644 --- a/client/ticket_add.php +++ b/client/ticket_add.php @@ -6,6 +6,9 @@ require_once 'includes/inc_all.php'; +// Allow clients to select a related asset when raising a ticket +$sql_assets = mysqli_query($mysqli, "SELECT asset_id, asset_name, asset_type FROM assets WHERE asset_contact_id = $session_contact_id AND asset_client_id = $session_client_id AND asset_archived_at IS NULL ORDER BY asset_name ASC"); + ?>
+ 0) { ?> +
+ +
+
+ +
+ +
+
+
From 3f21e73b2e63aea4d06ba1ce152806cd4e6c00b5 Mon Sep 17 00:00:00 2001 From: Marcus Hill Date: Sat, 28 Jun 2025 15:55:13 +0100 Subject: [PATCH 5/6] Comment ticket collabs, still broken --- ticket.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ticket.php b/ticket.php index df7bfba3..7717a10b 100644 --- a/ticket.php +++ b/ticket.php @@ -888,11 +888,12 @@ if (isset($_GET['ticket_id'])) { - -
- Collaborators: -
- + + + + + + From 1939e06a2448e37f5d5697b6f23d278117a2095e Mon Sep 17 00:00:00 2001 From: Marcus Hill Date: Sat, 28 Jun 2025 17:56:55 +0100 Subject: [PATCH 6/6] Start using the new modal when creating tickets. This allows us to select both the client and contact, along with a client-specific asset, location and vendor --- ajax.php | 73 ++++++ js/recurring_tickets_add_modal.js | 63 ----- js/tickets_add_modal.js | 176 ++++++++++++++ modals/recurring_ticket_add_modal.php | 6 +- modals/ticket_add_modal.php | 24 +- modals/ticket_add_modal_v2.php | 333 ++++++++++++++++++++++++++ tickets.php | 4 +- 7 files changed, 599 insertions(+), 80 deletions(-) delete mode 100644 js/recurring_tickets_add_modal.js create mode 100644 js/tickets_add_modal.js create mode 100644 modals/ticket_add_modal_v2.php diff --git a/ajax.php b/ajax.php index 3dae8752..d98812b7 100644 --- a/ajax.php +++ b/ajax.php @@ -329,6 +329,79 @@ if (isset($_GET['get_client_contacts'])) { echo json_encode($response); } +/* + * Returns ordered list of active assets for a specified client + */ +if (isset($_GET['get_client_assets'])) { + enforceUserPermission('module_client'); + + $client_id = intval($_GET['client_id']); + + $asset_sql = mysqli_query( + $mysqli, + "SELECT asset_id, asset_name, contact_name FROM assets + LEFT JOIN clients on asset_client_id = client_id + LEFT JOIN contacts ON contact_id = asset_contact_id + WHERE assets.asset_archived_at IS NULL AND asset_client_id = $client_id + $access_permission_query + ORDER BY asset_important DESC, asset_name" + ); + + while ($row = mysqli_fetch_array($asset_sql)) { + $response['assets'][] = $row; + } + + echo json_encode($response); +} + +/* + * Returns locations for a specified client + */ +if (isset($_GET['get_client_locations'])) { + enforceUserPermission('module_client'); + + $client_id = intval($_GET['client_id']); + + $locations_sql = mysqli_query( + $mysqli, + "SELECT location_id, location_name FROM locations + LEFT JOIN clients on location_client_id = client_id + WHERE locations.location_archived_at IS NULL AND location_client_id = $client_id + $access_permission_query + ORDER BY location_primary DESC, location_name ASC" + ); + + while ($row = mysqli_fetch_array($locations_sql)) { + $response['locations'][] = $row; + } + + echo json_encode($response); +} + +/* + * Returns ordered list of vendors for a specified client + */ +if (isset($_GET['get_client_vendors'])) { + enforceUserPermission('module_client'); + + $client_id = intval($_GET['client_id']); + + $vendors_sql = mysqli_query( + $mysqli, + "SELECT vendor_id, vendor_name FROM vendors + LEFT JOIN clients on vendor_client_id = client_id + WHERE vendors.vendor_archived_at IS NULL AND vendor_client_id = $client_id + $access_permission_query + ORDER BY vendor_name ASC" + ); + + while ($row = mysqli_fetch_array($vendors_sql)) { + $response['vendors'][] = $row; + } + + echo json_encode($response); +} + /* * NEW TOTP getter for client login/passwords page * When provided with a login ID, checks permissions and returns the 6-digit code diff --git a/js/recurring_tickets_add_modal.js b/js/recurring_tickets_add_modal.js deleted file mode 100644 index 433fb5fe..00000000 --- a/js/recurring_tickets_add_modal.js +++ /dev/null @@ -1,63 +0,0 @@ -// Client selected listener -// We seem to have to use jQuery to listen for events, as the client input is a select2 component? - -const clientSelectDropdown = document.getElementById("changeClientSelect"); // Define client selector - -// // If the client selector is disabled, we must be on client_recurring_tickets.php instead. Trigger the contact list update. -if (clientSelectDropdown.disabled) { - - let client_id = $(clientSelectDropdown).find(':selected').val(); - - // Update the contacts dropdown list - populateContactsDropdown(client_id); -} - -// Listener for client selection. Populate contact select when a client is selected -$(clientSelectDropdown).on('select2:select', function (e) { - let client_id = $(this).find(':selected').val(); - - // Update the contacts dropdown list - populateContactsDropdown(client_id); - - // TODO: Update the assets dropdown list - -}); - -// Populate client contact function (after a client is selected) -function populateContactsDropdown(client_id) { - // Send a GET request to ajax.php as ajax.php?get_client_contacts=true&client_id=NUM - jQuery.get( - "ajax.php", - {get_client_contacts: 'true', client_id: client_id}, - function(data) { - - // If we get a response from ajax.php, parse it as JSON - const response = JSON.parse(data); - - // Access the data for contacts (multiple) - const contacts = response.contacts; - - // Contacts dropdown - const contactSelectDropdown = document.getElementById("contactSelect"); - - // Clear Category dropdown - let i, L = contactSelectDropdown.options.length - 1; - for (i = L; i >= 0; i--) { - contactSelectDropdown.remove(i); - } - contactSelectDropdown[contactSelectDropdown.length] = new Option('- Contact -', '0'); - - // Populate dropdown - contacts.forEach(contact => { - var appendText = ""; - if (contact.contact_primary == "1") { - appendText = " (Primary)"; - } else if (contact.contact_technical == "1") { - appendText = " (Technical)"; - } - contactSelectDropdown[contactSelectDropdown.length] = new Option(contact.contact_name + appendText, contact.contact_id); - }); - - } - ); -} diff --git a/js/tickets_add_modal.js b/js/tickets_add_modal.js new file mode 100644 index 00000000..154e29f3 --- /dev/null +++ b/js/tickets_add_modal.js @@ -0,0 +1,176 @@ +// Used to populate dynamic content in recurring_ticket_add_modal and ticket_add_modal_v2 based on selected client + +// Client selected listener +// We seem to have to use jQuery to listen for events, as the client input is a select2 component? + +const clientSelectDropdown = document.getElementById("changeClientSelect"); // Define client selector + +// // If the client selector is disabled, we must be on a client-specific page instead. Trigger the lists to update. +if (clientSelectDropdown.disabled) { + + let client_id = $(clientSelectDropdown).find(':selected').val(); + + populateLists(client_id); +} + +// Listener for client selection. Populate select lists when a client is selected +$(clientSelectDropdown).on('select2:select', function (e) { + let client_id = $(this).find(':selected').val(); + + // Update the contacts dropdown list + populateLists(client_id); + +}); + +// Populates dropdowns with dynamic content based on the client ID +// Called the client select dropdown is used or if the client select is disabled +function populateLists(client_id) { + + populateContactsDropdown(client_id); + + populateAssetsDropdown(client_id); + + populateLocationsDropdown(client_id); + + populateVendorsDropdown(client_id); +} + +// Populate client contacts +function populateContactsDropdown(client_id) { + // Send a GET request to ajax.php as ajax.php?get_client_contacts=true&client_id=NUM + jQuery.get( + "ajax.php", + {get_client_contacts: 'true', client_id: client_id}, + function(data) { + + // If we get a response from ajax.php, parse it as JSON + const response = JSON.parse(data); + + // Access the data for contacts (multiple) + const contacts = response.contacts; + + // Contacts dropdown + const contactSelectDropdown = document.getElementById("contactSelect"); + + // Clear dropdown + let i, L = contactSelectDropdown.options.length - 1; + for (i = L; i >= 0; i--) { + contactSelectDropdown.remove(i); + } + contactSelectDropdown[contactSelectDropdown.length] = new Option('- Contact -', '0'); + + // Populate dropdown + contacts.forEach(contact => { + var appendText = ""; + if (contact.contact_primary == "1") { + appendText = " (Primary)"; + } else if (contact.contact_technical == "1") { + appendText = " (Technical)"; + } + contactSelectDropdown[contactSelectDropdown.length] = new Option(contact.contact_name + appendText, contact.contact_id); + }); + + } + ); +} + +// Populate client assets +function populateAssetsDropdown(client_id) { + jQuery.get( + "ajax.php", + {get_client_assets: 'true', client_id: client_id}, + function(data) { + + // If we get a response from ajax.php, parse it as JSON + const response = JSON.parse(data); + + // Access the data for assets (multiple) + const assets = response.assets; + + // Assets dropdown + const assetSelectDropdown = document.getElementById("assetSelect"); + + // Clear dropdown + let i, L = assetSelectDropdown.options.length - 1; + for (i = L; i >= 0; i--) { + assetSelectDropdown.remove(i); + } + assetSelectDropdown[assetSelectDropdown.length] = new Option('- Asset -', '0'); + + // Populate dropdown with asset name (and contact, if set) + assets.forEach(asset => { + let displayText = asset.asset_name; + if (asset.contact_name !== null) { + displayText = asset.asset_name + " - " + asset.contact_name; + } + + assetSelectDropdown[assetSelectDropdown.length] = new Option(displayText, asset.asset_id); + }); + + } + ); +} + +// Populate client locations +function populateLocationsDropdown(client_id) { + jQuery.get( + "ajax.php", + {get_client_locations: 'true', client_id: client_id}, + function(data) { + + // If we get a response from ajax.php, parse it as JSON + const response = JSON.parse(data); + + // Access the data for locations (multiple) + const locations = response.locations; + + // Locations dropdown + const locationSelectDropdown = document.getElementById("locationSelect"); + + // Clear dropdown + let i, L = locationSelectDropdown.options.length - 1; + for (i = L; i >= 0; i--) { + locationSelectDropdown.remove(i); + } + locationSelectDropdown[locationSelectDropdown.length] = new Option('- Location -', '0'); + + // Populate dropdown + locations.forEach(location => { + locationSelectDropdown[locationSelectDropdown.length] = new Option(location.location_name, location.location_id); + }); + + } + ); +} + +// Populate client vendors +function populateVendorsDropdown(client_id) { + jQuery.get( + "ajax.php", + {get_client_vendors: 'true', client_id: client_id}, + function(data) { + + // If we get a response from ajax.php, parse it as JSON + const response = JSON.parse(data); + + // Access the data for locations (multiple) + const vendors = response.vendors; + + // Locations dropdown + const vendorSelectDropdown = document.getElementById("vendorSelect"); + + // Clear dropdown + let i, L = vendorSelectDropdown.options.length - 1; + for (i = L; i >= 0; i--) { + vendorSelectDropdown.remove(i); + } + vendorSelectDropdown[vendorSelectDropdown.length] = new Option('- Vendor -', '0'); + + // Populate dropdown + vendors.forEach(vendor => { + vendorSelectDropdown[vendorSelectDropdown.length] = new Option(vendor.vendor_name, vendor.vendor_id); + }); + + } + ); +} diff --git a/modals/recurring_ticket_add_modal.php b/modals/recurring_ticket_add_modal.php index 891c978c..73117579 100644 --- a/modals/recurring_ticket_add_modal.php +++ b/modals/recurring_ticket_add_modal.php @@ -51,7 +51,7 @@
- +
@@ -233,7 +233,7 @@ $asset_name_select = nullable_htmlentities($row['asset_name']); ?>