diff --git a/cli/class-tmdo-cli-post.php b/cli/class-tmdo-cli-post.php index 54f0e01..1678bd2 100644 --- a/cli/class-tmdo-cli-post.php +++ b/cli/class-tmdo-cli-post.php @@ -211,7 +211,7 @@ class TMDO_CLI_Post { * * ## EXAMPLES * - * wp wpdo post-migrate-group --group=wc_product + * wp wpdo post-migrate-group --group=hp_listing_core * wp wpdo post-migrate-group --group=hp_listing_core * * @param array $args Positional arguments (unused). diff --git a/includes/class-tmdo-post-shadow-verifier.php b/includes/class-tmdo-post-shadow-verifier.php index 6421ce7..9b74b88 100644 --- a/includes/class-tmdo-post-shadow-verifier.php +++ b/includes/class-tmdo-post-shadow-verifier.php @@ -55,7 +55,7 @@ final class TMDO_Post_Shadow_Verifier { * is available. * * @param string $post_type WP post_type to sample. - * @param string $group_name Entity group name (e.g. 'wc_product'). + * @param string $group_name Entity group name (e.g. 'hp_listing_core'). * @param string $flat_table Fully qualified flat table name. * @param string[] $keys Meta keys to compare (each post checks all). * @param int $sample_size Number of posts to sample (capped to DB count). diff --git a/includes/integrations/class-tmdo-post-fields.php b/includes/integrations/class-tmdo-post-fields.php index 5057c1f..511b144 100644 --- a/includes/integrations/class-tmdo-post-fields.php +++ b/includes/integrations/class-tmdo-post-fields.php @@ -2,19 +2,18 @@ /** * Post entity field registration for WP Data Optimizer (v2.9.1). * - * Registers four post entity groups designed to absorb the bulk of + * Registers three post entity groups designed to absorb the bulk of * wp_postmeta rows that previously bypassed any flat-table strategy. * All groups use TMDO_Entity_Registry → flat tables instead of wp_postmeta EAV. * * Groups → flat tables (post_type targets): * wp_core → wp_wpdo_post_wp_core (cross post_type) * attachment → wp_wpdo_post_attachment (attachment) - * wc_product → wp_wpdo_post_wc_product (product) * nav_menu_item → wp_wpdo_post_nav_menu_item (nav_menu_item) * * Pattern mirrors TMDO_Member_Fields::register(). Called from * TMDO_Core::run() before the wpdo_register_entity_fields action fires; - * Schema_Manager materializes the four flat tables in init:1 + * Schema_Manager materializes the three flat tables in init:1 * via process_pending_migrations() (idempotent via schema_hash compare). * * Keys not registered here (HivePress dynamic attrs, WPCS legacy keys, @@ -39,7 +38,10 @@ if ( ! defined( 'ABSPATH' ) ) { } /** - * Registers four post entity groups. + * Registers three post entity groups. WooCommerce product meta is not one of + * them (1.2.0): WooCommerce reads it directly and writes stock by SQL, so + * native meta stays its only copy. Existing wp_wpdo_post_wc_product tables + * are left alone (see `wp tmdo bridge-restore-native post --group=wc_product --retire`). */ final class TMDO_Post_Fields { @@ -49,7 +51,6 @@ final class TMDO_Post_Fields { */ private const GROUP_POST_TYPES = array( 'attachment' => 'attachment', - 'wc_product' => 'product', 'nav_menu_item' => 'nav_menu_item', ); @@ -104,7 +105,6 @@ final class TMDO_Post_Fields { self::register_wp_core_group(); self::register_attachment_group(); - self::register_wc_product_group(); self::register_nav_menu_item_group(); } @@ -176,131 +176,6 @@ final class TMDO_Post_Fields { ); } - /** - * WooCommerce product core meta keys (post_type=product). - * 19 keys — covers ~94% of wp_postmeta rows for products on dev10. - * - * @return void - */ - private static function register_wc_product_group(): void { - TMDO_Entity_Registry::register_group( - 'post', - 'wc_product', - array( - array( - 'key' => '_price', - 'type' => 'decimal', - 'searchable' => true, - 'label' => 'Effective price', - ), - array( - 'key' => '_regular_price', - 'type' => 'decimal', - 'searchable' => true, - 'label' => 'Regular price', - ), - array( - 'key' => '_sale_price', - 'type' => 'decimal', - 'searchable' => true, - 'label' => 'Sale price', - ), - array( - 'key' => '_stock', - 'type' => 'integer', - 'searchable' => true, - 'label' => 'Stock quantity', - ), - array( - 'key' => '_stock_status', - 'type' => 'enum', - 'searchable' => true, - 'options' => array( 'instock', 'outofstock', 'onbackorder' ), - 'label' => 'Stock status', - ), - array( - 'key' => '_sku', - 'type' => 'text', - 'searchable' => true, - 'label' => 'Product SKU', - ), - array( - 'key' => '_manage_stock', - 'type' => 'enum', - 'options' => array( 'yes', 'no' ), - 'label' => 'Manage stock?', - ), - array( - 'key' => '_backorders', - 'type' => 'enum', - 'options' => array( 'yes', 'no', 'notify' ), - 'label' => 'Allow backorders?', - ), - array( - 'key' => '_sold_individually', - 'type' => 'enum', - 'options' => array( 'yes', 'no' ), - 'label' => 'Sold individually?', - ), - array( - 'key' => '_virtual', - 'type' => 'enum', - 'options' => array( 'yes', 'no' ), - 'label' => 'Virtual product?', - ), - array( - 'key' => '_downloadable', - 'type' => 'enum', - 'options' => array( 'yes', 'no' ), - 'label' => 'Downloadable?', - ), - array( - 'key' => '_tax_class', - 'type' => 'text', - 'label' => 'Tax class slug', - ), - array( - 'key' => '_tax_status', - 'type' => 'enum', - 'options' => array( 'taxable', 'shipping', 'none' ), - 'label' => 'Tax status', - ), - array( - 'key' => '_download_limit', - 'type' => 'integer', - 'label' => 'Download limit', - ), - array( - 'key' => '_download_expiry', - 'type' => 'integer', - 'label' => 'Download expiry days', - ), - array( - 'key' => '_product_version', - 'type' => 'text', - 'label' => 'WC version product was created on', - ), - array( - 'key' => '_wc_average_rating', - 'type' => 'decimal', - 'searchable' => true, - 'label' => 'Average rating', - ), - array( - 'key' => '_wc_review_count', - 'type' => 'integer', - 'label' => 'Review count', - ), - array( - 'key' => 'total_sales', - 'type' => 'integer', - 'searchable' => true, - 'label' => 'Total sales count', - ), - ) - ); - } - /** * Nav menu item meta keys (post_type=nav_menu_item). * diff --git a/includes/migration/class-tmdo-post-migration.php b/includes/migration/class-tmdo-post-migration.php index 4f77abe..9059b47 100644 --- a/includes/migration/class-tmdo-post-migration.php +++ b/includes/migration/class-tmdo-post-migration.php @@ -102,7 +102,7 @@ final class TMDO_Post_Migration { * into the group's flat table. Groups with json fields go row by row * through backfill_group_json() instead. * - * @param string $group_name Entity group name (e.g. 'wc_product'). + * @param string $group_name Entity group name (e.g. 'hp_listing_core'). * @return array{migrated:int,group:string,post_type:string|null} * @throws InvalidArgumentException When group is not registered, or is a restored orphan group. * @throws RuntimeException When the pivot SQL fails. diff --git a/tests/integration/PostEntityLifecycleTest.php b/tests/integration/PostEntityLifecycleTest.php index 097c951..b074999 100644 --- a/tests/integration/PostEntityLifecycleTest.php +++ b/tests/integration/PostEntityLifecycleTest.php @@ -129,6 +129,7 @@ class PostEntityLifecycleTest extends TestCase { WPDO_Entity_Registry::init(); WPDO_Entity_Registry::register_adapter( 'post', new WPDO_Adapter_Post() ); WPDO_Post_Fields::register_entity_fields(); + tmdo_test_register_wc_product_group(); } public static function tearDownAfterClass(): void { diff --git a/tests/integration/PostMigrationTest.php b/tests/integration/PostMigrationTest.php index 164a118..d8eb3ff 100644 --- a/tests/integration/PostMigrationTest.php +++ b/tests/integration/PostMigrationTest.php @@ -93,6 +93,7 @@ class PostMigrationTest extends TestCase { WPDO_Entity_Registry::init(); WPDO_Entity_Registry::register_adapter( 'post', new WPDO_Adapter_Post() ); WPDO_Post_Fields::register_entity_fields(); + tmdo_test_register_wc_product_group(); } public static function tearDownAfterClass(): void { diff --git a/tests/integration/PostQueryRouterDriftTest.php b/tests/integration/PostQueryRouterDriftTest.php index db076e7..a113202 100644 --- a/tests/integration/PostQueryRouterDriftTest.php +++ b/tests/integration/PostQueryRouterDriftTest.php @@ -15,6 +15,7 @@ class PostQueryRouterDriftTest extends TestCase { WPDO_Entity_Registry::init(); WPDO_Entity_Registry::register_adapter( 'post', new WPDO_Adapter_Post() ); WPDO_Post_Fields::register_entity_fields(); + tmdo_test_register_wc_product_group(); } public static function tearDownAfterClass(): void { diff --git a/tests/integration/PostQueryRouterOrderTest.php b/tests/integration/PostQueryRouterOrderTest.php index 5653581..b9a1198 100644 --- a/tests/integration/PostQueryRouterOrderTest.php +++ b/tests/integration/PostQueryRouterOrderTest.php @@ -20,6 +20,7 @@ class PostQueryRouterOrderTest extends TestCase { WPDO_Entity_Registry::init(); WPDO_Entity_Registry::register_adapter( 'post', new WPDO_Adapter_Post() ); WPDO_Post_Fields::register_entity_fields(); + tmdo_test_register_wc_product_group(); } protected function setUp(): void { diff --git a/tests/integration/PostQueryRouterTest.php b/tests/integration/PostQueryRouterTest.php index 9c16205..2f82d5b 100644 --- a/tests/integration/PostQueryRouterTest.php +++ b/tests/integration/PostQueryRouterTest.php @@ -39,6 +39,7 @@ class PostQueryRouterTest extends TestCase { WPDO_Entity_Registry::init(); WPDO_Entity_Registry::register_adapter( 'post', new WPDO_Adapter_Post() ); WPDO_Post_Fields::register_entity_fields(); + tmdo_test_register_wc_product_group(); } public static function tearDownAfterClass(): void { diff --git a/tests/integration/bootstrap.php b/tests/integration/bootstrap.php index 0eadfa2..6d3dad8 100644 --- a/tests/integration/bootstrap.php +++ b/tests/integration/bootstrap.php @@ -649,3 +649,43 @@ if ( ! class_exists( 'TMDO_Listing_Stats' ) ) { } class_alias( 'TMDO_Listing_Stats', 'WPDO_Listing_Stats' ); } + +/** + * The WooCommerce product group core registered before 1.2.0 (PR-077 moved + * product meta back to native only). Tests that use products as a routed + * post type register it themselves; the table name wp_itest_wpdo_post_wc_product + * is unchanged. + */ +function tmdo_test_register_wc_product_group(): void { + $enum = static fn( string $key, array $options ): array => array( + 'key' => $key, + 'type' => 'enum', + 'options' => $options, + ); + TMDO_Entity_Registry::register_group( + 'post', + 'wc_product', + array( + array( 'key' => '_price', 'type' => 'decimal', 'searchable' => true ), + array( 'key' => '_regular_price', 'type' => 'decimal', 'searchable' => true ), + array( 'key' => '_sale_price', 'type' => 'decimal', 'searchable' => true ), + array( 'key' => '_stock', 'type' => 'integer', 'searchable' => true ), + array( 'key' => '_stock_status', 'type' => 'enum', 'searchable' => true, 'options' => array( 'instock', 'outofstock', 'onbackorder' ) ), + array( 'key' => '_sku', 'type' => 'text', 'searchable' => true ), + $enum( '_manage_stock', array( 'yes', 'no' ) ), + $enum( '_backorders', array( 'yes', 'no', 'notify' ) ), + $enum( '_sold_individually', array( 'yes', 'no' ) ), + $enum( '_virtual', array( 'yes', 'no' ) ), + $enum( '_downloadable', array( 'yes', 'no' ) ), + array( 'key' => '_tax_class', 'type' => 'text' ), + $enum( '_tax_status', array( 'taxable', 'shipping', 'none' ) ), + array( 'key' => '_download_limit', 'type' => 'integer' ), + array( 'key' => '_download_expiry', 'type' => 'integer' ), + array( 'key' => '_product_version', 'type' => 'text' ), + array( 'key' => '_wc_average_rating', 'type' => 'decimal', 'searchable' => true ), + array( 'key' => '_wc_review_count', 'type' => 'integer' ), + array( 'key' => 'total_sales', 'type' => 'integer', 'searchable' => true ), + ), + array( 'post_types' => array( 'product' ) ) + ); +} diff --git a/tests/live/suites/router.php b/tests/live/suites/router.php index 0ce0a6e..147516d 100644 --- a/tests/live/suites/router.php +++ b/tests/live/suites/router.php @@ -247,21 +247,22 @@ TMDO_Live::test( TMDO_Live::test( 'Q06 LIKE matches substrings and escapes wildcards, routed', static function () { - TMDO_Live_Fixtures::field( 'post', '_sku', 'product' ); + // A text key of wp_core (WooCommerce's _sku left the flat tables in 1.2.0). + TMDO_Live_Fixtures::field( 'post', '_wp_page_template' ); TMDO_Live_Fixtures::use_modes( array( 'post' => 'shadow_read' ) ); $p = tmdo_live_router_posts( - 'product', + 'post', array( - 'P1' => array( '_sku' => 'tmdo-live-B1-1' ), - 'P2' => array( '_sku' => 'tmdo-live-B1-10' ), - 'P3' => array( '_sku' => 'tmdo-live-B2-1' ), + 'P1' => array( '_wp_page_template' => 'tmdo-live-B1-1' ), + 'P2' => array( '_wp_page_template' => 'tmdo-live-B1-10' ), + 'P3' => array( '_wp_page_template' => 'tmdo-live-B2-1' ), ) ); foreach ( array( 'B1-1', 'B1_1' ) as $needle ) { tmdo_live_router_assert_native( array( - 'post_type' => 'product', - 'meta_query' => array( array( 'key' => '_sku', 'value' => $needle, 'compare' => 'LIKE' ) ), + 'post_type' => 'post', + 'meta_query' => array( array( 'key' => '_wp_page_template', 'value' => $needle, 'compare' => 'LIKE' ) ), ), array_values( $p ), true diff --git a/tests/unit/PostFields/PostFieldsRegistrationTest.php b/tests/unit/PostFields/PostFieldsRegistrationTest.php index 112d572..4e9ddb3 100644 --- a/tests/unit/PostFields/PostFieldsRegistrationTest.php +++ b/tests/unit/PostFields/PostFieldsRegistrationTest.php @@ -33,13 +33,12 @@ class PostFieldsRegistrationTest extends TestCase { $this->assertTrue( true ); } - public function test_all_four_groups_are_registered(): void { + public function test_the_core_groups_are_registered(): void { WPDO_Post_Fields::register_entity_fields(); $expected = array( 'wp_core', 'attachment', - 'wc_product', 'nav_menu_item', ); @@ -92,38 +91,14 @@ class PostFieldsRegistrationTest extends TestCase { $this->assertSame( 'json', $field['type'] ); } - // ── wc_product group ───────────────────────────────────────────────────── + // ── WooCommerce products stay in native meta (2.2 PR-077) ─────────────── - public function test_wc_product_group_has_19_keys(): void { + public function test_core_registers_no_woocommerce_product_group(): void { WPDO_Post_Fields::register_entity_fields(); - $keys = $this->get_field_keys( 'wc_product' ); - $this->assertCount( 19, $keys, 'wc_product group should register exactly 19 keys.' ); - } - public function test_wc_product_critical_keys_present(): void { - WPDO_Post_Fields::register_entity_fields(); - $keys = $this->get_field_keys( 'wc_product' ); - foreach ( array( '_price', '_regular_price', '_sale_price', '_stock', '_stock_status', '_sku' ) as $key ) { - $this->assertContains( $key, $keys, "wc_product missing critical key: {$key}" ); - } - } - - public function test_wc_product_price_is_searchable_decimal(): void { - WPDO_Post_Fields::register_entity_fields(); - $field = $this->find_field( 'wc_product', '_price' ); - $this->assertNotNull( $field ); - $this->assertSame( 'decimal', $field['type'] ); - $this->assertTrue( (bool) ( $field['searchable'] ?? false ) ); - } - - public function test_wc_product_stock_status_is_enum_searchable(): void { - WPDO_Post_Fields::register_entity_fields(); - $field = $this->find_field( 'wc_product', '_stock_status' ); - $this->assertNotNull( $field ); - $this->assertSame( 'enum', $field['type'] ); - $this->assertTrue( (bool) ( $field['searchable'] ?? false ) ); - $this->assertContains( 'instock', $field['options'] ); - $this->assertContains( 'outofstock', $field['options'] ); + $this->assertSame( array(), WPDO_Entity_Registry::get_group_fields( 'post', 'wc_product' ), 'WooCommerce reads and writes product meta directly (stock by SQL)' ); + $this->assertNull( WPDO_Entity_Registry::get_field( 'post', '_price', 'product' ) ); + $this->assertSame( array(), WPDO_Post_Fields::groups_for_post_type( 'product' ) ); } // ── HivePress groups moved to the HivePress AddOn ─────────────────────── @@ -139,7 +114,6 @@ class PostFieldsRegistrationTest extends TestCase { public function test_core_groups_map_to_their_post_types(): void { $this->assertSame( 'attachment', WPDO_Post_Fields::post_type_for_group( 'attachment' ) ); - $this->assertSame( 'product', WPDO_Post_Fields::post_type_for_group( 'wc_product' ) ); $this->assertSame( 'nav_menu_item', WPDO_Post_Fields::post_type_for_group( 'nav_menu_item' ) ); $this->assertNull( WPDO_Post_Fields::post_type_for_group( 'wp_core' ), 'wp_core spans every post type' ); } @@ -149,7 +123,6 @@ class PostFieldsRegistrationTest extends TestCase { $this->assertSame( 'x_type', WPDO_Post_Fields::post_type_for_group( 'x_core' ) ); $this->assertSame( array( 'x_core' ), WPDO_Post_Fields::groups_for_post_type( 'x_type' ) ); - $this->assertSame( array( 'wc_product' ), WPDO_Post_Fields::groups_for_post_type( 'product' ) ); $this->assertNull( WPDO_Post_Fields::post_type_for_group( 'hp_listing_core' ), 'unregistered HivePress group is unknown to core' ); }