diff --git a/wp-includes/comment-template.php b/wp-includes/comment-template.php index ba6851e554..b5cbdc5cbe 100644 --- a/wp-includes/comment-template.php +++ b/wp-includes/comment-template.php @@ -218,14 +218,45 @@ function get_comment_author_email_link( $linktext = '', $before = '', $after = ' * @return string The comment author name or HTML link for author's URL. */ function get_comment_author_link( $comment_ID = 0 ) { - $comment = get_comment( $comment_ID ); - $url = get_comment_author_url( $comment ); - $author = get_comment_author( $comment ); + $comment = get_comment( $comment_ID ); + $comment_ID = ! empty( $comment->comment_ID ) ? $comment->comment_ID : (string) $comment_ID; + $url = get_comment_author_url( $comment ); + $author = get_comment_author( $comment ); if ( empty( $url ) || 'http://' === $url ) { $return = $author; } else { - $return = "$author"; + $rel_parts = array( 'ugc' ); + if ( ! wp_is_internal_link( $url ) ) { + $rel_parts = array_merge( + $rel_parts, + array( 'external', 'nofollow' ) + ); + } + + /** + * Filters the rel attributes of the comment author's link. + * + * @since 6.2.0 + * + * @param string[] $rel_parts An array of strings representing the rel + * tags which will be joined into the anchor's + * rel attribute. + * @param WP_Comment $comment The comment object + */ + $rel_parts = apply_filters( 'comment_author_link_rel', $rel_parts, $comment ); + + $rel = implode( ' ', $rel_parts ); + $rel = esc_attr( $rel ); + // empty space before rel necessary for later sprintf. + $rel = ! empty( $rel ) ? sprintf( ' rel="%s"', $rel ) : ''; + + $return = sprintf( + '%3$s', + $url, + $rel, + $author + ); } /** @@ -239,7 +270,7 @@ function get_comment_author_link( $comment_ID = 0 ) { * @param string $author The comment author's username. * @param string $comment_ID The comment ID as a numeric string. */ - return apply_filters( 'get_comment_author_link', $return, $author, $comment->comment_ID ); + return apply_filters( 'get_comment_author_link', $return, $author, $comment_ID ); } /** diff --git a/wp-includes/formatting.php b/wp-includes/formatting.php index d598d354d3..bb822b24b5 100644 --- a/wp-includes/formatting.php +++ b/wp-includes/formatting.php @@ -2917,24 +2917,9 @@ function _make_url_clickable_cb( $matches ) { return $matches[0]; } - if ( 'comment_text' === current_filter() ) { - $rel = 'nofollow ugc'; - } else { - $rel = 'nofollow'; - } + $rel_attr = _make_clickable_rel_attr( $url ); + return $matches[1] . "$url" . $suffix; - /** - * Filters the rel value that is added to URL matches converted to links. - * - * @since 5.3.0 - * - * @param string $rel The rel value. - * @param string $url The matched URL being converted to a link tag. - */ - $rel = apply_filters( 'make_clickable_rel', $rel, $url ); - $rel = esc_attr( $rel ); - - return $matches[1] . "$url" . $suffix; } /** @@ -2965,17 +2950,8 @@ function _make_web_ftp_clickable_cb( $matches ) { return $matches[0]; } - if ( 'comment_text' === current_filter() ) { - $rel = 'nofollow ugc'; - } else { - $rel = 'nofollow'; - } - - /** This filter is documented in wp-includes/formatting.php */ - $rel = apply_filters( 'make_clickable_rel', $rel, $dest ); - $rel = esc_attr( $rel ); - - return $matches[1] . "$dest$ret"; + $rel_attr = _make_clickable_rel_attr( $dest ); + return $matches[1] . "{$dest}{$ret}"; } /** @@ -2994,6 +2970,48 @@ function _make_email_clickable_cb( $matches ) { return $matches[1] . "$email"; } +/** + * Helper function used to build the "rel" attribute for a URL when creating an anchor using make_clickable(). + * + * @since 6.2.0 + * + * @param string $url The URL. + * @return string The rel attribute for the anchor or an empty string if no rel attribute should be added. + */ +function _make_clickable_rel_attr( $url ) { + + $rel_parts = array(); + $scheme = strtolower( wp_parse_url( $url, PHP_URL_SCHEME ) ); + $nofollow_schemes = array_intersect( wp_allowed_protocols(), array( 'https', 'http' ) ); + + // Apply "nofollow" to external links with qualifying URL schemes (mailto:, tel:, etc... shouldn't be followed). + if ( ! wp_is_internal_link( $url ) && in_array( $scheme, $nofollow_schemes, true ) ) { + $rel_parts[] = 'nofollow'; + } + + // Apply "ugc" when in comment context. + if ( 'comment_text' === current_filter() ) { + $rel_parts[] = 'ugc'; + } + + $rel = implode( ' ', $rel_parts ); + + /** + * Filters the rel value that is added to URL matches converted to links. + * + * @since 5.3.0 + * + * @param string $rel The rel value. + * @param string $url The matched URL being converted to a link tag. + */ + $rel = apply_filters( 'make_clickable_rel', $rel, $url ); + + $rel_attr = $rel ? ' rel="' . esc_attr( $rel ) . '"' : ''; + + return $rel_attr; + +} + /** * Converts plaintext URI to HTML links. * @@ -3137,12 +3155,8 @@ function wp_rel_callback( $matches, $rel ) { $text = $matches[1]; $atts = wp_kses_hair( $matches[1], wp_allowed_protocols() ); - if ( ! empty( $atts['href'] ) ) { - if ( in_array( strtolower( wp_parse_url( $atts['href']['value'], PHP_URL_SCHEME ) ), array( 'http', 'https' ), true ) ) { - if ( strtolower( wp_parse_url( $atts['href']['value'], PHP_URL_HOST ) ) === strtolower( wp_parse_url( home_url(), PHP_URL_HOST ) ) ) { - return ""; - } - } + if ( ! empty( $atts['href'] ) && wp_is_internal_link( $atts['href']['value'] ) ) { + $rel = trim( str_replace( 'nofollow', '', $rel ) ); } if ( ! empty( $atts['rel'] ) ) { @@ -3162,7 +3176,10 @@ function wp_rel_callback( $matches, $rel ) { } $text = trim( $html ); } - return "'; + + $rel_attr = $rel ? ' rel="' . esc_attr( $rel ) . '"' : ''; + + return ""; } /** diff --git a/wp-includes/link-template.php b/wp-includes/link-template.php index 03e6a4cc49..e823282277 100644 --- a/wp-includes/link-template.php +++ b/wp-includes/link-template.php @@ -4688,3 +4688,63 @@ function get_the_privacy_policy_link( $before = '', $after = '' ) { return ''; } + +/** + * Returns an array of URL hosts which are considered to be internal hosts. + * + * By default the list of internal hosts is comproside of the PHP_URL_HOST of + * the site's home_url() (as parsed by wp_parse_url()). + * + * This list is used when determining if a specificed URL is a link to a page on + * the site itself or a link offsite (to an external host). This is used, for + * example, when determining if the "nofollow" attribute should be applied to a + * link. + * + * @see wp_is_internal_link + * + * @since 6.2.0 + * + * @return string[] An array of URL hosts. + */ +function wp_internal_hosts() { + static $internal_hosts; + + if ( empty( $internal_hosts ) ) { + /** + * Filters the array of URL hosts which are considered internal. + * + * @since 6.2.9 + * + * @param array $internal_hosts An array of internal URL hostnames. + */ + $internal_hosts = apply_filters( + 'wp_internal_hosts', + array( + wp_parse_url( home_url(), PHP_URL_HOST ), + ) + ); + $internal_hosts = array_unique( + array_map( 'strtolower', (array) $internal_hosts ) + ); + } + + return $internal_hosts; +} + +/** + * Determines whether or not the specified URL is of a host included in the internal hosts list. + * + * @see wp_internal_hosts() + * + * @since 6.2.0 + * + * @param string $link The URL to test. + * @return bool Returns true for internal URLs and false for all other URLs. + */ +function wp_is_internal_link( $link ) { + $link = strtolower( $link ); + if ( in_array( wp_parse_url( $link, PHP_URL_SCHEME ), wp_allowed_protocols(), true ) ) { + return in_array( wp_parse_url( $link, PHP_URL_HOST ), wp_internal_hosts(), true ); + } + return false; +} diff --git a/wp-includes/version.php b/wp-includes/version.php index 995c2adbe5..06d155f3a3 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -16,7 +16,7 @@ * * @global string $wp_version */ -$wp_version = '6.2-alpha-55288'; +$wp_version = '6.2-alpha-55289'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.