'Forbidden']); exit; } // Sanitizasyon function alldami_sanitize_key($key) { return preg_replace('/[^a-z0-9_\-]/', '', strtolower($key)); } $job_id = isset($_POST['job_id']) ? alldami_sanitize_key($_POST['job_id']) : ''; $step = isset($_POST['step']) ? alldami_sanitize_key($_POST['step']) : ''; if (empty($job_id) || !in_array($step, ['db_import', 'db_import_dry'])) { echo json_encode(['success' => false, 'message' => 'Geçersiz parametreler']); exit; } $is_dry_run = ($step === 'db_import_dry'); // State dosyası $state_file = ALLDAMI_STATE_DIR . $job_id . '.json'; if (!file_exists($state_file)) { echo json_encode(['success' => false, 'message' => 'Job bulunamadı: ' . $job_id]); exit; } $state = json_decode(file_get_contents($state_file), true); if (!$state) { echo json_encode(['success' => false, 'message' => 'State okunamadı']); exit; } // Security Token Kontrolü // Settings'den token kontrolü aktif mi kontrol et (varsayılan: aktif) $token_check_enabled = true; if (isset($state['settings']['security_import_token_enabled'])) { $token_check_enabled = (bool) $state['settings']['security_import_token_enabled']; } if ($token_check_enabled && !empty($state['import_token'])) { $provided_token = isset($_POST['token']) ? $_POST['token'] : ''; if ($provided_token !== $state['import_token']) { echo json_encode(['success' => false, 'message' => 'Geçersiz güvenlik token\'ı']); exit; } // Token Expiry Kontrolü (6 saat) $token_created_at = isset($state['import_token_created_at']) ? (int) $state['import_token_created_at'] : 0; $token_max_age = 6 * 3600; // 6 saat (21600 saniye) if ($token_created_at > 0 && (time() - $token_created_at) > $token_max_age) { echo json_encode(['success' => false, 'message' => 'Güvenlik token\'ı süresi dolmuş. Lütfen import işlemini yeniden başlatın.']); exit; } } // PHP limits - scoped to this import operation only if (function_exists('set_time_limit')) { set_time_limit(300); } if (function_exists('ignore_user_abort')) { ignore_user_abort(true); } if (function_exists('ini_set')) { ini_set('memory_limit', '256M'); // phpcs:ignore WordPress.PHP.IniSet.memory_limit_Blacklisted } // Chunk ayarları $max_queries_per_chunk = 500; $max_execution_seconds = 25; $start_time = time(); // Çalışma dizini $work_dir = ALLDAMI_TEMP_DIR . $job_id . '/'; $db_dir = $work_dir . 'db/'; if (!is_dir($db_dir)) { wp_send_json_error(['message' => 'DB klasörü bulunamadı']); exit; } // SQL dosyalarını al $sql_files = $state['resume_pointer']['sql_files'] ?? []; $current_index = $state['resume_pointer']['current_sql'] ?? 0; $byte_offset = $state['resume_pointer']['byte_offset'] ?? 0; $total_queries_in_file = $state['resume_pointer']['total_queries_in_file'] ?? 0; if (empty($sql_files)) { if (is_dir($db_dir)) { $files = scandir($db_dir); foreach ($files as $file) { if (substr($file, -4) === '.sql') { $sql_files[] = $file; } } sort($sql_files); } if (empty($sql_files)) { echo json_encode(['success' => false, 'message' => 'SQL dosyası bulunamadı']); exit; } } // Tüm dosyalar işlendi mi? if ($current_index >= count($sql_files)) { // Normal import ise pointer'ları sıfırla, dry-run ise stats dön $state['resume_pointer']['current_sql'] = 0; $state['resume_pointer']['byte_offset'] = 0; // Dry run bitti, stats hazırla if ($is_dry_run) { $final_stats = $state['dry_run_stats'] ?? ['tables' => 0, 'queries' => 0]; // Dry run bitti, state'i temizle veya logla // State update etmiyoruz ki gerçek import temiz başlasın (veya sadece stats tutuyoruz) // İsteğe bağlı: dry run bitince resume pointer'ı zaten sıfırladık. } file_put_contents($state_file, json_encode($state)); echo json_encode([ 'success' => true, 'data' => [ 'next_step' => $is_dry_run ? 'dry_run_completed' : 'file_restore', 'message' => $is_dry_run ? 'Dry-run analizi tamamlandı.' : 'Veritabanı import tamamlandı', 'stats' => $is_dry_run ? ($state['dry_run_stats'] ?? []) : null ] ]); exit; } // Mevcut SQL dosyası $sql_file = $sql_files[$current_index]; $sql_path = $db_dir . $sql_file; if (!file_exists($sql_path)) { echo json_encode(['success' => false, 'message' => 'SQL dosyası bulunamadı: ' . $sql_path]); exit; } // Foreign key kontrollerini kapat (Sadece gerçek importta) if (!$is_dry_run) { $wpdb->query('SET FOREIGN_KEY_CHECKS = 0'); $wpdb->query('SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"'); $wpdb->query('START TRANSACTION'); } // ============================================ // STREAM-BASED SQL IMPORT (v1.2 - Kritik Düzeltmeler) // ============================================ $handle = fopen($sql_path, 'r'); if ($handle === false) { echo json_encode(['success' => false, 'message' => 'SQL dosyası açılamadı']); exit; } $file_size = filesize($sql_path); // Kaldığımız yerden devam et if ($byte_offset > 0) { fseek($handle, $byte_offset); } $statement = ''; $executed = 0; $errors = []; $first_line = ($byte_offset === 0); $in_multiline_comment = false; // ÖNEMLİ: Safe offset - statement tamamlandıktan sonraki pozisyon $last_safe_position = $byte_offset; // Stream okuma döngüsü while (($line = fgets($handle)) !== false) { // Timeout kontrolü - AMA önce mevcut statement'ı bitir $should_break = false; if ((time() - $start_time) > $max_execution_seconds) { $should_break = true; } if ($executed >= $max_queries_per_chunk) { $should_break = true; } // Eğer break gerekiyorsa ve statement boşsa, güvenli çık if ($should_break && empty(trim($statement))) { break; } // BOM temizle if ($first_line) { if (substr($line, 0, 3) === "\xEF\xBB\xBF") { $line = substr($line, 3); } $first_line = false; } $trimmed = trim($line); // ============================================ // YORUM KONTROLÜ (tüm türler) // ============================================ // Multi-line comment başlangıcı if (strpos($trimmed, '/*') === 0 && strpos($trimmed, '*/') === false) { $in_multiline_comment = true; continue; } // Multi-line comment bitişi if ($in_multiline_comment) { if (strpos($trimmed, '*/') !== false) { $in_multiline_comment = false; } continue; } // Tek satır yorumlar: --, #, /* ... */ if ( empty($trimmed) || strpos($trimmed, '--') === 0 || strpos($trimmed, '#') === 0 || (strpos($trimmed, '/*') === 0 && strpos($trimmed, '*/') !== false) ) { continue; } // ============================================ // SET NAMES / CHARSET KOMUTLARI // ============================================ if (preg_match('/^SET\s+(NAMES|CHARACTER_SET|COLLATION)/i', $trimmed)) { if (!$is_dry_run) { $wpdb->query(rtrim($trimmed, ';')); } $last_safe_position = ftell($handle); continue; } // ============================================ // MySQL CONDITIONAL COMMENTS (/*!40101 ... */) // ============================================ if (preg_match('/^\/\*![0-9]+(.*)?\*\/;?$/', $trimmed, $matches)) { $conditional_sql = trim($matches[1]); // Only allow safe MySQL conditional statements (SET, CHARACTER SET, COLLATION, ALTER, CREATE, DROP, INSERT) $safe_patterns = ['/^SET\s/i', '/^CHARACTER\s/i', '/^COLLAT/i', '/^ALTER\s/i', '/^CREATE\s/i', '/^DROP\s/i', '/^INSERT\s/i']; $is_safe = false; foreach ($safe_patterns as $pattern) { if (preg_match($pattern, $conditional_sql)) { $is_safe = true; break; } } if (!empty($conditional_sql) && $is_safe) { if (!$is_dry_run) { // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- SQL from trusted export dump, whitelisted $wpdb->query($conditional_sql); } } $last_safe_position = ftell($handle); continue; } // ============================================ // STATEMENT BİRİKTİRME // ============================================ $statement .= $line; // ============================================ // STATEMENT BİTİŞ KONTROLÜ (STATEMENT-BASED, sadece ;) // Multi-line string desteği: satıra değil, biriken statement'a bak // ============================================ // Statement ; ile bitiyor mu? (STATEMENT'a bak, satıra değil) if (substr(rtrim($statement), -1) === ';') { // Execute statement executeStatementSafe($wpdb, $statement, $executed, $errors, $is_dry_run, $state); // ÖNEMLİ: Safe offset güncelle $last_safe_position = ftell($handle); // Reset $statement = ''; // Şimdi break kontrolü yap if ($should_break) { break; } } // Statement buffer limiti (10MB) if (strlen($statement) > 10 * 1024 * 1024) { $errors[] = ['error' => 'Statement çok büyük (>10MB), atlanıyor']; $statement = ''; $last_safe_position = ftell($handle); } } // Dosya sonuna ulaştık mı? $file_finished = feof($handle); // Son kalan statement (dosya bittiyse) if ($file_finished && !empty(trim($statement))) { executeStatementSafe($wpdb, $statement, $executed, $errors); $last_safe_position = ftell($handle); } fclose($handle); // Transaction commit (başarılı import) $wpdb->query('COMMIT'); // Foreign key kontrollerini aç $wpdb->query('SET FOREIGN_KEY_CHECKS = 1'); // ============================================ // STATE GÜNCELLEME (Safe Offset ile) // ============================================ if ($file_finished) { $state['resume_pointer']['current_sql'] = $current_index + 1; $state['resume_pointer']['byte_offset'] = 0; $state['resume_pointer']['total_queries_in_file'] = 0; $state['messages'][] = [ 'type' => 'success', 'text' => sprintf('SQL import tamamlandı: %s (%d sorgu)', $sql_file, $total_queries_in_file + $executed), 'time' => gmdate('H:i:s') ]; } else { // ÖNEMLİ: Safe offset kullan, ftell değil! $state['resume_pointer']['byte_offset'] = $last_safe_position; $state['resume_pointer']['total_queries_in_file'] = $total_queries_in_file + $executed; $state['messages'][] = [ 'type' => 'info', 'text' => sprintf('SQL import devam ediyor: %s (%d sorgu)', $sql_file, $executed), 'time' => gmdate('H:i:s') ]; } $state['resume_pointer']['sql_files'] = $sql_files; if (!empty($errors)) { $state['messages'][] = [ 'type' => 'warning', 'text' => sprintf('%d SQL hatası oluştu', count($errors)), 'time' => gmdate('H:i:s') ]; } // Progress $file_progress = ($file_size > 0) ? round(($last_safe_position / $file_size) * 100) : 100; $overall_progress = round((($current_index + ($file_progress / 100)) / count($sql_files)) * 100); $next_step = ($file_finished && ($current_index + 1) >= count($sql_files)) ? ($is_dry_run ? 'dry_run_completed' : 'file_restore') : ($is_dry_run ? 'db_import_dry' : 'db_import_direct'); if ($next_step === 'file_restore' || $next_step === 'dry_run_completed') { $state['steps']['db_import']['status'] = $is_dry_run ? 'pending' : 'completed'; // Dry run ise statusu tamamlama, bekleyen kalsın $state['steps']['db_import']['progress'] = $is_dry_run ? 0 : 100; if (!$is_dry_run) { $state['steps']['file_restore']['status'] = 'running'; $state['steps']['file_restore']['progress'] = 0; } } else { $state['steps']['db_import']['status'] = 'running'; $state['steps']['db_import']['progress'] = $overall_progress; } file_put_contents($state_file, json_encode($state)); echo json_encode([ 'success' => true, 'data' => [ 'next_step' => $next_step, 'job_id' => $job_id, 'message' => sprintf('SQL import: %s (%d/%d) - %%%d', $sql_file, $current_index + 1, count($sql_files), $file_progress), 'executed' => $executed, 'errors' => count($errors), 'progress' => [ 'current_file' => $current_index + 1, 'total_files' => count($sql_files), 'file_percent' => $file_progress, 'overall_percent' => $overall_progress ] ] ]); // ============================================ // HELPER FUNCTION (% PLACEHOLDER RESTORE DAHİL) // ============================================ function executeStatementSafe($wpdb, $statement, &$executed, &$errors, $is_dry_run = false, &$state = null) { $statement = trim($statement); if (empty($statement)) return; // ÖNEMLİ: % placeholder geri dönüşümü $statement = str_replace('{{WPCLONER_PERCENT}}', '%', $statement); // Sadece yorum mu kontrol et $lines = explode("\n", $statement); $has_content = false; foreach ($lines as $line) { $line = trim($line); if (!empty($line) && strpos($line, '--') !== 0 && strpos($line, '#') !== 0) { $has_content = true; break; } } if (!$has_content) return; // CREATE TABLE analizi (Dry run için) $table_name = ''; if (preg_match('/^\s*CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?[`"]?([a-zA-Z0-9_]+)[`"]?/i', $statement, $matches)) { $table_name = $matches[1]; } if ($is_dry_run) { // İstatistik topla if (!isset($state['dry_run_stats'])) { $state['dry_run_stats'] = ['tables' => 0, 'queries' => 0]; } $state['dry_run_stats']['queries']++; if ($table_name) { $state['dry_run_stats']['tables']++; } $executed++; return; } // CREATE TABLE için önce DROP if ($table_name) { $wpdb->query("DROP TABLE IF EXISTS `{$table_name}`"); } // Sorguyu çalıştır $result = $wpdb->query($statement); if ($result === false && !empty($wpdb->last_error)) { $errors[] = [ 'query' => substr($statement, 0, 200) . '...', 'error' => $wpdb->last_error ]; } else { $executed++; } }