PluginProbe
QuickWebP – WebP & AVIF Image Optimizer, Compression & SEO for WordPress / 4.1.2
QuickWebP – WebP & AVIF Image Optimizer, Compression & SEO for WordPress v4.1.2
4.1.2 4.1.1 4.1.0 4.0.1 4.0.0 3.3.1 3.3.0 trunk 1.0.0 2.0.0 2.1.0 3.0.0 3.1.0 3.2.0 3.2.1 3.2.2 3.2.3 3.2.4 3.2.5 3.2.6 3.2.7 3.2.8
← All changes | admin/class-image-optimizer.php +1317 -78 2.1.04.1.2 View file →
@@ -1,8 +1,6 @@
1 1 <?php
2 2
3 -use Intervention\Image\ImageManager;
4 -
5 3 /**
6 4 * The core functionality for image optimizing.
7 5 *
8 6 * @link http://webdeclic.com
@@ -31,8 +29,13 @@
31 29 */
32 30 private $version;
33 31
34 32 /**
33 + * Allowed mime types for the optimazation
34 + */
35 + public $allowed_mime_types;
36 +
37 + /**
35 38 * Initialize the class and set its properties.
36 39 *
37 40 * @since 1.0.0
38 41 * @param string $plugin_name The name of this plugin.
@@ -39,82 +42,958 @@
39 42 * @param string $version The version of this plugin.
40 43 */
41 44 public function __construct( $plugin_name, $version ) {
42 45
43 - $this->plugin_name = $plugin_name;
44 - $this->version = $version;
45 -
46 + $this->plugin_name = $plugin_name;
47 + $this->version = $version;
48 + $this->allowed_mime_types = array( 'image/jpeg', 'image/png', 'image/webp', 'image/avif' );
46 49 }
47 50
48 51 /**
49 52 * Optimize an image added in wp_media
50 53 */
51 - public function image_optimizition( $file ) {
54 + public function image_optimization( $file ) {
55 + global $quickwebp_surecart_client;
52 56
57 + quickwebp_debug_log( 'image_optimization:start', array(
58 + 'file_name' => $file['name'] ?? '',
59 + 'file_type' => $file['type'] ?? '',
60 + ) );
61 +
53 62 $settings = $this->get_settings();
63 + $image_file = $this->file_is_image( $file, $settings );
54 64
55 - $image_file = $this->file_is_image( $file, $settings );
56 65 if ( ! $image_file ) {
66 + quickwebp_debug_log( 'image_optimization:not_image' );
57 67 return $file;
58 68 }
59 69
60 - if ( $settings['quickwebp_settings_conversion'] != '1' ) {
70 + $mode_enabled = $settings['quickwebp_settings_conversion'];
71 + if ( $mode_enabled === '0' ) {
72 + quickwebp_debug_log( 'image_optimization:conversion_disabled' );
61 73 return $image_file;
62 74 }
63 75
64 - $extension_to_use = $this->image_extension_loaded();
65 - if ( ! $extension_to_use ) {
76 + $save_original = is_array( $settings['quickwebp_settings_conversion_save_original'] ) ? $settings['quickwebp_settings_conversion_save_original'] : array();
77 + if ( in_array( 'checked', $save_original ) ) {
78 + quickwebp_debug_log( 'image_optimization:save_original_enabled' );
66 79 return $image_file;
67 80 }
68 81
69 - $manager = new ImageManager(array('driver'=>$extension_to_use));
70 - $image = $manager->make($image_file['tmp_name']);
71 - $height = $image->height();
72 - $width = $image->width();
82 + $ignore_same_format = is_array( $settings['quickwebp_settings_conversion_ignore_webp'] ) ? $settings['quickwebp_settings_conversion_ignore_webp'] : array();
83 + $mime_type = wp_get_image_mime( $image_file['tmp_name'] );
84 + if ( in_array( 'checked', $ignore_same_format ) ) {
85 + if ( '1' == $mode_enabled && 'image/webp' == $mime_type ) {
86 + quickwebp_debug_log( 'image_optimization:skip_same_format', array( 'mime_type' => $mime_type ) );
87 + return $image_file;
88 + } elseif( '2' == $mode_enabled && 'image/avif' == $mime_type ) {
89 + quickwebp_debug_log( 'image_optimization:skip_same_format', array( 'mime_type' => $mime_type ) );
90 + return $image_file;
91 + }
92 + }
73 93
74 - if ( $width > $settings['quickwebp_settings_conversion_max_width'] ) {
75 - $image->widen( $settings['quickwebp_settings_conversion_max_width'] );
94 + $quality = $this->get_the_quality( $settings );
95 + $is_pro = false;
96 + if ( $quickwebp_surecart_client ) {
97 + $is_pro = $quickwebp_surecart_client->license()->is_valid();
76 98 }
99 +
100 + $image_file['new_size'] = $image_file['size'];
101 + $image_file['new_type'] = $image_file['type'];
102 +
103 + if ( '2' === $mode_enabled && $is_pro ) {
104 + quickwebp_debug_log( 'image_optimization:try_avif', array( 'mime_type' => $mime_type, 'quality' => $quality ) );
105 + $avif_image = $this->create_avif_image( $image_file['tmp_name'], $image_file['tmp_name'], $quality );
106 + if ( $avif_image ) {
107 + $image_file['size'] = filesize( $image_file['tmp_name'] );
108 + $image_file['type'] = 'image/avif';
109 + $image_file['quickwebp'] = 'optimized';
110 + $this->record_optimization_stats( $image_file['new_size'], $image_file['size'] );
111 + quickwebp_debug_log( 'image_optimization:avif_success', array( 'new_size' => $image_file['size'] ) );
112 + }
113 + }
114 +
115 + if ( '1' === $mode_enabled ) {
116 + quickwebp_debug_log( 'image_optimization:try_webp', array( 'mime_type' => $mime_type, 'quality' => $quality ) );
117 + $webp_image = $this->create_webp_image( $image_file['tmp_name'], $image_file['tmp_name'], $quality );
118 + if ( $webp_image ) {
119 + $image_file['size'] = filesize( $image_file['tmp_name'] );
120 + $image_file['type'] = 'image/webp';
121 + $image_file['quickwebp'] = 'optimized';
122 + $this->record_optimization_stats( $image_file['new_size'], $image_file['size'] );
123 + quickwebp_debug_log( 'image_optimization:webp_success', array( 'new_size' => $image_file['size'] ) );
124 + }
125 + }
126 +
127 + return $image_file;
128 + }
129 +
130 + /**
131 + * Add post meta to the attachment that already optimized
132 + */
133 + public function add_already_optimized_meta( $metadata, $attachment_id, $context ) {
134 + //phpcs:ignore WordPress.Security.NonceVerification.Missing
135 + $quickwebp = sanitize_text_field( wp_unslash( $_FILES['async-upload']['quickwebp'] ?? '' ) );
77 136
78 - if ( $height > $settings['quickwebp_settings_conversion_max_height'] ) {
79 - $image->heighten( $settings['quickwebp_settings_conversion_max_height'] );
137 + if ( 'optimized' == $quickwebp ) {
138 + //phpcs:ignore WordPress.Security.NonceVerification.Missing
139 + $type = sanitize_text_field( wp_unslash( $_FILES['async-upload']['type'] ?? '' ) );
140 +
141 + if ( 'image/webp' == $type ) {
142 + update_post_meta( $attachment_id, 'quickwebp_already_optimized', '1' );
143 + } elseif ( 'image/avif' == $type ) {
144 + update_post_meta( $attachment_id, 'quickwebp_already_optimized', '2' );
145 + }
80 146 }
81 147
82 - $image->sharpen($settings['quickwebp_settings_conversion_sharpen']);
83 - $image->save( $image_file['tmp_name'], $settings['quickwebp_settings_conversion_quality'], 'webp' );
148 + return $metadata;
149 + }
84 150
85 - $size_before = $image_file['size'];
86 - $size_after = $image->filesize();
87 - $mime = $image->mime();
151 + /**
152 + * Save the original image in the attachment meta
153 + */
154 + public function save_original_image( $metadata, $attachment_id, $context ) {
88 155
89 - $image_file['size'] = $size_after;
90 - $image_file['type'] = $mime;
156 + if ( $context != 'create' ) {
157 + return $metadata;
158 + }
159 +
160 + $settings = $this->get_settings();
161 +
162 + $mode_enabled = $settings['quickwebp_settings_conversion'];
163 + if ( $mode_enabled === '0' ) {
164 + return $metadata;
165 + }
166 +
167 + $save_original = is_array( $settings['quickwebp_settings_conversion_save_original'] ) ? $settings['quickwebp_settings_conversion_save_original'] : array();
168 + if ( ! in_array( 'checked', $save_original ) ) {
169 + return $metadata;
170 + }
171 +
172 + $ignore_same_format = is_array( $settings['quickwebp_settings_conversion_ignore_webp'] ) ? $settings['quickwebp_settings_conversion_ignore_webp'] : array();
173 + //phpcs:ignore WordPress.Security.NonceVerification.Missing
174 + $mime_type = sanitize_text_field( wp_unslash( $_FILES['async-upload']['type'] ?? '' ) );
175 + if ( in_array( 'checked', $ignore_same_format ) ) {
176 + if ( '1' == $mode_enabled && 'image/webp' == $mime_type ) {
177 + return $metadata;
178 + } elseif( '2' == $mode_enabled && 'image/avif' == $mime_type ) {
179 + return $metadata;
180 + }
181 + }
182 +
183 + $sizes = $this->get_media_files( $attachment_id );
184 + $new_sizes = array();
91 185
92 - return $image_file;
186 + foreach ( $sizes as $key => $size ) {
187 + $result = $this->optimize_local_file( $size );
188 +
189 + if ( $result ) {
190 + $new_sizes[$key] = $result;
191 + }
192 + }
193 +
194 + if ( ! empty( $new_sizes ) ) {
195 + $this->record_attachment_optimization_stats( $new_sizes );
196 +
197 + if ( '1' == $mode_enabled ) {
198 + update_post_meta( $attachment_id, 'quickwebp_already_optimized', '1' );
199 + } elseif ( '2' == $mode_enabled ) {
200 + update_post_meta( $attachment_id, 'quickwebp_already_optimized', '2' );
201 + }
202 +
203 + update_post_meta( $attachment_id, 'quickwebp_data', $new_sizes );
204 + delete_post_meta( $attachment_id, 'quickwebp_has_error' );
205 + } else {
206 + update_post_meta( $attachment_id, 'quickwebp_has_error', '1' );
207 + }
208 +
209 + return $metadata;
93 210 }
94 211
95 212 /**
96 - * Get the package used by the server
97 - *
213 + * Add data to the attachment
98 214 */
99 - public function image_extension_loaded() {
215 + public function add_data_to_attachment( $metadata, $attachment_id, $context ) {
100 216
101 - $accepted_librarys = array( 'gd', 'imagick' );
102 - $library_to_use = get_option( 'quickwebp_settings_library', quickwebp_settings_default('quickwebp_settings_library') );
217 + if ( $context != 'create' ) {
218 + return $metadata;
219 + }
103 220
104 - if ( in_array( $library_to_use, $accepted_librarys ) ) {
221 + $settings = $this->get_settings();
222 + if ( $settings['quickwebp_settings_completion'] != '1' ) {
223 + return $metadata;
224 + }
105 225
106 - return $library_to_use;
226 + //phpcs:ignore WordPress.Security.NonceVerification.Missing
227 + $original_name = isset( $_FILES['async-upload']['original_name'] ) ? sanitize_text_field( wp_unslash( $_FILES['async-upload']['original_name'] ) ) : sanitize_text_field( $_FILES['async-upload']['name'] ?? '' );
228 + if ( ! empty( $original_name ) ) {
229 + $original_name = pathinfo( $original_name, PATHINFO_FILENAME );
230 + $post_arr = array();
231 +
232 + $completion_options = is_array( $settings['quickwebp_settings_completion_options'] ) ? $settings['quickwebp_settings_completion_options'] : array();
233 +
234 + if ( in_array( 'title', $completion_options ) ) {
235 + $post_arr['post_title'] = $original_name;
236 + }
237 +
238 + if ( in_array( 'caption', $completion_options ) ) {
239 + $post_arr['post_excerpt'] = $original_name;
240 + }
241 +
242 + if ( in_array( 'alt', $completion_options ) ) {
243 + $post_arr['meta_input']['_wp_attachment_image_alt'] = $original_name;
244 + }
245 +
246 + if ( in_array( 'description', $completion_options ) ) {
247 + $post_arr['post_content'] = $original_name;
248 + }
249 +
250 + if ( ! empty( $post_arr ) ) {
251 + $post_arr['ID'] = $attachment_id;
252 + wp_update_post( $post_arr );
253 + }
107 254 }
108 255
256 + return $metadata;
257 + }
258 +
259 + /**
260 + * Change the default size of wp editor
261 + */
262 + public function change_wp_max_size( $max_size, $imagesize ) {
263 +
264 + $settings = $this->get_settings();
265 + $resize_active = $settings['quickwebp_settings_resize'];
266 +
267 + if ( $resize_active == '1' ) {
268 +
269 + $imagesize_width = isset($imagesize[0]) ? $imagesize[0] : 0;
270 + $resize_value = $settings['quickwebp_settings_resize_value'];
271 +
272 + if ( $imagesize_width > $resize_value ) {
273 + $max_size = $resize_value;
274 + }
275 + }
276 +
277 + return $max_size;
278 + }
279 +
280 + /**
281 + * Change the default quality of wp editor
282 + */
283 + public function change_wp_quality( $default_quality ) {
284 +
285 + $settings = $this->get_settings();
286 + $mode_enabled = $settings['quickwebp_settings_conversion'];
287 + if ( $mode_enabled === '0' ) {
288 + return $default_quality;
289 + }
290 +
291 + return 100;
292 + }
293 +
294 + /**
295 + * Optimize image through ajax
296 + */
297 + public function image_optimization_ajax() {
298 + global $quickwebp_surecart_client;
299 +
300 + quickwebp_debug_log( 'image_optimization_ajax:start' );
301 +
302 + if ( ! current_user_can( 'upload_files' ) ) {
303 + wp_send_json_error( __( 'You are not allowed to upload files.', 'quickwebp' ), 403 );
304 + }
305 +
306 + // verify the nonce.
307 + $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : '';
308 + if( !wp_verify_nonce( $nonce, 'image_optimize_nonce' ) ) {
309 + wp_send_json_error( __( 'Refresh the page and try again.', 'quickwebp' ) );
310 + }
311 +
312 + // Get the file
313 + $file = count($_FILES) > 0 ? array_shift($_FILES) : array();
314 + if ( empty($file) ) {
315 + quickwebp_debug_log( 'image_optimization_ajax:missing_file' );
316 + wp_send_json_error( __( 'No image uploaded, try again.', 'quickwebp' ) );
317 + }
318 +
319 + $settings = $this->get_ajax_settings();
320 + $image_file = $this->file_is_image( $file, $settings );
321 +
322 + if ( ! $image_file ) {
323 + quickwebp_debug_log( 'image_optimization_ajax:not_image' );
324 + wp_send_json_error( __( 'No image uploaded, try again.', 'quickwebp' ) );
325 + }
326 +
327 + $mode_enabled = $settings['quickwebp_settings_conversion'];
328 + if ( $mode_enabled === '0' ) {
329 + quickwebp_debug_log( 'image_optimization_ajax:conversion_disabled' );
330 + $image_file['new_size'] = $image_file['size'];
331 + $image_file['new_type'] = $image_file['type'];
332 + $this->return_ajax_data( $image_file );
333 + }
334 +
335 + $quality = $this->get_the_quality( $settings );
336 + $is_pro = false;
337 + if ( $quickwebp_surecart_client ) {
338 + $is_pro = $quickwebp_surecart_client->license()->is_valid();
339 + }
340 +
341 + $image_file['new_size'] = $image_file['size'];
342 + $image_file['new_type'] = $image_file['type'];
343 +
344 + if ( '2' === $mode_enabled && $is_pro ) {
345 + quickwebp_debug_log( 'image_optimization_ajax:try_avif', array( 'quality' => $quality ) );
346 + $avif_image = $this->create_avif_image( $image_file['tmp_name'], $image_file['tmp_name'], $quality );
347 + if ( $avif_image ) {
348 + $image_file['new_size'] = filesize( $image_file['tmp_name'] );
349 + $image_file['new_type'] = 'image/avif';
350 + }
351 + }
352 +
353 + if ( '1' === $mode_enabled ) {
354 + quickwebp_debug_log( 'image_optimization_ajax:try_webp', array( 'quality' => $quality ) );
355 + $webp_image = $this->create_webp_image( $image_file['tmp_name'], $image_file['tmp_name'], $quality );
356 + if ( $webp_image ) {
357 + $image_file['new_size'] = filesize( $image_file['tmp_name'] );
358 + $image_file['new_type'] = 'image/webp';
359 + }
360 + }
361 +
362 + $this->return_ajax_data( $image_file );
363 + }
364 +
365 + /**
366 + * Optimize a single media
367 + */
368 + public function single_optimization_ajax() {
369 + quickwebp_debug_log( 'single_optimization_ajax:start' );
370 +
371 + // verify the nonce.
372 + $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : '';
373 + if( !wp_verify_nonce( $nonce, 'quickwebp_admin_attachment' ) ) {
374 + wp_send_json_error( __( 'Refresh the page and try again.', 'quickwebp' ) );
375 + }
376 +
377 + // Sanitize data
378 + $attachment_id = isset( $_POST['attachment_id'] ) ? absint( wp_unslash( $_POST['attachment_id'] ) ) : 0;
379 +
380 + if ( ! $attachment_id ) {
381 + quickwebp_debug_log( 'single_optimization_ajax:missing_attachment_id' );
382 + wp_send_json_error( __( 'No attachment id.', 'quickwebp' ) );
383 + }
384 +
385 + $settings = $this->get_settings();
386 + $mode_enabled = $settings['quickwebp_settings_conversion'];
387 + if ( '0' === $mode_enabled ) {
388 + wp_send_json_error( __( 'Choose an image format and save the settings.', 'quickwebp' ) );
389 + }
390 +
391 + $already_optimized = get_post_meta( $attachment_id, 'quickwebp_already_optimized', true );
392 +
393 + if ( '1' == $already_optimized && '1' == $mode_enabled ) {
394 + wp_send_json_error( __( 'Already optimized.', 'quickwebp' ) );
395 + }
396 +
397 + if ( '2' == $already_optimized && '2' == $mode_enabled ) {
398 + wp_send_json_error( __( 'Already optimized.', 'quickwebp' ) );
399 + }
400 +
401 + $post_mime_type = get_post_mime_type( $attachment_id );
402 + if ( ! in_array( $post_mime_type, $this->allowed_mime_types ) ) {
403 + wp_send_json_error( __( 'Not a valid image.', 'quickwebp' ) );
404 + }
405 +
406 + $sizes = $this->get_media_files( $attachment_id );
407 + $new_sizes = array();
408 +
409 + foreach ( $sizes as $key => $size ) {
410 + quickwebp_debug_log( 'single_optimization_ajax:optimize_size', array( 'size_key' => $key, 'path' => $size['path'] ?? '' ) );
411 + $result = $this->optimize_local_file( $size );
412 +
413 + if ( $result ) {
414 + $new_sizes[$key] = $result;
415 + }
416 + }
417 +
418 + if ( ! empty( $new_sizes ) ) {
419 + $this->record_attachment_optimization_stats( $new_sizes );
420 +
421 + $data = get_post_meta( $attachment_id, 'quickwebp_data', true );
422 + if ( ! empty( $data ) ) {
423 + $this->remove_related_files( $data );
424 + }
425 +
426 + if ( '1' == $mode_enabled ) {
427 + update_post_meta( $attachment_id, 'quickwebp_already_optimized', '1' );
428 + } elseif ( '2' == $mode_enabled ) {
429 + update_post_meta( $attachment_id, 'quickwebp_already_optimized', '2' );
430 + }
431 +
432 + update_post_meta( $attachment_id, 'quickwebp_data', $new_sizes );
433 + delete_post_meta( $attachment_id, 'quickwebp_has_error' );
434 + } else {
435 + update_post_meta( $attachment_id, 'quickwebp_has_error', '1' );
436 + delete_post_meta( $attachment_id, 'quickwebp_already_optimized' );
437 + delete_post_meta( $attachment_id, 'quickwebp_data' );
438 + }
439 +
440 + $wp_media_extend = new Quickwebp_Wp_Media_Extends( $this->plugin_name, $this->version );
441 + $html = $wp_media_extend->attachment_data( $new_sizes, $attachment_id );
442 +
443 + wp_send_json_success( $html );
444 + }
445 +
446 + /**
447 + * Undo a single media optimization
448 + */
449 + public function undo_single_optimization_ajax() {
450 +
451 + // verify the nonce.
452 + $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : '';
453 + if( !wp_verify_nonce( $nonce, 'quickwebp_admin_attachment' ) ) {
454 + wp_send_json_error( __( 'Refresh the page and try again.', 'quickwebp' ) );
455 + }
456 +
457 + // Sanitize data
458 + $attachment_id = isset( $_POST['attachment_id'] ) ? absint( wp_unslash( $_POST['attachment_id'] ) ) : 0;
459 +
460 + if ( ! $attachment_id ) {
461 + wp_send_json_error( __( 'No attachment id.', 'quickwebp' ) );
462 + }
463 +
464 + $data = get_post_meta( $attachment_id, 'quickwebp_data', true );
465 + if ( ! empty( $data ) ) {
466 + $this->remove_related_files( $data );
467 + delete_post_meta( $attachment_id, 'quickwebp_already_optimized' );
468 + delete_post_meta( $attachment_id, 'quickwebp_data' );
469 +
470 + $wp_media_extend = new Quickwebp_Wp_Media_Extends( $this->plugin_name, $this->version );
471 + $html = $wp_media_extend->optimize_btn( $attachment_id );
472 + wp_send_json_success( $html );
473 + } else {
474 + wp_send_json_error( __( 'Not optimized.', 'quickwebp' ) );
475 + }
476 + }
477 +
478 + /**
479 + * Trigger before delete attachment
480 + */
481 + public function before_delete_attachment( $post_id ) {
482 + $data = get_post_meta( $post_id, 'quickwebp_data', true );
483 + if ( ! empty( $data ) ) {
484 + $this->remove_related_files( $data );
485 + }
486 + }
487 +
488 + /**
489 + * Get the image data for the preview in the settings page
490 + */
491 + public function get_image_data_for_preview_ajax() {
492 + quickwebp_debug_log( 'get_image_data_for_preview_ajax:start' );
493 +
494 + // verify the nonce.
495 + $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : '';
496 + if( !wp_verify_nonce( $nonce, 'image_optimize_nonce' ) ) {
497 + wp_send_json_error( __( 'Refresh the page and try again.', 'quickwebp' ) );
498 + }
499 +
500 + // Get the file
501 + $file = count($_FILES) > 0 ? array_shift($_FILES) : array();
502 + if ( empty($file) ) {
503 + quickwebp_debug_log( 'get_image_data_for_preview_ajax:missing_file' );
504 + wp_send_json_error( __( 'No image uploaded, try again.', 'quickwebp' ) );
505 + }
506 +
507 + $image_file = $this->file_is_image( $file );
508 + if ( ! $image_file ) {
509 + quickwebp_debug_log( 'get_image_data_for_preview_ajax:not_image' );
510 + wp_send_json_error( __( 'No image uploaded, try again.', 'quickwebp' ) );
511 + }
512 +
513 + $preview_image_data = array(
514 + 'name' => $image_file['name'],
515 + 'size' => size_format( $image_file['size'], 2 ),
516 + 'dimensions' => $image_file['width'] . ' x ' . $image_file['height'],
517 + );
518 +
519 + $conversions = array( '1', '2' );
520 + $qualities = array( 'low', 'medium', 'high', 'extra_high' );
521 +
522 + foreach ( $conversions as $conversion ) {
523 + foreach ( $qualities as $quality ) {
524 + quickwebp_debug_log( 'get_image_data_for_preview_ajax:preview_variant', array( 'conversion' => $conversion, 'quality' => $quality ) );
525 +
526 + $size_after = 0;
527 +
528 + if ( '2' == $conversion ) {
529 + $new_path = $image_file['tmp_name'] . '-' . $quality . '.avif';
530 + $quality_value = $this->get_the_quality_values( $quality, $conversion );
531 + $avif_image = $this->create_avif_image( $image_file['tmp_name'], $new_path, $quality_value );
532 +
533 + if ( $avif_image ) {
534 + $size_after = filesize( $new_path );
535 + }
536 + } elseif ( '1' == $conversion ) {
537 + $new_path = $image_file['tmp_name'] . '-' . $quality . '.webp';
538 + $quality_value = $this->get_the_quality_values( $quality, $conversion );
539 + $webp_image = $this->create_webp_image( $image_file['tmp_name'], $new_path, $quality_value );
540 +
541 + if ( $webp_image ) {
542 + $size_after = filesize( $new_path );
543 + }
544 + }
545 +
546 + $deference = $image_file['size'] - $size_after;
547 + $percent = $deference / $image_file['size'] * 100;
548 +
549 + $preview_image_data[$conversion . '_' . $quality] = array(
550 + 'size' => size_format( $size_after, 2 ),
551 + 'save' => size_format( $deference, 2 ),
552 + 'save_percent' => round( $percent ) . '%',
553 + 'percent' => round( 100 - $percent ) . '%',
554 + );
555 + }
556 + }
557 +
558 + wp_send_json_success( $preview_image_data );
559 + }
560 +
561 + /**
562 + * Get the unoptimized media ids
563 + *
564 + * @param int $limit Maximum number of media IDs to retrieve.
565 + * @return int[]
566 + */
567 + public function get_unoptimized_media_ids( $limit = 5 ) {
568 + $query_args = $this->get_unoptimized_media_query_args();
569 + $query_args['posts_per_page'] = max( 1, absint( $limit ) );
570 + $query_args['fields'] = 'ids';
571 + $query_args['no_found_rows'] = true;
572 +
573 + return get_posts( $query_args );
574 + }
575 +
576 + /**
577 + * Count unoptimized media.
578 + *
579 + * @return int
580 + */
581 + public function get_unoptimized_media_count() {
582 + $query_args = $this->get_unoptimized_media_query_args();
583 + $query_args['posts_per_page'] = 1;
584 + $query_args['fields'] = 'ids';
585 +
586 + $query = new WP_Query( $query_args );
587 +
588 + return absint( $query->found_posts );
589 + }
590 +
591 + /**
592 + * Get the shared query arguments for unoptimized media.
593 + *
594 + * @return array
595 + */
596 + private function get_unoptimized_media_query_args() {
597 +
598 + $settings = $this->get_settings();
599 + $mode_enabled = $settings['quickwebp_settings_conversion'];
600 + $ignore_same_format = is_array( $settings['quickwebp_settings_conversion_ignore_webp'] ) ? $settings['quickwebp_settings_conversion_ignore_webp'] : array();
601 +
602 + $statuses = array(
603 + 'inherit' => 'inherit',
604 + 'private' => 'private',
605 + );
606 + $custom_statuses = get_post_stati( array( 'public' => true ) );
607 + unset( $custom_statuses['publish'] );
608 + if ( $custom_statuses ) {
609 + $statuses = array_merge( $statuses, $custom_statuses );
610 + }
611 +
612 + $allowed_mime_types = $this->allowed_mime_types;
613 + if ( in_array( 'checked', $ignore_same_format ) ) {
614 + if ( '1' == $mode_enabled ) {
615 + $webp_index = array_search( 'image/webp', $allowed_mime_types );
616 + unset( $allowed_mime_types[$webp_index] );
617 + } elseif ( '2' == $mode_enabled ) {
618 + $avif_index = array_search( 'image/avif', $allowed_mime_types );
619 + unset( $allowed_mime_types[$avif_index] );
620 + }
621 + }
622 +
623 + $meta_query_already_optimized = array(
624 + 'relation' => 'OR',
625 + array(
626 + 'key' => 'quickwebp_already_optimized',
627 + 'compare' => 'NOT EXISTS'
628 + ),
629 + );
630 +
631 + if ( 'webp' == $mode_enabled ) {
632 + $meta_query_already_optimized[] = array(
633 + 'key' => 'quickwebp_already_optimized',
634 + 'compare' => '!=',
635 + 'value' => '1',
636 + );
637 + } elseif ( 'avif' == $mode_enabled ) {
638 + $meta_query_already_optimized[] = array(
639 + 'key' => 'quickwebp_already_optimized',
640 + 'compare' => '!=',
641 + 'value' => '2',
642 + );
643 + }
644 +
645 + return array(
646 + 'post_type' => 'attachment',
647 + 'post_mime_type' => $allowed_mime_types,
648 + 'post_status' => array_keys( $statuses ),
649 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
650 + 'meta_query' => array(
651 + 'relation' => 'AND',
652 + array(
653 + 'key' => 'quickwebp_has_error',
654 + 'compare' => 'NOT EXISTS'
655 + ),
656 + $meta_query_already_optimized,
657 + ),
658 + );
659 + }
660 +
661 + /**
662 + * Get the list of media files
663 + */
664 + public function get_media_files( $media_id ) {
665 + $fullsize_path = get_attached_file( $media_id );
666 +
667 + if ( ! $fullsize_path ) {
668 + return array();
669 + }
670 +
671 + $media_data = wp_get_attachment_image_src( $media_id, 'full' );
672 + $file_type = wp_check_filetype( $fullsize_path );
673 +
674 + $all_sizes = [
675 + 'full' => [
676 + 'size' => 'full',
677 + 'path' => $fullsize_path,
678 + 'width' => $media_data[1],
679 + 'height' => $media_data[2],
680 + 'mime-type' => $file_type['type'],
681 + 'disabled' => false,
682 + ],
683 + ];
684 +
685 + $sizes = wp_get_attachment_metadata( $media_id, true );
686 + $sizes = ! empty( $sizes['sizes'] ) && is_array( $sizes['sizes'] ) ? $sizes['sizes'] : [];
687 +
688 + $dir_path = trailingslashit( dirname( $fullsize_path ) );
689 +
690 + foreach ( $sizes as $size => $size_data ) {
691 + $all_sizes[ $size ] = [
692 + 'size' => $size,
693 + 'path' => $dir_path . $size_data['file'],
694 + 'width' => $size_data['width'],
695 + 'height' => $size_data['height'],
696 + 'mime-type' => $size_data['mime-type'],
697 + 'disabled' => false
698 + ];
699 + }
700 +
701 + return $all_sizes;
702 + }
703 +
704 + /**
705 + * Optimize a local file
706 + */
707 + public function optimize_local_file( $size ) {
708 + global $quickwebp_surecart_client;
709 +
710 + quickwebp_debug_log( 'optimize_local_file:start', array( 'path' => $size['path'] ?? '' ) );
711 +
712 + if ( !is_file($size['path']) ) {
713 + quickwebp_debug_log( 'optimize_local_file:missing_file', array( 'path' => $size['path'] ?? '' ) );
714 + return false;
715 + }
716 +
717 + $real_type = mime_content_type( $size['path']);
718 + if ( !in_array( $real_type, $this->allowed_mime_types ) ) {
719 + quickwebp_debug_log( 'optimize_local_file:invalid_mime_type', array( 'mime_type' => $real_type ) );
720 + return false;
721 + }
722 +
723 + $settings = $this->get_settings();
724 + $quality = $this->get_the_quality( $settings );
725 + $is_pro = false;
726 + if ( $quickwebp_surecart_client ) {
727 + $is_pro = $quickwebp_surecart_client->license()->is_valid();
728 + }
729 + $mode_enabled = $settings['quickwebp_settings_conversion'];
730 + $size_before = filesize( $size['path'] );
731 + $new_path = $size['path'] . '.' . ( '2' === $mode_enabled && $is_pro ? 'avif' : 'webp' );
732 + $size_after = 0;
733 +
734 + if ( '2' === $mode_enabled && $is_pro ) {
735 + quickwebp_debug_log( 'optimize_local_file:try_avif', array( 'path' => $size['path'], 'quality' => $quality ) );
736 + $avif_image = $this->create_avif_image( $size['path'], $new_path, $quality );
737 + if ( $avif_image ) {
738 + $size_after = filesize( $new_path );
739 + }
740 + } elseif ( '1' === $mode_enabled ) {
741 + quickwebp_debug_log( 'optimize_local_file:try_webp', array( 'path' => $size['path'], 'quality' => $quality ) );
742 + $webp_image = $this->create_webp_image( $size['path'], $new_path, $quality );
743 + if ( $webp_image ) {
744 + $size_after = filesize( $new_path );
745 + }
746 + }
747 +
748 + if ( ! $size_after ) {
749 + quickwebp_debug_log( 'optimize_local_file:conversion_failed', array( 'path' => $size['path'], 'target_path' => $new_path ) );
750 + return false;
751 + }
752 +
753 + $deference = $size_before - $size_after;
754 + $percent = $deference / $size_before * 100;
755 +
756 + return array(
757 + 'success' => 1,
758 + 'original_size' => $size_before,
759 + 'optimized_size' => $size_after,
760 + 'percent' => round( $percent, 2 ),
761 + 'path' => $new_path,
762 + 'format' => $mode_enabled,
763 + );
764 + }
765 +
766 + /**
767 + * Record aggregated optimization statistics for one image attachment.
768 + *
769 + * @param array $optimized_sizes Optimization results for the attachment sizes.
770 + */
771 + public function record_attachment_optimization_stats( $optimized_sizes ) {
772 + $stats = $this->get_attachment_optimization_stats( $optimized_sizes );
773 + $this->record_optimization_stats( $stats['bytes_before'], $stats['bytes_after'], $stats['images_optimized'] );
774 + }
775 +
776 + /**
777 + * Calculate optimization statistics for attachment sizes without persisting them.
778 + *
779 + * @param array $optimized_sizes Optimization results for the attachment sizes.
780 + * @return array
781 + */
782 + public function get_attachment_optimization_stats( $optimized_sizes ) {
783 + $stats = array(
784 + 'images_optimized' => 0,
785 + 'bytes_before' => 0,
786 + 'bytes_after' => 0,
787 + );
788 +
789 + foreach ( $optimized_sizes as $optimized_size ) {
790 + $stats['bytes_before'] += absint( $optimized_size['original_size'] ?? 0 );
791 + $stats['bytes_after'] += absint( $optimized_size['optimized_size'] ?? 0 );
792 + }
793 +
794 + if ( $stats['bytes_before'] && $stats['bytes_after'] ) {
795 + $stats['images_optimized'] = 1;
796 + }
797 +
798 + return $stats;
799 + }
800 +
801 + /**
802 + * Record local cumulative optimization statistics.
803 + *
804 + * @param int $size_before File size before optimization in bytes.
805 + * @param int $size_after File size after optimization in bytes.
806 + * @param int $images_optimized Number of optimized attachments.
807 + */
808 + public function record_optimization_stats( $size_before, $size_after, $images_optimized = 1 ) {
809 + $size_before = absint( $size_before );
810 + $size_after = absint( $size_after );
811 + $images_optimized = absint( $images_optimized );
812 +
813 + if ( ! $size_before || ! $size_after || ! $images_optimized ) {
814 + return;
815 + }
816 +
817 + $lock_token = $this->acquire_optimization_stats_lock();
818 + if ( ! $lock_token ) {
819 + $this->queue_optimization_stats_delta( $size_before, $size_after, $images_optimized );
820 + return;
821 + }
822 +
823 + $stats = get_option( 'quickwebp_optimization_stats', array() );
824 + $stats = wp_parse_args(
825 + is_array( $stats ) ? $stats : array(),
826 + array(
827 + 'images_optimized' => 0,
828 + 'bytes_before' => 0,
829 + 'bytes_after' => 0,
830 + 'bytes_saved' => 0,
831 + )
832 + );
833 + $pending_stats = $this->consume_optimization_stats_deltas();
834 +
835 + $stats['images_optimized'] = absint( $stats['images_optimized'] ) + $images_optimized + $pending_stats['images_optimized'];
836 + $stats['bytes_before'] = absint( $stats['bytes_before'] ) + $size_before + $pending_stats['bytes_before'];
837 + $stats['bytes_after'] = absint( $stats['bytes_after'] ) + $size_after + $pending_stats['bytes_after'];
838 + $stats['bytes_saved'] = max( 0, $stats['bytes_before'] - $stats['bytes_after'] );
839 +
840 + update_option( 'quickwebp_optimization_stats', $stats, false );
841 + $this->release_optimization_stats_lock( $lock_token );
842 + }
843 +
844 + /**
845 + * Queue a statistics delta when another request owns the update lock.
846 + *
847 + * @param int $size_before File size before optimization in bytes.
848 + * @param int $size_after File size after optimization in bytes.
849 + * @param int $images_optimized Number of optimized attachments.
850 + */
851 + private function queue_optimization_stats_delta( $size_before, $size_after, $images_optimized ) {
852 + $option_name = 'quickwebp_optimization_stats_delta_' . str_replace( '-', '', wp_generate_uuid4() );
853 + add_option(
854 + $option_name,
855 + array(
856 + 'images_optimized' => $images_optimized,
857 + 'bytes_before' => $size_before,
858 + 'bytes_after' => $size_after,
859 + ),
860 + '',
861 + false
862 + );
863 + }
864 +
865 + /**
866 + * Consume queued statistics deltas while holding the update lock.
867 + *
868 + * @return array
869 + */
870 + private function consume_optimization_stats_deltas() {
871 + global $wpdb;
872 +
873 + $pending_stats = array(
874 + 'images_optimized' => 0,
875 + 'bytes_before' => 0,
876 + 'bytes_after' => 0,
877 + );
878 + $option_prefix = 'quickwebp_optimization_stats_delta_';
879 + $option_names = $wpdb->get_col(
880 + $wpdb->prepare(
881 + "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s",
882 + $wpdb->esc_like( $option_prefix ) . '%'
883 + )
884 + );
885 +
886 + foreach ( $option_names as $option_name ) {
887 + $delta = get_option( $option_name, array() );
888 + if ( is_array( $delta ) ) {
889 + $pending_stats['images_optimized'] += absint( $delta['images_optimized'] ?? 0 );
890 + $pending_stats['bytes_before'] += absint( $delta['bytes_before'] ?? 0 );
891 + $pending_stats['bytes_after'] += absint( $delta['bytes_after'] ?? 0 );
892 + }
893 + delete_option( $option_name );
894 + }
895 +
896 + return $pending_stats;
897 + }
898 +
899 + /**
900 + * Acquire an expiring lock for cumulative statistics updates.
901 + *
902 + * @return string|false
903 + */
904 + private function acquire_optimization_stats_lock() {
905 + $lock_token = wp_generate_uuid4();
906 + $lock = array(
907 + 'token' => $lock_token,
908 + 'expires' => time() + MINUTE_IN_SECONDS,
909 + );
910 +
911 + if ( add_option( 'quickwebp_optimization_stats_lock', $lock, '', false ) ) {
912 + return $lock_token;
913 + }
914 +
915 + $existing_lock = get_option( 'quickwebp_optimization_stats_lock', array() );
916 + if ( is_array( $existing_lock ) && absint( $existing_lock['expires'] ?? 0 ) < time() ) {
917 + delete_option( 'quickwebp_optimization_stats_lock' );
918 + if ( add_option( 'quickwebp_optimization_stats_lock', $lock, '', false ) ) {
919 + return $lock_token;
920 + }
921 + }
922 +
109 923 return false;
110 924 }
111 925
112 926 /**
927 + * Release the statistics lock owned by this process.
928 + *
929 + * @param string $lock_token Lock ownership token.
930 + */
931 + private function release_optimization_stats_lock( $lock_token ) {
932 + $lock = get_option( 'quickwebp_optimization_stats_lock', array() );
933 + if ( is_array( $lock ) && hash_equals( (string) ( $lock['token'] ?? '' ), $lock_token ) ) {
934 + delete_option( 'quickwebp_optimization_stats_lock' );
935 + }
936 + }
937 +
938 + /**
939 + * Remove the related files of an optimized attachment
940 + */
941 + public function remove_related_files( $data ) {
942 + foreach ( $data as $value ) {
943 + $path = $value['path'] ?? '';
944 +
945 + if ( ! empty( $path ) && file_exists( $path ) ) {
946 + wp_delete_file( $path );
947 + }
948 + }
949 + }
950 +
951 + /**
952 + * Get the optimization settings
953 + */
954 + public function get_settings() {
955 + return array(
956 + 'quickwebp_settings_conversion' => get_option('quickwebp_settings_conversion', quickwebp_settings_default('quickwebp_settings_conversion') ),
957 + 'quickwebp_settings_conversion_quality' => get_option('quickwebp_settings_conversion_quality', quickwebp_settings_default('quickwebp_settings_conversion_quality') ),
958 + 'quickwebp_settings_conversion_ignore_webp' => get_option('quickwebp_settings_conversion_ignore_webp', quickwebp_settings_default('quickwebp_settings_conversion_ignore_webp') ),
959 + 'quickwebp_settings_conversion_save_original' => get_option('quickwebp_settings_conversion_save_original', quickwebp_settings_default('quickwebp_settings_conversion_save_original') ),
960 + 'quickwebp_settings_resize' => get_option('quickwebp_settings_resize', quickwebp_settings_default('quickwebp_settings_resize') ),
961 + 'quickwebp_settings_resize_value' => get_option('quickwebp_settings_resize_value', quickwebp_settings_default('quickwebp_settings_resize_value') ),
962 + 'quickwebp_settings_completion' => get_option('quickwebp_settings_completion', quickwebp_settings_default('quickwebp_settings_completion') ),
963 + 'quickwebp_settings_completion_options' => get_option('quickwebp_settings_completion_options', quickwebp_settings_default('quickwebp_settings_completion_options') ),
964 + 'quickwebp_settings_cleanup' => get_option('quickwebp_settings_cleanup', quickwebp_settings_default('quickwebp_settings_cleanup') ),
965 + );
966 + }
967 +
968 + /**
969 + * Get the optimization settings
970 + */
971 + private function get_ajax_settings() {
972 + //phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
973 + $raw_settings = isset( $_POST['settings'] ) ? wp_unslash( $_POST['settings'] ) : '';
974 + $settings = is_string( $raw_settings ) ? json_decode( $raw_settings, true ) : array();
975 +
976 + if ( ! is_array( $settings ) ) {
977 + $settings = array();
978 + }
979 +
980 + return array(
981 + 'quickwebp_settings_conversion' => isset( $settings['quickwebp_settings_conversion'] ) ? sanitize_text_field( (string) $settings['quickwebp_settings_conversion'] ) : '0',
982 + 'quickwebp_settings_conversion_quality' => isset( $settings['quickwebp_settings_conversion_quality'] ) ? sanitize_text_field( $settings['quickwebp_settings_conversion_quality'] ) : 'high',
983 + 'quickwebp_settings_conversion_ignore_webp' => isset( $settings['quickwebp_settings_conversion_ignore_webp'] ) && is_array( $settings['quickwebp_settings_conversion_ignore_webp'] ) ? array_map( 'sanitize_text_field', $settings['quickwebp_settings_conversion_ignore_webp'] ) : array(),
984 + 'quickwebp_settings_resize' => isset( $settings['quickwebp_settings_resize'] ) ? sanitize_text_field( (string) $settings['quickwebp_settings_resize'] ) : '',
985 + 'quickwebp_settings_resize_value' => isset( $settings['quickwebp_settings_resize_value'] ) ? absint( $settings['quickwebp_settings_resize_value'] ) : 0,
986 + 'quickwebp_settings_completion' => isset( $settings['quickwebp_settings_completion'] ) ? sanitize_text_field( (string) $settings['quickwebp_settings_completion'] ) : '',
987 + 'quickwebp_settings_completion_options' => isset( $settings['quickwebp_settings_completion_options'] ) && is_array( $settings['quickwebp_settings_completion_options'] ) ? array_map( 'sanitize_text_field', $settings['quickwebp_settings_completion_options'] ) : array(),
988 + 'quickwebp_settings_cleanup' => isset( $settings['quickwebp_settings_cleanup'] ) ? sanitize_text_field( (string) $settings['quickwebp_settings_cleanup'] ) : '',
989 + );
990 + }
991 +
992 + /**
113 993 * Check if the file is an image
114 - *
115 994 */
116 - public function file_is_image( $file, $settings ) {
995 + private function file_is_image( $file, $settings = array() ) {
117 996
118 997 $file_name = isset($file['name']) ? $file['name'] : '';
119 998 $file_type = isset($file['type']) ? $file['type'] : '';
120 999 $file_tmp_name = isset($file['tmp_name']) ? $file['tmp_name'] : '';
@@ -121,21 +1000,23 @@
121 1000 $file_error = isset($file['error']) ? $file['error'] : '';
122 1001 $file_size = isset($file['size']) ? $file['size'] : '';
123 1002
124 1003 if ( empty($file_tmp_name) ) {
1004 + quickwebp_debug_log( 'file_is_image:missing_tmp_name', array( 'file_name' => $file_name ) );
125 1005 return false;
126 1006 }
127 1007
128 - $allowed_mime_types = apply_filters( 'quickwebp_mime_types_allowed', array( 'image/jpeg', 'image/png', 'image/webp' ) );
1008 + $allowed_mime_types = apply_filters( 'quickwebp_mime_types_allowed', $this->allowed_mime_types );
129 1009 $is_image = wp_getimagesize($file_tmp_name);
130 1010 $mime_type = wp_get_image_mime($file_tmp_name);
131 1011 if ( ! $is_image || ! in_array( $mime_type, $allowed_mime_types ) ) {
1012 + quickwebp_debug_log( 'file_is_image:rejected', array( 'file_name' => $file_name, 'mime_type' => $mime_type ) );
132 1013 return false;
133 1014 }
134 1015
135 1016 $name = $file_name;
136 1017
137 - if ( $settings['quickwebp_settings_cleanup'] ) {
1018 + if ( isset($settings['quickwebp_settings_cleanup']) && $settings['quickwebp_settings_cleanup'] == '1' ) {
138 1019 $name = quickwebp_sanitize_name($file_name);
139 1020 }
140 1021
141 1022 return array(
@@ -143,74 +1024,432 @@
143 1024 'name' => $name,
144 1025 'type' => $file_type,
145 1026 'tmp_name' => $file_tmp_name,
146 1027 'error' => $file_error,
147 - 'size' => $file_size
1028 + 'size' => $file_size,
1029 + 'width' => $is_image[0],
1030 + 'height' => $is_image[1],
148 1031 );
149 1032 }
150 1033
151 1034 /**
152 - * Get the optimization settings
1035 + * Return ajax data
153 1036 */
154 - public function get_settings() {
1037 + private function return_ajax_data( $data ) {
1038 + $return = array(
1039 + 'original_name' => $data['original_name'],
1040 + 'size' => $data['size'],
1041 + 'type' => $this->type_from_mime_type( $data['type'] ),
1042 + 'mime_type' => $data['type'],
1043 + 'image' => 'data:'.$data['new_type'].';base64,' . base64_encode( file_get_contents( $data['tmp_name'] ) ),
1044 + 'name' => $data['name'],
1045 + 'new_size' => $data['new_size'],
1046 + 'new_type' => $this->type_from_mime_type( $data['new_type'] ),
1047 + 'new_mime_type' => $data['new_type']
1048 + );
155 1049
156 - return array(
157 - 'quickwebp_settings_conversion' => get_option('quickwebp_settings_conversion', quickwebp_settings_default('quickwebp_settings_conversion') ),
158 - 'quickwebp_settings_conversion_max_width' => get_option('quickwebp_settings_conversion_max_width', quickwebp_settings_default('quickwebp_settings_conversion_max_width') ),
159 - 'quickwebp_settings_conversion_max_height' => get_option('quickwebp_settings_conversion_max_height', quickwebp_settings_default('quickwebp_settings_conversion_max_height') ),
160 - 'quickwebp_settings_conversion_quality' => get_option('quickwebp_settings_conversion_quality', quickwebp_settings_default('quickwebp_settings_conversion_quality') ),
161 - 'quickwebp_settings_conversion_sharpen' => get_option('quickwebp_settings_conversion_sharpen', quickwebp_settings_default('quickwebp_settings_conversion_sharpen') ),
162 - 'quickwebp_settings_completion' => get_option('quickwebp_settings_completion', quickwebp_settings_default('quickwebp_settings_completion') ),
163 - 'quickwebp_settings_completion_options' => get_option('quickwebp_settings_completion_options', quickwebp_settings_default('quickwebp_settings_completion_options') ),
164 - 'quickwebp_settings_cleanup' => get_option('quickwebp_settings_cleanup', quickwebp_settings_default('quickwebp_settings_cleanup') )
1050 + wp_send_json_success( $return );
1051 + }
1052 +
1053 + /**
1054 + * Get the type from the mime type
1055 + */
1056 + private function type_from_mime_type( $mime_type ) {
1057 + $array = array(
1058 + 'image/jpeg' => 'JPEG',
1059 + 'image/png' => 'PNG',
1060 + 'image/webp' => 'WebP',
1061 + 'image/avif' => 'AVIF',
165 1062 );
1063 +
1064 + return $array[$mime_type] ?? '';
166 1065 }
167 1066
168 1067 /**
169 - * Add data to the attachment
1068 + * Get the quality
170 1069 */
171 - public function add_data_to_attachment( $metadata, $attachment_id, $context ) {
1070 + private function get_the_quality( $settings ) {
1071 + $mode_enabled = $settings['quickwebp_settings_conversion'];
1072 + $quality = $settings['quickwebp_settings_conversion_quality'];
172 1073
173 - $settings = $this->get_settings();
174 - if ( $settings['quickwebp_settings_completion'] != '1' ) {
175 - return $metadata;
1074 + return $this->get_the_quality_values( $quality, $mode_enabled );
1075 + }
1076 +
1077 + /**
1078 + * Get the quality values for avif and webp
1079 + */
1080 + private function get_the_quality_values( $quality, $mode_enabled ) {
1081 + $result = 50;
1082 +
1083 + switch ( $quality ) {
1084 + case 'low':
1085 + $result = 50;
1086 + if ( '2' === $mode_enabled ) {
1087 + $result = 30;
1088 + }
1089 + break;
1090 + case 'medium':
1091 + $result = 60;
1092 + if ( '2' === $mode_enabled ) {
1093 + $result = 40;
1094 + }
1095 + break;
1096 + case 'high':
1097 + $result = 75;
1098 + if ( '2' === $mode_enabled ) {
1099 + $result = 50;
1100 + }
1101 + break;
1102 + case 'extra_high':
1103 + $result = 90;
1104 + if ( '2' === $mode_enabled ) {
1105 + $result = 70;
1106 + }
1107 + break;
176 1108 }
177 1109
178 - if ( $context != 'create' ) {
179 - return $metadata;
1110 + return $result;
1111 + }
1112 +
1113 + /**
1114 + * Get the extension matching an image mime type.
1115 + *
1116 + * @param string $mime_type Image mime type.
1117 + *
1118 + * @return string
1119 + */
1120 + private function get_extension_from_mime_type( $mime_type ) {
1121 + $extensions = array(
1122 + 'image/avif' => 'avif',
1123 + 'image/webp' => 'webp',
1124 + 'image/jpeg' => 'jpg',
1125 + 'image/png' => 'png',
1126 + );
1127 +
1128 + return $extensions[ $mime_type ] ?? '';
1129 + }
1130 +
1131 + /**
1132 + * Create an image using the WordPress image editor abstraction.
1133 + *
1134 + * @param string $file_path Path to the source image.
1135 + * @param string $output_path Path where the converted image should be written.
1136 + * @param int $quality Compression quality.
1137 + * @param string $target_mime_type Output mime type.
1138 + *
1139 + * @return bool
1140 + */
1141 + private function create_image_with_wp_editor( $file_path, $output_path, $quality, $target_mime_type ) {
1142 + quickwebp_debug_log( 'create_image_with_wp_editor:start', array(
1143 + 'file_path' => $file_path,
1144 + 'output_path' => $output_path,
1145 + 'quality' => $quality,
1146 + 'target_mime_type' => $target_mime_type,
1147 + ) );
1148 +
1149 + $editor = wp_get_image_editor( $file_path );
1150 +
1151 + if ( is_wp_error( $editor ) ) {
1152 + quickwebp_debug_log( 'create_image_with_wp_editor:editor_error', array( 'message' => $editor->get_error_message() ) );
1153 + return false;
180 1154 }
181 1155
182 - if ( isset($_FILES['async-upload']['original_name']) ) {
1156 + $extension = $this->get_extension_from_mime_type( $target_mime_type );
1157 + if ( empty( $extension ) ) {
1158 + quickwebp_debug_log( 'create_image_with_wp_editor:missing_extension', array( 'target_mime_type' => $target_mime_type ) );
1159 + return false;
1160 + }
183 1161
184 - $original_name = sanitize_text_field( $_FILES['async-upload']['original_name'] );
185 - $original_name = pathinfo( $original_name, PATHINFO_FILENAME );
1162 + $save_path = $output_path;
1163 + if ( pathinfo( $output_path, PATHINFO_EXTENSION ) !== $extension ) {
1164 + $save_path = $output_path . '.' . $extension;
1165 + }
186 1166
187 - $post_arr = array(
188 - 'ID' => $attachment_id,
189 - 'meta_input' => array()
190 - );
1167 + $editor->set_quality( $quality );
1168 + $result = $editor->save( $save_path, $target_mime_type );
191 1169
192 - $completion_options = is_array($settings['quickwebp_settings_completion_options']) ? $settings['quickwebp_settings_completion_options'] : array();
1170 + if ( is_wp_error( $result ) || empty( $result['path'] ) || ! file_exists( $result['path'] ) ) {
1171 + quickwebp_debug_log( 'create_image_with_wp_editor:save_failed', array(
1172 + 'output_path' => $save_path,
1173 + 'error' => is_wp_error( $result ) ? $result->get_error_message() : 'missing_generated_file',
1174 + ) );
1175 + return false;
1176 + }
193 1177
194 - if ( in_array( 'title', $completion_options ) ) {
195 - $post_arr['post_title'] = $original_name;
1178 + $source_size = file_exists( $file_path ) ? filesize( $file_path ) : 0;
1179 + $generated_size = filesize( $result['path'] );
1180 +
1181 + if ( $source_size > 0 && $generated_size > $source_size ) {
1182 + quickwebp_debug_log( 'create_image_with_wp_editor:larger_than_source', array(
1183 + 'source_size' => $source_size,
1184 + 'generated_size' => $generated_size,
1185 + 'output_path' => $result['path'],
1186 + ) );
1187 + wp_delete_file( $result['path'] );
1188 + return false;
1189 + }
1190 +
1191 + if ( $result['path'] !== $output_path ) {
1192 + $file_contents = file_get_contents( $result['path'] );
1193 +
1194 + if ( false === $file_contents || false === file_put_contents( $output_path, $file_contents ) ) {
1195 + quickwebp_debug_log( 'create_image_with_wp_editor:copy_failed', array( 'output_path' => $output_path ) );
1196 + return false;
196 1197 }
197 -
198 - if ( in_array( 'caption', $completion_options ) ) {
199 - $post_arr['post_excerpt'] = $original_name;
1198 +
1199 + wp_delete_file( $result['path'] );
1200 + }
1201 +
1202 + quickwebp_debug_log( 'create_image_with_wp_editor:success', array(
1203 + 'output_path' => $output_path,
1204 + 'generated_size' => file_exists( $output_path ) ? filesize( $output_path ) : $generated_size,
1205 + 'target_mime_type'=> $target_mime_type,
1206 + ) );
1207 +
1208 + return true;
1209 + }
1210 +
1211 + /**
1212 + * Create a GD image resource when the required loader is available.
1213 + *
1214 + * @param string $file_path Path to the source image.
1215 + * @param string $mime_type Source mime type.
1216 + *
1217 + * @return GdImage|resource|false
1218 + */
1219 + private function create_gd_image_resource( $file_path, $mime_type ) {
1220 + $loaders = array(
1221 + 'image/avif' => 'imagecreatefromavif',
1222 + 'image/webp' => 'imagecreatefromwebp',
1223 + 'image/jpeg' => 'imagecreatefromjpeg',
1224 + 'image/png' => 'imagecreatefrompng',
1225 + );
1226 +
1227 + if ( empty( $loaders[ $mime_type ] ) ) {
1228 + return false;
1229 + }
1230 +
1231 + $loader = $loaders[ $mime_type ];
1232 +
1233 + if ( ! function_exists( $loader ) ) {
1234 + return false;
1235 + }
1236 +
1237 + return $loader( $file_path );
1238 + }
1239 +
1240 + /**
1241 + * Check whether the source mime type can be loaded through GD.
1242 + *
1243 + * @param string $mime_type Source mime type.
1244 + *
1245 + * @return bool
1246 + */
1247 + private function can_load_image_with_gd( $mime_type ) {
1248 + $loaders = array(
1249 + 'image/avif' => 'imagecreatefromavif',
1250 + 'image/webp' => 'imagecreatefromwebp',
1251 + 'image/jpeg' => 'imagecreatefromjpeg',
1252 + 'image/png' => 'imagecreatefrompng',
1253 + );
1254 +
1255 + if ( empty( $loaders[ $mime_type ] ) ) {
1256 + return false;
1257 + }
1258 +
1259 + return function_exists( $loaders[ $mime_type ] );
1260 + }
1261 +
1262 + /**
1263 + * Create the avif image
1264 + */
1265 + private function create_avif_image( $file_path, $output_path, $quality ) {
1266 + $image = false;
1267 + $mime_type = wp_get_image_mime( $file_path );
1268 +
1269 + quickwebp_debug_log( 'create_avif_image:start', array(
1270 + 'file_path' => $file_path,
1271 + 'output_path' => $output_path,
1272 + 'mime_type' => $mime_type,
1273 + 'quality' => $quality,
1274 + ) );
1275 +
1276 + if ( ! $this->can_load_image_with_gd( $mime_type ) && $this->create_image_with_wp_editor( $file_path, $output_path, $quality, 'image/avif' ) ) {
1277 + quickwebp_debug_log( 'create_avif_image:used_wp_editor', array( 'mime_type' => $mime_type ) );
1278 + return true;
1279 + }
1280 +
1281 + $image = $this->create_gd_image_resource( $file_path, $mime_type );
1282 +
1283 + if ( ! $image ) {
1284 + quickwebp_debug_log( 'create_avif_image:gd_load_failed', array( 'mime_type' => $mime_type ) );
1285 + return false;
1286 + }
1287 +
1288 + // Handle EXIF orientation for proper image rotation
1289 + $exif_data = @exif_read_data( $file_path );
1290 + if ( ! empty( $exif_data['Orientation'] ) ) {
1291 + $orientation = (int) $exif_data['Orientation'];
1292 + //phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
1293 + $orientation = apply_filters( 'wp_image_maybe_exif_rotate', $orientation, $file_path );
1294 + if ( $orientation && 1 !== $orientation ) {
1295 + switch ( $orientation ) {
1296 + case 2:
1297 + // Flip horizontally.
1298 + imageflip( $image, IMG_FLIP_HORIZONTAL );
1299 + break;
1300 + case 3:
1301 + // Rotate 180 degrees.
1302 + $image = imagerotate( $image, 180, 0 );
1303 + break;
1304 + case 4:
1305 + // Flip vertically.
1306 + imageflip( $image, IMG_FLIP_VERTICAL );
1307 + break;
1308 + case 5:
1309 + // Rotate 90 degrees counter-clockwise and flip vertically.
1310 + $image = imagerotate( $image, -90, 0 );
1311 + imageflip( $image, IMG_FLIP_VERTICAL );
1312 + break;
1313 + case 6:
1314 + // Rotate 90 degrees clockwise (270 counter-clockwise).
1315 + $image = imagerotate( $image, -90, 0 );
1316 + break;
1317 + case 7:
1318 + // Rotate 90 degrees counter-clockwise and flip horizontally.
1319 + $image = imagerotate( $image, 90, 0 );
1320 + imageflip( $image, IMG_FLIP_HORIZONTAL );
1321 + break;
1322 + case 8:
1323 + // Rotate 90 degrees counter-clockwise.
1324 + $image = imagerotate( $image, 90, 0 );
1325 + break;
1326 + }
200 1327 }
1328 + }
1329 +
1330 + if ( ! imageistruecolor( $image ) ) {
1331 + $truecolor = imagecreatetruecolor( imagesx( $image ), imagesy( $image ) );
1332 +
1333 + if ( $mime_type === 'image/png' ) {
1334 + imagealphablending( $truecolor, false );
1335 + imagesavealpha( $truecolor, true );
1336 + $transparent = imagecolorallocatealpha( $truecolor, 0, 0, 0, 127 );
1337 + imagefilledrectangle( $truecolor, 0, 0, imagesx( $image ), imagesy( $image ), $transparent );
1338 + }
1339 +
1340 + imagecopy( $truecolor, $image, 0, 0, 0, 0, imagesx( $image ), imagesy( $image ) );
1341 + $image = $truecolor;
1342 + }
201 1343
202 - if ( in_array( 'alt', $completion_options ) ) {
203 - $post_arr['meta_input']['_wp_attachment_image_alt'] = $original_name;
1344 + $avif_image = imageavif( $image, $output_path, $quality );
1345 + imagedestroy( $image );
1346 + if ( ! $avif_image ) {
1347 + quickwebp_debug_log( 'create_avif_image:gd_save_failed', array( 'output_path' => $output_path ) );
1348 + return false;
1349 + }
1350 +
1351 + quickwebp_debug_log( 'create_avif_image:gd_success', array(
1352 + 'output_path' => $output_path,
1353 + 'file_size' => file_exists( $output_path ) ? filesize( $output_path ) : 0,
1354 + ) );
1355 +
1356 + return $avif_image;
1357 + }
1358 +
1359 + /**
1360 + * Create the webp image
1361 + */
1362 + private function create_webp_image( $file_path, $output_path, $quality ) {
1363 + $image = false;
1364 + $mime_type = wp_get_image_mime( $file_path );
1365 +
1366 + quickwebp_debug_log( 'create_webp_image:start', array(
1367 + 'file_path' => $file_path,
1368 + 'output_path' => $output_path,
1369 + 'mime_type' => $mime_type,
1370 + 'quality' => $quality,
1371 + ) );
1372 +
1373 + if ( ! $this->can_load_image_with_gd( $mime_type ) && $this->create_image_with_wp_editor( $file_path, $output_path, $quality, 'image/webp' ) ) {
1374 + quickwebp_debug_log( 'create_webp_image:used_wp_editor', array( 'mime_type' => $mime_type ) );
1375 + return true;
1376 + }
1377 +
1378 + $image = $this->create_gd_image_resource( $file_path, $mime_type );
1379 +
1380 + if ( ! $image ) {
1381 + quickwebp_debug_log( 'create_webp_image:gd_load_failed', array( 'mime_type' => $mime_type ) );
1382 + return false;
1383 + }
1384 +
1385 + // Handle EXIF orientation for proper image rotation
1386 + $exif_data = @exif_read_data( $file_path );
1387 + if ( ! empty( $exif_data['Orientation'] ) ) {
1388 + $orientation = (int) $exif_data['Orientation'];
1389 + //phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
1390 + $orientation = apply_filters( 'wp_image_maybe_exif_rotate', $orientation, $file_path );
1391 + if ( $orientation && 1 !== $orientation ) {
1392 + switch ( $orientation ) {
1393 + case 2:
1394 + // Flip horizontally.
1395 + imageflip( $image, IMG_FLIP_HORIZONTAL );
1396 + break;
1397 + case 3:
1398 + // Rotate 180 degrees.
1399 + $image = imagerotate( $image, 180, 0 );
1400 + break;
1401 + case 4:
1402 + // Flip vertically.
1403 + imageflip( $image, IMG_FLIP_VERTICAL );
1404 + break;
1405 + case 5:
1406 + // Rotate 90 degrees counter-clockwise and flip vertically.
1407 + $image = imagerotate( $image, -90, 0 );
1408 + imageflip( $image, IMG_FLIP_VERTICAL );
1409 + break;
1410 + case 6:
1411 + // Rotate 90 degrees clockwise (270 counter-clockwise).
1412 + $image = imagerotate( $image, -90, 0 );
1413 + break;
1414 + case 7:
1415 + // Rotate 90 degrees counter-clockwise and flip horizontally.
1416 + $image = imagerotate( $image, 90, 0 );
1417 + imageflip( $image, IMG_FLIP_HORIZONTAL );
1418 + break;
1419 + case 8:
1420 + // Rotate 90 degrees counter-clockwise.
1421 + $image = imagerotate( $image, 90, 0 );
1422 + break;
1423 + }
204 1424 }
205 -
206 - if ( in_array( 'description', $completion_options ) ) {
207 - $post_arr['post_content'] = $original_name;
1425 + }
1426 +
1427 + if ( ! imageistruecolor( $image ) ) {
1428 + $truecolor = imagecreatetruecolor( imagesx( $image ), imagesy( $image ) );
1429 +
1430 + if ( $mime_type === 'image/png' ) {
1431 + imagealphablending( $truecolor, false );
1432 + imagesavealpha( $truecolor, true );
1433 + $transparent = imagecolorallocatealpha( $truecolor, 0, 0, 0, 127 );
1434 + imagefilledrectangle( $truecolor, 0, 0, imagesx( $image ), imagesy( $image ), $transparent );
208 1435 }
1436 +
1437 + imagecopy( $truecolor, $image, 0, 0, 0, 0, imagesx( $image ), imagesy( $image ) );
1438 + $image = $truecolor;
1439 + }
209 1440
210 - wp_update_post( $post_arr );
1441 + $webp_image = imagewebp( $image, $output_path, $quality );
1442 + imagedestroy( $image );
1443 + if ( ! $webp_image ) {
1444 + quickwebp_debug_log( 'create_webp_image:gd_save_failed', array( 'output_path' => $output_path ) );
1445 + return false;
211 1446 }
212 1447
213 - return $metadata;
1448 + quickwebp_debug_log( 'create_webp_image:gd_success', array(
1449 + 'output_path' => $output_path,
1450 + 'file_size' => file_exists( $output_path ) ? filesize( $output_path ) : 0,
1451 + ) );
1452 +
1453 + return $webp_image;
214 1454 }
215 -
216 -}
1455 +}