Changeset 41050
- Timestamp:
- 07/14/2017 05:08:20 PM (7 years ago)
- Location:
- trunk
- Files:
-
- 5 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/wp-admin/css/widgets.css
r40653 r41050 620 620 } 621 621 622 /* =Specific widget styling 623 -------------------------------------------------------------- */ 624 .text-widget-fields { 625 position: relative; 626 } 627 .text-widget-fields [hidden] { 628 display: none; 629 } 630 .text-widget-fields .wp-pointer.wp-pointer-top { 631 position: absolute; 632 z-index: 3; 633 top: 100px; 634 right: 10px; 635 left: 10px; 636 } 637 .text-widget-fields .wp-pointer .wp-pointer-arrow { 638 left: auto; 639 right: 15px; 640 } 641 .text-widget-fields .wp-pointer .wp-pointer-buttons { 642 line-height: 1.4em; 643 } 644 622 645 /* =Media Queries 623 646 -------------------------------------------------------------- */ -
trunk/src/wp-admin/js/widgets/text-widgets.js
r40995 r41050 4 4 'use strict'; 5 5 6 var component = {}; 6 var component = { 7 dismissedPointers: [] 8 }; 7 9 8 10 /** … … 45 47 control.$el.addClass( 'text-widget-fields' ); 46 48 control.$el.html( wp.template( 'widget-text-control-fields' ) ); 49 50 control.customHtmlWidgetPointer = control.$el.find( '.wp-pointer.custom-html-widget-pointer' ); 51 if ( control.customHtmlWidgetPointer.length ) { 52 control.customHtmlWidgetPointer.find( '.close' ).on( 'click', function( event ) { 53 event.preventDefault(); 54 control.customHtmlWidgetPointer.hide(); 55 $( '#' + control.fields.text.attr( 'id' ) + '-html' ).focus(); 56 control.dismissPointers( [ 'text_widget_custom_html' ] ); 57 }); 58 control.customHtmlWidgetPointer.find( '.add-widget' ).on( 'click', function( event ) { 59 event.preventDefault(); 60 control.customHtmlWidgetPointer.hide(); 61 control.openAvailableWidgetsPanel(); 62 }); 63 } 64 65 control.pasteHtmlPointer = control.$el.find( '.wp-pointer.paste-html-pointer' ); 66 if ( control.pasteHtmlPointer.length ) { 67 control.pasteHtmlPointer.find( '.close' ).on( 'click', function( event ) { 68 event.preventDefault(); 69 control.pasteHtmlPointer.hide(); 70 control.editor.focus(); 71 control.dismissPointers( [ 'text_widget_custom_html', 'text_widget_paste_html' ] ); 72 }); 73 } 47 74 48 75 control.fields = { … … 67 94 68 95 /** 96 * Dismiss pointers for Custom HTML widget. 97 * 98 * @since 4.8.1 99 * 100 * @param {Array} pointers Pointer IDs to dismiss. 101 * @returns {void} 102 */ 103 dismissPointers: function dismissPointers( pointers ) { 104 _.each( pointers, function( pointer ) { 105 wp.ajax.post( 'dismiss-wp-pointer', { 106 pointer: pointer 107 }); 108 component.dismissedPointers.push( pointer ); 109 }); 110 }, 111 112 /** 113 * Open available widgets panel. 114 * 115 * @since 4.8.1 116 * @returns {void} 117 */ 118 openAvailableWidgetsPanel: function openAvailableWidgetsPanel() { 119 var sidebarControl; 120 wp.customize.section.each( function( section ) { 121 if ( section.extended( wp.customize.Widgets.SidebarSection ) && section.expanded() ) { 122 sidebarControl = wp.customize.control( 'sidebars_widgets[' + section.params.sidebarId + ']' ); 123 } 124 }); 125 if ( ! sidebarControl ) { 126 return; 127 } 128 setTimeout( function() { // Timeout to prevent click event from causing panel to immediately collapse. 129 wp.customize.Widgets.availableWidgetsPanel.open( sidebarControl ); 130 wp.customize.Widgets.availableWidgetsPanel.$search.val( 'HTML' ).trigger( 'keyup' ); 131 }); 132 }, 133 134 /** 69 135 * Update input fields from the sync fields. 70 136 * … … 109 175 */ 110 176 function buildEditor() { 111 var editor, triggerChangeIfDirty, onInit ;177 var editor, triggerChangeIfDirty, onInit, showPointerElement; 112 178 113 179 // Abort building if the textarea is gone, likely due to the widget having been deleted entirely. … … 138 204 }); 139 205 206 /** 207 * Show a pointer, focus on dismiss, and speak the contents for a11y. 208 * 209 * @param {jQuery} pointerElement Pointer element. 210 * @returns {void} 211 */ 212 showPointerElement = function( pointerElement ) { 213 pointerElement.show(); 214 pointerElement.find( '.close' ).focus(); 215 wp.a11y.speak( pointerElement.find( 'h3, p' ).map( function() { 216 return $( this ).text(); 217 } ).get().join( '\n\n' ) ); 218 }; 219 140 220 editor = window.tinymce.get( id ); 141 221 if ( ! editor ) { … … 153 233 switchEditors.go( id, 'toggle' ); 154 234 } 235 236 // Show the pointer. 237 $( '#' + id + '-html' ).on( 'click', function() { 238 control.pasteHtmlPointer.hide(); // Hide the HTML pasting pointer. 239 240 if ( -1 !== component.dismissedPointers.indexOf( 'text_widget_custom_html' ) ) { 241 return; 242 } 243 showPointerElement( control.customHtmlWidgetPointer ); 244 }); 245 246 // Hide the pointer when switching tabs. 247 $( '#' + id + '-tmce' ).on( 'click', function() { 248 control.customHtmlWidgetPointer.hide(); 249 }); 250 251 // Show pointer when pasting HTML. 252 editor.on( 'pastepreprocess', function( event ) { 253 var content = event.content; 254 if ( -1 !== component.dismissedPointers.indexOf( 'text_widget_paste_html' ) || ! content || ! /<\w+.*?>/.test( content ) ) { 255 return; 256 } 257 258 // Show the pointer after a slight delay so the user sees what they pasted. 259 _.delay( function() { 260 showPointerElement( control.pasteHtmlPointer ); 261 }, 250 ); 262 }); 155 263 }; 156 264 … … 234 342 } 235 343 344 // Bypass using TinyMCE when widget is in legacy mode. 345 if ( widgetForm.find( '.legacy' ).length > 0 ) { 346 return; 347 } 348 236 349 /* 237 350 * Create a container element for the widget control fields. … … 290 403 } 291 404 405 // Bypass using TinyMCE when widget is in legacy mode. 406 if ( widgetForm.find( '.legacy' ).length > 0 ) { 407 return; 408 } 409 292 410 fieldContainer = $( '<div></div>' ); 293 411 syncContainer = widgetForm.find( '> .widget-inside' ); -
trunk/src/wp-includes/script-loader.php
r40948 r41050 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' ) );611 $scripts->add( 'text-widgets', "/wp-admin/js/widgets/text-widgets$suffix.js", array( 'jquery', 'backbone', 'editor', 'wp-util', 'wp-a11y' ) ); 612 612 $scripts->add_inline_script( 'text-widgets', 'wp.textWidgets.init();', 'after' ); 613 613 … … 846 846 $styles->add( 'about', "/wp-admin/css/about$suffix.css" ); 847 847 $styles->add( 'nav-menus', "/wp-admin/css/nav-menus$suffix.css" ); 848 $styles->add( 'widgets', "/wp-admin/css/widgets$suffix.css" );848 $styles->add( 'widgets', "/wp-admin/css/widgets$suffix.css", array( 'wp-pointer' ) ); 849 849 $styles->add( 'site-icon', "/wp-admin/css/site-icon$suffix.css" ); 850 850 $styles->add( 'l10n', "/wp-admin/css/l10n$suffix.css" ); -
trunk/src/wp-includes/widgets/class-wp-widget-text.php
r41028 r41050 65 65 66 66 /** 67 * Determines whether a given instance is legacy and should bypass using TinyMCE. 68 * 69 * @since 4.8.1 70 * 71 * @param array $instance { 72 * Instance data. 73 * 74 * @type string $text Content. 75 * @type bool|string $filter Whether autop or content filters should apply. 76 * @type bool $legacy Whether widget is in legacy mode. 77 * } 78 * @return bool Whether Text widget instance contains legacy data. 79 */ 80 public function is_legacy_instance( $instance ) { 81 82 // If the widget has been updated while in legacy mode, it stays in legacy mode. 83 if ( ! empty( $instance['legacy'] ) ) { 84 return true; 85 } 86 87 // If the widget has been added/updated in 4.8 then filter prop is 'content' and it is no longer legacy. 88 if ( isset( $instance['filter'] ) && 'content' === $instance['filter'] ) { 89 return false; 90 } 91 92 // If the text is empty, then nothing is preventing migration to TinyMCE. 93 if ( empty( $instance['text'] ) ) { 94 return false; 95 } 96 97 $wpautop = ! empty( $instance['filter'] ); 98 $has_line_breaks = ( false !== strpos( $instance['text'], "\n" ) ); 99 100 // If auto-paragraphs are not enabled and there are line breaks, then ensure legacy mode. 101 if ( ! $wpautop && $has_line_breaks ) { 102 return true; 103 } 104 105 // If an HTML comment is present, assume legacy mode. 106 if ( false !== strpos( $instance['text'], '<!--' ) ) { 107 return true; 108 } 109 110 /* 111 * If a shortcode is present (with support added by a plugin), assume legacy mode 112 * since shortcodes would apply at the widget_text filter and thus be applied 113 * before wpautop runs at the widget_text_content filter. 114 */ 115 if ( preg_match( '/' . get_shortcode_regex() . '/', $instance['text'] ) ) { 116 return true; 117 } 118 119 // In the rare case that DOMDocument is not available we cannot reliably sniff content and so we assume legacy. 120 if ( ! class_exists( 'DOMDocument' ) ) { 121 // @codeCoverageIgnoreStart 122 return true; 123 // @codeCoverageIgnoreEnd 124 } 125 126 $doc = new DOMDocument(); 127 $doc->loadHTML( sprintf( 128 '<html><head><meta charset="%s"></head><body>%s</body></html>', 129 esc_attr( get_bloginfo( 'charset' ) ), 130 $instance['text'] 131 ) ); 132 $body = $doc->getElementsByTagName( 'body' )->item( 0 ); 133 134 // See $allowedposttags. 135 $safe_elements_attributes = array( 136 'strong' => array(), 137 'em' => array(), 138 'b' => array(), 139 'i' => array(), 140 'u' => array(), 141 's' => array(), 142 'ul' => array(), 143 'ol' => array(), 144 'li' => array(), 145 'hr' => array(), 146 'abbr' => array(), 147 'acronym' => array(), 148 'code' => array(), 149 'dfn' => array(), 150 'a' => array( 151 'href' => true, 152 ), 153 'img' => array( 154 'src' => true, 155 'alt' => true, 156 ), 157 ); 158 $safe_empty_elements = array( 'img', 'hr', 'iframe' ); 159 160 foreach ( $body->getElementsByTagName( '*' ) as $element ) { 161 /** @var DOMElement $element */ 162 $tag_name = strtolower( $element->nodeName ); 163 164 // If the element is not safe, then the instance is legacy. 165 if ( ! isset( $safe_elements_attributes[ $tag_name ] ) ) { 166 return true; 167 } 168 169 // If the element is not safely empty and it has empty contents, then legacy mode. 170 if ( ! in_array( $tag_name, $safe_empty_elements, true ) && '' === trim( $element->textContent ) ) { 171 return true; 172 } 173 174 // If an attribute is not recognized as safe, then the instance is legacy. 175 foreach ( $element->attributes as $attribute ) { 176 /** @var DOMAttr $attribute */ 177 $attribute_name = strtolower( $attribute->nodeName ); 178 179 if ( ! isset( $safe_elements_attributes[ $tag_name ][ $attribute_name ] ) ) { 180 return true; 181 } 182 } 183 } 184 185 // Otherwise, the text contains no elements/attributes that TinyMCE could drop, and therefore the widget does not need legacy mode. 186 return false; 187 } 188 189 /** 67 190 * Outputs the content for the current Text widget instance. 68 191 * … … 80 203 81 204 $text = ! empty( $instance['text'] ) ? $instance['text'] : ''; 205 $is_visual_text_widget = ( isset( $instance['filter'] ) && 'content' === $instance['filter'] ); 206 207 /* 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. 212 */ 213 $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 ) { 216 remove_filter( 'widget_text', 'do_shortcode', $widget_text_do_shortcode_priority ); 217 add_filter( 'widget_text_content', 'do_shortcode', 11 ); 218 } 82 219 83 220 /** … … 114 251 } 115 252 253 // Undo temporary upgrade of the plugin-supplied shortcode handling. 254 if ( $should_upgrade_shortcode_handling ) { 255 remove_filter( 'widget_text_content', 'do_shortcode', 11 ); 256 add_filter( 'widget_text', 'do_shortcode', $widget_text_do_shortcode_priority ); 257 } 258 116 259 echo $args['before_widget']; 117 260 if ( ! empty( $title ) ) { … … 146 289 147 290 /* 148 * Re-use legacy 'filter' (wpautop) property to now indicate content filters will always apply. 291 * If the Text widget is in legacy mode, then a hidden input will indicate this 292 * and the new content value for the filter prop will by bypassed. Otherwise, 293 * re-use legacy 'filter' (wpautop) property to now indicate content filters will always apply. 149 294 * Prior to 4.8, this is a boolean value used to indicate whether or not wpautop should be 150 295 * applied. By re-using this property, downgrading WordPress from 4.8 to 4.7 will ensure 151 296 * that the content for Text widgets created with TinyMCE will continue to get wpautop. 152 297 */ 153 $instance['filter'] = 'content'; 298 if ( isset( $new_instance['legacy'] ) || isset( $old_instance['legacy'] ) || ( isset( $new_instance['filter'] ) && 'content' !== $new_instance['filter'] ) ) { 299 $instance['filter'] = ! empty( $new_instance['filter'] ); 300 $instance['legacy'] = true; 301 } else { 302 $instance['filter'] = 'content'; 303 unset( $instance['legacy'] ); 304 } 154 305 155 306 return $instance; … … 172 323 * @since 2.8.0 173 324 * @since 4.8.0 Form only contains hidden inputs which are synced with JS template. 325 * @since 4.8.1 Restored original form to be displayed when in legacy mode. 174 326 * @access public 175 327 * @see WP_Widget_Visual_Text::render_control_template_scripts() … … 187 339 ); 188 340 ?> 189 <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'] ); ?>"> 190 <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'] ); ?>"> 341 <?php if ( ! $this->is_legacy_instance( $instance ) ) : ?> 342 <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'] ); ?>"> 343 <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'] ); ?>"> 344 <?php else : ?> 345 <input name="<?php echo $this->get_field_name( 'legacy' ); ?>" type="hidden" class="legacy" value="true"> 346 <p> 347 <label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php _e( 'Title:' ); ?></label> 348 <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'] ); ?>"/> 349 </p> 350 <div class="notice inline notice-info notice-alt"> 351 <p><?php _e( 'This widget contains code that may work better in the new “Custom HTML” widget. How about trying that widget instead?' ); ?></p> 352 </div> 353 <p> 354 <label for="<?php echo $this->get_field_id( 'text' ); ?>"><?php _e( 'Content:' ); ?></label> 355 <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> 356 </p> 357 <p> 358 <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> 359 </p> 191 360 <?php 361 endif; 192 362 } 193 363 … … 199 369 */ 200 370 public function render_control_template_scripts() { 371 $dismissed_pointers = explode( ',', (string) get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true ) ); 201 372 ?> 202 373 <script type="text/html" id="tmpl-widget-text-control-fields"> … … 206 377 <input id="{{ elementIdPrefix }}title" type="text" class="widefat title"> 207 378 </p> 379 380 <?php if ( ! in_array( 'text_widget_custom_html', $dismissed_pointers, true ) ) : ?> 381 <div hidden class="wp-pointer custom-html-widget-pointer wp-pointer-top"> 382 <div class="wp-pointer-content"> 383 <h3><?php _e( 'New Custom HTML Widget' ); ?></h3> 384 <?php if ( is_customize_preview() ) : ?> 385 <p><?php _e( 'Hey, did you hear we have a “Custom HTML” widget now? You can find it by pressing the “<a class="add-widget" href="#">Add a Widget</a>” button and searching for “HTML”. Check it out to add some custom code to your site!' ); ?></p> 386 <?php else : ?> 387 <p><?php _e( 'Hey, did you hear we have a “Custom HTML” widget now? You can find it by scanning the list of available widgets on this screen. Check it out to add some custom code to your site!' ); ?></p> 388 <?php endif; ?> 389 <div class="wp-pointer-buttons"> 390 <a class="close" href="#"><?php _e( 'Dismiss' ); ?></a> 391 </div> 392 </div> 393 <div class="wp-pointer-arrow"> 394 <div class="wp-pointer-arrow-inner"></div> 395 </div> 396 </div> 397 <?php endif; ?> 398 399 <?php if ( ! in_array( 'text_widget_paste_html', $dismissed_pointers, true ) ) : ?> 400 <div hidden class="wp-pointer paste-html-pointer wp-pointer-top"> 401 <div class="wp-pointer-content"> 402 <h3><?php _e( 'Did you just paste HTML?' ); ?></h3> 403 <p><?php _e( 'Hey there, looks like you just pasted HTML into the “Visual” tab of the Text widget. You may want to paste your code into the “Text” tab instead. Alternately, try out the new “Custom HTML” widget!' ); ?></p> 404 <div class="wp-pointer-buttons"> 405 <a class="close" href="#"><?php _e( 'Dismiss' ); ?></a> 406 </div> 407 </div> 408 <div class="wp-pointer-arrow"> 409 <div class="wp-pointer-arrow-inner"></div> 410 </div> 411 </div> 412 <?php endif; ?> 413 208 414 <p> 209 415 <label for="{{ elementIdPrefix }}text" class="screen-reader-text"><?php esc_html_e( 'Content:' ); ?></label> -
trunk/tests/phpunit/tests/widgets/text-widget.php
r40673 r41050 38 38 $wp_scripts = null; 39 39 $wp_styles = null; 40 } 41 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'] ); 40 54 } 41 55 … … 123 137 124 138 /** 139 * Example shortcode content to test for wpautop corruption. 140 * 141 * @var string 142 */ 143 protected $example_shortcode_content = "<p>One\nTwo\n\nThree</p>\n<script>\ndocument.write('Test1');\n\ndocument.write('Test2');\n</script>"; 144 145 /** 146 * Do example shortcode. 147 * 148 * @return string Shortcode content. 149 */ 150 function do_example_shortcode() { 151 return $this->example_shortcode_content; 152 } 153 154 /** 155 * Test widget method when a plugin has added shortcode support. 156 * 157 * @covers WP_Widget_Text::widget 158 */ 159 function test_widget_shortcodes() { 160 $args = array( 161 'before_title' => '<h2>', 162 'after_title' => "</h2>\n", 163 'before_widget' => '<section>', 164 'after_widget' => "</section>\n", 165 ); 166 $widget = new WP_Widget_Text(); 167 add_filter( 'widget_text', 'do_shortcode' ); 168 add_shortcode( 'example', array( $this, 'do_example_shortcode' ) ); 169 170 $base_instance = array( 171 'title' => 'Example', 172 'text' => "This is an example:\n\n[example]", 173 'filter' => false, 174 ); 175 176 // Legacy Text Widget. 177 $instance = array_merge( $base_instance, array( 178 'filter' => false, 179 ) ); 180 ob_start(); 181 $widget->widget( $args, $instance ); 182 $output = ob_get_clean(); 183 $this->assertContains( $this->example_shortcode_content, $output, 'Shortcode was applied without wpautop corrupting it.' ); 184 $this->assertEquals( 10, has_filter( 'widget_text', 'do_shortcode' ), 'Filter was restored.' ); 185 186 // Visual Text Widget. 187 $instance = array_merge( $base_instance, array( 188 'filter' => 'content', 189 ) ); 190 ob_start(); 191 $widget->widget( $args, $instance ); 192 $output = ob_get_clean(); 193 $this->assertContains( $this->example_shortcode_content, $output, 'Shortcode was applied without wpautop corrupting it.' ); 194 $this->assertEquals( 10, has_filter( 'widget_text', 'do_shortcode' ), 'Filter was restored.' ); 195 $this->assertFalse( has_filter( 'widget_text_content', 'do_shortcode' ), 'Filter was removed.' ); 196 197 // Visual Text Widget with properly-used widget_text_content filter. 198 remove_filter( 'widget_text', 'do_shortcode' ); 199 add_filter( 'widget_text_content', 'do_shortcode', 11 ); 200 $instance = array_merge( $base_instance, array( 201 'filter' => 'content', 202 ) ); 203 ob_start(); 204 $widget->widget( $args, $instance ); 205 $output = ob_get_clean(); 206 $this->assertContains( $this->example_shortcode_content, $output, 'Shortcode was applied without wpautop corrupting it.' ); 207 $this->assertFalse( has_filter( 'widget_text', 'do_shortcode' ), 'Filter was not erroneously restored.' ); 208 } 209 210 /** 125 211 * Filters the content of the Text widget. 126 212 * … … 153 239 154 240 /** 241 * Test is_legacy_instance method. 242 * 243 * @covers WP_Widget_Text::is_legacy_instance 244 */ 245 function test_is_legacy_instance() { 246 $widget = new WP_Widget_Text(); 247 $base_instance = array( 248 'title' => 'Title', 249 'text' => "Hello\n\nWorld", 250 ); 251 252 $instance = array_merge( $base_instance, array( 253 'legacy' => true, 254 ) ); 255 $this->assertTrue( $widget->is_legacy_instance( $instance ), 'Legacy when legacy prop is present.' ); 256 257 $instance = array_merge( $base_instance, array( 258 'filter' => 'content', 259 ) ); 260 $this->assertFalse( $widget->is_legacy_instance( $instance ), 'Not legacy when filter is explicitly content.' ); 261 262 $instance = array_merge( $base_instance, array( 263 'text' => '', 264 'filter' => true, 265 ) ); 266 $this->assertFalse( $widget->is_legacy_instance( $instance ), 'Not legacy when text is empty.' ); 267 268 $instance = array_merge( $base_instance, array( 269 'text' => "One\nTwo", 270 'filter' => false, 271 ) ); 272 $this->assertTrue( $widget->is_legacy_instance( $instance ), 'Legacy when not-wpautop and there are line breaks.' ); 273 274 $instance = array_merge( $base_instance, array( 275 'text' => "One\n\nTwo", 276 'filter' => false, 277 ) ); 278 $this->assertTrue( $widget->is_legacy_instance( $instance ), 'Legacy when not-wpautop and there are paragraph breaks.' ); 279 280 $instance = array_merge( $base_instance, array( 281 'text' => "One\nTwo", 282 'filter' => true, 283 ) ); 284 $this->assertFalse( $widget->is_legacy_instance( $instance ), 'Not automatically legacy when wpautop and there are line breaks.' ); 285 286 $instance = array_merge( $base_instance, array( 287 'text' => "One\n\nTwo", 288 'filter' => true, 289 ) ); 290 $this->assertFalse( $widget->is_legacy_instance( $instance ), 'Not automatically legacy when wpautop and there are paragraph breaks.' ); 291 292 $instance = array_merge( $base_instance, array( 293 'text' => 'Test<!-- comment -->', 294 'filter' => true, 295 ) ); 296 $this->assertTrue( $widget->is_legacy_instance( $instance ), 'Legacy when HTML comment is present.' ); 297 298 $instance = array_merge( $base_instance, array( 299 'text' => 'Here is a [gallery]', 300 'filter' => true, 301 ) ); 302 $this->assertTrue( $widget->is_legacy_instance( $instance ), 'Legacy mode when a shortcode is present.' ); 303 304 // Check text examples that will not migrate to TinyMCE. 305 $legacy_text_examples = array( 306 '<span class="hello"></span>', 307 '<span></span>', 308 "<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>", 309 '<a href="#" class="map"></a>', 310 "<script>\n\\Line one\n\n\\Line two</script>", 311 "<style>body {\ncolor:red;\n}</style>", 312 '<span class="fa fa-cc-discover fa-2x" aria-hidden="true"></span>', 313 "<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>", 314 '<span class="sectiondown"><a href="#front-page-3"><i class="fa fa-chevron-circle-down"></i></a></span>', 315 ); 316 foreach ( $legacy_text_examples as $legacy_text_example ) { 317 $instance = array_merge( $base_instance, array( 318 'text' => $legacy_text_example, 319 'filter' => true, 320 ) ); 321 $this->assertTrue( $widget->is_legacy_instance( $instance ), 'Legacy when wpautop and there is HTML that is not liable to be mutated.' ); 322 323 $instance = array_merge( $base_instance, array( 324 'text' => $legacy_text_example, 325 'filter' => false, 326 ) ); 327 $this->assertTrue( $widget->is_legacy_instance( $instance ), 'Legacy when not-wpautop and there is HTML that is not liable to be mutated.' ); 328 } 329 330 // Check text examples that will migrate to TinyMCE, where elements and attributes are not in whitelist. 331 $migratable_text_examples = array( 332 'Check out <a href="http://example.com">Example</a>', 333 '<img src="http://example.com/img.jpg" alt="Img">', 334 '<strong><em>Hello</em></strong>', 335 '<b><i><u><s>Hello</s></u></i></b>', 336 "<ul>\n<li>One</li>\n<li>One</li>\n<li>One</li>\n</ul>", 337 "<ol>\n<li>One</li>\n<li>One</li>\n<li>One</li>\n</ol>", 338 "Text\n<hr>\nAddendum", 339 "Look at this code:\n\n<code>echo 'Hello World!';</code>", 340 ); 341 foreach ( $migratable_text_examples as $migratable_text_example ) { 342 $instance = array_merge( $base_instance, array( 343 'text' => $migratable_text_example, 344 'filter' => true, 345 ) ); 346 $this->assertFalse( $widget->is_legacy_instance( $instance ), 'Legacy when wpautop and there is HTML that is not liable to be mutated.' ); 347 } 348 } 349 350 /** 351 * Test update method. 352 * 353 * @covers WP_Widget_Text::form 354 */ 355 function test_form() { 356 $widget = new WP_Widget_Text(); 357 $instance = array( 358 'title' => 'Title', 359 'text' => 'Text', 360 'filter' => false, 361 'legacy' => true, 362 ); 363 $this->assertTrue( $widget->is_legacy_instance( $instance ) ); 364 ob_start(); 365 $widget->form( $instance ); 366 $form = ob_get_clean(); 367 $this->assertContains( 'class="legacy"', $form ); 368 369 $instance = array( 370 'title' => 'Title', 371 'text' => 'Text', 372 'filter' => 'content', 373 ); 374 $this->assertFalse( $widget->is_legacy_instance( $instance ) ); 375 ob_start(); 376 $widget->form( $instance ); 377 $form = ob_get_clean(); 378 $this->assertNotContains( 'class="legacy"', $form ); 379 } 380 381 /** 155 382 * Test update method. 156 383 * … … 162 389 'title' => "The\nTitle", 163 390 'text' => "The\n\nText", 164 'filter' => false,391 'filter' => 'content', 165 392 ); 166 393 … … 169 396 ) ) ); 170 397 171 // Should return valid instance .398 // Should return valid instance in legacy mode since filter=false and there are line breaks. 172 399 $expected = array( 173 400 'title' => sanitize_text_field( $instance['title'] ), … … 176 403 ); 177 404 $result = $widget->update( $instance, array() ); 178 $this->assertEquals( $ result, $expected);405 $this->assertEquals( $expected, $result ); 179 406 $this->assertTrue( ! empty( $expected['filter'] ), 'Expected filter prop to be truthy, to handle case where 4.8 is downgraded to 4.7.' ); 180 407 … … 185 412 $expected['text'] = $instance['text']; 186 413 $result = $widget->update( $instance, array() ); 187 $this->assertEquals( $ result, $expected);414 $this->assertEquals( $expected, $result ); 188 415 remove_filter( 'map_meta_cap', array( $this, 'grant_unfiltered_html_cap' ) ); 189 416 … … 193 420 $expected['text'] = wp_kses_post( $instance['text'] ); 194 421 $result = $widget->update( $instance, array() ); 195 $this->assertEquals( $ result, $expected);422 $this->assertEquals( $expected, $result ); 196 423 remove_filter( 'map_meta_cap', array( $this, 'revoke_unfiltered_html_cap' ), 10 ); 424 } 425 426 /** 427 * Test update for legacy widgets. 428 * 429 * @covers WP_Widget_Text::update 430 */ 431 function test_update_legacy() { 432 $widget = new WP_Widget_Text(); 433 434 // Updating a widget with explicit filter=true persists with legacy mode. 435 $instance = array( 436 'title' => 'Legacy', 437 'text' => 'Text', 438 'filter' => true, 439 ); 440 $result = $widget->update( $instance, array() ); 441 $expected = array_merge( $instance, array( 442 'legacy' => true, 443 'filter' => true, 444 ) ); 445 $this->assertEquals( $expected, $result ); 446 447 // Updating a widget with explicit filter=false persists with legacy mode. 448 $instance['filter'] = false; 449 $result = $widget->update( $instance, array() ); 450 $expected = array_merge( $instance, array( 451 'legacy' => true, 452 'filter' => false, 453 ) ); 454 $this->assertEquals( $expected, $result ); 455 456 // Updating a widget in legacy form results in filter=false when checkbox not checked. 457 $instance['filter'] = true; 458 $result = $widget->update( $instance, array() ); 459 $expected = array_merge( $instance, array( 460 'legacy' => true, 461 'filter' => true, 462 ) ); 463 $this->assertEquals( $expected, $result ); 464 465 // Updating a widget that previously had legacy form results in filter persisting. 466 unset( $instance['legacy'] ); 467 $instance['filter'] = true; 468 $result = $widget->update( $instance, array( 469 'legacy' => true, 470 ) ); 471 $expected = array_merge( $instance, array( 472 'legacy' => true, 473 'filter' => true, 474 ) ); 475 $this->assertEquals( $expected, $result ); 197 476 } 198 477
Note: See TracChangeset
for help on using the changeset viewer.