Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion src/wp-content/themes/twentyeleven/header.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,19 @@
?>
</title>
<link rel="profile" href="https://gmpg.org/xfn/11" />
<link rel="stylesheet" media="all" href="<?php echo esc_url( get_stylesheet_uri() ); ?>?ver=20260819" />
<?php
if ( function_exists( 'wp_print_style_link_tag' ) ) {
wp_print_style_link_tag(
array(
'rel' => 'stylesheet',
'media' => 'all',
'href' => get_stylesheet_uri() . '?ver=20260819',
)
);
} else {
echo '<link rel="stylesheet" media="all" href="' . esc_url( get_stylesheet_uri() ) . '?ver=20260819" />' . "\n";
}
?>
<link rel="pingback" href="<?php echo esc_url( get_bloginfo( 'pingback_url' ) ); ?>">
<?php
/*
Expand Down
14 changes: 13 additions & 1 deletion src/wp-content/themes/twentyten/header.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,19 @@
?>
</title>
<link rel="profile" href="https://gmpg.org/xfn/11" />
<link rel="stylesheet" media="all" href="<?php echo esc_url( get_stylesheet_uri() ); ?>?ver=20260819" />
<?php
if ( function_exists( 'wp_print_style_link_tag' ) ) {
wp_print_style_link_tag(
array(
'rel' => 'stylesheet',
'media' => 'all',
'href' => get_stylesheet_uri() . '?ver=20260819',
)
);
} else {
echo '<link rel="stylesheet" media="all" href="' . esc_url( get_stylesheet_uri() ) . '?ver=20260819" />' . "\n";
}
?>
<link rel="pingback" href="<?php echo esc_url( get_bloginfo( 'pingback_url' ) ); ?>">
<?php
/*
Expand Down
47 changes: 31 additions & 16 deletions src/wp-includes/class-wp-styles.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ public function __construct() {
*
* @since 2.6.0
* @since 5.5.0 Added the `$group` parameter.
* @since 7.3.0 The attributes of the stylesheet link tags can be filtered with
* {@see 'wp_style_attributes'}.
*
* @see WP_Dependencies::do_item()
*
Expand Down Expand Up @@ -178,22 +180,31 @@ public function do_item( $handle, $group = false ) {
return true;
}

$href = $this->_css_href( $src, $obj->ver, $handle );
$raw_href = $this->get_src( $handle );
$href = esc_url( $raw_href );
if ( ! $href ) {
return true;
}

$rel = isset( $obj->extra['alt'] ) && $obj->extra['alt'] ? 'alternate stylesheet' : 'stylesheet';
$title = $obj->extra['title'] ?? '';

$tag = sprintf(
"<link rel='%s' id='%s-css'%s href='%s' media='%s' />\n",
$rel,
esc_attr( $handle ),
$title ? sprintf( " title='%s'", esc_attr( $title ) ) : '',
$href,
esc_attr( $media )
/*
* The title and media are cast to strings, as esc_attr() did when the tag was built as a
* string, so that a value such as `true` is still printed as `'1'`, and an array passed by
* mistake as `'Array'`, rather than as a boolean attribute or a fatal error.
*/
$attributes = array(
'rel' => $rel,
'id' => "{$handle}-css",
);
if ( $title ) {
$attributes['title'] = (string) $title;
}
$attributes['href'] = $raw_href;
$attributes['media'] = (string) $media;

$tag = _wp_get_legacy_style_link_tag( $attributes, __METHOD__, array( 'href' => $href ) );

/**
* Filters the HTML link tag of an enqueued style.
Expand All @@ -212,16 +223,17 @@ public function do_item( $handle, $group = false ) {
$rtl_src = $this->get_rtl_src( $handle );

if ( null !== $rtl_src ) {
$rtl_href = esc_url( $rtl_src );
$rtl_tag = sprintf(
"<link rel='%s' id='%s-rtl-css'%s href='%s' media='%s' />\n",
$rel,
esc_attr( $handle ),
$title ? sprintf( " title='%s'", esc_attr( $title ) ) : '',
$rtl_href,
esc_attr( $media )
$rtl_href = esc_url( $rtl_src );
$rtl_attributes = array_merge(
$attributes,
array(
'id' => "{$handle}-rtl-css",
'href' => $rtl_src,
)
);

$rtl_tag = _wp_get_legacy_style_link_tag( $rtl_attributes, __METHOD__, array( 'href' => $rtl_href ) );

/** This filter is documented in wp-includes/class-wp-styles.php */
$rtl_tag = apply_filters( 'style_loader_tag', $rtl_tag, $handle, $rtl_href, $media );

Expand Down Expand Up @@ -415,6 +427,9 @@ public function all_deps( $handles, $recursion = false, $group = false ) {
* Generates an enqueued style's fully-qualified URL.
*
* @since 2.6.0
* @since 7.3.0 No longer used by WP_Styles::do_item(), which escapes the URL from
* WP_Styles::get_src() instead, so overriding this method in a subclass
* no longer changes the printed URL.
*
* @param string $src The source of the enqueued style.
* @param string|false|null $ver The version of the enqueued style.
Expand Down
20 changes: 14 additions & 6 deletions src/wp-includes/general-template.php
Original file line number Diff line number Diff line change
Expand Up @@ -5380,6 +5380,8 @@ function wp_admin_css_uri( $file = 'wp-admin' ) {
* stylesheet link to that generated URL is printed.
*
* @since 2.3.0
* @since 7.3.0 The attributes of the printed stylesheet link tags can be filtered with
* {@see 'wp_style_attributes'}.
*
* @param string $file Optional. Style handle name or file name (without ".css" extension) relative
* to wp-admin/. Defaults to 'wp-admin'.
Expand All @@ -5400,9 +5402,12 @@ function wp_admin_css( $file = 'wp-admin', $force_echo = false ) {
return;
}

$stylesheet_link = sprintf(
"<link rel='stylesheet' href='%s' />\n",
esc_url( wp_admin_css_uri( $file ) )
$stylesheet_link = _wp_get_legacy_style_link_tag(
array(
'rel' => 'stylesheet',
'href' => wp_admin_css_uri( $file ),
),
__FUNCTION__
);

/**
Expand All @@ -5419,9 +5424,12 @@ function wp_admin_css( $file = 'wp-admin', $force_echo = false ) {
echo apply_filters( 'wp_admin_css', $stylesheet_link, $file );

if ( function_exists( 'is_rtl' ) && is_rtl() ) {
$rtl_stylesheet_link = sprintf(
"<link rel='stylesheet' href='%s' />\n",
esc_url( wp_admin_css_uri( "$file-rtl" ) )
$rtl_stylesheet_link = _wp_get_legacy_style_link_tag(
array(
'rel' => 'stylesheet',
'href' => wp_admin_css_uri( "$file-rtl" ),
),
__FUNCTION__
);

/** This filter is documented in wp-includes/general-template.php */
Expand Down
220 changes: 220 additions & 0 deletions src/wp-includes/script-loader.php
Original file line number Diff line number Diff line change
Expand Up @@ -3420,6 +3420,226 @@ function wp_print_inline_script_tag( $data, $attributes = array() ) {
echo wp_get_inline_script_tag( $data, $attributes );
}

/**
* Formats a `<link rel="stylesheet">` tag.
*
* It is possible to inject attributes in the stylesheet link tag via the {@see 'wp_style_attributes'} filter.
*
* Attributes are printed in the order they are given, so `rel` can be printed first, as is conventional.
*
* @since 7.3.0
*
* @param array<string, string|bool|null> $attributes Key-value pairs representing `<link>` tag attributes. Only
* the attribute name is added for entries whose value is `true`
* or `null`, as for wp_get_script_tag(), and entries whose value
* is `false` are omitted.
* @return string String containing a `<link>` tag.
*
* @phpstan-param array<non-empty-string, string|bool|null> $attributes
* @phpstan-return non-falsy-string
*/
function wp_get_style_link_tag( array $attributes ): string {
return _wp_build_style_link_tag( _wp_filter_style_link_attributes( $attributes, __FUNCTION__ ) );
}

/**
* Prints a formatted stylesheet link tag.
*
* It is possible to inject attributes in the stylesheet link tag via the {@see 'wp_style_attributes'} filter.
*
* @since 7.3.0
*
* @param array<string, string|bool|null> $attributes Key-value pairs representing `<link>` tag attributes. Only
* the attribute name is added for entries whose value is `true`
* or `null`, as for wp_get_script_tag(), and entries whose value
* is `false` are omitted.
*
* @phpstan-param array<non-empty-string, string|bool|null> $attributes
*/
function wp_print_style_link_tag( array $attributes ): void {
echo wp_get_style_link_tag( $attributes );
}

/**
* Formats a stylesheet link tag in the markup Core printed before wp_get_style_link_tag().
*
* Callbacks for filters such as {@see 'style_loader_tag'} and {@see 'wp_admin_css'} have long
* edited that markup as a string, so the values supplied here are printed as they were before:
* escaped with esc_url() for the `href` attribute and with esc_attr() otherwise, and single-quoted.
* A value which the {@see 'wp_style_attributes'} filter adds or changes is escaped by the Tag
* Processor instead.
*
* @since 7.3.0
* @access private
*
* @param array<string, string|bool> $attributes Key-value pairs representing `<link>` tag attributes.
* @param string $function_name The function printing the tag, named in the notice
* issued when the filter changes the `id` attribute.
* @param array<string, string> $escaped_values Optional. Values from `$attributes` which have already
* been escaped, keyed by attribute name, such as an `href`
* escaped with esc_url() for another use, so that they are
* not escaped again. Default empty array.
* @return string String containing a `<link>` tag.
*
* @phpstan-param array<non-empty-string, string|bool> $attributes
* @phpstan-return non-falsy-string
*/
function _wp_get_legacy_style_link_tag( array $attributes, string $function_name, array $escaped_values = array() ): string {
$filtered_attributes = _wp_filter_style_link_attributes( $attributes, $function_name );

$legacy_values = array();
foreach ( $attributes as $name => $value ) {
if ( ! is_string( $value ) || ! array_key_exists( $name, $filtered_attributes ) || $filtered_attributes[ $name ] !== $value ) {
continue;
}

if ( isset( $escaped_values[ $name ] ) ) {
$legacy_values[ $name ] = $escaped_values[ $name ];
} elseif ( 'href' === $name ) {
$legacy_values[ $name ] = esc_url( $value );
} else {
$legacy_values[ $name ] = esc_attr( $value );
}
}

// Attribute values are escaped, so the only double quotes in the tag are those delimiting them.
return str_replace( '"', "'", _wp_build_style_link_tag( $filtered_attributes, $legacy_values ) );
}

/**
* Applies the {@see 'wp_style_attributes'} filter to the attributes of a stylesheet link tag.
*
* @since 7.3.0
* @access private
*
* @param array<string, string|bool|null> $attributes Key-value pairs representing `<link>` tag attributes.
* @param string $function_name The function printing the tag, named in the notice
* issued when the filter changes the `id` attribute.
* @return array<string, string|bool|null> Filtered attributes.
*
* @phpstan-param array<non-empty-string, string|bool|null> $attributes
*/
function _wp_filter_style_link_attributes( array $attributes, string $function_name ) {
/**
* Filters attributes to be added to a stylesheet link tag.
*
* This applies to the stylesheet links printed by Core, including those for enqueued styles,
* whose `id` attribute is the style's handle with `-css` appended, or `-rtl-css` for a
* right-to-left stylesheet. For an enqueued style the URL passed to the
* {@see 'style_loader_tag'} filter is the one from before this filter, even when this
* filter changes the `href` attribute.
*
* Attribute values are not yet escaped. The `href` attribute is escaped with esc_url() when the
* tag is printed, which applies the {@see 'clean_url'} filter and omits a URL with a disallowed
* protocol, so the printed URL may differ from the one passed to this filter.
*
* The `id` attribute, when supplied, cannot be changed or removed by this filter, since other
* code may locate the `<link>` tag by it. For example, the user profile screen locates the
* `colors-css` stylesheet to preview a color scheme.
*
* @since 7.3.0
*
* @param array<string, string|bool|null> $attributes Key-value pairs representing `<link>` tag attributes.
* Only the attribute name is added to the `<link>` tag
* for entries whose value is `true` or `null`, and
* entries whose value is `false` are omitted.
*/
$filtered_attributes = apply_filters( 'wp_style_attributes', $attributes );

if ( ! isset( $attributes['id'] ) ) {
return $filtered_attributes;
}

if ( ( $filtered_attributes['id'] ?? null ) !== $attributes['id'] ) {
_doing_it_wrong(
$function_name,
sprintf(
/* translators: 1: id, 2: wp_style_attributes */
__( 'The %1$s attribute cannot be changed by the %2$s filter.' ),
'<code>id</code>',
'<code>wp_style_attributes</code>'
),
'7.3.0'
);
}

// Retain the supplied ID in place of the first lexical variation of its name, or after the others when removed.
$retained_attributes = array();
foreach ( $filtered_attributes as $name => $value ) {
if ( 'id' !== strtolower( (string) $name ) ) {
$retained_attributes[ $name ] = $value;
} elseif ( ! isset( $retained_attributes['id'] ) ) {
$retained_attributes['id'] = $attributes['id'];
}
}
$retained_attributes['id'] = $attributes['id'];

return $retained_attributes;
}

/**
* Builds a `<link>` tag from attributes which have already been filtered.
*
* @since 7.3.0
* @access private
*
* @param array<string, string|bool|null> $attributes Key-value pairs representing `<link>` tag attributes.
* @param array<string, string> $escaped_values Optional. Values which have already been escaped, keyed
* by attribute name, to print as-is. Default empty array.
* @return string String containing a `<link>` tag.
*
* @phpstan-return non-falsy-string
*/
function _wp_build_style_link_tag( $attributes, array $escaped_values = array() ): string {
$html = '<link';
$seen = array();

foreach ( $attributes as $name => $value ) {
$name = (string) $name;

/*
* Lexical variations of an attribute name may represent the same attribute in HTML.
* Calling code should rewrite an attribute's value rather than sending a duplicate,
* so only the first is used, even when its value omits the attribute.
*/
$normalized_name = strtolower( $name );
if ( isset( $seen[ $normalized_name ] ) ) {
continue;
}
$seen[ $normalized_name ] = true;

if ( false === $value ) {
continue;
}

if ( isset( $escaped_values[ $name ] ) ) {
// Escaping functions are filterable, so a quote must not end the value early.
$html .= " {$name}=\"" . strtr(
$escaped_values[ $name ],
array(
'"' => '&quot;',
"'" => '&#039;',
)
) . '"';
continue;
}

/*
* The Tag Processor adds new attributes to a tag in alphabetical order, so each attribute
* is set on a tag of its own, which escapes and validates it, and then printed in the
* order given. A rejected name or value, such as a URL with a disallowed protocol, is
* left out.
*/
$processor = new WP_HTML_Tag_Processor( '<link>' );
$processor->next_tag();
if ( $processor->set_attribute( $name, $value ?? true ) ) {
$html .= substr( $processor->get_updated_html(), strlen( '<link' ), -1 );
}
}

return "{$html} />\n";
}

/**
* Allows small styles to be inlined.
*
Expand Down
Loading
Loading