From 8ae28cfe63bf161535d8253959dfcc7504dc2fa5 Mon Sep 17 00:00:00 2001 From: Gerhard Potgieter Date: Wed, 7 Feb 2018 14:52:23 +0200 Subject: [PATCH 1/5] Add extra checks before processing on-the-fly regenration images to catch remote files. --- includes/class-wc-regenerate-images.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/includes/class-wc-regenerate-images.php b/includes/class-wc-regenerate-images.php index f5555340853..13d8ff2b553 100644 --- a/includes/class-wc-regenerate-images.php +++ b/includes/class-wc-regenerate-images.php @@ -106,7 +106,8 @@ class WC_Regenerate_Images { $original_image_file_path = get_attached_file( $attachment->ID ); - if ( ! file_exists( $original_image_file_path ) || ! getimagesize( $original_image_file_path ) ) { + // Check if the file exists, if not just return the original image. + if ( false === $original_image_file_path || is_wp_error( $original_image_file_path ) || ! file_exists( $original_image_file_path ) ) { return $image; } From 2280cabb94866c1cb8809ba2ff95e674cafd6a10 Mon Sep 17 00:00:00 2001 From: Gerhard Potgieter Date: Thu, 8 Feb 2018 15:36:50 +0200 Subject: [PATCH 2/5] Change on-the-fly image regenration to use same process as backrgound resizing, ensures CDN images are updated and show. --- includes/class-wc-regenerate-images.php | 126 +++++++++++++++++------- 1 file changed, 89 insertions(+), 37 deletions(-) diff --git a/includes/class-wc-regenerate-images.php b/includes/class-wc-regenerate-images.php index 13d8ff2b553..d10819f841d 100644 --- a/includes/class-wc-regenerate-images.php +++ b/includes/class-wc-regenerate-images.php @@ -82,33 +82,50 @@ class WC_Regenerate_Images { } /** - * Regenerate the image according to the required size + * Ensure we are dealing with the correct image attachment * - * @param int $attachment_id Attachment ID. - * @param array $image Original Image. - * @param string $size Size to return for new URL. - * @param bool $icon If icon or not. - * @return string + * @param WP_Post $attachment Attachment object. + * @return boolean */ - private static function resize_and_return_image( $attachment_id, $image, $size, $icon ) { - $attachment = get_post( $attachment_id ); - if ( ! $attachment || 'attachment' !== $attachment->post_type || 'image/' !== substr( $attachment->post_mime_type, 0, 6 ) ) { - return $image; + public static function is_regeneratable( $attachment ) { + if ( 'site-icon' === get_post_meta( $attachment->ID, '_wp_attachment_context', true ) ) { + return false; } - if ( ! function_exists( 'wp_crop_image' ) ) { - include ABSPATH . 'wp-admin/includes/image.php'; + if ( wp_attachment_is_image( $attachment ) ) { + return true; } - $wp_uploads = wp_upload_dir( null, false ); - $wp_uploads_dir = $wp_uploads['basedir']; - $wp_uploads_url = $wp_uploads['baseurl']; + return false; + } + /** + * Only regenerate images for these specific sizes. + * + * @param array $sizes Array of image sizes. + * @return array + */ + public static function adjust_intermediate_image_sizes( $sizes ) { + return apply_filters( 'woocommerce_regenerate_images_intermediate_image_sizes', array( 'woocommerce_thumbnail', 'woocommerce_thumbnail_2x', 'woocommerce_single' ) ); + } + + /** + * Check whether an attachment has the specific sizes on disk. + * + * @param WP_Post $attachment Attachment object. + * @param array $size Sizes to test. + * @return boolean + */ + public static function should_generate_image( $attachment, $size ) { $original_image_file_path = get_attached_file( $attachment->ID ); + $editor = wp_get_image_editor( $original_image_file_path ); + + if ( is_wp_error( $editor ) ) { + return false; + } - // Check if the file exists, if not just return the original image. if ( false === $original_image_file_path || is_wp_error( $original_image_file_path ) || ! file_exists( $original_image_file_path ) ) { - return $image; + return false; } $info = pathinfo( $original_image_file_path ); @@ -119,7 +136,7 @@ class WC_Regenerate_Images { $image_size = wc_get_image_size( $size ); $dimensions = image_resize_dimensions( $orig_w, $orig_h, $image_size['width'], $image_size['height'], $image_size['crop'] ); if ( ! $dimensions || ! is_array( $dimensions ) ) { - return $image; + return false; } $dst_w = $dimensions[4]; @@ -128,31 +145,66 @@ class WC_Regenerate_Images { $dst_rel_path = str_replace( '.' . $ext, '', $original_image_file_path ); $destfilename = "{$dst_rel_path}-{$suffix}.{$ext}"; - // If the file is already there perhaps just load it. - if ( file_exists( $destfilename ) ) { - return array( - 0 => str_replace( $wp_uploads_dir, $wp_uploads_url, $destfilename ), - 1 => $image_size['width'], - 2 => $image_size['height'], - ); + // If the file does not exist we should regenerate. + if ( ! file_exists( $destfilename ) ) { + return true; } + return false; + } - // Lets resize the image if it does not exist yet. - $editor = wp_get_image_editor( $original_image_file_path ); - if ( is_wp_error( $editor ) || is_wp_error( $editor->resize( $image_size['width'], $image_size['height'], $image_size['crop'] ) ) ) { + /** + * Regenerate the image according to the required size + * + * @param int $attachment_id Attachment ID. + * @param array $image Original Image. + * @param string $size Size to return for new URL. + * @param bool $icon If icon or not. + * @return string + */ + private static function resize_and_return_image( $attachment_id, $image, $size, $icon ) { + $attachment = get_post( $attachment_id ); + + if ( ! $attachment || 'attachment' !== $attachment->post_type || ! self::is_regeneratable( $attachment ) ) { return $image; } - $resized_file = $editor->save(); - if ( ! is_wp_error( $resized_file ) ) { - $img_url = str_replace( $wp_uploads_dir, $wp_uploads_url, $resized_file['path'] ); - return array( - 0 => $img_url, - 1 => $image_size['width'], - 2 => $image_size['height'], - ); + + if ( ! function_exists( 'wp_crop_image' ) ) { + include ABSPATH . 'wp-admin/includes/image.php'; } - // Lets just add this here as a fallback. + if ( ! self::should_generate_image( $attachment, $size ) ) { + return $image; + } + + $fullsizepath = get_attached_file( $attachment_id ); + $old_metadata = wp_get_attachment_metadata( $attachment_id ); + + // We only want to regen WC images. + add_filter( 'intermediate_image_sizes', array( __CLASS__, 'adjust_intermediate_image_sizes' ) ); + + // This function will generate the new image sizes. + $new_metadata = wp_generate_attachment_metadata( $attachment_id, $fullsizepath ); + + // Remove custom filter. + remove_filter( 'intermediate_image_sizes', array( __CLASS__, 'adjust_intermediate_image_sizes' ) ); + + // If something went wrong lets just return the original image. + if ( is_wp_error( $new_metadata ) || empty( $new_metadata ) ) { + return $image; + } + + if ( ! empty( $old_metadata ) && ! empty( $old_metadata['sizes'] ) && is_array( $old_metadata['sizes'] ) ) { + foreach ( $old_metadata['sizes'] as $old_size => $old_size_data ) { + if ( empty( $new_metadata['sizes'][ $old_size ] ) ) { + $new_metadata['sizes'][ $old_size ] = $old_metadata['sizes'][ $old_size ]; + } + } + } + + // Update the meta data with the new size values. + wp_update_attachment_metadata( $attachment_id, $new_metadata ); + + // If we made it here the image has been regenerated and we can simply continue with normal operation. return $image; } From 961cd6dfa6d2b17da0e916cb8cbebebe677ddc37 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Fri, 9 Feb 2018 14:04:43 +0000 Subject: [PATCH 3/5] Copy sizes to legacy sizes --- includes/class-wc-regenerate-images-request.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/includes/class-wc-regenerate-images-request.php b/includes/class-wc-regenerate-images-request.php index 587488aafea..e9f8cb19ddb 100644 --- a/includes/class-wc-regenerate-images-request.php +++ b/includes/class-wc-regenerate-images-request.php @@ -132,6 +132,16 @@ class WC_Regenerate_Images_Request extends WC_Background_Process { $new_metadata['sizes'][ $old_size ] = $old_metadata['sizes'][ $old_size ]; } } + // Handle legacy sizes. + if ( isset( $new_metadata['sizes']['shop_thumbnail'], $new_metadata['sizes']['woocommerce_thumbnail'] ) ) { + $new_metadata['sizes']['shop_thumbnail'] = $new_metadata['sizes']['woocommerce_thumbnail']; + } + if ( isset( $new_metadata['sizes']['shop_catalog'], $new_metadata['sizes']['woocommerce_thumbnail'] ) ) { + $new_metadata['sizes']['shop_catalog'] = $new_metadata['sizes']['woocommerce_thumbnail']; + } + if ( isset( $new_metadata['sizes']['shop_single'], $new_metadata['sizes']['woocommerce_single'] ) ) { + $new_metadata['sizes']['shop_single'] = $new_metadata['sizes']['woocommerce_single']; + } } // Update the meta data with the new size values. From 9d898bbf6e86f623f0e156438ea8c594ca70c983 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Fri, 9 Feb 2018 14:06:00 +0000 Subject: [PATCH 4/5] Return the resized image after regen, or if regen is not needed --- includes/class-wc-regenerate-images.php | 129 +++++++++++++++++------- 1 file changed, 91 insertions(+), 38 deletions(-) diff --git a/includes/class-wc-regenerate-images.php b/includes/class-wc-regenerate-images.php index d10819f841d..c6a387807a2 100644 --- a/includes/class-wc-regenerate-images.php +++ b/includes/class-wc-regenerate-images.php @@ -23,6 +23,13 @@ class WC_Regenerate_Images { */ protected static $background_process; + /** + * Stores size being generated on the fly. + * + * @var string + */ + protected static $requested_size; + /** * Init function */ @@ -30,6 +37,8 @@ class WC_Regenerate_Images { include_once WC_ABSPATH . 'includes/class-wc-regenerate-images-request.php'; self::$background_process = new WC_Regenerate_Images_Request(); + add_filter( 'wp_generate_attachment_metadata', array( __CLASS__, 'add_uncropped_metadata' ) ); + if ( ! is_admin() ) { // Handle on-the-fly image resizing. add_filter( 'wp_get_attachment_image_src', array( __CLASS__, 'maybe_resize_image' ), 10, 4 ); @@ -44,6 +53,18 @@ class WC_Regenerate_Images { } } + /** + * We need to track if uncropped was on or off when generating the images. + * + * @param array $metadata Array of meta data. + * @return array + */ + public static function add_uncropped_metadata( $metadata ) { + $size_settings = wc_get_image_size( 'woocommerce_thumbnail' ); + $metadata['woocommerce_thumbnail_uncropped'] = empty( $size_settings['height'] ); + return $metadata; + } + /** * Check if we should maybe generate a new image size if not already there. * @@ -67,17 +88,22 @@ class WC_Regenerate_Images { // Get image metadata - we need it to proceed. $imagemeta = wp_get_attachment_metadata( $attachment_id ); - if ( false === $imagemeta || empty( $imagemeta ) ) { + if ( empty( $imagemeta ) ) { return $image; } $size_settings = wc_get_image_size( $size ); - // If size differs from image meta, regen. + // If size differs from image meta, or height differs and we're cropping, regenerate the image. if ( ! isset( $imagemeta['sizes'], $imagemeta['sizes'][ $size ] ) || $imagemeta['sizes'][ $size ]['width'] !== $size_settings['width'] || ( $size_settings['crop'] && $imagemeta['sizes'][ $size ]['height'] !== $size_settings['height'] ) ) { $image = self::resize_and_return_image( $attachment_id, $image, $size, $icon ); } + // If cropping mode has changed, regenerate the image. + if ( '' === $size_settings['height'] && empty( $imagemeta['woocommerce_thumbnail_uncropped'] ) ) { + $image = self::resize_and_return_image( $attachment_id, $image, $size, $icon ); + } + return $image; } @@ -100,56 +126,47 @@ class WC_Regenerate_Images { } /** - * Only regenerate images for these specific sizes. + * Only regenerate images for the requested size. * * @param array $sizes Array of image sizes. * @return array */ public static function adjust_intermediate_image_sizes( $sizes ) { - return apply_filters( 'woocommerce_regenerate_images_intermediate_image_sizes', array( 'woocommerce_thumbnail', 'woocommerce_thumbnail_2x', 'woocommerce_single' ) ); + return array( self::$requested_size ); } /** - * Check whether an attachment has the specific sizes on disk. + * Generate the thumbnail filename and dimensions for a given file. * - * @param WP_Post $attachment Attachment object. - * @param array $size Sizes to test. - * @return boolean + * @param string $fullsizepath Path to full size image. + * @param int $thumbnail_width The width of the thumbnail. + * @param int $thumbnail_height The height of the thumbnail. + * @param bool $crop Whether to crop or not. + * @return array|false An array of the filename, thumbnail width, and thumbnail height, or false on failure to resize such as the thumbnail being larger than the fullsize image. */ - public static function should_generate_image( $attachment, $size ) { - $original_image_file_path = get_attached_file( $attachment->ID ); - $editor = wp_get_image_editor( $original_image_file_path ); + private static function get_image( $fullsizepath, $thumbnail_width, $thumbnail_height, $crop ) { + list( $fullsize_width, $fullsize_height ) = getimagesize( $fullsizepath ); + + $dimensions = image_resize_dimensions( $fullsize_width, $fullsize_height, $thumbnail_width, $thumbnail_height, $crop ); + $editor = wp_get_image_editor( $fullsizepath ); if ( is_wp_error( $editor ) ) { return false; } - if ( false === $original_image_file_path || is_wp_error( $original_image_file_path ) || ! file_exists( $original_image_file_path ) ) { - return false; - } - - $info = pathinfo( $original_image_file_path ); - $ext = $info['extension']; - - list( $orig_w, $orig_h ) = getimagesize( $original_image_file_path ); - // Get image size after cropping. - $image_size = wc_get_image_size( $size ); - $dimensions = image_resize_dimensions( $orig_w, $orig_h, $image_size['width'], $image_size['height'], $image_size['crop'] ); if ( ! $dimensions || ! is_array( $dimensions ) ) { return false; } - $dst_w = $dimensions[4]; - $dst_h = $dimensions[5]; - $suffix = "{$dst_w}x{$dst_h}"; - $dst_rel_path = str_replace( '.' . $ext, '', $original_image_file_path ); - $destfilename = "{$dst_rel_path}-{$suffix}.{$ext}"; + list( , , , , $dst_w, $dst_h ) = $dimensions; + $suffix = "{$dst_w}x{$dst_h}"; + $file_ext = strtolower( pathinfo( $fullsizepath, PATHINFO_EXTENSION ) ); - // If the file does not exist we should regenerate. - if ( ! file_exists( $destfilename ) ) { - return true; - } - return false; + return array( + 'filename' => $editor->generate_filename( $suffix, null, $file_ext ), + 'width' => $dst_w, + 'height' => $dst_h, + ); } /** @@ -162,21 +179,45 @@ class WC_Regenerate_Images { * @return string */ private static function resize_and_return_image( $attachment_id, $image, $size, $icon ) { - $attachment = get_post( $attachment_id ); + self::$requested_size = $size; + $image_size = wc_get_image_size( $size ); + $wp_uploads = wp_upload_dir( null, false ); + $wp_uploads_dir = $wp_uploads['basedir']; + $wp_uploads_url = $wp_uploads['baseurl']; + $attachment = get_post( $attachment_id ); if ( ! $attachment || 'attachment' !== $attachment->post_type || ! self::is_regeneratable( $attachment ) ) { return $image; } + $fullsizepath = get_attached_file( $attachment_id ); + + if ( false === $fullsizepath || is_wp_error( $fullsizepath ) || ! file_exists( $fullsizepath ) ) { + return $image; + } + if ( ! function_exists( 'wp_crop_image' ) ) { include ABSPATH . 'wp-admin/includes/image.php'; } - if ( ! self::should_generate_image( $attachment, $size ) ) { - return $image; + // Make sure registered image size matches the size we're requesting. + add_image_size( $size, $image_size['width'], $image_size['height'], $image_size['crop'] ); + + $thumbnail = self::get_image( $fullsizepath, $image_size['width'], $image_size['height'], $image_size['crop'] ); + + // If the file is already there perhaps just load it. + if ( $thumbnail && file_exists( $thumbnail['filename'] ) ) { + $wp_uploads = wp_upload_dir( null, false ); + $wp_uploads_dir = $wp_uploads['basedir']; + $wp_uploads_url = $wp_uploads['baseurl']; + + return array( + 0 => str_replace( $wp_uploads_dir, $wp_uploads_url, $thumbnail['filename'] ), + 1 => $thumbnail['width'], + 2 => $thumbnail['height'], + ); } - $fullsizepath = get_attached_file( $attachment_id ); $old_metadata = wp_get_attachment_metadata( $attachment_id ); // We only want to regen WC images. @@ -199,13 +240,25 @@ class WC_Regenerate_Images { $new_metadata['sizes'][ $old_size ] = $old_metadata['sizes'][ $old_size ]; } } + // Handle legacy sizes. + if ( isset( $new_metadata['sizes']['shop_thumbnail'], $new_metadata['sizes']['woocommerce_thumbnail'] ) ) { + $new_metadata['sizes']['shop_thumbnail'] = $new_metadata['sizes']['woocommerce_thumbnail']; + } + if ( isset( $new_metadata['sizes']['shop_catalog'], $new_metadata['sizes']['woocommerce_thumbnail'] ) ) { + $new_metadata['sizes']['shop_catalog'] = $new_metadata['sizes']['woocommerce_thumbnail']; + } + if ( isset( $new_metadata['sizes']['shop_single'], $new_metadata['sizes']['woocommerce_single'] ) ) { + $new_metadata['sizes']['shop_single'] = $new_metadata['sizes']['woocommerce_single']; + } } // Update the meta data with the new size values. wp_update_attachment_metadata( $attachment_id, $new_metadata ); - // If we made it here the image has been regenerated and we can simply continue with normal operation. - return $image; + // Now we've done our regen, attempt to return the new size. + $new_image = image_downsize( $attachment_id, $size ); + + return $new_image ? $new_image : $image; } /** From 7339511495faa69d01b091941bf4f56755968a0c Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Fri, 9 Feb 2018 14:12:19 +0000 Subject: [PATCH 5/5] Height must be int --- includes/wc-core-functions.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/wc-core-functions.php b/includes/wc-core-functions.php index 32e377518af..b08de6bc3c3 100644 --- a/includes/wc-core-functions.php +++ b/includes/wc-core-functions.php @@ -744,13 +744,13 @@ function wc_get_image_size( $image_size ) { } elseif ( 'custom' === $cropping ) { $width = max( 1, get_option( 'woocommerce_thumbnail_cropping_custom_width', '4' ) ); $height = max( 1, get_option( 'woocommerce_thumbnail_cropping_custom_height', '3' ) ); - $size['height'] = round( ( $size['width'] / $width ) * $height ); + $size['height'] = absint( round( ( $size['width'] / $width ) * $height ) ); $size['crop'] = 1; } else { $cropping_split = explode( ':', $cropping ); $width = max( 1, current( $cropping_split ) ); $height = max( 1, end( $cropping_split ) ); - $size['height'] = round( ( $size['width'] / $width ) * $height ); + $size['height'] = absint( round( ( $size['width'] / $width ) * $height ) ); $size['crop'] = 1; } $image_size = 'thumbnail';