=' ); if ( ! $minimum_woocommerce_active ) { return false; } // Ensure the WC Tracks classes exist. if ( ! class_exists( 'WC_Tracks' ) ) { if ( ! defined( 'WC_ABSPATH' ) || ! file_exists( WC_ABSPATH . 'includes/tracks/class-wc-tracks.php' ) ) { return false; } include_once WC_ABSPATH . 'includes/tracks/class-wc-tracks.php'; include_once WC_ABSPATH . 'includes/tracks/class-wc-tracks-event.php'; include_once WC_ABSPATH . 'includes/tracks/class-wc-tracks-client.php'; } add_action( 'admin_init', array( __CLASS__, 'maybe_update_proxy_speed_module' ) ); // Tracking only Site pages. if ( is_admin() || wp_doing_ajax() || wp_is_xml_request() || is_login() || is_feed() || ( defined( 'WP_CLI' ) && WP_CLI ) ) { return false; } // Make sure the site is connected to WordPress.com. if ( ! ( new Jetpack_Connection() )->is_connected() ) { return false; } return true; } /** * Make _wca available to queue events */ public static function wp_head_top() { if ( is_cart() || is_checkout() || is_checkout_pay_page() || is_order_received_page() || is_add_payment_method_page() ) { echo '' . "\r\n"; } echo '' . "\r\n"; } /** * Place script to call s.js, Store Analytics. */ public static function enqueue_tracking_script() { $url = sprintf( 'https://stats.wp.com/s-%d.js', gmdate( 'YW' ) ); wp_enqueue_script( 'woocommerce-analytics', $url, array(), null, // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion -- The version is set in the URL. array( 'in_footer' => false, 'strategy' => 'defer', ) ); } /** * Enqueue client-side analytics script. */ public static function enqueue_client_script() { Assets::register_script( 'woocommerce-analytics-client', '../build/woocommerce-analytics-client.js', __FILE__, array( 'in_footer' => true, 'strategy' => 'defer', 'enqueue' => true, ) ); } /** * Register REST API routes. * * A site that has never used proxy tracking does not get the endpoint. It stays * registered after being disabled, so cached pages receive a visible 403. */ public static function register_rest_routes() { if ( 'yes' !== get_option( self::PROXY_TRACKING_EVER_ENABLED_OPTION ) ) { return; } $controller = new WC_Analytics_Tracking_Proxy(); $controller->register_routes(); } /** * Sync proxy tracking state for the REST route and MU-plugin speed module. * * @since 0.18.0 * * @return void */ public static function sync_proxy_tracking_state() { if ( \Automattic\Woocommerce_Analytics\Features::is_proxy_tracking_enabled() && 'yes' !== get_option( self::PROXY_TRACKING_EVER_ENABLED_OPTION ) ) { update_option( self::PROXY_TRACKING_EVER_ENABLED_OPTION, 'yes' ); } // Track module eligibility so a removed module cannot be reauthorized. $authorized = self::should_install_proxy_speed_module() ? 'yes' : 'no'; $current = get_option( self::PROXY_SPEED_MODULE_AUTHORIZED_OPTION ); if ( $current === $authorized ) { return; } // An absent option already means unauthorized, so most sites installing this // package never need a row saying so. if ( false === $current && 'no' === $authorized ) { return; } update_option( self::PROXY_SPEED_MODULE_AUTHORIZED_OPTION, $authorized ); } /** * Forget that proxy tracking was enabled, so the REST route is unregistered. * * This is separate from removing the speed module because cached pages may remain. * * @since 0.18.0 * * @return void */ public static function reset_proxy_tracking_state() { delete_option( self::PROXY_TRACKING_EVER_ENABLED_OPTION ); delete_option( self::PROXY_SPEED_MODULE_AUTHORIZED_OPTION ); } /** * Update the proxy speed module. * * The module must refuse itself because this periodic check can be delayed. */ public static function maybe_update_proxy_speed_module() { // Skip if we've already checked recently. if ( get_transient( self::PROXY_SPEED_MODULE_VERSION_CHECK_TRANSIENT ) ) { return; } $version = get_option( self::PROXY_SPEED_MODULE_VERSION_OPTION, false ); if ( self::should_install_proxy_speed_module() ) { if ( $version !== self::PACKAGE_VERSION ) { self::maybe_add_proxy_speed_module(); } } elseif ( $version !== false ) { self::maybe_remove_proxy_speed_module(); } // Set the transient after the update attempt to prevent checking on every admin_init. // If the update failed, it will be retried after the transient expires (1 day). set_transient( self::PROXY_SPEED_MODULE_VERSION_CHECK_TRANSIENT, 1, DAY_IN_SECONDS ); } /** * Whether the proxy speed module belongs on this site. * * It requires its own opt-in and proxy tracking, so it cannot serve when * tracking is disabled. * * @since 0.18.0 * * @return bool */ private static function should_install_proxy_speed_module() { return \Automattic\Woocommerce_Analytics\Features::is_proxy_speed_module_enabled() && \Automattic\Woocommerce_Analytics\Features::is_proxy_tracking_enabled(); } /** * Maybe add proxy speed module. */ public static function maybe_add_proxy_speed_module() { if ( ! self::should_install_proxy_speed_module() ) { return; } // Write before the module file exists because it fails closed on this option. self::sync_proxy_tracking_state(); if ( ! self::init_filesystem() ) { if ( function_exists( 'wc_get_logger' ) ) { wc_get_logger()->error( 'WooCommerce Analytics proxy speed module not installed: filesystem unavailable.', array( 'source' => 'woocommerce-analytics' ) ); } return; } global $wp_filesystem; // Create the mu-plugin directory if it doesn't exist. if ( ! is_dir( WPMU_PLUGIN_DIR ) ) { wp_mkdir_p( WPMU_PLUGIN_DIR ); } // If the mu-plugin directory doesn't exist, we can't copy the files. if ( ! is_dir( WPMU_PLUGIN_DIR ) ) { if ( function_exists( 'wc_get_logger' ) ) { wc_get_logger()->error( 'WooCommerce Analytics proxy speed module not installed: mu-plugins directory could not be created.', array( 'source' => 'woocommerce-analytics' ) ); } return; } // Check if the mu-plugin directory is writable. if ( ! $wp_filesystem->is_writable( WPMU_PLUGIN_DIR ) ) { if ( function_exists( 'wc_get_logger' ) ) { wc_get_logger()->debug( 'WooCommerce Analytics proxy speed module not installed: mu-plugins directory is not writable.', array( 'source' => 'woocommerce-analytics' ) ); } return; } if ( get_option( self::PROXY_SPEED_MODULE_VERSION_OPTION ) === self::PACKAGE_VERSION ) { // No need to copy the files again. return; } $mu_plugin_src_file = __DIR__ . '/mu-plugin/woocommerce-analytics-proxy-speed-module-template.php'; $mu_plugin_dest_file = trailingslashit( WPMU_PLUGIN_DIR ) . 'woocommerce-analytics-proxy-speed-module.php'; // Verify source file exists before attempting to copy. if ( ! file_exists( $mu_plugin_src_file ) ) { if ( function_exists( 'wc_get_logger' ) ) { wc_get_logger()->error( 'WooCommerce Analytics proxy speed module source file not found.', array( 'source' => 'woocommerce-analytics' ) ); } return; } $content = $wp_filesystem->get_contents( $mu_plugin_src_file ); if ( false === $content ) { if ( function_exists( 'wc_get_logger' ) ) { wc_get_logger()->error( 'Failed to read the WooCommerce Analytics proxy speed module source file.', array( 'source' => 'woocommerce-analytics' ) ); } return; } // Get the autoloader path from the current plugin location. $autoloader_path = self::locate_autoloader_file(); if ( null === $autoloader_path ) { if ( function_exists( 'wc_get_logger' ) ) { wc_get_logger()->error( 'WooCommerce Analytics proxy speed module not installed: could not locate autoloader.', array( 'source' => 'woocommerce-analytics' ) ); } return; } // Replace placeholders with actual values. $content = str_replace( array( '{{AUTOLOADER_PATH}}', '{{VERSION}}' ), array( $autoloader_path, self::PACKAGE_VERSION ), $content ); if ( ! $wp_filesystem->put_contents( $mu_plugin_dest_file, $content ) ) { if ( function_exists( 'wc_get_logger' ) ) { wc_get_logger()->error( 'Failed to write the WooCommerce Analytics proxy speed module file.', array( 'source' => 'woocommerce-analytics' ) ); } return; } update_option( self::PROXY_SPEED_MODULE_VERSION_OPTION, self::PACKAGE_VERSION ); } /** * Remove the proxy speed module when the plugin is deactivated. * * Clear its authorization because an undeletable MU-plugin can still load. */ public static function maybe_remove_proxy_speed_module() { // Revoked before anything can fail: WP_Filesystem() returns false outright on // hosts that ask for credentials, and that is exactly the case where the file // survives and keeps loading. delete_option( self::PROXY_SPEED_MODULE_AUTHORIZED_OPTION ); if ( ! self::init_filesystem() ) { return; } global $wp_filesystem; /** * Clean up MU plugin. */ $file_path = trailingslashit( WPMU_PLUGIN_DIR ) . 'woocommerce-analytics-proxy-speed-module.php'; if ( $wp_filesystem->exists( $file_path ) && $wp_filesystem->is_writable( $file_path ) ) { $deleted = $wp_filesystem->delete( $file_path ); if ( ! $deleted && function_exists( 'wc_get_logger' ) ) { wc_get_logger()->error( 'Failed to delete WooCommerce Analytics proxy speed module file. The MU-plugin may continue running.', array( 'source' => 'woocommerce-analytics' ) ); } } delete_option( self::PROXY_SPEED_MODULE_VERSION_OPTION ); delete_transient( self::PROXY_SPEED_MODULE_VERSION_CHECK_TRANSIENT ); } /** * Finds the path to the autoloader file. * * Uses multiple strategies to locate the autoloader, since this package * can be included in different plugins (Jetpack, WooCommerce Analytics, etc.): * 1. Jetpack autoloader global (if available) * 2. Composer's InstalledVersions API * 3. Directory-based guessing as a fallback * * @return string|null The path to the autoloader file, or null if not found. */ private static function locate_autoloader_file() { global $jetpack_autoloader_loader; $autoload_file = null; // Try the Jetpack autoloader. if ( isset( $jetpack_autoloader_loader ) ) { $class_file = $jetpack_autoloader_loader->find_class_file( self::class ); if ( $class_file ) { // Walk up 5 levels: src/ → woocommerce-analytics/ → automattic/ → jetpack_vendor/ → plugin root. $autoload_file = dirname( $class_file, 5 ) . '/vendor/autoload.php'; } } // Try Composer's InstalledVersions API. if ( null === $autoload_file && is_callable( array( InstalledVersions::class, 'getInstallPath' ) ) && InstalledVersions::isInstalled( 'automattic/woocommerce-analytics' ) ) { $package_file = InstalledVersions::getInstallPath( 'automattic/woocommerce-analytics' ); $expected_suffix = '/automattic/woocommerce-analytics'; if ( substr( $package_file, -strlen( $expected_suffix ) ) === $expected_suffix ) { // Walk up 3 levels: woocommerce-analytics/ → automattic/ → jetpack_vendor/ → plugin root. $autoload_file = dirname( $package_file, 3 ) . '/vendor/autoload.php'; } } // Guess based on directory structure. // First try standard vendor layout (vendor/automattic/woocommerce-analytics/src/), // then try standalone package with its own vendor dir. if ( null === $autoload_file ) { // Walk up 4 levels from src/: woocommerce-analytics/ → automattic/ → vendor/ → project root. $autoload_file = dirname( __DIR__, 4 ) . '/vendor/autoload.php'; if ( ! file_exists( $autoload_file ) ) { $autoload_file = dirname( __DIR__ ) . '/vendor/autoload.php'; } } if ( ! file_exists( $autoload_file ) ) { return null; } return $autoload_file; } /** * Initialize the WP filesystem. * * @return bool True if filesystem is initialized, false otherwise. */ private static function init_filesystem() { if ( ! function_exists( 'WP_Filesystem' ) ) { require_once ABSPATH . 'wp-admin/includes/file.php'; } // Initialize the WP filesystem. ob_start(); $initialized = WP_Filesystem(); ob_end_clean(); return $initialized; } }