Ticket #40951: 40951.4.diff
File 40951.4.diff, 21.8 KB (added by , 8 years ago) |
---|
-
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
3 3 wp.textWidgets = ( function( $ ) { 4 4 'use strict'; 5 5 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 }; 7 15 8 16 /** 9 17 * Text widget control. … … wp.textWidgets = ( function( $ ) { 152 160 if ( restoreTextMode ) { 153 161 switchEditors.go( id, 'toggle' ); 154 162 } 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 }); 155 185 }; 156 186 157 187 if ( editor.initialized ) { … … wp.textWidgets = ( function( $ ) { 233 263 return; 234 264 } 235 265 266 // Bypass using TinyMCE when widget is in legacy mode. 267 if ( widgetForm.find( '.legacy' ).length > 0 ) { 268 return; 269 } 270 236 271 /* 237 272 * Create a container element for the widget control fields. 238 273 * This is inserted into the DOM immediately before the the .widget-content … … wp.textWidgets = ( function( $ ) { 337 372 * When WordPress enqueues this script, it should have an inline script 338 373 * attached which calls wp.textWidgets.init(). 339 374 * 375 * @param {object} exports Server exports. 376 * @param {object} exports.l10n Translations. 340 377 * @returns {void} 341 378 */ 342 component.init = function init( ) {379 component.init = function init( exports ) { 343 380 var $document = $( document ); 381 component.data = exports; 344 382 $document.on( 'widget-added', component.handleWidgetAdded ); 345 383 $document.on( 'widget-synced widget-updated', component.handleWidgetUpdated ); 346 384 -
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 ) { 608 608 $scripts->add( 'media-audio-widget', "/wp-admin/js/widgets/media-audio-widget$suffix.js", array( 'media-widgets', 'media-audiovideo' ) ); 609 609 $scripts->add( 'media-image-widget', "/wp-admin/js/widgets/media-image-widget$suffix.js", array( 'media-widgets' ) ); 610 610 $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' ); 613 620 614 621 $scripts->add( 'theme', "/wp-admin/js/theme$suffix.js", array( 'wp-backbone', 'wp-a11y' ), false, 1 ); 615 622 -
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 { 53 53 } 54 54 55 55 /** 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 /** 56 174 * Outputs the content for the current Text widget instance. 57 175 * 58 176 * @since 2.8.0 … … class WP_Widget_Text extends WP_Widget { 134 252 } 135 253 136 254 /* 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. 138 258 * Prior to 4.8, this is a boolean value used to indicate whether or not wpautop should be 139 259 * applied. By re-using this property, downgrading WordPress from 4.8 to 4.7 will ensure 140 260 * that the content for Text widgets created with TinyMCE will continue to get wpautop. 141 261 */ 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 } 143 269 144 270 return $instance; 145 271 } … … class WP_Widget_Text extends WP_Widget { 153 279 public function enqueue_admin_scripts() { 154 280 wp_enqueue_editor(); 155 281 wp_enqueue_script( 'text-widgets' ); 282 wp_enqueue_style( 'wp-pointer' ); 156 283 } 157 284 158 285 /** … … class WP_Widget_Text extends WP_Widget { 160 287 * 161 288 * @since 2.8.0 162 289 * @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. 163 291 * @access public 164 292 * @see WP_Widget_Visual_Text::render_control_template_scripts() 165 293 * … … class WP_Widget_Text extends WP_Widget { 175 303 ) 176 304 ); 177 305 ?> 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 “Custom HTML” 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'] ) ); ?> /> <label for="<?php echo $this->get_field_id( 'filter' ); ?>"><?php _e( 'Automatically add paragraphs' ); ?></label> 324 </p> 325 <?php endif; 181 326 } 182 327 183 328 /** -
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 { 40 40 } 41 41 42 42 /** 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 /** 43 57 * Test enqueue_admin_scripts method. 44 58 * 45 59 * @covers WP_Widget_Text::_register … … class Test_WP_Widget_Text extends WP_UnitTestCase { 152 166 } 153 167 154 168 /** 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 /** 155 304 * Test update method. 156 305 * 157 306 * @covers WP_Widget_Text::update … … class Test_WP_Widget_Text extends WP_UnitTestCase { 161 310 $instance = array( 162 311 'title' => "The\nTitle", 163 312 'text' => "The\n\nText", 164 'filter' => false,313 'filter' => 'content', 165 314 ); 166 315 167 316 wp_set_current_user( $this->factory()->user->create( array( 168 317 'role' => 'administrator', 169 318 ) ) ); 170 319 171 // Should return valid instance .320 // Should return valid instance in legacy mode since filter=false and there are line breaks. 172 321 $expected = array( 173 322 'title' => sanitize_text_field( $instance['title'] ), 174 323 'text' => $instance['text'], 175 324 'filter' => 'content', 176 325 ); 177 326 $result = $widget->update( $instance, array() ); 178 $this->assertEquals( $ result, $expected);327 $this->assertEquals( $expected, $result ); 179 328 $this->assertTrue( ! empty( $expected['filter'] ), 'Expected filter prop to be truthy, to handle case where 4.8 is downgraded to 4.7.' ); 180 329 181 330 // Make sure KSES is applying as expected. … … class Test_WP_Widget_Text extends WP_UnitTestCase { 184 333 $instance['text'] = '<script>alert( "Howdy!" );</script>'; 185 334 $expected['text'] = $instance['text']; 186 335 $result = $widget->update( $instance, array() ); 187 $this->assertEquals( $ result, $expected);336 $this->assertEquals( $expected, $result ); 188 337 remove_filter( 'map_meta_cap', array( $this, 'grant_unfiltered_html_cap' ) ); 189 338 190 339 add_filter( 'map_meta_cap', array( $this, 'revoke_unfiltered_html_cap' ), 10, 2 ); … … class Test_WP_Widget_Text extends WP_UnitTestCase { 192 341 $instance['text'] = '<script>alert( "Howdy!" );</script>'; 193 342 $expected['text'] = wp_kses_post( $instance['text'] ); 194 343 $result = $widget->update( $instance, array() ); 195 $this->assertEquals( $ result, $expected);344 $this->assertEquals( $expected, $result ); 196 345 remove_filter( 'map_meta_cap', array( $this, 'revoke_unfiltered_html_cap' ), 10 ); 197 346 } 198 347 199 348 /** 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 /** 200 401 * Grant unfiltered_html cap via map_meta_cap. 201 402 * 202 403 * @param array $caps Returns the user's actual capabilities.