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' ) . '
';
}
?>