diff --git a/src/wp-includes/class-wp-scripts.php b/src/wp-includes/class-wp-scripts.php
index cb37b2b653877..6870383cc63e3 100644
--- a/src/wp-includes/class-wp-scripts.php
+++ b/src/wp-includes/class-wp-scripts.php
@@ -508,15 +508,20 @@ public function do_item( $handle, $group = false ) {
* Adds extra code to a registered script.
*
* @since 4.5.0
+ * @since x.x.x Added the `$type` parameter.
*
* @param string $handle Name of the script to add the inline script to.
* Must be lowercase.
* @param string $data String containing the JavaScript to be added.
* @param string $position Optional. Whether to add the inline script
* before the handle or after. Default 'after'.
+ * @param string $type Optional. Value of the type attribute for the inline script tag.
+ * Use this to set a custom type such as 'application/json' or
+ * 'application/ld+json'. Scripts with a custom type are rendered
+ * in their own script tag separate from JavaScript. Default empty string.
* @return bool True on success, false on failure.
*/
- public function add_inline_script( $handle, $data, $position = 'after' ) {
+ public function add_inline_script( $handle, $data, $position = 'after', $type = '' ) {
if ( ! $data ) {
return false;
}
@@ -528,6 +533,17 @@ public function add_inline_script( $handle, $data, $position = 'after' ) {
$script = (array) $this->get_data( $handle, $position );
$script[] = $data;
+ // Store the type for this specific inline script if provided.
+ if ( '' !== $type ) {
+ $types_key = "{$position}_types";
+ $types = $this->get_data( $handle, $types_key );
+ if ( ! is_array( $types ) ) {
+ $types = array();
+ }
+ $types[ count( $script ) - 1 ] = $type;
+ $this->add_data( $handle, $types_key, $types );
+ }
+
return $this->add_data( $handle, $position, $script );
}
@@ -562,7 +578,12 @@ public function print_inline_script( $handle, $position = 'after', $display = tr
/**
* Gets data for inline scripts registered for a specific handle.
*
+ * Returns only the JavaScript inline scripts (those without a custom type).
+ * Scripts with a custom type are excluded as they are not JavaScript and
+ * should not be concatenated with JS code.
+ *
* @since 6.3.0
+ * @since x.x.x Scripts with a custom type are excluded from the returned data.
*
* @param string $handle Name of the script to get data for.
* Must be lowercase.
@@ -576,6 +597,25 @@ public function get_inline_script_data( $handle, $position = 'after' ) {
return '';
}
+ // Exclude scripts that have a custom type (non-JavaScript).
+ $types = $this->get_data( $handle, "{$position}_types" );
+ if ( is_array( $types ) && ! empty( $types ) ) {
+ $data = array_filter(
+ $data,
+ static function ( $index ) use ( $types ) {
+ return ! isset( $types[ $index ] ) || '' === $types[ $index ];
+ },
+ ARRAY_FILTER_USE_KEY
+ );
+ }
+
+ // Remove empty entries that may be artifacts of data initialization.
+ $data = array_filter( $data );
+
+ if ( empty( $data ) ) {
+ return '';
+ }
+
/*
* Print sourceURL comment regardless of concatenation.
*
@@ -593,7 +633,11 @@ public function get_inline_script_data( $handle, $position = 'after' ) {
/**
* Gets tags for inline scripts registered for a specific handle.
*
+ * Scripts with a custom type are rendered in separate script tags
+ * from the default JavaScript inline scripts.
+ *
* @since 6.3.0
+ * @since x.x.x Scripts with a custom type are rendered in their own script tag.
*
* @param string $handle Name of the script to get associated inline script tag for.
* Must be lowercase.
@@ -602,14 +646,38 @@ public function get_inline_script_data( $handle, $position = 'after' ) {
* @return string Inline script, which may be empty string.
*/
public function get_inline_script_tag( $handle, $position = 'after' ) {
- $js = $this->get_inline_script_data( $handle, $position );
- if ( empty( $js ) ) {
+ $data = $this->get_data( $handle, $position );
+ if ( empty( $data ) || ! is_array( $data ) ) {
return '';
}
- $id = "{$handle}-js-{$position}";
+ $types = $this->get_data( $handle, "{$position}_types" );
+ if ( ! is_array( $types ) ) {
+ $types = array();
+ }
+
+ $output = '';
- return wp_get_inline_script_tag( $js, compact( 'id' ) );
+ // Render the default JavaScript inline scripts (concatenated).
+ $js = $this->get_inline_script_data( $handle, $position );
+ if ( ! empty( $js ) ) {
+ $id = "{$handle}-js-{$position}";
+ $output .= wp_get_inline_script_tag( $js, compact( 'id' ) );
+ }
+
+ // Render scripts with custom types in their own separate script tags.
+ if ( ! empty( $types ) ) {
+ foreach ( $types as $index => $type ) {
+ if ( '' === $type || ! isset( $data[ $index ] ) ) {
+ continue;
+ }
+
+ $id = "{$handle}-js-{$position}-{$index}";
+ $output .= wp_get_inline_script_tag( $data[ $index ], compact( 'id', 'type' ) );
+ }
+ }
+
+ return $output;
}
/**
diff --git a/src/wp-includes/functions.wp-scripts.php b/src/wp-includes/functions.wp-scripts.php
index 59e4e54a1a1ad..a41bf30f9824e 100644
--- a/src/wp-includes/functions.wp-scripts.php
+++ b/src/wp-includes/functions.wp-scripts.php
@@ -161,6 +161,7 @@ function wp_print_scripts( $handles = false ) {
* they were added, i.e. the latter added code can redeclare the previous.
*
* @since 4.5.0
+ * @since x.x.x Added the `$type` parameter.
*
* @see WP_Scripts::add_inline_script()
*
@@ -168,9 +169,13 @@ function wp_print_scripts( $handles = false ) {
* @param string $data String containing the JavaScript to be added.
* @param string $position Optional. Whether to add the inline script before the handle
* or after. Default 'after'.
+ * @param string $type Optional. Value of the type attribute for the inline script tag.
+ * Use this to set a custom type such as 'application/json' or
+ * 'application/ld+json'. Scripts with a custom type are rendered
+ * in their own script tag separate from JavaScript. Default empty string.
* @return bool True on success, false on failure.
*/
-function wp_add_inline_script( $handle, $data, $position = 'after' ) {
+function wp_add_inline_script( $handle, $data, $position = 'after', $type = '' ) {
_wp_scripts_maybe_doing_it_wrong( __FUNCTION__, $handle );
if ( false !== stripos( $data, '' ) ) {
@@ -187,7 +192,7 @@ function wp_add_inline_script( $handle, $data, $position = 'after' ) {
$data = trim( (string) preg_replace( '##is', '$1', $data ) );
}
- return wp_scripts()->add_inline_script( $handle, $data, $position );
+ return wp_scripts()->add_inline_script( $handle, $data, $position, $type );
}
/**
diff --git a/tests/phpunit/tests/dependencies/scripts.php b/tests/phpunit/tests/dependencies/scripts.php
index 5f1c30fe4cf47..b182d185e3556 100644
--- a/tests/phpunit/tests/dependencies/scripts.php
+++ b/tests/phpunit/tests/dependencies/scripts.php
@@ -4307,6 +4307,74 @@ public function data_varying_versions_handle_args() {
);
}
+ /**
+ * @ticket 51124
+ *
+ * @covers ::wp_add_inline_script
+ * @covers WP_Scripts::add_inline_script
+ * @covers WP_Scripts::get_inline_script_tag
+ */
+ public function test_wp_add_inline_script_with_custom_type() {
+ wp_enqueue_script( 'test-example', 'example.com', array(), null );
+ wp_add_inline_script( 'test-example', '{"key":"value"}', 'after', 'application/json' );
+ wp_add_inline_script( 'test-example', '{"config":true}', 'before', 'application/json' );
+
+ $expected = "\n";
+ $expected .= "\n";
+ $expected .= "\n";
+
+ $this->assertEqualHTML( $expected, get_echo( 'wp_print_scripts' ) );
+ }
+
+ /**
+ * @ticket 51124
+ *
+ * @covers ::wp_add_inline_script
+ * @covers WP_Scripts::get_inline_script_data
+ * @covers WP_Scripts::get_inline_script_tag
+ */
+ public function test_wp_add_inline_script_custom_type_separated_from_js() {
+ wp_enqueue_script( 'test-example', 'example.com', array(), null );
+ wp_add_inline_script( 'test-example', 'console.log("js");', 'after' );
+ wp_add_inline_script( 'test-example', '{"key":"value"}', 'after', 'application/json' );
+ wp_add_inline_script( 'test-example', 'console.log("more js");', 'after' );
+
+ $expected = "\n";
+ $expected .= "\n";
+ $expected .= "\n";
+
+ $output = get_echo( 'wp_print_scripts' );
+ $this->assertEqualHTML( $expected, $output );
+
+ // Typed scripts must not appear in get_inline_script_data() output.
+ wp_enqueue_script( 'test-verify', 'example2.com', array(), null );
+ wp_add_inline_script( 'test-verify', 'var x = 1;', 'after' );
+ wp_add_inline_script( 'test-verify', '{"excluded":true}', 'after', 'application/ld+json' );
+
+ global $wp_scripts;
+ $data = $wp_scripts->get_inline_script_data( 'test-verify', 'after' );
+ $this->assertStringContainsString( 'var x = 1;', $data );
+ $this->assertStringNotContainsString( '{"excluded":true}', $data );
+ }
+
+ /**
+ * @ticket 51124
+ *
+ * @covers ::wp_add_inline_script
+ * @covers WP_Scripts::get_inline_script_tag
+ */
+ public function test_wp_add_inline_script_multiple_custom_types() {
+ wp_enqueue_script( 'test-example', 'example.com', array(), null );
+ wp_add_inline_script( 'test-example', '{"config":1}', 'after', 'application/json' );
+ wp_add_inline_script( 'test-example', '{"@type":"Thing"}', 'after', 'application/ld+json' );
+
+ $expected = "\n";
+ $expected .= "\n";
+ $expected .= "\n";
+
+ $this->assertEqualHTML( $expected, get_echo( 'wp_print_scripts' ) );
+ }
+
/**
* Normalizes markup for snapshot.
*