Includes all work from phases 6-9+ and frontend polish rounds 1 & 2: - Login history with device trust, new device notifications, session management - Churn prevention: cancellation surveys, winback campaigns with email sequences - Financial reports: revenue, P&L, tax, aging, refund, subscription reports with PDF/CSV/JSON export - Configurable checkout: plan config groups/options, build-your-own VPS - Frontend polish: fix broken legal links, add SEO meta tags, favicon, font display=swap, Head titles on all 14 marketing pages, mobile responsive fixes, AuthLayout legal footer, remove false 24/7 claims, hide empty stats, correct uptime SLA to 99.9%, GameServers notify buttons linked to /contact, 301 redirects for /terms and /privacy - WHMCS migration scripts - Update legal page effective dates to March 16, 2026 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
223 lines
7.2 KiB
PHP
223 lines
7.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* WHMCS to EZSCALE Migration Script
|
|
*
|
|
* Usage:
|
|
* php migrate.php Run all phases (fetches from WHMCS API)
|
|
* php migrate.php --phase=1 Run only phase 1
|
|
* php migrate.php --dry-run Run without writing to the database
|
|
* php migrate.php --export-only Export all WHMCS data to JSONL files (no DB writes)
|
|
* php migrate.php --from-export Import from previously exported JSONL files (no API calls)
|
|
* php migrate.php --status Show current migration progress
|
|
* php migrate.php --export-status Show exported JSONL file summary
|
|
* php migrate.php --validate-only Test connectivity without migrating
|
|
* php migrate.php --reset Reset all migration state
|
|
* php migrate.php --reset-exports Delete all exported JSONL files
|
|
* php migrate.php --help Show this help message
|
|
*/
|
|
|
|
require_once __DIR__ . '/vendor/autoload.php';
|
|
|
|
use WhmcsMigrate\MigrationRunner;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Banner
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const VERSION = '2.0.0';
|
|
|
|
$banner = <<<BANNER
|
|
|
|
╔═══════════════════════════════════════════════════╗
|
|
║ WHMCS → EZSCALE Migration Tool v%s ║
|
|
║ %s ║
|
|
╚═══════════════════════════════════════════════════╝
|
|
|
|
BANNER;
|
|
|
|
fprintf(STDOUT, $banner, VERSION, date('Y-m-d H:i:s'));
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// CLI argument parsing
|
|
// ---------------------------------------------------------------------------
|
|
|
|
$options = getopt('', [
|
|
'dry-run',
|
|
'export-only',
|
|
'from-export',
|
|
'phase:',
|
|
'reset',
|
|
'reset-exports',
|
|
'status',
|
|
'export-status',
|
|
'validate-only',
|
|
'help',
|
|
]);
|
|
|
|
if (isset($options['help'])) {
|
|
printUsage();
|
|
exit(0);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Resolve base path (directory containing this script)
|
|
// ---------------------------------------------------------------------------
|
|
|
|
$basePath = __DIR__;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Apply CLI overrides before constructing the runner
|
|
// ---------------------------------------------------------------------------
|
|
|
|
if (isset($options['dry-run'])) {
|
|
putenv('DRY_RUN=true');
|
|
$_ENV['DRY_RUN'] = 'true';
|
|
$_SERVER['DRY_RUN'] = 'true';
|
|
}
|
|
|
|
if (isset($options['export-only'])) {
|
|
putenv('EXPORT_ONLY=true');
|
|
$_ENV['EXPORT_ONLY'] = 'true';
|
|
$_SERVER['EXPORT_ONLY'] = 'true';
|
|
}
|
|
|
|
if (isset($options['from-export'])) {
|
|
putenv('FROM_EXPORT=true');
|
|
$_ENV['FROM_EXPORT'] = 'true';
|
|
$_SERVER['FROM_EXPORT'] = 'true';
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Execute
|
|
// ---------------------------------------------------------------------------
|
|
|
|
try {
|
|
$runner = new MigrationRunner($basePath);
|
|
|
|
if (isset($options['reset'])) {
|
|
$runner->reset();
|
|
exit(0);
|
|
}
|
|
|
|
if (isset($options['reset-exports'])) {
|
|
$runner->resetExports();
|
|
exit(0);
|
|
}
|
|
|
|
if (isset($options['status'])) {
|
|
$runner->showStatus();
|
|
exit(0);
|
|
}
|
|
|
|
if (isset($options['export-status'])) {
|
|
$runner->showExportSummary();
|
|
exit(0);
|
|
}
|
|
|
|
if (isset($options['validate-only'])) {
|
|
$runner->validateOnly();
|
|
exit(0);
|
|
}
|
|
|
|
$phaseNumber = isset($options['phase']) ? (int) $options['phase'] : null;
|
|
|
|
$runner->run($phaseNumber);
|
|
|
|
exit(0);
|
|
} catch (Throwable $e) {
|
|
fprintf(STDERR, "\nFATAL ERROR: %s\n", $e->getMessage());
|
|
fprintf(STDERR, " File: %s:%d\n", $e->getFile(), $e->getLine());
|
|
fprintf(STDERR, " Trace:\n%s\n", $e->getTraceAsString());
|
|
|
|
exit(1);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Helpers
|
|
// ---------------------------------------------------------------------------
|
|
|
|
function printUsage(): void
|
|
{
|
|
$usage = <<<USAGE
|
|
Usage: php migrate.php [OPTIONS]
|
|
|
|
Options:
|
|
--dry-run Run the migration without writing to the database.
|
|
All operations are logged but no INSERT/UPDATE queries
|
|
are executed.
|
|
|
|
--export-only Export all WHMCS data to JSONL files without touching
|
|
the database. Each entity type gets its own .jsonl file
|
|
in the exports/ directory. This is fast for re-imports.
|
|
|
|
--from-export Import from previously exported JSONL files instead of
|
|
hitting the WHMCS API. Much faster for subsequent runs
|
|
since there are no API calls or rate-limit waits.
|
|
Requires --export-only to have been run first.
|
|
|
|
--phase=N Run only the specified phase number (1-7).
|
|
Phases:
|
|
1 Clients → Users + UserProfiles
|
|
2 Products → Plans + PlanPrices
|
|
3 Services → Subscriptions + Services
|
|
4 Invoices → Invoices + InvoiceItems
|
|
5 Transactions → PaymentTransactions
|
|
6 Promotions → Coupons
|
|
7 Orders → Orders
|
|
|
|
--status Display the current progress of all phases and exit.
|
|
|
|
--export-status Display a summary of all exported JSONL files.
|
|
|
|
--validate-only Test WHMCS API and EZSCALE database connectivity
|
|
without performing any migration.
|
|
|
|
--reset Delete all migration state (ID mappings and progress)
|
|
so the migration can be re-run from scratch.
|
|
|
|
--reset-exports Delete all exported JSONL files from the exports/
|
|
directory.
|
|
|
|
--help Show this help message and exit.
|
|
|
|
Configuration:
|
|
Copy .env.example to .env and fill in:
|
|
- WHMCS API credentials (WHMCS_API_URL, WHMCS_API_IDENTIFIER, WHMCS_API_SECRET)
|
|
- EZSCALE database credentials (EZSCALE_DB_*)
|
|
- Laravel APP_KEY (LARAVEL_APP_KEY) for encrypting sensitive data
|
|
- BATCH_SIZE (default: 250, WHMCS API supports up to 250)
|
|
|
|
Optional plan mapping:
|
|
Edit plan_mapping.json to map WHMCS product IDs to existing EZSCALE
|
|
plan slugs. Unmapped products will be auto-created as new plans.
|
|
|
|
Workflow:
|
|
1. Export all data: php migrate.php --export-only
|
|
2. Review exports: php migrate.php --export-status
|
|
3. Test import: php migrate.php --from-export --dry-run
|
|
4. Run import: php migrate.php --from-export
|
|
5. Check status: php migrate.php --status
|
|
|
|
For a direct migration (no export step):
|
|
php migrate.php
|
|
|
|
State:
|
|
Migration progress and ID mappings are saved to the state/ directory.
|
|
If the migration is interrupted, re-running will resume from the last
|
|
checkpoint. Use --reset to clear all state.
|
|
|
|
Exports:
|
|
JSONL files are saved to the exports/ directory. Each line is one JSON
|
|
record. Use --reset-exports to delete all export files.
|
|
|
|
Logs:
|
|
Detailed logs are written to logs/migration.log.
|
|
|
|
USAGE;
|
|
|
|
echo $usage;
|
|
}
|