Make WordPress Core

Opened 5 months ago

Closed 3 months ago

#63806 closed enhancement (fixed)

Bundled themes: Scripts are printed directly without using wp_print_script_tag()/wp_print_inline_script_tag()

Reported by: westonruter's profile westonruter Owned by: westonruter's profile westonruter
Milestone: 6.9 Priority: normal
Severity: normal Version: 5.7
Component: Bundled Theme Keywords: has-patch commit
Focuses: javascript Cc:

Description

Core was updated in #59446 to use the script helper functions—wp_get_script_tag(), wp_print_inline_script_tag(), wp_get_inline_script_tag()—were leveraged to eliminate manual construction of script tags on the frontend and the login screen. These were introduced in #39941.

However, the core themes have not all been updated to use these functions, meaning they cannot opt in to a Strict Content Security Policy. See examples.

Instead of a theme doing something like this:

<?php
function my_theme_supports_js() {
        echo '<script>document.body.classList.remove("no-js");</script>'; // ❌ DO NOT DO THIS 👎
}
add_action( 'wp_footer', 'my_theme_supports_js' );

It should be updated to do:

<?php
function my_theme_supports_js() {
        wp_print_inline_script_tag( 'document.body.classList.remove("no-js");' ); // ✅ Do this instead 👍
}
add_action( 'wp_footer', 'my_theme_supports_js' );

Note that if a bundled theme was introduced for a WordPress version prior to 5.7 then it won't have the necessary helper functions available. In their case, either they should be skipped or the necessary functions can be included as polyfills.

See also #59446 which tracks this for the WP Admin.

See the Strict CSP plugin as a way to enforce Strict CSP to help discover if there are scripts being manually printed.

Change History (19)

#1 @westonruter
5 months ago

  • Summary changed from Bundled themes: Scripts are printed directly without using wp_print_inline_script_tag() to Bundled themes: Scripts are printed directly without using wp_print_script_tag()/wp_print_inline_script_tag()

This ticket was mentioned in PR #9415 on WordPress/wordpress-develop by @iamadisingh.


5 months ago
#2

  • Keywords has-patch added; needs-patch removed

This PR updates bundled themes to use wp_print_inline_script_tag() instead of manual <script> tag output, enabling Strict Content Security Policy (CSP) compatibility.

Trac ticket: https://core.trac.wordpress.org/ticket/63806

#3 @poena
5 months ago

Since we are not allowed to change the minimum required WordPress versions of the themes, the themes that support a version lower than were the functions were introduced, need a fallback.

For example Twenty Fifteen needs to work on WordPress version 4.1.
wp_print_inline_script_tag was introduced in 5.7.0.

Last edited 5 months ago by poena (previous) (diff)

#4 @westonruter
5 months ago

@poena yes, for themes introduced prior to 5.7, the necessary functions can shimmed by copying them from core into the themes' functions.php files. For example, Twenty Twenty has the following:

<?php
if ( ! function_exists( 'wp_body_open' ) ) {

        /**
         * Shim for wp_body_open, ensuring backward compatibility with versions of WordPress older than 5.2.
         *
         * @since Twenty Twenty 1.0
         */
        function wp_body_open() {
                /**
                 * Triggered after the opening <body> tag.
                 *
                 * @since Twenty Twenty 1.0
                 */
                do_action( 'wp_body_open' );
        }
}

Similarly, Twenty Twelve does the following:

<?php
if ( ! function_exists( 'wp_get_list_item_separator' ) ) :
        /**
         * Retrieves the list item separator based on the locale.
         *
         * Added for backward compatibility to support pre-6.0.0 WordPress versions.
         *
         * @since 6.0.0
         */
        function wp_get_list_item_separator() {
                /* translators: Used between list items, there is a space after the comma. */
                return __( ', ', 'twentytwelve' );
        }
endif;

This ticket was mentioned in PR #9416 on WordPress/wordpress-develop by @debarghyabanerjee.


5 months ago
#5

Trac Ticket: Core-63806

This pull request updates all inline script outputs in the bundled themes to use WordPress’s script helper functions, specifically wp_print_inline_script_tag(), in place of manually constructed <script> tags.

---

## ✅ Why This Matters

As of #59446, WordPress Core has adopted the use of wp_get_script_tag(), wp_get_inline_script_tag(), and wp_print_inline_script_tag() to eliminate manually constructed <script> tags. This change was made to:

However, many default and third-party themes still use raw <script> tags, which prevents them from fully benefiting from these improvements.

---

## 🛠 What’s Changed

  • Replaced all instances of echo '<script>...</script>' with calls to wp_print_inline_script_tag().

---

## 🔙 Backward Compatibility
Since these helper functions were introduced in WordPress 5.7+, this PR also includes polyfill definitions in functions.php to ensure compatibility with earlier WordPress versions.

The polyfills conditionally define wp_print_inline_script_tag() and wp_get_inline_script_tag() functions only if they don’t already exist, making it safe for all supported versions.

#6 follow-up: @sabernhardt
5 months ago

  • Keywords good-first-bug removed
  1. The three twenty*_skip_link_focus_fix functions are deprecated; I prefer not to edit those at all.
  2. Polyfills might need updating later, and I think any site that opts in for a strict CSP should have a newer version of WordPress than 5.7. Could the existing theme functions simply check if the wp_print_inline_script_tag function exists and print/echo if not?
    /**
     * JavaScript Detection.
     *
     * Adds a `js` class to the root `<html>` element when JavaScript is detected.
     *
     * @since Twenty Fifteen 1.1
     * @since Twenty Fifteen 4.1 Added `wp_print_inline_script_tag()` support.
     */
    function twentyfifteen_javascript_detection() {
            $js = "(function(html){html.className = html.className.replace(/\bno-js\b/,'js')})(document.documentElement);";
    
            if ( function_exists( 'wp_print_inline_script_tag' ) ) {
                    wp_print_inline_script_tag( $js );
            } else {
                    printf( "<script>%s</script>\n", $js );
            }
    }
    add_action( 'wp_head', 'twentyfifteen_javascript_detection', 0 );
    
  3. I think the auto-focus script in Twenty Ten's 404.php template should be removed entirely. Screen readers currently announce the search form without the context of the heading and explanatory paragraph. (separate ticket: #64064)
  4. Hopefully #58836 will remove the html5.js scripts from header templates.
  5. Neither PR addresses the Customizer CSS templates in Twenty Fifteen and Twenty Sixteen. The wp_print_inline_script_tag() function would include the attributes array for those, and the documentation should capitalize CSS.
    /**
     * Outputs an Underscore template for generating CSS for the color scheme.
     *
     * The template generates the CSS dynamically for instant display in the Customizer
     * preview.
     *
     * @since Twenty Fifteen 1.0
     * @since Twenty Fifteen 4.1 Added `wp_print_inline_script_tag()` support.
     */
    function twentyfifteen_color_scheme_css_template() {
            $colors = array(
                    'background_color'            => '{{ data.background_color }}',
                    'header_background_color'     => '{{ data.header_background_color }}',
                    'box_background_color'        => '{{ data.box_background_color }}',
                    'textcolor'                   => '{{ data.textcolor }}',
                    'secondary_textcolor'         => '{{ data.secondary_textcolor }}',
                    'border_color'                => '{{ data.border_color }}',
                    'border_focus_color'          => '{{ data.border_focus_color }}',
                    'sidebar_textcolor'           => '{{ data.sidebar_textcolor }}',
                    'sidebar_border_color'        => '{{ data.sidebar_border_color }}',
                    'sidebar_border_focus_color'  => '{{ data.sidebar_border_focus_color }}',
                    'secondary_sidebar_textcolor' => '{{ data.secondary_sidebar_textcolor }}',
                    'meta_box_background_color'   => '{{ data.meta_box_background_color }}',
            );
    
            $css = twentyfifteen_get_color_scheme_css( $colors );
    
            if ( function_exists( 'wp_print_inline_script_tag' ) ) {
                    wp_print_inline_script_tag( $css, array( 'type' => 'text/html', 'id' => 'tmpl-twentyfifteen-color-scheme' ) );
            } else {
                    printf(
                            "<script %s>\n%s\n\t</script>\n",
                            'type="text/html" id="tmpl-twentyfifteen-color-scheme"',
                            $css
                    );
            }
    }
    add_action( 'customize_controls_print_footer_scripts', 'twentyfifteen_color_scheme_css_template' );
    
Last edited 4 months ago by sabernhardt (previous) (diff)

#7 @westonruter
5 months ago

@debarghyabanerjee and @iamadisingh Could you pull together to collaborate on a single PR together? Right now there are two open. It seems like the second PR is a but further along, so perhaps the first PR (a draft), should be closed in favor of the second?

@iamadisingh commented on PR #9415:


5 months ago
#8

Closing in favour of #9416

#9 @rollybueno
5 months ago

There's still a comment on the https://github.com/WordPress/wordpress-develop/pull/9416 that needs to address. I'll give this a test once those have been addressed.

#10 in reply to: ↑ 6 @westonruter
5 months ago

Replying to sabernhardt:

  1. Polyfills might need updating later, and I think any site that opts in for a strict CSP should have a newer version of WordPress than 5.7. Could the existing theme functions simply check if the wp_print_inline_script_tag function exists and print/echo if not?
    /**
     * JavaScript Detection.
     *
     * Adds a `js` class to the root `<html>` element when JavaScript is detected.
     *
     * @since Twenty Fifteen 1.1
     * @since Twenty Fifteen 4.1 Added `wp_print_inline_script_tag()` support.
     */
    function twentyfifteen_javascript_detection() {
            $js = "(function(html){html.className = html.className.replace(/\bno-js\b/,'js')})(document.documentElement);";
    
            if ( function_exists( 'wp_print_inline_script_tag' ) ) {
                    wp_print_inline_script_tag( $js );
            } else {
                    printf( "<script>%s</script>\n", $js );
            }
    }
    add_action( 'wp_head', 'twentyfifteen_javascript_detection', 0 );
    

This is a good point. If the functions aren't available, then a site wouldn't be able to opt-in to Strict CSP anyway since the other core code wouldn't be using them. So yes, I think directly printing the scripts as you suggest would make sense if the functions aren't available.

#11 @westonruter
4 months ago

  • Keywords changes-requested added

Note: Now with #63887, I think it makes sense to include the sourceURL comments for these inline scripts in the patch as well.

For example, in Twenty_Twenty_One_Dark_Mode::the_script() there is:

<?php
        public function the_script() {
                echo '<script>';
                include get_template_directory() . '/assets/js/dark-mode-toggler.js'; // phpcs:ignore WPThemeReview.CoreFunctionality.FileInclude
                echo '</script>';
        }

This method can address the CSP issue and the lack of a sourceURL comment by being changed to:

<?php
        public function the_script() {
                wp_print_inline_script_tag(
                        file_get_contents( get_template_directory() . '/assets/js/dark-mode-toggler.js' ) .
                        "\n//# sourceURL=" . trailingslashit( get_template_directory_uri() ) . 'assets/js/dark-mode-toggler.js'
                );
        }

@westonruter commented on PR #9416:


4 months ago
#12

@Debarghya-Banerjee Hello! Have you seen the latest PR feedback yet?

Also, note the comment I just added to the ticket, related the changes to Core-63887.

@debarghyabanerjee commented on PR #9416:


4 months ago
#13

Hi @westonruter , I have checked the comments and feedback, I am working on it, and will push the changes in sometime by today itself. Thanks.

@debarghyabanerjee commented on PR #9416:


4 months ago
#14

Hi @westonruter , Apologies for the late update. I’ve pushed the changes based on your and @sabernhardt 's feedback. Could you please take a look? Thanks!

@westonruter commented on PR #9416:


3 months ago
#15

@Debarghya-Banerjee Are you planning to address the remaining feedback or should we start pushing up commits to this PR? Thanks!

@debarghyabanerjee commented on PR #9416:


3 months ago
#16

Hi @westonruter , I have addressed the feedback. Sorry for getting back late. Can you please check once? Thanks.

@westonruter commented on PR #9416:


3 months ago
#17

I wrote a script that checked the output of rendered output of the homepages for each of the themes to verify that there were no regressions in the generated markup, and that only the expected changes occur:

<details><summary><code>grab-output.sh</code></summary>

#!/bin/bash

outdir=/tmp/trac-63806-output
mkdir -p "$outdir"
echo '' > "$outdir/report.md"

check_theme() {
	theme="$1"
	url="$2"
	theme_dir="/tmp/trac-63806-output/$1"
	echo "# $theme" >> "$outdir/report.md"
	mkdir -p "$theme_dir"
	npm run env:cli theme activate "$theme"
	git checkout trunk
	curl -s "$url" > "$theme_dir/before.html"
	git checkout fix/63806-update-themes-to-use-wp_print_script_tag
	curl -s "$url" > "$theme_dir/after.html"
	prettier "$theme_dir/before.html" > "$theme_dir/before.prettier.html"
	prettier "$theme_dir/after.html" > "$theme_dir/after.prettier.html"
	diff -u "$theme_dir/before.html" "$theme_dir/after.html" > "$theme_dir/raw.diff"
	diff -u "$theme_dir/before.prettier.html" "$theme_dir/after.prettier.html" > "$theme_dir/prettier.diff"

	{
		echo "URL: \`$url\`"
		echo
		echo 'Prettier Diff:'
		echo '{{{diff'
		cat "$theme_dir/prettier.diff"
		echo '}}}'
		echo
		echo '<details><summary>Raw Diff</summary>'
		echo
		echo '{{{diff'
		cat "$theme_dir/raw.diff"
		echo '}}}'
		echo
		echo '</details>'
		echo
	} >> "$outdir/report.md"
}

home_url="http://localhost:8000/?enable_plugins=none"

check_theme twentyfifteen "$home_url"
check_theme twentyseventeen "$home_url"
check_theme twentysixteen "$home_url"
check_theme twentytwenty "$home_url"
check_theme twentytwentyone "$home_url"

cat "$outdir/report.md"

</details>

Here is the output:

# twentyfifteen
URL: http://localhost:8000/?enable_plugins=none

Prettier Diff:

  • /tmp/trac-63806-output/twentyfifteen/

    old new  
    99      (function (html) {
    1010        html.className = html.className.replace(/\bno-js\b/, "js");
    1111      })(document.documentElement);
     12      //# sourceURL=twentyfifteen_javascript_detection
    1213    </script>
    1314    <title>WordPress Develop</title>
    1415    <meta name="robots" content="max-image-preview:large" />

<details><summary>Raw Diff</summary>

  • /tmp/trac-63806-output/twentyfifteen/

    old new  
    55        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    66        <link rel="profile" href="https://gmpg.org/xfn/11">
    77        <link rel="pingback" href="http://localhost:8000/xmlrpc.php">
    8         <script>(function(html){html.className = html.className.replace(/\bno-js\b/,'js')})(document.documentElement);</script>
     8        <script>
     9(function(html){html.className = html.className.replace(/\bno-js\b/,'js')})(document.documentElement);
     10//# sourceURL=twentyfifteen_javascript_detection
     11</script>
    912<title>WordPress Develop</title>
    1013<meta name='robots' content='max-image-preview:large' />
    1114<link rel="alternate" type="application/rss+xml" title="WordPress Develop &raquo; Feed" href="http://localhost:8000/feed/" />

</details>

# twentyseventeen
URL: http://localhost:8000/?enable_plugins=none

Prettier Diff:

  • /tmp/trac-63806-output/twentyseventeen/

    old new  
    99      (function (html) {
    1010        html.className = html.className.replace(/\bno-js\b/, "js");
    1111      })(document.documentElement);
     12      //# sourceURL=twentyseventeen_javascript_detection
    1213    </script>
    1314    <title>WordPress Develop</title>
    1415    <meta name="robots" content="max-image-preview:large" />

<details><summary>Raw Diff</summary>

  • /tmp/trac-63806-output/twentyseventeen/

    old new  
    55<meta name="viewport" content="width=device-width, initial-scale=1.0">
    66<link rel="profile" href="https://gmpg.org/xfn/11">
    77
    8 <script>(function(html){html.className = html.className.replace(/\bno-js\b/,'js')})(document.documentElement);</script>
     8<script>
     9(function(html){html.className = html.className.replace(/\bno-js\b/,'js')})(document.documentElement);
     10//# sourceURL=twentyseventeen_javascript_detection
     11</script>
    912<title>WordPress Develop</title>
    1013<meta name='robots' content='max-image-preview:large' />
    1114<link rel="alternate" type="application/rss+xml" title="WordPress Develop &raquo; Feed" href="http://localhost:8000/feed/" />

</details>

# twentysixteen
URL: http://localhost:8000/?enable_plugins=none

Prettier Diff:

  • /tmp/trac-63806-output/twentysixteen/

    old new  
    88      (function (html) {
    99        html.className = html.className.replace(/\bno-js\b/, "js");
    1010      })(document.documentElement);
     11      //# sourceURL=twentysixteen_javascript_detection
    1112    </script>
    1213    <title>WordPress Develop</title>
    1314    <meta name="robots" content="max-image-preview:large" />

<details><summary>Raw Diff</summary>

  • /tmp/trac-63806-output/twentysixteen/

    old new  
    44        <meta charset="UTF-8">
    55        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    66        <link rel="profile" href="https://gmpg.org/xfn/11">
    7                 <script>(function(html){html.className = html.className.replace(/\bno-js\b/,'js')})(document.documentElement);</script>
     7                <script>
     8(function(html){html.className = html.className.replace(/\bno-js\b/,'js')})(document.documentElement);
     9//# sourceURL=twentysixteen_javascript_detection
     10</script>
    811<title>WordPress Develop</title>
    912<meta name='robots' content='max-image-preview:large' />
    1013<link rel="alternate" type="application/rss+xml" title="WordPress Develop &raquo; Feed" href="http://localhost:8000/feed/" />

</details>

# twentytwenty
URL: http://localhost:8000/?enable_plugins=none

Prettier Diff:

  • /tmp/trac-63806-output/twentytwenty/

    old new  
    618618    <script>
    619619      document.documentElement.className =
    620620        document.documentElement.className.replace("no-js", "js");
     621      //# sourceURL=twentytwenty_no_js_class
    621622    </script>
    622623    <style id="custom-background-css">
    623624      body.custom-background {

<details><summary>Raw Diff</summary>

  • /tmp/trac-63806-output/twentytwenty/

    old new  
    7878<script src="http://localhost:8000/wp-content/themes/twentytwenty/assets/js/index.js?ver=2.9" id="twentytwenty-js-js" defer data-wp-strategy="defer"></script>
    7979<link rel="https://api.w.org/" href="http://localhost:8000/wp-json/" /><link rel="EditURI" type="application/rsd+xml" title="RSD" href="http://localhost:8000/xmlrpc.php?rsd" />
    8080<meta name="generator" content="WordPress 6.9-alpha-60093-src" />
    81         <script>document.documentElement.className = document.documentElement.className.replace( 'no-js', 'js' );</script>
    82         <style id="custom-background-css">
     81<script>
     82document.documentElement.className = document.documentElement.className.replace( 'no-js', 'js' );
     83//# sourceURL=twentytwenty_no_js_class
     84</script>
     85<style id="custom-background-css">
    8386body.custom-background { background-color: #fff; }
    8487</style>

</details>

# twentytwentyone
URL: http://localhost:8000/?enable_plugins=none

Prettier Diff:

  • /tmp/trac-63806-output/twentytwentyone/

    old new  
    15241524    </script>
    15251525    <script>
    15261526      document.body.classList.remove("no-js");
     1527      //# sourceURL=twenty_twenty_one_supports_js
    15271528    </script>
    15281529    <button
    15291530      id="dark-mode-toggler"
     
    16221623
    16231624      darkModeInitialLoad();
    16241625      darkModeRepositionTogglerOnScroll();
     1626      //# sourceURL=http://localhost:8000/wp-content/themes/twentytwentyone/assets/js/dark-mode-toggler.js
    16251627    </script>
    16261628    <script>
    16271629      if (
     
    16301632      ) {
    16311633        document.body.classList.add("is-IE");
    16321634      }
     1635      //# sourceURL=twentytwentyone_add_ie_class
    16331636    </script>
    16341637    <style id="core-block-supports-inline-css">
    16351638      /**

<details><summary>Raw Diff</summary>

  • /tmp/trac-63806-output/twentytwentyone/

    old new  
    470470<script type="speculationrules">
    471471{"prefetch":[{"source":"document","where":{"and":[{"href_matches":"/*"},{"not":{"href_matches":["/wp-*.php","/wp-admin/*","/wp-content/uploads/*","/wp-content/*","/wp-content/plugins/*","/wp-content/themes/twentytwentyone/*","/*\\?(.+)"]}},{"not":{"selector_matches":"a[rel~=\"nofollow\"]"}},{"not":{"selector_matches":".no-prefetch, .no-prefetch a"}}]},"eagerness":"conservative"}]}
    472472</script>
    473 <script>document.body.classList.remove("no-js");</script><button id="dark-mode-toggler" class="fixed-bottom" aria-pressed="false" onClick="toggleDarkMode()">Dark Mode: <span aria-hidden="true"></span></button>               <style>
     473<script>
     474document.body.classList.remove('no-js');
     475//# sourceURL=twenty_twenty_one_supports_js
     476</script>
     477<button id="dark-mode-toggler" class="fixed-bottom" aria-pressed="false" onClick="toggleDarkMode()">Dark Mode: <span aria-hidden="true"></span></button>                <style>
    474478                        #dark-mode-toggler > span {
    475479                                margin-left: 5px;
    476480                        }
     
    482486                        }
    483487                                        </style>
    484488
    485                 <script>function toggleDarkMode() { // jshint ignore:line
     489                <script>
     490function toggleDarkMode() { // jshint ignore:line
    486491        var toggler = document.getElementById( 'dark-mode-toggler' );
    487492
    488493        if ( 'false' === toggler.getAttribute( 'aria-pressed' ) ) {
     
    553558
    554559darkModeInitialLoad();
    555560darkModeRepositionTogglerOnScroll();
    556 </script>       <script>
    557         if ( -1 !== navigator.userAgent.indexOf( 'MSIE' ) || -1 !== navigator.appVersion.indexOf( 'Trident/' ) ) {
    558                 document.body.classList.add( 'is-IE' );
    559         }
    560         </script>
    561         <style id='core-block-supports-inline-css'>
     561//# sourceURL=http://localhost:8000/wp-content/themes/twentytwentyone/assets/js/dark-mode-toggler.js
     562</script>
     563<script>
     564                if ( -1 !== navigator.userAgent.indexOf('MSIE') || -1 !== navigator.appVersion.indexOf('Trident/') ) {
     565                        document.body.classList.add('is-IE');
     566                }
     567        //# sourceURL=twentytwentyone_add_ie_class
     568</script>
     569<style id='core-block-supports-inline-css'>
    562570/**
    563571 * Core styles: block-supports
    564572 */

</details>

#18 @westonruter
3 months ago

  • Keywords commit added; changes-requested removed
  • Owner set to westonruter
  • Status changed from new to accepted

#19 @westonruter
3 months ago

  • Resolution set to fixed
  • Status changed from accepted to closed

In 60913:

Bundled Themes: Use wp_print_inline_script_tag() when available and include sourceURL for JS.

Instead of manually constructing the markup for SCRIPT tags, leverage wp_print_inline_script_tag() when available to do the construction while also ensuring filters may inject additional attributes on the SCRIPT tags, such as nonce for CSP. When the function is not available (prior to WP 5.7), fall back to the manual markup construction.

This also adds the sourceURL comments to the inline scripts to facilitate debugging, per #63887.

Developed in https://github.com/WordPress/wordpress-develop/pull/9416.

Follow-up to [60909], [60899].

Props debarghyabanerjee, westonruter, hbhalodia, peterwilsoncc, sabernhardt, poena.
See #63887, #59446.
Fixes #63806.

Note: See TracTickets for help on using tickets.