refactor(2meet-data-optimizer): resolve post_type/group/table mappings through the registry

- TMDO_Post_Fields::post_type_for_group() / groups_for_post_type() derive HivePress-style groups from their registry post_type scope (core keeps its own attachment / wc_product / nav_menu_item map); the three copy-pasted switches and the query router's POST_TYPE_GROUP_MAP use them
- Term/comment backfill, shadow verifier and CLI take their groups from the registry (TMDO_Term_Comment_Backfill::registered_groups()); installer drops the hp_* entries already covered by its table patterns; REST listings default post type is filterable (tmdo_rest_default_post_type, default 'post')
- Verify: NoHpMappingsTest guard + mapping/default tests; core unit 640 / integration 449, HP 146/49, WC 10/22 OK
- PR: PR-020e
This commit is contained in:
2026-09-26 00:22:20 +08:00
parent 8a1c9c0353
commit 0603537f0a
13 changed files with 196 additions and 130 deletions
-7
View File
@@ -843,18 +843,11 @@ class TMDO_Installer {
$wpdb->prefix . 'wpdo_user_core_profile',
$wpdb->prefix . 'wpdo_user_social',
$wpdb->prefix . 'wpdo_user_commerce',
$wpdb->prefix . 'wpdo_user_hp_user',
$wpdb->prefix . 'wpdo_user_admin_prefs',
$wpdb->prefix . 'wpdo_post_wp_core',
$wpdb->prefix . 'wpdo_post_attachment',
$wpdb->prefix . 'wpdo_post_wc_product',
$wpdb->prefix . 'wpdo_post_hp_listing_core',
$wpdb->prefix . 'wpdo_post_hp_request_core',
$wpdb->prefix . 'wpdo_post_hp_vendor_core',
$wpdb->prefix . 'wpdo_post_nav_menu_item',
$wpdb->prefix . 'wpdo_hot_hp_listing',
$wpdb->prefix . 'wpdo_term_hp_taxonomy',
$wpdb->prefix . 'wpdo_comment_hp_review',
$wpdb->prefix . 'wpdo_term_misc',
$wpdb->prefix . 'wpdo_comment_misc',
);
+1 -17
View File
@@ -303,23 +303,7 @@ final class TMDO_Post_Shadow_Verifier {
* @return string|null
*/
private static function group_post_type( string $group ): ?string {
switch ( $group ) {
case 'attachment':
return 'attachment';
case 'wc_product':
return 'product';
case 'hp_listing_core':
return 'hp_listing';
case 'hp_request_core':
return 'hp_request';
case 'hp_vendor_core':
return 'hp_vendor';
case 'nav_menu_item':
return 'nav_menu_item';
case 'wp_core':
default:
return null;
}
return TMDO_Post_Fields::post_type_for_group( $group );
}
/**
+14 -2
View File
@@ -529,7 +529,7 @@ class TMDO_REST_API {
* @return WP_REST_Response REST response.
*/
public function get_listings( WP_REST_Request $request ): WP_REST_Response {
$post_type = $request->get_param( 'post_type' ) ?? 'hp_listing';
$post_type = $request->get_param( 'post_type' ) ?? self::default_post_type();
if ( ! post_type_exists( $post_type ) || ! is_post_type_viewable( $post_type ) ) {
return new WP_REST_Response(
array(
@@ -1550,13 +1550,25 @@ class TMDO_REST_API {
return $filters;
}
/**
* Post type GET /listings serves when the request names none.
*
* Filter `tmdo_rest_default_post_type` lets an integration (e.g. the
* HivePress AddOn) make its own post type the default.
*
* @return string
*/
public static function default_post_type(): string {
return (string) apply_filters( 'tmdo_rest_default_post_type', 'post' );
}
/**
* Argument definitions for GET /listings.
*/
private function listings_args(): array {
return array(
'post_type' => array(
'default' => 'hp_listing',
'default' => self::default_post_type(),
'sanitize_callback' => 'sanitize_key',
),
'per_page' => array(
+18 -16
View File
@@ -60,20 +60,22 @@ if ( ! defined( 'ABSPATH' ) ) {
final class TMDO_Term_Comment_Backfill {
/**
* Group → flat table suffix mapping. Mirror of TMDO_Hivepress_Term_Comment_Fields.
* Term and comment groups registered in the entity registry.
*
* @var array<string, array{entity_type:string, table_suffix:string}>
* @return array<string, string> Group name → entity type ('term' | 'comment').
*/
private const GROUP_MAP = array(
'hp_taxonomy' => array(
'entity_type' => 'term',
'table_suffix' => 'wpdo_term_hp_taxonomy',
),
'hp_review' => array(
'entity_type' => 'comment',
'table_suffix' => 'wpdo_comment_hp_review',
),
);
public static function registered_groups(): array {
$groups = array();
if ( ! class_exists( 'TMDO_Entity_Registry' ) ) {
return $groups;
}
foreach ( array( 'term', 'comment' ) as $entity_type ) {
foreach ( TMDO_Entity_Registry::get_groups_for_type( $entity_type ) as $group ) {
$groups[ $group ] = $groups[ $group ] ?? $entity_type;
}
}
return $groups;
}
/**
* Run backfill for every registered term + comment group.
@@ -83,8 +85,8 @@ final class TMDO_Term_Comment_Backfill {
*/
public static function backfill_all( bool $dry_run = false ): array {
$results = array();
foreach ( self::GROUP_MAP as $group_name => $cfg ) {
$results[ $group_name ] = self::backfill_group( $cfg['entity_type'], $group_name, $dry_run );
foreach ( self::registered_groups() as $group_name => $entity_type ) {
$results[ $group_name ] = self::backfill_group( $entity_type, $group_name, $dry_run );
}
return $results;
}
@@ -110,7 +112,7 @@ final class TMDO_Term_Comment_Backfill {
$result['error'] = 'Entity Registry unavailable';
return $result;
}
if ( ! isset( self::GROUP_MAP[ $group_name ] ) ) {
if ( ( self::registered_groups()[ $group_name ] ?? null ) !== $entity_type ) {
$result['error'] = "Unknown group: {$group_name}";
return $result;
}
@@ -140,7 +142,7 @@ final class TMDO_Term_Comment_Backfill {
return $result;
}
$flat_table = $wpdb->prefix . self::GROUP_MAP[ $group_name ]['table_suffix'];
$flat_table = TMDO_Schema_Manager::get_table_name( $entity_type, $group_name );
// Count candidate entities — those with at least one of the registered keys in wp_*meta.
$placeholders = implode( ',', array_fill( 0, count( $keys ), '%s' ) );
@@ -41,23 +41,6 @@ final class TMDO_Term_Comment_Shadow_Verifier {
/** Default sample size per entity per cron tick. */
public const DEFAULT_SAMPLE_SIZE = 100;
/**
* Group → flat table suffix mapping. Keep in sync with
* TMDO_Hivepress_Term_Comment_Fields registrations and the misc bucket.
*
* @var array<string, array{entity_type:string, table_suffix:string, has_misc:bool}>
*/
private const GROUP_MAP = array(
'hp_taxonomy' => array(
'entity_type' => 'term',
'table_suffix' => 'wpdo_term_hp_taxonomy',
),
'hp_review' => array(
'entity_type' => 'comment',
'table_suffix' => 'wpdo_comment_hp_review',
),
);
/**
* Run a sample-compare pass for a single entity group.
*
@@ -215,11 +198,7 @@ final class TMDO_Term_Comment_Shadow_Verifier {
return;
}
global $wpdb;
foreach ( self::GROUP_MAP as $group => $cfg ) {
$entity_type = $cfg['entity_type'];
foreach ( TMDO_Term_Comment_Backfill::registered_groups() as $group => $entity_type ) {
// Only run when the entity is in shadow_read mode.
if ( 'term' === $entity_type && ! $term_shadow ) {
continue;
@@ -232,7 +211,7 @@ final class TMDO_Term_Comment_Shadow_Verifier {
if ( empty( $keys ) ) {
continue;
}
$flat_table = $wpdb->prefix . $cfg['table_suffix'];
$flat_table = TMDO_Schema_Manager::get_table_name( $entity_type, $group );
try {
self::sample_compare( $entity_type, $group, $flat_table, $keys, self::DEFAULT_SAMPLE_SIZE );
@@ -337,16 +316,14 @@ final class TMDO_Term_Comment_Shadow_Verifier {
return array();
}
global $wpdb;
$out = array();
foreach ( self::GROUP_MAP as $group => $cfg ) {
$entity_type = $cfg['entity_type'];
$keys = TMDO_Entity_Registry::get_group_keys( $entity_type, $group );
foreach ( TMDO_Term_Comment_Backfill::registered_groups() as $group => $entity_type ) {
$keys = TMDO_Entity_Registry::get_group_keys( $entity_type, $group );
if ( empty( $keys ) ) {
continue;
}
$flat_table = $wpdb->prefix . $cfg['table_suffix'];
$flat_table = TMDO_Schema_Manager::get_table_name( $entity_type, $group );
try {
$out[ $group ] = self::sample_compare( $entity_type, $group, $flat_table, $keys, $sample_size );
@@ -43,6 +43,46 @@ if ( ! defined( 'ABSPATH' ) ) {
*/
final class TMDO_Post_Fields {
/**
* Post type of each core group that is not scoped to post types in the
* registry (wp_core spans every post type and is absent on purpose).
*/
private const GROUP_POST_TYPES = array(
'attachment' => 'attachment',
'wc_product' => 'product',
'nav_menu_item' => 'nav_menu_item',
);
/**
* Primary post type of a post group: the registry scope for scoped groups
* (e.g. the HivePress AddOn's), the core mapping otherwise.
*
* @param string $group Entity group name.
* @return string|null Null for cross-post-type groups such as wp_core.
*/
public static function post_type_for_group( string $group ): ?string {
$scoped = class_exists( 'TMDO_Entity_Registry' ) ? TMDO_Entity_Registry::get_group_post_types( 'post', $group ) : array();
return $scoped[0] ?? ( self::GROUP_POST_TYPES[ $group ] ?? null );
}
/**
* Post groups dedicated to a post type (wp_core excluded).
*
* @param string $post_type Post type slug.
* @return string[]
*/
public static function groups_for_post_type( string $post_type ): array {
$groups = array_keys( self::GROUP_POST_TYPES, $post_type, true );
if ( class_exists( 'TMDO_Entity_Registry' ) ) {
foreach ( TMDO_Entity_Registry::get_groups_for_type( 'post' ) as $group ) {
if ( in_array( $post_type, TMDO_Entity_Registry::get_group_post_types( 'post', $group ), true ) ) {
$groups[] = $group;
}
}
}
return array_values( array_unique( $groups ) );
}
/**
* Hook into WPDO entity field registration.
*
@@ -546,8 +546,8 @@ final class TMDO_Post_Migration {
}
/**
* Map group name to the post_type it targets. The mapping is canonical
* to v2.9.1 group definitions in TMDO_Post_Fields.
* Map group name to the post_type it targets (see
* TMDO_Post_Fields::post_type_for_group()).
*
* Returns null for cross-post_type groups (e.g. 'wp_core').
*
@@ -555,23 +555,7 @@ final class TMDO_Post_Migration {
* @return string|null
*/
private static function group_post_type( string $group ): ?string {
switch ( $group ) {
case 'attachment':
return 'attachment';
case 'wc_product':
return 'product';
case 'hp_listing_core':
return 'hp_listing';
case 'hp_request_core':
return 'hp_request';
case 'hp_vendor_core':
return 'hp_vendor';
case 'nav_menu_item':
return 'nav_menu_item';
case 'wp_core':
default:
return null;
}
return TMDO_Post_Fields::post_type_for_group( $group );
}
/**
@@ -43,23 +43,6 @@ class TMDO_Post_Query_Router {
/** Query var key for capturing routed clauses (consumed by posts_join/where). */
private const QUERY_VAR = 'wpdo_post_clauses';
/**
* Map post_type → entity group (mirror of TMDO_Post_Migration::group_post_type
* but inverted; a post_type may map to multiple groups eventually, currently
* we route to the most-specific *_core group plus the cross-cutting wp_core
* group when the key is in wp_core).
*
* @var array<string,string>
*/
private const POST_TYPE_GROUP_MAP = array(
'product' => 'wc_product',
'hp_listing' => 'hp_listing_core',
'hp_request' => 'hp_request_core',
'hp_vendor' => 'hp_vendor_core',
'attachment' => 'attachment',
'nav_menu_item' => 'nav_menu_item',
);
/**
* Register all WP_Query hooks.
*
@@ -117,8 +100,7 @@ class TMDO_Post_Query_Router {
// Confirm the field's group is appropriate for this post_type:
// either cross-cutting wp_core (any) or the post_type-specific group.
$group = $field['group'] ?? '';
$expected_grp = self::POST_TYPE_GROUP_MAP[ $pt ] ?? null;
$is_appropriate = ( 'wp_core' === $group ) || ( null !== $expected_grp && $expected_grp === $group );
$is_appropriate = ( 'wp_core' === $group ) || in_array( $group, TMDO_Post_Fields::groups_for_post_type( (string) $pt ), true );
if ( ! $is_appropriate ) {
continue;
}