From 761e98dad46938e58cf36a85fb536f146a7fe9ea Mon Sep 17 00:00:00 2001 From: Ashley Rich Date: Mon, 19 Jun 2017 15:14:49 +0100 Subject: [PATCH] Deploying version 1.2 --- README.md | 33 +- assets/js/modal.js | 8 +- assets/js/modal.min.js | 2 +- assets/js/script.js | 74 +- assets/js/script.min.js | 2 +- classes/amazon-s3-and-cloudfront.php | 448 ++++----- classes/as3cf-filter.php | 253 ++++- classes/as3cf-notices.php | 11 +- classes/as3cf-plugin-compatibility.php | 265 +---- classes/as3cf-upgrade.php | 600 ----------- classes/as3cf-utils.php | 265 ++++- classes/filters/as3cf-local-to-s3.php | 53 +- classes/filters/as3cf-s3-to-local.php | 30 +- .../batch-limits-exceeded-exception.php | 7 + .../exceptions/no-more-blogs-exception.php | 7 + .../exceptions/too-many-errors-exception.php | 7 + classes/upgrades/network-upgrade.php | 95 ++ ...t.php => upgrade-content-replace-urls.php} | 27 +- ...r-edd.php => upgrade-edd-replace-urls.php} | 35 +- ...-file-sizes.php => upgrade-file-sizes.php} | 91 +- ...pt.php => upgrade-filter-post-excerpt.php} | 9 +- .../upgrade-filter-post.php} | 348 ++----- ...wp-error.php => upgrade-meta-wp-error.php} | 32 +- ...egion-meta.php => upgrade-region-meta.php} | 29 +- classes/upgrades/upgrade.php | 939 ++++++++++++++++++ languages/amazon-s3-and-cloudfront-en.pot | 271 +++-- readme.txt | 33 +- view/bucket-setting.php | 10 +- view/domain-setting.php | 2 +- view/settings-tabs.php | 5 +- view/settings.php | 16 +- view/sidebar.php | 19 +- view/wordpress-org-support.php | 6 +- wordpress-s3.php | 16 +- 34 files changed, 2221 insertions(+), 1827 deletions(-) delete mode 100644 classes/as3cf-upgrade.php create mode 100644 classes/upgrades/exceptions/batch-limits-exceeded-exception.php create mode 100644 classes/upgrades/exceptions/no-more-blogs-exception.php create mode 100644 classes/upgrades/exceptions/too-many-errors-exception.php create mode 100644 classes/upgrades/network-upgrade.php rename classes/upgrades/{as3cf-filter-post-content.php => upgrade-content-replace-urls.php} (73%) rename classes/upgrades/{as3cf-filter-edd.php => upgrade-edd-replace-urls.php} (73%) rename classes/upgrades/{as3cf-file-sizes.php => upgrade-file-sizes.php} (71%) rename classes/upgrades/{as3cf-filter-post-excerpt.php => upgrade-filter-post-excerpt.php} (82%) rename classes/{as3cf-upgrade-filter-post.php => upgrades/upgrade-filter-post.php} (56%) rename classes/upgrades/{as3cf-meta-wp-error.php => upgrade-meta-wp-error.php} (85%) rename classes/upgrades/{as3cf-region-meta.php => upgrade-region-meta.php} (85%) create mode 100644 classes/upgrades/upgrade.php diff --git a/README.md b/README.md index 80c020d2..ec285e83 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # WP Offload S3 Lite # **Contributors:** bradt, deliciousbrains **Tags:** uploads, amazon, s3, amazon s3, mirror, admin, media, cdn, cloudfront -**Requires at least:** 4.4 -**Tested up to:** 4.7.3 -**Stable tag:** 1.1.6 +**Requires at least:** 4.6 +**Tested up to:** 4.8 +**Stable tag:** 1.2 **License:** GPLv3 Copies files to Amazon S3 as they are uploaded to the Media Library. Optionally configure Amazon CloudFront for faster delivery. @@ -22,12 +22,12 @@ If you're adding this plugin to a site that's been around for a while, your exis * Upload existing Media Library to Amazon S3 * Control Amazon 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=os3-free-plugin#assets-addon) - Serve your CSS & JS from Amazon 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) +* [Assets addon](https://deliciousbrains.com/wp-offload-s3/?utm_campaign=WP%2BOffload%2BS3&utm_source=wordpress.org&utm_medium=free%2Bplugin%2Blisting&utm_content=assets%2Baddon#addons) - Serve your CSS & JS from Amazon S3/CloudFront +* [WooCommerce addon](https://deliciousbrains.com/wp-offload-s3/?utm_campaign=WP%2BOffload%2BS3&utm_source=wordpress.org&utm_medium=free%2Bplugin%2Blisting&utm_content=woocommerce%2Baddon#addons) +* [Easy Digital Downloads addon](https://deliciousbrains.com/wp-offload-s3/?utm_campaign=WP%2BOffload%2BS3&utm_source=wordpress.org&utm_medium=free%2Bplugin%2Blisting&utm_content=edd%2Baddon#addons) * PriorityExpert™ email support -[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) +[Compare pro vs free →](https://deliciousbrains.com/wp-offload-s3/upgrade/?utm_campaign=WP%2BOffload%2BS3&utm_source=wordpress.org&utm_medium=free%2Bplugin%2Blisting) The video below runs through the pro upgrade features... @@ -48,7 +48,7 @@ which is a fork of [Amazon S3 for WordPress](http://wordpress.org/extend/plugins ### What are the minimum requirements? ### -You can see the minimum requirements [here](https://deliciousbrains.com/wp-offload-s3/pricing/?utm_source=wordpress.org&utm_medium=web&utm_content=desc&utm_campaign=os3-free-plugin#requirements). +You can see the minimum requirements [here](https://deliciousbrains.com/wp-offload-s3/pricing/?utm_campaign=WP%2BOffload%2BS3&utm_source=wordpress.org&utm_medium=free%2Bplugin%2Blisting&utm_content=requirements#requirements). ## Screenshots ## @@ -69,6 +69,17 @@ This version requires PHP 5.3.3+ and the Amazon Web Services plugin ## Changelog ## +### WP Offload S3 Lite 1.2 - 2017-06-19 ### +* New: Compatibility with WordPress 4.8 +* New: Support for WP CLI `wp media regenerate` +* Improvement: Intermediate image sizes are now passed through the `as3cf_object_meta` filter +* Improvement: Content filtering cache now uses the external object when available +* Bug fix: Timeouts on large multisite installs due to excessive database queries on upgrade routines +* Bug fix: Video files with private ACL not working with WordPress's default media player +* Bug fix: Bucket permissions check not using configured path +* Bug fix: WordPress image editor sometimes shows a 404 when 'Remove Files From Server' enabled +* Bug fix: Notice: Undefined index: region + ### WP Offload S3 Lite 1.1.6 - 2017-03-13 ### * New: Compatibility with [Advanced Custom Fields](https://wordpress.org/plugins/advanced-custom-fields/) * New: `as3cf_filter_post_local_to_s3` and `as3cf_filter_post_s3_to_local` filters added for filtering S3 URLs in custom content @@ -118,7 +129,7 @@ This version requires PHP 5.3.3+ and the Amazon Web Services plugin ### WP Offload S3 Lite 1.0.5 - 2016-09-01 ### * New: Compatibility with WordPress 4.6 -* Improvement: No longer delete plugin data on uninstall. Manual removal possible, as per this [doc](https://deliciousbrains.com/wp-offload-s3/doc/uninstall/) +* Improvement: No longer delete plugin data on uninstall. Manual removal possible, as per this [doc](https://deliciousbrains.com/wp-offload-s3/doc/uninstall/?utm_campaign=changelogs&utm_source=wordpress.org&utm_medium=free%2Bplugin%2Blisting) ### WP Offload S3 Lite 1.0.4 - 2016-05-30 ### * New: Now using simpler Force HTTPS setting, removed redundant Always Use HTTP setting @@ -224,8 +235,8 @@ This version requires PHP 5.3.3+ and the Amazon Web Services plugin * Bug fix: Accidentally released the sidebar for after we launch the pro version ### WP Offload S3 0.9.1 - 2015-07-29 ### -* Improvement: Access denied sample IAM policy replaced with link to [Quick Start Guide](https://deliciousbrains.com/wp-offload-s3/doc/quick-start-guide/) -* Improvement: Access denied messages on bucket selection or bucket creation now link to [Quick Start Guide](https://deliciousbrains.com/wp-offload-s3/doc/quick-start-guide/) +* Improvement: Access denied sample IAM policy replaced with link to [Quick Start Guide](https://deliciousbrains.com/wp-offload-s3/doc/quick-start-guide/?utm_campaign=changelogs&utm_source=wordpress.org&utm_medium=free%2Bplugin%2Blisting) +* Improvement: Access denied messages on bucket selection or bucket creation now link to [Quick Start Guide](https://deliciousbrains.com/wp-offload-s3/doc/quick-start-guide/?utm_campaign=changelogs&utm_source=wordpress.org&utm_medium=free%2Bplugin%2Blisting) * Improvement: Object expires time can now be filtered using the `as3cf_object_meta` filter * Bug fix: Error not always shown when S3 bucket inaccessible due to incorrect permissions * Bug fix: Permission checks fail when S3 bucket is in a non-default region and defined by `AS3CF_BUCKET` constant diff --git a/assets/js/modal.js b/assets/js/modal.js index bb863392..6e370703 100644 --- a/assets/js/modal.js +++ b/assets/js/modal.js @@ -101,13 +101,13 @@ var as3cfModal = (function( $ ) { var target = $( '#as3cf-modal' ).data( 'as3cf-modal-target' ); $( '#as3cf-overlay' ).fadeOut( 150, function() { - if ( 'function' === typeof callback ) { - callback( target ); - } - $( 'body' ).removeClass( 'as3cf-modal-open' ); $( this ).remove(); + + if ( 'function' === typeof callback ) { + callback( target ); + } } ); $( 'body' ).trigger( 'as3cf-modal-close', [ target ] ); diff --git a/assets/js/modal.min.js b/assets/js/modal.min.js index bd5a79a7..7ff19714 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",loading:!1,dismissible:!0},d={};return c.exists=function(c){var e=b(c);return void 0!==d[e]||!!a(c).length},c.open=function(e,f,g){var h=b(e);a("body").append('
');var i=a("#as3cf-overlay");c.dismissible?i.append('
×
'):i.append('
');var j=a("#as3cf-modal");if(void 0===d[h]){var k=a(e);d[h]=k.clone(!0).css("display","block"),k.remove()}j.data("as3cf-modal-target",e).append(d[h]),void 0!==g&&j.addClass(g),"function"==typeof f&&f(e),a("body").addClass("as3cf-modal-open"),i.fadeIn(150),j.fadeIn(150),a("body").trigger("as3cf-modal-open",[e])},c.close=function(b){if(!c.loading&&c.dismissible){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},c.setDismissibleState=function(a){c.dismissible=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&&void c.close()})}),c}(jQuery); \ 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,dismissible:!0},d={};return c.exists=function(c){var e=b(c);return void 0!==d[e]||!!a(c).length},c.open=function(e,f,g){var h=b(e);a("body").append('
');var i=a("#as3cf-overlay");c.dismissible?i.append('
×
'):i.append('
');var j=a("#as3cf-modal");if(void 0===d[h]){var k=a(e);d[h]=k.clone(!0).css("display","block"),k.remove()}j.data("as3cf-modal-target",e).append(d[h]),void 0!==g&&j.addClass(g),"function"==typeof f&&f(e),a("body").addClass("as3cf-modal-open"),i.fadeIn(150),j.fadeIn(150),a("body").trigger("as3cf-modal-open",[e])},c.close=function(b){if(!c.loading&&c.dismissible){var d=a("#as3cf-modal").data("as3cf-modal-target");a("#as3cf-overlay").fadeOut(150,function(){a("body").removeClass("as3cf-modal-open"),a(this).remove(),"function"==typeof b&&b(d)}),a("body").trigger("as3cf-modal-close",[d])}},c.setLoadingState=function(a){c.loading=a},c.setDismissibleState=function(a){c.dismissible=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&&void c.close()})}),c}(jQuery); \ No newline at end of file diff --git a/assets/js/script.js b/assets/js/script.js index 4e4271f3..1179e041 100644 --- a/assets/js/script.js +++ b/assets/js/script.js @@ -459,7 +459,12 @@ setBucketLink(); - as3cfModal.close( unlockBucketSelect ); + as3cfModal.close( function() { + $activeTab.trigger( 'bucket-change', [ canWrite ] ); + + // Unlock setting the bucket + as3cf.buckets.bucketSelectLock = false; + } ); }, /** @@ -623,15 +628,6 @@ } ); } - /** - * Reset the bucket select lock - */ - function unlockBucketSelect( target ) { - - // Unlock setting the bucket - as3cf.buckets.bucketSelectLock = false; - } - /* * Toggle the lost files notice */ @@ -654,47 +650,41 @@ } } + /** + * Update the UI with the current active tab set in the URL hash. + */ + function renderCurrentTab() { + + // If rendering the default tab, or a bare hash clean the hash. + if ( '#' + as3cf.tabs.defaultTab === location.hash ) { + location.hash = ''; + + return; + } + + // Strip the # if still on the end of the URL + if ( 'function' === typeof history.replaceState && '#' === location.href.slice( -1 ) ) { + history.replaceState( {}, '', location.href.slice( 0, -1 ) ); + } + + as3cf.tabs.toggle( location.hash.replace( '#', '' ), true ); + } + $( document ).ready( function() { // Tabs // -------------------- + renderCurrentTab(); + + /** + * Set the hashchange callback to update the rendered active tab. + */ + window.onhashchange = renderCurrentTab; // Move any compatibility errors below the nav tabs var $navTabs = $( '.wrap.aws-main .nav-tab-wrapper' ); $( '.aws-compatibility-notice, div.updated, div.error, div.notice' ).not( '.below-h2, .inline' ).insertAfter( $navTabs ); - // Check for hash in url and switch tabs accordingly - if ( window.location.hash ) { - var hash = window.location.hash.substring( 1 ); - as3cf.tabs.toggle( hash, true ); - } else { - - // Default settings tab - $activeTab = $( '#tab-' + as3cf.tabs.defaultTab ); - $( '.aws-main' ).attr( 'data-tab', as3cf.tabs.defaultTab ); - } - - $( '.aws-main' ).on( 'click', '.nav-tab', function( e ) { - e.preventDefault(); - if ( $( this ).hasClass( 'nav-tab-active' ) ) { - return; - } - var nextTab = $( this ).attr( 'data-tab' ); - as3cf.tabs.toggle( nextTab ); - if ( 'media' === nextTab ) { - - // As it's the default remove the hash - window.location.hash = ''; - if ( 'function' === typeof window.history.replaceState && '#' === window.location.href.slice( -1 ) ) { - - // Strip the # if still on the end of the URL - history.replaceState( {}, '', window.location.href.slice( 0, -1 ) ); - } - } else { - window.location.hash = nextTab; - } - } ); - // Settings // -------------------- diff --git a/assets/js/script.min.js b/assets/js/script.min.js index d44524a1..457c04ca 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(a){var b=k.find("#"+a),c=b.find("input[type=checkbox]");b.toggleClass("on").find("span").toggleClass("checked");var d=b.find("span.on").hasClass("checked");c.attr("checked",d).trigger("change")}function e(b){var c=b.next(".as3cf-validation-error"),d=a("#"+k.attr("id")+' form button[type="submit"]'),e=/[^a-zA-Z0-9\.\-]/;e.test(b.val())?(c.show(),d.attr("disabled",!0)):(c.hide(),d.attr("disabled",!1))}function f(){var c=a("#"+b.prefix+"-bucket").val(),d=k.find('input[name="object-prefix"]'),e=d.val();""!==e&&(e="&prefix="+encodeURIComponent(e));var f=as3cf.aws_bucket_link+c+e;a("#"+b.prefix+"-view-bucket").attr("href",f)}function g(){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,c,d){"undefined"!=typeof b.success?a(".as3cf-url-preview").html(b.url):alert(as3cf.strings.get_url_preview_error+b.error)}})}function h(a){as3cf.buckets.bucketSelectLock=!1}function i(){a("#as3cf-remove-local-file").is(":checked")&&a("#as3cf-serve-from-s3").is(":not(:checked)")?a("#as3cf-lost-files-notice").show():a("#as3cf-lost-files-notice").hide()}function j(){a("#as3cf-remove-local-file").is(":checked")?a("#as3cf-remove-local-notice").show():a("#as3cf-remove-local-notice").hide()}var k,l={},m=/[^a-z0-9.-]/,n=!1,o=a(".as3cf-tab");as3cf.tabs={defaultTab:"media",toggle:function(c,d){c=as3cf.tabs.sanitizeHash(c),o.hide(),k=a("#tab-"+c),k.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),k.attr("data-prefix")&&(b.prefix=k.attr("data-prefix")),d||a(".as3cf-updated").removeClass("show"),"support"===c&&as3cf.tabs.getDiagnosticInfo()},getDiagnosticInfo:function(){var b=a(".debug-log-textarea");b.html(as3cf.strings.get_diagnostic_info);var c={action:"as3cf-get-diagnostic-info",_nonce:as3cf.nonces.get_diagnostic_info};a.ajax({url:ajaxurl,type:"POST",dataType:"JSON",data:c,error:function(a,c,d){b.html(d)},success:function(a,c,d){"undefined"!=typeof a.success?b.html(a.diagnostic_info):(b.html(as3cf.strings.get_diagnostic_info_error),b.append(a.error))}})},sanitizeHash:function(b){var c=a("#tab-"+b);return 0===c.length&&(b=as3cf.tabs.defaultTab),b}},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,c,f){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===k.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(n),n=!1),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(),g=e.first().text();if(f===a("#"+b.prefix+"-active-bucket").text())return a(".as3cf-bucket-error").hide(),k.addClass("as3cf-has-bucket"),void b.close();a(".as3cf-bucket-error").hide(),e.text(e.attr("data-working")),e.prop("disabled",!0);var h={action:b.prefix+"-manual-save-bucket",bucket_name:f,_nonce:window[b.prefix.replace(/-/g,"_")].nonces.manual_bucket},i=this;a.ajax({url:ajaxurl,type:"POST",dataType:"JSON",data:h,error:function(a,b,c){e.text(g),i.showError(as3cf.strings.save_bucket_error,c,"as3cf-bucket-manual")},success:function(c,d,h){e.text(g),e.prop("disabled",!1),"undefined"!=typeof c.success?(i.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"),n=!0):i.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 k.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"),g={action:b.prefix+"-save-bucket",bucket_name:f,_nonce:window[b.prefix.replace(/-/g,"_")].nonces.save_bucket},h=this;a.ajax({url:ajaxurl,type:"POST",dataType:"JSON",data:g,error:function(b,c,f){d.removeClass("saving"),h.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,i,j){c.find(".spinner").hide().css("visibility","hidden"),d.removeClass("saving"),"undefined"!=typeof g.success?(h.set(f,g.region,g.can_write),a("#"+b.prefix+"-bucket-select").val("")):(h.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(e,i,j){var m=a(".as3cf-bucket-container."+b.prefix+" .as3cf-manual-save-bucket-form"),n=a("#"+b.prefix+"-active-bucket");if("as3cf"===b.prefix&&0===n.text().trim().length){d("as3cf-copy-to-s3-wrap"),d("as3cf-serve-from-s3-wrap");var o=k.attr("id");l[o]=c(o)}a(".as3cf-error.fatal").hide(),n.text(e),m.find(".as3cf-bucket-name").val(e),a("#"+b.prefix+"-bucket").val(e),a("#"+b.prefix+"-region").val(i),a(".updated").not(".as3cf-notice").show(),k.addClass("as3cf-has-bucket"),k.find(".as3cf-can-write-error").toggle(!j),k.find(".as3cf-bucket-error").hide(),"as3cf"===b.prefix&&g(),f(),b.close(h)},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,c,e){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),n=!0):j.showError(as3cf.strings.create_bucket_error,b.error,"as3cf-bucket-create")}})},isValidName:function(a){return!(a.length<3||a.length>63)&&!0!==m.test(a)},updateNameNotice:function(b){var c=null;!0===m.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),c&&b.length>0?a(".as3cf-invalid-bucket-name").html(c):a(".as3cf-invalid-bucket-name").html("")}},a(document).ready(function(){var h=a(".wrap.aws-main .nav-tab-wrapper");if(a(".aws-compatibility-notice, div.updated, div.error, div.notice").not(".below-h2, .inline").insertAfter(h),window.location.hash){var m=window.location.hash.substring(1);as3cf.tabs.toggle(m,!0)}else k=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}}),o.length&&o.each(function(a,b){l[b.id]=c(b.id)}),a(window).on("beforeunload.as3cf-settings",function(){if(!a.isEmptyObject(l)){var b=k.attr("id");return c(b)!==l[b]?as3cf.strings.save_alert:void 0}}),a(document).on("submit",".as3cf-main-settings form",function(b){a(window).off("beforeunload.as3cf-settings")}),a(".as3cf-switch").on("click",function(b){a(this).hasClass("disabled")||d(a(this).attr("id"))}),o.on("change",".sub-toggle",function(b){var c=a(this).attr("id");a(".as3cf-setting."+c).toggleClass("hide")}),a(".as3cf-domain").on("change",'input[type="radio"]',function(b){var c=a(this).closest('input:radio[name="domain"]:checked'),d=c.val(),e=a(this).parents(".as3cf-domain").find(".as3cf-setting.cloudfront"),f="cloudfront"===d;e.toggleClass("hide",!f)}),a(".url-preview").on("change","input",function(a){g()}),i(),a("#as3cf-serve-from-s3,#as3cf-remove-local-file").on("change",function(a){i()}),j(),a("#as3cf-remove-local-file").on("change",function(a){j()}),a('.as3cf-setting input[type="text"]').keypress(function(a){if(13===a.which)return a.preventDefault(),!1}),a('input[name="cloudfront"]').on("keyup",function(b){e(a(this))}),a('input[name="domain"]').on("change",function(b){var c=a(this),d=a("#"+k.attr("id")+' form button[type="submit"]');"cloudfront"!==c.val()?d.attr("disabled",!1):e(c.next(".as3cf-setting").find('input[name="cloudfront"]'))}),a('input[name="object-prefix"]').on("change",function(a){f()}),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(n),n=!1}),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(c){var d=a(this).val(),e=a(".as3cf-bucket-container."+b.prefix+" .as3cf-create-bucket-form");as3cf.buckets.isValidName(d)?e.find("button[type=submit]").removeAttr("disabled"):e.find("button[type=submit]").attr("disabled",!0),as3cf.buckets.updateNameNotice(d)}),a("body").on("input keyup",".as3cf-manual-save-bucket-form .as3cf-bucket-name",function(c){var d=a(".as3cf-bucket-container."+b.prefix+" .as3cf-manual-save-bucket-form");d.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,c,f){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===k.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(n),n=!1),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(),g=e.first().text();if(f===a("#"+b.prefix+"-active-bucket").text())return a(".as3cf-bucket-error").hide(),k.addClass("as3cf-has-bucket"),void b.close();a(".as3cf-bucket-error").hide(),e.text(e.attr("data-working")),e.prop("disabled",!0);var h={action:b.prefix+"-manual-save-bucket",bucket_name:f,_nonce:window[b.prefix.replace(/-/g,"_")].nonces.manual_bucket},i=this;a.ajax({url:ajaxurl,type:"POST",dataType:"JSON",data:h,error:function(a,b,c){e.text(g),i.showError(as3cf.strings.save_bucket_error,c,"as3cf-bucket-manual")},success:function(c,d,h){e.text(g),e.prop("disabled",!1),"undefined"!=typeof c.success?(i.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"),n=!0):i.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 k.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"),g={action:b.prefix+"-save-bucket",bucket_name:f,_nonce:window[b.prefix.replace(/-/g,"_")].nonces.save_bucket},h=this;a.ajax({url:ajaxurl,type:"POST",dataType:"JSON",data:g,error:function(b,c,f){d.removeClass("saving"),h.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,i,j){c.find(".spinner").hide().css("visibility","hidden"),d.removeClass("saving"),"undefined"!=typeof g.success?(h.set(f,g.region,g.can_write),a("#"+b.prefix+"-bucket-select").val("")):(h.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(e,h,i){var j=a(".as3cf-bucket-container."+b.prefix+" .as3cf-manual-save-bucket-form"),m=a("#"+b.prefix+"-active-bucket");if("as3cf"===b.prefix&&0===m.text().trim().length){d("as3cf-copy-to-s3-wrap"),d("as3cf-serve-from-s3-wrap");var n=k.attr("id");l[n]=c(n)}a(".as3cf-error.fatal").hide(),m.text(e),j.find(".as3cf-bucket-name").val(e),a("#"+b.prefix+"-bucket").val(e),a("#"+b.prefix+"-region").val(h),a(".updated").not(".as3cf-notice").show(),k.addClass("as3cf-has-bucket"),k.find(".as3cf-can-write-error").toggle(!i),k.find(".as3cf-bucket-error").hide(),"as3cf"===b.prefix&&g(),f(),b.close(function(){k.trigger("bucket-change",[i]),as3cf.buckets.bucketSelectLock=!1})},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,c,e){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),n=!0):j.showError(as3cf.strings.create_bucket_error,b.error,"as3cf-bucket-create")}})},isValidName:function(a){return!(a.length<3||a.length>63)&&!0!==m.test(a)},updateNameNotice:function(b){var c=null;!0===m.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),c&&b.length>0?a(".as3cf-invalid-bucket-name").html(c):a(".as3cf-invalid-bucket-name").html("")}},a(document).ready(function(){j(),window.onhashchange=j;var m=a(".wrap.aws-main .nav-tab-wrapper");a(".aws-compatibility-notice, div.updated, div.error, div.notice").not(".below-h2, .inline").insertAfter(m),o.length&&o.each(function(a,b){l[b.id]=c(b.id)}),a(window).on("beforeunload.as3cf-settings",function(){if(!a.isEmptyObject(l)){var b=k.attr("id");return c(b)!==l[b]?as3cf.strings.save_alert:void 0}}),a(document).on("submit",".as3cf-main-settings form",function(b){a(window).off("beforeunload.as3cf-settings")}),a(".as3cf-switch").on("click",function(b){a(this).hasClass("disabled")||d(a(this).attr("id"))}),o.on("change",".sub-toggle",function(b){var c=a(this).attr("id");a(".as3cf-setting."+c).toggleClass("hide")}),a(".as3cf-domain").on("change",'input[type="radio"]',function(b){var c=a(this).closest('input:radio[name="domain"]:checked'),d=c.val(),e=a(this).parents(".as3cf-domain").find(".as3cf-setting.cloudfront"),f="cloudfront"===d;e.toggleClass("hide",!f)}),a(".url-preview").on("change","input",function(a){g()}),h(),a("#as3cf-serve-from-s3,#as3cf-remove-local-file").on("change",function(a){h()}),i(),a("#as3cf-remove-local-file").on("change",function(a){i()}),a('.as3cf-setting input[type="text"]').keypress(function(a){if(13===a.which)return a.preventDefault(),!1}),a('input[name="cloudfront"]').on("keyup",function(b){e(a(this))}),a('input[name="domain"]').on("change",function(b){var c=a(this),d=a("#"+k.attr("id")+' form button[type="submit"]');"cloudfront"!==c.val()?d.attr("disabled",!1):e(c.next(".as3cf-setting").find('input[name="cloudfront"]'))}),a('input[name="object-prefix"]').on("change",function(a){f()}),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(n),n=!1}),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(c){var d=a(this).val(),e=a(".as3cf-bucket-container."+b.prefix+" .as3cf-create-bucket-form");as3cf.buckets.isValidName(d)?e.find("button[type=submit]").removeAttr("disabled"):e.find("button[type=submit]").attr("disabled",!0),as3cf.buckets.updateNameNotice(d)}),a("body").on("input keyup",".as3cf-manual-save-bucket-form .as3cf-bucket-name",function(c){var d=a(".as3cf-bucket-container."+b.prefix+" .as3cf-manual-save-bucket-form");d.find(".as3cf-bucket-name").val().lengthplugin_title = __( 'Offload S3 Lite', 'amazon-s3-and-cloudfront' ); $this->plugin_menu_title = __( 'S3 and CloudFront', 'amazon-s3-and-cloudfront' ); - new AS3CF_Upgrade_Region_Meta( $this ); - new AS3CF_Upgrade_File_Sizes( $this ); - new AS3CF_Upgrade_Meta_WP_Error( $this ); - new AS3CF_Upgrade_Content_Replace_URLs( $this ); - new AS3CF_Upgrade_EDD_Replace_URLs( $this ); - new AS3CF_Upgrade_Filter_Post_Excerpt( $this ); + new Upgrade_Region_Meta( $this ); + new Upgrade_File_Sizes( $this ); + new Upgrade_Meta_WP_Error( $this ); + new Upgrade_Content_Replace_URLs( $this ); + new Upgrade_EDD_Replace_URLs( $this ); + new Upgrade_Filter_Post_Excerpt( $this ); // Plugin setup add_action( 'aws_admin_menu', array( $this, 'admin_menu' ) ); @@ -150,7 +157,8 @@ function init( $plugin_file_path ) { add_filter( 'wp_prepare_attachment_for_js', array( $this, 'maybe_encode_wp_prepare_attachment_for_js', ), 99, 3 ); add_filter( 'image_get_intermediate_size', array( $this, 'maybe_encode_image_get_intermediate_size' ), 99, 3 ); add_filter( 'get_attached_file', array( $this, 'get_attached_file' ), 10, 2 ); - add_filter( 'wp_audio_shortcode', array( $this, 'wp_audio_shortcode' ), 100, 5 ); + add_filter( 'wp_audio_shortcode', array( $this, 'wp_media_shortcode' ), 100, 5 ); + add_filter( 'wp_video_shortcode', array( $this, 'wp_media_shortcode' ), 100, 5 ); // Communication with S3, plugin needs to be setup add_filter( 'wp_handle_upload_prefilter', array( $this, 'wp_handle_upload_prefilter' ), 1 ); @@ -213,14 +221,14 @@ function get_setting_args( $key ) { 'key' => $key, 'disabled' => false, 'disabled_attr' => '', - 'tr_class' => '', + 'tr_class' => str_replace( '_', '-', $this->plugin_prefix . '-' . $key . '-container' ), 'setting_msg' => '', ); if ( false !== $is_defined ) { $args['disabled'] = true; $args['disabled_attr'] = 'disabled="disabled"'; - $args['tr_class'] = 'as3cf-defined-setting'; + $args['tr_class'] .= ' as3cf-defined-setting'; $args['setting_msg'] = '' . __( 'defined in wp-config.php', 'as3cf' ) . ''; } @@ -385,8 +393,10 @@ public function get_setting_region( $settings, $key, $default ) { } // Store the region for future use - parent::set_setting( 'region', $region ); - $this->save_settings(); + if ( is_string( $region ) ) { + parent::set_setting( 'region', $region ); + $this->save_settings(); + } return $region; } @@ -779,7 +789,7 @@ function remove_attachment_files_from_s3( $post_id, $s3object, $remove_backup_si $prefix = $this->normalize_object_prefix( $s3object['key'] ); $bucket = $s3object['bucket']; $region = $this->get_s3object_region( $s3object ); - $paths = $this->get_attachment_file_paths( $post_id, false, false, $remove_backup_sizes ); + $paths = AS3CF_Utils::get_attachment_file_paths( $post_id, false, false, $remove_backup_sizes ); $paths = apply_filters( 'as3cf_remove_attachment_paths', $paths, $post_id, $s3object, $remove_backup_sizes ); if ( is_wp_error( $region ) ) { @@ -845,9 +855,13 @@ function wp_update_attachment_metadata( $data, $post_id ) { } // upload attachment to S3 - $data = $this->upload_attachment_to_s3( $post_id, $data ); + $s3_meta = $this->upload_attachment_to_s3( $post_id, $data ); - return $data; + if ( is_wp_error( $s3_meta ) ) { + return $data; + } + + return $s3_meta; } /** @@ -979,28 +993,27 @@ public function upload_attachment_to_s3( $post_id, $data = null, $file_path = nu $args['ContentEncoding'] = 'gzip'; } - $args = apply_filters( 'as3cf_object_meta', $args, $post_id ); + $image_size = wp_attachment_is_image( $post_id ) ? 'full' : ''; + $args = apply_filters( 'as3cf_object_meta', $args, $post_id, $image_size, false ); do_action( 'as3cf_upload_attachment_pre_remove', $post_id, $s3object, $prefix, $args ); $files_to_remove = array(); - if ( file_exists( $file_path ) ) { - try { - $s3client->putObject( $args ); - $files_to_remove[] = $file_path; - } catch ( Exception $e ) { - $error_msg = sprintf( __( 'Error uploading %s to S3: %s', 'amazon-s3-and-cloudfront' ), $file_path, $e->getMessage() ); + try { + $s3client->putObject( $args ); + $files_to_remove[] = $file_path; + } catch ( Exception $e ) { + $error_msg = sprintf( __( 'Error uploading %s to S3: %s', 'amazon-s3-and-cloudfront' ), $file_path, $e->getMessage() ); - return $this->return_upload_error( $error_msg, $return_metadata ); - } + return $this->return_upload_error( $error_msg, $return_metadata ); } delete_post_meta( $post_id, 'amazonS3_info' ); add_post_meta( $post_id, 'amazonS3_info', $s3object ); - $file_paths = $this->get_attachment_file_paths( $post_id, true, $data ); + $file_paths = AS3CF_Utils::get_attachment_file_paths( $post_id, false, $data ); $additional_images = array(); $filesize_total = 0; @@ -1028,7 +1041,7 @@ public function upload_attachment_to_s3( $post_id, $data = null, $file_path = nu if ( ! in_array( $file_path, $files_to_remove ) ) { $acl = apply_filters( 'as3cf_upload_acl_sizes', self::DEFAULT_ACL, $size, $post_id, $data ); - $additional_images[] = array( + $additional_images[ $size ] = array( 'Key' => $prefix . wp_basename( $file_path ), 'SourceFile' => $file_path, 'ACL' => $acl, @@ -1039,7 +1052,7 @@ public function upload_attachment_to_s3( $post_id, $data = null, $file_path = nu $s3object_sizes[ $size ]['acl'] = $acl; } - if ( $remove_local_files_setting ) { + if ( $remove_local_files_setting && file_exists( $file_path ) ) { // Record the file size for the additional image $bytes = filesize( $file_path ); if ( false !== $bytes ) { @@ -1049,13 +1062,21 @@ public function upload_attachment_to_s3( $post_id, $data = null, $file_path = nu } } - foreach ( $additional_images as $image ) { + $upload_errors = array(); + + foreach ( $additional_images as $size => $image ) { + $args = apply_filters( 'as3cf_object_meta', array_merge( $args, $image ), $post_id, $size, false ); + + if ( ! file_exists( $args['SourceFile'] ) ) { + $upload_errors[] = $this->return_upload_error( sprintf( __( 'File %s does not exist', 'amazon-s3-and-cloudfront' ), $args['SourceFile'] ) ); + continue; + } + try { - $args = array_merge( $args, $image ); $s3client->putObject( $args ); $files_to_remove[] = $image['SourceFile']; } catch ( Exception $e ) { - AS3CF_Error::log( 'Error uploading ' . $args['SourceFile'] . ' to S3: ' . $e->getMessage() ); + $upload_errors[] = $this->return_upload_error( sprintf( __( 'Error uploading %s to S3: %s', 'amazon-s3-and-cloudfront' ), $args['SourceFile'], $e->getMessage() ) ); } } @@ -1101,6 +1122,10 @@ public function upload_attachment_to_s3( $post_id, $data = null, $file_path = nu do_action( 'wpos3_post_upload_attachment', $post_id, $s3object ); + if ( $upload_errors ) { + return $this->consolidate_upload_errors( $upload_errors ); + } + if ( ! is_null( $return_metadata ) ) { // If the attachment metadata is supplied, return it return $data; @@ -1168,7 +1193,7 @@ protected function get_mime_types_to_gzip( $media_library = false ) { } /** - * Helper to return meta data on upload error + * Helper to record errors and return meta data on upload error. * * @param string $error_msg * @param array|null $return @@ -1176,12 +1201,13 @@ protected function get_mime_types_to_gzip( $media_library = false ) { * @return array|WP_Error */ protected function return_upload_error( $error_msg, $return = null ) { + + AS3CF_Error::log( $error_msg ); + if ( is_null( $return ) ) { return new WP_Error( 'exception', $error_msg ); } - AS3CF_Error::log( $error_msg ); - return $return; } @@ -1429,8 +1455,8 @@ function does_file_exist_s3( $filename, $time ) { } $s3client = $this->get_s3client( $region ); - $prefix = ltrim( trailingslashit( $this->get_object_prefix() ), '/' ); - $prefix .= ltrim( trailingslashit( $this->get_dynamic_prefix( $time ) ), '/' ); + $prefix = AS3CF_Utils::trailingslash_prefix( $this->get_object_prefix() ); + $prefix .= AS3CF_Utils::trailingslash_prefix( $this->get_dynamic_prefix( $time ) ); return $s3client->doesObjectExist( $bucket, $prefix . $filename ); } @@ -1464,7 +1490,15 @@ function generate_unique_filename( $name, $ext, $time ) { * @return mixed */ public function get_attachment_s3_info( $post_id ) { - return apply_filters( 'as3cf_get_attachment_s3_info', get_post_meta( $post_id, 'amazonS3_info', true ), $post_id ); + $s3_object = get_post_meta( $post_id, 'amazonS3_info', true ); + + if ( is_array( $s3_object ) ) { + $s3_object = array_merge( array( + 'region' => null, + ), $s3_object ); + } + + return apply_filters( 'as3cf_get_attachment_s3_info', $s3_object, $post_id ); } /** @@ -1502,9 +1536,9 @@ function is_plugin_setup() { * @param array $headers Header overrides for request * @param bool $skip_rewrite_check * - * @return mixed|void|WP_Error + * @return mixed|WP_Error */ - function get_secure_attachment_url( $post_id, $expires = null, $size = null, $headers = array(), $skip_rewrite_check = false ) { + public function get_secure_attachment_url( $post_id, $expires = null, $size = null, $headers = array(), $skip_rewrite_check = false ) { if ( is_null( $expires ) ) { $expires = self::DEFAULT_EXPIRES; } @@ -1561,12 +1595,10 @@ function use_ssl( $use_ssl = null ) { */ function get_object_prefix( $toggle_setting = 'enable-object-prefix' ) { if ( $this->get_setting( $toggle_setting ) ) { - $prefix = trim( $this->get_setting( 'object-prefix' ) ); - } else { - $prefix = ''; + return trailingslashit( trim( $this->get_setting( 'object-prefix' ) ) ); } - return $prefix; + return ''; } /** @@ -1576,12 +1608,12 @@ function get_object_prefix( $toggle_setting = 'enable-object-prefix' ) { * * @return string */ - function get_file_prefix( $time = null ) { - $prefix = ltrim( trailingslashit( $this->get_object_prefix() ), '/' ); - $prefix .= ltrim( trailingslashit( $this->get_dynamic_prefix( $time ) ), '/' ); + public function get_file_prefix( $time = null ) { + $prefix = AS3CF_Utils::trailingslash_prefix( $this->get_object_prefix() ); + $prefix .= AS3CF_Utils::trailingslash_prefix( $this->get_dynamic_prefix( $time ) ); if ( $this->get_setting( 'object-versioning' ) ) { - $prefix .= $this->get_object_version_string(); + $prefix .= AS3CF_Utils::trailingslash_prefix( $this->get_object_version_string() ); } return $prefix; @@ -2423,7 +2455,7 @@ function admin_menu( $aws ) { * * @return Aws\S3\S3Client */ - function get_s3client( $region = false, $force = false ) { + public function get_s3client( $region = false, $force = false ) { if ( is_null( $this->s3client ) || $force ) { $args = array( @@ -2577,34 +2609,30 @@ function check_write_permission( $bucket = null, $region = null ) { } } + // need to set region for buckets in non default region + if ( is_null( $region ) ) { + $region = $this->get_setting( 'region' ); + + if ( is_wp_error( $region ) ) { + return $region; + } + } + if ( isset( self::$buckets_check[ $bucket ] ) ) { return self::$buckets_check[ $bucket ]; } - $file_name = 'as3cf-permission-check.txt'; + $key = $this->get_file_prefix() . 'as3cf-permission-check.txt'; $file_contents = __( 'This is a test file to check if the user has write permission to S3. Delete me if found.', 'amazon-s3-and-cloudfront' ); - $path = $this->get_object_prefix(); - $key = $path . $file_name; - - $args = array( - 'Bucket' => $bucket, - 'Key' => $key, - 'Body' => $file_contents, - 'ACL' => 'public-read', - ); - try { - // need to set region for buckets in non default region - if ( is_null( $region ) ) { - $region = $this->get_setting( 'region' ); - - if ( is_wp_error( $region ) ) { - return $region; - } - } // attempt to create the test file - $this->get_s3client( $region, true )->putObject( $args ); + $this->get_s3client( $region, true )->putObject( array( + 'Bucket' => $bucket, + 'Key' => $key, + 'Body' => $file_contents, + 'ACL' => 'public-read', + ) ); // delete it straight away if created $this->get_s3client()->deleteObject( array( 'Bucket' => $bucket, @@ -2657,15 +2685,12 @@ function register_modal_assets() { wp_register_script( 'as3cf-modal', $src, array( 'jquery' ), $version, true ); } - function plugin_load() { - $version = $this->get_asset_version(); - $suffix = $this->get_asset_suffix(); - - $src = plugins_url( 'assets/css/styles.css', $this->plugin_file_path ); - wp_enqueue_style( 'as3cf-styles', $src, array( 'as3cf-modal' ), $version ); - - $src = plugins_url( 'assets/js/script' . $suffix . '.js', $this->plugin_file_path ); - wp_enqueue_script( 'as3cf-script', $src, array( 'jquery', 'as3cf-modal' ), $version, true ); + /** + * On plugin load. + */ + public function plugin_load() { + $this->enqueue_style( 'as3cf-styles', 'assets/css/styles', array( 'as3cf-modal' ) ); + $this->enqueue_script( 'as3cf-script', 'assets/js/script', array( 'jquery', 'as3cf-modal' ) ); wp_localize_script( 'as3cf-script', 'as3cf', @@ -3029,7 +3054,7 @@ function get_aws_bucket_link( $bucket = '', $prefix = '' ) { * * @return array|bool|WP_Error */ - function set_attachment_acl_on_s3( $post_id, $s3object, $acl ) { + public function set_attachment_acl_on_s3( $post_id, $s3object, $acl ) { // Return early if already set to the desired ACL if ( isset( $s3object['acl'] ) && $acl === $s3object['acl'] ) { return false; @@ -3689,80 +3714,6 @@ function get_all_blog_table_prefixes( $exclude_blog_ids = array() ) { return $table_prefixes; } - /** - * Get file paths for all attachment versions. - * - * @param int $attachment_id - * @param bool $exists_locally - * @param array|bool $meta - * @param bool $include_backups - * - * @return array - */ - public function get_attachment_file_paths( $attachment_id, $exists_locally = true, $meta = false, $include_backups = true ) { - $file_path = get_attached_file( $attachment_id, true ); - $paths = array( - 'original' => $file_path, - ); - - if ( ! $meta ) { - $meta = get_post_meta( $attachment_id, '_wp_attachment_metadata', true ); - } - - if ( is_wp_error( $meta ) ) { - return $paths; - } - - $file_name = wp_basename( $file_path ); - - // If file edited, current file name might be different. - if ( isset( $meta['file'] ) ) { - $paths['file'] = str_replace( $file_name, wp_basename( $meta['file'] ), $file_path ); - } - - // Thumb - if ( isset( $meta['thumb'] ) ) { - $paths['thumb'] = str_replace( $file_name, $meta['thumb'], $file_path ); - } - - // Sizes - if ( isset( $meta['sizes'] ) ) { - foreach ( $meta['sizes'] as $size => $file ) { - if ( isset( $file['file'] ) ) { - $paths[ $size ] = str_replace( $file_name, $file['file'], $file_path ); - } - } - } - - $backups = get_post_meta( $attachment_id, '_wp_attachment_backup_sizes', true ); - - // Backups - if ( $include_backups && is_array( $backups ) ) { - foreach ( $backups as $size => $file ) { - if ( isset( $file['file'] ) ) { - $paths[ $size ] = str_replace( $file_name, $file['file'], $file_path ); - } - } - } - - // Allow other processes to add files to be uploaded - $paths = apply_filters( 'as3cf_attachment_file_paths', $paths, $attachment_id, $meta ); - - // Remove duplicates - $paths = array_unique( $paths ); - - // Remove paths that don't exist - if ( $exists_locally ) { - foreach ( $paths as $key => $path ) { - if ( ! file_exists( $path ) ) { - unset( $paths[ $key ] ); - } - } - } - - return $paths; - } - /** * Get the access denied bucket error notice message * @@ -3771,12 +3722,15 @@ public function get_attachment_file_paths( $attachment_id, $exists_locally = tru * @return string */ function get_access_denied_notice_message( $single = true ) { - $quick_start = sprintf( '%s', 'https://deliciousbrains.com/wp-offload-s3/doc/quick-start-guide/#bucket-restrictions', __( 'Quick Start Guide', 'amazon-s3-and-cloudfront' ) ); + $url = $this->dbrains_url( '/wp-offload-s3/doc/quick-start-guide/', array( + 'utm_campaign' => 'error+messages', + ), 'bucket-restrictions' ); + + $quick_start = sprintf( '%s', $url, __( 'Quick Start Guide', 'amazon-s3-and-cloudfront' ) ); $message = sprintf( __( "Looks like we don't have write access to this bucket. It's likely that the user you've provided access keys for hasn't been granted the correct permissions. Please see our %s for instructions on setting up permissions correctly.", 'amazon-s3-and-cloudfront' ), $quick_start ); if ( ! $single ) { $message = sprintf( __( "Looks like we don't have access to the buckets. It's likely that the user you've provided access keys for hasn't been granted the correct permissions. Please see our %s for instructions on setting up permissions correctly.", 'amazon-s3-and-cloudfront' ), $quick_start ); - } return $message; @@ -3914,7 +3868,7 @@ protected function diagnostic_media_counts() { 's3' => $all_media_s3, ); - set_site_transient( 'wpos3_attachment_counts', $attachment_counts, 2 * HOUR_IN_SECONDS ); + set_site_transient( 'wpos3_attachment_counts', $attachment_counts, 2 * MINUTE_IN_SECONDS ); } return $attachment_counts; @@ -3955,69 +3909,49 @@ public function _throw_error( $code, $message = '', $data = '' ) { } /** - * Create a More Info campaign url for given url. - * - * @param string $url + * Get UTM source for plugin. * * @return string */ - private function more_info_campaign_url( $url ) { - $campaign = $this->is_pro() ? 'os3-pro-plugin' : 'os3-free-plugin'; - $url .= '?utm_source=insideplugin&utm_medium=web&utm_content=more-info&utm_campaign=' . $campaign; - - return $url; + protected function get_utm_source() { + return $this->is_pro() ? 'OS3+Paid' : 'OS3+Free'; } /** - * Create a site link for given url, link text and optional anchor, usually with campaign. - * - * TODO: Update *all* hardcoded https://deliciousbrains.com urls to use relative path - * that this function then prepends with configured base URL. - * https://github.com/deliciousbrains/wp-aws/issues/1291 + * More info link. * - * @param string $url - * @param string $text - * @param string $hash Optional anchor text. - * @param bool $append_campaign + * @param string $path + * @param string $utm_content + * @param string $hash * * @return string */ - public function dbrains_link( $url, $text, $hash = '', $append_campaign = true ) { - if ( $append_campaign ) { - $url = $this->more_info_campaign_url( $url ); - } + public function more_info_link( $path, $utm_content = '', $hash = '' ) { + $args = array( + 'utm_campaign' => 'support+docs', + ); - if ( ! empty( $hash ) ) { - $url .= '#' . $hash; + if ( ! empty( $utm_content ) ) { + $args['utm_content'] = $utm_content; } - return sprintf( '%s', esc_url( $url ), esc_html( $text ) ); - } - - /** - * More info link - * - * @param string $url - * @param string $hash - * @param bool $append_campaign - * - * @return string - */ - public function more_info_link( $url, $hash = '', $append_campaign = true ) { - $link = $this->dbrains_link( $url, __( 'More info', 'amazon-s3-and-cloudfront' ), $hash, $append_campaign ); + $url = $this->dbrains_url( $path, $args, $hash ); + $text = __( 'More info', 'amazon-s3-and-cloudfront' ); + $link = AS3CF_Utils::dbrains_link( $url, $text ); return sprintf( '%s »', $link ); } /** - * Settings more info link + * Settings more info link. * * @param string $hash + * @param string $utm_content * * @return string */ - public function settings_more_info_link( $hash ) { - return $this->more_info_link( 'https://deliciousbrains.com/wp-offload-s3/doc/settings/', $hash ); + public function settings_more_info_link( $hash, $utm_content = '' ) { + return $this->more_info_link( '/wp-offload-s3/doc/settings/', $utm_content, $hash ); } /** @@ -4094,8 +4028,10 @@ protected function maybe_display_deprecated_http_notice() { 'flash' => false, ); - $doc_url = 'https://deliciousbrains.com/wp-offload-s3/doc/force-http-setting/'; - $doc_link = $this->dbrains_link( $doc_url, __( 'this doc' ) ); + $doc_url = $this->dbrains_url( '/wp-offload-s3/doc/force-http-setting/', array( + 'utm_campaign' => 'support+docs' + ) ); + $doc_link = AS3CF_Utils::dbrains_link( $doc_url, __( 'this doc' ) ); $message = sprintf( '%s — ', __( 'WP Offload S3 Feature Removed', 'amazon-s3-and-cloudfront' ) ); $message .= sprintf( __( 'You had the "Always non-SSL" option selected in your settings, but we\'ve removed this option in version 1.3. We\'ll now use HTTPS when the request is HTTPS and regular HTTP when the request is HTTP. This should work fine for your site, but please take a poke around and make sure things are working ok. See %s for more details on why we did this and how you can revert back to the old behavior.', 'amazon-s3-and-cloudfront' ), $doc_link ); @@ -4245,31 +4181,20 @@ public function get_media_action_strings( $string = null ) { * Load media assets. */ public function load_media_assets() { - $version = $this->get_asset_version(); - $suffix = $this->get_asset_suffix(); - - $src = plugins_url( 'assets/css/media.css', $this->plugin_file_path ); - wp_enqueue_style( 'as3cf-media-styles', $src, array( 'as3cf-modal' ), $version ); - - $src = plugins_url( 'assets/js/media' . $suffix . '.js', $this->plugin_file_path ); - wp_enqueue_script( - 'as3cf-media-script', - $src, - array( 'jquery', 'media-views', 'media-grid', 'wp-util' ), - $version, - true - ); + $this->enqueue_style( 'as3cf-media-styles', 'assets/css/media', array( 'as3cf-modal' ) ); + $this->enqueue_script( 'as3cf-media-script', 'assets/js/media', array( + 'jquery', + 'media-views', + 'media-grid', + 'wp-util', + ) ); - wp_localize_script( - 'as3cf-media-script', - 'as3cf_media', - array( + wp_localize_script( 'as3cf-media-script', 'as3cf_media', array( 'strings' => $this->get_media_action_strings(), 'nonces' => array( 'get_attachment_s3_details' => wp_create_nonce( 'get-attachment-s3-details' ), ), - ) - ); + ) ); } /** @@ -4313,18 +4238,14 @@ function add_media_row_actions( $actions = array(), $post ) { * @param $hook_suffix */ public function load_attachment_assets( $hook_suffix ) { - $version = $this->get_asset_version(); - $suffix = $this->get_asset_suffix(); - global $post; - if ( 'post.php' != $hook_suffix || 'attachment' != $post->post_type ) { + if ( 'post.php' !== $hook_suffix || 'attachment' !== $post->post_type ) { return; } - $src = plugins_url( 'assets/css/attachment.css', $this->plugin_file_path ); - wp_enqueue_style( 'as3cf-pro-attachment-styles', $src, array( 'as3cf-modal' ), $version ); + $this->enqueue_style( 'as3cf-pro-attachment-styles', 'assets/css/attachment', array( 'as3cf-modal' ) ); - do_action( 'as3cf_load_attachment_assets', $version, $suffix ); + do_action( 'as3cf_load_attachment_assets' ); } /** @@ -4353,36 +4274,6 @@ protected function normalize_object_prefix( $prefix ) { return ( '.' === $directory ) ? '' : $directory . '/'; } - /** - * Remove scheme from URL. - * - * @param string $url - * - * @return string - */ - public function remove_scheme( $url ) { - return preg_replace( '/^(?:http|https):/', '', $url ); - } - - /** - * Remove size from filename (image[-100x100].jpeg). - * - * @param string $url - * @param bool $remove_extension - * - * @return string - */ - public function remove_size_from_filename( $url, $remove_extension = false ) { - $url = preg_replace( '/^(\S+)-[0-9]{1,4}x[0-9]{1,4}(\.[a-zA-Z0-9\.]{2,})?/', '$1$2', $url ); - - if ( $remove_extension ) { - $ext = pathinfo( $url, PATHINFO_EXTENSION ); - $url = str_replace( ".$ext", '', $url ); - } - - return $url; - } - /** * Has the given attachment been uploaded by this instance? * @@ -4399,19 +4290,19 @@ public function attachment_just_uploaded( $attachment_id ) { } /** - * Filters the audio shortcode output to remove "&_=NN" params from source.src as it breaks signed URLs. + * Filters the audio & video shortcodes output to remove "&_=NN" params from source.src as it breaks signed URLs. * - * @param string $html Audio shortcode HTML output. - * @param array $atts Array of audio shortcode attributes. - * @param string $audio Audio file. + * @param string $html Shortcode HTML output. + * @param array $atts Array of shortcode attributes. + * @param string $media Media file. * @param int $post_id Post ID. - * @param string $library Media library used for the audio shortcode. + * @param string $library Media library used for the shortcode. * * @return string * * Note: Depends on 30377.4.diff from https://core.trac.wordpress.org/ticket/30377 */ - public function wp_audio_shortcode( $html, $atts, $audio, $post_id, $library ) { + public function wp_media_shortcode( $html, $atts, $media, $post_id, $library ) { $html = preg_replace( '/&_=[0-9]+/', '', $html ); return $html; @@ -4435,4 +4326,45 @@ public function maybe_fix_local_subsite_url( $url ) { return $url; } + + /** + * Get ACL for intermediate size. + * + * @param int $attachment_id + * @param string $size + * + * @return string + */ + public function get_acl_for_intermediate_size( $attachment_id, $size ) { + $s3_info = $this->get_attachment_s3_info( $attachment_id ); + + if ( 'original' === $size || empty( $size ) ) { + return isset( $s3_info['acl'] ) ? $s3_info['acl'] : self::DEFAULT_ACL; + } + + if ( ! empty( $s3_info['sizes'][ $size ]['acl'] ) ) { + return $s3_info['sizes'][ $size ]['acl']; + } + + return self::DEFAULT_ACL; + } + + /** + * Consolidate an array of WP_Errors into a single WP_Error object. + * + * @param array $upload_errors + * + * @return WP_Error + */ + protected function consolidate_upload_errors( $upload_errors ) { + $errors = new WP_Error; + + foreach ( $upload_errors as $error ) { + + /* @var WP_Error $error */ + $errors->add( $error->get_error_code(), $error->get_error_message() ); + } + + return $errors; + } } diff --git a/classes/as3cf-filter.php b/classes/as3cf-filter.php index be4bfc0e..506e12cc 100644 --- a/classes/as3cf-filter.php +++ b/classes/as3cf-filter.php @@ -2,6 +2,21 @@ abstract class AS3CF_Filter { + /** + * The key used for storing the URL cache. + */ + const CACHE_KEY = 'amazonS3_cache'; + + /** + * The cache group used by an external object cache for posts. + */ + const POST_CACHE_GROUP = 'post_amazonS3_cache'; + + /** + * The cache group used by an external object cache for options. + */ + const OPTION_CACHE_GROUP = 'option_amazonS3_cache'; + /** * @var Amazon_S3_And_CloudFront */ @@ -12,6 +27,11 @@ abstract class AS3CF_Filter { */ protected $query_cache = array(); + /** + * @var array IDs which have already been purged this request. + */ + protected static $purged_ids = array(); + /** * Constructor * @@ -26,6 +46,13 @@ public function __construct( $as3cf ) { $this->init(); } + /** + * Initialize the filter. + */ + protected function init() { + // Optionally override in a sub-class. + } + /** * Filter EDD download files. * @@ -118,6 +145,87 @@ public function filter_post( $content ) { return $content; } + /** + * Handle widget instances. + * + * @param array $instance + * @param WP_Widget $class + * + * @return array + */ + protected function handle_widget( $instance, $class ) { + if ( empty( $instance ) ) { + return $instance; + } + + $update_cache = true; + + // Editing widgets in Customizer throws an error if more than one option record is updated. + // Therefore cache updating has to wait until render or edit via Appearance menu. + if ( isset( $_POST['wp_customize'] ) && 'on' === $_POST['wp_customize'] ) { + $update_cache = false; + } + + if ( $class instanceof WP_Widget_Media ) { + return $this->filter_media_widget( $instance, $update_cache ); + } + + if ( $class instanceof WP_Widget_Text ) { + return $this->filter_text_widget( $instance, $update_cache ); + } + + return $instance; + } + + /** + * Filter media widget. + * + * @param array $instance + * @param bool $update_cache + * + * @return array + */ + protected function filter_media_widget( $instance, $update_cache ) { + $cache = $this->get_option_cache(); + $to_cache = array(); + + foreach ( $instance as $key => $value ) { + if ( empty( $value ) ) { + continue; + } + + if ( AS3CF_Utils::is_url( $value ) ) { + $instance[ $key ] = $this->process_content( $value, $cache, $to_cache ); + } + } + + if ( $update_cache ) { + $this->maybe_update_option_cache( $to_cache ); + } + + return $instance; + } + + /** + * Filter text widget. + * + * @param array $instance + * @param bool $update_cache + * + * @return array + */ + protected function filter_text_widget( $instance, $update_cache ) { + $cache = $this->get_option_cache(); + $to_cache = array(); + $instance['text'] = $this->process_content( $instance['text'], $cache, $to_cache ); + + if ( $update_cache ) { + $this->maybe_update_option_cache( $to_cache ); + } + + return $instance; + } + /** * Process content. * @@ -206,6 +314,8 @@ protected function get_urls_from_img_src( $content, &$to_cache ) { continue; } + $url = AS3CF_Utils::reduce_url( $url ); + if ( ! preg_match( '/wp-image-([0-9]+)/i', $image, $class_id ) || ! isset( $class_id[1] ) ) { // Can't determine ID from class, skip continue; @@ -264,7 +374,7 @@ protected function get_urls_from_content( $content, $cache, &$to_cache ) { continue; } - $parts = parse_url( $url ); + $parts = AS3CF_Utils::parse_url( $url ); if ( ! isset( $parts['path'] ) ) { // URL doesn't have a path, continue @@ -277,7 +387,7 @@ protected function get_urls_from_content( $content, $cache, &$to_cache ) { } $attachment_id = null; - $bare_url = $this->as3cf->maybe_remove_query_string( $url ); + $bare_url = AS3CF_Utils::reduce_url( $url ); if ( isset( $cache[ $bare_url ] ) ) { $attachment_id = $cache[ $bare_url ]; @@ -290,9 +400,9 @@ protected function get_urls_from_content( $content, $cache, &$to_cache ) { if ( is_null( $attachment_id ) || is_array( $attachment_id ) ) { // Attachment ID not cached, need to search by URL. - $urls[] = $url; + $urls[] = $bare_url; } else { - $this->push_to_url_pairs( $url_pairs, $attachment_id, $url, $to_cache ); + $this->push_to_url_pairs( $url_pairs, $attachment_id, $bare_url, $to_cache ); } } @@ -351,7 +461,7 @@ protected function attachment_id_matches_src( $attachment_id, $url ) { return false; } - $base_url = $this->as3cf->remove_scheme( $this->as3cf->maybe_remove_query_string( $this->get_base_url( $attachment_id ) ) ); + $base_url = AS3CF_Utils::reduce_url( $this->get_base_url( $attachment_id ) ); $basename = wp_basename( $base_url ); // Add full size URL @@ -362,7 +472,7 @@ protected function attachment_id_matches_src( $attachment_id, $url ) { $base_urls[] = str_replace( $basename, $size['file'], $base_url ); } - $url = $this->as3cf->remove_scheme( $this->as3cf->maybe_remove_query_string( $url ) ); + $url = AS3CF_Utils::reduce_url( $url ); if ( in_array( $url, $base_urls ) ) { // Match found, return true @@ -381,7 +491,7 @@ protected function attachment_id_matches_src( $attachment_id, $url ) { * @param array $to_cache */ protected function push_to_url_pairs( &$url_pairs, $attachment_id, $find, &$to_cache ) { - $find_full = $this->as3cf->remove_size_from_filename( $find ); + $find_full = AS3CF_Utils::remove_size_from_filename( $find ); $find_full = $this->normalize_find_value( $this->as3cf->maybe_remove_query_string( $find_full ) ); $find_size = $this->normalize_find_value( $this->as3cf->maybe_remove_query_string( $find ) ); @@ -404,8 +514,8 @@ protected function push_to_url_pairs( &$url_pairs, $attachment_id, $find, &$to_c $parts = parse_url( $find ); if ( ! isset( $parts['scheme'] ) ) { - $replace_full = $this->as3cf->remove_scheme( $replace_full ); - $replace_size = $this->as3cf->remove_scheme( $replace_size ); + $replace_full = AS3CF_Utils::remove_scheme( $replace_full ); + $replace_size = AS3CF_Utils::remove_scheme( $replace_size ); } // Find and replace full version @@ -458,7 +568,7 @@ protected function get_size_string_from_url( $attachment_id, $url ) { * @param array $to_cache */ protected function url_cache_failure( $url, &$to_cache ) { - $full = $this->as3cf->remove_size_from_filename( $url ); + $full = AS3CF_Utils::remove_size_from_filename( $url ); $failure = array( 'timestamp' => time(), ); @@ -495,19 +605,22 @@ protected function replace_urls( $content, $url_pairs ) { /** * Get post cache * - * @param bool|int $post_id + * @param null|int|WP_Post $post Optional. Post ID or post object. Defaults to current post. * * @return array */ - protected function get_post_cache( $post_id = false ) { - $post_id = $this->get_post_id( $post_id ); + public function get_post_cache( $post = null ) { + $post_id = AS3CF_Utils::get_post_id( $post ); if ( ! $post_id ) { - // Post ID not found, return empty cache return array(); } - $cache = get_post_meta( $post_id, 'amazonS3_cache', true ); + if ( wp_using_ext_object_cache() ) { + $cache = wp_cache_get( $post_id, self::POST_CACHE_GROUP ); + } else { + $cache = get_post_meta( $post_id, self::CACHE_KEY, true ); + } if ( empty( $cache ) ) { $cache = array(); @@ -517,42 +630,59 @@ protected function get_post_cache( $post_id = false ) { } /** - * Maybe update post cache + * Set the cache for the given post. * - * @param array $to_cache - * @param bool|int $post_id + * @param null|int|WP_Post $post Optional. Post ID or post object. Defaults to current post. + * @param $data */ - protected function maybe_update_post_cache( $to_cache, $post_id = false ) { - $post_id = $this->get_post_id( $post_id ); + protected function set_post_cache( $post, $data ) { + $post_id = AS3CF_Utils::get_post_id( $post ); - if ( ! $post_id || empty( $to_cache ) ) { + if ( ! $post_id ) { return; } - $urls = array_merge( $this->get_post_cache( $post_id ), $to_cache ); + if ( wp_using_ext_object_cache() ) { + $expires = apply_filters( 'as3cf_' . self::POST_CACHE_GROUP . '_expires', DAY_IN_SECONDS, $post_id, $data ); + wp_cache_set( $post_id, $data, self::POST_CACHE_GROUP, $expires ); + } else { + update_post_meta( $post_id, self::CACHE_KEY, $data ); + } + } - update_post_meta( $post_id, 'amazonS3_cache', $urls ); + /** + * Set the option cache with the given data. + * + * @param $data + */ + protected function set_option_cache( $data ) { + if ( wp_using_ext_object_cache() ) { + $expires = apply_filters( 'as3cf_' . self::OPTION_CACHE_GROUP . '_expires', DAY_IN_SECONDS, self::CACHE_KEY, $data ); + wp_cache_set( self::CACHE_KEY, $data, self::OPTION_CACHE_GROUP, $expires ); + } else { + update_option( self::CACHE_KEY, $data ); + } } /** - * Get post ID. + * Maybe update post cache * + * @param array $to_cache * @param bool|int $post_id - * - * @return bool|int */ - protected function get_post_id( $post_id ) { - if ( false !== $post_id ) { - return $post_id; + protected function maybe_update_post_cache( $to_cache, $post_id = false ) { + $post_id = AS3CF_Utils::get_post_id( $post_id ); + + if ( ! $post_id || empty( $to_cache ) ) { + return; } - global $post; + $cached = $this->get_post_cache( $post_id ); + $urls = static::merge_cache( $cached, $to_cache ); - if ( isset( $post->ID ) ) { - return $post->ID; + if ( $urls !== $cached ) { + $this->set_post_cache( $post_id, $urls ); } - - return false; } /** @@ -561,7 +691,17 @@ protected function get_post_id( $post_id ) { * @return array */ protected function get_option_cache() { - return get_option( 'amazonS3_cache', array() ); + if ( wp_using_ext_object_cache() ) { + $cache = wp_cache_get( self::CACHE_KEY, self::OPTION_CACHE_GROUP ); + } else { + $cache = get_option( self::CACHE_KEY, array() ); + } + + if ( empty( $cache ) ) { + $cache = array(); + } + + return $cache; } /** @@ -574,9 +714,12 @@ protected function maybe_update_option_cache( $to_cache ) { return; } - $urls = array_merge( $this->get_option_cache(), $to_cache ); + $cached = $this->get_option_cache(); + $urls = static::merge_cache( $cached, $to_cache ); - update_option( 'amazonS3_cache', $urls ); + if ( $urls !== $cached ) { + $this->set_option_cache( $urls ); + } } /** @@ -585,11 +728,17 @@ protected function maybe_update_option_cache( $to_cache ) { * @param int $post_id */ public function purge_cache_on_attachment_delete( $post_id ) { - $this->purge_from_cache( $this->get_url( $post_id ) ); + if ( ! in_array( $post_id, self::$purged_ids ) ) { + $this->purge_from_cache( $this->get_url( $post_id ) ); + self::$purged_ids[] = $post_id; + } } /** - * Purge URL from cache + * Purge URL from cache. + * + * Currently does nothing for purging from an external object cache. + * Values are left to expire using the expiration time provided when set. * * @param string $url * @param bool|int $blog_id @@ -606,7 +755,7 @@ public function purge_from_cache( $url, $blog_id = false ) { DELETE FROM {$wpdb->postmeta} WHERE meta_key = %s AND meta_value LIKE %s; - ", 'amazonS3_cache', '%"' . $url . '"%' ); + ", self::CACHE_KEY, '%"' . $url . '"%' ); $wpdb->query( $sql ); @@ -615,7 +764,7 @@ public function purge_from_cache( $url, $blog_id = false ) { DELETE FROM {$wpdb->options} WHERE option_name = %s AND option_value LIKE %s; - ", 'amazonS3_cache', '%"' . $url . '"%' ); + ", self::CACHE_KEY, '%"' . $url . '"%' ); $wpdb->query( $sql ); @@ -691,6 +840,28 @@ protected function filter_custom_css( $css, $stylesheet ) { return $css; } + /** + * Merge content filtering cache arrays. + * + * @param array $existing_cache + * @param array $merge_cache + * + * @return array + */ + public static function merge_cache( $existing_cache, $merge_cache ) { + if ( ! empty( $existing_cache ) ) { + $post_cache_keys = array_map( 'AS3CF_Utils::reduce_url', array_keys( $existing_cache ) ); + $existing_cache = array_combine( $post_cache_keys, $existing_cache ); + } + + if ( ! empty( $merge_cache ) ) { + $add_cache_keys = array_map( 'AS3CF_Utils::reduce_url', array_keys( $merge_cache ) ); + $merge_cache = array_combine( $add_cache_keys, $merge_cache ); + } + + return array_merge( $existing_cache, $merge_cache ); + } + /** * Get custom CSS post ID. * diff --git a/classes/as3cf-notices.php b/classes/as3cf-notices.php index 2cdea6bf..26c70a50 100644 --- a/classes/as3cf-notices.php +++ b/classes/as3cf-notices.php @@ -423,15 +423,8 @@ protected function check_capability_for_notice( $notice ) { * Enqueue notice scripts in the admin */ public function enqueue_notice_scripts() { - $version = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? time() : $GLOBALS['aws_meta']['amazon-s3-and-cloudfront']['version']; - $suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min'; - - // Enqueue notice.css & notice.js globally as some notices can be shown & dismissed on any admin page. - $src = plugins_url( 'assets/css/notice.css', $this->as3cf->get_plugin_file_path() ); - wp_enqueue_style( 'as3cf-notice', $src, array(), $version ); - - $src = plugins_url( 'assets/js/notice' . $suffix . '.js', $this->as3cf->get_plugin_file_path() ); - wp_enqueue_script( 'as3cf-notice', $src, array( 'jquery' ), $version, true ); + $this->as3cf->enqueue_style( 'as3cf-notice', 'assets/css/notice' ); + $this->as3cf->enqueue_script( 'as3cf-notice', 'assets/js/notice', array( 'jquery' ) ); wp_localize_script( 'as3cf-notice', 'as3cf_notice', array( 'strings' => array( diff --git a/classes/as3cf-plugin-compatibility.php b/classes/as3cf-plugin-compatibility.php index 796d75de..8ba2d27e 100644 --- a/classes/as3cf-plugin-compatibility.php +++ b/classes/as3cf-plugin-compatibility.php @@ -72,9 +72,6 @@ function compatibility_init() { * Register the compatibility hooks as long as the plugin is setup. */ function compatibility_init_if_setup() { - // Add notices about compatibility addons to install - add_action( 'admin_init', array( $this, 'maybe_render_compatibility_addons_notice' ) ); - // Turn on stream wrapper S3 file add_filter( 'as3cf_get_attached_file', array( $this, 'get_stream_wrapper_file' ), 20, 4 ); @@ -88,7 +85,7 @@ function compatibility_init_if_setup() { * WP_Image_Editor * /wp-includes/class-wp-image-editor.php */ - add_action( 'as3cf_upload_attachment_pre_remove', array( $this, 'image_editor_remove_files' ), 10, 4 ); + add_action( 'as3cf_pre_upload_attachment', array( $this, 'image_editor_remove_files' ), 10, 3 ); 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_crop_download_file' ), 10, 4 ); @@ -100,202 +97,13 @@ function compatibility_init_if_setup() { * https://wordpress.org/plugins/regenerate-thumbnails/ */ add_filter( 'as3cf_get_attached_file', array( $this, 'regenerate_thumbnails_download_file' ), 10, 4 ); - } - - /** - * Get the addons for the Pro upgrade - * - * @return array - */ - public function get_pro_addons() { - global $amazon_web_services; - - $all_addons = $amazon_web_services->get_addons( true ); - if ( ! isset( $all_addons['amazon-s3-and-cloudfront-pro']['addons'] ) ) { - return array(); - } - - $addons = $all_addons['amazon-s3-and-cloudfront-pro']['addons']; - - return $addons; - } - - /** - * Get compatibility addons that are required to be installed - * - * @return array - */ - public function get_compatibility_addons_to_install() { - if ( isset( $this->compatibility_addons ) ) { - return $this->compatibility_addons; - } - - $addons = $this->get_pro_addons(); - $addons_to_install = array(); - - if ( empty ( $addons ) ) { - return $addons_to_install; - } - - foreach ( $addons as $addon_slug => $addon ) { - if ( file_exists( WP_PLUGIN_DIR . '/' . $addon_slug . '/' . $addon_slug . '.php' ) ) { - // Addon already installed, ignore. - continue; - } - - if ( ! isset( $addon['parent_plugin_basename'] ) || '' === $addon['parent_plugin_basename'] ) { - // Addon doesn't have a parent plugin, ignore. - continue; - } - - if ( ! file_exists( WP_PLUGIN_DIR . '/' . $addon['parent_plugin_basename'] ) || ! is_plugin_active( $addon['parent_plugin_basename'] ) ) { - // Parent plugin not installed or not activated, ignore. - continue; - } - - $addons_to_install[ $addon_slug ] = array( - 'title' => $addon['title'], - 'url' => $addon['url'], - ); - } - - $this->compatibility_addons = $addons_to_install; - - return $addons_to_install; - } - - /** - * Maybe show a notice about installing addons when the site is using the - * plugins they add compatibility for. - */ - public function maybe_render_compatibility_addons_notice() { - if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { - return; - } - - global $as3cf_compat_check; - if ( ! $as3cf_compat_check->check_capabilities() ) { - // User can't install plugins anyway, bail. - return; - } - - if ( ! $this->should_show_compatibility_notice() ) { - // No addons to install, or addons haven't changed - return; - } - - $notice_id = 'as3cf-compat-addons'; - $addons_to_install = $this->get_compatibility_addons_to_install(); - - // Remove previous notice to refresh addon list - $this->remove_compatibility_notice(); - - $title = __( 'WP Offload S3 Compatibility Addons', 'amazon-s3-and-cloudfront' ); - $compat_url = 'https://deliciousbrains.com/wp-offload-s3/doc/compatibility-with-other-plugins/'; - $compat_link = sprintf( '%s', $compat_url, __( 'compatibility addons', 'amazon-s3-and-cloudfront' ) ); - $message = sprintf( __( "To get WP Offload S3 to work with certain 3rd party plugins, you might need to install and activate some of our %s. We've detected the following addons might need to be installed. Please click the links for more information about each addon to determine if you need it or not.", 'amazon-s3-and-cloudfront' ), $compat_link ); - - $notice_addons_text = $this->render_addon_list( $addons_to_install ); - $support_email = 'nom@deliciousbrains.com'; - $support_link = sprintf( '%1$s', $support_email ); - - $notice_addons_text .= '

    ' . sprintf( __( "You will need to purchase a license to get access to these addons. If you're having trouble determining whether or not you need the addons, send an email to %s.", 'amazon-s3-and-cloudfront' ), $support_link ) . '

    '; - $notice_addons_text .= sprintf( '

    %s

    ', 'https://deliciousbrains.com/wp-offload-s3/pricing/', __( 'View Licenses', 'amazon-s3-and-cloudfront' ) ); - - $notice_addons_text = apply_filters( 'wpos3_compat_addons_notice', $notice_addons_text, $addons_to_install ); - - if ( false === $notice_addons_text ) { - // Allow the notice to be aborted. - return; - } - - $notice = '

    ' . $title . ' — ' . $message . '

    ' . $notice_addons_text; - - $notice_args = array( - 'type' => 'notice-info', - 'custom_id' => $notice_id, - 'only_show_to_user' => false, - 'flash' => false, - 'auto_p' => false, - ); - - $notice_args = apply_filters( 'wpos3_compat_addons_notice_args', $notice_args, $addons_to_install ); - - update_site_option( 'as3cf_compat_addons_to_install', $addons_to_install ); - $this->as3cf->notices->add_notice( $notice, $notice_args ); - } - - /** - * Should show compatibility notice - * - * @return bool - */ - protected function should_show_compatibility_notice() { - $addons = $this->get_compatibility_addons_to_install(); - $previous_addons = get_site_option( 'as3cf_compat_addons_to_install', array() ); - - if ( empty( $addons ) && empty( $previous_addons ) ) { - // No addons to install - return false; - } - - if ( empty( $addons ) && ! empty( $previous_addons ) ) { - // No addons to install but previous exist - $this->remove_compatibility_notice( true ); - - return false; - } - - if ( $previous_addons === $addons ) { - // Addons have not changed - return false; - } - - return true; - } - - /** - * Remove compatibility notice - * - * @param bool $delete_option - */ - protected function remove_compatibility_notice( $delete_option = false ) { - $notice_id = 'as3cf-compat-addons'; - - if ( $this->as3cf->notices->find_notice_by_id( $notice_id ) ) { - $this->as3cf->notices->undismiss_notice_for_all( $notice_id ); - $this->as3cf->notices->remove_notice_by_id( $notice_id ); - } - - if ( $delete_option ) { - delete_site_option( 'as3cf_compat_addons_to_install' ); - } - } - - /** - * Render list of addons for a notice - * - * @param array $addons - * - * @return string - */ - protected function render_addon_list( $addons ) { - if ( ! is_array( $addons ) || empty( $addons ) ) { - return ''; - } - - sort( $addons ); - - $html = '
      '; - foreach ( $addons as $addon ) { - $html .= '
    • '; - $html .= '' . $addon['title'] . ''; - $html .= '
    • '; + /* + * WP-CLI Compatibility + */ + if ( defined( 'WP_CLI' ) && class_exists( 'WP_CLI') ) { + WP_CLI::add_hook( 'before_invoke:media regenerate', array( $this, 'enable_get_attached_file_copy_back_to_local' ) ); } - $html .= '
    '; - - return $html; } /** @@ -325,6 +133,13 @@ function legacy_copy_back_to_local( $url, $file, $attachment_id, $s3_object ) { return $url; } + /** + * Enables copying missing local files back to the server when `get_attached_file` filter is called. + */ + public function enable_get_attached_file_copy_back_to_local() { + add_filter( 'as3cf_get_attached_file_copy_back_to_local', '__return_true' ); + } + /** * Is this an AJAX process? * @@ -426,21 +241,49 @@ function get_original_image_file( $post_id, $file_path ) { * Allow the WordPress Image Editor to remove edited version of images * if the original image is being restored and 'IMAGE_EDIT_OVERWRITE' is set * - * @param int $post_id - * @param array $s3object - * @param string $prefix - * @param array $args + * @param bool $pre + * @param int $post_id + * @param array $data + * + * @return bool */ - function image_editor_remove_files( $post_id, $s3object, $prefix, $args ) { + public function image_editor_remove_files( $pre, $post_id, $data ) { if ( ! isset( $_POST['do'] ) || 'restore' !== $_POST['do'] ) { - return; + return $pre; } if ( ! defined( 'IMAGE_EDIT_OVERWRITE' ) || ! IMAGE_EDIT_OVERWRITE ) { + return $pre; + } + + $s3object = $this->as3cf->get_attachment_s3_info( $post_id ); + $this->remove_edited_image_files( $post_id, $s3object ); + + // Update object key with original filename + $restored_filename = wp_basename( $data['file'] ); + $old_filename = wp_basename( $s3object['key'] ); + $s3object['key'] = str_replace( $old_filename, $restored_filename, $s3object['key'] ); + update_post_meta( $post_id, 'amazonS3_info', $s3object ); + + return true; + } + + /** + * Remove edited image files from S3. + * + * @param int $attachment_id + * @param array $s3object + */ + protected function remove_edited_image_files( $attachment_id, $s3object ) { + $bucket = $s3object['bucket']; + $region = $this->as3cf->get_s3object_region( $s3object ); + $keys = AS3CF_Utils::get_attachment_edited_keys( $attachment_id, $s3object ); + + if ( empty( $keys ) ) { return; } - $this->as3cf->remove_attachment_files_from_s3( $post_id, $s3object, false ); + $this->as3cf->delete_s3_objects( $region, $bucket, $keys ); } /** @@ -475,19 +318,19 @@ 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 ( ( $s3_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 $s3_file; }; } - // must be the image-editor process - if ( isset( $_POST['action'] ) && 'image-editor' == sanitize_key( $_POST['action'] ) ) { // input var okay - $callers = debug_backtrace(); - foreach ( $callers as $caller ) { + $action = filter_input( INPUT_GET, 'action' ) ?: filter_input( INPUT_POST, 'action' ); + + if ( in_array( $action, array( 'image-editor', 'imgedit-preview' ) ) ) { // input var okay + foreach ( debug_backtrace() 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 ( ( $s3_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 $s3_file; }; diff --git a/classes/as3cf-upgrade.php b/classes/as3cf-upgrade.php deleted file mode 100644 index 1240dc0f..00000000 --- a/classes/as3cf-upgrade.php +++ /dev/null @@ -1,600 +0,0 @@ -as3cf = $as3cf; - - $this->running_update_text = $this->get_running_update_text(); - $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', 2 ); - $this->error_threshold = apply_filters( 'as3cf_update_' . $this->upgrade_name . '_error_threshold', 20 ); - - add_filter( 'cron_schedules', array( $this, 'cron_schedules' ) ); - 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' ) ); - - // Do default checks if the upgrade can be started - if ( $this->maybe_init() ) { - $this->init(); - } - } - - /** - * Can we start the upgrade using default checks - * - * @return bool - */ - protected function maybe_init() { - if ( ! is_admin() || ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) { - return false; - } - - // make sure this only fires inside the network admin for multisites - if ( is_multisite() && ! is_network_admin() ) { - return false; - } - - // Is plugin setup? - if ( ! $this->as3cf->is_plugin_setup() ) { - return false; - } - - // Have we completed the upgrade? - if ( $this->get_saved_upgrade_id() >= $this->upgrade_id ) { - return false; - } - - // Has the previous upgrade completed yet? - if ( ! $this->has_previous_upgrade_completed() ) { - return false; - } - - // Does the upgrade lock exist? - if ( false !== get_site_transient( $this->lock_key ) ) { - return false; - } - - // Do we actually have attachments to process? - if ( 0 === $this->count_items_to_process() ) { - $this->upgrade_finished(); - - return false; - } - - // If the upgrade status is already set, then we've already initialized the upgrade - if ( $upgrade_status = $this->get_upgrade_status() ) { - if ( self::STATUS_RUNNING === $upgrade_status ) { - // Make sure cron job is persisted in case it has dropped - $this->schedule(); - } - - return false; - } - - return true; - } - - /** - * Count items to process. - * - * @return int - */ - abstract protected function count_items_to_process(); - - /** - * Get items to process. - * - * @param string $prefix - * @param int $limit - * @param bool|mixed $offset - * - * @return array - */ - abstract protected function get_items_to_process( $prefix, $limit, $offset = false ); - - /** - * Upgrade attachment. - * - * @param mixed $attachment - * - * @return bool - */ - abstract protected function upgrade_item( $attachment ); - - /** - * Get running update text. - * - * @return string - */ - abstract protected function get_running_update_text(); - - /** - * Fire up the upgrade - */ - protected function init() { - // Initialize the upgrade - $this->save_session( array( 'status' => self::STATUS_RUNNING ) ); - - $this->schedule(); - } - - /** - * Cron job to update the region of the bucket in s3 metadata - */ - public function do_upgrade() { - $this->lock_upgrade(); - - // Check if the cron should even be running - if ( $this->get_saved_upgrade_id() >= $this->upgrade_id || $this->get_upgrade_status() !== self::STATUS_RUNNING ) { - $this->unschedule(); - - return; - } - - // set the batch size limit for the query - $limit = apply_filters( 'as3cf_update_' . $this->upgrade_name . '_batch_size', 500 ); - $all_limit = $limit; - $finish = time() + apply_filters( 'as3cf_update_' . $this->upgrade_name . '_time_limit', 20 ); - - $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(); - $this->error_count = isset( $session['error_count'] ) ? $session['error_count'] : 0; - $offset = isset( $session['offset'] ) ? $session['offset'] : false; - - // get the table prefixes for all the blogs - $table_prefixes = $this->as3cf->get_all_blog_table_prefixes( $processed_blog_ids ); - - $all_items = array(); - $all_count = 0; - - foreach ( $table_prefixes as $blog_id => $table_prefix ) { - $items = $this->get_items_to_process( $table_prefix, $limit, $offset ); - $count = count( $items ); - - if ( 0 === $count ) { - // No more items, record the blog ID to skip next time - $session['offset'] = false; - $processed_blog_ids[] = $blog_id; - } else { - $all_count += $count; - $all_items[ $blog_id ] = $items; - } - - if ( $all_count >= $all_limit ) { - break; - } - - $limit = $limit - $count; - } - - if ( 0 === $all_count ) { - $this->upgrade_finished(); - - return; - } - - // loop through and update s3 meta with region - foreach ( $all_items as $blog_id => $items ) { - $this->as3cf->switch_to_blog( $blog_id ); - - foreach ( $items as $item ) { - if ( $this->error_count >= $this->error_threshold ) { - $this->upgrade_error( $session ); - - return; - } - - // Do the actual upgrade to the item - $this->upgrade_item( $item ); - - if ( time() >= $finish || $this->as3cf->memory_exceeded( 'as3cf_update_' . $this->upgrade_name . '_memory_exceeded' ) ) { - // Batch limits reached - $this->as3cf->restore_current_blog(); - - break 2; - } - } - - $this->as3cf->restore_current_blog(); - } - - $session['offset'] = isset( $item ) ? $item : false; - $session['processed_blog_ids'] = $processed_blog_ids; - $session['error_count'] = $this->error_count; - - $this->save_session( $session ); - } - - /** - * Adds notices about issues with upgrades allowing user to restart them - */ - public 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'; - - switch ( $this->get_upgrade_status() ) { - case self::STATUS_RUNNING: - $msg = $this->get_running_message(); - $action_text = __( 'Pause Update', 'amazon-s3-and-cloudfront' ); - $action_url = $this->as3cf->get_plugin_page_url( array( - 'action' => 'pause_update', - 'update' => $this->upgrade_name, - ), 'self' ); - break; - case self::STATUS_PAUSED: - $msg = $this->get_paused_message(); - $action_text = __( 'Restart Update', 'amazon-s3-and-cloudfront' ); - break; - case self::STATUS_ERROR: - $msg = $this->get_error_message(); - $action_text = __( 'Try Run It Again', 'amazon-s3-and-cloudfront' ); - $msg_type = 'error'; - break; - default: - return; - } - - $msg .= ' ' . $action_text . ''; - - $args = array( - 'message' => $msg, - 'type' => $msg_type, - ); - - $this->as3cf->render_view( 'notice', $args ); - } - - /** - * Get running message. - * - * @return string - */ - protected function get_running_message() { - return sprintf( __( 'Running %1$s Update%2$s — We’re going through all the Media Library items uploaded to S3 %3$s This will be done quietly in the background, processing a small batch of Media Library items every %4$d minutes. There should be no noticeable impact on your server’s performance.', 'amazon-s3-and-cloudfront' ), - ucwords( $this->upgrade_type ), - $this->get_progress_text(), - $this->running_update_text, - $this->cron_interval_in_minutes - ); - } - - /** - * Get paused message. - * - * @return string - */ - protected function get_paused_message() { - return sprintf( __( '%1$s Update Paused%2$s — Updating Media Library %3$s has been paused.', 'amazon-s3-and-cloudfront' ), - ucwords( $this->upgrade_type ), - $this->get_progress_text(), - $this->upgrade_type - ); - } - - /** - * Get error message. - * - * @return string - */ - protected function get_error_message() { - return sprintf( __( 'Error Updating %1$s — We ran into some errors attempting to update the %2$s for all your Media Library items that have been uploaded to S3. Please check your error log for details. (#%3$d)', 'amazon-s3-and-cloudfront' ), - ucwords( $this->upgrade_type ), - $this->upgrade_type, - $this->upgrade_id - ); - } - - /** - * Get progress text. - * - * @return string - */ - protected function get_progress_text() { - $progress = $this->calculate_progress(); - - if ( false === $progress ) { - // Progress can not be calculated, return - return ''; - } - - if ( $progress > 100 ) { - $progress = 100; - } - - return sprintf( __( ' (%s%% Complete)', 'amazon-s3-and-cloudfront' ), $progress ); - } - - /** - * Calculate progress. - * - * @return bool|int|float - */ - protected function calculate_progress() { - return false; - } - - /** - * Handler for the running upgrade actions - */ - public function maybe_handle_action() { - if ( ! isset( $_GET['page'] ) || sanitize_key( $_GET['page'] ) !== $this->as3cf->get_plugin_slug() ) { // input var okay - return; - } - - if ( ! isset( $_GET['action'] ) ) { - return; - } - - if ( ! isset( $_GET['update'] ) || sanitize_key( $_GET['update'] ) !== $this->upgrade_name ) { // input var okay - return; - } - - $method_name = 'action_' . sanitize_key( $_GET['action'] ); // input var okay - - if ( method_exists( $this, $method_name ) ) { - call_user_func( array( $this, $method_name ) ); - } - } - - /** - * Exit upgrade with an error - * - * @param array $session - */ - protected function upgrade_error( $session ) { - $session['status'] = self::STATUS_ERROR; - $this->save_session( $session ); - $this->unschedule(); - } - - /** - * Complete the upgrade - */ - protected function upgrade_finished() { - $this->clear_session(); - $this->update_saved_upgrade_id(); - $this->unschedule(); - } - - /** - * Restart upgrade - */ - protected function action_restart_update() { - $this->schedule(); - $this->change_status_request( self::STATUS_RUNNING ); - } - - /** - * Pause upgrade - */ - protected function action_pause_update() { - $this->unschedule(); - $this->change_status_request( self::STATUS_PAUSED ); - } - - /** - * Helper for the above action requests - * - * @param int $status - */ - protected 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 ); - exit; - } - - /** - * Schedule the cron - */ - protected function schedule() { - $this->as3cf->schedule_event( $this->cron_hook, $this->cron_schedule_key ); - } - - /** - * Remove the cron schedule - */ - protected function unschedule() { - $this->as3cf->clear_scheduled_event( $this->cron_hook ); - } - - /** - * Add custom cron interval schedules - * - * @param array $schedules - * - * @return array - */ - public 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', 'amazon-s3-and-cloudfront' ), $this->cron_interval_in_minutes ), - ); - - return $schedules; - } - - /** - * Get the current status of the upgrade - * See STATUS_* constants in the class declaration above. - */ - protected function get_upgrade_status() { - $session = $this->get_session(); - - if ( ! isset( $session['status'] ) ) { - return ''; - } - - return $session['status']; - } - - /** - * Retrieve session data from plugin settings - * - * @return array - */ - protected function get_session() { - return get_site_option( 'update_' . $this->upgrade_name . '_session', array() ); - } - - /** - * Store data to be used between requests in plugin settings - * - * @param array $session session data to store - */ - protected function save_session( $session ) { - update_site_option( 'update_' . $this->upgrade_name . '_session', $session ); - } - - /** - * Remove the session data to be used between requests - * - */ - protected function clear_session() { - delete_site_option( 'update_' . $this->upgrade_name . '_session' ); - } - - /** - * Get the saved upgrade ID - * - * @return int|mixed|string|WP_Error - */ - protected function get_saved_upgrade_id() { - return $this->as3cf->get_setting( $this->settings_key, 0 ); - } - - /** - * Update the saved upgrade ID - */ - protected function update_saved_upgrade_id() { - $this->as3cf->set_setting( $this->settings_key, $this->upgrade_id ); - $this->as3cf->save_settings(); - } - - /** - * Has previous upgrade completed - * - * @return bool - */ - protected function has_previous_upgrade_completed() { - // Has the previous upgrade completed yet? - $previous_id = $this->upgrade_id - 1; - if ( 0 !== $previous_id && (int) $this->get_saved_upgrade_id() < $previous_id ) { - // Previous still running, abort - return false; - } - - return true; - } - - /** - * Lock upgrade. - */ - protected function lock_upgrade() { - set_site_transient( $this->lock_key, $this->upgrade_id, MINUTE_IN_SECONDS * 3 ); - } -} diff --git a/classes/as3cf-utils.php b/classes/as3cf-utils.php index 166f157a..5c536bc5 100644 --- a/classes/as3cf-utils.php +++ b/classes/as3cf-utils.php @@ -23,6 +23,16 @@ * */ class AS3CF_Utils { + /** + * Get post ID. + * + * @param null|int|WP_Post $post Optional. Post ID or post object. Defaults to current post. + * + * @return int + */ + public static function get_post_id( $post = null ) { + return (int) get_post_field( 'ID', $post ); + } /** * Checks if another version of WP Offload S3 (Lite) is active and deactivates it. @@ -94,5 +104,258 @@ public static function get_plugin_version_from_basename( $basename ) { return $plugin_data['Version']; } + + /** + * Trailing slash prefix string ensuring no leading slashes. + * + * @param $string + * + * @return string + */ + public static function trailingslash_prefix( $string ) { + return ltrim( trailingslashit( $string ), '/\\' ); + } + + /** + * Remove scheme from URL. + * + * @param string $url + * + * @return string + */ + public static function remove_scheme( $url ) { + return preg_replace( '/^(?:http|https):/', '', $url ); + } + + /** + * Remove size from filename (image[-100x100].jpeg). + * + * @param string $url + * @param bool $remove_extension + * + * @return string + */ + public static function remove_size_from_filename( $url, $remove_extension = false ) { + $url = preg_replace( '/^(\S+)-[0-9]{1,4}x[0-9]{1,4}(\.[a-zA-Z0-9\.]{2,})?/', '$1$2', $url ); + + if ( $remove_extension ) { + $ext = pathinfo( $url, PATHINFO_EXTENSION ); + $url = str_replace( ".$ext", '', $url ); + } + + return $url; + } + + /** + * Reduce the given URL down to the simplest version of itself. + * + * Useful for matching against the full version of the URL in a full-text search + * or saving as a key for dictionary type lookup. + * + * @param string $url + * + * @return string + */ + public static function reduce_url( $url ) { + $parts = self::parse_url( $url ); + $host = isset( $parts['host'] ) ? $parts['host'] : ''; + $port = isset( $parts['port'] ) ? ":{$parts['port']}" : ''; + $path = isset( $parts['path'] ) ? $parts['path'] : ''; + + return '//' . $host . $port . $path; + } + + /** + * Parses a URL into its components. Compatible with PHP < 5.4.7. + * + * @param $url string The URL to parse. + * + * @return array|false The parsed components or false on error. + */ + public static function parse_url( $url ) { + $url = trim( $url ); + $no_scheme = 0 === strpos( $url, '//' ); + + if ( $no_scheme ) { + $url = 'http:' . $url; + } + + $parts = parse_url( $url ); + + if ( $no_scheme ) { + unset( $parts['scheme'] ); + } + + return $parts; + } + + /** + * Is the string a URL? + * + * @param string $string + * + * @return bool + */ + public static function is_url( $string ) { + if ( preg_match( '@^(?:https?:)?\/\/[a-zA-Z0-9\-]{3,}@', $string ) ) { + return true; + } + + return false; + } + + /** + * Get file paths for all attachment versions. + * + * @param int $attachment_id + * @param bool $exists_locally + * @param array|bool $meta + * @param bool $include_backups + * + * @return array + */ + public static function get_attachment_file_paths( $attachment_id, $exists_locally = true, $meta = false, $include_backups = true ) { + $file_path = get_attached_file( $attachment_id, true ); + $paths = array( + 'original' => $file_path, + ); + + if ( ! $meta ) { + $meta = get_post_meta( $attachment_id, '_wp_attachment_metadata', true ); + } + + if ( is_wp_error( $meta ) ) { + return $paths; + } + + $file_name = wp_basename( $file_path ); + + // If file edited, current file name might be different. + if ( isset( $meta['file'] ) ) { + $paths['file'] = str_replace( $file_name, wp_basename( $meta['file'] ), $file_path ); + } + + // Thumb + if ( isset( $meta['thumb'] ) ) { + $paths['thumb'] = str_replace( $file_name, $meta['thumb'], $file_path ); + } + + // Sizes + if ( isset( $meta['sizes'] ) ) { + foreach ( $meta['sizes'] as $size => $file ) { + if ( isset( $file['file'] ) ) { + $paths[ $size ] = str_replace( $file_name, $file['file'], $file_path ); + } + } + } + + $backups = get_post_meta( $attachment_id, '_wp_attachment_backup_sizes', true ); + + // Backups + if ( $include_backups && is_array( $backups ) ) { + foreach ( $backups as $size => $file ) { + if ( isset( $file['file'] ) ) { + $paths[ $size ] = str_replace( $file_name, $file['file'], $file_path ); + } + } + } + + // Allow other processes to add files to be uploaded + $paths = apply_filters( 'as3cf_attachment_file_paths', $paths, $attachment_id, $meta ); + + // Remove duplicates + $paths = array_unique( $paths ); + + // Remove paths that don't exist + if ( $exists_locally ) { + foreach ( $paths as $key => $path ) { + if ( ! file_exists( $path ) ) { + unset( $paths[ $key ] ); + } + } + } + + return $paths; + } + + /** + * Get an attachment's edited file paths. + * + * @param int $attachment_id + * + * @return array + */ + public static function get_attachment_edited_file_paths( $attachment_id ) { + $paths = self::get_attachment_file_paths( $attachment_id, false ); + $paths = array_filter( $paths, function ( $path ) { + return preg_match( '/-e[0-9]{13}(?:-[0-9]{1,4}x[0-9]{1,4})?\./', wp_basename( $path ) ); + } ); + + return $paths; + } + + /** + * Get an attachment's edited S3 keys. + * + * @param int $attachment_id + * @param array $s3object + * + * @return array + */ + public static function get_attachment_edited_keys( $attachment_id, $s3object ) { + $prefix = trailingslashit( pathinfo( $s3object['key'], PATHINFO_DIRNAME ) ); + $paths = self::get_attachment_edited_file_paths( $attachment_id ); + $paths = array_map( function ( $path ) use ( $prefix ) { + return array( 'Key' => $prefix . wp_basename( $path ) ); + }, $paths ); + + return $paths; + } + + /** + * Get intermediate size from attachment filename. + * + * @param int $attachment_id + * @param string $filename + * + * @return string + */ + public static function get_intermediate_size_from_filename( $attachment_id, $filename ) { + $sizes = self::get_attachment_file_paths( $attachment_id, false ); + + foreach ( $sizes as $size => $file ) { + if ( wp_basename( $file ) === $filename ) { + return $size; + } + } + + return ''; + } + + /** + * Strip edited image suffix and extension from path. + * + * @param string $path + * + * @return string + */ + public static function strip_image_edit_suffix_and_extension( $path ) { + $parts = pathinfo( $path ); + $filename = preg_replace( '/-e[0-9]{13}/', '', $parts['filename'] ); + + return str_replace( $parts['basename'], $filename, $path ); + } + + /** + * Create a site link for given URL. + * + * @param string $url + * @param string $text + * + * @return string + */ + public static function dbrains_link( $url, $text ) { + return sprintf( '%s', esc_url( $url ), esc_html( $text ) ); + } } -} \ No newline at end of file +} diff --git a/classes/filters/as3cf-local-to-s3.php b/classes/filters/as3cf-local-to-s3.php index f4b29a4e..7a9587b4 100644 --- a/classes/filters/as3cf-local-to-s3.php +++ b/classes/filters/as3cf-local-to-s3.php @@ -22,8 +22,8 @@ protected function init() { add_filter( 'excerpt_edit_pre', array( $this, 'filter_post' ) ); add_filter( 'as3cf_filter_post_local_to_s3', array( $this, 'filter_post' ) ); // Widgets - add_filter( 'widget_text', array( $this, 'filter_widget' ) ); - add_filter( 'widget_form_callback', array( $this, 'filter_widget_form' ), 10, 2 ); + add_filter( 'widget_form_callback', array( $this, 'filter_widget_display' ), 10, 2 ); + add_filter( 'widget_display_callback', array( $this, 'filter_widget_display' ), 10, 2 ); } /** @@ -58,7 +58,7 @@ public function filter_wp_get_custom_css( $css, $stylesheet ) { public function filter_post_data( $post ) { global $pages; - $cache = $this->get_post_cache(); + $cache = $this->get_post_cache( $post->ID ); $to_cache = array(); if ( 1 === count( $pages ) && ! empty( $pages[0] ) ) { @@ -94,42 +94,15 @@ public function filter_content_pagination( $pages ) { } /** - * Filter widget. - * - * @param string $content - * - * @return string - */ - public function filter_widget( $content ) { - $cache = $this->get_option_cache(); - $to_cache = array(); - $content = $this->process_content( $content, $cache, $to_cache ); - - $this->maybe_update_option_cache( $to_cache ); - - return $content; - } - - /** - * Filter widget form. + * Filter widget display. * * @param array $instance * @param WP_Widget $class * - * @return string + * @return array */ - public function filter_widget_form( $instance, $class ) { - if ( ! is_a( $class, 'WP_Widget_Text' ) || empty( $instance ) ) { - return $instance; - } - - $cache = $this->get_option_cache(); - $to_cache = array(); - $instance['text'] = $this->process_content( $instance['text'], $cache, $to_cache ); - - $this->maybe_update_option_cache( $to_cache ); - - return $instance; + public function filter_widget_display( $instance, $class ) { + return $this->handle_widget( $instance, $class ); } /** @@ -142,7 +115,7 @@ public function filter_widget_form( $instance, $class ) { protected function url_needs_replacing( $url ) { $uploads = wp_upload_dir(); $base_url = $this->as3cf->maybe_fix_local_subsite_url( $uploads['baseurl'] ); - $base_url = $this->as3cf->remove_scheme( $base_url ); + $base_url = AS3CF_Utils::remove_scheme( $base_url ); if ( false !== strpos( $url, $base_url ) ) { // Local URL, perform replacement @@ -186,7 +159,7 @@ protected function get_base_url( $attachment_id ) { protected function get_attachment_id_from_url( $url ) { global $wpdb; - $full_url = $this->as3cf->remove_scheme( $this->as3cf->remove_size_from_filename( $url ) ); + $full_url = AS3CF_Utils::remove_scheme( AS3CF_Utils::remove_size_from_filename( $url ) ); if ( isset( $this->query_cache[ $full_url ] ) ) { // ID already cached, return @@ -194,7 +167,7 @@ protected function get_attachment_id_from_url( $url ) { } $upload_dir = wp_upload_dir(); - $base_url = $this->as3cf->remove_scheme( $upload_dir['baseurl'] ); + $base_url = AS3CF_Utils::remove_scheme( $upload_dir['baseurl'] ); $path = $this->as3cf->decode_filename_in_path( ltrim( str_replace( $base_url, '', $full_url ), '/' ) ); $sql = $wpdb->prepare( " @@ -238,13 +211,13 @@ protected function get_attachment_ids_from_urls( $urls ) { } $upload_dir = wp_upload_dir(); - $base_url = $this->as3cf->remove_scheme( $upload_dir['baseurl'] ); + $base_url = AS3CF_Utils::remove_scheme( $upload_dir['baseurl'] ); $paths = array(); $full_urls = array(); foreach ( $urls as $url ) { - $full_url = $this->as3cf->remove_scheme( $this->as3cf->remove_size_from_filename( $url ) ); + $full_url = AS3CF_Utils::remove_scheme( AS3CF_Utils::remove_size_from_filename( $url ) ); if ( isset( $this->query_cache[ $full_url ] ) ) { // ID already cached, use it. @@ -335,7 +308,7 @@ protected function post_process_content( $content ) { */ protected function pre_replace_content( $content ) { $uploads = wp_upload_dir(); - $base_url = $this->as3cf->remove_scheme( $uploads['baseurl'] ); + $base_url = AS3CF_Utils::remove_scheme( $uploads['baseurl'] ); return $this->remove_aws_query_strings( $content, $base_url ); } diff --git a/classes/filters/as3cf-s3-to-local.php b/classes/filters/as3cf-s3-to-local.php index c374b223..179d3dde 100644 --- a/classes/filters/as3cf-s3-to-local.php +++ b/classes/filters/as3cf-s3-to-local.php @@ -18,7 +18,7 @@ protected function init() { add_filter( 'excerpt_save_pre', array( $this, 'filter_post' ) ); add_filter( 'as3cf_filter_post_s3_to_local', array( $this, 'filter_post' ) ); // Widgets - add_filter( 'widget_update_callback', array( $this, 'filter_widget_update' ), 10, 4 ); + add_filter( 'widget_update_callback', array( $this, 'filter_widget_save' ), 10, 4 ); } /** @@ -36,7 +36,7 @@ public function filter_update_custom_css_data( $data, $args ) { } /** - * Filter widget update. + * Filter widget on save. * * @param array $instance * @param array $new_instance @@ -46,24 +46,8 @@ public function filter_update_custom_css_data( $data, $args ) { * @return array * */ - public function filter_widget_update( $instance, $new_instance, $old_instance, $class ) { - if ( ! is_a( $class, 'WP_Widget_Text' ) || empty( $instance ) ) { - return $instance; - } - - $cache = $this->get_option_cache(); - $to_cache = array(); - $instance['text'] = $this->process_content( $instance['text'], $cache, $to_cache ); - - // Editing Text Widget in Customizer throws an error if more than one option record is updated. - // Therefore cache updating has to wait until render or edit via Appearance menu. - if ( isset( $_POST['wp_customize'] ) && 'on' === $_POST['wp_customize'] ) { - return $instance; - } - - $this->maybe_update_option_cache( $to_cache ); - - return $instance; + public function filter_widget_save( $instance, $new_instance, $old_instance, $class ) { + return $this->handle_widget( $instance, $class ); } /** @@ -84,7 +68,7 @@ protected function should_filter_content() { */ protected function url_needs_replacing( $url ) { $uploads = wp_upload_dir(); - $base_url = $this->as3cf->remove_scheme( $uploads['baseurl'] ); + $base_url = AS3CF_Utils::remove_scheme( $uploads['baseurl'] ); if ( false !== strpos( $url, $base_url ) ) { // Local URL, no replacement needed @@ -128,14 +112,14 @@ protected function get_base_url( $attachment_id ) { protected function get_attachment_id_from_url( $url ) { global $wpdb; - $full_url = $this->as3cf->remove_size_from_filename( $url ); + $full_url = AS3CF_Utils::remove_size_from_filename( $url ); if ( isset( $this->query_cache[ $full_url ] ) ) { // ID already cached, return return $this->query_cache[ $full_url ]; } - $parts = parse_url( $full_url ); + $parts = AS3CF_Utils::parse_url( $full_url ); $path = $this->as3cf->decode_filename_in_path( ltrim( $parts['path'], '/' ) ); if ( false !== strpos( $path, '/' ) ) { diff --git a/classes/upgrades/exceptions/batch-limits-exceeded-exception.php b/classes/upgrades/exceptions/batch-limits-exceeded-exception.php new file mode 100644 index 00000000..f968a5bb --- /dev/null +++ b/classes/upgrades/exceptions/batch-limits-exceeded-exception.php @@ -0,0 +1,7 @@ +as3cf = $as3cf; + $this->version = $version; + + add_action( 'admin_init', array( $this, 'init' ) ); + } + + /** + * Init upgrade. + */ + public function init() { + if ( ! $this->maybe_upgrade() ) { + return; + } + + $this->do_upgrade(); + $this->save_version(); + } + + /** + * Maybe perform upgrade? + * + * @return bool + */ + protected function maybe_upgrade() { + $stored_version = $this->get_stored_version(); + + if ( version_compare( $stored_version, $this->version, '<' ) ) { + return true; + } + + return false; + } + + /** + * Get stored version. + * + * @return string + */ + protected function get_stored_version() { + static $version; + + if ( is_null( $version ) ) { + $version = get_site_option( $this->get_option_name(), '0.0' ); + } + + return $version; + } + + /** + * Get option name. + * + * @return string + */ + protected function get_option_name() { + return strtolower( get_class( $this->as3cf ) ) . '_version'; + } + + /** + * Save version to options table. + */ + protected function save_version() { + update_site_option( $this->get_option_name(), $this->version ); + } + + /** + * Perform upgrade logic. + */ + abstract protected function do_upgrade(); + +} \ No newline at end of file diff --git a/classes/upgrades/as3cf-filter-post-content.php b/classes/upgrades/upgrade-content-replace-urls.php similarity index 73% rename from classes/upgrades/as3cf-filter-post-content.php rename to classes/upgrades/upgrade-content-replace-urls.php index aa3546bc..bdac0709 100644 --- a/classes/upgrades/as3cf-filter-post-content.php +++ b/classes/upgrades/upgrade-content-replace-urls.php @@ -1,19 +1,16 @@ upgrade_theme_mods( $blog['prefix'] ); + protected function upgrade_blog() { + $this->upgrade_theme_mods(); + + return parent::upgrade_blog(); } /** * Upgrade theme mods. Ensures background and header images have local URLs saved to the database. - * - * @param string $prefix */ - protected function upgrade_theme_mods( $prefix ) { + protected function upgrade_theme_mods() { global $wpdb; - $mods = $wpdb->get_results( "SELECT * FROM `{$prefix}options` WHERE option_name LIKE 'theme_mods_%'" ); + $mods = $wpdb->get_results( "SELECT * FROM `{$wpdb->prefix}options` WHERE option_name LIKE 'theme_mods_%'" ); foreach ( $mods as $mod ) { $value = maybe_unserialize( $mod->option_value ); @@ -85,7 +82,7 @@ protected function upgrade_theme_mods( $prefix ) { $value = maybe_serialize( $value ); if ( $value !== $mod->option_value ) { - $wpdb->query( "UPDATE `{$prefix}options` SET option_value = '{$value}' WHERE option_id = '{$mod->option_id}'" ); + $wpdb->query( "UPDATE `{$wpdb->prefix}options` SET option_value = '{$value}' WHERE option_id = '{$mod->option_id}'" ); } } } diff --git a/classes/upgrades/as3cf-filter-edd.php b/classes/upgrades/upgrade-edd-replace-urls.php similarity index 73% rename from classes/upgrades/as3cf-filter-edd.php rename to classes/upgrades/upgrade-edd-replace-urls.php index 61a7d9bf..9e33630e 100644 --- a/classes/upgrades/as3cf-filter-edd.php +++ b/classes/upgrades/upgrade-edd-replace-urls.php @@ -1,19 +1,16 @@ as3cf->get_all_blog_table_prefixes(); - $count = 0; - - foreach ( $table_prefixes as $blog_id => $table_prefix ) { - $count += (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$table_prefix}postmeta` WHERE meta_key = 'edd_download_files'" ); - } - - return $count; - } - /** * Get items to process. * @@ -75,7 +54,9 @@ protected function get_items_to_process( $prefix, $limit, $offset = false ) { $sql .= " AND meta_id > {$offset->meta_id}"; } - $sql .= " LIMIT {$limit}"; + if ( $limit && $limit > 0 ) { + $sql .= sprintf( ' LIMIT %d', (int) $limit ); + } return $wpdb->get_results( $sql ); } @@ -107,8 +88,8 @@ protected function upgrade_item( $item ) { } update_post_meta( $item->post_id, 'edd_download_files', $attachments ); - + return true; } - + } \ No newline at end of file diff --git a/classes/upgrades/as3cf-file-sizes.php b/classes/upgrades/upgrade-file-sizes.php similarity index 71% rename from classes/upgrades/as3cf-file-sizes.php rename to classes/upgrades/upgrade-file-sizes.php index 3d2bea93..e9f5b5b5 100644 --- a/classes/upgrades/as3cf-file-sizes.php +++ b/classes/upgrades/upgrade-file-sizes.php @@ -9,20 +9,20 @@ * @since 0.9.3 */ -// Exit if accessed directly -if ( ! defined( 'ABSPATH' ) ) { - exit; -} +namespace DeliciousBrains\WP_Offload_S3\Upgrades; + +use AS3CF_Error; +use Exception; /** - * AS3CF_Upgrade_File_Sizes Class + * Upgrade_File_Sizes Class * * This class handles updating the file sizes in the meta data * for attachments that have been removed from the local server * * @since 0.9.3 */ -class AS3CF_Upgrade_File_Sizes extends AS3CF_Upgrade { +class Upgrade_File_Sizes extends Upgrade { /** * @var int @@ -72,11 +72,11 @@ protected function upgrade_item( $attachment ) { return false; } - $s3client = $this->as3cf->get_s3client( $region, true ); - $main_file = $s3object['key']; + $s3client = $this->as3cf->get_s3client( $region, true ); + $main_file = $s3object['key']; - $ext = pathinfo( $main_file, PATHINFO_EXTENSION ); - $prefix = trailingslashit( dirname( $s3object['key'] ) ); + $ext = pathinfo( $main_file, PATHINFO_EXTENSION ); + $prefix = trailingslashit( dirname( $s3object['key'] ) ); // Used to search S3 for all files related to an attachment $search_prefix = $prefix . wp_basename( $main_file, ".$ext" ); @@ -91,15 +91,15 @@ protected function upgrade_item( $attachment ) { $result = $s3client->ListObjects( $args ); } catch ( Exception $e ) { AS3CF_Error::log( 'Error listing objects of prefix ' . $search_prefix . ' for attachment ' . $attachment->ID . ' from S3: ' . $e->getMessage() ); - $this->error_count ++; + $this->error_count++; return false; } $file_size_total = 0; - $main_file_size = 0; + $main_file_size = 0; - foreach ( $result->get( 'Contents' ) as $object ) { + foreach ( (array) $result->get( 'Contents' ) as $object ) { if ( ! isset( $object['Size'] ) ) { continue; } @@ -117,7 +117,7 @@ protected function upgrade_item( $attachment ) { if ( 0 === $file_size_total ) { AS3CF_Error::log( 'Total file size for the attachment is 0: ' . $attachment->ID ); - $this->error_count ++; + $this->error_count++; return false; } @@ -134,26 +134,7 @@ protected function upgrade_item( $attachment ) { } /** - * Get a count of all attachments without region in their S3 metadata - * for the whole site - * - * @return int - */ - protected function count_items_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 + * Get all attachments removed from the server. * * @param string $prefix * @param int $limit @@ -162,7 +143,14 @@ protected function count_items_to_process() { * @return array */ protected function get_items_to_process( $prefix, $limit, $offset = false ) { - $attachments = $this->get_attachments_removed_from_server( $prefix, false, $limit ); + $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; + } + } return $attachments; } @@ -187,39 +175,10 @@ protected function get_s3_attachments( $prefix, $limit = null ) { WHERE pm1.`meta_key` = 'amazonS3_info' AND pm2.`post_id` is null"; - if ( ! is_null( $limit ) ) { - $sql .= ' LIMIT %d'; - - $sql = $wpdb->prepare( $sql, $limit ); + if ( $limit && $limit > 0 ) { + $sql .= sprintf( ' LIMIT %d', (int) $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 - */ - protected 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-filter-post-excerpt.php b/classes/upgrades/upgrade-filter-post-excerpt.php similarity index 82% rename from classes/upgrades/as3cf-filter-post-excerpt.php rename to classes/upgrades/upgrade-filter-post-excerpt.php index 07dce7a2..0983f79d 100644 --- a/classes/upgrades/as3cf-filter-post-excerpt.php +++ b/classes/upgrades/upgrade-filter-post-excerpt.php @@ -1,19 +1,16 @@ self::STATUS_RUNNING, - 'total_attachments' => 0, - 'processed_attachments' => 0, - 'blogs_processed' => false, - 'blogs' => array(), - ); - - foreach ( $this->as3cf->get_all_blog_table_prefixes() as $blog_id => $prefix ) { - $session['blogs'][ $blog_id ] = array( - 'prefix' => $prefix, - 'processed' => false, - 'total_attachments' => null, - 'last_attachment_id' => null, - 'highest_post_id' => null, - 'last_post_id' => null, - ); - } - - $this->save_session( $session ); - $this->schedule(); - } - - /** - * Count attachments to process. We don't care about the total at this stage - * so just loop over blogs until attachments exist on S3. - * - * @return int + * @var int The last post ID used for the bottom range of the item upgrade. */ - protected function count_items_to_process() { - $table_prefixes = $this->as3cf->get_all_blog_table_prefixes(); - - foreach ( $table_prefixes as $blog_id => $table_prefix ) { - if ( $this->as3cf->count_attachments( $table_prefix, true ) ) { - return 1; - } - } - - return 0; - } - - /** - * Cron job to update post content, ensuring no S3 URLs exist. - */ - public function do_upgrade() { - $this->lock_upgrade(); - - // Check if the cron should even be running - if ( $this->get_saved_upgrade_id() >= $this->upgrade_id || $this->get_upgrade_status() !== self::STATUS_RUNNING ) { - $this->unschedule(); - - return; - } - - $limit = apply_filters( 'as3cf_update_' . $this->upgrade_name . '_batch_size', 50 ); - $this->finish = time() + apply_filters( 'as3cf_update_' . $this->upgrade_name . '_time_limit', 10 ); - $this->session = $this->get_session(); - - if ( ! $this->maybe_process_blogs() ) { - // Blogs still to process but limits reached, return - $this->save_session( $this->session ); - - return; - } - - foreach ( $this->session['blogs'] as $blog_id => $blog ) { - $this->blog_id = $blog_id; - $this->as3cf->switch_to_blog( $blog_id ); - - if ( $this->batch_limit_reached() ) { - // Limits reached, end batch - break; - } - - if ( $blog['processed'] ) { - // Blog processed, move onto the next - continue; - } - - $offset = $this->session['blogs'][ $blog_id ]['last_attachment_id']; - $attachments = $this->get_items_to_process( $blog['prefix'], $limit, $offset ); - - if ( empty( $attachments ) ) { - // All attachments processed, maybe move onto next blog - $this->session['blogs'][ $blog_id ]['processed'] = true; - - if ( $this->all_blogs_processed() ) { - // All blogs processed, complete upgrade - $this->upgrade_finished(); - - return; - } - - continue; - } - - foreach ( $attachments as $attachment ) { - if ( $this->batch_limit_reached() ) { - // Limits reached, end batch - break 2; - } - - if ( $this->upgrade_item( $attachment ) ) { - $this->session['processed_attachments'] += 1; - $this->session['blogs'][ $blog_id ]['last_attachment_id'] = $attachment->ID; - $this->session['blogs'][ $blog_id ]['last_post_id'] = null; - } else { - // Limits reached while processing posts, end batch - break 2; - } - } - - $this->as3cf->restore_current_blog(); - } - - $this->maybe_finish_upgrade(); - } - - /** - * Maybe finish the upgrade process. - */ - protected function maybe_finish_upgrade() { - if ( $this->session['processed_attachments'] >= $this->session['total_attachments']) { - $this->upgrade_finished(); - - return; - } - - $this->save_session( $this->session ); - } - - /** - * Maybe process blogs. - * - * @return bool - */ - protected function maybe_process_blogs() { - if ( $this->session['blogs_processed'] ) { - // Blogs already processed, return - return true; - } - - foreach ( $this->session['blogs'] as $blog_id => $blog ) { - if ( $this->batch_limit_reached() ) { - // Limits reached, return - return false; - } - - if ( is_null( $blog['total_attachments'] ) ) { - if ( method_exists( $this, 'process_blog' ) ) { - $this->process_blog( $blog ); - } - - // Count total attachments - $count = $this->as3cf->count_attachments( $blog['prefix'], true ); - - // Update blog session data - $this->session['blogs'][ $blog_id ]['total_attachments'] = $count; - $this->session['total_attachments'] += $count; - } - - if ( is_null( $blog['highest_post_id'] ) ) { - // Retrieve highest post ID - $this->session['blogs'][ $blog_id ]['highest_post_id'] = $this->get_highest_post_id( $blog['prefix'] ); - } - } - - $this->session['blogs_processed'] = true; - - return true; - } + protected $last_post_id; /** * Get highest post ID. * - * @param string $prefix - * * @return int */ - protected function get_highest_post_id( $prefix ) { + protected function get_highest_post_id() { global $wpdb; - $sql = "SELECT ID FROM `{$prefix}posts` ORDER BY ID DESC LIMIT 1"; - - return (int) $wpdb->get_var( $sql ); - } - - /** - * All blogs processed. - * - * @return bool - */ - protected function all_blogs_processed() { - foreach ( $this->session['blogs'] as $blog ) { - if ( ! $blog['processed'] ) { - return false; - } - } - - return true; + return (int) $wpdb->get_var( "SELECT MAX(ID) FROM {$wpdb->prefix}posts" ); } /** @@ -268,33 +74,59 @@ protected function get_items_to_process( $prefix, $limit, $offset = false ) { $sql .= " AND posts.ID < '{$offset}'"; } - $sql .= " ORDER BY posts.ID DESC LIMIT {$limit}"; + $sql .= " ORDER BY posts.ID DESC"; + + if ( $limit && $limit > 0 ) { + $sql .= sprintf( ' LIMIT %d', (int) $limit ); + } return $wpdb->get_results( $sql ); } + /** + * Switch to the given blog, and update blog-specific state. + * + * @param $blog_id + */ + protected function switch_to_blog( $blog_id ) { + parent::switch_to_blog( $blog_id ); + $this->last_post_id = $this->load_last_post_id(); + } + + /** + * Mark the current blog upgrade as complete. + */ + protected function blog_upgrade_completed() { + parent::blog_upgrade_completed(); + $this->last_post_id = null; + } + + /** + * Prepare the session to be persisted. + */ + protected function close_session() { + parent::close_session(); + $this->session['last_post_id'] = $this->last_post_id; + } + /** * Upgrade attachment. * + * @throws Batch_Limits_Exceeded_Exception + * @throws Too_Many_Errors_Exception + * * @param mixed $attachment * * @return bool */ protected function upgrade_item( $attachment ) { $limit = apply_filters( 'as3cf_update_' . $this->upgrade_name . '_sql_limit', 100000 ); - $highest_post_id = $this->session['blogs'][ $this->blog_id ]['highest_post_id']; - $last_post_id = $this->session['blogs'][ $this->blog_id ]['last_post_id']; - $where_highest_id = is_null( $last_post_id ) ? $highest_post_id : $last_post_id; + $where_highest_id = $this->last_post_id; $where_lowest_id = max( $where_highest_id - $limit, 0 ); while ( true ) { $this->find_and_replace_attachment_urls( $attachment->ID, $where_lowest_id, $where_highest_id ); - if ( $this->batch_limit_reached() ) { - // Batch limit reached - break; - } - if ( $where_lowest_id <= 0 ) { // Batch completed return true; @@ -302,13 +134,25 @@ protected function upgrade_item( $attachment ) { $where_highest_id = $where_lowest_id; $where_lowest_id = max( $where_lowest_id - $limit, 0 ); + + $this->check_batch_limits(); } - $this->session['blogs'][ $this->blog_id ]['last_post_id'] = $where_lowest_id; + $this->last_post_id = $where_lowest_id; return false; } + /** + * Perform any actions necessary after the given item is completed. + * + * @param $item + */ + protected function item_upgrade_completed( $item ) { + parent::item_upgrade_completed( $item ); + $this->last_item = $item->ID; + } + /** * Find and replace embedded URLs for an attachment. * @@ -385,8 +229,8 @@ protected function get_find_and_replace_urls( $file_path, $old_url, $new_url, $m // Remove URL protocols foreach ( $url_pairs as $key => $url_pair ) { - $url_pairs[ $key ]['old_url'] = $this->as3cf->remove_scheme( $url_pair['old_url'] ); - $url_pairs[ $key ]['new_url'] = $this->as3cf->remove_scheme( $url_pair['new_url'] ); + $url_pairs[ $key ]['old_url'] = AS3CF_Utils::remove_scheme( $url_pair['old_url'] ); + $url_pairs[ $key ]['new_url'] = AS3CF_Utils::remove_scheme( $url_pair['new_url'] ); } return apply_filters( 'as3cf_update_' . $this->upgrade_name . '_url_pairs', $url_pairs, $file_path, $old_url, $new_url, $meta ); @@ -486,7 +330,7 @@ protected function generate_select_sql( $url_pairs, $where_lowest_id, $where_hig // Get unique URLs without size string and extension foreach ( $url_pairs as $url_pair ) { - $paths[] = $this->as3cf->remove_size_from_filename( $url_pair['old_url'], true ); + $paths[] = AS3CF_Utils::remove_size_from_filename( $url_pair['old_url'], true ); } $paths = array_unique( $paths ); @@ -551,42 +395,62 @@ protected function get_paused_message() { */ protected function get_generic_message() { $link_text = __( 'See our documentation', 'amazon-s3-and-cloudfront' ); - $link = $this->as3cf->dbrains_link( 'https://deliciousbrains.com/wp-offload-s3/doc/version-1-2-upgrade', $link_text ); + $url = $this->as3cf->dbrains_url( '/wp-offload-s3/doc/version-1-2-upgrade', array( + 'utm_campaign' => 'support+docs', + ) ); + $link = AS3CF_Utils::dbrains_link( $url, $link_text ); return sprintf( __( '%s for details on why we’re doing this, why it runs slowly, and how to make it run faster.', 'amazon-s3-and-cloudfront' ), $link ); } /** - * Calculate progress. + * Load the last blog ID from the session. * - * @return bool|int|float + * If the ID is found using the standard session key, use that. + * Otherwise if it is an older session, derive the ID from the blogs in the session. + * + * @return bool|int|mixed */ - protected function calculate_progress() { - $session = $this->get_session(); + protected function load_last_blog_id() { + if ( $blog_id = parent::load_last_blog_id() ) { + return $blog_id; + } + + $blog_ids = $this->load_processesed_blog_ids(); + + return end( $blog_ids ); + } - if ( ! isset( $session['total_attachments'] ) || ! isset( $session['processed_attachments'] ) ) { - // Session data not created, return - return false; + /** + * Get all of the processed blog IDs from the session. + * + * @return array + */ + protected function load_processesed_blog_ids() { + if ( $ids = parent::load_processesed_blog_ids() ) { + return $ids; } - if ( ! $session['blogs_processed'] || is_null( $session['total_attachments'] ) || is_null( $session['processed_attachments'] ) ) { - // Still processing blogs, return 0 - return 0; + if ( isset( $this->session['blogs'] ) && is_array( $this->session['blogs'] ) ) { + return array_keys( $this->session['blogs'] ); } - return round( $session['processed_attachments'] / $session['total_attachments'] * 100, 2 ); + return array(); } /** - * Batch limit reached. + * Populate the last post ID. * - * @return bool + * The last post ID is set from the session if set, + * otherwise it defaults to the highest post ID on the site. + * + * @return int Post ID. */ - protected function batch_limit_reached() { - if ( time() >= $this->finish || $this->as3cf->memory_exceeded( 'as3cf_update_' . $this->upgrade_name . '_memory_exceeded' ) ) { - return true; + protected function load_last_post_id() { + if ( isset( $this->session['last_post_id'] ) ) { + return (int) $this->session['last_post_id']; } - return false; + return $this->get_highest_post_id(); } } \ No newline at end of file diff --git a/classes/upgrades/as3cf-meta-wp-error.php b/classes/upgrades/upgrade-meta-wp-error.php similarity index 85% rename from classes/upgrades/as3cf-meta-wp-error.php rename to classes/upgrades/upgrade-meta-wp-error.php index 99e8a299..aae52909 100644 --- a/classes/upgrades/as3cf-meta-wp-error.php +++ b/classes/upgrades/upgrade-meta-wp-error.php @@ -9,13 +9,13 @@ * @since 0.9.5 */ -// Exit if accessed directly -if ( ! defined( 'ABSPATH' ) ) { - exit; -} +namespace DeliciousBrains\WP_Offload_S3\Upgrades; + +use AS3CF_Error; +use Exception; /** - * AS3CF_Upgrade_Meta_WP_Error Class + * Upgrade_Meta_WP_Error Class * * This class handles updating the _wp_attachment_metadata * for attachments that have been removed from the local server @@ -23,7 +23,7 @@ * * @since 0.9.5 */ -class AS3CF_Upgrade_Meta_WP_Error extends AS3CF_Upgrade { +class Upgrade_Meta_WP_Error extends Upgrade { /** * @var int @@ -95,22 +95,12 @@ protected function upgrade_item( $attachment ) { } /** - * Get a count of all attachments without region in their S3 metadata - * for the whole site + * Get a count of all attachments without region in their S3 metadata. * * @return int */ protected function count_items_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_with_error_metadata( $table_prefix, true ); - $all_count += $count; - } - - return $all_count; + return (int) $this->get_attachments_with_error_metadata( $this->blog_prefix, true ); } /** @@ -155,10 +145,8 @@ protected function get_attachments_with_error_metadata( $prefix, $count = false, $sql = "SELECT pm1.`post_id` as `ID`, pm1.`meta_value` AS 's3object'" . $sql; - if ( ! is_null( $limit ) ) { - $sql .= ' LIMIT %d'; - - $sql = $wpdb->prepare( $sql, $limit ); + if ( $limit && $limit > 0 ) { + $sql .= sprintf( ' LIMIT %d', (int) $limit ); } return $wpdb->get_results( $sql, OBJECT ); diff --git a/classes/upgrades/as3cf-region-meta.php b/classes/upgrades/upgrade-region-meta.php similarity index 85% rename from classes/upgrades/as3cf-region-meta.php rename to classes/upgrades/upgrade-region-meta.php index 048ed2e7..ba49e950 100644 --- a/classes/upgrades/as3cf-region-meta.php +++ b/classes/upgrades/upgrade-region-meta.php @@ -9,19 +9,18 @@ * @since 0.6.2 */ -// Exit if accessed directly -if ( ! defined( 'ABSPATH' ) ) { - exit; -} +namespace DeliciousBrains\WP_Offload_S3\Upgrades; + +use AS3CF_Error; /** - * AS3CF_Upgrade_Region_Meta Class + * Upgrade_Region_Meta Class * * This class handles updating the region of the attachment's bucket in the meta data * * @since 0.6.2 */ -class AS3CF_Upgrade_Region_Meta extends AS3CF_Upgrade { +class Upgrade_Region_Meta extends Upgrade { /** * @var int @@ -81,16 +80,7 @@ protected function upgrade_item( $attachment ) { * @return int */ protected function count_items_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; + return $this->count_attachments_without_region( $this->blog_prefix ); } /** @@ -110,6 +100,7 @@ protected function get_items_to_process( $prefix, $limit, $offset = false ) { /** * Get a count of attachments that don't have region in their S3 meta data for a blog + * * @param $prefix * * @return int @@ -144,10 +135,8 @@ protected function get_attachments_without_region_results( $prefix, $count = fal $sql = "SELECT `post_id` as `ID`, `meta_value` AS 's3object'" . $sql; - if ( ! is_null( $limit ) ) { - $sql .= ' LIMIT %d'; - - $sql = $wpdb->prepare( $sql, $limit ); + if ( $limit && $limit > 0 ) { + $sql .= sprintf( ' LIMIT %d', (int) $limit ); } return $wpdb->get_results( $sql, OBJECT ); diff --git a/classes/upgrades/upgrade.php b/classes/upgrades/upgrade.php new file mode 100644 index 00000000..49958814 --- /dev/null +++ b/classes/upgrades/upgrade.php @@ -0,0 +1,939 @@ +as3cf = $as3cf; + + $this->running_update_text = $this->get_running_update_text(); + $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', 2 ); + $this->error_threshold = apply_filters( 'as3cf_update_' . $this->upgrade_name . '_error_threshold', 20 ); + $this->max_items_processable = apply_filters( 'as3cf_update_' . $this->upgrade_name . '_batch_size', $this->size_limit ); + + add_filter( 'cron_schedules', array( $this, 'cron_schedules' ) ); + 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' ) ); + + // Do default checks if the upgrade can be started + if ( $this->maybe_init() ) { + $this->init(); + } + } + + /** + * Can we start the upgrade using default checks + * + * @return bool + */ + protected function maybe_init() { + if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { + return false; + } + + if ( ! $this->screen_can_init() ) { + return false; + } + + if ( ! $this->as3cf->is_plugin_setup() ) { + return false; + } + + if ( $this->is_completed() ) { + return false; + } + + if ( ! $this->has_previous_upgrade_completed() ) { + return false; + } + + if ( $this->is_locked() ) { + return false; + } + + // If the upgrade status is already set, then we've already initialized the upgrade + if ( $this->get_upgrade_status() ) { + if ( $this->is_running() ) { + // Make sure cron job is persisted in case it has dropped + $this->schedule(); + } + + return false; + } + + return true; + } + + /** + * Count items left to process for the current blog. + * + * @return int + */ + protected function count_items_to_process() { + return count( $this->get_items_to_process( $this->blog_prefix, false, $this->last_item ) ); + } + + /** + * Get items to process. + * + * @param string $prefix + * @param int $limit + * @param bool|mixed $offset + * + * @return array + */ + abstract protected function get_items_to_process( $prefix, $limit, $offset = false ); + + /** + * Upgrade attachment. + * + * @param mixed $attachment + * + * @return bool + */ + abstract protected function upgrade_item( $attachment ); + + /** + * Get running update text. + * + * @return string + */ + abstract protected function get_running_update_text(); + + /** + * Fire up the upgrade + */ + protected function init() { + // Initialize the upgrade + $this->save_session( array( 'status' => self::STATUS_RUNNING ) ); + + $this->schedule(); + } + + /** + * WP Cron callback to run the upgrade. + */ + public function do_upgrade() { + $this->lock_upgrade(); + $this->start_timer(); + + if ( $this->is_completed() || ! $this->is_running() ) { + $this->unschedule(); + + return; + } + + $this->boot_session(); + $this->run_upgrade(); + } + + /** + * Run or resume the main upgrade process. + */ + protected function run_upgrade() { + try { + $blog_id = $this->get_initial_blog_id(); + + do { + $this->switch_to_blog( $blog_id ); + $this->check_batch_limits(); + + if ( $this->upgrade_blog() ) { + $this->blog_upgrade_completed(); + } else { + + // If the blog didn't complete, break and try again next time before moving on. + break; + } + } while ( $blog_id = $this->next_blog_id() ); + + } catch ( No_More_Blogs_Exception $e ) { + /* + * The upgrade is complete when there are no more blogs left to finish. + */ + $this->upgrade_finished(); + + return; + } catch ( Too_Many_Errors_Exception $e ) { + $this->upgrade_error(); + + return; + } catch ( Batch_Limits_Exceeded_Exception $e ) { + + // Save the session and finish this round right away. + } + + $this->close_session(); + $this->save_session( $this->session ); + } + + /** + * Upgrade the current blog. + * + * @throws Too_Many_Errors_Exception + * @throws Batch_Limits_Exceeded_Exception + * + * @return bool true if all items for the blog were upgraded, otherwise false. + */ + protected function upgrade_blog() { + $total = $this->count_items_to_process(); + $items = $this->blog_batch_items(); + $upgraded = 0; + + foreach ( $items as $item ) { + if ( $this->upgrade_item( $item ) ) { + $this->item_upgrade_completed( $item ); + $upgraded++; + } + + // Items always count towards processing limits. + $this->items_processed++; + + $this->check_batch_limits(); + } + + /* + * If the number upgraded is the same as the remaining total to process + * then all items have been upgraded for this blog. + */ + if ( $upgraded === (int) $total ) { + return true; + } + + return false; + } + + /** + * Get the next sequential blog ID if there is one. + * + * @throws No_More_Blogs_Exception + * + * @return int + */ + protected function next_blog_id() { + $blog_id = $this->blog_id ?: $this->last_blog_id; + + do { + $blog_id--; + + if ( $blog_id < 1 ) { + throw new No_More_Blogs_Exception; + } + + } while ( ! $this->is_blog_processable( $blog_id ) ); + + return $blog_id; + } + + /** + * Get the maximum number of processable items for the current blog, + * limited by the remaining number of items possible to process for this request. + * + * @return array + */ + protected function blog_batch_items() { + $limit = $this->max_items_processable - $this->items_processed; + + return $this->get_items_to_process( $this->blog_prefix, $limit, $this->last_item ); + } + + /** + * Adds notices about issues with upgrades allowing user to restart them + */ + public 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'; + + switch ( $this->get_upgrade_status() ) { + case self::STATUS_RUNNING: + $msg = $this->get_running_message(); + $action_text = __( 'Pause Update', 'amazon-s3-and-cloudfront' ); + $action_url = $this->as3cf->get_plugin_page_url( array( + 'action' => 'pause_update', + 'update' => $this->upgrade_name, + ), 'self' ); + break; + case self::STATUS_PAUSED: + $msg = $this->get_paused_message(); + $action_text = __( 'Restart Update', 'amazon-s3-and-cloudfront' ); + break; + case self::STATUS_ERROR: + $msg = $this->get_error_message(); + $action_text = __( 'Try Run It Again', 'amazon-s3-and-cloudfront' ); + $msg_type = 'error'; + break; + default: + return; + } + + $msg .= ' ' . $action_text . ''; + + $args = array( + 'message' => $msg, + 'type' => $msg_type, + ); + + $this->as3cf->render_view( 'notice', $args ); + } + + /** + * Get running message. + * + * @return string + */ + protected function get_running_message() { + return sprintf( __( 'Running %1$s Update%2$s — We’re going through all the Media Library items uploaded to S3 %3$s This will be done quietly in the background, processing a small batch of Media Library items every %4$d minutes. There should be no noticeable impact on your server’s performance.', 'amazon-s3-and-cloudfront' ), + ucwords( $this->upgrade_type ), + $this->get_progress_text(), + $this->running_update_text, + $this->cron_interval_in_minutes + ); + } + + /** + * Get paused message. + * + * @return string + */ + protected function get_paused_message() { + return sprintf( __( '%1$s Update Paused%2$s — Updating Media Library %3$s has been paused.', 'amazon-s3-and-cloudfront' ), + ucwords( $this->upgrade_type ), + $this->get_progress_text(), + $this->upgrade_type + ); + } + + /** + * Get error message. + * + * @return string + */ + protected function get_error_message() { + return sprintf( __( 'Error Updating %1$s — We ran into some errors attempting to update the %2$s for all your Media Library items that have been uploaded to S3. Please check your error log for details. (#%3$d)', 'amazon-s3-and-cloudfront' ), + ucwords( $this->upgrade_type ), + $this->upgrade_type, + $this->upgrade_id + ); + } + + /** + * Get progress text. + * + * @return string + */ + protected function get_progress_text() { + $progress = $this->calculate_progress(); + + if ( false === $progress ) { + // Progress can not be calculated, return + return ''; + } + + if ( $progress > 100 ) { + $progress = 100; + } + + return sprintf( __( ' (%s%% Complete)', 'amazon-s3-and-cloudfront' ), $progress ); + } + + /** + * Calculate progress. + * + * @return bool|float + */ + protected function calculate_progress() { + $this->boot_session(); + + if ( is_multisite() ) { + $all_blog_ids = $this->as3cf->get_blog_ids(); + $decimal = count( $this->processed_blogs_ids ) / count( $all_blog_ids ); + } else { + // Set up any per-site state + $this->switch_to_blog( get_current_blog_id() ); + $total_items = $this->as3cf->count_attachments( $this->blog_prefix ); + + // If there are no attachments, disable progress calculation + // and protect against division by zero. + if ( ! $total_items ) { + return false; + } + + $remaining = $this->count_items_to_process(); + $decimal = ( $total_items - $remaining ) / $total_items; + } + + return round( $decimal * 100, 2 ); + } + + /** + * Handler for the running upgrade actions + */ + public function maybe_handle_action() { + if ( ! isset( $_GET['page'] ) || sanitize_key( $_GET['page'] ) !== $this->as3cf->get_plugin_slug() ) { // input var okay + return; + } + + if ( ! isset( $_GET['action'] ) ) { + return; + } + + if ( ! isset( $_GET['update'] ) || sanitize_key( $_GET['update'] ) !== $this->upgrade_name ) { // input var okay + return; + } + + $method_name = 'action_' . sanitize_key( $_GET['action'] ); // input var okay + + if ( method_exists( $this, $method_name ) ) { + call_user_func( array( $this, $method_name ) ); + } + } + + /** + * Exit upgrade with an error + */ + protected function upgrade_error() { + $this->close_session(); + $this->session['status'] = self::STATUS_ERROR; + $this->save_session( $this->session ); + $this->unschedule(); + } + + /** + * Complete the upgrade + */ + protected function upgrade_finished() { + $this->clear_session(); + $this->update_saved_upgrade_id(); + $this->unlock_upgrade(); + $this->unschedule(); + } + + /** + * Restart upgrade + */ + protected function action_restart_update() { + $this->schedule(); + $this->change_status_request( self::STATUS_RUNNING ); + } + + /** + * Pause upgrade + */ + protected function action_pause_update() { + $this->unschedule(); + + if ( $this->is_running() ) { + $this->change_status_request( self::STATUS_PAUSED ); + } + } + + /** + * Helper for the above action requests + * + * @param int $status + */ + protected 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 ); + exit; + } + + /** + * Schedule the cron + */ + protected function schedule() { + $this->as3cf->schedule_event( $this->cron_hook, $this->cron_schedule_key ); + } + + /** + * Remove the cron schedule + */ + protected function unschedule() { + $this->as3cf->clear_scheduled_event( $this->cron_hook ); + } + + /** + * Add custom cron interval schedules + * + * @param array $schedules + * + * @return array + */ + public 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', 'amazon-s3-and-cloudfront' ), $this->cron_interval_in_minutes ), + ); + + return $schedules; + } + + /** + * Get the current status of the upgrade + * See STATUS_* constants in the class declaration above. + */ + protected function get_upgrade_status() { + $session = $this->get_session(); + + if ( ! isset( $session['status'] ) ) { + return ''; + } + + return $session['status']; + } + + /** + * Retrieve session data from plugin settings + * + * @return array + */ + protected function get_session() { + return get_site_option( 'update_' . $this->upgrade_name . '_session', array() ); + } + + /** + * Store data to be used between requests in plugin settings + * + * @param array $session session data to store + */ + protected function save_session( $session ) { + update_site_option( 'update_' . $this->upgrade_name . '_session', $session ); + } + + /** + * Remove the session data to be used between requests + * + */ + protected function clear_session() { + delete_site_option( 'update_' . $this->upgrade_name . '_session' ); + } + + /** + * Get the saved upgrade ID + * + * @return int|mixed|string|WP_Error + */ + protected function get_saved_upgrade_id() { + return $this->as3cf->get_setting( $this->settings_key, 0 ); + } + + /** + * Update the saved upgrade ID + */ + protected function update_saved_upgrade_id() { + $this->as3cf->set_setting( $this->settings_key, $this->upgrade_id ); + $this->as3cf->save_settings(); + } + + /** + * Has previous upgrade completed + * + * @return bool + */ + protected function has_previous_upgrade_completed() { + // Has the previous upgrade completed yet? + $previous_id = $this->upgrade_id - 1; + if ( 0 !== $previous_id && (int) $this->get_saved_upgrade_id() < $previous_id ) { + // Previous still running, abort + return false; + } + + return true; + } + + /** + * Lock upgrade. + */ + protected function lock_upgrade() { + set_site_transient( $this->lock_key, $this->upgrade_id, MINUTE_IN_SECONDS * 3 ); + } + + /** + * Unlock the upgrade. + * + * Voids the lock after 1 second rather than deleting to avoid a race condition. + */ + protected function unlock_upgrade() { + set_site_transient( $this->lock_key, $this->upgrade_id, 1 ); + } + + /** + * Whether or not the upgrade lock has been set. + * + * @return bool + */ + protected function is_locked() { + return false !== get_site_transient( $this->lock_key ); + } + + /** + * Whether this upgrade has been completed or not. + * + * @return bool + */ + protected function is_completed() { + return $this->get_saved_upgrade_id() >= $this->upgrade_id; + } + + /** + * Whether this upgrade is currently running or not. + * + * @return bool + */ + protected function is_running() { + return self::STATUS_RUNNING === $this->get_upgrade_status(); + } + + /** + * Set the time when the upgrade must finish by. + */ + protected function start_timer() { + $this->finish = time() + apply_filters( 'as3cf_update_' . $this->upgrade_name . '_time_limit', $this->time_limit ); + } + + /** + * Check to see if batch limits have been exceeded. + * + * @throws Batch_Limits_Exceeded_Exception + * @throws Too_Many_Errors_Exception + */ + protected function check_batch_limits() { + if ( $this->error_count > $this->error_threshold ) { + throw new Too_Many_Errors_Exception; + } + + if ( $this->items_processed > $this->max_items_processable ) { + throw new Batch_Limits_Exceeded_Exception( 'Item limit reached.' ); + } + + if ( time() > $this->finish ) { + throw new Batch_Limits_Exceeded_Exception( 'Time limit exceeded.' ); + } + + if ( $this->as3cf->memory_exceeded( 'as3cf_update_' . $this->upgrade_name . '_memory_exceeded' ) ) { + throw new Batch_Limits_Exceeded_Exception( 'Memory limit exceeded with ' . memory_get_usage( true ) / 1024 / 1024 . 'MB' ); + } + } + + /** + * Check if a blog exists for the given blog ID. + * + * @param $blog_id + * + * @return bool + */ + protected function blog_exists( $blog_id ) { + static $all_ids; + + if ( function_exists( 'get_site' ) ) { + return (bool) get_site( $blog_id ); + } + + if ( ! $all_ids ) { + $all_ids = $this->as3cf->get_blog_ids(); + } + + return in_array( $blog_id, $all_ids ); + } + + /** + * Get the largest blog ID on the network. + * + * @return null|string + */ + protected function get_final_blog_id() { + global $wpdb; + + if ( is_multisite() ) { + return $wpdb->get_var( "SELECT MAX(blog_id) FROM {$wpdb->prefix}blogs" ); + } + + return 1; + } + + /** + * Get the initial blog ID to start iterating with. + * + * @return int + */ + protected function get_initial_blog_id() { + if ( $this->last_blog_id ) { + return $this->next_blog_id(); + } + + return (int) $this->get_final_blog_id(); + } + + /** + * Whether the given blog ID is processable or not. + * + * @param int $blog_id + * + * @return bool + */ + protected function is_blog_processable( $blog_id ) { + if ( in_array( $blog_id, $this->processed_blogs_ids ) ) { + return false; + } + + return $this->blog_exists( $blog_id ); + } + + /** + * Populate the session properties from the saved state. + */ + protected function boot_session() { + $this->session = $this->get_session(); + $this->last_blog_id = $this->load_last_blog_id(); + $this->processed_blogs_ids = $this->load_processesed_blog_ids(); + $this->error_count = isset( $this->session['error_count'] ) ? $this->session['error_count'] : 0; + $this->last_item = $this->load_last_item(); + } + + /** + * Get all of the processed blog IDs from the session. + * + * @return array + */ + protected function load_processesed_blog_ids() { + $session = $this->session ?: $this->get_session(); + + return isset( $session['processed_blog_ids'] ) ? $session['processed_blog_ids'] : array(); + } + + /** + * Mark the current blog upgrade as complete. + */ + protected function blog_upgrade_completed() { + $this->last_blog_id = $this->blog_id; + $this->processed_blogs_ids[] = $this->blog_id; + $this->last_item = false; + } + + /** + * Perform any actions necessary after the given item is completed. + * + * @param $item + */ + protected function item_upgrade_completed( $item ) { + $this->last_item = $item; + } + + /** + * Prepare the session to be persisted. + */ + protected function close_session() { + $this->session['last_blog_id'] = $this->last_blog_id; + $this->session['offset'] = $this->last_item; + $this->session['error_count'] = $this->error_count; + $this->session['processed_blog_ids'] = $this->processed_blogs_ids; + } + + /** + * Load the last completed blog ID from the session. + * + * @return bool|int + */ + protected function load_last_blog_id() { + if ( ! empty( $this->session['last_blog_id'] ) ) { + return (int) $this->session['last_blog_id']; + } + + return null; + } + + /** + * Switch to the given blog, and update blog-specific state. + * + * @param $blog_id + * + * @throws Batch_Limits_Exceeded_Exception + */ + protected function switch_to_blog( $blog_id ) { + $this->as3cf->switch_to_blog( $blog_id ); + $this->blog_id = (int) $blog_id; + $this->blog_prefix = $GLOBALS['wpdb']->prefix; + } + + /** + * Get the last processed item from the session. + * + * @return bool|mixed + */ + protected function load_last_item() { + return isset( $this->session['offset'] ) ? $this->session['offset'] : false; + } + + /** + * Whether or not the current screen can initialize the upgrade. + * + * @return bool + */ + protected function screen_can_init() { + if ( is_multisite() ) { + return is_network_admin(); + } + + return is_admin(); + } +} diff --git a/languages/amazon-s3-and-cloudfront-en.pot b/languages/amazon-s3-and-cloudfront-en.pot index 540f75a6..aa804d6f 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: 2017-03-13 13:16+0000\n" +"POT-Creation-Date: 2017-06-19 15:09+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,135 +17,137 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: classes/amazon-s3-and-cloudfront.php:115 +#: classes/amazon-s3-and-cloudfront.php:122 msgid "Offload S3 Lite" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:116 +#: classes/amazon-s3-and-cloudfront.php:123 msgid "S3 and CloudFront" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:224 +#: classes/amazon-s3-and-cloudfront.php:232 #: view/bucket-setting.php:18 msgid "defined in wp-config.php" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:886 +#: classes/amazon-s3-and-cloudfront.php:900 msgid "Upload aborted by filter 'as3cf_pre_upload_attachment'" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:897 +#: classes/amazon-s3-and-cloudfront.php:911 +#: classes/amazon-s3-and-cloudfront.php:1071 #, php-format msgid "File %s does not exist" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:908 +#: classes/amazon-s3-and-cloudfront.php:922 #, php-format msgid "Mime type %s is not allowed" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:993 +#: classes/amazon-s3-and-cloudfront.php:1007 +#: classes/amazon-s3-and-cloudfront.php:1079 #, php-format msgid "Error uploading %s to S3: %s" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:2191 +#: classes/amazon-s3-and-cloudfront.php:2223 msgid "Cheatin’ eh?" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:2195 +#: classes/amazon-s3-and-cloudfront.php:2227 msgid "You do not have sufficient permissions to access this page." msgstr "" -#: classes/amazon-s3-and-cloudfront.php:2201 +#: classes/amazon-s3-and-cloudfront.php:2233 msgid "No bucket name provided." msgstr "" -#: classes/amazon-s3-and-cloudfront.php:2465 +#: classes/amazon-s3-and-cloudfront.php:2497 msgid "Error Getting Bucket Region" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:2466 +#: classes/amazon-s3-and-cloudfront.php:2498 #, php-format msgid "There was an error attempting to get the region of the bucket %s: %s" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:2585 +#: classes/amazon-s3-and-cloudfront.php:2626 msgid "" "This is a test file to check if the user has write permission to S3. Delete " "me if found." msgstr "" -#: classes/amazon-s3-and-cloudfront.php:2617 +#: classes/amazon-s3-and-cloudfront.php:2645 #, php-format msgid "" "There was an error attempting to check the permissions of the bucket %s: %s" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:2674 +#: classes/amazon-s3-and-cloudfront.php:2699 msgid "Error creating bucket" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:2675 +#: classes/amazon-s3-and-cloudfront.php:2700 msgid "Bucket name too short." msgstr "" -#: classes/amazon-s3-and-cloudfront.php:2676 +#: classes/amazon-s3-and-cloudfront.php:2701 msgid "Bucket name too long." msgstr "" -#: classes/amazon-s3-and-cloudfront.php:2677 +#: classes/amazon-s3-and-cloudfront.php:2702 msgid "" "Invalid character. Bucket names can contain lowercase letters, numbers, " "periods and hyphens." msgstr "" -#: classes/amazon-s3-and-cloudfront.php:2678 +#: classes/amazon-s3-and-cloudfront.php:2703 msgid "Error saving bucket" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:2679 +#: classes/amazon-s3-and-cloudfront.php:2704 msgid "Error fetching buckets" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:2680 +#: classes/amazon-s3-and-cloudfront.php:2705 msgid "Error getting URL preview: " msgstr "" -#: classes/amazon-s3-and-cloudfront.php:2681 +#: classes/amazon-s3-and-cloudfront.php:2706 msgid "The changes you made will be lost if you navigate away from this page" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:2682 +#: classes/amazon-s3-and-cloudfront.php:2707 msgid "Getting diagnostic info..." msgstr "" -#: classes/amazon-s3-and-cloudfront.php:2683 +#: classes/amazon-s3-and-cloudfront.php:2708 msgid "Error getting diagnostic info: " msgstr "" -#: classes/amazon-s3-and-cloudfront.php:2751 +#: classes/amazon-s3-and-cloudfront.php:2776 msgid "Cheatin' eh?" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:2858 +#: classes/amazon-s3-and-cloudfront.php:2883 msgctxt "Show the media library tab" msgid "Media Library" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:2859 +#: classes/amazon-s3-and-cloudfront.php:2884 msgctxt "Show the support tab" msgid "Support" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:3076 +#: classes/amazon-s3-and-cloudfront.php:3101 #, php-format msgid "" "WP Offload S3 — The file %s has been given %s " "permissions on Amazon S3." msgstr "" -#: classes/amazon-s3-and-cloudfront.php:3095 +#: classes/amazon-s3-and-cloudfront.php:3120 msgid "" "WP Offload S3 Requirement Missing — Looks like you " "don't have an image manipulation library installed on this server and " @@ -153,11 +155,11 @@ msgid "" "Please setup GD or ImageMagick." msgstr "" -#: classes/amazon-s3-and-cloudfront.php:3774 +#: classes/amazon-s3-and-cloudfront.php:3729 msgid "Quick Start Guide" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:3776 +#: classes/amazon-s3-and-cloudfront.php:3731 #, php-format msgid "" "Looks like we don't have write access to this bucket. It's likely that the " @@ -166,7 +168,7 @@ msgid "" "correctly." msgstr "" -#: classes/amazon-s3-and-cloudfront.php:3778 +#: classes/amazon-s3-and-cloudfront.php:3733 #, php-format msgid "" "Looks like we don't have access to the buckets. It's likely that the user " @@ -174,39 +176,39 @@ msgid "" "Please see our %s for instructions on setting up permissions correctly." msgstr "" -#: classes/amazon-s3-and-cloudfront.php:3929 +#: classes/amazon-s3-and-cloudfront.php:3883 msgid "WP Offload S3 Activation" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:3930 +#: classes/amazon-s3-and-cloudfront.php:3884 msgid "" "WP Offload S3 Lite and WP Offload S3 cannot both be active. We've " "automatically deactivated WP Offload S3 Lite." msgstr "" -#: classes/amazon-s3-and-cloudfront.php:3932 +#: classes/amazon-s3-and-cloudfront.php:3886 msgid "WP Offload S3 Lite Activation" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:3933 +#: classes/amazon-s3-and-cloudfront.php:3887 msgid "" "WP Offload S3 Lite and WP Offload S3 cannot both be active. We've " "automatically deactivated WP Offload S3." msgstr "" -#: classes/amazon-s3-and-cloudfront.php:4007 +#: classes/amazon-s3-and-cloudfront.php:3939 msgid "More info" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:4098 +#: classes/amazon-s3-and-cloudfront.php:4034 msgid "this doc" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:4100 +#: classes/amazon-s3-and-cloudfront.php:4036 msgid "WP Offload S3 Feature Removed" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:4101 +#: classes/amazon-s3-and-cloudfront.php:4037 #, php-format msgid "" "You had the \"Always non-SSL\" option selected in your settings, but we've " @@ -217,115 +219,131 @@ msgid "" "to the old behavior." msgstr "" -#: classes/amazon-s3-and-cloudfront.php:4131 -#: classes/amazon-s3-and-cloudfront.php:4229 +#: classes/amazon-s3-and-cloudfront.php:4067 +#: classes/amazon-s3-and-cloudfront.php:4165 msgid "Amazon S3" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:4230 +#: classes/amazon-s3-and-cloudfront.php:4166 msgctxt "Amazon S3 bucket" msgid "Bucket" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:4231 +#: classes/amazon-s3-and-cloudfront.php:4167 msgctxt "Path to file on Amazon S3" msgid "Path" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:4232 +#: classes/amazon-s3-and-cloudfront.php:4168 msgctxt "Location of Amazon S3 bucket" msgid "Region" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:4233 +#: classes/amazon-s3-and-cloudfront.php:4169 msgctxt "Access control list of the file on Amazon S3" msgid "Access" msgstr "" -#: classes/amazon-s3-and-cloudfront.php:4234 +#: classes/amazon-s3-and-cloudfront.php:4170 msgid "URL" msgstr "" -#: classes/as3cf-notices.php:438 +#: classes/as3cf-notices.php:431 msgid "Error dismissing notice." msgstr "" -#: classes/as3cf-notices.php:453 +#: classes/as3cf-notices.php:446 msgid "Invalid notice ID." msgstr "" -#: classes/as3cf-plugin-compatibility.php:193 -msgid "WP Offload S3 Compatibility Addons" +#: classes/as3cf-plugin-compatibility.php:512 +#, php-format +msgid "The local directory %s does not exist and could not be created." msgstr "" -#: classes/as3cf-plugin-compatibility.php:195 -msgid "compatibility addons" +#: classes/as3cf-plugin-compatibility.php:513 +#: classes/as3cf-plugin-compatibility.php:525 +#: classes/upgrades/upgrade-meta-wp-error.php:81 +#, php-format +msgid "There was an error attempting to download the file %s from S3: %s" msgstr "" -#: classes/as3cf-plugin-compatibility.php:196 -#, php-format -msgid "" -"To get WP Offload S3 to work with certain 3rd party plugins, you might need " -"to install and activate some of our %s. We've detected the following addons " -"might need to be installed. Please click the links for more information " -"about each addon to determine if you need it or not." +#: classes/upgrades/upgrade-content-replace-urls.php:36 +msgid "and ensuring that only the local URL exists in post content." msgstr "" -#: classes/as3cf-plugin-compatibility.php:202 +#: classes/upgrades/upgrade-content-replace-urls.php:45 #, php-format msgid "" -"You will need to purchase a license to get access to these addons. If you're " -"having trouble determining whether or not you need the addons, send an email " -"to %s." +"Running Content Upgrade%1$s
    A find & replace is " +"running in the background to update URLs in your post content. %2$s" msgstr "" -#: classes/as3cf-plugin-compatibility.php:203 -msgid "View Licenses" +#: classes/upgrades/upgrade-edd-replace-urls.php:36 +msgid "and ensuring that only the local URL exists in EDD post meta." msgstr "" -#: classes/as3cf-plugin-compatibility.php:669 -#, php-format -msgid "The local directory %s does not exist and could not be created." +#: classes/upgrades/upgrade-file-sizes.php:48 +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/as3cf-plugin-compatibility.php:670 -#: classes/as3cf-plugin-compatibility.php:682 -#: classes/upgrades/as3cf-meta-wp-error.php:81 +#: classes/upgrades/upgrade-filter-post-excerpt.php:36 +msgid "and ensuring that only the local URL exists in post excerpts." +msgstr "" + +#: classes/upgrades/upgrade-filter-post-excerpt.php:45 #, php-format -msgid "There was an error attempting to download the file %s from S3: %s" +msgid "" +"Running Excerpts Upgrade%1$s
    A find & replace is " +"running in the background to update URLs in your post excerpts. %2$s" msgstr "" -#: classes/as3cf-upgrade-filter-post.php:544 +#: classes/upgrades/upgrade-filter-post.php:388 #, php-format msgid "" "Paused Upgrade
    The find & replace to update URLs has " "been paused. %s" msgstr "" -#: classes/as3cf-upgrade-filter-post.php:553 +#: classes/upgrades/upgrade-filter-post.php:397 msgid "See our documentation" msgstr "" -#: classes/as3cf-upgrade-filter-post.php:556 +#: classes/upgrades/upgrade-filter-post.php:403 #, php-format msgid "" "%s for details on why we’re doing this, why it runs slowly, and how to " "make it run faster." msgstr "" -#: classes/as3cf-upgrade.php:318 +#: classes/upgrades/upgrade-meta-wp-error.php:49 +msgid "" +"and rebuilding the metadata for attachments that may have been corrupted." +msgstr "" + +#: classes/upgrades/upgrade-region-meta.php:46 +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/upgrades/upgrade.php:405 msgid "Pause Update" msgstr "" -#: classes/as3cf-upgrade.php:326 +#: classes/upgrades/upgrade.php:413 msgid "Restart Update" msgstr "" -#: classes/as3cf-upgrade.php:330 +#: classes/upgrades/upgrade.php:417 msgid "Try Run It Again" msgstr "" -#: classes/as3cf-upgrade.php:353 +#: classes/upgrades/upgrade.php:440 #, php-format msgid "" "Running %1$s Update%2$s — We’re going through " @@ -335,14 +353,14 @@ msgid "" "performance." msgstr "" -#: classes/as3cf-upgrade.php:367 +#: classes/upgrades/upgrade.php:454 #, php-format msgid "" "%1$s Update Paused%2$s — Updating Media Library %3$s " "has been paused." msgstr "" -#: classes/as3cf-upgrade.php:380 +#: classes/upgrades/upgrade.php:467 #, php-format msgid "" "Error Updating %1$s — We ran into some errors " @@ -350,61 +368,16 @@ msgid "" "been uploaded to S3. Please check your error log for details. (#%3$d)" msgstr "" -#: classes/as3cf-upgrade.php:404 +#: classes/upgrades/upgrade.php:491 #, php-format msgid " (%s%% Complete)" msgstr "" -#: classes/as3cf-upgrade.php:515 +#: classes/upgrades/upgrade.php:625 #, php-format msgid "Every %d Minutes" msgstr "" -#: classes/upgrades/as3cf-file-sizes.php:48 -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-filter-edd.php:39 -msgid "and ensuring that only the local URL exists in EDD post meta." -msgstr "" - -#: classes/upgrades/as3cf-filter-post-content.php:39 -msgid "and ensuring that only the local URL exists in post content." -msgstr "" - -#: classes/upgrades/as3cf-filter-post-content.php:48 -#, php-format -msgid "" -"Running Content Upgrade%1$s
    A find & replace is " -"running in the background to update URLs in your post content. %2$s" -msgstr "" - -#: classes/upgrades/as3cf-filter-post-excerpt.php:39 -msgid "and ensuring that only the local URL exists in post excerpts." -msgstr "" - -#: classes/upgrades/as3cf-filter-post-excerpt.php:48 -#, php-format -msgid "" -"Running Excerpts Upgrade%1$s
    A find & replace is " -"running in the background to update URLs in your post excerpts. %2$s" -msgstr "" - -#: classes/upgrades/as3cf-meta-wp-error.php:49 -msgid "" -"and rebuilding the metadata for attachments that may have been corrupted." -msgstr "" - -#: classes/upgrades/as3cf-region-meta.php:47 -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:323 msgid "deactivate" msgstr "" @@ -582,6 +555,12 @@ msgstr "" msgid "Change" msgstr "" +#: view/bucket-setting.php:35 +msgid "" +"Bucket Select Disabled — Bucket selection has been " +"disabled while files are copied between buckets." +msgstr "" + #: view/cloudfront-setting.php:7 #: view/deprecated-domain-setting.php:49 msgid "Invalid character. Letters, numbers, periods and hyphens are allowed." @@ -737,62 +716,62 @@ msgstr "" msgid "Save Changes" msgstr "" -#: view/sidebar.php:10 +#: view/sidebar.php:13 msgid "Upload existing Media Library to S3" msgstr "" -#: view/sidebar.php:11 +#: view/sidebar.php:14 msgid "Manage S3 files in WordPress" msgstr "" -#: view/sidebar.php:12 +#: view/sidebar.php:15 msgid "Assets addon - Serve your CSS & JS from S3/CloudFront" msgstr "" -#: view/sidebar.php:13 +#: view/sidebar.php:16 msgid "WooCommerce addon" msgstr "" -#: view/sidebar.php:14 +#: view/sidebar.php:17 msgid "Easy Digital Downloads addon" msgstr "" -#: view/sidebar.php:15 +#: view/sidebar.php:18 msgid "PriorityExpert™ email support" msgstr "" -#: view/sidebar.php:18 +#: view/sidebar.php:25 msgid "Visit deliciousbrains.com →" msgstr "" -#: view/sidebar.php:25 +#: view/sidebar.php:33 msgid "Get 20% Off!" msgstr "" -#: view/sidebar.php:28 +#: view/sidebar.php:36 #, php-format msgid "" "Submit your name and email and we’ll send you a coupon for 20% off your " "upgrade." msgstr "" -#: view/sidebar.php:32 +#: view/sidebar.php:40 msgid "Your Email" msgstr "" -#: view/sidebar.php:36 +#: view/sidebar.php:44 msgid "First Name" msgstr "" -#: view/sidebar.php:40 +#: view/sidebar.php:48 msgid "Last Name" msgstr "" -#: view/sidebar.php:47 +#: view/sidebar.php:55 msgid "Send me the coupon" msgstr "" -#: view/sidebar.php:51 +#: view/sidebar.php:59 msgid "" "We promise we will not use your email for anything else and you can " "unsubscribe with 1-click anytime." @@ -810,14 +789,14 @@ msgid "" "to a few weeks and will likely be from a non-developer." msgstr "" -#: view/wordpress-org-support.php:6 +#: view/wordpress-org-support.php:10 #, php-format msgid "" "If you want a timely response via email from a developer " "who works on this plugin, upgrade and send us an email." msgstr "" -#: view/wordpress-org-support.php:8 +#: view/wordpress-org-support.php:12 #, php-format msgid "" "If you've found a bug, please submit an issue on GitHub." diff --git a/readme.txt b/readme.txt index 7c482d81..7e363d80 100644 --- a/readme.txt +++ b/readme.txt @@ -1,9 +1,9 @@ === WP Offload S3 Lite === Contributors: bradt, deliciousbrains Tags: uploads, amazon, s3, amazon s3, mirror, admin, media, cdn, cloudfront -Requires at least: 4.4 -Tested up to: 4.7.3 -Stable tag: 1.1.6 +Requires at least: 4.6 +Tested up to: 4.8 +Stable tag: 1.2 License: GPLv3 Copies files to Amazon S3 as they are uploaded to the Media Library. Optionally configure Amazon CloudFront for faster delivery. @@ -22,12 +22,12 @@ If you're adding this plugin to a site that's been around for a while, your exis * Upload existing Media Library to Amazon S3 * Control Amazon 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=os3-free-plugin#assets-addon) - Serve your CSS & JS from Amazon 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) +* [Assets addon](https://deliciousbrains.com/wp-offload-s3/?utm_campaign=WP%2BOffload%2BS3&utm_source=wordpress.org&utm_medium=free%2Bplugin%2Blisting&utm_content=assets%2Baddon#addons) - Serve your CSS & JS from Amazon S3/CloudFront +* [WooCommerce addon](https://deliciousbrains.com/wp-offload-s3/?utm_campaign=WP%2BOffload%2BS3&utm_source=wordpress.org&utm_medium=free%2Bplugin%2Blisting&utm_content=woocommerce%2Baddon#addons) +* [Easy Digital Downloads addon](https://deliciousbrains.com/wp-offload-s3/?utm_campaign=WP%2BOffload%2BS3&utm_source=wordpress.org&utm_medium=free%2Bplugin%2Blisting&utm_content=edd%2Baddon#addons) * PriorityExpert™ email support -[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) +[Compare pro vs free →](https://deliciousbrains.com/wp-offload-s3/upgrade/?utm_campaign=WP%2BOffload%2BS3&utm_source=wordpress.org&utm_medium=free%2Bplugin%2Blisting) The video below runs through the pro upgrade features... @@ -48,7 +48,7 @@ which is a fork of [Amazon S3 for WordPress](http://wordpress.org/extend/plugins = What are the minimum requirements? = -You can see the minimum requirements [here](https://deliciousbrains.com/wp-offload-s3/pricing/?utm_source=wordpress.org&utm_medium=web&utm_content=desc&utm_campaign=os3-free-plugin#requirements). +You can see the minimum requirements [here](https://deliciousbrains.com/wp-offload-s3/pricing/?utm_campaign=WP%2BOffload%2BS3&utm_source=wordpress.org&utm_medium=free%2Bplugin%2Blisting&utm_content=requirements#requirements). == Screenshots == @@ -65,6 +65,17 @@ This version requires PHP 5.3.3+ and the Amazon Web Services plugin == Changelog == += WP Offload S3 Lite 1.2 - 2017-06-19 = +* New: Compatibility with WordPress 4.8 +* New: Support for WP CLI `wp media regenerate` +* Improvement: Intermediate image sizes are now passed through the `as3cf_object_meta` filter +* Improvement: Content filtering cache now uses the external object when available +* Bug fix: Timeouts on large multisite installs due to excessive database queries on upgrade routines +* Bug fix: Video files with private ACL not working with WordPress's default media player +* Bug fix: Bucket permissions check not using configured path +* Bug fix: WordPress image editor sometimes shows a 404 when 'Remove Files From Server' enabled +* Bug fix: Notice: Undefined index: region + = WP Offload S3 Lite 1.1.6 - 2017-03-13 = * New: Compatibility with [Advanced Custom Fields](https://wordpress.org/plugins/advanced-custom-fields/) * New: `as3cf_filter_post_local_to_s3` and `as3cf_filter_post_s3_to_local` filters added for filtering S3 URLs in custom content @@ -114,7 +125,7 @@ This version requires PHP 5.3.3+ and the Amazon Web Services plugin = WP Offload S3 Lite 1.0.5 - 2016-09-01 = * New: Compatibility with WordPress 4.6 -* Improvement: No longer delete plugin data on uninstall. Manual removal possible, as per this [doc](https://deliciousbrains.com/wp-offload-s3/doc/uninstall/) +* Improvement: No longer delete plugin data on uninstall. Manual removal possible, as per this [doc](https://deliciousbrains.com/wp-offload-s3/doc/uninstall/?utm_campaign=changelogs&utm_source=wordpress.org&utm_medium=free%2Bplugin%2Blisting) = WP Offload S3 Lite 1.0.4 - 2016-05-30 = * New: Now using simpler Force HTTPS setting, removed redundant Always Use HTTP setting @@ -220,8 +231,8 @@ This version requires PHP 5.3.3+ and the Amazon Web Services plugin * Bug fix: Accidentally released the sidebar for after we launch the pro version = WP Offload S3 0.9.1 - 2015-07-29 = -* Improvement: Access denied sample IAM policy replaced with link to [Quick Start Guide](https://deliciousbrains.com/wp-offload-s3/doc/quick-start-guide/) -* Improvement: Access denied messages on bucket selection or bucket creation now link to [Quick Start Guide](https://deliciousbrains.com/wp-offload-s3/doc/quick-start-guide/) +* Improvement: Access denied sample IAM policy replaced with link to [Quick Start Guide](https://deliciousbrains.com/wp-offload-s3/doc/quick-start-guide/?utm_campaign=changelogs&utm_source=wordpress.org&utm_medium=free%2Bplugin%2Blisting) +* Improvement: Access denied messages on bucket selection or bucket creation now link to [Quick Start Guide](https://deliciousbrains.com/wp-offload-s3/doc/quick-start-guide/?utm_campaign=changelogs&utm_source=wordpress.org&utm_medium=free%2Bplugin%2Blisting) * Improvement: Object expires time can now be filtered using the `as3cf_object_meta` filter * Bug fix: Error not always shown when S3 bucket inaccessible due to incorrect permissions * Bug fix: Permission checks fail when S3 bucket is in a non-default region and defined by `AS3CF_BUCKET` constant diff --git a/view/bucket-setting.php b/view/bucket-setting.php index 4a2f821a..dfaeb11a 100644 --- a/view/bucket-setting.php +++ b/view/bucket-setting.php @@ -30,6 +30,14 @@ if ( isset( $after_bucket_content ) ) { echo $after_bucket_content; } - ?> + + $lock_bucket_args = array( + 'message' => __( 'Bucket Select Disabled — Bucket selection has been disabled while files are copied between buckets.', 'amazon-s3-and-cloudfront' ), + 'id' => 'as3cf-bucket-select-locked', + 'inline' => true, + 'type' => 'notice-warning', + 'style' => 'display: none', + ); + $this->render_view( 'notice', $lock_bucket_args ); ?> diff --git a/view/domain-setting.php b/view/domain-setting.php index 36ba7c46..eb23f1d7 100644 --- a/view/domain-setting.php +++ b/view/domain-setting.php @@ -19,7 +19,7 @@

    - settings_more_info_link( 'domain' ); ?> + settings_more_info_link( 'domain', 'media+cloudfront+or+custom+domain' ); ?>

    get_setting_args( 'cloudfront' ); diff --git a/view/settings-tabs.php b/view/settings-tabs.php index f48f89ea..eb39b9aa 100644 --- a/view/settings-tabs.php +++ b/view/settings-tabs.php @@ -1,8 +1,7 @@

    not copied to S3.', 'amazon-s3-and-cloudfront' ); ?> - settings_more_info_link( 'copy-to-s3' ); ?> + settings_more_info_link( 'copy-to-s3', 'media+copy+files+to+S3' ); ?>

    @@ -71,7 +71,7 @@

    - settings_more_info_link( 'serve-from-s3' ); ?> + settings_more_info_link( 'serve-from-s3', 'media+rewrite+file+urls' ); ?>

    @@ -101,7 +101,7 @@

    - settings_more_info_link( 'object-prefix' ); ?> + settings_more_info_link( 'object-prefix', 'media+path' ); ?>

    get_setting_args( 'object-prefix' ); ?> @@ -120,7 +120,7 @@

    - settings_more_info_link( 'use-yearmonth-folders' ); ?> + settings_more_info_link( 'use-yearmonth-folders', 'media+year+month' ); ?>

    @@ -135,7 +135,7 @@

    - settings_more_info_link( 'force-https' ); ?> + settings_more_info_link( 'force-https', 'media+force+https' ); ?>

    @@ -152,7 +152,7 @@

    - settings_more_info_link( 'remove-local-file' ); ?> + settings_more_info_link( 'remove-local-file', 'media+remove+files+from+server' ); ?>

    Broken URLs — There will be broken URLs for files that don\'t exist locally. You can fix this by enabling Rewrite File URLs to use the S3 URLs.', 'amazon-s3-and-cloudfront' ) ); @@ -165,7 +165,7 @@ ); $this->render_view( 'notice', $lost_files_args ); - $remove_local_link = $this->more_info_link( 'https://deliciousbrains.com/wp-offload-s3/doc/compatibility-with-other-plugins/' ); + $remove_local_link = $this->more_info_link( '/wp-offload-s3/doc/compatibility-with-other-plugins/', 'error-media+remove+files+from+server' ); $remove_local_msg = apply_filters( 'as3cf_remove_local_notice', sprintf( __( 'Warning — Some plugins depend on the file being present on the local server and may not work when the file is removed. %s', 'amazon-s3-and-cloudfront' ), $remove_local_link ) ); $remove_local_args = array( 'message' => $remove_local_msg, @@ -187,7 +187,7 @@

    - settings_more_info_link( 'object-versioning' ); ?> + settings_more_info_link( 'object-versioning', 'media+object+versioning' ); ?>

    diff --git a/view/sidebar.php b/view/sidebar.php index ad5c8d8b..811daf13 100644 --- a/view/sidebar.php +++ b/view/sidebar.php @@ -1,6 +1,9 @@
    - +

    Upgrade

    @@ -15,11 +18,16 @@
  • -

    +

    + +

    -