Make WordPress Core

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

File 40854-widget-shortcodes.3.diff, 12.6 KB (added by westonruter, 7 years ago)

Account for shortcode_unautop()

  • src/wp-includes/default-filters.php

    diff --git src/wp-includes/default-filters.php src/wp-includes/default-filters.php
    index a196f4ff19..9fb06cb083 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', 'shortcode_unautop'    );
     173add_filter( 'widget_text_content', 'do_shortcode',     11 ); // Runs after wpautop() but note that $post global will be null when shortcodes run.
    172174
    173175add_filter( 'date_i18n', 'wp_maybe_decline_date' );
    174176
  • 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..13a737ab6e 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
     271                                if ( ! empty( $instance['filter'] ) ) {
     272                                        $text = shortcode_unautop( $text );
     273                                }
     274
     275                                $text = do_shortcode( $text );
     276                        }
     277                }
    247278
    248                 } elseif ( ! empty( $instance['filter'] ) ) {
    249                         $text = wpautop( $text ); // Back-compat for instances prior to 4.8.
     279                // Restore post global.
     280                if ( isset( $suspended_post ) ) {
     281                        $post = $suspended_post;
    250282                }
    251283
    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 );
     284                // Undo suspension of legacy plugin-supplied shortcode handling.
     285                if ( $should_suspend_legacy_shortcode_support ) {
    255286                        add_filter( 'widget_text', 'do_shortcode', $widget_text_do_shortcode_priority );
    256287                }
    257288
  • 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..a3e348f8a6 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 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        /**
    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(
    253272                        'title' => 'Example',
    254                         'text' => "This is an example:\n\n[example]",
     273                        'text' => "This is an example:\n\n[example]\n\nHello.",
    255274                        'filter' => false,
    256275                );
    257276
    258                 // Legacy Text Widget.
     277                // Legacy Text Widget without wpautop.
    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->assertNotContains( '<p>' . $this->example_shortcode_content . '</p>', $output, 'Expected shortcode_unautop() to have run.' );
     289                $this->assertNull( $this->post_during_shortcode );
    267290
    268                 // Visual Text Widget.
     291                // Legacy Text Widget with wpautop.
    269292                $instance = array_merge( $base_instance, array(
    270                         'filter' => 'content',
     293                        'filter' => true,
     294                        'visual' => false,
    271295                ) );
     296                $this->shortcode_render_count = 0;
    272297                ob_start();
    273298                $widget->widget( $args, $instance );
    274299                $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.' );
     303                $this->assertNotContains( '<p>' . $this->example_shortcode_content . '</p>', $output, 'Expected shortcode_unautop() to have run.' );
     304                $this->assertNull( $this->post_during_shortcode );
    278305
    279                 // Visual Text Widget with properly-used widget_text_content filter.
     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 );
     317
    282318                $instance = array_merge( $base_instance, array(
    283                         'filter' => 'content',
     319                        'filter' => true,
     320                        'visual' => true,
    284321                ) );
     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;
    285327                ob_start();
    286328                $widget->widget( $args, $instance );
    287329                $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
    292365        /**