WP Offload S3 Lite - Version 1.1

Version Description

This is a major change, which ensures S3 URLs are no longer saved in post content. Instead, local URLs are filtered on page generation and replaced with the S3 version. If you depend on the S3 URLs being stored in post content you will need to make modifications to support this version.

Download this release

Release Info

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

Code changes from version 1.0.5 to 1.1

README.md CHANGED
@@ -2,8 +2,8 @@
2
  **Contributors:** bradt, deliciousbrains
3
  **Tags:** uploads, amazon, s3, amazon s3, mirror, admin, media, cdn, cloudfront
4
  **Requires at least:** 4.4
5
- **Tested up to:** 4.6
6
- **Stable tag:** 1.0.5
7
  **License:** GPLv3
8
 
9
  Copies files to Amazon S3 as they are uploaded to the Media Library. Optionally configure Amazon CloudFront for faster delivery.
@@ -21,7 +21,6 @@ If you're adding this plugin to a site that's been around for a while, your exis
21
  **PRO Upgrade with Email Support and More Features**
22
 
23
  * Upload existing Media Library to Amazon S3
24
- * Find & replace file URLs in content
25
  * Control Amazon S3 files from the Media Library
26
  * [Assets addon](https://deliciousbrains.com/wp-offload-s3/?utm_source=wordpress.org&utm_medium=web&utm_content=desc&utm_campaign=os3-free-plugin#assets-addon) - Serve your CSS & JS from Amazon S3/CloudFront
27
  * [WooCommerce addon](https://deliciousbrains.com/wp-offload-s3/?utm_source=wordpress.org&utm_medium=web&utm_content=desc&utm_campaign=os3-free-plugin#woocommerce-addon)
@@ -62,25 +61,40 @@ You can see the minimum requirements [here](https://deliciousbrains.com/wp-offlo
62
 
63
  ## Upgrade Notice ##
64
 
 
 
 
65
  ### 0.6 ###
66
  This version requires PHP 5.3.3+ and the Amazon Web Services plugin
67
 
68
  ## Changelog ##
69
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  ### WP Offload S3 Lite 1.0.5 - 2016-09-01 ###
71
  * New: Compatibility with WordPress 4.6
72
  * Improvement: No longer delete plugin data on uninstall. Manual removal possible, as per this [doc](https://deliciousbrains.com/wp-offload-s3/doc/uninstall/)
73
 
74
  ### WP Offload S3 Lite 1.0.4 - 2016-05-30 ###
75
- * New: Now using simpler Force HTTPS setting, removed redundant Always Use HTTP setting.
76
- * New: `as3cf_cloudfront_path_parts` filter allows changing served CloudFront path (useful when distribution pulls subdirectory).
77
- * Improvement: Better compatibility with non-standard notices from other plugins and themes.
78
- * Improvement: Added basic auth and proxy info to diagnostic info.
79
- * Improvement: Added `allow_url_fopen` status to diagnostic info.
80
- * Improvement: Added memory usage to diagnostic info.
81
- * Improvement: Ensure notice text is 800px or less in width.
82
- * Improvement: Reduced database queries on settings screen.
83
- * Bug fix: Properly handle _wp_attachment_data metadata when it is a serialized WP_Error.
84
 
85
  ### WP Offload S3 Lite 1.0.3 - 2016-03-23 ###
86
  * Bug fix: Don't replace srcset URLs when Rewrite File URLs option disabled
@@ -101,7 +115,7 @@ This version requires PHP 5.3.3+ and the Amazon Web Services plugin
101
  * Improvement: Far future expiration header set by default
102
  * Improvement: Newly created bucket now immediately appears in the bucket list
103
  * Improvement: Cleanup user meta on uninstall
104
- * Improvement: WP Retina 2x integration [removed](https://deliciousbrains.com/wp-offload-s3/doc/copy-hidpi-2x-images-support/)
105
  * Bug fix: Year/Month folder structure on S3 not created if the 'Organise my uploads into month and year-based folders' WordPress setting is disabled
106
  * Bug fix: Responsive srcset PHP notices
107
  * Bug fix: Compatibility addon notices displayed to non-admin users
2
  **Contributors:** bradt, deliciousbrains
3
  **Tags:** uploads, amazon, s3, amazon s3, mirror, admin, media, cdn, cloudfront
4
  **Requires at least:** 4.4
5
+ **Tested up to:** 4.6.1
6
+ **Stable tag:** 1.1
7
  **License:** GPLv3
8
 
9
  Copies files to Amazon S3 as they are uploaded to the Media Library. Optionally configure Amazon CloudFront for faster delivery.
21
  **PRO Upgrade with Email Support and More Features**
22
 
23
  * Upload existing Media Library to Amazon S3
 
24
  * Control Amazon S3 files from the Media Library
25
  * [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
26
  * [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)
61
 
62
  ## Upgrade Notice ##
63
 
64
+ ### 1.1 ###
65
+ This is a major change, which ensures S3 URLs are no longer saved in post content. Instead, local URLs are filtered on page generation and replaced with the S3 version. If you depend on the S3 URLs being stored in post content you will need to make modifications to support this version.
66
+
67
  ### 0.6 ###
68
  This version requires PHP 5.3.3+ and the Amazon Web Services plugin
69
 
70
  ## Changelog ##
71
 
72
+ ### WP Offload S3 Lite 1.1 - 2016-09-29 ###
73
+ * New: Filter post content. S3 URLs will no longer be saved to the database
74
+ * New: Upgrade routine to replace all S3 URLs in content with local URLs
75
+ * New: Support for theme custom logos
76
+ * New: Control the ACL for intermediate image sizes using the `as3cf_upload_acl_sizes` filter
77
+ * Bug fix: File names containing special characters double encoded
78
+ * Bug fix: `srcset` not working for file names containing special characters
79
+ * Bug fix: Incorrect placeholder text for 'Path' option
80
+ * Bug fix: Objects in root of bucket not deleted when removed from the Media Library
81
+ * Bug fix: No longer use deprecated functions in WordPress 4.6
82
+ * Bug fix: Don't delete local file when 'Remove Files From Server' enabled and upload to S3 fails
83
+
84
  ### WP Offload S3 Lite 1.0.5 - 2016-09-01 ###
85
  * New: Compatibility with WordPress 4.6
86
  * Improvement: No longer delete plugin data on uninstall. Manual removal possible, as per this [doc](https://deliciousbrains.com/wp-offload-s3/doc/uninstall/)
87
 
88
  ### WP Offload S3 Lite 1.0.4 - 2016-05-30 ###
89
+ * New: Now using simpler Force HTTPS setting, removed redundant Always Use HTTP setting
90
+ * New: `as3cf_cloudfront_path_parts` filter allows changing served CloudFront path (useful when distribution pulls subdirectory)
91
+ * Improvement: Better compatibility with non-standard notices from other plugins and themes
92
+ * Improvement: Added basic auth and proxy info to diagnostic info
93
+ * Improvement: Added `allow_url_fopen` status to diagnostic info
94
+ * Improvement: Added memory usage to diagnostic info
95
+ * Improvement: Ensure notice text is 800px or less in width
96
+ * Improvement: Reduced database queries on settings screen
97
+ * Bug fix: Properly handle _wp_attachment_data metadata when it is a serialized WP_Error
98
 
99
  ### WP Offload S3 Lite 1.0.3 - 2016-03-23 ###
100
  * Bug fix: Don't replace srcset URLs when Rewrite File URLs option disabled
115
  * Improvement: Far future expiration header set by default
116
  * Improvement: Newly created bucket now immediately appears in the bucket list
117
  * Improvement: Cleanup user meta on uninstall
118
+ * Improvement: WP Retina 2x integration removed
119
  * Bug fix: Year/Month folder structure on S3 not created if the 'Organise my uploads into month and year-based folders' WordPress setting is disabled
120
  * Bug fix: Responsive srcset PHP notices
121
  * Bug fix: Compatibility addon notices displayed to non-admin users
assets/css/attachment.css ADDED
@@ -0,0 +1 @@
 
1
+ #s3-actions.postbox .inside{margin:0;padding:0}#s3-actions.postbox a,#s3-actions.postbox a:hover{text-decoration:none}#s3-actions.postbox .s3-details{padding:6px 0}#s3-actions.postbox .s3-details .misc-pub-section{clear:both;float:left;width:100%;-webkit-box-sizing:border-box;box-sizing:border-box}#s3-actions.postbox .s3-details .misc-pub-section .s3-key{float:left;width:20%}#s3-actions.postbox .s3-details .misc-pub-section .s3-value{font-weight:bold;float:left;width:80%}#s3-actions.postbox .s3-details .not-copied{color:#666666}#s3-actions.postbox .s3-actions{padding:10px;clear:both;border-top:1px solid #ddd;border-bottom:1px solid #ddd;background:#f5f5f5}#s3-actions.postbox .s3-actions .copy-action{text-align:right;float:right;line-height:23px}#s3-actions.postbox .s3-actions .remove-action{line-height:28px;vertical-align:middle;text-align:left;float:left}#s3-actions.postbox .s3-actions .remove-action a.local-warning{color:#a00}#s3-actions.postbox .s3-actions .remove-action a.local-warning:hover{color:#f00}
assets/css/media.css ADDED
@@ -0,0 +1 @@
 
1
+ body.as3cf-pro .attachments-browser .media-toolbar-secondary{max-width:100%}.as3cfpro_remove a.local-warning{color:#a00}.as3cfpro_remove a.local-warning:hover{color:#f00;text-decoration:none;border:none}.media-modal a.local-warning{color:#bc0b0b}.media-modal a.local-warning:hover{color:red}.attachment-info .attachment-s3-details{font-weight:bold;margin-bottom:5px}
assets/js/media.js ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ var test = {};
2
+
3
+ (function( $, _ ) {
4
+
5
+ // Local reference to the WordPress media namespace.
6
+ var media = wp.media;
7
+
8
+ // Local instance of the Attachment Details TwoColumn used in the edit attachment modal view
9
+ var wpAttachmentDetailsTwoColumn = media.view.Attachment.Details.TwoColumn;
10
+
11
+ /**
12
+ * Add S3 details to attachment.
13
+ */
14
+ media.view.Attachment.Details.TwoColumn = wpAttachmentDetailsTwoColumn.extend( {
15
+ events: function() {
16
+ return _.extend( {}, wpAttachmentDetailsTwoColumn.prototype.events, {
17
+ 'click .local-warning': 'confirmS3Removal',
18
+ 'click #as3cfpro-toggle-acl': 'toggleACL'
19
+ } );
20
+ },
21
+
22
+ render: function() {
23
+ // Retrieve the S3 details for the attachment
24
+ // before we render the view
25
+ this.fetchS3Details( this.model.get( 'id' ) );
26
+ },
27
+
28
+ fetchS3Details: function( id ) {
29
+ wp.ajax.send( 'as3cf_get_attachment_s3_details', {
30
+ data: {
31
+ _nonce: as3cf_media.nonces.get_attachment_s3_details,
32
+ id: id
33
+ }
34
+ } ).done( _.bind( this.renderView, this ) );
35
+ },
36
+
37
+ renderView: function( response ) {
38
+ // Render parent media.view.Attachment.Details
39
+ wpAttachmentDetailsTwoColumn.prototype.render.apply( this );
40
+
41
+ this.renderActionLinks( response );
42
+ this.renderS3Details( response );
43
+ },
44
+
45
+ renderActionLinks: function( response ) {
46
+ var links = ( response && response.links ) || [];
47
+ var $actionsHtml = this.$el.find( '.actions' );
48
+ var $s3Actions = $( '<div />', {
49
+ 'class': 's3-actions'
50
+ } );
51
+
52
+ var s3Links = [];
53
+ _( links ).each( function( link ) {
54
+ s3Links.push( link );
55
+ } );
56
+
57
+ $s3Actions.append( s3Links.join( ' | ' ) );
58
+ $actionsHtml.append( $s3Actions );
59
+ },
60
+
61
+ renderS3Details: function( response ) {
62
+ if ( ! response || ! response.s3object ) {
63
+ return;
64
+ }
65
+ var $detailsHtml = this.$el.find( '.attachment-info .details' );
66
+ var html = this.generateDetails( response, [ 'bucket', 'key', 'region', 'acl' ] );
67
+ $detailsHtml.append( html );
68
+ },
69
+
70
+ generateDetails: function( response, keys ) {
71
+ var html = '';
72
+ var template = _.template( '<div class="<%= key %>"><strong><%= label %>:</strong> <%= value %></div>' );
73
+
74
+ _( keys ).each( function( key ) {
75
+ if ( response.s3object[ key ] ) {
76
+ var value = response.s3object[ key ];
77
+
78
+ if ( 'acl' === key ) {
79
+ value = response.s3object[ key ]['name'];
80
+
81
+ if ( response.acl_toggle ) {
82
+ var acl_template = _.template( '<a href="#" id="as3cfpro-toggle-acl" title="<%= title %>" data-currentACL="<%= acl %>"><%= value %></a>' );
83
+
84
+ value = acl_template( {
85
+ title: response.s3object[ key ][ 'title' ],
86
+ acl: response.s3object[ key ][ 'acl' ],
87
+ value: value
88
+ } );
89
+ }
90
+ }
91
+
92
+ html += template( {
93
+ key: key,
94
+ label: as3cf_media.strings[ key ],
95
+ value: value
96
+ } );
97
+ }
98
+ } );
99
+
100
+ return html;
101
+ },
102
+
103
+ confirmS3Removal: function( event ) {
104
+ if ( ! confirm( as3cfpro_media.strings.local_warning ) ) {
105
+ event.preventDefault();
106
+ event.stopImmediatePropagation();
107
+ return false;
108
+ }
109
+ },
110
+
111
+ toggleACL: function( event ) {
112
+ event.preventDefault();
113
+
114
+ var toggle = $( '#as3cfpro-toggle-acl' );
115
+ var currentACL = toggle.attr( 'data-currentACL' );
116
+ var newACL = as3cfpro_media.settings.private_acl;
117
+
118
+ toggle.hide();
119
+ toggle.after( '<span id="as3cfpro-updating">' + as3cfpro_media.strings.updating_acl + '</span>' );
120
+
121
+ if ( currentACL === as3cfpro_media.settings.private_acl ) {
122
+ newACL = as3cfpro_media.settings.default_acl;
123
+ }
124
+
125
+ wp.ajax.send( 'as3cfpro_update_acl', {
126
+ data: {
127
+ _nonce: as3cfpro_media.nonces.update_acl,
128
+ id: this.model.get( 'id' ),
129
+ acl: newACL
130
+ }
131
+ } )
132
+ .done( _.bind( this.updateACL, this ) )
133
+ .fail( _.bind( this.renderACLError, this ) );
134
+ },
135
+
136
+ renderACLError: function() {
137
+ $( '#as3cfpro-updating' ).remove();
138
+ $( '#as3cfpro-toggle-acl' ).show();
139
+ alert( as3cfpro_media.strings.change_acl_error );
140
+ },
141
+
142
+ updateACL: function( response ) {
143
+ if ( 'undefined' === typeof response.acl_display || 'undefined' === typeof response.title || 'undefined' === typeof response.acl ) {
144
+ this.renderACLError();
145
+
146
+ return;
147
+ }
148
+
149
+ var toggle = $( '#as3cfpro-toggle-acl' );
150
+
151
+ $( '#as3cfpro-updating' ).remove();
152
+
153
+ toggle.text( response.acl_display );
154
+ toggle.attr( 'title', response.title );
155
+ toggle.attr( 'data-currentACL', response.acl );
156
+ toggle.show();
157
+ }
158
+ } );
159
+
160
+ })( jQuery, _ );
assets/js/media.min.js ADDED
@@ -0,0 +1 @@
 
1
+ var test={};!function(a,b){var c=wp.media,d=c.view.Attachment.Details.TwoColumn;c.view.Attachment.Details.TwoColumn=d.extend({events:function(){return b.extend({},d.prototype.events,{"click .local-warning":"confirmS3Removal","click #as3cfpro-toggle-acl":"toggleACL"})},render:function(){this.fetchS3Details(this.model.get("id"))},fetchS3Details:function(a){wp.ajax.send("as3cf_get_attachment_s3_details",{data:{_nonce:as3cf_media.nonces.get_attachment_s3_details,id:a}}).done(b.bind(this.renderView,this))},renderView:function(a){d.prototype.render.apply(this),this.renderActionLinks(a),this.renderS3Details(a)},renderActionLinks:function(c){var d=c&&c.links||[],e=this.$el.find(".actions"),f=a("<div />",{"class":"s3-actions"}),g=[];b(d).each(function(a){g.push(a)}),f.append(g.join(" | ")),e.append(f)},renderS3Details:function(a){if(a&&a.s3object){var b=this.$el.find(".attachment-info .details"),c=this.generateDetails(a,["bucket","key","region","acl"]);b.append(c)}},generateDetails:function(a,c){var d="",e=b.template('<div class="<%= key %>"><strong><%= label %>:</strong> <%= value %></div>');return b(c).each(function(c){if(a.s3object[c]){var f=a.s3object[c];if("acl"===c&&(f=a.s3object[c].name,a.acl_toggle)){var g=b.template('<a href="#" id="as3cfpro-toggle-acl" title="<%= title %>" data-currentACL="<%= acl %>"><%= value %></a>');f=g({title:a.s3object[c].title,acl:a.s3object[c].acl,value:f})}d+=e({key:c,label:as3cf_media.strings[c],value:f})}}),d},confirmS3Removal:function(a){return confirm(as3cfpro_media.strings.local_warning)?void 0:(a.preventDefault(),a.stopImmediatePropagation(),!1)},toggleACL:function(c){c.preventDefault();var d=a("#as3cfpro-toggle-acl"),e=d.attr("data-currentACL"),f=as3cfpro_media.settings.private_acl;d.hide(),d.after('<span id="as3cfpro-updating">'+as3cfpro_media.strings.updating_acl+"</span>"),e===as3cfpro_media.settings.private_acl&&(f=as3cfpro_media.settings.default_acl),wp.ajax.send("as3cfpro_update_acl",{data:{_nonce:as3cfpro_media.nonces.update_acl,id:this.model.get("id"),acl:f}}).done(b.bind(this.updateACL,this)).fail(b.bind(this.renderACLError,this))},renderACLError:function(){a("#as3cfpro-updating").remove(),a("#as3cfpro-toggle-acl").show(),alert(as3cfpro_media.strings.change_acl_error)},updateACL:function(b){if("undefined"==typeof b.acl_display||"undefined"==typeof b.title||"undefined"==typeof b.acl)return void this.renderACLError();var c=a("#as3cfpro-toggle-acl");a("#as3cfpro-updating").remove(),c.text(b.acl_display),c.attr("title",b.title),c.attr("data-currentACL",b.acl),c.show()}})}(jQuery,_);
assets/js/modal.min.js CHANGED
@@ -1 +1 @@
1
- var as3cfModal=function(a){function b(a){return a.replace(/[^a-z]/g,"")}var c={prefix:"as3cf",loading:!1},d={};return c.exists=function(c){var e=b(c);return void 0!==d[e]||!!a(c).length},c.open=function(c,e,f){var g=b(c);a("body").append('<div id="as3cf-overlay"></div>');var h=a("#as3cf-overlay");h.append('<div id="as3cf-modal"><span class="close-as3cf-modal">×</span></div>');var i=a("#as3cf-modal");if(void 0===d[g]){var j=a(c);d[g]=j.clone(!0).css("display","block"),j.remove()}i.data("as3cf-modal-target",c).append(d[g]),void 0!==f&&i.addClass(f),"function"==typeof e&&e(c),a("body").addClass("as3cf-modal-open"),h.fadeIn(150),i.fadeIn(150),a("body").trigger("as3cf-modal-open",[c])},c.close=function(b){if(!c.loading){var d=a("#as3cf-modal").data("as3cf-modal-target");a("#as3cf-overlay").fadeOut(150,function(){"function"==typeof b&&b(d),a("body").removeClass("as3cf-modal-open"),a(this).remove()}),a("body").trigger("as3cf-modal-close",[d])}},c.setLoadingState=function(a){c.loading=a},a(document).ready(function(){a("body").on("click","[data-as3cf-modal]",function(b){b.preventDefault(),c.open(a(this).data("as3cf-modal")+"."+c.prefix)}),a("body").on("click","#as3cf-overlay, .close-as3cf-modal",function(a){return a.preventDefault(),a.target===this&&void c.close()})}),c}(jQuery);
1
+ var as3cfModal=function(a){function b(a){return a.replace(/[^a-z]/g,"")}var c={prefix:"as3cf",loading:!1},d={};return c.exists=function(c){var e=b(c);return void 0!==d[e]?!0:!!a(c).length},c.open=function(c,e,f){var g=b(c);a("body").append('<div id="as3cf-overlay"></div>');var h=a("#as3cf-overlay");h.append('<div id="as3cf-modal"><span class="close-as3cf-modal">×</span></div>');var i=a("#as3cf-modal");if(void 0===d[g]){var j=a(c);d[g]=j.clone(!0).css("display","block"),j.remove()}i.data("as3cf-modal-target",c).append(d[g]),void 0!==f&&i.addClass(f),"function"==typeof e&&e(c),a("body").addClass("as3cf-modal-open"),h.fadeIn(150),i.fadeIn(150),a("body").trigger("as3cf-modal-open",[c])},c.close=function(b){if(!c.loading){var d=a("#as3cf-modal").data("as3cf-modal-target");a("#as3cf-overlay").fadeOut(150,function(){"function"==typeof b&&b(d),a("body").removeClass("as3cf-modal-open"),a(this).remove()}),a("body").trigger("as3cf-modal-close",[d])}},c.setLoadingState=function(a){c.loading=a},a(document).ready(function(){a("body").on("click","[data-as3cf-modal]",function(b){b.preventDefault(),c.open(a(this).data("as3cf-modal")+"."+c.prefix)}),a("body").on("click","#as3cf-overlay, .close-as3cf-modal",function(a){return a.preventDefault(),a.target!==this?!1:void c.close()})}),c}(jQuery);
assets/js/script.min.js CHANGED
@@ -1 +1 @@
1
- !function(a,b){function c(b){return a("#"+b+" .as3cf-main-settings form").find("input:not(.no-compare)").serialize()}function d(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("#remove-local-file").is(":checked")&&a("#serve-from-s3").is(":not(:checked)")?a("#as3cf-lost-files-notice").show():a("#as3cf-lost-files-notice").hide()}function j(){a("#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('<li class="loading">'+d.attr("data-working")+"</li>");var f={action:b.prefix+"-get-buckets",_nonce:window[b.prefix.replace(/-/g,"_")].nonces.get_buckets},g=this;a.ajax({url:ajaxurl,type:"POST",dataType:"JSON",data:f,error:function(a,b,c){d.html(""),g.showError(as3cf.strings.get_buckets_error,c,"as3cf-bucket-select")},success:function(b,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('<li><a class="'+c+'" href="#" data-bucket="'+b.Name+'"><span class="bucket"><span class="dashicons dashicons-portfolio"></span> '+b.Name+'</span><span class="spinner"></span></span></a></li>')}),g.scrollToSelected()):g.showError(as3cf.strings.get_buckets_error,b.error,"as3cf-bucket-select")}})},scrollToSelected:function(){if(a(".as3cf-bucket-list a.selected").length){var b=a("ul.as3cf-bucket-list li").first().position().top+150;a(".as3cf-bucket-list").animate({scrollTop:a("ul.as3cf-bucket-list li a.selected").position().top-b})}},resetModal:function(){var c=a(".as3cf-bucket-container."+b.prefix);!1===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+" &mdash;"),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("copy-to-s3-wrap"),d("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("#serve-from-s3,#remove-local-file").on("change",function(a){i()}),j(),a("#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().length<as3cf.buckets.validLength?d.find("button[type=submit]").attr("disabled",!0):d.find("button[type=submit]").removeAttr("disabled")})})}(jQuery,as3cfModal);
1
+ !function(a,b){function c(b){return a("#"+b+" .as3cf-main-settings form").find("input:not(.no-compare)").serialize()}function d(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("#remove-local-file").is(":checked")&&a("#serve-from-s3").is(":not(:checked)")?a("#as3cf-lost-files-notice").show():a("#as3cf-lost-files-notice").hide()}function j(){a("#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('<li class="loading">'+d.attr("data-working")+"</li>");var f={action:b.prefix+"-get-buckets",_nonce:window[b.prefix.replace(/-/g,"_")].nonces.get_buckets},g=this;a.ajax({url:ajaxurl,type:"POST",dataType:"JSON",data:f,error:function(a,b,c){d.html(""),g.showError(as3cf.strings.get_buckets_error,c,"as3cf-bucket-select")},success:function(b,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('<li><a class="'+c+'" href="#" data-bucket="'+b.Name+'"><span class="bucket"><span class="dashicons dashicons-portfolio"></span> '+b.Name+'</span><span class="spinner"></span></span></a></li>')}),g.scrollToSelected()):g.showError(as3cf.strings.get_buckets_error,b.error,"as3cf-bucket-select")}})},scrollToSelected:function(){if(a(".as3cf-bucket-list a.selected").length){var b=a("ul.as3cf-bucket-list li").first().position().top+150;a(".as3cf-bucket-list").animate({scrollTop:a("ul.as3cf-bucket-list li a.selected").position().top-b})}},resetModal:function(){var c=a(".as3cf-bucket-container."+b.prefix);!1===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+" &mdash;"),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("copy-to-s3-wrap"),d("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?!1:!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("#serve-from-s3,#remove-local-file").on("change",function(a){i()}),j(),a("#remove-local-file").on("change",function(a){j()}),a('.as3cf-setting input[type="text"]').keypress(function(a){return 13===a.which?(a.preventDefault(),!1):void 0}),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().length<as3cf.buckets.validLength?d.find("button[type=submit]").attr("disabled",!0):d.find("button[type=submit]").removeAttr("disabled")})})}(jQuery,as3cfModal);
assets/js/tinymce.min.js ADDED
@@ -0,0 +1 @@
 
1
+ tinymce.PluginManager.add("as3cf",function(a){function b(a){var b=[];if(h.each(a,function(a,c){h.has(j,a)||b.push(a)}),0!==b.length&&!(h.isUndefined(i.frame)||h.isUndefined(i.frame.states)||h.isUndefined(i.frame.states.models))){var c=h.find(i.frame.states.models,function(a){return"insert"===a.id});if(!h.isUndefined(c)&&0!==h.size(c.attributes.library.models)){var d=c.attributes.library.models,e={};h.each(a,function(a,b){var c=a.replace(/-[0-9]{1,4}x[0-9]{1,4}(\.[a-z]{2,4})$/,"$1"),f=h.find(d,function(a){return a.attributes.url===c});if(!h.isUndefined(f)){var g=h.find(f.attributes.sizes,function(b){return a===b.local_url});return h.isUndefined(g)?!1:void(e[a]=g.url)}}),h.extend(j,e)}}}function c(a){var b=[];h.each(a,function(a,c){h.has(j,a)||b.push(a)}),0!==b.length&&g.ajax({url:ajaxurl,type:"POST",dataType:"JSON",async:!1,cache:!1,data:{action:"as3cf_tinymce_fetch_urls",_nonce:as3cf_tinymce.fetch_urls_nonce,urls:b},success:function(a){j=h.extend(j,a.data)}})}function d(a){return h.has(j,a)?j[a]:!1}function e(a){var e=new RegExp("("+as3cf_tinymce.local_url_escaped+'[^"<\\s]*)',"g"),f=a.match(e);return null===f?a:(b(f),c(f),h.each(f,function(b,c){var e=d(b);!1!==e&&(a=a.replace(new RegExp(b,"g"),e))}),a)}function f(a){return h.each(j,function(b,c){!1!==b&&(a=a.replace(new RegExp(b,"g"),c))}),a}var g=window.jQuery,h=window._,i=window.wp.media,j={};a.on("BeforeSetContent",function(a){a.content=e(a.content)}),a.on("PostProcess",function(a){a.get&&(a.content=f(a.content))})});
assets/sass/attachment.scss ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #s3-actions.postbox {
2
+ .inside {
3
+ margin: 0;
4
+ padding: 0;
5
+ }
6
+ a, a:hover {
7
+ text-decoration: none;
8
+ }
9
+ .s3-details {
10
+ padding: 6px 0;
11
+
12
+ .misc-pub-section {
13
+ clear: both;
14
+ float: left;
15
+ width: 100%;
16
+ -webkit-box-sizing: border-box;
17
+ -moz-box-sizing: border-box;
18
+ box-sizing: border-box;
19
+
20
+ .s3-key {
21
+ float: left;
22
+ width: 20%;
23
+ }
24
+ .s3-value {
25
+ font-weight: bold;
26
+ float: left;
27
+ width: 80%;
28
+ }
29
+ }
30
+ .not-copied {
31
+ color: #666666;
32
+ }
33
+ }
34
+ .s3-actions {
35
+ padding: 10px;
36
+ clear: both;
37
+ border-top: 1px solid #ddd;
38
+ border-bottom: 1px solid #ddd;
39
+ background: #f5f5f5;
40
+
41
+ .copy-action {
42
+ text-align: right;
43
+ float: right;
44
+ line-height: 23px;
45
+ }
46
+
47
+ .remove-action {
48
+ line-height: 28px;
49
+ vertical-align: middle;
50
+ text-align: left;
51
+ float: left;
52
+
53
+ a.local-warning {
54
+ color: #a00;
55
+ &:hover {
56
+ color: #f00;
57
+ }
58
+ }
59
+ }
60
+ }
61
+ }
assets/sass/media.scss ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ body.as3cf-pro {
2
+ .attachments-browser .media-toolbar-secondary {
3
+ max-width: 100%;
4
+ }
5
+ }
6
+
7
+ .as3cfpro_remove a.local-warning {
8
+ color: #a00;
9
+ &:hover {
10
+ color: #f00;
11
+ text-decoration: none;
12
+ border: none;
13
+ }
14
+ }
15
+
16
+ .media-modal a.local-warning {
17
+ color: #bc0b0b;
18
+ &:hover {
19
+ color: red;
20
+ }
21
+ }
22
+
23
+ .attachment-info .attachment-s3-details {
24
+ font-weight: bold;
25
+ margin-bottom: 5px;
26
+ }
classes/amazon-s3-and-cloudfront.php CHANGED
@@ -42,6 +42,16 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
42
  */
43
  protected $default_tab = '';
44
 
 
 
 
 
 
 
 
 
 
 
45
  /**
46
  * @var AS3CF_Notices
47
  */
@@ -57,11 +67,6 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
57
  */
58
  protected static $buckets_check = array();
59
 
60
- /**
61
- * @var array
62
- */
63
- protected $encode_files = array();
64
-
65
  /**
66
  * @var AS3CF_Plugin_Compatibility
67
  */
@@ -75,6 +80,8 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
75
  const SETTINGS_KEY = 'tantan_wordpress_s3';
76
  const SETTINGS_CONSTANT = 'WPOS3_SETTINGS';
77
 
 
 
78
  /**
79
  * @param string $plugin_file_path
80
  * @param Amazon_Web_Services $aws
@@ -86,7 +93,7 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
86
  parent::__construct( $plugin_file_path );
87
 
88
  $this->aws = $aws;
89
- $this->notices = AS3CF_Notices::get_instance( $this, $plugin_file_path );
90
 
91
  $this->init( $plugin_file_path );
92
  }
@@ -104,7 +111,8 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
104
  new AS3CF_Upgrade_Region_Meta( $this );
105
  new AS3CF_Upgrade_File_Sizes( $this );
106
  new AS3CF_Upgrade_Meta_WP_Error( $this );
107
- $this->maybe_display_deprecated_retina_notice();
 
108
 
109
  // Plugin setup
110
  add_action( 'aws_admin_menu', array( $this, 'admin_menu' ) );
@@ -113,12 +121,18 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
113
  // display a notice when either lite or pro is automatically deactivated
114
  add_action( 'pre_current_active_plugins', array( $this, 'plugin_deactivated_notice' ) );
115
 
 
 
 
 
 
116
  // UI AJAX
117
  add_action( 'wp_ajax_as3cf-get-buckets', array( $this, 'ajax_get_buckets' ) );
118
  add_action( 'wp_ajax_as3cf-save-bucket', array( $this, 'ajax_save_bucket' ) );
119
  add_action( 'wp_ajax_as3cf-create-bucket', array( $this, 'ajax_create_bucket' ) );
120
  add_action( 'wp_ajax_as3cf-manual-save-bucket', array( $this, 'ajax_save_bucket' ) );
121
  add_action( 'wp_ajax_as3cf-get-url-preview', array( $this, 'ajax_get_url_preview' ) );
 
122
  add_action( 'wp_ajax_as3cf-get-diagnostic-info', array( $this, 'ajax_get_diagnostic_info' ) );
123
 
124
  // Rewriting URLs, doesn't depend on plugin being setup
@@ -135,6 +149,10 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
135
  add_filter( 'delete_attachment', array( $this, 'delete_attachment' ), 20 );
136
  add_filter( 'update_attached_file', array( $this, 'update_attached_file' ), 100, 2 );
137
 
 
 
 
 
138
  // include compatibility code for other plugins
139
  $this->plugin_compat = new AS3CF_Plugin_Compatibility( $this );
140
 
@@ -229,6 +247,16 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
229
  return '1';
230
  }
231
 
 
 
 
 
 
 
 
 
 
 
232
  // Turn on object versioning by default
233
  if ( 'object-versioning' == $key && ! isset( $settings['object-versioning'] ) ) {
234
  return '1';
@@ -589,6 +617,26 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
589
  }
590
  }
591
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
592
  /**
593
  * Generate a preview of the URL of files uploaded to S3
594
  *
@@ -610,7 +658,7 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
610
 
611
  $url = $scheme . '://' . $domain . '/' . $path . $suffix;
612
 
613
- // replace hyphens with non breaking hyphens for formatting
614
  if ( $escape ) {
615
  $url = str_replace( '-', '&#8209;', $url );
616
  }
@@ -694,7 +742,7 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
694
  * to cope with possible different regions
695
  */
696
  function remove_attachment_files_from_s3( $post_id, $s3object, $remove_backup_sizes = true, $log_error = false, $return_on_error = false, $force_new_s3_client = false ) {
697
- $prefix = trailingslashit( dirname( $s3object['key'] ) );
698
  $bucket = $s3object['bucket'];
699
  $region = $this->get_s3object_region( $s3object );
700
  $paths = $this->get_attachment_file_paths( $post_id, false, false, $remove_backup_sizes );
@@ -836,8 +884,7 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
836
  $acl = $old_s3object['acl'];
837
  }
838
  // use existing prefix
839
- $prefix = dirname( $old_s3object['key'] );
840
- $prefix = ( '.' === $prefix ) ? '' : $prefix . '/';
841
  // use existing bucket
842
  $bucket = $old_s3object['bucket'];
843
  // get existing region
@@ -904,9 +951,9 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
904
  $files_to_remove = array();
905
 
906
  if ( file_exists( $file_path ) ) {
907
- $files_to_remove[] = $file_path;
908
  try {
909
  $s3client->putObject( $args );
 
910
  } catch ( Exception $e ) {
911
  $error_msg = sprintf( __( 'Error uploading %s to S3: %s', 'amazon-s3-and-cloudfront' ), $file_path, $e->getMessage() );
912
 
@@ -940,14 +987,21 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
940
  }
941
  }
942
 
943
- foreach ( $file_paths as $file_path ) {
 
 
944
  if ( ! in_array( $file_path, $files_to_remove ) ) {
 
 
945
  $additional_images[] = array(
946
  'Key' => $prefix . basename( $file_path ),
947
  'SourceFile' => $file_path,
 
948
  );
949
 
950
- $files_to_remove[] = $file_path;
 
 
951
 
952
  if ( $remove_local_files_setting ) {
953
  // Record the file size for the additional image
@@ -961,9 +1015,9 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
961
 
962
  foreach ( $additional_images as $image ) {
963
  try {
964
- $args = array_merge( $args, $image );
965
- $args['ACL'] = self::DEFAULT_ACL;
966
  $s3client->putObject( $args );
 
967
  } catch ( Exception $e ) {
968
  AS3CF_Error::log( 'Error uploading ' . $args['SourceFile'] . ' to S3: ' . $e->getMessage() );
969
  }
@@ -975,7 +1029,6 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
975
  $files_to_remove = apply_filters( 'as3cf_upload_attachment_local_files_to_remove', $files_to_remove, $post_id, $file_path );
976
  // Remove duplicates
977
  $files_to_remove = array_unique( $files_to_remove );
978
-
979
  // Delete the files
980
  $this->remove_local_files( $files_to_remove );
981
  }
@@ -1001,6 +1054,12 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
1001
  }
1002
  }
1003
 
 
 
 
 
 
 
1004
  do_action( 'wpos3_post_upload_attachment', $post_id, $s3object );
1005
 
1006
  if ( ! is_null( $return_metadata ) ) {
@@ -1337,26 +1396,6 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
1337
  return $filename;
1338
  }
1339
 
1340
- /**
1341
- * Get attachment url
1342
- *
1343
- * @param string $url
1344
- * @param int $post_id
1345
- *
1346
- * @return bool|mixed|void|WP_Error
1347
- */
1348
- function wp_get_attachment_url( $url, $post_id ) {
1349
- $new_url = $this->get_attachment_url( $post_id );
1350
- if ( false === $new_url ) {
1351
- return $url;
1352
- }
1353
-
1354
- $new_url = apply_filters( 'wps3_get_attachment_url', $new_url, $post_id, $this ); // Old naming convention, will be deprecated soon
1355
- $new_url = apply_filters( 'as3cf_wp_get_attachment_url', $new_url, $post_id );
1356
-
1357
- return $new_url;
1358
- }
1359
-
1360
  /**
1361
  * Get attachment s3 info
1362
  *
@@ -1364,7 +1403,7 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
1364
  *
1365
  * @return mixed
1366
  */
1367
- function get_attachment_s3_info( $post_id ) {
1368
  return apply_filters( 'as3cf_get_attachment_s3_info', get_post_meta( $post_id, 'amazonS3_info', true ), $post_id );
1369
  }
1370
 
@@ -1570,25 +1609,101 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
1570
  *
1571
  * @return bool|mixed|void|WP_Error
1572
  */
1573
- function get_attachment_url( $post_id, $expires = null, $size = null, $meta = null, $headers = array(), $skip_rewrite_check = false ) {
1574
  if ( ! ( $s3object = $this->is_attachment_served_by_s3( $post_id, $skip_rewrite_check ) ) ) {
1575
  return false;
1576
  }
1577
 
1578
- return $this->get_attachment_s3_url( $post_id, $s3object, $expires, $size, $meta, $headers );
 
 
1579
  }
1580
 
1581
  /**
1582
- * Get the S3 URL for an attachment
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1583
  *
1584
  * @param int $post_id
1585
- * @param array $s3object
1586
- * @param null|int $expires
1587
- * @param null|string $size
1588
- * @param null|array $meta
1589
- * @param array $headers
1590
  *
1591
- * @return mixed|void|WP_Error
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1592
  */
1593
  public function get_attachment_s3_url( $post_id, $s3object, $expires = null, $size = null, $meta = null, $headers = array() ) {
1594
  $scheme = $this->get_s3_url_scheme();
@@ -1601,12 +1716,20 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
1601
  $region = '';
1602
  }
1603
 
1604
- // force use of secured url when ACL has been set to private
1605
- if ( is_null( $expires ) && isset( $s3object['acl'] ) && self::PRIVATE_ACL == $s3object['acl'] ) {
1606
- $expires = self::DEFAULT_EXPIRES;
1607
- }
1608
 
1609
- $domain_bucket = $this->get_s3_url_domain( $s3object['bucket'], $region, $expires );
 
 
 
 
 
 
 
 
 
 
 
1610
 
1611
  if ( ! is_null( $size ) ) {
1612
  if ( is_null( $meta ) ) {
@@ -1617,7 +1740,7 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
1617
  return $meta;
1618
  }
1619
 
1620
- if ( isset( $meta['sizes'][ $size ]['file'] ) ) {
1621
  $size_prefix = dirname( $s3object['key'] );
1622
  $size_file_prefix = ( '.' === $size_prefix ) ? '' : $size_prefix . '/';
1623
 
@@ -1638,12 +1761,34 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
1638
 
1639
  $s3object['key'] = $this->maybe_update_cloudfront_path( $s3object['key'] );
1640
 
1641
- $file = $this->encode_filename_in_path( $s3object['key'], $post_id );
1642
- $url = $scheme . '://' . $domain_bucket . '/' . $file;
 
1643
 
1644
  return apply_filters( 'as3cf_get_attachment_url', $url, $s3object, $post_id, $expires, $headers );
1645
  }
1646
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1647
  /**
1648
  * Maybe encode attachment URLs when retrieving the image tag
1649
  *
@@ -1657,7 +1802,8 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
1657
  * @return string
1658
  */
1659
  public function maybe_encode_get_image_tag( $html, $id, $alt, $title, $align, $size ) {
1660
- if ( ! $this->is_attachment_served_by_s3( $id ) ) {
 
1661
  return $html;
1662
  }
1663
 
@@ -1669,9 +1815,10 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
1669
  }
1670
 
1671
  $img_src = $matches[1];
1672
- $encoded_src = $this->encode_filename_in_path( $img_src, $id );
 
1673
 
1674
- return str_replace( $img_src, $encoded_src, $html );
1675
  }
1676
 
1677
  /**
@@ -1685,12 +1832,16 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
1685
  * @return array
1686
  */
1687
  public function maybe_encode_wp_get_attachment_image_src( $image, $attachment_id, $size, $icon ) {
1688
- if ( ! $this->is_attachment_served_by_s3( $attachment_id ) ) {
 
1689
  return $image;
1690
  }
1691
 
1692
  if ( isset( $image[0] ) ) {
1693
- $image[0] = $this->encode_filename_in_path( $image[0], $attachment_id );
 
 
 
1694
  }
1695
 
1696
  return $image;
@@ -1706,17 +1857,21 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
1706
  * @return array
1707
  */
1708
  public function maybe_encode_wp_prepare_attachment_for_js( $response, $attachment, $meta ) {
1709
- if ( ! $this->is_attachment_served_by_s3( $attachment->ID ) ) {
 
1710
  return $response;
1711
  }
1712
 
1713
  if ( isset( $response['url'] ) ) {
1714
- $response['url'] = $this->encode_filename_in_path( $response['url'], $attachment->ID );
1715
  }
1716
 
1717
  if ( isset( $response['sizes'] ) && is_array( $response['sizes'] ) ) {
1718
- foreach ( $response['sizes'] as $key => $value ) {
1719
- $response['sizes'][ $key ]['url'] = $this->encode_filename_in_path( $value['url'], $attachment->ID );
 
 
 
1720
  }
1721
  }
1722
 
@@ -1733,17 +1888,110 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
1733
  * @return array
1734
  */
1735
  public function maybe_encode_image_get_intermediate_size( $data, $post_id, $size ) {
1736
- if ( ! $this->is_attachment_served_by_s3( $post_id ) ) {
 
1737
  return $data;
1738
  }
1739
 
1740
  if ( isset( $data['url'] ) ) {
1741
- $data['url'] = $this->encode_filename_in_path( $data['url'], $post_id );
 
 
 
1742
  }
1743
 
1744
  return $data;
1745
  }
1746
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1747
  /**
1748
  * Is attachment served by S3.
1749
  *
@@ -1770,12 +2018,11 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
1770
  * Encode file names according to RFC 3986 when generating urls
1771
  * As per Amazon https://forums.aws.amazon.com/thread.jspa?threadID=55746#jive-message-244233
1772
  *
1773
- * @param string $file
1774
- * @param null|int $attachment_id
1775
  *
1776
- * @return string Encoded filename with path prefix untouched
1777
  */
1778
- function encode_filename_in_path( $file, $attachment_id = null ) {
1779
  $url = parse_url( $file );
1780
 
1781
  if ( ! isset( $url['path'] ) ) {
@@ -1783,61 +2030,48 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
1783
  return $file;
1784
  }
1785
 
1786
- if ( in_array( $this->normalize_file_path( $url['path'], $attachment_id ), $this->encode_files ) ) {
1787
- // Already encoded, return original
 
 
1788
  return $file;
1789
  }
1790
 
1791
- $file_path = dirname( $file );
1792
- $file_path = ( '.' !== $file_path ) ? trailingslashit( $file_path ) : '';
1793
- $file_name = basename( $url['path'] );
1794
  $encoded_file_name = rawurlencode( $file_name );
1795
- $encoded_file_path = $file_path . $encoded_file_name;
1796
 
1797
  if ( $file_name === $encoded_file_name ) {
1798
  // File name doesn't need encoding, return original
1799
  return $file;
1800
  }
1801
 
1802
- $normalized_file_path = $this->normalize_file_path( $encoded_file_path, $attachment_id );
1803
-
1804
- if ( ! in_array( $normalized_file_path, $this->encode_files ) ) {
1805
- $this->encode_files[] = $normalized_file_path;
1806
- }
1807
-
1808
  return str_replace( $file_name, $encoded_file_name, $file );
1809
  }
1810
 
1811
  /**
1812
- * Normalize file path
1813
  *
1814
- * @param string $path
1815
- * @param null|int $attachment_id
1816
  *
1817
- * @return string mixed
1818
  */
1819
- public function normalize_file_path( $path, $attachment_id = null ) {
1820
- $url = parse_url( $path );
1821
 
1822
- if ( isset( $url['scheme'] ) ) {
1823
- $path = str_replace( $url['scheme'] . '://', '', $path );
1824
- } else {
1825
- $path = ltrim( $path, '/' );
1826
 
1827
- if ( ! is_null( $attachment_id ) ) {
1828
- // Attempt to remove bucket from path using amazonS3_info key
1829
- $s3info = $this->get_attachment_s3_info( $attachment_id );
1830
- $bucket = $s3info['bucket'];
1831
- } else {
1832
- // Attempt to remove bucket from path using tantan key
1833
- $bucket = $this->get_setting( 'bucket' );
1834
- }
1835
 
1836
- $preg = '/^' . preg_quote( $bucket ) . '/';
1837
- $path = preg_replace( $preg, '', $path );
 
1838
  }
1839
 
1840
- return '/' . ltrim( $path, '/' );
 
 
1841
  }
1842
 
1843
  /**
@@ -2783,6 +3017,7 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
2783
  if ( $acl == self::DEFAULT_ACL ) {
2784
  unset( $s3object['acl'] );
2785
  }
 
2786
  update_post_meta( $post_id, 'amazonS3_info', $s3object );
2787
  } catch ( Exception $e ) {
2788
  $msg = 'Error setting ACL to ' . $acl . ' for ' . $s3object['key'] . ': ' . $e->getMessage();
@@ -3132,7 +3367,10 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
3132
  $output .= "\r\n";
3133
  $output .= "\r\n";
3134
 
3135
- $output .= 'URL Preview: ';
 
 
 
3136
  $output .= $this->get_url_preview( $escape );
3137
  $output .= "\r\n";
3138
  $output .= "\r\n";
@@ -3433,13 +3671,13 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
3433
  }
3434
 
3435
  // Original file
3436
- $paths[] = $original_file;
3437
 
3438
  // Sizes
3439
  if ( isset( $meta['sizes'] ) ) {
3440
- foreach ( $meta['sizes'] as $size ) {
3441
- if ( isset( $size['file'] ) ) {
3442
- $paths[] = str_replace( $file_name, $size['file'], $file_path );
3443
  }
3444
  }
3445
  }
@@ -3578,7 +3816,7 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
3578
  * true - Attachments only uploaded to S3
3579
  * false - Attachments not uploaded to S3
3580
  *
3581
- * @return null|string
3582
  */
3583
  public function count_attachments( $prefix, $uploaded_to_s3 = null ) {
3584
  global $wpdb;
@@ -3599,7 +3837,7 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
3599
 
3600
  $sql .= ' ' . $where;
3601
 
3602
- return $wpdb->get_var( $sql );
3603
  }
3604
 
3605
  /**
@@ -3737,11 +3975,12 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
3737
  * @param string $variable
3738
  * @param int $type
3739
  * @param int $filter
 
3740
  *
3741
  * @return mixed
3742
  */
3743
- public function filter_input( $variable, $type = INPUT_GET, $filter = FILTER_DEFAULT ) {
3744
- return filter_input( $type, $variable, $filter );
3745
  }
3746
 
3747
  /**
@@ -3790,31 +4029,6 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
3790
  return $domain;
3791
  }
3792
 
3793
- /**
3794
- * Display a notice if using the retina hidpi setting when
3795
- * we removed support for it in 1.1.
3796
- */
3797
- protected function maybe_display_deprecated_retina_notice() {
3798
- if ( ! $this->get_setting( 'hidpi-images' ) ) {
3799
- // Not using setting, abort
3800
- return;
3801
- }
3802
-
3803
- $notice_args = array(
3804
- 'type' => 'notice-info',
3805
- 'only_show_to_user' => false,
3806
- 'flash' => false,
3807
- );
3808
-
3809
- $doc_url = 'https://deliciousbrains.com/wp-offload-s3/doc/copy-hidpi-2x-images-support/';
3810
- $doc_link = $this->dbrains_link( $doc_url, __( 'this doc' ) );
3811
-
3812
- $message = sprintf( '<strong>%s</strong> &mdash; ', __( 'WP Offload S3 Feature Removed', 'amazon-s3-and-cloudfront' ) );
3813
- $message .= sprintf( __( 'The "Copy HiDPI (@2x) Images" feature has been removed as of version 1.1 of WP Offload S3. It looks like you had this feature turned on. Please see %s for why we removed this feature and how you can continue copying @2x images to S3.', 'amazon-s3-and-cloudfront' ), $doc_link );
3814
-
3815
- $this->notices->add_notice( $message, $notice_args );
3816
- }
3817
-
3818
  /**
3819
  * Display a notice if using setting to force HTTP as url scheme, removed in 1.3.
3820
  */
@@ -3856,4 +4070,268 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
3856
 
3857
  return $path;
3858
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3859
  }
42
  */
43
  protected $default_tab = '';
44
 
45
+ /**
46
+ * @var AS3CF_Local_To_S3
47
+ */
48
+ public $filter_local;
49
+
50
+ /**
51
+ * @var AS3CF_S3_To_Local
52
+ */
53
+ public $filter_s3;
54
+
55
  /**
56
  * @var AS3CF_Notices
57
  */
67
  */
68
  protected static $buckets_check = array();
69
 
 
 
 
 
 
70
  /**
71
  * @var AS3CF_Plugin_Compatibility
72
  */
80
  const SETTINGS_KEY = 'tantan_wordpress_s3';
81
  const SETTINGS_CONSTANT = 'WPOS3_SETTINGS';
82
 
83
+ const LATEST_UPGRADE_ROUTINE = 5;
84
+
85
  /**
86
  * @param string $plugin_file_path
87
  * @param Amazon_Web_Services $aws
93
  parent::__construct( $plugin_file_path );
94
 
95
  $this->aws = $aws;
96
+ $this->notices = AS3CF_Notices::get_instance( $this );
97
 
98
  $this->init( $plugin_file_path );
99
  }
111
  new AS3CF_Upgrade_Region_Meta( $this );
112
  new AS3CF_Upgrade_File_Sizes( $this );
113
  new AS3CF_Upgrade_Meta_WP_Error( $this );
114
+ new AS3CF_Upgrade_Content_Replace_URLs( $this );
115
+ new AS3CF_Upgrade_EDD_Replace_URLs( $this );
116
 
117
  // Plugin setup
118
  add_action( 'aws_admin_menu', array( $this, 'admin_menu' ) );
121
  // display a notice when either lite or pro is automatically deactivated
122
  add_action( 'pre_current_active_plugins', array( $this, 'plugin_deactivated_notice' ) );
123
 
124
+ // Attachment screens/modals
125
+ add_action( 'load-upload.php', array( $this, 'load_media_assets' ), 11 );
126
+ add_action( 'admin_enqueue_scripts', array( $this, 'load_attachment_assets' ), 11 );
127
+ add_action( 'add_meta_boxes', array( $this, 'attachment_s3_meta_box' ) );
128
+
129
  // UI AJAX
130
  add_action( 'wp_ajax_as3cf-get-buckets', array( $this, 'ajax_get_buckets' ) );
131
  add_action( 'wp_ajax_as3cf-save-bucket', array( $this, 'ajax_save_bucket' ) );
132
  add_action( 'wp_ajax_as3cf-create-bucket', array( $this, 'ajax_create_bucket' ) );
133
  add_action( 'wp_ajax_as3cf-manual-save-bucket', array( $this, 'ajax_save_bucket' ) );
134
  add_action( 'wp_ajax_as3cf-get-url-preview', array( $this, 'ajax_get_url_preview' ) );
135
+ add_action( 'wp_ajax_as3cf_get_attachment_s3_details', array( $this, 'ajax_get_attachment_s3_details' ) );
136
  add_action( 'wp_ajax_as3cf-get-diagnostic-info', array( $this, 'ajax_get_diagnostic_info' ) );
137
 
138
  // Rewriting URLs, doesn't depend on plugin being setup
149
  add_filter( 'delete_attachment', array( $this, 'delete_attachment' ), 20 );
150
  add_filter( 'update_attached_file', array( $this, 'update_attached_file' ), 100, 2 );
151
 
152
+ // Content filtering
153
+ $this->filter_local = new AS3CF_Local_To_S3( $this );
154
+ $this->filter_s3 = new AS3CF_S3_To_Local( $this );
155
+
156
  // include compatibility code for other plugins
157
  $this->plugin_compat = new AS3CF_Plugin_Compatibility( $this );
158
 
247
  return '1';
248
  }
249
 
250
+ // Don't run upgrade routines on fresh install
251
+ if ( 'post_meta_version' === $key && ! isset( $settings['post_meta_version'] ) ) {
252
+ $routine = self::LATEST_UPGRADE_ROUTINE;
253
+
254
+ $this->set_setting( 'post_meta_version', $routine );
255
+ $this->save_settings();
256
+
257
+ return $routine;
258
+ }
259
+
260
  // Turn on object versioning by default
261
  if ( 'object-versioning' == $key && ! isset( $settings['object-versioning'] ) ) {
262
  return '1';
617
  }
618
  }
619
 
620
+ /**
621
+ * Get local URL preview.
622
+ *
623
+ * @param bool $escape
624
+ * @param string $suffix
625
+ *
626
+ * @return string
627
+ */
628
+ protected function get_local_url_preview( $escape = true, $suffix = 'photo.jpg' ) {
629
+ $uploads = wp_upload_dir();
630
+ $url = trailingslashit( $uploads['url'] ) . $suffix;
631
+
632
+ // Replace hyphens with non breaking hyphens for formatting
633
+ if ( $escape ) {
634
+ $url = str_replace( '-', '&#8209;', $url );
635
+ }
636
+
637
+ return $url;
638
+ }
639
+
640
  /**
641
  * Generate a preview of the URL of files uploaded to S3
642
  *
658
 
659
  $url = $scheme . '://' . $domain . '/' . $path . $suffix;
660
 
661
+ // Replace hyphens with non breaking hyphens for formatting
662
  if ( $escape ) {
663
  $url = str_replace( '-', '&#8209;', $url );
664
  }
742
  * to cope with possible different regions
743
  */
744
  function remove_attachment_files_from_s3( $post_id, $s3object, $remove_backup_sizes = true, $log_error = false, $return_on_error = false, $force_new_s3_client = false ) {
745
+ $prefix = $this->normalize_object_prefix( $s3object['key'] );
746
  $bucket = $s3object['bucket'];
747
  $region = $this->get_s3object_region( $s3object );
748
  $paths = $this->get_attachment_file_paths( $post_id, false, false, $remove_backup_sizes );
884
  $acl = $old_s3object['acl'];
885
  }
886
  // use existing prefix
887
+ $prefix = $this->normalize_object_prefix( $old_s3object['key'] );
 
888
  // use existing bucket
889
  $bucket = $old_s3object['bucket'];
890
  // get existing region
951
  $files_to_remove = array();
952
 
953
  if ( file_exists( $file_path ) ) {
 
954
  try {
955
  $s3client->putObject( $args );
956
+ $files_to_remove[] = $file_path;
957
  } catch ( Exception $e ) {
958
  $error_msg = sprintf( __( 'Error uploading %s to S3: %s', 'amazon-s3-and-cloudfront' ), $file_path, $e->getMessage() );
959
 
987
  }
988
  }
989
 
990
+ $s3object_sizes = array();
991
+
992
+ foreach ( $file_paths as $size => $file_path ) {
993
  if ( ! in_array( $file_path, $files_to_remove ) ) {
994
+ $acl = apply_filters( 'as3cf_upload_acl_sizes', self::DEFAULT_ACL, $size, $post_id, $data );
995
+
996
  $additional_images[] = array(
997
  'Key' => $prefix . basename( $file_path ),
998
  'SourceFile' => $file_path,
999
+ 'ACL' => $acl,
1000
  );
1001
 
1002
+ if ( self::DEFAULT_ACL !== $acl ) {
1003
+ $s3object_sizes[ $size ]['acl'] = $acl;
1004
+ }
1005
 
1006
  if ( $remove_local_files_setting ) {
1007
  // Record the file size for the additional image
1015
 
1016
  foreach ( $additional_images as $image ) {
1017
  try {
1018
+ $args = array_merge( $args, $image );
 
1019
  $s3client->putObject( $args );
1020
+ $files_to_remove[] = $image['SourceFile'];
1021
  } catch ( Exception $e ) {
1022
  AS3CF_Error::log( 'Error uploading ' . $args['SourceFile'] . ' to S3: ' . $e->getMessage() );
1023
  }
1029
  $files_to_remove = apply_filters( 'as3cf_upload_attachment_local_files_to_remove', $files_to_remove, $post_id, $file_path );
1030
  // Remove duplicates
1031
  $files_to_remove = array_unique( $files_to_remove );
 
1032
  // Delete the files
1033
  $this->remove_local_files( $files_to_remove );
1034
  }
1054
  }
1055
  }
1056
 
1057
+ if ( ! empty( $s3object_sizes ) ) {
1058
+ // Additional image sizes have custom ACLs, update meta
1059
+ $s3object['sizes'] = $s3object_sizes;
1060
+ update_post_meta( $post_id, 'amazonS3_info', $s3object );
1061
+ }
1062
+
1063
  do_action( 'wpos3_post_upload_attachment', $post_id, $s3object );
1064
 
1065
  if ( ! is_null( $return_metadata ) ) {
1396
  return $filename;
1397
  }
1398
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1399
  /**
1400
  * Get attachment s3 info
1401
  *
1403
  *
1404
  * @return mixed
1405
  */
1406
+ public function get_attachment_s3_info( $post_id ) {
1407
  return apply_filters( 'as3cf_get_attachment_s3_info', get_post_meta( $post_id, 'amazonS3_info', true ), $post_id );
1408
  }
1409
 
1609
  *
1610
  * @return bool|mixed|void|WP_Error
1611
  */
1612
+ public function get_attachment_url( $post_id, $expires = null, $size = null, $meta = null, $headers = array(), $skip_rewrite_check = false ) {
1613
  if ( ! ( $s3object = $this->is_attachment_served_by_s3( $post_id, $skip_rewrite_check ) ) ) {
1614
  return false;
1615
  }
1616
 
1617
+ $url = $this->get_attachment_s3_url( $post_id, $s3object, $expires, $size, $meta, $headers );
1618
+
1619
+ return apply_filters( 'as3cf_wp_get_attachment_url', $url, $post_id );
1620
  }
1621
 
1622
  /**
1623
+ * Get attachment local URL.
1624
+ *
1625
+ * This is a direct copy of wp_get_attachment_url() from /wp-includes/post.php
1626
+ * as we filter the URL in AS3CF and can't remove this filter using the current implementation
1627
+ * of globals for class instances.
1628
+ *
1629
+ * @param int $post_id
1630
+ *
1631
+ * @return string|false Attachment URL, otherwise false.
1632
+ */
1633
+ public function get_attachment_local_url( $post_id ) {
1634
+ $url = '';
1635
+
1636
+ // Get attached file.
1637
+ if ( $file = get_post_meta( $post_id, '_wp_attached_file', true ) ) {
1638
+ // Get upload directory.
1639
+ if ( ( $uploads = wp_upload_dir() ) && false === $uploads['error'] ) {
1640
+ // Check that the upload base exists in the file location.
1641
+ if ( 0 === strpos( $file, $uploads['basedir'] ) ) {
1642
+ // Replace file location with url location.
1643
+ $url = str_replace( $uploads['basedir'], $uploads['baseurl'], $file );
1644
+ } elseif ( false !== strpos( $file, 'wp-content/uploads' ) ) {
1645
+ $url = $uploads['baseurl'] . substr( $file, strpos( $file, 'wp-content/uploads' ) + 18 );
1646
+ } else {
1647
+ // It's a newly-uploaded file, therefore $file is relative to the basedir.
1648
+ $url = $uploads['baseurl'] . "/$file";
1649
+ }
1650
+ }
1651
+ }
1652
+
1653
+ if ( empty( $url ) ) {
1654
+ return false;
1655
+ }
1656
+
1657
+ // Set correct domain on multisite subdomain installs
1658
+ if ( is_multisite() ) {
1659
+ $siteurl = trailingslashit( get_option( 'siteurl' ) );
1660
+ $network_siteurl = trailingslashit( network_site_url() );
1661
+
1662
+ if ( 0 !== strpos( $url, $siteurl ) ) {
1663
+ // URL already using site URL, no replacement needed
1664
+ $url = str_replace( $network_siteurl, $siteurl, $url );
1665
+ }
1666
+ }
1667
+
1668
+ return $url;
1669
+ }
1670
+
1671
+ /**
1672
+ * Get attachment local URL size.
1673
  *
1674
  * @param int $post_id
1675
+ * @param string|null $size
 
 
 
 
1676
  *
1677
+ * @return false|string
1678
+ */
1679
+ public function get_attachment_local_url_size( $post_id, $size = null ) {
1680
+ $url = $this->get_attachment_local_url( $post_id );
1681
+
1682
+ if ( empty( $size ) ) {
1683
+ return $url;
1684
+ }
1685
+
1686
+ $meta = get_post_meta( $post_id, '_wp_attachment_metadata', true );
1687
+
1688
+ if ( empty( $meta['sizes'][ $size ]['file'] ) ) {
1689
+ // No alternative sizes available, return
1690
+ return $url;
1691
+ }
1692
+
1693
+ return str_replace( wp_basename( $url ), $meta['sizes'][ $size ]['file'], $url );
1694
+ }
1695
+
1696
+ /**
1697
+ * Get the S3 URL for an attachment
1698
+ *
1699
+ * @param int $post_id
1700
+ * @param array $s3object
1701
+ * @param null|int $expires
1702
+ * @param null|string|array $size
1703
+ * @param null|array $meta
1704
+ * @param array $headers
1705
+ *
1706
+ * @return mixed|WP_Error
1707
  */
1708
  public function get_attachment_s3_url( $post_id, $s3object, $expires = null, $size = null, $meta = null, $headers = array() ) {
1709
  $scheme = $this->get_s3_url_scheme();
1716
  $region = '';
1717
  }
1718
 
1719
+ $size = $this->maybe_convert_size_to_string( $post_id, $size );
 
 
 
1720
 
1721
+ // Force use of secured URL when ACL has been set to private
1722
+ if ( is_null( $expires ) ) {
1723
+ if ( is_null( $size ) && isset( $s3object['acl'] ) && self::PRIVATE_ACL === $s3object['acl'] ) {
1724
+ // Full size URL private
1725
+ $expires = self::DEFAULT_EXPIRES;
1726
+ }
1727
+
1728
+ if ( ! is_null( $size ) && isset( $s3object['sizes'][ $size ]['acl'] ) && self::PRIVATE_ACL === $s3object['sizes'][ $size ]['acl'] ) {
1729
+ // Alternative size URL private
1730
+ $expires = self::DEFAULT_EXPIRES;
1731
+ }
1732
+ }
1733
 
1734
  if ( ! is_null( $size ) ) {
1735
  if ( is_null( $meta ) ) {
1740
  return $meta;
1741
  }
1742
 
1743
+ if ( ! empty( $meta ) && isset( $meta['sizes'][ $size ]['file'] ) ) {
1744
  $size_prefix = dirname( $s3object['key'] );
1745
  $size_file_prefix = ( '.' === $size_prefix ) ? '' : $size_prefix . '/';
1746
 
1761
 
1762
  $s3object['key'] = $this->maybe_update_cloudfront_path( $s3object['key'] );
1763
 
1764
+ $domain_bucket = $this->get_s3_url_domain( $s3object['bucket'], $region, $expires );
1765
+ $file = $this->encode_filename_in_path( $s3object['key'] );
1766
+ $url = $scheme . '://' . $domain_bucket . '/' . $file;
1767
 
1768
  return apply_filters( 'as3cf_get_attachment_url', $url, $s3object, $post_id, $expires, $headers );
1769
  }
1770
 
1771
+ /**
1772
+ * Get attachment url
1773
+ *
1774
+ * @param string $url
1775
+ * @param int $post_id
1776
+ *
1777
+ * @return bool|mixed|void|WP_Error
1778
+ */
1779
+ public function wp_get_attachment_url( $url, $post_id ) {
1780
+ $new_url = $this->get_attachment_url( $post_id );
1781
+
1782
+ if ( false === $new_url ) {
1783
+ return $url;
1784
+ }
1785
+
1786
+ $new_url = apply_filters( 'wps3_get_attachment_url', $new_url, $post_id, $this ); // Old naming convention, will be deprecated soon
1787
+ $new_url = apply_filters( 'as3cf_wp_get_attachment_url', $new_url, $post_id );
1788
+
1789
+ return $new_url;
1790
+ }
1791
+
1792
  /**
1793
  * Maybe encode attachment URLs when retrieving the image tag
1794
  *
1802
  * @return string
1803
  */
1804
  public function maybe_encode_get_image_tag( $html, $id, $alt, $title, $align, $size ) {
1805
+ if ( ! ( $s3object = $this->is_attachment_served_by_s3( $id ) ) ) {
1806
+ // Not served by S3, return
1807
  return $html;
1808
  }
1809
 
1815
  }
1816
 
1817
  $img_src = $matches[1];
1818
+ $new_img_src = $this->maybe_sign_intermediate_size( $img_src, $id, $size, $s3object );
1819
+ $new_img_src = $this->encode_filename_in_path( $new_img_src );
1820
 
1821
+ return str_replace( $img_src, $new_img_src, $html );
1822
  }
1823
 
1824
  /**
1832
  * @return array
1833
  */
1834
  public function maybe_encode_wp_get_attachment_image_src( $image, $attachment_id, $size, $icon ) {
1835
+ if ( ! ( $s3object = $this->is_attachment_served_by_s3( $attachment_id ) ) ) {
1836
+ // Not served by S3, return
1837
  return $image;
1838
  }
1839
 
1840
  if ( isset( $image[0] ) ) {
1841
+ $url = $this->maybe_sign_intermediate_size( $image[0], $attachment_id, $size, $s3object );
1842
+ $url = $this->encode_filename_in_path( $url );
1843
+
1844
+ $image[0] = $url;
1845
  }
1846
 
1847
  return $image;
1857
  * @return array
1858
  */
1859
  public function maybe_encode_wp_prepare_attachment_for_js( $response, $attachment, $meta ) {
1860
+ if ( ! ( $s3object = $this->is_attachment_served_by_s3( $attachment->ID ) ) ) {
1861
+ // Not served by S3, return
1862
  return $response;
1863
  }
1864
 
1865
  if ( isset( $response['url'] ) ) {
1866
+ $response['url'] = $this->encode_filename_in_path( $response['url'] );
1867
  }
1868
 
1869
  if ( isset( $response['sizes'] ) && is_array( $response['sizes'] ) ) {
1870
+ foreach ( $response['sizes'] as $size => $value ) {
1871
+ $url = $this->maybe_sign_intermediate_size( $value['url'], $attachment->ID, $size, $s3object );
1872
+ $url = $this->encode_filename_in_path( $url );
1873
+
1874
+ $response['sizes'][ $size ]['url'] = $url;
1875
  }
1876
  }
1877
 
1888
  * @return array
1889
  */
1890
  public function maybe_encode_image_get_intermediate_size( $data, $post_id, $size ) {
1891
+ if ( ! ( $s3object = $this->is_attachment_served_by_s3( $post_id ) ) ) {
1892
+ // Not served by S3, return
1893
  return $data;
1894
  }
1895
 
1896
  if ( isset( $data['url'] ) ) {
1897
+ $url = $this->maybe_sign_intermediate_size( $data['url'], $post_id, $size, $s3object );
1898
+ $url = $this->encode_filename_in_path( $url );
1899
+
1900
+ $data['url'] = $url;
1901
  }
1902
 
1903
  return $data;
1904
  }
1905
 
1906
+ /**
1907
+ * Sign intermediate size.
1908
+ *
1909
+ * @param string $url
1910
+ * @param int $attachment_id
1911
+ * @param string|array $size
1912
+ * @param bool|array $s3object
1913
+ *
1914
+ * @return mixed|WP_Error
1915
+ */
1916
+ protected function maybe_sign_intermediate_size( $url, $attachment_id, $size, $s3object = false ) {
1917
+ if ( ! $s3object ) {
1918
+ $s3object = $this->get_attachment_s3_info( $attachment_id );
1919
+ }
1920
+
1921
+ $size = $this->maybe_convert_size_to_string( $attachment_id, $size );
1922
+
1923
+ if ( isset( $s3object['sizes'][ $size ] ) ) {
1924
+ // Private file, add AWS signature if required
1925
+ return $this->get_attachment_s3_url( $attachment_id, $s3object, null, $size );
1926
+ }
1927
+
1928
+ return $url;
1929
+ }
1930
+
1931
+ /**
1932
+ * Convert dimensions to size
1933
+ *
1934
+ * @param int $attachment_id
1935
+ * @param array $dimensions
1936
+ *
1937
+ * @return null|string
1938
+ */
1939
+ protected function convert_dimensions_to_size_name( $attachment_id, $dimensions ) {
1940
+ $w = $dimensions[0];
1941
+ $h = $dimensions[1];
1942
+ $original_aspect_ratio = $w / $h;
1943
+ $meta = wp_get_attachment_metadata( $attachment_id );
1944
+
1945
+ if ( ! isset( $meta['sizes'] ) || empty( $meta['sizes'] ) ) {
1946
+ return null;
1947
+ }
1948
+
1949
+ $sizes = $meta['sizes'];
1950
+ uasort( $sizes, function( $a, $b ) {
1951
+ // Order by image area
1952
+ return ( $a['width'] * $a['height'] ) - ( $b['width'] * $b['height'] );
1953
+ } );
1954
+
1955
+ $nearest_matches = array();
1956
+
1957
+ foreach ( $sizes as $size => $value ) {
1958
+ if ( $w > $value['width'] || $h > $value['height'] ) {
1959
+ continue;
1960
+ }
1961
+
1962
+ $aspect_ratio = $value['width'] / $value['height'];
1963
+
1964
+ if ( $aspect_ratio === $original_aspect_ratio ) {
1965
+ return $size;
1966
+ }
1967
+
1968
+ $nearest_matches[] = $size;
1969
+ }
1970
+
1971
+ // Return nearest match
1972
+ if ( ! empty( $nearest_matches ) ) {
1973
+ return $nearest_matches[0];
1974
+ }
1975
+
1976
+ return null;
1977
+ }
1978
+
1979
+ /**
1980
+ * Maybe convert size to string
1981
+ *
1982
+ * @param int $attachment_id
1983
+ * @param mixed $size
1984
+ *
1985
+ * @return null|string
1986
+ */
1987
+ protected function maybe_convert_size_to_string( $attachment_id, $size ) {
1988
+ if ( is_array( $size ) ) {
1989
+ return $this->convert_dimensions_to_size_name( $attachment_id, $size );
1990
+ }
1991
+
1992
+ return $size;
1993
+ }
1994
+
1995
  /**
1996
  * Is attachment served by S3.
1997
  *
2018
  * Encode file names according to RFC 3986 when generating urls
2019
  * As per Amazon https://forums.aws.amazon.com/thread.jspa?threadID=55746#jive-message-244233
2020
  *
2021
+ * @param string $file
 
2022
  *
2023
+ * @return string Encoded filename
2024
  */
2025
+ public function encode_filename_in_path( $file ) {
2026
  $url = parse_url( $file );
2027
 
2028
  if ( ! isset( $url['path'] ) ) {
2030
  return $file;
2031
  }
2032
 
2033
+ $file_name = basename( $file );
2034
+
2035
+ if ( false !== strpos( $file_name, '%' ) ) {
2036
+ // File name already encoded, return original
2037
  return $file;
2038
  }
2039
 
 
 
 
2040
  $encoded_file_name = rawurlencode( $file_name );
 
2041
 
2042
  if ( $file_name === $encoded_file_name ) {
2043
  // File name doesn't need encoding, return original
2044
  return $file;
2045
  }
2046
 
 
 
 
 
 
 
2047
  return str_replace( $file_name, $encoded_file_name, $file );
2048
  }
2049
 
2050
  /**
2051
+ * Decode file name.
2052
  *
2053
+ * @param string $file
 
2054
  *
2055
+ * @return string
2056
  */
2057
+ public function decode_filename_in_path( $file ) {
2058
+ $url = parse_url( $file );
2059
 
2060
+ if ( ! isset( $url['path'] ) ) {
2061
+ // Can't determine path, return original
2062
+ return $file;
2063
+ }
2064
 
2065
+ $file_name = basename( $url['path'] );
 
 
 
 
 
 
 
2066
 
2067
+ if ( false === strpos( $file_name, '%' ) ) {
2068
+ // File name not encoded, return original
2069
+ return $file;
2070
  }
2071
 
2072
+ $decoded_file_name = rawurldecode( $file_name );
2073
+
2074
+ return str_replace( $file_name, $decoded_file_name, $file );
2075
  }
2076
 
2077
  /**
3017
  if ( $acl == self::DEFAULT_ACL ) {
3018
  unset( $s3object['acl'] );
3019
  }
3020
+
3021
  update_post_meta( $post_id, 'amazonS3_info', $s3object );
3022
  } catch ( Exception $e ) {
3023
  $msg = 'Error setting ACL to ' . $acl . ' for ' . $s3object['key'] . ': ' . $e->getMessage();
3367
  $output .= "\r\n";
3368
  $output .= "\r\n";
3369
 
3370
+ $output .= "Local URL:\r\n";
3371
+ $output .= $this->get_local_url_preview( $escape );
3372
+ $output .= "\r\n";
3373
+ $output .= "S3 URL:\r\n";
3374
  $output .= $this->get_url_preview( $escape );
3375
  $output .= "\r\n";
3376
  $output .= "\r\n";
3671
  }
3672
 
3673
  // Original file
3674
+ $paths['full'] = $original_file;
3675
 
3676
  // Sizes
3677
  if ( isset( $meta['sizes'] ) ) {
3678
+ foreach ( $meta['sizes'] as $size => $file ) {
3679
+ if ( isset( $file['file'] ) ) {
3680
+ $paths[ $size ] = str_replace( $file_name, $file['file'], $file_path );
3681
  }
3682
  }
3683
  }
3816
  * true - Attachments only uploaded to S3
3817
  * false - Attachments not uploaded to S3
3818
  *
3819
+ * @return int
3820
  */
3821
  public function count_attachments( $prefix, $uploaded_to_s3 = null ) {
3822
  global $wpdb;
3837
 
3838
  $sql .= ' ' . $where;
3839
 
3840
+ return (int) $wpdb->get_var( $sql );
3841
  }
3842
 
3843
  /**
3975
  * @param string $variable
3976
  * @param int $type
3977
  * @param int $filter
3978
+ * @param mixed $options
3979
  *
3980
  * @return mixed
3981
  */
3982
+ public function filter_input( $variable, $type = INPUT_GET, $filter = FILTER_DEFAULT, $options = array() ) {
3983
+ return filter_input( $type, $variable, $filter, $options );
3984
  }
3985
 
3986
  /**
4029
  return $domain;
4030
  }
4031
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4032
  /**
4033
  * Display a notice if using setting to force HTTP as url scheme, removed in 1.3.
4034
  */
4070
 
4071
  return $path;
4072
  }
4073
+
4074
+ /**
4075
+ * Add the S3 meta box to the attachment screen
4076
+ */
4077
+ public function attachment_s3_meta_box() {
4078
+ add_meta_box( 's3-actions', __( 'Amazon S3', 'amazon-s3-and-cloudfront' ), array( $this, 'attachment_s3_actions_meta_box' ), 'attachment', 'side', 'core' );
4079
+ }
4080
+
4081
+ /**
4082
+ * Check we can do the media actions
4083
+ *
4084
+ * @return bool
4085
+ */
4086
+ public function verify_media_actions() {
4087
+ return false;
4088
+ }
4089
+
4090
+ /**
4091
+ * Render the S3 attachment meta box
4092
+ */
4093
+ public function attachment_s3_actions_meta_box() {
4094
+ global $post;
4095
+ $file = get_attached_file( $post->ID, true );
4096
+
4097
+ $args = array(
4098
+ 's3object' => $this->get_formatted_s3_info( $post->ID ),
4099
+ 'post' => $post,
4100
+ 'local_file_exists' => file_exists( $file ),
4101
+ 'user_can_perform_actions' => $this->verify_media_actions(),
4102
+ 'sendback' => 'post.php?post=' . $post->ID . '&action=edit',
4103
+ );
4104
+
4105
+ $this->render_view( 'attachment-metabox', $args );
4106
+ }
4107
+
4108
+ /**
4109
+ * Get ACL value string.
4110
+ *
4111
+ * @param array $acl
4112
+ *
4113
+ * @return string
4114
+ */
4115
+ protected function get_acl_value_string( $acl ) {
4116
+ return $acl['name'];
4117
+ }
4118
+
4119
+ /**
4120
+ * Return a formatted S3 info with display friendly defaults
4121
+ *
4122
+ * @param int $id
4123
+ * @param array|null $s3object
4124
+ *
4125
+ * @return array
4126
+ */
4127
+ public function get_formatted_s3_info( $id, $s3object = null ) {
4128
+ if ( is_null( $s3object ) ) {
4129
+ if ( ! ( $s3object = $this->get_attachment_s3_info( $id ) ) ) {
4130
+ return false;
4131
+ }
4132
+ }
4133
+
4134
+ $s3object['url'] = $this->get_attachment_s3_url( $id, $s3object );
4135
+
4136
+ $acl = ( isset( $s3object['acl'] ) ) ? $s3object['acl'] : self::DEFAULT_ACL;
4137
+ $acl_info = array(
4138
+ 'acl' => $acl,
4139
+ 'name' => $this->get_acl_display_name( $acl ),
4140
+ 'title' => $this->get_media_action_strings( 'change_to_private' ),
4141
+ );
4142
+
4143
+ if ( self::PRIVATE_ACL === $acl ) {
4144
+ $acl_info['title'] = $this->get_media_action_strings( 'change_to_public' );
4145
+ }
4146
+
4147
+ $s3object['acl'] = $acl_info;
4148
+
4149
+ $regions = $this->get_aws_regions();
4150
+
4151
+ if ( isset( $s3object['region'] ) && '' == $s3object['region'] ) {
4152
+ $s3object['region'] = self::DEFAULT_REGION;
4153
+ }
4154
+
4155
+ if ( isset( $regions[ $s3object['region'] ] ) ) {
4156
+ $s3object['region'] = $regions[ $s3object['region'] ];
4157
+ }
4158
+
4159
+ return $s3object;
4160
+ }
4161
+
4162
+ /**
4163
+ * Get all strings or a specific string used for the media actions
4164
+ *
4165
+ * @param null|string $string
4166
+ *
4167
+ * @return array|string
4168
+ */
4169
+ public function get_media_action_strings( $string = null ) {
4170
+ $strings = apply_filters( 'as3cf_media_action_strings', array(
4171
+ 'amazon_s3' => __( 'Amazon S3', 'amazon-s3-and-cloudfront' ),
4172
+ 'bucket' => _x( 'Bucket', 'Amazon S3 bucket', 'amazon-s3-and-cloudfront' ),
4173
+ 'key' => _x( 'Path', 'Path to file on Amazon S3', 'amazon-s3-and-cloudfront' ),
4174
+ 'region' => _x( 'Region', 'Location of Amazon S3 bucket', 'amazon-s3-and-cloudfront' ),
4175
+ 'acl' => _x( 'Access', 'Access control list of the file on Amazon S3', 'amazon-s3-and-cloudfront' ),
4176
+ 'url' => __( 'URL', 'amazon-s3-and-cloudfront' ),
4177
+ ) );
4178
+
4179
+ if ( ! is_null( $string ) ) {
4180
+ return isset( $strings[ $string ] ) ? $strings[ $string ] : '';
4181
+ }
4182
+
4183
+ return $strings;
4184
+ }
4185
+
4186
+ /**
4187
+ * Load media assets.
4188
+ */
4189
+ public function load_media_assets() {
4190
+ $version = $this->get_asset_version();
4191
+ $suffix = $this->get_asset_suffix();
4192
+
4193
+ $src = plugins_url( 'assets/css/media.css', $this->plugin_file_path );
4194
+ wp_enqueue_style( 'as3cf-media-styles', $src, array( 'as3cf-modal' ), $version );
4195
+
4196
+ $src = plugins_url( 'assets/js/media' . $suffix . '.js', $this->plugin_file_path );
4197
+ wp_enqueue_script( 'as3cf-media-script', $src, array( 'jquery', 'media-views', 'media-grid', 'wp-util' ), $version, true );
4198
+
4199
+ wp_localize_script( 'as3cf-media-script',
4200
+ 'as3cf_media',
4201
+ array(
4202
+ 'strings' => $this->get_media_action_strings(),
4203
+ 'nonces' => array(
4204
+ 'get_attachment_s3_details' => wp_create_nonce( 'get-attachment-s3-details' ),
4205
+ )
4206
+ )
4207
+ );
4208
+ }
4209
+
4210
+ /**
4211
+ * Handle retieving the S3 details for attachment modals.
4212
+ */
4213
+ public function ajax_get_attachment_s3_details() {
4214
+ if ( ! isset( $_POST['id'] ) ) {
4215
+ return;
4216
+ }
4217
+
4218
+ check_ajax_referer( 'get-attachment-s3-details', '_nonce' );
4219
+
4220
+ $id = intval( $_POST['id'] );
4221
+
4222
+ // get the actions available for the attachment
4223
+ $data = array(
4224
+ 'links' => $this->add_media_row_actions( array(), $id ),
4225
+ 's3object' => $this->get_formatted_s3_info( $id ),
4226
+ 'acl_toggle' => $this->verify_media_actions(),
4227
+ );
4228
+
4229
+ wp_send_json_success( $data );
4230
+ }
4231
+
4232
+ /**
4233
+ * Conditionally adds copy, remove and download S3 action links for an
4234
+ * attachment on the Media library list view
4235
+ *
4236
+ * @param array $actions
4237
+ * @param WP_Post|int $post
4238
+ *
4239
+ * @return array
4240
+ */
4241
+ function add_media_row_actions( $actions = array(), $post ) {
4242
+ return $actions;
4243
+ }
4244
+
4245
+ /**
4246
+ * Load the attachment assets only when editing an attachment
4247
+ *
4248
+ * @param $hook_suffix
4249
+ */
4250
+ public function load_attachment_assets( $hook_suffix ) {
4251
+ $version = $this->get_asset_version();
4252
+ $suffix = $this->get_asset_suffix();
4253
+
4254
+ global $post;
4255
+ if ( 'post.php' != $hook_suffix || 'attachment' != $post->post_type ) {
4256
+ return;
4257
+ }
4258
+
4259
+ $src = plugins_url( 'assets/css/attachment.css', $this->plugin_file_path );
4260
+ wp_enqueue_style( 'as3cf-pro-attachment-styles', $src, array( 'as3cf-modal' ), $version );
4261
+
4262
+ do_action( 'as3cf_load_attachment_assets', $version, $suffix );
4263
+ }
4264
+
4265
+ /**
4266
+ * Maybe remove query string from URL.
4267
+ *
4268
+ * @param string $url
4269
+ *
4270
+ * @return string
4271
+ */
4272
+ public function maybe_remove_query_string( $url ) {
4273
+ $parts = explode( '?', $url );
4274
+
4275
+ return reset( $parts );
4276
+ }
4277
+
4278
+ /**
4279
+ * Normalize object prefix.
4280
+ *
4281
+ * @param string $prefix
4282
+ *
4283
+ * @return string
4284
+ */
4285
+ protected function normalize_object_prefix( $prefix ) {
4286
+ $directory = dirname( $prefix );
4287
+
4288
+ return ( '.' === $directory ) ? '' : $directory . '/';
4289
+ }
4290
+
4291
+ /**
4292
+ * Remove scheme from URL.
4293
+ *
4294
+ * @param string $url
4295
+ *
4296
+ * @return string
4297
+ */
4298
+ public function remove_scheme( $url ) {
4299
+ return preg_replace( '/^(?:http|https):/', '', $url );
4300
+ }
4301
+
4302
+ /**
4303
+ * Remove size from filename (image[-100x100].jpeg).
4304
+ *
4305
+ * @param string $url
4306
+ * @param bool $remove_extension
4307
+ *
4308
+ * @return string
4309
+ */
4310
+ public function remove_size_from_filename( $url, $remove_extension = false ) {
4311
+ $url = preg_replace( '/^(\S+)-[0-9]{1,4}x[0-9]{1,4}(\.[a-zA-Z0-9\.]{2,})?/', '$1$2', $url );
4312
+
4313
+ if ( $remove_extension ) {
4314
+ $parts = pathinfo( $url );
4315
+ $url = str_replace( '.' . $parts['extension'], '', $url );
4316
+ }
4317
+
4318
+ return $url;
4319
+ }
4320
+
4321
+ /**
4322
+ * Update site option.
4323
+ *
4324
+ * @param string $option
4325
+ * @param mixed $value
4326
+ * @param bool $autoload
4327
+ *
4328
+ * @return bool
4329
+ */
4330
+ public function update_site_option( $option, $value, $autoload = true ) {
4331
+ if ( is_multisite() ) {
4332
+ return update_site_option( $option, $value );
4333
+ }
4334
+
4335
+ return update_option( $option, $value, $autoload );
4336
+ }
4337
  }
classes/as3cf-filter.php ADDED
@@ -0,0 +1,687 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ abstract class AS3CF_Filter {
4
+
5
+ /**
6
+ * @var Amazon_S3_And_CloudFront
7
+ */
8
+ protected $as3cf;
9
+
10
+ /**
11
+ * @var array
12
+ */
13
+ protected $query_cache = array();
14
+
15
+ /**
16
+ * Constructor
17
+ *
18
+ * @param Amazon_S3_And_CloudFront $as3cf
19
+ */
20
+ public function __construct( $as3cf ) {
21
+ $this->as3cf = $as3cf;
22
+
23
+ // Purge on attachment delete
24
+ add_action( 'delete_attachment', array( $this, 'purge_cache_on_attachment_delete' ) );
25
+
26
+ $this->init();
27
+ }
28
+
29
+ /**
30
+ * Filter EDD download files.
31
+ *
32
+ * @param array $value
33
+ *
34
+ * @return array
35
+ */
36
+ public function filter_edd_download_files( $value ) {
37
+ if ( ! $this->should_filter_content() ) {
38
+ // Not filtering content, return
39
+ return $value;
40
+ }
41
+
42
+ if ( empty( $value ) ) {
43
+ // Nothing to filter, return
44
+ return $value;
45
+ }
46
+
47
+ foreach ( $value as $key => $attachment ) {
48
+ $url = $this->get_url( $attachment['attachment_id'] );
49
+
50
+ if ( $url ) {
51
+ $value[ $key ]['file'] = $this->get_url( $attachment['attachment_id'] );
52
+ }
53
+ }
54
+
55
+ return $value;
56
+ }
57
+
58
+ /**
59
+ * Filter customizer image.
60
+ *
61
+ * @param string $value
62
+ * @param bool|string $old_value
63
+ *
64
+ * @return string
65
+ */
66
+ public function filter_customizer_image( $value, $old_value = false ) {
67
+ if ( empty( $value ) || is_a( $value, 'stdClass' ) ) {
68
+ return $value;
69
+ }
70
+
71
+ $cache = $this->get_option_cache();
72
+ $to_cache = array();
73
+ $value = $this->process_content( $value, $cache, $to_cache );
74
+
75
+ $this->maybe_update_option_cache( $to_cache );
76
+
77
+ return $value;
78
+ }
79
+
80
+ /**
81
+ * Filter header image data.
82
+ *
83
+ * @param stdClass $value
84
+ * @param bool|stdClass $old_value
85
+ *
86
+ * @return stdClass
87
+ */
88
+ public function filter_header_image_data( $value, $old_value = false ) {
89
+ $url = $this->get_url( $value->attachment_id );
90
+
91
+ if ( $url ) {
92
+ $value->url = $url;
93
+ $value->thumbnail_url = $url;
94
+ }
95
+
96
+ return $value;
97
+ }
98
+
99
+ /**
100
+ * Filter post.
101
+ *
102
+ * @param string $content
103
+ *
104
+ * @return string
105
+ */
106
+ public function filter_post( $content ) {
107
+ $cache = $this->get_post_cache();
108
+ $to_cache = array();
109
+ $content = $this->process_content( $content, $cache, $to_cache );
110
+
111
+ $this->maybe_update_post_cache( $to_cache );
112
+
113
+ return $content;
114
+ }
115
+
116
+ /**
117
+ * Process content.
118
+ *
119
+ * @param string $content
120
+ * @param array $cache
121
+ * @param array $to_cache
122
+ *
123
+ * @return mixed
124
+ */
125
+ protected function process_content( $content, $cache, &$to_cache ) {
126
+ if ( ! $this->should_filter_content() ) {
127
+ // Not filtering content, return
128
+ return $content;
129
+ }
130
+
131
+ $content = $this->pre_replace_content( $content );
132
+
133
+ // Find URLs from img src
134
+ $url_pairs = $this->get_urls_from_img_src( $content, $to_cache );
135
+ $content = $this->replace_urls( $content, $url_pairs );
136
+
137
+ // Find leftover URLs
138
+ $content = $this->find_urls_and_replace( $content, $cache, $to_cache );
139
+
140
+ // Perform post processing if required
141
+ $content = $this->post_process_content( $content );
142
+
143
+ return $content;
144
+ }
145
+
146
+ /**
147
+ * Find URLs and replace.
148
+ *
149
+ * @param string $value
150
+ * @param array $cache
151
+ * @param array $to_cache
152
+ *
153
+ * @return string
154
+ */
155
+ protected function find_urls_and_replace( $value, $cache, &$to_cache ) {
156
+ if ( ! $this->should_filter_content() ) {
157
+ // Not filtering content, return
158
+ return $value;
159
+ }
160
+
161
+ $url_pairs = $this->get_urls_from_content( $value, $cache, $to_cache );
162
+ $value = $this->replace_urls( $value, $url_pairs );
163
+
164
+ return $value;
165
+ }
166
+
167
+ /**
168
+ * Get URLs from img src.
169
+ *
170
+ * @param string $content
171
+ * @param array $to_cache
172
+ *
173
+ * @return array
174
+ */
175
+ protected function get_urls_from_img_src( $content, &$to_cache ) {
176
+ $url_pairs = array();
177
+
178
+ if ( ! preg_match_all( '/<img [^>]+>/', $content, $matches ) || ! isset( $matches[0] ) ) {
179
+ // No img tags found, return
180
+ return $url_pairs;
181
+ }
182
+
183
+ $matches = array_unique( $matches[0] );
184
+ $attachment_ids = array();
185
+
186
+ foreach ($matches as $image) {
187
+ if ( ! preg_match( '/src=\\\?["\']+([^"\'\\\]+)/', $image, $src ) || ! isset( $src[1] ) ) {
188
+ // Can't determine URL, skip
189
+ continue;
190
+ }
191
+
192
+ $url = $src[1];
193
+
194
+ if ( ! $this->url_needs_replacing( $url ) ) {
195
+ // URL already correct, skip
196
+ continue;
197
+ }
198
+
199
+ if ( ! preg_match( '/wp-image-([0-9]+)/i', $image, $class_id ) || ! isset( $class_id[1] ) ) {
200
+ // Can't determine ID from class, skip
201
+ continue;
202
+ }
203
+
204
+ $attachment_ids[ absint( $class_id[1] ) ] = $url;
205
+ }
206
+
207
+ if ( count( $attachment_ids ) > 1 ) {
208
+ /*
209
+ * Warm object cache for use with 'get_post_meta()'.
210
+ *
211
+ * To avoid making a database call for each image, a single query
212
+ * warms the object cache with the meta information for all images.
213
+ */
214
+ update_meta_cache( 'post', array_keys( $attachment_ids ) );
215
+ }
216
+
217
+ foreach ( $attachment_ids as $attachment_id => $url ) {
218
+ if ( ! $this->attachment_id_matches_src( $attachment_id, $url ) ) {
219
+ // Path doesn't match attachment, skip
220
+ continue;
221
+ }
222
+
223
+ $this->push_to_url_pairs( $url_pairs, $attachment_id, $url, $to_cache );
224
+ }
225
+
226
+ return $url_pairs;
227
+ }
228
+
229
+ /**
230
+ * Get URLs from content.
231
+ *
232
+ * @param string $content
233
+ * @param array $cache
234
+ * @param array $to_cache
235
+ *
236
+ * @return array
237
+ */
238
+ protected function get_urls_from_content( $content, $cache, &$to_cache ) {
239
+ $url_pairs = array();
240
+
241
+ if ( ! preg_match_all( '/(http|https)?:?\/\/[^"\'\s<>\\\]*/', $content, $matches ) || ! isset( $matches[0] ) ) {
242
+ // No URLs found, return
243
+ return $url_pairs;
244
+ }
245
+
246
+ $matches = array_unique( $matches[0] );
247
+
248
+ foreach ($matches as $url) {
249
+ if ( ! $this->url_needs_replacing( $url ) ) {
250
+ // URL already correct, skip
251
+ continue;
252
+ }
253
+
254
+ $parts = parse_url( $url );
255
+
256
+ if ( ! isset( $parts['path'] ) ) {
257
+ // URL doesn't have a path, continue
258
+ continue;
259
+ }
260
+
261
+ $info = pathinfo( $parts['path'] );
262
+
263
+ if ( ! isset( $info['extension'] ) ) {
264
+ // URL doesn't have a file extension, continue
265
+ continue;
266
+ }
267
+
268
+ $attachment_id = null;
269
+
270
+ if ( isset( $cache[ $this->as3cf->maybe_remove_query_string( $url ) ] ) ) {
271
+ $cached_id = $cache[ $this->as3cf->maybe_remove_query_string( $url ) ];
272
+
273
+ if ( $this->is_failure( $cached_id ) ) {
274
+ // Attachment ID failure, continue
275
+ continue;
276
+ }
277
+
278
+ // Attachment ID cached
279
+ $attachment_id = $cached_id;
280
+ }
281
+
282
+ if ( is_null( $attachment_id ) || is_array( $attachment_id ) ) {
283
+ // Attachment ID not cached
284
+ $attachment_id = $this->get_attachment_id_from_url( $url );
285
+ }
286
+
287
+ if ( ! $attachment_id ) {
288
+ // Can't determine attachment ID, continue
289
+ $this->url_cache_failure( $url, $to_cache );
290
+
291
+ continue;
292
+ }
293
+
294
+ $this->push_to_url_pairs( $url_pairs, $attachment_id, $url, $to_cache );
295
+ }
296
+
297
+ return $url_pairs;
298
+ }
299
+
300
+ /**
301
+ * Is failure?
302
+ *
303
+ * @param mixed $value
304
+ *
305
+ * @return bool
306
+ */
307
+ protected function is_failure( $value ) {
308
+ if ( ! is_array( $value ) || ! isset( $value['timestamp'] ) ) {
309
+ return false;
310
+ }
311
+
312
+ $expires = time() - ( 15 * MINUTE_IN_SECONDS );
313
+
314
+ if ( $expires >= $value['timestamp'] ) {
315
+ return false;
316
+ }
317
+
318
+ return true;
319
+ }
320
+
321
+ /**
322
+ * Does attachment ID match src?
323
+ *
324
+ * @param int $attachment_id
325
+ * @param string $url
326
+ *
327
+ * @return bool
328
+ */
329
+ protected function attachment_id_matches_src( $attachment_id, $url ) {
330
+ $base_urls = array();
331
+ $meta = get_post_meta( $attachment_id, '_wp_attachment_metadata', true );
332
+
333
+ if ( ! isset( $meta['sizes'] ) ) {
334
+ // No sizes found, return
335
+ return false;
336
+ }
337
+
338
+ $base_url = $this->as3cf->remove_scheme( $this->as3cf->maybe_remove_query_string( $this->get_base_url( $attachment_id ) ) );
339
+ $basename = wp_basename( $base_url );
340
+ $url = $this->as3cf->remove_scheme( $this->as3cf->maybe_remove_query_string( $url ) );
341
+
342
+ // Add full size URL
343
+ $base_urls[] = $base_url;
344
+
345
+ // Add additional image size URLs
346
+ foreach ( $meta['sizes'] as $size ) {
347
+ $base_urls[] = str_replace( $basename, $size['file'], $base_url );
348
+ }
349
+
350
+ if ( in_array( $this->as3cf->remove_scheme( $url ), $base_urls ) ) {
351
+ // Match found, return true
352
+ return true;
353
+ }
354
+
355
+ return false;
356
+ }
357
+
358
+ /**
359
+ * Push to URL pairs.
360
+ *
361
+ * @param array $url_pairs
362
+ * @param int $attachment_id
363
+ * @param string $find
364
+ * @param array $to_cache
365
+ */
366
+ protected function push_to_url_pairs( &$url_pairs, $attachment_id, $find, &$to_cache ) {
367
+ $replace_full = $this->get_url( $attachment_id );
368
+
369
+ if ( ! $replace_full ) {
370
+ // Replacement URL can't be found, return
371
+ return;
372
+ }
373
+
374
+ $size = $this->get_size_string_from_url( $attachment_id, $find );
375
+ $replace_size = $this->get_url( $attachment_id, $size );
376
+ $parts = parse_url( $find );
377
+
378
+ if ( ! isset( $parts['scheme'] ) ) {
379
+ $replace_full = $this->as3cf->remove_scheme( $replace_full );
380
+ $replace_size = $this->as3cf->remove_scheme( $replace_size );
381
+ }
382
+
383
+ $find_full = $this->as3cf->remove_size_from_filename( $find );
384
+ $find_full = $this->normalize_find_value( $this->as3cf->maybe_remove_query_string( $find_full ) );
385
+ $find_size = $this->normalize_find_value( $this->as3cf->maybe_remove_query_string( $find ) );
386
+
387
+ // Find and replace full version
388
+ $url_pairs[ $find_full ] = $replace_full;
389
+ $to_cache[ $find_full ] = $attachment_id;
390
+
391
+ // Find and replace sized version
392
+ if ( wp_basename( $find_full ) !== wp_basename( $find_size ) ) {
393
+ $url_pairs[ $find_size ] = $replace_size;
394
+ $to_cache[ $find_size ] = $attachment_id;
395
+ }
396
+
397
+ // Prime cache, when filtering the opposite way
398
+ $replace_full = $this->as3cf->maybe_remove_query_string( $replace_full );
399
+ $replace_size = $this->as3cf->maybe_remove_query_string( $replace_size );
400
+
401
+ $to_cache[ $this->normalize_find_value( $replace_full ) ] = $attachment_id;
402
+ $to_cache[ $this->normalize_find_value( $replace_size ) ] = $attachment_id;
403
+ }
404
+
405
+ /**
406
+ * Get size string from URL.
407
+ *
408
+ * @param int $attachment_id
409
+ * @param string $url
410
+ *
411
+ * @return null|string
412
+ */
413
+ protected function get_size_string_from_url( $attachment_id, $url ) {
414
+ $meta = get_post_meta( $attachment_id, '_wp_attachment_metadata', true );
415
+ $basename = wp_basename( $this->as3cf->maybe_remove_query_string( $url ) );
416
+
417
+ if ( empty( $meta['sizes'] ) ) {
418
+ // No alternative sizes available, return
419
+ return null;
420
+ }
421
+
422
+ foreach ( $meta['sizes'] as $size => $file ) {
423
+ if ( $basename === $file['file'] ) {
424
+ return $size;
425
+ }
426
+ }
427
+
428
+ return null;
429
+ }
430
+
431
+ /**
432
+ * URL cache failure.
433
+ *
434
+ * @param string $url
435
+ * @param array $to_cache
436
+ */
437
+ protected function url_cache_failure( $url, &$to_cache ) {
438
+ $full = $this->as3cf->remove_size_from_filename( $url );
439
+ $failure = array(
440
+ 'timestamp' => time(),
441
+ );
442
+
443
+ $to_cache[ $full ] = $failure;
444
+
445
+ if ( $full !== $url ) {
446
+ $to_cache[ $url ] = $failure;
447
+ }
448
+ }
449
+
450
+ /**
451
+ * Replace URLs.
452
+ *
453
+ * @param string $content
454
+ * @param array $url_pairs
455
+ *
456
+ * @return string
457
+ */
458
+ protected function replace_urls( $content, $url_pairs ) {
459
+ if ( empty( $url_pairs ) ) {
460
+ // No URLs to replace return
461
+ return $content;
462
+ }
463
+
464
+ foreach ( $url_pairs as $find => $replace ) {
465
+ $replace = $this->normalize_replace_value( $replace );
466
+ $content = str_replace( $find, $replace, $content );
467
+ }
468
+
469
+ return $content;
470
+ }
471
+
472
+ /**
473
+ * Get post cache
474
+ *
475
+ * @return array
476
+ */
477
+ protected function get_post_cache() {
478
+ global $post;
479
+
480
+ if ( ! isset( $post->ID ) ) {
481
+ // Post ID not found, return empty cache
482
+ return array();
483
+ }
484
+
485
+ $cache = get_post_meta( $post->ID, 'amazonS3_cache', true );
486
+
487
+ if ( empty( $cache ) ) {
488
+ $cache = array();
489
+ }
490
+
491
+ return $cache;
492
+ }
493
+
494
+ /**
495
+ * Maybe update post cache
496
+ *
497
+ * @param array $to_cache
498
+ */
499
+ protected function maybe_update_post_cache( $to_cache ) {
500
+ global $post;
501
+
502
+ if ( ! isset( $post->ID ) || empty( $to_cache ) ) {
503
+ return;
504
+ }
505
+
506
+ $urls = array_merge( $this->get_post_cache(), $to_cache );
507
+
508
+ update_post_meta( $post->ID, 'amazonS3_cache', $urls );
509
+ }
510
+
511
+ /**
512
+ * Get option cache.
513
+ *
514
+ * @return array
515
+ */
516
+ protected function get_option_cache() {
517
+ return get_option( 'amazonS3_cache', array() );
518
+ }
519
+
520
+ /**
521
+ * Maybe update option cache.
522
+ *
523
+ * @param array $to_cache
524
+ */
525
+ protected function maybe_update_option_cache( $to_cache ) {
526
+ if ( empty( $to_cache ) ) {
527
+ return;
528
+ }
529
+
530
+ $urls = array_merge( $this->get_option_cache(), $to_cache );
531
+
532
+ update_option( 'amazonS3_cache', $urls );
533
+ }
534
+
535
+ /**
536
+ * Purge attachment from cache on delete.
537
+ *
538
+ * @param int $post_id
539
+ */
540
+ public function purge_cache_on_attachment_delete( $post_id ) {
541
+ $this->purge_from_cache( $this->get_url( $post_id ) );
542
+ }
543
+
544
+ /**
545
+ * Purge URL from cache
546
+ *
547
+ * @param string $url
548
+ * @param bool|int $blog_id
549
+ */
550
+ public function purge_from_cache( $url, $blog_id = false ) {
551
+ global $wpdb;
552
+
553
+ if ( false !== $blog_id ) {
554
+ $this->as3cf->switch_to_blog( $blog_id );
555
+ }
556
+
557
+ // Purge postmeta cache
558
+ $sql = $wpdb->prepare( "
559
+ DELETE FROM {$wpdb->postmeta}
560
+ WHERE meta_key = %s
561
+ AND meta_value LIKE %s;
562
+ ", 'amazonS3_cache', '%"' . $url . '"%' );
563
+
564
+ $wpdb->query( $sql );
565
+
566
+ // Purge option cache
567
+ $sql = $wpdb->prepare( "
568
+ DELETE FROM {$wpdb->options}
569
+ WHERE option_name = %s
570
+ AND option_value LIKE %s;
571
+ ", 'amazonS3_cache', '%"' . $url . '"%' );
572
+
573
+ $wpdb->query( $sql );
574
+
575
+ if ( false !== $blog_id ) {
576
+ $this->as3cf->restore_current_blog();
577
+ }
578
+ }
579
+
580
+ /**
581
+ * Should filter content.
582
+ *
583
+ * @return bool
584
+ */
585
+ protected function should_filter_content() {
586
+ if ( ! $this->as3cf->is_plugin_setup() || ! $this->as3cf->get_setting( 'serve-from-s3' ) ) {
587
+ return false;
588
+ }
589
+
590
+ return true;
591
+ }
592
+
593
+ /**
594
+ * Remove AWS query strings.
595
+ *
596
+ * @param string $content
597
+ *
598
+ * @return string
599
+ */
600
+ protected function remove_aws_query_strings( $content ) {
601
+ if ( ! preg_match_all( '/\?[^\s"<\?]*X-Amz-Algorithm[^\s"<\?]+/', $content, $matches ) || ! isset( $matches[0] ) ) {
602
+ // No query strings found, return
603
+ return $content;
604
+ }
605
+
606
+ $matches = array_unique( $matches[0] );
607
+
608
+ foreach ( $matches as $match ) {
609
+ $content = str_replace( $match, '', $content );
610
+ }
611
+
612
+ return $content;
613
+ }
614
+
615
+ /**
616
+ * Does URL need replacing?
617
+ *
618
+ * @param string $url
619
+ *
620
+ * @return bool
621
+ */
622
+ abstract protected function url_needs_replacing( $url );
623
+
624
+ /**
625
+ * Get URL.
626
+ *
627
+ * @param int $attachment_id
628
+ * @param null|string $size
629
+ *
630
+ * @return bool|string
631
+ */
632
+ abstract protected function get_url( $attachment_id, $size = null );
633
+
634
+ /**
635
+ * Get base URL.
636
+ *
637
+ * @param int $attachment_id
638
+ *
639
+ * @return string|false
640
+ */
641
+ abstract protected function get_base_url( $attachment_id );
642
+
643
+ /**
644
+ * Get attachment ID from URL.
645
+ *
646
+ * @param string $url
647
+ *
648
+ * @return bool|int
649
+ */
650
+ abstract protected function get_attachment_id_from_url( $url );
651
+
652
+ /**
653
+ * Normalize find value.
654
+ *
655
+ * @param string $url
656
+ *
657
+ * @return string
658
+ */
659
+ abstract protected function normalize_find_value( $url );
660
+
661
+ /**
662
+ * Normalize replace value.
663
+ *
664
+ * @param string $url
665
+ *
666
+ * @return string
667
+ */
668
+ abstract protected function normalize_replace_value( $url );
669
+
670
+ /**
671
+ * Post process content.
672
+ *
673
+ * @param string $content
674
+ *
675
+ * @return string
676
+ */
677
+ abstract protected function post_process_content( $content );
678
+
679
+ /**
680
+ * Pre replace content.
681
+ *
682
+ * @param string $content
683
+ *
684
+ * @return string
685
+ */
686
+ abstract protected function pre_replace_content( $content );
687
+ }
classes/as3cf-notices.php CHANGED
@@ -12,24 +12,18 @@ class AS3CF_Notices {
12
  */
13
  private $as3cf;
14
 
15
- /**
16
- * @var string
17
- */
18
- private $plugin_file_path;
19
-
20
  /**
21
  * Make this class a singleton
22
  *
23
  * Use this instead of __construct()
24
  *
25
  * @param Amazon_S3_And_CloudFront $as3cf
26
- * @param string $plugin_file_path
27
  *
28
  * @return AS3CF_Notices
29
  */
30
- public static function get_instance( $as3cf, $plugin_file_path ) {
31
  if ( ! isset( static::$instance ) ) {
32
- static::$instance = new static( $as3cf, $plugin_file_path );
33
  }
34
 
35
  return static::$instance;
@@ -39,11 +33,9 @@ class AS3CF_Notices {
39
  * Constructor
40
  *
41
  * @param Amazon_S3_And_CloudFront $as3cf
42
- * @param string $plugin_file_path
43
  */
44
- protected function __construct( $as3cf, $plugin_file_path ) {
45
- $this->as3cf = $as3cf;
46
- $this->plugin_file_path = $plugin_file_path;
47
 
48
  add_action( 'admin_notices', array( $this, 'admin_notices' ) );
49
  add_action( 'network_admin_notices', array( $this, 'admin_notices' ) );
@@ -83,6 +75,7 @@ class AS3CF_Notices {
83
  'show_callback' => false, // Callback to display extra info on notices. Passing a callback automatically handles show/hide toggle.
84
  'callback_args' => array(), // Arguments to pass to the callback.
85
  'lock_key' => '', // If lock key set, do not show message until lock released.
 
86
  );
87
 
88
  $notice = array_intersect_key( array_merge( $defaults, $args ), $defaults );
@@ -386,6 +379,10 @@ class AS3CF_Notices {
386
  $notice['type'] = 'notice-info';
387
  }
388
 
 
 
 
 
389
  $this->as3cf->render_view( 'notice', $notice );
390
 
391
  if ( $notice['flash'] ) {
@@ -430,10 +427,10 @@ class AS3CF_Notices {
430
  $suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
431
 
432
  // Enqueue notice.css & notice.js globally as some notices can be shown & dismissed on any admin page.
433
- $src = plugins_url( 'assets/css/notice.css', $this->plugin_file_path );
434
  wp_enqueue_style( 'as3cf-notice', $src, array(), $version );
435
-
436
- $src = plugins_url( 'assets/js/notice' . $suffix . '.js', $this->plugin_file_path );
437
  wp_enqueue_script( 'as3cf-notice', $src, array( 'jquery' ), $version, true );
438
 
439
  wp_localize_script( 'as3cf-notice', 'as3cf_notice', array(
12
  */
13
  private $as3cf;
14
 
 
 
 
 
 
15
  /**
16
  * Make this class a singleton
17
  *
18
  * Use this instead of __construct()
19
  *
20
  * @param Amazon_S3_And_CloudFront $as3cf
 
21
  *
22
  * @return AS3CF_Notices
23
  */
24
+ public static function get_instance( $as3cf ) {
25
  if ( ! isset( static::$instance ) ) {
26
+ static::$instance = new static( $as3cf );
27
  }
28
 
29
  return static::$instance;
33
  * Constructor
34
  *
35
  * @param Amazon_S3_And_CloudFront $as3cf
 
36
  */
37
+ protected function __construct( $as3cf ) {
38
+ $this->as3cf = $as3cf;
 
39
 
40
  add_action( 'admin_notices', array( $this, 'admin_notices' ) );
41
  add_action( 'network_admin_notices', array( $this, 'admin_notices' ) );
75
  'show_callback' => false, // Callback to display extra info on notices. Passing a callback automatically handles show/hide toggle.
76
  'callback_args' => array(), // Arguments to pass to the callback.
77
  'lock_key' => '', // If lock key set, do not show message until lock released.
78
+ 'pre_render_callback' => false, // Callback to call before notice render.
79
  );
80
 
81
  $notice = array_intersect_key( array_merge( $defaults, $args ), $defaults );
379
  $notice['type'] = 'notice-info';
380
  }
381
 
382
+ if ( ! empty( $notice['pre_render_callback'] ) && is_callable( $notice['pre_render_callback'] ) ) {
383
+ call_user_func( $notice['pre_render_callback'] );
384
+ }
385
+
386
  $this->as3cf->render_view( 'notice', $notice );
387
 
388
  if ( $notice['flash'] ) {
427
  $suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
428
 
429
  // Enqueue notice.css & notice.js globally as some notices can be shown & dismissed on any admin page.
430
+ $src = plugins_url( 'assets/css/notice.css', $this->as3cf->get_plugin_file_path() );
431
  wp_enqueue_style( 'as3cf-notice', $src, array(), $version );
432
+
433
+ $src = plugins_url( 'assets/js/notice' . $suffix . '.js', $this->as3cf->get_plugin_file_path() );
434
  wp_enqueue_script( 'as3cf-notice', $src, array( 'jquery' ), $version, true );
435
 
436
  wp_localize_script( 'as3cf-notice', 'as3cf_notice', array(
classes/as3cf-plugin-compatibility.php CHANGED
@@ -61,16 +61,6 @@ class AS3CF_Plugin_Compatibility {
61
  * Responsive Images WP 4.4
62
  */
63
  add_filter( 'wp_calculate_image_srcset', array( $this, 'wp_calculate_image_srcset' ), 10, 5 );
64
-
65
- global $wp_version;
66
- if ( 0 === version_compare( $wp_version, '4.4' ) ) {
67
- // Hot fix for 4.4
68
- add_filter( 'the_content', array( $this, 'wp_make_content_images_responsive' ), 11 );
69
- }
70
-
71
- /*
72
- * Responsive Images WP 4.4.1+
73
- */
74
  add_filter( 'wp_calculate_image_srcset_meta', array( $this, 'wp_calculate_image_srcset_meta' ), 10, 4 );
75
 
76
  if ( $this->as3cf->is_plugin_setup() ) {
@@ -92,7 +82,7 @@ class AS3CF_Plugin_Compatibility {
92
  * Legacy filter
93
  * 'as3cf_get_attached_file_copy_back_to_local'
94
  */
95
- add_filter( 'as3cf_get_attached_file', array( $this, 'legacy_copy_back_to_local'), 10, 4 );
96
 
97
  /*
98
  * WP_Image_Editor
@@ -146,7 +136,7 @@ class AS3CF_Plugin_Compatibility {
146
  return $addons_to_install;
147
  }
148
 
149
- foreach( $addons as $addon_slug => $addon ) {
150
  if ( file_exists( WP_PLUGIN_DIR . '/' . $addon_slug . '/' . $addon_slug . '.php' ) ) {
151
  // Addon already installed, ignore.
152
  continue;
@@ -183,7 +173,7 @@ class AS3CF_Plugin_Compatibility {
183
  }
184
 
185
  global $as3cf_compat_check;
186
- if ( ! $as3cf_compat_check->check_capabilities() ){
187
  // User can't install plugins anyway, bail.
188
  return;
189
  }
@@ -208,7 +198,7 @@ class AS3CF_Plugin_Compatibility {
208
  $support_email = 'nom@deliciousbrains.com';
209
  $support_link = sprintf( '<a href="mailto:%1$s">%1$s</a>', $support_email );
210
 
211
- $notice_addons_text .= '<p>' . 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 ). '</p>';
212
  $notice_addons_text .= sprintf( '<p><a href="%s" class="button button-large">%s</a></p>', 'https://deliciousbrains.com/wp-offload-s3/pricing/', __( 'View Licenses', 'amazon-s3-and-cloudfront' ) );
213
 
214
  $notice_addons_text = apply_filters( 'wpos3_compat_addons_notice', $notice_addons_text, $addons_to_install );
@@ -351,9 +341,9 @@ class AS3CF_Plugin_Compatibility {
351
  * Check the current request is a specific one based on action and
352
  * optional context
353
  *
354
- * @param string $action_key
355
- * @param bool $ajax
356
- * @param null|string $context_key
357
  *
358
  * @return bool
359
  */
@@ -375,9 +365,14 @@ class AS3CF_Plugin_Compatibility {
375
 
376
  $context_check = true;
377
  if ( ! is_null( $context_key ) ) {
378
- $global = constant( 'INPUT_' . $var_type );
379
- $context = $this->as3cf->filter_input( 'context', $global );
380
- $context_check = ( $context_key === $context );
 
 
 
 
 
381
  }
382
 
383
  return ( $action_key === sanitize_key( $action ) && $context_check );
@@ -533,10 +528,12 @@ class AS3CF_Plugin_Compatibility {
533
  * @return bool
534
  */
535
  protected function is_customizer_crop_action() {
536
- $header_crop = $this->maybe_process_on_action( 'custom-header-crop', true );
537
- $identity_crop = $this->maybe_process_on_action( 'crop-image', true, 'site-icon' );
538
 
539
- if ( ! $header_crop && ! $identity_crop ) {
 
 
 
540
  // Not doing a Customizer action
541
  return false;
542
  }
@@ -671,13 +668,11 @@ class AS3CF_Plugin_Compatibility {
671
  }
672
 
673
  try {
674
- $this->as3cf->get_s3client( $s3_object['region'], true )->getObject(
675
- array(
676
- 'Bucket' => $s3_object['bucket'],
677
- 'Key' => $s3_object['key'],
678
- 'SaveAs' => $file,
679
- )
680
- );
681
  } catch ( Exception $e ) {
682
  AS3CF_Error::log( sprintf( __( 'There was an error attempting to download the file %s from S3: %s', 'amazon-s3-and-cloudfront' ), $s3_object['key'], $e->getMessage() ) );
683
 
@@ -762,47 +757,6 @@ class AS3CF_Plugin_Compatibility {
762
  return $this->prepare_stream_wrapper_file( $s3_object['bucket'], $s3_object['region'], $s3_object['key'] );
763
  }
764
 
765
- /**
766
- * Filters 'img' elements in post content to add 'srcset' and 'sizes' attributes for S3 URLs.
767
- *
768
- * @param string $content The raw post content to be filtered.
769
- *
770
- * @return string Converted content with 'srcset' and 'sizes' attributes added to images.
771
- */
772
- public function wp_make_content_images_responsive( $content ) {
773
- if ( ! preg_match_all( '/<img [^>]+>/', $content, $matches ) ) {
774
- return $content;
775
- }
776
-
777
- $selected_images = $attachment_ids = array();
778
-
779
- foreach( $matches[0] as $image ) {
780
- if ( false === strpos( $image, ' srcset=' ) && preg_match( '/wp-image-([0-9]+)/i', $image, $class_id ) &&
781
- ( $attachment_id = absint( $class_id[1] ) ) ) {
782
-
783
- /*
784
- * If exactly the same image tag is used more than once, overwrite it.
785
- * All identical tags will be replaced later with 'str_replace()'.
786
- */
787
- $selected_images[ $image ] = $attachment_id;
788
- // Overwrite the ID when the same image is included more than once.
789
- $attachment_ids[ $attachment_id ] = true;
790
- }
791
- }
792
-
793
- foreach ( $selected_images as $image => $attachment_id ) {
794
- if ( ! ( $s3object = $this->as3cf->is_attachment_served_by_s3( $attachment_id ) ) ) {
795
- // Attachment not uploaded to S3, abort
796
- continue;
797
- }
798
-
799
- $image_meta = get_post_meta( $attachment_id, '_wp_attachment_metadata', true );
800
- $content = str_replace( $image, $this->wp_image_add_srcset_and_sizes( $image, $image_meta, $attachment_id ), $content );
801
- }
802
-
803
- return $content;
804
- }
805
-
806
  /**
807
  * Adds 'srcset' and 'sizes' attributes to an existing S3 'img' element.
808
  *
@@ -818,7 +772,7 @@ class AS3CF_Plugin_Compatibility {
818
  return $image;
819
  }
820
 
821
- $image_src = preg_match( '/src="([^"]+)"/', $image, $match_src ) ? $match_src[1] : '';
822
  list( $image_src ) = explode( '?', $image_src );
823
 
824
  // Return early if we couldn't get the image source.
@@ -827,13 +781,12 @@ class AS3CF_Plugin_Compatibility {
827
  }
828
 
829
  // Bail early if an image has been inserted and later edited.
830
- if ( preg_match( '/-e[0-9]{13}/', $image_meta['file'], $img_edit_hash ) &&
831
- strpos( wp_basename( $image_src ), $img_edit_hash[0] ) === false ) {
832
 
833
  return $image;
834
  }
835
 
836
- $width = preg_match( '/ width="([0-9]+)"/', $image, $match_width ) ? (int) $match_width[1] : 0;
837
  $height = preg_match( '/ height="([0-9]+)"/', $image, $match_height ) ? (int) $match_height[1] : 0;
838
 
839
  if ( ! $width || ! $height ) {
@@ -847,7 +800,7 @@ class AS3CF_Plugin_Compatibility {
847
  $width = (int) $image_meta['width'];
848
  $height = (int) $image_meta['height'];
849
  } else {
850
- foreach( $image_meta['sizes'] as $image_size_data ) {
851
  if ( $image_filename === $image_size_data['file'] ) {
852
  $width = (int) $image_size_data['width'];
853
  $height = (int) $image_size_data['height'];
@@ -905,7 +858,7 @@ class AS3CF_Plugin_Compatibility {
905
  return $image_meta;
906
  }
907
 
908
- if ( false !== strpos( $image_src, $image_meta['file'] ) ) {
909
  // Path matches URL, no need to change
910
  return $image_meta;
911
  }
@@ -923,8 +876,18 @@ class AS3CF_Plugin_Compatibility {
923
  }
924
 
925
  // Strip the meta file prefix so the just the filename will always match
926
- // the S3 URL regardless of different prefixes for the offloaded file
927
- $image_meta['file'] = $image_basename;
 
 
 
 
 
 
 
 
 
 
928
 
929
  return $image_meta;
930
  }
@@ -957,6 +920,7 @@ class AS3CF_Plugin_Compatibility {
957
  $s3_url = $this->as3cf->get_attachment_s3_url( $attachment_id, $s3object, null, $size, $image_meta );
958
 
959
  if ( false === $s3_url || is_wp_error( $s3_url ) ) {
 
960
  continue;
961
  }
962
 
@@ -984,4 +948,4 @@ class AS3CF_Plugin_Compatibility {
984
 
985
  return null;
986
  }
987
- }
61
  * Responsive Images WP 4.4
62
  */
63
  add_filter( 'wp_calculate_image_srcset', array( $this, 'wp_calculate_image_srcset' ), 10, 5 );
 
 
 
 
 
 
 
 
 
 
64
  add_filter( 'wp_calculate_image_srcset_meta', array( $this, 'wp_calculate_image_srcset_meta' ), 10, 4 );
65
 
66
  if ( $this->as3cf->is_plugin_setup() ) {
82
  * Legacy filter
83
  * 'as3cf_get_attached_file_copy_back_to_local'
84
  */
85
+ add_filter( 'as3cf_get_attached_file', array( $this, 'legacy_copy_back_to_local' ), 10, 4 );
86
 
87
  /*
88
  * WP_Image_Editor
136
  return $addons_to_install;
137
  }
138
 
139
+ foreach ( $addons as $addon_slug => $addon ) {
140
  if ( file_exists( WP_PLUGIN_DIR . '/' . $addon_slug . '/' . $addon_slug . '.php' ) ) {
141
  // Addon already installed, ignore.
142
  continue;
173
  }
174
 
175
  global $as3cf_compat_check;
176
+ if ( ! $as3cf_compat_check->check_capabilities() ) {
177
  // User can't install plugins anyway, bail.
178
  return;
179
  }
198
  $support_email = 'nom@deliciousbrains.com';
199
  $support_link = sprintf( '<a href="mailto:%1$s">%1$s</a>', $support_email );
200
 
201
+ $notice_addons_text .= '<p>' . 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 ) . '</p>';
202
  $notice_addons_text .= sprintf( '<p><a href="%s" class="button button-large">%s</a></p>', 'https://deliciousbrains.com/wp-offload-s3/pricing/', __( 'View Licenses', 'amazon-s3-and-cloudfront' ) );
203
 
204
  $notice_addons_text = apply_filters( 'wpos3_compat_addons_notice', $notice_addons_text, $addons_to_install );
341
  * Check the current request is a specific one based on action and
342
  * optional context
343
  *
344
+ * @param string $action_key
345
+ * @param bool $ajax
346
+ * @param null|string|array $context_key
347
  *
348
  * @return bool
349
  */
365
 
366
  $context_check = true;
367
  if ( ! is_null( $context_key ) ) {
368
+ $global = constant( 'INPUT_' . $var_type );
369
+ $context = $this->as3cf->filter_input( 'context', $global );
370
+
371
+ if ( is_array( $context_key ) ) {
372
+ $context_check = in_array( $context, $context_key );
373
+ } else {
374
+ $context_check = ( $context_key === $context );
375
+ }
376
  }
377
 
378
  return ( $action_key === sanitize_key( $action ) && $context_check );
528
  * @return bool
529
  */
530
  protected function is_customizer_crop_action() {
531
+ $header_crop = $this->maybe_process_on_action( 'custom-header-crop', true );
 
532
 
533
+ $context = array( 'site-icon', 'custom_logo' );
534
+ $image_crop = $this->maybe_process_on_action( 'crop-image', true, $context );
535
+
536
+ if ( ! $header_crop && ! $image_crop ) {
537
  // Not doing a Customizer action
538
  return false;
539
  }
668
  }
669
 
670
  try {
671
+ $this->as3cf->get_s3client( $s3_object['region'], true )->getObject( array(
672
+ 'Bucket' => $s3_object['bucket'],
673
+ 'Key' => $s3_object['key'],
674
+ 'SaveAs' => $file,
675
+ ) );
 
 
676
  } catch ( Exception $e ) {
677
  AS3CF_Error::log( sprintf( __( 'There was an error attempting to download the file %s from S3: %s', 'amazon-s3-and-cloudfront' ), $s3_object['key'], $e->getMessage() ) );
678
 
757
  return $this->prepare_stream_wrapper_file( $s3_object['bucket'], $s3_object['region'], $s3_object['key'] );
758
  }
759
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
760
  /**
761
  * Adds 'srcset' and 'sizes' attributes to an existing S3 'img' element.
762
  *
772
  return $image;
773
  }
774
 
775
+ $image_src = preg_match( '/src="([^"]+)"/', $image, $match_src ) ? $match_src[1] : '';
776
  list( $image_src ) = explode( '?', $image_src );
777
 
778
  // Return early if we couldn't get the image source.
781
  }
782
 
783
  // Bail early if an image has been inserted and later edited.
784
+ if ( preg_match( '/-e[0-9]{13}/', $image_meta['file'], $img_edit_hash ) && strpos( wp_basename( $image_src ), $img_edit_hash[0] ) === false ) {
 
785
 
786
  return $image;
787
  }
788
 
789
+ $width = preg_match( '/ width="([0-9]+)"/', $image, $match_width ) ? (int) $match_width[1] : 0;
790
  $height = preg_match( '/ height="([0-9]+)"/', $image, $match_height ) ? (int) $match_height[1] : 0;
791
 
792
  if ( ! $width || ! $height ) {
800
  $width = (int) $image_meta['width'];
801
  $height = (int) $image_meta['height'];
802
  } else {
803
+ foreach ( $image_meta['sizes'] as $image_size_data ) {
804
  if ( $image_filename === $image_size_data['file'] ) {
805
  $width = (int) $image_size_data['width'];
806
  $height = (int) $image_size_data['height'];
858
  return $image_meta;
859
  }
860
 
861
+ if ( false !== strpos( $image_src, $image_meta['file'] ) ) {
862
  // Path matches URL, no need to change
863
  return $image_meta;
864
  }
876
  }
877
 
878
  // Strip the meta file prefix so the just the filename will always match
879
+ // the S3 URL regardless of different prefixes for the offloaded file.
880
+ // Also ensure filename is encoded the same way as URL.
881
+ $image_meta['file'] = rawurlencode( $image_basename );
882
+
883
+ // Ensure each size filename is encoded the same way as URL.
884
+ if ( ! empty( $image_meta['sizes'] ) ) {
885
+ $image_meta['sizes'] = array_map( function ( $size ) {
886
+ $size['file'] = rawurlencode( $size['file'] );
887
+
888
+ return $size;
889
+ }, $image_meta['sizes'] );
890
+ }
891
 
892
  return $image_meta;
893
  }
920
  $s3_url = $this->as3cf->get_attachment_s3_url( $attachment_id, $s3object, null, $size, $image_meta );
921
 
922
  if ( false === $s3_url || is_wp_error( $s3_url ) ) {
923
+ // Skip URLs not offloaded to S3
924
  continue;
925
  }
926
 
948
 
949
  return null;
950
  }
951
+ }
classes/as3cf-upgrade.php CHANGED
@@ -31,17 +31,17 @@ abstract class AS3CF_Upgrade {
31
  /**
32
  * @var int
33
  */
34
- protected $upgrade_id;
35
 
36
  /**
37
  * @var string
38
  */
39
- protected $upgrade_name;
40
 
41
  /**
42
  * @var string 'metadata', 'attachment'
43
  */
44
- protected $upgrade_type;
45
 
46
  /**
47
  * @var string
@@ -87,13 +87,13 @@ abstract class AS3CF_Upgrade {
87
  *
88
  * @param Amazon_S3_And_CloudFront $as3cf - the instance of the as3cf class
89
  */
90
- function __construct( $as3cf ) {
91
  $this->as3cf = $as3cf;
92
 
93
  $this->cron_hook = 'as3cf_cron_update_' . $this->upgrade_name;
94
  $this->cron_schedule_key = 'as3cf_update_' . $this->upgrade_name . '_interval';
95
 
96
- $this->cron_interval_in_minutes = apply_filters( 'as3cf_update_' . $this->upgrade_name . '_interval', 5 );
97
  $this->error_threshold = apply_filters( 'as3cf_update_' . $this->upgrade_name . '_error_threshold', 20 );
98
 
99
  add_filter( 'cron_schedules', array( $this, 'cron_schedules' ) );
@@ -123,12 +123,18 @@ abstract class AS3CF_Upgrade {
123
  return false;
124
  }
125
 
 
 
 
 
 
126
  // If the upgrade status is already set, then we've already initialized the upgrade
127
  if ( $upgrade_status = $this->get_upgrade_status() ) {
128
  if ( self::STATUS_RUNNING === $upgrade_status ) {
129
  // Make sure cron job is persisted in case it has dropped
130
  $this->schedule();
131
  }
 
132
  return false;
133
  }
134
 
@@ -142,8 +148,8 @@ abstract class AS3CF_Upgrade {
142
  return false;
143
  }
144
 
145
- // Do we actually attachments to process?
146
- if ( 0 === $this->count_attachments_to_process() ) {
147
  $this->upgrade_finished();
148
 
149
  return false;
@@ -153,24 +159,31 @@ abstract class AS3CF_Upgrade {
153
  }
154
 
155
  /**
 
 
156
  * @return int
157
  */
158
- abstract protected function count_attachments_to_process();
159
 
160
  /**
161
- * @param $prefix
162
- * @param $limit
 
 
 
163
  *
164
  * @return array
165
  */
166
- abstract protected function get_attachments_to_process( $prefix, $limit );
167
 
168
  /**
169
- * @param $attachment
 
 
170
  *
171
  * @return bool
172
  */
173
- abstract protected function upgrade_attachment( $attachment );
174
 
175
  /**
176
  * Fire up the upgrade
@@ -183,9 +196,9 @@ abstract class AS3CF_Upgrade {
183
  }
184
 
185
  /**
186
- * Cron jon to update the region of the bucket in s3 metadata
187
  */
188
- function do_upgrade() {
189
  // Check if the cron should even be running
190
  if ( $this->get_saved_upgrade_id() >= $this->upgrade_id || $this->get_upgrade_status() !== self::STATUS_RUNNING ) {
191
  $this->unschedule();
@@ -196,35 +209,32 @@ abstract class AS3CF_Upgrade {
196
  // set the batch size limit for the query
197
  $limit = apply_filters( 'as3cf_update_' . $this->upgrade_name . '_batch_size', 500 );
198
  $all_limit = $limit;
199
-
200
- // only process the loop for a certain amount of time
201
- $minutes = $this->cron_interval_in_minutes * 60;
202
- // smaller time limit so won't run into another instance of cron
203
- $minutes = $minutes * 0.8;
204
- $finish = time() + $minutes;
205
 
206
  $session = $this->get_session();
207
 
208
  // find the blog IDs that have been processed so we can skip them
209
  $processed_blog_ids = isset( $session['processed_blog_ids'] ) ? $session['processed_blog_ids'] : array();
210
  $this->error_count = isset( $session['error_count'] ) ? $session['error_count'] : 0;
 
211
 
212
  // get the table prefixes for all the blogs
213
  $table_prefixes = $this->as3cf->get_all_blog_table_prefixes( $processed_blog_ids );
214
 
215
- $all_attachments = array();
216
- $all_count = 0;
217
 
218
  foreach ( $table_prefixes as $blog_id => $table_prefix ) {
219
- $attachments = $this->get_attachments_to_process( $table_prefix, $limit );
220
- $count = count( $attachments );
221
 
222
  if ( 0 === $count ) {
223
- // no more attachments, record the blog ID to skip next time
 
224
  $processed_blog_ids[] = $blog_id;
225
  } else {
226
  $all_count += $count;
227
- $all_attachments[ $blog_id ] = $attachments;
228
  }
229
 
230
  if ( $all_count >= $all_limit ) {
@@ -241,18 +251,18 @@ abstract class AS3CF_Upgrade {
241
  }
242
 
243
  // loop through and update s3 meta with region
244
- foreach ( $all_attachments as $blog_id => $attachments ) {
245
  $this->as3cf->switch_to_blog( $blog_id );
246
 
247
- foreach ( $attachments as $attachment ) {
248
  if ( $this->error_count >= $this->error_threshold ) {
249
  $this->upgrade_error( $session );
250
 
251
  return;
252
  }
253
 
254
- // Do the actual upgrade to the attachment
255
- $this->upgrade_attachment( $attachment );
256
 
257
  if ( time() >= $finish || $this->as3cf->memory_exceeded( 'as3cf_update_' . $this->upgrade_name . '_memory_exceeded' ) ) {
258
  // Batch limits reached
@@ -265,6 +275,7 @@ abstract class AS3CF_Upgrade {
265
  $this->as3cf->restore_current_blog();
266
  }
267
 
 
268
  $session['processed_blog_ids'] = $processed_blog_ids;
269
  $session['error_count'] = $this->error_count;
270
 
@@ -274,22 +285,28 @@ abstract class AS3CF_Upgrade {
274
  /**
275
  * Adds notices about issues with upgrades allowing user to restart them
276
  */
277
- function maybe_display_notices() {
278
- $action_url = $this->as3cf->get_plugin_page_url( array( 'action' => 'restart_update', 'update' => $this->upgrade_name ), 'self' );
 
 
 
279
  $msg_type = 'notice-info';
280
 
281
  switch ( $this->get_upgrade_status() ) {
282
  case self::STATUS_RUNNING:
283
- $msg = sprintf( __( '<strong>Running %s Update</strong> &mdash; We&#8217;re going through all the Media Library items uploaded to S3 %s This will be done quietly in the background, processing a small batch of Media Library items every %d minutes. There should be no noticeable impact on your server&#8217;s performance.', 'amazon-s3-and-cloudfront' ), ucfirst( $this->upgrade_type ), $this->running_update_text, $this->cron_interval_in_minutes );
284
  $action_text = __( 'Pause Update', 'amazon-s3-and-cloudfront' );
285
- $action_url = $this->as3cf->get_plugin_page_url( array( 'action' => 'pause_update', 'update' => $this->upgrade_name ), 'self' );
 
 
 
286
  break;
287
  case self::STATUS_PAUSED:
288
- $msg = sprintf( __( '<strong>%s Update Paused</strong> &mdash; Updating Media Library %s has been paused.', 'amazon-s3-and-cloudfront' ), ucfirst( $this->upgrade_type ), $this->upgrade_type );
289
  $action_text = __( 'Restart Update', 'amazon-s3-and-cloudfront' );
290
  break;
291
  case self::STATUS_ERROR:
292
- $msg = sprintf( __( '<strong>Error Updating %s</strong> &mdash; We ran into some errors attempting to update the %s for all your Media Library items that have been uploaded to S3. Please check your error log for details. (#%d)', 'amazon-s3-and-cloudfront' ), ucfirst( $this->upgrade_type ), $this->upgrade_type, $this->upgrade_id );
293
  $action_text = __( 'Try Run It Again', 'amazon-s3-and-cloudfront' );
294
  $msg_type = 'error';
295
  break;
@@ -307,10 +324,79 @@ abstract class AS3CF_Upgrade {
307
  $this->as3cf->render_view( 'notice', $args );
308
  }
309
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
310
  /**
311
  * Handler for the running upgrade actions
312
  */
313
- function maybe_handle_action() {
314
  if ( ! isset( $_GET['page'] ) || sanitize_key( $_GET['page'] ) !== $this->as3cf->get_plugin_slug() ) { // input var okay
315
  return;
316
  }
@@ -324,6 +410,7 @@ abstract class AS3CF_Upgrade {
324
  }
325
 
326
  $method_name = 'action_' . sanitize_key( $_GET['action'] ); // input var okay
 
327
  if ( method_exists( $this, $method_name ) ) {
328
  call_user_func( array( $this, $method_name ) );
329
  }
@@ -334,7 +421,7 @@ abstract class AS3CF_Upgrade {
334
  *
335
  * @param array $session
336
  */
337
- function upgrade_error( $session ) {
338
  $session['status'] = self::STATUS_ERROR;
339
  $this->save_session( $session );
340
  $this->unschedule();
@@ -343,7 +430,7 @@ abstract class AS3CF_Upgrade {
343
  /**
344
  * Complete the upgrade
345
  */
346
- function upgrade_finished() {
347
  $this->clear_session();
348
  $this->update_saved_upgrade_id();
349
  $this->unschedule();
@@ -352,7 +439,7 @@ abstract class AS3CF_Upgrade {
352
  /**
353
  * Restart upgrade
354
  */
355
- function action_restart_update() {
356
  $this->schedule();
357
  $this->change_status_request( self::STATUS_RUNNING );
358
  }
@@ -360,7 +447,7 @@ abstract class AS3CF_Upgrade {
360
  /**
361
  * Pause upgrade
362
  */
363
- function action_pause_update() {
364
  $this->unschedule();
365
  $this->change_status_request( self::STATUS_PAUSED );
366
  }
@@ -370,7 +457,7 @@ abstract class AS3CF_Upgrade {
370
  *
371
  * @param int $status
372
  */
373
- function change_status_request( $status ) {
374
  $session = $this->get_session();
375
  $session['status'] = $status;
376
  $this->save_session( $session );
@@ -383,14 +470,14 @@ abstract class AS3CF_Upgrade {
383
  /**
384
  * Schedule the cron
385
  */
386
- function schedule() {
387
  $this->as3cf->schedule_event( $this->cron_hook, $this->cron_schedule_key );
388
  }
389
 
390
  /**
391
  * Remove the cron schedule
392
  */
393
- function unschedule() {
394
  $this->as3cf->clear_scheduled_event( $this->cron_hook );
395
  }
396
 
@@ -401,7 +488,7 @@ abstract class AS3CF_Upgrade {
401
  *
402
  * @return array
403
  */
404
- function cron_schedules( $schedules ) {
405
  // Add the upgrade interval to the existing schedules.
406
  $schedules[ $this->cron_schedule_key ] = array(
407
  'interval' => $this->cron_interval_in_minutes * 60,
@@ -415,7 +502,7 @@ abstract class AS3CF_Upgrade {
415
  * Get the current status of the upgrade
416
  * See STATUS_* constants in the class declaration above.
417
  */
418
- function get_upgrade_status() {
419
  $session = $this->get_session();
420
 
421
  if ( ! isset( $session['status'] ) ) {
@@ -430,7 +517,7 @@ abstract class AS3CF_Upgrade {
430
  *
431
  * @return array
432
  */
433
- function get_session() {
434
  return get_site_option( 'update_' . $this->upgrade_name . '_session', array() );
435
  }
436
 
@@ -439,7 +526,7 @@ abstract class AS3CF_Upgrade {
439
  *
440
  * @param array $session session data to store
441
  */
442
- function save_session( $session ) {
443
  update_site_option( 'update_' . $this->upgrade_name . '_session', $session );
444
  }
445
 
@@ -447,7 +534,7 @@ abstract class AS3CF_Upgrade {
447
  * Remove the session data to be used between requests
448
  *
449
  */
450
- function clear_session() {
451
  delete_site_option( 'update_' . $this->upgrade_name . '_session' );
452
  }
453
 
@@ -456,14 +543,14 @@ abstract class AS3CF_Upgrade {
456
  *
457
  * @return int|mixed|string|WP_Error
458
  */
459
- function get_saved_upgrade_id() {
460
  return $this->as3cf->get_setting( $this->settings_key, 0 );
461
  }
462
 
463
  /**
464
  * Update the saved upgrade ID
465
  */
466
- function update_saved_upgrade_id() {
467
  $this->as3cf->set_setting( $this->settings_key, $this->upgrade_id );
468
  $this->as3cf->save_settings();
469
  }
@@ -473,7 +560,7 @@ abstract class AS3CF_Upgrade {
473
  *
474
  * @return bool
475
  */
476
- function has_previous_upgrade_completed() {
477
  // Has the previous upgrade completed yet?
478
  $previous_id = $this->upgrade_id - 1;
479
  if ( 0 !== $previous_id && (int) $this->get_saved_upgrade_id() < $previous_id ) {
31
  /**
32
  * @var int
33
  */
34
+ protected $upgrade_id = 0;
35
 
36
  /**
37
  * @var string
38
  */
39
+ protected $upgrade_name = 'base';
40
 
41
  /**
42
  * @var string 'metadata', 'attachment'
43
  */
44
+ protected $upgrade_type = 'attachment';
45
 
46
  /**
47
  * @var string
87
  *
88
  * @param Amazon_S3_And_CloudFront $as3cf - the instance of the as3cf class
89
  */
90
+ public function __construct( $as3cf ) {
91
  $this->as3cf = $as3cf;
92
 
93
  $this->cron_hook = 'as3cf_cron_update_' . $this->upgrade_name;
94
  $this->cron_schedule_key = 'as3cf_update_' . $this->upgrade_name . '_interval';
95
 
96
+ $this->cron_interval_in_minutes = apply_filters( 'as3cf_update_' . $this->upgrade_name . '_interval', 2 );
97
  $this->error_threshold = apply_filters( 'as3cf_update_' . $this->upgrade_name . '_error_threshold', 20 );
98
 
99
  add_filter( 'cron_schedules', array( $this, 'cron_schedules' ) );
123
  return false;
124
  }
125
 
126
+ // Is plugin setup?
127
+ if ( ! $this->as3cf->is_plugin_setup() ) {
128
+ return false;
129
+ }
130
+
131
  // If the upgrade status is already set, then we've already initialized the upgrade
132
  if ( $upgrade_status = $this->get_upgrade_status() ) {
133
  if ( self::STATUS_RUNNING === $upgrade_status ) {
134
  // Make sure cron job is persisted in case it has dropped
135
  $this->schedule();
136
  }
137
+
138
  return false;
139
  }
140
 
148
  return false;
149
  }
150
 
151
+ // Do we actually have attachments to process?
152
+ if ( 0 === $this->count_items_to_process() ) {
153
  $this->upgrade_finished();
154
 
155
  return false;
159
  }
160
 
161
  /**
162
+ * Count items to process.
163
+ *
164
  * @return int
165
  */
166
+ abstract protected function count_items_to_process();
167
 
168
  /**
169
+ * Get items to process.
170
+ *
171
+ * @param string $prefix
172
+ * @param int $limit
173
+ * @param bool|mixed $offset
174
  *
175
  * @return array
176
  */
177
+ abstract protected function get_items_to_process( $prefix, $limit, $offset = false );
178
 
179
  /**
180
+ * Upgrade attachment.
181
+ *
182
+ * @param mixed $attachment
183
  *
184
  * @return bool
185
  */
186
+ abstract protected function upgrade_item( $attachment );
187
 
188
  /**
189
  * Fire up the upgrade
196
  }
197
 
198
  /**
199
+ * Cron job to update the region of the bucket in s3 metadata
200
  */
201
+ public function do_upgrade() {
202
  // Check if the cron should even be running
203
  if ( $this->get_saved_upgrade_id() >= $this->upgrade_id || $this->get_upgrade_status() !== self::STATUS_RUNNING ) {
204
  $this->unschedule();
209
  // set the batch size limit for the query
210
  $limit = apply_filters( 'as3cf_update_' . $this->upgrade_name . '_batch_size', 500 );
211
  $all_limit = $limit;
212
+ $finish = time() + apply_filters( 'as3cf_update_' . $this->upgrade_name . '_time_limit', 20 );
 
 
 
 
 
213
 
214
  $session = $this->get_session();
215
 
216
  // find the blog IDs that have been processed so we can skip them
217
  $processed_blog_ids = isset( $session['processed_blog_ids'] ) ? $session['processed_blog_ids'] : array();
218
  $this->error_count = isset( $session['error_count'] ) ? $session['error_count'] : 0;
219
+ $offset = isset( $session['offset'] ) ? $session['offset'] : false;
220
 
221
  // get the table prefixes for all the blogs
222
  $table_prefixes = $this->as3cf->get_all_blog_table_prefixes( $processed_blog_ids );
223
 
224
+ $all_items = array();
225
+ $all_count = 0;
226
 
227
  foreach ( $table_prefixes as $blog_id => $table_prefix ) {
228
+ $items = $this->get_items_to_process( $table_prefix, $limit, $offset );
229
+ $count = count( $items );
230
 
231
  if ( 0 === $count ) {
232
+ // No more items, record the blog ID to skip next time
233
+ $session['offset'] = false;
234
  $processed_blog_ids[] = $blog_id;
235
  } else {
236
  $all_count += $count;
237
+ $all_items[ $blog_id ] = $items;
238
  }
239
 
240
  if ( $all_count >= $all_limit ) {
251
  }
252
 
253
  // loop through and update s3 meta with region
254
+ foreach ( $all_items as $blog_id => $items ) {
255
  $this->as3cf->switch_to_blog( $blog_id );
256
 
257
+ foreach ( $items as $item ) {
258
  if ( $this->error_count >= $this->error_threshold ) {
259
  $this->upgrade_error( $session );
260
 
261
  return;
262
  }
263
 
264
+ // Do the actual upgrade to the item
265
+ $this->upgrade_item( $item );
266
 
267
  if ( time() >= $finish || $this->as3cf->memory_exceeded( 'as3cf_update_' . $this->upgrade_name . '_memory_exceeded' ) ) {
268
  // Batch limits reached
275
  $this->as3cf->restore_current_blog();
276
  }
277
 
278
+ $session['offset'] = isset( $item ) ? $item : false;
279
  $session['processed_blog_ids'] = $processed_blog_ids;
280
  $session['error_count'] = $this->error_count;
281
 
285
  /**
286
  * Adds notices about issues with upgrades allowing user to restart them
287
  */
288
+ public function maybe_display_notices() {
289
+ $action_url = $this->as3cf->get_plugin_page_url( array(
290
+ 'action' => 'restart_update',
291
+ 'update' => $this->upgrade_name,
292
+ ), 'self' );
293
  $msg_type = 'notice-info';
294
 
295
  switch ( $this->get_upgrade_status() ) {
296
  case self::STATUS_RUNNING:
297
+ $msg = $this->get_running_message();
298
  $action_text = __( 'Pause Update', 'amazon-s3-and-cloudfront' );
299
+ $action_url = $this->as3cf->get_plugin_page_url( array(
300
+ 'action' => 'pause_update',
301
+ 'update' => $this->upgrade_name,
302
+ ), 'self' );
303
  break;
304
  case self::STATUS_PAUSED:
305
+ $msg = $this->get_paused_message();
306
  $action_text = __( 'Restart Update', 'amazon-s3-and-cloudfront' );
307
  break;
308
  case self::STATUS_ERROR:
309
+ $msg = $this->get_error_message();
310
  $action_text = __( 'Try Run It Again', 'amazon-s3-and-cloudfront' );
311
  $msg_type = 'error';
312
  break;
324
  $this->as3cf->render_view( 'notice', $args );
325
  }
326
 
327
+ /**
328
+ * Get running message.
329
+ *
330
+ * @return string
331
+ */
332
+ protected function get_running_message() {
333
+ return sprintf( __( '<strong>Running %1$s Update%2$s</strong> &mdash; We&#8217;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&#8217;s performance.', 'amazon-s3-and-cloudfront' ),
334
+ ucwords( $this->upgrade_type ),
335
+ $this->get_progress_text(),
336
+ $this->running_update_text,
337
+ $this->cron_interval_in_minutes
338
+ );
339
+ }
340
+
341
+ /**
342
+ * Get paused message.
343
+ *
344
+ * @return string
345
+ */
346
+ protected function get_paused_message() {
347
+ return sprintf( __( '<strong>%1$s Update Paused%2$s</strong> &mdash; Updating Media Library %3$s has been paused.', 'amazon-s3-and-cloudfront' ),
348
+ ucwords( $this->upgrade_type ),
349
+ $this->get_progress_text(),
350
+ $this->upgrade_type
351
+ );
352
+ }
353
+
354
+ /**
355
+ * Get error message.
356
+ *
357
+ * @return string
358
+ */
359
+ protected function get_error_message() {
360
+ return sprintf( __( '<strong>Error Updating %1$s</strong> &mdash; 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' ),
361
+ ucwords( $this->upgrade_type ),
362
+ $this->upgrade_type,
363
+ $this->upgrade_id
364
+ );
365
+ }
366
+
367
+ /**
368
+ * Get progress text.
369
+ *
370
+ * @return string
371
+ */
372
+ protected function get_progress_text() {
373
+ $progress = $this->calculate_progress();
374
+
375
+ if ( false === $progress ) {
376
+ // Progress can not be calculated, return
377
+ return '';
378
+ }
379
+
380
+ if ( $progress > 100 ) {
381
+ $progress = 100;
382
+ }
383
+
384
+ return sprintf( __( ' (%s%% Complete)', 'amazon-s3-and-cloudfront' ), $progress );
385
+ }
386
+
387
+ /**
388
+ * Calculate progress.
389
+ *
390
+ * @return bool|int|float
391
+ */
392
+ protected function calculate_progress() {
393
+ return false;
394
+ }
395
+
396
  /**
397
  * Handler for the running upgrade actions
398
  */
399
+ public function maybe_handle_action() {
400
  if ( ! isset( $_GET['page'] ) || sanitize_key( $_GET['page'] ) !== $this->as3cf->get_plugin_slug() ) { // input var okay
401
  return;
402
  }
410
  }
411
 
412
  $method_name = 'action_' . sanitize_key( $_GET['action'] ); // input var okay
413
+
414
  if ( method_exists( $this, $method_name ) ) {
415
  call_user_func( array( $this, $method_name ) );
416
  }
421
  *
422
  * @param array $session
423
  */
424
+ protected function upgrade_error( $session ) {
425
  $session['status'] = self::STATUS_ERROR;
426
  $this->save_session( $session );
427
  $this->unschedule();
430
  /**
431
  * Complete the upgrade
432
  */
433
+ protected function upgrade_finished() {
434
  $this->clear_session();
435
  $this->update_saved_upgrade_id();
436
  $this->unschedule();
439
  /**
440
  * Restart upgrade
441
  */
442
+ protected function action_restart_update() {
443
  $this->schedule();
444
  $this->change_status_request( self::STATUS_RUNNING );
445
  }
447
  /**
448
  * Pause upgrade
449
  */
450
+ protected function action_pause_update() {
451
  $this->unschedule();
452
  $this->change_status_request( self::STATUS_PAUSED );
453
  }
457
  *
458
  * @param int $status
459
  */
460
+ protected function change_status_request( $status ) {
461
  $session = $this->get_session();
462
  $session['status'] = $status;
463
  $this->save_session( $session );
470
  /**
471
  * Schedule the cron
472
  */
473
+ protected function schedule() {
474
  $this->as3cf->schedule_event( $this->cron_hook, $this->cron_schedule_key );
475
  }
476
 
477
  /**
478
  * Remove the cron schedule
479
  */
480
+ protected function unschedule() {
481
  $this->as3cf->clear_scheduled_event( $this->cron_hook );
482
  }
483
 
488
  *
489
  * @return array
490
  */
491
+ public function cron_schedules( $schedules ) {
492
  // Add the upgrade interval to the existing schedules.
493
  $schedules[ $this->cron_schedule_key ] = array(
494
  'interval' => $this->cron_interval_in_minutes * 60,
502
  * Get the current status of the upgrade
503
  * See STATUS_* constants in the class declaration above.
504
  */
505
+ protected function get_upgrade_status() {
506
  $session = $this->get_session();
507
 
508
  if ( ! isset( $session['status'] ) ) {
517
  *
518
  * @return array
519
  */
520
+ protected function get_session() {
521
  return get_site_option( 'update_' . $this->upgrade_name . '_session', array() );
522
  }
523
 
526
  *
527
  * @param array $session session data to store
528
  */
529
+ protected function save_session( $session ) {
530
  update_site_option( 'update_' . $this->upgrade_name . '_session', $session );
531
  }
532
 
534
  * Remove the session data to be used between requests
535
  *
536
  */
537
+ protected function clear_session() {
538
  delete_site_option( 'update_' . $this->upgrade_name . '_session' );
539
  }
540
 
543
  *
544
  * @return int|mixed|string|WP_Error
545
  */
546
+ protected function get_saved_upgrade_id() {
547
  return $this->as3cf->get_setting( $this->settings_key, 0 );
548
  }
549
 
550
  /**
551
  * Update the saved upgrade ID
552
  */
553
+ protected function update_saved_upgrade_id() {
554
  $this->as3cf->set_setting( $this->settings_key, $this->upgrade_id );
555
  $this->as3cf->save_settings();
556
  }
560
  *
561
  * @return bool
562
  */
563
+ protected function has_previous_upgrade_completed() {
564
  // Has the previous upgrade completed yet?
565
  $previous_id = $this->upgrade_id - 1;
566
  if ( 0 !== $previous_id && (int) $this->get_saved_upgrade_id() < $previous_id ) {
classes/filters/as3cf-local-to-s3.php ADDED
@@ -0,0 +1,195 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class AS3CF_Local_To_S3 extends AS3CF_Filter {
4
+
5
+ /**
6
+ * Init.
7
+ */
8
+ protected function init() {
9
+ // Hot fix for 4.4 responsive images
10
+ $priority = 1;
11
+
12
+ global $wp_version;
13
+ if ( 0 === version_compare( $wp_version, '4.4' ) ) {
14
+ $priority = 10;
15
+ }
16
+
17
+ // EDD
18
+ add_filter( 'edd_download_files', array( $this, 'filter_edd_download_files' ) );
19
+ // Customizer
20
+ add_filter( 'theme_mod_background_image', array( $this, 'filter_customizer_image' ) );
21
+ add_filter( 'theme_mod_header_image', array( $this, 'filter_customizer_image' ) );
22
+ // Posts
23
+ add_filter( 'the_content', array( $this, 'filter_post' ), $priority, 1 );
24
+ add_filter( 'content_edit_pre', array( $this, 'filter_post' ) );
25
+ // Widgets
26
+ add_filter( 'widget_text', array( $this, 'filter_widget' ) );
27
+ add_filter( 'widget_form_callback', array( $this, 'filter_widget_form' ), 10, 2 );
28
+ }
29
+
30
+ /**
31
+ * Filter widget.
32
+ *
33
+ * @param string $content
34
+ *
35
+ * @return string
36
+ */
37
+ public function filter_widget( $content ) {
38
+ $cache = $this->get_option_cache();
39
+ $to_cache = array();
40
+ $content = $this->process_content( $content, $cache, $to_cache );
41
+
42
+ $this->maybe_update_option_cache( $to_cache );
43
+
44
+ return $content;
45
+ }
46
+
47
+ /**
48
+ * Filter widget form.
49
+ *
50
+ * @param array $instance
51
+ * @param WP_Widget $class
52
+ *
53
+ * @return string
54
+ */
55
+ public function filter_widget_form( $instance, $class ) {
56
+ if ( ! is_a( $class, 'WP_Widget_Text' ) || empty( $instance ) ) {
57
+ return $instance;
58
+ }
59
+
60
+ $cache = $this->get_option_cache();
61
+ $to_cache = array();
62
+ $instance['text'] = $this->process_content( $instance['text'], $cache, $to_cache );
63
+
64
+ $this->maybe_update_option_cache( $to_cache );
65
+
66
+ return $instance;
67
+ }
68
+
69
+ /**
70
+ * Does URL need replacing?
71
+ *
72
+ * @param string $url
73
+ *
74
+ * @return bool
75
+ */
76
+ protected function url_needs_replacing( $url ) {
77
+ $uploads = wp_upload_dir();
78
+ $base_url = $this->as3cf->remove_scheme( $uploads['baseurl'] );
79
+
80
+ if ( false !== strpos( $url, $base_url ) ) {
81
+ // Local URL, perform replacement
82
+ return true;
83
+ }
84
+
85
+ // Remote URL, no replacement needed
86
+ return false;
87
+ }
88
+
89
+ /**
90
+ * Get URL
91
+ *
92
+ * @param int $attachment_id
93
+ * @param null|string $size
94
+ *
95
+ * @return bool|string
96
+ */
97
+ protected function get_url( $attachment_id, $size = null ) {
98
+ return $this->as3cf->get_attachment_url( $attachment_id, null, $size );
99
+ }
100
+
101
+ /**
102
+ * Get base URL.
103
+ *
104
+ * @param int $attachment_id
105
+ *
106
+ * @return string|false
107
+ */
108
+ protected function get_base_url( $attachment_id ) {
109
+ return $this->as3cf->get_attachment_local_url( $attachment_id );
110
+ }
111
+
112
+ /**
113
+ * Get attachment ID from URL.
114
+ *
115
+ * @param string $url
116
+ *
117
+ * @return bool|int
118
+ */
119
+ protected function get_attachment_id_from_url( $url ) {
120
+ global $wpdb;
121
+
122
+ $upload_dir = wp_upload_dir();
123
+ $base_url = $this->as3cf->remove_scheme( $upload_dir['baseurl'] );
124
+ $full_url = $this->as3cf->remove_scheme( $this->as3cf->remove_size_from_filename( $url ) );
125
+ $path = $this->as3cf->decode_filename_in_path( ltrim( str_replace( $base_url, '', $full_url ), '/' ) );
126
+
127
+ if ( isset( $this->query_cache[ $full_url ] ) ) {
128
+ // ID already cached, return
129
+ return $this->query_cache[ $full_url ];
130
+ }
131
+
132
+ $sql = $wpdb->prepare( "
133
+ SELECT post_id FROM {$wpdb->postmeta}
134
+ WHERE meta_key = %s
135
+ AND meta_value = %s
136
+ ", '_wp_attached_file', $path );
137
+
138
+ $result = $wpdb->get_var( $sql );
139
+
140
+ if ( is_null( $result ) ) {
141
+ // Attachment ID not found, return false
142
+ $this->query_cache[ $full_url ] = false;
143
+
144
+ return false;
145
+ }
146
+
147
+ $this->query_cache[ $full_url ] = (int) $result;
148
+
149
+ return (int) $result;
150
+ }
151
+
152
+ /**
153
+ * Normalize find value.
154
+ *
155
+ * @param string $url
156
+ *
157
+ * @return string
158
+ */
159
+ protected function normalize_find_value( $url ) {
160
+ return $this->as3cf->decode_filename_in_path( $url );
161
+ }
162
+
163
+ /**
164
+ * Normalize replace value.
165
+ *
166
+ * @param string $url
167
+ *
168
+ * @return string
169
+ */
170
+ protected function normalize_replace_value( $url ) {
171
+ return $this->as3cf->encode_filename_in_path( $url );
172
+ }
173
+
174
+ /**
175
+ * Post process content.
176
+ *
177
+ * @param string $content
178
+ *
179
+ * @return string
180
+ */
181
+ protected function post_process_content( $content ) {
182
+ return $content;
183
+ }
184
+
185
+ /**
186
+ * Pre replace content.
187
+ *
188
+ * @param string $content
189
+ *
190
+ * @return string
191
+ */
192
+ protected function pre_replace_content( $content ) {
193
+ return $this->remove_aws_query_strings( $content );
194
+ }
195
+ }
classes/filters/as3cf-s3-to-local.php ADDED
@@ -0,0 +1,207 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class AS3CF_S3_To_Local extends AS3CF_Filter {
4
+
5
+ /**
6
+ * Init.
7
+ */
8
+ protected function init() {
9
+ // EDD
10
+ add_filter( 'edd_metabox_save_edd_download_files', array( $this, 'filter_edd_download_files' ) );
11
+ // Customizer
12
+ add_filter( 'pre_set_theme_mod_background_image', array( $this, 'filter_customizer_image' ), 10, 2 );
13
+ add_filter( 'pre_set_theme_mod_header_image', array( $this, 'filter_customizer_image' ), 10, 2 );
14
+ add_filter( 'pre_set_theme_mod_header_image_data', array( $this, 'filter_header_image_data' ), 10, 2 );
15
+ // Posts
16
+ add_filter( 'content_save_pre', array( $this, 'filter_post' ) );
17
+ // Widgets
18
+ add_filter( 'widget_update_callback', array( $this, 'filter_widget_update' ), 10, 4 );
19
+ }
20
+
21
+ /**
22
+ * Filter widget update.
23
+ *
24
+ * @param array $instance
25
+ * @param array $new_instance
26
+ * @param array $old_instance
27
+ * @param WP_Widget $class
28
+ *
29
+ * @return array
30
+ *
31
+ */
32
+ public function filter_widget_update( $instance, $new_instance, $old_instance, $class ) {
33
+ if ( ! is_a( $class, 'WP_Widget_Text' ) || empty( $instance ) ) {
34
+ return $instance;
35
+ }
36
+
37
+ $cache = $this->get_option_cache();
38
+ $to_cache = array();
39
+ $instance['text'] = $this->process_content( $instance['text'], $cache, $to_cache );
40
+
41
+ $this->maybe_update_option_cache( $to_cache );
42
+
43
+ return $instance;
44
+ }
45
+
46
+ /**
47
+ * Does URL need replacing?
48
+ *
49
+ * @param string $url
50
+ *
51
+ * @return bool
52
+ */
53
+ protected function url_needs_replacing( $url ) {
54
+ $uploads = wp_upload_dir();
55
+ $base_url = $this->as3cf->remove_scheme( $uploads['baseurl'] );
56
+
57
+ if ( false !== strpos( $url, $base_url ) ) {
58
+ // Local URL, no replacement needed
59
+ return false;
60
+ }
61
+
62
+ // Remote URL, perform replacement
63
+ return true;
64
+ }
65
+
66
+ /**
67
+ * Get URL
68
+ *
69
+ * @param int $attachment_id
70
+ * @param null|string $size
71
+ *
72
+ * @return bool|string
73
+ */
74
+ protected function get_url( $attachment_id, $size = null ) {
75
+ return $this->as3cf->get_attachment_local_url_size( $attachment_id, $size );
76
+ }
77
+
78
+ /**
79
+ * Get base URL.
80
+ *
81
+ * @param int $attachment_id
82
+ *
83
+ * @return string|false
84
+ */
85
+ protected function get_base_url( $attachment_id ) {
86
+ return $this->as3cf->get_attachment_url( $attachment_id );
87
+ }
88
+
89
+ /**
90
+ * Get attachment ID from URL.
91
+ *
92
+ * @param string $url
93
+ *
94
+ * @return bool|int
95
+ */
96
+ protected function get_attachment_id_from_url( $url ) {
97
+ global $wpdb;
98
+
99
+ $full_url = $this->as3cf->remove_size_from_filename( $url );
100
+ $parts = parse_url( $full_url );
101
+ $path = $this->as3cf->decode_filename_in_path( ltrim( $parts['path'], '/' ) );
102
+
103
+ if ( isset( $this->query_cache[ $full_url ] ) ) {
104
+ // ID already cached, return
105
+ return $this->query_cache[ $full_url ];
106
+ }
107
+
108
+ if ( false !== strpos( $path, '/' ) ) {
109
+ // Remove the first directory to cater for bucket in path domain settings
110
+ $path = explode( '/', $path );
111
+ array_shift( $path );
112
+ $path = implode( '/', $path );
113
+ }
114
+
115
+ $sql = $wpdb->prepare( "
116
+ SELECT * FROM {$wpdb->postmeta}
117
+ WHERE meta_key = %s
118
+ AND meta_value LIKE %s;
119
+ ", 'amazonS3_info', '%' . $path . '%' );
120
+
121
+ $results = $wpdb->get_results( $sql );
122
+
123
+ if ( empty( $results ) ) {
124
+ // No attachment found, return false
125
+ return false;
126
+ }
127
+
128
+ if ( 1 === count( $results ) ) {
129
+ // Attachment matched, return ID
130
+ $this->query_cache[ $full_url ] = $results[0]->post_id;
131
+
132
+ return $results[0]->post_id;
133
+ }
134
+
135
+ $path = ltrim( $parts['path'], '/' );
136
+
137
+ foreach ( $results as $result ) {
138
+ $meta = maybe_unserialize( $result->meta_value );
139
+
140
+ if ( ! isset( $meta['bucket'] ) || ! isset( $meta['key'] ) ) {
141
+ // Can't determine S3 bucket or key, continue
142
+ continue;
143
+ }
144
+
145
+ if ( false !== strpos( $path, $meta['bucket'] ) ) {
146
+ // Bucket in path, remove
147
+ $path = ltrim( str_replace( $meta['bucket'], '', $path ), '/' );
148
+ }
149
+
150
+ if ( $path === $meta['key'] ) {
151
+ // Exact match, return ID
152
+ $this->query_cache[ $full_url ] = $results[0]->post_id;
153
+
154
+ return $result->post_id;
155
+ }
156
+ }
157
+
158
+ // Can't determine ID, return false
159
+ $this->query_cache[ $full_url ] = false;
160
+
161
+ return false;
162
+ }
163
+
164
+ /**
165
+ * Normalize find value.
166
+ *
167
+ * @param string $url
168
+ *
169
+ * @return string
170
+ */
171
+ protected function normalize_find_value( $url ) {
172
+ return $this->as3cf->encode_filename_in_path( $url );
173
+ }
174
+
175
+ /**
176
+ * Normalize replace value.
177
+ *
178
+ * @param string $url
179
+ *
180
+ * @return string
181
+ */
182
+ protected function normalize_replace_value( $url ) {
183
+ return $this->as3cf->decode_filename_in_path( $url );
184
+ }
185
+
186
+ /**
187
+ * Post process content.
188
+ *
189
+ * @param string $content
190
+ *
191
+ * @return string
192
+ */
193
+ protected function post_process_content( $content ) {
194
+ return $this->remove_aws_query_strings( $content );
195
+ }
196
+
197
+ /**
198
+ * Pre replace content.
199
+ *
200
+ * @param string $content
201
+ *
202
+ * @return string
203
+ */
204
+ protected function pre_replace_content( $content ) {
205
+ return $content;
206
+ }
207
+ }
classes/upgrades/as3cf-content-replace-urls.php ADDED
@@ -0,0 +1,622 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // Exit if accessed directly
4
+ if ( ! defined( 'ABSPATH' ) ) {
5
+ exit;
6
+ }
7
+
8
+ /**
9
+ * AS3CF_Upgrade_Content_Replace_URLs Class
10
+ *
11
+ * This class handles replacing all S3 URLs in post
12
+ * content with the local URL.
13
+ *
14
+ * @since 1.2
15
+ */
16
+ class AS3CF_Upgrade_Content_Replace_URLs extends AS3CF_Upgrade {
17
+
18
+ /**
19
+ * @var int Current blog ID
20
+ */
21
+ protected $blog_id;
22
+
23
+ /**
24
+ * @var int Finish time
25
+ */
26
+ protected $finish;
27
+
28
+ /**
29
+ * @var array Session data
30
+ */
31
+ protected $session;
32
+
33
+ /**
34
+ * Initiate the upgrade
35
+ *
36
+ * @param object $as3cf Instance of calling class
37
+ */
38
+ public function __construct( $as3cf ) {
39
+ $this->upgrade_id = 4;
40
+ $this->upgrade_name = 'replace_s3_urls';
41
+ $this->upgrade_type = 'posts';
42
+
43
+ $this->running_update_text = __( 'and ensuring that only the local URL exists in post content.', 'amazon-s3-and-cloudfront' );
44
+
45
+ parent::__construct( $as3cf );
46
+ }
47
+
48
+ /**
49
+ * Fire up the upgrade
50
+ */
51
+ protected function init() {
52
+ $session = array(
53
+ 'status' => self::STATUS_RUNNING,
54
+ 'total_attachments' => 0,
55
+ 'processed_attachments' => 0,
56
+ 'blogs_processed' => false,
57
+ 'blogs' => array(),
58
+ );
59
+
60
+ foreach ( $this->as3cf->get_all_blog_table_prefixes() as $blog_id => $prefix ) {
61
+ $session['blogs'][ $blog_id ] = array(
62
+ 'prefix' => $prefix,
63
+ 'processed' => false,
64
+ 'total_attachments' => null,
65
+ 'last_attachment_id' => null,
66
+ 'highest_post_id' => null,
67
+ 'last_post_id' => null,
68
+ );
69
+ }
70
+
71
+ $this->save_session( $session );
72
+ $this->schedule();
73
+ }
74
+
75
+ /**
76
+ * Count attachments to process. We don't care about the total at this stage
77
+ * so just loop over blogs until attachments exist on S3.
78
+ *
79
+ * @return int
80
+ */
81
+ protected function count_items_to_process() {
82
+ $table_prefixes = $this->as3cf->get_all_blog_table_prefixes();
83
+
84
+ foreach ( $table_prefixes as $blog_id => $table_prefix ) {
85
+ if ( $this->as3cf->count_attachments( $table_prefix, true ) ) {
86
+ return 1;
87
+ }
88
+ }
89
+
90
+ return 0;
91
+ }
92
+
93
+ /**
94
+ * Cron job to update post content, ensuring no S3 URLs exist.
95
+ */
96
+ public function do_upgrade() {
97
+ // Check if the cron should even be running
98
+ if ( $this->get_saved_upgrade_id() >= $this->upgrade_id || $this->get_upgrade_status() !== self::STATUS_RUNNING ) {
99
+ $this->unschedule();
100
+
101
+ return;
102
+ }
103
+
104
+ $limit = apply_filters( 'as3cf_update_' . $this->upgrade_name . '_batch_size', 50 );
105
+ $this->finish = time() + apply_filters( 'as3cf_update_' . $this->upgrade_name . '_time_limit', 10 );
106
+ $this->session = $this->get_session();
107
+
108
+ if ( ! $this->maybe_process_blogs() ) {
109
+ // Blogs still to process but limits reached, return
110
+ $this->save_session( $this->session );
111
+
112
+ return;
113
+ }
114
+
115
+ foreach ( $this->session['blogs'] as $blog_id => $blog ) {
116
+ $this->blog_id = $blog_id;
117
+ $this->as3cf->switch_to_blog( $blog_id );
118
+
119
+ if ( $this->batch_limit_reached() ) {
120
+ // Limits reached, end batch
121
+ break;
122
+ }
123
+
124
+ if ( $blog['processed'] ) {
125
+ // Blog processed, move onto the next
126
+ continue;
127
+ }
128
+
129
+ $offset = $this->session['blogs'][ $blog_id ]['last_attachment_id'];
130
+ $attachments = $this->get_items_to_process( $blog['prefix'], $limit, $offset );
131
+
132
+ if ( empty( $attachments ) ) {
133
+ // All attachments processed, maybe move onto next blog
134
+ $this->session['blogs'][ $blog_id ]['processed'] = true;
135
+
136
+ if ( $this->all_blogs_processed() ) {
137
+ // All blogs processed, complete upgrade
138
+ $this->upgrade_finished();
139
+
140
+ return;
141
+ }
142
+
143
+ continue;
144
+ }
145
+
146
+ foreach ( $attachments as $attachment ) {
147
+ if ( $this->batch_limit_reached() ) {
148
+ // Limits reached, end batch
149
+ break 2;
150
+ }
151
+
152
+ if ( $this->upgrade_item( $attachment ) ) {
153
+ $this->session['processed_attachments'] += 1;
154
+ $this->session['blogs'][ $blog_id ]['last_attachment_id'] = $attachment->ID;
155
+ $this->session['blogs'][ $blog_id ]['last_post_id'] = null;
156
+ } else {
157
+ // Limits reached while processing posts, end batch
158
+ break 2;
159
+ }
160
+ }
161
+
162
+ $this->as3cf->restore_current_blog();
163
+ }
164
+
165
+ $this->save_session( $this->session );
166
+ }
167
+
168
+ /**
169
+ * Maybe process blogs.
170
+ *
171
+ * @return bool
172
+ */
173
+ protected function maybe_process_blogs() {
174
+ if ( $this->session['blogs_processed'] ) {
175
+ // Blogs already processed, return
176
+ return true;
177
+ }
178
+
179
+ foreach ( $this->session['blogs'] as $blog_id => $blog ) {
180
+ if ( $this->batch_limit_reached() ) {
181
+ // Limits reached, return
182
+ return false;
183
+ }
184
+
185
+ if ( is_null( $blog['total_attachments'] ) ) {
186
+ // Handle theme mods
187
+ $this->upgrade_theme_mods( $blog['prefix'] );
188
+
189
+ // Count total attachments
190
+ $count = $this->as3cf->count_attachments( $blog['prefix'], true );
191
+
192
+ // Update blog session data
193
+ $this->session['blogs'][ $blog_id ]['total_attachments'] = $count;
194
+ $this->session['total_attachments'] += $count;
195
+ }
196
+
197
+ if ( is_null( $blog['highest_post_id'] ) ) {
198
+ // Retrieve highest post ID
199
+ $this->session['blogs'][ $blog_id ]['highest_post_id'] = $this->get_highest_post_id( $blog['prefix'] );
200
+ }
201
+ }
202
+
203
+ $this->session['blogs_processed'] = true;
204
+
205
+ return true;
206
+ }
207
+
208
+ /**
209
+ * Get highest post ID.
210
+ *
211
+ * @param string $prefix
212
+ *
213
+ * @return int
214
+ */
215
+ protected function get_highest_post_id( $prefix ) {
216
+ global $wpdb;
217
+
218
+ $sql = "SELECT ID FROM `{$prefix}posts` ORDER BY ID DESC LIMIT 1";
219
+
220
+ return (int) $wpdb->get_var( $sql );
221
+ }
222
+
223
+ /**
224
+ * All blogs processed.
225
+ *
226
+ * @return bool
227
+ */
228
+ protected function all_blogs_processed() {
229
+ foreach ( $this->session['blogs'] as $blog ) {
230
+ if ( ! $blog['processed'] ) {
231
+ return false;
232
+ }
233
+ }
234
+
235
+ return true;
236
+ }
237
+
238
+ /**
239
+ * Upgrade theme mods. Ensures background and header images have local URLs saved to the database.
240
+ *
241
+ * @param string $prefix
242
+ */
243
+ protected function upgrade_theme_mods( $prefix ) {
244
+ global $wpdb;
245
+
246
+ $mods = $wpdb->get_results( "SELECT * FROM `{$prefix}options` WHERE option_name LIKE 'theme_mods_%'" );
247
+
248
+ foreach ( $mods as $mod ) {
249
+ $value = maybe_unserialize( $mod->option_value );
250
+
251
+ if ( isset( $value['background_image'] ) ) {
252
+ $value['background_image'] = $this->as3cf->filter_s3->filter_customizer_image( $value['background_image'] );
253
+ }
254
+
255
+ if ( isset( $value['header_image'] ) ) {
256
+ $value['header_image'] = $this->as3cf->filter_s3->filter_customizer_image( $value['header_image'] );
257
+ }
258
+
259
+ if ( isset( $value['header_image_data'] ) ) {
260
+ $value['header_image_data'] = $this->as3cf->filter_s3->filter_header_image_data( $value['header_image_data'] );
261
+ }
262
+
263
+ $value = maybe_serialize( $value );
264
+
265
+ if ( $value !== $mod->option_value ) {
266
+ $wpdb->query( "UPDATE `{$prefix}options` SET option_value = '{$value}' WHERE option_id = '{$mod->option_id}'" );
267
+ }
268
+ }
269
+ }
270
+
271
+ /**
272
+ * Get items to process.
273
+ *
274
+ * @param string $prefix
275
+ * @param int $limit
276
+ * @param bool|mixed $offset
277
+ *
278
+ * @return array
279
+ */
280
+ protected function get_items_to_process( $prefix, $limit, $offset = false ) {
281
+ global $wpdb;
282
+
283
+ $sql = "SELECT posts.ID FROM `{$prefix}posts` AS posts
284
+ INNER JOIN `{$prefix}postmeta` AS postmeta
285
+ ON posts.ID = postmeta.post_id
286
+ WHERE posts.post_type = 'attachment'
287
+ AND postmeta.meta_key = 'amazonS3_info'";
288
+
289
+ if ( ! empty( $offset ) ) {
290
+ $sql .= " AND posts.ID < '{$offset}'";
291
+ }
292
+
293
+ $sql .= " ORDER BY posts.ID DESC LIMIT {$limit}";
294
+
295
+ return $wpdb->get_results( $sql );
296
+ }
297
+
298
+ /**
299
+ * Upgrade attachment.
300
+ *
301
+ * @param mixed $attachment
302
+ *
303
+ * @return bool
304
+ */
305
+ protected function upgrade_item( $attachment ) {
306
+ $limit = apply_filters( 'as3cf_update_' . $this->upgrade_name . '_sql_limit', 100000 );
307
+ $highest_post_id = $this->session['blogs'][ $this->blog_id ]['highest_post_id'];
308
+ $last_post_id = $this->session['blogs'][ $this->blog_id ]['last_post_id'];
309
+ $where_highest_id = is_null( $last_post_id ) ? $highest_post_id : $last_post_id;
310
+ $where_lowest_id = max( $where_highest_id - $limit, 0 );
311
+
312
+ while ( true ) {
313
+ $this->find_and_replace_attachment_urls( $attachment->ID, $where_lowest_id, $where_highest_id );
314
+
315
+ if ( $this->batch_limit_reached() ) {
316
+ // Batch limit reached
317
+ break;
318
+ }
319
+
320
+ if ( $where_lowest_id <= 0 ) {
321
+ // Batch completed
322
+ return true;
323
+ }
324
+
325
+ $where_highest_id = $where_lowest_id;
326
+ $where_lowest_id = max( $where_lowest_id - $limit, 0 );
327
+ }
328
+
329
+ $this->session['blogs'][ $this->blog_id ]['last_post_id'] = $where_lowest_id;
330
+
331
+ return false;
332
+ }
333
+
334
+ /**
335
+ * Find and replace embedded URLs for an attachment.
336
+ *
337
+ * @param int $attachment_id
338
+ * @param int $where_lowest_id
339
+ * @param int $where_highest_id
340
+ */
341
+ protected function find_and_replace_attachment_urls( $attachment_id, $where_lowest_id, $where_highest_id ) {
342
+ $meta = wp_get_attachment_metadata( $attachment_id, true );
343
+ $backups = get_post_meta( $attachment_id, '_wp_attachment_backup_sizes', true );
344
+ $file_path = get_attached_file( $attachment_id, true );
345
+
346
+ $new_url = $this->as3cf->get_attachment_local_url( $attachment_id );
347
+ $old_url = $this->as3cf->maybe_remove_query_string( $this->as3cf->get_attachment_url( $attachment_id, null, null, $meta, array(), true ) );
348
+
349
+ if ( empty( $old_url ) || empty( $new_url ) ) {
350
+ return;
351
+ }
352
+
353
+ $urls = $this->get_find_and_replace_urls( $file_path, $old_url, $new_url, $meta, $backups );
354
+
355
+ $this->process_pair_replacement( $urls, $where_lowest_id, $where_highest_id );
356
+ }
357
+
358
+ /**
359
+ * Get find and replace URLs.
360
+ *
361
+ * @param string $file_path
362
+ * @param string $old_url
363
+ * @param string $new_url
364
+ * @param array $meta
365
+ * @param array|string $backups
366
+ *
367
+ * @return array
368
+ */
369
+ protected function get_find_and_replace_urls( $file_path, $old_url, $new_url, $meta, $backups = '' ) {
370
+ $url_pairs = array();
371
+ $file_name = basename( $file_path );
372
+ $old_file_name = basename( $old_url );
373
+ $new_file_name = basename( $new_url );
374
+
375
+ // Full size image
376
+ $url_pairs[] = $this->add_url_pair( $file_path, $file_name, $old_url, $old_file_name, $new_url, $new_file_name );
377
+
378
+ if ( isset( $meta['thumb'] ) && $meta['thumb'] ) {
379
+ // Replace URLs for legacy thumbnail of image
380
+ $url_pairs[] = $this->add_url_pair( $file_path, $file_name, $old_url, $old_file_name, $new_url, $new_file_name, $meta['thumb'] );
381
+ }
382
+
383
+ if ( ! empty( $meta['sizes'] ) ) {
384
+ // Replace URLs for intermediate sizes of image
385
+ foreach ( $meta['sizes'] as $key => $size ) {
386
+ if ( ! isset( $size['file'] ) ) {
387
+ continue;
388
+ }
389
+
390
+ $url_pairs[] = $this->add_url_pair( $file_path, $file_name, $old_url, $old_file_name, $new_url, $new_file_name, $size['file'] );
391
+ }
392
+ }
393
+
394
+ if ( ! empty( $backups ) ) {
395
+ // Replace URLs for backup images
396
+ foreach ( $backups as $backup ) {
397
+ if ( ! isset( $backup['file'] ) ) {
398
+ continue;
399
+ }
400
+
401
+ $url_pairs[] = $this->add_url_pair( $file_path, $file_name, $old_url, $old_file_name, $new_url, $new_file_name, $backup['file'] );
402
+ }
403
+ }
404
+
405
+ // Also find encoded file names
406
+ $url_pairs = $this->maybe_add_encoded_url_pairs( $url_pairs );
407
+
408
+ // Remove URL protocols
409
+ $url_pairs = array_map( function ( $url_pair ) {
410
+ $url_pair['old_url'] = $this->as3cf->remove_scheme( $url_pair['old_url'] );
411
+ $url_pair['new_url'] = $this->as3cf->remove_scheme( $url_pair['new_url'] );
412
+
413
+ return $url_pair;
414
+ }, $url_pairs );
415
+
416
+ return apply_filters( 'as3cf_find_replace_url_pairs', $url_pairs, $file_path, $old_url, $new_url, $meta );
417
+ }
418
+
419
+ /**
420
+ * Add URL pair.
421
+ *
422
+ * @param string $file_path
423
+ * @param string $file_name
424
+ * @param string $old_url
425
+ * @param string $old_file_name
426
+ * @param string $new_url
427
+ * @param string $new_file_name
428
+ * @param string|bool $size_file_name
429
+ *
430
+ * @return array
431
+ */
432
+ protected function add_url_pair( $file_path, $file_name, $old_url, $old_file_name, $new_url, $new_file_name, $size_file_name = false ) {
433
+ if ( ! $size_file_name ) {
434
+ return array(
435
+ 'old_path' => $file_path,
436
+ 'old_url' => str_replace( $old_file_name, $file_name, $old_url ),
437
+ 'new_url' => $new_url,
438
+ );
439
+ }
440
+
441
+ return array(
442
+ 'old_path' => str_replace( $file_name, $size_file_name, $file_path ),
443
+ 'old_url' => str_replace( $old_file_name, $size_file_name, $old_url ),
444
+ 'new_url' => str_replace( $new_file_name, $size_file_name, $new_url ),
445
+ );
446
+ }
447
+
448
+ /**
449
+ * Maybe add encoded URL pairs.
450
+ *
451
+ * @param array $url_pairs
452
+ *
453
+ * @return array
454
+ */
455
+ protected function maybe_add_encoded_url_pairs( $url_pairs ) {
456
+ foreach ( $url_pairs as $url_pair ) {
457
+ $file_name = basename( $url_pair['old_url'] );
458
+ $encoded_file_name = $this->as3cf->encode_filename_in_path( $file_name );
459
+
460
+ if ( $file_name !== $encoded_file_name ) {
461
+ $url_pair['old_url'] = str_replace( $file_name, $encoded_file_name, $url_pair['old_url'] );
462
+ $url_pairs[] = $url_pair;
463
+ }
464
+ }
465
+
466
+ return $url_pairs;
467
+ }
468
+
469
+ /**
470
+ * Perform the find and replace in the database of old and new URLs.
471
+ *
472
+ * @param array $url_pairs
473
+ * @param int $where_lowest_id
474
+ * @param int $where_highest_id
475
+ */
476
+ protected function process_pair_replacement( $url_pairs, $where_lowest_id, $where_highest_id ) {
477
+ global $wpdb;
478
+
479
+ $posts = $wpdb->get_results( $this->generate_select_sql( $url_pairs, $where_lowest_id, $where_highest_id ) );
480
+
481
+ if ( empty( $posts ) ) {
482
+ // Nothing to process, move on
483
+ return;
484
+ }
485
+
486
+ // Limit REPLACE statements to 10 per query and INTO to 100 per query
487
+ $url_pairs = array_chunk( $url_pairs, 10 );
488
+ $ids = array_chunk( wp_list_pluck( $posts, 'ID' ), 100 );
489
+
490
+ foreach ( $url_pairs as $url_pairs_chunk ) {
491
+ foreach ( $ids as $ids_chunk ) {
492
+ $wpdb->query( $this->generate_update_sql( $url_pairs_chunk, $ids_chunk ) );
493
+ }
494
+ }
495
+ }
496
+
497
+ /**
498
+ * Generate select SQL.
499
+ *
500
+ * @param array $url_pairs
501
+ * @param int $where_lowest_id
502
+ * @param int $where_highest_id
503
+ *
504
+ * @return string
505
+ */
506
+ protected function generate_select_sql( $url_pairs, $where_lowest_id, $where_highest_id ) {
507
+ global $wpdb;
508
+
509
+ // Get unique URLs without size string and extension
510
+ $paths = array_unique( array_map( function ( $pair ) {
511
+ return $this->as3cf->remove_size_from_filename( $pair['old_url'], true );
512
+ }, $url_pairs ) );
513
+
514
+ $sql = '';
515
+
516
+ foreach ( $paths as $path ) {
517
+ if ( ! empty( $sql ) ) {
518
+ $sql .= " OR ";
519
+ }
520
+
521
+ $sql .= "post_content LIKE '%{$path}%'";
522
+ }
523
+
524
+ return "SELECT ID FROM {$wpdb->posts} WHERE ID > {$where_lowest_id} AND ID <= {$where_highest_id} AND ({$sql})";
525
+ }
526
+
527
+ /**
528
+ * Generate update SQL.
529
+ *
530
+ * @param array $url_pairs
531
+ * @param array $ids
532
+ *
533
+ * @return string
534
+ */
535
+ protected function generate_update_sql( $url_pairs, $ids ) {
536
+ global $wpdb;
537
+
538
+ $ids = implode( ',', $ids );
539
+ $sql = '';
540
+
541
+ foreach ( $url_pairs as $pair ) {
542
+ if ( ! isset( $pair['old_url'] ) || ! isset( $pair['new_url'] ) ) {
543
+ // We need both URLs for the find and replace
544
+ continue;
545
+ }
546
+
547
+ if ( empty( $sql ) ) {
548
+ // First replace statement
549
+ $sql = "REPLACE(post_content, '{$pair['old_url']}', '{$pair['new_url']}')";
550
+ } else {
551
+ // Nested replace statement
552
+ $sql = "REPLACE({$sql}, '{$pair['old_url']}', '{$pair['new_url']}')";
553
+ }
554
+ }
555
+
556
+ return "UPDATE {$wpdb->posts} SET `post_content` = {$sql} WHERE `ID` IN({$ids})";
557
+ }
558
+
559
+ /**
560
+ * Get running message.
561
+ *
562
+ * @return string
563
+ */
564
+ protected function get_running_message() {
565
+ return sprintf( __( '<strong>Running 1.2 Upgrade%1$s</strong><br>A find &amp; replace is running in the background to update URLs in your content. %2$s', 'amazon-s3-and-cloudfront' ), $this->get_progress_text(), $this->get_generic_message() );
566
+ }
567
+
568
+ /**
569
+ * Get paused message.
570
+ *
571
+ * @return string
572
+ */
573
+ protected function get_paused_message() {
574
+ return sprintf( __( '<strong>Paused 1.2 Upgrade</strong><br>The find &amp; replace to update URLs in your content has been paused. %s', 'amazon-s3-and-cloudfront' ), $this->get_generic_message() );
575
+ }
576
+
577
+ /**
578
+ * Get notice message.
579
+ *
580
+ * @return string
581
+ */
582
+ protected function get_generic_message() {
583
+ $link_text = __( 'See our documentation', 'amazon-s3-and-cloudfront' );
584
+ $link = $this->as3cf->dbrains_link( 'https://deliciousbrains.com/wp-offload-s3/doc/version-1-2-upgrade', $link_text );
585
+
586
+ return sprintf( __( '%s for details on why we&#8217;re doing this, why it runs slowly, and how to make it run faster.', 'amazon-s3-and-cloudfront' ), $link );
587
+ }
588
+
589
+ /**
590
+ * Calculate progress.
591
+ *
592
+ * @return bool|int|float
593
+ */
594
+ protected function calculate_progress() {
595
+ $session = $this->get_session();
596
+
597
+ if ( ! isset( $session['total_attachments'] ) || ! isset( $session['processed_attachments'] ) ) {
598
+ // Session data not created, return
599
+ return false;
600
+ }
601
+
602
+ if ( ! $session['blogs_processed'] || is_null( $session['total_attachments'] ) || is_null( $session['processed_attachments'] ) ) {
603
+ // Still processing blogs, return 0
604
+ return 0;
605
+ }
606
+
607
+ return round( $session['processed_attachments'] / $session['total_attachments'] * 100, 2 );
608
+ }
609
+
610
+ /**
611
+ * Batch limit reached.
612
+ *
613
+ * @return bool
614
+ */
615
+ protected function batch_limit_reached() {
616
+ if ( time() >= $this->finish || $this->as3cf->memory_exceeded( 'as3cf_update_' . $this->upgrade_name . '_memory_exceeded' ) ) {
617
+ return true;
618
+ }
619
+
620
+ return false;
621
+ }
622
+ }
classes/upgrades/as3cf-edd-replace-urls.php ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ // Exit if accessed directly
4
+ if ( ! defined( 'ABSPATH' ) ) {
5
+ exit;
6
+ }
7
+
8
+ /**
9
+ * AS3CF_Upgrade_EDD_Replace_URLs Class
10
+ *
11
+ * This class handles replacing all S3 URLs in EDD
12
+ * downloads with the local URL.
13
+ *
14
+ * @since 1.2
15
+ */
16
+ class AS3CF_Upgrade_EDD_Replace_URLs extends AS3CF_Upgrade {
17
+
18
+ /**
19
+ * Initiate the upgrade
20
+ *
21
+ * @param object $as3cf Instance of calling class
22
+ */
23
+ public function __construct( $as3cf ) {
24
+ $this->upgrade_id = 5;
25
+ $this->upgrade_name = 'replace_edd_urls';
26
+ $this->upgrade_type = 'post meta';
27
+
28
+ $this->running_update_text = __( 'and ensuring that only the local URL exists in EDD post meta.', 'amazon-s3-and-cloudfront' );
29
+
30
+ parent::__construct( $as3cf );
31
+ }
32
+
33
+ /**
34
+ * Count attachments to process.
35
+ *
36
+ * @return int
37
+ */
38
+ protected function count_items_to_process() {
39
+ global $wpdb;
40
+
41
+ $table_prefixes = $this->as3cf->get_all_blog_table_prefixes();
42
+ $count = 0;
43
+
44
+ foreach ( $table_prefixes as $blog_id => $table_prefix ) {
45
+ $count += (int) $wpdb->get_var( "SELECT COUNT(*) FROM `{$table_prefix}postmeta` WHERE meta_key = 'edd_download_files'" );
46
+ }
47
+
48
+ return $count;
49
+ }
50
+
51
+ /**
52
+ * Get items to process.
53
+ *
54
+ * @param string $prefix
55
+ * @param int $limit
56
+ * @param bool|mixed $offset
57
+ *
58
+ * @return array
59
+ */
60
+ protected function get_items_to_process( $prefix, $limit, $offset = false ) {
61
+ global $wpdb;
62
+
63
+ $sql = "SELECT * FROM `{$prefix}postmeta` WHERE meta_key = 'edd_download_files'";
64
+
65
+ if ( false !== $offset ) {
66
+ $sql .= " AND meta_id > {$offset->meta_id}";
67
+ }
68
+
69
+ $sql .= " LIMIT {$limit}";
70
+
71
+ return $wpdb->get_results( $sql );
72
+ }
73
+
74
+ /**
75
+ * Upgrade item.
76
+ *
77
+ * @param mixed $item
78
+ *
79
+ * @return bool
80
+ */
81
+ protected function upgrade_item( $item ) {
82
+ $attachments = maybe_unserialize( $item->meta_value );
83
+
84
+ if ( ! is_array( $attachments ) || empty( $attachments ) ) {
85
+ // No attachments to process, return
86
+ return false;
87
+ }
88
+
89
+ foreach ( $attachments as $key => $attachment ) {
90
+ if ( ! isset( $attachment['attachment_id'] ) || ! isset( $attachment['file'] ) ) {
91
+ // Can't determine ID or file, continue
92
+ continue;
93
+ }
94
+
95
+ if ( $url = $this->as3cf->get_attachment_local_url( $attachment['attachment_id'] ) ) {
96
+ $attachments[ $key ]['file'] = $url;
97
+ }
98
+ }
99
+
100
+ update_post_meta( $item->post_id, 'edd_download_files', $attachments );
101
+
102
+ return true;
103
+ }
104
+
105
+ }
classes/upgrades/as3cf-file-sizes.php CHANGED
@@ -42,11 +42,11 @@ class AS3CF_Upgrade_File_Sizes extends AS3CF_Upgrade {
42
  /**
43
  * Get the total file sizes for an attachment and associated files.
44
  *
45
- * @param $attachment
46
  *
47
  * @return bool
48
  */
49
- function upgrade_attachment( $attachment ) {
50
  $s3object = unserialize( $attachment->s3object );
51
  if ( false === $s3object ) {
52
  AS3CF_Error::log( 'Failed to unserialize S3 meta for attachment ' . $attachment->ID . ': ' . $attachment->s3object );
@@ -130,7 +130,7 @@ class AS3CF_Upgrade_File_Sizes extends AS3CF_Upgrade {
130
  *
131
  * @return int
132
  */
133
- function count_attachments_to_process() {
134
  // get the table prefixes for all the blogs
135
  $table_prefixes = $this->as3cf->get_all_blog_table_prefixes();
136
  $all_count = 0;
@@ -146,12 +146,13 @@ class AS3CF_Upgrade_File_Sizes extends AS3CF_Upgrade {
146
  /**
147
  * Get all attachments that don't have region in their S3 meta data for a blog
148
  *
149
- * @param string $prefix
150
- * @param int $limit
 
151
  *
152
- * @return mixed
153
  */
154
- function get_attachments_to_process( $prefix, $limit ) {
155
  $attachments = $this->get_attachments_removed_from_server( $prefix, false, $limit );
156
 
157
  return $attachments;
@@ -166,7 +167,7 @@ class AS3CF_Upgrade_File_Sizes extends AS3CF_Upgrade {
166
  *
167
  * @return mixed
168
  */
169
- function get_s3_attachments( $prefix, $limit = null ) {
170
  global $wpdb;
171
 
172
  $sql = "SELECT pm1.`post_id` as `ID`, pm1.`meta_value` AS 's3object'
@@ -195,7 +196,7 @@ class AS3CF_Upgrade_File_Sizes extends AS3CF_Upgrade {
195
  *
196
  * @return array|int
197
  */
198
- function get_attachments_removed_from_server( $prefix, $count = false, $limit = null ) {
199
  $all_attachments = $this->get_s3_attachments( $prefix, $limit );
200
  $attachments = array();
201
 
42
  /**
43
  * Get the total file sizes for an attachment and associated files.
44
  *
45
+ * @param mixed $attachment
46
  *
47
  * @return bool
48
  */
49
+ protected function upgrade_item( $attachment ) {
50
  $s3object = unserialize( $attachment->s3object );
51
  if ( false === $s3object ) {
52
  AS3CF_Error::log( 'Failed to unserialize S3 meta for attachment ' . $attachment->ID . ': ' . $attachment->s3object );
130
  *
131
  * @return int
132
  */
133
+ protected function count_items_to_process() {
134
  // get the table prefixes for all the blogs
135
  $table_prefixes = $this->as3cf->get_all_blog_table_prefixes();
136
  $all_count = 0;
146
  /**
147
  * Get all attachments that don't have region in their S3 meta data for a blog
148
  *
149
+ * @param string $prefix
150
+ * @param int $limit
151
+ * @param bool|mixed $offset
152
  *
153
+ * @return array
154
  */
155
+ protected function get_items_to_process( $prefix, $limit, $offset = false ) {
156
  $attachments = $this->get_attachments_removed_from_server( $prefix, false, $limit );
157
 
158
  return $attachments;
167
  *
168
  * @return mixed
169
  */
170
+ protected function get_s3_attachments( $prefix, $limit = null ) {
171
  global $wpdb;
172
 
173
  $sql = "SELECT pm1.`post_id` as `ID`, pm1.`meta_value` AS 's3object'
196
  *
197
  * @return array|int
198
  */
199
+ protected function get_attachments_removed_from_server( $prefix, $count = false, $limit = null ) {
200
  $all_attachments = $this->get_s3_attachments( $prefix, $limit );
201
  $attachments = array();
202
 
classes/upgrades/as3cf-meta-wp-error.php CHANGED
@@ -43,11 +43,11 @@ class AS3CF_Upgrade_Meta_WP_Error extends AS3CF_Upgrade {
43
  /**
44
  * Rebuild the attachment metadata for an attachment
45
  *
46
- * @param $attachment
47
  *
48
  * @return bool
49
  */
50
- function upgrade_attachment( $attachment ) {
51
  $s3object = unserialize( $attachment->s3object );
52
  if ( false === $s3object ) {
53
  AS3CF_Error::log( 'Failed to unserialize S3 meta for attachment ' . $attachment->ID . ': ' . $attachment->s3object );
@@ -91,7 +91,7 @@ class AS3CF_Upgrade_Meta_WP_Error extends AS3CF_Upgrade {
91
  *
92
  * @return int
93
  */
94
- function count_attachments_to_process() {
95
  // get the table prefixes for all the blogs
96
  $table_prefixes = $this->as3cf->get_all_blog_table_prefixes();
97
  $all_count = 0;
@@ -107,12 +107,13 @@ class AS3CF_Upgrade_Meta_WP_Error extends AS3CF_Upgrade {
107
  /**
108
  * Get all attachments that don't have region in their S3 meta data for a blog
109
  *
110
- * @param string $prefix
111
- * @param int $limit
 
112
  *
113
- * @return mixed
114
  */
115
- function get_attachments_to_process( $prefix, $limit ) {
116
  $attachments = $this->get_attachments_with_error_metadata( $prefix, false, $limit );
117
 
118
  return $attachments;
@@ -127,7 +128,7 @@ class AS3CF_Upgrade_Meta_WP_Error extends AS3CF_Upgrade {
127
  *
128
  * @return array|int
129
  */
130
- function get_attachments_with_error_metadata( $prefix, $count = false, $limit = null ) {
131
  global $wpdb;
132
 
133
  $sql = "FROM `{$prefix}postmeta` pm1
43
  /**
44
  * Rebuild the attachment metadata for an attachment
45
  *
46
+ * @param mixed $attachment
47
  *
48
  * @return bool
49
  */
50
+ protected function upgrade_item( $attachment ) {
51
  $s3object = unserialize( $attachment->s3object );
52
  if ( false === $s3object ) {
53
  AS3CF_Error::log( 'Failed to unserialize S3 meta for attachment ' . $attachment->ID . ': ' . $attachment->s3object );
91
  *
92
  * @return int
93
  */
94
+ protected function count_items_to_process() {
95
  // get the table prefixes for all the blogs
96
  $table_prefixes = $this->as3cf->get_all_blog_table_prefixes();
97
  $all_count = 0;
107
  /**
108
  * Get all attachments that don't have region in their S3 meta data for a blog
109
  *
110
+ * @param string $prefix
111
+ * @param int $limit
112
+ * @param bool|mixed $offset
113
  *
114
+ * @return array
115
  */
116
+ protected function get_items_to_process( $prefix, $limit, $offset = false ) {
117
  $attachments = $this->get_attachments_with_error_metadata( $prefix, false, $limit );
118
 
119
  return $attachments;
128
  *
129
  * @return array|int
130
  */
131
+ protected function get_attachments_with_error_metadata( $prefix, $count = false, $limit = null ) {
132
  global $wpdb;
133
 
134
  $sql = "FROM `{$prefix}postmeta` pm1
classes/upgrades/as3cf-region-meta.php CHANGED
@@ -41,11 +41,11 @@ class AS3CF_Upgrade_Region_Meta extends AS3CF_Upgrade {
41
  /**
42
  * Get the region for the bucket where an attachment is located, update the S3 meta.
43
  *
44
- * @param $attachment
45
  *
46
  * @return bool
47
  */
48
- function upgrade_attachment( $attachment ) {
49
  $s3object = unserialize( $attachment->s3object );
50
  if ( false === $s3object ) {
51
  AS3CF_Error::log( 'Failed to unserialize S3 meta for attachment ' . $attachment->ID . ': ' . $attachment->s3object );
@@ -71,7 +71,7 @@ class AS3CF_Upgrade_Region_Meta extends AS3CF_Upgrade {
71
  *
72
  * @return int
73
  */
74
- function count_attachments_to_process() {
75
  // get the table prefixes for all the blogs
76
  $table_prefixes = $this->as3cf->get_all_blog_table_prefixes();
77
  $all_count = 0;
@@ -87,12 +87,13 @@ class AS3CF_Upgrade_Region_Meta extends AS3CF_Upgrade {
87
  /**
88
  * Get all attachments that don't have region in their S3 meta data for a blog
89
  *
90
- * @param string $prefix
91
- * @param int $limit
 
92
  *
93
- * @return mixed
94
  */
95
- function get_attachments_to_process( $prefix, $limit ) {
96
  $attachments = $this->get_attachments_without_region_results( $prefix, false, $limit );
97
 
98
  return $attachments;
@@ -104,7 +105,7 @@ class AS3CF_Upgrade_Region_Meta extends AS3CF_Upgrade {
104
  *
105
  * @return int
106
  */
107
- function count_attachments_without_region( $prefix ) {
108
  $count = $this->get_attachments_without_region_results( $prefix, true );
109
 
110
  return $count;
@@ -119,7 +120,7 @@ class AS3CF_Upgrade_Region_Meta extends AS3CF_Upgrade {
119
  *
120
  * @return mixed
121
  */
122
- function get_attachments_without_region_results( $prefix, $count = false, $limit = null ) {
123
  global $wpdb;
124
 
125
  $sql = " FROM `{$prefix}postmeta`
41
  /**
42
  * Get the region for the bucket where an attachment is located, update the S3 meta.
43
  *
44
+ * @param mixed $attachment
45
  *
46
  * @return bool
47
  */
48
+ protected function upgrade_item( $attachment ) {
49
  $s3object = unserialize( $attachment->s3object );
50
  if ( false === $s3object ) {
51
  AS3CF_Error::log( 'Failed to unserialize S3 meta for attachment ' . $attachment->ID . ': ' . $attachment->s3object );
71
  *
72
  * @return int
73
  */
74
+ protected function count_items_to_process() {
75
  // get the table prefixes for all the blogs
76
  $table_prefixes = $this->as3cf->get_all_blog_table_prefixes();
77
  $all_count = 0;
87
  /**
88
  * Get all attachments that don't have region in their S3 meta data for a blog
89
  *
90
+ * @param string $prefix
91
+ * @param int $limit
92
+ * @param bool|mixed $offset
93
  *
94
+ * @return array
95
  */
96
+ protected function get_items_to_process( $prefix, $limit, $offset = false ) {
97
  $attachments = $this->get_attachments_without_region_results( $prefix, false, $limit );
98
 
99
  return $attachments;
105
  *
106
  * @return int
107
  */
108
+ protected function count_attachments_without_region( $prefix ) {
109
  $count = $this->get_attachments_without_region_results( $prefix, true );
110
 
111
  return $count;
120
  *
121
  * @return mixed
122
  */
123
+ protected function get_attachments_without_region_results( $prefix, $count = false, $limit = null ) {
124
  global $wpdb;
125
 
126
  $sql = " FROM `{$prefix}postmeta`
languages/amazon-s3-and-cloudfront-en.pot CHANGED
@@ -1,6 +1,6 @@
1
  # SOME DESCRIPTIVE TITLE.
2
  # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3
- # This file is distributed under the same license as the amazon-s3-and-cloudfront package.
4
  # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5
  #
6
  #, fuzzy
@@ -8,7 +8,7 @@ msgid ""
8
  msgstr ""
9
  "Project-Id-Version: amazon-s3-and-cloudfront\n"
10
  "Report-Msgid-Bugs-To: nom@deliciousbrains.com\n"
11
- "POT-Creation-Date: 2016-09-01 10:33-0400\n"
12
  "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
  "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,135 +17,135 @@ msgstr ""
17
  "Content-Type: text/plain; charset=UTF-8\n"
18
  "Content-Transfer-Encoding: 8bit\n"
19
 
20
- #: classes/amazon-s3-and-cloudfront.php:101
21
  msgid "Offload S3 Lite"
22
  msgstr ""
23
 
24
- #: classes/amazon-s3-and-cloudfront.php:102
25
  msgid "S3 and CloudFront"
26
  msgstr ""
27
 
28
- #: classes/amazon-s3-and-cloudfront.php:196
29
  #: view/bucket-setting.php:18
30
  msgid "defined in wp-config.php"
31
  msgstr ""
32
 
33
- #: classes/amazon-s3-and-cloudfront.php:803
34
  msgid "Upload aborted by filter 'as3cf_pre_upload_attachment'"
35
  msgstr ""
36
 
37
- #: classes/amazon-s3-and-cloudfront.php:814
38
  #, php-format
39
  msgid "File %s does not exist"
40
  msgstr ""
41
 
42
- #: classes/amazon-s3-and-cloudfront.php:825
43
  #, php-format
44
  msgid "Mime type %s is not allowed"
45
  msgstr ""
46
 
47
- #: classes/amazon-s3-and-cloudfront.php:911
48
  #, php-format
49
  msgid "Error uploading %s to S3: %s"
50
  msgstr ""
51
 
52
- #: classes/amazon-s3-and-cloudfront.php:1900
53
  msgid "Cheatin&#8217; eh?"
54
  msgstr ""
55
 
56
- #: classes/amazon-s3-and-cloudfront.php:1904
57
  msgid "You do not have sufficient permissions to access this page."
58
  msgstr ""
59
 
60
- #: classes/amazon-s3-and-cloudfront.php:1910
61
  msgid "No bucket name provided."
62
  msgstr ""
63
 
64
- #: classes/amazon-s3-and-cloudfront.php:2195
65
  msgid "Error Getting Bucket Region"
66
  msgstr ""
67
 
68
- #: classes/amazon-s3-and-cloudfront.php:2196
69
  #, php-format
70
  msgid "There was an error attempting to get the region of the bucket %s: %s"
71
  msgstr ""
72
 
73
- #: classes/amazon-s3-and-cloudfront.php:2316
74
  msgid ""
75
  "This is a test file to check if the user has write permission to S3. Delete "
76
  "me if found."
77
  msgstr ""
78
 
79
- #: classes/amazon-s3-and-cloudfront.php:2348
80
  #, php-format
81
  msgid ""
82
  "There was an error attempting to check the permissions of the bucket %s: %s"
83
  msgstr ""
84
 
85
- #: classes/amazon-s3-and-cloudfront.php:2405
86
  msgid "Error creating bucket"
87
  msgstr ""
88
 
89
- #: classes/amazon-s3-and-cloudfront.php:2406
90
  msgid "Bucket name too short."
91
  msgstr ""
92
 
93
- #: classes/amazon-s3-and-cloudfront.php:2407
94
  msgid "Bucket name too long."
95
  msgstr ""
96
 
97
- #: classes/amazon-s3-and-cloudfront.php:2408
98
  msgid ""
99
  "Invalid character. Bucket names can contain lowercase letters, numbers, "
100
  "periods and hyphens."
101
  msgstr ""
102
 
103
- #: classes/amazon-s3-and-cloudfront.php:2409
104
  msgid "Error saving bucket"
105
  msgstr ""
106
 
107
- #: classes/amazon-s3-and-cloudfront.php:2410
108
  msgid "Error fetching buckets"
109
  msgstr ""
110
 
111
- #: classes/amazon-s3-and-cloudfront.php:2411
112
  msgid "Error getting URL preview: "
113
  msgstr ""
114
 
115
- #: classes/amazon-s3-and-cloudfront.php:2412
116
  msgid "The changes you made will be lost if you navigate away from this page"
117
  msgstr ""
118
 
119
- #: classes/amazon-s3-and-cloudfront.php:2413
120
  msgid "Getting diagnostic info..."
121
  msgstr ""
122
 
123
- #: classes/amazon-s3-and-cloudfront.php:2414
124
  msgid "Error getting diagnostic info: "
125
  msgstr ""
126
 
127
- #: classes/amazon-s3-and-cloudfront.php:2482
128
  msgid "Cheatin' eh?"
129
  msgstr ""
130
 
131
- #: classes/amazon-s3-and-cloudfront.php:2590
132
  msgctxt "Show the media library tab"
133
  msgid "Media Library"
134
  msgstr ""
135
 
136
- #: classes/amazon-s3-and-cloudfront.php:2591
137
  msgctxt "Show the support tab"
138
  msgid "Support"
139
  msgstr ""
140
 
141
- #: classes/amazon-s3-and-cloudfront.php:2806
142
  #, php-format
143
  msgid ""
144
  "<strong>WP Offload S3</strong> &mdash; The file %s has been given %s "
145
  "permissions on Amazon S3."
146
  msgstr ""
147
 
148
- #: classes/amazon-s3-and-cloudfront.php:2825
149
  msgid ""
150
  "<strong>WP Offload S3 Requirement Missing</strong> &mdash; Looks like you "
151
  "don't have an image manipulation library installed on this server and "
@@ -153,11 +153,11 @@ msgid ""
153
  "Please setup GD or ImageMagick."
154
  msgstr ""
155
 
156
- #: classes/amazon-s3-and-cloudfront.php:3485
157
  msgid "Quick Start Guide"
158
  msgstr ""
159
 
160
- #: classes/amazon-s3-and-cloudfront.php:3487
161
  #, php-format
162
  msgid ""
163
  "Looks like we don't have write access to this bucket. It's likely that the "
@@ -166,7 +166,7 @@ msgid ""
166
  "correctly."
167
  msgstr ""
168
 
169
- #: classes/amazon-s3-and-cloudfront.php:3489
170
  #, php-format
171
  msgid ""
172
  "Looks like we don't have access to the buckets. It's likely that the user "
@@ -174,50 +174,39 @@ msgid ""
174
  "Please see our %s for instructions on setting up permissions correctly."
175
  msgstr ""
176
 
177
- #: classes/amazon-s3-and-cloudfront.php:3640
178
  msgid "WP Offload S3 Activation"
179
  msgstr ""
180
 
181
- #: classes/amazon-s3-and-cloudfront.php:3641
182
  msgid ""
183
  "WP Offload S3 Lite and WP Offload S3 cannot both be active. We've "
184
  "automatically deactivated WP Offload S3 Lite."
185
  msgstr ""
186
 
187
- #: classes/amazon-s3-and-cloudfront.php:3643
188
  msgid "WP Offload S3 Lite Activation"
189
  msgstr ""
190
 
191
- #: classes/amazon-s3-and-cloudfront.php:3644
192
  msgid ""
193
  "WP Offload S3 Lite and WP Offload S3 cannot both be active. We've "
194
  "automatically deactivated WP Offload S3."
195
  msgstr ""
196
 
197
- #: classes/amazon-s3-and-cloudfront.php:3718
198
  msgid "More info"
199
  msgstr ""
200
 
201
- #: classes/amazon-s3-and-cloudfront.php:3810
202
- #: classes/amazon-s3-and-cloudfront.php:3833
203
  msgid "this doc"
204
  msgstr ""
205
 
206
- #: classes/amazon-s3-and-cloudfront.php:3812
207
- #: classes/amazon-s3-and-cloudfront.php:3835
208
  msgid "WP Offload S3 Feature Removed"
209
  msgstr ""
210
 
211
- #: classes/amazon-s3-and-cloudfront.php:3813
212
- #, php-format
213
- msgid ""
214
- "The \"Copy HiDPI (@2x) Images\" feature has been removed as of version 1.1 "
215
- "of WP Offload S3. It looks like you had this feature turned on. Please see "
216
- "%s for why we removed this feature and how you can continue copying @2x "
217
- "images to S3."
218
- msgstr ""
219
-
220
- #: classes/amazon-s3-and-cloudfront.php:3836
221
  #, php-format
222
  msgid ""
223
  "You had the \"Always non-SSL\" option selected in your settings, but we've "
@@ -228,23 +217,52 @@ msgid ""
228
  "to the old behavior."
229
  msgstr ""
230
 
231
- #: classes/as3cf-notices.php:441
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
232
  msgid "Error dismissing notice."
233
  msgstr ""
234
 
235
- #: classes/as3cf-notices.php:456
236
  msgid "Invalid notice ID."
237
  msgstr ""
238
 
239
- #: classes/as3cf-plugin-compatibility.php:202
240
  msgid "WP Offload S3 Compatibility Addons"
241
  msgstr ""
242
 
243
- #: classes/as3cf-plugin-compatibility.php:204
244
  msgid "compatibility addons"
245
  msgstr ""
246
 
247
- #: classes/as3cf-plugin-compatibility.php:205
248
  #, php-format
249
  msgid ""
250
  "To get WP Offload S3 to work with certain 3rd party plugins, you might need "
@@ -253,7 +271,7 @@ msgid ""
253
  "about each addon to determine if you need it or not."
254
  msgstr ""
255
 
256
- #: classes/as3cf-plugin-compatibility.php:211
257
  #, php-format
258
  msgid ""
259
  "You will need to purchase a license to get access to these addons. If you're "
@@ -261,62 +279,100 @@ msgid ""
261
  "to %s."
262
  msgstr ""
263
 
264
- #: classes/as3cf-plugin-compatibility.php:212
265
  msgid "View Licenses"
266
  msgstr ""
267
 
268
- #: classes/as3cf-plugin-compatibility.php:667
269
  #, php-format
270
  msgid "The local directory %s does not exist and could not be created."
271
  msgstr ""
272
 
273
- #: classes/as3cf-plugin-compatibility.php:668
274
- #: classes/as3cf-plugin-compatibility.php:682
275
  #: classes/upgrades/as3cf-meta-wp-error.php:72
276
  #, php-format
277
  msgid "There was an error attempting to download the file %s from S3: %s"
278
  msgstr ""
279
 
280
- #: classes/as3cf-upgrade.php:283
 
 
 
 
 
 
 
 
 
 
 
 
281
  #, php-format
282
  msgid ""
283
- "<strong>Running %s Update</strong> &mdash; We&#8217;re going through all the "
284
- "Media Library items uploaded to S3 %s This will be done quietly in the "
285
- "background, processing a small batch of Media Library items every %d "
286
  "minutes. There should be no noticeable impact on your server&#8217;s "
287
  "performance."
288
  msgstr ""
289
 
290
- #: classes/as3cf-upgrade.php:284
291
- msgid "Pause Update"
 
 
 
292
  msgstr ""
293
 
294
- #: classes/as3cf-upgrade.php:288
295
  #, php-format
296
  msgid ""
297
- "<strong>%s Update Paused</strong> &mdash; Updating Media Library %s has been "
298
- "paused."
 
299
  msgstr ""
300
 
301
- #: classes/as3cf-upgrade.php:289
302
- msgid "Restart Update"
 
303
  msgstr ""
304
 
305
- #: classes/as3cf-upgrade.php:292
 
 
 
 
 
 
 
 
 
306
  #, php-format
307
  msgid ""
308
- "<strong>Error Updating %s</strong> &mdash; We ran into some errors "
309
- "attempting to update the %s for all your Media Library items that have been "
310
- "uploaded to S3. Please check your error log for details. (#%d)"
311
  msgstr ""
312
 
313
- #: classes/as3cf-upgrade.php:293
314
- msgid "Try Run It Again"
 
 
 
 
 
 
 
315
  msgstr ""
316
 
317
- #: classes/as3cf-upgrade.php:408
318
  #, php-format
319
- msgid "Every %d Minutes"
 
 
 
 
 
 
320
  msgstr ""
321
 
322
  #: classes/upgrades/as3cf-file-sizes.php:37
@@ -415,6 +471,14 @@ msgstr ""
415
  msgid "The %s plugin has been deactivated."
416
  msgstr ""
417
 
 
 
 
 
 
 
 
 
418
  #: view/bucket-select.php:3
419
  msgid "Change bucket"
420
  msgstr ""
@@ -603,62 +667,62 @@ msgstr ""
603
  msgid "By default the path is the same as your local WordPress files."
604
  msgstr ""
605
 
606
- #: view/settings.php:127
607
  msgid "Year/Month"
608
  msgstr ""
609
 
610
- #: view/settings.php:129
611
  msgid "Add the Year/Month in the URL."
612
  msgstr ""
613
 
614
- #: view/settings.php:142
615
  msgid "Force HTTPS"
616
  msgstr ""
617
 
618
- #: view/settings.php:144
619
  msgid ""
620
  "By default we use HTTPS when the request is HTTPS and regular HTTP when the "
621
  "request is HTTP, but you may want to force the use of HTTPS always, "
622
  "regardless of the request."
623
  msgstr ""
624
 
625
- #: view/settings.php:151
626
  msgid "Advanced Options"
627
  msgstr ""
628
 
629
- #: view/settings.php:160
630
  msgid "Remove Files From Server"
631
  msgstr ""
632
 
633
- #: view/settings.php:161
634
  msgid "Once a file has been copied to S3, remove it from the local server."
635
  msgstr ""
636
 
637
- #: view/settings.php:165
638
  msgid ""
639
  "<strong>Broken URLs</strong> &mdash; There will be broken URLs for files "
640
  "that don't exist locally. You can fix this by enabling <strong>Rewrite File "
641
  "URLs</strong> to use the S3 URLs."
642
  msgstr ""
643
 
644
- #: view/settings.php:176
645
  #, php-format
646
  msgid ""
647
  "<strong>Warning</strong> &mdash; Some plugins depend on the file being "
648
  "present on the local server and may not work when the file is removed. %s"
649
  msgstr ""
650
 
651
- #: view/settings.php:194
652
  msgid "Object Versioning"
653
  msgstr ""
654
 
655
- #: view/settings.php:196
656
  msgid ""
657
  "Append a timestamp to the S3 file path. Recommended when using CloudFront so "
658
  "you don't have to worry about cache invalidation."
659
  msgstr ""
660
 
661
- #: view/settings.php:203
662
  msgid "Save Changes"
663
  msgstr ""
664
 
@@ -667,61 +731,57 @@ msgid "Upload existing Media Library to S3"
667
  msgstr ""
668
 
669
  #: view/sidebar.php:11
670
- msgid "Find & replace file URLs in content"
671
- msgstr ""
672
-
673
- #: view/sidebar.php:12
674
  msgid "Manage S3 files in WordPress"
675
  msgstr ""
676
 
677
- #: view/sidebar.php:13
678
  msgid "Assets addon - Serve your CSS & JS from S3/CloudFront"
679
  msgstr ""
680
 
681
- #: view/sidebar.php:14
682
  msgid "WooCommerce addon"
683
  msgstr ""
684
 
685
- #: view/sidebar.php:15
686
  msgid "Easy Digital Downloads addon"
687
  msgstr ""
688
 
689
- #: view/sidebar.php:16
690
  msgid "PriorityExpert™ email support"
691
  msgstr ""
692
 
693
- #: view/sidebar.php:19
694
  msgid "Visit deliciousbrains.com &rarr;"
695
  msgstr ""
696
 
697
- #: view/sidebar.php:26
698
  msgid "Get 20% Off!"
699
  msgstr ""
700
 
701
- #: view/sidebar.php:29
702
  #, php-format
703
  msgid ""
704
  "Submit your name and email and we’ll send you a coupon for 20% off your "
705
  "upgrade."
706
  msgstr ""
707
 
708
- #: view/sidebar.php:33
709
  msgid "Your Email"
710
  msgstr ""
711
 
712
- #: view/sidebar.php:37
713
  msgid "First Name"
714
  msgstr ""
715
 
716
- #: view/sidebar.php:41
717
  msgid "Last Name"
718
  msgstr ""
719
 
720
- #: view/sidebar.php:48
721
  msgid "Send me the coupon"
722
  msgstr ""
723
 
724
- #: view/sidebar.php:52
725
  msgid ""
726
  "We promise we will not use your email for anything else and you can "
727
  "unsubscribe with 1-click anytime."
1
  # SOME DESCRIPTIVE TITLE.
2
  # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3
+ # This file is distributed under the same license as the PACKAGE package.
4
  # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5
  #
6
  #, fuzzy
8
  msgstr ""
9
  "Project-Id-Version: amazon-s3-and-cloudfront\n"
10
  "Report-Msgid-Bugs-To: nom@deliciousbrains.com\n"
11
+ "POT-Creation-Date: 2016-09-29 13:21+0100\n"
12
  "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
  "Language-Team: LANGUAGE <LL@li.org>\n"
17
  "Content-Type: text/plain; charset=UTF-8\n"
18
  "Content-Transfer-Encoding: 8bit\n"
19
 
20
+ #: classes/amazon-s3-and-cloudfront.php:108
21
  msgid "Offload S3 Lite"
22
  msgstr ""
23
 
24
+ #: classes/amazon-s3-and-cloudfront.php:109
25
  msgid "S3 and CloudFront"
26
  msgstr ""
27
 
28
+ #: classes/amazon-s3-and-cloudfront.php:214
29
  #: view/bucket-setting.php:18
30
  msgid "defined in wp-config.php"
31
  msgstr ""
32
 
33
+ #: classes/amazon-s3-and-cloudfront.php:851
34
  msgid "Upload aborted by filter 'as3cf_pre_upload_attachment'"
35
  msgstr ""
36
 
37
+ #: classes/amazon-s3-and-cloudfront.php:862
38
  #, php-format
39
  msgid "File %s does not exist"
40
  msgstr ""
41
 
42
+ #: classes/amazon-s3-and-cloudfront.php:873
43
  #, php-format
44
  msgid "Mime type %s is not allowed"
45
  msgstr ""
46
 
47
+ #: classes/amazon-s3-and-cloudfront.php:958
48
  #, php-format
49
  msgid "Error uploading %s to S3: %s"
50
  msgstr ""
51
 
52
+ #: classes/amazon-s3-and-cloudfront.php:2134
53
  msgid "Cheatin&#8217; eh?"
54
  msgstr ""
55
 
56
+ #: classes/amazon-s3-and-cloudfront.php:2138
57
  msgid "You do not have sufficient permissions to access this page."
58
  msgstr ""
59
 
60
+ #: classes/amazon-s3-and-cloudfront.php:2144
61
  msgid "No bucket name provided."
62
  msgstr ""
63
 
64
+ #: classes/amazon-s3-and-cloudfront.php:2429
65
  msgid "Error Getting Bucket Region"
66
  msgstr ""
67
 
68
+ #: classes/amazon-s3-and-cloudfront.php:2430
69
  #, php-format
70
  msgid "There was an error attempting to get the region of the bucket %s: %s"
71
  msgstr ""
72
 
73
+ #: classes/amazon-s3-and-cloudfront.php:2550
74
  msgid ""
75
  "This is a test file to check if the user has write permission to S3. Delete "
76
  "me if found."
77
  msgstr ""
78
 
79
+ #: classes/amazon-s3-and-cloudfront.php:2582
80
  #, php-format
81
  msgid ""
82
  "There was an error attempting to check the permissions of the bucket %s: %s"
83
  msgstr ""
84
 
85
+ #: classes/amazon-s3-and-cloudfront.php:2639
86
  msgid "Error creating bucket"
87
  msgstr ""
88
 
89
+ #: classes/amazon-s3-and-cloudfront.php:2640
90
  msgid "Bucket name too short."
91
  msgstr ""
92
 
93
+ #: classes/amazon-s3-and-cloudfront.php:2641
94
  msgid "Bucket name too long."
95
  msgstr ""
96
 
97
+ #: classes/amazon-s3-and-cloudfront.php:2642
98
  msgid ""
99
  "Invalid character. Bucket names can contain lowercase letters, numbers, "
100
  "periods and hyphens."
101
  msgstr ""
102
 
103
+ #: classes/amazon-s3-and-cloudfront.php:2643
104
  msgid "Error saving bucket"
105
  msgstr ""
106
 
107
+ #: classes/amazon-s3-and-cloudfront.php:2644
108
  msgid "Error fetching buckets"
109
  msgstr ""
110
 
111
+ #: classes/amazon-s3-and-cloudfront.php:2645
112
  msgid "Error getting URL preview: "
113
  msgstr ""
114
 
115
+ #: classes/amazon-s3-and-cloudfront.php:2646
116
  msgid "The changes you made will be lost if you navigate away from this page"
117
  msgstr ""
118
 
119
+ #: classes/amazon-s3-and-cloudfront.php:2647
120
  msgid "Getting diagnostic info..."
121
  msgstr ""
122
 
123
+ #: classes/amazon-s3-and-cloudfront.php:2648
124
  msgid "Error getting diagnostic info: "
125
  msgstr ""
126
 
127
+ #: classes/amazon-s3-and-cloudfront.php:2716
128
  msgid "Cheatin' eh?"
129
  msgstr ""
130
 
131
+ #: classes/amazon-s3-and-cloudfront.php:2824
132
  msgctxt "Show the media library tab"
133
  msgid "Media Library"
134
  msgstr ""
135
 
136
+ #: classes/amazon-s3-and-cloudfront.php:2825
137
  msgctxt "Show the support tab"
138
  msgid "Support"
139
  msgstr ""
140
 
141
+ #: classes/amazon-s3-and-cloudfront.php:3041
142
  #, php-format
143
  msgid ""
144
  "<strong>WP Offload S3</strong> &mdash; The file %s has been given %s "
145
  "permissions on Amazon S3."
146
  msgstr ""
147
 
148
+ #: classes/amazon-s3-and-cloudfront.php:3060
149
  msgid ""
150
  "<strong>WP Offload S3 Requirement Missing</strong> &mdash; Looks like you "
151
  "don't have an image manipulation library installed on this server and "
153
  "Please setup GD or ImageMagick."
154
  msgstr ""
155
 
156
+ #: classes/amazon-s3-and-cloudfront.php:3723
157
  msgid "Quick Start Guide"
158
  msgstr ""
159
 
160
+ #: classes/amazon-s3-and-cloudfront.php:3725
161
  #, php-format
162
  msgid ""
163
  "Looks like we don't have write access to this bucket. It's likely that the "
166
  "correctly."
167
  msgstr ""
168
 
169
+ #: classes/amazon-s3-and-cloudfront.php:3727
170
  #, php-format
171
  msgid ""
172
  "Looks like we don't have access to the buckets. It's likely that the user "
174
  "Please see our %s for instructions on setting up permissions correctly."
175
  msgstr ""
176
 
177
+ #: classes/amazon-s3-and-cloudfront.php:3878
178
  msgid "WP Offload S3 Activation"
179
  msgstr ""
180
 
181
+ #: classes/amazon-s3-and-cloudfront.php:3879
182
  msgid ""
183
  "WP Offload S3 Lite and WP Offload S3 cannot both be active. We've "
184
  "automatically deactivated WP Offload S3 Lite."
185
  msgstr ""
186
 
187
+ #: classes/amazon-s3-and-cloudfront.php:3881
188
  msgid "WP Offload S3 Lite Activation"
189
  msgstr ""
190
 
191
+ #: classes/amazon-s3-and-cloudfront.php:3882
192
  msgid ""
193
  "WP Offload S3 Lite and WP Offload S3 cannot both be active. We've "
194
  "automatically deactivated WP Offload S3."
195
  msgstr ""
196
 
197
+ #: classes/amazon-s3-and-cloudfront.php:3956
198
  msgid "More info"
199
  msgstr ""
200
 
201
+ #: classes/amazon-s3-and-cloudfront.php:4047
 
202
  msgid "this doc"
203
  msgstr ""
204
 
205
+ #: classes/amazon-s3-and-cloudfront.php:4049
 
206
  msgid "WP Offload S3 Feature Removed"
207
  msgstr ""
208
 
209
+ #: classes/amazon-s3-and-cloudfront.php:4050
 
 
 
 
 
 
 
 
 
210
  #, php-format
211
  msgid ""
212
  "You had the \"Always non-SSL\" option selected in your settings, but we've "
217
  "to the old behavior."
218
  msgstr ""
219
 
220
+ #: classes/amazon-s3-and-cloudfront.php:4078
221
+ #: classes/amazon-s3-and-cloudfront.php:4171
222
+ msgid "Amazon S3"
223
+ msgstr ""
224
+
225
+ #: classes/amazon-s3-and-cloudfront.php:4172
226
+ msgctxt "Amazon S3 bucket"
227
+ msgid "Bucket"
228
+ msgstr ""
229
+
230
+ #: classes/amazon-s3-and-cloudfront.php:4173
231
+ msgctxt "Path to file on Amazon S3"
232
+ msgid "Path"
233
+ msgstr ""
234
+
235
+ #: classes/amazon-s3-and-cloudfront.php:4174
236
+ msgctxt "Location of Amazon S3 bucket"
237
+ msgid "Region"
238
+ msgstr ""
239
+
240
+ #: classes/amazon-s3-and-cloudfront.php:4175
241
+ msgctxt "Access control list of the file on Amazon S3"
242
+ msgid "Access"
243
+ msgstr ""
244
+
245
+ #: classes/amazon-s3-and-cloudfront.php:4176
246
+ msgid "URL"
247
+ msgstr ""
248
+
249
+ #: classes/as3cf-notices.php:438
250
  msgid "Error dismissing notice."
251
  msgstr ""
252
 
253
+ #: classes/as3cf-notices.php:453
254
  msgid "Invalid notice ID."
255
  msgstr ""
256
 
257
+ #: classes/as3cf-plugin-compatibility.php:192
258
  msgid "WP Offload S3 Compatibility Addons"
259
  msgstr ""
260
 
261
+ #: classes/as3cf-plugin-compatibility.php:194
262
  msgid "compatibility addons"
263
  msgstr ""
264
 
265
+ #: classes/as3cf-plugin-compatibility.php:195
266
  #, php-format
267
  msgid ""
268
  "To get WP Offload S3 to work with certain 3rd party plugins, you might need "
271
  "about each addon to determine if you need it or not."
272
  msgstr ""
273
 
274
+ #: classes/as3cf-plugin-compatibility.php:201
275
  #, php-format
276
  msgid ""
277
  "You will need to purchase a license to get access to these addons. If you're "
279
  "to %s."
280
  msgstr ""
281
 
282
+ #: classes/as3cf-plugin-compatibility.php:202
283
  msgid "View Licenses"
284
  msgstr ""
285
 
286
+ #: classes/as3cf-plugin-compatibility.php:664
287
  #, php-format
288
  msgid "The local directory %s does not exist and could not be created."
289
  msgstr ""
290
 
291
+ #: classes/as3cf-plugin-compatibility.php:665
292
+ #: classes/as3cf-plugin-compatibility.php:677
293
  #: classes/upgrades/as3cf-meta-wp-error.php:72
294
  #, php-format
295
  msgid "There was an error attempting to download the file %s from S3: %s"
296
  msgstr ""
297
 
298
+ #: classes/as3cf-upgrade.php:298
299
+ msgid "Pause Update"
300
+ msgstr ""
301
+
302
+ #: classes/as3cf-upgrade.php:306
303
+ msgid "Restart Update"
304
+ msgstr ""
305
+
306
+ #: classes/as3cf-upgrade.php:310
307
+ msgid "Try Run It Again"
308
+ msgstr ""
309
+
310
+ #: classes/as3cf-upgrade.php:333
311
  #, php-format
312
  msgid ""
313
+ "<strong>Running %1$s Update%2$s</strong> &mdash; We&#8217;re going through "
314
+ "all the Media Library items uploaded to S3 %3$s This will be done quietly in "
315
+ "the background, processing a small batch of Media Library items every %4$d "
316
  "minutes. There should be no noticeable impact on your server&#8217;s "
317
  "performance."
318
  msgstr ""
319
 
320
+ #: classes/as3cf-upgrade.php:347
321
+ #, php-format
322
+ msgid ""
323
+ "<strong>%1$s Update Paused%2$s</strong> &mdash; Updating Media Library %3$s "
324
+ "has been paused."
325
  msgstr ""
326
 
327
+ #: classes/as3cf-upgrade.php:360
328
  #, php-format
329
  msgid ""
330
+ "<strong>Error Updating %1$s</strong> &mdash; We ran into some errors "
331
+ "attempting to update the %2$s for all your Media Library items that have "
332
+ "been uploaded to S3. Please check your error log for details. (#%3$d)"
333
  msgstr ""
334
 
335
+ #: classes/as3cf-upgrade.php:384
336
+ #, php-format
337
+ msgid " (%s%% Complete)"
338
  msgstr ""
339
 
340
+ #: classes/as3cf-upgrade.php:495
341
+ #, php-format
342
+ msgid "Every %d Minutes"
343
+ msgstr ""
344
+
345
+ #: classes/upgrades/as3cf-content-replace-urls.php:43
346
+ msgid "and ensuring that only the local URL exists in post content."
347
+ msgstr ""
348
+
349
+ #: classes/upgrades/as3cf-content-replace-urls.php:565
350
  #, php-format
351
  msgid ""
352
+ "<strong>Running 1.2 Upgrade%1$s</strong><br>A find &amp; replace is running "
353
+ "in the background to update URLs in your content. %2$s"
 
354
  msgstr ""
355
 
356
+ #: classes/upgrades/as3cf-content-replace-urls.php:574
357
+ #, php-format
358
+ msgid ""
359
+ "<strong>Paused 1.2 Upgrade</strong><br>The find &amp; replace to update URLs "
360
+ "in your content has been paused. %s"
361
+ msgstr ""
362
+
363
+ #: classes/upgrades/as3cf-content-replace-urls.php:583
364
+ msgid "See our documentation"
365
  msgstr ""
366
 
367
+ #: classes/upgrades/as3cf-content-replace-urls.php:586
368
  #, php-format
369
+ msgid ""
370
+ "%s for details on why we&#8217;re doing this, why it runs slowly, and how to "
371
+ "make it run faster."
372
+ msgstr ""
373
+
374
+ #: classes/upgrades/as3cf-edd-replace-urls.php:28
375
+ msgid "and ensuring that only the local URL exists in EDD post meta."
376
  msgstr ""
377
 
378
  #: classes/upgrades/as3cf-file-sizes.php:37
471
  msgid "The %s plugin has been deactivated."
472
  msgstr ""
473
 
474
+ #: view/attachment-metabox.php:4
475
+ msgid "This item has not been copied to S3 yet."
476
+ msgstr ""
477
+
478
+ #: view/attachment-metabox.php:29
479
+ msgid "File does not exist on server"
480
+ msgstr ""
481
+
482
  #: view/bucket-select.php:3
483
  msgid "Change bucket"
484
  msgstr ""
667
  msgid "By default the path is the same as your local WordPress files."
668
  msgstr ""
669
 
670
+ #: view/settings.php:120
671
  msgid "Year/Month"
672
  msgstr ""
673
 
674
+ #: view/settings.php:122
675
  msgid "Add the Year/Month in the URL."
676
  msgstr ""
677
 
678
+ #: view/settings.php:135
679
  msgid "Force HTTPS"
680
  msgstr ""
681
 
682
+ #: view/settings.php:137
683
  msgid ""
684
  "By default we use HTTPS when the request is HTTPS and regular HTTP when the "
685
  "request is HTTP, but you may want to force the use of HTTPS always, "
686
  "regardless of the request."
687
  msgstr ""
688
 
689
+ #: view/settings.php:144
690
  msgid "Advanced Options"
691
  msgstr ""
692
 
693
+ #: view/settings.php:153
694
  msgid "Remove Files From Server"
695
  msgstr ""
696
 
697
+ #: view/settings.php:154
698
  msgid "Once a file has been copied to S3, remove it from the local server."
699
  msgstr ""
700
 
701
+ #: view/settings.php:158
702
  msgid ""
703
  "<strong>Broken URLs</strong> &mdash; There will be broken URLs for files "
704
  "that don't exist locally. You can fix this by enabling <strong>Rewrite File "
705
  "URLs</strong> to use the S3 URLs."
706
  msgstr ""
707
 
708
+ #: view/settings.php:169
709
  #, php-format
710
  msgid ""
711
  "<strong>Warning</strong> &mdash; Some plugins depend on the file being "
712
  "present on the local server and may not work when the file is removed. %s"
713
  msgstr ""
714
 
715
+ #: view/settings.php:187
716
  msgid "Object Versioning"
717
  msgstr ""
718
 
719
+ #: view/settings.php:189
720
  msgid ""
721
  "Append a timestamp to the S3 file path. Recommended when using CloudFront so "
722
  "you don't have to worry about cache invalidation."
723
  msgstr ""
724
 
725
+ #: view/settings.php:196
726
  msgid "Save Changes"
727
  msgstr ""
728
 
731
  msgstr ""
732
 
733
  #: view/sidebar.php:11
 
 
 
 
734
  msgid "Manage S3 files in WordPress"
735
  msgstr ""
736
 
737
+ #: view/sidebar.php:12
738
  msgid "Assets addon - Serve your CSS & JS from S3/CloudFront"
739
  msgstr ""
740
 
741
+ #: view/sidebar.php:13
742
  msgid "WooCommerce addon"
743
  msgstr ""
744
 
745
+ #: view/sidebar.php:14
746
  msgid "Easy Digital Downloads addon"
747
  msgstr ""
748
 
749
+ #: view/sidebar.php:15
750
  msgid "PriorityExpert™ email support"
751
  msgstr ""
752
 
753
+ #: view/sidebar.php:18
754
  msgid "Visit deliciousbrains.com &rarr;"
755
  msgstr ""
756
 
757
+ #: view/sidebar.php:25
758
  msgid "Get 20% Off!"
759
  msgstr ""
760
 
761
+ #: view/sidebar.php:28
762
  #, php-format
763
  msgid ""
764
  "Submit your name and email and we’ll send you a coupon for 20% off your "
765
  "upgrade."
766
  msgstr ""
767
 
768
+ #: view/sidebar.php:32
769
  msgid "Your Email"
770
  msgstr ""
771
 
772
+ #: view/sidebar.php:36
773
  msgid "First Name"
774
  msgstr ""
775
 
776
+ #: view/sidebar.php:40
777
  msgid "Last Name"
778
  msgstr ""
779
 
780
+ #: view/sidebar.php:47
781
  msgid "Send me the coupon"
782
  msgstr ""
783
 
784
+ #: view/sidebar.php:51
785
  msgid ""
786
  "We promise we will not use your email for anything else and you can "
787
  "unsubscribe with 1-click anytime."
readme.txt CHANGED
@@ -2,8 +2,8 @@
2
  Contributors: bradt, deliciousbrains
3
  Tags: uploads, amazon, s3, amazon s3, mirror, admin, media, cdn, cloudfront
4
  Requires at least: 4.4
5
- Tested up to: 4.6
6
- Stable tag: 1.0.5
7
  License: GPLv3
8
 
9
  Copies files to Amazon S3 as they are uploaded to the Media Library. Optionally configure Amazon CloudFront for faster delivery.
@@ -21,7 +21,6 @@ If you're adding this plugin to a site that's been around for a while, your exis
21
  **PRO Upgrade with Email Support and More Features**
22
 
23
  * Upload existing Media Library to Amazon S3
24
- * Find & replace file URLs in content
25
  * Control Amazon S3 files from the Media Library
26
  * [Assets addon](https://deliciousbrains.com/wp-offload-s3/?utm_source=wordpress.org&utm_medium=web&utm_content=desc&utm_campaign=os3-free-plugin#assets-addon) - Serve your CSS & JS from Amazon S3/CloudFront
27
  * [WooCommerce addon](https://deliciousbrains.com/wp-offload-s3/?utm_source=wordpress.org&utm_medium=web&utm_content=desc&utm_campaign=os3-free-plugin#woocommerce-addon)
@@ -58,25 +57,40 @@ You can see the minimum requirements [here](https://deliciousbrains.com/wp-offlo
58
 
59
  == Upgrade Notice ==
60
 
 
 
 
61
  = 0.6 =
62
  This version requires PHP 5.3.3+ and the Amazon Web Services plugin
63
 
64
  == Changelog ==
65
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  = WP Offload S3 Lite 1.0.5 - 2016-09-01 =
67
  * New: Compatibility with WordPress 4.6
68
  * Improvement: No longer delete plugin data on uninstall. Manual removal possible, as per this [doc](https://deliciousbrains.com/wp-offload-s3/doc/uninstall/)
69
 
70
  = WP Offload S3 Lite 1.0.4 - 2016-05-30 =
71
- * New: Now using simpler Force HTTPS setting, removed redundant Always Use HTTP setting.
72
- * New: `as3cf_cloudfront_path_parts` filter allows changing served CloudFront path (useful when distribution pulls subdirectory).
73
- * Improvement: Better compatibility with non-standard notices from other plugins and themes.
74
- * Improvement: Added basic auth and proxy info to diagnostic info.
75
- * Improvement: Added `allow_url_fopen` status to diagnostic info.
76
- * Improvement: Added memory usage to diagnostic info.
77
- * Improvement: Ensure notice text is 800px or less in width.
78
- * Improvement: Reduced database queries on settings screen.
79
- * Bug fix: Properly handle _wp_attachment_data metadata when it is a serialized WP_Error.
80
 
81
  = WP Offload S3 Lite 1.0.3 - 2016-03-23 =
82
  * Bug fix: Don't replace srcset URLs when Rewrite File URLs option disabled
@@ -97,7 +111,7 @@ This version requires PHP 5.3.3+ and the Amazon Web Services plugin
97
  * Improvement: Far future expiration header set by default
98
  * Improvement: Newly created bucket now immediately appears in the bucket list
99
  * Improvement: Cleanup user meta on uninstall
100
- * Improvement: WP Retina 2x integration [removed](https://deliciousbrains.com/wp-offload-s3/doc/copy-hidpi-2x-images-support/)
101
  * Bug fix: Year/Month folder structure on S3 not created if the 'Organise my uploads into month and year-based folders' WordPress setting is disabled
102
  * Bug fix: Responsive srcset PHP notices
103
  * Bug fix: Compatibility addon notices displayed to non-admin users
2
  Contributors: bradt, deliciousbrains
3
  Tags: uploads, amazon, s3, amazon s3, mirror, admin, media, cdn, cloudfront
4
  Requires at least: 4.4
5
+ Tested up to: 4.6.1
6
+ Stable tag: 1.1
7
  License: GPLv3
8
 
9
  Copies files to Amazon S3 as they are uploaded to the Media Library. Optionally configure Amazon CloudFront for faster delivery.
21
  **PRO Upgrade with Email Support and More Features**
22
 
23
  * Upload existing Media Library to Amazon S3
 
24
  * Control Amazon S3 files from the Media Library
25
  * [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
26
  * [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)
57
 
58
  == Upgrade Notice ==
59
 
60
+ = 1.1 =
61
+ This is a major change, which ensures S3 URLs are no longer saved in post content. Instead, local URLs are filtered on page generation and replaced with the S3 version. If you depend on the S3 URLs being stored in post content you will need to make modifications to support this version.
62
+
63
  = 0.6 =
64
  This version requires PHP 5.3.3+ and the Amazon Web Services plugin
65
 
66
  == Changelog ==
67
 
68
+ = WP Offload S3 Lite 1.1 - 2016-09-29 =
69
+ * New: Filter post content. S3 URLs will no longer be saved to the database
70
+ * New: Upgrade routine to replace all S3 URLs in content with local URLs
71
+ * New: Support for theme custom logos
72
+ * New: Control the ACL for intermediate image sizes using the `as3cf_upload_acl_sizes` filter
73
+ * Bug fix: File names containing special characters double encoded
74
+ * Bug fix: `srcset` not working for file names containing special characters
75
+ * Bug fix: Incorrect placeholder text for 'Path' option
76
+ * Bug fix: Objects in root of bucket not deleted when removed from the Media Library
77
+ * Bug fix: No longer use deprecated functions in WordPress 4.6
78
+ * Bug fix: Don't delete local file when 'Remove Files From Server' enabled and upload to S3 fails
79
+
80
  = WP Offload S3 Lite 1.0.5 - 2016-09-01 =
81
  * New: Compatibility with WordPress 4.6
82
  * Improvement: No longer delete plugin data on uninstall. Manual removal possible, as per this [doc](https://deliciousbrains.com/wp-offload-s3/doc/uninstall/)
83
 
84
  = WP Offload S3 Lite 1.0.4 - 2016-05-30 =
85
+ * New: Now using simpler Force HTTPS setting, removed redundant Always Use HTTP setting
86
+ * New: `as3cf_cloudfront_path_parts` filter allows changing served CloudFront path (useful when distribution pulls subdirectory)
87
+ * Improvement: Better compatibility with non-standard notices from other plugins and themes
88
+ * Improvement: Added basic auth and proxy info to diagnostic info
89
+ * Improvement: Added `allow_url_fopen` status to diagnostic info
90
+ * Improvement: Added memory usage to diagnostic info
91
+ * Improvement: Ensure notice text is 800px or less in width
92
+ * Improvement: Reduced database queries on settings screen
93
+ * Bug fix: Properly handle _wp_attachment_data metadata when it is a serialized WP_Error
94
 
95
  = WP Offload S3 Lite 1.0.3 - 2016-03-23 =
96
  * Bug fix: Don't replace srcset URLs when Rewrite File URLs option disabled
111
  * Improvement: Far future expiration header set by default
112
  * Improvement: Newly created bucket now immediately appears in the bucket list
113
  * Improvement: Cleanup user meta on uninstall
114
+ * Improvement: WP Retina 2x integration removed
115
  * Bug fix: Year/Month folder structure on S3 not created if the 'Organise my uploads into month and year-based folders' WordPress setting is disabled
116
  * Bug fix: Responsive srcset PHP notices
117
  * Bug fix: Compatibility addon notices displayed to non-admin users
view/attachment-metabox.php ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div class="s3-details">
2
+ <?php if ( ! $s3object ) : ?>
3
+ <div class="misc-pub-section">
4
+ <em class="not-copied"><?php _e( 'This item has not been copied to S3 yet.', 'amazon-s3-and-cloudfront' ); ?></em>
5
+ </div>
6
+ <?php else : ?>
7
+ <div class="misc-pub-section">
8
+ <div class="s3-key"><?php echo $this->get_media_action_strings( 'bucket' ); ?>:</div>
9
+ <input type="text" class="widefat" readonly="readonly" value="<?php echo $s3object['bucket']; ?>">
10
+ </div>
11
+ <div class="misc-pub-section">
12
+ <div class="s3-key"><?php echo $this->get_media_action_strings( 'key' ); ?>:</div>
13
+ <input type="text" class="widefat" readonly="readonly" value="<?php echo $s3object['key']; ?>">
14
+ </div>
15
+ <?php if ( isset( $s3object['region'] ) && $s3object['region'] ) : ?>
16
+ <div class="misc-pub-section">
17
+ <div class="s3-key"><?php echo $this->get_media_action_strings( 'region' ); ?>:</div>
18
+ <div class="s3-value"><?php echo $s3object['region']; ?></div>
19
+ </div>
20
+ <?php endif; ?>
21
+ <div class="misc-pub-section">
22
+ <div class="s3-key"><?php echo $this->get_media_action_strings( 'acl' ); ?>:</div>
23
+ <div class="s3-value">
24
+ <?php echo $this->get_acl_value_string( $s3object['acl'] ); ?>
25
+ </div>
26
+ </div>
27
+ <?php if ( $user_can_perform_actions && ! $local_file_exists ) : ?>
28
+ <div class="misc-pub-section">
29
+ <div class="not-copied"><?php _e( 'File does not exist on server', 'amazon-s3-and-cloudfront' ); ?></div>
30
+ <a href="<?php echo $this->get_media_action_url( 'download', $post->ID, $sendback ); ?>">
31
+ <?php echo $this->get_media_action_strings( 'download' ); ?>
32
+ </a>
33
+ </div>
34
+ <?php endif; ?>
35
+ <?php endif; ?>
36
+ <div class="clear"></div>
37
+ </div>
38
+ <?php if ( $user_can_perform_actions && ( $s3object || $local_file_exists ) ) : ?>
39
+ <div class="s3-actions">
40
+ <?php if ( $s3object ) : ?>
41
+ <div class="remove-action">
42
+ <a href="<?php echo $this->get_media_action_url( 'remove', $post->ID, $sendback ); ?>" class="<?php echo ( ! $local_file_exists ) ? 'local-warning' : ''; ?>">
43
+ <?php echo $this->get_media_action_strings( 'remove' ); ?>
44
+ </a>
45
+ </div>
46
+ <?php endif; ?>
47
+ <?php if ( $local_file_exists ) : ?>
48
+ <div class="copy-action">
49
+ <a href="<?php echo $this->get_media_action_url( 'copy', $post->ID, $sendback ); ?>" class="button button-secondary">
50
+ <?php echo $this->get_media_action_strings( 'copy' ); ?>
51
+ </a>
52
+ </div>
53
+ <?php endif; ?>
54
+ <div class="clear"></div>
55
+ </div>
56
+ <?php endif; ?>
view/settings.php CHANGED
@@ -104,15 +104,8 @@ $selected_bucket_prefix = $this->get_object_prefix(); ?>
104
  <?php echo $this->settings_more_info_link( 'object-prefix' ); ?>
105
  </p>
106
  <p class="as3cf-setting enable-object-prefix <?php echo ( $this->get_setting( 'enable-object-prefix' ) ) ? '' : 'hide'; // xss ok ?>">
107
- <?php
108
- $args = $this->get_setting_args( 'object-prefix' );
109
- if ( false === $this->get_defined_setting( 'object-prefix', false ) ) {
110
- $placeholder = 'placeholder="placeholder"';
111
- } else {
112
- $placeholder = '';
113
- }
114
- ?>
115
- <input type="text" name="object-prefix" value="<?php echo esc_attr( $this->get_setting( 'object-prefix' ) ); ?>" size="30" placeholder="<?php echo $placeholder; ?>" <?php echo $args['disabled_attr']; ?> />
116
  </p>
117
  </td>
118
  </tr>
104
  <?php echo $this->settings_more_info_link( 'object-prefix' ); ?>
105
  </p>
106
  <p class="as3cf-setting enable-object-prefix <?php echo ( $this->get_setting( 'enable-object-prefix' ) ) ? '' : 'hide'; // xss ok ?>">
107
+ <?php $args = $this->get_setting_args( 'object-prefix' ); ?>
108
+ <input type="text" name="object-prefix" value="<?php echo esc_attr( $this->get_setting( 'object-prefix' ) ); ?>" size="30" placeholder="<?php echo $this->get_default_object_prefix(); ?>" <?php echo $args['disabled_attr']; ?> />
 
 
 
 
 
 
 
109
  </p>
110
  </td>
111
  </tr>
view/sidebar.php CHANGED
@@ -8,7 +8,6 @@
8
 
9
  <ul>
10
  <li><?php echo wptexturize( __( 'Upload existing Media Library to S3', 'amazon-s3-and-cloudfront' ) ); // xss ok ?></li>
11
- <li><?php echo wptexturize( __( 'Find & replace file URLs in content', 'amazon-s3-and-cloudfront' ) ); // xss ok ?></li>
12
  <li><?php echo wptexturize( __( 'Manage S3 files in WordPress', 'amazon-s3-and-cloudfront' ) ); // xss ok ?></li>
13
  <li><?php echo wptexturize( __( 'Assets addon - Serve your CSS & JS from S3/CloudFront', 'amazon-s3-and-cloudfront' ) ); // xss ok ?></li>
14
  <li><?php echo wptexturize( __( 'WooCommerce addon', 'amazon-s3-and-cloudfront' ) ); // xss ok ?></li>
8
 
9
  <ul>
10
  <li><?php echo wptexturize( __( 'Upload existing Media Library to S3', 'amazon-s3-and-cloudfront' ) ); // xss ok ?></li>
 
11
  <li><?php echo wptexturize( __( 'Manage S3 files in WordPress', 'amazon-s3-and-cloudfront' ) ); // xss ok ?></li>
12
  <li><?php echo wptexturize( __( 'Assets addon - Serve your CSS & JS from S3/CloudFront', 'amazon-s3-and-cloudfront' ) ); // xss ok ?></li>
13
  <li><?php echo wptexturize( __( 'WooCommerce addon', 'amazon-s3-and-cloudfront' ) ); // xss ok ?></li>
wordpress-s3.php CHANGED
@@ -4,7 +4,7 @@ Plugin Name: WP Offload S3 Lite
4
  Plugin URI: http://wordpress.org/extend/plugins/amazon-s3-and-cloudfront/
5
  Description: Automatically copies media uploads to Amazon S3 for storage and delivery. Optionally configure Amazon CloudFront for even faster delivery.
6
  Author: Delicious Brains
7
- Version: 1.0.5
8
  Author URI: http://deliciousbrains.com/
9
  Network: True
10
  Text Domain: amazon-s3-and-cloudfront
@@ -26,9 +26,9 @@ Domain Path: /languages/
26
  // Then completely rewritten.
27
  */
28
 
29
- $GLOBALS['aws_meta']['amazon-s3-and-cloudfront']['version'] = '1.0.5';
30
 
31
- $aws_plugin_version_required = '0.3.6';
32
 
33
  require_once dirname( __FILE__ ) . '/classes/wp-aws-compatibility-check.php';
34
  require_once dirname( __FILE__ ) . '/classes/as3cf-utils.php';
@@ -65,6 +65,11 @@ function as3cf_init( $aws ) {
65
  require_once $abspath . '/classes/upgrades/as3cf-region-meta.php';
66
  require_once $abspath . '/classes/upgrades/as3cf-file-sizes.php';
67
  require_once $abspath . '/classes/upgrades/as3cf-meta-wp-error.php';
 
 
 
 
 
68
  require_once $abspath . '/classes/as3cf-notices.php';
69
  require_once $abspath . '/classes/as3cf-stream-wrapper.php';
70
  require_once $abspath . '/classes/as3cf-plugin-compatibility.php';
4
  Plugin URI: http://wordpress.org/extend/plugins/amazon-s3-and-cloudfront/
5
  Description: Automatically copies media uploads to Amazon S3 for storage and delivery. Optionally configure Amazon CloudFront for even faster delivery.
6
  Author: Delicious Brains
7
+ Version: 1.1
8
  Author URI: http://deliciousbrains.com/
9
  Network: True
10
  Text Domain: amazon-s3-and-cloudfront
26
  // Then completely rewritten.
27
  */
28
 
29
+ $GLOBALS['aws_meta']['amazon-s3-and-cloudfront']['version'] = '1.1';
30
 
31
+ $aws_plugin_version_required = '1.0';
32
 
33
  require_once dirname( __FILE__ ) . '/classes/wp-aws-compatibility-check.php';
34
  require_once dirname( __FILE__ ) . '/classes/as3cf-utils.php';
65
  require_once $abspath . '/classes/upgrades/as3cf-region-meta.php';
66
  require_once $abspath . '/classes/upgrades/as3cf-file-sizes.php';
67
  require_once $abspath . '/classes/upgrades/as3cf-meta-wp-error.php';
68
+ require_once $abspath . '/classes/upgrades/as3cf-content-replace-urls.php';
69
+ require_once $abspath . '/classes/upgrades/as3cf-edd-replace-urls.php';
70
+ require_once $abspath . '/classes/as3cf-filter.php';
71
+ require_once $abspath . '/classes/filters/as3cf-local-to-s3.php';
72
+ require_once $abspath . '/classes/filters/as3cf-s3-to-local.php';
73
  require_once $abspath . '/classes/as3cf-notices.php';
74
  require_once $abspath . '/classes/as3cf-stream-wrapper.php';
75
  require_once $abspath . '/classes/as3cf-plugin-compatibility.php';