From 6e4239f0aec4e577899be0f72b72b3108a1840ac Mon Sep 17 00:00:00 2001 From: Iain Date: Thu, 27 Aug 2015 20:17:34 +0100 Subject: [PATCH] Deploying version 0.9.4 --- README.md | 28 +- assets/js/modal.js | 132 ++---- assets/js/modal.min.js | 2 +- assets/js/script.js | 13 + assets/js/script.min.js | 2 +- classes/amazon-s3-and-cloudfront.php | 344 +++++++++++++-- classes/as3cf-plugin-compatibility.php | 107 ++++- classes/as3cf-upgrade.php | 486 +++++++++++----------- classes/upgrades/as3cf-file-sizes.php | 215 ++++++++++ classes/upgrades/as3cf-region-meta.php | 145 +++++++ classes/wp-aws-compatibility-check.php | 64 ++- classes/wp-aws-uninstall.php | 116 ++++-- composer.lock | 107 ----- languages/amazon-s3-and-cloudfront-en.pot | 314 +++++++------- languages/as3cf-pt-br.mo | Bin 11918 -> 6039 bytes readme.txt | 28 +- uninstall.php | 27 +- view/bucket-select.php | 26 +- view/bucket-setting.php | 6 +- view/sidebar.php | 6 - wordpress-s3.php | 12 +- 21 files changed, 1431 insertions(+), 749 deletions(-) create mode 100644 classes/upgrades/as3cf-file-sizes.php create mode 100644 classes/upgrades/as3cf-region-meta.php delete mode 100644 composer.lock diff --git a/README.md b/README.md index 3130f743..4c3da245 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,9 @@ # WP Offload S3 # -**Contributors:** bradt -**Donate link:** https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=5VPMGLLK94XJC +**Contributors:** bradt, deliciousbrains **Tags:** uploads, amazon, s3, mirror, admin, media, cdn, cloudfront **Requires at least:** 3.7 **Tested up to:** 4.3 -**Stable tag:** 0.9.3 +**Stable tag:** 0.9.4 **License:** GPLv3 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 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. -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. +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. **PRO Upgrade with Email Support and More Features** * Upload existing Media Library to S3 * Find & replace file URLs in content * Control S3 files from the Media Library -* [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 -* [WooCommerce addon](https://deliciousbrains.com/wp-offload-s3/?utm_source=wordpress.org&utm_medium=web&utm_content=desc&utm_campaign=freeplugin#woocommerce-addon) -* [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) +* [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 +* [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) +* [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) * PriorityExpert™ email support -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. +[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) + +The video below runs through the pro upgrade features... https://www.youtube.com/watch?v=55xNGnbJ_CY @@ -66,6 +67,17 @@ This version requires PHP 5.3.3+ and the Amazon Web Services plugin ## Changelog ## +### 0.9.4 - 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 + ### 0.9.3 - 2015-08-17 ### * New: Pro upgrade sidebar * Bug fix: Create buckets in US standard region causing S3 URLs to 404 errors diff --git a/assets/js/modal.js b/assets/js/modal.js index fbb800bf..5e105604 100644 --- a/assets/js/modal.js +++ b/assets/js/modal.js @@ -1,7 +1,8 @@ var as3cfModal = (function ( $ ) { var modal = { - prefix: 'as3cf' + prefix: 'as3cf', + loading: false }; var modals = {}; @@ -9,9 +10,9 @@ var as3cfModal = (function ( $ ) { /** * Target to key * - * @param string target + * @param {string} target * - * @return string + * @return {string} */ function targetToKey( target ) { return target.replace( /[^a-z]/g, '' ); @@ -20,10 +21,11 @@ var as3cfModal = (function ( $ ) { /** * Open modal * - * @param string target - * @param function callback + * @param {string} target + * @param {function} callback + * @param {string} customClass */ - modal.open = function ( target, callback ) { + modal.open = function ( target, callback, customClass ) { var key = targetToKey( target ); // Overlay @@ -41,6 +43,10 @@ var as3cfModal = (function ( $ ) { } $modal.data( 'as3cf-modal-target', target ).append( modals[ key ] ); + if ( undefined !== customClass ) { + $modal.addClass( customClass ); + } + if ( 'function' === typeof callback ) { callback( target ); } @@ -58,9 +64,13 @@ var as3cfModal = (function ( $ ) { /** * Close modal * - * @param function callback + * @param {function} callback */ modal.close = function ( callback ) { + if ( modal.loading ) { + return; + } + var target = $( '#as3cf-modal' ).data( 'as3cf-modal-target' ); $( '#as3cf-overlay' ).fadeOut( 150, function () { @@ -76,6 +86,15 @@ var as3cfModal = (function ( $ ) { $( 'body' ).trigger( 'as3cf-modal-close', [ target ] ); }; + /** + * Set loading state + * + * @param {bool} state + */ + modal.setLoadingState = function ( state ) { + modal.loading = state; + }; + // Setup click handlers $( document ).ready( function () { @@ -99,101 +118,4 @@ var as3cfModal = (function ( $ ) { return modal; -})( jQuery ); - -var as3cfFindAndReplaceModal = (function ( $, as3cfModal ) { - - var modal = { - selector: '.as3cf-find-replace-container', - isBulk: false, - link: null, - payload: {} - }; - - /** - * Open modal - * - * @param string link - * @param mixed payload - */ - modal.open = function ( link, payload ) { - if ( typeof link !== 'undefined' ) { - modal.link = link; - } - if ( typeof payload !== 'undefined' ) { - modal.payload = payload; - } - - as3cfModal.open( modal.selector ); - - $( modal.selector ).find( '.single-file' ).show(); - $( modal.selector ).find( '.multiple-files' ).hide(); - if ( modal.isBulk ) { - $( modal.selector ).find( '.single-file' ).hide(); - $( modal.selector ).find( '.multiple-files' ).show(); - } - }; - - /** - * Close modal - */ - modal.close = function () { - as3cfModal.close( modal.selector ); - }; - - /** - * Set the isBulk flag - */ - modal.setBulk = function ( isBulk ) { - modal.isBulk = isBulk; - }; - - /** - * Create the loading state - */ - modal.startLoading = function () { - $( modal.selector + ' [data-find-replace]' ).prop( 'disabled', true ).siblings( '.spinner' ).css( 'visibility', 'visible' ).show(); - }; - - /** - * Remove the loading state - */ - modal.stopLoading = function () { - $( modal.selector + ' [data-find-replace]' ).prop( 'disabled', false ).siblings( '.spinner' ).css( 'visibility', 'hidden' ).hide(); - }; - - // Setup click handlers - $( document ).ready( function () { - - $( 'body' ).on( 'click', modal.selector + ' [data-find-replace]', function ( e ) { - var findAndReplace = $( this ).data( 'find-replace' ); - - if ( !modal.link ) { - // If there is no link set then this must be an AJAX - // request so trigger an event instead - $( modal.selector ).trigger( 'as3cf-find-and-replace', [ findAndReplace, modal.payload ] ); - return; - } - - if ( findAndReplace ) { - modal.link += '&find_and_replace=1'; - } - - modal.startLoading(); - - window.location = modal.link; - } ); - - $( 'body' ).on( 'as3cf-modal-close', function ( e ) { - modal.isBulk = false; - modal.link = null; - modal.payload = {}; - } ); - - } ); - - return modal; - -})( jQuery, as3cfModal ); - - +})( jQuery ); \ No newline at end of file diff --git a/assets/js/modal.min.js b/assets/js/modal.min.js index 301292c1..ccbbda03 100644 --- a/assets/js/modal.min.js +++ b/assets/js/modal.min.js @@ -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('
');var g=a("#as3cf-overlay");g.append('
×
');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); \ No newline at end of file +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('
');var h=a("#as3cf-overlay");h.append('
×
');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); \ No newline at end of file diff --git a/assets/js/script.js b/assets/js/script.js index e20abf21..b810f675 100644 --- a/assets/js/script.js +++ b/assets/js/script.js @@ -358,6 +358,9 @@ savedSettings[ id ] = serializedForm( id ); } + // Remove previous permission errors + $( '.as3cf-error.fatal' ).hide(); + $activeBucket.text( bucket ); $manualBucketForm.find( '.as3cf-bucket-name' ).val( bucket ); $( '#' + as3cfModal.prefix + '-bucket' ).val( bucket ); @@ -627,6 +630,16 @@ generateUrlPreview(); } ); + // Don't allow 'enter' key to submit form on text input settings + $( '.as3cf-setting input[type="text"]' ).keypress( function( event ) { + if ( 13 === event.which ) { + event.preventDefault(); + + return false; + } + + } ); + // Bucket select // -------------------- diff --git a/assets/js/script.min.js b/assets/js/script.min.js index bcc8b3cf..07bce0c7 100644 --- a/assets/js/script.min.js +++ b/assets/js/script.min.js @@ -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('
  • '+d.attr("data-working")+"
  • ");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('
  • '+b.Name+'
  • ')}),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+" —"),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().length1)return a(".as3cf-bucket-list a").removeClass("selected"),a('.as3cf-bucket-list a[data-bucket="'+e+'"]').addClass("selected"),void this.scrollToSelected();d.html('
  • '+d.attr("data-working")+"
  • ");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('
  • '+b.Name+'
  • ')}),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+" —"),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().lengthplugin_title = __( 'Offload S3', 'as3cf' ); $this->plugin_menu_title = __( 'S3 and CloudFront', 'as3cf' ); - // fire up the plugin upgrade checker - new AS3CF_Upgrade( $this ); + new AS3CF_Upgrade_Region_Meta( $this ); + new AS3CF_Upgrade_File_Sizes( $this ); add_action( 'aws_admin_menu', array( $this, 'admin_menu' ) ); add_action( 'wp_ajax_as3cf-get-buckets', array( $this, 'ajax_get_buckets' ) ); @@ -107,6 +107,7 @@ function init( $plugin_file_path ) { add_filter( 'update_attached_file', array( $this, 'update_attached_file' ), 100, 2 ); add_filter( 'get_attached_file', array( $this, 'get_attached_file' ), 10, 2 ); add_filter( 'plugin_action_links', array( $this, 'plugin_actions_settings_link' ), 10, 2 ); + add_filter( 'pre_get_space_used', array( $this, 'multisite_get_spaced_used' ) ); // include compatibility code for other plugins new AS3CF_Plugin_Compatibility( $this ); @@ -488,7 +489,7 @@ function wp_update_attachment_metadata( $data, $post_id ) { } // upload attachment to S3 - $this->upload_attachment_to_s3( $post_id, $data ); + $data = $this->upload_attachment_to_s3( $post_id, $data ); return $data; } @@ -503,10 +504,12 @@ function wp_update_attachment_metadata( $data, $post_id ) { * to cope with possible different regions * @param bool $remove_local_files * - * @return array|WP_Error $s3object + * @return array|WP_Error $s3object|$meta If meta is supplied, return it. Else return S3 meta */ function upload_attachment_to_s3( $post_id, $data = null, $file_path = null, $force_new_s3_client = false, $remove_local_files = true ) { + $return_metadata = true; if ( is_null( $data ) ) { + $return_metadata = false; $data = wp_get_attachment_metadata( $post_id, true ); } @@ -616,6 +619,26 @@ function upload_attachment_to_s3( $post_id, $data = null, $file_path = null, $fo $file_paths = $this->get_attachment_file_paths( $post_id, true, $data ); $additional_images = array(); + $filesize_total = 0; + $remove_local_files_setting = $this->get_setting( 'remove-local-file' ); + + if ( $remove_local_files_setting ) { + $bytes = filesize( $file_path ); + if ( false !== $bytes ) { + // Store in the attachment meta data for use by WP + $data['filesize'] = $bytes; + + if ( ! $return_metadata ) { + // Upload happening outside of 'wp_update_attachment_metadata' filter, + // So update metadata manually + update_post_meta( $post_id, '_wp_attachment_metadata', $data ); + } + + // Add to the file size total + $filesize_total += $bytes; + } + } + foreach ( $file_paths as $file_path ) { if ( ! in_array( $file_path, $files_to_remove ) ) { $additional_images[] = array( @@ -624,6 +647,14 @@ function upload_attachment_to_s3( $post_id, $data = null, $file_path = null, $fo ); $files_to_remove[] = $file_path; + + if ( $remove_local_files_setting ) { + // Record the file size for the additional image + $bytes = filesize( $file_path ); + if ( false !== $bytes ) { + $filesize_total += $bytes; + } + } } } @@ -639,20 +670,43 @@ function upload_attachment_to_s3( $post_id, $data = null, $file_path = null, $fo } if ( $remove_local_files ) { - if ( $this->get_setting( 'remove-local-file' ) ) { - if ( isset( $_POST['action'] ) && 'image-editor' == sanitize_key( $_POST['action'] ) && defined( 'DOING_AJAX' ) && DOING_AJAX ) { - // remove original main image after edit - $meta = get_post_meta( $post_id, '_wp_attachment_metadata', true ); - $original_file = trailingslashit( dirname( $file_path ) ) . basename( $meta['file'] ); - if ( file_exists( $original_file ) && ! in_array( $original_file, $files_to_remove ) ) { - $files_to_remove[] = $original_file; - } - } - + if ( $remove_local_files_setting ) { + // Allow other functions to remove files after they have processed + $files_to_remove = apply_filters( 'as3cf_upload_attachment_local_files_to_remove', $files_to_remove, $post_id, $file_path ); + // Remove duplicates + $files_to_remove = array_unique( $files_to_remove ); + + // Delete the files $this->remove_local_files( $files_to_remove ); } } + // Store the file size in the attachment meta if we are removing local file + if ( $remove_local_files_setting ) { + if ( $filesize_total > 0 ) { + // Add the total file size for all image sizes + update_post_meta( $post_id, 'wpos3_filesize_total', $filesize_total ); + } + } else { + if ( isset( $data['filesize'] ) ) { + // Make sure we don't have a cached file sizes in the meta + unset( $data['filesize'] ); + + if ( ! $return_metadata ) { + // Upload happening outside of 'wp_update_attachment_metadata' filter, + // So update metadata manually + update_post_meta( $post_id, '_wp_attachment_metadata', $data ); + } + + delete_post_meta( $post_id, 'wpos3_filesize_total' ); + } + } + + if ( $return_metadata ) { + // If the attachment metadata is supplied, return it + return $data; + } + return $s3object; } @@ -676,6 +730,13 @@ function get_hidpi_file_path( $orig_path ) { return $pathinfo['dirname'] . '/' . $pathinfo['filename'] . $hidpi_suffix . '.' . $pathinfo['extension']; } + /** + * Get the object versioning string prefix + * + * @param int $post_id + * + * @return string + */ function get_object_version_string( $post_id ) { if ( $this->get_setting( 'use-yearmonth-folders' ) ) { $date_format = 'dHis'; @@ -708,8 +769,15 @@ function get_folder_time_from_url( $url ) { return null; } - // Media files attached to a post use the post's date - // to determine the folder path they are placed in + /** + * Get the time of attachment upload. + * + * Use post datetime if attached. + * + * @param int $post_id + * + * @return int|string + */ function get_attachment_folder_time( $post_id ) { $time = current_time( 'timestamp' ); @@ -815,18 +883,39 @@ function get_attachment_s3_info( $post_id ) { return get_post_meta( $post_id, 'amazonS3_info', true ); } + /** + * Check the plugin is correctly setup + * + * @return bool + */ function is_plugin_setup() { - return (bool) $this->get_setting( 'bucket' ) && ! is_wp_error( $this->aws->get_client() ); + if ( is_wp_error( $this->aws->get_client() ) ) { + // AWS not configured + return false; + } + + if ( false === (bool) $this->get_setting( 'bucket' ) ) { + // No bucket selected + return false; + } + + if ( is_wp_error( $this->get_setting( 'region' ) ) ) { + // Region error when retrieving bucket location + return false; + } + + // All good, let's do this + return true; } /** * Generate a link to download a file from Amazon S3 using query string * authentication. This link is only valid for a limited amount of time. * - * @param int $post_id Post ID of the attachment - * @param int $expires Seconds for the link to live - * @param string $size Size of the image to get - * @param array $headers Header overrides for request + * @param int $post_id Post ID of the attachment + * @param int|null $expires Seconds for the link to live + * @param string|null $size Size of the image to get + * @param array $headers Header overrides for request * * @return mixed|void|WP_Error */ @@ -981,11 +1070,11 @@ function get_s3_url_domain( $bucket, $region = '', $expires = null, $args = arra /** * Get the url of the file from Amazon S3 * - * @param int $post_id Post ID of the attachment - * @param int $expires Seconds for the link to live - * @param string $size Size of the image to get - * @param array $meta Pre retrieved _wp_attachment_metadata for the attachment - * @param array $headers Header overrides for request + * @param int $post_id Post ID of the attachment + * @param int|null $expires Seconds for the link to live + * @param string|null $size Size of the image to get + * @param array|null $meta Pre retrieved _wp_attachment_metadata for the attachment + * @param array $headers Header overrides for request * * @return bool|mixed|void|WP_Error */ @@ -1016,7 +1105,7 @@ function get_attachment_url( $post_id, $expires = null, $size = null, $meta = nu $domain_bucket = $this->get_s3_url_domain( $s3object['bucket'], $region, $expires ); - if ( $size ) { + if ( ! is_null( $size ) ) { if ( is_null( $meta ) ) { $meta = get_post_meta( $post_id, '_wp_attachment_metadata', true ); } @@ -1027,23 +1116,20 @@ function get_attachment_url( $post_id, $expires = null, $size = null, $meta = nu if ( ! is_null( $expires ) ) { try { - $expires = time() + $expires; + $expires = time() + $expires; $secure_url = $this->get_s3client( $region )->getObjectUrl( $s3object['bucket'], $s3object['key'], $expires, $headers ); + + return apply_filters( 'as3cf_get_attachment_secure_url', $secure_url, $s3object, $post_id, $expires, $headers ); } catch ( Exception $e ) { return new WP_Error( 'exception', $e->getMessage() ); } } - // encode file $file = $this->encode_filename_in_path( $s3object['key'] ); + $url = $scheme . '://' . $domain_bucket . '/' . $file; - $url = $scheme . '://' . $domain_bucket . '/' . $file; - if ( isset( $secure_url ) ) { - $url .= substr( $secure_url, strpos( $secure_url, '?' ) ); - } - - return apply_filters( 'as3cf_get_attachment_url', $url, $s3object, $post_id, $expires ); + return apply_filters( 'as3cf_get_attachment_url', $url, $s3object, $post_id, $expires, $headers ); } /** @@ -1410,8 +1496,12 @@ function get_aws_regions() { * @param Amazon_Web_Services $aws */ function admin_menu( $aws ) { - $this->hook_suffix = $aws->add_page( $this->get_plugin_page_title(), $this->plugin_menu_title, 'manage_options', $this->plugin_slug, array( $this, 'render_page' ) ); - add_action( 'load-' . $this->hook_suffix , array( $this, 'plugin_load' ) ); + $hook_suffix = $aws->add_page( $this->get_plugin_page_title(), $this->plugin_menu_title, 'manage_options', $this->plugin_slug, array( $this, 'render_page' ) ); + + if ( false !== $hook_suffix ) { + $this->hook_suffix = $hook_suffix; + add_action( 'load-' . $this->hook_suffix, array( $this, 'plugin_load' ) ); + } } /** @@ -1450,12 +1540,12 @@ function get_s3client( $region = false, $force = false ) { function get_bucket_region( $bucket ) { try { $region = $this->get_s3client()->getBucketLocation( array( 'Bucket' => $bucket ) ); - } - catch ( Exception $e ) { - $error_msg = sprintf( __( 'There was an error attempting to get the region of the bucket %s: %s', 'as3cf' ), $bucket, $e->getMessage() ); + } catch ( Exception $e ) { + $error_msg_title = '' . __( 'Error Getting Bucket Region', 'as3cf' ) . ' —'; + $error_msg = sprintf( __( 'There was an error attempting to get the region of the bucket %s: %s', 'as3cf' ), $bucket, $e->getMessage() ); error_log( $error_msg ); - return new WP_Error( 'exception', $e->getMessage() ); + return new WP_Error( 'exception', $error_msg_title . $error_msg ); } $region = $this->translate_region( $region['Location'] ); @@ -1508,7 +1598,7 @@ function translate_region( $region ) { $region = strtolower( $region ); switch ( $region ) { - case 'eu' : + case 'eu': $region = 'eu-west-1'; break; } @@ -1924,7 +2014,7 @@ function is_pro() { * Apply ACL to an attachment and associated files * * @param int $post_id - * @param object $s3object + * @param array $s3object * @param string $acl */ function set_attachment_acl_on_s3( $post_id, $s3object, $acl ) { @@ -2144,6 +2234,21 @@ function output_diagnostic_info( $escape = true ) { } echo "\r\n\r\n"; + $media_counts = $this->diagnostic_media_counts(); + + echo 'Media Files: '; + echo number_format_i18n( $media_counts['all'] ); + echo "\r\n"; + + echo 'Media Files on S3: '; + echo number_format_i18n( $media_counts['s3'] ); + echo "\r\n"; + + echo 'Number of Image Sizes: '; + $sizes = count( get_intermediate_image_sizes() ); + echo number_format_i18n( $sizes ); + echo "\r\n\r\n"; + echo 'Bucket: '; echo $this->get_setting( 'bucket' ); echo "\r\n"; @@ -2215,7 +2320,7 @@ function output_diagnostic_info( $escape = true ) { /** * Helper for displaying settings * - * @param $key setting key + * @param string $key setting key * * @return string */ @@ -2228,7 +2333,7 @@ function on_off( $key ) { /** * Helper to display plugin details * - * @param $plugin_path + * @param string $plugin_path * @param string $suffix */ function print_plugin_details( $plugin_path, $suffix = '' ) { @@ -2317,6 +2422,32 @@ function is_current_blog( $blog_id ) { return false; } + /** + * Helper to switch to a Multisite blog + * - If the site is MS + * - If the blog is not the current blog defined + * + * @param $blog_id + */ + function switch_to_blog( $blog_id ) { + if ( is_multisite() && ! $this->is_current_blog( $blog_id ) ) { + switch_to_blog( $blog_id ); + } + } + + /** + * Helper to restore to the current Multisite blog + * - If the site is MS + * - If the blog is not the current blog defined + * + * @param $blog_id + */ + function restore_current_blog( $blog_id ) { + if ( is_multisite() && ! $this->is_current_blog( $blog_id ) ) { + restore_current_blog(); + } + } + /** * Get all the table prefixes for the blogs in the site. MS compatible * @@ -2437,4 +2568,125 @@ function get_access_denied_notice_message( $single = true ) { return $message; } + + /** + * Used to give a realistic total of storage space used on a Multisite subsite, + * when there have been attachments uploaded to S3 but removed from server + * + * @param $space_used bool + * + * @return float|int + */ + function multisite_get_spaced_used( $space_used ) { + if ( false === ( $space_used = get_transient( 'wpos3_site_space_used' ) ) ) { + global $wpdb; + + // Sum the total file size (including image sizes) for all S3 attachments + $sql = "SELECT SUM( meta_value ) AS bytes_total + FROM {$wpdb->postmeta} + WHERE meta_key = 'wpos3_filesize_total'"; + + $space_used = $wpdb->get_var( $sql ); + + // Get local upload sizes + $upload_dir = wp_upload_dir(); + $space_used += get_dirsize( $upload_dir['basedir'] ); + + if ( $space_used > 0 ) { + // Convert to bytes to MB + $space_used = $space_used / 1024 / 1024; + } + + set_transient( 'wpos3_site_space_used', $space_used, HOUR_IN_SECONDS ); + } + + return $space_used; + } + + /** + * Memory exceeded + * + * Ensures the a process never exceeds 90% of the maximum WordPress memory. + * + * @param null|string $filter_name Name of filter to apply to the return + * + * @return bool + */ + public function memory_exceeded( $filter_name = null ) { + $current_memory = memory_get_usage( true ); + $memory_limit = ( intval( WP_MEMORY_LIMIT ) * 1024 * 1024 ) * 0.9; // 90% of max memory + $return = false; + + if ( $current_memory >= $memory_limit ) { + $return = true; + } + + if ( is_null( $filter_name ) || ! is_string( $filter_name ) ) { + return $return; + } + + return apply_filters( $filter_name, $return ); + } + + /** + * Count attachments on a site + * + * @param string $prefix + * @param null|bool $uploaded_to_s3 + * null - All attachments + * true - Attachments only uploaded to S3 + * false - Attachments not uploaded to S3 + * + * @return null|string + */ + public function count_attachments( $prefix, $uploaded_to_s3 = null ) { + global $wpdb; + + $sql = "SELECT COUNT(*) + FROM `{$prefix}posts` p"; + + $where = "WHERE p.post_type = 'attachment'"; + + if ( ! is_null( $uploaded_to_s3 ) && is_bool( $uploaded_to_s3 ) ) { + $sql .= " LEFT OUTER JOIN `{$prefix}postmeta` pm + ON p.`ID` = pm.`post_id` + AND pm.`meta_key` = 'amazonS3_info'"; + + $operator = $uploaded_to_s3 ? 'not ' : ''; + $where .= " AND pm.`post_id` is {$operator}null"; + } + + $sql .= ' ' . $where; + + return $wpdb->get_var( $sql ); + } + + /** + * Get the total attachment and total S3 attachment counts for the diagnostic log + * + * @return array + */ + protected function diagnostic_media_counts() { + if ( false === ( $attachment_counts = get_site_transient( 'wpos3_attachment_counts' ) ) ) { + $table_prefixes = $this->get_all_blog_table_prefixes(); + $all_media = 0; + $all_media_s3 = 0; + + foreach ( $table_prefixes as $blog_id => $table_prefix ) { + $count = $this->count_attachments( $table_prefix ); + $all_media += $count; + $s3_count = $this->count_attachments( $table_prefix, true ); + $all_media_s3 += $s3_count; + } + + $attachment_counts = array( + 'all' => $all_media, + 's3' => $all_media_s3, + ); + + set_site_transient( 'wpos3_attachment_counts', $attachment_counts, 2 * HOUR_IN_SECONDS ); + } + + return $attachment_counts; + } } diff --git a/classes/as3cf-plugin-compatibility.php b/classes/as3cf-plugin-compatibility.php index ab551fd5..09f7c163 100644 --- a/classes/as3cf-plugin-compatibility.php +++ b/classes/as3cf-plugin-compatibility.php @@ -50,13 +50,15 @@ function compatibility_init() { */ add_action( 'as3cf_upload_attachment_pre_remove', array( $this, 'image_editor_remove_files' ), 10, 4 ); add_filter( 'as3cf_get_attached_file', array( $this, 'image_editor_download_file' ), 10, 4 ); + add_filter( 'as3cf_upload_attachment_local_files_to_remove', array( $this, 'image_editor_remove_original_image' ), 10, 3 ); + add_filter( 'as3cf_get_attached_file', array( $this, 'customizer_header_crop_download_file' ), 10, 4 ); + add_filter( 'as3cf_upload_attachment_local_files_to_remove', array( $this, 'customizer_header_crop_remove_original_image' ), 10, 3 ); /* * WP_Customize_Control * /wp-includes/class-wp-customize_control.php */ add_filter( 'attachment_url_to_postid', array( $this, 'customizer_background_image' ), 10, 2 ); - /* * Regenerate Thumbnails * https://wordpress.org/plugins/regenerate-thumbnails/ @@ -91,6 +93,25 @@ function legacy_copy_back_to_local( $url, $file, $attachment_id, $s3_object ) { return $url; } + /** + * Get the file path of the original image file before an update + * + * @param int $post_id + * @param string $file_path + * + * @return bool|string + */ + function get_original_image_file( $post_id, $file_path ) { + // remove original main image after edit + $meta = get_post_meta( $post_id, '_wp_attachment_metadata', true ); + $original_file = trailingslashit( dirname( $file_path ) ) . basename( $meta['file'] ); + if ( file_exists( $original_file ) ) { + return $original_file; + } + + return false; + } + /** * Allow the WordPress Image Editor to remove edited version of images * if the original image is being restored and 'IMAGE_EDIT_OVERWRITE' is set @@ -144,9 +165,9 @@ function image_editor_download_file( $url, $file, $attachment_id, $s3_object ) { $this->copy_s3_file_to_server( $orig_s3, $orig_file ); // Copy the edited file back to the server as well, it will be cleaned up later - if ( ( $file = $this->copy_s3_file_to_server( $s3_object, $file ) ) ) { + if ( ( $s3_file = $this->copy_s3_file_to_server( $s3_object, $file ) ) ) { // Return the file if successfully downloaded from S3 - return $file; + return $s3_file; }; } @@ -156,9 +177,9 @@ function image_editor_download_file( $url, $file, $attachment_id, $s3_object ) { foreach ( $callers as $caller ) { if ( isset( $caller['function'] ) && '_load_image_to_edit_path' == $caller['function'] ) { // check this has been called by '_load_image_to_edit_path' so as only to copy back once - if ( ( $file = $this->copy_s3_file_to_server( $s3_object, $file ) ) ) { + if ( ( $s3_file = $this->copy_s3_file_to_server( $s3_object, $file ) ) ) { // Return the file if successfully downloaded from S3 - return $file; + return $s3_file; }; } } @@ -167,6 +188,82 @@ function image_editor_download_file( $url, $file, $attachment_id, $s3_object ) { return $url; } + /** + * Allow the WordPress Image Editor to remove the main image file after it has been copied + * back from S3 after it has done the edit. + * + * @param array $files + * @param int $post_id + * @param string $file_path + * + * @return array + */ + function image_editor_remove_original_image( $files, $post_id, $file_path ) { + if ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) { + return $files; + } + + if ( isset( $_POST['action'] ) && 'image-editor' === sanitize_key( $_POST['action'] ) ) { // input var okay + // remove original main image after edit + if ( ( $original_file = $this->get_original_image_file( $post_id, $file_path ) ) ) { + $files[] = $original_file; + } + } + + return $files; + } + + /** + * Allow the WordPress Customizer to crop images that have been copied to S3 + * but removed from the local server, by copying them back temporarily + * + * @param string $url + * @param string $file + * @param int $attachment_id + * @param array $s3_object + * + * @return string + */ + function customizer_header_crop_download_file( $url, $file, $attachment_id, $s3_object ) { + if ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) { + return $url; + } + + if ( isset( $_POST['action'] ) && 'custom-header-crop' === sanitize_key( $_POST['action'] ) ) { // input var okay + if ( ( $file = $this->copy_s3_file_to_server( $s3_object, $file ) ) ) { + // Return the file if successfully downloaded from S3 + return $file; + }; + } + + return $url; + } + + /** + * Allow the WordPress Image Editor to remove the main image file after it has been copied + * back from S3 after it has done the edit. + * + * @param array $files + * @param int $post_id + * @param string $file_path + * + * @return array + */ + function customizer_header_crop_remove_original_image( $files, $post_id, $file_path ) { + if ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) { + return $files; + } + + if ( isset( $_POST['action'] ) && 'custom-header-crop' === sanitize_key( $_POST['action'] ) ) { // input var okay + // remove original main image after edit + if ( ( $original_file = $this->get_original_image_file( $_POST['id'], $file_path ) ) ) { + $files[] = $original_file; + } + } + + return $files; + } + /** * Show the correct background image in the customizer * diff --git a/classes/as3cf-upgrade.php b/classes/as3cf-upgrade.php index 0314ba60..e63aba3c 100644 --- a/classes/as3cf-upgrade.php +++ b/classes/as3cf-upgrade.php @@ -17,18 +17,66 @@ /** * AS3CF_Upgrade Class * - * This class handles data updates and other migrations after a plugin update + * This class handles updates to attachments and attachment meta data * * @since 0.6.2 */ -class AS3CF_Upgrade { +abstract class AS3CF_Upgrade { - private $as3cf; - private $cron_interval_in_minutes; - private $error_threshold; + /** + * @var Amazon_S3_And_CloudFront + */ + protected $as3cf; + + /** + * @var int + */ + protected $upgrade_id; + + /** + * @var string + */ + protected $upgrade_name; + + /** + * @var string 'metadata', 'attachment' + */ + protected $upgrade_type; + + /** + * @var string + */ + protected $running_update_text; + + /** + * @var string + */ + protected $settings_key = 'post_meta_version'; + + /** + * @var string + */ + protected $cron_hook; - const CRON_HOOK = 'as3cf_cron_update_meta_with_region'; - const CRON_SCHEDULE_KEY = 'as3cf_update_meta_with_region_interval'; + /** + * @var string + */ + protected $cron_schedule_key; + + /** + * @var mixed|void + */ + protected $cron_interval_in_minutes; + + /** + * @var mixed|void + */ + protected $error_threshold; + + /** + * @var int + */ + protected $error_count; const STATUS_RUNNING = 1; const STATUS_ERROR = 2; @@ -42,169 +90,122 @@ class AS3CF_Upgrade { function __construct( $as3cf ) { $this->as3cf = $as3cf; - $this->cron_interval_in_minutes = apply_filters( 'as3cf_update_meta_with_region_interval', 10 ); - $this->error_threshold = apply_filters( 'as3cf_update_meta_with_region_error_threshold', 20 ); + $this->cron_hook = 'as3cf_cron_update_' . $this->upgrade_name; + $this->cron_schedule_key = 'as3cf_update_' . $this->upgrade_name . '_interval'; + + $this->cron_interval_in_minutes = apply_filters( 'as3cf_update_' . $this->upgrade_name . '_interval', 5 ); + $this->error_threshold = apply_filters( 'as3cf_update_' . $this->upgrade_name . '_error_threshold', 20 ); add_filter( 'cron_schedules', array( $this, 'cron_schedules' ) ); - add_action( self::CRON_HOOK, array( $this, 'cron_update_meta_with_region' ) ); + add_action( $this->cron_hook, array( $this, 'do_upgrade' ) ); add_action( 'as3cf_pre_settings_render', array( $this, 'maybe_display_notices' ) ); add_action( 'admin_init', array( $this, 'maybe_handle_action' ) ); - $this->maybe_init_upgrade(); + // Do default checks if the upgrade can be started + if ( $this->maybe_init() ) { + $this->init(); + } } /** - * Maybe initialize the upgrade + * Can we start the upgrade using default checks + * + * @return bool */ - function maybe_init_upgrade() { + protected function maybe_init() { if ( ! is_admin() || ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) { - return; + return false; } // make sure this only fires inside the network admin for multisites if ( is_multisite() && ! is_network_admin() ) { - return; - } - - // Have we completed the upgrade yet? - if ( $this->as3cf->get_setting( 'post_meta_version', 0 ) > 0 ) { - return; + return false; } // If the upgrade status is already set, then we've already initialized the upgrade if ( $this->get_upgrade_status() ) { - return; + return false; } - // Do we actually have S3 meta data without regions to update? - // No need to bother for fresh sites, or media not uploaded to S3 - if ( 0 == $this->count_all_attachments_without_region() ) { - $this->as3cf->set_setting( 'post_meta_version', 1 ); - $this->as3cf->save_settings(); - - return; + // Have we completed the upgrade? + if ( $this->as3cf->get_setting( $this->settings_key, 0 ) >= $this->upgrade_id ) { + return false; } - // Initialize the upgrade - $this->save_session( array( 'status' => self::STATUS_RUNNING ) ); - - $this->as3cf->schedule_event( self::CRON_HOOK, self::CRON_SCHEDULE_KEY ); - } - - /** - * Adds notices about issues with upgrades allowing user to restart them - */ - function maybe_display_notices() { - $action_url = $this->as3cf->get_plugin_page_url( array( 'action' => 'restart_update_meta_with_region' ), 'self' ); - $msg_type = 'notice-info'; - - switch ( $this->get_upgrade_status() ) { - case self::STATUS_RUNNING : - $msg = sprintf( __( 'Running Metadata Update — We’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 (e.g. s3-us-west-2.amazonaws.com). 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’s performance.', 'as3cf' ), $this->cron_interval_in_minutes ); - $action_text = __( 'Pause Update', 'as3cf' ); - $action_url = $this->as3cf->get_plugin_page_url( array( 'action' => 'pause_update_meta_with_region' ), 'self' ); - break; - case self::STATUS_PAUSED : - $msg = __( 'Metadata Update Paused — Updating Media Library metadata has been paused.', 'as3cf' ); - $action_text = __( 'Restart Update', 'as3cf' ); - break; - case self::STATUS_ERROR : - $msg = __( 'Error Updating Metadata — 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' ); - $action_text = __( 'Try Run It Again', 'as3cf' ); - $msg_type = 'error'; - break; - default : - return; + // Has the previous upgrade completed yet? + $previous_id = $this->upgrade_id - 1; + if ( 0 !== $previous_id && (int) $this->as3cf->get_setting( $this->settings_key, 0 ) < $previous_id ) { + // Previous still running, abort + return false; } - $msg .= ' ' . $action_text . ''; - - $args = array( - 'message' => $msg, - 'type' => $msg_type, - ); + // Do we actually attachments to process? + if ( 0 === $this->count_attachments_to_process() ) { + $this->upgrade_finished(); - $this->as3cf->render_view( 'notice', $args ); - } - - function maybe_handle_action() { - if ( ! isset( $_GET['page'] ) || sanitize_key( $_GET['page'] ) != $this->as3cf->get_plugin_slug() || ! isset( $_GET['action'] ) ) { // input var okay - return; + return false; } - $method_name = 'action_' . sanitize_key( $_GET['action'] ); // input var okay - if ( method_exists( $this, $method_name ) ) { - call_user_func( array( $this, $method_name ) ); - } + return true; } /** - * Restart upgrade + * @return int */ - function action_restart_update_meta_with_region() { - $this->change_status_request( self::STATUS_RUNNING ); - $this->as3cf->schedule_event( self::CRON_HOOK, self::CRON_SCHEDULE_KEY ); - } + abstract protected function count_attachments_to_process(); /** - * Pause upgrade + * @param $prefix + * @param $limit + * + * @return array */ - function action_pause_update_meta_with_region() { - $this->clear_scheduled_event(); - $this->change_status_request( self::STATUS_PAUSED ); - } + abstract protected function get_attachments_to_process( $prefix, $limit ); /** - * Helper for the above action requests + * @param $attachment * - * @param integer $status + * @return bool */ - function change_status_request( $status ) { - $session = $this->get_session(); - $session['status'] = $status; - $this->save_session( $session ); - - $url = $this->as3cf->get_plugin_page_url( array(), 'self' ); - wp_redirect( $url ); - } + abstract protected function upgrade_attachment( $attachment ); /** - * Add custom cron interval schedules - * - * @param array $schedules - * - * @return array + * Fire up the upgrade */ - function cron_schedules( $schedules ) { - // Adds every 10 minutes to the existing schedules. - $schedules[ self::CRON_SCHEDULE_KEY ] = array( - 'interval' => $this->cron_interval_in_minutes * 60, - 'display' => sprintf( __( 'Every %d Minutes', 'as3cf' ), $this->cron_interval_in_minutes ), - ); + protected function init() { + // Initialize the upgrade + $this->save_session( array( 'status' => self::STATUS_RUNNING ) ); - return $schedules; + $this->as3cf->schedule_event( $this->cron_hook, $this->cron_schedule_key ); } - + /** * Cron jon to update the region of the bucket in s3 metadata */ - function cron_update_meta_with_region() { + function do_upgrade() { // Check if the cron should even be running - if ( $this->as3cf->get_setting( 'post_meta_version', 0 ) > 0 || $this->get_upgrade_status() != self::STATUS_RUNNING ) { - $this->as3cf->clear_scheduled_event( self::CRON_HOOK ); + if ( $this->as3cf->get_setting( $this->settings_key, 0 ) >= $this->upgrade_id || $this->get_upgrade_status() !== self::STATUS_RUNNING ) { + $this->as3cf->clear_scheduled_event( $this->cron_hook ); + return; } // set the batch size limit for the query - $limit = apply_filters( 'as3cf_update_meta_with_region_batch_size', 500 ); + $limit = apply_filters( 'as3cf_update_' . $this->upgrade_name . '_batch_size', 500 ); $all_limit = $limit; - $session = $this->get_session(); + // only process the loop for a certain amount of time + $minutes = $this->cron_interval_in_minutes * 60; + // smaller time limit so won't run into another instance of cron + $minutes = $minutes * 0.8; + $finish = time() + $minutes; + + $session = $this->get_session(); // find the blog IDs that have been processed so we can skip them $processed_blog_ids = isset( $session['processed_blog_ids'] ) ? $session['processed_blog_ids'] : array(); - $error_count = isset( $session['error_count'] ) ? $session['error_count'] : 0; + $this->error_count = isset( $session['error_count'] ) ? $session['error_count'] : 0; // get the table prefixes for all the blogs $table_prefixes = $this->as3cf->get_all_blog_table_prefixes( $processed_blog_ids ); @@ -213,10 +214,10 @@ function cron_update_meta_with_region() { $all_count = 0; foreach ( $table_prefixes as $blog_id => $table_prefix ) { - $attachments = $this->get_attachments_without_region( $table_prefix, $limit ); + $attachments = $this->get_attachments_to_process( $table_prefix, $limit ); $count = count( $attachments ); - if ( 0 == $count ) { + if ( 0 === $count ) { // no more attachments, record the blog ID to skip next time $processed_blog_ids[] = $blog_id; } else { @@ -231,204 +232,205 @@ function cron_update_meta_with_region() { $limit = $limit - $count; } - if ( 0 == $all_count ) { - $this->as3cf->set_setting( 'post_meta_version', 1 ); - $this->as3cf->remove_setting( 'update_meta_with_region_session' ); - $this->as3cf->save_settings(); - $this->as3cf->clear_scheduled_event( self::CRON_HOOK ); + if ( 0 === $all_count ) { + $this->upgrade_finished(); + return; } - // only process the loop for a certain amount of time - $minutes = $this->cron_interval_in_minutes * 60; - - // smaller time limit so won't run into another instance of cron - $minutes = $minutes * 0.8; - - $finish = time() + $minutes; - // loop through and update s3 meta with region foreach ( $all_attachments as $blog_id => $attachments ) { - if ( is_multisite() && ! $this->as3cf->is_current_blog( $blog_id ) ) { - switch_to_blog( $blog_id ); - } + $this->as3cf->switch_to_blog( $blog_id ); foreach ( $attachments as $attachment ) { - if ( $error_count >= $this->error_threshold ) { - $session['status'] = self::STATUS_ERROR; - $this->save_session( $session ); - $this->clear_scheduled_event(); - return; - } + if ( $this->error_count >= $this->error_threshold ) { + $this->upgrade_error( $session ); - if ( time() >= $finish ) { - break; + return; } - $s3object = unserialize( $attachment->s3object ); - if ( false === $s3object ) { - error_log( 'Failed to unserialize S3 meta for attachment ' . $attachment->ID . ': ' . $attachment->s3object ); - $error_count++; - continue; - } + // Do the actual upgrade to the attachment + $this->upgrade_attachment( $attachment ); - // retrieve region and update the attachment metadata - $region = $this->as3cf->get_s3object_region( $s3object, $attachment->ID ); - if ( is_wp_error( $region ) ) { - error_log( 'Error updating region: ' . $region->get_error_message() ); - $error_count++; + if ( time() >= $finish || $this->as3cf->memory_exceeded( 'as3cf_update_' . $this->upgrade_name . '_memory_exceeded' ) ) { + // Batch limits reached + break 2; } } - - if ( is_multisite() && ! $this->as3cf->is_current_blog( $blog_id ) ) { - restore_current_blog(); - } } + $this->as3cf->restore_current_blog( $blog_id ); + $session['processed_blog_ids'] = $processed_blog_ids; - $session['error_count'] = $error_count; + $session['error_count'] = $this->error_count; $this->save_session( $session ); } /** - * Get a count of all attachments without region in their S3 metadata - * for the whole site - * - * @return int + * Adds notices about issues with upgrades allowing user to restart them */ - function count_all_attachments_without_region() { - // get the table prefixes for all the blogs - $table_prefixes = $this->as3cf->get_all_blog_table_prefixes(); - $all_count = 0; + function maybe_display_notices() { + $action_url = $this->as3cf->get_plugin_page_url( array( 'action' => 'restart_update', 'update' => $this->upgrade_name ), 'self' ); + $msg_type = 'notice-info'; - foreach ( $table_prefixes as $blog_id => $table_prefix ) { - $count = $this->count_attachments_without_region( $table_prefix ); - $all_count += $count; + switch ( $this->get_upgrade_status() ) { + case self::STATUS_RUNNING: + $msg = sprintf( __( 'Running %s Update — We’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’s performance.', 'as3cf' ), ucfirst( $this->upgrade_type ), $this->running_update_text, $this->cron_interval_in_minutes ); + $action_text = __( 'Pause Update', 'as3cf' ); + $action_url = $this->as3cf->get_plugin_page_url( array( 'action' => 'pause_update', 'update' => $this->upgrade_name ), 'self' ); + break; + case self::STATUS_PAUSED: + $msg = sprintf( __( '%s Update Paused — Updating Media Library %s has been paused.', 'as3cf' ), ucfirst( $this->upgrade_type ), $this->upgrade_type ); + $action_text = __( 'Restart Update', 'as3cf' ); + break; + case self::STATUS_ERROR: + $msg = sprintf( __( 'Error Updating %s — 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 ); + $action_text = __( 'Try Run It Again', 'as3cf' ); + $msg_type = 'error'; + break; + default: + return; } - return $all_count; + $msg .= ' ' . $action_text . ''; + + $args = array( + 'message' => $msg, + 'type' => $msg_type, + ); + + $this->as3cf->render_view( 'notice', $args ); } /** - * Get the current status of the upgrade - * See STATUS_* constants in the class declaration above. + * Handler for the running upgrade actions */ - function get_upgrade_status() { - $session = $this->get_session(); - - if ( ! isset( $session['status'] ) ) { - return ''; + function maybe_handle_action() { + if ( ! isset( $_GET['page'] ) || sanitize_key( $_GET['page'] ) !== $this->as3cf->get_plugin_slug() || ! isset( $_GET['action'] ) ) { // input var okay + return; } - return $session['status']; + $method_name = 'action_' . sanitize_key( $_GET['action'] ); // input var okay + if ( method_exists( $this, $method_name ) ) { + call_user_func( array( $this, $method_name ) ); + } } /** - * Retrieve session data from plugin settings + * Exit upgrade with an error * - * @return array + * @param array $session */ - function get_session() { - return $this->as3cf->get_setting( 'update_meta_with_region_session', array() ); + function upgrade_error( $session ) { + $session['status'] = self::STATUS_ERROR; + $this->save_session( $session ); + $this->as3cf->clear_scheduled_event( $this->cron_hook ); } /** - * Store data to be used between requests in plugin settings - * - * @param $session array of session data to store + * Complete the upgrade */ - function save_session( $session ) { - $this->as3cf->set_setting( 'update_meta_with_region_session', $session ); + function upgrade_finished() { + $this->clear_session(); + $this->as3cf->set_setting( $this->settings_key, $this->upgrade_id ); $this->as3cf->save_settings(); + $this->as3cf->clear_scheduled_event( $this->cron_hook ); } /** - * Get all the table prefixes for the blogs in the site. MS compatible - * - * @param array $exclude_blog_ids blog ids to exclude - * - * @return array associative array with blog ID as key, prefix as value + * Restart upgrade */ - function get_all_blog_table_prefixes( $exclude_blog_ids = array() ) { - global $wpdb; - $prefix = $wpdb->prefix; - - $table_prefixes = array(); - - if ( ! in_array( 1, $exclude_blog_ids ) ) { - $table_prefixes[1] = $prefix; - } - - if ( is_multisite() ) { - $blog_ids = $this->as3cf->get_blog_ids(); - foreach ( $blog_ids as $blog_id ) { - if ( in_array( $blog_id, $exclude_blog_ids ) ) { - continue; - } - $table_prefixes[ $blog_id ] = $wpdb->get_blog_prefix( $blog_id ); - } + function action_restart_update() { + if ( ! isset( $_GET['update'] ) || $this->upgrade_name !== sanitize_key( $_GET['update'] ) ) { + return; } - return $table_prefixes; + $this->as3cf->schedule_event( $this->cron_hook, $this->cron_schedule_key ); + $this->change_status_request( self::STATUS_RUNNING ); } /** - * Get all attachments that don't have region in their S3 meta data for a blog - * - * @param string $prefix - * @param int $limit - * - * @return mixed + * Pause upgrade */ - function get_attachments_without_region( $prefix, $limit ) { - $attachments = $this->get_attachments_without_region_results( $prefix, false, $limit ); + function action_pause_update() { + if ( ! isset( $_GET['update'] ) || $this->upgrade_name !== sanitize_key( $_GET['update'] ) ) { + return; + } - return $attachments; + $this->as3cf->clear_scheduled_event( $this->cron_hook ); + $this->change_status_request( self::STATUS_PAUSED ); } /** - * Get a count of attachments that don't have region in their S3 meta data for a blog - * @param $prefix + * Helper for the above action requests * - * @return int + * @param int $status */ - function count_attachments_without_region( $prefix ) { - $count = $this->get_attachments_without_region_results( $prefix, true ); + function change_status_request( $status ) { + $session = $this->get_session(); + $session['status'] = $status; + $this->save_session( $session ); - return $count; + $url = $this->as3cf->get_plugin_page_url( array(), 'self' ); + wp_redirect( $url ); + exit; } /** - * Wrapper for database call to get attachments without region + * Add custom cron interval schedules * - * @param string $prefix - * @param bool $count return count of attachments - * @param null|int $limit + * @param array $schedules * - * @return mixed + * @return array */ - function get_attachments_without_region_results( $prefix, $count = false, $limit = null ) { - global $wpdb; + function cron_schedules( $schedules ) { + // Add the upgrade interval to the existing schedules. + $schedules[ $this->cron_schedule_key ] = array( + 'interval' => $this->cron_interval_in_minutes * 60, + 'display' => sprintf( __( 'Every %d Minutes', 'as3cf' ), $this->cron_interval_in_minutes ), + ); - $sql = " FROM `{$prefix}postmeta` - WHERE `meta_key` = 'amazonS3_info' - AND `meta_value` NOT LIKE '%%\"region\"%%'"; + return $schedules; + } - if ( $count ) { - $sql = 'SELECT COUNT(*)' . $sql; + /** + * Get the current status of the upgrade + * See STATUS_* constants in the class declaration above. + */ + function get_upgrade_status() { + $session = $this->get_session(); - return $wpdb->get_var( $sql ); + if ( ! isset( $session['status'] ) ) { + return ''; } - $sql = "SELECT `post_id` as `ID`, `meta_value` AS 's3object'" . $sql; + return $session['status']; + } - if ( ! is_null( $limit ) ) { - $sql .= ' LIMIT %d'; + /** + * Retrieve session data from plugin settings + * + * @return array + */ + function get_session() { + return get_site_option( 'update_' . $this->upgrade_name . '_session', array() ); + } - $sql = $wpdb->prepare( $sql, $limit ); - } + /** + * Store data to be used between requests in plugin settings + * + * @param array $session session data to store + */ + function save_session( $session ) { + update_site_option( 'update_' . $this->upgrade_name . '_session', $session ); + } - return $wpdb->get_results( $sql, OBJECT ); + /** + * Remove the session data to be used between requests + * + */ + function clear_session() { + delete_site_option( 'update_' . $this->upgrade_name . '_session' ); } } diff --git a/classes/upgrades/as3cf-file-sizes.php b/classes/upgrades/as3cf-file-sizes.php new file mode 100644 index 00000000..c70764ac --- /dev/null +++ b/classes/upgrades/as3cf-file-sizes.php @@ -0,0 +1,215 @@ +upgrade_id = 2; + $this->upgrade_name = 'file_sizes'; + $this->upgrade_type = 'attachments'; + + $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' ); + + parent::__construct( $as3cf ); + } + + /** + * Get the total file sizes for an attachment and associated files. + * + * @param $attachment + * + * @return bool + */ + function upgrade_attachment( $attachment ) { + $s3object = unserialize( $attachment->s3object ); + if ( false === $s3object ) { + error_log( 'Failed to unserialize S3 meta for attachment ' . $attachment->ID . ': ' . $attachment->s3object ); + $this->error_count++; + + return false; + } + + $region = $this->as3cf->get_s3object_region( $s3object ); + if ( is_wp_error( $region ) ) { + error_log( 'Failed to get the region for the bucket of the attachment ' . $attachment->ID ); + $this->error_count++; + + return false; + } + + $s3client = $this->as3cf->get_s3client( $region, true ); + $main_file = $s3object['key']; + + $path_parts = pathinfo( $main_file ); + $prefix = trailingslashit( dirname( $s3object['key'] ) ); + + // Used to search S3 for all files related to an attachment + $search_prefix = $prefix . basename( $main_file, '.' . $path_parts['extension'] ); + + $args = array( + 'Bucket' => $s3object['bucket'], + 'Prefix' => $search_prefix, + ); + + try { + // List objects for the attachment + $result = $s3client->ListObjects( $args ); + } catch ( Exception $e ) { + error_log( 'Error listing objects of prefix ' . $search_prefix . ' for attachment ' . $attachment->ID . ' from S3: ' . $e->getMessage() ); + $this->error_count ++; + + return false; + } + + $file_size_total = 0; + $main_file_size = 0; + + foreach ( $result->get( 'Contents' ) as $object ) { + if ( ! isset( $object['Size'] ) ) { + continue; + } + + $size = $object['Size']; + + // Increment the total size of files for the attachment + $file_size_total += $size; + + if ( $object['Key'] === $main_file ) { + // Record the size of the main file + $main_file_size = $size; + } + } + + if ( 0 === $file_size_total ) { + error_log( 'Total file size for the attachment is 0: ' . $attachment->ID ); + $this->error_count ++; + + return false; + } + + // Update the main file size for the attachment + $meta = get_post_meta( $attachment->ID, '_wp_attachment_metadata', true ); + $meta['filesize'] = $main_file_size; + update_post_meta( $attachment->ID, '_wp_attachment_metadata', $meta ); + + // Add the total file size for all image sizes + update_post_meta( $attachment->ID, 'wpos3_filesize_total', $file_size_total ); + + return true; + } + + /** + * Get a count of all attachments without region in their S3 metadata + * for the whole site + * + * @return int + */ + function count_attachments_to_process() { + // get the table prefixes for all the blogs + $table_prefixes = $this->as3cf->get_all_blog_table_prefixes(); + $all_count = 0; + + foreach ( $table_prefixes as $blog_id => $table_prefix ) { + $count = $this->get_attachments_removed_from_server( $table_prefix, true ); + $all_count += $count; + } + + return $all_count; + } + + /** + * Get all attachments that don't have region in their S3 meta data for a blog + * + * @param string $prefix + * @param int $limit + * + * @return mixed + */ + function get_attachments_to_process( $prefix, $limit ) { + $attachments = $this->get_attachments_removed_from_server( $prefix, false, $limit ); + + return $attachments; + } + + /** + * Wrapper for database call to get attachments uploaded to S3, + * that don't have the file size meta added already + * + * @param string $prefix + * @param null|int $limit + * + * @return mixed + */ + function get_s3_attachments( $prefix, $limit = null ) { + global $wpdb; + + $sql = "SELECT pm1.`post_id` as `ID`, pm1.`meta_value` AS 's3object' + FROM `{$prefix}postmeta` pm1 + LEFT OUTER JOIN `{$prefix}postmeta` pm2 + ON pm1.`post_id` = pm2.`post_id` + AND pm2.`meta_key` = 'wpos3_filesize_total' + WHERE pm1.`meta_key` = 'amazonS3_info' + AND pm2.`post_id` is null"; + + if ( ! is_null( $limit ) ) { + $sql .= ' LIMIT %d'; + + $sql = $wpdb->prepare( $sql, $limit ); + } + + return $wpdb->get_results( $sql, OBJECT ); + } + + /** + * Get S3 attachments that have had their local file removed from the server + * + * @param string $prefix + * @param bool|false $count + * @param null|int $limit + * + * @return array|int + */ + function get_attachments_removed_from_server( $prefix, $count = false, $limit = null ) { + $all_attachments = $this->get_s3_attachments( $prefix, $limit ); + $attachments = array(); + + foreach ( $all_attachments as $attachment ) { + if ( ! file_exists( get_attached_file( $attachment->ID, true ) ) ) { + $attachments[] = $attachment; + } + } + + if ( $count ) { + return count( $attachments ); + } + + return $attachments; + } + +} \ No newline at end of file diff --git a/classes/upgrades/as3cf-region-meta.php b/classes/upgrades/as3cf-region-meta.php new file mode 100644 index 00000000..4c4c3653 --- /dev/null +++ b/classes/upgrades/as3cf-region-meta.php @@ -0,0 +1,145 @@ +upgrade_id = 1; + $this->upgrade_name = 'meta_with_region'; + $this->upgrade_type = 'metadata'; + + $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 (e.g. s3-us-west-2.amazonaws.com).', 'as3cf' ); + + parent::__construct( $as3cf ); + } + + /** + * Get the region for the bucket where an attachment is located, update the S3 meta. + * + * @param $attachment + * + * @return bool + */ + function upgrade_attachment( $attachment ) { + $s3object = unserialize( $attachment->s3object ); + if ( false === $s3object ) { + error_log( 'Failed to unserialize S3 meta for attachment ' . $attachment->ID . ': ' . $attachment->s3object ); + $this->error_count++; + + return false; + } + // retrieve region and update the attachment metadata + $region = $this->as3cf->get_s3object_region( $s3object, $attachment->ID ); + if ( is_wp_error( $region ) ) { + error_log( 'Error updating region: ' . $region->get_error_message() ); + $this->error_count++; + + return false; + } + + return true; + } + + /** + * Get a count of all attachments without region in their S3 metadata + * for the whole site + * + * @return int + */ + function count_attachments_to_process() { + // get the table prefixes for all the blogs + $table_prefixes = $this->as3cf->get_all_blog_table_prefixes(); + $all_count = 0; + + foreach ( $table_prefixes as $blog_id => $table_prefix ) { + $count = $this->count_attachments_without_region( $table_prefix ); + $all_count += $count; + } + + return $all_count; + } + + /** + * Get all attachments that don't have region in their S3 meta data for a blog + * + * @param string $prefix + * @param int $limit + * + * @return mixed + */ + function get_attachments_to_process( $prefix, $limit ) { + $attachments = $this->get_attachments_without_region_results( $prefix, false, $limit ); + + return $attachments; + } + + /** + * Get a count of attachments that don't have region in their S3 meta data for a blog + * @param $prefix + * + * @return int + */ + function count_attachments_without_region( $prefix ) { + $count = $this->get_attachments_without_region_results( $prefix, true ); + + return $count; + } + + /** + * Wrapper for database call to get attachments without region + * + * @param string $prefix + * @param bool $count return count of attachments + * @param null|int $limit + * + * @return mixed + */ + function get_attachments_without_region_results( $prefix, $count = false, $limit = null ) { + global $wpdb; + + $sql = " FROM `{$prefix}postmeta` + WHERE `meta_key` = 'amazonS3_info' + AND `meta_value` NOT LIKE '%%\"region\"%%'"; + + if ( $count ) { + $sql = 'SELECT COUNT(*)' . $sql; + + return $wpdb->get_var( $sql ); + } + + $sql = "SELECT `post_id` as `ID`, `meta_value` AS 's3object'" . $sql; + + if ( ! is_null( $limit ) ) { + $sql .= ' LIMIT %d'; + + $sql = $wpdb->prepare( $sql, $limit ); + } + + return $wpdb->get_results( $sql, OBJECT ); + } +} \ No newline at end of file diff --git a/classes/wp-aws-compatibility-check.php b/classes/wp-aws-compatibility-check.php index 38d83198..3838d926 100644 --- a/classes/wp-aws-compatibility-check.php +++ b/classes/wp-aws-compatibility-check.php @@ -39,13 +39,18 @@ class WP_AWS_Compatibility_Check { */ protected $plugin_file_path; + /** + * @var null|string The name of the required parent plugin + */ + protected $parent_plugin_name; + /** * @var null|string The key of the required parent plugin, e.g. amazon-web-services */ protected $parent_plugin_slug; /** - * @var null|int The required version of the parent plugin + * @var null|string The required version of the parent plugin */ protected $parent_plugin_required_version; @@ -74,6 +79,22 @@ class WP_AWS_Compatibility_Check { */ protected $notice_class = 'error'; + /** + * @var bool Used to store if we are installing or updating plugins once per page request + */ + protected static $is_installing_or_updating_plugins; + + /** + * @param string $plugin_name + * @param string $plugin_slug + * @param string $plugin_file_path + * @param string|null $parent_plugin_name + * @param string|null $parent_plugin_slug + * @param string|null $parent_plugin_required_version + * @param string|null $parent_plugin_filename + * @param bool|false $deactivate_if_not_compatible + * @param string|null $parent_plugin_url + */ function __construct( $plugin_name, $plugin_slug, $plugin_file_path, $parent_plugin_name = null, $parent_plugin_slug = null, $parent_plugin_required_version = null, $parent_plugin_filename = null, $deactivate_if_not_compatible = false, $parent_plugin_url = null ) { $this->plugin_name = $plugin_name; $this->plugin_slug = $plugin_slug; @@ -117,7 +138,7 @@ function get_plugin_basename() { * @return string */ function get_parent_plugin_name() { - if ( $this->parent_plugin_name ) { + if ( ! is_null( $this->parent_plugin_name ) ) { return $this->parent_plugin_name; } @@ -127,10 +148,10 @@ function get_parent_plugin_name() { /** * Get the class of the parent plugin * - * @return mixed|string + * @return string */ function get_parent_plugin_class() { - if ( $this->parent_plugin_slug ) { + if ( ! is_null( $this->parent_plugin_slug ) ) { $class = ucwords( str_replace( '-', ' ', $this->parent_plugin_slug ) ); return str_replace( ' ', '_', $class ); @@ -175,7 +196,7 @@ function get_parent_plugin_basename() { /** * Get the URL for the parent plugin. Defaults to a wordpress.org URL. * - * @return null|string + * @return string */ function get_parent_plugin_url() { if ( ! is_null( $this->parent_plugin_slug ) ) { @@ -399,10 +420,8 @@ function hook_admin_notices() { return; } - global $pagenow; - - if ( 'update.php' === $pagenow && isset( $_GET['action'] ) && 'install-plugin' === $_GET['action'] ) { - // Don't show notice when installing plugins + if ( self::is_installing_or_updating_plugins() ) { + // Don't show notice when installing or updating plugins return; } @@ -415,7 +434,7 @@ function hook_admin_notices() { function get_admin_notice() { $error_msg = $this->get_error_msg(); - if ( ! $error_msg ) { + if ( false === $error_msg || '' === $error_msg ) { return; } @@ -447,24 +466,37 @@ function render_notice( $message ) { * @return bool */ public static function is_installing_or_updating_plugins() { + if ( ! is_null( self::$is_installing_or_updating_plugins ) ) { + return self::$is_installing_or_updating_plugins; + } + + self::$is_installing_or_updating_plugins = false; + global $pagenow; if ( 'update.php' === $pagenow && isset( $_GET['action'] ) && 'install-plugin' === $_GET['action'] ) { // We are installing a plugin - return true; + self::$is_installing_or_updating_plugins = true; } - if ( 'plugins.php' === $pagenow && isset( $_POST['action'] ) && 'update-selected' === $_POST['action'] ) { - // We are updating plugins from the plugin page - return true; + if ( 'plugins.php' === $pagenow && isset( $_POST['action'] ) ) { + $action = $_POST['action']; + if ( isset( $_POST['action2'] ) && '-1' !== $_POST['action2'] ) { + $action = $_POST['action2']; + } + + if ( 'update-selected' === $action ) { + // We are updating plugins from the plugin page + self::$is_installing_or_updating_plugins = true; + } } if ( 'update-core.php' === $pagenow && isset( $_GET['action'] ) && 'do-plugin-upgrade' === $_GET['action'] ) { // We are updating plugins from the updates page - return true; + self::$is_installing_or_updating_plugins = true; } - return false; + return self::$is_installing_or_updating_plugins; } } } \ No newline at end of file diff --git a/classes/wp-aws-uninstall.php b/classes/wp-aws-uninstall.php index 8555ef4f..b24a0974 100644 --- a/classes/wp-aws-uninstall.php +++ b/classes/wp-aws-uninstall.php @@ -26,37 +26,45 @@ class WP_AWS_Uninstall { /** - * @var array Options to be deleted + * @var array|string Options to be deleted */ protected $options; /** - * @var array Post meta to be deleted + * @var array|string Post meta to be deleted */ protected $postmeta; /** - * @var array Cron hooks to be unscheduled + * @var array|string Cron hooks to be unscheduled */ protected $crons; /** - * @var array Transients to be deleted + * @var array|string Transients to be deleted, this can be site wide and subsite, e.g. + * + * array( + * 'site' => array(...), + * 'subsite' => array(...), + * ) + * + * By default, an array of transients will be treated as site wide. + * */ protected $transients; /** - * @var Blog(s) in site + * @var array Blog(s) in site */ protected $blog_ids; /** * WP_AWS_Uninstall constructor. * - * @param array $options - * @param array $postmeta - * @param array $crons - * @param array $transients + * @param array|string $options + * @param array|string $postmeta + * @param array|string $crons + * @param array|string $transients */ public function __construct( $options = array(), @@ -64,10 +72,10 @@ public function __construct( $crons = array(), $transients = array() ) { - $this->options = $options; - $this->postmeta = $postmeta; - $this->crons = $crons; - $this->transients = $transients; + $this->options = $this->maybe_convert_to_array( $options ); + $this->postmeta = $this->maybe_convert_to_array( $postmeta ); + $this->crons = $this->maybe_convert_to_array( $crons ); + $this->transients = $this->maybe_convert_to_array( $transients ); $this->set_blog_ids(); @@ -81,7 +89,7 @@ public function __construct( * Set the blog id(s) for a site */ private function set_blog_ids() { - $blog_ids[] = 1; + $blog_ids = array( 1 ); if ( function_exists( 'is_multisite' ) && is_multisite() ) { $args = array( 'limit' => false, @@ -97,33 +105,42 @@ private function set_blog_ids() { } /** - * Check and ensure a property has been filled with an array + * Is the current blog ID that specified in wp-config.php * - * @param string $property + * @param int $blog_id * * @return bool */ - private function check_property( $property ) { - if ( empty( $this->$property ) ) { - return false; + private function is_current_blog( $blog_id ) { + $default = defined( 'BLOG_ID_CURRENT_SITE' ) ? BLOG_ID_CURRENT_SITE : 1; + + if ( $default === $blog_id ) { + return true; } - if ( ! is_array( $this->$property ) ) { - // Convert any strings to an array - $this->$property = array( $this->$property ); + return false; + } + + /** + * Helper to ensure a value is an array + * + * @param array|string $data + * + * @return array + */ + private function maybe_convert_to_array( $data ) { + if ( ! is_array( $data ) ) { + // Convert a string to an array + $data = array( $data ); } - return true; + return $data; } /** * Delete site wide options */ public function delete_options() { - if ( ! $this->check_property( 'options' ) ) { - return; - } - foreach ( $this->options as $option ) { delete_site_option( $option ); } @@ -133,10 +150,6 @@ public function delete_options() { * Delete post meta data for all blogs */ public function delete_postmeta() { - if ( ! $this->check_property( 'postmeta' ) ) { - return; - } - global $wpdb; foreach ( $this->blog_ids as $blog_id ) { @@ -153,10 +166,6 @@ public function delete_postmeta() { * Clear any scheduled cron jobs */ public function clear_crons() { - if ( ! $this->check_property( 'crons' ) ) { - return; - } - foreach ( $this->crons as $cron ) { $timestamp = wp_next_scheduled( $cron ); if ( $timestamp ) { @@ -166,15 +175,44 @@ public function clear_crons() { } /** - * Delete site wide transients + * Delete transients */ public function delete_transients() { - if ( ! $this->check_property( 'transients' ) ) { + if ( ! isset( $this->transients['site'] ) && ! isset( $this->transients['subsite'] ) ) { + // Single array of site wide transients + foreach ( $this->transients as $transient ) { + delete_site_transient( $transient ); + } + return; } - foreach ( $this->transients as $transient ) { - delete_site_transient( $transient ); + // Deal with site wide transients + if ( isset( $this->transients['site'] ) ) { + $site_transients = $this->maybe_convert_to_array( $this->transients['site'] ); + + foreach ( $site_transients as $transient ) { + delete_site_transient( $transient ); + } + } + + // Deal with subsite specific transients + if ( isset( $this->transients['subsite'] ) ) { + $subsite_transients = $this->maybe_convert_to_array( $this->transients['subsite'] ); + + foreach ( $this->blog_ids as $blog_id ) { + if ( is_multisite() && ! $this->is_current_blog( $blog_id ) ) { + switch_to_blog( $blog_id ); + } + + foreach ( $subsite_transients as $transient ) { + delete_transient( $transient ); + } + } + + if ( is_multisite() && ! $this->is_current_blog( $blog_id ) ) { + restore_current_blog(); + } } } } diff --git a/composer.lock b/composer.lock deleted file mode 100644 index 7be1339e..00000000 --- a/composer.lock +++ /dev/null @@ -1,107 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", - "This file is @generated automatically" - ], - "hash": "4b6bce2aaae303cfd3fc478e7cc55366", - "packages": [ - { - "name": "composer/installers", - "version": "v1.0.18", - "source": { - "type": "git", - "url": "https://github.com/composer/installers.git", - "reference": "74fb0a7a1a23696d9c8cc2fba5903f6711cdd067" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/installers/zipball/74fb0a7a1a23696d9c8cc2fba5903f6711cdd067", - "reference": "74fb0a7a1a23696d9c8cc2fba5903f6711cdd067", - "shasum": "" - }, - "replace": { - "roundcube/plugin-installer": "*", - "shama/baton": "*" - }, - "require-dev": { - "composer/composer": "1.0.*@dev", - "phpunit/phpunit": "4.1.*" - }, - "type": "composer-installer", - "extra": { - "class": "Composer\\Installers\\Installer", - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "psr-0": { - "Composer\\Installers\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Kyle Robinson Young", - "email": "kyle@dontkry.com", - "homepage": "https://github.com/shama" - } - ], - "description": "A multi-framework Composer library installer", - "homepage": "http://composer.github.com/installers/", - "keywords": [ - "Craft", - "Dolibarr", - "Hurad", - "MODX Evo", - "OXID", - "WolfCMS", - "agl", - "annotatecms", - "bitrix", - "cakephp", - "chef", - "codeigniter", - "concrete5", - "croogo", - "drupal", - "elgg", - "fuelphp", - "installer", - "joomla", - "kohana", - "laravel", - "lithium", - "magento", - "mako", - "mediawiki", - "modulework", - "moodle", - "phpbb", - "piwik", - "ppi", - "puppet", - "roundcube", - "shopware", - "silverstripe", - "symfony", - "typo3", - "wordpress", - "zend", - "zikula" - ], - "time": "2014-08-18 20:00:12" - } - ], - "packages-dev": [], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "platform": [], - "platform-dev": [] -} diff --git a/languages/amazon-s3-and-cloudfront-en.pot b/languages/amazon-s3-and-cloudfront-en.pot index 5d724686..0291c033 100644 --- a/languages/amazon-s3-and-cloudfront-en.pot +++ b/languages/amazon-s3-and-cloudfront-en.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: amazon-s3-and-cloudfront\n" "Report-Msgid-Bugs-To: nom@deliciousbrains.com\n" -"POT-Creation-Date: 2015-08-17 15:41-0300\n" +"POT-Creation-Date: 2015-08-27 20:17+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,116 +17,120 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:84 +#: classes/amazon-s3-and-cloudfront.php:84 msgid "Offload S3" msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:85 +#: classes/amazon-s3-and-cloudfront.php:85 msgid "S3 and CloudFront" msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:519 +#: classes/amazon-s3-and-cloudfront.php:522 #, php-format msgid "File %s does not exist" msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:528 +#: classes/amazon-s3-and-cloudfront.php:531 #, php-format msgid "Mime type %s is not allowed" msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:605 +#: classes/amazon-s3-and-cloudfront.php:608 #, php-format msgid "Error uploading %s to S3: %s" msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:1173 +#: classes/amazon-s3-and-cloudfront.php:1259 msgid "Cheatin’ eh?" msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:1177 +#: classes/amazon-s3-and-cloudfront.php:1263 msgid "You do not have sufficient permissions to access this page." msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:1183 +#: classes/amazon-s3-and-cloudfront.php:1269 msgid "No bucket name provided." msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:1455 +#: classes/amazon-s3-and-cloudfront.php:1544 +msgid "Error Getting Bucket Region" +msgstr "" + +#: classes/amazon-s3-and-cloudfront.php:1545 #, php-format msgid "There was an error attempting to get the region of the bucket %s: %s" msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:1575 +#: classes/amazon-s3-and-cloudfront.php:1665 msgid "" "This is a test file to check if the user has write permission to S3. Delete " "me if found." msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:1607 +#: classes/amazon-s3-and-cloudfront.php:1697 #, php-format msgid "" "There was an error attempting to check the permissions of the bucket %s: %s" msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:1665 +#: classes/amazon-s3-and-cloudfront.php:1755 msgid "Error creating bucket" msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:1666 +#: classes/amazon-s3-and-cloudfront.php:1756 msgid "Bucket name too short." msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:1667 +#: classes/amazon-s3-and-cloudfront.php:1757 msgid "Bucket name too long." msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:1668 +#: classes/amazon-s3-and-cloudfront.php:1758 msgid "" "Invalid character. Bucket names can contain lowercase letters, numbers, " "periods and hyphens." msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:1669 +#: classes/amazon-s3-and-cloudfront.php:1759 msgid "Error saving bucket" msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:1670 +#: classes/amazon-s3-and-cloudfront.php:1760 msgid "Error fetching buckets" msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:1671 +#: classes/amazon-s3-and-cloudfront.php:1761 msgid "Error getting URL preview: " msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:1672 +#: classes/amazon-s3-and-cloudfront.php:1762 msgid "The changes you made will be lost if you navigate away from this page" msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:1730 +#: classes/amazon-s3-and-cloudfront.php:1820 msgid "Cheatin' eh?" msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:1833 +#: classes/amazon-s3-and-cloudfront.php:1923 msgctxt "Show the media library tab" msgid "Media Library" msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:1834 +#: classes/amazon-s3-and-cloudfront.php:1924 msgctxt "Show the support tab" msgid "Support" msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:1970 +#: classes/amazon-s3-and-cloudfront.php:2060 #, php-format msgid "The file %s has been given %s permissions on Amazon S3." msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:2430 +#: classes/amazon-s3-and-cloudfront.php:2561 msgid "Quick Start Guide" msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:2432 +#: classes/amazon-s3-and-cloudfront.php:2563 #, php-format msgid "" "Looks like we don't have write access to this bucket. It's likely that the " @@ -135,7 +139,7 @@ msgid "" "correctly." msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/amazon-s3-and-cloudfront.php:2434 +#: classes/amazon-s3-and-cloudfront.php:2565 #, php-format msgid "" "Looks like we don't have access to the buckets. It's likely that the user " @@ -143,370 +147,392 @@ msgid "" "Please see our %s for instructions on setting up permissions correctly." msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/as3cf-plugin-compatibility.php:260 +#: classes/as3cf-plugin-compatibility.php:357 #, php-format msgid "There was an error attempting to download the file %s from S3: %s" msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/as3cf-upgrade.php:104 +#: classes/as3cf-upgrade.php:279 #, php-format msgid "" -"Running Metadata Update — We’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 (e." -"g. s3-us-west-2.amazonaws.com). This will be done quietly in the " +"Running %s Update — We’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’s " "performance." msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/as3cf-upgrade.php:105 +#: classes/as3cf-upgrade.php:280 msgid "Pause Update" msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/as3cf-upgrade.php:109 +#: classes/as3cf-upgrade.php:284 +#, php-format msgid "" -"Metadata Update Paused — Updating Media Library " -"metadata has been paused." +"%s Update Paused — Updating Media Library %s has been " +"paused." msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/as3cf-upgrade.php:110 +#: classes/as3cf-upgrade.php:285 msgid "Restart Update" msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/as3cf-upgrade.php:113 +#: classes/as3cf-upgrade.php:288 +#, php-format msgid "" -"Error Updating Metadata — 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." +"Error Updating %s — 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." msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/as3cf-upgrade.php:114 +#: classes/as3cf-upgrade.php:289 msgid "Try Run It Again" msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/as3cf-upgrade.php:183 +#: classes/as3cf-upgrade.php:391 #, php-format msgid "Every %d Minutes" msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/wp-aws-compatibility-check.php:289 +#: classes/upgrades/as3cf-file-sizes.php:37 +msgid "" +"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." +msgstr "" + +#: classes/upgrades/as3cf-region-meta.php:36 +msgid "" +"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 (e.g. s3-us-west-2.amazonaws.com)." +msgstr "" + +#: classes/wp-aws-compatibility-check.php:310 msgid "deactivate" msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/wp-aws-compatibility-check.php:290 +#: classes/wp-aws-compatibility-check.php:311 #, php-format msgid "You can %s the %s plugin to get rid of this notice." msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/wp-aws-compatibility-check.php:293 +#: classes/wp-aws-compatibility-check.php:314 #, php-format msgid "%s has been disabled as it requires the %s plugin." msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/wp-aws-compatibility-check.php:297 +#: classes/wp-aws-compatibility-check.php:318 msgid "which is currently disabled." msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/wp-aws-compatibility-check.php:299 +#: classes/wp-aws-compatibility-check.php:320 msgid "It appears to be installed already." msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/wp-aws-compatibility-check.php:301 +#: classes/wp-aws-compatibility-check.php:322 msgctxt "Activate plugin" msgid "Activate it now." msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/wp-aws-compatibility-check.php:308 +#: classes/wp-aws-compatibility-check.php:329 #, php-format msgid "Install and activate it." msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/wp-aws-compatibility-check.php:319 +#: classes/wp-aws-compatibility-check.php:340 #, php-format msgid "" "%s has been disabled as it requires version %s or later of the %s plugin." msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/wp-aws-compatibility-check.php:322 +#: classes/wp-aws-compatibility-check.php:343 #, php-format msgid "You currently have version %s installed." msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/wp-aws-compatibility-check.php:329 +#: classes/wp-aws-compatibility-check.php:350 #, php-format msgid "A valid license for %s is required to update." msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/wp-aws-compatibility-check.php:337 +#: classes/wp-aws-compatibility-check.php:358 msgid "Update to the latest version" msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/wp-aws-compatibility-check.php:349 +#: classes/wp-aws-compatibility-check.php:370 #, php-format msgid "" "%1$s has been disabled because it is not a supported addon of the %2$s " "plugin." msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/wp-aws-compatibility-check.php:358 +#: classes/wp-aws-compatibility-check.php:379 #, php-format msgid "" "%1$s has been disabled because it will not work with the version of the %2$s " "plugin installed. %1$s %3$s or later is required." msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/wp-aws-compatibility-check.php:361 +#: classes/wp-aws-compatibility-check.php:382 #, php-format msgid "Update %s to the latest version" msgstr "" -#: builds/amazon-s3-and-cloudfront/classes/wp-aws-compatibility-check.php:423 +#: classes/wp-aws-compatibility-check.php:442 #, php-format msgid "The %s plugin has been deactivated." msgstr "" -#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:3 +#: view/bucket-select.php:3 msgid "Change bucket" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:3 +#: view/bucket-select.php:3 msgid "What bucket would you like to use?" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:5 +#: view/bucket-select.php:5 msgid "Existing bucket name" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:7 -#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:12 +#: view/bucket-select.php:7 +#: view/bucket-select.php:12 msgid "Saving..." msgstr "" -#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:7 -#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:12 +#: view/bucket-select.php:7 +#: view/bucket-select.php:12 msgid "Save Bucket" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:8 +#: view/bucket-select.php:8 msgid "Browse existing buckets" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:9 -#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:26 -#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:31 +#: view/bucket-select.php:9 +#: view/bucket-select.php:26 +#: view/bucket-select.php:31 msgid "Create new bucket" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:13 -#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:21 -#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:61 +#: view/bucket-select.php:13 +#: view/bucket-select.php:21 +#: view/bucket-select.php:67 msgid "Cancel" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:18 +#: view/bucket-select.php:18 msgid "Select bucket" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:19 +#: view/bucket-select.php:19 msgid "Loading..." msgstr "" -#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:22 -#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:27 +#: view/bucket-select.php:22 +#: view/bucket-select.php:27 msgid "Refresh" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:25 +#: view/bucket-select.php:25 msgid "Enter bucket name" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:37 +#: view/bucket-select.php:37 msgid "Bucket Name:" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:40 +#: view/bucket-select.php:40 msgid "Bucket Name" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:47 +#: view/bucket-select.php:46 msgid "Region:" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:60 +#: view/bucket-select.php:60 +#, php-format +msgid "%s (defined in wp-config.php)" +msgstr "" + +#: view/bucket-select.php:66 msgid "Creating..." msgstr "" -#: builds/amazon-s3-and-cloudfront/view/bucket-select.php:60 +#: view/bucket-select.php:66 msgid "Create New Bucket" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/bucket-setting.php:7 +#: view/bucket-setting.php:7 msgid "Bucket" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/bucket-setting.php:11 +#: view/bucket-setting.php:11 msgid "Change" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/debug-info.php:2 +#: view/bucket-setting.php:13 +msgid "(defined in wp-config.php)" +msgstr "" + +#: view/debug-info.php:2 msgid "Diagnostic Info" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/debug-info.php:11 +#: view/debug-info.php:11 msgctxt "Download to your computer" msgid "Download" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/domain-setting.php:5 +#: view/domain-setting.php:5 msgid "Domain:" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/domain-setting.php:23 +#: view/domain-setting.php:23 msgid "Bucket name as subdomain" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/domain-setting.php:28 +#: view/domain-setting.php:28 msgid "Bucket name in path" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/domain-setting.php:33 +#: view/domain-setting.php:33 msgid "Bucket name as domain" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/domain-setting.php:38 +#: view/domain-setting.php:38 msgid "CloudFront or custom domain" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/error-access.php:4 +#: view/error-access.php:4 msgid "Access Denied to Bucket" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/settings.php:10 +#: view/settings.php:10 msgid "Settings saved." msgstr "" -#: builds/amazon-s3-and-cloudfront/view/settings.php:44 +#: view/settings.php:44 msgid "Enable/Disable the Plugin" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/settings.php:51 +#: view/settings.php:51 msgid "Copy Files to S3" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/settings.php:52 +#: view/settings.php:52 msgid "" "When a file is uploaded to the Media Library, copy it to S3. Existing files " "are not copied to S3." msgstr "" -#: builds/amazon-s3-and-cloudfront/view/settings.php:60 +#: view/settings.php:60 msgid "Rewrite File URLs" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/settings.php:61 +#: view/settings.php:61 msgid "" "For Media Library files that have been copied to S3, rewrite the URLs so " "that they are served from S3/CloudFront instead of your server." msgstr "" -#: builds/amazon-s3-and-cloudfront/view/settings.php:65 +#: view/settings.php:65 msgid "Configure File URLs" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/settings.php:83 +#: view/settings.php:83 msgid "Path" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/settings.php:85 +#: view/settings.php:85 msgid "By default the path is the same as your local WordPress files:" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/settings.php:98 +#: view/settings.php:98 msgid "Year/Month" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/settings.php:100 +#: view/settings.php:100 msgid "Add the Year/Month in the URL." msgstr "" -#: builds/amazon-s3-and-cloudfront/view/settings.php:106 +#: view/settings.php:106 msgid "SSL:" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/settings.php:114 +#: view/settings.php:114 msgid "Same as request" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/settings.php:115 +#: view/settings.php:115 msgid "When the request is https://, use https:// for the file URL as well." msgstr "" -#: builds/amazon-s3-and-cloudfront/view/settings.php:119 +#: view/settings.php:119 msgid "Always SSL" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/settings.php:120 +#: view/settings.php:120 msgid "Forces https:// to be used." msgstr "" -#: builds/amazon-s3-and-cloudfront/view/settings.php:121 +#: view/settings.php:121 msgid "" "You cannot use the \"Bucket as a subdomain\" domain option when using SSL." msgstr "" -#: builds/amazon-s3-and-cloudfront/view/settings.php:125 +#: view/settings.php:125 msgid "Always non-SSL" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/settings.php:126 +#: view/settings.php:126 msgid "Forces http:// to be used." msgstr "" -#: builds/amazon-s3-and-cloudfront/view/settings.php:132 +#: view/settings.php:132 msgid "Advanced Options" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/settings.php:139 +#: view/settings.php:139 msgid "Remove Files From Server" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/settings.php:140 +#: view/settings.php:140 msgid "Once a file has been copied to S3, remove it from the local server." msgstr "" -#: builds/amazon-s3-and-cloudfront/view/settings.php:148 +#: view/settings.php:148 msgid "Object Versioning" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/settings.php:150 +#: view/settings.php:150 msgid "" "Append a timestamp to the S3 file path. Recommended when using CloudFront so " "you don't have to worry about cache invalidation." msgstr "" -#: builds/amazon-s3-and-cloudfront/view/settings.php:152 -#: builds/amazon-s3-and-cloudfront/view/settings.php:165 +#: view/settings.php:152 +#: view/settings.php:165 msgid "More info" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/settings.php:162 +#: view/settings.php:162 msgid "Far Future Expiration Header" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/settings.php:163 +#: view/settings.php:163 msgid "" "Implements a \"Never Expire\" caching policy for browsers by setting an " "Expires header for 10 years in the future. Should be used in conjunction " "with object versioning above." msgstr "" -#: builds/amazon-s3-and-cloudfront/view/settings.php:175 +#: view/settings.php:175 msgid "Copy HiDPI (@2x) Images" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/settings.php:176 +#: view/settings.php:176 #, php-format msgid "" "When uploading a file to S3, checks if there's a file of the same name with " @@ -514,80 +540,80 @@ msgid "" "Retina 2x plugin." msgstr "" -#: builds/amazon-s3-and-cloudfront/view/settings.php:182 +#: view/settings.php:182 msgid "Save Changes" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/sidebar.php:10 +#: view/sidebar.php:10 msgid "Upload existing Media Library to S3" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/sidebar.php:11 +#: view/sidebar.php:11 msgid "Find & replace file URLs in content" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/sidebar.php:12 +#: view/sidebar.php:12 msgid "Manage S3 files in WordPress" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/sidebar.php:13 +#: view/sidebar.php:13 msgid "Assets addon - Serve your CSS & JS from S3/CloudFront" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/sidebar.php:14 +#: view/sidebar.php:14 msgid "WooCommerce addon" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/sidebar.php:15 +#: view/sidebar.php:15 msgid "Easy Digital Downloads addon" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/sidebar.php:16 +#: view/sidebar.php:16 msgid "PriorityExpert™ email support" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/sidebar.php:19 +#: view/sidebar.php:19 msgid "Visit deliciousbrains.com →" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/sidebar.php:26 +#: view/sidebar.php:26 msgid "Get 20% Off!" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/sidebar.php:29 +#: view/sidebar.php:29 #, php-format msgid "" "Submit your name and email and we’ll send you a coupon for 20% off your " "upgrade." msgstr "" -#: builds/amazon-s3-and-cloudfront/view/sidebar.php:33 +#: view/sidebar.php:33 msgid "Your Email" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/sidebar.php:37 +#: view/sidebar.php:37 msgid "First Name" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/sidebar.php:41 +#: view/sidebar.php:41 msgid "Last Name" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/sidebar.php:48 +#: view/sidebar.php:48 msgid "Send me the coupon" msgstr "" -#: builds/amazon-s3-and-cloudfront/view/sidebar.php:52 +#: view/sidebar.php:52 msgid "" "We promise we will not use your email for anything else and you can " "unsubscribe with 1-click anytime." msgstr "" -#: builds/amazon-s3-and-cloudfront/view/wordpress-org-support.php:2 +#: view/wordpress-org-support.php:2 msgid "As this is a free plugin, we do not provide support." msgstr "" -#: builds/amazon-s3-and-cloudfront/view/wordpress-org-support.php:4 +#: view/wordpress-org-support.php:4 #, php-format msgid "" "You may ask the WordPress community for help by posting to the timely response via email from a developer " "who works on this plugin, upgrade and send us an email." msgstr "" -#: builds/amazon-s3-and-cloudfront/view/wordpress-org-support.php:8 +#: view/wordpress-org-support.php:8 #, php-format msgid "" "If you've found a bug, please submit an issue on GitHub." diff --git a/languages/as3cf-pt-br.mo b/languages/as3cf-pt-br.mo index e2994000c799e28a07c1597d991796e03c464ef0..2364d53b5b0e825cbc0ff70e78a1a1644c9808c3 100644 GIT binary patch delta 1265 zcmYk)Nl2AZ6u|MTX^u~QYEGqoT23We2re=rbI2&mu*{IeFW*-VIX&M4Y0yPMW#Za%Vg%-(1loboSc!6AEBfInjK#CK8gHN^{s?8?+;aabo~Q3iR&m&ivVIuHa2D4R zUpYBdD(XjBa2o^gF~;BvwBsi%z#m94D%XE0@jPTmm7uIIL%ErHlzkn`c4H|0>nH_} zVF2;f!{v!-ETaDo3o(`~DzO@+vMH2GU!sNc*nqppUe-UrQCvX&)ezTKf)ki7rF$rkEvf=#z7}_)3&}}6L26JhF$&+H zRQ?s^hJT@alE_f*Uk0+7m4LfYGCPQ};0UJTX-ves$fHp+n1OS64ZX-h-oyaPzA2o= zw|j(2vvQ zs;96DpWz0K=aB8V6D8puIW*tlFkE$(amX^A*`+Pbt~#T>d2!@`z7?=h{|xHX=Yn79 zleR4V#g?QKLe_g4LUw!G%S>0DDl^^fmT{ofVY)2+Evz`fY<3t0$6MTvVkc**a6PJb?FsMGW<->}6miT`7+K{N_%FP+MNjxSjiM&A{Cog%y+1k2Gnzc+t=m!(^^KJ3(DEF^`Y*7~N;PyvYJ&DpbL+{p ir+PAd$TOVL>*b%{+>%{7J<_gUXTA<9x77FAbMBp)Qd;j!r`+C|X$N~}E^}K31P%L~v(7oY zv(MhAd+&4a+?tR^<)tAKCDkAbrV+swD<%X!fFDe542eOB5<_B?5KM?h`593wl7uCacY@qX-UIR{c|ZT020sGo`Bx%-6?`$*UkCY< zJpYAB^26XuK*@7E_@m&@g0BQ$4{Cf3YW~MTwvxO9{4wx7pyv5QP~$!UO8&nDe;oV@ zC^?_U=A^fmfm-J-Q1hRQ_ebEnxPCkMa`2WHh4DwgKjQi<_$u&gp!D@pCei$_1W{3P zFQ|1N0DlO4E4T?(pyc`_coX{|^uml3SQW^Spw8UJ9vNz? z|3(l|lDB}GHwQKEW1!@D8_3p^cY!|*{ub!KPlLCCe-CQ>O&H@W_!97L@aMrZ;CsP0 zf?og;DLICb?gBSJ>2VMA;8Wm(;D3RSf}1Z1%m489)xI`|pz z9GlblTTs&N;AQY6_yqV_@J~UFdlKbnUr&Mm3VsmOzTS-!d>#A~FbDqtWLEbAMq9pGE_}kzPLGm&1tKf&h4-h>3OwPDquO29U zUja4GRZ#nU5|o^u2X6-d0hIl|0!rWC1m!m`#QEfBM?hVl0gr)i1?BI*0ZPvIf!hCv zK)wGlQ1ko+C_DTeDEa>rycK-WO7NrCfQt8vASO&oQ2PC4@Cf)(@OQw!1yNn{7|yNv z-wpmb_+e1<=O|hJ_$0`y$@@S|oqQT($>eh&rbzw`dF?I4dD0}=m zsQr8pMAgYNpvHd&lpekm?|%c7USH0pZUXNBwa;Uq;^je5dff(PharedCk?m;z5~=g zKMBg;zXWQXo4MEoUkSbj^x!)9yWr#C7eK9Z31!^}J_<54c@}&U{A+Lvd^3~38+-IH% zRWLb~x>4l^_pThSS5BWV>L$%|mo=x>($g+2`Y!D?*>2kS)#Oy&RAn(deYUE~$~`ja zr%hH2-Ii}sex|3^;&XTFxS!UelkT#2Ra&^LXi8U?WAD61)-G)tKc3iNo=i27cg@JV zah%ExSU_^@m(z*``dR88%DPor?IW5W*SwuJZj|nN*Y&<&j=W6!zVDcC=gw8Po%^)* zt~c_%D`65_+U4cY=I*mlme=z=wVMdr@@_kw*1rF(TUyxlqEIuzM`;}-n%D&M1ujjC zVo_Uhp>JK`vcL6J_nbI>_sPn;VW}a_s4Az!ktsx4x}H9k6<~%ly@v4%n%&Eqk?FcS z?OpMWtNbu4u~Fl)+SR_=MTvu|9Iv{EN8H)VumYAS_uLe%V@JIbR659VU)#$z;iM`j zzC!)&ob|NZFUM(CxKs56Kd77i+~2#hH^PJ+?)3b6QSMdgST5B~o8HKkgX^l%?|NM9aNmuyV%qpx%Xth@kIHG@$Fi;{`O{=QpLTQavN79e z911i~HRV*%ezij#IPy3xdPL7gj3YP6r^BqcX?Bh3QA4uP$9u8vn|xZWZIuOPiW_?5 z(nAdI?`odDyGY=aMV)Nqd+C1dc6J_We->rYu}f2&459el%*$#29D&;;8#j1BAVsh3 zXSMc4)Yo!}%A?x~BTF!dB3iKVSDQ}X_sR+eC=0Au_>(IOG!hoNwdmH9lb3xr{Mf;B z&|7WH5|qHU{?8&|sal?UxOR|54uw>*>i-8xyS}IBmj*3?UvKV^-@?E;dj$KQ^qI@mM6q5 zAHWW|tEZDmSvAT1#z+y|g>>u}f3GL^@4LPqq|>}HNhP63OH$XoET6R~%*$SyyUS(O z-^O=gDDl*KvZ?sVlg&|D3_akLzGAF8Kf15Ibz8V7i$OM=68q<*4q~uQHp|JrT^bYa z+!<~>n4Q@^?{0tXiK|E4`Efe*@kM_%t7C#(Ocj=)lFf?ckwO4Ft*)*nXN)V?le2|F zaP3S8McdxCQ3g-smMXUbRVL@KC2qnSm2avHDvgWBe6Y$Jl{+_Wq|~!lCs|+* z_n;?pSIIe|csa9${BljE@0F7*tg>_GQD)w&$R?&FnTRrj-;@S81r8|Yo~JZ?du_pU z6;CWJ(pVsGq%12~%8i<4vc9&a{XqMw29_Sx*FHNxp5&fTX{c}9$^~VTuuH#UF(3s_ z$~^1sTLsds$~|O*clVJo*n}KudeQ2p6npddF}F`nfRICe8<+-H-Oem0NjN?2mBpje zf;U8dt9ndiTDr|6+`5jTd;&{_9tF0 zK*)*EtSIpPWUEBWSurS+3ur176@|i)+`F4W9IMI2AjTU4V>4%Rkx)cU#x%3!bFFE| zh$;?MIbs$LfgO@8vsTW$UlSF6uZ@6Y8(!ca5wKNjKH08H7Z<-Tx#S11!U(ka72UNX zKaQOel&^RIAy!R0_)>JSISoLO+rf~i;u6a0TrBeaEmKCNQC9 z!8`U~W^KdH4a>!U+EaaI)-`9S_X*1J|2_^Hvq7ADj)n zg+l?-qZBEzvhpYJ2o>8nqh2I{cvB~dGuVX9p*=iJHNyUC+RPS>hYGozl9txip zl;Cx*$}pMn!SPNH0=mLW`%3()$z^|=>_crLa$wg>&V$fUsg*EU(aWcJfVMFxbso@w z0oe;SC%Il$aS#I(ewFKOa=Q+Ng(4yhCJ3t` zVKOF^Ik*jG5+I(GH`_-WZIW^&P_pdEA*empu2A*b@wT)h*zAYa^ z-_q!;oHxz&?OYk&GEeGdjMEV^5|U-=8C{}x5o}nb@S!3aLNle#X$uXVKC@&EwGh0p z5y*BcEf}g&#oUa-!5C%Z@{THqx}@CI+Zc}5%qte^7OBHXlQH6sa3By5nKQ(5ox{DV z)#u(G`mww3IL3d6w$5*z?ZiyK?vAe>JG9BUmyLEFrg&K=4_s~5COMRSQpv5N z@VfVrhtGBHnZHNFsvh{NbGGP}Du349J>9H1LDx}Hyik5y#Pk{NZs_Vj=#w;4_<=w{tKD;v(bx*8umr&A6^j#xJxlYsF2 zVXC1vOKi;HCP8+_k*rl>>>RFxxNkhMINz-lD^Q_L(wvD>B!5nVq zI*jUr9Gj|lJN{(-a@C!$p@ytF zcsfu@lU1F9DO5~nX7se8*R#v5g-O_GV7r6oicAJ{RDq98Y4_f^rKe_A=;^uJwR3-! zUT;q0@Xk)2c4*5stLai_&&^tUCLn8H_RQTW46fl3%M18*u=N@*%V)8M~7qAucYgDA-EPUiY#L8iVC|JF?Hzp8Cf4Z zz3X$f%L+An4!ACzq zD%+sfckmH}s`*&N)bOm%67BB->8#V2EF5S3y!>RDSzFC7GysXrOu`zGASwQ%!#cXc z1KKvzciiTq&EIu8X3Uy<+VyuV06MOv^}|rjl+?jFpvv$cv$Z;=-+2S_yO0deRRl@X zj?%;pI9Fpg8W;+?)M|M1k(u7&ju(|d?WL9-0o}Ij6hccD3A-iZ!NqBXs}i$?mqQ;s zU1g~>$sxDynfv2@uxd8$nxBtwsFqrLDGlr3eld<&DYox8AtC{UU}OZtDSYORbCjy4 z&c<%H>>WHCHjKaZ=sw}aM3*mnQwW^Q-Ng;tK3*hWd?=+_M$Db5dUzIXz7x(SY0(@! zO$)47@);`h_UI9Cl3Kbzm~hRPvTf`=N&6L=RcN=nLMO^?HQp+tSQ?tqm6`%*57o^& ze#{h_eQAaMavVuNV#m+5Ex(b^iu?FLT?{F7$Vc&-ioqIxY}qmQe(*W?7sNH&4Wrt~X$9D9xI?|M&ku;tOL!I-+~ zn8+{lgXah?tG-q)z)xxHs#YsO-*jfI0E0iw1r(`A`8CM+5{a-zCyVOz)|3ryqSQ_2 zktC~=RaCFqA#{`2F_Mc^*kNcoVgV83tnbvO%ROiz9z5Hjy5T|e1HMJ+ z`va?7qzj`!)=WjY+g52r$Y!8xX#+i0vl`x1AH!Bwb@{P2Yp4O&AAaDL>N=PR3@6CRwmWxlWcSF9Q!67 zEk`Lj+G>L85P7mnA3C=kybJRU!o zTLvUL{B1-AS@!HuAsX#R8@={~v%YM06|KA8!N;NS*&b#q^Gz}o_tBV34Yb&jrA@QW=XQO@G1(66CMr|9gHJX3 zn%_o^8ctdTLgW7-(J9#awzF6aSsbnl>(G%D(*zdd1Q^aE#IH>O0r5W}D7Y;^h$BHe6E*Y+RKA|XyO array( + 'as3cf_notices', + 'wpos3_attachment_counts', + ), + 'subsite' => array( 'wpos3_site_space_used' ), +); $as3cf_uninstall = new WP_AWS_Uninstall( $options, $postmeta, $crons, $transients ); diff --git a/view/bucket-select.php b/view/bucket-select.php index 6cdad892..4d87a3af 100644 --- a/view/bucket-select.php +++ b/view/bucket-select.php @@ -41,20 +41,26 @@

    - - - - - - + + + + + + get_aws_regions(); + if ( ! defined( 'AS3CF_REGION' ) ) { ?> - - - + + +

    diff --git a/view/bucket-setting.php b/view/bucket-setting.php index eba4e9f6..3b501982 100644 --- a/view/bucket-setting.php +++ b/view/bucket-setting.php @@ -7,9 +7,11 @@

    - + - + get_setting( 'region' ); diff --git a/view/sidebar.php b/view/sidebar.php index 0b4ad88b..bd44073a 100644 --- a/view/sidebar.php +++ b/view/sidebar.php @@ -56,12 +56,6 @@

    Created & maintained by