WP Offload S3 Lite - Version 0.9.4

Version Description

  • 2015-08-27 =
  • New: Update all existing attachments with missing file sizes when the 'Remove Files From Server' option is enabled (automatically runs in the background)
  • Improvement: Show when constants are used to set bucket and region options
  • Improvement: Don't show compatibility notices on plugin update screen
  • Improvement: On Multisite installs don't call restore_current_blog() on successive loop iterations
  • Bug fix: 'Error getting URL preview' alert shown when enter key pressed on settings screen
  • Bug fix: Unable to crop header images when the 'Remove Files From Server' option is enabled
  • Bug fix: Incorrect storage space shown on Multisite installs when the 'Remove Files From Server' option is enabled
  • Bug fix: Upload attempted to non existent bucket when defined by constant
  • Bug fix: 'SignatureDoesNotMatch' error shown when using signed URLs with bucket names containing '.' characters
Download this release

Release Info

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

Code changes from version 0.9.3 to 0.9.4

README.md CHANGED
@@ -1,10 +1,9 @@
1
  # WP Offload S3 #
2
- **Contributors:** bradt
3
- **Donate link:** https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=5VPMGLLK94XJC
4
  **Tags:** uploads, amazon, s3, mirror, admin, media, cdn, cloudfront
5
  **Requires at least:** 3.7
6
  **Tested up to:** 4.3
7
- **Stable tag:** 0.9.3
8
  **License:** GPLv3
9
 
10
  Copies files to Amazon S3 as they are uploaded to the Media Library. Optionally configure Amazon CloudFront for faster delivery.
@@ -17,19 +16,21 @@ This plugin automatically copies images, videos, documents, and any other media
17
 
18
  Uploading files *directly* to your S3 account is not currently supported by this plugin. They are uploaded to your server first, then copied to S3. There is an option to automatically remove the files from your server once they are copied to S3 however.
19
 
20
- If you're adding this plugin to a site that's been around for a while, your existing media files will not be copied or served from S3. Only newly uploaded files will be copied and served from S3.
21
 
22
  **PRO Upgrade with Email Support and More Features**
23
 
24
  * Upload existing Media Library to S3
25
  * Find & replace file URLs in content
26
  * Control S3 files from the Media Library
27
- * [Assets addon](https://deliciousbrains.com/wp-offload-s3/?utm_source=wordpress.org&utm_medium=web&utm_content=desc&utm_campaign=freeplugin#assets-addon) - Serve your CSS & JS from S3/CloudFront
28
- * [WooCommerce addon](https://deliciousbrains.com/wp-offload-s3/?utm_source=wordpress.org&utm_medium=web&utm_content=desc&utm_campaign=freeplugin#woocommerce-addon)
29
- * [Easy Digital Downloads addon](https://deliciousbrains.com/wp-offload-s3/?utm_source=wordpress.org&utm_medium=web&utm_content=desc&utm_campaign=freeplugin#edd-addon)
30
  * PriorityExpert™ email support
31
 
32
- See the video below or [visit the web site](http://deliciousbrains.com/wp-offload-s3/?utm_source=wordpress.org&utm_medium=web&utm_content=desc&utm_campaign=freeplugin) to learn more about the pro version.
 
 
33
 
34
  https://www.youtube.com/watch?v=55xNGnbJ_CY
35
 
@@ -66,6 +67,17 @@ This version requires PHP 5.3.3+ and the Amazon Web Services plugin
66
 
67
  ## Changelog ##
68
 
 
 
 
 
 
 
 
 
 
 
 
69
  ### 0.9.3 - 2015-08-17 ###
70
  * New: Pro upgrade sidebar
71
  * Bug fix: Create buckets in US standard region causing S3 URLs to 404 errors
1
  # WP Offload S3 #
2
+ **Contributors:** bradt, deliciousbrains
 
3
  **Tags:** uploads, amazon, s3, mirror, admin, media, cdn, cloudfront
4
  **Requires at least:** 3.7
5
  **Tested up to:** 4.3
6
+ **Stable tag:** 0.9.4
7
  **License:** GPLv3
8
 
9
  Copies files to Amazon S3 as they are uploaded to the Media Library. Optionally configure Amazon CloudFront for faster delivery.
16
 
17
  Uploading files *directly* to your S3 account is not currently supported by this plugin. They are uploaded to your server first, then copied to S3. There is an option to automatically remove the files from your server once they are copied to S3 however.
18
 
19
+ If you're adding this plugin to a site that's been around for a while, your existing media files will not be copied or served from S3. Only newly uploaded files will be copied and served from S3. The pro upgrade has an upload tool to handle existing media files.
20
 
21
  **PRO Upgrade with Email Support and More Features**
22
 
23
  * Upload existing Media Library to S3
24
  * Find & replace file URLs in content
25
  * Control S3 files from the Media Library
26
+ * [Assets addon](https://deliciousbrains.com/wp-offload-s3/?utm_source=wordpress.org&utm_medium=web&utm_content=desc&utm_campaign=os3-free-plugin#assets-addon) - Serve your CSS & JS from S3/CloudFront
27
+ * [WooCommerce addon](https://deliciousbrains.com/wp-offload-s3/?utm_source=wordpress.org&utm_medium=web&utm_content=desc&utm_campaign=os3-free-plugin#woocommerce-addon)
28
+ * [Easy Digital Downloads addon](https://deliciousbrains.com/wp-offload-s3/?utm_source=wordpress.org&utm_medium=web&utm_content=desc&utm_campaign=os3-free-plugin#edd-addon)
29
  * PriorityExpert™ email support
30
 
31
+ [Compare pro vs free →](http://deliciousbrains.com/wp-offload-s3/upgrade/?utm_source=wordpress.org&utm_medium=web&utm_content=desc&utm_campaign=os3-free-plugin)
32
+
33
+ The video below runs through the pro upgrade features...
34
 
35
  https://www.youtube.com/watch?v=55xNGnbJ_CY
36
 
67
 
68
  ## Changelog ##
69
 
70
+ ### 0.9.4 - 2015-08-27 ###
71
+ * New: Update all existing attachments with missing file sizes when the 'Remove Files From Server' option is enabled (automatically runs in the background)
72
+ * Improvement: Show when constants are used to set bucket and region options
73
+ * Improvement: Don't show compatibility notices on plugin update screen
74
+ * Improvement: On Multisite installs don't call `restore_current_blog()` on successive loop iterations
75
+ * Bug fix: 'Error getting URL preview' alert shown when enter key pressed on settings screen
76
+ * Bug fix: Unable to crop header images when the 'Remove Files From Server' option is enabled
77
+ * Bug fix: Incorrect storage space shown on Multisite installs when the 'Remove Files From Server' option is enabled
78
+ * Bug fix: Upload attempted to non existent bucket when defined by constant
79
+ * Bug fix: 'SignatureDoesNotMatch' error shown when using signed URLs with bucket names containing '.' characters
80
+
81
  ### 0.9.3 - 2015-08-17 ###
82
  * New: Pro upgrade sidebar
83
  * Bug fix: Create buckets in US standard region causing S3 URLs to 404 errors
assets/js/modal.js CHANGED
@@ -1,7 +1,8 @@
1
  var as3cfModal = (function ( $ ) {
2
 
3
  var modal = {
4
- prefix: 'as3cf'
 
5
  };
6
 
7
  var modals = {};
@@ -9,9 +10,9 @@ var as3cfModal = (function ( $ ) {
9
  /**
10
  * Target to key
11
  *
12
- * @param string target
13
  *
14
- * @return string
15
  */
16
  function targetToKey( target ) {
17
  return target.replace( /[^a-z]/g, '' );
@@ -20,10 +21,11 @@ var as3cfModal = (function ( $ ) {
20
  /**
21
  * Open modal
22
  *
23
- * @param string target
24
- * @param function callback
 
25
  */
26
- modal.open = function ( target, callback ) {
27
  var key = targetToKey( target );
28
 
29
  // Overlay
@@ -41,6 +43,10 @@ var as3cfModal = (function ( $ ) {
41
  }
42
  $modal.data( 'as3cf-modal-target', target ).append( modals[ key ] );
43
 
 
 
 
 
44
  if ( 'function' === typeof callback ) {
45
  callback( target );
46
  }
@@ -58,9 +64,13 @@ var as3cfModal = (function ( $ ) {
58
  /**
59
  * Close modal
60
  *
61
- * @param function callback
62
  */
63
  modal.close = function ( callback ) {
 
 
 
 
64
  var target = $( '#as3cf-modal' ).data( 'as3cf-modal-target' );
65
 
66
  $( '#as3cf-overlay' ).fadeOut( 150, function () {
@@ -76,6 +86,15 @@ var as3cfModal = (function ( $ ) {
76
  $( 'body' ).trigger( 'as3cf-modal-close', [ target ] );
77
  };
78
 
 
 
 
 
 
 
 
 
 
79
  // Setup click handlers
80
  $( document ).ready( function () {
81
 
@@ -99,101 +118,4 @@ var as3cfModal = (function ( $ ) {
99
 
100
  return modal;
101
 
102
- })( jQuery );
103
-
104
- var as3cfFindAndReplaceModal = (function ( $, as3cfModal ) {
105
-
106
- var modal = {
107
- selector: '.as3cf-find-replace-container',
108
- isBulk: false,
109
- link: null,
110
- payload: {}
111
- };
112
-
113
- /**
114
- * Open modal
115
- *
116
- * @param string link
117
- * @param mixed payload
118
- */
119
- modal.open = function ( link, payload ) {
120
- if ( typeof link !== 'undefined' ) {
121
- modal.link = link;
122
- }
123
- if ( typeof payload !== 'undefined' ) {
124
- modal.payload = payload;
125
- }
126
-
127
- as3cfModal.open( modal.selector );
128
-
129
- $( modal.selector ).find( '.single-file' ).show();
130
- $( modal.selector ).find( '.multiple-files' ).hide();
131
- if ( modal.isBulk ) {
132
- $( modal.selector ).find( '.single-file' ).hide();
133
- $( modal.selector ).find( '.multiple-files' ).show();
134
- }
135
- };
136
-
137
- /**
138
- * Close modal
139
- */
140
- modal.close = function () {
141
- as3cfModal.close( modal.selector );
142
- };
143
-
144
- /**
145
- * Set the isBulk flag
146
- */
147
- modal.setBulk = function ( isBulk ) {
148
- modal.isBulk = isBulk;
149
- };
150
-
151
- /**
152
- * Create the loading state
153
- */
154
- modal.startLoading = function () {
155
- $( modal.selector + ' [data-find-replace]' ).prop( 'disabled', true ).siblings( '.spinner' ).css( 'visibility', 'visible' ).show();
156
- };
157
-
158
- /**
159
- * Remove the loading state
160
- */
161
- modal.stopLoading = function () {
162
- $( modal.selector + ' [data-find-replace]' ).prop( 'disabled', false ).siblings( '.spinner' ).css( 'visibility', 'hidden' ).hide();
163
- };
164
-
165
- // Setup click handlers
166
- $( document ).ready( function () {
167
-
168
- $( 'body' ).on( 'click', modal.selector + ' [data-find-replace]', function ( e ) {
169
- var findAndReplace = $( this ).data( 'find-replace' );
170
-
171
- if ( !modal.link ) {
172
- // If there is no link set then this must be an AJAX
173
- // request so trigger an event instead
174
- $( modal.selector ).trigger( 'as3cf-find-and-replace', [ findAndReplace, modal.payload ] );
175
- return;
176
- }
177
-
178
- if ( findAndReplace ) {
179
- modal.link += '&find_and_replace=1';
180
- }
181
-
182
- modal.startLoading();
183
-
184
- window.location = modal.link;
185
- } );
186
-
187
- $( 'body' ).on( 'as3cf-modal-close', function ( e ) {
188
- modal.isBulk = false;
189
- modal.link = null;
190
- modal.payload = {};
191
- } );
192
-
193
- } );
194
-
195
- return modal;
196
-
197
- })( jQuery, as3cfModal );
198
-
199
-
1
  var as3cfModal = (function ( $ ) {
2
 
3
  var modal = {
4
+ prefix: 'as3cf',
5
+ loading: false
6
  };
7
 
8
  var modals = {};
10
  /**
11
  * Target to key
12
  *
13
+ * @param {string} target
14
  *
15
+ * @return {string}
16
  */
17
  function targetToKey( target ) {
18
  return target.replace( /[^a-z]/g, '' );
21
  /**
22
  * Open modal
23
  *
24
+ * @param {string} target
25
+ * @param {function} callback
26
+ * @param {string} customClass
27
  */
28
+ modal.open = function ( target, callback, customClass ) {
29
  var key = targetToKey( target );
30
 
31
  // Overlay
43
  }
44
  $modal.data( 'as3cf-modal-target', target ).append( modals[ key ] );
45
 
46
+ if ( undefined !== customClass ) {
47
+ $modal.addClass( customClass );
48
+ }
49
+
50
  if ( 'function' === typeof callback ) {
51
  callback( target );
52
  }
64
  /**
65
  * Close modal
66
  *
67
+ * @param {function} callback
68
  */
69
  modal.close = function ( callback ) {
70
+ if ( modal.loading ) {
71
+ return;
72
+ }
73
+
74
  var target = $( '#as3cf-modal' ).data( 'as3cf-modal-target' );
75
 
76
  $( '#as3cf-overlay' ).fadeOut( 150, function () {
86
  $( 'body' ).trigger( 'as3cf-modal-close', [ target ] );
87
  };
88
 
89
+ /**
90
+ * Set loading state
91
+ *
92
+ * @param {bool} state
93
+ */
94
+ modal.setLoadingState = function ( state ) {
95
+ modal.loading = state;
96
+ };
97
+
98
  // Setup click handlers
99
  $( document ).ready( function () {
100
 
118
 
119
  return modal;
120
 
121
+ })( jQuery );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
assets/js/modal.min.js CHANGED
@@ -1 +1 @@
1
- var as3cfModal=function(a){function b(a){return a.replace(/[^a-z]/g,"")}var c={prefix:"as3cf"},d={};return c.open=function(c,e){var f=b(c);a("body").append('<div id="as3cf-overlay"></div>');var g=a("#as3cf-overlay");g.append('<div id="as3cf-modal"><span class="close-as3cf-modal">×</span></div>');var h=a("#as3cf-modal");if(void 0===d[f]){var i=a(c);d[f]=i.clone(!0).css("display","block"),i.remove()}h.data("as3cf-modal-target",c).append(d[f]),"function"==typeof e&&e(c),a("body").addClass("as3cf-modal-open"),g.fadeIn(150),h.fadeIn(150),a("body").trigger("as3cf-modal-open",[c])},c.close=function(b){var c=a("#as3cf-modal").data("as3cf-modal-target");a("#as3cf-overlay").fadeOut(150,function(){"function"==typeof b&&b(c),a("body").removeClass("as3cf-modal-open"),a(this).remove()}),a("body").trigger("as3cf-modal-close",[c])},a(document).ready(function(){a("body").on("click","[data-as3cf-modal]",function(b){b.preventDefault(),c.open(a(this).data("as3cf-modal")+"."+c.prefix)}),a("body").on("click","#as3cf-overlay, .close-as3cf-modal",function(a){return a.preventDefault(),a.target!==this?!1:void c.close()})}),c}(jQuery),as3cfFindAndReplaceModal=function(a,b){var c={selector:".as3cf-find-replace-container",isBulk:!1,link:null,payload:{}};return c.open=function(d,e){"undefined"!=typeof d&&(c.link=d),"undefined"!=typeof e&&(c.payload=e),b.open(c.selector),a(c.selector).find(".single-file").show(),a(c.selector).find(".multiple-files").hide(),c.isBulk&&(a(c.selector).find(".single-file").hide(),a(c.selector).find(".multiple-files").show())},c.close=function(){b.close(c.selector)},c.setBulk=function(a){c.isBulk=a},c.startLoading=function(){a(c.selector+" [data-find-replace]").prop("disabled",!0).siblings(".spinner").css("visibility","visible").show()},c.stopLoading=function(){a(c.selector+" [data-find-replace]").prop("disabled",!1).siblings(".spinner").css("visibility","hidden").hide()},a(document).ready(function(){a("body").on("click",c.selector+" [data-find-replace]",function(){var b=a(this).data("find-replace");return c.link?(b&&(c.link+="&find_and_replace=1"),c.startLoading(),void(window.location=c.link)):void a(c.selector).trigger("as3cf-find-and-replace",[b,c.payload])}),a("body").on("as3cf-modal-close",function(){c.isBulk=!1,c.link=null,c.payload={}})}),c}(jQuery,as3cfModal);
1
+ var as3cfModal=function(a){function b(a){return a.replace(/[^a-z]/g,"")}var c={prefix:"as3cf",loading:!1},d={};return c.open=function(c,e,f){var g=b(c);a("body").append('<div id="as3cf-overlay"></div>');var h=a("#as3cf-overlay");h.append('<div id="as3cf-modal"><span class="close-as3cf-modal">×</span></div>');var i=a("#as3cf-modal");if(void 0===d[g]){var j=a(c);d[g]=j.clone(!0).css("display","block"),j.remove()}i.data("as3cf-modal-target",c).append(d[g]),void 0!==f&&i.addClass(f),"function"==typeof e&&e(c),a("body").addClass("as3cf-modal-open"),h.fadeIn(150),i.fadeIn(150),a("body").trigger("as3cf-modal-open",[c])},c.close=function(b){if(!c.loading){var d=a("#as3cf-modal").data("as3cf-modal-target");a("#as3cf-overlay").fadeOut(150,function(){"function"==typeof b&&b(d),a("body").removeClass("as3cf-modal-open"),a(this).remove()}),a("body").trigger("as3cf-modal-close",[d])}},c.setLoadingState=function(a){c.loading=a},a(document).ready(function(){a("body").on("click","[data-as3cf-modal]",function(b){b.preventDefault(),c.open(a(this).data("as3cf-modal")+"."+c.prefix)}),a("body").on("click","#as3cf-overlay, .close-as3cf-modal",function(a){return a.preventDefault(),a.target!==this?!1:void c.close()})}),c}(jQuery);
assets/js/script.js CHANGED
@@ -358,6 +358,9 @@
358
  savedSettings[ id ] = serializedForm( id );
359
  }
360
 
 
 
 
361
  $activeBucket.text( bucket );
362
  $manualBucketForm.find( '.as3cf-bucket-name' ).val( bucket );
363
  $( '#' + as3cfModal.prefix + '-bucket' ).val( bucket );
@@ -627,6 +630,16 @@
627
  generateUrlPreview();
628
  } );
629
 
 
 
 
 
 
 
 
 
 
 
630
  // Bucket select
631
  // --------------------
632
 
358
  savedSettings[ id ] = serializedForm( id );
359
  }
360
 
361
+ // Remove previous permission errors
362
+ $( '.as3cf-error.fatal' ).hide();
363
+
364
  $activeBucket.text( bucket );
365
  $manualBucketForm.find( '.as3cf-bucket-name' ).val( bucket );
366
  $( '#' + as3cfModal.prefix + '-bucket' ).val( bucket );
630
  generateUrlPreview();
631
  } );
632
 
633
+ // Don't allow 'enter' key to submit form on text input settings
634
+ $( '.as3cf-setting input[type="text"]' ).keypress( function( event ) {
635
+ if ( 13 === event.which ) {
636
+ event.preventDefault();
637
+
638
+ return false;
639
+ }
640
+
641
+ } );
642
+
643
  // Bucket select
644
  // --------------------
645
 
assets/js/script.min.js CHANGED
@@ -1 +1 @@
1
- !function(a,b){function c(b){return a("#"+b+" .as3cf-main-settings form").find("input:not(.no-compare)").serialize()}function d(b){var c=a("#"+b),d=c.find("input[type=checkbox]");c.toggleClass("on").find("span").toggleClass("checked");var e=c.find("span.on").hasClass("checked");d.attr("checked",e).trigger("change")}function e(){a(".as3cf-url-preview").html("Generating...");var b={_nonce:as3cf.nonces.get_url_preview};a.each(a("#tab-"+as3cf.tabs.defaultTab+" .as3cf-main-settings form").serializeArray(),function(c,d){var e=d.name,f=d.value;e=e.replace("[]",""),b[e]=void 0===b[e]?f:a.isArray(b[e])?b[e].concat(f):[b[e],f]}),b.action="as3cf-get-url-preview",a.ajax({url:ajaxurl,type:"POST",dataType:"JSON",data:b,error:function(a,b,c){alert(as3cf.strings.get_url_preview_error+c)},success:function(b){"undefined"!=typeof b.success?a(".as3cf-url-preview").html(b.url):alert(as3cf.strings.get_url_preview_error+b.error)}})}function f(){as3cf.buckets.bucketSelectLock=!1}var g,h={},i=/[^a-z0-9.-]/,j=a(".as3cf-tab");as3cf.tabs={defaultTab:"media",toggle:function(c,d){j.hide(),g=a("#tab-"+c),g.show(),a(".nav-tab").removeClass("nav-tab-active"),a('a.nav-tab[data-tab="'+c+'"]').addClass("nav-tab-active"),a(".aws-main").attr("data-tab",c),g.attr("data-prefix")&&(b.prefix=g.attr("data-prefix")),d||a(".as3cf-updated").removeClass("show")}},as3cf.buckets={validLength:3,bucketSelectLock:!1,loadList:function(c){"undefined"==typeof c&&(c=!1);var d=a(".as3cf-bucket-container."+b.prefix+" .as3cf-bucket-list"),e=a("#"+b.prefix+"-bucket").val();if(!1===c&&d.find("li").length>1)return a(".as3cf-bucket-list a").removeClass("selected"),a('.as3cf-bucket-list a[data-bucket="'+e+'"]').addClass("selected"),void this.scrollToSelected();d.html('<li class="loading">'+d.attr("data-working")+"</li>");var f={action:b.prefix+"-get-buckets",_nonce:window[b.prefix.replace(/-/g,"_")].nonces.get_buckets},g=this;a.ajax({url:ajaxurl,type:"POST",dataType:"JSON",data:f,error:function(a,b,c){d.html(""),g.showError(as3cf.strings.get_buckets_error,c,"as3cf-bucket-select")},success:function(b){d.html(""),"undefined"!=typeof b.success?(a(".as3cf-bucket-error").hide(),a(b.buckets).each(function(a,b){var c=b.Name===e?"selected":"";d.append('<li><a class="'+c+'" href="#" data-bucket="'+b.Name+'"><span class="bucket"><span class="dashicons dashicons-portfolio"></span> '+b.Name+'</span><span class="spinner"></span></span></a></li>')}),g.scrollToSelected()):g.showError(as3cf.strings.get_buckets_error,b.error,"as3cf-bucket-select")}})},scrollToSelected:function(){if(a(".as3cf-bucket-list a.selected").length){var b=a("ul.as3cf-bucket-list li").first().position().top+150;a(".as3cf-bucket-list").animate({scrollTop:a("ul.as3cf-bucket-list li a.selected").position().top-b})}},resetModal:function(){var c=a(".as3cf-bucket-container."+b.prefix);!1===g.hasClass("as3cf-has-bucket")||"manual"===a("#"+b.prefix+"-bucket-select").val()?(c.find(".as3cf-bucket-manual").show().siblings().hide(),c.find(".bucket-actions.manual").show().siblings(".bucket-actions").hide()):(c.find(".as3cf-bucket-select").show().siblings().hide(),c.find(".bucket-actions.select").show().siblings(".bucket-actions").hide(),this.loadList()),c.find(".as3cf-bucket-error").hide();var d=a("#"+b.prefix+"-bucket").val();c.find(".as3cf-bucket-manual .as3cf-bucket-name").val(d),this.bucketSelectLock=!1},saveManual:function(){var c=a(".as3cf-bucket-container."+b.prefix+" .as3cf-manual-save-bucket-form"),d=c.find(".as3cf-bucket-name"),e=c.find("button[type=submit]"),f=d.val(),h=e.first().text();if(f===a("#"+b.prefix+"-active-bucket").text())return a(".as3cf-bucket-error").hide(),g.addClass("as3cf-has-bucket"),void b.close();a(".as3cf-bucket-error").hide(),e.text(e.attr("data-working")),e.prop("disabled",!0);var i={action:b.prefix+"-manual-save-bucket",bucket_name:f,_nonce:window[b.prefix.replace(/-/g,"_")].nonces.manual_bucket},j=this;a.ajax({url:ajaxurl,type:"POST",dataType:"JSON",data:i,error:function(a,b,c){e.text(h),j.showError(as3cf.strings.save_bucket_error,c,"as3cf-bucket-manual")},success:function(c){e.text(h),e.prop("disabled",!1),"undefined"!=typeof c.success?(j.set(f,c.region,c.can_write),a("#"+b.prefix+"-bucket-select").val("manual"),a(".as3cf-bucket-list a").removeClass("selected").filter('[data-bucket="'+f+'"]').addClass("selected")):j.showError(as3cf.strings.save_bucket_error,c.error,"as3cf-bucket-manual")}})},saveSelected:function(c){var d=a(".as3cf-bucket-list");if(!this.bucketSelectLock){if(this.bucketSelectLock=!0,c.hasClass("selected"))return g.addClass("as3cf-has-bucket"),void b.close();var e=a(".as3cf-bucket-list a.selected").attr("data-bucket");a(".as3cf-bucket-list a").removeClass("selected"),c.addClass("selected"),d.addClass("saving"),c.find(".spinner").show().css("visibility","visible");var f=c.attr("data-bucket"),h={action:b.prefix+"-save-bucket",bucket_name:f,_nonce:window[b.prefix.replace(/-/g,"_")].nonces.save_bucket},i=this;a.ajax({url:ajaxurl,type:"POST",dataType:"JSON",data:h,error:function(b,c,f){d.removeClass("saving"),i.showError(as3cf.strings.save_bucket_error,f,"as3cf-bucket-select"),a(".as3cf-bucket-list a").removeClass("selected"),a('.as3cf-bucket-list a[data-bucket="'+e+'"]').addClass("selected")},success:function(g){c.find(".spinner").hide().css("visibility","hidden"),d.removeClass("saving"),"undefined"!=typeof g.success?(i.set(f,g.region,g.can_write),a("#"+b.prefix+"-bucket-select").val("")):(i.showError(as3cf.strings.save_bucket_error,g.error,"as3cf-bucket-select"),a(".as3cf-bucket-list a").removeClass("selected"),a('.as3cf-bucket-list a[data-bucket="'+e+'"]').addClass("selected"))}})}},disabledButtons:function(){if(0!==a(".as3cf-bucket-container."+b.prefix+" .as3cf-create-bucket-form").length){var c=a(".as3cf-bucket-container."+b.prefix+" .as3cf-create-bucket-form"),d=a(".as3cf-bucket-container."+b.prefix+" .as3cf-manual-save-bucket-form");c.find(".as3cf-bucket-name").val().length<3?c.find("button[type=submit]").attr("disabled",!0):c.find("button[type=submit]").attr("disabled",!1),d.find(".as3cf-bucket-name").val().length<3?d.find("button[type=submit]").attr("disabled",!0):d.find("button[type=submit]").attr("disabled",!1)}},showError:function(b,c,d){var e=a(".as3cf-bucket-container").children(":visible"),f=e.find(".as3cf-bucket-error");d="undefined"==typeof d?null:d,(!d||e.hasClass(d))&&(f.find("span.title").html(b+" &mdash;"),f.find("span.message").html(c),f.show(),this.bucketSelectLock=!1)},set:function(i,j,k){var l=a(".as3cf-bucket-container."+b.prefix+" .as3cf-manual-save-bucket-form"),m=a("#"+b.prefix+"-active-bucket");if("as3cf"===b.prefix&&""===m.text()){d("copy-to-s3-wrap"),d("serve-from-s3-wrap");var n=g.attr("id");h[n]=c(n)}m.text(i),l.find(".as3cf-bucket-name").val(i),a("#"+b.prefix+"-bucket").val(i),a("#"+b.prefix+"-region").val(j),a(".updated").not(".as3cf-notice").show(),g.addClass("as3cf-has-bucket"),g.find(".as3cf-can-write-error").toggle(!k),g.find(".as3cf-bucket-error").hide(),"as3cf"===b.prefix&&e(),b.close(f)},create:function(){var c=a(".as3cf-bucket-container."+b.prefix+" .as3cf-create-bucket-form"),d=c.find(".as3cf-bucket-name"),e=c.find(".bucket-create-region"),f=c.find("button[type=submit]"),g=d.val(),h=f.text();a(".as3cf-bucket-error").hide(),f.text(f.attr("data-working")),f.prop("disabled",!0);var i={action:b.prefix+"-create-bucket",bucket_name:g,_nonce:window[b.prefix.replace(/-/g,"_")].nonces.create_bucket};e.val()&&(i.region=e.val());var j=this;a.ajax({url:ajaxurl,type:"POST",dataType:"JSON",data:i,error:function(a,b,c){f.text(h),j.showError(as3cf.strings.create_bucket_error,c,"as3cf-bucket-create")},success:function(b){f.text(h),f.prop("disabled",!1),"undefined"!=typeof b.success?(j.set(g,b.region,b.can_write),a(".as3cf-bucket-select-region").hide(),a(".as3cf-bucket-select-region").removeAttr("selected"),d.val(""),f.attr("disabled",!0)):j.showError(as3cf.strings.create_bucket_error,b.error,"as3cf-bucket-create")}})},isValidName:function(a){return a.length<3||a.length>63?!1:!0===i.test(a)?!1:!0},updateNameNotice:function(b){var c=null;!0===i.test(b)?c=as3cf.strings.create_bucket_invalid_chars:b.length<3?c=as3cf.strings.create_bucket_name_short:b.length>63&&(c=as3cf.strings.create_bucket_name_long),a(".as3cf-invalid-bucket-name").html(c&&b.length>0?c:"")}},a(document).ready(function(){var f=a(".wrap.aws-main .nav-tab-wrapper");if(a(".aws-compatibility-notice, div.updated, div.error, div.notice").not(".below-h2, .inline").insertAfter(f),window.location.hash){var i=window.location.hash.substring(1);as3cf.tabs.toggle(i,!0)}else g=a("#tab-"+as3cf.tabs.defaultTab),a(".aws-main").attr("data-tab",as3cf.tabs.defaultTab);a(".aws-main").on("click",".nav-tab",function(b){if(b.preventDefault(),!a(this).hasClass("nav-tab-active")){var c=a(this).attr("data-tab");as3cf.tabs.toggle(c),"media"===c?(window.location.hash="","function"==typeof window.history.replaceState&&"#"===window.location.href.slice(-1)&&history.replaceState({},"",window.location.href.slice(0,-1))):window.location.hash=c}}),j.length&&j.each(function(a,b){h[b.id]=c(b.id)}),a(window).on("beforeunload.as3cf-settings",function(){if(!a.isEmptyObject(h)){var b=g.attr("id");return c(b)!==h[b]?as3cf.strings.save_alert:void 0}}),a(document).on("submit",".as3cf-main-settings form",function(){a(window).off("beforeunload.as3cf-settings")}),a(".as3cf-switch").on("click",function(){a(this).hasClass("disabled")||d(a(this).attr("id"))}),j.on("change",".sub-toggle",function(){var b=a(this).attr("id");a(".as3cf-setting."+b).toggleClass("hide")}),a(".as3cf-domain").on("change",'input[type="radio"]',function(){var b=a(this).closest('input:radio[name="domain"]:checked'),c=b.val(),d=a(this).parents(".as3cf-domain").find(".as3cf-setting.cloudfront"),e="cloudfront"===c;d.toggleClass("hide",!e)}),a(".as3cf-ssl").on("change",'input[type="radio"]',function(){var b=a('input:radio[name="ssl"]:checked').val();if("https"===b){var c=a('input:radio[name="domain"]:checked').val();"subdomain"===c&&a('input[name="domain"][value="path"]').attr("checked",!0),a(".subdomain-wrap input").attr("disabled",!0),a(".subdomain-wrap").addClass("disabled")}else a(".subdomain-wrap input").removeAttr("disabled"),a(".subdomain-wrap").removeClass("disabled")}),a(".url-preview").on("change","input",function(){e()}),a("#tab-media > .as3cf-bucket-error").detach().insertAfter(".as3cf-bucket-container h3"),a("body").on("click",".bucket-action-manual",function(c){c.preventDefault(),a(".as3cf-bucket-container."+b.prefix+" .as3cf-bucket-manual").show().siblings().hide()}),a("body").on("click",".bucket-action-browse",function(c){c.preventDefault(),a(".as3cf-bucket-container."+b.prefix+" .as3cf-bucket-select").show().siblings().hide(),as3cf.buckets.loadList()}),a("body").on("click",".bucket-action-create",function(c){c.preventDefault(),a(".as3cf-bucket-name").val(""),a(".as3cf-invalid-bucket-name").html(""),a(".as3cf-bucket-container."+b.prefix+" .as3cf-bucket-create").show().siblings().hide()}),a("body").on("click",".bucket-action-cancel",function(a){a.preventDefault(),as3cf.buckets.resetModal()}),a("body").on("click",".bucket-action-save",function(a){a.preventDefault(),as3cf.buckets.saveManual()}),a("body").on("click",'.as3cf-create-bucket-form button[type="submit"]',function(a){a.preventDefault(),as3cf.buckets.create()}),a("body").on("click",".bucket-action-refresh",function(a){a.preventDefault(),as3cf.buckets.loadList(!0)}),a("body").on("click",".as3cf-bucket-list a",function(b){b.preventDefault(),as3cf.buckets.saveSelected(a(this))}),a(".as3cf-bucket-container").on("click","a.js-link",function(b){return b.preventDefault(),window.open(a(this).attr("href")),!1}),a("body").on("as3cf-modal-open",function(c,d){if(".as3cf-bucket-container."+b.prefix===d){as3cf.buckets.resetModal();var e=a(".as3cf-bucket-manual h3").data("modal-title");a(".as3cf-bucket-manual h3").text(e),as3cf.buckets.disabledButtons()}}),as3cf.buckets.disabledButtons(),a("body").on("input keyup",".as3cf-create-bucket-form .as3cf-bucket-name",function(){var c=a(this).val(),d=a(".as3cf-bucket-container."+b.prefix+" .as3cf-create-bucket-form");as3cf.buckets.isValidName(c)?d.find("button[type=submit]").removeAttr("disabled"):d.find("button[type=submit]").attr("disabled",!0),as3cf.buckets.updateNameNotice(c)}),a("body").on("input keyup",".as3cf-manual-save-bucket-form .as3cf-bucket-name",function(){var c=a(".as3cf-bucket-container."+b.prefix+" .as3cf-manual-save-bucket-form");c.find(".as3cf-bucket-name").val().length<as3cf.buckets.validLength?c.find("button[type=submit]").attr("disabled",!0):c.find("button[type=submit]").removeAttr("disabled")})})}(jQuery,as3cfModal);
1
+ !function(a,b){function c(b){return a("#"+b+" .as3cf-main-settings form").find("input:not(.no-compare)").serialize()}function d(b){var c=a("#"+b),d=c.find("input[type=checkbox]");c.toggleClass("on").find("span").toggleClass("checked");var e=c.find("span.on").hasClass("checked");d.attr("checked",e).trigger("change")}function e(){a(".as3cf-url-preview").html("Generating...");var b={_nonce:as3cf.nonces.get_url_preview};a.each(a("#tab-"+as3cf.tabs.defaultTab+" .as3cf-main-settings form").serializeArray(),function(c,d){var e=d.name,f=d.value;e=e.replace("[]",""),b[e]=void 0===b[e]?f:a.isArray(b[e])?b[e].concat(f):[b[e],f]}),b.action="as3cf-get-url-preview",a.ajax({url:ajaxurl,type:"POST",dataType:"JSON",data:b,error:function(a,b,c){alert(as3cf.strings.get_url_preview_error+c)},success:function(b){"undefined"!=typeof b.success?a(".as3cf-url-preview").html(b.url):alert(as3cf.strings.get_url_preview_error+b.error)}})}function f(){as3cf.buckets.bucketSelectLock=!1}var g,h={},i=/[^a-z0-9.-]/,j=a(".as3cf-tab");as3cf.tabs={defaultTab:"media",toggle:function(c,d){j.hide(),g=a("#tab-"+c),g.show(),a(".nav-tab").removeClass("nav-tab-active"),a('a.nav-tab[data-tab="'+c+'"]').addClass("nav-tab-active"),a(".aws-main").attr("data-tab",c),g.attr("data-prefix")&&(b.prefix=g.attr("data-prefix")),d||a(".as3cf-updated").removeClass("show")}},as3cf.buckets={validLength:3,bucketSelectLock:!1,loadList:function(c){"undefined"==typeof c&&(c=!1);var d=a(".as3cf-bucket-container."+b.prefix+" .as3cf-bucket-list"),e=a("#"+b.prefix+"-bucket").val();if(!1===c&&d.find("li").length>1)return a(".as3cf-bucket-list a").removeClass("selected"),a('.as3cf-bucket-list a[data-bucket="'+e+'"]').addClass("selected"),void this.scrollToSelected();d.html('<li class="loading">'+d.attr("data-working")+"</li>");var f={action:b.prefix+"-get-buckets",_nonce:window[b.prefix.replace(/-/g,"_")].nonces.get_buckets},g=this;a.ajax({url:ajaxurl,type:"POST",dataType:"JSON",data:f,error:function(a,b,c){d.html(""),g.showError(as3cf.strings.get_buckets_error,c,"as3cf-bucket-select")},success:function(b){d.html(""),"undefined"!=typeof b.success?(a(".as3cf-bucket-error").hide(),a(b.buckets).each(function(a,b){var c=b.Name===e?"selected":"";d.append('<li><a class="'+c+'" href="#" data-bucket="'+b.Name+'"><span class="bucket"><span class="dashicons dashicons-portfolio"></span> '+b.Name+'</span><span class="spinner"></span></span></a></li>')}),g.scrollToSelected()):g.showError(as3cf.strings.get_buckets_error,b.error,"as3cf-bucket-select")}})},scrollToSelected:function(){if(a(".as3cf-bucket-list a.selected").length){var b=a("ul.as3cf-bucket-list li").first().position().top+150;a(".as3cf-bucket-list").animate({scrollTop:a("ul.as3cf-bucket-list li a.selected").position().top-b})}},resetModal:function(){var c=a(".as3cf-bucket-container."+b.prefix);!1===g.hasClass("as3cf-has-bucket")||"manual"===a("#"+b.prefix+"-bucket-select").val()?(c.find(".as3cf-bucket-manual").show().siblings().hide(),c.find(".bucket-actions.manual").show().siblings(".bucket-actions").hide()):(c.find(".as3cf-bucket-select").show().siblings().hide(),c.find(".bucket-actions.select").show().siblings(".bucket-actions").hide(),this.loadList()),c.find(".as3cf-bucket-error").hide();var d=a("#"+b.prefix+"-bucket").val();c.find(".as3cf-bucket-manual .as3cf-bucket-name").val(d),this.bucketSelectLock=!1},saveManual:function(){var c=a(".as3cf-bucket-container."+b.prefix+" .as3cf-manual-save-bucket-form"),d=c.find(".as3cf-bucket-name"),e=c.find("button[type=submit]"),f=d.val(),h=e.first().text();if(f===a("#"+b.prefix+"-active-bucket").text())return a(".as3cf-bucket-error").hide(),g.addClass("as3cf-has-bucket"),void b.close();a(".as3cf-bucket-error").hide(),e.text(e.attr("data-working")),e.prop("disabled",!0);var i={action:b.prefix+"-manual-save-bucket",bucket_name:f,_nonce:window[b.prefix.replace(/-/g,"_")].nonces.manual_bucket},j=this;a.ajax({url:ajaxurl,type:"POST",dataType:"JSON",data:i,error:function(a,b,c){e.text(h),j.showError(as3cf.strings.save_bucket_error,c,"as3cf-bucket-manual")},success:function(c){e.text(h),e.prop("disabled",!1),"undefined"!=typeof c.success?(j.set(f,c.region,c.can_write),a("#"+b.prefix+"-bucket-select").val("manual"),a(".as3cf-bucket-list a").removeClass("selected").filter('[data-bucket="'+f+'"]').addClass("selected")):j.showError(as3cf.strings.save_bucket_error,c.error,"as3cf-bucket-manual")}})},saveSelected:function(c){var d=a(".as3cf-bucket-list");if(!this.bucketSelectLock){if(this.bucketSelectLock=!0,c.hasClass("selected"))return g.addClass("as3cf-has-bucket"),void b.close();var e=a(".as3cf-bucket-list a.selected").attr("data-bucket");a(".as3cf-bucket-list a").removeClass("selected"),c.addClass("selected"),d.addClass("saving"),c.find(".spinner").show().css("visibility","visible");var f=c.attr("data-bucket"),h={action:b.prefix+"-save-bucket",bucket_name:f,_nonce:window[b.prefix.replace(/-/g,"_")].nonces.save_bucket},i=this;a.ajax({url:ajaxurl,type:"POST",dataType:"JSON",data:h,error:function(b,c,f){d.removeClass("saving"),i.showError(as3cf.strings.save_bucket_error,f,"as3cf-bucket-select"),a(".as3cf-bucket-list a").removeClass("selected"),a('.as3cf-bucket-list a[data-bucket="'+e+'"]').addClass("selected")},success:function(g){c.find(".spinner").hide().css("visibility","hidden"),d.removeClass("saving"),"undefined"!=typeof g.success?(i.set(f,g.region,g.can_write),a("#"+b.prefix+"-bucket-select").val("")):(i.showError(as3cf.strings.save_bucket_error,g.error,"as3cf-bucket-select"),a(".as3cf-bucket-list a").removeClass("selected"),a('.as3cf-bucket-list a[data-bucket="'+e+'"]').addClass("selected"))}})}},disabledButtons:function(){if(0!==a(".as3cf-bucket-container."+b.prefix+" .as3cf-create-bucket-form").length){var c=a(".as3cf-bucket-container."+b.prefix+" .as3cf-create-bucket-form"),d=a(".as3cf-bucket-container."+b.prefix+" .as3cf-manual-save-bucket-form");c.find(".as3cf-bucket-name").val().length<3?c.find("button[type=submit]").attr("disabled",!0):c.find("button[type=submit]").attr("disabled",!1),d.find(".as3cf-bucket-name").val().length<3?d.find("button[type=submit]").attr("disabled",!0):d.find("button[type=submit]").attr("disabled",!1)}},showError:function(b,c,d){var e=a(".as3cf-bucket-container").children(":visible"),f=e.find(".as3cf-bucket-error");d="undefined"==typeof d?null:d,(!d||e.hasClass(d))&&(f.find("span.title").html(b+" &mdash;"),f.find("span.message").html(c),f.show(),this.bucketSelectLock=!1)},set:function(i,j,k){var l=a(".as3cf-bucket-container."+b.prefix+" .as3cf-manual-save-bucket-form"),m=a("#"+b.prefix+"-active-bucket");if("as3cf"===b.prefix&&""===m.text()){d("copy-to-s3-wrap"),d("serve-from-s3-wrap");var n=g.attr("id");h[n]=c(n)}a(".as3cf-error.fatal").hide(),m.text(i),l.find(".as3cf-bucket-name").val(i),a("#"+b.prefix+"-bucket").val(i),a("#"+b.prefix+"-region").val(j),a(".updated").not(".as3cf-notice").show(),g.addClass("as3cf-has-bucket"),g.find(".as3cf-can-write-error").toggle(!k),g.find(".as3cf-bucket-error").hide(),"as3cf"===b.prefix&&e(),b.close(f)},create:function(){var c=a(".as3cf-bucket-container."+b.prefix+" .as3cf-create-bucket-form"),d=c.find(".as3cf-bucket-name"),e=c.find(".bucket-create-region"),f=c.find("button[type=submit]"),g=d.val(),h=f.text();a(".as3cf-bucket-error").hide(),f.text(f.attr("data-working")),f.prop("disabled",!0);var i={action:b.prefix+"-create-bucket",bucket_name:g,_nonce:window[b.prefix.replace(/-/g,"_")].nonces.create_bucket};e.val()&&(i.region=e.val());var j=this;a.ajax({url:ajaxurl,type:"POST",dataType:"JSON",data:i,error:function(a,b,c){f.text(h),j.showError(as3cf.strings.create_bucket_error,c,"as3cf-bucket-create")},success:function(b){f.text(h),f.prop("disabled",!1),"undefined"!=typeof b.success?(j.set(g,b.region,b.can_write),a(".as3cf-bucket-select-region").hide(),a(".as3cf-bucket-select-region").removeAttr("selected"),d.val(""),f.attr("disabled",!0)):j.showError(as3cf.strings.create_bucket_error,b.error,"as3cf-bucket-create")}})},isValidName:function(a){return a.length<3||a.length>63?!1:!0===i.test(a)?!1:!0},updateNameNotice:function(b){var c=null;!0===i.test(b)?c=as3cf.strings.create_bucket_invalid_chars:b.length<3?c=as3cf.strings.create_bucket_name_short:b.length>63&&(c=as3cf.strings.create_bucket_name_long),a(".as3cf-invalid-bucket-name").html(c&&b.length>0?c:"")}},a(document).ready(function(){var f=a(".wrap.aws-main .nav-tab-wrapper");if(a(".aws-compatibility-notice, div.updated, div.error, div.notice").not(".below-h2, .inline").insertAfter(f),window.location.hash){var i=window.location.hash.substring(1);as3cf.tabs.toggle(i,!0)}else g=a("#tab-"+as3cf.tabs.defaultTab),a(".aws-main").attr("data-tab",as3cf.tabs.defaultTab);a(".aws-main").on("click",".nav-tab",function(b){if(b.preventDefault(),!a(this).hasClass("nav-tab-active")){var c=a(this).attr("data-tab");as3cf.tabs.toggle(c),"media"===c?(window.location.hash="","function"==typeof window.history.replaceState&&"#"===window.location.href.slice(-1)&&history.replaceState({},"",window.location.href.slice(0,-1))):window.location.hash=c}}),j.length&&j.each(function(a,b){h[b.id]=c(b.id)}),a(window).on("beforeunload.as3cf-settings",function(){if(!a.isEmptyObject(h)){var b=g.attr("id");return c(b)!==h[b]?as3cf.strings.save_alert:void 0}}),a(document).on("submit",".as3cf-main-settings form",function(){a(window).off("beforeunload.as3cf-settings")}),a(".as3cf-switch").on("click",function(){a(this).hasClass("disabled")||d(a(this).attr("id"))}),j.on("change",".sub-toggle",function(){var b=a(this).attr("id");a(".as3cf-setting."+b).toggleClass("hide")}),a(".as3cf-domain").on("change",'input[type="radio"]',function(){var b=a(this).closest('input:radio[name="domain"]:checked'),c=b.val(),d=a(this).parents(".as3cf-domain").find(".as3cf-setting.cloudfront"),e="cloudfront"===c;d.toggleClass("hide",!e)}),a(".as3cf-ssl").on("change",'input[type="radio"]',function(){var b=a('input:radio[name="ssl"]:checked').val();if("https"===b){var c=a('input:radio[name="domain"]:checked').val();"subdomain"===c&&a('input[name="domain"][value="path"]').attr("checked",!0),a(".subdomain-wrap input").attr("disabled",!0),a(".subdomain-wrap").addClass("disabled")}else a(".subdomain-wrap input").removeAttr("disabled"),a(".subdomain-wrap").removeClass("disabled")}),a(".url-preview").on("change","input",function(){e()}),a('.as3cf-setting input[type="text"]').keypress(function(a){return 13===a.which?(a.preventDefault(),!1):void 0}),a("#tab-media > .as3cf-bucket-error").detach().insertAfter(".as3cf-bucket-container h3"),a("body").on("click",".bucket-action-manual",function(c){c.preventDefault(),a(".as3cf-bucket-container."+b.prefix+" .as3cf-bucket-manual").show().siblings().hide()}),a("body").on("click",".bucket-action-browse",function(c){c.preventDefault(),a(".as3cf-bucket-container."+b.prefix+" .as3cf-bucket-select").show().siblings().hide(),as3cf.buckets.loadList()}),a("body").on("click",".bucket-action-create",function(c){c.preventDefault(),a(".as3cf-bucket-name").val(""),a(".as3cf-invalid-bucket-name").html(""),a(".as3cf-bucket-container."+b.prefix+" .as3cf-bucket-create").show().siblings().hide()}),a("body").on("click",".bucket-action-cancel",function(a){a.preventDefault(),as3cf.buckets.resetModal()}),a("body").on("click",".bucket-action-save",function(a){a.preventDefault(),as3cf.buckets.saveManual()}),a("body").on("click",'.as3cf-create-bucket-form button[type="submit"]',function(a){a.preventDefault(),as3cf.buckets.create()}),a("body").on("click",".bucket-action-refresh",function(a){a.preventDefault(),as3cf.buckets.loadList(!0)}),a("body").on("click",".as3cf-bucket-list a",function(b){b.preventDefault(),as3cf.buckets.saveSelected(a(this))}),a(".as3cf-bucket-container").on("click","a.js-link",function(b){return b.preventDefault(),window.open(a(this).attr("href")),!1}),a("body").on("as3cf-modal-open",function(c,d){if(".as3cf-bucket-container."+b.prefix===d){as3cf.buckets.resetModal();var e=a(".as3cf-bucket-manual h3").data("modal-title");a(".as3cf-bucket-manual h3").text(e),as3cf.buckets.disabledButtons()}}),as3cf.buckets.disabledButtons(),a("body").on("input keyup",".as3cf-create-bucket-form .as3cf-bucket-name",function(){var c=a(this).val(),d=a(".as3cf-bucket-container."+b.prefix+" .as3cf-create-bucket-form");as3cf.buckets.isValidName(c)?d.find("button[type=submit]").removeAttr("disabled"):d.find("button[type=submit]").attr("disabled",!0),as3cf.buckets.updateNameNotice(c)}),a("body").on("input keyup",".as3cf-manual-save-bucket-form .as3cf-bucket-name",function(){var c=a(".as3cf-bucket-container."+b.prefix+" .as3cf-manual-save-bucket-form");c.find(".as3cf-bucket-name").val().length<as3cf.buckets.validLength?c.find("button[type=submit]").attr("disabled",!0):c.find("button[type=submit]").removeAttr("disabled")})})}(jQuery,as3cfModal);
classes/amazon-s3-and-cloudfront.php CHANGED
@@ -43,7 +43,7 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
43
  protected $default_tab = '';
44
 
45
  /**
46
- * @var array
47
  */
48
  public $hook_suffix;
49
 
@@ -84,8 +84,8 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
84
  $this->plugin_title = __( 'Offload S3', 'as3cf' );
85
  $this->plugin_menu_title = __( 'S3 and CloudFront', 'as3cf' );
86
 
87
- // fire up the plugin upgrade checker
88
- new AS3CF_Upgrade( $this );
89
 
90
  add_action( 'aws_admin_menu', array( $this, 'admin_menu' ) );
91
  add_action( 'wp_ajax_as3cf-get-buckets', array( $this, 'ajax_get_buckets' ) );
@@ -107,6 +107,7 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
107
  add_filter( 'update_attached_file', array( $this, 'update_attached_file' ), 100, 2 );
108
  add_filter( 'get_attached_file', array( $this, 'get_attached_file' ), 10, 2 );
109
  add_filter( 'plugin_action_links', array( $this, 'plugin_actions_settings_link' ), 10, 2 );
 
110
 
111
  // include compatibility code for other plugins
112
  new AS3CF_Plugin_Compatibility( $this );
@@ -488,7 +489,7 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
488
  }
489
 
490
  // upload attachment to S3
491
- $this->upload_attachment_to_s3( $post_id, $data );
492
 
493
  return $data;
494
  }
@@ -503,10 +504,12 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
503
  * to cope with possible different regions
504
  * @param bool $remove_local_files
505
  *
506
- * @return array|WP_Error $s3object
507
  */
508
  function upload_attachment_to_s3( $post_id, $data = null, $file_path = null, $force_new_s3_client = false, $remove_local_files = true ) {
 
509
  if ( is_null( $data ) ) {
 
510
  $data = wp_get_attachment_metadata( $post_id, true );
511
  }
512
 
@@ -616,6 +619,26 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
616
  $file_paths = $this->get_attachment_file_paths( $post_id, true, $data );
617
  $additional_images = array();
618
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
619
  foreach ( $file_paths as $file_path ) {
620
  if ( ! in_array( $file_path, $files_to_remove ) ) {
621
  $additional_images[] = array(
@@ -624,6 +647,14 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
624
  );
625
 
626
  $files_to_remove[] = $file_path;
 
 
 
 
 
 
 
 
627
  }
628
  }
629
 
@@ -639,20 +670,43 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
639
  }
640
 
641
  if ( $remove_local_files ) {
642
- if ( $this->get_setting( 'remove-local-file' ) ) {
643
- if ( isset( $_POST['action'] ) && 'image-editor' == sanitize_key( $_POST['action'] ) && defined( 'DOING_AJAX' ) && DOING_AJAX ) {
644
- // remove original main image after edit
645
- $meta = get_post_meta( $post_id, '_wp_attachment_metadata', true );
646
- $original_file = trailingslashit( dirname( $file_path ) ) . basename( $meta['file'] );
647
- if ( file_exists( $original_file ) && ! in_array( $original_file, $files_to_remove ) ) {
648
- $files_to_remove[] = $original_file;
649
- }
650
- }
651
-
652
  $this->remove_local_files( $files_to_remove );
653
  }
654
  }
655
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
656
  return $s3object;
657
  }
658
 
@@ -676,6 +730,13 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
676
  return $pathinfo['dirname'] . '/' . $pathinfo['filename'] . $hidpi_suffix . '.' . $pathinfo['extension'];
677
  }
678
 
 
 
 
 
 
 
 
679
  function get_object_version_string( $post_id ) {
680
  if ( $this->get_setting( 'use-yearmonth-folders' ) ) {
681
  $date_format = 'dHis';
@@ -708,8 +769,15 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
708
  return null;
709
  }
710
 
711
- // Media files attached to a post use the post's date
712
- // to determine the folder path they are placed in
 
 
 
 
 
 
 
713
  function get_attachment_folder_time( $post_id ) {
714
  $time = current_time( 'timestamp' );
715
 
@@ -815,18 +883,39 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
815
  return get_post_meta( $post_id, 'amazonS3_info', true );
816
  }
817
 
 
 
 
 
 
818
  function is_plugin_setup() {
819
- return (bool) $this->get_setting( 'bucket' ) && ! is_wp_error( $this->aws->get_client() );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
820
  }
821
 
822
  /**
823
  * Generate a link to download a file from Amazon S3 using query string
824
  * authentication. This link is only valid for a limited amount of time.
825
  *
826
- * @param int $post_id Post ID of the attachment
827
- * @param int $expires Seconds for the link to live
828
- * @param string $size Size of the image to get
829
- * @param array $headers Header overrides for request
830
  *
831
  * @return mixed|void|WP_Error
832
  */
@@ -981,11 +1070,11 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
981
  /**
982
  * Get the url of the file from Amazon S3
983
  *
984
- * @param int $post_id Post ID of the attachment
985
- * @param int $expires Seconds for the link to live
986
- * @param string $size Size of the image to get
987
- * @param array $meta Pre retrieved _wp_attachment_metadata for the attachment
988
- * @param array $headers Header overrides for request
989
  *
990
  * @return bool|mixed|void|WP_Error
991
  */
@@ -1016,7 +1105,7 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
1016
 
1017
  $domain_bucket = $this->get_s3_url_domain( $s3object['bucket'], $region, $expires );
1018
 
1019
- if ( $size ) {
1020
  if ( is_null( $meta ) ) {
1021
  $meta = get_post_meta( $post_id, '_wp_attachment_metadata', true );
1022
  }
@@ -1027,23 +1116,20 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
1027
 
1028
  if ( ! is_null( $expires ) ) {
1029
  try {
1030
- $expires = time() + $expires;
1031
  $secure_url = $this->get_s3client( $region )->getObjectUrl( $s3object['bucket'], $s3object['key'], $expires, $headers );
 
 
1032
  }
1033
  catch ( Exception $e ) {
1034
  return new WP_Error( 'exception', $e->getMessage() );
1035
  }
1036
  }
1037
 
1038
- // encode file
1039
  $file = $this->encode_filename_in_path( $s3object['key'] );
 
1040
 
1041
- $url = $scheme . '://' . $domain_bucket . '/' . $file;
1042
- if ( isset( $secure_url ) ) {
1043
- $url .= substr( $secure_url, strpos( $secure_url, '?' ) );
1044
- }
1045
-
1046
- return apply_filters( 'as3cf_get_attachment_url', $url, $s3object, $post_id, $expires );
1047
  }
1048
 
1049
  /**
@@ -1410,8 +1496,12 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
1410
  * @param Amazon_Web_Services $aws
1411
  */
1412
  function admin_menu( $aws ) {
1413
- $this->hook_suffix = $aws->add_page( $this->get_plugin_page_title(), $this->plugin_menu_title, 'manage_options', $this->plugin_slug, array( $this, 'render_page' ) );
1414
- add_action( 'load-' . $this->hook_suffix , array( $this, 'plugin_load' ) );
 
 
 
 
1415
  }
1416
 
1417
  /**
@@ -1450,12 +1540,12 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
1450
  function get_bucket_region( $bucket ) {
1451
  try {
1452
  $region = $this->get_s3client()->getBucketLocation( array( 'Bucket' => $bucket ) );
1453
- }
1454
- catch ( Exception $e ) {
1455
- $error_msg = sprintf( __( 'There was an error attempting to get the region of the bucket %s: %s', 'as3cf' ), $bucket, $e->getMessage() );
1456
  error_log( $error_msg );
1457
 
1458
- return new WP_Error( 'exception', $e->getMessage() );
1459
  }
1460
 
1461
  $region = $this->translate_region( $region['Location'] );
@@ -1508,7 +1598,7 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
1508
  $region = strtolower( $region );
1509
 
1510
  switch ( $region ) {
1511
- case 'eu' :
1512
  $region = 'eu-west-1';
1513
  break;
1514
  }
@@ -1924,7 +2014,7 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
1924
  * Apply ACL to an attachment and associated files
1925
  *
1926
  * @param int $post_id
1927
- * @param object $s3object
1928
  * @param string $acl
1929
  */
1930
  function set_attachment_acl_on_s3( $post_id, $s3object, $acl ) {
@@ -2144,6 +2234,21 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
2144
  }
2145
  echo "\r\n\r\n";
2146
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2147
  echo 'Bucket: ';
2148
  echo $this->get_setting( 'bucket' );
2149
  echo "\r\n";
@@ -2215,7 +2320,7 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
2215
  /**
2216
  * Helper for displaying settings
2217
  *
2218
- * @param $key setting key
2219
  *
2220
  * @return string
2221
  */
@@ -2228,7 +2333,7 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
2228
  /**
2229
  * Helper to display plugin details
2230
  *
2231
- * @param $plugin_path
2232
  * @param string $suffix
2233
  */
2234
  function print_plugin_details( $plugin_path, $suffix = '' ) {
@@ -2317,6 +2422,32 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
2317
  return false;
2318
  }
2319
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2320
  /**
2321
  * Get all the table prefixes for the blogs in the site. MS compatible
2322
  *
@@ -2437,4 +2568,125 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
2437
 
2438
  return $message;
2439
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2440
  }
43
  protected $default_tab = '';
44
 
45
  /**
46
+ * @var string
47
  */
48
  public $hook_suffix;
49
 
84
  $this->plugin_title = __( 'Offload S3', 'as3cf' );
85
  $this->plugin_menu_title = __( 'S3 and CloudFront', 'as3cf' );
86
 
87
+ new AS3CF_Upgrade_Region_Meta( $this );
88
+ new AS3CF_Upgrade_File_Sizes( $this );
89
 
90
  add_action( 'aws_admin_menu', array( $this, 'admin_menu' ) );
91
  add_action( 'wp_ajax_as3cf-get-buckets', array( $this, 'ajax_get_buckets' ) );
107
  add_filter( 'update_attached_file', array( $this, 'update_attached_file' ), 100, 2 );
108
  add_filter( 'get_attached_file', array( $this, 'get_attached_file' ), 10, 2 );
109
  add_filter( 'plugin_action_links', array( $this, 'plugin_actions_settings_link' ), 10, 2 );
110
+ add_filter( 'pre_get_space_used', array( $this, 'multisite_get_spaced_used' ) );
111
 
112
  // include compatibility code for other plugins
113
  new AS3CF_Plugin_Compatibility( $this );
489
  }
490
 
491
  // upload attachment to S3
492
+ $data = $this->upload_attachment_to_s3( $post_id, $data );
493
 
494
  return $data;
495
  }
504
  * to cope with possible different regions
505
  * @param bool $remove_local_files
506
  *
507
+ * @return array|WP_Error $s3object|$meta If meta is supplied, return it. Else return S3 meta
508
  */
509
  function upload_attachment_to_s3( $post_id, $data = null, $file_path = null, $force_new_s3_client = false, $remove_local_files = true ) {
510
+ $return_metadata = true;
511
  if ( is_null( $data ) ) {
512
+ $return_metadata = false;
513
  $data = wp_get_attachment_metadata( $post_id, true );
514
  }
515
 
619
  $file_paths = $this->get_attachment_file_paths( $post_id, true, $data );
620
  $additional_images = array();
621
 
622
+ $filesize_total = 0;
623
+ $remove_local_files_setting = $this->get_setting( 'remove-local-file' );
624
+
625
+ if ( $remove_local_files_setting ) {
626
+ $bytes = filesize( $file_path );
627
+ if ( false !== $bytes ) {
628
+ // Store in the attachment meta data for use by WP
629
+ $data['filesize'] = $bytes;
630
+
631
+ if ( ! $return_metadata ) {
632
+ // Upload happening outside of 'wp_update_attachment_metadata' filter,
633
+ // So update metadata manually
634
+ update_post_meta( $post_id, '_wp_attachment_metadata', $data );
635
+ }
636
+
637
+ // Add to the file size total
638
+ $filesize_total += $bytes;
639
+ }
640
+ }
641
+
642
  foreach ( $file_paths as $file_path ) {
643
  if ( ! in_array( $file_path, $files_to_remove ) ) {
644
  $additional_images[] = array(
647
  );
648
 
649
  $files_to_remove[] = $file_path;
650
+
651
+ if ( $remove_local_files_setting ) {
652
+ // Record the file size for the additional image
653
+ $bytes = filesize( $file_path );
654
+ if ( false !== $bytes ) {
655
+ $filesize_total += $bytes;
656
+ }
657
+ }
658
  }
659
  }
660
 
670
  }
671
 
672
  if ( $remove_local_files ) {
673
+ if ( $remove_local_files_setting ) {
674
+ // Allow other functions to remove files after they have processed
675
+ $files_to_remove = apply_filters( 'as3cf_upload_attachment_local_files_to_remove', $files_to_remove, $post_id, $file_path );
676
+ // Remove duplicates
677
+ $files_to_remove = array_unique( $files_to_remove );
678
+
679
+ // Delete the files
 
 
 
680
  $this->remove_local_files( $files_to_remove );
681
  }
682
  }
683
 
684
+ // Store the file size in the attachment meta if we are removing local file
685
+ if ( $remove_local_files_setting ) {
686
+ if ( $filesize_total > 0 ) {
687
+ // Add the total file size for all image sizes
688
+ update_post_meta( $post_id, 'wpos3_filesize_total', $filesize_total );
689
+ }
690
+ } else {
691
+ if ( isset( $data['filesize'] ) ) {
692
+ // Make sure we don't have a cached file sizes in the meta
693
+ unset( $data['filesize'] );
694
+
695
+ if ( ! $return_metadata ) {
696
+ // Upload happening outside of 'wp_update_attachment_metadata' filter,
697
+ // So update metadata manually
698
+ update_post_meta( $post_id, '_wp_attachment_metadata', $data );
699
+ }
700
+
701
+ delete_post_meta( $post_id, 'wpos3_filesize_total' );
702
+ }
703
+ }
704
+
705
+ if ( $return_metadata ) {
706
+ // If the attachment metadata is supplied, return it
707
+ return $data;
708
+ }
709
+
710
  return $s3object;
711
  }
712
 
730
  return $pathinfo['dirname'] . '/' . $pathinfo['filename'] . $hidpi_suffix . '.' . $pathinfo['extension'];
731
  }
732
 
733
+ /**
734
+ * Get the object versioning string prefix
735
+ *
736
+ * @param int $post_id
737
+ *
738
+ * @return string
739
+ */
740
  function get_object_version_string( $post_id ) {
741
  if ( $this->get_setting( 'use-yearmonth-folders' ) ) {
742
  $date_format = 'dHis';
769
  return null;
770
  }
771
 
772
+ /**
773
+ * Get the time of attachment upload.
774
+ *
775
+ * Use post datetime if attached.
776
+ *
777
+ * @param int $post_id
778
+ *
779
+ * @return int|string
780
+ */
781
  function get_attachment_folder_time( $post_id ) {
782
  $time = current_time( 'timestamp' );
783
 
883
  return get_post_meta( $post_id, 'amazonS3_info', true );
884
  }
885
 
886
+ /**
887
+ * Check the plugin is correctly setup
888
+ *
889
+ * @return bool
890
+ */
891
  function is_plugin_setup() {
892
+ if ( is_wp_error( $this->aws->get_client() ) ) {
893
+ // AWS not configured
894
+ return false;
895
+ }
896
+
897
+ if ( false === (bool) $this->get_setting( 'bucket' ) ) {
898
+ // No bucket selected
899
+ return false;
900
+ }
901
+
902
+ if ( is_wp_error( $this->get_setting( 'region' ) ) ) {
903
+ // Region error when retrieving bucket location
904
+ return false;
905
+ }
906
+
907
+ // All good, let's do this
908
+ return true;
909
  }
910
 
911
  /**
912
  * Generate a link to download a file from Amazon S3 using query string
913
  * authentication. This link is only valid for a limited amount of time.
914
  *
915
+ * @param int $post_id Post ID of the attachment
916
+ * @param int|null $expires Seconds for the link to live
917
+ * @param string|null $size Size of the image to get
918
+ * @param array $headers Header overrides for request
919
  *
920
  * @return mixed|void|WP_Error
921
  */
1070
  /**
1071
  * Get the url of the file from Amazon S3
1072
  *
1073
+ * @param int $post_id Post ID of the attachment
1074
+ * @param int|null $expires Seconds for the link to live
1075
+ * @param string|null $size Size of the image to get
1076
+ * @param array|null $meta Pre retrieved _wp_attachment_metadata for the attachment
1077
+ * @param array $headers Header overrides for request
1078
  *
1079
  * @return bool|mixed|void|WP_Error
1080
  */
1105
 
1106
  $domain_bucket = $this->get_s3_url_domain( $s3object['bucket'], $region, $expires );
1107
 
1108
+ if ( ! is_null( $size ) ) {
1109
  if ( is_null( $meta ) ) {
1110
  $meta = get_post_meta( $post_id, '_wp_attachment_metadata', true );
1111
  }
1116
 
1117
  if ( ! is_null( $expires ) ) {
1118
  try {
1119
+ $expires = time() + $expires;
1120
  $secure_url = $this->get_s3client( $region )->getObjectUrl( $s3object['bucket'], $s3object['key'], $expires, $headers );
1121
+
1122
+ return apply_filters( 'as3cf_get_attachment_secure_url', $secure_url, $s3object, $post_id, $expires, $headers );
1123
  }
1124
  catch ( Exception $e ) {
1125
  return new WP_Error( 'exception', $e->getMessage() );
1126
  }
1127
  }
1128
 
 
1129
  $file = $this->encode_filename_in_path( $s3object['key'] );
1130
+ $url = $scheme . '://' . $domain_bucket . '/' . $file;
1131
 
1132
+ return apply_filters( 'as3cf_get_attachment_url', $url, $s3object, $post_id, $expires, $headers );
 
 
 
 
 
1133
  }
1134
 
1135
  /**
1496
  * @param Amazon_Web_Services $aws
1497
  */
1498
  function admin_menu( $aws ) {
1499
+ $hook_suffix = $aws->add_page( $this->get_plugin_page_title(), $this->plugin_menu_title, 'manage_options', $this->plugin_slug, array( $this, 'render_page' ) );
1500
+
1501
+ if ( false !== $hook_suffix ) {
1502
+ $this->hook_suffix = $hook_suffix;
1503
+ add_action( 'load-' . $this->hook_suffix, array( $this, 'plugin_load' ) );
1504
+ }
1505
  }
1506
 
1507
  /**
1540
  function get_bucket_region( $bucket ) {
1541
  try {
1542
  $region = $this->get_s3client()->getBucketLocation( array( 'Bucket' => $bucket ) );
1543
+ } catch ( Exception $e ) {
1544
+ $error_msg_title = '<strong>' . __( 'Error Getting Bucket Region', 'as3cf' ) . '</strong> &mdash;';
1545
+ $error_msg = sprintf( __( 'There was an error attempting to get the region of the bucket %s: %s', 'as3cf' ), $bucket, $e->getMessage() );
1546
  error_log( $error_msg );
1547
 
1548
+ return new WP_Error( 'exception', $error_msg_title . $error_msg );
1549
  }
1550
 
1551
  $region = $this->translate_region( $region['Location'] );
1598
  $region = strtolower( $region );
1599
 
1600
  switch ( $region ) {
1601
+ case 'eu':
1602
  $region = 'eu-west-1';
1603
  break;
1604
  }
2014
  * Apply ACL to an attachment and associated files
2015
  *
2016
  * @param int $post_id
2017
+ * @param array $s3object
2018
  * @param string $acl
2019
  */
2020
  function set_attachment_acl_on_s3( $post_id, $s3object, $acl ) {
2234
  }
2235
  echo "\r\n\r\n";
2236
 
2237
+ $media_counts = $this->diagnostic_media_counts();
2238
+
2239
+ echo 'Media Files: ';
2240
+ echo number_format_i18n( $media_counts['all'] );
2241
+ echo "\r\n";
2242
+
2243
+ echo 'Media Files on S3: ';
2244
+ echo number_format_i18n( $media_counts['s3'] );
2245
+ echo "\r\n";
2246
+
2247
+ echo 'Number of Image Sizes: ';
2248
+ $sizes = count( get_intermediate_image_sizes() );
2249
+ echo number_format_i18n( $sizes );
2250
+ echo "\r\n\r\n";
2251
+
2252
  echo 'Bucket: ';
2253
  echo $this->get_setting( 'bucket' );
2254
  echo "\r\n";
2320
  /**
2321
  * Helper for displaying settings
2322
  *
2323
+ * @param string $key setting key
2324
  *
2325
  * @return string
2326
  */
2333
  /**
2334
  * Helper to display plugin details
2335
  *
2336
+ * @param string $plugin_path
2337
  * @param string $suffix
2338
  */
2339
  function print_plugin_details( $plugin_path, $suffix = '' ) {
2422
  return false;
2423
  }
2424
 
2425
+ /**
2426
+ * Helper to switch to a Multisite blog
2427
+ * - If the site is MS
2428
+ * - If the blog is not the current blog defined
2429
+ *
2430
+ * @param $blog_id
2431
+ */
2432
+ function switch_to_blog( $blog_id ) {
2433
+ if ( is_multisite() && ! $this->is_current_blog( $blog_id ) ) {
2434
+ switch_to_blog( $blog_id );
2435
+ }
2436
+ }
2437
+
2438
+ /**
2439
+ * Helper to restore to the current Multisite blog
2440
+ * - If the site is MS
2441
+ * - If the blog is not the current blog defined
2442
+ *
2443
+ * @param $blog_id
2444
+ */
2445
+ function restore_current_blog( $blog_id ) {
2446
+ if ( is_multisite() && ! $this->is_current_blog( $blog_id ) ) {
2447
+ restore_current_blog();
2448
+ }
2449
+ }
2450
+
2451
  /**
2452
  * Get all the table prefixes for the blogs in the site. MS compatible
2453
  *
2568
 
2569
  return $message;
2570
  }
2571
+
2572
+ /**
2573
+ * Used to give a realistic total of storage space used on a Multisite subsite,
2574
+ * when there have been attachments uploaded to S3 but removed from server
2575
+ *
2576
+ * @param $space_used bool
2577
+ *
2578
+ * @return float|int
2579
+ */
2580
+ function multisite_get_spaced_used( $space_used ) {
2581
+ if ( false === ( $space_used = get_transient( 'wpos3_site_space_used' ) ) ) {
2582
+ global $wpdb;
2583
+
2584
+ // Sum the total file size (including image sizes) for all S3 attachments
2585
+ $sql = "SELECT SUM( meta_value ) AS bytes_total
2586
+ FROM {$wpdb->postmeta}
2587
+ WHERE meta_key = 'wpos3_filesize_total'";
2588
+
2589
+ $space_used = $wpdb->get_var( $sql );
2590
+
2591
+ // Get local upload sizes
2592
+ $upload_dir = wp_upload_dir();
2593
+ $space_used += get_dirsize( $upload_dir['basedir'] );
2594
+
2595
+ if ( $space_used > 0 ) {
2596
+ // Convert to bytes to MB
2597
+ $space_used = $space_used / 1024 / 1024;
2598
+ }
2599
+
2600
+ set_transient( 'wpos3_site_space_used', $space_used, HOUR_IN_SECONDS );
2601
+ }
2602
+
2603
+ return $space_used;
2604
+ }
2605
+
2606
+ /**
2607
+ * Memory exceeded
2608
+ *
2609
+ * Ensures the a process never exceeds 90% of the maximum WordPress memory.
2610
+ *
2611
+ * @param null|string $filter_name Name of filter to apply to the return
2612
+ *
2613
+ * @return bool
2614
+ */
2615
+ public function memory_exceeded( $filter_name = null ) {
2616
+ $current_memory = memory_get_usage( true );
2617
+ $memory_limit = ( intval( WP_MEMORY_LIMIT ) * 1024 * 1024 ) * 0.9; // 90% of max memory
2618
+ $return = false;
2619
+
2620
+ if ( $current_memory >= $memory_limit ) {
2621
+ $return = true;
2622
+ }
2623
+
2624
+ if ( is_null( $filter_name ) || ! is_string( $filter_name ) ) {
2625
+ return $return;
2626
+ }
2627
+
2628
+ return apply_filters( $filter_name, $return );
2629
+ }
2630
+
2631
+ /**
2632
+ * Count attachments on a site
2633
+ *
2634
+ * @param string $prefix
2635
+ * @param null|bool $uploaded_to_s3
2636
+ * null - All attachments
2637
+ * true - Attachments only uploaded to S3
2638
+ * false - Attachments not uploaded to S3
2639
+ *
2640
+ * @return null|string
2641
+ */
2642
+ public function count_attachments( $prefix, $uploaded_to_s3 = null ) {
2643
+ global $wpdb;
2644
+
2645
+ $sql = "SELECT COUNT(*)
2646
+ FROM `{$prefix}posts` p";
2647
+
2648
+ $where = "WHERE p.post_type = 'attachment'";
2649
+
2650
+ if ( ! is_null( $uploaded_to_s3 ) && is_bool( $uploaded_to_s3 ) ) {
2651
+ $sql .= " LEFT OUTER JOIN `{$prefix}postmeta` pm
2652
+ ON p.`ID` = pm.`post_id`
2653
+ AND pm.`meta_key` = 'amazonS3_info'";
2654
+
2655
+ $operator = $uploaded_to_s3 ? 'not ' : '';
2656
+ $where .= " AND pm.`post_id` is {$operator}null";
2657
+ }
2658
+
2659
+ $sql .= ' ' . $where;
2660
+
2661
+ return $wpdb->get_var( $sql );
2662
+ }
2663
+
2664
+ /**
2665
+ * Get the total attachment and total S3 attachment counts for the diagnostic log
2666
+ *
2667
+ * @return array
2668
+ */
2669
+ protected function diagnostic_media_counts() {
2670
+ if ( false === ( $attachment_counts = get_site_transient( 'wpos3_attachment_counts' ) ) ) {
2671
+ $table_prefixes = $this->get_all_blog_table_prefixes();
2672
+ $all_media = 0;
2673
+ $all_media_s3 = 0;
2674
+
2675
+ foreach ( $table_prefixes as $blog_id => $table_prefix ) {
2676
+ $count = $this->count_attachments( $table_prefix );
2677
+ $all_media += $count;
2678
+ $s3_count = $this->count_attachments( $table_prefix, true );
2679
+ $all_media_s3 += $s3_count;
2680
+ }
2681
+
2682
+ $attachment_counts = array(
2683
+ 'all' => $all_media,
2684
+ 's3' => $all_media_s3,
2685
+ );
2686
+
2687
+ set_site_transient( 'wpos3_attachment_counts', $attachment_counts, 2 * HOUR_IN_SECONDS );
2688
+ }
2689
+
2690
+ return $attachment_counts;
2691
+ }
2692
  }
classes/as3cf-plugin-compatibility.php CHANGED
@@ -50,13 +50,15 @@ class AS3CF_Plugin_Compatibility {
50
  */
51
  add_action( 'as3cf_upload_attachment_pre_remove', array( $this, 'image_editor_remove_files' ), 10, 4 );
52
  add_filter( 'as3cf_get_attached_file', array( $this, 'image_editor_download_file' ), 10, 4 );
 
 
 
53
 
54
  /*
55
  * WP_Customize_Control
56
  * /wp-includes/class-wp-customize_control.php
57
  */
58
  add_filter( 'attachment_url_to_postid', array( $this, 'customizer_background_image' ), 10, 2 );
59
-
60
  /*
61
  * Regenerate Thumbnails
62
  * https://wordpress.org/plugins/regenerate-thumbnails/
@@ -91,6 +93,25 @@ class AS3CF_Plugin_Compatibility {
91
  return $url;
92
  }
93
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  /**
95
  * Allow the WordPress Image Editor to remove edited version of images
96
  * if the original image is being restored and 'IMAGE_EDIT_OVERWRITE' is set
@@ -144,9 +165,9 @@ class AS3CF_Plugin_Compatibility {
144
  $this->copy_s3_file_to_server( $orig_s3, $orig_file );
145
 
146
  // Copy the edited file back to the server as well, it will be cleaned up later
147
- if ( ( $file = $this->copy_s3_file_to_server( $s3_object, $file ) ) ) {
148
  // Return the file if successfully downloaded from S3
149
- return $file;
150
  };
151
  }
152
 
@@ -156,9 +177,9 @@ class AS3CF_Plugin_Compatibility {
156
  foreach ( $callers as $caller ) {
157
  if ( isset( $caller['function'] ) && '_load_image_to_edit_path' == $caller['function'] ) {
158
  // check this has been called by '_load_image_to_edit_path' so as only to copy back once
159
- if ( ( $file = $this->copy_s3_file_to_server( $s3_object, $file ) ) ) {
160
  // Return the file if successfully downloaded from S3
161
- return $file;
162
  };
163
  }
164
  }
@@ -167,6 +188,82 @@ class AS3CF_Plugin_Compatibility {
167
  return $url;
168
  }
169
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
  /**
171
  * Show the correct background image in the customizer
172
  *
50
  */
51
  add_action( 'as3cf_upload_attachment_pre_remove', array( $this, 'image_editor_remove_files' ), 10, 4 );
52
  add_filter( 'as3cf_get_attached_file', array( $this, 'image_editor_download_file' ), 10, 4 );
53
+ add_filter( 'as3cf_upload_attachment_local_files_to_remove', array( $this, 'image_editor_remove_original_image' ), 10, 3 );
54
+ add_filter( 'as3cf_get_attached_file', array( $this, 'customizer_header_crop_download_file' ), 10, 4 );
55
+ add_filter( 'as3cf_upload_attachment_local_files_to_remove', array( $this, 'customizer_header_crop_remove_original_image' ), 10, 3 );
56
 
57
  /*
58
  * WP_Customize_Control
59
  * /wp-includes/class-wp-customize_control.php
60
  */
61
  add_filter( 'attachment_url_to_postid', array( $this, 'customizer_background_image' ), 10, 2 );
 
62
  /*
63
  * Regenerate Thumbnails
64
  * https://wordpress.org/plugins/regenerate-thumbnails/
93
  return $url;
94
  }
95
 
96
+ /**
97
+ * Get the file path of the original image file before an update
98
+ *
99
+ * @param int $post_id
100
+ * @param string $file_path
101
+ *
102
+ * @return bool|string
103
+ */
104
+ function get_original_image_file( $post_id, $file_path ) {
105
+ // remove original main image after edit
106
+ $meta = get_post_meta( $post_id, '_wp_attachment_metadata', true );
107
+ $original_file = trailingslashit( dirname( $file_path ) ) . basename( $meta['file'] );
108
+ if ( file_exists( $original_file ) ) {
109
+ return $original_file;
110
+ }
111
+
112
+ return false;
113
+ }
114
+
115
  /**
116
  * Allow the WordPress Image Editor to remove edited version of images
117
  * if the original image is being restored and 'IMAGE_EDIT_OVERWRITE' is set
165
  $this->copy_s3_file_to_server( $orig_s3, $orig_file );
166
 
167
  // Copy the edited file back to the server as well, it will be cleaned up later
168
+ if ( ( $s3_file = $this->copy_s3_file_to_server( $s3_object, $file ) ) ) {
169
  // Return the file if successfully downloaded from S3
170
+ return $s3_file;
171
  };
172
  }
173
 
177
  foreach ( $callers as $caller ) {
178
  if ( isset( $caller['function'] ) && '_load_image_to_edit_path' == $caller['function'] ) {
179
  // check this has been called by '_load_image_to_edit_path' so as only to copy back once
180
+ if ( ( $s3_file = $this->copy_s3_file_to_server( $s3_object, $file ) ) ) {
181
  // Return the file if successfully downloaded from S3
182
+ return $s3_file;
183
  };
184
  }
185
  }
188
  return $url;
189
  }
190
 
191
+ /**
192
+ * Allow the WordPress Image Editor to remove the main image file after it has been copied
193
+ * back from S3 after it has done the edit.
194
+ *
195
+ * @param array $files
196
+ * @param int $post_id
197
+ * @param string $file_path
198
+ *
199
+ * @return array
200
+ */
201
+ function image_editor_remove_original_image( $files, $post_id, $file_path ) {
202
+ if ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) {
203
+ return $files;
204
+ }
205
+
206
+ if ( isset( $_POST['action'] ) && 'image-editor' === sanitize_key( $_POST['action'] ) ) { // input var okay
207
+ // remove original main image after edit
208
+ if ( ( $original_file = $this->get_original_image_file( $post_id, $file_path ) ) ) {
209
+ $files[] = $original_file;
210
+ }
211
+ }
212
+
213
+ return $files;
214
+ }
215
+
216
+ /**
217
+ * Allow the WordPress Customizer to crop images that have been copied to S3
218
+ * but removed from the local server, by copying them back temporarily
219
+ *
220
+ * @param string $url
221
+ * @param string $file
222
+ * @param int $attachment_id
223
+ * @param array $s3_object
224
+ *
225
+ * @return string
226
+ */
227
+ function customizer_header_crop_download_file( $url, $file, $attachment_id, $s3_object ) {
228
+ if ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) {
229
+ return $url;
230
+ }
231
+
232
+ if ( isset( $_POST['action'] ) && 'custom-header-crop' === sanitize_key( $_POST['action'] ) ) { // input var okay
233
+ if ( ( $file = $this->copy_s3_file_to_server( $s3_object, $file ) ) ) {
234
+ // Return the file if successfully downloaded from S3
235
+ return $file;
236
+ };
237
+ }
238
+
239
+ return $url;
240
+ }
241
+
242
+ /**
243
+ * Allow the WordPress Image Editor to remove the main image file after it has been copied
244
+ * back from S3 after it has done the edit.
245
+ *
246
+ * @param array $files
247
+ * @param int $post_id
248
+ * @param string $file_path
249
+ *
250
+ * @return array
251
+ */
252
+ function customizer_header_crop_remove_original_image( $files, $post_id, $file_path ) {
253
+ if ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) {
254
+ return $files;
255
+ }
256
+
257
+ if ( isset( $_POST['action'] ) && 'custom-header-crop' === sanitize_key( $_POST['action'] ) ) { // input var okay
258
+ // remove original main image after edit
259
+ if ( ( $original_file = $this->get_original_image_file( $_POST['id'], $file_path ) ) ) {
260
+ $files[] = $original_file;
261
+ }
262
+ }
263
+
264
+ return $files;
265
+ }
266
+
267
  /**
268
  * Show the correct background image in the customizer
269
  *
classes/as3cf-upgrade.php CHANGED
@@ -17,18 +17,66 @@ if ( ! defined( 'ABSPATH' ) ) {
17
  /**
18
  * AS3CF_Upgrade Class
19
  *
20
- * This class handles data updates and other migrations after a plugin update
21
  *
22
  * @since 0.6.2
23
  */
24
- class AS3CF_Upgrade {
25
 
26
- private $as3cf;
27
- private $cron_interval_in_minutes;
28
- private $error_threshold;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
 
30
- const CRON_HOOK = 'as3cf_cron_update_meta_with_region';
31
- const CRON_SCHEDULE_KEY = 'as3cf_update_meta_with_region_interval';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
  const STATUS_RUNNING = 1;
34
  const STATUS_ERROR = 2;
@@ -42,169 +90,122 @@ class AS3CF_Upgrade {
42
  function __construct( $as3cf ) {
43
  $this->as3cf = $as3cf;
44
 
45
- $this->cron_interval_in_minutes = apply_filters( 'as3cf_update_meta_with_region_interval', 10 );
46
- $this->error_threshold = apply_filters( 'as3cf_update_meta_with_region_error_threshold', 20 );
 
 
 
47
 
48
  add_filter( 'cron_schedules', array( $this, 'cron_schedules' ) );
49
- add_action( self::CRON_HOOK, array( $this, 'cron_update_meta_with_region' ) );
50
 
51
  add_action( 'as3cf_pre_settings_render', array( $this, 'maybe_display_notices' ) );
52
  add_action( 'admin_init', array( $this, 'maybe_handle_action' ) );
53
 
54
- $this->maybe_init_upgrade();
 
 
 
55
  }
56
 
57
  /**
58
- * Maybe initialize the upgrade
 
 
59
  */
60
- function maybe_init_upgrade() {
61
  if ( ! is_admin() || ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) {
62
- return;
63
  }
64
 
65
  // make sure this only fires inside the network admin for multisites
66
  if ( is_multisite() && ! is_network_admin() ) {
67
- return;
68
- }
69
-
70
- // Have we completed the upgrade yet?
71
- if ( $this->as3cf->get_setting( 'post_meta_version', 0 ) > 0 ) {
72
- return;
73
  }
74
 
75
  // If the upgrade status is already set, then we've already initialized the upgrade
76
  if ( $this->get_upgrade_status() ) {
77
- return;
78
  }
79
 
80
- // Do we actually have S3 meta data without regions to update?
81
- // No need to bother for fresh sites, or media not uploaded to S3
82
- if ( 0 == $this->count_all_attachments_without_region() ) {
83
- $this->as3cf->set_setting( 'post_meta_version', 1 );
84
- $this->as3cf->save_settings();
85
-
86
- return;
87
  }
88
 
89
- // Initialize the upgrade
90
- $this->save_session( array( 'status' => self::STATUS_RUNNING ) );
91
-
92
- $this->as3cf->schedule_event( self::CRON_HOOK, self::CRON_SCHEDULE_KEY );
93
- }
94
-
95
- /**
96
- * Adds notices about issues with upgrades allowing user to restart them
97
- */
98
- function maybe_display_notices() {
99
- $action_url = $this->as3cf->get_plugin_page_url( array( 'action' => 'restart_update_meta_with_region' ), 'self' );
100
- $msg_type = 'notice-info';
101
-
102
- switch ( $this->get_upgrade_status() ) {
103
- case self::STATUS_RUNNING :
104
- $msg = sprintf( __( '<strong>Running Metadata Update</strong> &mdash; We&#8217;re going through all the Media Library items uploaded to S3 and updating the metadata with the bucket region it is served from. This will allow us to serve your files from the proper S3 region subdomain <span style="white-space:nowrap;">(e.g. s3-us-west-2.amazonaws.com)</span>. This will be done quietly in the background, processing a small batch of Media Library items every %d minutes. There should be no noticeable impact on your server&#8217;s performance.', 'as3cf' ), $this->cron_interval_in_minutes );
105
- $action_text = __( 'Pause Update', 'as3cf' );
106
- $action_url = $this->as3cf->get_plugin_page_url( array( 'action' => 'pause_update_meta_with_region' ), 'self' );
107
- break;
108
- case self::STATUS_PAUSED :
109
- $msg = __( '<strong>Metadata Update Paused</strong> &mdash; Updating Media Library metadata has been paused.', 'as3cf' );
110
- $action_text = __( 'Restart Update', 'as3cf' );
111
- break;
112
- case self::STATUS_ERROR :
113
- $msg = __( '<strong>Error Updating Metadata</strong> &mdash; We ran into some errors attempting to update the metadata for all your Media Library items that have been uploaded to S3. Please check your error log for details.', 'as3cf' );
114
- $action_text = __( 'Try Run It Again', 'as3cf' );
115
- $msg_type = 'error';
116
- break;
117
- default :
118
- return;
119
  }
120
 
121
- $msg .= ' <strong><a href="' . $action_url . '">' . $action_text . '</a></strong>';
122
-
123
- $args = array(
124
- 'message' => $msg,
125
- 'type' => $msg_type,
126
- );
127
 
128
- $this->as3cf->render_view( 'notice', $args );
129
- }
130
-
131
- function maybe_handle_action() {
132
- if ( ! isset( $_GET['page'] ) || sanitize_key( $_GET['page'] ) != $this->as3cf->get_plugin_slug() || ! isset( $_GET['action'] ) ) { // input var okay
133
- return;
134
  }
135
 
136
- $method_name = 'action_' . sanitize_key( $_GET['action'] ); // input var okay
137
- if ( method_exists( $this, $method_name ) ) {
138
- call_user_func( array( $this, $method_name ) );
139
- }
140
  }
141
 
142
  /**
143
- * Restart upgrade
144
  */
145
- function action_restart_update_meta_with_region() {
146
- $this->change_status_request( self::STATUS_RUNNING );
147
- $this->as3cf->schedule_event( self::CRON_HOOK, self::CRON_SCHEDULE_KEY );
148
- }
149
 
150
  /**
151
- * Pause upgrade
 
 
 
152
  */
153
- function action_pause_update_meta_with_region() {
154
- $this->clear_scheduled_event();
155
- $this->change_status_request( self::STATUS_PAUSED );
156
- }
157
 
158
  /**
159
- * Helper for the above action requests
160
  *
161
- * @param integer $status
162
  */
163
- function change_status_request( $status ) {
164
- $session = $this->get_session();
165
- $session['status'] = $status;
166
- $this->save_session( $session );
167
-
168
- $url = $this->as3cf->get_plugin_page_url( array(), 'self' );
169
- wp_redirect( $url );
170
- }
171
 
172
  /**
173
- * Add custom cron interval schedules
174
- *
175
- * @param array $schedules
176
- *
177
- * @return array
178
  */
179
- function cron_schedules( $schedules ) {
180
- // Adds every 10 minutes to the existing schedules.
181
- $schedules[ self::CRON_SCHEDULE_KEY ] = array(
182
- 'interval' => $this->cron_interval_in_minutes * 60,
183
- 'display' => sprintf( __( 'Every %d Minutes', 'as3cf' ), $this->cron_interval_in_minutes ),
184
- );
185
 
186
- return $schedules;
187
  }
188
-
189
  /**
190
  * Cron jon to update the region of the bucket in s3 metadata
191
  */
192
- function cron_update_meta_with_region() {
193
  // Check if the cron should even be running
194
- if ( $this->as3cf->get_setting( 'post_meta_version', 0 ) > 0 || $this->get_upgrade_status() != self::STATUS_RUNNING ) {
195
- $this->as3cf->clear_scheduled_event( self::CRON_HOOK );
 
196
  return;
197
  }
198
 
199
  // set the batch size limit for the query
200
- $limit = apply_filters( 'as3cf_update_meta_with_region_batch_size', 500 );
201
  $all_limit = $limit;
202
 
203
- $session = $this->get_session();
 
 
 
 
 
 
204
 
205
  // find the blog IDs that have been processed so we can skip them
206
  $processed_blog_ids = isset( $session['processed_blog_ids'] ) ? $session['processed_blog_ids'] : array();
207
- $error_count = isset( $session['error_count'] ) ? $session['error_count'] : 0;
208
 
209
  // get the table prefixes for all the blogs
210
  $table_prefixes = $this->as3cf->get_all_blog_table_prefixes( $processed_blog_ids );
@@ -213,10 +214,10 @@ class AS3CF_Upgrade {
213
  $all_count = 0;
214
 
215
  foreach ( $table_prefixes as $blog_id => $table_prefix ) {
216
- $attachments = $this->get_attachments_without_region( $table_prefix, $limit );
217
  $count = count( $attachments );
218
 
219
- if ( 0 == $count ) {
220
  // no more attachments, record the blog ID to skip next time
221
  $processed_blog_ids[] = $blog_id;
222
  } else {
@@ -231,204 +232,205 @@ class AS3CF_Upgrade {
231
  $limit = $limit - $count;
232
  }
233
 
234
- if ( 0 == $all_count ) {
235
- $this->as3cf->set_setting( 'post_meta_version', 1 );
236
- $this->as3cf->remove_setting( 'update_meta_with_region_session' );
237
- $this->as3cf->save_settings();
238
- $this->as3cf->clear_scheduled_event( self::CRON_HOOK );
239
  return;
240
  }
241
 
242
- // only process the loop for a certain amount of time
243
- $minutes = $this->cron_interval_in_minutes * 60;
244
-
245
- // smaller time limit so won't run into another instance of cron
246
- $minutes = $minutes * 0.8;
247
-
248
- $finish = time() + $minutes;
249
-
250
  // loop through and update s3 meta with region
251
  foreach ( $all_attachments as $blog_id => $attachments ) {
252
- if ( is_multisite() && ! $this->as3cf->is_current_blog( $blog_id ) ) {
253
- switch_to_blog( $blog_id );
254
- }
255
 
256
  foreach ( $attachments as $attachment ) {
257
- if ( $error_count >= $this->error_threshold ) {
258
- $session['status'] = self::STATUS_ERROR;
259
- $this->save_session( $session );
260
- $this->clear_scheduled_event();
261
- return;
262
- }
263
 
264
- if ( time() >= $finish ) {
265
- break;
266
  }
267
 
268
- $s3object = unserialize( $attachment->s3object );
269
- if ( false === $s3object ) {
270
- error_log( 'Failed to unserialize S3 meta for attachment ' . $attachment->ID . ': ' . $attachment->s3object );
271
- $error_count++;
272
- continue;
273
- }
274
 
275
- // retrieve region and update the attachment metadata
276
- $region = $this->as3cf->get_s3object_region( $s3object, $attachment->ID );
277
- if ( is_wp_error( $region ) ) {
278
- error_log( 'Error updating region: ' . $region->get_error_message() );
279
- $error_count++;
280
  }
281
  }
282
-
283
- if ( is_multisite() && ! $this->as3cf->is_current_blog( $blog_id ) ) {
284
- restore_current_blog();
285
- }
286
  }
287
 
 
 
288
  $session['processed_blog_ids'] = $processed_blog_ids;
289
- $session['error_count'] = $error_count;
290
 
291
  $this->save_session( $session );
292
  }
293
 
294
  /**
295
- * Get a count of all attachments without region in their S3 metadata
296
- * for the whole site
297
- *
298
- * @return int
299
  */
300
- function count_all_attachments_without_region() {
301
- // get the table prefixes for all the blogs
302
- $table_prefixes = $this->as3cf->get_all_blog_table_prefixes();
303
- $all_count = 0;
304
 
305
- foreach ( $table_prefixes as $blog_id => $table_prefix ) {
306
- $count = $this->count_attachments_without_region( $table_prefix );
307
- $all_count += $count;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
308
  }
309
 
310
- return $all_count;
 
 
 
 
 
 
 
311
  }
312
 
313
  /**
314
- * Get the current status of the upgrade
315
- * See STATUS_* constants in the class declaration above.
316
  */
317
- function get_upgrade_status() {
318
- $session = $this->get_session();
319
-
320
- if ( ! isset( $session['status'] ) ) {
321
- return '';
322
  }
323
 
324
- return $session['status'];
 
 
 
325
  }
326
 
327
  /**
328
- * Retrieve session data from plugin settings
329
  *
330
- * @return array
331
  */
332
- function get_session() {
333
- return $this->as3cf->get_setting( 'update_meta_with_region_session', array() );
 
 
334
  }
335
 
336
  /**
337
- * Store data to be used between requests in plugin settings
338
- *
339
- * @param $session array of session data to store
340
  */
341
- function save_session( $session ) {
342
- $this->as3cf->set_setting( 'update_meta_with_region_session', $session );
 
343
  $this->as3cf->save_settings();
 
344
  }
345
 
346
  /**
347
- * Get all the table prefixes for the blogs in the site. MS compatible
348
- *
349
- * @param array $exclude_blog_ids blog ids to exclude
350
- *
351
- * @return array associative array with blog ID as key, prefix as value
352
  */
353
- function get_all_blog_table_prefixes( $exclude_blog_ids = array() ) {
354
- global $wpdb;
355
- $prefix = $wpdb->prefix;
356
-
357
- $table_prefixes = array();
358
-
359
- if ( ! in_array( 1, $exclude_blog_ids ) ) {
360
- $table_prefixes[1] = $prefix;
361
- }
362
-
363
- if ( is_multisite() ) {
364
- $blog_ids = $this->as3cf->get_blog_ids();
365
- foreach ( $blog_ids as $blog_id ) {
366
- if ( in_array( $blog_id, $exclude_blog_ids ) ) {
367
- continue;
368
- }
369
- $table_prefixes[ $blog_id ] = $wpdb->get_blog_prefix( $blog_id );
370
- }
371
  }
372
 
373
- return $table_prefixes;
 
374
  }
375
 
376
  /**
377
- * Get all attachments that don't have region in their S3 meta data for a blog
378
- *
379
- * @param string $prefix
380
- * @param int $limit
381
- *
382
- * @return mixed
383
  */
384
- function get_attachments_without_region( $prefix, $limit ) {
385
- $attachments = $this->get_attachments_without_region_results( $prefix, false, $limit );
 
 
386
 
387
- return $attachments;
 
388
  }
389
 
390
  /**
391
- * Get a count of attachments that don't have region in their S3 meta data for a blog
392
- * @param $prefix
393
  *
394
- * @return int
395
  */
396
- function count_attachments_without_region( $prefix ) {
397
- $count = $this->get_attachments_without_region_results( $prefix, true );
 
 
398
 
399
- return $count;
 
 
400
  }
401
 
402
  /**
403
- * Wrapper for database call to get attachments without region
404
  *
405
- * @param string $prefix
406
- * @param bool $count return count of attachments
407
- * @param null|int $limit
408
  *
409
- * @return mixed
410
  */
411
- function get_attachments_without_region_results( $prefix, $count = false, $limit = null ) {
412
- global $wpdb;
 
 
 
 
413
 
414
- $sql = " FROM `{$prefix}postmeta`
415
- WHERE `meta_key` = 'amazonS3_info'
416
- AND `meta_value` NOT LIKE '%%\"region\"%%'";
417
 
418
- if ( $count ) {
419
- $sql = 'SELECT COUNT(*)' . $sql;
 
 
 
 
420
 
421
- return $wpdb->get_var( $sql );
 
422
  }
423
 
424
- $sql = "SELECT `post_id` as `ID`, `meta_value` AS 's3object'" . $sql;
 
425
 
426
- if ( ! is_null( $limit ) ) {
427
- $sql .= ' LIMIT %d';
 
 
 
 
 
 
428
 
429
- $sql = $wpdb->prepare( $sql, $limit );
430
- }
 
 
 
 
 
 
431
 
432
- return $wpdb->get_results( $sql, OBJECT );
 
 
 
 
 
433
  }
434
  }
17
  /**
18
  * AS3CF_Upgrade Class
19
  *
20
+ * This class handles updates to attachments and attachment meta data
21
  *
22
  * @since 0.6.2
23
  */
24
+ abstract class AS3CF_Upgrade {
25
 
26
+ /**
27
+ * @var Amazon_S3_And_CloudFront
28
+ */
29
+ protected $as3cf;
30
+
31
+ /**
32
+ * @var int
33
+ */
34
+ protected $upgrade_id;
35
+
36
+ /**
37
+ * @var string
38
+ */
39
+ protected $upgrade_name;
40
+
41
+ /**
42
+ * @var string 'metadata', 'attachment'
43
+ */
44
+ protected $upgrade_type;
45
+
46
+ /**
47
+ * @var string
48
+ */
49
+ protected $running_update_text;
50
+
51
+ /**
52
+ * @var string
53
+ */
54
+ protected $settings_key = 'post_meta_version';
55
+
56
+ /**
57
+ * @var string
58
+ */
59
+ protected $cron_hook;
60
 
61
+ /**
62
+ * @var string
63
+ */
64
+ protected $cron_schedule_key;
65
+
66
+ /**
67
+ * @var mixed|void
68
+ */
69
+ protected $cron_interval_in_minutes;
70
+
71
+ /**
72
+ * @var mixed|void
73
+ */
74
+ protected $error_threshold;
75
+
76
+ /**
77
+ * @var int
78
+ */
79
+ protected $error_count;
80
 
81
  const STATUS_RUNNING = 1;
82
  const STATUS_ERROR = 2;
90
  function __construct( $as3cf ) {
91
  $this->as3cf = $as3cf;
92
 
93
+ $this->cron_hook = 'as3cf_cron_update_' . $this->upgrade_name;
94
+ $this->cron_schedule_key = 'as3cf_update_' . $this->upgrade_name . '_interval';
95
+
96
+ $this->cron_interval_in_minutes = apply_filters( 'as3cf_update_' . $this->upgrade_name . '_interval', 5 );
97
+ $this->error_threshold = apply_filters( 'as3cf_update_' . $this->upgrade_name . '_error_threshold', 20 );
98
 
99
  add_filter( 'cron_schedules', array( $this, 'cron_schedules' ) );
100
+ add_action( $this->cron_hook, array( $this, 'do_upgrade' ) );
101
 
102
  add_action( 'as3cf_pre_settings_render', array( $this, 'maybe_display_notices' ) );
103
  add_action( 'admin_init', array( $this, 'maybe_handle_action' ) );
104
 
105
+ // Do default checks if the upgrade can be started
106
+ if ( $this->maybe_init() ) {
107
+ $this->init();
108
+ }
109
  }
110
 
111
  /**
112
+ * Can we start the upgrade using default checks
113
+ *
114
+ * @return bool
115
  */
116
+ protected function maybe_init() {
117
  if ( ! is_admin() || ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) {
118
+ return false;
119
  }
120
 
121
  // make sure this only fires inside the network admin for multisites
122
  if ( is_multisite() && ! is_network_admin() ) {
123
+ return false;
 
 
 
 
 
124
  }
125
 
126
  // If the upgrade status is already set, then we've already initialized the upgrade
127
  if ( $this->get_upgrade_status() ) {
128
+ return false;
129
  }
130
 
131
+ // Have we completed the upgrade?
132
+ if ( $this->as3cf->get_setting( $this->settings_key, 0 ) >= $this->upgrade_id ) {
133
+ return false;
 
 
 
 
134
  }
135
 
136
+ // Has the previous upgrade completed yet?
137
+ $previous_id = $this->upgrade_id - 1;
138
+ if ( 0 !== $previous_id && (int) $this->as3cf->get_setting( $this->settings_key, 0 ) < $previous_id ) {
139
+ // Previous still running, abort
140
+ return false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
  }
142
 
143
+ // Do we actually attachments to process?
144
+ if ( 0 === $this->count_attachments_to_process() ) {
145
+ $this->upgrade_finished();
 
 
 
146
 
147
+ return false;
 
 
 
 
 
148
  }
149
 
150
+ return true;
 
 
 
151
  }
152
 
153
  /**
154
+ * @return int
155
  */
156
+ abstract protected function count_attachments_to_process();
 
 
 
157
 
158
  /**
159
+ * @param $prefix
160
+ * @param $limit
161
+ *
162
+ * @return array
163
  */
164
+ abstract protected function get_attachments_to_process( $prefix, $limit );
 
 
 
165
 
166
  /**
167
+ * @param $attachment
168
  *
169
+ * @return bool
170
  */
171
+ abstract protected function upgrade_attachment( $attachment );
 
 
 
 
 
 
 
172
 
173
  /**
174
+ * Fire up the upgrade
 
 
 
 
175
  */
176
+ protected function init() {
177
+ // Initialize the upgrade
178
+ $this->save_session( array( 'status' => self::STATUS_RUNNING ) );
 
 
 
179
 
180
+ $this->as3cf->schedule_event( $this->cron_hook, $this->cron_schedule_key );
181
  }
182
+
183
  /**
184
  * Cron jon to update the region of the bucket in s3 metadata
185
  */
186
+ function do_upgrade() {
187
  // Check if the cron should even be running
188
+ if ( $this->as3cf->get_setting( $this->settings_key, 0 ) >= $this->upgrade_id || $this->get_upgrade_status() !== self::STATUS_RUNNING ) {
189
+ $this->as3cf->clear_scheduled_event( $this->cron_hook );
190
+
191
  return;
192
  }
193
 
194
  // set the batch size limit for the query
195
+ $limit = apply_filters( 'as3cf_update_' . $this->upgrade_name . '_batch_size', 500 );
196
  $all_limit = $limit;
197
 
198
+ // only process the loop for a certain amount of time
199
+ $minutes = $this->cron_interval_in_minutes * 60;
200
+ // smaller time limit so won't run into another instance of cron
201
+ $minutes = $minutes * 0.8;
202
+ $finish = time() + $minutes;
203
+
204
+ $session = $this->get_session();
205
 
206
  // find the blog IDs that have been processed so we can skip them
207
  $processed_blog_ids = isset( $session['processed_blog_ids'] ) ? $session['processed_blog_ids'] : array();
208
+ $this->error_count = isset( $session['error_count'] ) ? $session['error_count'] : 0;
209
 
210
  // get the table prefixes for all the blogs
211
  $table_prefixes = $this->as3cf->get_all_blog_table_prefixes( $processed_blog_ids );
214
  $all_count = 0;
215
 
216
  foreach ( $table_prefixes as $blog_id => $table_prefix ) {
217
+ $attachments = $this->get_attachments_to_process( $table_prefix, $limit );
218
  $count = count( $attachments );
219
 
220
+ if ( 0 === $count ) {
221
  // no more attachments, record the blog ID to skip next time
222
  $processed_blog_ids[] = $blog_id;
223
  } else {
232
  $limit = $limit - $count;
233
  }
234
 
235
+ if ( 0 === $all_count ) {
236
+ $this->upgrade_finished();
237
+
 
 
238
  return;
239
  }
240
 
 
 
 
 
 
 
 
 
241
  // loop through and update s3 meta with region
242
  foreach ( $all_attachments as $blog_id => $attachments ) {
243
+ $this->as3cf->switch_to_blog( $blog_id );
 
 
244
 
245
  foreach ( $attachments as $attachment ) {
246
+ if ( $this->error_count >= $this->error_threshold ) {
247
+ $this->upgrade_error( $session );
 
 
 
 
248
 
249
+ return;
 
250
  }
251
 
252
+ // Do the actual upgrade to the attachment
253
+ $this->upgrade_attachment( $attachment );
 
 
 
 
254
 
255
+ if ( time() >= $finish || $this->as3cf->memory_exceeded( 'as3cf_update_' . $this->upgrade_name . '_memory_exceeded' ) ) {
256
+ // Batch limits reached
257
+ break 2;
 
 
258
  }
259
  }
 
 
 
 
260
  }
261
 
262
+ $this->as3cf->restore_current_blog( $blog_id );
263
+
264
  $session['processed_blog_ids'] = $processed_blog_ids;
265
+ $session['error_count'] = $this->error_count;
266
 
267
  $this->save_session( $session );
268
  }
269
 
270
  /**
271
+ * Adds notices about issues with upgrades allowing user to restart them
 
 
 
272
  */
273
+ function maybe_display_notices() {
274
+ $action_url = $this->as3cf->get_plugin_page_url( array( 'action' => 'restart_update', 'update' => $this->upgrade_name ), 'self' );
275
+ $msg_type = 'notice-info';
 
276
 
277
+ switch ( $this->get_upgrade_status() ) {
278
+ case self::STATUS_RUNNING:
279
+ $msg = sprintf( __( '<strong>Running %s Update</strong> &mdash; We&#8217;re going through all the Media Library items uploaded to S3 %s This will be done quietly in the background, processing a small batch of Media Library items every %d minutes. There should be no noticeable impact on your server&#8217;s performance.', 'as3cf' ), ucfirst( $this->upgrade_type ), $this->running_update_text, $this->cron_interval_in_minutes );
280
+ $action_text = __( 'Pause Update', 'as3cf' );
281
+ $action_url = $this->as3cf->get_plugin_page_url( array( 'action' => 'pause_update', 'update' => $this->upgrade_name ), 'self' );
282
+ break;
283
+ case self::STATUS_PAUSED:
284
+ $msg = sprintf( __( '<strong>%s Update Paused</strong> &mdash; Updating Media Library %s has been paused.', 'as3cf' ), ucfirst( $this->upgrade_type ), $this->upgrade_type );
285
+ $action_text = __( 'Restart Update', 'as3cf' );
286
+ break;
287
+ case self::STATUS_ERROR:
288
+ $msg = sprintf( __( '<strong>Error Updating %s</strong> &mdash; We ran into some errors attempting to update the %s for all your Media Library items that have been uploaded to S3. Please check your error log for details.', 'as3cf' ), ucfirst( $this->upgrade_type ), $this->upgrade_type );
289
+ $action_text = __( 'Try Run It Again', 'as3cf' );
290
+ $msg_type = 'error';
291
+ break;
292
+ default:
293
+ return;
294
  }
295
 
296
+ $msg .= ' <strong><a href="' . $action_url . '">' . $action_text . '</a></strong>';
297
+
298
+ $args = array(
299
+ 'message' => $msg,
300
+ 'type' => $msg_type,
301
+ );
302
+
303
+ $this->as3cf->render_view( 'notice', $args );
304
  }
305
 
306
  /**
307
+ * Handler for the running upgrade actions
 
308
  */
309
+ function maybe_handle_action() {
310
+ if ( ! isset( $_GET['page'] ) || sanitize_key( $_GET['page'] ) !== $this->as3cf->get_plugin_slug() || ! isset( $_GET['action'] ) ) { // input var okay
311
+ return;
 
 
312
  }
313
 
314
+ $method_name = 'action_' . sanitize_key( $_GET['action'] ); // input var okay
315
+ if ( method_exists( $this, $method_name ) ) {
316
+ call_user_func( array( $this, $method_name ) );
317
+ }
318
  }
319
 
320
  /**
321
+ * Exit upgrade with an error
322
  *
323
+ * @param array $session
324
  */
325
+ function upgrade_error( $session ) {
326
+ $session['status'] = self::STATUS_ERROR;
327
+ $this->save_session( $session );
328
+ $this->as3cf->clear_scheduled_event( $this->cron_hook );
329
  }
330
 
331
  /**
332
+ * Complete the upgrade
 
 
333
  */
334
+ function upgrade_finished() {
335
+ $this->clear_session();
336
+ $this->as3cf->set_setting( $this->settings_key, $this->upgrade_id );
337
  $this->as3cf->save_settings();
338
+ $this->as3cf->clear_scheduled_event( $this->cron_hook );
339
  }
340
 
341
  /**
342
+ * Restart upgrade
 
 
 
 
343
  */
344
+ function action_restart_update() {
345
+ if ( ! isset( $_GET['update'] ) || $this->upgrade_name !== sanitize_key( $_GET['update'] ) ) {
346
+ return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
347
  }
348
 
349
+ $this->as3cf->schedule_event( $this->cron_hook, $this->cron_schedule_key );
350
+ $this->change_status_request( self::STATUS_RUNNING );
351
  }
352
 
353
  /**
354
+ * Pause upgrade