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,260 @@
|
||||
<?php
|
||||
/**
|
||||
* HivePress family addon detector.
|
||||
*
|
||||
* Probes for the 13 known HivePress family plugins by checking the most
|
||||
* stable `class_exists()` / `defined()` signal each addon publishes. Returns
|
||||
* a `slug => version` map of detected addons.
|
||||
*
|
||||
* Detection happens during `TMDO_HivePress_Bootstrap::boot()` on
|
||||
* `plugins_loaded:5`, AFTER WPDO core (priority 4) but BEFORE HivePress own
|
||||
* boot (priority 8). The bootstrap then instantiates an adapter per detected
|
||||
* addon and binds its lifecycle hooks.
|
||||
*
|
||||
* Result is cached in a 5-min transient (`wpdo_hivepress_detector_cache`) so
|
||||
* repeated calls within a single page-load (e.g. admin tab + REST endpoint)
|
||||
* don't re-probe. Cache busts on plugin activation/deactivation via the
|
||||
* static `bust_cache()` method.
|
||||
*
|
||||
* Cost: when HivePress core is NOT installed, detector short-circuits after
|
||||
* a single `class_exists('HivePress\\Core')` check — zero-cost fallback path
|
||||
* for sites that don't use HivePress.
|
||||
*
|
||||
* @package WP_Data_Optimizer
|
||||
* @since 3.0.0
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'TMDO_HivePress_Detector' ) ) {
|
||||
|
||||
/**
|
||||
* Static detector for HivePress family addons.
|
||||
*
|
||||
* Stateless apart from the request-level memo cache; reset between tests
|
||||
* via `reset_for_tests()`.
|
||||
*/
|
||||
final class TMDO_HivePress_Detector {
|
||||
|
||||
/** Transient key for the detection cache. */
|
||||
private const CACHE_KEY = 'wpdo_hivepress_detector_cache';
|
||||
|
||||
/** Transient TTL (5 minutes — short enough to pick up plugin activation, long enough to avoid per-request cost). */
|
||||
private const CACHE_TTL = 300;
|
||||
|
||||
/**
|
||||
* Request-level memo cache. Survives only the current request.
|
||||
*
|
||||
* @var array<string,string>|null
|
||||
*/
|
||||
private static ?array $memo = null;
|
||||
|
||||
/**
|
||||
* Detection probe table: slug => [class, version_const, name].
|
||||
*
|
||||
* Keep this list in sync with the HivePress family on wordpress.org.
|
||||
* Detection prefers `class_exists()` over `defined()` because version
|
||||
* constants sometimes leak to other globals; the class probe is more
|
||||
* specific. Both signals are checked — either one suffices.
|
||||
*
|
||||
* For addons not yet installed locally (bookings, marketplace,
|
||||
* statistics) the probe still runs — it just returns no match. The
|
||||
* adapter file exists for future installs.
|
||||
*
|
||||
* @var array<string, array{class:string, const:string, name:string}>
|
||||
*/
|
||||
private const PROBES = array(
|
||||
'hivepress' => array(
|
||||
'class' => 'HivePress\\Core',
|
||||
'const' => 'HIVEPRESS_VERSION',
|
||||
'name' => 'HivePress',
|
||||
),
|
||||
'hivepress-blocks' => array(
|
||||
'class' => 'HivePress\\Blocks\\Plugin',
|
||||
'const' => 'HIVEPRESS_BLOCKS_VERSION',
|
||||
'name' => 'HivePress Blocks',
|
||||
),
|
||||
'hivepress-bookings' => array(
|
||||
'class' => 'HivePress\\Bookings\\Plugin',
|
||||
'const' => 'HIVEPRESS_BOOKINGS_VERSION',
|
||||
'name' => 'HivePress Bookings',
|
||||
),
|
||||
'hivepress-favorites' => array(
|
||||
'class' => 'HivePress\\Favorites\\Plugin',
|
||||
'const' => 'HIVEPRESS_FAVORITES_VERSION',
|
||||
'name' => 'HivePress Favorites',
|
||||
),
|
||||
'hivepress-marketplace' => array(
|
||||
'class' => 'HivePress\\Marketplace\\Plugin',
|
||||
'const' => 'HIVEPRESS_MARKETPLACE_VERSION',
|
||||
'name' => 'HivePress Marketplace',
|
||||
),
|
||||
'hivepress-memberships' => array(
|
||||
'class' => 'HivePress\\Memberships\\Plugin',
|
||||
'const' => 'HIVEPRESS_MEMBERSHIPS_VERSION',
|
||||
'name' => 'HivePress Memberships',
|
||||
),
|
||||
'hivepress-messages' => array(
|
||||
'class' => 'HivePress\\Messages\\Plugin',
|
||||
'const' => 'HIVEPRESS_MESSAGES_VERSION',
|
||||
'name' => 'HivePress Messages',
|
||||
),
|
||||
'hivepress-requests' => array(
|
||||
'class' => 'HivePress\\Requests\\Plugin',
|
||||
'const' => 'HIVEPRESS_REQUESTS_VERSION',
|
||||
'name' => 'HivePress Requests',
|
||||
),
|
||||
'hivepress-reviews' => array(
|
||||
'class' => 'HivePress\\Reviews\\Plugin',
|
||||
'const' => 'HIVEPRESS_REVIEWS_VERSION',
|
||||
'name' => 'HivePress Reviews',
|
||||
),
|
||||
'hivepress-seo' => array(
|
||||
'class' => 'HivePress\\Seo\\Plugin',
|
||||
'const' => 'HIVEPRESS_SEO_VERSION',
|
||||
'name' => 'HivePress SEO',
|
||||
),
|
||||
'hivepress-social-links' => array(
|
||||
'class' => 'HivePress\\SocialLinks\\Plugin',
|
||||
'const' => 'HIVEPRESS_SOCIAL_LINKS_VERSION',
|
||||
'name' => 'HivePress Social Links',
|
||||
),
|
||||
'hivepress-statistics' => array(
|
||||
'class' => 'HivePress\\Statistics\\Plugin',
|
||||
'const' => 'HIVEPRESS_STATISTICS_VERSION',
|
||||
'name' => 'HivePress Statistics',
|
||||
),
|
||||
'hivepress-tags' => array(
|
||||
'class' => 'HivePress\\Tags\\Plugin',
|
||||
'const' => 'HIVEPRESS_TAGS_VERSION',
|
||||
'name' => 'HivePress Tags',
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* Detect installed HivePress family addons.
|
||||
*
|
||||
* Returns map of slug → version-string. Empty array when HivePress
|
||||
* core is not installed (zero-overhead short circuit for non-HP sites).
|
||||
*
|
||||
* @return array<string,string>
|
||||
*/
|
||||
public static function detect(): array {
|
||||
if ( null !== self::$memo ) {
|
||||
return self::$memo;
|
||||
}
|
||||
|
||||
$cached = function_exists( 'get_transient' ) ? get_transient( self::CACHE_KEY ) : false;
|
||||
if ( is_array( $cached ) ) {
|
||||
self::$memo = $cached;
|
||||
return $cached;
|
||||
}
|
||||
|
||||
// Short-circuit: if HivePress core is not present, no addon can be loaded.
|
||||
if ( ! self::probe_one( self::PROBES['hivepress'] ) ) {
|
||||
self::$memo = array();
|
||||
if ( function_exists( 'set_transient' ) ) {
|
||||
set_transient( self::CACHE_KEY, self::$memo, self::CACHE_TTL );
|
||||
}
|
||||
return self::$memo;
|
||||
}
|
||||
|
||||
$found = array();
|
||||
foreach ( self::PROBES as $slug => $probe ) {
|
||||
if ( self::probe_one( $probe ) ) {
|
||||
$found[ $slug ] = self::version_for( $probe );
|
||||
}
|
||||
}
|
||||
|
||||
self::$memo = $found;
|
||||
if ( function_exists( 'set_transient' ) ) {
|
||||
set_transient( self::CACHE_KEY, $found, self::CACHE_TTL );
|
||||
}
|
||||
|
||||
return $found;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force re-detection on next call.
|
||||
*
|
||||
* Call from plugin activation / deactivation hooks if the listening
|
||||
* code wants up-to-date detection state immediately.
|
||||
*/
|
||||
public static function bust_cache(): void {
|
||||
self::$memo = null;
|
||||
if ( function_exists( 'delete_transient' ) ) {
|
||||
delete_transient( self::CACHE_KEY );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset internal state for unit tests.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function reset_for_tests(): void {
|
||||
self::$memo = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Catalog of supported addons for UI display (slug => human-readable name).
|
||||
*
|
||||
* @return array<string,string>
|
||||
*/
|
||||
public static function catalog(): array {
|
||||
$out = array();
|
||||
foreach ( self::PROBES as $slug => $probe ) {
|
||||
$out[ $slug ] = $probe['name'];
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a given addon slug is in the supported catalog.
|
||||
*
|
||||
* @param string $slug Addon slug.
|
||||
*/
|
||||
public static function is_known( string $slug ): bool {
|
||||
return isset( self::PROBES[ $slug ] );
|
||||
}
|
||||
|
||||
// ── Internal probes ─────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Probe a single addon. Either the class OR the const must exist.
|
||||
*
|
||||
* @param array{class:string, const:string, name:string} $probe Probe definition.
|
||||
*/
|
||||
private static function probe_one( array $probe ): bool {
|
||||
$class = (string) ( $probe['class'] ?? '' );
|
||||
$const = (string) ( $probe['const'] ?? '' );
|
||||
|
||||
if ( '' !== $class && class_exists( $class ) ) {
|
||||
return true;
|
||||
}
|
||||
if ( '' !== $const && defined( $const ) ) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Best-effort version extraction. Empty string when no version available.
|
||||
*
|
||||
* @param array{class:string, const:string, name:string} $probe Probe definition.
|
||||
*/
|
||||
private static function version_for( array $probe ): string {
|
||||
$const = (string) ( $probe['const'] ?? '' );
|
||||
if ( '' !== $const && defined( $const ) ) {
|
||||
$value = constant( $const );
|
||||
if ( is_string( $value ) || is_numeric( $value ) ) {
|
||||
return (string) $value;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
} // end if ( ! class_exists )
|
||||
Reference in New Issue
Block a user