diff --git a/classes/CDN/PushCDNInterface.php b/classes/CDN/PushCDNInterface.php index 2d9e484ce..763f91a94 100644 --- a/classes/CDN/PushCDNInterface.php +++ b/classes/CDN/PushCDNInterface.php @@ -89,7 +89,7 @@ public function get_file_url( $file_name = false ); * @access public * @author Grégory Viguier * - * @param string $file_name Name of the file. Leave empty for the full size file. + * @param string $file_name Name of the file. Leave empty for the full size file. Use 'original' to get the path to the original file. * @return string Path to the file. */ public function get_file_path( $file_name = false ); diff --git a/classes/Context/AbstractContext.php b/classes/Context/AbstractContext.php index 70ef81d17..ca3d475aa 100644 --- a/classes/Context/AbstractContext.php +++ b/classes/Context/AbstractContext.php @@ -63,16 +63,6 @@ abstract class AbstractContext implements ContextInterface { */ protected $thumbnail_sizes; - /** - * Tell if the optimization process is allowed resize in this context. - * - * @var bool - * @since 1.9 - * @access protected - * @author Grégory Viguier - */ - protected $can_resize; - /** * Tell if the optimization process is allowed to backup in this context. * @@ -167,7 +157,7 @@ public function get_thumbnail_sizes() { * @return bool */ public function can_resize() { - return $this->can_resize; + return $this->get_resizing_threshold() > 0; } /** diff --git a/classes/Context/ContextInterface.php b/classes/Context/ContextInterface.php index d9a52c5d6..ead8361c7 100644 --- a/classes/Context/ContextInterface.php +++ b/classes/Context/ContextInterface.php @@ -78,6 +78,18 @@ public function get_allowed_mime_types(); */ public function get_thumbnail_sizes(); + /** + * Get images max width for this context. This is used when resizing. + * 0 means to not resize. + * + * @since 1.9.8 + * @access public + * @author Grégory Viguier + * + * @return int + */ + public function get_resizing_threshold(); + /** * Tell if the optimization process is allowed resize in this context. * diff --git a/classes/Context/CustomFolders.php b/classes/Context/CustomFolders.php index 8c76ec5c1..a8ebb404c 100644 --- a/classes/Context/CustomFolders.php +++ b/classes/Context/CustomFolders.php @@ -41,14 +41,18 @@ class CustomFolders extends AbstractContext { protected $thumbnail_sizes = []; /** - * Tell if the optimization process is allowed resize in this context. + * Get images max width for this context. This is used when resizing. + * 0 means to not resize. * - * @var bool - * @since 1.9 - * @access protected + * @since 1.9.8 + * @access public * @author Grégory Viguier + * + * @return int */ - protected $can_resize = false; + public function get_resizing_threshold() { + return 0; + } /** * Tell if the optimization process is allowed to backup in this context. diff --git a/classes/Context/Noop.php b/classes/Context/Noop.php index 863ca8a63..2cae5df62 100644 --- a/classes/Context/Noop.php +++ b/classes/Context/Noop.php @@ -77,16 +77,17 @@ public function get_thumbnail_sizes() { } /** - * Tell if the optimization process is allowed resize in this context. + * Get images max width for this context. This is used when resizing. + * 0 means to not resize. * - * @since 1.9 + * @since 1.9.8 * @access public * @author Grégory Viguier * - * @return bool + * @return int */ - public function can_resize() { - return false; + public function get_resizing_threshold() { + return 0; } /** diff --git a/classes/Context/WP.php b/classes/Context/WP.php index 856808d26..bd10e3fd8 100644 --- a/classes/Context/WP.php +++ b/classes/Context/WP.php @@ -22,6 +22,16 @@ class WP extends AbstractContext { */ protected $context = 'wp'; + /** + * Images max width for this context. This is used when resizing. + * + * @var int + * @since 1.9.8 + * @access protected + * @author Grégory Viguier + */ + protected $resizing_threshold; + /** * Get the thumbnail sizes for this context, except the full size. * @@ -50,22 +60,27 @@ public function get_thumbnail_sizes() { } /** - * Tell if the optimization process is allowed resize in this context. + * Get images max width for this context. This is used when resizing. + * 0 means to not resize. * - * @since 1.9 + * @since 1.9.8 * @access public * @author Grégory Viguier * - * @return bool + * @return int */ - public function can_resize() { - if ( isset( $this->can_resize ) ) { - return $this->can_resize; + public function get_resizing_threshold() { + if ( isset( $this->resizing_threshold ) ) { + return $this->resizing_threshold; } - $this->can_resize = get_imagify_option( 'resize_larger' ) && get_imagify_option( 'resize_larger_w' ) > 0; + if ( ! get_imagify_option( 'resize_larger' ) ) { + $this->resizing_threshold = 0; + } else { + $this->resizing_threshold = max( 0, get_imagify_option( 'resize_larger_w' ) ); + } - return $this->can_resize; + return $this->resizing_threshold; } /** diff --git a/classes/Media/AbstractMedia.php b/classes/Media/AbstractMedia.php index c0787e821..87647597f 100644 --- a/classes/Media/AbstractMedia.php +++ b/classes/Media/AbstractMedia.php @@ -237,13 +237,41 @@ public function get_original_path() { return false; } - $backup_path = $this->get_raw_original_path(); + $original_path = $this->get_raw_original_path(); - if ( ! $backup_path || ! $this->filesystem->exists( $backup_path ) ) { + if ( ! $original_path || ! $this->filesystem->exists( $original_path ) ) { return false; } - return $backup_path; + return $original_path; + } + + + /** ----------------------------------------------------------------------------------------- */ + /** FULL SIZE FILE ========================================================================== */ + /** ----------------------------------------------------------------------------------------- */ + + /** + * Get the path to the media’s full size file if the file exists. + * + * @since 1.9.8 + * @access public + * @author Grégory Viguier + * + * @return string|bool The file path. False if it doesn't exist. + */ + public function get_fullsize_path() { + if ( ! $this->is_valid() ) { + return false; + } + + $original_path = $this->get_raw_fullsize_path(); + + if ( ! $original_path || ! $this->filesystem->exists( $original_path ) ) { + return false; + } + + return $original_path; } @@ -398,7 +426,7 @@ public function update_dimensions() { return false; } - $dimensions = $this->filesystem->get_image_size( $this->get_raw_original_path() ); + $dimensions = $this->filesystem->get_image_size( $this->get_raw_fullsize_path() ); if ( ! $dimensions ) { // Could not get the new dimensions. @@ -487,7 +515,7 @@ protected function get_file_type() { return $this->file_type; } - $path = $this->get_raw_original_path(); + $path = $this->get_raw_fullsize_path(); if ( ! $path ) { return $this->file_type; @@ -506,7 +534,7 @@ protected function get_file_type() { * @see $this->get_media_files() * @author Grégory Viguier * - * @param array $files An array with the size names as keys ('full' is used for the original file), and arrays of data as values. + * @param array $files An array with the size names as keys ('full' is used for the full size file), and arrays of data as values. * @return array */ protected function filter_media_files( $files ) { @@ -516,7 +544,7 @@ protected function filter_media_files( $files ) { * @since 1.9 * @author Grégory Viguier * - * @param array $files An array with the size names as keys ('full' is used for the original file), and arrays of data as values. + * @param array $files An array with the size names as keys ('full' is used for the full size file), and arrays of data as values. * @param MediaInterface $media This instance. */ return (array) apply_filters( 'imagify_media_files', $files, $this ); diff --git a/classes/Media/CustomFolders.php b/classes/Media/CustomFolders.php index 181f2e4ef..72456cdb0 100644 --- a/classes/Media/CustomFolders.php +++ b/classes/Media/CustomFolders.php @@ -11,6 +11,7 @@ */ class CustomFolders extends AbstractMedia { use \Imagify\Traits\MediaRowTrait; + use \Imagify\Deprecated\Traits\Media\CustomFoldersDeprecatedTrait; /** * Context (where the media "comes from"). @@ -90,15 +91,47 @@ public static function constructor_accepts( $id ) { /** ----------------------------------------------------------------------------------------- */ /** - * Get the original media's URL. + * Get the original media's path. * * @since 1.9 * @access public * @author Grégory Viguier * + * @return string|bool The file path. False on failure. + */ + public function get_raw_original_path() { + if ( ! $this->is_valid() ) { + return false; + } + + if ( $this->get_cdn() ) { + return $this->get_cdn()->get_file_path( 'original' ); + } + + $row = $this->get_row(); + + if ( ! $row || empty( $row['path'] ) ) { + return false; + } + + return \Imagify_Files_Scan::remove_placeholder( $row['path'] ); + } + + + /** ----------------------------------------------------------------------------------------- */ + /** FULL SIZE FILE ========================================================================== */ + /** ----------------------------------------------------------------------------------------- */ + + /** + * Get the URL of the media’s full size file. + * + * @since 1.9.8 + * @access public + * @author Grégory Viguier + * * @return string|bool The file URL. False on failure. */ - public function get_original_url() { + public function get_fullsize_url() { if ( ! $this->is_valid() ) { return false; } @@ -117,15 +150,15 @@ public function get_original_url() { } /** - * Get the original media's path. + * Get the path to the media’s full size file, even if the file doesn't exist. * - * @since 1.9 + * @since 1.9.8 * @access public * @author Grégory Viguier * * @return string|bool The file path. False on failure. */ - public function get_raw_original_path() { + public function get_raw_fullsize_path() { if ( ! $this->is_valid() ) { return false; } @@ -224,14 +257,14 @@ public function has_required_media_data() { } /** - * Get the list of the files of this media, including the original file. + * Get the list of the files of this media, including the full size file. * * @since 1.9 * @access public * @author Grégory Viguier * * @return array { - * An array with the size names as keys ('full' is used for the original file), and arrays of data as values: + * An array with the size names as keys ('full' is used for the full size file), and arrays of data as values: * * @type string $size The size name. * @type string $path Absolute path to the file. @@ -246,9 +279,9 @@ public function get_media_files() { return []; } - $original_path = $this->get_raw_original_path(); + $fullsize_path = $this->get_raw_fullsize_path(); - if ( ! $original_path ) { + if ( ! $fullsize_path ) { return []; } @@ -256,7 +289,7 @@ public function get_media_files() { $sizes = [ 'full' => [ 'size' => 'full', - 'path' => $original_path, + 'path' => $fullsize_path, 'width' => $dimensions['width'], 'height' => $dimensions['height'], 'mime-type' => $this->get_mime_type(), diff --git a/classes/Media/MediaInterface.php b/classes/Media/MediaInterface.php index 2e911862e..79be27807 100644 --- a/classes/Media/MediaInterface.php +++ b/classes/Media/MediaInterface.php @@ -88,37 +88,64 @@ public function get_cdn(); /** ----------------------------------------------------------------------------------------- */ /** - * Get the original media's URL. + * Get the original file path, even if the file doesn't exist. * * @since 1.9 * @access public * @author Grégory Viguier * - * @return string|bool The file URL. False on failure. + * @return string|bool The file path. False on failure. */ - public function get_original_url(); + public function get_raw_original_path(); /** - * Get the original file path, even if the file doesn't exist. + * Get the original media's path if the file exists. * * @since 1.9 * @access public * @author Grégory Viguier * + * @return string|bool The file path. False if it doesn't exist. + */ + public function get_original_path(); + + + /** ----------------------------------------------------------------------------------------- */ + /** FULL SIZE FILE ========================================================================== */ + /** ----------------------------------------------------------------------------------------- */ + + /** + * Get the URL of the media’s full size file. + * + * @since 1.9.8 + * @access public + * @author Grégory Viguier + * + * @return string|bool The file URL. False on failure. + */ + public function get_fullsize_url(); + + /** + * Get the path to the media’s full size file, even if the file doesn't exist. + * + * @since 1.9.8 + * @access public + * @author Grégory Viguier + * * @return string|bool The file path. False on failure. */ - public function get_raw_original_path(); + public function get_raw_fullsize_path(); /** - * Get the original media's path if the file exists. + * Get the path to the media’s full size file if the file exists. * - * @since 1.9 + * @since 1.9.8 * @access public * @author Grégory Viguier * * @return string|bool The file path. False if it doesn't exist. */ - public function get_original_path(); + public function get_fullsize_path(); /** ----------------------------------------------------------------------------------------- */ diff --git a/classes/Media/Noop.php b/classes/Media/Noop.php index 4ff67fc09..850e0a451 100644 --- a/classes/Media/Noop.php +++ b/classes/Media/Noop.php @@ -10,6 +10,7 @@ * @author Grégory Viguier */ class Noop implements MediaInterface { + use \Imagify\Deprecated\Traits\Media\NoopDeprecatedTrait; /** * Tell if the given entry can be accepted in the constructor. @@ -97,41 +98,72 @@ public function get_cdn() { /** ----------------------------------------------------------------------------------------- */ /** - * Get the original media's URL. + * Get the original file path, even if the file doesn't exist. * * @since 1.9 * @access public * @author Grégory Viguier * - * @return string|bool The file URL. False on failure. + * @return string|bool The file path. False on failure. */ - public function get_original_url() { + public function get_raw_original_path() { return false; } /** - * Get the original file path, even if the file doesn't exist. + * Get the original media's path if the file exists. * * @since 1.9 * @access public * @author Grégory Viguier * + * @return string|bool The file path. False if it doesn't exist. + */ + public function get_original_path() { + return false; + } + + + /** ----------------------------------------------------------------------------------------- */ + /** FULL SIZE FILE ========================================================================== */ + /** ----------------------------------------------------------------------------------------- */ + + /** + * Get the URL of the media’s full size file. + * + * @since 1.9.8 + * @access public + * @author Grégory Viguier + * + * @return string|bool The file URL. False on failure. + */ + public function get_fullsize_url() { + return false; + } + + /** + * Get the path to the media’s full size file, even if the file doesn't exist. + * + * @since 1.9.8 + * @access public + * @author Grégory Viguier + * * @return string|bool The file path. False on failure. */ - public function get_raw_original_path() { + public function get_raw_fullsize_path() { return false; } /** - * Get the original media's path if the file exists. + * Get the path to the media’s full size file if the file exists. * - * @since 1.9 + * @since 1.9.8 * @access public * @author Grégory Viguier * * @return string|bool The file path. False if it doesn't exist. */ - public function get_original_path() { + public function get_fullsize_path() { return false; } diff --git a/classes/Media/WP.php b/classes/Media/WP.php index e3d3b5022..1bc3d7671 100644 --- a/classes/Media/WP.php +++ b/classes/Media/WP.php @@ -10,6 +10,17 @@ * @author Grégory Viguier */ class WP extends AbstractMedia { + use \Imagify\Deprecated\Traits\Media\WPDeprecatedTrait; + + /** + * Tell if we’re playing in WP 5.3’s garden. + * + * @var bool + * @since 1.9.8 + * @access protected + * @author Grégory Viguier + */ + protected $is_wp53; /** * The constructor. @@ -58,15 +69,52 @@ public static function constructor_accepts( $id ) { /** ----------------------------------------------------------------------------------------- */ /** - * Get the original media's URL. + * Get the original file path, even if the file doesn't exist. * * @since 1.9 * @access public * @author Grégory Viguier * + * @return string|bool The file path. False on failure. + */ + public function get_raw_original_path() { + if ( ! $this->is_valid() ) { + return false; + } + + if ( $this->get_cdn() ) { + return $this->get_cdn()->get_file_path( 'original' ); + } + + if ( $this->is_wp_53() ) { + // `wp_get_original_image_path()` may return false. + $path = wp_get_original_image_path( $this->id ); + } else { + $path = false; + } + + if ( ! $path ) { + $path = get_attached_file( $this->id ); + } + + return $path ? $path : false; + } + + + /** ----------------------------------------------------------------------------------------- */ + /** FULL SIZE FILE ========================================================================== */ + /** ----------------------------------------------------------------------------------------- */ + + /** + * Get the URL of the media’s full size file. + * + * @since 1.9.8 + * @access public + * @author Grégory Viguier + * * @return string|bool The file URL. False on failure. */ - public function get_original_url() { + public function get_fullsize_url() { if ( ! $this->is_valid() ) { return false; } @@ -81,15 +129,15 @@ public function get_original_url() { } /** - * Get the original media's path. + * Get the path to the media’s full size file, even if the file doesn't exist. * - * @since 1.9 + * @since 1.9.8 * @access public * @author Grégory Viguier * * @return string|bool The file path. False on failure. */ - public function get_raw_original_path() { + public function get_raw_fullsize_path() { if ( ! $this->is_valid() ) { return false; } @@ -149,6 +197,7 @@ public function get_raw_backup_path() { /** * Create the media thumbnails. + * With WP 5.3+, this will also generate a new full size file if the original file is wider or taller than a defined threshold. * * @since 1.9 * @access public @@ -165,10 +214,34 @@ public function generate_thumbnails() { require_once ABSPATH . 'wp-admin/includes/image.php'; } - $metadata = wp_generate_attachment_metadata( $this->get_id(), $this->get_raw_original_path() ); + // Store the path to the current full size file before generating the thumbnails. + $old_full_size_path = $this->get_raw_fullsize_path(); + $metadata = wp_generate_attachment_metadata( $this->get_id(), $this->get_raw_original_path() ); + + if ( empty( $metadata['file'] ) ) { + // Σ(゚Д゚). + update_post_meta( $this->get_id(), '_wp_attachment_metadata', $metadata ); + + return true; + } + + /** + * Don't change the full size file name. + * WP 5.3+ will rename the full size file if the resizing threshold has changed (not the same as the one used to generate it previously). + * This will force WP to keep the previous file name. + */ + $old_full_size_file_name = $this->filesystem->file_name( $old_full_size_path ); + $new_full_size_file_name = $this->filesystem->file_name( $metadata['file'] ); + + if ( $new_full_size_file_name !== $old_full_size_file_name ) { + $new_full_size_path = $this->filesystem->dir_path( $old_full_size_path ) . $new_full_size_file_name; - if ( empty( $metadata['sizes'] ) ) { - return new \WP_Error( 'thumbnails_creation_filure', __( 'New thumbnails could not be created.', 'imagify' ) ); + $moved = $this->filesystem->move( $new_full_size_path, $old_full_size_path, true ); + + if ( $moved ) { + $metadata['file'] = $this->filesystem->dir_path( $metadata['file'] ) . $old_full_size_file_name; + update_post_meta( $this->get_id(), '_wp_attached_file', $metadata['file'] ); + } } update_post_meta( $this->get_id(), '_wp_attachment_metadata', $metadata ); @@ -205,14 +278,14 @@ public function has_required_media_data() { } /** - * Get the list of the files of this media, including the original file. + * Get the list of the files of this media, including the full size file. * * @since 1.9 * @access public * @author Grégory Viguier * * @return array { - * An array with the size names as keys ('full' is used for the original file), and arrays of data as values: + * An array with the size names as keys ('full' is used for the full size file), and arrays of data as values: * * @type string $size The size name. * @type string $path Absolute path to the file. @@ -227,9 +300,9 @@ public function get_media_files() { return []; } - $original_path = $this->get_raw_original_path(); + $fullsize_path = $this->get_raw_fullsize_path(); - if ( ! $original_path ) { + if ( ! $fullsize_path ) { return []; } @@ -237,7 +310,7 @@ public function get_media_files() { $all_sizes = [ 'full' => [ 'size' => 'full', - 'path' => $original_path, + 'path' => $fullsize_path, 'width' => $dimensions['width'], 'height' => $dimensions['height'], 'mime-type' => $this->get_mime_type(), @@ -257,7 +330,7 @@ public function get_media_files() { return $all_sizes; } - $dir_path = $this->filesystem->dir_path( $original_path ); + $dir_path = $this->filesystem->dir_path( $fullsize_path ); $disallowed_sizes = get_imagify_option( 'disallowed-sizes' ); $is_active_for_network = imagify_is_active_for_network(); @@ -304,7 +377,7 @@ public function get_dimensions() { * Update the media data dimensions. * * @since 1.9 - * @access public + * @access protected * @author Grégory Viguier * * @param array $dimensions { @@ -330,4 +403,28 @@ protected function update_media_data_dimensions( $dimensions ) { update_post_meta( $this->get_id(), '_wp_attachment_metadata', $metadata ); } + + + /** ----------------------------------------------------------------------------------------- */ + /** INTERNAL TOOLS ========================================================================== */ + /** ----------------------------------------------------------------------------------------- */ + + /** + * Tell if we’re playing in WP 5.3’s garden. + * + * @since 1.9.8 + * @access protected + * @author Grégory Viguier + * + * @return bool + */ + protected function is_wp_53() { + if ( isset( $this->is_wp53 ) ) { + return $this->is_wp53; + } + + $this->is_wp53 = function_exists( 'wp_get_original_image_path' ); + + return $this->is_wp53; + } } diff --git a/classes/Optimization/Data/AbstractData.php b/classes/Optimization/Data/AbstractData.php index c3671edaa..13168d503 100644 --- a/classes/Optimization/Data/AbstractData.php +++ b/classes/Optimization/Data/AbstractData.php @@ -321,7 +321,7 @@ public function get_optimized_size( $human_format = true, $decimals = 2, $use_we if ( $use_webp && ! empty( $data['sizes'][ $webp_size_name ]['success'] ) ) { // Try with the webp file first. - $filepath = $media->get_raw_original_path(); + $filepath = $media->get_raw_fullsize_path(); $filepath = $filepath ? imagify_path_to_webp( $filepath ) : false; if ( ! $filepath || ! $this->filesystem->exists( $filepath ) ) { @@ -331,7 +331,7 @@ public function get_optimized_size( $human_format = true, $decimals = 2, $use_we if ( ! $filepath ) { // No webp? The full size then. - $filepath = $media->get_original_path(); + $filepath = $media->get_fullsize_path(); } if ( ! $filepath ) { diff --git a/classes/Optimization/Data/CustomFolders.php b/classes/Optimization/Data/CustomFolders.php index 80363d2d7..1ba511c9b 100644 --- a/classes/Optimization/Data/CustomFolders.php +++ b/classes/Optimization/Data/CustomFolders.php @@ -153,7 +153,7 @@ public function update_size_optimization_data( $size, array $data ) { $old_data['status'] = $data['status']; $old_data['modified'] = 0; - $file_path = $this->get_media()->get_original_path(); + $file_path = $this->get_media()->get_fullsize_path(); if ( $file_path ) { $old_data['hash'] = md5_file( $file_path ); @@ -223,6 +223,48 @@ public function delete_optimization_data() { $this->update_row( $this->get_reset_data() ); } + /** + * Delete the optimization data for the given sizes. + * If all sizes are removed, all optimization data is deleted. + * Status and level are not modified nor removed if the "full" size is removed. This leaves the media in a Schrödinger state. + * + * @since 1.9.8 + * @access public + * @author Grégory Viguier + * + * @param array $sizes A list of sizes to remove. + */ + public function delete_sizes_optimization_data( array $sizes ) { + if ( ! $sizes || ! $this->is_valid() ) { + return; + } + + $data = array_merge( $this->get_reset_data(), $this->get_row() ); + + $data['data']['sizes'] = ! empty( $data['data']['sizes'] ) && is_array( $data['data']['sizes'] ) ? $data['data']['sizes'] : []; + + if ( ! $data['data']['sizes'] ) { + return; + } + + $remaining_sizes_data = array_diff_key( $data['data']['sizes'], array_flip( $sizes ) ); + + if ( ! $remaining_sizes_data ) { + // All sizes have been removed: delete everything. + $this->delete_optimization_data(); + return; + } + + if ( count( $remaining_sizes_data ) === count( $data['data']['sizes'] ) ) { + // Nothing has been removed. + return; + } + + $data['data']['sizes'] = $remaining_sizes_data; + + $this->update_row( $data ); + } + /** * Get default values used to reset optimization data. * @@ -263,7 +305,7 @@ protected function get_reset_data() { $imagify_columns = $column_defaults; // Also set the new file hash. - $file_path = $this->get_media()->get_original_path(); + $file_path = $this->get_media()->get_fullsize_path(); if ( $file_path ) { $imagify_columns['hash'] = md5_file( $file_path ); diff --git a/classes/Optimization/Data/DataInterface.php b/classes/Optimization/Data/DataInterface.php index a15574973..b8f272d4e 100644 --- a/classes/Optimization/Data/DataInterface.php +++ b/classes/Optimization/Data/DataInterface.php @@ -147,6 +147,19 @@ public function update_size_optimization_data( $size, array $data ); */ public function delete_optimization_data(); + /** + * Delete the optimization data for the given sizes. + * If all sizes are removed, all optimization data is deleted. + * Status and level are not modified nor removed if the "full" size is removed. This leaves the media in a Schrödinger state. + * + * @since 1.9.8 + * @access public + * @author Grégory Viguier + * + * @param array $sizes A list of sizes to remove. + */ + public function delete_sizes_optimization_data( array $sizes ); + /** * Get the media's optimization level. * diff --git a/classes/Optimization/Data/Noop.php b/classes/Optimization/Data/Noop.php index 9a49143d4..4af92e862 100644 --- a/classes/Optimization/Data/Noop.php +++ b/classes/Optimization/Data/Noop.php @@ -139,6 +139,19 @@ public function update_size_optimization_data( $size, array $data ) {} */ public function delete_optimization_data() {} + /** + * Delete the optimization data for the given sizes. + * If all sizes are removed, all optimization data is deleted. + * Status and level are not modified nor removed if the "full" size is removed. This leaves the media in a Schrödinger state. + * + * @since 1.9.8 + * @access public + * @author Grégory Viguier + * + * @param array $sizes A list of sizes to remove. + */ + public function delete_sizes_optimization_data( array $sizes ) {} + /** * Get the media's optimization level. * diff --git a/classes/Optimization/Data/WP.php b/classes/Optimization/Data/WP.php index f56dde167..220d48916 100644 --- a/classes/Optimization/Data/WP.php +++ b/classes/Optimization/Data/WP.php @@ -140,4 +140,63 @@ public function delete_optimization_data() { delete_post_meta( $id, '_imagify_status' ); delete_post_meta( $id, '_imagify_optimization_level' ); } + + /** + * Delete the optimization data for the given sizes. + * If all sizes are removed, all optimization data is deleted. + * Status and level are not modified nor removed if the "full" size is removed. This leaves the media in a Schrödinger state. + * + * @since 1.9.8 + * @access public + * @author Grégory Viguier + * + * @param array $sizes A list of sizes to remove. + */ + public function delete_sizes_optimization_data( array $sizes ) { + if ( ! $sizes || ! $this->is_valid() ) { + return; + } + + $media_id = $this->get_media()->get_id(); + $data = get_post_meta( $media_id, '_imagify_data', true ); + + if ( empty( $data['sizes'] ) || ! is_array( $data['sizes'] ) ) { + return; + } + + $remaining_sizes_data = array_diff_key( $data['sizes'], array_flip( $sizes ) ); + + if ( ! $remaining_sizes_data ) { + // All sizes have been removed: delete everything. + $this->delete_optimization_data(); + return; + } + + if ( count( $remaining_sizes_data ) === count( $data['sizes'] ) ) { + // Nothing has been removed. + return; + } + + $data['sizes'] = $remaining_sizes_data; + + // Update stats. + $data['stats'] = [ + 'original_size' => 0, + 'optimized_size' => 0, + 'percent' => 0, + ]; + + foreach ( $data['sizes'] as $size_data ) { + if ( empty( $size_data['success'] ) ) { + continue; + } + + $data['stats']['original_size'] += $size_data['original_size']; + $data['stats']['optimized_size'] += $size_data['optimized_size']; + } + + $data['stats']['percent'] = round( ( ( $data['stats']['original_size'] - $data['stats']['optimized_size'] ) / $data['stats']['original_size'] ) * 100, 2 ); + + update_post_meta( $media_id, '_imagify_data', $data ); + } } diff --git a/classes/Optimization/File.php b/classes/Optimization/File.php index dee398e40..dd90aeca3 100644 --- a/classes/Optimization/File.php +++ b/classes/Optimization/File.php @@ -173,7 +173,7 @@ public function can_be_processed() { /** ----------------------------------------------------------------------------------------- */ /** - * Resize an image if it is bigger than the maximum width defined in the settings. + * Resize (and rotate) an image if it is bigger than the maximum width provided. * * @since 1.9 * @access public @@ -186,7 +186,7 @@ public function can_be_processed() { * @type int $width The image width. * @type int $height The image height. * } - * @param int $max_width Maximum width defined in the settings. + * @param int $max_width Maximum width to resize to. * @return string|WP_Error Path the the resized image. A WP_Error object on failure. */ public function resize( $dimensions = [], $max_width = 0 ) { @@ -196,6 +196,13 @@ public function resize( $dimensions = [], $max_width = 0 ) { return $can_be_processed; } + if ( ! $max_width ) { + return new \WP_Error( + 'no_resizing_threshold', + __( 'No threshold provided for resizing.', 'imagify' ) + ); + } + if ( ! $this->is_image() ) { return new \WP_Error( 'not_an_image', @@ -219,14 +226,43 @@ public function resize( $dimensions = [], $max_width = 0 ) { $orientation = isset( $exif['Orientation'] ) ? (int) $exif['Orientation'] : 1; switch ( $orientation ) { + case 2: + // Flip horizontally. + $editor->flip( true, false ); + break; case 3: - $editor->rotate( 180 ); + // Rotate 180 degrees or flip horizontally and vertically. + // Flipping seems faster/uses less resources. + $editor->flip( true, true ); + break; + case 4: + // Flip vertically. + $editor->flip( false, true ); + break; + case 5: + // Rotate 90 degrees counter-clockwise and flip vertically. + $result = $editor->rotate( 90 ); + + if ( ! is_wp_error( $result ) ) { + $editor->flip( false, true ); + } break; case 6: - $editor->rotate( -90 ); + // Rotate 90 degrees clockwise (270 counter-clockwise). + $editor->rotate( 270 ); + break; + case 7: + // Rotate 90 degrees counter-clockwise and flip horizontally. + $result = $editor->rotate( 90 ); + + if ( ! is_wp_error( $result ) ) { + $editor->flip( true, false ); + } break; case 8: + // Rotate 90 degrees counter-clockwise. $editor->rotate( 90 ); + break; } } @@ -234,10 +270,6 @@ public function resize( $dimensions = [], $max_width = 0 ) { $dimensions = $this->get_dimensions(); } - if ( ! $max_width ) { - $max_width = $this->get_option( 'resize_larger_w' ); - } - // Prevent removal of the exif data when resizing (only works with Imagick). add_filter( 'image_strip_meta', '__return_false', 789 ); @@ -351,23 +383,21 @@ public function create_thumbnail( $destination ) { * Backup a file. * * @since 1.9 + * @since 1.9.8 Added $backup_source argument. * @access public * @author Grégory Viguier * - * @param string $backup_path The backup path. - * @return bool|WP_Error True on success. False if the backup option is disabled. A WP_Error object on failure. + * @param string $backup_path The backup path. + * @param string $backup_source Path to the file to backup. This is useful in WP 5.3+ when we want to optimize the full size: in that case we need to backup the original file. + * @return bool|WP_Error True on success. False if the backup option is disabled. A WP_Error object on failure. */ - public function backup( $backup_path = null ) { + public function backup( $backup_path = null, $backup_source = null ) { $can_be_processed = $this->can_be_processed(); if ( is_wp_error( $can_be_processed ) ) { return $can_be_processed; } - if ( ! isset( $backup_path ) ) { - $backup_path = get_imagify_attachment_backup_path( $this->path ); - } - // Make sure the backups directory has no errors. if ( ! $backup_path ) { return new \WP_Error( 'wp_upload_error', __( 'Error while retrieving the backups directory path.', 'imagify' ) ); @@ -380,6 +410,8 @@ public function backup( $backup_path = null ) { return new \WP_Error( 'backup_dir_not_writable', __( 'The backup directory is not writable.', 'imagify' ) ); } + $path = $backup_source && $this->filesystem->exists( $backup_source ) ? $backup_source : $this->path; + /** * Allow to overwrite the backup file if it already exists. * @@ -390,15 +422,15 @@ public function backup( $backup_path = null ) { * @param string $path The file path. * @param string $backup_path The backup path. */ - $overwrite = apply_filters( 'imagify_backup_overwrite_backup', false, $this->path, $backup_path ); + $overwrite = apply_filters( 'imagify_backup_overwrite_backup', false, $path, $backup_path ); // Copy the file. - $this->filesystem->copy( $this->path, $backup_path, $overwrite, FS_CHMOD_FILE ); + $this->filesystem->copy( $path, $backup_path, $overwrite, FS_CHMOD_FILE ); // Make sure the backup copy exists. if ( ! $this->filesystem->exists( $backup_path ) ) { return new \WP_Error( 'backup_doesnt_exist', __( 'The file could not be saved.', 'imagify' ), array( - 'file_path' => $this->filesystem->make_path_relative( $this->path ), + 'file_path' => $this->filesystem->make_path_relative( $path ), 'backup_path' => $this->filesystem->make_path_relative( $backup_path ), ) ); } @@ -430,6 +462,7 @@ public function optimize( $args = [] ) { $args = array_merge( [ 'backup' => true, 'backup_path' => null, + 'backup_source' => null, 'optimization_level' => 0, 'keep_exif' => true, 'convert' => '', @@ -471,7 +504,7 @@ public function optimize( $args = [] ) { do_action_deprecated( 'before_do_imagify', [ $this->path, $args['backup'] ], '1.9', 'imagify_before_optimize_file' ); if ( $args['backup'] ) { - $backup_result = $this->backup( $args['backup_path'] ); + $backup_result = $this->backup( $args['backup_path'], $args['backup_source'] ); if ( is_wp_error( $backup_result ) ) { // Stop the process if we can't backup the file. diff --git a/classes/Optimization/Process/AbstractProcess.php b/classes/Optimization/Process/AbstractProcess.php index cd616cc9c..31145deed 100644 --- a/classes/Optimization/Process/AbstractProcess.php +++ b/classes/Optimization/Process/AbstractProcess.php @@ -14,6 +14,7 @@ * @author Grégory Viguier */ abstract class AbstractProcess implements ProcessInterface { + use \Imagify\Deprecated\Traits\Optimization\Process\AbstractProcessDeprecatedTrait; /** * The suffix used in the thumbnail size name. @@ -176,15 +177,15 @@ public function get_media() { } /** - * Get the File instance. + * Get the File instance of the original file. * - * @since 1.9 + * @since 1.9.8 * @access public * @author Grégory Viguier * * @return File|false */ - public function get_file() { + public function get_original_file() { if ( isset( $this->file ) ) { return $this->file; } @@ -198,6 +199,29 @@ public function get_file() { return $this->file; } + /** + * Get the File instance of the full size file. + * + * @since 1.9.8 + * @access public + * @author Grégory Viguier + * + * @return File|false + */ + public function get_fullsize_file() { + if ( isset( $this->file ) ) { + return $this->file; + } + + $this->file = false; + + if ( $this->get_media() ) { + $this->file = new File( $this->get_media()->get_raw_fullsize_path() ); + } + + return $this->file; + } + /** * Tell if the current media is valid. * @@ -381,6 +405,9 @@ public function optimize_sizes( $sizes, $optimization_level = null, $args = [] ) if ( 'image/webp' === $files[ $size_name ]['mime-type'] ) { continue; } + if ( in_array( $size_name . static::WEBP_SUFFIX, $sizes, true ) ) { + continue; + } array_unshift( $sizes, $size_name . static::WEBP_SUFFIX ); } @@ -402,7 +429,7 @@ public function optimize_sizes( $sizes, $optimization_level = null, $args = [] ) if ( $webp ) { // We have at least one webp conversion to do: create a temporary backup. - $backuped = $this->get_file()->backup( $media->get_raw_backup_path() ); + $backuped = $this->get_original_file()->backup( $media->get_raw_backup_path() ); if ( $backuped ) { // See \Imagify\Job\MediaOptimization->delete_backup(). @@ -412,6 +439,7 @@ public function optimize_sizes( $sizes, $optimization_level = null, $args = [] ) } } + $sizes = array_unique( $sizes ); $optimization_level = $this->sanitize_optimization_level( $optimization_level ); /** @@ -438,7 +466,7 @@ public function optimize_sizes( $sizes, $optimization_level = null, $args = [] ) */ MediaOptimization::get_instance()->push_to_queue( [ 'id' => $media->get_id(), - 'sizes' => array_unique( $sizes ), + 'sizes' => $sizes, 'optimization_level' => $optimization_level, 'process_class' => get_class( $this ), 'data' => $args, @@ -652,6 +680,7 @@ public function optimize_size( $size, $optimization_level = null ) { $response = $file->optimize( [ 'backup' => ! $response['backuped'] && $this->can_backup( $size ), 'backup_path' => $media->get_raw_backup_path(), + 'backup_source' => 'full' === $thumb_size ? $media->get_original_path() : null, 'optimization_level' => $optimization_level, 'convert' => $webp ? 'webp' : '', 'keep_exif' => $this->can_keep_exif( $size ), @@ -847,22 +876,22 @@ public function restore() { $this->lock( 'restoring' ); - $backup_path = $media->get_backup_path(); - $media_path = $media->get_raw_original_path(); + $backup_path = $media->get_backup_path(); + $original_path = $media->get_raw_original_path(); - if ( $backup_path === $media_path ) { + if ( $backup_path === $original_path ) { // Uh?! $this->unlock(); return new \WP_Error( 'same_path', __( 'Image path and backup path are identical.', 'imagify' ) ); } - $dest_dir = $this->filesystem->dir_path( $media_path ); + $dest_dir = $this->filesystem->dir_path( $original_path ); if ( ! $this->filesystem->exists( $dest_dir ) ) { $this->filesystem->make_dir( $dest_dir ); } - $dest_file_is_writable = ! $this->filesystem->exists( $media_path ) || $this->filesystem->is_writable( $media_path ); + $dest_file_is_writable = ! $this->filesystem->exists( $original_path ) || $this->filesystem->is_writable( $original_path ); if ( ! $dest_file_is_writable || ! $this->filesystem->is_writable( $dest_dir ) ) { $this->unlock(); @@ -887,14 +916,14 @@ public function restore() { if ( ! is_wp_error( $response ) ) { // Create the original image from the backup. - $response = $this->filesystem->copy( $backup_path, $media_path, true ); + $response = $this->filesystem->copy( $backup_path, $original_path, true ); if ( ! $response ) { // Failure. $response = new \WP_Error( 'copy_failed', __( 'The backup file could not be copied over the optimized one.', 'imagify' ) ); } else { // Backup successfully copied. - $this->filesystem->chmod_file( $media_path ); + $this->filesystem->chmod_file( $original_path ); // Remove old optimization data. $this->get_data()->delete_optimization_data(); @@ -904,7 +933,7 @@ public function restore() { $media->update_dimensions(); // Delete the webp version. - $this->delete_webp_file( $media_path ); + $this->delete_webp_file( $original_path ); // Restore the thumbnails. $response = $this->restore_thumbnails(); @@ -940,10 +969,18 @@ public function restore() { * @return bool|WP_Error True on success. A \WP_Error instance on failure. */ protected function restore_thumbnails() { - // Delete the webp versions. - $this->delete_webp_files( true ); + $media = $this->get_media(); + + /** + * Delete the webp versions. + * If the full size file and the original file are not the same, the full size is considered like a thumbnail. + * In that case we must also delete the webp file associated to the full size. + */ + $keep_full_webp = $media->get_raw_original_path() === $media->get_raw_fullsize_path(); + $this->delete_webp_files( $keep_full_webp ); + // Generate new thumbnails. - return $this->get_media()->generate_thumbnails(); + return $media->generate_thumbnails(); } @@ -1144,7 +1181,7 @@ protected function create_temporary_copy( $size, $sizes = null ) { */ protected function get_temporary_copy_path( $size, $sizes = null ) { if ( 'full' === $size ) { - $path = $this->get_media()->get_raw_original_path(); + $path = $this->get_media()->get_raw_fullsize_path(); } else { if ( ! isset( $sizes ) ) { $sizes = $this->get_media()->get_media_files(); @@ -1216,7 +1253,7 @@ public function maybe_resize( $size, $file ) { ); } - $resize_width = $this->get_option( 'resize_larger_w' ); + $resize_width = $media->get_context_instance()->get_resizing_threshold(); if ( $resize_width >= $dimensions['width'] ) { // No need to resize. @@ -1242,7 +1279,8 @@ public function maybe_resize( $size, $file ) { } if ( $this->can_backup( $size ) ) { - $backuped = $file->backup( $media->get_raw_backup_path() ); + $source = 'full' === $size ? $media->get_original_path() : null; + $backuped = $file->backup( $media->get_raw_backup_path(), $source ); if ( is_wp_error( $backuped ) ) { // The backup failed. diff --git a/classes/Optimization/Process/ProcessInterface.php b/classes/Optimization/Process/ProcessInterface.php index 36030f3c7..252cc8516 100644 --- a/classes/Optimization/Process/ProcessInterface.php +++ b/classes/Optimization/Process/ProcessInterface.php @@ -47,15 +47,26 @@ public function get_data(); public function get_media(); /** - * Get the File instance. + * Get the File instance of the original file. * - * @since 1.9 + * @since 1.9.8 + * @access public + * @author Grégory Viguier + * + * @return File|false + */ + public function get_original_file(); + + /** + * Get the File instance of the full size file. + * + * @since 1.9.8 * @access public * @author Grégory Viguier * * @return File|false */ - public function get_file(); + public function get_fullsize_file(); /** * Tell if the current media is valid. diff --git a/classes/Optimization/Process/WP.php b/classes/Optimization/Process/WP.php index eac1f1f1c..74a2190f7 100644 --- a/classes/Optimization/Process/WP.php +++ b/classes/Optimization/Process/WP.php @@ -214,8 +214,8 @@ protected function create_missing_thumbnails( $missing_sizes ) { $metadata = wp_get_attachment_metadata( $media_id ); $metadata['sizes'] = ! empty( $metadata['sizes'] ) && is_array( $metadata['sizes'] ) ? $metadata['sizes'] : []; - $destination_dir = $this->filesystem->dir_path( $media->get_raw_original_path() ); - $file = new File( $media->get_backup_path() ); + $destination_dir = $this->filesystem->dir_path( $media->get_raw_fullsize_path() ); + $backup_file = new File( $media->get_backup_path() ); $without_errors = []; $has_new_data = false; @@ -225,7 +225,7 @@ protected function create_missing_thumbnails( $missing_sizes ) { $thumbnail_data['path'] = $destination_dir . $thumbnail_data['file']; if ( ! $this->filesystem->exists( $thumbnail_data['path'] ) ) { - $result = $file->create_thumbnail( $thumbnail_data ); + $result = $backup_file->create_thumbnail( $thumbnail_data ); if ( is_array( $result ) ) { // New file. diff --git a/classes/Webp/Picture/Display.php b/classes/Webp/Picture/Display.php index b1c03d30e..2639b5d45 100644 --- a/classes/Webp/Picture/Display.php +++ b/classes/Webp/Picture/Display.php @@ -168,6 +168,8 @@ public function process_content( $content ) { protected function build_picture_tag( $image ) { $to_remove = [ 'alt' => '', + 'height' => '', + 'width' => '', 'data-lazy-src' => '', 'data-src' => '', 'src' => '', @@ -225,9 +227,7 @@ protected function build_source_tag( $image ) { $srcset_source = ! empty( $image['srcset_attribute'] ) ? $image['srcset_attribute'] : $image['src_attribute'] . 'set'; $attributes = [ 'type' => 'image/webp', - $srcset_source => [ - $image['src']['webp_url'], - ], + $srcset_source => [], ]; if ( ! empty( $image['srcset'] ) ) { @@ -235,9 +235,6 @@ protected function build_source_tag( $image ) { if ( empty( $srcset['webp_url'] ) ) { continue; } - if ( $srcset['webp_url'] === $image['src']['webp_url'] ) { - continue; - } $attributes[ $srcset_source ][] = $srcset['webp_url'] . ' ' . $srcset['descriptor']; } @@ -290,11 +287,9 @@ protected function build_source_tag( $image ) { protected function build_img_tag( $image ) { $to_remove = [ 'class' => '', - 'height' => '', 'id' => '', 'style' => '', 'title' => '', - 'width' => '', ]; $attributes = array_diff_key( $image['attributes'], $to_remove ); @@ -630,7 +625,7 @@ protected function url_to_path( $url ) { $url = set_url_scheme( $url ); - if ( $domain_url && stripos( $url, $cdn_url ) === 0 ) { + if ( $cdn_url && $domain_url && stripos( $url, $cdn_url ) === 0 ) { // CDN. $url = str_ireplace( $cdn_url, $domain_url, $url ); } diff --git a/composer.json b/composer.json index d8d66ae63..6b963ba3a 100644 --- a/composer.json +++ b/composer.json @@ -44,6 +44,7 @@ "autoload": { "psr-4": { "Imagify\\": "classes/", + "Imagify\\Deprecated\\Traits\\": "inc/deprecated/Traits/", "Imagify\\ThirdParty\\AS3CF\\": "inc/3rd-party/amazon-s3-and-cloudfront/classes/", "Imagify\\ThirdParty\\EnableMediaReplace\\": "inc/3rd-party/enable-media-replace/classes/", "Imagify\\ThirdParty\\FormidablePro\\": "inc/3rd-party/formidable-pro/classes/", diff --git a/imagify.php b/imagify.php index fb78d43ef..b9103c7de 100644 --- a/imagify.php +++ b/imagify.php @@ -3,7 +3,7 @@ * Plugin Name: Imagify * Plugin URI: https://wordpress.org/plugins/imagify/ * Description: Dramaticaly reduce image file sizes without losing quality, make your website load faster, boost your SEO and save money on your bandwidth using Imagify, the new most advanced image optimization tool. - * Version: 1.9.7 + * Version: 1.9.8 * Requires PHP: 5.4 * Author: WP Media * Author URI: https://wp-media.me/ @@ -20,7 +20,7 @@ defined( 'ABSPATH' ) || die( 'Cheatin’ uh?' ); // Imagify defines. -define( 'IMAGIFY_VERSION', '1.9.7' ); +define( 'IMAGIFY_VERSION', '1.9.8' ); define( 'IMAGIFY_SLUG', 'imagify' ); define( 'IMAGIFY_FILE', __FILE__ ); define( 'IMAGIFY_PATH', realpath( plugin_dir_path( IMAGIFY_FILE ) ) . '/' ); diff --git a/inc/3rd-party/amazon-s3-and-cloudfront/classes/CDN/WP/AS3.php b/inc/3rd-party/amazon-s3-and-cloudfront/classes/CDN/WP/AS3.php index 167ab3b97..d834870e8 100644 --- a/inc/3rd-party/amazon-s3-and-cloudfront/classes/CDN/WP/AS3.php +++ b/inc/3rd-party/amazon-s3-and-cloudfront/classes/CDN/WP/AS3.php @@ -34,6 +34,16 @@ class AS3 implements PushCDNInterface { */ protected $filesystem; + /** + * Tell if we’re playing in WP 5.3’s garden. + * + * @var bool + * @since 1.9.8 + * @access protected + * @author Grégory Viguier + */ + protected $is_wp53; + /** * Constructor. * @@ -309,17 +319,35 @@ public function get_file_url( $file_name = false ) { * @access public * @author Grégory Viguier * - * @param string $file_name Name of the file. Leave empty for the full size file. + * @param string $file_name Name of the file. Leave empty for the full size file. Use 'original' to get the path to the original file. * @return string Path to the file. */ public function get_file_path( $file_name = false ) { - $file_path = get_attached_file( $this->id, true ); + if ( ! $file_name ) { + // Full size. + return get_attached_file( $this->id, true ); + } - if ( $file_name ) { - // It's not the full size. - $file_path = $this->filesystem->dir_path( $file_path ) . $file_name; + if ( 'original' === $file_name ) { + // Original file. + if ( $this->is_wp_53() ) { + // `wp_get_original_image_path()` may return false. + $file_path = wp_get_original_image_path( $this->id ); + } else { + $file_path = false; + } + + if ( ! $file_path ) { + $file_path = get_attached_file( $this->id, true ); + } + + return $file_path; } + // Thumbnail. + $file_path = get_attached_file( $this->id, true ); + $file_path = $this->filesystem->dir_path( $file_path ) . $file_name; + return $file_path; } @@ -463,4 +491,23 @@ protected function should_delete_files( $is_new_upload ) { // If the attachment has a 'filesize' metadata, that means the local files are meant to be deleted. return (bool) get_post_meta( $this->id, 'wpos3_filesize_total', true ); } + + /** + * Tell if we’re playing in WP 5.3’s garden. + * + * @since 1.9.8 + * @access protected + * @author Grégory Viguier + * + * @return bool + */ + protected function is_wp_53() { + if ( isset( $this->is_wp53 ) ) { + return $this->is_wp53; + } + + $this->is_wp53 = function_exists( 'wp_get_original_image_path' ); + + return $this->is_wp53; + } } diff --git a/inc/3rd-party/enable-media-replace/classes/Main.php b/inc/3rd-party/enable-media-replace/classes/Main.php index db8096bc0..78cdceee5 100755 --- a/inc/3rd-party/enable-media-replace/classes/Main.php +++ b/inc/3rd-party/enable-media-replace/classes/Main.php @@ -51,6 +51,16 @@ class Main extends \Imagify_Enable_Media_Replace_Deprecated { */ protected $old_backup_path; + /** + * List of paths to the old webp files. + * + * @var array + * @since 1.9.8 + * @access protected + * @author Grégory Viguier + */ + protected $old_webp_paths = []; + /** * Launch the hooks before the files and data are replaced. * @@ -76,6 +86,21 @@ public function init( $unfiltered = true ) { return $unfiltered; } + // Store the old backup file path. + $this->get_process(); + + if ( ! $this->process ) { + $this->media_id = 0; + return $unfiltered; + } + + $this->old_backup_path = $this->process->get_media()->get_backup_path(); + + if ( ! $this->old_backup_path ) { + $this->media_id = 0; + return $unfiltered; + } + // Store the old backup file path. add_filter( 'emr_unique_filename', [ $this, 'store_old_backup_path' ], 10, 3 ); // Delete the old backup file. @@ -114,13 +139,24 @@ public function store_old_backup_path( $new_filename, $current_path, $post_id ) return $new_filename; } - $backup_path = $this->process->get_media()->get_backup_path(); + $media = $this->process->get_media(); + $backup_path = $media->get_backup_path(); if ( $backup_path ) { $this->old_backup_path = $backup_path; + + // Keep track of existing webp files. + $media_files = $media->get_media_files(); + + if ( $media_files ) { + foreach ( $media_files as $media_file ) { + $this->old_webp_paths[] = imagify_path_to_webp( $media_file['path'] ); + } + } } else { $this->media_id = 0; $this->old_backup_path = false; + $this->old_webp_paths = []; } return $new_filename; @@ -143,12 +179,17 @@ public function delete_backup( $media_id ) { $filesystem = \Imagify_Filesystem::get_instance(); - if ( ! $filesystem->exists( $this->old_backup_path ) ) { - return; + if ( $filesystem->exists( $this->old_backup_path ) ) { + $filesystem->delete( $this->old_backup_path ); + $this->old_backup_path = false; } - $filesystem->delete( $this->old_backup_path ); - $this->old_backup_path = false; + if ( $this->old_webp_paths ) { + // If the files have been renamed, delete old webp files. + $this->old_webp_paths = array_filter( $this->old_webp_paths, [ $filesystem, 'exists' ] ); + array_map( [ $filesystem, 'delete' ], $this->old_webp_paths ); + $this->old_webp_paths = []; + } } diff --git a/inc/3rd-party/enable-media-replace/enable-media-replace.php b/inc/3rd-party/enable-media-replace/enable-media-replace.php index 6d00b6ce9..2e0f39272 100755 --- a/inc/3rd-party/enable-media-replace/enable-media-replace.php +++ b/inc/3rd-party/enable-media-replace/enable-media-replace.php @@ -1,7 +1,7 @@ $size ) { $size_status = $data->get_size_data( $size, 'success' ); diff --git a/inc/3rd-party/nextgen-gallery/classes/Media/NGG.php b/inc/3rd-party/nextgen-gallery/classes/Media/NGG.php index 395861dbf..aa70fe9d8 100644 --- a/inc/3rd-party/nextgen-gallery/classes/Media/NGG.php +++ b/inc/3rd-party/nextgen-gallery/classes/Media/NGG.php @@ -10,6 +10,7 @@ * @author Grégory Viguier */ class NGG extends \Imagify\Media\AbstractMedia { + use \Imagify\Deprecated\Traits\Media\NGGDeprecatedTrait; /** * Context (where the media "comes from"). @@ -151,15 +152,41 @@ public function get_ngg_storage() { /** ----------------------------------------------------------------------------------------- */ /** - * Get the original media's URL. + * Get the original file path, even if the file doesn't exist. * * @since 1.9 * @access public * @author Grégory Viguier * + * @return string|bool The file path. False on failure. + */ + public function get_raw_original_path() { + if ( ! $this->is_valid() ) { + return false; + } + + if ( $this->get_cdn() ) { + return $this->get_cdn()->get_file_path( 'original' ); + } + + return ! empty( $this->image->imagePath ) ? $this->image->imagePath : false; + } + + + /** ----------------------------------------------------------------------------------------- */ + /** FULL SIZE FILE ========================================================================== */ + /** ----------------------------------------------------------------------------------------- */ + + /** + * Get the URL of the media’s full size file. + * + * @since 1.9.8 + * @access public + * @author Grégory Viguier + * * @return string|bool The file URL. False on failure. */ - public function get_original_url() { + public function get_fullsize_url() { if ( ! $this->is_valid() ) { return false; } @@ -172,15 +199,15 @@ public function get_original_url() { } /** - * Get the original media's path. + * Get the path to the media’s full size file, even if the file doesn't exist. * - * @since 1.9 + * @since 1.9.8 * @access public * @author Grégory Viguier * * @return string|bool The file path. False on failure. */ - public function get_raw_original_path() { + public function get_raw_fullsize_path() { if ( ! $this->is_valid() ) { return false; } @@ -357,18 +384,18 @@ public function has_required_media_data() { $sizes = $this->get_media_files(); } - return $sizes && $this->get_raw_original_path(); + return $sizes && ! empty( $this->image->imagePath ); } /** - * Get the list of the files of this media, including the original file. + * Get the list of the files of this media, including the full size file. * * @since 1.9 * @access public * @author Grégory Viguier * * @return array { - * An array with the size names as keys ('full' is used for the original file), and arrays of data as values: + * An array with the size names as keys ('full' is used for the full size file), and arrays of data as values: * * @type string $size The size name. * @type string $path Absolute path to the file. @@ -383,9 +410,9 @@ public function get_media_files() { return []; } - $original_path = $this->get_raw_original_path(); + $fullsize_path = $this->get_raw_fullsize_path(); - if ( ! $original_path ) { + if ( ! $fullsize_path ) { return []; } @@ -393,7 +420,7 @@ public function get_media_files() { $all_sizes = [ 'full' => [ 'size' => 'full', - 'path' => $original_path, + 'path' => $fullsize_path, 'width' => $dimensions['width'], 'height' => $dimensions['height'], 'mime-type' => $this->get_mime_type(), @@ -499,7 +526,7 @@ protected function update_media_data_dimensions( $dimensions ) { $data = [ 'width' => $dimensions['width'], 'height' => $dimensions['height'], - 'md5' => md5_file( $this->get_raw_original_path() ), + 'md5' => md5_file( $this->get_raw_fullsize_path() ), ]; foreach ( $data as $k => $v ) { diff --git a/inc/3rd-party/nextgen-gallery/classes/Optimization/Data/NGG.php b/inc/3rd-party/nextgen-gallery/classes/Optimization/Data/NGG.php index abdf1209f..1c9383055 100644 --- a/inc/3rd-party/nextgen-gallery/classes/Optimization/Data/NGG.php +++ b/inc/3rd-party/nextgen-gallery/classes/Optimization/Data/NGG.php @@ -69,6 +69,11 @@ public function get_optimization_data() { $data['status'] = $row['status']; $data['level'] = $row['optimization_level']; $data['level'] = is_numeric( $data['level'] ) ? (int) $data['level'] : false; + $data['stats'] = [ + 'original_size' => 0, + 'optimized_size' => 0, + 'percent' => 0, + ]; if ( ! empty( $row['data']['sizes'] ) && is_array( $row['data']['sizes'] ) ) { $data['sizes'] = $row['data']['sizes']; @@ -139,7 +144,6 @@ public function update_size_optimization_data( $size, array $data ) { $old_data = array_merge( $this->get_reset_data(), $this->get_row() ); $old_data['data']['sizes'] = ! empty( $old_data['data']['sizes'] ) && is_array( $old_data['data']['sizes'] ) ? $old_data['data']['sizes'] : []; - $old_data['data']['stats'] = ! empty( $old_data['data']['stats'] ) && is_array( $old_data['data']['stats'] ) ? $old_data['data']['stats'] : []; if ( 'full' === $size ) { /** @@ -187,6 +191,48 @@ public function delete_optimization_data() { $this->delete_row(); } + /** + * Delete the optimization data for the given sizes. + * If all sizes are removed, all optimization data is deleted. + * Status and level are not modified nor removed if the "full" size is removed. This leaves the media in a Schrödinger state. + * + * @since 1.9.8 + * @access public + * @author Grégory Viguier + * + * @param array $sizes A list of sizes to remove. + */ + public function delete_sizes_optimization_data( array $sizes ) { + if ( ! $sizes || ! $this->is_valid() ) { + return; + } + + $data = array_merge( $this->get_reset_data(), $this->get_row() ); + + $data['data']['sizes'] = ! empty( $data['data']['sizes'] ) && is_array( $data['data']['sizes'] ) ? $data['data']['sizes'] : []; + + if ( ! $data['data']['sizes'] ) { + return; + } + + $remaining_sizes_data = array_diff_key( $data['data']['sizes'], array_flip( $sizes ) ); + + if ( ! $remaining_sizes_data ) { + // All sizes have been removed: delete everything. + $this->delete_optimization_data(); + return; + } + + if ( count( $remaining_sizes_data ) === count( $data['data']['sizes'] ) ) { + // Nothing has been removed. + return; + } + + $data['data']['sizes'] = $remaining_sizes_data; + + $this->update_row( $data ); + } + /** * Get default values used to reset optimization data. * diff --git a/inc/3rd-party/nextgen-gallery/inc/common/attachments.php b/inc/3rd-party/nextgen-gallery/inc/common/attachments.php index 181f46c2d..7bc33d6fc 100644 --- a/inc/3rd-party/nextgen-gallery/inc/common/attachments.php +++ b/inc/3rd-party/nextgen-gallery/inc/common/attachments.php @@ -164,7 +164,7 @@ function _imagify_ngg_media_library_imported_image_data( $image, $attachment ) { $wp_webp_data = $wp_data->get_size_data( $webp_size_name ); // Get the path to the webp image if it exists. - $wp_full_path_webp = $wp_process->get_file()->get_path_to_webp(); + $wp_full_path_webp = $wp_process->get_fullsize_file()->get_path_to_webp(); if ( $wp_full_path_webp && ! $filesystem->exists( $wp_full_path_webp ) ) { $wp_full_path_webp = false; @@ -202,7 +202,7 @@ function _imagify_ngg_media_library_imported_image_data( $image, $attachment ) { if ( $wp_full_path_webp && $wp_webp_data ) { // We have the file and the data. // Copy the file. - $ngg_full_file = new File( $ngg_media->get_raw_original_path() ); + $ngg_full_file = new File( $ngg_media->get_raw_fullsize_path() ); $ngg_full_path_webp = $ngg_full_file->get_path_to_webp(); // Destination. if ( $ngg_full_path_webp ) { diff --git a/inc/3rd-party/regenerate-thumbnails/classes/Main.php b/inc/3rd-party/regenerate-thumbnails/classes/Main.php index cf53aa297..6f8e9e75e 100644 --- a/inc/3rd-party/regenerate-thumbnails/classes/Main.php +++ b/inc/3rd-party/regenerate-thumbnails/classes/Main.php @@ -40,6 +40,16 @@ class Main extends \Imagify_Regenerate_Thumbnails_Deprecated { */ protected $processes = []; + /** + * Tell if we’re playing in WP 5.3’s garden. + * + * @var bool + * @since 1.9.8 + * @access protected + * @author Grégory Viguier + */ + protected $is_wp53; + /** ----------------------------------------------------------------------------------------- */ /** INIT ==================================================================================== */ @@ -58,7 +68,6 @@ public function init() { } add_filter( 'rest_dispatch_request', [ $this, 'maybe_init_attachment' ], 4, 4 ); - add_filter( 'wp_generate_attachment_metadata', [ $this, 'launch_async_optimization' ], IMAGIFY_INT_MAX - 30, 2 ); add_action( 'imagify_after_' . static::HOOK_SUFFIX, [ $this, 'after_regenerate_thumbnails' ], 8, 2 ); } @@ -91,10 +100,14 @@ public function maybe_init_attachment( $dispatch_result, $request, $route = null return $dispatch_result; } + $media_id = $this->get_process( $media_id )->get_media()->get_id(); + // The attachment can be regenerated: keep the optimized full-sized file safe, and replace it by the backup file. $this->backup_optimized_file( $media_id ); // Prevent automatic optimization. \Imagify_Auto_Optimization::prevent_optimization( $media_id ); + // Launch the needed hook. + add_filter( 'wp_generate_attachment_metadata', [ $this, 'launch_async_optimization' ], IMAGIFY_INT_MAX - 30, 2 ); return $dispatch_result; } @@ -106,32 +119,69 @@ public function maybe_init_attachment( $dispatch_result, $request, $route = null * @access public * @author Grégory Viguier * - * @param array $metadata An array of attachment meta data. + * @param array $metadata An array of attachment meta data, containing the sizes that have been regenerated. * @param int $media_id Current media ID. * @return array */ public function launch_async_optimization( $metadata, $media_id ) { - if ( ! $this->get_process( $media_id ) ) { + $process = $this->get_process( $media_id ); + + if ( ! $process ) { return $metadata; } - $sizes = isset( $metadata['sizes'] ) && is_array( $metadata['sizes'] ) ? $metadata['sizes'] : []; + $sizes = isset( $metadata['sizes'] ) && is_array( $metadata['sizes'] ) ? $metadata['sizes'] : []; + $media = $process->get_media(); + $fullsize_path = $media->get_raw_fullsize_path(); + + if ( $fullsize_path ) { + $original_path = $media->get_original_path(); + + if ( $original_path && $this->is_wp_53() && $original_path !== $fullsize_path ) { + /** + * The original file and the full-sized file are not the same: + * That means wp_generate_attachment_metadata() recreated the full-sized file, based on the original one. + * So it must be optimized again now. + */ + $sizes['full'] = []; + } + } if ( ! $sizes ) { // Put the optimized full-sized file back. $this->put_optimized_file_back( $media_id ); - // Allow auto-optimization back. - \Imagify_Auto_Optimization::allow_optimization( $media_id ); - return $metadata; } - // Optimize the sizes that have been regenerated. - $process = $this->get_process( $media_id ); - $level = $process->get_data()->get_optimization_level(); - $args = [ 'hook_suffix' => static::HOOK_SUFFIX ]; + /** + * Optimize the sizes that have been regenerated. + */ + // If the media has webp versions, recreate them for the sizes that have been regenerated. + $data = $process->get_data(); + $optimization_data = $data->get_optimization_data(); + + if ( ! empty( $optimization_data['sizes'] ) ) { + foreach ( $optimization_data['sizes'] as $size_name => $size_data ) { + $non_webp_size_name = $process->is_size_webp( $size_name ); + + if ( ! $non_webp_size_name || ! isset( $sizes[ $non_webp_size_name ] ) ) { + continue; + } + + // Add the webp size. + $sizes[ $size_name ] = []; + } + } + + $sizes = array_keys( $sizes ); + $optimization_level = $data->get_optimization_level(); + $optimization_args = [ 'hook_suffix' => static::HOOK_SUFFIX ]; - $process->optimize_sizes( array_keys( $sizes ), $level, $args ); + // Delete related optimization data or nothing will be optimized. + $data->delete_sizes_optimization_data( $sizes ); + $process->optimize_sizes( $sizes, $optimization_level, $optimization_args ); + + $this->unset_process( $media_id ); return $metadata; } @@ -224,7 +274,25 @@ protected function get_process( $media_id ) { * @param int $media_id Media ID. */ protected function backup_optimized_file( $media_id ) { - $media = $this->get_process( $media_id )->get_media(); + $media = $this->get_process( $media_id )->get_media(); + $fullsize_path = $media->get_raw_fullsize_path(); + + if ( ! $fullsize_path ) { + // Uh? + return; + } + + $original_path = $media->get_original_path(); + + if ( $original_path && $this->is_wp_53() && $original_path !== $fullsize_path ) { + /** + * The original file and the full-sized file are not the same: + * That means wp_generate_attachment_metadata() will recreate the full-sized file, based on the original one. + * Then, the thumbnails will be created from a newly created (unoptimized) file. + */ + return; + } + $backup_path = $media->get_backup_path(); if ( ! $backup_path ) { @@ -236,15 +304,14 @@ protected function backup_optimized_file( $media_id ) { * Replace the optimized full-sized file by the backup, so any optimization will not use an optimized file, but the original one. * The optimized full-sized file is kept and renamed, and will be put back in place at the end of the optimization process. */ - $filesystem = \Imagify_Filesystem::get_instance(); - $file_path = $media->get_raw_original_path(); - $tmp_file_path = static::get_temporary_file_path( $file_path ); + $filesystem = \Imagify_Filesystem::get_instance(); - if ( $filesystem->exists( $file_path ) ) { - $moved = $filesystem->move( $file_path, $tmp_file_path, true ); + if ( $filesystem->exists( $fullsize_path ) ) { + $tmp_file_path = static::get_temporary_file_path( $fullsize_path ); + $moved = $filesystem->move( $fullsize_path, $tmp_file_path, true ); } - $copied = $filesystem->copy( $backup_path, $file_path ); + $filesystem->copy( $backup_path, $fullsize_path ); } /** @@ -258,7 +325,7 @@ protected function backup_optimized_file( $media_id ) { * @param int $media_id Media ID. */ protected function put_optimized_file_back( $media_id ) { - $file_path = $this->get_process( $media_id )->get_media()->get_raw_original_path(); + $file_path = $this->get_process( $media_id )->get_media()->get_raw_fullsize_path(); $tmp_file_path = static::get_temporary_file_path( $file_path ); $filesystem = \Imagify_Filesystem::get_instance(); @@ -267,6 +334,25 @@ protected function put_optimized_file_back( $media_id ) { } } + /** + * Tell if we’re playing in WP 5.3’s garden. + * + * @since 1.9.8 + * @access protected + * @author Grégory Viguier + * + * @return bool + */ + protected function is_wp_53() { + if ( isset( $this->is_wp53 ) ) { + return $this->is_wp53; + } + + $this->is_wp53 = function_exists( 'wp_get_original_image_path' ); + + return $this->is_wp53; + } + /** ----------------------------------------------------------------------------------------- */ /** PUBLIC TOOLS ============================================================================ */ diff --git a/inc/3rd-party/wp-rocket/classes/Main.php b/inc/3rd-party/wp-rocket/classes/Main.php index 3b7d092c4..060939795 100755 --- a/inc/3rd-party/wp-rocket/classes/Main.php +++ b/inc/3rd-party/wp-rocket/classes/Main.php @@ -68,7 +68,7 @@ public function dequeue_sweetalert() { * @return array */ public function set_cdn_source( $source ) { - if ( ! function_exists( 'get_rocket_cdn_cnames' ) || ! function_exists( 'get_rocket_option' ) ) { + if ( ! function_exists( 'get_rocket_option' ) ) { return $source; } @@ -76,9 +76,21 @@ public function set_cdn_source( $source ) { return $source; } - $url = get_rocket_cdn_cnames( [ 'all', 'images' ] ); + $container = apply_filters( 'rocket_container', null ); - if ( ! $url ) { + if ( is_object( $container ) && method_exists( $container, 'get' ) ) { + $cdn = $container->get( 'cdn' ); + + if ( $cdn && method_exists( $cdn, 'get_cdn_urls' ) ) { + $url = $cdn->get_cdn_urls( [ 'all', 'images' ] ); + } + } + + if ( ! isset( $url ) && function_exists( 'get_rocket_cdn_cnames' ) ) { + $url = get_rocket_cdn_cnames( [ 'all', 'images' ] ); + } + + if ( empty( $url ) ) { return $source; } diff --git a/inc/admin/media.php b/inc/admin/media.php index 16f8cd949..31f52d05b 100755 --- a/inc/admin/media.php +++ b/inc/admin/media.php @@ -88,7 +88,7 @@ function _imagify_add_actions_to_media_list_row( $actions, $post ) { $actions['imagify-compare'] = Imagify_Views::get_instance()->get_template( 'button/compare-images', [ 'url' => get_edit_post_link( $media->get_id() ) . '#imagify-compare', 'backup_url' => $media->get_backup_url(), - 'original_url' => $media->get_original_url(), + 'original_url' => $media->get_fullsize_url(), 'media_id' => $media->get_id(), 'width' => $dimensions['width'], 'height' => $dimensions['height'], diff --git a/inc/classes/class-imagify-auto-optimization.php b/inc/classes/class-imagify-auto-optimization.php index a9d0a52c5..992ad3d22 100644 --- a/inc/classes/class-imagify-auto-optimization.php +++ b/inc/classes/class-imagify-auto-optimization.php @@ -10,15 +10,7 @@ * @author Grégory Viguier */ class Imagify_Auto_Optimization { - - /** - * Class version. - * - * @var string - * @since 1.8.4 - * @author Grégory Viguier - */ - const VERSION = '1.1'; + use \Imagify\Traits\InstanceGetterTrait; /** * An array containing the IDs (as keys) of attachments just being uploaded. @@ -42,14 +34,14 @@ class Imagify_Auto_Optimization { protected $attachments = []; /** - * The single instance of the class. + * The ID of the attachment that failed to be uploaded. * - * @var object - * @since 1.8.4 + * @var int + * @since 1.9.8 * @access protected * @author Grégory Viguier */ - protected static $instance; + protected $upload_failure_id = 0; /** * Used to prevent an auto-optimization locally. @@ -62,31 +54,14 @@ class Imagify_Auto_Optimization { private static $prevented = []; /** - * Get the main Instance. - * Ensures only one instance of class is loaded or can be loaded. + * Used to prevent an auto-optimization internally. * - * @since 1.8.4 - * @access public - * @author Grégory Viguier - * - * @return object Main instance. - */ - public static function get_instance() { - if ( ! isset( self::$instance ) ) { - self::$instance = new self(); - } - - return self::$instance; - } - - /** - * The class constructor. - * - * @since 1.8.4 - * @access protected + * @var array + * @since 1.9.8 + * @access private * @author Grégory Viguier */ - protected function __construct() {} + private static $prevented_internally = []; /** * Init. @@ -96,15 +71,25 @@ protected function __construct() {} * @author Grégory Viguier */ public function init() { + global $wp_version; $prio = IMAGIFY_INT_MAX - 30; // Automatic optimization tunel. add_action( 'add_attachment', [ $this, 'store_upload_ids' ], $prio ); add_filter( 'wp_update_attachment_metadata', [ $this, 'store_ids_to_optimize' ], $prio, 2 ); - add_action( 'updated_post_meta', [ $this, 'do_auto_optimization' ], $prio, 4 ); - add_action( 'added_post_meta', [ $this, 'do_auto_optimization' ], $prio, 4 ); + add_action( 'updated_post_meta', [ $this, 'do_auto_optimization_after_meta_update' ], $prio, 4 ); + add_action( 'added_post_meta', [ $this, 'do_auto_optimization_after_meta_update' ], $prio, 4 ); add_action( 'deleted_post_meta', [ $this, 'unset_optimization' ], $prio, 3 ); + if ( version_compare( $wp_version, '5.3-alpha1' ) >= 0 ) { + // WP 5.3+. + add_filter( 'big_image_size_threshold', [ $this, 'prevent_auto_optimization_when_generating_thumbnails' ], $prio, 4 ); + add_filter( 'wp_generate_attachment_metadata', [ $this, 'allow_auto_optimization_when_generating_thumbnails' ], $prio, 3 ); + add_action( 'imagify_after_auto_optimization_init', [ $this, 'do_auto_optimization' ], $prio, 2 ); + // Upload failure recovering. + add_action( 'wp_ajax_media-create-image-subsizes', [ $this, 'prevent_auto_optimization_when_recovering_from_upload_failure' ], -5 ); // Before WP’s hook (priority 1). + } + // Prevent to re-optimize when updating the image width and height (when resizing the full image). add_action( 'imagify_before_update_wp_media_data_dimensions', [ __CLASS__, 'prevent_optimization' ], 5 ); add_action( 'imagify_after_update_wp_media_data_dimensions', [ __CLASS__, 'allow_optimization' ], 5 ); @@ -120,11 +105,23 @@ public function init() { public function remove_hooks() { $prio = IMAGIFY_INT_MAX - 30; + // Automatic optimization tunel. remove_action( 'add_attachment', [ $this, 'store_upload_ids' ], $prio ); remove_filter( 'wp_update_attachment_metadata', [ $this, 'store_ids_to_optimize' ], $prio ); - remove_action( 'updated_post_meta', [ $this, 'do_auto_optimization' ], $prio ); - remove_action( 'added_post_meta', [ $this, 'do_auto_optimization' ], $prio ); + remove_action( 'updated_post_meta', [ $this, 'do_auto_optimization_after_meta_update' ], $prio ); + remove_action( 'added_post_meta', [ $this, 'do_auto_optimization_after_meta_update' ], $prio ); remove_action( 'deleted_post_meta', [ $this, 'unset_optimization' ], $prio ); + + if ( version_compare( $wp_version, '5.3-alpha1' ) >= 0 ) { + // WP 5.3+. + remove_filter( 'big_image_size_threshold', [ $this, 'prevent_auto_optimization_when_generating_thumbnails' ], $prio ); + remove_filter( 'wp_generate_attachment_metadata', [ $this, 'allow_auto_optimization_when_generating_thumbnails' ], $prio ); + remove_action( 'imagify_after_auto_optimization_init', [ $this, 'do_auto_optimization' ], $prio ); + // Upload failure handling. + remove_action( 'wp_ajax_media_create_image_subsizes', [ $this, 'prevent_auto_optimization_when_recovering_from_upload_failure' ], -5 ); + } + + // Prevent to re-optimize when updating the image width and height (when resizing the full image). remove_action( 'imagify_before_update_wp_media_data_dimensions', [ __CLASS__, 'prevent_optimization' ], 5 ); remove_action( 'imagify_after_update_wp_media_data_dimensions', [ __CLASS__, 'allow_optimization' ], 5 ); } @@ -270,13 +267,25 @@ public function store_ids_to_optimize( $metadata, $attachment_id ) { // Ready for the next step. $this->attachments[ $attachment_id ] = $is_new_upload; + /** + * Triggered after a media auto-optimization init. + * + * @since 1.9.8 + * @author Grégory Viguier + * + * @param int $attachment_id The media ID. + * @param bool $is_new_upload True if it's a new upload. False otherwize. + */ + do_action( 'imagify_after_auto_optimization_init', $attachment_id, $is_new_upload ); + return $metadata; } /** * Launch auto optimization immediately after the post meta '_wp_attachment_metadata' is added or updated. * - * @since 1.8.4 + * @since 1.9 + * @since 1.9 Previously named do_auto_optimization(). * @access public * @author Grégory Viguier * @@ -285,8 +294,12 @@ public function store_ids_to_optimize( $metadata, $attachment_id ) { * @param string $meta_key Meta key. * @param mixed $metadata Meta value. */ - public function do_auto_optimization( $meta_id, $attachment_id, $meta_key, $metadata ) { - if ( '_wp_attachment_metadata' !== $meta_key || ! isset( $this->attachments[ $attachment_id ] ) ) { + public function do_auto_optimization_after_meta_update( $meta_id, $attachment_id, $meta_key, $metadata ) { + if ( '_wp_attachment_metadata' !== $meta_key ) { + return; + } + + if ( ! isset( $this->attachments[ $attachment_id ] ) ) { return; } @@ -297,6 +310,21 @@ public function do_auto_optimization( $meta_id, $attachment_id, $meta_key, $meta $is_new_upload = $this->attachments[ $attachment_id ]; unset( $this->attachments[ $attachment_id ] ); + $this->do_auto_optimization( $attachment_id, $is_new_upload ); + } + + /** + * Launch auto optimization immediately after the post meta '_wp_attachment_metadata' is added or updated. + * + * @since 1.8.4 + * @since 1.9.8 Changed signature. + * @access public + * @author Grégory Viguier + * + * @param int $attachment_id The media ID. + * @param bool $is_new_upload True if it's a new upload. False otherwize. + */ + public function do_auto_optimization( $attachment_id, $is_new_upload ) { $process = imagify_get_optimization_process( $attachment_id, 'wp' ); /** @@ -392,6 +420,144 @@ public function unset_optimization( $meta_ids, $attachment_id, $meta_key ) { } + /** ----------------------------------------------------------------------------------------- */ + /** WP 5.3+ HOOKS =========================================================================== */ + /** ----------------------------------------------------------------------------------------- */ + + /** + * With WP 5.3+, prevent auto-optimization inside wp_generate_attachment_metadata() because it triggers a wp_update_attachment_metadata() for each thumbnail size. + * + * @since 1.9.8 + * @access public + * @see wp_generate_attachment_metadata() + * @see wp_create_image_subsizes() + * @author Grégory Viguier + * + * @param int $threshold The threshold value in pixels. Default 2560. + * @param array $imagesize Indexed array of the image width and height (in that order). + * @param string $file Full path to the uploaded image file. + * @param int $attachment_id Attachment post ID. + * @return int The threshold value in pixels. + */ + public function prevent_auto_optimization_when_generating_thumbnails( $threshold, $imagesize, $file, $attachment_id ) { + static::prevent_optimization_internally( $attachment_id ); + return $threshold; + } + + /** + * With WP 5.3+, allow auto-optimization back after wp_generate_attachment_metadata(). + * + * @since 1.9.8 + * @access public + * @see $this->prevent_auto_optimization_when_generating_thumbnails() + * @author Grégory Viguier + * + * @param array $metadata An array of attachment meta data. + * @param int $attachment_id Current attachment ID. + * @param string $context Additional context. Can be 'create' when metadata was initially created for new attachment or 'update' when the metadata was updated. + * @return array An array of attachment meta data. + */ + public function allow_auto_optimization_when_generating_thumbnails( $metadata, $attachment_id, $context = null ) { + if ( ! empty( $context ) && 'create' !== $context ) { + return $metadata; + } + + // Fired from wp_generate_attachment_metadata(): $context is empty (WP < 5.3) or equal to 'create' (>P >= 5.3). + static::allow_optimization_internally( $attachment_id ); + return $metadata; + } + + + /** ----------------------------------------------------------------------------------------- */ + /** HOOKS FOR WP 5.3+’S UPLOAD FAILURE RECOVERING =========================================== */ + /** ----------------------------------------------------------------------------------------- */ + + /** + * With WP 5.3+, prevent auto-optimization when WP tries to create thumbnails after an upload error, because it triggers wp_update_attachment_metadata() for each thumbnail size. + * + * @since 1.9.8 + * @access public + * @see wp_ajax_media_create_image_subsizes() + * @see wp_update_image_subsizes() + * @author Grégory Viguier + */ + public function prevent_auto_optimization_when_recovering_from_upload_failure() { + if ( ! check_ajax_referer( 'media-form', false, false ) ) { + return; + } + + if ( ! current_user_can( 'upload_files' ) ) { + return; + } + + if ( ! imagify_get_context( 'wp' )->current_user_can( 'auto-optimize' ) ) { + return; + } + + $attachment_id = ! empty( $_POST['attachment_id'] ) ? (int) $_POST['attachment_id'] : 0; + + if ( ! $attachment_id ) { + return; + } + + if ( ! imagify_is_attachment_mime_type_supported( $attachment_id ) ) { + return; + } + + $this->upload_failure_id = $attachment_id; + + static::prevent_optimization_internally( $attachment_id ); + + // Auto-optimization will be done on shutdown. + ob_start( [ $this, 'maybe_do_auto_optimization_after_recovering_from_upload_failure' ] ); + } + + /** + * Maybe launch auto-optimization after recovering from an upload failure, when all thumbnails are created. + * + * @since 1.9.8 + * @access public + * @see wp_ajax_media_create_image_subsizes() + * @author Grégory Viguier + * + * @param string $content Buffer’s content. + * @return string Buffer’s content. + */ + public function maybe_do_auto_optimization_after_recovering_from_upload_failure( $content ) { + if ( empty( $content ) ) { + return $content; + } + + if ( ! $this->upload_failure_id ) { + // Uh? + return $content; + } + + if ( ! get_post( $this->upload_failure_id ) ) { + return $content; + } + + $json = @json_decode( $content ); + + if ( empty( $json->success ) ) { + return $content; + } + + $attachment_id = $this->upload_failure_id; + $metadata = wp_get_attachment_metadata( $attachment_id ); + + $this->upload_failure_id = 0; + $this->uploads[ $attachment_id ] = 1; // New upload. + + static::allow_optimization_internally( $attachment_id ); + + // Launch the process. + $this->store_ids_to_optimize( $metadata, $attachment_id ); + + return $content; + } + + /** ----------------------------------------------------------------------------------------- */ /** TOOLS =================================================================================== */ /** ----------------------------------------------------------------------------------------- */ @@ -404,13 +570,18 @@ public function unset_optimization( $meta_ids, $attachment_id, $meta_key ) { * Imagify_Auto_Optimization::allow_optimization( $attachment_id ); * * @since 1.8.4 + * @since 1.9.8 Prevents/Allows can stack. * @access public * @author Grégory Viguier * * @param int $attachment_id Current attachment ID. */ public static function prevent_optimization( $attachment_id ) { - self::$prevented[ $attachment_id ] = 1; + if ( ! isset( self::$prevented[ $attachment_id ] ) ) { + self::$prevented[ $attachment_id ] = 1; + } else { + ++self::$prevented[ $attachment_id ]; + } } /** @@ -421,13 +592,21 @@ public static function prevent_optimization( $attachment_id ) { * Imagify_Auto_Optimization::allow_optimization( $attachment_id ); * * @since 1.8.4 + * @since 1.9.8 Prevents/Allows can stack. * @access public * @author Grégory Viguier * * @param int $attachment_id Current attachment ID. */ public static function allow_optimization( $attachment_id ) { - unset( self::$prevented[ $attachment_id ] ); + if ( ! isset( self::$prevented[ $attachment_id ] ) ) { + return; + } + --self::$prevented[ $attachment_id ]; + + if ( self::$prevented[ $attachment_id ] <= 0 ) { + unset( self::$prevented[ $attachment_id ] ); + } } /** @@ -441,6 +620,32 @@ public static function allow_optimization( $attachment_id ) { * @return bool */ public static function is_optimization_prevented( $attachment_id ) { - return ! empty( self::$prevented[ $attachment_id ] ); + return ! empty( self::$prevented[ $attachment_id ] ) || ! empty( self::$prevented_internally[ $attachment_id ] ); + } + + /** + * Prevent an auto-optimization internally. + * + * @since 1.9.8 + * @access protected + * @author Grégory Viguier + * + * @param int $attachment_id Current attachment ID. + */ + protected static function prevent_optimization_internally( $attachment_id ) { + self::$prevented_internally[ $attachment_id ] = 1; + } + + /** + * Allow an auto-optimization internally. + * + * @since 1.9.8 + * @access protected + * @author Grégory Viguier + * + * @param int $attachment_id Current attachment ID. + */ + protected static function allow_optimization_internally( $attachment_id ) { + unset( self::$prevented_internally[ $attachment_id ] ); } } diff --git a/inc/classes/class-imagify-custom-folders.php b/inc/classes/class-imagify-custom-folders.php index 467e4ca23..2910514e0 100644 --- a/inc/classes/class-imagify-custom-folders.php +++ b/inc/classes/class-imagify-custom-folders.php @@ -194,7 +194,7 @@ public static function delete_file( $args = [] ) { } if ( ! $args['file_path'] && $args['file_id'] ) { - $args['file_path'] = $process->get_media()->get_original_path(); + $args['file_path'] = $process->get_media()->get_fullsize_path(); } if ( ! $args['backup_path'] && $args['file_path'] ) { @@ -254,7 +254,7 @@ public static function refresh_file( $process, $is_folder_active = null ) { $filesystem = imagify_get_filesystem(); $media = $process->get_media(); - $file_path = $media->get_original_path(); + $file_path = $media->get_fullsize_path(); $mime_type = $filesystem->get_mime_type( $file_path ); $is_image = $mime_type && strpos( $mime_type, 'image/' ) === 0; $webp_path = $is_image ? imagify_path_to_webp( $file_path ) : false; diff --git a/inc/classes/class-imagify-db.php b/inc/classes/class-imagify-db.php index f379bf75a..6a12cd066 100644 --- a/inc/classes/class-imagify-db.php +++ b/inc/classes/class-imagify-db.php @@ -489,7 +489,7 @@ public static function cache_process_locks( $context, $media_ids ) { // Sanitize the IDs. $media_ids = array_filter( $media_ids ); - $media_ids = array_unique( $media_ids, true ); + $media_ids = array_unique( $media_ids ); if ( ! $media_ids ) { return; diff --git a/inc/classes/class-imagify-files-list-table.php b/inc/classes/class-imagify-files-list-table.php index 01bc0fab4..9539307b1 100755 --- a/inc/classes/class-imagify-files-list-table.php +++ b/inc/classes/class-imagify-files-list-table.php @@ -526,9 +526,9 @@ public function column_cb( $item ) { public function column_title( $item ) { $item = $this->maybe_set_item_folder( $item ); $media = $item->process->get_media(); - $url = $media->get_original_url(); + $url = $media->get_fullsize_url(); $base = ! empty( $item->folder_path ) ? Imagify_Files_Scan::remove_placeholder( $item->folder_path ) : ''; - $title = $this->filesystem->make_path_relative( $media->get_original_path(), $base ); + $title = $this->filesystem->make_path_relative( $media->get_fullsize_path(), $base ); list( $mime ) = explode( '/', $media->get_mime_type() ); @@ -946,7 +946,7 @@ protected function comparison_tool_button( $item ) { return; } - $file_path = $media->get_original_path(); + $file_path = $media->get_fullsize_path(); if ( ! $file_path ) { return; @@ -963,7 +963,7 @@ protected function comparison_tool_button( $item ) { echo $this->views->get_template( 'button/compare-images', [ 'url' => $backup_url, 'backup_url' => $backup_url, - 'original_url' => $media->get_original_url(), + 'original_url' => $media->get_fullsize_url(), 'media_id' => $media->get_id(), 'width' => $dimensions['width'], 'height' => $dimensions['height'], diff --git a/inc/classes/class-imagify-options.php b/inc/classes/class-imagify-options.php index fdee187b7..9a903a299 100644 --- a/inc/classes/class-imagify-options.php +++ b/inc/classes/class-imagify-options.php @@ -90,6 +90,25 @@ protected function __construct() { $this->default_values['api_key'] = (string) IMAGIFY_API_KEY; } + if ( function_exists( 'wp_get_original_image_path' ) ) { + $this->reset_values['resize_larger'] = 1; + + $filter_cb = [ imagify_get_context( 'wp' ), 'get_resizing_threshold' ]; + $filtered = has_filter( 'big_image_size_threshold', $filter_cb ); + + if ( $filtered ) { + remove_filter( 'big_image_size_threshold', $filter_cb, IMAGIFY_INT_MAX ); + } + + /** This filter is documented in wp-admin/includes/image.php */ + $this->reset_values['resize_larger_w'] = (int) apply_filters( 'big_image_size_threshold', 2560, [ 0, 0 ], '', 0 ); + $this->reset_values['resize_larger_w'] = $this->sanitize_and_validate_value( 'resize_larger_w', $this->reset_values['resize_larger_w'], $this->default_values['resize_larger_w'] ); + + if ( $filtered ) { + add_filter( 'big_image_size_threshold', $filter_cb, IMAGIFY_INT_MAX ); + } + } + $this->network_option = imagify_is_active_for_network(); parent::__construct(); @@ -160,7 +179,7 @@ public function sanitize_and_validate_value( $key, $value, $default ) { case 'resize_larger_w': if ( $value <= 0 ) { // Invalid. - return 0; + return $default; } if ( ! isset( $max_sizes ) ) { $max_sizes = get_imagify_max_intermediate_image_size(); diff --git a/inc/classes/class-imagify-views.php b/inc/classes/class-imagify-views.php index 614045439..a439839ad 100644 --- a/inc/classes/class-imagify-views.php +++ b/inc/classes/class-imagify-views.php @@ -160,7 +160,7 @@ public function add_site_menus() { $wp_context = imagify_get_context( 'wp' ); // Sub-menu item: bulk optimization. - add_media_page( __( 'Bulk Optimization', 'imagify' ), __( 'Bulk Optimization', 'imagify' ), $wp_context->get_capacity( 'bulk-optimize' ), $this->get_bulk_page_slug(), array( $this, 'display_bulk_page' ) ); + add_media_page( __( 'Bulk Optimization', 'imagify' ), __( 'Bulk Optimization', 'imagify' ), $wp_context->get_capacity( 'bulk-optimize' ), $this->get_bulk_page_slug(), [ $this, 'display_bulk_page' ] ); if ( imagify_is_active_for_network() ) { return; @@ -172,16 +172,16 @@ public function add_site_menus() { if ( imagify_can_optimize_custom_folders() ) { // Sub-menu item: custom folders list. $cf_context = imagify_get_context( 'custom-folders' ); - $screen_id = add_media_page( __( 'Other Media optimized by Imagify', 'imagify' ), __( 'Other Media', 'imagify' ), $cf_context->current_user_can( 'optimize' ), $this->get_files_page_slug(), array( $this, 'display_files_list' ) ); + $screen_id = add_media_page( __( 'Other Media optimized by Imagify', 'imagify' ), __( 'Other Media', 'imagify' ), $cf_context->get_capacity( 'optimize' ), $this->get_files_page_slug(), [ $this, 'display_files_list' ] ); if ( $screen_id ) { // Load the data for this page. - add_action( 'load-' . $screen_id, array( $this, 'load_files_list' ) ); + add_action( 'load-' . $screen_id, [ $this, 'load_files_list' ] ); } } // Sub-menu item: settings. - add_options_page( 'Imagify', 'Imagify', $wp_context->get_capacity( 'manage' ), $this->get_settings_page_slug(), array( $this, 'display_settings_page' ) ); + add_options_page( 'Imagify', 'Imagify', $wp_context->get_capacity( 'manage' ), $this->get_settings_page_slug(), [ $this, 'display_settings_page' ] ); } /** diff --git a/inc/common/attachments.php b/inc/common/attachments.php index 52c603dbb..5b713816a 100755 --- a/inc/common/attachments.php +++ b/inc/common/attachments.php @@ -58,3 +58,12 @@ function imagify_add_webp_type( $ext2type ) { } return $ext2type; } + +/** + * Set WP’s "big images threshold" to Imagify’s resizing value. + * + * @since 1.9.8 + * @since WP 5.3 + * @author Grégory Viguier + */ +add_filter( 'big_image_size_threshold', [ imagify_get_context( 'wp' ), 'get_resizing_threshold' ], IMAGIFY_INT_MAX ); diff --git a/inc/deprecated/Traits/Media/CustomFoldersDeprecatedTrait.php b/inc/deprecated/Traits/Media/CustomFoldersDeprecatedTrait.php new file mode 100644 index 000000000..d4c8e4c2f --- /dev/null +++ b/inc/deprecated/Traits/Media/CustomFoldersDeprecatedTrait.php @@ -0,0 +1,44 @@ +get_fullsize_url()' ); + + if ( ! $this->is_valid() ) { + return false; + } + + if ( $this->get_cdn() ) { + return $this->get_cdn()->get_file_url(); + } + + $row = $this->get_row(); + + if ( ! $row || empty( $row['path'] ) ) { + return false; + } + + return \Imagify_Files_Scan::remove_placeholder( $row['path'], 'url' ); + } +} diff --git a/inc/deprecated/Traits/Media/NGGDeprecatedTrait.php b/inc/deprecated/Traits/Media/NGGDeprecatedTrait.php new file mode 100644 index 000000000..6eee39655 --- /dev/null +++ b/inc/deprecated/Traits/Media/NGGDeprecatedTrait.php @@ -0,0 +1,38 @@ +get_fullsize_url()' ); + + if ( ! $this->is_valid() ) { + return false; + } + + if ( $this->get_cdn() ) { + return $this->get_cdn()->get_file_url(); + } + + return ! empty( $this->image->imageURL ) ? $this->image->imageURL : false; + } +} diff --git a/inc/deprecated/Traits/Media/NoopDeprecatedTrait.php b/inc/deprecated/Traits/Media/NoopDeprecatedTrait.php new file mode 100644 index 000000000..2bef3bba9 --- /dev/null +++ b/inc/deprecated/Traits/Media/NoopDeprecatedTrait.php @@ -0,0 +1,30 @@ +get_fullsize_url()' ); + + return false; + } +} diff --git a/inc/deprecated/Traits/Media/WPDeprecatedTrait.php b/inc/deprecated/Traits/Media/WPDeprecatedTrait.php new file mode 100644 index 000000000..0c89bc5d4 --- /dev/null +++ b/inc/deprecated/Traits/Media/WPDeprecatedTrait.php @@ -0,0 +1,40 @@ +get_fullsize_url()' ); + + if ( ! $this->is_valid() ) { + return false; + } + + if ( $this->get_cdn() ) { + return $this->get_cdn()->get_file_url(); + } + + $url = wp_get_attachment_url( $this->id ); + + return $url ? $url : false; + } +} diff --git a/inc/deprecated/Traits/Optimization/Process/AbstractProcessDeprecatedTrait.php b/inc/deprecated/Traits/Optimization/Process/AbstractProcessDeprecatedTrait.php new file mode 100644 index 000000000..679a48984 --- /dev/null +++ b/inc/deprecated/Traits/Optimization/Process/AbstractProcessDeprecatedTrait.php @@ -0,0 +1,44 @@ +get_fullsize_file()' ); + + if ( isset( $this->file ) ) { + return $this->file; + } + + $this->file = false; + + if ( $this->get_media() ) { + $this->file = new File( $this->get_media()->get_raw_fullsize_path() ); + } + + return $this->file; + } +} diff --git a/inc/functions/admin-ui.php b/inc/functions/admin-ui.php index 0eb72714e..39686751e 100644 --- a/inc/functions/admin-ui.php +++ b/inc/functions/admin-ui.php @@ -124,7 +124,7 @@ function get_imagify_attachment_optimization_text( $process ) { if ( ! $is_library_page ) { $output .= ''; $output .= ''; - $output .= ''; + $output .= ''; if ( $media->is_image() ) { $dimensions = $media->get_dimensions(); diff --git a/inc/functions/i18n.php b/inc/functions/i18n.php index 0a48f65dd..3b08e009a 100755 --- a/inc/functions/i18n.php +++ b/inc/functions/i18n.php @@ -164,7 +164,7 @@ function get_imagify_localize_script_translations( $context ) { if ( $media->is_image() ) { $dimensions = $media->get_dimensions(); $image = [ - 'src' => $media->get_original_url(), + 'src' => $media->get_fullsize_url(), 'width' => $dimensions['width'], 'height' => $dimensions['height'], ]; diff --git a/package.json b/package.json index 56d0c74bd..3d22002da 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "imagify", "description": "Imagify Image Optimizer. Dramatically reduce image file sizes without losing quality, make your website load faster, boost your SEO and save money on your bandwidth.", - "version": "1.9.7", + "version": "1.9.8", "homepage": "https://wordpress.org/plugins/imagify/", "license": "GPL-2.0", "private": true, diff --git a/readme.txt b/readme.txt index 32f7eb542..eea3d4d5d 100755 --- a/readme.txt +++ b/readme.txt @@ -2,8 +2,8 @@ Contributors: wp_media, GregLone Tags: optimize images, images, optimize, performance, webp Requires at least: 4.0.0 -Tested up to: 5.2.2 -Stable tag: 1.9.7 +Tested up to: 5.3 +Stable tag: 1.9.8 Optimize images in one click: reduce image file sizes, convert WebP, keep your images beautiful… and boost your loading time and your SEO! @@ -153,6 +153,15 @@ When the plugin is disabled, your existing images remain optimized. Backups of t 4. Other Media Page == Changelog == += 1.9.8 - 2019/11/11 = +* Improvement: compatibility with WordPress 5.3! +* New: among other things, WordPress 5.3 automatically resizes large images on upload, using a predefined threshold value that can be changed only by filter (no setting fields are provided). Imagify’s "Resize larger images" setting field is now used to tweak this threshold. +* Caution: to be able to work on WordPress 5.3, some adjustments have been made to our compatibility with Enable Media Replace and Regenerate Thumbnails. However, these plugins must be updated to work with WordPress 5.3: do not use them until then. +* Improvement: moved the `width` and `height` attributes from the `` tag to the `` tag to be valid HTML markup. +* Fix: added a missing descriptor in `srcset` attribute when using `` tags to display webp images. This should also fix an issue with LasyLoad. +* Fix: fixed an issue with the user capacity used for "Other Media" menu item. +* Fix: a php notice `stripos(): Non-string needles will be interpreted as strings in the future.`. + = 1.9.7 - 2019/10/08 = * Improvement: prevent greedy antiviruses from crashing the website by renaming our highly dangerous php file with a ".suspected" suffix. * Improvement: on the settings page, display the "Save & Go to Bulk Optimizer" button only if the user has the ability to bulk optimize. diff --git a/views/part-settings-library.php b/views/part-settings-library.php index fe6bc812c..b00e2cd76 100755 --- a/views/part-settings-library.php +++ b/views/part-settings-library.php @@ -6,17 +6,19 @@ $option_name = $options->get_option_name(); ?>
-

+

field_checkbox( array( - 'option_name' => 'resize_larger', - 'label' => __( 'Resize larger images', 'imagify' ), - 'attributes' => array( - 'aria-describedby' => 'describe-resize_larger', - ), - ) ); + $settings->field_checkbox( + [ + 'option_name' => 'resize_larger', + 'label' => __( 'Resize larger images', 'imagify' ), + 'attributes' => [ + 'aria-describedby' => 'describe-resize_larger', + ], + ] + ); ?> @@ -26,7 +28,7 @@ $resize_larger_w = $options->get( 'resize_larger_w' ); printf( /* translators: 1 is a text input for a number of pixels (don't use %d). */ - __( 'to maximum %s pixels width', 'imagify' ), + esc_html__( 'to maximum %s pixels width', 'imagify' ), '' ); ?> @@ -38,9 +40,17 @@ ' . esc_html__( 'Resizing is done on upload or during optimization.', 'imagify' ) . ''; + } else { + esc_html_e( 'Resizing is done only during optimization.', 'imagify' ); + } ?>

@@ -49,23 +59,23 @@
-

+

- +

', '' ); ?>
- +

@@ -73,12 +83,14 @@ /** * Disallowed thumbnail sizes. */ - $settings->field_checkbox_list( array( - 'option_name' => 'disallowed-sizes', - 'legend' => __( 'Choose the sizes to optimize', 'imagify' ), - 'values' => Imagify_Settings::get_thumbnail_sizes(), - 'reverse_check' => true, - ) ); + $settings->field_checkbox_list( + [ + 'option_name' => 'disallowed-sizes', + 'legend' => __( 'Choose the sizes to optimize', 'imagify' ), + 'values' => Imagify_Settings::get_thumbnail_sizes(), + 'reverse_check' => true, + ] + ); ?>