PluginProbe ʕ •ᴥ•ʔ
Jetpack – WP Security, Backup, Speed, & Growth / 16.2-a.1
Jetpack – WP Security, Backup, Speed, & Growth v16.2-a.1
16.1.2 16.2-a.1 16.1.1 16.1 16.1-beta 16.1-beta.2 16.1-beta.3 16.1-a.5 16.1-a.3 16.0.1 16.1-a.1 16.0 16.0-beta 16.0-a.7 16.0-a.5 15.9.1 16.0-a.3 16.0-a.1 15.9 15.9-beta 15.9-a.7 15.9-a.5 15.9-a.3 15.9-a.1 15.8 15.8-beta 15.8-a.7 15.8-a.5 5.2.5 5.3.4 5.4.4 5.5.5 5.6.5 5.7.5 5.8.4 5.9.4 6.0.4 6.1 6.1.1 6.1.2 6.1.3 6.1.4 6.1.5 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.3 6.3.1 6.3.2 6.3.3 6.3.4 6.3.5 6.3.6 6.3.7 6.4 6.4.1 6.4.2 6.4.3 6.4.4 6.4.5 6.4.6 6.5 6.5.1 6.5.2 6.5.3 6.5.4 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.6.5 6.7 6.7.1 6.7.2 6.7.3 6.7.4 6.8 6.8.1 6.8.2 6.8.3 6.8.4 6.8.5 6.9 6.9.1 6.9.2 6.9.3 6.9.4 7.0 7.0.1 7.0.2 7.0.3 7.0.4 7.0.5 7.1 7.1.1 7.1.2 7.1.3 7.1.4 7.1.5 7.2 7.2.1 7.2.1.1 7.2.2 7.2.3 7.2.4 7.2.5 7.3 7.3.0.1 7.3.1 7.3.1.1 7.3.2 7.3.3 7.3.4 7.3.5 7.4 7.4.1 7.4.2 7.4.3 7.4.4 7.4.5 7.5 7.5.0.1 7.5.1 7.5.2 7.5.3 7.5.4 7.5.5 7.5.6 7.5.7 7.6 7.6.1 7.6.2 7.6.3 7.6.4 7.7 7.7.1 7.7.2 7.7.3 7.7.4 7.7.5 7.7.6 7.8 7.8.1 7.8.2 7.8.3 7.8.4 7.9 7.9.1 7.9.2 7.9.3 7.9.4 8.0 8.0.1 8.0.2 8.0.3 8.1 8.1.1 8.1.2 8.1.3 8.1.4 8.2 8.2.0.1 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.2.6 8.3 8.3.1 8.3.2 8.3.3 8.4 8.4.1 8.4.2 8.4.3 8.4.4 8.4.5 8.5 8.5.1 8.5.2 8.5.3 8.6 8.6.1 8.6.2 8.6.3 8.6.4 8.7 8.7.0.1 8.7.1 8.7.2 8.7.3 8.7.4 8.8 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.9 8.9.1 8.9.2 8.9.3 8.9.4 9.0 9.0.1 9.0.2 9.0.3 9.0.4 9.0.5 9.1 9.1.1 9.1.2 9.1.3 9.2 9.2.1 9.2.2 9.2.3 9.2.4 9.3 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.4 9.4.1 9.4.2 9.4.3 9.4.4 9.5 9.5.1 9.5.2 9.5.3 9.5.4 9.5.5 9.6 9.6.1 9.6.2 9.6.3 9.6.4 9.7 9.7.1 9.7.2 15.7-beta.2 9.7.3 15.7.1 9.8 15.8-a.1 9.8.1 15.8-a.3 9.8.2 2.0.9 9.8.3 2.1.7 9.9 2.2.10 9.9.1 2.3.10 9.9.2 2.4.7 9.9.3 2.5.5 2.6.6 2.7.5 2.8.5 2.9.6 3.0.6 3.1.5 3.2.5 3.3.6 3.4.6 3.5.6 3.6.4 3.7.5 3.8.5 3.9.10 4.0.7 4.1.4 4.2.5 4.3.5 4.4.5 4.5.3 4.6.3 4.7.4 4.8.5 4.9.3 5.0.3 5.1.4 trunk 10.0 10.0.1 10.0.2 10.1 10.1.1 10.1.2 10.2 10.2.1 10.2.2 10.2.3 10.3 10.3.1 10.3.2 10.4 10.4.1 10.4.2 10.5 10.5.1 10.5.2 10.5.3 10.6 10.6.1 10.6.2 10.7 10.7.1 10.7.2 10.8 10.8.1 10.8.2 10.9 10.9.1 10.9.2 10.9.3 11.0 11.0.1 11.0.2 11.1 11.1.1 11.1.2 11.1.3 11.1.4 11.2 11.2.1 11.2.2 11.3 11.3.1 11.3.2 11.3.3 11.3.4 11.4 11.4.1 11.4.2 11.5 11.5.1 11.5.2 11.5.3 11.6 11.6.1 11.6.2 11.7 11.7.1 11.7.2 11.7.3 11.8 11.8.3 11.8.4 11.8.5 11.8.6 11.9 11.9.1 11.9.2 11.9.3 12.0 12.0.1 12.0.2 12.1 12.1.1 12.1.2 12.2 12.2.1 12.2.2 12.3 12.3.1 12.4 12.4.1 12.5 12.5.1 12.6 12.6.1 12.6.2 12.6.3 12.7 12.7.1 12.7.2 12.8 12.8.1 12.8.2 12.9 12.9.1 12.9.2 12.9.3 12.9.4 13.0 13.0.1 13.1 13.1.1 13.1.2 13.1.3 13.1.4 13.2 13.2.1 13.2.2 13.2.3 13.3 13.3.1 13.3.2 13.4 13.4.1 13.4.2 13.4.3 13.4.4 13.5 13.5.1 13.6 13.6.1 13.7 13.7.1 13.8 13.8.1 13.8.2 13.9 13.9.1 14.0 14.1 14.2 14.2.1 14.3 14.4 14.4.1 14.5 14.6 14.7 14.8 14.9 14.9.1 15.0 15.0.1 15.0.2 15.1 15.1.1 15.2 15.3 15.3.1 15.4 15.5 15.6 15.7 15.7-a.1 15.7-a.3 15.7-a.5 15.7-a.7 15.7-beta
jetpack / jetpack_vendor / automattic / woocommerce-analytics / src / mu-plugin / woocommerce-analytics-proxy-speed-module-template.php
jetpack / jetpack_vendor / automattic / woocommerce-analytics / src / mu-plugin Last commit date
woocommerce-analytics-proxy-speed-module-template.php 5 months ago
woocommerce-analytics-proxy-speed-module-template.php
296 lines
1 <?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2 /**
3 * Plugin Name: WooCommerce Analytics - Proxy Speed Module
4 * Description: Speeds up WooCommerce Analytics' proxy by handling requests at MU-plugin stage and exiting early.
5 * Plugin URI: https://woocommerce.com
6 * Author: WooCommerce
7 * Version: {{VERSION}}
8 * Author URI: https://woocommerce.com
9 *
10 * Text Domain: woocommerce-analytics
11 *
12 * This module intercepts proxy tracking requests at the MU-plugin stage (before regular plugins load)
13 * and handles them completely, then exits. This dramatically reduces response time by avoiding
14 * the full WordPress plugin initialization.
15 */
16
17 defined( 'ABSPATH' ) || exit;
18
19 /**
20 * WooCommerce Analytics Proxy Speed Module
21 */
22 class WooCommerceAnalyticsProxySpeed {
23
24 /**
25 * Path of the proxy request.
26 *
27 * @var string
28 */
29 const PROXY_REQUEST_PATH = 'woocommerce-analytics/v1/track';
30
31 /**
32 * Autoloader path - this placeholder is replaced during installation.
33 * DO NOT MODIFY - this value is injected by the parent plugin.
34 *
35 * @var string
36 */
37 const AUTOLOADER_PATH = '{{AUTOLOADER_PATH}}';
38
39 /**
40 * Initialize the proxy speed module.
41 *
42 * @return void
43 */
44 public function init() {
45 // Only intercept POST requests to the proxy endpoint.
46 if ( ! $this->is_proxy_request() ) {
47 return;
48 }
49
50 // If autoloader failed, let WordPress continue loading
51 // and fallback to the regular REST API.
52 if ( ! $this->load_autoloader() ) {
53 return;
54 }
55
56 // Handle the request completely and exit.
57 $this->handle_proxy_request();
58 exit;
59 }
60
61 /**
62 * Check if current request is a proxy request.
63 *
64 * @return bool
65 */
66 private function is_proxy_request() {
67 if ( 'POST' !== $this->get_request_method() ) {
68 return false;
69 }
70
71 $path = $this->get_request_path();
72
73 if ( '' === $path ) {
74 return false;
75 }
76
77 $normalized_path = rtrim( $path, '/' );
78 $proxy_suffix = '/' . ltrim( self::PROXY_REQUEST_PATH, '/' );
79
80 if ( strlen( $normalized_path ) < strlen( $proxy_suffix ) ) {
81 return false;
82 }
83
84 return substr( $normalized_path, -strlen( $proxy_suffix ) ) === $proxy_suffix;
85 }
86
87 /**
88 * Load the autoloader.
89 *
90 * At MU-plugin stage, plugins haven't loaded yet, so we bootstrap
91 * the autoloader directly using the path injected during installation.
92 *
93 * @return bool True if autoloader loaded and classes are available.
94 */
95 private function load_autoloader() {
96 $autoload_path = self::AUTOLOADER_PATH;
97
98 // Validate the path was properly injected (not still a placeholder).
99 if ( strpos( $autoload_path, '{{' ) !== false ) {
100 error_log( 'WooCommerce Analytics Proxy Speed Module: Autoloader path placeholder was not replaced during installation.' ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
101 return false;
102 }
103
104 if ( ! file_exists( $autoload_path ) ) {
105 error_log( 'WooCommerce Analytics Proxy Speed Module: Autoloader file not found at: ' . $autoload_path ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
106 return false;
107 }
108
109 try {
110 require_once $autoload_path;
111 } catch ( \Throwable $e ) {
112 error_log( 'WooCommerce Analytics Proxy Speed Module: Failed to load autoloader: ' . $e->getMessage() ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
113 return false;
114 }
115
116 if ( ! class_exists( '\Automattic\Woocommerce_Analytics\WC_Analytics_Tracking' ) ) {
117 error_log( 'WooCommerce Analytics Proxy Speed Module: WC_Analytics_Tracking class not found after loading autoloader.' ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
118 return false;
119 }
120
121 return true;
122 }
123
124 /**
125 * Handle the proxy request completely.
126 *
127 * Processes the events and sends the response without loading
128 * regular WordPress plugins.
129 *
130 * @return void
131 */
132 private function handle_proxy_request() {
133 if ( ! headers_sent() ) {
134 header( 'Content-Type: application/json; charset=utf-8' );
135 header( 'Cache-Control: no-cache, must-revalidate' );
136 }
137
138 try {
139 $this->process_proxy_request();
140 } catch ( \Throwable $e ) {
141 error_log( 'WooCommerce Analytics Proxy Speed Module: Uncaught error in handle_proxy_request: ' . $e->getMessage() ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
142 $this->send_json_response(
143 array(
144 'success' => false,
145 'error' => 'Internal server error while processing analytics events.',
146 ),
147 500
148 );
149 }
150 }
151
152 /**
153 * Process the proxy request body: parse events, record them, and send the response.
154 *
155 * @return void
156 */
157 private function process_proxy_request() {
158 // Apply magic quotes to superglobals ($_GET, $_POST, $_COOKIE, $_REQUEST) for compatibility with the regular API flow.
159 if ( function_exists( 'wp_magic_quotes' ) ) {
160 wp_magic_quotes();
161 }
162
163 $body = file_get_contents( 'php://input' );
164 if ( empty( $body ) ) {
165 $this->send_json_response(
166 array(
167 'success' => false,
168 'error' => 'Empty request body',
169 ),
170 400
171 );
172 return;
173 }
174
175 $events = json_decode( $body, true );
176 if ( json_last_error() !== JSON_ERROR_NONE ) {
177 $this->send_json_response(
178 array(
179 'success' => false,
180 'error' => 'Invalid JSON',
181 ),
182 400
183 );
184 return;
185 }
186
187 // Normalize: wrap a single event object or unexpected scalar in an array.
188 if ( ! is_array( $events ) || isset( $events['event_name'] ) ) {
189 $events = array( $events );
190 }
191
192 $results = array();
193 $has_errors = false;
194
195 foreach ( $events as $index => $event ) {
196 if ( empty( $event ) || ! is_array( $event ) ) {
197 $results[ $index ] = array(
198 'success' => false,
199 'error' => 'Invalid event format',
200 );
201 $has_errors = true;
202 continue;
203 }
204
205 $event_name = $event['event_name'] ?? null;
206 $properties = $event['properties'] ?? array();
207
208 if ( ! $event_name || ! is_array( $properties ) ) {
209 $results[ $index ] = array(
210 'success' => false,
211 'error' => 'Missing event_name or invalid properties',
212 );
213 $has_errors = true;
214 continue;
215 }
216
217 $result = \Automattic\Woocommerce_Analytics\WC_Analytics_Tracking::record_event( $event_name, $properties );
218
219 if ( is_wp_error( $result ) ) {
220 $results[ $index ] = array(
221 'success' => false,
222 'error' => $result->get_error_message(),
223 );
224 $has_errors = true;
225 continue;
226 }
227
228 $results[ $index ] = array( 'success' => true );
229 }
230
231 \Automattic\Woocommerce_Analytics\WC_Analytics_Tracking::send_batched_pixels();
232
233 $this->send_json_response(
234 array(
235 'success' => ! $has_errors,
236 'results' => $results,
237 'is_proxy_speed_module' => true,
238 ),
239 $has_errors ? 207 : 200
240 );
241 }
242
243 /**
244 * Send a JSON response.
245 *
246 * @param array $data Response data.
247 * @param int $status_code HTTP status code.
248 * @return void
249 */
250 private function send_json_response( $data, $status_code = 200 ) {
251 http_response_code( $status_code );
252 echo wp_json_encode( $data, JSON_UNESCAPED_SLASHES );
253 }
254
255 /**
256 * Helper method to retrieve the request path.
257 *
258 * Extracts and validates the path component from $_SERVER['REQUEST_URI']
259 * for safe internal matching.
260 *
261 * @return string The validated path, or empty string on failure.
262 */
263 private function get_request_path() {
264 $raw_uri = isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
265
266 if ( ! is_string( $raw_uri ) ) {
267 return '';
268 }
269
270 // Extract just the path component to avoid matching against query strings, etc.
271 $path = wp_parse_url( $raw_uri, PHP_URL_PATH );
272
273 if ( ! is_string( $path ) ) {
274 return '';
275 }
276
277 // Ensure the path contains only expected URL path characters.
278 if ( preg_match( '/[^A-Za-z0-9\-._~\/]/', $path ) ) {
279 return '';
280 }
281
282 return $path;
283 }
284
285 /**
286 * Helper method to get request method.
287 *
288 * @return string
289 */
290 private function get_request_method() {
291 return isset( $_SERVER['REQUEST_METHOD'] ) ? strtoupper( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
292 }
293 }
294
295 ( new WooCommerceAnalyticsProxySpeed() )->init();
296