PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.3
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.3
1.10.19 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 All 163 releases
woocommerce-pos / includes / API / V2 / Ping.php

Ping.php in WCPOS – Point of Sale (POS) plugin for WooCommerce 1.10.3, at includes/API/V2/Ping.php

159 lines 6.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Public ping REST API controller and bootstrap fast path.
4 *
5 * @package WCPOS\WooCommercePOS\API\V2
6 */
7
8 namespace WCPOS\WooCommercePOS\API\V2;
9
10 use WP_REST_Response;
11 use const WCPOS\WooCommercePOS\VERSION;
12 /**
13 * Serves the lightweight public status response.
14 */
15 final class Ping {
16 private const ROUTE = '/wcpos/v2/ping';
17 private const PRETTY_ROUTE = '/wp-json/wcpos/v2/ping';
18
19 /**
20 * Request-scoped host pressure bucket.
21 *
22 * @var string|null
23 */
24 private static $host_pressure_bucket = null;
25
26 /**
27 * Whether host pressure has been read this request.
28 *
29 * @var bool
30 */
31 private static $host_pressure_checked = false;
32 // phpcs:disable Squiz.Commenting.FunctionComment.MissingParamTag, Squiz.Commenting.FunctionComment.Missing -- Typed signatures keep this bootstrap path within its strict size budget.
33 /** Detect an exact raw ping request. */
34 public static function matches_request( string $method, string $request_uri, ?string $rest_route ): bool {
35 if ( 'GET' !== $method && 'HEAD' !== $method ) {
36 return false;
37 }
38 $path = explode( '?', $request_uri, 2 )[0];
39
40 return self::ROUTE === $rest_route || ( \strlen( $path ) >= \strlen( self::PRETTY_ROUTE ) && self::PRETTY_ROUTE === substr( $path, -\strlen( self::PRETTY_ROUTE ) ) );
41 }
42
43 /** Serve a matching request before the remaining plugins load. */
44 public static function maybe_serve(): void {
45 $method = isset( $_SERVER['REQUEST_METHOD'] ) && \is_string( $_SERVER['REQUEST_METHOD'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) : '';
46 if ( 'GET' !== $method && 'HEAD' !== $method ) {
47 return;
48 }
49 $request_uri = isset( $_SERVER['REQUEST_URI'] ) && \is_string( $_SERVER['REQUEST_URI'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
50 if ( false === strpos( $request_uri, 'wcpos' ) ) {
51 return;
52 }
53 $rest_route = isset( $_GET['rest_route'] ) && \is_string( $_GET['rest_route'] ) ? sanitize_text_field( wp_unslash( $_GET['rest_route'] ) ) : null;
54 if ( ! self::matches_request( $method, $request_uri, $rest_route ) ) {
55 return;
56 }
57 $data = self::payload();
58 http_response_code( 200 );
59 header( 'Content-Type: application/json; charset=UTF-8' );
60 header( 'Access-Control-Allow-Origin: *' );
61 // Deliberately just the one header this fast path can emit, not the
62 // full Rest_Cors::EXPOSE_HEADERS set: this short-circuits before the
63 // autoloader and WP REST exist. The OPTIONS preflight for this route
64 // is answered by Rest_Cors on the normal REST lane.
65 header( 'Access-Control-Expose-Headers: X-WCPOS-Pressure' );
66 if ( isset( $data['pressure'] ) ) {
67 header( 'X-WCPOS-Pressure: ' . $data['pressure'] );
68 }
69 if ( 'HEAD' !== $method ) {
70 echo wp_json_encode( $data ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- JSON HTTP response.
71 }
72 // Ship the response before exit's shutdown handlers run: the OTel
73 // wordpress instrumentation reads conditional tags (is_404) at shutdown,
74 // and with no query having run, WP_DEBUG_DISPLAY sites would append a
75 // _doing_it_wrong notice after the JSON body (#1582). Under FPM, closing
76 // the request first makes late output unreachable; elsewhere, mute
77 // display so shutdown notices cannot corrupt the payload.
78 if ( \function_exists( 'fastcgi_finish_request' ) ) {
79 fastcgi_finish_request();
80 } else {
81 @ini_set( 'display_errors', '0' ); // phpcs:ignore WordPress.PHP.IniSet.display_errors_Disallowed, WordPress.PHP.NoSilencedErrors.Discouraged -- last-resort mute on non-FPM SAPIs; the response is already emitted.
82 }
83 exit;
84 }
85
86 /** Register the canonical REST fallback. */
87 public function register_routes(): void {
88 register_rest_route(
89 'wcpos/v2',
90 '/ping',
91 array(
92 'methods' => 'GET, HEAD',
93 'callback' => array( $this, 'get_ping' ),
94 'permission_callback' => '__return_true',
95 )
96 );
97 }
98
99 /** @return array<string, string[]> */ // phpcs:ignore Generic.Commenting.DocComment.MissingShort -- compact typed classification.
100 public function wcpos_route_classifications(): array {
101 return array( 'public' => array( self::ROUTE ) );
102 }
103
104 /** Return the canonical REST response. */
105 public function get_ping(): WP_REST_Response {
106 $data = self::payload();
107 $response = new WP_REST_Response( $data, 200 );
108 if ( isset( $data['pressure'] ) ) {
109 $response->header( 'X-WCPOS-Pressure', $data['pressure'] );
110 }
111
112 return $response;
113 }
114
115 /** Convert normalized load to a pressure bucket, or read host load when omitted (memoized per request so body and header always agree). */
116 public static function pressure_bucket( ?float $load = null ): ?string {
117 if ( null === $load ) {
118 if ( ! self::$host_pressure_checked ) {
119 self::$host_pressure_checked = true;
120 self::$host_pressure_bucket = self::read_host_pressure_bucket();
121 }
122
123 return self::$host_pressure_bucket;
124 }
125 if ( $load < 0.9 ) {
126 return 'low';
127 }
128
129 return $load <= 1.8 ? 'elevated' : 'high';
130 }
131
132 /** Read the host load average and convert it to a bucket, or null when unavailable. */
133 private static function read_host_pressure_bucket(): ?string {
134 if ( ! \function_exists( 'sys_getloadavg' ) || ! \is_array( $average = @sys_getloadavg() ) || ! isset( $average[0] ) ) { // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.FoundInControlStructure -- call only after availability check.
135 return null;
136 }
137 /** @var int|null $cpus */ // phpcs:ignore Generic.Commenting.DocComment.MissingShort -- local static type.
138 static $cpus = null;
139 if ( null === $cpus ) {
140 $cpuinfo = @file_get_contents( '/proc/cpuinfo' );
141 $found = \is_string( $cpuinfo ) ? preg_match_all( '/^processor\s*:/m', $cpuinfo ) : false;
142 $cpus = \is_int( $found ) && $found > 0 ? $found : 1;
143 }
144
145 return self::pressure_bucket( (float) $average[0] / $cpus );
146 }
147
148 /** @return array<string, bool|int|string> */ // phpcs:ignore Generic.Commenting.DocComment.MissingShort -- compact typed payload.
149 private static function payload(): array {
150 $data = array( 'ok' => true, 'ts' => time(), 'v' => VERSION ); // phpcs:ignore WordPress.Arrays.ArrayDeclarationSpacing.AssociativeArrayFound -- fixed four-field maximum.
151 $pressure = self::pressure_bucket();
152 if ( null !== $pressure ) {
153 $data['pressure'] = $pressure;
154 }
155
156 return $data;
157 }
158 }
159