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
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
496
  }
497
  }
9
  */
10
 
11
  // Exit if accessed directly
12
+ use DeliciousBrains\WP_Offload_Media\Items\Media_Library_Item;
13
+
14
  if ( ! defined( 'ABSPATH' ) ) {
15
  exit;
16
  }
100
  /**
101
  * Parses a URL into its components. Compatible with PHP < 5.4.7.
102
  *
103
+ * @param string $url The URL to parse.
104
  *
105
+ * @param int $component PHP_URL_ constant for URL component to return.
106
  *
107
  * @return mixed An array of the parsed components, mixed for a requested component, or false on error.
108
  */
199
  $paths['thumb'] = str_replace( $file_name, $meta['thumb'], $file_path );
200
  }
201
 
202
+ // Original Image (when large image scaled down to threshold size and used as "full").
203
+ if ( isset( $meta['original_image'] ) ) {
204
+ $paths['original_image'] = str_replace( $file_name, $meta['original_image'], $file_path );
205
+ }
206
+
207
  // Sizes
208
  if ( isset( $meta['sizes'] ) ) {
209
  foreach ( $meta['sizes'] as $size => $file ) {
261
  /**
262
  * Get an attachment's edited S3 keys.
263
  *
264
+ * @param int $attachment_id
265
+ * @param Media_Library_Item $as3cf_item
266
  *
267
  * @return array
268
  */
269
+ public static function get_attachment_edited_keys( $attachment_id, Media_Library_Item $as3cf_item ) {
270
+ $prefix = trailingslashit( $as3cf_item->normalized_path_dir() );
271
  $paths = self::get_attachment_edited_file_paths( $attachment_id );
272
  $paths = array_map( function ( $path ) use ( $prefix ) {
273
  return array( 'Key' => $prefix . wp_basename( $path ) );
500
 
501
  return $domain;
502
  }
503
+
504
+ /**
505
+ * Decode file name in potentially URL encoded URL path.
506
+ *
507
+ * @param string $file
508
+ *
509
+ * @return string
510
+ */
511
+ public static function decode_filename_in_path( $file ) {
512
+ $url = parse_url( $file );
513
+
514
+ if ( ! isset( $url['path'] ) ) {
515
+ // Can't determine path, return original
516
+ return $file;
517
+ }
518
+
519
+ $file_name = wp_basename( $url['path'] );
520
+
521
+ if ( false === strpos( $file_name, '%' ) ) {
522
+ // File name not encoded, return original
523
+ return $file;
524
+ }
525
+
526
+ $decoded_file_name = rawurldecode( $file_name );
527
+
528
+ return str_replace( $file_name, $decoded_file_name, $file );
529
+ }
530
+
531
+ /**
532
+ * Returns indexed array of full size paths, e.g. orig and edited.
533
+ *
534
+ * @param array $paths Associative array of sizes and relative paths
535
+ *
536
+ * @return array
537
+ *
538
+ * @see get_attachment_file_paths
539
+ */
540
+ public static function fullsize_paths( $paths ) {
541
+ if ( is_array( $paths ) && ! empty( $paths ) ) {
542
+ return array_values( array_unique( array_intersect_key( $paths, array_flip( array( 'original', 'file', 'full-orig', 'original_image' ) ) ) ) );
543
+ } else {
544
+ return array();
545
+ }
546
+ }
547
+
548
+ /**
549
+ * Converts an array of upload file paths to all be relative paths.
550
+ * If any path is not absolute or does begin with current uploads base dir it will not be altered.
551
+ *
552
+ * @param array $paths Array of upload file paths, absolute or relative.
553
+ *
554
+ * @return array Input array with values switched to relative upload file paths.
555
+ */
556
+ public static function make_upload_file_paths_relative( $paths ) {
557
+ if ( empty( $paths ) ) {
558
+ return array();
559
+ }
560
+
561
+ if ( ! is_array( $paths ) ) {
562
+ $paths = array( $paths );
563
+ }
564
+
565
+ $uploads = wp_upload_dir();
566
+ $basedir = trailingslashit( $uploads['basedir'] );
567
+ $offset = strlen( $basedir );
568
+
569
+ foreach ( $paths as $key => $path ) {
570
+ if ( 0 === strpos( $path, $basedir ) ) {
571
+ $paths[ $key ] = substr( $path, $offset );
572
+ }
573
+ }
574
+
575
+ return $paths;
576
+ }
577
  }
578
  }
classes/filters/as3cf-local-to-s3.php CHANGED
@@ -1,5 +1,7 @@
1
  <?php
2
 
 
 
3
  class AS3CF_Local_To_S3 extends AS3CF_Filter {
4
 
5
  /**
@@ -207,7 +209,7 @@ class AS3CF_Local_To_S3 extends AS3CF_Filter {
207
  return $this->query_cache[ $full_url ];
208
  }
209
 
210
- $path = $this->as3cf->decode_filename_in_path( ltrim( str_replace( $this->get_bare_upload_base_urls(), '', $full_url ), '/' ) );
211
 
212
  $sql = $wpdb->prepare( "
213
  SELECT post_id FROM {$wpdb->postmeta}
@@ -237,8 +239,6 @@ class AS3CF_Local_To_S3 extends AS3CF_Filter {
237
  * @return array url => attachment ID (or false)
238
  */
239
  protected function get_attachment_ids_from_urls( $urls ) {
240
- global $wpdb;
241
-
242
  $results = array();
243
 
244
  if ( empty( $urls ) ) {
@@ -262,35 +262,35 @@ class AS3CF_Local_To_S3 extends AS3CF_Filter {
262
  continue;
263
  }
264
 
265
- $path = $this->as3cf->decode_filename_in_path( ltrim( str_replace( $this->get_bare_upload_base_urls(), '', $full_url ), '/' ) );
266
 
267
  $paths[ $path ] = $full_url;
268
  $full_urls[ $full_url ][] = $url;
269
- $meta_values[] = "'" . esc_sql( $path ) . "'";
270
  }
271
 
272
- if ( ! empty( $meta_values ) ) {
273
- $sql = "
274
- SELECT post_id, meta_value FROM {$wpdb->postmeta}
275
- WHERE meta_key = '_wp_attached_file'
276
- AND meta_value IN ( " . implode( ',', array_unique( $meta_values ) ) . " )
277
- ORDER BY post_id
278
- ";
279
-
280
- $query_results = $wpdb->get_results( $sql );
281
-
282
- if ( ! empty( $query_results ) ) {
283
- foreach ( $query_results as $postmeta ) {
284
- $full_url = $paths[ $postmeta->meta_value ];
285
-
286
- if ( ! empty( $full_urls[ $full_url ] ) ) {
287
- $attachment_id = (int) $postmeta->post_id;
288
- $this->query_cache[ $full_url ] = $attachment_id;
289
-
290
- foreach ( $full_urls[ $full_url ] as $url ) {
291
- $results[ $url ] = $attachment_id;
 
 
292
  }
293
- unset( $full_urls[ $full_url ] );
294
  }
295
  }
296
  }
@@ -319,7 +319,7 @@ class AS3CF_Local_To_S3 extends AS3CF_Filter {
319
  * @return string
320
  */
321
  protected function normalize_find_value( $url ) {
322
- return $this->as3cf->decode_filename_in_path( $url );
323
  }
324
 
325
  /**
1
  <?php
2
 
3
+ use DeliciousBrains\WP_Offload_Media\Items\Media_Library_Item;
4
+
5
  class AS3CF_Local_To_S3 extends AS3CF_Filter {
6
 
7
  /**
209
  return $this->query_cache[ $full_url ];
210
  }
211
 
212
+ $path = AS3CF_Utils::decode_filename_in_path( ltrim( str_replace( $this->get_bare_upload_base_urls(), '', $full_url ), '/' ) );
213
 
214
  $sql = $wpdb->prepare( "
215
  SELECT post_id FROM {$wpdb->postmeta}
239
  * @return array url => attachment ID (or false)
240
  */
241
  protected function get_attachment_ids_from_urls( $urls ) {
 
 
242
  $results = array();
243
 
244
  if ( empty( $urls ) ) {
262
  continue;
263
  }
264
 
265
+ $path = AS3CF_Utils::decode_filename_in_path( ltrim( str_replace( $this->get_bare_upload_base_urls(), '', $full_url ), '/' ) );
266
 
267
  $paths[ $path ] = $full_url;
268
  $full_urls[ $full_url ][] = $url;
 
269
  }
270
 
271
+ if ( ! empty( $paths ) ) {
272
+ $as3cf_items = Media_Library_Item::get_by_source_path( array_keys( $paths ) );
273
+
274
+ if ( ! empty( $as3cf_items ) ) {
275
+ /* @var Media_Library_Item $as3cf_item */
276
+ foreach ( $as3cf_items as $as3cf_item ) {
277
+ // Each returned item may have matched on either the source_path or original_source_path.
278
+ // Because the base image file name of a thumbnail might match the original rather scaled or rotated full image
279
+ // it's possible that both source paths are used by separate URLs.
280
+ foreach ( array( $as3cf_item->source_path(), $as3cf_item->original_source_path() ) as $source_path ) {
281
+ if ( ! empty( $paths[ $source_path ] ) ) {
282
+ $matched_full_url = $paths[ $source_path ];
283
+
284
+ if ( ! empty( $full_urls[ $matched_full_url ] ) ) {
285
+ $attachment_id = $as3cf_item->source_id();
286
+ $this->query_cache[ $matched_full_url ] = $attachment_id;
287
+
288
+ foreach ( $full_urls[ $matched_full_url ] as $url ) {
289
+ $results[ $url ] = $attachment_id;
290
+ }
291
+ unset( $full_urls[ $matched_full_url ] );
292
+ }
293
  }
 
294
  }
295
  }
296
  }
319
  * @return string
320
  */
321
  protected function normalize_find_value( $url ) {
322
+ return AS3CF_Utils::decode_filename_in_path( $url );
323
  }
324
 
325
  /**
classes/filters/as3cf-s3-to-local.php CHANGED
@@ -1,5 +1,7 @@
1
  <?php
2
 
 
 
3
  class AS3CF_S3_To_Local extends AS3CF_Filter {
4
 
5
  /**
@@ -110,73 +112,19 @@ class AS3CF_S3_To_Local extends AS3CF_Filter {
110
  *
111
  * @return bool|int
112
  */
113
- protected function get_attachment_id_from_url( $url ) {
114
- global $wpdb;
115
-
116
  $full_url = AS3CF_Utils::remove_size_from_filename( $url );
117
 
 
118
  if ( isset( $this->query_cache[ $full_url ] ) ) {
119
- // ID already cached, return
120
  return $this->query_cache[ $full_url ];
121
  }
122
 
123
- $parts = AS3CF_Utils::parse_url( $full_url );
124
- $path = $this->as3cf->decode_filename_in_path( ltrim( $parts['path'], '/' ) );
125
-
126
- if ( false !== strpos( $path, '/' ) ) {
127
- // Remove the first directory to cater for bucket in path domain settings
128
- $path = explode( '/', $path );
129
- array_shift( $path );
130
- $path = implode( '/', $path );
131
- }
132
-
133
- $sql = $wpdb->prepare( "
134
- SELECT * FROM {$wpdb->postmeta}
135
- WHERE meta_key = %s
136
- AND meta_value LIKE %s;
137
- ", 'amazonS3_info', '%' . $path . '%' );
138
-
139
- $results = $wpdb->get_results( $sql );
140
-
141
- if ( empty( $results ) ) {
142
- // No attachment found, return false
143
- return false;
144
- }
145
-
146
- if ( 1 === count( $results ) ) {
147
- // Attachment matched, return ID
148
- $this->query_cache[ $full_url ] = $results[0]->post_id;
149
-
150
- return $results[0]->post_id;
151
- }
152
-
153
- $path = ltrim( $parts['path'], '/' );
154
-
155
- foreach ( $results as $result ) {
156
- $meta = maybe_unserialize( $result->meta_value );
157
-
158
- if ( ! isset( $meta['bucket'] ) || ! isset( $meta['key'] ) ) {
159
- // Can't determine S3 bucket or key, continue
160
- continue;
161
- }
162
-
163
- if ( false !== strpos( $path, $meta['bucket'] ) ) {
164
- // Bucket in path, remove
165
- $path = ltrim( str_replace( $meta['bucket'], '', $path ), '/' );
166
- }
167
-
168
- if ( $path === $meta['key'] ) {
169
- // Exact match, return ID
170
- $this->query_cache[ $full_url ] = $results[0]->post_id;
171
-
172
- return $result->post_id;
173
- }
174
- }
175
 
176
- // Can't determine ID, return false
177
- $this->query_cache[ $full_url ] = false;
178
 
179
- return false;
180
  }
181
 
182
  /**
@@ -223,7 +171,7 @@ class AS3CF_S3_To_Local extends AS3CF_Filter {
223
  * @return string
224
  */
225
  protected function normalize_replace_value( $url ) {
226
- return $this->as3cf->decode_filename_in_path( $url );
227
  }
228
 
229
  /**
1
  <?php
2
 
3
+ use DeliciousBrains\WP_Offload_Media\Items\Media_Library_Item;
4
+
5
  class AS3CF_S3_To_Local extends AS3CF_Filter {
6
 
7
  /**
112
  *
113
  * @return bool|int
114
  */
115
+ public function get_attachment_id_from_url( $url ) {
 
 
116
  $full_url = AS3CF_Utils::remove_size_from_filename( $url );
117
 
118
+ // Result for URL already cached in request whether found or not, return it.
119
  if ( isset( $this->query_cache[ $full_url ] ) ) {
 
120
  return $this->query_cache[ $full_url ];
121
  }
122
 
123
+ $post_id = Media_Library_Item::get_source_id_by_remote_url( $full_url );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
 
125
+ $this->query_cache[ $full_url ] = $post_id;
 
126
 
127
+ return $post_id;
128
  }
129
 
130
  /**
171
  * @return string
172
  */
173
  protected function normalize_replace_value( $url ) {
174
+ return AS3CF_Utils::decode_filename_in_path( $url );
175
  }
176
 
177
  /**
classes/items/item.php ADDED
@@ -0,0 +1,794 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace DeliciousBrains\WP_Offload_Media\Items;
4
+
5
+ use Amazon_S3_And_CloudFront;
6
+ use AS3CF_Utils;
7
+ use WP_Error;
8
+
9
+ abstract class Item {
10
+ const ITEMS_TABLE = 'as3cf_items';
11
+
12
+ protected static $source_type = 'media-library';
13
+ protected static $source_table = 'posts';
14
+ protected static $source_fk = 'id';
15
+
16
+ private static $checked_table_exists = array();
17
+
18
+ protected static $items_cache_by_id = array();
19
+ protected static $items_cache_by_source_id = array();
20
+ protected static $items_cache_by_path = array();
21
+ protected static $items_cache_by_source_path = array();
22
+
23
+ private $id;
24
+ private $provider;
25
+ private $region;
26
+ private $bucket;
27
+ private $path;
28
+ private $original_path;
29
+ private $is_private;
30
+ private $source_id;
31
+ private $source_path;
32
+ private $original_source_path;
33
+ private $extra_info;
34
+
35
+ /**
36
+ * Item constructor.
37
+ *
38
+ * @param string $provider Storage provider key name, e.g. "aws".
39
+ * @param string $region Region for item's bucket.
40
+ * @param string $bucket Bucket for item.
41
+ * @param string $path Key path for item (full sized if type has thumbnails etc).
42
+ * @param bool $is_private Is the object private in the bucket.
43
+ * @param int $source_id ID that source has.
44
+ * @param string $source_path Path that source uses, could be relative or absolute depending on source.
45
+ * @param string $original_filename An optional filename with no path that was previously used for the item.
46
+ * @param array $extra_info An optional array of extra data specific to the source type.
47
+ * @param null $id Optional Item record ID.
48
+ */
49
+ public function __construct( $provider, $region, $bucket, $path, $is_private, $source_id, $source_path, $original_filename = null, $extra_info = array(), $id = null ) {
50
+ $this->provider = $provider;
51
+ $this->region = $region;
52
+ $this->bucket = $bucket;
53
+ $this->path = $path;
54
+ $this->is_private = $is_private;
55
+ $this->source_id = $source_id;
56
+ $this->source_path = $source_path;
57
+ $this->extra_info = serialize( $extra_info );
58
+
59
+ if ( empty( $original_filename ) ) {
60
+ $this->original_path = $path;
61
+ $this->original_source_path = $source_path;
62
+ } else {
63
+ $this->original_path = str_replace( wp_basename( $path ), $original_filename, $path );
64
+ $this->original_source_path = str_replace( wp_basename( $source_path ), $original_filename, $source_path );
65
+ }
66
+
67
+ if ( ! empty( $id ) ) {
68
+ $this->id = $id;
69
+ }
70
+
71
+ static::add_to_items_cache( $this );
72
+ }
73
+
74
+ /**
75
+ * (Re)initialize the static cache used for speeding up queries.
76
+ */
77
+ public static function init_cache() {
78
+ self::$checked_table_exists = array();
79
+
80
+ static::$items_cache_by_id = array();
81
+ static::$items_cache_by_source_id = array();
82
+ static::$items_cache_by_path = array();
83
+ static::$items_cache_by_source_path = array();
84
+ }
85
+
86
+ /**
87
+ * Add an item to the static cache to allow fast retrieval via get_from_items_cache_by_* functions.
88
+ *
89
+ * @param Item $item
90
+ */
91
+ protected static function add_to_items_cache( $item ) {
92
+ $blog_id = get_current_blog_id();
93
+
94
+ if ( ! empty( $item->id() ) ) {
95
+ static::$items_cache_by_id[ $blog_id ][ $item->id() ] = $item;
96
+ }
97
+
98
+ if ( ! empty( $item->source_id() ) ) {
99
+ static::$items_cache_by_source_id[ $blog_id ][ static::$source_type ][ $item->source_id() ] = $item;
100
+ }
101
+
102
+ if ( ! empty( $item->path() ) ) {
103
+ static::$items_cache_by_path[ $blog_id ][ static::$source_type ][ $item->original_path() ] = $item;
104
+ static::$items_cache_by_path[ $blog_id ][ static::$source_type ][ $item->path() ] = $item;
105
+ }
106
+
107
+ if ( ! empty( $item->source_path() ) ) {
108
+ static::$items_cache_by_source_path[ $blog_id ][ static::$source_type ][ $item->original_source_path() ] = $item;
109
+ static::$items_cache_by_source_path[ $blog_id ][ static::$source_type ][ $item->source_path() ] = $item;
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Add an item to the static cache to allow fast retrieval via get_from_items_cache_by_* functions.
115
+ *
116
+ * @param Item $item
117
+ */
118
+ protected static function remove_from_items_cache( $item ) {
119
+ $blog_id = get_current_blog_id();
120
+
121
+ if ( ! empty( $item->id() ) ) {
122
+ unset( static::$items_cache_by_id[ $blog_id ][ $item->id() ] );
123
+ }
124
+
125
+ if ( ! empty( $item->source_id() ) ) {
126
+ unset( static::$items_cache_by_source_id[ $blog_id ][ static::$source_type ][ $item->source_id() ] );
127
+ }
128
+
129
+ if ( ! empty( $item->path() ) ) {
130
+ unset( static::$items_cache_by_path[ $blog_id ][ static::$source_type ][ $item->original_path() ] );
131
+ unset( static::$items_cache_by_path[ $blog_id ][ static::$source_type ][ $item->path() ] );
132
+ }
133
+
134
+ if ( ! empty( $item->source_path() ) ) {
135
+ unset( static::$items_cache_by_source_path[ $blog_id ][ static::$source_type ][ $item->original_source_path() ] );
136
+ unset( static::$items_cache_by_source_path[ $blog_id ][ static::$source_type ][ $item->source_path() ] );
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Try and get Item from cache by known id.
142
+ *
143
+ * @param int $id
144
+ *
145
+ * @return bool|Item
146
+ */
147
+ private static function get_from_items_cache_by_id( $id ) {
148
+ $blog_id = get_current_blog_id();
149
+
150
+ if ( ! empty( static::$items_cache_by_id[ $blog_id ][ $id ] ) ) {
151
+ return static::$items_cache_by_id[ $blog_id ][ $id ];
152
+ }
153
+
154
+ return false;
155
+ }
156
+
157
+ /**
158
+ * Try and get Item from cache by known source_id.
159
+ *
160
+ * @param int $source_id
161
+ *
162
+ * @return bool|Item
163
+ */
164
+ private static function get_from_items_cache_by_source_id( $source_id ) {
165
+ $blog_id = get_current_blog_id();
166
+
167
+ if ( ! empty( static::$items_cache_by_source_id[ $blog_id ][ static::$source_type ][ $source_id ] ) ) {
168
+ return static::$items_cache_by_source_id[ $blog_id ][ static::$source_type ][ $source_id ];
169
+ }
170
+
171
+ return false;
172
+ }
173
+
174
+ /**
175
+ * Try and get Item from cache by known bucket and path.
176
+ *
177
+ * @param string $bucket
178
+ * @param string $path
179
+ *
180
+ * @return bool|Item
181
+ */
182
+ private static function get_from_items_cache_by_bucket_and_path( $bucket, $path ) {
183
+ $blog_id = get_current_blog_id();
184
+
185
+ if ( ! empty( static::$items_cache_by_path[ $blog_id ][ static::$source_type ][ $path ] ) ) {
186
+ /** @var Item $item */
187
+ $item = static::$items_cache_by_path[ $blog_id ][ static::$source_type ][ $path ];
188
+
189
+ if ( $item->bucket() === $bucket ) {
190
+ return $item;
191
+ }
192
+ }
193
+
194
+ return false;
195
+ }
196
+
197
+ /**
198
+ * The full items table name for current blog.
199
+ *
200
+ * @return string
201
+ */
202
+ protected static function items_table() {
203
+ global $wpdb;
204
+
205
+ /* @var Amazon_S3_And_CloudFront $as3cf */
206
+ global $as3cf;
207
+
208
+ $table_name = $wpdb->get_blog_prefix() . static::ITEMS_TABLE;
209
+
210
+ if ( empty( self::$checked_table_exists[ $table_name ] ) ) {
211
+ self::$checked_table_exists[ $table_name ] = true;
212
+
213
+ $schema_version = get_option( $as3cf->get_plugin_prefix() . '_schema_version', '0.0.0' );
214
+
215
+ if ( version_compare( $schema_version, $as3cf->get_plugin_version(), '<' ) ) {
216
+ self::install_table( $table_name );
217
+
218
+ update_option( $as3cf->get_plugin_prefix() . '_schema_version', $as3cf->get_plugin_version() );
219
+ }
220
+ }
221
+
222
+ return $table_name;
223
+ }
224
+
225
+ /**
226
+ * Create the table needed by this class with given name (for current site).
227
+ *
228
+ * @param string $table_name
229
+ */
230
+ private static function install_table( $table_name ) {
231
+ global $wpdb;
232
+
233
+ require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
234
+
235
+ $wpdb->hide_errors();
236
+
237
+ $charset_collate = $wpdb->get_charset_collate();
238
+
239
+ $sql = "
240
+ CREATE TABLE {$table_name} (
241
+ id BIGINT(20) NOT NULL AUTO_INCREMENT,
242
+ provider VARCHAR(18) NOT NULL,
243
+ region VARCHAR(255) NOT NULL,
244
+ bucket VARCHAR(255) NOT NULL,
245
+ path VARCHAR(1024) NOT NULL,
246
+ original_path VARCHAR(1024) NOT NULL,
247
+ is_private BOOLEAN NOT NULL DEFAULT 0,
248
+ source_type VARCHAR(18) NOT NULL,
249
+ source_id BIGINT(20) NOT NULL,
250
+ source_path VARCHAR(1024) NOT NULL,
251
+ original_source_path VARCHAR(1024) NOT NULL,
252
+ extra_info LONGTEXT,
253
+ PRIMARY KEY (id),
254
+ UNIQUE KEY uidx_path (path(190), id),
255
+ UNIQUE KEY uidx_original_path (original_path(190), id),
256
+ UNIQUE KEY uidx_source_path (source_path(190), id),
257
+ UNIQUE KEY uidx_original_source_path (original_source_path(190), id),
258
+ UNIQUE KEY uidx_source (source_type, source_id),
259
+ UNIQUE KEY uidx_provider_bucket (provider, bucket(190), id)
260
+ ) $charset_collate;
261
+ ";
262
+ dbDelta( $sql );
263
+ }
264
+
265
+ /**
266
+ * Get item's data as an array, optionally with id if available.
267
+ *
268
+ * @param bool $include_id Default false.
269
+ *
270
+ * @return array
271
+ */
272
+ public function key_values( $include_id = false ) {
273
+ $key_values = array(
274
+ 'provider' => $this->provider,
275
+ 'region' => $this->region,
276
+ 'bucket' => $this->bucket,
277
+ 'path' => $this->path,
278
+ 'original_path' => $this->original_path,
279
+ 'is_private' => $this->is_private,
280
+ 'source_type' => static::$source_type,
281
+ 'source_id' => $this->source_id,
282
+ 'source_path' => $this->source_path,
283
+ 'original_source_path' => $this->original_source_path,
284
+ 'extra_info' => $this->extra_info,
285
+ );
286
+
287
+ if ( $include_id && ! empty( $this->id ) ) {
288
+ $key_values['id'] = $this->id;
289
+ }
290
+
291
+ ksort( $key_values );
292
+
293
+ return $key_values;
294
+ }
295
+
296
+ /**
297
+ * All the item's property names in an array, optionally with id if available.
298
+ *
299
+ * @param bool $include_id Default false.
300
+ *
301
+ * @return array
302
+ */
303
+ private function keys( $include_id = false ) {
304
+ return array_keys( $this->key_values( $include_id ) );
305
+ }
306
+
307
+ /**
308
+ * All the item's property values in an array, optionally with id if available.
309
+ *
310
+ * @param bool $include_id Default false.
311
+ *
312
+ * @return array
313
+ */
314
+ private function values( $include_id = false ) {
315
+ return array_values( $this->key_values( $include_id ) );
316
+ }
317
+
318
+ /**
319
+ * Get item's column formats as an associative array, optionally with id if available.
320
+ *
321
+ * @param bool $include_id Default false.
322
+ *
323
+ * @return array
324
+ */
325
+ private function key_formats( $include_id = false ) {
326
+ $key_values = array(
327
+ 'provider' => '%s',
328
+ 'region' => '%s',
329
+ 'bucket' => '%s',
330
+ 'path' => '%s',
331
+ 'original_path' => '%s',
332
+ 'is_private' => '%s',
333
+ 'source_type' => '%s',
334
+ 'source_id' => '%d',
335
+ 'source_path' => '%s',
336
+ 'original_source_path' => '%s',
337
+ 'extra_info' => '%s',
338
+ );
339
+
340
+ if ( $include_id && ! empty( $this->id ) ) {
341
+ $key_values['id'] = '%d';
342
+ }
343
+
344
+ ksort( $key_values );
345
+
346
+ return $key_values;
347
+ }
348
+
349
+ /**
350
+ * All the item's column formats in an indexed array, optionally with id if available.
351
+ *
352
+ * @param bool $include_id Default false.
353
+ *
354
+ * @return array
355
+ */
356
+ private function formats( $include_id = false ) {
357
+ return array_values( $this->key_formats( $include_id ) );
358
+ }
359
+
360
+ /**
361
+ * Save the item's current data.
362
+ *
363
+ * @return int|WP_Error
364
+ */
365
+ public function save() {
366
+ global $wpdb;
367
+
368
+ if ( empty( $this->id ) ) {
369
+ $result = $wpdb->insert( static::items_table(), $this->key_values(), $this->formats() );
370
+
371
+ if ( $result ) {
372
+ $this->id = $wpdb->insert_id;
373
+
374
+ // Now that the item has an ID it should be (re)cached.
375
+ self::add_to_items_cache( $this );
376
+ }
377
+ } else {
378
+ $result = $wpdb->update( static::items_table(), $this->key_values(), array( 'id' => $this->id ), $this->formats(), array( '%d' ) );
379
+ }
380
+
381
+ if ( false === $result ) {
382
+ self::remove_from_items_cache( $this );
383
+
384
+ return new WP_Error( 'item_save', 'Error saving item:- ' . $wpdb->last_error );
385
+ }
386
+
387
+ return $this->id;
388
+ }
389
+
390
+ /**
391
+ * Delete the current item.
392
+ *
393
+ * @return bool|WP_Error
394
+ */
395
+ public function delete() {
396
+ global $wpdb;
397
+
398
+ static::remove_from_items_cache( $this );
399
+
400
+ if ( empty( $this->id ) ) {
401
+ return new WP_Error( 'item_delete', 'Error trying to delete item with no id.' );
402
+ } else {
403
+ $result = $wpdb->delete( static::items_table(), array( 'id' => $this->id ), array( '%d' ) );
404
+ }
405
+
406
+ if ( ! $result ) {
407
+ return new WP_Error( 'item_delete', 'Error deleting item:- ' . $wpdb->last_error );
408
+ }
409
+
410
+ return true;
411
+ }
412
+
413
+ /**
414
+ * Creates an item based on object from database.
415
+ *
416
+ * @param object $object
417
+ *
418
+ * @return Item
419
+ */
420
+ protected static function create( $object ) {
421
+ $extra_info = array();
422
+
423
+ if ( ! empty( $object->extra_info ) ) {
424
+ $extra_info = unserialize( $object->extra_info );
425
+ }
426
+
427
+ return new static(
428
+ $object->provider,
429
+ $object->region,
430
+ $object->bucket,
431
+ $object->path,
432
+ $object->is_private,
433
+ $object->source_id,
434
+ $object->source_path,
435
+ wp_basename( $object->original_source_path ),
436
+ $extra_info,
437
+ $object->id
438
+ );
439
+ }
440
+
441
+ /**
442
+ * Get an item by its id.
443
+ *
444
+ * @param integer $id
445
+ *
446
+ * @return bool|Item
447
+ */
448
+ public static function get_by_id( $id ) {
449
+ global $wpdb;
450
+
451
+ if ( empty( $id ) ) {
452
+ return false;
453
+ }
454
+
455
+ $item = static::get_from_items_cache_by_id( $id );
456
+
457
+ if ( ! empty( $item ) ) {
458
+ return $item;
459
+ }
460
+
461
+ $sql = $wpdb->prepare( "SELECT * FROM " . static::items_table() . " WHERE source_type = %s AND id = %d", static::$source_type, $id );
462
+
463
+ $object = $wpdb->get_row( $sql );
464
+
465
+ if ( empty( $object ) ) {
466
+ return false;
467
+ }
468
+
469
+ return static::create( $object );
470
+ }
471
+
472
+ /**
473
+ * Get an item by its source id.
474
+ *
475
+ * While source id isn't strictly unique, it is by source type, which is always used in queries based on called class.
476
+ *
477
+ * @param integer $source_id
478
+ *
479
+ * @return bool|Item
480
+ */
481
+ public static function get_by_source_id( $source_id ) {
482
+ global $wpdb;
483
+
484
+ if ( ! is_numeric( $source_id ) ) {
485
+ return false;
486
+ }
487
+
488
+ $source_id = (int) $source_id;
489
+
490
+ if ( empty( $source_id ) ) {
491
+ return false;
492
+ }
493
+
494
+ $item = static::get_from_items_cache_by_source_id( $source_id );
495
+
496
+ if ( ! empty( $item ) ) {
497
+ return $item;
498
+ }
499
+
500
+ $sql = $wpdb->prepare( "SELECT * FROM " . static::items_table() . " WHERE source_id = %d AND source_type = %s", $source_id, static::$source_type );
501
+
502
+ $object = $wpdb->get_row( $sql );
503
+
504
+ if ( empty( $object ) ) {
505
+ return false;
506
+ }
507
+
508
+ return static::create( $object );
509
+ }
510
+
511
+ /**
512
+ * Getter for item's id value.
513
+ *
514
+ * @return integer
515
+ */
516
+ public function id() {
517
+ return $this->id;
518
+ }
519
+
520
+ /**
521
+ * Getter for item's provider value.
522
+ *
523
+ * @return string
524
+ */
525
+ public function provider() {
526
+ return $this->provider;
527
+ }
528
+
529
+ /**
530
+ * Getter for item's region value.
531
+ *
532
+ * @return string
533
+ */
534
+ public function region() {
535
+ return $this->region;
536
+ }
537
+
538
+ /**
539
+ * Getter for item's bucket value.
540
+ *
541
+ * @return string
542
+ */
543
+ public function bucket() {
544
+ return $this->bucket;
545
+ }
546
+
547
+ /**
548
+ * Getter for item's path value.
549
+ *
550
+ * @return string
551
+ */
552
+ public function path() {
553
+ return $this->path;
554
+ }
555
+
556
+ /**
557
+ * Getter for item's original_path value.
558
+ *
559
+ * @return string
560
+ */
561
+ public function original_path() {
562
+ return $this->original_path;
563
+ }
564
+
565
+ /**
566
+ * Getter for item's is_private value.
567
+ *
568
+ * @return bool
569
+ */
570
+ public function is_private() {
571
+ return (bool) $this->is_private;
572
+ }
573
+
574
+ /**
575
+ * Getter for item's source_id value.
576
+ *
577
+ * @return integer
578
+ */
579
+ public function source_id() {
580
+ return $this->source_id;
581
+ }
582
+
583
+ /**
584
+ * Getter for item's source_path value.
585
+ *
586
+ * @return string
587
+ */
588
+ public function source_path() {
589
+ return $this->source_path;
590
+ }
591
+
592
+ /**
593
+ * Getter for item's original_source_path value.
594
+ *
595
+ * @return string
596
+ */
597
+ public function original_source_path() {
598
+ return $this->original_source_path;
599
+ }
600
+
601
+ /**
602
+ * Getter for item's extra_info value.
603
+ *
604
+ * @return array
605
+ */
606
+ protected function extra_info() {
607
+ return unserialize( $this->extra_info );
608
+ }
609
+
610
+ /**
611
+ * Get normalized object path dir.
612
+ *
613
+ * @return string
614
+ */
615
+ public function normalized_path_dir() {
616
+ $directory = dirname( $this->path );
617
+
618
+ return ( '.' === $directory ) ? '' : trailingslashit( $directory );
619
+ }
620
+
621
+ /**
622
+ * Get the first source id for a bucket and path.
623
+ *
624
+ * @param string $bucket
625
+ * @param string $path
626
+ *
627
+ * @return int|bool
628
+ */
629
+ public static function get_source_id_by_bucket_and_path( $bucket, $path ) {
630
+ global $wpdb;
631
+
632
+ if ( empty( $bucket ) || empty( $path ) ) {
633
+ return false;
634
+ }
635
+
636
+ $item = static::get_from_items_cache_by_bucket_and_path( $bucket, $path );
637
+
638
+ if ( ! empty( $item ) ) {
639
+ return $item->source_id();
640
+ }
641
+
642
+ $sql = $wpdb->prepare(
643
+ "
644
+ SELECT source_id FROM " . static::items_table() . "
645
+ WHERE source_type = %s
646
+ AND bucket = %s
647
+ AND (path = %s OR original_path = %s)
648
+ ORDER BY source_id LIMIT 1
649
+ ",
650
+ static::$source_type,
651
+ $bucket,
652
+ $path,
653
+ $path
654
+ );
655
+
656
+ $result = $wpdb->get_var( $sql );
657
+
658
+ return empty( $result ) ? false : (int) $result;
659
+ }
660
+
661
+ /**
662
+ * Get the source id for a given remote URL.
663
+ *
664
+ * @param string $url
665
+ *
666
+ * @return int|bool
667
+ */
668
+ public static function get_source_id_by_remote_url( $url ) {
669
+ global $wpdb;
670
+
671
+ $parts = AS3CF_Utils::parse_url( $url );
672
+ $path = AS3CF_Utils::decode_filename_in_path( ltrim( $parts['path'], '/' ) );
673
+
674
+ // Remove the first directory to cater for bucket in path domain settings.
675
+ if ( false !== strpos( $path, '/' ) ) {
676
+ $path = explode( '/', $path );
677
+ array_shift( $path );
678
+ $path = implode( '/', $path );
679
+ }
680
+
681
+ $sql = $wpdb->prepare(
682
+ "SELECT * FROM " . static::items_table() . " WHERE source_type = %s AND (path LIKE %s OR original_path LIKE %s);"
683
+ , static::$source_type
684
+ , '%' . $path
685
+ , '%' . $path
686
+ );
687
+
688
+ $results = $wpdb->get_results( $sql );
689
+
690
+ // Nothing found, shortcut out.
691
+ if ( 0 === count( $results ) ) {
692
+ // TODO: If upgrade in progress, fallback to 'amazonS3_info' in Media_Library_Item override of this function.
693
+ return false;
694
+ }
695
+
696
+ // Only one attachment matched, return ID.
697
+ if ( 1 === count( $results ) ) {
698
+ return $results[0]->source_id;
699
+ }
700
+
701
+ $path = ltrim( $parts['path'], '/' );
702
+
703
+ foreach ( $results as $result ) {
704
+ $as3cf_item = static::create( $result );
705
+
706
+ // If item's bucket matches first segment of URL path, remove it from URL path before checking match.
707
+ if ( 0 === strpos( $path, trailingslashit( $as3cf_item->bucket() ) ) ) {
708
+ $match_path = ltrim( substr_replace( $path, '', 0, strlen( $as3cf_item->bucket() ) ), '/' );
709
+ } else {
710
+ $match_path = $path;
711
+ }
712
+
713
+ // Exact match, return ID.
714
+ if ( $as3cf_item->path() === $match_path || $as3cf_item->original_path() === $match_path ) {
715
+ return $as3cf_item->source_id();
716
+ }
717
+ }
718
+
719
+ return false;
720
+ }
721
+
722
+ /**
723
+ * Get an array of managed source_ids in descending order.
724
+ *
725
+ * While source id isn't strictly unique, it is by source type, which is always used in queries based on called class.
726
+ *
727
+ * @param integer $upper_bound Returned source_ids should be lower than this, use null/0 for no upper bound.
728
+ * @param integer $limit Maximum number of source_ids to return. Required if not counting.
729
+ * @param bool $count Just return a count of matching source_ids? Negates $limit, default false.
730
+ *
731
+ * @return array|int
732
+ */
733
+ public static function get_source_ids( $upper_bound, $limit, $count = false ) {
734
+ global $wpdb;
735
+
736
+ $args = array( static::$source_type );
737
+
738
+ if ( $count ) {
739
+ $sql = 'SELECT COUNT(DISTINCT source_id)';
740
+ } else {
741
+ $sql = 'SELECT DISTINCT source_id';
742
+ }
743
+
744
+ $sql .= ' FROM ' . static::items_table() . ' WHERE source_type = %s';
745
+
746
+ if ( ! empty( $upper_bound ) ) {
747
+ $sql .= ' AND source_id < %d';
748
+ $args[] = $upper_bound;
749
+ }
750
+
751
+ if ( ! $count ) {
752
+ $sql .= ' ORDER BY source_id DESC LIMIT %d';
753
+ $args[] = $limit;
754
+ }
755
+
756
+ $sql = $wpdb->prepare( $sql, $args );
757
+
758
+ if ( $count ) {
759
+ return $wpdb->get_var( $sql );
760
+ } else {
761
+ return array_map( 'intval', $wpdb->get_col( $sql ) );
762
+ }
763
+ }
764
+
765
+ /**
766
+ * Get an array of un-managed source_ids in descending order.
767
+ *
768
+ * While source id isn't strictly unique, it is by source type, which is always used in queries based on called class.
769
+ *
770
+ * @param integer $upper_bound Returned source_ids should be lower than this, use null/0 for no upper bound.
771
+ * @param integer $limit Maximum number of source_ids to return. Required if not counting.
772
+ * @param bool $count Just return a count of matching source_ids? Negates $limit, default false.
773
+ *
774
+ * @return array|int
775
+ *
776
+ * NOTE: Must be overridden by subclass, only reason this is not abstract is because static is preferred.
777
+ */
778
+ public static function get_missing_source_ids( $upper_bound, $limit, $count = false ) {
779
+ if ( $count ) {
780
+ return 0;
781
+ } else {
782
+ return array();
783
+ }
784
+ }
785
+
786
+ /**
787
+ * Get absolute file paths associated with source item.
788
+ *
789
+ * @param integer $id
790
+ *
791
+ * @return array
792
+ */
793
+ abstract protected function source_paths( $id );
794
+ }
classes/items/media-library-item.php ADDED
@@ -0,0 +1,450 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace DeliciousBrains\WP_Offload_Media\Items;
4
+
5
+ use Amazon_S3_And_CloudFront;
6
+ use WP_Error;
7
+
8
+ class Media_Library_Item extends Item {
9
+ private static $attachment_counts = array();
10
+ private static $attachment_count_skips = array();
11
+
12
+ /**
13
+ * Item constructor.
14
+ *
15
+ * @param string $provider Storage provider key name, e.g. "aws".
16
+ * @param string $region Region for item's bucket.
17
+ * @param string $bucket Bucket for item.
18
+ * @param string $path Key path for item (full sized if type has thumbnails etc).
19
+ * @param bool $is_private Is the object private in the bucket.
20
+ * @param int $source_id ID that source has.
21
+ * @param string $source_path Path that source uses, could be relative or absolute depending on source.
22
+ * @param string $original_filename An optional filename with no path that was previously used for the item.
23
+ * @param array $private_sizes An optional array of thumbnail sizes that should be private objects in the bucket.
24
+ * @param null $id Optional Item record ID.
25
+ */
26
+ public function __construct( $provider, $region, $bucket, $path, $is_private, $source_id, $source_path, $original_filename = null, $private_sizes = array(), $id = null ) {
27
+ // For Media Library items, the source path should be relative to the Media Library's uploads directory.
28
+ $uploads = wp_upload_dir();
29
+
30
+ if ( false === $uploads['error'] && 0 === strpos( $source_path, $uploads['basedir'] ) ) {
31
+ $source_path = ltrim( substr( $source_path, strlen( $uploads['basedir'] ) ), DIRECTORY_SEPARATOR );
32
+ }
33
+
34
+ /*
35
+ * We only need private sizes info at the moment, but just in case we add a bit more extra info...
36
+ */
37
+
38
+ // Ensure re-hydration is clean.
39
+ if ( ! empty( $private_sizes['private_sizes'] ) ) {
40
+ $private_sizes = $private_sizes['private_sizes'];
41
+ }
42
+
43
+ if ( empty( $private_sizes ) || ! is_array( $private_sizes ) ) {
44
+ $private_sizes = array();
45
+ }
46
+
47
+ $extra_info = array(
48
+ 'private_sizes' => $private_sizes,
49
+ );
50
+
51
+ parent::__construct( $provider, $region, $bucket, $path, $is_private, $source_id, $source_path, $original_filename, $extra_info, $id );
52
+ }
53
+
54
+ /**
55
+ * (Re)initialize the static cache used for speeding up queries.
56
+ */
57
+ public static function init_cache() {
58
+ parent::init_cache();
59
+
60
+ self::$attachment_counts = array();
61
+ self::$attachment_count_skips = array();
62
+ }
63
+
64
+ /**
65
+ * Get the item based on source id.
66
+ *
67
+ * @param integer $source_id
68
+ *
69
+ * @return bool|Media_Library_Item
70
+ */
71
+ public static function get_by_source_id( $source_id ) {
72
+ $as3cf_item = parent::get_by_source_id( $source_id );
73
+
74
+ if ( ! $as3cf_item ) {
75
+ $provider_object = static::_legacy_get_attachment_provider_info( $source_id );
76
+
77
+ if ( is_array( $provider_object ) ) {
78
+ $as3cf_item = static::_legacy_provider_info_to_item( $source_id, $provider_object );
79
+ }
80
+ }
81
+
82
+ return $as3cf_item;
83
+ }
84
+
85
+ /**
86
+ * Get absolute file paths associated with source item.
87
+ *
88
+ * @param integer $id
89
+ *
90
+ * @return array
91
+ */
92
+ protected function source_paths( $id ) {
93
+ $paths = array();
94
+
95
+ return $paths;
96
+ }
97
+
98
+ /**
99
+ * Get the array of thumbnail sizes that are private in the bucket.
100
+ *
101
+ * @return array
102
+ */
103
+ public function private_sizes() {
104
+ $extra_info = $this->extra_info();
105
+
106
+ if ( ! empty( $extra_info['private_sizes'] ) ) {
107
+ return $extra_info['private_sizes'];
108
+ }
109
+
110
+ return array();
111
+ }
112
+
113
+ /**
114
+ * Get the private status for a specific size.
115
+ *
116
+ * @param string $size
117
+ *
118
+ * @return bool
119
+ */
120
+ public function is_private_size( $size ) {
121
+ if ( ( 'original' === $size || empty( $size ) ) ) {
122
+ return $this->is_private();
123
+ }
124
+
125
+ return in_array( $size, $this->private_sizes() );
126
+ }
127
+
128
+ /**
129
+ * Count attachments on current site.
130
+ *
131
+ * @param bool $skip_transient Whether to force database query and skip transient, default false
132
+ * @param bool $force Whether to force database query and skip static cache, implies $skip_transient, default false
133
+ *
134
+ * @return array Keys:
135
+ * total: Total media count for site (current blog id)
136
+ * offloaded: Count of offloaded media for site (current blog id)
137
+ * not_offloaded: Difference between total and offloaded
138
+ */
139
+ public static function count_attachments( $skip_transient = false, $force = false ) {
140
+ global $wpdb;
141
+
142
+ $transient_key = 'as3cf_' . get_current_blog_id() . '_attachment_counts';
143
+
144
+ // Been here, done it, won't do it again!
145
+ // Well, unless this is the first transient skip for the prefix, then we need to do it.
146
+ if ( ! $force && ! empty( self::$attachment_counts[ $transient_key ] ) && ( false === $skip_transient || ! empty( self::$attachment_count_skips[ $transient_key ] ) ) ) {
147
+ return self::$attachment_counts[ $transient_key ];
148
+ }
149
+
150
+ if ( $force || $skip_transient || false === ( $result = get_site_transient( $transient_key ) ) ) {
151
+ // We want to count distinct relative Media Library paths
152
+ // and ensure type is also attachment as other post types can use the same _wp_attached_file postmeta key.
153
+ $sql = "
154
+ SELECT COUNT(DISTINCT p.`ID`) total, COUNT(DISTINCT i.`id`) offloaded, COUNT(DISTINCT m.`meta_value`) total_paths, COUNT(DISTINCT i.`source_path`) offloaded_paths
155
+ FROM " . $wpdb->postmeta . " AS m
156
+ LEFT JOIN " . $wpdb->posts . " AS p ON m.post_id = p.ID AND p.`post_type` = 'attachment'
157
+ LEFT OUTER JOIN " . static::items_table() . " AS i ON p.`ID` = i.`source_id` AND i.`source_type` = 'media-library'
158
+ WHERE m.`meta_key` = '_wp_attached_file'
159
+ ";
160
+
161
+ $result = $wpdb->get_row( $sql, ARRAY_A );
162
+
163
+ $result['not_offloaded'] = $result['total'] - $result['offloaded'];
164
+ $result['not_offloaded_paths'] = max( 0, $result['total_paths'] - $result['offloaded_paths'] );
165
+
166
+ ksort( $result );
167
+
168
+ set_site_transient( $transient_key, $result, 2 * MINUTE_IN_SECONDS );
169
+
170
+ // One way or another we've skipped the transient.
171
+ self::$attachment_count_skips[ $transient_key ] = true;
172
+ }
173
+
174
+ self::$attachment_counts[ $transient_key ] = $result;
175
+
176
+ return $result;
177
+ }
178
+
179
+ /**
180
+ * Get an array of un-managed source_ids in descending order.
181
+ *
182
+ * While source id isn't strictly unique, it is by source type, which is always used in queries based on called class.
183
+ *
184
+ * @param integer $upper_bound Returned source_ids should be lower than this, use null/0 for no upper bound.
185
+ * @param integer $limit Maximum number of source_ids to return. Required if not counting.
186
+ * @param bool $count Just return a count of matching source_ids? Negates $limit, default false.
187
+ *
188
+ * @return array|int
189
+ */
190
+ public static function get_missing_source_ids( $upper_bound, $limit, $count = false ) {
191
+ global $wpdb;
192
+
193
+ $args = array( static::$source_type );
194
+
195
+ if ( $count ) {
196
+ $sql = 'SELECT COUNT(DISTINCT posts.ID)';
197
+ } else {
198
+ $sql = 'SELECT DISTINCT posts.ID';
199
+ }
200
+
201
+ $sql .= "
202
+ FROM {$wpdb->prefix}posts AS posts
203
+ WHERE posts.post_type = 'attachment'
204
+ AND posts.ID NOT IN (
205
+ SELECT items.source_id
206
+ FROM " . static::items_table() . " AS items
207
+ WHERE items.source_type = %s
208
+ AND items.source_id = posts.ID
209
+ )
210
+ ";
211
+
212
+ if ( ! empty( $upper_bound ) ) {
213
+ $sql .= ' AND posts.ID < %d';
214
+ $args[] = $upper_bound;
215
+ }
216
+
217
+ /**
218
+ * Allow users to exclude certain MIME types from attachments to upload.
219
+ *
220
+ * @param array
221
+ */
222
+ $ignored_mime_types = apply_filters( 'as3cf_ignored_mime_types', array() );
223
+ if ( is_array( $ignored_mime_types ) && ! empty( $ignored_mime_types ) ) {
224
+ $ignored_mime_types = array_map( 'sanitize_text_field', $ignored_mime_types );
225
+ $sql .= ' AND posts.post_mime_type NOT IN ("' . implode( '", "', $ignored_mime_types ) . '")';
226
+ }
227
+
228
+ if ( ! $count ) {
229
+ $sql .= ' ORDER BY posts.ID DESC LIMIT %d';
230
+ $args[] = $limit;
231
+ }
232
+
233
+ $sql = $wpdb->prepare( $sql, $args );
234
+
235
+ if ( $count ) {
236
+ return $wpdb->get_var( $sql );
237
+ } else {
238
+ return array_map( 'intval', $wpdb->get_col( $sql ) );
239
+ }
240
+ }
241
+
242
+ /**
243
+ * Search for all items that have the source path(s).
244
+ *
245
+ * @param array|string $paths Array of relative source paths.
246
+ * @param array|int $exclude_source_ids Array of source_ids to exclude from search. Default, none.
247
+ * @param bool $exact_match Use paths as supplied (true, default), or greedy match on path without extension (e.g. find edited too).
248
+ * @param bool $first_only Only return first matched item sorted by source_id. Default false.
249
+ *
250
+ * @return array
251
+ */
252
+ public static function get_by_source_path( $paths, $exclude_source_ids = array(), $exact_match = true, $first_only = false ) {
253
+ global $wpdb;
254
+
255
+ if ( ! is_array( $paths ) && is_string( $paths ) && ! empty( $paths ) ) {
256
+ $paths = array( $paths );
257
+ }
258
+
259
+ if ( ! is_array( $paths ) || empty( $paths ) ) {
260
+ return array();
261
+ }
262
+
263
+ $paths = \AS3CF_Utils::make_upload_file_paths_relative( $paths );
264
+
265
+ $args = array( static::$source_type );
266
+
267
+ $sql = '
268
+ SELECT DISTINCT items.*
269
+ FROM ' . static::items_table() . ' AS items
270
+ WHERE items.source_type = %s
271
+ ';
272
+
273
+ if ( ! empty( $exclude_source_ids ) ) {
274
+ if ( ! is_array( $exclude_source_ids ) ) {
275
+ $exclude_source_ids = array( $exclude_source_ids );
276
+ }
277
+
278
+ $sql .= ' AND items.source_id NOT IN (' . join( ',', $exclude_source_ids ) . ')';
279
+ }
280
+
281
+ if ( $exact_match ) {
282
+ $sql .= ' AND (items.source_path IN ("' . join( '","', $paths ) . '")';
283
+ $sql .= ' OR items.original_source_path IN ("' . join( '","', $paths ) . '"))';
284
+ } else {
285
+ $likes = array_map( function ( $path ) {
286
+ $ext = '.' . pathinfo( $path, PATHINFO_EXTENSION );
287
+ $path = substr_replace( $path, '%', -strlen( $ext ) );
288
+
289
+ return 'items.source_path LIKE "' . $path . '" OR items.original_source_path LIKE "' . $path . '"';
290
+ }, $paths );
291
+
292
+ $sql .= ' AND (' . join( ' OR ', $likes ) . ')';
293
+ }
294
+
295
+ if ( $first_only ) {
296
+ $sql .= ' ORDER BY items.source_id LIMIT 1';
297
+ }
298
+
299
+ $sql = $wpdb->prepare( $sql, $args );
300
+
301
+ return array_map( 'static::create', $wpdb->get_results( $sql ) );
302
+ }
303
+
304
+ /**
305
+ * Finds Media Library items with same source_path and sets them as offloaded.
306
+ */
307
+ public function offload_duplicate_items() {
308
+ global $wpdb;
309
+
310
+ $sql = $wpdb->prepare(
311
+ "
312
+ SELECT m.post_id
313
+ FROM " . $wpdb->postmeta . " AS m
314
+ LEFT JOIN " . $wpdb->posts . " AS p ON m.post_id = p.ID AND p.`post_type` = 'attachment'
315
+ WHERE m.meta_key = '_wp_attached_file'
316
+ AND m.meta_value = %s
317
+ AND m.post_id != %d
318
+ AND m.post_id NOT IN (
319
+ SELECT i.source_id
320
+ FROM " . static::items_table() . " AS i
321
+ WHERE i.source_type = %s
322
+ AND i.source_id = m.post_id
323
+ )
324
+ ;
325
+ "
326
+ , $this->source_path()
327
+ , $this->source_id()
328
+ , static::$source_type
329
+ );
330
+
331
+ $results = $wpdb->get_results( $sql );
332
+
333
+ // Nothing found, shortcut out.
334
+ if ( 0 === count( $results ) ) {
335
+ return;
336
+ }
337
+
338
+ foreach ( $results as $result ) {
339
+ $as3cf_item = new Media_Library_Item(
340
+ $this->provider(),
341
+ $this->region(),
342
+ $this->bucket(),
343
+ $this->path(),
344
+ $this->is_private(),
345
+ $result->post_id,
346
+ $this->source_path(),
347
+ wp_basename( $this->original_source_path() ),
348
+ $this->private_sizes()
349
+ );
350
+ $as3cf_item->save();
351
+ }
352
+ }
353
+
354
+ /*
355
+ * >>> LEGACY ROUTINES BEGIN >>>
356
+ */
357
+
358
+ /**
359
+ * Convert the provider info array for an attachment to item object.
360
+ *
361
+ * @param integer $source_id
362
+ * @param array $provider_info
363
+ *
364
+ * @return bool|Media_Library_Item
365
+ */
366
+ private static function _legacy_provider_info_to_item( $source_id, $provider_info ) {
367
+ $attached_file = get_post_meta( $source_id, '_wp_attached_file', true );
368
+
369
+ if ( is_string( $attached_file ) && ! empty( $attached_file ) ) {
370
+ $private_sizes = array();
371
+
372
+ if ( ! empty( $provider_info['sizes'] ) && is_array( $provider_info['sizes'] ) ) {
373
+ $private_sizes = array_keys( $provider_info['sizes'] );
374
+ }
375
+
376
+ return new static(
377
+ $provider_info['provider'],
378
+ $provider_info['region'],
379
+ $provider_info['bucket'],
380
+ $provider_info['key'],
381
+ isset( $provider_info['acl'] ) && false !== strpos( $provider_info['acl'], 'private' ) ? true : false,
382
+ $source_id,
383
+ $attached_file,
384
+ wp_basename( $attached_file ),
385
+ $private_sizes
386
+ );
387
+ }
388
+
389
+ return false;
390
+ }
391
+
392
+ /**
393
+ * Get attachment provider info
394
+ *
395
+ * @param int $post_id
396
+ *
397
+ * @return bool|array
398
+ */
399
+ private static function _legacy_get_attachment_provider_info( $post_id ) {
400
+ $provider_object = get_post_meta( $post_id, 'amazonS3_info', true );
401
+
402
+ if ( ! empty( $provider_object ) && is_array( $provider_object ) && ! empty( $provider_object['bucket'] ) && ! empty( $provider_object['key'] ) ) {
403
+ $provider_object = array_merge( array(
404
+ 'provider' => Amazon_S3_And_CloudFront::get_default_provider(),
405
+ ), $provider_object );
406
+ } else {
407
+ return false;
408
+ }
409
+
410
+ $provider_object['region'] = static::_legacy_get_provider_object_region( $provider_object );
411
+
412
+ if ( is_wp_error( $provider_object['region'] ) ) {
413
+ return false;
414
+ }
415
+
416
+ $provider_object = apply_filters( 'as3cf_get_attachment_s3_info', $provider_object, $post_id ); // Backwards compatibility
417
+
418
+ return apply_filters( 'as3cf_get_attachment_provider_info', $provider_object, $post_id );
419
+ }
420
+
421
+ /**
422
+ * Get the region of the bucket stored in the provider metadata.
423
+ *
424
+ * @param array $provider_object
425
+ *
426
+ * @return string|WP_Error - region name
427
+ */
428
+ private static function _legacy_get_provider_object_region( $provider_object ) {
429
+ if ( ! isset( $provider_object['region'] ) ) {
430
+ /** @var Amazon_S3_And_CloudFront $as3cf */
431
+ global $as3cf;
432
+
433
+ // If region hasn't been stored in the provider metadata retrieve using the bucket.
434
+ $region = $as3cf->get_bucket_region( $provider_object['bucket'], true );
435
+
436
+ // Could just return $region here regardless, but this format is good for debug during legacy migration.
437
+ if ( is_wp_error( $region ) ) {
438
+ return $region;
439
+ }
440
+
441
+ $provider_object['region'] = $region;
442
+ }
443
+
444
+ return $provider_object['region'];
445
+ }
446
+
447
+ /*
448
+ * <<< LEGACY ROUTINES END <<<
449
+ */
450
+ }
classes/upgrades/upgrade-file-sizes.php CHANGED
@@ -12,6 +12,7 @@
12
  namespace DeliciousBrains\WP_Offload_Media\Upgrades;
13
 
14
  use AS3CF_Error;
 
15
  use Exception;
16
 
17
  /**
@@ -64,14 +65,19 @@ class Upgrade_File_Sizes extends Upgrade {
64
  return false;
65
  }
66
 
67
- $region = $this->as3cf->get_provider_object_region( $provider_object );
68
- if ( is_wp_error( $region ) ) {
69
- AS3CF_Error::log( 'Failed to get the region for the bucket of the attachment ' . $attachment->ID );
 
 
70
  $this->error_count++;
71
 
72
  return false;
73
  }
74
 
 
 
 
75
  $provider_client = $this->as3cf->get_provider_client( $region, true );
76
  $main_file = $provider_object['key'];
77
 
12
  namespace DeliciousBrains\WP_Offload_Media\Upgrades;
13
 
14
  use AS3CF_Error;
15
+ use DeliciousBrains\WP_Offload_Media\Items\Media_Library_Item;
16
  use Exception;
17
 
18
  /**
65
  return false;
66
  }
67
 
68
+ // Using Media_Library_Item::get_by_source_id falls back to legacy metadata and substitutes in defaults and potentially missing values.
69
+ $as3cf_item = Media_Library_Item::get_by_source_id( $attachment->ID );
70
+
71
+ if ( ! $as3cf_item ) {
72
+ AS3CF_Error::log( 'Could not construct item for attachment with ID ' . $attachment->ID . ' from legacy offload metadata.' );
73
  $this->error_count++;
74
 
75
  return false;
76
  }
77
 
78
+ // $as3cf_item can't exist without a region value, so we can just use it here.
79
+ $region = $as3cf_item->region();
80
+
81
  $provider_client = $this->as3cf->get_provider_client( $region, true );
82
  $main_file = $provider_object['key'];
83
 
classes/upgrades/upgrade-items-table.php ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Upgrade Metadata to use custom objects table.
4
+ *
5
+ * @package amazon-s3-and-cloudfront
6
+ * @subpackage Classes/Upgrades/Upgrade_Items_Table
7
+ * @copyright Copyright (c) 2014, Delicious Brains
8
+ * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
9
+ * @since 2.3.0
10
+ */
11
+
12
+ namespace DeliciousBrains\WP_Offload_Media\Upgrades;
13
+
14
+ use AS3CF_Error;
15
+ use DeliciousBrains\WP_Offload_Media\Items\Item;
16
+ use DeliciousBrains\WP_Offload_Media\Items\Media_Library_Item;
17
+
18
+ /**
19
+ * Upgrade_Items_Table Class
20
+ *
21
+ * This class handles updating the offload metadata for attachments to use a custom table.
22
+ *
23
+ * @since 2.3.0
24
+ */
25
+ class Upgrade_Items_Table extends Upgrade {
26
+
27
+ /**
28
+ * @var int
29
+ */
30
+ protected $upgrade_id = 8;
31
+
32
+ /**
33
+ * @var string
34
+ */
35
+ protected $upgrade_name = 'as3cf_items_table';
36
+
37
+ /**
38
+ * @var string 'metadata', 'attachment'
39
+ */
40
+ protected $upgrade_type = 'metadata';
41
+
42
+ /**
43
+ * Get running update text.
44
+ *
45
+ * @return string
46
+ */
47
+ protected function get_running_update_text() {
48
+ return __( 'and updating the plugin\'s metadata to use a faster storage method. During the update the site\'s total offloaded media count may be inaccurate but will settle down shortly after completing.', 'amazon-s3-and-cloudfront' );
49
+ }
50
+
51
+ /**
52
+ * Move an attachment's provider object data from the postmeta table to the custom as3cf_objects table.
53
+ *
54
+ * @param mixed $attachment
55
+ *
56
+ * @return bool
57
+ */
58
+ protected function upgrade_item( $attachment ) {
59
+ // Make sure legacy metadata isn't broken.
60
+ $provider_object = unserialize( $attachment->provider_object );
61
+
62
+ if ( false === $provider_object ) {
63
+ AS3CF_Error::log( 'Failed to unserialize legacy offload metadata for attachment ' . $attachment->ID . ': ' . $attachment->provider_object );
64
+ $this->error_count++;
65
+
66
+ return false;
67
+ }
68
+
69
+ if ( empty( $attachment->source_path ) ) {
70
+ AS3CF_Error::log( 'Attachment with ID ' . $attachment->ID . ' with legacy offload metadata has no local file path.' );
71
+ $this->error_count++;
72
+
73
+ return false;
74
+ }
75
+
76
+ // Using Media_Library_Item::get_by_source_id falls back to legacy metadata and substitutes in defaults and potentially missing values.
77
+ // If we're here we already know there's legacy metadata and that there isn't a new items table record yet,
78
+ // or there's legacy metadata and an existing items table record that we can just re-save without issue before deleting legacy metadata.
79
+ // An existing items table entry takes precedence over legacy metadata to avoid accidental overrides from migrations, custom code or other plugins.
80
+ $as3cf_item = Media_Library_Item::get_by_source_id( $attachment->ID );
81
+
82
+ if ( ! $as3cf_item ) {
83
+ AS3CF_Error::log( 'Could not construct item for attachment with ID ' . $attachment->ID . ' from legacy offload metadata.' );
84
+ $this->error_count++;
85
+
86
+ return false;
87
+ }
88
+
89
+ $result = $as3cf_item->save();
90
+
91
+ if ( is_wp_error( $result ) ) {
92
+ AS3CF_Error::log( 'Error saving item: ' . $result->get_error_message() );
93
+ $this->error_count++;
94
+
95
+ return false;
96
+ }
97
+
98
+ // Delete old metadata.
99
+ return delete_post_meta( $attachment->ID, 'amazonS3_info' );
100
+ }
101
+
102
+ /**
103
+ * Get a count of all attachments to be processed.
104
+ * for the whole site
105
+ *
106
+ * @return int
107
+ */
108
+ protected function count_items_to_process() {
109
+ return $this->count_attachments_with_legacy_metadata( $this->blog_prefix );
110
+ }
111
+
112
+ /**
113
+ * Get all attachments to be processed.
114
+ *
115
+ * @param string $prefix Table prefix for blog.
116
+ * @param int $limit
117
+ * @param bool|mixed $offset
118
+ *
119
+ * @return array
120
+ */
121
+ protected function get_items_to_process( $prefix, $limit, $offset = false ) {
122
+ $attachments = $this->get_attachments_with_legacy_metadata( $prefix, false, $limit );
123
+
124
+ return $attachments;
125
+ }
126
+
127
+ /**
128
+ * Get a count of attachments that have legacy metadata.
129
+ *
130
+ * @param string $prefix Table prefix for blog.
131
+ *
132
+ * @return int
133
+ */
134
+ protected function count_attachments_with_legacy_metadata( $prefix ) {
135
+ $count = $this->get_attachments_with_legacy_metadata( $prefix, true );
136
+
137
+ return $count;
138
+ }
139
+
140
+ /**
141
+ * Wrapper for database call to get attachments with legacy metadata.
142
+ *
143
+ * @param string $prefix Table prefix for blog.
144
+ * @param bool $count return count of attachments
145
+ * @param null|int $limit
146
+ *
147
+ * @return mixed
148
+ */
149
+ protected function get_attachments_with_legacy_metadata( $prefix, $count = false, $limit = null ) {
150
+ global $wpdb;
151
+
152
+ $sql = "
153
+ FROM {$prefix}postmeta AS a, {$prefix}postmeta AS p
154
+ WHERE a.meta_key = '_wp_attached_file'
155
+ AND p.meta_key = 'amazonS3_info'
156
+ AND a.post_id = p.post_id
157
+ ";
158
+
159
+ if ( $count ) {
160
+ $sql = 'SELECT COUNT(DISTINCT p.post_id)' . $sql;
161
+
162
+ return $wpdb->get_var( $sql );
163
+ }
164
+
165
+ $sql = 'SELECT DISTINCT a.post_id AS ID, a.meta_value AS source_path, p.meta_value AS provider_object' . $sql;
166
+ $sql .= ' ORDER BY p.post_id, p.meta_id';
167
+
168
+ if ( $limit && $limit > 0 ) {
169
+ $sql .= sprintf( ' LIMIT %d', (int) $limit );
170
+ }
171
+
172
+ return $wpdb->get_results( $sql, OBJECT );
173
+ }
174
+ }
classes/upgrades/upgrade-region-meta.php CHANGED
@@ -12,6 +12,7 @@
12
  namespace DeliciousBrains\WP_Offload_Media\Upgrades;
13
 
14
  use AS3CF_Error;
 
15
 
16
  /**
17
  * Upgrade_Region_Meta Class
@@ -61,10 +62,24 @@ class Upgrade_Region_Meta extends Upgrade {
61
 
62
  return false;
63
  }
64
- // retrieve region and update the attachment metadata
65
- $region = $this->as3cf->get_provider_object_region( $provider_object, $attachment->ID );
66
- if ( is_wp_error( $region ) ) {
67
- AS3CF_Error::log( 'Error updating region: ' . $region->get_error_message() );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  $this->error_count++;
69
 
70
  return false;
12
  namespace DeliciousBrains\WP_Offload_Media\Upgrades;
13
 
14
  use AS3CF_Error;
15
+ use DeliciousBrains\WP_Offload_Media\Items\Media_Library_Item;
16
 
17
  /**
18
  * Upgrade_Region_Meta Class
62
 
63
  return false;
64
  }
65
+
66
+ // Using Media_Library_Item::get_by_source_id falls back to legacy metadata and substitutes in defaults and potentially missing values.
67
+ $as3cf_item = Media_Library_Item::get_by_source_id( $attachment->ID );
68
+
69
+ if ( ! $as3cf_item ) {
70
+ AS3CF_Error::log( 'Could not construct item for attachment with ID ' . $attachment->ID . ' from legacy offload metadata.' );
71
+ $this->error_count++;
72
+
73
+ return false;
74
+ }
75
+
76
+ // Update legacy amazonS3_info record with region required for subsequent upgrades.
77
+ $provider_object['region'] = $as3cf_item->region();
78
+
79
+ $result = update_post_meta( $attachment->ID, 'amazonS3_info', $provider_object );
80
+
81
+ if ( false === $result ) {
82
+ AS3CF_Error::log( 'Error updating region in legacy offload metadata for attachment ' . $attachment->ID );
83
  $this->error_count++;
84
 
85
  return false;
classes/upgrades/upgrade.php CHANGED
@@ -12,6 +12,7 @@
12
  namespace DeliciousBrains\WP_Offload_Media\Upgrades;
13
 
14
  use Amazon_S3_And_CloudFront;
 
15
  use DeliciousBrains\WP_Offload_Media\Upgrades\Exceptions\No_More_Blogs_Exception;
16
  use DeliciousBrains\WP_Offload_Media\Upgrades\Exceptions\Batch_Limits_Exceeded_Exception;
17
  use DeliciousBrains\WP_Offload_Media\Upgrades\Exceptions\Too_Many_Errors_Exception;
@@ -84,7 +85,7 @@ abstract class Upgrade {
84
  /**
85
  * @var string
86
  */
87
- protected $lock_key = 'as3cf_upgrade_lock';
88
 
89
  /**
90
  * @var int Time limit in seconds.
@@ -208,6 +209,9 @@ abstract class Upgrade {
208
  if ( $this->is_running() ) {
209
  // Make sure cron job is persisted in case it has dropped
210
  $this->schedule();
 
 
 
211
  }
212
 
213
  return false;
@@ -412,7 +416,7 @@ abstract class Upgrade {
412
  break;
413
  case self::STATUS_ERROR:
414
  $msg = $this->get_error_message();
415
- $action_text = __( 'Try Run It Again', 'amazon-s3-and-cloudfront' );
416
  $msg_type = 'error';
417
  break;
418
  default:
@@ -503,7 +507,7 @@ abstract class Upgrade {
503
  } else {
504
  // Set up any per-site state
505
  $this->switch_to_blog( get_current_blog_id() );
506
- $counts = $this->as3cf->count_attachments( $this->blog_prefix );
507
 
508
  // If there are no attachments, disable progress calculation
509
  // and protect against division by zero.
@@ -565,8 +569,7 @@ abstract class Upgrade {
565
  * Restart upgrade
566
  */
567
  protected function action_restart_update() {
568
- $this->schedule();
569
- $this->change_status_request( self::STATUS_RUNNING );
570
  }
571
 
572
  /**
@@ -646,7 +649,7 @@ abstract class Upgrade {
646
  * @return array
647
  */
648
  protected function get_session() {
649
- return get_site_option( 'update_' . $this->upgrade_name . '_session', array() );
650
  }
651
 
652
  /**
@@ -655,7 +658,7 @@ abstract class Upgrade {
655
  * @param array $session session data to store
656
  */
657
  protected function save_session( $session ) {
658
- update_site_option( 'update_' . $this->upgrade_name . '_session', $session );
659
  }
660
 
661
  /**
@@ -663,7 +666,7 @@ abstract class Upgrade {
663
  *
664
  */
665
  protected function clear_session() {
666
- delete_site_option( 'update_' . $this->upgrade_name . '_session' );
667
  }
668
 
669
  /**
@@ -703,7 +706,7 @@ abstract class Upgrade {
703
  * Lock upgrade.
704
  */
705
  protected function lock_upgrade() {
706
- set_site_transient( $this->lock_key, $this->upgrade_id, MINUTE_IN_SECONDS * 3 );
707
  }
708
 
709
  /**
@@ -712,7 +715,7 @@ abstract class Upgrade {
712
  * Voids the lock after 1 second rather than deleting to avoid a race condition.
713
  */
714
  protected function unlock_upgrade() {
715
- set_site_transient( $this->lock_key, $this->upgrade_id, 1 );
716
  }
717
 
718
  /**
@@ -720,8 +723,8 @@ abstract class Upgrade {
720
  *
721
  * @return bool
722
  */
723
- protected function is_locked() {
724
- return false !== get_site_transient( $this->lock_key );
725
  }
726
 
727
  /**
12
  namespace DeliciousBrains\WP_Offload_Media\Upgrades;
13
 
14
  use Amazon_S3_And_CloudFront;
15
+ use DeliciousBrains\WP_Offload_Media\Items\Media_Library_Item;
16
  use DeliciousBrains\WP_Offload_Media\Upgrades\Exceptions\No_More_Blogs_Exception;
17
  use DeliciousBrains\WP_Offload_Media\Upgrades\Exceptions\Batch_Limits_Exceeded_Exception;
18
  use DeliciousBrains\WP_Offload_Media\Upgrades\Exceptions\Too_Many_Errors_Exception;
85
  /**
86
  * @var string
87
  */
88
+ public static $lock_key = 'as3cf_upgrade_lock';
89
 
90
  /**
91
  * @var int Time limit in seconds.
209
  if ( $this->is_running() ) {
210
  // Make sure cron job is persisted in case it has dropped
211
  $this->schedule();
212
+ } else {
213
+ // Refresh the lock to stop anything from interfering while paused.
214
+ $this->lock_upgrade();
215
  }
216
 
217
  return false;
416
  break;
417
  case self::STATUS_ERROR:
418
  $msg = $this->get_error_message();
419
+ $action_text = __( 'Try To Run It Again', 'amazon-s3-and-cloudfront' );
420
  $msg_type = 'error';
421
  break;
422
  default:
507
  } else {
508
  // Set up any per-site state
509
  $this->switch_to_blog( get_current_blog_id() );
510
+ $counts = Media_Library_Item::count_attachments();
511
 
512
  // If there are no attachments, disable progress calculation
513
  // and protect against division by zero.
569
  * Restart upgrade
570
  */
571
  protected function action_restart_update() {
572
+ $this->init();
 
573
  }
574
 
575
  /**
649
  * @return array
650
  */
651
  protected function get_session() {
652
+ return get_site_option( 'as3cf_update_' . $this->upgrade_name . '_session', array() );
653
  }
654
 
655
  /**
658
  * @param array $session session data to store
659
  */
660
  protected function save_session( $session ) {
661
+ update_site_option( 'as3cf_update_' . $this->upgrade_name . '_session', $session );
662
  }
663
 
664
  /**
666
  *
667
  */
668
  protected function clear_session() {
669
+ delete_site_option( 'as3cf_update_' . $this->upgrade_name . '_session' );
670
  }
671
 
672
  /**
706
  * Lock upgrade.
707
  */
708
  protected function lock_upgrade() {
709
+ set_site_transient( static::$lock_key, $this->upgrade_id, MINUTE_IN_SECONDS * 3 );
710
  }
711
 
712
  /**
715
  * Voids the lock after 1 second rather than deleting to avoid a race condition.
716
  */
717
  protected function unlock_upgrade() {
718
+ set_site_transient( static::$lock_key, $this->upgrade_id, 1 );
719
  }
720
 
721
  /**
723
  *
724
  * @return bool
725
  */
726
+ public static function is_locked() {
727
+ return false !== get_site_transient( static::$lock_key );
728
  }
729
 
730
  /**
languages/amazon-s3-and-cloudfront-en.pot CHANGED
@@ -8,7 +8,7 @@ msgid ""
8
  msgstr ""
9
  "Project-Id-Version: amazon-s3-and-cloudfront\n"
10
  "Report-Msgid-Bugs-To: nom@deliciousbrains.com\n"
11
- "POT-Creation-Date: 2019-07-18 14:13+0100\n"
12
  "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
  "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,177 +17,182 @@ msgstr ""
17
  "Content-Type: text/plain; charset=UTF-8\n"
18
  "Content-Transfer-Encoding: 8bit\n"
19
 
20
- #: classes/amazon-s3-and-cloudfront.php:136
21
- #: classes/amazon-s3-and-cloudfront.php:137
22
  msgid "Offload Media Lite"
23
  msgstr ""
24
 
25
- #: classes/amazon-s3-and-cloudfront.php:274
26
- #: classes/amazon-s3-and-cloudfront.php:288
27
  msgid "Unknown"
28
  msgstr ""
29
 
30
- #: classes/amazon-s3-and-cloudfront.php:351
31
  #: view/bucket-setting.php:17
32
  #: view/provider-select.php:122
33
  msgid "defined in wp-config.php"
34
  msgstr ""
35
 
36
- #: classes/amazon-s3-and-cloudfront.php:1097
37
- #: classes/amazon-s3-and-cloudfront.php:1231
 
 
 
 
 
38
  #, php-format
39
  msgid "File %s does not exist"
40
  msgstr ""
41
 
42
- #: classes/amazon-s3-and-cloudfront.php:1110
43
  #, php-format
44
  msgid "Mime type %s is not allowed"
45
  msgstr ""
46
 
47
- #: classes/amazon-s3-and-cloudfront.php:1121
48
  msgid "Already offloaded to a different provider"
49
  msgstr ""
50
 
51
- #: classes/amazon-s3-and-cloudfront.php:1195
52
- #: classes/amazon-s3-and-cloudfront.php:1239
53
  #, php-format
54
  msgid "Error offloading %s to provider: %s"
55
  msgstr ""
56
 
57
- #: classes/amazon-s3-and-cloudfront.php:2381
58
  msgid "This action can only be performed through an admin screen."
59
  msgstr ""
60
 
61
- #: classes/amazon-s3-and-cloudfront.php:2383
62
  msgid "Cheatin&#8217; eh?"
63
  msgstr ""
64
 
65
- #: classes/amazon-s3-and-cloudfront.php:2385
66
  msgid "You do not have sufficient permissions to access this page."
67
  msgstr ""
68
 
69
- #: classes/amazon-s3-and-cloudfront.php:2665
70
  msgid "Error Getting Bucket Region"
71
  msgstr ""
72
 
73
- #: classes/amazon-s3-and-cloudfront.php:2666
74
  #, php-format
75
  msgid "There was an error attempting to get the region of the bucket %s: %s"
76
  msgstr ""
77
 
78
- #: classes/amazon-s3-and-cloudfront.php:2797
79
  msgid ""
80
  "This is a test file to check if the user has write permission to the bucket. "
81
  "Delete me if found."
82
  msgstr ""
83
 
84
- #: classes/amazon-s3-and-cloudfront.php:2803
85
  #, php-format
86
  msgid ""
87
  "There was an error attempting to check the permissions of the bucket %s: %s"
88
  msgstr ""
89
 
90
- #: classes/amazon-s3-and-cloudfront.php:2885
91
  msgid "Error creating bucket"
92
  msgstr ""
93
 
94
- #: classes/amazon-s3-and-cloudfront.php:2886
95
  msgid "Bucket name too short."
96
  msgstr ""
97
 
98
- #: classes/amazon-s3-and-cloudfront.php:2887
99
  msgid "Bucket name too long."
100
  msgstr ""
101
 
102
- #: classes/amazon-s3-and-cloudfront.php:2888
103
  msgid ""
104
  "Invalid character. Bucket names can contain lowercase letters, numbers, "
105
  "periods and hyphens."
106
  msgstr ""
107
 
108
- #: classes/amazon-s3-and-cloudfront.php:2889
109
  msgid "Error saving bucket"
110
  msgstr ""
111
 
112
- #: classes/amazon-s3-and-cloudfront.php:2890
113
  msgid "Error fetching buckets"
114
  msgstr ""
115
 
116
- #: classes/amazon-s3-and-cloudfront.php:2891
117
  msgid "Error getting URL preview: "
118
  msgstr ""
119
 
120
- #: classes/amazon-s3-and-cloudfront.php:2892
121
  msgid "The changes you made will be lost if you navigate away from this page"
122
  msgstr ""
123
 
124
- #: classes/amazon-s3-and-cloudfront.php:2893
125
  msgid "Getting diagnostic info..."
126
  msgstr ""
127
 
128
- #: classes/amazon-s3-and-cloudfront.php:2894
129
  msgid "Error getting diagnostic info: "
130
  msgstr ""
131
 
132
- #: classes/amazon-s3-and-cloudfront.php:2895
133
  msgctxt "placeholder for hidden access key, 39 char max"
134
  msgid "-- not shown --"
135
  msgstr ""
136
 
137
- #: classes/amazon-s3-and-cloudfront.php:2897
138
- #: classes/amazon-s3-and-cloudfront.php:4934
139
  msgid "Settings saved."
140
  msgstr ""
141
 
142
- #: classes/amazon-s3-and-cloudfront.php:2987
143
  msgid "Cheatin' eh?"
144
  msgstr ""
145
 
146
- #: classes/amazon-s3-and-cloudfront.php:3071
147
  msgid "No bucket name provided."
148
  msgstr ""
149
 
150
- #: classes/amazon-s3-and-cloudfront.php:3080
151
  msgid "Bucket name not valid."
152
  msgstr ""
153
 
154
- #: classes/amazon-s3-and-cloudfront.php:3093
155
  msgid "No region provided."
156
  msgstr ""
157
 
158
- #: classes/amazon-s3-and-cloudfront.php:3170
159
  #: view/provider-select.php:329
160
  msgctxt "placeholder for hidden secret access key, 39 char max"
161
  msgid "-- not shown --"
162
  msgstr ""
163
 
164
- #: classes/amazon-s3-and-cloudfront.php:3193
165
  msgid "Key File not valid JSON."
166
  msgstr ""
167
 
168
- #: classes/amazon-s3-and-cloudfront.php:3239
169
  msgctxt "Show the media library tab"
170
  msgid "Media Library"
171
  msgstr ""
172
 
173
- #: classes/amazon-s3-and-cloudfront.php:3240
174
  msgctxt "Show the addons tab"
175
  msgid "Addons"
176
  msgstr ""
177
 
178
- #: classes/amazon-s3-and-cloudfront.php:3241
179
  msgctxt "Show the support tab"
180
  msgid "Support"
181
  msgstr ""
182
 
183
- #: classes/amazon-s3-and-cloudfront.php:3461
184
  #, php-format
185
  msgid ""
186
  "<strong>WP Offload Media</strong> &mdash; The file %s has been given %s "
187
  "permissions in the bucket."
188
  msgstr ""
189
 
190
- #: classes/amazon-s3-and-cloudfront.php:3480
191
  msgid ""
192
  "<strong>WP Offload Media Requirement Missing</strong> &mdash; Looks like you "
193
  "don't have an image manipulation library installed on this server and "
@@ -195,18 +200,18 @@ msgid ""
195
  "Please setup GD or ImageMagick."
196
  msgstr ""
197
 
198
- #: classes/amazon-s3-and-cloudfront.php:4193
199
  #, php-format
200
  msgid ""
201
  "<a href=\"%s\">Define your access keys</a> to enable write access to the "
202
  "bucket"
203
  msgstr ""
204
 
205
- #: classes/amazon-s3-and-cloudfront.php:4200
206
  msgid "Quick Start Guide"
207
  msgstr ""
208
 
209
- #: classes/amazon-s3-and-cloudfront.php:4202
210
  #, php-format
211
  msgid ""
212
  "Looks like we don't have write access to this bucket. It's likely that the "
@@ -215,7 +220,7 @@ msgid ""
215
  "correctly."
216
  msgstr ""
217
 
218
- #: classes/amazon-s3-and-cloudfront.php:4204
219
  #, php-format
220
  msgid ""
221
  "Looks like we don't have access to the buckets. It's likely that the user "
@@ -223,39 +228,39 @@ msgid ""
223
  "Please see our %s for instructions on setting up permissions correctly."
224
  msgstr ""
225
 
226
- #: classes/amazon-s3-and-cloudfront.php:4375
227
  msgid "WP Offload Media Activation"
228
  msgstr ""
229
 
230
- #: classes/amazon-s3-and-cloudfront.php:4376
231
  msgid ""
232
  "WP Offload Media Lite and WP Offload Media cannot both be active. We've "
233
  "automatically deactivated WP Offload Media Lite."
234
  msgstr ""
235
 
236
- #: classes/amazon-s3-and-cloudfront.php:4378
237
  msgid "WP Offload Media Lite Activation"
238
  msgstr ""
239
 
240
- #: classes/amazon-s3-and-cloudfront.php:4379
241
  msgid ""
242
  "WP Offload Media Lite and WP Offload Media cannot both be active. We've "
243
  "automatically deactivated WP Offload Media."
244
  msgstr ""
245
 
246
- #: classes/amazon-s3-and-cloudfront.php:4431
247
  msgid "More&nbsp;info&nbsp;&raquo;"
248
  msgstr ""
249
 
250
- #: classes/amazon-s3-and-cloudfront.php:4526
251
  msgid "this doc"
252
  msgstr ""
253
 
254
- #: classes/amazon-s3-and-cloudfront.php:4528
255
  msgid "WP Offload Media Feature Removed"
256
  msgstr ""
257
 
258
- #: classes/amazon-s3-and-cloudfront.php:4529
259
  #, php-format
260
  msgid ""
261
  "You had the \"Always non-SSL\" option selected in your settings, but we've "
@@ -266,59 +271,59 @@ msgid ""
266
  "to the old behavior."
267
  msgstr ""
268
 
269
- #: classes/amazon-s3-and-cloudfront.php:4559
270
  msgid "Offload"
271
  msgstr ""
272
 
273
- #: classes/amazon-s3-and-cloudfront.php:4667
274
  msgctxt "Storage provider key name"
275
  msgid "Storage Provider"
276
  msgstr ""
277
 
278
- #: classes/amazon-s3-and-cloudfront.php:4668
279
  msgctxt "Storage provider name"
280
  msgid "Storage Provider"
281
  msgstr ""
282
 
283
- #: classes/amazon-s3-and-cloudfront.php:4669
284
  msgctxt "Bucket name"
285
  msgid "Bucket"
286
  msgstr ""
287
 
288
- #: classes/amazon-s3-and-cloudfront.php:4670
289
  msgctxt "Path to file in bucket"
290
  msgid "Path"
291
  msgstr ""
292
 
293
- #: classes/amazon-s3-and-cloudfront.php:4671
294
  msgctxt "Location of bucket"
295
  msgid "Region"
296
  msgstr ""
297
 
298
- #: classes/amazon-s3-and-cloudfront.php:4672
299
  msgctxt "Access control list of the file in bucket"
300
  msgid "Access"
301
  msgstr ""
302
 
303
- #: classes/amazon-s3-and-cloudfront.php:4673
304
  msgid "URL"
305
  msgstr ""
306
 
307
- #: classes/amazon-s3-and-cloudfront.php:4897
308
  msgid "Assets Pull"
309
  msgstr ""
310
 
311
- #: classes/amazon-s3-and-cloudfront.php:4898
312
  msgid ""
313
  "An addon for WP Offload Media to serve your site's JS, CSS, and other "
314
  "enqueued assets from Amazon CloudFront or another CDN."
315
  msgstr ""
316
 
317
- #: classes/amazon-s3-and-cloudfront.php:4902
318
  msgid "Feature"
319
  msgstr ""
320
 
321
- #: classes/amazon-s3-and-cloudfront.php:4948
322
  #, php-format
323
  msgid ""
324
  "<strong>Amazon Web Services Plugin No Longer Required</strong> &mdash; As of "
@@ -329,7 +334,7 @@ msgid ""
329
  "plugin, it should be safe to deactivate and delete it. %2$s"
330
  msgstr ""
331
 
332
- #: classes/amazon-s3-and-cloudfront.php:4980
333
  #, php-format
334
  msgid ""
335
  "<strong>WP Offload Media Settings Moved</strong> &mdash; You now define your "
@@ -462,20 +467,20 @@ msgstr ""
462
  msgid "Settings"
463
  msgstr ""
464
 
465
- #: classes/as3cf-plugin-compatibility.php:579
466
  #, php-format
467
  msgid "The local directory %s does not exist and could not be created."
468
  msgstr ""
469
 
470
- #: classes/as3cf-plugin-compatibility.php:580
471
- #: classes/as3cf-plugin-compatibility.php:592
472
  #: classes/upgrades/upgrade-meta-wp-error.php:81
473
  #, php-format
474
  msgid ""
475
  "There was an error attempting to download the file %s from the bucket: %s"
476
  msgstr ""
477
 
478
- #: classes/as3cf-plugin-compatibility.php:917
479
  #, php-format
480
  msgid ""
481
  "<strong>Warning:</strong> This site is using PHP %1$s, in a future update WP "
@@ -515,7 +520,7 @@ msgstr ""
515
  msgid "and ensuring that only the local URL exists in EDD post meta."
516
  msgstr ""
517
 
518
- #: classes/upgrades/upgrade-file-sizes.php:48
519
  msgid ""
520
  "and updating the metadata with the sizes of files that have been removed "
521
  "from the server. This will allow us to serve the correct size for media "
@@ -551,12 +556,19 @@ msgid ""
551
  "make it run faster."
552
  msgstr ""
553
 
 
 
 
 
 
 
 
554
  #: classes/upgrades/upgrade-meta-wp-error.php:49
555
  msgid ""
556
  "and rebuilding the metadata for attachments that may have been corrupted."
557
  msgstr ""
558
 
559
- #: classes/upgrades/upgrade-region-meta.php:46
560
  msgid ""
561
  "and updating the metadata with the bucket region it is served from. This "
562
  "will allow us to serve your files from the proper region subdomain <span "
@@ -569,19 +581,19 @@ msgid ""
569
  "version."
570
  msgstr ""
571
 
572
- #: classes/upgrades/upgrade.php:403
573
  msgid "Pause Update"
574
  msgstr ""
575
 
576
- #: classes/upgrades/upgrade.php:411
577
  msgid "Restart Update"
578
  msgstr ""
579
 
580
- #: classes/upgrades/upgrade.php:415
581
- msgid "Try Run It Again"
582
  msgstr ""
583
 
584
- #: classes/upgrades/upgrade.php:438
585
  #, php-format
586
  msgid ""
587
  "<strong>Running %1$s Update%2$s</strong> &mdash; We&#8217;re going through "
@@ -591,14 +603,14 @@ msgid ""
591
  "performance."
592
  msgstr ""
593
 
594
- #: classes/upgrades/upgrade.php:452
595
  #, php-format
596
  msgid ""
597
  "<strong>%1$s Update Paused%2$s</strong> &mdash; Updating Media Library %3$s "
598
  "has been paused."
599
  msgstr ""
600
 
601
- #: classes/upgrades/upgrade.php:465
602
  #, php-format
603
  msgid ""
604
  "<strong>Error Updating %1$s</strong> &mdash; We ran into some errors "
@@ -606,12 +618,12 @@ msgid ""
606
  "been offloaded. Please check your error log for details. (#%3$d)"
607
  msgstr ""
608
 
609
- #: classes/upgrades/upgrade.php:489
610
  #, php-format
611
  msgid " (%s%% Complete)"
612
  msgstr ""
613
 
614
- #: classes/upgrades/upgrade.php:623
615
  #, php-format
616
  msgid "Every %d Minutes"
617
  msgstr ""
8
  msgstr ""
9
  "Project-Id-Version: amazon-s3-and-cloudfront\n"
10
  "Report-Msgid-Bugs-To: nom@deliciousbrains.com\n"
11
+ "POT-Creation-Date: 2019-11-12 10:09+0000\n"
12
  "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
  "Language-Team: LANGUAGE <LL@li.org>\n"
17
  "Content-Type: text/plain; charset=UTF-8\n"
18
  "Content-Transfer-Encoding: 8bit\n"
19
 
20
+ #: classes/amazon-s3-and-cloudfront.php:138
21
+ #: classes/amazon-s3-and-cloudfront.php:139
22
  msgid "Offload Media Lite"
23
  msgstr ""
24
 
25
+ #: classes/amazon-s3-and-cloudfront.php:279
26
+ #: classes/amazon-s3-and-cloudfront.php:293
27
  msgid "Unknown"
28
  msgstr ""
29
 
30
+ #: classes/amazon-s3-and-cloudfront.php:365
31
  #: view/bucket-setting.php:17
32
  #: view/provider-select.php:122
33
  msgid "defined in wp-config.php"
34
  msgstr ""
35
 
36
+ #: classes/amazon-s3-and-cloudfront.php:1141
37
+ #, php-format
38
+ msgid "Media Library item with ID %d does not have a valid file path"
39
+ msgstr ""
40
+
41
+ #: classes/amazon-s3-and-cloudfront.php:1191
42
+ #: classes/amazon-s3-and-cloudfront.php:1353
43
  #, php-format
44
  msgid "File %s does not exist"
45
  msgstr ""
46
 
47
+ #: classes/amazon-s3-and-cloudfront.php:1221
48
  #, php-format
49
  msgid "Mime type %s is not allowed"
50
  msgstr ""
51
 
52
+ #: classes/amazon-s3-and-cloudfront.php:1232
53
  msgid "Already offloaded to a different provider"
54
  msgstr ""
55
 
56
+ #: classes/amazon-s3-and-cloudfront.php:1315
57
+ #: classes/amazon-s3-and-cloudfront.php:1366
58
  #, php-format
59
  msgid "Error offloading %s to provider: %s"
60
  msgstr ""
61
 
62
+ #: classes/amazon-s3-and-cloudfront.php:2521
63
  msgid "This action can only be performed through an admin screen."
64
  msgstr ""
65
 
66
+ #: classes/amazon-s3-and-cloudfront.php:2523
67
  msgid "Cheatin&#8217; eh?"
68
  msgstr ""
69
 
70
+ #: classes/amazon-s3-and-cloudfront.php:2525
71
  msgid "You do not have sufficient permissions to access this page."
72
  msgstr ""
73
 
74
+ #: classes/amazon-s3-and-cloudfront.php:2814
75
  msgid "Error Getting Bucket Region"
76
  msgstr ""
77
 
78
+ #: classes/amazon-s3-and-cloudfront.php:2815
79
  #, php-format
80
  msgid "There was an error attempting to get the region of the bucket %s: %s"
81
  msgstr ""
82
 
83
+ #: classes/amazon-s3-and-cloudfront.php:2918
84
  msgid ""
85
  "This is a test file to check if the user has write permission to the bucket. "
86
  "Delete me if found."
87
  msgstr ""
88
 
89
+ #: classes/amazon-s3-and-cloudfront.php:2924
90
  #, php-format
91
  msgid ""
92
  "There was an error attempting to check the permissions of the bucket %s: %s"
93
  msgstr ""
94
 
95
+ #: classes/amazon-s3-and-cloudfront.php:3006
96
  msgid "Error creating bucket"
97
  msgstr ""
98
 
99
+ #: classes/amazon-s3-and-cloudfront.php:3007
100
  msgid "Bucket name too short."
101
  msgstr ""
102
 
103
+ #: classes/amazon-s3-and-cloudfront.php:3008
104
  msgid "Bucket name too long."
105
  msgstr ""
106
 
107
+ #: classes/amazon-s3-and-cloudfront.php:3009
108
  msgid ""
109
  "Invalid character. Bucket names can contain lowercase letters, numbers, "
110
  "periods and hyphens."
111
  msgstr ""
112
 
113
+ #: classes/amazon-s3-and-cloudfront.php:3010
114
  msgid "Error saving bucket"
115
  msgstr ""
116
 
117
+ #: classes/amazon-s3-and-cloudfront.php:3011
118
  msgid "Error fetching buckets"
119
  msgstr ""
120
 
121
+ #: classes/amazon-s3-and-cloudfront.php:3012
122
  msgid "Error getting URL preview: "
123
  msgstr ""
124
 
125
+ #: classes/amazon-s3-and-cloudfront.php:3013
126
  msgid "The changes you made will be lost if you navigate away from this page"
127
  msgstr ""
128
 
129
+ #: classes/amazon-s3-and-cloudfront.php:3014
130
  msgid "Getting diagnostic info..."
131
  msgstr ""
132
 
133
+ #: classes/amazon-s3-and-cloudfront.php:3015
134
  msgid "Error getting diagnostic info: "
135
  msgstr ""
136
 
137
+ #: classes/amazon-s3-and-cloudfront.php:3016
138
  msgctxt "placeholder for hidden access key, 39 char max"
139
  msgid "-- not shown --"
140
  msgstr ""
141
 
142
+ #: classes/amazon-s3-and-cloudfront.php:3018
143
+ #: classes/amazon-s3-and-cloudfront.php:5012
144
  msgid "Settings saved."
145
  msgstr ""
146
 
147
+ #: classes/amazon-s3-and-cloudfront.php:3108
148
  msgid "Cheatin' eh?"
149
  msgstr ""
150
 
151
+ #: classes/amazon-s3-and-cloudfront.php:3192
152
  msgid "No bucket name provided."
153
  msgstr ""
154
 
155
+ #: classes/amazon-s3-and-cloudfront.php:3201
156
  msgid "Bucket name not valid."
157
  msgstr ""
158
 
159
+ #: classes/amazon-s3-and-cloudfront.php:3214
160
  msgid "No region provided."
161
  msgstr ""
162
 
163
+ #: classes/amazon-s3-and-cloudfront.php:3291
164
  #: view/provider-select.php:329
165
  msgctxt "placeholder for hidden secret access key, 39 char max"
166
  msgid "-- not shown --"
167
  msgstr ""
168
 
169
+ #: classes/amazon-s3-and-cloudfront.php:3314
170
  msgid "Key File not valid JSON."
171
  msgstr ""
172
 
173
+ #: classes/amazon-s3-and-cloudfront.php:3360
174
  msgctxt "Show the media library tab"
175
  msgid "Media Library"
176
  msgstr ""
177
 
178
+ #: classes/amazon-s3-and-cloudfront.php:3361
179
  msgctxt "Show the addons tab"
180
  msgid "Addons"
181
  msgstr ""
182
 
183
+ #: classes/amazon-s3-and-cloudfront.php:3362
184
  msgctxt "Show the support tab"
185
  msgid "Support"
186
  msgstr ""
187
 
188
+ #: classes/amazon-s3-and-cloudfront.php:3589
189
  #, php-format
190
  msgid ""
191
  "<strong>WP Offload Media</strong> &mdash; The file %s has been given %s "
192
  "permissions in the bucket."
193
  msgstr ""
194
 
195
+ #: classes/amazon-s3-and-cloudfront.php:3608
196
  msgid ""
197
  "<strong>WP Offload Media Requirement Missing</strong> &mdash; Looks like you "
198
  "don't have an image manipulation library installed on this server and "
200
  "Please setup GD or ImageMagick."
201
  msgstr ""
202
 
203
+ #: classes/amazon-s3-and-cloudfront.php:4327
204
  #, php-format
205
  msgid ""
206
  "<a href=\"%s\">Define your access keys</a> to enable write access to the "
207
  "bucket"
208
  msgstr ""
209
 
210
+ #: classes/amazon-s3-and-cloudfront.php:4334
211
  msgid "Quick Start Guide"
212
  msgstr ""
213
 
214
+ #: classes/amazon-s3-and-cloudfront.php:4336
215
  #, php-format
216
  msgid ""
217
  "Looks like we don't have write access to this bucket. It's likely that the "
220
  "correctly."
221
  msgstr ""
222
 
223
+ #: classes/amazon-s3-and-cloudfront.php:4338
224
  #, php-format
225
  msgid ""
226
  "Looks like we don't have access to the buckets. It's likely that the user "
228
  "Please see our %s for instructions on setting up permissions correctly."
229
  msgstr ""
230
 
231
+ #: classes/amazon-s3-and-cloudfront.php:4473
232
  msgid "WP Offload Media Activation"
233
  msgstr ""
234
 
235
+ #: classes/amazon-s3-and-cloudfront.php:4474
236
  msgid ""
237
  "WP Offload Media Lite and WP Offload Media cannot both be active. We've "
238
  "automatically deactivated WP Offload Media Lite."
239
  msgstr ""
240
 
241
+ #: classes/amazon-s3-and-cloudfront.php:4476
242
  msgid "WP Offload Media Lite Activation"
243
  msgstr ""
244
 
245
+ #: classes/amazon-s3-and-cloudfront.php:4477
246
  msgid ""
247
  "WP Offload Media Lite and WP Offload Media cannot both be active. We've "
248
  "automatically deactivated WP Offload Media."
249
  msgstr ""
250
 
251
+ #: classes/amazon-s3-and-cloudfront.php:4529
252
  msgid "More&nbsp;info&nbsp;&raquo;"
253
  msgstr ""
254
 
255
+ #: classes/amazon-s3-and-cloudfront.php:4624
256
  msgid "this doc"
257
  msgstr ""
258
 
259
+ #: classes/amazon-s3-and-cloudfront.php:4626
260
  msgid "WP Offload Media Feature Removed"
261
  msgstr ""
262
 
263
+ #: classes/amazon-s3-and-cloudfront.php:4627
264
  #, php-format
265
  msgid ""
266
  "You had the \"Always non-SSL\" option selected in your settings, but we've "
271
  "to the old behavior."
272
  msgstr ""
273
 
274
+ #: classes/amazon-s3-and-cloudfront.php:4657
275
  msgid "Offload"
276
  msgstr ""
277
 
278
+ #: classes/amazon-s3-and-cloudfront.php:4762
279
  msgctxt "Storage provider key name"
280
  msgid "Storage Provider"
281
  msgstr ""
282
 
283
+ #: classes/amazon-s3-and-cloudfront.php:4763
284
  msgctxt "Storage provider name"
285
  msgid "Storage Provider"
286
  msgstr ""
287
 
288
+ #: classes/amazon-s3-and-cloudfront.php:4764
289
  msgctxt "Bucket name"
290
  msgid "Bucket"
291
  msgstr ""
292
 
293
+ #: classes/amazon-s3-and-cloudfront.php:4765
294
  msgctxt "Path to file in bucket"
295
  msgid "Path"
296
  msgstr ""
297
 
298
+ #: classes/amazon-s3-and-cloudfront.php:4766
299
  msgctxt "Location of bucket"
300
  msgid "Region"
301
  msgstr ""
302
 
303
+ #: classes/amazon-s3-and-cloudfront.php:4767
304
  msgctxt "Access control list of the file in bucket"
305
  msgid "Access"
306
  msgstr ""
307
 
308
+ #: classes/amazon-s3-and-cloudfront.php:4768
309
  msgid "URL"
310
  msgstr ""
311
 
312
+ #: classes/amazon-s3-and-cloudfront.php:4975
313
  msgid "Assets Pull"
314
  msgstr ""
315
 
316
+ #: classes/amazon-s3-and-cloudfront.php:4976
317
  msgid ""
318
  "An addon for WP Offload Media to serve your site's JS, CSS, and other "
319
  "enqueued assets from Amazon CloudFront or another CDN."
320
  msgstr ""
321
 
322
+ #: classes/amazon-s3-and-cloudfront.php:4980
323
  msgid "Feature"
324
  msgstr ""
325
 
326
+ #: classes/amazon-s3-and-cloudfront.php:5026
327
  #, php-format
328
  msgid ""
329
  "<strong>Amazon Web Services Plugin No Longer Required</strong> &mdash; As of "
334
  "plugin, it should be safe to deactivate and delete it. %2$s"
335
  msgstr ""
336
 
337
+ #: classes/amazon-s3-and-cloudfront.php:5058
338
  #, php-format
339
  msgid ""
340
  "<strong>WP Offload Media Settings Moved</strong> &mdash; You now define your "
467
  msgid "Settings"
468
  msgstr ""
469
 
470
+ #: classes/as3cf-plugin-compatibility.php:592
471
  #, php-format
472
  msgid "The local directory %s does not exist and could not be created."
473
  msgstr ""
474
 
475
+ #: classes/as3cf-plugin-compatibility.php:593
476
+ #: classes/as3cf-plugin-compatibility.php:605
477
  #: classes/upgrades/upgrade-meta-wp-error.php:81
478
  #, php-format
479
  msgid ""
480
  "There was an error attempting to download the file %s from the bucket: %s"
481
  msgstr ""
482
 
483
+ #: classes/as3cf-plugin-compatibility.php:930
484
  #, php-format
485
  msgid ""
486
  "<strong>Warning:</strong> This site is using PHP %1$s, in a future update WP "
520
  msgid "and ensuring that only the local URL exists in EDD post meta."
521
  msgstr ""
522
 
523
+ #: classes/upgrades/upgrade-file-sizes.php:49
524
  msgid ""
525
  "and updating the metadata with the sizes of files that have been removed "
526
  "from the server. This will allow us to serve the correct size for media "
556
  "make it run faster."
557
  msgstr ""
558
 
559
+ #: classes/upgrades/upgrade-items-table.php:48
560
+ msgid ""
561
+ "and updating the plugin's metadata to use a faster storage method. During "
562
+ "the update the site's total offloaded media count may be inaccurate but will "
563
+ "settle down shortly after completing."
564
+ msgstr ""
565
+
566
  #: classes/upgrades/upgrade-meta-wp-error.php:49
567
  msgid ""
568
  "and rebuilding the metadata for attachments that may have been corrupted."
569
  msgstr ""
570
 
571
+ #: classes/upgrades/upgrade-region-meta.php:47
572
  msgid ""
573
  "and updating the metadata with the bucket region it is served from. This "
574
  "will allow us to serve your files from the proper region subdomain <span "
581
  "version."
582
  msgstr ""
583
 
584
+ #: classes/upgrades/upgrade.php:407
585
  msgid "Pause Update"
586
  msgstr ""
587
 
588
+ #: classes/upgrades/upgrade.php:415
589
  msgid "Restart Update"
590
  msgstr ""
591
 
592
+ #: classes/upgrades/upgrade.php:419
593
+ msgid "Try To Run It Again"
594
  msgstr ""
595
 
596
+ #: classes/upgrades/upgrade.php:442
597
  #, php-format
598
  msgid ""
599
  "<strong>Running %1$s Update%2$s</strong> &mdash; We&#8217;re going through "
603
  "performance."
604
  msgstr ""
605
 
606
+ #: classes/upgrades/upgrade.php:456
607
  #, php-format
608
  msgid ""
609
  "<strong>%1$s Update Paused%2$s</strong> &mdash; Updating Media Library %3$s "
610
  "has been paused."
611
  msgstr ""
612
 
613
+ #: classes/upgrades/upgrade.php:469
614
  #, php-format
615
  msgid ""
616
  "<strong>Error Updating %1$s</strong> &mdash; We ran into some errors "
618
  "been offloaded. Please check your error log for details. (#%3$d)"
619
  msgstr ""
620
 
621
+ #: classes/upgrades/upgrade.php:493
622
  #, php-format
623
  msgid " (%s%% Complete)"
624
  msgstr ""
625
 
626
+ #: classes/upgrades/upgrade.php:626
627
  #, php-format
628
  msgid "Every %d Minutes"
629
  msgstr ""
readme.txt 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.
@@ -67,6 +67,9 @@ If you upgrade to the pro version of [WP Offload Media](https://deliciousbrains.
67
 
68
  == Upgrade Notice ==
69
 
 
 
 
70
  = 2.0 =
71
  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+.
72
 
@@ -78,6 +81,18 @@ This version requires PHP 5.3.3+ and the Amazon Web Services plugin
78
 
79
  == Changelog ==
80
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  = WP Offload Media Lite 2.2.1 - 2019-07-18 =
82
  * Improvement: Menu option and settings page title now include "Lite"
83
  * 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.
67
 
68
  == Upgrade Notice ==
69
 
70
+ = 2.3 =
71
+ 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.
72
+
73
  = 2.0 =
74
  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+.
75
 
81
 
82
  == Changelog ==
83
 
84
+ = WP Offload Media Lite 2.3 - 2019-11-12 =
85
+ * [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)
86
+ * New: Upgrade routine to migrate offload data to custom table
87
+ * New: Support for changed Media Library upload process introduced with WordPress 5.3
88
+ * New: Support for new "-scaled" and "-rotated" images introduced with WordPress 5.3
89
+ * New: Support for customizer changes introduced with WordPress 5.3
90
+ * New: Offload new "original_image" file introduced with WordPress 5.3
91
+ * Improvement: Performance boost during both page display and save
92
+ * Improvement: Better detection of offloaded media URLs during page display
93
+ * Bug fix: New Media Library upload given same local file name as offloaded and removed file after Remove Files From Server turned off
94
+ * Bug fix: PHP message: PHP Deprecated: strpos(): Non-string needles will be interpreted as strings in the future
95
+
96
  = WP Offload Media Lite 2.2.1 - 2019-07-18 =
97
  * Improvement: Menu option and settings page title now include "Lite"
98
  * Improvement: Remove Files From Server option now warns about media backups when switched on
wordpress-s3.php CHANGED
@@ -4,7 +4,7 @@ Plugin Name: WP Offload Media Lite
4
  Plugin URI: http://wordpress.org/extend/plugins/amazon-s3-and-cloudfront/
5
  Description: Automatically copies media uploads to Amazon S3, DigitalOcean Spaces or Google Cloud Storage for storage and delivery. Optionally configure Amazon CloudFront or another CDN for even faster delivery.
6
  Author: Delicious Brains
7
- Version: 2.2.1
8
  Author URI: https://deliciousbrains.com/
9
  Network: True
10
  Text Domain: amazon-s3-and-cloudfront
@@ -26,7 +26,7 @@ Domain Path: /languages/
26
  // Then completely rewritten.
27
  */
28
 
29
- $GLOBALS['aws_meta']['amazon-s3-and-cloudfront']['version'] = '2.2.1';
30
 
31
  require_once dirname( __FILE__ ) . '/classes/as3cf-compatibility-check.php';
32
 
4
  Plugin URI: http://wordpress.org/extend/plugins/amazon-s3-and-cloudfront/
5
  Description: Automatically copies media uploads to Amazon S3, DigitalOcean Spaces or Google Cloud Storage for storage and delivery. Optionally configure Amazon CloudFront or another CDN for even faster delivery.
6
  Author: Delicious Brains
7
+ Version: 2.3
8
  Author URI: https://deliciousbrains.com/
9
  Network: True
10
  Text Domain: amazon-s3-and-cloudfront
26
  // Then completely rewritten.
27
  */
28
 
29
+ $GLOBALS['aws_meta']['amazon-s3-and-cloudfront']['version'] = '2.3';
30
 
31
  require_once dirname( __FILE__ ) . '/classes/as3cf-compatibility-check.php';
32