diff --git a/tests/phpunit/tests/media.php b/tests/phpunit/tests/media.php index 90b7003..b26bd7e 100644 --- a/tests/phpunit/tests/media.php +++ b/tests/phpunit/tests/media.php @@ -52,7 +52,12 @@ function test_wp_lazy_load_content_media() { $content_unfiltered = sprintf( $content, $img, $img_xhtml, $img_html5, $iframe, $img_eager ); $content_filtered = sprintf( $content, $lazy_img, $lazy_img_xhtml, $lazy_img_html5, $iframe, $img_eager ); - $this->assertSame( $content_filtered, wp_add_lazy_load_attributes( $content_unfiltered ) ); + // Do not add srcset and sizes while testing. + add_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' ); + + $this->assertSame( $content_filtered, wp_filter_content_tags( $content_unfiltered ) ); + + remove_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' ); } /** @@ -70,11 +75,15 @@ function test_wp_lazy_load_content_media_opted_in() { $content_unfiltered = sprintf( $content, $img ); $content_filtered = sprintf( $content, $lazy_img ); + // Do not add srcset and sizes while testing. + add_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' ); + // Enable globally for all tags. add_filter( 'wp_lazy_loading_enabled', '__return_true' ); - $this->assertSame( $content_filtered, wp_add_lazy_load_attributes( $content_unfiltered ) ); + $this->assertSame( $content_filtered, wp_filter_content_tags( $content_unfiltered ) ); remove_filter( 'wp_lazy_loading_enabled', '__return_true' ); + remove_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' ); } /** @@ -88,10 +97,14 @@ function test_wp_lazy_load_content_media_opted_out() { %1$s'; $content = sprintf( $content, $img ); + // Do not add srcset and sizes while testing. + add_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' ); + // Disable globally for all tags. add_filter( 'wp_lazy_loading_enabled', '__return_false' ); - $this->assertSame( $content, wp_add_lazy_load_attributes( $content ) ); + $this->assertSame( $content, wp_filter_content_tags( $content ) ); remove_filter( 'wp_lazy_loading_enabled', '__return_false' ); + remove_filter( 'wp_img_tag_add_srcset_and_sizes_attr', '__return_false' ); } } diff --git a/wp-lazy-loading.php b/wp-lazy-loading.php index 1eefea1..5c5c28b 100644 --- a/wp-lazy-loading.php +++ b/wp-lazy-loading.php @@ -31,14 +31,16 @@ */ function _wp_lazy_loading_initialize_filters() { // The following filters would be merged into core. - foreach ( array( 'the_content', 'the_excerpt', 'comment_text', 'widget_text_content' ) as $filter ) { - // After parsing blocks and shortcodes. - add_filter( $filter, 'wp_add_lazy_load_attributes', 25 ); + foreach ( array( 'the_content', 'the_excerpt', 'widget_text_content' ) as $filter ) { + add_filter( $filter, 'wp_filter_content_tags' ); } // The following filters are only needed while this is a feature plugin. add_filter( 'wp_get_attachment_image_attributes', '_wp_lazy_loading_add_attribute_to_attachment_image' ); add_filter( 'get_avatar', '_wp_lazy_loading_add_attribute_to_avatar' ); + + // The following relevant filter from core should be removed when merged. + remove_filter( 'the_content', 'wp_make_content_images_responsive' ); } add_action( 'plugins_loaded', '_wp_lazy_loading_initialize_filters', 1 ); @@ -102,7 +104,7 @@ function _wp_lazy_loading_add_attribute_to_attachment_image( $attr ) { */ function wp_lazy_loading_enabled( $tag_name, $context ) { // By default add to all 'img' tags. - // See https://github.com/whatwg/html/issues/2806 + // See https://html.spec.whatwg.org/multipage/embedded-content.html#attr-img-loading $default = ( 'img' === $tag_name ); /** @@ -118,57 +120,150 @@ function wp_lazy_loading_enabled( $tag_name, $context ) { } /** - * Add `loading="lazy"` to `img` HTML tags. + * Filters specific tags in post content and modifies their markup. * - * Currently the "loading" attribute is only supported for `img`, and is enabled by default. + * This function adds `srcset`, `sizes`, and `loading` attributes to `img` HTML tags. * * @since (TBD) * + * @see wp_img_tag_add_loading_attr() + * @see wp_img_tag_add_srcset_and_sizes_attr() + * * @param string $content The HTML content to be filtered. * @param string $context Optional. Additional context to pass to the filters. Defaults to `current_filter()` when not set. - * @return string Converted content with 'loading' attributes added to images. + * @return string Converted content with images modified. */ -function wp_add_lazy_load_attributes( $content, $context = null ) { +function wp_filter_content_tags( $content, $context = null ) { if ( null === $context ) { $context = current_filter(); } - if ( ! wp_lazy_loading_enabled( 'img', $context ) ) { + $add_loading_attr = wp_lazy_loading_enabled( 'img', $context ); + + if ( false === strpos( $content, ']+>/', - function( array $matches ) use( $content, $context ) { - if ( ! preg_match( '/\sloading\s*=/', $matches[0] ) ) { - $tag_html = $matches[0]; - - /** - * Filters the `loading` attribute value. Default `lazy`. - * - * Returning `false` or an empty string will not add the attribute. - * Returning `true` will add the default value. - * - * @since (TBD) - * - * @param string $default The filtered value, defaults to `lazy`. - * @param string $tag_html The tag's HTML. - * @param string $content The HTML containing the image tag. - * @param string $context Optional. Additional context. Defaults to `current_filter()`. - */ - $value = apply_filters( 'wp_set_image_loading_attr', 'lazy', $tag_html, $content, $context ); + if ( ! preg_match_all( '/]+>/', $content, $matches ) ) { + return $content; + } + + // List of the unique `img` tags found in $content. + $images = array(); - if ( $value ) { - if ( ! in_array( $value, array( 'lazy', 'eager' ), true ) ) { - $value = 'lazy'; - } + foreach ( $matches[0] as $image ) { + if ( preg_match( '/wp-image-([0-9]+)/i', $image, $class_id ) ) { + $attachment_id = absint( $class_id[1] ); - return str_replace( ' 1 ) { + /* + * Warm the object cache with post and meta information for all found + * images to avoid making individual database calls. + */ + _prime_post_caches( $attachment_ids, false, true ); + } + + foreach ( $images as $image => $attachment_id ) { + $filtered_image = $image; + + // Add 'srcset' and 'sizes' attributes if applicable. + if ( $attachment_id > 0 && false === strpos( $filtered_image, ' srcset=' ) ) { + $filtered_image = wp_img_tag_add_srcset_and_sizes_attr( $filtered_image, $context, $attachment_id ); + } + + // Add 'loading' attribute if applicable. + if ( $add_loading_attr && false === strpos( $filtered_image, ' loading=' ) ) { + $filtered_image = wp_img_tag_add_loading_attr( $filtered_image, $context ); + } + + if ( $filtered_image !== $image ) { + $content = str_replace( $image, $filtered_image, $content ); + } + } + + return $content; +} + +/** + * Adds `loading` attribute to an existing `img` HTML tag. + * + * @since (TBD) + * + * @param string $image The HTML `img` tag where the attribute should be added. + * @param string $context Additional context to pass to the filters. + * @return string Converted `img` tag with `loading` attribute added. + */ +function wp_img_tag_add_loading_attr( $image, $context ) { + /** + * Filters the `loading` attribute value. Default `lazy`. + * + * Returning `false` or an empty string will not add the attribute. + * Returning `true` will add the default value. + * + * @since (TBD) + * + * @param string $value The 'loading' attribute value, defaults to `lazy`. + * @param string $image The HTML 'img' element to be filtered. + * @param string $context Additional context about how the function was called or where the img tag is. + */ + $value = apply_filters( 'wp_img_tag_add_loading_attr', 'lazy', $image, $context ); + + if ( $value ) { + if ( ! in_array( $value, array( 'lazy', 'eager' ), true ) ) { + $value = 'lazy'; + } + + return str_replace( '