bind_anti_eav_hooks(); } /** Adapter slug (matches wp.org plugin slug). */ public function plugin_slug(): string { return 'hivepress-requests'; } /** Class probed for addon presence. */ public function detection_class(): string { return 'HivePress\\Requests\\Plugin'; } /** Version constant probed for addon presence. */ public function detection_const(): string { return 'HIVEPRESS_REQUESTS_VERSION'; } /** Minimum addon version supported. */ public function minimum_addon_version(): string { return '1.2.0'; } /** * Register hot fields for hp_request post type. * * @param TMDO_Schema_Registry $registry Schema registry singleton. */ public function on_register_fields( TMDO_Schema_Registry $registry ): void { $registry->register_many( $this->plugin_slug(), array( array( 'post_type' => 'hp_request', 'meta_key' => 'hp_drafted', 'zone' => 'hot', 'data_type' => 'tinyint(1) NOT NULL DEFAULT 0', 'column' => 'hp_drafted', 'indexed' => true, ), array( 'post_type' => 'hp_request', 'meta_key' => 'hp_expired_time', 'zone' => 'hot', 'data_type' => 'bigint(20) UNSIGNED NOT NULL DEFAULT 0', 'column' => 'hp_expired_time', 'indexed' => true, ), ) ); } /** * Declare offer shadow table. * * @param TMDO_Custom_Table_Registry $registry Registry singleton. */ public function on_register_custom_tables( TMDO_Custom_Table_Registry $registry ): void { $this->register_custom_table( $registry, array( 'table_name' => self::OFFER_TABLE, 'primary_key' => 'comment_id', 'post_type_link' => 'hp_request', 'expected_columns' => array( 'comment_id' => 'BIGINT UNSIGNED NOT NULL PRIMARY KEY', 'request_id' => 'BIGINT UNSIGNED NOT NULL', 'bidder_id' => 'BIGINT UNSIGNED NOT NULL', 'approved' => 'TINYINT(1) NOT NULL DEFAULT 0', 'created_at' => 'DATETIME NOT NULL', ), 'indexes' => array( 'idx_request_approved' => '(request_id, approved)', 'idx_bidder' => '(bidder_id)', ), 'doctor_callback' => array( $this, 'doctor_check' ), ) ); } /** * Mirror new hp_offer comments into the shadow table. */ public function on_register_event_hooks(): void { add_action( 'wp_insert_comment', array( $this, 'mirror_offer_insert' ), 10, 2 ); add_action( 'edit_comment', array( $this, 'mirror_offer_update' ), 10, 2 ); add_action( 'deleted_comment', array( $this, 'mirror_offer_delete' ), 10, 2 ); } /** * Insert a hp_offer mirror row. * * @param int $comment_id Comment ID. * @param object|\WP_Comment $comment Comment object. */ public function mirror_offer_insert( int $comment_id, $comment ): void { if ( ! is_object( $comment ) || ( $comment->comment_type ?? '' ) !== self::OFFER_COMMENT_TYPE ) { return; } global $wpdb; $wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared $wpdb->prepare( 'INSERT INTO `' . $wpdb->prefix . self::OFFER_TABLE . '` (comment_id, request_id, bidder_id, approved, created_at) VALUES (%d, %d, %d, %d, %s) ON DUPLICATE KEY UPDATE approved = VALUES(approved)', $comment_id, (int) ( $comment->comment_post_ID ?? 0 ), (int) ( $comment->user_id ?? 0 ), (int) ( $comment->comment_approved ?? 0 ), $comment->comment_date ?? current_time( 'mysql' ) ) ); } /** * Update mirror row when comment is edited (approval flip is the hot path). * * @param int $comment_id Comment ID. * @param array $data Comment data array passed by edit_comment. */ public function mirror_offer_update( int $comment_id, $data ): void { unset( $data ); global $wpdb; // Re-fetch comment object for accurate state then re-mirror via insert path. if ( function_exists( 'get_comment' ) ) { $comment = get_comment( $comment_id ); if ( $comment ) { $this->mirror_offer_insert( $comment_id, $comment ); } } unset( $wpdb ); } /** * Remove mirror row on hp_offer deletion. * * @param int $comment_id Comment ID. * @param object|\WP_Comment $comment Comment object. */ public function mirror_offer_delete( int $comment_id, $comment ): void { if ( ! is_object( $comment ) || ( $comment->comment_type ?? '' ) !== self::OFFER_COMMENT_TYPE ) { return; } global $wpdb; $wpdb->delete( // phpcs:ignore WordPress.DB.DirectDatabaseQuery $wpdb->prefix . self::OFFER_TABLE, array( 'comment_id' => $comment_id ), array( '%d' ) ); } /** * Doctor probe — request hot table + offer shadow table. * * @return array{ok:bool, message:string, details?:array} */ public function doctor_check(): array { global $wpdb; if ( ! isset( $wpdb ) || ! is_object( $wpdb ) ) { return array( 'ok' => false, 'message' => 'wpdb global not available', ); } $details = array(); $ok = true; foreach ( array( 'hp_request_hot' => $wpdb->prefix . 'wpdo_hot_hp_request', 'hp_offer_shadow' => $wpdb->prefix . self::OFFER_TABLE, ) as $key => $table ) { try { $exists = (bool) $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) ); $details[ $key ] = array( 'table' => $table, 'exists' => $exists, ); if ( ! $exists ) { $ok = false; } } catch ( \Throwable $e ) { $details[ $key ] = array( 'table' => $table, 'error' => $e->getMessage(), ); $ok = false; } } return array( 'ok' => $ok, 'message' => $ok ? 'hivepress-requests: tables OK' : 'hivepress-requests: one or more tables missing', 'details' => $details, ); } /** * 8-D self-score. */ public function suitability_score(): array { return $this->compose_score( array() ); } /** * FSM modules this adapter contributes. * * @return array */ public function migrations(): array { return array( 'hot_hp_request', 'comment_hp_offer', ); } } } // end if ( ! class_exists )