@@ -26,11 +26,22 @@ if ( ! defined( 'ABSPATH' ) ) {
*/
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 known class names .
$detected = class_exists ( 'TMEETIC_Booking_Plugin ' )
|| class_exists ( 'TMEETIC_Booking ' )
|| defined ( 'TMEETIC_BOOKING_VERSION ' );
// 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' ) ) {
@@ -47,29 +58,271 @@ final class TMDO_Bookings {
* 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_booking s' => 'id' ,
'2mb_services' => 'id' ,
'2mb_availability' => 'id' ,
'2mb_date_overrides' => 'id ' ,
'2mb_ vendor_settings' => 'id ' ,
'2mb_service_reviews' => 'id ',
'2mb_waitlist_submissions' => 'id ',
'2mb_audit_log ' => 'id ' , // v2.1.5: missing previously
'2mb_service s' => 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 => $primary_key ) {
foreach ( $tables as $table_name => $definition ) {
$registry -> register (
'2meet-bookings' ,
array (
'table_name' => $table_name ,
'primary_key' => $primary_key ,
'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' ],
)
);
}