Widgets: Prevent visual Text widget from decoding encoded HTML.

Also apply `the_editor_content` filters on widget `text` with `format_for_editor()` as is done for the post editor.

Amends [40631].
Props westonruter, azaozz.
See #35243.
Fixes #41596.


git-svn-id: https://develop.svn.wordpress.org/trunk@41260 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Weston Ruter 2017-08-17 23:36:53 +00:00
parent e0de3f6d1c
commit 66ee109e0a
3 changed files with 56 additions and 16 deletions

View File

@ -81,7 +81,7 @@ wp.textWidgets = ( function( $ ) {
// Sync input fields to hidden sync fields which actually get sent to the server.
_.each( control.fields, function( fieldInput, fieldName ) {
fieldInput.on( 'input change', function updateSyncField() {
var syncInput = control.syncContainer.find( 'input[type=hidden].' + fieldName );
var syncInput = control.syncContainer.find( '.sync-input.' + fieldName );
if ( syncInput.val() !== fieldInput.val() ) {
syncInput.val( fieldInput.val() );
syncInput.trigger( 'change' );
@ -89,7 +89,7 @@ wp.textWidgets = ( function( $ ) {
});
// Note that syncInput cannot be re-used because it will be destroyed with each widget-updated event.
fieldInput.val( control.syncContainer.find( 'input[type=hidden].' + fieldName ).val() );
fieldInput.val( control.syncContainer.find( '.sync-input.' + fieldName ).val() );
});
},
@ -145,11 +145,11 @@ wp.textWidgets = ( function( $ ) {
var control = this, syncInput;
if ( ! control.fields.title.is( document.activeElement ) ) {
syncInput = control.syncContainer.find( 'input[type=hidden].title' );
syncInput = control.syncContainer.find( '.sync-input.title' );
control.fields.title.val( syncInput.val() );
}
syncInput = control.syncContainer.find( 'input[type=hidden].text' );
syncInput = control.syncContainer.find( '.sync-input.text' );
if ( control.fields.text.is( ':visible' ) ) {
if ( ! control.fields.text.is( document.activeElement ) ) {
control.fields.text.val( syncInput.val() );

View File

@ -332,6 +332,7 @@ class WP_Widget_Text extends WP_Widget {
* @since 4.8.0 Form only contains hidden inputs which are synced with JS template.
* @since 4.8.1 Restored original form to be displayed when in legacy mode.
* @see WP_Widget_Visual_Text::render_control_template_scripts()
* @see _WP_Editors::editor()
*
* @param array $instance Current settings.
* @return void
@ -346,10 +347,31 @@ class WP_Widget_Text extends WP_Widget {
);
?>
<?php if ( ! $this->is_legacy_instance( $instance ) ) : ?>
<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'] ); ?>">
<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'] ); ?>">
<input id="<?php echo $this->get_field_id( 'filter' ); ?>" name="<?php echo $this->get_field_name( 'filter' ); ?>" class="filter" type="hidden" value="on">
<input id="<?php echo $this->get_field_id( 'visual' ); ?>" name="<?php echo $this->get_field_name( 'visual' ); ?>" class="visual" type="hidden" value="on">
<?php
if ( user_can_richedit() ) {
add_filter( 'the_editor_content', 'format_for_editor', 10, 2 );
$default_editor = 'tinymce';
} else {
$default_editor = 'html';
}
/** This filter is documented in wp-includes/class-wp-editor.php */
$text = apply_filters( 'the_editor_content', $instance['text'], $default_editor );
// Reset filter addition.
if ( user_can_richedit() ) {
remove_filter( 'the_editor_content', 'format_for_editor' );
}
// Prevent premature closing of textarea in case format_for_editor() didn't apply or the_editor_content filter did a wrong thing.
$escaped_text = preg_replace( '#</textarea#i', '&lt;/textarea', $text );
?>
<input id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" class="title sync-input" type="hidden" value="<?php echo esc_attr( $instance['title'] ); ?>">
<textarea id="<?php echo $this->get_field_id( 'text' ); ?>" name="<?php echo $this->get_field_name( 'text' ); ?>" class="text sync-input" hidden><?php echo $escaped_text; ?></textarea>
<input id="<?php echo $this->get_field_id( 'filter' ); ?>" name="<?php echo $this->get_field_name( 'filter' ); ?>" class="filter sync-input" type="hidden" value="on">
<input id="<?php echo $this->get_field_id( 'visual' ); ?>" name="<?php echo $this->get_field_name( 'visual' ); ?>" class="visual sync-input" type="hidden" value="on">
<?php else : ?>
<input id="<?php echo $this->get_field_id( 'visual' ); ?>" name="<?php echo $this->get_field_name( 'visual' ); ?>" class="visual" type="hidden" value="">
<p>

View File

@ -447,7 +447,9 @@ class Test_WP_Widget_Text extends WP_UnitTestCase {
* @covers WP_Widget_Text::form
*/
function test_form() {
add_filter( 'user_can_richedit', '__return_true' );
$widget = new WP_Widget_Text();
$widget->_set( 2 );
$instance = array(
'title' => 'Title',
'text' => 'Text',
@ -459,7 +461,7 @@ class Test_WP_Widget_Text extends WP_UnitTestCase {
$widget->form( $instance );
$form = ob_get_clean();
$this->assertContains( 'class="visual" type="hidden" value=""', $form );
$this->assertNotContains( 'class="visual" type="hidden" value="on"', $form );
$this->assertNotContains( 'class="visual sync-input" type="hidden" value="on"', $form );
$instance = array(
'title' => 'Title',
@ -470,8 +472,8 @@ class Test_WP_Widget_Text extends WP_UnitTestCase {
ob_start();
$widget->form( $instance );
$form = ob_get_clean();
$this->assertContains( 'class="visual" type="hidden" value="on"', $form );
$this->assertNotContains( 'class="visual" type="hidden" value=""', $form );
$this->assertContains( 'class="visual sync-input" type="hidden" value="on"', $form );
$this->assertNotContains( 'class="visual sync-input" type="hidden" value=""', $form );
$instance = array(
'title' => 'Title',
@ -482,12 +484,12 @@ class Test_WP_Widget_Text extends WP_UnitTestCase {
ob_start();
$widget->form( $instance );
$form = ob_get_clean();
$this->assertContains( 'class="visual" type="hidden" value="on"', $form );
$this->assertNotContains( 'class="visual" type="hidden" value=""', $form );
$this->assertContains( 'class="visual sync-input" type="hidden" value="on"', $form );
$this->assertNotContains( 'class="visual sync-input" type="hidden" value=""', $form );
$instance = array(
'title' => 'Title',
'text' => 'Text',
'text' => 'This is some HTML Code: <code>&lt;strong&gt;BOLD!&lt;/strong&gt;</code>',
'filter' => true,
'visual' => true,
);
@ -495,8 +497,24 @@ class Test_WP_Widget_Text extends WP_UnitTestCase {
ob_start();
$widget->form( $instance );
$form = ob_get_clean();
$this->assertContains( 'class="visual" type="hidden" value="on"', $form );
$this->assertNotContains( 'class="visual" type="hidden" value=""', $form );
$this->assertContains( 'class="visual sync-input" type="hidden" value="on"', $form );
$this->assertContains( '&lt;code&gt;&amp;lt;strong&amp;gt;BOLD!', $form );
$this->assertNotContains( 'class="visual sync-input" type="hidden" value=""', $form );
remove_filter( 'user_can_richedit', '__return_true' );
add_filter( 'user_can_richedit', '__return_false' );
$instance = array(
'title' => 'Title',
'text' => 'Evil:</textarea><script>alert("XSS")</script>',
'filter' => true,
'visual' => true,
);
$this->assertFalse( $widget->is_legacy_instance( $instance ) );
ob_start();
$widget->form( $instance );
$form = ob_get_clean();
$this->assertNotContains( 'Evil:</textarea>', $form );
$this->assertContains( 'Evil:&lt;/textarea>', $form );
}
/**