fix: partner 偵測符號對齊實際存在的常數 + schema 期望值補齊 #1

Open
wpdev wants to merge 1 commits from fix/partner-detection-symbols into main
3 changed files with 301 additions and 19 deletions
Showing only changes of commit bb71e38408 - Show all commits
+2 -2
View File
@@ -3,7 +3,7 @@
* Plugin Name: 2meet Data Optimizer — Bookings AddOn * Plugin Name: 2meet Data Optimizer — Bookings AddOn
* Plugin URI: https://2meet.io/2meet-data-optimizer/bookings * Plugin URI: https://2meet.io/2meet-data-optimizer/bookings
* Description: 2meet-bookings 8 張自訂表 (2mb_bookings/services/availability/date_overrides/vendor_settings/service_reviews/waitlist_submissions/audit_log) 註冊 * Description: 2meet-bookings 8 張自訂表 (2mb_bookings/services/availability/date_overrides/vendor_settings/service_reviews/waitlist_submissions/audit_log) 註冊
* Version: 0.1.0 * Version: 0.1.1
* Requires at least: 6.0 * Requires at least: 6.0
* Tested up to: 6.9.4 * Tested up to: 6.9.4
* Requires PHP: 8.1 * Requires PHP: 8.1
@@ -24,7 +24,7 @@ if ( ! defined( 'ABSPATH' ) ) {
exit; exit;
} }
define( 'TMDO_BOOKINGS_VERSION', '0.1.0' ); define( 'TMDO_BOOKINGS_VERSION', '0.1.1' );
define( 'TMDO_BOOKINGS_PATH', plugin_dir_path( __FILE__ ) ); define( 'TMDO_BOOKINGS_PATH', plugin_dir_path( __FILE__ ) );
define( 'TMDO_BOOKINGS_URL', plugin_dir_url( __FILE__ ) ); define( 'TMDO_BOOKINGS_URL', plugin_dir_url( __FILE__ ) );
define( 'TMDO_BOOKINGS_FILE', __FILE__ ); define( 'TMDO_BOOKINGS_FILE', __FILE__ );
+29
View File
@@ -13,6 +13,35 @@ Versioning follows [Semantic Versioning](https://semver.org/).
--- ---
## [0.1.1] — 2026-08-08 — 8 張 `2mb_*` 表補齊 schema 期望值
### Added
- 為全部 8 張 `2mb_*` 表補上 `expected_columns` 與索引定義:`2mb_services`
`2mb_availability``2mb_date_overrides``2mb_bookings``2mb_vendor_settings`
`2mb_service_reviews``2mb_waitlist_submissions``2mb_audit_log`。欄位與索引
逐項轉錄自 2meet-bookings activator 的 `CREATE TABLE`。先前只註冊表名,
`wp wpdo doctor` 只知道表存在,無從判斷欄位或索引是否漂移——這正是升級後
漏建欄位/漏建索引最容易矇混過關的地方。
### Fixed
- `TMDO_Custom_Table_Registry::register()` 的索引期望值 config key 由
`expected_indexes` 更正為 `indexes`。該 API 以 `wp_parse_args()` 併入預設值,
未知 key 會被**靜默丟棄**——先前索引期望值等同從未送達 registry,
`wp wpdo doctor` 的 schema drift 只驗欄位、不驗索引。此問題橫跨全部 8 個 AddOn,
已一併更正。
- `TMDO_Bookings::register()` 的 partner 偵測符號對齊實際存在的常數。舊版偵測
`TMEETIC_Booking_Plugin` 類別與 `TMEETIC_BOOKING_VERSION` 常數,兩者在 2meet-bookings
中**根本不存在**`TMEETIC_Booking` 雖存在,但要到 `plugins_loaded:26``tmb_run`
才 require,在本 AddOn bootstrap 的 `:6` 當下必定 MISSING。改用 `TMB_VERSION` /
`TMB_PLUGIN_DIR`,兩者在主檔檔案解析當下即 define,早於 `plugins_loaded`
實測(mu-plugin 於 `plugins_loaded:6` 量測,同一請求內對照):
`TMB_VERSION` = AVAILABLE、`TMEETIC_Booking` = MISSING。
`file_exists()` 後備保留不動,行為不變;但偵測不再是死碼。
---
## [0.1.0] — TBD ## [0.1.0] — TBD
### Added (initial release, scaffold) ### Added (initial release, scaffold)
+267 -14
View File
@@ -26,11 +26,22 @@ if ( ! defined( 'ABSPATH' ) ) {
*/ */
final class TMDO_Bookings { final class TMDO_Bookings {
/**
* 偵測以常數優先:`TMB_VERSION` / `TMB_PLUGIN_DIR` 在 2meet-bookings 主檔的
* **檔案解析當下**(第 22/25 行)即 define,早於 `plugins_loaded`;本 AddOn
* bootstrap 掛在 `plugins_loaded:6`,故此時常數已可用。
*
* 舊版偵測的 `TMEETIC_Booking_Plugin` 類別與 `TMEETIC_BOOKING_VERSION` 常數在
* 2meet-bookings 中**皆不存在**`TMEETIC_Booking` 雖存在,但要到 `plugins_loaded:26`
* `tmb_run`)才 require,在 :6 當下必定 MISSING。偵測實際完全靠下方的
* `file_exists()` 撐著。保留 file_exists 作最終後備,但常數檢查現在真的有效——
* 即使將來移除後備也不會像 infocards 那樣整個 AddOn 靜默失效。
*/
public static function register(): void { public static function register(): void {
// Detect via known class names. // Detect via constants defined at partner file-parse time (class loads too late at :6).
$detected = class_exists( 'TMEETIC_Booking_Plugin' ) $detected = defined( 'TMB_VERSION' )
|| class_exists( 'TMEETIC_Booking' ) || defined( 'TMB_PLUGIN_DIR' )
|| defined( 'TMEETIC_BOOKING_VERSION' ); || class_exists( 'TMEETIC_Booking' );
if ( ! $detected ) { if ( ! $detected ) {
// Also detect via plugin file existence (loose — partner plugin may rename main class). // Also detect via plugin file existence (loose — partner plugin may rename main class).
if ( ! file_exists( WP_PLUGIN_DIR . '/2meet-bookings/2meet-bookings.php' ) ) { if ( ! file_exists( WP_PLUGIN_DIR . '/2meet-bookings/2meet-bookings.php' ) ) {
@@ -47,29 +58,271 @@ final class TMDO_Bookings {
* V2.1.5: added wp_2mb_audit_log (was 7 tables, now 8 — matches plugin's * V2.1.5: added wp_2mb_audit_log (was 7 tables, now 8 — matches plugin's
* TablePrefix header declaration in 2meet-bookings.php). * TablePrefix header declaration in 2meet-bookings.php).
* *
* expected_columns/expected_indexes (this audit) are transcribed verbatim
* from the CREATE TABLE statements in
* 2meet-bookings/includes/class-2meetic-bookings-activator.php::create_tables()
* so `wp wpdo doctor`/`benchmark` can do schema drift detection.
*
* @param TMDO_Custom_Table_Registry $registry Singleton instance. * @param TMDO_Custom_Table_Registry $registry Singleton instance.
* @return void * @return void
*/ */
public static function register_custom_tables( TMDO_Custom_Table_Registry $registry ): void { public static function register_custom_tables( TMDO_Custom_Table_Registry $registry ): void {
$tables = array( $tables = array(
'2mb_bookings' => 'id', '2mb_services' => array(
'2mb_services' => 'id', 'primary_key' => 'id',
'2mb_availability' => 'id', 'expected_columns' => array(
'2mb_date_overrides' => 'id', 'id' => 'BIGINT',
'2mb_vendor_settings' => 'id', 'vendor_id' => 'BIGINT',
'2mb_service_reviews' => 'id', 'title' => 'VARCHAR',
'2mb_waitlist_submissions' => 'id', 'slug' => 'VARCHAR',
'2mb_audit_log' => 'id', // v2.1.5: missing previously 'description' => 'TEXT',
'category' => 'VARCHAR',
'tags' => 'VARCHAR',
'duration_min' => 'INT',
'buffer_min' => 'INT',
'capacity_max' => 'TINYINT',
'service_fee' => 'INT',
'deposit_amount' => 'INT',
'cover_image_url' => 'VARCHAR',
'location_type' => 'VARCHAR',
'location_county' => 'VARCHAR',
'location_address' => 'VARCHAR',
'location_url' => 'VARCHAR',
'online_url' => 'VARCHAR',
'cancellation_hours' => 'INT',
'advance_booking_days' => 'INT',
'min_booking_hours' => 'INT',
'confirmation_mode' => 'VARCHAR',
'questionnaire_json' => 'TEXT',
'notes' => 'TEXT',
'target_audience' => 'VARCHAR',
'payment_cash' => 'TINYINT',
'payment_bank_info' => 'VARCHAR',
'payment_note' => 'TEXT',
'status' => 'VARCHAR',
'admin_note' => 'TEXT',
'community_type' => 'VARCHAR',
'community_url' => 'VARCHAR',
'waitlist_enabled' => 'TINYINT',
'waitlist_max' => 'INT',
'questionnaire_form_id' => 'BIGINT',
'booking_count' => 'INT',
'avg_rating' => 'DECIMAL',
'created_at' => 'DATETIME',
'updated_at' => 'DATETIME',
),
'expected_indexes' => array(
'slug' => array( 'slug' ),
'vendor_id_status' => array( 'vendor_id', 'status' ),
'category' => array( 'category' ),
'location_county' => array( 'location_county' ),
'status' => array( 'status' ),
),
),
'2mb_availability' => array(
'primary_key' => 'id',
'expected_columns' => array(
'id' => 'BIGINT',
'vendor_id' => 'BIGINT',
'service_id' => 'BIGINT',
'day_of_week' => 'TINYINT',
'start_time' => 'VARCHAR',
'end_time' => 'VARCHAR',
'is_active' => 'TINYINT',
'created_at' => 'DATETIME',
'updated_at' => 'DATETIME',
),
'expected_indexes' => array(
'vendor_id_service' => array( 'vendor_id', 'service_id' ),
'day_of_week' => array( 'day_of_week' ),
),
),
'2mb_date_overrides' => array(
'primary_key' => 'id',
'expected_columns' => array(
'id' => 'BIGINT',
'vendor_id' => 'BIGINT',
'service_id' => 'BIGINT',
'override_date' => 'VARCHAR',
'override_type' => 'VARCHAR',
'override_source' => 'VARCHAR',
'start_time' => 'VARCHAR',
'end_time' => 'VARCHAR',
'note' => 'VARCHAR',
'created_at' => 'DATETIME',
),
'expected_indexes' => array(
'vendor_date' => array( 'vendor_id', 'override_date' ),
'service_date' => array( 'service_id', 'override_date' ),
'source_date' => array( 'vendor_id', 'override_source', 'override_date' ),
),
),
'2mb_bookings' => array(
'primary_key' => 'id',
'expected_columns' => array(
'id' => 'BIGINT',
'service_id' => 'BIGINT',
'vendor_id' => 'BIGINT',
'wp_user_id' => 'BIGINT',
'customer_name' => 'VARCHAR',
'customer_email' => 'VARCHAR',
'customer_phone' => 'VARCHAR',
'line_id' => 'VARCHAR',
'booking_date' => 'VARCHAR',
'start_time' => 'VARCHAR',
'end_time' => 'VARCHAR',
'headcount' => 'TINYINT',
'custom_note' => 'TEXT',
'booking_status' => 'VARCHAR',
'payment_status' => 'VARCHAR',
'payment_note' => 'VARCHAR',
'payment_gateway' => 'VARCHAR',
'payment_transaction_id' => 'VARCHAR',
'payment_amount' => 'DECIMAL',
'paid_at' => 'DATETIME',
'vendor_note' => 'TEXT',
'confirmation_token' => 'VARCHAR',
'google_event_id' => 'VARCHAR',
'confirmed_at' => 'DATETIME',
'cancelled_at' => 'DATETIME',
'cancel_reason' => 'VARCHAR',
'cancelled_by' => 'VARCHAR',
'rescheduled_from' => 'BIGINT',
'questionnaire_answers' => 'TEXT',
'group_label' => 'VARCHAR',
'completed_at' => 'DATETIME',
'order_id' => 'BIGINT',
'commission_rate' => 'DECIMAL',
'platform_fee' => 'INT',
'vendor_payout' => 'INT',
'payout_status' => 'VARCHAR',
'payout_note' => 'TEXT',
'settled_at' => 'DATETIME',
'waitlist_position' => 'INT',
'eligibility_tier' => 'VARCHAR',
'questionnaire_status' => 'VARCHAR',
'review_reminder_sent_at' => 'DATETIME',
'created_at' => 'DATETIME',
'updated_at' => 'DATETIME',
),
'expected_indexes' => array(
'confirmation_token' => array( 'confirmation_token' ),
'service_id' => array( 'service_id' ),
'vendor_id' => array( 'vendor_id' ),
'wp_user_id' => array( 'wp_user_id' ),
'customer_email' => array( 'customer_email' ),
'booking_status' => array( 'booking_status' ),
'booking_date' => array( 'booking_date' ),
'payment_status' => array( 'payment_status' ),
'payout_status' => array( 'payout_status' ),
'service_date_status' => array( 'service_id', 'booking_date', 'booking_status' ),
),
),
'2mb_vendor_settings' => array(
'primary_key' => 'id',
'expected_columns' => array(
'id' => 'BIGINT',
'vendor_id' => 'BIGINT',
'payment_linepay_url' => 'VARCHAR',
'payment_cash_default' => 'TINYINT',
'payment_bank_default' => 'VARCHAR',
'payment_note_default' => 'TEXT',
'ical_token' => 'VARCHAR',
'line_notify_token' => 'VARCHAR',
'line_notify_enabled' => 'TINYINT',
'default_buffer_min' => 'INT',
'default_cancellation_hours' => 'INT',
'google_tokens' => 'TEXT',
'gcal_watch_channel_id' => 'VARCHAR',
'gcal_watch_resource_id' => 'VARCHAR',
'gcal_watch_expiration' => 'BIGINT',
'gcal_watch_token' => 'VARCHAR',
'telegram_bot_token' => 'VARCHAR',
'telegram_chat_id' => 'VARCHAR',
'slack_webhook_url' => 'VARCHAR',
'created_at' => 'DATETIME',
'updated_at' => 'DATETIME',
),
'expected_indexes' => array(
'vendor_id' => array( 'vendor_id' ),
'gcal_watch_expiration' => array( 'gcal_watch_expiration' ),
'gcal_watch_channel_id' => array( 'gcal_watch_channel_id' ),
),
),
'2mb_service_reviews' => array(
'primary_key' => 'id',
'expected_columns' => array(
'id' => 'BIGINT',
'service_id' => 'BIGINT',
'booking_id' => 'BIGINT',
'vendor_id' => 'BIGINT',
'customer_name' => 'VARCHAR',
'rating' => 'TINYINT',
'review_text' => 'TEXT',
'vendor_reply' => 'TEXT',
'status' => 'VARCHAR',
'created_at' => 'DATETIME',
'updated_at' => 'DATETIME',
),
'expected_indexes' => array(
'booking_id' => array( 'booking_id' ),
'service_id_status' => array( 'service_id', 'status' ),
'vendor_id' => array( 'vendor_id' ),
),
),
'2mb_waitlist_submissions' => array(
'primary_key' => 'id',
'expected_columns' => array(
'id' => 'BIGINT',
'booking_id' => 'BIGINT',
'fluent_entry_id' => 'BIGINT',
'answers_snapshot' => 'TEXT',
'submitted_at' => 'DATETIME',
'review_status' => 'VARCHAR',
'reviewed_by_vendor' => 'BIGINT',
'reviewed_at_vendor' => 'DATETIME',
'vendor_notes' => 'TEXT',
'reviewed_by_admin' => 'BIGINT',
'reviewed_at_admin' => 'DATETIME',
'admin_notes' => 'TEXT',
'revision_count' => 'INT',
),
'expected_indexes' => array(
'idx_booking_id' => array( 'booking_id' ),
'idx_fluent_entry' => array( 'fluent_entry_id' ),
'review_status' => array( 'review_status' ),
),
),
'2mb_audit_log' => array(
'primary_key' => 'id', // v2.1.5: missing previously
'expected_columns' => array(
'id' => 'BIGINT',
'level' => 'VARCHAR',
'event' => 'VARCHAR',
'message' => 'VARCHAR',
'context' => 'TEXT',
'user_id' => 'BIGINT',
'ip' => 'VARCHAR',
'created_at' => 'DATETIME',
),
'expected_indexes' => array(
'level_created' => array( 'level', 'created_at' ),
'event_created' => array( 'event', 'created_at' ),
'user_id' => array( 'user_id' ),
),
),
); );
foreach ( $tables as $table_name => $primary_key ) { foreach ( $tables as $table_name => $definition ) {
$registry->register( $registry->register(
'2meet-bookings', '2meet-bookings',
array( array(
'table_name' => $table_name, 'table_name' => $table_name,
'primary_key' => $primary_key, 'primary_key' => $definition['primary_key'],
'post_type_link' => null, 'post_type_link' => null,
'doctor_callback' => array( __CLASS__, 'doctor_table_factory' ), 'doctor_callback' => array( __CLASS__, 'doctor_table_factory' ),
'expected_columns' => $definition['expected_columns'],
'indexes' => $definition['expected_indexes'],
) )
); );
} }