chore: initial snapshot of 2meet-data-optimizer-hivepress-addon v0.1.0
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
This commit is contained in:
@@ -0,0 +1,266 @@
|
||||
<?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 )
|
||||
Reference in New Issue
Block a user