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:
+48
-56
@@ -300,15 +300,15 @@ class TMDO_Admin {
|
||||
}
|
||||
|
||||
// Handle rate limit stats reset.
|
||||
if ( isset( $_GET['wpdo_reset_rl_stats'] ) && check_admin_referer( 'wpdo_reset_rl_stats' ) ) {
|
||||
if ( isset( $_POST['wpdo_reset_rl_stats'] ) && check_admin_referer( 'wpdo_reset_rl_stats' ) ) {
|
||||
delete_option( 'wpdo_rl_stats' );
|
||||
wp_safe_redirect( remove_query_arg( array( 'wpdo_reset_rl_stats', '_wpnonce' ) ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
// v2.5.0 M16: one-click enable a module from suggestions tab.
|
||||
if ( isset( $_GET['wpdo_enable_module'] ) && check_admin_referer( 'wpdo_enable_module' ) && TMDO_Capability::current_user_can_admin() ) {
|
||||
$module = sanitize_key( wp_unslash( (string) $_GET['wpdo_enable_module'] ) );
|
||||
if ( isset( $_POST['wpdo_enable_module'] ) && check_admin_referer( 'wpdo_enable_module' ) && TMDO_Capability::current_user_can_admin() ) {
|
||||
$module = sanitize_key( wp_unslash( (string) $_POST['wpdo_enable_module'] ) );
|
||||
$flag = 'enable_failed';
|
||||
if ( '' !== $module && class_exists( 'TMDO_Feature_Flags' ) ) {
|
||||
$result = TMDO_Feature_Flags::set( $module, 'dual_write' );
|
||||
@@ -457,7 +457,7 @@ class TMDO_Admin {
|
||||
}
|
||||
|
||||
// v2.3.0 M6: run health check on demand from Doctor tab.
|
||||
if ( isset( $_GET['wpdo_run_health'] ) && check_admin_referer( 'wpdo_run_health' ) && class_exists( 'TMDO_Health_Cron' ) ) {
|
||||
if ( isset( $_POST['wpdo_run_health'] ) && check_admin_referer( 'wpdo_run_health' ) && class_exists( 'TMDO_Health_Cron' ) ) {
|
||||
$res = TMDO_Health_Cron::run();
|
||||
$flag = ( $res['critical_count'] ?? 0 ) > 0 ? 'health_critical' : 'health_ok';
|
||||
wp_safe_redirect(
|
||||
@@ -473,7 +473,7 @@ class TMDO_Admin {
|
||||
}
|
||||
|
||||
// v2.2.0 M4: snapshot create / delete admin actions.
|
||||
if ( isset( $_GET['wpdo_create_snapshot'] ) && check_admin_referer( 'wpdo_create_snapshot' ) && class_exists( 'TMDO_Snapshot_Manager' ) ) {
|
||||
if ( isset( $_POST['wpdo_create_snapshot'] ) && check_admin_referer( 'wpdo_create_snapshot' ) && class_exists( 'TMDO_Snapshot_Manager' ) ) {
|
||||
$result = TMDO_Snapshot_Manager::create(
|
||||
'manual',
|
||||
array(),
|
||||
@@ -493,8 +493,8 @@ class TMDO_Admin {
|
||||
);
|
||||
exit;
|
||||
}
|
||||
if ( isset( $_GET['wpdo_delete_snapshot'] ) && check_admin_referer( 'wpdo_delete_snapshot' ) && class_exists( 'TMDO_Snapshot_Manager' ) ) {
|
||||
$id = sanitize_text_field( wp_unslash( (string) $_GET['wpdo_delete_snapshot'] ) );
|
||||
if ( isset( $_POST['wpdo_delete_snapshot'] ) && check_admin_referer( 'wpdo_delete_snapshot' ) && class_exists( 'TMDO_Snapshot_Manager' ) ) {
|
||||
$id = sanitize_text_field( wp_unslash( (string) $_POST['wpdo_delete_snapshot'] ) );
|
||||
$ok = '' !== $id && TMDO_Snapshot_Manager::delete( $id );
|
||||
wp_safe_redirect(
|
||||
add_query_arg(
|
||||
@@ -507,7 +507,7 @@ class TMDO_Admin {
|
||||
);
|
||||
exit;
|
||||
}
|
||||
if ( isset( $_GET['wpdo_prune_snapshots'] ) && check_admin_referer( 'wpdo_prune_snapshots' ) && class_exists( 'TMDO_Snapshot_Manager' ) ) {
|
||||
if ( isset( $_POST['wpdo_prune_snapshots'] ) && check_admin_referer( 'wpdo_prune_snapshots' ) && class_exists( 'TMDO_Snapshot_Manager' ) ) {
|
||||
$res = TMDO_Snapshot_Manager::prune();
|
||||
$msg = sprintf( 'pruned_%d', (int) ( $res['pruned'] ?? 0 ) );
|
||||
wp_safe_redirect(
|
||||
@@ -523,7 +523,7 @@ class TMDO_Admin {
|
||||
}
|
||||
|
||||
// v2.10.0: Post Migration Wizard — backfill all 7 groups.
|
||||
if ( isset( $_GET['wpdo_post_backfill_all'] ) && check_admin_referer( 'wpdo_post_backfill_all' ) && class_exists( 'TMDO_Post_Migration' ) ) {
|
||||
if ( isset( $_POST['wpdo_post_backfill_all'] ) && check_admin_referer( 'wpdo_post_backfill_all' ) && class_exists( 'TMDO_Post_Migration' ) ) {
|
||||
$total_migrated = 0;
|
||||
$errors = array();
|
||||
foreach ( array( 'wp_core', 'attachment', 'wc_product', 'hp_listing_core', 'hp_request_core', 'hp_vendor_core', 'nav_menu_item' ) as $group ) {
|
||||
@@ -557,7 +557,7 @@ class TMDO_Admin {
|
||||
}
|
||||
|
||||
// v2.10.0: Post Migration Wizard — copy legacy hot table.
|
||||
if ( isset( $_GET['wpdo_post_cutover_legacy'] ) && check_admin_referer( 'wpdo_post_cutover_legacy' ) && class_exists( 'TMDO_Post_Migration' ) ) {
|
||||
if ( isset( $_POST['wpdo_post_cutover_legacy'] ) && check_admin_referer( 'wpdo_post_cutover_legacy' ) && class_exists( 'TMDO_Post_Migration' ) ) {
|
||||
global $wpdb;
|
||||
$hot = $wpdb->prefix . 'wpdo_hot_hp_listing';
|
||||
$flat = $wpdb->prefix . 'wpdo_post_hp_listing_core';
|
||||
@@ -596,9 +596,9 @@ class TMDO_Admin {
|
||||
}
|
||||
|
||||
// v2.10.0: Post Migration Wizard — promote mode (dual_write or aeav_only).
|
||||
if ( ( isset( $_GET['wpdo_post_promote_dual_write'] ) || isset( $_GET['wpdo_post_promote_aeav'] ) ) && class_exists( 'TMDO_Post_Migration' ) ) {
|
||||
$target = isset( $_GET['wpdo_post_promote_dual_write'] ) ? 'dual_write' : 'aeav_only';
|
||||
$nonce = isset( $_GET['wpdo_post_promote_dual_write'] ) ? 'wpdo_post_promote_dual_write' : 'wpdo_post_promote_aeav';
|
||||
if ( ( isset( $_POST['wpdo_post_promote_dual_write'] ) || isset( $_POST['wpdo_post_promote_aeav'] ) ) && class_exists( 'TMDO_Post_Migration' ) ) {
|
||||
$target = isset( $_POST['wpdo_post_promote_dual_write'] ) ? 'dual_write' : 'aeav_only';
|
||||
$nonce = isset( $_POST['wpdo_post_promote_dual_write'] ) ? 'wpdo_post_promote_dual_write' : 'wpdo_post_promote_aeav';
|
||||
if ( check_admin_referer( $nonce ) ) {
|
||||
$result = TMDO_Post_Migration::set_mode( $target );
|
||||
$err = is_wp_error( $result ) ? $result->get_error_message() : '';
|
||||
@@ -627,10 +627,10 @@ class TMDO_Admin {
|
||||
|
||||
// v2.11.0: Post Stress Test — bulk create test posts.
|
||||
// v2.11.2: optional `mode` query arg (fast|realistic).
|
||||
if ( isset( $_GET['wpdo_post_stress_create'] ) && check_admin_referer( 'wpdo_post_stress_create' ) && class_exists( 'TMDO_Post_Stress_Tester' ) ) {
|
||||
$post_type = isset( $_GET['post_type'] ) ? sanitize_key( wp_unslash( (string) $_GET['post_type'] ) ) : 'product';
|
||||
$count = isset( $_GET['count'] ) ? max( 1, min( 10000, absint( wp_unslash( $_GET['count'] ) ) ) ) : 100;
|
||||
$mode = isset( $_GET['mode'] ) && 'realistic' === sanitize_key( wp_unslash( (string) $_GET['mode'] ) ) ? 'realistic' : 'fast';
|
||||
if ( isset( $_POST['wpdo_post_stress_create'] ) && check_admin_referer( 'wpdo_post_stress_create' ) && class_exists( 'TMDO_Post_Stress_Tester' ) ) {
|
||||
$post_type = isset( $_POST['post_type'] ) ? sanitize_key( wp_unslash( (string) $_POST['post_type'] ) ) : 'product';
|
||||
$count = isset( $_POST['count'] ) ? max( 1, min( 10000, absint( wp_unslash( $_POST['count'] ) ) ) ) : 100;
|
||||
$mode = isset( $_POST['mode'] ) && 'realistic' === sanitize_key( wp_unslash( (string) $_POST['mode'] ) ) ? 'realistic' : 'fast';
|
||||
try {
|
||||
$result = 'realistic' === $mode
|
||||
? TMDO_Post_Stress_Tester::create_realistic( $post_type, $count )
|
||||
@@ -655,7 +655,7 @@ class TMDO_Admin {
|
||||
}
|
||||
|
||||
// v2.11.0: Post Stress Test — cleanup all test posts.
|
||||
if ( isset( $_GET['wpdo_post_stress_cleanup'] ) && check_admin_referer( 'wpdo_post_stress_cleanup' ) && class_exists( 'TMDO_Post_Stress_Tester' ) ) {
|
||||
if ( isset( $_POST['wpdo_post_stress_cleanup'] ) && check_admin_referer( 'wpdo_post_stress_cleanup' ) && class_exists( 'TMDO_Post_Stress_Tester' ) ) {
|
||||
try {
|
||||
$result = TMDO_Post_Stress_Tester::cleanup();
|
||||
$msg = 'stress_cleanup_' . (int) $result['deleted_posts'];
|
||||
@@ -678,8 +678,8 @@ class TMDO_Admin {
|
||||
}
|
||||
|
||||
// v2.11.0: Post Stress Test — run benchmark on all 7 groups.
|
||||
if ( isset( $_GET['wpdo_post_stress_bench'] ) && check_admin_referer( 'wpdo_post_stress_bench' ) && class_exists( 'TMDO_Post_Migration' ) ) {
|
||||
$samples = isset( $_GET['samples'] ) ? max( 10, min( 1000, absint( wp_unslash( $_GET['samples'] ) ) ) ) : 100;
|
||||
if ( isset( $_POST['wpdo_post_stress_bench'] ) && check_admin_referer( 'wpdo_post_stress_bench' ) && class_exists( 'TMDO_Post_Migration' ) ) {
|
||||
$samples = isset( $_POST['samples'] ) ? max( 10, min( 1000, absint( wp_unslash( $_POST['samples'] ) ) ) ) : 100;
|
||||
set_transient( 'wpdo_post_stress_bench_samples', $samples, 60 );
|
||||
$msg = 'stress_bench_ready_' . $samples;
|
||||
wp_safe_redirect(
|
||||
@@ -695,7 +695,7 @@ class TMDO_Admin {
|
||||
}
|
||||
|
||||
// v2.9.0 Phase 0: wp_postmeta garbage cleanup (transients/_wp_old_date/stale _edit_lock).
|
||||
if ( isset( $_GET['wpdo_postmeta_cleanup'] ) && check_admin_referer( 'wpdo_postmeta_cleanup' ) && class_exists( 'TMDO_Postmeta_Cleaner' ) ) {
|
||||
if ( isset( $_POST['wpdo_postmeta_cleanup'] ) && check_admin_referer( 'wpdo_postmeta_cleanup' ) && class_exists( 'TMDO_Postmeta_Cleaner' ) ) {
|
||||
$deleted = TMDO_Postmeta_Cleaner::delete_garbage( TMDO_Postmeta_Cleaner::TARGET_ALL );
|
||||
if ( class_exists( 'TMDO_Logger' ) ) {
|
||||
TMDO_Logger::info(
|
||||
@@ -1261,21 +1261,11 @@ class TMDO_Admin {
|
||||
<p class="description">
|
||||
<?php esc_html_e( 'POST /view 端點因 IP 或 Cookie 重複計數而被拒絕的次數(HTTP 429)。', '2meet-data-optimizer' ); ?>
|
||||
<?php if ( $rl_total > 0 ) : ?>
|
||||
<a href="
|
||||
<?php
|
||||
echo esc_url(
|
||||
wp_nonce_url(
|
||||
add_query_arg(
|
||||
array(
|
||||
'page' => '2meet-data-optimizer',
|
||||
'wpdo_reset_rl_stats' => '1',
|
||||
)
|
||||
),
|
||||
'wpdo_reset_rl_stats'
|
||||
)
|
||||
);
|
||||
?>
|
||||
" class="button button-small"><?php esc_html_e( '重置統計', '2meet-data-optimizer' ); ?></a>
|
||||
<form method="post" style="display:inline">
|
||||
<input type="hidden" name="wpdo_reset_rl_stats" value="1">
|
||||
<?php wp_nonce_field( 'wpdo_reset_rl_stats' ); ?>
|
||||
<button type="submit" class="button button-small"><?php esc_html_e( '重置統計', '2meet-data-optimizer' ); ?></button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</p>
|
||||
<table class="widefat striped wpdo-table--narrow">
|
||||
@@ -2849,15 +2839,17 @@ wpdo.getListings({ per_page: 3 }).then(r => console.log(r));'
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a class="button button-primary"
|
||||
href="<?php echo esc_url( wp_nonce_url( add_query_arg( array( 'wpdo_create_snapshot' => '1' ), admin_url( 'tools.php?page=' . self::MENU_SLUG ) ), 'wpdo_create_snapshot' ) ); ?>">
|
||||
<?php esc_html_e( '立即建立快照', '2meet-data-optimizer' ); ?>
|
||||
</a>
|
||||
<a class="button"
|
||||
href="<?php echo esc_url( wp_nonce_url( add_query_arg( array( 'wpdo_prune_snapshots' => '1' ), admin_url( 'tools.php?page=' . self::MENU_SLUG ) ), 'wpdo_prune_snapshots' ) ); ?>"
|
||||
onclick="return confirm('<?php echo esc_js( __( '確定要清除過期快照嗎?', '2meet-data-optimizer' ) ); ?>');">
|
||||
<?php esc_html_e( '清除過期快照', '2meet-data-optimizer' ); ?>
|
||||
</a>
|
||||
<form method="post" style="display:inline">
|
||||
<input type="hidden" name="wpdo_create_snapshot" value="1">
|
||||
<?php wp_nonce_field( 'wpdo_create_snapshot' ); ?>
|
||||
<button type="submit" class="button button-primary"><?php esc_html_e( '立即建立快照', '2meet-data-optimizer' ); ?></button>
|
||||
</form>
|
||||
<form method="post" style="display:inline">
|
||||
<input type="hidden" name="wpdo_prune_snapshots" value="1">
|
||||
<?php wp_nonce_field( 'wpdo_prune_snapshots' ); ?>
|
||||
<button type="submit" class="button"
|
||||
onclick="return confirm('<?php echo esc_js( __( '確定要清除過期快照嗎?', '2meet-data-optimizer' ) ); ?>');"><?php esc_html_e( '清除過期快照', '2meet-data-optimizer' ); ?></button>
|
||||
</form>
|
||||
</p>
|
||||
|
||||
<?php if ( empty( $rows ) ) : ?>
|
||||
@@ -2887,11 +2879,12 @@ wpdo.getListings({ per_page: 3 }).then(r => console.log(r));'
|
||||
<td><?php echo esc_html( $row['created_at'] ); ?></td>
|
||||
<td><?php echo esc_html( (string) ( $row['expires_at'] ?? '—' ) ); ?></td>
|
||||
<td>
|
||||
<a class="button button-small button-link-delete"
|
||||
href="<?php echo esc_url( wp_nonce_url( add_query_arg( array( 'wpdo_delete_snapshot' => $row['snapshot_id'] ), admin_url( 'tools.php?page=' . self::MENU_SLUG ) ), 'wpdo_delete_snapshot' ) ); ?>"
|
||||
onclick="return confirm('<?php echo esc_js( __( '確定要刪除此快照?無法復原。', '2meet-data-optimizer' ) ); ?>');">
|
||||
<?php esc_html_e( '刪除', '2meet-data-optimizer' ); ?>
|
||||
</a>
|
||||
<form method="post" style="display:inline">
|
||||
<input type="hidden" name="wpdo_delete_snapshot" value="<?php echo esc_attr( $row['snapshot_id'] ); ?>">
|
||||
<?php wp_nonce_field( 'wpdo_delete_snapshot' ); ?>
|
||||
<button type="submit" class="button button-small button-link-delete"
|
||||
onclick="return confirm('<?php echo esc_js( __( '確定要刪除此快照?無法復原。', '2meet-data-optimizer' ) ); ?>');"><?php esc_html_e( '刪除', '2meet-data-optimizer' ); ?></button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
@@ -3109,10 +3102,6 @@ wpdo.getListings({ per_page: 3 }).then(r => console.log(r));'
|
||||
$conf = (float) $r['confidence'];
|
||||
$bar_w = (int) round( $conf * 100 );
|
||||
$color = $conf >= 0.7 ? '#46b450' : ( $conf >= 0.5 ? '#dba617' : '#c3c4c7' );
|
||||
$enable_url = wp_nonce_url(
|
||||
add_query_arg( 'wpdo_enable_module', $module, admin_url( 'tools.php?page=' . self::MENU_SLUG ) ),
|
||||
'wpdo_enable_module'
|
||||
);
|
||||
printf(
|
||||
'<tr><td><code>%s</code></td><td><div style="background:#f0f0f1;border-radius:3px;width:80px;height:18px;position:relative;"><div style="background:%s;width:%d%%;height:100%%;border-radius:3px;"></div><span style="position:absolute;inset:0;text-align:center;font-size:11px;line-height:18px;">%s</span></div></td>',
|
||||
esc_html( $module ),
|
||||
@@ -3129,12 +3118,15 @@ wpdo.getListings({ per_page: 3 }).then(r => console.log(r));'
|
||||
}
|
||||
echo '</td>';
|
||||
printf( '<td><code>%s</code></td>', esc_html( (string) $r['current_state'] ) );
|
||||
echo '<td><form method="post" style="display:inline">';
|
||||
printf( '<input type="hidden" name="wpdo_enable_module" value="%s">', esc_attr( $module ) );
|
||||
wp_nonce_field( 'wpdo_enable_module' );
|
||||
printf(
|
||||
'<td><a class="button button-primary button-small" href="%s" onclick="return confirm(\'%s\');">%s</a></td>',
|
||||
esc_url( $enable_url ),
|
||||
'<button type="submit" class="button button-primary button-small" onclick="return confirm(\'%s\');">%s</button>',
|
||||
esc_js( __( '確定啟用此 module(推進到 dual_write)?', '2meet-data-optimizer' ) ),
|
||||
esc_html__( '✅ 啟用', '2meet-data-optimizer' )
|
||||
);
|
||||
echo '</form></td>';
|
||||
echo '</tr>';
|
||||
}
|
||||
echo '</tbody></table>';
|
||||
|
||||
@@ -59,8 +59,6 @@ class TMDO_Dashboard_Widget {
|
||||
*/
|
||||
public static function render(): void {
|
||||
$page_url = admin_url( 'tools.php?page=wp-data-optimizer' );
|
||||
$run_health_url = wp_nonce_url( add_query_arg( array( 'wpdo_run_health' => '1' ), $page_url ), 'wpdo_run_health' );
|
||||
$create_snap_url = wp_nonce_url( add_query_arg( array( 'wpdo_create_snapshot' => '1' ), $page_url ), 'wpdo_create_snapshot' );
|
||||
$bridge_url = add_query_arg( 'tab', 'entity-bridge', $page_url );
|
||||
|
||||
$status = self::compute_status();
|
||||
@@ -188,10 +186,6 @@ class TMDO_Dashboard_Widget {
|
||||
? TMDO_Postmeta_Cleaner::count_garbage( TMDO_Postmeta_Cleaner::TARGET_ALL )
|
||||
: array( 'total' => 0 );
|
||||
if ( ! empty( $gc['total'] ) && (int) $gc['total'] > 0 ) :
|
||||
$cleanup_url = wp_nonce_url(
|
||||
add_query_arg( array( 'wpdo_postmeta_cleanup' => '1' ), $page_url ),
|
||||
'wpdo_postmeta_cleanup'
|
||||
);
|
||||
$confirm_msg = sprintf(
|
||||
/* translators: %s: total garbage row count */
|
||||
esc_html__( '即將從 wp_postmeta 刪除 %s 行垃圾資料(transients + _wp_old_date + 過期 _edit_lock)。確認執行?', '2meet-data-optimizer' ),
|
||||
@@ -211,10 +205,14 @@ class TMDO_Dashboard_Widget {
|
||||
esc_html( number_format_i18n( (int) $gc['edit_locks'] ) )
|
||||
);
|
||||
?>
|
||||
<a href="<?php echo esc_url( $cleanup_url ); ?>"
|
||||
<form method="post" action="<?php echo esc_url( $page_url ); ?>" style="display:inline">
|
||||
<input type="hidden" name="wpdo_postmeta_cleanup" value="1">
|
||||
<?php wp_nonce_field( 'wpdo_postmeta_cleanup' ); ?>
|
||||
<button type="submit" class="button-link"
|
||||
onclick="return confirm(<?php echo wp_json_encode( $confirm_msg ); ?>);">
|
||||
<strong><?php esc_html_e( '一鍵清理 →', '2meet-data-optimizer' ); ?></strong>
|
||||
</a>
|
||||
</button>
|
||||
</form>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
|
||||
@@ -275,15 +273,19 @@ class TMDO_Dashboard_Widget {
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="wpdo-widget-actions">
|
||||
<a class="button button-small button-primary" href="<?php echo esc_url( $run_health_url ); ?>">
|
||||
<?php esc_html_e( '跑健康檢查', '2meet-data-optimizer' ); ?>
|
||||
</a>
|
||||
<form method="post" action="<?php echo esc_url( $page_url ); ?>" style="display:inline">
|
||||
<input type="hidden" name="wpdo_run_health" value="1">
|
||||
<?php wp_nonce_field( 'wpdo_run_health' ); ?>
|
||||
<button type="submit" class="button button-small button-primary"><?php esc_html_e( '跑健康檢查', '2meet-data-optimizer' ); ?></button>
|
||||
</form>
|
||||
<a class="button button-small" href="<?php echo esc_url( $bridge_url ); ?>">
|
||||
<?php esc_html_e( 'Entity Bridge', '2meet-data-optimizer' ); ?>
|
||||
</a>
|
||||
<a class="button button-small" href="<?php echo esc_url( $create_snap_url ); ?>">
|
||||
<?php esc_html_e( '建立快照', '2meet-data-optimizer' ); ?>
|
||||
</a>
|
||||
<form method="post" action="<?php echo esc_url( $page_url ); ?>" style="display:inline">
|
||||
<input type="hidden" name="wpdo_create_snapshot" value="1">
|
||||
<?php wp_nonce_field( 'wpdo_create_snapshot' ); ?>
|
||||
<button type="submit" class="button button-small"><?php esc_html_e( '建立快照', '2meet-data-optimizer' ); ?></button>
|
||||
</form>
|
||||
<a href="<?php echo esc_url( $page_url ); ?>" style="float: right; padding-top: 4px;">
|
||||
<?php esc_html_e( '完整儀表板 →', '2meet-data-optimizer' ); ?>
|
||||
</a>
|
||||
|
||||
@@ -343,15 +343,18 @@ class TMDO_Setup_Wizard {
|
||||
<p style="color: #46b450; font-weight: bold;">✅ <?php esc_html_e( '快照已建立。可隨時於「備份快照」tab 查看與管理。', '2meet-data-optimizer' ); ?></p>
|
||||
<?php else : ?>
|
||||
<p><?php esc_html_e( '我們會建立一個 baseline snapshot,命名為 wizard_baseline,保留 365 天。即使你日後沒做任何 destructive 操作,這也是一個 known-good 還原點。', '2meet-data-optimizer' ); ?></p>
|
||||
<form method="post" action="<?php echo esc_url( admin_url( 'tools.php?page=wpdo-setup-wizard&step=4' ) ); ?>" style="display:inline">
|
||||
<?php wp_nonce_field( self::NONCE_NAME ); ?>
|
||||
<input type="hidden" name="wpdo_take_snapshot" value="1">
|
||||
<p>
|
||||
<a class="button button-secondary"
|
||||
href="<?php echo esc_url( wp_nonce_url( admin_url( 'tools.php?page=wpdo-setup-wizard&step=4&action=take_snapshot' ), self::NONCE_NAME ) ); ?>">
|
||||
<button type="submit" class="button button-secondary">
|
||||
<?php esc_html_e( '建立 baseline snapshot', '2meet-data-optimizer' ); ?>
|
||||
</a>
|
||||
</button>
|
||||
</p>
|
||||
</form>
|
||||
<?php
|
||||
// Take snapshot if action requested.
|
||||
if ( isset( $_GET['action'] ) && 'take_snapshot' === $_GET['action'] && check_admin_referer( self::NONCE_NAME ) && class_exists( 'TMDO_Snapshot_Manager' ) ) {
|
||||
// Take snapshot if action requested (POST + nonce, prevents Referer leak — v3.3.x P1-6b hardening).
|
||||
if ( isset( $_POST['wpdo_take_snapshot'] ) && check_admin_referer( self::NONCE_NAME ) && class_exists( 'TMDO_Snapshot_Manager' ) ) {
|
||||
$result = TMDO_Snapshot_Manager::create(
|
||||
'manual',
|
||||
array(),
|
||||
|
||||
@@ -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'; ?>"
|
||||
<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' ); ?>
|
||||
</a>
|
||||
</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"
|
||||
<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' ); ?>
|
||||
</a>
|
||||
</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"
|
||||
<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' ); ?>
|
||||
</a>
|
||||
</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"
|
||||
<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' ); ?>
|
||||
</a>
|
||||
</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"
|
||||
<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' ); ?>
|
||||
</a>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
* 2. 即時進度(progress bar + processed/target/rate/ETA/peak memory)
|
||||
* 3. Benchmark 報告(write metrics + DB sizes + query performance)
|
||||
*
|
||||
* Backward-compat: legacy GET ?wpdo_post_stress_create / cleanup / bench
|
||||
* handlers in admin still work for bookmarked URLs; the new UI uses REST.
|
||||
* The UI uses REST/AJAX exclusively; legacy admin action handlers now require POST.
|
||||
*
|
||||
* Variables in scope from render_post_stress_test():
|
||||
* $test_post_count — int, posts matching TMDO_STRESS_TEST_ prefix
|
||||
@@ -23,11 +22,6 @@ if ( ! defined( 'ABSPATH' ) ) {
|
||||
|
||||
$page_url = admin_url( 'tools.php?page=wp-data-optimizer&tab=post-stress-test' );
|
||||
|
||||
$cleanup_url = wp_nonce_url(
|
||||
add_query_arg( array( 'wpdo_post_stress_cleanup' => '1' ), $page_url ),
|
||||
'wpdo_post_stress_cleanup'
|
||||
);
|
||||
|
||||
// Read-only display banner — server-set redirect message, no form processing.
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$msg_raw = isset( $_GET['wpdo_msg'] ) ? sanitize_text_field( wp_unslash( (string) $_GET['wpdo_msg'] ) ) : '';
|
||||
|
||||
Reference in New Issue
Block a user