$value ) { if ( ! is_array( $value ) ) { continue; } if ( empty( $value ) ) { // Empty array becomes empty string. $properties[ $key ] = ''; continue; } // Check if array is indexed (not associative) and contains only scalar values. $is_indexed_array = array_keys( $value ) === range( 0, count( $value ) - 1 ); $has_scalar_only = ! array_filter( $value, function ( $item ) { return is_array( $item ) || is_object( $item ); } ); if ( $is_indexed_array && $has_scalar_only ) { // Indexed arrays with scalar values: join as comma string. $properties[ $key ] = implode( ',', array_map( 'strval', $value ) ); continue; } // Associative arrays or nested arrays become JSON strings. $encoded = wp_json_encode( $value, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ); $properties[ $key ] = ( false === $encoded ) ? '' : $encoded; } return $properties; } /** * Check if a SOCKS proxy is configured. * * The Requests library doesn't support SOCKS proxies, so we need to fall back * to wp_remote_get() which respects WordPress proxy settings. * * @return bool True if a SOCKS proxy is configured. */ private static function is_socks_proxy_configured() { if ( ! defined( 'WP_PROXY_HOST' ) || ! is_string( WP_PROXY_HOST ) || '' === WP_PROXY_HOST ) { return false; } return self::is_socks_proxy_host( (string) WP_PROXY_HOST ); } /** * Check if a proxy host string indicates a SOCKS proxy. * * @param string $proxy_host The proxy host value. * @return bool True if the host indicates a SOCKS proxy. */ public static function is_socks_proxy_host( $proxy_host ) { $proxy_host = strtolower( $proxy_host ); return 0 === strpos( $proxy_host, 'socks5://' ) || 0 === strpos( $proxy_host, 'socks4://' ) || 0 === strpos( $proxy_host, 'socks://' ); } /** * Send pixel requests using batched non-blocking HTTP calls. * * Uses Requests library's request_multiple() for parallel execution via curl_multi. * * @param array $pixels Array of pixel URLs to send. * @return bool True on success. */ public static function send_pixels_batched( $pixels ) { if ( empty( $pixels ) ) { return true; } // Check if batching is supported. // Note: WpOrg\Requests\Requests doesn't support SOCKS proxies, so we fall back // to individual wp_remote_get() requests which respect WP_PROXY_* settings. $can_batch = ( class_exists( 'WpOrg\Requests\Requests' ) && method_exists( 'WpOrg\Requests\Requests', 'request_multiple' ) ) || ( class_exists( 'Requests' ) && method_exists( 'Requests', 'request_multiple' ) ); $can_batch = $can_batch && ! self::is_socks_proxy_configured(); if ( ! $can_batch ) { // Fallback to individual requests. foreach ( $pixels as $pixel ) { self::send_pixel( $pixel ); } return true; } // Add timestamp and nocache to all pixels. $pixels_to_send = array(); foreach ( $pixels as $pixel ) { $pixels_to_send[] = self::add_request_timestamp_and_nocache( $pixel ); } // Build request array for batch sending. $requests = array(); $options = array( 'blocking' => false, // Non-blocking mode. 'timeout' => 1, ); foreach ( $pixels_to_send as $pixel ) { $requests[] = array( 'url' => $pixel, 'headers' => array(), 'data' => array(), 'type' => 'GET', ); } try { if ( class_exists( 'WpOrg\Requests\Requests' ) ) { \WpOrg\Requests\Requests::request_multiple( $requests, $options ); } elseif ( class_exists( 'Requests' ) ) { \Requests::request_multiple( $requests, $options ); // phpcs:ignore PHPCompatibility.FunctionUse.RemovedFunctions.requestsDeprecated } } catch ( \Exception $e ) { // Log error but don't break the site - tracking pixels should fail gracefully. $error_message = 'WooCommerce Analytics: Batch pixel request failed - ' . $e->getMessage(); if ( function_exists( 'wc_get_logger' ) ) { wc_get_logger()->error( $error_message, array( 'source' => 'woocommerce-analytics' ) ); } else { // Fallback for MU-plugin stage when WooCommerce logger is not available. // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log error_log( $error_message ); } return false; } return true; } /** * Send a single pixel request. * * @param string $pixel Pixel URL. * @return bool True on success. */ public static function send_pixel( $pixel ) { $pixel = self::add_request_timestamp_and_nocache( $pixel ); wp_remote_get( $pixel, array( 'blocking' => false, 'redirection' => 2, 'httpversion' => '1.1', 'timeout' => 1, ) ); return true; } }