From e443f20edbd7595b49e423d32af4b663ae73d606 Mon Sep 17 00:00:00 2001
From: wpdev
Date: Fri, 25 Sep 2026 22:28:54 +0800
Subject: [PATCH 01/12] fix(2meet-data-optimizer-hivepress-addon): map bookings
hot field to hp_quantity
- hivepress-bookings has no guests field; the places booked are stored as
hp_quantity (only when "Allow multiple places" is enabled)
- Verified against hivepress-bookings 1.5.5: start_time / end_time were
already correct and stay unchanged
Verify: vendor/bin/phpunit --filter test_on_register_fields_uses_real_booking_meta_keys;
addon unit 155 OK, integration 57 OK
PR: PR-009
---
.../class-tmdo-hivepress-bookings-adapter.php | 7 ++++---
tests/unit/BookingsAdapterTest.php | 12 ++++++++++++
2 files changed, 16 insertions(+), 3 deletions(-)
diff --git a/includes/hivepress/adapters/class-tmdo-hivepress-bookings-adapter.php b/includes/hivepress/adapters/class-tmdo-hivepress-bookings-adapter.php
index 52e1967..60cfaa1 100644
--- a/includes/hivepress/adapters/class-tmdo-hivepress-bookings-adapter.php
+++ b/includes/hivepress/adapters/class-tmdo-hivepress-bookings-adapter.php
@@ -10,7 +10,8 @@
*
* start_time / end_time → unix timestamp range queries (hot)
* status → enum filter (hot)
- * guests → numeric count (hot for slot-availability)
+ * quantity → places booked, hp_quantity (hot for slot-availability;
+ * only written when "Allow multiple places" is on)
* listing → post_parent (already a posts column)
*
* Hot-zone target: `wp_wpdo_hot_hp_booking` with covering indexes on
@@ -91,10 +92,10 @@ if ( ! class_exists( 'TMDO_HivePress_Bookings_Adapter' ) ) {
),
array(
'post_type' => 'hp_booking',
- 'meta_key' => 'hp_guests',
+ 'meta_key' => 'hp_quantity',
'zone' => 'hot',
'data_type' => 'int(11) NOT NULL DEFAULT 0',
- 'column' => 'hp_guests',
+ 'column' => 'hp_quantity',
'indexed' => false,
),
)
diff --git a/tests/unit/BookingsAdapterTest.php b/tests/unit/BookingsAdapterTest.php
index 155603d..b0bd684 100644
--- a/tests/unit/BookingsAdapterTest.php
+++ b/tests/unit/BookingsAdapterTest.php
@@ -46,6 +46,18 @@ class HivePressBookingsAdapterTest extends TestCase {
$this->assertTrue( (bool) $end['indexed'] );
}
+ public function test_on_register_fields_uses_real_booking_meta_keys(): void {
+ $registry = WPDO_Schema_Registry::instance();
+ $this->adapter->on_register_fields( $registry );
+
+ // hivepress-bookings has no guests field; places are stored as hp_quantity.
+ $this->assertNull( $registry->get_field( 'hp_booking', 'hp_guests' ) );
+
+ $quantity = $registry->get_field( 'hp_booking', 'hp_quantity' );
+ $this->assertNotNull( $quantity );
+ $this->assertSame( 'hot', $quantity['zone'] );
+ }
+
public function test_score_reflects_unverified_perf_dimension(): void {
$score = $this->adapter->suitability_score();
// D8 reduced to 0.9 because schema is best-effort until addon installs.
From d3ddbb893345643854b8c86fd0cf729e20b233b5 Mon Sep 17 00:00:00 2001
From: wpdev
Date: Fri, 25 Sep 2026 22:35:30 +0800
Subject: [PATCH 02/12] fix(2meet-data-optimizer-hivepress-addon): register
enabled social links as hp_{name}
- hivepress-social-links stores each link as hp_{name} (hp_facebook, not
hp_facebook_url) and lets admins enable links separately for listings
and vendors; the hardcoded 7 *_url vendor keys never matched real meta
- Read hp_listing_social_links / hp_vendor_social_links and register each
enabled link on its own post type, so custom links (e.g. line_oa) work
- dev34 check: meta holds hp_instagram / hp_website, and zero *_url keys
Verify: vendor/bin/phpunit --filter HivePressSocialLinksAdapterTest;
addon unit 156 OK, integration 57 OK
PR: PR-010
---
...ss-tmdo-hivepress-social-links-adapter.php | 69 ++++++++++---------
tests/unit/SocialLinksAdapterTest.php | 38 +++++++---
2 files changed, 62 insertions(+), 45 deletions(-)
diff --git a/includes/hivepress/adapters/class-tmdo-hivepress-social-links-adapter.php b/includes/hivepress/adapters/class-tmdo-hivepress-social-links-adapter.php
index 7636de1..225bef5 100644
--- a/includes/hivepress/adapters/class-tmdo-hivepress-social-links-adapter.php
+++ b/includes/hivepress/adapters/class-tmdo-hivepress-social-links-adapter.php
@@ -2,13 +2,13 @@
/**
* Social Links adapter — `hivepress-social-links`.
*
- * Adds social URL fields to vendor profiles (Facebook, Instagram, Twitter,
- * LinkedIn, YouTube, etc.). Stored in postmeta as `hp_social_*` keys.
+ * The addon adds one attribute per social link the admin enables, separately
+ * for listings (`hp_listing_social_links`) and vendors (`hp_vendor_social_links`).
+ * Each attribute is stored in postmeta as `hp_{name}` (e.g. `hp_facebook`).
*
* Display-only — never used in search/filter — so the right zone is Cold
- * (object cache + JSON blob fallback). Listing page reads ~7 social URLs;
- * caching them as a single blob keyed on vendor_id eliminates 7 postmeta
- * lookups per render.
+ * (object cache + JSON blob fallback): one blob per post replaces one
+ * postmeta lookup per link on every render.
*
* @package WP_Data_Optimizer
* @since 3.0.0
@@ -27,25 +27,6 @@ if ( ! class_exists( 'TMDO_HivePress_Social_Links_Adapter' ) ) {
use TMDO_HivePress_Adapter_Trait;
- /**
- * Social URL meta keys owned by this addon.
- *
- * Sources (HivePress 1.0.3 default field set):
- * facebook_url, instagram_url, twitter_url, linkedin_url,
- * youtube_url, telegram_url, whatsapp_url
- *
- * @var array
- */
- private const SOCIAL_KEYS = array(
- 'hp_facebook_url',
- 'hp_instagram_url',
- 'hp_twitter_url',
- 'hp_linkedin_url',
- 'hp_youtube_url',
- 'hp_telegram_url',
- 'hp_whatsapp_url',
- );
-
/**
* Constructor — bind register hooks via inherited trait.
*/
@@ -74,24 +55,44 @@ if ( ! class_exists( 'TMDO_HivePress_Social_Links_Adapter' ) ) {
}
/**
- * Register vendor social URL fields to Cold zone.
+ * Register the enabled social link fields to the Cold zone.
*
* @param TMDO_Schema_Registry $registry Schema registry singleton.
*/
public function on_register_fields( TMDO_Schema_Registry $registry ): void {
$fields = array();
- foreach ( self::SOCIAL_KEYS as $key ) {
- $fields[] = array(
- 'post_type' => 'hp_vendor',
- 'meta_key' => $key,
- 'zone' => 'cold',
- 'cache_group' => 'wpdo_cold_hp_vendor',
- 'cache_ttl' => HOUR_IN_SECONDS,
- );
+ foreach ( $this->enabled_meta_keys() as $post_type => $keys ) {
+ foreach ( $keys as $key ) {
+ $fields[] = array(
+ 'post_type' => $post_type,
+ 'meta_key' => $key,
+ 'zone' => 'cold',
+ 'cache_group' => 'wpdo_cold_' . $post_type,
+ 'cache_ttl' => HOUR_IN_SECONDS,
+ );
+ }
}
$registry->register_many( $this->plugin_slug(), $fields );
}
+ /**
+ * Meta keys of the social links enabled in the addon settings.
+ *
+ * @return array> post_type => meta keys.
+ */
+ private function enabled_meta_keys(): array {
+ $keys = array();
+ foreach ( array( 'listing', 'vendor' ) as $model ) {
+ foreach ( (array) get_option( 'hp_' . $model . '_social_links', array() ) as $name ) {
+ $name = sanitize_key( (string) $name );
+ if ( '' !== $name ) {
+ $keys[ 'hp_' . $model ][] = 'hp_' . $name;
+ }
+ }
+ }
+ return $keys;
+ }
+
/**
* Doctor probe — cold zone uses cache; verify cache layer is reachable.
*
@@ -100,7 +101,7 @@ if ( ! class_exists( 'TMDO_HivePress_Social_Links_Adapter' ) ) {
public function doctor_check(): array {
return array(
'ok' => true,
- 'message' => sprintf( 'hivepress-social-links: %d cold fields registered', count( self::SOCIAL_KEYS ) ),
+ 'message' => sprintf( 'hivepress-social-links: %d cold fields registered', array_sum( array_map( 'count', $this->enabled_meta_keys() ) ) ),
);
}
diff --git a/tests/unit/SocialLinksAdapterTest.php b/tests/unit/SocialLinksAdapterTest.php
index b14b543..eb0ce82 100644
--- a/tests/unit/SocialLinksAdapterTest.php
+++ b/tests/unit/SocialLinksAdapterTest.php
@@ -18,6 +18,8 @@ class HivePressSocialLinksAdapterTest extends TestCase {
$prop->setAccessible( true );
$prop->setValue( null, null );
+ unset( $GLOBALS['_wp_options']['hp_vendor_social_links'], $GLOBALS['_wp_options']['hp_listing_social_links'] );
+
$this->adapter = new WPDO_HivePress_Social_Links_Adapter();
}
@@ -30,25 +32,39 @@ class HivePressSocialLinksAdapterTest extends TestCase {
$this->assertSame( 'HivePress\\SocialLinks\\Plugin', $this->adapter->detection_class() );
}
- public function test_on_register_fields_registers_seven_cold_fields(): void {
+ public function test_registers_enabled_links_per_model(): void {
+ // hivepress-social-links stores each enabled link as the attribute hp_{name},
+ // with separate admin selections for listings and vendors.
+ update_option( 'hp_vendor_social_links', array( 'facebook', 'website' ) );
+ update_option( 'hp_listing_social_links', array( 'instagram' ) );
+
$registry = WPDO_Schema_Registry::instance();
$this->adapter->on_register_fields( $registry );
- // All seven social URL fields should land in cold zone on hp_vendor.
- $facebook = $registry->get_field( 'hp_vendor', 'hp_facebook_url' );
- $this->assertSame( 'cold', $facebook['zone'] );
+ $this->assertSame( 'cold', $registry->get_field( 'hp_vendor', 'hp_facebook' )['zone'] );
+ $this->assertSame( 'cold', $registry->get_field( 'hp_vendor', 'hp_website' )['zone'] );
+ $this->assertSame( 'cold', $registry->get_field( 'hp_listing', 'hp_instagram' )['zone'] );
- $instagram = $registry->get_field( 'hp_vendor', 'hp_instagram_url' );
- $this->assertSame( 'cold', $instagram['zone'] );
-
- $whatsapp = $registry->get_field( 'hp_vendor', 'hp_whatsapp_url' );
- $this->assertSame( 'cold', $whatsapp['zone'] );
+ $this->assertNull( $registry->get_field( 'hp_vendor', 'hp_instagram' ) );
+ $this->assertNull( $registry->get_field( 'hp_vendor', 'hp_facebook_url' ) );
}
- public function test_doctor_check_reports_field_count(): void {
+ public function test_registers_nothing_when_no_links_enabled(): void {
+ $registry = WPDO_Schema_Registry::instance();
+ $this->adapter->on_register_fields( $registry );
+
+ $this->assertNull( $registry->get_field( 'hp_vendor', 'hp_facebook' ) );
+ $this->assertNull( $registry->get_field( 'hp_listing', 'hp_facebook' ) );
+ }
+
+ public function test_doctor_check_reports_registered_count(): void {
+ update_option( 'hp_vendor_social_links', array( 'facebook', 'website' ) );
+ update_option( 'hp_listing_social_links', array( 'instagram' ) );
+
$result = $this->adapter->doctor_check();
+
$this->assertTrue( $result['ok'] );
- $this->assertStringContainsString( '7 cold fields', $result['message'] );
+ $this->assertStringContainsString( '3 cold fields', $result['message'] );
}
public function test_score_aggregates_to_perfect_ten(): void {
From f089b0934b24ee3c8f93ce9e4e9a8d710ced9160 Mon Sep 17 00:00:00 2001
From: wpdev
Date: Fri, 25 Sep 2026 22:39:57 +0800
Subject: [PATCH 03/12] fix(2meet-data-optimizer-hivepress-addon): map
marketplace hot field to hp_price
- hivepress-marketplace stores its filterable, sortable listing price as
hp_price; hp_purchase_price never existed (dev34: 588 hp_price rows)
- Drop hp_purchase_count: the addon keeps no per-listing sales counter
- Remove the stale docblock note about the legacy core registration path
Verify: vendor/bin/phpunit --filter test_on_register_fields_registers_hot_hp_price;
addon unit 156 OK, integration 57 OK
PR: PR-011
---
...ass-tmdo-hivepress-marketplace-adapter.php | 24 ++++++-------------
tests/unit/MarketplaceAdapterTest.php | 10 ++++----
2 files changed, 13 insertions(+), 21 deletions(-)
diff --git a/includes/hivepress/adapters/class-tmdo-hivepress-marketplace-adapter.php b/includes/hivepress/adapters/class-tmdo-hivepress-marketplace-adapter.php
index 776219f..f5042b2 100644
--- a/includes/hivepress/adapters/class-tmdo-hivepress-marketplace-adapter.php
+++ b/includes/hivepress/adapters/class-tmdo-hivepress-marketplace-adapter.php
@@ -3,16 +3,12 @@
* Marketplace adapter — `hivepress-marketplace` (DETECT-ONLY in v3.0.0).
*
* The marketplace addon turns listings into purchasable items, integrating
- * with WooCommerce. It adds:
+ * with WooCommerce. Its filterable, sortable listing attribute `price` is
+ * stored as `hp_price` → decimal(10,2), used in price-range filters (HOT).
*
- * hp_purchase_price → decimal(10,2), used in price-range filters (HOT)
- * hp_purchase_count → counter (WARM zone, low-criticality)
- *
- * Note: `hp_purchase_price` is also conditionally registered by the existing
- * legacy hivepress integration (Sprint 1 left that path) — the dedup in
- * Schema_Registry is provider+key based, so registering again under the
- * `hivepress-marketplace` provider is safe and lets `wp wpdo doctor` show
- * the right ownership.
+ * Its other listing attributes (price tiers / extras / discounts, quantity,
+ * subscription period, …) are not search filters, and it keeps no per-listing
+ * sales counter.
*
* Detect-only: not installed in dev environment. Schema reserved for the
* day the addon ships.
@@ -72,18 +68,12 @@ if ( ! class_exists( 'TMDO_HivePress_Marketplace_Adapter' ) ) {
array(
array(
'post_type' => 'hp_listing',
- 'meta_key' => 'hp_purchase_price',
+ 'meta_key' => 'hp_price',
'zone' => 'hot',
'data_type' => 'decimal(10,2) DEFAULT NULL',
- 'column' => 'hp_purchase_price',
+ 'column' => 'hp_price',
'indexed' => true,
),
- array(
- 'post_type' => 'hp_listing',
- 'meta_key' => 'hp_purchase_count',
- 'zone' => 'warm',
- 'ttl' => null,
- ),
)
);
}
diff --git a/tests/unit/MarketplaceAdapterTest.php b/tests/unit/MarketplaceAdapterTest.php
index e1d592c..55e93de 100644
--- a/tests/unit/MarketplaceAdapterTest.php
+++ b/tests/unit/MarketplaceAdapterTest.php
@@ -33,17 +33,19 @@ class HivePressMarketplaceAdapterTest extends TestCase {
$this->assertSame( 'HivePress\\Marketplace\\Plugin', $this->adapter->detection_class() );
}
- public function test_on_register_fields_registers_hot_price_and_warm_counter(): void {
+ public function test_on_register_fields_registers_hot_hp_price(): void {
$registry = WPDO_Schema_Registry::instance();
$this->adapter->on_register_fields( $registry );
- $price = $registry->get_field( 'hp_listing', 'hp_purchase_price' );
+ // Marketplace's filterable, sortable listing "price" attribute is stored as hp_price.
+ $price = $registry->get_field( 'hp_listing', 'hp_price' );
$this->assertSame( 'hot', $price['zone'] );
$this->assertTrue( (bool) $price['indexed'] );
$this->assertStringContainsString( 'decimal', $price['data_type'] );
- $count = $registry->get_field( 'hp_listing', 'hp_purchase_count' );
- $this->assertSame( 'warm', $count['zone'] );
+ // Neither key exists in hivepress-marketplace.
+ $this->assertNull( $registry->get_field( 'hp_listing', 'hp_purchase_price' ) );
+ $this->assertNull( $registry->get_field( 'hp_listing', 'hp_purchase_count' ) );
}
public function test_score_aggregates_to_nine_eight_eight(): void {
From 57772101849961da1f600721a56c7c47cd4634cb Mon Sep 17 00:00:00 2001
From: wpdev
Date: Fri, 25 Sep 2026 22:58:34 +0800
Subject: [PATCH 04/12] refactor(2meet-data-optimizer-hivepress-addon): remove
comment mirror writes to never-created tables
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Favorites/Messages adapters reduced to detection only; Requests keeps hot_hp_request fields, drops hp_offer mirror
- Delete TMDO_HivePress_Comment_Router (read path to wpdo_comment_hp_* tables that no installer creates)
- Verify: addon unit 141 OK, integration 52 OK; phpcs 41E/57W → 35E/48W
- PR: PR-013
---
includes/back-compat-aliases.php | 1 -
includes/class-tmdo-hivepress-bootstrap.php | 1 -
...class-tmdo-hivepress-favorites-adapter.php | 181 +--------------
.../class-tmdo-hivepress-messages-adapter.php | 216 +-----------------
.../class-tmdo-hivepress-requests-adapter.php | 173 ++------------
.../class-tmdo-hivepress-reviews-adapter.php | 12 +-
.../class-tmdo-hivepress-bootstrap.php | 9 +-
.../class-tmdo-hivepress-comment-router.php | 166 --------------
.../hivepress/interface-tmdo-hp-adapter.php | 2 +-
tests/bootstrap.php | 1 -
.../integration/HivepressIntegrationTest.php | 136 -----------
tests/integration/bootstrap.php | 1 -
tests/unit/CommentRouterTest.php | 92 --------
tests/unit/FavoritesAdapterTest.php | 71 +-----
tests/unit/MessagesAdapterTest.php | 74 ++----
tests/unit/RequestsAdapterTest.php | 47 +---
tests/unit/WpdbMockTrait.php | 2 +-
17 files changed, 77 insertions(+), 1108 deletions(-)
delete mode 100644 includes/hivepress/class-tmdo-hivepress-comment-router.php
delete mode 100644 tests/unit/CommentRouterTest.php
diff --git a/includes/back-compat-aliases.php b/includes/back-compat-aliases.php
index 6eb79a0..78f487f 100644
--- a/includes/back-compat-aliases.php
+++ b/includes/back-compat-aliases.php
@@ -29,7 +29,6 @@ add_action(
'TMDO_HivePress_Blocks_Adapter' => 'WPDO_HivePress_Blocks_Adapter',
'TMDO_HivePress_Bookings_Adapter' => 'WPDO_HivePress_Bookings_Adapter',
'TMDO_HivePress_Bootstrap' => 'WPDO_HivePress_Bootstrap',
- 'TMDO_HivePress_Comment_Router' => 'WPDO_HivePress_Comment_Router',
'TMDO_HivePress_Conflict_Guard' => 'WPDO_HivePress_Conflict_Guard',
'TMDO_HivePress_Core_Adapter' => 'WPDO_HivePress_Core_Adapter',
'TMDO_HivePress_Cron_Optimizer' => 'WPDO_HivePress_Cron_Optimizer',
diff --git a/includes/class-tmdo-hivepress-bootstrap.php b/includes/class-tmdo-hivepress-bootstrap.php
index 4c3ea9c..c5f9c5c 100644
--- a/includes/class-tmdo-hivepress-bootstrap.php
+++ b/includes/class-tmdo-hivepress-bootstrap.php
@@ -44,7 +44,6 @@ final class TMDO_HP_Bootstrap {
require_once $base . 'includes/hivepress/class-tmdo-hivepress-detector.php';
require_once $base . 'includes/hivepress/class-tmdo-hivepress-conflict-guard.php';
require_once $base . 'includes/hivepress/class-tmdo-hivepress-bootstrap.php';
- require_once $base . 'includes/hivepress/class-tmdo-hivepress-comment-router.php';
require_once $base . 'includes/hivepress/class-tmdo-hivepress-cron-optimizer.php';
// 13 adapters
diff --git a/includes/hivepress/adapters/class-tmdo-hivepress-favorites-adapter.php b/includes/hivepress/adapters/class-tmdo-hivepress-favorites-adapter.php
index 9086044..d87bc84 100644
--- a/includes/hivepress/adapters/class-tmdo-hivepress-favorites-adapter.php
+++ b/includes/hivepress/adapters/class-tmdo-hivepress-favorites-adapter.php
@@ -8,27 +8,9 @@
* - listing → comment_post_ID
* - added_at → comment_date
*
- * Problem: `wp_comments` has no UNIQUE constraint on (user_id, comment_post_ID, comment_type).
- * A double-tap on the favourite button can race and create two duplicate
- * favourite rows. HivePress relies on application-level dedup (SELECT before
- * INSERT) which is not race-safe.
- *
- * Fix: shadow table `wp_wpdo_comment_hp_favorite` with UNIQUE(user_id, listing_id).
- * Adapter mirrors every favorite write into the shadow table; the comment
- * router rewrites "is X favorited by user Y" reads to hit the shadow table
- * (1 indexed lookup vs WP_Comment_Query meta scan).
- *
- * Schema (declared via expected_columns; created by TMDO_Schema_Manager
- * when adapter mode flips to dual_write):
- *
- * CREATE TABLE wp_wpdo_comment_hp_favorite (
- * comment_id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
- * user_id BIGINT UNSIGNED NOT NULL,
- * listing_id BIGINT UNSIGNED NOT NULL,
- * created_at DATETIME NOT NULL,
- * UNIQUE KEY unique_user_listing (user_id, listing_id),
- * KEY idx_listing (listing_id)
- * );
+ * Nothing is stored in comment meta, so there is no EAV surface to flatten:
+ * the adapter only reports detection and doctor status. (It used to mirror
+ * favorites into `wpdo_comment_hp_favorite`, a table nothing ever created.)
*
* @package WP_Data_Optimizer
* @since 3.0.0
@@ -41,18 +23,15 @@ if ( ! defined( 'ABSPATH' ) ) {
if ( ! class_exists( 'TMDO_HivePress_Favorites_Adapter' ) ) {
/**
- * Favorites addon adapter.
+ * Favorites addon adapter (detection and doctor only).
*/
final class TMDO_HivePress_Favorites_Adapter implements TMDO_HivePress_Adapter {
use TMDO_HivePress_Adapter_Trait;
- /** Comment_type owned by this adapter. */
+ /** Comment_type of the Favorite model. */
public const COMMENT_TYPE = 'hp_favorite';
- /** Shadow table name (without wpdb prefix). */
- public const TABLE = 'wpdo_comment_hp_favorite';
-
/**
* Constructor — bind register hooks via inherited trait.
*/
@@ -79,156 +58,6 @@ if ( ! class_exists( 'TMDO_HivePress_Favorites_Adapter' ) ) {
public function minimum_addon_version(): string {
return '1.2.0';
}
-
- /**
- * Declare the shadow table to the Custom_Table_Registry.
- *
- * @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::TABLE,
- 'primary_key' => 'comment_id',
- 'post_type_link' => null,
- 'expected_columns' => array(
- 'comment_id' => 'BIGINT UNSIGNED NOT NULL PRIMARY KEY',
- 'user_id' => 'BIGINT UNSIGNED NOT NULL',
- 'listing_id' => 'BIGINT UNSIGNED NOT NULL',
- 'created_at' => 'DATETIME NOT NULL',
- ),
- 'indexes' => array(
- 'unique_user_listing' => 'UNIQUE (user_id, listing_id)',
- 'idx_listing' => '(listing_id)',
- ),
- 'doctor_callback' => array( $this, 'doctor_check' ),
- )
- );
- }
-
- /**
- * Sync wp_comments → shadow table on insert.
- *
- * Bound during `on_register_event_hooks()`. The comment-router uses
- * the shadow table for "is favorited" lookups; sync keeps it consistent.
- */
- public function on_register_event_hooks(): void {
- add_action( 'wp_insert_comment', array( $this, 'mirror_insert' ), 10, 2 );
- add_action( 'deleted_comment', array( $this, 'mirror_delete' ), 10, 2 );
- }
-
- /**
- * Mirror a newly-inserted hp_favorite into the shadow table.
- *
- * @param int $comment_id Comment ID.
- * @param object|\WP_Comment $comment Comment object.
- */
- public function mirror_insert( int $comment_id, $comment ): void {
- if ( ! is_object( $comment ) || ( $comment->comment_type ?? '' ) !== self::COMMENT_TYPE ) {
- return;
- }
- global $wpdb;
- $user_id = (int) ( $comment->user_id ?? 0 );
- $listing_id = (int) ( $comment->comment_post_ID ?? 0 );
- if ( $user_id <= 0 || $listing_id <= 0 ) {
- return;
- }
- $wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
- $wpdb->prepare(
- 'INSERT IGNORE INTO `' . $wpdb->prefix . self::TABLE . '` (comment_id, user_id, listing_id, created_at) VALUES (%d, %d, %d, %s)',
- $comment_id,
- $user_id,
- $listing_id,
- $comment->comment_date ?? current_time( 'mysql' )
- )
- );
- }
-
- /**
- * Remove the shadow row when a hp_favorite comment is deleted.
- *
- * @param int $comment_id Comment ID.
- * @param object|\WP_Comment $comment Comment object.
- */
- public function mirror_delete( int $comment_id, $comment ): void {
- if ( ! is_object( $comment ) || ( $comment->comment_type ?? '' ) !== self::COMMENT_TYPE ) {
- return;
- }
- global $wpdb;
- $wpdb->delete( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
- $wpdb->prefix . self::TABLE,
- array( 'comment_id' => $comment_id ),
- array( '%d' )
- );
- }
-
- /**
- * Doctor probe — verify shadow table exists.
- *
- * @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',
- );
- }
- $table = $wpdb->prefix . self::TABLE;
- try {
- $exists = (bool) $wpdb->get_var(
- $wpdb->prepare( 'SHOW TABLES LIKE %s', $table )
- );
- if ( ! $exists ) {
- return array(
- 'ok' => false,
- 'message' => sprintf( 'hivepress-favorites: shadow table %s missing', $table ),
- 'details' => array( 'table' => $table ),
- );
- }
- $rows = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$table}`" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
- return array(
- 'ok' => true,
- 'message' => sprintf( 'hivepress-favorites: %s rows=%d', $table, $rows ),
- 'details' => array(
- 'table' => $table,
- 'rows' => $rows,
- ),
- );
- } catch ( \Throwable $e ) {
- return array(
- 'ok' => false,
- 'message' => sprintf( 'hivepress-favorites doctor failed: %s', $e->getMessage() ),
- );
- }
- }
-
- /**
- * 8-D self-score.
- *
- * D1: 1.0 — adapter never calls *_meta()
- * D2: 1.0 — uses wpdb prefix, no hardcoded table literals
- * D3: 1.0 — shadow table covers the model
- * D4: 1.0 — expected_columns + indexes registered
- * D5: 1.0 — listens to wp_insert_comment for cross-write consistency
- * D6: 1.0 — zero options/transients
- * D7: 1.0 — only queries own shadow table
- * D8: 1.0 — UNIQUE(user_id, listing_id) covering index
- */
- public function suitability_score(): array {
- return $this->compose_score( array() );
- }
-
- /**
- * FSM modules this adapter contributes.
- *
- * @return array
- */
- public function migrations(): array {
- return array( 'comment_hp_favorite' );
- }
}
} // end if ( ! class_exists )
diff --git a/includes/hivepress/adapters/class-tmdo-hivepress-messages-adapter.php b/includes/hivepress/adapters/class-tmdo-hivepress-messages-adapter.php
index cc044dc..6fc1d29 100644
--- a/includes/hivepress/adapters/class-tmdo-hivepress-messages-adapter.php
+++ b/includes/hivepress/adapters/class-tmdo-hivepress-messages-adapter.php
@@ -3,28 +3,12 @@
* Messages adapter — `hivepress-messages`.
*
* The Message model extends Comment (`comment_type = hp_message`) and stores
- * the recipient_id in `comment_karma` — a creative re-use of an unused column,
- * but it leaves the field unindexed for "messages received by user N"
- * queries. Result: every dashboard "unread count" badge does an O(N)
- * `WHERE comment_type = 'hp_message' AND comment_karma = N` scan over
- * the entire wp_comments table.
+ * the recipient in `comment_karma`, the sender in `user_id` and the read flag
+ * in `comment_approved`.
*
- * Fix: shadow table `wp_wpdo_comment_hp_message` with proper recipient_id
- * column + index. Adapter mirrors every hp_message write into the shadow,
- * and the comment-router rewrites recipient/unread queries to hit the index.
- *
- * Schema:
- *
- * CREATE TABLE wp_wpdo_comment_hp_message (
- * comment_id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
- * sender_id BIGINT UNSIGNED NOT NULL,
- * recipient_id BIGINT UNSIGNED NOT NULL,
- * listing_id BIGINT UNSIGNED NOT NULL,
- * is_read TINYINT(1) NOT NULL DEFAULT 0,
- * sent_at DATETIME NOT NULL,
- * KEY idx_recipient_unread (recipient_id, is_read),
- * KEY idx_thread (listing_id, sent_at)
- * );
+ * Nothing is stored in comment meta, so there is no EAV surface to flatten:
+ * the adapter only reports detection and doctor status. (It used to mirror
+ * messages into `wpdo_comment_hp_message`, a table nothing ever created.)
*
* @package WP_Data_Optimizer
* @since 3.0.0
@@ -37,18 +21,15 @@ if ( ! defined( 'ABSPATH' ) ) {
if ( ! class_exists( 'TMDO_HivePress_Messages_Adapter' ) ) {
/**
- * Messages addon adapter.
+ * Messages addon adapter (detection and doctor only).
*/
final class TMDO_HivePress_Messages_Adapter implements TMDO_HivePress_Adapter {
use TMDO_HivePress_Adapter_Trait;
- /** Comment_type owned by this adapter. */
+ /** Comment_type of the Message model. */
public const COMMENT_TYPE = 'hp_message';
- /** Shadow table name (without wpdb prefix). */
- public const TABLE = 'wpdo_comment_hp_message';
-
/**
* Constructor — bind register hooks via inherited trait.
*/
@@ -75,189 +56,6 @@ if ( ! class_exists( 'TMDO_HivePress_Messages_Adapter' ) ) {
public function minimum_addon_version(): string {
return '1.4.0';
}
-
- /**
- * Declare the shadow table to the Custom_Table_Registry.
- *
- * @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::TABLE,
- 'primary_key' => 'comment_id',
- 'post_type_link' => null,
- 'expected_columns' => array(
- 'comment_id' => 'BIGINT UNSIGNED NOT NULL PRIMARY KEY',
- 'sender_id' => 'BIGINT UNSIGNED NOT NULL',
- 'recipient_id' => 'BIGINT UNSIGNED NOT NULL',
- 'listing_id' => 'BIGINT UNSIGNED NOT NULL',
- 'is_read' => 'TINYINT(1) NOT NULL DEFAULT 0',
- 'sent_at' => 'DATETIME NOT NULL',
- ),
- 'indexes' => array(
- 'idx_recipient_unread' => '(recipient_id, is_read)',
- 'idx_thread' => '(listing_id, sent_at)',
- ),
- 'doctor_callback' => array( $this, 'doctor_check' ),
- )
- );
- }
-
- /**
- * Bind sync hooks on hp_message comment insert / update / delete.
- *
- * The recipient_id is stored in comment_karma by HivePress; we mirror
- * it into a properly-named indexed column. is_read tracks comment_approved
- * (HP uses 0=unread / 1=read) — same column, mirrored.
- */
- public function on_register_event_hooks(): void {
- add_action( 'wp_insert_comment', array( $this, 'mirror_insert' ), 10, 2 );
- add_action( 'wp_set_comment_status', array( $this, 'mirror_status_change' ), 10, 2 );
- add_action( 'deleted_comment', array( $this, 'mirror_delete' ), 10, 2 );
- }
-
- /**
- * Mirror a new hp_message into the shadow table.
- *
- * @param int $comment_id Comment ID.
- * @param object|\WP_Comment $comment Comment object.
- */
- public function mirror_insert( int $comment_id, $comment ): void {
- if ( ! is_object( $comment ) || ( $comment->comment_type ?? '' ) !== self::COMMENT_TYPE ) {
- return;
- }
- global $wpdb;
- $sender_id = (int) ( $comment->user_id ?? 0 );
- $recipient_id = (int) ( $comment->comment_karma ?? 0 );
- $listing_id = (int) ( $comment->comment_post_ID ?? 0 );
- $is_read = (int) ( $comment->comment_approved ?? 0 );
- if ( $sender_id <= 0 || $recipient_id <= 0 ) {
- // Malformed message — nothing useful to mirror.
- return;
- }
- $wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
- $wpdb->prepare(
- 'INSERT INTO `' . $wpdb->prefix . self::TABLE . '` (comment_id, sender_id, recipient_id, listing_id, is_read, sent_at) VALUES (%d, %d, %d, %d, %d, %s) ON DUPLICATE KEY UPDATE is_read = VALUES(is_read)',
- $comment_id,
- $sender_id,
- $recipient_id,
- $listing_id,
- $is_read,
- $comment->comment_date ?? current_time( 'mysql' )
- )
- );
- }
-
- /**
- * Update is_read when a message is marked read (comment_approved flip).
- *
- * @param int $comment_id Comment ID.
- * @param string $status New status ('approve', 'hold', 'spam', 'trash').
- */
- public function mirror_status_change( int $comment_id, string $status ): void {
- global $wpdb;
- // Look up the comment to confirm it's hp_message before touching.
- if ( ! function_exists( 'get_comment' ) ) {
- return;
- }
- $comment = get_comment( $comment_id );
- if ( ! $comment || ( $comment->comment_type ?? '' ) !== self::COMMENT_TYPE ) {
- return;
- }
- $is_read = ( 'approve' === $status ) ? 1 : 0;
- $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
- $wpdb->prefix . self::TABLE,
- array( 'is_read' => $is_read ),
- array( 'comment_id' => $comment_id ),
- array( '%d' ),
- array( '%d' )
- );
- }
-
- /**
- * Remove shadow row on hp_message deletion.
- *
- * @param int $comment_id Comment ID.
- * @param object|\WP_Comment $comment Comment object.
- */
- public function mirror_delete( int $comment_id, $comment ): void {
- if ( ! is_object( $comment ) || ( $comment->comment_type ?? '' ) !== self::COMMENT_TYPE ) {
- return;
- }
- global $wpdb;
- $wpdb->delete( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
- $wpdb->prefix . self::TABLE,
- array( 'comment_id' => $comment_id ),
- array( '%d' )
- );
- }
-
- /**
- * Doctor probe — verify 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',
- );
- }
- $table = $wpdb->prefix . self::TABLE;
- try {
- $exists = (bool) $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) );
- if ( ! $exists ) {
- return array(
- 'ok' => false,
- 'message' => sprintf( 'hivepress-messages: shadow table %s missing', $table ),
- 'details' => array( 'table' => $table ),
- );
- }
- $rows = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$table}`" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
- return array(
- 'ok' => true,
- 'message' => sprintf( 'hivepress-messages: %s rows=%d', $table, $rows ),
- 'details' => array(
- 'table' => $table,
- 'rows' => $rows,
- ),
- );
- } catch ( \Throwable $e ) {
- return array(
- 'ok' => false,
- 'message' => sprintf( 'hivepress-messages doctor failed: %s', $e->getMessage() ),
- );
- }
- }
-
- /**
- * 8-D self-score.
- *
- * D1: 1.0 — adapter never calls *_meta()
- * D2: 1.0 — uses wpdb prefix exclusively
- * D3: 1.0 — shadow table covers recipient/unread access
- * D4: 1.0 — expected_columns + indexes registered
- * D5: 1.0 — listens to wp_insert_comment + status change for sync
- * D6: 1.0 — zero options/transients
- * D7: 1.0 — only queries own shadow table
- * D8: 1.0 — idx_recipient_unread covers the hot dashboard query
- */
- public function suitability_score(): array {
- return $this->compose_score( array() );
- }
-
- /**
- * FSM modules this adapter contributes.
- *
- * @return array
- */
- public function migrations(): array {
- return array( 'comment_hp_message' );
- }
}
} // end if ( ! class_exists )
diff --git a/includes/hivepress/adapters/class-tmdo-hivepress-requests-adapter.php b/includes/hivepress/adapters/class-tmdo-hivepress-requests-adapter.php
index 934f651..4ef0b7f 100644
--- a/includes/hivepress/adapters/class-tmdo-hivepress-requests-adapter.php
+++ b/includes/hivepress/adapters/class-tmdo-hivepress-requests-adapter.php
@@ -9,21 +9,9 @@
* — same access pattern as Listing (user dashboard + expiry cron)
*
* - Offer (comment_type=hp_offer, parent post_type=hp_request)
- * comment-column based; benefit comes from comment-router using
- * (comment_post_ID, comment_type) covering index + dedicated shadow
- * table for "approved offers per request" aggregation.
- *
- * Shadow table for offers (analogous to favorites pattern):
- *
- * CREATE TABLE wp_wpdo_comment_hp_offer (
- * 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,
- * KEY idx_request_approved (request_id, approved),
- * KEY idx_bidder (bidder_id)
- * );
+ * stored in comment columns only — no comment meta to flatten.
+ * (The adapter used to mirror offers into `wpdo_comment_hp_offer`,
+ * a table nothing ever created.)
*
* @package WP_Data_Optimizer
* @since 3.0.0
@@ -45,9 +33,6 @@ if ( ! class_exists( 'TMDO_HivePress_Requests_Adapter' ) ) {
/** Comment_type for the Offer side of Requests. */
public const OFFER_COMMENT_TYPE = 'hp_offer';
- /** Shadow table for offers (without wpdb prefix). */
- public const OFFER_TABLE = 'wpdo_comment_hp_offer';
-
/**
* Constructor — bind register hooks via inherited trait.
*/
@@ -105,104 +90,7 @@ if ( ! class_exists( 'TMDO_HivePress_Requests_Adapter' ) ) {
}
/**
- * 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.
+ * Doctor probe — request hot table.
*
* @return array{ok:bool, message:string, details?:array}
*/
@@ -214,41 +102,21 @@ if ( ! class_exists( 'TMDO_HivePress_Requests_Adapter' ) ) {
'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;
- }
+ $table = $wpdb->prefix . 'wpdo_hot_hp_request';
+ try {
+ $exists = (bool) $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) );
+ return array(
+ 'ok' => $exists,
+ 'message' => $exists
+ ? sprintf( 'hivepress-requests: %s present', $table )
+ : sprintf( 'hivepress-requests: %s missing', $table ),
+ );
+ } catch ( \Throwable $e ) {
+ return array(
+ 'ok' => false,
+ 'message' => sprintf( 'hivepress-requests doctor failed: %s', $e->getMessage() ),
+ );
}
- 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() );
}
/**
@@ -257,10 +125,7 @@ if ( ! class_exists( 'TMDO_HivePress_Requests_Adapter' ) ) {
* @return array
*/
public function migrations(): array {
- return array(
- 'hot_hp_request',
- 'comment_hp_offer',
- );
+ return array( 'hot_hp_request' );
}
}
diff --git a/includes/hivepress/adapters/class-tmdo-hivepress-reviews-adapter.php b/includes/hivepress/adapters/class-tmdo-hivepress-reviews-adapter.php
index 8dae0ad..a3ded1c 100644
--- a/includes/hivepress/adapters/class-tmdo-hivepress-reviews-adapter.php
+++ b/includes/hivepress/adapters/class-tmdo-hivepress-reviews-adapter.php
@@ -10,14 +10,12 @@
* - approved → comment_approved
* - listing → comment_post_ID
*
- * No postmeta is used; no entity-bridge group needed. Hot-path optimization
- * comes from `TMDO_HivePress_Comment_Router` rewriting comment-query SQL to
- * use `comment_post_ID + comment_type + comment_approved` index, and from
- * the `wp_wpdo_comment_hp_review` flat table that WPDO Comment entity bridge
- * already maintains (since v2.13.x).
+ * No postmeta is used; no entity-bridge group needed. Queries use the core
+ * `wp_comments` indexes on `comment_post_ID` / `comment_type`, and the
+ * `wp_wpdo_comment_hp_review` flat table is maintained by the WPDO Comment
+ * entity bridge (since v2.13.x).
*
* This adapter's role:
- * - Declare the comment_type to the comment-router
* - Doctor probe of the entity-bridge table
* - Self-report 8-D suitability score
*
@@ -124,7 +122,7 @@ if ( ! class_exists( 'TMDO_HivePress_Reviews_Adapter' ) ) {
* D5: 1.0 — no cross-adapter writes needed yet
* D6: 1.0 — zero options/transients written
* D7: 1.0 — only queries own entity-bridge table
- * D8: 1.0 — comment-router uses (comment_post_ID, comment_type) covering index
+ * D8: 1.0 — reads use the core wp_comments (comment_post_ID, comment_type) indexes
*/
public function suitability_score(): array {
return $this->compose_score( array() );
diff --git a/includes/hivepress/class-tmdo-hivepress-bootstrap.php b/includes/hivepress/class-tmdo-hivepress-bootstrap.php
index d271b28..5654af6 100644
--- a/includes/hivepress/class-tmdo-hivepress-bootstrap.php
+++ b/includes/hivepress/class-tmdo-hivepress-bootstrap.php
@@ -118,12 +118,9 @@ if ( ! class_exists( 'TMDO_HivePress_Bootstrap' ) ) {
self::bind_adapter( $slug, $version );
}
- // Step 5: register cross-cutting components (router + cron optimizer).
- // Both are gated by their own opt-in option and only activate when
- // the operator flips it; safe to call here unconditionally.
- if ( class_exists( 'TMDO_HivePress_Comment_Router' ) ) {
- TMDO_HivePress_Comment_Router::register();
- }
+ // Step 5: register the cron optimizer. It is gated by its own opt-in
+ // option and only activates when the operator flips it; safe to call
+ // here unconditionally.
if ( class_exists( 'TMDO_HivePress_Cron_Optimizer' ) ) {
TMDO_HivePress_Cron_Optimizer::register();
}
diff --git a/includes/hivepress/class-tmdo-hivepress-comment-router.php b/includes/hivepress/class-tmdo-hivepress-comment-router.php
deleted file mode 100644
index cb37e5a..0000000
--- a/includes/hivepress/class-tmdo-hivepress-comment-router.php
+++ /dev/null
@@ -1,166 +0,0 @@
-
- */
- private const SHADOW_MAP = array(
- 'hp_message' => 'wpdo_comment_hp_message',
- 'hp_favorite' => 'wpdo_comment_hp_favorite',
- 'hp_offer' => 'wpdo_comment_hp_offer',
- 'hp_review' => 'wpdo_comment_hp_review',
- );
-
- /**
- * Whether the router is bound this request.
- *
- * @var bool
- */
- private static bool $bound = false;
-
- /**
- * Bind the comments_clauses filter (idempotent).
- *
- * Bootstrap calls this from each adapter's on_register_query_hooks()
- * so router activation follows the same gate as adapter binding.
- */
- public static function register(): void {
- if ( self::$bound ) {
- return;
- }
- if ( ! self::is_enabled() ) {
- return;
- }
- add_filter( 'comments_clauses', array( __CLASS__, 'rewrite' ), 20, 2 );
- self::$bound = true;
- }
-
- /**
- * Reset internal state (test only).
- *
- * @internal
- */
- public static function reset_for_tests(): void {
- self::$bound = false;
- }
-
- /**
- * Whether router is enabled via the operator option.
- */
- public static function is_enabled(): bool {
- if ( ! function_exists( 'get_option' ) ) {
- return false;
- }
- return (bool) (int) get_option( self::OPTION_ENABLED, 0 );
- }
-
- /**
- * Filter callback: rewrite comments_clauses for known hp comment types.
- *
- * @param array $clauses SQL clauses (join, where, fields, ...).
- * @param \WP_Comment_Query $query The query object.
- * @return array Possibly-modified clauses.
- */
- public static function rewrite( array $clauses, $query ): array {
- $type = self::query_comment_type( $query );
- if ( '' === $type || ! isset( self::SHADOW_MAP[ $type ] ) ) {
- return $clauses;
- }
-
- global $wpdb;
- $shadow = $wpdb->prefix . self::SHADOW_MAP[ $type ];
- $alias = 'wpdo_shadow';
-
- // LEFT JOIN keeps row visibility when shadow lags (during backfill).
- $join = ( $clauses['join'] ?? '' ) . " LEFT JOIN `{$shadow}` AS {$alias} ON {$alias}.comment_id = {$wpdb->comments}.comment_ID";
-
- // Filter pushdown: hp_message recipient lookup uses comment_karma in HP.
- // We rewrite WHERE clauses that filter by comment_karma (= recipient hack)
- // to use the indexed shadow column.
- $where = (string) ( $clauses['where'] ?? '' );
- if ( 'hp_message' === $type && '' !== $where ) {
- $where = preg_replace(
- '/' . preg_quote( $wpdb->comments, '/' ) . '\.comment_karma\s*=\s*(\d+)/',
- $alias . '.recipient_id = $1',
- $where
- );
- }
-
- $clauses['join'] = $join;
- $clauses['where'] = $where;
-
- /**
- * Filter: wpdo_hivepress_comment_router_clauses
- *
- * Allows fine-grained tweaks per comment_type. Intended for adapter
- * tests + future custom-column lookups.
- *
- * @param array $clauses Modified clauses.
- * @param string $type Matched comment_type.
- * @param string $alias Shadow table alias.
- */
- return apply_filters( 'wpdo_hivepress_comment_router_clauses', $clauses, $type, $alias );
- }
-
- /**
- * Extract the comment_type from a WP_Comment_Query.
- *
- * @param \WP_Comment_Query $query Query object.
- */
- private static function query_comment_type( $query ): string {
- if ( ! is_object( $query ) || ! isset( $query->query_vars ) ) {
- return '';
- }
- $type = $query->query_vars['type'] ?? '';
- if ( is_array( $type ) ) {
- $type = reset( $type );
- }
- return is_string( $type ) ? $type : '';
- }
- }
-
-} // end if ( ! class_exists )
diff --git a/includes/hivepress/interface-tmdo-hp-adapter.php b/includes/hivepress/interface-tmdo-hp-adapter.php
index acc2233..0cd52c4 100644
--- a/includes/hivepress/interface-tmdo-hp-adapter.php
+++ b/includes/hivepress/interface-tmdo-hp-adapter.php
@@ -119,7 +119,7 @@ if ( ! interface_exists( 'TMDO_HivePress_Adapter' ) ) {
*
* Adapters with hot zone fields used in search/filter MUST register a
* `pre_get_posts` / `pre_get_comments` filter that defers to
- * `TMDO_HivePress_Query_Router` / `..._Comment_Router`.
+ * `TMDO_HivePress_Query_Router`.
*
* Called on `init:5` after Schema_Registry is fully populated.
*/
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
index d041084..e358c4e 100644
--- a/tests/bootstrap.php
+++ b/tests/bootstrap.php
@@ -38,7 +38,6 @@ $tmdo_hp_files = array(
'includes/hivepress/class-tmdo-hivepress-detector.php',
'includes/hivepress/class-tmdo-hivepress-conflict-guard.php',
'includes/hivepress/class-tmdo-hivepress-bootstrap.php',
- 'includes/hivepress/class-tmdo-hivepress-comment-router.php',
'includes/hivepress/class-tmdo-hivepress-cron-optimizer.php',
'includes/hivepress/class-tmdo-hivepress-attribute-bridge.php',
'includes/hivepress/class-tmdo-hivepress-suitability-scorer.php',
diff --git a/tests/integration/HivepressIntegrationTest.php b/tests/integration/HivepressIntegrationTest.php
index b1f6ce1..bfa2e4b 100644
--- a/tests/integration/HivepressIntegrationTest.php
+++ b/tests/integration/HivepressIntegrationTest.php
@@ -12,147 +12,17 @@ use PHPUnit\Framework\TestCase;
* Exercises against real MariaDB (via tests/integration/bootstrap.php) the
* scenarios that unit tests can only stub:
*
- * - Shadow table CREATE actually works with the declared expected_columns
- * - UNIQUE constraint on (user_id, listing_id) really prevents dup favorites
- * - mirror_insert() roundtrips through real $wpdb (SQL syntax valid)
* - 7-state FSM lifecycle moves WPDO_Feature_Flags correctly
*
* @group integration
*/
class HivepressIntegrationTest extends TestCase {
- /** Test-scope shadow table for favorites. */
- private const TABLE_FAVORITE = 'wp_itest_wpdo_comment_hp_favorite';
-
- /** Test-scope shadow table for messages. */
- private const TABLE_MESSAGE = 'wp_itest_wpdo_comment_hp_message';
-
- public static function setUpBeforeClass(): void {
- // HivePress integration files are loaded at file-top level so the
- // Testable* subclass definitions below can resolve their parents at
- // parse time. Schema setup happens here.
- global $wpdb;
- $wpdb->query( 'DROP TABLE IF EXISTS `' . self::TABLE_FAVORITE . '`' );
- $wpdb->query( "CREATE TABLE `" . self::TABLE_FAVORITE . "` (
- comment_id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
- user_id BIGINT UNSIGNED NOT NULL,
- listing_id BIGINT UNSIGNED NOT NULL,
- created_at DATETIME NOT NULL,
- UNIQUE KEY unique_user_listing (user_id, listing_id),
- KEY idx_listing (listing_id)
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4" );
-
- $wpdb->query( 'DROP TABLE IF EXISTS `' . self::TABLE_MESSAGE . '`' );
- $wpdb->query( "CREATE TABLE `" . self::TABLE_MESSAGE . "` (
- comment_id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
- sender_id BIGINT UNSIGNED NOT NULL,
- recipient_id BIGINT UNSIGNED NOT NULL,
- listing_id BIGINT UNSIGNED NOT NULL,
- is_read TINYINT(1) NOT NULL DEFAULT 0,
- sent_at DATETIME NOT NULL,
- KEY idx_recipient_unread (recipient_id, is_read),
- KEY idx_thread (listing_id, sent_at)
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4" );
- }
-
- public static function tearDownAfterClass(): void {
- global $wpdb;
- $wpdb->query( 'DROP TABLE IF EXISTS `' . self::TABLE_FAVORITE . '`' );
- $wpdb->query( 'DROP TABLE IF EXISTS `' . self::TABLE_MESSAGE . '`' );
- }
-
protected function setUp(): void {
- global $wpdb;
- $wpdb->query( 'TRUNCATE TABLE `' . self::TABLE_FAVORITE . '`' );
- $wpdb->query( 'TRUNCATE TABLE `' . self::TABLE_MESSAGE . '`' );
// Clear FSM module state between tests.
$GLOBALS['_wp_options']['wpdo_features'] = array();
}
- // ── Schema tests ────────────────────────────────────────────────────────
-
- public function test_favorite_table_has_unique_user_listing_constraint(): void {
- global $wpdb;
- $row = $wpdb->get_row( 'SHOW CREATE TABLE `' . self::TABLE_FAVORITE . '`', 'ARRAY_A' );
- $this->assertNotNull( $row );
- $ddl = $row['Create Table'] ?? '';
- $this->assertStringContainsString( 'UNIQUE KEY', $ddl );
- $this->assertStringContainsString( 'unique_user_listing', $ddl );
- }
-
- public function test_message_table_has_recipient_unread_index(): void {
- global $wpdb;
- $row = $wpdb->get_row( 'SHOW CREATE TABLE `' . self::TABLE_MESSAGE . '`', 'ARRAY_A' );
- $this->assertNotNull( $row );
- $ddl = $row['Create Table'] ?? '';
- $this->assertStringContainsString( 'idx_recipient_unread', $ddl );
- $this->assertStringContainsString( 'recipient_id', $ddl );
- }
-
- // ── mirror_insert real roundtrip ────────────────────────────────────────
-
- public function test_favorite_mirror_insert_writes_real_row(): void {
- global $wpdb;
- $adapter = new WPDO_HivePress_Favorites_Adapter();
-
- $comment = new stdClass();
- $comment->comment_type = 'hp_favorite';
- $comment->user_id = 11;
- $comment->comment_post_ID = 222;
- $comment->comment_date = '2026-05-04 12:00:00';
-
- $adapter->mirror_insert( 1001, $comment );
-
- $rows = (int) $wpdb->get_var( 'SELECT COUNT(*) FROM `' . self::TABLE_FAVORITE . '`' );
- $this->assertSame( 1, $rows );
-
- $saved = $wpdb->get_row( 'SELECT * FROM `' . self::TABLE_FAVORITE . '` WHERE comment_id = 1001', 'ARRAY_A' );
- $this->assertSame( '11', (string) $saved['user_id'] );
- $this->assertSame( '222', (string) $saved['listing_id'] );
- }
-
- public function test_favorite_unique_constraint_blocks_double_tap(): void {
- global $wpdb;
- $adapter = new WPDO_HivePress_Favorites_Adapter();
-
- // Two different comment_ids but same user+listing — UNIQUE should win.
- $comment = new stdClass();
- $comment->comment_type = 'hp_favorite';
- $comment->user_id = 7;
- $comment->comment_post_ID = 99;
- $comment->comment_date = '2026-05-04 12:00:00';
-
- $adapter->mirror_insert( 2001, $comment );
- $adapter->mirror_insert( 2002, $comment ); // Race-style duplicate.
-
- $rows = (int) $wpdb->get_var( 'SELECT COUNT(*) FROM `' . self::TABLE_FAVORITE . '` WHERE user_id = 7 AND listing_id = 99' );
- $this->assertSame( 1, $rows, 'UNIQUE(user_id, listing_id) must reject duplicate.' );
- }
-
- public function test_message_mirror_insert_promotes_recipient_id(): void {
- global $wpdb;
- $adapter = new WPDO_HivePress_Messages_Adapter();
-
- $comment = new stdClass();
- $comment->comment_type = 'hp_message';
- $comment->user_id = 5; // sender
- $comment->comment_karma = 42; // recipient (HP hack)
- $comment->comment_post_ID = 99;
- $comment->comment_approved = 0;
- $comment->comment_date = '2026-05-04 12:00:00';
-
- $adapter->mirror_insert( 3001, $comment );
-
- $saved = $wpdb->get_row( 'SELECT * FROM `' . self::TABLE_MESSAGE . '` WHERE comment_id = 3001', 'ARRAY_A' );
- $this->assertNotNull( $saved );
- $this->assertSame( '42', (string) $saved['recipient_id'] );
- $this->assertSame( '5', (string) $saved['sender_id'] );
-
- // recipient_unread index supports the dashboard query.
- $unread_count = (int) $wpdb->get_var( 'SELECT COUNT(*) FROM `' . self::TABLE_MESSAGE . '` WHERE recipient_id = 42 AND is_read = 0' );
- $this->assertSame( 1, $unread_count );
- }
-
// ── FSM lifecycle ───────────────────────────────────────────────────────
public function test_fsm_lifecycle_progression(): void {
@@ -189,9 +59,3 @@ class HivepressIntegrationTest extends TestCase {
$this->assertSame( 0, $count, 'Optimizer must short-circuit when module is idle.' );
}
}
-
-// Production adapters use `$wpdb->prefix . self::TABLE` to compose the full
-// table name. Integration test bootstrap sets prefix to `wp_itest_`, so the
-// production mirror_insert() naturally writes to wp_itest_wpdo_comment_hp_*.
-// No subclassing or test-double needed — the production code path is exercised
-// directly against real MariaDB with isolated prefix.
diff --git a/tests/integration/bootstrap.php b/tests/integration/bootstrap.php
index 2ce8c97..72f5713 100644
--- a/tests/integration/bootstrap.php
+++ b/tests/integration/bootstrap.php
@@ -39,7 +39,6 @@ $tmdo_hp_files = array(
'includes/hivepress/class-tmdo-hivepress-detector.php',
'includes/hivepress/class-tmdo-hivepress-conflict-guard.php',
'includes/hivepress/class-tmdo-hivepress-bootstrap.php',
- 'includes/hivepress/class-tmdo-hivepress-comment-router.php',
'includes/hivepress/class-tmdo-hivepress-cron-optimizer.php',
'includes/hivepress/class-tmdo-hivepress-attribute-bridge.php',
'includes/hivepress/class-tmdo-hivepress-suitability-scorer.php',
diff --git a/tests/unit/CommentRouterTest.php b/tests/unit/CommentRouterTest.php
deleted file mode 100644
index 7ce74f4..0000000
--- a/tests/unit/CommentRouterTest.php
+++ /dev/null
@@ -1,92 +0,0 @@
-install_wpdb_mock();
- WPDO_HivePress_Comment_Router::reset_for_tests();
- // Default disabled.
- $GLOBALS['_wp_options'][ WPDO_HivePress_Comment_Router::OPTION_ENABLED ] = 0;
- }
-
- public function test_disabled_by_default(): void {
- $this->assertFalse( WPDO_HivePress_Comment_Router::is_enabled() );
- }
-
- public function test_enabled_when_option_set(): void {
- $GLOBALS['_wp_options'][ WPDO_HivePress_Comment_Router::OPTION_ENABLED ] = 1;
- $this->assertTrue( WPDO_HivePress_Comment_Router::is_enabled() );
- }
-
- public function test_register_is_idempotent(): void {
- $GLOBALS['_wp_options'][ WPDO_HivePress_Comment_Router::OPTION_ENABLED ] = 1;
- WPDO_HivePress_Comment_Router::register();
- WPDO_HivePress_Comment_Router::register(); // Second call must be no-op.
- $this->assertTrue( true ); // No throw = pass
- }
-
- public function test_rewrite_skips_unknown_comment_type(): void {
- $query = new stdClass();
- $query->query_vars = array( 'type' => 'comment' ); // not in shadow map
-
- $clauses = array( 'join' => '', 'where' => '1=1' );
- $out = WPDO_HivePress_Comment_Router::rewrite( $clauses, $query );
-
- $this->assertSame( $clauses, $out );
- }
-
- public function test_rewrite_adds_left_join_for_hp_favorite(): void {
- $query = new stdClass();
- $query->query_vars = array( 'type' => 'hp_favorite' );
-
- $clauses = array( 'join' => '', 'where' => '1=1' );
- $out = WPDO_HivePress_Comment_Router::rewrite( $clauses, $query );
-
- $this->assertStringContainsString( 'LEFT JOIN', $out['join'] );
- $this->assertStringContainsString( 'wpdo_comment_hp_favorite', $out['join'] );
- $this->assertStringContainsString( 'AS wpdo_shadow', $out['join'] );
- }
-
- public function test_rewrite_promotes_recipient_for_hp_message(): void {
- $query = new stdClass();
- $query->query_vars = array( 'type' => 'hp_message' );
-
- $clauses = array(
- 'join' => '',
- 'where' => 'wp_comments.comment_karma = 42 AND comment_approved = 0',
- );
- $out = WPDO_HivePress_Comment_Router::rewrite( $clauses, $query );
-
- // recipient lookup should be rewritten to use shadow column.
- $this->assertStringContainsString( 'wpdo_shadow.recipient_id = 42', $out['where'] );
- // Other predicates preserved.
- $this->assertStringContainsString( 'comment_approved = 0', $out['where'] );
- }
-
- public function test_rewrite_handles_array_type(): void {
- $query = new stdClass();
- $query->query_vars = array( 'type' => array( 'hp_review', 'comment' ) );
-
- $clauses = array( 'join' => '', 'where' => '' );
- $out = WPDO_HivePress_Comment_Router::rewrite( $clauses, $query );
-
- // First element of type array should match (hp_review).
- $this->assertStringContainsString( 'wpdo_comment_hp_review', $out['join'] );
- }
-
- public function test_rewrite_returns_unchanged_when_query_invalid(): void {
- $clauses = array( 'join' => '', 'where' => '1=1' );
- $out = WPDO_HivePress_Comment_Router::rewrite( $clauses, 'not an object' );
- $this->assertSame( $clauses, $out );
- }
-}
diff --git a/tests/unit/FavoritesAdapterTest.php b/tests/unit/FavoritesAdapterTest.php
index 0b74b86..b6a3e23 100644
--- a/tests/unit/FavoritesAdapterTest.php
+++ b/tests/unit/FavoritesAdapterTest.php
@@ -6,6 +6,9 @@ use PHPUnit\Framework\TestCase;
/**
* Tests for WPDO_HivePress_Favorites_Adapter.
*
+ * The adapter no longer mirrors favorites into a shadow table: that table was
+ * never created, so every mirror write failed.
+ *
* @covers WPDO_HivePress_Favorites_Adapter
*/
class HivePressFavoritesAdapterTest extends TestCase {
@@ -32,72 +35,22 @@ class HivePressFavoritesAdapterTest extends TestCase {
public function test_constants_exposed(): void {
$this->assertSame( 'hp_favorite', WPDO_HivePress_Favorites_Adapter::COMMENT_TYPE );
- $this->assertSame( 'wpdo_comment_hp_favorite', WPDO_HivePress_Favorites_Adapter::TABLE );
}
- public function test_on_register_custom_tables_declares_shadow_table(): void {
+ public function test_registers_no_shadow_table(): void {
$registry = WPDO_Custom_Table_Registry::instance();
$this->adapter->on_register_custom_tables( $registry );
- $tables = $registry->all();
- $this->assertCount( 1, $tables );
-
- $cfg = reset( $tables );
- $this->assertSame( 'hivepress-favorites', $cfg['provider'] );
- $this->assertSame( 'wpdo_comment_hp_favorite', $cfg['table_name'] );
- $this->assertSame( 'comment_id', $cfg['primary_key'] );
- $this->assertArrayHasKey( 'unique_user_listing', $cfg['indexes'] );
- $this->assertStringContainsString( 'UNIQUE', $cfg['indexes']['unique_user_listing'] );
+ $this->assertSame( array(), $registry->all() );
}
- public function test_mirror_insert_skips_non_favorite_comments(): void {
- // Stub $wpdb tracks queries.
- global $wpdb;
- $wpdb->queries = array();
+ public function test_binds_no_comment_hooks(): void {
+ $GLOBALS['_wp_filter_callbacks'] = array();
- $comment = new stdClass();
- $comment->comment_type = 'comment'; // ← not hp_favorite
- $comment->user_id = 1;
- $comment->comment_post_ID = 99;
+ $this->adapter->on_register_event_hooks();
- $this->adapter->mirror_insert( 123, $comment );
- $this->assertSame( array(), $wpdb->queries );
- }
-
- public function test_mirror_insert_writes_for_hp_favorite(): void {
- global $wpdb;
- $wpdb->queries = array();
-
- $comment = new stdClass();
- $comment->comment_type = 'hp_favorite';
- $comment->user_id = 7;
- $comment->comment_post_ID = 42;
- $comment->comment_date = '2026-05-04 12:00:00';
-
- $this->adapter->mirror_insert( 100, $comment );
-
- $this->assertCount( 1, $wpdb->queries );
- $this->assertStringContainsString( 'INSERT IGNORE', $wpdb->queries[0] );
- $this->assertStringContainsString( 'wpdo_comment_hp_favorite', $wpdb->queries[0] );
- }
-
- public function test_mirror_insert_skips_when_user_or_listing_missing(): void {
- global $wpdb;
- $wpdb->queries = array();
-
- $comment = new stdClass();
- $comment->comment_type = 'hp_favorite';
- $comment->user_id = 0;
- $comment->comment_post_ID = 99;
-
- $this->adapter->mirror_insert( 100, $comment );
- $this->assertSame( array(), $wpdb->queries );
- }
-
- public function test_doctor_check_reports_missing_table_in_stub_env(): void {
- $result = $this->adapter->doctor_check();
- $this->assertFalse( $result['ok'] );
- $this->assertStringContainsString( 'wpdo_comment_hp_favorite', $result['message'] );
+ $this->assertArrayNotHasKey( 'wp_insert_comment', $GLOBALS['_wp_filter_callbacks'] );
+ $this->assertArrayNotHasKey( 'deleted_comment', $GLOBALS['_wp_filter_callbacks'] );
}
public function test_score_aggregates_to_perfect_ten(): void {
@@ -105,7 +58,7 @@ class HivePressFavoritesAdapterTest extends TestCase {
$this->assertSame( 10.0, $score['aggregate'] );
}
- public function test_migrations_lists_comment_module(): void {
- $this->assertSame( array( 'comment_hp_favorite' ), $this->adapter->migrations() );
+ public function test_migrations_is_empty(): void {
+ $this->assertSame( array(), $this->adapter->migrations() );
}
}
diff --git a/tests/unit/MessagesAdapterTest.php b/tests/unit/MessagesAdapterTest.php
index ab59f5c..c9d6c33 100644
--- a/tests/unit/MessagesAdapterTest.php
+++ b/tests/unit/MessagesAdapterTest.php
@@ -6,6 +6,9 @@ use PHPUnit\Framework\TestCase;
/**
* Tests for WPDO_HivePress_Messages_Adapter.
*
+ * The adapter no longer mirrors messages into a shadow table: that table was
+ * never created, so every mirror write failed.
+ *
* @covers WPDO_HivePress_Messages_Adapter
*/
class HivePressMessagesAdapterTest extends TestCase {
@@ -28,77 +31,26 @@ class HivePressMessagesAdapterTest extends TestCase {
$this->assertSame( 'hivepress-messages', $this->adapter->plugin_slug() );
$this->assertSame( 'HivePress\\Messages\\Plugin', $this->adapter->detection_class() );
$this->assertSame( 'hp_message', WPDO_HivePress_Messages_Adapter::COMMENT_TYPE );
- $this->assertSame( 'wpdo_comment_hp_message', WPDO_HivePress_Messages_Adapter::TABLE );
}
- public function test_on_register_custom_tables_declares_indexed_recipient(): void {
+ public function test_registers_no_shadow_table(): void {
$registry = WPDO_Custom_Table_Registry::instance();
$this->adapter->on_register_custom_tables( $registry );
- $tables = $registry->all();
- $cfg = reset( $tables );
- $this->assertSame( 'wpdo_comment_hp_message', $cfg['table_name'] );
- $this->assertArrayHasKey( 'recipient_id', $cfg['expected_columns'] );
- $this->assertArrayHasKey( 'idx_recipient_unread', $cfg['indexes'] );
- $this->assertStringContainsString( 'recipient_id, is_read', $cfg['indexes']['idx_recipient_unread'] );
+ $this->assertSame( array(), $registry->all() );
}
- public function test_mirror_insert_skips_non_message_comments(): void {
- global $wpdb;
- $wpdb->queries = array();
+ public function test_binds_no_comment_hooks(): void {
+ $GLOBALS['_wp_filter_callbacks'] = array();
- $comment = new stdClass();
- $comment->comment_type = 'comment';
- $comment->user_id = 1;
- $comment->comment_karma = 5;
+ $this->adapter->on_register_event_hooks();
- $this->adapter->mirror_insert( 1, $comment );
- $this->assertSame( array(), $wpdb->queries );
+ $this->assertArrayNotHasKey( 'wp_insert_comment', $GLOBALS['_wp_filter_callbacks'] );
+ $this->assertArrayNotHasKey( 'wp_set_comment_status', $GLOBALS['_wp_filter_callbacks'] );
+ $this->assertArrayNotHasKey( 'deleted_comment', $GLOBALS['_wp_filter_callbacks'] );
}
- public function test_mirror_insert_promotes_recipient_from_comment_karma(): void {
- global $wpdb;
- $wpdb->queries = array();
-
- $comment = new stdClass();
- $comment->comment_type = 'hp_message';
- $comment->user_id = 7; // sender
- $comment->comment_karma = 42; // ← recipient hack lives here
- $comment->comment_post_ID = 99;
- $comment->comment_approved = 0;
- $comment->comment_date = '2026-05-04 12:00:00';
-
- $this->adapter->mirror_insert( 100, $comment );
-
- $this->assertCount( 1, $wpdb->queries );
- $sql = $wpdb->queries[0];
- $this->assertStringContainsString( 'INSERT INTO', $sql );
- $this->assertStringContainsString( 'wpdo_comment_hp_message', $sql );
- // The promoted recipient_id should appear in the bound SQL.
- $this->assertStringContainsString( '42', $sql );
- }
-
- public function test_mirror_insert_skips_when_sender_or_recipient_zero(): void {
- global $wpdb;
- $wpdb->queries = array();
-
- $comment = new stdClass();
- $comment->comment_type = 'hp_message';
- $comment->user_id = 0; // bad sender
- $comment->comment_karma = 42;
- $comment->comment_post_ID = 99;
-
- $this->adapter->mirror_insert( 100, $comment );
- $this->assertSame( array(), $wpdb->queries );
- }
-
- public function test_doctor_check_reports_missing_table_in_stub_env(): void {
- $result = $this->adapter->doctor_check();
- $this->assertFalse( $result['ok'] );
- $this->assertStringContainsString( 'wpdo_comment_hp_message', $result['message'] );
- }
-
- public function test_score_aggregates_to_perfect_ten(): void {
- $this->assertSame( 10.0, $this->adapter->suitability_score()['aggregate'] );
+ public function test_migrations_is_empty(): void {
+ $this->assertSame( array(), $this->adapter->migrations() );
}
}
diff --git a/tests/unit/RequestsAdapterTest.php b/tests/unit/RequestsAdapterTest.php
index 276629e..0c850b1 100644
--- a/tests/unit/RequestsAdapterTest.php
+++ b/tests/unit/RequestsAdapterTest.php
@@ -48,54 +48,29 @@ class HivePressRequestsAdapterTest extends TestCase {
$this->assertTrue( (bool) $expired['indexed'] );
}
- public function test_on_register_custom_tables_declares_offer_shadow(): void {
+ public function test_registers_no_offer_shadow_table(): void {
+ // The offer shadow table was never created, so every mirror write failed.
$registry = WPDO_Custom_Table_Registry::instance();
$this->adapter->on_register_custom_tables( $registry );
- $tables = $registry->all();
- $cfg = reset( $tables );
- $this->assertSame( 'wpdo_comment_hp_offer', $cfg['table_name'] );
- $this->assertSame( 'hp_request', $cfg['post_type_link'] );
- $this->assertArrayHasKey( 'idx_request_approved', $cfg['indexes'] );
+ $this->assertSame( array(), $registry->all() );
}
- public function test_mirror_offer_insert_skips_other_comment_types(): void {
- global $wpdb;
- $wpdb->queries = array();
+ public function test_binds_no_comment_hooks(): void {
+ $GLOBALS['_wp_filter_callbacks'] = array();
- $comment = new stdClass();
- $comment->comment_type = 'comment'; // ← not hp_offer
- $comment->comment_post_ID = 99;
- $comment->user_id = 1;
+ $this->adapter->on_register_event_hooks();
- $this->adapter->mirror_offer_insert( 1, $comment );
- $this->assertSame( array(), $wpdb->queries );
- }
-
- public function test_mirror_offer_insert_writes_for_hp_offer(): void {
- global $wpdb;
- $wpdb->queries = array();
-
- $comment = new stdClass();
- $comment->comment_type = 'hp_offer';
- $comment->comment_post_ID = 42;
- $comment->user_id = 7;
- $comment->comment_approved = 0;
- $comment->comment_date = '2026-05-04 12:00:00';
-
- $this->adapter->mirror_offer_insert( 100, $comment );
- $this->assertCount( 1, $wpdb->queries );
- $this->assertStringContainsString( 'INSERT INTO', $wpdb->queries[0] );
- $this->assertStringContainsString( 'wpdo_comment_hp_offer', $wpdb->queries[0] );
+ $this->assertArrayNotHasKey( 'wp_insert_comment', $GLOBALS['_wp_filter_callbacks'] );
+ $this->assertArrayNotHasKey( 'edit_comment', $GLOBALS['_wp_filter_callbacks'] );
+ $this->assertArrayNotHasKey( 'deleted_comment', $GLOBALS['_wp_filter_callbacks'] );
}
public function test_score_aggregates_to_perfect_ten(): void {
$this->assertSame( 10.0, $this->adapter->suitability_score()['aggregate'] );
}
- public function test_migrations_lists_request_and_offer_modules(): void {
- $mods = $this->adapter->migrations();
- $this->assertContains( 'hot_hp_request', $mods );
- $this->assertContains( 'comment_hp_offer', $mods );
+ public function test_migrations_lists_only_request_hot_module(): void {
+ $this->assertSame( array( 'hot_hp_request' ), $this->adapter->migrations() );
}
}
diff --git a/tests/unit/WpdbMockTrait.php b/tests/unit/WpdbMockTrait.php
index e5ec071..c8da95d 100644
--- a/tests/unit/WpdbMockTrait.php
+++ b/tests/unit/WpdbMockTrait.php
@@ -7,7 +7,7 @@ declare(strict_types=1);
* Several existing wpdo tests (ZoneHotTest, SyncBridgeTest, etc.) replace the
* global $wpdb with their own anonymous-class mock and never restore it. When
* our adapter tests run after those, $wpdb lacks the query/get_col/delete
- * methods our mirror_insert / cron-optimizer code paths call, causing
+ * methods our cron-optimizer code paths call, causing
* cross-test pollution failures.
*
* Trait reinstalls a known $wpdb mock + clears the wpdo_features option so
From ff6d6b10e49804b6e1f52940b01c3527c16eba7e Mon Sep 17 00:00:00 2001
From: wpdev
Date: Fri, 25 Sep 2026 23:01:56 +0800
Subject: [PATCH 05/12] refactor(2meet-data-optimizer-hivepress-addon): remove
HPCT-era interceptors and query rewriters
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Delete 7 interceptors + 5 query rewriters that read/write hpct_* tables no site has (only an empty hpct_latepoint_idx exists)
- Drop their loading/registration in TMDO_HP_Bootstrap, back-compat aliases, integration bootstrap, and ListingMetaInterceptorTest
- Verify: unit 141 OK, integration 49 OK, bootstrap smoke OK; phpcs 34E/40W → 25E/28W
- PR: PR-014
---
includes/back-compat-aliases.php | 12 -
includes/class-tmdo-hivepress-bootstrap.php | 60 +---
.../class-tmdo-favorites-interceptor.php | 117 ------
.../class-tmdo-listing-meta-interceptor.php | 339 ------------------
.../class-tmdo-memberships-interceptor.php | 76 ----
.../class-tmdo-messages-interceptor.php | 78 ----
.../class-tmdo-requests-interceptor.php | 74 ----
.../class-tmdo-reviews-interceptor.php | 116 ------
.../class-tmdo-statistics-interceptor.php | 139 -------
.../query/class-tmdo-listing-meta-query.php | 236 ------------
.../query/class-tmdo-memberships-query.php | 67 ----
includes/query/class-tmdo-messages-query.php | 63 ----
includes/query/class-tmdo-requests-query.php | 61 ----
includes/query/class-tmdo-reviews-query.php | 64 ----
.../ListingMetaInterceptorTest.php | 160 ---------
tests/integration/bootstrap.php | 8 -
16 files changed, 1 insertion(+), 1669 deletions(-)
delete mode 100644 includes/interceptors/class-tmdo-favorites-interceptor.php
delete mode 100644 includes/interceptors/class-tmdo-listing-meta-interceptor.php
delete mode 100644 includes/interceptors/class-tmdo-memberships-interceptor.php
delete mode 100644 includes/interceptors/class-tmdo-messages-interceptor.php
delete mode 100644 includes/interceptors/class-tmdo-requests-interceptor.php
delete mode 100644 includes/interceptors/class-tmdo-reviews-interceptor.php
delete mode 100644 includes/interceptors/class-tmdo-statistics-interceptor.php
delete mode 100644 includes/query/class-tmdo-listing-meta-query.php
delete mode 100644 includes/query/class-tmdo-memberships-query.php
delete mode 100644 includes/query/class-tmdo-messages-query.php
delete mode 100644 includes/query/class-tmdo-requests-query.php
delete mode 100644 includes/query/class-tmdo-reviews-query.php
delete mode 100644 tests/integration/ListingMetaInterceptorTest.php
diff --git a/includes/back-compat-aliases.php b/includes/back-compat-aliases.php
index 78f487f..27c5adf 100644
--- a/includes/back-compat-aliases.php
+++ b/includes/back-compat-aliases.php
@@ -20,7 +20,6 @@ add_action(
$aliases = array(
'TMDO_Admin_HivePress' => 'WPDO_Admin_HivePress',
'TMDO_CLI_HivePress' => 'WPDO_CLI_HivePress',
- 'TMDO_Favorites_Interceptor' => 'WPDO_Favorites_Interceptor',
'TMDO_HPCT_Import' => 'WPDO_HPCT_Import',
'TMDO_HP_Bootstrap' => 'WPDO_HP_Bootstrap',
'TMDO_HivePress' => 'WPDO_HivePress',
@@ -47,18 +46,7 @@ add_action(
'TMDO_HivePress_Tags_Adapter' => 'WPDO_HivePress_Tags_Adapter',
'TMDO_Hivepress_Term_Comment_Fields' => 'WPDO_Hivepress_Term_Comment_Fields',
'TMDO_Hivepress_Transient_Filter' => 'WPDO_Hivepress_Transient_Filter',
- 'TMDO_Listing_Meta_Interceptor' => 'WPDO_Listing_Meta_Interceptor',
- 'TMDO_Listing_Meta_Query' => 'WPDO_Listing_Meta_Query',
'TMDO_Listing_Stats' => 'WPDO_Listing_Stats',
- 'TMDO_Memberships_Interceptor' => 'WPDO_Memberships_Interceptor',
- 'TMDO_Memberships_Query' => 'WPDO_Memberships_Query',
- 'TMDO_Messages_Interceptor' => 'WPDO_Messages_Interceptor',
- 'TMDO_Messages_Query' => 'WPDO_Messages_Query',
- 'TMDO_Requests_Interceptor' => 'WPDO_Requests_Interceptor',
- 'TMDO_Requests_Query' => 'WPDO_Requests_Query',
- 'TMDO_Reviews_Interceptor' => 'WPDO_Reviews_Interceptor',
- 'TMDO_Reviews_Query' => 'WPDO_Reviews_Query',
- 'TMDO_Statistics_Interceptor' => 'WPDO_Statistics_Interceptor',
);
foreach ( $aliases as $tmdo_class => $wpdo_alias ) {
if ( class_exists( $tmdo_class, false ) && ! class_exists( $wpdo_alias, false ) ) {
diff --git a/includes/class-tmdo-hivepress-bootstrap.php b/includes/class-tmdo-hivepress-bootstrap.php
index c5f9c5c..831d33d 100644
--- a/includes/class-tmdo-hivepress-bootstrap.php
+++ b/includes/class-tmdo-hivepress-bootstrap.php
@@ -2,8 +2,7 @@
/**
* Bootstrap for 2meet Data Optimizer HivePress AddOn.
*
- * 載入 13 adapter + 7 interceptor + 5 query interceptor + 補助組件 +
- * admin tab + CLI namespace。
+ * 載入 13 adapter + 補助組件 + admin tab + CLI namespace。
*
* 注意:HP 的 V3.0.0 family bootstrap 內部也有一個 class TMDO_HivePress_Bootstrap
* (在 includes/hivepress/class-tmdo-hivepress-bootstrap.php)。為避免衝突,本
@@ -64,35 +63,6 @@ final class TMDO_HP_Bootstrap {
require_once $base . 'includes/hivepress/class-tmdo-hivepress-benchmark.php';
require_once $base . 'includes/hivepress/class-tmdo-hivepress-rest.php';
- // 7 HPCT interceptors.
- // reviews / messages / memberships / requests extend the core's
- // TMDO_Standard_Post_Interceptor — requiring them against a core older than
- // v0.2.0 would fatal at parse time, so gate those four on the base class.
- $has_standard_base = class_exists( 'TMDO_Standard_Post_Interceptor' );
- foreach ( array(
- 'reviews', 'messages', 'favorites', 'memberships',
- 'statistics', 'requests', 'listing-meta',
- ) as $intslug ) {
- $needs_base = in_array( $intslug, array( 'reviews', 'messages', 'memberships', 'requests' ), true );
- if ( $needs_base && ! $has_standard_base ) {
- continue;
- }
- $file = $base . "includes/interceptors/class-tmdo-{$intslug}-interceptor.php";
- if ( file_exists( $file ) ) {
- require_once $file;
- }
- }
-
- // 5 query interceptors
- foreach ( array(
- 'reviews', 'messages', 'memberships', 'requests', 'listing-meta',
- ) as $qslug ) {
- $file = $base . "includes/query/class-tmdo-{$qslug}-query.php";
- if ( file_exists( $file ) ) {
- require_once $file;
- }
- }
-
// Misc HP integration files
require_once $base . 'includes/class-tmdo-hivepress.php';
require_once $base . 'includes/class-tmdo-hivepress-transient-filter.php';
@@ -110,34 +80,6 @@ final class TMDO_HP_Bootstrap {
TMDO_HivePress_Bootstrap::boot();
}
- // 7 HPCT interceptors
- foreach ( array(
- 'TMDO_Reviews_Interceptor',
- 'TMDO_Messages_Interceptor',
- 'TMDO_Favorites_Interceptor',
- 'TMDO_Memberships_Interceptor',
- 'TMDO_Statistics_Interceptor',
- 'TMDO_Requests_Interceptor',
- 'TMDO_Listing_Meta_Interceptor',
- ) as $cls ) {
- if ( class_exists( $cls ) ) {
- ( new $cls() )->register_hooks();
- }
- }
-
- // 5 query interceptors
- foreach ( array(
- 'TMDO_Reviews_Query',
- 'TMDO_Messages_Query',
- 'TMDO_Memberships_Query',
- 'TMDO_Requests_Query',
- 'TMDO_Listing_Meta_Query',
- ) as $cls ) {
- if ( class_exists( $cls ) ) {
- ( new $cls() )->register_hooks();
- }
- }
-
// HP transient filter (init:5)
if ( class_exists( 'TMDO_Hivepress_Transient_Filter' ) ) {
add_action( 'init', array( 'TMDO_Hivepress_Transient_Filter', 'init' ), 5 );
diff --git a/includes/interceptors/class-tmdo-favorites-interceptor.php b/includes/interceptors/class-tmdo-favorites-interceptor.php
deleted file mode 100644
index c24abad..0000000
--- a/includes/interceptors/class-tmdo-favorites-interceptor.php
+++ /dev/null
@@ -1,117 +0,0 @@
-is_active() ) {
- return $check;
- }
-
- try {
- $this->sync_favorites( $user_id, $meta_value );
- } catch ( \Throwable $e ) {
- TMDO_Logger::error( $this->module, 'update_user_metadata', $e->getMessage() );
- }
-
- return $check;
- }
-
- /**
- * Deletes favorites records when a listing post is deleted.
- *
- * @param int $post_id Post ID.
- * @param \WP_Post $post Post object.
- * @return void
- */
- public function action_delete_post( int $post_id, \WP_Post $post ): void {
- if ( 'hp_listing' !== $post->post_type || ! $this->is_active() ) {
- return;
- }
-
- try {
- global $wpdb;
- $wpdb->delete( TMDO_DB::table( 'hpct_favorites' ), array( 'listing_id' => $post_id ), array( '%d' ) );
- } catch ( \Throwable $e ) {
- TMDO_Logger::error( $this->module, 'before_delete_post', $e->getMessage() );
- }
- }
-
- /**
- * Syncs user favorites to the hpct_favorites table.
- *
- * @param int $user_id User ID.
- * @param mixed $meta_value Favorites meta value (array or serialized).
- * @return void
- */
- private function sync_favorites( int $user_id, $meta_value ): void {
- global $wpdb;
- $table = TMDO_DB::table( 'hpct_favorites' );
- $now = TMDO_DB::now();
-
- // v2.13.3: object-injection-safe unserialize (fixes L-DESER-1).
- // Input is user-controlled wp_usermeta value via update_user_meta hook.
- $listing_ids = is_array( $meta_value )
- ? $meta_value
- : (array) TMDO_Safe_Unserialize::run( $meta_value );
- $listing_ids = array_filter( array_map( 'absint', $listing_ids ) );
-
- // Delete all existing and re-insert.
- $wpdb->delete( $table, array( 'user_id' => $user_id ), array( '%d' ) );
-
- $insert_sql = TMDO_IS_SQLITE ? 'INSERT OR IGNORE' : 'INSERT IGNORE';
-
- // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- insert_sql is 'INSERT IGNORE'/'INSERT OR IGNORE'; table name from TMDO_DB::table().
- foreach ( $listing_ids as $listing_id ) {
- $wpdb->query(
- $wpdb->prepare(
- "{$insert_sql} INTO `{$table}` (user_id, listing_id, created_at) VALUES (%d, %d, %s)",
- $user_id,
- $listing_id,
- $now
- )
- );
- }
- // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
- }
-}
diff --git a/includes/interceptors/class-tmdo-listing-meta-interceptor.php b/includes/interceptors/class-tmdo-listing-meta-interceptor.php
deleted file mode 100644
index bd4e9d6..0000000
--- a/includes/interceptors/class-tmdo-listing-meta-interceptor.php
+++ /dev/null
@@ -1,339 +0,0 @@
-get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) );
- $exists = ( null !== $found && '' !== $found );
- return $exists;
- }
-
- /**
- * Reset cached table_exists state — for tests and PR-3 schema changes.
- *
- * @internal
- */
- public static function reset_table_exists_cache(): void {
- // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged
- // Use reflection to clear the static. Cleanest approach in PHP 8.1+.
- ( function () {
- static $exists = null;
- $exists = null;
- } )();
- // The above closure does not actually reset the bound static of table_exists().
- // Instead we expose a flag via a class-level static.
- self::$table_exists_cache_invalidated_at = microtime( true );
- }
-
- /**
- * Marker for cache invalidation. Real reset happens by re-calling table_exists()
- * in a fresh process; tests should isolate via runInSeparateProcess where needed.
- *
- * @var float
- */
- private static float $table_exists_cache_invalidated_at = 0.0;
-
- /**
- * Records a single "skipped — table missing" entry in wpdo_errors per request.
- *
- * Direct INSERT (not via TMDO_Logger::error) so we avoid cascading the
- * message to PHP's error_log on every page load. Idempotent within a
- * single request via static guard.
- *
- * @return void
- */
- private static function log_skip_once(): void {
- static $logged = false;
- if ( $logged ) {
- return;
- }
- $logged = true;
-
- // Direct INSERT — wrapped in try/catch because the wpdo_errors table
- // might not exist in fresh installs. Skipping the log is acceptable;
- // breaking register_hooks() is not.
- try {
- global $wpdb;
- $wpdb->insert(
- $wpdb->prefix . 'wpdo_errors',
- array(
- 'module' => 'listing_meta',
- 'zone' => '',
- 'hook' => 'register_hooks',
- 'message' => 'Skipped: hpct_listing_meta table missing. Run `wp wpdo import-hpct` or migrate first.',
- 'context' => '{}',
- 'created_at' => current_time( 'mysql' ),
- ),
- array( '%s', '%s', '%s', '%s', '%s', '%s' )
- );
- } catch ( \Throwable $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch -- intentional: wpdo_errors absence is non-critical.
- // Silently swallow — wpdo_errors table absence is non-critical.
- }
- }
-
- /**
- * Deletes listing meta when a post is deleted.
- *
- * @param int $post_id Post ID.
- * @param \WP_Post $post Post object.
- * @return void
- */
- public function action_delete_post( int $post_id, \WP_Post $post ): void {
- if ( 'hp_listing' !== $post->post_type || ! $this->is_active() ) {
- return;
- }
-
- try {
- global $wpdb;
- $wpdb->delete( TMDO_DB::table( 'hpct_listing_meta' ), array( 'listing_id' => $post_id ), array( '%d' ) );
- } catch ( \Throwable $e ) {
- TMDO_Logger::error( $this->module, 'before_delete_post', $e->getMessage() );
- }
- }
-
- /**
- * Filters get_post_metadata for hp_listing posts.
- *
- * @param mixed $value Current value or null.
- * @param int $object_id Post ID.
- * @param string $meta_key Meta key.
- * @param bool $single Whether to return single value.
- * @return mixed Filtered value.
- */
- public function filter_get_meta( $value, int $object_id, string $meta_key, bool $single ) {
- if ( ! $this->is_enabled() || ! $this->is_hp_listing( $object_id ) || ! $this->is_hp_key( $meta_key ) ) {
- return $value;
- }
-
- return $this->intercept(
- function () use ( $object_id, $meta_key, $single ) {
- global $wpdb;
- $table = TMDO_DB::table( 'hpct_listing_meta' );
-
- if ( $single ) {
- $val = $wpdb->get_var(
- $wpdb->prepare(
- "SELECT meta_value FROM `{$table}` WHERE listing_id = %d AND meta_key = %s LIMIT 1", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name validated by TMDO_DB::table() + sanitize_key().
- $object_id,
- $meta_key
- )
- );
- return null !== $val ? $val : null;
- }
-
- return $wpdb->get_col(
- $wpdb->prepare(
- "SELECT meta_value FROM `{$table}` WHERE listing_id = %d AND meta_key = %s", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name validated by TMDO_DB::table() + sanitize_key().
- $object_id,
- $meta_key
- )
- ) ?: null;
- },
- fn() => $value,
- 'get_post_metadata'
- );
- }
-
- /**
- * Filters update_post_metadata for hp_listing posts.
- *
- * @param mixed $check Whether to short-circuit.
- * @param int $object_id Post ID.
- * @param string $meta_key Meta key.
- * @param mixed $meta_value Meta value.
- * @param mixed $prev_value Previous meta value.
- * @return mixed Filtered check value.
- */
- public function filter_update_meta( $check, int $object_id, string $meta_key, $meta_value, $prev_value ) {
- if ( ! $this->is_hp_listing( $object_id ) || ! $this->is_hp_key( $meta_key ) || ! $this->is_active() ) {
- return $check;
- }
-
- try {
- $this->upsert_meta( $object_id, $meta_key, $meta_value );
- } catch ( \Throwable $e ) {
- TMDO_Logger::error( $this->module, 'update_post_metadata', $e->getMessage() );
- }
-
- return $check;
- }
-
- /**
- * Filters add_post_metadata for hp_listing posts.
- *
- * @param mixed $check Whether to short-circuit.
- * @param int $object_id Post ID.
- * @param string $meta_key Meta key.
- * @param mixed $meta_value Meta value.
- * @param bool $unique Whether meta key should be unique.
- * @return mixed Filtered check value.
- */
- public function filter_add_meta( $check, int $object_id, string $meta_key, $meta_value, bool $unique ) {
- if ( ! $this->is_hp_listing( $object_id ) || ! $this->is_hp_key( $meta_key ) || ! $this->is_active() ) {
- return $check;
- }
-
- try {
- $this->upsert_meta( $object_id, $meta_key, $meta_value );
- } catch ( \Throwable $e ) {
- TMDO_Logger::error( $this->module, 'add_post_metadata', $e->getMessage() );
- }
-
- return $check;
- }
-
- /**
- * Filters delete_post_metadata for hp_listing posts.
- *
- * @param mixed $check Whether to short-circuit.
- * @param int $object_id Post ID.
- * @param string $meta_key Meta key.
- * @param mixed $meta_value Meta value to match.
- * @param bool $delete_all Whether to delete all matching.
- * @return mixed Filtered check value.
- */
- public function filter_delete_meta( $check, int $object_id, string $meta_key, $meta_value, bool $delete_all ) {
- if ( ! $this->is_hp_listing( $object_id ) || ! $this->is_hp_key( $meta_key ) || ! $this->is_active() ) {
- return $check;
- }
-
- try {
- global $wpdb;
- $wpdb->delete(
- TMDO_DB::table( 'hpct_listing_meta' ),
- array(
- 'listing_id' => $object_id,
- 'meta_key' => $meta_key,
- ),
- array( '%d', '%s' )
- );
- } catch ( \Throwable $e ) {
- TMDO_Logger::error( $this->module, 'delete_post_metadata', $e->getMessage() );
- }
-
- return $check;
- }
-
- /**
- * Checks whether the post is an hp_listing.
- *
- * @param int $post_id Post ID.
- * @return bool True if hp_listing post type.
- */
- private function is_hp_listing( int $post_id ): bool {
- return 'hp_listing' === get_post_type( $post_id );
- }
-
- /**
- * Checks whether the meta key is an hp_ or _hp_ key.
- *
- * @param string $meta_key Meta key.
- * @return bool True if key starts with hp_ or _hp_.
- */
- private function is_hp_key( string $meta_key ): bool {
- return str_starts_with( $meta_key, 'hp_' ) || str_starts_with( $meta_key, '_hp_' );
- }
-
- /**
- * Inserts or updates a listing meta value in the hpct_listing_meta table.
- *
- * Single round-trip via TMDO_DB::upsert() — uses ON DUPLICATE KEY UPDATE
- * (MySQL) or ON CONFLICT(listing_id, meta_key) DO UPDATE (SQLite).
- * Solves audit finding P-C1 (the previous SELECT + INSERT/UPDATE pattern
- * cost 2 SQL round-trips per write).
- *
- * Requires UNIQUE KEY (listing_id, meta_key) on hpct_listing_meta —
- * present by design from HPCT v1.0+.
- *
- * @param int $listing_id Listing post ID.
- * @param string $meta_key Meta key.
- * @param mixed $meta_value Meta value.
- * @return void
- */
- private function upsert_meta( int $listing_id, string $meta_key, $meta_value ): void {
- // maybe_serialize array values to match WordPress's native postmeta semantics.
- $serialized = is_array( $meta_value ) || is_object( $meta_value )
- ? maybe_serialize( $meta_value )
- : (string) $meta_value;
-
- TMDO_DB::upsert(
- TMDO_DB::table( 'hpct_listing_meta' ),
- array(
- 'listing_id' => $listing_id,
- 'meta_key' => $meta_key,
- 'meta_value' => $serialized,
- ),
- array( 'meta_value' ), // Update only meta_value on conflict.
- array( 'listing_id', 'meta_key' ), // Composite unique key.
- array( '%d', '%s', '%s' )
- );
- }
-}
diff --git a/includes/interceptors/class-tmdo-memberships-interceptor.php b/includes/interceptors/class-tmdo-memberships-interceptor.php
deleted file mode 100644
index fad2903..0000000
--- a/includes/interceptors/class-tmdo-memberships-interceptor.php
+++ /dev/null
@@ -1,76 +0,0 @@
- 'plan_id',
- 'hp_user' => 'user_id',
- 'hp_price' => 'price',
- 'hp_order' => 'order_id',
- 'hp_start_date' => 'start_date',
- 'hp_end_date' => 'end_date',
- );
-
- /**
- * Returns the WordPress post_type slug handled by this interceptor.
- *
- * @return string
- */
- protected function get_post_type(): string {
- return 'hp_membership';
- }
-
- /**
- * Returns the table key passed to TMDO_DB::table().
- *
- * @return string
- */
- protected function get_table_key(): string {
- return 'hpct_memberships';
- }
-
- /**
- * Builds the INSERT row for a newly created membership post.
- *
- * @param int $post_id Post ID.
- * @param \WP_Post $post Post object.
- * @param string $now Formatted datetime string.
- * @return array{values: array, formats: array}
- */
- protected function build_insert_data( int $post_id, \WP_Post $post, string $now ): array {
- return array(
- 'values' => array(
- 'post_id' => $post_id,
- 'user_id' => (int) $post->post_author,
- 'status' => $post->post_status,
- 'created_at' => $now,
- 'updated_at' => $now,
- ),
- 'formats' => array( '%d', '%d', '%s', '%s', '%s' ),
- );
- }
-}
diff --git a/includes/interceptors/class-tmdo-messages-interceptor.php b/includes/interceptors/class-tmdo-messages-interceptor.php
deleted file mode 100644
index 777545c..0000000
--- a/includes/interceptors/class-tmdo-messages-interceptor.php
+++ /dev/null
@@ -1,78 +0,0 @@
- 'sender_id',
- 'hp_recipient' => 'recipient_id',
- 'hp_listing' => 'listing_id',
- 'hp_read' => 'is_read',
- );
-
- /**
- * Returns the WordPress post_type slug handled by this interceptor.
- *
- * @return string
- */
- protected function get_post_type(): string {
- return 'hp_message';
- }
-
- /**
- * Returns the table key passed to TMDO_DB::table().
- *
- * @return string
- */
- protected function get_table_key(): string {
- return 'hpct_messages';
- }
-
- /**
- * Builds the INSERT row for a newly created message post.
- *
- * @param int $post_id Post ID.
- * @param \WP_Post $post Post object.
- * @param string $now Formatted datetime string.
- * @return array{values: array, formats: array}
- */
- protected function build_insert_data( int $post_id, \WP_Post $post, string $now ): array {
- return array(
- 'values' => array(
- 'post_id' => $post_id,
- 'thread_id' => (int) $post->post_parent,
- 'sender_id' => (int) $post->post_author,
- 'subject' => sanitize_text_field( $post->post_title ),
- 'body' => $post->post_content,
- 'status' => $post->post_status,
- 'sent_at' => $now,
- 'created_at' => $now,
- 'updated_at' => $now,
- ),
- 'formats' => array( '%d', '%d', '%d', '%s', '%s', '%s', '%s', '%s', '%s' ),
- );
- }
-}
diff --git a/includes/interceptors/class-tmdo-requests-interceptor.php b/includes/interceptors/class-tmdo-requests-interceptor.php
deleted file mode 100644
index 87cef31..0000000
--- a/includes/interceptors/class-tmdo-requests-interceptor.php
+++ /dev/null
@@ -1,74 +0,0 @@
- 'vendor_id',
- 'hp_listing' => 'listing_id',
- 'hp_budget' => 'budget',
- );
-
- /**
- * Returns the WordPress post_type slug handled by this interceptor.
- *
- * @return string
- */
- protected function get_post_type(): string {
- return 'hp_request';
- }
-
- /**
- * Returns the table key passed to TMDO_DB::table().
- *
- * @return string
- */
- protected function get_table_key(): string {
- return 'hpct_requests';
- }
-
- /**
- * Builds the INSERT row for a newly created request post.
- *
- * @param int $post_id Post ID.
- * @param \WP_Post $post Post object.
- * @param string $now Formatted datetime string.
- * @return array{values: array, formats: array}
- */
- protected function build_insert_data( int $post_id, \WP_Post $post, string $now ): array {
- return array(
- 'values' => array(
- 'post_id' => $post_id,
- 'user_id' => (int) $post->post_author,
- 'status' => $post->post_status,
- 'message' => $post->post_content,
- 'created_at' => $now,
- 'updated_at' => $now,
- ),
- 'formats' => array( '%d', '%d', '%s', '%s', '%s', '%s' ),
- );
- }
-}
diff --git a/includes/interceptors/class-tmdo-reviews-interceptor.php b/includes/interceptors/class-tmdo-reviews-interceptor.php
deleted file mode 100644
index fa66e6e..0000000
--- a/includes/interceptors/class-tmdo-reviews-interceptor.php
+++ /dev/null
@@ -1,116 +0,0 @@
- 'rating',
- 'hp_listing' => 'listing_id',
- '_hp_vendor' => 'vendor_id',
- );
-
- /**
- * Returns the WordPress post_type slug handled by this interceptor.
- *
- * @return string
- */
- protected function get_post_type(): string {
- return 'hp_review';
- }
-
- /**
- * Returns the table key passed to TMDO_DB::table().
- *
- * @return string
- */
- protected function get_table_key(): string {
- return 'hpct_reviews';
- }
-
- /**
- * Registers WordPress hooks, including the extra get_post_metadata hook.
- *
- * @return void
- */
- public function register_hooks(): void {
- parent::register_hooks();
- add_filter( 'get_post_metadata', array( $this, 'filter_get_meta' ), 10, 4 );
- }
-
- /**
- * Builds the INSERT row for a newly created review post.
- *
- * @param int $post_id Post ID.
- * @param \WP_Post $post Post object.
- * @param string $now Formatted datetime string.
- * @return array{values: array, formats: array}
- */
- protected function build_insert_data( int $post_id, \WP_Post $post, string $now ): array {
- return array(
- 'values' => array(
- 'post_id' => $post_id,
- 'user_id' => (int) $post->post_author,
- 'status' => $post->post_status,
- 'title' => sanitize_text_field( $post->post_title ),
- 'content' => $post->post_content,
- 'created_at' => $now,
- 'updated_at' => $now,
- ),
- 'formats' => array( '%d', '%d', '%s', '%s', '%s', '%s', '%s' ),
- );
- }
-
- /**
- * Reads hp_rating directly from the reviews flat table.
- *
- * @param mixed $value Short-circuit value.
- * @param int $post_id Post ID.
- * @param string $meta_key Meta key.
- * @param bool $single Whether to return a single value.
- * @return mixed
- */
- public function filter_get_meta( $value, int $post_id, string $meta_key, bool $single ) {
- if ( ! $this->is_enabled() || 'hp_rating' !== $meta_key || 'hp_review' !== get_post_type( $post_id ) ) {
- return $value;
- }
-
- return $this->intercept(
- function () use ( $post_id, $single ) {
- global $wpdb;
- $rating = $wpdb->get_var(
- $wpdb->prepare(
- 'SELECT rating FROM ' . TMDO_DB::table( 'hpct_reviews' ) . ' WHERE post_id = %d LIMIT 1',
- $post_id
- )
- );
- return null !== $rating ? ( $single ? $rating : array( $rating ) ) : null;
- },
- fn() => $value,
- 'get_post_metadata'
- );
- }
-}
diff --git a/includes/interceptors/class-tmdo-statistics-interceptor.php b/includes/interceptors/class-tmdo-statistics-interceptor.php
deleted file mode 100644
index 4f160eb..0000000
--- a/includes/interceptors/class-tmdo-statistics-interceptor.php
+++ /dev/null
@@ -1,139 +0,0 @@
-is_active() ) {
- return $check;
- }
-
- try {
- $this->record_event( $post_id, 'view' );
- } catch ( \Throwable $e ) {
- TMDO_Logger::error( $this->module, 'update_post_metadata', $e->getMessage() );
- }
-
- return $check;
- }
-
- /**
- * Records a view event when a HivePress listing is viewed.
- *
- * @param mixed $listing Listing object or ID.
- * @return void
- */
- public function action_listing_viewed( $listing ): void {
- if ( ! $this->is_active() ) {
- return;
- }
-
- $listing_id = is_object( $listing ) && method_exists( $listing, 'get_id' )
- ? $listing->get_id()
- : ( is_numeric( $listing ) ? (int) $listing : 0 );
-
- if ( ! $listing_id ) {
- return;
- }
-
- try {
- $this->record_event( $listing_id, 'hp_action_view' );
- } catch ( \Throwable $e ) {
- TMDO_Logger::error( $this->module, 'listing_viewed', $e->getMessage() );
- }
- }
-
- /**
- * Deletes statistics records when a listing post is deleted.
- *
- * @param int $post_id Post ID.
- * @param \WP_Post $post Post object.
- * @return void
- */
- public function action_delete_post( int $post_id, \WP_Post $post ): void {
- if ( 'hp_listing' !== $post->post_type || ! $this->is_active() ) {
- return;
- }
-
- try {
- global $wpdb;
- $wpdb->delete( TMDO_DB::table( 'hpct_statistics' ), array( 'listing_id' => $post_id ), array( '%d' ) );
- } catch ( \Throwable $e ) {
- TMDO_Logger::error( $this->module, 'before_delete_post', $e->getMessage() );
- }
- }
-
- /**
- * Records a statistics event to the hpct_statistics table.
- *
- * @param int $listing_id Listing post ID.
- * @param string $event_type Event type identifier.
- * @return void
- */
- private function record_event( int $listing_id, string $event_type ): void {
- global $wpdb;
-
- $user_id = get_current_user_id();
- $ip = sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ?? '' ) );
- $ip_hash = $ip ? hash( 'sha256', $ip ) : '';
-
- $wpdb->insert(
- TMDO_DB::table( 'hpct_statistics' ),
- array(
- 'listing_id' => $listing_id,
- 'event_type' => $event_type,
- 'user_id' => $user_id,
- 'ip_hash' => $ip_hash,
- 'referrer' => substr( esc_url_raw( wp_unslash( $_SERVER['HTTP_REFERER'] ?? '' ) ), 0, 500 ),
- 'created_at' => TMDO_DB::now(),
- ),
- array( '%d', '%s', '%d', '%s', '%s', '%s' )
- );
- }
-}
diff --git a/includes/query/class-tmdo-listing-meta-query.php b/includes/query/class-tmdo-listing-meta-query.php
deleted file mode 100644
index 5d634f1..0000000
--- a/includes/query/class-tmdo-listing-meta-query.php
+++ /dev/null
@@ -1,236 +0,0 @@
-= 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_' );
- }
-}
diff --git a/includes/query/class-tmdo-memberships-query.php b/includes/query/class-tmdo-memberships-query.php
deleted file mode 100644
index 083c8bf..0000000
--- a/includes/query/class-tmdo-memberships-query.php
+++ /dev/null
@@ -1,67 +0,0 @@
- plan_id (BIGINT)
- * hp_user => user_id (BIGINT)
- * hp_price => price (VARCHAR)
- * hp_order => order_id (BIGINT)
- * hp_start_date => start_date (DATETIME)
- * hp_end_date => end_date (DATETIME)
- */
-class TMDO_Memberships_Query extends TMDO_Query_Interceptor_Base {
-
- /**
- * Returns the module identifier.
- *
- * @return string Module name.
- */
- protected function get_module(): string {
- return 'memberships';
- }
-
- /**
- * Returns the post types this interceptor handles.
- *
- * @return string[] Post type slugs.
- */
- protected function get_post_types(): array {
- return array( 'hp_membership' );
- }
-
- /**
- * Returns the custom table name.
- *
- * @return string Table name without prefix.
- */
- protected function get_table(): string {
- return 'hpct_memberships';
- }
-
- /**
- * Returns the meta_key to column mapping for hpct_memberships.
- *
- * @return array Meta key to column name map.
- */
- protected function get_meta_key_map(): array {
- return array(
- 'hp_plan' => 'plan_id',
- 'hp_user' => 'user_id',
- 'hp_price' => 'price',
- 'hp_order' => 'order_id',
- 'hp_start_date' => 'start_date',
- 'hp_end_date' => 'end_date',
- );
- }
-}
diff --git a/includes/query/class-tmdo-messages-query.php b/includes/query/class-tmdo-messages-query.php
deleted file mode 100644
index 604806f..0000000
--- a/includes/query/class-tmdo-messages-query.php
+++ /dev/null
@@ -1,63 +0,0 @@
- sender_id (BIGINT)
- * hp_recipient => recipient_id (BIGINT)
- * hp_listing => listing_id (BIGINT)
- * hp_read => is_read (TINYINT)
- */
-class TMDO_Messages_Query extends TMDO_Query_Interceptor_Base {
-
- /**
- * Returns the module identifier.
- *
- * @return string Module name.
- */
- protected function get_module(): string {
- return 'messages';
- }
-
- /**
- * Returns the post types this interceptor handles.
- *
- * @return string[] Post type slugs.
- */
- protected function get_post_types(): array {
- return array( 'hp_message' );
- }
-
- /**
- * Returns the custom table name.
- *
- * @return string Table name without prefix.
- */
- protected function get_table(): string {
- return 'hpct_messages';
- }
-
- /**
- * Returns the meta_key to column mapping for hpct_messages.
- *
- * @return array Meta key to column name map.
- */
- protected function get_meta_key_map(): array {
- return array(
- 'hp_sender' => 'sender_id',
- 'hp_recipient' => 'recipient_id',
- 'hp_listing' => 'listing_id',
- 'hp_read' => 'is_read',
- );
- }
-}
diff --git a/includes/query/class-tmdo-requests-query.php b/includes/query/class-tmdo-requests-query.php
deleted file mode 100644
index bdf5e58..0000000
--- a/includes/query/class-tmdo-requests-query.php
+++ /dev/null
@@ -1,61 +0,0 @@
- vendor_id (BIGINT)
- * hp_listing => listing_id (BIGINT)
- * hp_budget => budget (VARCHAR)
- */
-class TMDO_Requests_Query extends TMDO_Query_Interceptor_Base {
-
- /**
- * Returns the module identifier.
- *
- * @return string Module name.
- */
- protected function get_module(): string {
- return 'requests';
- }
-
- /**
- * Returns the post types this interceptor handles.
- *
- * @return string[] Post type slugs.
- */
- protected function get_post_types(): array {
- return array( 'hp_request' );
- }
-
- /**
- * Returns the custom table name.
- *
- * @return string Table name without prefix.
- */
- protected function get_table(): string {
- return 'hpct_requests';
- }
-
- /**
- * Returns the meta_key to column mapping for hpct_requests.
- *
- * @return array Meta key to column name map.
- */
- protected function get_meta_key_map(): array {
- return array(
- 'hp_vendor' => 'vendor_id',
- 'hp_listing' => 'listing_id',
- 'hp_budget' => 'budget',
- );
- }
-}
diff --git a/includes/query/class-tmdo-reviews-query.php b/includes/query/class-tmdo-reviews-query.php
deleted file mode 100644
index 55804c6..0000000
--- a/includes/query/class-tmdo-reviews-query.php
+++ /dev/null
@@ -1,64 +0,0 @@
- listing_id (BIGINT)
- * _hp_vendor => vendor_id (BIGINT)
- * hp_rating => rating (TINYINT)
- */
-class TMDO_Reviews_Query extends TMDO_Query_Interceptor_Base {
-
- /**
- * Returns the module identifier.
- *
- * @return string Module name.
- */
- protected function get_module(): string {
- return 'reviews';
- }
-
- /**
- * Returns the post types this interceptor handles.
- *
- * @return string[] Post type slugs.
- */
- protected function get_post_types(): array {
- return array( 'hp_review' );
- }
-
- /**
- * Returns the custom table name.
- *
- * @return string Table name without prefix.
- */
- protected function get_table(): string {
- return 'hpct_reviews';
- }
-
- /**
- * Returns the meta_key to column mapping for hpct_reviews.
- *
- * @return array Meta key to column name map.
- */
- protected function get_meta_key_map(): array {
- return array(
- 'hp_listing' => 'listing_id',
- '_hp_vendor' => 'vendor_id',
- 'hp_rating' => 'rating',
- );
- }
-}
diff --git a/tests/integration/ListingMetaInterceptorTest.php b/tests/integration/ListingMetaInterceptorTest.php
deleted file mode 100644
index 6f0a04a..0000000
--- a/tests/integration/ListingMetaInterceptorTest.php
+++ /dev/null
@@ -1,160 +0,0 @@
-query( "DROP TABLE IF EXISTS `{$wpdb->prefix}hpct_listing_meta`" );
-
- // Simulate HPCT_Core being present — register_hooks() only logs the skip
- // when HPCT is loaded but its table is missing (genuine inconsistency).
- if ( ! class_exists( 'HPCT_Core' ) ) {
- // phpcs:ignore Generic.Commenting.InlineComment.InvalidEndChar -- runtime stub
- eval( 'class HPCT_Core {}' ); // @phpcs:ignore
- }
-
- // Ensure wpdo_errors exists for the skip log; harmless if pre-created.
- $wpdb->query(
- "CREATE TABLE IF NOT EXISTS `{$wpdb->prefix}wpdo_errors` (
- id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
- module varchar(50) NOT NULL DEFAULT '',
- zone varchar(10) NOT NULL DEFAULT '',
- hook varchar(255) NOT NULL DEFAULT '',
- message longtext NOT NULL,
- context longtext,
- created_at datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
- PRIMARY KEY (id),
- KEY idx_module (module)
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"
- );
-
- // Snapshot pre-call count of skip messages.
- $pre = (int) $wpdb->get_var(
- $wpdb->prepare(
- "SELECT COUNT(*) FROM `{$wpdb->prefix}wpdo_errors` WHERE module = %s AND hook = %s",
- 'listing_meta',
- 'register_hooks'
- )
- );
-
- $interceptor = new WPDO_Listing_Meta_Interceptor();
- $interceptor->register_hooks();
-
- $post = (int) $wpdb->get_var(
- $wpdb->prepare(
- "SELECT COUNT(*) FROM `{$wpdb->prefix}wpdo_errors` WHERE module = %s AND hook = %s",
- 'listing_meta',
- 'register_hooks'
- )
- );
- $this->assertSame( $pre + 1, $post, 'Expected exactly one new skip message after register_hooks()' );
- }
-
- /**
- * P-C1: WPDO_DB::upsert() must produce an INSERT ... ON DUPLICATE KEY UPDATE
- * statement (single round-trip), and writes must succeed via the composite
- * unique key (listing_id, meta_key).
- *
- * @runInSeparateProcess
- */
- public function test_upsert_uses_composite_unique_key(): void {
- global $wpdb;
-
- $table = $wpdb->prefix . 'hpct_listing_meta';
-
- // Clean slate.
- $wpdb->query( "DROP TABLE IF EXISTS `{$table}`" );
- $wpdb->query(
- "CREATE TABLE `{$table}` (
- id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
- listing_id bigint(20) unsigned NOT NULL DEFAULT 0,
- meta_key varchar(255) NOT NULL DEFAULT '',
- meta_value longtext,
- PRIMARY KEY (id),
- UNIQUE KEY ui_listing_meta (listing_id, meta_key(191))
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"
- );
-
- // First upsert: INSERT.
- $result1 = WPDO_DB::upsert(
- $table,
- array(
- 'listing_id' => 4242,
- 'meta_key' => 'hp_price',
- 'meta_value' => '100.00',
- ),
- array( 'meta_value' ),
- array( 'listing_id', 'meta_key' ),
- array( '%d', '%s', '%s' )
- );
- $this->assertNotFalse( $result1 );
-
- // Second upsert: UPDATE via the composite key.
- $result2 = WPDO_DB::upsert(
- $table,
- array(
- 'listing_id' => 4242,
- 'meta_key' => 'hp_price',
- 'meta_value' => '250.50',
- ),
- array( 'meta_value' ),
- array( 'listing_id', 'meta_key' ),
- array( '%d', '%s', '%s' )
- );
- $this->assertNotFalse( $result2 );
-
- // Verify only one row exists for (4242, 'hp_price') with the latest value.
- $rows = $wpdb->get_results(
- $wpdb->prepare(
- "SELECT meta_value FROM `{$table}` WHERE listing_id = %d AND meta_key = %s",
- 4242,
- 'hp_price'
- ),
- ARRAY_A
- );
- $this->assertCount( 1, $rows, 'Composite UNIQUE must collapse two upserts into a single row' );
- $this->assertSame( '250.50', $rows[0]['meta_value'] );
-
- // Cleanup.
- $wpdb->query( "DROP TABLE IF EXISTS `{$table}`" );
- }
-
- /**
- * Verify table_exists() returns false when table missing, true after CREATE.
- *
- * @runInSeparateProcess
- */
- public function test_table_exists_detection(): void {
- global $wpdb;
-
- $wpdb->query( "DROP TABLE IF EXISTS `{$wpdb->prefix}hpct_listing_meta`" );
-
- // Use reflection to invoke private static.
- $ref = new ReflectionClass( WPDO_Listing_Meta_Interceptor::class );
- $method = $ref->getMethod( 'table_exists' );
- $method->setAccessible( true );
-
- $this->assertFalse( $method->invoke( null ), 'table_exists must return false when table missing' );
- }
-}
diff --git a/tests/integration/bootstrap.php b/tests/integration/bootstrap.php
index 72f5713..ec73697 100644
--- a/tests/integration/bootstrap.php
+++ b/tests/integration/bootstrap.php
@@ -29,8 +29,6 @@ define( 'TMDO_HIVEPRESS_FILE', TMDO_HIVEPRESS_PATH . '2meet-data-optimizer-hivep
define( 'TMDO_HIVEPRESS_VERSION', '0.1.0' );
// ── AddOn classes ──────────────────────────────────────────────────────────
-// Superset of the unit bootstrap's list: the integration suite also exercises
-// the interceptors, which the unit suite does not load.
$tmdo_hp_files = array(
'includes/class-tmdo-hivepress.php',
@@ -54,12 +52,6 @@ $tmdo_hp_files = array(
foreach ( glob( TMDO_HIVEPRESS_PATH . 'includes/hivepress/adapters/*.php' ) ?: array() as $tmdo_hp_glob ) {
$tmdo_hp_files[] = 'includes/hivepress/adapters/' . basename( $tmdo_hp_glob );
}
-foreach ( glob( TMDO_HIVEPRESS_PATH . 'includes/interceptors/*.php' ) ?: array() as $tmdo_hp_glob ) {
- $tmdo_hp_files[] = 'includes/interceptors/' . basename( $tmdo_hp_glob );
-}
-foreach ( glob( TMDO_HIVEPRESS_PATH . 'includes/query/*.php' ) ?: array() as $tmdo_hp_glob ) {
- $tmdo_hp_files[] = 'includes/query/' . basename( $tmdo_hp_glob );
-}
foreach ( $tmdo_hp_files as $tmdo_hp_file ) {
$tmdo_hp_path = TMDO_HIVEPRESS_PATH . $tmdo_hp_file;
From 19add5fc16c2d379bc45bd3866c0a6d3c5160422 Mon Sep 17 00:00:00 2001
From: wpdev
Date: Fri, 25 Sep 2026 23:43:40 +0800
Subject: [PATCH 06/12] refactor(2meet-data-optimizer-hivepress-addon): take
ownership of hp_* entity groups
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- Add TMDO_HivePress_Entity_Groups registering hp_listing_core / hp_request_core / hp_vendor_core / hp_user, copied verbatim from core (same order; schema hashes match the persisted wpdo_registry_meta rows)
- Drop the core adapter's conflicting hp_user definition (hp_verified / hp_avatar_id — keys HivePress never writes)
- Verify: HivePressEntityGroupsTest (identical to core, same shared-key resolution, no collisions); HP unit 144 / integration 49 OK; hashes = dev20 registry_meta
- PR: PR-019
---
includes/back-compat-aliases.php | 1 +
includes/class-tmdo-hivepress-bootstrap.php | 4 +
.../class-tmdo-hivepress-entity-groups.php | 251 ++++++++++++++++++
.../class-tmdo-hivepress-core-adapter.php | 54 +---
tests/bootstrap.php | 1 +
tests/integration/bootstrap.php | 1 +
tests/unit/HivePressEntityGroupsTest.php | 82 ++++++
7 files changed, 346 insertions(+), 48 deletions(-)
create mode 100644 includes/class-tmdo-hivepress-entity-groups.php
create mode 100644 tests/unit/HivePressEntityGroupsTest.php
diff --git a/includes/back-compat-aliases.php b/includes/back-compat-aliases.php
index 27c5adf..1a729c0 100644
--- a/includes/back-compat-aliases.php
+++ b/includes/back-compat-aliases.php
@@ -30,6 +30,7 @@ add_action(
'TMDO_HivePress_Bootstrap' => 'WPDO_HivePress_Bootstrap',
'TMDO_HivePress_Conflict_Guard' => 'WPDO_HivePress_Conflict_Guard',
'TMDO_HivePress_Core_Adapter' => 'WPDO_HivePress_Core_Adapter',
+ 'TMDO_HivePress_Entity_Groups' => 'WPDO_HivePress_Entity_Groups',
'TMDO_HivePress_Cron_Optimizer' => 'WPDO_HivePress_Cron_Optimizer',
'TMDO_HivePress_Detector' => 'WPDO_HivePress_Detector',
'TMDO_HivePress_Favorites_Adapter' => 'WPDO_HivePress_Favorites_Adapter',
diff --git a/includes/class-tmdo-hivepress-bootstrap.php b/includes/class-tmdo-hivepress-bootstrap.php
index 831d33d..c146afe 100644
--- a/includes/class-tmdo-hivepress-bootstrap.php
+++ b/includes/class-tmdo-hivepress-bootstrap.php
@@ -68,6 +68,7 @@ final class TMDO_HP_Bootstrap {
require_once $base . 'includes/class-tmdo-hivepress-transient-filter.php';
require_once $base . 'includes/class-tmdo-hpct-import.php';
require_once $base . 'includes/class-tmdo-hivepress-term-comment-fields.php';
+ require_once $base . 'includes/class-tmdo-hivepress-entity-groups.php';
require_once $base . 'includes/class-tmdo-listing-stats.php';
}
@@ -97,6 +98,9 @@ final class TMDO_HP_Bootstrap {
TMDO_Hivepress_Term_Comment_Fields::register();
}
+ // Post + user entity groups (hp_listing_core / hp_request_core / hp_vendor_core / hp_user).
+ TMDO_HivePress_Entity_Groups::register();
+
// Admin tab
if ( is_admin() ) {
$admin_file = TMDO_HIVEPRESS_PATH . 'admin/class-tmdo-admin-hivepress.php';
diff --git a/includes/class-tmdo-hivepress-entity-groups.php b/includes/class-tmdo-hivepress-entity-groups.php
new file mode 100644
index 0000000..8e94bf5
--- /dev/null
+++ b/includes/class-tmdo-hivepress-entity-groups.php
@@ -0,0 +1,251 @@
+ 'hp_price',
+ 'type' => 'decimal',
+ 'searchable' => true,
+ 'label' => 'Listing price',
+ ),
+ array(
+ 'key' => 'hp_status',
+ 'type' => 'enum',
+ 'searchable' => true,
+ 'options' => array( 'publish', 'draft', 'pending', 'expired', 'private' ),
+ 'label' => 'Listing status',
+ ),
+ array(
+ 'key' => 'hp_featured',
+ 'type' => 'integer',
+ 'searchable' => true,
+ 'label' => 'Featured flag (0/1)',
+ ),
+ array(
+ 'key' => 'hp_verified',
+ 'type' => 'integer',
+ 'searchable' => true,
+ 'label' => 'Verified flag (0/1)',
+ ),
+ array(
+ 'key' => 'hp_vendor',
+ 'type' => 'integer',
+ 'searchable' => true,
+ 'label' => 'Vendor user ID',
+ ),
+ array(
+ 'key' => 'hp_expired_time',
+ 'type' => 'integer',
+ 'searchable' => true,
+ 'label' => 'Expiry unix ts',
+ ),
+ array(
+ 'key' => 'hp_featured_time',
+ 'type' => 'integer',
+ 'label' => 'Featured-until unix ts',
+ ),
+ array(
+ 'key' => 'hp_view_count',
+ 'type' => 'integer',
+ 'searchable' => true,
+ 'label' => 'View counter',
+ ),
+ array(
+ 'key' => 'hp_rating',
+ 'type' => 'decimal',
+ 'searchable' => true,
+ 'label' => 'Average rating',
+ ),
+ array(
+ 'key' => 'hp_rating_count',
+ 'type' => 'integer',
+ 'label' => 'Rating count',
+ ),
+ array(
+ 'key' => 'hp_hourly_rate',
+ 'type' => 'decimal',
+ 'label' => 'Hourly rate',
+ ),
+ )
+ );
+ }
+ /**
+ * HivePress request core meta keys (post_type=hp_request).
+ *
+ * @return void
+ */
+ private static function register_hp_request_core_group(): void {
+ TMDO_Entity_Registry::register_group(
+ 'post',
+ 'hp_request_core',
+ array(
+ array(
+ 'key' => 'hp_status',
+ 'type' => 'enum',
+ 'searchable' => true,
+ 'options' => array( 'publish', 'draft', 'pending', 'expired' ),
+ 'label' => 'Request status',
+ ),
+ array(
+ 'key' => 'hp_user',
+ 'type' => 'integer',
+ 'searchable' => true,
+ 'label' => 'Request author user ID',
+ ),
+ array(
+ 'key' => 'hp_expired_time',
+ 'type' => 'integer',
+ 'searchable' => true,
+ 'label' => 'Expiry unix ts',
+ ),
+ array(
+ 'key' => 'hp_budget',
+ 'type' => 'decimal',
+ 'searchable' => true,
+ 'label' => 'Budget amount',
+ ),
+ array(
+ 'key' => 'hp_view_count',
+ 'type' => 'integer',
+ 'label' => 'View counter',
+ ),
+ )
+ );
+ }
+ /**
+ * HivePress vendor core meta keys (post_type=hp_vendor).
+ *
+ * @return void
+ */
+ private static function register_hp_vendor_core_group(): void {
+ TMDO_Entity_Registry::register_group(
+ 'post',
+ 'hp_vendor_core',
+ array(
+ array(
+ 'key' => 'hp_user',
+ 'type' => 'integer',
+ 'searchable' => true,
+ 'label' => 'Vendor user ID (linked WP user)',
+ ),
+ array(
+ 'key' => 'hp_verified',
+ 'type' => 'integer',
+ 'searchable' => true,
+ 'label' => 'Verified flag (0/1)',
+ ),
+ array(
+ 'key' => 'hp_hourly_rate',
+ 'type' => 'decimal',
+ 'searchable' => true,
+ 'label' => 'Hourly rate',
+ ),
+ array(
+ 'key' => 'hp_rating_count',
+ 'type' => 'integer',
+ 'label' => 'Rating count',
+ ),
+ array(
+ 'key' => 'hp_rating',
+ 'type' => 'decimal',
+ 'searchable' => true,
+ 'label' => 'Average rating',
+ ),
+ )
+ );
+ }
+ /**
+ * HivePress per-user fields (favorites + avatar attachment).
+ *
+ * Hp_favorited_listings is a serialized array of post IDs in legacy storage;
+ * the migration engine safe_unserialize()s it (allowed_classes=false to block
+ * PHP-object injection) then the json type encodes back to a JSON array column.
+ *
+ * @return void
+ */
+ private static function register_hp_user_group(): void {
+ TMDO_Entity_Registry::register_group(
+ 'user',
+ 'hp_user',
+ array(
+ array(
+ 'key' => 'hp_favorited_listings',
+ 'type' => 'json',
+ 'label' => 'HivePress favorited listing IDs (array)',
+ ),
+ array(
+ 'key' => 'hp_image',
+ 'type' => 'text',
+ 'label' => 'HivePress avatar attachment ID',
+ ),
+ )
+ );
+ }
+}
diff --git a/includes/hivepress/adapters/class-tmdo-hivepress-core-adapter.php b/includes/hivepress/adapters/class-tmdo-hivepress-core-adapter.php
index 25e9664..1580f78 100644
--- a/includes/hivepress/adapters/class-tmdo-hivepress-core-adapter.php
+++ b/includes/hivepress/adapters/class-tmdo-hivepress-core-adapter.php
@@ -2,15 +2,14 @@
/**
* Core HivePress adapter — `hivepress` (the parent plugin).
*
- * Owns three primary HivePress models:
+ * Covers the primary HivePress models:
*
* - `Listing` (post_type=hp_listing) — Hot zone columns for search/filter
* (drafted, featured, verified, expired_time, featured_time).
* - `Vendor` (post_type=hp_vendor) — Hot column `verified`; cold blob
* for image / description (display-only).
- * - `User` (entity=user) — Entity group `hp_user_core` for
- * `hp_verified` and the WP-stock `first_name` / `last_name` /
- * `description`.
+ * - `User` (entity=user) — no zone fields; its `hp_user`
+ * entity group is registered by TMDO_HivePress_Entity_Groups.
*
* This adapter SUPERSEDES the legacy `class-tmdo-hivepress.php` (242 lines).
* Behavioural parity:
@@ -29,9 +28,9 @@
* - `hp_drafted` (Listing) — Hot tinyint(1) for user-dashboard filtering.
* - `hp_expired_time` / `hp_featured_time` (Listing) — Hot bigint indexed
* for cron expiry scan acceleration (replaces postmeta full scan).
- * - `hp_user_core` entity group — first_name / last_name / hp_verified
- * promoted from usermeta to wp_wpdo_user_hp_user (already created by
- * TMDO_Member_Fields v2.7.0).
+ * (This adapter used to register a second `hp_user` definition —
+ * hp_verified / hp_avatar_id, keys HivePress never writes — which the
+ * registry silently dropped; TMDO_HivePress_Entity_Groups owns it now.)
*
* @package WP_Data_Optimizer
* @since 3.0.0
@@ -166,47 +165,6 @@ if ( ! class_exists( 'TMDO_HivePress_Core_Adapter' ) ) {
);
}
- /**
- * Register entity field group for HP User model.
- *
- * The `hp_user` group is already created by `TMDO_Member_Fields`
- * (v2.7.0). We add adapter ownership here for cross-cutting tooling
- * (`wp wpdo hivepress doctor` reports owner; admin UI shows source).
- *
- * The `register_entity_fields()` helper from `TMDO_Anti_EAV_Aware`
- * delegates to `TMDO_Entity_Registry::register_group()` which has
- * internal dedup — so re-registering with same group name is a no-op.
- */
- public function on_register_entity_fields(): void {
- if ( ! class_exists( 'TMDO_Entity_Registry' ) ) {
- return;
- }
-
- // Mirror the (canonical) group definition from TMDO_Member_Fields::HP_USER_FIELDS.
- // Source of truth lives in TMDO_Member_Fields; we register defensively for the
- // case where Member_Fields didn't load (custom builds without that integration).
- $this->register_entity_fields(
- 'user',
- 'hp_user',
- array(
- array(
- 'key' => 'hp_verified',
- 'type' => 'boolean',
- 'default' => false,
- 'searchable' => true,
- 'label' => 'HivePress KYC verified',
- ),
- array(
- 'key' => 'hp_avatar_id',
- 'type' => 'integer',
- 'default' => 0,
- 'searchable' => false,
- 'label' => 'HivePress avatar attachment id',
- ),
- )
- );
- }
-
// ── Query optimization (preserved from legacy class-tmdo-hivepress.php) ──
/**
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
index e358c4e..cbaaa44 100644
--- a/tests/bootstrap.php
+++ b/tests/bootstrap.php
@@ -45,6 +45,7 @@ $tmdo_hp_files = array(
'includes/hivepress/class-tmdo-hivepress-rest.php',
'includes/class-tmdo-hivepress-transient-filter.php',
'includes/class-tmdo-hivepress-term-comment-fields.php',
+ 'includes/class-tmdo-hivepress-entity-groups.php',
'includes/class-tmdo-hpct-import.php',
'includes/class-tmdo-listing-stats.php',
'admin/class-tmdo-admin-hivepress.php',
diff --git a/tests/integration/bootstrap.php b/tests/integration/bootstrap.php
index ec73697..1b21448 100644
--- a/tests/integration/bootstrap.php
+++ b/tests/integration/bootstrap.php
@@ -44,6 +44,7 @@ $tmdo_hp_files = array(
'includes/hivepress/class-tmdo-hivepress-rest.php',
'includes/class-tmdo-hivepress-transient-filter.php',
'includes/class-tmdo-hivepress-term-comment-fields.php',
+ 'includes/class-tmdo-hivepress-entity-groups.php',
'includes/class-tmdo-hpct-import.php',
'admin/class-tmdo-admin-hivepress.php',
'cli/class-tmdo-cli-hivepress.php',
diff --git a/tests/unit/HivePressEntityGroupsTest.php b/tests/unit/HivePressEntityGroupsTest.php
new file mode 100644
index 0000000..fa917d2
--- /dev/null
+++ b/tests/unit/HivePressEntityGroupsTest.php
@@ -0,0 +1,82 @@
+createMock( TMDO_Entity_Adapter_Interface::class ) );
+ TMDO_Entity_Registry::register_adapter( 'user', $this->createMock( TMDO_Entity_Adapter_Interface::class ) );
+ }
+
+ private function register_core(): void {
+ TMDO_Post_Fields::register_entity_fields();
+ TMDO_Member_Fields::register_entity_fields();
+ }
+
+ public function test_definitions_match_core_exactly(): void {
+ $this->fresh_registry();
+ $this->register_core();
+ $core = array();
+ foreach ( self::GROUPS as $g ) {
+ $core[ $g[1] ] = TMDO_Entity_Registry::get_group_fields( $g[0], $g[1] );
+ }
+
+ $this->fresh_registry();
+ TMDO_HivePress_Entity_Groups::register_entity_fields();
+
+ foreach ( self::GROUPS as $g ) {
+ $this->assertNotEmpty( $core[ $g[1] ], "core no longer registers {$g[1]}" );
+ $this->assertSame( $core[ $g[1] ], TMDO_Entity_Registry::get_group_fields( $g[0], $g[1] ), "{$g[0]}/{$g[1]} drifted from core" );
+ }
+ }
+
+ public function test_shared_keys_resolve_to_the_same_group_as_core(): void {
+ // hp_status / hp_user / hp_view_count … appear in several post groups and
+ // field_index is last-registered-wins, so registration order matters.
+ $this->fresh_registry();
+ $this->register_core();
+ $core = array();
+ foreach ( self::GROUPS as $g ) {
+ foreach ( TMDO_Entity_Registry::get_group_keys( $g[0], $g[1] ) as $key ) {
+ $core[ $g[0] . ':' . $key ] = TMDO_Entity_Registry::get_field( $g[0], $key )['group'];
+ }
+ }
+
+ $this->fresh_registry();
+ TMDO_HivePress_Entity_Groups::register_entity_fields();
+
+ foreach ( $core as $id => $group ) {
+ list( $type, $key ) = explode( ':', $id, 2 );
+ $this->assertSame( $group, TMDO_Entity_Registry::get_field( $type, $key )['group'], $id );
+ }
+ }
+
+ public function test_registering_alongside_core_raises_no_collision(): void {
+ $this->fresh_registry();
+ $this->register_core();
+
+ TMDO_HivePress_Entity_Groups::register_entity_fields();
+ ( new TMDO_HivePress_Core_Adapter() )->on_register_entity_fields();
+
+ $this->assertSame( array(), TMDO_Entity_Registry::get_collisions() );
+ }
+}
From 7d0edb6228d97d1e0e6ea1dc5e336455a12cdef9 Mon Sep 17 00:00:00 2001
From: wpdev
Date: Fri, 25 Sep 2026 23:49:00 +0800
Subject: [PATCH 07/12] test(2meet-data-optimizer-hivepress-addon): pin hp
entity group definitions to persisted schema hashes
- HivePressEntityGroupsTest no longer loads core TMDO_Post_Fields / TMDO_Member_Fields, so it survives core dropping them (PR-020b)
- Pin the 4 groups to their wpdo_registry_meta hashes and the current owner of the 8 shared post keys (documented as existing behaviour, not intent)
- Verify: HP unit 144 OK; mutating one field type fails the hash test
- PR: PR-020a
---
tests/unit/HivePressEntityGroupsTest.php | 93 +++++++++++-------------
1 file changed, 41 insertions(+), 52 deletions(-)
diff --git a/tests/unit/HivePressEntityGroupsTest.php b/tests/unit/HivePressEntityGroupsTest.php
index fa917d2..dc4be8a 100644
--- a/tests/unit/HivePressEntityGroupsTest.php
+++ b/tests/unit/HivePressEntityGroupsTest.php
@@ -7,74 +7,63 @@ use PHPUnit\Framework\TestCase;
* The AddOn owns the HivePress entity groups core used to register.
*
* Their definitions are persisted (wpdo_registry_meta schema hash) and the
- * flat tables on live sites were built from core's version, so the AddOn must
- * register byte-identical definitions: any drift changes the schema hash and
- * would ALTER tables that hold aeav_only data once core stops registering
- * them (PR-020).
+ * flat tables on live sites were built from them, so they are pinned to the
+ * persisted hashes: any drift would ALTER flat tables that may hold
+ * aeav_only data. (Values recorded on dev20, 2026-09-25.)
*/
class HivePressEntityGroupsTest extends TestCase {
- private const GROUPS = array(
- array( 'post', 'hp_listing_core' ),
- array( 'post', 'hp_request_core' ),
- array( 'post', 'hp_vendor_core' ),
- array( 'user', 'hp_user' ),
+ private const PERSISTED_HASHES = array(
+ 'post/hp_listing_core' => 'fb5ab760',
+ 'post/hp_request_core' => '27d6f4f5',
+ 'post/hp_vendor_core' => '5366e5c8',
+ 'user/hp_user' => '61ac526f',
);
- private function fresh_registry(): void {
+ /**
+ * Keys shared by several post groups resolve to the LAST group registered
+ * (the post entity has a single field index, not one per post_type).
+ *
+ * ⚠️ This pins current behaviour, not desired behaviour: a listing's
+ * hp_rating is routed to the hp_vendor_core table, its hp_status to
+ * hp_request_core, etc. It is pinned so that moving the registration
+ * between plugins cannot change routing silently; fixing the routing is
+ * tracked separately in the backlog.
+ */
+ private const SHARED_KEY_OWNERS = array(
+ 'hp_status' => 'hp_request_core',
+ 'hp_expired_time' => 'hp_request_core',
+ 'hp_view_count' => 'hp_request_core',
+ 'hp_verified' => 'hp_vendor_core',
+ 'hp_rating' => 'hp_vendor_core',
+ 'hp_rating_count' => 'hp_vendor_core',
+ 'hp_hourly_rate' => 'hp_vendor_core',
+ 'hp_user' => 'hp_vendor_core',
+ );
+
+ protected function setUp(): void {
TMDO_Entity_Registry::init();
TMDO_Entity_Registry::register_adapter( 'post', $this->createMock( TMDO_Entity_Adapter_Interface::class ) );
TMDO_Entity_Registry::register_adapter( 'user', $this->createMock( TMDO_Entity_Adapter_Interface::class ) );
- }
-
- private function register_core(): void {
- TMDO_Post_Fields::register_entity_fields();
- TMDO_Member_Fields::register_entity_fields();
- }
-
- public function test_definitions_match_core_exactly(): void {
- $this->fresh_registry();
- $this->register_core();
- $core = array();
- foreach ( self::GROUPS as $g ) {
- $core[ $g[1] ] = TMDO_Entity_Registry::get_group_fields( $g[0], $g[1] );
- }
-
- $this->fresh_registry();
TMDO_HivePress_Entity_Groups::register_entity_fields();
+ }
- foreach ( self::GROUPS as $g ) {
- $this->assertNotEmpty( $core[ $g[1] ], "core no longer registers {$g[1]}" );
- $this->assertSame( $core[ $g[1] ], TMDO_Entity_Registry::get_group_fields( $g[0], $g[1] ), "{$g[0]}/{$g[1]} drifted from core" );
+ public function test_definitions_match_persisted_schema_hashes(): void {
+ foreach ( self::PERSISTED_HASHES as $id => $hash ) {
+ list( $type, $group ) = explode( '/', $id );
+ $fields = TMDO_Entity_Registry::get_group_fields( $type, $group );
+ $this->assertNotEmpty( $fields, "{$id} not registered" );
+ $this->assertSame( $hash, substr( TMDO_Schema_Manager::calculate_schema_hash( $fields ), 0, 8 ), "{$id} drifted from the persisted definition" );
}
}
- public function test_shared_keys_resolve_to_the_same_group_as_core(): void {
- // hp_status / hp_user / hp_view_count … appear in several post groups and
- // field_index is last-registered-wins, so registration order matters.
- $this->fresh_registry();
- $this->register_core();
- $core = array();
- foreach ( self::GROUPS as $g ) {
- foreach ( TMDO_Entity_Registry::get_group_keys( $g[0], $g[1] ) as $key ) {
- $core[ $g[0] . ':' . $key ] = TMDO_Entity_Registry::get_field( $g[0], $key )['group'];
- }
- }
-
- $this->fresh_registry();
- TMDO_HivePress_Entity_Groups::register_entity_fields();
-
- foreach ( $core as $id => $group ) {
- list( $type, $key ) = explode( ':', $id, 2 );
- $this->assertSame( $group, TMDO_Entity_Registry::get_field( $type, $key )['group'], $id );
+ public function test_shared_keys_keep_their_current_owner(): void {
+ foreach ( self::SHARED_KEY_OWNERS as $key => $group ) {
+ $this->assertSame( $group, TMDO_Entity_Registry::get_field( 'post', $key )['group'], $key );
}
}
- public function test_registering_alongside_core_raises_no_collision(): void {
- $this->fresh_registry();
- $this->register_core();
-
- TMDO_HivePress_Entity_Groups::register_entity_fields();
+ public function test_core_adapter_does_not_register_a_conflicting_group(): void {
( new TMDO_HivePress_Core_Adapter() )->on_register_entity_fields();
$this->assertSame( array(), TMDO_Entity_Registry::get_collisions() );
From 3c57fb0ee6be969e4adde25e9f04eb16bd35616d Mon Sep 17 00:00:00 2001
From: wpdev
Date: Fri, 25 Sep 2026 23:57:52 +0800
Subject: [PATCH 08/12] fix(2meet-data-optimizer-hivepress-addon): declare
post_type scopes on HivePress post groups
- hp_listing_core / hp_request_core / hp_vendor_core declare hp_listing / hp_request / hp_vendor, matching core (PR-033), so the AddOn keeps scoped routing once core stops registering them (PR-020b)
- Test: with tmdo_post_scoped_routing on, every key of each group resolves to its own post type's group; legacy owner and hash pins unchanged
- Verify: HP unit 145 / integration 49 OK
- PR: PR-033b
---
.../class-tmdo-hivepress-entity-groups.php | 14 ++++++----
tests/unit/HivePressEntityGroupsTest.php | 27 +++++++++++++------
2 files changed, 28 insertions(+), 13 deletions(-)
diff --git a/includes/class-tmdo-hivepress-entity-groups.php b/includes/class-tmdo-hivepress-entity-groups.php
index 8e94bf5..f0e5e58 100644
--- a/includes/class-tmdo-hivepress-entity-groups.php
+++ b/includes/class-tmdo-hivepress-entity-groups.php
@@ -15,8 +15,9 @@
* persisted in wpdo_registry_meta (originally registered by core's
* TMDO_Post_Fields / TMDO_Member_Fields). Any drift changes the schema hash
* and triggers ALTER TABLE on flat tables that may hold aeav_only data.
- * The three post groups also share keys (hp_status, hp_user, …) and the
- * entity field index is last-registered-wins, so keep their order.
+ * The three post groups share keys (hp_status, hp_user, …): each declares
+ * its post type so a post's key resolves to its own type's group, while
+ * lookups without a post type keep last-registered-wins — keep their order.
*
* @package TMDO_HIVEPRESS
*/
@@ -133,7 +134,8 @@ final class TMDO_HivePress_Entity_Groups {
'type' => 'decimal',
'label' => 'Hourly rate',
),
- )
+ ),
+ array( 'post_types' => array( 'hp_listing' ) )
);
}
/**
@@ -176,7 +178,8 @@ final class TMDO_HivePress_Entity_Groups {
'type' => 'integer',
'label' => 'View counter',
),
- )
+ ),
+ array( 'post_types' => array( 'hp_request' ) )
);
}
/**
@@ -218,7 +221,8 @@ final class TMDO_HivePress_Entity_Groups {
'searchable' => true,
'label' => 'Average rating',
),
- )
+ ),
+ array( 'post_types' => array( 'hp_vendor' ) )
);
}
/**
diff --git a/tests/unit/HivePressEntityGroupsTest.php b/tests/unit/HivePressEntityGroupsTest.php
index dc4be8a..1273406 100644
--- a/tests/unit/HivePressEntityGroupsTest.php
+++ b/tests/unit/HivePressEntityGroupsTest.php
@@ -21,14 +21,10 @@ class HivePressEntityGroupsTest extends TestCase {
);
/**
- * Keys shared by several post groups resolve to the LAST group registered
- * (the post entity has a single field index, not one per post_type).
- *
- * ⚠️ This pins current behaviour, not desired behaviour: a listing's
- * hp_rating is routed to the hp_vendor_core table, its hp_status to
- * hp_request_core, etc. It is pinned so that moving the registration
- * between plugins cannot change routing silently; fixing the routing is
- * tracked separately in the backlog.
+ * Lookups that do not pass a post type (or run with scoped routing off,
+ * until a site's rows are reconciled) keep the legacy owner of keys shared
+ * by several post groups: the LAST group registered. Pinned so the
+ * registration order cannot change silently.
*/
private const SHARED_KEY_OWNERS = array(
'hp_status' => 'hp_request_core',
@@ -48,6 +44,10 @@ class HivePressEntityGroupsTest extends TestCase {
TMDO_HivePress_Entity_Groups::register_entity_fields();
}
+ protected function tearDown(): void {
+ unset( $GLOBALS['_wp_options'][ TMDO_Entity_Registry::SCOPED_ROUTING_OPTION ] );
+ }
+
public function test_definitions_match_persisted_schema_hashes(): void {
foreach ( self::PERSISTED_HASHES as $id => $hash ) {
list( $type, $group ) = explode( '/', $id );
@@ -63,6 +63,17 @@ class HivePressEntityGroupsTest extends TestCase {
}
}
+ public function test_shared_keys_follow_the_post_type_when_scoped_routing_is_on(): void {
+ $GLOBALS['_wp_options'][ TMDO_Entity_Registry::SCOPED_ROUTING_OPTION ] = '1';
+
+ foreach ( array( 'hp_listing' => 'hp_listing_core', 'hp_request' => 'hp_request_core', 'hp_vendor' => 'hp_vendor_core' ) as $post_type => $group ) {
+ foreach ( TMDO_Entity_Registry::get_group_keys( 'post', $group ) as $key ) {
+ $this->assertSame( $group, TMDO_Entity_Registry::get_field( 'post', $key, $post_type )['group'], "{$post_type}:{$key}" );
+ }
+ }
+ $this->assertNull( TMDO_Entity_Registry::get_field( 'post', 'hp_rating', 'post' ), 'HivePress keys stay native on other post types' );
+ }
+
public function test_core_adapter_does_not_register_a_conflicting_group(): void {
( new TMDO_HivePress_Core_Adapter() )->on_register_entity_fields();
From dca134b5b60826edd36a8e0f825be1084511ac79 Mon Sep 17 00:00:00 2001
From: wpdev
Date: Sat, 26 Sep 2026 00:16:00 +0800
Subject: [PATCH 09/12] refactor(2meet-data-optimizer-hivepress-addon): remove
TMDO_HPCT_Import
- Delete the HPCT importer (no caller left after core dropped HPCT in PR-020c) and its bootstrap/alias/test-bootstrap entries
- Conflict guard notice, log line and HivePress admin tab no longer point to the never-existing `wp wpdo hivepress migrate-from-hpct`; they say to deactivate the legacy plugins (their own tables are not imported)
- Verify: ConflictGuardTest guard; HP unit 146 / integration 49 OK, bootstrap smoke OK
- PR: PR-020d
---
admin/class-tmdo-admin-hivepress.php | 2 +-
includes/back-compat-aliases.php | 1 -
includes/class-tmdo-hivepress-bootstrap.php | 1 -
includes/class-tmdo-hpct-import.php | 248 ------------------
.../class-tmdo-hivepress-conflict-guard.php | 9 +-
tests/bootstrap.php | 1 -
tests/integration/bootstrap.php | 1 -
tests/unit/ConflictGuardTest.php | 10 +
8 files changed, 15 insertions(+), 258 deletions(-)
delete mode 100644 includes/class-tmdo-hpct-import.php
diff --git a/admin/class-tmdo-admin-hivepress.php b/admin/class-tmdo-admin-hivepress.php
index 462a725..ebe1c68 100644
--- a/admin/class-tmdo-admin-hivepress.php
+++ b/admin/class-tmdo-admin-hivepress.php
@@ -138,7 +138,7 @@ wp wpdo hivepress benchmark
. esc_html__( '偵測到衝突外掛:', 'tmdo-hivepress' ) . ' '
. esc_html( implode( ', ', $conflicts ) )
. '
'
- . esc_html__( 'WPDO HivePress integration 已自動退讓。請執行 `wp wpdo hivepress migrate-from-hpct` 後停用舊外掛。', 'tmdo-hivepress' )
+ . esc_html__( 'WPDO HivePress integration 已自動退讓。停用舊外掛後即由 WPDO 接手(舊外掛自有資料表的資料不會匯入)。', 'tmdo-hivepress' )
. '
';
}
diff --git a/includes/back-compat-aliases.php b/includes/back-compat-aliases.php
index 1a729c0..2d30a89 100644
--- a/includes/back-compat-aliases.php
+++ b/includes/back-compat-aliases.php
@@ -20,7 +20,6 @@ add_action(
$aliases = array(
'TMDO_Admin_HivePress' => 'WPDO_Admin_HivePress',
'TMDO_CLI_HivePress' => 'WPDO_CLI_HivePress',
- 'TMDO_HPCT_Import' => 'WPDO_HPCT_Import',
'TMDO_HP_Bootstrap' => 'WPDO_HP_Bootstrap',
'TMDO_HivePress' => 'WPDO_HivePress',
'TMDO_HivePress_Attribute_Bridge' => 'WPDO_HivePress_Attribute_Bridge',
diff --git a/includes/class-tmdo-hivepress-bootstrap.php b/includes/class-tmdo-hivepress-bootstrap.php
index c146afe..354925f 100644
--- a/includes/class-tmdo-hivepress-bootstrap.php
+++ b/includes/class-tmdo-hivepress-bootstrap.php
@@ -66,7 +66,6 @@ final class TMDO_HP_Bootstrap {
// Misc HP integration files
require_once $base . 'includes/class-tmdo-hivepress.php';
require_once $base . 'includes/class-tmdo-hivepress-transient-filter.php';
- require_once $base . 'includes/class-tmdo-hpct-import.php';
require_once $base . 'includes/class-tmdo-hivepress-term-comment-fields.php';
require_once $base . 'includes/class-tmdo-hivepress-entity-groups.php';
require_once $base . 'includes/class-tmdo-listing-stats.php';
diff --git a/includes/class-tmdo-hpct-import.php b/includes/class-tmdo-hpct-import.php
deleted file mode 100644
index 71f72d9..0000000
--- a/includes/class-tmdo-hpct-import.php
+++ /dev/null
@@ -1,248 +0,0 @@
- WP Data Optimizer > HPCT Import tab
- * - CLI: wp wpdo import-hpct
- */
-class TMDO_HPCT_Import {
-
- /** Option key that marks import as completed. */
- public const IMPORTED_OPTION = 'wpdo_hpct_imported';
-
- /**
- * Check if HPCT import has already been completed.
- */
- public static function is_imported(): bool {
- return (bool) get_option( self::IMPORTED_OPTION, false );
- }
-
- /**
- * Check if hp-custom-tables is active and has data to import.
- */
- public static function can_import(): bool {
- if ( self::is_imported() ) {
- return false;
- }
-
- return TMDO_Compatibility::is_hpct_active();
- }
-
- /**
- * Preview: return what will be imported without actually doing it.
- *
- * @return array{modules: array, migration_records: int, has_data: bool}
- */
- public static function preview(): array {
- $hpct_features = get_option( 'hpct_features', array() );
- if ( ! is_array( $hpct_features ) ) {
- $hpct_features = array();
- }
-
- $modules = array();
- foreach ( TMDO_Feature_Flags::HPCT_MODULES as $module ) {
- $hpct_status = $hpct_features[ $module ] ?? 'disabled';
- $wpdo_state = TMDO_Feature_Flags::map_hpct_status( $hpct_status );
-
- $modules[] = array(
- 'module' => $module,
- 'hpct_status' => $hpct_status,
- 'wpdo_state' => $wpdo_state,
- );
- }
-
- // Count HPCT migration records.
- global $wpdb;
- $hpct_migrations_table = $wpdb->prefix . 'hpct_migrations';
- $migration_count = 0;
-
- // Check if hpct_migrations table exists.
- if ( TMDO_IS_SQLITE ) {
- $table_exists = $wpdb->get_var(
- $wpdb->prepare(
- "SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name = %s",
- $hpct_migrations_table
- )
- );
- } else {
- $table_exists = $wpdb->get_var(
- $wpdb->prepare(
- 'SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = %s',
- $hpct_migrations_table
- )
- );
- }
-
- if ( $table_exists ) {
- $migration_count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$hpct_migrations_table}`" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name is $wpdb->prefix . 'hpct_migrations'.
- }
-
- return array(
- 'modules' => $modules,
- 'migration_records' => $migration_count,
- 'has_data' => ! empty( $hpct_features ),
- );
- }
-
- /**
- * Execute the import.
- *
- * @return array{success: bool, message: string, imported: array}
- */
- public static function run(): array {
- if ( self::is_imported() ) {
- return array(
- 'success' => false,
- 'message' => 'HPCT import has already been completed.',
- 'imported' => array(),
- );
- }
-
- // Step 1: Read HPCT feature flags.
- $hpct_features = get_option( 'hpct_features', array() );
- if ( ! is_array( $hpct_features ) ) {
- $hpct_features = array();
- }
-
- $imported = array();
-
- // Step 2: Map each HPCT module state to WPDO state.
- foreach ( TMDO_Feature_Flags::HPCT_MODULES as $module ) {
- $hpct_status = $hpct_features[ $module ] ?? 'disabled';
- $wpdo_state = TMDO_Feature_Flags::map_hpct_status( $hpct_status );
-
- TMDO_Feature_Flags::set( $module, $wpdo_state );
-
- $imported[] = array(
- 'module' => $module,
- 'hpct_status' => $hpct_status,
- 'wpdo_state' => $wpdo_state,
- );
- }
-
- // Step 3: Copy HPCT migration records to WPDO.
- self::copy_migration_records();
-
- // Step 4: Mark import as completed.
- update_option( self::IMPORTED_OPTION, true );
-
- return array(
- 'success' => true,
- 'message' => sprintf(
- 'Successfully imported %d HPCT modules. You can now deactivate hp-custom-tables.',
- count( $imported )
- ),
- 'imported' => $imported,
- );
- }
-
- /**
- * Copy HPCT migration progress records to wpdo_migrations.
- */
- private static function copy_migration_records(): void {
- global $wpdb;
-
- $hpct_table = $wpdb->prefix . 'hpct_migrations';
- $wpdo_table = TMDO_DB::table( 'wpdo_migrations' );
- $now = TMDO_DB::now();
-
- // Check if hpct_migrations table exists.
- $exists = false;
- if ( TMDO_IS_SQLITE ) {
- $exists = (bool) $wpdb->get_var(
- $wpdb->prepare(
- "SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name = %s",
- $hpct_table
- )
- );
- } else {
- $exists = (bool) $wpdb->get_var(
- $wpdb->prepare(
- 'SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = %s',
- $hpct_table
- )
- );
- }
-
- if ( ! $exists ) {
- return;
- }
-
- $hpct_records = $wpdb->get_results( "SELECT * FROM `{$hpct_table}`", ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name is $wpdb->prefix . 'hpct_migrations'.
- if ( ! $hpct_records ) {
- return;
- }
-
- foreach ( $hpct_records as $record ) {
- $module = $record['module'] ?? '';
- if ( ! $module ) {
- continue;
- }
-
- // Map HPCT status to WPDO state.
- $hpct_status = $record['status'] ?? 'pending';
- $state = self::map_migration_status( $hpct_status );
-
- // Check if WPDO already has a record for this module.
- // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from TMDO_DB::table().
- $existing = $wpdb->get_var(
- $wpdb->prepare(
- "SELECT id FROM `{$wpdo_table}` WHERE module = %s",
- $module
- )
- );
- // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
-
- $data = array(
- 'module' => $module,
- 'zone' => '',
- 'state' => $state,
- 'total_rows' => (int) ( $record['total_rows'] ?? 0 ),
- 'processed_rows' => (int) ( $record['processed_rows'] ?? 0 ),
- 'last_offset' => (int) ( $record['last_offset'] ?? 0 ),
- 'error_count' => (int) ( $record['error_count'] ?? 0 ),
- 'started_at' => $record['started_at'] ?? null,
- 'completed_at' => $record['completed_at'] ?? null,
- 'updated_at' => $now,
- );
-
- if ( $existing ) {
- $wpdb->update( $wpdo_table, $data, array( 'id' => (int) $existing ) );
- } else {
- $data['created_at'] = $now;
- $wpdb->insert( $wpdo_table, $data );
- }
- }
- }
-
- /**
- * Map HPCT migration record status to WPDO migration state.
- *
- * @param string $hpct_status HPCT migration status string.
- * @return string Corresponding WPDO migration state.
- */
- private static function map_migration_status( string $hpct_status ): string {
- return match ( $hpct_status ) {
- 'pending' => 'idle',
- 'running' => 'backfill',
- 'completed' => 'verify',
- 'failed' => 'idle',
- default => 'idle',
- };
- }
-}
diff --git a/includes/hivepress/class-tmdo-hivepress-conflict-guard.php b/includes/hivepress/class-tmdo-hivepress-conflict-guard.php
index 56523d2..b0e9fe0 100644
--- a/includes/hivepress/class-tmdo-hivepress-conflict-guard.php
+++ b/includes/hivepress/class-tmdo-hivepress-conflict-guard.php
@@ -10,9 +10,8 @@
*
* Strategy: detect, log, render admin notice with guidance, AND short-circuit
* the WPDO HivePress bootstrap via `wpdo_hivepress_should_bind` filter so
- * adapters are NOT bound. Operator must explicitly run
- * `wp wpdo hivepress migrate-from-hpct` to switch to WPDO and deactivate
- * the legacy plugins.
+ * adapters are NOT bound. The operator deactivates the legacy plugins to let
+ * WPDO take over; data kept in their own tables is not imported.
*
* @package WP_Data_Optimizer
* @since 3.0.0
@@ -163,7 +162,7 @@ if ( ! class_exists( 'TMDO_HivePress_Conflict_Guard' ) ) {
$message = sprintf(
/* translators: %s: comma-separated list of conflicting plugin names. */
- __( 'wp-data-optimizer detected legacy HivePress data-layer plugins: %s. WPDO HivePress integration is paused to prevent dual-write corruption. Run `wp wpdo hivepress migrate-from-hpct` to migrate, then deactivate the legacy plugins.', 'tmdo-hivepress' ),
+ __( 'wp-data-optimizer detected legacy HivePress data-layer plugins: %s. WPDO HivePress integration is paused to prevent dual-write corruption. Deactivate the legacy plugins to let WPDO take over (data in their own tables is not imported).', 'tmdo-hivepress' ),
implode( ', ', $names )
);
@@ -206,7 +205,7 @@ if ( ! class_exists( 'TMDO_HivePress_Conflict_Guard' ) ) {
}
$message = sprintf(
- 'Conflict detected: %s. wp-data-optimizer fully supersedes these. Run: wp wpdo hivepress migrate-from-hpct',
+ 'Conflict detected: %s. wp-data-optimizer fully supersedes these. Deactivate them to let WPDO take over.',
implode( ', ', $conflicts )
);
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
index cbaaa44..c4a0cc2 100644
--- a/tests/bootstrap.php
+++ b/tests/bootstrap.php
@@ -46,7 +46,6 @@ $tmdo_hp_files = array(
'includes/class-tmdo-hivepress-transient-filter.php',
'includes/class-tmdo-hivepress-term-comment-fields.php',
'includes/class-tmdo-hivepress-entity-groups.php',
- 'includes/class-tmdo-hpct-import.php',
'includes/class-tmdo-listing-stats.php',
'admin/class-tmdo-admin-hivepress.php',
'cli/class-tmdo-cli-hivepress.php',
diff --git a/tests/integration/bootstrap.php b/tests/integration/bootstrap.php
index 1b21448..b34bf9d 100644
--- a/tests/integration/bootstrap.php
+++ b/tests/integration/bootstrap.php
@@ -45,7 +45,6 @@ $tmdo_hp_files = array(
'includes/class-tmdo-hivepress-transient-filter.php',
'includes/class-tmdo-hivepress-term-comment-fields.php',
'includes/class-tmdo-hivepress-entity-groups.php',
- 'includes/class-tmdo-hpct-import.php',
'admin/class-tmdo-admin-hivepress.php',
'cli/class-tmdo-cli-hivepress.php',
);
diff --git a/tests/unit/ConflictGuardTest.php b/tests/unit/ConflictGuardTest.php
index 8243854..9ef19f0 100644
--- a/tests/unit/ConflictGuardTest.php
+++ b/tests/unit/ConflictGuardTest.php
@@ -47,6 +47,16 @@ class HivePressConflictGuardTest extends TestCase {
$this->assertSame( '', $out );
}
+ public function test_guidance_does_not_point_to_a_missing_command(): void {
+ // There is no HPCT importer: `wp wpdo hivepress migrate-from-hpct` never
+ // existed and TMDO_HPCT_Import had no caller once core dropped HPCT.
+ $root = dirname( __DIR__, 2 );
+ foreach ( array( 'includes/hivepress/class-tmdo-hivepress-conflict-guard.php', 'admin/class-tmdo-admin-hivepress.php' ) as $file ) {
+ $this->assertStringNotContainsString( 'migrate-from-hpct', (string) file_get_contents( $root . '/' . $file ), $file );
+ }
+ $this->assertFileDoesNotExist( $root . '/includes/class-tmdo-hpct-import.php' );
+ }
+
public function test_memo_cache_avoids_repeat_probing(): void {
WPDO_HivePress_Conflict_Guard::reset_for_tests();
$first = WPDO_HivePress_Conflict_Guard::detect_conflicts();
From 69c8bf2a88c641dd2b3a97e6a1309ae360994913 Mon Sep 17 00:00:00 2001
From: wpdev
Date: Sat, 26 Sep 2026 00:24:43 +0800
Subject: [PATCH 10/12] feat(2meet-data-optimizer-hivepress-addon): provide
HivePress defaults to core via filters
- TMDO_HivePress_Entity_Groups hooks tmdo_rest_default_post_type so GET /wpdo/v1/listings keeps defaulting to hp_listing now that core defaults to 'post' (PR-020e)
- Test: TMDO_Post_Group_Reconciler::plan() over the AddOn's groups yields the 8 misrouted HivePress keys (coverage moved out of core in PR-020b)
- Verify: HP unit 148 / integration 49 OK, bootstrap smoke OK
- PR: PR-020f
---
.../class-tmdo-hivepress-entity-groups.php | 12 +++++++-
tests/unit/HivePressEntityGroupsTest.php | 29 +++++++++++++++++++
2 files changed, 40 insertions(+), 1 deletion(-)
diff --git a/includes/class-tmdo-hivepress-entity-groups.php b/includes/class-tmdo-hivepress-entity-groups.php
index f0e5e58..798fba3 100644
--- a/includes/class-tmdo-hivepress-entity-groups.php
+++ b/includes/class-tmdo-hivepress-entity-groups.php
@@ -32,12 +32,22 @@ if ( ! defined( 'ABSPATH' ) ) {
final class TMDO_HivePress_Entity_Groups {
/**
- * Hook into WPDO entity field registration.
+ * Hook into WPDO entity field registration and the REST default post type.
*
* @return void
*/
public static function register(): void {
add_action( 'wpdo_register_entity_fields', array( __CLASS__, 'register_entity_fields' ) );
+ add_filter( 'tmdo_rest_default_post_type', array( __CLASS__, 'rest_default_post_type' ) );
+ }
+
+ /**
+ * GET /wpdo/v1/listings serves HivePress listings when no post type is given.
+ *
+ * @return string
+ */
+ public static function rest_default_post_type(): string {
+ return 'hp_listing';
}
/**
diff --git a/tests/unit/HivePressEntityGroupsTest.php b/tests/unit/HivePressEntityGroupsTest.php
index 1273406..72fface 100644
--- a/tests/unit/HivePressEntityGroupsTest.php
+++ b/tests/unit/HivePressEntityGroupsTest.php
@@ -46,6 +46,7 @@ class HivePressEntityGroupsTest extends TestCase {
protected function tearDown(): void {
unset( $GLOBALS['_wp_options'][ TMDO_Entity_Registry::SCOPED_ROUTING_OPTION ] );
+ unset( $GLOBALS['_wp_filter_callbacks']['tmdo_rest_default_post_type'] );
}
public function test_definitions_match_persisted_schema_hashes(): void {
@@ -74,6 +75,34 @@ class HivePressEntityGroupsTest extends TestCase {
$this->assertNull( TMDO_Entity_Registry::get_field( 'post', 'hp_rating', 'post' ), 'HivePress keys stay native on other post types' );
}
+ public function test_reconciler_plans_the_eight_misrouted_hivepress_keys(): void {
+ $moves = array();
+ foreach ( TMDO_Post_Group_Reconciler::plan() as $m ) {
+ $moves[] = "{$m['source']}>{$m['target']}:{$m['key']}";
+ }
+ sort( $moves );
+
+ $this->assertSame(
+ array(
+ 'hp_request_core>hp_listing_core:hp_expired_time',
+ 'hp_request_core>hp_listing_core:hp_status',
+ 'hp_request_core>hp_listing_core:hp_view_count',
+ 'hp_vendor_core>hp_listing_core:hp_hourly_rate',
+ 'hp_vendor_core>hp_listing_core:hp_rating',
+ 'hp_vendor_core>hp_listing_core:hp_rating_count',
+ 'hp_vendor_core>hp_listing_core:hp_verified',
+ 'hp_vendor_core>hp_request_core:hp_user',
+ ),
+ $moves
+ );
+ }
+
+ public function test_rest_listings_default_to_hivepress_listings(): void {
+ TMDO_HivePress_Entity_Groups::register();
+
+ $this->assertSame( 'hp_listing', TMDO_REST_API::default_post_type() );
+ }
+
public function test_core_adapter_does_not_register_a_conflicting_group(): void {
( new TMDO_HivePress_Core_Adapter() )->on_register_entity_fields();
From 0e260ee3a94670a36965437ee11baa49397d210c Mon Sep 17 00:00:00 2001
From: wpdev
Date: Sat, 26 Sep 2026 01:09:34 +0800
Subject: [PATCH 11/12] feat(2meet-data-optimizer-hivepress-addon): register
Hot-only HivePress fields as entity groups
- New scoped post groups for fields only the Hot zone covered (ADR-003):
hp_draft_flags (hp_listing, hp_request: hp_drafted), hp_booking_core,
hp_membership_plan_core, hp_membership_core. hp_drafted lives in one group
spanning both post types, so it adds no shared key.
- New groups register before the existing ones: the legacy (unscoped)
owner of hp_expired_time stays hp_request_core; existing hashes unchanged.
- Reconciler now also plans hp_membership rows of hp_expired_time out of
hp_request_core. dev20-22 hold 0 native rows for these keys.
- Verify: HP unit 151 / integration 49 green, phpcs clean
- PR: PR-036
---
.../class-tmdo-hivepress-entity-groups.php | 112 ++++++++++++++++++
tests/unit/HivePressEntityGroupsTest.php | 37 +++++-
2 files changed, 148 insertions(+), 1 deletion(-)
diff --git a/includes/class-tmdo-hivepress-entity-groups.php b/includes/class-tmdo-hivepress-entity-groups.php
index 798fba3..2894502 100644
--- a/includes/class-tmdo-hivepress-entity-groups.php
+++ b/includes/class-tmdo-hivepress-entity-groups.php
@@ -8,6 +8,14 @@
* post:hp_vendor_core → wp_wpdo_post_hp_vendor_core (hp_vendor)
* user:hp_user → wp_wpdo_user_hp_user
*
+ * Fields only the Hot zone used to cover (ADR-003), as their own groups:
+ * post:hp_draft_flags (hp_listing, hp_request)
+ * post:hp_booking_core (hp_booking)
+ * post:hp_membership_plan_core (hp_membership_plan)
+ * post:hp_membership_core (hp_membership)
+ * They register FIRST so the legacy last-registered owner of hp_expired_time
+ * stays hp_request_core.
+ *
* The term/comment groups (hp_taxonomy, hp_review) live in
* TMDO_Hivepress_Term_Comment_Fields.
*
@@ -60,6 +68,10 @@ final class TMDO_HivePress_Entity_Groups {
return;
}
+ self::register_hp_draft_flags_group();
+ self::register_hp_booking_core_group();
+ self::register_hp_membership_plan_core_group();
+ self::register_hp_membership_core_group();
self::register_hp_listing_core_group();
self::register_hp_request_core_group();
self::register_hp_vendor_core_group();
@@ -68,6 +80,106 @@ final class TMDO_HivePress_Entity_Groups {
// ── Group definitions ────────────────────────────────────────────────────
+ /**
+ * Draft flag shared by listings and requests (user-dashboard filtering).
+ *
+ * @return void
+ */
+ private static function register_hp_draft_flags_group(): void {
+ TMDO_Entity_Registry::register_group(
+ 'post',
+ 'hp_draft_flags',
+ array(
+ array(
+ 'key' => 'hp_drafted',
+ 'type' => 'integer',
+ 'searchable' => true,
+ 'label' => 'Drafted flag (0/1)',
+ ),
+ ),
+ array( 'post_types' => array( 'hp_listing', 'hp_request' ) )
+ );
+ }
+
+ /**
+ * HivePress Bookings (post_type=hp_booking): slot-availability lookups.
+ *
+ * @return void
+ */
+ private static function register_hp_booking_core_group(): void {
+ TMDO_Entity_Registry::register_group(
+ 'post',
+ 'hp_booking_core',
+ array(
+ array(
+ 'key' => 'hp_start_time',
+ 'type' => 'integer',
+ 'searchable' => true,
+ 'label' => 'Booking start unix ts',
+ ),
+ array(
+ 'key' => 'hp_end_time',
+ 'type' => 'integer',
+ 'searchable' => true,
+ 'label' => 'Booking end unix ts',
+ ),
+ array(
+ 'key' => 'hp_quantity',
+ 'type' => 'integer',
+ 'label' => 'Places booked',
+ ),
+ ),
+ array( 'post_types' => array( 'hp_booking' ) )
+ );
+ }
+
+ /**
+ * HivePress Memberships plan (post_type=hp_membership_plan).
+ *
+ * @return void
+ */
+ private static function register_hp_membership_plan_core_group(): void {
+ TMDO_Entity_Registry::register_group(
+ 'post',
+ 'hp_membership_plan_core',
+ array(
+ array(
+ 'key' => 'hp_expire_period',
+ 'type' => 'integer',
+ 'label' => 'Plan expiry period (days)',
+ ),
+ array(
+ 'key' => 'hp_primary',
+ 'type' => 'integer',
+ 'searchable' => true,
+ 'label' => 'Primary plan flag (0/1)',
+ ),
+ ),
+ array( 'post_types' => array( 'hp_membership_plan' ) )
+ );
+ }
+
+ /**
+ * HivePress Memberships subscription (post_type=hp_membership).
+ *
+ * @return void
+ */
+ private static function register_hp_membership_core_group(): void {
+ TMDO_Entity_Registry::register_group(
+ 'post',
+ 'hp_membership_core',
+ array(
+ array(
+ 'key' => 'hp_expired_time',
+ 'type' => 'integer',
+ 'searchable' => true,
+ 'label' => 'Expiry unix ts',
+ ),
+ ),
+ array( 'post_types' => array( 'hp_membership' ) )
+ );
+ }
+
/**
* HivePress listing core meta keys (post_type=hp_listing).
* Aligns with the existing wpdo_hot_hp_listing flat table; v2.9.5 will
diff --git a/tests/unit/HivePressEntityGroupsTest.php b/tests/unit/HivePressEntityGroupsTest.php
index 72fface..db610ce 100644
--- a/tests/unit/HivePressEntityGroupsTest.php
+++ b/tests/unit/HivePressEntityGroupsTest.php
@@ -75,7 +75,41 @@ class HivePressEntityGroupsTest extends TestCase {
$this->assertNull( TMDO_Entity_Registry::get_field( 'post', 'hp_rating', 'post' ), 'HivePress keys stay native on other post types' );
}
- public function test_reconciler_plans_the_eight_misrouted_hivepress_keys(): void {
+ /**
+ * Fields only the Hot zone used to cover (ADR-003), as their own groups.
+ */
+ private const HOT_ONLY_GROUPS = array(
+ 'hp_draft_flags' => array( array( 'hp_listing', 'hp_request' ), array( 'hp_drafted' ) ),
+ 'hp_booking_core' => array( array( 'hp_booking' ), array( 'hp_start_time', 'hp_end_time', 'hp_quantity' ) ),
+ 'hp_membership_plan_core' => array( array( 'hp_membership_plan' ), array( 'hp_expire_period', 'hp_primary' ) ),
+ 'hp_membership_core' => array( array( 'hp_membership' ), array( 'hp_expired_time' ) ),
+ );
+
+ public function test_hot_only_fields_have_entity_groups_scoped_to_their_post_types(): void {
+ foreach ( self::HOT_ONLY_GROUPS as $group => list( $post_types, $keys ) ) {
+ $this->assertSame( $post_types, TMDO_Entity_Registry::get_group_post_types( 'post', $group ), $group );
+ $this->assertSame( $keys, TMDO_Entity_Registry::get_group_keys( 'post', $group ), $group );
+ }
+ }
+
+ public function test_hot_only_fields_route_to_their_group_when_scoped_routing_is_on(): void {
+ $GLOBALS['_wp_options'][ TMDO_Entity_Registry::SCOPED_ROUTING_OPTION ] = '1';
+
+ foreach ( self::HOT_ONLY_GROUPS as $group => list( $post_types, $keys ) ) {
+ foreach ( $post_types as $post_type ) {
+ foreach ( $keys as $key ) {
+ $this->assertSame( $group, TMDO_Entity_Registry::get_field( 'post', $key, $post_type )['group'], "{$post_type}:{$key}" );
+ }
+ }
+ }
+ $this->assertSame( 'hp_listing_core', TMDO_Entity_Registry::get_field( 'post', 'hp_expired_time', 'hp_listing' )['group'] );
+ }
+
+ public function test_hot_only_groups_add_no_collisions(): void {
+ $this->assertSame( array(), TMDO_Entity_Registry::get_collisions() );
+ }
+
+ public function test_reconciler_plans_the_misrouted_hivepress_keys(): void {
$moves = array();
foreach ( TMDO_Post_Group_Reconciler::plan() as $m ) {
$moves[] = "{$m['source']}>{$m['target']}:{$m['key']}";
@@ -87,6 +121,7 @@ class HivePressEntityGroupsTest extends TestCase {
'hp_request_core>hp_listing_core:hp_expired_time',
'hp_request_core>hp_listing_core:hp_status',
'hp_request_core>hp_listing_core:hp_view_count',
+ 'hp_request_core>hp_membership_core:hp_expired_time',
'hp_vendor_core>hp_listing_core:hp_hourly_rate',
'hp_vendor_core>hp_listing_core:hp_rating',
'hp_vendor_core>hp_listing_core:hp_rating_count',
From 0254e40d8352068610c9737fb0e11b281f523bf7 Mon Sep 17 00:00:00 2001
From: wpdev
Date: Sat, 26 Sep 2026 01:13:35 +0800
Subject: [PATCH 12/12] refactor(2meet-data-optimizer-hivepress-addon): stop
registering Hot zone fields
- ADR-003 step 4 (AddOn side): core, requests, bookings, memberships and
marketplace adapters no longer register zone=hot fields or contribute
hot_* FSM modules; their keys live in TMDO_HivePress_Entity_Groups.
Cold/warm registrations are unchanged.
- Hot-table doctor probes go with them (trait defaults apply). The
hot-clause query rewrite and cron optimizer are now inert and are left
for the ADR step-5 removal together with core's hot path.
- New NoHotZoneRegistrationTest pins the invariant across all 13 adapters;
per-adapter hot assertions removed.
- Verify: HP unit 142 / integration 49 green, phpcs clean (WordPress)
- PR: PR-037
---
.../class-tmdo-hivepress-bookings-adapter.php | 91 +--------------
.../class-tmdo-hivepress-core-adapter.php | 106 +-----------------
...ass-tmdo-hivepress-marketplace-adapter.php | 55 +--------
...ass-tmdo-hivepress-memberships-adapter.php | 100 +----------------
.../class-tmdo-hivepress-requests-adapter.php | 72 +-----------
tests/unit/BookingsAdapterTest.php | 29 -----
tests/unit/CoreAdapterTest.php | 29 -----
tests/unit/MarketplaceAdapterTest.php | 15 ---
tests/unit/MembershipsAdapterTest.php | 33 ------
tests/unit/NoHotZoneRegistrationTest.php | 51 +++++++++
tests/unit/RequestsAdapterTest.php | 17 ---
11 files changed, 71 insertions(+), 527 deletions(-)
create mode 100644 tests/unit/NoHotZoneRegistrationTest.php
diff --git a/includes/hivepress/adapters/class-tmdo-hivepress-bookings-adapter.php b/includes/hivepress/adapters/class-tmdo-hivepress-bookings-adapter.php
index 60cfaa1..a30a5e7 100644
--- a/includes/hivepress/adapters/class-tmdo-hivepress-bookings-adapter.php
+++ b/includes/hivepress/adapters/class-tmdo-hivepress-bookings-adapter.php
@@ -8,18 +8,13 @@
* Booking model (post_type=hp_booking) — fields are best-effort from the
* addon's documented field aliases:
*
- * start_time / end_time → unix timestamp range queries (hot)
- * status → enum filter (hot)
- * quantity → places booked, hp_quantity (hot for slot-availability;
- * only written when "Allow multiple places" is on)
+ * start_time / end_time → unix timestamp range queries
+ * quantity → places booked, hp_quantity (only written when
+ * "Allow multiple places" is on)
* listing → post_parent (already a posts column)
*
- * Hot-zone target: `wp_wpdo_hot_hp_booking` with covering indexes on
- * `(listing_id, start_time)` for slot-availability queries and
- * `(status, start_time)` for vendor dashboard listings.
- *
- * The schema MAY drift from real Booking model when the addon is finally
- * installed; reconcile with `wp wpdo doctor` then.
+ * These keys are flattened by the `hp_booking_core` entity group
+ * (TMDO_HivePress_Entity_Groups); the adapter registers no zone fields.
*
* @package WP_Data_Optimizer
* @since 3.0.0
@@ -65,73 +60,6 @@ if ( ! class_exists( 'TMDO_HivePress_Bookings_Adapter' ) ) {
return '1.0.0';
}
- /**
- * Register hot fields for hp_booking 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_booking',
- 'meta_key' => 'hp_start_time',
- 'zone' => 'hot',
- 'data_type' => 'bigint(20) UNSIGNED 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) UNSIGNED NOT NULL DEFAULT 0',
- 'column' => 'hp_end_time',
- 'indexed' => true,
- ),
- array(
- 'post_type' => 'hp_booking',
- 'meta_key' => 'hp_quantity',
- 'zone' => 'hot',
- 'data_type' => 'int(11) NOT NULL DEFAULT 0',
- 'column' => 'hp_quantity',
- 'indexed' => false,
- ),
- )
- );
- }
-
- /**
- * Doctor probe — verify hot table when addon is active.
- *
- * @return array{ok:bool, message:string}
- */
- public function doctor_check(): array {
- global $wpdb;
- if ( ! isset( $wpdb ) || ! is_object( $wpdb ) ) {
- return array(
- 'ok' => false,
- 'message' => 'wpdb global not available',
- );
- }
- $table = $wpdb->prefix . 'wpdo_hot_hp_booking';
- try {
- $exists = (bool) $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) );
- return array(
- 'ok' => $exists,
- 'message' => $exists
- ? sprintf( 'hivepress-bookings: %s present', $table )
- : sprintf( 'hivepress-bookings: %s missing (addon installed but not migrated)', $table ),
- );
- } catch ( \Throwable $e ) {
- return array(
- 'ok' => false,
- 'message' => sprintf( 'hivepress-bookings doctor failed: %s', $e->getMessage() ),
- );
- }
- }
-
/**
* 8-D self-score.
*
@@ -146,15 +74,6 @@ if ( ! class_exists( 'TMDO_HivePress_Bookings_Adapter' ) ) {
)
);
}
-
- /**
- * FSM modules this adapter contributes.
- *
- * @return array
- */
- public function migrations(): array {
- return array( 'hot_hp_booking' );
- }
}
} // end if ( ! class_exists )
diff --git a/includes/hivepress/adapters/class-tmdo-hivepress-core-adapter.php b/includes/hivepress/adapters/class-tmdo-hivepress-core-adapter.php
index 1580f78..9326ee2 100644
--- a/includes/hivepress/adapters/class-tmdo-hivepress-core-adapter.php
+++ b/includes/hivepress/adapters/class-tmdo-hivepress-core-adapter.php
@@ -4,13 +4,14 @@
*
* Covers the primary HivePress models:
*
- * - `Listing` (post_type=hp_listing) — Hot zone columns for search/filter
- * (drafted, featured, verified, expired_time, featured_time).
- * - `Vendor` (post_type=hp_vendor) — Hot column `verified`; cold blob
- * for image / description (display-only).
+ * - `Listing` (post_type=hp_listing) — cold blob for the gallery.
+ * - `Vendor` (post_type=hp_vendor) — cold blob for the image.
* - `User` (entity=user) — no zone fields; its `hp_user`
* entity group is registered by TMDO_HivePress_Entity_Groups.
*
+ * Search/filter keys are flattened by TMDO_HivePress_Entity_Groups
+ * (ADR-003: no Hot zone fields).
+ *
* This adapter SUPERSEDES the legacy `class-tmdo-hivepress.php` (242 lines).
* Behavioural parity:
*
@@ -23,11 +24,6 @@
* adapter only registers `hivepress` core fields.
* - Vendor cold fields (`hp_image`, `hp_images`) — preserved.
*
- * NEW additions vs legacy:
- *
- * - `hp_drafted` (Listing) — Hot tinyint(1) for user-dashboard filtering.
- * - `hp_expired_time` / `hp_featured_time` (Listing) — Hot bigint indexed
- * for cron expiry scan acceleration (replaces postmeta full scan).
* (This adapter used to register a second `hp_user` definition —
* hp_verified / hp_avatar_id, keys HivePress never writes — which the
* registry silently dropped; TMDO_HivePress_Entity_Groups owns it now.)
@@ -114,37 +110,10 @@ if ( ! class_exists( 'TMDO_HivePress_Core_Adapter' ) ) {
* @param TMDO_Schema_Registry $registry Schema registry singleton.
*/
public function on_register_fields( TMDO_Schema_Registry $registry ): void {
- // ── Listing hot fields (search/filter accelerators) ──
+ // ── Cold blobs (display-only) ──
$registry->register_many(
$this->plugin_slug(),
array(
- // Drafted — used in user dashboard "my drafts" filter.
- array(
- 'post_type' => 'hp_listing',
- 'meta_key' => 'hp_drafted',
- 'zone' => 'hot',
- 'data_type' => 'tinyint(1) NOT NULL DEFAULT 0',
- 'column' => 'hp_drafted',
- 'indexed' => true,
- ),
- // Expired time — replaces `meta_query` clause in hourly cron.
- array(
- 'post_type' => 'hp_listing',
- 'meta_key' => 'hp_expired_time',
- 'zone' => 'hot',
- 'data_type' => 'bigint(20) UNSIGNED NOT NULL DEFAULT 0',
- 'column' => 'hp_expired_time',
- 'indexed' => true,
- ),
- // Featured time — same expiry pattern as above.
- array(
- 'post_type' => 'hp_listing',
- 'meta_key' => 'hp_featured_time',
- 'zone' => 'hot',
- 'data_type' => 'bigint(20) UNSIGNED NOT NULL DEFAULT 0',
- 'column' => 'hp_featured_time',
- 'indexed' => true,
- ),
// Vendor cold blob — image attachment id (display-only).
array(
'post_type' => 'hp_vendor',
@@ -276,57 +245,6 @@ if ( ! class_exists( 'TMDO_HivePress_Core_Adapter' ) ) {
// ── Doctor / score / migrations ────────────────────────────────────
- /**
- * Health probe: verify hot tables exist + sample row count.
- *
- * @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 ( self::HOT_MODELS as $post_type ) {
- $table = $wpdb->prefix . 'wpdo_hot_' . sanitize_key( $post_type );
- try {
- $exists = (bool) $wpdb->get_var(
- $wpdb->prepare( 'SHOW TABLES LIKE %s', $table )
- );
- $details[ $post_type ] = array(
- 'table' => $table,
- 'exists' => $exists,
- );
- if ( $exists ) {
- $details[ $post_type ]['rows'] = (int) $wpdb->get_var(
- "SELECT COUNT(*) FROM `{$table}`" // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Sanitized via sanitize_key + wpdb prefix.
- );
- } else {
- $ok = false;
- }
- } catch ( \Throwable $e ) {
- $details[ $post_type ] = array(
- 'table' => $table,
- 'error' => $e->getMessage(),
- );
- $ok = false;
- }
- }
-
- return array(
- 'ok' => $ok,
- 'message' => $ok
- ? 'hivepress core: hot tables OK'
- : 'hivepress core: one or more hot tables missing or unreadable',
- 'details' => $details,
- );
- }
-
/**
* 8-dimension self-score for the core adapter.
*
@@ -348,18 +266,6 @@ if ( ! class_exists( 'TMDO_HivePress_Core_Adapter' ) ) {
)
);
}
-
- /**
- * FSM modules this adapter contributes.
- *
- * @return array
- */
- public function migrations(): array {
- return array(
- 'hot_hp_listing',
- 'hot_hp_vendor',
- );
- }
}
} // end if ( ! class_exists )
diff --git a/includes/hivepress/adapters/class-tmdo-hivepress-marketplace-adapter.php b/includes/hivepress/adapters/class-tmdo-hivepress-marketplace-adapter.php
index f5042b2..316f5d7 100644
--- a/includes/hivepress/adapters/class-tmdo-hivepress-marketplace-adapter.php
+++ b/includes/hivepress/adapters/class-tmdo-hivepress-marketplace-adapter.php
@@ -4,7 +4,9 @@
*
* The marketplace addon turns listings into purchasable items, integrating
* with WooCommerce. Its filterable, sortable listing attribute `price` is
- * stored as `hp_price` → decimal(10,2), used in price-range filters (HOT).
+ * stored as `hp_price` → decimal(10,2), used in price-range filters. It is
+ * flattened by the `hp_listing_core` entity group
+ * (TMDO_HivePress_Entity_Groups); the adapter registers no zone fields.
*
* Its other listing attributes (price tiers / extras / discounts, quantity,
* subscription period, …) are not search filters, and it keeps no per-listing
@@ -57,57 +59,6 @@ if ( ! class_exists( 'TMDO_HivePress_Marketplace_Adapter' ) ) {
return '1.0.0';
}
- /**
- * Register marketplace hot fields on the listing 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_listing',
- 'meta_key' => 'hp_price',
- 'zone' => 'hot',
- 'data_type' => 'decimal(10,2) DEFAULT NULL',
- 'column' => 'hp_price',
- 'indexed' => true,
- ),
- )
- );
- }
-
- /**
- * Doctor probe — verify hot column exists on listing hot table.
- *
- * @return array{ok:bool, message:string}
- */
- public function doctor_check(): array {
- global $wpdb;
- if ( ! isset( $wpdb ) || ! is_object( $wpdb ) ) {
- return array(
- 'ok' => false,
- 'message' => 'wpdb global not available',
- );
- }
- $table = $wpdb->prefix . 'wpdo_hot_hp_listing';
- try {
- $exists = (bool) $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) );
- return array(
- 'ok' => $exists,
- 'message' => $exists
- ? 'hivepress-marketplace: shares wp_wpdo_hot_hp_listing'
- : 'hivepress-marketplace: hot listing table missing',
- );
- } catch ( \Throwable $e ) {
- return array(
- 'ok' => false,
- 'message' => sprintf( 'hivepress-marketplace doctor failed: %s', $e->getMessage() ),
- );
- }
- }
-
/**
* 8-D self-score.
*
diff --git a/includes/hivepress/adapters/class-tmdo-hivepress-memberships-adapter.php b/includes/hivepress/adapters/class-tmdo-hivepress-memberships-adapter.php
index e6a2b2e..14a50dc 100644
--- a/includes/hivepress/adapters/class-tmdo-hivepress-memberships-adapter.php
+++ b/includes/hivepress/adapters/class-tmdo-hivepress-memberships-adapter.php
@@ -5,18 +5,13 @@
* Two post-type models:
*
* - Membership_Plan (post_type=hp_membership_plan)
- * hot fields: hp_expire_period (int), hp_primary (bool)
+ * hp_expire_period (int), hp_primary (bool)
*
* - Membership (post_type=hp_membership)
- * hot fields: hp_expired_time (unix ts) — checked on every page load
+ * hp_expired_time (unix ts) — checked on every page load
*
- * Both fields registered to Hot zone so:
- * - `hp_expired_time < NOW()` lookups become indexed range scans
- * (replaces O(N) postmeta scan on each request).
- * - `hp_primary = 1` lookups for "default plan" become O(1).
- *
- * Tables `wp_wpdo_hot_hp_membership_plan` and `wp_wpdo_hot_hp_membership`
- * are created by `TMDO_Schema_Manager` from the registered hot columns.
+ * Flattened by the `hp_membership_plan_core` / `hp_membership_core` entity
+ * groups (TMDO_HivePress_Entity_Groups); the adapter registers no zone fields.
*
* @package WP_Data_Optimizer
* @since 3.0.0
@@ -62,81 +57,6 @@ if ( ! class_exists( 'TMDO_HivePress_Memberships_Adapter' ) ) {
return '2.0.0';
}
- /**
- * Register hot fields for both post types.
- *
- * @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(
- // Plan fields.
- array(
- 'post_type' => 'hp_membership_plan',
- 'meta_key' => 'hp_expire_period',
- 'zone' => 'hot',
- 'data_type' => 'int(11) NOT NULL DEFAULT 0',
- 'column' => 'hp_expire_period',
- 'indexed' => false,
- ),
- array(
- 'post_type' => 'hp_membership_plan',
- 'meta_key' => 'hp_primary',
- 'zone' => 'hot',
- 'data_type' => 'tinyint(1) NOT NULL DEFAULT 0',
- 'column' => 'hp_primary',
- 'indexed' => true,
- ),
- // Membership (subscription) field — hottest path: every page load.
- array(
- 'post_type' => 'hp_membership',
- 'meta_key' => 'hp_expired_time',
- 'zone' => 'hot',
- 'data_type' => 'bigint(20) UNSIGNED NOT NULL DEFAULT 0',
- 'column' => 'hp_expired_time',
- 'indexed' => true,
- ),
- )
- );
- }
-
- /**
- * Doctor probe — verify both hot tables.
- *
- * @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_membership_plan', 'hp_membership' ) as $pt ) {
- $table = $wpdb->prefix . 'wpdo_hot_' . sanitize_key( $pt );
- try {
- $exists = (bool) $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) );
- $details[ $pt ]['table'] = $table;
- $details[ $pt ]['exists'] = $exists;
- if ( ! $exists ) {
- $ok = false;
- }
- } catch ( \Throwable $e ) {
- $details[ $pt ]['error'] = $e->getMessage();
- $ok = false;
- }
- }
- return array(
- 'ok' => $ok,
- 'message' => $ok ? 'hivepress-memberships: hot tables OK' : 'hivepress-memberships: hot tables missing',
- 'details' => $details,
- );
- }
-
/**
* 8-D self-score.
*
@@ -152,18 +72,6 @@ if ( ! class_exists( 'TMDO_HivePress_Memberships_Adapter' ) ) {
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_membership_plan',
- 'hot_hp_membership',
- );
- }
}
} // end if ( ! class_exists )
diff --git a/includes/hivepress/adapters/class-tmdo-hivepress-requests-adapter.php b/includes/hivepress/adapters/class-tmdo-hivepress-requests-adapter.php
index 4ef0b7f..903ac8e 100644
--- a/includes/hivepress/adapters/class-tmdo-hivepress-requests-adapter.php
+++ b/includes/hivepress/adapters/class-tmdo-hivepress-requests-adapter.php
@@ -5,8 +5,8 @@
* Two related models:
*
* - Request (post_type=hp_request)
- * hot fields: hp_drafted (bool), hp_expired_time (unix ts)
- * — same access pattern as Listing (user dashboard + expiry cron)
+ * hp_drafted / hp_expired_time are flattened by the `hp_draft_flags`
+ * and `hp_request_core` entity groups (TMDO_HivePress_Entity_Groups).
*
* - Offer (comment_type=hp_offer, parent post_type=hp_request)
* stored in comment columns only — no comment meta to flatten.
@@ -59,74 +59,6 @@ if ( ! class_exists( 'TMDO_HivePress_Requests_Adapter' ) ) {
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,
- ),
- )
- );
- }
-
- /**
- * Doctor probe — request hot 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',
- );
- }
- $table = $wpdb->prefix . 'wpdo_hot_hp_request';
- try {
- $exists = (bool) $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) );
- return array(
- 'ok' => $exists,
- 'message' => $exists
- ? sprintf( 'hivepress-requests: %s present', $table )
- : sprintf( 'hivepress-requests: %s missing', $table ),
- );
- } catch ( \Throwable $e ) {
- return array(
- 'ok' => false,
- 'message' => sprintf( 'hivepress-requests doctor failed: %s', $e->getMessage() ),
- );
- }
- }
-
- /**
- * FSM modules this adapter contributes.
- *
- * @return array
- */
- public function migrations(): array {
- return array( 'hot_hp_request' );
- }
}
} // end if ( ! class_exists )
diff --git a/tests/unit/BookingsAdapterTest.php b/tests/unit/BookingsAdapterTest.php
index b0bd684..20a62ca 100644
--- a/tests/unit/BookingsAdapterTest.php
+++ b/tests/unit/BookingsAdapterTest.php
@@ -33,31 +33,6 @@ class HivePressBookingsAdapterTest extends TestCase {
$this->assertSame( 'HivePress\\Bookings\\Plugin', $this->adapter->detection_class() );
}
- public function test_on_register_fields_reserves_booking_hot_columns(): void {
- $registry = WPDO_Schema_Registry::instance();
- $this->adapter->on_register_fields( $registry );
-
- $start = $registry->get_field( 'hp_booking', 'hp_start_time' );
- $this->assertSame( 'hot', $start['zone'] );
- $this->assertTrue( (bool) $start['indexed'] );
-
- $end = $registry->get_field( 'hp_booking', 'hp_end_time' );
- $this->assertSame( 'hot', $end['zone'] );
- $this->assertTrue( (bool) $end['indexed'] );
- }
-
- public function test_on_register_fields_uses_real_booking_meta_keys(): void {
- $registry = WPDO_Schema_Registry::instance();
- $this->adapter->on_register_fields( $registry );
-
- // hivepress-bookings has no guests field; places are stored as hp_quantity.
- $this->assertNull( $registry->get_field( 'hp_booking', 'hp_guests' ) );
-
- $quantity = $registry->get_field( 'hp_booking', 'hp_quantity' );
- $this->assertNotNull( $quantity );
- $this->assertSame( 'hot', $quantity['zone'] );
- }
-
public function test_score_reflects_unverified_perf_dimension(): void {
$score = $this->adapter->suitability_score();
// D8 reduced to 0.9 because schema is best-effort until addon installs.
@@ -65,8 +40,4 @@ class HivePressBookingsAdapterTest extends TestCase {
// Aggregate = (7 × 1.0 + 0.9) / 8 × 10 = 9.875 → 9.88.
$this->assertSame( 9.88, $score['aggregate'] );
}
-
- public function test_migrations_lists_booking_module(): void {
- $this->assertSame( array( 'hot_hp_booking' ), $this->adapter->migrations() );
- }
}
diff --git a/tests/unit/CoreAdapterTest.php b/tests/unit/CoreAdapterTest.php
index 6af0e24..5e1dc03 100644
--- a/tests/unit/CoreAdapterTest.php
+++ b/tests/unit/CoreAdapterTest.php
@@ -46,29 +46,10 @@ class HivePressCoreAdapterTest extends TestCase {
$this->assertSame( '1.7.0', $this->adapter->minimum_addon_version() );
}
- public function test_migrations_returns_listing_and_vendor_modules(): void {
- $mods = $this->adapter->migrations();
- $this->assertContains( 'hot_hp_listing', $mods );
- $this->assertContains( 'hot_hp_vendor', $mods );
- }
-
public function test_on_register_fields_populates_schema_registry(): void {
$registry = WPDO_Schema_Registry::instance();
$this->adapter->on_register_fields( $registry );
- // Listing hot fields.
- $drafted = $registry->get_field( 'hp_listing', 'hp_drafted' );
- $this->assertIsArray( $drafted );
- $this->assertSame( 'hot', $drafted['zone'] );
- $this->assertTrue( (bool) $drafted['indexed'] );
-
- $expired = $registry->get_field( 'hp_listing', 'hp_expired_time' );
- $this->assertSame( 'hot', $expired['zone'] );
- $this->assertStringContainsString( 'bigint', $expired['data_type'] );
-
- $featured_time = $registry->get_field( 'hp_listing', 'hp_featured_time' );
- $this->assertSame( 'hot', $featured_time['zone'] );
-
// Vendor cold blob.
$vendor_image = $registry->get_field( 'hp_vendor', 'hp_image' );
$this->assertIsArray( $vendor_image );
@@ -87,16 +68,6 @@ class HivePressCoreAdapterTest extends TestCase {
}
}
- public function test_doctor_check_reports_table_status(): void {
- $result = $this->adapter->doctor_check();
- $this->assertIsArray( $result );
- $this->assertArrayHasKey( 'ok', $result );
- $this->assertArrayHasKey( 'message', $result );
- $this->assertArrayHasKey( 'details', $result );
- // The stub $wpdb returns null for SHOW TABLES, so tables appear missing → ok=false.
- $this->assertFalse( $result['ok'] );
- }
-
public function test_optimize_query_extracts_hot_clauses(): void {
$registry = WPDO_Schema_Registry::instance();
$this->adapter->on_register_fields( $registry );
diff --git a/tests/unit/MarketplaceAdapterTest.php b/tests/unit/MarketplaceAdapterTest.php
index 55e93de..bd7248d 100644
--- a/tests/unit/MarketplaceAdapterTest.php
+++ b/tests/unit/MarketplaceAdapterTest.php
@@ -33,21 +33,6 @@ class HivePressMarketplaceAdapterTest extends TestCase {
$this->assertSame( 'HivePress\\Marketplace\\Plugin', $this->adapter->detection_class() );
}
- public function test_on_register_fields_registers_hot_hp_price(): void {
- $registry = WPDO_Schema_Registry::instance();
- $this->adapter->on_register_fields( $registry );
-
- // Marketplace's filterable, sortable listing "price" attribute is stored as hp_price.
- $price = $registry->get_field( 'hp_listing', 'hp_price' );
- $this->assertSame( 'hot', $price['zone'] );
- $this->assertTrue( (bool) $price['indexed'] );
- $this->assertStringContainsString( 'decimal', $price['data_type'] );
-
- // Neither key exists in hivepress-marketplace.
- $this->assertNull( $registry->get_field( 'hp_listing', 'hp_purchase_price' ) );
- $this->assertNull( $registry->get_field( 'hp_listing', 'hp_purchase_count' ) );
- }
-
public function test_score_aggregates_to_nine_eight_eight(): void {
// D8 = 0.9 → aggregate = (7×1.0 + 0.9)/8 × 10 = 9.875 → 9.88.
$this->assertSame( 9.88, $this->adapter->suitability_score()['aggregate'] );
diff --git a/tests/unit/MembershipsAdapterTest.php b/tests/unit/MembershipsAdapterTest.php
index fe039a1..8a6e038 100644
--- a/tests/unit/MembershipsAdapterTest.php
+++ b/tests/unit/MembershipsAdapterTest.php
@@ -33,41 +33,8 @@ class HivePressMembershipsAdapterTest extends TestCase {
$this->assertSame( '2.0.0', $this->adapter->minimum_addon_version() );
}
- public function test_on_register_fields_registers_three_hot_fields(): void {
- $registry = WPDO_Schema_Registry::instance();
- $this->adapter->on_register_fields( $registry );
-
- // Plan fields.
- $expire_period = $registry->get_field( 'hp_membership_plan', 'hp_expire_period' );
- $this->assertSame( 'hot', $expire_period['zone'] );
- $this->assertStringContainsString( 'int', $expire_period['data_type'] );
-
- $primary = $registry->get_field( 'hp_membership_plan', 'hp_primary' );
- $this->assertSame( 'hot', $primary['zone'] );
- $this->assertTrue( (bool) $primary['indexed'] );
-
- // Membership field — the hottest path.
- $expired = $registry->get_field( 'hp_membership', 'hp_expired_time' );
- $this->assertSame( 'hot', $expired['zone'] );
- $this->assertTrue( (bool) $expired['indexed'] );
- $this->assertStringContainsString( 'bigint', $expired['data_type'] );
- }
-
- public function test_doctor_check_reports_missing_tables_in_stub_env(): void {
- $result = $this->adapter->doctor_check();
- $this->assertFalse( $result['ok'] );
- $this->assertArrayHasKey( 'hp_membership_plan', $result['details'] );
- $this->assertArrayHasKey( 'hp_membership', $result['details'] );
- }
-
public function test_score_aggregates_to_perfect_ten(): void {
$score = $this->adapter->suitability_score();
$this->assertSame( 10.0, $score['aggregate'] );
}
-
- public function test_migrations_lists_both_hot_modules(): void {
- $mods = $this->adapter->migrations();
- $this->assertContains( 'hot_hp_membership_plan', $mods );
- $this->assertContains( 'hot_hp_membership', $mods );
- }
}
diff --git a/tests/unit/NoHotZoneRegistrationTest.php b/tests/unit/NoHotZoneRegistrationTest.php
new file mode 100644
index 0000000..3f6d2d1
--- /dev/null
+++ b/tests/unit/NoHotZoneRegistrationTest.php
@@ -0,0 +1,51 @@
+getProperty( 'instance' );
+ $prop->setAccessible( true );
+ $prop->setValue( null, null );
+ }
+
+ public function test_no_adapter_registers_hot_fields(): void {
+ $registry = TMDO_Schema_Registry::instance();
+ foreach ( self::ADAPTERS as $class ) {
+ ( new $class() )->on_register_fields( $registry );
+ }
+
+ $this->assertSame( array(), $registry->get_zone_fields( 'hot' ) );
+ $this->assertSame( 'cold', $registry->get_field_zone( 'hp_listing', 'hp_images' ), 'cold fields stay' );
+ }
+
+ public function test_no_adapter_contributes_hot_modules(): void {
+ foreach ( self::ADAPTERS as $class ) {
+ $this->assertSame( array(), ( new $class() )->migrations(), $class );
+ }
+ }
+}
diff --git a/tests/unit/RequestsAdapterTest.php b/tests/unit/RequestsAdapterTest.php
index 0c850b1..d7cf176 100644
--- a/tests/unit/RequestsAdapterTest.php
+++ b/tests/unit/RequestsAdapterTest.php
@@ -35,19 +35,6 @@ class HivePressRequestsAdapterTest extends TestCase {
$this->assertSame( 'hp_offer', WPDO_HivePress_Requests_Adapter::OFFER_COMMENT_TYPE );
}
- public function test_on_register_fields_registers_request_hot_columns(): void {
- $registry = WPDO_Schema_Registry::instance();
- $this->adapter->on_register_fields( $registry );
-
- $drafted = $registry->get_field( 'hp_request', 'hp_drafted' );
- $this->assertSame( 'hot', $drafted['zone'] );
- $this->assertTrue( (bool) $drafted['indexed'] );
-
- $expired = $registry->get_field( 'hp_request', 'hp_expired_time' );
- $this->assertSame( 'hot', $expired['zone'] );
- $this->assertTrue( (bool) $expired['indexed'] );
- }
-
public function test_registers_no_offer_shadow_table(): void {
// The offer shadow table was never created, so every mirror write failed.
$registry = WPDO_Custom_Table_Registry::instance();
@@ -69,8 +56,4 @@ class HivePressRequestsAdapterTest extends TestCase {
public function test_score_aggregates_to_perfect_ten(): void {
$this->assertSame( 10.0, $this->adapter->suitability_score()['aggregate'] );
}
-
- public function test_migrations_lists_only_request_hot_module(): void {
- $this->assertSame( array( 'hot_hp_request' ), $this->adapter->migrations() );
- }
}