Settings and * orchestrates the full migration wizard. Handles all POST actions for: * - Table rename / restore (preparation) * - Wizard step navigation (steps 1–6) * - Wizard reset * - CSV log export * * Network Admin only. Uses exclusively: * network_admin_menu, network_admin_init. * * @package Robotstxt_Mlp2_Mlp5 */ if ( ! defined( 'ABSPATH' ) ) { exit; } /** * Class Robotstxt_Mlp2_Mlp5_Admin_Page */ class Robotstxt_Mlp2_Mlp5_Admin_Page { /** * Menu slug for the plugin settings page. * * @var string */ const MENU_SLUG = 'robotstxt-mlp2-mlp5'; /** * Required capability to access the plugin page. * * The manage_network capability is the standard for Network Admin settings. * * @var string */ const CAPABILITY = 'manage_network'; /** * Register the Network Admin page and all POST/GET handlers. */ public static function init(): void { add_action( 'network_admin_menu', array( static::class, 'register_menu' ) ); add_action( 'admin_post_mlp_migrator_rename_tables', array( static::class, 'handle_rename_action' ) ); add_action( 'admin_post_mlp_migrator_restore_tables', array( static::class, 'handle_restore_action' ) ); add_action( 'admin_post_mlp_migrator_step', array( static::class, 'handle_step_actions' ) ); add_action( 'admin_post_mlp_migrator_reset_wizard', array( static::class, 'handle_reset_action' ) ); add_action( 'admin_post_mlp_migrator_export_log', array( 'Robotstxt_Mlp2_Mlp5_Step_Verification', 'maybe_export_csv' ) ); } /** * Add the settings page under Network Admin > Settings. */ public static function register_menu(): void { add_submenu_page( 'settings.php', __( 'MLP2 to MLP5 Migrator', 'robotstxt-mlp2-mlp5' ), __( 'MLP Migrator', 'robotstxt-mlp2-mlp5' ), self::CAPABILITY, self::MENU_SLUG, array( static::class, 'render' ) ); } /** * Return the URL to this plugin's Network Admin page. * * @param array $extra Optional extra query args. * @return string Absolute URL. */ public static function page_url( array $extra = array() ): string { $args = array_merge( array( 'page' => self::MENU_SLUG ), $extra ); return network_admin_url( 'settings.php?' . http_build_query( $args ) ); } // ------------------------------------------------------------------------- // POST handlers (preparation) // ------------------------------------------------------------------------- /** * Handle the "Rename MLP2 tables" POST action. */ public static function handle_rename_action(): void { check_admin_referer( 'mlp_migrator_rename', 'mlp_migrator_nonce' ); if ( ! current_user_can( self::CAPABILITY ) ) { wp_die( esc_html__( 'You do not have permission to perform this action.', 'robotstxt-mlp2-mlp5' ) ); } $result = Robotstxt_Mlp2_Mlp5_Table_Preparer::rename_to_backup(); $args = array( 'renamed' => count( $result['renamed'] ) ); if ( ! empty( $result['errors'] ) ) { $args['rename_errors'] = count( $result['errors'] ); } wp_safe_redirect( self::page_url( $args ) ); exit; } /** * Handle the "Restore original table names" POST action. */ public static function handle_restore_action(): void { check_admin_referer( 'mlp_migrator_restore', 'mlp_migrator_nonce' ); if ( ! current_user_can( self::CAPABILITY ) ) { wp_die( esc_html__( 'You do not have permission to perform this action.', 'robotstxt-mlp2-mlp5' ) ); } $result = Robotstxt_Mlp2_Mlp5_Table_Preparer::restore_from_backup(); $args = array( 'restored' => count( $result['restored'] ) ); if ( ! empty( $result['errors'] ) ) { $args['restore_errors'] = count( $result['errors'] ); } wp_safe_redirect( self::page_url( $args ) ); exit; } // ------------------------------------------------------------------------- // POST handlers (wizard steps) // ------------------------------------------------------------------------- /** * Route all wizard step POST actions. */ public static function handle_step_actions(): void { // phpcs:ignore WordPress.Security.NonceVerification.Missing if ( ! isset( $_POST['mlp_migrator_step_action'] ) ) { return; } if ( ! current_user_can( self::CAPABILITY ) ) { wp_die( esc_html__( 'You do not have permission to perform this action.', 'robotstxt-mlp2-mlp5' ) ); } // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $action = sanitize_key( wp_unslash( $_POST['mlp_migrator_step_action'] ) ); switch ( $action ) { case '1': self::handle_step_1_next(); break; case '2_run': self::handle_step_2_run(); break; case '2': self::handle_step_next( 2, 3, 'mlp_migrator_step_2' ); break; case '3_run': self::handle_step_3_run(); break; case '3': self::handle_step_next( 3, 4, 'mlp_migrator_step_3' ); break; case '4': self::handle_step_next( 4, 5, 'mlp_migrator_step_4' ); break; case '5': self::handle_step_next( 5, 6, 'mlp_migrator_step_5' ); break; case '6_cleanup': self::handle_step_6_cleanup(); break; } } /** * Handle "Next" from Step 1 (Preflight). */ private static function handle_step_1_next(): void { check_admin_referer( 'mlp_migrator_step_1', 'mlp_migrator_nonce' ); $checks = Robotstxt_Mlp2_Mlp5_Step_Preflight::run(); if ( $checks['can_proceed'] ) { Robotstxt_Mlp2_Mlp5_Wizard_State::mark_step_complete( 1 ); Robotstxt_Mlp2_Mlp5_Wizard_State::set_step( 2 ); } wp_safe_redirect( self::page_url() ); exit; } /** * Handle "Run Analysis" from Step 2. */ private static function handle_step_2_run(): void { check_admin_referer( 'mlp_migrator_analysis', 'mlp_migrator_nonce' ); $result = Robotstxt_Mlp2_Mlp5_Step_Analysis::run(); Robotstxt_Mlp2_Mlp5_Wizard_State::set_step_data( 2, $result ); wp_safe_redirect( self::page_url() ); exit; } /** * Handle "Start" from Step 3 (Site Relations). */ private static function handle_step_3_run(): void { check_admin_referer( 'mlp_migrator_site_relations', 'mlp_migrator_nonce' ); $result = Robotstxt_Mlp2_Mlp5_Step_Site_Relations::run(); Robotstxt_Mlp2_Mlp5_Wizard_State::set_step_data( 3, $result ); wp_safe_redirect( self::page_url() ); exit; } /** * Handle "Next" for a standard step (advance to next, mark complete). * * @param int $current_step Current step number. * @param int $next_step Next step number. * @param string $nonce_action Nonce action string. */ private static function handle_step_next( int $current_step, int $next_step, string $nonce_action ): void { check_admin_referer( $nonce_action, 'mlp_migrator_nonce' ); Robotstxt_Mlp2_Mlp5_Wizard_State::mark_step_complete( $current_step ); Robotstxt_Mlp2_Mlp5_Wizard_State::set_step( $next_step ); wp_safe_redirect( self::page_url() ); exit; } /** * Handle the cleanup POST from Step 6. */ private static function handle_step_6_cleanup(): void { check_admin_referer( 'mlp_migrator_cleanup', 'mlp_migrator_nonce' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $confirm = isset( $_POST['cleanup_confirm'] ) ? wp_unslash( $_POST['cleanup_confirm'] ) : ''; if ( 'DELETE' !== $confirm ) { wp_safe_redirect( self::page_url( array( 'cleanup_error' => '1' ) ) ); exit; } $deactivate = ! empty( $_POST['cleanup_deactivate'] ); $drop_tables = ! empty( $_POST['cleanup_drop_tables'] ) && $deactivate; $sitemeta = ! empty( $_POST['cleanup_sitemeta'] ); if ( $deactivate ) { Robotstxt_Mlp2_Mlp5_Step_Cleanup::deactivate_mlp2(); } if ( $drop_tables ) { Robotstxt_Mlp2_Mlp5_Step_Cleanup::drop_backup_tables(); } if ( $sitemeta ) { Robotstxt_Mlp2_Mlp5_Step_Cleanup::remove_mlp2_sitemeta(); } Robotstxt_Mlp2_Mlp5_Wizard_State::mark_step_complete( 6 ); wp_safe_redirect( self::page_url( array( 'cleanup_done' => '1' ) ) ); exit; } /** * Handle the wizard reset POST action. */ public static function handle_reset_action(): void { check_admin_referer( 'mlp_migrator_reset', 'mlp_migrator_nonce' ); if ( ! current_user_can( self::CAPABILITY ) ) { wp_die( esc_html__( 'You do not have permission to perform this action.', 'robotstxt-mlp2-mlp5' ) ); } Robotstxt_Mlp2_Mlp5_Wizard_State::reset(); Robotstxt_Mlp2_Mlp5_Logger::clear(); wp_safe_redirect( self::page_url( array( 'reset' => '1' ) ) ); exit; } // ------------------------------------------------------------------------- // Rendering // ------------------------------------------------------------------------- /** * Render the full page. */ public static function render(): void { if ( ! current_user_can( self::CAPABILITY ) ) { wp_die( esc_html__( 'You do not have permission to access this page.', 'robotstxt-mlp2-mlp5' ) ); } $has_original = Robotstxt_Mlp2_Mlp5_Table_Preparer::has_original_tables(); $has_backup = Robotstxt_Mlp2_Mlp5_Table_Preparer::has_backup_tables(); $mlp5 = new Robotstxt_Mlp2_Mlp5_Detector_Mlp5(); $mlp5_active = $mlp5->is_active(); // Once MLP5 is active its own tables match the mlp_* pattern. // They are not leftover MLP2 tables, so suppress the "must be renamed" signal. if ( $mlp5_active ) { $has_original = false; } // Migration is complete when wizard step 6 is marked done. $wizard_done = Robotstxt_Mlp2_Mlp5_Wizard_State::is_step_complete( Robotstxt_Mlp2_Mlp5_Wizard_State::TOTAL_STEPS ); $ready = ( $has_backup && $mlp5_active ) || ( $wizard_done && $mlp5_active ); ?>

__( 'Preflight', 'robotstxt-mlp2-mlp5' ), 2 => __( 'Analysis', 'robotstxt-mlp2-mlp5' ), 3 => __( 'Site Relations', 'robotstxt-mlp2-mlp5' ), 4 => __( 'Content Relations', 'robotstxt-mlp2-mlp5' ), 5 => __( 'Verification', 'robotstxt-mlp2-mlp5' ), 6 => __( 'Cleanup', 'robotstxt-mlp2-mlp5' ), ); ?>

%s

', esc_html( sprintf( _n( '%d table renamed successfully.', '%d tables renamed successfully.', $count, 'robotstxt-mlp2-mlp5' ), $count ) ) ); // phpcs:ignore WordPress.WP.I18n.MismatchedPlaceholders } if ( isset( $_GET['rename_errors'] ) ) { $count = (int) $_GET['rename_errors']; /* translators: %d: number of tables that could not be renamed */ printf( '

%s

', esc_html( sprintf( _n( '%d table could not be renamed. Check database permissions.', '%d tables could not be renamed. Check database permissions.', $count, 'robotstxt-mlp2-mlp5' ), $count ) ) ); // phpcs:ignore WordPress.WP.I18n.MismatchedPlaceholders } if ( isset( $_GET['restored'] ) ) { $count = (int) $_GET['restored']; /* translators: %d: number of restored tables */ printf( '

%s

', esc_html( sprintf( _n( '%d table restored to original name.', '%d tables restored to original names.', $count, 'robotstxt-mlp2-mlp5' ), $count ) ) ); // phpcs:ignore WordPress.WP.I18n.MismatchedPlaceholders } if ( isset( $_GET['restore_errors'] ) ) { $count = (int) $_GET['restore_errors']; /* translators: %d: number of tables that could not be restored */ printf( '

%s

', esc_html( sprintf( _n( '%d table could not be restored. Check database permissions.', '%d tables could not be restored. Check database permissions.', $count, 'robotstxt-mlp2-mlp5' ), $count ) ) ); // phpcs:ignore WordPress.WP.I18n.MismatchedPlaceholders } if ( isset( $_GET['reset'] ) ) { printf( '

%s

', esc_html__( 'Wizard reset. You can start the migration from Step 1.', 'robotstxt-mlp2-mlp5' ) ); } if ( isset( $_GET['cleanup_done'] ) ) { printf( '

%s

', esc_html__( 'Cleanup completed.', 'robotstxt-mlp2-mlp5' ) ); } if ( isset( $_GET['cleanup_error'] ) ) { printf( '

%s

', esc_html__( 'Cleanup confirmation invalid. Type DELETE exactly.', 'robotstxt-mlp2-mlp5' ) ); } // phpcs:enable WordPress.Security.NonceVerification.Recommended } /** * Show a banner communicating the overall system state (preparation view only). * * @param bool $has_original Whether original MLP2 tables exist. * @param bool $has_backup Whether backup MLP2 tables exist. * @param bool $mlp5_active Whether MLP5 is network-active. */ private static function render_state_banner( bool $has_original, bool $has_backup, bool $mlp5_active ): void { if ( $has_original && ! $has_backup ) { $color = '#d63638'; $msg = __( 'Action required: MLP2 tables must be renamed before MultilingualPress 5 can be activated.', 'robotstxt-mlp2-mlp5' ); } elseif ( $has_backup && ! $mlp5_active ) { $color = '#dba617'; $msg = __( 'MLP2 tables renamed. Now go to Network Admin > Plugins and activate MultilingualPress 5.', 'robotstxt-mlp2-mlp5' ); } elseif ( $has_original && $has_backup ) { $color = '#dba617'; $msg = __( 'Warning: both original and backup MLP2 tables were found. Investigate before proceeding.', 'robotstxt-mlp2-mlp5' ); } else { $color = '#d63638'; $msg = __( 'No MLP2 tables found. Migration cannot proceed. Restore your database from a backup.', 'robotstxt-mlp2-mlp5' ); } printf( '
%s
', esc_attr( $color ), wp_kses_post( $msg ) ); } /** * Show the system status card. * * @param bool $has_original Whether original MLP2 tables exist. * @param bool $has_backup Whether backup MLP2 tables exist. * @param bool $mlp5_active Whether MLP5 is network-active. * @param Robotstxt_Mlp2_Mlp5_Detector_Mlp5 $mlp5 MLP5 detector instance. */ private static function render_status_card( bool $has_original, bool $has_backup, bool $mlp5_active, Robotstxt_Mlp2_Mlp5_Detector_Mlp5 $mlp5 ): void { $orig_count = count( Robotstxt_Mlp2_Mlp5_Table_Preparer::get_original_tables() ); $backup_count = count( Robotstxt_Mlp2_Mlp5_Table_Preparer::get_backup_tables() ); ?>

● ✓
✓ —
✓ get_version() ) ); ?> —