/class-foo-bar.php. */ public static function register_autoloader(): void { spl_autoload_register( static function ( string $class ): void { if ( 0 !== strpos( $class, 'Mench_Migrate_' ) ) { return; } $file = 'class-' . str_replace( '_', '-', strtolower( substr( $class, strlen( 'Mench_Migrate_' ) ) ) ) . '.php'; foreach ( self::AUTOLOAD_DIRS as $dir ) { $path = MENCH_MIGRATE_DIR . 'includes/' . $dir . $file; if ( is_readable( $path ) ) { require_once $path; return; } } } ); } /** The All Add-ons bundle on the store; every catalogue entry links to it unless it names its own. */ public const BUNDLE_URL = 'https://menchplugins.com/downloads/mench-migrate-all-add-ons/'; /** Core export steps by priority; add-ons slot theirs in between through the same filter. */ public const EXPORT_STEPS = [ 10 => 'Mench_Migrate_Step_Export_Init', 20 => 'Mench_Migrate_Step_Scan_Files', 30 => 'Mench_Migrate_Step_Export_Db', 40 => 'Mench_Migrate_Step_Write_Files', 50 => 'Mench_Migrate_Step_Finalize_Export', 60 => 'Mench_Migrate_Step_Save_To_Storage', ]; /** Core import steps by priority: staging first, the site switches only at the swap (60). */ public const IMPORT_STEPS = [ 5 => 'Mench_Migrate_Step_Import_Download', 10 => 'Mench_Migrate_Step_Import_Open', 20 => 'Mench_Migrate_Step_Import_Preflight', 25 => 'Mench_Migrate_Step_Import_Convert_Wpress', 30 => 'Mench_Migrate_Step_Import_Db_Create', 40 => 'Mench_Migrate_Step_Import_Db_Rows', 50 => 'Mench_Migrate_Step_Import_Files', 60 => 'Mench_Migrate_Step_Import_Swap', 70 => 'Mench_Migrate_Step_Import_Finalize', 75 => 'Mench_Migrate_Step_Import_Verify_Links', ]; /** Wire up hooks; later tasks add their own registrations here. */ public function init(): void { add_action( 'plugins_loaded', [ self::class, 'fire_loaded' ], 20 ); if ( defined( 'WP_CLI' ) && WP_CLI && class_exists( 'WP_CLI' ) ) { WP_CLI::add_command( 'mench-migrate', 'Mench_Migrate_Cli' ); } add_filter( 'mench_migrate_export_steps', [ self::class, 'export_steps' ] ); add_filter( 'mench_migrate_import_steps', [ self::class, 'import_steps' ] ); add_action( Mench_Migrate_Snapshot::CRON, [ 'Mench_Migrate_Job_Runner', 'reap' ], 5 ); add_action( Mench_Migrate_Snapshot::CRON, [ 'Mench_Migrate_Snapshot', 'cleanup_expired' ] ); add_action( Mench_Migrate_Snapshot::CRON, [ 'Mench_Migrate_Rest_Uploads', 'cleanup_stale' ] ); add_action( Mench_Migrate_Snapshot::CRON, [ 'Mench_Migrate_Snapshot', 'purge_trash' ], 20, 0 ); add_action( Mench_Migrate_Paths::PROBE_CRON, [ 'Mench_Migrate_Paths', 'probe' ], 10, 0 ); Mench_Migrate_Snapshot::schedule(); add_action( 'rest_api_init', [ 'Mench_Migrate_Rest_Uploads', 'prepare_stream' ], 0 ); add_action( 'rest_api_init', [ 'Mench_Migrate_Rest', 'register' ] ); add_filter( 'rest_pre_serve_request', [ 'Mench_Migrate_Rest_Backups', 'serve' ], 10, 4 ); add_action( 'admin_init', [ 'Mench_Migrate_Link_Check', 'maybe_flush' ] ); // Post types are registered by then. add_action( 'wp', [ 'Mench_Migrate_Link_Check', 'maybe_flush' ] ); // Front-end pages only, never REST. Mench_Migrate_Admin::init(); } /** Add-ons hook mench_migrate_loaded (spec ยง5.1) and register their storages, steps, tabs and catalogue entries. */ public static function fire_loaded(): void { do_action( 'mench_migrate_loaded', self::instance() ); } /** @return array Admin tabs by slug (filter mench_migrate_admin_tabs); the admin screens render them. */ public static function admin_tabs(): array { $tabs = apply_filters( 'mench_migrate_admin_tabs', [ 'export' => __( 'Export', 'mench-migrate' ), 'import' => __( 'Import', 'mench-migrate' ), 'backups' => __( 'Backups', 'mench-migrate' ), 'addons' => __( 'Add-ons', 'mench-migrate' ), 'settings' => __( 'Settings', 'mench-migrate' ), ] ); $out = []; foreach ( is_array( $tabs ) ? $tabs : [] as $slug => $label ) { if ( is_string( $slug ) && '' !== $slug && is_scalar( $label ) ) { $out[ $slug ] = (string) $label; } } return $out; } /** * The Add-ons tab catalogue (filter mench_migrate_addons). Entries without a string id are dropped; missing keys * get empty defaults, so the tab can rely on every key. * * @return list */ public static function addons(): array { $entry = static fn( string $id, string $name, string $description, string $price, string $status ): array => [ 'id' => $id, 'name' => $name, 'description' => $description, 'price_from' => $price, 'url' => 'https://menchplugins.com/downloads/' . $id . '/', 'bundle_url' => self::BUNDLE_URL, 'plugin_file' => $id . '/' . $id . '.php', 'status' => $status, ]; $soon = __( 'Coming soon.', 'mench-migrate' ); $catalog = apply_filters( 'mench_migrate_addons', [ $entry( 'mench-migrate-cloud', __( 'Cloud Storage', 'mench-migrate' ), __( 'Save and restore backups on S3-compatible storage: Cloudflare R2, AWS S3, Backblaze B2, Wasabi, DigitalOcean Spaces.', 'mench-migrate' ), '$19', 'available' ), $entry( 'mench-migrate-schedules', __( 'Schedules', 'mench-migrate' ), __( 'Automatic backups on a schedule, with retention and Mench Notify alerts.', 'mench-migrate' ), '$19', 'available' ), $entry( 'mench-migrate-google-drive', __( 'Google Drive', 'mench-migrate' ), __( 'Keep backups in Google Drive.', 'mench-migrate' ) . ' ' . $soon, '', 'coming_soon' ), $entry( 'mench-migrate-dropbox', __( 'Dropbox', 'mench-migrate' ), __( 'Keep backups in Dropbox.', 'mench-migrate' ) . ' ' . $soon, '', 'coming_soon' ), $entry( 'mench-migrate-sftp', __( 'SFTP/FTP', 'mench-migrate' ), __( 'Send backups to your own server over SFTP or FTP.', 'mench-migrate' ) . ' ' . $soon, '', 'coming_soon' ), $entry( 'mench-migrate-site-to-site', __( 'Site-to-Site', 'mench-migrate' ), __( 'Push or pull a whole site between two WordPress installs.', 'mench-migrate' ), '$19', 'available' ), $entry( 'mench-migrate-multisite', __( 'Multisite', 'mench-migrate' ), __( 'Export and import WordPress multisite networks and single subsites.', 'mench-migrate' ) . ' ' . $soon, '', 'coming_soon' ), ] ); $out = []; foreach ( is_array( $catalog ) ? $catalog : [] as $a ) { if ( ! is_array( $a ) || ! isset( $a['id'] ) || ! is_string( $a['id'] ) || '' === $a['id'] ) { continue; } $row = []; foreach ( [ 'id', 'name', 'description', 'price_from', 'url', 'bundle_url', 'plugin_file', 'status' ] as $k ) { $row[ $k ] = isset( $a[ $k ] ) && is_scalar( $a[ $k ] ) ? (string) $a[ $k ] : ''; } $row['bundle_url'] = '' === $row['bundle_url'] ? self::BUNDLE_URL : $row['bundle_url']; $row['status'] = 'coming_soon' === $row['status'] ? 'coming_soon' : 'available'; $out[] = $row; } return $out; } /** Whether an add-on's main file ("slug/slug.php") is active, installed but inactive, or missing. */ public static function addon_status( string $plugin_file ): string { if ( '' === $plugin_file || 0 !== validate_file( $plugin_file ) || '.php' !== substr( $plugin_file, -4 ) ) { return 'missing'; } if ( in_array( $plugin_file, (array) get_option( 'active_plugins', [] ), true ) ) { return 'active'; } return is_file( WP_PLUGIN_DIR . '/' . $plugin_file ) ? 'installed' : 'missing'; } /** @param mixed $steps Steps registered so far; an add-on's step at the same priority wins. */ public static function export_steps( $steps ): array { return ( is_array( $steps ) ? $steps : [] ) + self::EXPORT_STEPS; } /** @param mixed $steps Steps registered so far; an add-on's step at the same priority wins. */ public static function import_steps( $steps ): array { return ( is_array( $steps ) ? $steps : [] ) + self::IMPORT_STEPS; } /** Creates the protected storage root and schedules the hourly rollback cleanup. */ public static function on_activate(): void { Mench_Migrate_Paths::ensure(); Mench_Migrate_Snapshot::schedule(); } public static function on_deactivate(): void { Mench_Migrate_Snapshot::unschedule(); } }