From 2fd753a1f90c7153ef7b0c0b6da3311eab45474a Mon Sep 17 00:00:00 2001 From: Jonathan Williams Date: Tue, 6 Aug 2024 23:11:54 -0400 Subject: [PATCH 1/7] calculate the new dimensions for each size instead of using bare size data from the registered sizes --- src/filters.php | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/src/filters.php b/src/filters.php index 9250554..1f1831d 100644 --- a/src/filters.php +++ b/src/filters.php @@ -106,16 +106,46 @@ function generate_metadata_sizes( $metadata, $attachment_id ) { // Get the mime type for the original file. $mime_type = get_post_mime_type( $attachment_id ); + // Get the original image dimensions. + $original_width = $metadata['width']; + $original_height = $metadata['height']; + + // Calculate the aspect ratio of the original image. + $aspect_ratio = $original_width / $original_height; + // Recalculate the sizes that would have been generated and add them to the metadata. foreach ( $sizes as $size => $size_data ) { - // Calcualte the new filename by adding the size to the original filename using the WordPress convention. - $new_filename = $pathinfo['filename'] . '-' . $size_data['width'] . 'x' . $size_data['height'] . '.' . $pathinfo['extension']; + // Determine the new dimensions based on the aspect ratio, taking into account that either width or height may be 0. + if ( $size_data['width'] == 0 ) { + // Scale based on height only. + $new_height = $size_data['height']; + $new_width = (int) ( $new_height * $aspect_ratio ); + } elseif ( $size_data['height'] == 0 ) { + // Scale based on width only. + $new_width = $size_data['width']; + $new_height = (int) ( $new_width / $aspect_ratio ); + } else { + // Scale based on both dimensions. + $scale = min( $size_data['width'] / $original_width, $size_data['height'] / $original_height ); + $new_width = (int) ( $original_width * $scale ); + $new_height = (int) ( $original_height * $scale ); + } + + // Clamp the dimensions to the original image size if the new size is larger. + // Basically, we don't want to scale up the image. + if ( $new_width > $original_width || $new_height > $original_height ) { + $new_width = $original_width; + $new_height = $original_height; + } + + // Calculate the new filename by adding the size to the original filename using the WordPress convention. + $new_filename = $pathinfo['filename'] . '-' . $new_width . 'x' . $new_height . '.' . $pathinfo['extension']; // Add the new size to the metadata. $metadata['sizes'][ $size ] = array( 'file' => $new_filename, - 'width' => $size_data['width'], - 'height' => $size_data['height'], + 'width' => $new_width, + 'height' => $new_height, 'mime-type' => $mime_type, ); } From 381e3649824b2b3a81c9f41330efe7d631b50a78 Mon Sep 17 00:00:00 2001 From: Jonathan Williams Date: Fri, 9 Aug 2024 23:54:39 -0400 Subject: [PATCH 2/7] simple stub class --- .../class-skip-save-image-editor.php | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/s3uploads-plugin/class-skip-save-image-editor.php diff --git a/src/s3uploads-plugin/class-skip-save-image-editor.php b/src/s3uploads-plugin/class-skip-save-image-editor.php new file mode 100644 index 0000000..48e898b --- /dev/null +++ b/src/s3uploads-plugin/class-skip-save-image-editor.php @@ -0,0 +1,35 @@ + $filename ?: '', + 'file' => wp_basename( $filename ?: '' ), + 'width' => $this->size['width'] ?? 0, + 'height' => $this->size['height'] ?? 0, + 'mime-type' => $mime_type, + ]; + } +} From 394ed136d1a6929212bed4a7f38009dad7cd2709 Mon Sep 17 00:00:00 2001 From: Jonathan Williams Date: Sat, 10 Aug 2024 00:38:22 -0400 Subject: [PATCH 3/7] get the right filename in the non-saving _save() override --- .../class-skip-save-image-editor.php | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/s3uploads-plugin/class-skip-save-image-editor.php b/src/s3uploads-plugin/class-skip-save-image-editor.php index 48e898b..d29327a 100644 --- a/src/s3uploads-plugin/class-skip-save-image-editor.php +++ b/src/s3uploads-plugin/class-skip-save-image-editor.php @@ -23,13 +23,26 @@ class Skip_Save_Image_Editor extends Image_Editor_Imagick { * @return WP_Error|array{path: string, file: string, width: int, height: int, mime-type: string} */ protected function _save( $image, $filename = null, $mime_type = null ) { - // Skip saving the image - return [ - 'path' => $filename ?: '', - 'file' => wp_basename( $filename ?: '' ), + /** + * @var ?string $filename + * @var string $extension + * @var string $mime_type + */ + list( $filename, $extension, $mime_type ) = $this->get_output_format( $filename, $mime_type ); + + // What we really need is to get the filename here, then return it without saving the image. + if ( ! $filename ) { + $filename = parent::generate_filename( null, null, $extension ); + } + + $response = [ + 'path' => $filename, + 'file' => wp_basename( apply_filters( 'image_make_intermediate_size', $filename ) ), 'width' => $this->size['width'] ?? 0, 'height' => $this->size['height'] ?? 0, 'mime-type' => $mime_type, ]; + + return $response; } } From c9d0f01ed26f9e9168fad8064db702068f4d9378 Mon Sep 17 00:00:00 2001 From: Jonathan Williams Date: Sat, 10 Aug 2024 17:55:46 -0400 Subject: [PATCH 4/7] switch to a custom image editor --- src/filters.php | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/src/filters.php b/src/filters.php index 1f1831d..cee67b5 100644 --- a/src/filters.php +++ b/src/filters.php @@ -52,34 +52,37 @@ function s3_multisite_upload_dir( $upload ) { } add_filter( 'upload_dir', __NAMESPACE__ . '\s3_multisite_upload_dir' ); -// Conditionally adds a filter only during the upload process, this filter adds a second filter that removes all the image sizes. -// It also adds a filter to preemptively add the sizes to the attachment metadata, otherwise the first filter would prevent the sizes from being added. +// Add a custom filter to indicate that this is an upload request. add_filter( 'wp_handle_upload', function( $file ) { - // This filters the image sizes that are generated during the upload process, removing all of them by returning an empty array. - add_filter( - 'image_resize_dimensions', - function( $orig_w, $orig_h ) { - return array(); - }, - 10, - 6 - ); + // Set a custom flag to indicate that this is an upload request. + add_filter('is_upload_request', '__return_true'); - // Preemptively add the sizes to the attachment metadata. - add_filter( - 'wp_generate_attachment_metadata', - __NAMESPACE__ . '\generate_metadata_sizes', - 10, - 2 - ); + // May still want to set a generate_metadata filter here to add the file size, which seems to be getting dropped. // We need to pass along the original prefilter value unaltered; we're not actually changing it, just using it as a hook for the resize filter. return $file; } ); +// Add a custom filter to suppress resized image generation during the upload process. +add_filter( 'wp_image_editors', function( $editors ) { + // Check if the custom flag indicating an upload request is set. + if ( apply_filters('is_upload_request', false) ) { + // This is an upload request, so we should use the custom image editor that skips saving the image to S3. + // Include the custom image editor that skips saving the image to S3. + require_once dirname( __FILE__ ) . '/s3uploads-plugin/class-skip-save-image-editor.php'; + + // Add the custom image editor to the list of available editors as the first editor, so that's what WordPress uses. + array_unshift( $editors, 'BU\Plugins\MediaS3\Skip_Save_Image_Editor' ); + } + + // Return the list of editors. + return $editors; +} ); + + /** * Generate metadata for image sizes without creating the actual resized images. * From 28c10f65d4fae687a3551de59f34582e9ad9cdb1 Mon Sep 17 00:00:00 2001 From: Jonathan Williams Date: Sat, 10 Aug 2024 17:56:56 -0400 Subject: [PATCH 5/7] adjust comments --- src/s3uploads-plugin/class-skip-save-image-editor.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/s3uploads-plugin/class-skip-save-image-editor.php b/src/s3uploads-plugin/class-skip-save-image-editor.php index d29327a..bcef063 100644 --- a/src/s3uploads-plugin/class-skip-save-image-editor.php +++ b/src/s3uploads-plugin/class-skip-save-image-editor.php @@ -2,7 +2,8 @@ /** * Image Editor subclass of the S3_Uploads plugin Image_Editor_Imagick class. * - * This custom image editor is used to skip saving the image to S3. + * This custom image editor is used to skip saving the image to S3. It works by subclassing the + * S3_Uploads plugin's Image_Editor_Imagick class and overriding the _save method to skip saving the image to S3. * * @package BU MediaS3 */ @@ -30,11 +31,14 @@ protected function _save( $image, $filename = null, $mime_type = null ) { */ list( $filename, $extension, $mime_type ) = $this->get_output_format( $filename, $mime_type ); - // What we really need is to get the filename here, then return it without saving the image. + // Crucially, we need to determine the filename so we can return it correctly in the response. if ( ! $filename ) { $filename = parent::generate_filename( null, null, $extension ); } + // We don't want to save the image to S3, so we don't call the parent::_save method. + + // Return the metadata for the image. $response = [ 'path' => $filename, 'file' => wp_basename( apply_filters( 'image_make_intermediate_size', $filename ) ), From bac1d69a3af634bdf4f84653df25a30162e8bb32 Mon Sep 17 00:00:00 2001 From: Jonathan Williams Date: Sat, 10 Aug 2024 19:30:56 -0400 Subject: [PATCH 6/7] remove generate sizes function we don't need to generate sizes anymore --- src/filters.php | 73 ------------------------------------------------- 1 file changed, 73 deletions(-) diff --git a/src/filters.php b/src/filters.php index cee67b5..6259b03 100644 --- a/src/filters.php +++ b/src/filters.php @@ -82,79 +82,6 @@ function( $file ) { return $editors; } ); - -/** - * Generate metadata for image sizes without creating the actual resized images. - * - * This function gets the registered image sizes and calculates the filename for each size - * using the WordPress convention for size annotation. It then adds this information to the attachment metadata. - * - * This function is designed to be added to the wp_handle_upload_prefilter in order to restore the metadata that - * would have been generated. Becuase the sizes are being suppressed on upload, the actual resized images - * are not being created, but we still need to add the metadata for the sizes to the attachment. This function - * should have the effect of pre-generating the metadata for the sizes that would have been created. - * - * @param array $metadata The attachment metadata. - * @param int $attachment_id The ID of the attachment. - * - * @return array The modified attachment metadata, with added sizes. - */ -function generate_metadata_sizes( $metadata, $attachment_id ) { - // Get the registered image sizes. - $sizes = wp_get_registered_image_subsizes(); - - // Get the pathinfo for the original file. - $pathinfo = pathinfo( $metadata['file'] ); - - // Get the mime type for the original file. - $mime_type = get_post_mime_type( $attachment_id ); - - // Get the original image dimensions. - $original_width = $metadata['width']; - $original_height = $metadata['height']; - - // Calculate the aspect ratio of the original image. - $aspect_ratio = $original_width / $original_height; - - // Recalculate the sizes that would have been generated and add them to the metadata. - foreach ( $sizes as $size => $size_data ) { - // Determine the new dimensions based on the aspect ratio, taking into account that either width or height may be 0. - if ( $size_data['width'] == 0 ) { - // Scale based on height only. - $new_height = $size_data['height']; - $new_width = (int) ( $new_height * $aspect_ratio ); - } elseif ( $size_data['height'] == 0 ) { - // Scale based on width only. - $new_width = $size_data['width']; - $new_height = (int) ( $new_width / $aspect_ratio ); - } else { - // Scale based on both dimensions. - $scale = min( $size_data['width'] / $original_width, $size_data['height'] / $original_height ); - $new_width = (int) ( $original_width * $scale ); - $new_height = (int) ( $original_height * $scale ); - } - - // Clamp the dimensions to the original image size if the new size is larger. - // Basically, we don't want to scale up the image. - if ( $new_width > $original_width || $new_height > $original_height ) { - $new_width = $original_width; - $new_height = $original_height; - } - - // Calculate the new filename by adding the size to the original filename using the WordPress convention. - $new_filename = $pathinfo['filename'] . '-' . $new_width . 'x' . $new_height . '.' . $pathinfo['extension']; - - // Add the new size to the metadata. - $metadata['sizes'][ $size ] = array( - 'file' => $new_filename, - 'width' => $new_width, - 'height' => $new_height, - 'mime-type' => $mime_type, - ); - } - return $metadata; -} - // Disable the big image threshold, we don't want WordPress to do any resizing at all. add_filter( 'big_image_size_threshold', '__return_false' ); From 48bbf13df5d138a779db3d068ee295fcf05f0d07 Mon Sep 17 00:00:00 2001 From: Jonathan Williams Date: Mon, 12 Aug 2024 13:06:45 -0400 Subject: [PATCH 7/7] update readme --- readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 326a6c6..be70fc8 100644 --- a/readme.md +++ b/readme.md @@ -6,11 +6,11 @@ BU Media S3 is a WordPress plugin designed to work with the [Human Made S3 Uploa - **S3 Upload Directory**: The plugin filters the `upload_dir` hook and rewrites the values in a way that is compatible with the S3 Uploads plugin and the BU Protected S3 Object Lambda stack, redirecting all media uploads to the S3 bucket. It also changes the default upload location from `wp-content/uploads` to `files`, which is the convention for BU WordPress sites. -- **Prevent Image Scaling**: By default, WordPress generates a scaled derivative for every image size that is defined for the current site. Because the BU Protected S3 Object Lambda stack handles image resizing automatically, the WordPress media library only needs to handle the full sized original upload. This plugin tells WordPress not to generate any derivative sizes on upload. It preserves the attachment metadata for all of the defined sizes by generating it directly during the upload process. +- **Prevent Image Scaling**: By default, WordPress generates scaled derivatives for every image size defined for the current site. This plugin sets a custom filter flag during the upload process and uses it to interpose a custom Image Editor object. This approach leverages the technique used by the S3 Uploads plugin, ensuring that the core WordPress image size metadata generation remains intact while skipping the actual resizing. This allows the BU Protected S3 Object Lambda stack to handle image resizing automatically, and preserves the attachment metadata for all defined sizes. -- **Suppress Big Image Threshold Resizing**: WordPress 5.3 introduced a new feature that automatically resizes images for "web-ready" dimensions. This plugin suppresses this feature, allowing you to upload images at their full size. +- **Suppress Big Image Threshold Resizing**: WordPress 5.3 introduced a feature that automatically resizes images to "web-ready" dimensions. This plugin suppresses this feature, allowing you to upload images at their full size. -- **DynamoDB Crop Data**: This plugin provides a way to write crop data to DynamoDB. The BU Protected S3 Object Lambda stack can read this crop data and apply it when the image is requested. The site-manager plugin automatically writes crop data to DynamoDB when a sites are moved or cloned. I'm not sure about site creation though, that might be a todo. +- **DynamoDB Crop Data**: This plugin provides a way to write crop data to DynamoDB. The BU Protected S3 Object Lambda stack can read this crop data and apply it when the image is requested. The site-manager plugin automatically writes crop data to DynamoDB when sites are moved or cloned. Site creation might be a todo. - **Handle Site File Deletion**: When a site is deleted, this plugin handles the deletion of files from S3 and the corresponding crop data in DynamoDB. It deletes both the original and derivative images from S3.