diff --git a/includes/class-amp-theme-support.php b/includes/class-amp-theme-support.php index c9154f33454..16a8d5d0c01 100644 --- a/includes/class-amp-theme-support.php +++ b/includes/class-amp-theme-support.php @@ -248,6 +248,7 @@ public static function add_hooks() { add_filter( 'cancel_comment_reply_link', array( __CLASS__, 'filter_cancel_comment_reply_link' ), 10, 3 ); add_action( 'comment_form', array( __CLASS__, 'amend_comment_form' ), 100 ); remove_action( 'comment_form', 'wp_comment_form_unfiltered_html_nonce' ); + add_filter( 'wp_kses_allowed_html', array( __CLASS__, 'whitelist_layout_in_wp_kses_allowed_html' ), 10 ); if ( AMP_Validation_Utils::should_validate_response() ) { AMP_Validation_Utils::add_validation_hooks(); @@ -1113,6 +1114,22 @@ public static function prepare_response( $response, $args = array() ) { return $response; } + /** + * Adds 'data-amp-layout' to the allowed attributes for wp_kses(). + * + * @since 0.7 + * + * @param array $context Allowed tags and their allowed attributes. + * @return array $context Filtered allowed tags and attributes. + */ + public static function whitelist_layout_in_wp_kses_allowed_html( $context ) { + if ( ! empty( $context['img']['width'] ) && ! empty( $context['img']['height'] ) ) { + $context['img']['data-amp-layout'] = true; + } + + return $context; + } + /** * Enqueue AMP assets if this is an AMP endpoint. * diff --git a/includes/sanitizers/class-amp-base-sanitizer.php b/includes/sanitizers/class-amp-base-sanitizer.php index cecf6325ae7..0fc9a697085 100644 --- a/includes/sanitizers/class-amp-base-sanitizer.php +++ b/includes/sanitizers/class-amp-base-sanitizer.php @@ -201,7 +201,7 @@ public function sanitize_dimension( $value, $dimension ) { } /** - * Enforce fixed height. + * Sets the layout, and possibly the 'height' and 'width' attributes. * * @param string[] $attributes { * Attributes. @@ -214,12 +214,11 @@ public function sanitize_dimension( $value, $dimension ) { * } * @return string[] */ - public function enforce_fixed_height( $attributes ) { + public function set_layout( $attributes ) { if ( empty( $attributes['height'] ) ) { unset( $attributes['width'] ); $attributes['height'] = self::FALLBACK_HEIGHT; } - if ( empty( $attributes['width'] ) ) { $attributes['layout'] = 'fixed-height'; } @@ -227,47 +226,6 @@ public function enforce_fixed_height( $attributes ) { return $attributes; } - /** - * This is our workaround to enforce max sizing with layout=responsive. - * - * We want elements to not grow beyond their width and shrink to fill the screen on viewports smaller than their width. - * - * See https://github.com/ampproject/amphtml/issues/1280#issuecomment-171533526 - * See https://github.com/Automattic/amp-wp/issues/101 - * - * @param string[] $attributes { - * Attributes. - * - * @type int $height - * @type int $width - * @type string $sizes - * @type string $class - * @type string $layout - * } - * @return string[] - */ - public function enforce_sizes_attribute( $attributes ) { - if ( ! isset( $attributes['width'], $attributes['height'] ) ) { - return $attributes; - } - - $max_width = $attributes['width']; - if ( isset( $this->args['content_max_width'] ) && $max_width >= $this->args['content_max_width'] ) { - $max_width = $this->args['content_max_width']; - } - - // Allows floats and integers but prevents negative values. - // Uses string format to prevent additional modification. - $attributes['sizes'] = sprintf( - '(min-width: %1$spx) %1$spx, 100vw', - max( 0, floatval( $max_width ) ) - ); - - $this->add_or_append_attribute( $attributes, 'class', 'amp-wp-enforced-sizes' ); - - return $attributes; - } - /** * Adds or appends key and value to list of attributes * diff --git a/includes/sanitizers/class-amp-iframe-sanitizer.php b/includes/sanitizers/class-amp-iframe-sanitizer.php index 9dac35a8a3e..07c07875f72 100644 --- a/includes/sanitizers/class-amp-iframe-sanitizer.php +++ b/includes/sanitizers/class-amp-iframe-sanitizer.php @@ -79,9 +79,11 @@ public function sanitize() { } $this->did_convert_elements = true; - - $new_attributes = $this->enforce_fixed_height( $new_attributes ); - $new_attributes = $this->enforce_sizes_attribute( $new_attributes ); + $new_attributes = $this->set_layout( $new_attributes ); + if ( empty( $new_attributes['layout'] ) && ! empty( $new_attributes['width'] ) && ! empty( $new_attributes['height'] ) ) { + $new_attributes['layout'] = 'intrinsic'; + $this->add_or_append_attribute( $new_attributes, 'class', 'amp-wp-enforced-sizes' ); + } $new_node = AMP_DOM_Utils::create_node( $this->dom, 'amp-iframe', $new_attributes ); @@ -126,8 +128,7 @@ public function sanitize() { * @return array Returns HTML attributes; removes any not specifically declared above from input. */ private function filter_attributes( $attributes ) { - $out = array(); - $out['style'] = 'max-width:100%'; // AMP_Style_Sanitizer will move this to the amp-custom style. + $out = array(); foreach ( $attributes as $name => $value ) { switch ( $name ) { diff --git a/includes/sanitizers/class-amp-img-sanitizer.php b/includes/sanitizers/class-amp-img-sanitizer.php index e4a99512dea..e3fa9a878c3 100644 --- a/includes/sanitizers/class-amp-img-sanitizer.php +++ b/includes/sanitizers/class-amp-img-sanitizer.php @@ -119,7 +119,6 @@ private function filter_attributes( $attributes ) { case 'alt': case 'class': case 'srcset': - case 'sizes': case 'on': case 'attribution': $out[ $name ] = $value; @@ -130,6 +129,10 @@ private function filter_attributes( $attributes ) { $out[ $name ] = $this->sanitize_dimension( $value, $name ); break; + case 'data-amp-layout': + $out['layout'] = $value; + break; + default: break; } @@ -204,7 +207,10 @@ private function adjust_and_replace_nodes_in_array_map( $node_lists ) { private function adjust_and_replace_node( $node ) { $old_attributes = AMP_DOM_Utils::get_node_attributes_as_assoc_array( $node ); $new_attributes = $this->filter_attributes( $old_attributes ); - $new_attributes = $this->enforce_sizes_attribute( $new_attributes ); + $this->add_or_append_attribute( $new_attributes, 'class', 'amp-wp-enforced-sizes' ); + if ( empty( $new_attributes['layout'] ) && ! empty( $new_attributes['height'] ) && ! empty( $new_attributes['width'] ) ) { + $new_attributes['layout'] = 'intrinsic'; + } if ( $this->is_gif_url( $new_attributes['src'] ) ) { $this->did_convert_elements = true; diff --git a/includes/sanitizers/class-amp-video-sanitizer.php b/includes/sanitizers/class-amp-video-sanitizer.php index 1dadfcc00eb..0512da17754 100644 --- a/includes/sanitizers/class-amp-video-sanitizer.php +++ b/includes/sanitizers/class-amp-video-sanitizer.php @@ -49,9 +49,10 @@ public function sanitize() { $old_attributes = AMP_DOM_Utils::get_node_attributes_as_assoc_array( $node ); $new_attributes = $this->filter_attributes( $old_attributes ); - - $new_attributes = $this->enforce_fixed_height( $new_attributes ); - $new_attributes = $this->enforce_sizes_attribute( $new_attributes ); + $new_attributes = $this->set_layout( $new_attributes ); + if ( empty( $new_attributes['layout'] ) && ! empty( $new_attributes['width'] ) && ! empty( $new_attributes['height'] ) ) { + $new_attributes['layout'] = 'responsive'; + } $new_node = AMP_DOM_Utils::create_node( $this->dom, 'amp-video', $new_attributes ); @@ -124,8 +125,7 @@ public function sanitize() { * @return array Returns HTML attributes; removes any not specifically declared above from input. */ private function filter_attributes( $attributes ) { - $out = array(); - $out['style'] = 'max-width:100%'; // Note that this will get moved to amp-custom style by AMP_Style_Sanitizer. + $out = array(); foreach ( $attributes as $name => $value ) { switch ( $name ) { diff --git a/tests/test-amp-iframe-sanitizer.php b/tests/test-amp-iframe-sanitizer.php index d451f4715be..be2f00becfd 100644 --- a/tests/test-amp-iframe-sanitizer.php +++ b/tests/test-amp-iframe-sanitizer.php @@ -10,57 +10,57 @@ public function get_data() { 'simple_iframe' => array( '', - '', + '', ), 'force_https' => array( '', - '', + '', ), 'iframe_without_dimensions' => array( '', - '', + '', ), 'iframe_with_height_only' => array( '', - '', + '', ), 'iframe_with_width_only' => array( '', - '', + '', ), 'iframe_with_invalid_frameborder' => array( '', - '', + '', ), 'iframe_with_1_frameborder' => array( '', - '', + '', ), 'simple_iframe_with_sandbox' => array( '', - '', + '', ), 'iframe_with_blacklisted_attribute' => array( '', - '', + '', ), 'iframe_with_sizes_attribute_is_overridden' => array( - '', - '', + '', + '', ), 'iframe_with_protocol_relative_url' => array( '', - '', + '', ), 'multiple_same_iframe' => array( @@ -69,7 +69,7 @@ public function get_data() { ', - '', + '', ), 'multiple_different_iframes' => array( @@ -78,19 +78,19 @@ public function get_data() { ', - '', + '', ), 'iframe_in_p_tag' => array( '

', - '', + '', ), 'multiple_iframes_in_p_tag' => array( '

', - '', + '', ), 'multiple_iframes_and_contents_in_p_tag' => array( '

contents

', - '

contents

', + '

contents

', ), ); } @@ -160,7 +160,7 @@ public function test_get_scripts__did_convert() { public function test__args__placeholder() { $source = ''; - $expected = '
'; + $expected = '
'; $dom = AMP_DOM_Utils::get_dom_from_content( $source ); $sanitizer = new AMP_Iframe_Sanitizer( $dom, array( diff --git a/tests/test-amp-img-sanitizer.php b/tests/test-amp-img-sanitizer.php index c9c02a90633..0f15018618c 100644 --- a/tests/test-amp-img-sanitizer.php +++ b/tests/test-amp-img-sanitizer.php @@ -27,6 +27,11 @@ public function get_data() { '

', ), + 'image_with_layout' => array( + '', + '', + ), + 'image_with_spaces_only_src' => array( '

', '

', @@ -34,77 +39,77 @@ public function get_data() { 'image_with_empty_width_and_height' => array( '

', - '

', + '

', ), 'image_with_empty_width' => array( '

', - '

', + '

', ), 'image_with_empty_height' => array( '

', - '

', + '

', ), 'image_with_zero_width' => array( '

', - '

', + '

', ), 'image_with_zero_width_and_height' => array( '

', - '

', + '

', ), 'image_with_decimal_width' => array( '

', - '

', + '

', ), 'image_with_self_closing_tag' => array( 'Placeholder!', - '', + '', ), 'image_with_no_end_tag' => array( 'Placeholder!', - '', + '', ), 'image_with_end_tag' => array( 'Placeholder!', - '', + '', ), 'image_with_on_attribute' => array( '', - '', + '', ), 'image_with_blacklisted_attribute' => array( '', - '', + '', ), 'image_with_no_dimensions_is_forced_dimensions' => array( '', - '', + '', ), 'image_with_sizes_attribute_is_overridden' => array( - '', - '', + '', + '', ), 'gif_image_conversion' => array( 'Placeholder!', - '', + '', ), 'gif_image_url_with_querystring' => array( 'Placeholder!', - '', + '', ), 'multiple_same_image' => array( @@ -113,7 +118,7 @@ public function get_data() { ', - '', + '', ), 'multiple_different_images' => array( @@ -121,7 +126,7 @@ public function get_data() { ', - '', + '', ), ); } diff --git a/tests/test-amp-video-sanitizer.php b/tests/test-amp-video-sanitizer.php index e5cb4308651..5a0f989db05 100644 --- a/tests/test-amp-video-sanitizer.php +++ b/tests/test-amp-video-sanitizer.php @@ -10,42 +10,42 @@ public function get_data() { 'simple_video' => array( '', - '', + '', ), 'video_without_dimensions' => array( '', - '', + '', ), 'autoplay_attribute' => array( '', - '', + '', ), 'autoplay_attribute__false' => array( '', - '', + '', ), 'video_with_whitelisted_attributes__enabled' => array( '', - '', + '', ), 'video_with_whitelisted_attributes__disabled' => array( '', - '', + '', ), 'video_with_blacklisted_attribute' => array( '', - '', + '', ), 'video_with_sizes_attribute_is_overridden' => array( - '', - '', + '', + '', ), 'video_with_children' => array( @@ -53,7 +53,7 @@ public function get_data() { ', - '', + '', ), 'multiple_same_video' => array( @@ -61,19 +61,19 @@ public function get_data() { ', - '', + '', ), 'multiple_different_videos' => array( ' ', - '', + '', ), 'https_not_required' => array( '', - '', + '', ), ); } diff --git a/tests/test-class-amp-base-sanitizer.php b/tests/test-class-amp-base-sanitizer.php index f3cf1db37c7..8902bb297cf 100644 --- a/tests/test-class-amp-base-sanitizer.php +++ b/tests/test-class-amp-base-sanitizer.php @@ -13,136 +13,22 @@ class AMP_Base_Sanitizer_Test extends WP_UnitTestCase { /** - * Get enforce sizes data. + * Gets data for test_set_layout(). * - * @return array Data + * @return array */ - public function get_enforce_sizes_data() { - return array( - 'already_has_sizes' => array( - array( - 'sizes' => 'blah', - ), - array( - 'sizes' => 'blah', - ), - ), - - 'empty' => array( - array(), - array(), - ), - - 'no_width' => array( - array( - 'height' => 100, - ), - array( - 'height' => 100, - ), - ), - - 'no_height' => array( - array( - 'width' => 200, - ), - array( - 'width' => 200, - ), - ), - - 'enforce_sizes_no_class' => array( - array( - 'width' => 200, - 'height' => 100, - ), - array( - 'width' => 200, - 'height' => 100, - 'sizes' => '(min-width: 200px) 200px, 100vw', - 'class' => 'amp-wp-enforced-sizes', - ), - ), - - 'enforce_sizes_has_class' => array( - array( - 'width' => 200, - 'height' => 100, - 'class' => 'my-class', - ), - array( - 'width' => 200, - 'height' => 100, - 'sizes' => '(min-width: 200px) 200px, 100vw', - 'class' => 'my-class amp-wp-enforced-sizes', - ), - ), - - 'enforce_sizes_with_bigger_content_max_width' => array( - array( - 'width' => 250, - 'height' => 100, - ), - array( - 'width' => 250, - 'height' => 100, - 'sizes' => '(min-width: 250px) 250px, 100vw', - 'class' => 'amp-wp-enforced-sizes', - ), - array( - 'content_max_width' => 500, - ), - ), - - 'enforce_sizes_with_smaller_content_max_width' => array( - array( - 'width' => 800, - 'height' => 350, - ), - array( - 'width' => 800, - 'height' => 350, - 'sizes' => '(min-width: 675px) 675px, 100vw', - 'class' => 'amp-wp-enforced-sizes', - ), - array( - 'content_max_width' => 675, - ), - ), - ); - } - - /** - * Test AMP_Base_Sanitizer::enforce_sizes_attribute(). - * - * @dataProvider get_enforce_sizes_data - * @param array $source_attributes Source Attrs. - * @param array $expected_attributes Expected Attrs. - * @param array $args Args. - * @covers AMP_Base_Sanitizer::enforce_sizes_attribute() - */ - public function test_enforce_sizes_attribute( $source_attributes, $expected_attributes, $args = array() ) { - $sanitizer = new AMP_Test_Stub_Sanitizer( new DOMDocument(), $args ); - $returned_attributes = $sanitizer->enforce_sizes_attribute( $source_attributes ); - - $this->assertEquals( $expected_attributes, $returned_attributes ); - } - - /** - * Get enforce fixed data. - * - * @return array Data. - */ - public function get_enforce_fixed_data() { + public function get_data() { return array( 'both_dimensions_included' => array( array( 'width' => 100, 'height' => 100, + 'layout' => 'responsive', ), array( 'width' => 100, 'height' => 100, + 'layout' => 'responsive', ), ), @@ -184,22 +70,32 @@ public function get_enforce_fixed_data() { 'layout' => 'fixed-height', ), ), + + 'no_layout_specified' => array( + array( + 'width' => 100, + 'height' => 100, + ), + array( + 'width' => 100, + 'height' => 100, + ), + ), ); } /** - * Test AMP_Base_Sanitizer::enforce_fixed_height(). + * Test AMP_Base_Sanitizer::set_layout(). * - * @dataProvider get_enforce_fixed_data + * @dataProvider get_data * @param array $source_attributes Source Attrs. * @param array $expected_attributes Expected Attrs. * @param array $args Args. * @covers AMP_Base_Sanitizer::enforce_fixed_height() */ - public function test_enforce_fixed_height( $source_attributes, $expected_attributes, $args = array() ) { + public function test_set_layout( $source_attributes, $expected_attributes, $args = array() ) { $sanitizer = new AMP_Test_Stub_Sanitizer( new DOMDocument(), $args ); - $returned_attributes = $sanitizer->enforce_fixed_height( $source_attributes ); - + $returned_attributes = $sanitizer->set_layout( $source_attributes ); $this->assertEquals( $expected_attributes, $returned_attributes ); } diff --git a/tests/test-class-amp-theme-support.php b/tests/test-class-amp-theme-support.php index 597375b3990..cc551d75a16 100644 --- a/tests/test-class-amp-theme-support.php +++ b/tests/test-class-amp-theme-support.php @@ -1074,4 +1074,38 @@ public function test_enqueue_assets() { $this->assertTrue( in_array( $script_slug, wp_scripts()->queue, true ) ); $this->assertTrue( in_array( $style_slug, wp_styles()->queue, true ) ); } + + /** + * Test AMP_Theme_Support::whitelist_layout_in_wp_kses_allowed_html(). + * + * @see AMP_Theme_Support::whitelist_layout_in_wp_kses_allowed_html() + */ + public function test_whitelist_layout_in_wp_kses_allowed_html() { + $attribute = 'data-amp-layout'; + $image_no_dimensions = array( + 'img' => array( + $attribute => true, + ), + ); + $image_with_dimensions = array_merge( + $image_no_dimensions, + array( + 'height' => '100', + 'width' => '100', + ) + ); + + $this->assertEquals( array(), AMP_Theme_Support::whitelist_layout_in_wp_kses_allowed_html( array() ) ); + $this->assertEquals( $image_no_dimensions, AMP_Theme_Support::whitelist_layout_in_wp_kses_allowed_html( $image_no_dimensions ) ); + + $context = AMP_Theme_Support::whitelist_layout_in_wp_kses_allowed_html( $image_with_dimensions ); + $this->assertTrue( $context['img'][ $attribute ] ); + + $context = AMP_Theme_Support::whitelist_layout_in_wp_kses_allowed_html( $image_with_dimensions ); + $this->assertTrue( $context['img'][ $attribute ] ); + + add_filter( 'wp_kses_allowed_html', 'AMP_Theme_Support::whitelist_layout_in_wp_kses_allowed_html', 10, 2 ); + $image = ''; + $this->assertEquals( $image, wp_kses_post( $image ) ); + } }