@@ -3220,34 +3220,81 @@ function _split_str_by_whitespace( $text, $goal ) {
3220
3220
* @return string HTML A element with the added rel attribute.
3221
3221
*/
3222
3222
function 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
+ }
3225
3230
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 ;
3228
3251
}
3229
3252
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 ;
3236
3259
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
+ $ tokens = WP_HTML_Attribute::from_unordered_set_of_space_separated_tokens ( $ without_nofollow );
3267
+ $ without_nofollow = '' ;
3268
+
3269
+ foreach ( $ tokens as $ token ) {
3270
+ if ( 'nofollow ' === $ token ) {
3271
+ $ adding_no_follow = true ;
3241
3272
} else {
3242
- $ html .= "{ $ name } = \"" . esc_attr ( $ value [ ' value ' ] ) . ' " ' ;
3273
+ $ without_nofollow .= " { $ token }" ;
3243
3274
}
3244
3275
}
3245
- $ text = trim ( $ html );
3246
3276
}
3247
3277
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 : '' ;
3249
3283
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 ();
3251
3298
}
3252
3299
3253
3300
/**
@@ -3261,13 +3308,7 @@ function wp_rel_callback( $matches, $rel ) {
3261
3308
function wp_rel_nofollow ( $ text ) {
3262
3309
// This is a pre-save filter, so text is already escaped.
3263
3310
$ 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 ' );
3271
3312
return wp_slash ( $ text );
3272
3313
}
3273
3314
@@ -3281,6 +3322,11 @@ static function ( $matches ) {
3281
3322
* @return string HTML A Element with `rel="nofollow"`.
3282
3323
*/
3283
3324
function wp_rel_nofollow_callback ( $ matches ) {
3325
+ _deprecated_function (
3326
+ __FUNCTION__ ,
3327
+ '{WP_VERSION} ' ,
3328
+ 'wp_include_in_all_a_rel() '
3329
+ );
3284
3330
return wp_rel_callback ( $ matches , 'nofollow ' );
3285
3331
}
3286
3332
@@ -3295,13 +3341,7 @@ function wp_rel_nofollow_callback( $matches ) {
3295
3341
function wp_rel_ugc ( $ text ) {
3296
3342
// This is a pre-save filter, so text is already escaped.
3297
3343
$ 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 ' );
3305
3345
return wp_slash ( $ text );
3306
3346
}
3307
3347
0 commit comments