PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.16
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.16
1.10.18 1.10.17 1.10.16 1.10.15 1.10.13 1.10.14 1.10.12 1.10.11 1.10.10 1.10.9 1.10.8 untagged-3d9b7ccddc54df87c672 1.10.7 1.10.6 1.10.5 1.10.3 1.10.4 1.10.2 1.10.1 1.10.0 1.9.17 1.9.15 1.9.16 1.9.14 1.9.13 All 162 releases
woocommerce-pos / includes / Services / Request_Lane.php

Request_Lane.php in WCPOS – Point of Sale (POS) plugin for WooCommerce 1.10.16, at includes/Services/Request_Lane.php

123 lines 4.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Classify the current request into the lane that decides which POS services it needs.
4 *
5 * @package WCPOS\WooCommercePOS
6 */
7
8 namespace WCPOS\WooCommercePOS\Services;
9
10 use WCPOS\WooCommercePOS\Admin\Permalink;
11
12 use const WCPOS\WooCommercePOS\SHORT_NAME;
13
14 /**
15 * One classifier for "what kind of request is this?", computed once per request.
16 *
17 * Why it exists: `Init::init_common()` used to construct every POS service on
18 * every request. On a storefront page that was ~80 plugin files and 22 objects
19 * for hooks that never fire there (measured 2026-09-03, see
20 * .claude/research/2026-09-03-online-store-footprint.md and the
21 * lazy-service-construction spec beside it). The lane is an OPTIMISATION, not a
22 * correctness gate: order services are also constructed late, on the first
23 * order write of any request (see `Init::ensure_order_services()`), so a lane
24 * misclassified as storefront still gets every observer before an order is
25 * touched.
26 *
27 * Detection runs on `init`, before `REST_REQUEST` is defined and before the
28 * rewrite rules populate the POS query vars, so REST and the browser-loaded
29 * POS routes are matched from the request path. WooCommerce's `wc-ajax`
30 * endpoints (cart fragments, add to cart, the classic checkout) are shopper
31 * traffic and stay on the storefront lane even though WooCommerce marks them
32 * DOING_AJAX; admin-ajax.php is covered by `is_admin()`.
33 */
34 final class Request_Lane {
35 public const ADMIN = 'admin';
36 public const CRON = 'cron';
37 public const CLI = 'cli';
38 public const REST = 'rest';
39 public const POS = 'pos';
40 public const STOREFRONT = 'storefront';
41
42 /**
43 * Memoised lane for this request.
44 *
45 * @var string|null
46 */
47 private static ?string $lane = null;
48
49 /**
50 * The lane of the current request.
51 *
52 * @return string One of the class constants.
53 */
54 public static function current(): string {
55 if ( null === self::$lane ) {
56 /**
57 * Filters the request lane. Hosts and tests can force one; the
58 * default is {@see Request_Lane::detect()}.
59 *
60 * @param string $lane One of the Request_Lane constants.
61 */
62 self::$lane = (string) apply_filters( 'woocommerce_pos_request_lane', self::detect() );
63 }
64 return self::$lane;
65 }
66
67 /** Whether the request is a plain shopper page: shop, product, cart, account, wc-ajax. */
68 public static function is_storefront(): bool {
69 return self::STOREFRONT === self::current();
70 }
71
72 /**
73 * Forget the memoised lane. Tests only: the single PHPUnit process never
74 * ends a request.
75 *
76 * @internal
77 */
78 public static function reset(): void {
79 self::$lane = null;
80 }
81
82 /**
83 * Detect the lane from the environment WordPress has established by `init`.
84 *
85 * @return string
86 */
87 private static function detect(): string {
88 if ( \defined( 'WP_CLI' ) && WP_CLI ) {
89 return self::CLI;
90 }
91 if ( wp_doing_cron() ) {
92 return self::CRON;
93 }
94 if ( is_admin() ) {
95 return self::ADMIN;
96 }
97 if ( \function_exists( 'woocommerce_pos_request' ) && woocommerce_pos_request() ) {
98 return self::POS;
99 }
100 $uri = isset( $_SERVER['REQUEST_URI'] ) ? (string) wp_unslash( $_SERVER['REQUEST_URI'] ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- only inspected for a prefix.
101 if ( '' === $uri ) {
102 return self::STOREFRONT;
103 }
104 // The raw ?wcpos=1 marker is not in the query vars yet at init.
105 if ( 1 === preg_match( '#(?:\?|&)' . preg_quote( SHORT_NAME, '#' ) . '=1(?:&|$)#', $uri ) ) {
106 return self::POS;
107 }
108 if ( false !== strpos( $uri, '/' . rest_get_url_prefix() . '/' ) || false !== strpos( $uri, 'rest_route=' ) ) {
109 return self::REST;
110 }
111 $path = (string) wp_parse_url( $uri, PHP_URL_PATH );
112 $home_path = trailingslashit( (string) wp_parse_url( home_url( '/' ), PHP_URL_PATH ) );
113 if ( '/' !== $home_path && 0 === strpos( trailingslashit( $path ), $home_path ) ) {
114 $path = (string) substr( $path, \strlen( untrailingslashit( $home_path ) ) );
115 }
116 $slug = Permalink::get_slug();
117 if ( 1 === preg_match( '#^/(?:index\.php/)?(' . preg_quote( $slug, '#' ) . '|wcpos-[a-z-]+)(/|$)#i', $path ) ) {
118 return self::POS;
119 }
120 return self::STOREFRONT;
121 }
122 }
123