class-backup.php
29.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
<?php
/**
* Smush backup class
*
* @package Smush\Core\Modules
*/
namespace Smush\Core\Modules;
use Smush\Core\Core;
use Smush\Core\Helper;
use WP_Smush;
if ( ! defined( 'WPINC' ) ) {
die;
}
/**
* Class Backup
*/
class Backup extends Abstract_Module {
/**
* Module slug.
*
* @var string
*/
protected $slug = 'backup';
/**
* Key for storing file path for image backup
*
* @var string
*/
private $backup_key = 'smush-full';
/**
* Backup constructor.
*/
public function init() {
// Handle Restore operation.
add_action( 'wp_ajax_smush_restore_image', array( $this, 'restore_image' ) );
// Handle bulk restore from modal.
add_action( 'wp_ajax_get_image_count', array( $this, 'get_image_count' ) );
add_action( 'wp_ajax_restore_step', array( $this, 'restore_step' ) );
}
/**
* Check if the backup file exists.
*
* @param int $attachment_id Attachment ID.
* @param string $file_path Current file path.
* @return bool True if the backup file exists, false otherwise.
*/
public function backup_exists( $attachment_id, $file_path = false ) {
return apply_filters( 'wp_smush_backup_exists', $this->get_backup_file( $attachment_id, $file_path ), $attachment_id, $file_path );
}
/**
* Generate unique .bak file.
*
* @param string $bak_file The .bak file.
* @param int $attachment_id Attachment ID.
* @return string Returns a unique backup file.
*/
private function generate_unique_bak_file( $bak_file, $attachment_id ) {
if ( strpos( $bak_file, '.bak' ) && Helper::file_exists( $bak_file, $attachment_id ) ) {
$count = 1;
$ext = Helper::get_file_ext( $bak_file );
$ext = ".bak.$ext";
$file_without_ext = rtrim( $bak_file, $ext );
$bak_file = $file_without_ext . '-' . $count . $ext;
while ( Helper::file_exists( $bak_file, $attachment_id ) ) {
$count++;
$bak_file = $file_without_ext . '-' . $count . $ext;
}
return $bak_file;
}
return $bak_file;
}
/**
* Creates a backup of file for the given attachment path.
*
* Checks if there is an existing backup, else create one.
*
* @param string $file_path File path.
* @param int $attachment_id Attachment ID.
*
* @return void
*/
public function create_backup( $file_path, $attachment_id ) {
if ( empty( $file_path ) || empty( $attachment_id ) ) {
return;
}
// If backup not enabled, return.
if ( ! $this->is_active() ) {
return;
}
/**
* If [ not compress original ]:
* if [ is-scaled.file ]:
* Backup original file.
* elseif [ no-resize + no-png2jpg ]:
* We don't need to backup, let user try to use regenerate plugin
* to restore the compressed thumbnails size.
* else: continue as compress_original.
* else:
* We don't need to backup if we had a backup file for PNG2JPG,
* or .bak file. But if the .bak file is from third party, we will generate our new backup file.
* end.
*/
// We might not need to backup the file if we're not compressing original.
if ( ! $this->settings->get( 'original' ) ) {
/**
* Add WordPress 5.3 support for -scaled images size, and those can always be used to restore.
* Maybe user doesn't want to auto-scale JPG from WP for some images,
* so we allow user to restore it even we don't Smush this image.
*/
if ( false !== strpos( $file_path, '-scaled.' ) && function_exists( 'wp_get_original_image_path' ) ) {
// Scaled images already have a backup. Use that and don't create a new one.
$file_path = Helper::get_attached_file( $attachment_id, 'backup' );// Supported S3.
if ( file_exists( $file_path ) ) {
/**
* We do not need an additional backup file if we're not compressing originals.
* But we need to save the original file as a backup file in the metadata to allow restoring this image later.
*/
$this->add_to_image_backup_sizes( $attachment_id, $file_path );
return;
}
}
$mod = WP_Smush::get_instance()->core()->mod;
// If there is not *-scaled.jpg file, we don't need to backup the file if we don't work with original file.
if ( ! $mod->resize->is_active() && ! $mod->png2jpg->is_active() ) {
/**
* In this case, we can add the meta to save the original file as a backup file,
* but if there is a lot of images, might take a lot of row for postmeta table,
* so leave it for user to use a "regenerate thumbnail" plugin instead.
*/
Helper::logger()->backup()->info( sprintf( 'Not modify the original file [%s(%d)], skip the backup.', Helper::clean_file_path( $file_path ), $attachment_id ) );
return;
}
$should_backup = false;
// We should backup this image if we can resize it.
if ( $mod->resize->is_active() && $mod->resize->should_resize( $attachment_id ) ) {
$should_backup = true;
}
// We should backup this image if we can convert it from PNG to JPEG.
if (
! $should_backup && $mod->png2jpg->is_active() && Helper::get_file_ext( $file_path, 'png' )
&& $mod->png2jpg->can_be_converted( $attachment_id, 'full', 'image/png', $file_path )
) {
$should_backup = true;
}
// As we don't work with the original file, so we don't back it up.
if ( ! $should_backup ) {
Helper::logger()->backup()->info( sprintf( 'Not modify the original file [%s(%d)], skip the backup.', Helper::clean_file_path( $file_path ), $attachment_id ) );
return;
}
}
/**
* Check if exists backup file from meta,
* Because we will compress the original file,
* so we only keep the backup file if there is PNG2JPG or .bak file.
*/
$backup_path = $this->get_backup_file( $attachment_id, $file_path );
if ( $backup_path ) {
/**
* We will compress the original file so the backup file have to different from current file.
* And the backup file should be the same folder with the main file.
*/
if ( $backup_path !== $file_path && dirname( $file_path ) === dirname( $backup_path ) ) {
// Check if there is a .bak file or PNG2JPG file.
if ( strpos( $backup_path, '.bak' ) || ( Helper::get_file_ext( $backup_path, 'png' ) && Helper::get_file_ext( $file_path, 'jpg' ) ) ) {
Helper::logger()->backup()->info( sprintf( 'Found backed up file [%s(%d)].', Helper::clean_file_path( $backup_path ), $attachment_id ) );
return;
}
}
}
/**
* To avoid the conflict with 3rd party, we will generate a new backup file.
* Because how about if 3rd party delete the backup file before trying to restore it from Smush?
* We only try to use their bak file while restoring the backup file.
*/
$backup_path = $this->generate_unique_bak_file( $this->get_image_backup_path( $file_path ), $attachment_id );
/**
* We need to save the .bak file to the meta. Because if there is a PNG, when we convert PNG2JPG,
* the converted file is .jpg, so the bak file will be .bak.jpg not .bak.png
*/
// Store the backup path in image backup sizes.
if ( copy( $file_path, $backup_path ) ) {
$this->add_to_image_backup_sizes( $attachment_id, $backup_path );
} else {
Helper::logger()->backup()->error( sprintf( 'Cannot backup file [%s(%d)].', Helper::clean_file_path( $file_path ), $attachment_id ) );
}
}
/**
* Store new backup path for the image.
*
* @param int $attachment_id Attachment ID.
* @param string $backup_path Backup path.
* @param string $backup_key Backup key.
*/
public function add_to_image_backup_sizes( $attachment_id, $backup_path, $backup_key = '' ) {
if ( empty( $attachment_id ) || empty( $backup_path ) ) {
return;
}
// Get the Existing backup sizes.
$backup_sizes = $this->get_backup_sizes( $attachment_id );
if ( empty( $backup_sizes ) ) {
$backup_sizes = array();
}
// Prevent phar deserialization vulnerability.
if ( false !== stripos( $backup_path, 'phar://' ) ) {
Helper::logger()->backup()->info( sprintf( 'Prevent phar deserialization vulnerability [%s(%d)].', Helper::clean_file_path( $backup_path ), $attachment_id ) );
return;
}
// Return if backup file doesn't exist.
if ( ! file_exists( $backup_path ) ) {
Helper::logger()->backup()->notice( sprintf( 'Back file [%s(%d)] does not exist.', Helper::clean_file_path( $backup_path ), $attachment_id ) );
return;
}
list( $width, $height ) = getimagesize( $backup_path );
// Store our backup path.
$backup_key = empty( $backup_key ) ? $this->backup_key : $backup_key;
$backup_sizes[ $backup_key ] = array(
'file' => wp_basename( $backup_path ),
'width' => $width,
'height' => $height,
);
wp_cache_delete( 'images_with_backups', 'wp-smush' );
update_post_meta( $attachment_id, '_wp_attachment_backup_sizes', $backup_sizes );
}
/**
* Get backup sizes.
*
* @param int $attachment_id Attachment ID.
* @return mixed False or an array of backup sizes.
*/
public function get_backup_sizes( $attachment_id ) {
return get_post_meta( $attachment_id, '_wp_attachment_backup_sizes', true );
}
/**
* Back up an image if it hasn't backed up yet.
*
* @since 3.9.6
*
* @param int $attachment_id Image id.
* @param string $backup_file File path to back up.
*
* Note, we used it to manage backup PNG2JPG to keep the backup file is the original file to avoid conflicts with a duplicate PNG file.
* If the backup file exists it will rename the original backup file to
* the new backup file.
*
* @return bool True if added this file to the backup sizes, false if the image was backed up before.
*/
public function maybe_backup_image( $attachment_id, $backup_file ) {
if ( ! file_exists( $backup_file ) ) {
return false;
}
// We don't use .bak file from 3rd party while backing up.
$backed_up_file = $this->get_backup_file( $attachment_id, $backup_file );
$was_backed_up = true;
if ( $backed_up_file && $backed_up_file !== $backup_file && dirname( $backed_up_file ) === dirname( $backup_file ) ) {
$was_backed_up = rename( $backed_up_file, $backup_file );
}
// Backup the image.
if ( $was_backed_up ) {
$this->add_to_image_backup_sizes( $attachment_id, $backup_file );
}
return $was_backed_up;
}
/**
* Get the backup file from the meta.
*
* @since 3.9.6
*
* @param int $id Image ID.
* @param string $file_path Current file path.
*
* @return bool|null Backup file or false|null if the image doesn't exist.
*/
public function get_backup_file( $id, $file_path = false ) {
if ( empty( $id ) ) {
return null;
}
if ( empty( $file_path ) ) {
// Get unfiltered path file.
$file_path = Helper::get_attached_file( $id, 'original' );
// If the file path is still empty, nothing to check here.
if ( empty( $file_path ) ) {
return null;
}
}
// Initial result.
$backup_file = false;
// Try to get the backup file from _wp_attachment_backup_sizes.
$backup_sizes = $this->get_backup_sizes( $id );
// Check if we have backup file from the metadata.
if ( $backup_sizes ) {
// Try to get the original file first.
if ( isset( $backup_sizes[ $this->backup_key ]['file'] ) ) {
$original_file = str_replace( wp_basename( $file_path ), wp_basename( $backup_sizes[ $this->backup_key ]['file'] ), $file_path );
if ( Helper::file_exists( $original_file, $id ) ) {
$backup_file = $original_file;
}
}
// Try to check it from legacy original file or from the resized PNG file.
if ( ! $backup_file ) {
// If we don't have the original backup path in backup sizes, check for legacy original file path. It's for old version < V.2.7.0.
$original_file = get_post_meta( $id, 'wp-smush-original_file', true );
if ( ! empty( $original_file ) ) {
// For old version < v.2.7.0, we are saving meta['file'] or _wp_attached_file.
$original_file = Helper::original_file( $original_file );
if ( Helper::file_exists( $original_file, $id ) ) {
$backup_file = $original_file;
// As we don't use this meta key so save it as a full backup file and delete the old metadata.
WP_Smush::get_instance()->core()->mod->backup->add_to_image_backup_sizes( $id, $backup_file );
delete_post_meta( $id, 'wp-smush-original_file' );
}
}
// Check the backup file from resized PNG file.
if ( ! $backup_file && isset( $backup_sizes['smush_png_path']['file'] ) ) {
$original_file = str_replace( wp_basename( $file_path ), wp_basename( $backup_sizes['smush_png_path']['file'] ), $file_path );
if ( Helper::file_exists( $original_file, $id ) ) {
$backup_file = $original_file;
}
}
}
}
return $backup_file;
}
/**
* Restore the image and its sizes from backup
*
* @param string $attachment_id Attachment ID.
* @param bool $resp Send JSON response or not.
*
* @return bool
*/
public function restore_image( $attachment_id = '', $resp = true ) {
// If no attachment id is provided, check $_POST variable for attachment_id.
if ( empty( $attachment_id ) ) {
// Check Empty fields.
if ( empty( $_POST['attachment_id'] ) || empty( $_POST['_nonce'] ) ) {
wp_send_json_error(
array(
'error_msg' => esc_html__( 'Error in processing restore action, fields empty.', 'wp-smushit' ),
)
);
}
$nonce_value = filter_input( INPUT_POST, '_nonce', FILTER_SANITIZE_SPECIAL_CHARS );
$attachment_id = filter_input( INPUT_POST, 'attachment_id', FILTER_SANITIZE_NUMBER_INT );
if ( ! wp_verify_nonce( $nonce_value, "wp-smush-restore-$attachment_id" ) ) {
wp_send_json_error(
array(
'error_msg' => esc_html__( 'Image not restored, nonce verification failed.', 'wp-smushit' ),
)
);
}
// Check capability.
if ( ! Helper::is_user_allowed( 'upload_files' ) ) {
wp_send_json_error(
array(
'error_msg' => esc_html__( "You don't have permission to work with uploaded files.", 'wp-smushit' ),
)
);
}
}
$attachment_id = (int) $attachment_id;
$mod = WP_Smush::get_instance()->core()->mod;
// Set an option to avoid the smush-restore-smush loop.
set_transient( 'wp-smush-restore-' . $attachment_id, 1, HOUR_IN_SECONDS );
/**
* Delete WebP.
*
* Run WebP::delete_images always even when the module is deactivated.
*
* @since 3.8.0
*/
$mod->webp->delete_images( $attachment_id );
// Restore Full size -> get other image sizes -> restore other images.
// Get the Original Path, supported S3.
$file_path = Helper::get_attached_file( $attachment_id, 'original' );
// Store the restore success/failure for full size image.
$restored = false;
// Retrieve backup file.
$backup_full_path = $this->get_backup_file( $attachment_id, $file_path );
// Is restoring the PNG which is converted to JPG or not.
$restore_png = false;
/**
* Fires before restoring a file.
*
* @since 3.9.6
*
* @param string|false $backup_full_path Full backup path.
* @param int $attachment_id Attachment id.
* @param string $file_path Original unfiltered file path.
*
* @hooked Smush\Core\Integrations\s3::maybe_download_file()
*/
do_action( 'wp_smush_before_restore_backup', $backup_full_path, $attachment_id, $file_path );
// Finally, if we have the backup path, perform the restore operation.
if ( ! empty( $backup_full_path ) ) {
// If the backup file is the same as the main file, we only need to re-generate the metadata.
if ( $backup_full_path === $file_path ) {
$restored = true;
} else {
// Is real backup file or .bak file.
$is_real_filename = false === strpos( $backup_full_path, '.bak' );
$restore_png = Helper::get_file_ext( trim( $backup_full_path ), 'png' ) && ! Helper::get_file_ext( $file_path, 'png' );
if ( $restore_png ) {
// Restore PNG full size.
$org_backup_full_path = $backup_full_path;
if ( ! $is_real_filename ) {
// Try to get a unique file name.
$dirname = dirname( $backup_full_path );
$new_file_name = wp_unique_filename( $dirname, wp_basename( str_replace( '.bak', '', $backup_full_path ) ) );
$new_png_file = path_join( $dirname, $new_file_name );
// Restore PNG full size.
$restored = copy( $backup_full_path, $new_png_file );
if ( $restored ) {
// Assign the new PNG file to the backup file.
$backup_full_path = $new_png_file;
}
} else {
$restored = true;
}
// Restore all other image sizes.
if ( $restored ) {
$metadata = $this->restore_png( $attachment_id, $backup_full_path, $file_path );
$restored = ! empty( $metadata );
if ( $restored && ! $is_real_filename ) {
// Reset the backup file to delete it later.
$backup_full_path = $org_backup_full_path;
}
}
} else {
// If file exists, corresponding to our backup path - restore.
if ( ! $is_real_filename ) {
$restored = copy( $backup_full_path, $file_path );
} else {
$restored = true;
}
}
// Remove the backup, if we were able to restore the image.
if ( $restored ) {
// Remove our backup file.
$this->remove_from_backup_sizes( $attachment_id );
/**
* Delete our backup file if it's .bak file, we will try to backup later when running Smush.
*/
if ( ! $is_real_filename ) {
// It will also delete file from the cloud, e.g. S3.
Helper::delete_permanently( array( $this->backup_key => $backup_full_path ), $attachment_id, false );
}
}
}
} else {
Helper::logger()->backup()->warning( sprintf( 'Backup file [%s(%d)] does not exist.', Helper::clean_file_path( $backup_full_path ), $attachment_id ) );
}
/**
* Regenerate thumbnails
*
* All this is handled in self::restore_png().
*/
if ( $restored ) {
if ( ! $restore_png ) {
// Generate all other image size, and update attachment metadata.
$metadata = wp_generate_attachment_metadata( $attachment_id, $file_path );
}
// Update metadata to db if it was successfully generated.
if ( ! empty( $metadata ) && ! is_wp_error( $metadata ) ) {
Helper::wp_update_attachment_metadata( $attachment_id, $metadata );
} else {
Helper::logger()->backup()->warning( sprintf( 'Meta file [%s(%d)] is empty.', Helper::clean_file_path( $file_path ), $attachment_id ) );
}
}
/**
* Fires before restoring a file.
*
* @since 3.9.6
*
* @param bool $restored Restore status.
* @param string|false $backup_full_path Full backup path.
* @param int $attachment_id Attachment id.
* @param string $file_path Original unfiltered file path.
*/
do_action( 'wp_smush_after_restore_backup', $restored, $backup_full_path, $attachment_id, $file_path );
// If any of the image is restored, we count it as success.
if ( $restored ) {
// Remove the Meta, And send json success.
delete_post_meta( $attachment_id, Smush::$smushed_meta_key );
// Remove PNG to JPG conversion savings.
delete_post_meta( $attachment_id, 'wp-smush-pngjpg_savings' );
// Remove Original File.
delete_post_meta( $attachment_id, 'wp-smush-original_file' );
// Delete resize savings.
delete_post_meta( $attachment_id, 'wp-smush-resize_savings' );
// Remove lossy flag.
delete_post_meta( $attachment_id, 'wp-smush-lossy' );
// Clear backups cache.
wp_cache_delete( 'images_with_backups', 'wp-smush' );
Core::remove_from_smushed_list( $attachment_id );
// Get the Button html without wrapper.
$button_html = WP_Smush::get_instance()->library()->generate_markup( $attachment_id );
// Release the attachment after restoring.
delete_transient( 'wp-smush-restore-' . $attachment_id );
if ( ! $resp ) {
return true;
}
$size = file_exists( $file_path ) ? filesize( $file_path ) : 0;
if ( $size > 0 ) {
$update_size = size_format( $size ); // Used in js to update image stat.
}
wp_send_json_success(
array(
'stats' => $button_html,
'new_size' => isset( $update_size ) ? $update_size : 0,
)
);
}
// Release the attachment after restoring.
delete_transient( 'wp-smush-restore-' . $attachment_id );
if ( $resp ) {
wp_send_json_error( array( 'error_msg' => esc_html__( 'Unable to restore image', 'wp-smushit' ) ) );
}
return false;
}
/**
* Restore PNG.
*
* @param int $attachment_id Attachment ID.
* @param string $backup_file_path Full backup file, the result of self::get_backup_file().
* @param string $file_path File path.
*
* @since 3.9.10 Moved wp_update_attachment_metadata into self::restore_image() after deleting the backup file,
* in order to support S3 - @see SMUSH-1141.
*
* @return bool|array
*/
private function restore_png( $attachment_id, $backup_file_path, $file_path ) {
if ( empty( $attachment_id ) || empty( $backup_file_path ) || empty( $file_path ) ) {
return false;
}
$meta = array();
// Else get the Attachment details.
/**
* For Full Size
* 1. Get the original file path
* 2. Update the attachment metadata and all other meta details
* 3. Delete the JPEG
* 4. And we're done
* 5. Add an action after updating the URLs, that'd allow the users to perform an additional search, replace action
*/
if ( file_exists( $backup_file_path ) ) {
$mod = WP_Smush::get_instance()->core()->mod;
// Update the path details in meta and attached file, replace the image.
$meta = $mod->png2jpg->update_image_path( $attachment_id, $file_path, $backup_file_path, $meta, 'full', 'restore' );
$files_to_remove = array();
// Unlink JPG after updating attached file.
if ( ! empty( $meta['file'] ) && wp_basename( $backup_file_path ) === wp_basename( $meta['file'] ) ) {
/**
* Note, we use size key smush-png2jpg-full for PNG2JPG file to support S3 private media,
* to remove converted JPG file after restoring in private folder.
*
* @see Smush\Core\Integrations\S3::get_object_key()
*/
$files_to_remove['smush-png2jpg-full'] = $file_path;
}
$jpg_meta = wp_get_attachment_metadata( $attachment_id );
foreach ( $jpg_meta['sizes'] as $size_key => $size_data ) {
$size_path = str_replace( wp_basename( $backup_file_path ), wp_basename( $size_data['file'] ), $backup_file_path );
// Add to delete the thumbnails jpg.
$files_to_remove[ $size_key ] = $size_path;
}
// Re-generate metadata for PNG file.
$metadata = wp_generate_attachment_metadata( $attachment_id, $backup_file_path );
// Perform an action after the image URL is updated in post content.
do_action( 'wp_smush_image_url_updated', $attachment_id, $file_path, $backup_file_path );
} else {
Helper::logger()->backup()->warning( sprintf( 'Backup file [%s(%d)] does not exist.', Helper::clean_file_path( $backup_file_path ), $attachment_id ) );
}
if ( ! empty( $metadata ) ) {
// Delete jpg files, we also try to delete these files on cloud, e.g S3.
Helper::delete_permanently( $files_to_remove, $attachment_id, false );
return $metadata;
} else {
Helper::logger()->backup()->warning( sprintf( 'Meta file [%s(%d)] is empty.', Helper::clean_file_path( $backup_file_path ), $attachment_id ) );
}
return false;
}
/**
* Remove a specific backup key from the backup size array.
*
* @param int $attachment_id Attachment ID.
*/
private function remove_from_backup_sizes( $attachment_id ) {
// Get backup sizes.
$backup_sizes = $this->get_backup_sizes( $attachment_id );
// If we don't have any backup sizes list or if the particular key is not set, return.
if ( empty( $backup_sizes ) || ! isset( $backup_sizes[ $this->backup_key ] ) ) {
return;
}
unset( $backup_sizes[ $this->backup_key ] );
if ( empty( $backup_sizes ) ) {
delete_post_meta( $attachment_id, '_wp_attachment_backup_sizes' );
} else {
update_post_meta( $attachment_id, '_wp_attachment_backup_sizes', $backup_sizes );
}
}
/**
* Get the attachments that can be restored.
*
* @since 3.6.0 Changed from private to public.
*
* @return array Array of attachments IDs.
*/
public function get_attachments_with_backups() {
$images = wp_cache_get( 'images_with_backups', 'wp-smush' );
if ( ! $images ) {
global $wpdb;
$images = $wpdb->get_col(
"SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key='_wp_attachment_backup_sizes' AND (`meta_value` LIKE '%smush-full%' OR `meta_value` LIKE '%smush_png_path%')"
); // Db call ok.
if ( $images ) {
wp_cache_set( 'images_with_backups', $images, 'wp-smush' );
}
}
return $images;
}
/**
* Get the number of attachments that can be restored.
*
* @since 3.2.2
*/
public function get_image_count() {
check_ajax_referer( 'smush_bulk_restore' );
// Check for permission.
if ( ! Helper::is_user_allowed( 'manage_options' ) ) {
wp_die( esc_html__( 'Unauthorized', 'wp-smushit' ), 403 );
}
wp_send_json_success(
array(
'items' => $this->get_attachments_with_backups(),
)
);
}
/**
* Bulk restore images from the modal.
*
* @since 3.2.2
*/
public function restore_step() {
check_ajax_referer( 'smush_bulk_restore' );
// Check for permission.
if ( ! Helper::is_user_allowed( 'manage_options' ) ) {
wp_die( esc_html__( 'Unauthorized', 'wp-smushit' ), 403 );
}
$id = filter_input( INPUT_POST, 'item', FILTER_SANITIZE_NUMBER_INT, FILTER_NULL_ON_FAILURE );
$status = $id && $this->restore_image( $id, false );
$original_meta = wp_get_attachment_metadata( $id, true );
// Try to get the file name from path.
$file_name = explode( '/', $original_meta['file'] );
$file_name = is_array( $file_name ) ? array_pop( $file_name ) : $original_meta['file'];
wp_send_json_success(
array(
'success' => $status,
'src' => $file_name ? $file_name : __( 'Error getting file name', 'wp-smushit' ),
'thumb' => wp_get_attachment_image( $id ),
'link' => Helper::get_image_media_link( $id, $file_name, true ),
)
);
}
/**
* Returns the backup path for attachment
*
* @param string $attachment_path Attachment path.
*
* @return string
*/
public function get_image_backup_path( $attachment_path ) {
if ( empty( $attachment_path ) ) {
return '';
}
$path = pathinfo( $attachment_path );
if ( empty( $path['extension'] ) ) {
return '';
}
return trailingslashit( $path['dirname'] ) . $path['filename'] . '.bak.' . $path['extension'];
}
/**
* Clear up all the backup files for the image while deleting the image.
*
* @since 3.9.6
* Note, we only call this method while deleting the image, as it will delete
* .bak file and might be the original file too.
*
* Note, for the old version < 3.9.6 we also save all PNG files (original file and thumbnails)
* when the site doesn't compress original file.
* But it's not safe to remove them if the user add another image with the same PNG file name, and didn't convert it.
* So we still leave them there.
*
* @param int $attachment_id Attachment ID.
*/
public function delete_backup_files( $attachment_id ) {
$smush_meta = get_post_meta( $attachment_id, Smush::$smushed_meta_key, true );
if ( empty( $smush_meta ) ) {
return;
}
// Save list files to remove.
$files_to_remove = array();
$unfiltered = false;
$file_path = get_attached_file( $attachment_id, false );
// We only work with the real file path, not cloud URL like S3.
if ( false === strpos( $file_path, ABSPATH ) ) {
$unfiltered = true;
$file_path = get_attached_file( $attachment_id, true );
}
// Remove from the cache.
wp_cache_delete( 'images_with_backups', 'wp-smush' );
/**
* We only remove the backup file from the metadata,
* keep the backup file from 3rd-party.
*/
$backup_path = null;// Reset backup file.
$backup_sizes = $this->get_backup_sizes( $attachment_id );
if ( isset( $backup_sizes[ $this->backup_key ]['file'] ) ) {
$backup_path = str_replace( wp_basename( $file_path ), wp_basename( $backup_sizes[ $this->backup_key ]['file'] ), $file_path );
// Add to remove the backup file.
$files_to_remove[ $this->backup_key ] = $backup_path;
}
// Check the backup file from resized PNG file (< 3.9.6).
if ( isset( $backup_sizes['smush_png_path']['file'] ) ) {
$backup_path = str_replace( wp_basename( $file_path ), wp_basename( $backup_sizes['smush_png_path']['file'] ), $file_path );
// Add to remove the backup file.
$files_to_remove['smush_png_path'] = $backup_path;
}
if ( ! $backup_path ) {
// Check for legacy original file path. It's for old version < V.2.7.0.
$original_file = get_post_meta( $attachment_id, 'wp-smush-original_file', true );
if ( ! empty( $original_file ) ) {
// For old version < v.2.7.0, we are saving meta['file'] or _wp_attached_file.
$backup_path = Helper::original_file( $original_file );
// Add to remove the backup file.
$files_to_remove[] = $backup_path;
}
}
// Check meta for rest of the sizes.
$meta = wp_get_attachment_metadata( $attachment_id, $unfiltered );
if ( empty( $meta ) || empty( $meta['sizes'] ) ) {
Helper::logger()->backup()->info( sprintf( 'Empty meta sizes [%s(%d)]', $file_path, $attachment_id ) );
return;
}
foreach ( $meta['sizes'] as $size ) {
if ( empty( $size['file'] ) ) {
continue;
}
// Image path and backup path.
$image_size_path = path_join( dirname( $file_path ), $size['file'] );
$image_backup_path = $this->get_image_backup_path( $image_size_path );
// Add to remove the backup file.
$files_to_remove[] = $image_backup_path;
}
// We also try to delete this file on cloud, e.g. S3.
Helper::delete_permanently( $files_to_remove, $attachment_id, false );
}
}