9fa84845be
評估 NinjaFirewall (WP Edition) 4.9 的相容性,結論是兩者可共存且
不需要開發 AddOn(該外掛全 codebase 零個 apply_filters('nfw_*') /
do_action('nfw_*'),官方相容手段全在部署層)。但核心有一個隱患必須補。
TMDO_Options_Manager::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。
nfw_rules 約 77KB 且 autoload=auto,正是 autoload 瘦身最誘人的目標,
因此這條路徑並非理論風險。
Added
- PROTECTED_OPTIONS 常數與註冊守衛(nfw_options / nfw_rules / nfw_checked),
命中時發出 _doing_it_wrong()。守衛置於方法開頭,全部鍵都被擋時提前返回,
不再建立空的設定表。
- tests/unit/OptionsManagerProtectedTest.php(4 tests / 10 assertions),
鎖住「受保護鍵絕不會被掛上 pre_option_* / pre_update_option_* 攔截」。
- docs/WAF-COMPATIBILITY.md:模式差異、symlink 多租戶部署、WP SaaS 開站
流程與驗證清單、三條開發約束、實測風險矩陣。
Changed
- Migration Wizard 輪詢 500ms → 2s,與四個 stress-test 面板一致。
原本 2 req/s 打同一 REST endpoint,易觸發 WAF rate-limit 與 bot 偵測。
autoload 最佳化不受影響:optimize_autoload() 只改 autoload 欄位、不刪列,
而 Full WAF 的 SELECT * 不看 autoload。
驗證:591 tests / 1166 assertions 通過,PHPCS 零違規,版本一致性 1.0.2。
dev30 於 Full WAF 與 WP WAF 兩種模式下實測,firewall log 中 TMDO 相關
攔截 0 筆。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdjXAU473eekjPBB8vPVRS
315 lines
22 KiB
Markdown
315 lines
22 KiB
Markdown
# PLAN — 2meet Data Optimizer (核心)
|
||
|
||
> 完整四階段拆分計畫見 `~/.claude/plans/wp-data-optimizer-2meet-data-optimizer-zesty-liskov.md`
|
||
|
||
---
|
||
|
||
## Current state
|
||
|
||
- **Version**: 1.0.2 (2026-08-15;scaffold + 4 phase 完成於 2026-05-15)
|
||
- **Phase**: 全 4 phase 已完成
|
||
- **Source**: 從 `wp-data-optimizer v2.16.0` 提煉
|
||
|
||
---
|
||
|
||
## Phase 0 ✅ — 骨架建立 (2026-05-15)
|
||
|
||
- [x] 12 plugin 目錄結構(核心 + 11 AddOn)
|
||
- [x] 主檔 `2meet-data-optimizer.php` + Plugin Header
|
||
- [x] `includes/class-tmdo-bootstrap.php` stub
|
||
- [x] `uninstall.php` stub
|
||
- [x] `composer.json`
|
||
- [x] `.gitignore`
|
||
- [x] 7 件 MD(README / PLAN / CHANGELOG / DEPLOY / SECURITY / DESIGN / CLAUDE)
|
||
|
||
---
|
||
|
||
## Phase 1 ✅ — Core v0.1.0 (2026-05-15)
|
||
|
||
- [x] rsync 從 wp-data-optimizer 複製 ~110 個核心檔案,排除 HP/WC/LP/2meet 整合
|
||
- [x] 檔名 `class-wpdo-*` → `class-tmdo-*`(含 abstract / interface / trait)
|
||
- [x] sed 內容批次 rename:`WPDO_` → `TMDO_`(class + 常數,hook/table/option 前綴 `wpdo_` 保留共用)
|
||
- [x] 常數 `TMDO_PLUGIN_DIR` → `TMDO_PATH` 等對齊
|
||
- [x] Text-domain `'wp-data-optimizer'` → `'2meet-data-optimizer'`
|
||
- [x] 主檔重寫成完整 loader(require chain ~150 行)
|
||
- [x] 加 `tmdo_run()` boot 函式 + `plugins_loaded:4` action
|
||
- [x] 加 `late-bind safety net` (priority 30) 確保 AddOn listener 收到 hook
|
||
- [x] 加 `includes/class-tmdo-back-compat.php` — 80+ class_alias + 8 常數 alias + 9 hook dual-fire
|
||
- [x] 移除 `register_hivepress_defaults()` (193 行) → 搬到 HP AddOn
|
||
- [x] 把 core 內所有 HP/WC/LP 整合 class 引用加 `class_exists` 守門
|
||
- [x] PHP lint:111 files PASS
|
||
- [x] Live test:`wp tmdo doctor` 全 PASS、`wp wpdo` (alias) 通
|
||
|
||
**Phase 1 DoD:✅ 全達標**(除舊 unit/integration test 套件需後續 phase 1 follow-up)
|
||
|
||
---
|
||
|
||
## Phase 2 ✅ — Hub + Spoke AddOn v0.1.0 (2026-05-15)
|
||
|
||
### Hub AddOn (`2meet-data-optimizer-hub-addon`)
|
||
- [x] `TMDO_Hub_Bootstrap` 主啟動類別
|
||
- [x] `TMDO_Hub_Vendor_Field` — 註冊 `_2meet_global_vendor_id` (hp_vendor / cold zone / indexed)
|
||
- [x] `TMDO_Hub_Custom_Tables` — 註冊 12 張 `2mhc_*` 自訂表(fallback 列表 + delegate to hub-core helper if available)
|
||
- [x] dual-emit 支援 (`tmdo_*` + `wpdo_*` 兩個 hook 路徑)
|
||
- [x] PHP lint PASS
|
||
|
||
### Spoke AddOn (`2meet-data-optimizer-spoke-addon`)
|
||
- [x] `TMDO_Spoke_Bootstrap` 主啟動類別
|
||
- [x] `TMDO_Spoke_SSO_Group` — 註冊 user entity `sso` group(7 欄位 → wp_wpdo_user_sso)
|
||
- [x] `TMDO_Spoke_Custom_Tables` — 註冊 `2mso_sso_config` / `2mso_user_mapping`
|
||
- [x] `TMDO_Spoke_CLI::sync()` — 從舊 `_tmso_*` usermeta 遷移
|
||
- [x] `TMDO_Spoke_CLI::force_logout()` — 設置 token_expires_at = 過去
|
||
- [x] CLI 雙命名空間:`wp tmdo spoke-*` + `wp wpdo spoke-*`
|
||
- [x] PHP lint PASS
|
||
|
||
**Phase 2 Live test 結果:**
|
||
- ✅ Hub: 12 hub tables 全部 OK
|
||
- ✅ Spoke: SSO group 7 fields registered, 2 tables registered
|
||
- ✅ CLI: `wp tmdo spoke-sync --dry-run` 跑通,`spoke-force-logout --user-id=1 --dry-run` 跑通
|
||
|
||
---
|
||
|
||
## Phase 3 ✅ — HP + WC + LP AddOn v0.1.0 (2026-05-15)
|
||
|
||
### HivePress AddOn (`2meet-data-optimizer-hivepress-addon`, ~7,400 行)
|
||
- [x] 複製 13 adapter(core / reviews / bookings / messages / memberships / requests / favorites / statistics / tags / seo / social-links / blocks / marketplace)
|
||
- [x] 複製 7 HPCT interceptor(reviews / messages / favorites / memberships / statistics / requests / listing-meta)
|
||
- [x] 複製 5 query interceptor(reviews / messages / memberships / requests / listing-meta)
|
||
- [x] 複製 補助組件(bootstrap framework / detector / conflict-guard / comment-router / cron-optimizer / attribute-bridge / suitability-scorer / benchmark / rest)
|
||
- [x] 複製 admin tab + CLI namespace
|
||
- [x] 複製 HP transient filter / HPCT import / Listing Stats / term-comment-fields
|
||
- [x] `TMDO_HP_Bootstrap` outer bootstrap(避免與 inner family bootstrap class 衝突)
|
||
- [x] PHP lint:46 files PASS
|
||
|
||
### WooCommerce AddOn (`2meet-data-optimizer-woocommerce-addon`, ~1,316 行)
|
||
- [x] 複製 4 個 WC 整合檔
|
||
- [x] `TMDO_Woocommerce_Bootstrap` 載入 + register
|
||
- [x] PHP lint PASS
|
||
|
||
### LatePoint AddOn (`2meet-data-optimizer-latepoint-addon`, ~151 行)
|
||
- [x] 複製 LatePoint interceptor
|
||
- [x] `TMDO_Latepoint_Bootstrap` 載入 + register_hooks
|
||
- [x] PHP lint PASS
|
||
|
||
**Phase 3 Live test 結果:**
|
||
- ✅ Plugin 全 active
|
||
- ✅ Hot zone 20 fields registered(mostly WC product/order)
|
||
- ✅ Cold zone 3 fields
|
||
- ✅ Total 23 fields
|
||
- ✅ HP no-op when HivePress not installed(detector check)
|
||
|
||
---
|
||
|
||
## Phase 4 ✅ — 6 個 2meet 家族 AddOn v0.1.0 (2026-05-15)
|
||
|
||
| AddOn | Source | Status |
|
||
|---|---|---|
|
||
| infocards | class-tmdo-infocards.php | ✅ |
|
||
| bookings | class-tmdo-bookings.php | ✅ |
|
||
| quotation | class-tmdo-quotation.php | ✅ |
|
||
| mobile-bridge | class-tmdo-mobile-bridge.php | ✅ |
|
||
| collab | class-tmdo-collab.php | ✅ |
|
||
| playlist | class-tmdo-playlist.php | ✅ |
|
||
|
||
每個 AddOn 都有:
|
||
- `TMDO_{Module}_Bootstrap` 啟動類別
|
||
- 核心整合 class(從 wp-data-optimizer 搬入)
|
||
- partner detection (early return if target plugin not installed)
|
||
- `register()` 掛 `wpdo_register_fields` / `wpdo_register_custom_tables`
|
||
|
||
**Phase 4 Live test 結果:**
|
||
- ✅ 6 AddOn 全 active
|
||
- ✅ Target plugin 未安裝時正確 no-op (designed behavior)
|
||
- ✅ 0 fatal、0 lint error
|
||
|
||
---
|
||
|
||
## 整體驗收(2026-05-15 末)
|
||
|
||
| 指標 | 結果 |
|
||
|---|---|
|
||
| 12 plugin active | ✅ |
|
||
| PHP lint 全 files | ✅ **203 files PASS** |
|
||
| `wp tmdo` CLI | ✅ 18 個 subcommand 全 listed |
|
||
| `wp wpdo` alias CLI | ✅ 通 |
|
||
| `wp tmdo doctor` | ✅ system tables 全綠 + 14 partner tables 註冊 |
|
||
| `wp tmdo status` | ✅ 23 fields registered |
|
||
| SSO group 7 欄位 | ✅ 全 7 欄位(含 hub_global_user_id searchable) |
|
||
| Hub-addon 12 表 | ✅ 全 12 表存在 + doctor PASS |
|
||
| Spoke-addon 2 表 | ✅ 註冊 OK(DB 表 missing 是 spoke-sso 從未啟用之故) |
|
||
| WC 20 表 | ✅ 註冊 OK(DB 表 missing 是 WC 未安裝) |
|
||
| 6 family AddOn no-op | ✅ target 未安裝時正確 early return |
|
||
|
||
---
|
||
|
||
## Lessons learned
|
||
|
||
### Phase 1
|
||
- rsync 的 `--exclude` 對 `tests/` 與 `tools/` top-level 不生效(因為 source 路徑沒這個前綴),需後續 rm -rf 清理
|
||
- sed 全檔大寫 `WPDO_` → `TMDO_` 不影響 hook/table/option 小寫前綴 `wpdo_`,確保資料相容
|
||
- 主檔的 require chain 無法以 sed 自動產出(需手動寫,因為 require 路徑與檔名變動)
|
||
- TMDO_Core::run() 內有 8 處對「整合層 class」的硬引用,需逐個加 `class_exists` 守門
|
||
- `class_alias()` 對 trait 與 interface 不適用(需 wrapping wrapper,留 Phase 1 follow-up)
|
||
- WP 6.5+ `Requires Plugins:` 強制檢查 Plugin Name 而非 slug;hub-core / spoke-sso 仍寫死 `Requires Plugins: wp-data-optimizer` 故無法直接啟用 → Phase 5 計畫請求 hub-core / spoke-sso 端更新或加 `or 2meet-data-optimizer` 邏輯
|
||
|
||
### Phase 2
|
||
- inner Hub class `TMDO_Hub_Bootstrap` 需與 outer AddOn bootstrap 區分名稱(衝突避免)
|
||
- `TMDO_Entity_Registry::register_group()` 是公開 API;其他 read methods (`get_group_fields` not `get_fields`) 命名不直觀
|
||
|
||
### Phase 3
|
||
- HP family 內部已有 `TMDO_HivePress_Bootstrap` class(v3.0.0 family bootstrap),故 outer AddOn bootstrap 改名 `TMDO_HP_Bootstrap` 避免衝突
|
||
|
||
### Phase 4
|
||
- 所有 family integration class 皆有 partner detection (e.g. `class_exists('TMEETIC_Plugin')`) → AddOn 無需自己重複偵測
|
||
|
||
---
|
||
|
||
## Phase 5 follow-up 執行狀態(2026-05-15)
|
||
|
||
| # | 項目 | 狀態 |
|
||
|---|---|---|
|
||
| 1 | **Tests 套件搬遷**:`tests/unit/` 與 `tests/integration/` 重命名 + path 修正 | ✅ **完成** |
|
||
| 2 | PHPCS 套件:補 phpcs.xml.dist + WordPress coding standards | ⬜ 待執行 |
|
||
| 3 | **PHPUnit bootstrap**:完整重建 unit/integration test 環境 | ✅ **完成** |
|
||
| 4 | DB version migration:`TMDO_DB_VERSION` 對齊 `SCHEMA_VERSION = '2.0.0'` | ✅ **完成** |
|
||
| 5 | `Requires Plugins: 2meet-data-optimizer` — 12 個 AddOn + brandcards header 全部更新 | ✅ **完成** |
|
||
| 6 | Trait/interface alias wrappers:`Trait_TMDO_Anti_EAV_Aware` 等 | ✅ **完成** |
|
||
| 7 | Doctor callback signature 修正 | ✅ **完成** |
|
||
| 8 | 打包驗收:`scripts/package-plugin.sh` 12 次 10 終檢 PASS | ⬜ 待執行 |
|
||
|
||
### #1 + #3 完成摘要(2026-05-15)
|
||
|
||
**Unit tests:373/373 GREEN(771 assertions)**
|
||
- `phpunit.xml` 已建立,bootstrap `tests/bootstrap.php` 完整重建
|
||
- 32 個 unit test 檔從 `wp-data-optimizer` 搬遷,全部 `class-wpdo-` → `class-tmdo-` 修正
|
||
- bootstrap 新增:minimal filter registry(`$GLOBALS['_wp_filter_callbacks']`)、全域 FSM bypass filter、`TMDO_Listing_Stats` stub、`esc_sql` 用 `addslashes()`
|
||
- 跳過 HP/WC/LP 專屬 test(HivePress/*、WooCommerceIntegrationTest、ListingStatsTest)
|
||
|
||
**Integration tests:398/398 GREEN(1139 assertions)**
|
||
- `phpunit-integration.xml` 已建立,bootstrap `tests/integration/bootstrap.php` 完整重建
|
||
- 35 個 integration test 檔搬遷(跳過 HivepressIntegrationTest × 3、WCTermCountFilterTest、ListingMetaInterceptorTest、TermCommentBackfillTest)
|
||
- bootstrap 新增:`wp_upload_dir()`、`wp_mkdir_p()`、`TMDO_Listing_Stats` stub
|
||
- 移除 `WarmArchiveIntegrationTest` 的 listing_stats 2 個方法(需真實 HP AddOn)
|
||
- 移除 `RestApiIntegrationTest` 的 post_view increment + rate-limit 2 個方法(同上)
|
||
|
||
---
|
||
|
||
## Backport wp-data-optimizer v3.0.1–v3.4.6(2026-07-31 起)
|
||
|
||
完整計畫:`~/.claude/plans/a-data-www-studio-wp-local-dev-wp-conten-imperative-wind.md`
|
||
|
||
背景:B 分叉自 A **v3.0.0**(2026-05-15 11:40),A 其後又走了 92 個 commit 到 v3.4.6,無一回流。目標是全量對齊後讓 `wp-data-optimizer` 退休。
|
||
|
||
| 階段 | 內容 | 狀態 |
|
||
|---|---|---|
|
||
| 0 | 回歸網:composer dev 依賴、test bootstrap FSM guard、`failOnWarning=true`、12 個 repo `git init` | ✅ 2026-07-31(unit 379 / integration 398 全綠) |
|
||
| 1 | 安全與正確性 A1–A14(13 個 admin GET→POST、REST allowlist、DDL 白名單、Logger 3-arity fatal…) | ✅ 2026-07-31 |
|
||
| 2 | DDL 與並發 B1–B4(warm UNIQUE KEY → Zone_Cold save_patch → Zone_Warm increment) | ✅ 2026-07-31 |
|
||
| 3 | 架構回填(Zone_Router + Routing_Predicate;13 個 Migration Phase 類別) | ✅ 2026-07-31 |
|
||
| 4 | 相容層 E1–E7(雙向 hook 橋、57 個 AddOn alias、38 個 CLI 子指令、選單 slug) | ✅ 2026-07-31 |
|
||
| 5 | AddOn 同步 F1–F5(Standard_Post_Interceptor、HP detector v3.4.5、WC) | ✅ 2026-07-31 |
|
||
| 6 | 功能與診斷 PR-G(crypto 健檢、doctor_callback ABI、15 個 usermeta 欄位…) | ✅ 2026-07-31(**Settings 分區 AJAX 未做**,見下) |
|
||
| 7 | 全域清理與 CI(strict_types、phpcs.xml、phpstan + baseline、gitea workflows) | 🔶 strict_types / PHPCS / PHPStan / 3 個 workflow / ci-package.sh ✅;**測試補齊(剩 36 檔,多為 HP/WC)、文件移植未做** |
|
||
| 退休 | 兩站台切到 B、移除 A、B 升 1.0.0 | 🔶 dev30 已切換並驗證通過;dev 未切、A 未移除、版本未升 |
|
||
|
||
### dev30 實機驗證結果(2026-07-31)
|
||
|
||
環境:`2meet-brandcards` 1.8.3 / `2meet-hub-core` 1.16.0 / `2meet-spoke-sso` 1.15.0 同時啟用;HivePress / WooCommerce 未安裝,故只啟用核心 + hub-addon + spoke-addon。
|
||
|
||
| 項目 | A v3.0.0(切換前) | B v0.1.0(切換後) |
|
||
|---|---|---|
|
||
| `doctor` | 33 個 `[OK]` + 15 個 WC 表 MISSING 警告 | **49 個 `[OK]`、All checks passed** |
|
||
| 姊妹外掛自訂表 | 36 張 OK | 36 張 OK(含 brandcards 的 `doctor_callback` 回傳訊息) |
|
||
| 新健檢 | 無 | `Backup dir blocked (HTTP 404)`、`Crypto key derivable` 皆 OK |
|
||
|
||
逐項確認:
|
||
- **alias**:15 個核心類別 + trait + interface + `wpdo_run()` + `WPDO_VERSION` 全部可用
|
||
- **CLI**:`wp tmdo bridge-status / crypto-status / mode-audit` 與 `wp wpdo *` 兩條路徑都通(38 個子指令)
|
||
- **anti-EAV 寫入**:`WPDO_API::set_entity()` → `wp_wpdo_user_sso.known_login_ips` 寫入成功(A14 生效)
|
||
- **audit**:`wp_wpdo_audit` 的 `group_name` / `action` / `trace_id`(UUIDv4)皆有值(A11/A12 生效)
|
||
- **REST allowlist**:註冊 `show_in_rest => false` 的欄位不出現在 `get_rest_visible_hot_columns()`(A5/A6 生效)
|
||
- **doctor_callback ABI 變更**:brandcards 既有 callback 在 1 參數簽章下仍正常
|
||
- 前台 HTTP 200;`_load_textdomain_just_in_time` notice 僅出現在 `wp plugin activate` 當下(啟用鉤子早於 `init`),一般請求與前台皆無
|
||
|
||
**實機才抓到的 fatal**:`TMDO_Logger::trace_id()` 在 B 提煉時遺漏,而 `Audit_Logger::write_row()` 會呼叫它。A12 把 Audit_Logger 掛上後,任何一次受管 meta 寫入都會 fatal。PHPUnit 沒有覆蓋 audit 寫入路徑 → 這類「跨模組才會踩到」的缺口只有實機能發現。
|
||
|
||
### 尚未完成(follow-up)
|
||
|
||
1. **Settings 分區 AJAX 儲存**(A v3.0.2):`ajax_save_settings_section()` + 7 個 `save_section_*()` + `admin/assets/wpdo-settings.js`。B 目前是一次存全部的 inline `update_option` 巨塊,功能可用,屬 UX 改善。其中 `save_section_hp_transient` / `save_section_wc_term_count` 對應 AddOn 已移除的設定,移植時要拆掉或加 `class_exists` 守衛。
|
||
2. ~~CI workflow~~ ✅ 三個檔已建,但 **B 沒有 git remote**,要推上 gitea 才會實際執行;推上去後還需設 repo secret `TMDO_TEST_DB_PASS`、var `RELEASE_GITEA_URL`、secret `RELEASE_TOKEN`,並開 main branch protection。11 個 AddOn 的 CI 仍未建。
|
||
3. ~~`scripts/ci-package.sh`~~ ✅(委派共用 `package-plugin.sh`,實測 10 項終檢全 PASS)
|
||
4. ~~補測試檔~~ ✅ 核心補 `HookBusIntegrationTest`(14)+ 新寫 `AuditLoggerIntegrationTest`(4);HP AddOn 建 phpunit 基建並移植 25 檔(145 tests);WC AddOn 建基建並移植 1 檔(10 tests)。11 個 AddOn 皆有 CI workflow(HP/WC 跑 lint+unit,其餘 9 個薄註冊層只跑 lint)。
|
||
- 仍缺:`TermCommentBackfillTest`(require HP AddOn 的 term-comment-fields,可移入 HP AddOn 套件)、`ListingStatsTest`(同上)、HP/WC 的 integration 測試(`HivepressIntegrationTest` ×3、`WCTermCountFilterTest`、`ListingMetaInterceptorTest`)——這些需要 AddOn 端的 integration bootstrap。
|
||
|
||
### 全家族測試現況(2026-07-31)
|
||
|
||
| 套件 | 結果 |
|
||
|---|---|
|
||
| 核心 unit | 451 tests / 889 assertions |
|
||
| 核心 integration | 416 tests / 1165 assertions |
|
||
| hivepress-addon unit | 145 tests / 357 assertions |
|
||
| woocommerce-addon unit | 10 tests / 33 assertions |
|
||
|
||
AddOn 測試 bootstrap 直接 `require` 核心 plugin 的 `tests/bootstrap.php`(避免複製 ~700 行 WP stub),因此 **AddOn 的 CI job 必須同時 checkout 核心 plugin**,workflow 已如此設定。
|
||
5. **文件移植**:`readme.txt`、`CONTEXT.md`、`docs/`(ENTITY_ADAPTER_COOKBOOK、INTEGRATION_PATTERN_DECISION、2 篇 ADR)。
|
||
6. **實機驗證與退休切換**:兩站台目前仍跑 A(dev = v3.4.6、dev30 = v3.0.0),B 家族 12 個外掛全 inactive,尚未做過 `wp tmdo doctor` 實機驗證。
|
||
|
||
### ABI / 行為變更(升級須知)
|
||
|
||
- `doctor_callback` 由 3 參數改為 1 參數(表名)——AddOn 若註冊過該 callback 需同步。
|
||
- `wpdo_capture_before_value` 預設 `true` → `false`,每次受管寫入省一次 DB read;需要 `value_before` 的消費者(audit log)要 `add_filter( 'wpdo_capture_before_value', '__return_true' )`。
|
||
- `wpdo_allow_mass_column_clear` 現為 opt-in:`delete_all=true` 的整欄清空預設被拒。
|
||
- 選單 slug 由 `2meet-data-optimizer` 改回 `wp-data-optimizer`。
|
||
|
||
### Lessons learned(backport)
|
||
|
||
- **階段 0 的初始 git 快照已包含階段 0 的修復**(composer.json / 兩個 bootstrap / phpunit.xml / HealthCronTest),不是純粹的 pre-change 現況。要看純現況請比對 A 或 `dist/` 內的 v0.1.0 ZIP。
|
||
- `composer update` 後 autoload 出現 `WPDO_Entity_Adapter_Interface` 三重宣告警告(`includes/adapters/interface-entity-adapter.php`、`back-compat/interface-wpdo-entity-adapter-alias.php`、`back-compat/trait-wpdo-anti-eav-aware-alias.php`)→ 階段 4 E2 要刪掉重複的那一份。
|
||
- `TMDO_FSM_GUARD_DISABLED` 兩個 test bootstrap 原本都沒 define,但 `class-tmdo-feature-flags.php:128` 會檢查它 → 強制 FSM 轉換的測試會被 guard 擋掉(A 在 v3.4.6 踩過同一個坑)。
|
||
- integration 測試需要 `TMDO_TEST_DB_PASS`(或 `WPDO_TEST_DB_PASS`)環境變數,DB 為本機 `wp_wpdo_test`。
|
||
- **跑完 `scripts/ci-package.sh` 或 `package-plugin.sh` 後要重跑 `composer install`**:打包流程內含 `composer install --no-dev`,會把 `vendor/bin/{phpunit,phpcs,phpstan}` 移掉,之後任何測試指令都會 "No such file or directory"。
|
||
- **A 不是照抄對象,有兩處自身缺陷不可照搬**:
|
||
1. `A/includes/engine/class-wpdo-hook-bus.php:511,522` 傳 3 個 string 給 `Logger::warning( string $event, array $context )` → A 有 `strict_types`,mass-clear 路徑必炸。B 用正確簽章。
|
||
2. `A/admin/class-wpdo-dashboard-widget.php:191` 的 `wpdo_postmeta_cleanup` 仍是 `wp_nonce_url` GET 連結,但其 handler 已只收 POST → 該按鈕在 A 是壞的。B 改成 form。
|
||
- B 有 7 處 `Logger::error( 'event', array(...) )` 兩參數呼叫(4 個 stress tester、auto-promoter、conflict-detector、hook-bus),全部是 array→string 的 TypeError。用 token 掃描器(`scan_logger.php`)找出來的,肉眼與 grep 都容易漏。
|
||
- `Zone_Warm::set()` 改 upsert 後,**測試表也要有 `UNIQUE KEY ui_post_meta`**,否則 integration 會出現「set 沒覆蓋舊值」的假失敗。
|
||
- **B 的 unit bootstrap 會載入 `Member_Fields` / `Post_Fields`,A 的不會**。這使 Entity_Registry 在測試啟動時就有註冊 group,從 A 移植過來、假設「registry 為空」的測試(`MigrationPhaseRemainingTest`)會真的走進 `dbDelta` 而爆掉。修法是在該測試 setUp 以 reflection 清空 registry **並移除 `wpdo_register_entity_fields` listener**(phase 內部會再 fire 一次)。
|
||
- **快取類重構必定連帶測試隔離工作**:`Routing_Predicate::flush_cache()`(SyncBridgeEntityGuardTest)與 `Schema_Manager::flush_table_exists_cache()`(PostMigrationTest / PostEntityLifecycleTest)兩處都是 A 事後才補的,移植時要一起帶,否則出現「表被 drop 但快取說還在」的假錯誤。
|
||
- **B 自身的 slug 不一致**:`MENU_SLUG` 是 `2meet-data-optimizer`,但 help-tabs / setup-wizard / conflict-monitor / 3 個 template 共 10+ 處硬編碼 `page=wp-data-optimizer` → 這些連結原本全部 404。已統一回 `wp-data-optimizer`(與 wpdo_ 表/option/cron/hook/CLI 命名空間一致)。
|
||
- **雙向 hook 橋會暴露 test bootstrap 的 stub 缺口**:單向時 `do_action_ref_array()` 從未被走到,改雙向後兩個 bootstrap 都要補這個 stub。
|
||
- **AddOn 的 alias 必須各自做**:核心 back-compat 在 `plugins_loaded:4` 執行,AddOn 在 `:6` 才載入類別,核心的 alias 表涵蓋不到它們(57 個整合層類別先前完全沒有 `WPDO_` 名稱)。
|
||
- **PHP 無法 `class_alias()` 介面**。`WPDO_Migration_Phase_Interface` 沒有雙向 alias 的乾淨解法(單向 `extends` wrapper 無法讓 `instanceof` 成立),該契約是核心內部擴充點,測試改用 `TMDO_` 正式名稱即可。
|
||
|
||
---
|
||
|
||
## Git tag plan
|
||
|
||
- `v0.1.0` — 本 release(Phase 0-4 完成)
|
||
- `v0.1.1` — Phase 5 follow-up(tests / PHPCS / packaging)
|
||
- `v0.2.0` — WPDO_ deprecation notice + wpdo → tmdo migration CLI
|
||
|
||
---
|
||
|
||
## NinjaFirewall 相容性 ✅(2026-08-15,v1.0.2)
|
||
|
||
計畫檔:`~/.claude/plans/2meet-data-optimizer-ninjafirewall-effervescent-lighthouse.md`
|
||
|
||
- [x] 評估與 NinjaFirewall 4.9 的相容性 → **不需要開發 AddOn**
|
||
- [x] 診斷 `Cannot retrieve user options from database (#3)` → 與本外掛無關,根因為 symlink 部署
|
||
- [x] 建立 `/var/www/Studio/.htninja`(symlink-safe 站台錨點,全租戶共用)
|
||
- [x] dev30 啟用 ninjafirewall,補上缺失的 `nfw_options` / `nfw_rules`
|
||
- [x] `TMDO_Options_Manager::PROTECTED_OPTIONS` 守衛 + 4 個單元測試
|
||
- [x] Migration Wizard 輪詢 500ms → 2s
|
||
- [x] `docs/WAF-COMPATIBILITY.md`
|
||
- [x] 全套件回歸:591 tests / 1166 assertions OK
|
||
|
||
### Lessons learned(WAF)
|
||
|
||
- **`__DIR__` 會解析 symlink**。NinjaFirewall Full WAF 用 `dirname(dirname(dirname(__DIR__)))`(`lib/firewall.php:78`)推導站台位置,在 symlink 共享 codebase 的 SaaS 架構下,所有租戶都會被判定成「共享目錄所屬的那個站」,於是連錯資料庫、撈不到自己的 `nfw_options`,回錯誤碼 6(訊息寫作 `#3`)。**per-site 的可靠錨點是 `$_SERVER['DOCUMENT_ROOT']`**(web server 的 root 指令值,不受 symlink 影響),這正是 `.htninja` 的搜尋基準。
|
||
- **`.htninja` 放在 `dirname(DOCUMENT_ROOT)` 可服務全部租戶**(`firewall.php:47-48` 的第二順位),因為內容以 DOCUMENT_ROOT 動態推導,一份檔案通用。切勿在其中寫 `return` —— `'ALLOW'` / `'BLOCK'` 是有意義的回傳值。
|
||
- **`NFW_LOG_DIR` 對 WP WAF 模式同樣必要**:L78 的誤判在兩種模式都存在,只是 WP WAF 不用它連 DB,但 log / cache / session 仍會全部寫進共享目錄互相覆蓋。
|
||
- **WAF 失效是靜默的**。讀不到設定時 `nfw_quit()` 直接返回,不擋任何請求也不寫 log —— 「your site is not protected」是字面意思。因此任何會讓 `nfw_options` 離開 `wp_options` 的機制(例如本外掛的 `register_settings_group()` 重導向)都必須擋在註冊階段。
|
||
- **`.user.ini` 必須讓 php-fpm worker 可讀**。dev32 那份是 `root:root 0640`,www-data 讀不到 → `auto_prepend_file` 靜默失效、Full WAF 等同沒裝。排查時容易誤判成「WAF 有在跑」,實際上擋下請求的是共享 mu-plugin 的 WP WAF。
|
||
- **判斷 WAF 是否真的生效,要用會被規則擋的請求實測**(例如 `?x=../../etc/passwd` → 規則 1 → 403),光看首頁 200 或後台無錯誤訊息都不算數。
|
||
- WP-CLI 完全豁免(`firewall.php:18-23`),所以 CLI 全綠**不能**當作「WAF 與外掛相容」的證據,必須另外走 HTTP 驗證。
|