diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php index 801da32c21..44e9263b78 100644 --- a/src/wp-includes/script-loader.php +++ b/src/wp-includes/script-loader.php @@ -2701,6 +2701,7 @@ function wp_maybe_inline_styles() { if ( wp_styles()->get_data( $handle, 'path' ) && file_exists( $wp_styles->registered[ $handle ]->extra['path'] ) ) { $styles[] = array( 'handle' => $handle, + 'src' => $wp_styles->registered[ $handle ]->src, 'path' => $wp_styles->registered[ $handle ]->extra['path'], 'size' => filesize( $wp_styles->registered[ $handle ]->extra['path'] ), ); @@ -2735,6 +2736,10 @@ function wp_maybe_inline_styles() { // Get the styles if we don't already have them. $style['css'] = file_get_contents( $style['path'] ); + // Check if the style contains relative URLs that need to be modified. + // URLs relative to the stylesheet's path should be converted to relative to the site's root. + $style['css'] = _wp_normalize_relative_css_links( $style['css'], $style['src'] ); + // Set `src` to `false` and add styles inline. $wp_styles->registered[ $style['handle'] ]->src = false; if ( empty( $wp_styles->registered[ $style['handle'] ]->extra['after'] ) ) { @@ -2748,6 +2753,45 @@ function wp_maybe_inline_styles() { } } +/** + * Make URLs relative to the WordPress installation. + * + * @since 5.9.0 + * @access private + * + * @param string $css The CSS to make URLs relative to the WordPress installation. + * @param string $stylesheet_url The URL to the stylesheet. + * + * @return string The CSS with URLs made relative to the WordPress installation. + */ +function _wp_normalize_relative_css_links( $css, $stylesheet_url ) { + $has_src_results = preg_match_all( '#url\s*\(\s*[\'"]?\s*([^\'"\)]+)#', $css, $src_results ); + if ( $has_src_results ) { + // Loop through the URLs to find relative ones. + foreach ( $src_results[1] as $src_index => $src_result ) { + // Skip if this is an absolute URL. + if ( 0 === strpos( $src_result, 'http' ) || 0 === strpos( $src_result, '//' ) ) { + continue; + } + + // Build the absolute URL. + $absolute_url = dirname( $stylesheet_url ) . '/' . $src_result; + $absolute_url = str_replace( '/./', '/', $absolute_url ); + // Convert to URL related to the site root. + $relative_url = wp_make_link_relative( $absolute_url ); + + // Replace the URL in the CSS. + $css = str_replace( + $src_results[0][ $src_index ], + str_replace( $src_result, $relative_url, $src_results[0][ $src_index ] ), + $css + ); + } + } + + return $css; +} + /** * Inject the block editor assets that need to be loaded into the editor's iframe as an inline script. * diff --git a/tests/phpunit/tests/dependencies/styles.php b/tests/phpunit/tests/dependencies/styles.php index f5eed18758..bec55ac032 100644 --- a/tests/phpunit/tests/dependencies/styles.php +++ b/tests/phpunit/tests/dependencies/styles.php @@ -189,6 +189,51 @@ class Tests_Dependencies_Styles extends WP_UnitTestCase { } + /** + * Test normalizing relative links in CSS. + * + * @dataProvider data_normalize_relative_css_links + * + * @ticket 54243 + * + * @covers ::_wp_normalize_relative_css_links + * + * @param string $css Given CSS to test. + * @param string $expected Expected result. + */ + public function test_normalize_relative_css_links( $css, $expected ) { + $this->assertSame( + $expected, + _wp_normalize_relative_css_links( $css, site_url( 'wp-content/themes/test/style.css' ) ) + ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_normalize_relative_css_links() { + return array( + 'Double quotes, same path' => array( + 'css' => 'p {background:url( "image0.svg" );}', + 'expected' => 'p {background:url( "/wp-content/themes/test/image0.svg" );}', + ), + 'Single quotes, same path, prefixed with "./"' => array( + 'css' => 'p {background-image: url(\'./image2.png\');}', + 'expected' => 'p {background-image: url(\'/wp-content/themes/test/image2.png\');}', + ), + 'Single quotes, one level up, prefixed with "../"' => array( + 'css' => 'p {background-image: url(\'../image1.jpg\');}', + 'expected' => 'p {background-image: url(\'/wp-content/themes/test/../image1.jpg\');}', + ), + 'External URLs, shouldn\'t change' => array( + 'css' => 'p {background-image: url(\'http://foo.com/image2.png\');}', + 'expected' => 'p {background-image: url(\'http://foo.com/image2.png\');}', + ), + ); + } + /** * Test if multiple inline styles work *