Make WordPress Core


Ignore:
Timestamp:
09/10/2017 06:32:34 AM (8 years ago)
Author:
westonruter
Message:

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.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/tests/phpunit/tests/widgets/text-widget.php

    r41260 r41361  
    223223     * @var string
    224224     */
    225     protected $example_shortcode_content = "<p>One\nTwo\n\nThree</p>\n<script>\ndocument.write('Test1');\n\ndocument.write('Test2');\n</script>";
     225    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>";
     226
     227    /**
     228     * The captured global post during shortcode rendering.
     229     *
     230     * @var WP_Post|null
     231     */
     232    protected $post_during_shortcode = null;
     233
     234    /**
     235     * Number of times the shortcode was rendered.
     236     *
     237     * @var int
     238     */
     239    protected $shortcode_render_count = 0;
    226240
    227241    /**
     
    231245     */
    232246    function do_example_shortcode() {
     247        $this->post_during_shortcode = get_post();
     248        $this->shortcode_render_count++;
    233249        return $this->example_shortcode_content;
    234250    }
    235251
    236252    /**
    237      * Test widget method when a plugin has added shortcode support.
     253     * Test widget method with shortcodes.
    238254     *
    239255     * @covers WP_Widget_Text::widget
    240256     */
    241257    function test_widget_shortcodes() {
     258        global $post;
     259        $post_id = $this->factory()->post->create();
     260        $post = get_post( $post_id );
     261
    242262        $args = array(
    243263            'before_title'  => '<h2>',
     
    247267        );
    248268        $widget = new WP_Widget_Text();
    249         add_filter( 'widget_text', 'do_shortcode' );
    250269        add_shortcode( 'example', array( $this, 'do_example_shortcode' ) );
    251270
    252271        $base_instance = array(
    253272            'title' => 'Example',
    254             'text' => "This is an example:\n\n[example]",
    255             'filter' => false,
    256         );
    257 
    258         // Legacy Text Widget.
    259         $instance = array_merge( $base_instance, array(
    260             'filter' => false,
    261         ) );
    262         ob_start();
    263         $widget->widget( $args, $instance );
    264         $output = ob_get_clean();
     273            'text' => "This is an example:\n\n[example]\n\nHello.",
     274            'filter' => false,
     275        );
     276
     277        // Legacy Text Widget without wpautop.
     278        $instance = array_merge( $base_instance, array(
     279            'filter' => false,
     280        ) );
     281        $this->shortcode_render_count = 0;
     282        ob_start();
     283        $widget->widget( $args, $instance );
     284        $output = ob_get_clean();
     285        $this->assertEquals( 1, $this->shortcode_render_count );
     286        $this->assertNotContains( '[example]', $output, 'Expected shortcode to be processed in legacy widget with plugin adding filter' );
    265287        $this->assertContains( $this->example_shortcode_content, $output, 'Shortcode was applied without wpautop corrupting it.' );
    266         $this->assertEquals( 10, has_filter( 'widget_text', 'do_shortcode' ), 'Filter was restored.' );
    267 
    268         // Visual Text Widget.
    269         $instance = array_merge( $base_instance, array(
    270             'filter' => 'content',
    271         ) );
    272         ob_start();
    273         $widget->widget( $args, $instance );
    274         $output = ob_get_clean();
     288        $this->assertNotContains( '<p>' . $this->example_shortcode_content . '</p>', $output, 'Expected shortcode_unautop() to have run.' );
     289        $this->assertNull( $this->post_during_shortcode );
     290
     291        // Legacy Text Widget with wpautop.
     292        $instance = array_merge( $base_instance, array(
     293            'filter' => true,
     294            'visual' => false,
     295        ) );
     296        $this->shortcode_render_count = 0;
     297        ob_start();
     298        $widget->widget( $args, $instance );
     299        $output = ob_get_clean();
     300        $this->assertEquals( 1, $this->shortcode_render_count );
     301        $this->assertNotContains( '[example]', $output, 'Expected shortcode to be processed in legacy widget with plugin adding filter' );
    275302        $this->assertContains( $this->example_shortcode_content, $output, 'Shortcode was applied without wpautop corrupting it.' );
    276         $this->assertEquals( 10, has_filter( 'widget_text', 'do_shortcode' ), 'Filter was restored.' );
    277         $this->assertFalse( has_filter( 'widget_text_content', 'do_shortcode' ), 'Filter was removed.' );
    278 
    279         // Visual Text Widget with properly-used widget_text_content filter.
     303        $this->assertNotContains( '<p>' . $this->example_shortcode_content . '</p>', $output, 'Expected shortcode_unautop() to have run.' );
     304        $this->assertNull( $this->post_during_shortcode );
     305
     306        // Legacy text widget with plugin adding shortcode support as well.
     307        add_filter( 'widget_text', 'do_shortcode' );
     308        $this->shortcode_render_count = 0;
     309        ob_start();
     310        $widget->widget( $args, $instance );
     311        $output = ob_get_clean();
     312        $this->assertEquals( 1, $this->shortcode_render_count );
     313        $this->assertNotContains( '[example]', $output, 'Expected shortcode to be processed in legacy widget with plugin adding filter' );
     314        $this->assertContains( wpautop( $this->example_shortcode_content ), $output, 'Shortcode was applied *with* wpautop() applying to shortcode output since plugin used legacy filter.' );
     315        $this->assertNull( $this->post_during_shortcode );
    280316        remove_filter( 'widget_text', 'do_shortcode' );
    281         add_filter( 'widget_text_content', 'do_shortcode', 11 );
    282         $instance = array_merge( $base_instance, array(
    283             'filter' => 'content',
    284         ) );
    285         ob_start();
    286         $widget->widget( $args, $instance );
    287         $output = ob_get_clean();
     317
     318        $instance = array_merge( $base_instance, array(
     319            'filter' => true,
     320            'visual' => true,
     321        ) );
     322
     323        // Visual Text Widget with only core-added widget_text_content filter for do_shortcode.
     324        $this->assertFalse( has_filter( 'widget_text', 'do_shortcode' ) );
     325        $this->assertEquals( 11, has_filter( 'widget_text_content', 'do_shortcode' ), 'Expected core to have set do_shortcode as widget_text_content filter.' );
     326        $this->shortcode_render_count = 0;
     327        ob_start();
     328        $widget->widget( $args, $instance );
     329        $output = ob_get_clean();
     330        $this->assertEquals( 1, $this->shortcode_render_count );
    288331        $this->assertContains( $this->example_shortcode_content, $output, 'Shortcode was applied without wpautop corrupting it.' );
    289         $this->assertFalse( has_filter( 'widget_text', 'do_shortcode' ), 'Filter was not erroneously restored.' );
     332        $this->assertNotContains( '<p>' . $this->example_shortcode_content . '</p>', $output, 'Expected shortcode_unautop() to have run.' );
     333        $this->assertFalse( has_filter( 'widget_text', 'do_shortcode' ), 'The widget_text filter still lacks do_shortcode handler.' );
     334        $this->assertEquals( 11, has_filter( 'widget_text_content', 'do_shortcode' ), 'The widget_text_content filter still has do_shortcode handler.' );
     335        $this->assertNull( $this->post_during_shortcode );
     336
     337        // Visual Text Widget with both filters applied added, one from core and another via plugin.
     338        add_filter( 'widget_text', 'do_shortcode' );
     339        $this->shortcode_render_count = 0;
     340        ob_start();
     341        $widget->widget( $args, $instance );
     342        $output = ob_get_clean();
     343        $this->assertEquals( 1, $this->shortcode_render_count );
     344        $this->assertContains( $this->example_shortcode_content, $output, 'Shortcode was applied without wpautop corrupting it.' );
     345        $this->assertNotContains( '<p>' . $this->example_shortcode_content . '</p>', $output, 'Expected shortcode_unautop() to have run.' );
     346        $this->assertEquals( 10, has_filter( 'widget_text', 'do_shortcode' ), 'Expected do_shortcode to be restored to widget_text.' );
     347        $this->assertNull( $this->post_during_shortcode );
     348        $this->assertNull( $this->post_during_shortcode );
     349        remove_filter( 'widget_text', 'do_shortcode' );
     350
     351        // Visual Text Widget with shortcode handling disabled via plugin removing filter.
     352        remove_filter( 'widget_text_content', 'do_shortcode', 11 );
     353        remove_filter( 'widget_text', 'do_shortcode' );
     354        $this->shortcode_render_count = 0;
     355        ob_start();
     356        $widget->widget( $args, $instance );
     357        $output = ob_get_clean();
     358        $this->assertEquals( 0, $this->shortcode_render_count );
     359        $this->assertContains( '[example]', $output );
     360        $this->assertNotContains( $this->example_shortcode_content, $output );
     361        $this->assertFalse( has_filter( 'widget_text', 'do_shortcode' ) );
     362        $this->assertFalse( has_filter( 'widget_text_content', 'do_shortcode' ) );
    290363    }
    291364
Note: See TracChangeset for help on using the changeset viewer.