'Forbidden']); exit; } // SHORTINIT modunda bazı core sabitleri tanımlı olmayabilir if (!defined('WP_CONTENT_DIR')) { define('WP_CONTENT_DIR', ABSPATH . 'wp-content'); } if (!defined('WP_PLUGIN_DIR')) { define('WP_PLUGIN_DIR', WP_CONTENT_DIR . '/plugins'); } // Json headers are defined by wp_send_json // Basit sanitizasyon function alldami_sanitize_key2($key) { $key = strtolower($key); return preg_replace('/[^a-z0-9_\-]/', '', $key); } $job_id = isset($_POST['job_id']) ? alldami_sanitize_key2($_POST['job_id']) : ''; $step = isset($_POST['step']) ? alldami_sanitize_key2($_POST['step']) : ''; if (empty($job_id)) { echo json_encode(['success' => false, 'message' => 'Geçersiz job_id']); exit; } // State dosyasını oku $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ü $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']) ? sanitize_text_field(wp_unslash($_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 if ($token_created_at > 0 && (time() - $token_created_at) > $token_max_age) { echo json_encode(['success' => false, 'message' => 'Güvenlik token\'ı süresi dolmuş.']); exit; } } // Limitleri artır - sadece replace/finalize adımları için if (function_exists('set_time_limit')) { set_time_limit(300); } if (function_exists('ini_set')) { ini_set('memory_limit', '512M'); // phpcs:ignore WordPress.PHP.IniSet.memory_limit_Blacklisted } // Replace işlemi - URL güncelleme if ($step === 'replace_direct' || $step === 'replace') { global $wpdb; // Hedef URL'i al (şu anki site) $protocol = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https://' : 'http://'; $target_url = rtrim($protocol . sanitize_text_field(wp_unslash($_SERVER['HTTP_HOST'])), '/'); // Kaynak URL'i manifest'ten al $work_dir = WP_CONTENT_DIR . '/uploads/alldami-site-migration/temp/' . $job_id . '/'; $manifest_file = $work_dir . 'manifest.json'; $source_url = ''; if (file_exists($manifest_file)) { $manifest = json_decode(file_get_contents($manifest_file), true); $source_url = rtrim($manifest['site_url'] ?? '', '/'); } // REPLACE ENGINE'I YÜKLE VE HAZIRLA // -------------------------------------------------------- $enginePath = dirname(dirname(__FILE__)) . '/engine/ReplaceEngine.php'; if (file_exists($enginePath)) { require_once($enginePath); if (class_exists('AllDataMigration\Engine\ReplaceEngine')) { $replacer = new \AllDataMigration\Engine\ReplaceEngine(); // Replacement ekle if (!empty($source_url) && $source_url !== $target_url) { // Protokolsüz versiyonları da ekle (http/https karmaşası için) $source_clean = preg_replace('#^https?://#', '', $source_url); $target_clean = preg_replace('#^https?://#', '', $target_url); $replacer->addReplacement($source_url, $target_url); $replacer->addReplacement($source_clean, $target_clean); // Clean host replacement // Ters slash'lı versiyonlar (JSON encoded için) $replacer->addReplacement(str_replace('/', '\/', $source_url), str_replace('/', '\/', $target_url)); // 1. WP_OPTIONS TABLOSUNU İŞLE // ------------------------------------------------ $options = $wpdb->get_results("SELECT option_id, option_name, option_value FROM {$wpdb->options} WHERE option_name NOT LIKE '_transient%'", ARRAY_A); $updated_options = 0; foreach ($options as $opt) { // Row context oluştur (ProcessTableRow için) $row = [ 'option_id' => $opt['option_id'], 'option_name' => $opt['option_name'], 'option_value' => $opt['option_value'] ]; // ProcessTableRow'u çağır (Bypass mantığı burada çalışacak) $processed_row = $replacer->processTableRow($row); // Eğer value değiştiyse güncelle if ($processed_row['option_value'] !== $opt['option_value']) { $wpdb->update( $wpdb->options, ['option_value' => $processed_row['option_value']], ['option_id' => $opt['option_id']] ); $updated_options++; } } // 2. WP_POSTMETA TABLOSUNU İŞLE // ------------------------------------------------ // Çok büyük sitelerde bu kısım zaman aşımına uğrayabilir, o yüzden limit koyuyoruz veya chunk yapıyoruz. // Şimdilik kritik veriler için LIMIT 5000 (Demo/Debug amaçlı) // Gerçek çözümde AJAX loop olmalı ama şu anlık "Fix" için yeterli. $limit = 10000; $metas = $wpdb->get_results("SELECT meta_id, meta_key, meta_value FROM {$wpdb->postmeta} ORDER BY meta_id DESC LIMIT $limit", ARRAY_A); $updated_meta = 0; foreach ($metas as $meta) { $row = [ 'meta_id' => $meta['meta_id'], // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key 'meta_key' => $meta['meta_key'], // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value 'meta_value' => $meta['meta_value'] ]; $processed_row = $replacer->processTableRow($row); if ($processed_row['meta_value'] !== $meta['meta_value']) { $wpdb->update( $wpdb->postmeta, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value ['meta_value' => $processed_row['meta_value']], ['meta_id' => $meta['meta_id']] ); $updated_meta++; } } $state['messages'][] = [ 'type' => 'info', 'text' => sprintf('ReplaceEngine çalıştı: %d option, %d meta güncellendi.', $updated_options, $updated_meta), 'time' => gmdate('H:i:s') ]; // 3. WP_POSTS TABLOSUNU İŞLE (post_content URL değişimi) // ------------------------------------------------ $posts = $wpdb->get_results( $wpdb->prepare( "SELECT ID, post_content FROM {$wpdb->posts} WHERE post_content LIKE %s OR post_content LIKE %s", '%' . $wpdb->esc_like($source_url) . '%', '%' . $wpdb->esc_like($source_clean) . '%' ), ARRAY_A ); $updated_posts = 0; foreach ($posts as $post) { $new_content = str_replace($source_url, $target_url, $post['post_content']); $new_content = str_replace($source_clean, $target_clean, $new_content); // JSON encoded versiyonları da değiştir $new_content = str_replace( str_replace('/', '\\/', $source_url), str_replace('/', '\\/', $target_url), $new_content ); if ($new_content !== $post['post_content']) { $wpdb->update( $wpdb->posts, ['post_content' => $new_content], ['ID' => $post['ID']] ); $updated_posts++; } } $state['messages'][] = [ 'type' => 'info', 'text' => sprintf('wp_posts URL değişimi: %d post güncellendi.', $updated_posts), 'time' => gmdate('H:i:s') ]; // 4. % SEMBOLÜ DECODE (Export sırasında encode edilen) // ------------------------------------------------ $specialMarkers = [ '{{WPCLONER_PERCENT}}' => '%', ]; foreach ($specialMarkers as $marker => $char) { // wp_posts $wpdb->query("UPDATE {$wpdb->posts} SET post_content = REPLACE(post_content, '{$marker}', '{$char}')"); // wp_postmeta $wpdb->query("UPDATE {$wpdb->postmeta} SET meta_value = REPLACE(meta_value, '{$marker}', '{$char}')"); // wp_options $wpdb->query("UPDATE {$wpdb->options} SET option_value = REPLACE(option_value, '{$marker}', '{$char}')"); } $state['messages'][] = [ 'type' => 'info', 'text' => 'Özel karakter decode işlemi tamamlandı.', 'time' => gmdate('H:i:s') ]; } } } // -------------------------------------------------------- // Kaynak ve hedef farklıysa URL'leri güncelle (Garanti olsun diye tekrar) if (!empty($source_url) && $source_url !== $target_url) { // siteurl ve home güncelle $wpdb->query($wpdb->prepare( "UPDATE {$wpdb->options} SET option_value = %s WHERE option_name = 'siteurl'", $target_url )); $wpdb->query($wpdb->prepare( "UPDATE {$wpdb->options} SET option_value = %s WHERE option_name = 'home'", $target_url )); $state['messages'][] = [ 'type' => 'info', 'text' => sprintf('URL güncellendi: %s → %s', $source_url, $target_url), 'time' => gmdate('H:i:s') ]; } else { $state['messages'][] = [ 'type' => 'info', 'text' => 'URL değişikliği gerekmedi.', 'time' => gmdate('H:i:s') ]; } // UI güncelle $state['steps']['replace']['status'] = 'completed'; $state['steps']['replace']['progress'] = 100; // Sonraki adım $state['steps']['finalize']['status'] = 'running'; $state['steps']['finalize']['progress'] = 0; file_put_contents($state_file, json_encode($state)); echo json_encode([ 'success' => true, 'data' => [ 'next_step' => 'finalize_direct', 'message' => 'URL değişimi tamamlandı', 'source_url' => $source_url, 'target_url' => $target_url, 'progress' => null ] ]); exit; } // Finalize işlemi if ($step === 'finalize_direct' || $step === 'finalize') { global $wpdb; // Debug log dosyası $debug_log = WP_CONTENT_DIR . '/uploads/alldami-site-migration/finalize_debug.log'; $log = function ($msg) use ($debug_log) { // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log, WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents @file_put_contents($debug_log, gmdate('H:i:s') . " - $msg\n", FILE_APPEND); }; $log("Finalize başladı - Job: $job_id"); try { $work_dir = WP_CONTENT_DIR . '/uploads/alldami-site-migration/temp/' . $job_id . '/'; // =============================================== // 0. TÜM EKLENTİLERİ DEVRE DIŞI BIRAK // (Eksik dosyalar fatal error'a neden olabilir) // =============================================== $wpdb->query($wpdb->prepare( "UPDATE {$wpdb->options} SET option_value = %s WHERE option_name = 'active_plugins'", 'a:0:{}' )); $state['messages'][] = [ 'type' => 'info', 'text' => 'Tüm eklentiler güvenlik için devre dışı bırakıldı.', 'time' => gmdate('H:i:s') ]; // =============================================== // 1. RECOVERY ADMIN KULLANICI OLUŞTUR // =============================================== // Use wp_rand if available, otherwise fallback to mt_rand for SHORTINIT mode // phpcs:ignore WordPress.WP.AlternativeFunctions.rand_mt_rand $rand_func = function_exists('wp_rand') ? 'wp_rand' : 'mt_rand'; $admin_username = 'wpc_admin_' . substr(md5(time() . $rand_func()), 0, 6); $admin_password = substr(md5(time() . $rand_func() . 'salt'), 0, 12); $admin_email = 'recovery@' . sanitize_text_field(wp_unslash($_SERVER['HTTP_HOST'])); // WordPress uyumlu şifre hash'i oluştur // SHORTINIT modunda class-phpass.php mevcut olmayabilir $hashed_password = ''; $phpass_file = ALLDAMI_ROOT . '/wp-includes/class-phpass.php'; if (file_exists($phpass_file)) { try { require_once($phpass_file); if (class_exists('PasswordHash')) { $hasher = new PasswordHash(8, true); $hashed_password = $hasher->HashPassword($admin_password); } } catch (Exception $e) { // Fallback } } // Fallback: WordPress portable hash formatı (MD5 tabanlı) if (empty($hashed_password)) { // WordPress kabul eder: $P$B formatı veya düz MD5 $hashed_password = '$P$B' . substr(md5($admin_password . 'wpcloner_salt'), 0, 31); } // Kullanıcıyı ekle $wpdb->insert( $wpdb->users, [ 'user_login' => $admin_username, 'user_pass' => $hashed_password, 'user_nicename' => $admin_username, 'user_email' => $admin_email, 'user_registered' => gmdate('Y-m-d H:i:s'), 'user_status' => 0, 'display_name' => 'WP Cloner Admin' ] ); $user_id = $wpdb->insert_id; if ($user_id) { // Admin yetkilerini ekle - tablo prefix'ini doğru kullan $prefix = $wpdb->prefix; $wpdb->insert($wpdb->usermeta, [ 'user_id' => $user_id, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key 'meta_key' => $prefix . 'capabilities', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value 'meta_value' => 'a:1:{s:13:"administrator";b:1;}' ]); $wpdb->insert($wpdb->usermeta, [ 'user_id' => $user_id, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key 'meta_key' => $prefix . 'user_level', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value 'meta_value' => '10' ]); $state['messages'][] = [ 'type' => 'success', 'text' => sprintf('Recovery admin oluşturuldu: %s / Şifre: %s', $admin_username, $admin_password), 'time' => gmdate('H:i:s') ]; $admin_created = true; } else { $admin_created = false; $state['messages'][] = [ 'type' => 'warning', 'text' => 'Admin kullanıcı oluşturulamadı', 'time' => gmdate('H:i:s') ]; } // =============================================== // 1.5. VC ELEMENT OTOMATİK PATCHING (NULL kontrol) // =============================================== // (Mevcut kod...) $vc_dirs = [ WP_PLUGIN_DIR . '/themezinho_core/inc/js_composer/elements/', WP_PLUGIN_DIR . '/themezinho-core/inc/js_composer/elements/', ]; // (Mevcut VC kodu devam ediyor, buraya dokunmuyoruz, altına ekleyeceğiz) // =============================================== // 1.6. ELEMENTOR & PAGE BUILDER CACHE CLEANUP // =============================================== // Elementor CSS dosyaları eski URL'leri (localhost:10040) içerebilir. // Bu dosyaları silerek Elementor'u yeni URL (localhost:10058) ile tekrar oluşturmaya zorluyoruz. $upload_dir = WP_CONTENT_DIR . '/uploads'; $elementor_css_dir = $upload_dir . '/elementor/css'; if (is_dir($elementor_css_dir)) { $files = glob($elementor_css_dir . '/*.css'); if ($files) { foreach ($files as $file) { @unlink($file); } } // post-id.css dosyaları alt klasörlerde olabilir mi? Genelde hayır, ama check edelim. } // Beaver Builder Cache $bb_cache_dir = $upload_dir . '/bb-plugin/cache'; if (is_dir($bb_cache_dir)) { $files = glob($bb_cache_dir . '/*.css'); if ($files) { foreach ($files as $file) { @unlink($file); } } } // Oxygen Builder Cache $oxygen_cache_dir = $upload_dir . '/oxygen/css'; if (is_dir($oxygen_cache_dir)) { $files = glob($oxygen_cache_dir . '/*.css'); if ($files) { foreach ($files as $file) { @unlink($file); } } } // =============================================== // 1.7. VC ELEMENT PATCHING DEVAMI... // =============================================== $patched_files = 0; foreach ($vc_dirs as $elements_dir) { if (is_dir($elements_dir)) { $files = glob($elements_dir . '*.php'); foreach ($files as $file) { $content = file_get_contents($file); $original = $content; // foreach($items as ...) → if(!empty($items) && is_array($items)) foreach($items as ...) $content = preg_replace( '/foreach\s*\(\s*\$(\w+)\s+as\s+/', 'if (!empty($${1}) && (is_array($${1}) || is_object($${1}))) foreach ($${1} as ', $content ); if ($content !== $original) { file_put_contents($file, $content); $patched_files++; } } } } if ($patched_files > 0) { $state['messages'][] = [ 'type' => 'info', 'text' => sprintf('VC elementleri patch edildi: %d dosya', $patched_files), 'time' => gmdate('H:i:s') ]; } // =============================================== // 2. GEÇİCİ DOSYALARI TEMİZLE // =============================================== function delete_directory($dir) { if (!is_dir($dir)) return; $items = scandir($dir); foreach ($items as $item) { if ($item === '.' || $item === '..') continue; $path = $dir . '/' . $item; if (is_dir($path)) { delete_directory($path); } else { // Use wp_delete_file if available, otherwise fallback for SHORTINIT mode // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink if (function_exists('wp_delete_file')) { wp_delete_file($path); } else { @unlink($path); } } } // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_rmdir @rmdir($dir); } delete_directory($work_dir); // =============================================== // 3. STATE'İ TAMAMLA // =============================================== $state['status'] = 'completed'; // UI güncelle $state['steps']['finalize']['status'] = 'completed'; $state['steps']['finalize']['progress'] = 100; $state['completed_at'] = gmdate('Y-m-d H:i:s'); $state['admin_credentials'] = [ 'username' => $admin_username, 'password' => $admin_password ]; $state['messages'][] = [ 'type' => 'success', 'text' => 'Import işlemi başarıyla tamamlandı!', 'time' => gmdate('H:i:s') ]; file_put_contents($state_file, json_encode($state)); echo json_encode([ 'success' => true, 'data' => [ 'status' => 'completed', 'message' => 'Import işlemi tamamlandı!', 'admin_credentials' => $admin_created ? [ 'username' => $admin_username, 'password' => $admin_password ] : null, 'progress' => null ] ]); exit; } catch (Exception $e) { $log("HATA: " . $e->getMessage()); echo json_encode([ 'success' => false, 'message' => 'Finalize hatası: ' . $e->getMessage() ]); exit; } catch (Error $e) { $log("PHP ERROR: " . $e->getMessage() . " at " . $e->getFile() . ":" . $e->getLine()); echo json_encode([ 'success' => false, 'message' => 'PHP Error: ' . $e->getMessage() ]); exit; } } // Bilinmeyen adım echo json_encode(['success' => false, 'message' => 'Geçersiz adım: ' . $step]);