refactor(2meet-data-optimizer-hivepress-addon): take ownership of hp_* entity groups
- 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
This commit is contained in:
@@ -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',
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
<?php
|
||||
/**
|
||||
* HivePress entity groups owned by the HivePress AddOn.
|
||||
*
|
||||
* Groups → flat tables:
|
||||
* post:hp_listing_core → wp_wpdo_post_hp_listing_core (hp_listing)
|
||||
* post:hp_request_core → wp_wpdo_post_hp_request_core (hp_request)
|
||||
* post:hp_vendor_core → wp_wpdo_post_hp_vendor_core (hp_vendor)
|
||||
* user:hp_user → wp_wpdo_user_hp_user
|
||||
*
|
||||
* The term/comment groups (hp_taxonomy, hp_review) live in
|
||||
* TMDO_Hivepress_Term_Comment_Fields.
|
||||
*
|
||||
* ⚠️ These definitions must stay byte-identical to the ones live sites
|
||||
* 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.
|
||||
*
|
||||
* @package TMDO_HIVEPRESS
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the HivePress post + user entity groups.
|
||||
*/
|
||||
final class TMDO_HivePress_Entity_Groups {
|
||||
|
||||
/**
|
||||
* Hook into WPDO entity field registration.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register(): void {
|
||||
add_action( 'wpdo_register_entity_fields', array( __CLASS__, 'register_entity_fields' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the groups (order matters, see file header).
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register_entity_fields(): void {
|
||||
if ( ! class_exists( 'TMDO_Entity_Registry' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::register_hp_listing_core_group();
|
||||
self::register_hp_request_core_group();
|
||||
self::register_hp_vendor_core_group();
|
||||
self::register_hp_user_group();
|
||||
}
|
||||
|
||||
// ── Group definitions ────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* HivePress listing core meta keys (post_type=hp_listing).
|
||||
* Aligns with the existing wpdo_hot_hp_listing flat table; v2.9.5 will
|
||||
* copy-then-cutover the 230 dev10 rows to this group's table.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function register_hp_listing_core_group(): void {
|
||||
TMDO_Entity_Registry::register_group(
|
||||
'post',
|
||||
'hp_listing_core',
|
||||
array(
|
||||
array(
|
||||
'key' => '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',
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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) ──
|
||||
|
||||
/**
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
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).
|
||||
*/
|
||||
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 function fresh_registry(): 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_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() );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user