Make WordPress Core

Ticket #40951: 40951.4.diff

File 40951.4.diff, 21.8 KB (added by westonruter, 8 years ago)

https://github.com/xwp/wordpress-develop/pull/235/files/d8cfffb..beb1c76

  • src/wp-admin/js/widgets/text-widgets.js

    diff --git src/wp-admin/js/widgets/text-widgets.js src/wp-admin/js/widgets/text-widgets.js
    index 7932606e3d..5af65f134c 100644
     
    33wp.textWidgets = ( function( $ ) {
    44        'use strict';
    55
    6         var component = {};
     6        var component = {
     7                data: {
     8                        custom_html_pointer_dismissed: false,
     9                        l10n: {
     10                                pointer_heading: '',
     11                                pointer_text: ''
     12                        }
     13                }
     14        };
    715
    816        /**
    917         * Text widget control.
    wp.textWidgets = ( function( $ ) { 
    152160                                        if ( restoreTextMode ) {
    153161                                                switchEditors.go( id, 'toggle' );
    154162                                        }
     163
     164                                        $( '#' + id + '-html' ).one( 'click', function() {
     165                                                var tabContainer;
     166                                                if ( component.data.custom_html_pointer_dismissed ) {
     167                                                        return;
     168                                                }
     169                                                tabContainer = $( this ).parent();
     170                                                tabContainer.pointer({
     171                                                        position: 'bottom',
     172                                                        align: 'right',
     173                                                        edge: 'right',
     174                                                        content: '<h3>' + component.data.l10n.pointer_heading + '</h3><p>' + component.data.l10n.pointer_text + '</p>',
     175                                                        close: function() {
     176                                                                wp.ajax.post( 'dismiss-wp-pointer', {
     177                                                                        pointer: 'text_widget_custom_html'
     178                                                                });
     179                                                                component.data.custom_html_pointer_dismissed = true;
     180                                                        }
     181                                                });
     182                                                tabContainer.pointer( 'open' );
     183                                                tabContainer.pointer( 'widget' ).css( 'z-index', 999999 ); // Default z-index of 9999 is not enough in Customizer.
     184                                        });
    155185                                };
    156186
    157187                                if ( editor.initialized ) {
    wp.textWidgets = ( function( $ ) { 
    233263                        return;
    234264                }
    235265
     266                // Bypass using TinyMCE when widget is in legacy mode.
     267                if ( widgetForm.find( '.legacy' ).length > 0 ) {
     268                        return;
     269                }
     270
    236271                /*
    237272                 * Create a container element for the widget control fields.
    238273                 * This is inserted into the DOM immediately before the the .widget-content
    wp.textWidgets = ( function( $ ) { 
    337372         * When WordPress enqueues this script, it should have an inline script
    338373         * attached which calls wp.textWidgets.init().
    339374         *
     375         * @param {object} exports      Server exports.
     376         * @param {object} exports.l10n Translations.
    340377         * @returns {void}
    341378         */
    342         component.init = function init() {
     379        component.init = function init( exports ) {
    343380                var $document = $( document );
     381                component.data = exports;
    344382                $document.on( 'widget-added', component.handleWidgetAdded );
    345383                $document.on( 'widget-synced widget-updated', component.handleWidgetUpdated );
    346384
  • src/wp-includes/script-loader.php

    diff --git src/wp-includes/script-loader.php src/wp-includes/script-loader.php
    index 7562e2839b..05b0fe0c84 100644
    function wp_default_scripts( &$scripts ) { 
    608608                $scripts->add( 'media-audio-widget', "/wp-admin/js/widgets/media-audio-widget$suffix.js", array( 'media-widgets', 'media-audiovideo' ) );
    609609                $scripts->add( 'media-image-widget', "/wp-admin/js/widgets/media-image-widget$suffix.js", array( 'media-widgets' ) );
    610610                $scripts->add( 'media-video-widget', "/wp-admin/js/widgets/media-video-widget$suffix.js", array( 'media-widgets', 'media-audiovideo' ) );
    611                 $scripts->add( 'text-widgets', "/wp-admin/js/widgets/text-widgets$suffix.js", array( 'jquery', 'backbone', 'editor', 'wp-util' ) );
    612                 $scripts->add_inline_script( 'text-widgets', 'wp.textWidgets.init();', 'after' );
     611                $scripts->add( 'text-widgets', "/wp-admin/js/widgets/text-widgets$suffix.js", array( 'jquery', 'backbone', 'editor', 'wp-util', 'wp-pointer' ) );
     612                $exports = array(
     613                        'custom_html_pointer_dismissed' => is_user_logged_in() && in_array( 'text_widget_custom_html', explode( ',', (string) get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true ) ) ),
     614                        'l10n' => array(
     615                                'pointer_heading' => __( 'New Custom HTML Widget' ),
     616                                'pointer_text' => __( 'Hey, did you hear we have a "Custom HTML" widget now? Check it out to add some custom code to your site!' ),
     617                        ),
     618                );
     619                $scripts->add_inline_script( 'text-widgets', sprintf( 'wp.textWidgets.init( %s );', wp_json_encode( $exports ) ), 'after' );
    613620
    614621                $scripts->add( 'theme', "/wp-admin/js/theme$suffix.js", array( 'wp-backbone', 'wp-a11y' ), false, 1 );
    615622
  • 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 7d149c0a0f..61135aa5de 100644
    class WP_Widget_Text extends WP_Widget { 
    5353        }
    5454
    5555        /**
     56         * Determines whether a given instance is legacy and should bypass using TinyMCE.
     57         *
     58         * @since 4.8.1
     59         *
     60         * @param array $instance {
     61         *     Instance data.
     62         *
     63         *     @type string      $text   Content.
     64         *     @type bool|string $filter Whether autop or content filters should apply.
     65         *     @type bool        $legacy Whether widget is in legacy mode.
     66         * }
     67         * @return bool Whether Text widget instance contains legacy data.
     68         */
     69        public function is_legacy_instance( $instance ) {
     70
     71                // If the widget has been updated while in legacy mode, it stays in legacy mode.
     72                if ( ! empty( $instance['legacy'] ) ) {
     73                        return true;
     74                }
     75
     76                // If the widget has been added/updated in 4.8 then filter prop is 'content' and it is no longer legacy.
     77                if ( isset( $instance['filter'] ) && 'content' === $instance['filter'] ) {
     78                        return false;
     79                }
     80
     81                // If the text is empty, then nothing is preventing migration to TinyMCE.
     82                if ( empty( $instance['text'] ) ) {
     83                        return false;
     84                }
     85
     86                $wpautop = ! empty( $instance['filter'] );
     87                $has_line_breaks = ( false !== strpos( $instance['text'], "\n" ) );
     88
     89                // If auto-paragraphs are not enabled and there are line breaks, then ensure legacy mode.
     90                if ( ! $wpautop && $has_line_breaks ) {
     91                        return true;
     92                }
     93
     94                /*
     95                 * If a shortcode is present (with support added by a plugin), assume legacy mode
     96                 * since shortcodes would apply at the widget_text filter and thus be applied
     97                 * before wpautop runs at the widget_text_content filter.
     98                 */
     99                if ( preg_match( '/' . get_shortcode_regex() . '/', $instance['text'] ) ) {
     100                        return true;
     101                }
     102
     103                // In the rare case that DOMDocument is not available we cannot reliably sniff content and so we assume legacy.
     104                if ( ! class_exists( 'DOMDocument' ) ) {
     105                        // @codeCoverageIgnoreStart
     106                        return true;
     107                        // @codeCoverageIgnoreEnd
     108                }
     109
     110                $doc = new DOMDocument();
     111                $doc->loadHTML( sprintf(
     112                        '<html><head><meta charset="%s"></head><body>%s</body></html>',
     113                        esc_attr( get_bloginfo( 'charset' ) ),
     114                        $instance['text']
     115                ) );
     116                $body = $doc->getElementsByTagName( 'body' )->item( 0 );
     117
     118                // See $allowedposttags.
     119                $safe_elements_attributes = array(
     120                        'strong' => array(),
     121                        'em' => array(),
     122                        'b' => array(),
     123                        'i' => array(),
     124                        'u' => array(),
     125                        's' => array(),
     126                        'ul' => array(),
     127                        'ol' => array(),
     128                        'li' => array(),
     129                        'hr' => array(),
     130                        'abbr' => array(),
     131                        'acronym' => array(),
     132                        'code' => array(),
     133                        'dfn' => array(),
     134                        'a' => array(
     135                                'href' => true,
     136                        ),
     137                        'img' => array(
     138                                'src' => true,
     139                                'alt' => true,
     140                        ),
     141                );
     142                $safe_empty_elements = array( 'img', 'hr', 'iframe' );
     143
     144                foreach ( $body->getElementsByTagName( '*' ) as $element ) {
     145                        /** @var DOMElement $element */
     146                        $tag_name = strtolower( $element->nodeName );
     147
     148                        // If the element is not safe, then the instance is legacy.
     149                        if ( ! isset( $safe_elements_attributes[ $tag_name ] ) ) {
     150                                return true;
     151                        }
     152
     153                        // If the element is not safely empty and it has empty contents, then legacy mode.
     154                        if ( ! in_array( $tag_name, $safe_empty_elements, true ) && '' === trim( $element->textContent ) ) {
     155                                return true;
     156                        }
     157
     158                        // If an attribute is not recognized as safe, then the instance is legacy.
     159                        foreach ( $element->attributes as $attribute ) {
     160                                /** @var DOMAttr $attribute */
     161                                $attribute_name = strtolower( $attribute->nodeName );
     162
     163                                if ( ! isset( $safe_elements_attributes[ $tag_name ][ $attribute_name ] ) ) {
     164                                        return true;
     165                                }
     166                        }
     167                }
     168
     169                // Otherwise, the text contains no elements/attributes that TinyMCE could drop, and therefore the widget does not need legacy mode.
     170                return false;
     171        }
     172
     173        /**
    56174         * Outputs the content for the current Text widget instance.
    57175         *
    58176         * @since 2.8.0
    class WP_Widget_Text extends WP_Widget { 
    134252                }
    135253
    136254                /*
    137                  * Re-use legacy 'filter' (wpautop) property to now indicate content filters will always apply.
     255                 * If the Text widget is in legacy mode, then a hidden input will indicate this
     256                 * and the new content value for the filter prop will by bypassed. Otherwise,
     257                 * re-use legacy 'filter' (wpautop) property to now indicate content filters will always apply.
    138258                 * Prior to 4.8, this is a boolean value used to indicate whether or not wpautop should be
    139259                 * applied. By re-using this property, downgrading WordPress from 4.8 to 4.7 will ensure
    140260                 * that the content for Text widgets created with TinyMCE will continue to get wpautop.
    141261                 */
    142                 $instance['filter'] = 'content';
     262                if ( isset( $new_instance['legacy'] ) || isset( $old_instance['legacy'] ) || ( isset( $new_instance['filter'] ) && 'content' !== $new_instance['filter'] ) ) {
     263                        $instance['filter'] = ! empty( $new_instance['filter'] );
     264                        $instance['legacy'] = true;
     265                } else {
     266                        $instance['filter'] = 'content';
     267                        unset( $instance['legacy'] );
     268                }
    143269
    144270                return $instance;
    145271        }
    class WP_Widget_Text extends WP_Widget { 
    153279        public function enqueue_admin_scripts() {
    154280                wp_enqueue_editor();
    155281                wp_enqueue_script( 'text-widgets' );
     282                wp_enqueue_style( 'wp-pointer' );
    156283        }
    157284
    158285        /**
    class WP_Widget_Text extends WP_Widget { 
    160287         *
    161288         * @since 2.8.0
    162289         * @since 4.8.0 Form only contains hidden inputs which are synced with JS template.
     290         * @since 4.8.1 Restored original form to be displayed when in legacy mode.
    163291         * @access public
    164292         * @see WP_Widget_Visual_Text::render_control_template_scripts()
    165293         *
    class WP_Widget_Text extends WP_Widget { 
    175303                        )
    176304                );
    177305                ?>
    178                 <input id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" class="title" type="hidden" value="<?php echo esc_attr( $instance['title'] ); ?>">
    179                 <input id="<?php echo $this->get_field_id( 'text' ); ?>" name="<?php echo $this->get_field_name( 'text' ); ?>" class="text" type="hidden" value="<?php echo esc_attr( $instance['text'] ); ?>">
    180                 <?php
     306                <?php if ( ! $this->is_legacy_instance( $instance ) ) : ?>
     307                        <input id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" class="title" type="hidden" value="<?php echo esc_attr( $instance['title'] ); ?>">
     308                        <input id="<?php echo $this->get_field_id( 'text' ); ?>" name="<?php echo $this->get_field_name( 'text' ); ?>" class="text" type="hidden" value="<?php echo esc_attr( $instance['text'] ); ?>">
     309                <?php else : ?>
     310                        <input name="<?php echo $this->get_field_name( 'legacy' ); ?>" type="hidden" class="legacy" value="true">
     311                        <p>
     312                                <label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php _e( 'Title:' ); ?></label>
     313                                <input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" type="text" value="<?php echo esc_attr( $instance['title'] ); ?>"/>
     314                        </p>
     315                        <div class="notice inline notice-info notice-alt">
     316                                <p><?php _e( 'This widget contains code that may work better in the new &#8220;Custom HTML&#8221; widget. How about trying that widget instead?' ); ?></p>
     317                        </div>
     318                        <p>
     319                                <label for="<?php echo $this->get_field_id( 'text' ); ?>"><?php _e( 'Content:' ); ?></label>
     320                                <textarea class="widefat" rows="16" cols="20" id="<?php echo $this->get_field_id( 'text' ); ?>" name="<?php echo $this->get_field_name( 'text' ); ?>"><?php echo esc_textarea( $instance['text'] ); ?></textarea>
     321                        </p>
     322                        <p>
     323                                <input id="<?php echo $this->get_field_id( 'filter' ); ?>" name="<?php echo $this->get_field_name( 'filter' ); ?>" type="checkbox"<?php checked( ! empty( $instance['filter'] ) ); ?> />&nbsp;<label for="<?php echo $this->get_field_id( 'filter' ); ?>"><?php _e( 'Automatically add paragraphs' ); ?></label>
     324                        </p>
     325                <?php endif;
    181326        }
    182327
    183328        /**
  • tests/phpunit/tests/widgets/text-widget.php

    diff --git tests/phpunit/tests/widgets/text-widget.php tests/phpunit/tests/widgets/text-widget.php
    index a28e6a668a..38625302f8 100644
    class Test_WP_Widget_Text extends WP_UnitTestCase { 
    4040        }
    4141
    4242        /**
     43         * Test constructor method.
     44         *
     45         * @covers WP_Widget_Text::__construct
     46         */
     47        function test_construct() {
     48                $widget = new WP_Widget_Text();
     49                $this->assertEquals( 'text', $widget->id_base );
     50                $this->assertEquals( 'widget_text', $widget->widget_options['classname'] );
     51                $this->assertTrue( $widget->widget_options['customize_selective_refresh'] );
     52                $this->assertEquals( 400, $widget->control_options['width'] );
     53                $this->assertEquals( 350, $widget->control_options['height'] );
     54        }
     55
     56        /**
    4357         * Test enqueue_admin_scripts method.
    4458         *
    4559         * @covers WP_Widget_Text::_register
    class Test_WP_Widget_Text extends WP_UnitTestCase { 
    152166        }
    153167
    154168        /**
     169         * Test is_legacy_instance method.
     170         *
     171         * @covers WP_Widget_Text::is_legacy_instance
     172         */
     173        function test_is_legacy_instance() {
     174                $widget = new WP_Widget_Text();
     175                $base_instance = array(
     176                        'title' => 'Title',
     177                        'text' => "Hello\n\nWorld",
     178                );
     179
     180                $instance = array_merge( $base_instance, array(
     181                        'legacy' => true,
     182                ) );
     183                $this->assertTrue( $widget->is_legacy_instance( $instance ), 'Legacy when legacy prop is present.' );
     184
     185                $instance = array_merge( $base_instance, array(
     186                        'filter' => 'content',
     187                ) );
     188                $this->assertFalse( $widget->is_legacy_instance( $instance ), 'Not legacy when filter is explicitly content.' );
     189
     190                $instance = array_merge( $base_instance, array(
     191                        'text' => '',
     192                        'filter' => true,
     193                ) );
     194                $this->assertFalse( $widget->is_legacy_instance( $instance ), 'Not legacy when text is empty.' );
     195
     196                $instance = array_merge( $base_instance, array(
     197                        'text' => "One\nTwo",
     198                        'filter' => false,
     199                ) );
     200                $this->assertTrue( $widget->is_legacy_instance( $instance ), 'Legacy when not-wpautop and there are line breaks.' );
     201
     202                $instance = array_merge( $base_instance, array(
     203                        'text' => "One\n\nTwo",
     204                        'filter' => false,
     205                ) );
     206                $this->assertTrue( $widget->is_legacy_instance( $instance ), 'Legacy when not-wpautop and there are paragraph breaks.' );
     207
     208                $instance = array_merge( $base_instance, array(
     209                        'text' => "One\nTwo",
     210                        'filter' => true,
     211                ) );
     212                $this->assertFalse( $widget->is_legacy_instance( $instance ), 'Not automatically legacy when wpautop and there are line breaks.' );
     213
     214                $instance = array_merge( $base_instance, array(
     215                        'text' => "One\n\nTwo",
     216                        'filter' => true,
     217                ) );
     218                $this->assertFalse( $widget->is_legacy_instance( $instance ), 'Not automatically legacy when wpautop and there are paragraph breaks.' );
     219
     220                $instance = array_merge( $base_instance, array(
     221                        'text' => 'Here is a [gallery]',
     222                        'filter' => true,
     223                ) );
     224                $this->assertTrue( $widget->is_legacy_instance( $instance ), 'Legacy mode when a shortcode is present.' );
     225
     226                // Check text examples that will not migrate to TinyMCE.
     227                $legacy_text_examples = array(
     228                        '<span class="hello"></span>',
     229                        '<span></span>',
     230                        "<ul>\n<li><a href=\"#\" class=\"location\"></a>List Item 1</li>\n<li><a href=\"#\" class=\"location\"></a>List Item 2</li>\n</ul>",
     231                        '<a href="#" class="map"></a>',
     232                        "<script>\n\\Line one\n\n\\Line two</script>",
     233                        "<style>body {\ncolor:red;\n}</style>",
     234                        '<span class="fa fa-cc-discover fa-2x" aria-hidden="true"></span>',
     235                        "<p>\nStay updated with our latest news and specials. We never sell your information and you can unsubscribe at any time.\n</p>\n\n<div class=\"custom-form-class\">\n\t<form action=\"#\" method=\"post\" name=\"mc-embedded-subscribe-form\">\n\n\t\t<label class=\"screen-reader-text\" for=\"mce-EMAIL-b\">Email </label>\n\t\t<input id=\"mce-EMAIL-b\" class=\"required email\" name=\"EMAIL\" required=\"\" type=\"email\" value=\"\" placeholder=\"Email Address*\" />\n\n\t\t<input class=\"button\" name=\"subscribe\" type=\"submit\" value=\"Go!\" />\n\n\t</form>\n</div>",
     236                        '<span class="sectiondown"><a href="#front-page-3"><i class="fa fa-chevron-circle-down"></i></a></span>',
     237                );
     238                foreach ( $legacy_text_examples as $legacy_text_example ) {
     239                        $instance = array_merge( $base_instance, array(
     240                                'text' => $legacy_text_example,
     241                                'filter' => true,
     242                        ) );
     243                        $this->assertTrue( $widget->is_legacy_instance( $instance ), 'Legacy when wpautop and there is HTML that is not liable to be mutated.' );
     244
     245                        $instance = array_merge( $base_instance, array(
     246                                'text' => $legacy_text_example,
     247                                'filter' => false,
     248                        ) );
     249                        $this->assertTrue( $widget->is_legacy_instance( $instance ), 'Legacy when not-wpautop and there is HTML that is not liable to be mutated.' );
     250                }
     251
     252                // Check text examples that will migrate to TinyMCE, where elements and attributes are not in whitelist.
     253                $migratable_text_examples = array(
     254                        'Check out <a href="http://example.com">Example</a>',
     255                        '<img src="http://example.com/img.jpg" alt="Img">',
     256                        '<strong><em>Hello</em></strong>',
     257                        '<b><i><u><s>Hello</s></u></i></b>',
     258                        "<ul>\n<li>One</li>\n<li>One</li>\n<li>One</li>\n</ul>",
     259                        "<ol>\n<li>One</li>\n<li>One</li>\n<li>One</li>\n</ol>",
     260                        "Text\n<hr>\nAddendum",
     261                        "Look at this code:\n\n<code>echo 'Hello World!';</code>",
     262                );
     263                foreach ( $migratable_text_examples as $migratable_text_example ) {
     264                        $instance = array_merge( $base_instance, array(
     265                                'text' => $migratable_text_example,
     266                                'filter' => true,
     267                        ) );
     268                        $this->assertFalse( $widget->is_legacy_instance( $instance ), 'Legacy when wpautop and there is HTML that is not liable to be mutated.' );
     269                }
     270        }
     271
     272        /**
     273         * Test update method.
     274         *
     275         * @covers WP_Widget_Text::form
     276         */
     277        function test_form() {
     278                $widget = new WP_Widget_Text();
     279                $instance = array(
     280                        'title' => 'Title',
     281                        'text' => 'Text',
     282                        'filter' => false,
     283                        'legacy' => true,
     284                );
     285                $this->assertTrue( $widget->is_legacy_instance( $instance ) );
     286                ob_start();
     287                $widget->form( $instance );
     288                $form = ob_get_clean();
     289                $this->assertContains( 'class="legacy"', $form );
     290
     291                $instance = array(
     292                        'title' => 'Title',
     293                        'text' => 'Text',
     294                        'filter' => 'content',
     295                );
     296                $this->assertFalse( $widget->is_legacy_instance( $instance ) );
     297                ob_start();
     298                $widget->form( $instance );
     299                $form = ob_get_clean();
     300                $this->assertNotContains( 'class="legacy"', $form );
     301        }
     302
     303        /**
    155304         * Test update method.
    156305         *
    157306         * @covers WP_Widget_Text::update
    class Test_WP_Widget_Text extends WP_UnitTestCase { 
    161310                $instance = array(
    162311                        'title' => "The\nTitle",
    163312                        'text'  => "The\n\nText",
    164                         'filter' => false,
     313                        'filter' => 'content',
    165314                );
    166315
    167316                wp_set_current_user( $this->factory()->user->create( array(
    168317                        'role' => 'administrator',
    169318                ) ) );
    170319
    171                 // Should return valid instance.
     320                // Should return valid instance in legacy mode since filter=false and there are line breaks.
    172321                $expected = array(
    173322                        'title'  => sanitize_text_field( $instance['title'] ),
    174323                        'text'   => $instance['text'],
    175324                        'filter' => 'content',
    176325                );
    177326                $result = $widget->update( $instance, array() );
    178                 $this->assertEquals( $result, $expected );
     327                $this->assertEquals( $expected, $result );
    179328                $this->assertTrue( ! empty( $expected['filter'] ), 'Expected filter prop to be truthy, to handle case where 4.8 is downgraded to 4.7.' );
    180329
    181330                // Make sure KSES is applying as expected.
    class Test_WP_Widget_Text extends WP_UnitTestCase { 
    184333                $instance['text'] = '<script>alert( "Howdy!" );</script>';
    185334                $expected['text'] = $instance['text'];
    186335                $result = $widget->update( $instance, array() );
    187                 $this->assertEquals( $result, $expected );
     336                $this->assertEquals( $expected, $result );
    188337                remove_filter( 'map_meta_cap', array( $this, 'grant_unfiltered_html_cap' ) );
    189338
    190339                add_filter( 'map_meta_cap', array( $this, 'revoke_unfiltered_html_cap' ), 10, 2 );
    class Test_WP_Widget_Text extends WP_UnitTestCase { 
    192341                $instance['text'] = '<script>alert( "Howdy!" );</script>';
    193342                $expected['text'] = wp_kses_post( $instance['text'] );
    194343                $result = $widget->update( $instance, array() );
    195                 $this->assertEquals( $result, $expected );
     344                $this->assertEquals( $expected, $result );
    196345                remove_filter( 'map_meta_cap', array( $this, 'revoke_unfiltered_html_cap' ), 10 );
    197346        }
    198347
    199348        /**
     349         * Test update for legacy widgets.
     350         *
     351         * @covers WP_Widget_Text::update
     352         */
     353        function test_update_legacy() {
     354                $widget = new WP_Widget_Text();
     355
     356                // Updating a widget with explicit filter=true persists with legacy mode.
     357                $instance = array(
     358                        'title' => 'Legacy',
     359                        'text' => 'Text',
     360                        'filter' => true,
     361                );
     362                $result = $widget->update( $instance, array() );
     363                $expected = array_merge( $instance, array(
     364                        'legacy' => true,
     365                        'filter' => true,
     366                ) );
     367                $this->assertEquals( $expected, $result );
     368
     369                // Updating a widget with explicit filter=false persists with legacy mode.
     370                $instance['filter'] = false;
     371                $result = $widget->update( $instance, array() );
     372                $expected = array_merge( $instance, array(
     373                        'legacy' => true,
     374                        'filter' => false,
     375                ) );
     376                $this->assertEquals( $expected, $result );
     377
     378                // Updating a widget in legacy form results in filter=false when checkbox not checked.
     379                $instance['filter'] = true;
     380                $result = $widget->update( $instance, array() );
     381                $expected = array_merge( $instance, array(
     382                        'legacy' => true,
     383                        'filter' => true,
     384                ) );
     385                $this->assertEquals( $expected, $result );
     386
     387                // Updating a widget that previously had legacy form results in filter persisting.
     388                unset( $instance['legacy'] );
     389                $instance['filter'] = true;
     390                $result = $widget->update( $instance, array(
     391                        'legacy' => true,
     392                ) );
     393                $expected = array_merge( $instance, array(
     394                        'legacy' => true,
     395                        'filter' => true,
     396                ) );
     397                $this->assertEquals( $expected, $result );
     398        }
     399
     400        /**
    200401         * Grant unfiltered_html cap via map_meta_cap.
    201402         *
    202403         * @param array  $caps    Returns the user's actual capabilities.