do nothing ---- if ( is_admin() || wp_doing_cron() ) { return; } if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) { return; } if ( defined( 'WP_CLI' ) && WP_CLI ) { return; } if ( ! Xpay_Plugin::is_connected() ) { return; // unconnected stores never deflect } if ( is_user_logged_in() ) { return; // never reroute staff / admins / logged-in shoppers } $method = isset( $_SERVER['REQUEST_METHOD'] ) ? strtoupper( sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) ) : 'GET'; if ( 'GET' !== $method && 'HEAD' !== $method ) { return; // never touch POST/PUT/etc (checkout, forms) } // Our own self-probe header — never redirect it. if ( ! empty( $_SERVER['HTTP_X_XPAY_PROBE'] ) ) { return; } $ua = isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : ''; if ( '' === $ua ) { return; } $policy = self::policy(); if ( ! $policy || empty( $policy['enabled'] ) ) { return; // no policy / explicitly disabled => no-op } // Backend can hold deflection back until the sidecar is ready. if ( array_key_exists( 'agentSurfaceReady', $policy ) && ! $policy['agentSurfaceReady'] ) { return; } $target = isset( $policy['target'] ) ? (string) $policy['target'] : ''; if ( '' === $target || ( 0 !== strpos( $target, 'https://' ) && 0 !== strpos( $target, 'http://' ) ) ) { return; // no / malformed target => no-op } $agents = ( isset( $policy['deflectAgents'] ) && is_array( $policy['deflectAgents'] ) ) ? $policy['deflectAgents'] : array(); if ( empty( $agents ) ) { return; } // Loop guard: if we're already on the target host, do nothing. $host = isset( $_SERVER['HTTP_HOST'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_HOST'] ) ) : ''; $target_host = (string) wp_parse_url( $target, PHP_URL_HOST ); if ( '' !== $host && '' !== $target_host && strtolower( $host ) === strtolower( $target_host ) ) { return; } // Path excludes — hard-coded safety set + any extra from the policy. $uri = isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '/'; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- used only for prefix match + redirect; not echoed. $uri = is_string( $uri ) ? $uri : '/'; $path = (string) wp_parse_url( $uri, PHP_URL_PATH ); $path = ( '' === $path ) ? '/' : strtolower( $path ); $policy_excludes = ( isset( $policy['excludePaths'] ) && is_array( $policy['excludePaths'] ) ) ? $policy['excludePaths'] : array(); foreach ( array_merge( self::ALWAYS_EXCLUDE, $policy_excludes ) as $ex ) { $ex = strtolower( (string) $ex ); if ( '' !== $ex && 0 === strpos( $path, $ex ) ) { return; } } // UA match — only the realtime shopping fetchers in the policy list. $matched = false; foreach ( $agents as $token ) { $token = (string) $token; if ( '' !== $token && false !== stripos( $ua, $token ) ) { $matched = true; break; } } // Exact-match fetchers (bare/ambiguous UAs). Gemini's user-triggered fetch // is the literal "Google" — matched with strict equality so we never // deflect Googlebot / Google-Extended / APIs-Google (substring "Google"), // which stay on origin for SEO + citations. if ( ! $matched ) { $exact = ( isset( $policy['deflectAgentsExact'] ) && is_array( $policy['deflectAgentsExact'] ) ) ? $policy['deflectAgentsExact'] : array(); $ua_trimmed = trim( $ua ); foreach ( $exact as $token ) { $token = trim( (string) $token ); if ( '' !== $token && $ua_trimmed === $token ) { $matched = true; break; } } } if ( ! $matched ) { return; } // All clear: 302 (temporary) to the sidecar, preserving the path + query. $location = rtrim( $target, '/' ) . $uri; // In-memory marker so Xpay_Agent_Analytics (which runs on `shutdown`, after // this exit) can record deflected=true for this request. Purely a runtime // flag — writes nothing, changes no behaviour here. $GLOBALS['xpay_wc_deflected'] = true; nocache_headers(); // External host by design (the merchant's sidecar) — wp_redirect, not // wp_safe_redirect (which would strip the off-site host). wp_redirect( $location, 302 ); // phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect -- intentional off-site deflection to the merchant's own sidecar. exit; } }