From 661399edae6f53eb131fde4fe8d4d30e1cc01a89 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 12 Dec 2025 17:11:05 +0000
Subject: [PATCH 01/12] Initial plan
From 2c0960c87d4bd85d86d17f6e7972239a7acabb64 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 12 Dec 2025 17:15:52 +0000
Subject: [PATCH 02/12] Add script_data_{$handle} filter for classic scripts
Co-authored-by: sirreal <841763+sirreal@users.noreply.github.com>
---
src/wp-includes/class-wp-scripts.php | 17 +++
tests/phpunit/tests/dependencies/scripts.php | 135 +++++++++++++++++++
2 files changed, 152 insertions(+)
diff --git a/src/wp-includes/class-wp-scripts.php b/src/wp-includes/class-wp-scripts.php
index a30b09249fd52..1bbd6d66ca012 100644
--- a/src/wp-includes/class-wp-scripts.php
+++ b/src/wp-includes/class-wp-scripts.php
@@ -634,6 +634,23 @@ public function localize( $handle, $object_name, $l10n ) {
}
}
+ /**
+ * Filters data associated with a given script.
+ *
+ * The dynamic portion of the hook name, `$handle`, refers to the script handle.
+ *
+ * This filter allows developers to modify the data passed to a script via
+ * wp_localize_script() before it is output. This is analogous to the
+ * `script_module_data_{$module_id}` filter for script modules.
+ *
+ * @since 6.8.0
+ *
+ * @param array|string $l10n The data to be localized.
+ * @param string $object_name The JavaScript object name.
+ * @param string $handle The script handle.
+ */
+ $l10n = apply_filters( "script_data_{$handle}", $l10n, $object_name, $handle );
+
$script = "var $object_name = " . wp_json_encode( $l10n, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ) . ';';
if ( ! empty( $after ) ) {
diff --git a/tests/phpunit/tests/dependencies/scripts.php b/tests/phpunit/tests/dependencies/scripts.php
index a3c8b92695f4f..55d1b5d7d0129 100644
--- a/tests/phpunit/tests/dependencies/scripts.php
+++ b/tests/phpunit/tests/dependencies/scripts.php
@@ -4122,4 +4122,139 @@ public function test_wp_scripts_doing_it_wrong_for_missing_dependencies() {
'Expected _doing_it_wrong() notice to indicate missing dependencies for enqueued script.'
);
}
+
+ /**
+ * Tests that the script_data_{$handle} filter allows modifying localized script data.
+ *
+ * @ticket TBD
+ * @covers WP_Scripts::localize
+ */
+ public function test_script_data_filter_modifies_localized_data() {
+ wp_enqueue_script( 'test-script', '/test.js', array(), null );
+ wp_localize_script( 'test-script', 'testData', array( 'foo' => 'bar' ) );
+
+ add_filter(
+ 'script_data_test-script',
+ function ( $l10n, $object_name, $handle ) {
+ $this->assertSame( 'testData', $object_name );
+ $this->assertSame( 'test-script', $handle );
+ $this->assertIsArray( $l10n );
+ $this->assertSame( 'bar', $l10n['foo'] );
+ $l10n['baz'] = 'qux';
+ return $l10n;
+ },
+ 10,
+ 3
+ );
+
+ $output = get_echo( 'wp_print_scripts' );
+
+ $this->assertStringContainsString( '"foo":"bar"', $output );
+ $this->assertStringContainsString( '"baz":"qux"', $output );
+ }
+
+ /**
+ * Tests that the script_data_{$handle} filter receives correct parameters.
+ *
+ * @ticket TBD
+ * @covers WP_Scripts::localize
+ */
+ public function test_script_data_filter_receives_correct_parameters() {
+ wp_enqueue_script( 'test-handle', '/test.js', array(), null );
+ wp_localize_script( 'test-handle', 'myObject', array( 'key' => 'value' ) );
+
+ $filter_called = false;
+ add_filter(
+ 'script_data_test-handle',
+ function ( $l10n, $object_name, $handle ) use ( &$filter_called ) {
+ $filter_called = true;
+ $this->assertSame( array( 'key' => 'value' ), $l10n );
+ $this->assertSame( 'myObject', $object_name );
+ $this->assertSame( 'test-handle', $handle );
+ return $l10n;
+ },
+ 10,
+ 3
+ );
+
+ get_echo( 'wp_print_scripts' );
+
+ $this->assertTrue( $filter_called, 'Filter should have been called' );
+ }
+
+ /**
+ * Tests that the script_data_{$handle} filter works with multiple localizations.
+ *
+ * @ticket TBD
+ * @covers WP_Scripts::localize
+ */
+ public function test_script_data_filter_with_multiple_localizations() {
+ wp_enqueue_script( 'test-script', '/test.js', array(), null );
+ wp_localize_script( 'test-script', 'data1', array( 'a' => '1' ) );
+ wp_localize_script( 'test-script', 'data2', array( 'b' => '2' ) );
+
+ $filter_call_count = 0;
+ add_filter(
+ 'script_data_test-script',
+ function ( $l10n ) use ( &$filter_call_count ) {
+ $filter_call_count++;
+ $l10n['modified'] = 'yes';
+ return $l10n;
+ }
+ );
+
+ $output = get_echo( 'wp_print_scripts' );
+
+ $this->assertSame( 2, $filter_call_count, 'Filter should be called twice for two localizations' );
+ $this->assertStringContainsString( '"modified":"yes"', $output );
+ }
+
+ /**
+ * Tests that the script_data_{$handle} filter can return the data unmodified.
+ *
+ * @ticket TBD
+ * @covers WP_Scripts::localize
+ */
+ public function test_script_data_filter_returns_data_unmodified() {
+ wp_enqueue_script( 'test-script', '/test.js', array(), null );
+ wp_localize_script( 'test-script', 'testData', array( 'foo' => 'bar' ) );
+
+ add_filter(
+ 'script_data_test-script',
+ function ( $l10n ) {
+ // Return data unmodified.
+ return $l10n;
+ }
+ );
+
+ $output = get_echo( 'wp_print_scripts' );
+
+ $this->assertStringContainsString( 'var testData = {"foo":"bar"};', $output );
+ }
+
+ /**
+ * Tests that the script_data_{$handle} filter works correctly with jquery handle remapping.
+ *
+ * @ticket TBD
+ * @covers WP_Scripts::localize
+ */
+ public function test_script_data_filter_with_jquery_handle() {
+ wp_enqueue_script( 'jquery' );
+ wp_localize_script( 'jquery', 'jqueryData', array( 'test' => 'value' ) );
+
+ $filter_called = false;
+ add_filter(
+ 'script_data_jquery-core',
+ function ( $l10n ) use ( &$filter_called ) {
+ $filter_called = true;
+ $l10n['filtered'] = 'true';
+ return $l10n;
+ }
+ );
+
+ $output = get_echo( 'wp_print_scripts' );
+
+ $this->assertTrue( $filter_called, 'Filter should be called for jquery-core handle' );
+ $this->assertStringContainsString( '"filtered":"true"', $output );
+ }
}
From f4afe132d99b799e435498ef5d9507a0f3e51c49 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 12 Dec 2025 17:20:44 +0000
Subject: [PATCH 03/12] Fix test docblocks - remove TBD ticket annotations
Co-authored-by: sirreal <841763+sirreal@users.noreply.github.com>
---
tests/phpunit/tests/dependencies/scripts.php | 5 -----
1 file changed, 5 deletions(-)
diff --git a/tests/phpunit/tests/dependencies/scripts.php b/tests/phpunit/tests/dependencies/scripts.php
index 55d1b5d7d0129..afdac92106b72 100644
--- a/tests/phpunit/tests/dependencies/scripts.php
+++ b/tests/phpunit/tests/dependencies/scripts.php
@@ -4126,7 +4126,6 @@ public function test_wp_scripts_doing_it_wrong_for_missing_dependencies() {
/**
* Tests that the script_data_{$handle} filter allows modifying localized script data.
*
- * @ticket TBD
* @covers WP_Scripts::localize
*/
public function test_script_data_filter_modifies_localized_data() {
@@ -4156,7 +4155,6 @@ function ( $l10n, $object_name, $handle ) {
/**
* Tests that the script_data_{$handle} filter receives correct parameters.
*
- * @ticket TBD
* @covers WP_Scripts::localize
*/
public function test_script_data_filter_receives_correct_parameters() {
@@ -4185,7 +4183,6 @@ function ( $l10n, $object_name, $handle ) use ( &$filter_called ) {
/**
* Tests that the script_data_{$handle} filter works with multiple localizations.
*
- * @ticket TBD
* @covers WP_Scripts::localize
*/
public function test_script_data_filter_with_multiple_localizations() {
@@ -4212,7 +4209,6 @@ function ( $l10n ) use ( &$filter_call_count ) {
/**
* Tests that the script_data_{$handle} filter can return the data unmodified.
*
- * @ticket TBD
* @covers WP_Scripts::localize
*/
public function test_script_data_filter_returns_data_unmodified() {
@@ -4235,7 +4231,6 @@ function ( $l10n ) {
/**
* Tests that the script_data_{$handle} filter works correctly with jquery handle remapping.
*
- * @ticket TBD
* @covers WP_Scripts::localize
*/
public function test_script_data_filter_with_jquery_handle() {
From d3cd233401d1c9dd531fc5e020b079dcc376c02c Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 12 Dec 2025 17:29:28 +0000
Subject: [PATCH 04/12] Implement script_data_{$handle} filter correctly -
outputs JSON script tags separate from wp_localize_script
Co-authored-by: sirreal <841763+sirreal@users.noreply.github.com>
---
src/wp-admin/includes/admin-filters.php | 1 +
src/wp-includes/class-wp-scripts.php | 133 ++++++++++++--
src/wp-includes/default-filters.php | 1 +
src/wp-includes/functions.wp-scripts.php | 17 ++
tests/phpunit/tests/dependencies/scripts.php | 177 +++++++++++--------
5 files changed, 243 insertions(+), 86 deletions(-)
diff --git a/src/wp-admin/includes/admin-filters.php b/src/wp-admin/includes/admin-filters.php
index 6776f5898ad58..e48cac473ef4d 100644
--- a/src/wp-admin/includes/admin-filters.php
+++ b/src/wp-admin/includes/admin-filters.php
@@ -59,6 +59,7 @@
add_action( 'admin_print_scripts', 'print_emoji_detection_script' );
add_action( 'admin_print_scripts', 'print_head_scripts', 20 );
add_action( 'admin_print_footer_scripts', '_wp_footer_scripts' );
+add_action( 'admin_print_footer_scripts', 'wp_print_script_data', 21 );
add_action( 'admin_enqueue_scripts', 'wp_enqueue_emoji_styles' );
add_action( 'admin_print_styles', 'print_emoji_styles' ); // Retained for backwards-compatibility. Unhooked by wp_enqueue_emoji_styles().
add_action( 'admin_print_styles', 'print_admin_styles', 20 );
diff --git a/src/wp-includes/class-wp-scripts.php b/src/wp-includes/class-wp-scripts.php
index 1bbd6d66ca012..e710b371ae5e6 100644
--- a/src/wp-includes/class-wp-scripts.php
+++ b/src/wp-includes/class-wp-scripts.php
@@ -634,23 +634,6 @@ public function localize( $handle, $object_name, $l10n ) {
}
}
- /**
- * Filters data associated with a given script.
- *
- * The dynamic portion of the hook name, `$handle`, refers to the script handle.
- *
- * This filter allows developers to modify the data passed to a script via
- * wp_localize_script() before it is output. This is analogous to the
- * `script_module_data_{$module_id}` filter for script modules.
- *
- * @since 6.8.0
- *
- * @param array|string $l10n The data to be localized.
- * @param string $object_name The JavaScript object name.
- * @param string $handle The script handle.
- */
- $l10n = apply_filters( "script_data_{$handle}", $l10n, $object_name, $handle );
-
$script = "var $object_name = " . wp_json_encode( $l10n, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ) . ';';
if ( ! empty( $after ) ) {
@@ -1199,4 +1182,120 @@ protected function get_dependency_warning_message( $handle, $missing_dependency_
implode( ', ', $missing_dependency_handles )
);
}
+
+ /**
+ * Prints data associated with scripts.
+ *
+ * The data will be embedded in the page HTML and can be read by scripts on page load.
+ *
+ * Data can be associated with a script via the {@see "script_data_{$handle}"} filter.
+ *
+ * The data for a script will be serialized as JSON in a script tag with an ID of the
+ * form `wp-script-data-{$handle}`.
+ *
+ * @since 6.8.0
+ */
+ public function print_script_data() {
+ $scripts = array();
+
+ // Collect all enqueued scripts and their dependencies.
+ foreach ( array_unique( $this->queue ) as $handle ) {
+ $scripts[ $handle ] = true;
+ }
+
+ foreach ( array_keys( $scripts ) as $handle ) {
+ /**
+ * Filters data associated with a given script.
+ *
+ * The dynamic portion of the hook name, `$handle`, refers to the script handle.
+ *
+ * Scripts may require data that is required for initialization or is essential
+ * to have immediately available on page load. These are suitable use cases for
+ * this data.
+ *
+ * This is best suited to pass essential data that must be available to the script for
+ * initialization or immediately on page load. It does not replace the REST API or
+ * fetching data from the client.
+ *
+ * Example:
+ *
+ * add_filter(
+ * 'script_data_my-script-handle',
+ * function ( array $data ): array {
+ * $data['myData'] = array(
+ * 'option' => get_option( 'my_option' ),
+ * );
+ * return $data;
+ * }
+ * );
+ *
+ * If the filter returns no data (an empty array), nothing will be embedded in the page.
+ *
+ * The data for a given script, if provided, will be JSON serialized in a script
+ * tag with an ID of the form `wp-script-data-{$handle}`.
+ *
+ * The data can be read on the client with a pattern like this:
+ *
+ * Example:
+ *
+ * const dataContainer = document.getElementById( 'wp-script-data-my-script-handle' );
+ * let data = {};
+ * if ( dataContainer ) {
+ * try {
+ * data = JSON.parse( dataContainer.textContent );
+ * } catch {}
+ * }
+ * initMyScriptWithData( data );
+ *
+ * @since 6.8.0
+ *
+ * @param array $data The data associated with the script.
+ */
+ $data = apply_filters( "script_data_{$handle}", array() );
+
+ if ( is_array( $data ) && array() !== $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 ``. 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. 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(
+ (string) wp_json_encode(
+ $data,
+ $json_encode_flags
+ ),
+ array(
+ 'type' => 'application/json',
+ 'id' => "wp-script-data-{$handle}",
+ )
+ );
+ }
+ }
+ }
}
diff --git a/src/wp-includes/default-filters.php b/src/wp-includes/default-filters.php
index 68dccd979f2fe..0eac638ddcce7 100644
--- a/src/wp-includes/default-filters.php
+++ b/src/wp-includes/default-filters.php
@@ -361,6 +361,7 @@
add_action( 'wp_head', 'wp_site_icon', 99 );
add_action( 'wp_footer', 'wp_print_speculation_rules' );
add_action( 'wp_footer', 'wp_print_footer_scripts', 20 );
+add_action( 'wp_footer', 'wp_print_script_data', 21 );
add_action( 'template_redirect', 'wp_shortlink_header', 11, 0 );
add_action( 'wp_print_footer_scripts', '_wp_footer_scripts' );
add_action( 'init', '_register_core_block_patterns_and_categories' );
diff --git a/src/wp-includes/functions.wp-scripts.php b/src/wp-includes/functions.wp-scripts.php
index f86b456d5f69a..c34b8bca92df4 100644
--- a/src/wp-includes/functions.wp-scripts.php
+++ b/src/wp-includes/functions.wp-scripts.php
@@ -450,3 +450,20 @@ function wp_script_is( $handle, $status = 'enqueued' ) {
function wp_script_add_data( $handle, $key, $value ) {
return wp_scripts()->add_data( $handle, $key, $value );
}
+
+/**
+ * Prints data associated with enqueued scripts.
+ *
+ * @since 6.8.0
+ *
+ * @see WP_Scripts::print_script_data()
+ */
+function wp_print_script_data() {
+ global $wp_scripts;
+
+ if ( ! ( $wp_scripts instanceof WP_Scripts ) ) {
+ return;
+ }
+
+ $wp_scripts->print_script_data();
+}
diff --git a/tests/phpunit/tests/dependencies/scripts.php b/tests/phpunit/tests/dependencies/scripts.php
index afdac92106b72..3e7be7234baf0 100644
--- a/tests/phpunit/tests/dependencies/scripts.php
+++ b/tests/phpunit/tests/dependencies/scripts.php
@@ -4124,132 +4124,171 @@ public function test_wp_scripts_doing_it_wrong_for_missing_dependencies() {
}
/**
- * Tests that the script_data_{$handle} filter allows modifying localized script data.
+ * Tests that the script_data_{$handle} filter outputs JSON script tags.
*
- * @covers WP_Scripts::localize
+ * @covers WP_Scripts::print_script_data
*/
- public function test_script_data_filter_modifies_localized_data() {
+ public function test_script_data_filter_outputs_json_script_tag() {
wp_enqueue_script( 'test-script', '/test.js', array(), null );
- wp_localize_script( 'test-script', 'testData', array( 'foo' => 'bar' ) );
add_filter(
'script_data_test-script',
- function ( $l10n, $object_name, $handle ) {
- $this->assertSame( 'testData', $object_name );
- $this->assertSame( 'test-script', $handle );
- $this->assertIsArray( $l10n );
- $this->assertSame( 'bar', $l10n['foo'] );
- $l10n['baz'] = 'qux';
- return $l10n;
- },
- 10,
- 3
+ function ( $data ) {
+ $data['foo'] = 'bar';
+ return $data;
+ }
);
- $output = get_echo( 'wp_print_scripts' );
+ $output = get_echo( 'wp_print_script_data' );
+ $this->assertStringContainsString( '
+ *
+ * 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. 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;
}
- foreach ( array_keys( $scripts ) as $handle ) {
+ foreach ( array_unique( $this->queue ) as $handle ) {
/**
* Filters data associated with a given script.
*
@@ -1253,38 +1280,7 @@ public function print_script_data() {
*/
$data = apply_filters( "script_data_{$handle}", array() );
- if ( is_array( $data ) && array() !== $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 ``. 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. 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;
- }
-
+ if ( is_array( $data ) && ! empty( $data ) ) {
wp_print_inline_script_tag(
(string) wp_json_encode(
$data,
From 14e4ff92009298da32662e6da7d035d3d883bea6 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 12 Dec 2025 17:33:50 +0000
Subject: [PATCH 06/12] Remove redundant is_array check and improve test naming
Co-authored-by: sirreal <841763+sirreal@users.noreply.github.com>
---
src/wp-includes/class-wp-scripts.php | 2 +-
tests/phpunit/tests/dependencies/scripts.php | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/wp-includes/class-wp-scripts.php b/src/wp-includes/class-wp-scripts.php
index 034355a6a3309..affc55adac562 100644
--- a/src/wp-includes/class-wp-scripts.php
+++ b/src/wp-includes/class-wp-scripts.php
@@ -1280,7 +1280,7 @@ public function print_script_data() {
*/
$data = apply_filters( "script_data_{$handle}", array() );
- if ( is_array( $data ) && ! empty( $data ) ) {
+ if ( ! empty( $data ) ) {
wp_print_inline_script_tag(
(string) wp_json_encode(
$data,
diff --git a/tests/phpunit/tests/dependencies/scripts.php b/tests/phpunit/tests/dependencies/scripts.php
index 3e7be7234baf0..e57418c3c957b 100644
--- a/tests/phpunit/tests/dependencies/scripts.php
+++ b/tests/phpunit/tests/dependencies/scripts.php
@@ -4124,11 +4124,11 @@ public function test_wp_scripts_doing_it_wrong_for_missing_dependencies() {
}
/**
- * Tests that the script_data_{$handle} filter outputs JSON script tags.
+ * Tests that print_script_data outputs JSON script tags.
*
* @covers WP_Scripts::print_script_data
*/
- public function test_script_data_filter_outputs_json_script_tag() {
+ public function test_print_script_data_outputs_json_script_tag() {
wp_enqueue_script( 'test-script', '/test.js', array(), null );
add_filter(
From 16c9b3fb2d2731f2b24d2b27e122ae6b87e1272b Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 12 Dec 2025 17:46:51 +0000
Subject: [PATCH 07/12] Refactor: Integrate script_data filter directly into
do_item() method
- Removed separate print_script_data() method and wp_print_script_data() wrapper
- Removed action hooks from wp_footer and admin_print_footer_scripts
- Filter now runs during script processing in do_item()
- Data script tag is output immediately before each script tag
- Updated all tests to use wp_print_scripts instead of wp_print_script_data
- Added test to verify data tag appears before script tag
Co-authored-by: sirreal <841763+sirreal@users.noreply.github.com>
---
src/wp-admin/includes/admin-filters.php | 1 -
src/wp-includes/class-wp-scripts.php | 207 +++++++++----------
src/wp-includes/default-filters.php | 1 -
src/wp-includes/functions.wp-scripts.php | 17 --
tests/phpunit/tests/dependencies/scripts.php | 54 +++--
5 files changed, 120 insertions(+), 160 deletions(-)
diff --git a/src/wp-admin/includes/admin-filters.php b/src/wp-admin/includes/admin-filters.php
index e48cac473ef4d..6776f5898ad58 100644
--- a/src/wp-admin/includes/admin-filters.php
+++ b/src/wp-admin/includes/admin-filters.php
@@ -59,7 +59,6 @@
add_action( 'admin_print_scripts', 'print_emoji_detection_script' );
add_action( 'admin_print_scripts', 'print_head_scripts', 20 );
add_action( 'admin_print_footer_scripts', '_wp_footer_scripts' );
-add_action( 'admin_print_footer_scripts', 'wp_print_script_data', 21 );
add_action( 'admin_enqueue_scripts', 'wp_enqueue_emoji_styles' );
add_action( 'admin_print_styles', 'print_emoji_styles' ); // Retained for backwards-compatibility. Unhooked by wp_enqueue_emoji_styles().
add_action( 'admin_print_styles', 'print_admin_styles', 20 );
diff --git a/src/wp-includes/class-wp-scripts.php b/src/wp-includes/class-wp-scripts.php
index affc55adac562..15796241193c4 100644
--- a/src/wp-includes/class-wp-scripts.php
+++ b/src/wp-includes/class-wp-scripts.php
@@ -452,7 +452,101 @@ public function do_item( $handle, $group = false ) {
$attr['data-wp-fetchpriority'] = $original_fetchpriority;
}
- $tag = $translations . $before_script;
+ /**
+ * Filters data associated with a given script.
+ *
+ * Scripts may require data that is required for initialization or is essential
+ * to have immediately available on page load. These are suitable use cases for
+ * this data.
+ *
+ * The dynamic portion of the hook name, `$handle`, refers to the script handle.
+ *
+ * This is best suited to pass essential data that must be available to the script for
+ * initialization or immediately on page load. It does not replace the REST API or
+ * fetching data from the client.
+ *
+ * Example:
+ *
+ * add_filter(
+ * 'script_data_my-script-handle',
+ * function ( array $data ): array {
+ * $data['myConfig'] = array( 'key' => 'value' );
+ * return $data;
+ * }
+ * );
+ *
+ * If the filter returns no data (an empty array), nothing will be embedded in the page.
+ *
+ * The data for a given script, if provided, will be JSON serialized in a script
+ * tag with an ID of the form `wp-script-data-{$handle}` and type `application/json`.
+ *
+ * The data can be read on the client with a pattern like this:
+ *
+ * Example:
+ *
+ * const dataContainer = document.getElementById( 'wp-script-data-my-script-handle' );
+ * let data = {};
+ * if ( dataContainer ) {
+ * try {
+ * data = JSON.parse( dataContainer.textContent );
+ * } catch {}
+ * }
+ * // data.myConfig.key === 'value';
+ * initMyScriptWithData( data );
+ *
+ * @since 6.8.0
+ *
+ * @param array $data The data associated with the script.
+ */
+ $script_data = apply_filters( "script_data_{$handle}", array() );
+
+ $data_tag = '';
+ if ( ! empty( $script_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 ``. 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. 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;
+ }
+
+ $data_tag = wp_print_inline_script_tag(
+ (string) wp_json_encode(
+ $script_data,
+ $json_encode_flags
+ ),
+ array(
+ 'type' => 'application/json',
+ 'id' => "wp-script-data-{$handle}",
+ ),
+ false
+ );
+ }
+
+ $tag = $translations . $before_script . $data_tag;
$tag .= wp_get_script_tag( $attr );
$tag .= $after_script;
@@ -1183,115 +1277,4 @@ protected function get_dependency_warning_message( $handle, $missing_dependency_
);
}
- /**
- * Prints data associated with scripts.
- *
- * The data will be embedded in the page HTML and can be read by scripts on page load.
- *
- * Data can be associated with a script via the {@see "script_data_{$handle}"} filter.
- *
- * The data for a script will be serialized as JSON in a script tag with an ID of the
- * form `wp-script-data-{$handle}`.
- *
- * @since 6.8.0
- */
- public function print_script_data() {
- /*
- * Determine JSON encoding flags once, outside the loop.
- * The charset won't change during iteration.
- *
- * This data will be printed as JSON inside a script tag like this:
- *
- *
- * 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. 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;
- }
-
- foreach ( array_unique( $this->queue ) as $handle ) {
- /**
- * Filters data associated with a given script.
- *
- * The dynamic portion of the hook name, `$handle`, refers to the script handle.
- *
- * Scripts may require data that is required for initialization or is essential
- * to have immediately available on page load. These are suitable use cases for
- * this data.
- *
- * This is best suited to pass essential data that must be available to the script for
- * initialization or immediately on page load. It does not replace the REST API or
- * fetching data from the client.
- *
- * Example:
- *
- * add_filter(
- * 'script_data_my-script-handle',
- * function ( array $data ): array {
- * $data['myData'] = array(
- * 'option' => get_option( 'my_option' ),
- * );
- * return $data;
- * }
- * );
- *
- * If the filter returns no data (an empty array), nothing will be embedded in the page.
- *
- * The data for a given script, if provided, will be JSON serialized in a script
- * tag with an ID of the form `wp-script-data-{$handle}`.
- *
- * The data can be read on the client with a pattern like this:
- *
- * Example:
- *
- * const dataContainer = document.getElementById( 'wp-script-data-my-script-handle' );
- * let data = {};
- * if ( dataContainer ) {
- * try {
- * data = JSON.parse( dataContainer.textContent );
- * } catch {}
- * }
- * initMyScriptWithData( data );
- *
- * @since 6.8.0
- *
- * @param array $data The data associated with the script.
- */
- $data = apply_filters( "script_data_{$handle}", array() );
-
- if ( ! empty( $data ) ) {
- wp_print_inline_script_tag(
- (string) wp_json_encode(
- $data,
- $json_encode_flags
- ),
- array(
- 'type' => 'application/json',
- 'id' => "wp-script-data-{$handle}",
- )
- );
- }
- }
- }
}
diff --git a/src/wp-includes/default-filters.php b/src/wp-includes/default-filters.php
index 0eac638ddcce7..68dccd979f2fe 100644
--- a/src/wp-includes/default-filters.php
+++ b/src/wp-includes/default-filters.php
@@ -361,7 +361,6 @@
add_action( 'wp_head', 'wp_site_icon', 99 );
add_action( 'wp_footer', 'wp_print_speculation_rules' );
add_action( 'wp_footer', 'wp_print_footer_scripts', 20 );
-add_action( 'wp_footer', 'wp_print_script_data', 21 );
add_action( 'template_redirect', 'wp_shortlink_header', 11, 0 );
add_action( 'wp_print_footer_scripts', '_wp_footer_scripts' );
add_action( 'init', '_register_core_block_patterns_and_categories' );
diff --git a/src/wp-includes/functions.wp-scripts.php b/src/wp-includes/functions.wp-scripts.php
index c34b8bca92df4..f86b456d5f69a 100644
--- a/src/wp-includes/functions.wp-scripts.php
+++ b/src/wp-includes/functions.wp-scripts.php
@@ -450,20 +450,3 @@ function wp_script_is( $handle, $status = 'enqueued' ) {
function wp_script_add_data( $handle, $key, $value ) {
return wp_scripts()->add_data( $handle, $key, $value );
}
-
-/**
- * Prints data associated with enqueued scripts.
- *
- * @since 6.8.0
- *
- * @see WP_Scripts::print_script_data()
- */
-function wp_print_script_data() {
- global $wp_scripts;
-
- if ( ! ( $wp_scripts instanceof WP_Scripts ) ) {
- return;
- }
-
- $wp_scripts->print_script_data();
-}
diff --git a/tests/phpunit/tests/dependencies/scripts.php b/tests/phpunit/tests/dependencies/scripts.php
index e57418c3c957b..44f097eba68fb 100644
--- a/tests/phpunit/tests/dependencies/scripts.php
+++ b/tests/phpunit/tests/dependencies/scripts.php
@@ -4124,11 +4124,11 @@ public function test_wp_scripts_doing_it_wrong_for_missing_dependencies() {
}
/**
- * Tests that print_script_data outputs JSON script tags.
+ * Tests that script_data_{$handle} filter outputs JSON script tags before the script.
*
- * @covers WP_Scripts::print_script_data
+ * @covers WP_Scripts::do_item
*/
- public function test_print_script_data_outputs_json_script_tag() {
+ public function test_script_data_filter_outputs_json_script_tag() {
wp_enqueue_script( 'test-script', '/test.js', array(), null );
add_filter(
@@ -4139,7 +4139,7 @@ function ( $data ) {
}
);
- $output = get_echo( 'wp_print_script_data' );
+ $output = get_echo( 'wp_print_scripts' );
$this->assertStringContainsString( '