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