* Prints an inline script tag.
* It is possible to inject attributes in the `<script>` tag via the {@see 'wp_script_attributes'} filter.
* Automatically injects type attribute if needed.
* @param string $data Data for script tag: JavaScript, importmap, speculationrules, etc.
* @param array $attributes Optional. Key-value pairs representing `<script>` tag attributes.
function wp_print_inline_script_tag( $data, $attributes = array() ) {
echo wp_get_inline_script_tag( $data, $attributes );
* Allows small styles to be inlined.
* This improves performance and sustainability, and is opt-in. Stylesheets can opt in
* by adding `path` data using `wp_style_add_data`, and defining the file's absolute path:
* wp_style_add_data( $style_handle, 'path', $file_path );
* @global WP_Styles $wp_styles
function wp_maybe_inline_styles() {
$total_inline_limit = 20000;
* The maximum size of inlined styles in bytes.
* @param int $total_inline_limit The file-size threshold, in bytes. Default 20000.
$total_inline_limit = apply_filters( 'styles_inline_size_limit', $total_inline_limit );
// Build an array of styles that have a path defined.
foreach ( $wp_styles->queue as $handle ) {
if ( ! isset( $wp_styles->registered[ $handle ] ) ) {
$src = $wp_styles->registered[ $handle ]->src;
$path = $wp_styles->get_data( $handle, 'path' );
$size = wp_filesize( $path );
if ( ! empty( $styles ) ) {
// Reorder styles array based on size.
static function ( $a, $b ) {
return ( $a['size'] <= $b['size'] ) ? -1 : 1;
* The total inlined size.
* On each iteration of the loop, if a style gets added inline the value of this var increases
* to reflect the total size of inlined styles.
foreach ( $styles as $style ) {
// Size check. Since styles are ordered by size, we can break the loop.
if ( $total_inline_size + $style['size'] > $total_inline_limit ) {
// Get the styles if we don't already have them.
$style['css'] = file_get_contents( $style['path'] );
* Check if the style contains relative URLs that need to be modified.
* URLs relative to the stylesheet's path should be converted to relative to the site's root.
$style['css'] = _wp_normalize_relative_css_links( $style['css'], $style['src'] );
// Set `src` to `false` and add styles inline.
$wp_styles->registered[ $style['handle'] ]->src = false;
if ( empty( $wp_styles->registered[ $style['handle'] ]->extra['after'] ) ) {
$wp_styles->registered[ $style['handle'] ]->extra['after'] = array();
array_unshift( $wp_styles->registered[ $style['handle'] ]->extra['after'], $style['css'] );
// Add the styles size to the $total_inline_size var.
$total_inline_size += (int) $style['size'];
* Makes URLs relative to the WordPress installation.
* @param string $css The CSS to make URLs relative to the WordPress installation.
* @param string $stylesheet_url The URL to the stylesheet.
* @return string The CSS with URLs made relative to the WordPress installation.
function _wp_normalize_relative_css_links( $css, $stylesheet_url ) {
return preg_replace_callback(
'#(url\s*\(\s*[\'"]?\s*)([^\'"\)]+)#',
static function ( $matches ) use ( $stylesheet_url ) {
list( , $prefix, $url ) = $matches;
// Short-circuit if the URL does not require normalization.
str_starts_with( $url, 'http:' ) ||
str_starts_with( $url, 'https:' ) ||
str_starts_with( $url, '/' ) ||
str_starts_with( $url, '#' ) ||
str_starts_with( $url, 'data:' )
// Build the absolute URL.
$absolute_url = dirname( $stylesheet_url ) . '/' . $url;
$absolute_url = str_replace( '/./', '/', $absolute_url );
// Convert to URL related to the site root.
$url = wp_make_link_relative( $absolute_url );
* Function that enqueues the CSS Custom Properties coming from theme.json.
function wp_enqueue_global_styles_css_custom_properties() {
wp_register_style( 'global-styles-css-custom-properties', false );
wp_add_inline_style( 'global-styles-css-custom-properties', wp_get_global_stylesheet( array( 'variables' ) ) );
wp_enqueue_style( 'global-styles-css-custom-properties' );
* Hooks inline styles in the proper place, depending on the active theme.
* @since 6.1.0 Added the `$priority` parameter.
* For block themes, styles are loaded in the head.
* For classic ones, styles are loaded in the body because the wp_head action happens before render_block.
* @link https://core.trac.wordpress.org/ticket/53494.
* @param string $style String containing the CSS styles to be added.
* @param int $priority To set the priority for the add_action.
function wp_enqueue_block_support_styles( $style, $priority = 10 ) {
$action_hook_name = 'wp_footer';
if ( wp_is_block_theme() ) {
$action_hook_name = 'wp_head';
static function () use ( $style ) {
echo "<style>$style</style>\n";
* Fetches, processes and compiles stored core styles, then combines and renders them to the page.
* Styles are stored via the style engine API.
* @link https://developer.wordpress.org/block-editor/reference-guides/packages/packages-style-engine/
* @param array $options {
* Optional. An array of options to pass to wp_style_engine_get_stylesheet_from_context().
* @type bool $optimize Whether to optimize the CSS output, e.g., combine rules.
* @type bool $prettify Whether to add new lines and indents to output.
* Default to whether the `SCRIPT_DEBUG` constant is defined.
function wp_enqueue_stored_styles( $options = array() ) {
$is_block_theme = wp_is_block_theme();
$is_classic_theme = ! $is_block_theme;
* For block themes, this function prints stored styles in the header.
* For classic themes, in the footer.
( $is_block_theme && doing_action( 'wp_footer' ) ) ||
( $is_classic_theme && doing_action( 'wp_enqueue_scripts' ) )
$core_styles_keys = array( 'block-supports' );
$compiled_core_stylesheet = '';
// Adds comment if code is prettified to identify core styles sections in debugging.
$should_prettify = isset( $options['prettify'] ) ? true === $options['prettify'] : defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG;
foreach ( $core_styles_keys as $style_key ) {
if ( $should_prettify ) {
$compiled_core_stylesheet .= "/**\n * Core styles: $style_key\n */\n";
// Chains core store ids to signify what the styles contain.
$style_tag_id .= '-' . $style_key;
$compiled_core_stylesheet .= wp_style_engine_get_stylesheet_from_context( $style_key, $options );
if ( ! empty( $compiled_core_stylesheet ) ) {
wp_register_style( $style_tag_id, false );
wp_add_inline_style( $style_tag_id, $compiled_core_stylesheet );
wp_enqueue_style( $style_tag_id );
// Prints out any other stores registered by themes or otherwise.
$additional_stores = WP_Style_Engine_CSS_Rules_Store::get_stores();
foreach ( array_keys( $additional_stores ) as $store_name ) {
if ( in_array( $store_name, $core_styles_keys, true ) ) {
$styles = wp_style_engine_get_stylesheet_from_context( $store_name, $options );
if ( ! empty( $styles ) ) {
$key = "wp-style-engine-$store_name";
wp_register_style( $key, false );
wp_add_inline_style( $key, $styles );
wp_enqueue_style( $key );
* Enqueues a stylesheet for a specific block.
* If the theme has opted-in to load block styles on demand,
* then the stylesheet will be enqueued on-render,
* otherwise when the block inits.
* @param string $block_name The block-name, including namespace.
* An array of arguments. See wp_register_style() for full information about each argument.
* @type string $handle The handle for the stylesheet.
* @type string|false $src The source URL of the stylesheet.
* @type string[] $deps Array of registered stylesheet handles this stylesheet depends on.
* @type string|bool|null $ver Stylesheet version number.
* @type string $media The media for which this stylesheet has been defined.
* @type string|null $path Absolute path to the stylesheet, so that it can potentially be inlined.
function wp_enqueue_block_style( $block_name, $args ) {
* Callback function to register and enqueue styles.
* @param string $content When the callback is used for the render_block filter,
* the content needs to be returned so the function parameter
* is to ensure the content exists.
* @return string Block content.
$callback = static function ( $content ) use ( $args ) {
// Register the stylesheet.
if ( ! empty( $args['src'] ) ) {
wp_register_style( $args['handle'], $args['src'], $args['deps'], $args['ver'], $args['media'] );
// Add `path` data if provided.
if ( isset( $args['path'] ) ) {
wp_style_add_data( $args['handle'], 'path', $args['path'] );
// Get the RTL file path.
$rtl_file_path = str_replace( '.css', '-rtl.css', $args['path'] );
if ( file_exists( $rtl_file_path ) ) {
wp_style_add_data( $args['handle'], 'rtl', 'replace' );
wp_style_add_data( $args['handle'], 'path', $rtl_file_path );
// Enqueue the stylesheet.
wp_enqueue_style( $args['handle'] );
$hook = did_action( 'wp_enqueue_scripts' ) ? 'wp_footer' : 'wp_enqueue_scripts';
if ( wp_should_load_block_assets_on_demand() ) {
* Callback function to register and enqueue styles.
* @param string $content The block content.
* @param array $block The full block, including name and attributes.
* @return string Block content.
$callback_separate = static function ( $content, $block ) use ( $block_name, $callback ) {
if ( ! empty( $block['blockName'] ) && $block_name === $block['blockName'] ) {
return $callback( $content );
* The filter's callback here is an anonymous function because
* using a named function in this case is not possible.
* The function cannot be unhooked, however, users are still able
* to dequeue the stylesheets registered/enqueued by the callback
* which is why in this case, using an anonymous function
add_filter( 'render_block', $callback_separate, 10, 2 );
* The filter's callback here is an anonymous function because
* using a named function in this case is not possible.
* The function cannot be unhooked, however, users are still able
* to dequeue the stylesheets registered/enqueued by the callback
* which is why in this case, using an anonymous function
add_filter( $hook, $callback );
// Enqueue assets in the editor.
add_action( 'enqueue_block_assets', $callback );
* Loads classic theme styles on classic themes in the frontend.
* This is used for backwards compatibility for Button and File blocks specifically.
* @since 6.2.0 Added File block styles.
* @since 6.8.0 Moved stylesheet registration outside of this function.
function wp_enqueue_classic_theme_styles() {
if ( ! wp_theme_has_theme_json() ) {
wp_enqueue_style( 'classic-theme-styles' );
* Removes leading and trailing _empty_ script tags.
* This is a helper meant to be used for literal script tag construction
* within `wp_get_inline_script_tag()` or `wp_print_inline_script_tag()`.
* It removes the literal values of "<script>" and "</script>" from
* around an inline script after trimming whitespace. Typically this
* is used in conjunction with output buffering, where `ob_get_clean()`
* is passed as the `$contents` argument.
* // Strips exact literal empty SCRIPT tags.
* $js = '<script>sayHello();</script>;
* 'sayHello();' === wp_remove_surrounding_empty_script_tags( $js );
* // Otherwise if anything is different it warns in the JS console.
* $js = '<script type="text/javascript">console.log( "hi" );</script>';
* 'console.error( ... )' === wp_remove_surrounding_empty_script_tags( $js );
* @see wp_print_inline_script_tag()
* @see wp_get_inline_script_tag()
* @param string $contents Script body with manually created SCRIPT tag literals.
* @return string Script body without surrounding script tag literals, or
* original contents if both exact literals aren't present.
function wp_remove_surrounding_empty_script_tags( $contents ) {
$contents = trim( $contents );
strlen( $contents ) > strlen( $opener ) + strlen( $closer ) &&
strtoupper( substr( $contents, 0, strlen( $opener ) ) ) === $opener &&
strtoupper( substr( $contents, -strlen( $closer ) ) ) === $closer
return substr( $contents, strlen( $opener ), -strlen( $closer ) );
$error_message = __( 'Expected string to start with script tag (without attributes) and end with script tag, with optional whitespace.' );
_doing_it_wrong( __FUNCTION__, $error_message, '6.4' );
/* translators: %s: wp_remove_surrounding_empty_script_tags() */
__( 'Function %s used incorrectly in PHP.' ),
'wp_remove_surrounding_empty_script_tags()'