Skip to content

Commit a95ce30

Browse files
committed
WIP: Explore using the HTML API for link rel processing
1 parent 305de43 commit a95ce30

1 file changed

Lines changed: 26 additions & 68 deletions

File tree

src/wp-includes/formatting.php

Lines changed: 26 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -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
*/
33133314
function 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

Comments
 (0)