Skip to content

Commit

Permalink
Script Loader: Use wp_get_script_tag() and `wp_get_inline_script_ta…
Browse files Browse the repository at this point in the history
…g()`/`wp_print_inline_script_tag()` helper functions to output scripts on the frontend and login screen.

Using script tag helper functions allows plugins to employ the `wp_script_attributes` and `wp_inline_script_attributes` filters to inject the `nonce` attribute to apply Content Security Policy (e.g. Strict CSP). Use of helper functions also simplifies logic in `WP_Scripts`.

* Update `wp_get_inline_script_tag()` to wrap inline script in CDATA blocks for XHTML-compatibility when not using HTML5.
* Ensure the `type` attribute is printed first in `wp_get_inline_script_tag()` for back-compat.
* Wrap existing `<script>` tags in output buffering to retain IDE supports.
* In `wp_get_inline_script_tag()`, append the newline to `$javascript` before it is passed into the `wp_inline_script_attributes` filter so that the CSP hash can be computed properly.
* In `the_block_template_skip_link()`, opt to enqueue the inline script rather than print it.
* Add `ext-php` to `composer.json` under `suggest` as previously it was an undeclared dependency for running PHPUnit tests.
* Update tests to rely on `DOMDocument` to compare script markup, normalizing unsemantic differences.

Props westonruter, spacedmonkey, flixos90, 10upsimon, dmsnell, mukesh27, joemcgill, swissspidy, azaozz.
Fixes #58664.
See #39941.


git-svn-id: https://develop.svn.wordpress.org/trunk@56687 602fd350-edb4-49c9-b593-d223f7449a82
  • Loading branch information
westonruter authored and Anton Vlasenko committed Sep 29, 2023
1 parent 61bc455 commit bcee361
Show file tree
Hide file tree
Showing 17 changed files with 214 additions and 147 deletions.
3 changes: 3 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
"require": {
"php": ">=7.0"
},
"suggest": {
"ext-dom": "*"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "^0.7.0",
"squizlabs/php_codesniffer": "3.6.0",
Expand Down
14 changes: 11 additions & 3 deletions src/wp-includes/class-wp-customize-manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,8 @@ protected function wp_die( $ajax_message, $message = null ) {
),
'error' => $ajax_message,
);
$message .= ob_get_clean();
ob_start();
?>
<script>
( function( api, settings ) {
Expand All @@ -472,7 +474,7 @@ protected function wp_die( $ajax_message, $message = null ) {
} )( wp.customize, <?php echo wp_json_encode( $settings ); ?> );
</script>
<?php
$message .= ob_get_clean();
$message .= wp_get_inline_script_tag( str_replace( array( '<script>', '</script>' ), '', ob_get_clean() ) );
}

wp_die( $message );
Expand Down Expand Up @@ -2083,6 +2085,7 @@ public function remove_frameless_preview_messenger_channel() {
if ( ! $this->messenger_channel ) {
return;
}
ob_start();
?>
<script>
( function() {
Expand All @@ -2106,6 +2109,7 @@ public function remove_frameless_preview_messenger_channel() {
} )();
</script>
<?php
wp_print_inline_script_tag( str_replace( array( '<script>', '</script>' ), '', ob_get_clean() ) );
}

/**
Expand Down Expand Up @@ -2201,8 +2205,9 @@ public function customize_preview_settings() {
}
}

ob_start();
?>
<script type="text/javascript">
<script>
var _wpCustomizeSettings = <?php echo wp_json_encode( $settings ); ?>;
_wpCustomizeSettings.values = {};
(function( v ) {
Expand All @@ -2225,6 +2230,7 @@ public function customize_preview_settings() {
})( _wpCustomizeSettings.values );
</script>
<?php
wp_print_inline_script_tag( str_replace( array( '<script>', '</script>' ), '', ob_get_clean() ) );
}

/**
Expand Down Expand Up @@ -4976,8 +4982,9 @@ public function customize_pane_settings() {
}
}

ob_start();
?>
<script type="text/javascript">
<script>
var _wpCustomizeSettings = <?php echo wp_json_encode( $settings ); ?>;
_wpCustomizeSettings.initialClientTimestamp = _.now();
_wpCustomizeSettings.controls = {};
Expand Down Expand Up @@ -5012,6 +5019,7 @@ public function customize_pane_settings() {
?>
</script>
<?php
wp_print_inline_script_tag( str_replace( array( '<script>', '</script>' ), '', ob_get_clean() ) );
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/wp-includes/class-wp-customize-nav-menus.php
Original file line number Diff line number Diff line change
Expand Up @@ -1559,7 +1559,7 @@ public function export_preview_data() {
$exports = array(
'navMenuInstanceArgs' => $this->preview_nav_menu_instance_args,
);
printf( '<script>var _wpCustomizePreviewNavMenusExports = %s;</script>', wp_json_encode( $exports ) );
wp_print_inline_script_tag( sprintf( 'var _wpCustomizePreviewNavMenusExports = %s;', wp_json_encode( $exports ) ) );
}

/**
Expand Down
9 changes: 3 additions & 6 deletions src/wp-includes/class-wp-customize-widgets.php
Original file line number Diff line number Diff line change
Expand Up @@ -1310,12 +1310,9 @@ public function export_preview_data() {
foreach ( $settings['registeredWidgets'] as &$registered_widget ) {
unset( $registered_widget['callback'] ); // May not be JSON-serializeable.
}

?>
<script type="text/javascript">
var _wpWidgetCustomizerPreviewSettings = <?php echo wp_json_encode( $settings ); ?>;
</script>
<?php
wp_print_inline_script_tag(
sprintf( 'var _wpWidgetCustomizerPreviewSettings = %s;', wp_json_encode( $settings ) )
);
}

/**
Expand Down
59 changes: 15 additions & 44 deletions src/wp-includes/class-wp-scripts.php
Original file line number Diff line number Diff line change
Expand Up @@ -122,17 +122,6 @@ class WP_Scripts extends WP_Dependencies {
*/
public $default_dirs;

/**
* Holds a string which contains the type attribute for script tag.
*
* If the active theme does not declare HTML5 support for 'script',
* then it initializes as `type='text/javascript'`.
*
* @since 5.3.0
* @var string
*/
private $type_attr = '';

/**
* Holds a mapping of dependents (as handles) for a given script handle.
* Used to optimize recursive dependency tree checks.
Expand Down Expand Up @@ -167,14 +156,6 @@ public function __construct() {
* @since 3.4.0
*/
public function init() {
if (
function_exists( 'is_admin' ) && ! is_admin()
&&
function_exists( 'current_theme_supports' ) && ! current_theme_supports( 'html5', 'script' )
) {
$this->type_attr = " type='text/javascript'";
}

/**
* Fires when the WP_Scripts instance is initialized.
*
Expand Down Expand Up @@ -245,20 +226,7 @@ public function print_extra_script( $handle, $display = true ) {
return $output;
}

printf( "<script%s id='%s-js-extra'>\n", $this->type_attr, esc_attr( $handle ) );

// CDATA is not needed for HTML 5.
if ( $this->type_attr ) {
echo "/* <![CDATA[ */\n";
}

echo "$output\n";

if ( $this->type_attr ) {
echo "/* ]]> */\n";
}

echo "</script>\n";
wp_print_inline_script_tag( $output, array( 'id' => "{$handle}-js-extra" ) );

return true;
}
Expand Down Expand Up @@ -335,7 +303,7 @@ public function do_item( $handle, $group = false ) {

$translations = $this->print_translations( $handle, false );
if ( $translations ) {
$translations = sprintf( "<script%s id='%s-js-translations'>\n%s\n</script>\n", $this->type_attr, esc_attr( $handle ), $translations );
$translations = wp_get_inline_script_tag( $translations, array( 'id' => "{$handle}-js-translations" ) );
}

if ( $this->do_concat ) {
Expand Down Expand Up @@ -403,21 +371,24 @@ public function do_item( $handle, $group = false ) {
}

/** This filter is documented in wp-includes/class-wp-scripts.php */
$src = esc_url( apply_filters( 'script_loader_src', $src, $handle ) );
$src = esc_url_raw( apply_filters( 'script_loader_src', $src, $handle ) );

if ( ! $src ) {
return true;
}

$tag = $translations . $cond_before . $before_script;
$tag .= sprintf(
"<script%s src='%s' id='%s-js'%s%s></script>\n",
$this->type_attr,
$src, // Value is escaped above.
esc_attr( $handle ),
$strategy ? " {$strategy}" : '',
$intended_strategy ? " data-wp-strategy='{$intended_strategy}'" : ''
$attr = array(
'src' => $src,
'id' => "{$handle}-js",
);
if ( $strategy ) {
$attr[ $strategy ] = true;
}
if ( $intended_strategy ) {
$attr['data-wp-strategy'] = $intended_strategy;
}
$tag = $translations . $cond_before . $before_script;
$tag .= wp_get_script_tag( $attr );
$tag .= $after_script . $cond_after;

/**
Expand Down Expand Up @@ -720,7 +691,7 @@ public function print_translations( $handle, $display = true ) {
JS;

if ( $display ) {
printf( "<script%s id='%s-js-translations'>\n%s\n</script>\n", $this->type_attr, esc_attr( $handle ), $output );
wp_print_inline_script_tag( $output, array( 'id' => "{$handle}-js-translations" ) );
}

return $output;
Expand Down
2 changes: 1 addition & 1 deletion src/wp-includes/comment-template.php
Original file line number Diff line number Diff line change
Expand Up @@ -1366,7 +1366,7 @@ function wp_comment_form_unfiltered_html_nonce() {

if ( current_user_can( 'unfiltered_html' ) ) {
wp_nonce_field( 'unfiltered-html-comment_' . $post_id, '_wp_unfiltered_html_comment_disabled', false );
echo "<script>(function(){if(window===window.parent){document.getElementById('_wp_unfiltered_html_comment_disabled').name='_wp_unfiltered_html_comment';}})();</script>\n";
wp_print_inline_script_tag( "(function(){if(window===window.parent){document.getElementById('_wp_unfiltered_html_comment_disabled').name='_wp_unfiltered_html_comment';}})();" );
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ public function export_preview_data() {
);

// Export data to JS.
printf( '<script>var _customizePartialRefreshExports = %s;</script>', wp_json_encode( $exports ) );
wp_print_inline_script_tag( sprintf( 'var _customizePartialRefreshExports = %s;', wp_json_encode( $exports ) ) );
}

/**
Expand Down
2 changes: 2 additions & 0 deletions src/wp-includes/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -7655,6 +7655,7 @@ function wp_post_preview_js() {
// Has to match the window name used in post_submit_meta_box().
$name = 'wp-preview-' . (int) $post->ID;

ob_start();
?>
<script>
( function() {
Expand All @@ -7670,6 +7671,7 @@ function wp_post_preview_js() {
}());
</script>
<?php
wp_print_inline_script_tag( str_replace( array( '<script>', '</script>' ), '', ob_get_clean() ) );
}

/**
Expand Down
26 changes: 21 additions & 5 deletions src/wp-includes/script-loader.php
Original file line number Diff line number Diff line change
Expand Up @@ -2787,7 +2787,11 @@ function wp_sanitize_script_attributes( $attributes ) {
*/
function wp_get_script_tag( $attributes ) {
if ( ! isset( $attributes['type'] ) && ! is_admin() && ! current_theme_supports( 'html5', 'script' ) ) {
$attributes['type'] = 'text/javascript';
// Keep the type attribute as the first for legacy reasons (it has always been this way in core).
$attributes = array_merge(
array( 'type' => 'text/javascript' ),
$attributes
);
}
/**
* Filters attributes to be added to a script tag.
Expand Down Expand Up @@ -2830,9 +2834,23 @@ function wp_print_script_tag( $attributes ) {
* @return string String containing inline JavaScript code wrapped around `<script>` tag.
*/
function wp_get_inline_script_tag( $javascript, $attributes = array() ) {
if ( ! isset( $attributes['type'] ) && ! is_admin() && ! current_theme_supports( 'html5', 'script' ) ) {
$attributes['type'] = 'text/javascript';
$is_html5 = current_theme_supports( 'html5', 'script' ) || is_admin();
if ( ! isset( $attributes['type'] ) && ! $is_html5 ) {
// Keep the type attribute as the first for legacy reasons (it has always been this way in core).
$attributes = array_merge(
array( 'type' => 'text/javascript' ),
$attributes
);
}

// Ensure markup is XHTML compatible if not HTML5.
if ( ! $is_html5 ) {
$javascript = str_replace( ']]>', ']]]]><![CDATA[>', $javascript ); // Escape any existing CDATA section.
$javascript = sprintf( "/* <![CDATA[ */\n%s\n/* ]]> */", $javascript );
}

$javascript = "\n" . trim( $javascript, "\n\r " ) . "\n";

/**
* Filters attributes to be added to a script tag.
*
Expand All @@ -2845,8 +2863,6 @@ function wp_get_inline_script_tag( $javascript, $attributes = array() ) {
*/
$attributes = apply_filters( 'wp_inline_script_attributes', $attributes, $javascript );

$javascript = "\n" . trim( $javascript, "\n\r " ) . "\n";

return sprintf( "<script%s>%s</script>\n", wp_sanitize_script_attributes( $attributes ), $javascript );
}

Expand Down
8 changes: 7 additions & 1 deletion src/wp-includes/theme-templates.php
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,9 @@ function the_block_template_skip_link() {
wp_enqueue_style( $handle );

/**
* Print the skip-link script.
* Enqueue the skip-link script.
*/
ob_start();
?>
<script>
( function() {
Expand Down Expand Up @@ -204,6 +205,11 @@ function the_block_template_skip_link() {
}() );
</script>
<?php
$skip_link_script = str_replace( array( '<script>', '</script>' ), '', ob_get_clean() );
$script_handle = 'wp-block-template-skip-link';
wp_register_script( $script_handle, false );
wp_add_inline_script( $script_handle, $skip_link_script );
wp_enqueue_script( $script_handle );
}

/**
Expand Down
5 changes: 3 additions & 2 deletions src/wp-includes/theme.php
Original file line number Diff line number Diff line change
Expand Up @@ -3783,9 +3783,9 @@ function wp_customize_support_script() {
$admin_origin = parse_url( admin_url() );
$home_origin = parse_url( home_url() );
$cross_domain = ( strtolower( $admin_origin['host'] ) !== strtolower( $home_origin['host'] ) );
$type_attr = current_theme_supports( 'html5', 'script' ) ? '' : ' type="text/javascript"';
ob_start();
?>
<script<?php echo $type_attr; ?>>
<script>
(function() {
var request, b = document.body, c = 'className', cs = 'customize-support', rcs = new RegExp('(^|\\s+)(no-)?'+cs+'(\\s+|$)');

Expand All @@ -3801,6 +3801,7 @@ function wp_customize_support_script() {
}());
</script>
<?php
wp_print_inline_script_tag( str_replace( array( '<script>', '</script>' ), '', ob_get_clean() ) );
}

/**
Expand Down
8 changes: 3 additions & 5 deletions src/wp-includes/widgets/class-wp-widget-archives.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,17 +100,15 @@ public function widget( $args, $instance ) {
$label = __( 'Select Post' );
break;
}

$type_attr = current_theme_supports( 'html5', 'script' ) ? '' : ' type="text/javascript"';
?>

<option value=""><?php echo esc_html( $label ); ?></option>
<?php wp_get_archives( $dropdown_args ); ?>

</select>

<script<?php echo $type_attr; ?>>
/* <![CDATA[ */
<?php ob_start(); ?>
<script>
(function() {
var dropdown = document.getElementById( "<?php echo esc_js( $dropdown_id ); ?>" );
function onSelectChange() {
Expand All @@ -120,9 +118,9 @@ function onSelectChange() {
}
dropdown.onchange = onSelectChange;
})();
/* ]]> */
</script>
<?php
wp_print_inline_script_tag( str_replace( array( '<script>', '</script>' ), '', ob_get_clean() ) );
} else {
$format = current_theme_supports( 'html5', 'navigation-widgets' ) ? 'html5' : 'xhtml';

Expand Down
7 changes: 3 additions & 4 deletions src/wp-includes/widgets/class-wp-widget-categories.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,10 @@ public function widget( $args, $instance ) {

echo '</form>';

$type_attr = current_theme_supports( 'html5', 'script' ) ? '' : ' type="text/javascript"';
ob_start();
?>

<script<?php echo $type_attr; ?>>
/* <![CDATA[ */
<script>
(function() {
var dropdown = document.getElementById( "<?php echo esc_js( $dropdown_id ); ?>" );
function onCatChange() {
Expand All @@ -106,10 +105,10 @@ function onCatChange() {
}
dropdown.onchange = onCatChange;
})();
/* ]]> */
</script>

<?php
wp_print_inline_script_tag( str_replace( array( '<script>', '</script>' ), '', ob_get_clean() ) );
} else {
$format = current_theme_supports( 'html5', 'navigation-widgets' ) ? 'html5' : 'xhtml';

Expand Down
Loading

0 comments on commit bcee361

Please sign in to comment.