|
<?php |
|
// Prevent user abort but respect per-batch time limits |
|
ignore_user_abort(true); |
|
|
|
// Error reporting for debugging |
|
ini_set('display_errors', 1); |
|
ini_set('display_startup_errors', 1); |
|
error_reporting(E_ALL); |
|
|
|
// Maximum execution time per AJAX call (in seconds) |
|
define('MAX_EXECUTION_TIME', 25); |
|
|
|
// Function to check write permissions |
|
function check_write_permissions() { |
|
$test_file = getcwd() . '/test_write_' . uniqid() . '.txt'; |
|
$result = @file_put_contents($test_file, 'test'); |
|
if ($result !== false) { |
|
unlink($test_file); |
|
return ['success' => true, 'message' => 'Directory is writable.']; |
|
} |
|
return ['success' => false, 'message' => 'Directory is not writable. Please set appropriate permissions.']; |
|
} |
|
|
|
// Function to check required PHP modules |
|
function check_archive_modules($archives) { |
|
$has_zip = class_exists('ZipArchive'); |
|
$has_phar = class_exists('PharData'); |
|
$issues = []; |
|
|
|
foreach ($archives as $archive) { |
|
$ext = strtolower(pathinfo($archive, PATHINFO_EXTENSION)); |
|
if ($ext === 'zip' && !$has_zip) { |
|
$issues[] = 'ZipArchive module is not available for .zip files.'; |
|
} elseif ($ext === 'gz' && !$has_phar) { |
|
$issues[] = 'PharData module is not available for .tar.gz files.'; |
|
} |
|
} |
|
|
|
if (count($issues) == 0) { |
|
return ['success' => true, 'message' => 'All required modules are available.']; |
|
} else { |
|
return ['success' => false, 'message' => implode(' ', $issues)]; |
|
} |
|
} |
|
|
|
// Function to update wp-config.php |
|
function update_wp_config($db_host, $db_user, $db_pass, $db_name) { |
|
$wp_config_path = getcwd() . '/wp-config.php'; |
|
if (!file_exists($wp_config_path)) { |
|
return ['success' => false, 'message' => 'wp-config.php not found.']; |
|
} |
|
|
|
$content = file_get_contents($wp_config_path); |
|
if ($content === false) { |
|
return ['success' => false, 'message' => 'Failed to read wp-config.php.']; |
|
} |
|
|
|
$replacements = [ |
|
"/define\s*\(\s*['\"]DB_HOST['\"]\s*,\s*['\"][^'\"]*['\"]\s*\);/" => "define('DB_HOST', '$db_host');", |
|
"/define\s*\(\s*['\"]DB_USER['\"]\s*,\s*['\"][^'\"]*['\"]\s*\);/" => "define('DB_USER', '$db_user');", |
|
"/define\s*\(\s*['\"]DB_PASSWORD['\"]\s*,\s*['\"][^'\"]*['\"]\s*\);/" => "define('DB_PASSWORD', '$db_pass');", |
|
"/define\s*\(\s*['\"]DB_NAME['\"]\s*,\s*['\"][^'\"]*['\"]\s*\);/" => "define('DB_NAME', '$db_name');" |
|
]; |
|
|
|
$updated = false; |
|
foreach ($replacements as $pattern => $replacement) { |
|
if (preg_match($pattern, $content)) { |
|
$content = preg_replace($pattern, $replacement, $content); |
|
$updated = true; |
|
} |
|
} |
|
|
|
if (!$updated) { |
|
return ['success' => false, 'message' => 'Failed to update wp-config.php definitions.']; |
|
} |
|
|
|
if (file_put_contents($wp_config_path, $content) === false) { |
|
return ['success' => false, 'message' => 'Failed to write to wp-config.php.']; |
|
} |
|
|
|
return ['success' => true, 'message' => 'wp-config.php updated successfully.']; |
|
} |
|
|
|
// Function to drop all tables in the database |
|
function drop_all_tables($conn) { |
|
$tables = mysqli_query($conn, "SHOW TABLES"); |
|
if ($tables) { |
|
while ($table = mysqli_fetch_array($tables)) { |
|
$table_name = $table[0]; |
|
mysqli_query($conn, "DROP TABLE `$table_name`"); |
|
} |
|
} |
|
} |
|
|
|
// Function to parse SQL file into statements |
|
function parse_sql_statements($sql_file, &$line_number) { |
|
$sql_content = file_get_contents($sql_file); |
|
if ($sql_content === false) { |
|
return false; |
|
} |
|
|
|
$statements = []; |
|
$current_statement = ''; |
|
$in_comment = false; |
|
$in_quote = false; |
|
$quote_char = ''; |
|
$line_number = 0; |
|
$escape_next = false; |
|
$hex_escape = false; |
|
$hex_chars = ''; |
|
|
|
$lines = explode("\n", $sql_content); |
|
foreach ($lines as $line) { |
|
$line_number++; |
|
$line = trim($line); |
|
|
|
if (empty($line)) { |
|
continue; |
|
} |
|
|
|
if (strpos($line, '/*') === 0 && !$in_comment) { |
|
$in_comment = true; |
|
if (strpos($line, '*/') !== false) { |
|
$in_comment = false; |
|
} |
|
continue; |
|
} |
|
if ($in_comment) { |
|
if (strpos($line, '*/') !== false) { |
|
$in_comment = false; |
|
} |
|
continue; |
|
} |
|
|
|
if (preg_match('/^(--|#)/', $line)) { |
|
continue; |
|
} |
|
|
|
$chars = str_split($line); |
|
for ($i = 0; $i < strlen($line); $i++) { |
|
$char = $chars[$i]; |
|
|
|
if ($escape_next) { |
|
$current_statement .= $char; |
|
$escape_next = false; |
|
continue; |
|
} |
|
|
|
if ($hex_escape) { |
|
$hex_chars .= $char; |
|
if (strlen($hex_chars) == 2) { |
|
$current_statement .= $hex_chars; |
|
$hex_escape = false; |
|
$hex_chars = ''; |
|
} |
|
continue; |
|
} |
|
|
|
if ($in_quote) { |
|
if ($char === '\\') { |
|
$escape_next = true; |
|
$current_statement .= $char; |
|
continue; |
|
} |
|
if ($char === 'x' && $current_statement[strlen($current_statement) - 1] === '\\') { |
|
$hex_escape = true; |
|
$current_statement .= $char; |
|
continue; |
|
} |
|
if ($char === $quote_char) { |
|
if (isset($chars[$i + 1]) && $chars[$i + 1] === $quote_char) { |
|
$current_statement .= $char; |
|
$i++; |
|
$current_statement .= $char; |
|
continue; |
|
} |
|
$in_quote = false; |
|
$quote_char = ''; |
|
} |
|
$current_statement .= $char; |
|
continue; |
|
} |
|
|
|
if ($char === '"' || $char === "'") { |
|
$in_quote = true; |
|
$quote_char = $char; |
|
$current_statement .= $char; |
|
continue; |
|
} |
|
|
|
if ($char === ';' && !$in_comment) { |
|
$current_statement = trim($current_statement); |
|
if (!empty($current_statement)) { |
|
$statements[] = ['statement' => $current_statement, 'line' => $line_number]; |
|
$current_statement = ''; |
|
} |
|
continue; |
|
} |
|
|
|
$current_statement .= $char; |
|
} |
|
|
|
if (!empty($current_statement)) { |
|
$current_statement .= "\n"; |
|
} |
|
} |
|
|
|
$current_statement = trim($current_statement); |
|
if (!empty($current_statement)) { |
|
$statements[] = ['statement' => $current_statement, 'line' => $line_number]; |
|
} |
|
|
|
return $statements; |
|
} |
|
|
|
// Handle AJAX requests |
|
if (isset($_POST['action'])) { |
|
header('Content-Type: application/json'); |
|
|
|
if ($_POST['action'] === 'check_prerequisites') { |
|
$write_check = check_write_permissions(); |
|
if (!$write_check['success']) { |
|
echo json_encode($write_check); |
|
exit; |
|
} |
|
|
|
$archives = glob("*.{zip,tar.gz}", GLOB_BRACE); |
|
$module_check = check_archive_modules($archives); |
|
if (!$module_check['success']) { |
|
echo json_encode($module_check); |
|
exit; |
|
} |
|
|
|
$sql_files = glob("*.sql"); |
|
if (!is_array($sql_files)) { |
|
$sql_files = []; |
|
} |
|
echo json_encode([ |
|
'success' => true, |
|
'message' => 'Prerequisites checked successfully.', |
|
'archives' => $archives, |
|
'sql_files' => $sql_files |
|
]); |
|
exit; |
|
} |
|
|
|
if ($_POST['action'] === 'test_db') { |
|
$host = $_POST['host']; |
|
$user = $_POST['user']; |
|
$pass = $_POST['pass']; |
|
$db = $_POST['db']; |
|
|
|
$response = ['success' => false, 'message' => '']; |
|
$conn = @mysqli_connect($host, $user, $pass, $db); |
|
|
|
if ($conn) { |
|
$response['success'] = true; |
|
$response['message'] = 'Database connection successful!'; |
|
mysqli_close($conn); |
|
} else { |
|
$response['message'] = 'Failed to connect: ' . mysqli_connect_error(); |
|
} |
|
|
|
echo json_encode($response); |
|
exit; |
|
} |
|
|
|
if ($_POST['action'] === 'init_sql_import') { |
|
$sql_file = $_POST['sql_file']; |
|
$db_host = $_POST['db_host']; |
|
$db_user = $_POST['db_user']; |
|
$db_pass = $_POST['db_pass']; |
|
$db_name = $_POST['db_name']; |
|
|
|
if (!file_exists($sql_file)) { |
|
echo json_encode(['success' => false, 'message' => 'SQL file not found.']); |
|
exit; |
|
} |
|
|
|
$state_file = getcwd() . '/migration_state.json'; |
|
$line_number = 0; |
|
$statements = parse_sql_statements($sql_file, $line_number); |
|
if ($statements === false) { |
|
echo json_encode(['success' => false, 'message' => 'Failed to parse SQL file.']); |
|
exit; |
|
} |
|
|
|
$state = [ |
|
'sql_file' => $sql_file, |
|
'db_host' => $db_host, |
|
'db_user' => $db_user, |
|
'db_pass' => $db_pass, |
|
'db_name' => $db_name, |
|
'total_statements' => count($statements), |
|
'processed_statements' => 0, |
|
'batch_size' => 100, |
|
'statements' => $statements |
|
]; |
|
|
|
file_put_contents($state_file, json_encode($state)); |
|
echo json_encode(['success' => true, 'message' => 'SQL import initialized.']); |
|
exit; |
|
} |
|
|
|
if ($_POST['action'] === 'process_sql_batch') { |
|
$state_file = getcwd() . '/migration_state.json'; |
|
if (!file_exists($state_file)) { |
|
echo json_encode(['success' => false, 'message' => 'Migration state file not found.']); |
|
exit; |
|
} |
|
|
|
$state = json_decode(file_get_contents($state_file), true); |
|
$conn = @mysqli_connect($state['db_host'], $state['db_user'], $state['db_pass'], $state['db_name']); |
|
if (!$conn) { |
|
echo json_encode(['success' => false, 'message' => 'Database connection failed: ' . mysqli_connect_error()]); |
|
exit; |
|
} |
|
|
|
set_time_limit(MAX_EXECUTION_TIME + 5); |
|
|
|
$start_time = time(); |
|
$processed = $state['processed_statements']; |
|
$batch_size = $state['batch_size']; |
|
$statements = $state['statements']; |
|
$response = ['success' => true, 'message' => '', 'complete' => false]; |
|
|
|
for ($i = $processed; $i < min($processed + $batch_size, count($statements)); $i++) { |
|
if ((time() - $start_time) > MAX_EXECUTION_TIME) { |
|
$state['processed_statements'] = $i; |
|
file_put_contents($state_file, json_encode($state)); |
|
$response['message'] = 'Batch processed, continuing...'; |
|
$response['progress'] = round(($i / count($statements)) * 100, 2); |
|
echo json_encode($response); |
|
mysqli_close($conn); |
|
exit; |
|
} |
|
|
|
$stmt = $statements[$i]['statement']; |
|
if (!empty($stmt)) { |
|
if (!mysqli_query($conn, $stmt)) { |
|
$error = mysqli_error($conn); |
|
$line_number = $statements[$i]['line']; |
|
unlink($state_file); |
|
|
|
if (preg_match('/at line (\d+)/i', $error, $matches)) { |
|
$error_line = (int)$matches[1]; |
|
$line_number = max($line_number, $line_number + ($error_line - 1)); |
|
} |
|
|
|
echo json_encode([ |
|
'success' => false, |
|
'message' => "SQL error at approximately line $line_number: $error" |
|
]); |
|
mysqli_close($conn); |
|
exit; |
|
} |
|
} |
|
} |
|
|
|
$state['processed_statements'] = $i; |
|
if ($i >= count($statements)) { |
|
unlink($state_file); |
|
$response['complete'] = true; |
|
$response['message'] = 'Database imported successfully.'; |
|
} else { |
|
file_put_contents($state_file, json_encode($state)); |
|
$response['message'] = 'Batch processed, continuing...'; |
|
$response['progress'] = round(($i / count($statements)) * 100, 2); |
|
} |
|
|
|
mysqli_close($conn); |
|
echo json_encode($response); |
|
exit; |
|
} |
|
|
|
if ($_POST['action'] === 'reset_sql_import') { |
|
$state_file = getcwd() . '/migration_state.json'; |
|
if (!file_exists($state_file)) { |
|
echo json_encode(['success' => false, 'message' => 'Migration state file not found.']); |
|
exit; |
|
} |
|
|
|
$state = json_decode(file_get_contents($state_file), true); |
|
$conn = @mysqli_connect($state['db_host'], $state['db_user'], $state['db_pass'], $state['db_name']); |
|
if (!$conn) { |
|
echo json_encode(['success' => false, 'message' => 'Database connection failed: ' . mysqli_connect_error()]); |
|
exit; |
|
} |
|
|
|
drop_all_tables($conn); |
|
mysqli_close($conn); |
|
|
|
$state['processed_statements'] = 0; |
|
$state['batch_size'] = max(1, floor($state['batch_size'] * 0.75)); |
|
file_put_contents($state_file, json_encode($state)); |
|
|
|
echo json_encode(['success' => true, 'message' => 'Database reset, retrying with reduced batch size.']); |
|
exit; |
|
} |
|
|
|
if ($_POST['action'] === 'migrate') { |
|
$archive = $_POST['archive']; |
|
$sql_file = $_POST['sql_file']; |
|
$db_host = $_POST['db_host']; |
|
$db_user = $_POST['db_user']; |
|
$db_pass = $_POST['db_pass']; |
|
$db_name = $_POST['db_name']; |
|
$do_domain_migration = isset($_POST['do_domain_migration']) && $_POST['do_domain_migration'] === 'yes'; |
|
$new_domain = $_POST['new_domain'] ?? ''; |
|
|
|
$response = ['success' => false, 'message' => '']; |
|
|
|
if (!file_exists($archive)) { |
|
$response['message'] = 'Archive file not found.'; |
|
echo json_encode($response); |
|
exit; |
|
} |
|
|
|
$ext = strtolower(pathinfo($archive, PATHINFO_EXTENSION)); |
|
if ($ext === 'zip') { |
|
$zip = new ZipArchive; |
|
if ($zip->open($archive) === true) { |
|
$zip->extractTo(getcwd()); |
|
$zip->close(); |
|
$response['message'] .= 'Archive extracted successfully. '; |
|
} else { |
|
$response['message'] = 'Failed to extract .zip archive.'; |
|
echo json_encode($response); |
|
exit; |
|
} |
|
} elseif ($ext === 'gz') { |
|
try { |
|
$phar = new PharData($archive); |
|
$phar->extractTo(getcwd(), null, true); |
|
$response['message'] .= 'Archive extracted successfully. '; |
|
} catch (Exception $e) { |
|
$response['message'] = 'Failed to extract .tar.gz archive: ' . $e->getMessage(); |
|
echo json_encode($response); |
|
exit; |
|
} |
|
} |
|
|
|
if ($do_domain_migration && !empty($new_domain)) { |
|
$conn = @mysqli_connect($db_host, $db_user, $db_pass, $db_name); |
|
if (!$conn) { |
|
$response['message'] .= 'Database connection failed: ' . mysqli_connect_error(); |
|
echo json_encode($response); |
|
exit; |
|
} |
|
|
|
$query = "SELECT option_value FROM wp_options WHERE option_name = 'siteurl'"; |
|
$result = mysqli_query($conn, $query); |
|
if ($result && $row = mysqli_fetch_assoc($result)) { |
|
$old_domain = $row['option_value']; |
|
} else { |
|
$response['message'] .= 'Failed to retrieve old domain from wp_options.'; |
|
echo json_encode($response); |
|
exit; |
|
} |
|
|
|
$old_domain = rtrim(preg_replace('#^https?://(www\.)?#', '', $old_domain), '/'); |
|
$new_domain = rtrim(preg_replace('#^https?://(www\.)?#', '', $new_domain), '/'); |
|
$old_variations = [ |
|
"http://$old_domain", |
|
"https://$old_domain", |
|
"http://www.$old_domain", |
|
"https://www.$old_domain" |
|
]; |
|
$new_domain_url = "https://$new_domain"; |
|
|
|
$updates = [ |
|
"UPDATE wp_options SET option_value = %s WHERE option_name IN ('siteurl', 'home')" |
|
]; |
|
foreach ($updates as $update) { |
|
$query = sprintf($update, "'$new_domain_url'"); |
|
if (!mysqli_query($conn, $query)) { |
|
$response['message'] .= 'Error updating wp_options: ' . mysqli_error($conn); |
|
echo json_encode($response); |
|
exit; |
|
} |
|
} |
|
|
|
$tables = mysqli_query($conn, "SHOW TABLES"); |
|
while ($table = mysqli_fetch_array($tables)[0]) { |
|
$columns = mysqli_query($conn, "SHOW COLUMNS FROM `$table` WHERE Type LIKE '%text%' OR Type LIKE '%varchar%'"); |
|
while ($column = mysqli_fetch_assoc($columns)) { |
|
$col_name = $column['Field']; |
|
$rows = mysqli_query($conn, "SELECT `$col_name` FROM `$table` WHERE `$col_name` LIKE '%$old_domain%'"); |
|
while ($row = mysqli_fetch_assoc($rows)) { |
|
$value = $row[$col_name]; |
|
$new_value = $value; |
|
|
|
if (($unserialized = @unserialize($value)) !== false) { |
|
$unserialized = recursive_unserialize_replace($old_variations, $new_domain_url, $unserialized); |
|
$new_value = serialize($unserialized); |
|
} else { |
|
foreach ($old_variations as $old) { |
|
$new_value = str_replace($old, $new_domain_url, $new_value); |
|
} |
|
} |
|
|
|
$new_value = mysqli_real_escape_string($conn, $new_value); |
|
$update_query = "UPDATE `$table` SET `$col_name` = '$new_value' WHERE `$col_name` = '" . mysqli_real_escape_string($conn, $value) . "'"; |
|
if (!mysqli_query($conn, $update_query)) { |
|
$response['message'] .= "Error updating `$table`.`$col_name`: " . mysqli_error($conn); |
|
echo json_encode($response); |
|
exit; |
|
} |
|
} |
|
} |
|
} |
|
|
|
$response['message'] .= 'Domain migration completed successfully. '; |
|
mysqli_close($conn); |
|
} |
|
|
|
$config_update = update_wp_config($db_host, $db_user, $db_pass, $db_name); |
|
if (!$config_update['success']) { |
|
$response['message'] .= $config_update['message']; |
|
echo json_encode($response); |
|
exit; |
|
} |
|
$response['message'] .= $config_update['message']; |
|
|
|
$response['success'] = true; |
|
echo json_encode($response); |
|
exit; |
|
} |
|
} |
|
|
|
function recursive_unserialize_replace($old_variations, $new_domain_url, $data) { |
|
if (is_string($data)) { |
|
foreach ($old_variations as $old) { |
|
$data = str_replace($old, $new_domain_url, $data); |
|
} |
|
} elseif (is_array($data)) { |
|
foreach ($data as $key => $value) { |
|
$data[$key] = recursive_unserialize_replace($old_variations, $new_domain_url, $value); |
|
} |
|
} elseif (is_object($data)) { |
|
foreach ($data as $key => $value) { |
|
$data->$key = recursive_unserialize_replace($old_variations, $new_domain_url, $value); |
|
} |
|
} |
|
return $data; |
|
} |
|
|
|
?> |
|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>WordPress Migration Tool</title> |
|
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> |
|
<style> |
|
/* Meyer Reset CSS v2.0 */ |
|
html, body, div, span, applet, object, iframe, |
|
h1, h2, h3, h4, h5, h6, p, blockquote, pre, |
|
a, abbr, acronym, address, big, cite, code, |
|
del, dfn, em, img, ins, kbd, q, s, samp, |
|
small, strike, strong, sub, sup, tt, var, |
|
b, u, i, center, |
|
dl, dt, dd, ol, ul, li, |
|
fieldset, form, label, legend, |
|
table, caption, tbody, tfoot, thead, tr, th, td, |
|
article, aside, canvas, details, embed, |
|
figure, figcaption, footer, header, hgroup, |
|
menu, nav, output, ruby, section, summary, |
|
time, mark, audio, video { |
|
margin: 0; |
|
padding: 0; |
|
border: 0; |
|
font-size: 100%; |
|
font: inherit; |
|
vertical-align: baseline; |
|
} |
|
article, aside, details, figcaption, figure, |
|
footer, header, hgroup, menu, nav, section { |
|
display: block; |
|
} |
|
body { |
|
line-height: 1; |
|
} |
|
ol, ul { |
|
list-style: none; |
|
} |
|
blockquote, q { |
|
quotes: none; |
|
} |
|
blockquote:before, blockquote:after, |
|
q:before, q:after { |
|
content: ''; |
|
content: none; |
|
} |
|
table { |
|
border-collapse: collapse; |
|
border-spacing: 0; |
|
} |
|
|
|
/* Box-Sizing */ |
|
html { |
|
box-sizing: border-box; |
|
} |
|
*, *:before, *:after { |
|
box-sizing: inherit; |
|
} |
|
|
|
/* Custom Styles */ |
|
body { |
|
font-family: 'Segoe UI', Arial, sans-serif; |
|
background-color: #f4f7fa; |
|
min-height: 100vh; |
|
display: flex; |
|
flex-direction: column; |
|
justify-content: space-between; |
|
} |
|
.page { |
|
display: none; |
|
flex: 1; |
|
flex-direction: column; |
|
justify-content: space-between; |
|
max-width: 600px; |
|
margin: 0 auto; |
|
padding: 20px; |
|
} |
|
.page.active { |
|
display: flex; |
|
} |
|
h1 { |
|
font-size: 2.2em; |
|
color: #2c3e50; |
|
text-align: center; |
|
margin: 20px 0; |
|
font-weight: 600; |
|
} |
|
#page-1 .subtitle { |
|
text-align: right; |
|
font-size: 1.1em; |
|
color: #7f8c8d; |
|
line-height: 1.5; |
|
margin-bottom: 10px; |
|
} |
|
.subtitle a { |
|
color: #3498db; |
|
text-decoration: none; |
|
} |
|
.subtitle a:hover { |
|
text-decoration: underline; |
|
} |
|
.content { |
|
background: white; |
|
padding: 30px; |
|
border-radius: 12px; |
|
box-shadow: 0 4px 20px rgba(0,0,0,0.1); |
|
width: 100%; |
|
max-width: 500px; |
|
margin: auto; |
|
} |
|
.content p { |
|
font-size: 1em; |
|
color: #34495e; |
|
text-align: center; |
|
line-height: 1.5; |
|
} |
|
.form-group { |
|
margin-bottom: 20px; |
|
position: relative; |
|
} |
|
.form-group label { |
|
display: inline-flex; |
|
align-items: center; |
|
font-weight: 600; |
|
color: #34495e; |
|
margin-bottom: 8px; |
|
} |
|
.form-group input, .form-group select { |
|
width: 100%; |
|
padding: 12px; |
|
border: 1px solid #dfe6e9; |
|
border-radius: 6px; |
|
font-size: 1em; |
|
transition: border-color 0.3s, box-shadow 0.3s; |
|
} |
|
.form-group input:focus, .form-group select:focus { |
|
border-color: #3498db; |
|
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1); |
|
outline: none; |
|
} |
|
.form-group select[size] { |
|
height: auto; |
|
padding: 8px; |
|
} |
|
.password-wrapper { |
|
position: relative; |
|
} |
|
.password-wrapper input { |
|
padding-right: 48px; |
|
} |
|
.password-toggle { |
|
position: absolute; |
|
right: 0; |
|
top: calc(50% - 19px); |
|
background: none; |
|
border: none; |
|
color: #7f8c8d; |
|
cursor: pointer; |
|
font-size: 14px; |
|
margin-bottom: 2px; |
|
padding: 12px; |
|
} |
|
.password-toggle:hover { |
|
color: #3498db; |
|
} |
|
.help-icon { |
|
display: inline-block; |
|
width: 16px; |
|
height: 16px; |
|
color: #3498db; |
|
text-align: center; |
|
line-height: 16px; |
|
border: 1px solid #3498db; |
|
border-radius: 50%; |
|
margin-left: 8px; |
|
cursor: help; |
|
font-size: 12px; |
|
position: relative; |
|
} |
|
.help-icon:hover::after { |
|
content: attr(data-tooltip); |
|
position: absolute; |
|
top: 100%; |
|
left: 50%; |
|
transform: translateX(-50%); |
|
background-color: #2c3e50; |
|
color: white; |
|
padding: 8px 12px; |
|
border-radius: 6px; |
|
font-size: 14px; |
|
width: 200px; |
|
z-index: 10; |
|
white-space: normal; |
|
box-shadow: 0 2px 10px rgba(0,0,0,0.2); |
|
} |
|
.error { |
|
color: #e74c3c; |
|
font-size: 0.9em; |
|
margin-top: 5px; |
|
display: block; |
|
} |
|
.success { |
|
color: #2ecc71; |
|
font-size: 0.9em; |
|
margin-top: 5px; |
|
display: block; |
|
} |
|
.success-message { |
|
font-size: 1.8em; |
|
color: #2ecc71; |
|
text-align: center; |
|
font-weight: 600; |
|
padding: 20px; |
|
background-color: #e8f5e9; |
|
border-radius: 12px; |
|
animation: fadeIn 1s ease-in; |
|
} |
|
@keyframes fadeIn { |
|
0% { opacity: 0; transform: scale(0.95); } |
|
100% { opacity: 1; transform: scale(1); } |
|
} |
|
#migration-progress { |
|
margin-top: 20px; |
|
font-style: italic; |
|
color: #7f8c8d; |
|
} |
|
.navigation { |
|
display: flex; |
|
justify-content: flex-end; |
|
padding: 20px 0; |
|
position: sticky; |
|
bottom: 0; |
|
} |
|
button { |
|
padding: 12px 24px; |
|
margin-left: 10px; |
|
border: none; |
|
border-radius: 6px; |
|
cursor: pointer; |
|
font-size: 1em; |
|
font-weight: 600; |
|
transition: background-color 0.3s, transform 0.1s; |
|
} |
|
button.primary { |
|
background-color: #3498db; |
|
color: white; |
|
} |
|
button.primary:hover { |
|
background-color: #2980b9; |
|
transform: translateY(-1px); |
|
} |
|
button.secondary { |
|
background-color: #95a5a6; |
|
color: white; |
|
} |
|
button.secondary:hover { |
|
background-color: #7f8c8d; |
|
transform: translateY(-1px); |
|
} |
|
button.disabled { |
|
background-color: #ecf0f1; |
|
color: #bdc3c7; |
|
cursor: not-allowed; |
|
transform: none; |
|
} |
|
#begin-button::after { |
|
content: ' \2192'; |
|
} |
|
#prereq-status { |
|
margin-bottom: 20px; |
|
} |
|
.toggle-container { |
|
display: flex; |
|
align-items: center; |
|
margin-bottom: 20px; |
|
} |
|
.toggle-label { |
|
font-weight: 600; |
|
color: #34495e; |
|
margin-right: 15px; |
|
} |
|
.toggle-pill { |
|
position: relative; |
|
width: 80px; |
|
height: 34px; |
|
background-color: #ecf0f1; |
|
border-radius: 17px; |
|
cursor: pointer; |
|
transition: background-color 0.3s; |
|
} |
|
.toggle-pill.yes { |
|
background-color: #2ecc71; |
|
} |
|
.toggle-knob { |
|
position: absolute; |
|
top: 2px; |
|
left: 2px; |
|
width: 30px; |
|
height: 30px; |
|
background-color: white; |
|
border-radius: 50%; |
|
transition: transform 0.3s; |
|
box-shadow: 0 1px 3px rgba(0,0,0,0.2); |
|
} |
|
.toggle-pill.yes .toggle-knob { |
|
transform: translateX(46px); |
|
} |
|
.toggle-text { |
|
position: absolute; |
|
top: 50%; |
|
transform: translateY(-50%); |
|
font-size: 14px; |
|
color: #7f8c8d; |
|
transition: color 0.3s; |
|
} |
|
.toggle-text.yes { |
|
left: 10px; |
|
color: white; |
|
} |
|
.toggle-text.no { |
|
right: 10px; |
|
color: #34495e; |
|
} |
|
.toggle-pill.yes .toggle-text.no { |
|
color: white; |
|
} |
|
.migration-info { |
|
font-size: 0.9em; |
|
color: #34495e; |
|
line-height: 1.5; |
|
margin-bottom: 20px; |
|
} |
|
.migration-details { |
|
font-size: 0.9em; |
|
color: #7f8c8d; |
|
line-height: 1.5; |
|
margin-top: 10px; |
|
display: none; |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<div id="page-1" class="page active"> |
|
<h1>WordPress Migration Tool</h1> |
|
<div class="subtitle">by <a href="https://gist.github.com/shanept/9691fad6cf5dc446d7c56f39df7644cd" target="_blank">shanept</a></div> |
|
<div class="content"> |
|
<p>Easily migrate your WordPress site by uploading files and configuring database settings.</p> |
|
</div> |
|
<div class="navigation"> |
|
<button id="prev-1" class="secondary disabled">Previous</button> |
|
<button id="next-1" class="primary">Next</button> |
|
</div> |
|
</div> |
|
|
|
<div id="page-2" class="page"> |
|
<h1>WordPress Migration Tool</h1> |
|
<div class="content"> |
|
<form id="archive-form"> |
|
<div id="prereq-status"></div> |
|
<div class="form-group"> |
|
<label for="archive">Archive File <span class="help-icon" data-tooltip="Select the .zip or .tar.gz file containing your WordPress files.">?</span></label> |
|
<select name="archive" id="archive" size="5" required></select> |
|
</div> |
|
<div class="form-group"> |
|
<label for="sql_file">SQL File <span class="help-icon" data-tooltip="Select the .sql file containing your WordPress database export.">?</span></label> |
|
<select name="sql_file" id="sql_file" size="5"></select> |
|
</div> |
|
</form> |
|
</div> |
|
<div class="details"> |
|
<h2>Details</h2> |
|
<p>Ensure that .zip or .tar.gz archives are uploaded to the same directory as this script. The .sql file should be the database export from your WordPress site.</p> |
|
</div> |
|
<div class="navigation"> |
|
<button id="prev-page2" class="secondary">Previous</button> |
|
<button id="next-page2" class="primary disabled">Continue</button> |
|
</div> |
|
</div> |
|
|
|
<div id="page-3" class="page"> |
|
<h1>WordPress Migration Tool</h1> |
|
<div class="content"> |
|
<form id="db_form"> |
|
<div class="form-group"> |
|
<label for="host">Database Host <span class="help-icon" data-tooltip="Enter the database host (e.g., localhost or a remote server IP).">?</span></label> |
|
<input type="text" name="host" id="host_field" value="localhost" required> |
|
</div> |
|
<div class="form-group"> |
|
<label for="user">User Name <span class="help-icon" data-tooltip="Enter the database username.">?</span></label> |
|
<input type="text" name="user" id="user_field" required> |
|
</div> |
|
<div class="form-group password"> |
|
<label for="pass">Password <span class="help-icon" data-tooltip="Enter the database password.">?</span></label> |
|
<div class="password-wrapper"> |
|
<input type="password" name="pass" id="password_field" required> |
|
<button type="button" class="password-toggle" data-state="hidden">Show</button> |
|
</div> |
|
</div> |
|
<div class="form-group"> |
|
<label for="db">Database Name <span class="help-icon" data-tooltip="Enter the database name for your WordPress site.">?</span></label> |
|
<input type="text" name="db" id="db_field" required> |
|
</div> |
|
<div class="form-group"> |
|
<button type="button" id="db_connect_test" class="primary">Test Connection</button> |
|
<span id="db_connect_result"></span> |
|
</div> |
|
</form> |
|
</div> |
|
<div class="navigation"> |
|
<button id="prev-page3" class="secondary">Previous</button> |
|
<button id="next-page3" class="primary disabled">Continue</button> |
|
</div> |
|
</div> |
|
|
|
<div id="page-4" class="page"> |
|
<h1>Confirm Migration</h1> |
|
<div class="content"> |
|
<form id="migration_form"> |
|
<p class="migration-info">Domain migration updates your site’s URLs to a new domain. Enable it if you’re changing domains; skip it if you don’t need to update URLs or the domain remains unchanged.</p> |
|
<div class="form-group toggle-container"> |
|
<span class="toggle-label">Perform Domain Migration</span> |
|
<div class="toggle-pill" id="toggle_domain_migration" data-value="no"> |
|
<span class="toggle-text yes">Yes</span> |
|
<span class="toggle-text no">No</span> |
|
<div class="toggle-knob"></div> |
|
<input type="hidden" name="do_domain_migration" id="do_migration_field" value="no"> |
|
</div> |
|
<span class="help-icon" data-tooltip="Toggle to update URLs to a new domain.">?</span> |
|
</div> |
|
<div class="form-group" id="new_domain_form" style="display: none;"> |
|
<label for="new_domain">New Domain <span class="help-icon" data-tooltip="Enter a new domain (e.g., example.com) without http:// or https://.">?</span></label> |
|
<input type="text" name="new_domain" id="new_domain_field"> |
|
<span id="domain_error" class="error"></span> |
|
</div> |
|
<p class="migration-details" id="migration-details">The migration process will search for http://, https://, www., and all instances of your old domain, updating all instances to the new domain to ensure data integrity.</p> |
|
</form> |
|
</div> |
|
<div class="navigation"> |
|
<button id="prev-page4" class="secondary">Previous</button> |
|
<button id="start_migration" class="primary">Start Migration</button> |
|
</div> |
|
</div> |
|
|
|
<div id="page-5" class="page"> |
|
<h1>Migrating Website</h1> |
|
<div class="content"> |
|
<div id="migration-progress"></div> |
|
</div> |
|
</div> |
|
|
|
<script type="text/javascript"> |
|
$(document).ready(function() { |
|
let currentPage = 1; |
|
let prereqData = null; |
|
let dbTested = false; |
|
|
|
// Show/Hide Pages |
|
function showPage(pageNum) { |
|
$('.page').removeClass('active').hide(); |
|
$(`#page-${pageNum}`).addClass('active').show(); |
|
currentPage = pageNum; |
|
} |
|
|
|
// Check Prerequisites on Load |
|
function checkPrerequisites() { |
|
$.ajax({ |
|
url: '', |
|
type: 'POST', |
|
data: { action: 'check_prerequisites' }, |
|
success: function(response) { |
|
prereqData = response; |
|
if (response.success) { |
|
$('#prereq-status').append('<p class="success">' + response.message + '</p>'); |
|
if (response.archives.length > 0) { |
|
response.archives.forEach(function(file) { |
|
$('#archive').append(`<option value="${file}">${file}</option>`); |
|
}); |
|
} else { |
|
$('#archive').append('<option value="">No archives found</option>'); |
|
} |
|
if (response.sql_files.length > 0) { |
|
response.sql_files.forEach(function(file) { |
|
$('#sql_file').append(`<option value="${file}">${file}</option>`); |
|
}); |
|
} else { |
|
$('#sql_file').append('<option value="">No SQL files found</option>'); |
|
} |
|
checkPage2Files(); |
|
} else { |
|
$('#prereq-status').append('<p class="error">' + response.message + '</p>'); |
|
$('#next-1').addClass('disabled').prop('disabled', true); |
|
} |
|
}, |
|
error: function() { |
|
$('#prereq-status').append('<p class="error">Error checking prerequisites. Please try again.</p>'); |
|
$('#next-1').addClass('disabled').prop('disabled', true); |
|
} |
|
}); |
|
} |
|
|
|
checkPrerequisites(); |
|
|
|
// Check File Selection |
|
function checkPage2Files() { |
|
let archive = $('#archive').val(); |
|
let sql_file = $('#sql_file').val(); |
|
if (archive && sql_file && archive !== '' && sql_file !== '') { |
|
$('#next-page2').removeClass('disabled').prop('disabled', false); |
|
} else { |
|
$('#next-page2').addClass('disabled').prop('disabled', true); |
|
} |
|
} |
|
|
|
$('#archive, #sql_file').on('change', checkPage2Files); |
|
|
|
// Password Toggle |
|
$('.password-toggle').click(function() { |
|
const input = $('#password_field'); |
|
const state = $(this).attr('data-state'); |
|
if (state === 'hidden') { |
|
input.attr('type', 'text'); |
|
$(this).text('Hide').attr('data-state', 'visible'); |
|
} else { |
|
input.attr('type', 'password'); |
|
$(this).text('Show').attr('data-state', 'hidden'); |
|
} |
|
}); |
|
|
|
// Validate URL |
|
function validateURL(url) { |
|
const urlPattern = /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/; |
|
return urlPattern.test(url); |
|
} |
|
|
|
// Check Domain Validity and Button State |
|
function checkDomainValidity() { |
|
const doMigration = $('#do_migration_field').val() === 'yes'; |
|
const newDomain = $('#new_domain_field').val().trim(); |
|
const $startMigration = $('#start_migration'); |
|
const $domainError = $('#domain_error'); |
|
|
|
if (doMigration) { |
|
if (newDomain === '') { |
|
$startMigration.addClass('disabled').prop('disabled', true); |
|
$domainError.text('Please enter a new domain.'); |
|
return false; |
|
} else if (!validateURL(newDomain)) { |
|
$startMigration.addClass('disabled').prop('disabled', true); |
|
$domainError.text('Please enter a valid domain (e.g., example.com).'); |
|
return false; |
|
} else { |
|
$startMigration.removeClass('disabled').prop('disabled', false); |
|
$domainError.text(''); |
|
return true; |
|
} |
|
} else { |
|
$startMigration.removeClass('disabled').prop('disabled', false); |
|
$domainError.text(''); |
|
return true; |
|
} |
|
} |
|
|
|
// Toggle Domain Migration |
|
$('#toggle_domain_migration').on('click', function() { |
|
const current_value = $(this).attr('data-value'); |
|
if (current_value === 'no') { |
|
$(this).addClass('yes').attr('data-value', 'yes'); |
|
$('#do_migration_field').val('yes'); |
|
$('#new_domain_form').show(); |
|
$('#migration-details').show(); |
|
$('#new_domain_field').prop('required', true); |
|
checkDomainValidity(); |
|
} else { |
|
$(this).removeClass('yes').attr('data-value', 'no'); |
|
$('#do_migration_field').val('no'); |
|
$('#new_domain_form').hide(); |
|
$('#migration-details').hide(); |
|
$('#new_domain_field').prop('required', false); |
|
checkDomainValidity(); |
|
} |
|
}); |
|
|
|
// New Domain Input Handler |
|
$('#new_domain_field').on('input', function() { |
|
checkDomainValidity(); |
|
}); |
|
|
|
// Start Migration Button Handler |
|
$('#start_migration').on('click', function() { |
|
if (!$(this).hasClass('disabled')) { |
|
$('#migration_form').submit(); |
|
} |
|
}); |
|
|
|
// Navigation Handlers |
|
$('#next-1').on('click', function() { |
|
if (prereqData && prereqData.success) { |
|
showPage(2); |
|
} |
|
}); |
|
|
|
$('#prev-page2').on('click', function() { |
|
showPage(1); |
|
}); |
|
|
|
$('#next-page2').on('click', function() { |
|
if (currentPage === 2 && !$('#next-page2').hasClass('disabled')) { |
|
showPage(3); |
|
} |
|
}); |
|
|
|
$('#prev-page3').on('click', function() { |
|
showPage(2); |
|
}); |
|
|
|
$('#next-page3').on('click', function() { |
|
if (currentPage === 3 && dbTested) { |
|
showPage(4); |
|
} |
|
}); |
|
|
|
$('#prev-page4').on('click', function() { |
|
showPage(3); |
|
}); |
|
|
|
// Test Database Connection |
|
$('#db_connect_test').on('click', function() { |
|
const db_data = { |
|
action: 'test_db', |
|
host: $('#host_field').val(), |
|
user: $('#user_field').val(), |
|
pass: $('#password_field').val(), |
|
db: $('#db_field').val() |
|
}; |
|
|
|
$('#db_connect_result').text('Testing connection...'); |
|
$.ajax({ |
|
url: '', |
|
type: 'POST', |
|
data: db_data, |
|
success: function(response) { |
|
if (response.success) { |
|
$('#db_connect_result').empty().append('<p class="success">' + response.message + '</p>'); |
|
$('#next-page3').removeClass('disabled').prop('disabled', false); |
|
dbTested = true; |
|
} else { |
|
$('#db_connect_result').empty().append('<p class="error">' + response.message + '</p>'); |
|
$('#next-page3').addClass('disabled').prop('disabled', true); |
|
dbTested = false; |
|
} |
|
}, |
|
error: function() { |
|
$('#db_connect_result').empty().append('<p class="error">Database connection test failed. Please try again.</p>'); |
|
$('#next-page3').addClass('disabled').prop('disabled', true); |
|
dbTested = false; |
|
} |
|
}); |
|
}); |
|
|
|
// SQL Import Process |
|
function processSQLImport() { |
|
$.ajax({ |
|
url: '', |
|
type: 'POST', |
|
data: { action: 'process_sql_batch' }, |
|
timeout: 40000, |
|
success: function(response) { |
|
if (response.success) { |
|
$('#migration-progress').append('<p class="success">' + response.message + (response.progress ? ` (${response.progress}% complete)` : '') + '</p>'); |
|
if (!response.complete) { |
|
setTimeout(processSQLImport, 1000); |
|
} else { |
|
completeMigration(); |
|
} |
|
} else { |
|
$('#migration-progress').append('<p class="error">' + response.message + '</p>'); |
|
$('#start_migration').prop('disabled', false); |
|
} |
|
}, |
|
error: function() { |
|
$('#migration-progress').append('<p class="error">SQL import timed out, retrying...</p>'); |
|
$.ajax({ |
|
url: '', |
|
type: 'POST', |
|
data: { action: 'reset_sql_import' }, |
|
success: function(response) { |
|
if (response.success) { |
|
$('#migration-progress').append('<p class="success">' + response.message + '</p>'); |
|
setTimeout(processSQLImport, 1000); |
|
} else { |
|
$('#migration-progress').append('<p class="error">' + response.message + '</p>'); |
|
$('#start_migration').prop('disabled', false); |
|
} |
|
} |
|
}); |
|
} |
|
}); |
|
} |
|
|
|
// Complete Migration |
|
function completeMigration() { |
|
const data = { |
|
action: 'migrate', |
|
archive: $('#archive').val(), |
|
sql_file: $('#sql_file').val(), |
|
db_host: $('#host_field').val(), |
|
db_user: $('#user_field').val(), |
|
db_pass: $('#password_field').val(), |
|
db_name: $('#db_field').val(), |
|
do_domain_migration: $('#do_migration_field').val(), |
|
new_domain: $('#new_domain_field').val() |
|
}; |
|
|
|
$('#migration-progress').append('<p class="success">Finalizing migration...</p>'); |
|
$.ajax({ |
|
url: '', |
|
type: 'POST', |
|
data: data, |
|
success: function(response) { |
|
if (response.success) { |
|
$('#migration-progress').append('<p class="success-message">Migration Completed Successfully! Your WordPress site is now ready.</p>'); |
|
} else { |
|
$('#migration-progress').append('<p class="error">' + response.message + '</p>'); |
|
$('#start_migration').prop('disabled', false); |
|
} |
|
}, |
|
error: function() { |
|
$('#migration-progress').append('<p class="error">Migration failed. Please try again.</p>'); |
|
$('#start_migration').prop('disabled', false); |
|
} |
|
}); |
|
} |
|
|
|
// Form Submission |
|
$('#migration_form').on('submit', function(e) { |
|
e.preventDefault(); |
|
if (!checkDomainValidity()) { |
|
return; |
|
} |
|
const data = { |
|
action: 'init_sql_import', |
|
sql_file: $('#sql_file').val(), |
|
db_host: $('#host_field').val(), |
|
db_user: $('#user_field').val(), |
|
db_pass: $('#password_field').val(), |
|
db_name: $('#db_field').val() |
|
}; |
|
|
|
$('#migration-progress').empty().append('<p class="success">Starting SQL import...</p>'); |
|
$('#start_migration').prop('disabled', true); |
|
showPage(5); |
|
|
|
$.ajax({ |
|
url: '', |
|
type: 'POST', |
|
data: data, |
|
success: function(response) { |
|
if (response.success) { |
|
$('#migration-progress').append('<p class="success">' + response.message + '</p>'); |
|
processSQLImport(); |
|
} else { |
|
$('#migration-progress').append('<p class="error">' + response.message + '</p>'); |
|
$('#start_migration').prop('disabled', false); |
|
} |
|
}, |
|
error: function() { |
|
$('#migration-progress').append('<p class="error">SQL import initialization failed.</p>'); |
|
$('#start_migration').prop('disabled', false); |
|
} |
|
}); |
|
}); |
|
}); |
|
</script> |
|
</body> |
|
</html> |