From 105584a5f7b32d6b63b6688a82c75fdba37430ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Greg=20Zi=C3=83=C2=B3=C3=85=E2=80=9Akowski?= Date: Mon, 8 Nov 2021 13:23:44 +0000 Subject: [PATCH] Fix relative URLs in inlined block styles that involve external assets When styles got inlined, relative URLs break. The problem was that URLs inside CSS files are relative to the stylesheet's path, and when styles get inlined that relation is lost. This patch fixes the issue by finding relative URLs which then get modified to be relative to the site's root. Fixes #54243. Props aristath, cdyerkes, hellofromtonya. git-svn-id: https://develop.svn.wordpress.org/trunk@52036 602fd350-edb4-49c9-b593-d223f7449a82 --- src/wp-includes/script-loader.php | 44 ++++++++++++++++++++ tests/phpunit/tests/dependencies/styles.php | 45 +++++++++++++++++++++ 2 files changed, 89 insertions(+) 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 *