hardening 2.0: 2meet-data-optimizer-hivepress-addon (staged rollout, dev22 first) #1
@@ -138,7 +138,7 @@ wp wpdo hivepress benchmark</pre>
|
|||||||
. esc_html__( '偵測到衝突外掛:', 'tmdo-hivepress' ) . '</strong> '
|
. esc_html__( '偵測到衝突外掛:', 'tmdo-hivepress' ) . '</strong> '
|
||||||
. esc_html( implode( ', ', $conflicts ) )
|
. esc_html( implode( ', ', $conflicts ) )
|
||||||
. '</p><p>'
|
. '</p><p>'
|
||||||
. esc_html__( 'WPDO HivePress integration 已自動退讓。請執行 `wp wpdo hivepress migrate-from-hpct` 後停用舊外掛。', 'tmdo-hivepress' )
|
. esc_html__( 'WPDO HivePress integration 已自動退讓。停用舊外掛後即由 WPDO 接手(舊外掛自有資料表的資料不會匯入)。', 'tmdo-hivepress' )
|
||||||
. '</p></div>';
|
. '</p></div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ add_action(
|
|||||||
$aliases = array(
|
$aliases = array(
|
||||||
'TMDO_Admin_HivePress' => 'WPDO_Admin_HivePress',
|
'TMDO_Admin_HivePress' => 'WPDO_Admin_HivePress',
|
||||||
'TMDO_CLI_HivePress' => 'WPDO_CLI_HivePress',
|
'TMDO_CLI_HivePress' => 'WPDO_CLI_HivePress',
|
||||||
'TMDO_HPCT_Import' => 'WPDO_HPCT_Import',
|
|
||||||
'TMDO_HP_Bootstrap' => 'WPDO_HP_Bootstrap',
|
'TMDO_HP_Bootstrap' => 'WPDO_HP_Bootstrap',
|
||||||
'TMDO_HivePress' => 'WPDO_HivePress',
|
'TMDO_HivePress' => 'WPDO_HivePress',
|
||||||
'TMDO_HivePress_Attribute_Bridge' => 'WPDO_HivePress_Attribute_Bridge',
|
'TMDO_HivePress_Attribute_Bridge' => 'WPDO_HivePress_Attribute_Bridge',
|
||||||
|
|||||||
@@ -66,7 +66,6 @@ final class TMDO_HP_Bootstrap {
|
|||||||
// Misc HP integration files
|
// Misc HP integration files
|
||||||
require_once $base . 'includes/class-tmdo-hivepress.php';
|
require_once $base . 'includes/class-tmdo-hivepress.php';
|
||||||
require_once $base . 'includes/class-tmdo-hivepress-transient-filter.php';
|
require_once $base . 'includes/class-tmdo-hivepress-transient-filter.php';
|
||||||
require_once $base . 'includes/class-tmdo-hpct-import.php';
|
|
||||||
require_once $base . 'includes/class-tmdo-hivepress-term-comment-fields.php';
|
require_once $base . 'includes/class-tmdo-hivepress-term-comment-fields.php';
|
||||||
require_once $base . 'includes/class-tmdo-hivepress-entity-groups.php';
|
require_once $base . 'includes/class-tmdo-hivepress-entity-groups.php';
|
||||||
require_once $base . 'includes/class-tmdo-listing-stats.php';
|
require_once $base . 'includes/class-tmdo-listing-stats.php';
|
||||||
|
|||||||
@@ -1,248 +0,0 @@
|
|||||||
<?php
|
|
||||||
/**
|
|
||||||
* HPCT Import integration for WP Data Optimizer.
|
|
||||||
*
|
|
||||||
* @package WP_Data_Optimizer
|
|
||||||
*/
|
|
||||||
|
|
||||||
if ( ! defined( 'ABSPATH' ) ) {
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HPCT Import — reads hp-custom-tables' feature flags and migration records,
|
|
||||||
* maps them to WPDO's 7-state lifecycle, and copies progress data.
|
|
||||||
*
|
|
||||||
* After import, WPDO takes ownership of all 9 HPCT modules and their
|
|
||||||
* corresponding hpct_* tables. The user is advised to deactivate hp-custom-tables.
|
|
||||||
*
|
|
||||||
* Usage:
|
|
||||||
* - Admin UI: Tools > WP Data Optimizer > HPCT Import tab
|
|
||||||
* - CLI: wp wpdo import-hpct
|
|
||||||
*/
|
|
||||||
class TMDO_HPCT_Import {
|
|
||||||
|
|
||||||
/** Option key that marks import as completed. */
|
|
||||||
public const IMPORTED_OPTION = 'wpdo_hpct_imported';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if HPCT import has already been completed.
|
|
||||||
*/
|
|
||||||
public static function is_imported(): bool {
|
|
||||||
return (bool) get_option( self::IMPORTED_OPTION, false );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if hp-custom-tables is active and has data to import.
|
|
||||||
*/
|
|
||||||
public static function can_import(): bool {
|
|
||||||
if ( self::is_imported() ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return TMDO_Compatibility::is_hpct_active();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Preview: return what will be imported without actually doing it.
|
|
||||||
*
|
|
||||||
* @return array{modules: array, migration_records: int, has_data: bool}
|
|
||||||
*/
|
|
||||||
public static function preview(): array {
|
|
||||||
$hpct_features = get_option( 'hpct_features', array() );
|
|
||||||
if ( ! is_array( $hpct_features ) ) {
|
|
||||||
$hpct_features = array();
|
|
||||||
}
|
|
||||||
|
|
||||||
$modules = array();
|
|
||||||
foreach ( TMDO_Feature_Flags::HPCT_MODULES as $module ) {
|
|
||||||
$hpct_status = $hpct_features[ $module ] ?? 'disabled';
|
|
||||||
$wpdo_state = TMDO_Feature_Flags::map_hpct_status( $hpct_status );
|
|
||||||
|
|
||||||
$modules[] = array(
|
|
||||||
'module' => $module,
|
|
||||||
'hpct_status' => $hpct_status,
|
|
||||||
'wpdo_state' => $wpdo_state,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Count HPCT migration records.
|
|
||||||
global $wpdb;
|
|
||||||
$hpct_migrations_table = $wpdb->prefix . 'hpct_migrations';
|
|
||||||
$migration_count = 0;
|
|
||||||
|
|
||||||
// Check if hpct_migrations table exists.
|
|
||||||
if ( TMDO_IS_SQLITE ) {
|
|
||||||
$table_exists = $wpdb->get_var(
|
|
||||||
$wpdb->prepare(
|
|
||||||
"SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name = %s",
|
|
||||||
$hpct_migrations_table
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
$table_exists = $wpdb->get_var(
|
|
||||||
$wpdb->prepare(
|
|
||||||
'SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = %s',
|
|
||||||
$hpct_migrations_table
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( $table_exists ) {
|
|
||||||
$migration_count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$hpct_migrations_table}`" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name is $wpdb->prefix . 'hpct_migrations'.
|
|
||||||
}
|
|
||||||
|
|
||||||
return array(
|
|
||||||
'modules' => $modules,
|
|
||||||
'migration_records' => $migration_count,
|
|
||||||
'has_data' => ! empty( $hpct_features ),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute the import.
|
|
||||||
*
|
|
||||||
* @return array{success: bool, message: string, imported: array}
|
|
||||||
*/
|
|
||||||
public static function run(): array {
|
|
||||||
if ( self::is_imported() ) {
|
|
||||||
return array(
|
|
||||||
'success' => false,
|
|
||||||
'message' => 'HPCT import has already been completed.',
|
|
||||||
'imported' => array(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 1: Read HPCT feature flags.
|
|
||||||
$hpct_features = get_option( 'hpct_features', array() );
|
|
||||||
if ( ! is_array( $hpct_features ) ) {
|
|
||||||
$hpct_features = array();
|
|
||||||
}
|
|
||||||
|
|
||||||
$imported = array();
|
|
||||||
|
|
||||||
// Step 2: Map each HPCT module state to WPDO state.
|
|
||||||
foreach ( TMDO_Feature_Flags::HPCT_MODULES as $module ) {
|
|
||||||
$hpct_status = $hpct_features[ $module ] ?? 'disabled';
|
|
||||||
$wpdo_state = TMDO_Feature_Flags::map_hpct_status( $hpct_status );
|
|
||||||
|
|
||||||
TMDO_Feature_Flags::set( $module, $wpdo_state );
|
|
||||||
|
|
||||||
$imported[] = array(
|
|
||||||
'module' => $module,
|
|
||||||
'hpct_status' => $hpct_status,
|
|
||||||
'wpdo_state' => $wpdo_state,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 3: Copy HPCT migration records to WPDO.
|
|
||||||
self::copy_migration_records();
|
|
||||||
|
|
||||||
// Step 4: Mark import as completed.
|
|
||||||
update_option( self::IMPORTED_OPTION, true );
|
|
||||||
|
|
||||||
return array(
|
|
||||||
'success' => true,
|
|
||||||
'message' => sprintf(
|
|
||||||
'Successfully imported %d HPCT modules. You can now deactivate hp-custom-tables.',
|
|
||||||
count( $imported )
|
|
||||||
),
|
|
||||||
'imported' => $imported,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy HPCT migration progress records to wpdo_migrations.
|
|
||||||
*/
|
|
||||||
private static function copy_migration_records(): void {
|
|
||||||
global $wpdb;
|
|
||||||
|
|
||||||
$hpct_table = $wpdb->prefix . 'hpct_migrations';
|
|
||||||
$wpdo_table = TMDO_DB::table( 'wpdo_migrations' );
|
|
||||||
$now = TMDO_DB::now();
|
|
||||||
|
|
||||||
// Check if hpct_migrations table exists.
|
|
||||||
$exists = false;
|
|
||||||
if ( TMDO_IS_SQLITE ) {
|
|
||||||
$exists = (bool) $wpdb->get_var(
|
|
||||||
$wpdb->prepare(
|
|
||||||
"SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name = %s",
|
|
||||||
$hpct_table
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
$exists = (bool) $wpdb->get_var(
|
|
||||||
$wpdb->prepare(
|
|
||||||
'SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = %s',
|
|
||||||
$hpct_table
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( ! $exists ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$hpct_records = $wpdb->get_results( "SELECT * FROM `{$hpct_table}`", ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name is $wpdb->prefix . 'hpct_migrations'.
|
|
||||||
if ( ! $hpct_records ) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ( $hpct_records as $record ) {
|
|
||||||
$module = $record['module'] ?? '';
|
|
||||||
if ( ! $module ) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Map HPCT status to WPDO state.
|
|
||||||
$hpct_status = $record['status'] ?? 'pending';
|
|
||||||
$state = self::map_migration_status( $hpct_status );
|
|
||||||
|
|
||||||
// Check if WPDO already has a record for this module.
|
|
||||||
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- table name from TMDO_DB::table().
|
|
||||||
$existing = $wpdb->get_var(
|
|
||||||
$wpdb->prepare(
|
|
||||||
"SELECT id FROM `{$wpdo_table}` WHERE module = %s",
|
|
||||||
$module
|
|
||||||
)
|
|
||||||
);
|
|
||||||
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
|
|
||||||
|
|
||||||
$data = array(
|
|
||||||
'module' => $module,
|
|
||||||
'zone' => '',
|
|
||||||
'state' => $state,
|
|
||||||
'total_rows' => (int) ( $record['total_rows'] ?? 0 ),
|
|
||||||
'processed_rows' => (int) ( $record['processed_rows'] ?? 0 ),
|
|
||||||
'last_offset' => (int) ( $record['last_offset'] ?? 0 ),
|
|
||||||
'error_count' => (int) ( $record['error_count'] ?? 0 ),
|
|
||||||
'started_at' => $record['started_at'] ?? null,
|
|
||||||
'completed_at' => $record['completed_at'] ?? null,
|
|
||||||
'updated_at' => $now,
|
|
||||||
);
|
|
||||||
|
|
||||||
if ( $existing ) {
|
|
||||||
$wpdb->update( $wpdo_table, $data, array( 'id' => (int) $existing ) );
|
|
||||||
} else {
|
|
||||||
$data['created_at'] = $now;
|
|
||||||
$wpdb->insert( $wpdo_table, $data );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Map HPCT migration record status to WPDO migration state.
|
|
||||||
*
|
|
||||||
* @param string $hpct_status HPCT migration status string.
|
|
||||||
* @return string Corresponding WPDO migration state.
|
|
||||||
*/
|
|
||||||
private static function map_migration_status( string $hpct_status ): string {
|
|
||||||
return match ( $hpct_status ) {
|
|
||||||
'pending' => 'idle',
|
|
||||||
'running' => 'backfill',
|
|
||||||
'completed' => 'verify',
|
|
||||||
'failed' => 'idle',
|
|
||||||
default => 'idle',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,9 +10,8 @@
|
|||||||
*
|
*
|
||||||
* Strategy: detect, log, render admin notice with guidance, AND short-circuit
|
* Strategy: detect, log, render admin notice with guidance, AND short-circuit
|
||||||
* the WPDO HivePress bootstrap via `wpdo_hivepress_should_bind` filter so
|
* the WPDO HivePress bootstrap via `wpdo_hivepress_should_bind` filter so
|
||||||
* adapters are NOT bound. Operator must explicitly run
|
* adapters are NOT bound. The operator deactivates the legacy plugins to let
|
||||||
* `wp wpdo hivepress migrate-from-hpct` to switch to WPDO and deactivate
|
* WPDO take over; data kept in their own tables is not imported.
|
||||||
* the legacy plugins.
|
|
||||||
*
|
*
|
||||||
* @package WP_Data_Optimizer
|
* @package WP_Data_Optimizer
|
||||||
* @since 3.0.0
|
* @since 3.0.0
|
||||||
@@ -163,7 +162,7 @@ if ( ! class_exists( 'TMDO_HivePress_Conflict_Guard' ) ) {
|
|||||||
|
|
||||||
$message = sprintf(
|
$message = sprintf(
|
||||||
/* translators: %s: comma-separated list of conflicting plugin names. */
|
/* translators: %s: comma-separated list of conflicting plugin names. */
|
||||||
__( 'wp-data-optimizer detected legacy HivePress data-layer plugins: %s. WPDO HivePress integration is paused to prevent dual-write corruption. Run `wp wpdo hivepress migrate-from-hpct` to migrate, then deactivate the legacy plugins.', 'tmdo-hivepress' ),
|
__( 'wp-data-optimizer detected legacy HivePress data-layer plugins: %s. WPDO HivePress integration is paused to prevent dual-write corruption. Deactivate the legacy plugins to let WPDO take over (data in their own tables is not imported).', 'tmdo-hivepress' ),
|
||||||
implode( ', ', $names )
|
implode( ', ', $names )
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -206,7 +205,7 @@ if ( ! class_exists( 'TMDO_HivePress_Conflict_Guard' ) ) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$message = sprintf(
|
$message = sprintf(
|
||||||
'Conflict detected: %s. wp-data-optimizer fully supersedes these. Run: wp wpdo hivepress migrate-from-hpct',
|
'Conflict detected: %s. wp-data-optimizer fully supersedes these. Deactivate them to let WPDO take over.',
|
||||||
implode( ', ', $conflicts )
|
implode( ', ', $conflicts )
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ $tmdo_hp_files = array(
|
|||||||
'includes/class-tmdo-hivepress-transient-filter.php',
|
'includes/class-tmdo-hivepress-transient-filter.php',
|
||||||
'includes/class-tmdo-hivepress-term-comment-fields.php',
|
'includes/class-tmdo-hivepress-term-comment-fields.php',
|
||||||
'includes/class-tmdo-hivepress-entity-groups.php',
|
'includes/class-tmdo-hivepress-entity-groups.php',
|
||||||
'includes/class-tmdo-hpct-import.php',
|
|
||||||
'includes/class-tmdo-listing-stats.php',
|
'includes/class-tmdo-listing-stats.php',
|
||||||
'admin/class-tmdo-admin-hivepress.php',
|
'admin/class-tmdo-admin-hivepress.php',
|
||||||
'cli/class-tmdo-cli-hivepress.php',
|
'cli/class-tmdo-cli-hivepress.php',
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ $tmdo_hp_files = array(
|
|||||||
'includes/class-tmdo-hivepress-transient-filter.php',
|
'includes/class-tmdo-hivepress-transient-filter.php',
|
||||||
'includes/class-tmdo-hivepress-term-comment-fields.php',
|
'includes/class-tmdo-hivepress-term-comment-fields.php',
|
||||||
'includes/class-tmdo-hivepress-entity-groups.php',
|
'includes/class-tmdo-hivepress-entity-groups.php',
|
||||||
'includes/class-tmdo-hpct-import.php',
|
|
||||||
'admin/class-tmdo-admin-hivepress.php',
|
'admin/class-tmdo-admin-hivepress.php',
|
||||||
'cli/class-tmdo-cli-hivepress.php',
|
'cli/class-tmdo-cli-hivepress.php',
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -47,6 +47,16 @@ class HivePressConflictGuardTest extends TestCase {
|
|||||||
$this->assertSame( '', $out );
|
$this->assertSame( '', $out );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_guidance_does_not_point_to_a_missing_command(): void {
|
||||||
|
// There is no HPCT importer: `wp wpdo hivepress migrate-from-hpct` never
|
||||||
|
// existed and TMDO_HPCT_Import had no caller once core dropped HPCT.
|
||||||
|
$root = dirname( __DIR__, 2 );
|
||||||
|
foreach ( array( 'includes/hivepress/class-tmdo-hivepress-conflict-guard.php', 'admin/class-tmdo-admin-hivepress.php' ) as $file ) {
|
||||||
|
$this->assertStringNotContainsString( 'migrate-from-hpct', (string) file_get_contents( $root . '/' . $file ), $file );
|
||||||
|
}
|
||||||
|
$this->assertFileDoesNotExist( $root . '/includes/class-tmdo-hpct-import.php' );
|
||||||
|
}
|
||||||
|
|
||||||
public function test_memo_cache_avoids_repeat_probing(): void {
|
public function test_memo_cache_avoids_repeat_probing(): void {
|
||||||
WPDO_HivePress_Conflict_Guard::reset_for_tests();
|
WPDO_HivePress_Conflict_Guard::reset_for_tests();
|
||||||
$first = WPDO_HivePress_Conflict_Guard::detect_conflicts();
|
$first = WPDO_HivePress_Conflict_Guard::detect_conflicts();
|
||||||
|
|||||||
Reference in New Issue
Block a user