diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index 21cf847c..84329956 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1 +1 @@
-custom: ["https://donate.itflow.org"]
+custom: ["https://services.itflow.org"]
diff --git a/.htaccess b/.htaccess
new file mode 100644
index 00000000..f38dbabc
--- /dev/null
+++ b/.htaccess
@@ -0,0 +1,2 @@
+# Prevent access to .git, .github, and config.php
+RedirectMatch 404 ^/(\.git|\.github|config\.php)
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 00000000..385e5534
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,116 @@
+# Changelog
+
+This file documents all notable changes made to ITFlow.
+
+## [25.02]
+### Fixed
+- Migrated several reports to the new permissions/roles system
+- Resolved issue with empty task box showing for closed/resolved tickets
+- Corrected ticket priority sorting
+- Cloned asset interfaces when transferring assets between clients
+
+### Added / Changed
+- Restored max number of records per page option back to 500 since we dont have repeating modals.
+- Bulk Categorize Tickets feature
+- Renamed "Interface port" to "Interface Description." "Interface Name" should now refer to port name and/or number
+- Changed "Transfer Asset to Client" from a single action to a bulk action
+- Updated Filter Footer UI to show "Showing x to x of x records" instead of just the total records
+- Added Client Overview section to view client assets, contacts, licenses, credentials, etc.
+- Introduced Quick Peek for asset details, contact information, and document viewing throughout the ITFlow App, all made possible by AJAX
+- Enabled Simple Drag-and-Drop Ordering for Invoices, Recurring Invoices, Quotes, Ticket Tasks, and Ticket Template Tasks
+- Added new Ticket View options: Kanban and Simple View
+- Migrated all repeating modals to the new AJAX modal function for faster loading times and quicker development
+- Allowed clients to upload PDF documents to accepted quotes
+- Client Portal now shows ticket category
+- Custom links can now be added to the Client Portal navbar
+- Lots of little tweaks to UI, performance, bugs, etc.
+
+### Breaking Changes
+- Cron scripts have officially been moved to the /scripts folder and are no longer in the root directory; they must be updated to function properly
+
+## [25.01.3]
+### Fixed
+- Fixed ticket assignment modal showing client contacts.
+
+## [25.01.2]
+### Fixed
+- Fixed app version.
+
+## [25.01.1]
+
+### Added / Changed
+- Redesigned the Multi-Factor Authentication (MFA) Setup and Enforcement Flow UI/UX for a more intuitive user experience.
+- Added a "Member" column in the user roles listing for improved visibility.
+- General UI/UX improvements, along with minor performance optimizations and cleanups.
+
+### Fixed
+- Fixed an issue where Stripe was not appearing as a recurring payment option.
+- Corrected inaccurate Quarter 2 Expense results in the Profit & Loss Report.
+- Resolved TOTP code not displaying correctly on hover in the Contact or Asset Details sections.
+- Archived contacts no longer appear in the Bulk Mail section.
+- Fixed an issue where the Ticket Assign Modal was showing both ITFlow and client users.
+- Fixed issue with login key redirecting to legacy client portal page.
+
+## [25.01]
+
+### Added / Changed
+- Added support for saving cards in Stripe for automatic invoice payments.
+- Page titles now display detailed information (e.g., page name, client selection, company name, ticket and invoice info) for easier multi-tab navigation.
+- Reintroduced the new admin role-check for admin pages.
+- Admin roles can now be archived.
+- Debug mode now shows the current Git branch.
+- The auto-acknowledgment email for email-parsed tickets now includes a guest link.
+- Recurring tickets no longer require a contact.
+- Stripe online payment setup now prompts you to set the income/expense account.
+- New cron/CLI scripts have been moved to the `/scripts` subfolder — remember to update your cron configurations!
+- Moved modal includes to `/modals` to tidy up the root directory.
+- Moved most include files to `/includes` to improve directory structure.
+- Moved guest pages to `/guest` for better organization.
+- Renamed the include file `pagination.php` to `filter_footer.php`, as it is used in conjunction with `filter_header.php` for page filtering.
+- Guest ticket feedback now shows the ticket prefix and number, not just the ID.
+- Individual POST handler logic pages are no longer directly accessible.
+- Added the ability to delete payments on the Payments and Client Payments pages.
+- Implemented domain history tracking.
+- Added Asset Interface Linking/Connections to show what interface is connected to which interface port of another asset.
+- Added Force Recurring Ticket option in more locations, not just for recurring tickets.
+- Implemented row spanning and centered devices that occupy multiple units in a rack.
+- Added tooltips to main navigation badge counts to clarify what is being counted.
+- Reduced max records per page from 500 to 100 to prevent performance issues.
+- Updated several plugins:
+ - `stripe-php` from 10.5.0 to 16.4.0
+ - `Inputmask` from 5.0.8 to 5.0.9
+ - `DataTables` from 2.1.8 to 2.2.1
+ - `pdfmake` from 0.2.8 to 0.2.18
+ - `php-mime-mail-parser` to 9.0.1
+ - `TinyMCE` from 7.5.1 to 7.6.1
+- Removed unused libraries from the vendor folder and moved Stripe to the plugins folder, eliminating the vendor folder.
+- Merged the MFA TOTP functionality files `base32static.php` and `rfc6238.php` into a single file (`totp`) and moved it to the plugins folder.
+- No longer need to pass the DB connection (`$mysqli`) to the `addToMailQueue` function.
+- Disabled HTML Purifier caching.
+- Replaced the `nullable_htmlentities` function with `htmlspecialchars`.
+- Updated filter variable naming.
+- Implemented other minor UI updates, performance optimizations, and directory cleanups.
+
+### Fixed
+- Fixed an issue where the ticket edit modal didn't show multi-client or no-client projects.
+- Fixed asset interface losing DHCP settings.
+- Fixed a 500 error when creating or editing recurring expenses due to an incorrect variable name.
+- Fixed tickets created via the portal/email not being marked as billable.
+- Fixed issues with editing recurring expenses.
+- Resolved a regression where the TinyMCE editor didn’t display when adding or editing ticket templates.
+- Fixed a TinyMCE license issue.
+
+### Removed / Deprecated
+- Deprecated the cron scripts in the root directory. Cron jobs should now use the ones in the `/scripts` subfolder, which no longer require a cron key and must be run via CLI.
+
+### BREAKING CHANGES
+- The client portal has been moved from `/portal` to `/client`:
+ - Links in previous emails will be broken.
+ - The Azure Entra ID SSO Redirect URI needs to be updated to `/client`.
+ - You may need to update other links (e.g., website, support page).
+- Guest links have been moved from `/` to `/guest`. Previous links will be broken.
+
+## [24.12]
+
+### Added / Changed
+- Introduced versioned releases for the first time!
\ No newline at end of file
diff --git a/README.md b/README.md
index 6c1791cc..4863620d 100644
--- a/README.md
+++ b/README.md
@@ -3,15 +3,10 @@
[![Contributors][contributors-shield]][contributors-url]
[![Stargazers][stars-shield]][stars-url]
-[![Issues][issues-shield]][issues-url]
[![Commits][commit-shield]][commit-url]
[![GPL License][license-shield]][license-url]
-
-
diff --git a/admin_legacy_debug.php b/admin_legacy_debug.php
deleted file mode 100644
index 61591900..00000000
--- a/admin_legacy_debug.php
+++ /dev/null
@@ -1,335 +0,0 @@
- $count,
- 'size' => $size
- ];
-}
-
-// Function to compare two arrays recursively and return the differences
-function arrayDiffRecursive($array1, $array2) {
- $diff = array();
-
- foreach ($array1 as $key => $value) {
- if (is_array($value)) {
- if (!isset($array2[$key]) || !is_array($array2[$key])) {
- $diff[$key] = $value;
- } else {
- $recursiveDiff = arrayDiffRecursive($value, $array2[$key]);
- if (!empty($recursiveDiff)) {
- $diff[$key] = $recursiveDiff;
- }
- }
- } else {
- if (!isset($array2[$key]) || $array2[$key] !== $value) {
- $diff[$key] = $value;
- }
- }
- }
-
- return $diff;
-}
-
-// Function to load the table structures from an SQL dump file URL
-function loadTableStructuresFromSQLDumpURL($fileURL) {
- $context = stream_context_create(array('http' => array('header' => 'Accept: application/octet-stream')));
- $fileContent = file_get_contents($fileURL, false, $context);
-
- if ($fileContent === false) {
- return null;
- }
-
- $structure = array();
- $queries = explode(";", $fileContent);
-
- foreach ($queries as $query) {
- $query = trim($query);
-
- if (!empty($query)) {
- if (preg_match("/^CREATE TABLE `(.*)` \((.*)\)$/s", $query, $matches)) {
- $tableName = $matches[1];
- $tableStructure = $matches[2];
- $structure[$tableName] = array('structure' => $tableStructure);
- }
- }
- }
-
- return $structure;
-}
-
-// Function to fetch the database structure from the MySQL server
-function fetchDatabaseStructureFromServer() {
-
- global $mysqli;
-
- $tables = array();
-
- // Fetch table names
- $result = $mysqli->query("SHOW TABLES");
-
- if ($result->num_rows > 0) {
- while ($row = $result->fetch_row()) {
- $tableName = $row[0];
- $tables[$tableName] = array();
- }
- }
-
- // Fetch table structures
- foreach ($tables as $tableName => &$table) {
- $result = $mysqli->query("SHOW CREATE TABLE `$tableName`");
-
- if ($result->num_rows > 0) {
- $row = $result->fetch_row();
- $table['structure'] = $row[1];
- }
- }
-
- return $tables;
-}
-
-//function to get current crontab and return it as an array
-function get_crontab() {
- $crontab = shell_exec('crontab -l');
- $crontab = explode(PHP_EOL, $crontab);
- return $crontab;
-}
-
-// URL to the SQL dump file
-$fileURL = "https://raw.githubusercontent.com/itflow-org/itflow/master/db.sql";
-
-// Load the desired table structures from the SQL dump file URL
-$desiredStructure = loadTableStructuresFromSQLDumpURL($fileURL);
-
-if ($desiredStructure === null) {
- die("Failed to load the desired table structures from the SQL dump file URL.");
-}
-
-// Fetch the current database structure from the MySQL server
-$currentStructure = fetchDatabaseStructureFromServer();
-
-if ($currentStructure === null) {
- die("Failed to fetch the current database structure from the server.");
-}
-
-// Compare the structures and display the differences
-$differences = arrayDiffRecursive($desiredStructure, $currentStructure);
-
-//DB Stats
-// Query to fetch the number of tables
-$tablesQuery = "SHOW TABLES";
-$tablesResult = $mysqli->query($tablesQuery);
-
-$numTables = $tablesResult->num_rows;
-$numFields = 0;
-$numRows = 0;
-
-// Loop through each table
-while ($row = $tablesResult->fetch_row()) {
- $tableName = $row[0];
-
- // Query to fetch the number of fields
- $fieldsQuery = "DESCRIBE `$tableName`";
- $fieldsResult = $mysqli->query($fieldsQuery);
-
- // Check if the query was successful
- if ($fieldsResult) {
- $numFields += $fieldsResult->num_rows;
-
- // Query to fetch the number of rows
- $rowsQuery = "SELECT COUNT(*) FROM `$tableName`";
- $rowsResult = $mysqli->query($rowsQuery);
-
- // Check if the query was successful
- if ($rowsResult) {
- $numRows += $rowsResult->fetch_row()[0];
- } else {
- echo "Error executing query: " . $mysqli->error;
- }
- } else {
- echo "Error executing query: " . $mysqli->error;
- }
-}
-
-//Get loaded PHP modules
-$loadedModules = get_loaded_extensions();
-
-//Get Server Info / Service versions
-$phpVersion = phpversion();
-$databaseInfo = mysqli_get_server_info($mysqli) . " / " . $mysqli->server_version;
-$operatingSystem = php_uname();
-$webServer = $_SERVER['SERVER_SOFTWARE'];
-$errorLog = ini_get('error_log') ?: "Debian/Ubuntu default is usually /var/log/apache2/error.log";
-$updates = fetchUpdates();
-
-?>
-
-
-
-
Debug
-
-
-
-
Debugging
-
-
If you are experiencing a problem with ITFlow you may be directed to this page to gather server/app info.
-
When creating forum posts / support requests ensure you share the information under Server Info, ITFlow app and Database stats.
-
Caution: Be careful when sharing the full debug output - it contains your PHP session variables/cookies ("PHPSESSID") which could allow anyone to login to your ITFlow instance
-
Note: Sometimes you might need to gather PHP error logs as well
@@ -87,16 +80,32 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
$sql_role_user_count = mysqli_query($mysqli, "SELECT COUNT(users.user_id) FROM users LEFT JOIN user_settings on users.user_id = user_settings.user_id WHERE user_role = $role_id AND user_archived_at IS NULL");
$role_user_count = mysqli_fetch_row($sql_role_user_count)[0];
+ $sql_users = mysqli_query($mysqli, "SELECT * FROM users LEFT JOIN user_settings on users.user_id = user_settings.user_id WHERE user_role = $role_id AND user_archived_at IS NULL");
+ // Initialize an empty array to hold user names
+ $user_names = [];
+
+ // Fetch each row and store the user_name in the array
+ while($row = mysqli_fetch_assoc($sql_users)) {
+ $user_names[] = nullable_htmlentities($row['user_name']);
+ }
+
+ // Convert the array of user names to a comma-separated string
+ $user_names_string = implode(",", $user_names) ;
+
+ if (empty($user_names_string)) {
+ $user_names_string = "-";
+ }
+
?>
+ $row[login_uri]";
+ }
+ }
+
+ // Reset the $sql_assets pointer to the start
+ mysqli_data_seek($sql_assets, 0);
+
+ // Show URLs linked to assets, that also have logins
+ while ($row = mysqli_fetch_array($sql_assets)) {
+ if (!empty($row['login_uri'])) {
+ echo "