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
267 lines
7.4 KiB
PHP
267 lines
7.4 KiB
PHP
<?php
|
|
/**
|
|
* Conflict guard against legacy HivePress data-layer plugins.
|
|
*
|
|
* `wp-data-optimizer` v3.0.0 fully supersedes `hp-custom-tables` (HPCT) and
|
|
* `hp-info-cards`. When either is detected alongside WPDO HivePress
|
|
* integration, dual-write would corrupt zone tables (HPCT writes to its own
|
|
* `hpct_*` tables while WPDO writes to `wpdo_hot_hp_*`, then both copies
|
|
* drift) and admin tooling would show contradictory state.
|
|
*
|
|
* Strategy: detect, log, render admin notice with guidance, AND short-circuit
|
|
* the WPDO HivePress bootstrap via `wpdo_hivepress_should_bind` filter so
|
|
* adapters are NOT bound. Operator must explicitly run
|
|
* `wp wpdo hivepress migrate-from-hpct` to switch to WPDO and deactivate
|
|
* the legacy plugins.
|
|
*
|
|
* @package WP_Data_Optimizer
|
|
* @since 3.0.0
|
|
*/
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
if ( ! class_exists( 'TMDO_HivePress_Conflict_Guard' ) ) {
|
|
|
|
/**
|
|
* Static guard checking for legacy plugin conflicts.
|
|
*
|
|
* Stateful across requests via memo cache only; persists no DB state.
|
|
*/
|
|
final class TMDO_HivePress_Conflict_Guard {
|
|
|
|
/**
|
|
* Conflicting legacy plugins to detect.
|
|
*
|
|
* Probe by either `class_exists()` OR `defined()` — both signals are
|
|
* checked since plugin authors are inconsistent about which they expose.
|
|
*
|
|
* @var array<string, array{classes:array<int,string>, consts:array<int,string>, name:string}>
|
|
*/
|
|
private const CONFLICTS = array(
|
|
'hp-custom-tables' => array(
|
|
'classes' => array(
|
|
'HPCT_Core',
|
|
'HPCT_Plugin',
|
|
'HP_Custom_Tables',
|
|
),
|
|
'consts' => array(
|
|
'HPCT_VERSION',
|
|
'HP_CUSTOM_TABLES_VERSION',
|
|
),
|
|
'name' => 'HP Custom Tables',
|
|
),
|
|
'hp-info-cards' => array(
|
|
'classes' => array(
|
|
'HP_Info_Cards',
|
|
'HPIC_Plugin',
|
|
),
|
|
'consts' => array(
|
|
'HP_INFO_CARDS_VERSION',
|
|
'HPIC_VERSION',
|
|
),
|
|
'name' => 'HP Info Cards',
|
|
),
|
|
);
|
|
|
|
/**
|
|
* Memoized detection result for the current request.
|
|
*
|
|
* @var array<int,string>|null
|
|
*/
|
|
private static ?array $memo = null;
|
|
|
|
/**
|
|
* Whether the admin_notices hook has been bound this request.
|
|
*
|
|
* @var bool
|
|
*/
|
|
private static bool $notice_bound = false;
|
|
|
|
/**
|
|
* Run the conflict check at boot time.
|
|
*
|
|
* Called by `TMDO_HivePress_Bootstrap::boot()` BEFORE adapter binding
|
|
* so the `wpdo_hivepress_should_bind` filter has chance to short-circuit
|
|
* adapter wiring when conflicts exist.
|
|
*/
|
|
public static function check_and_warn(): void {
|
|
$conflicts = self::detect_conflicts();
|
|
if ( empty( $conflicts ) ) {
|
|
return;
|
|
}
|
|
|
|
self::log_conflicts( $conflicts );
|
|
self::ensure_notice_bound();
|
|
self::install_should_bind_short_circuit( $conflicts );
|
|
}
|
|
|
|
/**
|
|
* Detect currently-active legacy plugins.
|
|
*
|
|
* @return array<int,string> Slugs of detected conflicting plugins.
|
|
*/
|
|
public static function detect_conflicts(): array {
|
|
if ( null !== self::$memo ) {
|
|
return self::$memo;
|
|
}
|
|
|
|
$out = array();
|
|
foreach ( self::CONFLICTS as $slug => $probe ) {
|
|
if ( self::probe_one( $probe ) ) {
|
|
$out[] = $slug;
|
|
}
|
|
}
|
|
|
|
self::$memo = $out;
|
|
return $out;
|
|
}
|
|
|
|
/**
|
|
* Reset internal state for unit tests.
|
|
*
|
|
* @internal
|
|
*/
|
|
public static function reset_for_tests(): void {
|
|
self::$memo = null;
|
|
self::$notice_bound = false;
|
|
}
|
|
|
|
/**
|
|
* Whether any conflict is currently active.
|
|
*/
|
|
public static function has_conflict(): bool {
|
|
return ! empty( self::detect_conflicts() );
|
|
}
|
|
|
|
/**
|
|
* Render the admin notice (bound only when conflicts exist).
|
|
*
|
|
* @internal Called by WordPress; do not call directly.
|
|
*/
|
|
public static function render_notice(): void {
|
|
if ( ! function_exists( 'is_admin' ) || ! is_admin() ) {
|
|
return;
|
|
}
|
|
$conflicts = self::detect_conflicts();
|
|
if ( empty( $conflicts ) ) {
|
|
return;
|
|
}
|
|
|
|
$names = array_map(
|
|
static function ( string $slug ): string {
|
|
$probe = self::CONFLICTS[ $slug ] ?? null;
|
|
if ( null === $probe ) {
|
|
return $slug;
|
|
}
|
|
$name = (string) ( $probe['name'] ?? $slug );
|
|
return esc_html( $name );
|
|
},
|
|
$conflicts
|
|
);
|
|
|
|
$message = sprintf(
|
|
/* 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' ),
|
|
implode( ', ', $names )
|
|
);
|
|
|
|
printf(
|
|
'<div class="notice notice-warning"><p><strong>%s</strong></p><p>%s</p></div>',
|
|
esc_html__( 'WP Data Optimizer — HivePress integration paused', 'tmdo-hivepress' ),
|
|
esc_html( $message )
|
|
);
|
|
}
|
|
|
|
// ── Internal helpers ────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Probe a single legacy plugin for presence.
|
|
*
|
|
* @param array{classes:array<int,string>, consts:array<int,string>, name:string} $probe Probe spec.
|
|
*/
|
|
private static function probe_one( array $probe ): bool {
|
|
foreach ( (array) $probe['classes'] as $cls ) {
|
|
if ( '' !== $cls && class_exists( $cls ) ) {
|
|
return true;
|
|
}
|
|
}
|
|
foreach ( (array) $probe['consts'] as $const ) {
|
|
if ( '' !== $const && defined( $const ) ) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Log conflicts to WPDO error log so audit script picks them up.
|
|
*
|
|
* @param array<int,string> $conflicts Conflict slugs.
|
|
*/
|
|
private static function log_conflicts( array $conflicts ): void {
|
|
if ( ! class_exists( 'TMDO_Logger' ) ) {
|
|
return;
|
|
}
|
|
|
|
$message = sprintf(
|
|
'Conflict detected: %s. wp-data-optimizer fully supersedes these. Run: wp wpdo hivepress migrate-from-hpct',
|
|
implode( ', ', $conflicts )
|
|
);
|
|
|
|
// Prefer warn() but fall back to log() if the API differs.
|
|
if ( method_exists( 'TMDO_Logger', 'warn' ) ) {
|
|
TMDO_Logger::warn( 'hivepress-integration', $message );
|
|
} elseif ( method_exists( 'TMDO_Logger', 'log' ) ) {
|
|
TMDO_Logger::log( 'warning', 'hivepress-integration', $message );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Bind the admin notice exactly once per request.
|
|
*/
|
|
private static function ensure_notice_bound(): void {
|
|
if ( self::$notice_bound ) {
|
|
return;
|
|
}
|
|
if ( function_exists( 'add_action' ) ) {
|
|
add_action( 'admin_notices', array( __CLASS__, 'render_notice' ) );
|
|
}
|
|
self::$notice_bound = true;
|
|
}
|
|
|
|
/**
|
|
* Install the short-circuit so HivePress adapter binding is skipped.
|
|
*
|
|
* Bootstrap reads `apply_filters('wpdo_hivepress_should_bind', true, $context)`
|
|
* before binding adapters. We force `false` whenever conflicts exist
|
|
* so dual-write cannot occur. The conflict context is passed to filter
|
|
* so other code can react.
|
|
*
|
|
* @param array<int,string> $conflicts Conflict slugs.
|
|
*/
|
|
private static function install_should_bind_short_circuit( array $conflicts ): void {
|
|
if ( ! function_exists( 'add_filter' ) ) {
|
|
return;
|
|
}
|
|
|
|
add_filter(
|
|
'wpdo_hivepress_should_bind',
|
|
static function ( $should_bind, $context = array() ) use ( $conflicts ) {
|
|
unset( $context );
|
|
return false;
|
|
// Note: $conflicts captured purely for greppability via closure inspection.
|
|
// PHPCS may flag the `unset` if context is unused, but keeping the symmetry
|
|
// with the documented filter signature.
|
|
},
|
|
1,
|
|
2
|
|
);
|
|
|
|
unset( $conflicts ); // Satisfy use-after for static analysis.
|
|
}
|
|
}
|
|
|
|
} // end if ( ! class_exists )
|