PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.3.7
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.3.7
1.3.7 1.3.6 1.3.5 1.3.4 1.3.3 1.3.2 1.3.1 1.3.0 1.2.4 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 All 33 releases
← All changes | includes/class-hit-counter.php +44 -5 1.2.4 → 1.3.7 View file →
@@ -88,13 +88,20 @@
88 88 self::flush_pending();
89 89 }
90 90
91 91 /**
92 + * The bot / crawler / scanner alternation, without delimiters so the
93 + * drop-in can compose it — see excluded_ua_regex().
94 + */
95 + public const BOT_UA_PATTERN = 'bot|crawl|spider|slurp|scan|curl|wget|python-requests|python-urllib|libwww|httpclient|go-http|okhttp|axios|node-fetch|headless|phantomjs|masscan|nikto|sqlmap|zgrab|semrush|ahrefs|mj12|dotbot|petalbot|bytespider|facebookexternalhit|preview|monitor|uptime|pingdom|gtmetrix|lighthouse|pagespeed';
96 +
97 + /**
92 98 * Whether a User-Agent is a known bot / crawler / vulnerability scanner —
93 99 * its cache misses are cache-warming or hostile noise, not a signal of how
94 100 * the cache serves real visitors. Deliberately broad: matches the common
95 101 * crawler tokens plus the generic markers scanners and libraries carry.
96 - * Pure + unit-tested. (#118)
102 + * Unit-tested; no longer pure — Self_Traffic::is_self() runs the
103 + * xspeed_self_user_agents filter, so the answer can vary per site. (#118)
97 104 */
98 105 public static function is_bot_ua( string $ua ): bool {
99 106 if ( '' === $ua ) {
100 107 // No UA at all is overwhelmingly automated traffic, not a browser.
@@ -99,14 +106,39 @@
99 106 if ( '' === $ua ) {
100 107 // No UA at all is overwhelmingly automated traffic, not a browser.
101 108 return true;
102 109 }
103 - return 1 === preg_match(
104 - '~(bot|crawl|spider|slurp|scan|curl|wget|python-requests|python-urllib|libwww|httpclient|go-http|okhttp|axios|node-fetch|headless|phantomjs|masscan|nikto|sqlmap|zgrab|semrush|ahrefs|mj12|dotbot|petalbot|bytespider|facebookexternalhit|preview|monitor|uptime|pingdom|gtmetrix|lighthouse|pagespeed)~i',
105 - $ua
106 - );
110 + // Our own warmer, benchmark and verifier are warming the cache, not
111 + // visiting it: `xSpeed-Warmer`, `xSpeed Benchmark`, and the rest.
112 + // Callers with a request also check Self_Traffic::request_is_marked().
113 + if ( Self_Traffic::is_self( $ua ) ) {
114 + return true;
115 + }
116 + return 1 === preg_match( '~(' . self::BOT_UA_PATTERN . ')~i', $ua );
107 117 }
108 118
119 + /**
120 + * The "do not count this user agent" alternation: bots and scanners,
121 + * plus the fragments xSpeed's own requests carry. A renamed warmer is
122 + * not in it on purpose; that request is recognised by
123 + * Self_Traffic::HEADER, because its UA may be a real browser's.
124 + *
125 + * Baked into the drop-in at install time (`@@XSPEED_HIT_EXCLUDE_RE@@`).
126 + * The drop-in runs before WordPress, so it cannot ask this class and the
127 + * hits.log line it writes carries no user agent — nothing downstream can
128 + * reclassify the line later, which is why the decision has to travel
129 + * with the file. A hardcoded copy of the fragments drifted instead: it
130 + * excluded the warmer but still counted every crawler HIT, and it could
131 + * not know about an overridden `xspeed_preloader_user_agent`.
132 + */
133 + public static function excluded_ua_regex(): string {
134 + $parts = array( self::BOT_UA_PATTERN );
135 + foreach ( Self_Traffic::agents() as $agent ) {
136 + $parts[] = preg_quote( $agent, '#' );
137 + }
138 + return implode( '|', $parts );
139 + }
140 +
109 141 public static function record_miss(): void {
110 142 ++self::$pending['miss'];
111 143 // Flush misses INLINE, not at shutdown. A MISS is recorded ONLY here
112 144 // (HITs additionally have the durable hits.log drain as a backstop),
@@ -232,8 +264,15 @@
232 264 // .htaccess can't header/log a static serve — see
233 265 // Cache::static_rewrite_allowed(). So Apache is the lone server that
234 266 // serves static hits below PHP yet logs them to the SERVER's access
235 267 // log, which is what we scan here.
268 + //
269 + // LiteSpeed stays out even with the Static Fast Path opt-in (#509):
270 + // its access log records the ORIGINAL request line ("GET / …"), not
271 + // the rewritten static-file path, so the needle below can never
272 + // match and scanning would only pretend to count. Verified on
273 + // OpenLiteSpeed 1.8. Those hits are genuinely uncounted, which the
274 + // dashboard discloses via stats.static_hits_uncounted.
236 275 if ( Server::APACHE !== Server::type() ) {
237 276 return 0;
238 277 }
239 278