WP Offload S3 Lite - Version 2.6.0

Version Description

= 2.6 = This is a major upgrade that updates the format of information stored about offloaded Media Library items. Once upgraded you will not be able to downgrade without restoring data from a backup. This version requires PHP 5.6+

= 2.3 = 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.

= 2.0 = 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+.

= 1.1 = This is a major change, which ensures S3 URLs are no longer saved in post content. Instead, local URLs are filtered on page generation and replaced with the S3 version. If you depend on the S3 URLs being stored in post content you will need to make modifications to support this version.

= 0.6 = This version requires PHP 5.3.3+ and the Amazon Web Services plugin

Download this release

Release Info

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

Code changes from version 2.5.5 to 2.6.0

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.8
6
- **Requires PHP:** 5.5
7
- **Stable tag:** 2.5.5
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.
@@ -13,7 +13,7 @@ Copies files to Amazon S3, DigitalOcean Spaces or Google Cloud Storage as they a
13
 
14
  FORMERLY WP OFFLOAD S3 LITE
15
 
16
- https://www.youtube.com/watch?v=_PVybEGaRXc
17
 
18
  This plugin automatically copies images, videos, documents, and any other media added through WordPress' media uploader to [Amazon S3](http://aws.amazon.com/s3/), [DigitalOcean Spaces](https://www.digitalocean.com/products/spaces/) or [Google Cloud Storage](https://cloud.google.com/storage/). It then automatically replaces the URL to each media file with their respective Amazon S3, DigitalOcean Spaces or Google Cloud Storage URL or, if you have configured [Amazon CloudFront](http://aws.amazon.com/cloudfront/) or another CDN with or without a custom domain, that URL instead. Image thumbnails are also copied to the bucket and delivered through the correct remote URL.
19
 
@@ -75,6 +75,10 @@ If you upgrade to the pro version of [WP Offload Media](https://deliciousbrains.
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
 
@@ -89,6 +93,16 @@ This version requires PHP 5.3.3+ and the Amazon Web Services plugin
89
 
90
  ## Changelog ##
91
 
 
 
 
 
 
 
 
 
 
 
92
  ### WP Offload Media Lite 2.5.5 - 2021-07-19 ###
93
  * Bug fix: Signed GCS URLs broken when updating a post
94
  * Bug fix: Incorrect mime type set on scaled image's bucket object when thumbnail format differs from original file's format
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.9
6
+ **Requires PHP:** 5.6
7
+ **Stable tag:** 2.6.0
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.
13
 
14
  FORMERLY WP OFFLOAD S3 LITE
15
 
16
+ https://www.youtube.com/watch?v=I-wTMXMeFu4
17
 
18
  This plugin automatically copies images, videos, documents, and any other media added through WordPress' media uploader to [Amazon S3](http://aws.amazon.com/s3/), [DigitalOcean Spaces](https://www.digitalocean.com/products/spaces/) or [Google Cloud Storage](https://cloud.google.com/storage/). It then automatically replaces the URL to each media file with their respective Amazon S3, DigitalOcean Spaces or Google Cloud Storage URL or, if you have configured [Amazon CloudFront](http://aws.amazon.com/cloudfront/) or another CDN with or without a custom domain, that URL instead. Image thumbnails are also copied to the bucket and delivered through the correct remote URL.
19
 
75
 
76
  ## Upgrade Notice ##
77
 
78
+ ### 2.6 ###
79
+ This is a major upgrade that updates the format of information stored about offloaded Media Library items. Once upgraded you will not be able to downgrade without restoring data from a backup.
80
+ This version requires PHP 5.6+
81
+
82
  ### 2.3 ###
83
  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.
84
 
93
 
94
  ## Changelog ##
95
 
96
+ ### WP Offload Media Lite 2.6 - 2022-03-09 ###
97
+ * [Release Summary Blog Post](https://deliciousbrains.com/wp-offload-media-2-6-released/?utm_campaign=changelogs&utm_source=wordpress.org&utm_medium=free%2Bplugin%2Blisting)
98
+ * New: WP Offload Media is now compatible with WordPress 5.9 and Full Site Editing
99
+ * Improvement: Offloaded thumbnail sizes are now tracked for better handling of changes to registered sizes
100
+ * Improvement: Offloads and other storage provider actions are faster
101
+ * Bug fix: URL rewriting now works in the Full Site Editor
102
+ * Bug fix: Offloaded images are now shown when re-editing a Block Template or Template Part
103
+ * Bug fix: URL rewriting now works for Widgets migrated to a Widget Sidebar Block
104
+ * Bug fix: Objects are no longer left in the bucket when deleting a Media Library item with many changes to its thumbnail sizes
105
+
106
  ### WP Offload Media Lite 2.5.5 - 2021-07-19 ###
107
  * Bug fix: Signed GCS URLs broken when updating a post
108
  * Bug fix: Incorrect mime type set on scaled image's bucket object when thumbnail format differs from original file's format
classes/amazon-s3-and-cloudfront.php CHANGED
@@ -1,7 +1,15 @@
1
  <?php
2
 
3
- use DeliciousBrains\WP_Offload_Media\Items\Media_Library_Item;
 
 
 
4
  use DeliciousBrains\WP_Offload_Media\Items\Item;
 
 
 
 
 
5
  use DeliciousBrains\WP_Offload_Media\Providers\Delivery\Another_CDN;
6
  use DeliciousBrains\WP_Offload_Media\Providers\Delivery\AWS_CloudFront;
7
  use DeliciousBrains\WP_Offload_Media\Providers\Delivery\Cloudflare;
@@ -23,9 +31,11 @@ use DeliciousBrains\WP_Offload_Media\Upgrades\Upgrade_Content_Replace_URLs;
23
  use DeliciousBrains\WP_Offload_Media\Upgrades\Upgrade_EDD_Replace_URLs;
24
  use DeliciousBrains\WP_Offload_Media\Upgrades\Upgrade_File_Sizes;
25
  use DeliciousBrains\WP_Offload_Media\Upgrades\Upgrade_Filter_Post_Excerpt;
 
26
  use DeliciousBrains\WP_Offload_Media\Upgrades\Upgrade_Items_Table;
27
  use DeliciousBrains\WP_Offload_Media\Upgrades\Upgrade_Meta_WP_Error;
28
  use DeliciousBrains\WP_Offload_Media\Upgrades\Upgrade_Region_Meta;
 
29
  use DeliciousBrains\WP_Offload_Media\Upgrades\Upgrade_WPOS3_To_AS3CF;
30
 
31
  class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
@@ -120,6 +130,11 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
120
  */
121
  protected static $delivery_provider_classes = array();
122
 
 
 
 
 
 
123
  /**
124
  * @var AS3CF_Plugin_Compatibility
125
  */
@@ -137,7 +152,21 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
137
  'WPOS3_SETTINGS',
138
  );
139
 
140
- const LATEST_UPGRADE_ROUTINE = 8;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
 
142
  /**
143
  * @param string $plugin_file_path
@@ -145,7 +174,7 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
145
  *
146
  * @throws Exception
147
  */
148
- function __construct( $plugin_file_path, $slug = null ) {
149
  $this->plugin_slug = ( is_null( $slug ) ) ? 'amazon-s3-and-cloudfront' : $slug;
150
 
151
  parent::__construct( $plugin_file_path );
@@ -162,7 +191,7 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
162
  *
163
  * @throws Exception
164
  */
165
- function init( $plugin_file_path ) {
166
  $this->plugin_title = __( 'Offload Media Lite', 'amazon-s3-and-cloudfront' );
167
  $this->plugin_menu_title = __( 'Offload Media Lite', 'amazon-s3-and-cloudfront' );
168
 
@@ -186,8 +215,6 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
186
  Storage::get_provider_key_name() => 'DeliciousBrains\WP_Offload_Media\Providers\Delivery\Storage',
187
  ) );
188
 
189
- Media_Library_Item::init_cache();
190
-
191
  $this->set_storage_provider();
192
  $this->set_delivery_provider();
193
 
@@ -202,6 +229,8 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
202
  new Upgrade_Filter_Post_Excerpt( $this );
203
  new Upgrade_WPOS3_To_AS3CF( $this );
204
  new Upgrade_Items_Table( $this );
 
 
205
 
206
  // Plugin setup
207
  add_action( 'admin_menu', array( $this, 'admin_menu' ) );
@@ -210,38 +239,18 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
210
  add_filter( 'plugin_action_links', array( $this, 'plugin_actions_settings_link' ), 10, 2 );
211
  add_filter( 'network_admin_plugin_action_links', array( $this, 'plugin_actions_settings_link' ), 10, 2 );
212
  add_filter( 'pre_get_space_used', array( $this, 'multisite_get_space_used' ) );
 
213
  // display a notice when either lite or pro is automatically deactivated
214
  add_action( 'pre_current_active_plugins', array( $this, 'plugin_deactivated_notice' ) );
215
  add_action( 'as3cf_plugin_load', array( $this, 'remove_access_keys_if_constants_set' ) );
216
 
217
- // Attachment screens/modals
218
- add_action( 'load-upload.php', array( $this, 'load_media_assets' ), 11 );
219
- add_action( 'admin_enqueue_scripts', array( $this, 'load_attachment_assets' ), 11 );
220
- add_action( 'add_meta_boxes', array( $this, 'attachment_provider_meta_box' ) );
221
-
222
  // UI AJAX
223
  add_action( 'wp_ajax_as3cf-get-buckets', array( $this, 'ajax_get_buckets' ) );
224
  add_action( 'wp_ajax_as3cf-get-url-preview', array( $this, 'ajax_get_url_preview' ) );
225
- add_action( 'wp_ajax_as3cf_get_attachment_provider_details', array( $this, 'ajax_get_attachment_provider_details' ) );
226
  add_action( 'wp_ajax_as3cf-get-diagnostic-info', array( $this, 'ajax_get_diagnostic_info' ) );
227
 
228
- // Rewriting URLs, doesn't depend on plugin being setup
229
- add_filter( 'wp_get_attachment_url', array( $this, 'wp_get_attachment_url' ), 99, 2 );
230
- add_filter( 'wp_get_attachment_image_attributes', array( $this, 'wp_get_attachment_image_attributes' ), 99, 3 );
231
- add_filter( 'get_image_tag', array( $this, 'maybe_encode_get_image_tag' ), 99, 6 );
232
- add_filter( 'wp_get_attachment_image_src', array( $this, 'maybe_encode_wp_get_attachment_image_src' ), 99, 4 );
233
- add_filter( 'wp_prepare_attachment_for_js', array( $this, 'maybe_encode_wp_prepare_attachment_for_js', ), 99, 3 );
234
- add_filter( 'image_get_intermediate_size', array( $this, 'maybe_encode_image_get_intermediate_size' ), 99, 3 );
235
- add_filter( 'get_attached_file', array( $this, 'get_attached_file' ), 10, 2 );
236
- add_filter( 'wp_get_original_image_path', array( $this, 'get_attached_file' ), 10, 2 );
237
- add_filter( 'wp_audio_shortcode', array( $this, 'wp_media_shortcode' ), 100, 5 );
238
- add_filter( 'wp_video_shortcode', array( $this, 'wp_media_shortcode' ), 100, 5 );
239
-
240
- // Communication with provider, plugin needs to be setup
241
- add_filter( 'wp_unique_filename', array( $this, 'wp_unique_filename' ), 10, 3 );
242
- add_filter( 'wp_update_attachment_metadata', array( $this, 'wp_update_attachment_metadata' ), 110, 2 );
243
- add_filter( 'delete_attachment', array( $this, 'delete_attachment' ), 20 );
244
- add_filter( 'update_attached_file', array( $this, 'update_attached_file' ), 100, 2 );
245
 
246
  // Listen for settings changes
247
  if ( false !== static::settings_constant() ) {
@@ -267,6 +276,31 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
267
  $this->register_delivery_provider_assets();
268
  }
269
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
270
  /**
271
  * @return Storage_Provider
272
  */
@@ -419,6 +453,15 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
419
  return empty( $class ) ? __( 'Unknown', 'amazon-s3-and-cloudfront' ) : $class::get_provider_service_name();
420
  }
421
 
 
 
 
 
 
 
 
 
 
422
  /**
423
  * Get the plugin title to be used in page headings
424
  *
@@ -1047,7 +1090,12 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
1047
  *
1048
  * @return array
1049
  */
1050
- function get_allowed_mime_types() {
 
 
 
 
 
1051
  return apply_filters( 'as3cf_allowed_mime_types', get_allowed_mime_types() );
1052
  }
1053
 
@@ -1125,18 +1173,18 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
1125
  *
1126
  * @return string
1127
  */
1128
- function get_url_preview( $escape = true, $suffix = 'photo.jpg' ) {
1129
  $as3cf_item = new Media_Library_Item(
1130
  $this->get_storage_provider()->get_provider_key_name(),
1131
  $this->get_setting( 'region' ),
1132
  $this->get_setting( 'bucket' ),
1133
- AS3CF_Utils::trailingslash_prefix( $this->get_file_prefix() ) . $suffix,
1134
  false,
1135
  null,
1136
- null
1137
  );
1138
 
1139
- $url = $this->get_attachment_provider_url( null, $as3cf_item );
1140
 
1141
  if ( is_wp_error( $url ) ) {
1142
  return '';
@@ -1200,1538 +1248,213 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
1200
  }
1201
 
1202
  /**
1203
- * Delete bulk objects from an provider bucket
1204
- *
1205
- * @param string $region
1206
- * @param string $bucket
1207
- * @param array $objects
1208
- * @param bool $log_error
1209
- * @param bool $return_on_error
1210
- * @param bool $force_new_provider_client if we are deleting in bulk, force new provider client
1211
- * to cope with possible different regions
1212
  *
1213
- * @return bool
1214
  */
1215
- function delete_objects( $region, $bucket, $objects, $log_error = false, $return_on_error = false, $force_new_provider_client = false ) {
1216
- $chunks = array_chunk( $objects, 1000 );
 
 
 
 
1217
 
1218
- try {
1219
- foreach ( $chunks as $chunk ) {
1220
- $this->get_provider_client( $region, $force_new_provider_client )->delete_objects( array(
1221
- 'Bucket' => $bucket,
1222
- 'Objects' => $chunk,
1223
- ) );
1224
- }
1225
- } catch ( Exception $e ) {
1226
- if ( $log_error ) {
1227
- AS3CF_Error::log( 'Error removing files from bucket: ' . $e->getMessage() );
1228
- }
1229
 
1230
- return false;
1231
- }
1232
 
1233
- return true;
1234
  }
1235
 
1236
  /**
1237
- * Removes an attachment's files from provider.
1238
  *
1239
- * @param int $post_id
1240
- * @param Media_Library_Item $as3cf_item
1241
- * @param bool $include_backups remove previous edited image versions
1242
- * @param bool $log_error
1243
- * @param bool $return_on_error
1244
- * @param bool $force_new_provider_client if we are deleting in bulk, force new provider client
1245
- * to cope with possible different regions
1246
  */
1247
- 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 ) {
1248
- $prefix = $as3cf_item->normalized_path_dir();
1249
- $private_prefix = $as3cf_item->private_prefix();
1250
- $paths = AS3CF_Utils::get_attachment_file_paths( $post_id, false, false, $include_backups );
1251
- $paths = apply_filters( 'as3cf_remove_attachment_paths', $paths, $post_id, $as3cf_item, $include_backups );
1252
-
1253
- // If another item in current site shares full size *local* paths, only remove remote files not referenced by duplicates.
1254
- // We reference local paths as they should be reflected one way or another remotely, including backups.
1255
- $fullsize_paths = AS3CF_Utils::fullsize_paths( $paths );
1256
- $as3cf_items_with_paths = Media_Library_Item::get_by_source_path( $fullsize_paths, array( $post_id ), false );
1257
-
1258
- $duplicate_paths = array();
1259
-
1260
- foreach ( $as3cf_items_with_paths as $as3cf_item_with_path ) {
1261
- /* @var Media_Library_Item $as3cf_item_with_path */
1262
- $duplicate_paths += array_values( AS3CF_Utils::get_attachment_file_paths( $as3cf_item_with_path->source_id(), false, false, $include_backups ) );
1263
- }
1264
-
1265
- if ( ! empty( $duplicate_paths ) ) {
1266
- $paths = array_diff( $paths, $duplicate_paths );
1267
- }
1268
-
1269
- // Nothing to do, shortcut out.
1270
- if ( empty( $paths ) ) {
1271
- return;
1272
  }
1273
 
1274
- $objects_to_remove = array();
1275
- $paths_to_remove = array_unique( $paths );
1276
-
1277
- foreach ( $paths_to_remove as $size => $path ) {
1278
- $objects_to_remove[] = array(
1279
- 'Key' => $as3cf_item->key( wp_basename( $path ) ),
1280
- );
1281
  }
1282
 
1283
- // finally delete the objects from provider
1284
- $this->delete_objects( $as3cf_item->region(), $as3cf_item->bucket(), $objects_to_remove, $log_error, $return_on_error, $force_new_provider_client );
1285
  }
1286
 
1287
  /**
1288
- * Removes an attachment and intermediate image size files from provider
 
 
 
1289
  *
1290
- * @param int $post_id
1291
- * @param bool $force_new_provider_client if we are deleting in bulk, force new provider client
1292
- * to cope with possible different regions
1293
  */
1294
- function delete_attachment( $post_id, $force_new_provider_client = false ) {
1295
- if ( ! $this->is_plugin_setup( true ) ) {
1296
- return;
1297
- }
1298
 
1299
- $as3cf_item = Media_Library_Item::get_by_source_id( $post_id );
 
1300
 
1301
- if ( ! $as3cf_item ) {
1302
- return;
1303
  }
 
1304
 
1305
- if ( ! $this->is_attachment_served_by_provider( $post_id, true ) ) {
1306
- return;
 
 
 
 
 
 
 
 
1307
  }
1308
 
1309
- $this->remove_attachment_files_from_provider( $post_id, $as3cf_item, true, true, true, $force_new_provider_client );
 
 
 
1310
 
1311
- $as3cf_item->delete();
1312
  }
1313
 
1314
  /**
1315
- * Handles the upload of the attachment to provider when an attachment is updated using
1316
- * the 'wp_update_attachment_metadata' filter
1317
  *
1318
- * @param array $data meta data for attachment
1319
- * @param int $post_id
1320
  *
1321
- * @return array
1322
  * @throws Exception
1323
  */
1324
- function wp_update_attachment_metadata( $data, $post_id ) {
1325
- if ( ! $this->is_plugin_setup( true ) ) {
1326
- return $data;
1327
- }
1328
-
1329
- // Protect against updates of partially formed metadata since WordPress 5.3.
1330
- // Checks whether new upload currently has no subsizes recorded but is expected to have subsizes during upload,
1331
- // and if so, are any of its currently missing sizes part of the set.
1332
- if ( ! empty( $data ) && function_exists( 'wp_get_registered_image_subsizes' ) && function_exists( 'wp_get_missing_image_subsizes' ) ) {
1333
-
1334
- // Plugin compat may require that we wait for wp_generate_attachment_metadata
1335
- // to be run before proceeding. I.e Regenrerate Thumbnails requires this
1336
- if ( apply_filters( 'as3cf_wait_for_generate_attachment_metadata', false ) ) {
1337
- return $data;
1338
- }
1339
-
1340
- if ( empty( $data['sizes'] ) && wp_attachment_is_image( $post_id ) ) {
1341
-
1342
- // There is no unified way of checking whether subsizes are expected, so we have to duplicate WordPress code here.
1343
- $new_sizes = wp_get_registered_image_subsizes();
1344
- $new_sizes = apply_filters( 'intermediate_image_sizes_advanced', $new_sizes, $data, $post_id );
1345
- $missing_sizes = wp_get_missing_image_subsizes( $post_id );
1346
-
1347
- if ( ! empty( $new_sizes ) && ! empty( $missing_sizes ) && array_intersect_key( $missing_sizes, $new_sizes ) ) {
1348
- return $data;
1349
- }
1350
- }
1351
- }
1352
-
1353
- $as3cf_item = Media_Library_Item::get_by_source_id( $post_id );
1354
-
1355
- if ( ! $as3cf_item && ! $this->get_setting( 'copy-to-s3' ) ) {
1356
- // abort if not already uploaded to provider and the copy setting is off
1357
- return $data;
1358
- }
1359
-
1360
- if ( empty( $as3cf_item ) ) {
1361
- $as3cf_item = null;
1362
- }
1363
 
1364
- // allow provider upload to be cancelled for any reason
1365
- $pre = apply_filters( 'as3cf_pre_update_attachment_metadata', false, $data, $post_id, $as3cf_item );
1366
- if ( false !== $pre ) {
1367
- return $data;
1368
  }
1369
 
1370
- // upload attachment to provider
1371
- $attachment_metadata = $this->upload_attachment( $post_id, $data );
1372
-
1373
- if ( is_wp_error( $attachment_metadata ) || empty( $attachment_metadata ) || ! is_array( $attachment_metadata ) ) {
1374
- return $data;
1375
- }
1376
 
1377
- return $attachment_metadata;
1378
  }
1379
 
1380
  /**
1381
- * Upload attachment to provider
1382
  *
1383
- * @param int $post_id
1384
- * @param array|null $data
1385
- * @param string|null $file_path
1386
- * @param bool $force_new_provider_client if we are uploading in bulk, force new provider client
1387
- * to cope with possible different regions
1388
- * @param bool $remove_local_files
1389
  *
1390
- * @return array|Media_Library_Item|WP_Error
1391
- * @throws Exception
1392
  */
1393
- public function upload_attachment( $post_id, $data = null, $file_path = null, $force_new_provider_client = false, $remove_local_files = true ) {
1394
- static $offloaded_path_filesizes = array();
1395
- static $offloaded_size_paths = array();
1396
-
1397
- $return_metadata = null;
1398
- if ( is_null( $data ) ) {
1399
- $data = wp_get_attachment_metadata( $post_id, true );
1400
- } else {
1401
- // As we have passed in the meta, return it later
1402
- $return_metadata = $data;
1403
- }
1404
-
1405
- if ( is_wp_error( $data ) ) {
1406
- return $data;
1407
- }
1408
-
1409
- // Allow provider upload to be hijacked / cancelled for any reason
1410
- try {
1411
- $pre = apply_filters( 'as3cf_pre_upload_attachment', false, $post_id, $data );
1412
- } catch ( Exception $e ) {
1413
- return $this->return_upload_error( $e->getMessage() );
1414
- }
1415
 
1416
- if ( false !== $pre ) {
1417
- return $data;
 
1418
  }
1419
 
1420
- // If $file_path was passed in with a non-null value, ensure it's a string
1421
- if ( ! is_null( $file_path ) && ! is_string( $file_path ) ) {
1422
- $error_msg = sprintf( __( 'Media Library item ID %d. Provided path is not a string', 'amazon-s3-and-cloudfront' ), $post_id );
1423
 
1424
- return $this->return_upload_error( $error_msg );
 
 
 
 
 
 
 
 
 
 
 
 
1425
  }
1426
 
1427
- // Ensure WordPress own post meta for relative URL is a string
1428
- $attached_file_meta = get_post_meta( $post_id, '_wp_attached_file', true );
1429
- if ( ! is_string( $attached_file_meta ) ) {
1430
- $error_msg = sprintf( __( 'Media Library item with ID %d has damaged meta data', 'amazon-s3-and-cloudfront' ), $post_id );
1431
-
1432
- return $this->return_upload_error( $error_msg );
1433
  }
1434
 
1435
- if ( is_null( $file_path ) ) {
1436
- $file_path = get_attached_file( $post_id, true );
 
1437
  }
1438
 
1439
- // Check for valid "full" file path before attempting upload
1440
- if ( empty( $file_path ) ) {
1441
- $error_msg = sprintf( __( 'Media Library item with ID %d does not have a valid file path', 'amazon-s3-and-cloudfront' ), $post_id );
1442
 
1443
- return $this->return_upload_error( $error_msg, $return_metadata );
 
 
 
 
 
 
 
 
 
 
 
1444
  }
1445
 
1446
- $offload_full = true;
1447
- $old_item = Media_Library_Item::get_by_source_id( $post_id );
1448
-
1449
- // If item not already offloaded, is it a duplicate?
1450
- $duplicate = false;
1451
- if ( empty( $old_item ) ) {
1452
- $old_items = Media_Library_Item::get_by_source_path( $file_path, $post_id, true, true );
1453
-
1454
- if ( ! empty( $old_items[0] ) ) {
1455
- $duplicate = true;
1456
-
1457
- /** @var Media_Library_Item $duplicate_item */
1458
- $duplicate_item = $old_items[0];
1459
-
1460
- $old_item = new Media_Library_Item(
1461
- $duplicate_item->provider(),
1462
- $duplicate_item->region(),
1463
- $duplicate_item->bucket(),
1464
- $duplicate_item->path(),
1465
- $duplicate_item->is_private(),
1466
- $post_id,
1467
- $duplicate_item->source_path(),
1468
- wp_basename( $duplicate_item->original_source_path() ),
1469
- $duplicate_item->extra_info()
1470
- );
1471
-
1472
- $old_item->save();
1473
-
1474
- // If original offloaded in same process, skip offloading anything it's already processed.
1475
- // Otherwise, do not need to offload full file if duplicate and file missing.
1476
- if ( ! empty( $offloaded_path_filesizes[ $duplicate_item->id() ] ) ) {
1477
- $offloaded_path_filesizes[ $old_item->id() ] = $offloaded_path_filesizes[ $duplicate_item->id() ];
1478
- $offloaded_size_paths[ $old_item->id() ] = $offloaded_size_paths[ $duplicate_item->id() ];
1479
- } elseif ( ! file_exists( $file_path ) ) {
1480
- $offload_full = false;
1481
- }
1482
 
1483
- unset( $old_items, $duplicate_item );
1484
- }
 
 
 
 
 
 
 
 
1485
  }
1486
 
1487
- // If not already offloaded in request, check full file exists locally before attempting offload.
1488
- if ( $offload_full ) {
1489
- if ( $old_item && ! empty( $offloaded_path_filesizes[ $old_item->id() ][ $file_path ] ) ) {
1490
- $offload_full = false;
1491
- } elseif ( ! file_exists( $file_path ) ) {
1492
- $error_msg = sprintf( __( 'File %s does not exist', 'amazon-s3-and-cloudfront' ), $file_path );
1493
-
1494
- return $this->return_upload_error( $error_msg, $return_metadata );
1495
- }
1496
  }
1497
 
1498
- $file_paths = AS3CF_Utils::get_attachment_file_paths( $post_id, false, $data );
1499
- $file_paths = array_diff( $file_paths, array( $file_path ) );
1500
-
1501
- // Are there any files not already offloaded if full already offloaded in this request?
1502
- if ( false === $offload_full ) {
1503
- if ( empty( $file_paths ) ) {
1504
- // Item does not have any additional files, we're done.
1505
- return $return_metadata;
1506
- }
1507
-
1508
- if ( ! empty( $offloaded_size_paths[ $old_item->id() ] ) && empty( array_diff_key( $file_paths, $offloaded_size_paths[ $old_item->id() ] ) ) ) {
1509
- // Item's additional files all offloaded, we're done.
1510
- return $return_metadata;
1511
- }
1512
  }
1513
 
1514
- // Get original file's stats.
1515
- $file_name = wp_basename( $file_path );
1516
- $type = get_post_mime_type( $post_id );
1517
- $allowed_types = $this->get_allowed_mime_types();
1518
-
1519
- // Check mime type of file is in allowed provider mime types.
1520
- // Note: This check is based on the item's original upload format.
1521
- if ( ! in_array( $type, $allowed_types ) ) {
1522
- $error_msg = sprintf( __( 'Mime type %s is not allowed', 'amazon-s3-and-cloudfront' ), $type );
1523
 
1524
- return $this->return_upload_error( $error_msg, $return_metadata );
 
 
 
 
 
 
 
 
 
1525
  }
1526
 
1527
- $acl = $this->get_storage_provider()->get_default_acl();
1528
-
1529
- // check the attachment already exists in provider, eg. edit or restore image
1530
- if ( $old_item ) {
1531
- // Must be offloaded to same provider as currently configured.
1532
- if ( ! $this->is_attachment_served_by_provider( $post_id, true ) ) {
1533
- return $this->return_upload_error( __( 'Already offloaded to a different provider', 'amazon-s3-and-cloudfront' ), $return_metadata );
1534
- }
1535
-
1536
- // Use private ACL if existing offload is already private.
1537
- if ( $old_item->is_private() ) {
1538
- $acl = $this->get_storage_provider()->get_private_acl();
1539
- }
1540
-
1541
- // use existing prefix
1542
- $prefix = $old_item->normalized_path_dir();
1543
- // use existing private prefix
1544
- $private_prefix = $old_item->private_prefix();
1545
- // use existing bucket
1546
- $bucket = $old_item->bucket();
1547
- // get existing region
1548
- $region = $old_item->region();
1549
- // Get existing original filename.
1550
- $original_filename = wp_basename( $old_item->original_source_path() );
1551
- } else {
1552
- // derive prefix from various settings
1553
- $prefix = $this->get_new_attachment_prefix( $post_id, $data );
1554
-
1555
- // maybe set a private prefix.
1556
- if ( $this->private_prefix_enabled() ) {
1557
- $private_prefix = AS3CF_Utils::trailingslash_prefix( $this->get_setting( 'signed-urls-object-prefix', '' ) );
1558
- } else {
1559
- $private_prefix = '';
1560
- }
1561
-
1562
- // use bucket from settings
1563
- $bucket = $this->get_setting( 'bucket' );
1564
- $region = $this->get_setting( 'region' );
1565
- if ( is_wp_error( $region ) ) {
1566
- $region = '';
1567
- }
1568
-
1569
- // There may be an original image that can override the default original filename.
1570
- $original_filename = empty( $data['original_image'] ) ? null : $data['original_image'];
1571
- }
1572
-
1573
- $acl = apply_filters( 'wps3_upload_acl', $acl, $type, $data, $post_id, $this ); // Old naming convention, will be deprecated soon
1574
- $acl = apply_filters( 'as3cf_upload_acl', $acl, $data, $post_id );
1575
- $is_private = ! empty( $acl ) && $this->get_storage_provider()->get_private_acl() === $acl;
1576
-
1577
- $args = array(
1578
- 'Bucket' => $bucket,
1579
- 'Key' => $prefix . $file_name,
1580
- 'SourceFile' => $file_path,
1581
- 'ContentType' => $this->get_mime_type( $file_path ),
1582
- 'CacheControl' => 'max-age=31536000',
1583
- 'Expires' => date( 'D, d M Y H:i:s O', time() + 31536000 ),
1584
- );
1585
-
1586
- $image_size = wp_attachment_is_image( $post_id ) ? 'full' : '';
1587
-
1588
- // Only set ACL if actually required, some storage provider and bucket settings disable changing ACL.
1589
- if ( ! empty( $acl ) && $this->use_acl_for_intermediate_size( $post_id, $image_size, $bucket ) ) {
1590
- $args['ACL'] = $acl;
1591
- }
1592
-
1593
- // TODO: Remove GZIP functionality.
1594
- // Handle gzip on supported items
1595
- if ( $this->should_gzip_file( $file_path, $type ) && false !== ( $gzip_body = gzencode( file_get_contents( $file_path ) ) ) ) {
1596
- unset( $args['SourceFile'] );
1597
-
1598
- $args['Body'] = $gzip_body;
1599
- $args['ContentEncoding'] = 'gzip';
1600
- }
1601
-
1602
- $args = apply_filters( 'as3cf_object_meta', $args, $post_id, $image_size, false );
1603
-
1604
- $provider = $this->get_storage_provider()->get_provider_key_name();
1605
- $region = $bucket !== $args['Bucket'] ? $this->get_bucket_region( $args['Bucket'], true ) : $region;
1606
- $is_private = ( ! empty( $args['ACL'] ) && $this->get_storage_provider()->get_private_acl() === $args['ACL'] ) ? true : $is_private;
1607
- $extra_info = empty( $old_item ) ? array( 'private_prefix' => $private_prefix ) : $old_item->extra_info();
1608
- $item_id = empty( $old_item ) ? null : $old_item->id();
1609
-
1610
- // Protect against filter use and only set ACL if actually required, some storage provider and bucket settings disable changing ACL.
1611
- if ( isset( $args['ACL'] ) && ! $this->use_acl_for_intermediate_size( $post_id, $image_size, $bucket ) ) {
1612
- unset( $args['ACL'] );
1613
- }
1614
-
1615
- $as3cf_item = new Media_Library_Item( $provider, $region, $args['Bucket'], $args['Key'], $is_private, $post_id, $file_path, $original_filename, $extra_info, $item_id );
1616
-
1617
- // With public path and private prefix now in place, we can set the final path for the full sized file.
1618
- $size_private_prefix = $as3cf_item->is_private() ? $as3cf_item->private_prefix() : '';
1619
- $args['Key'] = $size_private_prefix . $args['Key'];
1620
-
1621
- do_action( 'as3cf_upload_attachment_pre_remove', $post_id, $as3cf_item, $as3cf_item->normalized_path_dir(), $args );
1622
-
1623
- $new_offload_path_filesizes = array();
1624
- $new_offload_size_paths = array();
1625
- $files_to_remove = array();
1626
-
1627
- $provider_client = $this->get_provider_client( $as3cf_item->region(), $force_new_provider_client );
1628
-
1629
- if ( $offload_full ) {
1630
- try {
1631
- // May raise exception, so don't offload anything else if there's an error.
1632
- $filesize = (int) filesize( $file_path );
1633
-
1634
- // May raise exception, so don't offload anything else if there's an error.
1635
- $provider_client->upload_object( $args );
1636
-
1637
- $new_offload_path_filesizes[ $file_path ] = $filesize; // Note: pre `as3cf_object_meta` filter value.
1638
- $new_offload_size_paths['original'] = $file_path;
1639
- $files_to_remove[] = $file_path; // Note: pre `as3cf_object_meta` filter value.
1640
- } catch ( Exception $e ) {
1641
- $error_msg = sprintf( __( 'Error offloading %s to provider: %s', 'amazon-s3-and-cloudfront' ), $file_path, $e->getMessage() );
1642
-
1643
- return $this->return_upload_error( $error_msg, $return_metadata );
1644
- }
1645
- }
1646
-
1647
- $additional_images = array();
1648
- $private_sizes = array(); // Reset private sizes to be as expected at time of (re)upload.
1649
-
1650
- foreach ( $file_paths as $size => $file_path ) {
1651
- if ( ! in_array( $file_path, $files_to_remove ) ) {
1652
- $acl = apply_filters( 'as3cf_upload_acl_sizes', $this->get_storage_provider()->get_default_acl(), $size, $post_id, $data );
1653
-
1654
- if ( ! empty( $acl ) && $this->get_storage_provider()->get_private_acl() === $acl ) {
1655
- $private_sizes[] = $size;
1656
- }
1657
-
1658
- // Public path, modified to private in next block as needed.
1659
- $additional_images[ $size ] = array(
1660
- 'Key' => $as3cf_item->normalized_path_dir() . wp_basename( $file_path ),
1661
- 'SourceFile' => $file_path,
1662
- 'ContentType' => $this->get_mime_type( $file_path ),
1663
- );
1664
-
1665
- // Only set ACL if actually required, some storage provider and bucket settings disable changing ACL.
1666
- if ( ! empty( $acl ) && $this->use_acl_for_intermediate_size( $post_id, $size, $bucket, $as3cf_item ) ) {
1667
- $additional_images[ $size ]['ACL'] = $acl;
1668
- }
1669
- } else {
1670
- // If the previous offload of file path was private, this size also needs to be private.
1671
- // This is a case of first (in process) offload wins, duplicate file paths should have same access.
1672
- if ( ! empty( $private_sizes ) ) {
1673
- $duplicate_private = array_intersect( $private_sizes, array_keys( array_intersect( $file_paths, array( $file_path ) ) ) );
1674
-
1675
- if ( ! empty( $duplicate_private ) ) {
1676
- $private_sizes[] = $size;
1677
- }
1678
- }
1679
- }
1680
- }
1681
-
1682
- $upload_errors = array();
1683
-
1684
- foreach ( $additional_images as $size => $image ) {
1685
- // If this file has already been offloaded during this request, skip actual offload.
1686
- if ( $old_item && ! empty( $offloaded_path_filesizes[ $old_item->id() ][ $image['SourceFile'] ] ) ) {
1687
- // We still have to record whether this size is private based on previous offload of the source file.
1688
- // We also need to ensure this file is marked as possibly needing removal from server, and size has been processed.
1689
- if ( ! empty( $private_sizes ) ) {
1690
- $duplicate_private = array_intersect( $private_sizes, array_keys( array_intersect( $file_paths, array( $image['SourceFile'] ) ) ) );
1691
-
1692
- if ( ! empty( $duplicate_private ) ) {
1693
- $private_sizes[] = $size;
1694
- }
1695
- }
1696
-
1697
- // Processed file, but not this duplicate size, so file may have been re-created by WordPress.
1698
- if ( file_exists( $image['SourceFile'] ) ) {
1699
- $files_to_remove[] = $image['SourceFile'];
1700
- }
1701
- $new_offload_size_paths[ $size ] = $image['SourceFile'];
1702
- continue;
1703
- }
1704
-
1705
- $args = apply_filters( 'as3cf_object_meta', array_merge( $args, $image ), $post_id, $size, false );
1706
-
1707
- // Is size private and therefore needs to be in private prefix?
1708
- $size_private_prefix = in_array( $size, $private_sizes ) ? $as3cf_item->private_prefix() : '';
1709
- $args['Key'] = $size_private_prefix . $args['Key'];
1710
-
1711
- // Protect against filter use and only set ACL if actually required, some storage provider and bucket settings disable changing ACL.
1712
- if ( isset( $args['ACL'] ) && ! $this->use_acl_for_intermediate_size( $post_id, $size, $bucket, $as3cf_item ) ) {
1713
- unset( $args['ACL'] );
1714
- }
1715
-
1716
- if ( ! file_exists( $args['SourceFile'] ) ) {
1717
- if ( ! $duplicate ) {
1718
- $upload_errors[] = $this->return_upload_error( sprintf( __( 'File %s does not exist', 'amazon-s3-and-cloudfront' ), $args['SourceFile'] ) );
1719
- }
1720
- continue;
1721
- }
1722
-
1723
- try {
1724
- // May raise exception, but for sizes we'll just log it and maybe try again later if called.
1725
- $provider_client->upload_object( $args );
1726
- $files_to_remove[] = $image['SourceFile']; // Note: pre `as3cf_object_meta` filter value.
1727
- $new_offload_size_paths[ $size ] = $image['SourceFile'];
1728
-
1729
- // May raise exception, we'll log that, and carry on anyway.
1730
- $new_offload_path_filesizes[ $image['SourceFile'] ] = (int) filesize( $image['SourceFile'] ); // Note: pre `as3cf_object_meta` filter value.
1731
- } catch ( Exception $e ) {
1732
- $upload_errors[] = $this->return_upload_error( sprintf( __( 'Error offloading %s to provider: %s', 'amazon-s3-and-cloudfront' ), $args['SourceFile'], $e->getMessage() ) );
1733
- }
1734
-
1735
- // Edge Case: If previously uploaded and a different original_image wasn't picked up but is now, record it.
1736
- // This is most likely to happen if older version of plugin was used with WP5.3 and large or rotated image auto-created.
1737
- if ( 'original_image' === $size && wp_basename( $as3cf_item->original_source_path() ) !== wp_basename( $image['SourceFile'] ) ) {
1738
- $as3cf_item = new Media_Library_Item(
1739
- $as3cf_item->provider(),
1740
- $as3cf_item->region(),
1741
- $as3cf_item->bucket(),
1742
- $as3cf_item->path(),
1743
- $as3cf_item->is_private(),
1744
- $as3cf_item->source_id(),
1745
- $as3cf_item->source_path(),
1746
- wp_basename( $image['SourceFile'] ),
1747
- $as3cf_item->extra_info(),
1748
- $as3cf_item->id()
1749
- );
1750
- }
1751
- }
1752
-
1753
- $remove_local_files_setting = $this->get_setting( 'remove-local-file' );
1754
-
1755
- if ( $remove_local_files && $remove_local_files_setting ) {
1756
- // Allow other functions to remove files after they have processed
1757
- $files_to_remove = apply_filters( 'as3cf_upload_attachment_local_files_to_remove', $files_to_remove, $post_id, $file_path );
1758
-
1759
- // Remove duplicates
1760
- $files_to_remove = array_unique( $files_to_remove );
1761
-
1762
- $filesize_total = 0;
1763
- if ( ! empty( $old_item ) && ! empty( $offloaded_path_filesizes[ $old_item->id() ] ) ) {
1764
- $filesize_total = array_sum( $offloaded_path_filesizes[ $old_item->id() ] );
1765
- }
1766
- // Delete the files and record original file's size before removal.
1767
- $this->remove_local_files( $files_to_remove, $post_id, $filesize_total );
1768
-
1769
- // Store filesize in the attachment meta data for use by WP if we've just offloaded the full size file.
1770
- if ( ! empty( $filesize ) ) {
1771
- $data['filesize'] = $filesize;
1772
-
1773
- if ( is_null( $return_metadata ) ) {
1774
- // Update metadata with filesize
1775
- update_post_meta( $post_id, '_wp_attachment_metadata', $data );
1776
- }
1777
- }
1778
- }
1779
-
1780
- // Make sure we don't have cached file sizes in the meta if we previously added it.
1781
- if ( ! $remove_local_files_setting && isset( $data['filesize'] ) && ! empty( get_post_meta( $post_id, 'as3cf_filesize_total', true ) ) ) {
1782
- $data = $this->maybe_cleanup_filesize_metadata( $post_id, $data, empty( $return_metadata ) );
1783
- }
1784
-
1785
- // Additional image sizes have custom ACLs, record them.
1786
- if ( ! empty( $private_sizes ) ) {
1787
- $extra_info = $as3cf_item->extra_info();
1788
- $extra_info['private_sizes'] = $private_sizes;
1789
-
1790
- $as3cf_item = new Media_Library_Item(
1791
- $as3cf_item->provider(),
1792
- $as3cf_item->region(),
1793
- $as3cf_item->bucket(),
1794
- $as3cf_item->path(),
1795
- $as3cf_item->is_private(),
1796
- $as3cf_item->source_id(),
1797
- $as3cf_item->source_path(),
1798
- wp_basename( $as3cf_item->original_source_path() ),
1799
- $extra_info,
1800
- $as3cf_item->id()
1801
- );
1802
- }
1803
-
1804
- // All done, save record of offloaded item.
1805
- $as3cf_item->save();
1806
-
1807
- // Keep track of individual files offloaded during this request.
1808
- if ( empty( $offloaded_path_filesizes[ $as3cf_item->id() ] ) ) {
1809
- $offloaded_path_filesizes[ $as3cf_item->id() ] = $new_offload_path_filesizes;
1810
- $offloaded_size_paths[ $as3cf_item->id() ] = $new_offload_size_paths;
1811
- } else {
1812
- $offloaded_path_filesizes[ $as3cf_item->id() ] += $new_offload_path_filesizes;
1813
- $offloaded_size_paths[ $as3cf_item->id() ] += $new_offload_size_paths;
1814
- }
1815
-
1816
- // Keep track of attachments uploaded by this instance.
1817
- $this->uploaded_post_ids[] = $post_id;
1818
-
1819
- do_action( 'as3cf_post_upload_attachment', $post_id, $as3cf_item );
1820
-
1821
- if ( $upload_errors ) {
1822
- return $this->consolidate_upload_errors( $upload_errors );
1823
- }
1824
-
1825
- if ( ! is_null( $return_metadata ) ) {
1826
- // If the attachment metadata is supplied, return it
1827
- return $data;
1828
- }
1829
-
1830
- return $as3cf_item;
1831
- }
1832
-
1833
- /**
1834
- * Get a file's real mime type
1835
- *
1836
- * @param string $file_path
1837
- *
1838
- * @return string
1839
- */
1840
- protected function get_mime_type( $file_path ) {
1841
- $file_type = wp_check_filetype_and_ext( $file_path, wp_basename( $file_path ) );
1842
-
1843
- return $file_type['type'];
1844
- }
1845
-
1846
- /**
1847
- * Should gzip file
1848
- *
1849
- * @param string $file_path
1850
- * @param string $type
1851
- *
1852
- * @return bool
1853
- */
1854
- protected function should_gzip_file( $file_path, $type ) {
1855
- $mimes = $this->get_mime_types_to_gzip( true );
1856
-
1857
- if ( in_array( $type, $mimes ) && is_readable( $file_path ) ) {
1858
- return true;
1859
- }
1860
-
1861
- return false;
1862
- }
1863
-
1864
- /**
1865
- * Get mime types to gzip
1866
- *
1867
- * @param bool $media_library
1868
- *
1869
- * @return array
1870
- */
1871
- protected function get_mime_types_to_gzip( $media_library = false ) {
1872
- $mimes = apply_filters( 'as3cf_gzip_mime_types', array(
1873
- 'css' => 'text/css',
1874
- 'eot' => 'application/vnd.ms-fontobject',
1875
- 'html' => 'text/html',
1876
- 'ico' => 'image/x-icon',
1877
- 'js' => 'application/javascript',
1878
- 'json' => 'application/json',
1879
- 'otf' => 'application/x-font-opentype',
1880
- 'rss' => 'application/rss+xml',
1881
- 'svg' => 'image/svg+xml',
1882
- 'ttf' => 'application/x-font-ttf',
1883
- 'woff' => 'application/font-woff',
1884
- 'woff2' => 'application/font-woff2',
1885
- 'xml' => 'application/xml',
1886
- ), $media_library );
1887
-
1888
- return $mimes;
1889
- }
1890
-
1891
- /**
1892
- * Helper to record errors and return meta data on upload error.
1893
- *
1894
- * @param string $error_msg
1895
- * @param array|null $return
1896
- *
1897
- * @return array|WP_Error
1898
- */
1899
- protected function return_upload_error( $error_msg, $return = null ) {
1900
-
1901
- AS3CF_Error::log( $error_msg );
1902
-
1903
- if ( is_null( $return ) ) {
1904
- return new WP_Error( 'exception', $error_msg );
1905
- }
1906
-
1907
- return $return;
1908
- }
1909
-
1910
- /**
1911
- * Remove files from the local site, recording total filesize in meta if attachment ID given.
1912
- *
1913
- * @param array $file_paths Files to remove.
1914
- * @param int $attachment_id Optional, if supplied filesize metadata recorded.
1915
- * @param int $filesize_total Optional, if removing partial set of an attachment's files, pass in previously removed total.
1916
- */
1917
- function remove_local_files( $file_paths, $attachment_id = 0, $filesize_total = 0 ) {
1918
- if ( empty( $filesize_total ) ) {
1919
- $filesize_total = 0;
1920
- }
1921
-
1922
- foreach ( $file_paths as $index => $path ) {
1923
- if ( ! empty( $attachment_id ) && is_int( $attachment_id ) ) {
1924
- $bytes = filesize( $path );
1925
-
1926
- $filesize_total += ( false !== $bytes ) ? $bytes : 0;
1927
- }
1928
-
1929
- // Individual files might still be kept local, but we're still going to count them towards total above.
1930
- if ( false !== ( $pre = apply_filters( 'as3cf_preserve_file_from_local_removal', false, $path ) ) ) {
1931
- continue;
1932
- }
1933
-
1934
- if ( ! @unlink( $path ) ) {
1935
- $message = 'Error removing local file ';
1936
-
1937
- if ( ! file_exists( $path ) ) {
1938
- $message = "Error removing local file. Couldn't find the file at ";
1939
- } else if ( ! is_writable( $path ) ) {
1940
- $message = 'Error removing local file. Ownership or permissions are mis-configured for ';
1941
- }
1942
-
1943
- AS3CF_Error::log( $message . $path );
1944
- }
1945
- }
1946
-
1947
- // If we were able to sum up file sizes for an attachment, record it.
1948
- if ( ! empty( $attachment_id ) && is_int( $attachment_id ) && $filesize_total > 0 ) {
1949
- update_post_meta( $attachment_id, 'as3cf_filesize_total', $filesize_total );
1950
- }
1951
- }
1952
-
1953
- /**
1954
- * Get the object versioning string prefix
1955
- *
1956
- * @return string
1957
- */
1958
- function get_object_version_string() {
1959
- if ( $this->get_setting( 'use-yearmonth-folders' ) ) {
1960
- $date_format = 'dHis';
1961
- } else {
1962
- $date_format = 'YmdHis';
1963
- }
1964
-
1965
- // Use current time so that object version is unique
1966
- $time = current_time( 'timestamp' );
1967
-
1968
- $object_version = date( $date_format, $time ) . '/';
1969
- $object_version = apply_filters( 'as3cf_get_object_version_string', $object_version );
1970
-
1971
- return $object_version;
1972
- }
1973
-
1974
- /**
1975
- * Get the upload folder time from given URL
1976
- *
1977
- * @param string $url
1978
- *
1979
- * @return null|string YYYY/MM format.
1980
- */
1981
- function get_folder_time_from_url( $url ) {
1982
- if ( ! is_string( $url ) ) {
1983
- return null;
1984
- }
1985
-
1986
- preg_match( '@[0-9]{4}/[0-9]{2}/@', $url, $matches );
1987
-
1988
- if ( isset( $matches[0] ) ) {
1989
- return untrailingslashit( $matches[0] );
1990
- }
1991
-
1992
- return null;
1993
- }
1994
-
1995
- /**
1996
- * Get the year/month string for attachment's upload.
1997
- *
1998
- * Fall back to post date if attached, otherwise current date.
1999
- *
2000
- * @param int $post_id Attachment's ID
2001
- * @param array $data Attachment's metadata
2002
- *
2003
- * @return string
2004
- */
2005
- function get_attachment_folder_year_month( $post_id, $data = null ) {
2006
- if ( isset( $data['file'] ) ) {
2007
- $time = $this->get_folder_time_from_url( $data['file'] );
2008
- }
2009
-
2010
- if ( empty( $time ) && ( $local_url = wp_get_attachment_url( $post_id ) ) ) {
2011
- $time = $this->get_folder_time_from_url( $local_url );
2012
- }
2013
-
2014
- if ( empty( $time ) ) {
2015
- $time = date( 'Y/m' );
2016
-
2017
- if ( ! ( $attach = get_post( $post_id ) ) ) {
2018
- return $time;
2019
- }
2020
-
2021
- if ( ! $attach->post_parent ) {
2022
- return $time;
2023
- }
2024
-
2025
- if ( ! ( $post = get_post( $attach->post_parent ) ) ) {
2026
- return $time;
2027
- }
2028
-
2029
- if ( substr( $post->post_date_gmt, 0, 4 ) > 0 ) {
2030
- return date( 'Y/m', strtotime( $post->post_date_gmt . ' +0000' ) );
2031
- }
2032
- }
2033
-
2034
- return $time;
2035
- }
2036
-
2037
- /**
2038
- * Filters the result when generating a unique file name.
2039
- *
2040
- * @param string $filename Unique file name.
2041
- * @param string $ext File extension, eg. ".png".
2042
- * @param string $dir Directory path.
2043
- *
2044
- * @return string
2045
- * @since 4.5.0
2046
- *
2047
- */
2048
- public function wp_unique_filename( $filename, $ext, $dir ) {
2049
- // Get Post ID if uploaded in post screen.
2050
- $post_id = filter_input( INPUT_POST, 'post_id', FILTER_VALIDATE_INT );
2051
-
2052
- $filename = $this->filter_unique_filename( $filename, $ext, $dir, $post_id );
2053
-
2054
- return $filename;
2055
- }
2056
-
2057
- /**
2058
- * Create unique names for file to be uploaded to AWS.
2059
- * This only applies when the remove local file option is enabled.
2060
- *
2061
- * @param string $filename Unique file name.
2062
- * @param string $ext File extension, eg. ".png".
2063
- * @param string $dir Directory path.
2064
- * @param int $post_id Attachment's parent Post ID.
2065
- *
2066
- * @return string
2067
- */
2068
- public function filter_unique_filename( $filename, $ext, $dir, $post_id = null ) {
2069
- if ( ! $this->is_plugin_setup( true ) ) {
2070
- return $filename;
2071
- }
2072
-
2073
- // sanitize the file name before we begin processing
2074
- $filename = sanitize_file_name( $filename );
2075
- $ext = strtolower( $ext );
2076
- $name = wp_basename( $filename, $ext );
2077
-
2078
- // Edge case: if file is named '.ext', treat as an empty name.
2079
- if ( $name === $ext ) {
2080
- $name = '';
2081
- }
2082
-
2083
- // Rebuild filename with lowercase extension as provider will have converted extension on upload.
2084
- $filename = $name . $ext;
2085
- $time = current_time( 'mysql' );
2086
-
2087
- // Get time if uploaded in post screen.
2088
- if ( ! empty( $post_id ) ) {
2089
- $time = $this->get_post_time( $post_id );
2090
- }
2091
-
2092
- if ( ! $this->does_file_exist( $filename, $time ) ) {
2093
- // File doesn't exist locally or on provider, return it.
2094
- return $filename;
2095
- }
2096
-
2097
- $filename = $this->generate_unique_filename( $name, $ext, $time );
2098
-
2099
- return $filename;
2100
- }
2101
-
2102
- /**
2103
- * Get post time
2104
- *
2105
- * @param int $post_id
2106
- *
2107
- * @return string
2108
- */
2109
- function get_post_time( $post_id ) {
2110
- $time = current_time( 'mysql' );
2111
-
2112
- if ( ! $post = get_post( $post_id ) ) {
2113
- return $time;
2114
- }
2115
-
2116
- if ( substr( $post->post_date, 0, 4 ) > 0 ) {
2117
- $time = $post->post_date;
2118
- }
2119
-
2120
- return $time;
2121
- }
2122
-
2123
- /**
2124
- * Does file exist
2125
- *
2126
- * @param string $filename
2127
- * @param string $time
2128
- *
2129
- * @return bool
2130
- */
2131
- function does_file_exist( $filename, $time ) {
2132
- if ( $this->does_file_exist_local( $filename, $time ) ) {
2133
- return true;
2134
- }
2135
-
2136
- if ( ! $this->get_setting( 'object-versioning' ) && $this->does_file_exist_provider( $filename, $time ) ) {
2137
- return true;
2138
- }
2139
-
2140
- return false;
2141
- }
2142
-
2143
- /**
2144
- * Does file exist local
2145
- *
2146
- * @param string $filename
2147
- * @param string $time
2148
- *
2149
- * @return bool
2150
- */
2151
- function does_file_exist_local( $filename, $time ) {
2152
- global $wpdb;
2153
-
2154
- $path = wp_upload_dir( $time );
2155
- $path = ltrim( $path['subdir'], '/' );
2156
-
2157
- if ( '' !== $path ) {
2158
- $path = trailingslashit( $path );
2159
- }
2160
- $file = $path . $filename;
2161
-
2162
- // WordPress doesn't check its own basic record, so we will.
2163
- $sql = $wpdb->prepare( "
2164
- SELECT COUNT(*)
2165
- FROM $wpdb->postmeta
2166
- WHERE meta_key = %s
2167
- AND meta_value = %s
2168
- ", '_wp_attached_file', $file );
2169
-
2170
- if ( (bool) $wpdb->get_var( $sql ) ) {
2171
- return true;
2172
- }
2173
-
2174
- // Check our records of local source path as it also covers original_image.
2175
- if ( ! empty( Media_Library_Item::get_by_source_path( array( $file ), array(), true, true ) ) ) {
2176
- return true;
2177
- }
2178
-
2179
- return false;
2180
- }
2181
-
2182
- /**
2183
- * Does file exist provider
2184
- *
2185
- * @param string $filename
2186
- * @param string $time
2187
- *
2188
- * @return bool
2189
- * @throws Exception
2190
- */
2191
- function does_file_exist_provider( $filename, $time ) {
2192
- $bucket = $this->get_setting( 'bucket' );
2193
- $region = $this->get_setting( 'region' );
2194
-
2195
- if ( is_wp_error( $region ) ) {
2196
- return false;
2197
- }
2198
-
2199
- $provider_client = $this->get_provider_client( $region );
2200
- $prefix = AS3CF_Utils::trailingslash_prefix( $this->get_object_prefix() );
2201
- $prefix .= AS3CF_Utils::trailingslash_prefix( $this->get_dynamic_prefix( $time ) );
2202
-
2203
- return $provider_client->does_object_exist( $bucket, $prefix . $filename );
2204
- }
2205
-
2206
- /**
2207
- * Generate unique filename
2208
- *
2209
- * @param string $name
2210
- * @param string $ext
2211
- * @param string $time
2212
- *
2213
- * @return string
2214
- */
2215
- function generate_unique_filename( $name, $ext, $time ) {
2216
- $count = 1;
2217
- $filename = $name . '-' . $count . $ext;
2218
-
2219
- while ( $this->does_file_exist( $filename, $time ) ) {
2220
- $count++;
2221
- $filename = $name . '-' . $count . $ext;
2222
- }
2223
-
2224
- return $filename;
2225
- }
2226
-
2227
- /**
2228
- * Check the plugin is correctly setup
2229
- *
2230
- * @param bool $with_credentials Do provider credentials need to be set up too? Defaults to false.
2231
- *
2232
- * @return bool
2233
- *
2234
- * TODO: Performance - cache / static var by param.
2235
- */
2236
- function is_plugin_setup( $with_credentials = false ) {
2237
- if ( $with_credentials && $this->get_storage_provider()->needs_access_keys() ) {
2238
- // AWS not configured
2239
- return false;
2240
- }
2241
-
2242
- if ( false === (bool) $this->get_setting( 'bucket' ) ) {
2243
- // No bucket selected
2244
- return false;
2245
- }
2246
-
2247
- if ( is_wp_error( $this->get_setting( 'region' ) ) ) {
2248
- // Region error when retrieving bucket location
2249
- return false;
2250
- }
2251
-
2252
- // All good, let's do this
2253
- return true;
2254
- }
2255
-
2256
- /**
2257
- * Generate a link to download a file from Amazon provider using query string
2258
- * authentication. This link is only valid for a limited amount of time.
2259
- *
2260
- * @param int $post_id Post ID of the attachment
2261
- * @param int|null $expires Seconds for the link to live
2262
- * @param string|null $size Size of the image to get
2263
- * @param array $headers Header overrides for request
2264
- * @param bool $skip_rewrite_check
2265
- *
2266
- * @return string|bool|WP_Error
2267
- */
2268
- public function get_secure_attachment_url( $post_id, $expires = null, $size = null, $headers = array(), $skip_rewrite_check = false ) {
2269
- if ( is_null( $expires ) ) {
2270
- $expires = self::DEFAULT_EXPIRES;
2271
- }
2272
-
2273
- return $this->get_attachment_url( $post_id, $expires, $size, null, $headers, $skip_rewrite_check );
2274
- }
2275
-
2276
- /**
2277
- * Return the scheme to be used in URLs
2278
- *
2279
- * @param bool|null $use_ssl
2280
- *
2281
- * @return string
2282
- */
2283
- function get_url_scheme( $use_ssl = null ) {
2284
- if ( $this->use_ssl( $use_ssl ) ) {
2285
- $scheme = 'https';
2286
- } else {
2287
- $scheme = 'http';
2288
- }
2289
-
2290
- return $scheme;
2291
- }
2292
-
2293
- /**
2294
- * Determine when to use https in URLS
2295
- *
2296
- * @param bool|null $use_ssl
2297
- *
2298
- * @return bool
2299
- */
2300
- public function use_ssl( $use_ssl = null ) {
2301
- if ( is_ssl() ) {
2302
- $use_ssl = true;
2303
- }
2304
-
2305
- if ( ! is_bool( $use_ssl ) ) {
2306
- $use_ssl = $this->get_setting( 'force-https' );
2307
- }
2308
-
2309
- if ( empty( $use_ssl ) ) {
2310
- $use_ssl = false;
2311
- }
2312
-
2313
- return apply_filters( 'as3cf_use_ssl', $use_ssl );
2314
- }
2315
-
2316
- /**
2317
- * Get the custom object prefix if enabled
2318
- *
2319
- * @param string $toggle_setting
2320
- *
2321
- * @return string
2322
- */
2323
- function get_object_prefix( $toggle_setting = 'enable-object-prefix' ) {
2324
- if ( $this->get_setting( $toggle_setting ) ) {
2325
- return trailingslashit( trim( $this->get_setting( 'object-prefix' ) ) );
2326
- }
2327
-
2328
- return '';
2329
- }
2330
-
2331
- /**
2332
- * Get the file prefix
2333
- *
2334
- * @param null|string $time
2335
- * @param bool $object_versioning_allowed Can an Object Versioning string be appended if setting turned on? Default true.
2336
- *
2337
- * @return string
2338
- */
2339
- public function get_file_prefix( $time = null, $object_versioning_allowed = true ) {
2340
- $prefix = AS3CF_Utils::trailingslash_prefix( $this->get_object_prefix() );
2341
- $prefix .= AS3CF_Utils::trailingslash_prefix( $this->get_dynamic_prefix( $time ) );
2342
-
2343
- if ( ! empty( $object_versioning_allowed ) && $this->get_setting( 'object-versioning' ) ) {
2344
- $prefix .= AS3CF_Utils::trailingslash_prefix( $this->get_object_version_string() );
2345
- }
2346
-
2347
- return $prefix;
2348
- }
2349
-
2350
- /**
2351
- * Get attachment's new public prefix path for current settings.
2352
- *
2353
- * @param int $post_id Attachment ID
2354
- * @param array $metadata Optional attachment metadata
2355
- * @param bool $object_versioning_allowed Can an Object Versioning string be appended if setting turned on? Default true.
2356
- *
2357
- * @return string
2358
- */
2359
- public function get_new_attachment_prefix( $post_id, $metadata = null, $object_versioning_allowed = true ) {
2360
- if ( empty( $metadata ) ) {
2361
- $metadata = wp_get_attachment_metadata( $post_id, true );
2362
- }
2363
-
2364
- $time = $this->get_attachment_folder_year_month( $post_id, $metadata );
2365
-
2366
- return $this->get_file_prefix( $time, $object_versioning_allowed );
2367
- }
2368
-
2369
- /**
2370
- * Get the url of the file from provider
2371
- *
2372
- * @param int $post_id Post ID of the attachment, required.
2373
- * @param int|null $expires Seconds for the link to live, optional.
2374
- * @param string|null $size Size of the image to get, optional.
2375
- * @param array|null $meta Pre retrieved _wp_attachment_metadata for the attachment, optional.
2376
- * @param array $headers Header overrides for request, optional.
2377
- * @param bool $skip_rewrite_check Always return the URL regardless of the 'Rewrite File URLs' setting, optional, default: false.
2378
- * Useful for the EDD and Woo addons to not break download URLs when the option is disabled.
2379
- *
2380
- * @return string|bool|WP_Error
2381
- */
2382
- public function get_attachment_url( $post_id, $expires = null, $size = null, $meta = null, $headers = array(), $skip_rewrite_check = false ) {
2383
- if ( ! ( $as3cf_item = $this->is_attachment_served_by_provider( $post_id, $skip_rewrite_check ) ) ) {
2384
- return false;
2385
- }
2386
-
2387
- $url = $this->get_attachment_provider_url( $post_id, $as3cf_item, $expires, $size, $meta, $headers );
2388
-
2389
- return apply_filters( 'as3cf_wp_get_attachment_url', $url, $post_id );
2390
- }
2391
-
2392
- /**
2393
- * Get attachment local URL.
2394
- *
2395
- * This is a direct copy of wp_get_attachment_url() from /wp-includes/post.php
2396
- * as we filter the URL in AS3CF and can't remove this filter using the current implementation
2397
- * of globals for class instances.
2398
- *
2399
- * @param int $post_id
2400
- *
2401
- * @return string|false Attachment URL, otherwise false.
2402
- */
2403
- public function get_attachment_local_url( $post_id ) {
2404
- $url = '';
2405
-
2406
- // Get attached file.
2407
- if ( $file = get_post_meta( $post_id, '_wp_attached_file', true ) ) {
2408
- // Get upload directory.
2409
- if ( ( $uploads = wp_upload_dir() ) && false === $uploads['error'] ) {
2410
- // Check that the upload base exists in the file location.
2411
- if ( 0 === strpos( $file, $uploads['basedir'] ) ) {
2412
- // Replace file location with url location.
2413
- $url = str_replace( $uploads['basedir'], $uploads['baseurl'], $file );
2414
- } elseif ( false !== strpos( $file, 'wp-content/uploads' ) ) {
2415
- $url = $uploads['baseurl'] . substr( $file, strpos( $file, 'wp-content/uploads' ) + 18 );
2416
- } else {
2417
- // It's a newly-uploaded file, therefore $file is relative to the basedir.
2418
- $url = $uploads['baseurl'] . "/$file";
2419
- }
2420
- }
2421
- }
2422
-
2423
- if ( empty( $url ) ) {
2424
- return false;
2425
- }
2426
-
2427
- $url = $this->maybe_fix_local_subsite_url( $url );
2428
-
2429
- return $url;
2430
- }
2431
-
2432
- /**
2433
- * Get attachment local URL size.
2434
- *
2435
- * @param int $post_id
2436
- * @param string|null $size
2437
- *
2438
- * @return false|string
2439
- */
2440
- public function get_attachment_local_url_size( $post_id, $size = null ) {
2441
- $url = $this->get_attachment_local_url( $post_id );
2442
-
2443
- if ( empty( $size ) ) {
2444
- return $url;
2445
- }
2446
-
2447
- $meta = get_post_meta( $post_id, '_wp_attachment_metadata', true );
2448
-
2449
- if ( empty( $meta['sizes'][ $size ]['file'] ) ) {
2450
- // No alternative sizes available, return
2451
- return $url;
2452
- }
2453
-
2454
- return str_replace( wp_basename( $url ), $meta['sizes'][ $size ]['file'], $url );
2455
- }
2456
-
2457
- /**
2458
- * Get the provider URL for an attachment
2459
- *
2460
- * @param int $post_id
2461
- * @param Media_Library_Item $as3cf_item
2462
- * @param null|int $expires
2463
- * @param null|string|array $size
2464
- * @param null|array $meta
2465
- * @param array $headers
2466
- *
2467
- * @return string|WP_Error
2468
- */
2469
- public function get_attachment_provider_url( $post_id, Media_Library_Item $as3cf_item, $expires = null, $size = null, $meta = null, $headers = array() ) {
2470
- $size = AS3CF_Utils::maybe_convert_size_to_string( $post_id, $size );
2471
-
2472
- // Is a signed expiring URL required for the requested object?
2473
- if ( is_null( $expires ) ) {
2474
- $expires = $as3cf_item->is_private_size( $size ) ? self::DEFAULT_EXPIRES : null;
2475
- } else {
2476
- $expires = $as3cf_item->is_private_size( $size ) ? $expires : null;
2477
- }
2478
-
2479
- $item_path = $as3cf_item->path();
2480
-
2481
- if ( ! is_null( $size ) ) {
2482
- if ( is_null( $meta ) ) {
2483
- $meta = get_post_meta( $post_id, '_wp_attachment_metadata', true );
2484
- }
2485
-
2486
- if ( is_wp_error( $meta ) ) {
2487
- return $meta;
2488
- }
2489
-
2490
- if ( ! empty( $meta ) && isset( $meta['sizes'][ $size ]['file'] ) ) {
2491
- $size_prefix = dirname( $item_path );
2492
- $size_file_prefix = ( '.' === $size_prefix ) ? '' : $size_prefix . '/';
2493
-
2494
- $item_path = $size_file_prefix . $meta['sizes'][ $size ]['file'];
2495
- }
2496
- }
2497
-
2498
- $scheme = $this->get_url_scheme();
2499
- $enable_delivery_domain = $this->get_delivery_provider()->delivery_domain_allowed() ? $this->get_setting( 'enable-delivery-domain' ) : false;
2500
- $delivery_domain = $this->get_setting( 'delivery-domain' );
2501
-
2502
- if ( ! $enable_delivery_domain || empty( $delivery_domain ) ) {
2503
- $region = $as3cf_item->region();
2504
-
2505
- if ( is_wp_error( $region ) ) {
2506
- return $region;
2507
- }
2508
-
2509
- $delivery_domain = $this->get_storage_provider()->get_url_domain( $as3cf_item->bucket(), $region, $expires );
2510
- } else {
2511
- $delivery_domain = AS3CF_Utils::sanitize_custom_domain( $delivery_domain );
2512
- }
2513
-
2514
- if ( ! is_null( $expires ) && $this->is_plugin_setup( true ) ) {
2515
- try {
2516
- $timestamp = time() + apply_filters( 'as3cf_expires', $expires );
2517
- $url = $this->get_delivery_provider()->get_signed_url( $as3cf_item, $item_path, $delivery_domain, $scheme, $timestamp, $headers );
2518
-
2519
- return apply_filters( 'as3cf_get_attachment_secure_url', $url, $as3cf_item, $post_id, $timestamp, $headers );
2520
- } catch ( Exception $e ) {
2521
- return new WP_Error( 'exception', $e->getMessage() );
2522
- }
2523
- } else {
2524
- try {
2525
- $url = $this->get_delivery_provider()->get_url( $as3cf_item, $item_path, $delivery_domain, $scheme, $headers );
2526
-
2527
- return apply_filters( 'as3cf_get_attachment_url', $url, $as3cf_item, $post_id, $expires, $headers );
2528
- } catch ( Exception $e ) {
2529
- return new WP_Error( 'exception', $e->getMessage() );
2530
- }
2531
- }
2532
- }
2533
-
2534
- /**
2535
- * Get attachment url
2536
- *
2537
- * @param string $url
2538
- * @param int $post_id
2539
- *
2540
- * @return bool|mixed|WP_Error
2541
- */
2542
- public function wp_get_attachment_url( $url, $post_id ) {
2543
- if ( $this->plugin_compat->is_customizer_crop_action() ) {
2544
- return $url;
2545
- }
2546
-
2547
- $new_url = $this->get_attachment_url( $post_id );
2548
-
2549
- if ( false === $new_url ) {
2550
- return $url;
2551
- }
2552
-
2553
- $new_url = apply_filters( 'wps3_get_attachment_url', $new_url, $post_id, $this ); // Old naming convention, will be deprecated soon
2554
- $new_url = apply_filters( 'as3cf_wp_get_attachment_url', $new_url, $post_id );
2555
-
2556
- return $new_url;
2557
- }
2558
-
2559
- /**
2560
- * Filters the list of attachment image attributes.
2561
- *
2562
- * @param array $attr Attributes for the image markup.
2563
- * @param WP_Post $attachment Image attachment post.
2564
- * @param string|array $size Requested size. Image size or array of width and height values (in that order).
2565
- *
2566
- * @return array
2567
- */
2568
- public function wp_get_attachment_image_attributes( $attr, $attachment, $size ) {
2569
- if ( ! ( $as3cf_item = $this->is_attachment_served_by_provider( $attachment->ID ) ) ) {
2570
- return $attr;
2571
- }
2572
-
2573
- $size = AS3CF_Utils::maybe_convert_size_to_string( $attachment->ID, $size );
2574
-
2575
- // image_downsize incorrectly substitutes size filename into full URL for src attribute instead of clobbering.
2576
- // So we need to fix up the src attribute if a size is being used.
2577
- if ( ! empty( $size ) && ! empty( $attr['src'] ) ) {
2578
- $attr['src'] = $this->get_attachment_provider_url( $attachment->ID, $as3cf_item, null, $size );
2579
- }
2580
-
2581
- /**
2582
- * Filtered list of attachment image attributes.
2583
- *
2584
- * @param array $attr Attributes for the image markup.
2585
- * @param WP_Post $attachment Image attachment post.
2586
- * @param string $size Requested size.
2587
- * @param Media_Library_Item $as3cf_item
2588
- */
2589
- return apply_filters( 'as3cf_wp_get_attachment_image_attributes', $attr, $attachment, $size, $as3cf_item );
2590
- }
2591
-
2592
- /**
2593
- * Maybe encode attachment URLs when retrieving the image tag
2594
- *
2595
- * @param string $html
2596
- * @param int $id
2597
- * @param string $alt
2598
- * @param string $title
2599
- * @param string $align
2600
- * @param string $size
2601
- *
2602
- * @return string
2603
- */
2604
- public function maybe_encode_get_image_tag( $html, $id, $alt, $title, $align, $size ) {
2605
- if ( ! ( $as3cf_item = $this->is_attachment_served_by_provider( $id ) ) ) {
2606
- // Not served by provider, return
2607
- return $html;
2608
- }
2609
-
2610
- if ( ! is_string( $html ) ) {
2611
- return $html;
2612
- }
2613
-
2614
- preg_match( '@\ssrc=[\'\"]([^\'\"]*)[\'\"]@', $html, $matches );
2615
-
2616
- if ( ! isset( $matches[1] ) ) {
2617
- // Can't establish img src
2618
- return $html;
2619
- }
2620
-
2621
- $img_src = $matches[1];
2622
- $new_img_src = $this->maybe_sign_intermediate_size( $img_src, $id, $size, $as3cf_item );
2623
- $new_img_src = AS3CF_Utils::encode_filename_in_path( $new_img_src );
2624
-
2625
- return str_replace( $img_src, $new_img_src, $html );
2626
- }
2627
-
2628
- /**
2629
- * Maybe encode URLs for images that represent an attachment
2630
- *
2631
- * @param array|bool $image
2632
- * @param int $attachment_id
2633
- * @param string|array $size
2634
- * @param bool $icon
2635
- *
2636
- * @return array
2637
- */
2638
- public function maybe_encode_wp_get_attachment_image_src( $image, $attachment_id, $size, $icon ) {
2639
- if ( ! ( $as3cf_item = $this->is_attachment_served_by_provider( $attachment_id ) ) ) {
2640
- // Not served by provider, return
2641
- return $image;
2642
- }
2643
-
2644
- if ( isset( $image[0] ) ) {
2645
- $url = $this->maybe_sign_intermediate_size( $image[0], $attachment_id, $size, $as3cf_item );
2646
- $url = AS3CF_Utils::encode_filename_in_path( $url );
2647
-
2648
- $image[0] = $url;
2649
- }
2650
-
2651
- return $image;
2652
- }
2653
-
2654
- /**
2655
- * Maybe encode URLs when outputting attachments in the media grid
2656
- *
2657
- * @param array $response
2658
- * @param int|object $attachment
2659
- * @param array $meta
2660
- *
2661
- * @return array
2662
- */
2663
- public function maybe_encode_wp_prepare_attachment_for_js( $response, $attachment, $meta ) {
2664
- if ( ! ( $as3cf_item = $this->is_attachment_served_by_provider( $attachment->ID ) ) ) {
2665
- // Not served by provider, return
2666
- return $response;
2667
- }
2668
-
2669
- if ( isset( $response['url'] ) ) {
2670
- $response['url'] = AS3CF_Utils::encode_filename_in_path( $response['url'] );
2671
- }
2672
-
2673
- if ( isset( $response['sizes'] ) && is_array( $response['sizes'] ) ) {
2674
- foreach ( $response['sizes'] as $size => $value ) {
2675
- $url = $this->maybe_sign_intermediate_size( $value['url'], $attachment->ID, $size, $as3cf_item, true );
2676
- $url = AS3CF_Utils::encode_filename_in_path( $url );
2677
-
2678
- $response['sizes'][ $size ]['url'] = $url;
2679
- }
2680
- }
2681
-
2682
- return $response;
2683
- }
2684
-
2685
- /**
2686
- * Maybe encode URLs when retrieving intermediate sizes.
2687
- *
2688
- * @param array $data
2689
- * @param int $post_id
2690
- * @param string|array $size
2691
- *
2692
- * @return array
2693
- */
2694
- public function maybe_encode_image_get_intermediate_size( $data, $post_id, $size ) {
2695
- if ( ! ( $as3cf_item = $this->is_attachment_served_by_provider( $post_id ) ) ) {
2696
- // Not served by provider, return
2697
- return $data;
2698
- }
2699
-
2700
- if ( isset( $data['url'] ) ) {
2701
- $url = $this->maybe_sign_intermediate_size( $data['url'], $post_id, $size, $as3cf_item );
2702
- $url = AS3CF_Utils::encode_filename_in_path( $url );
2703
-
2704
- $data['url'] = $url;
2705
- }
2706
-
2707
- return $data;
2708
- }
2709
-
2710
- /**
2711
- * Sign intermediate size.
2712
- *
2713
- * @param string $url
2714
- * @param int $attachment_id
2715
- * @param string|array $size
2716
- * @param bool|Media_Library_Item $as3cf_item
2717
- * @param bool $force_rewrite If size not signed, make sure correct URL is being used anyway.
2718
- *
2719
- * @return string|WP_Error
2720
- */
2721
- protected function maybe_sign_intermediate_size( $url, $attachment_id, $size, $as3cf_item = false, $force_rewrite = false ) {
2722
- if ( ! $as3cf_item ) {
2723
- $as3cf_item = Media_Library_Item::get_by_source_id( $attachment_id );
2724
- }
2725
-
2726
- $size = AS3CF_Utils::maybe_convert_size_to_string( $attachment_id, $size );
2727
-
2728
- if ( $force_rewrite || $as3cf_item->is_private_size( $size ) ) {
2729
- // Private file, add AWS signature if required
2730
- return $this->get_attachment_provider_url( $attachment_id, $as3cf_item, null, $size );
2731
- }
2732
-
2733
- return $url;
2734
- }
2735
 
2736
  /**
2737
  * Is attachment served by provider.
@@ -2744,91 +1467,34 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
2744
  *
2745
  * @return bool|Media_Library_Item
2746
  */
2747
- public function is_attachment_served_by_provider( $attachment_id, $skip_rewrite_check = false, $skip_current_provider_check = false, Storage_Provider $provider = null, $check_is_verified = false ) {
2748
- if ( ! $skip_rewrite_check && ! $this->get_setting( 'serve-from-s3' ) ) {
2749
- // Not serving provider URLs
2750
- return false;
2751
- }
2752
-
2753
- $as3cf_item = Media_Library_Item::get_by_source_id( $attachment_id );
2754
-
2755
- if ( ! $as3cf_item ) {
2756
- // File not uploaded to a provider
2757
- return false;
2758
- }
2759
-
2760
- if ( ! $skip_rewrite_check && ! empty( $check_is_verified ) && ! $as3cf_item->is_verified() ) {
2761
- // Offload not verified, treat as not offloaded.
2762
- return false;
2763
- }
2764
-
2765
- if ( ! $skip_current_provider_check && empty( $provider ) ) {
2766
- $provider = $this->get_storage_provider();
2767
- }
2768
-
2769
- if ( ! empty( $provider ) && $provider::get_provider_key_name() !== $as3cf_item->provider() ) {
2770
- // File not uploaded to required provider
2771
- return false;
2772
- }
2773
-
2774
- return $as3cf_item;
2775
- }
2776
-
2777
- /**
2778
- * Allow processes to update the file on provider via update_attached_file()
2779
- *
2780
- * @param string $file
2781
- * @param int $attachment_id
2782
- *
2783
- * @return string
2784
- */
2785
- function update_attached_file( $file, $attachment_id ) {
2786
- if ( ! $this->is_plugin_setup( true ) ) {
2787
- return $file;
2788
  }
2789
 
2790
  $as3cf_item = Media_Library_Item::get_by_source_id( $attachment_id );
2791
 
2792
  if ( ! $as3cf_item ) {
2793
- return $file;
 
2794
  }
2795
 
2796
- $file = apply_filters( 'as3cf_update_attached_file', $file, $attachment_id, $as3cf_item );
2797
-
2798
- return $file;
2799
- }
2800
-
2801
- /**
2802
- * Return the provider URL when the local file is missing
2803
- * unless we know who the calling process is and we are happy
2804
- * to copy the file back to the server to be used.
2805
- *
2806
- * @handles get_attached_file
2807
- * @handles wp_get_original_image_path
2808
- *
2809
- * @param string $file
2810
- * @param int $attachment_id
2811
- *
2812
- * @return string
2813
- */
2814
- function get_attached_file( $file, $attachment_id ) {
2815
- $as3cf_item = $this->is_attachment_served_by_provider( $attachment_id );
2816
-
2817
- if ( file_exists( $file ) || ! $as3cf_item ) {
2818
- if ( $as3cf_item ) {
2819
- // Although we have a local file, give filter implementors a chance to override or copy back siblings.
2820
- return apply_filters( 'as3cf_get_attached_file_noop', $file, $file, $attachment_id, $as3cf_item );
2821
- } else {
2822
- return $file;
2823
- }
2824
  }
2825
 
2826
- $url = $this->get_attachment_url( $attachment_id );
 
 
2827
 
2828
- // return the URL by default
2829
- $file = apply_filters( 'as3cf_get_attached_file', $url, $file, $attachment_id, $as3cf_item );
 
 
2830
 
2831
- return $file;
2832
  }
2833
 
2834
  /**
@@ -3068,7 +1734,7 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
3068
  /**
3069
  * Add the settings page to the top-level AWS menu item for backwards compatibility.
3070
  *
3071
- * @param \Amazon_Web_Services $aws Plugin class instance from the amazon-web-services plugin.
3072
  */
3073
  public function aws_admin_menu( $aws ) {
3074
  $aws->add_page(
@@ -3283,7 +1949,7 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
3283
  return self::$buckets_check[ $bucket ];
3284
  }
3285
 
3286
- $key = $this->get_file_prefix() . 'as3cf-permission-check.txt';
3287
  $file_contents = __( 'This is a test file to check if the user has write permission to the bucket. Delete me if found.', 'amazon-s3-and-cloudfront' );
3288
 
3289
  $can_write = $this->get_provider_client( $region, true )->can_write( $bucket, $key, $file_contents );
@@ -3301,6 +1967,28 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
3301
  return $can_write;
3302
  }
3303
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3304
  /**
3305
  * Render error messages in a view for bucket permission and access issues
3306
  *
@@ -4010,10 +2698,11 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
4010
  * year month subdirectory setting and just uses S3 setting
4011
  *
4012
  * @param string $time
 
4013
  *
4014
  * @return string
4015
  */
4016
- function get_dynamic_prefix( $time = null ) {
4017
  $prefix = '';
4018
  $subdir = '';
4019
 
@@ -4056,7 +2745,7 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
4056
  }
4057
  }
4058
 
4059
- if ( $this->get_setting( 'use-yearmonth-folders' ) ) {
4060
  $subdir = $this->get_year_month_directory_name( $time );
4061
  $prefix .= $subdir;
4062
  }
@@ -4153,9 +2842,9 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
4153
  * @param Media_Library_Item $as3cf_item
4154
  * @param string|null $size
4155
  */
4156
- function make_acl_admin_notice( Media_Library_Item $as3cf_item, $size = null ) {
4157
  $filename = wp_basename( $as3cf_item->path( $size ) );
4158
- $acl = $as3cf_item->is_private_size( $size ) ? $this->get_storage_provider()->get_private_acl() : $this->get_storage_provider()->get_default_acl();
4159
  $acl_name = $this->get_acl_display_name( $acl );
4160
  $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>" );
4161
 
@@ -5043,7 +3732,7 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
5043
  * Used to give a realistic total of storage space used on a Multisite subsite,
5044
  * when there have been attachments uploaded to S3 but removed from server
5045
  *
5046
- * @param $space_used bool
5047
  *
5048
  * @return float|int
5049
  */
@@ -5133,10 +3822,12 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
5133
  foreach ( $table_prefixes as $blog_id => $table_prefix ) {
5134
  $this->switch_to_blog( $blog_id );
5135
 
5136
- $counts = Media_Library_Item::count_attachments( $skip_transient, $force );
5137
- $total += $counts['total'];
5138
- $offloaded += $counts['offloaded'];
5139
- $not_offloaded += $counts['not_offloaded'];
 
 
5140
 
5141
  $this->restore_current_blog();
5142
  }
@@ -5295,17 +3986,6 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
5295
  return filter_input( $type, $variable, $filter, $options );
5296
  }
5297
 
5298
- /**
5299
- * Helper function for terminating script execution. Easily testable.
5300
- *
5301
- * @param int|string $exit_code
5302
- *
5303
- * @return void
5304
- */
5305
- public function _exit( $exit_code = 0 ) {
5306
- exit( $exit_code );
5307
- }
5308
-
5309
  /**
5310
  * Upgrade the 'virtual host' / 'bucket as domain' setting to the
5311
  * new CloudFront / Domain setting
@@ -5371,210 +4051,6 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
5371
  return $path;
5372
  }
5373
 
5374
- /**
5375
- * Add the S3 meta box to the attachment screen
5376
- */
5377
- public function attachment_provider_meta_box() {
5378
- add_meta_box(
5379
- 's3-actions',
5380
- __( 'Offload', 'amazon-s3-and-cloudfront' ),
5381
- array( $this, 'attachment_provider_actions_meta_box' ),
5382
- 'attachment',
5383
- 'side',
5384
- 'core'
5385
- );
5386
- }
5387
-
5388
- /**
5389
- * Check we can do the media actions
5390
- *
5391
- * @return bool
5392
- */
5393
- public function verify_media_actions() {
5394
- return false;
5395
- }
5396
-
5397
- /**
5398
- * Get a list of available media actions which can be performed according to plugin and user capability requirements.
5399
- *
5400
- * @param string|null $scope
5401
- *
5402
- * @return array
5403
- */
5404
- public function get_available_media_actions( $scope = '' ) {
5405
- return array();
5406
- }
5407
-
5408
- /**
5409
- * Render the S3 attachment meta box
5410
- */
5411
- public function attachment_provider_actions_meta_box() {
5412
- global $post;
5413
- $file = get_attached_file( $post->ID, true );
5414
-
5415
- $args = array(
5416
- 'provider_object' => $this->get_formatted_provider_info( $post->ID ),
5417
- 'post' => $post,
5418
- 'local_file_exists' => file_exists( $file ),
5419
- 'available_actions' => $this->get_available_media_actions( 'singular' ),
5420
- 'sendback' => 'post.php?post=' . $post->ID . '&action=edit',
5421
- );
5422
-
5423
- $this->render_view( 'attachment-metabox', $args );
5424
- }
5425
-
5426
- /**
5427
- * Get ACL value string.
5428
- *
5429
- * @param array $acl
5430
- * @param int $post_id
5431
- *
5432
- * @return string
5433
- */
5434
- protected function get_acl_value_string( $acl, $post_id ) {
5435
- return $acl['name'];
5436
- }
5437
-
5438
- /**
5439
- * Return a formatted provider info array with display friendly defaults
5440
- *
5441
- * @param int $id
5442
- *
5443
- * @return bool|array
5444
- */
5445
- public function get_formatted_provider_info( $id ) {
5446
- $as3cf_item = Media_Library_Item::get_by_source_id( $id );
5447
-
5448
- if ( ! $as3cf_item ) {
5449
- return false;
5450
- }
5451
-
5452
- $provider_object = $as3cf_item->key_values();
5453
-
5454
- // Backwards compatibility.
5455
- $provider_object['key'] = $provider_object['path'];
5456
- $provider_object['url'] = $this->get_attachment_provider_url( $id, $as3cf_item );
5457
-
5458
- $acl = $as3cf_item->is_private() ? $this->get_storage_provider()->get_private_acl() : $this->get_storage_provider()->get_default_acl();
5459
- $acl_info = array(
5460
- 'acl' => $acl,
5461
- 'name' => $this->get_acl_display_name( $acl ),
5462
- 'title' => $this->get_media_action_strings( 'change_to_private' ),
5463
- );
5464
-
5465
- if ( $as3cf_item->is_private() ) {
5466
- $acl_info['title'] = $this->get_media_action_strings( 'change_to_public' );
5467
- }
5468
-
5469
- $provider_object['acl'] = $acl_info;
5470
- $provider_object['region'] = $this->get_storage_provider()->get_region_name( $provider_object['region'] );
5471
- $provider_object['provider_name'] = $this->get_provider_service_name( $provider_object['provider'] );
5472
-
5473
- return $provider_object;
5474
- }
5475
-
5476
- /**
5477
- * Get all strings or a specific string used for the media actions
5478
- *
5479
- * @param null|string $string
5480
- *
5481
- * @return array|string
5482
- */
5483
- public function get_media_action_strings( $string = null ) {
5484
- $not_verified_value = __( 'No', 'amazon-s3-and-cloudfront' );
5485
- $not_verified_value .= '&nbsp;';
5486
- $not_verified_value .= $this->more_info_link( '/wp-offload-media/doc/add-metadata-tool/', 'os3+attachment+metabox', 'analyze-and-repair', 'More Info', '(', ')' );
5487
-
5488
- $strings = apply_filters( 'as3cf_media_action_strings', array(
5489
- 'provider' => _x( 'Storage Provider', 'Storage provider key name', 'amazon-s3-and-cloudfront' ),
5490
- 'provider_name' => _x( 'Storage Provider', 'Storage provider name', 'amazon-s3-and-cloudfront' ),
5491
- 'bucket' => _x( 'Bucket', 'Bucket name', 'amazon-s3-and-cloudfront' ),
5492
- 'key' => _x( 'Path', 'Path to file in bucket', 'amazon-s3-and-cloudfront' ),
5493
- 'region' => _x( 'Region', 'Location of bucket', 'amazon-s3-and-cloudfront' ),
5494
- 'acl' => _x( 'Access', 'Access control list of the file in bucket', 'amazon-s3-and-cloudfront' ),
5495
- 'url' => __( 'URL', 'amazon-s3-and-cloudfront' ),
5496
- 'is_verified' => _x( 'Verified', 'Whether or not metadata has been verified', 'amazon-s3-and-cloudfront' ),
5497
- 'not_verified' => $not_verified_value,
5498
- ) );
5499
-
5500
- if ( ! is_null( $string ) ) {
5501
- return isset( $strings[ $string ] ) ? $strings[ $string ] : '';
5502
- }
5503
-
5504
- return $strings;
5505
- }
5506
-
5507
- /**
5508
- * Load media assets.
5509
- */
5510
- public function load_media_assets() {
5511
- $this->enqueue_style( 'as3cf-media-styles', 'assets/css/media', array( 'as3cf-modal' ) );
5512
- $this->enqueue_script( 'as3cf-media-script', 'assets/js/media', array(
5513
- 'jquery',
5514
- 'media-views',
5515
- 'media-grid',
5516
- 'wp-util',
5517
- ) );
5518
-
5519
- wp_localize_script( 'as3cf-media-script', 'as3cf_media', array(
5520
- 'strings' => $this->get_media_action_strings(),
5521
- 'nonces' => array(
5522
- 'get_attachment_provider_details' => wp_create_nonce( 'get-attachment-s3-details' ),
5523
- ),
5524
- ) );
5525
- }
5526
-
5527
- /**
5528
- * Handle retrieving the provider details for attachment modals.
5529
- */
5530
- public function ajax_get_attachment_provider_details() {
5531
- if ( ! isset( $_POST['id'] ) ) {
5532
- return;
5533
- }
5534
-
5535
- check_ajax_referer( 'get-attachment-s3-details', '_nonce' );
5536
-
5537
- $id = intval( $_POST['id'] );
5538
-
5539
- // get the actions available for the attachment
5540
- $data = array(
5541
- 'links' => $this->add_media_row_actions( array(), $id ),
5542
- 'provider_object' => $this->get_formatted_provider_info( $id ),
5543
- 'acl_toggle' => $this->verify_media_actions() && $this->is_attachment_served_by_provider( $id, true ),
5544
- );
5545
-
5546
- wp_send_json_success( $data );
5547
- }
5548
-
5549
- /**
5550
- * Conditionally adds copy, remove and download S3 action links for an
5551
- * attachment on the Media library list view
5552
- *
5553
- * @param array $actions
5554
- * @param WP_Post|int $post
5555
- *
5556
- * @return array
5557
- */
5558
- function add_media_row_actions( array $actions, $post ) {
5559
- return $actions;
5560
- }
5561
-
5562
- /**
5563
- * Load the attachment assets only when editing an attachment
5564
- *
5565
- * @param $hook_suffix
5566
- */
5567
- public function load_attachment_assets( $hook_suffix ) {
5568
- global $post;
5569
- if ( 'post.php' !== $hook_suffix || 'attachment' !== $post->post_type ) {
5570
- return;
5571
- }
5572
-
5573
- $this->enqueue_style( 'as3cf-pro-attachment-styles', 'assets/css/attachment', array( 'as3cf-modal' ) );
5574
-
5575
- do_action( 'as3cf_load_attachment_assets' );
5576
- }
5577
-
5578
  /**
5579
  * Maybe remove query string from URL.
5580
  *
@@ -5588,40 +4064,6 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
5588
  return reset( $parts );
5589
  }
5590
 
5591
- /**
5592
- * Has the given attachment been uploaded by this instance?
5593
- *
5594
- * @param int $attachment_id
5595
- *
5596
- * @return bool
5597
- */
5598
- public function attachment_just_uploaded( $attachment_id ) {
5599
- if ( is_int( $attachment_id ) && in_array( $attachment_id, $this->uploaded_post_ids ) ) {
5600
- return true;
5601
- }
5602
-
5603
- return false;
5604
- }
5605
-
5606
- /**
5607
- * Filters the audio & video shortcodes output to remove "&_=NN" params from source.src as it breaks signed URLs.
5608
- *
5609
- * @param string $html Shortcode HTML output.
5610
- * @param array $atts Array of shortcode attributes.
5611
- * @param string $media Media file.
5612
- * @param int $post_id Post ID.
5613
- * @param string $library Media library used for the shortcode.
5614
- *
5615
- * @return string
5616
- *
5617
- * Note: Depends on 30377.4.diff from https://core.trac.wordpress.org/ticket/30377
5618
- */
5619
- public function wp_media_shortcode( $html, $atts, $media, $post_id, $library ) {
5620
- $html = preg_replace( '/&#038;_=[0-9]+/', '', $html );
5621
-
5622
- return $html;
5623
- }
5624
-
5625
  /**
5626
  * Ensure local URL is correct for multisite's non-primary subsites.
5627
  *
@@ -5662,7 +4104,7 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
5662
  $acl = $this->get_storage_provider()->get_default_acl();
5663
 
5664
  if ( ! empty( $as3cf_item ) ) {
5665
- $acl = $as3cf_item->is_private_size( $size ) ? $this->get_storage_provider()->get_private_acl() : $this->get_storage_provider()->get_default_acl();
5666
  }
5667
  }
5668
 
@@ -5672,14 +4114,15 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
5672
  /**
5673
  * Are ACLs in use for intermediate size on bucket?
5674
  *
5675
- * @param int $attachment_id
5676
- * @param string $size
5677
- * @param string $bucket Optional bucket that ACL is potentially to be used with.
5678
- * @param Media_Library_Item|null $as3cf_item Optional item.
5679
  *
5680
  * @return bool
5681
  */
5682
- public function use_acl_for_intermediate_size( $attachment_id, $size, $bucket = null, Media_Library_Item $as3cf_item = null ) {
 
5683
  if ( empty( $as3cf_item ) ) {
5684
  $as3cf_item = Media_Library_Item::get_by_source_id( $attachment_id );
5685
  }
@@ -5696,7 +4139,7 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
5696
  $use_private_prefix = apply_filters( 'as3cf_enable_signed_urls_for_intermediate_size', $this->private_prefix_enabled(), $attachment_id, $size, $bucket, $as3cf_item );
5697
 
5698
  // If signed custom URLs are in play, and we have a private object, usually you can not use ACLs.
5699
- if ( $use_acl && $use_private_prefix && ! empty( $as3cf_item ) && $as3cf_item->is_private_size( $size ) ) {
5700
  $use_acl = false;
5701
  }
5702
 
@@ -5704,25 +4147,6 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
5704
  return apply_filters( 'as3cf_use_acl_for_intermediate_size', $use_acl, $attachment_id, $size, $bucket, $as3cf_item );
5705
  }
5706
 
5707
- /**
5708
- * Consolidate an array of WP_Errors into a single WP_Error object.
5709
- *
5710
- * @param array $upload_errors
5711
- *
5712
- * @return WP_Error
5713
- */
5714
- protected function consolidate_upload_errors( $upload_errors ) {
5715
- $errors = new WP_Error;
5716
-
5717
- foreach ( $upload_errors as $error ) {
5718
-
5719
- /* @var WP_Error $error */
5720
- $errors->add( $error->get_error_code(), $error->get_error_message() );
5721
- }
5722
-
5723
- return $errors;
5724
- }
5725
-
5726
  /**
5727
  * Get all defined addons that use this plugin
5728
  *
@@ -5838,46 +4262,6 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
5838
  $this->render_view( 'notice', $notice );
5839
  }
5840
 
5841
- /**
5842
- * Remove 'filesize' from attachment's metatdata if appropriate, also our total filesize record.
5843
- *
5844
- * @param integer $post_id Attachment's post_id.
5845
- * @param array $data Attachment's metadata.
5846
- * @param bool $update_metadata Update the metadata record now? Defaults to true.
5847
- *
5848
- * @return array Attachment's cleaned up metadata.
5849
- */
5850
- public function maybe_cleanup_filesize_metadata( $post_id, $data, $update_metadata = true ) {
5851
- if ( ! is_int( $post_id ) || empty( $post_id ) || empty( $data ) || ! is_array( $data ) ) {
5852
- return $data;
5853
- }
5854
-
5855
- /*
5856
- * Audio and video have a filesize added to metadata by default, but images and anything else don't.
5857
- * Note: Could have used `wp_generate_attachment_metadata` here to test whether default metadata has 'filesize',
5858
- * but it not only has side effects it also does a lot of work considering it's not a huge deal for this entry to hang around.
5859
- */
5860
- if (
5861
- empty( $data['mime_type'] ) ||
5862
- 0 === strpos( $data['mime_type'], 'image/' ) ||
5863
- ! ( 0 === strpos( $data['mime_type'], 'audio/' ) || 0 === strpos( $data['mime_type'], 'video/' ) )
5864
- ) {
5865
- unset( $data['filesize'] );
5866
- }
5867
-
5868
- if ( $update_metadata ) {
5869
- if ( empty( $data ) ) {
5870
- delete_post_meta( $post_id, '_wp_attachment_metadata' );
5871
- } else {
5872
- update_post_meta( $post_id, '_wp_attachment_metadata', $data );
5873
- }
5874
- }
5875
-
5876
- delete_post_meta( $post_id, 'as3cf_filesize_total' );
5877
-
5878
- return $data;
5879
- }
5880
-
5881
  /**
5882
  * Is there an upgrade in progress?
5883
  *
@@ -5903,4 +4287,87 @@ class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
5903
 
5904
  return false;
5905
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5906
  }
1
  <?php
2
 
3
+ use DeliciousBrains\WP_Offload_Media\Integrations\Core as Core_Integration;
4
+ use DeliciousBrains\WP_Offload_Media\Integrations\Integration_Manager;
5
+ use DeliciousBrains\WP_Offload_Media\Integrations\Media_Library as Media_Library_Integration;
6
+ use DeliciousBrains\WP_Offload_Media\Items\Download_Handler;
7
  use DeliciousBrains\WP_Offload_Media\Items\Item;
8
+ use DeliciousBrains\WP_Offload_Media\Items\Item_Handler;
9
+ use DeliciousBrains\WP_Offload_Media\Items\Media_Library_Item;
10
+ use DeliciousBrains\WP_Offload_Media\Items\Remove_Local_Handler;
11
+ use DeliciousBrains\WP_Offload_Media\Items\Remove_Provider_Handler;
12
+ use DeliciousBrains\WP_Offload_Media\Items\Upload_Handler;
13
  use DeliciousBrains\WP_Offload_Media\Providers\Delivery\Another_CDN;
14
  use DeliciousBrains\WP_Offload_Media\Providers\Delivery\AWS_CloudFront;
15
  use DeliciousBrains\WP_Offload_Media\Providers\Delivery\Cloudflare;
31
  use DeliciousBrains\WP_Offload_Media\Upgrades\Upgrade_EDD_Replace_URLs;
32
  use DeliciousBrains\WP_Offload_Media\Upgrades\Upgrade_File_Sizes;
33
  use DeliciousBrains\WP_Offload_Media\Upgrades\Upgrade_Filter_Post_Excerpt;
34
+ use DeliciousBrains\WP_Offload_Media\Upgrades\Upgrade_Item_Extra_Data;
35
  use DeliciousBrains\WP_Offload_Media\Upgrades\Upgrade_Items_Table;
36
  use DeliciousBrains\WP_Offload_Media\Upgrades\Upgrade_Meta_WP_Error;
37
  use DeliciousBrains\WP_Offload_Media\Upgrades\Upgrade_Region_Meta;
38
+ use DeliciousBrains\WP_Offload_Media\Upgrades\Upgrade_Tools_Errors;
39
  use DeliciousBrains\WP_Offload_Media\Upgrades\Upgrade_WPOS3_To_AS3CF;
40
 
41
  class Amazon_S3_And_CloudFront extends AS3CF_Plugin_Base {
130
  */
131
  protected static $delivery_provider_classes = array();
132
 
133
+ /**
134
+ * @var Item_Handler[]
135
+ */
136
+ protected $item_handlers = array();
137
+
138
  /**
139
  * @var AS3CF_Plugin_Compatibility
140
  */
152
  'WPOS3_SETTINGS',
153
  );
154
 
155
+ /**
156
+ * Class map to determine Item subclass per item source type
157
+ *
158
+ * @var string[]
159
+ */
160
+ private $source_type_classes = array(
161
+ 'media-library' => 'DeliciousBrains\WP_Offload_Media\Items\Media_Library_Item',
162
+ );
163
+
164
+ /**
165
+ * @var Integration_Manager
166
+ */
167
+ protected $integration_manager;
168
+
169
+ const LATEST_UPGRADE_ROUTINE = 10;
170
 
171
  /**
172
  * @param string $plugin_file_path
174
  *
175
  * @throws Exception
176
  */
177
+ public function __construct( $plugin_file_path, $slug = null ) {
178
  $this->plugin_slug = ( is_null( $slug ) ) ? 'amazon-s3-and-cloudfront' : $slug;
179
 
180
  parent::__construct( $plugin_file_path );
191
  *
192
  * @throws Exception
193
  */
194
+ public function init( $plugin_file_path ) {
195
  $this->plugin_title = __( 'Offload Media Lite', 'amazon-s3-and-cloudfront' );
196
  $this->plugin_menu_title = __( 'Offload Media Lite', 'amazon-s3-and-cloudfront' );
197
 
215
  Storage::get_provider_key_name() => 'DeliciousBrains\WP_Offload_Media\Providers\Delivery\Storage',
216
  ) );
217
 
 
 
218
  $this->set_storage_provider();
219
  $this->set_delivery_provider();
220
 
229
  new Upgrade_Filter_Post_Excerpt( $this );
230
  new Upgrade_WPOS3_To_AS3CF( $this );
231
  new Upgrade_Items_Table( $this );
232
+ new Upgrade_Tools_Errors( $this );
233
+ new Upgrade_Item_Extra_Data( $this );
234
 
235
  // Plugin setup
236
  add_action( 'admin_menu', array( $this, 'admin_menu' ) );
239
  add_filter( 'plugin_action_links', array( $this, 'plugin_actions_settings_link' ), 10, 2 );
240
  add_filter( 'network_admin_plugin_action_links', array( $this, 'plugin_actions_settings_link' ), 10, 2 );
241
  add_filter( 'pre_get_space_used', array( $this, 'multisite_get_space_used' ) );
242
+
243
  // display a notice when either lite or pro is automatically deactivated
244
  add_action( 'pre_current_active_plugins', array( $this, 'plugin_deactivated_notice' ) );
245
  add_action( 'as3cf_plugin_load', array( $this, 'remove_access_keys_if_constants_set' ) );
246
 
 
 
 
 
 
247
  // UI AJAX
248
  add_action( 'wp_ajax_as3cf-get-buckets', array( $this, 'ajax_get_buckets' ) );
249
  add_action( 'wp_ajax_as3cf-get-url-preview', array( $this, 'ajax_get_url_preview' ) );
 
250
  add_action( 'wp_ajax_as3cf-get-diagnostic-info', array( $this, 'ajax_get_diagnostic_info' ) );
251
 
252
+ // Enable integrations once everything has been initialized.
253
+ add_action( 'as3cf_init', array( $this, 'enable_integrations' ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
 
255
  // Listen for settings changes
256
  if ( false !== static::settings_constant() ) {
276
  $this->register_delivery_provider_assets();
277
  }
278
 
279
+ /**
280
+ * Enable integrations.
281
+ *
282
+ * @param Amazon_S3_And_CloudFront $as3cf
283
+ */
284
+ public function enable_integrations( $as3cf ) {
285
+ /**
286
+ * Filters which integrations to enable. To disable an integration
287
+ * implement this filter and unset all unwanted integrations from
288
+ * the array.
289
+ *
290
+ * @param array $integrations Associative array of integrations
291
+ */
292
+ $integrations = apply_filters( 'as3cf_integrations', array(
293
+ 'core' => new Core_Integration( $as3cf ),
294
+ 'mlib' => new Media_Library_Integration( $as3cf ),
295
+ ) );
296
+
297
+ $this->integration_manager = Integration_Manager::get_instance();
298
+
299
+ foreach ( $integrations as $integration_key => $integration ) {
300
+ $this->integration_manager->register_integration( $integration_key, $integration );
301
+ }
302
+ }
303
+
304
  /**
305
  * @return Storage_Provider
306
  */
453
  return empty( $class ) ? __( 'Unknown', 'amazon-s3-and-cloudfront' ) : $class::get_provider_service_name();
454
  }
455
 
456
+ /**
457
+ * Getter for the Integrations Manager instance
458
+ *
459
+ * @return Integration_Manager
460
+ */
461
+ public function get_integration_manager() {
462
+ return $this->integration_manager;
463
+ }
464
+
465
  /**
466
  * Get the plugin title to be used in page headings
467
  *
1090
  *
1091
  * @return array
1092
  */
1093
+ public function get_allowed_mime_types() {
1094
+ /**
1095
+ * Filters list of allowed mime types and file extensions for uploading
1096
+ *
1097
+ * @param array $types Mime types keyed by the file extension regex corresponding to those types.
1098
+ */
1099
  return apply_filters( 'as3cf_allowed_mime_types', get_allowed_mime_types() );
1100
  }
1101
 
1173
  *
1174
  * @return string
1175
  */
1176
+ public function get_url_preview( $escape = true, $suffix = 'photo.jpg' ) {
1177
  $as3cf_item = new Media_Library_Item(
1178
  $this->get_storage_provider()->get_provider_key_name(),
1179
  $this->get_setting( 'region' ),
1180
  $this->get_setting( 'bucket' ),
1181
+ AS3CF_Utils::trailingslash_prefix( $this->get_simple_file_prefix() ) . $suffix,
1182
  false,
1183
  null,
1184
+ AS3CF_Utils::trailingslash_prefix( $this->get_simple_file_prefix() ) . $suffix
1185
  );
1186
 
1187
+ $url = $as3cf_item->get_provider_url();
1188
 
1189
  if ( is_wp_error( $url ) ) {
1190
  return '';
1248
  }
1249
 
1250
  /**
1251
+ * Get the object versioning string prefix
 
 
 
 
 
 
 
 
1252
  *
1253
+ * @return string
1254
  */
1255
+ function get_object_version_string() {
1256
+ if ( $this->get_setting( 'use-yearmonth-folders' ) ) {
1257
+ $date_format = 'dHis';
1258
+ } else {
1259
+ $date_format = 'YmdHis';
1260
+ }
1261
 
1262
+ // Use current time so that object version is unique
1263
+ $time = current_time( 'timestamp' );
 
 
 
 
 
 
 
 
 
1264
 
1265
+ $object_version = date( $date_format, $time ) . '/';
1266
+ $object_version = apply_filters( 'as3cf_get_object_version_string', $object_version );
1267
 
1268
+ return $object_version;
1269
  }
1270
 
1271
  /**
1272
+ * Does file exist
1273
  *
1274
+ * @param string $filename
1275
+ * @param string $time
1276
+ *
1277
+ * @return bool
 
 
 
1278
  */
1279
+ function does_file_exist( $filename, $time ) {
1280
+ if ( $this->does_file_exist_local( $filename, $time ) ) {
1281
+ return true;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1282
  }
1283
 
1284
+ if ( ! $this->get_setting( 'object-versioning' ) && $this->does_file_exist_provider( $filename, $time ) ) {
1285
+ return true;
 
 
 
 
 
1286
  }
1287
 
1288
+ return false;
 
1289
  }
1290
 
1291
  /**
1292
+ * Does file exist local
1293
+ *
1294
+ * @param string $filename
1295
+ * @param string $time
1296
  *
1297
+ * @return bool
 
 
1298
  */
1299
+ function does_file_exist_local( $filename, $time ) {
1300
+ global $wpdb;
 
 
1301
 
1302
+ $path = wp_upload_dir( $time );
1303
+ $path = ltrim( $path['subdir'], '/' );
1304
 
1305
+ if ( '' !== $path ) {
1306
+ $path = trailingslashit( $path );
1307
  }
1308
+ $file = $path . $filename;
1309
 
1310
+ // WordPress doesn't check its own basic record, so we will.
1311
+ $sql = $wpdb->prepare( "
1312
+ SELECT COUNT(*)
1313
+ FROM $wpdb->postmeta
1314
+ WHERE meta_key = %s
1315
+ AND meta_value = %s
1316
+ ", '_wp_attached_file', $file );
1317
+
1318
+ if ( (bool) $wpdb->get_var( $sql ) ) {
1319
+ return true;
1320
  }
1321
 
1322
+ // Check our records of local source path as it also covers original_image.
1323
+ if ( ! empty( Media_Library_Item::get_by_source_path( array( $file ), array(), true, true ) ) ) {
1324
+ return true;
1325
+ }
1326
 
1327
+ return false;
1328
  }
1329
 
1330
  /**
1331
+ * Does file exist provider
 
1332
  *
1333
+ * @param string $filename
1334
+ * @param string $time
1335
  *
1336
+ * @return bool
1337
  * @throws Exception
1338
  */
1339
+ function does_file_exist_provider( $filename, $time ) {
1340
+ $bucket = $this->get_setting( 'bucket' );
1341
+ $region = $this->get_setting( 'region' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1342
 
1343
+ if ( is_wp_error( $region ) ) {
1344
+ return false;
 
 
1345
  }
1346
 
1347
+ $provider_client = $this->get_provider_client( $region );
1348
+ $prefix = AS3CF_Utils::trailingslash_prefix( $this->get_object_prefix() );
1349
+ $prefix .= AS3CF_Utils::trailingslash_prefix( $this->get_dynamic_prefix( $time ) );
 
 
 
1350
 
1351
+ return $provider_client->does_object_exist( $bucket, $prefix . $filename );
1352
  }
1353
 
1354
  /**
1355
+ * Generate unique filename
1356
  *
1357
+ * @param string $name
1358
+ * @param string $ext
1359
+ * @param string $time
 
 
 
1360
  *
1361
+ * @return string
 
1362
  */
1363
+ function generate_unique_filename( $name, $ext, $time ) {
1364
+ $count = 1;
1365
+ $filename = $name . '-' . $count . $ext;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1366
 
1367
+ while ( $this->does_file_exist( $filename, $time ) ) {
1368
+ $count++;
1369
+ $filename = $name . '-' . $count . $ext;
1370
  }
1371
 
1372
+ return $filename;
1373
+ }
 
1374
 
1375
+ /**
1376
+ * Check the plugin is correctly setup
1377
+ *
1378
+ * @param bool $with_credentials Do provider credentials need to be set up too? Defaults to false.
1379
+ *
1380
+ * @return bool
1381
+ *
1382
+ * TODO: Performance - cache / static var by param.
1383
+ */
1384
+ function is_plugin_setup( $with_credentials = false ) {
1385
+ if ( $with_credentials && $this->get_storage_provider()->needs_access_keys() ) {
1386
+ // AWS not configured
1387
+ return false;
1388
  }
1389
 
1390
+ if ( false === (bool) $this->get_setting( 'bucket' ) ) {
1391
+ // No bucket selected
1392
+ return false;
 
 
 
1393
  }
1394
 
1395
+ if ( is_wp_error( $this->get_setting( 'region' ) ) ) {
1396
+ // Region error when retrieving bucket location
1397
+ return false;
1398
  }
1399
 
1400
+ // All good, let's do this
1401
+ return true;
1402
+ }
1403
 
1404
+ /**
1405
+ * Return the scheme to be used in URLs
1406
+ *
1407
+ * @param bool|null $use_ssl
1408
+ *
1409
+ * @return string
1410
+ */
1411
+ function get_url_scheme( $use_ssl = null ) {
1412
+ if ( $this->use_ssl( $use_ssl ) ) {
1413
+ $scheme = 'https';
1414
+ } else {
1415
+ $scheme = 'http';
1416
  }
1417
 
1418
+ return $scheme;
1419
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1420
 
1421
+ /**
1422
+ * Determine when to use https in URLS
1423
+ *
1424
+ * @param bool|null $use_ssl
1425
+ *
1426
+ * @return bool
1427
+ */
1428
+ public function use_ssl( $use_ssl = null ) {
1429
+ if ( is_ssl() ) {
1430
+ $use_ssl = true;
1431
  }
1432
 
1433
+ if ( ! is_bool( $use_ssl ) ) {
1434
+ $use_ssl = $this->get_setting( 'force-https' );
 
 
 
 
 
 
 
1435
  }
1436
 
1437
+ if ( empty( $use_ssl ) ) {
1438
+ $use_ssl = false;
 
 
 
 
 
 
 
 
 
 
 
 
1439
  }
1440
 
1441
+ return apply_filters( 'as3cf_use_ssl', $use_ssl );
1442
+ }
 
 
 
 
 
 
 
1443
 
1444
+ /**
1445
+ * Get the custom object prefix if enabled
1446
+ *
1447
+ * @param string $toggle_setting
1448
+ *
1449
+ * @return string
1450
+ */
1451
+ function get_object_prefix( $toggle_setting = 'enable-object-prefix' ) {
1452
+ if ( $this->get_setting( $toggle_setting ) ) {
1453
+ return trailingslashit( trim( $this->get_setting( 'object-prefix' ) ) );
1454
  }
1455
 
1456
+ return '';
1457
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1458
 
1459
  /**
1460
  * Is attachment served by provider.
1467
  *
1468
  * @return bool|Media_Library_Item
1469
  */
1470
+ public function is_attachment_served_by_provider( $attachment_id, $skip_rewrite_check = false, $skip_current_provider_check = false, Storage_Provider $provider = null, $check_is_verified = false ) {
1471
+ if ( ! $skip_rewrite_check && ! $this->get_setting( 'serve-from-s3' ) ) {
1472
+ // Not serving provider URLs
1473
+ return false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1474
  }
1475
 
1476
  $as3cf_item = Media_Library_Item::get_by_source_id( $attachment_id );
1477
 
1478
  if ( ! $as3cf_item ) {
1479
+ // File not uploaded to a provider
1480
+ return false;
1481
  }
1482
 
1483
+ if ( ! $skip_rewrite_check && ! empty( $check_is_verified ) && ! $as3cf_item->is_verified() ) {
1484
+ // Offload not verified, treat as not offloaded.
1485
+ return false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1486
  }
1487
 
1488
+ if ( ! $skip_current_provider_check && empty( $provider ) ) {
1489
+ $provider = $this->get_storage_provider();
1490
+ }
1491
 
1492
+ if ( ! empty( $provider ) && $provider::get_provider_key_name() !== $as3cf_item->provider() ) {
1493
+ // File not uploaded to required provider
1494
+ return false;
1495
+ }
1496
 
1497
+ return $as3cf_item;
1498
  }
1499
 
1500
  /**
1734
  /**
1735
  * Add the settings page to the top-level AWS menu item for backwards compatibility.
1736
  *
1737
+ * @param Amazon_Web_Services $aws Plugin class instance from the amazon-web-services plugin.
1738
  */
1739
  public function aws_admin_menu( $aws ) {
1740
  $aws->add_page(
1949
  return self::$buckets_check[ $bucket ];
1950
  }
1951
 
1952
+ $key = $this->get_simple_file_prefix() . 'as3cf-permission-check.txt';
1953
  $file_contents = __( 'This is a test file to check if the user has write permission to the bucket. Delete me if found.', 'amazon-s3-and-cloudfront' );
1954
 
1955
  $can_write = $this->get_provider_client( $region, true )->can_write( $bucket, $key, $file_contents );
1967
  return $can_write;
1968
  }
1969
 
1970
+ /**
1971
+ * Get the file prefix for test and display purposes.
1972
+ *
1973
+ * Note: This should only be used for "naive" prefix calculations for
1974
+ * display and write permission test purposes
1975
+ *
1976
+ * @param null|string $time
1977
+ * @param bool $object_versioning_allowed Can an Object Versioning string be appended if setting turned on? Default true.
1978
+ *
1979
+ * @return string
1980
+ */
1981
+ private function get_simple_file_prefix( $time = null, $object_versioning_allowed = true ) {
1982
+ $prefix = AS3CF_Utils::trailingslash_prefix( $this->get_object_prefix() );
1983
+ $prefix .= AS3CF_Utils::trailingslash_prefix( $this->get_dynamic_prefix( $time ) );
1984
+
1985
+ if ( ! empty( $object_versioning_allowed ) && $this->get_setting( 'object-versioning' ) ) {
1986
+ $prefix .= AS3CF_Utils::trailingslash_prefix( $this->get_object_version_string() );
1987
+ }
1988
+
1989
+ return $prefix;
1990
+ }
1991
+
1992
  /**
1993
  * Render error messages in a view for bucket permission and access issues
1994
  *
2698
  * year month subdirectory setting and just uses S3 setting
2699
  *
2700
  * @param string $time
2701
+ * @param bool $can_use_yearmonth
2702
  *
2703
  * @return string
2704
  */
2705
+ public function get_dynamic_prefix( $time = null, $can_use_yearmonth = true ) {
2706
  $prefix = '';
2707
  $subdir = '';
2708
 
2745
  }
2746
  }
2747
 
2748
+ if ( $this->get_setting( 'use-yearmonth-folders' ) && $can_use_yearmonth ) {
2749
  $subdir = $this->get_year_month_directory_name( $time );
2750
  $prefix .= $subdir;
2751
  }
2842
  * @param Media_Library_Item $as3cf_item
2843
  * @param string|null $size
2844
  */
2845
+ public function make_acl_admin_notice( Media_Library_Item $as3cf_item, $size = null ) {
2846
  $filename = wp_basename( $as3cf_item->path( $size ) );
2847
+ $acl = $as3cf_item->is_private( $size ) ? $this->get_storage_provider()->get_private_acl() : $this->get_storage_provider()->get_default_acl();
2848
  $acl_name = $this->get_acl_display_name( $acl );
2849
  $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>" );
2850
 
3732
  * Used to give a realistic total of storage space used on a Multisite subsite,
3733
  * when there have been attachments uploaded to S3 but removed from server
3734
  *
3735
+ * @param bool $space_used
3736
  *
3737
  * @return float|int
3738
  */
3822
  foreach ( $table_prefixes as $blog_id => $table_prefix ) {
3823
  $this->switch_to_blog( $blog_id );
3824
 
3825
+ foreach ( $this->get_source_type_classes() as $class ) {
3826
+ $counts = $class::count_items( $skip_transient, $force );
3827
+ $total += $counts['total'];
3828
+ $offloaded += $counts['offloaded'];
3829
+ $not_offloaded += $counts['not_offloaded'];
3830
+ }
3831
 
3832
  $this->restore_current_blog();
3833
  }
3986
  return filter_input( $type, $variable, $filter, $options );
3987
  }
3988
 
 
 
 
 
 
 
 
 
 
 
 
3989
  /**
3990
  * Upgrade the 'virtual host' / 'bucket as domain' setting to the
3991
  * new CloudFront / Domain setting
4051
  return $path;
4052
  }
4053
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4054
  /**
4055
  * Maybe remove query string from URL.
4056
  *
4064
  return reset( $parts );
4065
  }
4066
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4067
  /**
4068
  * Ensure local URL is correct for multisite's non-primary subsites.
4069
  *
4104
  $acl = $this->get_storage_provider()->get_default_acl();
4105
 
4106
  if ( ! empty( $as3cf_item ) ) {
4107
+ $acl = $as3cf_item->is_private( $size ) ? $this->get_storage_provider()->get_private_acl() : $this->get_storage_provider()->get_default_acl();
4108
  }
4109
  }
4110
 
4114
  /**
4115
  * Are ACLs in use for intermediate size on bucket?
4116
  *
4117
+ * @param int $attachment_id
4118
+ * @param string $size
4119
+ * @param string $bucket Optional bucket that ACL is potentially to be used with.
4120
+ * @param Item|null $as3cf_item Optional item.
4121
  *
4122
  * @return bool
4123
  */
4124
+ public function use_acl_for_intermediate_size( $attachment_id, $size, $bucket = null, Item $as3cf_item = null ) {
4125
+ // If this function is used without passing in an Item object, we're assuming $attachment
4126
  if ( empty( $as3cf_item ) ) {
4127
  $as3cf_item = Media_Library_Item::get_by_source_id( $attachment_id );
4128
  }
4139
  $use_private_prefix = apply_filters( 'as3cf_enable_signed_urls_for_intermediate_size', $this->private_prefix_enabled(), $attachment_id, $size, $bucket, $as3cf_item );
4140
 
4141
  // If signed custom URLs are in play, and we have a private object, usually you can not use ACLs.
4142
+ if ( $use_acl && $use_private_prefix && ! empty( $as3cf_item ) && $as3cf_item->is_private( $size ) ) {
4143
  $use_acl = false;
4144
  }
4145
 
4147
  return apply_filters( 'as3cf_use_acl_for_intermediate_size', $use_acl, $attachment_id, $size, $bucket, $as3cf_item );
4148
  }
4149
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4150
  /**
4151
  * Get all defined addons that use this plugin
4152
  *
4262
  $this->render_view( 'notice', $notice );
4263
  }
4264
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4265
  /**
4266
  * Is there an upgrade in progress?
4267
  *
4287
 
4288
  return false;
4289
  }
4290
+
4291
+ /**
4292
+ * Register an item type name and class
4293
+ *
4294
+ * @param string $source_type
4295
+ * @param string $class
4296
+ */
4297
+ public function register_source_type( $source_type, $class ) {
4298
+ $this->source_type_classes[ $source_type ] = $class;
4299
+ }
4300
+
4301
+ /**
4302
+ * Get Item type class from item type identifier
4303
+ *
4304
+ * @param string $source_type
4305
+ *
4306
+ * @return string|false
4307
+ */
4308
+ public function get_source_type_class( $source_type ) {
4309
+ if ( isset( $this->source_type_classes[ $source_type ] ) ) {
4310
+ return $this->source_type_classes[ $source_type ];
4311
+ }
4312
+
4313
+ return false;
4314
+ }
4315
+
4316
+ /**
4317
+ * Get Item type human friendly name item type identifier
4318
+ *
4319
+ * @param string $source_type
4320
+ *
4321
+ * @return string|false
4322
+ */
4323
+ public function get_source_type_name( $source_type ) {
4324
+ /** @var Item $class */
4325
+ $class = $this->get_source_type_class( $source_type );
4326
+ if ( ! empty( $class ) ) {
4327
+ return $class::source_type_name();
4328
+ }
4329
+
4330
+ return false;
4331
+ }
4332
+
4333
+ /**
4334
+ * Get all registered Item classes
4335
+ *
4336
+ * @return array
4337
+ */
4338
+ public function get_source_type_classes() {
4339
+ return $this->source_type_classes;
4340
+ }
4341
+
4342
+ /**
4343
+ * Returns the Item_Handler instance for the given handler type.
4344
+ *
4345
+ * @param string $handler_type
4346
+ *
4347
+ * @return Item_Handler
4348
+ */
4349
+ public function get_item_handler( $handler_type ) {
4350
+ if ( isset( $this->item_handlers[ $handler_type ] ) ) {
4351
+ return $this->item_handlers[ $handler_type ];
4352
+ }
4353
+
4354
+ switch ( $handler_type ) {
4355
+ case Upload_Handler::get_item_handler_key_name():
4356
+ $this->item_handlers[ $handler_type ] = new Upload_Handler( $this );
4357
+ break;
4358
+ case Download_Handler::get_item_handler_key_name():
4359
+ $this->item_handlers[ $handler_type ] = new Download_Handler( $this );
4360
+ break;
4361
+ case Remove_Local_Handler::get_item_handler_key_name():
4362
+ $this->item_handlers[ $handler_type ] = new Remove_Local_Handler( $this );
4363
+ break;
4364
+ case Remove_Provider_Handler::get_item_handler_key_name():
4365
+ $this->item_handlers[ $handler_type ] = new Remove_Provider_Handler( $this );
4366
+ break;
4367
+ default:
4368
+ return null;
4369
+ }
4370
+
4371
+ return $this->item_handlers[ $handler_type ];
4372
+ }
4373
  }
classes/as3cf-filter.php CHANGED
@@ -1,5 +1,8 @@
1
  <?php
2
 
 
 
 
3
  abstract class AS3CF_Filter {
4
 
5
  /**
@@ -17,21 +20,20 @@ abstract class AS3CF_Filter {
17
  */
18
  const OPTION_CACHE_GROUP = 'option_amazonS3_cache';
19
 
 
 
 
 
 
20
  /**
21
  * @var Amazon_S3_And_CloudFront
22
  */
23
  protected $as3cf;
24
-
25
  /**
26
  * @var array
27
  */
28
  protected $query_cache = array();
29
 
30
- /**
31
- * @var array IDs which have already been purged this request.
32
- */
33
- protected static $purged_ids = array();
34
-
35
  /**
36
  * Constructor
37
  *
@@ -72,7 +74,11 @@ abstract class AS3CF_Filter {
72
  }
73
 
74
  foreach ( $value as $key => $attachment ) {
75
- $url = $this->get_url( $attachment['attachment_id'] );
 
 
 
 
76
 
77
  if ( $url ) {
78
  $value[ $key ]['file'] = $url;
@@ -113,7 +119,11 @@ abstract class AS3CF_Filter {
113
  * @return stdClass
114
  */
115
  public function filter_header_image_data( $value, $old_value = false ) {
116
- $url = $this->get_url( $value->attachment_id );
 
 
 
 
117
 
118
  if ( $url ) {
119
  $value->url = $url;
@@ -148,16 +158,17 @@ abstract class AS3CF_Filter {
148
  /**
149
  * Handle widget instances.
150
  *
151
- * @param array $instance
152
- * @param WP_Widget $class
153
  *
154
  * @return array
155
  */
156
- protected function handle_widget( $instance, $class ) {
157
- if ( empty( $instance ) ) {
158
  return $instance;
159
  }
160
 
 
 
161
  $update_cache = true;
162
 
163
  // Editing widgets in Customizer throws an error if more than one option record is updated.
@@ -166,39 +177,12 @@ abstract class AS3CF_Filter {
166
  $update_cache = false;
167
  }
168
 
169
- if ( $class instanceof WP_Widget_Media ) {
170
- return $this->filter_media_widget( $instance, $update_cache );
171
- }
172
-
173
- if ( $class instanceof WP_Widget_Text ) {
174
- return $this->filter_text_widget( $instance, $update_cache );
175
- }
176
-
177
- if ( $class instanceof WP_Widget_Custom_HTML ) {
178
- return $this->filter_custom_html_widget( $instance, $update_cache );
179
- }
180
-
181
- return $instance;
182
- }
183
-
184
- /**
185
- * Filter media widget.
186
- *
187
- * @param array $instance
188
- * @param bool $update_cache
189
- *
190
- * @return array
191
- */
192
- protected function filter_media_widget( $instance, $update_cache ) {
193
- $cache = $this->get_option_cache();
194
- $to_cache = array();
195
-
196
  foreach ( $instance as $key => $value ) {
197
  if ( empty( $value ) ) {
198
  continue;
199
  }
200
 
201
- if ( AS3CF_Utils::is_url( $value ) ) {
202
  $instance[ $key ] = $this->process_content( $value, $cache, $to_cache );
203
  }
204
  }
@@ -210,46 +194,6 @@ abstract class AS3CF_Filter {
210
  return $instance;
211
  }
212
 
213
- /**
214
- * Filter text widget.
215
- *
216
- * @param array $instance
217
- * @param bool $update_cache
218
- *
219
- * @return array
220
- */
221
- protected function filter_text_widget( $instance, $update_cache ) {
222
- $cache = $this->get_option_cache();
223
- $to_cache = array();
224
- $instance['text'] = $this->process_content( $instance['text'], $cache, $to_cache );
225
-
226
- if ( $update_cache ) {
227
- $this->maybe_update_option_cache( $to_cache );
228
- }
229
-
230
- return $instance;
231
- }
232
-
233
- /**
234
- * Filter custom html widget.
235
- *
236
- * @param array $instance
237
- * @param bool $update_cache
238
- *
239
- * @return array
240
- */
241
- protected function filter_custom_html_widget( $instance, $update_cache ) {
242
- $cache = $this->get_option_cache();
243
- $to_cache = array();
244
- $instance['content'] = $this->process_content( $instance['content'], $cache, $to_cache );
245
-
246
- if ( $update_cache ) {
247
- $this->maybe_update_option_cache( $to_cache );
248
- }
249
-
250
- return $instance;
251
- }
252
-
253
  /**
254
  * Process content.
255
  *
@@ -326,8 +270,8 @@ abstract class AS3CF_Filter {
326
  return $url_pairs;
327
  }
328
 
329
- $matches = array_unique( $matches[0] );
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] ) ) {
@@ -349,26 +293,29 @@ abstract class AS3CF_Filter {
349
 
350
  $url = AS3CF_Utils::reduce_url( $url );
351
 
352
- $attachment_ids[ $url ] = absint( $class_id[1] );
 
 
 
353
  }
354
 
355
- if ( count( $attachment_ids ) > 1 ) {
356
  /*
357
  * Warm object cache for use with 'get_post_meta()'.
358
  *
359
  * To avoid making a database call for each image, a single query
360
  * warms the object cache with the meta information for all images.
361
  */
362
- update_meta_cache( 'post', array_unique( array_values( $attachment_ids ) ) );
363
  }
364
 
365
- foreach ( $attachment_ids as $url => $attachment_id ) {
366
- if ( ! $this->attachment_id_matches_src( $attachment_id, $url ) ) {
367
  // Path doesn't match attachment, skip
368
  continue;
369
  }
370
 
371
- $this->push_to_url_pairs( $url_pairs, $attachment_id, $url, $to_cache );
372
  }
373
 
374
  return $url_pairs;
@@ -418,46 +365,46 @@ abstract class AS3CF_Filter {
418
  continue;
419
  }
420
 
421
- $attachment_id = null;
422
- $bare_url = AS3CF_Utils::reduce_url( $url );
423
 
424
  // If attachment ID recently or previously cached, skip full search.
425
  if ( isset( $to_cache[ $bare_url ] ) ) {
426
- $attachment_id = $to_cache[ $bare_url ];
427
 
428
- if ( $this->is_failure( $attachment_id ) ) {
429
  // Attachment ID failure, continue
430
  continue;
431
  }
432
  } elseif ( isset( $cache[ $bare_url ] ) ) {
433
- $attachment_id = $cache[ $bare_url ];
434
 
435
- if ( $this->is_failure( $attachment_id ) ) {
436
  // Attachment ID failure, continue
437
  continue;
438
  }
439
  }
440
 
441
- if ( is_null( $attachment_id ) || is_array( $attachment_id ) ) {
442
  // Attachment ID not cached, need to search by URL.
443
  $urls[] = $bare_url;
444
  } else {
445
- $this->push_to_url_pairs( $url_pairs, $attachment_id, $bare_url, $to_cache );
446
  }
447
  }
448
 
449
  if ( ! empty( $urls ) ) {
450
- $attachments = $this->get_attachment_ids_from_urls( $urls );
451
 
452
- foreach ( $attachments as $url => $attachment_id ) {
453
- if ( ! $attachment_id ) {
454
- // Can't determine attachment ID, continue
455
  $this->url_cache_failure( $url, $to_cache );
456
 
457
  continue;
458
  }
459
 
460
- $this->push_to_url_pairs( $url_pairs, $attachment_id, $url, $to_cache );
461
  }
462
  }
463
 
@@ -467,7 +414,7 @@ abstract class AS3CF_Filter {
467
  /**
468
  * Is failure?
469
  *
470
- * @param mixed $value
471
  *
472
  * @return bool
473
  */
@@ -488,20 +435,23 @@ abstract class AS3CF_Filter {
488
  /**
489
  * Does attachment ID match src?
490
  *
491
- * @param int $attachment_id
492
  * @param string $url
493
  *
494
  * @return bool
495
  */
496
- protected function attachment_id_matches_src( $attachment_id, $url ) {
497
- $meta = get_post_meta( $attachment_id, '_wp_attachment_metadata', true );
 
 
 
498
 
499
  if ( ! isset( $meta['sizes'] ) ) {
500
  // No sizes found, return
501
  return false;
502
  }
503
 
504
- $base_url = AS3CF_Utils::encode_filename_in_path( AS3CF_Utils::reduce_url( $this->get_base_url( $attachment_id ) ) );
505
  $basename = wp_basename( $base_url );
506
 
507
  // Add full size URL
@@ -526,31 +476,31 @@ abstract class AS3CF_Filter {
526
  * Push to URL pairs.
527
  *
528
  * @param array $url_pairs
529
- * @param int $attachment_id
530
  * @param string $find
531
  * @param array $to_cache
532
  */
533
- protected function push_to_url_pairs( &$url_pairs, $attachment_id, $find, &$to_cache ) {
534
  $find_full = AS3CF_Utils::remove_size_from_filename( $find );
535
  $find_full = $this->normalize_find_value( $this->as3cf->maybe_remove_query_string( $find_full ) );
536
  $find_size = $this->normalize_find_value( $this->as3cf->maybe_remove_query_string( $find ) );
537
 
538
  // Cache find URLs even if no replacement.
539
- $to_cache[ $find_full ] = $attachment_id;
540
 
541
  if ( wp_basename( $find_full ) !== wp_basename( $find_size ) ) {
542
- $to_cache[ $find_size ] = $attachment_id;
543
  }
544
 
545
- $replace_full = $this->get_url( $attachment_id );
546
 
547
  // Replacement URL can't be found.
548
  if ( ! $replace_full ) {
549
  return;
550
  }
551
 
552
- $size = $this->get_size_string_from_url( $attachment_id, $find );
553
- $replace_size = $this->get_url( $attachment_id, $size );
554
  $parts = parse_url( $find );
555
 
556
  if ( ! isset( $parts['scheme'] ) ) {
@@ -570,35 +520,24 @@ abstract class AS3CF_Filter {
570
  $replace_full = $this->as3cf->maybe_remove_query_string( $replace_full );
571
  $replace_size = $this->as3cf->maybe_remove_query_string( $replace_size );
572
 
573
- $to_cache[ $this->normalize_find_value( $replace_full ) ] = $attachment_id;
574
- $to_cache[ $this->normalize_find_value( $replace_size ) ] = $attachment_id;
575
  }
576
 
577
  /**
578
  * Get size string from URL.
579
  *
580
- * @param int $attachment_id
581
  * @param string $url
582
  *
583
  * @return null|string
584
  */
585
- public function get_size_string_from_url( $attachment_id, $url ) {
586
- $meta = get_post_meta( $attachment_id, '_wp_attachment_metadata', true );
587
-
588
- if ( empty( $meta['sizes'] ) ) {
589
- // No alternative sizes available, return
590
- return null;
591
- }
592
-
593
- $basename = AS3CF_Utils::encode_filename_in_path( wp_basename( $this->as3cf->maybe_remove_query_string( $url ) ) );
594
-
595
- foreach ( $meta['sizes'] as $size => $file ) {
596
- if ( $basename === AS3CF_Utils::encode_filename_in_path( $file['file'] ) ) {
597
- return $size;
598
- }
599
  }
600
 
601
- return null;
602
  }
603
 
604
  /**
@@ -659,11 +598,14 @@ abstract class AS3CF_Filter {
659
  /**
660
  * Get post cache
661
  *
662
- * @param null|int|WP_Post $post Optional. Post ID or post object. Defaults to current post.
 
 
663
  *
664
- * @return array
 
665
  */
666
- public function get_post_cache( $post = null ) {
667
  $post_id = AS3CF_Utils::get_post_id( $post );
668
 
669
  if ( ! $post_id ) {
@@ -680,6 +622,21 @@ abstract class AS3CF_Filter {
680
  $cache = array();
681
  }
682
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
683
  return $cache;
684
  }
685
 
@@ -731,7 +688,7 @@ abstract class AS3CF_Filter {
731
  return;
732
  }
733
 
734
- $cached = $this->get_post_cache( $post_id );
735
  $urls = static::merge_cache( $cached, $to_cache );
736
 
737
  if ( $urls !== $cached ) {
@@ -777,13 +734,17 @@ abstract class AS3CF_Filter {
777
  }
778
 
779
  /**
780
- * Purge attachment from cache on delete.
781
  *
782
  * @param int $post_id
783
  */
784
  public function purge_cache_on_attachment_delete( $post_id ) {
785
  if ( ! in_array( $post_id, self::$purged_ids ) ) {
786
- $this->purge_from_cache( $this->get_url( $post_id ) );
 
 
 
 
787
  self::$purged_ids[] = $post_id;
788
  }
789
  }
@@ -1044,30 +1005,30 @@ abstract class AS3CF_Filter {
1044
  /**
1045
  * Get URL.
1046
  *
1047
- * @param int $attachment_id
1048
- * @param null|string $size
1049
  *
1050
  * @return bool|string
1051
  */
1052
- abstract protected function get_url( $attachment_id, $size = null );
1053
 
1054
  /**
1055
  * Get base URL.
1056
  *
1057
- * @param int $attachment_id
1058
  *
1059
  * @return string|false
1060
  */
1061
- abstract protected function get_base_url( $attachment_id );
1062
 
1063
  /**
1064
  * Get attachment ID from URL.
1065
  *
1066
  * @param string $url
1067
  *
1068
- * @return bool|int
1069
  */
1070
- abstract protected function get_attachment_id_from_url( $url );
1071
 
1072
  /**
1073
  * Get attachment IDs from URLs.
@@ -1076,7 +1037,7 @@ abstract class AS3CF_Filter {
1076
  *
1077
  * @return array url => attachment ID (or false)
1078
  */
1079
- abstract protected function get_attachment_ids_from_urls( $urls );
1080
 
1081
  /**
1082
  * Normalize find value.
1
  <?php
2
 
3
+ use DeliciousBrains\WP_Offload_Media\Items\Item;
4
+ use DeliciousBrains\WP_Offload_Media\Items\Media_Library_Item;
5
+
6
  abstract class AS3CF_Filter {
7
 
8
  /**
20
  */
21
  const OPTION_CACHE_GROUP = 'option_amazonS3_cache';
22
 
23
+ /**
24
+ * @var array IDs which have already been purged this request.
25
+ */
26
+ protected static $purged_ids = array();
27
+
28
  /**
29
  * @var Amazon_S3_And_CloudFront
30
  */
31
  protected $as3cf;
 
32
  /**
33
  * @var array
34
  */
35
  protected $query_cache = array();
36
 
 
 
 
 
 
37
  /**
38
  * Constructor
39
  *
74
  }
75
 
76
  foreach ( $value as $key => $attachment ) {
77
+ $item_source = array(
78
+ 'id' => $attachment['attachment_id'],
79
+ 'source_type' => Media_Library_Item::source_type(),
80
+ );
81
+ $url = $this->get_url( $item_source );
82
 
83
  if ( $url ) {
84
  $value[ $key ]['file'] = $url;
119
  * @return stdClass
120
  */
121
  public function filter_header_image_data( $value, $old_value = false ) {
122
+ $item_source = array(
123
+ 'id' => $value->attachment_id,
124
+ 'source_type' => Media_Library_Item::source_type(),
125
+ );
126
+ $url = $this->get_url( $item_source );
127
 
128
  if ( $url ) {
129
  $value->url = $url;
158
  /**
159
  * Handle widget instances.
160
  *
161
+ * @param array $instance
 
162
  *
163
  * @return array
164
  */
165
+ protected function handle_widget( $instance ) {
166
+ if ( empty( $instance ) || ! is_array( $instance ) ) {
167
  return $instance;
168
  }
169
 
170
+ $cache = $this->get_option_cache();
171
+ $to_cache = array();
172
  $update_cache = true;
173
 
174
  // Editing widgets in Customizer throws an error if more than one option record is updated.
177
  $update_cache = false;
178
  }
179
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
  foreach ( $instance as $key => $value ) {
181
  if ( empty( $value ) ) {
182
  continue;
183
  }
184
 
185
+ if ( in_array( $key, array( 'text', 'content' ) ) || AS3CF_Utils::is_url( $value ) ) {
186
  $instance[ $key ] = $this->process_content( $value, $cache, $to_cache );
187
  }
188
  }
194
  return $instance;
195
  }
196
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
  /**
198
  * Process content.
199
  *
270
  return $url_pairs;
271
  }
272
 
273
+ $matches = array_unique( $matches[0] );
274
+ $item_sources = array();
275
 
276
  foreach ( $matches as $image ) {
277
  if ( ! preg_match( '/wp-image-([0-9]+)/i', $image, $class_id ) || ! isset( $class_id[1] ) ) {
293
 
294
  $url = AS3CF_Utils::reduce_url( $url );
295
 
296
+ $item_sources[ $url ] = array(
297
+ 'id' => absint( $class_id[1] ),
298
+ 'source_type' => Media_Library_Item::source_type(),
299
+ );
300
  }
301
 
302
+ if ( count( $item_sources ) > 1 ) {
303
  /*
304
  * Warm object cache for use with 'get_post_meta()'.
305
  *
306
  * To avoid making a database call for each image, a single query
307
  * warms the object cache with the meta information for all images.
308
  */
309
+ update_meta_cache( 'post', array_unique( array_column( $item_sources, 'id' ) ) );
310
  }
311
 
312
+ foreach ( $item_sources as $url => $item_source ) {
313
+ if ( ! $this->item_matches_src( $item_source, $url ) ) {
314
  // Path doesn't match attachment, skip
315
  continue;
316
  }
317
 
318
+ $this->push_to_url_pairs( $url_pairs, $item_source, $url, $to_cache );
319
  }
320
 
321
  return $url_pairs;
365
  continue;
366
  }
367
 
368
+ $item_source = null;
369
+ $bare_url = AS3CF_Utils::reduce_url( $url );
370
 
371
  // If attachment ID recently or previously cached, skip full search.
372
  if ( isset( $to_cache[ $bare_url ] ) ) {
373
+ $item_source = $to_cache[ $bare_url ];
374
 
375
+ if ( $this->is_failure( $item_source ) ) {
376
  // Attachment ID failure, continue
377
  continue;
378
  }
379
  } elseif ( isset( $cache[ $bare_url ] ) ) {
380
+ $item_source = $cache[ $bare_url ];
381
 
382
+ if ( $this->is_failure( $item_source ) ) {
383
  // Attachment ID failure, continue
384
  continue;
385
  }
386
  }
387
 
388
+ if ( is_null( $item_source ) || ( is_array( $item_source ) && ! empty( $item_source['timestamp'] ) ) ) {
389
  // Attachment ID not cached, need to search by URL.
390
  $urls[] = $bare_url;
391
  } else {
392
+ $this->push_to_url_pairs( $url_pairs, $item_source, $bare_url, $to_cache );
393
  }
394
  }
395
 
396
  if ( ! empty( $urls ) ) {
397
+ $item_sources = $this->get_item_sources_from_urls( $urls );
398
 
399
+ foreach ( $item_sources as $url => $item_source ) {
400
+ if ( ! $item_source ) {
401
+ // Can't determine item ID, continue
402
  $this->url_cache_failure( $url, $to_cache );
403
 
404
  continue;
405
  }
406
 
407
+ $this->push_to_url_pairs( $url_pairs, $item_source, $url, $to_cache );
408
  }
409
  }
410
 
414
  /**
415
  * Is failure?
416
  *
417
+ * @param array $value
418
  *
419
  * @return bool
420
  */
435
  /**
436
  * Does attachment ID match src?
437
  *
438
+ * @param array $item_source
439
  * @param string $url
440
  *
441
  * @return bool
442
  */
443
+ public function item_matches_src( $item_source, $url ) {
444
+ if ( empty( $item_source['id'] ) || empty( $item_source['source_type'] ) || Media_Library_Item::source_type() !== $item_source['source_type'] ) {
445
+ return false;
446
+ }
447
+ $meta = get_post_meta( $item_source['id'], '_wp_attachment_metadata', true );
448
 
449
  if ( ! isset( $meta['sizes'] ) ) {
450
  // No sizes found, return
451
  return false;
452
  }
453
 
454
+ $base_url = AS3CF_Utils::encode_filename_in_path( AS3CF_Utils::reduce_url( $this->get_base_url( $item_source ) ) );
455
  $basename = wp_basename( $base_url );
456
 
457
  // Add full size URL
476
  * Push to URL pairs.
477
  *
478
  * @param array $url_pairs
479
+ * @param array $item_source
480
  * @param string $find
481
  * @param array $to_cache
482
  */
483
+ protected function push_to_url_pairs( &$url_pairs, $item_source, $find, &$to_cache ) {
484
  $find_full = AS3CF_Utils::remove_size_from_filename( $find );
485
  $find_full = $this->normalize_find_value( $this->as3cf->maybe_remove_query_string( $find_full ) );
486
  $find_size = $this->normalize_find_value( $this->as3cf->maybe_remove_query_string( $find ) );
487
 
488
  // Cache find URLs even if no replacement.
489
+ $to_cache[ $find_full ] = $item_source;
490
 
491
  if ( wp_basename( $find_full ) !== wp_basename( $find_size ) ) {
492
+ $to_cache[ $find_size ] = $item_source;
493
  }
494
 
495
+ $replace_full = $this->get_url( $item_source );
496
 
497
  // Replacement URL can't be found.
498
  if ( ! $replace_full ) {
499
  return;
500
  }
501
 
502
+ $size = $this->get_size_string_from_url( $item_source, $find );
503
+ $replace_size = $this->get_url( $item_source, $size );
504
  $parts = parse_url( $find );
505
 
506
  if ( ! isset( $parts['scheme'] ) ) {
520
  $replace_full = $this->as3cf->maybe_remove_query_string( $replace_full );
521
  $replace_size = $this->as3cf->maybe_remove_query_string( $replace_size );
522
 
523
+ $to_cache[ $this->normalize_find_value( $replace_full ) ] = $item_source;
524
+ $to_cache[ $this->normalize_find_value( $replace_size ) ] = $item_source;
525
  }
526
 
527
  /**
528
  * Get size string from URL.
529
  *
530
+ * @param array $item_source
531
  * @param string $url
532
  *
533
  * @return null|string
534
  */
535
+ public function get_size_string_from_url( $item_source, $url ) {
536
+ if ( empty( $item_source['id'] ) || empty( $item_source['source_type'] ) ) {
537
+ return false;
 
 
 
 
 
 
 
 
 
 
 
538
  }
539
 
540
+ return apply_filters( 'as3cf_get_size_string_from_url_for_item_source', Item::primary_object_key(), $url, $item_source );
541
  }
542
 
543
  /**
598
  /**
599
  * Get post cache
600
  *
601
+ * @param null|int|WP_Post $post Optional. Post ID or post object. Defaults to current post.
602
+ * @param bool $transform_ints Optional. If true (default), convert integer hits to array with id and source_type keys.
603
+ * If false, return integer hits as integers
604
  *
605
+ *
606
+ * @return array|int
607
  */
608
+ public function get_post_cache( $post = null, $transform_ints = true ) {
609
  $post_id = AS3CF_Utils::get_post_id( $post );
610
 
611
  if ( ! $post_id ) {
622
  $cache = array();
623
  }
624
 
625
+ if ( ! $transform_ints ) {
626
+ return $cache;
627
+ }
628
+
629
+ // Handle old cache items that are stored as plain integers
630
+ foreach ( $cache as &$cache_item ) {
631
+ if ( ! is_array( $cache_item ) && is_numeric( $cache_item ) ) {
632
+ $id = $cache_item;
633
+ $cache_item = array(
634
+ 'id' => $id,
635
+ 'source_type' => Media_Library_Item::source_type(),
636
+ );
637
+ }
638
+ }
639
+
640
  return $cache;
641
  }
642
 
688
  return;
689
  }
690
 
691
+ $cached = $this->get_post_cache( $post_id, false );
692
  $urls = static::merge_cache( $cached, $to_cache );
693
 
694
  if ( $urls !== $cached ) {
734
  }
735
 
736
  /**
737
+ * Purge items from cache on delete.
738
  *
739
  * @param int $post_id
740
  */
741
  public function purge_cache_on_attachment_delete( $post_id ) {
742
  if ( ! in_array( $post_id, self::$purged_ids ) ) {
743
+ $item_source = array(
744
+ 'id' => $post_id,
745
+ 'source_type' => Media_Library_Item::source_type(),
746
+ );
747
+ $this->purge_from_cache( $this->get_url( $item_source ) );
748
  self::$purged_ids[] = $post_id;
749
  }
750
  }
1005
  /**
1006
  * Get URL.
1007
  *
1008
+ * @param int|array $item_source
1009
+ * @param null|string $object_key
1010
  *
1011
  * @return bool|string
1012
  */
1013
+ abstract protected function get_url( $item_source, $object_key = null );
1014
 
1015
  /**
1016
  * Get base URL.
1017
  *
1018
+ * @param int|array $item_source
1019
  *
1020
  * @return string|false
1021
  */
1022
+ abstract protected function get_base_url( $item_source );
1023
 
1024
  /**
1025
  * Get attachment ID from URL.
1026
  *
1027
  * @param string $url
1028
  *
1029
+ * @return array
1030
  */
1031
+ abstract public function get_item_source_from_url( $url );
1032
 
1033
  /**
1034
  * Get attachment IDs from URLs.
1037
  *
1038
  * @return array url => attachment ID (or false)
1039
  */
1040
+ abstract protected function get_item_sources_from_urls( $urls );
1041
 
1042
  /**
1043
  * Normalize find value.
classes/as3cf-plugin-compatibility.php CHANGED
@@ -9,7 +9,11 @@
9
  * @since 0.8.3
10
  */
11
 
 
 
 
12
  use DeliciousBrains\WP_Offload_Media\Items\Media_Library_Item;
 
13
  use DeliciousBrains\WP_Offload_Media\Providers\Storage\Storage_Provider;
14
 
15
  // Exit if accessed directly
@@ -26,35 +30,25 @@ if ( ! defined( 'ABSPATH' ) ) {
26
  */
27
  class AS3CF_Plugin_Compatibility {
28
 
29
- /**
30
- * @var Amazon_S3_And_CloudFront
31
- */
32
- protected $as3cf;
33
-
34
  /**
35
  * @var array
36
  */
37
  protected static $stream_wrappers = array();
38
 
39
  /**
40
- * @var array
41
- */
42
- protected $compatibility_addons;
43
-
44
- /**
45
- * @var array
46
  */
47
- private $removed_files = array();
48
 
49
  /**
50
  * @var bool
51
  */
52
- protected $generate_attachment_metadata_done = false;
53
 
54
  /**
55
- * @var bool
56
  */
57
- protected $wait_for_generate_attachment_metadata = false;
58
 
59
  /**
60
  * @param Amazon_S3_And_CloudFront $as3cf
@@ -110,12 +104,12 @@ class AS3CF_Plugin_Compatibility {
110
  * WP_Image_Editor
111
  * /wp-includes/class-wp-image-editor.php
112
  */
113
- add_action( 'as3cf_pre_upload_attachment', array( $this, 'image_editor_remove_files' ), 10, 3 );
114
  add_filter( 'as3cf_get_attached_file_noop', array( $this, 'image_editor_download_file' ), 10, 4 );
115
  add_filter( 'as3cf_get_attached_file', array( $this, 'image_editor_download_file' ), 10, 4 );
116
- add_filter( 'as3cf_upload_attachment_local_files_to_remove', array( $this, 'image_editor_remove_original_image' ), 10, 3 );
117
  add_filter( 'as3cf_get_attached_file', array( $this, 'customizer_crop_download_file' ), 10, 4 );
118
- add_filter( 'as3cf_upload_attachment_local_files_to_remove', array( $this, 'customizer_crop_remove_original_image' ), 10, 3 );
119
  add_filter( 'wp_unique_filename', array( $this, 'customizer_crop_unique_filename' ), 10, 3 );
120
 
121
  /*
@@ -134,7 +128,7 @@ class AS3CF_Plugin_Compatibility {
134
  * WP-CLI Compatibility
135
  */
136
  if ( defined( 'WP_CLI' ) && class_exists( 'WP_CLI' ) ) {
137
- WP_CLI::add_hook( 'before_invoke:media regenerate', array( $this, 'enable_get_attached_file_copy_back_to_local' ) );
138
  }
139
  }
140
 
@@ -159,12 +153,24 @@ class AS3CF_Plugin_Compatibility {
159
  if ( ( $file = $this->copy_provider_file_to_server( $as3cf_item, $file ) ) ) {
160
  // Return the file if successfully downloaded from S3
161
  return $file;
162
- };
163
 
164
  // Return S3 URL as a fallback
165
  return $url;
166
  }
167
 
 
 
 
 
 
 
 
 
 
 
 
 
168
  /**
169
  * Enables copying missing local files back to the server when `get_attached_file` filter is called.
170
  */
@@ -238,12 +244,13 @@ class AS3CF_Plugin_Compatibility {
238
  *
239
  * @handles wp_generate_attachment_metadata
240
  *
241
- * @param $metadata
242
  *
243
  * @return mixed
244
  */
245
  public function wp_generate_attachment_metadata( $metadata ) {
246
- $this->generate_attachment_metadata_done = true;
 
247
  return $metadata;
248
  }
249
 
@@ -253,14 +260,16 @@ class AS3CF_Plugin_Compatibility {
253
  *
254
  * @handles as3cf_wait_for_generate_attachment_metadata
255
  *
 
 
256
  * @return bool
257
  */
258
- public function wait_for_generate_attachment_metadata() {
259
- if ( ! $this->wait_for_generate_attachment_metadata ) {
260
- return false;
261
  }
262
 
263
- return ! $this->generate_attachment_metadata_done;
264
  }
265
 
266
  /**
@@ -323,23 +332,30 @@ class AS3CF_Plugin_Compatibility {
323
  if ( ( $file = $this->copy_provider_file_to_server( $as3cf_item, $file ) ) ) {
324
  // Return the file if successfully downloaded from S3
325
  return $file;
326
- };
327
 
328
  return $url;
329
  }
330
 
331
  /**
332
- * Get the file path of the original image file before an update
 
 
 
 
333
  *
334
- * @param int $post_id
335
- * @param string $file_path
336
  *
337
  * @return bool|string
338
  */
339
- function get_original_image_file( $post_id, $file_path ) {
340
- // remove original main image after edit
341
- $meta = get_post_meta( $post_id, '_wp_attachment_metadata', true );
342
- $original_file = trailingslashit( dirname( $file_path ) ) . wp_basename( $meta['file'] );
 
 
 
 
343
  if ( file_exists( $original_file ) ) {
344
  return $original_file;
345
  }
@@ -351,66 +367,43 @@ class AS3CF_Plugin_Compatibility {
351
  * Allow the WordPress Image Editor to remove edited version of images
352
  * if the original image is being restored and 'IMAGE_EDIT_OVERWRITE' is set
353
  *
354
- * @param bool $pre
355
- * @param int $post_id
356
- * @param array $data
 
357
  *
358
  * @return bool
359
  */
360
- public function image_editor_remove_files( $pre, $post_id, $data ) {
361
  if ( ! isset( $_POST['do'] ) || 'restore' !== $_POST['do'] ) {
362
- return $pre;
363
  }
364
 
365
  if ( ! defined( 'IMAGE_EDIT_OVERWRITE' ) || ! IMAGE_EDIT_OVERWRITE ) {
366
- return $pre;
367
  }
368
 
369
- $as3cf_item = Media_Library_Item::get_by_source_id( $post_id );
370
-
371
- if ( ! $as3cf_item ) {
372
- return $pre;
373
  }
374
 
375
- $this->remove_edited_image_files( $post_id, $as3cf_item );
376
-
377
- // Update object key with original filename
378
- $restored_filename = wp_basename( $data['file'] );
379
- $old_filename = wp_basename( $as3cf_item->path() );
380
- $item_path = str_replace( $old_filename, $restored_filename, $as3cf_item->path() );
381
-
382
- $as3cf_item = new Media_Library_Item(
383
- $as3cf_item->provider(),
384
- $as3cf_item->region(),
385
- $as3cf_item->bucket(),
386
- $item_path,
387
- $as3cf_item->is_private(),
388
- $as3cf_item->source_id(),
389
- $as3cf_item->source_path(),
390
- wp_basename( $as3cf_item->original_source_path() ),
391
- $as3cf_item->extra_info(),
392
- $as3cf_item->id()
393
- );
394
 
 
 
 
 
395
  $as3cf_item->save();
396
 
397
- return true;
398
- }
399
-
400
- /**
401
- * Remove edited image files from S3.
402
- *
403
- * @param int $attachment_id
404
- * @param Media_Library_Item $as3cf_item
405
- */
406
- protected function remove_edited_image_files( $attachment_id, Media_Library_Item $as3cf_item ) {
407
- $keys = AS3CF_Utils::get_attachment_edited_keys( $attachment_id, $as3cf_item );
408
-
409
- if ( empty( $keys ) ) {
410
- return;
411
- }
412
-
413
- $this->as3cf->delete_objects( $as3cf_item->region(), $as3cf_item->bucket(), $keys );
414
  }
415
 
416
  /**
@@ -434,25 +427,11 @@ class AS3CF_Plugin_Compatibility {
434
  // for the restore to be successful and edited images to be deleted from the bucket
435
  // via image_editor_remove_files()
436
  if ( isset( $_POST['do'] ) && 'restore' == $_POST['do'] ) {
437
- $backup_sizes = get_post_meta( $attachment_id, '_wp_attachment_backup_sizes', true );
438
- $original_filename = $backup_sizes['full-orig']['file'];
439
-
440
- $as3cf_item_orig = new Media_Library_Item(
441
- $as3cf_item->provider(),
442
- $as3cf_item->region(),
443
- $as3cf_item->bucket(),
444
- $as3cf_item->normalized_path_dir() . $original_filename,
445
- $as3cf_item->is_private(),
446
- $as3cf_item->source_id(),
447
- $as3cf_item->source_path(),
448
- wp_basename( $as3cf_item->original_source_path() ),
449
- $as3cf_item->extra_info(),
450
- $as3cf_item->id()
451
- );
452
- $orig_file = dirname( $file ) . '/' . $original_filename;
453
-
454
- // Copy the original file back to the server for the restore process
455
- $this->copy_provider_file_to_server( $as3cf_item_orig, $orig_file );
456
 
457
  // Copy the edited file back to the server as well, it will be cleaned up later
458
  if ( $provider_file = $this->copy_provider_file_to_server( $as3cf_item, $file ) ) {
@@ -480,27 +459,26 @@ class AS3CF_Plugin_Compatibility {
480
 
481
  /**
482
  * Allow the WordPress Image Editor to remove the main image file after it has been copied
483
- * back from S3 after it has done the edit.
484
  *
485
- * @param array $files
486
- * @param int $post_id
487
- * @param string $file_path
488
  *
489
  * @return array
490
  */
491
- function image_editor_remove_original_image( $files, $post_id, $file_path ) {
492
  if ( ! $this->is_ajax() ) {
493
- return $files;
494
  }
495
 
496
  if ( isset( $_POST['action'] ) && 'image-editor' === sanitize_key( $_POST['action'] ) ) { // input var okay
497
- // remove original main image after edit
498
- if ( ( $original_file = $this->get_original_image_file( $post_id, $file_path ) ) ) {
499
- $files[] = $original_file;
500
  }
501
  }
502
 
503
- return $files;
504
  }
505
 
506
  /**
@@ -538,39 +516,40 @@ class AS3CF_Plugin_Compatibility {
538
  return $url;
539
  }
540
 
541
- if ( $this->as3cf->attachment_just_uploaded( $attachment_id ) ) {
 
 
542
  return $url;
543
  }
544
 
545
  if ( ( $file = $this->copy_provider_file_to_server( $as3cf_item, $file ) ) ) {
546
  // Return the file if successfully downloaded from bucket.
547
  return $file;
548
- };
549
 
550
  return $url;
551
  }
552
 
553
  /**
554
  * Allow the WordPress Image Editor to remove the main image file after it has been copied
555
- * back from S3 after it has done the edit.
556
  *
557
- * @param array $files
558
- * @param int $post_id
559
- * @param string $file_path
560
  *
561
  * @return array
562
  */
563
- function customizer_crop_remove_original_image( $files, $post_id, $file_path ) {
564
  if ( false === $this->is_customizer_crop_action() ) {
565
- return $files;
566
  }
567
 
568
- // remove original main image after edit
569
- if ( ( $original_file = $this->get_original_image_file( $_POST['id'], $file_path ) ) ) {
570
- $files[] = $original_file;
571
  }
572
 
573
- return $files;
574
  }
575
 
576
  /**
@@ -595,7 +574,9 @@ class AS3CF_Plugin_Compatibility {
595
  $post_id = $url;
596
  }
597
  } else {
598
- $post_id = $this->as3cf->filter_provider->get_attachment_id_from_url( $url );
 
 
599
  }
600
 
601
  // Must return null if not found.
@@ -631,26 +612,11 @@ class AS3CF_Plugin_Compatibility {
631
  * @return string|bool File if downloaded, false on failure
632
  */
633
  public function copy_provider_file_to_server( Media_Library_Item $as3cf_item, $file ) {
634
- $filename = wp_basename( $file );
635
-
636
- // Make sure the directory exists
637
- $dir = dirname( $file );
638
- if ( ! wp_mkdir_p( $dir ) ) {
639
- $error_message = sprintf( __( 'The local directory %s does not exist and could not be created.', 'amazon-s3-and-cloudfront' ), $dir );
640
- AS3CF_Error::log( sprintf( __( 'There was an error attempting to download the file %s from the bucket: %s', 'amazon-s3-and-cloudfront' ), $as3cf_item->key( $filename ), $error_message ) );
641
-
642
- return false;
643
- }
644
-
645
- try {
646
- $this->as3cf->get_provider_client( $as3cf_item->region(), true )->get_object( array(
647
- 'Bucket' => $as3cf_item->bucket(),
648
- 'Key' => $as3cf_item->key( $filename ),
649
- 'SaveAs' => $file,
650
- ) );
651
- } catch ( Exception $e ) {
652
- AS3CF_Error::log( sprintf( __( 'There was an error attempting to download the file %s from the bucket: %s', 'amazon-s3-and-cloudfront' ), $as3cf_item->key( $filename ), $e->getMessage() ) );
653
 
 
654
  return false;
655
  }
656
 
@@ -722,11 +688,13 @@ class AS3CF_Plugin_Compatibility {
722
  public function wp_get_attachment_metadata( $data, $attachment_id ) {
723
  global $wp_current_filter;
724
 
 
725
  if (
726
  is_array( $wp_current_filter ) &&
727
  ! empty( $wp_current_filter[0] ) &&
728
  'the_content' === $wp_current_filter[0] &&
729
- $this->as3cf->is_attachment_served_by_provider( $attachment_id )
 
730
  ) {
731
  // Ensure each filename is encoded the same way as URL, slightly fixed up for wp_basename() manipulation compatibility.
732
  if ( ! empty( $data['file'] ) ) {
@@ -855,7 +823,8 @@ class AS3CF_Plugin_Compatibility {
855
  return $image_meta;
856
  }
857
 
858
- if ( ! ( $as3cf_item = $this->as3cf->is_attachment_served_by_provider( $attachment_id ) ) ) {
 
859
  // Attachment not uploaded to S3, abort
860
  return $image_meta;
861
  }
@@ -901,7 +870,8 @@ class AS3CF_Plugin_Compatibility {
901
  return $sources;
902
  }
903
 
904
- if ( ! ( $as3cf_item = $this->as3cf->is_attachment_served_by_provider( $attachment_id ) ) ) {
 
905
  // Attachment not uploaded to S3, abort
906
  return $sources;
907
  }
@@ -909,7 +879,7 @@ class AS3CF_Plugin_Compatibility {
909
  foreach ( $sources as $width => $source ) {
910
  $filename = wp_basename( $source['url'] );
911
  $size = $this->find_image_size_from_width( $image_meta['sizes'], $width, $filename );
912
- $provider_url = $this->as3cf->get_attachment_provider_url( $attachment_id, $as3cf_item, null, $size, $image_meta );
913
 
914
  if ( false === $provider_url || is_wp_error( $provider_url ) ) {
915
  // Skip URLs not offloaded to S3
@@ -958,7 +928,9 @@ class AS3CF_Plugin_Compatibility {
958
  // Get parent Post ID for cropped image.
959
  $post_id = filter_input( INPUT_POST, 'id', FILTER_VALIDATE_INT );
960
 
961
- $filename = $this->as3cf->filter_unique_filename( $filename, $ext, $dir, $post_id );
 
 
962
 
963
  return $filename;
964
  }
@@ -1026,9 +998,7 @@ class AS3CF_Plugin_Compatibility {
1026
  if ( ! empty( $routes ) ) {
1027
  foreach ( $routes as $match_route ) {
1028
  if ( preg_match( '@' . $match_route . '@i', $route ) ) {
1029
- $this->enable_get_attached_file_copy_back_to_local();
1030
- $this->wait_for_generate_attachment_metadata = true;
1031
- $this->generate_attachment_metadata_done = false;
1032
  break;
1033
  }
1034
  }
9
  * @since 0.8.3
10
  */
11
 
12
+ use DeliciousBrains\WP_Offload_Media\Integrations\Media_Library;
13
+ use DeliciousBrains\WP_Offload_Media\Items\Download_Handler;
14
+ use DeliciousBrains\WP_Offload_Media\Items\Item;
15
  use DeliciousBrains\WP_Offload_Media\Items\Media_Library_Item;
16
+ use DeliciousBrains\WP_Offload_Media\Items\Remove_Provider_Handler;
17
  use DeliciousBrains\WP_Offload_Media\Providers\Storage\Storage_Provider;
18
 
19
  // Exit if accessed directly
30
  */
31
  class AS3CF_Plugin_Compatibility {
32
 
 
 
 
 
 
33
  /**
34
  * @var array
35
  */
36
  protected static $stream_wrappers = array();
37
 
38
  /**
39
+ * @var Amazon_S3_And_CloudFront
 
 
 
 
 
40
  */
41
+ protected $as3cf;
42
 
43
  /**
44
  * @var bool
45
  */
46
+ protected $wait_for_generate_attachment_metadata = false;
47
 
48
  /**
49
+ * @var array
50
  */
51
+ private $removed_files = array();
52
 
53
  /**
54
  * @param Amazon_S3_And_CloudFront $as3cf
104
  * WP_Image_Editor
105
  * /wp-includes/class-wp-image-editor.php
106
  */
107
+ add_filter( 'as3cf_pre_update_attachment_metadata', array( $this, 'image_editor_remove_files' ), 10, 4 );
108
  add_filter( 'as3cf_get_attached_file_noop', array( $this, 'image_editor_download_file' ), 10, 4 );
109
  add_filter( 'as3cf_get_attached_file', array( $this, 'image_editor_download_file' ), 10, 4 );
110
+ add_filter( 'as3cf_remove_local_files', array( $this, 'image_editor_remove_original_image' ), 10, 3 );
111
  add_filter( 'as3cf_get_attached_file', array( $this, 'customizer_crop_download_file' ), 10, 4 );
112
+ add_filter( 'as3cf_remove_local_files', array( $this, 'customizer_crop_remove_original_image' ), 10, 3 );
113
  add_filter( 'wp_unique_filename', array( $this, 'customizer_crop_unique_filename' ), 10, 3 );
114
 
115
  /*
128
  * WP-CLI Compatibility
129
  */
130
  if ( defined( 'WP_CLI' ) && class_exists( 'WP_CLI' ) ) {
131
+ WP_CLI::add_hook( 'before_invoke:media regenerate', array( $this, 'enable_copy_back_and_wait_for_generate_metadata' ) );
132
  }
133
  }
134
 
153
  if ( ( $file = $this->copy_provider_file_to_server( $as3cf_item, $file ) ) ) {
154
  // Return the file if successfully downloaded from S3
155
  return $file;
156
+ }
157
 
158
  // Return S3 URL as a fallback
159
  return $url;
160
  }
161
 
162
+ /**
163
+ * Enable copying back attachments from provider
164
+ * and waiting for their metadata to be regenerated
165
+ * before re-offloading.
166
+ *
167
+ * @handles WP_CLI:before_invoke:media regenerate
168
+ */
169
+ public function enable_copy_back_and_wait_for_generate_metadata() {
170
+ $this->enable_get_attached_file_copy_back_to_local();
171
+ $this->wait_for_generate_attachment_metadata = true;
172
+ }
173
+
174
  /**
175
  * Enables copying missing local files back to the server when `get_attached_file` filter is called.
176
  */
244
  *
245
  * @handles wp_generate_attachment_metadata
246
  *
247
+ * @param mixed $metadata
248
  *
249
  * @return mixed
250
  */
251
  public function wp_generate_attachment_metadata( $metadata ) {
252
+ $this->wait_for_generate_attachment_metadata = false;
253
+
254
  return $metadata;
255
  }
256
 
260
  *
261
  * @handles as3cf_wait_for_generate_attachment_metadata
262
  *
263
+ * @param bool $wait
264
+ *
265
  * @return bool
266
  */
267
+ public function wait_for_generate_attachment_metadata( $wait ) {
268
+ if ( $this->wait_for_generate_attachment_metadata ) {
269
+ return true;
270
  }
271
 
272
+ return $wait;
273
  }
274
 
275
  /**
332
  if ( ( $file = $this->copy_provider_file_to_server( $as3cf_item, $file ) ) ) {
333
  // Return the file if successfully downloaded from S3
334
  return $file;
335
+ }
336
 
337
  return $url;
338
  }
339
 
340
  /**
341
+ * Get the file path of the primary image file if it exists.
342
+ *
343
+ * This helper function looks at the current metadata for the Media Library item.
344
+ * In various scenarios this is useful when an item's offloaded objects
345
+ * and the attachment's metadata are not yet in sync.
346
  *
347
+ * @param Item $as3cf_item
 
348
  *
349
  * @return bool|string
350
  */
351
+ private function get_original_image_file( Item $as3cf_item ) {
352
+ if ( Media_Library_Item::source_type() !== $as3cf_item->source_type() ) {
353
+ return false;
354
+ }
355
+
356
+ $meta = get_post_meta( $as3cf_item->source_id(), '_wp_attachment_metadata', true );
357
+ $original_file = trailingslashit( dirname( $as3cf_item->full_source_path() ) ) . wp_basename( $meta['file'] );
358
+
359
  if ( file_exists( $original_file ) ) {
360
  return $original_file;
361
  }
367
  * Allow the WordPress Image Editor to remove edited version of images
368
  * if the original image is being restored and 'IMAGE_EDIT_OVERWRITE' is set
369
  *
370
+ * @param bool $cancel True if the upload should be cancelled
371
+ * @param array $data Array describing the object being uploaded
372
+ * @param int $post_id Attachment's ID
373
+ * @param Media_Library_Item $as3cf_item The Media Library Item object if previously offloaded
374
  *
375
  * @return bool
376
  */
377
+ public function image_editor_remove_files( $cancel, $data, $post_id, $as3cf_item ) {
378
  if ( ! isset( $_POST['do'] ) || 'restore' !== $_POST['do'] ) {
379
+ return $cancel;
380
  }
381
 
382
  if ( ! defined( 'IMAGE_EDIT_OVERWRITE' ) || ! IMAGE_EDIT_OVERWRITE ) {
383
+ return $cancel;
384
  }
385
 
386
+ if ( empty( $as3cf_item ) ) {
387
+ return $cancel;
 
 
388
  }
389
 
390
+ $keys_to_remove = array();
391
+ $pattern = '/-e[0-9]{13}(?:-[0-9]{1,4}x[0-9]{1,4})?\./';
392
+ $objects = $as3cf_item->objects();
393
+ foreach ( $objects as $object_key => $object ) {
394
+ if ( preg_match( $pattern, $object['source_file'] ) ) {
395
+ $keys_to_remove[] = $object_key;
396
+ unset( $objects[ $object_key ] );
397
+ }
398
+ }
 
 
 
 
 
 
 
 
 
 
399
 
400
+ $remove_provider_handler = $this->as3cf->get_item_handler( Remove_Provider_Handler::get_item_handler_key_name() );
401
+ $remove_provider_handler->handle( $as3cf_item, array( 'object_keys' => $keys_to_remove ) );
402
+ // TODO: Check these following statements are required.
403
+ $as3cf_item->set_objects( $objects );
404
  $as3cf_item->save();
405
 
406
+ return $cancel;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
407
  }
408
 
409
  /**
427
  // for the restore to be successful and edited images to be deleted from the bucket
428
  // via image_editor_remove_files()
429
  if ( isset( $_POST['do'] ) && 'restore' == $_POST['do'] ) {
430
+ $objects = $as3cf_item->objects();
431
+ if ( isset( $objects['full-orig'] ) ) {
432
+ // Copy the original file back to the server for the restore process
433
+ $this->copy_provider_file_to_server( $as3cf_item, $objects['full-orig']['source_file'] );
434
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
435
 
436
  // Copy the edited file back to the server as well, it will be cleaned up later
437
  if ( $provider_file = $this->copy_provider_file_to_server( $as3cf_item, $file ) ) {
459
 
460
  /**
461
  * Allow the WordPress Image Editor to remove the main image file after it has been copied
462
+ * back from the bucket after it has done the edit.
463
  *
464
+ * @param array $files_to_remove
465
+ * @param Item $as3cf_item
466
+ * @param array $item_source
467
  *
468
  * @return array
469
  */
470
+ public function image_editor_remove_original_image( $files_to_remove, $as3cf_item, $item_source ) {
471
  if ( ! $this->is_ajax() ) {
472
+ return $files_to_remove;
473
  }
474
 
475
  if ( isset( $_POST['action'] ) && 'image-editor' === sanitize_key( $_POST['action'] ) ) { // input var okay
476
+ if ( ( $original_file = $this->get_original_image_file( $as3cf_item ) ) ) {
477
+ $files_to_remove[] = $original_file;
 
478
  }
479
  }
480
 
481
+ return $files_to_remove;
482
  }
483
 
484
  /**
516
  return $url;
517
  }
518
 
519
+ /** @var Media_Library $media_library */
520
+ $media_library = $this->as3cf->get_integration_manager()->get_integration( 'mlib' );
521
+ if ( $media_library->item_just_uploaded( $attachment_id ) ) {
522
  return $url;
523
  }
524
 
525
  if ( ( $file = $this->copy_provider_file_to_server( $as3cf_item, $file ) ) ) {
526
  // Return the file if successfully downloaded from bucket.
527
  return $file;
528
+ }
529
 
530
  return $url;
531
  }
532
 
533
  /**
534
  * Allow the WordPress Image Editor to remove the main image file after it has been copied
535
+ * back from the bucket after it has done the edit.
536
  *
537
+ * @param array $files_to_remove
538
+ * @param Item $as3cf_item
539
+ * @param array $item_source
540
  *
541
  * @return array
542
  */
543
+ function customizer_crop_remove_original_image( $files_to_remove, $as3cf_item, $item_source ) {
544
  if ( false === $this->is_customizer_crop_action() ) {
545
+ return $files_to_remove;
546
  }
547
 
548
+ if ( ( $original_file = $this->get_original_image_file( $as3cf_item ) ) ) {
549
+ $files_to_remove[] = $original_file;
 
550
  }
551
 
552
+ return $files_to_remove;
553
  }
554
 
555
  /**
574
  $post_id = $url;
575
  }
576
  } else {
577
+ /** @var Media_Library $media_library */
578
+ $media_library = $this->as3cf->get_integration_manager()->get_integration( 'mlib' );
579
+ $post_id = $media_library->get_attachment_id_from_provider_url( $url );
580
  }
581
 
582
  // Must return null if not found.
612
  * @return string|bool File if downloaded, false on failure
613
  */
614
  public function copy_provider_file_to_server( Media_Library_Item $as3cf_item, $file ) {
615
+ /** @var Download_Handler $download_handler */
616
+ $download_handler = $this->as3cf->get_item_handler( Download_Handler::get_item_handler_key_name() );
617
+ $result = $download_handler->handle( $as3cf_item, array( 'full_source_paths' => array( $file ) ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
618
 
619
+ if ( empty( $result ) || is_wp_error( $result ) ) {
620
  return false;
621
  }
622
 
688
  public function wp_get_attachment_metadata( $data, $attachment_id ) {
689
  global $wp_current_filter;
690
 
691
+ $as3cf_item = Media_Library_Item::get_by_source_id( $attachment_id );
692
  if (
693
  is_array( $wp_current_filter ) &&
694
  ! empty( $wp_current_filter[0] ) &&
695
  'the_content' === $wp_current_filter[0] &&
696
+ ! empty( $as3cf_item ) &&
697
+ $as3cf_item->served_by_provider( $attachment_id )
698
  ) {
699
  // Ensure each filename is encoded the same way as URL, slightly fixed up for wp_basename() manipulation compatibility.
700
  if ( ! empty( $data['file'] ) ) {
823
  return $image_meta;
824
  }
825
 
826
+ $as3cf_item = Media_Library_Item::get_by_source_id( $attachment_id );
827
+ if ( ! $as3cf_item || ! $as3cf_item->served_by_provider() ) {
828
  // Attachment not uploaded to S3, abort
829
  return $image_meta;
830
  }
870
  return $sources;
871
  }
872
 
873
+ $as3cf_item = Media_Library_Item::get_by_source_id( $attachment_id );
874
+ if ( ! $as3cf_item || ! $as3cf_item->served_by_provider() ) {
875
  // Attachment not uploaded to S3, abort
876
  return $sources;
877
  }
879
  foreach ( $sources as $width => $source ) {
880
  $filename = wp_basename( $source['url'] );
881
  $size = $this->find_image_size_from_width( $image_meta['sizes'], $width, $filename );
882
+ $provider_url = $as3cf_item->get_provider_url( $size );
883
 
884
  if ( false === $provider_url || is_wp_error( $provider_url ) ) {
885
  // Skip URLs not offloaded to S3
928
  // Get parent Post ID for cropped image.
929
  $post_id = filter_input( INPUT_POST, 'id', FILTER_VALIDATE_INT );
930
 
931
+ /** @var Media_Library $media_library */
932
+ $media_library = $this->as3cf->get_integration_manager()->get_integration( 'mlib' );
933
+ $filename = $media_library->filter_unique_filename( $filename, $ext, $dir, $post_id );
934
 
935
  return $filename;
936
  }
998
  if ( ! empty( $routes ) ) {
999
  foreach ( $routes as $match_route ) {
1000
  if ( preg_match( '@' . $match_route . '@i', $route ) ) {
1001
+ $this->enable_copy_back_and_wait_for_generate_metadata();
 
 
1002
  break;
1003
  }
1004
  }
classes/as3cf-utils.php CHANGED
@@ -8,6 +8,7 @@
8
  * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
9
  */
10
 
 
11
  use DeliciousBrains\WP_Offload_Media\Items\Media_Library_Item;
12
 
13
  // Exit if accessed directly
@@ -103,6 +104,8 @@ if ( ! class_exists( 'AS3CF_Utils' ) ) {
103
  public static function remove_size_from_filename( $url, $remove_extension = false ) {
104
  $url = preg_replace( '/^(\S+)-[0-9]{1,4}x[0-9]{1,4}(\.[a-zA-Z0-9\.]{2,})?/', '$1$2', $url );
105
 
 
 
106
  if ( $remove_extension ) {
107
  $ext = pathinfo( $url, PATHINFO_EXTENSION );
108
  $url = str_replace( ".$ext", '', $url );
@@ -119,7 +122,7 @@ if ( ! class_exists( 'AS3CF_Utils' ) ) {
119
  * @return bool
120
  */
121
  public static function is_full_size( $size ) {
122
- if ( empty( $size ) || in_array( $size, array( 'full', 'original' ) ) ) {
123
  return true;
124
  }
125
 
@@ -224,7 +227,7 @@ if ( ! class_exists( 'AS3CF_Utils' ) ) {
224
  public static function get_attachment_file_paths( $attachment_id, $exists_locally = true, $meta = false, $include_backups = true ) {
225
  $file_path = get_attached_file( $attachment_id, true );
226
  $paths = array(
227
- 'original' => $file_path,
228
  );
229
 
230
  if ( ! $meta ) {
@@ -240,6 +243,11 @@ if ( ! class_exists( 'AS3CF_Utils' ) ) {
240
  // If file edited, current file name might be different.
241
  if ( isset( $meta['file'] ) ) {
242
  $paths['file'] = str_replace( $file_name, wp_basename( $meta['file'] ), $file_path );
 
 
 
 
 
243
  }
244
 
245
  // Thumb
@@ -287,39 +295,6 @@ if ( ! class_exists( 'AS3CF_Utils' ) ) {
287
  return $paths;
288
  }
289
 
290
- /**
291
- * Get an attachment's edited file paths.
292
- *
293
- * @param int $attachment_id
294
- *
295
- * @return array
296
- */
297
- public static function get_attachment_edited_file_paths( $attachment_id ) {
298
- $paths = self::get_attachment_file_paths( $attachment_id, false );
299
- $paths = array_filter( $paths, function ( $path ) {
300
- return preg_match( '/-e[0-9]{13}(?:-[0-9]{1,4}x[0-9]{1,4})?\./', wp_basename( $path ) );
301
- } );
302
-
303
- return $paths;
304
- }
305
-
306
- /**
307
- * Get an attachment's edited S3 keys.
308
- *
309
- * @param int $attachment_id
310
- * @param Media_Library_Item $as3cf_item
311
- *
312
- * @return array
313
- */
314
- public static function get_attachment_edited_keys( $attachment_id, Media_Library_Item $as3cf_item ) {
315
- $paths = self::get_attachment_edited_file_paths( $attachment_id );
316
- $paths = array_map( function ( $path ) use ( $as3cf_item ) {
317
- return array( 'Key' => $as3cf_item->key( wp_basename( $path ) ) );
318
- }, $paths );
319
-
320
- return $paths;
321
- }
322
-
323
  /**
324
  * Get intermediate size from attachment filename.
325
  * If multiple sizes exist with same filename, only the first size found will be returned.
@@ -345,14 +320,24 @@ if ( ! class_exists( 'AS3CF_Utils' ) ) {
345
  * Strip edited image suffix and extension from path.
346
  *
347
  * @param string $path
 
348
  *
349
  * @return string
350
  */
351
- public static function strip_image_edit_suffix_and_extension( $path ) {
352
  $parts = pathinfo( $path );
353
  $filename = preg_replace( '/-e[0-9]{13}/', '', $parts['filename'] );
 
354
 
355
- return str_replace( $parts['basename'], $filename, $path );
 
 
 
 
 
 
 
 
356
  }
357
 
358
  /**
@@ -513,19 +498,23 @@ if ( ! class_exists( 'AS3CF_Utils' ) ) {
513
  /**
514
  * Ensure returned keys are for correct attachment.
515
  *
516
- * @param array $keys
 
 
517
  *
518
  * @return array
519
  */
520
- public static function validate_attachment_keys( $attachment_id, $keys ) {
521
- $paths = self::get_attachment_file_paths( $attachment_id, false );
522
- $filenames = array_map( 'wp_basename', $paths );
 
523
 
524
- foreach ( $keys as $key => $value ) {
525
- $filename = wp_basename( $value );
526
 
527
- if ( ! in_array( $filename, $filenames ) ) {
528
- unset( $keys[ $key ] );
 
529
  }
530
  }
531
 
@@ -584,7 +573,7 @@ if ( ! class_exists( 'AS3CF_Utils' ) ) {
584
  */
585
  public static function fullsize_paths( $paths ) {
586
  if ( is_array( $paths ) && ! empty( $paths ) ) {
587
- return array_values( array_unique( array_intersect_key( $paths, array_flip( array( 'original', 'file', 'full-orig', 'original_image' ) ) ) ) );
588
  } else {
589
  return array();
590
  }
@@ -620,70 +609,6 @@ if ( ! class_exists( 'AS3CF_Utils' ) ) {
620
  return $paths;
621
  }
622
 
623
- /**
624
- * Convert dimensions to size
625
- *
626
- * @param int $attachment_id
627
- * @param array $dimensions
628
- *
629
- * @return null|string
630
- */
631
- public static function convert_dimensions_to_size_name( $attachment_id, $dimensions ) {
632
- $w = ( isset( $dimensions[0] ) && $dimensions[0] > 0 ) ? $dimensions[0] : 1;
633
- $h = ( isset( $dimensions[1] ) && $dimensions[1] > 0 ) ? $dimensions[1] : 1;
634
- $original_aspect_ratio = $w / $h;
635
- $meta = wp_get_attachment_metadata( $attachment_id );
636
-
637
- if ( ! isset( $meta['sizes'] ) || empty( $meta['sizes'] ) ) {
638
- return null;
639
- }
640
-
641
- $sizes = $meta['sizes'];
642
- uasort( $sizes, function ( $a, $b ) {
643
- // Order by image area
644
- return ( $a['width'] * $a['height'] ) - ( $b['width'] * $b['height'] );
645
- } );
646
-
647
- $nearest_matches = array();
648
-
649
- foreach ( $sizes as $size => $value ) {
650
- if ( $w > $value['width'] || $h > $value['height'] ) {
651
- continue;
652
- }
653
-
654
- $aspect_ratio = $value['width'] / $value['height'];
655
-
656
- if ( $aspect_ratio === $original_aspect_ratio ) {
657
- return $size;
658
- }
659
-
660
- $nearest_matches[] = $size;
661
- }
662
-
663
- // Return nearest match
664
- if ( ! empty( $nearest_matches ) ) {
665
- return $nearest_matches[0];
666
- }
667
-
668
- return null;
669
- }
670
-
671
- /**
672
- * Maybe convert size to string
673
- *
674
- * @param int $attachment_id
675
- * @param mixed $size
676
- *
677
- * @return null|string
678
- */
679
- public static function maybe_convert_size_to_string( $attachment_id, $size ) {
680
- if ( is_array( $size ) ) {
681
- return static::convert_dimensions_to_size_name( $attachment_id, $size );
682
- }
683
-
684
- return $size;
685
- }
686
-
687
  /**
688
  * Encode file names according to RFC 3986 when generating urls
689
  * As per Amazon https://forums.aws.amazon.com/thread.jspa?threadID=55746#jive-message-244233
@@ -721,5 +646,18 @@ if ( ! class_exists( 'AS3CF_Utils' ) ) {
721
 
722
  return str_replace( $file_name, $encoded_file_name, $file );
723
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
724
  }
725
  }
8
  * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
9
  */
10
 
11
+ use DeliciousBrains\WP_Offload_Media\Items\Item;
12
  use DeliciousBrains\WP_Offload_Media\Items\Media_Library_Item;
13
 
14
  // Exit if accessed directly
104
  public static function remove_size_from_filename( $url, $remove_extension = false ) {
105
  $url = preg_replace( '/^(\S+)-[0-9]{1,4}x[0-9]{1,4}(\.[a-zA-Z0-9\.]{2,})?/', '$1$2', $url );
106
 
107
+ $url = apply_filters( 'as3cf_remove_size_from_filename', $url );
108
+
109
  if ( $remove_extension ) {
110
  $ext = pathinfo( $url, PATHINFO_EXTENSION );
111
  $url = str_replace( ".$ext", '', $url );
122
  * @return bool
123
  */
124
  public static function is_full_size( $size ) {
125
+ if ( empty( $size ) || in_array( $size, array( 'full', Item::primary_object_key() ) ) ) {
126
  return true;
127
  }
128
 
227
  public static function get_attachment_file_paths( $attachment_id, $exists_locally = true, $meta = false, $include_backups = true ) {
228
  $file_path = get_attached_file( $attachment_id, true );
229
  $paths = array(
230
+ Item::primary_object_key() => $file_path,
231
  );
232
 
233
  if ( ! $meta ) {
243
  // If file edited, current file name might be different.
244
  if ( isset( $meta['file'] ) ) {
245
  $paths['file'] = str_replace( $file_name, wp_basename( $meta['file'] ), $file_path );
246
+
247
+ // However, if this file path turns out to be exactly the same as the primary objet key, we don't need it.
248
+ if ( $paths[ Item::primary_object_key() ] === $paths['file'] ) {
249
+ unset( $paths['file'] );
250
+ }
251
  }
252
 
253
  // Thumb
295
  return $paths;
296
  }
297
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
298
  /**
299
  * Get intermediate size from attachment filename.
300
  * If multiple sizes exist with same filename, only the first size found will be returned.
320
  * Strip edited image suffix and extension from path.
321
  *
322
  * @param string $path
323
+ * @param string $source_type
324
  *
325
  * @return string
326
  */
327
+ public static function strip_image_edit_suffix_and_extension( $path, $source_type = 'media-library' ) {
328
  $parts = pathinfo( $path );
329
  $filename = preg_replace( '/-e[0-9]{13}/', '', $parts['filename'] );
330
+ $result = str_replace( $parts['basename'], $filename, $path );
331
 
332
+ /**
333
+ * Allow source type specific cleanup
334
+ *
335
+ * @param string $path
336
+ * @param string $source_type
337
+ *
338
+ * @return string
339
+ */
340
+ return apply_filters( 'as3cf_strip_image_edit_suffix_and_extension', $result, $source_type );
341
  }
342
 
343
  /**
498
  /**
499
  * Ensure returned keys are for correct attachment.
500
  *
501
+ * @param int $source_id
502
+ * @param array $keys
503
+ * @param string $source_type
504
  *
505
  * @return array
506
  */
507
+ public static function validate_attachment_keys( $source_id, $keys, $source_type ) {
508
+ if ( Media_Library_Item::source_type() === $source_type ) {
509
+ $paths = self::get_attachment_file_paths( $source_id, false );
510
+ $filenames = array_map( 'wp_basename', $paths );
511
 
512
+ foreach ( $keys as $key => $value ) {
513
+ $filename = wp_basename( $value );
514
 
515
+ if ( ! in_array( $filename, $filenames ) ) {
516
+ unset( $keys[ $key ] );
517
+ }
518
  }
519
  }
520
 
573
  */
574
  public static function fullsize_paths( $paths ) {
575
  if ( is_array( $paths ) && ! empty( $paths ) ) {
576
+ return array_values( array_unique( array_intersect_key( $paths, array_flip( array( Item::primary_object_key(), 'file', 'full-orig', 'original_image' ) ) ) ) );
577
  } else {
578
  return array();
579
  }
609
  return $paths;
610
  }
611
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
612
  /**
613
  * Encode file names according to RFC 3986 when generating urls
614
  * As per Amazon https://forums.aws.amazon.com/thread.jspa?threadID=55746#jive-message-244233
646
 
647
  return str_replace( $file_name, $encoded_file_name, $file );
648
  }
649
+
650
+ /**
651
+ * Get a file's real mime type
652
+ *
653
+ * @param string $file_path
654
+ *
655
+ * @return string
656
+ */
657
+ public static function get_mime_type( $file_path ) {
658
+ $file_type = wp_check_filetype_and_ext( $file_path, wp_basename( $file_path ) );
659
+
660
+ return $file_type['type'];
661
+ }
662
  }
663
  }
classes/filters/as3cf-local-to-s3.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- use DeliciousBrains\WP_Offload_Media\Items\Media_Library_Item;
4
 
5
  class AS3CF_Local_To_S3 extends AS3CF_Filter {
6
 
@@ -26,10 +26,20 @@ class AS3CF_Local_To_S3 extends AS3CF_Filter {
26
  add_filter( 'as3cf_filter_post_local_to_s3', array( $this, 'filter_post' ) ); // Backwards compatibility
27
  add_filter( 'as3cf_filter_post_local_to_provider', array( $this, 'filter_post' ) );
28
  // Widgets
29
- add_filter( 'widget_form_callback', array( $this, 'filter_widget_display' ), 10, 2 );
30
- add_filter( 'widget_display_callback', array( $this, 'filter_widget_display' ), 10, 2 );
 
 
 
 
31
  // Edit Media page
32
  add_filter( 'set_url_scheme', array( $this, 'set_url_scheme' ), 10, 3 );
 
 
 
 
 
 
33
  }
34
 
35
  /**
@@ -38,7 +48,7 @@ class AS3CF_Local_To_S3 extends AS3CF_Filter {
38
  * @param mixed $value
39
  * @param WP_Customize_Custom_CSS_Setting $setting
40
  *
41
- * @return mixed
42
  */
43
  public function filter_customize_value_custom_css( $value, $setting ) {
44
  return $this->filter_custom_css( $value, $setting->stylesheet );
@@ -102,13 +112,49 @@ class AS3CF_Local_To_S3 extends AS3CF_Filter {
102
  /**
103
  * Filter widget display.
104
  *
105
- * @param array $instance
106
- * @param WP_Widget $class
107
  *
108
  * @return array
109
  */
110
- public function filter_widget_display( $instance, $class ) {
111
- return $this->handle_widget( $instance, $class );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  }
113
 
114
  /**
@@ -131,35 +177,61 @@ class AS3CF_Local_To_S3 extends AS3CF_Filter {
131
  /**
132
  * Get URL
133
  *
134
- * @param int $attachment_id
135
- * @param null|string $size
136
  *
137
  * @return bool|string
138
  */
139
- protected function get_url( $attachment_id, $size = null ) {
140
- return $this->as3cf->get_attachment_url( $attachment_id, null, $size );
 
 
 
 
 
 
 
 
 
 
 
 
 
141
  }
142
 
143
  /**
144
  * Get base URL.
145
  *
146
- * @param int $attachment_id
147
  *
148
  * @return string|false
149
  */
150
- protected function get_base_url( $attachment_id ) {
151
- return $this->as3cf->get_attachment_local_url( $attachment_id );
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  }
153
 
154
  /**
155
- * Get attachment ID from URL.
156
  *
157
  * @param string $url
158
  *
159
- * @return bool|int
160
  */
161
- public function get_attachment_id_from_url( $url ) {
162
- $results = $this->get_attachment_ids_from_urls( array( $url ) );
163
 
164
  if ( empty( $results ) ) {
165
  return false;
@@ -175,13 +247,13 @@ class AS3CF_Local_To_S3 extends AS3CF_Filter {
175
  }
176
 
177
  /**
178
- * Get attachment IDs from URLs.
179
  *
180
  * @param array $urls
181
  *
182
- * @return array url => attachment ID (or false)
183
  */
184
- protected function get_attachment_ids_from_urls( $urls ) {
185
  $results = array();
186
 
187
  if ( empty( $urls ) ) {
@@ -226,24 +298,28 @@ class AS3CF_Local_To_S3 extends AS3CF_Filter {
226
  }
227
 
228
  if ( ! empty( $paths ) ) {
229
- $as3cf_items = Media_Library_Item::get_by_source_path( array_keys( $paths ) );
230
 
231
  if ( ! empty( $as3cf_items ) ) {
232
- /* @var Media_Library_Item $as3cf_item */
233
  foreach ( $as3cf_items as $as3cf_item ) {
234
  // Each returned item may have matched on either the source_path or original_source_path.
235
- // Because the base image file name of a thumbnail might match the original rather scaled or rotated full image
236
  // it's possible that both source paths are used by separate URLs.
237
  foreach ( array( $as3cf_item->source_path(), $as3cf_item->original_source_path() ) as $source_path ) {
238
  if ( ! empty( $paths[ $source_path ] ) ) {
239
  $matched_full_url = $paths[ $source_path ];
240
 
241
  if ( ! empty( $full_urls[ $matched_full_url ] ) ) {
242
- $attachment_id = $as3cf_item->source_id();
243
- $this->query_cache[ $matched_full_url ] = $attachment_id;
 
 
 
 
244
 
245
  foreach ( $full_urls[ $matched_full_url ] as $url ) {
246
- $results[ $url ] = $attachment_id;
247
  }
248
  unset( $full_urls[ $matched_full_url ] );
249
  }
@@ -252,7 +328,7 @@ class AS3CF_Local_To_S3 extends AS3CF_Filter {
252
  }
253
  }
254
 
255
- // No more attachment IDs found, set remaining results as false.
256
  if ( count( $query_set ) !== count( $results ) ) {
257
  foreach ( $full_urls as $full_url => $schema_urls ) {
258
  foreach ( $schema_urls as $url ) {
@@ -369,4 +445,54 @@ class AS3CF_Local_To_S3 extends AS3CF_Filter {
369
 
370
  return $url;
371
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
372
  }
1
  <?php
2
 
3
+ use DeliciousBrains\WP_Offload_Media\Items\Item;
4
 
5
  class AS3CF_Local_To_S3 extends AS3CF_Filter {
6
 
26
  add_filter( 'as3cf_filter_post_local_to_s3', array( $this, 'filter_post' ) ); // Backwards compatibility
27
  add_filter( 'as3cf_filter_post_local_to_provider', array( $this, 'filter_post' ) );
28
  // Widgets
29
+ add_filter( 'widget_form_callback', array( $this, 'filter_widget_display' ) );
30
+ add_filter( 'widget_display_callback', array( $this, 'filter_widget_display' ) );
31
+ if ( function_exists( 'is_wp_version_compatible' ) && is_wp_version_compatible( '5.8' ) ) {
32
+ add_filter( 'customize_value_widget_block', array( $this, 'filter_customize_value_widget_block' ) );
33
+ add_filter( 'widget_block_content', array( $this, 'filter_widget_block_content' ) );
34
+ }
35
  // Edit Media page
36
  add_filter( 'set_url_scheme', array( $this, 'set_url_scheme' ), 10, 3 );
37
+ // Blocks
38
+ if ( function_exists( 'is_wp_version_compatible' ) && is_wp_version_compatible( '5.9' ) ) {
39
+ add_filter( 'render_block', array( $this, 'filter_post' ), 100 );
40
+ add_filter( 'get_block_templates', array( $this, 'filter_get_block_templates' ), 100, 3 );
41
+ add_filter( 'get_block_template', array( $this, 'filter_get_block_template' ), 100, 3 );
42
+ }
43
  }
44
 
45
  /**
48
  * @param mixed $value
49
  * @param WP_Customize_Custom_CSS_Setting $setting
50
  *
51
+ * @return string
52
  */
53
  public function filter_customize_value_custom_css( $value, $setting ) {
54
  return $this->filter_custom_css( $value, $setting->stylesheet );
112
  /**
113
  * Filter widget display.
114
  *
115
+ * @param array $instance
 
116
  *
117
  * @return array
118
  */
119
+ public function filter_widget_display( $instance ) {
120
+ return $this->handle_widget( $instance );
121
+ }
122
+
123
+ /**
124
+ * Filters the content of the block widget during initial load of the customizer.
125
+ *
126
+ * @param array $value The widget block.
127
+ *
128
+ * @return array
129
+ */
130
+ public function filter_customize_value_widget_block( $value ) {
131
+ return $this->handle_widget( $value );
132
+ }
133
+
134
+ /**
135
+ * Filters the content of the block widget before output.
136
+ *
137
+ * @param string $content The widget content.
138
+ *
139
+ * @return string
140
+ */
141
+ public function filter_widget_block_content( $content ) {
142
+ if ( empty( $content ) ) {
143
+ return $content;
144
+ }
145
+
146
+ $cache = $this->get_option_cache();
147
+ $to_cache = array();
148
+
149
+ $changed_content = $this->process_content( $content, $cache, $to_cache );
150
+
151
+ if ( ! empty( $changed_content ) && $changed_content !== $content ) {
152
+ $content = $changed_content;
153
+ }
154
+
155
+ $this->maybe_update_option_cache( $to_cache );
156
+
157
+ return $content;
158
  }
159
 
160
  /**
177
  /**
178
  * Get URL
179
  *
180
+ * @param array $item_source
181
+ * @param null|string $object_key
182
  *
183
  * @return bool|string
184
  */
185
+ protected function get_url( $item_source, $object_key = null ) {
186
+ if ( empty( $item_source['id'] ) || empty( $item_source['source_type'] ) ) {
187
+ return false;
188
+ }
189
+
190
+ /**
191
+ * Return the provider URL for an item
192
+ *
193
+ * @param string|false $url Url for the item, false if no URL can be determined
194
+ * @param array $item_source Associative array describing the item, guaranteed keys:-
195
+ * id: source item's unique integer id
196
+ * source_type: source item's string type identifier
197
+ * @param string|null $object_key Object key (size) describing what sub file of an item to return url for
198
+ */
199
+ return apply_filters( 'as3cf_get_provider_url_for_item_source', false, $item_source, $object_key );
200
  }
201
 
202
  /**
203
  * Get base URL.
204
  *
205
+ * @param array $item_source
206
  *
207
  * @return string|false
208
  */
209
+ protected function get_base_url( $item_source ) {
210
+ if ( empty( $item_source['id'] ) || empty( $item_source['source_type'] ) ) {
211
+ return false;
212
+ }
213
+
214
+ /**
215
+ * Return the local URL for an item
216
+ *
217
+ * @param string|false $url Url for the item, false if no URL can be determined
218
+ * @param array $item_source Associative array describing the item, guaranteed keys:-
219
+ * id: source item's unique integer id
220
+ * source_type: source item's string type identifier
221
+ * @param string|null $object_key Object key (size) describing what sub file of an item to return url for
222
+ */
223
+ return apply_filters( 'as3cf_get_local_url_for_item_source', false, $item_source, null );
224
  }
225
 
226
  /**
227
+ * Get item source descriptor from URL.
228
  *
229
  * @param string $url
230
  *
231
+ * @return bool|array
232
  */
233
+ public function get_item_source_from_url( $url ) {
234
+ $results = $this->get_item_sources_from_urls( array( $url ) );
235
 
236
  if ( empty( $results ) ) {
237
  return false;
247
  }
248
 
249
  /**
250
+ * Get item source descriptors from URLs.
251
  *
252
  * @param array $urls
253
  *
254
+ * @return array url => item source descriptor array (or false)
255
  */
256
+ protected function get_item_sources_from_urls( $urls ) {
257
  $results = array();
258
 
259
  if ( empty( $urls ) ) {
298
  }
299
 
300
  if ( ! empty( $paths ) ) {
301
+ $as3cf_items = Item::get_by_source_path( array_keys( $paths ) );
302
 
303
  if ( ! empty( $as3cf_items ) ) {
304
+ /* @var Item $as3cf_item */
305
  foreach ( $as3cf_items as $as3cf_item ) {
306
  // Each returned item may have matched on either the source_path or original_source_path.
307
+ // Because the base image file name of a thumbnail might match the primary rather scaled or rotated full image
308
  // it's possible that both source paths are used by separate URLs.
309
  foreach ( array( $as3cf_item->source_path(), $as3cf_item->original_source_path() ) as $source_path ) {
310
  if ( ! empty( $paths[ $source_path ] ) ) {
311
  $matched_full_url = $paths[ $source_path ];
312
 
313
  if ( ! empty( $full_urls[ $matched_full_url ] ) ) {
314
+ $item_source = array(
315
+ 'id' => $as3cf_item->source_id(),
316
+ 'source_type' => $as3cf_item->source_type(),
317
+ );
318
+
319
+ $this->query_cache[ $matched_full_url ] = $item_source;
320
 
321
  foreach ( $full_urls[ $matched_full_url ] as $url ) {
322
+ $results[ $url ] = $item_source;
323
  }
324
  unset( $full_urls[ $matched_full_url ] );
325
  }
328
  }
329
  }
330
 
331
+ // No more item IDs found, set remaining results as false.
332
  if ( count( $query_set ) !== count( $results ) ) {
333
  foreach ( $full_urls as $full_url => $schema_urls ) {
334
  foreach ( $schema_urls as $url ) {
445
 
446
  return $url;
447
  }
448
+
449
+ /**
450
+ * Filters the array of queried block templates array after they've been fetched.
451
+ *
452
+ * @param WP_Block_Template[] $query_result Array of found block templates.
453
+ * @param array $query Arguments to retrieve templates.
454
+ * @param string $template_type wp_template or wp_template_part.
455
+ *
456
+ * @return WP_Block_Template[]
457
+ */
458
+ public function filter_get_block_templates( $query_result, $query, $template_type ) {
459
+ if ( empty( $query_result ) ) {
460
+ return $query_result;
461
+ }
462
+
463
+ foreach ( $query_result as $block_template ) {
464
+ $block_template = $this->filter_get_block_template( $block_template, $block_template->id, $template_type );
465
+ }
466
+
467
+ return $query_result;
468
+ }
469
+
470
+ /**
471
+ * Filters the queried block template object after it's been fetched.
472
+ *
473
+ * @param WP_Block_Template|null $block_template The found block template, or null if there isn't one.
474
+ * @param string $id Template unique identifier (example: theme_slug//template_slug).
475
+ * @param string $template_type Template type: `'wp_template'` or '`wp_template_part'`.
476
+ *
477
+ * @return WP_Block_Template|null
478
+ */
479
+ public function filter_get_block_template( $block_template, $id, $template_type ) {
480
+ if ( empty( $block_template ) ) {
481
+ return $block_template;
482
+ }
483
+
484
+ $content = $block_template->content;
485
+
486
+ if ( empty( $content ) ) {
487
+ return $block_template;
488
+ }
489
+
490
+ $content = $this->filter_post( $content );
491
+
492
+ if ( ! empty( $content ) && $content !== $block_template->content ) {
493
+ $block_template->content = $content;
494
+ }
495
+
496
+ return $block_template;
497
+ }
498
  }
classes/filters/as3cf-s3-to-local.php CHANGED
@@ -1,6 +1,6 @@
1
  <?php
2
 
3
- use DeliciousBrains\WP_Offload_Media\Items\Media_Library_Item;
4
 
5
  class AS3CF_S3_To_Local extends AS3CF_Filter {
6
 
@@ -21,9 +21,8 @@ class AS3CF_S3_To_Local extends AS3CF_Filter {
21
  add_filter( 'as3cf_filter_post_s3_to_local', array( $this, 'filter_post' ) ); // Backwards compatibility
22
  add_filter( 'as3cf_filter_post_provider_to_local', array( $this, 'filter_post' ) );
23
  // Widgets
24
- add_filter( 'widget_update_callback', array( $this, 'filter_widget_save' ), 10, 4 );
25
- // Srcset handling
26
- add_filter( 'wp_image_file_matches_image_meta', array( $this, 'image_file_matches_image_meta' ), 10, 4 );
27
  }
28
 
29
  /**
@@ -43,16 +42,32 @@ class AS3CF_S3_To_Local extends AS3CF_Filter {
43
  /**
44
  * Filter widget on save.
45
  *
46
- * @param array $instance
47
- * @param array $new_instance
48
- * @param array $old_instance
49
- * @param WP_Widget $class
50
  *
51
  * @return array
52
  *
53
  */
54
- public function filter_widget_save( $instance, $new_instance, $old_instance, $class ) {
55
- return $this->handle_widget( $instance, $class );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  }
57
 
58
  /**
@@ -89,54 +104,80 @@ class AS3CF_S3_To_Local extends AS3CF_Filter {
89
  /**
90
  * Get URL
91
  *
92
- * @param int $attachment_id
93
- * @param null|string $size
94
  *
95
  * @return bool|string
96
  */
97
- protected function get_url( $attachment_id, $size = null ) {
98
- return $this->as3cf->get_attachment_local_url_size( $attachment_id, $size );
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  }
100
 
101
  /**
102
  * Get base URL.
103
  *
104
- * @param int $attachment_id
105
  *
106
  * @return string|false
107
  */
108
- protected function get_base_url( $attachment_id ) {
109
- return $this->as3cf->get_attachment_url( $attachment_id );
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  }
111
 
112
  /**
113
- * Get attachment ID from URL.
114
  *
115
  * @param string $url
116
  *
117
  * @return bool|int
118
  */
119
- public function get_attachment_id_from_url( $url ) {
120
  // Result for sized URL already cached in request, return it.
121
  if ( isset( $this->query_cache[ $url ] ) ) {
122
  return $this->query_cache[ $url ];
123
  }
124
 
125
- $post_id = Media_Library_Item::get_source_id_by_remote_url( $url );
126
 
127
- if ( $post_id ) {
128
- $this->query_cache[ $url ] = $post_id;
129
 
130
- return $post_id;
131
  }
132
 
133
  $full_url = AS3CF_Utils::remove_size_from_filename( $url );
134
 
135
  // If we've already tried to find this URL above because it didn't have a size suffix, cache and return.
136
  if ( $url === $full_url ) {
137
- $this->query_cache[ $url ] = $post_id;
138
 
139
- return $post_id;
140
  }
141
 
142
  // Result for URL already cached in request whether found or not, return it.
@@ -144,21 +185,21 @@ class AS3CF_S3_To_Local extends AS3CF_Filter {
144
  return $this->query_cache[ $full_url ];
145
  }
146
 
147
- $post_id = Media_Library_Item::get_source_id_by_remote_url( $full_url );
148
 
149
- $this->query_cache[ $full_url ] = $post_id;
150
 
151
- return $post_id;
152
  }
153
 
154
  /**
155
- * Get attachment IDs from URLs.
156
  *
157
  * @param array $urls
158
  *
159
- * @return array url => attachment ID (or false)
160
  */
161
- protected function get_attachment_ids_from_urls( $urls ) {
162
  $results = array();
163
 
164
  if ( empty( $urls ) ) {
@@ -170,7 +211,7 @@ class AS3CF_S3_To_Local extends AS3CF_Filter {
170
  }
171
 
172
  foreach ( $urls as $url ) {
173
- $results[ $url ] = $this->get_attachment_id_from_url( $url );
174
  }
175
 
176
  return $results;
@@ -219,25 +260,4 @@ class AS3CF_S3_To_Local extends AS3CF_Filter {
219
  protected function pre_replace_content( $content ) {
220
  return $content;
221
  }
222
-
223
- /**
224
- * Determines if the image meta data is for the image source file.
225
- *
226
- * @handles wp_image_file_matches_image_meta
227
- *
228
- * @param bool $match
229
- * @param string $image_location
230
- * @param array $image_meta
231
- * @param int $attachment_id
232
- *
233
- * @return bool
234
- */
235
- public function image_file_matches_image_meta( $match, $image_location, $image_meta, $attachment_id ) {
236
- // If already matched or the URL is local, there's nothing for us to do.
237
- if ( $match || ! $this->url_needs_replacing( $image_location ) ) {
238
- return $match;
239
- }
240
-
241
- return $this->attachment_id_matches_src( $attachment_id, $image_location );
242
- }
243
  }
1
  <?php
2
 
3
+ use DeliciousBrains\WP_Offload_Media\Items\Item;
4
 
5
  class AS3CF_S3_To_Local extends AS3CF_Filter {
6
 
21
  add_filter( 'as3cf_filter_post_s3_to_local', array( $this, 'filter_post' ) ); // Backwards compatibility
22
  add_filter( 'as3cf_filter_post_provider_to_local', array( $this, 'filter_post' ) );
23
  // Widgets
24
+ add_filter( 'widget_update_callback', array( $this, 'filter_widget_save' ) );
25
+ add_filter( 'pre_update_option_widget_block', array( $this, 'filter_widget_block_save' ) );
 
26
  }
27
 
28
  /**
42
  /**
43
  * Filter widget on save.
44
  *
45
+ * @param array $instance
 
 
 
46
  *
47
  * @return array
48
  *
49
  */
50
+ public function filter_widget_save( $instance ) {
51
+ return $this->handle_widget( $instance );
52
+ }
53
+
54
+ /**
55
+ * Filter widget block on save.
56
+ *
57
+ * @param array $value The new, unserialized option value.
58
+ *
59
+ * @return array
60
+ */
61
+ public function filter_widget_block_save( $value ) {
62
+ if ( empty( $value ) || ! is_array( $value ) ) {
63
+ return $value;
64
+ }
65
+
66
+ foreach ( $value as $idx => $section ) {
67
+ $value[ $idx ] = $this->handle_widget( $section );
68
+ }
69
+
70
+ return $value;
71
  }
72
 
73
  /**
104
  /**
105
  * Get URL
106
  *
107
+ * @param array $item_source
108
+ * @param null|string $object_key
109
  *
110
  * @return bool|string
111
  */
112
+ protected function get_url( $item_source, $object_key = null ) {
113
+ if ( empty( $item_source['id'] ) || empty( $item_source['source_type'] ) ) {
114
+ return false;
115
+ }
116
+
117
+ /**
118
+ * Return the local URL for an item
119
+ *
120
+ * @param string|false $url Url for the item, false if no URL can be determined
121
+ * @param array $item_source Associative array describing the item, guaranteed keys:-
122
+ * id: source item's unique integer id
123
+ * source_type: source item's string type identifier
124
+ * @param string|null $object_key Object key (size) describing what sub file of an item to return url for
125
+ */
126
+ return apply_filters( 'as3cf_get_local_url_for_item_source', false, $item_source, $object_key );
127
  }
128
 
129
  /**
130
  * Get base URL.
131
  *
132
+ * @param array $item_source
133
  *
134
  * @return string|false
135
  */
136
+ protected function get_base_url( $item_source ) {
137
+ if ( empty( $item_source['id'] ) || empty( $item_source['source_type'] ) ) {
138
+ return false;
139
+ }
140
+
141
+ /**
142
+ * Return the provider URL for an item
143
+ *
144
+ * @param string|false $url Url for the item, false if no URL can be determined
145
+ * @param array $item_source Associative array describing the item, guaranteed keys:-
146
+ * id: source item's unique integer id
147
+ * source_type: source item's string type identifier
148
+ * @param string|null $object_key Object key (size) describing what sub file of an item to return url for
149
+ */
150
+ return apply_filters( 'as3cf_get_provider_url_for_item_source', false, $item_source, null );
151
  }
152
 
153
  /**
154
+ * Get item source descriptor from URL.
155
  *
156
  * @param string $url
157
  *
158
  * @return bool|int
159
  */
160
+ public function get_item_source_from_url( $url ) {
161
  // Result for sized URL already cached in request, return it.
162
  if ( isset( $this->query_cache[ $url ] ) ) {
163
  return $this->query_cache[ $url ];
164
  }
165
 
166
+ $item_source = Item::get_item_source_by_remote_url( $url );
167
 
168
+ if ( ! empty( $item_source['id'] ) ) {
169
+ $this->query_cache[ $url ] = $item_source;
170
 
171
+ return $item_source;
172
  }
173
 
174
  $full_url = AS3CF_Utils::remove_size_from_filename( $url );
175
 
176
  // If we've already tried to find this URL above because it didn't have a size suffix, cache and return.
177
  if ( $url === $full_url ) {
178
+ $this->query_cache[ $url ] = $item_source;
179
 
180
+ return $item_source;
181
  }
182
 
183
  // Result for URL already cached in request whether found or not, return it.
185
  return $this->query_cache[ $full_url ];
186
  }
187
 
188
+ $item_source = Item::get_item_source_by_remote_url( $full_url );
189
 
190
+ $this->query_cache[ $full_url ] = ! empty( $item_source['id'] ) ? $item_source : false;
191
 
192
+ return $this->query_cache[ $full_url ];
193
  }
194
 
195
  /**
196
+ * Get item source descriptors from URLs.
197
  *
198
  * @param array $urls
199
  *
200
+ * @return array url => item source descriptor (or false)
201
  */
202
+ protected function get_item_sources_from_urls( $urls ) {
203
  $results = array();
204
 
205
  if ( empty( $urls ) ) {
211
  }
212
 
213
  foreach ( $urls as $url ) {
214
+ $results[ $url ] = $this->get_item_source_from_url( $url );
215
  }
216
 
217
  return $results;
260
  protected function pre_replace_content( $content ) {
261
  return $content;
262
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
263
  }
classes/integrations/core.php ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace DeliciousBrains\WP_Offload_Media\Integrations;
4
+
5
+ use DeliciousBrains\WP_Offload_Media\Items\Item;
6
+ use DeliciousBrains\WP_Offload_Media\Items\Remove_Local_Handler;
7
+ use DeliciousBrains\WP_Offload_Media\Items\Upload_Handler;
8
+ use WP_Error;
9
+
10
+ class Core extends Integration {
11
+ /**
12
+ * Is installed?
13
+ *
14
+ * @return bool
15
+ */
16
+ public static function is_installed() {
17
+ return true;
18
+ }
19
+
20
+ /**
21
+ * Init integration.
22
+ */
23
+ public function init() {
24
+ add_action( 'as3cf_post_handle_item_' . Upload_Handler::get_item_handler_key_name(), array( $this, 'maybe_remove_local_files' ), 10, 3 );
25
+ }
26
+
27
+ /**
28
+ * After an upload completes, maybe remove local files.
29
+ *
30
+ * @handles as3cf_post_handle_item_upload
31
+ *
32
+ * @param bool|WP_Error $result Result for the action, either handled (true/false), or an error.
33
+ * @param Item $as3cf_item The item that the action was being handled for.
34
+ * @param array $options Handler dependent options that may have been set for the action.
35
+ */
36
+ public function maybe_remove_local_files( $result, Item $as3cf_item, array $options ) {
37
+ if ( ! is_wp_error( $result ) && $this->as3cf->get_setting( 'remove-local-file', false ) && $as3cf_item->exists_locally() ) {
38
+ $remove_local_handler = $this->as3cf->get_item_handler( Remove_Local_Handler::get_item_handler_key_name() );
39
+
40
+ $remove_local_handler->handle( $as3cf_item );
41
+ }
42
+ }
43
+ }
classes/integrations/integration-manager.php ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace DeliciousBrains\WP_Offload_Media\Integrations;
4
+
5
+ class Integration_Manager {
6
+
7
+ /**
8
+ * @var Integration_Manager
9
+ */
10
+ protected static $instance;
11
+
12
+ /**
13
+ * @var array
14
+ */
15
+ private $integrations;
16
+
17
+ /**
18
+ * Protected constructor to prevent creating a new instance of the
19
+ * class via the `new` operator from outside this class.
20
+ */
21
+ protected function __construct() {
22
+ $this->integrations = array();
23
+ }
24
+
25
+ /**
26
+ * Make this class a singleton.
27
+ *
28
+ * Use this instead of __construct().
29
+ *
30
+ * @return Integration_Manager
31
+ */
32
+ public static function get_instance() {
33
+ if ( ! isset( static::$instance ) && ! ( self::$instance instanceof Integration_Manager ) ) {
34
+ static::$instance = new Integration_Manager();
35
+ }
36
+
37
+ return static::$instance;
38
+ }
39
+
40
+ /**
41
+ * Getter for integration class instance
42
+ *
43
+ * @param string $integration_key
44
+ *
45
+ * @return Integration|null
46
+ */
47
+ public function get_integration( $integration_key ) {
48
+ if ( ! empty( $this->integrations[ $integration_key ] ) ) {
49
+ return $this->integrations[ $integration_key ];
50
+ }
51
+
52
+ return null;
53
+ }
54
+
55
+ /**
56
+ * Register integration.
57
+ *
58
+ * @param string $integration_key
59
+ * @param Integration $integration
60
+ */
61
+ public function register_integration( $integration_key, Integration $integration ) {
62
+ if ( $integration::is_installed() ) {
63
+ $integration->init();
64
+ }
65
+
66
+ $this->integrations[ $integration_key ] = $integration;
67
+ }
68
+
69
+ /**
70
+ * As this class is a singleton it should not be clone-able.
71
+ */
72
+ protected function __clone() {
73
+ }
74
+
75
+ /**
76
+ * As this class is a singleton it should not be able to be unserialized.
77
+ */
78
+ public function __wakeup() {
79
+ }
80
+
81
+ }
classes/integrations/integration.php ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace DeliciousBrains\WP_Offload_Media\Integrations;
4
+
5
+ use Amazon_S3_And_CloudFront;
6
+
7
+ abstract class Integration {
8
+
9
+ /**
10
+ * @var Amazon_S3_And_CloudFront
11
+ */
12
+ protected $as3cf;
13
+
14
+ /**
15
+ * Integration constructor.
16
+ *
17
+ * @param Amazon_S3_And_CloudFront $as3cf
18
+ */
19
+ public function __construct( $as3cf ) {
20
+ $this->as3cf = $as3cf;
21
+ }
22
+
23
+ /**
24
+ * Is installed?
25
+ *
26
+ * @return bool
27
+ */
28
+ public static function is_installed() {
29
+ return false;
30
+ }
31
+
32
+ /**
33
+ * Init integration.
34
+ */
35
+ abstract public function init();
36
+
37
+ }
classes/integrations/media-library.php ADDED
@@ -0,0 +1,1551 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace DeliciousBrains\WP_Offload_Media\Integrations;
4
+
5
+ use AS3CF_Error;
6
+ use AS3CF_Utils;
7
+ use DeliciousBrains\WP_Offload_Media\Items\Item;
8
+ use DeliciousBrains\WP_Offload_Media\Items\Media_Library_Item;
9
+ use DeliciousBrains\WP_Offload_Media\Items\Remove_Provider_Handler;
10
+ use DeliciousBrains\WP_Offload_Media\Items\Upload_Handler;
11
+ use Exception;
12
+ use WP_Error;
13
+ use WP_Post;
14
+
15
+ class Media_Library extends Integration {
16
+ /**
17
+ * Is the current process deleting an attachment?
18
+ *
19
+ * @var bool
20
+ */
21
+ private $deleting_attachment = false;
22
+
23
+ /**
24
+ * Keep track of items that are being updated multiple times in one request. I.e. when WP
25
+ * calls wp_update_attachment_metadata repeatedly during thumbnail generation
26
+ *
27
+ * @var array
28
+ */
29
+ protected $items_in_progress = array();
30
+
31
+ /**
32
+ * Keep track of items that has been replaced by an edit image operation
33
+ *
34
+ * @var array
35
+ */
36
+ protected $replaced_object_keys = array();
37
+
38
+ /**
39
+ * Init Media Library integration.
40
+ */
41
+ public function init() {
42
+ // Filter from WordPress media library handling, plugin needs to be set up
43
+ add_filter( 'wp_unique_filename', array( $this, 'wp_unique_filename' ), 10, 3 );
44
+ add_filter( 'wp_update_attachment_metadata', array( $this, 'wp_update_attachment_metadata' ), 110, 2 );
45
+ add_filter( 'pre_delete_attachment', array( $this, 'pre_delete_attachment' ), 20 );
46
+ add_filter( 'delete_attachment', array( $this, 'delete_attachment' ), 20 );
47
+ add_action( 'delete_post', array( $this, 'delete_post' ) );
48
+ add_filter( 'update_attached_file', array( $this, 'update_attached_file' ), 100, 2 );
49
+ add_filter( 'update_post_metadata', array( $this, 'update_post_metadata' ), 100, 5 );
50
+
51
+ // Attachment screens/modals
52
+ add_action( 'load-upload.php', array( $this, 'load_media_assets' ), 11 );
53
+ add_action( 'admin_enqueue_scripts', array( $this, 'load_attachment_assets' ), 11 );
54
+ add_action( 'add_meta_boxes', array( $this, 'attachment_provider_meta_box' ) );
55
+
56
+ // AJAX
57
+ add_action( 'wp_ajax_as3cf_get_attachment_provider_details', array( $this, 'ajax_get_attachment_provider_details' ) );
58
+
59
+ // Rewriting URLs, doesn't depend on plugin being set up
60
+ add_filter( 'wp_get_attachment_url', array( $this, 'wp_get_attachment_url' ), 99, 2 );
61
+ add_filter( 'wp_get_attachment_image_attributes', array( $this, 'wp_get_attachment_image_attributes' ), 99, 3 );
62
+ add_filter( 'get_image_tag', array( $this, 'maybe_encode_get_image_tag' ), 99, 6 );
63
+ add_filter( 'wp_get_attachment_image_src', array( $this, 'maybe_encode_wp_get_attachment_image_src' ), 99, 4 );
64
+ add_filter( 'wp_prepare_attachment_for_js', array( $this, 'maybe_encode_wp_prepare_attachment_for_js', ), 99, 3 );
65
+ add_filter( 'image_get_intermediate_size', array( $this, 'maybe_encode_image_get_intermediate_size' ), 99, 3 );
66
+ add_filter( 'get_attached_file', array( $this, 'get_attached_file' ), 10, 2 );
67
+ add_filter( 'wp_get_original_image_path', array( $this, 'get_attached_file' ), 10, 2 );
68
+ add_filter( 'wp_audio_shortcode', array( $this, 'wp_media_shortcode' ), 100, 5 );
69
+ add_filter( 'wp_video_shortcode', array( $this, 'wp_media_shortcode' ), 100, 5 );
70
+
71
+ // Srcset handling
72
+ add_filter( 'wp_image_file_matches_image_meta', array( $this, 'image_file_matches_image_meta' ), 10, 4 );
73
+
74
+ // Internal filters and actions
75
+ add_filter( 'as3cf_get_provider_url_for_item_source', array( $this, 'filter_get_provider_url_for_item_source' ), 10, 3 );
76
+ add_filter( 'as3cf_get_local_url_for_item_source', array( $this, 'filter_get_local_url_for_item_source' ), 10, 3 );
77
+ add_filter( 'as3cf_get_size_string_from_url_for_item_source', array( $this, 'get_size_string_from_url_for_item_source' ), 10, 3 );
78
+ add_filter( 'as3cf_get_item_secure_url', array( $this, 'get_item_secure_url' ), 10, 5 );
79
+ add_filter( 'as3cf_get_item_url', array( $this, 'get_item_url' ), 10, 5 );
80
+ add_filter( 'as3cf_remove_local_files', array( $this, 'filter_remove_local_files' ), 10, 3 );
81
+ add_filter( 'as3cf_remove_source_files_from_provider', array( $this, 'filter_remove_source_files_from_provider' ), 10, 3 );
82
+ add_action( 'as3cf_post_upload_item', array( $this, 'post_upload_item' ), 10, 1 );
83
+ add_filter( 'as3cf_pre_handle_item_' . Upload_Handler::get_item_handler_key_name(), array( $this, 'pre_handle_item_upload' ), 10, 3 );
84
+ add_filter( 'as3cf_upload_object_key_as_private', array( $this, 'filter_upload_object_key_as_private' ), 10, 3 );
85
+ add_action( 'as3cf_pre_upload_object', array( $this, 'action_pre_upload_object' ), 10, 2 );
86
+
87
+ Media_Library_Item::init_cache();
88
+ }
89
+
90
+ /**
91
+ * Is installed?
92
+ *
93
+ * @return bool
94
+ */
95
+ public static function is_installed() {
96
+ return true;
97
+ }
98
+
99
+ /**
100
+ * Handles the upload of the attachment to provider when an attachment is updated.
101
+ *
102
+ * @handles wp_update_attachment_metadata
103
+ *
104
+ * @param array $data meta data for attachment
105
+ * @param int $post_id
106
+ *
107
+ * @return array|WP_Error
108
+ * @throws Exception
109
+ */
110
+ public function wp_update_attachment_metadata( $data, $post_id ) {
111
+ if ( ! $this->as3cf->is_plugin_setup( true ) ) {
112
+ return $data;
113
+ }
114
+
115
+ // Some other filter may already have corrupted $data
116
+ if ( is_wp_error( $data ) ) {
117
+ return $data;
118
+ }
119
+
120
+ // Protect against updates of partially formed metadata since WordPress 5.3.
121
+ // Checks whether new upload currently has no subsizes recorded but is expected to have subsizes during upload,
122
+ // and if so, are any of its currently missing sizes part of the set.
123
+ if ( ! empty( $data ) && function_exists( 'wp_get_registered_image_subsizes' ) && function_exists( 'wp_get_missing_image_subsizes' ) ) {
124
+
125
+ /**
126
+ * Plugin compat may require that we wait for wp_generate_attachment_metadata
127
+ * to be run before proceeding with uploading. I.e. Regenerate Thumbnails requires this.
128
+ *
129
+ * @param bool True if we should wait AND generate_attachment_metadata hasn't run yet
130
+ *
131
+ */
132
+ if ( apply_filters( 'as3cf_wait_for_generate_attachment_metadata', false ) ) {
133
+ return $data;
134
+ }
135
+
136
+ if ( empty( $data['sizes'] ) && wp_attachment_is_image( $post_id ) ) {
137
+
138
+ // There is no unified way of checking whether subsizes are expected, so we have to duplicate WordPress code here.
139
+ $new_sizes = wp_get_registered_image_subsizes();
140
+ $new_sizes = apply_filters( 'intermediate_image_sizes_advanced', $new_sizes, $data, $post_id );
141
+ $missing_sizes = wp_get_missing_image_subsizes( $post_id );
142
+
143
+ if ( ! empty( $new_sizes ) && ! empty( $missing_sizes ) && array_intersect_key( $missing_sizes, $new_sizes ) ) {
144
+ return $data;
145
+ }
146
+ }
147
+ }
148
+
149
+ // Is this a new item that we're already started working on in this request?
150
+ if ( ! empty( $this->items_in_progress[ $post_id ] ) ) {
151
+ $as3cf_item = $this->items_in_progress[ $post_id ];
152
+ }
153
+
154
+ // Is this an update for an existing item.
155
+ if ( empty( $as3cf_item ) || is_wp_error( $as3cf_item ) ) {
156
+ $as3cf_item = Media_Library_Item::get_by_source_id( $post_id );
157
+ }
158
+
159
+ // Abort if not already uploaded to provider and the copy setting is off.
160
+ if ( ! $as3cf_item && ! $this->as3cf->get_setting( 'copy-to-s3' ) ) {
161
+ return $data;
162
+ }
163
+
164
+ if ( empty( $as3cf_item ) ) {
165
+ $as3cf_item = null;
166
+ }
167
+
168
+ /**
169
+ * Allows implementors to cancel uploading a Media Library item for any reason.
170
+ *
171
+ * This filter is triggered by updates to an attachment's metadata.
172
+ * To potentially cancel an upload started by any method,
173
+ * please use the 'as3cf_pre_upload_item' filter.
174
+ *
175
+ * @param bool $cancel True if the upload should be cancelled
176
+ * @param array $data Array describing the object being uploaded
177
+ * @param int $post_id Attachment's ID
178
+ * @param Media_Library_Item $as3cf_item The Media Library Item object if previously offloaded
179
+ *
180
+ * @see as3cf_pre_upload_item
181
+ */
182
+ $cancel = apply_filters( 'as3cf_pre_update_attachment_metadata', false, $data, $post_id, $as3cf_item );
183
+ if ( false !== $cancel ) {
184
+ return $data;
185
+ }
186
+
187
+ $offloaded_files = array();
188
+
189
+ // If we still don't have a valid item, create one from scratch.
190
+ if ( empty( $as3cf_item ) || is_wp_error( $as3cf_item ) ) {
191
+ $as3cf_item = Media_Library_Item::create_from_source_id( $post_id );
192
+ } else {
193
+ $offloaded_files = $as3cf_item->offloaded_files();
194
+ }
195
+
196
+ // Did we get a WP_Error?
197
+ if ( is_wp_error( $as3cf_item ) ) {
198
+ AS3CF_Error::Log( $as3cf_item->get_error_message() );
199
+
200
+ return $data;
201
+ }
202
+
203
+ // Or didn't we get anything at all?
204
+ if ( empty( $as3cf_item ) ) {
205
+ $message = sprintf( __( "Can't create item from media library item %d", 'amazon-s3-and-cloudfront' ), $post_id );
206
+ AS3CF_Error::Log( $message );
207
+
208
+ return $data;
209
+ }
210
+
211
+ // Update item's expected objects from attachment's new metadata.
212
+ $this->update_item_from_new_metadata( $as3cf_item, $data );
213
+
214
+ $this->upload_item( $as3cf_item, $offloaded_files );
215
+ $this->items_in_progress[ $post_id ] = $as3cf_item;
216
+
217
+ return $data;
218
+ }
219
+
220
+ /**
221
+ * Upload item.
222
+ *
223
+ * @param Media_Library_Item $as3cf_item
224
+ * @param array $offloaded_files An array of files previously offloaded for the item.
225
+ */
226
+ protected function upload_item( Media_Library_Item $as3cf_item, array $offloaded_files ) {
227
+ $upload_handler = $this->as3cf->get_item_handler( Upload_Handler::get_item_handler_key_name() );
228
+ $upload_result = $upload_handler->handle( $as3cf_item, array( 'offloaded_files' => $offloaded_files ) );
229
+
230
+ if ( is_wp_error( $upload_result ) ) {
231
+ foreach ( $upload_result->get_error_messages() as $error_message ) {
232
+ AS3CF_Error::Log( $error_message );
233
+ }
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Handle update_post_metadata for some media library related keys
239
+ *
240
+ * @handles update_post_metadata
241
+ *
242
+ * @param bool $check
243
+ * @param int $object_id
244
+ * @param string $meta_key
245
+ * @param mixed $meta_value
246
+ * @param mixed $prev_value
247
+ */
248
+ public function update_post_metadata( $check, $object_id, $meta_key, $meta_value, $prev_value ) {
249
+ if ( '_wp_attachment_backup_sizes' === $meta_key ) {
250
+ if ( $this->as3cf->is_plugin_setup( true ) ) {
251
+ $this->update_attachment_backup_sizes( $object_id, $meta_value );
252
+ }
253
+ }
254
+
255
+ return $check;
256
+ }
257
+
258
+ /**
259
+ * Handle updated attachment_backup_sizes.
260
+ *
261
+ * @param int $post_id
262
+ * @param array $sizes
263
+ */
264
+ protected function update_attachment_backup_sizes( $post_id, $sizes ) {
265
+ // This item should already be known in this request, if not bail out
266
+ if ( empty( $this->items_in_progress[ $post_id ] ) ) {
267
+ return;
268
+ }
269
+
270
+ // We should also have recorded some replaced keys in this request, if not bail
271
+ if ( empty( $this->replaced_object_keys[ $post_id ] ) ) {
272
+ return;
273
+ }
274
+
275
+ /** @var Media_Library_Item $as3cf_item */
276
+ $as3cf_item = $this->items_in_progress[ $post_id ];
277
+ $existing_objects = $as3cf_item->objects();
278
+
279
+ foreach ( array_keys( $sizes ) as $key ) {
280
+ if ( ! isset( $existing_objects[ $key ] ) ) {
281
+ $parts = explode( '-', $key );
282
+ $size = join( '-', array_slice( $parts, 0, -1 ) );
283
+ if ( 'full' === $size ) {
284
+ $size = Item::primary_object_key();
285
+ }
286
+
287
+ if ( isset( $this->replaced_object_keys[ $post_id ][ $size ] ) ) {
288
+ $existing_objects[ $key ] = $this->replaced_object_keys[ $post_id ][ $size ];
289
+ }
290
+ }
291
+ }
292
+
293
+ $as3cf_item->set_objects( $existing_objects );
294
+ $as3cf_item->save();
295
+ }
296
+
297
+ /**
298
+ * Filters the result when generating a unique file name.
299
+ *
300
+ * @param string $filename Unique file name.
301
+ * @param string $ext File extension, eg. ".png".
302
+ * @param string $dir Directory path.
303
+ *
304
+ * @return string
305
+ * @since 4.5.0
306
+ *
307
+ */
308
+ public function wp_unique_filename( $filename, $ext, $dir ) {
309
+ // Get Post ID if uploaded in post screen.
310
+ $post_id = filter_input( INPUT_POST, 'post_id', FILTER_VALIDATE_INT );
311
+
312
+ return $this->filter_unique_filename( $filename, $ext, $dir, $post_id );
313
+ }
314
+
315
+ /**
316
+ * Create unique names for file to be uploaded to AWS.
317
+ * This only applies when the remove local file option is enabled.
318
+ *
319
+ * @param string $filename Unique file name.
320
+ * @param string $ext File extension, eg. ".png".
321
+ * @param string $dir Directory path.
322
+ * @param int $post_id Attachment's parent Post ID.
323
+ *
324
+ * @return string
325
+ */
326
+ public function filter_unique_filename( $filename, $ext, $dir, $post_id = null ) {
327
+ if ( ! $this->as3cf->is_plugin_setup( true ) ) {
328
+ return $filename;
329
+ }
330
+
331
+ // sanitize the file name before we begin processing
332
+ $filename = sanitize_file_name( $filename );
333
+ $ext = strtolower( $ext );
334
+ $name = wp_basename( $filename, $ext );
335
+
336
+ // Edge case: if file is named '.ext', treat as an empty name.
337
+ if ( $name === $ext ) {
338
+ $name = '';
339
+ }
340
+
341
+ // Rebuild filename with lowercase extension as provider will have converted extension on upload.
342
+ $filename = $name . $ext;
343
+ $time = current_time( 'mysql' );
344
+
345
+ // Get time if uploaded in post screen.
346
+ if ( ! empty( $post_id ) ) {
347
+ $time = $this->get_post_time( $post_id );
348
+ }
349
+
350
+ if ( ! $this->as3cf->does_file_exist( $filename, $time ) ) {
351
+ // File doesn't exist locally or on provider, return it.
352
+ return $filename;
353
+ }
354
+
355
+ return $this->as3cf->generate_unique_filename( $name, $ext, $time );
356
+ }
357
+
358
+ /**
359
+ * Allow processes to update the file on provider via update_attached_file()
360
+ *
361
+ * @param string $file
362
+ * @param int $attachment_id
363
+ *
364
+ * @return string
365
+ */
366
+ public function update_attached_file( $file, $attachment_id ) {
367
+ if ( ! $this->as3cf->is_plugin_setup( true ) ) {
368
+ return $file;
369
+ }
370
+
371
+ $as3cf_item = Media_Library_Item::get_by_source_id( $attachment_id );
372
+
373
+ if ( ! $as3cf_item ) {
374
+ return $file;
375
+ }
376
+
377
+ /**
378
+ * Allow processes to update the file on provider via update_attached_file()
379
+ *
380
+ * @param string $file File name/path
381
+ * @param int $attachment_id Attachment id
382
+ * @param Media_Library_Item $as3cf_item The item object
383
+ */
384
+ return apply_filters( 'as3cf_update_attached_file', $file, $attachment_id, $as3cf_item );
385
+ }
386
+
387
+ /**
388
+ * Removes an attachment and intermediate image size files from provider
389
+ *
390
+ * @param int $post_id
391
+ */
392
+ public function delete_attachment( $post_id ) {
393
+ if ( ! $this->as3cf->is_plugin_setup( true ) ) {
394
+ return;
395
+ }
396
+
397
+ $as3cf_item = Media_Library_Item::get_by_source_id( $post_id );
398
+
399
+ if ( ! $as3cf_item ) {
400
+ return;
401
+ }
402
+
403
+ if ( ! $as3cf_item->served_by_provider( true ) ) {
404
+ return;
405
+ }
406
+
407
+ // Remove the objects from the provider
408
+ $remove_provider_handler = $this->as3cf->get_item_handler( Remove_Provider_Handler::get_item_handler_key_name() );
409
+ $remove_provider_handler->handle( $as3cf_item, array( 'verify_exists_on_local' => false ) );
410
+ $as3cf_item->delete();
411
+ }
412
+
413
+ /**
414
+ * Update an existing item's expected objects from attachment's new metadata.
415
+ *
416
+ * @param Media_Library_Item $as3cf_item
417
+ * @param array $metadata
418
+ */
419
+ protected function update_item_from_new_metadata( $as3cf_item, $metadata ) {
420
+ if ( empty( $metadata ) || ! is_array( $metadata ) ) {
421
+ return;
422
+ }
423
+
424
+ $files = AS3CF_Utils::get_attachment_file_paths( $as3cf_item->source_id(), false, $metadata );
425
+ $existing_basename = wp_basename( $as3cf_item->path() );
426
+ $existing_objects = $as3cf_item->objects();
427
+
428
+ if ( ! isset( $this->replaced_object_keys[ $as3cf_item->source_id() ] ) ) {
429
+ $this->replaced_object_keys[ $as3cf_item->source_id() ] = array();
430
+ }
431
+
432
+ foreach ( $files as $object_key => $file ) {
433
+ $new_filename = wp_basename( $file );
434
+
435
+ if ( ! empty( $existing_objects[ $object_key ]['source_file'] ) && $existing_objects[ $object_key ]['source_file'] !== $new_filename ) {
436
+ $this->replaced_object_keys[ $as3cf_item->source_id() ][ $object_key ] = $existing_objects[ $object_key ];
437
+ }
438
+
439
+ if ( Item::primary_object_key() === $object_key && $existing_basename !== $new_filename ) {
440
+ $as3cf_item->set_path( str_replace( $existing_basename, $new_filename, $as3cf_item->path() ) );
441
+ $as3cf_item->set_source_path( str_replace( $existing_basename, $new_filename, $as3cf_item->source_path() ) );
442
+ }
443
+
444
+ $existing_objects[ $object_key ] = array(
445
+ 'source_file' => $new_filename,
446
+ 'is_private' => isset( $existing_objects[ $object_key ]['is_private'] ) ? $existing_objects[ $object_key ]['is_private'] : false,
447
+ );
448
+ }
449
+
450
+ $extra_info = $as3cf_item->extra_info();
451
+ $extra_info['objects'] = $existing_objects;
452
+ $as3cf_item->set_extra_info( $extra_info );
453
+ }
454
+
455
+ /**
456
+ * Load media assets.
457
+ */
458
+ public function load_media_assets() {
459
+ $this->as3cf->enqueue_style( 'as3cf-media-styles', 'assets/css/media', array( 'as3cf-modal' ) );
460
+ $this->as3cf->enqueue_script( 'as3cf-media-script', 'assets/js/media', array(
461
+ 'jquery',
462
+ 'media-views',
463
+ 'media-grid',
464
+ 'wp-util',
465
+ ) );
466
+
467
+ wp_localize_script( 'as3cf-media-script', 'as3cf_media', array(
468
+ 'strings' => $this->get_media_action_strings(),
469
+ 'nonces' => array(
470
+ 'get_attachment_provider_details' => wp_create_nonce( 'get-attachment-s3-details' ),
471
+ ),
472
+ ) );
473
+ }
474
+
475
+ /**
476
+ * Load the attachment assets only when editing an attachment
477
+ *
478
+ * @param $hook_suffix
479
+ */
480
+ public function load_attachment_assets( $hook_suffix ) {
481
+ global $post;
482
+ if ( 'post.php' !== $hook_suffix || 'attachment' !== $post->post_type ) {
483
+ return;
484
+ }
485
+
486
+ $this->as3cf->enqueue_style( 'as3cf-pro-attachment-styles', 'assets/css/attachment', array( 'as3cf-modal' ) );
487
+
488
+ do_action( 'as3cf_load_attachment_assets' );
489
+ }
490
+
491
+ /**
492
+ * Add the S3 meta box to the attachment screen
493
+ */
494
+ public function attachment_provider_meta_box() {
495
+ add_meta_box(
496
+ 's3-actions',
497
+ __( 'Offload', 'amazon-s3-and-cloudfront' ),
498
+ array( $this, 'attachment_provider_actions_meta_box' ),
499
+ 'attachment',
500
+ 'side',
501
+ 'core'
502
+ );
503
+ }
504
+
505
+ /**
506
+ * Handle retrieving the provider details for attachment modals.
507
+ */
508
+ public function ajax_get_attachment_provider_details() {
509
+ if ( ! isset( $_POST['id'] ) ) {
510
+ return;
511
+ }
512
+
513
+ check_ajax_referer( 'get-attachment-s3-details', '_nonce' );
514
+
515
+ $id = intval( $_POST['id'] );
516
+ $as3cf_item = Media_Library_Item::get_by_source_id( $id );
517
+ $served_by_provider = false;
518
+
519
+ if ( ! empty( $as3cf_item ) ) {
520
+ $served_by_provider = $as3cf_item->served_by_provider( true );
521
+ }
522
+
523
+ // get the actions available for the attachment
524
+ $data = array(
525
+ 'links' => $this->add_media_row_actions( array(), $id ),
526
+ 'provider_object' => $this->get_formatted_provider_info( $id ),
527
+ 'acl_toggle' => $this->verify_media_actions() && $served_by_provider,
528
+ );
529
+
530
+ wp_send_json_success( $data );
531
+ }
532
+
533
+ /**
534
+ * Conditionally adds copy, remove and download S3 action links for an
535
+ * attachment on the Media library list view
536
+ *
537
+ * @param array $actions
538
+ * @param WP_Post|int $post
539
+ *
540
+ * @return array
541
+ */
542
+ public function add_media_row_actions( array $actions, $post ) {
543
+ return $actions;
544
+ }
545
+
546
+ /**
547
+ * Get a list of available media actions which can be performed according to plugin and user capability requirements.
548
+ *
549
+ * @param string|null $scope
550
+ *
551
+ * @return array
552
+ */
553
+ public function get_available_media_actions( $scope = '' ) {
554
+ return array();
555
+ }
556
+
557
+ /**
558
+ * Render the S3 attachment meta box
559
+ */
560
+ public function attachment_provider_actions_meta_box() {
561
+ global $post;
562
+ $file = get_attached_file( $post->ID, true );
563
+
564
+ $args = array(
565
+ 'provider_object' => $this->get_formatted_provider_info( $post->ID ),
566
+ 'post' => $post,
567
+ 'local_file_exists' => file_exists( $file ),
568
+ 'available_actions' => $this->get_available_media_actions( 'singular' ),
569
+ 'sendback' => 'post.php?post=' . $post->ID . '&action=edit',
570
+ );
571
+
572
+ $this->as3cf->render_view( 'attachment-metabox', $args );
573
+ }
574
+
575
+ /**
576
+ * Get attachment url
577
+ *
578
+ * @param string $url
579
+ * @param int $post_id
580
+ *
581
+ * @return bool|mixed|WP_Error
582
+ */
583
+ public function wp_get_attachment_url( $url, $post_id ) {
584
+ if ( $this->as3cf->plugin_compat->is_customizer_crop_action() ) {
585
+ return $url;
586
+ }
587
+
588
+ $as3cf_item = Media_Library_Item::get_by_source_id( $post_id );
589
+ if ( empty( $as3cf_item ) || ! $as3cf_item->served_by_provider() ) {
590
+ return $url;
591
+ }
592
+
593
+ $size = $as3cf_item->get_object_key_from_filename( $url );
594
+ $new_url = $as3cf_item->get_provider_url( $size );
595
+
596
+ if ( is_wp_error( $new_url ) || false === $new_url ) {
597
+ return $url;
598
+ }
599
+
600
+ // Old naming convention, will be deprecated soon
601
+ $new_url = apply_filters( 'wps3_get_attachment_url', $new_url, $post_id, $this );
602
+
603
+ /**
604
+ * Filter the rewritten provider URL for a Media Library Item (attachment)
605
+ *
606
+ * @param string $url The URL
607
+ * @param int $post_id Attachment post id
608
+ */
609
+ return apply_filters( 'as3cf_wp_get_attachment_url', $new_url, $post_id );
610
+ }
611
+
612
+ /**
613
+ * Return a formatted provider info array with display friendly defaults
614
+ *
615
+ * @param int $id
616
+ *
617
+ * @return bool|array
618
+ */
619
+ public function get_formatted_provider_info( $id ) {
620
+ $as3cf_item = Media_Library_Item::get_by_source_id( $id );
621
+
622
+ if ( ! $as3cf_item ) {
623
+ return false;
624
+ }
625
+
626
+ $provider_object = $as3cf_item->key_values();
627
+
628
+ // Backwards compatibility.
629
+ $provider_object['key'] = $provider_object['path'];
630
+ $provider_object['url'] = $as3cf_item->get_provider_url();
631
+
632
+ $acl = $as3cf_item->is_private() ? $this->as3cf->get_storage_provider()->get_private_acl() : $this->as3cf->get_storage_provider()->get_default_acl();
633
+ $acl_info = array(
634
+ 'acl' => $acl,
635
+ 'name' => $this->as3cf->get_acl_display_name( $acl ),
636
+ 'title' => $this->get_media_action_strings( 'change_to_private' ),
637
+ );
638
+
639
+ if ( $as3cf_item->is_private() ) {
640
+ $acl_info['title'] = $this->get_media_action_strings( 'change_to_public' );
641
+ }
642
+
643
+ $provider_object['acl'] = $acl_info;
644
+ $provider_object['region'] = $this->as3cf->get_storage_provider()->get_region_name( $provider_object['region'] );
645
+ $provider_object['provider_name'] = $this->as3cf->get_provider_service_name( $provider_object['provider'] );
646
+
647
+ return $provider_object;
648
+ }
649
+
650
+ /**
651
+ * Filters the list of attachment image attributes.
652
+ *
653
+ * @param array $attr Attributes for the image markup.
654
+ * @param WP_Post $attachment Image attachment post.
655
+ * @param string|array $size Requested size. Image size or array of width and height values (in that order).
656
+ *
657
+ * @return array
658
+ */
659
+ public function wp_get_attachment_image_attributes( $attr, $attachment, $size ) {
660
+ $as3cf_item = Media_Library_Item::get_by_source_id( $attachment->ID );
661
+ if ( ! $as3cf_item || ! $as3cf_item->served_by_provider() ) {
662
+ return $attr;
663
+ }
664
+
665
+ $size = $this->maybe_convert_size_to_string( $attachment->ID, $size );
666
+
667
+ // image_downsize incorrectly substitutes size filename into full URL for src attribute instead of clobbering.
668
+ // So we need to fix up the src attribute if a size is being used.
669
+ if ( ! empty( $size ) && ! empty( $attr['src'] ) ) {
670
+ $attr['src'] = $as3cf_item->get_provider_url( $size );
671
+ }
672
+
673
+ /**
674
+ * Filtered list of attachment image attributes.
675
+ *
676
+ * @param array $attr Attributes for the image markup.
677
+ * @param WP_Post $attachment Image attachment post.
678
+ * @param string $size Requested size.
679
+ * @param Media_Library_Item $as3cf_item
680
+ */
681
+ return apply_filters( 'as3cf_wp_get_attachment_image_attributes', $attr, $attachment, $size, $as3cf_item );
682
+ }
683
+
684
+ /**
685
+ * Maybe encode attachment URLs when retrieving the image tag
686
+ *
687
+ * @param string $html
688
+ * @param int $id
689
+ * @param string $alt
690
+ * @param string $title
691
+ * @param string $align
692
+ * @param string $size
693
+ *
694
+ * @return string
695
+ */
696
+ public function maybe_encode_get_image_tag( $html, $id, $alt, $title, $align, $size ) {
697
+ $as3cf_item = Media_Library_Item::get_by_source_id( $id );
698
+ if ( ! $as3cf_item || ! $as3cf_item->served_by_provider() ) {
699
+ return $html;
700
+ }
701
+
702
+ if ( ! is_string( $html ) ) {
703
+ return $html;
704
+ }
705
+
706
+ preg_match( '@\ssrc=[\'\"]([^\'\"]*)[\'\"]@', $html, $matches );
707
+
708
+ if ( ! isset( $matches[1] ) ) {
709
+ // Can't establish img src
710
+ return $html;
711
+ }
712
+
713
+ $img_src = $matches[1];
714
+ $new_img_src = $this->maybe_sign_intermediate_size( $img_src, $id, $size, $as3cf_item );
715
+ $new_img_src = AS3CF_Utils::encode_filename_in_path( $new_img_src );
716
+
717
+ return str_replace( $img_src, $new_img_src, $html );
718
+ }
719
+
720
+ /**
721
+ * Maybe encode URLs for images that represent an attachment
722
+ *
723
+ * @param array|bool $image
724
+ * @param int $attachment_id
725
+ * @param string|array $size
726
+ * @param bool $icon
727
+ *
728
+ * @return array
729
+ */
730
+ public function maybe_encode_wp_get_attachment_image_src( $image, $attachment_id, $size, $icon ) {
731
+ $as3cf_item = Media_Library_Item::get_by_source_id( $attachment_id );
732
+ if ( ! $as3cf_item || ! $as3cf_item->served_by_provider() ) {
733
+ return $image;
734
+ }
735
+
736
+ if ( isset( $image[0] ) ) {
737
+ $url = $this->maybe_sign_intermediate_size( $image[0], $attachment_id, $size, $as3cf_item );
738
+ $url = AS3CF_Utils::encode_filename_in_path( $url );
739
+
740
+ $image[0] = $url;
741
+ }
742
+
743
+ return $image;
744
+ }
745
+
746
+ /**
747
+ * Maybe encode URLs when outputting attachments in the media grid
748
+ *
749
+ * @param array $response
750
+ * @param int|object $attachment
751
+ * @param array $meta
752
+ *
753
+ * @return array
754
+ */
755
+ public function maybe_encode_wp_prepare_attachment_for_js( $response, $attachment, $meta ) {
756
+ $as3cf_item = Media_Library_Item::get_by_source_id( $attachment->ID );
757
+ if ( empty( $as3cf_item ) || ! $as3cf_item->served_by_provider() ) {
758
+ return $response;
759
+ }
760
+
761
+ if ( isset( $response['url'] ) ) {
762
+ $response['url'] = AS3CF_Utils::encode_filename_in_path( $response['url'] );
763
+ }
764
+
765
+ if ( isset( $response['sizes'] ) && is_array( $response['sizes'] ) ) {
766
+ foreach ( $response['sizes'] as $size => $value ) {
767
+ $url = $this->maybe_sign_intermediate_size( $value['url'], $attachment->ID, $size, $as3cf_item, true );
768
+ $url = AS3CF_Utils::encode_filename_in_path( $url );
769
+
770
+ $response['sizes'][ $size ]['url'] = $url;
771
+ }
772
+ }
773
+
774
+ return $response;
775
+ }
776
+
777
+ /**
778
+ * Maybe encode URLs when retrieving intermediate sizes.
779
+ *
780
+ * @param array $data
781
+ * @param int $post_id
782
+ * @param string|array $size
783
+ *
784
+ * @return array
785
+ */
786
+ public function maybe_encode_image_get_intermediate_size( $data, $post_id, $size ) {
787
+ $as3cf_item = Media_Library_Item::get_by_source_id( $post_id );
788
+ if ( ! $as3cf_item || ! $as3cf_item->served_by_provider() ) {
789
+ return $data;
790
+ }
791
+
792
+ if ( isset( $data['url'] ) ) {
793
+ $url = $this->maybe_sign_intermediate_size( $data['url'], $post_id, $size, $as3cf_item );
794
+ $url = AS3CF_Utils::encode_filename_in_path( $url );
795
+
796
+ $data['url'] = $url;
797
+ }
798
+
799
+ return $data;
800
+ }
801
+
802
+ /**
803
+ * Sign intermediate size.
804
+ *
805
+ * @param string $url
806
+ * @param int $attachment_id
807
+ * @param string|array $size
808
+ * @param bool|Media_Library_Item $as3cf_item
809
+ * @param bool $force_rewrite If size not signed, make sure correct URL is being used anyway.
810
+ *
811
+ * @return string|WP_Error
812
+ */
813
+ protected function maybe_sign_intermediate_size( $url, $attachment_id, $size, $as3cf_item = false, $force_rewrite = false ) {
814
+ if ( ! $as3cf_item ) {
815
+ $as3cf_item = Media_Library_Item::get_by_source_id( $attachment_id );
816
+ }
817
+
818
+ $size = $this->maybe_convert_size_to_string( $attachment_id, $size );
819
+
820
+ if ( $force_rewrite || $as3cf_item->is_private( $size ) ) {
821
+ // Private file, add AWS signature if required
822
+ return $as3cf_item->get_provider_url( $size );
823
+ }
824
+
825
+ return $url;
826
+ }
827
+
828
+ /**
829
+ * Return the provider URL when the local file is missing
830
+ * unless we know who the calling process is, and we are happy
831
+ * to copy the file back to the server to be used.
832
+ *
833
+ * @handles get_attached_file
834
+ * @handles wp_get_original_image_path
835
+ *
836
+ * @param string $file
837
+ * @param int $attachment_id
838
+ *
839
+ * @return string
840
+ */
841
+ public function get_attached_file( $file, $attachment_id ) {
842
+ // During the deletion of an attachment, stream wrapper URLs should not be returned.
843
+ if ( $this->deleting_attachment ) {
844
+ return $file;
845
+ }
846
+
847
+ $as3cf_item = Media_Library_Item::get_by_source_id( $attachment_id );
848
+ if ( ! empty( $as3cf_item ) && ! $as3cf_item->served_by_provider() ) {
849
+ $as3cf_item = false;
850
+ }
851
+
852
+ if ( file_exists( $file ) || ! $as3cf_item ) {
853
+ if ( $as3cf_item ) {
854
+ /**
855
+ * This filter gives filter implementors a chance to copy back siblings for
856
+ * a local file even if the main already exists locally.
857
+ *
858
+ * @param string $url Item URL
859
+ * @param string $file Local file path
860
+ * @param int $attachment_id Attachment post id
861
+ * @param Media_Library_Item $as3cf_item The Item object
862
+ *
863
+ */
864
+ return apply_filters( 'as3cf_get_attached_file_noop', $file, $file, $attachment_id, $as3cf_item );
865
+ } else {
866
+ return $file;
867
+ }
868
+ }
869
+
870
+ $url = $as3cf_item->get_provider_url();
871
+ if ( false === $url || is_wp_error( $url ) ) {
872
+ return $file;
873
+ }
874
+
875
+ /**
876
+ * This filter gives filter implementors a chance to copy back missing item files
877
+ * from the provider before WordPress returns the file name/path for it. Defaults to
878
+ * returning the remote URL.
879
+ *
880
+ * @param string $url Item URL
881
+ * @param string $file Local file path
882
+ * @param int $attachment_id Attachment post id
883
+ * @param Media_Library_Item $as3cf_item The Item object
884
+ *
885
+ */
886
+ return apply_filters( 'as3cf_get_attached_file', $url, $file, $attachment_id, $as3cf_item );
887
+ }
888
+
889
+ /**
890
+ * Filters the audio & video shortcodes output to remove "&_=NN" params from source.src as it breaks signed URLs.
891
+ *
892
+ * @param string $html Shortcode HTML output.
893
+ * @param array $atts Array of shortcode attributes.
894
+ * @param string $media Media file.
895
+ * @param int $post_id Post ID.
896
+ * @param string $library Media library used for the shortcode.
897
+ *
898
+ * @return string
899
+ *
900
+ * Note: Depends on 30377.4.diff from https://core.trac.wordpress.org/ticket/30377
901
+ */
902
+ public function wp_media_shortcode( $html, $atts, $media, $post_id, $library ) {
903
+ return preg_replace( '/&#038;_=[0-9]+/', '', $html );
904
+ }
905
+
906
+ /**
907
+ * Check we can do the media actions
908
+ *
909
+ * @return bool
910
+ */
911
+ public function verify_media_actions() {
912
+ return false;
913
+ }
914
+
915
+ /**
916
+ * Get all strings or a specific string used for the media actions
917
+ *
918
+ * @param null|string $string
919
+ *
920
+ * @return array|string
921
+ */
922
+ public function get_media_action_strings( $string = null ) {
923
+ $not_verified_value = __( 'No', 'amazon-s3-and-cloudfront' );
924
+ $not_verified_value .= '&nbsp;';
925
+ $not_verified_value .= $this->as3cf->more_info_link( '/wp-offload-media/doc/add-metadata-tool/', 'os3+attachment+metabox', 'analyze-and-repair', 'More Info', '(', ')' );
926
+
927
+ /**
928
+ * Returns all strings used to render meta boxes on the WordPress Media Library edit page
929
+ *
930
+ * @param array $strings Associative array of strings
931
+ */
932
+ $strings = apply_filters( 'as3cf_media_action_strings', array(
933
+ 'provider' => _x( 'Storage Provider', 'Storage provider key name', 'amazon-s3-and-cloudfront' ),
934
+ 'provider_name' => _x( 'Storage Provider', 'Storage provider name', 'amazon-s3-and-cloudfront' ),
935
+ 'bucket' => _x( 'Bucket', 'Bucket name', 'amazon-s3-and-cloudfront' ),
936
+ 'key' => _x( 'Path', 'Path to file in bucket', 'amazon-s3-and-cloudfront' ),
937
+ 'region' => _x( 'Region', 'Location of bucket', 'amazon-s3-and-cloudfront' ),
938
+ 'acl' => _x( 'Access', 'Access control list of the file in bucket', 'amazon-s3-and-cloudfront' ),
939
+ 'url' => __( 'URL', 'amazon-s3-and-cloudfront' ),
940
+ 'is_verified' => _x( 'Verified', 'Whether or not metadata has been verified', 'amazon-s3-and-cloudfront' ),
941
+ 'not_verified' => $not_verified_value,
942
+ ) );
943
+
944
+ if ( ! is_null( $string ) ) {
945
+ return isset( $strings[ $string ] ) ? $strings[ $string ] : '';
946
+ }
947
+
948
+ return $strings;
949
+ }
950
+
951
+ /**
952
+ * Remove 'filesize' from attachment's metadata if appropriate, also our total filesize record.
953
+ *
954
+ * @param int $post_id Attachment's post_id.
955
+ * @param array $data Attachment's metadata.
956
+ * @param bool $update_metadata Update the metadata record now? Defaults to true.
957
+ *
958
+ * @return array Attachment's cleaned up metadata.
959
+ */
960
+ public function maybe_cleanup_filesize_metadata( $post_id, $data, $update_metadata = true ) {
961
+ if ( ! is_int( $post_id ) || empty( $post_id ) || empty( $data ) || ! is_array( $data ) ) {
962
+ return $data;
963
+ }
964
+
965
+ /*
966
+ * Audio and video have a filesize added to metadata by default, but images and anything else don't.
967
+ * Note: Could have used `wp_generate_attachment_metadata` here to test whether default metadata has 'filesize',
968
+ * but it not only has side effects it also does a lot of work considering it's not a huge deal for this entry to hang around.
969
+ */
970
+ if (
971
+ empty( $data['mime_type'] ) ||
972
+ 0 === strpos( $data['mime_type'], 'image/' ) ||
973
+ ! ( 0 === strpos( $data['mime_type'], 'audio/' ) || 0 === strpos( $data['mime_type'], 'video/' ) )
974
+ ) {
975
+ unset( $data['filesize'] );
976
+ }
977
+
978
+ if ( $update_metadata ) {
979
+ if ( empty( $data ) ) {
980
+ delete_post_meta( $post_id, '_wp_attachment_metadata' );
981
+ } else {
982
+ update_post_meta( $post_id, '_wp_attachment_metadata', $data );
983
+ }
984
+ }
985
+
986
+ delete_post_meta( $post_id, 'as3cf_filesize_total' );
987
+
988
+ return $data;
989
+ }
990
+
991
+ /**
992
+ * Get ACL value string.
993
+ *
994
+ * @param array $acl
995
+ * @param int $post_id
996
+ *
997
+ * @return string
998
+ */
999
+ public function get_acl_value_string( $acl, $post_id ) {
1000
+ return $acl['name'];
1001
+ }
1002
+
1003
+ /**
1004
+ * Determines if the image metadata is for the image source file.
1005
+ *
1006
+ * @handles wp_image_file_matches_image_meta
1007
+ *
1008
+ * @param bool $match
1009
+ * @param string $image_location
1010
+ * @param array $image_meta
1011
+ * @param int $source_id
1012
+ *
1013
+ * @return bool
1014
+ */
1015
+ public function image_file_matches_image_meta( $match, $image_location, $image_meta, $source_id ) {
1016
+ // If already matched or the URL is local, there's nothing for us to do.
1017
+ if ( $match || ! $this->as3cf->filter_local->url_needs_replacing( $image_location ) ) {
1018
+ return $match;
1019
+ }
1020
+
1021
+ $item = array(
1022
+ 'id' => $source_id,
1023
+ 'source_type' => Media_Library_Item::source_type(),
1024
+ );
1025
+
1026
+ return $this->as3cf->filter_local->item_matches_src( $item, $image_location );
1027
+ }
1028
+
1029
+ /**
1030
+ * Get the local URL for a Media Library Item
1031
+ *
1032
+ * @handles as3cf_get_local_url_for_item_source
1033
+ *
1034
+ * @param string $url Url
1035
+ * @param array $item_source The item source descriptor array
1036
+ * @param string $size Name of requested size
1037
+ *
1038
+ * @return string|false
1039
+ */
1040
+ public function filter_get_local_url_for_item_source( $url, $item_source, $size ) {
1041
+ if ( Media_Library_Item::source_type() !== $item_source['source_type'] ) {
1042
+ return $url;
1043
+ }
1044
+
1045
+ $as3cf_item = Media_Library_Item::get_by_source_id( $item_source['id'] );
1046
+ if ( ! empty( $as3cf_item ) ) {
1047
+ return $as3cf_item->get_local_url( $size );
1048
+ }
1049
+
1050
+ return $url;
1051
+ }
1052
+
1053
+ /**
1054
+ * Get the remote URL for a Media Library Item
1055
+ *
1056
+ * @handles as3cf_get_provider_url_for_item_source
1057
+ *
1058
+ * @param string $url Url
1059
+ * @param array $item_source The item source descriptor array
1060
+ * @param string $size Name of requested size
1061
+ *
1062
+ * @return string|false
1063
+ */
1064
+ public function filter_get_provider_url_for_item_source( $url, $item_source, $size ) {
1065
+ if ( Media_Library_Item::source_type() !== $item_source['source_type'] ) {
1066
+ return $url;
1067
+ }
1068
+
1069
+ $as3cf_item = Media_Library_Item::get_by_source_id( $item_source['id'] );
1070
+ if ( empty( $as3cf_item ) ) {
1071
+ return $url;
1072
+ }
1073
+
1074
+ $url = $as3cf_item->get_provider_url( $size );
1075
+
1076
+ if ( is_wp_error( $url ) ) {
1077
+ return false;
1078
+ }
1079
+
1080
+ return $url;
1081
+ }
1082
+
1083
+ /**
1084
+ * Get the size from a URL for media library item types
1085
+ *
1086
+ * @handles as3cf_get_size_string_from_url_for_item_source
1087
+ *
1088
+ * @param string $size
1089
+ * @param string $url
1090
+ * @param array $item_source
1091
+ *
1092
+ * @return string
1093
+ */
1094
+ public function get_size_string_from_url_for_item_source( $size, $url, $item_source ) {
1095
+ if ( Media_Library_Item::source_type() !== $item_source['source_type'] ) {
1096
+ return $size;
1097
+ }
1098
+
1099
+ $meta = get_post_meta( $item_source['id'], '_wp_attachment_metadata', true );
1100
+
1101
+ if ( empty( $meta['sizes'] ) ) {
1102
+ // No alternative sizes available, return
1103
+ return $size;
1104
+ }
1105
+
1106
+ $basename = AS3CF_Utils::encode_filename_in_path( wp_basename( $this->as3cf->maybe_remove_query_string( $url ) ) );
1107
+
1108
+ foreach ( $meta['sizes'] as $size_name => $file ) {
1109
+ if ( $basename === AS3CF_Utils::encode_filename_in_path( $file['file'] ) ) {
1110
+ return $size_name;
1111
+ }
1112
+ }
1113
+
1114
+ return $size;
1115
+ }
1116
+
1117
+ /**
1118
+ * Get attachment id from remote URL.
1119
+ *
1120
+ * @param string $url
1121
+ *
1122
+ * @return bool|int
1123
+ */
1124
+ public function get_attachment_id_from_provider_url( $url ) {
1125
+ $item_source = $this->as3cf->filter_provider->get_item_source_from_url( $url );
1126
+
1127
+ if ( ! empty( $item_source['id'] ) && ! empty( $item_source['source_type'] ) && Media_Library_Item::source_type() === $item_source['source_type'] ) {
1128
+ return $item_source['id'];
1129
+ }
1130
+
1131
+ return false;
1132
+ }
1133
+
1134
+ /**
1135
+ * Get attachment id from local URL.
1136
+ *
1137
+ * @param string $url
1138
+ *
1139
+ * @return bool|int
1140
+ */
1141
+ public function get_attachment_id_from_local_url( $url ) {
1142
+ $item_source = $this->as3cf->filter_local->get_item_source_from_url( $url );
1143
+
1144
+ if ( ! empty( $item_source['id'] ) && ! empty( $item_source['source_type'] ) && Media_Library_Item::source_type() === $item_source['source_type'] ) {
1145
+ return $item_source['id'];
1146
+ }
1147
+
1148
+ return false;
1149
+ }
1150
+
1151
+ /**
1152
+ * Get post time
1153
+ *
1154
+ * @param int $post_id
1155
+ *
1156
+ * @return string
1157
+ */
1158
+ private function get_post_time( $post_id ) {
1159
+ $time = current_time( 'mysql' );
1160
+
1161
+ if ( ! $post = get_post( $post_id ) ) {
1162
+ return $time;
1163
+ }
1164
+
1165
+ if ( substr( $post->post_date, 0, 4 ) > 0 ) {
1166
+ $time = $post->post_date;
1167
+ }
1168
+
1169
+ return $time;
1170
+ }
1171
+
1172
+ /**
1173
+ * Maybe convert size to string
1174
+ *
1175
+ * @param int $attachment_id
1176
+ * @param mixed $size
1177
+ *
1178
+ * @return null|string
1179
+ */
1180
+ private function maybe_convert_size_to_string( $attachment_id, $size ) {
1181
+ if ( is_array( $size ) ) {
1182
+ return $this->convert_dimensions_to_size_name( $attachment_id, $size );
1183
+ }
1184
+
1185
+ return $size;
1186
+ }
1187
+
1188
+ /**
1189
+ * Convert dimensions to size
1190
+ *
1191
+ * @param int $attachment_id
1192
+ * @param array $dimensions
1193
+ *
1194
+ * @return null|string
1195
+ */
1196
+ private function convert_dimensions_to_size_name( $attachment_id, $dimensions ) {
1197
+ $w = ( isset( $dimensions[0] ) && $dimensions[0] > 0 ) ? $dimensions[0] : 1;
1198
+ $h = ( isset( $dimensions[1] ) && $dimensions[1] > 0 ) ? $dimensions[1] : 1;
1199
+ $original_aspect_ratio = $w / $h;
1200
+ $meta = wp_get_attachment_metadata( $attachment_id );
1201
+
1202
+ if ( ! isset( $meta['sizes'] ) || empty( $meta['sizes'] ) ) {
1203
+ return null;
1204
+ }
1205
+
1206
+ $sizes = $meta['sizes'];
1207
+ uasort( $sizes, function ( $a, $b ) {
1208
+ // Order by image area
1209
+ return ( $a['width'] * $a['height'] ) - ( $b['width'] * $b['height'] );
1210
+ } );
1211
+
1212
+ $nearest_matches = array();
1213
+
1214
+ foreach ( $sizes as $size => $value ) {
1215
+ if ( $w > $value['width'] || $h > $value['height'] ) {
1216
+ continue;
1217
+ }
1218
+
1219
+ $aspect_ratio = $value['width'] / $value['height'];
1220
+
1221
+ if ( $aspect_ratio === $original_aspect_ratio ) {
1222
+ return $size;
1223
+ }
1224
+
1225
+ $nearest_matches[] = $size;
1226
+ }
1227
+
1228
+ // Return nearest match
1229
+ if ( ! empty( $nearest_matches ) ) {
1230
+ return $nearest_matches[0];
1231
+ }
1232
+
1233
+ return null;
1234
+ }
1235
+
1236
+ /**
1237
+ * Has the given attachment been uploaded by this instance?
1238
+ *
1239
+ * @param int $source_id
1240
+ *
1241
+ * @return bool
1242
+ */
1243
+ public function item_just_uploaded( $source_id ) {
1244
+ if ( is_int( $source_id ) && isset( $this->items_in_progress[ $source_id ] ) ) {
1245
+ return true;
1246
+ }
1247
+
1248
+ return false;
1249
+ }
1250
+
1251
+ /**
1252
+ * Call legacy attachment specific version of the as3cf_get_item_secure_url filter.
1253
+ *
1254
+ * @param string $url The URL
1255
+ * @param Item $as3cf_item The Item object
1256
+ * @param array $item_source The item source descriptor array
1257
+ * @param int $timestamp Expiry timestamp
1258
+ * @param array $headers Optional extra http headers
1259
+ *
1260
+ * @handles as3cf_get_item_secure_url
1261
+ *
1262
+ * @return string|mixed
1263
+ */
1264
+ public function get_item_secure_url( $url, $as3cf_item, $item_source, $timestamp, $headers ) {
1265
+ if ( Media_Library_Item::source_type() !== $item_source['source_type'] ) {
1266
+ return $url;
1267
+ }
1268
+
1269
+ /**
1270
+ * Filters the secure url for an attachment
1271
+ *
1272
+ * @param string $url The URL
1273
+ * @param Item $as3cf_item The Item object
1274
+ * @param int $id The attachment id
1275
+ * @param int $timestamp Expiry timestamp
1276
+ * @param array $headers Optional extra http headers
1277
+ *
1278
+ * @deprecated 2.6.0 Please use filter "as3cf_get_item_secure_url" instead.
1279
+ */
1280
+ return apply_filters( 'as3cf_get_attachment_secure_url', $url, $as3cf_item, $item_source['id'], $timestamp, $headers );
1281
+ }
1282
+
1283
+ /**
1284
+ * Call legacy attachment specific version of the as3cf_get_item_url filter.
1285
+ *
1286
+ * @param string $url The URL
1287
+ * @param Item $as3cf_item The Item object
1288
+ * @param array $item_source The item source descriptor array
1289
+ * @param int $timestamp Expiry timestamp
1290
+ * @param array $headers Optional extra http headers
1291
+ *
1292
+ * @handles as3cf_get_item_url
1293
+ *
1294
+ * @return string|mixed
1295
+ */
1296
+ public function get_item_url( $url, $as3cf_item, $item_source, $timestamp, $headers ) {
1297
+ if ( Media_Library_Item::source_type() !== $item_source['source_type'] ) {
1298
+ return $url;
1299
+ }
1300
+
1301
+ /**
1302
+ * Filters the url for an attachment
1303
+ *
1304
+ * @param string $url The URL
1305
+ * @param Item $as3cf_item The Item object
1306
+ * @param int $id The attachment id
1307
+ * @param int $timestamp Expiry timestamp
1308
+ * @param array $headers Optional extra http headers
1309
+ *
1310
+ * @deprecated 2.6.0 Please use filter "as3cf_get_item_url" instead.
1311
+ */
1312
+ return apply_filters( 'as3cf_get_attachment_url', $url, $as3cf_item, $item_source['id'], $timestamp, $headers );
1313
+ }
1314
+
1315
+ /**
1316
+ * Call legacy attachment specific version of the as3cf_remove_source_files_from_provider filter.
1317
+ *
1318
+ * @param array $paths Array of local paths to be removed from provider
1319
+ * @param Item $as3cf_item The Item object
1320
+ * @param array $item_source The item source descriptor array
1321
+ *
1322
+ * @handles as3cf_remove_source_files_from_provider
1323
+ *
1324
+ * @return array|mixed
1325
+ */
1326
+ public function filter_remove_source_files_from_provider( $paths, Item $as3cf_item, $item_source ) {
1327
+ if ( Media_Library_Item::source_type() !== $item_source['source_type'] ) {
1328
+ return $paths;
1329
+ }
1330
+
1331
+ /**
1332
+ * Filters which provider files to remove
1333
+ *
1334
+ * @param array $paths Array of local paths to be removed from provider
1335
+ * @param int $id Item attachment id
1336
+ * @param Item $as3cf_item The Item
1337
+ * @param bool $include_backups Also include backup files?
1338
+ *
1339
+ * @deprecated 2.6.0 Please use filter "as3cf_remove_source_files_from_provider" instead.
1340
+ */
1341
+ return apply_filters( 'as3cf_remove_attachment_paths', $paths, $item_source['id'], $as3cf_item, true );
1342
+ }
1343
+
1344
+ /**
1345
+ * Calls legacy attachment specific version of as3cf_remove_local_files filter.
1346
+ *
1347
+ * @param array $files_to_remove Array of paths to be removed
1348
+ * @param Item $as3cf_item The Item object
1349
+ * @param array $item_source The item source descriptor array
1350
+ *
1351
+ * @handles as3cf_remove_local_files
1352
+ *
1353
+ * @return array|mixed
1354
+ */
1355
+ public function filter_remove_local_files( $files_to_remove, $as3cf_item, $item_source ) {
1356
+ if ( Media_Library_Item::source_type() !== $item_source['source_type'] ) {
1357
+ return $files_to_remove;
1358
+ }
1359
+
1360
+ /**
1361
+ * Filters which local files should be removed.
1362
+ *
1363
+ * @param array $paths Paths that will be removed
1364
+ * @param int $id Attachment id
1365
+ * @param string $source_path Path to primary file
1366
+ *
1367
+ * @deprecated 2.6.0 Please use filter "as3cf_remove_local_files" instead.
1368
+ */
1369
+ return apply_filters( 'as3cf_upload_attachment_local_files_to_remove', $files_to_remove, $item_source['id'], $as3cf_item->full_source_path( Item::primary_object_key() ) );
1370
+ }
1371
+
1372
+ /**
1373
+ * Handle post upload duties if uploaded item is a media-library item.
1374
+ *
1375
+ * @handles as3cf_post_upload_item
1376
+ *
1377
+ * @param Media_Library_Item $as3cf_item
1378
+ */
1379
+ public function post_upload_item( $as3cf_item ) {
1380
+ if ( Media_Library_Item::source_type() !== $as3cf_item->source_type() ) {
1381
+ return;
1382
+ }
1383
+
1384
+ // Make sure duplicates are marked as offloaded too.
1385
+ $as3cf_item->offload_duplicate_items();
1386
+
1387
+ /**
1388
+ * Fires after an attachment has been uploaded to the provider.
1389
+ *
1390
+ * @param int $id Attachment id
1391
+ * @param Item $as3cf_item The item that was just uploaded
1392
+ *
1393
+ * @deprecated 2.6.0 Please use action "as3cf_post_upload_item" instead.
1394
+ */
1395
+ do_action( 'as3cf_post_upload_attachment', $as3cf_item->source_id(), $as3cf_item );
1396
+ }
1397
+
1398
+ /**
1399
+ * Call legacy media library specific filter for cancelling an upload.
1400
+ *
1401
+ * @param bool $cancel Should the action on the item be cancelled?
1402
+ * @param Item $as3cf_item The item that the action is being handled for.
1403
+ * @param array $options Handler dependent options that may have been set for the action.
1404
+ *
1405
+ * @handles as3cf_pre_handle_item_upload
1406
+ *
1407
+ * @return bool
1408
+ */
1409
+ public function pre_handle_item_upload( $cancel, $as3cf_item, array $options ) {
1410
+ if ( Media_Library_Item::source_type() !== $as3cf_item->source_type() ) {
1411
+ return $cancel;
1412
+ }
1413
+
1414
+ // Get unfiltered attachment metadata to pass into legacy filter.
1415
+ $metadata = wp_get_attachment_metadata( $as3cf_item->source_id(), true );
1416
+ if ( is_wp_error( $metadata ) ) {
1417
+ return $metadata;
1418
+ }
1419
+
1420
+ /**
1421
+ * Allow provider upload to be cancelled for any reason.
1422
+ *
1423
+ * @param bool $cancel Should the upload for the attachment be cancelled?
1424
+ * @param int $id Attachment id
1425
+ * @param array $metadata Attachment metadata
1426
+ *
1427
+ * @deprecated 2.6.0 Please use filter "as3cf_pre_upload_item" instead.
1428
+ */
1429
+ return apply_filters( 'as3cf_pre_upload_attachment', $cancel, $as3cf_item->source_id(), $metadata );
1430
+ }
1431
+
1432
+ /**
1433
+ * Call legacy filter for determining private status on an item's individual object_key.
1434
+ *
1435
+ * @param bool $is_private
1436
+ * @param string $object_key
1437
+ * @param Item $as3cf_item
1438
+ *
1439
+ * @handles as3cf_upload_object_key_as_private
1440
+ *
1441
+ * @return bool
1442
+ */
1443
+ public function filter_upload_object_key_as_private( $is_private, $object_key, $as3cf_item ) {
1444
+ if ( Media_Library_Item::source_type() !== $as3cf_item->source_type() ) {
1445
+ return $is_private;
1446
+ }
1447
+
1448
+ $metadata = wp_get_attachment_metadata( $as3cf_item->source_id(), true );
1449
+ $default_acl = $this->as3cf->get_storage_provider()->get_default_acl();
1450
+ $private_acl = $this->as3cf->get_storage_provider()->get_private_acl();
1451
+ $acl = true === $is_private ? $private_acl : $default_acl;
1452
+
1453
+ if ( Item::primary_object_key() === $object_key ) {
1454
+ $file_name = wp_basename( $as3cf_item->source_path() );
1455
+ $file_type = wp_check_filetype_and_ext( $as3cf_item->source_path(), $file_name );
1456
+
1457
+ // Old naming convention, will be removed soon.
1458
+ $acl = apply_filters( 'wps3_upload_acl', $acl, $file_type['type'], $metadata, $as3cf_item->source_id(), $this->as3cf );
1459
+
1460
+ /**
1461
+ * Determine canned ACL for an item's original (full size) file about to be uploaded to provider.
1462
+ *
1463
+ * @param string $acl The canned ACL for the provider.
1464
+ * @param array $metadata The attachment's metadata.
1465
+ * @param int $id The attachment's ID.
1466
+ *
1467
+ * @deprecated 2.6.0 Please use filter "as3cf_upload_object_key_as_private" instead.
1468
+ */
1469
+ $acl = apply_filters( 'as3cf_upload_acl', $acl, $metadata, $as3cf_item->source_id() );
1470
+ } else {
1471
+ /**
1472
+ * Determine ACL for an item's individual thumbnail size about to be uploaded to provider.
1473
+ *
1474
+ * @param string $acl The canned ACL for the provider.
1475
+ * @param string $size Size name for file (thumbnail, medium, large).
1476
+ * @param int $id The attachment's ID.
1477
+ * @param array $data The attachment's metadata.
1478
+ *
1479
+ * @deprecated 2.6.0 Please use filter "as3cf_upload_object_key_as_private" instead.
1480
+ */
1481
+ $acl = apply_filters( 'as3cf_upload_acl_sizes', $acl, $object_key, $as3cf_item->source_id(), $metadata );
1482
+ }
1483
+
1484
+ if ( ! empty( $acl ) && $private_acl === $acl ) {
1485
+ return true;
1486
+ }
1487
+
1488
+ return $is_private;
1489
+ }
1490
+
1491
+ /**
1492
+ * Fire legacy action just before a Media Library Item is offloaded.
1493
+ *
1494
+ * @handles as3cf_pre_upload_object
1495
+ *
1496
+ * @param Item $as3cf_item
1497
+ * @param array $args
1498
+ */
1499
+ public function action_pre_upload_object( $as3cf_item, $args ) {
1500
+ if ( Media_Library_Item::source_type() !== $as3cf_item->source_type() ) {
1501
+ return;
1502
+ }
1503
+
1504
+ /**
1505
+ * Actions fires when an Item's original file might be offloaded.
1506
+ *
1507
+ * This action gives notice that an Item is being processed for upload to a bucket,
1508
+ * and the given arguments represent the original file's potential offload location.
1509
+ * However, if the current process is for picking up extra files associated with the item,
1510
+ * the indicated original file may not actually be offloaded if it does not exist
1511
+ * on the server but has already been offloaded.
1512
+ *
1513
+ *
1514
+ * @param int $id The attachment id.
1515
+ * @param Media_Library_Item $as3cf_item The Item whose files are being offloaded.
1516
+ * @param string $path The path to the item.
1517
+ * @param array $args The arguments that could be used to offload the original file.
1518
+ *
1519
+ * @deprecated 2.6.0 Please use action "as3cf_pre_upload_object" instead.
1520
+ */
1521
+ do_action( 'as3cf_upload_attachment_pre_remove', $as3cf_item->source_id(), $as3cf_item, $as3cf_item->normalized_path_dir(), $args );
1522
+ }
1523
+
1524
+ /**
1525
+ * Takes notice that an attachment is about to be deleted and prepares for it.
1526
+ *
1527
+ * @handles pre_delete_attachment
1528
+ *
1529
+ * @param bool|null $delete Whether to go forward with deletion.
1530
+ *
1531
+ * @return bool|null
1532
+ */
1533
+ public function pre_delete_attachment( $delete ) {
1534
+ if ( is_null( $delete ) ) {
1535
+ $this->deleting_attachment = true;
1536
+ }
1537
+
1538
+ return $delete;
1539
+ }
1540
+
1541
+ /**
1542
+ * Takes notice that an attachment has been deleted and undoes previous preparations for the event.
1543
+ *
1544
+ * @handles delete_post
1545
+ *
1546
+ * Note: delete_post is used as there is a potential that deleted_post is not reached.
1547
+ */
1548
+ public function delete_post() {
1549
+ $this->deleting_attachment = false;
1550
+ }
1551
+ }
classes/items/download-handler.php ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace DeliciousBrains\WP_Offload_Media\Items;
4
+
5
+ use AS3CF_Error;
6
+ use DeliciousBrains\WP_Offload_Media\Providers\Storage\Storage_Provider;
7
+ use Exception;
8
+ use WP_Error;
9
+
10
+ class Download_Handler extends Item_Handler {
11
+ /**
12
+ * @var string
13
+ */
14
+ protected static $item_handler_key = 'download';
15
+
16
+ /**
17
+ * The default options that should be used if none supplied.
18
+ *
19
+ * @return array
20
+ */
21
+ public static function default_options() {
22
+ return array(
23
+ 'full_source_paths' => array(),
24
+ );
25
+ }
26
+
27
+ /**
28
+ * Prepare a manifest based on the item.
29
+ *
30
+ * @param Item $as3cf_item
31
+ * @param array $options
32
+ *
33
+ * @return Manifest
34
+ */
35
+ protected function pre_handle( Item $as3cf_item, array $options ) {
36
+ $manifest = new Manifest();
37
+ $file_paths = array();
38
+
39
+ foreach ( $as3cf_item->objects() as $object_key => $object ) {
40
+ $file = $as3cf_item->full_source_path( $object_key );
41
+
42
+ if ( 0 < count( $options['full_source_paths'] ) && ! in_array( $file, $options['full_source_paths'] ) ) {
43
+ continue;
44
+ }
45
+
46
+ $file_paths[ $object_key ] = $file;
47
+ }
48
+
49
+ $file_paths = array_unique( $file_paths );
50
+
51
+ foreach ( $file_paths as $object_key => $file_path ) {
52
+ if ( ! file_exists( $file_path ) ) {
53
+ $manifest->objects[] = array(
54
+ 'args' => array(
55
+ 'Bucket' => $as3cf_item->bucket(),
56
+ 'Key' => $as3cf_item->provider_key( $object_key ),
57
+ 'SaveAs' => $file_path,
58
+ ),
59
+ );
60
+ }
61
+ }
62
+
63
+ return $manifest;
64
+ }
65
+
66
+ /**
67
+ * Perform the downloads.
68
+ *
69
+ * @param Item $as3cf_item
70
+ * @param Manifest $manifest
71
+ * @param array $options
72
+ *
73
+ * @return boolean|WP_Error
74
+ * @throws Exception
75
+ */
76
+ protected function handle_item( Item $as3cf_item, Manifest $manifest, array $options ) {
77
+ if ( ! empty( $manifest->objects ) ) {
78
+ // This test is "late" so that we don't raise the error if the local files exist anyway.
79
+ // If the provider of this item is different from what's currently configured,
80
+ // we'll return an error.
81
+ $current_provider = $this->as3cf->get_storage_provider();
82
+ if ( ! empty( $current_provider ) && $current_provider::get_provider_key_name() !== $as3cf_item->provider() ) {
83
+ $message = sprintf(
84
+ __( '%1$s with ID %d is offloaded to a different provider than currently configured', 'amazon-s3-and-cloudfront' ),
85
+ $this->as3cf->get_source_type_name( $as3cf_item->source_type() ),
86
+ $as3cf_item->source_id()
87
+ );
88
+
89
+ return new WP_Error( 'exception', $message );
90
+ } else {
91
+ $provider_client = $this->as3cf->get_provider_client( $as3cf_item->region() );
92
+
93
+ foreach ( $manifest->objects as &$manifest_object ) {
94
+ // Save object to a file.
95
+ $result = $this->download_object( $provider_client, $manifest_object['args'] );
96
+
97
+ $manifest_object['download_result']['status'] = self::STATUS_OK;
98
+
99
+ if ( is_wp_error( $result ) ) {
100
+ $manifest_object['download_result']['status'] = self::STATUS_FAILED;
101
+ $manifest_object['download_result']['message'] = $result->get_error_message();
102
+ }
103
+ }
104
+ }
105
+ }
106
+
107
+ return true;
108
+ }
109
+
110
+ /**
111
+ * Perform post handle tasks. Log errors, update filesize totals etc.
112
+ *
113
+ * @param Item $as3cf_item
114
+ * @param Manifest $manifest
115
+ * @param array $options
116
+ *
117
+ * @return bool|WP_Error
118
+ */
119
+ protected function post_handle( Item $as3cf_item, Manifest $manifest, array $options ) {
120
+ // Look for errors
121
+ $errors = new WP_Error;
122
+ $i = 1;
123
+
124
+ foreach ( $manifest->objects as $manifest_object ) {
125
+ if ( $manifest_object['download_result']['status'] !== self::STATUS_OK ) {
126
+ $errors->add( 'download-error-' . $i++, $manifest_object['download_result']['message'] );
127
+ }
128
+ }
129
+
130
+ if ( count( $errors->get_error_codes() ) ) {
131
+ return $errors;
132
+ }
133
+
134
+ $as3cf_item->update_filesize_after_download_local();
135
+
136
+ return true;
137
+ }
138
+
139
+ /**
140
+ * Download an object from provider.
141
+ *
142
+ * @param Storage_Provider $provider_client
143
+ * @param array $object
144
+ *
145
+ * @return bool|WP_Error
146
+ */
147
+ private function download_object( $provider_client, $object ) {
148
+ // Make sure the local directory exists.
149
+ $dir = dirname( $object['SaveAs'] );
150
+ if ( ! is_dir( $dir ) && ! wp_mkdir_p( $dir ) ) {
151
+ $error_message = sprintf( __( 'The local directory %s does not exist and could not be created.', 'amazon-s3-and-cloudfront' ), $dir );
152
+ AS3CF_Error::log( sprintf( __( 'There was an error attempting to download the file %s from the bucket: %s', 'amazon-s3-and-cloudfront' ), $object['Key'], $error_message ) );
153
+ }
154
+
155
+ try {
156
+ $provider_client->get_object( $object );
157
+ } catch ( Exception $e ) {
158
+ $error_msg = sprintf( __( 'Error downloading %1$s from bucket: %2$s', 'amazon-s3-and-cloudfront' ), $object['Key'], $e->getMessage() );
159
+ AS3CF_Error::log( $error_msg );
160
+
161
+ // If storage provider file doesn't exist, an empty local file will be created, clean it up.
162
+ @unlink( $object['SaveAs'] );
163
+
164
+ return new WP_Error( 'download_object', $error_msg );
165
+ }
166
+
167
+ return true;
168
+ }
169
+ }
classes/items/item-handler.php ADDED
@@ -0,0 +1,247 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace DeliciousBrains\WP_Offload_Media\Items;
4
+
5
+ use Amazon_S3_And_CloudFront;
6
+ use AS3CF_Error;
7
+ use Exception;
8
+ use WP_Error;
9
+
10
+ /**
11
+ * Class Item_Handler
12
+ *
13
+ * Base class for item handler classes.
14
+ *
15
+ * @package DeliciousBrains\WP_Offload_Media\Items
16
+ */
17
+ abstract class Item_Handler {
18
+ /**
19
+ * Status codes
20
+ */
21
+ const STATUS_OK = 'ok';
22
+ const STATUS_FAILED = 'failed';
23
+
24
+ /**
25
+ * @var string
26
+ */
27
+ protected static $item_handler_key;
28
+
29
+ /**
30
+ * @var Amazon_S3_And_CloudFront
31
+ */
32
+ protected $as3cf;
33
+
34
+ /**
35
+ * AS3CF_Item_Handler constructor.
36
+ *
37
+ * @param Amazon_S3_And_CloudFront $as3cf
38
+ */
39
+ public function __construct( Amazon_S3_And_CloudFront $as3cf ) {
40
+ $this->as3cf = $as3cf;
41
+ }
42
+
43
+ /**
44
+ * Get the item handler key name.
45
+ *
46
+ * @return string
47
+ */
48
+ public static function get_item_handler_key_name() {
49
+ return static::$item_handler_key;
50
+ }
51
+
52
+ /**
53
+ * The default options that should be used if none supplied.
54
+ *
55
+ * @return array
56
+ */
57
+ public static function default_options() {
58
+ return array();
59
+ }
60
+
61
+ /**
62
+ * Main entrypoint for handling an item.
63
+ *
64
+ * @param Item $as3cf_item
65
+ * @param array $options
66
+ *
67
+ * @return boolean|WP_Error
68
+ */
69
+ public function handle( Item $as3cf_item, array $options = array() ) {
70
+ // Merge supplied option values into the defaults as long as supplied options are recognised.
71
+ if ( empty( $options ) || ! is_array( $options ) ) {
72
+ $options = array();
73
+ }
74
+ $options = array_merge( $this->default_options(), array_intersect_key( $options, $this->default_options() ) );
75
+
76
+ try {
77
+ /**
78
+ * Filter fires before handling an action on an item, allows action to be cancelled.
79
+ *
80
+ * This is a generic handler filter that includes the handler's key name as the last param.
81
+ *
82
+ * @param bool $cancel Should the action on the item be cancelled?
83
+ * @param Item $as3cf_item The item that the action is being handled for.
84
+ * @param array $options Handler dependent options that may have been set for the action.
85
+ * @param array $handler_key_name The handler's key name as per `Item_Handler::get_item_handler_key_name()`.
86
+ *
87
+ * @see Item_Handler::get_item_handler_key_name()
88
+ */
89
+ $cancel = apply_filters(
90
+ 'as3cf_pre_handle_item',
91
+ /**
92
+ * Filter fires before handling an action on an item, allows action to be cancelled.
93
+ *
94
+ * This is a handler specific filter whose name ends with the handler's key name.
95
+ * Format is `as3cf_pre_handle_item_{item-handler-key-name}`.
96
+ *
97
+ * Example filter names:
98
+ *
99
+ * as3cf_pre_handle_item_upload
100
+ * as3cf_pre_handle_item_download
101
+ * as3cf_pre_handle_item_remove-local
102
+ * as3cf_pre_handle_item_remove-provider
103
+ * as3cf_pre_handle_item_update-acl
104
+ *
105
+ * For a more generic filter, use `as3cf_pre_handle_item`.
106
+ *
107
+ * @param bool $cancel Should the action on the item be cancelled?
108
+ * @param Item $as3cf_item The item that the action is being handled for.
109
+ * @param array $options Handler dependent options that may have been set for the action.
110
+ *
111
+ * @see Item_Handler::get_item_handler_key_name()
112
+ */
113
+ apply_filters( 'as3cf_pre_handle_item_' . static::get_item_handler_key_name(), false, $as3cf_item, $options ),
114
+ $as3cf_item,
115
+ $options,
116
+ static::get_item_handler_key_name()
117
+ );
118
+ } catch ( Exception $e ) {
119
+ return $this->return_result( new WP_Error( $e->getMessage() ), $as3cf_item, $options );
120
+ }
121
+
122
+ // Cancelled, let caller know that request was not handled.
123
+ if ( false !== $cancel ) {
124
+ return $this->return_result( false, $as3cf_item, $options );
125
+ }
126
+
127
+ $manifest = $this->pre_handle( $as3cf_item, $options );
128
+ if ( is_wp_error( $manifest ) ) {
129
+ return $this->return_result( $manifest, $as3cf_item, $options );
130
+ }
131
+
132
+ // Nothing to do, let caller know that request was not handled.
133
+ if ( empty( $manifest ) || empty( $manifest->objects ) ) {
134
+ return $this->return_result( false, $as3cf_item, $options );
135
+ }
136
+
137
+ $result = $this->handle_item( $as3cf_item, $manifest, $options );
138
+ if ( is_wp_error( $result ) ) {
139
+ return $this->return_result( $result, $as3cf_item, $options );
140
+ }
141
+
142
+ $result = $this->post_handle( $as3cf_item, $manifest, $options );
143
+
144
+ return $this->return_result( $result, $as3cf_item, $options );
145
+ }
146
+
147
+ /**
148
+ * Process an Item and options to generate a Manifest for `handle_item`.
149
+ *
150
+ * @param Item $as3cf_item
151
+ * @param array $options
152
+ *
153
+ * @return Manifest|WP_Error
154
+ */
155
+ abstract protected function pre_handle( Item $as3cf_item, array $options );
156
+
157
+ /**
158
+ * Perform action for Item using given Manifest.
159
+ *
160
+ * @param Item $as3cf_item
161
+ * @param Manifest $manifest
162
+ * @param array $options
163
+ *
164
+ * @return bool|WP_Error
165
+ */
166
+ abstract protected function handle_item( Item $as3cf_item, Manifest $manifest, array $options );
167
+
168
+ /**
169
+ * Process results of `handle_item` as appropriate.
170
+ *
171
+ * @param Item $as3cf_item
172
+ * @param Manifest $manifest
173
+ * @param array $options
174
+ *
175
+ * @return bool|WP_Error
176
+ */
177
+ abstract protected function post_handle( Item $as3cf_item, Manifest $manifest, array $options );
178
+
179
+ /**
180
+ * Helper to record errors and return meta data on handler error.
181
+ *
182
+ * @param string $error_msg
183
+ * @param array|null $return
184
+ *
185
+ * @return array|WP_Error
186
+ */
187
+ protected function return_handler_error( $error_msg, $return = null ) {
188
+ AS3CF_Error::log( $error_msg );
189
+
190
+ if ( is_null( $return ) ) {
191
+ return new WP_Error( 'exception', $error_msg );
192
+ }
193
+
194
+ return $return;
195
+ }
196
+
197
+ /**
198
+ * Fires a couple of actions to let interested parties know that a handler has returned a result.
199
+ *
200
+ * @param bool|WP_Error $result Result for the action, either handled (true/false), or an error.
201
+ * @param Item $as3cf_item The item that the action was being handled for.
202
+ * @param array $options Handler dependent options that may have been set for the action.
203
+ *
204
+ * @return bool|WP_Error
205
+ */
206
+ private function return_result( $result, Item $as3cf_item, array $options ) {
207
+ /**
208
+ * Action fires after attempting to handle an action on an item.
209
+ *
210
+ * This is a handler specific action whose name ends with the handler's key name.
211
+ * Format is `as3cf_post_handle_item_{item-handler-key-name}`.
212
+ *
213
+ * Example filter names:
214
+ *
215
+ * as3cf_post_handle_item_upload
216
+ * as3cf_post_handle_item_download
217
+ * as3cf_post_handle_item_remove-local
218
+ * as3cf_post_handle_item_remove-provider
219
+ * as3cf_post_handle_item_update-acl
220
+ *
221
+ * For a more generic filter, use `as3cf_post_handle_item`.
222
+ *
223
+ * @param bool|WP_Error $result Result for the action, either handled (true/false), or an error.
224
+ * @param Item $as3cf_item The item that the action was being handled for.
225
+ * @param array $options Handler dependent options that may have been set for the action.
226
+ *
227
+ * @see Item_Handler::get_item_handler_key_name()
228
+ */
229
+ do_action( 'as3cf_post_handle_item_' . static::get_item_handler_key_name(), $result, $as3cf_item, $options );
230
+
231
+ /**
232
+ * Action fires after attempting to handle an action on an item.
233
+ *
234
+ * This is a generic handler action that includes the handler's key name as the last param.
235
+ *
236
+ * @param bool|WP_Error $result Result for the action, either handled (true/false), or an error.
237
+ * @param Item $as3cf_item The item that the action was being handled for.
238
+ * @param array $options Handler dependent options that may have been set for the action.
239
+ * @param array $handler_key_name The handler's key name as per `Item_Handler::get_item_handler_key_name()`.
240
+ *
241
+ * @see Item_Handler::get_item_handler_key_name()
242
+ */
243
+ do_action( 'as3cf_post_handle_item', $result, $as3cf_item, $options, static::get_item_handler_key_name() );
244
+
245
+ return $result;
246
+ }
247
+ }
classes/items/item.php CHANGED
@@ -3,21 +3,26 @@
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
- const ORIGINATORS = array(
12
  'standard' => 0,
13
  'metadata-tool' => 1,
14
  );
 
15
 
16
- protected static $source_type = 'media-library';
17
- protected static $source_table = 'posts';
18
- protected static $source_fk = 'id';
 
19
 
20
- private static $checked_table_exists = array();
21
 
22
  protected static $items_cache_by_id = array();
23
  protected static $items_cache_by_source_id = array();
@@ -34,6 +39,8 @@ abstract class Item {
34
  'source_path' => array( 'source_path', 'original_source_path' ),
35
  );
36
 
 
 
37
  private $id;
38
  private $provider;
39
  private $region;
@@ -51,18 +58,19 @@ abstract class Item {
51
  /**
52
  * Item constructor.
53
  *
54
- * @param string $provider Storage provider key name, e.g. "aws".
55
- * @param string $region Region for item's bucket.
56
- * @param string $bucket Bucket for item.
57
- * @param string $path Key path for item (full sized if type has thumbnails etc).
58
- * @param bool $is_private Is the object private in the bucket.
59
- * @param int $source_id ID that source has.
60
- * @param string $source_path Path that source uses, could be relative or absolute depending on source.
61
- * @param string $original_filename An optional filename with no path that was previously used for the item.
62
- * @param array $extra_info An optional array of extra data specific to the source type.
63
- * @param int $id Optional Item record ID.
64
- * @param int $originator Optional originator of record from ORIGINATORS const.
65
- * @param bool $is_verified Optional flag as to whether Item's objects are known to exist.
 
66
  */
67
  public function __construct(
68
  $provider,
@@ -76,34 +84,101 @@ abstract class Item {
76
  $extra_info = array(),
77
  $id = null,
78
  $originator = 0,
79
- $is_verified = true
 
80
  ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  $this->provider = $provider;
82
  $this->region = $region;
83
  $this->bucket = $bucket;
84
  $this->path = $path;
85
- $this->is_private = $is_private;
86
- $this->source_id = $source_id;
87
- $this->source_path = $source_path;
88
- $this->extra_info = serialize( $extra_info );
89
  $this->originator = $originator;
90
  $this->is_verified = $is_verified;
91
 
92
  if ( empty( $original_filename ) ) {
93
- $this->original_path = $path;
94
- $this->original_source_path = $source_path;
95
  } else {
96
- $this->original_path = str_replace( wp_basename( $path ), $original_filename, $path );
97
- $this->original_source_path = str_replace( wp_basename( $source_path ), $original_filename, $source_path );
98
  }
99
 
100
  if ( ! empty( $id ) ) {
101
  $this->id = $id;
102
  }
103
 
 
 
104
  static::add_to_items_cache( $this );
105
  }
106
 
 
 
 
 
 
 
 
 
 
107
  /**
108
  * Returns the string used to group all keys in the object cache by.
109
  *
@@ -115,6 +190,12 @@ abstract class Item {
115
  if ( empty( $group ) ) {
116
  /** @var Amazon_S3_And_CloudFront $as3cf */
117
  global $as3cf;
 
 
 
 
 
 
118
  $group = trim( '' . apply_filters( 'as3cf_object_cache_group', $as3cf->get_plugin_prefix() ) );
119
  }
120
 
@@ -463,7 +544,7 @@ abstract class Item {
463
  'source_id' => $this->source_id,
464
  'source_path' => $this->source_path,
465
  'original_source_path' => $this->original_source_path,
466
- 'extra_info' => $this->extra_info,
467
  'originator' => $this->originator,
468
  'is_verified' => $this->is_verified,
469
  );
@@ -477,28 +558,6 @@ abstract class Item {
477
  return $key_values;
478
  }
479
 
480
- /**
481
- * All the item's property names in an array, optionally with id if available.
482
- *
483
- * @param bool $include_id Default false.
484
- *
485
- * @return array
486
- */
487
- private function keys( $include_id = false ) {
488
- return array_keys( $this->key_values( $include_id ) );
489
- }
490
-
491
- /**
492
- * All the item's property values in an array, optionally with id if available.
493
- *
494
- * @param bool $include_id Default false.
495
- *
496
- * @return array
497
- */
498
- private function values( $include_id = false ) {
499
- return array_values( $this->key_values( $include_id ) );
500
- }
501
-
502
  /**
503
  * Get item's column formats as an associative array, optionally with id if available.
504
  *
@@ -546,11 +605,15 @@ abstract class Item {
546
  /**
547
  * Save the item's current data.
548
  *
 
 
549
  * @return int|WP_Error
550
  */
551
- public function save() {
552
  global $wpdb;
553
 
 
 
554
  if ( empty( $this->id ) ) {
555
  $result = $wpdb->insert( static::items_table(), $this->key_values(), $this->formats() );
556
 
@@ -561,6 +624,8 @@ abstract class Item {
561
  static::add_to_items_cache( $this );
562
  }
563
  } else {
 
 
564
  // Make sure object cache does not have stale items.
565
  $old_item = static::get_from_object_cache( 'id', $this->id() );
566
  static::remove_from_object_cache( $old_item );
@@ -578,6 +643,34 @@ abstract class Item {
578
  return new WP_Error( 'item_save', 'Error saving item:- ' . $wpdb->last_error );
579
  }
580
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
581
  return $this->id;
582
  }
583
 
@@ -614,13 +707,29 @@ abstract class Item {
614
  * @return Item
615
  */
616
  protected static function create( $object, $add_to_object_cache = false ) {
 
 
 
617
  $extra_info = array();
618
 
619
  if ( ! empty( $object->extra_info ) ) {
620
  $extra_info = unserialize( $object->extra_info );
 
 
 
 
 
 
 
 
 
 
 
 
 
621
  }
622
 
623
- $item = new static(
624
  $object->provider,
625
  $object->region,
626
  $object->bucket,
@@ -636,7 +745,7 @@ abstract class Item {
636
  );
637
 
638
  if ( $add_to_object_cache ) {
639
- static::add_to_object_cache( $item );
640
  }
641
 
642
  return $item;
@@ -678,7 +787,7 @@ abstract class Item {
678
  *
679
  * While source id isn't strictly unique, it is by source type, which is always used in queries based on called class.
680
  *
681
- * @param integer $source_id
682
  *
683
  * @return bool|Item
684
  */
@@ -697,7 +806,7 @@ abstract class Item {
697
 
698
  $item = static::get_from_items_cache_by_source_id( $source_id );
699
 
700
- if ( ! empty( $item ) ) {
701
  return $item;
702
  }
703
 
@@ -712,6 +821,24 @@ abstract class Item {
712
  return static::create( $object, true );
713
  }
714
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
715
  /**
716
  * Getter for item's id value.
717
  *
@@ -739,6 +866,15 @@ abstract class Item {
739
  return $this->region;
740
  }
741
 
 
 
 
 
 
 
 
 
 
742
  /**
743
  * Getter for item's bucket value.
744
  *
@@ -748,13 +884,45 @@ abstract class Item {
748
  return $this->bucket;
749
  }
750
 
 
 
 
 
 
 
 
 
 
751
  /**
752
  * Getter for item's path value.
753
  *
 
 
 
 
 
754
  * @return string
755
  */
756
- public function path() {
757
- return $this->path;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
758
  }
759
 
760
  /**
@@ -766,22 +934,168 @@ abstract class Item {
766
  return $this->original_path;
767
  }
768
 
 
 
 
 
 
 
 
 
 
769
  /**
770
  * Getter for item's is_private value.
771
  *
 
 
772
  * @return bool
773
  */
774
- public function is_private() {
 
 
 
 
 
 
 
 
 
775
  return (bool) $this->is_private;
776
  }
777
 
778
  /**
779
  * Setter for item's is_private value
780
  *
781
- * @param bool $private
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
782
  */
783
- public function set_is_private( $private ) {
784
- $this->is_private = (bool) $private;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
785
  }
786
 
787
  /**
@@ -796,12 +1110,32 @@ abstract class Item {
796
  /**
797
  * Getter for item's source_path value.
798
  *
 
 
799
  * @return string
800
  */
801
- public function source_path() {
 
 
 
 
 
 
 
 
 
802
  return $this->source_path;
803
  }
804
 
 
 
 
 
 
 
 
 
 
805
  /**
806
  * Getter for item's original_source_path value.
807
  *
@@ -811,22 +1145,80 @@ abstract class Item {
811
  return $this->original_source_path;
812
  }
813
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
814
  /**
815
  * Getter for item's extra_info value.
816
  *
817
  * @return array
818
  */
819
  public function extra_info() {
820
- return unserialize( $this->extra_info );
821
  }
822
 
823
  /**
824
- * Setter for extra_info value
825
  *
826
  * @param array $extra_info
827
  */
828
- protected function set_extra_info( $extra_info ) {
829
- $this->extra_info = serialize( $extra_info );
830
  }
831
 
832
  /**
@@ -838,6 +1230,15 @@ abstract class Item {
838
  return $this->originator;
839
  }
840
 
 
 
 
 
 
 
 
 
 
841
  /**
842
  * Getter for item's is_verified value.
843
  *
@@ -848,7 +1249,7 @@ abstract class Item {
848
  }
849
 
850
  /**
851
- * Setter for item's is_verified value
852
  *
853
  * @param bool $is_verified
854
  */
@@ -856,6 +1257,15 @@ abstract class Item {
856
  $this->is_verified = (bool) $is_verified;
857
  }
858
 
 
 
 
 
 
 
 
 
 
859
  /**
860
  * Get normalized object path dir.
861
  *
@@ -912,14 +1322,12 @@ abstract class Item {
912
  *
913
  * @param string $url
914
  *
915
- * @return int|bool
916
  */
917
- public static function get_source_id_by_remote_url( $url ) {
918
  global $wpdb;
919
 
920
- /**
921
- * @var Amazon_S3_And_CloudFront|\Amazon_S3_And_CloudFront_Pro $as3cf
922
- */
923
  global $as3cf;
924
 
925
  $parts = AS3CF_Utils::parse_url( $url );
@@ -952,8 +1360,7 @@ abstract class Item {
952
  }
953
 
954
  $sql = $wpdb->prepare(
955
- "SELECT * FROM " . static::items_table() . " WHERE source_type = %s AND (path LIKE %s OR original_path LIKE %s);"
956
- , static::$source_type
957
  , '%' . $path
958
  , '%' . $path
959
  );
@@ -970,7 +1377,9 @@ abstract class Item {
970
  $path = AS3CF_Utils::decode_filename_in_path( ltrim( $parts['path'], '/' ) );
971
 
972
  foreach ( $results as $result ) {
973
- $as3cf_item = static::create( $result );
 
 
974
 
975
  // If item's bucket matches first segment of URL path, remove it from URL path before checking match.
976
  if ( 0 === strpos( $path, trailingslashit( $as3cf_item->bucket() ) ) ) {
@@ -986,7 +1395,10 @@ abstract class Item {
986
 
987
  // Exact match, return ID.
988
  if ( $as3cf_item->path() === $match_path || $as3cf_item->original_path() === $match_path ) {
989
- return $as3cf_item->source_id();
 
 
 
990
  }
991
  }
992
 
@@ -998,11 +1410,11 @@ abstract class Item {
998
  *
999
  * While source id isn't strictly unique, it is by source type, which is always used in queries based on called class.
1000
  *
1001
- * @param integer $upper_bound Returned source_ids should be lower than this, use null/0 for no upper bound.
1002
- * @param integer $limit Maximum number of source_ids to return. Required if not counting.
1003
- * @param bool $count Just return a count of matching source_ids? Negates $limit, default false.
1004
- * @param int $originator Optionally restrict to only records with given originator type from ORIGINATORS const.
1005
- * @param bool $is_verified Optionally restrict to only records that either are or are not verified.
1006
  *
1007
  * @return array|int
1008
  */
@@ -1030,7 +1442,7 @@ abstract class Item {
1030
  $sql .= ' AND originator = %d';
1031
  $args[] = $originator;
1032
  } else {
1033
- \AS3CF_Error::log( __METHOD__ . ' called with invalid originator: ' . $originator );
1034
 
1035
  return $count ? 0 : array();
1036
  }
@@ -1042,7 +1454,7 @@ abstract class Item {
1042
  $sql .= ' AND is_verified = %d';
1043
  $args[] = (int) $is_verified;
1044
  } else {
1045
- \AS3CF_Error::log( __METHOD__ . ' called with invalid is_verified: ' . $is_verified );
1046
 
1047
  return $count ? 0 : array();
1048
  }
@@ -1067,9 +1479,9 @@ abstract class Item {
1067
  *
1068
  * While source id isn't strictly unique, it is by source type, which is always used in queries based on called class.
1069
  *
1070
- * @param integer $upper_bound Returned source_ids should be lower than this, use null/0 for no upper bound.
1071
- * @param integer $limit Maximum number of source_ids to return. Required if not counting.
1072
- * @param bool $count Just return a count of matching source_ids? Negates $limit, default false.
1073
  *
1074
  * @return array|int
1075
  *
@@ -1084,11 +1496,475 @@ abstract class Item {
1084
  }
1085
 
1086
  /**
1087
- * Get absolute file paths associated with source item.
1088
  *
1089
- * @param integer $id
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1090
  *
1091
  * @return array
1092
  */
1093
- abstract protected function source_paths( $id );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1094
  }
3
  namespace DeliciousBrains\WP_Offload_Media\Items;
4
 
5
  use Amazon_S3_And_CloudFront;
6
+ use AS3CF_Error;
7
  use AS3CF_Utils;
8
+ use DeliciousBrains\WP_Offload_Media\Providers\Storage\Storage_Provider;
9
+ use Exception;
10
  use WP_Error;
11
 
12
  abstract class Item {
13
+ const ITEMS_TABLE = 'as3cf_items';
14
+ const ORIGINATORS = array(
15
  'standard' => 0,
16
  'metadata-tool' => 1,
17
  );
18
+ const CAN_USE_OBJECT_VERSIONING = true;
19
 
20
+ protected static $source_type_name = 'Item';
21
+ protected static $source_type = '';
22
+ protected static $source_table = '';
23
+ protected static $source_fk = '';
24
 
25
+ protected static $can_use_yearmonth = true;
26
 
27
  protected static $items_cache_by_id = array();
28
  protected static $items_cache_by_source_id = array();
39
  'source_path' => array( 'source_path', 'original_source_path' ),
40
  );
41
 
42
+ private static $checked_table_exists = array();
43
+
44
  private $id;
45
  private $provider;
46
  private $region;
58
  /**
59
  * Item constructor.
60
  *
61
+ * @param string $provider Storage provider key name, e.g. "aws".
62
+ * @param string $region Region for item's bucket.
63
+ * @param string $bucket Bucket for item.
64
+ * @param string $path Key path for item (full sized if type has thumbnails etc).
65
+ * @param bool $is_private Is the object private in the bucket.
66
+ * @param int $source_id ID that source has.
67
+ * @param string $source_path Path that source uses, could be relative or absolute depending on source.
68
+ * @param string $original_filename An optional filename with no path that was previously used for the item.
69
+ * @param array $extra_info An optional array of extra data specific to the source type.
70
+ * @param int $id Optional Item record ID.
71
+ * @param int $originator Optional originator of record from ORIGINATORS const.
72
+ * @param bool $is_verified Optional flag as to whether Item's objects are known to exist.
73
+ * @param bool $use_object_versioning Optional flag as to whether path prefix should use Object Versioning if type allows it.
74
  */
75
  public function __construct(
76
  $provider,
84
  $extra_info = array(),
85
  $id = null,
86
  $originator = 0,
87
+ $is_verified = true,
88
+ $use_object_versioning = self::CAN_USE_OBJECT_VERSIONING
89
  ) {
90
+ /** @var Amazon_S3_And_CloudFront $as3cf */
91
+ global $as3cf;
92
+
93
+ $this->source_id = $source_id;
94
+ $this->source_path = $source_path;
95
+
96
+ if ( empty( $original_filename ) ) {
97
+ $this->original_source_path = $source_path;
98
+ } else {
99
+ $this->original_source_path = str_replace( wp_basename( $source_path ), $original_filename, $source_path );
100
+ }
101
+
102
+ // Set offload data from previous duplicate if exact match by source path exists.
103
+ if ( empty( $path ) ) {
104
+ $prev_items = static::get_by_source_path( array( $this->source_path, $this->original_source_path ), $this->source_id, true, true );
105
+
106
+ if ( ! is_wp_error( $prev_items ) && ! empty( $prev_items[0] ) && is_a( $prev_items[0], get_class( $this ) ) ) {
107
+ /** @var Item $prev_item */
108
+ $prev_item = $prev_items[0];
109
+ $provider = $prev_item->provider();
110
+ $region = $prev_item->region();
111
+ $bucket = $prev_item->bucket();
112
+ $path = $prev_item->path();
113
+ $is_private = $prev_item->is_private();
114
+ $extra_info = $prev_item->extra_info();
115
+ }
116
+ }
117
+
118
+ // Not a duplicate, create a new path to offload to.
119
+ if ( empty( $path ) ) {
120
+ $prefix = $this->get_new_item_prefix( $use_object_versioning );
121
+ $path = $prefix . wp_basename( $source_path );
122
+ }
123
+
124
+ if ( ! is_array( $extra_info ) ) {
125
+ $extra_info = array();
126
+ }
127
+
128
+ if ( ! isset( $extra_info['private_prefix'] ) || is_null( $extra_info['private_prefix'] ) ) {
129
+ $extra_info['private_prefix'] = '';
130
+ if ( $as3cf->private_prefix_enabled() ) {
131
+ $extra_info['private_prefix'] = AS3CF_Utils::trailingslash_prefix( $as3cf->get_setting( 'signed-urls-object-prefix', '' ) );
132
+ }
133
+ }
134
+
135
+ if ( empty( $provider ) ) {
136
+ $provider = $as3cf->get_storage_provider()->get_provider_key_name();
137
+ }
138
+
139
+ if ( empty( $region ) ) {
140
+ $region = $as3cf->get_setting( 'region' );
141
+ if ( is_wp_error( $region ) ) {
142
+ $region = '';
143
+ }
144
+ }
145
+
146
+ if ( empty( $bucket ) ) {
147
+ $bucket = $as3cf->get_setting( 'bucket' );
148
+ }
149
+
150
  $this->provider = $provider;
151
  $this->region = $region;
152
  $this->bucket = $bucket;
153
  $this->path = $path;
154
+ $this->extra_info = $extra_info;
 
 
 
155
  $this->originator = $originator;
156
  $this->is_verified = $is_verified;
157
 
158
  if ( empty( $original_filename ) ) {
159
+ $this->original_path = $path;
 
160
  } else {
161
+ $this->original_path = str_replace( wp_basename( $path ), $original_filename, $path );
 
162
  }
163
 
164
  if ( ! empty( $id ) ) {
165
  $this->id = $id;
166
  }
167
 
168
+ $this->set_is_private( (bool) $is_private );
169
+
170
  static::add_to_items_cache( $this );
171
  }
172
 
173
+ /**
174
+ * Returns the standard object key for an items primary object
175
+ *
176
+ * @return string
177
+ */
178
+ public static function primary_object_key() {
179
+ return '__as3cf_primary';
180
+ }
181
+
182
  /**
183
  * Returns the string used to group all keys in the object cache by.
184
  *
190
  if ( empty( $group ) ) {
191
  /** @var Amazon_S3_And_CloudFront $as3cf */
192
  global $as3cf;
193
+
194
+ /**
195
+ * Filters the object cache group name.
196
+ *
197
+ * @param string $group Defaults to 'as3cf'
198
+ */
199
  $group = trim( '' . apply_filters( 'as3cf_object_cache_group', $as3cf->get_plugin_prefix() ) );
200
  }
201
 
544
  'source_id' => $this->source_id,
545
  'source_path' => $this->source_path,
546
  'original_source_path' => $this->original_source_path,
547
+ 'extra_info' => serialize( $this->extra_info ),
548
  'originator' => $this->originator,
549
  'is_verified' => $this->is_verified,
550
  );
558
  return $key_values;
559
  }
560
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
561
  /**
562
  * Get item's column formats as an associative array, optionally with id if available.
563
  *
605
  /**
606
  * Save the item's current data.
607
  *
608
+ * @param bool $update_duplicates If updating, also update records for duplicated source, defaults to true.
609
+ *
610
  * @return int|WP_Error
611
  */
612
+ public function save( $update_duplicates = true ) {
613
  global $wpdb;
614
 
615
+ $update = false;
616
+
617
  if ( empty( $this->id ) ) {
618
  $result = $wpdb->insert( static::items_table(), $this->key_values(), $this->formats() );
619
 
624
  static::add_to_items_cache( $this );
625
  }
626
  } else {
627
+ $update = true;
628
+
629
  // Make sure object cache does not have stale items.
630
  $old_item = static::get_from_object_cache( 'id', $this->id() );
631
  static::remove_from_object_cache( $old_item );
643
  return new WP_Error( 'item_save', 'Error saving item:- ' . $wpdb->last_error );
644
  }
645
 
646
+ // If one or more duplicate exists that still has the same source paths, keep them in step.
647
+ if ( $update && $update_duplicates ) {
648
+ $duplicates = static::get_by_source_path( array( $this->source_path, $this->original_source_path ), $this->source_id );
649
+
650
+ if ( ! empty( $duplicates ) && ! is_wp_error( $duplicates ) ) {
651
+ /** @var Item $duplicate */
652
+ foreach ( $duplicates as $duplicate ) {
653
+ if (
654
+ ! is_wp_error( $duplicate ) &&
655
+ $duplicate->source_type() === $this->source_type() &&
656
+ $duplicate->source_path() === $this->source_path() &&
657
+ $duplicate->original_source_path() === $this->original_source_path()
658
+ ) {
659
+ $duplicate->provider = $this->provider;
660
+ $duplicate->region = $this->region;
661
+ $duplicate->bucket = $this->bucket;
662
+ $duplicate->path = $this->path;
663
+ $duplicate->original_path = $this->original_path;
664
+ $duplicate->is_private = $this->is_private;
665
+ $duplicate->extra_info = $this->extra_info;
666
+ $duplicate->originator = $this->originator;
667
+ $duplicate->is_verified = $this->is_verified;
668
+ $duplicate->save( false );
669
+ }
670
+ }
671
+ }
672
+ }
673
+
674
  return $this->id;
675
  }
676
 
707
  * @return Item
708
  */
709
  protected static function create( $object, $add_to_object_cache = false ) {
710
+ /** @var Amazon_S3_And_CloudFront $as3cf */
711
+ global $as3cf;
712
+
713
  $extra_info = array();
714
 
715
  if ( ! empty( $object->extra_info ) ) {
716
  $extra_info = unserialize( $object->extra_info );
717
+ static::maybe_update_extra_info( $extra_info, $object->source_id, $object->is_private );
718
+ }
719
+
720
+ if ( ! empty( static::$source_type ) && static::$source_type !== $object->source_type ) {
721
+ AS3CF_Error::log( sprintf( 'Doing it wrong! Trying to create a %s class instance with data representing a %s', __CLASS__, $object->source_type ) );
722
+ }
723
+
724
+ if ( empty( static::$source_type ) ) {
725
+ /** @var Item $class */
726
+ $class = $as3cf->get_source_type_class( $object->source_type );
727
+ } else {
728
+ /** @var Item $class */
729
+ $class = $as3cf->get_source_type_class( static::$source_type );
730
  }
731
 
732
+ $item = new $class(
733
  $object->provider,
734
  $object->region,
735
  $object->bucket,
745
  );
746
 
747
  if ( $add_to_object_cache ) {
748
+ $class::add_to_object_cache( $item );
749
  }
750
 
751
  return $item;
787
  *
788
  * While source id isn't strictly unique, it is by source type, which is always used in queries based on called class.
789
  *
790
+ * @param int $source_id
791
  *
792
  * @return bool|Item
793
  */
806
 
807
  $item = static::get_from_items_cache_by_source_id( $source_id );
808
 
809
+ if ( ! empty( $item ) && ! empty( $item->id() ) ) {
810
  return $item;
811
  }
812
 
821
  return static::create( $object, true );
822
  }
823
 
824
+ /**
825
+ * Getter for item's source type.
826
+ *
827
+ * @return string
828
+ */
829
+ public static function source_type() {
830
+ return static::$source_type;
831
+ }
832
+
833
+ /**
834
+ * Getter for item's source type name.
835
+ *
836
+ * @return string
837
+ */
838
+ public static function source_type_name() {
839
+ return static::$source_type_name;
840
+ }
841
+
842
  /**
843
  * Getter for item's id value.
844
  *
866
  return $this->region;
867
  }
868
 
869
+ /**
870
+ * Setter for item's region value.
871
+ *
872
+ * @param string $region
873
+ */
874
+ public function set_region( $region ) {
875
+ $this->region = $region;
876
+ }
877
+
878
  /**
879
  * Getter for item's bucket value.
880
  *
884
  return $this->bucket;
885
  }
886
 
887
+ /**
888
+ * Setter for item's bucket value.
889
+ *
890
+ * @param string $bucket
891
+ */
892
+ public function set_bucket( $bucket ) {
893
+ $this->bucket = $bucket;
894
+ }
895
+
896
  /**
897
  * Getter for item's path value.
898
  *
899
+ * The path is always the public representation,
900
+ * see provider_key() and provider_keys() for realised versions.
901
+ *
902
+ * @param string $object_key
903
+ *
904
  * @return string
905
  */
906
+ public function path( $object_key = null ) {
907
+ $path = $this->path;
908
+
909
+ if ( ! empty( $object_key ) ) {
910
+ $objects = $this->objects();
911
+ if ( isset( $objects[ $object_key ]['source_file'] ) ) {
912
+ $path = $this->prefix() . $objects[ $object_key ]['source_file'];
913
+ }
914
+ }
915
+
916
+ return $path;
917
+ }
918
+
919
+ /**
920
+ * Setter for item's path value.
921
+ *
922
+ * @param $path
923
+ */
924
+ public function set_path( $path ) {
925
+ $this->path = $path;
926
  }
927
 
928
  /**
934
  return $this->original_path;
935
  }
936
 
937
+ /**
938
+ * Setter for item's original path value.
939
+ *
940
+ * @param $path
941
+ */
942
+ public function set_original_path( $path ) {
943
+ $this->original_path = $path;
944
+ }
945
+
946
  /**
947
  * Getter for item's is_private value.
948
  *
949
+ * @param string|null $object_key
950
+ *
951
  * @return bool
952
  */
953
+ public function is_private( $object_key = null ) {
954
+ if ( ! empty( $object_key ) ) {
955
+ $objects = $this->objects();
956
+ if ( isset( $objects[ $object_key ]['is_private'] ) ) {
957
+ return (bool) $objects[ $object_key ]['is_private'];
958
+ }
959
+
960
+ return false;
961
+ }
962
+
963
  return (bool) $this->is_private;
964
  }
965
 
966
  /**
967
  * Setter for item's is_private value
968
  *
969
+ * @param bool $private
970
+ * @param string|null $object_key
971
+ */
972
+ public function set_is_private( $private, $object_key = null ) {
973
+ if ( ! empty( $object_key ) ) {
974
+ $objects = $this->objects();
975
+ if ( isset( $objects[ $object_key ] ) ) {
976
+ $objects[ $object_key ]['is_private'] = $private;
977
+ $this->set_objects( $objects );
978
+ }
979
+
980
+ if ( $object_key === Item::primary_object_key() ) {
981
+ $this->is_private = $private;
982
+ }
983
+
984
+ return;
985
+ }
986
+
987
+ $this->set_is_private( $private, Item::primary_object_key() );
988
+ }
989
+
990
+ /**
991
+ * Any private objects in this item
992
+ *
993
+ * @return bool
994
  */
995
+ public function has_private_objects() {
996
+ foreach ( $this->objects() as $object ) {
997
+ if ( $object['is_private'] ) {
998
+ return true;
999
+ }
1000
+ }
1001
+
1002
+ return false;
1003
+ }
1004
+
1005
+ /**
1006
+ * Getter for the item prefix
1007
+ *
1008
+ * @return string
1009
+ */
1010
+ public function prefix() {
1011
+ $dirname = dirname( $this->path );
1012
+ $dirname = $dirname === '.' ? '' : $dirname;
1013
+
1014
+ return AS3CF_Utils::trailingslash_prefix( $dirname );
1015
+ }
1016
+
1017
+ /**
1018
+ * Get the private prefix for item's private objects.
1019
+ *
1020
+ * @return string
1021
+ */
1022
+ public function private_prefix() {
1023
+ $extra_info = $this->extra_info();
1024
+
1025
+ if ( ! empty( $extra_info['private_prefix'] ) ) {
1026
+ return AS3CF_Utils::trailingslash_prefix( $extra_info['private_prefix'] );
1027
+ }
1028
+
1029
+ return '';
1030
+ }
1031
+
1032
+ /**
1033
+ * Setter for the private prefix
1034
+ *
1035
+ * @param string $new_private_prefix
1036
+ */
1037
+ public function set_private_prefix( $new_private_prefix ) {
1038
+ $extra_info = $this->extra_info();
1039
+ $extra_info['private_prefix'] = AS3CF_Utils::trailingslash_prefix( $new_private_prefix );
1040
+ $this->set_extra_info( $extra_info );
1041
+ }
1042
+
1043
+ /**
1044
+ * Get the full remote key for this item including private prefix when needed
1045
+ *
1046
+ * @param string|null $object_key
1047
+ *
1048
+ * @return string
1049
+ */
1050
+ public function provider_key( $object_key = null ) {
1051
+ $path = $this->path( $object_key );
1052
+ if ( $this->is_private( $object_key ) ) {
1053
+ $path = $this->private_prefix() . $path;
1054
+ }
1055
+
1056
+ return $path;
1057
+ }
1058
+
1059
+ /**
1060
+ * Returns an associative array of provider keys by their object_key.
1061
+ *
1062
+ * NOTE: There may be duplicate keys if object_keys reference same source file/object.
1063
+ *
1064
+ * @return array
1065
+ */
1066
+ public function provider_keys() {
1067
+ $keys = array();
1068
+
1069
+ foreach ( array_keys( $this->objects() ) as $object_key ) {
1070
+ $keys[ $object_key ] = $this->provider_key( $object_key );
1071
+ }
1072
+
1073
+ return $keys;
1074
+ }
1075
+
1076
+ /**
1077
+ * Creates a provider key for a given filename using the item's prefix settings.
1078
+ *
1079
+ * This function can be used to create ad-hoc custom provider keys.
1080
+ * There are no tests to see if the filename is known to be associated with the item.
1081
+ *
1082
+ * @param string $filename Just a filename without any path.
1083
+ * @param bool $is_private Should a private prefixed provider key be created if appropriate?
1084
+ *
1085
+ * @return string
1086
+ */
1087
+ public function provider_key_for_filename( $filename, $is_private ) {
1088
+ $provider_key = '';
1089
+
1090
+ if ( ! empty( $filename ) ) {
1091
+ $provider_key = $this->prefix() . wp_basename( trim( $filename ) );
1092
+
1093
+ if ( $is_private ) {
1094
+ $provider_key = $this->private_prefix() . $provider_key;
1095
+ }
1096
+ }
1097
+
1098
+ return $provider_key;
1099
  }
1100
 
1101
  /**
1110
  /**
1111
  * Getter for item's source_path value.
1112
  *
1113
+ * @param string|null $object_key
1114
+ *
1115
  * @return string
1116
  */
1117
+ public function source_path( $object_key = null ) {
1118
+ if ( ! empty( $object_key ) ) {
1119
+ $objects = $this->objects();
1120
+ if ( isset( $objects[ $object_key ] ) ) {
1121
+ $object_file = $objects[ $object_key ]['source_file'];
1122
+
1123
+ return str_replace( wp_basename( $this->source_path ), $object_file, $this->source_path );
1124
+ }
1125
+ }
1126
+
1127
  return $this->source_path;
1128
  }
1129
 
1130
+ /**
1131
+ * Setter for item's source_path value
1132
+ *
1133
+ * @param string $new_path
1134
+ */
1135
+ public function set_source_path( $new_path ) {
1136
+ $this->source_path = $new_path;
1137
+ }
1138
+
1139
  /**
1140
  * Getter for item's original_source_path value.
1141
  *
1145
  return $this->original_source_path;
1146
  }
1147
 
1148
+ /**
1149
+ * Setter for item's original_source_path value
1150
+ *
1151
+ * @param string $new_path
1152
+ */
1153
+ public function set_original_source_path( $new_path ) {
1154
+ $this->original_source_path = $new_path;
1155
+ }
1156
+
1157
+ /**
1158
+ * Get an absolute source path.
1159
+ *
1160
+ * Default it is based on the WordPress uploads folder.
1161
+ *
1162
+ * @param string|null $object_key Optional, by default the original file's source path is used.
1163
+ *
1164
+ * @return string
1165
+ */
1166
+ public function full_source_path( $object_key = null ) {
1167
+ /**
1168
+ * Filter the absolute directory path prefix for an item's source files.
1169
+ *
1170
+ * @param string $basedir Default is WordPress uploads folder.
1171
+ * @param Item $as3cf_item The Item whose full source path is being accessed.
1172
+ */
1173
+ $basedir = trailingslashit( apply_filters( 'as3cf_item_basedir', wp_upload_dir()['basedir'], $this ) );
1174
+
1175
+ return $basedir . $this->source_path( $object_key );
1176
+ }
1177
+
1178
+ /**
1179
+ * Creates an absolute source path for a given filename using the item's source path settings.
1180
+ *
1181
+ * This function can be used to create ad-hoc custom source file paths.
1182
+ * There are no tests to see if the filename is known to be associated with the item.
1183
+ *
1184
+ * Default it is based on the WordPress uploads folder.
1185
+ *
1186
+ * @param string $filename Just a filename without any path.
1187
+ *
1188
+ * @return string
1189
+ */
1190
+ public function full_source_path_for_filename( $filename ) {
1191
+ if ( empty( $filename ) ) {
1192
+ return '';
1193
+ }
1194
+
1195
+ /**
1196
+ * Filter the absolute directory path prefix for an item's source files.
1197
+ *
1198
+ * @param string $basedir Default is WordPress uploads folder.
1199
+ * @param Item $as3cf_item The Item whose full source path is being accessed.
1200
+ */
1201
+ $basedir = trailingslashit( apply_filters( 'as3cf_item_basedir', wp_upload_dir()['basedir'], $this ) );
1202
+
1203
+ return $basedir . str_replace( wp_basename( $this->source_path ), wp_basename( trim( $filename ) ), $this->source_path );
1204
+ }
1205
+
1206
  /**
1207
  * Getter for item's extra_info value.
1208
  *
1209
  * @return array
1210
  */
1211
  public function extra_info() {
1212
+ return $this->extra_info;
1213
  }
1214
 
1215
  /**
1216
+ * Setter for extra_info value.
1217
  *
1218
  * @param array $extra_info
1219
  */
1220
+ public function set_extra_info( $extra_info ) {
1221
+ $this->extra_info = $extra_info;
1222
  }
1223
 
1224
  /**
1230
  return $this->originator;
1231
  }
1232
 
1233
+ /**
1234
+ * Setter for item's originator value.
1235
+ *
1236
+ * @param int $originator
1237
+ */
1238
+ public function set_originator( $originator ) {
1239
+ $this->originator = $originator;
1240
+ }
1241
+
1242
  /**
1243
  * Getter for item's is_verified value.
1244
  *
1249
  }
1250
 
1251
  /**
1252
+ * Setter for item's is_verified value.
1253
  *
1254
  * @param bool $is_verified
1255
  */
1257
  $this->is_verified = (bool) $is_verified;
1258
  }
1259
 
1260
+ /**
1261
+ * Does this item type use object versioning?
1262
+ *
1263
+ * @return bool
1264
+ */
1265
+ public static function can_use_object_versioning() {
1266
+ return static::CAN_USE_OBJECT_VERSIONING;
1267
+ }
1268
+
1269
  /**
1270
  * Get normalized object path dir.
1271
  *
1322
  *
1323
  * @param string $url
1324
  *
1325
+ * @return array|bool
1326
  */
1327
+ public static function get_item_source_by_remote_url( $url ) {
1328
  global $wpdb;
1329
 
1330
+ /** @var Amazon_S3_And_CloudFront $as3cf */
 
 
1331
  global $as3cf;
1332
 
1333
  $parts = AS3CF_Utils::parse_url( $url );
1360
  }
1361
 
1362
  $sql = $wpdb->prepare(
1363
+ "SELECT * FROM " . static::items_table() . " WHERE (path LIKE %s OR original_path LIKE %s);"
 
1364
  , '%' . $path
1365
  , '%' . $path
1366
  );
1377
  $path = AS3CF_Utils::decode_filename_in_path( ltrim( $parts['path'], '/' ) );
1378
 
1379
  foreach ( $results as $result ) {
1380
+ /** @var Item $class */
1381
+ $class = $as3cf->get_source_type_class( $result->source_type );
1382
+ $as3cf_item = $class::create( $result );
1383
 
1384
  // If item's bucket matches first segment of URL path, remove it from URL path before checking match.
1385
  if ( 0 === strpos( $path, trailingslashit( $as3cf_item->bucket() ) ) ) {
1395
 
1396
  // Exact match, return ID.
1397
  if ( $as3cf_item->path() === $match_path || $as3cf_item->original_path() === $match_path ) {
1398
+ return array(
1399
+ 'id' => $as3cf_item->source_id(),
1400
+ 'source_type' => $as3cf_item->source_type(),
1401
+ );
1402
  }
1403
  }
1404
 
1410
  *
1411
  * While source id isn't strictly unique, it is by source type, which is always used in queries based on called class.
1412
  *
1413
+ * @param int $upper_bound Returned source_ids should be lower than this, use null/0 for no upper bound.
1414
+ * @param int $limit Maximum number of source_ids to return. Required if not counting.
1415
+ * @param bool $count Just return a count of matching source_ids? Negates $limit, default false.
1416
+ * @param int $originator Optionally restrict to only records with given originator type from ORIGINATORS const.
1417
+ * @param bool $is_verified Optionally restrict to only records that either are or are not verified.
1418
  *
1419
  * @return array|int
1420
  */
1442
  $sql .= ' AND originator = %d';
1443
  $args[] = $originator;
1444
  } else {
1445
+ AS3CF_Error::log( __METHOD__ . ' called with invalid originator: ' . $originator );
1446
 
1447
  return $count ? 0 : array();
1448
  }
1454
  $sql .= ' AND is_verified = %d';
1455
  $args[] = (int) $is_verified;
1456
  } else {
1457
+ AS3CF_Error::log( __METHOD__ . ' called with invalid is_verified: ' . $is_verified );
1458
 
1459
  return $count ? 0 : array();
1460
  }
1479
  *
1480
  * While source id isn't strictly unique, it is by source type, which is always used in queries based on called class.
1481
  *
1482
+ * @param int $upper_bound Returned source_ids should be lower than this, use null/0 for no upper bound.
1483
+ * @param int $limit Maximum number of source_ids to return. Required if not counting.
1484
+ * @param bool $count Just return a count of matching source_ids? Negates $limit, default false.
1485
  *
1486
  * @return array|int
1487
  *
1496
  }
1497
 
1498
  /**
1499
+ * Get array of objects (i.e. different sizes of same attachment item)
1500
  *
1501
+ * @return array
1502
+ */
1503
+ public function objects() {
1504
+ $extra_info = $this->extra_info();
1505
+ if ( isset( $extra_info['objects'] ) && is_array( $extra_info['objects'] ) ) {
1506
+ // Make sure that the primary object key, if exists, comes first
1507
+ $array_keys = array_keys( $extra_info['objects'] );
1508
+ $primary_key = Item::primary_object_key();
1509
+ if ( in_array( $primary_key, $array_keys ) && $primary_key !== $array_keys[0] ) {
1510
+ $extra_info['objects'] = array_merge( array( $primary_key => null ), $extra_info['objects'] );
1511
+ }
1512
+
1513
+ return $extra_info['objects'];
1514
+ }
1515
+
1516
+ return array();
1517
+ }
1518
+
1519
+ /**
1520
+ * Set array of objects (i.e. different sizes of same attachment item)
1521
+ *
1522
+ * @param array $objects
1523
+ */
1524
+ public function set_objects( $objects ) {
1525
+ $extra_info = $this->extra_info();
1526
+
1527
+ $extra_info['objects'] = $objects;
1528
+ $this->set_extra_info( $extra_info );
1529
+ }
1530
+
1531
+ /**
1532
+ * Synthesize a data struct to be used when passing information
1533
+ * about the current item to filters that assume the item is a
1534
+ * media library item.
1535
  *
1536
  * @return array
1537
  */
1538
+ public function item_data_for_acl_filter() {
1539
+ return array(
1540
+ 'source_type' => $this->source_type(),
1541
+ 'file' => $this->path( Item::primary_object_key() ),
1542
+ 'sizes' => array_keys( $this->objects() ),
1543
+ );
1544
+ }
1545
+
1546
+ /**
1547
+ * Get absolute source file paths for offloaded files.
1548
+ *
1549
+ * @return array Associative array of object_key => path
1550
+ */
1551
+ abstract public function full_source_paths();
1552
+
1553
+ /**
1554
+ * Get size name from file name.
1555
+ *
1556
+ * @return string
1557
+ */
1558
+ abstract public function get_object_key_from_filename( $filename );
1559
+
1560
+ /**
1561
+ * Get the provider URL for an item
1562
+ *
1563
+ * @param string|null $object_key
1564
+ *
1565
+ * @return string|false
1566
+ */
1567
+ public abstract function get_local_url( $object_key = null );
1568
+
1569
+ /**
1570
+ * Create a new item from the source id.
1571
+ *
1572
+ * @param int $source_id
1573
+ * @param array $options
1574
+ *
1575
+ * @return Item|WP_Error
1576
+ */
1577
+ public static function create_from_source_id( $source_id, $options = array() ) {
1578
+ return new WP_Error(
1579
+ 'exception',
1580
+ sprintf( 'Doing it wrong! Trying to create a base %s class instance from source ID %d', __CLASS__, $source_id )
1581
+ );
1582
+ }
1583
+
1584
+ /**
1585
+ * Return a year/month string for the item
1586
+ *
1587
+ * @return string
1588
+ */
1589
+ protected function get_item_time() {
1590
+ return null;
1591
+ }
1592
+
1593
+ /**
1594
+ * Return an additional 'internal' prefix used by some item types
1595
+ *
1596
+ * @return string
1597
+ */
1598
+ protected function get_internal_prefix() {
1599
+ return '';
1600
+ }
1601
+
1602
+ /**
1603
+ * Get item's new public prefix path for current settings.
1604
+ *
1605
+ * @param bool $use_object_versioning
1606
+ *
1607
+ * @return string
1608
+ */
1609
+ public function get_new_item_prefix( $use_object_versioning = true ) {
1610
+ /** @var Amazon_S3_And_CloudFront $as3cf */
1611
+ global $as3cf;
1612
+
1613
+ $prefix = $as3cf->get_object_prefix();
1614
+
1615
+ $time = $this->get_item_time();
1616
+ $prefix .= AS3CF_Utils::trailingslash_prefix( $as3cf->get_dynamic_prefix( $time, static::$can_use_yearmonth ) );
1617
+
1618
+ if ( $use_object_versioning && static::can_use_object_versioning() && $as3cf->get_setting( 'object-versioning' ) ) {
1619
+ $prefix .= AS3CF_Utils::trailingslash_prefix( $as3cf->get_object_version_string() );
1620
+ }
1621
+
1622
+ return AS3CF_Utils::trailingslash_prefix( $prefix );
1623
+ }
1624
+
1625
+ /**
1626
+ * Get ACL for object key
1627
+ *
1628
+ * @param string $object_key Object key
1629
+ * @param string|null $bucket Optional bucket that ACL is potentially to be used with.
1630
+ *
1631
+ * @return string|null
1632
+ */
1633
+ public function get_acl_for_object_key( $object_key, $bucket = null ) {
1634
+ /** @var Amazon_S3_And_CloudFront $as3cf */
1635
+ global $as3cf;
1636
+
1637
+ $acl = null;
1638
+ $use_acl = $as3cf->use_acl_for_intermediate_size( 0, $object_key, $bucket, $this );
1639
+
1640
+ if ( $use_acl ) {
1641
+ $acl = $this->is_private( $object_key ) ? $as3cf->get_storage_provider()->get_private_acl() : $as3cf->get_storage_provider()->get_default_acl();
1642
+ }
1643
+
1644
+ return $acl;
1645
+ }
1646
+
1647
+ /**
1648
+ * Search for all items that have the source path(s).
1649
+ *
1650
+ * @param array|string $paths Array of relative source paths.
1651
+ * @param array|int $exclude_source_ids Array of source_ids to exclude from search. Default, none.
1652
+ * @param bool $exact_match Use paths as supplied (true, default), or greedy match on path without extension (e.g. find edited too).
1653
+ * @param bool $first_only Only return first matched item sorted by source_id. Default false.
1654
+ *
1655
+ * @return array
1656
+ */
1657
+ public static function get_by_source_path( $paths, $exclude_source_ids = array(), $exact_match = true, $first_only = false ) {
1658
+ global $wpdb;
1659
+
1660
+ if ( ! is_array( $paths ) && is_string( $paths ) && ! empty( $paths ) ) {
1661
+ $paths = array( $paths );
1662
+ }
1663
+
1664
+ if ( ! is_array( $paths ) || empty( $paths ) ) {
1665
+ return array();
1666
+ }
1667
+
1668
+ $paths = AS3CF_Utils::make_upload_file_paths_relative( array_unique( $paths ) );
1669
+
1670
+ $sql = '
1671
+ SELECT DISTINCT items.*
1672
+ FROM ' . static::items_table() . ' AS items USE INDEX (uidx_source_path, uidx_original_source_path)
1673
+ WHERE 1=1
1674
+ ';
1675
+
1676
+ if ( ! empty( $exclude_source_ids ) ) {
1677
+ if ( ! is_array( $exclude_source_ids ) ) {
1678
+ $exclude_source_ids = array( $exclude_source_ids );
1679
+ }
1680
+
1681
+ $sql .= ' AND items.source_id NOT IN (' . join( ',', $exclude_source_ids ) . ')';
1682
+ }
1683
+
1684
+ if ( $exact_match ) {
1685
+ $sql .= " AND (items.source_path IN ('" . join( "','", $paths ) . "')";
1686
+ $sql .= " OR items.original_source_path IN ('" . join( "','", $paths ) . "'))";
1687
+ } else {
1688
+ $likes = array_map( function ( $path ) {
1689
+ $ext = '.' . pathinfo( $path, PATHINFO_EXTENSION );
1690
+ $path = substr_replace( $path, '%', -strlen( $ext ) );
1691
+
1692
+ return "items.source_path LIKE '" . $path . "' OR items.original_source_path LIKE '" . $path . "'";
1693
+ }, $paths );
1694
+
1695
+ $sql .= ' AND (' . join( ' OR ', $likes ) . ')';
1696
+ }
1697
+
1698
+ if ( $first_only ) {
1699
+ $sql .= ' ORDER BY items.source_id LIMIT 1';
1700
+ }
1701
+
1702
+ return array_map( 'static::create', $wpdb->get_results( $sql ) );
1703
+ }
1704
+
1705
+ /**
1706
+ * Update path and original path with a new prefix
1707
+ *
1708
+ * @param string $new_prefix
1709
+ */
1710
+ public function update_path_prefix( $new_prefix ) {
1711
+ $this->set_path( $new_prefix . wp_basename( $this->path() ) );
1712
+ $this->set_original_path( $new_prefix . wp_basename( $this->original_path() ) );
1713
+ }
1714
+
1715
+ /**
1716
+ * Returns a link to the items edit page in WordPress
1717
+ *
1718
+ * @param object $error
1719
+ *
1720
+ * @return object|null Null or object containing properties 'url' and 'text'
1721
+ */
1722
+ public static function admin_link( $error ) {
1723
+ return null;
1724
+ }
1725
+
1726
+ /**
1727
+ * Is the item served by provider.
1728
+ *
1729
+ * @param bool $skip_rewrite_check Still check if offloaded even if not currently rewriting URLs? Default: false
1730
+ * @param bool $skip_current_provider_check Skip checking if offloaded to current provider. Default: false, negated if $provider supplied
1731
+ * @param Storage_Provider|null $provider Provider where item is expected to be offloaded to. Default: currently configured provider
1732
+ * @param bool $check_is_verified Check that metadata is verified, has no effect if $skip_rewrite_check is true. Default: false
1733
+ *
1734
+ * @return bool
1735
+ */
1736
+ public function served_by_provider( $skip_rewrite_check = false, $skip_current_provider_check = false, Storage_Provider $provider = null, $check_is_verified = false ) {
1737
+ /** @var Amazon_S3_And_CloudFront $as3cf */
1738
+ global $as3cf;
1739
+
1740
+ if ( ! $skip_rewrite_check && ! $as3cf->get_setting( 'serve-from-s3' ) ) {
1741
+ // Not serving provider URLs
1742
+ return false;
1743
+ }
1744
+
1745
+ if ( ! $skip_rewrite_check && ! empty( $check_is_verified ) && ! $this->is_verified() ) {
1746
+ // Offload not verified, treat as not offloaded.
1747
+ return false;
1748
+ }
1749
+
1750
+ if ( ! $skip_current_provider_check && empty( $provider ) ) {
1751
+ $provider = $as3cf->get_storage_provider();
1752
+ }
1753
+
1754
+ if ( ! empty( $provider ) && $provider::get_provider_key_name() !== $this->provider() ) {
1755
+ // File not uploaded to required provider
1756
+ return false;
1757
+ }
1758
+
1759
+ return true;
1760
+ }
1761
+
1762
+ /**
1763
+ * Does the item's files exist locally?
1764
+ *
1765
+ * @return bool
1766
+ */
1767
+ public function exists_locally() {
1768
+ foreach ( $this->full_source_paths() as $path ) {
1769
+ if ( file_exists( $path ) ) {
1770
+ return true;
1771
+ }
1772
+ }
1773
+
1774
+ return false;
1775
+ }
1776
+
1777
+ /**
1778
+ * Get the provider URL for an item
1779
+ *
1780
+ * @param string $object_key
1781
+ * @param null|int $expires
1782
+ * @param array $headers
1783
+ *
1784
+ * @return string|WP_Error|bool
1785
+ */
1786
+ public function get_provider_url( $object_key = null, $expires = null, $headers = array() ) {
1787
+ /** @var Amazon_S3_And_CloudFront $as3cf */
1788
+ global $as3cf;
1789
+
1790
+ if ( is_null( $object_key ) ) {
1791
+ $object_key = Item::primary_object_key();
1792
+ }
1793
+
1794
+ // Is a signed expiring URL required for the requested object?
1795
+ if ( is_null( $expires ) ) {
1796
+ $expires = $this->is_private( $object_key ) ? Amazon_S3_And_CloudFront::DEFAULT_EXPIRES : null;
1797
+ } else {
1798
+ $expires = $this->is_private( $object_key ) ? $expires : null;
1799
+ }
1800
+
1801
+ $scheme = $as3cf->get_url_scheme();
1802
+ $enable_delivery_domain = $as3cf->get_delivery_provider()->delivery_domain_allowed() ? $as3cf->get_setting( 'enable-delivery-domain' ) : false;
1803
+ $delivery_domain = $as3cf->get_setting( 'delivery-domain' );
1804
+ $item_path = $this->path( $object_key );
1805
+
1806
+ if ( ! $enable_delivery_domain || empty( $delivery_domain ) ) {
1807
+ $region = $this->region();
1808
+
1809
+ if ( is_wp_error( $region ) ) {
1810
+ return $region;
1811
+ }
1812
+
1813
+ $delivery_domain = $as3cf->get_storage_provider()->get_url_domain( $this->bucket(), $region, $expires );
1814
+ } else {
1815
+ $delivery_domain = AS3CF_Utils::sanitize_custom_domain( $delivery_domain );
1816
+ }
1817
+
1818
+ if ( ! is_null( $expires ) && $as3cf->is_plugin_setup( true ) ) {
1819
+ try {
1820
+ /**
1821
+ * Filters the expires time for private content
1822
+ *
1823
+ * @param int $expires The expires time in seconds
1824
+ */
1825
+ $timestamp = time() + apply_filters( 'as3cf_expires', $expires );
1826
+ $url = $as3cf->get_delivery_provider()->get_signed_url( $this, $item_path, $delivery_domain, $scheme, $timestamp, $headers );
1827
+
1828
+ /**
1829
+ * Filters the secure URL for private content
1830
+ *
1831
+ * @param string $url The URL
1832
+ * @param Item $item The Item object
1833
+ * @param array $item_source The item source descriptor array
1834
+ * @param int $timestamp Expiry timestamp
1835
+ * @param array $headers Optional extra http headers
1836
+ */
1837
+ return apply_filters( 'as3cf_get_item_secure_url', $url, $this, $this->get_item_source_array(), $timestamp, $headers );
1838
+ } catch ( Exception $e ) {
1839
+ return new WP_Error( 'exception', $e->getMessage() );
1840
+ }
1841
+ } else {
1842
+ try {
1843
+ $url = $as3cf->get_delivery_provider()->get_url( $this, $item_path, $delivery_domain, $scheme, $headers );
1844
+
1845
+ /**
1846
+ * Filters the URL for public content
1847
+ *
1848
+ * @param string $url The URL
1849
+ * @param Item $item The Item object
1850
+ * @param array $item_source The item source descriptor array
1851
+ * @param int $source_id The source ID of the object
1852
+ * @param int $timestamp Expiry timestamp
1853
+ * @param array $headers Optional extra http headers
1854
+ */
1855
+ return apply_filters( 'as3cf_get_item_url', $url, $this, $this->get_item_source_array(), $expires, $headers );
1856
+ } catch ( Exception $e ) {
1857
+ return new WP_Error( 'exception', $e->getMessage() );
1858
+ }
1859
+ }
1860
+ }
1861
+
1862
+ /**
1863
+ * Update file sizes after removing local files for an item
1864
+ *
1865
+ * @param int $original_size
1866
+ * @param int $total_size
1867
+ */
1868
+ public function update_filesize_after_remove_local( $original_size, $total_size ) {
1869
+ }
1870
+
1871
+ /**
1872
+ * Cleanup file sizes after getting item files back from the bucket
1873
+ */
1874
+ public function update_filesize_after_download_local() {
1875
+ }
1876
+
1877
+ /**
1878
+ * If another item in current site shares full size *local* paths, only remove remote files not referenced by duplicates.
1879
+ * We reference local paths as they should be reflected one way or another remotely, including backups.
1880
+ *
1881
+ * @params Item $as3cf_item
1882
+ * @params array $paths
1883
+ */
1884
+ public function remove_duplicate_paths( Item $as3cf_item, $paths ) {
1885
+ return $paths;
1886
+ }
1887
+
1888
+ /**
1889
+ * Verify that the extra info uses the new format set in plugin version 2.6.0
1890
+ * Update if needed
1891
+ *
1892
+ * @param array $extra_info
1893
+ * @param int $source_id
1894
+ * @param bool $is_private
1895
+ *
1896
+ * @since 2.6.0
1897
+ */
1898
+ protected static function maybe_update_extra_info( &$extra_info, $source_id, $is_private ) {
1899
+ if ( ! is_array( $extra_info ) ) {
1900
+ return;
1901
+ }
1902
+
1903
+ // Compatibility fallback for if just an array of private sizes is supplied.
1904
+ $private_sizes = array();
1905
+ if ( ! isset( $extra_info['private_sizes'] ) && ! isset( $extra_info['private_prefix'] ) && ! isset( $extra_info['objects'] ) ) {
1906
+ $private_sizes = $extra_info;
1907
+ }
1908
+
1909
+ if ( ! isset( $extra_info['objects'] ) ) {
1910
+ $private_sizes = isset( $extra_info['private_sizes'] ) && is_array( $extra_info['private_sizes'] ) ? $extra_info['private_sizes'] : $private_sizes;
1911
+ $extra_info['objects'] = array();
1912
+
1913
+ $files = AS3CF_Utils::get_attachment_file_paths( $source_id, false );
1914
+ foreach ( $files as $object_key => $file ) {
1915
+ if ( 'file' === $object_key ) {
1916
+ continue;
1917
+ }
1918
+
1919
+ $new_object = array(
1920
+ 'source_file' => wp_basename( $file ),
1921
+ 'is_private' => Item::primary_object_key() === $object_key ? $is_private : in_array( $object_key, $private_sizes ),
1922
+ );
1923
+
1924
+ $extra_info['objects'][ $object_key ] = $new_object;
1925
+ }
1926
+ }
1927
+
1928
+ if ( isset( $extra_info['private_sizes'] ) ) {
1929
+ unset( $extra_info['private_sizes'] );
1930
+ }
1931
+ }
1932
+
1933
+ /**
1934
+ * Returns the item source description array for this item
1935
+ *
1936
+ * @return array Array with the format:
1937
+ * array(
1938
+ * 'id' => 1,
1939
+ * 'source_type' => 'foo-type',
1940
+ * )
1941
+ */
1942
+ public function get_item_source_array() {
1943
+ return array(
1944
+ 'id' => $this->source_id(),
1945
+ 'source_type' => $this->source_type(),
1946
+ );
1947
+ }
1948
+
1949
+ /**
1950
+ * Returns an array keyed by offloaded source file name.
1951
+ *
1952
+ * Each entry is as per objects, but also includes an array of object_keys.
1953
+ *
1954
+ * @return array
1955
+ */
1956
+ public function offloaded_files() {
1957
+ $offloaded_files = array();
1958
+
1959
+ foreach ( $this->objects() as $object_key => $object ) {
1960
+ if ( isset( $offloaded_files[ $object['source_file'] ] ) ) {
1961
+ $offloaded_files[ $object['source_file'] ]['object_keys'][] = $object_key;
1962
+ } else {
1963
+ $object['object_keys'] = array( $object_key );
1964
+ $offloaded_files[ $object['source_file'] ] = $object;
1965
+ }
1966
+ }
1967
+
1968
+ return $offloaded_files;
1969
+ }
1970
  }
classes/items/manifest.php ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace DeliciousBrains\WP_Offload_Media\Items;
4
+
5
+ class Manifest {
6
+ /**
7
+ * @var array
8
+ */
9
+ public $objects = array();
10
+ }
classes/items/media-library-item.php CHANGED
@@ -3,33 +3,65 @@
3
  namespace DeliciousBrains\WP_Offload_Media\Items;
4
 
5
  use Amazon_S3_And_CloudFront;
6
- use WP_Error;
7
  use AS3CF_Utils;
 
8
 
9
  class Media_Library_Item extends Item {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  private static $attachment_counts = array();
11
  private static $attachment_count_skips = array();
12
 
13
  /**
14
  * Item constructor.
15
  *
16
- * @param string $provider Storage provider key name, e.g. "aws".
17
- * @param string $region Region for item's bucket.
18
- * @param string $bucket Bucket for item.
19
- * @param string $path Key path for item (full sized if type has thumbnails etc).
20
- * @param bool $is_private Is the object private in the bucket.
21
- * @param int $source_id ID that source has.
22
- * @param string $source_path Path that source uses, could be relative or absolute depending on source.
23
- * @param string $original_filename An optional filename with no path that was previously used for the item.
24
- * @param array $extra_info An optional associative array of extra data to be associated with the item.
25
- * Recognised keys:
26
- * 'private_sizes' => ['thumbnail', 'medium', ...]
27
- * 'private_prefix' => 'private/'
28
- * For backwards compatibility, if a simple array is supplied it is treated as
29
- * private thumbnail sizes that should be private objects in the bucket.
30
- * @param int $id Optional Item record ID.
31
- * @param int $originator Optional originator of record from ORIGINATORS const.
32
- * @param bool $is_verified Optional flag as to whether Item's objects are known to exist.
 
 
 
 
33
  */
34
  public function __construct(
35
  $provider,
@@ -43,7 +75,8 @@ class Media_Library_Item extends Item {
43
  $extra_info = array(),
44
  $id = null,
45
  $originator = 0,
46
- $is_verified = true
 
47
  ) {
48
  // For Media Library items, the source path should be relative to the Media Library's uploads directory.
49
  $uploads = wp_upload_dir();
@@ -52,155 +85,201 @@ class Media_Library_Item extends Item {
52
  $source_path = AS3CF_Utils::unleadingslashit( substr( $source_path, strlen( $uploads['basedir'] ) ) );
53
  }
54
 
55
- $private_sizes = array();
56
- $private_prefix = '';
57
 
58
  // Ensure re-hydration is clean.
59
  if ( ! empty( $extra_info ) && is_array( $extra_info ) ) {
60
- if ( isset( $extra_info['private_sizes'] ) ) {
61
- $private_sizes = $extra_info['private_sizes'];
62
- }
63
  if ( isset( $extra_info['private_prefix'] ) ) {
64
  $private_prefix = $extra_info['private_prefix'];
65
  }
66
-
67
- // Compatibility fallback for if just an array of private sizes is supplied.
68
- if ( ! isset( $extra_info['private_sizes'] ) && ! isset( $extra_info['private_prefix'] ) ) {
69
- $private_sizes = $extra_info;
70
  }
71
  }
72
 
73
  $extra_info = array(
74
- 'private_sizes' => $private_sizes,
75
  'private_prefix' => $private_prefix,
76
  );
77
 
78
- parent::__construct( $provider, $region, $bucket, $path, $is_private, $source_id, $source_path, $original_filename, $extra_info, $id, $originator, $is_verified );
79
  }
80
 
81
  /**
82
- * Get a new Media_Library_Item with all data derived from attachment data and current settings.
83
- *
84
- * @param int $attachment_id Attachment ID to construct record from.
85
- * @param bool $object_versioning_allowed Can an Object Versioning string be appended if setting turned on? Default true.
86
- * @param int $originator Originator of new record. Optional, default standard (0).
87
  *
88
- * @return Media_Library_Item|WP_Error
89
  */
90
- public static function create_from_attachment( $attachment_id, $object_versioning_allowed = true, $originator = 0 ) {
91
- /** @var Amazon_S3_And_CloudFront $as3cf */
92
- global $as3cf;
 
 
 
 
 
 
 
93
 
94
- if ( empty( $attachment_id ) ) {
 
 
 
 
 
 
 
 
 
 
 
 
95
  return new WP_Error(
96
  'exception',
97
  __( 'Empty Attachment ID passed to ' . __FUNCTION__, 'amazon-s3-and-cloudfront' )
98
  );
99
  }
100
 
101
- $object_versioning_allowed = empty( $object_versioning_allowed ) ? false : true;
 
 
 
 
 
 
102
 
103
- if ( ! in_array( $originator, self::ORIGINATORS ) ) {
104
  return new WP_Error(
105
  'exception',
106
  __( 'Invalid Originator passed to ' . __FUNCTION__, 'amazon-s3-and-cloudfront' )
107
  );
108
  }
109
 
110
- // If we ever expand originators to include more pre-verified versions, this will need changing.
111
- $is_verified = 0 === $originator;
112
-
113
  /*
114
- * Provider basics.
115
- */
116
-
117
- $provider = $as3cf->get_storage_provider()->get_provider_key_name();
118
- $region = $as3cf->get_setting( 'region' );
119
- if ( is_wp_error( $region ) ) {
120
- $region = '';
121
- }
122
- $bucket = $as3cf->get_setting( 'bucket' );
123
-
124
- /*
125
- * Derive local and remote paths.
126
  */
127
 
128
  // Verify that get_attached_file will not blow up as it does not check the data it manipulates.
129
- $attached_file_meta = get_post_meta( $attachment_id, '_wp_attached_file', true );
130
  if ( ! is_string( $attached_file_meta ) ) {
131
  return new WP_Error(
132
  'exception',
133
- sprintf( __( 'Media Library item with ID %d has damaged meta data', 'amazon-s3-and-cloudfront' ), $attachment_id )
134
  );
135
  }
136
  unset( $attached_file_meta );
137
 
138
- $source_path = get_attached_file( $attachment_id, true );
139
 
140
  // Check for valid "full" file path otherwise we'll not be able to create offload path or download in the future.
141
  if ( empty( $source_path ) ) {
142
  return new WP_Error(
143
  'exception',
144
- sprintf( __( 'Media Library item with ID %d does not have a valid file path', 'amazon-s3-and-cloudfront' ), $attachment_id )
145
  );
146
  }
147
 
148
- $attachment_metadata = wp_get_attachment_metadata( $attachment_id, true );
149
-
150
  if ( is_wp_error( $attachment_metadata ) ) {
151
  return $attachment_metadata;
152
  }
153
 
154
- $prefix = $as3cf->get_new_attachment_prefix( $attachment_id, $attachment_metadata, $object_versioning_allowed );
155
- $path = $prefix . wp_basename( $source_path );
156
-
157
- // There may be an original image that can override the default original filename.
158
- $original_filename = empty( $attachment_metadata['original_image'] ) ? null : $attachment_metadata['original_image'];
159
-
160
- /*
161
- * Private file handling.
162
- */
163
-
164
- $acl = apply_filters( 'as3cf_upload_acl', $as3cf->get_storage_provider()->get_default_acl(), $attachment_metadata, $attachment_id );
165
- $is_private = ! empty( $acl ) && $as3cf->get_storage_provider()->get_private_acl() === $acl;
166
-
167
- // Maybe set private sizes and private prefix.
168
  $extra_info = array(
169
- 'private_sizes' => array(),
170
- 'private_prefix' => '',
171
  );
172
 
173
- $file_paths = AS3CF_Utils::get_attachment_file_paths( $attachment_id, false, $attachment_metadata );
174
- $file_paths = array_diff( $file_paths, array( $source_path ) );
175
 
 
176
  foreach ( $file_paths as $size => $size_file_path ) {
177
- $acl = apply_filters( 'as3cf_upload_acl_sizes', $as3cf->get_storage_provider()->get_default_acl(), $size, $attachment_id, $attachment_metadata );
178
-
179
- if ( ! empty( $acl ) && $as3cf->get_storage_provider()->get_private_acl() === $acl ) {
180
- $extra_info['private_sizes'][] = $size;
181
  }
182
- }
183
 
184
- if ( $as3cf->private_prefix_enabled() ) {
185
- $extra_info['private_prefix'] = AS3CF_Utils::trailingslash_prefix( $as3cf->get_setting( 'signed-urls-object-prefix', '' ) );
 
 
 
 
186
  }
187
 
188
  return new self(
189
- $provider,
190
- $region,
191
- $bucket,
192
- $path,
193
- $is_private,
194
- $attachment_id,
195
  $source_path,
196
  $original_filename,
197
  $extra_info,
198
  null,
199
- $originator,
200
- $is_verified
 
201
  );
202
  }
203
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
  /**
205
  * (Re)initialize the static cache used for speeding up queries.
206
  */
@@ -214,7 +293,7 @@ class Media_Library_Item extends Item {
214
  /**
215
  * Get the item based on source id.
216
  *
217
- * @param integer $source_id
218
  *
219
  * @return bool|Media_Library_Item
220
  */
@@ -254,10 +333,10 @@ class Media_Library_Item extends Item {
254
  }
255
 
256
  if ( ! empty( $this->private_prefix() ) ) {
257
- $size = \AS3CF_Utils::get_intermediate_size_from_filename( $this->source_id(), $filename );
258
 
259
  // Private path.
260
- if ( $this->is_private_size( $size ) ) {
261
  return $this->private_prefix() . $this->normalized_path_dir() . $filename;
262
  }
263
  }
@@ -267,116 +346,36 @@ class Media_Library_Item extends Item {
267
  }
268
 
269
  /**
270
- * Get absolute file paths associated with source item.
271
- *
272
- * @param integer $id
273
  *
274
- * @return array
275
  */
276
- protected function source_paths( $id ) {
277
- $paths = array();
278
-
279
- return $paths;
280
  }
281
 
282
  /**
283
- * Getter for item's path value, optionally for a specific size
284
- *
285
- * @param null|string $size
286
  *
287
  * @return string
288
  */
289
- public function path( $size = null ) {
290
- $path = parent::path();
291
-
292
- if ( empty( $size ) ) {
293
- return $path;
294
- }
295
-
296
- $meta = get_post_meta( $this->source_id(), '_wp_attachment_metadata', true );
297
- if ( ! empty( $meta['sizes'][ $size ]['file'] ) ) {
298
- $path = str_replace( wp_basename( $path ), $meta['sizes'][ $size ]['file'], $path );
299
- }
300
-
301
- return $path;
302
- }
303
-
304
- /**
305
- * Get the array of thumbnail sizes that are private in the bucket.
306
- *
307
- * @return array
308
- */
309
- public function private_sizes() {
310
- $extra_info = $this->extra_info();
311
-
312
- if ( ! empty( $extra_info['private_sizes'] ) ) {
313
- // There was an issue with class re-hydration that meant empty private sizes embedded itself inside its key.
314
- if (
315
- isset( $extra_info['private_sizes']['private_sizes'] ) &&
316
- is_array( $extra_info['private_sizes']['private_sizes'] ) &&
317
- empty( $extra_info['private_sizes']['private_sizes'] )
318
- ) {
319
- unset( $extra_info['private_sizes']['private_sizes'] );
320
- }
321
-
322
- return $extra_info['private_sizes'];
323
- }
324
-
325
- return array();
326
  }
327
 
328
  /**
329
- * Set the private status for a specific size.
330
  *
331
- * @param $size
332
- * @param $private
333
- */
334
- public function set_private_size( $size, $private ) {
335
- if ( empty( $size ) || AS3CF_Utils::is_full_size( $size ) ) {
336
- return;
337
- }
338
-
339
- $extra_info = $this->extra_info();
340
- $private_sizes = $this->private_sizes();
341
- if ( $private && ! in_array( $size, $private_sizes, true ) ) {
342
- $private_sizes[] = $size;
343
- }
344
- if ( ! $private && in_array( $size, $private_sizes, true ) ) {
345
- $private_sizes = array_diff( $private_sizes, array( $size ) );
346
- }
347
- $extra_info['private_sizes'] = $private_sizes;
348
-
349
- $this->set_extra_info( $extra_info );
350
- }
351
-
352
- /**
353
- * Get the private status for a specific size.
354
- *
355
- * @param string $size
356
- *
357
- * @return bool
358
- */
359
- public function is_private_size( $size ) {
360
- if ( AS3CF_Utils::is_full_size( $size ) ) {
361
- return $this->is_private();
362
- }
363
-
364
- return in_array( $size, $this->private_sizes() );
365
- }
366
-
367
- /**
368
- * Get the private prefix for attachment's private objects.
369
  *
370
- * @return string
371
  */
372
- public function private_prefix() {
373
- $extra_info = $this->extra_info();
374
-
375
- if ( ! empty( $extra_info['private_prefix'] ) ) {
376
- return \AS3CF_Utils::trailingslash_prefix( $extra_info['private_prefix'] );
377
- }
378
 
379
- return '';
380
  }
381
 
382
  /**
@@ -390,7 +389,7 @@ class Media_Library_Item extends Item {
390
  * offloaded: Count of offloaded media for site (current blog id)
391
  * not_offloaded: Difference between total and offloaded
392
  */
393
- public static function count_attachments( $skip_transient = false, $force = false ) {
394
  global $wpdb;
395
 
396
  $transient_key = 'as3cf_' . get_current_blog_id() . '_attachment_counts';
@@ -402,19 +401,17 @@ class Media_Library_Item extends Item {
402
  }
403
 
404
  if ( $force || $skip_transient || false === ( $result = get_site_transient( $transient_key ) ) ) {
405
- // We want to count distinct relative Media Library paths
406
- // and ensure type is also attachment as other post types can use the same _wp_attached_file postmeta key.
407
- $sql = "
408
- SELECT COUNT(DISTINCT p.`ID`) total, COUNT(DISTINCT i.`id`) offloaded
409
- FROM " . $wpdb->posts . " AS p
410
- STRAIGHT_JOIN " . $wpdb->postmeta . " AS m ON p.ID = m.post_id AND m.`meta_key` = '_wp_attached_file'
411
- LEFT OUTER JOIN " . static::items_table() . " AS i ON p.`ID` = i.`source_id` AND i.`source_type` = 'media-library'
412
- WHERE p.`post_type` = 'attachment'
413
- ";
414
 
415
- $result = $wpdb->get_row( $sql, ARRAY_A );
 
 
416
 
417
- $result['not_offloaded'] = max( $result['total'] - $result['offloaded'], 0 );
 
 
418
 
419
  ksort( $result );
420
 
@@ -434,9 +431,9 @@ class Media_Library_Item extends Item {
434
  *
435
  * While source id isn't strictly unique, it is by source type, which is always used in queries based on called class.
436
  *
437
- * @param integer $upper_bound Returned source_ids should be lower than this, use null/0 for no upper bound.
438
- * @param integer $limit Maximum number of source_ids to return. Required if not counting.
439
- * @param bool $count Just return a count of matching source_ids? Negates $limit, default false.
440
  *
441
  * @return array|int
442
  */
@@ -486,74 +483,12 @@ class Media_Library_Item extends Item {
486
  $sql = $wpdb->prepare( $sql, $args );
487
 
488
  if ( $count ) {
489
- return $wpdb->get_var( $sql );
490
  } else {
491
  return array_map( 'intval', $wpdb->get_col( $sql ) );
492
  }
493
  }
494
 
495
- /**
496
- * Search for all items that have the source path(s).
497
- *
498
- * @param array|string $paths Array of relative source paths.
499
- * @param array|int $exclude_source_ids Array of source_ids to exclude from search. Default, none.
500
- * @param bool $exact_match Use paths as supplied (true, default), or greedy match on path without extension (e.g. find edited too).
501
- * @param bool $first_only Only return first matched item sorted by source_id. Default false.
502
- *
503
- * @return array
504
- */
505
- public static function get_by_source_path( $paths, $exclude_source_ids = array(), $exact_match = true, $first_only = false ) {
506
- global $wpdb;
507
-
508
- if ( ! is_array( $paths ) && is_string( $paths ) && ! empty( $paths ) ) {
509
- $paths = array( $paths );
510
- }
511
-
512
- if ( ! is_array( $paths ) || empty( $paths ) ) {
513
- return array();
514
- }
515
-
516
- $paths = \AS3CF_Utils::make_upload_file_paths_relative( $paths );
517
-
518
- $args = array( static::$source_type );
519
-
520
- $sql = '
521
- SELECT DISTINCT items.*
522
- FROM ' . static::items_table() . ' AS items USE INDEX (uidx_source_path, uidx_original_source_path)
523
- WHERE items.source_type = %s
524
- ';
525
-
526
- if ( ! empty( $exclude_source_ids ) ) {
527
- if ( ! is_array( $exclude_source_ids ) ) {
528
- $exclude_source_ids = array( $exclude_source_ids );
529
- }
530
-
531
- $sql .= ' AND items.source_id NOT IN (' . join( ',', $exclude_source_ids ) . ')';
532
- }
533
-
534
- if ( $exact_match ) {
535
- $sql .= " AND (items.source_path IN ('" . join( "','", $paths ) . "')";
536
- $sql .= " OR items.original_source_path IN ('" . join( "','", $paths ) . "'))";
537
- } else {
538
- $likes = array_map( function ( $path ) {
539
- $ext = '.' . pathinfo( $path, PATHINFO_EXTENSION );
540
- $path = substr_replace( $path, '%', -strlen( $ext ) );
541
-
542
- return "items.source_path LIKE '" . $path . "' OR items.original_source_path LIKE '" . $path . "'";
543
- }, $paths );
544
-
545
- $sql .= ' AND (' . join( ' OR ', $likes ) . ')';
546
- }
547
-
548
- if ( $first_only ) {
549
- $sql .= ' ORDER BY items.source_id LIMIT 1';
550
- }
551
-
552
- $sql = $wpdb->prepare( $sql, $args );
553
-
554
- return array_map( 'static::create', $wpdb->get_results( $sql ) );
555
- }
556
-
557
  /**
558
  * Finds Media Library items with same source_path and sets them as offloaded.
559
  */
@@ -601,9 +536,183 @@ class Media_Library_Item extends Item {
601
  $this->extra_info()
602
  );
603
  $as3cf_item->save();
 
604
  }
605
  }
606
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
607
  /*
608
  * >>> LEGACY ROUTINES BEGIN >>>
609
  */
@@ -611,8 +720,8 @@ class Media_Library_Item extends Item {
611
  /**
612
  * Convert the provider info array for an attachment to item object.
613
  *
614
- * @param integer $source_id
615
- * @param array $provider_info
616
  *
617
  * @return bool|Media_Library_Item
618
  */
@@ -631,7 +740,7 @@ class Media_Library_Item extends Item {
631
  $provider_info['region'],
632
  $provider_info['bucket'],
633
  $provider_info['key'],
634
- isset( $provider_info['acl'] ) && false !== strpos( $provider_info['acl'], 'private' ) ? true : false,
635
  $source_id,
636
  $attached_file,
637
  wp_basename( $attached_file ),
3
  namespace DeliciousBrains\WP_Offload_Media\Items;
4
 
5
  use Amazon_S3_And_CloudFront;
 
6
  use AS3CF_Utils;
7
+ use WP_Error;
8
 
9
  class Media_Library_Item extends Item {
10
+ /**
11
+ * Source type name
12
+ *
13
+ * @var string
14
+ */
15
+ protected static $source_type_name = 'Media Library Item';
16
+
17
+ /**
18
+ * Internal source type identifier
19
+ *
20
+ * @var string
21
+ */
22
+ protected static $source_type = 'media-library';
23
+
24
+ /**
25
+ * Table that corresponds to this item type
26
+ *
27
+ * @var string
28
+ */
29
+ protected static $source_table = 'posts';
30
+
31
+ /**
32
+ * Foreign key (if any) in the $source_table
33
+ *
34
+ * @var string
35
+ */
36
+ protected static $source_fk = 'id';
37
+
38
  private static $attachment_counts = array();
39
  private static $attachment_count_skips = array();
40
 
41
  /**
42
  * Item constructor.
43
  *
44
+ * @param string $provider Storage provider key name, e.g. "aws".
45
+ * @param string $region Region for item's bucket.
46
+ * @param string $bucket Bucket for item.
47
+ * @param string $path Key path for item (full sized if type has thumbnails etc).
48
+ * @param bool $is_private Is the object private in the bucket.
49
+ * @param int $source_id ID that source has.
50
+ * @param string $source_path Path that source uses, could be relative or absolute depending on source.
51
+ * @param string $original_filename An optional filename with no path that was previously used for the item.
52
+ * @param array $extra_info An optional associative array of extra data to be associated with the item.
53
+ * Recognised keys:
54
+ * 'objects' => array of ...
55
+ * -- 'thumbnail' => array of ...
56
+ * -- -- 'source_file' => 'image-150x150.png'
57
+ * -- -- 'is_private' => false
58
+ * 'private_prefix' => 'private/'
59
+ * For backwards compatibility, if a simple array is supplied it is treated as
60
+ * private thumbnail sizes that should be private objects in the bucket.
61
+ * @param int $id Optional Item record ID.
62
+ * @param int $originator Optional originator of record from ORIGINATORS const.
63
+ * @param bool $is_verified Optional flag as to whether Item's objects are known to exist.
64
+ * @param bool $use_object_versioning Optional flag as to whether path prefix should use Object Versioning if type allows it.
65
  */
66
  public function __construct(
67
  $provider,
75
  $extra_info = array(),
76
  $id = null,
77
  $originator = 0,
78
+ $is_verified = true,
79
+ $use_object_versioning = self::CAN_USE_OBJECT_VERSIONING
80
  ) {
81
  // For Media Library items, the source path should be relative to the Media Library's uploads directory.
82
  $uploads = wp_upload_dir();
85
  $source_path = AS3CF_Utils::unleadingslashit( substr( $source_path, strlen( $uploads['basedir'] ) ) );
86
  }
87
 
88
+ $objects = array();
89
+ $private_prefix = null;
90
 
91
  // Ensure re-hydration is clean.
92
  if ( ! empty( $extra_info ) && is_array( $extra_info ) ) {
 
 
 
93
  if ( isset( $extra_info['private_prefix'] ) ) {
94
  $private_prefix = $extra_info['private_prefix'];
95
  }
96
+ if ( isset( $extra_info['objects'] ) ) {
97
+ $objects = $extra_info['objects'];
 
 
98
  }
99
  }
100
 
101
  $extra_info = array(
102
+ 'objects' => $objects,
103
  'private_prefix' => $private_prefix,
104
  );
105
 
106
+ parent::__construct( $provider, $region, $bucket, $path, $is_private, $source_id, $source_path, $original_filename, $extra_info, $id, $originator, $is_verified, $use_object_versioning );
107
  }
108
 
109
  /**
110
+ * Synthesize a data struct to be used when passing information
111
+ * about the current item to filters that assume the item is a
112
+ * media library item.
 
 
113
  *
114
+ * @return array
115
  */
116
+ public function item_data_for_acl_filter() {
117
+ $item_data = parent::item_data_for_acl_filter();
118
+ $media_library_item_data = wp_get_attachment_metadata( $this->source_id(), true );
119
+
120
+ // Copy over specific elements only as i.e. 'size' may not be populated yet in $media_library_item_data
121
+ foreach ( array( 'file', 'original_image', 'image_meta' ) as $element ) {
122
+ if ( isset( $media_library_item_data[ $element ] ) ) {
123
+ $item_data[ $element ] = $media_library_item_data[ $element ];
124
+ }
125
+ }
126
 
127
+ return $item_data;
128
+ }
129
+
130
+ /**
131
+ * Create a new item from the source id.
132
+ *
133
+ * @param int $source_id
134
+ * @param array $options
135
+ *
136
+ * @return Item|WP_Error
137
+ */
138
+ public static function create_from_source_id( $source_id, $options = array() ) {
139
+ if ( empty( $source_id ) ) {
140
  return new WP_Error(
141
  'exception',
142
  __( 'Empty Attachment ID passed to ' . __FUNCTION__, 'amazon-s3-and-cloudfront' )
143
  );
144
  }
145
 
146
+ $default_options = array(
147
+ 'originator' => Item::ORIGINATORS['standard'],
148
+ 'is_verified' => true,
149
+ 'use_object_versioning' => static::can_use_object_versioning(),
150
+ );
151
+
152
+ $options = array_merge( $default_options, $options );
153
 
154
+ if ( ! in_array( $options['originator'], self::ORIGINATORS ) ) {
155
  return new WP_Error(
156
  'exception',
157
  __( 'Invalid Originator passed to ' . __FUNCTION__, 'amazon-s3-and-cloudfront' )
158
  );
159
  }
160
 
 
 
 
161
  /*
162
+ * Derive local path.
 
 
 
 
 
 
 
 
 
 
 
163
  */
164
 
165
  // Verify that get_attached_file will not blow up as it does not check the data it manipulates.
166
+ $attached_file_meta = get_post_meta( $source_id, '_wp_attached_file', true );
167
  if ( ! is_string( $attached_file_meta ) ) {
168
  return new WP_Error(
169
  'exception',
170
+ sprintf( __( 'Media Library item with ID %d has damaged meta data', 'amazon-s3-and-cloudfront' ), $source_id )
171
  );
172
  }
173
  unset( $attached_file_meta );
174
 
175
+ $source_path = get_attached_file( $source_id, true );
176
 
177
  // Check for valid "full" file path otherwise we'll not be able to create offload path or download in the future.
178
  if ( empty( $source_path ) ) {
179
  return new WP_Error(
180
  'exception',
181
+ sprintf( __( 'Media Library item with ID %d does not have a valid file path', 'amazon-s3-and-cloudfront' ), $source_id )
182
  );
183
  }
184
 
185
+ /** @var array|false|WP_Error $attachment_metadata */
186
+ $attachment_metadata = wp_get_attachment_metadata( $source_id, true );
187
  if ( is_wp_error( $attachment_metadata ) ) {
188
  return $attachment_metadata;
189
  }
190
 
191
+ // Initialize extra info array with empty values
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  $extra_info = array(
193
+ 'private_prefix' => null,
194
+ 'objects' => array(),
195
  );
196
 
197
+ // There may be an original image that can override the default original filename.
198
+ $original_filename = empty( $attachment_metadata['original_image'] ) ? null : $attachment_metadata['original_image'];
199
 
200
+ $file_paths = AS3CF_Utils::get_attachment_file_paths( $source_id, false, $attachment_metadata );
201
  foreach ( $file_paths as $size => $size_file_path ) {
202
+ if ( $size === 'file' ) {
203
+ continue;
 
 
204
  }
 
205
 
206
+ $new_object = array(
207
+ 'source_file' => wp_basename( $size_file_path ),
208
+ 'is_private' => false,
209
+ );
210
+
211
+ $extra_info['objects'][ $size ] = $new_object;
212
  }
213
 
214
  return new self(
215
+ '',
216
+ '',
217
+ '',
218
+ '',
219
+ false,
220
+ $source_id,
221
  $source_path,
222
  $original_filename,
223
  $extra_info,
224
  null,
225
+ $options['originator'],
226
+ $options['is_verified'],
227
+ $options['use_object_versioning']
228
  );
229
  }
230
 
231
+ /**
232
+ * Get attachment local URL.
233
+ *
234
+ * This is partly a direct copy of wp_get_attachment_url() from /wp-includes/post.php
235
+ * as we filter the URL in AS3CF and can't remove this filter using the current implementation
236
+ * of globals for class instances.
237
+ *
238
+ * @param string|null $object_key
239
+ *
240
+ * @return string|false
241
+ */
242
+ public function get_local_url( $object_key = null ) {
243
+ /** @var Amazon_S3_And_CloudFront $as3cf */
244
+ global $as3cf;
245
+ $url = '';
246
+
247
+ // Get attached file.
248
+ if ( $file = get_post_meta( $this->source_id(), '_wp_attached_file', true ) ) {
249
+ // Get upload directory.
250
+ if ( ( $uploads = wp_upload_dir() ) && false === $uploads['error'] ) {
251
+ // Check that the upload base exists in the file location.
252
+ if ( 0 === strpos( $file, $uploads['basedir'] ) ) {
253
+ // Replace file location with url location.
254
+ $url = str_replace( $uploads['basedir'], $uploads['baseurl'], $file );
255
+ } elseif ( false !== strpos( $file, 'wp-content/uploads' ) ) {
256
+ $url = $uploads['baseurl'] . substr( $file, strpos( $file, 'wp-content/uploads' ) + 18 );
257
+ } else {
258
+ // It's a newly-uploaded file, therefore $file is relative to the basedir.
259
+ $url = $uploads['baseurl'] . "/$file";
260
+ }
261
+ }
262
+ }
263
+
264
+ if ( empty( $url ) ) {
265
+ return false;
266
+ }
267
+
268
+ $url = $as3cf->maybe_fix_local_subsite_url( $url );
269
+
270
+ if ( ! empty( $object_key ) ) {
271
+ $meta = get_post_meta( $this->source_id(), '_wp_attachment_metadata', true );
272
+ if ( empty( $meta['sizes'][ $object_key ]['file'] ) ) {
273
+ // No alternative sizes available, return
274
+ return $url;
275
+ }
276
+
277
+ $url = str_replace( wp_basename( $url ), $meta['sizes'][ $object_key ]['file'], $url );
278
+ }
279
+
280
+ return $url;
281
+ }
282
+
283
  /**
284
  * (Re)initialize the static cache used for speeding up queries.
285
  */
293
  /**
294
  * Get the item based on source id.
295
  *
296
+ * @param int $source_id
297
  *
298
  * @return bool|Media_Library_Item
299
  */
333
  }
334
 
335
  if ( ! empty( $this->private_prefix() ) ) {
336
+ $size = $this->get_object_key_from_filename( $filename );
337
 
338
  // Private path.
339
+ if ( $this->is_private( $size ) ) {
340
  return $this->private_prefix() . $this->normalized_path_dir() . $filename;
341
  }
342
  }
346
  }
347
 
348
  /**
349
+ * Get absolute source file paths for offloaded files.
 
 
350
  *
351
+ * @return array Associative array of object_key => path
352
  */
353
+ public function full_source_paths() {
354
+ return array_intersect_key( AS3CF_Utils::get_attachment_file_paths( $this->source_id(), false ), $this->objects() );
 
 
355
  }
356
 
357
  /**
358
+ * Get size name from file name
 
 
359
  *
360
  * @return string
361
  */
362
+ public function get_object_key_from_filename( $filename ) {
363
+ return AS3CF_Utils::get_intermediate_size_from_filename( $this->source_id(), basename( $filename ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
364
  }
365
 
366
  /**
367
+ * Get ACL for intermediate size.
368
  *
369
+ * @param string $object_key Size name
370
+ * @param string|null $bucket Optional bucket that ACL is potentially to be used with.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
371
  *
372
+ * @return string|null
373
  */
374
+ public function get_acl_for_object_key( $object_key, $bucket = null ) {
375
+ /** @var Amazon_S3_And_CloudFront $as3cf */
376
+ global $as3cf;
 
 
 
377
 
378
+ return $as3cf->get_acl_for_intermediate_size( $this->source_id(), $object_key, $bucket, $this );
379
  }
380
 
381
  /**
389
  * offloaded: Count of offloaded media for site (current blog id)
390
  * not_offloaded: Difference between total and offloaded
391
  */
392
+ public static function count_items( $skip_transient = false, $force = false ) {
393
  global $wpdb;
394
 
395
  $transient_key = 'as3cf_' . get_current_blog_id() . '_attachment_counts';
401
  }
402
 
403
  if ( $force || $skip_transient || false === ( $result = get_site_transient( $transient_key ) ) ) {
404
+ // Simplified media counting
405
+ $sql = "SELECT count(id) FROM {$wpdb->posts} WHERE post_type = 'attachment'";
406
+ $attachment_count = (int) $wpdb->get_var( $sql );
 
 
 
 
 
 
407
 
408
+ $sql = 'SELECT count(id) FROM ' . static::items_table() . ' WHERE source_type = %s';
409
+ $sql = $wpdb->prepare( $sql, static::$source_type );
410
+ $offloaded_count = (int) $wpdb->get_var( $sql );
411
 
412
+ $result['total'] = $attachment_count;
413
+ $result['offloaded'] = $offloaded_count;
414
+ $result['not_offloaded'] = max( $attachment_count - $offloaded_count, 0 );
415
 
416
  ksort( $result );
417
 
431
  *
432
  * While source id isn't strictly unique, it is by source type, which is always used in queries based on called class.
433
  *
434
+ * @param int $upper_bound Returned source_ids should be lower than this, use null/0 for no upper bound.
435
+ * @param int $limit Maximum number of source_ids to return. Required if not counting.
436
+ * @param bool $count Just return a count of matching source_ids? Negates $limit, default false.
437
  *
438
  * @return array|int
439
  */
483
  $sql = $wpdb->prepare( $sql, $args );
484
 
485
  if ( $count ) {
486
+ return (int) $wpdb->get_var( $sql );
487
  } else {
488
  return array_map( 'intval', $wpdb->get_col( $sql ) );
489
  }
490
  }
491
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
492
  /**
493
  * Finds Media Library items with same source_path and sets them as offloaded.
494
  */
536
  $this->extra_info()
537
  );
538
  $as3cf_item->save();
539
+ $as3cf_item->duplicate_filesize_total( $this->source_id() );
540
  }
541
  }
542
 
543
+ /**
544
+ * Returns a link to the items edit page in WordPress
545
+ *
546
+ * @param object $error
547
+ *
548
+ * @return object|null Object containing url and link text
549
+ */
550
+ public static function admin_link( $error ) {
551
+ return (object) array(
552
+ 'url' => get_edit_post_link( $error->source_id ),
553
+ 'text' => __( 'Edit', 'amazon-s3-and-cloudfront' ),
554
+ );
555
+ }
556
+
557
+ /**
558
+ * Return a year/month string for the item
559
+ *
560
+ * @return string
561
+ */
562
+ protected function get_item_time() {
563
+ return $this->get_attachment_folder_year_month();
564
+ }
565
+
566
+ /**
567
+ * Get the year/month string for attachment's upload.
568
+ *
569
+ * Fall back to post date if attached, otherwise current date.
570
+ *
571
+ * @param array $data
572
+ *
573
+ * @return string
574
+ */
575
+ private function get_attachment_folder_year_month( $data = array() ) {
576
+ if ( empty( $data ) ) {
577
+ $data = wp_get_attachment_metadata( $this->source_id(), true );
578
+ }
579
+
580
+ if ( isset( $data['file'] ) ) {
581
+ $time = $this->get_folder_time_from_url( $data['file'] );
582
+ }
583
+
584
+ if ( empty( $time ) && ( $local_url = wp_get_attachment_url( $this->source_id() ) ) ) {
585
+ $time = $this->get_folder_time_from_url( $local_url );
586
+ }
587
+
588
+ if ( empty( $time ) ) {
589
+ $time = date( 'Y/m' );
590
+
591
+ if ( ! ( $attach = get_post( $this->source_id() ) ) ) {
592
+ return $time;
593
+ }
594
+
595
+ if ( ! $attach->post_parent ) {
596
+ return $time;
597
+ }
598
+
599
+ if ( ! ( $post = get_post( $attach->post_parent ) ) ) {
600
+ return $time;
601
+ }
602
+
603
+ if ( substr( $post->post_date_gmt, 0, 4 ) > 0 ) {
604
+ return date( 'Y/m', strtotime( $post->post_date_gmt . ' +0000' ) );
605
+ }
606
+ }
607
+
608
+ return $time;
609
+ }
610
+
611
+ /**
612
+ * Get the upload folder time from given URL
613
+ *
614
+ * @param string $url
615
+ *
616
+ * @return null|string YYYY/MM format.
617
+ */
618
+ private function get_folder_time_from_url( $url ) {
619
+ if ( ! is_string( $url ) ) {
620
+ return null;
621
+ }
622
+
623
+ preg_match( '@[0-9]{4}/[0-9]{2}/@', $url, $matches );
624
+
625
+ if ( isset( $matches[0] ) ) {
626
+ return untrailingslashit( $matches[0] );
627
+ }
628
+
629
+ return null;
630
+ }
631
+
632
+ /**
633
+ * Update filesize and as3cf_filesize_total metadata on the underlying media library item
634
+ * after removing the local file.
635
+ *
636
+ * @param int $original_size
637
+ * @param int $total_size
638
+ */
639
+ public function update_filesize_after_remove_local( $original_size, $total_size ) {
640
+ update_post_meta( $this->source_id(), 'as3cf_filesize_total', $total_size );
641
+
642
+ if ( 0 < $original_size && ( $data = get_post_meta( $this->source_id(), '_wp_attachment_metadata', true ) ) ) {
643
+ if ( empty( $data['filesize'] ) ) {
644
+ $data['filesize'] = $original_size;
645
+
646
+ // Update metadata with filesize
647
+ update_post_meta( $this->source_id(), '_wp_attachment_metadata', $data );
648
+ }
649
+ }
650
+ }
651
+
652
+ /**
653
+ * Cleanup filesize and as3cf_filesize_total metadata on the underlying media library item
654
+ * after downloading a file back from the bucket
655
+ */
656
+ public function update_filesize_after_download_local() {
657
+ $data = get_post_meta( $this->source_id(), '_wp_attachment_metadata', true );
658
+
659
+ /*
660
+ * Audio and video have a filesize added to metadata by default, but images and anything else don't.
661
+ * Note: Could have used `wp_generate_attachment_metadata` here to test whether default metadata has 'filesize',
662
+ * but it not only has side effects it also does a lot of work considering it's not a huge deal for this entry to hang around.
663
+ */
664
+ if (
665
+ ! empty( $data ) &&
666
+ ( empty( $data['mime_type'] ) ||
667
+ 0 === strpos( $data['mime_type'], 'image/' ) ||
668
+ ! ( 0 === strpos( $data['mime_type'], 'audio/' ) || 0 === strpos( $data['mime_type'], 'video/' ) ) )
669
+ ) {
670
+ unset( $data['filesize'] );
671
+ update_post_meta( $this->source_id(), '_wp_attachment_metadata', $data );
672
+ }
673
+
674
+ delete_post_meta( $this->source_id(), 'as3cf_filesize_total' );
675
+ }
676
+
677
+ /**
678
+ * Duplicate 'as3cf_filesize_total' meta if it exists for an attachment.
679
+ *
680
+ * @param int $attachment_id
681
+ */
682
+ public function duplicate_filesize_total( $attachment_id ) {
683
+ if ( ! ( $filesize = get_post_meta( $attachment_id, 'as3cf_filesize_total', true ) ) ) {
684
+ // No filesize to duplicate.
685
+ return;
686
+ }
687
+
688
+ update_post_meta( $this->source_id(), 'as3cf_filesize_total', $filesize );
689
+ }
690
+
691
+ /**
692
+ * If another item in current site shares full size *local* paths, only remove remote files not referenced by duplicates.
693
+ * We reference local paths as they should be reflected one way or another remotely, including backups.
694
+ *
695
+ * @params Item $as3cf_item
696
+ * @params array $paths
697
+ */
698
+ public function remove_duplicate_paths( Item $as3cf_item, $paths ) {
699
+ $full_size_paths = AS3CF_Utils::fullsize_paths( $as3cf_item->full_source_paths() );
700
+ $as3cf_items_with_paths = static::get_by_source_path( $full_size_paths, array( $as3cf_item->source_id() ), false );
701
+
702
+ $duplicate_paths = array();
703
+
704
+ foreach ( $as3cf_items_with_paths as $as3cf_item_with_path ) {
705
+ /* @var Media_Library_Item $as3cf_item_with_path */
706
+ $duplicate_paths += array_values( AS3CF_Utils::get_attachment_file_paths( $as3cf_item_with_path->source_id(), false, false, true ) );
707
+ }
708
+
709
+ if ( ! empty( $duplicate_paths ) ) {
710
+ $paths = array_diff( $paths, $duplicate_paths );
711
+ }
712
+
713
+ return $paths;
714
+ }
715
+
716
  /*
717
  * >>> LEGACY ROUTINES BEGIN >>>
718
  */
720
  /**
721
  * Convert the provider info array for an attachment to item object.
722
  *
723
+ * @param int $source_id
724
+ * @param array $provider_info
725
  *
726
  * @return bool|Media_Library_Item
727
  */
740
  $provider_info['region'],
741
  $provider_info['bucket'],
742
  $provider_info['key'],
743
+ isset( $provider_info['acl'] ) && false !== strpos( $provider_info['acl'], 'private' ),
744
  $source_id,
745
  $attached_file,
746
  wp_basename( $attached_file ),
classes/items/remove-local-handler.php ADDED
@@ -0,0 +1,209 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace DeliciousBrains\WP_Offload_Media\Items;
4
+
5
+ use AS3CF_Error;
6
+ use WP_Error;
7
+
8
+ class Remove_Local_Handler extends Item_Handler {
9
+ /**
10
+ * @var string
11
+ */
12
+ protected static $item_handler_key = 'remove-local';
13
+
14
+ /**
15
+ * Keep track of individual files we've already attempted to remove.
16
+ *
17
+ * @var array
18
+ */
19
+ private $remove_blocked = array();
20
+
21
+ /**
22
+ * Keep track of size of individual files we've already attempted to remove.
23
+ *
24
+ * @var array
25
+ */
26
+ private $removed_size = array();
27
+
28
+ /**
29
+ * If remove the primary file, we want to update the 'filesize'.
30
+ *
31
+ * @var int
32
+ */
33
+ private $removed_primary_size = array();
34
+
35
+ /**
36
+ * The default options that should be used if none supplied.
37
+ *
38
+ * @return array
39
+ */
40
+ public static function default_options() {
41
+ return array(
42
+ 'verify_exists_on_provider' => false,
43
+ 'provider_keys' => array(),
44
+ 'files_to_remove' => array(),
45
+ );
46
+ }
47
+
48
+ /**
49
+ * Create manifest for local removal.
50
+ *
51
+ * @param Item $as3cf_item
52
+ * @param array $options
53
+ *
54
+ * @return Manifest|WP_Error
55
+ */
56
+ protected function pre_handle( Item $as3cf_item, array $options ) {
57
+ $manifest = new Manifest();
58
+ $source_id = $as3cf_item->source_id();
59
+ $primary_file = '';
60
+ $files_to_remove = array();
61
+
62
+ // Note: Unable to use Item::full_size_paths() here
63
+ // as source item's metadata may not be up-to-date yet.
64
+ foreach ( $as3cf_item->objects() as $object_key => $object ) {
65
+ $file = $as3cf_item->full_source_path( $object_key );
66
+
67
+ if ( in_array( $file, $this->remove_blocked ) ) {
68
+ continue;
69
+ }
70
+
71
+ if ( 0 < count( $options['files_to_remove'] ) && ! in_array( $file, $options['files_to_remove'] ) ) {
72
+ continue;
73
+ }
74
+
75
+ // If needed, make sure this item exists among the provider keys.
76
+ if ( true === $options['verify_exists_on_provider'] ) {
77
+ if ( empty( $options['provider_keys'][ $source_id ] ) ) {
78
+ continue;
79
+ }
80
+
81
+ if ( ! in_array( $as3cf_item->provider_key( $object_key ), $options['provider_keys'][ $source_id ] ) ) {
82
+ continue;
83
+ }
84
+ }
85
+
86
+ if ( file_exists( $file ) ) {
87
+ $files_to_remove[] = $file;
88
+
89
+ if ( Item::primary_object_key() === $object_key ) {
90
+ $primary_file = $file;
91
+ }
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Filters array of local files before being removed from server.
97
+ *
98
+ * @param array $files_to_remove Array of paths to be removed
99
+ * @param Item $as3cf_item The Item object
100
+ * @param array $item_source Item source descriptor array
101
+ */
102
+ $filtered_files_to_remove = apply_filters( 'as3cf_remove_local_files', $files_to_remove, $as3cf_item, $as3cf_item->get_item_source_array() );
103
+
104
+ // Ensure fileset is unique and does not contain files already blocked.
105
+ $filtered_files_to_remove = array_unique( array_diff( $filtered_files_to_remove, $this->remove_blocked ) );
106
+
107
+ // If filter removes files from list, block attempts to remove them in later calls.
108
+ $this->remove_blocked = array_merge( $this->remove_blocked, array_diff( $files_to_remove, $filtered_files_to_remove ) );
109
+
110
+ foreach ( $filtered_files_to_remove as $file ) {
111
+ // Filter may have added some files to check for existence.
112
+ if ( ! in_array( $file, $files_to_remove ) ) {
113
+ if ( ! file_exists( $file ) ) {
114
+ continue;
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Filter individual files that might still be kept local.
120
+ *
121
+ * @param bool $preserve Should the file be kept on the server?
122
+ * @param string $file Full path to the local file
123
+ */
124
+ if ( false !== apply_filters( 'as3cf_preserve_file_from_local_removal', false, $file ) ) {
125
+ $this->remove_blocked[] = $file;
126
+ continue;
127
+ }
128
+
129
+ $manifest->objects[] = array(
130
+ 'file' => $file,
131
+ 'size' => filesize( $file ),
132
+ 'is_primary' => $file === $primary_file,
133
+ );
134
+ }
135
+
136
+ return $manifest;
137
+ }
138
+
139
+ /**
140
+ * Delete local files described in the manifest object array.
141
+ *
142
+ * @param Item $as3cf_item
143
+ * @param Manifest $manifest
144
+ * @param array $options
145
+ *
146
+ * @return bool|WP_Error
147
+ */
148
+ protected function handle_item( Item $as3cf_item, Manifest $manifest, array $options ) {
149
+ foreach ( $manifest->objects as &$file_to_remove ) {
150
+ $file = $file_to_remove['file'];
151
+
152
+ $file_to_remove['remove_result'] = array( 'status' => self::STATUS_OK );
153
+
154
+ if ( ! @unlink( $file ) ) {
155
+ $this->remove_blocked[] = $file;
156
+
157
+ $file_to_remove['remove_result']['status'] = self::STATUS_FAILED;
158
+ $file_to_remove['remove_result']['message'] = "Error removing local file at $file";
159
+
160
+ if ( ! file_exists( $file ) ) {
161
+ $file_to_remove['remove_result']['message'] = "Error removing local file. Couldn't find the file at $file";
162
+ } else if ( ! is_writable( $file ) ) {
163
+ $file_to_remove['remove_result']['message'] = "Error removing local file. Ownership or permissions are mis-configured for $file";
164
+ }
165
+ }
166
+ }
167
+
168
+ return true;
169
+ }
170
+
171
+ /**
172
+ * Perform post handle tasks.
173
+ *
174
+ * @param Item $as3cf_item
175
+ * @param Manifest $manifest
176
+ * @param array $options
177
+ *
178
+ * @return bool|WP_Error
179
+ */
180
+ protected function post_handle( Item $as3cf_item, Manifest $manifest, array $options ) {
181
+ if ( empty( $manifest->objects ) ) {
182
+ return true;
183
+ }
184
+
185
+ // Assume we didn't touch the primary file.
186
+ $this->removed_primary_size[ $as3cf_item->source_id() ] = 0;
187
+
188
+ foreach ( $manifest->objects as $file_to_remove ) {
189
+ if ( $file_to_remove['remove_result']['status'] !== self::STATUS_OK ) {
190
+ AS3CF_Error::log( $file_to_remove['remove_result']['message'] );
191
+ continue;
192
+ }
193
+
194
+ if ( empty( $this->removed_size[ $as3cf_item->source_id() ] ) ) {
195
+ $this->removed_size[ $as3cf_item->source_id() ] = $file_to_remove['size'];
196
+ } else {
197
+ $this->removed_size[ $as3cf_item->source_id() ] += $file_to_remove['size'];
198
+ }
199
+
200
+ if ( $file_to_remove['is_primary'] ) {
201
+ $this->removed_primary_size[ $as3cf_item->source_id() ] = $file_to_remove['size'];
202
+ }
203
+ }
204
+
205
+ $as3cf_item->update_filesize_after_remove_local( $this->removed_primary_size[ $as3cf_item->source_id() ], $this->removed_size[ $as3cf_item->source_id() ] );
206
+
207
+ return true;
208
+ }
209
+ }
classes/items/remove-provider-handler.php ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace DeliciousBrains\WP_Offload_Media\Items;
4
+
5
+ use AS3CF_Error;
6
+ use Exception;
7
+ use WP_Error;
8
+
9
+ class Remove_Provider_Handler extends Item_Handler {
10
+ /**
11
+ * @var string
12
+ */
13
+ protected static $item_handler_key = 'remove-provider';
14
+
15
+ /**
16
+ * The default options that should be used if none supplied.
17
+ *
18
+ * @return array
19
+ */
20
+ public static function default_options() {
21
+ return array(
22
+ 'object_keys' => array(),
23
+ 'offloaded_files' => array(),
24
+ );
25
+ }
26
+
27
+ /**
28
+ * Create manifest for removal from provider.
29
+ *
30
+ * @param Item $as3cf_item
31
+ * @param array $options
32
+ *
33
+ * @return Manifest|WP_Error
34
+ */
35
+ protected function pre_handle( Item $as3cf_item, array $options ) {
36
+ $manifest = new Manifest();
37
+ $paths = array();
38
+
39
+ if ( ! empty( $options['object_keys'] ) && ! is_array( $options['object_keys'] ) ) {
40
+ return new WP_Error( 'remove-error', __( 'Invalid object_keys option provided.', 'amazon-s3-and-cloudfront' ) );
41
+ }
42
+
43
+ if ( ! empty( $options['offloaded_files'] ) && ! is_array( $options['offloaded_files'] ) ) {
44
+ return new WP_Error( 'remove-error', __( 'Invalid offloaded_files option provided.', 'amazon-s3-and-cloudfront' ) );
45
+ }
46
+
47
+ if ( ! empty( $options['object_keys'] ) && ! empty( $options['offloaded_files'] ) ) {
48
+ return new WP_Error( 'remove-error', __( 'Providing both object_keys and offloaded_files options is not supported.', 'amazon-s3-and-cloudfront' ) );
49
+ }
50
+
51
+ if ( empty( $options['offloaded_files'] ) ) {
52
+ foreach ( $as3cf_item->objects() as $object_key => $object ) {
53
+ if ( 0 < count( $options['object_keys'] ) && ! in_array( $object_key, $options['object_keys'] ) ) {
54
+ continue;
55
+ }
56
+ $paths[ $object_key ] = $as3cf_item->full_source_path( $object_key );
57
+ }
58
+ } else {
59
+ foreach ( $options['offloaded_files'] as $filename => $object ) {
60
+ $paths[ $filename ] = $as3cf_item->full_source_path_for_filename( $filename );
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Filters array of source files before being removed from provider.
66
+ *
67
+ * @param array $paths Array of local paths to be removed from provider
68
+ * @param Item $as3cf_item The Item object
69
+ * @param array $item_source The item source descriptor array
70
+ */
71
+ $paths = apply_filters( 'as3cf_remove_source_files_from_provider', $paths, $as3cf_item, $as3cf_item->get_item_source_array() );
72
+ $paths = array_unique( $paths );
73
+
74
+ // Remove local source paths that other items may have offloaded.
75
+ $paths = $as3cf_item->remove_duplicate_paths( $as3cf_item, $paths );
76
+
77
+ // Nothing to do, shortcut out.
78
+ if ( empty( $paths ) ) {
79
+ return $manifest;
80
+ }
81
+
82
+ if ( empty( $options['offloaded_files'] ) ) {
83
+ foreach ( $paths as $object_key => $path ) {
84
+ $manifest->objects[] = array(
85
+ 'Key' => $as3cf_item->provider_key( $object_key ),
86
+ );
87
+ }
88
+ } else {
89
+ foreach ( $paths as $filename => $path ) {
90
+ $manifest->objects[] = array(
91
+ 'Key' => $as3cf_item->provider_key_for_filename( $filename, $options['offloaded_files'][ $filename ]['is_private'] ),
92
+ );
93
+ }
94
+ }
95
+
96
+ return $manifest;
97
+ }
98
+
99
+ /**
100
+ * Delete provider objects described in the manifest object array
101
+ *
102
+ * @param Item $as3cf_item
103
+ * @param Manifest $manifest
104
+ * @param array $options
105
+ *
106
+ * @return bool|WP_Error
107
+ */
108
+ protected function handle_item( Item $as3cf_item, Manifest $manifest, array $options ) {
109
+ $chunks = array_chunk( $manifest->objects, 1000 );
110
+ $region = $as3cf_item->region();
111
+ $bucket = $as3cf_item->bucket();
112
+
113
+ try {
114
+ foreach ( $chunks as $chunk ) {
115
+ $this->as3cf->get_provider_client( $region )->delete_objects( array(
116
+ 'Bucket' => $bucket,
117
+ 'Objects' => $chunk,
118
+ ) );
119
+ }
120
+ } catch ( Exception $e ) {
121
+ AS3CF_Error::log( 'Error removing files from bucket: ' . $e->getMessage() );
122
+
123
+ return new WP_Error( 'remove-error', $e->getMessage() );
124
+ }
125
+
126
+ return true;
127
+ }
128
+
129
+ /**
130
+ * Perform post handle tasks.
131
+ *
132
+ * @param Item $as3cf_item
133
+ * @param Manifest $manifest
134
+ * @param array $options
135
+ *
136
+ * @return bool|WP_Error
137
+ */
138
+ protected function post_handle( Item $as3cf_item, Manifest $manifest, array $options ) {
139
+ return true;
140
+ }
141
+ }
classes/items/upload-handler.php ADDED
@@ -0,0 +1,449 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace DeliciousBrains\WP_Offload_Media\Items;
4
+
5
+ use AS3CF_Error;
6
+ use AS3CF_Utils;
7
+ use Exception;
8
+ use WP_Error;
9
+
10
+ class Upload_Handler extends Item_Handler {
11
+ /**
12
+ * @var string
13
+ */
14
+ protected static $item_handler_key = 'upload';
15
+
16
+ /**
17
+ * Keep track of individual files we've already attempted to upload
18
+ *
19
+ * @var array
20
+ */
21
+ protected $attempted_upload = array();
22
+
23
+ /**
24
+ * The default options that should be used if none supplied.
25
+ *
26
+ * @return array
27
+ */
28
+ public static function default_options() {
29
+ return array(
30
+ 'offloaded_files' => array(),
31
+ );
32
+ }
33
+
34
+ /**
35
+ * Prepare item for uploading by running filters, updating
36
+ *
37
+ * @param Item $as3cf_item
38
+ * @param array $options
39
+ *
40
+ * @return Manifest|WP_Error
41
+ */
42
+ protected function pre_handle( Item $as3cf_item, array $options ) {
43
+ $manifest = new Manifest();
44
+ $source_type_name = $this->as3cf->get_source_type_name( $as3cf_item->source_type() );
45
+ $primary_key = Item::primary_object_key();
46
+
47
+ // Check for valid file path before attempting upload
48
+ if ( empty( $as3cf_item->source_path() ) ) {
49
+ $error_msg = sprintf( __( '%s with id %d does not have a valid file path', 'amazon-s3-and-cloudfront' ), $source_type_name, $as3cf_item->source_id() );
50
+
51
+ return $this->return_handler_error( $error_msg );
52
+ }
53
+
54
+ // Ensure path is a string
55
+ if ( ! is_string( $as3cf_item->source_path() ) ) {
56
+ $error_msg = sprintf( __( '%s with id %d. Provided path is not a string', 'amazon-s3-and-cloudfront' ), $source_type_name, $as3cf_item->source_id() );
57
+
58
+ return $this->return_handler_error( $error_msg );
59
+ }
60
+
61
+ // Ensure primary source file exists for new offload.
62
+ if ( empty( $as3cf_item->id() ) && ! file_exists( $as3cf_item->full_source_path( $primary_key ) ) ) {
63
+ $error_msg = sprintf( __( 'Primary file %s does not exist', 'amazon-s3-and-cloudfront' ), $as3cf_item->full_source_path( $primary_key ) );
64
+
65
+ return $this->return_handler_error( $error_msg );
66
+ }
67
+
68
+ // Get primary file's stats.
69
+ $file_name = wp_basename( $as3cf_item->source_path() );
70
+ $file_type = wp_check_filetype_and_ext( $as3cf_item->source_path(), $file_name );
71
+ $allowed_types = $this->as3cf->get_allowed_mime_types();
72
+
73
+ // check mime type of file is in allowed provider mime types
74
+ if ( ! in_array( $file_type['type'], $allowed_types, true ) ) {
75
+ $error_msg = sprintf( __( 'Mime type %s is not allowed', 'amazon-s3-and-cloudfront' ), $file_type['type'] );
76
+
77
+ return $this->return_handler_error( $error_msg );
78
+ }
79
+
80
+ $default_acl = $this->as3cf->get_storage_provider()->get_default_acl();
81
+ $private_acl = $this->as3cf->get_storage_provider()->get_private_acl();
82
+
83
+ foreach ( $as3cf_item->objects() as $object_key => $object ) {
84
+ // Avoid attempting uploading to an item that doesn't have the primary file in place.
85
+ if ( $primary_key !== $object_key && empty( $as3cf_item->id() ) && ! isset( $manifest->objects[ $primary_key ] ) ) {
86
+ continue;
87
+ }
88
+
89
+ $source_path = $as3cf_item->full_source_path( $object_key );
90
+
91
+ // If the file has already been offloaded,
92
+ // don't try and (fail to) re-offload if the file isn't available.
93
+ if ( $this->in_offloaded_files( $object['source_file'], $options ) && ! file_exists( $source_path ) ) {
94
+ continue;
95
+ }
96
+
97
+ /**
98
+ * This filter allows you to change the public/private status of an individual file associated
99
+ * with an uploaded item before it's uploaded to the provider.
100
+ *
101
+ * @param bool $is_private Should the object be private?
102
+ * @param string $object_key A unique file identifier for a composite item, e.g. image's "size" such as full, small, medium, large.
103
+ * @param Item $as3cf_item The item being uploaded.
104
+ *
105
+ * @return bool
106
+ */
107
+ $is_private = apply_filters( 'as3cf_upload_object_key_as_private', $as3cf_item->is_private( $object_key ), $object_key, $as3cf_item );
108
+ $as3cf_item->set_is_private( $is_private, $object_key );
109
+
110
+ $object_acl = $as3cf_item->is_private( $object_key ) ? $private_acl : $default_acl;
111
+
112
+ $args = array(
113
+ 'Bucket' => $as3cf_item->bucket(),
114
+ 'Key' => $as3cf_item->path( $object_key ),
115
+ 'SourceFile' => $source_path,
116
+ 'ContentType' => AS3CF_Utils::get_mime_type( $object['source_file'] ),
117
+ 'CacheControl' => 'max-age=31536000',
118
+ );
119
+
120
+ // Only set ACL if actually required, some storage provider and bucket settings disable changing ACL.
121
+ if ( ! empty( $object_acl ) && $this->as3cf->use_acl_for_intermediate_size( 0, $object_key, $as3cf_item->bucket(), $as3cf_item ) ) {
122
+ $args['ACL'] = $object_acl;
123
+ }
124
+
125
+ // TODO: Remove GZIP functionality.
126
+ // Handle gzip on supported items
127
+ if (
128
+ $this->should_gzip_file( $source_path, $as3cf_item->source_type() ) &&
129
+ false !== ( $gzip_body = gzencode( file_get_contents( $source_path ) ) )
130
+ ) {
131
+ unset( $args['SourceFile'] );
132
+
133
+ $args['Body'] = $gzip_body;
134
+ $args['ContentEncoding'] = 'gzip';
135
+ }
136
+
137
+ /**
138
+ * This filter allows you to change the arguments passed to the cloud storage SDK client when
139
+ * offloading a file to the bucket.
140
+ *
141
+ * Note: It is possible to change the destination 'Bucket' only while processing the primary object_key.
142
+ * All other object_keys will use the same bucket as the item's primary object.
143
+ * The 'Key' should be the "public" Key path. If a private prefix is configured
144
+ * for use with signed CloudFront URLs or similar, that prefix will be added later.
145
+ * A change to the 'Key' will only be handled when processing the primary object key.
146
+ *
147
+ * @param array $args Information to be sent to storage provider during offload (e.g. PutObject)
148
+ * @param int $source_id Original file's unique ID for its source type
149
+ * @param string $object_key A unique file identifier for a composite item, e.g. image's "size" such as full, small, medium, large
150
+ * @param bool $copy True if the object is being copied between buckets
151
+ * @param array $item_source Item source array containing source type and id
152
+ *
153
+ * @return array
154
+ */
155
+ $args = apply_filters( 'as3cf_object_meta', $args, $as3cf_item->source_id(), $object_key, false, $as3cf_item->get_item_source_array() );
156
+
157
+ // If the bucket is changed by the filter while processing the primary object,
158
+ // we should try and use that bucket for the item.
159
+ // If the bucket name is invalid, revert to configured bucket but log it.
160
+ // We don't abort here as ephemeral filesystems need to be accounted for,
161
+ // and the configured bucket is at least known to work.
162
+ if ( $primary_key === $object_key && $as3cf_item->bucket() !== $args['Bucket'] && empty( $as3cf_item->id() ) ) {
163
+ $bucket = $this->as3cf->check_bucket( $args['Bucket'] );
164
+
165
+ if ( $bucket ) {
166
+ $region = $this->as3cf->get_bucket_region( $bucket, true );
167
+
168
+ if ( is_wp_error( $region ) ) {
169
+ unset( $region );
170
+ }
171
+ }
172
+
173
+ if ( empty( $bucket ) || empty( $region ) ) {
174
+ $mesg = sprintf(
175
+ __( 'Bucket name "%1$s" is invalid, using "%2$s" instead.', 'amazon-s3-and-cloudfront' ),
176
+ $args['Bucket'],
177
+ $as3cf_item->bucket()
178
+ );
179
+ AS3CF_Error::log( $mesg );
180
+ $args['Bucket'] = $as3cf_item->bucket();
181
+ } else {
182
+ $args['Bucket'] = $bucket;
183
+ $as3cf_item->set_bucket( $bucket );
184
+ $as3cf_item->set_region( $region );
185
+ }
186
+
187
+ unset( $bucket, $region );
188
+ } elseif ( $primary_key === $object_key && $as3cf_item->bucket() !== $args['Bucket'] && ! empty( $as3cf_item->id() ) ) {
189
+ $args['Bucket'] = $as3cf_item->bucket();
190
+ AS3CF_Error::log( __( 'The bucket may not be changed via filters for a previously offloaded item.', 'amazon-s3-and-cloudfront' ) );
191
+ } elseif ( $primary_key !== $object_key && $as3cf_item->bucket() !== $args['Bucket'] ) {
192
+ $args['Bucket'] = $as3cf_item->bucket();
193
+ }
194
+
195
+ // If the Key has been changed for the primary object key, then that should be reflected in the item.
196
+ if ( $primary_key === $object_key && $as3cf_item->path( $object_key ) !== $args['Key'] && empty( $as3cf_item->id() ) ) {
197
+ $prefix = AS3CF_Utils::trailingslash_prefix( dirname( $args['Key'] ) );
198
+
199
+ if ( $prefix === '.' ) {
200
+ $prefix = '';
201
+ }
202
+
203
+ $as3cf_item->update_path_prefix( $prefix );
204
+
205
+ // If the filter tried to use a different filename too, log it.
206
+ if ( wp_basename( $args['Key'] ) !== wp_basename( $as3cf_item->path( $object_key ) ) ) {
207
+ $mesg = sprintf(
208
+ __( 'The offloaded filename must not be changed, "%1$s" has been used instead of "%2$s".', 'amazon-s3-and-cloudfront' ),
209
+ wp_basename( $as3cf_item->path( $object_key ) ),
210
+ wp_basename( $args['Key'] )
211
+ );
212
+ AS3CF_Error::log( $mesg );
213
+ }
214
+ } elseif ( $primary_key === $object_key && $as3cf_item->path( $object_key ) !== $args['Key'] && ! empty( $as3cf_item->id() ) ) {
215
+ $args['Key'] = $as3cf_item->path( $object_key );
216
+ AS3CF_Error::log( __( 'The key may not be changed via filters for a previously offloaded item.', 'amazon-s3-and-cloudfront' ) );
217
+ } elseif ( $primary_key !== $object_key && $as3cf_item->path( $object_key ) !== $args['Key'] ) {
218
+ $args['Key'] = $as3cf_item->path( $object_key );
219
+ }
220
+
221
+ // If ACL has been set, does the object's is_private need updating?
222
+ $is_private = ! empty( $args['ACL'] ) && $private_acl === $args['ACL'] || $as3cf_item->is_private( $object_key );
223
+ $as3cf_item->set_is_private( $is_private, $object_key );
224
+
225
+ // Protect against filter use and only set ACL if actually required, some storage provider and bucket settings disable changing ACL.
226
+ if ( isset( $args['ACL'] ) && ! $this->as3cf->use_acl_for_intermediate_size( 0, $object_key, $as3cf_item->bucket(), $as3cf_item ) ) {
227
+ unset( $args['ACL'] );
228
+ }
229
+
230
+ // Adjust the actual Key to add the private prefix before uploading.
231
+ if ( $as3cf_item->is_private( $object_key ) ) {
232
+ $args['Key'] = $as3cf_item->provider_key( $object_key );
233
+ }
234
+
235
+ // If we've already attempted to offload this source file, leave it out of the manifest.
236
+ if ( in_array( md5( serialize( $args ) ), $this->attempted_upload ) ) {
237
+ continue;
238
+ }
239
+
240
+ if ( $primary_key === $object_key ) {
241
+ /**
242
+ * Actions fires when an Item's primary file might be offloaded.
243
+ *
244
+ * This action gives notice that an Item is being processed for upload to a bucket,
245
+ * and the given arguments represent the primary file's potential offload location.
246
+ * However, if the current process is for picking up extra files associated with the item,
247
+ * the indicated primary file may not actually be offloaded if it does not exist
248
+ * on the server but has already been offloaded.
249
+ *
250
+ * @param Item $as3cf_item The Item whose files are being offloaded.
251
+ * @param array $args The arguments that could be used to offload the primary file.
252
+ */
253
+ do_action( 'as3cf_pre_upload_object', $as3cf_item, $args );
254
+ }
255
+
256
+ $manifest->objects[ $object_key ]['args'] = $args;
257
+ }
258
+
259
+ return $manifest;
260
+ }
261
+
262
+ /**
263
+ * Upload item files to remote storage provider
264
+ *
265
+ * @param Item $as3cf_item
266
+ * @param Manifest $manifest
267
+ * @param array $options
268
+ *
269
+ * @return bool|WP_Error
270
+ */
271
+ protected function handle_item( Item $as3cf_item, Manifest $manifest, array $options ) {
272
+ try {
273
+ $provider_client = $this->as3cf->get_provider_client( $as3cf_item->region() );
274
+ } catch ( Exception $e ) {
275
+ return $this->return_handler_error( $e->getMessage() );
276
+ }
277
+
278
+ foreach ( $manifest->objects as $object_key => &$object ) {
279
+ $args = $object['args'];
280
+
281
+ $object['upload_result'] = array(
282
+ 'status' => null,
283
+ 'message' => null,
284
+ );
285
+
286
+ if ( ! file_exists( $args['SourceFile'] ) ) {
287
+ $error_msg = sprintf( __( 'File %s does not exist', 'amazon-s3-and-cloudfront' ), $args['SourceFile'] );
288
+
289
+ $object['upload_result']['status'] = self::STATUS_FAILED;
290
+ $object['upload_result']['message'] = $error_msg;
291
+
292
+ // If the missing source file is the primary file, abort the whole process.
293
+ if ( Item::primary_object_key() === $object_key ) {
294
+ return false;
295
+ }
296
+
297
+ continue;
298
+ }
299
+
300
+ $this->attempted_upload[] = md5( serialize( $args ) );
301
+
302
+ // Try to do the upload
303
+ try {
304
+ $provider_client->upload_object( $args );
305
+
306
+ $object['upload_result']['status'] = self::STATUS_OK;
307
+ } catch ( Exception $e ) {
308
+ $error_msg = sprintf( __( 'Error offloading %1$s to provider: %2$s', 'amazon-s3-and-cloudfront' ), $args['SourceFile'], $e->getMessage() );
309
+
310
+ $object['upload_result']['status'] = self::STATUS_FAILED;
311
+ $object['upload_result']['message'] = $error_msg;
312
+ }
313
+ }
314
+
315
+ return true;
316
+ }
317
+
318
+ /**
319
+ * Handle local housekeeping after uploads.
320
+ *
321
+ * @param Item $as3cf_item
322
+ * @param Manifest $manifest
323
+ * @param array $options
324
+ *
325
+ * @return bool|WP_Error
326
+ */
327
+ protected function post_handle( Item $as3cf_item, Manifest $manifest, array $options ) {
328
+ $item_objects = $as3cf_item->objects();
329
+ $errors = new WP_Error;
330
+ $i = 1;
331
+
332
+ // Reconcile the Item's objects with their manifest status.
333
+ foreach ( $item_objects as $object_key => $object ) {
334
+ // If there was no attempt made to offload the file,
335
+ // then remove it from list of offloaded objects.
336
+ // However, if the source file has previously been offloaded,
337
+ // we should just skip any further processing of it
338
+ // as the associated objects are still offloaded.
339
+ if ( ! isset( $manifest->objects[ $object_key ]['upload_result']['status'] ) ) {
340
+ if ( empty( $options['offloaded_files'][ $object['source_file'] ] ) ) {
341
+ unset( $item_objects[ $object_key ] );
342
+ }
343
+ continue;
344
+ }
345
+
346
+ // If the upload didn't succeed, we need to remove the object/size from the item.
347
+ // However, if the source file has previously been offloaded, we should just log the error.
348
+ if ( $manifest->objects[ $object_key ]['upload_result']['status'] !== self::STATUS_OK ) {
349
+ if ( empty( $options['offloaded_files'][ $object['source_file'] ] ) ) {
350
+ unset( $item_objects[ $object_key ] );
351
+ }
352
+ $errors->add( 'upload-object-' . $i++, $manifest->objects[ $object_key ]['upload_result']['message'] );
353
+ }
354
+ }
355
+
356
+ // Set the potentially changed list of offloaded objects.
357
+ $as3cf_item->set_objects( $item_objects );
358
+
359
+ // Only save if we have the primary file uploaded.
360
+ if ( isset( $item_objects[ Item::primary_object_key() ] ) ) {
361
+ $as3cf_item->save();
362
+ }
363
+
364
+ /**
365
+ * Fires action after uploading finishes
366
+ *
367
+ * @param Item $as3cf_item The item that was just uploaded
368
+ */
369
+ do_action( 'as3cf_post_upload_item', $as3cf_item );
370
+
371
+ if ( count( $errors->get_error_codes() ) ) {
372
+ return $errors;
373
+ }
374
+
375
+ return true;
376
+ }
377
+
378
+ /**
379
+ * Should gzip file
380
+ *
381
+ * @param string $file_path
382
+ * @param string $source_type
383
+ *
384
+ * @return bool
385
+ */
386
+ protected function should_gzip_file( $file_path, $source_type ) {
387
+ $file_type = wp_check_filetype_and_ext( $file_path, $file_path );
388
+ $mimes = $this->get_mime_types_to_gzip( $source_type );
389
+
390
+ if ( in_array( $file_type, $mimes ) && is_readable( $file_path ) ) {
391
+ return true;
392
+ }
393
+
394
+ return false;
395
+ }
396
+
397
+ /**
398
+ * Get mime types to gzip
399
+ *
400
+ * @param string $source_type
401
+ *
402
+ * @return array
403
+ */
404
+ protected function get_mime_types_to_gzip( $source_type ) {
405
+ /**
406
+ * Return array of mime types that needs to be gzipped before upload
407
+ *
408
+ * @param array $mime_types The array of mime types
409
+ * @param bool $media_library If the uploaded file is part of the media library
410
+ * @param string $source_type The source type of the uploaded item
411
+ */
412
+ return apply_filters(
413
+ 'as3cf_gzip_mime_types',
414
+ array(
415
+ 'css' => 'text/css',
416
+ 'eot' => 'application/vnd.ms-fontobject',
417
+ 'html' => 'text/html',
418
+ 'ico' => 'image/x-icon',
419
+ 'js' => 'application/javascript',
420
+ 'json' => 'application/json',
421
+ 'otf' => 'application/x-font-opentype',
422
+ 'rss' => 'application/rss+xml',
423
+ 'svg' => 'image/svg+xml',
424
+ 'ttf' => 'application/x-font-ttf',
425
+ 'woff' => 'application/font-woff',
426
+ 'woff2' => 'application/font-woff2',
427
+ 'xml' => 'application/xml',
428
+ ),
429
+ 'media_library' === $source_type,
430
+ $source_type
431
+ );
432
+ }
433
+
434
+ /**
435
+ * Has the given file name already been offloaded?
436
+ *
437
+ * @param string $filename
438
+ * @param array $options
439
+ *
440
+ * @return bool
441
+ */
442
+ private function in_offloaded_files( $filename, $options ) {
443
+ if ( empty( $options['offloaded_files'] ) ) {
444
+ return false;
445
+ }
446
+
447
+ return array_key_exists( $filename, $options['offloaded_files'] );
448
+ }
449
+ }
classes/providers/storage/storage-provider.php CHANGED
@@ -572,11 +572,12 @@ abstract class Storage_Provider extends Provider {
572
  /**
573
  * Get object keys from multiple clients.
574
  *
575
- * @param array $regions
 
576
  *
577
  * @return array
578
  */
579
- public static function get_keys_from_regions( array $regions ) {
580
  $keys = array();
581
 
582
  foreach ( $regions as $region ) {
@@ -591,7 +592,7 @@ abstract class Storage_Provider extends Provider {
591
 
592
  if ( ! empty( $region_keys ) ) {
593
  foreach ( $region_keys as $attachment_id => $found_keys ) {
594
- $keys[ $attachment_id ] = AS3CF_Utils::validate_attachment_keys( $attachment_id, $found_keys );
595
  }
596
  }
597
  }
572
  /**
573
  * Get object keys from multiple clients.
574
  *
575
+ * @param array $regions
576
+ * @param string $source_type
577
  *
578
  * @return array
579
  */
580
+ public static function get_keys_from_regions( array $regions, $source_type ) {
581
  $keys = array();
582
 
583
  foreach ( $regions as $region ) {
592
 
593
  if ( ! empty( $region_keys ) ) {
594
  foreach ( $region_keys as $attachment_id => $found_keys ) {
595
+ $keys[ $attachment_id ] = AS3CF_Utils::validate_attachment_keys( $attachment_id, $found_keys, $source_type );
596
  }
597
  }
598
  }
classes/upgrades/upgrade-edd-replace-urls.php CHANGED
@@ -2,6 +2,8 @@
2
 
3
  namespace DeliciousBrains\WP_Offload_Media\Upgrades;
4
 
 
 
5
  /**
6
  * Upgrade_EDD_Replace_URLs Class
7
  *
@@ -82,7 +84,12 @@ class Upgrade_EDD_Replace_URLs extends Upgrade {
82
  continue;
83
  }
84
 
85
- if ( $url = $this->as3cf->get_attachment_local_url( $attachment['attachment_id'] ) ) {
 
 
 
 
 
86
  $attachments[ $key ]['file'] = $url;
87
  }
88
  }
2
 
3
  namespace DeliciousBrains\WP_Offload_Media\Upgrades;
4
 
5
+ use DeliciousBrains\WP_Offload_Media\Items\Media_Library_Item;
6
+
7
  /**
8
  * Upgrade_EDD_Replace_URLs Class
9
  *
84
  continue;
85
  }
86
 
87
+ $as3cf_item = Media_Library_Item::get_by_source_id( $attachment['attachment_id'] );
88
+ if ( empty( $as3cf_item ) ) {
89
+ continue;
90
+ }
91
+
92
+ if ( $url = $as3cf_item->get_local_url() ) {
93
  $attachments[ $key ]['file'] = $url;
94
  }
95
  }
classes/upgrades/upgrade-file-sizes.php CHANGED
@@ -52,24 +52,25 @@ class Upgrade_File_Sizes extends Upgrade {
52
  /**
53
  * Get the total file sizes for an attachment and associated files.
54
  *
55
- * @param mixed $attachment
56
  *
57
  * @return bool
 
58
  */
59
- protected function upgrade_item( $attachment ) {
60
- $provider_object = unserialize( $attachment->provider_object );
61
  if ( false === $provider_object ) {
62
- AS3CF_Error::log( 'Failed to unserialize offload meta for attachment ' . $attachment->ID . ': ' . $attachment->provider_object );
63
  $this->error_count++;
64
 
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;
@@ -96,7 +97,7 @@ class Upgrade_File_Sizes extends Upgrade {
96
  // List objects for the attachment
97
  $result = $provider_client->list_objects( $args );
98
  } catch ( Exception $e ) {
99
- AS3CF_Error::log( 'Error listing objects of prefix ' . $search_prefix . ' for attachment ' . $attachment->ID . ' in bucket: ' . $e->getMessage() );
100
  $this->error_count++;
101
 
102
  return false;
@@ -124,19 +125,19 @@ class Upgrade_File_Sizes extends Upgrade {
124
  }
125
 
126
  if ( 0 === $file_size_total ) {
127
- AS3CF_Error::log( 'Total file size for the attachment is 0: ' . $attachment->ID );
128
  $this->error_count++;
129
 
130
  return false;
131
  }
132
 
133
  // Update the main file size for the attachment
134
- $meta = get_post_meta( $attachment->ID, '_wp_attachment_metadata', true );
135
  $meta['filesize'] = $main_file_size;
136
- update_post_meta( $attachment->ID, '_wp_attachment_metadata', $meta );
137
 
138
  // Add the total file size for all image sizes
139
- update_post_meta( $attachment->ID, 'wpos3_filesize_total', $file_size_total );
140
 
141
  return true;
142
  }
52
  /**
53
  * Get the total file sizes for an attachment and associated files.
54
  *
55
+ * @param mixed $item
56
  *
57
  * @return bool
58
+ * @throws Exception
59
  */
60
+ protected function upgrade_item( $item ) {
61
+ $provider_object = unserialize( $item->provider_object );
62
  if ( false === $provider_object ) {
63
+ AS3CF_Error::log( 'Failed to unserialize offload meta for attachment ' . $item->ID . ': ' . $item->provider_object );
64
  $this->error_count++;
65
 
66
  return false;
67
  }
68
 
69
  // Using Media_Library_Item::get_by_source_id falls back to legacy metadata and substitutes in defaults and potentially missing values.
70
+ $as3cf_item = Media_Library_Item::get_by_source_id( $item->ID );
71
 
72
  if ( ! $as3cf_item ) {
73
+ AS3CF_Error::log( 'Could not construct item for attachment with ID ' . $item->ID . ' from legacy offload metadata.' );
74
  $this->error_count++;
75
 
76
  return false;
97
  // List objects for the attachment
98
  $result = $provider_client->list_objects( $args );
99
  } catch ( Exception $e ) {
100
+ AS3CF_Error::log( 'Error listing objects of prefix ' . $search_prefix . ' for attachment ' . $item->ID . ' in bucket: ' . $e->getMessage() );
101
  $this->error_count++;
102
 
103
  return false;
125
  }
126
 
127
  if ( 0 === $file_size_total ) {
128
+ AS3CF_Error::log( 'Total file size for the attachment is 0: ' . $item->ID );
129
  $this->error_count++;
130
 
131
  return false;
132
  }
133
 
134
  // Update the main file size for the attachment
135
+ $meta = get_post_meta( $item->ID, '_wp_attachment_metadata', true );
136
  $meta['filesize'] = $main_file_size;
137
+ update_post_meta( $item->ID, '_wp_attachment_metadata', $meta );
138
 
139
  // Add the total file size for all image sizes
140
+ update_post_meta( $item->ID, 'wpos3_filesize_total', $file_size_total );
141
 
142
  return true;
143
  }
classes/upgrades/upgrade-filter-post.php CHANGED
@@ -3,6 +3,7 @@
3
  namespace DeliciousBrains\WP_Offload_Media\Upgrades;
4
 
5
  use AS3CF_Utils;
 
6
  use DeliciousBrains\WP_Offload_Media\Upgrades\Exceptions\Batch_Limits_Exceeded_Exception;
7
  use DeliciousBrains\WP_Offload_Media\Upgrades\Exceptions\Too_Many_Errors_Exception;
8
 
@@ -110,37 +111,34 @@ abstract class Upgrade_Filter_Post extends Upgrade {
110
  }
111
 
112
  /**
113
- * Upgrade attachment.
114
  *
115
- * @param mixed $attachment
116
  *
117
  * @return bool
118
  * @throws Batch_Limits_Exceeded_Exception
119
  * @throws Too_Many_Errors_Exception
120
  *
121
  */
122
- protected function upgrade_item( $attachment ) {
123
  $limit = apply_filters( 'as3cf_update_' . $this->upgrade_name . '_sql_limit', 100000 );
124
  $where_highest_id = $this->last_post_id;
125
  $where_lowest_id = max( $where_highest_id - $limit, 0 );
126
 
127
  while ( true ) {
128
- $this->find_and_replace_attachment_urls( $attachment->ID, $where_lowest_id, $where_highest_id );
129
 
130
  if ( $where_lowest_id <= 0 ) {
131
  // Batch completed
132
  return true;
133
  }
134
 
135
- $where_highest_id = $where_lowest_id;
136
- $where_lowest_id = max( $where_lowest_id - $limit, 0 );
 
137
 
138
  $this->check_batch_limits();
139
  }
140
-
141
- $this->last_post_id = $where_lowest_id;
142
-
143
- return false;
144
  }
145
 
146
  /**
@@ -161,12 +159,17 @@ abstract class Upgrade_Filter_Post extends Upgrade {
161
  * @param int $where_highest_id
162
  */
163
  protected function find_and_replace_attachment_urls( $attachment_id, $where_lowest_id, $where_highest_id ) {
164
- $meta = wp_get_attachment_metadata( $attachment_id, true );
165
- $backups = get_post_meta( $attachment_id, '_wp_attachment_backup_sizes', true );
166
- $file_path = get_attached_file( $attachment_id, true );
 
 
 
 
 
167
 
168
- $new_url = $this->as3cf->get_attachment_local_url( $attachment_id );
169
- $old_url = $this->as3cf->maybe_remove_query_string( $this->as3cf->get_attachment_url( $attachment_id, null, null, $meta, array(), true ) );
170
 
171
  if ( empty( $old_url ) || empty( $new_url ) ) {
172
  return;
3
  namespace DeliciousBrains\WP_Offload_Media\Upgrades;
4
 
5
  use AS3CF_Utils;
6
+ use DeliciousBrains\WP_Offload_Media\Items\Item;
7
  use DeliciousBrains\WP_Offload_Media\Upgrades\Exceptions\Batch_Limits_Exceeded_Exception;
8
  use DeliciousBrains\WP_Offload_Media\Upgrades\Exceptions\Too_Many_Errors_Exception;
9
 
111
  }
112
 
113
  /**
114
+ * Upgrade item.
115
  *
116
+ * @param mixed $item
117
  *
118
  * @return bool
119
  * @throws Batch_Limits_Exceeded_Exception
120
  * @throws Too_Many_Errors_Exception
121
  *
122
  */
123
+ protected function upgrade_item( $item ) {
124
  $limit = apply_filters( 'as3cf_update_' . $this->upgrade_name . '_sql_limit', 100000 );
125
  $where_highest_id = $this->last_post_id;
126
  $where_lowest_id = max( $where_highest_id - $limit, 0 );
127
 
128
  while ( true ) {
129
+ $this->find_and_replace_attachment_urls( $item->ID, $where_lowest_id, $where_highest_id );
130
 
131
  if ( $where_lowest_id <= 0 ) {
132
  // Batch completed
133
  return true;
134
  }
135
 
136
+ $where_highest_id = $where_lowest_id;
137
+ $where_lowest_id = max( $where_lowest_id - $limit, 0 );
138
+ $this->last_post_id = $where_lowest_id;
139
 
140
  $this->check_batch_limits();
141
  }
 
 
 
 
142
  }
143
 
144
  /**
159
  * @param int $where_highest_id
160
  */
161
  protected function find_and_replace_attachment_urls( $attachment_id, $where_lowest_id, $where_highest_id ) {
162
+ $meta = wp_get_attachment_metadata( $attachment_id, true );
163
+ $backups = get_post_meta( $attachment_id, '_wp_attachment_backup_sizes', true );
164
+ $file_path = get_attached_file( $attachment_id, true );
165
+ $as3cf_item = Item::get_by_source_id( $attachment_id );
166
+
167
+ if ( empty( $as3cf_item ) ) {
168
+ return;
169
+ }
170
 
171
+ $new_url = $as3cf_item->get_local_url();
172
+ $old_url = $this->as3cf->maybe_remove_query_string( $as3cf_item->get_provider_url() );
173
 
174
  if ( empty( $old_url ) || empty( $new_url ) ) {
175
  return;
classes/upgrades/upgrade-item-extra-data.php ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Upgrade extra info in custom objects table.
4
+ *
5
+ * @package amazon-s3-and-cloudfront
6
+ * @subpackage Classes/Upgrades/Upgrade_Item_Extra_Data
7
+ * @copyright Copyright (c) 2021, Delicious Brains
8
+ * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
9
+ * @since 2.6.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_Item_Extra_Data Class
20
+ *
21
+ * This class handles updating extra info in the custom objects table.
22
+ *
23
+ * @since 2.6.0
24
+ */
25
+ class Upgrade_Item_Extra_Data extends Upgrade {
26
+
27
+ /**
28
+ * @var int
29
+ */
30
+ protected $upgrade_id = 10;
31
+
32
+ /**
33
+ * @var string
34
+ */
35
+ protected $upgrade_name = 'item_extra_data';
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 metadata about offloaded items to new format.', 'amazon-s3-and-cloudfront' );
49
+ }
50
+
51
+ /**
52
+ * Update extra_info in items table
53
+ *
54
+ * @param $item
55
+ *
56
+ * @return bool
57
+ */
58
+ protected function upgrade_item( $item ) {
59
+ $as3cf_item = Media_Library_Item::get_by_source_id( $item->source_id );
60
+
61
+ if ( ! $as3cf_item ) {
62
+ AS3CF_Error::log( 'Could not construct item for attachment with ID ' . $item->source_id . '.' );
63
+ $this->error_count++;
64
+
65
+ return false;
66
+ }
67
+
68
+ $result = $as3cf_item->save();
69
+
70
+ if ( is_wp_error( $result ) ) {
71
+ AS3CF_Error::log( 'Error saving item: ' . $result->get_error_message() );
72
+ $this->error_count++;
73
+
74
+ return false;
75
+ }
76
+
77
+ return true;
78
+ }
79
+
80
+ /**
81
+ * Get a count of all items to be processed.
82
+ * for the whole site
83
+ *
84
+ * @return int
85
+ */
86
+ protected function count_items_to_process() {
87
+ return $this->count_items_with_old_extra_info( $this->blog_prefix );
88
+ }
89
+
90
+ /**
91
+ * Get all items to be processed.
92
+ *
93
+ * @param string $prefix Table prefix for blog.
94
+ * @param int $limit
95
+ * @param bool|mixed $offset
96
+ *
97
+ * @return array
98
+ */
99
+ protected function get_items_to_process( $prefix, $limit, $offset = false ) {
100
+ $attachments = $this->get_items_with_old_extra_info( $prefix, false, $limit );
101
+
102
+ return $attachments;
103
+ }
104
+
105
+ /**
106
+ * Get a count of items that have legacy extra info.
107
+ *
108
+ * @param string $prefix Table prefix for blog.
109
+ *
110
+ * @return int
111
+ */
112
+ protected function count_items_with_old_extra_info( $prefix ) {
113
+ $count = $this->get_items_with_old_extra_info( $prefix, true );
114
+
115
+ return $count;
116
+ }
117
+
118
+ /**
119
+ * Wrapper for database call to get items with legacy extra info.
120
+ *
121
+ * @param string $prefix Table prefix for blog.
122
+ * @param bool $count return count of attachments
123
+ * @param null|int $limit
124
+ *
125
+ * @return mixed
126
+ */
127
+ protected function get_items_with_old_extra_info( $prefix, $count = false, $limit = null ) {
128
+ global $wpdb;
129
+
130
+ $table = Item::ITEMS_TABLE;
131
+
132
+ $sql = "
133
+ FROM {$prefix}{$table}
134
+ WHERE extra_info NOT LIKE '%s:7:\"objects\"%' AND source_type='media-library'
135
+ ";
136
+
137
+ if ( $count ) {
138
+ $sql = 'SELECT COUNT(source_id)' . $sql;
139
+
140
+ return $wpdb->get_var( $sql );
141
+ }
142
+
143
+ $sql = 'SELECT source_id' . $sql;
144
+ $sql .= ' ORDER BY id';
145
+
146
+ if ( $limit && $limit > 0 ) {
147
+ $sql .= sprintf( ' LIMIT %d', (int) $limit );
148
+ }
149
+
150
+ return $wpdb->get_results( $sql, OBJECT );
151
+ }
152
+ }
classes/upgrades/upgrade-items-table.php CHANGED
@@ -12,7 +12,6 @@
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
  /**
@@ -51,23 +50,23 @@ class Upgrade_Items_Table extends Upgrade {
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;
@@ -77,10 +76,10 @@ class Upgrade_Items_Table extends Upgrade {
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;
@@ -96,7 +95,7 @@ class Upgrade_Items_Table extends Upgrade {
96
  }
97
 
98
  // Delete old metadata.
99
- return delete_post_meta( $attachment->ID, 'amazonS3_info' );
100
  }
101
 
102
  /**
12
  namespace DeliciousBrains\WP_Offload_Media\Upgrades;
13
 
14
  use AS3CF_Error;
 
15
  use DeliciousBrains\WP_Offload_Media\Items\Media_Library_Item;
16
 
17
  /**
50
  /**
51
  * Move an attachment's provider object data from the postmeta table to the custom as3cf_objects table.
52
  *
53
+ * @param mixed $item
54
  *
55
  * @return bool
56
  */
57
+ protected function upgrade_item( $item ) {
58
  // Make sure legacy metadata isn't broken.
59
+ $provider_object = unserialize( $item->provider_object );
60
 
61
  if ( false === $provider_object ) {
62
+ AS3CF_Error::log( 'Failed to unserialize legacy offload metadata for attachment ' . $item->ID . ': ' . $item->provider_object );
63
  $this->error_count++;
64
 
65
  return false;
66
  }
67
 
68
+ if ( empty( $item->source_path ) ) {
69
+ AS3CF_Error::log( 'Attachment with ID ' . $item->ID . ' with legacy offload metadata has no local file path.' );
70
  $this->error_count++;
71
 
72
  return false;
76
  // If we're here we already know there's legacy metadata and that there isn't a new items table record yet,
77
  // or there's legacy metadata and an existing items table record that we can just re-save without issue before deleting legacy metadata.
78
  // An existing items table entry takes precedence over legacy metadata to avoid accidental overrides from migrations, custom code or other plugins.
79
+ $as3cf_item = Media_Library_Item::get_by_source_id( $item->ID );
80
 
81
  if ( ! $as3cf_item ) {
82
+ AS3CF_Error::log( 'Could not construct item for attachment with ID ' . $item->ID . ' from legacy offload metadata.' );
83
  $this->error_count++;
84
 
85
  return false;
95
  }
96
 
97
  // Delete old metadata.
98
+ return delete_post_meta( $item->ID, 'amazonS3_info' );
99
  }
100
 
101
  /**
classes/upgrades/upgrade-meta-wp-error.php CHANGED
@@ -52,20 +52,20 @@ class Upgrade_Meta_WP_Error extends Upgrade {
52
  /**
53
  * Rebuild the attachment metadata for an attachment
54
  *
55
- * @param mixed $attachment
56
  *
57
  * @return bool
58
  */
59
- protected function upgrade_item( $attachment ) {
60
- $provider_object = unserialize( $attachment->provider_object );
61
  if ( false === $provider_object ) {
62
- AS3CF_Error::log( 'Failed to unserialize offload meta for attachment ' . $attachment->ID . ': ' . $attachment->provider_object );
63
  $this->error_count++;
64
 
65
  return false;
66
  }
67
 
68
- $file = get_attached_file( $attachment->ID, true );
69
 
70
  if ( ! file_exists( $file ) ) {
71
  // Copy back the file to the server if doesn't exist so we can successfully
@@ -85,11 +85,11 @@ class Upgrade_Meta_WP_Error extends Upgrade {
85
  }
86
 
87
  // Remove corrupted meta
88
- delete_post_meta( $attachment->ID, '_wp_attachment_metadata' );
89
 
90
  require_once ABSPATH . '/wp-admin/includes/image.php';
91
  // Generate new attachment meta
92
- wp_update_attachment_metadata( $attachment->ID, wp_generate_attachment_metadata( $attachment->ID, $file ) );
93
 
94
  return true;
95
  }
52
  /**
53
  * Rebuild the attachment metadata for an attachment
54
  *
55
+ * @param mixed $item
56
  *
57
  * @return bool
58
  */
59
+ protected function upgrade_item( $item ) {
60
+ $provider_object = unserialize( $item->provider_object );
61
  if ( false === $provider_object ) {
62
+ AS3CF_Error::log( 'Failed to unserialize offload meta for attachment ' . $item->ID . ': ' . $item->provider_object );
63
  $this->error_count++;
64
 
65
  return false;
66
  }
67
 
68
+ $file = get_attached_file( $item->ID, true );
69
 
70
  if ( ! file_exists( $file ) ) {
71
  // Copy back the file to the server if doesn't exist so we can successfully
85
  }
86
 
87
  // Remove corrupted meta
88
+ delete_post_meta( $item->ID, '_wp_attachment_metadata' );
89
 
90
  require_once ABSPATH . '/wp-admin/includes/image.php';
91
  // Generate new attachment meta
92
+ wp_update_attachment_metadata( $item->ID, wp_generate_attachment_metadata( $item->ID, $file ) );
93
 
94
  return true;
95
  }
classes/upgrades/upgrade-region-meta.php CHANGED
@@ -50,24 +50,24 @@ class Upgrade_Region_Meta extends Upgrade {
50
  /**
51
  * Get the region for the bucket where an attachment is located, update the S3 meta.
52
  *
53
- * @param mixed $attachment
54
  *
55
  * @return bool
56
  */
57
- protected function upgrade_item( $attachment ) {
58
- $provider_object = unserialize( $attachment->provider_object );
59
  if ( false === $provider_object ) {
60
- AS3CF_Error::log( 'Failed to unserialize offload meta for attachment ' . $attachment->ID . ': ' . $attachment->provider_object );
61
  $this->error_count++;
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;
@@ -76,10 +76,10 @@ class Upgrade_Region_Meta extends Upgrade {
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;
50
  /**
51
  * Get the region for the bucket where an attachment is located, update the S3 meta.
52
  *
53
+ * @param mixed $item
54
  *
55
  * @return bool
56
  */
57
+ protected function upgrade_item( $item ) {
58
+ $provider_object = unserialize( $item->provider_object );
59
  if ( false === $provider_object ) {
60
+ AS3CF_Error::log( 'Failed to unserialize offload meta for attachment ' . $item->ID . ': ' . $item->provider_object );
61
  $this->error_count++;
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( $item->ID );
68
 
69
  if ( ! $as3cf_item ) {
70
+ AS3CF_Error::log( 'Could not construct item for attachment with ID ' . $item->ID . ' from legacy offload metadata.' );
71
  $this->error_count++;
72
 
73
  return false;
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( $item->ID, 'amazonS3_info', $provider_object );
80
 
81
  if ( false === $result ) {
82
+ AS3CF_Error::log( 'Error updating region in legacy offload metadata for attachment ' . $item->ID );
83
  $this->error_count++;
84
 
85
  return false;
classes/upgrades/upgrade-tools-errors.php ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace DeliciousBrains\WP_Offload_Media\Upgrades;
4
+
5
+ use DeliciousBrains\WP_Offload_Media\Pro\Sidebar_Presenter;
6
+
7
+ /**
8
+ * Upgrade_Tools_Errors Class
9
+ *
10
+ * This class handles updating internal error info from previous tools executions
11
+ *
12
+ * @since 2.6.0
13
+ */
14
+ class Upgrade_Tools_Errors extends Upgrade {
15
+
16
+ /**
17
+ * @var int
18
+ */
19
+ protected $upgrade_id = 9;
20
+
21
+ /**
22
+ * @var string
23
+ */
24
+ protected $upgrade_name = 'tools_error';
25
+
26
+ /**
27
+ * @var string 'metadata', 'attachment'
28
+ */
29
+ protected $upgrade_type = 'metadata';
30
+
31
+ /**
32
+ * Get running update text.
33
+ *
34
+ * @return string
35
+ */
36
+ protected function get_running_update_text() {
37
+ return __( 'and reformatting internal data about previous errors from tools .', 'amazon-s3-and-cloudfront' );
38
+ }
39
+
40
+ /**
41
+ * Get an array of tool names that may have saved error info
42
+ *
43
+ * @param string $prefix
44
+ * @param int $limit
45
+ * @param bool|mixed $offset
46
+ *
47
+ * @return array
48
+ */
49
+ protected function get_items_to_process( $prefix, $limit, $offset = false ) {
50
+ global $as3cf;
51
+
52
+ if ( get_class( $as3cf ) !== 'Amazon_S3_And_CloudFront_Pro' ) {
53
+ return array();
54
+ }
55
+
56
+ $sidebar_presenter = Sidebar_Presenter::get_instance( $as3cf );
57
+ $tools = $sidebar_presenter->get_all_tools();
58
+
59
+ return array_keys( $tools );
60
+ }
61
+
62
+ /**
63
+ * Update saved errors for a tool.
64
+ *
65
+ * @param mixed $item
66
+ *
67
+ * @return bool
68
+ */
69
+ protected function upgrade_item( $item ) {
70
+ global $as3cf;
71
+
72
+ if ( empty( $item ) || ! is_string( $item ) ) {
73
+ // We really don't want to this upgrade to fail,
74
+ // broken notices can still be dismissed, so just move on.
75
+ return true;
76
+ }
77
+
78
+ $sidebar_presenter = Sidebar_Presenter::get_instance( $as3cf );
79
+ $tools = $sidebar_presenter->get_all_tools();
80
+
81
+ if ( ! empty( $tools[ $item ] ) ) {
82
+ $tool = $tools[ $item ];
83
+
84
+ $errors = $tool->get_errors();
85
+ $new_errors = array();
86
+
87
+ if ( ! empty( $errors ) ) {
88
+ foreach ( $errors as $blog_id => $blog ) {
89
+ foreach ( $blog as $attachment_id => $messages ) {
90
+ $new_errors[] = (object) array(
91
+ 'blog_id' => $blog_id,
92
+ 'source_type' => 'media-library',
93
+ 'source_id' => $attachment_id,
94
+ 'messages' => (array) $messages,
95
+ );
96
+ }
97
+ }
98
+
99
+ $tool->update_errors( $new_errors );
100
+ }
101
+ }
102
+
103
+ return true;
104
+ }
105
+ }
classes/upgrades/upgrade.php CHANGED
@@ -21,7 +21,7 @@ use WP_Error;
21
  /**
22
  * Upgrade Class
23
  *
24
- * This class handles updates to attachments and attachment meta data
25
  *
26
  * @since 0.6.2
27
  */
@@ -113,7 +113,7 @@ abstract class Upgrade {
113
  protected $items_processed;
114
 
115
  /**
116
- * @var mixed Last attachment processed.
117
  */
118
  protected $last_item;
119
 
@@ -143,8 +143,8 @@ abstract class Upgrade {
143
  protected $session;
144
 
145
  const STATUS_RUNNING = 1;
146
- const STATUS_ERROR = 2;
147
- const STATUS_PAUSED = 3;
148
 
149
  /**
150
  * Start it up
@@ -244,13 +244,13 @@ abstract class Upgrade {
244
  abstract protected function get_items_to_process( $prefix, $limit, $offset = false );
245
 
246
  /**
247
- * Upgrade attachment.
248
  *
249
- * @param mixed $attachment
250
  *
251
  * @return bool
252
  */
253
- abstract protected function upgrade_item( $attachment );
254
 
255
  /**
256
  * Get running update text.
@@ -500,6 +500,7 @@ abstract class Upgrade {
500
  * Calculate progress.
501
  *
502
  * @return bool|float
 
503
  */
504
  protected function calculate_progress() {
505
  $this->boot_session();
@@ -510,9 +511,9 @@ abstract class Upgrade {
510
  } else {
511
  // Set up any per-site state
512
  $this->switch_to_blog( get_current_blog_id() );
513
- $counts = Media_Library_Item::count_attachments();
514
 
515
- // If there are no attachments, disable progress calculation
516
  // and protect against division by zero.
517
  if ( ! $counts['total'] ) {
518
  return false;
21
  /**
22
  * Upgrade Class
23
  *
24
+ * This class handles updates to offloaded items.
25
  *
26
  * @since 0.6.2
27
  */
113
  protected $items_processed;
114
 
115
  /**
116
+ * @var mixed Last item processed.
117
  */
118
  protected $last_item;
119
 
143
  protected $session;
144
 
145
  const STATUS_RUNNING = 1;
146
+ const STATUS_ERROR = 2;
147
+ const STATUS_PAUSED = 3;
148
 
149
  /**
150
  * Start it up
244
  abstract protected function get_items_to_process( $prefix, $limit, $offset = false );
245
 
246
  /**
247
+ * Upgrade item.
248
  *
249
+ * @param mixed $item
250
  *
251
  * @return bool
252
  */
253
+ abstract protected function upgrade_item( $item );
254
 
255
  /**
256
  * Get running update text.
500
  * Calculate progress.
501
  *
502
  * @return bool|float
503
+ * @throws Batch_Limits_Exceeded_Exception
504
  */
505
  protected function calculate_progress() {
506
  $this->boot_session();
511
  } else {
512
  // Set up any per-site state
513
  $this->switch_to_blog( get_current_blog_id() );
514
+ $counts = Media_Library_Item::count_items();
515
 
516
+ // If there are no items, disable progress calculation
517
  // and protect against division by zero.
518
  if ( ! $counts['total'] ) {
519
  return false;
include/functions.php CHANGED
@@ -1,4 +1,7 @@
1
  <?php
 
 
 
2
  if ( ! function_exists( 'as3cf_get_attachment_url' ) ) {
3
  /**
4
  * Get the url of the file from provider, may be a signed expiring URL if associated file is set as private.
@@ -12,10 +15,7 @@ if ( ! function_exists( 'as3cf_get_attachment_url' ) ) {
12
  * @return string|bool|WP_Error
13
  */
14
  function as3cf_get_attachment_url( $post_id, $size = null, $skip_rewrite_check = false ) {
15
- /** @var Amazon_S3_And_CloudFront $as3cf */
16
- global $as3cf;
17
-
18
- return $as3cf->get_attachment_url( $post_id, null, $size, null, array(), $skip_rewrite_check );
19
  }
20
  }
21
  if ( ! function_exists( 'as3cf_get_secure_attachment_url' ) ) {
@@ -32,9 +32,11 @@ if ( ! function_exists( 'as3cf_get_secure_attachment_url' ) ) {
32
  * @return string|bool|WP_Error
33
  */
34
  function as3cf_get_secure_attachment_url( $post_id, $expires = 900, $size = null, $skip_rewrite_check = false ) {
35
- /** @var Amazon_S3_And_CloudFront $as3cf */
36
- global $as3cf;
 
 
37
 
38
- return $as3cf->get_secure_attachment_url( $post_id, $expires, $size, array(), $skip_rewrite_check );
39
  }
40
  }
1
  <?php
2
+
3
+ use DeliciousBrains\WP_Offload_Media\Items\Media_Library_Item;
4
+
5
  if ( ! function_exists( 'as3cf_get_attachment_url' ) ) {
6
  /**
7
  * Get the url of the file from provider, may be a signed expiring URL if associated file is set as private.
15
  * @return string|bool|WP_Error
16
  */
17
  function as3cf_get_attachment_url( $post_id, $size = null, $skip_rewrite_check = false ) {
18
+ return as3cf_get_secure_attachment_url( $post_id, null, $size, $skip_rewrite_check );
 
 
 
19
  }
20
  }
21
  if ( ! function_exists( 'as3cf_get_secure_attachment_url' ) ) {
32
  * @return string|bool|WP_Error
33
  */
34
  function as3cf_get_secure_attachment_url( $post_id, $expires = 900, $size = null, $skip_rewrite_check = false ) {
35
+ $as3cf_item = Media_Library_Item::get_by_source_id( $post_id );
36
+ if ( ! empty( $as3cf_item ) && ! is_wp_error( $as3cf_item ) && $as3cf_item->served_by_provider( $skip_rewrite_check ) ) {
37
+ return $as3cf_item->get_provider_url( $size, $expires );
38
+ }
39
 
40
+ return false;
41
  }
42
  }
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: 2021-07-19 13:57+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,17 +17,17 @@ msgstr ""
17
  "Content-Type: text/plain; charset=UTF-8\n"
18
  "Content-Transfer-Encoding: 8bit\n"
19
 
20
- #: classes/amazon-s3-and-cloudfront.php:166
21
- #: classes/amazon-s3-and-cloudfront.php:167
22
  msgid "Offload Media Lite"
23
  msgstr ""
24
 
25
- #: classes/amazon-s3-and-cloudfront.php:402
26
- #: classes/amazon-s3-and-cloudfront.php:419
27
  msgid "Unknown"
28
  msgstr ""
29
 
30
- #: classes/amazon-s3-and-cloudfront.php:491
31
  #: view/bucket-select.php:87
32
  #: view/delivery-provider-select.php:129
33
  #: view/delivery-provider-select.php:149
@@ -38,223 +38,185 @@ msgstr ""
38
  msgid "defined in wp-config.php"
39
  msgstr ""
40
 
41
- #: classes/amazon-s3-and-cloudfront.php:1422
42
- #, php-format
43
- msgid "Media Library item ID %d. Provided path is not a string"
44
- msgstr ""
45
-
46
- #: classes/amazon-s3-and-cloudfront.php:1430
47
- #: classes/items/media-library-item.php:133
48
- #, php-format
49
- msgid "Media Library item with ID %d has damaged meta data"
50
- msgstr ""
51
-
52
- #: classes/amazon-s3-and-cloudfront.php:1441
53
- #: classes/items/media-library-item.php:144
54
- #, php-format
55
- msgid "Media Library item with ID %d does not have a valid file path"
56
- msgstr ""
57
-
58
- #: classes/amazon-s3-and-cloudfront.php:1492
59
- #: classes/amazon-s3-and-cloudfront.php:1718
60
- #, php-format
61
- msgid "File %s does not exist"
62
- msgstr ""
63
-
64
- #: classes/amazon-s3-and-cloudfront.php:1522
65
- #, php-format
66
- msgid "Mime type %s is not allowed"
67
- msgstr ""
68
-
69
- #: classes/amazon-s3-and-cloudfront.php:1533
70
- msgid "Already offloaded to a different provider"
71
- msgstr ""
72
-
73
- #: classes/amazon-s3-and-cloudfront.php:1641
74
- #: classes/amazon-s3-and-cloudfront.php:1732
75
- #, php-format
76
- msgid "Error offloading %s to provider: %s"
77
- msgstr ""
78
-
79
- #: classes/amazon-s3-and-cloudfront.php:2855
80
  msgid "This action can only be performed through an admin screen."
81
  msgstr ""
82
 
83
- #: classes/amazon-s3-and-cloudfront.php:2857
84
  msgid "Cheatin&#8217; eh?"
85
  msgstr ""
86
 
87
- #: classes/amazon-s3-and-cloudfront.php:2859
88
  msgid "You do not have sufficient permissions to access this page."
89
  msgstr ""
90
 
91
- #: classes/amazon-s3-and-cloudfront.php:3183
92
  msgid "Error Getting Bucket Region"
93
  msgstr ""
94
 
95
- #: classes/amazon-s3-and-cloudfront.php:3184
96
  #, php-format
97
  msgid "There was an error attempting to get the region of the bucket %s: %s"
98
  msgstr ""
99
 
100
- #: classes/amazon-s3-and-cloudfront.php:3287
101
  msgid ""
102
  "This is a test file to check if the user has write permission to the bucket. "
103
  "Delete me if found."
104
  msgstr ""
105
 
106
- #: classes/amazon-s3-and-cloudfront.php:3293
107
  #, php-format
108
  msgid ""
109
  "There was an error attempting to check the permissions of the bucket %s: %s"
110
  msgstr ""
111
 
112
- #: classes/amazon-s3-and-cloudfront.php:3389
113
  msgid "Error creating bucket"
114
  msgstr ""
115
 
116
- #: classes/amazon-s3-and-cloudfront.php:3390
117
  msgid "Bucket name too short."
118
  msgstr ""
119
 
120
- #: classes/amazon-s3-and-cloudfront.php:3391
121
  msgid "Bucket name too long."
122
  msgstr ""
123
 
124
- #: classes/amazon-s3-and-cloudfront.php:3392
125
  msgid ""
126
  "Invalid character. Bucket names can contain lowercase letters, numbers, "
127
  "periods and hyphens."
128
  msgstr ""
129
 
130
- #: classes/amazon-s3-and-cloudfront.php:3393
131
  msgid "Error saving bucket"
132
  msgstr ""
133
 
134
- #: classes/amazon-s3-and-cloudfront.php:3394
135
  msgid "Error fetching buckets"
136
  msgstr ""
137
 
138
- #: classes/amazon-s3-and-cloudfront.php:3395
139
  msgid "Error getting URL preview: "
140
  msgstr ""
141
 
142
- #: classes/amazon-s3-and-cloudfront.php:3396
143
  msgid "The changes you made will be lost if you navigate away from this page"
144
  msgstr ""
145
 
146
- #: classes/amazon-s3-and-cloudfront.php:3397
147
  msgid "Getting diagnostic info..."
148
  msgstr ""
149
 
150
- #: classes/amazon-s3-and-cloudfront.php:3398
151
  msgid "Error getting diagnostic info: "
152
  msgstr ""
153
 
154
- #: classes/amazon-s3-and-cloudfront.php:3399
155
  msgctxt "placeholder for hidden access key, 39 char max"
156
  msgid "-- not shown --"
157
  msgstr ""
158
 
159
- #: classes/amazon-s3-and-cloudfront.php:3401
160
- #: classes/amazon-s3-and-cloudfront.php:5786
161
  msgid "Settings saved."
162
  msgstr ""
163
 
164
- #: classes/amazon-s3-and-cloudfront.php:3523
165
  msgid "Cheatin' eh?"
166
  msgstr ""
167
 
168
- #: classes/amazon-s3-and-cloudfront.php:3596
169
  #, php-format
170
  msgid "Could not set new Delivery Provider: %s"
171
  msgstr ""
172
 
173
- #: classes/amazon-s3-and-cloudfront.php:3671
174
- #: classes/amazon-s3-and-cloudfront.php:3801
175
  msgid "No bucket name provided."
176
  msgstr ""
177
 
178
- #: classes/amazon-s3-and-cloudfront.php:3680
179
  msgid "Bucket name not valid."
180
  msgstr ""
181
 
182
- #: classes/amazon-s3-and-cloudfront.php:3693
183
  msgid "No region provided."
184
  msgstr ""
185
 
186
- #: classes/amazon-s3-and-cloudfront.php:3770
187
  #, php-format
188
  msgctxt "Trying to change public access setting for given provider's bucket."
189
  msgid "Can't change Block All Public Access setting for %s buckets."
190
  msgstr ""
191
 
192
- #: classes/amazon-s3-and-cloudfront.php:3779
193
  msgid "No block public access setting provided."
194
  msgstr ""
195
 
196
- #: classes/amazon-s3-and-cloudfront.php:3792
197
  msgid "Storage Provider not configured with access credentials."
198
  msgstr ""
199
 
200
- #: classes/amazon-s3-and-cloudfront.php:3819
201
  msgid "Could not change Block All Public Access status for bucket."
202
  msgstr ""
203
 
204
- #: classes/amazon-s3-and-cloudfront.php:3836
205
  msgid ""
206
  "<strong>Failed to Enable Block All Public Access</strong> &mdash; We could "
207
  "not enable Block All Public Access. You will need to log in to the AWS "
208
  "Console and do it manually."
209
  msgstr ""
210
 
211
- #: classes/amazon-s3-and-cloudfront.php:3838
212
  msgid ""
213
  "<strong>Failed to Disable Block All Public Access</strong> &mdash; We could "
214
  "not disable Block All Public Access. You will need to log in to the AWS "
215
  "Console and do it manually."
216
  msgstr ""
217
 
218
- #: classes/amazon-s3-and-cloudfront.php:3873
219
  #: view/provider-select.php:329
220
  msgctxt "placeholder for hidden secret access key, 39 char max"
221
  msgid "-- not shown --"
222
  msgstr ""
223
 
224
- #: classes/amazon-s3-and-cloudfront.php:3896
225
  msgid "Key File not valid JSON."
226
  msgstr ""
227
 
228
- #: classes/amazon-s3-and-cloudfront.php:3916
229
- #: classes/amazon-s3-and-cloudfront.php:3930
230
- #: classes/amazon-s3-and-cloudfront.php:3939
231
  msgctxt "missing form field"
232
  msgid " not provided."
233
  msgstr ""
234
 
235
- #: classes/amazon-s3-and-cloudfront.php:3982
236
  msgctxt "Show the media library tab"
237
  msgid "Media Library"
238
  msgstr ""
239
 
240
- #: classes/amazon-s3-and-cloudfront.php:3983
241
  msgctxt "Show the addons tab"
242
  msgid "Addons"
243
  msgstr ""
244
 
245
- #: classes/amazon-s3-and-cloudfront.php:3984
246
  msgctxt "Show the support tab"
247
  msgid "Support"
248
  msgstr ""
249
 
250
- #: classes/amazon-s3-and-cloudfront.php:4160
251
  #, php-format
252
  msgid ""
253
  "<strong>WP Offload Media</strong> &mdash; The file %s has been given %s "
254
  "permissions in the bucket."
255
  msgstr ""
256
 
257
- #: classes/amazon-s3-and-cloudfront.php:4179
258
  msgid ""
259
  "<strong>WP Offload Media Requirement Missing</strong> &mdash; Looks like you "
260
  "don't have an image manipulation library installed on this server and "
@@ -262,7 +224,7 @@ msgid ""
262
  "Please setup GD or ImageMagick."
263
  msgstr ""
264
 
265
- #: classes/amazon-s3-and-cloudfront.php:4203
266
  #, php-format
267
  msgid ""
268
  "<strong>Missing Table</strong> &mdash; One or more required database tables "
@@ -270,18 +232,18 @@ msgid ""
270
  "details. %s"
271
  msgstr ""
272
 
273
- #: classes/amazon-s3-and-cloudfront.php:5025
274
  #, php-format
275
  msgid ""
276
  "<a href=\"%s\">Define your access keys</a> to enable write access to the "
277
  "bucket"
278
  msgstr ""
279
 
280
- #: classes/amazon-s3-and-cloudfront.php:5032
281
  msgid "Quick Start Guide"
282
  msgstr ""
283
 
284
- #: classes/amazon-s3-and-cloudfront.php:5034
285
  #, php-format
286
  msgid ""
287
  "Looks like we don't have write access to this bucket. It's likely that the "
@@ -290,7 +252,7 @@ msgid ""
290
  "correctly."
291
  msgstr ""
292
 
293
- #: classes/amazon-s3-and-cloudfront.php:5036
294
  #, php-format
295
  msgid ""
296
  "Looks like we don't have access to the buckets. It's likely that the user "
@@ -298,39 +260,39 @@ msgid ""
298
  "Please see our %s for instructions on setting up permissions correctly."
299
  msgstr ""
300
 
301
- #: classes/amazon-s3-and-cloudfront.php:5204
302
  msgid "WP Offload Media Activation"
303
  msgstr ""
304
 
305
- #: classes/amazon-s3-and-cloudfront.php:5205
306
  msgid ""
307
  "WP Offload Media Lite and WP Offload Media cannot both be active. We've "
308
  "automatically deactivated WP Offload Media Lite."
309
  msgstr ""
310
 
311
- #: classes/amazon-s3-and-cloudfront.php:5207
312
  msgid "WP Offload Media Lite Activation"
313
  msgstr ""
314
 
315
- #: classes/amazon-s3-and-cloudfront.php:5208
316
  msgid ""
317
  "WP Offload Media Lite and WP Offload Media cannot both be active. We've "
318
  "automatically deactivated WP Offload Media."
319
  msgstr ""
320
 
321
- #: classes/amazon-s3-and-cloudfront.php:5262
322
  msgid "More&nbsp;info&nbsp;&raquo;"
323
  msgstr ""
324
 
325
- #: classes/amazon-s3-and-cloudfront.php:5342
326
  msgid "this doc"
327
  msgstr ""
328
 
329
- #: classes/amazon-s3-and-cloudfront.php:5344
330
  msgid "WP Offload Media Feature Removed"
331
  msgstr ""
332
 
333
- #: classes/amazon-s3-and-cloudfront.php:5345
334
  #, php-format
335
  msgid ""
336
  "You had the \"Always non-SSL\" option selected in your settings, but we've "
@@ -341,68 +303,21 @@ msgid ""
341
  "to the old behavior."
342
  msgstr ""
343
 
344
- #: classes/amazon-s3-and-cloudfront.php:5380
345
- msgid "Offload"
346
- msgstr ""
347
-
348
- #: classes/amazon-s3-and-cloudfront.php:5484
349
- msgid "No"
350
- msgstr ""
351
-
352
- #: classes/amazon-s3-and-cloudfront.php:5489
353
- msgctxt "Storage provider key name"
354
- msgid "Storage Provider"
355
- msgstr ""
356
-
357
- #: classes/amazon-s3-and-cloudfront.php:5490
358
- msgctxt "Storage provider name"
359
- msgid "Storage Provider"
360
- msgstr ""
361
-
362
- #: classes/amazon-s3-and-cloudfront.php:5491
363
- msgctxt "Bucket name"
364
- msgid "Bucket"
365
- msgstr ""
366
-
367
- #: classes/amazon-s3-and-cloudfront.php:5492
368
- msgctxt "Path to file in bucket"
369
- msgid "Path"
370
- msgstr ""
371
-
372
- #: classes/amazon-s3-and-cloudfront.php:5493
373
- msgctxt "Location of bucket"
374
- msgid "Region"
375
- msgstr ""
376
-
377
- #: classes/amazon-s3-and-cloudfront.php:5494
378
- msgctxt "Access control list of the file in bucket"
379
- msgid "Access"
380
- msgstr ""
381
-
382
- #: classes/amazon-s3-and-cloudfront.php:5495
383
- msgid "URL"
384
- msgstr ""
385
-
386
- #: classes/amazon-s3-and-cloudfront.php:5496
387
- msgctxt "Whether or not metadata has been verified"
388
- msgid "Verified"
389
- msgstr ""
390
-
391
- #: classes/amazon-s3-and-cloudfront.php:5749
392
  msgid "Assets Pull"
393
  msgstr ""
394
 
395
- #: classes/amazon-s3-and-cloudfront.php:5750
396
  msgid ""
397
  "An addon for WP Offload Media to serve your site's JS, CSS, and other "
398
  "enqueued assets from Amazon CloudFront or another CDN."
399
  msgstr ""
400
 
401
- #: classes/amazon-s3-and-cloudfront.php:5754
402
  msgid "Feature"
403
  msgstr ""
404
 
405
- #: classes/amazon-s3-and-cloudfront.php:5800
406
  #, php-format
407
  msgid ""
408
  "<strong>Amazon Web Services Plugin No Longer Required</strong> &mdash; As of "
@@ -413,7 +328,7 @@ msgid ""
413
  "plugin, it should be safe to deactivate and delete it. %2$s"
414
  msgstr ""
415
 
416
- #: classes/amazon-s3-and-cloudfront.php:5832
417
  #, php-format
418
  msgid ""
419
  "<strong>WP Offload Media Settings Moved</strong> &mdash; You now define your "
@@ -546,34 +461,174 @@ msgstr ""
546
  msgid "Settings"
547
  msgstr ""
548
 
549
- #: classes/as3cf-plugin-compatibility.php:639
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
550
  #, php-format
551
  msgid "The local directory %s does not exist and could not be created."
552
  msgstr ""
553
 
554
- #: classes/as3cf-plugin-compatibility.php:640
555
- #: classes/as3cf-plugin-compatibility.php:652
556
  #: classes/upgrades/upgrade-meta-wp-error.php:81
557
  #, php-format
558
  msgid ""
559
  "There was an error attempting to download the file %s from the bucket: %s"
560
  msgstr ""
561
 
562
- #: classes/as3cf-plugin-compatibility.php:977
563
  #, php-format
564
- msgid ""
565
- "<strong>Warning:</strong> This site is using PHP %1$s, in a future update WP "
566
- "Offload Media will require PHP %2$s or later. %3$s"
567
  msgstr ""
568
 
569
- #: classes/items/media-library-item.php:97
570
  msgid "Empty Attachment ID passed to "
571
  msgstr ""
572
 
573
- #: classes/items/media-library-item.php:106
574
  msgid "Invalid Originator passed to "
575
  msgstr ""
576
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
577
  #: classes/providers/delivery/another-cdn.php:47
578
  #: classes/providers/delivery/digitalocean-spaces-cdn.php:83
579
  msgid "Fast, No Private Media"
@@ -644,7 +699,7 @@ msgid ""
644
  "running in the background to update URLs in your post content. %2$s"
645
  msgstr ""
646
 
647
- #: classes/upgrades/upgrade-edd-replace-urls.php:36
648
  msgid "and ensuring that only the local URL exists in EDD post meta."
649
  msgstr ""
650
 
@@ -666,25 +721,29 @@ msgid ""
666
  "running in the background to update URLs in your post excerpts. %2$s"
667
  msgstr ""
668
 
669
- #: classes/upgrades/upgrade-filter-post.php:388
670
  #, php-format
671
  msgid ""
672
  "<strong>Paused Upgrade</strong><br>The find &amp; replace to update URLs has "
673
  "been paused. %s"
674
  msgstr ""
675
 
676
- #: classes/upgrades/upgrade-filter-post.php:397
677
  msgid "See our documentation"
678
  msgstr ""
679
 
680
- #: classes/upgrades/upgrade-filter-post.php:403
681
  #, php-format
682
  msgid ""
683
  "%s for details on why we&#8217;re doing this, why it runs slowly, and how to "
684
  "make it run faster."
685
  msgstr ""
686
 
687
- #: classes/upgrades/upgrade-items-table.php:48
 
 
 
 
688
  msgid ""
689
  "and updating the plugin's metadata to use a faster storage method. During "
690
  "the update the site's total offloaded media count may be inaccurate but will "
@@ -703,6 +762,10 @@ msgid ""
703
  "style=\"white-space:nowrap;\">(e.g. s3-us-west-2.amazonaws.com)</span>."
704
  msgstr ""
705
 
 
 
 
 
706
  #: classes/upgrades/upgrade-wpos3-to-as3cf.php:36
707
  msgid ""
708
  "and updating the metadata to use key names compatible with the current "
@@ -751,12 +814,12 @@ msgstr ""
751
  msgid " (%s%% Complete)"
752
  msgstr ""
753
 
754
- #: classes/upgrades/upgrade.php:629
755
  #, php-format
756
  msgid "Every %d Minutes"
757
  msgstr ""
758
 
759
- #: classes/upgrades/upgrade.php:956
760
  #, php-format
761
  msgid ""
762
  "<strong>Settings Locked Temporarily</strong> &mdash; You can't change any of "
@@ -787,15 +850,15 @@ msgctxt "Install plugin now"
787
  msgid "Install Now"
788
  msgstr ""
789
 
790
- #: view/attachment-metabox.php:20
791
  msgid "This item has not been offloaded yet."
792
  msgstr ""
793
 
794
- #: view/attachment-metabox.php:56
795
  msgid "File does not exist on server"
796
  msgstr ""
797
 
798
- #: view/attachment-metabox.php:64
799
  msgid "File exists on server"
800
  msgstr ""
801
 
8
  msgstr ""
9
  "Project-Id-Version: amazon-s3-and-cloudfront\n"
10
  "Report-Msgid-Bugs-To: nom@deliciousbrains.com\n"
11
+ "POT-Creation-Date: 2022-03-09 13:52+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:195
21
+ #: classes/amazon-s3-and-cloudfront.php:196
22
  msgid "Offload Media Lite"
23
  msgstr ""
24
 
25
+ #: classes/amazon-s3-and-cloudfront.php:436
26
+ #: classes/amazon-s3-and-cloudfront.php:453
27
  msgid "Unknown"
28
  msgstr ""
29
 
30
+ #: classes/amazon-s3-and-cloudfront.php:534
31
  #: view/bucket-select.php:87
32
  #: view/delivery-provider-select.php:129
33
  #: view/delivery-provider-select.php:149
38
  msgid "defined in wp-config.php"
39
  msgstr ""
40
 
41
+ #: classes/amazon-s3-and-cloudfront.php:1521
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  msgid "This action can only be performed through an admin screen."
43
  msgstr ""
44
 
45
+ #: classes/amazon-s3-and-cloudfront.php:1523
46
  msgid "Cheatin&#8217; eh?"
47
  msgstr ""
48
 
49
+ #: classes/amazon-s3-and-cloudfront.php:1525
50
  msgid "You do not have sufficient permissions to access this page."
51
  msgstr ""
52
 
53
+ #: classes/amazon-s3-and-cloudfront.php:1849
54
  msgid "Error Getting Bucket Region"
55
  msgstr ""
56
 
57
+ #: classes/amazon-s3-and-cloudfront.php:1850
58
  #, php-format
59
  msgid "There was an error attempting to get the region of the bucket %s: %s"
60
  msgstr ""
61
 
62
+ #: classes/amazon-s3-and-cloudfront.php:1953
63
  msgid ""
64
  "This is a test file to check if the user has write permission to the bucket. "
65
  "Delete me if found."
66
  msgstr ""
67
 
68
+ #: classes/amazon-s3-and-cloudfront.php:1959
69
  #, php-format
70
  msgid ""
71
  "There was an error attempting to check the permissions of the bucket %s: %s"
72
  msgstr ""
73
 
74
+ #: classes/amazon-s3-and-cloudfront.php:2077
75
  msgid "Error creating bucket"
76
  msgstr ""
77
 
78
+ #: classes/amazon-s3-and-cloudfront.php:2078
79
  msgid "Bucket name too short."
80
  msgstr ""
81
 
82
+ #: classes/amazon-s3-and-cloudfront.php:2079
83
  msgid "Bucket name too long."
84
  msgstr ""
85
 
86
+ #: classes/amazon-s3-and-cloudfront.php:2080
87
  msgid ""
88
  "Invalid character. Bucket names can contain lowercase letters, numbers, "
89
  "periods and hyphens."
90
  msgstr ""
91
 
92
+ #: classes/amazon-s3-and-cloudfront.php:2081
93
  msgid "Error saving bucket"
94
  msgstr ""
95
 
96
+ #: classes/amazon-s3-and-cloudfront.php:2082
97
  msgid "Error fetching buckets"
98
  msgstr ""
99
 
100
+ #: classes/amazon-s3-and-cloudfront.php:2083
101
  msgid "Error getting URL preview: "
102
  msgstr ""
103
 
104
+ #: classes/amazon-s3-and-cloudfront.php:2084
105
  msgid "The changes you made will be lost if you navigate away from this page"
106
  msgstr ""
107
 
108
+ #: classes/amazon-s3-and-cloudfront.php:2085
109
  msgid "Getting diagnostic info..."
110
  msgstr ""
111
 
112
+ #: classes/amazon-s3-and-cloudfront.php:2086
113
  msgid "Error getting diagnostic info: "
114
  msgstr ""
115
 
116
+ #: classes/amazon-s3-and-cloudfront.php:2087
117
  msgctxt "placeholder for hidden access key, 39 char max"
118
  msgid "-- not shown --"
119
  msgstr ""
120
 
121
+ #: classes/amazon-s3-and-cloudfront.php:2089
122
+ #: classes/amazon-s3-and-cloudfront.php:4210
123
  msgid "Settings saved."
124
  msgstr ""
125
 
126
+ #: classes/amazon-s3-and-cloudfront.php:2211
127
  msgid "Cheatin' eh?"
128
  msgstr ""
129
 
130
+ #: classes/amazon-s3-and-cloudfront.php:2284
131
  #, php-format
132
  msgid "Could not set new Delivery Provider: %s"
133
  msgstr ""
134
 
135
+ #: classes/amazon-s3-and-cloudfront.php:2359
136
+ #: classes/amazon-s3-and-cloudfront.php:2489
137
  msgid "No bucket name provided."
138
  msgstr ""
139
 
140
+ #: classes/amazon-s3-and-cloudfront.php:2368
141
  msgid "Bucket name not valid."
142
  msgstr ""
143
 
144
+ #: classes/amazon-s3-and-cloudfront.php:2381
145
  msgid "No region provided."
146
  msgstr ""
147
 
148
+ #: classes/amazon-s3-and-cloudfront.php:2458
149
  #, php-format
150
  msgctxt "Trying to change public access setting for given provider's bucket."
151
  msgid "Can't change Block All Public Access setting for %s buckets."
152
  msgstr ""
153
 
154
+ #: classes/amazon-s3-and-cloudfront.php:2467
155
  msgid "No block public access setting provided."
156
  msgstr ""
157
 
158
+ #: classes/amazon-s3-and-cloudfront.php:2480
159
  msgid "Storage Provider not configured with access credentials."
160
  msgstr ""
161
 
162
+ #: classes/amazon-s3-and-cloudfront.php:2507
163
  msgid "Could not change Block All Public Access status for bucket."
164
  msgstr ""
165
 
166
+ #: classes/amazon-s3-and-cloudfront.php:2524
167
  msgid ""
168
  "<strong>Failed to Enable Block All Public Access</strong> &mdash; We could "
169
  "not enable Block All Public Access. You will need to log in to the AWS "
170
  "Console and do it manually."
171
  msgstr ""
172
 
173
+ #: classes/amazon-s3-and-cloudfront.php:2526
174
  msgid ""
175
  "<strong>Failed to Disable Block All Public Access</strong> &mdash; We could "
176
  "not disable Block All Public Access. You will need to log in to the AWS "
177
  "Console and do it manually."
178
  msgstr ""
179
 
180
+ #: classes/amazon-s3-and-cloudfront.php:2561
181
  #: view/provider-select.php:329
182
  msgctxt "placeholder for hidden secret access key, 39 char max"
183
  msgid "-- not shown --"
184
  msgstr ""
185
 
186
+ #: classes/amazon-s3-and-cloudfront.php:2584
187
  msgid "Key File not valid JSON."
188
  msgstr ""
189
 
190
+ #: classes/amazon-s3-and-cloudfront.php:2604
191
+ #: classes/amazon-s3-and-cloudfront.php:2618
192
+ #: classes/amazon-s3-and-cloudfront.php:2627
193
  msgctxt "missing form field"
194
  msgid " not provided."
195
  msgstr ""
196
 
197
+ #: classes/amazon-s3-and-cloudfront.php:2670
198
  msgctxt "Show the media library tab"
199
  msgid "Media Library"
200
  msgstr ""
201
 
202
+ #: classes/amazon-s3-and-cloudfront.php:2671
203
  msgctxt "Show the addons tab"
204
  msgid "Addons"
205
  msgstr ""
206
 
207
+ #: classes/amazon-s3-and-cloudfront.php:2672
208
  msgctxt "Show the support tab"
209
  msgid "Support"
210
  msgstr ""
211
 
212
+ #: classes/amazon-s3-and-cloudfront.php:2849
213
  #, php-format
214
  msgid ""
215
  "<strong>WP Offload Media</strong> &mdash; The file %s has been given %s "
216
  "permissions in the bucket."
217
  msgstr ""
218
 
219
+ #: classes/amazon-s3-and-cloudfront.php:2868
220
  msgid ""
221
  "<strong>WP Offload Media Requirement Missing</strong> &mdash; Looks like you "
222
  "don't have an image manipulation library installed on this server and "
224
  "Please setup GD or ImageMagick."
225
  msgstr ""
226
 
227
+ #: classes/amazon-s3-and-cloudfront.php:2892
228
  #, php-format
229
  msgid ""
230
  "<strong>Missing Table</strong> &mdash; One or more required database tables "
232
  "details. %s"
233
  msgstr ""
234
 
235
+ #: classes/amazon-s3-and-cloudfront.php:3714
236
  #, php-format
237
  msgid ""
238
  "<a href=\"%s\">Define your access keys</a> to enable write access to the "
239
  "bucket"
240
  msgstr ""
241
 
242
+ #: classes/amazon-s3-and-cloudfront.php:3721
243
  msgid "Quick Start Guide"
244
  msgstr ""
245
 
246
+ #: classes/amazon-s3-and-cloudfront.php:3723
247
  #, php-format
248
  msgid ""
249
  "Looks like we don't have write access to this bucket. It's likely that the "
252
  "correctly."
253
  msgstr ""
254
 
255
+ #: classes/amazon-s3-and-cloudfront.php:3725
256
  #, php-format
257
  msgid ""
258
  "Looks like we don't have access to the buckets. It's likely that the user "
260
  "Please see our %s for instructions on setting up permissions correctly."
261
  msgstr ""
262
 
263
+ #: classes/amazon-s3-and-cloudfront.php:3895
264
  msgid "WP Offload Media Activation"
265
  msgstr ""
266
 
267
+ #: classes/amazon-s3-and-cloudfront.php:3896
268
  msgid ""
269
  "WP Offload Media Lite and WP Offload Media cannot both be active. We've "
270
  "automatically deactivated WP Offload Media Lite."
271
  msgstr ""
272
 
273
+ #: classes/amazon-s3-and-cloudfront.php:3898
274
  msgid "WP Offload Media Lite Activation"
275
  msgstr ""
276
 
277
+ #: classes/amazon-s3-and-cloudfront.php:3899
278
  msgid ""
279
  "WP Offload Media Lite and WP Offload Media cannot both be active. We've "
280
  "automatically deactivated WP Offload Media."
281
  msgstr ""
282
 
283
+ #: classes/amazon-s3-and-cloudfront.php:3953
284
  msgid "More&nbsp;info&nbsp;&raquo;"
285
  msgstr ""
286
 
287
+ #: classes/amazon-s3-and-cloudfront.php:4022
288
  msgid "this doc"
289
  msgstr ""
290
 
291
+ #: classes/amazon-s3-and-cloudfront.php:4024
292
  msgid "WP Offload Media Feature Removed"
293
  msgstr ""
294
 
295
+ #: classes/amazon-s3-and-cloudfront.php:4025
296
  #, php-format
297
  msgid ""
298
  "You had the \"Always non-SSL\" option selected in your settings, but we've "
303
  "to the old behavior."
304
  msgstr ""
305
 
306
+ #: classes/amazon-s3-and-cloudfront.php:4173
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
307
  msgid "Assets Pull"
308
  msgstr ""
309
 
310
+ #: classes/amazon-s3-and-cloudfront.php:4174
311
  msgid ""
312
  "An addon for WP Offload Media to serve your site's JS, CSS, and other "
313
  "enqueued assets from Amazon CloudFront or another CDN."
314
  msgstr ""
315
 
316
+ #: classes/amazon-s3-and-cloudfront.php:4178
317
  msgid "Feature"
318
  msgstr ""
319
 
320
+ #: classes/amazon-s3-and-cloudfront.php:4224
321
  #, php-format
322
  msgid ""
323
  "<strong>Amazon Web Services Plugin No Longer Required</strong> &mdash; As of "
328
  "plugin, it should be safe to deactivate and delete it. %2$s"
329
  msgstr ""
330
 
331
+ #: classes/amazon-s3-and-cloudfront.php:4256
332
  #, php-format
333
  msgid ""
334
  "<strong>WP Offload Media Settings Moved</strong> &mdash; You now define your "
461
  msgid "Settings"
462
  msgstr ""
463
 
464
+ #: classes/as3cf-plugin-compatibility.php:949
465
+ #, php-format
466
+ msgid ""
467
+ "<strong>Warning:</strong> This site is using PHP %1$s, in a future update WP "
468
+ "Offload Media will require PHP %2$s or later. %3$s"
469
+ msgstr ""
470
+
471
+ #: classes/integrations/media-library.php:205
472
+ #, php-format
473
+ msgid "Can't create item from media library item %d"
474
+ msgstr ""
475
+
476
+ #: classes/integrations/media-library.php:497
477
+ msgid "Offload"
478
+ msgstr ""
479
+
480
+ #: classes/integrations/media-library.php:923
481
+ msgid "No"
482
+ msgstr ""
483
+
484
+ #: classes/integrations/media-library.php:933
485
+ msgctxt "Storage provider key name"
486
+ msgid "Storage Provider"
487
+ msgstr ""
488
+
489
+ #: classes/integrations/media-library.php:934
490
+ msgctxt "Storage provider name"
491
+ msgid "Storage Provider"
492
+ msgstr ""
493
+
494
+ #: classes/integrations/media-library.php:935
495
+ msgctxt "Bucket name"
496
+ msgid "Bucket"
497
+ msgstr ""
498
+
499
+ #: classes/integrations/media-library.php:936
500
+ msgctxt "Path to file in bucket"
501
+ msgid "Path"
502
+ msgstr ""
503
+
504
+ #: classes/integrations/media-library.php:937
505
+ msgctxt "Location of bucket"
506
+ msgid "Region"
507
+ msgstr ""
508
+
509
+ #: classes/integrations/media-library.php:938
510
+ msgctxt "Access control list of the file in bucket"
511
+ msgid "Access"
512
+ msgstr ""
513
+
514
+ #: classes/integrations/media-library.php:939
515
+ msgid "URL"
516
+ msgstr ""
517
+
518
+ #: classes/integrations/media-library.php:940
519
+ msgctxt "Whether or not metadata has been verified"
520
+ msgid "Verified"
521
+ msgstr ""
522
+
523
+ #: classes/items/download-handler.php:84
524
+ msgid ""
525
+ "%1$s with ID %d is offloaded to a different provider than currently "
526
+ "configured"
527
+ msgstr ""
528
+
529
+ #: classes/items/download-handler.php:151
530
  #, php-format
531
  msgid "The local directory %s does not exist and could not be created."
532
  msgstr ""
533
 
534
+ #: classes/items/download-handler.php:152
 
535
  #: classes/upgrades/upgrade-meta-wp-error.php:81
536
  #, php-format
537
  msgid ""
538
  "There was an error attempting to download the file %s from the bucket: %s"
539
  msgstr ""
540
 
541
+ #: classes/items/download-handler.php:158
542
  #, php-format
543
+ msgid "Error downloading %1$s from bucket: %2$s"
 
 
544
  msgstr ""
545
 
546
+ #: classes/items/media-library-item.php:142
547
  msgid "Empty Attachment ID passed to "
548
  msgstr ""
549
 
550
+ #: classes/items/media-library-item.php:157
551
  msgid "Invalid Originator passed to "
552
  msgstr ""
553
 
554
+ #: classes/items/media-library-item.php:170
555
+ #, php-format
556
+ msgid "Media Library item with ID %d has damaged meta data"
557
+ msgstr ""
558
+
559
+ #: classes/items/media-library-item.php:181
560
+ #, php-format
561
+ msgid "Media Library item with ID %d does not have a valid file path"
562
+ msgstr ""
563
+
564
+ #: classes/items/media-library-item.php:553
565
+ msgid "Edit"
566
+ msgstr ""
567
+
568
+ #: classes/items/remove-provider-handler.php:40
569
+ msgid "Invalid object_keys option provided."
570
+ msgstr ""
571
+
572
+ #: classes/items/remove-provider-handler.php:44
573
+ msgid "Invalid offloaded_files option provided."
574
+ msgstr ""
575
+
576
+ #: classes/items/remove-provider-handler.php:48
577
+ msgid ""
578
+ "Providing both object_keys and offloaded_files options is not supported."
579
+ msgstr ""
580
+
581
+ #: classes/items/upload-handler.php:49
582
+ #, php-format
583
+ msgid "%s with id %d does not have a valid file path"
584
+ msgstr ""
585
+
586
+ #: classes/items/upload-handler.php:56
587
+ #, php-format
588
+ msgid "%s with id %d. Provided path is not a string"
589
+ msgstr ""
590
+
591
+ #: classes/items/upload-handler.php:63
592
+ #, php-format
593
+ msgid "Primary file %s does not exist"
594
+ msgstr ""
595
+
596
+ #: classes/items/upload-handler.php:75
597
+ #, php-format
598
+ msgid "Mime type %s is not allowed"
599
+ msgstr ""
600
+
601
+ #: classes/items/upload-handler.php:175
602
+ #, php-format
603
+ msgid "Bucket name \"%1$s\" is invalid, using \"%2$s\" instead."
604
+ msgstr ""
605
+
606
+ #: classes/items/upload-handler.php:190
607
+ msgid ""
608
+ "The bucket may not be changed via filters for a previously offloaded item."
609
+ msgstr ""
610
+
611
+ #: classes/items/upload-handler.php:208
612
+ #, php-format
613
+ msgid ""
614
+ "The offloaded filename must not be changed, \"%1$s\" has been used instead "
615
+ "of \"%2$s\"."
616
+ msgstr ""
617
+
618
+ #: classes/items/upload-handler.php:216
619
+ msgid "The key may not be changed via filters for a previously offloaded item."
620
+ msgstr ""
621
+
622
+ #: classes/items/upload-handler.php:287
623
+ #, php-format
624
+ msgid "File %s does not exist"
625
+ msgstr ""
626
+
627
+ #: classes/items/upload-handler.php:308
628
+ #, php-format
629
+ msgid "Error offloading %1$s to provider: %2$s"
630
+ msgstr ""
631
+
632
  #: classes/providers/delivery/another-cdn.php:47
633
  #: classes/providers/delivery/digitalocean-spaces-cdn.php:83
634
  msgid "Fast, No Private Media"
699
  "running in the background to update URLs in your post content. %2$s"
700
  msgstr ""
701
 
702
+ #: classes/upgrades/upgrade-edd-replace-urls.php:38
703
  msgid "and ensuring that only the local URL exists in EDD post meta."
704
  msgstr ""
705
 
721
  "running in the background to update URLs in your post excerpts. %2$s"
722
  msgstr ""
723
 
724
+ #: classes/upgrades/upgrade-filter-post.php:391
725
  #, php-format
726
  msgid ""
727
  "<strong>Paused Upgrade</strong><br>The find &amp; replace to update URLs has "
728
  "been paused. %s"
729
  msgstr ""
730
 
731
+ #: classes/upgrades/upgrade-filter-post.php:400
732
  msgid "See our documentation"
733
  msgstr ""
734
 
735
+ #: classes/upgrades/upgrade-filter-post.php:406
736
  #, php-format
737
  msgid ""
738
  "%s for details on why we&#8217;re doing this, why it runs slowly, and how to "
739
  "make it run faster."
740
  msgstr ""
741
 
742
+ #: classes/upgrades/upgrade-item-extra-data.php:48
743
+ msgid "and updating metadata about offloaded items to new format."
744
+ msgstr ""
745
+
746
+ #: classes/upgrades/upgrade-items-table.php:47
747
  msgid ""
748
  "and updating the plugin's metadata to use a faster storage method. During "
749
  "the update the site's total offloaded media count may be inaccurate but will "
762
  "style=\"white-space:nowrap;\">(e.g. s3-us-west-2.amazonaws.com)</span>."
763
  msgstr ""
764
 
765
+ #: classes/upgrades/upgrade-tools-errors.php:37
766
+ msgid "and reformatting internal data about previous errors from tools ."
767
+ msgstr ""
768
+
769
  #: classes/upgrades/upgrade-wpos3-to-as3cf.php:36
770
  msgid ""
771
  "and updating the metadata to use key names compatible with the current "
814
  msgid " (%s%% Complete)"
815
  msgstr ""
816
 
817
+ #: classes/upgrades/upgrade.php:630
818
  #, php-format
819
  msgid "Every %d Minutes"
820
  msgstr ""
821
 
822
+ #: classes/upgrades/upgrade.php:957
823
  #, php-format
824
  msgid ""
825
  "<strong>Settings Locked Temporarily</strong> &mdash; You can't change any of "
850
  msgid "Install Now"
851
  msgstr ""
852
 
853
+ #: view/attachment-metabox.php:27
854
  msgid "This item has not been offloaded yet."
855
  msgstr ""
856
 
857
+ #: view/attachment-metabox.php:63
858
  msgid "File does not exist on server"
859
  msgstr ""
860
 
861
+ #: view/attachment-metabox.php:71
862
  msgid "File exists on server"
863
  msgstr ""
864
 
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.8
6
- Requires PHP: 5.5
7
- Stable tag: 2.5.5
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,10 @@ If you upgrade to the pro version of [WP Offload Media](https://deliciousbrains.
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
 
@@ -81,6 +85,16 @@ This version requires PHP 5.3.3+ and the Amazon Web Services plugin
81
 
82
  == Changelog ==
83
 
 
 
 
 
 
 
 
 
 
 
84
  = WP Offload Media Lite 2.5.5 - 2021-07-19 =
85
  * Bug fix: Signed GCS URLs broken when updating a post
86
  * Bug fix: Incorrect mime type set on scaled image's bucket object when thumbnail format differs from original file's format
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.9
6
+ Requires PHP: 5.6
7
+ Stable tag: 2.6.0
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.6 =
71
+ This is a major upgrade that updates the format of information stored about offloaded Media Library items. Once upgraded you will not be able to downgrade without restoring data from a backup.
72
+ This version requires PHP 5.6+
73
+
74
  = 2.3 =
75
  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.
76
 
85
 
86
  == Changelog ==
87
 
88
+ = WP Offload Media Lite 2.6 - 2022-03-09 =
89
+ * [Release Summary Blog Post](https://deliciousbrains.com/wp-offload-media-2-6-released/?utm_campaign=changelogs&utm_source=wordpress.org&utm_medium=free%2Bplugin%2Blisting)
90
+ * New: WP Offload Media is now compatible with WordPress 5.9 and Full Site Editing
91
+ * Improvement: Offloaded thumbnail sizes are now tracked for better handling of changes to registered sizes
92
+ * Improvement: Offloads and other storage provider actions are faster
93
+ * Bug fix: URL rewriting now works in the Full Site Editor
94
+ * Bug fix: Offloaded images are now shown when re-editing a Block Template or Template Part
95
+ * Bug fix: URL rewriting now works for Widgets migrated to a Widget Sidebar Block
96
+ * Bug fix: Objects are no longer left in the bucket when deleting a Media Library item with many changes to its thumbnail sizes
97
+
98
  = WP Offload Media Lite 2.5.5 - 2021-07-19 =
99
  * Bug fix: Signed GCS URLs broken when updating a post
100
  * Bug fix: Incorrect mime type set on scaled image's bucket object when thumbnail format differs from original file's format
view/attachment-metabox.php CHANGED
@@ -1,4 +1,8 @@
1
  <?php
 
 
 
 
2
  /** @var Amazon_S3_And_CloudFront|Amazon_S3_And_CloudFront_Pro $this */
3
  /** @var array|bool $provider_object */
4
  /** @var WP_Post $post */
@@ -13,6 +17,9 @@ $is_removable = $is_current_provider && in_array( 'remove', $available_act
13
  $is_copyable = $local_file_exists && in_array( 'copy', $available_actions ) && ( $is_current_provider || empty( $provider_object ) );
14
  $is_downloadable = ! $local_file_exists && in_array( 'download', $available_actions ) && $is_current_provider;
15
  $is_local_removable = $is_current_provider && $local_file_exists && in_array( 'remove_local', $available_actions );
 
 
 
16
  ?>
17
  <div class="s3-details">
18
  <?php if ( ! $provider_object ) : ?>
@@ -21,49 +28,49 @@ $is_local_removable = $is_current_provider && $local_file_exists && in_array( 'r
21
  </div>
22
  <?php else : ?>
23
  <div class="misc-pub-section">
24
- <div class="s3-key"><?php echo $this->get_media_action_strings( 'provider' ); ?>:</div>
25
  <input type="text" id="as3cf-provider" class="widefat<?php echo $provider_class; ?>" readonly="readonly" value="<?php echo $provider_name; ?>">
26
  </div>
27
  <div class="misc-pub-section">
28
- <div class="s3-key"><?php echo $this->get_media_action_strings( 'bucket' ); ?>:</div>
29
  <input type="text" id="as3cf-bucket" class="widefat" readonly="readonly" value="<?php echo $provider_object['bucket']; ?>">
30
  </div>
31
  <div class="misc-pub-section">
32
- <div class="s3-key"><?php echo $this->get_media_action_strings( 'key' ); ?>:</div>
33
  <input type="text" id="as3cf-key" class="widefat" readonly="readonly" value="<?php echo $provider_object['key']; ?>">
34
  </div>
35
  <?php if ( isset( $provider_object['region'] ) && $provider_object['region'] ) : ?>
36
  <div class="misc-pub-section">
37
- <div class="s3-key"><?php echo $this->get_media_action_strings( 'region' ); ?>:</div>
38
  <div id="as3cf-region" class="s3-value"><?php echo $provider_object['region']; ?></div>
39
  </div>
40
  <?php endif; ?>
41
  <div class="misc-pub-section">
42
- <div class="s3-key"><?php echo $this->get_media_action_strings( 'acl' ); ?>:</div>
43
  <div id="as3cf-acl" class="s3-value">
44
- <?php echo $this->get_acl_value_string( $provider_object['acl'], $post->ID ); ?>
45
  </div>
46
  </div>
47
  <?php
48
  if ( isset( $provider_object['is_verified'] ) && empty( $provider_object['is_verified'] ) ) : ?>
49
  <div class="misc-pub-section">
50
- <div class="s3-key"><?php echo $this->get_media_action_strings( 'is_verified' ); ?>:</div>
51
- <div id="as3cf-is-verified" class="s3-value"><?php echo $this->get_media_action_strings( 'not_verified' ); ?></div>
52
  </div>
53
  <?php endif; ?>
54
  <?php if ( $is_downloadable ) : ?>
55
  <div class="misc-pub-section">
56
  <div class="not-copied"><?php _e( 'File does not exist on server', 'amazon-s3-and-cloudfront' ); ?></div>
57
- <a id="as3cf-download-action" href="<?php echo $this->get_media_action_url( 'download', $post->ID, $sendback ); ?>">
58
- <?php echo $this->get_media_action_strings( 'download' ); ?>
59
  </a>
60
  </div>
61
  <?php endif; ?>
62
  <?php if ( $is_local_removable ) : ?>
63
  <div class="misc-pub-section">
64
  <div class="not-copied"><?php _e( 'File exists on server', 'amazon-s3-and-cloudfront' ); ?></div>
65
- <a id="as3cf-remove-local-action" href="<?php echo $this->get_media_action_url( 'remove_local', $post->ID, $sendback ); ?>">
66
- <?php echo $this->get_media_action_strings( 'remove_local' ); ?>
67
  </a>
68
  </div>
69
  <?php endif; ?>
@@ -75,15 +82,15 @@ $is_local_removable = $is_current_provider && $local_file_exists && in_array( 'r
75
  <div class="s3-actions">
76
  <?php if ( $is_removable ) : ?>
77
  <div class="remove-action">
78
- <a id="as3cf-remove-action" href="<?php echo $this->get_media_action_url( 'remove', $post->ID, $sendback ); ?>">
79
- <?php echo $this->get_media_action_strings( 'remove' ); ?>
80
  </a>
81
  </div>
82
  <?php endif; ?>
83
  <?php if ( $is_copyable ) : ?>
84
  <div class="copy-action">
85
- <a id="as3cf-copy-action" href="<?php echo $this->get_media_action_url( 'copy', $post->ID, $sendback ); ?>" class="button button-secondary">
86
- <?php echo $this->get_media_action_strings( 'copy' ); ?>
87
  </a>
88
  </div>
89
  <?php endif; ?>
1
  <?php
2
+
3
+ use DeliciousBrains\WP_Offload_Media\Integrations\Media_Library;
4
+ use DeliciousBrains\WP_Offload_Media\Pro\Integrations\Media_Library_Pro;
5
+
6
  /** @var Amazon_S3_And_CloudFront|Amazon_S3_And_CloudFront_Pro $this */
7
  /** @var array|bool $provider_object */
8
  /** @var WP_Post $post */
17
  $is_copyable = $local_file_exists && in_array( 'copy', $available_actions ) && ( $is_current_provider || empty( $provider_object ) );
18
  $is_downloadable = ! $local_file_exists && in_array( 'download', $available_actions ) && $is_current_provider;
19
  $is_local_removable = $is_current_provider && $local_file_exists && in_array( 'remove_local', $available_actions );
20
+
21
+ /** @var Media_Library|Media_Library_Pro $media_integration */
22
+ $media_integration = $this->get_integration_manager()->get_integration( 'mlib' );
23
  ?>
24
  <div class="s3-details">
25
  <?php if ( ! $provider_object ) : ?>
28
  </div>
29
  <?php else : ?>
30
  <div class="misc-pub-section">
31
+ <div class="s3-key"><?php echo $media_integration->get_media_action_strings( 'provider' ); ?>:</div>
32
  <input type="text" id="as3cf-provider" class="widefat<?php echo $provider_class; ?>" readonly="readonly" value="<?php echo $provider_name; ?>">
33
  </div>
34
  <div class="misc-pub-section">
35
+ <div class="s3-key"><?php echo $media_integration->get_media_action_strings( 'bucket' ); ?>:</div>
36
  <input type="text" id="as3cf-bucket" class="widefat" readonly="readonly" value="<?php echo $provider_object['bucket']; ?>">
37
  </div>
38
  <div class="misc-pub-section">
39
+ <div class="s3-key"><?php echo $media_integration->get_media_action_strings( 'key' ); ?>:</div>
40
  <input type="text" id="as3cf-key" class="widefat" readonly="readonly" value="<?php echo $provider_object['key']; ?>">
41
  </div>
42
  <?php if ( isset( $provider_object['region'] ) && $provider_object['region'] ) : ?>
43
  <div class="misc-pub-section">
44
+ <div class="s3-key"><?php echo $media_integration->get_media_action_strings( 'region' ); ?>:</div>
45
  <div id="as3cf-region" class="s3-value"><?php echo $provider_object['region']; ?></div>
46
  </div>
47
  <?php endif; ?>
48
  <div class="misc-pub-section">
49
+ <div class="s3-key"><?php echo $media_integration->get_media_action_strings( 'acl' ); ?>:</div>
50
  <div id="as3cf-acl" class="s3-value">
51
+ <?php echo $media_integration->get_acl_value_string( $provider_object['acl'], $post->ID ); ?>
52
  </div>
53
  </div>
54
  <?php
55
  if ( isset( $provider_object['is_verified'] ) && empty( $provider_object['is_verified'] ) ) : ?>
56
  <div class="misc-pub-section">
57
+ <div class="s3-key"><?php echo $media_integration->get_media_action_strings( 'is_verified' ); ?>:</div>
58
+ <div id="as3cf-is-verified" class="s3-value"><?php echo $media_integration->get_media_action_strings( 'not_verified' ); ?></div>
59
  </div>
60
  <?php endif; ?>
61
  <?php if ( $is_downloadable ) : ?>
62
  <div class="misc-pub-section">
63
  <div class="not-copied"><?php _e( 'File does not exist on server', 'amazon-s3-and-cloudfront' ); ?></div>
64
+ <a id="as3cf-download-action" href="<?php echo $media_integration->get_media_action_url( 'download', $post->ID, $sendback ); ?>">
65
+ <?php echo $media_integration->get_media_action_strings( 'download' ); ?>
66
  </a>
67
  </div>
68
  <?php endif; ?>
69
  <?php if ( $is_local_removable ) : ?>
70
  <div class="misc-pub-section">
71
  <div class="not-copied"><?php _e( 'File exists on server', 'amazon-s3-and-cloudfront' ); ?></div>
72
+ <a id="as3cf-remove-local-action" href="<?php echo $media_integration->get_media_action_url( 'remove_local', $post->ID, $sendback ); ?>">
73
+ <?php echo $media_integration->get_media_action_strings( 'remove_local' ); ?>
74
  </a>
75
  </div>
76
  <?php endif; ?>
82
  <div class="s3-actions">
83
  <?php if ( $is_removable ) : ?>
84
  <div class="remove-action">
85
+ <a id="as3cf-remove-action" href="<?php echo $media_integration->get_media_action_url( 'remove', $post->ID, $sendback ); ?>">
86
+ <?php echo $media_integration->get_media_action_strings( 'remove' ); ?>
87
  </a>
88
  </div>
89
  <?php endif; ?>
90
  <?php if ( $is_copyable ) : ?>
91
  <div class="copy-action">
92
+ <a id="as3cf-copy-action" href="<?php echo $media_integration->get_media_action_url( 'copy', $post->ID, $sendback ); ?>" class="button button-secondary">
93
+ <?php echo $media_integration->get_media_action_strings( 'copy' ); ?>
94
  </a>
95
  </div>
96
  <?php endif; ?>
wordpress-s3.php CHANGED
@@ -4,8 +4,8 @@ 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.5.5
8
- Author URI: https://deliciousbrains.com/
9
  Network: True
10
  Text Domain: amazon-s3-and-cloudfront
11
  Domain Path: /languages/
@@ -26,7 +26,7 @@ Domain Path: /languages/
26
  // Then completely rewritten.
27
  */
28
 
29
- $GLOBALS['aws_meta']['amazon-s3-and-cloudfront']['version'] = '2.5.5';
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.6.0
8
+ Author URI: https://deliciousbrains.com/?utm_campaign=WP%2BOffload%2BS3&utm_source=wordpress.org&utm_medium=free%2Bplugin%2Blisting
9
  Network: True
10
  Text Domain: amazon-s3-and-cloudfront
11
  Domain Path: /languages/
26
  // Then completely rewritten.
27
  */
28
 
29
+ $GLOBALS['aws_meta']['amazon-s3-and-cloudfront']['version'] = '2.6.0';
30
 
31
  require_once dirname( __FILE__ ) . '/classes/as3cf-compatibility-check.php';
32