From 0e26adbaeaf801305332cb54252665042262c3c8 Mon Sep 17 00:00:00 2001
From: Sergey Biryukov
Date: Mon, 30 Sep 2019 01:29:10 +0000
Subject: [PATCH] Comments: Add `rel="nofollow ugc"` attribute to links in
comments.
UGC stands for User Generated Content, and the `ugc` attribute value is recommended for links within user generated content, such as comments and forum posts.
See https://webmasters.googleblog.com/2019/09/evolving-nofollow-new-ways-to-identify.html.
Props audrasjb, joostdevalk, dkarfa, SergeyBiryukov.
Fixes #48022.
git-svn-id: https://develop.svn.wordpress.org/trunk@46349 602fd350-edb4-49c9-b593-d223f7449a82
---
src/wp-includes/comment-template.php | 2 +-
src/wp-includes/default-filters.php | 2 +-
src/wp-includes/formatting.php | 98 +++++++++++++++------
tests/phpunit/tests/formatting/WPRelUgc.php | 83 +++++++++++++++++
4 files changed, 154 insertions(+), 31 deletions(-)
create mode 100644 tests/phpunit/tests/formatting/WPRelUgc.php
diff --git a/src/wp-includes/comment-template.php b/src/wp-includes/comment-template.php
index 69dcc1629a..294370c0fe 100644
--- a/src/wp-includes/comment-template.php
+++ b/src/wp-includes/comment-template.php
@@ -224,7 +224,7 @@ function get_comment_author_link( $comment_ID = 0 ) {
if ( empty( $url ) || 'http://' == $url ) {
$return = $author;
} else {
- $return = "$author";
+ $return = "$author";
}
/**
diff --git a/src/wp-includes/default-filters.php b/src/wp-includes/default-filters.php
index 423db4d540..025cabda19 100644
--- a/src/wp-includes/default-filters.php
+++ b/src/wp-includes/default-filters.php
@@ -246,7 +246,7 @@ add_filter( 'pre_kses', 'wp_pre_kses_less_than' );
add_filter( 'sanitize_title', 'sanitize_title_with_dashes', 10, 3 );
add_action( 'check_comment_flood', 'check_comment_flood_db', 10, 4 );
add_filter( 'comment_flood_filter', 'wp_throttle_comment_flood', 10, 3 );
-add_filter( 'pre_comment_content', 'wp_rel_nofollow', 15 );
+add_filter( 'pre_comment_content', 'wp_rel_ugc', 15 );
add_filter( 'comment_email', 'antispambot' );
add_filter( 'option_tag_base', '_wp_filter_taxonomy_base' );
add_filter( 'option_category_base', '_wp_filter_taxonomy_base' );
diff --git a/src/wp-includes/formatting.php b/src/wp-includes/formatting.php
index eeb3eb62af..ae772b8d0d 100644
--- a/src/wp-includes/formatting.php
+++ b/src/wp-includes/formatting.php
@@ -3012,35 +3012,19 @@ function _split_str_by_whitespace( $string, $goal ) {
}
/**
- * Adds rel nofollow string to all HTML A elements in content.
+ * Callback to add a rel attribute to HTML A element.
*
- * @since 1.5.0
+ * Will remove already existing string before adding to prevent invalidating (X)HTML.
*
- * @param string $text Content that may contain HTML A elements.
- * @return string Converted content.
+ * @since 5.3.0
+ *
+ * @param array $matches Single match.
+ * @param string $rel The rel attribute to add.
+ * @return string HTML A element with the added rel attribute.
*/
-function wp_rel_nofollow( $text ) {
- // This is a pre save filter, so text is already escaped.
- $text = stripslashes( $text );
- $text = preg_replace_callback( '||i', 'wp_rel_nofollow_callback', $text );
- return wp_slash( $text );
-}
-
-/**
- * Callback to add rel=nofollow string to HTML A element.
- *
- * Will remove already existing rel="nofollow" and rel='nofollow' from the
- * string to prevent from invalidating (X)HTML.
- *
- * @since 2.3.0
- *
- * @param array $matches Single Match
- * @return string HTML A Element with rel nofollow.
- */
-function wp_rel_nofollow_callback( $matches ) {
+function wp_rel_callback( $matches, $rel ) {
$text = $matches[1];
$atts = wp_kses_hair( $matches[1], wp_allowed_protocols() );
- $rel = 'nofollow';
if ( ! empty( $atts['href'] ) ) {
if ( in_array( strtolower( wp_parse_url( $atts['href']['value'], PHP_URL_SCHEME ) ), array( 'http', 'https' ), true ) ) {
@@ -3051,11 +3035,10 @@ function wp_rel_nofollow_callback( $matches ) {
}
if ( ! empty( $atts['rel'] ) ) {
- $parts = array_map( 'trim', explode( ' ', $atts['rel']['value'] ) );
- if ( false === array_search( 'nofollow', $parts ) ) {
- $parts[] = 'nofollow';
- }
- $rel = implode( ' ', $parts );
+ $parts = array_map( 'trim', explode( ' ', $atts['rel']['value'] ) );
+ $rel_array = array_map( 'trim', explode( ' ', $rel ) );
+ $parts = array_unique( array_merge( $parts, $rel_array ) );
+ $rel = implode( ' ', $parts );
unset( $atts['rel'] );
$html = '';
@@ -3071,6 +3054,63 @@ function wp_rel_nofollow_callback( $matches ) {
return "';
}
+/**
+ * Adds `rel="nofollow"` string to all HTML A elements in content.
+ *
+ * @since 1.5.0
+ *
+ * @param string $text Content that may contain HTML A elements.
+ * @return string Converted content.
+ */
+function wp_rel_nofollow( $text ) {
+ // This is a pre save filter, so text is already escaped.
+ $text = stripslashes( $text );
+ $rel = 'nofollow';
+ $text = preg_replace_callback(
+ '||i',
+ function( $matches ) use ( $rel ) {
+ return wp_rel_callback( $matches, $rel );
+ },
+ $text
+ );
+ return wp_slash( $text );
+}
+
+/**
+ * Callback to add `rel="nofollow"` string to HTML A element.
+ *
+ * @since 2.3.0
+ * @deprecated 5.3.0 Use wp_rel_callback()
+ *
+ * @param array $matches Single match.
+ * @return string HTML A Element with `rel="nofollow"`.
+ */
+function wp_rel_nofollow_callback( $matches ) {
+ return wp_rel_callback( $matches, 'nofollow' );
+}
+
+/**
+ * Adds `rel="nofollow ugc"` string to all HTML A elements in content.
+ *
+ * @since 5.3.0
+ *
+ * @param string $text Content that may contain HTML A elements.
+ * @return string Converted content.
+ */
+function wp_rel_ugc( $text ) {
+ // This is a pre save filter, so text is already escaped.
+ $text = stripslashes( $text );
+ $rel = 'nofollow ugc';
+ $text = preg_replace_callback(
+ '||i',
+ function( $matches ) use ( $rel ) {
+ return wp_rel_callback( $matches, $rel );
+ },
+ $text
+ );
+ return wp_slash( $text );
+}
+
/**
* Adds rel noreferrer and noopener to all HTML A elements that have a target.
*
diff --git a/tests/phpunit/tests/formatting/WPRelUgc.php b/tests/phpunit/tests/formatting/WPRelUgc.php
new file mode 100644
index 0000000000..515eebb736
--- /dev/null
+++ b/tests/phpunit/tests/formatting/WPRelUgc.php
@@ -0,0 +1,83 @@
+This is some cool Code
';
+ $expected = 'This is some cool Code
';
+ $this->assertEquals( $expected, wp_rel_ugc( $content ) );
+ }
+
+ /**
+ * @ticket 48022
+ */
+ public function test_convert_ugc() {
+ $content = 'This is some cool Code
';
+ $expected = 'This is some cool Code
';
+ $this->assertEquals( $expected, wp_rel_ugc( $content ) );
+ }
+
+ /**
+ * @ticket 48022
+ * @dataProvider data_wp_rel_ugc
+ */
+ public function test_wp_rel_ugc( $input, $output ) {
+ return $this->assertEquals( wp_slash( $output ), wp_rel_ugc( $input ) );
+ }
+
+ public function data_wp_rel_ugc() {
+ $home_url_http = set_url_scheme( home_url(), 'http' );
+ $home_url_https = set_url_scheme( home_url(), 'https' );
+
+ return array(
+ array(
+ 'Double Quotes',
+ 'Double Quotes',
+ ),
+ array(
+ 'Double Quotes',
+ 'Double Quotes',
+ ),
+ array(
+ "Single Quotes",
+ "Single Quotes",
+ ),
+ array(
+ 'Multiple attributes',
+ 'Multiple attributes',
+ ),
+ array(
+ 'Multiple attributes',
+ 'Multiple attributes',
+ ),
+ array(
+ 'Multiple attributes',
+ 'Multiple attributes',
+ ),
+ array(
+ 'Everything at once',
+ 'Everything at once',
+ ),
+ array(
+ 'Home URL (http)',
+ 'Home URL (http)',
+ ),
+ array(
+ 'Home URL (https)',
+ 'Home URL (https)',
+ ),
+ );
+ }
+
+ public function test_append_ugc_with_valueless_attribute() {
+ $content = 'This is some cool Code
';
+ $expected = 'This is some cool Code
';
+ $this->assertEquals( $expected, wp_rel_ugc( $content ) );
+ }
+}