Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
991a14f
Boost: let the host decide whether static cache URLs can be used
LiamSarsfield Jul 31, 2026
9fc11f5
Boost: guard the 404 tester and cover the emitted concat URLs
LiamSarsfield Jul 31, 2026
b38c791
Boost: keep the 404 tester scheduled on every host, and fix two false…
LiamSarsfield Aug 1, 2026
3ea8df1
Boost: normalize the static prefix in the early router, and purge cac…
LiamSarsfield Aug 4, 2026
441cc36
Boost: announce the dropped verdict from the upgrade path, and ignore…
LiamSarsfield Aug 4, 2026
8716984
Boost: require a concat payload in the early router, and stop announc…
LiamSarsfield Aug 4, 2026
01bfb27
Boost: polyfill str_starts_with for the unit suite, and fix the comme…
LiamSarsfield Aug 4, 2026
8be8f57
Boost: stop writing a placeholder verdict, and anchor the 404 rebuild…
LiamSarsfield Aug 4, 2026
1916a65
Boost: fix the red PHPCompatibility check, and deprecate the router h…
LiamSarsfield Aug 4, 2026
486c133
Boost: make the concatenation test self-contained, and correct three …
LiamSarsfield Aug 4, 2026
d7a19b9
Boost: reclaim the 404 marker on WP Cloud, and restore the admin gate…
LiamSarsfield Aug 5, 2026
e45e9de
Boost: fix the red PHP 7.2 test lane, and stop the tester's disable c…
LiamSarsfield Aug 5, 2026
0c8f64e
Boost: fold the 404 marker reclaim into the forget helper, and correc…
LiamSarsfield Aug 5, 2026
6370f33
Boost: drop two false claims from the forget helper, and anchor the o…
LiamSarsfield Aug 5, 2026
3a7860a
Boost: cut the BOOST-608 comments down to what a maintainer needs
LiamSarsfield Aug 5, 2026
686c053
Boost: name the mechanism the host check rests on
LiamSarsfield Aug 5, 2026
b37e5aa
Boost: correct seven comment claims the code does not support
LiamSarsfield Aug 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions projects/plugins/boost/app/admin/class-config.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ private static function get_custom_post_types() {
* Retrieves the hosting provider.
* We're only interested in 'atomic' or 'woa' for now.
*
* A new value here also changes CSS and JS delivery:
* jetpack_boost_minify_host_handles_wp_content_404s() reads anything but 'other' as a host that
* answers wp-content 404s itself, and opts it out of static cache URLs for good.
*
* @since 3.10.0
*
* @return string The hosting provider.
Expand Down
8 changes: 6 additions & 2 deletions projects/plugins/boost/app/class-jetpack-boost.php
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,12 @@ public function schedule_version_change() {
}

public function handle_version_change() {
// Remove this option to prevent the notice from showing up.
delete_site_option( 'jetpack_boost_static_minification' );
// Remove this option to prevent the notice from showing up. This runs before
// jetpack_boost_minify_activation() below, so route the delete through the helper: otherwise
// the activation path finds the row gone and nothing announces the change in emitted URLs.
// It is also the only cleanup a migrated site reaches with both minify modules off, which is
// why the marker drop lives in the helper rather than in the tester paths alone.
jetpack_boost_minify_forget_static_cache_verdict();

// Add upgrade check for Cornerstone Pages.
$pages = jetpack_boost_ds_get( 'cornerstone_pages_list' );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ public function do_items( $handles = false, $group = false ) {
foreach ( $css_groups as $css_group ) {
$file_name = jetpack_boost_page_optimize_generate_concat_path( $css_group, $this->dependency_path_mapping );

if ( get_site_option( 'jetpack_boost_static_minification' ) ) {
if ( jetpack_boost_minify_use_static_cache_urls() ) {
$href = jetpack_boost_get_minify_url( $file_name . '.min.css' );
} else {
$href = $siteurl . jetpack_boost_get_static_prefix() . '??' . $file_name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ public function do_items( $handles = false, $group = false ) {
if ( isset( $js_array['paths'] ) && count( $js_array['paths'] ) > 1 ) {
$file_name = jetpack_boost_page_optimize_generate_concat_path( $js_array['paths'], $this->dependency_path_mapping );

if ( get_site_option( 'jetpack_boost_static_minification' ) ) {
if ( jetpack_boost_minify_use_static_cache_urls() ) {
$href = jetpack_boost_get_minify_url( $file_name . '.min.js' );
} else {
$href = $siteurl . jetpack_boost_get_static_prefix() . '??' . $file_name;
Expand Down
127 changes: 108 additions & 19 deletions projects/plugins/boost/app/lib/minify/functions-helpers.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?php

use Automattic\Jetpack_Boost\Admin\Config as Boost_Admin_Config;
use Automattic\Jetpack_Boost\Lib\Minify\Cleanup_Stored_Paths;
use Automattic\Jetpack_Boost\Lib\Minify\Config;
use Automattic\Jetpack_Boost\Lib\Minify\Dependency_Path_Mapping;
Expand Down Expand Up @@ -480,44 +481,132 @@ function jetpack_boost_page_optimize_cache_bust_mtime( $path, $siteurl ) {
}

/**
* Get the URL prefix for static minify/concat resources. Defaults to /_jb_static/, but can be
* overridden by defining JETPACK_BOOST_STATIC_PREFIX.
* Get the URL prefix for static minify/concat resources.
*
* Defaults to /_jb_static/. JETPACK_BOOST_STATIC_PREFIX overrides it, unless the value resolves to
* the site root. See the branch below.
*
* @return string The prefix, with one leading and one trailing slash.
*/
function jetpack_boost_get_static_prefix() {
$prefix = defined( 'JETPACK_BOOST_STATIC_PREFIX' ) ? JETPACK_BOOST_STATIC_PREFIX : '/_jb_static/';
$default = '/_jb_static/';
$prefix = defined( 'JETPACK_BOOST_STATIC_PREFIX' ) ? JETPACK_BOOST_STATIC_PREFIX : $default;

if ( ! str_starts_with( $prefix, '/' ) ) {
$prefix = '/' . $prefix;
}

return trailingslashit( $prefix );
}
$prefix = trailingslashit( $prefix );

function jetpack_boost_get_minify_url( $file_name = '' ) {
return content_url( '/boost-cache/static/' . $file_name );
// '', '/' and '//' canonicalize to '/'. Callers match this against the end of the request path,
// so '/' claims every URL ending in a slash, including the home page. Treat those as unset.
if ( '/' === $prefix ) {
return $default;
}

return $prefix;
}

function jetpack_boost_get_minify_file_path( $file_name = '' ) {
return WP_CONTENT_DIR . '/boost-cache/static/' . $file_name;
/**
* Whether a request is for a URL the minify service should serve.
*
* The router in jetpack-boost.php ends in exit(), so the decision lives here where a test can reach
* it. The path must end with the prefix, because Boost emits URLs relative to the site URL. The query
* must be the ??<files> payload the concatenators emit, or a site with the prefix set to `assets`
* loses each page whose path ends in /assets/ to a 400 that WordPress never sees.
*
* @since $$next-version$$
*
* @param string $request_uri Request URI, query string included.
* @return bool True if the minify service should handle this request.
*/
function jetpack_boost_minify_request_is_for_static_prefix( $request_uri ) {
$prefix = jetpack_boost_get_static_prefix();
$parts = explode( '?', $request_uri, 2 );

if ( $prefix !== substr( $parts[0], -strlen( $prefix ) ) ) {
return false;
}

// The length test rejects a bare `<prefix>??`, which names no bundle.
return isset( $parts[1] ) && strlen( $parts[1] ) > 1 && str_starts_with( $parts[1], '?' );
}

/**
* Detects requests within the `/_jb_static/` directory, and serves minified content.
*
* Nothing in Boost calls this since the router moved into jetpack-boost.php, which runs while
* WordPress loads plugins. It is a jetpack_-prefixed global that shipped in released tags, so it
* stays for the deprecation window in docs/coding-guidelines.md. It now uses the corrected
* predicate, and like the original it does not return once it decides to serve.
*
* @deprecated $$next-version$$ Boost dispatches this from jetpack-boost.php, before the query loads.
*
* @return void
*/
function jetpack_boost_minify_serve_concatenated() {
// Potential improvement: Make concat URL dir configurable
if ( isset( $_SERVER['REQUEST_URI'] ) ) {
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$request_path = explode( '?', wp_unslash( $_SERVER['REQUEST_URI'] ) )[0];
$prefix = jetpack_boost_get_static_prefix();
if ( $prefix === substr( $request_path, -strlen( $prefix ), strlen( $prefix ) ) ) {
require_once __DIR__ . '/functions-service-fallback.php';
jetpack_boost_page_optimize_service_request();
exit( 0 ); // @phan-suppress-current-line PhanPluginUnreachableCode -- Safer to include it even though jetpack_boost_page_optimize_service_request() itself never returns.
}
_deprecated_function( __FUNCTION__, 'jetpack-boost-$$next-version$$' );

if ( ! isset( $_SERVER['REQUEST_URI'] ) ) {
return;
}

// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
if ( ! jetpack_boost_minify_request_is_for_static_prefix( wp_unslash( $_SERVER['REQUEST_URI'] ) ) ) {
return;
}

require_once __DIR__ . '/functions-service-fallback.php';
jetpack_boost_page_optimize_service_request();
exit( 0 ); // @phan-suppress-current-line PhanPluginUnreachableCode -- Safer to include it even though jetpack_boost_page_optimize_service_request() itself never returns.
}

/**
* Whether the host routes requests for missing wp-content files through WordPress.
*
* Static cache URLs depend on that: the first request 404s, and Boost builds the file while it
* answers the 404. Whether a missing .css or .js reaches WordPress is a per-site platform setting on
* Atomic and WP Cloud, and Boost cannot read it, so it does not use static cache URLs there.
*
* The name describes the condition, not the feature, to keep it apart from
* Minify\Config::can_use_static_cache(), which asks whether the cache directory is writable.
*
* Admin\Config::get_hosting_provider() returns 'other' for any host it does not name, so a host it
* does not recognize keeps the static cache. A new named value there opts that provider out for
* good, because the tester paths then skip the probe.
*
* @since $$next-version$$
*
* @return bool True if WordPress sees wp-content 404s, false if the web server answers them.
*/
function jetpack_boost_minify_host_handles_wp_content_404s() {
return 'other' === Boost_Admin_Config::get_hosting_provider();
}

/**
* Whether concatenated files should be linked from the static cache directory.
*
* The 404 tester's verdict describes the host, but jetpack_boost_static_minification travels with
* the database: a migrated site arrives with its previous host's `1`. Ask the host first.
*
* @since $$next-version$$
*
* @return bool True if static cache URLs should be used, false to fall back to /_jb_static/.
*/
function jetpack_boost_minify_use_static_cache_urls() {
if ( ! jetpack_boost_minify_host_handles_wp_content_404s() ) {
return false;
}

return (bool) get_site_option( 'jetpack_boost_static_minification' );
}

function jetpack_boost_get_minify_url( $file_name = '' ) {
return content_url( '/boost-cache/static/' . $file_name );
}

function jetpack_boost_get_minify_file_path( $file_name = '' ) {
return WP_CONTENT_DIR . '/boost-cache/static/' . $file_name;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,9 @@ function jetpack_boost_page_optimize_get_file_paths( $args ) {

// It's a base64 encoded list of file path.
// e.g.: /_jb_static/??-eJzTT8vP109KLNJLLi7W0QdyDEE8IK4CiVjn2hpZGluYmKcDABRMDPM=
if ( '-' === $args[0] ) {
// The empty test is this function's own: the static cache 404 handler also reaches here, and a
// request for `<static cache>/.js` gives an empty file name.
if ( '' !== $args && '-' === $args[0] ) {

// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged,WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
$args = @gzuncompress( base64_decode( substr( $args, 1 ) ) );
Expand Down
89 changes: 76 additions & 13 deletions projects/plugins/boost/app/lib/minify/functions-service.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<?php

use Automattic\Jetpack_Boost\Admin\Config as Boost_Admin_Config;
use Automattic\Jetpack_Boost\Lib\Minify;
use Automattic\Jetpack_Boost\Lib\Minify\Config;
use Automattic\Jetpack_Boost\Lib\Minify\File_Paths;
Expand Down Expand Up @@ -82,6 +81,48 @@ function jetpack_boost_404_tester_cron() {
jetpack_boost_404_tester();
}

/**
* Drop a static minification verdict, and announce it if the emitted URLs change as a result.
*
* The verdict travels with the database, so a site migrated onto Atomic or WP Cloud can arrive with
* one that was true of its previous host. Deleting rather than zeroing keeps the legacy notice quiet,
* because "never tested" is the truth here. The probe marker travels with the filesystem, so this
* drops it too: left behind, it makes the next probe record 1 for a delivery method the host cannot
* serve, including a probe in a Boost release old enough to have no host check.
*
* The delete is unconditional. The announcement needs a truthy verdict, the only kind that pointed
* renders at the static cache, and an unsupported host, because elsewhere WordPress still sees
* wp-content 404s and loader.php rebuilds each bundle from its File_Paths entry. Even where it fires,
* it reaches Boost's page cache and the WP Super Cache shim but no platform edge cache, so a site
* already showing the bug still wants one manual edge purge.
*
* @since $$next-version$$
*
* @return void
*/
function jetpack_boost_minify_forget_static_cache_verdict() {
$verdict = get_site_option( 'jetpack_boost_static_minification' );

delete_site_option( 'jetpack_boost_static_minification' );

if ( jetpack_boost_minify_host_handles_wp_content_404s() ) {
return;
}

$marker = Config::get_static_cache_dir_path() . '/404';
if ( file_exists( $marker ) ) {
wp_delete_file( $marker );
}

// Announced last: this fires third-party listener code, and both drops above must happen first.
if ( ! $verdict ) {
return;
}

/** This action is documented in app/modules/class-module.php */
do_action( 'jetpack_boost_page_output_changed' );
}

/**
* This function is used to test if is_404() is working in wp-content/
* It sends a request to a non-existent URL, that will execute the 404 handler
Expand All @@ -90,20 +131,41 @@ function jetpack_boost_404_tester_cron() {
* The constant JETPACK_BOOST_STATIC_CACHE_404_TESTER_PATH is the path to the file that will be requested.
*
* This function is called when the Minify_CSS or Minify_JS module is activated, and once per day.
*
* On Atomic and WP Cloud the answer is known in advance, so this skips the loopback request and
* clears any stale verdict and marker instead. That cleanup sits above the
* JETPACK_BOOST_DISABLE_404_TESTER guard: the constant turns off a request this branch never sends.
*
* @return int|null 1 or 0 once probed, null when the probe was skipped.
*/
function jetpack_boost_404_tester() {
// The answer is known on Atomic and WP Cloud, so skip the loopback. This also covers a cron event
// inherited from a previous host, because the schedule travels with the database.
if ( ! jetpack_boost_minify_host_handles_wp_content_404s() ) {
jetpack_boost_minify_forget_static_cache_verdict();

return;
}

if ( defined( 'JETPACK_BOOST_DISABLE_404_TESTER' ) && JETPACK_BOOST_DISABLE_404_TESTER ) {
return;
}

// Read where it is written, not through the helper above, which would delete the verdict this is
// about to write. Here a marker can answer the loopback below, or be left by an earlier request
// for the tester path.
$marker = Config::get_static_cache_dir_path() . '/404';
$minification_enabled = '';
wp_remote_get( home_url( JETPACK_BOOST_STATIC_CACHE_404_TESTER_PATH ) );
if ( file_exists( Config::get_static_cache_dir_path() . '/404' ) ) {
wp_delete_file( Config::get_static_cache_dir_path() . '/404' );
if ( file_exists( $marker ) ) {
wp_delete_file( $marker );
$minification_enabled = 1;
} else {
$minification_enabled = 0;
}

// A 1 -> 0 flip changes the emitted URLs but announces nothing. Cached HTML keeps working through
// loader.php here, and a failed loopback looks the same as a host that changed behaviour.
update_site_option( 'jetpack_boost_static_minification', $minification_enabled );

return $minification_enabled;
Expand All @@ -112,23 +174,24 @@ function jetpack_boost_404_tester() {
add_action( 'jetpack_boost_404_tester_cron', 'jetpack_boost_404_tester_cron' );

/**
* Setup the 404 tester.
* Setup the 404 tester, and drop a verdict inherited from a different host.
*
* Schedule the 404 tester if the concatenation modules
* haven't been toggled since this feature was released.
* Only run this in wp-admin to avoid excessive updates to the option.
*/
function jetpack_boost_404_setup() {
// If we're on Atomic or Woa, don't setup the 404 tester.
if ( in_array( Boost_Admin_Config::get_hosting_provider(), array( 'atomic', 'woa' ), true ) ) {
return;
}
// Schedule the daily tester on every host. jetpack_boost_404_tester() short-circuits where the
// static cache cannot work, and keeping the event on the books lets a site that later migrates
// onto a supported host re-test. Jetpack_Boost::handle_version_change() clears and recreates the
// event on upgrade, but only while a minify module is on.
jetpack_boost_page_optimize_schedule_404_tester();

if ( is_admin() && get_site_option( 'jetpack_boost_static_minification', 'na' ) === 'na' ) {
update_site_option( 'jetpack_boost_static_minification', 0 ); // Add a default value if not set to avoid an extra SQL query.
// No verdict is worth storing on Atomic or WP Cloud, so drop one left by a previous host. Nothing
// follows this branch: setup used to write a placeholder 0 here, which show_legacy_notice() reads
// as a measurement and reports as slow delivery before anything measures it.
if ( ! jetpack_boost_minify_host_handles_wp_content_404s() ) {
jetpack_boost_minify_forget_static_cache_verdict();
}

jetpack_boost_page_optimize_schedule_404_tester();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ public static function show_legacy_notice() {
return false;
}

// No notice where the host cannot serve the static cache at all.
if ( ! jetpack_boost_minify_host_handles_wp_content_404s() ) {
return false;
}

// If the static minfification has not ran yet, don't show the legacy notice.
$static_minification_enabled = get_site_option( 'jetpack_boost_static_minification', 'na' );
if ( $static_minification_enabled === 'na' ) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: fixed

Concatenate JS/CSS: Don't offer the legacy delivery method notice until the site's delivery method has actually been tested.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: minor
Type: deprecated

Concatenate JS/CSS: Deprecate jetpack_boost_minify_serve_concatenated(), an unused duplicate of the minify request router.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: fixed

Concatenate JS/CSS: Ignore a JETPACK_BOOST_STATIC_PREFIX that resolves to the site root, and only claim requests that carry a concatenation payload, so ordinary pages are never served by the minify service.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: fixed

Concatenate JS/CSS: Fix broken CSS and JS delivery on pages rendered after a site is migrated onto WP Cloud, and re-test for the faster delivery method automatically after a site migrates back off it. Pages already served from a platform edge cache keep the old URLs until that cache is purged, which Boost cannot do for you.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: fixed

Concatenate JS/CSS: Serve concatenated files when JETPACK_BOOST_STATIC_PREFIX is defined without slashes.
Loading
Loading