PluginProbe
QuickWebP – WebP & AVIF Image Optimizer, Compression & SEO for WordPress / 4.0.1
QuickWebP – WebP & AVIF Image Optimizer, Compression & SEO for WordPress v4.0.1
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
quickwebp / admin / class-image-optimizer.php

class-image-optimizer.php in QuickWebP – WebP & AVIF Image Optimizer, Compression & SEO for WordPress 4.0.1, at admin/class-image-optimizer.php

1,127 lines 35.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * The core functionality for image optimizing.
5 *
6 * @link http://webdeclic.com
7 * @since 1.0.0
8 *
9 * @package Quickwebp
10 * @subpackage Quickwebp/admin
11 */
12 class Quickwebp_Image_Optimizer {
13
14 /**
15 * The ID of this plugin.
16 *
17 * @since 1.0.0
18 * @access private
19 * @var string $plugin_name The ID of this plugin.
20 */
21 private $plugin_name;
22
23 /**
24 * The version of this plugin.
25 *
26 * @since 1.0.0
27 * @access private
28 * @var string $version The current version of this plugin.
29 */
30 private $version;
31
32 /**
33 * Allowed mime types for the optimazation
34 */
35 public $allowed_mime_types;
36
37 /**
38 * Initialize the class and set its properties.
39 *
40 * @since 1.0.0
41 * @param string $plugin_name The name of this plugin.
42 * @param string $version The version of this plugin.
43 */
44 public function __construct( $plugin_name, $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' );
49 }
50
51 /**
52 * Optimize an image added in wp_media
53 */
54 public function image_optimization( $file ) {
55 global $quickwebp_surecart_client;
56
57 $settings = $this->get_settings();
58 $image_file = $this->file_is_image( $file, $settings );
59
60 if ( ! $image_file ) {
61 return $file;
62 }
63
64 $mode_enabled = $settings['quickwebp_settings_conversion'];
65 if ( $mode_enabled === '0' ) {
66 return $image_file;
67 }
68
69 $save_original = is_array( $settings['quickwebp_settings_conversion_save_original'] ) ? $settings['quickwebp_settings_conversion_save_original'] : array();
70 if ( in_array( 'checked', $save_original ) ) {
71 return $image_file;
72 }
73
74 $ignore_same_format = is_array( $settings['quickwebp_settings_conversion_ignore_webp'] ) ? $settings['quickwebp_settings_conversion_ignore_webp'] : array();
75 $mime_type = wp_get_image_mime( $image_file['tmp_name'] );
76 if ( in_array( 'checked', $ignore_same_format ) ) {
77 if ( '1' == $mode_enabled && 'image/webp' == $mime_type ) {
78 return $image_file;
79 } elseif( '2' == $mode_enabled && 'image/avif' == $mime_type ) {
80 return $image_file;
81 }
82 }
83
84 $quality = $this->get_the_quality( $settings );
85 $is_pro = false;
86 if ( $quickwebp_surecart_client ) {
87 $is_pro = $quickwebp_surecart_client->license()->is_valid();
88 }
89
90 $image_file['new_size'] = $image_file['size'];
91 $image_file['new_type'] = $image_file['type'];
92
93 if ( '2' === $mode_enabled && $is_pro ) {
94 $avif_image = $this->create_avif_image( $image_file['tmp_name'], $image_file['tmp_name'], $quality );
95 if ( $avif_image ) {
96 $image_file['size'] = filesize( $image_file['tmp_name'] );
97 $image_file['type'] = 'image/avif';
98 $image_file['quickwebp'] = 'optimized';
99 }
100 }
101
102 if ( '1' === $mode_enabled ) {
103 $webp_image = $this->create_webp_image( $image_file['tmp_name'], $image_file['tmp_name'], $quality );
104 if ( $webp_image ) {
105 $image_file['size'] = filesize( $image_file['tmp_name'] );
106 $image_file['type'] = 'image/webp';
107 $image_file['quickwebp'] = 'optimized';
108 }
109 }
110
111 return $image_file;
112 }
113
114 /**
115 * Add post meta to the attachment that already optimized
116 */
117 public function add_already_optimized_meta( $metadata, $attachment_id, $context ) {
118 //phpcs:ignore WordPress.Security.NonceVerification.Missing
119 $quickwebp = sanitize_text_field( wp_unslash( $_FILES['async-upload']['quickwebp'] ?? '' ) );
120
121 if ( 'optimized' == $quickwebp ) {
122 //phpcs:ignore WordPress.Security.NonceVerification.Missing
123 $type = sanitize_text_field( wp_unslash( $_FILES['async-upload']['type'] ?? '' ) );
124
125 if ( 'image/webp' == $type ) {
126 update_post_meta( $attachment_id, 'quickwebp_already_optimized', '1' );
127 } elseif ( 'image/avif' == $type ) {
128 update_post_meta( $attachment_id, 'quickwebp_already_optimized', '2' );
129 }
130 }
131
132 return $metadata;
133 }
134
135 /**
136 * Save the original image in the attachment meta
137 */
138 public function save_original_image( $metadata, $attachment_id, $context ) {
139
140 if ( $context != 'create' ) {
141 return $metadata;
142 }
143
144 $settings = $this->get_settings();
145
146 $mode_enabled = $settings['quickwebp_settings_conversion'];
147 if ( $mode_enabled === '0' ) {
148 return $metadata;
149 }
150
151 $save_original = is_array( $settings['quickwebp_settings_conversion_save_original'] ) ? $settings['quickwebp_settings_conversion_save_original'] : array();
152 if ( ! in_array( 'checked', $save_original ) ) {
153 return $metadata;
154 }
155
156 $ignore_same_format = is_array( $settings['quickwebp_settings_conversion_ignore_webp'] ) ? $settings['quickwebp_settings_conversion_ignore_webp'] : array();
157 //phpcs:ignore WordPress.Security.NonceVerification.Missing
158 $mime_type = sanitize_text_field( wp_unslash( $_FILES['async-upload']['type'] ?? '' ) );
159 if ( in_array( 'checked', $ignore_same_format ) ) {
160 if ( '1' == $mode_enabled && 'image/webp' == $mime_type ) {
161 return $metadata;
162 } elseif( '2' == $mode_enabled && 'image/avif' == $mime_type ) {
163 return $metadata;
164 }
165 }
166
167 $sizes = $this->get_media_files( $attachment_id );
168 $new_sizes = array();
169
170 foreach ( $sizes as $key => $size ) {
171 $result = $this->optimize_local_file( $size );
172
173 if ( $result ) {
174 $new_sizes[$key] = $result;
175 }
176 }
177
178 if ( ! empty( $new_sizes ) ) {
179 if ( '1' == $mode_enabled ) {
180 update_post_meta( $attachment_id, 'quickwebp_already_optimized', '1' );
181 } elseif ( '2' == $mode_enabled ) {
182 update_post_meta( $attachment_id, 'quickwebp_already_optimized', '2' );
183 }
184
185 update_post_meta( $attachment_id, 'quickwebp_data', $new_sizes );
186 delete_post_meta( $attachment_id, 'quickwebp_has_error' );
187 } else {
188 update_post_meta( $attachment_id, 'quickwebp_has_error', '1' );
189 }
190
191 return $metadata;
192 }
193
194 /**
195 * Add data to the attachment
196 */
197 public function add_data_to_attachment( $metadata, $attachment_id, $context ) {
198
199 if ( $context != 'create' ) {
200 return $metadata;
201 }
202
203 $settings = $this->get_settings();
204 if ( $settings['quickwebp_settings_completion'] != '1' ) {
205 return $metadata;
206 }
207
208 //phpcs:ignore WordPress.Security.NonceVerification.Missing
209 $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'] ?? '' );
210 if ( ! empty( $original_name ) ) {
211 $original_name = pathinfo( $original_name, PATHINFO_FILENAME );
212 $post_arr = array();
213
214 $completion_options = is_array( $settings['quickwebp_settings_completion_options'] ) ? $settings['quickwebp_settings_completion_options'] : array();
215
216 if ( in_array( 'title', $completion_options ) ) {
217 $post_arr['post_title'] = $original_name;
218 }
219
220 if ( in_array( 'caption', $completion_options ) ) {
221 $post_arr['post_excerpt'] = $original_name;
222 }
223
224 if ( in_array( 'alt', $completion_options ) ) {
225 $post_arr['meta_input']['_wp_attachment_image_alt'] = $original_name;
226 }
227
228 if ( in_array( 'description', $completion_options ) ) {
229 $post_arr['post_content'] = $original_name;
230 }
231
232 if ( ! empty( $post_arr ) ) {
233 $post_arr['ID'] = $attachment_id;
234 wp_update_post( $post_arr );
235 }
236 }
237
238 return $metadata;
239 }
240
241 /**
242 * Change the default size of wp editor
243 */
244 public function change_wp_max_size( $max_size, $imagesize ) {
245
246 $settings = $this->get_settings();
247 $resize_active = $settings['quickwebp_settings_resize'];
248
249 if ( $resize_active == '1' ) {
250
251 $imagesize_width = isset($imagesize[0]) ? $imagesize[0] : 0;
252 $resize_value = $settings['quickwebp_settings_resize_value'];
253
254 if ( $imagesize_width > $resize_value ) {
255 $max_size = $resize_value;
256 }
257 }
258
259 return $max_size;
260 }
261
262 /**
263 * Change the default quality of wp editor
264 */
265 public function change_wp_quality( $default_quality ) {
266
267 $settings = $this->get_settings();
268 $mode_enabled = $settings['quickwebp_settings_conversion'];
269 if ( $mode_enabled === '0' ) {
270 return $default_quality;
271 }
272
273 return 100;
274 }
275
276 /**
277 * Optimize image through ajax
278 */
279 public function image_optimization_ajax() {
280 global $quickwebp_surecart_client;
281
282 if ( ! current_user_can( 'upload_files' ) ) {
283 wp_send_json_error( __( 'You are not allowed to upload files.', 'quickwebp' ), 403 );
284 }
285
286 // verify the nonce.
287 $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : '';
288 if( !wp_verify_nonce( $nonce, 'image_optimize_nonce' ) ) {
289 wp_send_json_error( __( 'Refresh the page and try again.', 'quickwebp' ) );
290 }
291
292 // Get the file
293 $file = count($_FILES) > 0 ? array_shift($_FILES) : array();
294 if ( empty($file) ) {
295 wp_send_json_error( __( 'No image uploaded, try again.', 'quickwebp' ) );
296 }
297
298 $settings = $this->get_ajax_settings();
299 $image_file = $this->file_is_image( $file, $settings );
300
301 if ( ! $image_file ) {
302 wp_send_json_error( __( 'No image uploaded, try again.', 'quickwebp' ) );
303 }
304
305 $mode_enabled = $settings['quickwebp_settings_conversion'];
306 if ( $mode_enabled === '0' ) {
307 $image_file['new_size'] = $image_file['size'];
308 $image_file['new_type'] = $image_file['type'];
309 $this->return_ajax_data( $image_file );
310 }
311
312 $quality = $this->get_the_quality( $settings );
313 $is_pro = false;
314 if ( $quickwebp_surecart_client ) {
315 $is_pro = $quickwebp_surecart_client->license()->is_valid();
316 }
317
318 $image_file['new_size'] = $image_file['size'];
319 $image_file['new_type'] = $image_file['type'];
320
321 if ( '2' === $mode_enabled && $is_pro ) {
322 $avif_image = $this->create_avif_image( $image_file['tmp_name'], $image_file['tmp_name'], $quality );
323 if ( $avif_image ) {
324 $image_file['new_size'] = filesize( $image_file['tmp_name'] );
325 $image_file['new_type'] = 'image/avif';
326 }
327 }
328
329 if ( '1' === $mode_enabled ) {
330 $webp_image = $this->create_webp_image( $image_file['tmp_name'], $image_file['tmp_name'], $quality );
331 if ( $webp_image ) {
332 $image_file['new_size'] = filesize( $image_file['tmp_name'] );
333 $image_file['new_type'] = 'image/webp';
334 }
335 }
336
337 $this->return_ajax_data( $image_file );
338 }
339
340 /**
341 * Optimize a single media
342 */
343 public function single_optimization_ajax() {
344
345 // verify the nonce.
346 $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : '';
347 if( !wp_verify_nonce( $nonce, 'quickwebp_admin_attachment' ) ) {
348 wp_send_json_error( __( 'Refresh the page and try again.', 'quickwebp' ) );
349 }
350
351 // Sanitize data
352 $attachment_id = isset( $_POST['attachment_id'] ) ? absint( wp_unslash( $_POST['attachment_id'] ) ) : 0;
353
354 if ( ! $attachment_id ) {
355 wp_send_json_error( __( 'No attachment id.', 'quickwebp' ) );
356 }
357
358 $settings = $this->get_settings();
359 $mode_enabled = $settings['quickwebp_settings_conversion'];
360 if ( '0' === $mode_enabled ) {
361 wp_send_json_error( __( 'Choose an image format and save the settings.', 'quickwebp' ) );
362 }
363
364 $already_optimized = get_post_meta( $attachment_id, 'quickwebp_already_optimized', true );
365
366 if ( '1' == $already_optimized && '1' == $mode_enabled ) {
367 wp_send_json_error( __( 'Already optimized.', 'quickwebp' ) );
368 }
369
370 if ( '2' == $already_optimized && '2' == $mode_enabled ) {
371 wp_send_json_error( __( 'Already optimized.', 'quickwebp' ) );
372 }
373
374 $post_mime_type = get_post_mime_type( $attachment_id );
375 if ( ! in_array( $post_mime_type, $this->allowed_mime_types ) ) {
376 wp_send_json_error( __( 'Not a valid image.', 'quickwebp' ) );
377 }
378
379 $sizes = $this->get_media_files( $attachment_id );
380 $new_sizes = array();
381
382 foreach ( $sizes as $key => $size ) {
383 $result = $this->optimize_local_file( $size );
384
385 if ( $result ) {
386 $new_sizes[$key] = $result;
387 }
388 }
389
390 if ( ! empty( $new_sizes ) ) {
391
392 $data = get_post_meta( $attachment_id, 'quickwebp_data', true );
393 if ( ! empty( $data ) ) {
394 $this->remove_related_files( $data );
395 }
396
397 if ( '1' == $mode_enabled ) {
398 update_post_meta( $attachment_id, 'quickwebp_already_optimized', '1' );
399 } elseif ( '2' == $mode_enabled ) {
400 update_post_meta( $attachment_id, 'quickwebp_already_optimized', '2' );
401 }
402
403 update_post_meta( $attachment_id, 'quickwebp_data', $new_sizes );
404 delete_post_meta( $attachment_id, 'quickwebp_has_error' );
405 } else {
406 update_post_meta( $attachment_id, 'quickwebp_has_error', '1' );
407 delete_post_meta( $attachment_id, 'quickwebp_already_optimized' );
408 delete_post_meta( $attachment_id, 'quickwebp_data' );
409 }
410
411 $wp_media_extend = new Quickwebp_Wp_Media_Extends( $this->plugin_name, $this->version );
412 $html = $wp_media_extend->attachment_data( $new_sizes, $attachment_id );
413
414 wp_send_json_success( $html );
415 }
416
417 /**
418 * Undo a single media optimization
419 */
420 public function undo_single_optimization_ajax() {
421
422 // verify the nonce.
423 $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : '';
424 if( !wp_verify_nonce( $nonce, 'quickwebp_admin_attachment' ) ) {
425 wp_send_json_error( __( 'Refresh the page and try again.', 'quickwebp' ) );
426 }
427
428 // Sanitize data
429 $attachment_id = isset( $_POST['attachment_id'] ) ? absint( wp_unslash( $_POST['attachment_id'] ) ) : 0;
430
431 if ( ! $attachment_id ) {
432 wp_send_json_error( __( 'No attachment id.', 'quickwebp' ) );
433 }
434
435 $data = get_post_meta( $attachment_id, 'quickwebp_data', true );
436 if ( ! empty( $data ) ) {
437 $this->remove_related_files( $data );
438 delete_post_meta( $attachment_id, 'quickwebp_already_optimized' );
439 delete_post_meta( $attachment_id, 'quickwebp_data' );
440
441 $wp_media_extend = new Quickwebp_Wp_Media_Extends( $this->plugin_name, $this->version );
442 $html = $wp_media_extend->optimize_btn( $attachment_id );
443 wp_send_json_success( $html );
444 } else {
445 wp_send_json_error( __( 'Not optimized.', 'quickwebp' ) );
446 }
447 }
448
449 /**
450 * Trigger before delete attachment
451 */
452 public function before_delete_attachment( $post_id ) {
453 $data = get_post_meta( $post_id, 'quickwebp_data', true );
454 if ( ! empty( $data ) ) {
455 $this->remove_related_files( $data );
456 }
457 }
458
459 /**
460 * Get the image data for the preview in the settings page
461 */
462 public function get_image_data_for_preview_ajax() {
463
464 // verify the nonce.
465 $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : '';
466 if( !wp_verify_nonce( $nonce, 'image_optimize_nonce' ) ) {
467 wp_send_json_error( __( 'Refresh the page and try again.', 'quickwebp' ) );
468 }
469
470 // Get the file
471 $file = count($_FILES) > 0 ? array_shift($_FILES) : array();
472 if ( empty($file) ) {
473 wp_send_json_error( __( 'No image uploaded, try again.', 'quickwebp' ) );
474 }
475
476 $image_file = $this->file_is_image( $file );
477 if ( ! $image_file ) {
478 wp_send_json_error( __( 'No image uploaded, try again.', 'quickwebp' ) );
479 }
480
481 $preview_image_data = array(
482 'name' => $image_file['name'],
483 'size' => size_format( $image_file['size'], 2 ),
484 'dimensions' => $image_file['width'] . ' x ' . $image_file['height'],
485 );
486
487 $conversions = array( '1', '2' );
488 $qualities = array( 'low', 'medium', 'high', 'extra_high' );
489
490 foreach ( $conversions as $conversion ) {
491 foreach ( $qualities as $quality ) {
492
493 $size_after = 0;
494
495 if ( '2' == $conversion ) {
496 $new_path = $image_file['tmp_name'] . '-' . $quality . '.avif';
497 $quality_value = $this->get_the_quality_values( $quality, $conversion );
498 $avif_image = $this->create_avif_image( $image_file['tmp_name'], $new_path, $quality_value );
499
500 if ( $avif_image ) {
501 $size_after = filesize( $new_path );
502 }
503 } elseif ( '1' == $conversion ) {
504 $new_path = $image_file['tmp_name'] . '-' . $quality . '.webp';
505 $quality_value = $this->get_the_quality_values( $quality, $conversion );
506 $webp_image = $this->create_webp_image( $image_file['tmp_name'], $new_path, $quality_value );
507
508 if ( $webp_image ) {
509 $size_after = filesize( $new_path );
510 }
511 }
512
513 $deference = $image_file['size'] - $size_after;
514 $percent = $deference / $image_file['size'] * 100;
515
516 $preview_image_data[$conversion . '_' . $quality] = array(
517 'size' => size_format( $size_after, 2 ),
518 'save' => size_format( $deference, 2 ),
519 'save_percent' => round( $percent ) . '%',
520 'percent' => round( 100 - $percent ) . '%',
521 );
522 }
523 }
524
525 wp_send_json_success( $preview_image_data );
526 }
527
528 /**
529 * Get the unoptimized media ids
530 */
531 public function get_unoptimized_media_ids() {
532
533 $settings = $this->get_settings();
534 $mode_enabled = $settings['quickwebp_settings_conversion'];
535 $ignore_same_format = is_array( $settings['quickwebp_settings_conversion_ignore_webp'] ) ? $settings['quickwebp_settings_conversion_ignore_webp'] : array();
536
537 $statuses = array(
538 'inherit' => 'inherit',
539 'private' => 'private',
540 );
541 $custom_statuses = get_post_stati( array( 'public' => true ) );
542 unset( $custom_statuses['publish'] );
543 if ( $custom_statuses ) {
544 $statuses = array_merge( $statuses, $custom_statuses );
545 }
546
547 $allowed_mime_types = $this->allowed_mime_types;
548 if ( in_array( 'checked', $ignore_same_format ) ) {
549 if ( '1' == $mode_enabled ) {
550 $webp_index = array_search( 'image/webp', $allowed_mime_types );
551 unset( $allowed_mime_types[$webp_index] );
552 } elseif ( '2' == $mode_enabled ) {
553 $avif_index = array_search( 'image/avif', $allowed_mime_types );
554 unset( $allowed_mime_types[$avif_index] );
555 }
556 }
557
558 $meta_query_already_optimized = array(
559 'relation' => 'OR',
560 array(
561 'key' => 'quickwebp_already_optimized',
562 'compare' => 'NOT EXISTS'
563 ),
564 );
565
566 if ( 'webp' == $mode_enabled ) {
567 $meta_query_already_optimized[] = array(
568 'key' => 'quickwebp_already_optimized',
569 'compare' => '!=',
570 'value' => '1',
571 );
572 } elseif ( 'avif' == $mode_enabled ) {
573 $meta_query_already_optimized[] = array(
574 'key' => 'quickwebp_already_optimized',
575 'compare' => '!=',
576 'value' => '2',
577 );
578 }
579
580 $media_ids = get_posts( array(
581 'post_type' => 'attachment',
582 'post_mime_type' => $allowed_mime_types,
583 'post_status' => array_keys( $statuses ),
584 'posts_per_page' => -1,
585 'fields' => 'ids',
586 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
587 'meta_query' => array(
588 'relation' => 'AND',
589 array(
590 'key' => 'quickwebp_has_error',
591 'compare' => 'NOT EXISTS'
592 ),
593 $meta_query_already_optimized,
594 ),
595 ) );
596
597 return $media_ids;
598 }
599
600 /**
601 * Get the list of media files
602 */
603 public function get_media_files( $media_id ) {
604 $fullsize_path = get_attached_file( $media_id );
605
606 if ( ! $fullsize_path ) {
607 return array();
608 }
609
610 $media_data = wp_get_attachment_image_src( $media_id, 'full' );
611 $file_type = wp_check_filetype( $fullsize_path );
612
613 $all_sizes = [
614 'full' => [
615 'size' => 'full',
616 'path' => $fullsize_path,
617 'width' => $media_data[1],
618 'height' => $media_data[2],
619 'mime-type' => $file_type['type'],
620 'disabled' => false,
621 ],
622 ];
623
624 $sizes = wp_get_attachment_metadata( $media_id, true );
625 $sizes = ! empty( $sizes['sizes'] ) && is_array( $sizes['sizes'] ) ? $sizes['sizes'] : [];
626
627 $dir_path = trailingslashit( dirname( $fullsize_path ) );
628
629 foreach ( $sizes as $size => $size_data ) {
630 $all_sizes[ $size ] = [
631 'size' => $size,
632 'path' => $dir_path . $size_data['file'],
633 'width' => $size_data['width'],
634 'height' => $size_data['height'],
635 'mime-type' => $size_data['mime-type'],
636 'disabled' => false
637 ];
638 }
639
640 return $all_sizes;
641 }
642
643 /**
644 * Optimize a local file
645 */
646 public function optimize_local_file( $size ) {
647 global $quickwebp_surecart_client;
648
649 if ( !is_file($size['path']) ) {
650 return false;
651 }
652
653 $real_type = mime_content_type( $size['path']);
654 if ( !in_array( $real_type, $this->allowed_mime_types ) ) {
655 return false;
656 }
657
658 $settings = $this->get_settings();
659 $quality = $this->get_the_quality( $settings );
660 $is_pro = false;
661 if ( $quickwebp_surecart_client ) {
662 $is_pro = $quickwebp_surecart_client->license()->is_valid();
663 }
664 $mode_enabled = $settings['quickwebp_settings_conversion'];
665 $size_before = filesize( $size['path'] );
666 $new_path = $size['path'] . '.' . ( '2' === $mode_enabled && $is_pro ? 'avif' : 'webp' );
667 $size_after = 0;
668
669 if ( '2' === $mode_enabled && $is_pro ) {
670 $avif_image = $this->create_avif_image( $size['path'], $new_path, $quality );
671 if ( $avif_image ) {
672 $size_after = filesize( $new_path );
673 }
674 } elseif ( '1' === $mode_enabled ) {
675 $webp_image = $this->create_webp_image( $size['path'], $new_path, $quality );
676 if ( $webp_image ) {
677 $size_after = filesize( $new_path );
678 }
679 }
680
681 if ( ! $size_after ) {
682 return false;
683 }
684
685 $deference = $size_before - $size_after;
686 $percent = $deference / $size_before * 100;
687
688 return array(
689 'success' => 1,
690 'original_size' => $size_before,
691 'optimized_size' => $size_after,
692 'percent' => round( $percent, 2 ),
693 'path' => $new_path,
694 'format' => $mode_enabled,
695 );
696 }
697
698 /**
699 * Remove the related files of an optimized attachment
700 */
701 public function remove_related_files( $data ) {
702 foreach ( $data as $value ) {
703 $path = $value['path'] ?? '';
704
705 if ( ! empty( $path ) && file_exists( $path ) ) {
706 wp_delete_file( $path );
707 }
708 }
709 }
710
711 /**
712 * Get the optimization settings
713 */
714 public function get_settings() {
715 return array(
716 'quickwebp_settings_conversion' => get_option('quickwebp_settings_conversion', quickwebp_settings_default('quickwebp_settings_conversion') ),
717 'quickwebp_settings_conversion_quality' => get_option('quickwebp_settings_conversion_quality', quickwebp_settings_default('quickwebp_settings_conversion_quality') ),
718 'quickwebp_settings_conversion_ignore_webp' => get_option('quickwebp_settings_conversion_ignore_webp', quickwebp_settings_default('quickwebp_settings_conversion_ignore_webp') ),
719 'quickwebp_settings_conversion_save_original' => get_option('quickwebp_settings_conversion_save_original', quickwebp_settings_default('quickwebp_settings_conversion_save_original') ),
720 'quickwebp_settings_resize' => get_option('quickwebp_settings_resize', quickwebp_settings_default('quickwebp_settings_resize') ),
721 'quickwebp_settings_resize_value' => get_option('quickwebp_settings_resize_value', quickwebp_settings_default('quickwebp_settings_resize_value') ),
722 'quickwebp_settings_completion' => get_option('quickwebp_settings_completion', quickwebp_settings_default('quickwebp_settings_completion') ),
723 'quickwebp_settings_completion_options' => get_option('quickwebp_settings_completion_options', quickwebp_settings_default('quickwebp_settings_completion_options') ),
724 'quickwebp_settings_cleanup' => get_option('quickwebp_settings_cleanup', quickwebp_settings_default('quickwebp_settings_cleanup') ),
725 );
726 }
727
728 /**
729 * Get the optimization settings
730 */
731 private function get_ajax_settings() {
732 //phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
733 $raw_settings = isset( $_POST['settings'] ) ? wp_unslash( $_POST['settings'] ) : '';
734 $settings = is_string( $raw_settings ) ? json_decode( $raw_settings, true ) : array();
735
736 if ( ! is_array( $settings ) ) {
737 $settings = array();
738 }
739
740 return array(
741 'quickwebp_settings_conversion' => isset( $settings['quickwebp_settings_conversion'] ) ? sanitize_text_field( (string) $settings['quickwebp_settings_conversion'] ) : '0',
742 'quickwebp_settings_conversion_quality' => isset( $settings['quickwebp_settings_conversion_quality'] ) ? sanitize_text_field( $settings['quickwebp_settings_conversion_quality'] ) : 'high',
743 '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(),
744 'quickwebp_settings_resize' => isset( $settings['quickwebp_settings_resize'] ) ? sanitize_text_field( (string) $settings['quickwebp_settings_resize'] ) : '',
745 'quickwebp_settings_resize_value' => isset( $settings['quickwebp_settings_resize_value'] ) ? absint( $settings['quickwebp_settings_resize_value'] ) : 0,
746 'quickwebp_settings_completion' => isset( $settings['quickwebp_settings_completion'] ) ? sanitize_text_field( (string) $settings['quickwebp_settings_completion'] ) : '',
747 '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(),
748 'quickwebp_settings_cleanup' => isset( $settings['quickwebp_settings_cleanup'] ) ? sanitize_text_field( (string) $settings['quickwebp_settings_cleanup'] ) : '',
749 );
750 }
751
752 /**
753 * Check if the file is an image
754 */
755 private function file_is_image( $file, $settings = array() ) {
756
757 $file_name = isset($file['name']) ? $file['name'] : '';
758 $file_type = isset($file['type']) ? $file['type'] : '';
759 $file_tmp_name = isset($file['tmp_name']) ? $file['tmp_name'] : '';
760 $file_error = isset($file['error']) ? $file['error'] : '';
761 $file_size = isset($file['size']) ? $file['size'] : '';
762
763 if ( empty($file_tmp_name) ) {
764 return false;
765 }
766
767 $allowed_mime_types = apply_filters( 'quickwebp_mime_types_allowed', $this->allowed_mime_types );
768 $is_image = wp_getimagesize($file_tmp_name);
769 $mime_type = wp_get_image_mime($file_tmp_name);
770 if ( ! $is_image || ! in_array( $mime_type, $allowed_mime_types ) ) {
771 return false;
772 }
773
774 $name = $file_name;
775
776 if ( isset($settings['quickwebp_settings_cleanup']) && $settings['quickwebp_settings_cleanup'] == '1' ) {
777 $name = quickwebp_sanitize_name($file_name);
778 }
779
780 return array(
781 'original_name' => $file_name,
782 'name' => $name,
783 'type' => $file_type,
784 'tmp_name' => $file_tmp_name,
785 'error' => $file_error,
786 'size' => $file_size,
787 'width' => $is_image[0],
788 'height' => $is_image[1],
789 );
790 }
791
792 /**
793 * Return ajax data
794 */
795 private function return_ajax_data( $data ) {
796 $return = array(
797 'original_name' => $data['original_name'],
798 'size' => $data['size'],
799 'type' => $this->type_from_mime_type( $data['type'] ),
800 'mime_type' => $data['type'],
801 'image' => 'data:'.$data['new_type'].';base64,' . base64_encode( file_get_contents( $data['tmp_name'] ) ),
802 'name' => $data['name'],
803 'new_size' => $data['new_size'],
804 'new_type' => $this->type_from_mime_type( $data['new_type'] ),
805 'new_mime_type' => $data['new_type']
806 );
807
808 wp_send_json_success( $return );
809 }
810
811 /**
812 * Get the type from the mime type
813 */
814 private function type_from_mime_type( $mime_type ) {
815 $array = array(
816 'image/jpeg' => 'JPEG',
817 'image/png' => 'PNG',
818 'image/webp' => 'WebP',
819 'image/avif' => 'AVIF',
820 );
821
822 return $array[$mime_type] ?? '';
823 }
824
825 /**
826 * Get the quality
827 */
828 private function get_the_quality( $settings ) {
829 $mode_enabled = $settings['quickwebp_settings_conversion'];
830 $quality = $settings['quickwebp_settings_conversion_quality'];
831
832 return $this->get_the_quality_values( $quality, $mode_enabled );
833 }
834
835 /**
836 * Get the quality values for avif and webp
837 */
838 private function get_the_quality_values( $quality, $mode_enabled ) {
839 $result = 50;
840
841 switch ( $quality ) {
842 case 'low':
843 $result = 50;
844 if ( '2' === $mode_enabled ) {
845 $result = 30;
846 }
847 break;
848 case 'medium':
849 $result = 60;
850 if ( '2' === $mode_enabled ) {
851 $result = 40;
852 }
853 break;
854 case 'high':
855 $result = 75;
856 if ( '2' === $mode_enabled ) {
857 $result = 50;
858 }
859 break;
860 case 'extra_high':
861 $result = 90;
862 if ( '2' === $mode_enabled ) {
863 $result = 70;
864 }
865 break;
866 }
867
868 return $result;
869 }
870
871 /**
872 * Get the extension matching an image mime type.
873 *
874 * @param string $mime_type Image mime type.
875 *
876 * @return string
877 */
878 private function get_extension_from_mime_type( $mime_type ) {
879 $extensions = array(
880 'image/avif' => 'avif',
881 'image/webp' => 'webp',
882 'image/jpeg' => 'jpg',
883 'image/png' => 'png',
884 );
885
886 return $extensions[ $mime_type ] ?? '';
887 }
888
889 /**
890 * Create an image using the WordPress image editor abstraction.
891 *
892 * @param string $file_path Path to the source image.
893 * @param string $output_path Path where the converted image should be written.
894 * @param int $quality Compression quality.
895 * @param string $target_mime_type Output mime type.
896 *
897 * @return bool
898 */
899 private function create_image_with_wp_editor( $file_path, $output_path, $quality, $target_mime_type ) {
900 $editor = wp_get_image_editor( $file_path );
901
902 if ( is_wp_error( $editor ) ) {
903 return false;
904 }
905
906 $extension = $this->get_extension_from_mime_type( $target_mime_type );
907 if ( empty( $extension ) ) {
908 return false;
909 }
910
911 $save_path = $output_path;
912 if ( pathinfo( $output_path, PATHINFO_EXTENSION ) !== $extension ) {
913 $save_path = $output_path . '.' . $extension;
914 }
915
916 $editor->set_quality( $quality );
917 $result = $editor->save( $save_path, $target_mime_type );
918
919 if ( is_wp_error( $result ) || empty( $result['path'] ) || ! file_exists( $result['path'] ) ) {
920 return false;
921 }
922
923 if ( $result['path'] !== $output_path ) {
924 $file_contents = file_get_contents( $result['path'] );
925
926 if ( false === $file_contents || false === file_put_contents( $output_path, $file_contents ) ) {
927 return false;
928 }
929
930 wp_delete_file( $result['path'] );
931 }
932
933 return true;
934 }
935
936 /**
937 * Create a GD image resource when the required loader is available.
938 *
939 * @param string $file_path Path to the source image.
940 * @param string $mime_type Source mime type.
941 *
942 * @return GdImage|resource|false
943 */
944 private function create_gd_image_resource( $file_path, $mime_type ) {
945 $loaders = array(
946 'image/avif' => 'imagecreatefromavif',
947 'image/webp' => 'imagecreatefromwebp',
948 'image/jpeg' => 'imagecreatefromjpeg',
949 'image/png' => 'imagecreatefrompng',
950 );
951
952 if ( empty( $loaders[ $mime_type ] ) ) {
953 return false;
954 }
955
956 $loader = $loaders[ $mime_type ];
957
958 if ( ! function_exists( $loader ) ) {
959 return false;
960 }
961
962 return $loader( $file_path );
963 }
964
965 /**
966 * Create the avif image
967 */
968 private function create_avif_image( $file_path, $output_path, $quality ) {
969 if ( $this->create_image_with_wp_editor( $file_path, $output_path, $quality, 'image/avif' ) ) {
970 return true;
971 }
972
973 $image = false;
974 $mime_type = wp_get_image_mime( $file_path );
975 $image = $this->create_gd_image_resource( $file_path, $mime_type );
976
977 if ( ! $image ) {
978 return false;
979 }
980
981 // Handle EXIF orientation for proper image rotation
982 $exif_data = @exif_read_data( $file_path );
983 if ( ! empty( $exif_data['Orientation'] ) ) {
984 $orientation = (int) $exif_data['Orientation'];
985 //phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
986 $orientation = apply_filters( 'wp_image_maybe_exif_rotate', $orientation, $file_path );
987 if ( $orientation && 1 !== $orientation ) {
988 switch ( $orientation ) {
989 case 2:
990 // Flip horizontally.
991 imageflip( $image, IMG_FLIP_HORIZONTAL );
992 break;
993 case 3:
994 // Rotate 180 degrees.
995 $image = imagerotate( $image, 180, 0 );
996 break;
997 case 4:
998 // Flip vertically.
999 imageflip( $image, IMG_FLIP_VERTICAL );
1000 break;
1001 case 5:
1002 // Rotate 90 degrees counter-clockwise and flip vertically.
1003 $image = imagerotate( $image, -90, 0 );
1004 imageflip( $image, IMG_FLIP_VERTICAL );
1005 break;
1006 case 6:
1007 // Rotate 90 degrees clockwise (270 counter-clockwise).
1008 $image = imagerotate( $image, -90, 0 );
1009 break;
1010 case 7:
1011 // Rotate 90 degrees counter-clockwise and flip horizontally.
1012 $image = imagerotate( $image, 90, 0 );
1013 imageflip( $image, IMG_FLIP_HORIZONTAL );
1014 break;
1015 case 8:
1016 // Rotate 90 degrees counter-clockwise.
1017 $image = imagerotate( $image, 90, 0 );
1018 break;
1019 }
1020 }
1021 }
1022
1023 if ( ! imageistruecolor( $image ) ) {
1024 $truecolor = imagecreatetruecolor( imagesx( $image ), imagesy( $image ) );
1025
1026 if ( $mime_type === 'image/png' ) {
1027 imagealphablending( $truecolor, false );
1028 imagesavealpha( $truecolor, true );
1029 $transparent = imagecolorallocatealpha( $truecolor, 0, 0, 0, 127 );
1030 imagefilledrectangle( $truecolor, 0, 0, imagesx( $image ), imagesy( $image ), $transparent );
1031 }
1032
1033 imagecopy( $truecolor, $image, 0, 0, 0, 0, imagesx( $image ), imagesy( $image ) );
1034 $image = $truecolor;
1035 }
1036
1037 $avif_image = imageavif( $image, $output_path, $quality );
1038 imagedestroy( $image );
1039 if ( ! $avif_image ) {
1040 return false;
1041 }
1042
1043 return $avif_image;
1044 }
1045
1046 /**
1047 * Create the webp image
1048 */
1049 private function create_webp_image( $file_path, $output_path, $quality ) {
1050 if ( $this->create_image_with_wp_editor( $file_path, $output_path, $quality, 'image/webp' ) ) {
1051 return true;
1052 }
1053
1054 $image = false;
1055 $mime_type = wp_get_image_mime( $file_path );
1056 $image = $this->create_gd_image_resource( $file_path, $mime_type );
1057
1058 if ( ! $image ) {
1059 return false;
1060 }
1061
1062 // Handle EXIF orientation for proper image rotation
1063 $exif_data = @exif_read_data( $file_path );
1064 if ( ! empty( $exif_data['Orientation'] ) ) {
1065 $orientation = (int) $exif_data['Orientation'];
1066 //phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
1067 $orientation = apply_filters( 'wp_image_maybe_exif_rotate', $orientation, $file_path );
1068 if ( $orientation && 1 !== $orientation ) {
1069 switch ( $orientation ) {
1070 case 2:
1071 // Flip horizontally.
1072 imageflip( $image, IMG_FLIP_HORIZONTAL );
1073 break;
1074 case 3:
1075 // Rotate 180 degrees.
1076 $image = imagerotate( $image, 180, 0 );
1077 break;
1078 case 4:
1079 // Flip vertically.
1080 imageflip( $image, IMG_FLIP_VERTICAL );
1081 break;
1082 case 5:
1083 // Rotate 90 degrees counter-clockwise and flip vertically.
1084 $image = imagerotate( $image, -90, 0 );
1085 imageflip( $image, IMG_FLIP_VERTICAL );
1086 break;
1087 case 6:
1088 // Rotate 90 degrees clockwise (270 counter-clockwise).
1089 $image = imagerotate( $image, -90, 0 );
1090 break;
1091 case 7:
1092 // Rotate 90 degrees counter-clockwise and flip horizontally.
1093 $image = imagerotate( $image, 90, 0 );
1094 imageflip( $image, IMG_FLIP_HORIZONTAL );
1095 break;
1096 case 8:
1097 // Rotate 90 degrees counter-clockwise.
1098 $image = imagerotate( $image, 90, 0 );
1099 break;
1100 }
1101 }
1102 }
1103
1104 if ( ! imageistruecolor( $image ) ) {
1105 $truecolor = imagecreatetruecolor( imagesx( $image ), imagesy( $image ) );
1106
1107 if ( $mime_type === 'image/png' ) {
1108 imagealphablending( $truecolor, false );
1109 imagesavealpha( $truecolor, true );
1110 $transparent = imagecolorallocatealpha( $truecolor, 0, 0, 0, 127 );
1111 imagefilledrectangle( $truecolor, 0, 0, imagesx( $image ), imagesy( $image ), $transparent );
1112 }
1113
1114 imagecopy( $truecolor, $image, 0, 0, 0, 0, imagesx( $image ), imagesy( $image ) );
1115 $image = $truecolor;
1116 }
1117
1118 $webp_image = imagewebp( $image, $output_path, $quality );
1119 imagedestroy( $image );
1120 if ( ! $webp_image ) {
1121 return false;
1122 }
1123
1124 return $webp_image;
1125 }
1126 }
1127