'hp_listing', 'vendor' => 'hp_vendor', ); /** * Constructor — bind WPDO core hooks via the inherited trait sugar. * * The bootstrap calls `on_register_query_hooks()` / `on_register_event_hooks()` * directly after instantiation, but field/table registration goes * through WordPress's action dispatch via this trait helper. */ public function __construct() { $this->bind_anti_eav_hooks(); } // ── Adapter identity ─────────────────────────────────────────────── /** * Stable adapter slug (matches the hivepress core wp.org slug). */ public function plugin_slug(): string { return 'hivepress'; } /** * Class probed for HivePress core presence. */ public function detection_class(): string { return 'HivePress\\Core'; } /** * Version constant probed for HivePress core presence. */ public function detection_const(): string { return 'HIVEPRESS_VERSION'; } /** * Minimum HivePress core version this adapter supports. * * Version 1.7.0 introduced the `_alias` / `_external` field convention * this adapter relies on. Older versions store fields differently and * would require fallback paths not implemented here. */ public function minimum_addon_version(): string { return '1.7.0'; } // ── Field registration ───────────────────────────────────────────── /** * Register Listing/Vendor zone field mappings. * * Fired on the `wpdo_register_fields` action via the trait's * `bind_anti_eav_hooks()`. Schema_Registry has internal dedup so * re-firing on init:1 is safe. * * @param TMDO_Schema_Registry $registry Schema registry singleton. */ public function on_register_fields( TMDO_Schema_Registry $registry ): void { // ── Cold blobs (display-only) ── $registry->register_many( $this->plugin_slug(), array( // Vendor cold blob — image attachment id (display-only). array( 'post_type' => 'hp_vendor', 'meta_key' => 'hp_image', 'zone' => 'cold', 'cache_group' => 'wpdo_cold_hp_vendor', 'cache_ttl' => HOUR_IN_SECONDS, ), // Listing cold blob — gallery (display-only). array( 'post_type' => 'hp_listing', 'meta_key' => 'hp_images', 'zone' => 'cold', 'cache_group' => 'wpdo_cold_hp_listing', 'cache_ttl' => HOUR_IN_SECONDS, ), ) ); } // ── Query optimization (preserved from legacy class-tmdo-hivepress.php) ── /** * Bind HivePress search query hooks at priority 20 (after HP setup). * * Not while core's post router serves HivePress searches from the * entity flat tables: two routers would claim the same clauses. */ public function on_register_query_hooks(): void { if ( class_exists( 'TMDO_Post_Query_Router' ) && TMDO_Post_Query_Router::is_router_active() ) { return; } foreach ( self::HOT_MODELS as $model => $post_type ) { unset( $post_type ); // Bound below by string-interpolated $model only. add_action( "hivepress/v1/models/{$model}/query", array( $this, 'optimize_query' ), 20, 1 ); add_action( "hivepress/v1/models/{$model}/search", array( $this, 'optimize_search' ), 20, 2 ); } } /** * Optimize a HivePress model query by extracting hot-zone meta_query clauses. * * Extracted clauses are stashed on the WP_Query as `wpdo_hot_clauses`; * `TMDO_Query_Router` rewrites them into flat-column JOINs at * `posts_join` time. * * @param \WP_Query $query The WP_Query object. */ public function optimize_query( \WP_Query $query ): void { $post_types = (array) $query->get( 'post_type' ); if ( empty( $post_types ) ) { return; } $existing_hot = $query->get( 'wpdo_hot_clauses' ); if ( ! empty( $existing_hot ) ) { return; } $meta_query = (array) $query->get( 'meta_query' ); if ( empty( $meta_query ) ) { return; } $registry = TMDO_Schema_Registry::instance(); $hot_clauses = array(); $remaining = array(); foreach ( $meta_query as $k => $clause ) { if ( 'relation' === $k || ! is_array( $clause ) || ! isset( $clause['key'] ) ) { $remaining[ $k ] = $clause; continue; } $matched = false; foreach ( $post_types as $pt ) { $field = $registry->get_field( $pt, $clause['key'] ); if ( $field && 'hot' === $field['zone'] ) { $module = 'hot_' . sanitize_key( $pt ); if ( class_exists( 'TMDO_Feature_Flags' ) && ! TMDO_Feature_Flags::is_query_active( $module ) ) { continue; } $hot_clauses[ $pt ][] = array( 'column' => $field['column'], 'value' => $clause['value'] ?? '', 'compare' => strtoupper( trim( $clause['compare'] ?? '=' ) ), 'type' => strtoupper( trim( $clause['type'] ?? 'CHAR' ) ), ); $matched = true; break; } } if ( ! $matched ) { $remaining[ $k ] = $clause; } } if ( empty( $hot_clauses ) ) { return; } if ( isset( $meta_query['relation'] ) && ! isset( $remaining['relation'] ) ) { $remaining['relation'] = $meta_query['relation']; } $query->set( 'meta_query', $remaining ); $query->set( 'wpdo_hot_clauses', $hot_clauses ); } /** * Optimize a HivePress search query (delegates to optimize_query). * * Bound to `hivepress/v1/models/{model}/search` which passes attribute * fields as second arg; we ignore them (extracted via meta_query). * * @param \WP_Query $query The WP_Query object. * @param array $attribute_fields HP attribute field objects (unused). */ public function optimize_search( \WP_Query $query, array $attribute_fields ): void { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter unset( $attribute_fields ); $this->optimize_query( $query ); } // ── Doctor / score / migrations ──────────────────────────────────── /** * 8-dimension self-score for the core adapter. * * Default 1.0 across the board because: * - D1: only TMDO_API + Schema_Registry calls (no direct *_meta()) * - D2: no hardcoded wp_postmeta literals * - D3: hot tables for both Listing + Vendor exist * - D4: every field registered with explicit data_type + indexed flag * - D5: no cross-adapter writes needed (single-adapter scope) * - D6: zero options written; no transients (relies on WPDO core caching) * - D7: no queries against other adapters' tables * - D8: hot columns indexed on every search-relevant field */ public function suitability_score(): array { return $this->compose_score( array( // D5 is N/A for a single-adapter; report 1.0 (no cross-write needed). 'd5_hook_bus' => 1.0, ) ); } } } // end if ( ! class_exists )