69cd4b6f8b
- review F5: a timeout entry expired before its value (a day vs a week), and HivePress reads a missing timeout as "never expires", serving the stale value for up to six more days. The timeout now expires a minute after the later of MAX_CACHE_TTL and its own time, so HivePress sees it expire first - review F7: site attributes accept only types with a plain index (json and binary would fail the CREATE TABLE) - review F8: the hp_listing_attrs group is registered on the run after after_setup_theme (init:1), so a filter added in a theme counts and the group has one definition per request; only keys of other groups are skipped - Verify: TTL test, attribute group tests, unit 148, integration 53, phpcs - PR: PR-115 (2.3 acceptance correctness review)
431 lines
12 KiB
PHP
431 lines
12 KiB
PHP
<?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
|
|
*
|
|
* 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.
|
|
*
|
|
* ⚠️ 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 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
|
|
*/
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Registers the HivePress post + user entity groups.
|
|
*/
|
|
final class TMDO_HivePress_Entity_Groups {
|
|
|
|
/**
|
|
* 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';
|
|
}
|
|
|
|
/**
|
|
* 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_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();
|
|
self::register_hp_user_group();
|
|
self::register_hp_listing_attributes_group();
|
|
}
|
|
|
|
// ── 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
|
|
* 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',
|
|
),
|
|
),
|
|
array(
|
|
'post_types' => array( 'hp_listing' ),
|
|
// Filtered and sorted on by HivePress search. Declared as indexes
|
|
// (added in place) rather than `searchable`, which would change
|
|
// the schema hash and rebuild a table that may hold aeav_only data.
|
|
// Only with core 1.3.0+ (ddl_allowed()): older cores run the ALTER
|
|
// inside whatever request comes first, a visitor's included.
|
|
'indexes' => method_exists( 'TMDO_Schema_Manager', 'ddl_allowed' ) ? array(
|
|
'hourly_rate' => array( 'hp_hourly_rate' ),
|
|
'rating_count' => array( 'hp_rating_count' ),
|
|
) : array(),
|
|
)
|
|
);
|
|
}
|
|
/**
|
|
* A site's own HivePress listing attributes (created under Listings →
|
|
* Attributes, e.g. a "Warranty" number → hp_warranty), opt-in through the
|
|
* `tmdo_hp_listing_attribute_fields` filter: `array( 'hp_warranty' => 'integer' )`.
|
|
*
|
|
* They get a group of their own — a new table, each column indexed at
|
|
* creation — so hp_listing_core and its pinned definition stay unchanged.
|
|
* Keys another group already manages, keys without the hp_ prefix and
|
|
* types that cannot take a plain index (json, binary) are ignored. Core
|
|
* fills the new table from native meta (drift fix) before using it.
|
|
*
|
|
* Registered once themes are loaded (the init:1 run): a filter added in a
|
|
* theme is seen, and the group gets one definition per request.
|
|
*
|
|
* @return void
|
|
*/
|
|
private static function register_hp_listing_attributes_group(): void {
|
|
if ( ! did_action( 'after_setup_theme' ) ) {
|
|
return;
|
|
}
|
|
$fields = array();
|
|
foreach ( (array) apply_filters( 'tmdo_hp_listing_attribute_fields', array() ) as $key => $type ) {
|
|
$key = (string) $key;
|
|
$owner = TMDO_Entity_Registry::get_field( 'post', $key, 'hp_listing' );
|
|
if ( 1 !== preg_match( '/^hp_[a-z0-9_]+$/', $key )
|
|
|| ! in_array( $type, array( 'text', 'textarea', 'integer', 'decimal', 'boolean', 'date', 'datetime', 'timestamp', 'enum' ), true )
|
|
|| ( null !== $owner && 'hp_listing_attrs' !== $owner['group'] ) ) {
|
|
continue;
|
|
}
|
|
$fields[] = array(
|
|
'key' => $key,
|
|
'type' => $type,
|
|
'searchable' => true,
|
|
'label' => 'Site listing attribute',
|
|
);
|
|
}
|
|
if ( $fields ) {
|
|
TMDO_Entity_Registry::register_group( 'post', 'hp_listing_attrs', $fields, array( 'post_types' => array( 'hp_listing' ) ) );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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',
|
|
),
|
|
),
|
|
array( 'post_types' => array( 'hp_request' ) )
|
|
);
|
|
}
|
|
/**
|
|
* 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',
|
|
),
|
|
),
|
|
array( 'post_types' => array( 'hp_vendor' ) )
|
|
);
|
|
}
|
|
/**
|
|
* 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',
|
|
),
|
|
)
|
|
);
|
|
}
|
|
}
|