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

Create image sub-sizes in additional MIME types using sources for storage. #147

Merged
merged 37 commits into from Feb 24, 2022
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
0706e81
Add the `sources` property to each image size
mitogh Feb 3, 2022
9c43b89
Fix linting errors from `phpcs`
mitogh Feb 3, 2022
25479a1
Make sure the correct size is updated
mitogh Feb 3, 2022
0f560d8
Merge branch 'trunk' into feature/142-multiple-mimes-for-image-sizes
mitogh Feb 17, 2022
410c124
Update code to use existing WP Core function instead.
mitogh Feb 17, 2022
7a8b0c5
Reorder functions and hooks.
mitogh Feb 17, 2022
f71a897
Use a third person verb instead.
mitogh Feb 17, 2022
4b4cbc2
Fix `webp` typo in the extension
mitogh Feb 17, 2022
b01be99
Merge branch 'feature/142-multiple-mimes-for-image-sizes' of github.c…
mitogh Feb 17, 2022
4ba9409
Define the right type for the doc block.
mitogh Feb 17, 2022
5321483
Fix syntax error when calling `wp_get_registered_image_subsizes`
mitogh Feb 17, 2022
2bf68f6
Removal of support class no longer required
mitogh Feb 21, 2022
de38c2d
Add test images, the images would be used in tests.
mitogh Feb 21, 2022
dd13025
Add required methods for the mock Editor.
mitogh Feb 21, 2022
70eeb33
Remove non required conditional on testing method.
mitogh Feb 21, 2022
107db40
Add support for multiple mime types for existing images sizes.
mitogh Feb 21, 2022
975fddd
Include tests for the deletion of an attachment.
mitogh Feb 21, 2022
e3c2e3a
Update file to follow `PHPCS` rules.
mitogh Feb 21, 2022
6e3afee
Prevent naming collisions with existing functions.
mitogh Feb 21, 2022
76f1c2f
Replace `array_key_exists` with `isset`
mitogh Feb 23, 2022
fba8fab
Replace `array_key_exists` with `isset`
mitogh Feb 23, 2022
7da54dc
Replace `array_key_exists` with `isset`
mitogh Feb 23, 2022
2c37d19
Replace `array_key_exists` with `isset`
mitogh Feb 23, 2022
2c6307f
Fix alignment on dockblock
mitogh Feb 23, 2022
5b5ff6d
Replace `array_key_exists` with `isset`
mitogh Feb 23, 2022
0e94c15
Replace `array_key_exists` with `isset`
mitogh Feb 23, 2022
a5b63df
Updates from code review.
mitogh Feb 23, 2022
e0b48c4
Fix linting errors
mitogh Feb 23, 2022
e996230
Adjust logic and variable names to follow WP Core.
mitogh Feb 23, 2022
d708dfd
Fix linting error of alignment
mitogh Feb 23, 2022
061ef31
Remove blank lines.
mitogh Feb 23, 2022
81ea713
Add parenthesis to method references.
mitogh Feb 23, 2022
411efe4
Add parenthesis to method references.
mitogh Feb 23, 2022
a2c6815
Update documentation on function return value.
mitogh Feb 23, 2022
a2c655c
Update image generation into a single process.
mitogh Feb 23, 2022
38224b0
Fix linting errors for PHP
mitogh Feb 24, 2022
acd12f1
Replace `array_key_exists` with `isset` instead.
mitogh Feb 24, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
291 changes: 270 additions & 21 deletions modules/images/webp-uploads/load.php
Expand Up @@ -4,41 +4,290 @@
* Description: Uses WebP as the default format for new JPEG image uploads if the server supports it.
* Experimental: No
*
* @since 1.0.0
* @package performance-lab
* @since 1.0.0
*/

/**
* Filter the image editor default output format mapping.
* Hook called by `wp_generate_attachment_metadata` to create the `sources` property for every image
* size, the sources' property would create a new image size with all the mime types specified in
* `webp_uploads_valid_image_mime_types`. If the original image is one of the mimes from
* `webp_uploads_valid_image_mime_types` the image is just added to the `sources` property and not
* created again. If the uploaded attachment is not a valid image this function does not alter the
* metadata of the attachment, on the other hand a `sources` property is added.
*
* For uploaded JPEG images, map the default output format to WebP.
* @since n.e.x.t
*
* @since 1.0.0
* @see wp_generate_attachment_metadata
* @see webp_uploads_valid_image_mime_types
mitogh marked this conversation as resolved.
Show resolved Hide resolved
*
* @param string $output_format The image editor default output format mapping.
* @param string $filename Path to the image.
* @param string $mime_type The source image mime type.
* @return string The new output format mapping.
* @param array $metadata An array with the metadata from this attachment.
* @param int $attachment_id The ID of the attachment where the hook was dispatched.
*
* @return array An array with the updated structure for the metadata before is stored in the database.
mitogh marked this conversation as resolved.
Show resolved Hide resolved
*/
function webp_uploads_create_sources_property( array $metadata, $attachment_id ) {
mitogh marked this conversation as resolved.
Show resolved Hide resolved
// This should take place only on the JPEG image.
$valid_mime_transforms = webp_uploads_get_supported_image_mime_transforms();

// Not a supported mime type to create the sources property.
$mime_type = get_post_mime_type( $attachment_id );
if ( ! isset( $valid_mime_transforms[ $mime_type ] ) ) {
adamsilverstein marked this conversation as resolved.
Show resolved Hide resolved
return $metadata;
}

$file = get_attached_file( $attachment_id, true );

// File does not exist.
if ( ! file_exists( $file ) ) {
return $metadata;
}

$dirname = pathinfo( $file, PATHINFO_DIRNAME );
$image_sizes = array();
if ( array_key_exists( 'sizes', $metadata ) && is_array( $metadata['sizes'] ) ) {
$image_sizes = $metadata['sizes'];
}

foreach ( wp_get_registered_image_subsizes() as $size_name => $properties ) {
// This image size does not exist on the defined sizes.
if ( ! isset( $image_sizes[ $size_name ] ) || ! is_array( $image_sizes[ $size_name ] ) ) {
continue;
}

$current_size = $image_sizes[ $size_name ];
$sources = array();
if ( isset( $current_size['sources'] ) && is_array( $current_size['sources'] ) ) {
$sources = $current_size['sources'];
}

// Try to find the mime type of the image size.
$current_mime = '';
if ( isset( $current_size['mime-type'] ) ) {
$current_mime = $current_size['mime-type'];
} elseif ( isset( $current_size['file'] ) ) {
$current_mime = wp_check_filetype( $current_size['file'] )['type'];
}

if ( empty( $current_mime ) ) {
continue;
}

$sources[ $current_mime ] = array(
'file' => array_key_exists( 'file', $current_size ) ? $current_size['file'] : '',
'filesize' => 0,
);

// Set the filesize from the current mime image.
$file_location = path_join( $dirname, $sources[ $current_mime ]['file'] );
if ( file_exists( $file_location ) ) {
$sources[ $current_mime ]['filesize'] = filesize( $file_location );
}
mitogh marked this conversation as resolved.
Show resolved Hide resolved

$formats = isset( $valid_mime_transforms[ $current_mime ] ) ? $valid_mime_transforms[ $current_mime ] : array();

foreach ( $formats as $mime ) {
wp_schedule_single_event( time(), 'webp_uploads_create_image', array( $attachment_id, $size_name, $mime ) );
}
Copy link
Member

Choose a reason for hiding this comment

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

The code here is still missing the critical check for whether we actually need to generate the format still. Since the process may fail at any point, we need to always make sure we only generate the sizes which are missing, see also my feedback in #147 (comment).

Also, why a cron event? WordPress core doesn't use a cron event to generate its image sizes, and I think we should follow how it generates its default sub-sizes, which happens in a single request, just allowing for failures as far as I can tell, and then it retries until everything has been completed (see the _wp_make_subsizes() function and how it is used).

We also need to make sure then that we keep using the latest version of $sources, since it could be updated within the function, so your webp_uploads_generate_image_size() function below should probably return the source array that it has added.

How about this:

Suggested change
foreach ( $formats as $mime ) {
wp_schedule_single_event( time(), 'webp_uploads_create_image', array( $attachment_id, $size_name, $mime ) );
}
foreach ( $formats as $mime ) {
if ( isset( $sources[ $mime ] ) ) {
continue;
}
$new_source = webp_uploads_generate_image_size( $attachment_id, $size_name, $mime );
if ( is_wp_error( $new_source ) ) {
continue;
}
$sources[ $mime ] = $new_source;
}

Copy link
Member Author

Choose a reason for hiding this comment

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

Actually, this is already covered here:

https://github.com/WordPress/performance/pull/147/files/6e3afeef0648073c2615aa15f4d0e090f0b3b93f#diff-71ed188a16b79d179868ee80e76f775462a451e871fc3d6214fcacca3edcea8cR47

As if the size has not been created yet the additional mime types for that image won't be created.

Since the process may fail at any point, we need to always make sure we only generate the sizes which are missing, see also my feedback in #147 (comment).

Once the image is retried and finally generated it would trigger this hook again, which at that point would contain the key for that particular image and then the image would be scheduled for creation.

Also, why a cron event?

To split the work for every single size in a different process, and only use as minimal resources as possible per image size so we can decrease the number of potential failures when creating an image in a constrained environment.

WordPress core doesn't use a cron event to generate its image sizes

It does not because it has to process the image in a single request and display the image right away, we as progressive enchantment can do that as we know a fallback image was already created. And we can create a background process (Cron) for any additional mime images.

This mechanism would ensure existing users would continue to use the same number of resources when uploading an image as they currently do, without the overhead of additional resources introduced by a new mime type.

just allowing for failures as far as I can tell, and then it retries until everything has been completed (see the _wp_make_subsizes() function and how it is used).

The failures would be handled by this hook as described at the beginning every time an image fails won't be added into the image sizes, and every time a new image size is added it would trigger this hook, ensuring that the image is only scheduled for creation when the main image was created.

We also need to make sure then that we keep using the latest version of $sources, since it could be updated within the function, so your webp_uploads_generate_image_size() function below should probably return the source array that it has added.

This is already handled here by just returning the updated metadata and the hook dispatched by this function would update the meta once is executed.

Copy link
Member

Choose a reason for hiding this comment

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

Although the wp_schedule_single_event approach works in my testing, I wonder if it will work across WordPress installs or might create problems for some users, for example does this work if define( 'DISABLE_WP_CRON', true ); is set?

I think we should test Felix's suggestion of trying to generate all images in one go, tracking the completed ones in meta . Core's upload process (in both media and the block editor) will catch incomplete generation runs (a timeout error) and send a retry request (which we may need to hook into?). I will try testing this approach with a low server timeout to verify.

Copy link
Member

Choose a reason for hiding this comment

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

i encountered a big with the callback approach: after uploading an image, I deleted it before WebP creation completed. A final WebP was generated after the deletion.

testing with a short server timeout, WordPress's retry mechanism worked as expected, and the cron callback was set up after the core image process completed.

Copy link
Member Author

Choose a reason for hiding this comment

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

Although the wp_schedule_single_event approach works in my testing, I wonder if it will work across WordPress installs or might create problems for some users, for example does this work if define( 'DISABLE_WP_CRON', true ); is set?

Usually when this is the case, is to run the corn with an actual CRON via the server which will still execute any remaining cron job. However, if the CRON is completely disabled from the server any additional functionality from WordPress won't work either like:

  • Scheduling publishing
  • Automatic updates from WP and plugins.

I think we should test Felix's suggestion of trying to generate all images in one go, tracking the completed ones in meta . Core's upload process (in both media and the block editor) will catch incomplete generation runs (a timeout error) and send a retry request (which we may need to hook into?). I will try testing this approach with a low server timeout to verify.

Can you share your setup PHP.ini so I can try to replicate it?

i encountered a big with the callback approach: after uploading an image, I deleted it before WebP creation was completed. A final WebP was generated after the deletion.

Oh, great point I think this can be an edge case but still would be ideal to make sure any remaining cron is removed when the image is removed as well.

Copy link
Member

Choose a reason for hiding this comment

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

@mitogh

Also, why a cron event?

To split the work for every single size in a different process, and only use as minimal resources as possible per image size so we can decrease the number of potential failures when creating an image in a constrained environment.

While I get the thinking behind this, we have to keep in mind we're building this for WordPress core, which already has an existing working solution for generating sub-sizes in a scalable way. WP core does not use cron for this, and if we wanted to change that, it would open up a big discussion on whether that makes sense or not, which is not the point we're trying to make here - we want to add support for adding sub-sizes also in WebP.

We should really minimize any sort of "refactoring" that is unrelated to the problem we're trying to solve. We can tie into the existing logic and follow the same approach that WP core has here without running into problems - and as @adamsilverstein has pointed out, using a different approach here potentially causes other problems and it will require a lot of extra testing - in contrast to using WP core's existing approach, which we know works.

For the above reasons, I still think it would be better to drop the cron usage here and just call the function right away, as that follows the existing approach in WP core.

Copy link
Member

Choose a reason for hiding this comment

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

I did some testing with a low php timeout threshold using both approaches. Unfortunately, the way we hook in now is after core completes its retries, so our generation code only gets one run. With a low timeout, this means not all sizes will be generated. We could either change how we hook in so it happens earlier (before completion so the error is caught), or we could hook in later and return an error if mime/sizes are still missing:

For Ajax hooking in with the wp_prepare_attachment_for_js filter, which is the last thing the ajax callback calls (returning false when mime/sizes are missing would trigger another retry)

I found the same issue in Gutenberg, the retry 'create-image-subsizes' was only called until all the jpg images are generated, then one attempt at webp images, the retry did not continue because the code only retries until a success return. We may be able to use the rest_prepare_attachment filter (in `src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php::L843). Returning an Error when the attachment has missing mime/sizes woulds trigger the retry.

Maybe we can also bump these retries up on the trac ticket.

Copy link
Member

Choose a reason for hiding this comment

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

I'm fine merging Felix's suggested code, then working on ensuring error catching (and retries) work correctly for secondary types as well.

It might be worth looking at hooking on wp_update_attachment_metadata, which is called for each image core generates, passing the full meta with each call, with the last item in the sizes array the most recently generated image. If our image generation threw a timeout error here it could trigger the retry.

wp_update_attachment_metadata is also called by image edits which could be handy if we want to handle those in alternate mime types.

Copy link
Member

Choose a reason for hiding this comment

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

@adamsilverstein Based on your feedback, I agree we may need to hook into wp_update_attachment_metadata. That would require some tweaks for our callback though, since that hook is fired every time a single new sub-size is generated in core. We had also discussed the potential usage of this hook in our call last week - the tricky part would be to figure out whether it is used for an image size being generated so that our code is only run in that case. Also, the logic for which sub-sizes to generate may need to be reworked.

Before we go towards doing that though, are we sure the current implementation is not working in a scalable way? I understand your argumentation @adamsilverstein, but why is that actually failing? Looking at the code, WP core uses wp_update_image_subsizes(), both via AJAX and the REST API. Before that function succeeds, it fires the wp_generate_attachment_metadata filter, which we are using here, so I don't follow how the code would return a success response without running our added filter callback 🤔

In any case, I think it makes sense to drop the cron hook, replace it with a direct call, and explore further from there on whether the wp_generate_attachment_metadata filter is okay or whether we need an alternative solution using the wp_generate_attachment_metadata filter. cc @mitogh

Copy link
Member

Choose a reason for hiding this comment

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

Summarizing a chat with @mitogh and @adamsilverstein here:

  • We're going to switch to "direct" generation of sub-sizes, essentially replace the cron hook with a direct call to the function as suggested above.
  • The remaining problem with that approach is that wp_update_image_subsizes() will no longer trigger the wp_generate_attachment_metadata filter in any subsequent requests after the WP core sizes have been generated (since wp_get_missing_image_subsizes() returns an empty array, it short-circuits) - that's why the implementation will only make one more request after a failure in our code, but that won't trigger the hook, so WP thinks the sub-sizes generation has succeeded.
  • We can work around that by hooking into the wp_get_missing_image_subsizes filter, and essentially misusing it as an action, as follows:
    • If the list of sizes is empty and we are in some sort of "upload" request, we manually call our function to generate the missing sub-sizes in the additional formats.
    • We just return the original filtered value without changing it.
    • In order to know whether we are in an "upload" request, we have to set a temporary meta key in the attachment as soon as our logic triggers, to flag that this attachment is basically "awaiting additional image MIME sub-sizes generation". Once our function has completed, we remove that same meta key flag again.
    • This approach will work regardless of whether image sizes are being generated via WP-AJAX or REST.
    • It is quite hacky, but it's what we need to do to work with the limits of not having a proper filter/action in WP core to use here. We should add a comment to that code indicating how that would behave differently in an eventual WP core patch.
  • Let's limit this PR to going with just the simple removal of the cron hook. The above "hack" we can then add in a follow-up PR to the same issue When uploading, generate both WebP and jpeg format images by default #142 - that allows the review to be simpler, since already a lot of work has gone into this, and it's a good stage to break the work into two PRs.


$current_size['sources'] = $sources;
$metadata['sizes'][ $size_name ] = $current_size;
}

return $metadata;
}

add_filter( 'wp_generate_attachment_metadata', 'webp_uploads_create_sources_property', 10, 2 );

/**
* Creates a new image based of the specified attachment with a defined mime type
* this image would be stored in the same place as the provided size name inside the
* metadata of the attachment.
*
* @since n.e.x.t
*
* @param int $attachment_id The ID of the attachment we are going to use as a reference to create the image.
* @param string $size The size name that would be used to create this image, out of the registered subsizes.
* @param string $mime A mime type we are looking to use to create this image.
*
* @return bool|int|WP_Error
mitogh marked this conversation as resolved.
Show resolved Hide resolved
*/
function webp_uploads_filter_image_editor_output_format( $output_format, $filename, $mime_type ) {
adamsilverstein marked this conversation as resolved.
Show resolved Hide resolved
// Only enable if the server supports WebP.
if ( ! wp_image_editor_supports( array( 'mime_type' => 'image/webp' ) ) ) {
return $output_format;
function webp_uploads_generate_image_size( $attachment_id, $size, $mime ) {
$sizes = wp_get_registered_image_subsizes();
$metadata = wp_get_attachment_metadata( $attachment_id );

if (
! isset( $metadata['sizes'][ $size ], $sizes[ $size ] )
|| ! is_array( $metadata['sizes'][ $size ] )
|| ! is_array( $sizes[ $size ] )
) {
return new WP_Error( 'image_mime_type_invalid_metadata', __( 'The image does not have a valid metadata.', 'performance-lab' ) );
}

// WebP lossless support is still limited on servers, so only apply to JPEGs.
if ( 'image/jpeg' !== $mime_type ) {
return $output_format;
// All subsizes are created out of the attached file.
$file = get_attached_file( $attachment_id );

// File does not exist.
if ( ! file_exists( $file ) ) {
return new WP_Error( 'image_file_size_not_found', __( 'The provided size does not have a valid image file.', 'performance-lab' ) );
}

// Skip conversion when creating the `-scaled` image (for large image uploads).
if ( preg_match( '/-scaled\..{3}.?$/', $filename ) ) {
return $output_format;
// Create the subsizes out of the attached file.
$editor = wp_get_image_editor( $file );

if ( is_wp_error( $editor ) ) {
return $editor;
}

$allowed_mimes = array_flip( wp_get_mime_types() );
if ( ! array_key_exists( $mime, $allowed_mimes ) || ! is_string( $allowed_mimes[ $mime ] ) ) {
return new WP_Error( 'image_mime_type_invalid', __( 'The provided mime type is not allowed.', 'performance-lab' ) );
}

if ( ! wp_image_editor_supports( array( 'mime_type' => $mime ) ) ) {
return new WP_Error( 'image_mime_type_not_supported', __( 'The provided mime type is not supported.', 'performance-lab' ) );
}

$output_format['image/jpeg'] = 'image/webp';
$extension = explode( '|', $allowed_mimes[ $mime ] );
$extension = reset( $extension );

return $output_format;
$width = null;
$height = null;
$crop = false;

if ( array_key_exists( 'width', $metadata['sizes'][ $size ] ) ) {
$width = $metadata['sizes'][ $size ]['width'];
} elseif ( array_key_exists( 'width', $sizes[ $size ] ) ) {
$width = $sizes[ $size ];
}

if ( array_key_exists( 'height', $metadata['sizes'][ $size ] ) ) {
$height = $metadata['sizes'][ $size ]['height'];
} elseif ( array_key_exists( 'width', $sizes[ $size ] ) ) {
$height = $sizes[ $size ];
}

if ( array_key_exists( 'crop', $sizes[ $size ] ) ) {
$crop = (bool) $sizes[ $size ]['crop'];
}

$editor->resize( $width, $height, $crop );
$filename = $editor->generate_filename( null, null, $extension );
$filename = preg_replace( '/-(scaled|rotated|imagifyresized)/', '', $filename );
adamsilverstein marked this conversation as resolved.
Show resolved Hide resolved
$image = $editor->save( $filename, $mime );

if ( is_wp_error( $image ) ) {
return $image;
}

if ( empty( $image['file'] ) ) {
return new WP_Error( 'image_file_not_present', __( 'The file key is not present on the image data', 'performance-lab' ) );
}

if ( ! isset( $metadata['sizes'][ $size ]['sources'] ) || ! is_array( $metadata['sizes'][ $size ]['sources'] ) ) {
$metadata['sizes'][ $size ]['sources'] = array();
}

$metadata['sizes'][ $size ]['sources'][ $mime ] = array(
'file' => $image['file'],
'filesize' => array_key_exists( 'path', $image ) ? filesize( $image['path'] ) : 0,
);

return wp_update_attachment_metadata( $attachment_id, $metadata );
Copy link
Member

Choose a reason for hiding this comment

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

See my comment above, we should return the new source here so that it can also be processed outside, so that the data in the above function remains intact.

Suggested change
$metadata['sizes'][ $size ]['sources'][ $mime ] = array(
'file' => $image['file'],
'filesize' => array_key_exists( 'path', $image ) ? filesize( $image['path'] ) : 0,
);
return wp_update_attachment_metadata( $attachment_id, $metadata );
$new_source = array(
'file' => $image['file'],
'filesize' => isset( $image['path'] ) ? filesize( $image['path'] ) : 0,
);
// Update metadata right away with the new source in case the process fails later.
$metadata['sizes'][ $size ]['sources'][ $mime ] = $new_source;
wp_update_attachment_metadata( $attachment_id, $metadata );
return $new_source;

Copy link
Member Author

Choose a reason for hiding this comment

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

Right, this behavior is to allow an async mechanism rather than a single request.

Copy link
Member

Choose a reason for hiding this comment

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

See my comment above - I think we should change that to use a single request while supporting retry attempts, thus following how WP core does it.

}

add_action( 'webp_uploads_create_image', 'webp_uploads_generate_image_size', 10, 3 );

/**
* Returns an array with the list of valid mime types that a specific mime type can be converted into it,
* for example an image/jpeg can be converted into an image/webp.
*
* @since n.e.x.t
*
* @return array<string, array<string>> An array of valid mime types, where the key is the mime type and the value is the extension type.
*/
function webp_uploads_get_supported_image_mime_transforms() {
$image_mime_transforms = array(
'image/jpeg' => array( 'image/webp' ),
'image/webp' => array( 'image/jpeg' ),
);

/**
* Filter to allow the definition of a custom mime types, in which a defined mime type
* can be transformed and provide a wide range of mime types.
*
* @since n.e.x.t
*
* @param array<string, array<string>> An array with the valid mime transforms.
*
* @return array<string, array<string>> An array with the valid mime transformation
mitogh marked this conversation as resolved.
Show resolved Hide resolved
*/
return (array) apply_filters( 'webp_uploads_supported_image_mime_transforms', $image_mime_transforms );
}

/**
* Hook fired when an attachment is deleted, this hook is in charge of removing any
* additional mime types created by this plugin besides the original image. Any source
* with the same as the main image would not be removed by this hook due this file would
* be removed by WordPress when the attachment is deleted, usually this happens after this
* hook is executed.
*
* @since n.e.x.t
*
* @see wp_delete_attachment
mitogh marked this conversation as resolved.
Show resolved Hide resolved
*
* @param int $attachment_id The ID of the attachment the sources are going to be deleted.
*/
function webp_uploads_remove_sources_files( $attachment_id ) {
$metadata = wp_get_attachment_metadata( $attachment_id );
$file = get_attached_file( $attachment_id );

if (
! isset( $metadata['sizes'] )
|| empty( $file )
|| ! is_array( $metadata['sizes'] )
) {
return;
}

$upload_path = wp_get_upload_dir();
if ( empty( $upload_path['basedir'] ) ) {
return;
}

$intermediate_dir = path_join( $upload_path['basedir'], dirname( $file ) );
$basename = wp_basename( $file );

foreach ( $metadata['sizes'] as $size ) {
if ( ! isset( $size['sources'] ) || ! is_array( $size['sources'] ) ) {
continue;
}

$original_size_mime = empty( $size['mime-type'] ) ? '' : $size['mime-type'];

foreach ( $size['sources'] as $mime => $properties ) {
/**
* When we face the same mime type as the original image, we ignore this file as this file
* would be removed when the size is removed by WordPress itself. The meta information as well
* would be deleted as soon as the image is removed.
*
* @see wp_delete_attachment
*/
if ( $original_size_mime === $mime ) {
continue;
}

if ( ! is_array( $properties ) || empty( $properties['file'] ) ) {
continue;
}

$intermediate_file = str_replace( $basename, $properties['file'], $file );
if ( ! empty( $intermediate_file ) ) {
$intermediate_file = path_join( $upload_path['basedir'], $intermediate_file );
wp_delete_file_from_directory( $intermediate_file, $intermediate_dir );
}
}
}
}
add_filter( 'image_editor_output_format', 'webp_uploads_filter_image_editor_output_format', 10, 3 );
mitogh marked this conversation as resolved.
Show resolved Hide resolved
adamsilverstein marked this conversation as resolved.
Show resolved Hide resolved

add_action( 'delete_attachment', 'webp_uploads_remove_sources_files', 10, 1 );