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. * * @covers WP_Widget_Text::_register */ function test__register() { set_current_screen( 'widgets.php' ); $widget = new WP_Widget_Text(); $widget->_register(); $this->assertEquals( 10, has_action( 'admin_print_scripts-widgets.php', array( $widget, 'enqueue_admin_scripts' ) ) ); $this->assertEquals( 10, has_action( 'admin_footer-widgets.php', array( 'WP_Widget_Text', 'render_control_template_scripts' ) ) ); $this->assertContains( 'wp.textWidgets.idBases.push( "text" );', wp_scripts()->registered['text-widgets']->extra['after'] ); } /** * Test widget method. * * @covers WP_Widget_Text::widget */ function test_widget() { $widget = new WP_Widget_Text(); $text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n Praesent ut turpis consequat lorem volutpat bibendum vitae vitae ante."; $args = array( 'before_title' => '

', 'after_title' => "

\n", 'before_widget' => '
', 'after_widget' => "
\n", ); add_filter( 'widget_text_content', array( $this, 'filter_widget_text_content' ), 5, 3 ); add_filter( 'widget_text', array( $this, 'filter_widget_text' ), 5, 3 ); // Test with filter=false, implicit legacy mode. $this->widget_text_content_args = null; ob_start(); $instance = array( 'title' => 'Foo', 'text' => $text, 'filter' => false, ); $widget->widget( $args, $instance ); $output = ob_get_clean(); $this->assertNotContains( '

', $output ); $this->assertNotContains( '
', $output ); $this->assertEmpty( $this->widget_text_content_args ); $this->assertNotEmpty( $this->widget_text_args ); $this->assertContains( '[filter:widget_text]', $output ); $this->assertNotContains( '[filter:widget_text_content]', $output ); // Test with filter=true, implicit legacy mode. $this->widget_text_content_args = null; $instance = array( 'title' => 'Foo', 'text' => $text, 'filter' => true, ); ob_start(); $widget->widget( $args, $instance ); $output = ob_get_clean(); $this->assertContains( '

', $output ); $this->assertContains( '
', $output ); $this->assertNotEmpty( $this->widget_text_args ); $this->assertEquals( $instance['text'], $this->widget_text_args[0] ); $this->assertEquals( $instance, $this->widget_text_args[1] ); $this->assertEquals( $widget, $this->widget_text_args[2] ); $this->assertEmpty( $this->widget_text_content_args ); $this->assertContains( '[filter:widget_text]', $output ); $this->assertNotContains( '[filter:widget_text_content]', $output ); // Test with filter=content, the upgraded widget, in 4.8.0 only. $this->widget_text_content_args = null; $instance = array( 'title' => 'Foo', 'text' => $text, 'filter' => 'content', ); $expected_instance = array_merge( $instance, array( 'filter' => true, 'visual' => true, ) ); ob_start(); $widget->widget( $args, $instance ); $output = ob_get_clean(); $this->assertContains( '

', $output ); $this->assertContains( '
', $output ); $this->assertCount( 3, $this->widget_text_args ); $this->assertEquals( $expected_instance['text'], $this->widget_text_args[0] ); $this->assertEquals( $expected_instance, $this->widget_text_args[1] ); $this->assertEquals( $widget, $this->widget_text_args[2] ); $this->assertCount( 3, $this->widget_text_content_args ); $this->assertEquals( $expected_instance['text'] . '[filter:widget_text]', $this->widget_text_content_args[0] ); $this->assertEquals( $expected_instance, $this->widget_text_content_args[1] ); $this->assertEquals( $widget, $this->widget_text_content_args[2] ); $this->assertContains( wpautop( $expected_instance['text'] . '[filter:widget_text][filter:widget_text_content]' ), $output ); // Test with filter=true&visual=true, the upgraded widget, in 4.8.1 and above. $this->widget_text_content_args = null; $instance = array( 'title' => 'Foo', 'text' => $text, 'filter' => true, 'visual' => true, ); $expected_instance = $instance; ob_start(); $widget->widget( $args, $instance ); $output = ob_get_clean(); $this->assertContains( '

', $output ); $this->assertContains( '
', $output ); $this->assertCount( 3, $this->widget_text_args ); $this->assertEquals( $expected_instance['text'], $this->widget_text_args[0] ); $this->assertEquals( $expected_instance, $this->widget_text_args[1] ); $this->assertEquals( $widget, $this->widget_text_args[2] ); $this->assertCount( 3, $this->widget_text_content_args ); $this->assertEquals( $expected_instance['text'] . '[filter:widget_text]', $this->widget_text_content_args[0] ); $this->assertEquals( $expected_instance, $this->widget_text_content_args[1] ); $this->assertEquals( $widget, $this->widget_text_content_args[2] ); $this->assertContains( wpautop( $expected_instance['text'] . '[filter:widget_text][filter:widget_text_content]' ), $output ); // Test with filter=true&visual=true, the upgraded widget, in 4.8.1 and above. $this->widget_text_content_args = null; $instance = array( 'title' => 'Foo', 'text' => $text, 'filter' => true, 'visual' => false, ); $expected_instance = $instance; ob_start(); $widget->widget( $args, $instance ); $output = ob_get_clean(); $this->assertContains( '

', $output ); $this->assertContains( '
', $output ); $this->assertCount( 3, $this->widget_text_args ); $this->assertEquals( $expected_instance['text'], $this->widget_text_args[0] ); $this->assertEquals( $expected_instance, $this->widget_text_args[1] ); $this->assertEquals( $widget, $this->widget_text_args[2] ); $this->assertNull( $this->widget_text_content_args ); $this->assertContains( wpautop( $expected_instance['text'] . '[filter:widget_text]' ), $output ); // Test with filter=false&visual=false, the upgraded widget, in 4.8.1 and above. $this->widget_text_content_args = null; $instance = array( 'title' => 'Foo', 'text' => $text, 'filter' => false, 'visual' => false, ); $expected_instance = $instance; ob_start(); $widget->widget( $args, $instance ); $output = ob_get_clean(); $this->assertNotContains( '

', $output ); $this->assertNotContains( '
', $output ); $this->assertCount( 3, $this->widget_text_args ); $this->assertEquals( $expected_instance['text'], $this->widget_text_args[0] ); $this->assertEquals( $expected_instance, $this->widget_text_args[1] ); $this->assertEquals( $widget, $this->widget_text_args[2] ); $this->assertNull( $this->widget_text_content_args ); $this->assertContains( $expected_instance['text'] . '[filter:widget_text]', $output ); } /** * Example shortcode content to test for wpautop corruption. * * @var string */ protected $example_shortcode_content = "

One\nTwo\n\nThree

\n"; /** * 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' => '

', 'after_title' => "

\n", 'before_widget' => '
', 'after_widget' => "
\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. * * @param string $widget_text The widget content. * @param array $instance Array of settings for the current widget. * @param WP_Widget_Text $widget Current Text widget instance. * @return string Widget text. */ function filter_widget_text( $widget_text, $instance, $widget ) { $this->widget_text_args = func_get_args(); $widget_text .= '[filter:widget_text]'; return $widget_text; } /** * Filters the content of the Text widget to apply changes expected from the visual (TinyMCE) editor. * * @param string $widget_text The widget content. * @param array $instance Array of settings for the current widget. * @param WP_Widget_Text $widget Current Text widget instance. * @return string Widget content. */ function filter_widget_text_content( $widget_text, $instance, $widget ) { $this->widget_text_content_args = func_get_args(); $widget_text .= '[filter:widget_text_content]'; 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( 'visual' => false, ) ); $this->assertTrue( $widget->is_legacy_instance( $instance ), 'Legacy when visual=false prop is present.' ); $instance = array_merge( $base_instance, array( 'visual' => true, ) ); $this->assertFalse( $widget->is_legacy_instance( $instance ), 'Not legacy when visual=true 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 (in WP 4.8.0 only).' ); $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' => "\nOne line", 'filter' => false, ) ); $this->assertFalse( $widget->is_legacy_instance( $instance ), 'Not legacy when there is leading whitespace.' ); $instance = array_merge( $base_instance, array( 'text' => "\nOne line\n\n", 'filter' => false, ) ); $this->assertFalse( $widget->is_legacy_instance( $instance ), 'Not legacy when there is trailing whitespace.' ); $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', 'filter' => true, ) ); $this->assertTrue( $widget->is_legacy_instance( $instance ), 'Legacy when HTML comment is present.' ); // Check text examples that will not migrate to TinyMCE. $legacy_text_examples = array( '', '', "", '', "", "", '', "

\nStay updated with our latest news and specials. We never sell your information and you can unsubscribe at any time.\n

\n\n
\n\t
\n\n\t\t\n\t\t\n\n\t\t\n\n\t
\n
", '', ); 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 Example', 'Img', 'Hello', 'Hello', "", "
    \n
  1. One
  2. \n
  3. One
  4. \n
  5. One
  6. \n
", "Text\n
\nAddendum", "Look at this code:\n\necho 'Hello World!';", ); 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, 'visual' => false, ); $this->assertTrue( $widget->is_legacy_instance( $instance ) ); ob_start(); $widget->form( $instance ); $form = ob_get_clean(); $this->assertContains( 'class="visual" type="hidden" value=""', $form ); $this->assertNotContains( 'class="visual" type="hidden" value="on"', $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->assertContains( 'class="visual" type="hidden" value="on"', $form ); $this->assertNotContains( 'class="visual" type="hidden" value=""', $form ); $instance = array( 'title' => 'Title', 'text' => 'Text', 'filter' => true, ); $this->assertFalse( $widget->is_legacy_instance( $instance ) ); 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 ); $instance = array( 'title' => 'Title', 'text' => 'Text', 'filter' => true, 'visual' => true, ); $this->assertFalse( $widget->is_legacy_instance( $instance ) ); 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 ); } /** * Test update method. * * @covers WP_Widget_Text::update */ function test_update() { $widget = new WP_Widget_Text(); $instance = array( 'title' => "The\nTitle", 'text' => "The\n\nText", 'filter' => true, 'visual' => true, ); wp_set_current_user( $this->factory()->user->create( array( 'role' => 'administrator', ) ) ); $expected = array( 'title' => sanitize_text_field( $instance['title'] ), 'text' => $instance['text'], 'filter' => true, 'visual' => true, ); $result = $widget->update( $instance, array() ); $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.' ); add_filter( 'map_meta_cap', array( $this, 'grant_unfiltered_html_cap' ), 10, 2 ); $this->assertTrue( current_user_can( 'unfiltered_html' ) ); $instance['text'] = ''; $expected['text'] = $instance['text']; $result = $widget->update( $instance, array() ); $this->assertEquals( $expected, $result, 'KSES should apply as expected.' ); remove_filter( 'map_meta_cap', array( $this, 'grant_unfiltered_html_cap' ) ); add_filter( 'map_meta_cap', array( $this, 'revoke_unfiltered_html_cap' ), 10, 2 ); $this->assertFalse( current_user_can( 'unfiltered_html' ) ); $instance['text'] = ''; $expected['text'] = wp_kses_post( $instance['text'] ); $result = $widget->update( $instance, array() ); $this->assertEquals( $expected, $result, 'KSES should not apply since user can unfiltered_html.' ); 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(); // -- $instance = array( 'title' => 'Legacy', 'text' => 'Text', 'filter' => false, ); $result = $widget->update( $instance, array() ); $this->assertEquals( $instance, $result, 'Updating a widget without visual prop and explicit filter=false leaves visual prop absent' ); // -- $instance = array( 'title' => 'Legacy', 'text' => 'Text', 'filter' => true, ); $result = $widget->update( $instance, array() ); $this->assertEquals( $instance, $result, 'Updating a widget without visual prop and explicit filter=true leaves legacy prop absent.' ); // -- $instance = array( 'title' => 'Legacy', 'text' => 'Text', 'visual' => true, ); $old_instance = array_merge( $instance, array( 'filter' => false, ) ); $expected = array_merge( $instance, array( 'filter' => true, ) ); $result = $widget->update( $instance, $old_instance ); $this->assertEquals( $expected, $result, 'Updating a pre-existing widget with visual mode forces filter to be true.' ); // -- $instance = array( 'title' => 'Legacy', 'text' => 'Text', 'filter' => true, ); $old_instance = array_merge( $instance, array( 'visual' => true, ) ); $result = $widget->update( $instance, $old_instance ); $expected = array_merge( $instance, array( 'visual' => true, ) ); $this->assertEquals( $expected, $result, 'Updating a pre-existing visual widget retains visual mode when updated.' ); // -- $instance = array( 'title' => 'Legacy', 'text' => 'Text', ); $old_instance = array_merge( $instance, array( 'visual' => true, ) ); $result = $widget->update( $instance, $old_instance ); $expected = array_merge( $instance, array( 'visual' => true, 'filter' => true, ) ); $this->assertEquals( $expected, $result, 'Updating a pre-existing visual widget retains visual=true and supplies missing filter=true.' ); // -- $instance = array( 'title' => 'Legacy', 'text' => 'Text', 'visual' => true, ); $expected = array_merge( $instance, array( 'filter' => true, ) ); $result = $widget->update( $instance, array() ); $this->assertEquals( $expected, $result, 'Updating a widget with explicit visual=true and absent filter prop causes filter to be set to true.' ); // -- $instance = array( 'title' => 'Legacy', 'text' => 'Text', 'visual' => false, ); $result = $widget->update( $instance, array() ); $expected = array_merge( $instance, array( 'filter' => false, ) ); $this->assertEquals( $expected, $result, 'Updating a widget in legacy mode results in filter=false as if checkbox not checked.' ); // -- $instance = array( 'title' => 'Title', 'text' => 'Text', 'filter' => false, ); $old_instance = array_merge( $instance, array( 'visual' => false, 'filter' => true, ) ); $result = $widget->update( $instance, $old_instance ); $expected = array_merge( $instance, array( 'visual' => false, 'filter' => false, ) ); $this->assertEquals( $expected, $result, 'Updating a widget that previously had legacy form results in filter allowed to be false.' ); // -- $instance = array( 'title' => 'Title', 'text' => 'Text', 'filter' => 'content', ); $result = $widget->update( $instance, array() ); $expected = array_merge( $instance, array( 'filter' => true, 'visual' => true, ) ); $this->assertEquals( $expected, $result, 'Updating a widget that had \'content\' as its filter value persists non-legacy mode. This only existed in WP 4.8.0.' ); // -- $instance = array( 'title' => 'Title', 'text' => 'Text', ); $old_instance = array_merge( $instance, array( 'filter' => 'content', ) ); $result = $widget->update( $instance, $old_instance ); $expected = array_merge( $instance, array( 'visual' => true, 'filter' => true, ) ); $this->assertEquals( $expected, $result, 'Updating a pre-existing widget with the filter=content prop in WP 4.8.0 upgrades to filter=true&visual=true.' ); // -- $instance = array( 'title' => 'Title', 'text' => 'Text', 'filter' => 'content', ); $result = $widget->update( $instance, array() ); $expected = array_merge( $instance, array( 'filter' => true, 'visual' => true, ) ); $this->assertEquals( $expected, $result, 'Updating a widget with filter=content (from WP 4.8.0) upgrades to filter=true&visual=true.' ); } /** * Grant unfiltered_html cap via map_meta_cap. * * @param array $caps Returns the user's actual capabilities. * @param string $cap Capability name. * @return array Caps. */ function grant_unfiltered_html_cap( $caps, $cap ) { if ( 'unfiltered_html' === $cap ) { $caps = array_diff( $caps, array( 'do_not_allow' ) ); $caps[] = 'unfiltered_html'; } return $caps; } /** * Revoke unfiltered_html cap via map_meta_cap. * * @param array $caps Returns the user's actual capabilities. * @param string $cap Capability name. * @return array Caps. */ function revoke_unfiltered_html_cap( $caps, $cap ) { if ( 'unfiltered_html' === $cap ) { $caps = array_diff( $caps, array( 'unfiltered_html' ) ); $caps[] = 'do_not_allow'; } return $caps; } /** * Test enqueue_admin_scripts method. * * @covers WP_Widget_Text::enqueue_admin_scripts */ function test_enqueue_admin_scripts() { set_current_screen( 'widgets.php' ); $widget = new WP_Widget_Text(); $widget->enqueue_admin_scripts(); $this->assertTrue( wp_script_is( 'text-widgets' ) ); } /** * Test render_control_template_scripts method. * * @covers WP_Widget_Text::render_control_template_scripts */ function test_render_control_template_scripts() { ob_start(); WP_Widget_Text::render_control_template_scripts(); $output = ob_get_clean(); $this->assertContains( '