diff --git a/src/wp-includes/default-filters.php b/src/wp-includes/default-filters.php index 8f0237028d..6f64ffab5d 100644 --- a/src/wp-includes/default-filters.php +++ b/src/wp-includes/default-filters.php @@ -170,6 +170,8 @@ add_filter( 'widget_text_content', 'wptexturize' ); add_filter( 'widget_text_content', 'convert_smilies', 20 ); add_filter( 'widget_text_content', 'wpautop' ); +add_filter( 'widget_html_code_content', 'balanceTags' ); + add_filter( 'date_i18n', 'wp_maybe_decline_date' ); // RSS filters diff --git a/src/wp-includes/default-widgets.php b/src/wp-includes/default-widgets.php index 87ad9dbfb3..32a908ac11 100644 --- a/src/wp-includes/default-widgets.php +++ b/src/wp-includes/default-widgets.php @@ -60,3 +60,6 @@ require_once( ABSPATH . WPINC . '/widgets/class-wp-widget-tag-cloud.php' ); /** WP_Nav_Menu_Widget class */ require_once( ABSPATH . WPINC . '/widgets/class-wp-nav-menu-widget.php' ); + +/** WP_Widget_HTML_Code class */ +require_once( ABSPATH . WPINC . '/widgets/class-wp-widget-html-code.php' ); diff --git a/src/wp-includes/widgets.php b/src/wp-includes/widgets.php index caa575b149..b44048d641 100644 --- a/src/wp-includes/widgets.php +++ b/src/wp-includes/widgets.php @@ -1474,6 +1474,8 @@ function wp_widgets_init() { register_widget( 'WP_Nav_Menu_Widget' ); + register_widget( 'WP_Widget_HTML_Code' ); + /** * Fires after all default WordPress widgets have been registered. * diff --git a/src/wp-includes/widgets/class-wp-widget-html-code.php b/src/wp-includes/widgets/class-wp-widget-html-code.php new file mode 100644 index 0000000000..d16900183f --- /dev/null +++ b/src/wp-includes/widgets/class-wp-widget-html-code.php @@ -0,0 +1,139 @@ + '', + 'content' => '', + ); + + /** + * Sets up a new HTML Code widget instance. + * + * @since 4.8.1 + */ + public function __construct() { + $widget_ops = array( + 'classname' => 'widget_html_code', + 'description' => __( 'Arbitrary HTML code.' ), + 'customize_selective_refresh' => true, + ); + $control_ops = array(); + parent::__construct( 'html_code', __( 'HTML Code' ), $widget_ops, $control_ops ); + } + + /** + * Outputs the content for the current HTML Code widget instance. + * + * @since 4.8.1 + * + * @param array $args Display arguments including 'before_title', 'after_title', + * 'before_widget', and 'after_widget'. + * @param array $instance Settings for the current HTML Code widget instance. + */ + public function widget( $args, $instance ) { + + $instance = array_merge( $this->default_instance, $instance ); + + /** This filter is documented in wp-includes/widgets/class-wp-widget-pages.php */ + $title = apply_filters( 'widget_title', $instance['title'], $instance, $this->id_base ); + + $content = $instance['content']; + + /** + * Filters the content of the HTML Code widget. + * + * @since 4.8.1 + * + * @param string $content The widget content. + * @param array $instance Array of settings for the current widget. + * @param WP_Widget_HTML_Code $this Current HTML Code widget instance. + */ + $content = apply_filters( 'widget_html_code_content', $content, $instance, $this ); + + echo $args['before_widget']; + if ( ! empty( $title ) ) { + echo $args['before_title'] . $title . $args['after_title']; + } + echo $content; + echo $args['after_widget']; + } + + /** + * Handles updating settings for the current HTML Code widget instance. + * + * @since 4.8.1 + * + * @param array $new_instance New settings for this instance as input by the user via + * WP_Widget::form(). + * @param array $old_instance Old settings for this instance. + * @return array Settings to save or bool false to cancel saving. + */ + public function update( $new_instance, $old_instance ) { + $instance = array_merge( $this->default_instance, $old_instance ); + $instance['title'] = sanitize_text_field( $new_instance['title'] ); + if ( current_user_can( 'unfiltered_html' ) ) { + $instance['content'] = $new_instance['content']; + } else { + $instance['content'] = wp_kses_post( $new_instance['content'] ); + } + return $instance; + } + + /** + * Outputs the HTML Code widget settings form. + * + * @since 4.8.1 + * + * @param array $instance Current instance. + * @returns void + */ + public function form( $instance ) { + $instance = wp_parse_args( (array) $instance, $this->default_instance ); + ?> +
+ + +
+ ++ + +
+ + + + +
+
+ , ', $disallowed_html ); ?>
+
', $output );
+ $this->assertNotContains( '
', $output );
+ $this->assertNotContains( '', $output );
+ $this->assertEquals( $instance, $this->widget_html_code_content_args[1] );
+ $this->assertSame( $widget, $this->widget_html_code_content_args[2] );
+ remove_filter( 'widget_html_code_content', array( $this, 'filter_widget_html_code_content' ), 5, 3 );
+
+ update_option( 'use_balanceTags', 1 );
+ ob_start();
+ $widget->widget( $args, $instance );
+ $output = ob_get_clean();
+ $this->assertContains( '', $output );
+ }
+
+ /**
+ * Filters the content of the HTML Code widget.
+ *
+ * @param string $widget_content The widget content.
+ * @param array $instance Array of settings for the current widget.
+ * @param WP_Widget_HTML_Code $widget Current HTML Code widget instance.
+ * @return string Widget content.
+ */
+ function filter_widget_html_code_content( $widget_content, $instance, $widget ) {
+ $this->widget_html_code_content_args = func_get_args();
+
+ $widget_content .= '[filter:widget_html_code_content]';
+ return $widget_content;
+ }
+
+ /**
+ * Test update method.
+ *
+ * @covers WP_Widget_HTML_Code::update
+ */
+ function test_update() {
+ $widget = new WP_Widget_HTML_Code();
+ $instance = array(
+ 'title' => "The\nTitle",
+ 'content' => "The\n\nCode",
+ );
+
+ wp_set_current_user( $this->factory()->user->create( array(
+ 'role' => 'administrator',
+ ) ) );
+
+ // Should return valid instance.
+ $expected = array(
+ 'title' => sanitize_text_field( $instance['title'] ),
+ 'content' => $instance['content'],
+ );
+ $result = $widget->update( $instance, array() );
+ $this->assertEquals( $result, $expected );
+
+ // Make sure KSES is applying as expected.
+ add_filter( 'map_meta_cap', array( $this, 'grant_unfiltered_html_cap' ), 10, 2 );
+ $this->assertTrue( current_user_can( 'unfiltered_html' ) );
+ $instance['content'] = '';
+ $expected['content'] = $instance['content'];
+ $result = $widget->update( $instance, array() );
+ $this->assertEquals( $result, $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['content'] = '';
+ $expected['content'] = wp_kses_post( $instance['content'] );
+ $result = $widget->update( $instance, array() );
+ $this->assertEquals( $result, $expected );
+ remove_filter( 'map_meta_cap', array( $this, 'revoke_unfiltered_html_cap' ), 10 );
+ }
+
+ /**
+ * 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;
+ }
+}