$backupName, 'source_code_zip' => $backupName . '_source_code.zip', 'database_zip' => $backupName . '_database.zip', 'source_code_size' => $sourceCodeSize, 'database_size' => $estimatedDatabaseSize, 'status' => 'completed', 'notes' => 'Full application backup created successfully', ]); return $backup; } /** * Create source code backup as ZIP */ public function createSourceCodeBackup($backupName, $backupDir) { $zipPath = $backupDir . '/' . $backupName . '_source_code.zip'; $zip = new ZipArchive(); if ($zip->open($zipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== TRUE) { throw new Exception('Cannot create source code zip file'); } $rootPath = base_path(); $iterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($rootPath), \RecursiveIteratorIterator::LEAVES_ONLY ); foreach ($iterator as $file) { if (!$file->isDir()) { $filePath = $file->getRealPath(); $relativePath = substr($filePath, strlen($rootPath) + 1); // Skip backup directory, storage/logs, node_modules, .git, etc. if ($this->shouldSkipFile($relativePath)) { continue; } $zip->addFile($filePath, $relativePath); } } $zip->close(); return $zipPath; } /** * Create database backup as ZIP (shared hosting compatible) */ public function createDatabaseBackup($backupName, $backupDir) { $sqlPath = $backupDir . '/' . $backupName . '_database.sql'; $zipPath = $backupDir . '/' . $backupName . '_database.zip'; try { // Create SQL dump using PHP (compatible with shared hosting) $this->createSQLDump($sqlPath); // Create ZIP file for database $zip = new ZipArchive(); if ($zip->open($zipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== TRUE) { throw new Exception('Cannot create database zip file'); } $zip->addFile($sqlPath, basename($sqlPath)); $zip->close(); // Remove the SQL file, keep only ZIP unlink($sqlPath); return $zipPath; } catch (\Exception $e) { // Clean up files on error if (file_exists($sqlPath)) { unlink($sqlPath); } if (file_exists($zipPath)) { unlink($zipPath); } throw new Exception('Database backup failed: ' . $e->getMessage()); } } /** * Restore application from backup files */ public function restoreFromBackup($sourceCodeZipPath, $databaseZipPath) { $rootPath = base_path(); $tempDir = storage_path('app/temp_restore'); try { // Create temporary directory for database restoration if (!file_exists($tempDir)) { mkdir($tempDir, 0755, true); } // Step 1: Extract source code backup (overwrites existing files) $this->extractSourceCodeBackup($sourceCodeZipPath, $rootPath); // Step 2: Extract and restore database backup $this->restoreDatabaseBackup($databaseZipPath, $tempDir); // Clean up temporary directory $this->removeDirectory($tempDir); } catch (\Exception $e) { // Clean up on failure if (file_exists($tempDir)) { $this->removeDirectory($tempDir); } throw $e; } } /** * Delete backup from database and filesystem */ public function deleteFromBackup($id) { // Find the backup record $backup = Backup::findOrFail($id); $backupDir = storage_path('app/backups'); // Delete physical files $sourceCodePath = $backupDir . '/' . $backup->source_code_zip; $databasePath = $backupDir . '/' . $backup->database_zip; $deletedFiles = []; $failedFiles = []; // Delete source code ZIP if (file_exists($sourceCodePath)) { if (unlink($sourceCodePath)) { $deletedFiles[] = $backup->source_code_zip; } else { $failedFiles[] = $backup->source_code_zip; } } else { $failedFiles[] = $backup->source_code_zip . ' (not found)'; } // Delete database ZIP if (file_exists($databasePath)) { if (unlink($databasePath)) { $deletedFiles[] = $backup->database_zip; } else { $failedFiles[] = $backup->database_zip; } } else { $failedFiles[] = $backup->database_zip . ' (not found)'; } // Delete database record $backup->delete(); return $backup; } /** * Estimate database backup size before creating it */ public function estimateDatabaseBackupSize() { try { $dbName = config('database.connections.mysql.database'); // Get total database size in bytes $result = DB::select(" SELECT ROUND(SUM(data_length + index_length)) as size_bytes FROM information_schema.tables WHERE table_schema = ? ", [$dbName]); $databaseSizeBytes = $result[0]->size_bytes ?? 0; return $databaseSizeBytes; } catch (\Exception $e) { // If estimation fails, return a reasonable default (5MB) return 5 * 1024 * 1024; } } /** * Create SQL dump using PHP (shared hosting compatible) */ private function createSQLDump($sqlPath) { $dbName = config('database.connections.mysql.database'); // Get all tables $tables = DB::select('SHOW TABLES'); $tableKey = 'Tables_in_' . $dbName; $sql = "-- Database: {$dbName}\n"; $sql .= "-- Generated on: " . date('Y-m-d H:i:s') . "\n"; $sql .= "-- PHP Version: " . phpversion() . "\n\n"; $sql .= "SET SQL_MODE = \"NO_AUTO_VALUE_ON_ZERO\";\n"; $sql .= "START TRANSACTION;\n"; $sql .= "SET time_zone = \"+00:00\";\n\n"; foreach ($tables as $table) { $tableName = $table->$tableKey; // Skip certain system tables if needed if (in_array($tableName, ['sessions', 'cache', 'cache_locks'])) { continue; } $sql .= $this->getDumpForTable($tableName); } $sql .= "COMMIT;\n"; // Write to file file_put_contents($sqlPath, $sql); } /** * Generate SQL dump for a specific table */ private function getDumpForTable($tableName) { $sql = "\n-- --------------------------------------------------------\n"; $sql .= "-- Table structure for table `{$tableName}`\n"; $sql .= "-- --------------------------------------------------------\n\n"; // Get CREATE TABLE statement $createTable = DB::select("SHOW CREATE TABLE `{$tableName}`")[0]; $sql .= "DROP TABLE IF EXISTS `{$tableName}`;\n"; $sql .= $createTable->{'Create Table'} . ";\n\n"; // Get table data $sql .= "-- Dumping data for table `{$tableName}`\n\n"; $rows = DB::table($tableName)->get(); if ($rows->count() > 0) { $sql .= "INSERT INTO `{$tableName}` ("; // Get column names $columns = array_keys((array)$rows->first()); $sql .= "`" . implode("`, `", $columns) . "`"; $sql .= ") VALUES\n"; $values = []; foreach ($rows as $row) { $rowData = (array)$row; $escapedValues = array_map(function ($value) { if ($value === null) { return 'NULL'; } return "'" . addslashes($value) . "'"; }, $rowData); $values[] = "(" . implode(", ", $escapedValues) . ")"; } $sql .= implode(",\n", $values) . ";\n\n"; } return $sql; } }