Files
2meet-data-optimizer/admin/templates/post-migration-wizard.php
T
wpdev d52d604d7a
Anti-EAV Lint + Quality Gate / anti-eav-lint (push) Successful in 8s
Tests / Unit Tests (push) Successful in 10s
Tests / Integration Tests (push) Successful in 35s
Tests / PHP Lint (push) Successful in 7s
Tests / PHPCS (push) Successful in 19s
Tests / PHPStan (push) Successful in 24s
style(admin): inline style 全面改用 CSS class(PR-H)
admin/ 的 inline style= 由 348 處降到 9 處,剩下的 9 處全是 CSS custom
property 載體(--wpdo-bar-width / --wpdo-cov-pct 等動態數值),與來源外掛的
設計一致。

- wpdo-admin.css 543 → 911 行:補上 148 個 class(來源檔在 selector 與
  rule-block 層級都是既有內容的超集,逐條核對過),另加 .wpdo-form-inline
- 5 個變數改為 class 版本:$mode_cls / $badge_mode_cls / $diffs_cls /
  $ap_cls / $e_mode_cls;3 個 stress-test template 補 $mode_text_cls /
  $mode_card_cls
- 刪掉因此變成孤兒的 $mode_badge / $style / $mode_style / $badge_style /
  $e_mode_bg / $badge_bg
- entity card 改 .wpdo-eb-card、pipeline dot 改 .wpdo-stage-pill、demote 按鈕
  改 .wpdo-eb-btn-dim;.wpdo-native-counts / .wpdo-recommendation /
  .wpdo-setup-banner-btn 的重複 inline style 移除
- dashboard 的 top-views 查詢一併改用上一個 commit 的 TMDO_Zone_Warm::VIEW_KEY

PHPStan 抓出自動轉換造成的 11 個未定義變數,已全數補回定義並複驗。
2026-07-31 10:34:57 +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
*/
declare(strict_types=1);
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' ),
);
// 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">
<form method="post">
<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>
<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">
<form method="post">
<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>
<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">
<form method="post">
<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>
<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">
<form method="post">
<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>
<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">
<form method="post">
<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>
<div class="wpdo-card wpdo-card--light">
<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>