register_many( '2meet-infocards', array( // Vendor profile colour fields → Hot zone (varchar columns). array( 'post_type' => 'hp_vendor', 'meta_key' => 'tmeetic_profile_primary', 'zone' => 'hot', 'data_type' => "varchar(20) NOT NULL DEFAULT ''", 'column' => 'tmeetic_profile_primary', ), array( 'post_type' => 'hp_vendor', 'meta_key' => 'tmeetic_profile_accent', 'zone' => 'hot', 'data_type' => "varchar(20) NOT NULL DEFAULT ''", 'column' => 'tmeetic_profile_accent', ), array( 'post_type' => 'hp_vendor', 'meta_key' => 'tmeetic_profile_font', 'zone' => 'hot', 'data_type' => "varchar(50) NOT NULL DEFAULT ''", 'column' => 'tmeetic_profile_font', ), // Hero image is descriptive → Cold zone. array( 'post_type' => 'hp_vendor', 'meta_key' => 'tmeetic_profile_hero_image', 'zone' => 'cold', 'cache_group' => 'wpdo_cold_hp_vendor', 'cache_ttl' => HOUR_IN_SECONDS, ), // IG tokens — sensitive + display-only → Cold zone. // 註:app id / secret 不在此列——那是站台層級的 wp_options // (`2meetic_ig_app_id` / `2meetic_ig_app_secret_enc`),非 hp_vendor // postmeta。舊版誤註冊的兩個 postmeta 欄位全外掛零讀寫,已移除。 array( 'post_type' => 'hp_vendor', 'meta_key' => 'tmeetic_ig_access_token_enc', 'zone' => 'cold', 'cache_group' => 'wpdo_cold_hp_vendor', 'cache_ttl' => HOUR_IN_SECONDS, ), array( 'post_type' => 'hp_vendor', 'meta_key' => 'tmeetic_ig_token_expiry', 'zone' => 'cold', 'cache_group' => 'wpdo_cold_hp_vendor', 'cache_ttl' => HOUR_IN_SECONDS, ), array( 'post_type' => 'hp_vendor', 'meta_key' => 'tmeetic_ig_user_id', 'zone' => 'cold', 'cache_group' => 'wpdo_cold_hp_vendor', 'cache_ttl' => HOUR_IN_SECONDS, ), array( 'post_type' => 'hp_vendor', 'meta_key' => 'tmeetic_ig_username', 'zone' => 'cold', 'cache_group' => 'wpdo_cold_hp_vendor', 'cache_ttl' => HOUR_IN_SECONDS, ), array( 'post_type' => 'hp_vendor', 'meta_key' => 'tmeetic_ig_feed_show', 'zone' => 'cold', 'cache_group' => 'wpdo_cold_hp_vendor', 'cache_ttl' => HOUR_IN_SECONDS, ), ) ); } /** * Register infocards custom tables so they appear in `wp wpdo doctor`, * `benchmark`, and `cleanup` tooling. * * @param TMDO_Custom_Table_Registry $registry Custom Table Registry singleton. * @return void */ public static function register_custom_tables( TMDO_Custom_Table_Registry $registry ): void { if ( ! self::partner_detected() ) { return; } // CSS variable source — checked by WPDO doctor for index health. $registry->register( '2meet-infocards', array( 'table_name' => '2meet_vendor_profiles', 'primary_key' => 'vendor_id', 'post_type_link' => 'hp_vendor', 'doctor_callback' => array( __CLASS__, 'doctor_vendor_profiles' ), 'expected_columns' => array( 'id' => 'BIGINT', 'vendor_id' => 'BIGINT', 'primary_color' => 'VARCHAR', 'accent_color' => 'VARCHAR', 'bg_color' => 'VARCHAR', 'text_color' => 'VARCHAR', 'muted_color' => 'VARCHAR', 'font' => 'VARCHAR', 'radius_card' => 'INT', 'radius_btn' => 'INT', 'shadow_style' => 'VARCHAR', 'hero_image_id' => 'BIGINT', 'hero_fullwidth' => 'TINYINT', 'ig_feed_show' => 'TINYINT', 'theme_style_id' => 'BIGINT', 'updated_at' => 'DATETIME', ), 'indexes' => array( 'vendor_id' => array( 'vendor_id' ), ), ) ); $registry->register( '2meet-infocards', array( 'table_name' => '2meet_cards', 'primary_key' => 'id', 'post_type_link' => 'hp_vendor', 'expected_columns' => array( 'id' => 'BIGINT', 'page_id' => 'BIGINT', 'story_id' => 'BIGINT', 'epk_id' => 'BIGINT', 'card_type' => 'VARCHAR', 'title' => 'VARCHAR', 'description' => 'TEXT', 'image_id' => 'BIGINT', 'config' => 'TEXT', 'cta_text' => 'VARCHAR', 'cta_url' => 'VARCHAR', 'button_bg_color' => 'VARCHAR', 'button_text_color' => 'VARCHAR', 'sort_order' => 'INT', 'status' => 'VARCHAR', 'start_at' => 'DATETIME', 'end_at' => 'DATETIME', 'vendor_id' => 'BIGINT', 'show_click_count' => 'TINYINT', 'ab_enabled' => 'TINYINT', 'liff_enabled' => 'TINYINT', 'created_at' => 'DATETIME', 'updated_at' => 'DATETIME', 'click_count' => 'BIGINT', 'last_click_at' => 'DATETIME', ), 'indexes' => array( 'page_id_status_sort' => array( 'page_id', 'status', 'sort_order' ), 'story_id_status_sort' => array( 'story_id', 'status', 'sort_order' ), 'epk_id_status_sort' => array( 'epk_id', 'status', 'sort_order' ), 'card_type' => array( 'card_type' ), 'vendor_status_sort' => array( 'vendor_id', 'status', 'sort_order' ), 'status_schedule' => array( 'status', 'start_at', 'end_at' ), ), ) ); $registry->register( '2meet-infocards', array( 'table_name' => '2meet_theme_styles', 'primary_key' => 'id', 'post_type_link' => null, 'expected_columns' => array( 'id' => 'BIGINT', 'name' => 'VARCHAR', 'slug' => 'VARCHAR', 'config' => 'TEXT', 'sort_order' => 'INT', 'is_active' => 'TINYINT', 'created_at' => 'DATETIME', 'updated_at' => 'DATETIME', ), 'indexes' => array( 'slug' => array( 'slug' ), ), ) ); } /** * Doctor check: verifies row count + sample read latency on vendor_profiles. * * @return array{ok:bool, message:string} */ public static function doctor_vendor_profiles(): array { global $wpdb; try { $exists = (bool) $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->prefix . '2meet_vendor_profiles' ) ); if ( ! $exists ) { return array( 'ok' => false, 'message' => 'wp_2meet_vendor_profiles table missing', ); } $count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$wpdb->prefix}2meet_vendor_profiles`" ); return array( 'ok' => true, 'message' => "wp_2meet_vendor_profiles: {$count} rows", ); } catch ( \Throwable $e ) { return array( 'ok' => false, 'message' => 'doctor failed: ' . $e->getMessage(), ); } } /** * Cache key for a vendor profile blob (used by inject_profile_styles). * * @param int $vendor_id Vendor post ID. */ public static function vendor_profile_cache_key( int $vendor_id ): string { return 'wpdo_vendor_profile_' . $vendor_id; } }