Skip to content

Instantly share code, notes, and snippets.

@shanept
Last active June 24, 2025 10:25
Show Gist options
  • Save shanept/9691fad6cf5dc446d7c56f39df7644cd to your computer and use it in GitHub Desktop.
Save shanept/9691fad6cf5dc446d7c56f39df7644cd to your computer and use it in GitHub Desktop.
Wordpress Migration Agent

WordPress Migration Tool

This migrate.php script facilitates the migration of a WordPress site by extracting a website codebase from an archive (.zip or .tar.gz), importing a database from an SQL file, optionally updating domain URLs, and configuring wp-config.php with new database credentials.

Instructions for Use

  1. Place your WordPress codebase archive (.zip or .tar.gz) and database export (.sql) in the same directory as migrate.php.

  2. Open migrate.php in a web browser (e.g., https://yourdomain.com/migrate.php).

  3. Complete the Form.

  4. Progress messages will be displayed on the page, indicating success or errors.

  5. Verify the site is accessible at the new location or domain.

  6. Remove migrate.php from the server for security.

Server Requirements

  • PHP: Version 7.2 or higher
  • PHP Extensions:
    • mysqli: For database operations
    • ZipArchive: For handling .zip archives
    • PharData: For handling .tar.gz archives
  • Web Server: Apache, Nginx, or similar, with PHP support
  • Permissions: Write access to the directory containing migrate.php, the archive, SQL file, and wp-config.php (if present)
  • Database: MySQL server with a user having sufficient privileges to create tables and execute SQL statements
  • Browser: Modern browser with JavaScript enabled (for AJAX functionality)

Potential Issues and Solutions

  1. Directory Not Writable:

    • Symptom: "Directory not writable" error at startup.
    • Solution: Ensure the web server user (e.g., www-data on Linux) has write permissions (chmod 775 or ownership (chown) of the directory.
  2. Missing Required PHP Modules:

    • Symptom: Errors like "ZipArchive not available" or "PharData module not available."
    • Solution: Install missing PHP extensions:
      • For .zip: Install php-zip (e.g., sudo apt-get install php-zip on Ubuntu).
      • For .tar.gz: Ensure php-phar is enabled.
      • Restart the web server after installation.
  3. Database Connection Failure:

    • Symptom: "Failed to connect" error when testing database credentials.
    • Solution: Verify host, username, password, and database name. Ensure the database exists and the user has appropriate permissions. Check server firewall or network settings.
  4. SQL File Import Errors:

    • Symptom: "SQL error" during migration.
    • Solution: Ensure the SQL file is valid UTF-8 encoded and compatible with the target MySQL version. Check for large file size issues; increase PHP’s upload_max_filesize and post_max_size if needed.
    • Note: The import script performs basic SQL parsing upon the file to stagger statements. This avoids overwhelming the database engine, however it may introduce issues with parsing of complex SQL statements. If you are having repeated SQL import issues, please contact me on github.
  5. wp-config.php Update Failure:

    • Symptom: "Failed to update wp-config.php" error.
    • Solution: Ensure wp-config.php exists in the extracted codebase and is writable. If non-standard syntax is used, manually update the file with new credentials.
  6. Domain Migration Issues:

    • Symptom: URLs not updated correctly or site breaks after migration.
    • Solution: Verify the new domain is entered without http:// or https://. Check for complex serialized data; in rare cases, manual database updates may be required.
  7. AJAX Request Failures:

    • Symptom: "AJAX request failed" error.
    • Solution: Ensure the server allows POST requests and check for network issues. Verify jQuery loads correctly (CDN availability).
  8. Large Archive or SQL File Issues:

    • Symptom: Timeout or memory errors during extraction or import.
    • Solution: Increase PHP’s memory_limit (e.g., 256M), max_execution_time (e.g., 300), and max_input_time in php.ini. Consider splitting large SQL files.

Notes

  • Security: Remove migrate.php after use to prevent unauthorized access.
  • Backups: Always back up your files and database before running the migration.
  • Testing: Test the migration on a staging environment first to avoid production issues.
  • Custom Configurations: Non-standard wp-config.php or database table prefixes may require manual adjustments.
<?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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment