From 1b50df7cd040f0f6e9bb8abb43e9229e6a1f2d08 Mon Sep 17 00:00:00 2001 From: wpdev Date: Sun, 27 Sep 2026 16:39:56 +0800 Subject: [PATCH] refactor(2meet-data-optimizer): handle admin form posts before the page renders - the main page's form handlers move unchanged from render_page() to handle_post(), hooked on load-{page}: inside render_page() the admin header was already sent, so their wp_safe_redirect() calls could not work - the setup wizard's step 4 snapshot post moves to handle_snapshot_post() on its load hook; a failure redirects with snap_failed=1 for the notice - Verify: unit 701, integration 577, phpcs, stan; dev36 load hook handles a posted reset and redirects before output - PR: PR-097 --- admin/class-tmdo-admin.php | 21 ++++++++++++--- admin/class-tmdo-setup-wizard.php | 45 ++++++++++++++++++++----------- 2 files changed, 47 insertions(+), 19 deletions(-) diff --git a/admin/class-tmdo-admin.php b/admin/class-tmdo-admin.php index 288aaff..27827ac 100644 --- a/admin/class-tmdo-admin.php +++ b/admin/class-tmdo-admin.php @@ -89,13 +89,16 @@ class TMDO_Admin { * Register the admin menu page under Tools. */ public static function register_menu(): void { - add_management_page( + $hook_suffix = add_management_page( __( 'WP Data Optimizer', '2meet-data-optimizer' ), __( 'WP Data Optimizer', '2meet-data-optimizer' ), 'manage_options', self::MENU_SLUG, array( __CLASS__, 'render_page' ) ); + if ( $hook_suffix ) { + add_action( 'load-' . $hook_suffix, array( __CLASS__, 'handle_post' ) ); + } } /** @@ -326,9 +329,10 @@ class TMDO_Admin { } /** - * Render the admin page. + * Handle the page's form posts on load-{page}, before any output, so their + * redirects work (inside render_page() the admin header was already sent). */ - public static function render_page(): void { + public static function handle_post(): void { if ( ! TMDO_Capability::current_user_can_admin() ) { return; } @@ -752,6 +756,15 @@ class TMDO_Admin { ); exit; } + } + + /** + * Render the admin page. + */ + public static function render_page(): void { + if ( ! TMDO_Capability::current_user_can_admin() ) { + return; + } $tabs = array( 'dashboard' => __( '儀表板', '2meet-data-optimizer' ), @@ -774,7 +787,7 @@ class TMDO_Admin { 'hivepress' => __( 'HivePress 整合', '2meet-data-optimizer' ), ); - $tab = sanitize_key( wp_unslash( $_GET['tab'] ?? 'dashboard' ) ); + $tab = sanitize_key( wp_unslash( $_GET['tab'] ?? 'dashboard' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- tab navigation, no state change. // v2.11.8: legacy tabs removed — redirect bookmarks to current main maintenance entry. if ( in_array( $tab, array( 'sop', 'migration' ), true ) ) { $tab = 'entity-bridge'; diff --git a/admin/class-tmdo-setup-wizard.php b/admin/class-tmdo-setup-wizard.php index 2554578..32c8ab9 100644 --- a/admin/class-tmdo-setup-wizard.php +++ b/admin/class-tmdo-setup-wizard.php @@ -106,7 +106,7 @@ class TMDO_Setup_Wizard { * @return void */ public static function register_menu_page(): void { - add_submenu_page( + $hook_suffix = add_submenu_page( 'tools.php', __( 'WPDO 設定嚮導', '2meet-data-optimizer' ), __( 'WPDO 設定嚮導', '2meet-data-optimizer' ), @@ -114,6 +114,33 @@ class TMDO_Setup_Wizard { 'wpdo-setup-wizard', array( __CLASS__, 'render_page' ) ); + if ( $hook_suffix ) { + add_action( 'load-' . $hook_suffix, array( __CLASS__, 'handle_snapshot_post' ) ); + } + } + + /** + * Step 4 baseline snapshot (POST + nonce, prevents Referer leak — v3.3.x + * P1-6b hardening), handled on load-{page} so the redirect runs before any + * output. + * + * @return void + */ + public static function handle_snapshot_post(): void { + if ( ! isset( $_POST['wpdo_take_snapshot'] ) || ! TMDO_Capability::current_user_can_admin() || ! class_exists( 'TMDO_Snapshot_Manager' ) ) { + return; + } + check_admin_referer( self::NONCE_NAME ); + $result = TMDO_Snapshot_Manager::create( + 'manual', + array(), + array( + 'notes' => 'wizard_baseline', + 'retention_days' => 365, + ) + ); + wp_safe_redirect( admin_url( 'tools.php?page=wpdo-setup-wizard&step=4&' . ( empty( $result['ok'] ) ? 'snap_failed=1' : 'snap_done=1' ) ) ); + exit; } /** @@ -356,20 +383,8 @@ class TMDO_Setup_Wizard {

'wizard_baseline', - 'retention_days' => 365, - ) - ); - if ( ! empty( $result['ok'] ) ) { - wp_safe_redirect( admin_url( 'tools.php?page=wpdo-setup-wizard&step=4&snap_done=1' ) ); - exit; - } + // The snapshot itself is taken in handle_snapshot_post(), before any output. + if ( isset( $_GET['snap_failed'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended echo '

' . esc_html__( 'Snapshot 建立失敗,請查日誌。', '2meet-data-optimizer' ) . '

'; } ?>