Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add picture element support #73

Merged
merged 86 commits into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from 77 commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
0d3484c
Begin work on picture element support.
adamsilverstein Dec 13, 2021
f29a491
Improve picture construction
adamsilverstein Dec 30, 2021
81dfa5e
Cleanup, docs
adamsilverstein Dec 30, 2021
bca723c
Doc block improvements, cleanup
adamsilverstein Dec 30, 2021
5d7b562
Move module into images folder
adamsilverstein Dec 31, 2021
f4abeed
clarify default is for testing
adamsilverstein Dec 31, 2021
89cf629
Use original image for fallback
adamsilverstein Dec 31, 2021
d4b710d
PHPCS cleanup
adamsilverstein Dec 31, 2021
8b32473
Add some demo picture elements to test approach
adamsilverstein Jan 3, 2022
1ebc2ec
start updating picture element support for new structure
adamsilverstein May 14, 2024
74b66c8
fixes for phpstan
adamsilverstein May 21, 2024
c0bf335
Use picture element by default if the theme declares support for it
adamsilverstein May 21, 2024
b9624a0
correct typo in function name
adamsilverstein May 21, 2024
d41a7f3
fixes for linter
adamsilverstein May 22, 2024
86602fb
cleanup
adamsilverstein May 23, 2024
a46ad9d
Merge branch 'trunk' into add/picture-support
adamsilverstein May 23, 2024
3ff5d6f
Merge branch 'trunk' into add/picture-support
adamsilverstein May 23, 2024
03f710f
Correct logic in webp_uploads_picture_element_enabled
adamsilverstein May 23, 2024
0360efe
Always hook picture support, bail early if disabled
adamsilverstein May 23, 2024
8c21fa2
Add initial picture element test
adamsilverstein May 23, 2024
cd6f975
Add additional test cases for picture element
adamsilverstein May 23, 2024
3a93d76
skip picture element test if server lacks webp support
adamsilverstein May 23, 2024
48573af
rename filter
adamsilverstein May 23, 2024
ba366a9
Merge branch 'trunk' into add/picture-support
adamsilverstein May 24, 2024
7eeb3b8
Merge branch 'trunk' into add/picture-support
adamsilverstein May 24, 2024
d49e704
Update plugins/webp-uploads/helper.php
adamsilverstein May 24, 2024
9bd5be8
only add filter if feature enabled
adamsilverstein May 24, 2024
25355a8
Update plugins/webp-uploads/picture-element.php
adamsilverstein May 24, 2024
43f8533
Update plugins/webp-uploads/picture-element.php
adamsilverstein May 24, 2024
21a3ad3
Update plugins/webp-uploads/picture-element.php
adamsilverstein May 24, 2024
4b657a6
Update plugins/webp-uploads/picture-element.php
adamsilverstein May 24, 2024
cf8a7a8
Update plugins/webp-uploads/picture-element.php
adamsilverstein May 24, 2024
7d884ec
Update plugins/webp-uploads/picture-element.php
adamsilverstein May 24, 2024
a934b0c
Update plugins/webp-uploads/picture-element.php
adamsilverstein May 24, 2024
2d0b40b
Update plugins/webp-uploads/picture-element.php
adamsilverstein May 24, 2024
14fd90f
Update plugins/webp-uploads/settings.php
adamsilverstein May 24, 2024
42a4713
Move PHPCS shared ruleset to tools dir
thelovekesh May 24, 2024
c99c92e
Move PHPStan stubs and constants to tools dir
thelovekesh May 24, 2024
d8f3969
Move webpack utils to tools dir
thelovekesh May 24, 2024
c5765a6
Update PHPCS shared ruleset path
thelovekesh May 24, 2024
ae319e5
Update utils path
thelovekesh May 24, 2024
dd13dcc
Update stub and constant files path
thelovekesh May 24, 2024
4e16457
Clean stale header
adamsilverstein May 24, 2024
de05e79
Merge branch 'trunk' into add/picture-support
adamsilverstein May 24, 2024
6a9d7e5
Update plugins/webp-uploads/picture-element.php
adamsilverstein May 24, 2024
1b10b5a
Update tests/plugins/webp-uploads/picture-element-tests.php
adamsilverstein May 24, 2024
ddaa425
fix for phpcs
adamsilverstein May 24, 2024
fab30c8
Update plugins/webp-uploads/picture-element.php
adamsilverstein May 28, 2024
6b38a3b
Merge branch 'trunk' into add/picture-support
adamsilverstein May 28, 2024
2cb0d68
Merge branch 'trunk' into add/picture-support
adamsilverstein May 28, 2024
05fe45f
Align equals
adamsilverstein May 28, 2024
132c784
Update plugins/webp-uploads/picture-element.php
adamsilverstein May 28, 2024
c0e0b25
Update plugins/webp-uploads/picture-element.php
adamsilverstein May 28, 2024
ac26be0
Ensure sizes and srcset available before trying to wrap in picture el…
adamsilverstein May 28, 2024
e0a382d
Improve tests
adamsilverstein May 28, 2024
0214583
whitespace
adamsilverstein May 28, 2024
0b7043e
Merge branch 'trunk' into add/picture-support
adamsilverstein May 28, 2024
06e4bf2
Update plugins/webp-uploads/hooks.php
adamsilverstein May 29, 2024
47304d4
Fix typo webp_uploads_wepb_fallback->webp_uploads_webp_fallback
adamsilverstein May 29, 2024
45e8c4a
Update tests/plugins/webp-uploads/picture-element-tests.php
adamsilverstein May 29, 2024
e49cc20
Update tests/plugins/webp-uploads/picture-element-tests.php
adamsilverstein May 29, 2024
78f28f5
Update tests/plugins/webp-uploads/picture-element-tests.php
adamsilverstein May 29, 2024
82c6f80
Update tests/plugins/webp-uploads/picture-element-tests.php
adamsilverstein May 29, 2024
4818932
Update plugins/webp-uploads/settings.php
adamsilverstein May 29, 2024
eccd446
Update plugins/webp-uploads/picture-element.php
adamsilverstein May 29, 2024
5d60dd6
Update plugins/webp-uploads/settings.php
adamsilverstein May 29, 2024
e473b92
Update tests/plugins/webp-uploads/picture-element-tests.php
adamsilverstein May 29, 2024
5b687bc
“Picture Element” -> “`<picture>` Element”
adamsilverstein May 29, 2024
f4042fe
Rename picture test file
adamsilverstein May 29, 2024
49a9d4b
Merge branch 'trunk' into add/picture-support + linting
adamsilverstein May 29, 2024
5f820d4
Add avif to enabled_mime_types for picture element
adamsilverstein May 29, 2024
f2b6ea5
Update plugins/webp-uploads/helper.php
adamsilverstein May 29, 2024
27be1c7
Update plugins/webp-uploads/settings.php
adamsilverstein May 29, 2024
7a07e91
Update plugins/webp-uploads/picture-element.php
adamsilverstein May 29, 2024
e0b584c
Update plugins/webp-uploads/picture-element.php
adamsilverstein May 29, 2024
d2af184
webp_uploads_picture_element_enabled -> webp_uploads_is_picture_eleme…
adamsilverstein May 29, 2024
ac46157
Add settings field back after merge
adamsilverstein May 29, 2024
0aa496e
settings cleanup
adamsilverstein May 29, 2024
2e68884
Update settings link to point to the entire settings section
westonruter May 29, 2024
e88caca
Update plugins/webp-uploads/settings.php
adamsilverstein May 29, 2024
2d57e43
Update plugins/webp-uploads/picture-element.php
adamsilverstein May 29, 2024
6d024de
Update plugins/webp-uploads/settings.php
adamsilverstein May 29, 2024
7d5f911
Update plugins/webp-uploads/settings.php
adamsilverstein May 29, 2024
86a03f2
fixup! Update settings link to point to the entire settings section
westonruter May 29, 2024
70400c8
Update plugins/webp-uploads/settings.php
adamsilverstein May 29, 2024
38edfdb
Improve doc block
adamsilverstein May 29, 2024
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
11 changes: 11 additions & 0 deletions plugins/webp-uploads/helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -384,3 +384,14 @@ function webp_uploads_get_image_output_format(): string {
function webp_uploads_sanitize_image_format( string $image_format ): string {
return in_array( $image_format, array( 'webp', 'avif' ), true ) ? $image_format : 'webp';
}

/**
* Checks if the `webp_uploads_is_picture_element_enabled` option is enabled.
*
* @since n.e.x.t
*
* @return bool True if the option is enabled, false otherwise.
*/
function webp_uploads_is_picture_element_enabled(): bool {
return (bool) get_option( 'webp_uploads_use_picture_element', false );
}
15 changes: 10 additions & 5 deletions plugins/webp-uploads/hooks.php
Original file line number Diff line number Diff line change
Expand Up @@ -569,7 +569,7 @@ function webp_uploads_update_image_references( string $content ): string {

return $content;
}
add_filter( 'the_content', 'webp_uploads_update_image_references', 10 );
add_filter( 'the_content', 'webp_uploads_update_image_references', 13 ); // Run after wp_filter_content_tags.

/**
* Finds all the urls with *.jpg and *.jpeg extension and updates with *.webp version for the provided image
Expand Down Expand Up @@ -675,13 +675,14 @@ function webp_uploads_img_tag_update_mime_type( string $original_image, string $
}

if (
! has_action( 'wp_footer', 'webp_uploads_wepb_fallback' ) &&
! has_action( 'wp_footer', 'webp_uploads_webp_fallback' ) &&
$image !== $original_image &&
'the_content' === $context &&
'image/jpeg' === $original_mime &&
'image/' . webp_uploads_get_image_output_format() === $target_mime
'image/' . webp_uploads_get_image_output_format() === $target_mime &&
! webp_uploads_is_picture_element_enabled() // Don't enqueue fallback JS if picture enabled.
) {
add_action( 'wp_footer', 'webp_uploads_wepb_fallback' );
add_action( 'wp_footer', 'webp_uploads_webp_fallback' );
}
}

Expand Down Expand Up @@ -709,7 +710,7 @@ function webp_uploads_update_featured_image( string $html, int $post_id, int $at
*
* @since 1.0.0
*/
function webp_uploads_wepb_fallback(): void {
function webp_uploads_webp_fallback(): void {
// Get mime type transforms for the site.
$transforms = webp_uploads_get_upload_image_mime_transforms();
$image_format = webp_uploads_get_image_output_format();
Expand Down Expand Up @@ -836,3 +837,7 @@ function webp_uploads_render_generator(): void {
echo '<meta name="generator" content="webp-uploads ' . esc_attr( WEBP_UPLOADS_VERSION ) . '">' . "\n";
}
add_action( 'wp_head', 'webp_uploads_render_generator' );

if ( webp_uploads_is_picture_element_enabled() ) {
add_filter( 'wp_content_img_tag', 'webp_uploads_wrap_image_in_picture', 10, 3 );
}
1 change: 1 addition & 0 deletions plugins/webp-uploads/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,6 @@
require_once __DIR__ . '/rest-api.php';
require_once __DIR__ . '/image-edit.php';
require_once __DIR__ . '/settings.php';
require_once __DIR__ . '/picture-element.php';
require_once __DIR__ . '/hooks.php';
require_once __DIR__ . '/deprecated.php';
159 changes: 159 additions & 0 deletions plugins/webp-uploads/picture-element.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
<?php
/**
* Add `picture` element support
*
* @package webp-uploads
*
* @since n.e.x.t
*/

/**
* Potentially wrap an image tag in a picture element.
*
* @since n.e.x.t
*
* @param string $image The image tag.
* @param string $context The context for the image tag.
* @param int $attachment_id The attachment id.
*
* @return string The new image tag.
*/
function webp_uploads_wrap_image_in_picture( string $image, string $context, int $attachment_id ): string {
if ( 'the_content' !== $context ) {
return $image;
}
$image_meta = wp_get_attachment_metadata( $attachment_id );
$original_file_mime_type = get_post_mime_type( $attachment_id );
if ( false === $original_file_mime_type || ! isset( $image_meta['sizes'] ) ) {
return $image;
}

// Collect all the sub size image mime types.
$mime_type_data = array();
foreach ( $image_meta['sizes'] as $size ) {
if ( isset( $size['sources'] ) && isset( $size['width'] ) && isset( $size['height'] ) ) {
foreach ( $size['sources'] as $mime_type => $data ) {
$mime_type_data[ $mime_type ] = $mime_type_data[ $mime_type ] ?? array();
$mime_type_data[ $mime_type ]['w'][ $size['width'] ] = $data;
$mime_type_data[ $mime_type ]['h'][ $size['height'] ] = $data;
}
}
}
$sub_size_mime_types = array_keys( $mime_type_data );

/**
* Filter the image mime types that can be used for the <picture> element.
*
* Default is: ['image/avif', 'image/webp', 'image/jpeg']. Returning an empty array will skip using the `picture` element.
*
* The mime types will output in the picture element in the order they are provided.
* The original image will be used as the fallback image for browsers that don't support the picture element.
*
* @since n.e.x.t
* @param string[] $mime_types Mime types than can be used.
* @param int $attachment_id The id of the image being evaluated.
*/
$enabled_mime_types = (array) apply_filters(
'webp_uploads_picture_element_mime_types',
array(
'image/avif',
'image/webp',
adamsilverstein marked this conversation as resolved.
Show resolved Hide resolved
'image/jpeg',
),
$attachment_id
);

$mime_types = array_filter(
$enabled_mime_types,
static function ( $mime_type ) use ( $sub_size_mime_types ) {
return in_array( $mime_type, $sub_size_mime_types, true );
}
);
adamsilverstein marked this conversation as resolved.
Show resolved Hide resolved

// No eligible mime types.
if ( count( $mime_types ) === 0 ) {
return $image;
}

// If the original mime types is the only one available, no picture element is needed.
if ( 1 === count( $mime_types ) && $mime_types[0] === $original_file_mime_type ) {
adamsilverstein marked this conversation as resolved.
Show resolved Hide resolved
return $image;
}
Copy link
Member

@joemcgill joemcgill May 29, 2024

Choose a reason for hiding this comment

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

For sites that don't support AVIF or WebP, if someone uploads an original in one of those formats, won't all the intermediate sizes be JPEG? If so, do we want this to result in using picture or would a simple img element be preferred?

Copy link
Member Author

Choose a reason for hiding this comment

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

I haven't tested with WebP support missing, but my expectation is that you shouldn't be able to upload a WebP image if your server doesn't support it. This may not be working correctly in the block editor currently, so for now I am referring to uploading in the media library. I'm not sure I have a test version of PHP that doesn't support WebP.

~98% of WordPress sites are on a host that supports WebP so this is an edge case.

Copy link
Member Author

Choose a reason for hiding this comment

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

@joemcgill - I created this follow up Issue to work on improving the settings screen in particular when WebP isn't supported.


// Add each mime type to the picture's sources.
$picture_sources = '';

// Extract sizes using regex to parse image tag, then use to retrieve tag.
$width = 0;
$height = 0;
$processor = new WP_HTML_Tag_Processor( $image );
if ( $processor->next_tag( array( 'tag_name' => 'IMG' ) ) ) {
$width = (int) $processor->get_attribute( 'width' );
$height = (int) $processor->get_attribute( 'height' );
}
$size_to_use = ( $width > 0 && $height > 0 ) ? array( $width, $height ) : 'full';

$image_src = wp_get_attachment_image_src( $attachment_id, $size_to_use );
if ( false === $image_src ) {
return $image;
}
list( $src, $width, $height ) = $image_src;
$size_array = array( absint( $width ), absint( $height ) );

foreach ( $mime_types as $image_mime_type ) {
$sizes = wp_calculate_image_sizes( $size_array, $src, $image_meta, $attachment_id );
if ( false === $sizes ) {
continue;
}

// Filter core's wp_get_attachment_image_srcset to return the sources for the current mime type.
$filter = static function ( $sources ) use ( $mime_type_data, $image_mime_type ): array {
$filtered_sources = array();
foreach ( $sources as $source ) {
// Swap the URL for the current mime type.
if ( isset( $mime_type_data[ $image_mime_type ][ $source['descriptor'] ][ $source['value'] ] ) ) {
$filename = $mime_type_data[ $image_mime_type ][ $source['descriptor'] ][ $source['value'] ]['file'];
$filtered_sources[] = array(
'url' => dirname( $source['url'] ) . '/' . $filename,
'descriptor' => $source['descriptor'],
'value' => $source['value'],
);
}
}
return $filtered_sources;
};
add_filter( 'wp_calculate_image_srcset', $filter );
$image_srcset = wp_get_attachment_image_srcset( $attachment_id, $size_array, $image_meta );
remove_filter( 'wp_calculate_image_srcset', $filter );
if ( false === $image_srcset ) {
continue;
}
$picture_sources .= sprintf(
'<source type="%s" srcset="%s" sizes="%s">',
esc_attr( $image_mime_type ),
esc_attr( $image_srcset ),
esc_attr( $sizes )
);
}

// Fall back to the original image without a srcset.
$original_sizes = array( $image_src[1], $image_src[2] );
$original_image = wp_get_original_image_url( $attachment_id );
// Fail gracefully if the original image is not found.
if ( false === $original_image ) {
return $image;
}
$filter = static function (): bool {
return false;
};
add_filter( 'wp_calculate_image_srcset_meta', $filter );
$original_image_without_srcset = wp_get_attachment_image( $attachment_id, $original_sizes, false, array( 'src' => $original_image ) );
remove_filter( 'wp_calculate_image_srcset_meta', $filter );

return sprintf(
'<picture class="%s">%s%s</picture>',
esc_attr( 'wp-picture-' . $attachment_id ),
$picture_sources,
$original_image_without_srcset
);
}
47 changes: 43 additions & 4 deletions plugins/webp-uploads/settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@ function webp_uploads_register_media_settings_field(): void {
'show_in_rest' => false,
)
);
// Add a setting to use the picture element.
register_setting(
'media',
'webp_uploads_use_picture_element',
array(
'type' => 'boolean',
// Use picture element by default if the theme declares support for it.
'default' => current_theme_supports( 'html5', 'picture' ),
'show_in_rest' => false,
)
);
}
add_action( 'init', 'webp_uploads_register_media_settings_field' );

Expand All @@ -49,7 +60,7 @@ function webp_uploads_register_media_settings_field(): void {
*
* @since 1.0.0
*/
function webp_uploads_add_media_settings_field(): void {
function webp_uploads_add_media_settings_fields(): void {

// Add a dropdown to select the output format between AVIF and WebP output.
add_settings_field(
Expand All @@ -61,7 +72,7 @@ function webp_uploads_add_media_settings_field(): void {
array( 'class' => 'perflab-generate-avif-and-webp' )
);

// Add settings field.
// Add JPEG Output settings field.
add_settings_field(
'perflab_generate_webp_and_jpeg',
__( 'Also output JPEG', 'webp-uploads' ),
Expand All @@ -70,8 +81,18 @@ function webp_uploads_add_media_settings_field(): void {
is_multisite() ? 'default' : 'uploads',
array( 'class' => 'perflab-generate-webp-and-jpeg' )
);

// Add picture element support settings field.
add_settings_field(
'webp_uploads_use_picture_element',
__( 'Enable `picture` element', 'webp-uploads' ),
adamsilverstein marked this conversation as resolved.
Show resolved Hide resolved
'webp_uploads_use_picture_element_callback',
'media',
is_multisite() ? 'default' : 'uploads',
array( 'class' => 'webp-uploads-use-picture-element' )
);
}
add_action( 'admin_init', 'webp_uploads_add_media_settings_field' );
add_action( 'admin_init', 'webp_uploads_add_media_settings_fields' );

/**
* Renders the settings field for the 'perflab_modern_image_format' setting.
Expand Down Expand Up @@ -111,8 +132,9 @@ function webp_uploads_generate_avif_webp_setting_callback(): void {
* @since 1.0.0
*/
function webp_uploads_generate_webp_jpeg_setting_callback(): void {
?>

?>
<tr><td colspan="2" class="td-full">
adamsilverstein marked this conversation as resolved.
Show resolved Hide resolved
<label for="perflab_generate_webp_and_jpeg">
<input name="perflab_generate_webp_and_jpeg" type="checkbox" id="perflab_generate_webp_and_jpeg" aria-describedby="perflab_generate_webp_and_jpeg_description" value="1"<?php checked( '1', get_option( 'perflab_generate_webp_and_jpeg' ) ); ?> />
<?php esc_html_e( 'Output JPEG images in addition to the modern format', 'webp-uploads' ); ?>
Expand All @@ -121,6 +143,23 @@ function webp_uploads_generate_webp_jpeg_setting_callback(): void {
<?php
}

/**
* Renders the settings field for the 'webp_uploads_use_picture_element' setting.
*
* @since 1.0.0
adamsilverstein marked this conversation as resolved.
Show resolved Hide resolved
*/
function webp_uploads_use_picture_element_callback(): void {
adamsilverstein marked this conversation as resolved.
Show resolved Hide resolved
?>
<tr><td colspan="2" class="td-full">
<label for="webp_uploads_use_picture_element">
adamsilverstein marked this conversation as resolved.
Show resolved Hide resolved
<input name="webp_uploads_use_picture_element" type="checkbox" id="webp_uploads_use_picture_element" aria-describedby="webp_uploads_use_picture_element_description" value="1"<?php checked( webp_uploads_is_picture_element_enabled() ); ?> />
<?php esc_html_e( 'Use <picture> Element', 'webp-uploads' ); ?>
adamsilverstein marked this conversation as resolved.
Show resolved Hide resolved
</label>
<p class="description" id="webp_uploads_use_picture_element_description"><?php esc_html_e( 'The picture element serves a modern image format with a fallback to JPEG. Warning: Make sure you test your theme and plugins for compatibility. In particular, CSS selectors will not match images when using the child combinator (e.g. figure > img).', 'webp-uploads' ); ?></p>
</td></tr>
<?php
}

/**
* Adds a settings link to the plugin's action links.
*
Expand Down
2 changes: 1 addition & 1 deletion tests/plugins/webp-uploads/test-helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

class Test_WebP_Uploads_Helper extends ImagesTestCase {

public function set_up() {
public function set_up(): void {
parent::set_up();
$this->set_image_output_type( 'webp' );
}
Expand Down
6 changes: 2 additions & 4 deletions tests/plugins/webp-uploads/test-load.php
Original file line number Diff line number Diff line change
Expand Up @@ -704,8 +704,6 @@ static function ( $editors ) {
* Replace the featured image to the proper type when requesting the featured image.
*
* @dataProvider data_provider_supported_image_types
*
* @param string $image_type
*/
public function test_it_should_replace_the_featured_image_to_webp_when_requesting_the_featured_image( string $image_type ): void {
$mime_type = 'image/' . $image_type;
Expand Down Expand Up @@ -876,7 +874,7 @@ public function test_it_should_add_fallback_script_if_content_has_updated_images
)
);

$this->assertTrue( has_action( 'wp_footer', 'webp_uploads_wepb_fallback' ) === 10 );
$this->assertTrue( has_action( 'wp_footer', 'webp_uploads_webp_fallback' ) === 10 );

$footer = get_echo( 'wp_footer' );
$this->assertStringContainsString( 'webp;base64,UklGR', wp_unslash( $footer ) );
Expand All @@ -890,7 +888,7 @@ public function test_it_should_not_add_fallback_script_if_content_has_no_updated

apply_filters( 'the_content', '<p>no image</p>' );

$this->assertFalse( has_action( 'wp_footer', 'webp_uploads_wepb_fallback' ) );
$this->assertFalse( has_action( 'wp_footer', 'webp_uploads_webp_fallback' ) );

$footer = get_echo( 'wp_footer' );
$this->assertStringNotContainsString( 'webp;base64,UklGR', $footer );
Expand Down
Loading
Loading