@@ -3306,89 +3306,47 @@ static function( $matches ) {
33063306 *
33073307 * @since 5.1.0
33083308 * @since 5.6.0 Removed 'noreferrer' relationship.
3309+ * @since 6.3.0 Rely on the Tag Processor for HTML searching and modification.
33093310 *
33103311 * @param string $text Content that may contain HTML A elements.
33113312 * @return string Converted content.
33123313 */
33133314function wp_targeted_link_rel ( $ text ) {
3314- // Don't run (more expensive) regex if no links with targets.
3315+ // Don't run (more expensive) code if no links with targets are possible .
33153316 if ( stripos ( $ text , 'target ' ) === false || stripos ( $ text , '<a ' ) === false || is_serialized ( $ text ) ) {
33163317 return $ text ;
33173318 }
33183319
3319- $ script_and_style_regex = '/<(script|style).*?<\/ \\1>/si ' ;
3320-
3321- preg_match_all ( $ script_and_style_regex , $ text , $ matches );
3322- $ extra_parts = $ matches [0 ];
3323- $ html_parts = preg_split ( $ script_and_style_regex , $ text );
3324-
3325- foreach ( $ html_parts as &$ part ) {
3326- $ part = preg_replace_callback ( '|<a\s([^>]*target\s*=[^>]*)>|i ' , 'wp_targeted_link_rel_callback ' , $ part );
3327- }
3328-
3329- $ text = '' ;
3330- for ( $ i = 0 ; $ i < count ( $ html_parts ); $ i ++ ) {
3331- $ text .= $ html_parts [ $ i ];
3332- if ( isset ( $ extra_parts [ $ i ] ) ) {
3333- $ text .= $ extra_parts [ $ i ];
3320+ $ p = new WP_HTML_Tag_Processor ( $ text );
3321+ while ( $ p ->next_tag ( 'a ' ) ) {
3322+ $ target = $ p ->get_attribute ( 'target ' );
3323+ if ( null === $ target ) {
3324+ continue ;
33343325 }
3335- }
3336-
3337- return $ text ;
3338- }
3339-
3340- /**
3341- * Callback to add `rel="noopener"` string to HTML A element.
3342- *
3343- * Will not duplicate an existing 'noopener' value to avoid invalidating the HTML.
3344- *
3345- * @since 5.1.0
3346- * @since 5.6.0 Removed 'noreferrer' relationship.
3347- *
3348- * @param array $matches Single match.
3349- * @return string HTML A Element with `rel="noopener"` in addition to any existing values.
3350- */
3351- function wp_targeted_link_rel_callback ( $ matches ) {
3352- $ link_html = $ matches [1 ];
3353- $ original_link_html = $ link_html ;
3354-
3355- // Consider the HTML escaped if there are no unescaped quotes.
3356- $ is_escaped = ! preg_match ( '/(^|[^ \\\\])[ \'"]/ ' , $ link_html );
3357- if ( $ is_escaped ) {
3358- // Replace only the quotes so that they are parsable by wp_kses_hair(), leave the rest as is.
3359- $ link_html = preg_replace ( '/ \\\\([ \'"])/ ' , '$1 ' , $ link_html );
3360- }
3361-
3362- $ atts = wp_kses_hair ( $ link_html , wp_allowed_protocols () );
3363-
3364- /**
3365- * Filters the rel values that are added to links with `target` attribute.
3366- *
3367- * @since 5.1.0
3368- *
3369- * @param string $rel The rel values.
3370- * @param string $link_html The matched content of the link tag including all HTML attributes.
3371- */
3372- $ rel = apply_filters ( 'wp_targeted_link_rel ' , 'noopener ' , $ link_html );
33733326
3374- // Return early if no rel values to be added or if no actual target attribute.
3375- if ( ! $ rel || ! isset ( $ atts ['target ' ] ) ) {
3376- return "<a $ original_link_html> " ;
3377- }
3378-
3379- if ( isset ( $ atts ['rel ' ] ) ) {
3380- $ all_parts = preg_split ( '/\s/ ' , "{$ atts ['rel ' ]['value ' ]} $ rel " , -1 , PREG_SPLIT_NO_EMPTY );
3381- $ rel = implode ( ' ' , array_unique ( $ all_parts ) );
3382- }
3327+ $ rel = $ p ->get_attribute ( 'rel ' );
3328+ $ rel = true === $ rel ? "" : $ rel ;
3329+ $ link_text = "rel= \"{$ rel }\"" ;
33833330
3384- $ atts ['rel ' ]['whole ' ] = 'rel=" ' . esc_attr ( $ rel ) . '" ' ;
3385- $ link_html = implode ( ' ' , array_column ( $ atts , 'whole ' ) );
3331+ /**
3332+ * Filters the rel values that are added to links with `target` attribute.
3333+ *
3334+ * @since 5.1.0
3335+ *
3336+ * @param string $rel The rel values.
3337+ * @param string $link_html The matched content of the link tag including all HTML attributes.
3338+ */
3339+ $ updated_rel = apply_filters ( 'wp_targeted_link_rel ' , 'noopener ' , $ link_text );
3340+ if ( ! $ updated_rel ) {
3341+ continue ;
3342+ }
33863343
3387- if ( $ is_escaped ) {
3388- $ link_html = preg_replace ( '/[ \'"]/ ' , '\\\\$0 ' , $ link_html );
3344+ $ all_parts = preg_split ( '/\s/ ' , "$ rel $ updated_rel " , -1 , PREG_SPLIT_NO_EMPTY );
3345+ $ new_rel = implode ( ' ' , array_unique ( $ all_parts ) );
3346+ $ p ->set_attribute ( 'rel ' , $ new_rel );
33893347 }
33903348
3391- return " <a $ link_html > " ;
3349+ return $ p -> get_updated_html () ;
33923350}
33933351
33943352/**
0 commit comments