diff --git a/phpcs.xml.dist b/phpcs.xml.dist
index 8736e29f8c..ab8124af29 100644
--- a/phpcs.xml.dist
+++ b/phpcs.xml.dist
@@ -171,6 +171,7 @@
+
diff --git a/src/wp-includes/class-wp-customize-manager.php b/src/wp-includes/class-wp-customize-manager.php
index aebcfeb892..c1cf81f142 100644
--- a/src/wp-includes/class-wp-customize-manager.php
+++ b/src/wp-includes/class-wp-customize-manager.php
@@ -474,7 +474,7 @@ final class WP_Customize_Manager {
} )( wp.customize, );
', '' ), '', ob_get_clean() ) );
+ $message .= wp_get_inline_script_tag( wp_remove_surrounding_empty_script_tags( ob_get_clean() ) );
}
wp_die( $message );
@@ -2109,7 +2109,7 @@ final class WP_Customize_Manager {
} )();
', '' ), '', ob_get_clean() ) );
+ wp_print_inline_script_tag( wp_remove_surrounding_empty_script_tags( ob_get_clean() ) );
}
/**
@@ -2230,7 +2230,7 @@ final class WP_Customize_Manager {
})( _wpCustomizeSettings.values );
', '' ), '', ob_get_clean() ) );
+ wp_print_inline_script_tag( wp_remove_surrounding_empty_script_tags( ob_get_clean() ) );
}
/**
@@ -5019,7 +5019,7 @@ final class WP_Customize_Manager {
?>
', '' ), '', ob_get_clean() ) );
+ wp_print_inline_script_tag( wp_remove_surrounding_empty_script_tags( ob_get_clean() ) );
}
/**
diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php
index d7c0a9bbdf..d070c1695a 100644
--- a/src/wp-includes/functions.php
+++ b/src/wp-includes/functions.php
@@ -7619,7 +7619,7 @@ function wp_post_preview_js() {
}());
', '' ), '', ob_get_clean() ) );
+ wp_print_inline_script_tag( wp_remove_surrounding_empty_script_tags( ob_get_clean() ) );
}
/**
diff --git a/src/wp-includes/script-loader.php b/src/wp-includes/script-loader.php
index 4f17461752..1527c69819 100644
--- a/src/wp-includes/script-loader.php
+++ b/src/wp-includes/script-loader.php
@@ -2853,9 +2853,43 @@ function wp_get_inline_script_tag( $javascript, $attributes = array() ) {
);
}
- // Ensure markup is XHTML compatible if not HTML5.
+ /*
+ * XHTML extracts the contents of the SCRIPT element and then the XML parser
+ * decodes character references and other syntax elements. This can lead to
+ * misinterpretation of the script contents or invalid XHTML documents.
+ *
+ * Wrapping the contents in a CDATA section instructs the XML parser not to
+ * transform the contents of the SCRIPT element before passing them to the
+ * JavaScript engine.
+ *
+ * Example:
+ *
+ *
+ *
+ * In an HTML document this would print "…" to the console,
+ * but in an XHTML document it would print "…" to the console.
+ *
+ *
+ *
+ * In an HTML document this would print "An image is
in HTML",
+ * but it's an invalid XHTML document because it interprets the `
`
+ * as an empty tag missing its closing `/`.
+ *
+ * @see https://www.w3.org/TR/xhtml1/#h-4.8
+ */
if ( ! $is_html5 ) {
- $javascript = str_replace( ']]>', ']]]]>', $javascript ); // Escape any existing CDATA section.
+ /*
+ * If the string `]]>` exists within the JavaScript it would break
+ * out of any wrapping CDATA section added here, so to start, it's
+ * necessary to escape that sequence which requires splitting the
+ * content into two CDATA sections wherever it's found.
+ *
+ * Note: it's only necessary to escape the closing `]]>` because
+ * an additional `', ']]]]>', $javascript );
+
+ // Wrap the entire escaped script inside a CDATA section.
$javascript = sprintf( "/* */", $javascript );
}
@@ -3299,3 +3333,51 @@ function wp_add_editor_classic_theme_styles( $editor_settings ) {
return $editor_settings;
}
+
+/**
+ * Removes leading and trailing _empty_ script tags.
+ *
+ * This is a helper meant to be used for literal script tag construction
+ * within `wp_get_inline_script_tag()` or `wp_print_inline_script_tag()`.
+ * It removes the literal values of "" from
+ * around an inline script after trimming whitespace. Typlically this
+ * is used in conjunction with output buffering, where `ob_get_clean()`
+ * is passed as the `$contents` argument.
+ *
+ * Example:
+ *
+ * // Strips exact literal empty SCRIPT tags.
+ * $js = ';
+ * 'sayHello();' === wp_remove_surrounding_empty_script_tags( $js );
+ *
+ * // Otherwise if anything is different it warns in the JS console.
+ * $js = '';
+ * 'console.error( ... )' === wp_remove_surrounding_empty_script_tags( $js );
+ *
+ * @private
+ * @since 6.4.0
+ *
+ * @see wp_print_inline_script_tag()
+ * @see wp_get_inline_script_tag()
+ *
+ * @param string $contents Script body with manually created SCRIPT tag literals.
+ * @return string Script body without surrounding script tag literals, or
+ * original contents if both exact literals aren't present.
+ */
+function wp_remove_surrounding_empty_script_tags( $contents ) {
+ $contents = trim( $contents );
+ $opener = '';
+
+ if (
+ strlen( $contents ) > strlen( $opener ) + strlen( $closer ) &&
+ strtoupper( substr( $contents, 0, strlen( $opener ) ) ) === $opener &&
+ strtoupper( substr( $contents, -strlen( $closer ) ) ) === $closer
+ ) {
+ return substr( $contents, strlen( $opener ), -strlen( $closer ) );
+ } else {
+ $error_message = __( 'Expected string to start with script tag (without attributes) and end with script tag, with optional whitespace.' );
+ _doing_it_wrong( __FUNCTION__, $error_message, '6.4' );
+ return sprintf( 'console.error(%s)', wp_json_encode( __( 'Function wp_remove_surrounding_empty_script_tags() used incorrectly in PHP.' ) . ' ' . $error_message ) );
+ }
+}
diff --git a/src/wp-includes/theme-templates.php b/src/wp-includes/theme-templates.php
index 8f0ee9a69f..cc4b19ecce 100644
--- a/src/wp-includes/theme-templates.php
+++ b/src/wp-includes/theme-templates.php
@@ -205,7 +205,7 @@ function the_block_template_skip_link() {
}() );
', '' ), '', ob_get_clean() );
+ $skip_link_script = wp_remove_surrounding_empty_script_tags( ob_get_clean() );
$script_handle = 'wp-block-template-skip-link';
wp_register_script( $script_handle, false );
wp_add_inline_script( $script_handle, $skip_link_script );
diff --git a/src/wp-includes/theme.php b/src/wp-includes/theme.php
index 087d563352..1eccd460b1 100644
--- a/src/wp-includes/theme.php
+++ b/src/wp-includes/theme.php
@@ -3800,7 +3800,7 @@ function wp_customize_support_script() {
}());
', '' ), '', ob_get_clean() ) );
+ wp_print_inline_script_tag( wp_remove_surrounding_empty_script_tags( ob_get_clean() ) );
}
/**
diff --git a/src/wp-includes/widgets/class-wp-widget-archives.php b/src/wp-includes/widgets/class-wp-widget-archives.php
index 21d3a7b672..7457244833 100644
--- a/src/wp-includes/widgets/class-wp-widget-archives.php
+++ b/src/wp-includes/widgets/class-wp-widget-archives.php
@@ -120,7 +120,7 @@ class WP_Widget_Archives extends WP_Widget {
})();
', '' ), '', ob_get_clean() ) );
+ wp_print_inline_script_tag( wp_remove_surrounding_empty_script_tags( ob_get_clean() ) );
} else {
$format = current_theme_supports( 'html5', 'navigation-widgets' ) ? 'html5' : 'xhtml';
diff --git a/src/wp-includes/widgets/class-wp-widget-categories.php b/src/wp-includes/widgets/class-wp-widget-categories.php
index e00ce7f960..b79881e752 100644
--- a/src/wp-includes/widgets/class-wp-widget-categories.php
+++ b/src/wp-includes/widgets/class-wp-widget-categories.php
@@ -108,7 +108,7 @@ class WP_Widget_Categories extends WP_Widget {
', '' ), '', ob_get_clean() ) );
+ wp_print_inline_script_tag( wp_remove_surrounding_empty_script_tags( ob_get_clean() ) );
} else {
$format = current_theme_supports( 'html5', 'navigation-widgets' ) ? 'html5' : 'xhtml';
diff --git a/src/wp-login.php b/src/wp-login.php
index 80887f0420..f03eea3636 100644
--- a/src/wp-login.php
+++ b/src/wp-login.php
@@ -105,7 +105,7 @@ function login_header( $title = 'Log In', $message = '', $wp_error = null ) {
?>
', '' ), '', ob_get_clean() ) );
+ wp_print_inline_script_tag( wp_remove_surrounding_empty_script_tags( ob_get_clean() ) );
}
/**
@@ -419,12 +419,12 @@ function login_footer( $input_id = '' ) {
if ( ! empty( $input_id ) ) {
ob_start();
?>
-
', '' ), '', ob_get_clean() ) );
+ wp_print_inline_script_tag( wp_remove_surrounding_empty_script_tags( ob_get_clean() ) );
}
/**
@@ -1362,7 +1362,7 @@ switch ( $action ) {
?>
', '' ), '', ob_get_clean() ) );
+ wp_print_inline_script_tag( wp_remove_surrounding_empty_script_tags( ob_get_clean() ) );
}
?>
@@ -1627,7 +1627,7 @@ switch ( $action ) {
}());
', '' ), '', ob_get_clean() ) );
+ wp_print_inline_script_tag( wp_remove_surrounding_empty_script_tags( ob_get_clean() ) );
}
login_footer();
diff --git a/tests/phpunit/tests/customize/manager.php b/tests/phpunit/tests/customize/manager.php
index 44f4c52ed9..9f0ff383ed 100644
--- a/tests/phpunit/tests/customize/manager.php
+++ b/tests/phpunit/tests/customize/manager.php
@@ -3135,8 +3135,8 @@ class Tests_WP_Customize_Manager extends WP_UnitTestCase {
$manager = new WP_Customize_Manager( array( 'messenger_channel' => 'preview-0' ) );
ob_start();
$manager->remove_frameless_preview_messenger_channel();
- $output = ob_get_clean();
- $this->assertStringContainsString( '" );
- $this->assertStringContainsString( $expected, $output, 'Blocking dependents must force delayed dependencies to become blocking.' );
+ $expected = <<
+
+JS;
+ $this->assertEqualMarkup( $expected, $output, 'Blocking dependents must force delayed dependencies to become blocking.' );
}
/**
@@ -2997,6 +3000,15 @@ HTML
}
}
+ // Normalize other whitespace nodes.
+ $xpath = new DOMXPath( $dom );
+ foreach ( $xpath->query( '//text()' ) as $node ) {
+ /** @var DOMText $node */
+ if ( preg_match( '/^\s+$/', $node->nodeValue ) ) {
+ $node->nodeValue = ' ';
+ }
+ }
+
return $dom;
}
diff --git a/tests/phpunit/tests/dependencies/wpRemoveSurroundingEmptyScriptTags.php b/tests/phpunit/tests/dependencies/wpRemoveSurroundingEmptyScriptTags.php
new file mode 100644
index 0000000000..39b0480ccb
--- /dev/null
+++ b/tests/phpunit/tests/dependencies/wpRemoveSurroundingEmptyScriptTags.php
@@ -0,0 +1,78 @@
+ array(
+ '',
+ 'alert("hello")',
+ false,
+ ),
+ 'BASIC_CASE' => array(
+ '',
+ 'alert("hello")',
+ false,
+ ),
+ 'whitespace_basic_case' => array(
+ ' ',
+ 'alert("hello")',
+ false,
+ ),
+ 'missing_tags' => array(
+ 'alert("hello")',
+ $error_js,
+ true,
+ ),
+ 'missing_start_tag' => array(
+ 'alert("hello")',
+ $error_js,
+ true,
+ ),
+ 'missing_end_tag' => array(
+ '',
+ $error_js,
+ true,
+ ),
+ );
+ }
+
+ /**
+ * Test scenarios for wp_remove_surrounding_empty_script_tags().
+ *
+ * @dataProvider get_data_to_test_wp_remove_surrounding_empty_script_tags
+ *
+ * @param string $input Input.
+ * @param string $expected Expected.
+ * @param bool $expect_doing_it_wrong Whether input is _doing_it_wrong().
+ */
+ public function test_wp_remove_surrounding_empty_script_tags( $input, $expected, $expect_doing_it_wrong ) {
+ if ( $expect_doing_it_wrong ) {
+ $this->setExpectedIncorrectUsage( 'wp_remove_surrounding_empty_script_tags' );
+ }
+
+ $this->assertSame(
+ $expected,
+ wp_remove_surrounding_empty_script_tags( $input )
+ );
+ }
+}