PluginProbe
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF / 2.3.1
Imagify Image Optimization: Optimize Images | Compress & Convert to WebP/AVIF v2.3.1
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.3.1, at inc/classes/class-imagify-auto-optimization.php

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