Widgets: Add shortcode support inside Text widgets.
* Used now in core to facilitate displaying inserted media. See #40854. * The `[embed]` shortcode is not supported because there is no post context for caching oEmbed responses. This depends on #34115. * Add `do_shortcode()` to the `widget_text_content` filter in the same way it is added for `the_content` at priority 11, with `shortcode_unautop()` called at priority 10 after `wpautop()`. * For Text widget in legacy mode, manually apply `do_shortcode()` (and `shortcode_unautop()` if auto-paragraph checked) if the core-added `widget_text_content` filter remains, unless a plugin added `do_shortcode()` to `widget_text` to prevent applying shortcodes twice. * Ensure that global `$post` is `null` while filters apply in the Text widget so shortcode handlers won't run with unexpected contexts. Props westonruter, nacin, aaroncampbell. See #40854, #34115. Fixes #10457. git-svn-id: https://develop.svn.wordpress.org/trunk@41361 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
parent
249c416f0d
commit
6327832abe
|
@ -169,6 +169,8 @@ add_filter( 'widget_text_content', 'capital_P_dangit', 11 );
|
||||||
add_filter( 'widget_text_content', 'wptexturize' );
|
add_filter( 'widget_text_content', 'wptexturize' );
|
||||||
add_filter( 'widget_text_content', 'convert_smilies', 20 );
|
add_filter( 'widget_text_content', 'convert_smilies', 20 );
|
||||||
add_filter( 'widget_text_content', 'wpautop' );
|
add_filter( 'widget_text_content', 'wpautop' );
|
||||||
|
add_filter( 'widget_text_content', 'shortcode_unautop' );
|
||||||
|
add_filter( 'widget_text_content', 'do_shortcode', 11 ); // Runs after wpautop(); note that $post global will be null when shortcodes run.
|
||||||
|
|
||||||
add_filter( 'date_i18n', 'wp_maybe_decline_date' );
|
add_filter( 'date_i18n', 'wp_maybe_decline_date' );
|
||||||
|
|
||||||
|
|
|
@ -183,11 +183,14 @@ class WP_Widget_Text extends WP_Widget {
|
||||||
*
|
*
|
||||||
* @since 2.8.0
|
* @since 2.8.0
|
||||||
*
|
*
|
||||||
|
* @global WP_Post $post
|
||||||
|
*
|
||||||
* @param array $args Display arguments including 'before_title', 'after_title',
|
* @param array $args Display arguments including 'before_title', 'after_title',
|
||||||
* 'before_widget', and 'after_widget'.
|
* 'before_widget', and 'after_widget'.
|
||||||
* @param array $instance Settings for the current Text widget instance.
|
* @param array $instance Settings for the current Text widget instance.
|
||||||
*/
|
*/
|
||||||
public function widget( $args, $instance ) {
|
public function widget( $args, $instance ) {
|
||||||
|
global $post;
|
||||||
|
|
||||||
/** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */
|
/** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */
|
||||||
$title = apply_filters( 'widget_title', empty( $instance['title'] ) ? '' : $instance['title'], $instance, $this->id_base );
|
$title = apply_filters( 'widget_title', empty( $instance['title'] ) ? '' : $instance['title'], $instance, $this->id_base );
|
||||||
|
@ -205,16 +208,22 @@ class WP_Widget_Text extends WP_Widget {
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Just-in-time temporarily upgrade Visual Text widget shortcode handling
|
* Suspend legacy plugin-supplied do_shortcode() for 'widget_text' filter for the visual Text widget to prevent
|
||||||
* (with support added by plugin) from the widget_text filter to
|
* shortcodes being processed twice. Now do_shortcode() is added to the 'widget_text_content' filter in core itself
|
||||||
* widget_text_content:11 to prevent wpautop from corrupting HTML output
|
* and it applies after wpautop() to prevent corrupting HTML output added by the shortcode. When do_shortcode() is
|
||||||
* added by the shortcode.
|
* added to 'widget_text_content' then do_shortcode() will be manually called when in legacy mode as well.
|
||||||
*/
|
*/
|
||||||
$widget_text_do_shortcode_priority = has_filter( 'widget_text', 'do_shortcode' );
|
$widget_text_do_shortcode_priority = has_filter( 'widget_text', 'do_shortcode' );
|
||||||
$should_upgrade_shortcode_handling = ( $is_visual_text_widget && false !== $widget_text_do_shortcode_priority );
|
$should_suspend_legacy_shortcode_support = ( $is_visual_text_widget && false !== $widget_text_do_shortcode_priority );
|
||||||
if ( $should_upgrade_shortcode_handling ) {
|
if ( $should_suspend_legacy_shortcode_support ) {
|
||||||
remove_filter( 'widget_text', 'do_shortcode', $widget_text_do_shortcode_priority );
|
remove_filter( 'widget_text', 'do_shortcode', $widget_text_do_shortcode_priority );
|
||||||
add_filter( 'widget_text_content', 'do_shortcode', 11 );
|
}
|
||||||
|
|
||||||
|
// Nullify the $post global during widget rendering to prevent shortcodes from running with the unexpected context.
|
||||||
|
$suspended_post = null;
|
||||||
|
if ( isset( $post ) ) {
|
||||||
|
$suspended_post = $post;
|
||||||
|
$post = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -244,14 +253,35 @@ class WP_Widget_Text extends WP_Widget {
|
||||||
* @param WP_Widget_Text $this Current Text widget instance.
|
* @param WP_Widget_Text $this Current Text widget instance.
|
||||||
*/
|
*/
|
||||||
$text = apply_filters( 'widget_text_content', $text, $instance, $this );
|
$text = apply_filters( 'widget_text_content', $text, $instance, $this );
|
||||||
|
} else {
|
||||||
|
// Now in legacy mode, add paragraphs and line breaks when checkbox is checked.
|
||||||
|
if ( ! empty( $instance['filter'] ) ) {
|
||||||
|
$text = wpautop( $text );
|
||||||
|
}
|
||||||
|
|
||||||
} elseif ( ! empty( $instance['filter'] ) ) {
|
/*
|
||||||
$text = wpautop( $text ); // Back-compat for instances prior to 4.8.
|
* Manually do shortcodes on the content when the core-added filter is present. It is added by default
|
||||||
|
* in core by adding do_shortcode() to the 'widget_text_content' filter to apply after wpautop().
|
||||||
|
* Since the legacy Text widget runs wpautop() after 'widget_text' filters are applied, the widget in
|
||||||
|
* legacy mode here manually applies do_shortcode() on the content unless the default
|
||||||
|
* core filter for 'widget_text_content' has been removed, or if do_shortcode() has already
|
||||||
|
* been applied via a plugin adding do_shortcode() to 'widget_text' filters.
|
||||||
|
*/
|
||||||
|
if ( has_filter( 'widget_text_content', 'do_shortcode' ) && ! $widget_text_do_shortcode_priority ) {
|
||||||
|
if ( ! empty( $instance['filter'] ) ) {
|
||||||
|
$text = shortcode_unautop( $text );
|
||||||
|
}
|
||||||
|
$text = do_shortcode( $text );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Undo temporary upgrade of the plugin-supplied shortcode handling.
|
// Restore post global.
|
||||||
if ( $should_upgrade_shortcode_handling ) {
|
if ( isset( $suspended_post ) ) {
|
||||||
remove_filter( 'widget_text_content', 'do_shortcode', 11 );
|
$post = $suspended_post;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Undo suspension of legacy plugin-supplied shortcode handling.
|
||||||
|
if ( $should_suspend_legacy_shortcode_support ) {
|
||||||
add_filter( 'widget_text', 'do_shortcode', $widget_text_do_shortcode_priority );
|
add_filter( 'widget_text', 'do_shortcode', $widget_text_do_shortcode_priority );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -222,7 +222,21 @@ class Test_WP_Widget_Text extends WP_UnitTestCase {
|
||||||
*
|
*
|
||||||
* @var string
|
* @var string
|
||||||
*/
|
*/
|
||||||
protected $example_shortcode_content = "<p>One\nTwo\n\nThree</p>\n<script>\ndocument.write('Test1');\n\ndocument.write('Test2');\n</script>";
|
protected $example_shortcode_content = "<p class='sortcodep'>One\nTwo\n\nThree\n\nThis is testing the <code>[example note='This will not get processed since it is part of shortcode output itself.']</code> shortcode.</p>\n<script>\ndocument.write('Test1');\n\ndocument.write('Test2');\n</script>";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The captured global post during shortcode rendering.
|
||||||
|
*
|
||||||
|
* @var WP_Post|null
|
||||||
|
*/
|
||||||
|
protected $post_during_shortcode = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of times the shortcode was rendered.
|
||||||
|
*
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $shortcode_render_count = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Do example shortcode.
|
* Do example shortcode.
|
||||||
|
@ -230,15 +244,21 @@ class Test_WP_Widget_Text extends WP_UnitTestCase {
|
||||||
* @return string Shortcode content.
|
* @return string Shortcode content.
|
||||||
*/
|
*/
|
||||||
function do_example_shortcode() {
|
function do_example_shortcode() {
|
||||||
|
$this->post_during_shortcode = get_post();
|
||||||
|
$this->shortcode_render_count++;
|
||||||
return $this->example_shortcode_content;
|
return $this->example_shortcode_content;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test widget method when a plugin has added shortcode support.
|
* Test widget method with shortcodes.
|
||||||
*
|
*
|
||||||
* @covers WP_Widget_Text::widget
|
* @covers WP_Widget_Text::widget
|
||||||
*/
|
*/
|
||||||
function test_widget_shortcodes() {
|
function test_widget_shortcodes() {
|
||||||
|
global $post;
|
||||||
|
$post_id = $this->factory()->post->create();
|
||||||
|
$post = get_post( $post_id );
|
||||||
|
|
||||||
$args = array(
|
$args = array(
|
||||||
'before_title' => '<h2>',
|
'before_title' => '<h2>',
|
||||||
'after_title' => "</h2>\n",
|
'after_title' => "</h2>\n",
|
||||||
|
@ -246,47 +266,100 @@ class Test_WP_Widget_Text extends WP_UnitTestCase {
|
||||||
'after_widget' => "</section>\n",
|
'after_widget' => "</section>\n",
|
||||||
);
|
);
|
||||||
$widget = new WP_Widget_Text();
|
$widget = new WP_Widget_Text();
|
||||||
add_filter( 'widget_text', 'do_shortcode' );
|
|
||||||
add_shortcode( 'example', array( $this, 'do_example_shortcode' ) );
|
add_shortcode( 'example', array( $this, 'do_example_shortcode' ) );
|
||||||
|
|
||||||
$base_instance = array(
|
$base_instance = array(
|
||||||
'title' => 'Example',
|
'title' => 'Example',
|
||||||
'text' => "This is an example:\n\n[example]",
|
'text' => "This is an example:\n\n[example]\n\nHello.",
|
||||||
'filter' => false,
|
'filter' => false,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Legacy Text Widget.
|
// Legacy Text Widget without wpautop.
|
||||||
$instance = array_merge( $base_instance, array(
|
$instance = array_merge( $base_instance, array(
|
||||||
'filter' => false,
|
'filter' => false,
|
||||||
) );
|
) );
|
||||||
|
$this->shortcode_render_count = 0;
|
||||||
ob_start();
|
ob_start();
|
||||||
$widget->widget( $args, $instance );
|
$widget->widget( $args, $instance );
|
||||||
$output = ob_get_clean();
|
$output = ob_get_clean();
|
||||||
|
$this->assertEquals( 1, $this->shortcode_render_count );
|
||||||
|
$this->assertNotContains( '[example]', $output, 'Expected shortcode to be processed in legacy widget with plugin adding filter' );
|
||||||
$this->assertContains( $this->example_shortcode_content, $output, 'Shortcode was applied without wpautop corrupting it.' );
|
$this->assertContains( $this->example_shortcode_content, $output, 'Shortcode was applied without wpautop corrupting it.' );
|
||||||
$this->assertEquals( 10, has_filter( 'widget_text', 'do_shortcode' ), 'Filter was restored.' );
|
$this->assertNotContains( '<p>' . $this->example_shortcode_content . '</p>', $output, 'Expected shortcode_unautop() to have run.' );
|
||||||
|
$this->assertNull( $this->post_during_shortcode );
|
||||||
|
|
||||||
// Visual Text Widget.
|
// Legacy Text Widget with wpautop.
|
||||||
$instance = array_merge( $base_instance, array(
|
$instance = array_merge( $base_instance, array(
|
||||||
'filter' => 'content',
|
'filter' => true,
|
||||||
|
'visual' => false,
|
||||||
) );
|
) );
|
||||||
|
$this->shortcode_render_count = 0;
|
||||||
ob_start();
|
ob_start();
|
||||||
$widget->widget( $args, $instance );
|
$widget->widget( $args, $instance );
|
||||||
$output = ob_get_clean();
|
$output = ob_get_clean();
|
||||||
|
$this->assertEquals( 1, $this->shortcode_render_count );
|
||||||
|
$this->assertNotContains( '[example]', $output, 'Expected shortcode to be processed in legacy widget with plugin adding filter' );
|
||||||
$this->assertContains( $this->example_shortcode_content, $output, 'Shortcode was applied without wpautop corrupting it.' );
|
$this->assertContains( $this->example_shortcode_content, $output, 'Shortcode was applied without wpautop corrupting it.' );
|
||||||
$this->assertEquals( 10, has_filter( 'widget_text', 'do_shortcode' ), 'Filter was restored.' );
|
$this->assertNotContains( '<p>' . $this->example_shortcode_content . '</p>', $output, 'Expected shortcode_unautop() to have run.' );
|
||||||
$this->assertFalse( has_filter( 'widget_text_content', 'do_shortcode' ), 'Filter was removed.' );
|
$this->assertNull( $this->post_during_shortcode );
|
||||||
|
|
||||||
// Visual Text Widget with properly-used widget_text_content filter.
|
// Legacy text widget with plugin adding shortcode support as well.
|
||||||
|
add_filter( 'widget_text', 'do_shortcode' );
|
||||||
|
$this->shortcode_render_count = 0;
|
||||||
|
ob_start();
|
||||||
|
$widget->widget( $args, $instance );
|
||||||
|
$output = ob_get_clean();
|
||||||
|
$this->assertEquals( 1, $this->shortcode_render_count );
|
||||||
|
$this->assertNotContains( '[example]', $output, 'Expected shortcode to be processed in legacy widget with plugin adding filter' );
|
||||||
|
$this->assertContains( wpautop( $this->example_shortcode_content ), $output, 'Shortcode was applied *with* wpautop() applying to shortcode output since plugin used legacy filter.' );
|
||||||
|
$this->assertNull( $this->post_during_shortcode );
|
||||||
remove_filter( 'widget_text', 'do_shortcode' );
|
remove_filter( 'widget_text', 'do_shortcode' );
|
||||||
add_filter( 'widget_text_content', 'do_shortcode', 11 );
|
|
||||||
$instance = array_merge( $base_instance, array(
|
$instance = array_merge( $base_instance, array(
|
||||||
'filter' => 'content',
|
'filter' => true,
|
||||||
|
'visual' => true,
|
||||||
) );
|
) );
|
||||||
|
|
||||||
|
// Visual Text Widget with only core-added widget_text_content filter for do_shortcode.
|
||||||
|
$this->assertFalse( has_filter( 'widget_text', 'do_shortcode' ) );
|
||||||
|
$this->assertEquals( 11, has_filter( 'widget_text_content', 'do_shortcode' ), 'Expected core to have set do_shortcode as widget_text_content filter.' );
|
||||||
|
$this->shortcode_render_count = 0;
|
||||||
ob_start();
|
ob_start();
|
||||||
$widget->widget( $args, $instance );
|
$widget->widget( $args, $instance );
|
||||||
$output = ob_get_clean();
|
$output = ob_get_clean();
|
||||||
|
$this->assertEquals( 1, $this->shortcode_render_count );
|
||||||
$this->assertContains( $this->example_shortcode_content, $output, 'Shortcode was applied without wpautop corrupting it.' );
|
$this->assertContains( $this->example_shortcode_content, $output, 'Shortcode was applied without wpautop corrupting it.' );
|
||||||
$this->assertFalse( has_filter( 'widget_text', 'do_shortcode' ), 'Filter was not erroneously restored.' );
|
$this->assertNotContains( '<p>' . $this->example_shortcode_content . '</p>', $output, 'Expected shortcode_unautop() to have run.' );
|
||||||
|
$this->assertFalse( has_filter( 'widget_text', 'do_shortcode' ), 'The widget_text filter still lacks do_shortcode handler.' );
|
||||||
|
$this->assertEquals( 11, has_filter( 'widget_text_content', 'do_shortcode' ), 'The widget_text_content filter still has do_shortcode handler.' );
|
||||||
|
$this->assertNull( $this->post_during_shortcode );
|
||||||
|
|
||||||
|
// Visual Text Widget with both filters applied added, one from core and another via plugin.
|
||||||
|
add_filter( 'widget_text', 'do_shortcode' );
|
||||||
|
$this->shortcode_render_count = 0;
|
||||||
|
ob_start();
|
||||||
|
$widget->widget( $args, $instance );
|
||||||
|
$output = ob_get_clean();
|
||||||
|
$this->assertEquals( 1, $this->shortcode_render_count );
|
||||||
|
$this->assertContains( $this->example_shortcode_content, $output, 'Shortcode was applied without wpautop corrupting it.' );
|
||||||
|
$this->assertNotContains( '<p>' . $this->example_shortcode_content . '</p>', $output, 'Expected shortcode_unautop() to have run.' );
|
||||||
|
$this->assertEquals( 10, has_filter( 'widget_text', 'do_shortcode' ), 'Expected do_shortcode to be restored to widget_text.' );
|
||||||
|
$this->assertNull( $this->post_during_shortcode );
|
||||||
|
$this->assertNull( $this->post_during_shortcode );
|
||||||
|
remove_filter( 'widget_text', 'do_shortcode' );
|
||||||
|
|
||||||
|
// Visual Text Widget with shortcode handling disabled via plugin removing filter.
|
||||||
|
remove_filter( 'widget_text_content', 'do_shortcode', 11 );
|
||||||
|
remove_filter( 'widget_text', 'do_shortcode' );
|
||||||
|
$this->shortcode_render_count = 0;
|
||||||
|
ob_start();
|
||||||
|
$widget->widget( $args, $instance );
|
||||||
|
$output = ob_get_clean();
|
||||||
|
$this->assertEquals( 0, $this->shortcode_render_count );
|
||||||
|
$this->assertContains( '[example]', $output );
|
||||||
|
$this->assertNotContains( $this->example_shortcode_content, $output );
|
||||||
|
$this->assertFalse( has_filter( 'widget_text', 'do_shortcode' ) );
|
||||||
|
$this->assertFalse( has_filter( 'widget_text_content', 'do_shortcode' ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue