b4400a68e5
Baseline before backporting wp-data-optimizer v3.0.1-v3.4.6. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TbG1keQQ7XBa7qMQY16KCY
273 lines
9.3 KiB
PHP
273 lines
9.3 KiB
PHP
<?php
|
|
/**
|
|
* Shared implementation trait for HivePress addon adapters.
|
|
*
|
|
* Composes `TMDO_Anti_EAV_Aware` (the canonical sugar layer for partner
|
|
* plugins) and adds HivePress-specific helpers:
|
|
*
|
|
* - Hot/Warm/Cold zone field registration shortcuts (default `hp_*` post types)
|
|
* - Custom-table registration with auto-filled HP conventions
|
|
* - 8-dimension suitability self-score skeleton with sensible defaults
|
|
* - Default no-op implementations for query/event hooks
|
|
* - `on_after_addon_write()` cross-adapter event subscription helper
|
|
*
|
|
* Concrete adapters `use TMDO_HivePress_Adapter_Trait;` and override only
|
|
* what they need. This keeps each adapter file focused on its domain
|
|
* (Listing fields, Booking fields, Review aggregations, etc.).
|
|
*
|
|
* @package WP_Data_Optimizer
|
|
* @since 3.0.0
|
|
*/
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
if ( ! trait_exists( 'TMDO_HivePress_Adapter_Trait' ) ) {
|
|
|
|
trait TMDO_HivePress_Adapter_Trait {
|
|
|
|
// Compose the canonical sugar trait so adapters get its helpers
|
|
// (`register_field`, `register_custom_table`, `register_entity_fields`,
|
|
// `get_field`, `set_field`, `on_after_write`, `bind_anti_eav_hooks`)
|
|
// without each having to `use` two traits.
|
|
use TMDO_Anti_EAV_Aware;
|
|
|
|
// ── Default no-op implementations for the interface ────────────────
|
|
|
|
/**
|
|
* Override in concrete adapter (default 'hivepress' for safety).
|
|
*/
|
|
public function plugin_slug(): string {
|
|
return 'hivepress';
|
|
}
|
|
|
|
/**
|
|
* Override in concrete adapter (default empty string disables detection).
|
|
*/
|
|
public function detection_class(): string {
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* Override in concrete adapter (default empty string).
|
|
*/
|
|
public function detection_const(): string {
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* Override in concrete adapter to set version floor.
|
|
*
|
|
* Default empty string = accept any installed version.
|
|
*/
|
|
public function minimum_addon_version(): string {
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* Default no-op for adapters with no zone fields (e.g. presentation-only addons).
|
|
*
|
|
* @param TMDO_Schema_Registry $registry Schema registry singleton.
|
|
*/
|
|
public function on_register_fields( TMDO_Schema_Registry $registry ): void {
|
|
unset( $registry );
|
|
}
|
|
|
|
/**
|
|
* Default no-op — most addons don't contribute entity fields.
|
|
*
|
|
* `TMDO_Anti_EAV_Aware::on_register_entity_fields()` is overridden in
|
|
* concrete adapters that own user/term/comment groups (e.g. core
|
|
* adapter for hp_user). The trait's own default is also no-op so this
|
|
* override exists only to satisfy the interface contract symmetry.
|
|
*/
|
|
public function on_register_entity_fields(): void {
|
|
// Intentional no-op.
|
|
}
|
|
|
|
/**
|
|
* Default no-op for adapters with no custom tables.
|
|
*
|
|
* @param TMDO_Custom_Table_Registry $registry Custom table registry singleton.
|
|
*/
|
|
public function on_register_custom_tables( TMDO_Custom_Table_Registry $registry ): void {
|
|
unset( $registry );
|
|
}
|
|
|
|
/**
|
|
* Default no-op — adapter has no query routing needs.
|
|
*
|
|
* Adapters with hot zone fields used in search/filter override and
|
|
* call `TMDO_HivePress_Query_Router::register_for( $this )` here.
|
|
*/
|
|
public function on_register_query_hooks(): void {
|
|
// Intentional no-op.
|
|
}
|
|
|
|
/**
|
|
* Default no-op — adapter has no cross-adapter event handling.
|
|
*/
|
|
public function on_register_event_hooks(): void {
|
|
// Intentional no-op.
|
|
}
|
|
|
|
/**
|
|
* Self-report 8-dimension anti-EAV suitability score.
|
|
*
|
|
* Default returns 1.0 across the board, plus aggregate 10.0. Adapters
|
|
* with known weaknesses (e.g. addon-only detection, missing hot
|
|
* tables) MUST override and lower the relevant dimensions to keep
|
|
* the aggregate honest.
|
|
*
|
|
* @return array{d1_meta_calls:float, d2_meta_sql:float, d3_table_coverage:float, d4_registry_meta:float, d5_hook_bus:float, d6_options:float, d7_coupling:float, d8_perf:float, aggregate:float}
|
|
*/
|
|
public function suitability_score(): array {
|
|
return $this->compose_score( array() );
|
|
}
|
|
|
|
/**
|
|
* Default doctor probe — returns ok with adapter name.
|
|
*
|
|
* Adapters with custom tables MUST override and verify table existence.
|
|
*
|
|
* @return array{ok:bool, message:string, details?:array<string,mixed>}
|
|
*/
|
|
public function doctor_check(): array {
|
|
return array(
|
|
'ok' => true,
|
|
'message' => sprintf( '%s: adapter loaded, no custom tables to probe', $this->plugin_slug() ),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Default no FSM migrations (adapter is read-only or zero hot fields).
|
|
*
|
|
* @return array<int, string>
|
|
*/
|
|
public function migrations(): array {
|
|
return array();
|
|
}
|
|
|
|
// ── Helper: zone-aware field registration shortcuts ─────────────────
|
|
|
|
/**
|
|
* Register a single hot zone field (varchar/int/decimal column on `wp_wpdo_hot_<post_type>`).
|
|
*
|
|
* @param TMDO_Schema_Registry $registry Provided by `wpdo_register_fields` hook.
|
|
* @param string $post_type WordPress post type (e.g. 'hp_listing').
|
|
* @param string $meta_key wp_postmeta meta_key.
|
|
* @param string $data_type SQL column type (e.g. "tinyint(1) NOT NULL DEFAULT 0").
|
|
* @param array<string,mixed> $extra Optional overrides: column, indexed, etc.
|
|
*/
|
|
protected function register_hot( TMDO_Schema_Registry $registry, string $post_type, string $meta_key, string $data_type, array $extra = array() ): void {
|
|
$config = array_merge(
|
|
array(
|
|
'post_type' => $post_type,
|
|
'meta_key' => $meta_key,
|
|
'zone' => 'hot',
|
|
'data_type' => $data_type,
|
|
'column' => sanitize_key( $meta_key ),
|
|
'indexed' => false,
|
|
),
|
|
$extra
|
|
);
|
|
$this->register_field( $registry, $config );
|
|
}
|
|
|
|
/**
|
|
* Register a single cold zone field (object cache + JSON blob fallback).
|
|
*
|
|
* @param TMDO_Schema_Registry $registry Provided by `wpdo_register_fields` hook.
|
|
* @param string $post_type WordPress post type.
|
|
* @param string $meta_key wp_postmeta meta_key.
|
|
* @param int $cache_ttl Cache TTL in seconds (default 1 hour).
|
|
* @param array<string,mixed> $extra Optional overrides.
|
|
*/
|
|
protected function register_cold( TMDO_Schema_Registry $registry, string $post_type, string $meta_key, int $cache_ttl = 3600, array $extra = array() ): void {
|
|
$config = array_merge(
|
|
array(
|
|
'post_type' => $post_type,
|
|
'meta_key' => $meta_key,
|
|
'zone' => 'cold',
|
|
'cache_group' => 'wpdo_cold_' . sanitize_key( $post_type ),
|
|
'cache_ttl' => $cache_ttl,
|
|
),
|
|
$extra
|
|
);
|
|
$this->register_field( $registry, $config );
|
|
}
|
|
|
|
/**
|
|
* Register a single warm zone field (TTL counter / temp value).
|
|
*
|
|
* @param TMDO_Schema_Registry $registry Provided by `wpdo_register_fields` hook.
|
|
* @param string $post_type WordPress post type.
|
|
* @param string $meta_key wp_postmeta meta_key.
|
|
* @param int|null $ttl TTL in seconds (null = no expiry).
|
|
* @param array<string,mixed> $extra Optional overrides.
|
|
*/
|
|
protected function register_warm( TMDO_Schema_Registry $registry, string $post_type, string $meta_key, ?int $ttl = null, array $extra = array() ): void {
|
|
$config = array_merge(
|
|
array(
|
|
'post_type' => $post_type,
|
|
'meta_key' => $meta_key,
|
|
'zone' => 'warm',
|
|
'ttl' => $ttl,
|
|
),
|
|
$extra
|
|
);
|
|
$this->register_field( $registry, $config );
|
|
}
|
|
|
|
/**
|
|
* Subscribe to writes on another adapter's keys (cross-adapter coordination).
|
|
*
|
|
* Wraps `TMDO_Anti_EAV_Aware::on_after_write()` with a description hook
|
|
* so the cross-adapter linkage is greppable for the suitability scorer's
|
|
* D5 (Hook Bus Integration) check.
|
|
*
|
|
* @param string $key_prefix Meta key prefix (e.g. 'hp_sold_out_').
|
|
* @param callable $callback Receives (entity_type, entity_id, key, value, ok, op, before).
|
|
*/
|
|
protected function on_after_addon_write( string $key_prefix, callable $callback ): void {
|
|
$this->on_after_write( $key_prefix, $callback );
|
|
}
|
|
|
|
// ── Helper: 8-dimension score composition ───────────────────────────
|
|
|
|
/**
|
|
* Compose an 8-dimension score, filling missing keys with 1.0 and
|
|
* computing the aggregate as `sum / 8 * 1.25` (so 1.0 across all
|
|
* → 10.0).
|
|
*
|
|
* @param array<string,float> $partial Override values keyed by `d1_meta_calls`...`d8_perf`.
|
|
* @return array{d1_meta_calls:float, d2_meta_sql:float, d3_table_coverage:float, d4_registry_meta:float, d5_hook_bus:float, d6_options:float, d7_coupling:float, d8_perf:float, aggregate:float}
|
|
*/
|
|
protected function compose_score( array $partial ): array {
|
|
$defaults = array(
|
|
'd1_meta_calls' => 1.0,
|
|
'd2_meta_sql' => 1.0,
|
|
'd3_table_coverage' => 1.0,
|
|
'd4_registry_meta' => 1.0,
|
|
'd5_hook_bus' => 1.0,
|
|
'd6_options' => 1.0,
|
|
'd7_coupling' => 1.0,
|
|
'd8_perf' => 1.0,
|
|
);
|
|
$score = array_merge( $defaults, $partial );
|
|
|
|
// Clamp every dimension to [0.0, 1.0].
|
|
foreach ( $defaults as $k => $_ ) {
|
|
$score[ $k ] = max( 0.0, min( 1.0, (float) ( $score[ $k ] ?? 1.0 ) ) );
|
|
}
|
|
|
|
$sum = array_sum( array_intersect_key( $score, $defaults ) );
|
|
$score['aggregate'] = round( $sum / 8.0 * 10.0, 2 );
|
|
|
|
return $score;
|
|
}
|
|
}
|
|
|
|
} // end if ( ! trait_exists )
|