hardening 2.2: 2meet-data-optimizer 1.2.0 (drift detection, scope, CLI and diagnostics) #6
@@ -556,11 +556,11 @@ class TMDO_Admin {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// v2.10.0: Post Migration Wizard — backfill all 7 groups.
|
// v2.10.0: Post Migration Wizard — backfill every registered post group.
|
||||||
if ( isset( $_POST['wpdo_post_backfill_all'] ) && check_admin_referer( 'wpdo_post_backfill_all' ) && class_exists( 'TMDO_Post_Migration' ) ) {
|
if ( isset( $_POST['wpdo_post_backfill_all'] ) && check_admin_referer( 'wpdo_post_backfill_all' ) && class_exists( 'TMDO_Post_Migration' ) ) {
|
||||||
$total_migrated = 0;
|
$total_migrated = 0;
|
||||||
$errors = array();
|
$errors = array();
|
||||||
foreach ( array( 'wp_core', 'attachment', 'wc_product', 'hp_listing_core', 'hp_request_core', 'hp_vendor_core', 'nav_menu_item' ) as $group ) {
|
foreach ( TMDO_Post_Migration::backfillable_groups() as $group ) {
|
||||||
try {
|
try {
|
||||||
$result = TMDO_Post_Migration::backfill_group( $group );
|
$result = TMDO_Post_Migration::backfill_group( $group );
|
||||||
$total_migrated += (int) $result['migrated'];
|
$total_migrated += (int) $result['migrated'];
|
||||||
|
|||||||
@@ -198,25 +198,16 @@ class TMDO_CLI_Post {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Backfill one post entity group from wp_postmeta into its flat table
|
* Backfill one post entity group from wp_postmeta into its flat table
|
||||||
* via a single bulk SQL pivot (ON DUPLICATE KEY UPDATE).
|
* via a single bulk SQL pivot (ON DUPLICATE KEY UPDATE). Groups with
|
||||||
|
* JSON-typed fields are backfilled row by row instead.
|
||||||
*
|
*
|
||||||
* Idempotent — safe to re-run. Skips JSON-typed fields (handled by the
|
* Idempotent — safe to re-run.
|
||||||
* row-by-row backfill phase in v2.9.4+).
|
|
||||||
*
|
*
|
||||||
* ## OPTIONS
|
* ## OPTIONS
|
||||||
*
|
*
|
||||||
* --group=<group>
|
* --group=<group>
|
||||||
* : Entity group name. Required.
|
* : Registered post entity group name (e.g. wp_core, attachment,
|
||||||
* ---
|
* hp_listing_core). Required.
|
||||||
* options:
|
|
||||||
* - wp_core
|
|
||||||
* - attachment
|
|
||||||
* - wc_product
|
|
||||||
* - hp_listing_core
|
|
||||||
* - hp_request_core
|
|
||||||
* - hp_vendor_core
|
|
||||||
* - nav_menu_item
|
|
||||||
* ---
|
|
||||||
*
|
*
|
||||||
* ## EXAMPLES
|
* ## EXAMPLES
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -99,11 +99,12 @@ final class TMDO_Post_Migration {
|
|||||||
/**
|
/**
|
||||||
* Bulk SQL pivot for one group: select managed keys from wp_postmeta,
|
* Bulk SQL pivot for one group: select managed keys from wp_postmeta,
|
||||||
* filter by post_type, pivot via MAX(CASE) GROUP BY post_id, UPSERT
|
* filter by post_type, pivot via MAX(CASE) GROUP BY post_id, UPSERT
|
||||||
* into the group's flat table.
|
* 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. 'wc_product').
|
||||||
* @return array{migrated:int,group:string,post_type:string|null}
|
* @return array{migrated:int,group:string,post_type:string|null}
|
||||||
* @throws InvalidArgumentException When group is not registered.
|
* @throws InvalidArgumentException When group is not registered, or is a restored orphan group.
|
||||||
* @throws RuntimeException When the pivot SQL fails.
|
* @throws RuntimeException When the pivot SQL fails.
|
||||||
*/
|
*/
|
||||||
public static function backfill_group( string $group_name ): array {
|
public static function backfill_group( string $group_name ): array {
|
||||||
@@ -115,8 +116,30 @@ final class TMDO_Post_Migration {
|
|||||||
'Unknown post entity group: ' . esc_html( $group_name )
|
'Unknown post entity group: ' . esc_html( $group_name )
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if ( ! in_array( $group_name, self::backfillable_groups(), true ) ) {
|
||||||
|
throw new InvalidArgumentException(
|
||||||
|
'Orphan post group, its values live only in the flat table: ' . esc_html( $group_name )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
$post_type = self::group_post_type( $group_name );
|
$post_type = self::group_post_type( $group_name );
|
||||||
|
|
||||||
|
// json values need PHP-level unserialize → json encode, which the bulk
|
||||||
|
// pivot below cannot do: backfill such groups row by row instead.
|
||||||
|
if ( in_array( 'json', array_column( $fields, 'type' ), true ) ) {
|
||||||
|
$result = self::backfill_group_json( $group_name, array( 'resume' => false ) );
|
||||||
|
if ( ! empty( $result['error'] ) || ! empty( $result['errors'] ) ) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
'backfill_group failed for ' . esc_html( $group_name ) . ': ' . esc_html( (string) ( $result['error'] ?? $result['errors'] . ' row error(s)' ) )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return array(
|
||||||
|
'migrated' => (int) $result['migrated'],
|
||||||
|
'group' => $group_name,
|
||||||
|
'post_type' => $post_type,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
$flat_table = TMDO_Schema_Manager::get_table_name( self::ENTITY_TYPE, $group_name );
|
$flat_table = TMDO_Schema_Manager::get_table_name( self::ENTITY_TYPE, $group_name );
|
||||||
|
|
||||||
// Introspect target table columns so we only pivot fields that actually
|
// Introspect target table columns so we only pivot fields that actually
|
||||||
@@ -131,16 +154,10 @@ final class TMDO_Post_Migration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Build column list and CASE expressions.
|
// Build column list and CASE expressions.
|
||||||
// Skip json/textarea types from the bulk pivot — those need row-by-row
|
|
||||||
// unserialize handling (deferred to v2.9.4 backfill_unserialize phase).
|
|
||||||
$columns = array();
|
$columns = array();
|
||||||
$cases = array();
|
$cases = array();
|
||||||
$update_parts = array();
|
$update_parts = array();
|
||||||
foreach ( $fields as $field ) {
|
foreach ( $fields as $field ) {
|
||||||
$type = $field['type'] ?? 'text';
|
|
||||||
if ( 'json' === $type ) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$col = TMDO_Schema_Manager::sanitize_column_name( $field['key'] );
|
$col = TMDO_Schema_Manager::sanitize_column_name( $field['key'] );
|
||||||
if ( ! isset( $existing_cols[ $col ] ) ) {
|
if ( ! isset( $existing_cols[ $col ] ) ) {
|
||||||
continue; // Column not present in this table — skip silently.
|
continue; // Column not present in this table — skip silently.
|
||||||
@@ -536,6 +553,23 @@ final class TMDO_Post_Migration {
|
|||||||
// Helpers
|
// Helpers
|
||||||
// ─────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registered post groups that can be backfilled from wp_postmeta. Orphan
|
||||||
|
* groups restored from wpdo_registry_meta are left out: their values live
|
||||||
|
* only in the flat table.
|
||||||
|
*
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public static function backfillable_groups(): array {
|
||||||
|
$orphans = array();
|
||||||
|
foreach ( TMDO_Entity_Registry::get_restored_groups() as $restored ) {
|
||||||
|
if ( self::ENTITY_TYPE === $restored['entity_type'] ) {
|
||||||
|
$orphans[] = $restored['group'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return array_values( array_diff( TMDO_Entity_Registry::get_groups_for_type( self::ENTITY_TYPE ), $orphans ) );
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All meta_keys registered for any post entity group.
|
* All meta_keys registered for any post entity group.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -215,6 +215,38 @@ class PostBackfillJsonTest extends TestCase {
|
|||||||
$this->assertSame( 0, $result['errors'] ?? -1 );
|
$this->assertSame( 0, $result['errors'] ?? -1 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── backfill_group() / backfillable_groups() (2.2 PR-067) ───────────────
|
||||||
|
|
||||||
|
public function test_backfill_group_fills_json_columns_instead_of_skipping_them(): void {
|
||||||
|
$this->seed_attachment( 1, array( 'width' => 640 ), 'Alt text' );
|
||||||
|
|
||||||
|
$result = WPDO_Post_Migration::backfill_group( 'attachment' );
|
||||||
|
|
||||||
|
$this->assertSame( 'attachment', $result['group'] );
|
||||||
|
global $wpdb;
|
||||||
|
$row = $wpdb->get_row( 'SELECT _wp_attachment_metadata, _wp_attachment_image_alt FROM `' . self::FLAT . '` WHERE post_id = 1', ARRAY_A );
|
||||||
|
$this->assertSame( array( 'width' => 640 ), json_decode( (string) $row['_wp_attachment_metadata'], true ), 'json field must not be left NULL by the bulk pivot' );
|
||||||
|
$this->assertSame( 'Alt text', $row['_wp_attachment_image_alt'] );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_backfillable_groups_are_the_registered_post_groups(): void {
|
||||||
|
WPDO_Entity_Registry::register_group( 'post', 'itest_extra', array( array( 'key' => 'itest_extra_key', 'type' => 'text' ) ) );
|
||||||
|
|
||||||
|
$groups = WPDO_Post_Migration::backfillable_groups();
|
||||||
|
|
||||||
|
$this->assertSame( WPDO_Entity_Registry::get_groups_for_type( 'post' ), $groups );
|
||||||
|
$this->assertContains( 'itest_extra', $groups, 'a group registered by an add-on must be offered for backfill' );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_restored_orphan_groups_are_never_backfilled(): void {
|
||||||
|
WPDO_Entity_Registry::restore_group( 'post', 'itest_orphan', array( array( 'key' => 'itest_orphan_key', 'type' => 'text' ) ) );
|
||||||
|
$this->assertContains( 'itest_orphan', WPDO_Entity_Registry::get_groups_for_type( 'post' ) );
|
||||||
|
|
||||||
|
$this->assertNotContains( 'itest_orphan', WPDO_Post_Migration::backfillable_groups(), 'orphan data lives only in the flat table' );
|
||||||
|
$this->expectException( InvalidArgumentException::class );
|
||||||
|
WPDO_Post_Migration::backfill_group( 'itest_orphan' );
|
||||||
|
}
|
||||||
|
|
||||||
public function test_returns_error_for_unknown_group(): void {
|
public function test_returns_error_for_unknown_group(): void {
|
||||||
$result = WPDO_Post_Migration::backfill_group_json( 'bogus_group' );
|
$result = WPDO_Post_Migration::backfill_group_json( 'bogus_group' );
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user