mirror of
https://github.com/itflow-org/itflow
synced 2026-02-28 02:44:53 +00:00
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
This commit is contained in:
@@ -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']);
|
||||
|
||||
Reference in New Issue
Block a user