@@ -3220,34 +3220,81 @@ function _split_str_by_whitespace( $text, $goal ) {
32203220 * @return string HTML A element with the added rel attribute.
32213221 */
32223222function wp_rel_callback ( $ matches , $ rel ) {
3223- $ text = $ matches [1 ];
3224- $ atts = wp_kses_hair ( $ matches [1 ], wp_allowed_protocols () );
3223+ _deprecated_function (
3224+ __FUNCTION__ ,
3225+ '{WP_VERSION} ' ,
3226+ 'wp_include_in_all_a_rel() '
3227+ );
3228+ return wp_include_in_all_a_rel ( $ matches [0 ], $ rel );
3229+ }
32253230
3226- if ( ! empty ( $ atts ['href ' ] ) && wp_is_internal_link ( $ atts ['href ' ]['value ' ] ) ) {
3227- $ rel = trim ( str_replace ( 'nofollow ' , '' , $ rel ) );
3231+ /**
3232+ * Ensures that all A elements in the given HTML contain
3233+ * the provided and unique “rel” keywords.
3234+ *
3235+ * Example:
3236+ *
3237+ * `<a rel="nofollow">` === wp_include_in_all_a_rel( '<a>', 'nofollow' );
3238+ * `<a rel="nofollow">` === wp_include_in_all_a_rel( '<a rel="nofollow">', 'nofollow' );
3239+ * `<a rel="pingback nofollow">` === wp_include_in_all_a_rel( '<a rel="pingback">', 'nofollow' );
3240+ * `<a rel="a b c">` === wp_include_in_all_a_rel( '<a rel="a a a">`, 'a a a b b c' );
3241+ *
3242+ * @since {WP_VERSION}
3243+ *
3244+ * @param string $html Add the given `rel` keywords to every `A` tag in this HTML.
3245+ * @param string $space_separated_rel_keywords Each of these keywords will be present in the final HTML.
3246+ * @return string Modified HTML with all `A` tags containing the given `rel` keywords.
3247+ */
3248+ function wp_include_in_all_a_rel ( $ html , $ space_separated_rel_keywords ) {
3249+ if ( empty ( $ html ) || empty ( $ space_separated_rel_keywords ) ) {
3250+ return $ html ;
32283251 }
32293252
3230- if ( ! empty ( $ atts [ ' rel ' ] ) ) {
3231- $ parts = array_map ( ' trim ' , explode ( ' ' , $ atts [ ' rel ' ][ ' value ' ] ) ) ;
3232- $ rel_array = array_map ( ' trim ' , explode ( ' ' , $ rel ) );
3233- $ parts = array_unique ( array_merge ( $ parts , $ rel_array ) );
3234- $ rel = implode ( ' ' , $ parts ) ;
3235- unset( $ atts [ ' rel ' ] ) ;
3253+ /*
3254+ * It’s not necessary to add the `nofollow` guard to internal links ;
3255+ * these are used to only check and remove `nofollow` when adding it.
3256+ */
3257+ $ without_nofollow = $ space_separated_rel_keywords ;
3258+ $ adding_no_follow = false ;
32363259
3237- $ html = '' ;
3238- foreach ( $ atts as $ name => $ value ) {
3239- if ( isset ( $ value ['vless ' ] ) && 'y ' === $ value ['vless ' ] ) {
3240- $ html .= $ name . ' ' ;
3260+ /*
3261+ * Although this could falsely match on longer tokens like `nofollowers`,
3262+ * it’s safe to check generously since the parsing will ensure that only
3263+ * `nofollow` is removed; only a bit of unnecessary processing will occur.
3264+ */
3265+ if ( str_contains ( $ without_nofollow , 'nofollow ' ) ) {
3266+ $ without_nofollow = '' ;
3267+ $ tokens = WP_HTML_Attribute::from_unordered_set_of_space_separated_tokens ( $ without_nofollow );
3268+
3269+ foreach ( $ tokens as $ token ) {
3270+ if ( 'nofollow ' === $ token ) {
3271+ $ adding_no_follow = true ;
32413272 } else {
3242- $ html .= "{ $ name } = \"" . esc_attr ( $ value [ ' value ' ] ) . ' " ' ;
3273+ $ without_nofollow .= " { $ token }" ;
32433274 }
32443275 }
3245- $ text = trim ( $ html );
32463276 }
32473277
3248- $ rel_attr = $ rel ? ' rel=" ' . esc_attr ( $ rel ) . '" ' : '' ;
3278+ // Update the `rel` attributes in every `A` element.
3279+ $ processor = new WP_HTML_Tag_Processor ( $ html );
3280+ while ( $ processor ->next_tag ( 'A ' ) ) {
3281+ $ rel = $ processor ->get_attribute ( 'rel ' );
3282+ $ rel = is_string ( $ rel ) ? $ rel : '' ;
32493283
3250- return "<a {$ text }{$ rel_attr }> " ;
3284+ $ href = $ adding_no_follow ? $ processor ->get_attribute ( 'href ' ) : null ;
3285+ $ skip_nofollow = is_string ( $ href ) && wp_is_internal_link ( $ href );
3286+
3287+ $ combined = $ skip_nofollow
3288+ ? "{$ rel } {$ without_nofollow }"
3289+ : "{$ rel } {$ space_separated_rel_keywords }" ;
3290+
3291+ $ tokens = WP_HTML_Attribute::from_unordered_set_of_space_separated_tokens ( $ combined );
3292+ $ new_rel = empty ( $ tokens ) ? false : implode ( ' ' , $ tokens );
3293+
3294+ $ processor ->set_attribute ( 'rel ' , $ new_rel );
3295+ }
3296+
3297+ return $ processor ->get_updated_html ();
32513298}
32523299
32533300/**
@@ -3261,13 +3308,7 @@ function wp_rel_callback( $matches, $rel ) {
32613308function wp_rel_nofollow ( $ text ) {
32623309 // This is a pre-save filter, so text is already escaped.
32633310 $ text = stripslashes ( $ text );
3264- $ text = preg_replace_callback (
3265- '|<a (.+?)>|i ' ,
3266- static function ( $ matches ) {
3267- return wp_rel_callback ( $ matches , 'nofollow ' );
3268- },
3269- $ text
3270- );
3311+ $ text = wp_include_in_all_a_rel ( $ text , 'nofollow ' );
32713312 return wp_slash ( $ text );
32723313}
32733314
@@ -3281,6 +3322,11 @@ static function ( $matches ) {
32813322 * @return string HTML A Element with `rel="nofollow"`.
32823323 */
32833324function wp_rel_nofollow_callback ( $ matches ) {
3325+ _deprecated_function (
3326+ __FUNCTION__ ,
3327+ '{WP_VERSION} ' ,
3328+ 'wp_include_in_all_a_rel() '
3329+ );
32843330 return wp_rel_callback ( $ matches , 'nofollow ' );
32853331}
32863332
@@ -3295,13 +3341,7 @@ function wp_rel_nofollow_callback( $matches ) {
32953341function wp_rel_ugc ( $ text ) {
32963342 // This is a pre-save filter, so text is already escaped.
32973343 $ text = stripslashes ( $ text );
3298- $ text = preg_replace_callback (
3299- '|<a (.+?)>|i ' ,
3300- static function ( $ matches ) {
3301- return wp_rel_callback ( $ matches , 'nofollow ugc ' );
3302- },
3303- $ text
3304- );
3344+ $ text = wp_include_in_all_a_rel ( $ text , 'nofollow ugc ' );
33053345 return wp_slash ( $ text );
33063346}
33073347
0 commit comments