PluginProbe
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF / 2.2.2
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF v2.2.2
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-auto-optimization.php

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

653 lines 19.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 use Imagify\Traits\InstanceGetterTrait;
4
5 defined( 'ABSPATH' ) || die( 'Cheatin’ uh?' );
6
7 /**
8 * Class that handles the auto-optimization process.
9 * This occurs when a new image is uploaded, and when an optimized image is worked with (resized, etc).
10 * The process will work only if wp_generate_attachment_metadata() and wp_update_attachment_metadata() are used.
11 *
12 * @since 1.8.4
13 */
14 class Imagify_Auto_Optimization extends Imagify_Auto_Optimization_Deprecated {
15 use InstanceGetterTrait;
16
17 /**
18 * An array containing all the "steps" an attachment is going through.
19 * This is used to decide the behavior of the automatic optimization.
20 *
21 * @var array {
22 * An array of arrays with attachment ID as keys.
23 * Each array can contain the following:
24 *
25 * @type $upload int Set to 1 if the attachment is a new upload.
26 * @type $generate int Set to 1 when going though wp_generate_attachment_metadata().
27 * @type $update int Set to 1 when going though wp_update_attachment_metadata().
28 * }
29 * @since 1.8.4
30 * @since 1.9.10 Private.
31 * @since 1.9.10 Items are arrays instead of 1s.
32 */
33 private $attachments = [];
34
35 /**
36 * Tell if we’re using WP 5.3+.
37 *
38 * @var bool
39 * @since 1.9.10
40 */
41 private $is_wp_53;
42
43 /**
44 * The ID of the attachment that failed to be uploaded.
45 *
46 * @var int
47 * @since 1.9.8
48 */
49 protected $upload_failure_id = 0;
50
51 /**
52 * Used to prevent an auto-optimization locally.
53 *
54 * @var array
55 * @since 1.8.4
56 */
57 private static $prevented = [];
58
59 /**
60 * Used to prevent an auto-optimization internally.
61 *
62 * @var array
63 * @since 1.9.8
64 */
65 private static $prevented_internally = [];
66
67 /**
68 * Init.
69 *
70 * @since 1.8.4
71 */
72 public function init() {
73 global $wp_version;
74
75 $priority = IMAGIFY_INT_MAX - 30;
76 $this->is_wp_53 = version_compare( $wp_version, '5.3-alpha1' ) >= 0;
77
78 // Automatic optimization tunel.
79 add_action( 'add_attachment', [ $this, 'store_upload_ids' ], $priority );
80 add_filter( 'wp_generate_attachment_metadata', [ $this, 'maybe_store_generate_step' ], $priority, 2 );
81 add_filter( 'wp_update_attachment_metadata', [ $this, 'store_ids_to_optimize' ], $priority, 2 );
82
83 if ( $this->is_wp_53 ) {
84 // WP 5.3+.
85 add_action( 'imagify_after_auto_optimization_init', [ $this, 'do_auto_optimization' ], $priority, 2 );
86 // Upload failure recovering.
87 add_action( 'wp_ajax_media-create-image-subsizes', [ $this, 'prevent_auto_optimization_when_recovering_from_upload_failure' ], -5 ); // Before WP’s hook (priority 1).
88 } else {
89 add_action( 'updated_post_meta', [ $this, 'do_auto_optimization_after_meta_update' ], $priority, 4 );
90 add_action( 'added_post_meta', [ $this, 'do_auto_optimization_after_meta_update' ], $priority, 4 );
91 }
92
93 add_action( 'deleted_post_meta', [ $this, 'unset_optimization' ], $priority, 3 );
94
95 // Prevent to re-optimize when updating the image width and height (when resizing the full image).
96 add_action( 'imagify_before_update_wp_media_data_dimensions', [ __CLASS__, 'prevent_optimization' ], 5 );
97 add_action( 'imagify_after_update_wp_media_data_dimensions', [ __CLASS__, 'allow_optimization' ], 5 );
98 }
99
100 /**
101 * Remove the hooks.
102 *
103 * @since 1.8.4
104 */
105 public function remove_hooks() {
106 $priority = IMAGIFY_INT_MAX - 30;
107
108 // Automatic optimization tunel.
109 remove_action( 'add_attachment', [ $this, 'store_upload_ids' ], $priority );
110 remove_filter( 'wp_generate_attachment_metadata', [ $this, 'maybe_store_generate_step' ], $priority );
111 remove_filter( 'wp_update_attachment_metadata', [ $this, 'store_ids_to_optimize' ], $priority );
112
113 if ( $this->is_wp_53 ) {
114 // WP 5.3+.
115 remove_action( 'imagify_after_auto_optimization_init', [ $this, 'do_auto_optimization' ], $priority );
116 // Upload failure recovering.
117 remove_action( 'wp_ajax_media-create-image-subsizes', [ $this, 'prevent_auto_optimization_when_recovering_from_upload_failure' ], -5 );
118 } else {
119 remove_action( 'updated_post_meta', [ $this, 'do_auto_optimization_after_meta_update' ], $priority );
120 remove_action( 'added_post_meta', [ $this, 'do_auto_optimization_after_meta_update' ], $priority );
121 }
122
123 remove_action( 'deleted_post_meta', [ $this, 'unset_optimization' ], $priority );
124
125 // Prevent to re-optimize when updating the image width and height (when resizing the full image).
126 remove_action( 'imagify_before_update_wp_media_data_dimensions', [ __CLASS__, 'prevent_optimization' ], 5 );
127 remove_action( 'imagify_after_update_wp_media_data_dimensions', [ __CLASS__, 'allow_optimization' ], 5 );
128 }
129
130
131 /** ----------------------------------------------------------------------------------------- */
132 /** HOOKS =================================================================================== */
133 /** ----------------------------------------------------------------------------------------- */
134
135 /**
136 * Store the "upload step" when an attachment has just been uploaded.
137 *
138 * @since 1.8.4
139 * @see $this->store_ids_to_optimize()
140 *
141 * @param int $attachment_id Current attachment ID.
142 */
143 public function store_upload_ids( $attachment_id ) {
144 if ( ! self::is_optimization_prevented( $attachment_id ) && imagify_is_attachment_mime_type_supported( $attachment_id ) ) {
145 $this->set_step( $attachment_id, 'upload' );
146 }
147 }
148
149 /**
150 * Store the "generate step" when wp_generate_attachment_metadata() is used.
151 *
152 * @since 1.9.10
153 *
154 * @param array $metadata An array of attachment meta data.
155 * @param int $attachment_id Current attachment ID.
156 * @return array
157 */
158 public function maybe_store_generate_step( $metadata, $attachment_id ) {
159 if ( self::is_optimization_prevented( $attachment_id ) ) {
160 return $metadata;
161 }
162
163 if ( empty( $metadata ) || ! imagify_is_attachment_mime_type_supported( $attachment_id ) ) {
164 $this->unset_steps( $attachment_id );
165 return $metadata;
166 }
167
168 $this->set_step( $attachment_id, 'generate' );
169
170 return $metadata;
171 }
172
173 /**
174 * After the attachment meta data has been generated (partially, since WP 5.3), init the auto-optimization.
175 * Two cases are possible to trigger the optimization:
176 * - It's a new upload and auto-optimization is enabled.
177 * - It's not a new upload (it is regenerated) and the attachment is already optimized.
178 *
179 * @since 1.8.4
180 *
181 * @param array $metadata An array of attachment meta data.
182 * @param int $attachment_id Current attachment ID.
183 * @return array
184 */
185 public function store_ids_to_optimize( $metadata, $attachment_id ) {
186 static $auto_optimize;
187
188 if ( self::is_optimization_prevented( $attachment_id ) ) {
189 return $metadata;
190 }
191
192 if ( empty( $metadata ) || ! imagify_is_attachment_mime_type_supported( $attachment_id ) ) {
193 $this->unset_steps( $attachment_id );
194 return $metadata;
195 }
196
197 if ( ! $this->has_step( $attachment_id, 'generate' ) ) {
198 return $metadata;
199 }
200
201 $is_new_upload = $this->has_step( $attachment_id, 'upload' );
202
203 if ( $is_new_upload ) {
204 // It's a new upload.
205 if ( ! isset( $auto_optimize ) ) {
206 $auto_optimize = get_imagify_option( 'auto_optimize' );
207 }
208
209 if ( ! $auto_optimize ) {
210 /**
211 * Fires when a new attachment is uploaded but auto-optimization is disabled.
212 *
213 * @since 1.8.4
214 *
215 * @param int $attachment_id Attachment ID.
216 * @param array $metadata An array of attachment meta data.
217 */
218 do_action( 'imagify_new_attachment_auto_optimization_disabled', $attachment_id, $metadata );
219
220 return $metadata;
221 }
222
223 /**
224 * Allow to prevent automatic optimization for a specific attachment.
225 *
226 * @since 1.6.12
227 *
228 * @param bool $optimize True to optimize, false otherwise.
229 * @param int $attachment_id Attachment ID.
230 * @param array $metadata An array of attachment meta data.
231 */
232 $optimize = apply_filters( 'imagify_auto_optimize_attachment', true, $attachment_id, $metadata );
233
234 if ( ! $optimize ) {
235 return $metadata;
236 }
237
238 /**
239 * It's a new upload and auto-optimization is enabled.
240 */
241 }
242
243 if ( ! $is_new_upload ) {
244 // An existing attachment being regenerated (or something).
245 $process = imagify_get_optimization_process( $attachment_id, 'wp' );
246
247 if ( ! $process->is_valid() ) {
248 // Uh?
249 return $metadata;
250 }
251
252 if ( ! $process->get_data()->get_optimization_status() ) {
253 /**
254 * Fires when an attachment is updated but not optimized yet.
255 *
256 * @since 1.8.4
257 *
258 * @param int $attachment_id Attachment ID.
259 * @param array $metadata An array of attachment meta data.
260 */
261 do_action( 'imagify_not_optimized_attachment_updated', $attachment_id, $metadata );
262
263 return $metadata;
264 }
265
266 /**
267 * Allow to prevent automatic reoptimization for a specific attachment.
268 *
269 * @since 1.8.4
270 *
271 * @param bool $optimize True to optimize, false otherwise.
272 * @param int $attachment_id Attachment ID.
273 * @param array $metadata An array of attachment meta data.
274 */
275 $optimize = apply_filters( 'imagify_auto_optimize_optimized_attachment', true, $attachment_id, $metadata );
276
277 if ( ! $optimize ) {
278 return $metadata;
279 }
280
281 /**
282 * The attachment already exists and was already optimized.
283 */
284 }
285
286 // Ready for the next step.
287 $this->set_step( $attachment_id, 'update' );
288
289 /**
290 * Triggered after a media auto-optimization init.
291 *
292 * @since 1.9.8
293 *
294 * @param int $attachment_id The media ID.
295 * @param bool $is_new_upload True if it's a new upload. False otherwize.
296 */
297 do_action( 'imagify_after_auto_optimization_init', $attachment_id, $is_new_upload );
298
299 return $metadata;
300 }
301
302 /**
303 * Launch auto optimization immediately after the post meta '_wp_attachment_metadata' is added or updated.
304 *
305 * @since 1.9
306 * @since 1.9 Previously named do_auto_optimization().
307 *
308 * @param int $meta_id ID of the metadata entry.
309 * @param int $attachment_id Current attachment ID.
310 * @param string $meta_key Meta key.
311 * @param mixed $metadata Meta value.
312 */
313 public function do_auto_optimization_after_meta_update( $meta_id, $attachment_id, $meta_key, $metadata ) {
314 if ( '_wp_attachment_metadata' !== $meta_key ) {
315 return;
316 }
317
318 if ( self::is_optimization_prevented( $attachment_id ) ) {
319 return;
320 }
321
322 if ( ! $this->has_step( $attachment_id, 'update' ) ) {
323 return;
324 }
325
326 $this->do_auto_optimization( $attachment_id, $this->has_step( $attachment_id, 'upload' ) );
327 }
328
329 /**
330 * Launch auto optimization immediately after the post meta '_wp_attachment_metadata' is added or updated.
331 *
332 * @since 1.8.4
333 * @since 1.9.8 Changed signature.
334 *
335 * @param int $attachment_id The media ID.
336 * @param bool $is_new_upload True if it's a new upload. False otherwize.
337 */
338 public function do_auto_optimization( $attachment_id, $is_new_upload ) {
339 $this->unset_steps( $attachment_id );
340
341 $process = imagify_get_optimization_process( $attachment_id, 'wp' );
342
343 /**
344 * Fires before an attachment auto-optimization is triggered.
345 *
346 * @since 1.8.4
347 *
348 * @param int $attachment_id The attachment ID.
349 * @param bool $is_new_upload True if it's a new upload. False otherwize.
350 */
351 do_action_deprecated( 'imagify_before_auto_optimization_launch', [ $attachment_id, $is_new_upload ], '1.9', 'imagify_before_auto_optimization' );
352
353 /**
354 * Triggered before a media is auto-optimized.
355 *
356 * @since 1.8.4
357 *
358 * @param int $attachment_id The media ID.
359 * @param bool $is_new_upload True if it's a new upload. False otherwize.
360 */
361 do_action( 'imagify_before_auto_optimization', $attachment_id, $is_new_upload );
362
363 if ( $is_new_upload ) {
364 /**
365 * It's a new upload.
366 */
367 // Optimize.
368 $process->optimize( null, [ 'is_new_upload' => 1 ] );
369 } else {
370 /**
371 * The media has already been optimized (or at least it has been tried).
372 */
373 $process_data = $process->get_data();
374
375 // Get the optimization level before deleting the optimization data.
376 $optimization_level = $process_data->get_optimization_level();
377
378 // Some specifics for the image editor.
379 if ( isset( $_POST['action'], $_POST['do'], $_POST['postid'] ) && 'image-editor' === $_POST['action'] && (int) $_POST['postid'] === $attachment_id ) { // WPCS: CSRF ok.
380 check_ajax_referer( 'image_editor-' . $attachment_id );
381
382 if ( ! current_user_can( 'edit_post', $attachment_id ) ) {
383 imagify_die();
384 }
385
386 // Restore the backup file.
387 $result = $process->restore();
388
389 if ( is_wp_error( $result ) ) {
390 // Restoration failed, there is no good way to handle this case.
391 $process_data->delete_optimization_data();
392 }
393 } else {
394 // Remove old optimization data.
395 $process_data->delete_optimization_data();
396 }
397
398 // Optimize.
399 $process->optimize( $optimization_level );
400 }
401
402 /**
403 * Triggered after a media auto-optimization is launched.
404 *
405 * @since 1.8.4
406 *
407 * @param int $attachment_id The media ID.
408 * @param bool $is_new_upload True if it's a new upload. False otherwize.
409 */
410 do_action( 'imagify_after_auto_optimization', $attachment_id, $is_new_upload );
411 }
412
413 /**
414 * Remove the attachment ID from the $attachments property if the post meta '_wp_attachment_metadata' is deleted.
415 *
416 * @since 1.8.4
417 *
418 * @param int $meta_ids An array of deleted metadata entry IDs.
419 * @param int $attachment_id Current attachment ID.
420 * @param string $meta_key Meta key.
421 */
422 public function unset_optimization( $meta_ids, $attachment_id, $meta_key ) {
423 if ( '_wp_attachment_metadata' !== $meta_key ) {
424 return;
425 }
426
427 $this->unset_steps( $attachment_id );
428 }
429
430
431 /** ----------------------------------------------------------------------------------------- */
432 /** HOOKS FOR WP 5.3+’S UPLOAD FAILURE RECOVERING =========================================== */
433 /** ----------------------------------------------------------------------------------------- */
434
435 /**
436 * With WP 5.3+, prevent auto-optimization when WP tries to create thumbnails after an upload error, because it triggers wp_update_attachment_metadata() for each thumbnail size.
437 *
438 * @since 1.9.8
439 * @see wp_ajax_media_create_image_subsizes()
440 * @see wp_update_image_subsizes()
441 */
442 public function prevent_auto_optimization_when_recovering_from_upload_failure() {
443 if ( ! check_ajax_referer( 'media-form', false, false ) ) {
444 return;
445 }
446
447 if ( ! current_user_can( 'upload_files' ) ) {
448 return;
449 }
450
451 if ( ! imagify_get_context( 'wp' )->current_user_can( 'auto-optimize' ) ) {
452 return;
453 }
454
455 $attachment_id = ! empty( $_POST['attachment_id'] ) ? (int) $_POST['attachment_id'] : 0;
456
457 if ( empty( $attachment_id ) ) {
458 return;
459 }
460
461 if ( ! imagify_is_attachment_mime_type_supported( $attachment_id ) ) {
462 return;
463 }
464
465 $this->upload_failure_id = $attachment_id;
466
467 // Auto-optimization will be done on shutdown.
468 ob_start( [ $this, 'maybe_do_auto_optimization_after_recovering_from_upload_failure' ] );
469 }
470
471 /**
472 * Maybe launch auto-optimization after recovering from an upload failure, when all thumbnails are created.
473 *
474 * @since 1.9.8
475 * @see wp_ajax_media_create_image_subsizes()
476 *
477 * @param string $content Buffer’s content.
478 * @return string Buffer’s content.
479 */
480 public function maybe_do_auto_optimization_after_recovering_from_upload_failure( $content ) {
481 if ( empty( $content ) ) {
482 return $content;
483 }
484
485 if ( empty( $this->upload_failure_id ) ) {
486 // Uh?
487 return $content;
488 }
489
490 if ( ! get_post( $this->upload_failure_id ) ) {
491 return $content;
492 }
493
494 $json = @json_decode( $content );
495
496 if ( empty( $json->success ) ) {
497 return $content;
498 }
499
500 $attachment_id = $this->upload_failure_id;
501 $metadata = wp_get_attachment_metadata( $attachment_id );
502
503 // Launch the process.
504 $this->upload_failure_id = 0;
505 $this->set_step( $attachment_id, 'generate' );
506 $this->store_ids_to_optimize( $metadata, $attachment_id );
507
508 return $content;
509 }
510
511
512 /** ----------------------------------------------------------------------------------------- */
513 /** TOOLS =================================================================================== */
514 /** ----------------------------------------------------------------------------------------- */
515
516 /**
517 * Set a "step" for an attachment.
518 *
519 * @since 1.9.10
520 * @see $this->attachments
521 *
522 * @param int $attachment_id Current attachment ID.
523 * @param string $step The step to add.
524 */
525 public function set_step( $attachment_id, $step ) {
526 if ( empty( $this->attachments[ $attachment_id ] ) ) {
527 $this->attachments[ $attachment_id ] = [];
528 }
529
530 $this->attachments[ $attachment_id ][ $step ] = 1;
531 }
532
533 /**
534 * Unset a "step" for an attachment.
535 *
536 * @since 1.9.10
537 * @see $this->attachments
538 *
539 * @param int $attachment_id Current attachment ID.
540 * @param string $step The step to add.
541 */
542 public function unset_step( $attachment_id, $step ) {
543 unset( $this->attachments[ $attachment_id ][ $step ] );
544
545 if ( empty( $this->attachments[ $attachment_id ] ) ) {
546 $this->unset_steps( $attachment_id );
547 }
548 }
549
550 /**
551 * Unset all "steps" for an attachment.
552 *
553 * @since 1.9.10
554 * @see $this->attachments
555 *
556 * @param int $attachment_id Current attachment ID.
557 */
558 public function unset_steps( $attachment_id ) {
559 unset( $this->attachments[ $attachment_id ] );
560 }
561
562 /**
563 * Tell if a "step" for an attachment exists.
564 *
565 * @since 1.9.10
566 * @see $this->attachments
567 *
568 * @param int $attachment_id Current attachment ID.
569 * @param string $step The step to add.
570 * @return bool
571 */
572 public function has_step( $attachment_id, $step ) {
573 return ! empty( $this->attachments[ $attachment_id ][ $step ] );
574 }
575
576 /**
577 * Prevent an auto-optimization locally.
578 * How to use it:
579 * Imagify_Auto_Optimization::prevent_optimization( $attachment_id );
580 * wp_update_attachment_metadata( $attachment_id );
581 * Imagify_Auto_Optimization::allow_optimization( $attachment_id );
582 *
583 * @since 1.8.4
584 * @since 1.9.8 Prevents/Allows can stack.
585 *
586 * @param int $attachment_id Current attachment ID.
587 */
588 public static function prevent_optimization( $attachment_id ) {
589 if ( ! isset( self::$prevented[ $attachment_id ] ) ) {
590 self::$prevented[ $attachment_id ] = 1;
591 } else {
592 ++self::$prevented[ $attachment_id ];
593 }
594 }
595
596 /**
597 * Allow an auto-optimization locally.
598 * How to use it:
599 * Imagify_Auto_Optimization::prevent_optimization( $attachment_id );
600 * wp_update_attachment_metadata( $attachment_id );
601 * Imagify_Auto_Optimization::allow_optimization( $attachment_id );
602 *
603 * @since 1.8.4
604 * @since 1.9.8 Prevents/Allows can stack.
605 *
606 * @param int $attachment_id Current attachment ID.
607 */
608 public static function allow_optimization( $attachment_id ) {
609 if ( ! isset( self::$prevented[ $attachment_id ] ) ) {
610 return;
611 }
612 --self::$prevented[ $attachment_id ];
613
614 if ( self::$prevented[ $attachment_id ] <= 0 ) {
615 unset( self::$prevented[ $attachment_id ] );
616 }
617 }
618
619 /**
620 * Tell if an auto-optimization is prevented locally.
621 *
622 * @since 1.8.4
623 *
624 * @param int $attachment_id Current attachment ID.
625 * @return bool
626 */
627 public static function is_optimization_prevented( $attachment_id ) {
628 return ! empty( self::$prevented[ $attachment_id ] ) || ! empty( self::$prevented_internally[ $attachment_id ] );
629 }
630
631 /**
632 * Prevent an auto-optimization internally.
633 *
634 * @since 1.9.8
635 *
636 * @param int $attachment_id Current attachment ID.
637 */
638 protected static function prevent_optimization_internally( $attachment_id ) {
639 self::$prevented_internally[ $attachment_id ] = 1;
640 }
641
642 /**
643 * Allow an auto-optimization internally.
644 *
645 * @since 1.9.8
646 *
647 * @param int $attachment_id Current attachment ID.
648 */
649 protected static function allow_optimization_internally( $attachment_id ) {
650 unset( self::$prevented_internally[ $attachment_id ] );
651 }
652 }
653