WP Offload S3 Lite - Version 2.3

Version Description

This is a major upgrade that switches to using a custom table for storing data about offloaded Media Library items. Once upgraded you will not be able to downgrade without restoring data from a backup.

Download this release

Release Info

Developer deliciousbrains
Plugin Icon 128x128 WP Offload S3 Lite
Version 2.3
Comparing to
See all releases

Code changes from version 2.2.1 to 2.3

README.md CHANGED
@@ -2,9 +2,9 @@
2
**Contributors:** bradt, deliciousbrains, ianmjones
3
**Tags:** uploads, amazon, s3, amazon s3, digitalocean, digitalocean spaces, google cloud storage, gcs, mirror, admin, media, cdn, cloudfront
4
**Requires at least:** 4.9
5
- **Tested up to:** 5.2
6
**Requires PHP:** 5.5
7
- **Stable tag:** 2.2.1
8
**License:** GPLv3
9
10
Copies files to Amazon S3, DigitalOcean Spaces or Google Cloud Storage as they are uploaded to the Media Library. Optionally configure Amazon CloudFront or another CDN for faster delivery.
@@ -75,6 +75,9 @@ If you upgrade to the pro version of [WP Offload Media](https://deliciousbrains.
75
76
## Upgrade Notice ##
77
78
### 2.0 ###
79
This is a major upgrade that introduces support for DigitalOcean Spaces, renames the plugin to WP Offload Media Lite, and coincidentally upgrades some of its database settings. You may not be able to downgrade to WP Offload S3 Lite 1.x after upgrading to WP Offload Media Lite 2.0+.
80
@@ -86,6 +89,18 @@ This version requires PHP 5.3.3+ and the Amazon Web Services plugin
86
87
## Changelog ##
88
89
### WP Offload Media Lite 2.2.1 - 2019-07-18 ###
90
* Improvement: Menu option and settings page title now include "Lite"
91
* Improvement: Remove Files From Server option now warns about media backups when switched on
2
**Contributors:** bradt, deliciousbrains, ianmjones
3
**Tags:** uploads, amazon, s3, amazon s3, digitalocean, digitalocean spaces, google cloud storage, gcs, mirror, admin, media, cdn, cloudfront
4
**Requires at least:** 4.9
5
+ **Tested up to:** 5.3
6
**Requires PHP:** 5.5
7
+ **Stable tag:** 2.3
8
**License:** GPLv3
9
10
Copies files to Amazon S3, DigitalOcean Spaces or Google Cloud Storage as they are uploaded to the Media Library. Optionally configure Amazon CloudFront or another CDN for faster delivery.
75
76
## Upgrade Notice ##
77
78
+ ### 2.3 ###
79
+ This is a major upgrade that switches to using a custom table for storing data about offloaded Media Library items. Once upgraded you will not be able to downgrade without restoring data from a backup.
80
+
81
### 2.0 ###
82
This is a major upgrade that introduces support for DigitalOcean Spaces, renames the plugin to WP Offload Media Lite, and coincidentally upgrades some of its database settings. You may not be able to downgrade to WP Offload S3 Lite 1.x after upgrading to WP Offload Media Lite 2.0+.
83
89
90
## Changelog ##
91
92
+ ### WP Offload Media Lite 2.3 - 2019-11-12 ###
93
+ * [Release Summary Blog Post](https://deliciousbrains.com/wp-offload-media-2-3-released/?utm_campaign=changelogs&utm_source=wordpress.org&utm_medium=free%2Bplugin%2Blisting)
94
+ * New: Upgrade routine to migrate offload data to custom table
95
+ * New: Support for changed Media Library upload process introduced with WordPress 5.3
96
+ * New: Support for new "-scaled" and "-rotated" images introduced with WordPress 5.3
97
+ * New: Support for customizer changes introduced with WordPress 5.3
98
+ * New: Offload new "original_image" file introduced with WordPress 5.3
99
+ * Improvement: Performance boost during both page display and save
100
+ * Improvement: Better detection of offloaded media URLs during page display
101
+ * Bug fix: New Media Library upload given same local file name as offloaded and removed file after Remove Files From Server turned off
102
+ * Bug fix: PHP message: PHP Deprecated: strpos(): Non-string needles will be interpreted as strings in the future
103
+
104
### WP Offload Media Lite 2.2.1 - 2019-07-18 ###
105
* Improvement: Menu option and settings page title now include "Lite"
106
* Improvement: Remove Files From Server option now warns about media backups when switched on
classes/amazon-s3-and-cloudfront.php CHANGED
@@ -1,5 +1,6 @@
1
<?php
2
3
use DeliciousBrains\WP_Offload_Media\Providers\AWS_Provider;
4
use DeliciousBrains\WP_Offload_Media\Providers\DigitalOcean_Provider;
5
use DeliciousBrains\WP_Offload_Media\Providers\GCP_Provider;
@@ -9,6 +10,7 @@ use DeliciousBrains\WP_Offload_Media\Upgrades\Upgrade_Content_Replace_URLs;
9
use DeliciousBrains\WP_Offload_Media\Upgrades\Upgrade_EDD_Replace_URLs;
10
use DeliciousBrains\WP_Offload_Media\Upgrades\Upgrade_File_Sizes;
11
use DeliciousBrains\WP_Offload_Media\Upgrades\Upgrade_Filter_Post_Excerpt;
12
use DeliciousBrains\WP_Offload_Media\Upgrades\Upgrade_Meta_WP_Error;
13
use DeliciousBrains\WP_Offload_Media\Upgrades\Upgrade_Region_Meta;
14
use DeliciousBrains\WP_Offload_Media\Upgrades\Upgrade_WPOS3_To_AS3CF;
@@ -107,7 +109,7 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
107
'WPOS3_SETTINGS',
108
);
109
110
- const LATEST_UPGRADE_ROUTINE = 7;
111
112
/**
113
* @param string $plugin_file_path
@@ -142,6 +144,8 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
142
GCP_Provider::get_provider_key_name() => 'DeliciousBrains\WP_Offload_Media\Providers\GCP_Provider',
143
);
144
145
$this->set_provider();
146
147
// Bundled SDK may require AWS setup before data migrations.
@@ -154,6 +158,7 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
154
new Upgrade_EDD_Replace_URLs( $this );
155
new Upgrade_Filter_Post_Excerpt( $this );
156
new Upgrade_WPOS3_To_AS3CF( $this );
157
158
// Plugin setup
159
add_action( 'admin_menu', array( $this, 'admin_menu' ) );
@@ -306,13 +311,22 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
306
return apply_filters( 'as3cf_settings_menu_title', $this->plugin_menu_title );
307
}
308
309
/**
310
* Get the plugin prefix in slug format, ie. replace underscores with hyphens
311
*
312
* @return string
313
*/
314
- function get_plugin_prefix_slug() {
315
- return str_replace( '_', '-', $this->plugin_prefix );
316
}
317
318
/**
@@ -338,7 +352,7 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
338
'key' => $key,
339
'disabled' => false,
340
'disabled_attr' => '',
341
- 'tr_class' => str_replace( '_', '-', $this->plugin_prefix . '-' . $key . '-container' ),
342
'setting_msg' => '',
343
'is_defined' => false,
344
);
@@ -959,23 +973,38 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
959
/**
960
* Removes an attachment's files from provider.
961
*
962
- * @param int $post_id
963
- * @param array $provider_object
964
- * @param bool $remove_backup_sizes remove previous edited image versions
965
- * @param bool $log_error
966
- * @param bool $return_on_error
967
- * @param bool $force_new_provider_client if we are deleting in bulk, force new provider client
968
- * to cope with possible different regions
969
- */
970
- function remove_attachment_files_from_provider( $post_id, $provider_object, $remove_backup_sizes = true, $log_error = false, $return_on_error = false, $force_new_provider_client = false ) {
971
- $prefix = $this->normalize_object_prefix( $provider_object['key'] );
972
- $bucket = $provider_object['bucket'];
973
- $region = $this->get_provider_object_region( $provider_object );
974
- $paths = AS3CF_Utils::get_attachment_file_paths( $post_id, false, false, $remove_backup_sizes );
975
- $paths = apply_filters( 'as3cf_remove_attachment_paths', $paths, $post_id, $provider_object, $remove_backup_sizes );
976
977
- if ( is_wp_error( $region ) ) {
978
- $region = '';
979
}
980
981
$objects_to_remove = array();
@@ -987,7 +1016,7 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
987
}
988
989
// finally delete the objects from provider
990
- $this->delete_objects( $region, $bucket, $objects_to_remove, $log_error, $return_on_error, $force_new_provider_client );
991
}
992
993
/**
@@ -1002,7 +1031,9 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
1002
return;
1003
}
1004
1005
- if ( ! ( $provider_object = $this->get_attachment_provider_info( $post_id ) ) ) {
1006
return;
1007
}
1008
@@ -1010,9 +1041,9 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
1010
return;
1011
}
1012
1013
- $this->remove_attachment_files_from_provider( $post_id, $provider_object, true, true, true, $force_new_provider_client );
1014
1015
- delete_post_meta( $post_id, 'amazonS3_info' );
1016
}
1017
1018
/**
@@ -1030,25 +1061,36 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
1030
return $data;
1031
}
1032
1033
- if ( ! ( $old_provider_object = $this->get_attachment_provider_info( $post_id ) ) && ! $this->get_setting( 'copy-to-s3' ) ) {
1034
// abort if not already uploaded to provider and the copy setting is off
1035
return $data;
1036
}
1037
1038
// allow provider upload to be cancelled for any reason
1039
- $pre = apply_filters( 'as3cf_pre_update_attachment_metadata', false, $data, $post_id, $old_provider_object );
1040
if ( false !== $pre ) {
1041
return $data;
1042
}
1043
1044
// upload attachment to provider
1045
- $provider_meta = $this->upload_attachment( $post_id, $data );
1046
1047
- if ( is_wp_error( $provider_meta ) ) {
1048
return $data;
1049
}
1050
1051
- return $provider_meta;
1052
}
1053
1054
/**
@@ -1061,10 +1103,12 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
1061
* to cope with possible different regions
1062
* @param bool $remove_local_files
1063
*
1064
- * @return array|WP_Error $provider_object|$meta If meta is supplied, return it. Else return provider meta
1065
* @throws Exception
1066
*/
1067
public function upload_attachment( $post_id, $data = null, $file_path = null, $force_new_provider_client = false, $remove_local_files = true ) {
1068
$return_metadata = null;
1069
if ( is_null( $data ) ) {
1070
$data = wp_get_attachment_metadata( $post_id, true );
@@ -1092,15 +1136,82 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
1092
$file_path = get_attached_file( $post_id, true );
1093
}
1094
1095
- // Check file exists locally before attempting upload
1096
- if ( ! file_exists( $file_path ) ) {
1097
- $error_msg = sprintf( __( 'File %s does not exist', 'amazon-s3-and-cloudfront' ), $file_path );
1098
1099
return $this->return_upload_error( $error_msg, $return_metadata );
1100
}
1101
1102
// Get original file's stats.
1103
- $filesize = filesize( $file_path );
1104
$file_name = wp_basename( $file_path );
1105
$type = get_post_mime_type( $post_id );
1106
$allowed_types = $this->get_allowed_mime_types();
@@ -1115,23 +1226,25 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
1115
$acl = $this->get_provider()->get_default_acl();
1116
1117
// check the attachment already exists in provider, eg. edit or restore image
1118
- if ( ( $old_provider_object = $this->get_attachment_provider_info( $post_id ) ) ) {
1119
// Must be offloaded to same provider as currently configured.
1120
if ( ! $this->is_attachment_served_by_provider( $post_id, true ) ) {
1121
return $this->return_upload_error( __( 'Already offloaded to a different provider', 'amazon-s3-and-cloudfront' ), $return_metadata );
1122
}
1123
1124
- // use existing non default ACL if attachment already exists
1125
- if ( isset( $old_provider_object['acl'] ) ) {
1126
- $acl = $old_provider_object['acl'];
1127
}
1128
1129
// use existing prefix
1130
- $prefix = $this->normalize_object_prefix( $old_provider_object['key'] );
1131
// use existing bucket
1132
- $bucket = $old_provider_object['bucket'];
1133
// get existing region
1134
- $region = isset( $old_provider_object['region'] ) ? $old_provider_object['region'] : '';
1135
} else {
1136
// derive prefix from various settings
1137
$time = $this->get_attachment_folder_year_month( $post_id, $data );
@@ -1143,6 +1256,9 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
1143
if ( is_wp_error( $region ) ) {
1144
$region = '';
1145
}
1146
}
1147
1148
$acl = apply_filters( 'wps3_upload_acl', $acl, $type, $data, $post_id, $this ); // Old naming convention, will be deprecated soon
@@ -1167,43 +1283,43 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
1167
$args['ContentEncoding'] = 'gzip';
1168
}
1169
1170
- $image_size = wp_attachment_is_image( $post_id ) ? 'full' : '';
1171
- $args = apply_filters( 'as3cf_object_meta', $args, $post_id, $image_size, false );
1172
- $provider_object = array(
1173
- 'provider' => $this->get_provider()->get_provider_key_name(),
1174
- 'region' => $bucket !== $args['Bucket'] ? $this->get_bucket_region( $args['Bucket'], true ) : $region,
1175
- 'bucket' => $args['Bucket'],
1176
- 'key' => $args['Key'],
1177
- 'acl' => $args['ACL'],
1178
- );
1179
1180
- // Do not store object ACL if set to the default value.
1181
- if ( $provider_object['acl'] === $this->get_provider()->get_default_acl() ) {
1182
- unset( $provider_object['acl'] );
1183
- }
1184
1185
- do_action( 'as3cf_upload_attachment_pre_remove', $post_id, $provider_object, $prefix, $args );
1186
1187
$files_to_remove = array();
1188
1189
- $provider_client = $this->get_provider_client( $provider_object['region'], $force_new_provider_client );
1190
1191
- try {
1192
- $provider_client->upload_object( $args );
1193
- $files_to_remove[] = $file_path;
1194
- } catch ( Exception $e ) {
1195
- $error_msg = sprintf( __( 'Error offloading %s to provider: %s', 'amazon-s3-and-cloudfront' ), $file_path, $e->getMessage() );
1196
1197
- return $this->return_upload_error( $error_msg, $return_metadata );
1198
- }
1199
1200
- delete_post_meta( $post_id, 'amazonS3_info' );
1201
1202
- add_post_meta( $post_id, 'amazonS3_info', $provider_object );
1203
1204
- $file_paths = AS3CF_Utils::get_attachment_file_paths( $post_id, false, $data );
1205
- $additional_images = array();
1206
- $provider_object_sizes = array();
1207
1208
foreach ( $file_paths as $size => $file_path ) {
1209
if ( ! in_array( $file_path, $files_to_remove ) ) {
@@ -1216,8 +1332,8 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
1216
'ContentType' => $this->get_mime_type( $file_path ),
1217
);
1218
1219
- if ( $this->get_provider()->get_default_acl() !== $acl ) {
1220
- $provider_object_sizes[ $size ]['acl'] = $acl;
1221
}
1222
}
1223
}
@@ -1225,42 +1341,72 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
1225
$upload_errors = array();
1226
1227
foreach ( $additional_images as $size => $image ) {
1228
$args = apply_filters( 'as3cf_object_meta', array_merge( $args, $image ), $post_id, $size, false );
1229
1230
if ( ! file_exists( $args['SourceFile'] ) ) {
1231
- $upload_errors[] = $this->return_upload_error( sprintf( __( 'File %s does not exist', 'amazon-s3-and-cloudfront' ), $args['SourceFile'] ) );
1232
continue;
1233
}
1234
1235
try {
1236
$provider_client->upload_object( $args );
1237
- $files_to_remove[] = $image['SourceFile'];
1238
} catch ( Exception $e ) {
1239
$upload_errors[] = $this->return_upload_error( sprintf( __( 'Error offloading %s to provider: %s', 'amazon-s3-and-cloudfront' ), $args['SourceFile'], $e->getMessage() ) );
1240
}
1241
}
1242
1243
$remove_local_files_setting = $this->get_setting( 'remove-local-file' );
1244
1245
- if ( $remove_local_files ) {
1246
- if ( $remove_local_files_setting ) {
1247
- // Allow other functions to remove files after they have processed
1248
- $files_to_remove = apply_filters( 'as3cf_upload_attachment_local_files_to_remove', $files_to_remove, $post_id, $file_path );
1249
1250
- // Remove duplicates
1251
- $files_to_remove = array_unique( $files_to_remove );
1252
1253
- // Delete the files and record original file's size before removal.
1254
- $this->remove_local_files( $files_to_remove, $post_id );
1255
1256
- // Store filesize in the attachment meta data for use by WP
1257
- if ( 0 < $filesize ) {
1258
- $data['filesize'] = $filesize;
1259
1260
- if ( is_null( $return_metadata ) ) {
1261
- // Update metadata with filesize
1262
- update_post_meta( $post_id, '_wp_attachment_metadata', $data );
1263
- }
1264
}
1265
}
1266
}
@@ -1270,16 +1416,36 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
1270
$data = $this->maybe_cleanup_filesize_metadata( $post_id, $data, empty( $return_metadata ) );
1271
}
1272
1273
- if ( ! empty( $provider_object_sizes ) ) {
1274
- // Additional image sizes have custom ACLs, update meta
1275
- $provider_object['sizes'] = $provider_object_sizes;
1276
- update_post_meta( $post_id, 'amazonS3_info', $provider_object );
1277
}
1278
1279
// Keep track of attachments uploaded by this instance.
1280
$this->uploaded_post_ids[] = $post_id;
1281
1282
- do_action( 'as3cf_post_upload_attachment', $post_id, $provider_object );
1283
1284
if ( $upload_errors ) {
1285
return $this->consolidate_upload_errors( $upload_errors );
@@ -1290,7 +1456,7 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
1290
return $data;
1291
}
1292
1293
- return $provider_object;
1294
}
1295
1296
/**
@@ -1373,11 +1539,14 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
1373
/**
1374
* Remove files from the local site, recording total filesize in meta if attachment ID given.
1375
*
1376
- * @param array $file_paths array of files to remove
1377
- * @param int $attachment_id
1378
*/
1379
- function remove_local_files( $file_paths, $attachment_id = 0 ) {
1380
- $filesize_total = 0;
1381
1382
foreach ( $file_paths as $index => $path ) {
1383
if ( ! empty( $attachment_id ) && is_int( $attachment_id ) ) {
@@ -1405,7 +1574,7 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
1405
}
1406
1407
// If we were able to sum up file sizes for an attachment, record it.
1408
- if ( $filesize_total > 0 ) {
1409
update_post_meta( $attachment_id, 'as3cf_filesize_total', $filesize_total );
1410
}
1411
}
@@ -1526,7 +1695,7 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
1526
* @return string
1527
*/
1528
public function filter_unique_filename( $filename, $ext, $dir, $post_id = null ) {
1529
- if ( ! $this->get_setting( 'copy-to-s3' ) || ! $this->is_plugin_setup( true ) ) {
1530
return $filename;
1531
}
1532
@@ -1619,6 +1788,7 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
1619
}
1620
$file = $path . $filename;
1621
1622
$sql = $wpdb->prepare( "
1623
SELECT COUNT(*)
1624
FROM $wpdb->postmeta
@@ -1626,7 +1796,16 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
1626
AND meta_value = %s
1627
", '_wp_attached_file', $file );
1628
1629
- return (bool) $wpdb->get_var( $sql );
1630
}
1631
1632
/**
@@ -1664,44 +1843,24 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
1664
*/
1665
function generate_unique_filename( $name, $ext, $time ) {
1666
$count = 1;
1667
- $filename = $name . $count . $ext;
1668
1669
while ( $this->does_file_exist( $filename, $time ) ) {
1670
$count++;
1671
- $filename = $name . $count . $ext;
1672
}
1673
1674
return $filename;
1675
}
1676
1677
- /**
1678
- * Get attachment provider info
1679
- *
1680
- * @param int $post_id
1681
- *
1682
- * @return mixed
1683
- */
1684
- public function get_attachment_provider_info( $post_id ) {
1685
- $provider_object = get_post_meta( $post_id, 'amazonS3_info', true );
1686
-
1687
- if ( is_array( $provider_object ) ) {
1688
- $provider_object = array_merge( array(
1689
- 'provider' => static::$default_provider,
1690
- 'region' => null,
1691
- ), $provider_object );
1692
- }
1693
-
1694
- $provider_object = apply_filters( 'as3cf_get_attachment_s3_info', $provider_object, $post_id ); // Backwards compatibility
1695
-
1696
- return apply_filters( 'as3cf_get_attachment_provider_info', $provider_object, $post_id );
1697
- }
1698
-
1699
/**
1700
* Check the plugin is correctly setup
1701
*
1702
* @param bool $with_credentials Do provider credentials need to be set up too? Defaults to false.
1703
*
1704
* @return bool
1705
*/
1706
function is_plugin_setup( $with_credentials = false ) {
1707
if ( $with_credentials && $this->get_provider()->needs_access_keys() ) {
@@ -1831,11 +1990,11 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
1831
* @return bool|mixed|WP_Error
1832
*/
1833
public function get_attachment_url( $post_id, $expires = null, $size = null, $meta = null, $headers = array(), $skip_rewrite_check = false ) {
1834
- if ( ! ( $provider_object = $this->is_attachment_served_by_provider( $post_id, $skip_rewrite_check ) ) ) {
1835
return false;
1836
}
1837
1838
- $url = $this->get_attachment_provider_url( $post_id, $provider_object, $expires, $size, $meta, $headers );
1839
1840
return apply_filters( 'as3cf_wp_get_attachment_url', $url, $post_id );
1841
}
@@ -1908,20 +2067,20 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
1908
/**
1909
* Get the provider URL for an attachment
1910
*
1911
- * @param int $post_id
1912
- * @param array $provider_object
1913
- * @param null|int $expires
1914
- * @param null|string|array $size
1915
- * @param null|array $meta
1916
- * @param array $headers
1917
*
1918
- * @return mixed|WP_Error
1919
*/
1920
- public function get_attachment_provider_url( $post_id, $provider_object, $expires = null, $size = null, $meta = null, $headers = array() ) {
1921
- // We don't use $this->get_provider_object_region() here because we don't want
1922
- // to make an AWS API call and slow down page loading
1923
- if ( isset( $provider_object['region'] ) && ( $this->get_provider()->region_required() || $this->get_provider()->get_default_region() !== $provider_object['region'] ) ) {
1924
- $region = $this->get_provider()->sanitize_region( $provider_object['region'] );
1925
} else {
1926
$region = '';
1927
}
@@ -1930,12 +2089,12 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
1930
1931
// Force use of secured URL when ACL has been set to private
1932
if ( is_null( $expires ) ) {
1933
- if ( is_null( $size ) && isset( $provider_object['acl'] ) && $this->get_provider()->get_private_acl() === $provider_object['acl'] ) {
1934
// Full size URL private
1935
$expires = self::DEFAULT_EXPIRES;
1936
}
1937
1938
- if ( ! is_null( $size ) && isset( $provider_object['sizes'][ $size ]['acl'] ) && $this->get_provider()->get_private_acl() === $provider_object['sizes'][ $size ]['acl'] ) {
1939
// Alternative size URL private
1940
$expires = self::DEFAULT_EXPIRES;
1941
}
@@ -1951,15 +2110,15 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
1951
}
1952
1953
if ( ! empty( $meta ) && isset( $meta['sizes'][ $size ]['file'] ) ) {
1954
- $size_prefix = dirname( $provider_object['key'] );
1955
$size_file_prefix = ( '.' === $size_prefix ) ? '' : $size_prefix . '/';
1956
1957
- $provider_object['key'] = $size_file_prefix . $meta['sizes'][ $size ]['file'];
1958
}
1959
}
1960
1961
$scheme = $this->get_url_scheme();
1962
- $domain = $this->get_provider()->get_url_domain( $provider_object['bucket'], $region, $expires );
1963
$base_url = $scheme . '://' . $domain;
1964
1965
if ( ! is_null( $expires ) && $this->is_plugin_setup( true ) ) {
@@ -1970,20 +2129,20 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
1970
1971
$expires = time() + apply_filters( 'as3cf_expires', $expires );
1972
$secure_url = $this->get_provider_client( $region )
1973
- ->get_object_url( $provider_object['bucket'], $provider_object['key'], $expires, $headers );
1974
1975
- return apply_filters( 'as3cf_get_attachment_secure_url', $secure_url, $provider_object, $post_id, $expires, $headers );
1976
} catch ( Exception $e ) {
1977
return new WP_Error( 'exception', $e->getMessage() );
1978
}
1979
}
1980
1981
- $provider_object['key'] = $this->maybe_update_cloudfront_path( $provider_object['key'] );
1982
1983
- $file = $this->encode_filename_in_path( $provider_object['key'] );
1984
$url = $base_url . '/' . $file;
1985
1986
- return apply_filters( 'as3cf_get_attachment_url', $url, $provider_object, $post_id, $expires, $headers );
1987
}
1988
1989
/**
@@ -1995,6 +2154,10 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
1995
* @return bool|mixed|WP_Error
1996
*/
1997
public function wp_get_attachment_url( $url, $post_id ) {
1998
$new_url = $this->get_attachment_url( $post_id );
1999
2000
if ( false === $new_url ) {
@@ -2020,7 +2183,7 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
2020
* @return string
2021
*/
2022
public function maybe_encode_get_image_tag( $html, $id, $alt, $title, $align, $size ) {
2023
- if ( ! ( $provider_object = $this->is_attachment_served_by_provider( $id ) ) ) {
2024
// Not served by provider, return
2025
return $html;
2026
}
@@ -2037,7 +2200,7 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
2037
}
2038
2039
$img_src = $matches[1];
2040
- $new_img_src = $this->maybe_sign_intermediate_size( $img_src, $id, $size, $provider_object );
2041
$new_img_src = $this->encode_filename_in_path( $new_img_src );
2042
2043
return str_replace( $img_src, $new_img_src, $html );
@@ -2054,13 +2217,13 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
2054
* @return array
2055
*/
2056
public function maybe_encode_wp_get_attachment_image_src( $image, $attachment_id, $size, $icon ) {
2057
- if ( ! ( $provider_object = $this->is_attachment_served_by_provider( $attachment_id ) ) ) {
2058
// Not served by provider, return
2059
return $image;
2060
}
2061
2062
if ( isset( $image[0] ) ) {
2063
- $url = $this->maybe_sign_intermediate_size( $image[0], $attachment_id, $size, $provider_object );
2064
$url = $this->encode_filename_in_path( $url );
2065
2066
$image[0] = $url;
@@ -2079,7 +2242,7 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
2079
* @return array
2080
*/
2081
public function maybe_encode_wp_prepare_attachment_for_js( $response, $attachment, $meta ) {
2082
- if ( ! ( $provider_object = $this->is_attachment_served_by_provider( $attachment->ID ) ) ) {
2083
// Not served by provider, return
2084
return $response;
2085
}
@@ -2090,7 +2253,7 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
2090
2091
if ( isset( $response['sizes'] ) && is_array( $response['sizes'] ) ) {
2092
foreach ( $response['sizes'] as $size => $value ) {
2093
- $url = $this->maybe_sign_intermediate_size( $value['url'], $attachment->ID, $size, $provider_object );
2094
$url = $this->encode_filename_in_path( $url );
2095
2096
$response['sizes'][ $size ]['url'] = $url;
@@ -2110,13 +2273,13 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
2110
* @return array
2111
*/
2112
public function maybe_encode_image_get_intermediate_size( $data, $post_id, $size ) {
2113
- if ( ! ( $provider_object = $this->is_attachment_served_by_provider( $post_id ) ) ) {
2114
// Not served by provider, return
2115
return $data;
2116
}
2117
2118
if ( isset( $data['url'] ) ) {
2119
- $url = $this->maybe_sign_intermediate_size( $data['url'], $post_id, $size, $provider_object );
2120
$url = $this->encode_filename_in_path( $url );
2121
2122
$data['url'] = $url;
@@ -2128,23 +2291,23 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
2128
/**
2129
* Sign intermediate size.
2130
*
2131
- * @param string $url
2132
- * @param int $attachment_id
2133
- * @param string|array $size
2134
- * @param bool|array $provider_object
2135
*
2136
- * @return mixed|WP_Error
2137
*/
2138
- protected function maybe_sign_intermediate_size( $url, $attachment_id, $size, $provider_object = false ) {
2139
- if ( ! $provider_object ) {
2140
- $provider_object = $this->get_attachment_provider_info( $attachment_id );
2141
}
2142
2143
$size = $this->maybe_convert_size_to_string( $attachment_id, $size );
2144
2145
- if ( isset( $provider_object['sizes'][ $size ] ) ) {
2146
// Private file, add AWS signature if required
2147
- return $this->get_attachment_provider_url( $attachment_id, $provider_object, null, $size );
2148
}
2149
2150
return $url;
@@ -2222,7 +2385,7 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
2222
* @param bool $skip_current_provider_check Skip checking if offloaded to current provider. Default: false, negated if $provider supplied
2223
* @param Provider|null $provider Provider where attachment expected to be offloaded to. Default: currently configured provider
2224
*
2225
- * @return bool|array
2226
*/
2227
public function is_attachment_served_by_provider( $attachment_id, $skip_rewrite_check = false, $skip_current_provider_check = false, Provider $provider = null ) {
2228
if ( ! $skip_rewrite_check && ! $this->get_setting( 'serve-from-s3' ) ) {
@@ -2230,7 +2393,9 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
2230
return false;
2231
}
2232
2233
- if ( ! ( $provider_object = $this->get_attachment_provider_info( $attachment_id ) ) ) {
2234
// File not uploaded to a provider
2235
return false;
2236
}
@@ -2239,12 +2404,12 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
2239
$provider = $this->get_provider();
2240
}
2241
2242
- if ( ! empty( $provider ) && $provider::get_provider_key_name() !== $provider_object['provider'] ) {
2243
// File not uploaded to required provider
2244
return false;
2245
}
2246
2247
- return $provider_object;
2248
}
2249
2250
/**
@@ -2285,33 +2450,6 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
2285
return str_replace( $file_name, $encoded_file_name, $file );
2286
}
2287
2288
- /**
2289
- * Decode file name.
2290
- *
2291
- * @param string $file
2292
- *
2293
- * @return string
2294
- */
2295
- public function decode_filename_in_path( $file ) {
2296
- $url = parse_url( $file );
2297
-
2298
- if ( ! isset( $url['path'] ) ) {
2299
- // Can't determine path, return original
2300
- return $file;
2301
- }
2302
-
2303
- $file_name = wp_basename( $url['path'] );
2304
-
2305
- if ( false === strpos( $file_name, '%' ) ) {
2306
- // File name not encoded, return original
2307
- return $file;
2308
- }
2309
-
2310
- $decoded_file_name = rawurldecode( $file_name );
2311
-
2312
- return str_replace( $file_name, $decoded_file_name, $file );
2313
- }
2314
-
2315
/**
2316
* Allow processes to update the file on provider via update_attached_file()
2317
*
@@ -2325,11 +2463,13 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
2325
return $file;
2326
}
2327
2328
- if ( ! ( $provider_object = $this->get_attachment_provider_info( $attachment_id ) ) ) {
2329
return $file;
2330
}
2331
2332
- $file = apply_filters( 'as3cf_update_attached_file', $file, $attachment_id, $provider_object );
2333
2334
return $file;
2335
}
@@ -2345,14 +2485,14 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
2345
* @return string
2346
*/
2347
function get_attached_file( $file, $attachment_id ) {
2348
- if ( file_exists( $file ) || ! ( $provider_object = $this->is_attachment_served_by_provider( $attachment_id ) ) ) {
2349
return $file;
2350
}
2351
2352
$url = $this->get_attachment_url( $attachment_id );
2353
2354
// return the URL by default
2355
- $file = apply_filters( 'as3cf_get_attached_file', $url, $file, $attachment_id, $provider_object );
2356
2357
return $file;
2358
}
@@ -2580,6 +2720,15 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
2580
);
2581
}
2582
2583
/**
2584
* Returns the Provider's default region slug.
2585
*
@@ -2679,34 +2828,6 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
2679
return $region;
2680
}
2681
2682
- /**
2683
- * Get the region of the bucket stored in the provider metadata.
2684
- *
2685
- *
2686
- * @param array $provider_object
2687
- * @param int $post_id - if supplied will update the provider meta if no region found
2688
- *
2689
- * @return string|WP_Error - region name
2690
- */
2691
- function get_provider_object_region( $provider_object, $post_id = null ) {
2692
- if ( ! isset( $provider_object['region'] ) ) {
2693
- // if region hasn't been stored in the provider metadata retrieve using the bucket
2694
- $region = $this->get_bucket_region( $provider_object['bucket'], true );
2695
- if ( is_wp_error( $region ) ) {
2696
- return $region;
2697
- }
2698
-
2699
- $provider_object['region'] = $region;
2700
-
2701
- if ( ! is_null( $post_id ) ) {
2702
- // retrospectively update provider metadata with region
2703
- update_post_meta( $post_id, 'amazonS3_info', $provider_object );
2704
- }
2705
- }
2706
-
2707
- return $provider_object['region'];
2708
- }
2709
-
2710
/**
2711
* AJAX handler for get_buckets()
2712
*/
@@ -3407,56 +3528,63 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
3407
/**
3408
* Apply ACL to an attachment and associated files
3409
*
3410
- * @param int $post_id
3411
- * @param array $provider_object
3412
- * @param string $acl
3413
*
3414
- * @return array|bool|WP_Error
3415
- * @throws Exception
3416
*/
3417
- public function set_attachment_acl_on_provider( $post_id, $provider_object, $acl ) {
3418
// Return early if already set to the desired ACL
3419
- if ( isset( $provider_object['acl'] ) && $acl === $provider_object['acl'] ) {
3420
return false;
3421
}
3422
3423
$args = array(
3424
'ACL' => $acl,
3425
- 'Bucket' => $provider_object['bucket'],
3426
- 'Key' => $provider_object['key'],
3427
);
3428
3429
- $region = ( isset( $provider_object['region'] ) ) ? $provider_object['region'] : false;
3430
- $provider_client = $this->get_provider_client( $region, true );
3431
3432
try {
3433
$provider_client->update_object_acl( $args );
3434
- $provider_object['acl'] = $acl;
3435
-
3436
- // update S3 meta data
3437
- if ( $acl == $this->get_provider()->get_default_acl() ) {
3438
- unset( $provider_object['acl'] );
3439
- }
3440
3441
- update_post_meta( $post_id, 'amazonS3_info', $provider_object );
3442
} catch ( Exception $e ) {
3443
- $msg = 'Error setting ACL to ' . $acl . ' for ' . $provider_object['key'] . ': ' . $e->getMessage();
3444
AS3CF_Error::log( $msg );
3445
3446
return new WP_Error( 'acl_exception', $msg );
3447
}
3448
3449
- return $provider_object;
3450
}
3451
3452
/**
3453
* Make admin notice for when object ACL has changed
3454
*
3455
- * @param array $provider_object
3456
*/
3457
- function make_acl_admin_notice( $provider_object ) {
3458
- $filename = wp_basename( $provider_object['key'] );
3459
- $acl = ( isset( $provider_object['acl'] ) ) ? $provider_object['acl'] : $this->get_provider()->get_default_acl();
3460
$acl_name = $this->get_acl_display_name( $acl );
3461
$text = sprintf( __( '<strong>WP Offload Media</strong> &mdash; The file %s has been given %s permissions in the bucket.', 'amazon-s3-and-cloudfront' ), "<strong>{$filename}</strong>", "<strong>{$acl_name}</strong>" );
3462
@@ -3727,13 +3855,19 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
3727
$media_counts = $this->media_counts();
3728
3729
$output .= 'Media Files: ';
3730
- $output .= number_format_i18n( $media_counts['total'] );
3731
$output .= "\r\n";
3732
3733
$output .= 'Offloaded Media Files: ';
3734
- $output .= number_format_i18n( $media_counts['offloaded'] );
3735
$output .= "\r\n";
3736
3737
$output .= 'Number of Image Sizes: ';
3738
$sizes = count( get_intermediate_image_sizes() );
3739
$output .= number_format_i18n( $sizes );
@@ -4283,55 +4417,6 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
4283
return intval( $memory_limit ) * 1024 * 1024;
4284
}
4285
4286
- /**
4287
- * Count attachments on a site.
4288
- *
4289
- * @param string $prefix
4290
- * @param bool $skip_transient Whether to force database query and skip transient, default false
4291
- * @param bool $force Whether to force database query and skip static cache, implies $skip_transient, default false
4292
- *
4293
- * @return array Keys:
4294
- * total: Total media count for site (prefix)
4295
- * offloaded: Count of offloaded media for site (prefix)
4296
- * not_offloaded: Difference between total and offloaded
4297
- */
4298
- public function count_attachments( $prefix, $skip_transient = false, $force = false ) {
4299
- global $wpdb;
4300
-
4301
- static $counts;
4302
- static $skips;
4303
-
4304
- $transient_key = 'as3cf_' . $prefix . '_attachment_counts';
4305
-
4306
- // Been here, done it, won't do it again!
4307
- // Well, unless this is the first transient skip for the prefix, then we need to do it.
4308
- if ( ! $force && ! empty( $counts[ $transient_key ] ) && ( false === $skip_transient || ! empty( $skips[ $transient_key ] ) ) ) {
4309
- return $counts[ $transient_key ];
4310
- }
4311
-
4312
- if ( $force || $skip_transient || false === ( $attachment_counts = get_site_transient( $transient_key ) ) ) {
4313
- $sql = "
4314
- SELECT COUNT(DISTINCT p.`ID`) total, COUNT(DISTINCT pm.`post_id`) offloaded
4315
- FROM `{$prefix}posts` p
4316
- LEFT OUTER JOIN `{$prefix}postmeta` pm ON p.`ID` = pm.`post_id` AND pm.`meta_key` = 'amazonS3_info'
4317
- WHERE p.`post_type` = 'attachment'
4318
- ";
4319
-
4320
- $attachment_counts = $wpdb->get_row( $sql, ARRAY_A );
4321
-
4322
- $attachment_counts['not_offloaded'] = $attachment_counts['total'] - $attachment_counts['offloaded'];
4323
-
4324
- set_site_transient( $transient_key, $attachment_counts, 2 * MINUTE_IN_SECONDS );
4325
-
4326
- // One way or another we've skipped the transient.
4327
- $skips[ $transient_key ] = true;
4328
- }
4329
-
4330
- $counts[ $transient_key ] = $attachment_counts;
4331
-
4332
- return $attachment_counts;
4333
- }
4334
-
4335
/**
4336
* Get the total attachment and total offloaded/not offloaded attachment counts
4337
*
@@ -4342,22 +4427,35 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
4342
*/
4343
public function media_counts( $skip_transient = false, $force = false ) {
4344
if ( $skip_transient || false === ( $attachment_counts = get_site_transient( 'as3cf_attachment_counts' ) ) ) {
4345
- $table_prefixes = $this->get_all_blog_table_prefixes();
4346
- $total = 0;
4347
- $offloaded = 0;
4348
- $not_offloaded = 0;
4349
4350
foreach ( $table_prefixes as $blog_id => $table_prefix ) {
4351
- $counts = $this->count_attachments( $table_prefix, $skip_transient, $force );
4352
- $total += $counts['total'];
4353
- $offloaded += $counts['offloaded'];
4354
- $not_offloaded += $counts['not_offloaded'];
4355
}
4356
4357
$attachment_counts = array(
4358
- 'total' => $total,
4359
- 'offloaded' => $offloaded,
4360
- 'not_offloaded' => $not_offloaded,
4361
);
4362
4363
set_site_transient( 'as3cf_attachment_counts', $attachment_counts, 2 * MINUTE_IN_SECONDS );
@@ -4615,42 +4713,39 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
4615
}
4616
4617
/**
4618
- * Return a formatted S3 info with display friendly defaults
4619
*
4620
- * @param int $id
4621
- * @param array|null $provider_object
4622
*
4623
- * @return array
4624
*/
4625
- public function get_formatted_provider_info( $id, $provider_object = null ) {
4626
- if ( is_null( $provider_object ) ) {
4627
- if ( ! ( $provider_object = $this->get_attachment_provider_info( $id ) ) ) {
4628
- return false;
4629
- }
4630
}
4631
4632
- $provider_object['url'] = $this->get_attachment_provider_url( $id, $provider_object );
4633
4634
- $acl = ( isset( $provider_object['acl'] ) ) ? $provider_object['acl'] : $this->get_provider()->get_default_acl();
4635
$acl_info = array(
4636
'acl' => $acl,
4637
'name' => $this->get_acl_display_name( $acl ),
4638
'title' => $this->get_media_action_strings( 'change_to_private' ),
4639
);
4640
4641
- if ( $this->get_provider()->get_private_acl() === $acl ) {
4642
$acl_info['title'] = $this->get_media_action_strings( 'change_to_public' );
4643
}
4644
4645
- $provider_object['acl'] = $acl_info;
4646
-
4647
- if ( isset( $provider_object['region'] ) ) {
4648
- $provider_object['region'] = $this->get_provider()->get_region_name( $provider_object['region'] );
4649
- }
4650
-
4651
- if ( ! empty( $provider_object['provider'] ) ) {
4652
- $provider_object['provider_name'] = $this->get_provider_service_name( $provider_object['provider'] );
4653
- }
4654
4655
return $provider_object;
4656
}
@@ -4764,19 +4859,6 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
4764
return reset( $parts );
4765
}
4766
4767
- /**
4768
- * Normalize object prefix.
4769
- *
4770
- * @param string $prefix
4771
- *
4772
- * @return string
4773
- */
4774
- protected function normalize_object_prefix( $prefix ) {
4775
- $directory = dirname( $prefix );
4776
-
4777
- return ( '.' === $directory ) ? '' : $directory . '/';
4778
- }
4779
-
4780
/**
4781
* Has the given attachment been uploaded by this instance?
4782
*
@@ -4839,14 +4921,10 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
4839
* @return string
4840
*/
4841
public function get_acl_for_intermediate_size( $attachment_id, $size ) {
4842
- $provider_info = $this->get_attachment_provider_info( $attachment_id );
4843
-
4844
- if ( 'original' === $size || empty( $size ) ) {
4845
- return isset( $provider_info['acl'] ) ? $provider_info['acl'] : $this->get_provider()->get_default_acl();
4846
- }
4847
4848
- if ( ! empty( $provider_info['sizes'][ $size ]['acl'] ) ) {
4849
- return $provider_info['sizes'][ $size ]['acl'];
4850
}
4851
4852
return $this->get_provider()->get_default_acl();
1
<?php
2
3
+ use DeliciousBrains\WP_Offload_Media\Items\Media_Library_Item;
4
use DeliciousBrains\WP_Offload_Media\Providers\AWS_Provider;
5
use DeliciousBrains\WP_Offload_Media\Providers\DigitalOcean_Provider;
6
use DeliciousBrains\WP_Offload_Media\Providers\GCP_Provider;
10
use DeliciousBrains\WP_Offload_Media\Upgrades\Upgrade_EDD_Replace_URLs;
11
use DeliciousBrains\WP_Offload_Media\Upgrades\Upgrade_File_Sizes;
12
use DeliciousBrains\WP_Offload_Media\Upgrades\Upgrade_Filter_Post_Excerpt;
13
+ use DeliciousBrains\WP_Offload_Media\Upgrades\Upgrade_Items_Table;
14
use DeliciousBrains\WP_Offload_Media\Upgrades\Upgrade_Meta_WP_Error;
15
use DeliciousBrains\WP_Offload_Media\Upgrades\Upgrade_Region_Meta;
16
use DeliciousBrains\WP_Offload_Media\Upgrades\Upgrade_WPOS3_To_AS3CF;
109
'WPOS3_SETTINGS',
110
);
111
112
+ const LATEST_UPGRADE_ROUTINE = 8;
113
114
/**
115
* @param string $plugin_file_path
144
GCP_Provider::get_provider_key_name() => 'DeliciousBrains\WP_Offload_Media\Providers\GCP_Provider',
145
);
146
147
+ Media_Library_Item::init_cache();
148
+
149
$this->set_provider();
150
151
// Bundled SDK may require AWS setup before data migrations.
158
new Upgrade_EDD_Replace_URLs( $this );
159
new Upgrade_Filter_Post_Excerpt( $this );
160
new Upgrade_WPOS3_To_AS3CF( $this );
161
+ new Upgrade_Items_Table( $this );
162
163
// Plugin setup
164
add_action( 'admin_menu', array( $this, 'admin_menu' ) );
311
return apply_filters( 'as3cf_settings_menu_title', $this->plugin_menu_title );
312
}
313
314
+ /**
315
+ * Get the plugin prefix.
316
+ *
317
+ * @return string
318
+ */
319
+ public function get_plugin_prefix() {
320
+ return $this->plugin_prefix;
321
+ }
322
+
323
/**
324
* Get the plugin prefix in slug format, ie. replace underscores with hyphens
325
*
326
* @return string
327
*/
328
+ public function get_plugin_prefix_slug() {
329
+ return str_replace( '_', '-', $this->get_plugin_prefix() );
330
}
331
332
/**
352
'key' => $key,
353
'disabled' => false,
354
'disabled_attr' => '',
355
+ 'tr_class' => str_replace( '_', '-', $this->get_plugin_prefix() . '-' . $key . '-container' ),
356
'setting_msg' => '',
357
'is_defined' => false,
358
);
973
/**
974
* Removes an attachment's files from provider.
975
*
976
+ * @param int $post_id
977
+ * @param Media_Library_Item $as3cf_item
978
+ * @param bool $include_backups remove previous edited image versions
979
+ * @param bool $log_error
980
+ * @param bool $return_on_error
981
+ * @param bool $force_new_provider_client if we are deleting in bulk, force new provider client
982
+ * to cope with possible different regions
983
+ */
984
+ function remove_attachment_files_from_provider( $post_id, Media_Library_Item $as3cf_item, $include_backups = true, $log_error = false, $return_on_error = false, $force_new_provider_client = false ) {
985
+ $prefix = $as3cf_item->normalized_path_dir();
986
+ $paths = AS3CF_Utils::get_attachment_file_paths( $post_id, false, false, $include_backups );
987
+ $paths = apply_filters( 'as3cf_remove_attachment_paths', $paths, $post_id, $as3cf_item, $include_backups );
988
989
+ // If another item in current site shares full size *local* paths, only remove remote files not referenced by duplicates.
990
+ // We reference local paths as they should be reflected one way or another remotely, including backups.
991
+ $fullsize_paths = AS3CF_Utils::fullsize_paths( $paths );
992
+ $as3cf_items_with_paths = Media_Library_Item::get_by_source_path( $fullsize_paths, array( $post_id ), false );
993
+
994
+ $duplicate_paths = array();
995
+
996
+ foreach ( $as3cf_items_with_paths as $as3cf_item_with_path ) {
997
+ /* @var Media_Library_Item $as3cf_item_with_path */
998
+ $duplicate_paths += array_values( AS3CF_Utils::get_attachment_file_paths( $as3cf_item_with_path->source_id(), false, false, $include_backups ) );
999
+ }
1000
+
1001
+ if ( ! empty( $duplicate_paths ) ) {
1002
+ $paths = array_diff( $paths, $duplicate_paths );
1003
+ }
1004
+
1005
+ // Nothing to do, shortcut out.
1006
+ if ( empty( $paths ) ) {
1007
+ return;
1008
}
1009
1010
$objects_to_remove = array();
1016
}
1017
1018
// finally delete the objects from provider
1019
+ $this->delete_objects( $as3cf_item->region(), $as3cf_item->bucket(), $objects_to_remove, $log_error, $return_on_error, $force_new_provider_client );
1020
}
1021
1022
/**
1031
return;
1032
}
1033
1034
+ $as3cf_item = Media_Library_Item::get_by_source_id( $post_id );
1035
+
1036
+ if ( ! $as3cf_item ) {
1037
return;
1038
}
1039
1041
return;
1042
}
1043
1044
+ $this->remove_attachment_files_from_provider( $post_id, $as3cf_item, true, true, true, $force_new_provider_client );
1045
1046
+ $as3cf_item->delete();
1047
}
1048
1049
/**
1061
return $data;
1062
}
1063
1064
+ // Protect against updates of partially formed metadata.
1065
+ if ( wp_attachment_is_image( $post_id ) && empty( $data['sizes'] ) ) {
1066
+ return $data;
1067
+ }
1068
+
1069
+ $as3cf_item = Media_Library_Item::get_by_source_id( $post_id );
1070
+
1071
+ if ( ! $as3cf_item && ! $this->get_setting( 'copy-to-s3' ) ) {
1072
// abort if not already uploaded to provider and the copy setting is off
1073
return $data;
1074
}
1075
1076
+ if ( empty( $as3cf_item ) ) {
1077
+ $as3cf_item = null;
1078
+ }
1079
+
1080
// allow provider upload to be cancelled for any reason
1081
+ $pre = apply_filters( 'as3cf_pre_update_attachment_metadata', false, $data, $post_id, $as3cf_item );
1082
if ( false !== $pre ) {
1083
return $data;
1084
}
1085
1086
// upload attachment to provider
1087
+ $attachment_metadata = $this->upload_attachment( $post_id, $data );
1088
1089
+ if ( is_wp_error( $attachment_metadata ) ) {
1090
return $data;
1091
}
1092
1093
+ return $attachment_metadata;
1094
}
1095
1096
/**
1103
* to cope with possible different regions
1104
* @param bool $remove_local_files
1105
*
1106
+ * @return array|Media_Library_Item|WP_Error
1107
* @throws Exception
1108
*/
1109
public function upload_attachment( $post_id, $data = null, $file_path = null, $force_new_provider_client = false, $remove_local_files = true ) {
1110
+ static $offloaded = array();
1111
+
1112
$return_metadata = null;
1113
if ( is_null( $data ) ) {
1114
$data = wp_get_attachment_metadata( $post_id, true );
1136
$file_path = get_attached_file( $post_id, true );
1137
}
1138
1139
+ // Check for valid "full" file path before attempting upload
1140
+ if ( empty( $file_path ) ) {
1141
+ $error_msg = sprintf( __( 'Media Library item with ID %d does not have a valid file path', 'amazon-s3-and-cloudfront' ), $post_id );
1142
1143
return $this->return_upload_error( $error_msg, $return_metadata );
1144
}
1145
1146
+ $offload_full = true;
1147
+ $old_item = Media_Library_Item::get_by_source_id( $post_id );
1148
+
1149
+ // If item not already offloaded, is it a duplicate?
1150
+ $duplicate = false;
1151
+ if ( empty( $old_item ) ) {
1152
+ $old_items = Media_Library_Item::get_by_source_path( $file_path, $post_id, true, true );
1153
+
1154
+ if ( ! empty( $old_items[0] ) ) {
1155
+ $duplicate = true;
1156
+
1157
+ /** @var Media_Library_Item $duplicate_item */
1158
+ $duplicate_item = $old_items[0];
1159
+
1160
+ $old_item = new Media_Library_Item(
1161
+ $duplicate_item->provider(),
1162
+ $duplicate_item->region(),
1163
+ $duplicate_item->bucket(),
1164
+ $duplicate_item->path(),
1165
+ $duplicate_item->is_private(),
1166
+ $post_id,
1167
+ $duplicate_item->source_path(),
1168
+ wp_basename( $duplicate_item->original_source_path() ),
1169
+ $duplicate_item->private_sizes()
1170
+ );
1171
+
1172
+ $old_item->save();
1173
+
1174
+ // If original offloaded in same process, skip offloading anything it's already processed.
1175
+ // Otherwise, do not need to offload full file if duplicate and file missing.
1176
+ if ( ! empty( $offloaded[ $duplicate_item->id() ] ) ) {
1177
+ $offloaded[ $old_item->id() ] = $offloaded[ $duplicate_item->id() ];
1178
+ } elseif ( ! file_exists( $file_path ) ) {
1179
+ $offload_full = false;
1180
+ }
1181
+
1182
+ unset( $old_items, $duplicate_item );
1183
+ }
1184
+ }
1185
+
1186
+ // If not already offloaded in request, check full file exists locally before attempting offload.
1187
+ if ( $offload_full ) {
1188
+ if ( $old_item && ! empty( $offloaded[ $old_item->id() ][ $file_path ] ) ) {
1189
+ $offload_full = false;
1190
+ } elseif ( ! file_exists( $file_path ) ) {
1191
+ $error_msg = sprintf( __( 'File %s does not exist', 'amazon-s3-and-cloudfront' ), $file_path );
1192
+
1193
+ return $this->return_upload_error( $error_msg, $return_metadata );
1194
+ }
1195
+ }
1196
+
1197
+ $file_paths = AS3CF_Utils::get_attachment_file_paths( $post_id, false, $data );
1198
+ $file_paths = array_diff( $file_paths, array( $file_path ) );
1199
+
1200
+ // Are there any files not already offloaded if full already offloaded in this request?
1201
+ if ( false === $offload_full ) {
1202
+ if ( empty( $file_paths ) ) {
1203
+ return $return_metadata;
1204
+ }
1205
+
1206
+ $offloaded_file_paths = empty( $offloaded[ $old_item->id() ] ) ? array() : $offloaded[ $old_item->id() ];
1207
+ unset( $offloaded_file_paths[ $file_path ] );
1208
+
1209
+ if ( ! empty( $offloaded_file_paths ) && empty( array_diff( $file_paths, array_keys( $offloaded_file_paths ) ) ) ) {
1210
+ return $return_metadata;
1211
+ }
1212
+ }
1213
+
1214
// Get original file's stats.
1215
$file_name = wp_basename( $file_path );
1216
$type = get_post_mime_type( $post_id );
1217
$allowed_types = $this->get_allowed_mime_types();
1226
$acl = $this->get_provider()->get_default_acl();
1227
1228
// check the attachment already exists in provider, eg. edit or restore image
1229
+ if ( $old_item ) {
1230
// Must be offloaded to same provider as currently configured.
1231
if ( ! $this->is_attachment_served_by_provider( $post_id, true ) ) {
1232
return $this->return_upload_error( __( 'Already offloaded to a different provider', 'amazon-s3-and-cloudfront' ), $return_metadata );
1233
}
1234
1235
+ // Use private ACL if existing offload is already private.
1236
+ if ( $old_item->is_private() ) {
1237
+ $acl = $this->get_provider()->get_private_acl();
1238
}
1239
1240
// use existing prefix
1241
+ $prefix = $old_item->normalized_path_dir();
1242
// use existing bucket
1243
+ $bucket = $old_item->bucket();
1244
// get existing region
1245
+ $region = $old_item->region();
1246
+ // Get existing original filename.
1247
+ $original_filename = wp_basename( $old_item->original_source_path() );
1248
} else {
1249
// derive prefix from various settings
1250
$time = $this->get_attachment_folder_year_month( $post_id, $data );
1256
if ( is_wp_error( $region ) ) {
1257
$region = '';
1258
}
1259
+
1260
+ // There may be an original image that can override the default original filename.
1261
+ $original_filename = empty( $data['original_image'] ) ? null : $data['original_image'];
1262
}
1263
1264
$acl = apply_filters( 'wps3_upload_acl', $acl, $type, $data, $post_id, $this ); // Old naming convention, will be deprecated soon
1283
$args['ContentEncoding'] = 'gzip';
1284
}
1285
1286
+ $image_size = wp_attachment_is_image( $post_id ) ? 'full' : '';
1287
+ $args = apply_filters( 'as3cf_object_meta', $args, $post_id, $image_size, false );
1288
1289
+ $provider = $this->get_provider()->get_provider_key_name();
1290
+ $region = $bucket !== $args['Bucket'] ? $this->get_bucket_region( $args['Bucket'], true ) : $region;
1291
+ $is_private = $this->get_provider()->get_private_acl() === $args['ACL'] ? true : false;
1292
+ $private_sizes = empty( $old_item ) ? array() : $old_item->private_sizes();
1293
+ $item_id = empty( $old_item ) ? null : $old_item->id();
1294
+
1295
+ $as3cf_item = new Media_Library_Item( $provider, $region, $args['Bucket'], $args['Key'], $is_private, $post_id, $file_path, $original_filename, $private_sizes, $item_id );
1296
1297
+ do_action( 'as3cf_upload_attachment_pre_remove', $post_id, $as3cf_item, $prefix, $args );
1298
1299
+ $new_offloads = array();
1300
$files_to_remove = array();
1301
1302
+ $provider_client = $this->get_provider_client( $as3cf_item->region(), $force_new_provider_client );
1303
1304
+ if ( $offload_full ) {
1305
+ try {
1306
+ // May raise exception, so don't offload anything else if there's an error.
1307
+ $filesize = (int) filesize( $file_path );
1308
1309
+ // May raise exception, so don't offload anything else if there's an error.
1310
+ $provider_client->upload_object( $args );
1311
1312
+ $new_offloads[ $file_path ] = $filesize; // Note: pre `as3cf_object_meta` filter value.
1313
+ $files_to_remove[] = $file_path; // Note: pre `as3cf_object_meta` filter value.
1314
+ } catch ( Exception $e ) {
1315
+ $error_msg = sprintf( __( 'Error offloading %s to provider: %s', 'amazon-s3-and-cloudfront' ), $file_path, $e->getMessage() );
1316
1317
+ return $this->return_upload_error( $error_msg, $return_metadata );
1318
+ }
1319
+ }
1320
1321
+ $additional_images = array();
1322
+ $private_sizes = array(); // Reset private sizes to be as expected at time of (re)upload.
1323
1324
foreach ( $file_paths as $size => $file_path ) {
1325
if ( ! in_array( $file_path, $files_to_remove ) ) {
1332
'ContentType' => $this->get_mime_type( $file_path ),
1333
);
1334
1335
+ if ( $this->get_provider()->get_private_acl() === $acl ) {
1336
+ $private_sizes[] = $size;
1337
}
1338
}
1339
}
1341
$upload_errors = array();
1342
1343
foreach ( $additional_images as $size => $image ) {
1344
+ // If this file has already been offloaded during this request, skip actual offload.
1345
+ if ( $old_item && ! empty( $offloaded[ $old_item->id() ][ $image['SourceFile'] ] ) ) {
1346
+ continue;
1347
+ }
1348
+
1349
$args = apply_filters( 'as3cf_object_meta', array_merge( $args, $image ), $post_id, $size, false );
1350
1351
if ( ! file_exists( $args['SourceFile'] ) ) {
1352
+ if ( ! $duplicate ) {
1353
+ $upload_errors[] = $this->return_upload_error( sprintf( __( 'File %s does not exist', 'amazon-s3-and-cloudfront' ), $args['SourceFile'] ) );
1354
+ }
1355
continue;
1356
}
1357
1358
try {
1359
+ // May raise exception, but for sizes we'll just log it and maybe try again later if called.
1360
$provider_client->upload_object( $args );
1361
+ $files_to_remove[] = $image['SourceFile']; // Note: pre `as3cf_object_meta` filter value.
1362
+
1363
+ // May raise exception, we'll log that, and carry on anyway.
1364
+ $new_offloads[ $image['SourceFile'] ] = (int) filesize( $image['SourceFile'] ); // Note: pre `as3cf_object_meta` filter value.
1365
} catch ( Exception $e ) {
1366
$upload_errors[] = $this->return_upload_error( sprintf( __( 'Error offloading %s to provider: %s', 'amazon-s3-and-cloudfront' ), $args['SourceFile'], $e->getMessage() ) );
1367
}
1368
+
1369
+ // Edge Case: If previously uploaded and a different original_image wasn't picked up but is now, record it.
1370
+ // This is most likely to happen if older version of plugin was used with WP5.3 and large or rotated image auto-created.
1371
+ if ( 'original_image' === $size && wp_basename( $as3cf_item->original_source_path() ) !== wp_basename( $image['SourceFile'] ) ) {
1372
+ $as3cf_item = new Media_Library_Item(
1373
+ $as3cf_item->provider(),
1374
+ $as3cf_item->region(),
1375
+ $as3cf_item->bucket(),
1376
+ $as3cf_item->path(),
1377
+ $as3cf_item->is_private(),
1378
+ $as3cf_item->source_id(),
1379
+ $as3cf_item->source_path(),
1380
+ wp_basename( $image['SourceFile'] ),
1381
+ $as3cf_item->private_sizes(),
1382
+ $as3cf_item->id()
1383
+ );
1384
+ }
1385
}
1386
1387
$remove_local_files_setting = $this->get_setting( 'remove-local-file' );
1388
1389
+ if ( $remove_local_files && $remove_local_files_setting ) {
1390
+ // Allow other functions to remove files after they have processed
1391
+ $files_to_remove = apply_filters( 'as3cf_upload_attachment_local_files_to_remove', $files_to_remove, $post_id, $file_path );
1392
1393
+ // Remove duplicates
1394
+ $files_to_remove = array_unique( $files_to_remove );
1395
1396
+ $filesize_total = 0;
1397
+ if ( ! empty( $old_item ) && ! empty( $offloaded[ $old_item->id() ] ) ) {
1398
+ $filesize_total = array_sum( $offloaded[ $old_item->id() ] );
1399
+ }
1400
+ // Delete the files and record original file's size before removal.
1401
+ $this->remove_local_files( $files_to_remove, $post_id, $filesize_total );
1402
1403
+ // Store filesize in the attachment meta data for use by WP if we've just offloaded the full size file.
1404
+ if ( ! empty( $filesize ) ) {
1405
+ $data['filesize'] = $filesize;
1406
1407
+ if ( is_null( $return_metadata ) ) {
1408
+ // Update metadata with filesize
1409
+ update_post_meta( $post_id, '_wp_attachment_metadata', $data );
1410
}
1411
}
1412
}
1416
$data = $this->maybe_cleanup_filesize_metadata( $post_id, $data, empty( $return_metadata ) );
1417
}
1418
1419
+ // Additional image sizes have custom ACLs, record them.
1420
+ if ( ! empty( $private_sizes ) ) {
1421
+ $as3cf_item = new Media_Library_Item(
1422
+ $as3cf_item->provider(),
1423
+ $as3cf_item->region(),
1424
+ $as3cf_item->bucket(),
1425
+ $as3cf_item->path(),
1426
+ $as3cf_item->is_private(),
1427
+ $as3cf_item->source_id(),
1428
+ $as3cf_item->source_path(),
1429
+ wp_basename( $as3cf_item->original_source_path() ),
1430
+ $private_sizes,
1431
+ $as3cf_item->id()
1432
+ );
1433
+ }
1434
+
1435
+ // All done, save record of offloaded item.
1436
+ $as3cf_item->save();
1437
+
1438
+ // Keep track of individual files offloaded during this request.
1439
+ if ( empty( $offloaded[ $as3cf_item->id() ] ) ) {
1440
+ $offloaded[ $as3cf_item->id() ] = $new_offloads;
1441
+ } else {
1442
+ $offloaded[ $as3cf_item->id() ] += $new_offloads;
1443
}
1444
1445
// Keep track of attachments uploaded by this instance.
1446
$this->uploaded_post_ids[] = $post_id;
1447
1448
+ do_action( 'as3cf_post_upload_attachment', $post_id, $as3cf_item );
1449
1450
if ( $upload_errors ) {
1451
return $this->consolidate_upload_errors( $upload_errors );
1456
return $data;
1457
}
1458
1459
+ return $as3cf_item;
1460
}
1461
1462
/**
1539
/**
1540
* Remove files from the local site, recording total filesize in meta if attachment ID given.
1541
*
1542
+ * @param array $file_paths Files to remove.
1543
+ * @param int $attachment_id Optional, if supplied filesize metadata recorded.
1544
+ * @param int $filesize_total Optional, if removing partial set of an attachment's files, pass in previously removed total.
1545
*/
1546
+ function remove_local_files( $file_paths, $attachment_id = 0, $filesize_total = 0 ) {
1547
+ if ( empty( $filesize_total ) ) {
1548
+ $filesize_total = 0;
1549
+ }
1550
1551
foreach ( $file_paths as $index => $path ) {
1552
if ( ! empty( $attachment_id ) && is_int( $attachment_id ) ) {
1574
}
1575
1576
// If we were able to sum up file sizes for an attachment, record it.
1577
+ if ( ! empty( $attachment_id ) && is_int( $attachment_id ) && $filesize_total > 0 ) {
1578
update_post_meta( $attachment_id, 'as3cf_filesize_total', $filesize_total );
1579
}
1580
}
1695
* @return string
1696
*/
1697
public function filter_unique_filename( $filename, $ext, $dir, $post_id = null ) {
1698
+ if ( ! $this->is_plugin_setup( true ) ) {
1699
return $filename;
1700
}
1701
1788
}
1789
$file = $path . $filename;
1790
1791
+ // WordPress doesn't check its own basic record, so we will.
1792
$sql = $wpdb->prepare( "
1793
SELECT COUNT(*)
1794
FROM $wpdb->postmeta
1796
AND meta_value = %s
1797
", '_wp_attached_file', $file );
1798
1799
+ if ( (bool) $wpdb->get_var( $sql ) ) {
1800
+ return true;
1801
+ }
1802
+
1803
+ // Check our records of local source path as it also covers original_image.
1804
+ if ( ! empty( Media_Library_Item::get_by_source_path( array( $file ), array(), true, true ) ) ) {
1805
+ return true;
1806
+ }
1807
+
1808
+ return false;
1809
}
1810
1811
/**
1843
*/
1844
function generate_unique_filename( $name, $ext, $time ) {
1845
$count = 1;
1846
+ $filename = $name . '-' . $count . $ext;
1847
1848
while ( $this->does_file_exist( $filename, $time ) ) {
1849
$count++;
1850
+ $filename = $name . '-' . $count . $ext;
1851
}
1852
1853
return $filename;
1854
}
1855
1856
/**
1857
* Check the plugin is correctly setup
1858
*
1859
* @param bool $with_credentials Do provider credentials need to be set up too? Defaults to false.
1860
*
1861
* @return bool
1862
+ *
1863
+ * TODO: Performance - cache / static var by param.
1864
*/
1865
function is_plugin_setup( $with_credentials = false ) {
1866
if ( $with_credentials && $this->get_provider()->needs_access_keys() ) {
1990
* @return bool|mixed|WP_Error
1991
*/
1992
public function get_attachment_url( $post_id, $expires = null, $size = null, $meta = null, $headers = array(), $skip_rewrite_check = false ) {
1993
+ if ( ! ( $as3cf_item = $this->is_attachment_served_by_provider( $post_id, $skip_rewrite_check ) ) ) {
1994
return false;
1995
}
1996
1997
+ $url = $this->get_attachment_provider_url( $post_id, $as3cf_item, $expires, $size, $meta, $headers );
1998
1999
return apply_filters( 'as3cf_wp_get_attachment_url', $url, $post_id );
2000
}
2067
/**
2068
* Get the provider URL for an attachment
2069
*
2070
+ * @param int $post_id
2071
+ * @param Media_Library_Item $as3cf_item
2072
+ * @param null|int $expires
2073
+ * @param null|string|array $size
2074
+ * @param null|array $meta
2075
+ * @param array $headers
2076
*
2077
+ * @return string|WP_Error
2078
*/
2079
+ public function get_attachment_provider_url( $post_id, Media_Library_Item $as3cf_item, $expires = null, $size = null, $meta = null, $headers = array() ) {
2080
+ $item_path = $as3cf_item->path();
2081
+
2082
+ if ( ! empty( $as3cf_item->region() ) && ( $this->get_provider()->region_required() || $this->get_provider()->get_default_region() !== $as3cf_item->region() ) ) {
2083
+ $region = $this->get_provider()->sanitize_region( $as3cf_item->region() );
2084
} else {
2085
$region = '';
2086
}
2089
2090
// Force use of secured URL when ACL has been set to private
2091
if ( is_null( $expires ) ) {
2092
+ if ( is_null( $size ) && $as3cf_item->is_private() ) {
2093
// Full size URL private
2094
$expires = self::DEFAULT_EXPIRES;
2095
}
2096
2097
+ if ( ! is_null( $size ) && $as3cf_item->is_private_size( $size ) ) {
2098
// Alternative size URL private
2099
$expires = self::DEFAULT_EXPIRES;
2100
}
2110
}
2111
2112
if ( ! empty( $meta ) && isset( $meta['sizes'][ $size ]['file'] ) ) {
2113
+ $size_prefix = dirname( $item_path );
2114
$size_file_prefix = ( '.' === $size_prefix ) ? '' : $size_prefix . '/';
2115
2116
+ $item_path = $size_file_prefix . $meta['sizes'][ $size ]['file'];
2117
}
2118
}
2119
2120
$scheme = $this->get_url_scheme();
2121
+ $domain = $this->get_provider()->get_url_domain( $as3cf_item->bucket(), $region, $expires );
2122
$base_url = $scheme . '://' . $domain;
2123
2124
if ( ! is_null( $expires ) && $this->is_plugin_setup( true ) ) {
2129
2130
$expires = time() + apply_filters( 'as3cf_expires', $expires );
2131
$secure_url = $this->get_provider_client( $region )
2132
+ ->get_object_url( $as3cf_item->bucket(), $item_path, $expires, $headers );
2133
2134
+ return apply_filters( 'as3cf_get_attachment_secure_url', $secure_url, $as3cf_item, $post_id, $expires, $headers );
2135
} catch ( Exception $e ) {
2136
return new WP_Error( 'exception', $e->getMessage() );
2137
}
2138
}
2139
2140
+ $item_path = $this->maybe_update_cloudfront_path( $item_path );
2141
2142
+ $file = $this->encode_filename_in_path( $item_path );
2143
$url = $base_url . '/' . $file;
2144
2145
+ return apply_filters( 'as3cf_get_attachment_url', $url, $as3cf_item, $post_id, $expires, $headers );
2146
}
2147
2148
/**
2154
* @return bool|mixed|WP_Error
2155
*/
2156
public function wp_get_attachment_url( $url, $post_id ) {
2157
+ if ( $this->plugin_compat->is_customizer_crop_action() ) {
2158
+ return $url;
2159
+ }
2160
+
2161
$new_url = $this->get_attachment_url( $post_id );
2162
2163
if ( false === $new_url ) {
2183
* @return string
2184
*/
2185
public function maybe_encode_get_image_tag( $html, $id, $alt, $title, $align, $size ) {
2186
+ if ( ! ( $as3cf_item = $this->is_attachment_served_by_provider( $id ) ) ) {
2187
// Not served by provider, return
2188
return $html;
2189
}
2200
}
2201
2202
$img_src = $matches[1];
2203
+ $new_img_src = $this->maybe_sign_intermediate_size( $img_src, $id, $size, $as3cf_item );
2204
$new_img_src = $this->encode_filename_in_path( $new_img_src );
2205
2206
return str_replace( $img_src, $new_img_src, $html );
2217
* @return array
2218
*/
2219
public function maybe_encode_wp_get_attachment_image_src( $image, $attachment_id, $size, $icon ) {
2220
+ if ( ! ( $as3cf_item = $this->is_attachment_served_by_provider( $attachment_id ) ) ) {
2221
// Not served by provider, return
2222
return $image;
2223
}
2224
2225
if ( isset( $image[0] ) ) {
2226
+ $url = $this->maybe_sign_intermediate_size( $image[0], $attachment_id, $size, $as3cf_item );
2227
$url = $this->encode_filename_in_path( $url );
2228
2229
$image[0] = $url;
2242
* @return array
2243
*/
2244
public function maybe_encode_wp_prepare_attachment_for_js( $response, $attachment, $meta ) {
2245
+ if ( ! ( $as3cf_item = $this->is_attachment_served_by_provider( $attachment->ID ) ) ) {
2246
// Not served by provider, return
2247
return $response;
2248
}
2253
2254
if ( isset( $response['sizes'] ) && is_array( $response['sizes'] ) ) {
2255
foreach ( $response['sizes'] as $size => $value ) {
2256
+ $url = $this->maybe_sign_intermediate_size( $value['url'], $attachment->ID, $size, $as3cf_item );
2257
$url = $this->encode_filename_in_path( $url );
2258
2259
$response['sizes'][ $size ]['url'] = $url;
2273
* @return array
2274
*/
2275
public function maybe_encode_image_get_intermediate_size( $data, $post_id, $size ) {
2276
+ if ( ! ( $as3cf_item = $this->is_attachment_served_by_provider( $post_id ) ) ) {
2277
// Not served by provider, return
2278
return $data;
2279
}
2280
2281
if ( isset( $data['url'] ) ) {
2282
+ $url = $this->maybe_sign_intermediate_size( $data['url'], $post_id, $size, $as3cf_item );
2283
$url = $this->encode_filename_in_path( $url );
2284
2285
$data['url'] = $url;
2291
/**
2292
* Sign intermediate size.
2293
*
2294
+ * @param string $url
2295
+ * @param int $attachment_id
2296
+ * @param string|array $size
2297
+ * @param bool|Media_Library_Item $as3cf_item
2298
*
2299
+ * @return string|WP_Error
2300
*/
2301
+ protected function maybe_sign_intermediate_size( $url, $attachment_id, $size, $as3cf_item = false ) {
2302
+ if ( ! $as3cf_item ) {
2303
+ $as3cf_item = Media_Library_Item::get_by_source_id( $attachment_id );
2304
}
2305
2306
$size = $this->maybe_convert_size_to_string( $attachment_id, $size );
2307
2308
+ if ( $as3cf_item->is_private_size( $size ) ) {
2309
// Private file, add AWS signature if required
2310
+ return $this->get_attachment_provider_url( $attachment_id, $as3cf_item, null, $size );
2311
}
2312
2313
return $url;
2385
* @param bool $skip_current_provider_check Skip checking if offloaded to current provider. Default: false, negated if $provider supplied
2386
* @param Provider|null $provider Provider where attachment expected to be offloaded to. Default: currently configured provider
2387
*
2388
+ * @return bool|Media_Library_Item
2389
*/
2390
public function is_attachment_served_by_provider( $attachment_id, $skip_rewrite_check = false, $skip_current_provider_check = false, Provider $provider = null ) {
2391
if ( ! $skip_rewrite_check && ! $this->get_setting( 'serve-from-s3' ) ) {
2393
return false;
2394
}
2395
2396
+ $as3cf_item = Media_Library_Item::get_by_source_id( $attachment_id );
2397
+
2398
+ if ( ! $as3cf_item ) {
2399
// File not uploaded to a provider
2400
return false;
2401
}
2404
$provider = $this->get_provider();
2405
}
2406
2407
+ if ( ! empty( $provider ) && $provider::get_provider_key_name() !== $as3cf_item->provider() ) {
2408
// File not uploaded to required provider
2409
return false;
2410
}
2411
2412
+ return $as3cf_item;
2413
}
2414
2415
/**
2450
return str_replace( $file_name, $encoded_file_name, $file );
2451
}
2452
2453
/**
2454
* Allow processes to update the file on provider via update_attached_file()
2455
*
2463
return $file;
2464
}
2465
2466
+ $as3cf_item = Media_Library_Item::get_by_source_id( $attachment_id );
2467
+
2468
+ if ( ! $as3cf_item ) {
2469
return $file;
2470
}
2471
2472
+ $file = apply_filters( 'as3cf_update_attached_file', $file, $attachment_id, $as3cf_item );
2473
2474
return $file;
2475
}
2485
* @return string
2486
*/
2487
function get_attached_file( $file, $attachment_id ) {
2488
+ if ( file_exists( $file ) || ! ( $as3cf_item = $this->is_attachment_served_by_provider( $attachment_id ) ) ) {
2489
return $file;
2490
}
2491
2492
$url = $this->get_attachment_url( $attachment_id );
2493
2494
// return the URL by default
2495
+ $file = apply_filters( 'as3cf_get_attached_file', $url, $file, $attachment_id, $as3cf_item );
2496
2497
return $file;
2498
}
2720
);
2721
}
2722
2723
+ /**
2724
+ * What is the default provider for legacy data?
2725
+ *
2726
+ * @return string
2727
+ */
2728
+ public static function get_default_provider() {
2729
+ return static::$default_provider;
2730
+ }
2731
+
2732
/**
2733
* Returns the Provider's default region slug.
2734
*
2828
return $region;
2829
}
2830
2831
/**
2832
* AJAX handler for get_buckets()
2833
*/
3528
/**
3529
* Apply ACL to an attachment and associated files
3530
*
3531
+ * @param int $post_id
3532
+ * @param Media_Library_Item $as3cf_item
3533
+ * @param bool $private
3534
*
3535
+ * @return Media_Library_Item|bool|WP_Error
3536
*/
3537
+ public function set_attachment_acl_on_provider( $post_id, Media_Library_Item $as3cf_item, $private ) {
3538
// Return early if already set to the desired ACL
3539
+ if ( $as3cf_item->is_private() === $private ) {
3540
return false;
3541
}
3542
3543
+ $acl = $private ? $this->get_provider()->get_private_acl() : $this->get_provider()->get_default_acl();
3544
+
3545
$args = array(
3546
'ACL' => $acl,
3547
+ 'Bucket' => $as3cf_item->bucket(),
3548
+ 'Key' => $as3cf_item->path(),
3549
);
3550
3551
+ $region = empty( $as3cf_item->region() ) ? false : $as3cf_item->region();
3552
3553
try {
3554
+ $provider_client = $this->get_provider_client( $region, true );
3555
$provider_client->update_object_acl( $args );
3556
3557
+ $as3cf_item = new Media_Library_Item(
3558
+ $as3cf_item->provider(),
3559
+ $as3cf_item->region(),
3560
+ $as3cf_item->bucket(),
3561
+ $as3cf_item->path(),
3562
+ $private,
3563
+ $as3cf_item->source_id(),
3564
+ $as3cf_item->source_path(),
3565
+ wp_basename( $as3cf_item->original_source_path() ),
3566
+ $as3cf_item->private_sizes(),
3567
+ $as3cf_item->id()
3568
+ );
3569
+ $as3cf_item->save();
3570
} catch ( Exception $e ) {
3571
+ $msg = 'Error setting ACL to ' . $acl . ' for ' . $as3cf_item->path() . ': ' . $e->getMessage();
3572
AS3CF_Error::log( $msg );
3573
3574
return new WP_Error( 'acl_exception', $msg );
3575
}
3576
3577
+ return $as3cf_item;
3578
}
3579
3580
/**
3581
* Make admin notice for when object ACL has changed
3582
*
3583
+ * @param Media_Library_Item $as3cf_item
3584
*/
3585
+ function make_acl_admin_notice( Media_Library_Item $as3cf_item ) {
3586
+ $filename = wp_basename( $as3cf_item->path() );
3587
+ $acl = $as3cf_item->is_private() ? $this->get_provider()->get_private_acl() : $this->get_provider()->get_default_acl();
3588
$acl_name = $this->get_acl_display_name( $acl );
3589
$text = sprintf( __( '<strong>WP Offload Media</strong> &mdash; The file %s has been given %s permissions in the bucket.', 'amazon-s3-and-cloudfront' ), "<strong>{$filename}</strong>", "<strong>{$acl_name}</strong>" );
3590
3855
$media_counts = $this->media_counts();
3856
3857
$output .= 'Media Files: ';
3858
+ $output .= number_format_i18n( $media_counts['total'] ) . ' (paths ' . number_format_i18n( $media_counts['total_paths'] ) . ')';
3859
$output .= "\r\n";
3860
3861
$output .= 'Offloaded Media Files: ';
3862
+ $output .= number_format_i18n( $media_counts['offloaded'] ) . ' (paths ' . number_format_i18n( $media_counts['offloaded_paths'] ) . ')';
3863
$output .= "\r\n";
3864
3865
+ $output .= 'Not Offloaded Media Files: ';
3866
+ $output .= number_format_i18n( $media_counts['not_offloaded'] ) . ' (paths ' . number_format_i18n( $media_counts['not_offloaded_paths'] ) . ')';
3867
+ $output .= "\r\n";
3868
+ $output .= 'Note: Approximate values, paths *try* and discard duplicates.';
3869
+ $output .= "\r\n\r\n";
3870
+
3871
$output .= 'Number of Image Sizes: ';
3872
$sizes = count( get_intermediate_image_sizes() );
3873
$output .= number_format_i18n( $sizes );
4417
return intval( $memory_limit ) * 1024 * 1024;
4418
}
4419
4420
/**
4421
* Get the total attachment and total offloaded/not offloaded attachment counts
4422
*
4427
*/
4428
public function media_counts( $skip_transient = false, $force = false ) {
4429
if ( $skip_transient || false === ( $attachment_counts = get_site_transient( 'as3cf_attachment_counts' ) ) ) {
4430
+ $table_prefixes = $this->get_all_blog_table_prefixes();
4431
+ $total = 0;
4432
+ $total_paths = 0;
4433
+ $offloaded = 0;
4434
+ $offloaded_paths = 0;
4435
+ $not_offloaded = 0;
4436
+ $not_offloaded_paths = 0;
4437
4438
foreach ( $table_prefixes as $blog_id => $table_prefix ) {
4439
+ $this->switch_to_blog( $blog_id );
4440
+
4441
+ $counts = Media_Library_Item::count_attachments( $skip_transient, $force );
4442
+ $total += $counts['total'];
4443
+ $total_paths += $counts['total_paths'];
4444
+ $offloaded += $counts['offloaded'];
4445
+ $offloaded_paths += $counts['offloaded_paths'];
4446
+ $not_offloaded += $counts['not_offloaded'];
4447
+ $not_offloaded_paths += $counts['not_offloaded_paths'];
4448
+
4449
+ $this->restore_current_blog();
4450
}
4451
4452
$attachment_counts = array(
4453
+ 'total' => $total,
4454
+ 'total_paths' => $total_paths,
4455
+ 'offloaded' => $offloaded,
4456
+ 'offloaded_paths' => $offloaded_paths,
4457
+ 'not_offloaded' => $not_offloaded,
4458
+ 'not_offloaded_paths' => $not_offloaded_paths,
4459
);
4460
4461
set_site_transient( 'as3cf_attachment_counts', $attachment_counts, 2 * MINUTE_IN_SECONDS );
4713
}
4714
4715
/**
4716
+ * Return a formatted provider info array with display friendly defaults
4717
*
4718
+ * @param int $id
4719
*
4720
+ * @return bool|array
4721
*/
4722
+ public function get_formatted_provider_info( $id ) {
4723
+ $as3cf_item = Media_Library_Item::get_by_source_id( $id );
4724
+
4725
+ if ( ! $as3cf_item ) {
4726
+ return false;
4727
}
4728
4729
+ $provider_object = $as3cf_item->key_values();
4730
+
4731
+ // Backwards compatibility.
4732
+ $provider_object['key'] = $provider_object['path'];
4733
+ $provider_object['url'] = $this->get_attachment_provider_url( $id, $as3cf_item );
4734
4735
+ $acl = $as3cf_item->is_private() ? $this->get_provider()->get_private_acl() : $this->get_provider()->get_default_acl();
4736
$acl_info = array(
4737
'acl' => $acl,
4738
'name' => $this->get_acl_display_name( $acl ),
4739
'title' => $this->get_media_action_strings( 'change_to_private' ),
4740
);
4741
4742
+ if ( $as3cf_item->is_private() ) {
4743
$acl_info['title'] = $this->get_media_action_strings( 'change_to_public' );
4744
}
4745
4746
+ $provider_object['acl'] = $acl_info;
4747
+ $provider_object['region'] = $this->get_provider()->get_region_name( $provider_object['region'] );
4748
+ $provider_object['provider_name'] = $this->get_provider_service_name( $provider_object['provider'] );
4749
4750
return $provider_object;
4751
}
4859
return reset( $parts );
4860
}
4861
4862
/**
4863
* Has the given attachment been uploaded by this instance?
4864
*
4921
* @return string
4922
*/
4923
public function get_acl_for_intermediate_size( $attachment_id, $size ) {
4924
+ $as3cf_item = Media_Library_Item::get_by_source_id( $attachment_id );
4925
4926
+ if ( ! empty( $as3cf_item ) ) {
4927
+ return $as3cf_item->is_private_size( $size ) ? $this->get_provider()->get_private_acl() : $this->get_provider()->get_default_acl();
4928
}
4929
4930
return $this->get_provider()->get_default_acl();
classes/as3cf-filter.php CHANGED
@@ -330,6 +330,11 @@ abstract class AS3CF_Filter {
330
$attachment_ids = array();
331
332
foreach ( $matches as $image ) {
333
if ( ! preg_match( '/src=\\\?["\']+([^"\'\\\]+)/', $image, $src ) || ! isset( $src[1] ) ) {
334
// Can't determine URL, skip
335
continue;
@@ -344,11 +349,6 @@ abstract class AS3CF_Filter {
344
345
$url = AS3CF_Utils::reduce_url( $url );
346
347
- if ( ! preg_match( '/wp-image-([0-9]+)/i', $image, $class_id ) || ! isset( $class_id[1] ) ) {
348
- // Can't determine ID from class, skip
349
- continue;
350
- }
351
-
352
$attachment_ids[ $url ] = absint( $class_id[1] );
353
}
354
330
$attachment_ids = array();
331
332
foreach ( $matches as $image ) {
333
+ if ( ! preg_match( '/wp-image-([0-9]+)/i', $image, $class_id ) || ! isset( $class_id[1] ) ) {
334
+ // Can't determine ID from class, skip
335
+ continue;
336
+ }
337
+
338
if ( ! preg_match( '/src=\\\?["\']+([^"\'\\\]+)/', $image, $src ) || ! isset( $src[1] ) ) {
339
// Can't determine URL, skip
340
continue;
349
350
$url = AS3CF_Utils::reduce_url( $url );
351
352
$attachment_ids[ $url ] = absint( $class_id[1] );
353
}
354
classes/as3cf-notices.php CHANGED
@@ -351,7 +351,7 @@ class AS3CF_Notices {
351
*/
352
protected function maybe_show_notice( $notice, $dismissed_notices, $tab ) {
353
$screen = get_current_screen();
354
- if ( $notice['only_show_in_settings'] && false === strpos( $screen->id, $this->as3cf->hook_suffix ) ) {
355
return;
356
}
357
351
*/
352
protected function maybe_show_notice( $notice, $dismissed_notices, $tab ) {
353
$screen = get_current_screen();
354
+ if ( $notice['only_show_in_settings'] && false === strpos( strval( $screen->id ), $this->as3cf->hook_suffix ) ) {
355
return;
356
}
357
classes/as3cf-plugin-compatibility.php CHANGED
@@ -9,6 +9,7 @@
9
* @since 0.8.3
10
*/
11
12
use DeliciousBrains\WP_Offload_Media\Providers\Provider;
13
14
// Exit if accessed directly
@@ -129,21 +130,21 @@ class AS3CF_Plugin_Compatibility {
129
* Allow any process to trigger the copy back to local with
130
* the filter 'as3cf_get_attached_file_copy_back_to_local'
131
*
132
- * @param string $url
133
- * @param string $file
134
- * @param int $attachment_id
135
- * @param array $provider_object
136
*
137
* @return string
138
*/
139
- function legacy_copy_back_to_local( $url, $file, $attachment_id, $provider_object ) {
140
- $copy_back_to_local = apply_filters( 'as3cf_get_attached_file_copy_back_to_local', false, $file, $attachment_id, $provider_object );
141
if ( false === $copy_back_to_local ) {
142
// Not copying back file
143
return $url;
144
}
145
146
- if ( ( $file = $this->copy_provider_file_to_server( $provider_object, $file ) ) ) {
147
// Return the file if successfully downloaded from S3
148
return $file;
149
};
@@ -189,14 +190,14 @@ class AS3CF_Plugin_Compatibility {
189
/**
190
* Prevent subsequent attempts to copy back after upload and remove.
191
*
192
- * @param bool $copy_back_to_local
193
- * @param string $file
194
- * @param integer $attachment_id
195
- * @param array $provider_object
196
*
197
* @return bool
198
*/
199
- public function prevent_copy_back_to_local_after_remove( $copy_back_to_local, $file, $attachment_id, $provider_object ) {
200
if ( $copy_back_to_local && in_array( $file, $this->removed_files ) ) {
201
$copy_back_to_local = false;
202
}
@@ -261,20 +262,20 @@ class AS3CF_Plugin_Compatibility {
261
/**
262
* Generic method for copying back an S3 file to the server on a specific AJAX action
263
*
264
- * @param string $action_key Action that must be in process
265
- * @param bool $ajax Must the process be an AJAX one?
266
- * @param string $url S3 URL
267
- * @param string $file Local file path of image
268
- * @param array $provider_object S3 meta data
269
*
270
* @return string
271
*/
272
- function copy_image_to_server_on_action( $action_key, $ajax, $url, $file, $provider_object ) {
273
if ( false === $this->maybe_process_on_action( $action_key, $ajax ) ) {
274
return $url;
275
}
276
277
- if ( ( $file = $this->copy_provider_file_to_server( $provider_object, $file ) ) ) {
278
// Return the file if successfully downloaded from S3
279
return $file;
280
};
@@ -320,14 +321,33 @@ class AS3CF_Plugin_Compatibility {
320
return $pre;
321
}
322
323
- $provider_object = $this->as3cf->get_attachment_provider_info( $post_id );
324
- $this->remove_edited_image_files( $post_id, $provider_object );
325
326
// Update object key with original filename
327
- $restored_filename = wp_basename( $data['file'] );
328
- $old_filename = wp_basename( $provider_object['key'] );
329
- $provider_object['key'] = str_replace( $old_filename, $restored_filename, $provider_object['key'] );
330
- update_post_meta( $post_id, 'amazonS3_info', $provider_object );
331
332
return true;
333
}
@@ -335,55 +355,63 @@ class AS3CF_Plugin_Compatibility {
335
/**
336
* Remove edited image files from S3.
337
*
338
- * @param int $attachment_id
339
- * @param array $provider_object
340
*/
341
- protected function remove_edited_image_files( $attachment_id, $provider_object ) {
342
- $bucket = $provider_object['bucket'];
343
- $region = $this->as3cf->get_provider_object_region( $provider_object );
344
- $keys = AS3CF_Utils::get_attachment_edited_keys( $attachment_id, $provider_object );
345
346
if ( empty( $keys ) ) {
347
return;
348
}
349
350
- $this->as3cf->delete_objects( $region, $bucket, $keys );
351
}
352
353
/**
354
* Allow the WordPress Image Editor to edit files that have been copied to S3
355
* but removed from the local server, by copying them back temporarily
356
*
357
- * @param string $url
358
- * @param string $file
359
- * @param int $attachment_id
360
- * @param array $provider_object
361
*
362
* @return string
363
*/
364
- function image_editor_download_file( $url, $file, $attachment_id, $provider_object ) {
365
if ( ! $this->is_ajax() ) {
366
return $url;
367
}
368
369
// When the image-editor restores the original it requests the edited image,
370
// but we actually need to copy back the original image at this point
371
- // for the restore to be successful and edited images to be deleted from S3
372
// via image_editor_remove_files()
373
if ( isset( $_POST['do'] ) && 'restore' == $_POST['do'] ) {
374
$backup_sizes = get_post_meta( $attachment_id, '_wp_attachment_backup_sizes', true );
375
$original_filename = $backup_sizes['full-orig']['file'];
376
377
- $orig_provider = $provider_object;
378
- $orig_provider['key'] = dirname( $provider_object['key'] ) . '/' . $original_filename;
379
- $orig_file = dirname( $file ) . '/' . $original_filename;
380
381
// Copy the original file back to the server for the restore process
382
- $this->copy_provider_file_to_server( $orig_provider, $orig_file );
383
384
// Copy the edited file back to the server as well, it will be cleaned up later
385
- if ( $provider_file = $this->copy_provider_file_to_server( $provider_object, $file ) ) {
386
- // Return the file if successfully downloaded from S3
387
return $provider_file;
388
};
389
}
@@ -394,8 +422,8 @@ class AS3CF_Plugin_Compatibility {
394
foreach ( debug_backtrace() as $caller ) {
395
if ( isset( $caller['function'] ) && '_load_image_to_edit_path' == $caller['function'] ) {
396
// check this has been called by '_load_image_to_edit_path' so as only to copy back once
397
- if ( $provider_file = $this->copy_provider_file_to_server( $provider_object, $file ) ) {
398
- // Return the file if successfully downloaded from S3
399
return $provider_file;
400
};
401
}
@@ -435,7 +463,7 @@ class AS3CF_Plugin_Compatibility {
435
*
436
* @return bool
437
*/
438
- protected function is_customizer_crop_action() {
439
$header_crop = $this->maybe_process_on_action( 'custom-header-crop', true );
440
441
$context = array( 'site-icon', 'custom_logo' );
@@ -450,17 +478,17 @@ class AS3CF_Plugin_Compatibility {
450
}
451
452
/**
453
- * Allow the WordPress Customizer to crop images that have been copied to S3
454
- * but removed from the local server, by copying them back temporarily
455
*
456
- * @param string $url
457
- * @param string $file
458
- * @param int $attachment_id
459
- * @param array $provider_object
460
*
461
* @return string
462
*/
463
- public function customizer_crop_download_file( $url, $file, $attachment_id, $provider_object ) {
464
if ( false === $this->is_customizer_crop_action() ) {
465
return $url;
466
}
@@ -469,8 +497,8 @@ class AS3CF_Plugin_Compatibility {
469
return $url;
470
}
471
472
- if ( ( $file = $this->copy_provider_file_to_server( $provider_object, $file ) ) ) {
473
- // Return the file if successfully downloaded from S3
474
return $file;
475
};
476
@@ -512,84 +540,69 @@ class AS3CF_Plugin_Compatibility {
512
if ( ! is_null( $post_id ) ) {
513
return $post_id;
514
}
515
- $url = parse_url( $url );
516
517
- if ( ! isset( $url['path'] ) ) {
518
- return $post_id; // URL path can't be determined
519
- }
520
-
521
- $key1 = ltrim( $url['path'], '/' );
522
- $length1 = strlen( $key1 );
523
-
524
- // URLs may contain the bucket name within the path, therefore we must
525
- // also perform the search with the first path segment removed
526
- $parts = explode( '/', $key1 );
527
- unset( $parts[0] );
528
-
529
- $key2 = implode( '/', $parts );
530
- $length2 = strlen( $key2 );
531
532
- global $wpdb;
533
- $sql = $wpdb->prepare( "
534
- SELECT `post_id`
535
- FROM `{$wpdb->prefix}postmeta`
536
- WHERE `{$wpdb->prefix}postmeta`.`meta_key` = 'amazonS3_info'
537
- AND ( `{$wpdb->prefix}postmeta`.`meta_value` LIKE %s
538
- OR `{$wpdb->prefix}postmeta`.`meta_value` LIKE %s )
539
- ",
540
- "%s:3:\"key\";s:{$length1}:\"{$key1}\";%",
541
- "%s:3:\"key\";s:{$length2}:\"{$key2}\";%"
542
- );
543
-
544
- if ( $id = $wpdb->get_var( $sql ) ) {
545
- return $id;
546
}
547
548
- return $post_id; // No attachment found on S3
549
}
550
551
/**
552
- * Allow the Regenerate Thumbnails plugin to copy the S3 file back to the local
553
- * server when the file is missing on the server via get_attached_file
554
*
555
- * @param string $url
556
- * @param string $file
557
- * @param int $attachment_id
558
- * @param array $provider_object
559
*
560
* @return string
561
*/
562
- function regenerate_thumbnails_download_file( $url, $file, $attachment_id, $provider_object ) {
563
- return $this->copy_image_to_server_on_action( 'regeneratethumbnail', true, $url, $file, $provider_object );
564
}
565
566
/**
567
- * Download a file from S3 if the file does not exist locally and places it where
568
* the attachment's file should be.
569
*
570
- * @param array $provider_object
571
- * @param string $file
572
*
573
* @return string|bool File if downloaded, false on failure
574
*/
575
- public function copy_provider_file_to_server( $provider_object, $file ) {
576
// Make sure the directory exists
577
$dir = dirname( $file );
578
if ( ! wp_mkdir_p( $dir ) ) {
579
$error_message = sprintf( __( 'The local directory %s does not exist and could not be created.', 'amazon-s3-and-cloudfront' ), $dir );
580
- AS3CF_Error::log( sprintf( __( 'There was an error attempting to download the file %s from the bucket: %s', 'amazon-s3-and-cloudfront' ), $provider_object['key'], $error_message ) );
581
582
return false;
583
}
584
585
try {
586
- $this->as3cf->get_provider_client( $provider_object['region'], true )->get_object( array(
587
- 'Bucket' => $provider_object['bucket'],
588
- 'Key' => $provider_object['key'],
589
'SaveAs' => $file,
590
) );
591
} catch ( Exception $e ) {
592
- AS3CF_Error::log( sprintf( __( 'There was an error attempting to download the file %s from the bucket: %s', 'amazon-s3-and-cloudfront' ), $provider_object['key'], $e->getMessage() ) );
593
594
return false;
595
}
@@ -627,25 +640,25 @@ class AS3CF_Plugin_Compatibility {
627
* Allow access to the remote file via the stream wrapper.
628
* This is useful for compatibility with plugins when attachments are removed from the local server after upload.
629
*
630
- * @param string $url
631
- * @param string $file
632
- * @param int $attachment_id
633
- * @param array $provider_object
634
*
635
* @return string
636
* @throws Exception
637
*/
638
- public function get_stream_wrapper_file( $url, $file, $attachment_id, $provider_object ) {
639
if ( $url === $file ) {
640
// Abort if an earlier hook to get the file has been called and it has been copied back.
641
return $file;
642
}
643
644
// Make sure the region stream wrapper is registered.
645
- $client = $this->register_stream_wrapper( $provider_object['region'] );
646
647
if ( ! empty( $client ) ) {
648
- return $client->prepare_stream_wrapper_file( $provider_object['region'], $provider_object['bucket'], $provider_object['key'] );
649
}
650
651
return $url;
@@ -775,7 +788,7 @@ class AS3CF_Plugin_Compatibility {
775
}
776
777
/**
778
- * Alter the image meta data to add srcset support for object versioned S3 URLs
779
*
780
* @param array $image_meta
781
* @param array $size_array
@@ -795,14 +808,14 @@ class AS3CF_Plugin_Compatibility {
795
return $image_meta;
796
}
797
798
- if ( ! ( $provider_object = $this->as3cf->is_attachment_served_by_provider( $attachment_id ) ) ) {
799
// Attachment not uploaded to S3, abort
800
return $image_meta;
801
}
802
803
$image_basename = $this->as3cf->encode_filename_in_path( wp_basename( $image_meta['file'] ) );
804
805
- if ( false === strpos( $this->as3cf->encode_filename_in_path( $provider_object['key'] ), $image_basename ) ) {
806
// Not the correct attachment, abort
807
return $image_meta;
808
}
@@ -825,7 +838,7 @@ class AS3CF_Plugin_Compatibility {
825
}
826
827
/**
828
- * Replace local URLs with S3 ones for srcset image sources
829
*
830
* @param array $sources
831
* @param array $size_array
@@ -841,7 +854,7 @@ class AS3CF_Plugin_Compatibility {
841
return $sources;
842
}
843
844
- if ( ! ( $provider_object = $this->as3cf->is_attachment_served_by_provider( $attachment_id ) ) ) {
845
// Attachment not uploaded to S3, abort
846
return $sources;
847
}
@@ -849,7 +862,7 @@ class AS3CF_Plugin_Compatibility {
849
foreach ( $sources as $width => $source ) {
850
$filename = wp_basename( $source['url'] );
851
$size = $this->find_image_size_from_width( $image_meta['sizes'], $width, $filename );
852
- $provider_url = $this->as3cf->get_attachment_provider_url( $attachment_id, $provider_object, null, $size, $image_meta );
853
854
if ( false === $provider_url || is_wp_error( $provider_url ) ) {
855
// Skip URLs not offloaded to S3
9
* @since 0.8.3
10
*/
11
12
+ use DeliciousBrains\WP_Offload_Media\Items\Media_Library_Item;
13
use DeliciousBrains\WP_Offload_Media\Providers\Provider;
14
15
// Exit if accessed directly
130
* Allow any process to trigger the copy back to local with
131
* the filter 'as3cf_get_attached_file_copy_back_to_local'
132
*
133
+ * @param string $url
134
+ * @param string $file
135
+ * @param int $attachment_id
136
+ * @param Media_Library_Item $as3cf_item
137
*
138
* @return string
139
*/
140
+ function legacy_copy_back_to_local( $url, $file, $attachment_id, Media_Library_Item $as3cf_item ) {
141
+ $copy_back_to_local = apply_filters( 'as3cf_get_attached_file_copy_back_to_local', false, $file, $attachment_id, $as3cf_item );
142
if ( false === $copy_back_to_local ) {
143
// Not copying back file
144
return $url;
145
}
146
147
+ if ( ( $file = $this->copy_provider_file_to_server( $as3cf_item, $file ) ) ) {
148
// Return the file if successfully downloaded from S3
149
return $file;
150
};
190
/**
191
* Prevent subsequent attempts to copy back after upload and remove.
192
*
193
+ * @param bool $copy_back_to_local
194
+ * @param string $file
195
+ * @param integer $attachment_id
196
+ * @param Media_Library_Item $as3cf_item
197
*
198
* @return bool
199
*/
200
+ public function prevent_copy_back_to_local_after_remove( $copy_back_to_local, $file, $attachment_id, Media_Library_Item $as3cf_item ) {
201
if ( $copy_back_to_local && in_array( $file, $this->removed_files ) ) {
202
$copy_back_to_local = false;
203
}
262
/**
263
* Generic method for copying back an S3 file to the server on a specific AJAX action
264
*
265
+ * @param string $action_key Action that must be in process
266
+ * @param bool $ajax Must the process be an AJAX one?
267
+ * @param string $url S3 URL
268
+ * @param string $file Local file path of image
269
+ * @param Media_Library_Item $as3cf_item S3 meta data
270
*
271
* @return string
272
*/
273
+ function copy_image_to_server_on_action( $action_key, $ajax, $url, $file, Media_Library_Item $as3cf_item ) {
274
if ( false === $this->maybe_process_on_action( $action_key, $ajax ) ) {
275
return $url;
276
}
277
278
+ if ( ( $file = $this->copy_provider_file_to_server( $as3cf_item, $file ) ) ) {
279
// Return the file if successfully downloaded from S3
280
return $file;
281
};
321
return $pre;
322
}
323
324
+ $as3cf_item = Media_Library_Item::get_by_source_id( $post_id );
325
+
326
+ if ( ! $as3cf_item ) {
327
+ return $pre;
328
+ }
329
+
330
+ $this->remove_edited_image_files( $post_id, $as3cf_item );
331
332
// Update object key with original filename
333
+ $restored_filename = wp_basename( $data['file'] );
334
+ $old_filename = wp_basename( $as3cf_item->path() );
335
+ $item_path = str_replace( $old_filename, $restored_filename, $as3cf_item->path() );
336
+
337
+ $as3cf_item = new Media_Library_Item(
338
+ $as3cf_item->provider(),
339
+ $as3cf_item->region(),
340
+ $as3cf_item->bucket(),
341
+ $item_path,
342
+ $as3cf_item->is_private(),
343
+ $as3cf_item->source_id(),
344
+ $as3cf_item->source_path(),
345
+ wp_basename( $as3cf_item->original_source_path() ),
346
+ $as3cf_item->private_sizes(),
347
+ $as3cf_item->id()
348
+ );
349
+
350
+ $as3cf_item->save();
351
352
return true;
353
}
355
/**
356
* Remove edited image files from S3.
357
*
358
+ * @param int $attachment_id
359
+ * @param Media_Library_Item $as3cf_item
360
*/
361
+ protected function remove_edited_image_files( $attachment_id, Media_Library_Item $as3cf_item ) {
362
+ $keys = AS3CF_Utils::get_attachment_edited_keys( $attachment_id, $as3cf_item );
363
364
if ( empty( $keys ) ) {
365
return;
366
}
367
368
+ $this->as3cf->delete_objects( $as3cf_item->region(), $as3cf_item->bucket(), $keys );
369
}
370
371
/**
372
* Allow the WordPress Image Editor to edit files that have been copied to S3
373
* but removed from the local server, by copying them back temporarily
374
*
375
+ * @param string $url
376
+ * @param string $file
377
+ * @param int $attachment_id
378
+ * @param Media_Library_Item $as3cf_item
379
*
380
* @return string
381
*/
382
+ function image_editor_download_file( $url, $file, $attachment_id, Media_Library_Item $as3cf_item ) {
383
if ( ! $this->is_ajax() ) {
384
return $url;
385
}
386
387
// When the image-editor restores the original it requests the edited image,
388
// but we actually need to copy back the original image at this point
389
+ // for the restore to be successful and edited images to be deleted from the bucket
390
// via image_editor_remove_files()
391
if ( isset( $_POST['do'] ) && 'restore' == $_POST['do'] ) {
392
$backup_sizes = get_post_meta( $attachment_id, '_wp_attachment_backup_sizes', true );
393
$original_filename = $backup_sizes['full-orig']['file'];
394
395
+ $as3cf_item_orig = new Media_Library_Item(
396
+ $as3cf_item->provider(),
397
+ $as3cf_item->region(),
398
+ $as3cf_item->bucket(),
399
+ dirname( $as3cf_item->path() ) . '/' . $original_filename,
400
+ $as3cf_item->is_private(),
401
+ $as3cf_item->source_id(),
402
+ $as3cf_item->source_path(),
403
+ wp_basename( $as3cf_item->original_source_path() ),
404
+ $as3cf_item->private_sizes(),
405
+ $as3cf_item->id()
406
+ );
407
+ $orig_file = dirname( $file ) . '/' . $original_filename;
408
409
// Copy the original file back to the server for the restore process
410
+ $this->copy_provider_file_to_server( $as3cf_item_orig, $orig_file );
411
412
// Copy the edited file back to the server as well, it will be cleaned up later
413
+ if ( $provider_file = $this->copy_provider_file_to_server( $as3cf_item, $file ) ) {
414
+ // Return the file if successfully downloaded from bucket.
415
return $provider_file;
416
};
417
}
422
foreach ( debug_backtrace() as $caller ) {
423
if ( isset( $caller['function'] ) && '_load_image_to_edit_path' == $caller['function'] ) {
424
// check this has been called by '_load_image_to_edit_path' so as only to copy back once
425
+ if ( $provider_file = $this->copy_provider_file_to_server( $as3cf_item, $file ) ) {
426
+ // Return the file if successfully downloaded from bucket.
427
return $provider_file;
428
};
429
}
463
*
464
* @return bool
465
*/
466
+ public function is_customizer_crop_action() {
467
$header_crop = $this->maybe_process_on_action( 'custom-header-crop', true );
468
469
$context = array( 'site-icon', 'custom_logo' );
478
}
479
480
/**
481
+ * Allow the WordPress Customizer to crop images that have been copied to bucket
482
+ * but removed from the local server, by copying them back temporarily.
483
*
484
+ * @param string $url
485
+ * @param string $file
486
+ * @param int $attachment_id
487
+ * @param Media_Library_Item $as3cf_item
488
*
489
* @return string
490
*/
491
+ public function customizer_crop_download_file( $url, $file, $attachment_id, Media_Library_Item $as3cf_item ) {
492
if ( false === $this->is_customizer_crop_action() ) {
493
return $url;
494
}
497
return $url;
498
}
499
500
+ if ( ( $file = $this->copy_provider_file_to_server( $as3cf_item, $file ) ) ) {
501
+ // Return the file if successfully downloaded from bucket.
502
return $file;
503
};
504
540
if ( ! is_null( $post_id ) ) {
541
return $post_id;
542
}
543
544
+ // There seems to be a bug in the WP Customizer whereby sometimes it puts the attachment ID on the URL.
545
+ if ( is_numeric( $url ) ) {
546
+ $as3cf_item = Media_Library_Item::get_by_source_id( $url );
547
548
+ // If we found an offloaded Media Library item for that ID, job's a good'n'.
549
+ if ( $as3cf_item ) {
550
+ $post_id = $url;
551
+ }
552
+ } else {
553
+ $post_id = $this->as3cf->filter_provider->get_attachment_id_from_url( $url );
554
}
555
556
+ // Must return null if not found.
557
+ if ( empty( $post_id ) ) {
558
+ return null;
559
+ } else {
560
+ return $post_id;
561
+ }
562
}
563
564
/**
565
+ * Allow the Regenerate Thumbnails plugin to copy the bucket file back to the local
566
+ * server when the file is missing on the server via get_attached_file.
567
*
568
+ * @param string $url
569
+ * @param string $file
570
+ * @param int $attachment_id
571
+ * @param Media_Library_Item $as3cf_item
572
*
573
* @return string
574
*/
575
+ function regenerate_thumbnails_download_file( $url, $file, $attachment_id, Media_Library_Item $as3cf_item ) {
576
+ return $this->copy_image_to_server_on_action( 'regeneratethumbnail', true, $url, $file, $as3cf_item );
577
}
578
579
/**
580
+ * Download a file from bucket if the file does not exist locally and places it where
581
* the attachment's file should be.
582
*
583
+ * @param Media_Library_Item $as3cf_item
584
+ * @param string $file
585
*
586
* @return string|bool File if downloaded, false on failure
587
*/
588
+ public function copy_provider_file_to_server( Media_Library_Item $as3cf_item, $file ) {
589
// Make sure the directory exists
590
$dir = dirname( $file );
591
if ( ! wp_mkdir_p( $dir ) ) {
592
$error_message = sprintf( __( 'The local directory %s does not exist and could not be created.', 'amazon-s3-and-cloudfront' ), $dir );
593
+ AS3CF_Error::log( sprintf( __( 'There was an error attempting to download the file %s from the bucket: %s', 'amazon-s3-and-cloudfront' ), $as3cf_item->path(), $error_message ) );
594
595
return false;
596
}
597
598
try {
599
+ $this->as3cf->get_provider_client( $as3cf_item->region(), true )->get_object( array(
600
+ 'Bucket' => $as3cf_item->bucket(),
601
+ 'Key' => $as3cf_item->path(),
602
'SaveAs' => $file,
603
) );
604
} catch ( Exception $e ) {
605
+ AS3CF_Error::log( sprintf( __( 'There was an error attempting to download the file %s from the bucket: %s', 'amazon-s3-and-cloudfront' ), $as3cf_item->path(), $e->getMessage() ) );
606
607
return false;
608
}
640
* Allow access to the remote file via the stream wrapper.
641
* This is useful for compatibility with plugins when attachments are removed from the local server after upload.
642
*
643
+ * @param string $url
644
+ * @param string $file
645
+ * @param int $attachment_id
646
+ * @param Media_Library_Item $as3cf_item
647
*
648
* @return string
649
* @throws Exception
650
*/
651
+ public function get_stream_wrapper_file( $url, $file, $attachment_id, Media_Library_Item $as3cf_item ) {
652
if ( $url === $file ) {
653
// Abort if an earlier hook to get the file has been called and it has been copied back.
654
return $file;
655
}
656
657
// Make sure the region stream wrapper is registered.
658
+ $client = $this->register_stream_wrapper( $as3cf_item->region() );
659
660
if ( ! empty( $client ) ) {
661
+ return $client->prepare_stream_wrapper_file( $as3cf_item->region(), $as3cf_item->bucket(), $as3cf_item->path() );
662
}
663
664
return $url;
788
}
789
790
/**
791
+ * Alter the image meta data to add srcset support for object versioned provider URLs.
792
*
793
* @param array $image_meta
794
* @param array $size_array
808
return $image_meta;
809
}
810
811
+ if ( ! ( $as3cf_item = $this->as3cf->is_attachment_served_by_provider( $attachment_id ) ) ) {
812
// Attachment not uploaded to S3, abort
813
return $image_meta;
814
}
815
816
$image_basename = $this->as3cf->encode_filename_in_path( wp_basename( $image_meta['file'] ) );
817
818
+ if ( false === strpos( $this->as3cf->encode_filename_in_path( $as3cf_item->path() ), $image_basename ) ) {
819
// Not the correct attachment, abort
820
return $image_meta;
821
}
838
}
839
840
/**
841
+ * Replace local URLs with provider ones for srcset image sources.
842
*
843
* @param array $sources
844
* @param array $size_array
854
return $sources;
855
}
856
857
+ if ( ! ( $as3cf_item = $this->as3cf->is_attachment_served_by_provider( $attachment_id ) ) ) {
858
// Attachment not uploaded to S3, abort
859
return $sources;
860
}
862
foreach ( $sources as $width => $source ) {
863
$filename = wp_basename( $source['url'] );
864
$size = $this->find_image_size_from_width( $image_meta['sizes'], $width, $filename );
865
+ $provider_url = $this->as3cf->get_attachment_provider_url( $attachment_id, $as3cf_item, null, $size, $image_meta );
866
867
if ( false === $provider_url || is_wp_error( $provider_url ) ) {
868
// Skip URLs not offloaded to S3
classes/as3cf-utils.php CHANGED
@@ -9,6 +9,8 @@
9
*/
10
11
// Exit if accessed directly
12
if ( ! defined( 'ABSPATH' ) ) {
13
exit;
14
}
@@ -98,9 +100,9 @@ if ( ! class_exists( 'AS3CF_Utils' ) ) {
98
/**
99
* Parses a URL into its components. Compatible with PHP < 5.4.7.
100
*
101
- * @param string $url The URL to parse.
102
*
103
- * @param int $component PHP_URL_ constant for URL component to return.
104
*
105
* @return mixed An array of the parsed components, mixed for a requested component, or false on error.
106
*/
@@ -197,6 +199,11 @@ if ( ! class_exists( 'AS3CF_Utils' ) ) {
197
$paths['thumb'] = str_replace( $file_name, $meta['thumb'], $file_path );
198
}
199
200
// Sizes
201
if ( isset( $meta['sizes'] ) ) {
202
foreach ( $meta['sizes'] as $size => $file ) {
@@ -254,13 +261,13 @@ if ( ! class_exists( 'AS3CF_Utils' ) ) {
254
/**
255
* Get an attachment's edited S3 keys.
256
*
257
- * @param int $attachment_id
258
- * @param array $provider_object
259
*
260
* @return array
261
*/
262
- public static function get_attachment_edited_keys( $attachment_id, $provider_object ) {
263
- $prefix = trailingslashit( pathinfo( $provider_object['key'], PATHINFO_DIRNAME ) );
264
$paths = self::get_attachment_edited_file_paths( $attachment_id );
265
$paths = array_map( function ( $path ) use ( $prefix ) {
266
return array( 'Key' => $prefix . wp_basename( $path ) );
@@ -493,5 +500,79 @@ if ( ! class_exists( 'AS3CF_Utils' ) ) {
493
494
return $domain;
495
}