From 670450bcfb25c91015c81cf55a178d81b9d5d6ad Mon Sep 17 00:00:00 2001 From: wrongecho Date: Wed, 23 Apr 2025 10:22:33 +0100 Subject: [PATCH 01/80] Ticket statuses - Allow ordering from admin settings, this can replace the need to move the Kanban columns --- admin_ticket_status.php | 16 ++++++++-------- ajax/ajax_custom_ticket_status_edit.php | 15 +++++++++++++-- post/admin/admin_ticket_status.php | 9 ++++++++- ticket.php | 2 +- tickets.php | 2 +- 5 files changed, 31 insertions(+), 13 deletions(-) diff --git a/admin_ticket_status.php b/admin_ticket_status.php index 6c687a0e..8b98dcff 100644 --- a/admin_ticket_status.php +++ b/admin_ticket_status.php @@ -1,7 +1,7 @@ Active"; } else { - $ticket_status_display = "
Disabled
"; + $ticket_status_display = "
Inactive
"; } ?> @@ -97,7 +97,6 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()")); - 5 ) { ?> - diff --git a/ajax/ajax_custom_ticket_status_edit.php b/ajax/ajax_custom_ticket_status_edit.php index 121b115a..c9a3ad16 100644 --- a/ajax/ajax_custom_ticket_status_edit.php +++ b/ajax/ajax_custom_ticket_status_edit.php @@ -8,6 +8,7 @@ $sql = mysqli_query($mysqli, "SELECT * FROM ticket_statuses WHERE ticket_status_ $row = mysqli_fetch_array($sql); $ticket_status_name = nullable_htmlentities($row['ticket_status_name']); $ticket_status_color = nullable_htmlentities($row['ticket_status_color']); +$ticket_status_order = intval($row['ticket_status_order']); $ticket_status_active = intval($row['ticket_status_active']); // Generate the HTML form content using output buffering. @@ -30,7 +31,7 @@ ob_start();
- + > @@ -44,6 +45,16 @@ ob_start(); +
+ +
+
+ +
+ +
+
+
@@ -52,7 +63,7 @@ ob_start();
diff --git a/post/admin/admin_ticket_status.php b/post/admin/admin_ticket_status.php index dce68dc1..861a8940 100644 --- a/post/admin/admin_ticket_status.php +++ b/post/admin/admin_ticket_status.php @@ -25,9 +25,10 @@ if (isset($_POST['edit_ticket_status'])) { $ticket_status_id = intval($_POST['ticket_status_id']); $name = sanitizeInput($_POST['name']); $color = sanitizeInput($_POST['color']); + $order = intval($_POST['order']); $status = intval($_POST['status']); - mysqli_query($mysqli, "UPDATE ticket_statuses SET ticket_status_name = '$name', ticket_status_color = '$color', ticket_status_active = $status WHERE ticket_status_id = $ticket_status_id"); + mysqli_query($mysqli, "UPDATE ticket_statuses SET ticket_status_name = '$name', ticket_status_color = '$color', ticket_status_order = $order, ticket_status_active = $status WHERE ticket_status_id = $ticket_status_id"); // Logging logAction("Ticket Status", "Edit", "$session_name edited custom ticket status $name", 0, $ticket_status_id); @@ -40,8 +41,14 @@ if (isset($_POST['edit_ticket_status'])) { if (isset($_GET['delete_ticket_status'])) { + validateCSRFToken($_GET['csrf_token']); + $ticket_status_id = intval($_GET['delete_ticket_status']); + if ($ticket_status_id <= 5) { + exit("Can't delete built-in statuses"); + } + // Get ticket status name for logging and notification $sql = mysqli_query($mysqli, "SELECT * FROM ticket_statuses WHERE ticket_status_id = $ticket_status_id"); $row = mysqli_fetch_array($sql); diff --git a/ticket.php b/ticket.php index 7f6fbcb5..8fb6c13c 100644 --- a/ticket.php +++ b/ticket.php @@ -676,7 +676,7 @@ if (isset($_GET['ticket_id'])) { if ($task_count !== $completed_task_count) { $status_snippet = "AND ticket_status_id != 4"; } - $sql_ticket_status = mysqli_query($mysqli, "SELECT * FROM ticket_statuses WHERE ticket_status_id != 1 AND ticket_status_id != 5 AND ticket_status_active = 1 $status_snippet"); + $sql_ticket_status = mysqli_query($mysqli, "SELECT * FROM ticket_statuses WHERE ticket_status_id != 1 AND ticket_status_id != 5 AND ticket_status_active = 1 $status_snippet ORDER BY ticket_status_order"); while ($row = mysqli_fetch_array($sql_ticket_status)) { $ticket_status_id_select = intval($row['ticket_status_id']); $ticket_status_name_select = nullable_htmlentities($row['ticket_status_name']); ?> diff --git a/tickets.php b/tickets.php index 91a0b428..a677beab 100644 --- a/tickets.php +++ b/tickets.php @@ -368,7 +368,7 @@ $sql_categories = mysqli_query( + Up to 20 files can be uploaded at once by holding down CTRL and selecting files diff --git a/post/user/file.php b/post/user/file.php index 44576f7e..9debb0fe 100644 --- a/post/user/file.php +++ b/post/user/file.php @@ -29,7 +29,8 @@ if (isset($_POST['upload_files'])) { 'jpg', 'jpeg', 'gif', 'png', 'webp', 'pdf', 'txt', 'md', 'doc', 'docx', 'odt', 'csv', 'xls', 'xlsx', 'ods', 'pptx', 'odp', 'zip', 'tar', 'gz', 'xml', 'msg', 'json', 'wav', 'mp3', 'ogg', 'mov', 'mp4', 'av1', 'ovpn', - 'cfg', 'ps1', 'vsdx', 'drawio', 'pfx', 'pages', 'numbers', 'unf', 'key' + 'cfg', 'ps1', 'vsdx', 'drawio', 'pfx', 'pages', 'numbers', 'unf', 'key', + 'bat', 'stk' ]; // Loop through each uploaded file @@ -138,7 +139,7 @@ if (isset($_POST['upload_files'])) { } // Resize image - imagecopyresampled($optimized_img, $src_img, 0, 0, 0, 0, + imagecopyresampled($optimized_img, $src_img, 0, 0, 0, 0, $preview_new_width, $preview_new_height, $orig_width, $orig_height); // Define WebP file path @@ -293,7 +294,7 @@ if (isset($_POST['delete_file'])) { $file_reference_name = sanitizeInput($row['file_reference_name']); $file_has_thumbnail = intval($row['file_has_thumbnail']); $file_has_preview = intval($row['file_has_preview']); - + unlink("uploads/clients/$client_id/$file_reference_name"); if ($file_has_thumbnail == 1) { @@ -325,7 +326,7 @@ if (isset($_POST['bulk_delete_files'])) { // Get selected file Count $file_count = count($_POST['file_ids']); - + foreach($_POST['file_ids'] as $file_id) { $file_id = intval($file_id); @@ -381,7 +382,7 @@ if (isset($_POST['bulk_move_files'])) { if (isset($_POST['file_ids'])) { // Get Selected file Count $file_count = count($_POST['file_ids']); - + // Move Documents to Folder Loop foreach($_POST['file_ids'] as $file_id) { $file_id = intval($file_id); From 7a7ac4a47f18ff6f656b4cd23f817dee38d3e05e Mon Sep 17 00:00:00 2001 From: johnnyq Date: Tue, 6 May 2025 13:16:56 -0400 Subject: [PATCH 04/80] Fix Edit Blank Recurring ticket in Asset Details --- asset_details.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/asset_details.php b/asset_details.php index 066f1eec..8c2fe149 100644 --- a/asset_details.php +++ b/asset_details.php @@ -94,7 +94,7 @@ if (isset($_GET['asset_id'])) { $ticket_count = mysqli_num_rows($sql_related_tickets); // Related Recurring Tickets Query - $sql_related_recurring_tickets = mysqli_query($mysqli, "SELECT * FROM recurring_tickets + $sql_related_recurring_tickets = mysqli_query($mysqli, "SELECT recurring_tickets.* FROM recurring_tickets LEFT JOIN recurring_ticket_assets ON recurring_tickets.recurring_ticket_id = recurring_ticket_assets.recurring_ticket_id WHERE recurring_ticket_asset_id = $asset_id OR recurring_ticket_assets.asset_id = $asset_id GROUP BY recurring_tickets.recurring_ticket_id From 58435d3460144194bc48be22b24b5c2a4ce31e0b Mon Sep 17 00:00:00 2001 From: johnnyq Date: Tue, 6 May 2025 17:18:54 -0400 Subject: [PATCH 05/80] Add Next Button if Database is already configured --- setup.php | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/setup.php b/setup.php index 0b46651c..0f762c85 100644 --- a/setup.php +++ b/setup.php @@ -6,7 +6,6 @@ if (file_exists("config.php")) { } include "functions.php"; - include "includes/database_version.php"; @@ -855,10 +854,19 @@ if (isset($_POST['add_telemetry'])) {

Step 2 - Connect your Database

- - Database is already configured. Any further changes should be made by editing the config.php file, - or deleting it and refreshing this page. - + Database is already configured. Any further changes should be made by editing the config.php file.

"; + + if (@$mysqli) { + echo "Next Step (User Setup) "; + } else { + echo "
Database connection failed. Check config.php.
"; + } + + } else { + ?>
Database Connection Details
From 80625f8c3fccd6cf75aef14e07f74ab898c8d5f6 Mon Sep 17 00:00:00 2001 From: johnnyq Date: Tue, 6 May 2025 19:36:31 -0400 Subject: [PATCH 06/80] Remove Unused vars in setup, added jpeg to the allow extension for user avatars --- setup.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/setup.php b/setup.php index 0f762c85..28558e94 100644 --- a/setup.php +++ b/setup.php @@ -145,7 +145,7 @@ if (isset($_POST['add_user'])) { $new_file_name = md5(time() . $file_name) . '.' . $file_extension; // check if file has one of the following extensions - $allowed_file_extensions = array('jpg', 'gif', 'png'); + $allowed_file_extensions = array('jpg', 'jpeg', 'gif', 'png', 'webp'); if (in_array($file_extension,$allowed_file_extensions) === false) { $file_error = 1; @@ -194,9 +194,6 @@ if (isset($_POST['add_company_settings'])) { $phone = preg_replace("/[^0-9]/", '',$_POST['phone']); $email = sanitizeInput($_POST['email']); $website = sanitizeInput($_POST['website']); - $locale = sanitizeInput($_POST['locale']); - $currency_code = sanitizeInput($_POST['currency_code']); - $timezone = sanitizeInput($_POST['timezone']); mysqli_query($mysqli,"INSERT INTO companies SET company_name = '$name', company_address = '$address', company_city = '$city', company_state = '$state', company_zip = '$zip', company_country = '$country', company_phone = '$phone', company_email = '$email', company_website = '$website', company_locale = '$locale', company_currency = '$currency_code'"); From b943c9cd8976ee6c4b1b8ce548d1549ec4694ab5 Mon Sep 17 00:00:00 2001 From: johnnyq Date: Tue, 6 May 2025 19:50:51 -0400 Subject: [PATCH 07/80] Remove Influencer as a referral --- setup.php | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.php b/setup.php index 28558e94..0ea74101 100644 --- a/setup.php +++ b/setup.php @@ -283,7 +283,6 @@ if (isset($_POST['add_company_settings'])) { mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Event', category_type = 'Referral', category_color = 'red'"); mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Affiliate', category_type = 'Referral', category_color = 'pink'"); mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Client', category_type = 'Referral', category_color = 'lightblue'"); - mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Influencer', category_type = 'Referral', category_color = 'turquoise'"); // Payment Methods mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Cash', category_type = 'Payment Method', category_color = 'blue'"); From 241ec50802bd292458ab967cc2e7fa20a317dda6 Mon Sep 17 00:00:00 2001 From: johnnyq Date: Wed, 7 May 2025 14:32:51 -0400 Subject: [PATCH 08/80] Add hidden option to restore dumped ITFlow Database during Setup --- admin_backup.php | 1 + post/admin/admin_backup.php | 49 +++++++++++++++++++++++++ setup.php | 73 +++++++++++++++++++++++++++++++++++++ 3 files changed, 123 insertions(+) diff --git a/admin_backup.php b/admin_backup.php index 841ab95d..3fa4dfa4 100644 --- a/admin_backup.php +++ b/admin_backup.php @@ -9,6 +9,7 @@ require_once "includes/inc_all_admin.php";
If you are unable to back up the entire VM, you'll need to back up the files & database individually. There is no built-in restore. See the docs here.


Download database
+

Download Uploads
diff --git a/post/admin/admin_backup.php b/post/admin/admin_backup.php index 7ad9fde8..1ea20c4a 100644 --- a/post/admin/admin_backup.php +++ b/post/admin/admin_backup.php @@ -71,6 +71,54 @@ if (isset($_GET['download_database'])) { exit; } +if (isset($_GET['download_uploads'])) { + validateCSRFToken($_GET['csrf_token']); + + function zipFolder($folderPath, $zipFilePath) { + $zip = new ZipArchive(); + if ($zip->open($zipFilePath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== TRUE) { + die("Cannot open <$zipFilePath>\n"); + } + + $folderPath = realpath($folderPath); + + $files = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($folderPath), + RecursiveIteratorIterator::LEAVES_ONLY + ); + + foreach ($files as $name => $file) { + if (!$file->isDir()) { + $filePath = $file->getRealPath(); + $relativePath = substr($filePath, strlen($folderPath) + 1); + $zip->addFile($filePath, $relativePath); + } + } + + $zip->close(); + } + + $uploadDir = 'uploads'; + $zipFile = 'uploads.zip'; + + zipFolder($uploadDir, $zipFile); + + // Trigger file download + if (file_exists($zipFile)) { + header('Content-Type: application/zip'); + header('Content-Disposition: attachment; filename="' . basename($zipFile) . '"'); + header('Content-Length: ' . filesize($zipFile)); + flush(); + readfile($zipFile); + unlink($zipFile); // Optional: delete after download + exit; + } + + logAction("Uploads", "Download", "$session_name downloaded the uploads folder."); + +} + + if (isset($_POST['backup_master_key'])) { validateCSRFToken($_POST['csrf_token']); @@ -104,3 +152,4 @@ if (isset($_POST['backup_master_key'])) { header("Location: " . $_SERVER["HTTP_REFERER"]); } } + diff --git a/setup.php b/setup.php index 0ea74101..1674cedd 100644 --- a/setup.php +++ b/setup.php @@ -108,6 +108,59 @@ if (isset($_POST['add_database'])) { } +if (isset($_POST['restore_database'])) { + + if (isset($_FILES["sql_file"])) { + + // Drop all existing tables + mysqli_query($mysqli, "SET foreign_key_checks = 0"); + $tables = mysqli_query($mysqli, "SHOW TABLES"); + while ($row = mysqli_fetch_array($tables)) { + mysqli_query($mysqli, "DROP TABLE IF EXISTS `" . $row[0] . "`"); + } + mysqli_query($mysqli, "SET foreign_key_checks = 1"); + + + $file = $_FILES["sql_file"]; + $filename = $file["name"]; + $tempPath = $file["tmp_name"]; + + $fileExt = pathinfo($filename, PATHINFO_EXTENSION); + if (strtolower($fileExt) !== "sql") { + die("Only .sql files are allowed."); + } + + // Save uploaded file temporarily + $destination = "temp_" . time() . ".sql"; + if (!move_uploaded_file($tempPath, $destination)) { + die("Failed to upload the file."); + } + + $command = sprintf( + 'mysql -h%s -u%s -p%s %s < %s', + escapeshellarg($dbhost), + escapeshellarg($dbusername), + escapeshellarg($dbpassword), + escapeshellarg($database), + escapeshellarg($destination) + ); + + exec($command, $output, $returnCode); + unlink($destination); // cleanup + + if ($returnCode === 0) { + echo "SQL file imported successfully!"; + } else { + echo "Import failed. Error code: $returnCode"; + } + } + + $_SESSION['alert_message'] = "Database imported successfully"; + + //header("Location: login.php"); + exit; +} + if (isset($_POST['add_user'])) { $user_count = mysqli_num_rows(mysqli_query($mysqli,"SELECT COUNT(*) FROM users")); if ($user_count < 0) { @@ -922,6 +975,26 @@ if (isset($_POST['add_telemetry'])) { + + +
+
+

Step 2.5 - Restore your Database

+
+
+ +
Upload SQL File to Import into DB
+ + + +
+ + +
+
+
From 069772f27de5d8407444203c5abd4667f1477e65 Mon Sep 17 00:00:00 2001 From: johnnyq Date: Wed, 7 May 2025 14:54:25 -0400 Subject: [PATCH 09/80] Add Upload uploads.zip file to restore files as well --- setup.php | 81 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 66 insertions(+), 15 deletions(-) diff --git a/setup.php b/setup.php index 1674cedd..dec0b6c5 100644 --- a/setup.php +++ b/setup.php @@ -108,8 +108,9 @@ if (isset($_POST['add_database'])) { } -if (isset($_POST['restore_database'])) { +if (isset($_POST['restore'])) { + // === 1. Restore SQL Dump === if (isset($_FILES["sql_file"])) { // Drop all existing tables @@ -120,7 +121,6 @@ if (isset($_POST['restore_database'])) { } mysqli_query($mysqli, "SET foreign_key_checks = 1"); - $file = $_FILES["sql_file"]; $filename = $file["name"]; $tempPath = $file["tmp_name"]; @@ -133,7 +133,7 @@ if (isset($_POST['restore_database'])) { // Save uploaded file temporarily $destination = "temp_" . time() . ".sql"; if (!move_uploaded_file($tempPath, $destination)) { - die("Failed to upload the file."); + die("Failed to upload the SQL file."); } $command = sprintf( @@ -148,16 +148,62 @@ if (isset($_POST['restore_database'])) { exec($command, $output, $returnCode); unlink($destination); // cleanup - if ($returnCode === 0) { - echo "SQL file imported successfully!"; - } else { - echo "Import failed. Error code: $returnCode"; + if ($returnCode !== 0) { + die("SQL import failed. Error code: $returnCode"); } } - $_SESSION['alert_message'] = "Database imported successfully"; + // === 2. Restore Upload Folder from ZIP === + if (isset($_FILES["upload_zip"])) { + $uploadDir = __DIR__ . "/uploads/"; - //header("Location: login.php"); + $zipFile = $_FILES["upload_zip"]; + $zipName = basename($zipFile["name"]); + $zipExt = strtolower(pathinfo($zipName, PATHINFO_EXTENSION)); + + if ($zipExt !== "zip") { + die("Only .zip files are allowed for upload restore."); + } + + $tempZip = "upload_restore_" . time() . ".zip"; + if (!move_uploaded_file($zipFile["tmp_name"], $tempZip)) { + die("Failed to upload the zip file."); + } + + $zip = new ZipArchive; + if ($zip->open($tempZip) === TRUE) { + // Clear existing upload folder + foreach (glob($uploadDir . '*') as $file) { + if (is_dir($file)) { + $files = array_diff(scandir($file), array('.', '..')); + foreach ($files as $subfile) { + unlink("$file/$subfile"); + } + rmdir($file); + } else { + unlink($file); + } + } + + // Extract new files + $zip->extractTo($uploadDir); + $zip->close(); + unlink($tempZip); // cleanup + } else { + unlink($tempZip); + die("Failed to open zip file."); + } + } + + // === 3. Final Setup Stages === + $myfile = fopen("config.php", "a"); + $txt = "\$config_enable_setup = 0;\n\n"; + fwrite($myfile, $txt); + fclose($myfile); + + $_SESSION['alert_message'] = "Database and uploads restored successfully"; + + // header("Location: login.php"); exit; } @@ -975,20 +1021,25 @@ if (isset($_POST['add_telemetry'])) {
- +
-

Step 2.5 - Restore your Database

+

Step 2.5 - Restore from Backup

- -
Upload SQL File to Import into DB
-
+
Upload SQL File to Import into DB
+
-
From 2ffb2be083b76ef52b5908704a8360b2f928e2bb Mon Sep 17 00:00:00 2001 From: johnnyq Date: Wed, 7 May 2025 15:37:57 -0400 Subject: [PATCH 10/80] Update the backup code to be a full backup zip file download of uploads and db dump along with version meta data file. Also allow to restore a single file in setup currently hidden --- admin_backup.php | 3 +- post/admin/admin_backup.php | 125 +++++++++++++++++--------------- setup.php | 140 +++++++++++++++++++----------------- 3 files changed, 142 insertions(+), 126 deletions(-) diff --git a/admin_backup.php b/admin_backup.php index 3fa4dfa4..63f3566e 100644 --- a/admin_backup.php +++ b/admin_backup.php @@ -8,8 +8,7 @@ require_once "includes/inc_all_admin.php";
If you are unable to back up the entire VM, you'll need to back up the files & database individually. There is no built-in restore. See the docs here.
-

Download database
-

Download Uploads
+

Download Backup
diff --git a/post/admin/admin_backup.php b/post/admin/admin_backup.php index 1ea20c4a..11ec83a6 100644 --- a/post/admin/admin_backup.php +++ b/post/admin/admin_backup.php @@ -6,28 +6,24 @@ defined('FROM_POST_HANDLER') || die("Direct file access is not allowed"); -if (isset($_GET['download_database'])) { - validateCSRFToken($_GET['csrf_token']); +require_once "includes/app_version.php"; +if (isset($_GET['download_backup'])) { + validateCSRFToken($_GET['csrf_token']); global $mysqli, $database; - $backupFileName = date('Y-m-d_H-i-s') . '_backup.sql'; + $timestamp = date('YmdHis'); + $baseName = "itflow_$timestamp"; + $sqlFile = "$baseName.sql"; + $uploadsZip = "$baseName_uploads.zip"; + $finalZip = "$baseName.zip"; + $versionFile = "version.txt"; - header('Content-Type: application/sql'); - header('Content-Disposition: attachment; filename="' . $backupFileName . '"'); - header('Cache-Control: no-store, no-cache, must-revalidate'); - header('Pragma: no-cache'); - header('Expires: 0'); + // === 1. Generate SQL Dump === + $sqlContent = "-- UTF-8 + Foreign Key Safe Dump\n"; + $sqlContent .= "SET NAMES 'utf8mb4';\n"; + $sqlContent .= "SET foreign_key_checks = 0;\n\n"; - if (ob_get_level()) ob_end_clean(); - flush(); - - // Start of dump file — charset declaration - echo "-- UTF-8 + Foreign Key Safe Dump\n"; - echo "SET NAMES 'utf8mb4';\n"; - echo "SET foreign_key_checks = 0;\n\n"; - - // Get all tables $tables = []; $res = $mysqli->query("SHOW TABLES"); while ($row = $res->fetch_row()) { @@ -35,59 +31,47 @@ if (isset($_GET['download_database'])) { } foreach ($tables as $table) { - // Table structure $createRes = $mysqli->query("SHOW CREATE TABLE `$table`"); $createRow = $createRes->fetch_assoc(); $createSQL = array_values($createRow)[1]; - echo "\n-- ----------------------------\n"; - echo "-- Table structure for `$table`\n"; - echo "-- ----------------------------\n"; - echo "DROP TABLE IF EXISTS `$table`;\n"; - echo $createSQL . ";\n\n"; + $sqlContent .= "\n-- ----------------------------\n"; + $sqlContent .= "-- Table structure for `$table`\n"; + $sqlContent .= "-- ----------------------------\n"; + $sqlContent .= "DROP TABLE IF EXISTS `$table`;\n"; + $sqlContent .= $createSQL . ";\n\n"; - // Table data $dataRes = $mysqli->query("SELECT * FROM `$table`"); if ($dataRes->num_rows > 0) { - echo "-- Dumping data for table `$table`\n"; + $sqlContent .= "-- Dumping data for table `$table`\n"; while ($row = $dataRes->fetch_assoc()) { $columns = array_map(fn($col) => '`' . $mysqli->real_escape_string($col) . '`', array_keys($row)); $values = array_map(function ($val) use ($mysqli) { - if (is_null($val)) return "NULL"; - return "'" . $mysqli->real_escape_string($val) . "'"; + return is_null($val) ? "NULL" : "'" . $mysqli->real_escape_string($val) . "'"; }, array_values($row)); - - echo "INSERT INTO `$table` (" . implode(", ", $columns) . ") VALUES (" . implode(", ", $values) . ");\n"; + $sqlContent .= "INSERT INTO `$table` (" . implode(", ", $columns) . ") VALUES (" . implode(", ", $values) . ");\n"; } - echo "\n"; + $sqlContent .= "\n"; } } - //FINAL STEP: Re-enable foreign key checks - echo "\nSET foreign_key_checks = 1;\n"; - - logAction("Database", "Download", "$session_name downloaded the database."); - $_SESSION['alert_message'] = "Database downloaded"; - exit; -} - -if (isset($_GET['download_uploads'])) { - validateCSRFToken($_GET['csrf_token']); + $sqlContent .= "SET foreign_key_checks = 1;\n"; + file_put_contents($sqlFile, $sqlContent); + // === 2. Create uploads.zip === function zipFolder($folderPath, $zipFilePath) { $zip = new ZipArchive(); if ($zip->open($zipFilePath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== TRUE) { - die("Cannot open <$zipFilePath>\n"); + die("Cannot open <$zipFilePath>"); } $folderPath = realpath($folderPath); - $files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator($folderPath), RecursiveIteratorIterator::LEAVES_ONLY ); - foreach ($files as $name => $file) { + foreach ($files as $file) { if (!$file->isDir()) { $filePath = $file->getRealPath(); $relativePath = substr($filePath, strlen($folderPath) + 1); @@ -98,27 +82,54 @@ if (isset($_GET['download_uploads'])) { $zip->close(); } - $uploadDir = 'uploads'; - $zipFile = 'uploads.zip'; + zipFolder("uploads", $uploadsZip); - zipFolder($uploadDir, $zipFile); + // === 3. Generate version.txt === + $commitHash = trim(shell_exec('git log -1 --format=%H')); + $gitBranch = trim(shell_exec('git rev-parse --abbrev-ref HEAD')); - // Trigger file download - if (file_exists($zipFile)) { - header('Content-Type: application/zip'); - header('Content-Disposition: attachment; filename="' . basename($zipFile) . '"'); - header('Content-Length: ' . filesize($zipFile)); - flush(); - readfile($zipFile); - unlink($zipFile); // Optional: delete after download - exit; + $versionContent = "ITFlow Backup Metadata\n"; + $versionContent .= "-----------------------------\n"; + $versionContent .= "Generated: " . date('Y-m-d H:i:s') . "\n"; + $versionContent .= "Backup File: $baseName.zip\n"; + $versionContent .= "Generated By: $session_name\n"; + $versionContent .= "Host: " . gethostname() . "\n"; + $versionContent .= "Git Branch: $gitBranch\n"; + $versionContent .= "Git Commit: $commitHash\n"; + $versionContent .= "ITFlow Version: " . (defined('APP_VERSION') ? APP_VERSION : 'Unknown') . "\n"; + $versionContent .= "Database Version: " . (defined('CURRENT_DATABASE_VERSION') ? CURRENT_DATABASE_VERSION : 'Unknown') . "\n"; + + file_put_contents($versionFile, $versionContent); + + // === 4. Combine into final .zip file === + $final = new ZipArchive(); + if ($final->open($finalZip, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== TRUE) { + die("Cannot create final backup zip."); } - logAction("Uploads", "Download", "$session_name downloaded the uploads folder."); + $final->addFile($sqlFile, "db.sql"); + $final->addFile($uploadsZip, "uploads.zip"); + $final->addFile($versionFile, "version.txt"); + $final->close(); + // Cleanup temp files before download + unlink($sqlFile); + unlink($uploadsZip); + unlink($versionFile); + + // === 5. Serve the zip for download === + header('Content-Type: application/zip'); + header('Content-Disposition: attachment; filename="' . $finalZip . '"'); + header('Content-Length: ' . filesize($finalZip)); + flush(); + readfile($finalZip); + unlink($finalZip); // remove final zip after serving + + logAction("System", "Backup Download", "$session_name downloaded full backup."); + $_SESSION['alert_message'] = "Full backup downloaded."; + exit; } - if (isset($_POST['backup_master_key'])) { validateCSRFToken($_POST['csrf_token']); diff --git a/setup.php b/setup.php index dec0b6c5..9d1a4661 100644 --- a/setup.php +++ b/setup.php @@ -110,10 +110,43 @@ if (isset($_POST['add_database'])) { if (isset($_POST['restore'])) { - // === 1. Restore SQL Dump === - if (isset($_FILES["sql_file"])) { + if (!isset($_FILES['backup_zip']) || $_FILES['backup_zip']['error'] !== UPLOAD_ERR_OK) { + die("No backup file uploaded or upload failed."); + } - // Drop all existing tables + $file = $_FILES['backup_zip']; + $fileExt = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); + if ($fileExt !== "zip") { + die("Only .zip files are allowed."); + } + + // Save uploaded file temporarily + $backupZip = "restore_" . time() . ".zip"; + if (!move_uploaded_file($file["tmp_name"], $backupZip)) { + die("Failed to save uploaded backup file."); + } + + $zip = new ZipArchive; + if ($zip->open($backupZip) !== TRUE) { + unlink($backupZip); + die("Failed to open backup zip file."); + } + + // Extract to a temp directory + $tempDir = "restore_temp_" . time(); + mkdir($tempDir); + + if (!$zip->extractTo($tempDir)) { + $zip->close(); + unlink($backupZip); + die("Failed to extract backup contents."); + } + $zip->close(); + unlink($backupZip); + + // === 1. Restore SQL Dump === + $sqlPath = "$tempDir/db.sql"; + if (file_exists($sqlPath)) { mysqli_query($mysqli, "SET foreign_key_checks = 0"); $tables = mysqli_query($mysqli, "SHOW TABLES"); while ($row = mysqli_fetch_array($tables)) { @@ -121,88 +154,68 @@ if (isset($_POST['restore'])) { } mysqli_query($mysqli, "SET foreign_key_checks = 1"); - $file = $_FILES["sql_file"]; - $filename = $file["name"]; - $tempPath = $file["tmp_name"]; - - $fileExt = pathinfo($filename, PATHINFO_EXTENSION); - if (strtolower($fileExt) !== "sql") { - die("Only .sql files are allowed."); - } - - // Save uploaded file temporarily - $destination = "temp_" . time() . ".sql"; - if (!move_uploaded_file($tempPath, $destination)) { - die("Failed to upload the SQL file."); - } - $command = sprintf( 'mysql -h%s -u%s -p%s %s < %s', escapeshellarg($dbhost), escapeshellarg($dbusername), escapeshellarg($dbpassword), escapeshellarg($database), - escapeshellarg($destination) + escapeshellarg($sqlPath) ); exec($command, $output, $returnCode); - unlink($destination); // cleanup - if ($returnCode !== 0) { die("SQL import failed. Error code: $returnCode"); } + } else { + die("Missing db.sql in the backup archive."); } - // === 2. Restore Upload Folder from ZIP === - if (isset($_FILES["upload_zip"])) { - $uploadDir = __DIR__ . "/uploads/"; + // === 2. Restore Upload Folder === + $uploadDir = __DIR__ . "/uploads/"; + $uploadsZip = "$tempDir/uploads.zip"; - $zipFile = $_FILES["upload_zip"]; - $zipName = basename($zipFile["name"]); - $zipExt = strtolower(pathinfo($zipName, PATHINFO_EXTENSION)); - - if ($zipExt !== "zip") { - die("Only .zip files are allowed for upload restore."); - } - - $tempZip = "upload_restore_" . time() . ".zip"; - if (!move_uploaded_file($zipFile["tmp_name"], $tempZip)) { - die("Failed to upload the zip file."); - } - - $zip = new ZipArchive; - if ($zip->open($tempZip) === TRUE) { - // Clear existing upload folder - foreach (glob($uploadDir . '*') as $file) { - if (is_dir($file)) { - $files = array_diff(scandir($file), array('.', '..')); - foreach ($files as $subfile) { - unlink("$file/$subfile"); - } - rmdir($file); + if (file_exists($uploadsZip)) { + $uploads = new ZipArchive; + if ($uploads->open($uploadsZip) === TRUE) { + // Clean existing uploads + foreach (glob($uploadDir . '*') as $item) { + if (is_dir($item)) { + array_map('unlink', glob("$item/*")); + rmdir($item); } else { - unlink($file); + unlink($item); } } - // Extract new files - $zip->extractTo($uploadDir); - $zip->close(); - unlink($tempZip); // cleanup + $uploads->extractTo($uploadDir); + $uploads->close(); } else { - unlink($tempZip); - die("Failed to open zip file."); + die("Failed to open uploads.zip in backup."); } + } else { + die("Missing uploads.zip in the backup archive."); } - // === 3. Final Setup Stages === + // === 3. Read version.txt (optional log/display) + $versionTxt = "$tempDir/version.txt"; + if (file_exists($versionTxt)) { + $versionInfo = file_get_contents($versionTxt); + // You could log it, show it, or ignore it + // e.g. logAction("Backup Restore", "Version Info", $versionInfo); + } + + // Cleanup temp restore directory + array_map('unlink', glob("$tempDir/*")); + rmdir($tempDir); + + // === 4. Final Setup Stages === $myfile = fopen("config.php", "a"); $txt = "\$config_enable_setup = 0;\n\n"; fwrite($myfile, $txt); fclose($myfile); - $_SESSION['alert_message'] = "Database and uploads restored successfully"; - + $_SESSION['alert_message'] = "Full backup restored successfully."; // header("Location: login.php"); exit; } @@ -1029,18 +1042,11 @@ if (isset($_POST['add_telemetry'])) {
-
Upload SQL File to Import into DB
- - + +
- -
Upload Folder Backup (.zip)
- - -
-
From fc344ef63606ef76362010119ad15ce09ad3cf0d Mon Sep 17 00:00:00 2001 From: johnnyq Date: Wed, 7 May 2025 19:23:11 -0400 Subject: [PATCH 11/80] add notification paging --- ajax/ajax_notifications.php | 102 ++++++++++++++++++++++++++---------- 1 file changed, 74 insertions(+), 28 deletions(-) diff --git a/ajax/ajax_notifications.php b/ajax/ajax_notifications.php index dbb7e701..7aec8740 100644 --- a/ajax/ajax_notifications.php +++ b/ajax/ajax_notifications.php @@ -24,40 +24,49 @@ ob_start(); From 2a43c5d868a65230d970fcce68bc8bfed246f665 Mon Sep 17 00:00:00 2001 From: johnnyq Date: Thu, 8 May 2025 16:46:25 -0400 Subject: [PATCH 13/80] Remove DB Check --- includes/top_nav.php | 50 ++++++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/includes/top_nav.php b/includes/top_nav.php index 24ac45e4..45aeaa88 100644 --- a/includes/top_nav.php +++ b/includes/top_nav.php @@ -28,37 +28,33 @@