From 05754aa386b4a026433187b6124321e347a50600 Mon Sep 17 00:00:00 2001 From: Liam Sarsfield <43409125+LiamSarsfield@users.noreply.github.com> Date: Thu, 6 Aug 2026 10:19:03 +0100 Subject: [PATCH] Boost: ask the host before linking concatenated assets from the static cache The 404 tester's verdict travels with the database in the jetpack_boost_static_minification site option. A site migrated onto Atomic or WP Cloud can arrive with its previous host's 1, and whether a missing wp-content file 404s through WordPress there is a per-site platform setting Boost cannot read. The concatenators now ask jetpack_boost_minify_use_static_cache_urls(), which checks the host before the option, so those sites emit the /_jb_static/?? fallback instead of static cache URLs the web server may answer with a 404. --- .../plugins/boost/app/admin/class-config.php | 4 + .../app/lib/minify/class-concatenate-css.php | 2 +- .../app/lib/minify/class-concatenate-js.php | 2 +- .../app/lib/minify/functions-helpers.php | 41 ++++ ...st-608-static-minification-after-migration | 4 + projects/plugins/boost/phpunit.11.xml.dist | 2 + projects/plugins/boost/phpunit.9.xml.dist | 2 + .../Concatenate_Static_Cache_Urls_Test.php | 219 ++++++++++++++++++ 8 files changed, 274 insertions(+), 2 deletions(-) create mode 100644 projects/plugins/boost/changelog/fix-boost-608-static-minification-after-migration create mode 100644 projects/plugins/boost/tests/php/lib/minify/Concatenate_Static_Cache_Urls_Test.php diff --git a/projects/plugins/boost/app/admin/class-config.php b/projects/plugins/boost/app/admin/class-config.php index 36d5d207c455..e580310276c3 100644 --- a/projects/plugins/boost/app/admin/class-config.php +++ b/projects/plugins/boost/app/admin/class-config.php @@ -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. diff --git a/projects/plugins/boost/app/lib/minify/class-concatenate-css.php b/projects/plugins/boost/app/lib/minify/class-concatenate-css.php index 22b17bee3229..cf59d2594054 100644 --- a/projects/plugins/boost/app/lib/minify/class-concatenate-css.php +++ b/projects/plugins/boost/app/lib/minify/class-concatenate-css.php @@ -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; diff --git a/projects/plugins/boost/app/lib/minify/class-concatenate-js.php b/projects/plugins/boost/app/lib/minify/class-concatenate-js.php index e43c496ec0e3..48c2bdf23491 100644 --- a/projects/plugins/boost/app/lib/minify/class-concatenate-js.php +++ b/projects/plugins/boost/app/lib/minify/class-concatenate-js.php @@ -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; diff --git a/projects/plugins/boost/app/lib/minify/functions-helpers.php b/projects/plugins/boost/app/lib/minify/functions-helpers.php index 4b7ce146fb0b..aca634eae9d4 100644 --- a/projects/plugins/boost/app/lib/minify/functions-helpers.php +++ b/projects/plugins/boost/app/lib/minify/functions-helpers.php @@ -1,5 +1,6 @@ tests/php/lib/critical-css/Critical_CSS_Storage_Test.php tests/php/modules/optimizations/critical-css/CSS_Proxy_Test.php tests/php/Jetpack_Boost_Test.php + tests/php/lib/minify/Concatenate_Static_Cache_Urls_Test.php tests/php/modules/optimizations/lcp/LCP_Optimize_Bg_Image_Test.php tests/php/modules/optimizations/lcp/LCP_Optimize_Img_Tag_Test.php tests/php/abilities/Boost_Abilities_Test.php tests/php/Jetpack_Boost_Test.php + tests/php/lib/minify/Concatenate_Static_Cache_Urls_Test.php tests/php/lib/critical-css/Display_Critical_CSS_Test.php tests/php/lib/critical-css/Critical_CSS_Storage_Test.php tests/php/modules/optimizations/critical-css/CSS_Proxy_Test.php diff --git a/projects/plugins/boost/phpunit.9.xml.dist b/projects/plugins/boost/phpunit.9.xml.dist index 30140ce16855..1b0ccf1d1c67 100644 --- a/projects/plugins/boost/phpunit.9.xml.dist +++ b/projects/plugins/boost/phpunit.9.xml.dist @@ -16,12 +16,14 @@ tests/php/lib/critical-css/Critical_CSS_Storage_Test.php tests/php/modules/optimizations/critical-css/CSS_Proxy_Test.php tests/php/Jetpack_Boost_Test.php + tests/php/lib/minify/Concatenate_Static_Cache_Urls_Test.php tests/php/modules/optimizations/lcp/LCP_Optimize_Bg_Image_Test.php tests/php/modules/optimizations/lcp/LCP_Optimize_Img_Tag_Test.php tests/php/abilities/Boost_Abilities_Test.php tests/php/Jetpack_Boost_Test.php + tests/php/lib/minify/Concatenate_Static_Cache_Urls_Test.php tests/php/lib/critical-css/Display_Critical_CSS_Test.php tests/php/lib/critical-css/Critical_CSS_Storage_Test.php tests/php/modules/optimizations/critical-css/CSS_Proxy_Test.php diff --git a/projects/plugins/boost/tests/php/lib/minify/Concatenate_Static_Cache_Urls_Test.php b/projects/plugins/boost/tests/php/lib/minify/Concatenate_Static_Cache_Urls_Test.php new file mode 100644 index 000000000000..67e06d27f321 --- /dev/null +++ b/projects/plugins/boost/tests/php/lib/minify/Concatenate_Static_Cache_Urls_Test.php @@ -0,0 +1,219 @@ +asset_dir = WP_CONTENT_DIR . '/boost-concat-test'; + if ( ! is_dir( $this->asset_dir ) ) { + mkdir( $this->asset_dir, 0755, true ); + } + + $this->asset_files = array(); + foreach ( array( + 'a.css' => '.a{color:red}', + 'b.css' => '.b{color:blue}', + 'a.js' => 'var a = 1;', + 'b.js' => 'var b = 2;', + ) as $name => $contents ) { + $path = $this->asset_dir . '/' . $name; + file_put_contents( $path, $contents ); + $this->asset_files[] = $path; + } + + // Both classes derive the site URL from the base_url WordPress hands them, which points at + // wp-includes here. Pin it, or nothing resolves as internal and every asset is skipped. + $this->site_url_filter = function () { + return site_url(); + }; + add_filter( 'page_optimize_site_url', $this->site_url_filter ); + } + + public function tear_down() { + if ( $this->site_url_filter ) { + remove_filter( 'page_optimize_site_url', $this->site_url_filter ); + } + + // Remove exactly the files set_up() wrote, then the directory if that emptied it. Tracking + // paths rather than ownership means a part-way run cannot leave the fixture behind. + foreach ( $this->asset_files as $file ) { + if ( file_exists( $file ) ) { + unlink( $file ); + } + } + if ( is_dir( $this->asset_dir ) ) { + @rmdir( $this->asset_dir ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- best effort; a non-empty directory holds something this test did not create. + } + + Constants::clear_constants(); + Status_Cache::clear(); + delete_site_option( 'jetpack_boost_static_minification' ); + + parent::tear_down(); + } + + /** + * Put the site on WP Cloud, where the web server may answer wp-content 404s itself. + */ + private function pretend_to_be_on_wp_cloud() { + Constants::set_constant( 'ATOMIC_SITE_ID', 1 ); + Constants::set_constant( 'ATOMIC_CLIENT_ID', 1 ); + Status_Cache::clear(); + } + + private function render_styles() { + $styles = new Concatenate_CSS( new WP_Styles() ); + $styles->add( 'boost-test-a', '/wp-content/boost-concat-test/a.css', array(), null ); + $styles->add( 'boost-test-b', '/wp-content/boost-concat-test/b.css', array(), null ); + $styles->enqueue( array( 'boost-test-a', 'boost-test-b' ) ); + + // finally, so a throw inside do_items() surfaces as that failure rather than as a leaked + // buffer tripping beStrictAboutOutputDuringTests. + ob_start(); + try { + $styles->do_items(); + } finally { + $output = ob_get_clean(); + } + + return $output; + } + + private function render_scripts() { + $scripts = new Concatenate_JS( new WP_Scripts() ); + $scripts->add( 'boost-test-a', '/wp-content/boost-concat-test/a.js', array(), null ); + $scripts->add( 'boost-test-b', '/wp-content/boost-concat-test/b.js', array(), null ); + $scripts->enqueue( array( 'boost-test-a', 'boost-test-b' ) ); + + ob_start(); + try { + $scripts->do_items(); + } finally { + $output = ob_get_clean(); + } + + return $output; + } + + /** + * The migrated database says the 404 tester passed, but it passed on the previous host. + */ + public function test_css_falls_back_when_a_stale_verdict_arrives_on_wp_cloud() { + update_site_option( 'jetpack_boost_static_minification', 1 ); + $this->pretend_to_be_on_wp_cloud(); + + $output = $this->render_styles(); + + $this->assertStringContainsString( '/_jb_static/??', $output ); + $this->assertStringNotContainsString( '/boost-cache/static/', $output ); + } + + public function test_js_falls_back_when_a_stale_verdict_arrives_on_wp_cloud() { + update_site_option( 'jetpack_boost_static_minification', 1 ); + $this->pretend_to_be_on_wp_cloud(); + + $output = $this->render_scripts(); + + $this->assertStringContainsString( '/_jb_static/??', $output ); + $this->assertStringNotContainsString( '/boost-cache/static/', $output ); + } + + /** + * Hosts that can serve the static cache keep doing so. The guard only moves a site onto the + * fallback, never the other way. + */ + public function test_css_uses_static_cache_urls_on_a_supported_host() { + update_site_option( 'jetpack_boost_static_minification', 1 ); + + $output = $this->render_styles(); + + $this->assertStringContainsString( '/boost-cache/static/', $output ); + $this->assertStringContainsString( '.min.css', $output ); + $this->assertStringNotContainsString( '/_jb_static/??', $output ); + } + + public function test_js_uses_static_cache_urls_on_a_supported_host() { + update_site_option( 'jetpack_boost_static_minification', 1 ); + + $output = $this->render_scripts(); + + $this->assertStringContainsString( '/boost-cache/static/', $output ); + $this->assertStringContainsString( '.min.js', $output ); + $this->assertStringNotContainsString( '/_jb_static/??', $output ); + } + + public function test_css_falls_back_on_a_supported_host_when_the_tester_failed() { + update_site_option( 'jetpack_boost_static_minification', 0 ); + + $output = $this->render_styles(); + + $this->assertStringContainsString( '/_jb_static/??', $output ); + $this->assertStringNotContainsString( '/boost-cache/static/', $output ); + } + + public function test_js_falls_back_on_a_supported_host_when_the_tester_failed() { + update_site_option( 'jetpack_boost_static_minification', 0 ); + + $output = $this->render_scripts(); + + $this->assertStringContainsString( '/_jb_static/??', $output ); + $this->assertStringNotContainsString( '/boost-cache/static/', $output ); + } +}