= 100 * * All hp_* and _hp_* meta keys are intercepted. Any other meta keys are * left in the meta_query to be handled by wp_postmeta as usual. */ class TMDO_Listing_Meta_Query extends TMDO_Query_Interceptor_Base { /** * Returns the module identifier. * * @return string Module name. */ protected function get_module(): string { return 'listing_meta'; } /** * Returns the post types this interceptor handles. * * @return string[] Post type slugs. */ protected function get_post_types(): array { return array( 'hp_listing' ); } /** * Returns the custom table name. * * @return string Table name without prefix. */ protected function get_table(): string { return 'hpct_listing_meta'; } /** * Returns the join column for this KV table. * * @return string Join column name. */ protected function get_join_column(): string { return 'listing_id'; } /** * Not used — this class overrides pre_get_posts directly. * * @return array Empty array. */ protected function get_meta_key_map(): array { return array(); } // ── Override pre_get_posts for KV-table pattern ─────────────────────── /** * Intercepts pre_get_posts to handle KV-table meta_query conditions. * * @param \WP_Query $query The WP_Query object. * @return void */ public function pre_get_posts( \WP_Query $query ): void { if ( ! $this->should_intercept( $query ) ) { return; } $raw_meta_query = (array) $query->get( 'meta_query' ); if ( empty( $raw_meta_query ) ) { return; } $our_clauses = array(); $remaining = array(); foreach ( $raw_meta_query as $k => $clause ) { if ( 'relation' === $k || ! is_array( $clause ) || ! isset( $clause['key'] ) ) { $remaining[ $k ] = $clause; continue; } if ( $this->is_hp_key( $clause['key'] ) ) { $our_clauses[] = array( 'meta_key' => $clause['key'], 'value' => $clause['value'] ?? '', 'compare' => strtoupper( trim( $clause['compare'] ?? '=' ) ), 'type' => strtoupper( trim( $clause['type'] ?? 'CHAR' ) ), ); } else { $remaining[ $k ] = $clause; } } if ( empty( $our_clauses ) ) { return; } if ( isset( $raw_meta_query['relation'] ) && ! isset( $remaining['relation'] ) ) { $remaining['relation'] = $raw_meta_query['relation']; } $query->set( 'meta_query', $remaining ); $query->set( $this->query_var(), $our_clauses ); } // ── Override posts_join for KV-table: one JOIN alias per condition ───── /** * Appends LEFT JOINs for each KV-table meta_query condition. * * @param string $join Current JOIN SQL. * @param \WP_Query $query The WP_Query object. * @return string Modified JOIN SQL. */ public function posts_join( ?string $join, \WP_Query $query ): string { $join = (string) ( $join ?? '' ); $clauses = $this->get_clauses( $query ); if ( empty( $clauses ) ) { return $join; } global $wpdb; $table = TMDO_DB::table( $this->get_table() ); $join_col = esc_sql( $this->get_join_column() ); foreach ( $clauses as $i => $clause ) { $alias = $this->alias_for( $i ); $meta_key = $clause['meta_key']; if ( false === strpos( $join, "`{$alias}`" ) ) { // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $table from TMDO_DB::table(); $alias and $join_col are sanitize_key()-validated. $join .= $wpdb->prepare( " LEFT JOIN `{$table}` AS `{$alias}`" . " ON (`{$wpdb->posts}`.`ID` = `{$alias}`.`{$join_col}`" . " AND `{$alias}`.`meta_key` = %s)", $meta_key ); // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared } } return $join; } // ── Override posts_where for KV-table ───────────────────────────────── /** * Appends WHERE conditions for KV-table meta_value comparisons. * * @param string $where Current WHERE SQL. * @param \WP_Query $query The WP_Query object. * @return string Modified WHERE SQL. */ public function posts_where( ?string $where, \WP_Query $query ): string { $where = (string) ( $where ?? '' ); $clauses = $this->get_clauses( $query ); if ( empty( $clauses ) ) { return $where; } foreach ( $clauses as $i => $clause ) { $alias = $this->alias_for( $i ); $col = "`{$alias}`.`meta_value`"; $compare = $this->sanitize_compare( $clause['compare'] ); $type = $clause['type']; $value = $clause['value']; $where .= $this->build_condition( $col, $compare, $type, $value ); } return $where; } // ── Override posts_groupby ───────────────────────────────────────────── /** * Ensures GROUP BY is set when KV-table clauses are active. * * @param string $groupby Current GROUP BY SQL. * @param \WP_Query $query The WP_Query object. * @return string Modified GROUP BY SQL. */ public function posts_groupby( ?string $groupby, \WP_Query $query ): string { $groupby = (string) ( $groupby ?? '' ); $clauses = $this->get_clauses( $query ); if ( empty( $clauses ) ) { return $groupby; } global $wpdb; if ( '' === trim( $groupby ) ) { $groupby = "`{$wpdb->posts}`.`ID`"; } return $groupby; } // ── Helpers ─────────────────────────────────────────────────────────── /** * Generates a unique SQL alias for a KV-table JOIN at a given index. * * @param int $index Zero-based clause index. * @return string SQL alias string. */ private function alias_for( int $index ): string { return "wpdo_lm_{$index}"; } /** * Checks whether a meta key belongs to HivePress (hp_ or _hp_ prefix). * * @param string $meta_key Meta key to check. * @return bool True if the key is an HP key. */ private function is_hp_key( string $meta_key ): bool { return str_starts_with( $meta_key, 'hp_' ) || str_starts_with( $meta_key, '_hp_' ); } }