progress = $progress; $this->zipPath = $zipPath; $this->config = $progress->getChunkConfig() ?? new ChunkConfig(); $this->replaceEngine = new ReplaceEngine(); $this->workDir = ALLDAMI_TEMP_DIR . $progress->getJobId() . '/'; if (!is_dir($this->workDir)) { wp_mkdir_p($this->workDir); } } /** * Import işlemini başlatır */ public function initialize(): array { $this->progress->setStep('init', 0, 1, __('Initializing...', 'alldami-site-migration')); $this->progress->addMessage(__('Import process started.', 'alldami-site-migration'), 'info'); $this->progress->completeStep('init'); return ['next_step' => 'extract']; } /** * Zip dosyasını extract eder */ public function extract(): array { $this->progress->setStep('extract', 0, 1, __('Extracting archive...', 'alldami-site-migration')); $zip = new \ZipArchive(); if ($zip->open($this->zipPath) !== true) { throw new \Exception(__('Could not open zip file.', 'alldami-site-migration')); } $zip->extractTo($this->workDir); $zip->close(); // Manifest oku $manifestPath = $this->workDir . 'manifest.json'; if (file_exists($manifestPath)) { $this->manifest = json_decode(file_get_contents($manifestPath), true); } // Replace map ayarla $this->setupReplacements(); $this->progress->setResumePointer([ 'manifest' => $this->manifest, 'sql_files' => $this->getSqlFiles(), 'current_sql' => 0, 'archive_files' => $this->getArchiveFiles(), 'current_archive' => 0 ]); $this->progress->completeStep('extract'); $this->progress->addMessage(__('Archive extracted.', 'alldami-site-migration'), 'info'); return ['next_step' => 'preflight']; } /** * Ön kontrol (Preflight Check) */ public function preflight(): array { $this->progress->setStep('preflight', 0, 1, __('Running preflight checks...', 'alldami-site-migration')); $this->progress->addMessage(__('Preflight step started.', 'alldami-site-migration'), 'info'); try { $preflightCheck = new PreflightCheck(); // Dosya yollarını logla $filesDir = $this->workDir . 'files/'; /* translators: %s: directory path */ $this->progress->addMessage(sprintf(__('Directory to check: %s', 'alldami-site-migration'), $filesDir), 'info'); /* translators: %s: zip file path */ $this->progress->addMessage(sprintf(__('Zip file path: %s', 'alldami-site-migration'), $this->zipPath), 'info'); // Zip path kontrolü if (empty($this->zipPath)) { $this->progress->addMessage(__('ERROR: Zip path is empty!', 'alldami-site-migration'), 'error'); throw new \Exception(__('Zip path not found.', 'alldami-site-migration')); } // Validate metodu $this->progress->addMessage(__('Starting validation...', 'alldami-site-migration'), 'info'); $validation = $preflightCheck->validate($this->zipPath); $this->progress->addMessage(__('Validation completed.', 'alldami-site-migration'), 'info'); if (!empty($validation['errors'])) { foreach ($validation['errors'] as $error) { $this->progress->addMessage('PREFLIGHT ERROR: ' . $error, 'error'); } throw new \Exception(__('Preflight check failed: ', 'alldami-site-migration') . implode(', ', $validation['errors'])); } if (!empty($validation['warnings'])) { foreach ($validation['warnings'] as $warning) { $this->progress->addMessage('PREFLIGHT WARNING: ' . $warning, 'warning'); } } $this->progress->addMessage(__('Preflight checks passed.', 'alldami-site-migration'), 'info'); $this->progress->completeStep('preflight'); return ['next_step' => 'db_import']; } catch (\Exception $e) { $this->progress->addMessage('PREFLIGHT EXCEPTION: ' . $e->getMessage(), 'error'); throw $e; } catch (\Error $e) { $this->progress->addMessage('PREFLIGHT FATAL ERROR: ' . $e->getMessage(), 'error'); throw new \Exception(__('Critical error: ', 'alldami-site-migration') . esc_html($e->getMessage())); } } /** * Veritabanı import chunk */ public function importDbChunk(): array { global $wpdb; try { $pointer = $this->progress->getResumePointer(); $sqlFiles = $pointer['sql_files'] ?? []; $currentIndex = $pointer['current_sql'] ?? 0; $this->progress->addMessage(sprintf('DB Import Chunk: Index %d / Total %d', $currentIndex, count($sqlFiles)), 'info'); if ($currentIndex >= count($sqlFiles)) { $this->progress->addMessage(__('DB import step completed.', 'alldami-site-migration'), 'info'); $this->progress->completeStep('db_import'); return ['next_step' => 'file_restore']; } /* translators: 1: current index, 2: total */ $this->progress->setStep('db_import', $currentIndex, count($sqlFiles), sprintf(__('SQL import (%1$d/%2$d)', 'alldami-site-migration'), $currentIndex + 1, count($sqlFiles))); $sqlFile = $sqlFiles[$currentIndex]; $sqlPath = $this->workDir . 'db/' . $sqlFile; /* translators: %s: SQL file path */ $this->progress->addMessage(sprintf(__('Processing SQL file: %s', 'alldami-site-migration'), $sqlPath), 'info'); if (!file_exists($sqlPath)) { throw new \Exception(__('SQL file not found: ', 'alldami-site-migration') . $sqlPath); } /* translators: %s: SQL file path */ $this->progress->addMessage(sprintf(__('Reading SQL file: %s', 'alldami-site-migration'), $sqlPath), 'info'); $sql = file_get_contents($sqlPath); if ($sql === false) { throw new \Exception(__('Could not read SQL file: ', 'alldami-site-migration') . $sqlPath); } $this->progress->addMessage(__('SQL file loaded. Size: ', 'alldami-site-migration') . strlen($sql), 'info'); // SQL'i çalıştır $this->progress->addMessage(__('Disabling Foreign Key Checks...', 'alldami-site-migration'), 'info'); if (!isset($wpdb)) { $this->progress->addMessage(__('ERROR: $wpdb is not defined!', 'alldami-site-migration'), 'error'); global $wpdb; } // phpcs:ignore WordPress.DB.DirectDatabaseQuery $wpdb->query('SET FOREIGN_KEY_CHECKS = 0'); $this->progress->addMessage(__('Parsing SQL...', 'alldami-site-migration'), 'info'); $queries = $this->parseSqlQueries($sql); $queryCount = count($queries); /* translators: %d: number of queries */ $this->progress->addMessage(sprintf(__('Query count: %d', 'alldami-site-migration'), $queryCount), 'info'); $executed = 0; $executed = 0; foreach ($queries as $query) { // phpcs:disable WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter $query = trim($query); if (!empty($query) && strpos($query, '--') !== 0) { // Tablo oluşturma sorgusu ise önce tabloyu sil if (preg_match('/^\s*CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?[`"]?([a-zA-Z0-9_]+)[`"]?/i', $query, $matches)) { $tableName = $matches[1]; // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared $wpdb->query("DROP TABLE IF EXISTS `{$tableName}`"); // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared } $result = $wpdb->query($query); if ($result === false) { // Hata detayı için wpdb->last_error kullanılabilir ama bazen güvenlik riski olabilir. // Yine de log olarak ekleyelim debug için. $this->progress->addMessage('SQL Error: ' . $wpdb->last_error . ' | Query: ' . substr($query, 0, 50) . '...', 'warning'); } $executed++; } // phpcs:enable } // phpcs:ignore WordPress.DB.DirectDatabaseQuery $wpdb->query('SET FOREIGN_KEY_CHECKS = 1'); /* translators: 1: SQL file name, 2: executed count */ $this->progress->addMessage(sprintf(__('SQL import completed: %1$s (Executed: %2$d)', 'alldami-site-migration'), $sqlFile, $executed), 'info'); $this->progress->setResumePointer(['current_sql' => $currentIndex + 1]); return ['next_step' => 'db_import']; } catch (\Exception $e) { $this->progress->addMessage('DB IMPORT EXCEPTION: ' . $e->getMessage(), 'error'); throw $e; } catch (\Error $e) { $this->progress->addMessage('DB IMPORT FATAL ERROR: ' . $e->getMessage(), 'error'); throw new \Exception(__('Critical DB error: ', 'alldami-site-migration') . esc_html($e->getMessage())); } } /** * Dosyaları geri yükler */ public function restoreFilesChunk(): array { $pointer = $this->progress->getResumePointer(); $archiveFiles = $pointer['archive_files'] ?? []; $currentIndex = $pointer['current_archive'] ?? 0; if ($currentIndex >= count($archiveFiles)) { $this->progress->completeStep('file_restore'); return ['next_step' => 'replace']; } /* translators: 1: current index, 2: total */ $this->progress->setStep('file_restore', $currentIndex, count($archiveFiles), sprintf(__('Restoring files (%1$d/%2$d)', 'alldami-site-migration'), $currentIndex + 1, count($archiveFiles))); $archiveFile = $archiveFiles[$currentIndex]; $archivePath = $this->workDir . 'files/' . $archiveFile; $zip = new \ZipArchive(); if ($zip->open($archivePath) === true) { // Dosya türüne göre hedef dizin belirle for ($i = 0; $i < $zip->numFiles; $i++) { $filename = $zip->getNameIndex($i); $content_dir = defined('WP_CONTENT_DIR') ? WP_CONTENT_DIR : ABSPATH . 'wp-content'; $targetPath = $content_dir . '/' . $filename; // Dizin oluştur $targetDir = dirname($targetPath); if (!is_dir($targetDir)) { wp_mkdir_p($targetDir); } // Dosyayı extract et $content = $zip->getFromIndex($i); if ($content !== false) { file_put_contents($targetPath, $content); } } $zip->close(); } /* translators: %s: archive filename */ $this->progress->addMessage(sprintf(__('Archive restored: %s', 'alldami-site-migration'), $archiveFile), 'info'); $this->progress->setResumePointer(['current_archive' => $currentIndex + 1]); return ['next_step' => 'file_restore']; } /** * URL değişimlerini yapar */ public function processReplacements(): array { global $wpdb; $this->progress->setStep('replace', 0, 3, __('Processing URL replacements...', 'alldami-site-migration')); // wp_options $this->progress->setStep('replace', 1, 3, 'wp_options işleniyor...'); $this->processTable($wpdb->options, 'option_value'); // wp_postmeta $this->progress->setStep('replace', 2, 3, 'wp_postmeta işleniyor...'); $this->processTable($wpdb->postmeta, 'meta_value'); // wp_usermeta $this->progress->setStep('replace', 3, 3, 'wp_usermeta işleniyor...'); $this->processTable($wpdb->usermeta, 'meta_value'); $this->progress->completeStep('replace'); $this->progress->addMessage(__('URL replacements completed.', 'alldami-site-migration'), 'info'); return ['next_step' => 'finalize']; } /** * Final işlemler */ public function finalize(): array { global $wpdb; $this->progress->setStep('finalize', 0, 3, __('Finalizing...', 'alldami-site-migration')); // Site URL güncelle $this->progress->setStep('finalize', 1, 3, __('Updating Site URL...', 'alldami-site-migration')); update_option('siteurl', site_url()); update_option('home', home_url()); // Permalinks flush $this->progress->setStep('finalize', 2, 3, __('Refreshing permalinks...', 'alldami-site-migration')); flush_rewrite_rules(); // Önbellek temizle $this->progress->setStep('finalize', 3, 3, __('Clearing cache...', 'alldami-site-migration')); wp_cache_flush(); // Temp dosyaları temizle $this->recursiveDelete($this->workDir); if (file_exists($this->zipPath)) { wp_delete_file($this->zipPath); } $this->progress->addMessage(__('Import completed.', 'alldami-site-migration'), 'info'); $this->progress->markComplete(['success' => true]); return ['status' => 'completed']; } private function setupReplacements(): void { $replaceMapPath = $this->workDir . 'meta/replace-map.json'; if (!file_exists($replaceMapPath)) { return; } $sourceMap = json_decode(file_get_contents($replaceMapPath), true); // URL değişimleri if (!empty($sourceMap['site_url'])) { $this->replaceEngine->addReplacement($sourceMap['site_url'], site_url()); } if (!empty($sourceMap['home_url'])) { $this->replaceEngine->addReplacement($sourceMap['home_url'], home_url()); } if (!empty($sourceMap['upload_path'])) { $this->replaceEngine->addReplacement($sourceMap['upload_path'], wp_upload_dir()['basedir']); } } private function processTable(string $table, string $column): void { global $wpdb; // phpcs:disable WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter $primaryKey = $wpdb->get_var("SHOW KEYS FROM `{$table}` WHERE Key_name = 'PRIMARY'"); // phpcs:enable if (!$primaryKey) { // phpcs:disable WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter $primaryKey = $wpdb->get_var("SHOW COLUMNS FROM `{$table}`"); // Use column name as PK proxy // phpcs:enable } // phpcs:disable WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter $rows = $wpdb->get_results("SELECT `{$primaryKey}`, `{$column}` FROM `{$table}` WHERE `{$column}` != ''", ARRAY_A); // phpcs:enable foreach ($rows as $row) { $original = $row[$column]; $processed = $this->replaceEngine->process($original); if ($processed !== $original) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery $wpdb->update($table, [$column => $processed], [$primaryKey => $row[$primaryKey]]); } } } private function getSqlFiles(): array { $dbDir = $this->workDir . 'db/'; if (!is_dir($dbDir)) return []; $files = glob($dbDir . '*.sql'); return array_map('basename', $files); } private function getArchiveFiles(): array { $filesDir = $this->workDir . 'files/'; if (!is_dir($filesDir)) return []; $files = glob($filesDir . '*.zip'); return array_map('basename', $files); } private function parseSqlQueries(string $sql): array { return preg_split('/;\s*$/m', $sql, -1, PREG_SPLIT_NO_EMPTY); } private function recursiveDelete(string $dir): void { if (!is_dir($dir)) return; $files = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::CHILD_FIRST ); foreach ($files as $file) { if ($file->isDir()) { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_rmdir @rmdir($file->getPathname()); } else { // Use wp_delete_file if available if (function_exists('wp_delete_file')) { wp_delete_file($file->getPathname()); } else { // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink @unlink($file->getPathname()); } } } // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_rmdir @rmdir($dir); } }