Widgets: Add legacy mode for Text widget and add usage pointers to default visual mode.

The Text widget in legacy mode omits TinyMCE and retains old behavior for matching pre-existing Text widgets. Usage pointers added to default visual mode appear when attempting to paste HTML code into the Visual tab and when clicking on the Text tab, informing users of the new Custom HTML widget.

Props westonruter, melchoyce, gitlost for testing, obenland for testing, dougal for testing, afercia for testing.
See #35243.
Fixes #40951.


git-svn-id: https://develop.svn.wordpress.org/trunk@41050 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Weston Ruter
2017-07-14 17:08:20 +00:00
parent b60a603d50
commit e85f291a79
5 changed files with 639 additions and 13 deletions
+284 -5
View File
@@ -39,6 +39,20 @@ class Test_WP_Widget_Text extends WP_UnitTestCase {
$wp_styles = null;
}
/**
* Test constructor method.
*
* @covers WP_Widget_Text::__construct
*/
function test_construct() {
$widget = new WP_Widget_Text();
$this->assertEquals( 'text', $widget->id_base );
$this->assertEquals( 'widget_text', $widget->widget_options['classname'] );
$this->assertTrue( $widget->widget_options['customize_selective_refresh'] );
$this->assertEquals( 400, $widget->control_options['width'] );
$this->assertEquals( 350, $widget->control_options['height'] );
}
/**
* Test enqueue_admin_scripts method.
*
@@ -121,6 +135,78 @@ class Test_WP_Widget_Text extends WP_UnitTestCase {
$this->assertContains( wpautop( $instance['text'] . '[filter:widget_text][filter:widget_text_content]' ), $output );
}
/**
* Example shortcode content to test for wpautop corruption.
*
* @var string
*/
protected $example_shortcode_content = "<p>One\nTwo\n\nThree</p>\n<script>\ndocument.write('Test1');\n\ndocument.write('Test2');\n</script>";
/**
* Do example shortcode.
*
* @return string Shortcode content.
*/
function do_example_shortcode() {
return $this->example_shortcode_content;
}
/**
* Test widget method when a plugin has added shortcode support.
*
* @covers WP_Widget_Text::widget
*/
function test_widget_shortcodes() {
$args = array(
'before_title' => '<h2>',
'after_title' => "</h2>\n",
'before_widget' => '<section>',
'after_widget' => "</section>\n",
);
$widget = new WP_Widget_Text();
add_filter( 'widget_text', 'do_shortcode' );
add_shortcode( 'example', array( $this, 'do_example_shortcode' ) );
$base_instance = array(
'title' => 'Example',
'text' => "This is an example:\n\n[example]",
'filter' => false,
);
// Legacy Text Widget.
$instance = array_merge( $base_instance, array(
'filter' => false,
) );
ob_start();
$widget->widget( $args, $instance );
$output = ob_get_clean();
$this->assertContains( $this->example_shortcode_content, $output, 'Shortcode was applied without wpautop corrupting it.' );
$this->assertEquals( 10, has_filter( 'widget_text', 'do_shortcode' ), 'Filter was restored.' );
// Visual Text Widget.
$instance = array_merge( $base_instance, array(
'filter' => 'content',
) );
ob_start();
$widget->widget( $args, $instance );
$output = ob_get_clean();
$this->assertContains( $this->example_shortcode_content, $output, 'Shortcode was applied without wpautop corrupting it.' );
$this->assertEquals( 10, has_filter( 'widget_text', 'do_shortcode' ), 'Filter was restored.' );
$this->assertFalse( has_filter( 'widget_text_content', 'do_shortcode' ), 'Filter was removed.' );
// Visual Text Widget with properly-used widget_text_content filter.
remove_filter( 'widget_text', 'do_shortcode' );
add_filter( 'widget_text_content', 'do_shortcode', 11 );
$instance = array_merge( $base_instance, array(
'filter' => 'content',
) );
ob_start();
$widget->widget( $args, $instance );
$output = ob_get_clean();
$this->assertContains( $this->example_shortcode_content, $output, 'Shortcode was applied without wpautop corrupting it.' );
$this->assertFalse( has_filter( 'widget_text', 'do_shortcode' ), 'Filter was not erroneously restored.' );
}
/**
* Filters the content of the Text widget.
*
@@ -151,6 +237,147 @@ class Test_WP_Widget_Text extends WP_UnitTestCase {
return $widget_text;
}
/**
* Test is_legacy_instance method.
*
* @covers WP_Widget_Text::is_legacy_instance
*/
function test_is_legacy_instance() {
$widget = new WP_Widget_Text();
$base_instance = array(
'title' => 'Title',
'text' => "Hello\n\nWorld",
);
$instance = array_merge( $base_instance, array(
'legacy' => true,
) );
$this->assertTrue( $widget->is_legacy_instance( $instance ), 'Legacy when legacy prop is present.' );
$instance = array_merge( $base_instance, array(
'filter' => 'content',
) );
$this->assertFalse( $widget->is_legacy_instance( $instance ), 'Not legacy when filter is explicitly content.' );
$instance = array_merge( $base_instance, array(
'text' => '',
'filter' => true,
) );
$this->assertFalse( $widget->is_legacy_instance( $instance ), 'Not legacy when text is empty.' );
$instance = array_merge( $base_instance, array(
'text' => "One\nTwo",
'filter' => false,
) );
$this->assertTrue( $widget->is_legacy_instance( $instance ), 'Legacy when not-wpautop and there are line breaks.' );
$instance = array_merge( $base_instance, array(
'text' => "One\n\nTwo",
'filter' => false,
) );
$this->assertTrue( $widget->is_legacy_instance( $instance ), 'Legacy when not-wpautop and there are paragraph breaks.' );
$instance = array_merge( $base_instance, array(
'text' => "One\nTwo",
'filter' => true,
) );
$this->assertFalse( $widget->is_legacy_instance( $instance ), 'Not automatically legacy when wpautop and there are line breaks.' );
$instance = array_merge( $base_instance, array(
'text' => "One\n\nTwo",
'filter' => true,
) );
$this->assertFalse( $widget->is_legacy_instance( $instance ), 'Not automatically legacy when wpautop and there are paragraph breaks.' );
$instance = array_merge( $base_instance, array(
'text' => 'Test<!-- comment -->',
'filter' => true,
) );
$this->assertTrue( $widget->is_legacy_instance( $instance ), 'Legacy when HTML comment is present.' );
$instance = array_merge( $base_instance, array(
'text' => 'Here is a [gallery]',
'filter' => true,
) );
$this->assertTrue( $widget->is_legacy_instance( $instance ), 'Legacy mode when a shortcode is present.' );
// Check text examples that will not migrate to TinyMCE.
$legacy_text_examples = array(
'<span class="hello"></span>',
'<span></span>',
"<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>",
'<a href="#" class="map"></a>',
"<script>\n\\Line one\n\n\\Line two</script>",
"<style>body {\ncolor:red;\n}</style>",
'<span class="fa fa-cc-discover fa-2x" aria-hidden="true"></span>',
"<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>",
'<span class="sectiondown"><a href="#front-page-3"><i class="fa fa-chevron-circle-down"></i></a></span>',
);
foreach ( $legacy_text_examples as $legacy_text_example ) {
$instance = array_merge( $base_instance, array(
'text' => $legacy_text_example,
'filter' => true,
) );
$this->assertTrue( $widget->is_legacy_instance( $instance ), 'Legacy when wpautop and there is HTML that is not liable to be mutated.' );
$instance = array_merge( $base_instance, array(
'text' => $legacy_text_example,
'filter' => false,
) );
$this->assertTrue( $widget->is_legacy_instance( $instance ), 'Legacy when not-wpautop and there is HTML that is not liable to be mutated.' );
}
// Check text examples that will migrate to TinyMCE, where elements and attributes are not in whitelist.
$migratable_text_examples = array(
'Check out <a href="http://example.com">Example</a>',
'<img src="http://example.com/img.jpg" alt="Img">',
'<strong><em>Hello</em></strong>',
'<b><i><u><s>Hello</s></u></i></b>',
"<ul>\n<li>One</li>\n<li>One</li>\n<li>One</li>\n</ul>",
"<ol>\n<li>One</li>\n<li>One</li>\n<li>One</li>\n</ol>",
"Text\n<hr>\nAddendum",
"Look at this code:\n\n<code>echo 'Hello World!';</code>",
);
foreach ( $migratable_text_examples as $migratable_text_example ) {
$instance = array_merge( $base_instance, array(
'text' => $migratable_text_example,
'filter' => true,
) );
$this->assertFalse( $widget->is_legacy_instance( $instance ), 'Legacy when wpautop and there is HTML that is not liable to be mutated.' );
}
}
/**
* Test update method.
*
* @covers WP_Widget_Text::form
*/
function test_form() {
$widget = new WP_Widget_Text();
$instance = array(
'title' => 'Title',
'text' => 'Text',
'filter' => false,
'legacy' => true,
);
$this->assertTrue( $widget->is_legacy_instance( $instance ) );
ob_start();
$widget->form( $instance );
$form = ob_get_clean();
$this->assertContains( 'class="legacy"', $form );
$instance = array(
'title' => 'Title',
'text' => 'Text',
'filter' => 'content',
);
$this->assertFalse( $widget->is_legacy_instance( $instance ) );
ob_start();
$widget->form( $instance );
$form = ob_get_clean();
$this->assertNotContains( 'class="legacy"', $form );
}
/**
* Test update method.
*
@@ -161,21 +388,21 @@ class Test_WP_Widget_Text extends WP_UnitTestCase {
$instance = array(
'title' => "The\nTitle",
'text' => "The\n\nText",
'filter' => false,
'filter' => 'content',
);
wp_set_current_user( $this->factory()->user->create( array(
'role' => 'administrator',
) ) );
// Should return valid instance.
// Should return valid instance in legacy mode since filter=false and there are line breaks.
$expected = array(
'title' => sanitize_text_field( $instance['title'] ),
'text' => $instance['text'],
'filter' => 'content',
);
$result = $widget->update( $instance, array() );
$this->assertEquals( $result, $expected );
$this->assertEquals( $expected, $result );
$this->assertTrue( ! empty( $expected['filter'] ), 'Expected filter prop to be truthy, to handle case where 4.8 is downgraded to 4.7.' );
// Make sure KSES is applying as expected.
@@ -184,7 +411,7 @@ class Test_WP_Widget_Text extends WP_UnitTestCase {
$instance['text'] = '<script>alert( "Howdy!" );</script>';
$expected['text'] = $instance['text'];
$result = $widget->update( $instance, array() );
$this->assertEquals( $result, $expected );
$this->assertEquals( $expected, $result );
remove_filter( 'map_meta_cap', array( $this, 'grant_unfiltered_html_cap' ) );
add_filter( 'map_meta_cap', array( $this, 'revoke_unfiltered_html_cap' ), 10, 2 );
@@ -192,10 +419,62 @@ class Test_WP_Widget_Text extends WP_UnitTestCase {
$instance['text'] = '<script>alert( "Howdy!" );</script>';
$expected['text'] = wp_kses_post( $instance['text'] );
$result = $widget->update( $instance, array() );
$this->assertEquals( $result, $expected );
$this->assertEquals( $expected, $result );
remove_filter( 'map_meta_cap', array( $this, 'revoke_unfiltered_html_cap' ), 10 );
}
/**
* Test update for legacy widgets.
*
* @covers WP_Widget_Text::update
*/
function test_update_legacy() {
$widget = new WP_Widget_Text();
// Updating a widget with explicit filter=true persists with legacy mode.
$instance = array(
'title' => 'Legacy',
'text' => 'Text',
'filter' => true,
);
$result = $widget->update( $instance, array() );
$expected = array_merge( $instance, array(
'legacy' => true,
'filter' => true,
) );
$this->assertEquals( $expected, $result );
// Updating a widget with explicit filter=false persists with legacy mode.
$instance['filter'] = false;
$result = $widget->update( $instance, array() );
$expected = array_merge( $instance, array(
'legacy' => true,
'filter' => false,
) );
$this->assertEquals( $expected, $result );
// Updating a widget in legacy form results in filter=false when checkbox not checked.
$instance['filter'] = true;
$result = $widget->update( $instance, array() );
$expected = array_merge( $instance, array(
'legacy' => true,
'filter' => true,
) );
$this->assertEquals( $expected, $result );
// Updating a widget that previously had legacy form results in filter persisting.
unset( $instance['legacy'] );
$instance['filter'] = true;
$result = $widget->update( $instance, array(
'legacy' => true,
) );
$expected = array_merge( $instance, array(
'legacy' => true,
'filter' => true,
) );
$this->assertEquals( $expected, $result );
}
/**
* Grant unfiltered_html cap via map_meta_cap.
*