@@ -3146,9 +3146,25 @@ function( $matches ) {
31463146 */
31473147function wp_targeted_link_rel ( $ text ) {
31483148 // Don't run (more expensive) regex if no links with targets.
3149- if ( stripos ( $ text , 'target ' ) !== false && stripos ( $ text , '<a ' ) !== false ) {
3150- if ( ! is_serialized ( $ text ) ) {
3151- $ text = preg_replace_callback ( '|<a\s([^>]*target\s*=[^>]*)>|i ' , 'wp_targeted_link_rel_callback ' , $ text );
3149+ if ( stripos ( $ text , 'target ' ) === false || stripos ( $ text , '<a ' ) === false || is_serialized ( $ text ) ) {
3150+ return $ text ;
3151+ }
3152+
3153+ $ script_and_style_regex = '/<(script|style).*?<\/ \\1>/si ' ;
3154+
3155+ preg_match_all ( $ script_and_style_regex , $ text , $ matches );
3156+ $ extra_parts = $ matches [0 ];
3157+ $ html_parts = preg_split ( $ script_and_style_regex , $ text );
3158+
3159+ foreach ( $ html_parts as &$ part ) {
3160+ $ part = preg_replace_callback ( '|<a\s([^>]*target\s*=[^>]*)>|i ' , 'wp_targeted_link_rel_callback ' , $ part );
3161+ }
3162+
3163+ $ text = '' ;
3164+ for ( $ i = 0 ; $ i < count ( $ html_parts ); $ i ++ ) {
3165+ $ text .= $ html_parts [ $ i ];
3166+ if ( isset ( $ extra_parts [ $ i ] ) ) {
3167+ $ text .= $ extra_parts [ $ i ];
31523168 }
31533169 }
31543170
@@ -3167,8 +3183,17 @@ function wp_targeted_link_rel( $text ) {
31673183 * @return string HTML A Element with rel noreferrer noopener in addition to any existing values
31683184 */
31693185function wp_targeted_link_rel_callback ( $ matches ) {
3170- $ link_html = $ matches [1 ];
3171- $ rel_match = array ();
3186+ $ link_html = $ matches [1 ];
3187+ $ original_link_html = $ link_html ;
3188+
3189+ // Consider the html escaped if there are no unescaped quotes
3190+ $ is_escaped = ! preg_match ( '/(^|[^ \\\\])[ \'"]/ ' , $ link_html );
3191+ if ( $ is_escaped ) {
3192+ // Replace only the quotes so that they are parsable by wp_kses_hair, leave the rest as is
3193+ $ link_html = preg_replace ( '/ \\\\([ \'"])/ ' , '$1 ' , $ link_html );
3194+ }
3195+
3196+ $ atts = wp_kses_hair ( $ link_html , wp_allowed_protocols () );
31723197
31733198 /**
31743199 * Filters the rel values that are added to links with `target` attribute.
@@ -3180,35 +3205,21 @@ function wp_targeted_link_rel_callback( $matches ) {
31803205 */
31813206 $ rel = apply_filters ( 'wp_targeted_link_rel ' , 'noopener noreferrer ' , $ link_html );
31823207
3183- // Avoid additional regex if the filter removes rel values.
3184- if ( ! $ rel ) {
3185- return "<a $ link_html> " ;
3186- }
3187-
3188- // Value with delimiters, spaces around are optional.
3189- $ attr_regex = '|rel\s*=\s*?( \\\\{0,1}[" \'])(.*?) \\1|i ' ;
3190- preg_match ( $ attr_regex , $ link_html , $ rel_match );
3191-
3192- if ( empty ( $ rel_match [0 ] ) ) {
3193- // No delimiters, try with a single value and spaces, because `rel = va"lue` is totally fine...
3194- $ attr_regex = '|rel\s*=(\s*)([^\s]*)|i ' ;
3195- preg_match ( $ attr_regex , $ link_html , $ rel_match );
3196- }
3197-
3198- if ( ! empty ( $ rel_match [0 ] ) ) {
3199- $ parts = preg_split ( '|\s+| ' , strtolower ( $ rel_match [2 ] ) );
3200- $ parts = array_map ( 'esc_attr ' , $ parts );
3201- $ needed = explode ( ' ' , $ rel );
3202- $ parts = array_unique ( array_merge ( $ parts , $ needed ) );
3203- $ delimiter = trim ( $ rel_match [1 ] ) ? $ rel_match [1 ] : '" ' ;
3204- $ rel = 'rel= ' . $ delimiter . trim ( implode ( ' ' , $ parts ) ) . $ delimiter ;
3205- $ link_html = str_replace ( $ rel_match [0 ], $ rel , $ link_html );
3206- } elseif ( preg_match ( '|target\s*=\s*? \\\\"| ' , $ link_html ) ) {
3207- $ link_html .= " rel= \\\"$ rel \\\"" ;
3208- } elseif ( preg_match ( '#(target|href)\s*=\s*? \'# ' , $ link_html ) ) {
3209- $ link_html .= " rel=' $ rel' " ;
3210- } else {
3211- $ link_html .= " rel= \"$ rel \"" ;
3208+ // Return early if no rel values to be added or if no actual target attribute
3209+ if ( ! $ rel || ! isset ( $ atts ['target ' ] ) ) {
3210+ return "<a $ original_link_html> " ;
3211+ }
3212+
3213+ if ( isset ( $ atts ['rel ' ] ) ) {
3214+ $ all_parts = preg_split ( '/\s/ ' , "{$ atts ['rel ' ]['value ' ]} $ rel " , -1 , PREG_SPLIT_NO_EMPTY );
3215+ $ rel = implode ( ' ' , array_unique ( $ all_parts ) );
3216+ }
3217+
3218+ $ atts ['rel ' ]['whole ' ] = 'rel=" ' . esc_attr ( $ rel ) . '" ' ;
3219+ $ link_html = join ( ' ' , array_column ( $ atts , 'whole ' ) );
3220+
3221+ if ( $ is_escaped ) {
3222+ $ link_html = preg_replace ( '/[ \'"]/ ' , '\\\\$0 ' , $ link_html );
32123223 }
32133224
32143225 return "<a $ link_html> " ;
0 commit comments