PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.18
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.18
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
← All changes | includes/API/V2/Ping.php +63 -8 1.10.71.10.18 View file →
@@ -36,8 +36,22 @@
36 36 *
37 37 * @var bool
38 38 */
39 39 private static $host_pressure_checked = false;
40 +
41 + /**
42 + * Host CPU count, or null when unavailable.
43 + *
44 + * @var int|null
45 + */
46 + private static $host_cpu_count = null;
47 +
48 + /**
49 + * Whether the host CPU count has been resolved.
50 + *
51 + * @var bool
52 + */
53 + private static $host_cpu_count_resolved = false;
40 54 // phpcs:disable Squiz.Commenting.FunctionComment.MissingParamTag, Squiz.Commenting.FunctionComment.Missing -- Typed signatures keep this bootstrap path within its strict size budget.
41 55 /** Detect an exact raw ping request. */
42 56 public static function matches_request( string $method, string $request_uri, ?string $rest_route ): bool {
43 57 if ( 'GET' !== $method && 'HEAD' !== $method ) {
@@ -47,8 +61,34 @@
47 61
48 62 return self::ROUTE === $rest_route || ( \strlen( $path ) >= \strlen( self::PRETTY_ROUTE ) && self::PRETTY_ROUTE === substr( $path, -\strlen( self::PRETTY_ROUTE ) ) );
49 63 }
50 64
65 + /**
66 + * Response headers that keep the ping out of proxy and server caches.
67 + *
68 + * The fast path answers before WP REST exists, so Rest_Cors never adds
69 + * its cache-defeating headers here; without these an origin page cache
70 + * served one host's ping (timestamp and pressure bucket) frozen for its
71 + * whole TTL (measured 2026-09-16). Same Cache-Control value as Rest_Cors.
72 + *
73 + * @return array<string, string>
74 + */
75 + public static function cache_defeating_headers(): array {
76 + return array(
77 + 'Cache-Control' => 'private, no-store',
78 + 'X-LiteSpeed-Cache-Control' => 'no-cache',
79 + );
80 + }
81 +
82 + /** Belt and braces for drop-in page caches that finalise at shutdown and read constants, not headers. */
83 + private static function forbid_page_cache(): void {
84 + foreach ( array( 'DONOTCACHEPAGE', 'LSCACHE_NO_CACHE' ) as $constant ) {
85 + if ( ! \defined( $constant ) ) {
86 + \define( $constant, true ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedConstantFound -- third-party constant.
87 + }
88 + }
89 + }
90 +
51 91 /** Serve a matching request before the remaining plugins load. */
52 92 public static function maybe_serve(): void {
53 93 $method = isset( $_SERVER['REQUEST_METHOD'] ) && \is_string( $_SERVER['REQUEST_METHOD'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REQUEST_METHOD'] ) ) : '';
54 94 if ( 'GET' !== $method && 'HEAD' !== $method ) {
@@ -62,10 +102,14 @@
62 102 if ( ! self::matches_request( $method, $request_uri, $rest_route ) ) {
63 103 return;
64 104 }
65 105 $data = self::payload();
106 + self::forbid_page_cache();
66 107 http_response_code( 200 );
67 108 header( 'Content-Type: application/json; charset=UTF-8' );
109 + foreach ( self::cache_defeating_headers() as $name => $value ) {
110 + header( $name . ': ' . $value );
111 + }
68 112 header( 'Access-Control-Allow-Origin: *' );
69 113 // Deliberately just the one header this fast path can emit, not the
70 114 // full Rest_Cors::EXPOSE_HEADERS set: this short-circuits before the
71 115 // autoloader and WP REST exist. The OPTIONS preflight for this route
@@ -136,22 +180,33 @@
136 180
137 181 return $load <= 1.8 ? 'elevated' : 'high';
138 182 }
139 183
140 - /** Read the host load average and convert it to a bucket, or null when unavailable. */
184 + /**
185 + * Use only /proc/cpuinfo because sys_getloadavg() reads host-wide /proc/loadavg,
186 + * so its CPU divisor must share the host namespace rather than a container quota.
187 + */
188 + public static function cpu_count_from_cpuinfo( ?string $cpuinfo ): ?int {
189 + $found = null !== $cpuinfo ? preg_match_all( '/^processor\s*:/m', $cpuinfo ) : false;
190 +
191 + return \is_int( $found ) && $found > 0 ? $found : null;
192 + }
193 +
194 + /**
195 + * Normalize host load using the /proc/cpuinfo CPU count.
196 + * Unknown counts yield null (no header), rather than misleading pressure from a guessed divisor.
197 + */
141 198 private static function read_host_pressure_bucket(): ?string {
142 199 if ( ! \function_exists( 'sys_getloadavg' ) || ! \is_array( $average = @sys_getloadavg() ) || ! isset( $average[0] ) ) { // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.FoundInControlStructure -- call only after availability check.
143 200 return null;
144 201 }
145 - /** @var int|null $cpus */ // phpcs:ignore Generic.Commenting.DocComment.MissingShort -- local static type.
146 - static $cpus = null;
147 - if ( null === $cpus ) {
148 - $cpuinfo = @file_get_contents( '/proc/cpuinfo' );
149 - $found = \is_string( $cpuinfo ) ? preg_match_all( '/^processor\s*:/m', $cpuinfo ) : false;
150 - $cpus = \is_int( $found ) && $found > 0 ? $found : 1;
202 + if ( ! self::$host_cpu_count_resolved ) {
203 + $cpuinfo = @file_get_contents( '/proc/cpuinfo' );
204 + self::$host_cpu_count = self::cpu_count_from_cpuinfo( false === $cpuinfo ? null : $cpuinfo );
205 + self::$host_cpu_count_resolved = true;
151 206 }
152 207
153 - return self::pressure_bucket( (float) $average[0] / $cpus );
208 + return null === self::$host_cpu_count ? null : self::pressure_bucket( (float) $average[0] / self::$host_cpu_count );
154 209 }
155 210
156 211 /** @return array<string, bool|int|string> */ // phpcs:ignore Generic.Commenting.DocComment.MissingShort -- compact typed payload.
157 212 private static function payload(): array {