diff --git a/src/wp-includes/interactivity-api/class-wp-interactivity-api.php b/src/wp-includes/interactivity-api/class-wp-interactivity-api.php index cc894c7e3018..ac9a48982a5c 100644 --- a/src/wp-includes/interactivity-api/class-wp-interactivity-api.php +++ b/src/wp-includes/interactivity-api/class-wp-interactivity-api.php @@ -167,10 +167,41 @@ public function print_client_interactivity_data() { } if ( ! empty( $interactivity_data ) ) { + /* + * This data will be printed as JSON inside a script tag like this: + * + * + * A script tag must be closed by a sequence beginning with `` will be printed as `\u003C/script\u00E3`. + * + * - 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. It uses the same behaviour as it was + * before PHP 7.1 without this constant. Available as of PHP 7.1.0. + * + * The JSON specification requires encoding in UTF-8, so if the generated HTML page + * is not encoded in UTF-8 then it's not safe to include those literals. They must + * be escaped to avoid encoding issues. + * + * @see https://www.rfc-editor.org/rfc/rfc8259.html for details on encoding requirements. + * @see https://www.php.net/manual/en/json.constants.php for details on these constants. + * @see https://html.spec.whatwg.org/#script-data-state for details on script tag parsing. + */ + $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; + } + wp_print_inline_script_tag( wp_json_encode( $interactivity_data, - JSON_HEX_TAG | JSON_HEX_AMP + $json_encode_flags ), array( 'type' => 'application/json', diff --git a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI.php b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI.php index ffcc8087acf3..3c2050cab38e 100644 --- a/tests/phpunit/tests/interactivity-api/wpInteractivityAPI.php +++ b/tests/phpunit/tests/interactivity-api/wpInteractivityAPI.php @@ -27,6 +27,10 @@ public function set_up() { $this->interactivity = new WP_Interactivity_API(); } + public function charset_iso_8859_1() { + return 'iso-8859-1'; + } + /** * Tests that the state and config methods return an empty array at the * beginning. @@ -349,22 +353,76 @@ public function test_config_printed_correctly_with_nested_empty_array() { * properly escaped. * * @ticket 60356 + * @ticket 61170 * * @covers ::state * @covers ::config * @covers ::print_client_interactivity_data */ public function test_state_and_config_escape_special_characters() { - $this->interactivity->state( 'myPlugin', array( 'amps' => 'http://site.test/?foo=1&baz=2' ) ); - $this->interactivity->config( 'myPlugin', array( 'tags' => 'Tags: