fix(admin): 13 個破壞性動作由 GET 改 POST + nonce(A1-A4)

nonce 走 query string 會經 Referer 外洩,且 GET 觸發的破壞性動作(刪快照、
cutover、promote aeav_only、清 postmeta)可被 prefetch/爬蟲觸發。對應 A v3.3.1 P1-6。

Handler 端(admin/class-tmdo-admin.php:300-700):13 個動作與其附屬參數
(post_type / count / mode / samples)全部改讀 $_POST。唯讀的 tab /
classify_type / wpdo_msg / wpdo_module 維持 GET。

渲染端改為 <form method=post> + wp_nonce_field():
- admin:重置速率統計、建立/清除快照、刪除快照、啟用 module
- dashboard-widget:跑健康檢查、建立快照、一鍵清理 postmeta
- post-migration-wizard:5 個步驟動作
- setup-wizard:建立 baseline snapshot
- post-stress-test:移除已無呼叫端的 legacy GET $cleanup_url

註:A 的 dashboard-widget 仍以 wp_nonce_url 產生 postmeta_cleanup 連結,
但其 handler 已只收 POST → 該按鈕在 A 是壞的;B 這邊一併改成 form。

unit 379 / integration 398 GREEN

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TbG1keQQ7XBa7qMQY16KCY
This commit is contained in:
2026-07-31 05:27:57 +08:00
parent 6e81dc51c5
commit 68f7f6871c
5 changed files with 119 additions and 130 deletions
+40 -42
View File
@@ -35,27 +35,6 @@ $mode_label = array(
'aeav_only' => __( '🟢 aeav_only — flat 表為 source-of-truth', '2meet-data-optimizer' ),
);
// Action URLs (each carries nonce).
$cleanup_garbage_url = wp_nonce_url(
add_query_arg( array( 'wpdo_postmeta_cleanup' => '1' ), $page_url ),
'wpdo_postmeta_cleanup'
);
$backfill_all_url = wp_nonce_url(
add_query_arg( array( 'wpdo_post_backfill_all' => '1' ), $page_url ),
'wpdo_post_backfill_all'
);
$cutover_legacy_url = wp_nonce_url(
add_query_arg( array( 'wpdo_post_cutover_legacy' => '1' ), $page_url ),
'wpdo_post_cutover_legacy'
);
$promote_dual_write_url = wp_nonce_url(
add_query_arg( array( 'wpdo_post_promote_dual_write' => '1' ), $page_url ),
'wpdo_post_promote_dual_write'
);
$promote_aeav_url = wp_nonce_url(
add_query_arg( array( 'wpdo_post_promote_aeav' => '1' ), $page_url ),
'wpdo_post_promote_aeav'
);
// Status banner from prior action redirect.
// Read-only display banner — server-set redirect message, no form processing.
@@ -234,11 +213,14 @@ $wpdo_format_msg = static function ( string $code ): string {
?>
</p>
<div class="actions">
<a href="<?php echo esc_url( $cleanup_garbage_url ); ?>"
class="button button-primary <?php echo (int) $garbage['total'] > 0 ? '' : 'disabled'; ?>"
onclick="return confirm(<?php echo wp_json_encode( __( '確定要刪除 wp_postmeta 中的垃圾資料?此動作不可逆。', '2meet-data-optimizer' ) ); ?>);">
<?php esc_html_e( '清理垃圾資料', '2meet-data-optimizer' ); ?>
</a>
<form method="post" style="display:inline">
<input type="hidden" name="wpdo_postmeta_cleanup" value="1">
<?php wp_nonce_field( 'wpdo_postmeta_cleanup' ); ?>
<button type="submit" class="button button-primary <?php echo (int) $garbage['total'] > 0 ? '' : 'disabled'; ?>"
onclick="return confirm(<?php echo wp_json_encode( __( '確定要刪除 wp_postmeta 中的垃圾資料?此動作不可逆。', '2meet-data-optimizer' ) ); ?>);">
<?php esc_html_e( '清理垃圾資料', '2meet-data-optimizer' ); ?>
</button>
</form>
</div>
</div>
@@ -246,10 +228,14 @@ $wpdo_format_msg = static function ( string $code ): string {
<h3><?php esc_html_e( '步驟 2:把 wp_postmeta 既有資料 backfill 至 flat 表(v2.9.3', '2meet-data-optimizer' ); ?></h3>
<p><?php esc_html_e( 'Idempotent — 重跑安全。每個 group 跑一次 bulk SQL pivot。', '2meet-data-optimizer' ); ?></p>
<div class="actions">
<a href="<?php echo esc_url( $backfill_all_url ); ?>" class="button button-primary"
onclick="return confirm(<?php echo wp_json_encode( __( '確定要對 7 個 group 跑 backfill?此動作 idempotent,可重跑。', '2meet-data-optimizer' ) ); ?>);">
<?php esc_html_e( '把 wp_postmeta backfill 到 7 張 flat 表', '2meet-data-optimizer' ); ?>
</a>
<form method="post" style="display:inline">
<input type="hidden" name="wpdo_post_backfill_all" value="1">
<?php wp_nonce_field( 'wpdo_post_backfill_all' ); ?>
<button type="submit" class="button button-primary"
onclick="return confirm(<?php echo wp_json_encode( __( '確定要對 7 個 group 跑 backfill?此動作 idempotent,可重跑。', '2meet-data-optimizer' ) ); ?>);">
<?php esc_html_e( '把 wp_postmeta backfill 到 7 張 flat 表', '2meet-data-optimizer' ); ?>
</button>
</form>
</div>
</div>
@@ -257,10 +243,14 @@ $wpdo_format_msg = static function ( string $code ): string {
<h3><?php esc_html_e( '步驟 3:把 legacy wpdo_hot_hp_listing 抄到 flat 表(v2.9.5', '2meet-data-optimizer' ); ?></h3>
<p><?php esc_html_e( '非破壞 — legacy hot 表保留作為 v3.0.0 rollback safety net。', '2meet-data-optimizer' ); ?></p>
<div class="actions">
<a href="<?php echo esc_url( $cutover_legacy_url ); ?>" class="button"
onclick="return confirm(<?php echo wp_json_encode( __( '確定要 copy wpdo_hot_hp_listing 到 wp_wpdo_post_hp_listing_core', '2meet-data-optimizer' ) ); ?>);">
<?php esc_html_e( 'Copy legacy hot table', '2meet-data-optimizer' ); ?>
</a>
<form method="post" style="display:inline">
<input type="hidden" name="wpdo_post_cutover_legacy" value="1">
<?php wp_nonce_field( 'wpdo_post_cutover_legacy' ); ?>
<button type="submit" class="button"
onclick="return confirm(<?php echo wp_json_encode( __( '確定要 copy wpdo_hot_hp_listing 到 wp_wpdo_post_hp_listing_core', '2meet-data-optimizer' ) ); ?>);">
<?php esc_html_e( 'Copy legacy hot table', '2meet-data-optimizer' ); ?>
</button>
</form>
</div>
</div>
@@ -268,10 +258,14 @@ $wpdo_format_msg = static function ( string $code ): string {
<h3><?php esc_html_e( '步驟 4:升級 mode 至 dual_write', '2meet-data-optimizer' ); ?></h3>
<p><?php esc_html_e( 'wp_postmeta 與 flat 表同時寫入。讀仍走 wp_postmeta(生產 safe)。建議至少觀察 24h 後再升級下一階。', '2meet-data-optimizer' ); ?></p>
<div class="actions">
<a href="<?php echo esc_url( $promote_dual_write_url ); ?>" class="button"
onclick="return confirm(<?php echo wp_json_encode( __( '確定要升級 post mode 至 dual_write?此後 update_post_meta() 會雙寫。', '2meet-data-optimizer' ) ); ?>);">
<?php esc_html_e( '升級 mode → dual_write', '2meet-data-optimizer' ); ?>
</a>
<form method="post" style="display:inline">
<input type="hidden" name="wpdo_post_promote_dual_write" value="1">
<?php wp_nonce_field( 'wpdo_post_promote_dual_write' ); ?>
<button type="submit" class="button"
onclick="return confirm(<?php echo wp_json_encode( __( '確定要升級 post mode 至 dual_write?此後 update_post_meta() 會雙寫。', '2meet-data-optimizer' ) ); ?>);">
<?php esc_html_e( '升級 mode → dual_write', '2meet-data-optimizer' ); ?>
</button>
</form>
</div>
</div>
@@ -279,10 +273,14 @@ $wpdo_format_msg = static function ( string $code ): string {
<h3><?php esc_html_e( '步驟 5:升級 mode 至 aeav_only(最終 cutover', '2meet-data-optimizer' ); ?></h3>
<p><?php esc_html_e( 'flat 表成為 source-of-truth,讀寫都走 flat。完成後可 wp wpdo post-cleanup --confirm 清掉 wp_postmeta 已遷移 keys。', '2meet-data-optimizer' ); ?></p>
<div class="actions">
<a href="<?php echo esc_url( $promote_aeav_url ); ?>" class="button"
onclick="return confirm(<?php echo wp_json_encode( __( '確定要升級 post mode 至 aeav_only?此後讀路徑切換到 flat 表,wp_postmeta 變成 backup。', '2meet-data-optimizer' ) ); ?>);">
<?php esc_html_e( '升級 mode → aeav_only', '2meet-data-optimizer' ); ?>
</a>
<form method="post" style="display:inline">
<input type="hidden" name="wpdo_post_promote_aeav" value="1">
<?php wp_nonce_field( 'wpdo_post_promote_aeav' ); ?>
<button type="submit" class="button"
onclick="return confirm(<?php echo wp_json_encode( __( '確定要升級 post mode 至 aeav_only?此後讀路徑切換到 flat 表,wp_postmeta 變成 backup。', '2meet-data-optimizer' ) ); ?>);">
<?php esc_html_e( '升級 mode → aeav_only', '2meet-data-optimizer' ); ?>
</button>
</form>
</div>
</div>