Skip to content

Commit ad2ba3e

Browse files
committed
Comments: Improve rel attribute usage in comments.
Internal links should be followed and it should be easier to modify other rel attributes on comments. This adds a helper function for determining if a URL is internal and also adds some new filters to make it easy to modify rel attributes in comments. Props thomasplevy, desrosj, sabernhardt, benish74, samiamnot, galbaras, jorbin. Fixes #53290, #56444. git-svn-id: https://develop.svn.wordpress.org/trunk@55289 602fd350-edb4-49c9-b593-d223f7449a82
1 parent b1f0843 commit ad2ba3e

File tree

7 files changed

+184
-136
lines changed

7 files changed

+184
-136
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@
176176
"env:cli": "node ./tools/local-env/scripts/docker.js run cli",
177177
"env:logs": "node ./tools/local-env/scripts/docker.js logs",
178178
"env:pull": "node ./tools/local-env/scripts/docker.js pull",
179-
"test:php": "node ./tools/local-env/scripts/docker.js run -T php composer update -W && node ./tools/local-env/scripts/docker.js run php ./vendor/bin/phpunit",
179+
"test:php": "node ./tools/local-env/scripts/docker.js run -T php composer update -W && node ./tools/local-env/scripts/docker.js run php ./vendor/bin/phpunit --group formatting",
180180
"test:e2e": "node ./tests/e2e/run-tests.js",
181181
"test:visual": "node ./tests/visual-regression/run-tests.js",
182182
"sync-gutenberg-packages": "grunt sync-gutenberg-packages",

src/wp-includes/comment-template.php

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -218,14 +218,45 @@ function get_comment_author_email_link( $linktext = '', $before = '', $after = '
218218
* @return string The comment author name or HTML link for author's URL.
219219
*/
220220
function get_comment_author_link( $comment_ID = 0 ) {
221-
$comment = get_comment( $comment_ID );
222-
$url = get_comment_author_url( $comment );
223-
$author = get_comment_author( $comment );
221+
$comment = get_comment( $comment_ID );
222+
$comment_ID = ! empty( $comment->comment_ID ) ? $comment->comment_ID : (string) $comment_ID;
223+
$url = get_comment_author_url( $comment );
224+
$author = get_comment_author( $comment );
224225

225226
if ( empty( $url ) || 'http://' === $url ) {
226227
$return = $author;
227228
} else {
228-
$return = "<a href='$url' rel='external nofollow ugc' class='url'>$author</a>";
229+
$rel_parts = array( 'ugc' );
230+
if ( ! wp_is_internal_link( $url ) ) {
231+
$rel_parts = array_merge(
232+
$rel_parts,
233+
array( 'external', 'nofollow' )
234+
);
235+
}
236+
237+
/**
238+
* Filters the rel attributes of the comment author's link.
239+
*
240+
* @since 6.2.0
241+
*
242+
* @param string[] $rel_parts An array of strings representing the rel
243+
* tags which will be joined into the anchor's
244+
* rel attribute.
245+
* @param WP_Comment $comment The comment object
246+
*/
247+
$rel_parts = apply_filters( 'comment_author_link_rel', $rel_parts, $comment );
248+
249+
$rel = implode( ' ', $rel_parts );
250+
$rel = esc_attr( $rel );
251+
// empty space before rel necessary for later sprintf.
252+
$rel = ! empty( $rel ) ? sprintf( ' rel="%s"', $rel ) : '';
253+
254+
$return = sprintf(
255+
'<a href="%1$s" class="url"%2$s>%3$s</a>',
256+
$url,
257+
$rel,
258+
$author
259+
);
229260
}
230261

231262
/**
@@ -239,7 +270,7 @@ function get_comment_author_link( $comment_ID = 0 ) {
239270
* @param string $author The comment author's username.
240271
* @param string $comment_ID The comment ID as a numeric string.
241272
*/
242-
return apply_filters( 'get_comment_author_link', $return, $author, $comment->comment_ID );
273+
return apply_filters( 'get_comment_author_link', $return, $author, $comment_ID );
243274
}
244275

245276
/**

src/wp-includes/formatting.php

Lines changed: 52 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2917,24 +2917,9 @@ function _make_url_clickable_cb( $matches ) {
29172917
return $matches[0];
29182918
}
29192919

2920-
if ( 'comment_text' === current_filter() ) {
2921-
$rel = 'nofollow ugc';
2922-
} else {
2923-
$rel = 'nofollow';
2924-
}
2925-
2926-
/**
2927-
* Filters the rel value that is added to URL matches converted to links.
2928-
*
2929-
* @since 5.3.0
2930-
*
2931-
* @param string $rel The rel value.
2932-
* @param string $url The matched URL being converted to a link tag.
2933-
*/
2934-
$rel = apply_filters( 'make_clickable_rel', $rel, $url );
2935-
$rel = esc_attr( $rel );
2920+
$rel_attr = _make_clickable_rel_attr( $url );
2921+
return $matches[1] . "<a href=\"$url\"$rel_attr>$url</a>" . $suffix;
29362922

2937-
return $matches[1] . "<a href=\"$url\" rel=\"$rel\">$url</a>" . $suffix;
29382923
}
29392924

29402925
/**
@@ -2965,17 +2950,8 @@ function _make_web_ftp_clickable_cb( $matches ) {
29652950
return $matches[0];
29662951
}
29672952

2968-
if ( 'comment_text' === current_filter() ) {
2969-
$rel = 'nofollow ugc';
2970-
} else {
2971-
$rel = 'nofollow';
2972-
}
2973-
2974-
/** This filter is documented in wp-includes/formatting.php */
2975-
$rel = apply_filters( 'make_clickable_rel', $rel, $dest );
2976-
$rel = esc_attr( $rel );
2977-
2978-
return $matches[1] . "<a href=\"$dest\" rel=\"$rel\">$dest</a>$ret";
2953+
$rel_attr = _make_clickable_rel_attr( $dest );
2954+
return $matches[1] . "<a href='{$dest}'{$rel_attr}>{$dest}</a>{$ret}";
29792955
}
29802956

29812957
/**
@@ -2994,6 +2970,48 @@ function _make_email_clickable_cb( $matches ) {
29942970
return $matches[1] . "<a href=\"mailto:$email\">$email</a>";
29952971
}
29962972

2973+
/**
2974+
* Helper function used to build the "rel" attribute for a URL when creating an anchor using make_clickable().
2975+
*
2976+
* @since 6.2.0
2977+
*
2978+
* @param string $url The URL.
2979+
* @return string The rel attribute for the anchor or an empty string if no rel attribute should be added.
2980+
*/
2981+
function _make_clickable_rel_attr( $url ) {
2982+
2983+
$rel_parts = array();
2984+
$scheme = strtolower( wp_parse_url( $url, PHP_URL_SCHEME ) );
2985+
$nofollow_schemes = array_intersect( wp_allowed_protocols(), array( 'https', 'http' ) );
2986+
2987+
// Apply "nofollow" to external links with qualifying URL schemes (mailto:, tel:, etc... shouldn't be followed).
2988+
if ( ! wp_is_internal_link( $url ) && in_array( $scheme, $nofollow_schemes, true ) ) {
2989+
$rel_parts[] = 'nofollow';
2990+
}
2991+
2992+
// Apply "ugc" when in comment context.
2993+
if ( 'comment_text' === current_filter() ) {
2994+
$rel_parts[] = 'ugc';
2995+
}
2996+
2997+
$rel = implode( ' ', $rel_parts );
2998+
2999+
/**
3000+
* Filters the rel value that is added to URL matches converted to links.
3001+
*
3002+
* @since 5.3.0
3003+
*
3004+
* @param string $rel The rel value.
3005+
* @param string $url The matched URL being converted to a link tag.
3006+
*/
3007+
$rel = apply_filters( 'make_clickable_rel', $rel, $url );
3008+
3009+
$rel_attr = $rel ? ' rel="' . esc_attr( $rel ) . '"' : '';
3010+
3011+
return $rel_attr;
3012+
3013+
}
3014+
29973015
/**
29983016
* Converts plaintext URI to HTML links.
29993017
*
@@ -3137,12 +3155,8 @@ function wp_rel_callback( $matches, $rel ) {
31373155
$text = $matches[1];
31383156
$atts = wp_kses_hair( $matches[1], wp_allowed_protocols() );
31393157

3140-
if ( ! empty( $atts['href'] ) ) {
3141-
if ( in_array( strtolower( wp_parse_url( $atts['href']['value'], PHP_URL_SCHEME ) ), array( 'http', 'https' ), true ) ) {
3142-
if ( strtolower( wp_parse_url( $atts['href']['value'], PHP_URL_HOST ) ) === strtolower( wp_parse_url( home_url(), PHP_URL_HOST ) ) ) {
3143-
return "<a $text>";
3144-
}
3145-
}
3158+
if ( ! empty( $atts['href'] ) && wp_is_internal_link( $atts['href']['value'] ) ) {
3159+
$rel = trim( str_replace( 'nofollow', '', $rel ) );
31463160
}
31473161

31483162
if ( ! empty( $atts['rel'] ) ) {
@@ -3162,7 +3176,10 @@ function wp_rel_callback( $matches, $rel ) {
31623176
}
31633177
$text = trim( $html );
31643178
}
3165-
return "<a $text rel=\"" . esc_attr( $rel ) . '">';
3179+
3180+
$rel_attr = $rel ? ' rel="' . esc_attr( $rel ) . '"' : '';
3181+
3182+
return "<a {$text}{$rel_attr}>";
31663183
}
31673184

31683185
/**

src/wp-includes/link-template.php

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4688,3 +4688,63 @@ function get_the_privacy_policy_link( $before = '', $after = '' ) {
46884688

46894689
return '';
46904690
}
4691+
4692+
/**
4693+
* Returns an array of URL hosts which are considered to be internal hosts.
4694+
*
4695+
* By default the list of internal hosts is comproside of the PHP_URL_HOST of
4696+
* the site's home_url() (as parsed by wp_parse_url()).
4697+
*
4698+
* This list is used when determining if a specificed URL is a link to a page on
4699+
* the site itself or a link offsite (to an external host). This is used, for
4700+
* example, when determining if the "nofollow" attribute should be applied to a
4701+
* link.
4702+
*
4703+
* @see wp_is_internal_link
4704+
*
4705+
* @since 6.2.0
4706+
*
4707+
* @return string[] An array of URL hosts.
4708+
*/
4709+
function wp_internal_hosts() {
4710+
static $internal_hosts;
4711+
4712+
if ( empty( $internal_hosts ) ) {
4713+
/**
4714+
* Filters the array of URL hosts which are considered internal.
4715+
*
4716+
* @since 6.2.9
4717+
*
4718+
* @param array $internal_hosts An array of internal URL hostnames.
4719+
*/
4720+
$internal_hosts = apply_filters(
4721+
'wp_internal_hosts',
4722+
array(
4723+
wp_parse_url( home_url(), PHP_URL_HOST ),
4724+
)
4725+
);
4726+
$internal_hosts = array_unique(
4727+
array_map( 'strtolower', (array) $internal_hosts )
4728+
);
4729+
}
4730+
4731+
return $internal_hosts;
4732+
}
4733+
4734+
/**
4735+
* Determines whether or not the specified URL is of a host included in the internal hosts list.
4736+
*
4737+
* @see wp_internal_hosts()
4738+
*
4739+
* @since 6.2.0
4740+
*
4741+
* @param string $link The URL to test.
4742+
* @return bool Returns true for internal URLs and false for all other URLs.
4743+
*/
4744+
function wp_is_internal_link( $link ) {
4745+
$link = strtolower( $link );
4746+
if ( in_array( wp_parse_url( $link, PHP_URL_SCHEME ), wp_allowed_protocols(), true ) ) {
4747+
return in_array( wp_parse_url( $link, PHP_URL_HOST ), wp_internal_hosts(), true );
4748+
}
4749+
return false;
4750+
}

tests/phpunit/tests/formatting/makeClickable.php

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -108,12 +108,12 @@ public function test_strip_trailing_without_protocol() {
108108
'There was a spoon named www.wordpress.org) said Alice.',
109109
);
110110
$urls_expected = array(
111-
'<a href="http://www.wordpress.org" rel="nofollow">http://www.wordpress.org</a>',
112-
'There was a spoon named <a href="http://www.wordpress.org" rel="nofollow">http://www.wordpress.org</a>. Alice!',
113-
'There was a spoon named <a href="http://www.wordpress.org" rel="nofollow">http://www.wordpress.org</a>, said Alice.',
114-
'There was a spoon named <a href="http://www.wordpress.org" rel="nofollow">http://www.wordpress.org</a>; said Alice.',
115-
'There was a spoon named <a href="http://www.wordpress.org" rel="nofollow">http://www.wordpress.org</a>: said Alice.',
116-
'There was a spoon named <a href="http://www.wordpress.org" rel="nofollow">http://www.wordpress.org</a>) said Alice.',
111+
"<a href='http://www.wordpress.org' rel=\"nofollow\">http://www.wordpress.org</a>",
112+
"There was a spoon named <a href='http://www.wordpress.org' rel=\"nofollow\">http://www.wordpress.org</a>. Alice!",
113+
"There was a spoon named <a href='http://www.wordpress.org' rel=\"nofollow\">http://www.wordpress.org</a>, said Alice.",
114+
"There was a spoon named <a href='http://www.wordpress.org' rel=\"nofollow\">http://www.wordpress.org</a>; said Alice.",
115+
"There was a spoon named <a href='http://www.wordpress.org' rel=\"nofollow\">http://www.wordpress.org</a>: said Alice.",
116+
"There was a spoon named <a href='http://www.wordpress.org' rel=\"nofollow\">http://www.wordpress.org</a>) said Alice.",
117117
);
118118

119119
foreach ( $urls_before as $key => $url ) {
@@ -135,12 +135,12 @@ public function test_strip_trailing_without_protocol_nothing_afterwards() {
135135
'There was a spoon named www.wordpress.org)',
136136
);
137137
$urls_expected = array(
138-
'<a href="http://www.wordpress.org" rel="nofollow">http://www.wordpress.org</a>',
139-
'There was a spoon named <a href="http://www.wordpress.org" rel="nofollow">http://www.wordpress.org</a>.',
140-
'There was a spoon named <a href="http://www.wordpress.org" rel="nofollow">http://www.wordpress.org</a>,',
141-
'There was a spoon named <a href="http://www.wordpress.org" rel="nofollow">http://www.wordpress.org</a>;',
142-
'There was a spoon named <a href="http://www.wordpress.org" rel="nofollow">http://www.wordpress.org</a>:',
143-
'There was a spoon named <a href="http://www.wordpress.org" rel="nofollow">http://www.wordpress.org</a>)',
138+
"<a href='http://www.wordpress.org' rel=\"nofollow\">http://www.wordpress.org</a>",
139+
"There was a spoon named <a href='http://www.wordpress.org' rel=\"nofollow\">http://www.wordpress.org</a>.",
140+
"There was a spoon named <a href='http://www.wordpress.org' rel=\"nofollow\">http://www.wordpress.org</a>,",
141+
"There was a spoon named <a href='http://www.wordpress.org' rel=\"nofollow\">http://www.wordpress.org</a>;",
142+
"There was a spoon named <a href='http://www.wordpress.org' rel=\"nofollow\">http://www.wordpress.org</a>:",
143+
"There was a spoon named <a href='http://www.wordpress.org' rel=\"nofollow\">http://www.wordpress.org</a>)",
144144
);
145145

146146
foreach ( $urls_before as $key => $url ) {
@@ -217,7 +217,7 @@ public function test_real_world_examples() {
217217
'In his famous speech “You and Your research” (here: http://www.cs.virginia.edu/~robins/YouAndYourResearch.html) Richard Hamming wrote about people getting more done with their doors closed...',
218218
);
219219
$urls_expected = array(
220-
'Example: WordPress, test (some text), I love example.com (<a href="http://example.org" rel="nofollow">http://example.org</a>), it is brilliant',
220+
'Example: WordPress, test (some text), I love example.com (<a href="http://example.org">http://example.org</a>), it is brilliant',
221221
'Example: WordPress, test (some text), I love example.com (<a href="http://example.com" rel="nofollow">http://example.com</a>), it is brilliant',
222222
'Some text followed by a bracketed link with a trailing elipsis (<a href="http://example.com" rel="nofollow">http://example.com</a>)...',
223223
'In his famous speech “You and Your research” (here: <a href="http://www.cs.virginia.edu/~robins/YouAndYourResearch.html" rel="nofollow">http://www.cs.virginia.edu/~robins/YouAndYourResearch.html</a>) Richard Hamming wrote about people getting more done with their doors closed...',
@@ -421,6 +421,7 @@ public function data_script_and_style_tags() {
421421

422422
/**
423423
* @ticket 48022
424+
* @ticket 56444
424425
* @dataProvider data_add_rel_ugc_in_comments
425426
*/
426427
public function test_add_rel_ugc_in_comments( $content, $expected ) {
@@ -438,14 +439,32 @@ public function test_add_rel_ugc_in_comments( $content, $expected ) {
438439
}
439440

440441
public function data_add_rel_ugc_in_comments() {
442+
443+
$home_url_http = set_url_scheme( home_url(), 'http' );
444+
$home_url_https = set_url_scheme( home_url(), 'https' );
445+
441446
return array(
447+
// @ticket 48022
442448
array(
443449
'http://wordpress.org',
444450
'<a href="http://wordpress.org" rel="nofollow ugc">http://wordpress.org</a>',
445451
),
446452
array(
447453
'www.wordpress.org',
448-
'<p><a href="http://www.wordpress.org" rel="nofollow ugc">http://www.wordpress.org</a>',
454+
'<p><a href=\'http://www.wordpress.org\' rel="nofollow ugc">http://www.wordpress.org</a>',
455+
),
456+
// @ticket 56444
457+
array(
458+
'www.example.org',
459+
'<p><a href=\'http://www.example.org\' rel="nofollow ugc">http://www.example.org</a>',
460+
),
461+
array(
462+
$home_url_http,
463+
'<a href="' . $home_url_http . '" rel="ugc">' . $home_url_http . '</a>',
464+
),
465+
array(
466+
$home_url_https,
467+
'<a href="' . $home_url_https . '" rel="ugc">' . $home_url_https . '</a>',
449468
),
450469
);
451470
}

0 commit comments

Comments
 (0)