a8190a6828
E4:38 個子指令補 'wp tmdo' 註冊(原本只有 'wp wpdo')
cli-v2 15、cli-post 8、cli-member 7、cli-term-comment 8。
CLAUDE.md 宣稱 tmdo 為主命名空間,但 wp tmdo bridge-status /
snapshot create 等先前根本不存在。
E6:MENU_SLUG 由 '2meet-data-optimizer' 改回 'wp-data-optimizer'。
B 內部有 10+ 處連結(help-tabs、setup-wizard ×5、conflict-monitor、
export、3 個 template)硬編碼舊 slug,與註冊值不符 → 這些連結目前
全部 404。既然本外掛已沿用 wpdo_ 的表/option/cron/hook/CLI 命名空間,
選單 slug 一併回到同一套最連貫,A 退休後亦無衝突。
E7:13 處殘留的 'uae' text domain 改為 '2meet-data-optimizer'
(conflict-detector 3、mode-manager 9、entity-migration-engine 1;
另一處 'uae' 是陣列 key 非 text domain,保留)。
unit 451 / integration 398 GREEN
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TbG1keQQ7XBa7qMQY16KCY
148 lines
5.1 KiB
PHP
148 lines
5.1 KiB
PHP
<?php
|
||
/**
|
||
* TMDO_Conflict_Detector — 偵測 UAE 與 UAEPG 同欄位衝突
|
||
*
|
||
* 背景:
|
||
* UAEPG(PostgreSQL sidecar)以 metadata filter priority 5 攔截,
|
||
* UAE 在 priority 10 攔截。若同一 meta_key 被雙方都登錄,
|
||
* UAEPG 會先短路 return,UAE 寫入永遠不執行,資料靜默遺失。
|
||
*
|
||
* 本類別在欄位登錄階段完成後(init:25)掃描雙方 Registry,
|
||
* 找出 overlap 並:
|
||
* 1. 寫入 error log(供 CI / 監控系統消化)
|
||
* 2. 在管理後台顯示紅色 admin_notices
|
||
* 3. 提供 TMDO_Conflict_Detector::get_conflicts() 與 CLI 指令 wp uae conflicts 查詢
|
||
*
|
||
* 結果 cache 在 static property 內,單 request 內不重複掃描。
|
||
*
|
||
* @package WP_Data_Optimizer
|
||
* @since 1.1.2
|
||
*/
|
||
|
||
// phpcs:disable Squiz.Commenting,Generic.Commenting,WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber,Generic.CodeAnalysis.UnusedFunctionParameter,Generic.CodeAnalysis.EmptyStatement,Squiz.PHP.DisallowMultipleAssignments,Squiz.PHP.DisallowSizeFunctionsInLoops,WordPress.WP.I18n.MissingTranslatorsComment,WordPress.PHP.NoSilencedErrors,WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents,Squiz.PHP.CommentedOutCode,Universal.NamingConventions.NoReservedKeywordParameterNames,WordPress.PHP.YodaConditions,Squiz.Commenting.InlineComment.InvalidEndChar -- PR-1 ported from UAE; cleanup PR scheduled.
|
||
|
||
|
||
defined( 'ABSPATH' ) || exit;
|
||
|
||
final class TMDO_Conflict_Detector {
|
||
|
||
/**
|
||
* @var array<int, array{entity_type:string, meta_key:string, wpdo_group:string, uaepg_group:string}>|null
|
||
*/
|
||
private static ?array $cache = null;
|
||
|
||
public static function init(): void {
|
||
// init:25 — register_default_fields 於 init:20 觸發 wpdo_register_fields,
|
||
// UAEPG 亦在 init:20 觸發 uaepg_register_fields;此時 priority 25 可確保雙邊皆完成登錄。
|
||
add_action( 'init', array( self::class, 'scan' ), 25 );
|
||
|
||
if ( is_admin() ) {
|
||
add_action( 'admin_notices', array( self::class, 'maybe_render_admin_notice' ) );
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 掃描並 cache 衝突清單。
|
||
*
|
||
* @return array<int, array{entity_type:string, meta_key:string, wpdo_group:string, uaepg_group:string}>
|
||
*/
|
||
public static function scan(): array {
|
||
if ( self::$cache !== null ) {
|
||
return self::$cache;
|
||
}
|
||
|
||
if ( ! class_exists( 'UAEPG_Registry' ) ) {
|
||
return self::$cache = array();
|
||
}
|
||
|
||
$conflicts = array();
|
||
|
||
foreach ( TMDO_Entity_Registry::get_all_fields() as $entity_type => $fields_by_key ) {
|
||
foreach ( $fields_by_key as $meta_key => $wpdo_field ) {
|
||
if ( ! UAEPG_Registry::is_managed( $entity_type, $meta_key ) ) {
|
||
continue;
|
||
}
|
||
|
||
$uaepg_field = UAEPG_Registry::get_field( $entity_type, $meta_key );
|
||
|
||
$conflicts[] = array(
|
||
'entity_type' => $entity_type,
|
||
'meta_key' => $meta_key,
|
||
'wpdo_group' => $wpdo_field['group'] ?? '(unknown)',
|
||
'uaepg_group' => $uaepg_field['group'] ?? '(unknown)',
|
||
);
|
||
}
|
||
}
|
||
|
||
if ( $conflicts ) {
|
||
TMDO_Logger::error(
|
||
'schema_registry',
|
||
'field_registration_conflict',
|
||
sprintf( 'Field registration conflict: %d field(s) affected', count( $conflicts ) ),
|
||
$conflicts
|
||
);
|
||
}
|
||
|
||
return self::$cache = $conflicts;
|
||
}
|
||
|
||
/**
|
||
* 取得衝突清單(若尚未掃描,觸發掃描)。
|
||
*/
|
||
public static function get_conflicts(): array {
|
||
return self::$cache ?? self::scan();
|
||
}
|
||
|
||
/**
|
||
* 測試用:強制清除 cache,下次 scan() 會重跑。
|
||
*
|
||
* @internal
|
||
*/
|
||
public static function reset_cache(): void {
|
||
self::$cache = null;
|
||
}
|
||
|
||
/**
|
||
* 若有衝突則輸出管理後台紅色通知。
|
||
*/
|
||
public static function maybe_render_admin_notice(): void {
|
||
$conflicts = self::get_conflicts();
|
||
if ( ! $conflicts ) {
|
||
return;
|
||
}
|
||
|
||
if ( ! TMDO_Capability::current_user_can_admin() ) {
|
||
return;
|
||
}
|
||
|
||
$count = count( $conflicts );
|
||
$lines = array();
|
||
foreach ( array_slice( $conflicts, 0, 5 ) as $c ) {
|
||
$lines[] = sprintf(
|
||
'%s / %s(UAE 群組「%s」 ↔ UAEPG 群組「%s」)',
|
||
esc_html( $c['entity_type'] ),
|
||
esc_html( $c['meta_key'] ),
|
||
esc_html( $c['wpdo_group'] ),
|
||
esc_html( $c['uaepg_group'] )
|
||
);
|
||
}
|
||
$extra = $count > 5 ? sprintf( __( '… 另有 %d 個欄位未顯示', '2meet-data-optimizer' ), $count - 5 ) : '';
|
||
|
||
printf(
|
||
'<div class="notice notice-error"><p><strong>%s</strong></p><ul style="list-style:disc;margin-left:20px;"><li>%s</li></ul>%s<p>%s <code>wp uae conflicts</code></p></div>',
|
||
esc_html(
|
||
sprintf(
|
||
/* translators: %d: conflict count */
|
||
__( 'UAE × UAEPG 欄位衝突偵測:發現 %d 個同 meta_key 被雙方 Registry 重複登錄 — UAEPG 會搶先短路,UAE 寫入將被靜默跳過', '2meet-data-optimizer' ),
|
||
$count
|
||
)
|
||
),
|
||
// $lines elements were each individually esc_html()'d above (lines 121-124).
|
||
// implode joins escaped strings with literal HTML markers — safe.
|
||
implode( '</li><li>', $lines ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- pre-escaped above.
|
||
$extra ? '<p><em>' . esc_html( $extra ) . '</em></p>' : '',
|
||
esc_html__( '查看完整清單:', '2meet-data-optimizer' )
|
||
);
|
||
}
|
||
}
|