fire_registration(); } if ( class_exists( 'TMDO_Schema_Manager' ) && class_exists( 'TMDO_Entity_Registry' ) ) { $pending = TMDO_Entity_Registry::get_pending_schemas(); if ( ! empty( $pending ) ) { TMDO_Schema_Manager::process_pending_migrations(); } } do_action( 'wpdo_late_bind_setup' ); }, 1 ); } // HivePress 整合已移至 2meet-data-optimizer-hivepress-addon。 // AddOn bootstrap 自行偵測 + register。Core 完全 HP-agnostic。 // Cache Layer hooks (Zone C prefetch + save_post warming). TMDO_Cache_Layer::register_hooks(); // Register HPCT-inherited module interceptors if allowed. if ( TMDO_Compatibility::can_register_hpct_hooks() ) { $this->register_hpct_interceptors(); } // v2.1.2 fix: WC commission interceptor is independent of HPCT — writes // to its own wp_wpdo_wc_commissions table. Register whenever WC is active. if ( class_exists( 'TMDO_WooCommerce' ) && TMDO_WooCommerce::is_active() && class_exists( 'TMDO_WC_Orders_Interceptor' ) ) { ( new TMDO_WC_Orders_Interceptor() )->register_hooks(); } // Zone interceptors are always safe to register. if ( TMDO_Compatibility::can_register_zone_hooks() ) { $this->register_zone_hooks(); } // v2.0.0: register entity adapters into Entity Registry. Adapters are // REGISTERED unconditionally (so TMDO_API::get_entity / Hook Bus can // find them), but Hook Bus only initializes when feature flag is on // (default off — preserves v1.3.x behaviour). $this->register_entity_adapters(); // v2.0.0: boot the unified Hook Bus when explicitly enabled. TMDO_Hook_Bus_Bridge::maybe_init_hook_bus(); // Register audit log listeners for wpdo_after_write / wpdo_after_delete. if ( class_exists( 'TMDO_Audit_Logger' ) ) { TMDO_Audit_Logger::init(); } // v2.0.0: fire the Custom Table Registry hook so partner plugins can register. TMDO_Custom_Table_Registry::instance()->fire_registration(); // v2.0.0: production-time conflict monitor (admin notice + admin bar). if ( class_exists( 'TMDO_Conflict_Monitor' ) ) { TMDO_Conflict_Monitor::register_hooks(); } // v2.0.x: partner plugin integrations have been pre-registered earlier // (see top of run() before the wpdo_register_fields do_action). Removing // the duplicate late-binding block — see commit log for time-ordering bug. // Schedule cron events. $this->schedule_cron(); // Admin notice for HPCT import. if ( is_admin() && TMDO_Compatibility::should_show_hpct_notice() ) { add_action( 'admin_notices', array( $this, 'render_hpct_notice' ) ); } // v2.6.6: async entity backfill cron handler. add_action( 'wpdo_entity_backfill_batch', array( $this, 'run_entity_backfill_batch' ), 10, 2 ); // v2.6.7: user stress-test batch cron handler. add_action( TMDO_User_Stress_Tester::CRON_HOOK, array( __CLASS__, 'run_stress_test_batch' ) ); // v2.11.4: post stress-test batch cron handler. if ( class_exists( 'TMDO_Post_Stress_Tester' ) ) { add_action( TMDO_Post_Stress_Tester::CRON_HOOK, array( __CLASS__, 'run_post_stress_test_batch' ) ); } // v2.13.0: term stress-test batch cron handler. if ( class_exists( 'TMDO_Term_Stress_Tester' ) ) { add_action( TMDO_Term_Stress_Tester::CRON_HOOK, array( __CLASS__, 'run_term_stress_test_batch' ) ); } // v2.13.1: comment stress-test batch cron handler. if ( class_exists( 'TMDO_Comment_Stress_Tester' ) ) { add_action( TMDO_Comment_Stress_Tester::CRON_HOOK, array( __CLASS__, 'run_comment_stress_test_batch' ) ); } // v2.11.5: HivePress per-post transient cache → wp_options reroute. // Filters registered on `init` so HivePress's own bootstrap (plugins_loaded:5) // runs first and our filter chain catches the actual cache writes happening // during save_post and term-cache rebuilds. if ( class_exists( 'TMDO_Hivepress_Transient_Filter' ) ) { add_action( 'init', array( 'TMDO_Hivepress_Transient_Filter', 'init' ), 5 ); } // v2.12.1: Term + Comment garbage write-time filter // (silent-drop _wxr_import_* / _2meet_demo_* / orphan post-meta keys). if ( class_exists( 'TMDO_Term_Comment_Garbage_Filter' ) ) { add_action( 'init', array( 'TMDO_Term_Comment_Garbage_Filter', 'init' ), 5 ); } // v2.12.3: WooCommerce term count cache → wp_options reroute // (product_count_ rows out of wp_termmeta). if ( class_exists( 'TMDO_WC_Term_Count_Filter' ) ) { add_action( 'init', array( 'TMDO_WC_Term_Count_Filter', 'init' ), 5 ); } // v2.12.4: Term + Comment misc bucket (catch-all flat for unregistered keys). // Filters at priority 99 (LAST in chain) so all other v2.12.x filters // + Hook Bus get first crack; only truly unhandled keys land here. if ( class_exists( 'TMDO_Term_Comment_Misc_Bucket' ) ) { add_action( 'init', array( 'TMDO_Term_Comment_Misc_Bucket', 'init' ), 5 ); } } /** * Cron handler: 執行壓力測試的單一批次(v2.6.7)。 */ public static function run_stress_test_batch(): void { if ( class_exists( 'TMDO_User_Stress_Tester' ) ) { TMDO_User_Stress_Tester::run_batch(); } } /** * Cron handler: post entity 壓力測試單一批次(v2.11.4)。 * * @return void */ public static function run_post_stress_test_batch(): void { if ( class_exists( 'TMDO_Post_Stress_Tester' ) ) { TMDO_Post_Stress_Tester::run_batch(); } } /** * Cron handler: term entity 壓力測試單一批次(v2.13.0)。 * * @return void */ public static function run_term_stress_test_batch(): void { if ( class_exists( 'TMDO_Term_Stress_Tester' ) ) { TMDO_Term_Stress_Tester::run_batch(); } } /** * Cron handler: comment entity 壓力測試單一批次(v2.13.1)。 * * @return void */ public static function run_comment_stress_test_batch(): void { if ( class_exists( 'TMDO_Comment_Stress_Tester' ) ) { TMDO_Comment_Stress_Tester::run_batch(); } } /** * Register the four entity adapters into TMDO_Entity_Registry. * * Registration is unconditional but write/read paths only activate when * the corresponding module is in a write-active or read-custom state * (feature flag controlled). * * @return void */ private function register_entity_adapters(): void { if ( ! class_exists( 'TMDO_Entity_Registry' ) ) { return; } TMDO_Entity_Registry::register_adapter( 'post', new TMDO_Adapter_Post() ); TMDO_Entity_Registry::register_adapter( 'user', new TMDO_Adapter_User() ); TMDO_Entity_Registry::register_adapter( 'term', new TMDO_Adapter_Term() ); TMDO_Entity_Registry::register_adapter( 'comment', new TMDO_Adapter_Comment() ); /** * Action: wpdo_register_entity_fields * * Partner plugins register their entity meta_key → adapter mappings here. * Mirrors `wpdo_register_fields` but for non-postmeta entities. * * @since 2.0.0 */ do_action( 'wpdo_register_entity_fields', TMDO_Entity_Registry::class ); } /** * Register default HivePress field mappings. * * These are the known HivePress meta_keys and their zone classifications. * * @param TMDO_Schema_Registry $registry The schema registry instance. * @return void */ private function register_hivepress_defaults( TMDO_Schema_Registry $registry ): void { // Zone A (Hot) — Listing search/filter fields. $registry->register_many( 'hivepress', array( array( 'post_type' => 'hp_listing', 'meta_key' => 'hp_price', 'zone' => 'hot', 'data_type' => 'decimal(10,2) NOT NULL DEFAULT 0', 'column' => 'hp_price', 'indexed' => true, ), array( 'post_type' => 'hp_listing', 'meta_key' => 'hp_featured', 'zone' => 'hot', 'data_type' => 'tinyint(1) NOT NULL DEFAULT 0', 'column' => 'hp_featured', 'indexed' => true, ), array( 'post_type' => 'hp_listing', 'meta_key' => 'hp_verified', 'zone' => 'hot', 'data_type' => 'tinyint(1) NOT NULL DEFAULT 0', 'column' => 'hp_verified', 'indexed' => true, ), array( 'post_type' => 'hp_listing', 'meta_key' => 'hp_expired_time', 'zone' => 'hot', 'data_type' => 'bigint(20) NOT NULL DEFAULT 0', 'column' => 'hp_expired_time', ), array( 'post_type' => 'hp_listing', 'meta_key' => 'hp_featured_time', 'zone' => 'hot', 'data_type' => 'bigint(20) NOT NULL DEFAULT 0', 'column' => 'hp_featured_time', ), // Vendor hot fields. array( 'post_type' => 'hp_vendor', 'meta_key' => 'hp_verified', 'zone' => 'hot', 'data_type' => 'tinyint(1) NOT NULL DEFAULT 0', 'column' => 'hp_verified', 'indexed' => true, ), array( 'post_type' => 'hp_vendor', 'meta_key' => 'hp_hourly_rate', 'zone' => 'hot', 'data_type' => 'decimal(10,2) NOT NULL DEFAULT 0', 'column' => 'hp_hourly_rate', 'indexed' => true, ), array( 'post_type' => 'hp_vendor', 'meta_key' => 'hp_rating_count', 'zone' => 'hot', 'data_type' => 'int(11) NOT NULL DEFAULT 0', 'column' => 'hp_rating_count', 'indexed' => false, ), ) ); // Zone C (Cold) — Profile/display fields. $registry->register_many( 'hivepress', array( array( 'post_type' => 'hp_vendor', 'meta_key' => 'hp_description', 'zone' => 'cold', 'cache_group' => 'wpdo_cold_hp_vendor', 'cache_ttl' => HOUR_IN_SECONDS, ), array( 'post_type' => 'hp_listing', 'meta_key' => 'hp_description', 'zone' => 'cold', 'cache_group' => 'wpdo_cold_hp_listing', 'cache_ttl' => HOUR_IN_SECONDS, ), ) ); // ── HivePress extension fields (filled the 47% → 94% coverage gap) ── // Reviews extension (hp_review post type). $registry->register_many( 'hivepress-reviews', array( array( 'post_type' => 'hp_review', 'meta_key' => 'hp_rating', 'zone' => 'hot', 'data_type' => 'tinyint(1) NOT NULL DEFAULT 0', 'column' => 'hp_rating', 'indexed' => true, ), array( 'post_type' => 'hp_vendor', 'meta_key' => 'hp_rating', 'zone' => 'hot', 'data_type' => 'decimal(3,2) NOT NULL DEFAULT 0', 'column' => 'hp_rating', 'indexed' => true, ), array( 'post_type' => 'hp_review', 'meta_key' => 'hp_text', 'zone' => 'cold', 'cache_group' => 'wpdo_cold_hp_review', 'cache_ttl' => HOUR_IN_SECONDS, ), ) ); // Bookings extension (hp_booking post type). $registry->register_many( 'hivepress-bookings', array( array( 'post_type' => 'hp_booking', 'meta_key' => 'hp_start_time', 'zone' => 'hot', 'data_type' => 'bigint(20) NOT NULL DEFAULT 0', 'column' => 'hp_start_time', 'indexed' => true, ), array( 'post_type' => 'hp_booking', 'meta_key' => 'hp_end_time', 'zone' => 'hot', 'data_type' => 'bigint(20) NOT NULL DEFAULT 0', 'column' => 'hp_end_time', 'indexed' => true, ), array( 'post_type' => 'hp_booking', 'meta_key' => 'hp_status', 'zone' => 'hot', 'data_type' => "varchar(20) NOT NULL DEFAULT ''", 'column' => 'hp_status', 'indexed' => true, ), array( 'post_type' => 'hp_listing', 'meta_key' => 'hp_booking_enabled', 'zone' => 'hot', 'data_type' => 'tinyint(1) NOT NULL DEFAULT 0', 'column' => 'hp_booking_enabled', 'indexed' => true, ), ) ); // Marketplace / Requests extension. $registry->register_many( 'hivepress-marketplace', array( array( 'post_type' => 'hp_request', 'meta_key' => 'hp_request_category', 'zone' => 'hot', 'data_type' => 'bigint(20) NOT NULL DEFAULT 0', 'column' => 'hp_request_category', 'indexed' => true, ), array( 'post_type' => 'hp_offer', 'meta_key' => 'hp_amount', 'zone' => 'hot', 'data_type' => 'decimal(10,2) NOT NULL DEFAULT 0', 'column' => 'hp_amount', 'indexed' => true, ), array( 'post_type' => 'hp_offer', 'meta_key' => 'hp_request', 'zone' => 'hot', 'data_type' => 'bigint(20) NOT NULL DEFAULT 0', 'column' => 'hp_request', 'indexed' => true, ), ) ); } /** * Register HPCT-inherited module interceptors and query interceptors. */ private function register_hpct_interceptors(): void { // 7 HPCT-era meta interceptors 已搬到 2meet-data-optimizer-hivepress-addon。 // 1 LatePoint interceptor 已搬到 2meet-data-optimizer-latepoint-addon。 // 5 query interceptors 已搬到 2meet-data-optimizer-hivepress-addon。 // 此方法保留為空殼以保持 ABI 相容;AddOn 各自於自身 bootstrap 內 register_hooks。 } /** * Register zone-specific hooks. */ private function register_zone_hooks(): void { // Sync Bridge: Zone-aware dual-write + read routing for all zone fields. $sync_bridge = new TMDO_Sync_Bridge(); $sync_bridge->register_hooks(); // Query Router: Zone A flat-column JOIN rewrite for WP_Query. $query_router = new TMDO_Query_Router(); $query_router->register_hooks(); // v2.10.1 Post Entity Query Router — only registers when post mode // is shadow_read or aeav_only (zero overhead in disabled/dual_write). // is_router_active() guards against early-bind issues; once mode flips // to reads_from_flat the hooks fire on subsequent requests. if ( class_exists( 'TMDO_Post_Query_Router' ) && TMDO_Post_Query_Router::is_router_active() ) { $post_router = new TMDO_Post_Query_Router(); $post_router->register_hooks(); } // Zone B: Warm cleanup cron handler. add_action( 'wpdo_warm_cleanup', array( $this, 'cleanup_warm_zone' ) ); // v2.10.3: Post shadow_read verifier cron handler. Only fires when // post mode is shadow_read (verifier::cron_tick() is internally gated). if ( class_exists( 'TMDO_Post_Shadow_Verifier' ) ) { add_action( TMDO_Post_Shadow_Verifier::CRON_HOOK, array( 'TMDO_Post_Shadow_Verifier', 'cron_tick' ) ); } // v2.12.5: Term + Comment shadow verifier cron handler. Only fires when // either term or comment mode is shadow_read (gated internally). if ( class_exists( 'TMDO_Term_Comment_Shadow_Verifier' ) ) { add_action( TMDO_Term_Comment_Shadow_Verifier::CRON_HOOK, array( 'TMDO_Term_Comment_Shadow_Verifier', 'cron_tick' ) ); } // v2.10.3: react to bridge mode changes — register/unregister verifier // cron when post mode crosses the shadow_read threshold. add_action( 'wpdo_bridge_mode_changed', array( $this, 'maybe_toggle_post_verifier_cron' ), 10, 3 ); // v2.12.5: same listener for term/comment. add_action( 'wpdo_bridge_mode_changed', array( $this, 'maybe_toggle_term_comment_verifier_cron' ), 10, 3 ); // Zone D: Archive sweep cron handler. add_action( 'wpdo_archive_sweep', array( $this, 'sweep_archive_zone' ) ); // Errors GC cron handler — keeps wp_wpdo_errors bounded (default 90 days). add_action( 'wpdo_errors_gc', array( $this, 'gc_errors' ) ); // Monthly health-snapshot cron handler. add_action( 'wpdo_health_snapshot_monthly', array( $this, 'monthly_health_snapshot' ) ); // v2.3.0 M6: daily health probe cron. add_action( 'wpdo_daily_health_check', array( 'TMDO_Health_Cron', 'run' ) ); // v2.3.0 M6: daily snapshot prune cron (paired with health check). add_action( 'wpdo_snapshot_prune_daily', array( 'TMDO_Snapshot_Pruner', 'cron_run' ) ); // v2.5.0 M13: daily FSM automator cron (opt-in). add_action( 'wpdo_fsm_automator_run', array( 'TMDO_FSM_Automator', 'run' ) ); // v2.6.2: daily site-wide EAV health metrics snapshot. TMDO_Site_Metrics_Collector::register(); // Add 'monthly' cron interval (WP only ships hourly/twicedaily/daily). add_filter( 'cron_schedules', static function ( $schedules ) { if ( ! isset( $schedules['monthly'] ) ) { $schedules['monthly'] = array( 'interval' => 30 * DAY_IN_SECONDS, 'display' => __( 'Once Monthly', '2meet-data-optimizer' ), ); } return $schedules; } ); // Admin notice when DB grows > 10% MoM. if ( is_admin() ) { add_action( 'admin_notices', array( $this, 'render_health_alert_notice' ) ); } // Zone B/D: Listing stats integration(已搬到 2meet-data-optimizer-hivepress-addon)。 if ( class_exists( 'TMDO_Listing_Stats' ) ) { TMDO_Listing_Stats::register_hooks(); } } /** * Schedule cron events if not already scheduled. */ private function schedule_cron(): void { if ( ! wp_next_scheduled( 'wpdo_warm_cleanup' ) ) { wp_schedule_event( time(), 'hourly', 'wpdo_warm_cleanup' ); } // v2.10.3: Post shadow_read verifier — only schedule when post mode // is shadow_read (auto-cleared in mode_changed listener below). if ( class_exists( 'TMDO_Post_Shadow_Verifier' ) && class_exists( 'TMDO_Mode_Manager' ) && 'shadow_read' === TMDO_Mode_Manager::get( 'post' ) && ! wp_next_scheduled( TMDO_Post_Shadow_Verifier::CRON_HOOK ) ) { wp_schedule_event( time() + HOUR_IN_SECONDS, 'hourly', TMDO_Post_Shadow_Verifier::CRON_HOOK ); } // v2.12.5: Term + Comment shadow verifier — schedule when EITHER term // or comment mode is shadow_read. if ( class_exists( 'TMDO_Term_Comment_Shadow_Verifier' ) && class_exists( 'TMDO_Mode_Manager' ) && ( 'shadow_read' === TMDO_Mode_Manager::get( 'term' ) || 'shadow_read' === TMDO_Mode_Manager::get( 'comment' ) ) && ! wp_next_scheduled( TMDO_Term_Comment_Shadow_Verifier::CRON_HOOK ) ) { wp_schedule_event( time() + HOUR_IN_SECONDS, 'hourly', TMDO_Term_Comment_Shadow_Verifier::CRON_HOOK ); } if ( ! wp_next_scheduled( 'wpdo_archive_sweep' ) ) { wp_schedule_event( time(), 'daily', 'wpdo_archive_sweep' ); } // Daily errors GC — keep 90 days, prevents wpdo_errors growing unbounded. if ( ! wp_next_scheduled( 'wpdo_errors_gc' ) ) { wp_schedule_event( time() + HOUR_IN_SECONDS, 'daily', 'wpdo_errors_gc' ); } // Monthly health snapshot — auto-runs first of each month. if ( ! wp_next_scheduled( 'wpdo_health_snapshot_monthly' ) ) { // Schedule at 03:00 on the 1st of next month. $next = strtotime( 'first day of next month 03:00' ); wp_schedule_event( $next ?: ( time() + 30 * DAY_IN_SECONDS ), 'monthly', 'wpdo_health_snapshot_monthly' ); } // v2.3.0 M6: daily health probe at 03:30 UTC (avoids hourly warm cleanup). if ( ! wp_next_scheduled( 'wpdo_daily_health_check' ) ) { $tomorrow_330 = strtotime( 'tomorrow 03:30 UTC' ); wp_schedule_event( $tomorrow_330 ?: ( time() + DAY_IN_SECONDS ), 'daily', 'wpdo_daily_health_check' ); } // v2.3.0 M6: daily snapshot prune at 04:00 UTC. if ( ! wp_next_scheduled( 'wpdo_snapshot_prune_daily' ) ) { $tomorrow_400 = strtotime( 'tomorrow 04:00 UTC' ); wp_schedule_event( $tomorrow_400 ?: ( time() + DAY_IN_SECONDS ), 'daily', 'wpdo_snapshot_prune_daily' ); } // v2.5.0 M13: FSM Automator daily cron at 04:30 UTC (after prune). if ( ! wp_next_scheduled( 'wpdo_fsm_automator_run' ) ) { $tomorrow_430 = strtotime( 'tomorrow 04:30 UTC' ); wp_schedule_event( $tomorrow_430 ?: ( time() + DAY_IN_SECONDS ), 'daily', 'wpdo_fsm_automator_run' ); } // v2.6.2: daily site metrics collection at 05:00 UTC. if ( ! wp_next_scheduled( TMDO_Site_Metrics_Collector::CRON_HOOK ) ) { $tomorrow_500 = strtotime( 'tomorrow 05:00 UTC' ); wp_schedule_event( $tomorrow_500 ?: ( time() + DAY_IN_SECONDS ), 'daily', TMDO_Site_Metrics_Collector::CRON_HOOK ); } // Zone B: Flush view counts to postmeta (hourly)(已搬到 hivepress addon)。 if ( class_exists( 'TMDO_Listing_Stats' ) ) { TMDO_Listing_Stats::schedule_cron(); } } /** * Cron handler: prune wpdo_errors > 90 days. * * @return void */ public function gc_errors(): void { TMDO_Logger::purge( 90 ); } /** * Cron handler: monthly health snapshot via WP_CLI subprocess. * * @return void */ public function monthly_health_snapshot(): void { // Only do anything when WP-CLI is invocable in the current request — the // cron runs via `wp cron event run` typically. Otherwise, dispatch via // shell. if ( ! class_exists( 'TMDO_CLI' ) || ! defined( 'WP_CLI' ) || ! WP_CLI ) { return; } $cli = new TMDO_CLI(); $cli->health_snapshot( array(), array() ); } /** * Render admin notice when DB has grown > 10% since last snapshot. * * Reads `wpdo_health_alert` option set by `health_snapshot` CLI when * threshold breached. Auto-dismissed after fix or next snapshot. * * @return void */ public function render_health_alert_notice(): void { $alert = (string) get_option( 'wpdo_health_alert', '' ); if ( '' === $alert ) { return; } if ( ! TMDO_Capability::current_user_can_admin() ) { return; } // Allow user to dismiss until next snapshot. // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only display. if ( ! empty( $_GET['wpdo_dismiss_health_alert'] ) ) { $nonce = isset( $_GET['_wpnonce'] ) ? sanitize_text_field( wp_unslash( (string) $_GET['_wpnonce'] ) ) : ''; if ( wp_verify_nonce( $nonce, 'wpdo_dismiss_health_alert' ) ) { delete_option( 'wpdo_health_alert' ); return; } } $dismiss_url = wp_nonce_url( add_query_arg( 'wpdo_dismiss_health_alert', '1' ), 'wpdo_dismiss_health_alert' ); ?>

WP Data Optimizer:  

WP Data Optimizer: ' . esc_html__( 'HPCT 匯入頁面', '2meet-data-optimizer' ) . '' ); ?>

$entity_type, 'group_name' => $group_name, 'migrated' => $result['migrated'], 'total' => $result['total'], 'done' => $result['done'], 'status' => $result['status'], ) ); } }