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
355
*/
356
+ function action_pause_update() {
357
+ if ( ! isset( $_GET['update'] ) || $this->upgrade_name !== sanitize_key( $_GET['update'] ) ) {
358
+ return;
359
+ }
360
361
+ $this->as3cf->clear_scheduled_event( $this->cron_hook );
362
+ $this->change_status_request( self::STATUS_PAUSED );
363
}
364
365
/**
366
+ * Helper for the above action requests
367
*
368
+ * @param int $status
369
*/
370
+ function change_status_request( $status ) {
371
+ $session = $this->get_session();
372
+ $session['status'] = $status;
373
+ $this->save_session( $session );
374
375
+ $url = $this->as3cf->get_plugin_page_url( array(), 'self' );
376
+ wp_redirect( $url );
377
+ exit;
378
}
379
380
/**
381
+ * Add custom cron interval schedules
382
*
383
+ * @param array $schedules
384
*
385
+ * @return array
386
*/
387
+ function cron_schedules( $schedules ) {
388
+ // Add the upgrade interval to the existing schedules.
389
+ $schedules[ $this->cron_schedule_key ] = array(
390
+ 'interval' => $this->cron_interval_in_minutes * 60,
391
+ 'display' => sprintf( __( 'Every %d Minutes', 'as3cf' ), $this->cron_interval_in_minutes ),
392
+ );
393
394
+ return $schedules;
395
+ }
396
397
+ /**
398
+ * Get the current status of the upgrade
399
+ * See STATUS_* constants in the class declaration above.
400
+ */
401
+ function get_upgrade_status() {
402
+ $session = $this->get_session();
403
404
+ if ( ! isset( $session['status'] ) ) {
405
+ return '';
406
}
407
408
+ return $session['status'];
409
+ }
410
411
+ /**
412
+ * Retrieve session data from plugin settings
413
+ *
414
+ * @return array
415
+ */
416
+ function get_session() {
417
+ return get_site_option( 'update_' . $this->upgrade_name . '_session', array() );
418
+ }
419
420
+ /**
421
+ * Store data to be used between requests in plugin settings
422
+ *
423
+ * @param array $session session data to store
424
+ */
425
+ function save_session( $session ) {
426
+ update_site_option( 'update_' . $this->upgrade_name . '_session', $session );
427
+ }
428
429
+ /**
430
+ * Remove the session data to be used between requests
431
+ *
432
+ */
433
+ function clear_session() {
434
+ delete_site_option( 'update_' . $this->upgrade_name . '_session' );
435
}
436
}
classes/upgrades/as3cf-file-sizes.php ADDED
@@ -0,0 +1,215 @@
1
+ <?php
2
+ /**
3
+ * Update File Sizes
4
+ *
5
+ * @package amazon-s3-and-cloudfront
6
+ * @subpackage Classes/Upgrades/File-Sizes
7
+ * @copyright Copyright (c) 2015, Delicious Brains
8
+ * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
9
+ * @since 0.9.3
10
+ */
11
+
12
+ // Exit if accessed directly
13
+ if ( ! defined( 'ABSPATH' ) ) {
14
+ exit;
15
+ }
16
+
17
+ /**
18
+ * AS3CF_Upgrade_File_Sizes Class
19
+ *
20
+ * This class handles updating the file sizes in the meta data
21
+ * for attachments that have been removed from the local server
22
+ *
23
+ * @since 0.9.3
24
+ */
25
+ class AS3CF_Upgrade_File_Sizes extends AS3CF_Upgrade {
26
+
27
+ /**
28
+ * Initiate the upgrade
29
+ *
30
+ * @param object $as3cf Instance of calling class
31
+ */
32
+ public function __construct( $as3cf ) {
33
+ $this->upgrade_id = 2;
34
+ $this->upgrade_name = 'file_sizes';
35
+ $this->upgrade_type = 'attachments';
36
+
37
+ $this->running_update_text = __( 'and updating the metadata with the sizes of files that have been removed from the server. This will allow us to serve the correct size for media items and the total space used in Multisite subsites.', 'as3cf' );
38
+
39
+ parent::__construct( $as3cf );
40
+ }
41
+
42
+ /**
43
+ * Get the total file sizes for an attachment and associated files.
44
+ *
45
+ * @param $attachment
46
+ *
47
+ * @return bool
48
+ */
49
+ function upgrade_attachment( $attachment ) {
50
+ $s3object = unserialize( $attachment->s3object );
51
+ if ( false === $s3object ) {
52
+ error_log( 'Failed to unserialize S3 meta for attachment ' . $attachment->ID . ': ' . $attachment->s3object );
53
+ $this->error_count++;
54
+
55
+ return false;
56
+ }
57
+
58
+ $region = $this->as3cf->get_s3object_region( $s3object );
59
+ if ( is_wp_error( $region ) ) {
60
+ error_log( 'Failed to get the region for the bucket of the attachment ' . $attachment->ID );
61
+ $this->error_count++;
62
+
63
+ return false;
64
+ }
65
+
66
+ $s3client = $this->as3cf->get_s3client( $region, true );
67
+ $main_file = $s3object['key'];
68
+
69
+ $path_parts = pathinfo( $main_file );
70
+ $prefix = trailingslashit( dirname( $s3object['key'] ) );
71
+
72
+ // Used to search S3 for all files related to an attachment
73
+ $search_prefix = $prefix . basename( $main_file, '.' . $path_parts['extension'] );
74
+
75
+ $args = array(
76
+ 'Bucket' => $s3object['bucket'],
77
+ 'Prefix' => $search_prefix,
78
+ );
79
+
80
+ try {
81
+ // List objects for the attachment
82
+ $result = $s3client->ListObjects( $args );
83
+ } catch ( Exception $e ) {
84
+ error_log( 'Error listing objects of prefix ' . $search_prefix . ' for attachment ' . $attachment->ID . ' from S3: ' . $e->getMessage() );
85
+ $this->error_count ++;
86
+
87
+ return false;
88
+ }
89
+
90
+ $file_size_total = 0;
91
+ $main_file_size = 0;
92
+
93
+ foreach ( $result->get( 'Contents' ) as $object ) {
94
+ if ( ! isset( $object['Size'] ) ) {
95
+ continue;
96
+ }
97
+
98
+ $size = $object['Size'];
99
+
100
+ // Increment the total size of files for the attachment
101
+ $file_size_total += $size;
102
+
103
+ if ( $object['Key'] === $main_file ) {
104
+ // Record the size of the main file
105
+ $main_file_size = $size;
106
+ }
107
+ }
108
+
109
+ if ( 0 === $file_size_total ) {
110
+ error_log( 'Total file size for the attachment is 0: ' . $attachment->ID );
111
+ $this->error_count ++;
112
+
113
+ return false;
114
+ }
115
+
116
+ // Update the main file size for the attachment
117
+ $meta = get_post_meta( $attachment->ID, '_wp_attachment_metadata', true );
118
+ $meta['filesize'] = $main_file_size;
119
+ update_post_meta( $attachment->ID, '_wp_attachment_metadata', $meta );
120
+
121
+ // Add the total file size for all image sizes
122
+ update_post_meta( $attachment->ID, 'wpos3_filesize_total', $file_size_total );
123
+
124
+ return true;
125
+ }
126
+
127
+ /**
128
+ * Get a count of all attachments without region in their S3 metadata
129
+ * for the whole site
130
+ *
131
+ * @return int
132
+ */
133
+ function count_attachments_to_process() {
134
+ // get the table prefixes for all the blogs
135
+ $table_prefixes = $this->as3cf->get_all_blog_table_prefixes();
136
+ $all_count = 0;
137
+
138
+ foreach ( $table_prefixes as $blog_id => $table_prefix ) {
139
+ $count = $this->get_attachments_removed_from_server( $table_prefix, true );
140
+ $all_count += $count;
141
+ }
142
+
143
+ return $all_count;
144
+ }
145
+
146
+ /**
147
+ * Get all attachments that don't have region in their S3 meta data for a blog
148
+ *
149
+ * @param string $prefix
150
+ * @param int $limit
151
+ *
152
+ * @return mixed
153
+ */
154
+ function get_attachments_to_process( $prefix, $limit ) {
155
+ $attachments = $this->get_attachments_removed_from_server( $prefix, false, $limit );
156
+
157
+ return $attachments;
158
+ }
159
+
160
+ /**
161
+ * Wrapper for database call to get attachments uploaded to S3,
162
+ * that don't have the file size meta added already
163
+ *
164
+ * @param string $prefix
165
+ * @param null|int $limit
166
+ *
167
+ * @return mixed
168
+ */
169
+ function get_s3_attachments( $prefix, $limit = null ) {
170
+ global $wpdb;
171
+
172
+ $sql = "SELECT pm1.`post_id` as `ID`, pm1.`meta_value` AS 's3object'
173
+ FROM `{$prefix}postmeta` pm1
174
+ LEFT OUTER JOIN `{$prefix}postmeta` pm2
175
+ ON pm1.`post_id` = pm2.`post_id`
176
+ AND pm2.`meta_key` = 'wpos3_filesize_total'
177
+ WHERE pm1.`meta_key` = 'amazonS3_info'
178
+ AND pm2.`post_id` is null";
179
+
180
+ if ( ! is_null( $limit ) ) {
181
+ $sql .= ' LIMIT %d';
182
+
183
+ $sql = $wpdb->prepare( $sql, $limit );
184
+ }
185
+
186
+ return $wpdb->get_results( $sql, OBJECT );
187
+ }
188
+
189
+ /**
190
+ * Get S3 attachments that have had their local file removed from the server
191
+ *
192
+ * @param string $prefix
193
+ * @param bool|false $count
194
+ * @param null|int $limit
195
+ *
196
+ * @return array|int
197
+ */
198
+ function get_attachments_removed_from_server( $prefix, $count = false, $limit = null ) {
199
+ $all_attachments = $this->get_s3_attachments( $prefix, $limit );
200
+ $attachments = array();
201
+
202
+ foreach ( $all_attachments as $attachment ) {
203
+ if ( ! file_exists( get_attached_file( $attachment->ID, true ) ) ) {
204
+ $attachments[] = $attachment;
205
+ }
206
+ }
207
+
208
+ if ( $count ) {
209
+ return count( $attachments );
210
+ }
211
+
212
+ return $attachments;
213
+ }
214
+
215
+ }
classes/upgrades/as3cf-region-meta.php ADDED
@@ -0,0 +1,145 @@
1
+ <?php
2
+ /**
3
+ * Upgrade Region in Meta
4
+ *
5
+ * @package amazon-s3-and-cloudfront
6
+ * @subpackage Classes/Upgrades/Region-Meta
7
+ * @copyright Copyright (c) 2014, Delicious Brains
8
+ * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
9
+ * @since 0.6.2
10
+ */
11
+
12
+ // Exit if accessed directly
13
+ if ( ! defined( 'ABSPATH' ) ) {
14
+ exit;
15
+ }
16
+
17
+ /**
18
+ * AS3CF_Upgrade_Region_Meta Class
19
+ *
20
+ * This class handles updating the region of the attachment's bucket in the meta data
21
+ *
22
+ * @since 0.6.2
23
+ */
24
+ class AS3CF_Upgrade_Region_Meta extends AS3CF_Upgrade {
25
+
26
+ /**
27
+ * Initiate the upgrade
28
+ *
29
+ * @param object $as3cf Instance of calling class
30
+ */
31
+ public function __construct( $as3cf ) {
32
+ $this->upgrade_id = 1;
33
+ $this->upgrade_name = 'meta_with_region';
34
+ $this->upgrade_type = 'metadata';
35
+
36
+ $this->running_update_text = __( '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>.', 'as3cf' );
37
+
38
+ parent::__construct( $as3cf );
39
+ }
40
+
41
+ /**
42
+ * Get the region for the bucket where an attachment is located, update the S3 meta.
43
+ *
44
+ * @param $attachment
45
+ *
46
+ * @return bool
47
+ */
48
+ function upgrade_attachment( $attachment ) {
49
+ $s3object = unserialize( $attachment->s3object );
50
+ if ( false === $s3object ) {
51
+ error_log( 'Failed to unserialize S3 meta for attachment ' . $attachment->ID . ': ' . $attachment->s3object );
52
+ $this->error_count++;
53
+
54
+ return false;
55
+ }
56
+ // retrieve region and update the attachment metadata
57
+ $region = $this->as3cf->get_s3object_region( $s3object, $attachment->ID );
58
+ if ( is_wp_error( $region ) ) {
59
+ error_log( 'Error updating region: ' . $region->get_error_message() );
60
+ $this->error_count++;
61
+
62
+ return false;
63
+ }
64
+
65
+ return true;
66
+ }
67
+
68
+ /**
69
+ * Get a count of all attachments without region in their S3 metadata
70
+ * for the whole site
71
+ *
72
+ * @return int
73
+ */
74
+ function count_attachments_to_process() {
75
+ // get the table prefixes for all the blogs
76
+ $table_prefixes = $this->as3cf->get_all_blog_table_prefixes();
77
+ $all_count = 0;
78