PluginProbe
Image Optimizer – Compress Images and Convert to WebP or AVIF / 1.7.7
Image Optimizer – Compress Images and Convert to WebP or AVIF v1.7.7
1.7.7 1.7.6 1.7.5 1.7.4 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.2.0 1.2.1 1.3.0 1.4.0 1.4.1 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 All 33 releases
← All changes | modules/optimization/classes/optimize-image.php +64 -43 1.1.01.7.7 View file →
@@ -11,9 +11,11 @@
11 11 Exceptions\Image_Backup_Creation_Error,
12 12 Exceptions\Invalid_Image_Exception,
13 13 Image,
14 14 Image_Backup,
15 + Image_Conversion,
15 16 Image_DB_Update,
17 + Image_Dimensions,
16 18 Image_Meta,
17 19 Image_Status,
18 20 WP_Image_Meta
19 21 };
@@ -18,10 +20,9 @@
18 20 WP_Image_Meta
19 21 };
20 22 use ImageOptimization\Classes\Logger;
21 23 use ImageOptimization\Classes\Utils;
22 -use ImageOptimization\Modules\Oauth\Classes\Exceptions\Quota_Exceeded_Error;
23 -use ImageOptimization\Modules\Oauth\Components\Connect;
24 +use ImageOptimization\Classes\Exceptions\Quota_Exceeded_Error;
24 25 use ImageOptimization\Modules\Optimization\Classes\{
25 26 Exceptions\Image_File_Already_Exists_Error,
26 27 Exceptions\Image_Optimization_Error,
27 28 Exceptions\Bulk_Token_Expired_Error,
@@ -26,11 +27,14 @@
26 27 Exceptions\Image_Optimization_Error,
27 28 Exceptions\Bulk_Token_Expired_Error,
28 29 Exceptions\Image_Already_Optimized_Error,
29 30 };
31 +use ImageOptimization\Modules\Connect\Classes\Exceptions\Connection_Error;
30 32 use ImageOptimization\Modules\Settings\Classes\Settings;
31 33 use Throwable;
32 34
35 +use ImageOptimization\Plugin;
36 +
33 37 if ( ! defined( 'ABSPATH' ) ) {
34 38 exit; // Exit if accessed directly.
35 39 }
36 40
@@ -40,9 +44,9 @@
40 44 *
41 45 * This class is used by manual, bulk and on-upload optimization flows.
42 46 */
43 47 class Optimize_Image {
44 - private const IMAGE_OPTIMIZE_ENDPOINT = 'image/optimize';
48 + public const IMAGE_OPTIMIZE_ENDPOINT = 'image/optimize';
45 49
46 50 protected ?Image $image;
47 51 protected WP_Image_Meta $wp_meta;
48 52 protected string $initiator;
@@ -48,19 +52,22 @@
48 52 protected string $initiator;
49 53 protected ?string $bulk_token;
50 54 private string $current_image_path;
51 55 private string $current_image_size;
52 - private bool $convert_to_webp;
53 56 private bool $keep_backups;
54 57 private array $current_size_duplicates;
58 + protected Image_Conversion $image_conversion;
59 + private bool $is_reoptimize;
55 60
56 61 /**
57 - * @throws Quota_Exceeded_Error|Bulk_Token_Expired_Error|Image_File_Already_Exists_Error|Image_Optimization_Error
62 + * @throws Quota_Exceeded_Error|Connection_Error|Bulk_Token_Expired_Error|Image_File_Already_Exists_Error|Image_Optimization_Error
58 63 */
59 64 public function optimize(): void {
60 65 $sizes_enabled = Settings::get( Settings::CUSTOM_SIZES_OPTION_NAME );
61 66 $sizes_exist = $this->wp_meta->get_size_keys();
62 67
68 + Logger::debug( "Start optimization of {$this->image->get_id()}" );
69 +
63 70 foreach ( $sizes_exist as $size_exist ) {
64 71 // If some image sizes optimization is disabled in settings, we check if the current one is still enabled
65 72 if (
66 73 'all' !== $sizes_enabled &&
@@ -69,17 +76,26 @@
69 76 ) {
70 77 continue;
71 78 }
72 79
80 + // Elementor editor generates thumbnails we don't need to optimize.
81 + if ( str_starts_with( $size_exist, 'elementor_' ) ) {
82 + continue;
83 + }
84 +
73 85 $image_meta = new Image_Meta( $this->image->get_id() );
74 86
75 87 // If the current size was already optimized -- ignore it.
76 - if ( in_array( $size_exist, $image_meta->get_optimized_sizes(), true ) ) {
88 + if ( ! $this->is_reoptimize && in_array( $size_exist, $image_meta->get_optimized_sizes(), true ) ) {
89 + Logger::debug( "Size `$size_exist` is already optimized" );
90 +
77 91 continue;
78 92 }
79 93
80 94 if ( ! file_exists( $this->image->get_file_path( $size_exist ) ) ) {
81 - throw new Image_Optimization_Error( esc_html__( 'File is missing. Verify the upload', 'image-optimization' ) );
95 + Logger::debug( "Can't access file for size `$size_exist`" );
96 +
97 + continue;
82 98 }
83 99
84 100 $this->current_image_size = $size_exist;
85 101 $this->current_size_duplicates = $this->wp_meta->get_size_duplicates( $size_exist );
@@ -96,8 +112,10 @@
96 112
97 113 if ( ! $this->keep_backups ) {
98 114 Image_Backup::remove( $this->image->get_id() );
99 115 }
116 +
117 + Logger::debug( "End optimization of {$this->image->get_id()}" );
100 118 }
101 119
102 120 private function optimize_current_size(): void {
103 121 try {
@@ -116,24 +134,24 @@
116 134
117 135 // This should only be updated after meta
118 136 $this->update_attachment_post();
119 137 } catch ( Image_Already_Optimized_Error $iao ) {
120 - // If we can't optimize it further, just file update the meta
138 + // If we can't optimize it further, just update the meta
121 139 $original_size = $this->wp_meta->get_file_size( $this->current_image_size )
122 140 ?? File_System::size( $this->image->get_file_path( $this->current_image_size ) );
123 141
124 - $this->update_attachment_meta( $original_size );
125 - $this->update_attachment_post();
126 - } catch ( Bulk_Token_Expired_Error | Quota_Exceeded_Error | Image_File_Already_Exists_Error $e ) {
142 + $this->update_attachment_meta( $original_size, true );
143 + $this->update_attachment_post( true );
144 + } catch ( Bulk_Token_Expired_Error | Quota_Exceeded_Error | Connection_Error | Image_File_Already_Exists_Error $e ) {
127 145 throw $e;
128 146 } catch ( Throwable $t ) {
129 147 // In case of anything else
130 - throw new Image_Optimization_Error( $t->getMessage() );
148 + throw new Image_Optimization_Error( esc_html( $t->getMessage() ) );
131 149 }
132 150 }
133 151
134 152 private function send_file() {
135 - $connect_status = Connect::check_connect_status();
153 + $connect_status = Plugin::instance()->modules_manager->get_modules( 'connect-manager' )->connect_instance->get_connect_status();
136 154 $headers = [
137 155 'access_token' => $connect_status->access_token ?? '',
138 156 ];
139 157
@@ -142,9 +160,9 @@
142 160 }
143 161
144 162 $optimization_options = [
145 163 'compression_level' => Settings::get( Settings::COMPRESSION_LEVEL_OPTION_NAME ),
146 - 'convert_to_webp' => $this->convert_to_webp,
164 + 'convert_to_format' => $this->image_conversion->get_current_conversion_option(),
147 165 'strip_exif' => Settings::get( Settings::STRIP_EXIF_METADATA_OPTION_NAME ),
148 166 ];
149 167
150 168 if ( Settings::get( Settings::RESIZE_LARGER_IMAGES_OPTION_NAME ) ) {
@@ -169,13 +187,14 @@
169 187 $this->current_image_path,
170 188 'image'
171 189 );
172 190
191 + if ( isset( $response->stats ) ) {
192 + Plugin::instance()->modules_manager->get_modules( 'connect-manager' )->connect_instance->update_usage_data( $response->stats );
193 + }
194 +
173 195 if ( ! isset( $response->imageKey ) || $image_key !== $response->imageKey ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
174 - Logger::log(
175 - Logger::LEVEL_ERROR,
176 - "Image key must be $image_key, instead got $response->imageKey"
177 - );
196 + Logger::error( "Image key must be $image_key, instead got $response->imageKey" );
178 197
179 198 throw new Image_Optimization_Error( esc_html__( 'Service response is incorrect', 'image-optimization' ) );
180 199 }
181 200
@@ -181,12 +200,9 @@
181 200
182 201 $received_file_hash = md5( base64_decode( $response->image, true ) );
183 202
184 203 if ( ! isset( $response->checksum ) || $received_file_hash !== $response->checksum ) {
185 - Logger::log(
186 - Logger::LEVEL_ERROR,
187 - "Image key must be $response->checksum, instead calculated $received_file_hash"
188 - );
204 + Logger::error( "Image key must be $response->checksum, instead calculated $received_file_hash" );
189 205
190 206 throw new Image_Optimization_Error( esc_html__( 'Service response is incorrect', 'image-optimization' ) );
191 207 }
192 208
@@ -204,18 +220,18 @@
204 220 $tmp_path = File_Utils::replace_extension( $path, 'tmp' );
205 221
206 222 File_System::put_contents( $tmp_path, $file_data );
207 223
208 - if ( $this->convert_to_webp ) {
209 - $webp_path = File_Utils::replace_extension( $tmp_path, 'webp' );
210 - $original_file_is_webp = strtolower( $path ) === strtolower( $webp_path );
224 + if ( $this->image_conversion->is_enabled() ) {
225 + $converted_path = File_Utils::replace_extension( $tmp_path, $this->image_conversion->get_current_file_extension() );
226 + $original_is_already_converted = strtolower( $path ) === strtolower( $converted_path );
211 227
212 - if ( ! $original_file_is_webp && File_System::exists( $webp_path ) ) {
228 + if ( ! $original_is_already_converted && File_System::exists( $converted_path ) ) {
213 229 throw new Image_File_Already_Exists_Error();
214 230 }
215 231
216 232 // We want to keep both original as a fallback and optimized webp to prevent 404s
217 - if ( ! $original_file_is_webp ) {
233 + if ( ! $original_is_already_converted ) {
218 234 $this->keep_backups = true;
219 235
220 236 ( new Image_Meta( $this->image->get_id() ) )
221 237 ->set_image_backup_path( $this->current_image_size, $this->current_image_path )
@@ -221,15 +237,15 @@
221 237 ->set_image_backup_path( $this->current_image_size, $this->current_image_path )
222 238 ->save();
223 239 }
224 240
225 - if ( $original_file_is_webp ) {
241 + if ( $original_is_already_converted ) {
226 242 Image_Backup::create( $this->image->get_id(), $this->current_image_size, $this->current_image_path );
227 243 }
228 244
229 - File_System::move( $tmp_path, $webp_path, true );
245 + File_System::move( $tmp_path, $converted_path, true );
230 246
231 - $path = $webp_path;
247 + $path = $converted_path;
232 248 } else {
233 249 Image_Backup::create( $this->image->get_id(), $this->current_image_size, $this->current_image_path );
234 250
235 251 File_System::move( $tmp_path, $path, true );
@@ -248,16 +264,20 @@
248 264 * Updates attachment records in the `wp_posts` table.
249 265 *
250 266 * @return void
251 267 */
252 - private function update_attachment_post() {
268 + private function update_attachment_post( ?bool $is_fully_optimized = false ) {
253 269 $update_query = [];
254 270
255 - if ( $this->convert_to_webp ) {
271 + if ( $this->image_conversion->is_enabled() && ! $is_fully_optimized ) {
256 272 $attachment_object = $this->image->get_attachment_object();
257 273
258 - $update_query['guid'] = File_Utils::replace_extension( $attachment_object->guid, 'webp' );
259 - $update_query['post_mime_type'] = 'image/webp';
274 + $update_query['guid'] = File_Utils::replace_extension(
275 + $attachment_object->guid,
276 + $this->image_conversion->get_current_file_extension()
277 + );
278 +
279 + $update_query['post_mime_type'] = $this->image_conversion->get_current_mime_type();
260 280 }
261 281
262 282 $update_query['post_modified'] = current_time( 'mysql' );
263 283 $update_query['post_modified_gmt'] = current_time( 'mysql', true );
@@ -268,16 +288,16 @@
268 288 /**
269 289 * Updates attachment records in the `wp_postmeta` table.
270 290 *
271 291 * @param int $optimized_size
292 + * @param bool|null $is_fully_optimized
272 293 *
273 294 * @return void
274 295 */
275 - private function update_attachment_meta( int $optimized_size ) {
296 + private function update_attachment_meta( int $optimized_size, ?bool $is_fully_optimized = false ) {
276 297 $meta = new Image_Meta( $this->image->get_id() );
298 + $dimensions = Image_Dimensions::get_by_path( $this->current_image_path );
277 299
278 - list($width, $height) = getimagesize( $this->current_image_path );
279 -
280 300 $sizes_to_update = [ $this->current_image_size, ...$this->current_size_duplicates ];
281 301
282 302 foreach ( $sizes_to_update as $size ) {
283 303 $meta
@@ -285,16 +305,16 @@
285 305 ->add_optimized_size( $size )
286 306 ->add_original_data( $size, $this->wp_meta->get_size_data( $size ) );
287 307
288 308 $this->wp_meta
289 - ->set_width( $size, $width )
290 - ->set_height( $size, $height )
309 + ->set_width( $size, $dimensions->width )
310 + ->set_height( $size, $dimensions->height )
291 311 ->set_file_size( $size, $optimized_size );
292 312
293 - if ( $this->convert_to_webp ) {
313 + if ( $this->image_conversion->is_enabled() && ! $is_fully_optimized ) {
294 314 $this->wp_meta
295 315 ->set_file_path( $size, $this->current_image_path )
296 - ->set_mime_type( $size, 'image/webp' );
316 + ->set_mime_type( $size, $this->image_conversion->get_current_mime_type() );
297 317 }
298 318 }
299 319
300 320 $meta->save();
@@ -331,13 +351,14 @@
331 351
332 352 /**
333 353 * @throws Invalid_Image_Exception
334 354 */
335 - public function __construct( int $image_id, string $initiator, ?string $bulk_token = null ) {
355 + public function __construct( int $image_id, string $initiator, ?string $bulk_token = null, ?bool $is_reoptimize = false ) {
336 356 $this->image = new Image( $image_id );
337 357 $this->wp_meta = new WP_Image_Meta( $image_id, $this->image );
338 358 $this->initiator = $initiator;
339 359 $this->bulk_token = $bulk_token;
340 - $this->convert_to_webp = Settings::get( Settings::CONVERT_TO_WEBP_OPTION_NAME );
360 + $this->is_reoptimize = $is_reoptimize;
361 + $this->image_conversion = new Image_Conversion();
341 362 $this->keep_backups = Settings::get( Settings::BACKUP_ORIGINAL_IMAGES_OPTION_NAME );
342 363 }
343 364 }