Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 26 additions & 12 deletions src/wp-includes/general-template.php
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,7 @@ function wp_get_tooltip_helper( $content, $args = array() ) {

$defaults = array(
'id' => wp_unique_id( 'wp-tooltip-' ),
'button' => '<button type="button" aria-label="%3$s"><span class="dashicons %4$s" aria-hidden="true"></span></button>',
'button' => '',
'label' => __( 'Help' ),
'close_label' => __( 'Close' ),
'icon' => 'dashicons-editor-help',
Expand All @@ -488,15 +488,29 @@ function wp_get_tooltip_helper( $content, $args = array() ) {
$classes .= ' ' . $args['class'];
}

$icon = ( $args['icon'] ) ? trim( $args['icon'] ) : $defaults['icon'];
$id = ( $args['id'] ) ? $args['id'] : $defaults['id'];
$button = ( $args['button'] ) ? $args['button'] : $defaults['button'];
$icon = ( $args['icon'] ) ? trim( $args['icon'] ) : $defaults['icon'];
$id = ( $args['id'] ) ? $args['id'] : $defaults['id'];

// Tooltips use the content as the accessible name; toggletips use the label.
$label = ( 'tooltip' === $args['type'] ) ? wp_strip_all_tags( $content, true ) : $args['label'];

/*
* The generated button is built with its final values rather than with
* placeholders, so that caller-supplied markup is never scanned or
* substituted. A percent sign in custom markup, such as a percent-encoded
* URL, is therefore never treated as a conversion specification.
*/
$default_button = '<button type="button" aria-label="' . esc_attr( $label ) . '">' .
'<span class="dashicons ' . esc_attr( $icon ) . '" aria-hidden="true"></span>' .
'</button>';

$button = ( $args['button'] ) ? $args['button'] : $default_button;
$processed = false;
$processor = new WP_HTML_Tag_Processor( $button );
if ( true === $processor->next_tag( 'button' ) ) {
$processor->add_class( 'wp-tooltip__toggle' );
if ( 'tooltip' !== $args['type'] ) {
$processor->set_attribute( 'popovertarget', '%2$s' );
$processor->set_attribute( 'popovertarget', $id );
$processor->set_attribute( 'aria-haspopup', 'dialog' );
}
$button = $processor->get_updated_html();
Expand All @@ -512,10 +526,10 @@ function wp_get_tooltip_helper( $content, $args = array() ) {
}
if ( ! $processed ) {
// Button HTML passed was not valid.
$processor = new WP_HTML_Tag_Processor( $defaults['button'] );
$processor = new WP_HTML_Tag_Processor( $default_button );
$processor->add_class( 'wp-tooltip__toggle' );
if ( 'tooltip' !== $args['type'] ) {
$processor->set_attribute( 'popovertarget', '%2$s' );
$processor->set_attribute( 'popovertarget', $id );
$processor->set_attribute( 'aria-haspopup', 'dialog' );
}
$button = $processor->get_updated_html();
Expand All @@ -528,11 +542,9 @@ function wp_get_tooltip_helper( $content, $args = array() ) {
* the layout. See #65660.
*/
if ( 'tooltip' === $args['type'] ) {
// Tooltips are only used to visually display labels.
$label = wp_strip_all_tags( $content, true );
$markup = sprintf(
'<span class="%1$s">
' . $button . '
%6$s
<span popover="hint" id="%2$s" class="wp-tooltip__bubble" role="tooltip">' .
'<span id="%2$s-text" class="wp-tooltip__text">%5$s</span>' .
'</span>' .
Expand All @@ -542,6 +554,7 @@ function wp_get_tooltip_helper( $content, $args = array() ) {
esc_attr( $label ),
esc_attr( $icon ),
esc_html( $content ),
$button,
);
} else {
/*
Expand All @@ -551,7 +564,7 @@ function wp_get_tooltip_helper( $content, $args = array() ) {
*/
$markup = sprintf(
'<span class="%1$s">
' . $button . '
%7$s
<span popover="auto" id="%2$s" class="wp-tooltip__bubble" role="dialog" aria-label="%3$s" tabindex="-1" autofocus>' .
'<span id="%2$s-text" class="wp-tooltip__text">%5$s</span>' .
'<button type="button" class="wp-tooltip__close" popovertarget="%2$s" popovertargetaction="hide" aria-label="%6$s">' .
Expand All @@ -561,10 +574,11 @@ function wp_get_tooltip_helper( $content, $args = array() ) {
'</span>',
esc_attr( $classes ),
esc_attr( $id ),
esc_attr( $args['label'] ),
esc_attr( $label ),
esc_attr( $icon ),
esc_html( $content ),
esc_attr( $args['close_label'] ),
$button,
);
}

Expand Down
84 changes: 84 additions & 0 deletions tests/phpunit/tests/general/wpGetTooltip.php
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,90 @@ public function test_wp_get_toggletip_bubble_uses_dialog_role_and_autofocus() {
$this->assertStringContainsString( 'tabindex="-1" autofocus>', $html );
}

/**
* Tests that a percent sign in the button markup is not treated as a
* `sprintf()` conversion specification.
*
* @ticket 65914
*
* @dataProvider data_button_markup_containing_percent_signs
*
* @param string $button Button markup containing a percent sign.
* @param string $expected Substring that must be preserved in the output.
*/
public function test_wp_get_tooltip_preserves_percent_signs_in_button_markup( $button, $expected ) {
$this->assertStringContainsString(
$expected,
wp_get_tooltip( 'Helpful text.', array( 'button' => $button ) ),
'The tooltip did not preserve the percent sign.'
);
$this->assertStringContainsString(
$expected,
wp_get_toggletip( 'Helpful text.', array( 'button' => $button ) ),
'The toggletip did not preserve the percent sign.'
);
}

/**
* Tests that a percent-encoded URL in anchor markup is not rewritten.
*
* @ticket 65914
*
* @dataProvider data_percent_encoded_hrefs
*
* @param string $href A percent-encoded URL.
*/
public function test_wp_get_tooltip_preserves_percent_encoding_in_anchor_href( $href ) {
$html = wp_get_tooltip(
'Helpful text.',
array( 'button' => '<a href="' . $href . '">Search</a>' )
);

$this->assertStringContainsString( 'href="' . $href . '"', $html );
}

/**
* Data provider.
*
* @return array[]
*/
public function data_percent_encoded_hrefs() {
return array(
'an encoded space' => array( '/wp-admin/edit.php?s=hello%20world' ),
'an encoded slash' => array( '/x?p=a%2Fb' ),
);
}

/**
* Data provider.
*
* @return array[]
*/
public function data_button_markup_containing_percent_signs() {
return array(
'a literal percent sign' => array(
'<button type="button">100% done</button>',
'100% done',
),
'a percent sign then a word' => array(
'<button type="button">Save 20%!</button>',
'Save 20%!',
),
'an unknown format specifier' => array(
'<button type="button">Buy %q now</button>',
'Buy %q now',
),
'a percent sign in an ID' => array(
'<button type="button" aria-describedby="box_100%_complete-title">Move up</button>',
'aria-describedby="box_100%_complete-title"',
),
'text resembling a argnum' => array(
'<button type="button">Use %2$s in your code</button>',
'Use %2$s in your code',
),
);
}

/**
* Data provider.
*
Expand Down
Loading