| @@ -1,8 +1,8 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | /** |
| 3 | 3 | * XSPEED_DROPIN |
| 4 | - * XSPEED_DROPIN_VERSION: 8 | |
| 4 | + * XSPEED_DROPIN_VERSION: 10 | |
| 5 | 5 | * Drop-in cache loader. Serves cached HTML before WordPress fully boots. |
| 6 | 6 | * |
| 7 | 7 | * Bump XSPEED_DROPIN_VERSION whenever this file's serve logic changes so |
| 8 | 8 | * Cache::ensure_dropin_current() reinstalls it on existing sites (the |
| @@ -28,8 +28,15 @@ | ||
| 28 | 28 | * v8: never serve an empty, stale, or short `.br` sibling — an uninflatable |
| 29 | 29 | * brotli stream renders as a blank page. THIS FILE IS A COPY made when |
| 30 | 30 | * caching was enabled, so without the bump an updated site keeps the old |
| 31 | 31 | * serve logic and never receives the fix (#286). |
| 32 | + * v9: carry the baked edge-header answer, so a hold set for a page reaches | |
| 33 | + * the paths that run without PHP. | |
| 34 | + * v10: keep bots, scanners, cached 404s and xSpeed's own requests (by UA | |
| 35 | + * or the X-XSpeed-Self header) out of hits.log. Without | |
| 36 | + * the bump an existing install keeps writing every crawler HIT into the | |
| 37 | + * ratio while its misses are excluded, which reads MORE optimistic than | |
| 38 | + * having no exclusion at all. | |
| 32 | 39 | * |
| 33 | 40 | * IMPORTANT: This file is included by wp-settings.php BEFORE |
| 34 | 41 | * wp-includes/formatting.php and wp-includes/load.php are loaded, so NO |
| 35 | 42 | * WordPress functions (sanitize_text_field, wp_unslash, is_admin, |
| @@ -319,8 +326,42 @@ | ||
| 319 | 326 | // rewrite sends "HIT (nginx)" for the fast 5-15ms path; same header, |
| 320 | 327 | // distinct value so you can tell which layer served the page. |
| 321 | 328 | header( 'X-XSpeed-Cache: HIT (php)' ); |
| 322 | 329 | |
| 330 | + // Edge/CDN headers decided by Cache::edge_headers_for(). No filter | |
| 331 | + // can run here — plugins are not loaded — so Cache::install_dropin() | |
| 332 | + // bakes the resolved pairs into the literal below and re-bakes them | |
| 333 | + // on every cache settings save. | |
| 334 | + // | |
| 335 | + // An un-substituted placeholder means this file was copied straight | |
| 336 | + // from a source checkout: it stays a string, is_array() rejects it, | |
| 337 | + // and the HIT is served with no edge headers rather than a fatal. | |
| 338 | + $xspeed_edge_headers = '@@XSPEED_EDGE_HEADERS@@'; | |
| 339 | + | |
| 340 | + // A page whose answer differs from the site-wide one carries its own | |
| 341 | + // pairs in the sidecar. It REPLACES the baked set rather than adding | |
| 342 | + // to it: the two describe the same response, and merging would leave | |
| 343 | + // the baked lifetime in place beside the hold meant to overrule it. | |
| 344 | + if ( isset( $xspeed_meta['edge_headers'] ) && is_array( $xspeed_meta['edge_headers'] ) ) { | |
| 345 | + $xspeed_edge_headers = $xspeed_meta['edge_headers']; | |
| 346 | + } | |
| 347 | + | |
| 348 | + // The one setting this file reads for itself. Everything else about | |
| 349 | + // the edge answer is baked, because re-deriving it here would mean | |
| 350 | + // loading options before WordPress exists. `off` is the exception | |
| 351 | + // because it is the emergency switch: when something is wrong in | |
| 352 | + // production at three in the morning, waiting for a re-bake is not an | |
| 353 | + // answer. Any other value is a pin, and a pin is already baked in. | |
| 354 | + if ( defined( 'XSPEED_EDGE_PROVIDER' ) && 'off' === strtolower( (string) XSPEED_EDGE_PROVIDER ) ) { | |
| 355 | + $xspeed_edge_headers = array(); | |
| 356 | + } | |
| 357 | + | |
| 358 | + if ( is_array( $xspeed_edge_headers ) ) { | |
| 359 | + foreach ( $xspeed_edge_headers as $xspeed_edge_name => $xspeed_edge_value ) { | |
| 360 | + header( $xspeed_edge_name . ': ' . $xspeed_edge_value ); | |
| 361 | + } | |
| 362 | + } | |
| 363 | + | |
| 323 | 364 | // Record the HIT for the dashboard hit-ratio. The drop-in runs |
| 324 | 365 | // BEFORE WordPress loads, so it can't call Hit_Counter — instead |
| 325 | 366 | // it appends one line to the same hits.log the nginx static path |
| 326 | 367 | // uses, and Hit_Counter::collect_nginx_log_hits() drains + counts |
| @@ -338,10 +379,33 @@ | ||
| 338 | 379 | $xspeed_hits_log = '@@XSPEED_HITS_LOG@@'; // replaced at install |
| 339 | 380 | if ( '@@' === substr( $xspeed_hits_log, 0, 2 ) ) { |
| 340 | 381 | $xspeed_hits_log = WP_CONTENT_DIR . '/uploads/xspeed/hits.log'; |
| 341 | 382 | } |
| 342 | - // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- pre-WP drop-in; WP_Filesystem isn't loaded. One short line, append + lock; failures are non-fatal (the ratio just under-counts). | |
| 343 | - @file_put_contents( $xspeed_hits_log, "hit\n", FILE_APPEND | LOCK_EX ); | |
| 383 | + // Don't count a bot, a scanner, a 404 or one of xSpeed's own | |
| 384 | + // requests as a visitor hit. It has to be decided HERE: the log line | |
| 385 | + // is just "hit" with no user agent, so Hit_Counter batch-counts these | |
| 386 | + // lines blind and nothing downstream can reclassify one. The UA | |
| 387 | + // pattern is baked in at install time from | |
| 388 | + // Hit_Counter::excluded_ua_regex() (the drop-in runs before | |
| 389 | + // WordPress, so it cannot ask). xSpeed's own requests also carry the | |
| 390 | + // X-XSpeed-Self header (Self_Traffic::HEADER), which is what catches | |
| 391 | + // a warmer renamed to a real browser's UA without dropping real | |
| 392 | + // visitors on that browser. An empty UA counts as automated, like | |
| 393 | + // is_bot_ua(''). A cached 404 is excluded on the PHP path too. | |
| 394 | + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- pre-WP drop-in; only matched against a baked pattern, never echoed or stored. | |
| 395 | + $xspeed_hit_ua = isset( $_SERVER['HTTP_USER_AGENT'] ) ? (string) $_SERVER['HTTP_USER_AGENT'] : ''; | |
| 396 | + $xspeed_hit_ex = '@@XSPEED_HIT_EXCLUDE_RE@@'; | |
| 397 | + $xspeed_self = '' === $xspeed_hit_ua | |
| 398 | + || ! empty( $_SERVER['HTTP_X_XSPEED_SELF'] ) | |
| 399 | + || ( isset( $xspeed_meta['status'] ) && 404 === (int) $xspeed_meta['status'] ); | |
| 400 | + if ( ! $xspeed_self && '@@' !== substr( $xspeed_hit_ex, 0, 2 ) && '' !== $xspeed_hit_ex ) { | |
| 401 | + // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- a pattern this file did not compose is not worth a warning on every hit. | |
| 402 | + $xspeed_self = 1 === @preg_match( '#(' . $xspeed_hit_ex . ')#i', $xspeed_hit_ua ); | |
| 403 | + } | |
| 404 | + if ( ! $xspeed_self ) { | |
| 405 | + // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- pre-WP drop-in; WP_Filesystem isn't loaded. One short line, append + lock; failures are non-fatal (the ratio just under-counts). | |
| 406 | + @file_put_contents( $xspeed_hits_log, "hit\n", FILE_APPEND | LOCK_EX ); | |
| 407 | + } | |
| 344 | 408 | |
| 345 | 409 | // Replay the cached response's status + content-type from .meta, so a |
| 346 | 410 | // cached 404 serves 404 (not a soft-404 200) and a cached feed serves |
| 347 | 411 | // application/rss+xml (not text/html). (FBS-82406, FBS-82407) |