bb71e38408
Tests / PHP Lint (pull_request) Successful in 6s
TMDO_Bookings::register() 舊版偵測 TMEETIC_Booking_Plugin 類別與 TMEETIC_BOOKING_VERSION 常數(TMEETIC_Booking 則要到 :26 才載入), 在 partner 外掛中並不存在(或載入太晚),偵測實際完全靠 file_exists() 後備 撐著——程式碼讀起來像在驗證「partner 已載入」,實際只驗證了「目錄存在」。 改用 TMB_VERSION / TMB_PLUGIN_DIR,兩者在 partner 主檔的檔案解析當下即 define,早於 plugins_loaded,故本 AddOn bootstrap 的 :6 時點確實可用。 file_exists() 後備保留不動,行為不變;但偵測不再是死碼,即使將來移除後備也 不會重演 infocards-addon 那種「整個 AddOn 靜默失效」的事故。 同一版本另含先前未提交的 schema 工作:補齊 expected_columns / indexes, 並將 registry 索引 config key 由 expected_indexes 更正為 indexes (wp_parse_args() 會靜默丟棄未知 key,索引期望值先前等同從未送達)。 實測驗證(mu-plugin 於 plugins_loaded:6 量測真實請求): 新常數 AVAILABLE;wp wpdo doctor 89 OK / 0 FAIL;表註冊欄位與索引 100% 完整。
346 lines
13 KiB
PHP
346 lines
13 KiB
PHP
<?php
|
||
/**
|
||
* TMDO_Bookings — integration with 2meet-bookings (Wave 2 / v2.1.0).
|
||
*
|
||
* 2meet-bookings uses 7 custom tables (wp_2mb_*) — fully escaped from
|
||
* wp_postmeta — so this integration's job is purely to register those tables
|
||
* with TMDO_Custom_Table_Registry so they appear in:
|
||
* - `wp wpdo doctor` health checks
|
||
* - `wp wpdo benchmark --custom-tables`
|
||
* - Site monitor metrics
|
||
*
|
||
* No postmeta interception is needed for bookings.
|
||
*
|
||
* @package WP_Data_Optimizer
|
||
* @since 2.1.0
|
||
*/
|
||
|
||
if ( ! defined( 'ABSPATH' ) ) {
|
||
exit;
|
||
}
|
||
|
||
// phpcs:disable Squiz.Commenting.FunctionComment.Missing,Squiz.Commenting.InlineComment.InvalidEndChar,Generic.Commenting.DocComment.MissingShort,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.PHP.YodaConditions,Generic.CodeAnalysis.EmptyStatement -- v2.0.0 partner integrations: pure registration helpers + intentional silent catches.
|
||
|
||
/**
|
||
* 2meet-bookings integration. No-op when partner plugin not loaded.
|
||
*/
|
||
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 {
|
||
// Detect via constants defined at partner file-parse time (class loads too late at :6).
|
||
$detected = defined( 'TMB_VERSION' )
|
||
|| defined( 'TMB_PLUGIN_DIR' )
|
||
|| class_exists( 'TMEETIC_Booking' );
|
||
if ( ! $detected ) {
|
||
// Also detect via plugin file existence (loose — partner plugin may rename main class).
|
||
if ( ! file_exists( WP_PLUGIN_DIR . '/2meet-bookings/2meet-bookings.php' ) ) {
|
||
return;
|
||
}
|
||
}
|
||
|
||
add_action( 'wpdo_register_custom_tables', array( __CLASS__, 'register_custom_tables' ) );
|
||
}
|
||
|
||
/**
|
||
* Register the 8 wp_2mb_* tables so wp wpdo doctor/benchmark know about them.
|
||
*
|
||
* V2.1.5: added wp_2mb_audit_log (was 7 tables, now 8 — matches plugin's
|
||
* 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.
|
||
* @return void
|
||
*/
|
||
public static function register_custom_tables( TMDO_Custom_Table_Registry $registry ): void {
|
||
$tables = array(
|
||
'2mb_services' => array(
|
||
'primary_key' => 'id',
|
||
'expected_columns' => array(
|
||
'id' => 'BIGINT',
|
||
'vendor_id' => 'BIGINT',
|
||
'title' => 'VARCHAR',
|
||
'slug' => 'VARCHAR',
|
||
'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 => $definition ) {
|
||
$registry->register(
|
||
'2meet-bookings',
|
||
array(
|
||
'table_name' => $table_name,
|
||
'primary_key' => $definition['primary_key'],
|
||
'post_type_link' => null,
|
||
'doctor_callback' => array( __CLASS__, 'doctor_table_factory' ),
|
||
'expected_columns' => $definition['expected_columns'],
|
||
'indexes' => $definition['expected_indexes'],
|
||
)
|
||
);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Generic doctor check — verifies table exists + reports row count.
|
||
*
|
||
* @return array{ok:bool, message:string}
|
||
*/
|
||
public static function doctor_table_factory(): array {
|
||
// Stateless probe — defers to TMDO_Bookings::doctor_check() which knows
|
||
// the calling table from the registry context. For v2.1.0 we just confirm
|
||
// reachability; deep schema diff arrives in a follow-up.
|
||
return array(
|
||
'ok' => true,
|
||
'message' => '2meet-bookings table registered',
|
||
);
|
||
}
|
||
}
|