Files
2meet-data-optimizer/CHANGELOG.md
T
wpdev 6f117f4e9b
Anti-EAV Lint + Quality Gate / anti-eav-lint (pull_request) Successful in 21s
Tests / PHP Lint (pull_request) Successful in 18s
Tests / Unit Tests (pull_request) Successful in 41s
Tests / Integration Tests (pull_request) Successful in 1m25s
Tests / PHPCS (pull_request) Successful in 44s
Tests / PHPStan (pull_request) Successful in 47s
fix(2meet-data-optimizer): 1.4.2 acceptance fixes for archive sweep and uninstall
- Archive sweep waits until idx_original_meta exists (a plugin update does
  not run dbDelta; without the index NOT EXISTS scans the archive per row)
- Duplicate archive cleanup runs from WP-CLI only, never in a web activation
- Uninstall keeps the LatePoint add-on's tmdo_latepoint_db_version
- AJAX save errors stay on screen instead of fading after 2.5 s
- Verify: 2 new integration tests; unit 711, integration 612, phpcs 0, stan OK
- PR: PR-141 (2.4.2 correctness review)
2026-09-28 18:43:50 +08:00

36 KiB
Raw Blame History

Changelog

All notable changes to this plugin will be documented here.

Format follows Keep a Changelog. Versioning follows Semantic Versioning.


[1.4.2] — 2026-09-28 — 手冊已知問題修正(hardening 2.4.2)

性質:修正。無 schema 版本變更;archive 表新增索引(見「升級後」)。來源:2meet-do-manual.md 附錄 F(F-2、F-4〜F-8、F-11)。

Fixed

  • 設定頁會說明模式為何沒有變更:完整表單與 AJAX 區塊儲存都經 TMDO_Mode_Manager::set(), 過去忽略它回傳的 WP_Error:不安全的跳階或 Promotion Guard 拒絕時,頁面仍顯示「已儲存」、 模式卻沒變。現在完整表單在重新導向後顯示拒絕原因(每位使用者一次),AJAX 儲存以錯誤訊息回傳。(PR-135,F-2)
  • archive 不再重複歸檔:wpdo_archive_sweep 略過已在 archive 表的 meta(依 original_meta_id), 過去同一筆 meta 每次 sweep 都再寫一列(dev20:300 列只有 150 筆不同)。archive 表新增 idx_original_meta 索引;wp tmdo install 會補上索引並刪除重複列(保留最早一列)。(PR-136,F-6)
  • entity 健康面板看得到執行中的 backfill:批次以 (entity, group) 參數排程,面板過去只查 (entity),永遠看不到、一直提示可開始。(PR-137,F-11)
  • 停用與解除安裝清掉所有排程:改用 wp_unschedule_hook(),連帶參數排程的事件 (tmdo_drift_resync、backfill 批次)也清除;修正錯誤的 wpdo_user_stress_test_batch 名稱, 補上 wpdo_migration_tick、wpdo_entity_backfill_batch、wpdo_auto_promote_check 與各壓測批次。 解除安裝另外刪除 tmdo_* option 及 tmdo_* transient。(PR-138,F-4、F-5)
  • 解除安裝被拒絕時的提示改為指向 wp tmdo bridge-restore-native(過去說沒有寫回工具);post 壓測工具不再使用已退役的 wc_product 表。(PR-139,F-7、F-8)

升級後

  • 每個站台執行一次 wp tmdo install:為 archive 表加上 idx_original_meta 並刪除重複的 archive 列(刪除重複只在 CLI 執行,web 啟用外掛時不做)。表很大時請在離峰時段執行。
  • 未執行前 archive sweep 會暫停(沒有索引時 NOT EXISTS 會對每筆候選掃整張 archive 表)。 sweep 只複製、不刪 postmeta,暫停不會遺失資料。
  • 解除安裝的 option 清理保留 LatePoint AddOn 的 tmdo_latepoint_db_version;設定頁的 AJAX 錯誤訊息 不再 2.5 秒後自動消失。(PR-141,驗收修正)

已知、未修改

  • 已歸檔的 meta 以 meta_id 判斷:文章還原、修改後再次丟到回收桶,archive 保留的是第一次的值。
  • post 壓測工具預設仍是 product,但 product 已沒有 flat 表,量測會回報 flat_table_missing。

[1.4.1] — 2026-09-28 — 2.4 驗收修正

性質:修正。無 schema 變更。來源:2.4 全面驗收的 security review 與 correctness review。

Fixed

  • meta_key 的排序不再被清掉:查詢帶 meta_key,且 orderby 在多個排序(陣列或 'meta_value date')中仍有 meta_value、meta_value_num 或該 key 名稱時,1.4.0 會把 meta_key 當 EXISTS 路由並清空,WordPress 因此丟掉價格排序。現在只在 orderby 完全沒有引用它時 才路由;meta_compare_key 不是 =(例如 LIKE 會比對到其他 key)時維持原生;排序形狀的 group 只有在 orderby 沒有引用它、meta_value(_num) 或它的 key 時才當成未使用而略過(沒有 meta_key 時它是 WordPress 的主要 clause)。(PR-130)
  • ORDER BY 只改寫單獨的 posts.ID:flat-first 與 copy route 共用 ties_on_post_id(); 外掛加上的 FIELD(wp_posts.ID, …)、(wp_posts.ID IN (…)) 等運算式保持原樣。1.4.0 會在 運算式內插入方向,SQL 語法錯誤、頁面沒有結果。正則失敗(PCRE 回溯或 JIT 上限)時回傳原 SQL,不再變成空字串。(PR-131)
  • 呼叫端以 new WP_Query( array( … ) ) 預設的 router 內部 query var(wpdo_post_clauses、 wpdo_post_sort、wpdo_post_copy_route、wpdo_post_count_sql)一律清除;copy route 的 排序方向改用白名單。無法從網址觸發。(PR-132)
  • 新文章建立的空 flat 列一併寫入 post_status / post_date 副本(wp_insert_post 在 transition_post_status 之後才執行,原本留下 NULL,會讓整個 group 關閉 copy route); wp tmdo post-status-sync 完成後遞增路由計數版本(dry run 不遞增)。(PR-133)

已知、未修改

  • 1.4.0 的 flat-first 排序同值時依 ID 與排序同方向(原生為 ID DESC),集合與總數相同(已接受)。
  • 若日後有 group 註冊名為 post_status / post_date 的欄位,遷移引擎寫副本時 $data 與 $formats 會錯位;目前沒有這樣的 group。

[1.4.0] — 2026-09-28 — 百萬級 listing 的路由排序與日期查詢(hardening 2.4)

性質:效能 + 修正。無 schema 變更;post status/date 副本的索引改為三欄(見「升級後」)。 搭配 HivePress AddOn 0.5.0。來源:dev36 第 3 階壓測(130 萬 listing、121 萬 product、6.2 萬 vendor)。

Changed

  • flat-first 排序讀 flat 的 post_id 作 tie-break:posts.ID 的 tie-break(ours 與外掛追加的) 改讀相等的 flat.post_id,方向與排序一致且只出現一次;group 宣告的索引可以 adapter 的 entity id 欄結尾,(欄, post_id) 索引可直接依序讀取、不需 filesort(主查詢 850 ms → 2 ms)。 同值的順序改為依 ID 與排序同方向(原生為 ID DESC);集合、總數不變,翻頁穩定。(PR-118)
  • 沒有 value 的 meta 條件以 EXISTS 路由:clause 沒有 value 時(HivePress 的精選 listing 查詢)視同 EXISTS;只帶 meta_key、沒有 meta_value 也不以 meta_value 排序的查詢亦同; 沒有被 orderby 引用的排序形狀 group 直接移除。HivePress 每個搜尋頁都會跑的精選 listing 查詢(orderby rand)改走扁平表:2.26 s → 0.2 s。(PR-122)
  • status/date copy route 在 LIMIT 停下:只有扁平表一個 JOIN 時去掉 WordPress 的 GROUP BY posts.ID,posts.ID tie-break 改讀 flat.post_id(各自保留方向、重複合併);副本索引改為 idx_post_status_date_id (post_status, post_date, post_id),取代兩欄的 idx_post_status_date。 dev36 價格區間(130 萬中 23.5 萬筆符合)主查詢 1.7 s → 3 ms,頁面 2.7 s → 0.34 s。(PR-128)

Fixed

  • 遷移引擎(backfill、drift-check --fix)以 REPLACE 重建 post 列時保留 post_status / post_date 副本;原本重建後為 NULL,一筆 NULL 就讓整個 group 關閉 copy route。(PR-127)
  • 遷移引擎重建 post 列後遞增路由計數版本(每次呼叫/每批一次,dry run 不遞增), 修正後的 found rows 不再沿用舊的快取計數。(PR-129)

Dev

  • live 測試工具(mut.php、qbench.php)不再清空物件快取(Redis Object Cache 以伺服器端腳本 掃描,會卡住共用 Redis 5–20 s)。(PR-123)

升級後

  • 只需替換索引:對 tmdo_post_status_columns 列出的每個 group 表執行 ALTER TABLE <表> ADD INDEX idx_post_status_date_id (post_status, post_date, post_id), DROP INDEX idx_post_status_date (空表瞬間完成,dev36 130 萬列約 5 s)。未替換前 copy route 照常運作,只是維持 1.3.0 的速度。
  • 不要為了換索引執行 wp tmdo post-status-sync:它除了替換索引,還會替所有尚未有副本的 post group 加上 post_status / post_date 欄位並逐列回填(大表 ALTER + 全表 UPDATE;dev36 130 萬列約 75 s,上千萬列的站更久)。只有要讓更多 group 啟用 copy route 時才執行,並先在 離峰時段以 --dry-run 確認列數。
  • 效能數字(dev36 前台,routed):價格區間 0.34 s、價格排序 0.37 s、時薪排序 0.35 s、 評分排序 0.51 s、關鍵字 + 價格 0.59 s(原生 6–43 s)。

[1.3.0] — 2026-09-27 — 大表索引、路由計數與稽核修正(hardening 2.3)

性質:效能 + 稽核修正。無 schema 變更(宣告的複合索引以 ALTER ADD INDEX 就地加上)。 搭配 HivePress AddOn 0.4.0、WooCommerce AddOn 0.3.0。來源:dev36 規模壓測(90 萬 listing) 與系列稽核(2026-09-27)。

Changed

  • group 宣告的複合索引只在 WP-CLI 或 WP-Cron 加上:網頁請求(包括 auth_redirect 之前的 /wp-admin/ 與 admin-post.php)改排單次事件 tmdo_ensure_group_indexes;以 MySQL GET_LOCK 保證同一時間只有一個程序執行 ALTER;每個 ALTER 都成功才記錄已套用,失敗記 schema.index_failed 並保留在 doctor 的 [INDEX] 待辦;新表在 CREATE TABLE 時就帶上 索引。filter:tmdo_schema_ddl_allowed。dev36 90 萬列加兩個索引約 4.7 s。(PR-090a)
  • 路由搜尋的 found rows:帶 post__not_in 的查詢(HivePress 每頁送一組隨機排除清單) 改為「不含排除條件的 COUNT(快取)」減去「排除清單中符合條件的篇數(主鍵查詢)」,結果 精確,快取不再被打散。(PR-091)快取 key 另外去掉 wpdb 替 % 產生的 placeholder escape (每個請求不同,關鍵字搜尋原本永遠不命中,也就是 2.2 驗收「關鍵字 + 篩選 routed 較慢」的 原因)(PR-116);計數改跟隨自有的 tmdo_posts last_changed(文章儲存/刪除、經 Hook Bus 的扁平寫入)與 terms 的 last_changed,不再跟隨 posts 的 last_changed——任何 post meta 寫入 都會推動它,dev36 的 Blocksy 佈景在爬蟲瀏覽每篇 listing 時都寫一筆 meta,快取幾秒就失效 (PR-117)。dev36 關鍵字 + 價格 routed 9.8 s → 首次後約 5.5 s(原生 5.0–6.2 s,剩餘為 WordPress 核心的 LIKE 全表掃描)。
  • POST /wpdo/v1/listings/{id}/view 被限流擋下時,wpdo_rl_stats 改為 1/10 抽樣寫入 (每次 +10),匿名請求不再 1:1 放大成 option 寫入。(PR-094)
  • TMDO_Logger::info() 只在 WP_DEBUG_LOG 時寫 error_log(filter tmdo_log_info); notice() 一律寫入,模式切換(bridge_mode_changed)與快照刪除改用它,保留操作紀錄; debug()、warning()、error() 不變。(PR-095、PR-113)
  • 公開 listings REST 一律不輸出名稱像 email、phone、address、IP 的欄位,即使宣告 show_in_rest。(PR-096)
  • 既有扁平表的 schema 升級只在 WP-CLI 或 WP-Cron 執行:網頁請求遇到定義變更時記入 tmdo_schema_deferred 並排 tmdo_ensure_group_indexes;升級前該 group 的 key 走原生 (Hook Bus 不攔、router 不路由),升級後排 tmdo_drift_resync 補回期間跳過的扁平寫入。 新表照常立即建立;aeav_only 仍立即升級(扁平表是唯一副本);doctor 顯示 [SCHEMA]。(PR-107) 驗收審查後補強:既有表的升級與索引共用同一把 GET_LOCK(匿名 /wp-cron.php 也會通過 ddl_allowed()),取得鎖後重新比對 hash;延後的 group 要等 drift fix 完成才改回扁平表; 寫入扁平表的 entity 新建 group 表時同樣先走原生,由 drift fix 從原生填滿(新宣告的 HivePress 屬性不會把搜尋導到空表);定義還原時解除延後;cron 事件本身也會升級延後的表 (ALTERNATE_WP_CRON);dbDelta 之後每個欄位都存在才記錄定義(否則 schema.upgrade_failed 並重試);延後期間 direct_read() 回傳原生值。(PR-111、PR-114)
  • 讀寫分離(HyperDB、LudicrousDB)時,取索引鎖前先 send_reads_to_masters(), GET_LOCK 才會在 primary 上。(PR-108)
  • Cold 快取的「清除」改為遞增版本號(wpdo_cold_cache_version),不再呼叫 wp_cache_flush_group()(Redis Object Cache 以伺服器端腳本掃描整個 group,大 group 會卡住 Redis);舊 key 依 TTL 過期。(PR-110)

Fixed

  • 後台主頁與設定精靈第 4 步的表單改在 load-{page}(輸出前)處理,wp_safe_redirect() 可以正常生效;精靈快照失敗改以 snap_failed=1 導回。(PR-097)
  • uninstall.php 的全域變數加上 $wpdo_ 前綴(WordPress.org Plugin Check)。(PR-098)
  • 扁平表已被刪除的 group 不再留在 tmdo_post_status_columns:每次文章狀態變更都 UPDATE 不存在的表並印出 DB error。(PR-106)

Security(已驗證,無程式變更)

  • 快照目錄在 nginx 下的外洩風險(稽核 Medium):快照檔名含 64-bit 隨機值、遷移備份檔名含 12 字元隨機 job id,目錄有 index.php 擋列表;doctor 的 HTTP 探測與 nginx 後台提示已存在。 dev31 探測全為 403。不採用「目錄名加隨機後綴」:各站 nginx deny 規則比對 wpdo-backups/, 改名反而讓新快照脫離保護。(PR-093)

升級後

  • 部署後第一個 WP-CLI 指令或 WP-Cron 會為 HivePress AddOn 0.4.0 宣告的索引執行 ALTER (90 萬列約 5 s);DISABLE_WP_CRON 且沒有系統 cron 的站,執行一次 wp tmdo doctor 即可。
  • CLI 輸出不再夾帶 [WPDO][INFO];要看請定義 WP_DEBUG_LOG 或加上 tmdo_log_info filter。
  • 已知限制:cold 快取版本遞增的瞬間,在遞增前就開始的長請求寫入後只會清掉舊版 key,新版 key 最多保留舊值到 TTL(預設 1 小時);cold module 預設 idle。

[1.2.0] — 2026-09-27 — 漂移偵測、範圍收斂、CLI 與診斷(hardening 2.2)

性質:功能 + 資料正確性修正。無 schema 變更。延續 ADR-004 的搜尋索引模式;搭配 HivePress AddOn 0.3.0、WooCommerce AddOn 0.2.0。

Added

  • wp tmdo drift-check <entity> [--group] [--sample=<n>] [--format](TMDO_Drift_Check): 逐 key 比對扁平表與原生 meta 的值(第一筆原生 row → 安全 unserialize → Type_Caster::to_db),回報 miss / diff / extra。序列化 vs JSON、50 vs 50.000000 不算漂移;超過 255 字元存 NULL 的 text 不算 miss。結果寫入 wpdo_drift_status。(PR-070)
  • drift-check --fix:刪除已刪實體與非本 group post type 的扁平列,其餘漂移列經 TMDO_Entity_Migration_Engine::migrate_entity() 從原生重建,清除列快取後再檢查一次。 不寫原生;aeav_only(扁平才是真實資料)一律拒絕。(PR-071、PR-073b)
  • 每日抽樣:wpdo_daily_health_check 對 dual_write 以上、非 aeav_only 的 entity 每個 group 抽 500 筆,結果取代既有紀錄。(PR-073)
  • Hook Bus 關閉期間的寫入補回:entity 從 disabled 進入 dual_write / shadow_read,或 Hook Bus flag 由關轉開時,60 秒後以單次 WP-Cron tmdo_drift_resync 執行全量 drift-check --fix(不做 post_modified_gmt 增量:WooCommerce 直接 SQL 改庫存不會更新 post_modified)。停用 / 解除安裝時清除排程。(PR-074)
  • wp tmdo bridge-mode <entity> <mode>:經 TMDO_Mode_Manager::set(),受 Promotion Guard 約束;被拒時 exit 1、模式不變。(PR-069)
  • wp tmdo member-backfill --all:回填所有已註冊的 user group。(PR-068)
  • wp tmdo cleanup-hp-transients --options [--dry-run|--confirm] [--batch=<n>]: 刪除 HivePress AddOn 0.3.0 之前寫進 wp_options 的 _transient_wpdo_hp_pm_*(含 timeout), 分批刪除(預設 5000),AddOn 未啟用也可執行。dev36 有 2,314,750 列。(PR-080)

Changed

  • Post query router 避開漂移中的 group:上次 drift-check 有 miss / diff / extra 的 group 不路由,整個查詢維持原生(全有或全無);從未檢查的 group 照常路由;aeav_only 不受影響。(PR-072、PR-073b)
  • 純排序搜尋 flat-first:沒有路由篩選的 HivePress _sort 搜尋,在該 post type 每篇都有 扁平列時改由扁平表驅動(STRAIGHT_JOIN + INNER JOIN);Router 在 wp_insert_post 替新文章建空列,覆蓋率以 anti-join 檢查並快取 15 分鐘(隨 post 模式變更失效)。 flat-first 且扁平表是唯一 JOIN 時拿掉 WordPress 為 tax query 加的 GROUP BY posts.ID。 dev36 rating__desc:原生約 3.3 s、1.1.0 約 1.5–1.9 s、現在約 0.51–0.85 s。(PR-079)
  • Auto-promoter 的第三個條件改為 TMDO_Drift_Check::clean_since():2 天內每個 group 都 檢查過(抽樣或全量)且沒有漂移;沒有紀錄就不升級。(PR-075)
  • shadow_read diff 節流:每個 entity type + key 每小時最多記 20 列(疊加既有的 每物件 5 分鐘視窗)。(PR-075)
  • 回填 group 改讀 registry:admin「全部回填」、post-migrate-group、member-backfill、 member-audit 取所有已註冊 group(排除已還原的孤兒 group),不再是寫死清單;含 json 欄位的 group(attachment、nav_menu_item)逐列經 migration engine。(PR-067、PR-068)
  • 不再註冊 post/wc_product:WooCommerce 直接讀 product meta、以 SQL 寫庫存,扁平副本 必然漂移(ADR-004 決策 2)。既有的 wp_wpdo_post_wc_product 表保留,以 wp tmdo bridge-restore-native post --group=wc_product --retire 封存;解除安裝仍會刪除。(PR-077)
  • member-sso-sync、member-force-logout 標示為 deprecated(sso group 已不註冊)。(PR-069)

Fixed

  • Entity 回填替沒有原生 row 的 key 寫入 DDL 預設值(session_count / account_flags = 0), 每個 user 都被捏造出 0;現在寫 NULL,required NOT NULL 欄位保留預設值。(PR-066)
  • member-cutover 在轉換被拒時 fatal(把 WP_Error 當陣列)。(PR-068)
  • doctor 依 registry 檢查 entity 表(不再因從未註冊的 sso 表而每站失敗),並顯示 Promotion Guard 違規與 drift 狀態;term-comment-diagnose 依各 filter 的 is_enabled() 顯示狀態;bridge-status 只計有值的扁平列、eav_ids 限定 group 的 post type (dev36 wp_core 15306% → 100%)。(PR-076)
  • 2.1 稽核:公開 REST route 註明公開理由;admin 23 處三元 echo 依情境 esc_attr / esc_html;migration engine 與 health cron 的例外改記 TMDO_Logger。(PR-078)

Docs

  • ADR-004「設計(Release 2.2)」、CONTEXT.md Entity Bridge 的漂移段落。(PR-086)

升級後

  • HivePress 站:部署 AddOn 0.3.0 後執行 wp tmdo cleanup-hp-transients --options --confirm。
  • 曾註冊 wc_product 的站:wp tmdo bridge-restore-native post --group=wc_product --retire。
  • 已進入 dual_write 以上的站:wp tmdo drift-check <entity> 建立第一筆紀錄(未檢查的 group 照常路由)。

[1.1.1] — 2026-09-26 — Security:公開 REST 不再回傳私有欄位

性質:安全修正(Critical)。1.0.x 起即存在;1.1.0 zip 已撤回(dist/withdrawn/)。

Security

  • 公開的 GET /wpdo/v1/listings、/wpdo/v1/listings/{id} 會把所有已註冊的 hot / cold 欄位回傳給未登入者:TMDO_Schema_Registry::register() 的 show_in_rest 預設為 true, 而 addon 都沒有設定它。dev36 實測可取得 infocards 的 tmeetic_ig_access_token_enc、 tmeetic_ig_token_expiry、tmeetic_ig_user_id,以及 WooCommerce 的 _purchase_note。
    • show_in_rest 預設改為 false:要公開的欄位必須明確宣告。
    • 受保護的 meta key(底線開頭)與名稱像憑證的 key(token、secret、password、api key、 private key、credential、salt、nonce)即使宣告公開也一律不輸出 (TMDO_Schema_Registry::rest_exposable())。
    • Zone A 列表路徑在沒有任何公開欄位時不再回傳整列(fail-closed);篩選參數與 orderby 只接受公開欄位,避免藉由篩選 / 排序推測隱藏值。
  • 相容性:依賴預設公開的欄位,現在需在註冊時加上 'show_in_rest' => true,或使用 既有的 tmdo_rest_listing_visible_fields filter。
  • 測試:RestApiTest 新增 3 個;live suite rest(未登入 GET 不得出現私有 / 憑證類 key)。

[1.1.0] — 2026-09-26 — HivePress / WooCommerce 搜尋索引模式(hardening 2.1)

性質:資料正確性修正 + 查詢改寫補強。無 schema 變更。決策與設計見 docs/adr-004-search-index-mode-for-hp-wc.md;搭配 HivePress AddOn 0.2.0。

Added

  • Promotion Guard(TMDO_Promotion_Guard):HivePress 啟用時 post / user / term / comment、WooCommerce 啟用時 post / user 不得升到 aeav_only 或執行 cleanup (WP_Error( 'promotion_guarded' ),filter tmdo_promotion_guard_allow);已在 aeav_only 的站只顯示後台 notice。(PR-053)
  • wp tmdo repair-array-literals [--entity] [--confirm]:把扁平表中的 "Array" 從原生修回;原生也被污染的列出清單。(PR-055)
  • bridge-restore-native --overwrite / --only-missing:預設依模式(aeav_only 覆寫、 其他只補缺);報告新增 missing / differs / same / default_skipped。(PR-058)
  • live 測試層 tests/live/(在真實站台以 wp eval-file 執行):suites shadow_write、serialized、resync、router、zero_side_effect,以及 qbench / fe.sh 等量測工具。(PR-052)

Changed

  • Hook Bus 寫入順序:dual_write / shadow_read 由 WordPress 寫原生,扁平表在 added_ / updated_ / deleted_{type}_meta 從原生重新同步(含 delete_all、 update_metadata_by_mid 改 key)。(PR-057)
  • shadow_read 讀原生,並抽樣比對扁平值(tmdo_shadow_compare_sample_rate)。(PR-056)
  • Post query router:
    • 全有或全無:OR、多 post type、含已註冊 key 的巢狀 group、type 語意與欄位不符的 clause 讓整個查詢維持原生;LIKE 包 %esc_like%;pre_get_posts priority 1000。(PR-060)
    • 每個 group 各自 JOIN、不加 GROUP BY;NOT EXISTS 不走狀態 / 日期副本路由。(PR-061)
    • meta orderby:HivePress _sort(NOT EXISTS + EXISTS)形狀、具名 clause、 meta_key + meta_value_num。(PR-062)
    • routed 搜尋維持 WordPress 的 split query(ID-first)。(PR-062b)
    • 依扁平欄位排序且有篩選的搜尋改由扁平表驅動(STRAIGHT_JOIN,posts.ID tie-break 改讀等值的 post_id),不再以 temporary table 排序;filter tmdo_router_flat_first 可關閉。(PR-062c)
  • composer phpcs 改用 phpcs.xml(與 CI 相同)。

Fixed

  • shadow_read 下 update_*_meta() 回傳 false、原生沒有寫入(資料遺失)。(PR-056)
  • 陣列 / 物件存進 text 欄位變成 "Array";text 超過 255 字元改存 NULL 並記錄 text_overflow。(PR-055)
  • bridge-restore-native 覆蓋較新的原生值(WC 庫存)、替每個 user 捏造預設值列。(PR-058)
  • Cold / Hot 表不存在時,save_post、loop_start、刪除文章仍查詢它們(DB error)。(PR-054、PR-054b)
  • 刪除 post / user / term / comment 後,扁平表留下全 NULL 的孤兒列(PR-057 的 NULL 同步會重建已刪的列)。(PR-057b)
  • Router:Q05(wp_core 與另一 group 的 key 並用)Unknown column;OR / 多 post type / 無 type 數值比較 / 具名 orderby 的結果與原生不同。(PR-060〜062)

Docs

  • ADR-004、CONTEXT.md Entity Bridge(寫入順序、shadow_read、搜尋索引模式、Router 規則)、 docs/BENCHMARK-SCALE.md 前台與寫入鏈實測。(PR-064)

[1.0.2] — 2026-08-15 — NinjaFirewall 相容性:WAF 設定選項保護

性質:防禦性修正 + 文件。無 schema 變更,無破壞性變更。

Added

  • docs/WAF-COMPATIBILITY.md — NinjaFirewall (WP Edition) 4.9 相容性評估

    結論:兩者可共存,不需要開發 AddOn(NinjaFirewall 全 codebase 零個 apply_filters('nfw_*') / do_action('nfw_*'),官方相容手段全在部署層)。 文件涵蓋 WP WAF 與 Full WAF 的模式差異、symlink 多租戶部署的必要設定、 三條開發約束,以及實測風險矩陣。

  • TMDO_Options_Manager::PROTECTED_OPTIONS 常數與註冊守衛 (modules/options/class-tmdo-options-manager.php)

    register_settings_group() 會以 pre_update_option_{key} 回傳 $old_value, 讓選項不再落地 wp_options、改存專屬設定表。但 NinjaFirewall 的 Full WAF 走 auto_prepend_file,在 WordPress 載入前就以原生 mysqli 直查 wp_options 取 nfw_options / nfw_rules。一旦這些鍵被重導向,WAF 會讀不到設定而 靜默停止防護 —— 不報錯、不寫 log。

    現以 PROTECTED_OPTIONS(nfw_options / nfw_rules / nfw_checked)在註冊 階段擋下,並發出 _doing_it_wrong()。守衛置於方法開頭,全部鍵都被擋時提前 返回,不再建立空的設定表。

    註:autoload 最佳化不受影響 —— optimize_autoload() 只改 autoload 欄位、 不刪列,而該處的 SELECT * 不看 autoload。

  • tests/unit/OptionsManagerProtectedTest.php — 4 tests / 10 assertions

    鎖住「受保護鍵絕不會被掛上 pre_option_* / pre_update_option_* 攔截」這個 核心保證,並驗證全數受保護時不對資料庫發出查詢。

Changed

  • Migration Wizard 輪詢間隔 500ms → 2s(admin/assets/wpdo-migration-wizard.js)

    原本 2 req/s 打同一個 REST endpoint,容易觸發 WAF 的 rate-limit 與 bot 偵測。 改為 2 秒,與四個 stress-test 面板既有的輪詢節奏一致。


[1.0.1] — 2026-08-08 — Schema Registry 冪等性修正(cold zone 欄位重複累積)

性質:核心 bug 修復。無 schema 變更,無 API 變更,無破壞性變更。

Fixed

  • TMDO_Schema_Registry::register() 的 cold zone 分支會無限累積重複項 (includes/class-tmdo-schema-registry.php)

    三個 zone 索引的寫入方式並不一致:

    • hot → $this->hot_columns[$post_type][$column] = ...(以 column 為 key,天生冪等)
    • warm → $this->warm_fields[$meta_key] = ...(以 meta_key 為 key,天生冪等)
    • cold → $this->cold_fields[$post_type][] = ...(append,無去重)

    而 wpdo_register_fields 本來就會被觸發多次——核心自身在 plugins_loaded:4 觸發, 主檔另有 late-bind safety net 於 plugins_loaded:30 再次觸發,好讓在 :6 之後才 bootstrap 的 AddOn 也能趕上;再加上主外掛與其 AddOn 可能同時註冊同一批欄位。

    實測影響:某站台 cold_fields['hp_vendor'] 累積到 35 筆但只有 9 個相異值, 每個 key 重複 4 次。修正後為 9 筆、零重複。

    修法:cold 分支加入 in_array( ..., true ) 去重,使 register() 對同一 (post_type, meta_key) 成為冪等操作,與 hot / warm 的既有行為一致。

驗證

  • 核心單元測試 587 tests / 1156 assertions 全綠
  • wp wpdo doctor 於兩個站台皆 0 FAIL
  • Version bump 1.0.0 → 1.0.1(2 點一致:header / TMDO_VERSION)

[1.0.0] — 2026-07-31 — 取代 wp-data-optimizer v3.4.6

本版把 wp-data-optimizer v3.0.1–v3.4.6(92 個 commit)全數 backport 進來, wp-data-optimizer 自此退休:兩個站台皆已停用它,本機目錄改名為 wp-data-optimizer.retired,其 git 歷史保留在 gitea。

🔒 Security / 正確性

  • 7 處 Logger::error() 參數不足(array 傳給 string $hook)修正 —— 4 個 stress tester、auto-promoter、conflict-detector、hook-bus,皆為 TypeError fatal
  • Logger::trace_id() 補回:Audit_Logger::write_row() 一直呼叫它,但提煉時漏了; Audit_Logger::init() 從未註冊所以隱藏至今 —— 實機驗證才暴露
  • REST 4 個公開端點加上 show_in_rest 欄位 allowlist(CVSS 5.3)
  • 13 個破壞性 admin 動作由 GET 改 POST + nonce
  • DDL 型別白名單(19 型別)、CLI 表名守衛
  • 整欄清空改 wpdo_allow_mass_column_clear opt-in(原本 >500 列才擋)
  • audit 表補 group_name / action 欄並註冊 Audit_Logger::init()
  • known_login_ips 註冊(spoke-sso 實際寫入的欄位,先前落 wp_usermeta)

⚡ 並發

  • warm 表 UNIQUE KEY ui_post_meta(缺它 ON DUPLICATE KEY UPDATE 不生效)
  • Zone_Cold 改 JSON_MERGE_PATCH / JSON_REMOVE、Zone_Warm 原子 increment()、 hook-bus write_to_flat 改 upsert

🏗 架構

  • 回填 TMDO_Zone_Router + TMDO_Routing_Predicate(Sync_Bridge 400→269 行)
  • 回填 Migration Phase Strategy:interface + base + 11 個 phase 類別, orchestrator 1107→640 行,公開介面不變
  • 核心新增 TMDO_Standard_Post_Interceptor,HP AddOn 4 個 interceptor 573→344 行

🔌 相容性

  • hook 橋改雙向:核心一律 do_action('wpdo_*'),原本單向轉發使所有 tmdo_* 契約收不到事件
  • 57 個 AddOn 類別補 WPDO_* alias(核心 alias 表在 plugins_loaded:4 執行, 涵蓋不到 :6 才載入的 AddOn)
  • 38 個 CLI 子指令補 wp tmdo 命名空間
  • 選單 slug 統一回 wp-data-optimizer(B 內部 10+ 處連結本來就指向它)
  • HP adapter 介面改為 TMDO_ extends WPDO_,讓 instanceof WPDO_HivePress_Adapter 成立

🖥 Admin

  • Settings 七個分區各加「儲存此區塊」AJAX 存檔(wp_ajax_wpdo_save_settings_section
    • 7 個 save_section_*() + admin/assets/wpdo-settings.js),整頁送出仍可用
  • inline style= 由 348 處降到 9 處(剩下的全是 --wpdo-* CSS custom property 載體);wpdo-admin.css 543 → 912 行,補 149 個 class

🚧 邊界

  • 核心不再無守衛呼叫 AddOn 類別。TMDO_Listing_Stats(HivePress AddOn)先前 在 admin Dashboard、兩個公開 REST 端點、wp tmdo cleanup --archive-expired 被直呼,沒裝該 AddOn 的站台會 fatal;TMDO_HPCT_Import 在 admin 與 wp tmdo import-hpct 同樣缺守衛
  • TMDO_Zone_Warm::VIEW_KEY / VIEW_TTL 由核心持有(值與 AddOn 常數相同的 'wpdo_views',指向同一批列,無資料遷移)
  • 新增 CoreBoundaryTest:靜態掃描 126 個核心生產檔,強制每處 AddOn 類別 呼叫都在同一函式內有 class_exists() 守衛

✅ 品質

  • 128 個生產檔 declare(strict_types=1)
  • PHPCS 0 errors / PHPStan level 6(baseline 710)
  • 測試:核心 unit 587 / integration 416、hivepress-addon unit 154 / integration 57、woocommerce-addon unit 10 / integration 18
  • gitea CI:核心 6 個 job 全綠 + 11 個 AddOn workflow + branch protection
  • 打包 10 項終檢全 PASS(569 KB / 187 entries,schema drift 0:34 CREATE / 34 DROP)

⚠️ ABI / 行為變更

  • doctor_callback 由 3 參數改為 1 參數(表名)
  • wpdo_capture_before_value 預設 true → false;需要 value_before 的 消費者(audit log)要 add_filter( 'wpdo_capture_before_value', '__return_true' )
  • wpdo_allow_mass_column_clear 現為 opt-in,整欄清空預設被拒
  • SCHEMA_VERSION 2.0.0 → 2.1.0(audit 表 ALTER)

[0.1.0] — 2026-05-15 (initial release, Phase 0-5 完成)

Phase 5 hotfix(同日完成)

  • 修 TMDO_WooCommerce::doctor_check() callback signature mismatch(cli/class-tmdo-cli.php:264 改 call_user_func 傳 3 參數)
  • 加 includes/back-compat/trait-wpdo-anti-eav-aware-alias.php — trait + interface 反向相容(PHP class_alias 不支援 trait/interface)
    • WPDO_Anti_EAV_Aware trait wrapper 可讓 2meet-brandcards 等 sister plugin 直接 use
    • WPDO_Entity_Adapter_Interface extends TMDO_Entity_Adapter_Interface
  • 完整 uninstall.php(核心 244 行從 wp-data-optimizer 提煉,34 張表 DROP TABLE 對齊)
  • HP AddOn + infocards AddOn uninstall.php 補 schema drift check 對齊
  • 12 plugin ZIP 全通過 10 終檢

12 ZIP 交付(dist/)

Plugin Size MD5
2meet-data-optimizer 521 KB a10bb191e662cb5fb53a3d23165c71e5
-hivepress-addon 117 KB 24bd8c3211421fed86dc551658d7064a
-woocommerce-addon 34 KB fbd6f60368fc93310f2e90e58c95b53c
-spoke-addon 24 KB c1e92b755fb1d108a141086ccf1fe519
-hub-addon 22 KB 4e010c63ed8ad1dd24f06b8741e3a1b0
-infocards-addon 22 KB 7f095fdc4748d3ff4cd4e05f1bf3d417
-latepoint-addon 21 KB 60eeb4ea67cdc996cb60b6ad7126f2df
-bookings-addon 21 KB ac32b85d84e912d3b5a1cdfb56f9fa96
-mobile-bridge-addon 20 KB 59905719cfee4bd31967736517391a43
-collab-addon 20 KB 1ed0a2983a692a94b6c42f21b62af2c2
-quotation-addon 20 KB 5e693a4df3ce48de447cf6277deaece5
-playlist-addon 20 KB d52552782f383abbe8f278a5b85cafef

CLI smoke test(12 指令 全 PASS)

  • wp tmdo status / doctor / analyze / install / cleanup / health-snapshot / site-metrics / benchmark
  • wp wpdo status / doctor (alias 路徑)
  • wp tmdo spoke-sync --dry-run / wp tmdo spoke-force-logout --user-id=1 --dry-run

[0.1.0] — 2026-05-15 (initial release, Phase 0-4 完成)

Added — 核心反 EAV 引擎(從 wp-data-optimizer v2.16.0 提煉)

  • 4 Zone 分流(Hot / Warm / Cold / Archive)+ Sync Bridge 雙寫
  • 7 態 FSM 模組生命週期(idle → dual_write → backfill → verify → cutover → cleanup → complete)
  • 4 entity 通用引擎(post / user / term / comment)+ Hook Bus + Mode Manager
  • Query Router (pre_get_posts 改寫) + Query Compiler
  • Migration Engine + Orchestrator + Backfill + Stress Tester + Shadow Verifier
  • Snapshot / Safety / Diagnostic / Notifications (Email / Slack / Discord / Telegram)
  • Crypto AES-256-GCM AEAD + Safe Unserialize
  • WP-CLI 18+ subcommand
  • Admin Dashboard + KPI Hero + Tab Groups + Setup Wizard + Help Tabs

Added — 對外向後相容

  • TMDO_* 類別 + class_alias 80+ 個 → 對外保留 WPDO_* 類別名
  • 8 個常數 alias(WPDO_VERSION / WPDO_PLUGIN_DIR / WPDO_TABLE_PREFIX 等)
  • 9 個 hook dual-fire 橋接(tmdo_register_fields ↔ wpdo_register_fields 等)
  • WP-CLI 雙命名空間:wp tmdo (主) + wp wpdo (alias)

Removed — 整合層搬出

  • HivePress + 12 擴充整合(13 adapter + 7 interceptor + 5 query interceptor + bootstrap + admin + CLI)→ 搬到 2meet-data-optimizer-hivepress-addon
  • WooCommerce 整合 → 2meet-data-optimizer-woocommerce-addon
  • LatePoint 整合 → 2meet-data-optimizer-latepoint-addon
  • 2meet-infocards / bookings / quotation / mobile-bridge / collab / playlist 整合 → 各自 6 個 AddOn
  • Hub-core SSO group 與自訂表註冊 → 2meet-data-optimizer-hub-addon
  • Spoke-sso SSO group 與 CLI → 2meet-data-optimizer-spoke-addon
  • TMDO_Core::register_hivepress_defaults() (193 行) → HP AddOn

Migration from wp-data-optimizer v2.16.0

  • wp_wpdo_* 表結構完全相容(不 rename,新外掛沿用既有 schema)
  • 舊外掛先停用即可改用新外掛接管
  • 兩外掛互斥:本外掛偵測舊外掛仍 active 時會發 admin notice(plan §8.1)

Limitations / Known issues (Phase 5 follow-up)

  • tests/ 目錄空殼,PHPUnit unit + integration tests 需後續搬遷
  • phpcs.xml.dist 缺;composer install --dev 後可手動跑
  • WP 6.5+ Requires Plugins: 強制 Plugin Name 比對;hub-core / spoke-sso 仍寫死 wp-data-optimizer,需更新或本 plugin 加偽 Plugin Name: WP Data Optimizer alias header(Phase 5)
  • TMDO_WooCommerce::doctor_check() signature 與 Custom_Table_Registry callback 不對齊(warning level)
  • Trait Trait_TMDO_Anti_EAV_Aware 暫無 alias wrapper(class_alias 不適用 trait)

Phase 0-4 統計

  • PHP files: 203(核心 111 + Hub 5 + Spoke 7 + HP 47 + WC 8 + LP 5 + 6 family 各 5)
  • PHP lint: 全 PASS
  • Plugin active: 12/12
  • Custom tables registered: 14(Hub 12 + Spoke 2,未含 6 family no-op)
  • SSO group fields: 7
  • Schema fields: 23 (Hot 20 + Cold 3,HP 未安裝 → HP fields no-op)

來源外掛累計重要 release(供考古)

詳見 /var/www/Studio/wp-local-dev30/wp-content/plugins/wp-data-optimizer/CHANGELOG.md 與 CLAUDE.md:v2.0.0 → v2.16.0 共 47 次發版的累計改動。

亮點:

  • v2.6.4 H-4 加密 webhook 密鑰
  • v2.13.3 audit 7/7 finding 全清零
  • v2.15.0 AES-256-CBC → AES-256-GCM AEAD
  • v2.16.0 UI/UX KPI Hero + Grouped Tab Navigation

[Unreleased]

Phase 5 候補

  • Tests 套件搬遷
  • PHPCS 套件 + 0/0 達成
  • 打包腳本 10 終檢驗收
  • WPDO_ → TMDO_ migration CLI