Skip to content
This repository was archived by the owner on Dec 23, 2020. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions tests/phpunit/tests/media.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ 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 ) );
$this->assertSame( $content_filtered, wp_filter_content_tags( $content_unfiltered ) );
}

/**
Expand All @@ -73,7 +73,7 @@ function test_wp_lazy_load_content_media_opted_in() {
// 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' );
}

Expand All @@ -91,7 +91,7 @@ function test_wp_lazy_load_content_media_opted_out() {
// 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' );
}
}
142 changes: 105 additions & 37 deletions wp-lazy-loading.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,15 @@
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 );
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' );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wp_make_content_images_responsive is used in a lot of plugins, some with > 1m installs. Removing or changing how it works is not a good idea.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It wouldn't be changed, it just would no longer be used by core. Also I'm curious how it is being used by plugins?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly to run content through it: https://wpdirectory.net/search/01E0PTA8MSD8ZHCC7V4QDG9EV3 but some may be removing it, which will fail if it is not used in core.

Also deprecating a "popular" function is generally not a good idea. I know, sometimes there's no way around it, but not convinced this case qualifies :) (There are about 200 plugins that use the function. If we rename and deprecate it, they will have to "catch up" or will fill millions of error logs with "deprecated" entries.)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the purposes of plugin development, the function needs to be replaced.

This discussion can be moved to an issue for wider input from the media team closer to any merge proposal.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@peterwilsoncc How do you mean "the function needs to be replaced", can you clarify? Not sure I understand.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@felixarntz I mean if you'd like to use a single regex while developing this plugin, you'll need to remove the core filter and replace it with a duplicate in this plugin (as the team has done in this PR).

This allows the naming/deprecation discussion to happen outside of this PR.

}

add_action( 'plugins_loaded', '_wp_lazy_loading_initialize_filters', 1 );
Expand Down Expand Up @@ -118,57 +120,123 @@ 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.
*
* This function performs the following replacements:
*
* Currently the "loading" attribute is only supported for `img`, and is enabled by default.
* * add 'srcset' and 'sizes' attributes to 'img' tags
* * add 'loading' attributes to 'img' tags
*
* @since (TBD)
*
* @see wp_image_add_srcset_and_sizes()
* @see wp_image_add_loading()
*
* @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_srcset_sizes = 'the_content' === $context;
$add_loading_attr = wp_lazy_loading_enabled( 'img', $context );

if ( false === strpos( $content, '<img' ) || ( ! $add_srcset_sizes && ! $add_loading_attr ) ) {
return $content;
}

return preg_replace_callback(
'/<img\s[^>]+>/',
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( '/<img\s[^>]+>/', $content, $matches ) ) {
return $content;
}

$selected_images = array();
$attachment_ids = array();

foreach ( $matches[0] as $image ) {
if ( preg_match( '/wp-image-([0-9]+)/i', $image, $class_id ) ) {
$attachment_id = absint( $class_id[1] );

if ( $value ) {
if ( ! in_array( $value, array( 'lazy', 'eager' ), true ) ) {
$value = 'lazy';
}
if ( $attachment_id ) {
/*
* If exactly the same image tag is used more than once, overwrite it.
* All identical tags will be replaced later with 'str_replace()'.
*/
$selected_images[ $image ] = $attachment_id;

return str_replace( '<img', '<img loading="' . $value . '"', $tag_html );
}
// Overwrite the ID when the same image is included more than once.
$attachment_ids[ $attachment_id ] = true;
}
}
}

if ( count( $attachment_ids ) > 1 ) {
/*
* Warm the object cache with post and meta information for all found
* images to avoid making individual database calls.
*/
_prime_post_caches( array_keys( $attachment_ids ), false, true );
}

foreach ( $selected_images as $image => $attachment_id ) {
$image_meta = wp_get_attachment_metadata( $attachment_id );
$filtered_image = $image;

// Add 'srcset' and 'sizes' attributes if applicable.
if ( $add_srcset_sizes && false === strpos( $filtered_image, ' srcset=' ) ) {
$filtered_image = wp_image_add_srcset_and_sizes( $filtered_image, $image_meta, $attachment_id );
}

// Add 'loading' attribute if applicable.
if ( $add_loading_attr && false === strpos( $filtered_image, ' loading=' ) ) {
$filtered_image = wp_image_add_loading_attr( $filtered_image, $image_meta, $attachment_id, $content, $context );
}

$content = str_replace( $image, $filtered_image, $content );
}

return $content;
}

/**
* Adds a 'loading' attribute to an existing 'img' element.
*
* @since (TBD)
*
* @param string $image An HTML 'img' element to be filtered.
* @param array $image_meta The image meta data as returned by 'wp_get_attachment_metadata()'.
* @param int $attachment_id Image attachment ID.
* @param string $content The HTML content that the 'img' element is part of.
* @param string $context Additional context to pass to the filters.
* @return string Converted 'img' element with 'loading' attribute added.
*/
function wp_image_add_loading_attr( $image, $image_meta, $attachment_id, $content, $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 'img' tag's HTML.
* @param array|null $image_meta The image meta data as returned by wp_get_attachment_metadata() or null.
* @param int $attachment_id Image attachment ID of the original image or 0.
* @param string $content The HTML containing the image tag.
* @param string $context Additional context, typically the current filter.
*/
$value = apply_filters( 'wp_image_add_loading_attr', 'lazy', $image, $image_meta, $attachment_id, $content, $context );

if ( $value ) {
if ( ! in_array( $value, array( 'lazy', 'eager' ), true ) ) {
$value = 'lazy';
}

return str_replace( '<img', '<img loading="' . $value . '"', $image );
}

return $matches[0];
},
$content
);
return $image;
}