Files
2meet-data-optimizer/admin/templates/post-migration-wizard.php
T
wpdev d36bb954d1 chore: initial snapshot of 2meet-data-optimizer v0.1.0
Baseline before backporting wp-data-optimizer v3.0.1-v3.4.6.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TbG1keQQ7XBa7qMQY16KCY
2026-07-31 05:06:36 +08:00

299 lines
12 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* Post Migration Wizard template (v2.10.0).
*
* Variables in scope from render_post_migration_wizard():
* $diagnose — TMDO_Post_Migration::diagnose() output
* $garbage — TMDO_Postmeta_Cleaner::count_garbage('all') output
*
* Sync execution model — each action button POSTs back to this page with a
* query arg + nonce, the action runs to completion in one request, then
* redirects with `wpdo_msg=*` for the success/failure banner.
*
* @package WP_Data_Optimizer
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
$page_url = admin_url( 'tools.php?page=wp-data-optimizer&tab=post-migration-wizard' );
$total_eav = 0;
foreach ( $diagnose['groups'] as $g ) {
$total_eav += (int) $g['eav_rows'];
}
$total_flat = 0;
foreach ( $diagnose['groups'] as $g ) {
$total_flat += (int) $g['flat_rows'];
}
$mode_label = array(
'disabled' => __( '🔵 disabled — wp_postmeta 仍是 source-of-truth', '2meet-data-optimizer' ),
'dual_write' => __( '🟡 dual_write — 同時寫 wp_postmeta + flat 表', '2meet-data-optimizer' ),
'shadow_read' => __( '🟠 shadow_read — 寫雙路徑,讀比對', '2meet-data-optimizer' ),
'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.
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$msg_raw = isset( $_GET['wpdo_msg'] ) ? sanitize_text_field( wp_unslash( (string) $_GET['wpdo_msg'] ) ) : '';
/**
* Map redirect message code → user-facing string.
*
* @param string $code Message code from wpdo_msg query arg.
* @return string
*/
$wpdo_format_msg = static function ( string $code ): string {
$prefix = strtok( $code, '_' );
$rest = substr( $code, strlen( (string) $prefix ) + 1 );
switch ( $prefix ) {
case 'backfill':
return sprintf(
/* translators: %s: total flat rows */
__( 'Backfill 完成:7 個 group 共 %s 個 post 已寫入 flat 表。', '2meet-data-optimizer' ),
$rest
);
case 'cutover':
return sprintf(
/* translators: %s: rows */
__( 'Legacy hot table cutover 完成:%s 行已 copy 至 flat 表。', '2meet-data-optimizer' ),
$rest
);
case 'promote':
return sprintf(
/* translators: %s: target mode */
__( 'Post mode 已升級至 %s。', '2meet-data-optimizer' ),
$rest
);
case 'postmetacleanupdone':
return sprintf(
/* translators: %s: deleted rows */
__( '已從 wp_postmeta 刪除 %s 行垃圾。', '2meet-data-optimizer' ),
$rest
);
case 'err':
return sprintf(
/* translators: %s: error code */
__( '錯誤:%s(請查看 wpdo_errors 日誌)。', '2meet-data-optimizer' ),
str_replace( '_', ' ', $rest )
);
default:
return $code;
}
};
?>
<style>
.wpdo-post-wizard h2 { margin-top: 1.4em; }
.wpdo-post-wizard .wpdo-card {
background: #fff;
border: 1px solid #c3c4c7;
border-radius: 4px;
padding: 1em 1.4em;
margin: 0.8em 0;
}
.wpdo-post-wizard .wpdo-stats {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 1em;
margin: 1em 0;
}
.wpdo-post-wizard .wpdo-stat {
background: #f6f7f7;
border-left: 4px solid #2271b1;
padding: 0.8em 1em;
border-radius: 3px;
}
.wpdo-post-wizard .wpdo-stat-label { font-size: 0.85em; color: #50575e; }
.wpdo-post-wizard .wpdo-stat-value { font-size: 1.6em; font-weight: 600; }
.wpdo-post-wizard .wpdo-mode-banner {
padding: 0.6em 1em;
border-radius: 3px;
margin: 0.8em 0;
}
.wpdo-post-wizard .wpdo-mode-disabled { background: #e5f5fa; border-left: 4px solid #00a0d2; }
.wpdo-post-wizard .wpdo-mode-dual_write { background: #fffbe5; border-left: 4px solid #dba617; }
.wpdo-post-wizard .wpdo-mode-shadow_read { background: #fff4e5; border-left: 4px solid #d54e21; }
.wpdo-post-wizard .wpdo-mode-aeav_only { background: #ecf7ed; border-left: 4px solid #46b450; }
.wpdo-post-wizard table.widefat td.right { text-align: right; }
.wpdo-post-wizard .actions {
margin-top: 1.6em;
padding-top: 1em;
border-top: 1px solid #dcdcde;
}
.wpdo-post-wizard .actions .button { margin-right: 0.4em; margin-bottom: 0.4em; }
</style>
<div class="wpdo-post-wizard">
<?php if ( $msg_raw ) : ?>
<?php
$msg_class = str_starts_with( $msg_raw, 'err' ) ? 'notice-error' : 'notice-success';
$msg_text = $wpdo_format_msg( $msg_raw );
?>
<div class="notice <?php echo esc_attr( $msg_class ); ?> is-dismissible">
<p><?php echo esc_html( $msg_text ); ?></p>
</div>
<?php endif; ?>
<h2><?php esc_html_e( 'Post Entity 遷移精靈', '2meet-data-optimizer' ); ?></h2>
<p class="description">
<?php esc_html_e( '把 wp_postmeta 中的 47 個 managed keys 遷移到 7 張 flat 表(wp_wpdo_post_*)。每個按鈕只執行單一階段;按順序執行可達成完整 EAV → flat 切換。', '2meet-data-optimizer' ); ?>
</p>
<!-- Mode banner -->
<div class="wpdo-mode-banner wpdo-mode-<?php echo esc_attr( $diagnose['mode'] ); ?>">
<strong><?php esc_html_e( '當前 post mode', '2meet-data-optimizer' ); ?></strong>
<?php echo esc_html( $mode_label[ $diagnose['mode'] ] ?? $diagnose['mode'] ); ?>
</div>
<!-- Stats -->
<div class="wpdo-stats">
<div class="wpdo-stat">
<div class="wpdo-stat-label"><?php esc_html_e( 'Posts', '2meet-data-optimizer' ); ?></div>
<div class="wpdo-stat-value"><?php echo esc_html( number_format_i18n( $diagnose['posts'] ) ); ?></div>
</div>
<div class="wpdo-stat">
<div class="wpdo-stat-label"><?php esc_html_e( 'Postmeta', '2meet-data-optimizer' ); ?></div>
<div class="wpdo-stat-value"><?php echo esc_html( number_format_i18n( $diagnose['postmeta'] ) ); ?></div>
</div>
<div class="wpdo-stat">
<div class="wpdo-stat-label"><?php esc_html_e( 'Ratio', '2meet-data-optimizer' ); ?></div>
<div class="wpdo-stat-value">1:<?php echo esc_html( (string) $diagnose['ratio'] ); ?></div>
</div>
<div class="wpdo-stat">
<div class="wpdo-stat-label"><?php esc_html_e( 'Total flat rows', '2meet-data-optimizer' ); ?></div>
<div class="wpdo-stat-value"><?php echo esc_html( number_format_i18n( $total_flat ) ); ?></div>
</div>
</div>
<!-- Group breakdown -->
<h3><?php esc_html_e( '7 個 group 詳細狀態', '2meet-data-optimizer' ); ?></h3>
<table class="widefat striped">
<thead>
<tr>
<th><?php esc_html_e( 'Group', '2meet-data-optimizer' ); ?></th>
<th><?php esc_html_e( 'post_type', '2meet-data-optimizer' ); ?></th>
<th class="right"><?php esc_html_e( 'Keys', '2meet-data-optimizer' ); ?></th>
<th class="right"><?php esc_html_e( 'EAV rows', '2meet-data-optimizer' ); ?></th>
<th class="right"><?php esc_html_e( 'Flat rows', '2meet-data-optimizer' ); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ( $diagnose['groups'] as $name => $g ) : ?>
<tr>
<td><code><?php echo esc_html( $name ); ?></code></td>
<td><?php echo esc_html( $g['post_type'] ?: '(any)' ); ?></td>
<td class="right"><?php echo esc_html( (string) count( $g['keys'] ) ); ?></td>
<td class="right"><?php echo esc_html( number_format_i18n( (int) $g['eav_rows'] ) ); ?></td>
<td class="right"><?php echo esc_html( number_format_i18n( (int) $g['flat_rows'] ) ); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<!-- Phase actions -->
<div class="wpdo-card">
<h3><?php esc_html_e( '步驟 1:清 wp_postmeta 垃圾(v2.9.0 phase 0', '2meet-data-optimizer' ); ?></h3>
<p>
<?php
printf(
/* translators: 1: total garbage rows, 2: transients, 3: wp_old_date, 4: edit_locks */
esc_html__( '偵測到 %1$s 行可清理垃圾(transients %2$s + _wp_old_date %3$s + 過期 _edit_lock %4$s)。', '2meet-data-optimizer' ),
esc_html( number_format_i18n( (int) $garbage['total'] ) ),
esc_html( number_format_i18n( (int) $garbage['transients'] ) ),
esc_html( number_format_i18n( (int) $garbage['wp_old_date'] ) ),
esc_html( number_format_i18n( (int) $garbage['edit_locks'] ) )
);
?>
</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>
</div>
</div>
<div class="wpdo-card">
<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>
</div>
</div>
<div class="wpdo-card">
<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>
</div>
</div>
<div class="wpdo-card">
<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>
</div>
</div>
<div class="wpdo-card">
<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>
</div>
</div>
<div class="wpdo-card" style="background: #f6f7f7;">
<h3><?php esc_html_e( '回退路徑(rollback', '2meet-data-optimizer' ); ?></h3>
<p>
<?php esc_html_e( '若任一步驟出問題,可下降 mode', '2meet-data-optimizer' ); ?>
<code>wp option update wpdo_bridge_modes '{"post":"disabled",...}'</code>
<?php esc_html_e( '。Legacy wpdo_hot_* 表完整保留 — 直到 v3.0.0 才考慮 DROP。', '2meet-data-optimizer' ); ?>
</p>
</div>
</div>