diff --git a/src/wp-includes/general-template.php b/src/wp-includes/general-template.php
index 23b2f1c5ab..5b36272270 100644
--- a/src/wp-includes/general-template.php
+++ b/src/wp-includes/general-template.php
@@ -2830,6 +2830,8 @@ function wp_resource_hints() {
$hints['dns-prefetch'][] = apply_filters( 'emoji_svg_url', 'https://s.w.org/images/core/emoji/2.2.1/svg/' );
foreach ( $hints as $relation_type => $urls ) {
+ $unique_urls = array();
+
/**
* Filters domains and URLs for resource hints of relation type.
*
@@ -2841,16 +2843,31 @@ function wp_resource_hints() {
$urls = apply_filters( 'wp_resource_hints', $urls, $relation_type );
foreach ( $urls as $key => $url ) {
+ $atts = array();
+
+ if ( is_array( $url ) ) {
+ if ( isset( $url['href'] ) ) {
+ $atts = $url;
+ $url = $url['href'];
+ } else {
+ continue;
+ }
+ }
+
$url = esc_url( $url, array( 'http', 'https' ) );
+
if ( ! $url ) {
- unset( $urls[ $key ] );
+ continue;
+ }
+
+ if ( isset( $unique_urls[ $url ] ) ) {
continue;
}
if ( in_array( $relation_type, array( 'preconnect', 'dns-prefetch' ) ) ) {
$parsed = wp_parse_url( $url );
+
if ( empty( $parsed['host'] ) ) {
- unset( $urls[ $key ] );
continue;
}
@@ -2862,13 +2879,34 @@ function wp_resource_hints() {
}
}
- $urls[ $key ] = $url;
+ $atts['rel'] = $relation_type;
+ $atts['href'] = $url;
+
+ $unique_urls[ $url ] = $atts;
}
- $urls = array_unique( $urls );
+ foreach ( $unique_urls as $atts ) {
+ $html = '';
- foreach ( $urls as $url ) {
- printf( "\n", $relation_type, $url );
+ foreach ( $atts as $attr => $value ) {
+ if ( ! is_scalar( $value ) ||
+ ( ! in_array( $attr, array( 'as', 'crossorigin', 'href', 'pr', 'rel', 'type' ), true ) && ! is_numeric( $attr ))
+ ) {
+ continue;
+ }
+
+ $value = ( 'href' === $attr ) ? esc_url( $value ) : esc_attr( $value );
+
+ if ( ! is_string( $attr ) ) {
+ $html .= " $value";
+ } else {
+ $html .= " $attr='$value'";
+ }
+ }
+
+ $html = trim( $html );
+
+ echo "\n";
}
}
}
diff --git a/tests/phpunit/tests/general/resourceHints.php b/tests/phpunit/tests/general/resourceHints.php
index a25d8bb07d..d7e30276f1 100644
--- a/tests/phpunit/tests/general/resourceHints.php
+++ b/tests/phpunit/tests/general/resourceHints.php
@@ -1,7 +1,7 @@
\n" .
+ "\n" .
+ "\n" .
+ "\n" .
+ "\n";
+
+ add_filter( 'wp_resource_hints', array( $this, '_add_url_with_attributes' ), 10, 2 );
+
+ $actual = get_echo( 'wp_resource_hints' );
+
+ remove_filter( 'wp_resource_hints', array( $this, '_add_url_with_attributes' ) );
+
+ $this->assertEquals( $expected, $actual );
+ }
+
+ function _add_url_with_attributes( $hints, $method ) {
+ // Ignore hints with missing href attributes.
+ $hints[] = array(
+ 'rel' => 'foo',
+ );
+
+ if ( 'preconnect' === $method ) {
+ // Should ignore rel attributes.
+ $hints[] = array(
+ 'rel' => 'foo',
+ 'href' => 'https://make.wordpress.org/great-again',
+ );
+ } elseif ( 'prefetch' === $method ) {
+ $hints[] = array(
+ 'crossorigin',
+ 'as' => 'image',
+ 'pr' => 0.5,
+ 'href' => 'https://example.com/foo.jpeg',
+ );
+ $hints[] = array(
+ 'crossorigin' => 'use-credentials',
+ 'as' => 'style',
+ 'href' => 'https://example.com/foo.css',
+ );
+ } elseif ( 'prerender' === $method ) {
+ // Ignore invalid attributes.
+ $hints[] = array(
+ 'foo' => 'bar',
+ 'bar' => 'baz',
+ 'href' => 'http://wordpress.org',
+ );
+ }
+
+ return $hints;
+ }
}