diff --git a/client_overview.php b/client_overview.php
index 11414807..4a1c02ba 100644
--- a/client_overview.php
+++ b/client_overview.php
@@ -654,7 +654,7 @@ $sql_asset_retired = mysqli_query(
?>
-
-
+
diff --git a/post/admin/admin_backup.php b/post/admin/admin_backup.php
index 11ec83a6..844a12d6 100644
--- a/post/admin/admin_backup.php
+++ b/post/admin/admin_backup.php
@@ -8,64 +8,22 @@ defined('FROM_POST_HANDLER') || die("Direct file access is not allowed");
require_once "includes/app_version.php";
-if (isset($_GET['download_backup'])) {
- validateCSRFToken($_GET['csrf_token']);
- global $mysqli, $database;
-
- $timestamp = date('YmdHis');
- $baseName = "itflow_$timestamp";
- $sqlFile = "$baseName.sql";
- $uploadsZip = "$baseName_uploads.zip";
- $finalZip = "$baseName.zip";
- $versionFile = "version.txt";
-
- // === 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";
-
- $tables = [];
- $res = $mysqli->query("SHOW TABLES");
- while ($row = $res->fetch_row()) {
- $tables[] = $row[0];
- }
-
- foreach ($tables as $table) {
- $createRes = $mysqli->query("SHOW CREATE TABLE `$table`");
- $createRow = $createRes->fetch_assoc();
- $createSQL = array_values($createRow)[1];
-
- $sqlContent .= "\n-- ----------------------------\n";
- $sqlContent .= "-- Table structure for `$table`\n";
- $sqlContent .= "-- ----------------------------\n";
- $sqlContent .= "DROP TABLE IF EXISTS `$table`;\n";
- $sqlContent .= $createSQL . ";\n\n";
-
- $dataRes = $mysqli->query("SELECT * FROM `$table`");
- if ($dataRes->num_rows > 0) {
- $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) {
- return is_null($val) ? "NULL" : "'" . $mysqli->real_escape_string($val) . "'";
- }, array_values($row));
- $sqlContent .= "INSERT INTO `$table` (" . implode(", ", $columns) . ") VALUES (" . implode(", ", $values) . ");\n";
- }
- $sqlContent .= "\n";
- }
- }
-
- $sqlContent .= "SET foreign_key_checks = 1;\n";
- file_put_contents($sqlFile, $sqlContent);
-
- // === 2. Create uploads.zip ===
+if (!function_exists('zipFolder')) {
function zipFolder($folderPath, $zipFilePath) {
$zip = new ZipArchive();
if ($zip->open($zipFilePath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== TRUE) {
- die("Cannot open <$zipFilePath>");
+ error_log("Failed to open zip file: $zipFilePath");
+ http_response_code(500);
+ exit("Internal Server Error: Cannot open zip archive.");
}
$folderPath = realpath($folderPath);
+ if (!$folderPath) {
+ error_log("Invalid folder path: $folderPath");
+ http_response_code(500);
+ exit("Internal Server Error: Invalid folder path.");
+ }
+
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($folderPath),
RecursiveIteratorIterator::LEAVES_ONLY
@@ -81,17 +39,82 @@ if (isset($_GET['download_backup'])) {
$zip->close();
}
+}
+if (isset($_GET['download_backup'])) {
+ validateCSRFToken($_GET['csrf_token']);
+ global $mysqli;
+
+ $session_name = $_SESSION['username'] ?? 'unknown';
+ $timestamp = date('YmdHis');
+ $baseName = "itflow_$timestamp";
+
+ // Use temp files
+ $sqlFile = tempnam(sys_get_temp_dir(), $baseName . "_sql_");
+ $uploadsZip = tempnam(sys_get_temp_dir(), $baseName . "_uploads_");
+ $versionFile = tempnam(sys_get_temp_dir(), $baseName . "_version_");
+ $finalZip = tempnam(sys_get_temp_dir(), $baseName . "_backup_");
+
+ // === 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";
+
+ $tables = [];
+ $res = $mysqli->query("SHOW TABLES");
+ if (!$res) {
+ error_log("MySQL Error: " . $mysqli->error);
+ exit("Error retrieving tables.");
+ }
+
+ while ($row = $res->fetch_row()) {
+ $tables[] = $row[0];
+ }
+
+ foreach ($tables as $table) {
+ $createRes = $mysqli->query("SHOW CREATE TABLE `$table`");
+ if (!$createRes) {
+ error_log("MySQL Error: " . $mysqli->error);
+ continue;
+ }
+
+ $createRow = $createRes->fetch_assoc();
+ $createSQL = array_values($createRow)[1];
+
+ $sqlContent .= "\n-- ----------------------------\n";
+ $sqlContent .= "-- Table structure for `$table`\n";
+ $sqlContent .= "-- ----------------------------\n";
+ $sqlContent .= "DROP TABLE IF EXISTS `$table`;\n";
+ $sqlContent .= $createSQL . ";\n\n";
+
+ $dataRes = $mysqli->query("SELECT * FROM `$table`");
+ if ($dataRes && $dataRes->num_rows > 0) {
+ $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) {
+ return is_null($val) ? "NULL" : "'" . $mysqli->real_escape_string($val) . "'";
+ }, array_values($row));
+ $sqlContent .= "INSERT INTO `$table` (" . implode(", ", $columns) . ") VALUES (" . implode(", ", $values) . ");\n";
+ }
+ $sqlContent .= "\n";
+ }
+ }
+
+ $sqlContent .= "SET foreign_key_checks = 1;\n";
+ file_put_contents($sqlFile, $sqlContent);
+
+ // === 2. Zip uploads folder ===
zipFolder("uploads", $uploadsZip);
- // === 3. Generate version.txt ===
- $commitHash = trim(shell_exec('git log -1 --format=%H'));
- $gitBranch = trim(shell_exec('git rev-parse --abbrev-ref HEAD'));
+ // === 3. Create version.txt ===
+ $commitHash = trim(shell_exec('git log -1 --format=%H')) ?: 'N/A';
+ $gitBranch = trim(shell_exec('git rev-parse --abbrev-ref HEAD')) ?: 'N/A';
$versionContent = "ITFlow Backup Metadata\n";
$versionContent .= "-----------------------------\n";
$versionContent .= "Generated: " . date('Y-m-d H:i:s') . "\n";
- $versionContent .= "Backup File: $baseName.zip\n";
+ $versionContent .= "Backup File: " . basename($finalZip) . "\n";
$versionContent .= "Generated By: $session_name\n";
$versionContent .= "Host: " . gethostname() . "\n";
$versionContent .= "Git Branch: $gitBranch\n";
@@ -99,12 +122,17 @@ if (isset($_GET['download_backup'])) {
$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);
+ // Generate SHA256 checksum
+ $versionContent .= "Checksum (SHA256): \n";
- // === 4. Combine into final .zip file ===
+ file_put_contents($versionFile, $versionContent); // Add metadata first
+
+ // === 4. Create final archive ===
$final = new ZipArchive();
if ($final->open($finalZip, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== TRUE) {
- die("Cannot create final backup zip.");
+ error_log("Failed to create final zip: $finalZip");
+ http_response_code(500);
+ exit("Internal Server Error: Unable to create backup archive.");
}
$final->addFile($sqlFile, "db.sql");
@@ -112,18 +140,29 @@ if (isset($_GET['download_backup'])) {
$final->addFile($versionFile, "version.txt");
$final->close();
- // Cleanup temp files before download
+ // Calculate checksum and re-write version.txt with it
+ $checksum = hash_file('sha256', $finalZip);
+ file_put_contents($versionFile, $versionContent . "$checksum\n");
+
+ // === 5. Serve final zip ===
+ header('Content-Type: application/zip');
+ header('Content-Disposition: attachment; filename="' . basename($finalZip) . '"');
+ header('Content-Length: ' . filesize($finalZip));
+ header('Pragma: public');
+ header('Expires: 0');
+ header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
+ header('Content-Transfer-Encoding: binary');
+
+ flush();
+ $fp = fopen($finalZip, 'rb');
+ fpassthru($fp);
+ fclose($fp);
+
+ // === 6. Cleanup temp files ===
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
+ unlink($finalZip);
logAction("System", "Backup Download", "$session_name downloaded full backup.");
$_SESSION['alert_message'] = "Full backup downloaded.";