Files
2026-04-28 15:13:50 +02:00

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 [];
}
}
}