2017-11-14 12:39:28 +00:00
< ? php
/**
* Regenerate Images Functionality
*
* All functionality pertaining to regenerating product images in realtime .
*
* @ package WooCommerce / Classes
* @ version 3.3 . 0
* @ since 3.3 . 0
*/
2018-01-25 17:03:54 +00:00
defined ( 'ABSPATH' ) || exit ;
2017-11-14 12:39:28 +00:00
/**
* Regenerate Images Class
*/
class WC_Regenerate_Images {
2017-11-15 06:36:57 +00:00
/**
* Background process to regenerate all images
*
* @ var WC_Regenerate_Images_Request
*/
2017-11-15 08:03:59 +00:00
protected static $background_process ;
2017-11-15 06:36:57 +00:00
2018-02-09 14:06:00 +00:00
/**
* Stores size being generated on the fly .
*
* @ var string
*/
2018-02-14 13:20:34 +00:00
protected static $regenerate_size ;
2018-02-09 14:06:00 +00:00
2017-11-14 12:39:28 +00:00
/**
* Init function
*/
public static function init () {
2018-02-14 13:20:34 +00:00
add_action ( 'image_get_intermediate_size' , array ( __CLASS__ , 'filter_image_get_intermediate_size' ), 10 , 3 );
2018-02-09 14:06:00 +00:00
add_filter ( 'wp_generate_attachment_metadata' , array ( __CLASS__ , 'add_uncropped_metadata' ) );
2018-02-13 07:55:50 +00:00
// Resize WooCommerce images on the fly when browsing site through customizer as to showcase image setting changes in real time.
if ( is_customize_preview () ) {
2017-12-12 03:01:25 +00:00
add_filter ( 'wp_get_attachment_image_src' , array ( __CLASS__ , 'maybe_resize_image' ), 10 , 4 );
2017-11-16 12:15:30 +00:00
}
2018-02-13 12:02:24 +00:00
2018-02-14 13:20:34 +00:00
// Not required when Jetpack Photon is in use.
if ( class_exists ( 'Jetpack' ) && Jetpack :: is_module_active ( 'photon' ) ) {
return ;
}
// Regenerate thumbnails in the background after settings change. Not ran on multisite to avoid multiple simultanious jobs, and not required when Jetpack Photon is in use.
2018-02-13 12:02:24 +00:00
if ( apply_filters ( 'woocommerce_background_image_regeneration' , ! is_multisite () ) ) {
include_once WC_ABSPATH . 'includes/class-wc-regenerate-images-request.php' ;
self :: $background_process = new WC_Regenerate_Images_Request ();
add_action ( 'admin_init' , array ( __CLASS__ , 'regenerating_notice' ) );
add_action ( 'woocommerce_hide_regenerating_thumbnails_notice' , array ( __CLASS__ , 'dismiss_regenerating_notice' ) );
add_action ( 'customize_save_after' , array ( __CLASS__ , 'maybe_regenerate_images' ) );
add_action ( 'after_switch_theme' , array ( __CLASS__ , 'maybe_regenerate_images' ) );
}
}
2018-02-14 13:20:34 +00:00
/**
* If an intermediate size meta differs from the actual image size ( settings were changed ? ) return false so the wrong size is not used .
*
* @ param array $data Size data .
* @ param int $attachment_id Attachment ID .
* @ param string $size Size name .
* @ return array
*/
public static function filter_image_get_intermediate_size ( $data , $attachment_id , $size ) {
if ( ! is_string ( $size ) || ! in_array ( $size , apply_filters ( 'woocommerce_image_sizes_to_resize' , array ( 'woocommerce_thumbnail' , 'woocommerce_gallery_thumbnail' , 'woocommerce_single' , 'shop_thumbnail' , 'shop_catalog' , 'shop_single' ) ), true ) ) {
return $data ;
}
// If we don't have sizes, we cannot proceed.
if ( ! isset ( $data [ 'width' ], $data [ 'height' ] ) ) {
return $data ;
}
if ( ! self :: image_size_matches_settings ( $data , $size ) ) {
return false ;
}
return $data ;
}
/**
* We need to track if uncropped was on or off when generating the images .
*
* @ param array $meta_data Array of meta data .
* @ return array
*/
public static function add_uncropped_metadata ( $meta_data ) {
$size_data = wc_get_image_size ( 'woocommerce_thumbnail' );
if ( isset ( $meta_data [ 'sizes' ], $meta_data [ 'sizes' ][ 'woocommerce_thumbnail' ] ) ) {
$meta_data [ 'sizes' ][ 'woocommerce_thumbnail' ][ 'uncropped' ] = empty ( $size_settings [ 'height' ] );
}
return $meta_data ;
}
/**
* See if an image ' s dimensions match actual settings .
*
* @ param array $image Image dimensions array .
* @ param string $size Named size .
* @ return bool
*/
protected static function image_size_matches_settings ( $image , $size ) {
$size_data = wc_get_image_size ( $size );
// Size is invalid if the widths or crop setting don't match.
if ( $size_data [ 'width' ] !== $image [ 'width' ] ) {
return false ;
}
// Size is invalid if the heights don't match.
if ( $size_data [ 'height' ] && $size_data [ 'height' ] !== $image [ 'height' ] ) {
return false ;
}
// If cropping mode has changed, regenerate the image.
if ( '' === $size_data [ 'height' ] && empty ( $image [ 'uncropped' ] ) ) {
return false ;
}
return true ;
}
2018-02-13 12:02:24 +00:00
/**
* Show notice when job is running in background .
*/
public static function regenerating_notice () {
if ( ! self :: $background_process -> is_running () ) {
WC_Admin_Notices :: add_notice ( 'regenerating_thumbnails' );
} else {
WC_Admin_Notices :: remove_notice ( 'regenerating_thumbnails' );
}
}
/**
* Dismiss notice and cancel jobs .
*/
public static function dismiss_regenerating_notice () {
if ( self :: $background_process ) {
self :: $background_process -> kill_process ();
2018-02-13 19:22:01 +00:00
$log = wc_get_logger ();
$log -> info ( __ ( 'Cancelled product image regeneration job.' , 'woocommerce' ),
array (
'source' => 'wc-image-regeneration' ,
)
);
2018-02-13 12:02:24 +00:00
}
WC_Admin_Notices :: remove_notice ( 'regenerating_thumbnails' );
}
/**
* Regenerate images if the settings have changed since last re - generation .
*
* @ return void
*/
2018-02-13 17:57:45 +00:00
public static function maybe_regenerate_images () {
2018-02-13 12:02:24 +00:00
$size_hash = md5 ( wp_json_encode ( array (
wc_get_image_size ( 'thumbnail' ),
wc_get_image_size ( 'single' ),
2018-02-13 13:58:27 +00:00
wc_get_image_size ( 'gallery_thumbnail' ),
2018-02-13 12:02:24 +00:00
) ) );
if ( update_option ( 'woocommerce_maybe_regenerate_images_hash' , $size_hash ) ) {
// Size settings have changed. Trigger regen.
self :: queue_image_regeneration ();
}
2017-11-14 12:39:28 +00:00
}
/**
* Check if we should maybe generate a new image size if not already there .
*
* @ param array $image Properties of the image .
* @ param int $attachment_id Attachment ID .
* @ param string | array $size Image size .
* @ param bool $icon If icon or not .
* @ return array
*/
public static function maybe_resize_image ( $image , $attachment_id , $size , $icon ) {
2018-01-31 21:10:56 +00:00
if ( ! apply_filters ( 'woocommerce_resize_images' , true ) ) {
return $image ;
}
2017-12-12 17:24:52 +00:00
// Use a whitelist of sizes we want to resize. Ignore others.
2018-02-13 12:51:55 +00:00
if ( ! in_array ( $size , apply_filters ( 'woocommerce_image_sizes_to_resize' , array ( 'woocommerce_thumbnail' , 'woocommerce_gallery_thumbnail' , 'woocommerce_single' , 'shop_thumbnail' , 'shop_catalog' , 'shop_single' ) ), true ) ) {
2017-12-12 17:24:52 +00:00
return $image ;
}
// Get image metadata - we need it to proceed.
2017-11-14 12:39:28 +00:00
$imagemeta = wp_get_attachment_metadata ( $attachment_id );
2018-02-09 14:06:00 +00:00
if ( empty ( $imagemeta ) ) {
2017-11-14 12:39:28 +00:00
return $image ;
}
2018-02-14 13:20:34 +00:00
if ( ! isset ( $imagemeta [ 'sizes' ], $imagemeta [ 'sizes' ][ $size ] ) || ! self :: image_size_matches_settings ( $imagemeta [ 'sizes' ][ $size ], $size ) ) {
2018-02-13 13:58:27 +00:00
return self :: resize_and_return_image ( $attachment_id , $image , $size , $icon );
2018-02-09 14:06:00 +00:00
}
2017-11-14 12:39:28 +00:00
return $image ;
}
/**
2018-02-08 13:36:50 +00:00
* Ensure we are dealing with the correct image attachment
2017-11-14 12:39:28 +00:00
*
2018-02-08 13:36:50 +00:00
* @ param WP_Post $attachment Attachment object .
* @ return boolean
2017-11-14 12:39:28 +00:00
*/
2018-02-08 13:36:50 +00:00
public static function is_regeneratable ( $attachment ) {
if ( 'site-icon' === get_post_meta ( $attachment -> ID , '_wp_attachment_context' , true ) ) {
return false ;
2017-11-14 12:39:28 +00:00
}
2018-02-08 13:36:50 +00:00
if ( wp_attachment_is_image ( $attachment ) ) {
return true ;
2017-11-14 12:39:28 +00:00
}
2018-02-08 13:36:50 +00:00
return false ;
}
2017-11-14 12:39:28 +00:00
2018-02-08 13:36:50 +00:00
/**
2018-02-09 14:06:00 +00:00
* Only regenerate images for the requested size .
2018-02-08 13:36:50 +00:00
*
* @ param array $sizes Array of image sizes .
* @ return array
*/
public static function adjust_intermediate_image_sizes ( $sizes ) {
2018-02-14 13:20:34 +00:00
return array ( self :: $regenerate_size );
2018-02-08 13:36:50 +00:00
}
/**
2018-02-09 14:06:00 +00:00
* Generate the thumbnail filename and dimensions for a given file .
2018-02-08 13:36:50 +00:00
*
2018-02-09 14:06:00 +00:00
* @ 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 .
2018-02-08 13:36:50 +00:00
*/
2018-02-09 14:06:00 +00:00
private static function get_image ( $fullsizepath , $thumbnail_width , $thumbnail_height , $crop ) {
list ( $fullsize_width , $fullsize_height ) = getimagesize ( $fullsizepath );
2018-02-08 13:36:50 +00:00
2018-02-09 14:06:00 +00:00
$dimensions = image_resize_dimensions ( $fullsize_width , $fullsize_height , $thumbnail_width , $thumbnail_height , $crop );
$editor = wp_get_image_editor ( $fullsizepath );
2017-11-14 12:39:28 +00:00
2018-02-09 14:06:00 +00:00
if ( is_wp_error ( $editor ) ) {
2018-02-08 13:36:50 +00:00
return false ;
2017-11-14 12:39:28 +00:00
}
if ( ! $dimensions || ! is_array ( $dimensions ) ) {
2018-02-08 13:36:50 +00:00
return false ;
2017-11-14 12:39:28 +00:00
}
2018-02-09 14:06:00 +00:00
list ( , , , , $dst_w , $dst_h ) = $dimensions ;
$suffix = " { $dst_w } x { $dst_h } " ;
$file_ext = strtolower ( pathinfo ( $fullsizepath , PATHINFO_EXTENSION ) );
2017-11-14 12:39:28 +00:00
2018-02-09 14:06:00 +00:00
return array (
'filename' => $editor -> generate_filename ( $suffix , null , $file_ext ),
'width' => $dst_w ,
'height' => $dst_h ,
);
2018-02-08 13:36:50 +00:00
}
/**
* 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 ) {
2018-02-09 14:06:00 +00:00
$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 );
2018-02-08 13:36:50 +00:00
if ( ! $attachment || 'attachment' !== $attachment -> post_type || ! self :: is_regeneratable ( $attachment ) ) {
return $image ;
2017-11-14 12:39:28 +00:00
}
2018-02-09 14:06:00 +00:00
$fullsizepath = get_attached_file ( $attachment_id );
if ( false === $fullsizepath || is_wp_error ( $fullsizepath ) || ! file_exists ( $fullsizepath ) ) {
return $image ;
}
2018-02-08 13:36:50 +00:00
if ( ! function_exists ( 'wp_crop_image' ) ) {
include ABSPATH . 'wp-admin/includes/image.php' ;
}
2018-02-09 14:06:00 +00:00
$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' ],
);
2017-11-14 12:39:28 +00:00
}
2018-02-08 13:36:50 +00:00
2018-02-13 14:33:45 +00:00
$metadata = wp_get_attachment_metadata ( $attachment_id );
2018-02-08 13:36:50 +00:00
2018-02-14 13:20:34 +00:00
// Make sure registered image size matches the size we're requesting.
add_image_size ( $size . '_preview' , $image_size [ 'width' ], $image_size [ 'height' ], $image_size [ 'crop' ] );
self :: $regenerate_size = $size . '_preview' ;
// We only want to regen a specific image size.
2018-02-08 13:36:50 +00:00
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 ;
2017-11-14 12:39:28 +00:00
}
2018-02-13 14:33:45 +00:00
// Since this is only a preview we should not update the actual size. That will be done later by the background job.
2018-02-14 13:20:34 +00:00
if ( isset ( $new_metadata [ 'sizes' ][ $size . '_preview' ] ) ) {
$metadata [ 'sizes' ][ $size . '_preview' ] = $new_metadata [ 'sizes' ][ $size . '_preview' ];
2018-02-13 14:33:45 +00:00
wp_update_attachment_metadata ( $attachment_id , $metadata );
2018-02-08 13:36:50 +00:00
}
2018-02-09 14:06:00 +00:00
// Now we've done our regen, attempt to return the new size.
2018-02-13 14:33:45 +00:00
$new_image = image_downsize ( $attachment_id , $size . '_preview' );
2018-02-09 14:06:00 +00:00
return $new_image ? $new_image : $image ;
2017-11-14 12:39:28 +00:00
}
2017-11-16 12:37:33 +00:00
/**
* Get list of images and queue them for regeneration
*
* @ return void
*/
2018-02-13 07:55:50 +00:00
public static function queue_image_regeneration () {
2017-11-16 12:37:33 +00:00
global $wpdb ;
2017-11-15 07:23:36 +00:00
// First lets cancel existing running queue to avoid running it more than once.
2018-02-01 14:03:28 +00:00
self :: $background_process -> kill_process ();
2017-11-15 07:23:36 +00:00
// Now lets find all product image attachments IDs and pop them onto the queue.
$images = $wpdb -> get_results ( // @codingStandardsIgnoreLine
" SELECT ID
FROM $wpdb -> posts
WHERE post_type = 'attachment'
AND post_mime_type LIKE 'image/%'
ORDER BY ID DESC "
);
foreach ( $images as $image ) {
2017-11-15 08:03:59 +00:00
self :: $background_process -> push_to_queue ( array (
2017-11-15 07:23:36 +00:00
'attachment_id' => $image -> ID ,
) );
}
// Lets dispatch the queue to start processing.
2017-11-15 08:03:59 +00:00
self :: $background_process -> save () -> dispatch ();
2017-11-14 12:39:28 +00:00
}
}
2018-02-13 12:51:55 +00:00
2018-02-14 13:20:34 +00:00
add_action ( 'init' , array ( 'WC_Regenerate_Images' , 'init' ) );