PluginProbe
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF / 1.7
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF v1.7
2.3.4 2.3.3 2.3.2 2.3.1 2.3.0 2.2.9 2.2.8 trunk 1.10 1.3.3 1.3.4 1.3.5 1.3.5.1 1.3.5.2 1.3.6 1.3.6.1 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.5 All 103 releases
imagify / inc / classes / class-imagify-attachment.php

class-imagify-attachment.php in Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF 1.7, at inc/classes/class-imagify-attachment.php

663 lines 20.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 defined( 'ABSPATH' ) || die( 'Cheatin\' uh?' );
3
4 /**
5 * Imagify Attachment class.
6 *
7 * @since 1.0
8 */
9 class Imagify_Attachment extends Imagify_Abstract_Attachment {
10
11 /**
12 * Class version.
13 *
14 * @var string
15 */
16 const VERSION = '1.1.1';
17
18 /**
19 * The editor instance used to resize files.
20 *
21 * @since 1.6.10
22 *
23 * @var object
24 * @access protected
25 */
26 protected $editor;
27
28 /**
29 * Get the attachment backup file path, even if the file doesn't exist.
30 *
31 * @since 1.6.13
32 * @author Grégory Viguier
33 * @access public
34 *
35 * @return string|bool The file path. False on failure.
36 */
37 public function get_raw_backup_path() {
38 return get_imagify_attachment_backup_path( $this->get_original_path() );
39 }
40
41 /**
42 * Get the attachment optimization data.
43 *
44 * @since 1.0
45 * @access public
46 *
47 * @return array
48 */
49 public function get_data() {
50 $data = get_post_meta( $this->id, '_imagify_data', true );
51 return is_array( $data ) ? $data : array();
52 }
53
54 /**
55 * Get the attachment optimization level.
56 *
57 * @since 1.0
58 * @access public
59 *
60 * @return int
61 */
62 public function get_optimization_level() {
63 $level = get_post_meta( $this->id, '_imagify_optimization_level', true );
64 return false !== $level ? (int) $level : false;
65 }
66
67 /**
68 * Get the attachment optimization status (success or error).
69 *
70 * @since 1.0
71 * @access public
72 *
73 * @return string
74 */
75 public function get_status() {
76 $status = get_post_meta( $this->id, '_imagify_status', true );
77 return is_string( $status ) ? $status : '';
78 }
79
80 /**
81 * Get the original attachment path.
82 *
83 * @since 1.0
84 * @access public
85 *
86 * @return string
87 */
88 public function get_original_path() {
89 return get_attached_file( $this->id );
90 }
91
92 /**
93 * Get the original attachment URL.
94 *
95 * @since 1.0
96 * @access public
97 *
98 * @return string
99 */
100 public function get_original_url() {
101 return wp_get_attachment_url( $this->id );
102 }
103
104 /**
105 * Get width and height of the original image.
106 *
107 * @since 1.7
108 * @author Grégory Viguier
109 * @access public
110 *
111 * @return array
112 */
113 public function get_dimensions() {
114 $values = wp_get_attachment_image_src( $this->id, 'full' );
115
116 return array(
117 'width' => $values[1],
118 'height' => $values[2],
119 );
120 }
121
122 /**
123 * Update the metadata size of the attachment.
124 *
125 * @since 1.2
126 * @access public
127 *
128 * @return bool
129 */
130 public function update_metadata_size() {
131 // Check if the attachment extension is allowed.
132 if ( ! $this->is_extension_supported() ) {
133 return false;
134 }
135
136 $size = @getimagesize( $this->get_original_path() );
137
138 if ( ! isset( $size[0], $size[1] ) ) {
139 return false;
140 }
141
142 $metadata = wp_get_attachment_metadata( $this->id );
143 $metadata['width'] = $size[0];
144 $metadata['height'] = $size[1];
145
146 wp_update_attachment_metadata( $this->id, $metadata );
147 return true;
148 }
149
150 /**
151 * Fills statistics data with values from $data array.
152 *
153 * @since 1.0
154 * @since 1.6.5 Not static anymore.
155 * @since 1.6.6 Removed the attachment ID parameter.
156 * @since 1.7 Removed the image URL parameter.
157 * @access public
158 *
159 * @param array $data The statistics data.
160 * @param object $response The API response.
161 * @param string $size The attachment size key.
162 * @return bool|array False if the original size has an error or an array contains the data for other result.
163 */
164 public function fill_data( $data, $response, $size = 'full' ) {
165 $data = is_array( $data ) ? $data : array();
166 $data['sizes'] = ! empty( $data['sizes'] ) && is_array( $data['sizes'] ) ? $data['sizes'] : array();
167
168 if ( empty( $data['stats'] ) ) {
169 $data['stats'] = array(
170 'original_size' => 0,
171 'optimized_size' => 0,
172 'percent' => 0,
173 );
174 }
175
176 if ( is_wp_error( $response ) ) {
177 $error = $response->get_error_message();
178 $error_status = 'error';
179
180 $data['sizes'][ $size ] = array(
181 'success' => false,
182 'error' => $error,
183 );
184
185 // Update the error status for the original size.
186 if ( 'full' === $size ) {
187 update_post_meta( $this->id, '_imagify_data', $data );
188
189 if ( false !== strpos( $error, 'This image is already compressed' ) ) {
190 $error_status = 'already_optimized';
191 }
192
193 update_post_meta( $this->id, '_imagify_status', $error_status );
194
195 return false;
196 }
197 } else {
198 $response = (object) array_merge( array(
199 'original_size' => 0,
200 'new_size' => 0,
201 'percent' => 0,
202 ), (array) $response );
203
204 $data['sizes'][ $size ] = array(
205 'success' => true,
206 'original_size' => $response->original_size,
207 'optimized_size' => $response->new_size,
208 'percent' => $response->percent,
209 );
210
211 $data['stats']['original_size'] += $response->original_size;
212 $data['stats']['optimized_size'] += $response->new_size;
213 } // End if().
214
215 return $data;
216 }
217
218 /**
219 * Create a thumbnail if it doesn't exist.
220 *
221 * @since 1.6.10
222 * @access protected
223 * @author Grégory Viguier
224 *
225 * @param array $thumbnail_data The thumbnail data (width, height, crop, name, file).
226 * @return bool|array|object True if the file exists. An array of thumbnail data if the file has just been created (width, height, crop, file). A WP_Error object on error.
227 */
228 protected function create_thumbnail( $thumbnail_data ) {
229 $thumbnail_size = $thumbnail_data['name'];
230 $metadata = wp_get_attachment_metadata( $this->id );
231 $metadata_sizes = ! empty( $metadata['sizes'] ) && is_array( $metadata['sizes'] ) ? $metadata['sizes'] : array();
232
233 $original_dirname = trailingslashit( dirname( $this->get_original_path() ) );
234 $thumbnail_path = $original_dirname . $thumbnail_data['file'];
235 $filesystem = imagify_get_filesystem();
236
237 if ( ! empty( $metadata_sizes[ $thumbnail_size ] ) && $filesystem->exists( $thumbnail_path ) ) {
238 imagify_chmod_file( $thumbnail_path );
239 return true;
240 }
241
242 // Get the editor.
243 if ( ! isset( $this->editor ) ) {
244 $this->editor = wp_get_image_editor( $this->get_backup_path() );
245 }
246
247 if ( is_wp_error( $this->editor ) ) {
248 return $this->editor;
249 }
250
251 // Create the file.
252 $result = $this->editor->multi_resize( array( $thumbnail_size => $thumbnail_data ) );
253
254 if ( ! $result ) {
255 return new WP_Error( 'image_resize_error' );
256 }
257
258 // The file name can change from what we expected (1px wider, etc).
259 $backup_dirname = trailingslashit( dirname( $this->get_backup_path() ) );
260 $backup_thumb_path = $backup_dirname . $result[ $thumbnail_size ]['file'];
261 $thumbnail_path = $original_dirname . $result[ $thumbnail_size ]['file'];
262
263 // Since we used the backup image as source, the new image is still in the backup folder, we need to move it.
264 $filesystem->move( $backup_thumb_path, $thumbnail_path, true );
265
266 if ( $filesystem->exists( $backup_thumb_path ) ) {
267 $filesystem->delete( $backup_thumb_path );
268 }
269
270 if ( ! $filesystem->exists( $thumbnail_path ) ) {
271 return new WP_Error( 'image_resize_error' );
272 }
273
274 imagify_chmod_file( $thumbnail_path );
275
276 return reset( $result );
277 }
278
279 /**
280 * Create all missing thumbnails if they don't exist and update the attachment metadata.
281 *
282 * @since 1.6.10
283 * @access protected
284 * @author Grégory Viguier
285 *
286 * @param array $missing_sizes An array of thumbnail data (width, height, crop, name, file) for each thumbnail size.
287 * @return array An array of thumbnail data (width, height, crop, file).
288 */
289 protected function create_missing_thumbnails( $missing_sizes ) {
290 if ( ! $missing_sizes ) {
291 return array();
292 }
293
294 $metadata = wp_get_attachment_metadata( $this->id );
295 $metadata['sizes'] = ! empty( $metadata['sizes'] ) && is_array( $metadata['sizes'] ) ? $metadata['sizes'] : array();
296 $thumbnail_new_datas = array();
297 $thumbnail_metadatas = array();
298
299 // Create the missing thumbnails.
300 foreach ( $missing_sizes as $size_name => $thumbnail_data ) {
301 $result = $this->create_thumbnail( $thumbnail_data );
302
303 if ( is_array( $result ) ) {
304 // New file.
305 $thumbnail_new_datas[ $size_name ] = $result;
306 unset( $thumbnail_new_datas[ $size_name ]['name'] );
307 } elseif ( true === $result ) {
308 // The file already exists.
309 $thumbnail_metadatas[ $size_name ] = $metadata['sizes'][ $size_name ];
310 }
311 }
312
313 // Save the new data into the attachment metadata.
314 if ( $thumbnail_new_datas ) {
315 $metadata['sizes'] = array_merge( $metadata['sizes'], $thumbnail_new_datas );
316
317 /**
318 * Here we don't use wp_update_attachment_metadata() to prevent triggering unwanted hooks.
319 */
320 update_post_meta( $this->id, '_wp_attachment_metadata', $metadata );
321 }
322
323 return array_merge( $thumbnail_metadatas, $thumbnail_new_datas );
324 }
325
326 /**
327 * Optimize all sizes with Imagify.
328 *
329 * @since 1.0
330 * @access public
331 *
332 * @param int $optimization_level The optimization level (2=ultra, 1=aggressive, 0=normal).
333 * @param array $metadata The attachment meta data.
334 * @return array $optimized_data The optimization data.
335 */
336 public function optimize( $optimization_level = null, $metadata = array() ) {
337 // Check if the attachment extension is allowed.
338 if ( ! $this->is_extension_supported() ) {
339 return;
340 }
341
342 $optimization_level = isset( $optimization_level ) ? (int) $optimization_level : get_imagify_option( 'optimization_level' );
343 $metadata = $metadata ? $metadata : wp_get_attachment_metadata( $this->id );
344 $sizes = isset( $metadata['sizes'] ) ? (array) $metadata['sizes'] : array();
345
346 // To avoid issue with "original_size" at 0 in "_imagify_data".
347 if ( 0 === (int) $this->get_stats_data( 'original_size' ) ) {
348 $this->delete_imagify_data();
349 }
350
351 // Check if the full size is already optimized.
352 if ( $this->is_optimized() && ( $this->get_optimization_level() === $optimization_level ) ) {
353 return;
354 }
355
356 // Get file path & URL for original image.
357 $attachment_path = $this->get_original_path();
358 $attachment_url = $this->get_original_url();
359 $attachment_original_size = $this->get_original_size( false );
360
361 /**
362 * Fires before optimizing an attachment.
363 *
364 * @since 1.0
365 *
366 * @param int $id The attachment ID.
367 */
368 do_action( 'before_imagify_optimize_attachment', $this->id );
369
370 set_transient( 'imagify-async-in-progress-' . $this->id, true, 10 * MINUTE_IN_SECONDS );
371
372 // Get the resize values for the original size.
373 $resized = false;
374 $do_resize = get_imagify_option( 'resize_larger' );
375 $resize_width = get_imagify_option( 'resize_larger_w' );
376 $attachment_size = @getimagesize( $attachment_path );
377
378 if ( $do_resize && isset( $attachment_size[0] ) && $resize_width < $attachment_size[0] ) {
379 $resized_attachment_path = $this->resize( $attachment_path, $attachment_size, $resize_width );
380
381 if ( ! is_wp_error( $resized_attachment_path ) ) {
382 // TODO (@Greg): Send an error message if the backup fails.
383 imagify_backup_file( $attachment_path );
384
385 $filesystem = imagify_get_filesystem();
386
387 $filesystem->move( $resized_attachment_path, $attachment_path, true );
388 imagify_chmod_file( $attachment_path );
389
390 // If resized temp file still exists, delete it.
391 if ( $filesystem->exists( $resized_attachment_path ) ) {
392 $filesystem->delete( $resized_attachment_path );
393 }
394
395 $resized = true;
396 }
397 }
398
399 // Optimize the original size.
400 $response = do_imagify( $attachment_path, array(
401 'optimization_level' => $optimization_level,
402 'context' => 'wp',
403 'resized' => $resized,
404 'original_size' => $attachment_original_size,
405 ) );
406
407 $data = $this->fill_data( null, $response );
408
409 // Save the optimization level.
410 update_post_meta( $this->id, '_imagify_optimization_level', $optimization_level );
411
412 // If we resized the original with success, we have to update the attachment metadata.
413 // If not, WordPress keeps the old attachment size.
414 if ( $resized ) {
415 $this->update_metadata_size();
416 }
417
418 if ( ! $data ) {
419 delete_transient( 'imagify-async-in-progress-' . $this->id );
420 return;
421 }
422
423 // Optimize all thumbnails.
424 if ( $sizes ) {
425 $disallowed_sizes = get_imagify_option( 'disallowed-sizes' );
426 $is_active_for_network = imagify_is_active_for_network();
427 $attachment_path_dirname = trailingslashit( dirname( $attachment_path ) );
428 $attachment_url_dirname = trailingslashit( dirname( $attachment_url ) );
429
430 foreach ( $sizes as $size_key => $size_data ) {
431 // Check if this size has to be optimized.
432 if ( ! $is_active_for_network && isset( $disallowed_sizes[ $size_key ] ) ) {
433 $data['sizes'][ $size_key ] = array(
434 'success' => false,
435 'error' => __( 'This size isn\'t authorized to be optimized. Update your Imagify settings if you want to optimize it.', 'imagify' ),
436 );
437 continue;
438 }
439
440 $thumbnail_path = $attachment_path_dirname . $size_data['file'];
441 $thumbnail_url = $attachment_url_dirname . $size_data['file'];
442
443 // Optimize the thumbnail size.
444 $response = do_imagify( $thumbnail_path, array(
445 'backup' => false,
446 'optimization_level' => $optimization_level,
447 'context' => 'wp',
448 ) );
449
450 $data = $this->fill_data( $data, $response, $size_key );
451
452 /**
453 * Filter the optimization data of a specific thumbnail.
454 *
455 * @since 1.0
456 *
457 * @param array $data The statistics data.
458 * @param object $response The API response.
459 * @param int $id The attachment ID.
460 * @param string $thumbnail_path The attachment path.
461 * @param string $thumbnail_url The attachment URL.
462 * @param string $size_key The attachment size key.
463 * @param bool $is_aggressive The optimization level.
464 * @return array $data The new optimization data.
465 */
466 $data = apply_filters( 'imagify_fill_thumbnail_data', $data, $response, $this->id, $thumbnail_path, $thumbnail_url, $size_key, $optimization_level );
467 } // End foreach().
468 } // End if().
469
470 $data['stats']['percent'] = round( ( ( $data['stats']['original_size'] - $data['stats']['optimized_size'] ) / $data['stats']['original_size'] ) * 100, 2 );
471
472 update_post_meta( $this->id, '_imagify_data', $data );
473 update_post_meta( $this->id, '_imagify_status', 'success' );
474
475 $optimized_data = $this->get_data();
476
477 /**
478 * Fires after optimizing an attachment.
479 *
480 * @since 1.0
481 *
482 * @param int $id The attachment ID.
483 * @param array $optimized_data The optimization data.
484 */
485 do_action( 'after_imagify_optimize_attachment', $this->id, $optimized_data );
486
487 delete_transient( 'imagify-async-in-progress-' . $this->id );
488
489 return $optimized_data;
490 }
491
492 /**
493 * Optimize missing thumbnail sizes with Imagify.
494 *
495 * @since 1.6.10
496 * @access public
497 * @author Grégory Viguier
498 *
499 * @param int $optimization_level The optimization level (2=ultra, 1=aggressive, 0=normal).
500 * @return array|object An array of thumbnail data, size by size. A WP_Error object on failure.
501 */
502 public function optimize_missing_thumbnails( $optimization_level = null ) {
503 // Check if the attachment extension is allowed.
504 if ( ! $this->is_extension_supported() ) {
505 return new WP_Error( 'mime_type_not_supported', __( 'This type of file is not supported.', 'imagify' ) );
506 }
507
508 $optimization_level = isset( $optimization_level ) ? (int) $optimization_level : get_imagify_option( 'optimization_level' );
509 $missing_sizes = $this->get_unoptimized_sizes();
510
511 if ( ! $missing_sizes ) {
512 // We have everything we need.
513 return array();
514 }
515
516 // Stop the process if there is no backup file to use.
517 if ( ! $this->has_backup() ) {
518 return new WP_Error( 'no_backup', __( 'This file has no backup file.', 'imagify' ) );
519 }
520
521 /**
522 * Fires before optimizing the missing thumbnails.
523 *
524 * @since 1.6.10
525 * @author Grégory Viguier
526 * @see $this->get_unoptimized_sizes()
527 *
528 * @param int $id The attachment ID.
529 * @param array $missing_sizes An array of the missing sizes.
530 */
531 do_action( 'before_imagify_optimize_missing_thumbnails', $this->id, $missing_sizes );
532
533 set_transient( 'imagify-async-in-progress-' . $this->id, true, 10 * MINUTE_IN_SECONDS );
534
535 $errors = new WP_Error();
536
537 // Create the missing thumbnails.
538 $result_sizes = $this->create_missing_thumbnails( $missing_sizes );
539 $failed_sizes = array_diff_key( $missing_sizes, $result_sizes );
540
541 if ( $failed_sizes ) {
542 $failed_count = count( $failed_sizes );
543 /* translators: %d is a number of thumbnails. */
544 $error_message = _n( '%d thumbnail failed to be created', '%d thumbnails failed to be created', $failed_count, 'imagify' );
545 $error_message = sprintf( $error_message, $failed_count );
546 $errors->add( 'image_resize_error', $error_message, array( 'nbr_failed' => $failed_count, 'sizes_failed' => $failed_sizes, 'sizes_succeeded' => $result_sizes ) );
547 }
548
549 if ( ! $result_sizes ) {
550 delete_transient( 'imagify-async-in-progress-' . $this->id );
551 return $errors;
552 }
553
554 // Optimize.
555 $imagify_data = $this->get_data();
556 $original_dirname = trailingslashit( dirname( $this->get_original_path() ) );
557 $orig_url_dirname = trailingslashit( dirname( $this->get_original_url() ) );
558
559 foreach ( $result_sizes as $size_name => $thumbnail_data ) {
560 $thumbnail_path = $original_dirname . $thumbnail_data['file'];
561 $thumbnail_url = $orig_url_dirname . $thumbnail_data['file'];
562
563 // Optimize the thumbnail size.
564 $response = do_imagify( $thumbnail_path, array(
565 'backup' => false,
566 'optimization_level' => $optimization_level,
567 'context' => 'wp',
568 ) );
569
570 $imagify_data = $this->fill_data( $imagify_data, $response, $size_name );
571
572 /** This filter is documented in inc/classes/class-imagify-attachment.php. */
573 $imagify_data = apply_filters( 'imagify_fill_thumbnail_data', $imagify_data, $response, $this->id, $thumbnail_path, $thumbnail_url, $size_name, $optimization_level );
574 }
575
576 // Save Imagify data.
577 $imagify_data['stats']['percent'] = round( ( ( $imagify_data['stats']['original_size'] - $imagify_data['stats']['optimized_size'] ) / $imagify_data['stats']['original_size'] ) * 100, 2 );
578
579 update_post_meta( $this->id, '_imagify_data', $imagify_data );
580
581 /**
582 * Fires after optimizing the missing thumbnails.
583 *
584 * @since 1.6.10
585 * @author Grégory Viguier
586 * @see $this->create_missing_thumbnails()
587 *
588 * @param int $id The attachment ID.
589 * @param array $result_sizes An array of created thumbnails.
590 * @param object $errors A WP_Error object that stores thumbnail creation failures.
591 */
592 do_action( 'after_imagify_optimize_missing_thumbnails', $this->id, $result_sizes, $errors );
593
594 delete_transient( 'imagify-async-in-progress-' . $this->id );
595
596 // Return the result.
597 if ( $errors->get_error_codes() ) {
598 return $errors;
599 }
600
601 return $result_sizes;
602 }
603
604 /**
605 * Process an attachment restoration from the backup file.
606 *
607 * @since 1.0
608 * @access public
609 *
610 * @return void
611 */
612 public function restore() {
613 // Check if the attachment extension is allowed.
614 if ( ! $this->is_extension_supported() ) {
615 return;
616 }
617
618 // Stop the process if there is no backup file to restore.
619 if ( ! $this->has_backup() ) {
620 return;
621 }
622
623 $backup_path = $this->get_backup_path();
624 $attachment_path = $this->get_original_path();
625 $filesystem = imagify_get_filesystem();
626
627 /**
628 * Fires before restoring an attachment.
629 *
630 * @since 1.0
631 *
632 * @param int $id The attachment ID
633 */
634 do_action( 'before_imagify_restore_attachment', $this->id );
635
636 // Create the original image from the backup.
637 $filesystem->copy( $backup_path, $attachment_path, true );
638 imagify_chmod_file( $attachment_path );
639
640 if ( ! function_exists( 'wp_generate_attachment_metadata' ) ) {
641 require_once( ABSPATH . 'wp-admin/includes/image.php' );
642 }
643
644 remove_filter( 'wp_generate_attachment_metadata', '_imagify_optimize_attachment', IMAGIFY_INT_MAX );
645 wp_generate_attachment_metadata( $this->id, $attachment_path );
646
647 // Remove old optimization data.
648 $this->delete_imagify_data();
649
650 // Restore the original size in the metadata.
651 $this->update_metadata_size();
652
653 /**
654 * Fires after restoring an attachment.
655 *
656 * @since 1.0
657 *
658 * @param int $id The attachment ID
659 */
660 do_action( 'after_imagify_restore_attachment', $this->id );
661 }
662 }
663