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' );