PluginProbe
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN / 1.3.3
xSpeed Cache: AI-Powered Performance Hub with MCP, Caching & CDN v1.3.3
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 1.1.5 1.1.6 1.1.7 1.1.8 All 29 releases
← All changes | includes/modules/Cache/CacheModule.php +136 -3 1.3.01.3.3 View file →
@@ -170,9 +170,11 @@
170 170 '/xmlrpc.php',
171 171 '~wp-.*\.php',
172 172 '/feed/',
173 173 'index.php',
174 - '~sitemap(_index)?\.xml',
174 + // `sitemaps?` — SEOPress generates sitemaps.xml (plural).
175 + '~sitemaps?(_index)?\.xml',
176 + '/robots.txt',
175 177 // Bare (no trailing slash) so "contains" matches both
176 178 // /cart and /cart/items — WooCommerce serves both forms.
177 179 '/cart',
178 180 '/checkout',
@@ -228,8 +230,53 @@
228 230 'default' => false,
229 231 'label' => __( 'Separate Mobile Cache', 'xspeed' ),
230 232 'description' => __( 'Keep mobile and desktop responses in separate cache buckets. Turn on for AMP, mobile-specific themes (WPtouch / Jetpack mobile theme), or any setup that serves different HTML by device.', 'xspeed' ),
231 233 ),
234 + 'edge_provider' => array(
235 + 'type' => 'enum',
236 + 'default' => 'auto',
237 + 'options' => array( 'auto', 'off', 'cloudflare', 'fastly', 'varnish', 'nginx', 'akamai', 'cloudfront', 'google', 'keycdn', 'bunny', 'sucuri', 'incapsula', 'generic', 'custom' ),
238 + 'option_labels' => array(
239 + 'auto' => 'Detect automatically',
240 + 'off' => 'Off — send nothing',
241 + 'cloudflare' => 'Cloudflare',
242 + 'varnish' => 'Varnish',
243 + 'nginx' => 'nginx proxy cache',
244 + 'cloudfront' => 'Amazon CloudFront',
245 + 'google' => 'Google Cloud CDN',
246 + 'keycdn' => 'KeyCDN',
247 + 'bunny' => 'Bunny',
248 + // These four cannot be presented as supported on the same
249 + // footing as the ones above. Vendor documentation either
250 + // does not establish that they honour what we send, or
251 + // establishes that they ignore origin cache headers until
252 + // the property is configured to respect them — Akamai
253 + // caches for a theoretically infinite time by default, and
254 + // Sucuri's default caching level ignores the headers
255 + // outright. Naming them without the caveat would promise a
256 + // protection the CDN is not currently giving.
257 + 'fastly' => 'Fastly (needs CDN configuration)',
258 + 'akamai' => 'Akamai (needs CDN configuration)',
259 + 'sucuri' => 'Sucuri (needs CDN configuration)',
260 + 'incapsula' => 'Imperva / Incapsula (needs CDN configuration)',
261 + 'generic' => 'Something else',
262 + 'custom' => 'Custom headers',
263 + ),
264 + 'label' => __( 'Cache In Front Of This Site', 'xspeed' ),
265 + 'description' => __( 'Ask a CDN or proxy in front of your site not to store pages xSpeed refused to cache. Leave it on Detect automatically unless you know what is in front of you; the marked providers ignore origin headers until you configure them to respect it.', 'xspeed' ),
266 + 'info_title' => __( 'Cache in front of this site', 'xspeed' ),
267 + 'info' => __( 'Naming your provider narrows the headers to the one it reads. Detect automatically works it out per request and otherwise sends a set every cache ignores unless it understands it, so it is safe not to know. Run "wp xspeed cache edge" to see what was detected and what gets sent.', 'xspeed' )
268 + ),
269 + 'edge_custom_headers' => array(
270 + 'type' => 'list',
271 + 'default' => array(),
272 + 'item_type' => 'string',
273 + 'label' => __( 'Custom Edge Headers', 'xspeed' ),
274 + 'dependsOn' => array( 'field' => 'edge_provider', 'value' => 'custom' ),
275 + 'description' => __( 'One header per line, as Name: value — for example "Surrogate-Control: no-store". Lines starting with # are ignored.', 'xspeed' ),
276 + 'info_title' => __( 'Custom edge headers', 'xspeed' ),
277 + 'info' => __( 'These replace the headers xSpeed would have picked for your CDN. Two baselines are still added underneath: a Cache-Control, and "X-Accel-Expires: 0" for a page cache running in nginx on your own server. Name either one yourself and yours is used instead. Values containing $, % or a backslash are dropped — the same pairs go into nginx and Apache directives, where those cannot be escaped safely. Content-Length, Content-Encoding, Content-Type, Transfer-Encoding, Set-Cookie and Location are refused.', 'xspeed' )
278 + ),
232 279 );
233 280 }
234 281
235 282 /**
@@ -416,14 +463,14 @@
416 463 ),
417 464 array(
418 465 'name' => 'xspeed cache',
419 466 'callback' => array( $this, 'cli_handler' ),
420 - 'shortdesc' => 'Inspect the Cache module: `status` (settings), `inventory` (which pages are cached, and how old), `size` (where the disk usage goes), `purge-log` (what cleared the cache, when and why), `purge-url <url>` to clear one page, `recheck-rewrite` to re-run the static-rewrite probe, or `nginx-config` to print the unified nginx server-block for pasting into a vhost. To clear the whole site use `wp xspeed purge`.',
467 + 'shortdesc' => 'Inspect the Cache module: `status` (settings), `inventory` (which pages are cached, and how old), `size` (where the disk usage goes), `purge-log` (what cleared the cache, when and why), `purge-url <url>` to clear one page, `recheck-rewrite` to re-run the static-rewrite probe, or `nginx-config` to print the unified nginx server-block for pasting into a vhost, or `edge` to show which cache is in front of the site and what xSpeed tells it. To clear the whole site use `wp xspeed purge`.',
421 468 'synopsis' => array(
422 469 array(
423 470 'type' => 'positional',
424 471 'name' => 'action',
425 - 'options' => array( 'status', 'inventory', 'size', 'purge-log', 'purge-url', 'recheck-rewrite', 'nginx-config' ),
472 + 'options' => array( 'status', 'inventory', 'size', 'purge-log', 'purge-url', 'recheck-rewrite', 'nginx-config', 'edge' ),
426 473 'optional' => true,
427 474 ),
428 475 array(
429 476 'type' => 'positional',
@@ -832,13 +879,99 @@
832 879 $this->cli_purge_log( $limit );
833 880 return;
834 881 }
835 882
883 + if ( 'edge' === $action ) {
884 + $this->cli_edge();
885 + return;
886 + }
887 +
836 888 $opts = Settings_Manager::get( self::SLUG );
837 889 \WP_CLI::log( 'cache_expiry ' . $opts['cache_expiry'] . 'h' );
838 890 \WP_CLI::log( 'excluded_urls ' . count( $opts['excluded_urls'] ) . ' entries' );
839 891 foreach ( $opts['excluded_urls'] as $u ) {
840 892 \WP_CLI::log( ' - ' . $u );
893 + }
894 + $edge = \XSpeed\Edge_Provider::detect();
895 + \WP_CLI::log( 'edge ' . ( '' !== $edge['provider'] ? $edge['provider'] : $edge['confidence'] ) . ' (' . $edge['source'] . ')' );
896 + }
897 +
898 + /**
899 + * `wp xspeed cache edge` — what we think is in front, and what we say to it.
900 + *
901 + * Worth printing even when nothing is held back. "You are behind
902 + * Cloudflare, and a Cache Rule set to ignore origin headers overrides
903 + * anything xSpeed sends" is the answer to a support question that
904 + * otherwise costs someone a week, and it is true whether or not a hold
905 + * ever fires.
906 + */
907 + private function cli_edge(): void {
908 + $answer = \XSpeed\Edge_Provider::detect();
909 +
910 + \WP_CLI::log( 'provider ' . ( '' !== $answer['provider'] ? $answer['provider'] : '(none named)' ) );
911 + \WP_CLI::log( 'confidence ' . $answer['confidence'] );
912 + \WP_CLI::log( 'source ' . $answer['source'] );
913 +
914 + // A pin outranks detection by design, so nothing re-checks it on the
915 + // site's behalf. Saying the two disagree is the whole mechanism by
916 + // which a site that changed CDN ever finds out.
917 + $sniffed = \XSpeed\Edge_Provider::sniffed();
918 + if ( in_array( $answer['source'], array( 'setting', 'constant', 'filter' ), true )
919 + && '' !== $sniffed['provider']
920 + && $sniffed['provider'] !== $answer['provider'] ) {
921 + \WP_CLI::warning(
922 + sprintf(
923 + 'This request looks like %s, but the provider is pinned to %s. If the site moved, change it — the pinned answer is also baked into the drop-in and the server rules.',
924 + $sniffed['provider'],
925 + '' !== $answer['provider'] ? $answer['provider'] : 'off'
926 + )
927 + );
928 + }
929 +
930 + if ( \XSpeed\Edge_Provider::is_off( $answer ) ) {
931 + \WP_CLI::log( '' );
932 + \WP_CLI::log( 'Nothing is sent: this is switched off.' );
933 + return;
934 + }
935 +
936 + // Resolved through edge_headers_for() rather than straight off the
937 + // provider, so this prints what the serve path would ACTUALLY send —
938 + // including `X-XSpeed-Edge-Hold`, and including the evidence gate.
939 + // Listing the provider's raw set ignored that gate and told operators
940 + // a first render would be held on a site where it would not be.
941 + //
942 + // `bake`, not `request`. Two reasons, and the second one matters:
943 + // this command answers for the site rather than for one response, and
944 + // `request` fires `xspeed_edge_optimization_pending`, whose Pro
945 + // listener resolves the CSS plan — which by its own description is
946 + // what queues a build. A read-only command must not burn a build
947 + // slot, quarantine an entry or purge a page just by being run, and
948 + // under WP-CLI it would do all three against the home page.
949 + $bypass = \XSpeed\Cache::edge_headers_for( 'BYPASS', 'bake', 'logged-in' );
950 + $miss = \XSpeed\Cache::edge_headers_for( 'MISS', 'bake' );
951 +
952 + \WP_CLI::log( '' );
953 + \WP_CLI::log( 'On a page xSpeed refuses to cache (a cart, a logged-in view):' );
954 + foreach ( $bypass as $name => $value ) {
955 + \WP_CLI::log( sprintf( ' %s: %s', $name, $value ) );
956 + }
957 +
958 + \WP_CLI::log( '' );
959 + if ( array() === $miss ) {
960 + \WP_CLI::log( 'On a first render: nothing. A MISS is a performance hedge, so it is held only where a cache in front was detected — and none was. Name the provider in Cache In Front Of This Site to cover first renders too.' );
961 + } else {
962 + \WP_CLI::log( 'On a first render:' );
963 + foreach ( $miss as $name => $value ) {
964 + \WP_CLI::log( sprintf( ' %s: %s', $name, $value ) );
965 + }
966 + }
967 +
968 + \WP_CLI::log( '' );
969 + \WP_CLI::log( 'X-XSpeed-Edge-Hold names why a response was held: bypass, bypass-shape, miss, mobile-split or pending. No header means nothing was held.' );
970 +
971 + if ( 'cloudflare' === $answer['provider'] ) {
972 + \WP_CLI::log( '' );
973 + \WP_CLI::log( 'A Cloudflare Cache Rule whose Edge TTL is "Ignore cache-control header and use this TTL" overrides all of the above. Use "Respect origin TTL" on that rule if pages are still being stored.' );
841 974 }
842 975 }
843 976
844 977 /** `wp xspeed cache inventory [--limit=N]` — which pages are cached, and how old. */