diff --git a/src/wp-includes/class-wp-scripts.php b/src/wp-includes/class-wp-scripts.php
index cb37b2b653877..f570f6e72da4f 100644
--- a/src/wp-includes/class-wp-scripts.php
+++ b/src/wp-includes/class-wp-scripts.php
@@ -388,6 +388,7 @@ public function do_item( $handle, $group = false ) {
_print_scripts();
$this->reset();
} elseif ( $this->in_default_dir( $filtered_src ) ) {
+ $this->print_code .= $this->print_script_data( $handle, false );
$this->print_code .= $this->print_extra_script( $handle, false );
$this->concat .= "$handle,";
$this->concat_version .= "$handle$ver";
@@ -398,6 +399,7 @@ public function do_item( $handle, $group = false ) {
}
}
+ $this->print_script_data( $handle );
$this->print_extra_script( $handle );
// A single item may alias a set of items, by having dependencies, but no source.
@@ -1235,6 +1237,128 @@ public function reset() {
$this->ext_handles = '';
}
+ /**
+ * Prints the data for a registered script as a JSON script tag.
+ *
+ * The data is provided via the {@see 'script_data_{$handle}'} filter and printed
+ * as a `
+ *
+ * A script tag must be closed by a sequence beginning with ``. It's impossible to
+ * close a script tag without using `<`. We ensure that `<` is escaped and `/` can
+ * remain unescaped, so `` will be printed as `\u003C/script>`.
+ *
+ * - JSON_HEX_TAG: All < and > are converted to \u003C and \u003E.
+ * - JSON_UNESCAPED_SLASHES: Don't escape /.
+ *
+ * If the page will use UTF-8 encoding, it's safe to print unescaped unicode:
+ *
+ * - JSON_UNESCAPED_UNICODE: Encode multibyte Unicode characters literally (instead of as `\uXXXX`).
+ * - JSON_UNESCAPED_LINE_TERMINATORS: The line terminators are kept unescaped when
+ * JSON_UNESCAPED_UNICODE is supplied. Available as of PHP 7.1.0.
+ */
+ $json_encode_flags = JSON_HEX_TAG | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_LINE_TERMINATORS;
+ if ( ! is_utf8_charset() ) {
+ $json_encode_flags = JSON_HEX_TAG | JSON_UNESCAPED_SLASHES;
+ }
+
+ $output = wp_get_inline_script_tag(
+ (string) wp_json_encode(
+ $data,
+ $json_encode_flags
+ ),
+ array(
+ 'type' => 'application/json',
+ 'id' => "{$handle}-js-data",
+ )
+ );
+
+ if ( ! $display ) {
+ return $output;
+ }
+
+ if ( $this->do_concat ) {
+ $this->print_html .= $output;
+ } else {
+ echo $output;
+ }
+
+ return true;
+ }
+
/**
* Gets a script-specific dependency warning message.
*
diff --git a/src/wp-includes/functions.wp-scripts.php b/src/wp-includes/functions.wp-scripts.php
index 59e4e54a1a1ad..5253135115ac6 100644
--- a/src/wp-includes/functions.wp-scripts.php
+++ b/src/wp-includes/functions.wp-scripts.php
@@ -190,6 +190,53 @@ function wp_add_inline_script( $handle, $data, $position = 'after' ) {
return wp_scripts()->add_inline_script( $handle, $data, $position );
}
+/**
+ * Adds data to be passed from PHP to a registered script via a JSON script tag.
+ *
+ * The data is printed as a `' ) );
+
+ $output = $this->get_print_output( 'test-handle' );
+
+ $this->assertStringNotContainsString( '', $output );
+ $this->assertStringContainsString( '\u003C', $output );
+ }
+
+ /**
+ * @ticket 58873
+ * @expectedIncorrectUsage WP_Scripts::print_script_data
+ */
+ public function test_print_script_data_doing_it_wrong_for_non_array_filter_return() {
+ wp_enqueue_script( 'test-handle', '/test.js' );
+
+ add_filter(
+ 'script_data_test-handle',
+ static function () {
+ return 'not an array';
+ }
+ );
+
+ $output = $this->get_print_output( 'test-handle' );
+
+ $this->assertStringNotContainsString( 'test-handle-js-data', $output );
+ }
+}