- show_in_rest defaulted to true, so GET /wpdo/v1/listings and /listings/{id} (public by design) returned every registered hot / cold field to anonymous visitors — e.g. infocards tmeetic_ig_access_token_enc / _token_expiry / _user_id and WooCommerce _purchase_note (confirmed on dev36)
- Fields are private unless registered with show_in_rest => true; protected (underscore) and credential-like keys (token, secret, password, api key, private key, credential, salt, nonce) are never exposed
- Zone A list path no longer returns whole rows when nothing is public, and filters / orderby accept public columns only
- Version 1.1.1; CHANGELOG, readme
- Verify: unit 690 (+3), integration 525 (+3 private-column tests, RED on 1.1.0), live suite rest 2/2 (RED on 1.1.0), all live 49/49, phpcs 0, stan OK
18 KiB
Changelog
All notable changes to this plugin will be documented here.
Format follows Keep a Changelog. Versioning follows Semantic Versioning.
[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_fieldsfilter。 - 測試:
RestApiTest新增 3 個;live suiterest(未登入 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' ),filtertmdo_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執行):suitesshadow_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_postspriority 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 排序;filtertmdo_router_flat_first可關閉。(PR-062c)
- 全有或全無:OR、多 post type、含已註冊 key 的巢狀 group、type 語意與欄位不符的
clause 讓整個查詢維持原生;LIKE 包
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.mdEntity 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_clearopt-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-buswrite_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),整頁送出仍可用
- 7 個
- inline
style=由 348 處降到 9 處(剩下的全是--wpdo-*CSS custom property 載體);wpdo-admin.css543 → 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_VERSION2.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_Awaretrait wrapper 可讓 2meet-brandcards 等 sister plugin 直接useWPDO_Entity_Adapter_InterfaceextendsTMDO_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 / benchmarkwp 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_alias80+ 個 → 對外保留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 Optimizeralias 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