Make WordPress Core

Ticket #40854: 40854-widget-shortcodes.2.diff

File 40854-widget-shortcodes.2.diff, 11.1 KB (added by westonruter, 7 years ago)
  • src/wp-includes/default-filters.php

    diff --git src/wp-includes/default-filters.php src/wp-includes/default-filters.php
    index a196f4ff19..1c7ae718f1 100644
    add_filter( 'widget_text_content', 'capital_P_dangit', 11 ); 
    169169add_filter( 'widget_text_content', 'wptexturize'          );
    170170add_filter( 'widget_text_content', 'convert_smilies',  20 );
    171171add_filter( 'widget_text_content', 'wpautop'              );
     172add_filter( 'widget_text_content', 'do_shortcode',     11 ); // Runs after wpautop() but note that $post global will be null when shortcodes run.
    172173
    173174add_filter( 'date_i18n', 'wp_maybe_decline_date' );
    174175
  • src/wp-includes/widgets/class-wp-widget-text.php

    diff --git src/wp-includes/widgets/class-wp-widget-text.php src/wp-includes/widgets/class-wp-widget-text.php
    index a79e0daa6c..f45f9aba90 100644
    class WP_Widget_Text extends WP_Widget { 
    183183         *
    184184         * @since 2.8.0
    185185         *
     186         * @global WP_Post $post
     187         *
    186188         * @param array $args     Display arguments including 'before_title', 'after_title',
    187189         *                        'before_widget', and 'after_widget'.
    188190         * @param array $instance Settings for the current Text widget instance.
    189191         */
    190192        public function widget( $args, $instance ) {
     193                global $post;
    191194
    192195                /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */
    193196                $title = apply_filters( 'widget_title', empty( $instance['title'] ) ? '' : $instance['title'], $instance, $this->id_base );
    class WP_Widget_Text extends WP_Widget { 
    205208                }
    206209
    207210                /*
    208                  * Just-in-time temporarily upgrade Visual Text widget shortcode handling
    209                  * (with support added by plugin) from the widget_text filter to
    210                  * widget_text_content:11 to prevent wpautop from corrupting HTML output
    211                  * added by the shortcode.
     211                 * Suspend legacy plugin-supplied do_shortcode() for 'widget_text' filter for the visual Text widget to prevent
     212                 * shortcodes being processed twice. Now do_shortcode() is added to the 'widget_text_content' filter in core itself
     213                 * and it applies after wpautop() to prevent corrupting HTML output added by the shortcode. When do_shortcode() is
     214                 * added to 'widget_text_content' then do_shortcode() will be manually called when in legacy mode as well.
    212215                 */
    213216                $widget_text_do_shortcode_priority = has_filter( 'widget_text', 'do_shortcode' );
    214                 $should_upgrade_shortcode_handling = ( $is_visual_text_widget && false !== $widget_text_do_shortcode_priority );
    215                 if ( $should_upgrade_shortcode_handling ) {
     217                $should_suspend_legacy_shortcode_support = ( $is_visual_text_widget && false !== $widget_text_do_shortcode_priority );
     218                if ( $should_suspend_legacy_shortcode_support ) {
    216219                        remove_filter( 'widget_text', 'do_shortcode', $widget_text_do_shortcode_priority );
    217                         add_filter( 'widget_text_content', 'do_shortcode', 11 );
     220                }
     221
     222                // Nullify the $post global during widget rendering to prevent shortcodes from running with the unexpected context.
     223                $suspended_post = null;
     224                if ( isset( $post ) ) {
     225                        $suspended_post = $post;
     226                        $post = null;
    218227                }
    219228
    220229                /**
    class WP_Widget_Text extends WP_Widget { 
    244253                         * @param WP_Widget_Text $this     Current Text widget instance.
    245254                         */
    246255                        $text = apply_filters( 'widget_text_content', $text, $instance, $this );
     256                } else {
     257                        if ( ! empty( $instance['filter'] ) ) {
     258                                $text = wpautop( $text ); // Back-compat for instances prior to 4.8.
     259                        }
     260
     261                        /*
     262                         * Manually do shortcodes on the content when the core-added filter is present. It is added by default
     263                         * in core by adding do_shortcode() to the 'widget_text_content' filter to apply after wpautop().
     264                         * Since the legacy Text widget runs wpautop() after 'widget_text' filters are applied, the widget in
     265                         * legacy mode here manually applies do_shortcode() on the content unless the default
     266                         * core filter for 'widget_text_content' has been removed, or if do_shortcode() has already
     267                         * been applied via a plugin adding do_shortcode() to 'widget_text' filters.
     268                         */
     269                        if ( has_filter( 'widget_text_content', 'do_shortcode' ) && ! $widget_text_do_shortcode_priority ) {
     270                                $text = do_shortcode( $text );
     271                        }
     272                }
    247273
    248                 } elseif ( ! empty( $instance['filter'] ) ) {
    249                         $text = wpautop( $text ); // Back-compat for instances prior to 4.8.
     274                // Restore post global.
     275                if ( isset( $suspended_post ) ) {
     276                        $post = $suspended_post;
    250277                }
    251278
    252                 // Undo temporary upgrade of the plugin-supplied shortcode handling.
    253                 if ( $should_upgrade_shortcode_handling ) {
    254                         remove_filter( 'widget_text_content', 'do_shortcode', 11 );
     279                // Undo suspension of legacy plugin-supplied shortcode handling.
     280                if ( $should_suspend_legacy_shortcode_support ) {
    255281                        add_filter( 'widget_text', 'do_shortcode', $widget_text_do_shortcode_priority );
    256282                }
    257283
  • tests/phpunit/tests/widgets/text-widget.php

    diff --git tests/phpunit/tests/widgets/text-widget.php tests/phpunit/tests/widgets/text-widget.php
    index f7e810fd04..eb793d95b7 100644
    class Test_WP_Widget_Text extends WP_UnitTestCase { 
    222222         *
    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>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        /**
    228242         * Do example shortcode.
    class Test_WP_Widget_Text extends WP_UnitTestCase { 
    230244         * @return string Shortcode content.
    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>',
    244264                        'after_title'   => "</h2>\n",
    class Test_WP_Widget_Text extends WP_UnitTestCase { 
    246266                        'after_widget'  => "</section>\n",
    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(
    class Test_WP_Widget_Text extends WP_UnitTestCase { 
    259278                $instance = array_merge( $base_instance, array(
    260279                        'filter' => false,
    261280                ) );
     281                $this->shortcode_render_count = 0;
    262282                ob_start();
    263283                $widget->widget( $args, $instance );
    264284                $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.' );
     288                $this->assertNull( $this->post_during_shortcode );
    267289
    268                 // Visual Text Widget.
    269                 $instance = array_merge( $base_instance, array(
    270                         'filter' => 'content',
    271                 ) );
     290                // Legacy text widget with plugin adding shortcode support as well.
     291                add_filter( 'widget_text', 'do_shortcode' );
     292                $this->shortcode_render_count = 0;
    272293                ob_start();
    273294                $widget->widget( $args, $instance );
    274295                $output = ob_get_clean();
     296                $this->assertEquals( 1, $this->shortcode_render_count );
     297                $this->assertNotContains( '[example]', $output, 'Expected shortcode to be processed in legacy widget with plugin adding filter' );
    275298                $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.
     299                $this->assertNull( $this->post_during_shortcode );
    280300                remove_filter( 'widget_text', 'do_shortcode' );
    281                 add_filter( 'widget_text_content', 'do_shortcode', 11 );
     301
    282302                $instance = array_merge( $base_instance, array(
    283                         'filter' => 'content',
     303                        'filter' => true,
     304                        'visual' => true,
    284305                ) );
     306
     307                // Visual Text Widget with only core-added widget_text_content filter for do_shortcode.
     308                $this->assertFalse( has_filter( 'widget_text', 'do_shortcode' ) );
     309                $this->assertEquals( 11, has_filter( 'widget_text_content', 'do_shortcode' ), 'Expected core to have set do_shortcode as widget_text_content filter.' );
     310                $this->shortcode_render_count = 0;
     311                ob_start();
     312                $widget->widget( $args, $instance );
     313                $output = ob_get_clean();
     314                $this->assertEquals( 1, $this->shortcode_render_count );
     315                $this->assertContains( $this->example_shortcode_content, $output, 'Shortcode was applied without wpautop corrupting it.' );
     316                $this->assertFalse( has_filter( 'widget_text', 'do_shortcode' ), 'The widget_text filter still lacks do_shortcode handler.' );
     317                $this->assertEquals( 11, has_filter( 'widget_text_content', 'do_shortcode' ), 'The widget_text_content filter still has do_shortcode handler.' );
     318                $this->assertNull( $this->post_during_shortcode );
     319
     320                // Visual Text Widget with both filters applied added, one from core and another via plugin.
     321                add_filter( 'widget_text', 'do_shortcode' );
     322                $this->shortcode_render_count = 0;
    285323                ob_start();
    286324                $widget->widget( $args, $instance );
    287325                $output = ob_get_clean();
     326                $this->assertEquals( 1, $this->shortcode_render_count );
    288327                $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.' );
     328                $this->assertEquals( 10, has_filter( 'widget_text', 'do_shortcode' ), 'Expected do_shortcode to be restored to widget_text.' );
     329                $this->assertNull( $this->post_during_shortcode );
     330                $this->assertNull( $this->post_during_shortcode );
     331                remove_filter( 'widget_text', 'do_shortcode' );
     332
     333                // Visual Text Widget with shortcode handling disabled via plugin removing filter.
     334                remove_filter( 'widget_text_content', 'do_shortcode', 11 );
     335                remove_filter( 'widget_text', 'do_shortcode' );
     336                $this->shortcode_render_count = 0;
     337                ob_start();
     338                $widget->widget( $args, $instance );
     339                $output = ob_get_clean();
     340                $this->assertEquals( 0, $this->shortcode_render_count );
     341                $this->assertContains( '[example]', $output );
     342                $this->assertNotContains( $this->example_shortcode_content, $output );
     343                $this->assertFalse( has_filter( 'widget_text', 'do_shortcode' ) );
     344                $this->assertFalse( has_filter( 'widget_text_content', 'do_shortcode' ) );
    290345        }
    291346
    292347        /**