241 lines
7.4 KiB
PHP
241 lines
7.4 KiB
PHP
<?php
|
|
declare( strict_types=1 );
|
|
|
|
namespace WooCommerce\Facebook\Integrations;
|
|
|
|
defined( 'ABSPATH' ) || exit;
|
|
|
|
/**
|
|
* Centralized registry for localization integrations and active plugin detection.
|
|
*
|
|
* Provides discovery mechanism for available localization integrations, manages
|
|
* instantiation of integration classes, and tracks active plugin availability
|
|
* to send plugin telemetry data to Facebook/Meta.
|
|
*
|
|
* @since 3.5.9
|
|
*/
|
|
class IntegrationRegistry {
|
|
|
|
/**
|
|
* @var array<string, string> Map of integration keys to class names
|
|
*/
|
|
private static $localization_integrations = [
|
|
'polylang' => Polylang::class,
|
|
'wpml' => WPML::class,
|
|
];
|
|
|
|
/**
|
|
* @var array<string, Abstract_Localization_Integration> Cached integration instances
|
|
*/
|
|
private static $integration_instances = [];
|
|
|
|
/**
|
|
* Get all localization integration keys
|
|
*
|
|
* @return array<string> Array of integration keys
|
|
*/
|
|
public static function get_localization_integration_keys(): array {
|
|
return array_keys( self::$localization_integrations );
|
|
}
|
|
|
|
/**
|
|
* Get a specific localization integration instance
|
|
*
|
|
* @param string $integration_key Integration key (e.g., 'polylang')
|
|
* @return Abstract_Localization_Integration|null Integration instance or null if not found
|
|
*/
|
|
public static function get_localization_integration( string $integration_key ): ?Abstract_Localization_Integration {
|
|
if ( ! isset( self::$localization_integrations[ $integration_key ] ) ) {
|
|
return null;
|
|
}
|
|
|
|
// Return cached instance if available
|
|
if ( isset( self::$integration_instances[ $integration_key ] ) ) {
|
|
return self::$integration_instances[ $integration_key ];
|
|
}
|
|
|
|
$class_name = self::$localization_integrations[ $integration_key ];
|
|
|
|
// Verify class exists and extends the abstract base
|
|
if ( ! class_exists( $class_name ) || ! is_subclass_of( $class_name, Abstract_Localization_Integration::class ) ) {
|
|
return null;
|
|
}
|
|
|
|
// Create and cache the instance
|
|
$instance = new $class_name();
|
|
self::$integration_instances[ $integration_key ] = $instance;
|
|
|
|
return $instance;
|
|
}
|
|
|
|
/**
|
|
* Get all localization integration instances
|
|
*
|
|
* @return array<string, Abstract_Localization_Integration> Array of integration instances keyed by integration key
|
|
*/
|
|
public static function get_all_localization_integrations(): array {
|
|
$integrations = [];
|
|
|
|
foreach ( self::get_localization_integration_keys() as $key ) {
|
|
$integration = self::get_localization_integration( $key );
|
|
if ( $integration ) {
|
|
$integrations[ $key ] = $integration;
|
|
}
|
|
}
|
|
|
|
return $integrations;
|
|
}
|
|
|
|
/**
|
|
* Get availability data for all localization integrations
|
|
*
|
|
* @return array<string, array> Array of integration availability data keyed by integration key
|
|
*/
|
|
public static function get_all_localization_availability_data(): array {
|
|
$availability_data = [];
|
|
|
|
foreach ( self::get_all_localization_integrations() as $key => $integration ) {
|
|
$availability_data[ $key ] = self::get_integration_availability_data( $integration );
|
|
}
|
|
|
|
return $availability_data;
|
|
}
|
|
|
|
/**
|
|
* Get availability data for a specific integration
|
|
*
|
|
* @param Abstract_Localization_Integration $integration Integration instance
|
|
* @return array Integration availability data
|
|
*/
|
|
private static function get_integration_availability_data( Abstract_Localization_Integration $integration ): array {
|
|
// Use the standardized method from the abstract base class
|
|
return $integration->get_availability_data();
|
|
}
|
|
|
|
/**
|
|
* Register a new localization integration
|
|
*
|
|
* @param string $key Integration key
|
|
* @param string $class_name Integration class name
|
|
* @return bool True if registered successfully, false otherwise
|
|
*/
|
|
public static function register_localization_integration( string $key, string $class_name ): bool {
|
|
// Verify class exists and extends the abstract base
|
|
if ( ! class_exists( $class_name ) || ! is_subclass_of( $class_name, Abstract_Localization_Integration::class ) ) {
|
|
return false;
|
|
}
|
|
|
|
self::$localization_integrations[ $key ] = $class_name;
|
|
|
|
// Clear cached instance if it exists
|
|
unset( self::$integration_instances[ $key ] );
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Check if any localization plugin is active and properly configured
|
|
*
|
|
* @return bool True if at least one localization plugin is available
|
|
*/
|
|
public static function has_active_localization_plugin(): bool {
|
|
$integrations = self::get_all_localization_integrations();
|
|
|
|
foreach ( $integrations as $integration ) {
|
|
if ( $integration->is_available() ) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get the first active localization integration
|
|
*
|
|
* Returns the first integration that has an active plugin (regardless of configuration).
|
|
* This is useful for getting language information even if the integration isn't fully configured.
|
|
*
|
|
* **Multiple Localization Plugins:**
|
|
* Integrations are checked in the order they appear in $localization_integrations array:
|
|
* 1. Polylang (checked first)
|
|
* 2. WPML (checked second)
|
|
*
|
|
* If both plugins are somehow active, only Polylang's integration will be returned and used.
|
|
*
|
|
* **In Practice:**
|
|
* Based on telemetry data from Meta for WooCommerce users, ZERO sites have both
|
|
* WPML and Polylang activated simultaneously. This is because:
|
|
* - WPML throws a fatal error during initialization if Polylang is already active
|
|
* - If Polylang is activated after WPML, WPML's functionality is disabled
|
|
*
|
|
* **Plugin Conflicts:**
|
|
* The order of precedence (Polylang first) is intentional but rarely matters in practice
|
|
* due to the plugin conflicts mentioned above. If conflicts are resolved in future versions
|
|
* of these plugins, this method will consistently return Polylang when both are active.
|
|
*
|
|
* @return Abstract_Localization_Integration|null The first active integration or null if none active
|
|
* @since 3.6.0
|
|
*/
|
|
public static function get_active_localization_integration(): ?Abstract_Localization_Integration {
|
|
$integrations = self::get_all_localization_integrations();
|
|
|
|
foreach ( $integrations as $integration ) {
|
|
if ( $integration->is_plugin_active() ) {
|
|
return $integration;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Clear all cached integration instances
|
|
*
|
|
* Useful for testing or when integration states might have changed
|
|
*/
|
|
public static function clear_cache(): void {
|
|
self::$integration_instances = [];
|
|
}
|
|
|
|
/**
|
|
* Get list of active plugin names
|
|
*
|
|
* @return array Array of active plugin names
|
|
*/
|
|
public static function get_all_active_plugin_data(): array {
|
|
try {
|
|
if ( ! function_exists( 'get_plugins' ) ) {
|
|
include_once ABSPATH . 'wp-admin/includes/plugin.php';
|
|
}
|
|
|
|
$active_plugins_list = get_option( 'active_plugins', [] );
|
|
$all_plugins = get_plugins();
|
|
$active_plugins_data = [];
|
|
|
|
foreach ( $active_plugins_list as $plugin_file ) {
|
|
if ( isset( $all_plugins[ $plugin_file ] ) ) {
|
|
$plugin_data = $all_plugins[ $plugin_file ];
|
|
$active_plugins_data[] = $plugin_data['Name'];
|
|
}
|
|
}
|
|
|
|
return $active_plugins_data;
|
|
} catch ( \Exception $e ) {
|
|
// Log error but return empty array to prevent breaking the update process
|
|
if ( class_exists( 'WooCommerce\Facebook\Framework\Logger' ) ) {
|
|
\WooCommerce\Facebook\Framework\Logger::log(
|
|
'Error getting active plugin data: ' . $e->getMessage(),
|
|
[],
|
|
array(
|
|
'should_send_log_to_meta' => false,
|
|
'should_save_log_in_woocommerce' => true,
|
|
'woocommerce_log_level' => \WC_Log_Levels::ERROR,
|
|
)
|
|
);
|
|
}
|
|
return [];
|
|
}
|
|
}
|
|
}
|