diff --git a/.gitignore b/.gitignore index 1dc7ee4b..751f2adf 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,22 @@ xcustom/* !xcustom/readme.php post/xcustom !post/xcustom/readme.php +admin/custom/* +!admin/custom/readme.php +agent/custom/* +!agent/custom/readme.php +client/custom/* +!client/custom/readme.php +guest/custom/* +!guest/custom/readme.php +cron/custom/* +!cron/custom/readme.php +scripts/custom/* +!scripts/custom/readme.php +setup/custom/* +!setup/custom/readme.php +api/v1/custom/* +!api/v1/custom/readme.php .zed + + diff --git a/CHANGELOG.md b/CHANGELOG.md index f09eb2bb..d35ccc2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,197 @@ This file documents all notable changes made to ITFlow. +## [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. + +### Fixes +- Fix regression missing custom Favicon. +- Update SMTP and IMAP provider to allow for empty strings, empty means disabled. +- Fix Client portal Microsoft SSO Logins. +- Fix regression in Vendor Templates. +- Fix refression in some broken links from user to agent. +- Fix Project edit. +- Prevent open redirects upon agent login. +- Fix regression on switching to Webklex IMAP to allow for no SSL/TLS in IMAP. +- Fix Setup Redirect not behaving properly when setup hasnt been performed. +- Added Server Document Root Var to several includes, headers, footers files to allow includes from deeper directory strutures such as the new custom directories. +- Fix edit contact in contact details. +- Add .htaccess to /cron/. + +### Added / Changed +- Support for HTML Signatures. +- Add Edit Project Functionality in a ticket. +- Added more custom locations: /cron/custom/, /scripts/custom/, /api/v1/custom/, /setup/custom/. +- Copied `/scripts/cron.php` `/scripts/cron_domain_refresher.php`, `/scripts/cron_certificate_refresher.php` to `/cron/cron.php`, `/cron/domain_refresher.php`, `/cron/certificate_refresher.php`. See Above! +- Signatures is now handled in post ticket reply on Public Comments only. + +## [25.10] + +### Breaking Changes +- Renamed `/user/` directory to `/agent/`. +- Deprecation Notice: `/scripts/cron_mail_queue.php` and `/scripts/cron_ticket_email_parser.php` are being phased out. Please transition to `/cron/mail_queue.php` and `/cron/ticket_email_parser.php`. These older scripts will be removed in the November release—update accordingly. New Installs via the script will have this already configured. +- Custom is working now. Custom code should be placed in /admin/custom/ , /agent/custom/ , /client/custom/ /guest/custom/ +We will provide example code with directory structure for each custom directory a week after this release. + +### Fixes +- Resolved issue with "Restore from Setup" not functioning correctly. +- Corrected asset name display in logs and flash messages when editing an asset in a ticket. +- Fixed Payment Provider Threshold not being applied. +- Fixed issue where Threshold setting was not saving properly. +- Various minor fixes for Payment Provider issues. +- Removed leads from the client selection list in the "New Ticket" modal. +- Fixed issues with the MFA modal. +- Resolved MFA enforcement bugs. +- Fixed KeepAlive functionality to maintain user sessions longer. +- Fixed multiple broken links caused by the `/user/` to `/agent/` path migration. +- Fixed Custom code directories. + +### Added / Changed +- Removed "ACH" as a payment method; added "Bank Transfer" instead. +- Replaced relative paths with absolute paths for web assets. +- Tickets can now be resolved via the API. +- Added a filter for Archived Users and an option to restore them. +- Introduced a modal when archiving users, allowing reassignment of open and recurring tickets to another agent. +- Improved logic for determining the index/root page. +- Added "Assigned Agent" column for recurring tickets. +- Introduced "Additional Assets" option when editing assets in tickets; modal now uses the updated AJAX method. +- Added Gibraltar to the list of supported countries. +- Added Custom Link Option for the Admin Nav. +- Added Custom Link Option for the Reports Nav. + +### Other notes +- Major releases will happen on the first week of every Month. + + +## [25.09.2] + +### Fixes +- Fix Payment Method Select box in Revenue. +- Remove Extra Feeback Wording When Invoice Sends. +- Updated all CSV exports to use escape parameters. +- Fix Missing First row on Asset interface export. +- Fix Edit User not working due to incorrect modal footer path. +- Fix Add Certificate breaking due spelling on function. +- Update all CSV Exports to include company name or client name depending on when its being exported from. +- Introduced new function sanitize_filename and implmented it in all exports. +- Spruced up UI/UX Saved Paymented section in Client Portal. +- Fix add Payment Link in client portal recurring invoice section. +- Better Logic handling for default page redirect. + +### Features +- Introduced new Beta mail parser cron using webklex imap library instead of php-imap as this is deprecated --Not Enabled on existing installs, only new installs. +- Introduced Beta support for OAUTH2 Authentication for Microsoft 365 and Google Workspaces for both incoming ticket parsing and outgoing email but must use new mail parser and mail queue for this to work, and requires changing the cron jobs: scripts/cron_mail_queue.php to cron/mail_queue.php and scripts/cron_ticket_email_parser.php to cron/ticket_email_parser.php. + +--- + +## [25.09.1] + +### Fixes +- **Web Installer**: Resolved issue with broken installer caused by incorrect database schema file name. +- Hide the "Add Credit" button as the feature is not fully implemented yet. +- Corrected long invoice/quote notes that were overlapping with the footer in PDF exports. +- Fixed AI settings not appearing in the Admin Menu when the Billing module was disabled. +- Enabled wrapping of client tags when they are too long. +- Fixed an issue where AI was not functioning correctly. +- Removed extra spacing between the contact name and icon in the Ticket Details contact card. + +### Features +- Redesigned **AI Ticket Summary**, now divided into 3 sections: Main Issue, Actions Taken, and Resolution/Next Steps. +- Updated the **AI Ticket Summary** prompt to include ticket status, reply author, source, category, and priority. + +--- + +## [25.09] + +***BACK UP*** before updating. + +--- + +### Breaking Changes and Notes +- We strongly recommend updating from the command line, however if performed via the webui and after performed it will return a 404. thats normal as the directory structure has changed, just close your browser then log back in then go back to update to perform the many database updates. +- This is a major release with significant changes. While the community has done a great job identifying bugs, some may still remain — continued testing is encouraged. +- All AI settings will be **reset** and must be reconfigured using the new AI provider backend. +- The `xcustom` directory has been renamed to `custom`. All custom libraries and post-processing scripts should now be placed here. + +--- + +### Added / Changed +- Numerous UI improvements and refinements across the application. +- Enhanced visual clarity by thickening the left border on ticket comments to help identify comment types. +- Ticket details UI redesigned to use less space at the top of the screen. +- Introduced tracking for the **first response date/time** on tickets. +- New reporting feature: **Average time to first response** on tickets. +- Stripe integration rebuilt using the new **payment provider backend**. +- Clients can now save and manage **multiple payment methods**. +- Support for selecting saved cards for **recurring invoices** in both the client and agent portals. +- Initial database structure and logic added for **credit management** (feature not yet enabled). +- Major **backend directory restructuring**. +- Introduced **stock/inventory management**, including a stock ledger backend. +- Stock quantities now update automatically when invoice items are added or removed. +- Invoice autocomplete now includes: **name, description, price, tax, stock levels**, and links `product_id` to `item_id`. +- Added a **category filter** to invoices. +- Linked stock to related expenses. +- New product fields: **location, code, and type**. +- Products now separated into two types: **Service** and **Product**. +- **Dark mode** introduced. +- Projects: Now support linking **closed tickets**. +- Clients: Added bulk actions for tags, referral source, industry, hourly rate, email, archive, and restore. +- Invoices: Bulk action added to **assign categories**. +- Assets: New `client_uri` field, visible in both the agent and client portals. +- Client Portal: Clients can now **select an asset** during ticket creation. +- Client Portal: Company logo now **displays in the header**. +- Client Portal: Dashboard cards are now **clickable** for more detail. +- Assets: Option added to include **MAC Address** in additional columns. +- Asset Interface: Bulk actions added — set DHCP, network type, and delete. +- API: + - Added `/location` endpoint. + - Ticket content now supports **HTML formatting**. +- New option to filter and display **500 records per page** in the footer. +- Payment methods are now treated as a **separate entity** instead of being grouped under categories. +- Updated libraries: + - **TinyMCE** + - **Chart.js** (major upgrade) + - **DataTables** + - **Bootstrap** + - **FullCalendar** + - **php-stripe** + +--- + +### Fixed +- Several security vulnerabilities patched. +- Ticket status is no longer updated when scheduling. +- Client Portal: Tech contacts can no longer edit their own details. +- Fixed overlapping logo issue in Invoice/Quote PDF exports. +- Refactored `check_login.php` into multiple files for modular login functionality. +- Removed redundant logging comments for redirects. +- Renamed `get_settings.php` to `load_global_settings.php`. +- Simplified syntax for `ajax-modal` and updated usage throughout the app. +- Fixed issue where primary contact text wasn’t displaying. +- Corrected client **Net Terms** display. +- Fixed logic for recurring expense **next run date**. +- Resolved broken **IMAP test button**. +- Archived clients can no longer log into the portal. +- Searching closed tickets no longer reverts to open tickets. +- Fixed project search filter not showing completed projects. +- Fixed issue where company logo was not being removed correctly. +- Resolved API bugs: + - Default rate and net terms. + - Contact location. + - Document endpoint. + +--- + +### Developer Updates +- Replaced legacy code with newer functions like `redirect()`, `getFieldById()`, and `flash_alert()`. +- Significantly improved performance of queries used for filter selection boxes. + + +## [25.06.1] + +### Fixed +- Fixed a regression in setup causing it to crash and never complete, due to missing default for currency. + ## [25.06] ### Breaking CHANGES diff --git a/admin/ai_model.php b/admin/ai_model.php new file mode 100644 index 00000000..cca1f863 --- /dev/null +++ b/admin/ai_model.php @@ -0,0 +1,108 @@ + + +
+
+

AI Models

+
+ +
+
+
+
+ + "> + + + + + + + + + + + + + + + + + + + No Records Here"; + } + + ?> + + +
+ + Model + + + + Provider + + + + Use Case + + + Prompt + Action
+ + + + + +
+ +
+
+
+ + + +
+
+

AI Providers

+
+ +
+
+
+
+ + "> + + + + + + + + + + + + + + + + + + + No Records Here"; + } + + ?> + + +
+ + Provider + + + + URL + + + + Key + + + Models + Action
+ + + + + +
+ +
+
+
+ + - - + - $client_name"; + $client_name_display = "$client_name"; } $log_entity_id = intval($row['log_entity_id']); @@ -292,11 +292,11 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); -
-
">Referral - Payment - Method - + @@ -155,11 +145,8 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); - + Edit - - + @@ -118,7 +119,7 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); - CURRENT_DATABASE_VERSION) { //Dropping patch panel as a patch panel can be documented as an asset with interfaces. mysqli_query($mysqli, "DROP TABLE `patch_panel_ports`"); mysqli_query($mysqli, "DROP TABLE `patch_panels`"); - + mysqli_query($mysqli, "RENAME TABLE `events` TO `calendar_events`"); mysqli_query($mysqli, "RENAME TABLE `event_attendees` TO `calendar_event_attendees`"); @@ -2957,7 +2957,7 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) { ALTER TABLE `calendar_events` ADD FOREIGN KEY (`event_calendar_id`) REFERENCES `calendars`(`calendar_id`) ON DELETE CASCADE "); - + mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.0.3'"); } @@ -2974,7 +2974,7 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) { ALTER TABLE `certificate_history` ADD FOREIGN KEY (`certificate_history_certificate_id`) REFERENCES `certificates`(`certificate_id`) ON DELETE CASCADE "); - + mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.0.4'"); } @@ -3335,14 +3335,14 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) { ADD FOREIGN KEY (`vendor_id`) REFERENCES `vendors`(`vendor_id`) ON DELETE CASCADE, ADD FOREIGN KEY (`file_id`) REFERENCES `files`(`file_id`) ON DELETE CASCADE "); - + mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.0.5'"); } if (CURRENT_DATABASE_VERSION == '2.0.5') { // CONVERT All tables TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci - + $tables = [ 'accounts', 'api_keys', 'app_logs', 'asset_credentials', 'asset_custom', 'asset_documents', 'asset_files', 'asset_history', 'asset_interface_links', 'asset_interfaces', 'asset_notes', 'assets', @@ -3381,14 +3381,14 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) { } if (CURRENT_DATABASE_VERSION == '2.0.7') { - + mysqli_query($mysqli, "ALTER TABLE `files` DROP `file_hash`"); mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.0.8'"); } if (CURRENT_DATABASE_VERSION == '2.0.8') { - + mysqli_query($mysqli, "ALTER TABLE `files` DROP `file_has_thumbnail`"); mysqli_query($mysqli, "ALTER TABLE `files` DROP `file_has_preview`"); mysqli_query($mysqli, "ALTER TABLE `files` DROP `file_asset_id`"); @@ -3397,7 +3397,7 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) { } if (CURRENT_DATABASE_VERSION == '2.0.9') { - + mysqli_query($mysqli, "ALTER TABLE `contacts` ADD `contact_phone_country_code` VARCHAR(10) DEFAULT 1 AFTER `contact_email`"); mysqli_query($mysqli, "ALTER TABLE `contacts` ADD `contact_mobile_country_code` VARCHAR(10) DEFAULT 1 AFTER `contact_extension`"); @@ -3425,7 +3425,7 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) { } if (CURRENT_DATABASE_VERSION == '2.1.2') { - + // Update country_code to NULL for `contacts` table mysqli_query($mysqli, "ALTER TABLE `contacts` MODIFY `contact_phone_country_code` VARCHAR(10) DEFAULT NULL"); mysqli_query($mysqli, "ALTER TABLE `contacts` MODIFY `contact_mobile_country_code` VARCHAR(10) DEFAULT NULL"); @@ -3460,7 +3460,7 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) { if (CURRENT_DATABASE_VERSION == '2.1.3') { mysqli_query($mysqli, "ALTER TABLE `client_stripe` ADD `stripe_pm_details` VARCHAR(200) DEFAULT NULL AFTER `stripe_pm`"); mysqli_query($mysqli, "ALTER TABLE `client_stripe` ADD `stripe_pm_created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `stripe_pm_details`"); - + mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.1.4'"); } @@ -3670,13 +3670,375 @@ if (LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION) { mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.1.9'"); } - // if (CURRENT_DATABASE_VERSION == '2.1.9') { - // // Insert queries here required to update to DB version 2.2.0 + if (CURRENT_DATABASE_VERSION == '2.1.9') { + mysqli_query($mysqli, "ALTER TABLE `companies` MODIFY `company_currency` VARCHAR(200) DEFAULT 'USD'"); + mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.2.0'"); + } + + if (CURRENT_DATABASE_VERSION == '2.2.0') { + mysqli_query($mysqli, "ALTER TABLE `tickets` ADD `ticket_quote_id` INT(11) NOT NULL DEFAULT 0 AFTER `ticket_asset_id`"); + mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.2.1'"); + } + + if (CURRENT_DATABASE_VERSION == '2.2.1') { + mysqli_query($mysqli, "CREATE TABLE `ai_providers` ( + `ai_provider_id` INT(11) NOT NULL AUTO_INCREMENT, + `ai_provider_name` VARCHAR(200) NOT NULL, + `ai_provider_api_url` VARCHAR(200) NOT NULL, + `ai_provider_api_key` VARCHAR(200) DEFAULT NULL, + `ai_provider_created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `ai_provider_updated_at` DATETIME NULL ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`ai_provider_id`) + )"); + + mysqli_query($mysqli, " + CREATE TABLE `ai_models` ( + `ai_model_id` INT(11) NOT NULL AUTO_INCREMENT, + `ai_model_name` VARCHAR(200) NOT NULL, + `ai_model_prompt` TEXT DEFAULT NULL, + `ai_model_use_case` VARCHAR(200) DEFAULT NULL, + `ai_model_created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `ai_model_updated_at` DATETIME NULL ON UPDATE CURRENT_TIMESTAMP, + `ai_model_ai_provider_id` INT(11) NOT NULL, + PRIMARY KEY (`ai_model_id`), + FOREIGN KEY (`ai_model_ai_provider_id`) + REFERENCES `ai_providers`(`ai_provider_id`) + ON DELETE CASCADE + ) + "); + + mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.2.2'"); + } + + if (CURRENT_DATABASE_VERSION == '2.2.2') { + mysqli_query($mysqli, "CREATE TABLE `payment_methods` ( + `payment_method_id` INT(11) NOT NULL AUTO_INCREMENT, + `payment_method_name` VARCHAR(200) NOT NULL, + `payment_method_description` VARCHAR(250) DEFAULT NULL, + `payment_method_created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `payment_method_updated_at` DATETIME NULL ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`payment_method_id`) + )"); + + mysqli_query($mysqli, "CREATE TABLE `payment_providers` ( + `payment_provider_id` INT(11) NOT NULL AUTO_INCREMENT, + `payment_provider_name` VARCHAR(200) NOT NULL, + `payment_provider_description` VARCHAR(250) DEFAULT NULL, + `payment_provider_public_key` VARCHAR(250) DEFAULT NULL, + `payment_provider_private_key` VARCHAR(250) DEFAULT NULL, + `payment_provider_threshold` DECIMAL(15,2) DEFAULT NULL, + `payment_provider_active` TINYINT(1) NOT NULL DEFAULT 1, + `payment_provider_account` INT(11) NOT NULL, + `payment_provider_expense_vendor` INT(11) NOT NULL DEFAULT 0, + `payment_provider_expense_category` INT(11) NOT NULL DEFAULT 0, + `payment_provider_expense_percentage_fee` DECIMAL(4,4) DEFAULT NULL, + `payment_provider_expense_flat_fee` DECIMAL(15,2) DEFAULT NULL, + `payment_provider_created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `payment_provider_updated_at` DATETIME NULL ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`payment_provider_id`) + )"); + + mysqli_query($mysqli, "CREATE TABLE `client_saved_payment_methods` ( + `saved_payment_id` INT(11) NOT NULL AUTO_INCREMENT, + `saved_payment_provider_method` VARCHAR(200) NOT NULL, + `saved_payment_description` VARCHAR(200) DEFAULT NULL, + `saved_payment_client_id` INT(11) NOT NULL, + `saved_payment_provider_id` INT(11) NOT NULL, + `saved_payment_created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `saved_payment_updated_at` DATETIME NULL ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`saved_payment_id`), + FOREIGN KEY (`saved_payment_client_id`) REFERENCES clients(`client_id`) ON DELETE CASCADE, + FOREIGN KEY (`saved_payment_provider_id`) REFERENCES payment_providers(`payment_provider_id`) ON DELETE CASCADE + )"); + + mysqli_query($mysqli, "CREATE TABLE `client_payment_provider` ( + `client_id` INT(11) NOT NULL, + `payment_provider_id` INT(11) NOT NULL, + `payment_provider_client` VARCHAR(200) NOT NULL, + `client_payment_provider_created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`client_id`, `payment_provider_id`), + FOREIGN KEY (`client_id`) REFERENCES clients(`client_id`) ON DELETE CASCADE, + FOREIGN KEY (`payment_provider_id`) REFERENCES payment_providers(`payment_provider_id`) ON DELETE CASCADE + )"); + + mysqli_query($mysqli, "ALTER TABLE `recurring_payments` ADD `recurring_payment_saved_payment_id` INT(11) DEFAULT NULL AFTER `recurring_payment_recurring_invoice_id`"); + + mysqli_query($mysqli, "ALTER TABLE `recurring_payments` ADD CONSTRAINT `fk_recurring_saved_payment` FOREIGN KEY (`recurring_payment_saved_payment_id`) REFERENCES `client_saved_payment_methods`(`saved_payment_id`) ON DELETE CASCADE"); + + mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.2.3'"); + } + + if (CURRENT_DATABASE_VERSION == '2.2.3') { + + mysqli_query($mysqli, "CREATE TABLE `credits` ( + `credit_id` INT(11) NOT NULL AUTO_INCREMENT, + `credit_amount` DECIMAL(15,2) NOT NULL, + `credit_reference` VARCHAR(250) DEFAULT NULL, + `credit_created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP(), + `credit_created_by` INT(11) NOT NULL, + `credit_expire_at` DATE DEFAULT NULL, + `credit_client_id` INT(11) NOT NULL, + PRIMARY KEY (`credit_id`) + )"); + + mysqli_query($mysqli, "ALTER TABLE `invoices` ADD `invoice_credit_amount` DECIMAL(15,2) NOT NULL DEFAULT 0.00 AFTER `invoice_discount_amount`"); + + mysqli_query($mysqli, "CREATE TABLE `discount_codes` ( + `discount_code_id` INT(11) NOT NULL AUTO_INCREMENT, + `discount_code_description` VARCHAR(250) DEFAULT NULL, + `discount_code_amount` DECIMAL(15,2) NOT NULL, + `discount_code` VARCHAR(200) NOT NULL, + `discount_code_created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP(), + `discount_code_created_by` INT(11) NOT NULL, + `discount_code_updated_at` DATETIME NULL ON UPDATE CURRENT_TIMESTAMP, + `discount_code_archived_at` DATETIME NULL DEFAULT NULL, + `discount_code_expire_at` DATE DEFAULT NULL, + PRIMARY KEY (`discount_code_id`) + )"); + + mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.2.4'"); + } + + if (CURRENT_DATABASE_VERSION == '2.2.4') { + mysqli_query($mysqli, "ALTER TABLE `settings` ADD `config_theme_dark` TINYINT(1) NOT NULL DEFAULT 0 AFTER `config_theme`"); + mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.2.5'"); + } + + if (CURRENT_DATABASE_VERSION == '2.2.5') { + mysqli_query($mysqli, "ALTER TABLE `assets` ADD `asset_uri_client` VARCHAR(500) NULL DEFAULT NULL AFTER `asset_uri_2`"); + mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.2.6'"); + } + + if (CURRENT_DATABASE_VERSION == '2.2.6') { + mysqli_query($mysqli, "ALTER TABLE `credits` DROP `credit_reference`"); + mysqli_query($mysqli, "ALTER TABLE `credits` ADD `credit_type` ENUM('prepaid', 'manual', 'refund', 'promotion', 'usage') NOT NULL DEFAULT 'manual' AFTER `credit_amount`"); + mysqli_query($mysqli, "ALTER TABLE `credits` ADD `credit_note` TEXT NULL DEFAULT NULL AFTER `credit_type`"); + mysqli_query($mysqli, "ALTER TABLE `credits` ADD `credit_invoice_id` INT(11) NULL DEFAULT NULL AFTER `credit_expire_at`"); + mysqli_query($mysqli, "ALTER TABLE `credits` ADD INDEX (`credit_client_id`)"); + mysqli_query($mysqli, "ALTER TABLE `credits` ADD INDEX (`credit_invoice_id`)"); + mysqli_query($mysqli, "ALTER TABLE `credits` ADD INDEX (`credit_created_at`)"); + + mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.2.7'"); + } + + if (CURRENT_DATABASE_VERSION == '2.2.7') { + mysqli_query($mysqli, "ALTER TABLE `user_settings` ADD `user_config_theme_dark` TINYINT(1) NOT NULL DEFAULT 0 AFTER `user_config_signature`"); + mysqli_query($mysqli, "ALTER TABLE `settings` DROP `config_theme_dark`"); + + mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.2.8'"); + } + + if (CURRENT_DATABASE_VERSION == '2.2.8') { + + mysqli_query($mysqli, "ALTER TABLE `products` ADD `product_type` ENUM('service', 'product') NOT NULL DEFAULT 'service' AFTER `product_name`"); + mysqli_query($mysqli, "ALTER TABLE `products` ADD `product_code` VARCHAR(200) DEFAULT NULL AFTER `product_description`"); + mysqli_query($mysqli, "ALTER TABLE `products` ADD `product_location` VARCHAR(250) DEFAULT NULL AFTER `product_code`"); + + mysqli_query($mysqli, "CREATE TABLE `product_stock` ( + `stock_id` INT(11) NOT NULL AUTO_INCREMENT, + `stock_qty` INT(11) NOT NULL, + `stock_note` TEXT DEFAULT NULL, + `stock_created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP(), + `stock_expense_id` INT(11) DEFAULT NULL, + `stock_item_id` INT(11) DEFAULT NULL, + `stock_product_id` INT(11) NOT NULL, + PRIMARY KEY (`stock_id`) + )"); + + mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.2.9'"); + } + + if (CURRENT_DATABASE_VERSION == '2.2.9') { + // Migrate Stripe Settings over to new Tables + + // Get Current Stripe Settings + $sql_stripe_settings = mysqli_query($mysqli, "SELECT * FROM settings WHERE company_id = 1"); + $row = mysqli_fetch_array($sql_stripe_settings); + $config_stripe_enable = intval($row['config_stripe_enable']); + if ($config_stripe_enable === 1) { + $config_stripe_publishable = mysqli_real_escape_string($mysqli, $row['config_stripe_publishable']); + $config_stripe_secret = mysqli_real_escape_string($mysqli, $row['config_stripe_secret']); + $config_stripe_account = intval($row['config_stripe_account']); + $config_stripe_expense_vendor = intval($row['config_stripe_expense_vendor']); + $config_stripe_expense_category = intval($row['config_stripe_expense_category']); + $config_stripe_percentage_fee = floatval($row['config_stripe_percentage_fee']); + $config_stripe_flat_fee = floatval($row['config_stripe_flat_fee']); + + mysqli_query($mysqli,"INSERT INTO payment_providers SET + payment_provider_name = 'Stripe', + payment_provider_public_key = '$config_stripe_publishable', + payment_provider_private_key = '$config_stripe_secret', + payment_provider_account = $config_stripe_account, + payment_provider_expense_vendor = $config_stripe_expense_vendor, + payment_provider_expense_category = $config_stripe_expense_category, + payment_provider_expense_percentage_fee = $config_stripe_percentage_fee, + payment_provider_expense_flat_fee = $config_stripe_flat_fee" + ); + + $provider_id = mysqli_insert_id($mysqli); + + // Migrate Clients and Payment Method over + $sql_stripe_clients = mysqli_query($mysqli, "SELECT * FROM client_stripe WHERE stripe_pm IS NOT NULL AND stripe_pm != ''"); + while ($row = mysqli_fetch_array($sql_stripe_clients)) { + $client_id = intval($row['client_id']); + $stripe_id = mysqli_real_escape_string($mysqli, $row['stripe_id']); + $stripe_pm = mysqli_real_escape_string($mysqli, $row['stripe_pm']); + $stripe_pm_details = mysqli_real_escape_string($mysqli, $row['stripe_pm_details'] ?? 'Saved Card'); + + mysqli_query($mysqli,"INSERT INTO client_payment_provider SET + client_id = $client_id, + payment_provider_id = $provider_id, + payment_provider_client = '$stripe_id'" + ); + + mysqli_query($mysqli,"INSERT INTO client_saved_payment_methods SET + saved_payment_provider_method = '$stripe_pm', + saved_payment_description = '$stripe_pm_details', + saved_payment_client_id = $client_id, + saved_payment_provider_id = $provider_id" + ); + } + } + + // Get Stripe provider id + $res = mysqli_query($mysqli, " + SELECT payment_provider_id + FROM payment_providers + WHERE payment_provider_name = 'Stripe' + ORDER BY payment_provider_id DESC + LIMIT 1 + "); + $stripe = mysqli_fetch_assoc($res); + $stripe_provider_id = intval($stripe['payment_provider_id']); + + // Correct mapping: RP -> Recurring Invoice -> Client -> Client's Stripe saved method + mysqli_query($mysqli, " + UPDATE recurring_payments rp + INNER JOIN recurring_invoices ri + ON ri.recurring_invoice_id = rp.recurring_payment_recurring_invoice_id + INNER JOIN client_saved_payment_methods spm + ON spm.saved_payment_client_id = ri.recurring_invoice_client_id + AND spm.saved_payment_provider_id = $stripe_provider_id + SET + rp.recurring_payment_method = 'Credit Card', + rp.recurring_payment_saved_payment_id = spm.saved_payment_id + WHERE rp.recurring_payment_method = 'Stripe' + "); + + mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.3.0'"); + } + + if (CURRENT_DATABASE_VERSION == '2.3.0') { + // Migrate Payment Methods from Categories Table to new payment_methods table + $sql_categories = mysqli_query($mysqli, "SELECT * FROM categories WHERE category_type = 'Payment Method' AND category_name != 'Stripe' AND category_archived_at IS NULL"); + + while ($row = mysqli_fetch_array($sql_categories)) { + $category_name = sanitizeInput($row['category_name']); + + mysqli_query($mysqli,"INSERT INTO payment_methods SET payment_method_name = '$category_name'"); + } + + mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.3.1'"); + } + + if (CURRENT_DATABASE_VERSION == '2.3.1') { + + // Delete all Recurring Payments that are Stripe + mysqli_query($mysqli, "DELETE FROM recurring_payments WHERE recurring_payment_method = 'Stripe'"); + + // Delete Stripe Specific ITFlow Client Stripe Client Relationship Table + mysqli_query($mysqli, "DROP TABLE client_stripe"); + + // Delete Unused Stripe and AI Settings now in their own tables + mysqli_query($mysqli, "ALTER TABLE `settings` + DROP `config_stripe_enable`, + DROP `config_stripe_publishable`, + DROP `config_stripe_secret`, + DROP `config_stripe_account`, + DROP `config_stripe_expense_vendor`, + DROP `config_stripe_expense_category`, + DROP `config_stripe_percentage_fee`, + DROP `config_stripe_flat_fee`, + DROP `config_ai_enable`, + DROP `config_ai_provider`, + DROP `config_ai_model`, + DROP `config_ai_url`, + DROP `config_ai_api_key` + "); + + mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.3.2'"); + } + + if (CURRENT_DATABASE_VERSION == '2.3.2') { + + mysqli_query($mysqli, "ALTER TABLE settings + ADD `config_imap_provider` ENUM('standard_imap','google_oauth','microsoft_oauth') NULL DEFAULT NULL AFTER `config_mail_from_name`, + ADD `config_mail_oauth_client_id` VARCHAR(255) NULL AFTER `config_imap_provider`, + ADD `config_mail_oauth_client_secret` VARCHAR(255) NULL AFTER `config_mail_oauth_client_id`, + ADD `config_mail_oauth_tenant_id` VARCHAR(255) NULL AFTER `config_mail_oauth_client_secret`, + ADD `config_mail_oauth_refresh_token` TEXT NULL AFTER `config_mail_oauth_tenant_id`, + ADD `config_mail_oauth_access_token` TEXT NULL AFTER `config_mail_oauth_refresh_token`, + ADD `config_mail_oauth_access_token_expires_at` DATETIME NULL AFTER `config_mail_oauth_access_token` + "); + + mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.3.3'"); + } + + if (CURRENT_DATABASE_VERSION == '2.3.3') { + + mysqli_query($mysqli, "ALTER TABLE settings + ADD `config_smtp_provider` ENUM('standard_smtp','google_oauth','microsoft_oauth') NULL DEFAULT NULL AFTER `config_start_page` + "); + + mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.3.4'"); + } + + if (CURRENT_DATABASE_VERSION == '2.3.4') { + + // Add Software Keys + mysqli_query($mysqli, "CREATE TABLE `software_keys` ( + `software_key_id` INT(11) NOT NULL AUTO_INCREMENT, + `software_key` VARCHAR(400) NOT NULL, + `software_key_software_id` INT(11) NOT NULL, + PRIMARY KEY (`software_key_id`), + FOREIGN KEY (`software_key_software_id`) REFERENCES `software`(`software_id`) ON DELETE CASCADE + )"); + + // Software Key Assignments to Contacts + mysqli_query($mysqli, "CREATE TABLE `software_key_contact_assignments` ( + `software_key_id` INT(11) NOT NULL, + `contact_id` INT(11) NOT NULL, + `software_key_assigned_at` DATETIME DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`software_key_id`, `contact_id`), + FOREIGN KEY (`software_key_id`) REFERENCES `software_keys`(`software_key_id`) ON DELETE CASCADE, + FOREIGN KEY (`contact_id`) REFERENCES `contacts`(`contact_id`) ON DELETE CASCADE + )"); + + // Software Key Assignments to Assets + mysqli_query($mysqli, "CREATE TABLE `software_key_asset_assignments` ( + `software_key_id` INT(11) NOT NULL, + `asset_id` INT(11) NOT NULL, + `software_key_assigned_at` DATETIME DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`software_key_id`, `asset_id`), + FOREIGN KEY (`software_key_id`) REFERENCES `software_keys`(`software_key_id`) ON DELETE CASCADE, + FOREIGN KEY (`asset_id`) REFERENCES `assets`(`asset_id`) ON DELETE CASCADE + )"); + + mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.3.5'"); + } + + if (CURRENT_DATABASE_VERSION == '2.3.5') { + mysqli_query($mysqli, "ALTER TABLE `settings` CHANGE `config_smtp_provider` `config_smtp_provider` VARCHAR(200) DEFAULT NULL"); + mysqli_query($mysqli, "ALTER TABLE `settings` CHANGE `config_imap_provider` `config_imap_provider` VARCHAR(200) DEFAULT NULL"); + 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 // // Then, update the database to the next sequential version - // mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.2.0'"); + // mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '2.3.6'"); // } } else { // Up-to-date } - diff --git a/admin_debug.php b/admin/debug.php similarity index 99% rename from admin_debug.php rename to admin/debug.php index edd3cb9c..bab12f91 100644 --- a/admin_debug.php +++ b/admin/debug.php @@ -1,8 +1,8 @@ 'curl', 'php-mbstring' => 'mbstring', 'php-gd' => 'gd', + 'php-zip' => 'zip', ]; foreach ($extensions as $name => $ext) { @@ -245,7 +246,7 @@ $filePermissions[] = [ $uploadsStats = []; // Define the uploads directory path -$uploadsDir = __DIR__ . '/uploads'; // Adjust the path if needed +$uploadsDir = __DIR__ . '/../uploads'; // Adjust the path if needed if (is_dir($uploadsDir)) { // Function to recursively count files and calculate total size @@ -348,7 +349,7 @@ if ($tablesResult) { $dbComparison = []; // Path to the db.sql file -$dbSqlFile = __DIR__ . '/db.sql'; +$dbSqlFile = __DIR__ . '/../db.sql'; if (file_exists($dbSqlFile)) { // Read the db.sql file @@ -765,5 +766,5 @@ $mysqli->close(); - +
@@ -93,12 +93,9 @@ - + - - + + + Client Portal SSO via Microsoft Entra
- +
@@ -55,4 +55,4 @@ require_once "includes/inc_all_admin.php";
-Tell your admin: Your role does not have admin access."); +} +require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/header.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/top_nav.php'; +require_once 'includes/side_nav.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/inc_wrapper.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/inc_alert_feedback.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/filter_header.php'; +require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/app_version.php'; diff --git a/includes/admin_side_nav.php b/admin/includes/side_nav.php similarity index 50% rename from includes/admin_side_nav.php rename to admin/includes/side_nav.php index f1b870e0..89b06517 100644 --- a/includes/admin_side_nav.php +++ b/admin/includes/side_nav.php @@ -1,6 +1,6 @@
- + - + + \ No newline at end of file diff --git a/admin/modals/ai/ai_model_edit.php b/admin/modals/ai/ai_model_edit.php new file mode 100644 index 00000000..87c5bbc2 --- /dev/null +++ b/admin/modals/ai/ai_model_edit.php @@ -0,0 +1,90 @@ + + + +
+ + + + + +
+ + + diff --git a/admin/modals/ai/ai_provider_edit.php b/admin/modals/ai/ai_provider_edit.php new file mode 100644 index 00000000..ee1ed12d --- /dev/null +++ b/admin/modals/ai/ai_provider_edit.php @@ -0,0 +1,69 @@ + + + +
+ + + + + +
+ +