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 `` 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 ); + } +}