WP Offload S3 Lite - Version 0.7

Version Description

  • 2014-12-04 =
  • New: Proper S3 region subdomain in URLs for buckets not in the US Standard region (e.g. https://s3-us-west-2.amazonaws.com/...)
  • New: Update all existing attachment meta with bucket region (automatically runs in the background)
  • New: Get secure URL for different image sizes (iamzozo)
  • New: S3 bucket can be set with constant in wp-config.php (dberube)
  • New: Filter for allowing/disallowing file types: as3cf_allowed_mime_types
  • New: Filter to cancel upload to S3 for any reason: as3cf_pre_update_attachment_metadata
  • New: Sidebar with email opt-in
  • Improvement: Show warning when S3 policy is read-only
  • Improvement: Tooltip added to clarify option
  • Improvement: Move object versioning option to make it clear it does not require CloudFront
  • Improvement: By default only allow file types in get_allowed_mime_types() to be uploaded to S3
  • Improvement: Compatibility with WPML Media plugin
  • Bug Fix: Edited images not removed on S3 when restoring image and IMAGE_EDIT_OVERWRITE true
  • Bug Fix: File names with certain characters broken not working
  • Bug Fix: Edited image uploaded to incorrect month folder
  • Bug Fix: When creating a new bucket the bucket select box appears empty on success
  • Bug Fix: SSL not working in regions other than US Standard
  • Bug Fix: 'Error uploading' and 'Error removing local file' messages when editing an image
  • Bug Fix: Upload and delete failing when bucket is non-US-region and bucket name contains dot
  • Bug Fix: S3 file overwritten when file with same name uploaded and local file removed (dataferret)
  • Bug Fix: Manually resized images not uploaded (gmauricio)
Download this release

Release Info

Developer bradt
Plugin Icon 128x128 WP Offload S3 Lite
Version 0.7
Comparing to
See all releases

Code changes from version 0.6.1 to 0.7

assets/Gruntfile.js ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ module.exports = function(grunt) {
2
+
3
+ grunt.initConfig({
4
+ pkg: grunt.file.readJSON('package.json'),
5
+ uglify: {
6
+ build: {
7
+ files: {
8
+ 'js/script.min.js': 'js/script.js'
9
+ }
10
+ }
11
+ },
12
+ compass: {
13
+ dist: {
14
+ options: {
15
+ }
16
+ }
17
+ },
18
+ watch: {
19
+ js: {
20
+ files: ['js/*'],
21
+ tasks: ['uglify']
22
+ },
23
+ sass: {
24
+ files: ['sass/*'],
25
+ tasks: ['compass']
26
+ }
27
+ }
28
+ });
29
+
30
+ grunt.loadNpmTasks('grunt-contrib-uglify');
31
+ grunt.loadNpmTasks('grunt-contrib-watch');
32
+ grunt.loadNpmTasks('grunt-contrib-compass');
33
+
34
+ grunt.registerTask('default', ['uglify','compass']);
35
+
36
+ };
assets/css/styles.css CHANGED
@@ -1 +1 @@
1
- .as3cf-settings select.bucket{margin-bottom:5px;width:380px}
1
+ .aws-main .error{max-width:835px}.aws-main .error pre{background:#eaeaea;background:rgba(0,0,0,0.07);display:block;padding:10px 15px}.aws-main .error pre code{padding:0;background:none}.aws-main .as3cf-notice{max-width:835px}.as3cf-settings{position:relative;width:550px;min-height:800px}.as3cf-settings select.bucket{margin-bottom:5px;width:380px}.as3cf-settings .form-table td{padding-left:0;padding-right:0}.as3cf-settings .form-table tr:first-child h3{margin-top:0}.as3cf-settings .tooltip{position:relative;z-index:2;cursor:pointer}.as3cf-settings .tooltip:before,.as3cf-settings .tooltip:after{visibility:hidden;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=0);opacity:0;pointer-events:none}.as3cf-settings .tooltip:before{position:absolute;bottom:150%;left:50%;margin-bottom:5px;margin-left:-250px;padding:10px;width:500px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;background-color:#000;background-color:rgba(51,51,51,0.9);color:#fff;content:attr(data-tooltip);text-align:center;font-size:14px;line-height:1.3}.as3cf-settings .tooltip:after{position:absolute;bottom:150%;left:50%;margin-left:-5px;width:0;border-top:5px solid #000;border-top:5px solid rgba(51,51,51,0.9);border-right:5px solid transparent;border-left:5px solid transparent;content:" ";font-size:0;line-height:0}.as3cf-settings .tooltip:hover:before,.as3cf-settings .tooltip:hover:after{visibility:visible;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);opacity:1}.as3cf-banner img{display:block}.as3cf-sidebar{position:absolute;top:17px;right:-312px;width:292px}.as3cf-sidebar .block{padding:20px;border:1px solid #ccc}.as3cf-sidebar .subscribe{border-top:none}.as3cf-sidebar .subscribe h2{padding:0;margin:0;margin-bottom:0.5em;color:#666;font-size:20px;line-height:1.2em;float:none}.as3cf-sidebar .subscribe h3{font-size:16px;margin:0}.as3cf-sidebar .subscribe p{margin:0}.as3cf-sidebar .subscribe .intro{margin-bottom:1em;line-height:1.4}.as3cf-sidebar .subscribe ul{margin-left:20px;list-style-type:disc}.as3cf-sidebar .subscribe li{line-height:1.4}.as3cf-sidebar .subscribe .links{margin-bottom:2em}.as3cf-sidebar .subscribe .links a{text-decoration:none}.as3cf-sidebar .subscribe .promise{color:#999;font-size:12px;line-height:1.4em}.as3cf-sidebar .subscribe .field{margin-bottom:0.5em}.as3cf-sidebar .subscribe .field p{margin-bottom:0.3em}.as3cf-sidebar .subscribe .field.submit-button{margin-bottom:1em}.as3cf-sidebar .credits{border-top:0}.as3cf-sidebar .credits h4{font-size:16px;margin-top:0;margin-bottom:10px}.as3cf-sidebar .credits ul{margin:0}.as3cf-sidebar .credits li{overflow:hidden}.as3cf-sidebar .credits li:last-child{margin-bottom:0}.as3cf-sidebar .credits img{float:left;margin-right:10px}.as3cf-sidebar .credits span{float:left;display:block;line-height:32px}.as3cf-sidebar .credits a{display:block;text-decoration:none;color:#444;font-size:16px;text-align:center}.as3cf-sidebar .credits a:hover{color:#888}@media (min--moz-device-pixel-ratio: 1.3), (-o-min-device-pixel-ratio: 2.6 / 2), (-webkit-min-device-pixel-ratio: 1.3), (min-device-pixel-ratio: 1.3), (min-resolution: 1.3dppx){.as3cf-sidebar .as3cf-banner{background-image:url(../img/snail@2x.jpg);background-size:292px 165px;width:292px;height:165px;display:block}.as3cf-sidebar .as3cf-banner img{display:none}}@media screen and (max-width: 1052px){.as3cf-sidebar{position:relative;top:auto;right:auto;margin-top:50px}}
assets/img/snail.jpg ADDED
Binary file
assets/img/snail@2x.jpg ADDED
Binary file
assets/js/script.js CHANGED
@@ -31,7 +31,7 @@
31
  if ($(this).val() == bucket_name) {
32
  return false;
33
  }
34
- if ($(this).val() > bucket_name) {
35
  $(opt).insertBefore($(this));
36
  return false;
37
  }
31
  if ($(this).val() == bucket_name) {
32
  return false;
33
  }
34
+ if ($(this).val() > bucket_name || 'new' == $(this).val() ) {
35
  $(opt).insertBefore($(this));
36
  return false;
37
  }
assets/js/script.min.js CHANGED
@@ -1 +1 @@
1
- (function(e){e(document).ready(function(){e(".as3cf-settings").each(function(){var t=e(this);e("select.bucket",t).change(function(){var t=e(this);if(t.val()!=="new")return;var n=function(e,n,r){alert(as3cf_i18n.create_bucket_error+r);t[0].selectedIndex=0;console.log(e);console.log(n);console.log(r)},r=function(n,r,s){if(typeof n["success"]!="undefined"){var o=document.createElement("option");o.value=o.innerHTML=i;var u=0;e("option",t).each(function(){if(e(this).val()==i)return!1;if(e(this).val()>i){e(o).insertBefore(e(this));return!1}u+=1});t[0].selectedIndex=u;as3cf_i18n.create_bucket_nonce=n._nonce}else{alert(as3cf_i18n.create_bucket_error+n.error);t[0].selectedIndex=0}},i=window.prompt(as3cf_i18n.create_bucket_prompt);if(!i){t[0].selectedIndex=0;return}var s={action:"as3cf-create-bucket",bucket_name:i,_nonce:as3cf_i18n.create_bucket_nonce};e.ajax({url:ajaxurl,type:"POST",dataType:"JSON",success:r,error:n,data:s})})})})})(jQuery);
1
+ (function(e){e(document).ready(function(){e(".as3cf-settings").each(function(){var t=e(this);e("select.bucket",t).change(function(){var t=e(this);if(t.val()!=="new"){return}var n=function(e,n,r){alert(as3cf_i18n.create_bucket_error+r);t[0].selectedIndex=0;console.log(e);console.log(n);console.log(r)};var r=function(n,r,s){if(typeof n["success"]!=="undefined"){var o=document.createElement("option");o.value=o.innerHTML=i;var u=0;e("option",t).each(function(){if(e(this).val()==i){return false}if(e(this).val()>i||"new"==e(this).val()){e(o).insertBefore(e(this));return false}u=u+1});t[0].selectedIndex=u;as3cf_i18n.create_bucket_nonce=n["_nonce"]}else{alert(as3cf_i18n.create_bucket_error+n["error"]);t[0].selectedIndex=0}};var i=window.prompt(as3cf_i18n.create_bucket_prompt);if(!i){t[0].selectedIndex=0;return}var s={action:"as3cf-create-bucket",bucket_name:i,_nonce:as3cf_i18n.create_bucket_nonce};e.ajax({url:ajaxurl,type:"POST",dataType:"JSON",success:r,error:n,data:s})})})})})(jQuery)
assets/package.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "amazon-s3-and-cloudfront",
3
+ "version": "0.1.0",
4
+ "devDependencies": {
5
+ "grunt": "^0.4.4",
6
+ "grunt-contrib-uglify": "^0.4.0",
7
+ "grunt-contrib-watch": "^0.6.0",
8
+ "grunt-contrib-compass": "^0.7.2"
9
+ }
10
+ }
assets/sass/styles.scss CHANGED
@@ -1,6 +1,258 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  .as3cf-settings {
 
 
 
 
2
  select.bucket {
3
  margin-bottom: 5px;
4
  width: 380px;
5
  }
6
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .aws-main {
2
+ .error {
3
+ max-width: 835px;
4
+ pre {
5
+ background: #eaeaea;
6
+ background: rgba(0, 0, 0, 0.07);
7
+ display: block;
8
+ padding: 10px 15px;
9
+
10
+ code {
11
+ padding: 0;
12
+ background: none;
13
+ }
14
+ }
15
+ }
16
+
17
+ .as3cf-notice {
18
+ max-width: 835px;
19
+ }
20
+ }
21
+
22
  .as3cf-settings {
23
+ position: relative;
24
+ width: 550px;
25
+ min-height: 800px;
26
+
27
  select.bucket {
28
  margin-bottom: 5px;
29
  width: 380px;
30
  }
31
+
32
+ .form-table {
33
+ td {
34
+ padding-left: 0;
35
+ padding-right: 0;
36
+ }
37
+
38
+ tr:first-child {
39
+ h3 {
40
+ margin-top: 0;
41
+ }
42
+ }
43
+ }
44
+
45
+ .tooltip {
46
+ position: relative;
47
+ z-index: 2;
48
+ cursor: pointer;
49
+ }
50
+
51
+ /* Hide the tooltip content by default */
52
+ .tooltip:before,
53
+ .tooltip:after {
54
+ visibility: hidden;
55
+ -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";
56
+ filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0);
57
+ opacity: 0;
58
+ pointer-events: none;
59
+ }
60
+
61
+ /* Position tooltip above the element */
62
+ .tooltip:before {
63
+ position: absolute;
64
+ bottom: 150%;
65
+ left: 50%;
66
+ margin-bottom: 5px;
67
+ margin-left: -250px;
68
+ padding: 10px;
69
+ width: 500px;
70
+ -webkit-border-radius: 3px;
71
+ -moz-border-radius: 3px;
72
+ border-radius: 3px;
73
+ background-color: #000;
74
+ background-color: hsla(0, 0%, 20%, 0.9);
75
+ color: #fff;
76
+ content: attr(data-tooltip);
77
+ text-align: center;
78
+ font-size: 14px;
79
+ line-height: 1.3;
80
+ }
81
+
82
+ /* Triangle hack to make tooltip look like a speech bubble */
83
+ .tooltip:after {
84
+ position: absolute;
85
+ bottom: 150%;
86
+ left: 50%;
87
+ margin-left: -5px;
88
+ width: 0;
89
+ border-top: 5px solid #000;
90
+ border-top: 5px solid hsla(0, 0%, 20%, 0.9);
91
+ border-right: 5px solid transparent;
92
+ border-left: 5px solid transparent;
93
+ content: " ";
94
+ font-size: 0;
95
+ line-height: 0;
96
+ }
97
+
98
+ /* Show tooltip content on hover */
99
+ .tooltip:hover:before,
100
+ .tooltip:hover:after {
101
+ visibility: visible;
102
+ -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";
103
+ filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100);
104
+ opacity: 1;
105
+ }
106
+ }
107
+
108
+ .as3cf-banner {
109
+ img {
110
+ display: block;
111
+ }
112
+ }
113
+
114
+ .as3cf-sidebar {
115
+ position: absolute;
116
+ top: 17px;
117
+ right: -312px;
118
+ width: 292px;
119
+
120
+ .block {
121
+ padding: 20px;
122
+ border: 1px solid #ccc;
123
+ }
124
+
125
+ .subscribe {
126
+ border-top: none;
127
+
128
+ h2 {
129
+ padding: 0;
130
+ margin: 0;
131
+ margin-bottom: 0.5em;
132
+ color: #666;
133
+ font-size: 20px;
134
+ line-height: 1.2em;
135
+ float: none;
136
+ }
137
+
138
+ h3 {
139
+ font-size: 16px;
140
+ margin: 0;
141
+ }
142
+
143
+ p {
144
+ margin: 0;
145
+ }
146
+
147
+ .intro {
148
+ margin-bottom: 1em;
149
+ line-height: 1.4;
150
+ }
151
+
152
+ ul {
153
+ margin-left: 20px;
154
+ list-style-type: disc;
155
+ }
156
+
157
+ li {
158
+ line-height: 1.4;
159
+ }
160
+
161
+ .links {
162
+ margin-bottom: 2em;
163
+
164
+ a {
165
+ text-decoration: none;
166
+ }
167
+ }
168
+
169
+ .promise {
170
+ color: #999;
171
+ font-size: 12px;
172
+ line-height: 1.4em;
173
+ }
174
+
175
+ .field {
176
+ margin-bottom: 0.5em;
177
+
178
+ p {
179
+ margin-bottom: 0.3em;
180
+ }
181
+
182
+ &.submit-button {
183
+ margin-bottom: 1em;
184
+ }
185
+ }
186
+ }
187
+
188
+ .credits {
189
+ border-top: 0;
190
+
191
+ h4 {
192
+ font-size: 16px;
193
+ margin-top: 0;
194
+ margin-bottom: 10px;
195
+ }
196
+
197
+ ul {
198
+ margin: 0;
199
+ }
200
+
201
+ li {
202
+ overflow: hidden;
203
+ }
204
+
205
+ li:last-child {
206
+ margin-bottom: 0;
207
+ }
208
+
209
+ img {
210
+ float: left;
211
+ margin-right: 10px;
212
+ }
213
+
214
+ span {
215
+ float: left;
216
+ display: block;
217
+ line-height: 32px;
218
+ }
219
+
220
+ a {
221
+ display: block;
222
+ text-decoration: none;
223
+ color: #444;
224
+ font-size: 16px;
225
+ text-align: center;
226
+
227
+ &:hover {
228
+ color: #888;
229
+ }
230
+ }
231
+ }
232
+
233
+ @media (min--moz-device-pixel-ratio: 1.3),
234
+ (-o-min-device-pixel-ratio: 2.6/2),
235
+ (-webkit-min-device-pixel-ratio: 1.3),
236
+ (min-device-pixel-ratio: 1.3),
237
+ (min-resolution: 1.3dppx) {
238
+
239
+ .as3cf-banner {
240
+ background-image: url(../img/snail@2x.jpg);
241
+ background-size: 292px 165px;
242
+ width: 292px;
243
+ height: 165px;
244
+ display: block;
245
+
246
+ img {
247
+ display: none;
248
+ }
249
+ }
250
+ }
251
+
252
+ @media screen and (max-width: 1052px) {
253
+ position: relative;
254
+ top: auto;
255
+ right: auto;
256
+ margin-top: 50px;
257
+ }
258
+ }
classes/amazon-s3-and-cloudfront.php CHANGED
@@ -4,13 +4,22 @@ use Aws\S3\S3Client;
4
  class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
5
  private $aws, $s3client;
6
 
 
 
 
 
7
  const SETTINGS_KEY = 'tantan_wordpress_s3';
8
 
9
  function __construct( $plugin_file_path, $aws ) {
 
 
10
  parent::__construct( $plugin_file_path );
11
 
12
  $this->aws = $aws;
13
 
 
 
 
14
  add_action( 'aws_admin_menu', array( $this, 'admin_menu' ) );
15
 
16
  $this->plugin_title = __( 'Amazon S3 and CloudFront', 'as3cf' );
@@ -18,12 +27,16 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
18
 
19
  add_action( 'wp_ajax_as3cf-create-bucket', array( $this, 'ajax_create_bucket' ) );
20
 
21
- add_filter( 'wp_get_attachment_url', array( $this, 'wp_get_attachment_url' ), 9, 2 );
22
- add_filter( 'wp_generate_attachment_metadata', array( $this, 'wp_generate_attachment_metadata' ), 20, 2 );
 
 
23
  add_filter( 'delete_attachment', array( $this, 'delete_attachment' ), 20 );
 
 
24
  }
25
 
26
- function get_setting( $key ) {
27
  $settings = $this->get_settings();
28
 
29
  // If legacy setting set, migrate settings
@@ -33,217 +46,334 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
33
 
34
  // Default object prefix
35
  if ( 'object-prefix' == $key && !isset( $settings['object-prefix'] ) ) {
36
- $uploads = wp_upload_dir();
37
- $parts = parse_url( $uploads['baseurl'] );
38
- return substr( $parts['path'], 1 ) . '/';
39
- }
40
-
41
- return parent::get_setting( $key );
42
- }
43
-
44
- function delete_attachment( $post_id ) {
45
- if ( !$this->is_plugin_setup() ) {
46
- return;
47
- }
48
-
49
- $backup_sizes = get_post_meta( $post_id, '_wp_attachment_backup_sizes', true );
50
-
51
- $intermediate_sizes = array();
52
- foreach ( get_intermediate_image_sizes() as $size ) {
53
- if ( $intermediate = image_get_intermediate_size( $post_id, $size ) )
54
- $intermediate_sizes[] = $intermediate;
55
- }
56
-
57
- if ( !( $s3object = $this->get_attachment_s3_info( $post_id ) ) ) {
58
- return;
59
- }
60
-
61
- $amazon_path = dirname( $s3object['key'] );
62
- $objects = array();
63
-
64
- // remove intermediate and backup images if there are any
65
- foreach ( $intermediate_sizes as $intermediate ) {
66
- $objects[] = array(
67
- 'Key' => path_join( $amazon_path, $intermediate['file'] )
68
- );
69
- }
70
-
71
- if ( is_array( $backup_sizes ) ) {
72
- foreach ( $backup_sizes as $size ) {
73
- $objects[] = array(
74
- 'Key' => path_join( $amazon_path, $del_file )
75
- );
76
- }
77
- }
78
-
79
- // Try removing any @2x images but ignore any errors
80
- if ( $objects ) {
81
- $hidpi_images = array();
82
- foreach ( $objects as $object ) {
83
- $hidpi_images[] = array(
84
- 'Key' => $this->get_hidpi_file_path( $object['Key'] )
85
- );
86
- }
87
 
88
- try {
89
- $this->get_s3client()->deleteObjects( array(
90
- 'Bucket' => $s3object['bucket'],
91
- 'Objects' => $hidpi_images
92
- ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  }
94
- catch ( Exception $e ) {}
95
- }
96
 
97
- $objects[] = array(
98
- 'Key' => $s3object['key']
99
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
 
 
 
 
 
 
 
 
 
 
101
  try {
102
- $this->get_s3client()->deleteObjects( array(
103
- 'Bucket' => $s3object['bucket'],
104
- 'Objects' => $objects
105
- ) );
 
 
 
 
 
 
 
106
  }
107
- catch ( Exception $e ) {
108
- error_log( 'Error removing files from S3: ' . $e->getMessage() );
 
 
109
  return;
110
  }
111
 
112
- delete_post_meta( $post_id, 'amazonS3_info' );
113
- }
 
114
 
115
- function wp_generate_attachment_metadata( $data, $post_id ) {
116
- if ( !$this->get_setting( 'copy-to-s3' ) || !$this->is_plugin_setup() ) {
117
- return $data;
118
- }
119
 
120
- $time = $this->get_attachment_folder_time( $post_id );
121
- $time = date( 'Y/m', $time );
122
 
123
- $prefix = ltrim( trailingslashit( $this->get_setting( 'object-prefix' ) ), '/' );
124
- $prefix .= ltrim( trailingslashit( $this->get_dynamic_prefix( $time ) ), '/' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
 
126
- if ( $this->get_setting( 'object-versioning' ) ) {
127
- $prefix .= $this->get_object_version_string( $post_id );
128
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
 
130
- $type = get_post_mime_type( $post_id );
 
 
 
 
 
131
 
132
- $file_path = get_attached_file( $post_id, true );
 
133
 
134
- $acl = apply_filters( 'wps3_upload_acl', 'public-read', $type, $data, $post_id, $this ); // Old naming convention, will be deprecated soon
135
- $acl = apply_filters( 'as3cf_upload_acl', $acl, $data, $post_id );
136
 
137
- if ( !file_exists( $file_path ) ) {
138
- return $data;
139
- }
140
 
141
- $file_name = basename( $file_path );
142
- $files_to_remove = array( $file_path );
 
 
143
 
144
- $s3client = $this->get_s3client();
 
 
 
145
 
146
- $bucket = $this->get_setting( 'bucket' );
 
 
 
147
 
148
- $args = array(
 
 
 
149
  'Bucket' => $bucket,
150
  'Key' => $prefix . $file_name,
151
  'SourceFile' => $file_path,
152
  'ACL' => $acl
153
- );
154
 
155
- // If far future expiration checked (10 years)
156
  if ( $this->get_setting( 'expires' ) ) {
157
  $args['Expires'] = date( 'D, d M Y H:i:s O', time()+315360000 );
158
  }
159
 
160
- try {
161
- $s3client->putObject( $args );
 
 
 
 
 
 
 
 
 
 
162
  }
163
- catch ( Exception $e ) {
164
- error_log( 'Error uploading ' . $file_path . ' to S3: ' . $e->getMessage() );
165
- return $data;
 
 
 
 
 
 
 
 
166
  }
167
 
168
- delete_post_meta( $post_id, 'amazonS3_info' );
169
 
170
- add_post_meta( $post_id, 'amazonS3_info', array(
171
- 'bucket' => $bucket,
172
- 'key' => $prefix . $file_name
173
- ) );
174
 
175
  $additional_images = array();
176
 
177
- if ( isset( $data['thumb'] ) && $data['thumb'] ) {
178
  $path = str_replace( $file_name, $data['thumb'], $file_path );
179
- $additional_images[] = array(
180
- 'Key' => $prefix . $data['thumb'],
181
- 'SourceFile' => $path
182
- );
183
- $files_to_remove[] = $path;
184
- }
185
- elseif ( !empty( $data['sizes'] ) ) {
186
- foreach ( $data['sizes'] as $size ) {
187
- $path = str_replace( $file_name, $size['file'], $file_path );
188
- $additional_images[] = array(
189
- 'Key' => $prefix . $size['file'],
190
  'SourceFile' => $path
191
- );
192
- $files_to_remove[] = $path;
193
- }
194
- }
195
-
196
- // Because we're just looking at the filesystem for files with @2x
197
- // this should work with most HiDPI plugins
198
- if ( $this->get_setting( 'hidpi-images' ) ) {
199
- $hidpi_images = array();
200
-
201
- foreach ( $additional_images as $image ) {
202
- $hidpi_path = $this->get_hidpi_file_path( $image['SourceFile'] );
203
- if ( file_exists( $hidpi_path ) ) {
204
- $hidpi_images[] = array(
 
 
 
 
 
 
 
 
 
 
 
 
205
  'Key' => $this->get_hidpi_file_path( $image['Key'] ),
206
  'SourceFile' => $hidpi_path
207
- );
208
- $files_to_remove[] = $hidpi_path;
209
- }
210
- }
211
 
212
  $additional_images = array_merge( $additional_images, $hidpi_images );
213
  }
214
 
215
- foreach ( $additional_images as $image ) {
216
  try {
217
  $args = array_merge( $args, $image );
 
218
  $s3client->putObject( $args );
219
  }
220
  catch ( Exception $e ) {
221
  error_log( 'Error uploading ' . $args['SourceFile'] . ' to S3: ' . $e->getMessage() );
222
  }
223
- }
224
 
225
- if ( $this->get_setting( 'remove-local-file' ) ) {
226
- $this->remove_local_files( $files_to_remove );
227
- }
228
 
229
- return $data;
230
- }
231
 
232
- function remove_local_files( $file_paths ) {
233
- foreach ( $file_paths as $path ) {
234
- if ( !@unlink( $path ) ) {
235
- error_log( 'Error removing local file ' . $path );
236
- }
237
- }
238
- }
239
 
240
- function get_hidpi_file_path( $orig_path ) {
241
  $hidpi_suffix = apply_filters( 'as3cf_hidpi_suffix', '@2x' );
242
  $pathinfo = pathinfo( $orig_path );
243
  return $pathinfo['dirname'] . '/' . $pathinfo['filename'] . $hidpi_suffix . '.' . $pathinfo['extension'];
244
- }
245
 
246
- function get_object_version_string( $post_id ) {
247
  if ( get_option( 'uploads_use_yearmonth_folders' ) ) {
248
  $date_format = 'dHis';
249
  }
@@ -255,22 +385,22 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
255
 
256
  $object_version = date( $date_format, $time ) . '/';
257
  $object_version = apply_filters( 'as3cf_get_object_version_string', $object_version );
258
-
259
  return $object_version;
260
- }
261
 
262
- // Media files attached to a post use the post's date
263
- // to determine the folder path they are placed in
264
- function get_attachment_folder_time( $post_id ) {
265
  $time = current_time( 'timestamp' );
266
 
267
- if ( !( $attach = get_post( $post_id ) ) ) {
268
- return $time;
269
- }
270
 
271
- if ( !$attach->post_parent ) {
272
- return $time;
273
- }
274
 
275
  if ( !( $post = get_post( $attach->post_parent ) ) ) {
276
  return $time;
@@ -280,15 +410,77 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
280
  return strtotime( $post->post_date_gmt . ' +0000' );
281
  }
282
 
283
- return $time;
284
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
285
 
286
  function wp_get_attachment_url( $url, $post_id ) {
287
  $new_url = $this->get_attachment_url( $post_id );
288
  if ( false === $new_url ) {
289
  return $url;
290
  }
291
-
292
  $new_url = apply_filters( 'wps3_get_attachment_url', $new_url, $post_id, $this ); // Old naming convention, will be deprecated soon
293
  $new_url = apply_filters( 'as3cf_wp_get_attachment_url', $new_url, $post_id );
294
 
@@ -307,15 +499,36 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
307
  * Generate a link to download a file from Amazon S3 using query string
308
  * authentication. This link is only valid for a limited amount of time.
309
  *
310
- * @param mixed $post_id Post ID of the attachment or null to use the loop
311
- * @param int $expires Seconds for the link to live
 
 
 
312
  */
313
- function get_secure_attachment_url( $post_id, $expires = 900 ) {
314
- return $this->get_attachment_url( $post_id, $expires );
 
 
 
315
  }
316
 
317
- function get_attachment_url( $post_id, $expires = null ) {
318
- if ( !$this->get_setting( 'serve-from-s3' ) || !( $s3object = $this->get_attachment_s3_info( $post_id ) ) ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
319
  return false;
320
  }
321
 
@@ -326,6 +539,22 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
326
  $scheme = 'http';
327
  }
328
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
329
  if ( is_null( $expires ) && $this->get_setting( 'cloudfront' ) ) {
330
  $domain_bucket = $this->get_setting( 'cloudfront' );
331
  }
@@ -333,26 +562,103 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
333
  $domain_bucket = $s3object['bucket'];
334
  }
335
  elseif ( is_ssl() || $this->get_setting( 'force-ssl' ) ) {
336
- $domain_bucket = 's3.amazonaws.com/' . $s3object['bucket'];
337
  }
338
  else {
339
- $domain_bucket = $s3object['bucket'] . '.s3.amazonaws.com';
340
  }
341
 
342
- $url = $scheme . '://' . $domain_bucket . '/' . $s3object['key'];
 
 
 
 
 
 
 
343
 
344
  if ( !is_null( $expires ) ) {
345
  try {
346
  $expires = time() + $expires;
347
- $secure_url = $this->get_s3client()->getObjectUrl( $s3object['bucket'], $s3object['key'], $expires );
348
- $url .= substr( $secure_url, strpos( $secure_url, '?' ) );
349
  }
350
  catch ( Exception $e ) {
351
  return new WP_Error( 'exception', $e->getMessage() );
352
  }
353
  }
354
 
355
- return apply_filters( 'as3cf_get_attachment_url', $url, $s3object, $post_id, $expires );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
356
  }
357
 
358
  function verify_ajax_request() {
@@ -381,12 +687,12 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
381
  }
382
 
383
  echo json_encode( $out );
384
- exit;
385
  }
386
 
387
  function create_bucket( $bucket_name ) {
388
  try {
389
- $this->get_s3client()->createBucket( array( 'Bucket' => $bucket_name ) );
390
  }
391
  catch ( Exception $e ) {
392
  return new WP_Error( 'exception', $e->getMessage() );
@@ -408,6 +714,59 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
408
  return $this->s3client;
409
  }
410
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
411
  function get_buckets() {
412
  try {
413
  $result = $this->get_s3client()->listBuckets();
@@ -419,20 +778,74 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
419
  return $result['Buckets'];
420
  }
421
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
422
  function plugin_load() {
 
 
423
  $src = plugins_url( 'assets/css/styles.css', $this->plugin_file_path );
424
- wp_enqueue_style( 'as3cf-styles', $src, array(), $this->get_installed_version() );
425
 
426
  $suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
427
 
428
  $src = plugins_url( 'assets/js/script' . $suffix . '.js', $this->plugin_file_path );
429
- wp_enqueue_script( 'as3cf-script', $src, array( 'jquery' ), $this->get_installed_version(), true );
430
-
431
  wp_localize_script( 'as3cf-script', 'as3cf_i18n', array(
432
- 'create_bucket_prompt' => __( 'Bucket Name:', 'as3cf' ),
433
- 'create_bucket_error' => __( 'Error creating bucket: ', 'as3cf' ),
434
- 'create_bucket_nonce' => wp_create_nonce( 'as3cf-create-bucket' )
435
- ) );
436
 
437
  $this->handle_post_request();
438
  }
@@ -446,10 +859,11 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
446
  die( __( "Cheatin' eh?", 'amazon-web-services' ) );
447
  }
448
 
449
- $this->set_settings( array() );
450
-
451
  $post_vars = array( 'bucket', 'virtual-host', 'expires', 'permissions', 'cloudfront', 'object-prefix', 'copy-to-s3', 'serve-from-s3', 'remove-local-file', 'force-ssl', 'hidpi-images', 'object-versioning' );
 
452
  foreach ( $post_vars as $var ) {
 
 
453
  if ( !isset( $_POST[$var] ) ) {
454
  continue;
455
  }
@@ -465,26 +879,27 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
465
 
466
  function render_page() {
467
  $this->aws->render_view( 'header', array( 'page_title' => $this->plugin_title ) );
468
-
469
  $aws_client = $this->aws->get_client();
470
 
471
  if ( is_wp_error( $aws_client ) ) {
472
- $this->render_view( 'error', array( 'error' => $aws_client ) );
473
  }
474
  else {
 
475
  $this->render_view( 'settings' );
476
  }
477
-
478
  $this->aws->render_view( 'footer' );
479
  }
480
 
481
  function get_dynamic_prefix( $time = null ) {
482
- $uploads = wp_upload_dir( $time );
483
- return str_replace( $this->get_base_upload_path(), '', $uploads['path'] );
484
  }
485
 
486
  // Without the multisite subdirectory
487
- function get_base_upload_path() {
488
  if ( defined( 'UPLOADS' ) && ! ( is_multisite() && get_site_option( 'ms_files_rewriting' ) ) ) {
489
  return ABSPATH . UPLOADS;
490
  }
@@ -501,4 +916,29 @@ class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
501
  }
502
  }
503
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
504
  }
4
  class Amazon_S3_And_CloudFront extends AWS_Plugin_Base {
5
  private $aws, $s3client;
6
 
7
+ const DEFAULT_ACL = 'public-read';
8
+ const PRIVATE_ACL = 'private';
9
+ const DEFAULT_EXPIRES = 900;
10
+
11
  const SETTINGS_KEY = 'tantan_wordpress_s3';
12
 
13
  function __construct( $plugin_file_path, $aws ) {
14
+ $this->plugin_slug = 'amazon-s3-and-cloudfront';
15
+
16
  parent::__construct( $plugin_file_path );
17
 
18
  $this->aws = $aws;
19
 
20
+ // fire up the plugin upgrade checker
21
+ new AS3CF_Upgrade( $this );
22
+
23
  add_action( 'aws_admin_menu', array( $this, 'admin_menu' ) );
24
 
25
  $this->plugin_title = __( 'Amazon S3 and CloudFront', 'as3cf' );
27
 
28
  add_action( 'wp_ajax_as3cf-create-bucket', array( $this, 'ajax_create_bucket' ) );
29
 
30
+ add_filter( 'wp_get_attachment_url', array( $this, 'wp_get_attachment_url' ), 99, 2 );
31
+ add_filter( 'wp_handle_upload_prefilter', array( $this, 'wp_handle_upload_prefilter' ), 1 );
32
+ add_filter( 'wp_update_attachment_metadata', array( $this, 'wp_update_attachment_metadata' ), 100, 2 );
33
+ add_filter( 'wp_get_attachment_metadata', array( $this, 'wp_get_attachment_metadata' ), 10, 2 );
34
  add_filter( 'delete_attachment', array( $this, 'delete_attachment' ), 20 );
35
+
36
+ load_plugin_textdomain( 'as3cf', false, dirname( plugin_basename( $plugin_file_path ) ) . '/languages/' );
37
  }
38
 
39
+ function get_setting( $key, $default = '' ) {
40
  $settings = $this->get_settings();
41
 
42
  // If legacy setting set, migrate settings
46
 
47
  // Default object prefix
48
  if ( 'object-prefix' == $key && !isset( $settings['object-prefix'] ) ) {
49
+ $uploads = wp_upload_dir();
50
+ $parts = parse_url( $uploads['baseurl'] );
51
+ return substr( $parts['path'], 1 ) . '/';
52
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
+ if ( 'bucket' == $key && defined( 'AS3CF_BUCKET' ) ) {
55
+ $value = AS3CF_BUCKET;
56
+ }
57
+ else {
58
+ $value = parent::get_setting( $key, $default );
59
+ }
60
+
61
+ return apply_filters( 'as3cf_setting_' . $key, $value );
62
+ }
63
+
64
+ /**
65
+ * Allowed mime types array that can be edited for specific S3 uploading
66
+ *
67
+ * @return array
68
+ */
69
+ function get_allowed_mime_types() {
70
+ return apply_filters( 'as3cf_allowed_mime_types', get_allowed_mime_types() );
71
+ }
72
+
73
+ /**
74
+ * Find backup images and add to array for removal
75
+ *
76
+ * @param unknown $post_id
77
+ * @param unknown $objects
78
+ * @param unknown $path
79
+ */
80
+ function prepare_backup_size_images_to_remove( $post_id, &$objects, $path ) {
81
+ $backup_sizes = get_post_meta( $post_id, '_wp_attachment_backup_sizes', true );
82
+
83
+ if ( is_array( $backup_sizes ) ) {
84
+ foreach ( $backup_sizes as $size ) {
85
+ $objects[] = array(
86
+ 'Key' => path_join( $path, $size['file'] )
87
+ );
88
  }
89
+ }
90
+ }
91
 
92
+ /**
93
+ * Find intermediate size images and add to array for removal
94
+ *
95
+ * @param unknown $post_id
96
+ * @param unknown $objects
97
+ * @param unknown $path
98
+ */
99
+ function prepare_intermediate_images_to_remove( $post_id, &$objects, $path ) {
100
+ $intermediate_images = get_intermediate_image_sizes();
101
+
102
+ foreach ( $intermediate_images as $size ) {
103
+ if ( $intermediate = image_get_intermediate_size( $post_id, $size ) ) {
104
+ $objects[] = array(
105
+ 'Key' => path_join( $path, $intermediate['file'] )
106
+ );
107
+ }
108
+ }
109
+ }
110
 
111
+ /**
112
+ * Delete bulk objects from an S3 bucket
113
+ *
114
+ * @param unknown $bucket
115
+ * @param unknown $objects
116
+ * @param bool $log_error
117
+ * @param bool $return_on_error
118
+ */
119
+ function delete_s3_objects( $bucket, $objects, $log_error = false, $return_on_error = false ) {
120
  try {
121
+ $this->get_s3client()->deleteObjects( array(
122
+ 'Bucket' => $bucket,
123
+ 'Objects' => $objects
124
+ ) );
125
+ } catch ( Exception $e ) {
126
+ if ( $log_error ) {
127
+ error_log( 'Error removing files from S3: ' . $e->getMessage() );
128
+ }
129
+ if ( $return_on_error ) {
130
+ return;
131
+ }
132
  }
133
+ }
134
+
135
+ function delete_attachment( $post_id ) {
136
+ if ( !$this->is_plugin_setup() ) {
137
  return;
138
  }
139
 
140
+ if ( !( $s3object = $this->get_attachment_s3_info( $post_id ) ) ) {
141
+ return;
142
+ }
143
 
144
+ $bucket = $s3object['bucket'];
 
 
 
145
 
146
+ $this->set_s3client_region( $s3object );
 
147
 
148
+ $amazon_path = dirname( $s3object['key'] );
149
+ $objects = array();
150
+
151
+ // remove intermediate images
152
+ $this->prepare_intermediate_images_to_remove( $post_id, $objects, $amazon_path );
153
+ // remove backup images
154
+ $this->prepare_backup_size_images_to_remove( $post_id, $objects, $amazon_path );
155
+
156
+ // Try removing any @2x images but ignore any errors
157
+ if ( $objects ) {
158
+ $hidpi_images = array();
159
+ foreach ( $objects as $object ) {
160
+ $hidpi_images[] = array(
161
+ 'Key' => $this->get_hidpi_file_path( $object['Key'] )
162
+ );
163
+ }
164
+
165
+ $this->delete_s3_objects( $bucket, $hidpi_images );
166
+ }
167
+
168
+ // add main file to be deleted
169
+ $objects[] = array(
170
+ 'Key' => $s3object['key']
171
+ );
172
+
173
+ $this->delete_s3_objects( $bucket, $objects, true, true );
174
+
175
+ delete_post_meta( $post_id, 'amazonS3_info' );
176
+ }
177
+
178
+ function wp_update_attachment_metadata( $data, $post_id ) {
179
+ if ( !$this->get_setting( 'copy-to-s3' ) || !$this->is_plugin_setup() ) {
180
+ return $data;
181
+ }
182
+
183
+ // allow S3 upload to be cancelled for any reason
184
+ $pre = apply_filters( 'as3cf_pre_update_attachment_metadata', false, $data, $post_id );
185
+ if ( false !== $pre ) {
186
+ return $data;
187
+ }
188
+
189
+ $type = get_post_mime_type( $post_id );
190
+ $allowed_types = $this->get_allowed_mime_types();
191
+
192
+ // check mime type of file is in allowed S3 mime types
193
+ if ( ! in_array( $type, $allowed_types ) ) {
194
+ return $data;
195
+ }
196
+
197
+ $acl = self::DEFAULT_ACL;
198
 
199
+ // check the attachment already exists in S3, eg. edit or restore image
200
+ if ( ( $old_s3object = $this->get_attachment_s3_info( $post_id ) ) ) {
201
+ // use existing non default ACL if attachment already exists
202
+ if ( isset( $old_s3object['acl'] ) ) {
203
+ $acl = $old_s3object['acl'];
204
+ }
205
+ // use existing prefix
206
+ $prefix = trailingslashit( dirname( $old_s3object['key'] ) );
207
+ // use existing bucket
208
+ $bucket = $old_s3object['bucket'];
209
+ // get existing region
210
+ if ( isset( $old_s3object['region'] ) ) {
211
+ $region = $old_s3object['region'];
212
+ };
213
+ } else {
214
+ // derive prefix from various settings
215
+ if ( isset( $data['file'] ) ) {
216
+ $time = untrailingslashit( dirname( $data['file'] ) );
217
+ } else {
218
+ $time = $this->get_attachment_folder_time( $post_id );
219
+ $time = date( 'Y/m', $time );
220
+ }
221
+
222
+ $prefix = ltrim( trailingslashit( $this->get_setting( 'object-prefix' ) ), '/' );
223
+ $prefix .= ltrim( trailingslashit( $this->get_dynamic_prefix( $time ) ), '/' );
224
 
225
+ if ( $this->get_setting( 'object-versioning' ) ) {
226
+ $prefix .= $this->get_object_version_string( $post_id );
227
+ }
228
+ // use bucket from settings
229
+ $bucket = $this->get_setting( 'bucket' );
230
+ }
231
 
232
+ $file_path = get_attached_file( $post_id, true );
233
+ $file_name = basename( $file_path );
234
 
235
+ $acl = apply_filters( 'wps3_upload_acl', $acl, $type, $data, $post_id, $this ); // Old naming convention, will be deprecated soon
236
+ $acl = apply_filters( 'as3cf_upload_acl', $acl, $data, $post_id );
237
 
238
+ $s3client = $this->get_s3client();
 
 
239
 
240
+ $s3object = array(
241
+ 'bucket' => $bucket,
242
+ 'key' => $prefix . $file_name
243
+ );
244
 
245
+ // store acl if not default
246
+ if ( $acl != self::DEFAULT_ACL ) {
247
+ $s3object['acl'] = $acl;
248
+ }
249
 
250
+ // use existing region
251
+ if ( isset( $region ) ) {
252
+ $s3object['region'] = $region;
253
+ }
254
 
255
+ // retrieve region when necessary and set the region of the s3client
256
+ $s3object['region'] = $this->set_s3client_region( $s3object );
257
+
258
+ $args = array(
259
  'Bucket' => $bucket,
260
  'Key' => $prefix . $file_name,
261
  'SourceFile' => $file_path,
262
  'ACL' => $acl
263
+ );
264
 
265
+ // If far future expiration checked (10 years)
266
  if ( $this->get_setting( 'expires' ) ) {
267
  $args['Expires'] = date( 'D, d M Y H:i:s O', time()+315360000 );
268
  }
269
 
270
+ // if the original image is being restored and 'IMAGE_EDIT_OVERWRITE' is set
271
+ // then we need to remove the edited image versions
272
+ if ( isset( $_POST['do'] ) && 'restore' == $_POST['do'] && defined( 'IMAGE_EDIT_OVERWRITE' ) && IMAGE_EDIT_OVERWRITE ) {
273
+ $objects_to_remove = array();
274
+ // edited main file
275
+ $meta = get_post_meta( $post_id, '_wp_attachment_metadata', true );
276
+ $objects_to_remove[] = array(
277
+ 'Key' => path_join( $prefix, basename( $meta['file'] ) )
278
+ );
279
+ // edited resized image files
280
+ $this->prepare_intermediate_images_to_remove( $post_id, $objects_to_remove, $prefix );
281
+ $this->delete_s3_objects( $bucket, $objects_to_remove, true );
282
  }
283
+
284
+ $files_to_remove = array();
285
+ if ( file_exists( $file_path ) ) {
286
+ $files_to_remove[] = $file_path;
287
+ try {
288
+ $s3client->putObject( $args );
289
+ }
290
+ catch ( Exception $e ) {
291
+ error_log( 'Error uploading ' . $file_path . ' to S3: ' . $e->getMessage() );
292
+ return $data;
293
+ }
294
  }
295
 
296
+ delete_post_meta( $post_id, 'amazonS3_info' );
297
 
298
+ add_post_meta( $post_id, 'amazonS3_info', $s3object );
 
 
 
299
 
300
  $additional_images = array();
301
 
302
+ if ( isset( $data['thumb'] ) && $data['thumb'] ) {
303
  $path = str_replace( $file_name, $data['thumb'], $file_path );
304
+ if ( file_exists( $path ) ) {
305
+ $additional_images[] = array(
306
+ 'Key' => $prefix . $data['thumb'],
 
 
 
 
 
 
 
 
307
  'SourceFile' => $path
308
+ );
309
+ $files_to_remove[] = $path;
310
+ }
311
+ }
312
+ elseif ( !empty( $data['sizes'] ) ) {
313
+ foreach ( $data['sizes'] as $size ) {
314
+ $path = str_replace( $file_name, $size['file'], $file_path );
315
+ if ( file_exists( $path ) ) {
316
+ $additional_images[] = array(
317
+ 'Key' => $prefix . $size['file'],
318
+ 'SourceFile' => $path
319
+ );
320
+ $files_to_remove[] = $path;
321
+ }
322
+ }
323
+ }
324
+
325
+ // Because we're just looking at the filesystem for files with @2x
326
+ // this should work with most HiDPI plugins
327
+ if ( $this->get_setting( 'hidpi-images' ) ) {
328
+ $hidpi_images = array();
329
+
330
+ foreach ( $additional_images as $image ) {
331
+ $hidpi_path = $this->get_hidpi_file_path( $image['SourceFile'] );
332
+ if ( file_exists( $hidpi_path ) ) {
333
+ $hidpi_images[] = array(
334
  'Key' => $this->get_hidpi_file_path( $image['Key'] ),
335
  'SourceFile' => $hidpi_path
336
+ );
337
+ $files_to_remove[] = $hidpi_path;
338
+ }
339
+ }
340
 
341
  $additional_images = array_merge( $additional_images, $hidpi_images );
342
  }
343
 
344
+ foreach ( $additional_images as $image ) {
345
  try {
346
  $args = array_merge( $args, $image );
347
+ $args['ACL'] = self::DEFAULT_ACL;
348
  $s3client->putObject( $args );
349
  }
350
  catch ( Exception $e ) {
351
  error_log( 'Error uploading ' . $args['SourceFile'] . ' to S3: ' . $e->getMessage() );
352
  }
353
+ }
354
 
355
+ if ( $this->get_setting( 'remove-local-file' ) ) {
356
+ $this->remove_local_files( $files_to_remove );
357
+ }
358
 
359
+ return $data;
360
+ }
361
 
362
+ function remove_local_files( $file_paths ) {
363
+ foreach ( $file_paths as $path ) {
364
+ if ( !@unlink( $path ) ) {
365
+ error_log( 'Error removing local file ' . $path );
366
+ }
367
+ }
368
+ }
369
 
370
+ function get_hidpi_file_path( $orig_path ) {
371
  $hidpi_suffix = apply_filters( 'as3cf_hidpi_suffix', '@2x' );
372
  $pathinfo = pathinfo( $orig_path );
373
  return $pathinfo['dirname'] . '/' . $pathinfo['filename'] . $hidpi_suffix . '.' . $pathinfo['extension'];
374
+ }
375
 
376
+ function get_object_version_string( $post_id ) {
377
  if ( get_option( 'uploads_use_yearmonth_folders' ) ) {
378
  $date_format = 'dHis';
379
  }
385
 
386
  $object_version = date( $date_format, $time ) . '/';
387
  $object_version = apply_filters( 'as3cf_get_object_version_string', $object_version );
388
+
389
  return $object_version;
390
+ }
391
 
392
+ // Media files attached to a post use the post's date
393
+ // to determine the folder path they are placed in
394
+ function get_attachment_folder_time( $post_id ) {
395
  $time = current_time( 'timestamp' );
396
 
397
+ if ( !( $attach = get_post( $post_id ) ) ) {
398
+ return $time;
399
+ }
400
 
401
+ if ( !$attach->post_parent ) {
402
+ return $time;
403
+ }
404
 
405
  if ( !( $post = get_post( $attach->post_parent ) ) ) {
406
  return $time;
410
  return strtotime( $post->post_date_gmt . ' +0000' );
411
  }
412
 
413
+ return $time;
414
+ }
415
+
416
+ /**
417
+ * Create unique names for file to be uploaded to AWS
418
+ * This only applies when the remove local file option is enabled
419
+ *
420
+ * @param array $file An array of data for a single file.
421
+ *
422
+ * @return array $file The altered file array with AWS unique filename.
423
+ */
424
+ function wp_handle_upload_prefilter( $file ) {
425
+ if ( ! $this->get_setting( 'copy-to-s3' ) || ! $this->is_plugin_setup() ) {
426
+ return $file;
427
+ }
428
+
429
+ // only do this when we are removing local versions of files
430
+ if ( ! $this->get_setting( 'remove-local-file' ) ) {
431
+ return $file;
432
+ }
433
+
434
+ $filename = $file['name'];
435
+
436
+ // sanitize the file name before we begin processing
437
+ $filename = sanitize_file_name( $filename );
438
+
439
+ // separate the filename into a name and extension
440
+ $info = pathinfo( $filename );
441
+ $ext = ! empty( $info['extension'] ) ? '.' . $info['extension'] : '';
442
+ $name = basename( $filename, $ext );
443
+
444
+ // edge case: if file is named '.ext', treat as an empty name
445
+ if ( $name === $ext ) {
446
+ $name = '';
447
+ }
448
+
449
+ // rebuild filename with lowercase extension as S3 will have converted extension on upload
450
+ $ext = strtolower( $ext );
451
+ $filename = $info['filename'] . $ext;
452
+
453
+ $time = current_time( 'timestamp' );
454
+ $time = date( 'Y/m', $time );
455
+
456
+ $prefix = ltrim( trailingslashit( $this->get_setting( 'object-prefix' ) ), '/' );
457
+ $prefix .= ltrim( trailingslashit( $this->get_dynamic_prefix( $time ) ), '/' );
458
+ $s3client = $this->get_s3client();
459
+
460
+ $bucket = $this->get_setting( 'bucket' );
461
+
462
+ $number = '';
463
+ while ( $s3client->doesObjectExist( $bucket, $prefix . $filename ) !== false ) {
464
+ $previous = $number;
465
+ ++$number;
466
+ if ( '' == $previous ) {
467
+ $filename = $name . $number . $ext;
468
+ } else {
469
+ $filename = str_replace( "$previous$ext", $number . $ext, $filename );
470
+ }
471
+ }
472
+
473
+ $file['name'] = $filename;
474
+
475
+ return $file;
476
+ }
477
 
478
  function wp_get_attachment_url( $url, $post_id ) {
479
  $new_url = $this->get_attachment_url( $post_id );
480
  if ( false === $new_url ) {
481
  return $url;
482
  }
483
+
484
  $new_url = apply_filters( 'wps3_get_attachment_url', $new_url, $post_id, $this ); // Old naming convention, will be deprecated soon
485
  $new_url = apply_filters( 'as3cf_wp_get_attachment_url', $new_url, $post_id );
486
 
499
  * Generate a link to download a file from Amazon S3 using query string
500
  * authentication. This link is only valid for a limited amount of time.
501
  *
502
+ * @param unknown $post_id Post ID of the attachment
503
+ * @param int $expires Seconds for the link to live
504
+ * @param null $size Size of the image to get
505
+ *
506
+ * @return mixed|void|WP_Error
507
  */
508
+ function get_secure_attachment_url( $post_id, $expires = null, $size = null ) {
509
+ if ( is_null( $expires ) ) {
510
+ $expires = self::DEFAULT_EXPIRES;
511
+ }
512
+ return $this->get_attachment_url( $post_id, $expires, $size );
513
  }
514
 
515
+ /**
516
+ * Get the url of the file from Amazon S3
517
+ *
518
+ * @param unknown $post_id Post ID of the attachment
519
+ * @param null $expires Seconds for the link to live
520
+ * @param null $size Size of the image to get
521
+ * @param null $meta Pre retrieved _wp_attachment_metadata for the attachment
522
+ *
523
+ * @return bool|mixed|void|WP_Error
524
+ */
525
+ function get_attachment_url( $post_id, $expires = null, $size = null, $meta = null ) {
526
+ if ( ! $this->get_setting( 'serve-from-s3' ) ) {
527
+ return false;
528
+ }
529
+
530
+ // check that the file has been uploaded to S3
531
+ if ( ! ( $s3object = $this->get_attachment_s3_info( $post_id ) ) ) {
532
  return false;
533
  }
534
 
539
  $scheme = 'http';
540
  }
541
 
542
+ // We don't use $this->get_s3object_region() here because we don't want
543
+ // to make an AWS API call and slow down page loading
544
+ if ( isset( $s3object['region'] ) ) {
545
+ $region = $s3object['region'];
546
+ }
547
+ else {
548
+ $region = '';
549
+ }
550
+
551
+ // force use of secured url when ACL has been set to private
552
+ if ( is_null( $expires ) && isset( $s3object['acl'] ) && self::PRIVATE_ACL == $s3object['acl'] ) {
553
+ $expires = self::DEFAULT_EXPIRES;
554
+ }
555
+
556
+ $prefix = ( '' == $region ) ? 's3' : 's3-' . $region;
557
+
558
  if ( is_null( $expires ) && $this->get_setting( 'cloudfront' ) ) {
559
  $domain_bucket = $this->get_setting( 'cloudfront' );
560
  }
562
  $domain_bucket = $s3object['bucket'];
563
  }
564
  elseif ( is_ssl() || $this->get_setting( 'force-ssl' ) ) {
565
+ $domain_bucket = $prefix . '.amazonaws.com/' . $s3object['bucket'];
566
  }
567
  else {
568
+ $domain_bucket = $s3object['bucket'] . '.' . $prefix . '.amazonaws.com';
569
  }
570
 
571
+ if ( $size ) {
572
+ if ( is_null( $meta ) ) {
573
+ $meta = get_post_meta( $post_id, '_wp_attachment_metadata', true );
574
+ }
575
+ if ( isset( $meta['sizes'][$size]['file'] ) ) {
576
+ $s3object['key'] = dirname( $s3object['key'] ) . '/' . $meta['sizes'][$size]['file'];
577
+ }
578
+ }
579
 
580
  if ( !is_null( $expires ) ) {
581
  try {
582
  $expires = time() + $expires;
583
+ $secure_url = $this->get_s3client()->getObjectUrl( $s3object['bucket'], $s3object['key'], $expires );
 
584
  }
585
  catch ( Exception $e ) {
586
  return new WP_Error( 'exception', $e->getMessage() );
587
  }
588
  }
589
 
590
+ // encode file
591
+ $file = $this->encode_filename_in_path( $s3object['key'] );
592
+
593
+ $url = $scheme . '://' . $domain_bucket . '/' . $file;
594
+ if ( isset( $secure_url ) ) {
595
+ $url .= substr( $secure_url, strpos( $secure_url, '?' ) );
596
+ }
597
+
598
+ return apply_filters( 'as3cf_get_attachment_url', $url, $s3object, $post_id, $expires );
599
+ }
600
+
601
+ /**
602
+ * Override the attachment metadata
603
+ *
604
+ * @param unknown $data
605
+ * @param unknown $post_id
606
+ *
607
+ * @return mixed
608
+ */
609
+ function wp_get_attachment_metadata( $data, $post_id ) {
610
+ return $this->maybe_encoded_file_of_resized_images( $data, $post_id );
611
+ }
612
+
613
+ /**
614
+ * Encodes the file names for resized image files for an attachment where necessary
615
+ *
616
+ * @param unknown $data
617
+ * @param unknown $post_id
618
+ *
619
+ * @return mixed Attachment meta data
620
+ */
621
+ function maybe_encoded_file_of_resized_images( $data, $post_id ) {
622
+ if ( ! $this->get_setting( 'serve-from-s3' ) ) {
623
+ return $data;
624
+ }
625
+
626
+ if ( ! ( $s3object = $this->get_attachment_s3_info( $post_id ) ) ) {
627
+ return $data;
628
+ }
629
+
630
+ // we only need to encode the file name if url encoding is needed
631
+ $filename = basename( $s3object['key'] );
632
+ if ( $filename == rawurlencode( $filename ) ) {
633
+ return $data;
634
+ }
635
+
636
+ // we only need to encode resized image files
637
+ if ( ! isset( $data['sizes'] ) ) {
638
+ return $data;
639
+ }
640
+
641
+ foreach ( $data['sizes'] as $key => $size ) {
642
+ $data['sizes'][ $key ]['file'] = $this->encode_filename_in_path( $data['sizes'][ $key ]['file'] );
643
+ }
644
+
645
+ return $data;
646
+ }
647
+
648
+ /**
649
+ * Encode file names according to RFC 3986 when generating urls
650
+ * As per Amazon https://forums.aws.amazon.com/thread.jspa?threadID=55746#jive-message-244233
651
+ *
652
+ * @param unknown $file
653
+ *
654
+ * @return string Encoded filename with path prefix untouched
655
+ */
656
+ function encode_filename_in_path( $file ) {
657
+ $file_path = dirname( $file );
658
+ $file_path = ( '.' != $file_path ) ? trailingslashit( $file_path ) : '';
659
+ $file_name = rawurlencode( basename( $file ) );
660
+
661
+ return $file_path . $file_name;
662
  }
663
 
664
  function verify_ajax_request() {
687
  }
688
 
689
  echo json_encode( $out );
690
+ exit;
691
  }
692
 
693
  function create_bucket( $bucket_name ) {
694
  try {
695
+ $this->get_s3client()->createBucket( array( 'Bucket' => $bucket_name ) );
696
  }
697
  catch ( Exception $e ) {
698
  return new WP_Error( 'exception', $e->getMessage() );
714
  return $this->s3client;
715
  }
716
 
717
+ /**
718
+ * Get the region of the bucket.
719
+ *
720
+ *
721
+ * @param unknown $s3object
722
+ * @param unknown $post_id - if supplied will update the s3 meta if no region found
723
+ *
724
+ * @return string - region name
725
+ */
726
+ function get_s3object_region( $s3object, $post_id = null ) {
727
+ if ( ! isset( $s3object['region'] ) ) {
728
+ // if region hasn't been stored in the s3 metadata retrieve using the bucket
729
+ try {
730
+ $region = $this->get_s3client()->getBucketLocation( array( 'Bucket' => $s3object['bucket'] ) );
731
+ }
732
+ catch ( Exception $e ) {
733
+ return new WP_Error( 'exception', $e->getMessage() );
734
+ }
735
+ $s3object['region'] = $region['Location'];
736
+
737
+ if ( ! is_null( $post_id ) ) {
738
+ // retrospectively update s3 metadata with region
739
+ update_post_meta( $post_id, 'amazonS3_info', $s3object );
740
+ }
741
+ }
742
+
743
+ return $s3object['region'];
744
+ }
745
+
746
+ /**
747
+ * Set the region of the AWS client based on the bucket.
748
+ *
749
+ * This is needed for non US standard buckets to add and delete files.
750
+ *
751
+ * @param unknown $s3object
752
+ * @param unknown $post_id
753
+ *
754
+ * @return string - region name
755
+ */
756
+ function set_s3client_region( $s3object, $post_id = null ) {
757
+ $region = $this->get_s3object_region( $s3object, $post_id );
758
+
759
+ if ( is_wp_error( $region ) ) {
760
+ return '';
761
+ }
762
+
763
+ if ( $region ) {
764
+ $this->get_s3client()->setRegion( $region );
765
+ }
766
+
767
+ return $region;
768
+ }
769
+
770
  function get_buckets() {
771
  try {
772
  $result = $this->get_s3client()->listBuckets();
778
  return $result['Buckets'];
779
  }
780
 
781
+ /**
782
+ * Checks the user has write permission for S3
783
+ *
784
+ * @param string $bucket
785
+ *
786
+ * @return bool
787
+ */
788
+ function check_write_permission( $bucket ) {
789
+ // fire up the filesystem API
790
+ $filesystem = WP_Filesystem();
791
+ global $wp_filesystem;
792
+ if ( false === $filesystem || is_null( $wp_filesystem ) ) {
793
+ return new WP_Error( 'exception', __( 'There was an error attempting to access the file system', 'as3cf' ) );
794
+ }
795
+
796
+ $uploads = wp_upload_dir();
797
+ $file_name = 'as3cf-permission-check.txt';
798
+ $file = trailingslashit( $uploads['basedir'] ) . $file_name;
799
+ $file_contents = __( 'This is a test file to check if the user has write permission to S3. Delete me if found.', 'as3cf' );
800
+ // create a temp file to upload
801
+ $temp_file = $wp_filesystem->put_contents( $file, $file_contents, FS_CHMOD_FILE );
802
+ if ( false === $temp_file ) {
803
+ return new WP_Error( 'exception', __( 'It looks like we cannot create a file locally to test the S3 permissions', 'as3cf' ) );
804
+ }
805
+
806
+ $args = array(
807
+ 'Bucket' => $bucket,
808
+ 'Key' => $file_name,
809
+ 'SourceFile' => $file,
810
+ 'ACL' => 'public-read'
811
+ );
812
+
813
+ try {
814
+ // attempt to create the test file
815
+ $this->get_s3client()->putObject( $args );
816
+ // delete it straight away if created
817
+ $this->get_s3client()->deleteObject( array(
818
+ 'Bucket' => $bucket,
819
+ 'Key' => $file_name
820
+ ) );
821
+ $can_write = true;
822
+ } catch ( Exception $e ) {
823
+ // write permission not found
824
+ $can_write = false;
825
+ }
826
+
827
+ // delete temp file
828
+ $wp_filesystem->delete( $file );
829
+
830
+ return $can_write;
831
+ }
832
+
833
  function plugin_load() {
834
+ $version = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? time() : $this->plugin_version;
835
+
836
  $src = plugins_url( 'assets/css/styles.css', $this->plugin_file_path );
837
+ wp_enqueue_style( 'as3cf-styles', $src, array(), $version );
838
 
839
  $suffix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
840
 
841
  $src = plugins_url( 'assets/js/script' . $suffix . '.js', $this->plugin_file_path );
842
+ wp_enqueue_script( 'as3cf-script', $src, array( 'jquery' ), $version, true );
843
+
844
  wp_localize_script( 'as3cf-script', 'as3cf_i18n', array(
845
+ 'create_bucket_prompt' => __( 'Bucket Name:', 'as3cf' ),
846
+ 'create_bucket_error' => __( 'Error creating bucket: ', 'as3cf' ),
847
+ 'create_bucket_nonce' => wp_create_nonce( 'as3cf-create-bucket' )
848
+ ) );
849
 
850
  $this->handle_post_request();
851
  }
859
  die( __( "Cheatin' eh?", 'amazon-web-services' ) );
860
  }
861
 
 
 
862
  $post_vars = array( 'bucket', 'virtual-host', 'expires', 'permissions', 'cloudfront', 'object-prefix', 'copy-to-s3', 'serve-from-s3', 'remove-local-file', 'force-ssl', 'hidpi-images', 'object-versioning' );
863
+
864
  foreach ( $post_vars as $var ) {
865
+ $this->remove_setting( $var );
866
+
867
  if ( !isset( $_POST[$var] ) ) {
868
  continue;
869
  }
879
 
880
  function render_page() {
881
  $this->aws->render_view( 'header', array( 'page_title' => $this->plugin_title ) );
882
+
883
  $aws_client = $this->aws->get_client();
884
 
885
  if ( is_wp_error( $aws_client ) ) {
886
+ $this->render_view( 'error-fatal', array( 'message' => $aws_client->get_error_message() ) );
887
  }
888
  else {
889
+ do_action( 'as3cf_pre_settings_render' );
890
  $this->render_view( 'settings' );
891
  }
892
+
893
  $this->aws->render_view( 'footer' );
894
  }
895
 
896
  function get_dynamic_prefix( $time = null ) {
897
+ $uploads = wp_upload_dir( $time );
898
+ return str_replace( $this->get_base_upload_path(), '', $uploads['path'] );
899
  }
900
 
901
  // Without the multisite subdirectory
902
+ function get_base_upload_path() {
903
  if ( defined( 'UPLOADS' ) && ! ( is_multisite() && get_site_option( 'ms_files_rewriting' ) ) ) {
904
  return ABSPATH . UPLOADS;
905
  }
916
  }
917
  }
918
 
919
+ /**
920
+ * Get all the blog IDs for the multisite network used for table prefixes
921
+ *
922
+ * @return array
923
+ */
924
+ function get_blog_ids() {
925
+ $args = array(
926
+ 'limit' => false,
927
+ 'spam' => 0,
928
+ 'deleted' => 0,
929
+ 'archived' => 0
930
+ );
931
+ $blogs = wp_get_sites( $args );
932
+
933
+ $blog_ids = array();
934
+ foreach ( $blogs as $blog ) {
935
+ if ( 1 == $blog['blog_id'] ) {
936
+ // ignore the first blog which doesn't have the ID in the table prefix
937
+ continue;
938
+ }
939
+ $blog_ids[] = $blog['blog_id'];
940
+ }
941
+
942
+ return $blog_ids;
943
+ }
944
  }
classes/as3cf-compatibility-check.php ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ class AS3CF_Compatibility_Check {
3
+
4
+ private $plugin_file_path, $aws_plugin_version_required;
5
+
6
+ function __construct( $plugin_file_path, $aws_plugin_version_required ) {
7
+ $this->plugin_file_path = $plugin_file_path;
8
+ $this->aws_plugin_version_required = $aws_plugin_version_required;
9
+
10
+ add_action( 'admin_notices', array( $this, 'hook_admin_notices' ) );
11
+ add_action( 'network_admin_notices', array( $this, 'hook_admin_notices' ) );
12
+ }
13
+
14
+ function is_compatible() {
15
+ return $this->get_error_msg() ? false : true;
16
+ }
17
+
18
+ function get_error_msg() {
19
+ static $msg;
20
+
21
+ if ( ! is_null( $msg ) ) {
22
+ return $msg;
23
+ }
24
+
25
+ $hide_notice_msg = '<br>' . __( 'You can deactivate the Amazon S3 and CloudFront plugin to get rid of this notice.', 'as3cf' );
26
+
27
+ if ( ! class_exists( 'Amazon_Web_Services' ) ) {
28
+ $msg = sprintf( __( 'Amazon S3 and CloudFront has been disabled as it requires the <a style="text-decoration:none;" href="%s">Amazon&nbsp;Web&nbsp;Services</a> plugin.', 'as3cf' ), 'http://wordpress.org/extend/plugins/amazon-web-services/' );
29
+
30
+ if ( file_exists( WP_PLUGIN_DIR . '/amazon-web-services/amazon-web-services.php' ) ) {
31
+ $msg .= ' ' . __( 'It appears to be installed already.', 'as3cf' );
32
+ $activate_url = wp_nonce_url( network_admin_url( 'plugins.php?action=activate&amp;plugin=amazon-web-services/amazon-web-services.php' ), 'activate-plugin_amazon-web-services/amazon-web-services.php' );
33
+ $msg .= ' <a style="font-weight:bold;text-decoration:none;" href="' . $activate_url . '">' . _x( 'Activate it now', 'Activate plugin', 'as3cf' ) . '</a>';
34
+ }
35
+ else {
36
+ $install_url = wp_nonce_url( network_admin_url( 'update.php?action=install-plugin&plugin=amazon-web-services' ), 'install-plugin_amazon-web-services' );
37
+ $msg .= ' ' . sprintf( __( '<a href="%s">Install it</a> and activate.', 'as3cf' ), $install_url );
38
+ }
39
+
40
+ $msg .= $hide_notice_msg;
41
+
42
+ return $msg;
43
+ }
44
+
45
+ $aws_plugin_version = isset( $GLOBALS['aws_meta']['amazon-web-services']['version'] ) ? $GLOBALS['aws_meta']['amazon-web-services']['version'] : 0;
46
+
47
+ if ( ! version_compare( $aws_plugin_version, $this->aws_plugin_version_required, '>=' ) ) {
48
+ $msg = sprintf( __( 'Amazon S3 and CloudFront has been disabled as it requires version %s or later of the <a style="text-decoration:none;" href="%s">Amazon&nbsp;Web&nbsp;Services</a> plugin.', 'as3cf' ), $this->aws_plugin_version_required, 'http://wordpress.org/extend/plugins/amazon-web-services/' );
49
+
50
+ if ( $aws_plugin_version ) {
51
+ $msg .= ' ' . sprintf( __( 'You currently have version %s installed.', 'as3cf' ), $aws_plugin_version );
52
+ }
53
+
54
+ $update_url = wp_nonce_url( network_admin_url( 'update.php?action=upgrade-plugin&plugin=amazon-web-services/amazon-web-services.php' ), 'upgrade-plugin_amazon-web-services/amazon-web-services.php' );
55
+ $msg .= ' <a style="font-weight:bold;text-decoration:none;white-space:nowrap;" href="' . $update_url . '">' . __( 'Update to the latest version', 'as3cf' ) . '</a>';
56
+
57
+ $msg .= $hide_notice_msg;
58
+
59
+ return $msg;
60
+ }
61
+
62
+ $as3cf_plugin_version_required = $GLOBALS['aws_meta']['amazon-web-services']['supported_addon_versions']['amazon-s3-and-cloudfront'];
63
+ $as3cf_plugin_version = $GLOBALS['aws_meta']['amazon-s3-and-cloudfront']['version'];
64
+
65
+ if ( ! version_compare( $as3cf_plugin_version, $as3cf_plugin_version_required, '>=' ) ) {
66
+ $msg = sprintf( __( 'Amazon S3 and CloudFront has been disabled because it will not work with the version of the Amazon&nbsp;Web&nbsp;Services plugin installed. Amazon&nbsp;S3&nbsp;and&nbsp;CloudFront %s or later is required.', 'as3cf' ), $as3cf_plugin_version_required );
67
+
68
+ $plugin_basename = plugin_basename( __FILE__ );
69
+ $update_url = wp_nonce_url( network_admin_url( 'update.php?action=upgrade-plugin&plugin=' . $plugin_basename ), 'upgrade-plugin_' . $plugin_basename );
70
+ $msg .= ' <a style="font-weight:bold;text-decoration:none;white-space:nowrap;" href="' . $update_url . '">' . __( 'Update Amazon S3 and CloudFront to the latest version', 'as3cf' ) . '</a>';
71
+
72
+ $msg .= $hide_notice_msg;
73
+
74
+ return $msg;
75
+ }
76
+
77
+ $msg = false;
78
+ return $msg;
79
+ }
80
+
81
+ function hook_admin_notices() {
82
+ if ( is_multisite() ) {
83
+ if ( ! current_user_can( 'manage_network_plugins' ) ) {
84
+ return; // Don't show notices if the user can't manage network plugins
85
+ }
86
+ }
87
+ else {
88
+ // Don't show notices if user doesn't have plugin management privileges
89
+ $caps = array( 'activate_plugins', 'update_plugins', 'install_plugins' );
90
+ foreach ( $caps as $cap ) {
91
+ if ( ! current_user_can( $cap ) ) {
92
+ return;
93
+ }
94
+ }
95
+ }
96
+
97
+ $error_msg = $this->get_error_msg();
98
+
99
+ if ( ! $error_msg ) {
100
+ return;
101
+ }
102
+
103
+ printf( '<div class="error"><p>%s</p></div>', $error_msg );
104
+ }
105
+ }
classes/as3cf-upgrade.php ADDED
@@ -0,0 +1,354 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Upgrade
4
+ *
5
+ * @package amazon-s3-and-cloudfront
6
+ * @subpackage Classes/Upgrade
7
+ * @copyright Copyright (c) 2014, Delicious Brains
8
+ * @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
9
+ * @since 0.6.2
10
+ */
11
+
12
+ // Exit if accessed directly
13
+ if ( ! defined( 'ABSPATH' ) ) {
14
+ exit;
15
+ }
16
+
17
+ /**
18
+ * AS3CF_Upgrade Class
19
+ *
20
+ * This class handles data updates and other migrations after a plugin update
21
+ *
22
+ * @since 0.6.2
23
+ */
24
+ class AS3CF_Upgrade {
25
+
26
+ private $as3cf;
27
+ private $cron_interval_in_minutes;
28
+ private $error_threshold;
29
+
30
+ const CRON_HOOK = 'as3cf_cron_update_meta_with_region';
31
+ const CRON_SCHEDULE_KEY = 'as3cf_update_meta_with_region_interval';
32
+
33
+ const STATUS_RUNNING = 1;
34
+ const STATUS_ERROR = 2;
35
+ const STATUS_PAUSED = 3;
36
+
37
+ /**
38
+ * Start it up
39
+ *
40
+ * @param unknown $as3cf - the instance of the as3cf class
41
+ */
42
+ function __construct( $as3cf ) {
43
+ $this->as3cf = $as3cf;
44
+
45
+ $this->cron_interval_in_minutes = apply_filters( 'as3cf_update_meta_with_region_interval', 10 );
46
+ $this->error_threshold = apply_filters( 'as3cf_update_meta_with_region_error_threshold', 20 );
47
+
48
+ add_filter( 'cron_schedules', array( $this, 'cron_schedules' ) );
49
+ add_action( self::CRON_HOOK, array( $this, 'cron_update_meta_with_region' ) );
50
+
51
+ add_action( 'as3cf_pre_settings_render', array( $this, 'maybe_display_notices' ) );
52
+ add_action( 'admin_init', array( $this, 'maybe_handle_action' ) );
53
+
54
+ $this->maybe_init_upgrade();
55
+ }
56
+
57
+ /**
58
+ * Maybe initialize the upgrade
59
+ */
60
+ function maybe_init_upgrade() {
61
+ if ( ! is_admin() || ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) {
62
+ return;
63
+ }
64
+
65
+ // Have we completed the upgrade yet?
66
+ if ( $this->as3cf->get_setting( 'post_meta_version', 0 ) > 0 ) {
67
+ return;
68
+ }
69
+
70
+ // If the upgrade status is already set, then we've already initialized the upgrade
71
+ if ( $this->get_upgrade_status() ) {
72
+ return;
73
+ }
74
+
75
+ // Initialize the upgrade
76
+ $this->save_session( array( 'status' => self::STATUS_RUNNING ) );
77
+
78
+ $this->schedule_event();
79
+ }
80
+
81
+ /**
82
+ * Adds notices about issues with upgrades allowing user to restart them
83
+ */
84
+ function maybe_display_notices() {
85
+ $restart_url = self_admin_url( 'admin.php?page=' . $this->as3cf->get_plugin_slug() . '&action=restart_update_meta_with_region' );
86
+
87
+ switch ( $this->get_upgrade_status() ) {
88
+ case self::STATUS_RUNNING :
89
+ $msg = sprintf( __( '<strong>Running Metadata Update</strong> &mdash; We&#8217;re going through all the Media Library items uploaded to S3 and updating the metadata with the bucket region it is served from. This will allow us to serve your files from the proper S3 region subdomain <span style="white-space:nowrap;">(e.g. s3-us-west-2.amazonaws.com)</span>. This will be done quietly in the background, processing a small batch of Media Library items every %d minutes. There should be no noticeable impact on your server&#8217;s performance.', 'as3cf' ), $this->cron_interval_in_minutes );
90
+ $msg .= ' <strong><a href="' . self_admin_url( 'admin.php?page=' . $this->as3cf->get_plugin_slug() . '&action=pause_update_meta_with_region' ) . '">' . __( 'Pause Update', 'as3cf' ) . '</a></strong>';
91
+ $this->as3cf->render_view( 'notice', array( 'message' => $msg ) );
92
+ break;
93
+ case self::STATUS_PAUSED :
94
+ $msg = __( '<strong>Metadata Update Paused</strong> &mdash; Updating Media Library metadata has been paused.', 'as3cf' );
95
+ $msg .= ' <strong><a href="' . $restart_url . '">' . __( 'Restart Update', 'as3cf' ) . '</a></strong>';
96
+ $this->as3cf->render_view( 'notice', array( 'message' => $msg ) );
97
+ break;
98
+ case self::STATUS_ERROR :
99
+ $msg = __( '<strong>Error Updating Metadata</strong> &mdash; We ran into some errors attempting to update the metadata for all your Media Library items that have been uploaded to S3. Please check your error log for details.', 'as3cf' );
100
+ $msg .= ' <strong><a href="' . $restart_url . '">' . __( 'Try Run It Again', 'as3cf' ) . '</a></strong>';
101
+ $this->as3cf->render_view( 'error', array( 'message' => $msg ) );
102
+ break;
103
+ }
104
+ }
105
+
106
+ function maybe_handle_action() {
107
+ if ( ! isset( $_GET['page'] ) || $_GET['page'] != $this->as3cf->get_plugin_slug() || ! isset( $_GET['action'] ) ) {
108
+ return;
109
+ }
110
+
111
+ $method_name = 'action_' . $_GET['action'];
112
+ if ( method_exists( $this, $method_name ) ) {
113
+ call_user_func( array( $this, $method_name ) );
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Restart upgrade
119
+ */
120
+ function action_restart_update_meta_with_region() {
121
+ $this->change_status_request( self::STATUS_RUNNING );
122
+ $this->schedule_event();
123
+ }
124
+
125
+ /**
126
+ * Pause upgrade
127
+ */
128
+ function action_pause_update_meta_with_region() {
129
+ $this->clear_scheduled_event();
130
+ $this->change_status_request( self::STATUS_PAUSED );
131
+ }
132
+
133
+ /**
134
+ * Helper for the above action requests
135
+ */
136
+ function change_status_request( $status ) {
137
+ $session = $this->get_session();
138
+ $session['status'] = $status;
139
+ $this->save_session( $session );
140
+
141
+ wp_redirect( self_admin_url( 'admin.php?page=' . $this->as3cf->get_plugin_slug() ) );
142
+ }
143
+
144
+ /**
145
+ * Add custom cron interval schedules
146
+ *
147
+ * @param array $schedules
148
+ *
149
+ * @return array
150
+ */
151
+ function cron_schedules( $schedules ) {
152
+ // Adds every 10 minutes to the existing schedules.
153
+ $schedules[ self::CRON_SCHEDULE_KEY ] = array(
154
+ 'interval' => $this->cron_interval_in_minutes * 60,
155
+ 'display' => __( 'Every ' . $this->cron_interval_in_minutes . ' Minutes', 'as3cf' )
156
+ );
157
+
158
+ return $schedules;
159
+ }
160
+
161
+ /**
162
+ * Wrapper for scheduling the cron job
163
+ */
164
+ function schedule_event() {
165
+ if ( ! wp_next_scheduled( self::CRON_HOOK ) ) {
166
+ wp_schedule_event( current_time( 'timestamp' ), self::CRON_SCHEDULE_KEY, self::CRON_HOOK );
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Wrapper for clearing scheduled events for a specific cron job
172
+ */
173
+ function clear_scheduled_event() {
174
+ $timestamp = wp_next_scheduled( self::CRON_HOOK );
175
+ if ( $timestamp ) {
176
+ wp_unschedule_event( $timestamp, self::CRON_HOOK );
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Cron jon to update the region of the bucket in s3 metadata
182
+ */
183
+ function cron_update_meta_with_region() {
184
+ // Check if the cron should even be running
185
+ if ( $this->as3cf->get_setting( 'post_meta_version', 0 ) > 0 || $this->get_upgrade_status() != self::STATUS_RUNNING ) {
186
+ $this->clear_scheduled_event();
187
+ return;
188
+ }
189
+
190
+ global $wpdb;
191
+ $prefix = $wpdb->prefix;
192
+
193
+ // set the batch size limit for the query
194
+ $limit = apply_filters( 'as3cf_update_meta_with_region_batch_size', 500 );
195
+ $all_limit = $limit;
196
+
197
+ $table_prefixes = array();
198
+ $session = $this->get_session();
199
+
200
+ // find the blog IDs that have been processed so we can skip them
201
+ $processed_blog_ids = isset( $session['processed_blog_ids'] ) ? $session['processed_blog_ids'] : array();
202
+ $error_count = isset( $session['error_count'] ) ? $session['error_count'] : 0;
203
+
204
+ if ( ! in_array( 1, $processed_blog_ids ) ) {
205
+ $table_prefixes[1] = $prefix;
206
+ }
207
+
208
+ if ( is_multisite() ) {
209
+ $blog_ids = $this->as3cf->get_blog_ids();
210
+ foreach ( $blog_ids as $blog_id ) {
211
+ if ( in_array( $blog_id, $processed_blog_ids ) ) {
212
+ continue;
213
+ }
214
+ $table_prefixes[ $blog_id ] = $prefix . $blog_id . '_';
215
+ }
216
+ }
217
+
218
+ $all_attachments = array();
219
+ $all_count = 0;
220
+
221
+ foreach ( $table_prefixes as $blog_id => $table_prefix ) {
222
+ $attachments = $this->get_attachments_without_region( $table_prefix, $limit );
223
+ $count = count( $attachments );
224
+
225
+ if ( 0 == $count ) {
226
+ // no more attachments, record the blog ID to skip next time
227
+ $processed_blog_ids[] = $blog_id;
228
+ } else {
229
+ $all_count += $count;
230
+ $all_attachments[ $blog_id ] = $attachments;
231
+ }
232
+
233
+ if ( $all_count >= $all_limit ) {
234
+ break;
235
+ }
236
+
237
+ $limit = $limit - $count;
238
+ }
239
+
240
+ if ( 0 == $all_count ) {
241
+ $this->as3cf->set_setting( 'post_meta_version', 1 );
242
+ $this->as3cf->remove_setting( 'update_meta_with_region_session' );
243
+ $this->as3cf->save_settings();
244
+ $this->clear_scheduled_event();
245
+ return;
246
+ }
247
+
248
+ // only process the loop for a certain amount of time
249
+ $minutes = $this->cron_interval_in_minutes * 60;
250
+
251
+ // smaller time limit so won't run into another instance of cron
252
+ $minutes = $minutes * 0.8;
253
+
254
+ $finish = time() + $minutes;
255
+
256
+ // loop through and update s3 meta with region
257
+ foreach ( $all_attachments as $blog_id => $attachments ) {
258
+ if ( 1 != $blog_id && is_multisite() ) {
259
+ switch_to_blog( $blog_id );
260
+ }
261
+
262
+ foreach ( $attachments as $attachment ) {
263
+ if ( $error_count >= $this->error_threshold ) {
264
+ $session['status'] = self::STATUS_ERROR;
265
+ $this->save_session( $session );
266
+ $this->clear_scheduled_event();
267
+ return;
268
+ }
269
+
270
+ if ( time() >= $finish ) {
271
+ break;
272
+ }
273
+
274
+ $s3object = unserialize( $attachment->s3object );
275
+ if ( false === $s3object ) {
276
+ error_log( 'Failed to unserialize S3 meta for attachment ' . $attachment->ID . ': ' . $attachment->s3object );
277
+ $error_count++;
278
+ continue;
279
+ }
280
+
281
+ // retrieve region and update the attachment metadata
282
+ $region = $this->as3cf->get_s3object_region( $s3object, $attachment->ID );
283
+ if ( is_wp_error( $region ) ) {
284
+ error_log( 'Error updating region: ' . $region->get_error_message() );
285
+ $error_count++;
286
+ }
287
+ }
288
+
289
+ if ( 1 != $blog_id && is_multisite() ) {
290
+ restore_current_blog();
291
+ }
292
+ }
293
+
294
+ $session['processed_blog_ids'] = $processed_blog_ids;
295
+ $session['error_count'] = $error_count;
296
+
297
+ $this->save_session( $session );
298
+ }
299
+
300
+ /*
301
+ * Get the current status of the upgrade
302
+ * See STATUS_* constants in the class declaration above.
303
+ */
304
+ function get_upgrade_status() {
305
+ $session = $this->get_session();
306
+
307
+ if ( ! isset( $session['status'] ) ) {
308
+ return '';
309
+ }
310
+
311
+ return $session['status'];
312
+ }
313
+
314
+ /*
315
+ * Retrieve session data from plugin settings
316
+ *
317
+ * @return array
318
+ */
319
+ function get_session() {
320
+ return $this->as3cf->get_setting( 'update_meta_with_region_session', array() );
321
+ }
322
+
323
+ /*
324
+ * Store data to be used between requests in plugin settings
325
+ *
326
+ * @param $session array of session data to store
327
+ */
328
+ function save_session( $session ) {
329
+ $this->as3cf->set_setting( 'update_meta_with_region_session', $session );
330
+ $this->as3cf->save_settings();
331
+ }
332
+
333
+ /**
334
+ * Get all attachments that don't have region in their S3 meta
335
+ *
336
+ * @param unknown $prefix
337
+ * @param unknown $limit
338
+ *
339
+ * @return mixed
340
+ */
341
+ function get_attachments_without_region( $prefix, $limit ) {
342
+ global $wpdb;
343
+ $sql = $wpdb->prepare(
344
+ "SELECT `post_id` as `ID`, `meta_value` AS 's3object'
345
+ FROM `{$prefix}postmeta`
346
+ WHERE `meta_key` = 'amazonS3_info'
347
+ AND `meta_value` NOT LIKE '%%\"region\"%%'
348
+ LIMIT %d",
349
+ $limit
350
+ );
351
+
352
+ return $wpdb->get_results( $sql, OBJECT );
353
+ }
354
+ }
composer.json ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "deliciousbrains/wp-amazon-s3-and-cloudfront",
3
+ "type": "wordpress-plugin",
4
+ "homepage": "https://github.com/deliciousbrains/wp-amazon-s3-and-cloudfront",
5
+ "license": "GPLv3",
6
+ "description": "Automatically copies media uploads to Amazon S3 for delivery. Optionally configure Amazon CloudFront for even faster delivery.",
7
+ "keywords": ["plugin","amazon-web-services","s3","cloudfront","cdn"],
8
+ "require": {
9
+ "composer/installers": "~1.0.6"
10
+ }
11
+ }
composer.lock ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_readme": [
3
+ "This file locks the dependencies of your project to a known state",
4
+ "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
5
+ "This file is @generated automatically"
6
+ ],
7
+ "hash": "4b6bce2aaae303cfd3fc478e7cc55366",
8
+ "packages": [
9
+ {
10
+ "name": "composer/installers",
11
+ "version": "v1.0.18",
12
+ "source": {
13
+ "type": "git",
14
+ "url": "https://github.com/composer/installers.git",
15
+ "reference": "74fb0a7a1a23696d9c8cc2fba5903f6711cdd067"
16
+ },
17
+ "dist": {
18
+ "type": "zip",
19
+ "url": "https://api.github.com/repos/composer/installers/zipball/74fb0a7a1a23696d9c8cc2fba5903f6711cdd067",
20
+ "reference": "74fb0a7a1a23696d9c8cc2fba5903f6711cdd067",
21
+ "shasum": ""
22
+ },
23
+ "replace": {
24
+ "roundcube/plugin-installer": "*",
25
+ "shama/baton": "*"
26
+ },
27
+ "require-dev": {
28
+ "composer/composer": "1.0.*@dev",
29
+ "phpunit/phpunit": "4.1.*"
30
+ },
31
+ "type": "composer-installer",
32
+ "extra": {
33
+ "class": "Composer\\Installers\\Installer",
34
+ "branch-alias": {
35
+ "dev-master": "1.0-dev"
36
+ }
37
+ },
38
+ "autoload": {
39
+ "psr-0": {
40
+ "Composer\\Installers\\": "src/"
41
+ }
42
+ },
43
+ "notification-url": "https://packagist.org/downloads/",
44
+ "license": [
45
+ "MIT"
46
+ ],
47
+ "authors": [
48
+ {
49
+ "name": "Kyle Robinson Young",
50
+ "email": "kyle@dontkry.com",
51
+ "homepage": "https://github.com/shama"
52
+ }
53
+ ],
54
+ "description": "A multi-framework Composer library installer",
55
+ "homepage": "http://composer.github.com/installers/",
56
+ "keywords": [
57
+ "Craft",
58
+ "Dolibarr",
59
+ "Hurad",
60
+ "MODX Evo",
61
+ "OXID",
62
+ "WolfCMS",
63
+ "agl",
64
+ "annotatecms",
65
+ "bitrix",
66
+ "cakephp",
67
+ "chef",
68
+ "codeigniter",
69
+ "concrete5",
70
+ "croogo",
71
+ "drupal",
72
+ "elgg",
73
+ "fuelphp",
74
+ "installer",
75
+ "joomla",
76
+ "kohana",
77
+ "laravel",
78
+ "lithium",
79
+ "magento",
80
+ "mako",
81
+ "mediawiki",
82
+ "modulework",
83
+ "moodle",
84
+ "phpbb",
85
+ "piwik",
86
+ "ppi",
87
+ "puppet",
88
+ "roundcube",
89
+ "shopware",
90
+ "silverstripe",
91
+ "symfony",
92
+ "typo3",
93
+ "wordpress",
94
+ "zend",
95
+ "zikula"
96
+ ],
97
+ "time": "2014-08-18 20:00:12"
98
+ }
99
+ ],
100
+ "packages-dev": [],
101
+ "aliases": [],
102
+ "minimum-stability": "stable",
103
+ "stability-flags": [],
104
+ "prefer-stable": false,
105
+ "platform": [],
106
+ "platform-dev": []
107
+ }
include/functions.php CHANGED
@@ -1,18 +1,14 @@
1
  <?php
2
  /**
3
- * Alias of as3cf_get_secure_attachment_url for backward compatibility
4
- * Will be depreated in a later version
5
  *
6
- * @since 2.0
7
- * @access public
8
- * @param mixed $post_id Post ID of the attachment or null to use the loop
9
- * @param int $expires Secondes for the link to live
10
- * @return array
11
  */
12
- function wps3_get_secure_attachment_url( $post_id, $expires = 900, $deprecated = '' ) {
13
- return as3cf_get_secure_attachment_url( $post_id, $expires = 900 );
14
- }
15
-
16
- function as3cf_get_secure_attachment_url( $post_id, $expires = 900, $operation = 'GET' ) {
17
 
18
- }
 
1
  <?php
2
  /**
3
+ * API function to generate a link to download a file from Amazon S3 using
4
+ * query string authentication, expiring after a set amount of time.
5
  *
6
+ * @param mixed $post_id Post ID of the attachment or null to use the loop
7
+ * @param int $expires Seconds for the link to live
8
+ * @param mixed $size Size of the image to get
 
 
9
  */
10
+ function as3cf_get_secure_attachment_url( $post_id, $expires = 900, $size = null ) {
11
+ global $as3cf;
 
 
 
12
 
13
+ return $as3cf->get_secure_attachment_url( $post_id, $expires, $size );
14
+ }
languages/amazon-s3-and-cloudfront.pot ADDED
@@ -0,0 +1,315 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ msgid ""
2
+ msgstr ""
3
+ "Project-Id-Version: Amazon S3 and CloudFront\n"
4
+ "POT-Creation-Date: 2014-12-02 16:41-0000\n"
5
+ "PO-Revision-Date: 2014-12-02 16:44-0000\n"
6
+ "Last-Translator: Delicious Brains <nom@deliciousbrains.com>\n"
7
+ "Language-Team: Delicious Brains <nom@deliciousbrains.com>\n"
8
+ "Language: en\n"
9
+ "MIME-Version: 1.0\n"
10
+ "Content-Type: text/plain; charset=UTF-8\n"
11
+ "Content-Transfer-Encoding: 8bit\n"
12
+ "X-Generator: Poedit 1.6.10\n"
13
+ "X-Poedit-Basepath: ..\n"
14
+ "X-Poedit-SourceCharset: UTF-8\n"
15
+ "X-Poedit-KeywordsList: __;_e;_n:1,2;_x:1,2c;_ex:1,2c;_nx:4c,1,2;esc_attr__;"
16
+ "esc_attr_e;esc_attr_x:1,2c;esc_html__;esc_html_e;esc_html_x:1,2c;_n_noop:1,2;"
17
+ "_nx_noop:3c,1,2;__ngettext_noop:1,2\n"
18
+ "Plural-Forms: nplurals=2; plural=(n != 1);\n"
19
+ "X-Poedit-SearchPath-0: .\n"
20
+
21
+ #: classes/amazon-s3-and-cloudfront.php:25
22
+ msgid "Amazon S3 and CloudFront"
23
+ msgstr ""
24
+
25
+ #: classes/amazon-s3-and-cloudfront.php:26
26
+ msgid "S3 and CloudFront"
27
+ msgstr ""
28
+
29
+ #: classes/amazon-s3-and-cloudfront.php:666
30
+ msgid "Cheatin&#8217; eh?"
31
+ msgstr ""
32
+
33
+ #: classes/amazon-s3-and-cloudfront.php:670
34
+ msgid "You do not have sufficient permissions to access this page."
35
+ msgstr ""
36
+
37
+ #: classes/amazon-s3-and-cloudfront.php:678
38
+ msgid "No bucket name provided."
39
+ msgstr ""
40
+
41
+ #: classes/amazon-s3-and-cloudfront.php:793
42
+ msgid "There was an error attempting to access the file system"
43
+ msgstr ""
44
+
45
+ #: classes/amazon-s3-and-cloudfront.php:799
46
+ msgid ""
47
+ "This is a test file to check if the user has write permission to S3. Delete "
48
+ "me if found."
49
+ msgstr ""
50
+
51
+ #: classes/amazon-s3-and-cloudfront.php:803
52
+ msgid ""
53
+ "It looks like we cannot create a file locally to test the S3 permissions"
54
+ msgstr ""
55
+
56
+ #: classes/amazon-s3-and-cloudfront.php:845
57
+ msgid "Bucket Name:"
58
+ msgstr ""
59
+
60
+ #: classes/amazon-s3-and-cloudfront.php:846
61
+ msgid "Error creating bucket: "
62
+ msgstr ""
63
+
64
+ #: classes/amazon-s3-and-cloudfront.php:859
65
+ msgid "Cheatin' eh?"
66
+ msgstr ""
67
+
68
+ #: classes/as3cf-compatibility-check.php:25
69
+ msgid ""
70
+ "You can deactivate the Amazon S3 and CloudFront plugin to get rid of this "
71
+ "notice."
72
+ msgstr ""
73
+
74
+ #: classes/as3cf-compatibility-check.php:28
75
+ #, php-format
76
+ msgid ""
77
+ "Amazon S3 and CloudFront has been disabled as it requires the <a style="
78
+ "\"text-decoration:none;\" href=\"%s\">Amazon&nbsp;Web&nbsp;Services</a> "
79
+ "plugin."
80
+ msgstr ""
81
+
82
+ #: classes/as3cf-compatibility-check.php:31
83
+ msgid "It appears to be installed already."
84
+ msgstr ""
85
+
86
+ #: classes/as3cf-compatibility-check.php:33
87
+ msgctxt "Activate plugin"
88
+ msgid "Activate it now"
89
+ msgstr ""
90
+
91
+ #: classes/as3cf-compatibility-check.php:37
92
+ #, php-format
93
+ msgid "<a href=\"%s\">Install it</a> and activate."
94
+ msgstr ""
95
+
96
+ #: classes/as3cf-compatibility-check.php:48
97
+ #, php-format
98
+ msgid ""
99
+ "Amazon S3 and CloudFront has been disabled as it requires version %s or "
100
+ "later of the <a style=\"text-decoration:none;\" href=\"%s\">Amazon&nbsp;"
101
+ "Web&nbsp;Services</a> plugin."
102
+ msgstr ""
103
+
104
+ #: classes/as3cf-compatibility-check.php:51
105
+ #, php-format
106
+ msgid "You currently have version %s installed."
107
+ msgstr ""
108
+
109
+ #: classes/as3cf-compatibility-check.php:55
110
+ msgid "Update to the latest version"
111
+ msgstr ""
112
+
113
+ #: classes/as3cf-compatibility-check.php:66
114
+ #, php-format
115
+ msgid ""
116
+ "Amazon S3 and CloudFront has been disabled because it will not work with the "
117
+ "version of the Amazon&nbsp;Web&nbsp;Services plugin installed. Amazon&nbsp;"
118
+ "S3&nbsp;and&nbsp;CloudFront %s or later is required."
119
+ msgstr ""
120
+
121
+ #: classes/as3cf-compatibility-check.php:70
122
+ msgid "Update Amazon S3 and CloudFront to the latest version"
123
+ msgstr ""
124
+
125
+ #: classes/as3cf-upgrade.php:89
126
+ #, php-format
127
+ msgid ""
128
+ "<strong>Running Metadata Update</strong> &mdash; We&#8217;re going through "
129
+ "all the Media Library items uploaded to S3 and updating the metadata with "
130
+ "the bucket region it is served from. This will allow us to serve your files "
131
+ "from the proper S3 region subdomain <span style=\"white-space:nowrap;\">(e."
132
+ "g. s3-us-west-2.amazonaws.com)</span>. This will be done quietly in the "
133
+ "background, processing a small batch of Media Library items every %d "
134
+ "minutes. There should be no noticeable impact on your server&#8217;s "
135
+ "performance."
136
+ msgstr ""
137
+
138
+ #: classes/as3cf-upgrade.php:90
139
+ msgid "Pause Update"
140
+ msgstr ""
141
+
142
+ #: classes/as3cf-upgrade.php:94
143
+ msgid ""
144
+ "<strong>Metadata Update Paused</strong> &mdash; Updating Media Library "
145
+ "metadata has been paused."
146
+ msgstr ""
147
+
148
+ #: classes/as3cf-upgrade.php:95
149
+ msgid "Restart Update"
150
+ msgstr ""
151
+
152
+ #: classes/as3cf-upgrade.php:99
153
+ msgid ""
154
+ "<strong>Error Updating Metadata</strong> &mdash; We ran into some errors "
155
+ "attempting to update the metadata for all your Media Library items that have "
156
+ "been uploaded to S3. Please check your error log for details."
157
+ msgstr ""
158
+
159
+ #: classes/as3cf-upgrade.php:100
160
+ msgid "Try Run It Again"
161
+ msgstr ""
162
+
163
+ #: classes/as3cf-upgrade.php:155
164
+ msgid "Every "
165
+ msgstr ""
166
+
167
+ #: view/settings.php:10
168
+ msgid "Error retrieving a list of your S3 buckets from AWS:"
169
+ msgstr ""
170
+
171
+ #: view/settings.php:21
172
+ msgid "Settings saved."
173
+ msgstr ""
174
+
175
+ #: view/settings.php:41
176
+ msgid "S3 Policy is Read-Only"
177
+ msgstr ""
178
+
179
+ #: view/settings.php:43
180
+ #, php-format
181
+ msgid ""
182
+ "You need to go to <a href=\"%s\">Identity and Access Management</a> in your "
183
+ "AWS console and manage the policy for the user you're using for this plugin. "
184
+ "Your policy should look something like the following:"
185
+ msgstr ""
186
+
187
+ #: view/settings.php:69
188
+ msgid "S3 Settings"
189
+ msgstr ""
190
+
191
+ #: view/settings.php:72
192
+ msgid "Select an S3 Bucket"
193
+ msgstr ""
194
+
195
+ #: view/settings.php:76
196
+ msgid "Create a new bucket..."
197
+ msgstr ""
198
+
199
+ #: view/settings.php:80
200
+ msgid "Bucket is setup for virtual hosting"
201
+ msgstr ""
202
+
203
+ #: view/settings.php:80
204
+ msgid "more info"
205
+ msgstr ""
206
+
207
+ #: view/settings.php:84
208
+ #, php-format
209
+ msgid ""
210
+ "Set a <a href=\"%s\" target=\"_blank\">far future HTTP expiration header</a> "
211
+ "for uploaded files <em>(recommended)</em>"
212
+ msgstr ""
213
+
214
+ #: view/settings.php:90
215
+ msgid "Object Path:"
216
+ msgstr ""
217
+
218
+ #: view/settings.php:98
219
+ msgid "CloudFront Settings"
220
+ msgstr ""
221
+
222
+ #: view/settings.php:100
223
+ msgid "Domain Name"
224
+ msgstr ""
225
+
226
+ #: view/settings.php:102
227
+ msgid "Leave blank if you aren&#8217;t using CloudFront."
228
+ msgstr ""
229
+
230
+ #: view/settings.php:109
231
+ msgid "Plugin Settings"
232
+ msgstr ""
233
+
234
+ #: view/settings.php:112
235
+ msgid "Copy files to S3 as they are uploaded to the Media Library"
236
+ msgstr ""
237
+
238
+ #: view/settings.php:116
239
+ msgid "Point file URLs to S3/CloudFront for files that have been copied to S3"
240
+ msgstr ""
241
+
242
+ #: view/settings.php:117
243
+ msgid ""
244
+ "When this is option unchecked, your Media Library images and other files "
245
+ "will be served from your server instead of S3/CloudFront. If any images were "
246
+ "removed from your server, they will be broken."
247
+ msgstr ""
248
+
249
+ #: view/settings.php:124
250
+ msgid ""
251
+ "Remove uploaded file from local filesystem once it has been copied to S3"
252
+ msgstr ""
253
+
254
+ #: view/settings.php:128
255
+ msgid "Always serve files over https (SSL)"
256
+ msgstr ""
257
+
258
+ #: view/settings.php:132
259
+ #, php-format
260
+ msgid ""
261
+ "Implement <a href=\"%s\">object versioning</a> by appending a timestamp to "
262
+ "the S3 file path"
263
+ msgstr ""
264
+
265
+ #: view/settings.php:136
266
+ msgid "Copy any HiDPI (@2x) images to S3 (works with WP Retina 2x plugin)"
267
+ msgstr ""
268
+
269
+ #: view/settings.php:142
270
+ msgid "Save Changes"
271
+ msgstr ""
272
+
273
+ #: view/sidebar.php:6
274
+ msgid "Pro Version?"
275
+ msgstr ""
276
+
277
+ #: view/sidebar.php:11
278
+ msgid ""
279
+ "We're working on a pro version that will include the following features:"
280
+ msgstr ""
281
+
282
+ #: view/sidebar.php:15
283
+ msgid "Copy existing Media Library to S3"
284
+ msgstr ""
285
+
286
+ #: view/sidebar.php:16
287
+ msgid "Serve theme JS & CSS from S3/CloudFront"
288
+ msgstr ""
289
+
290
+ #: view/sidebar.php:17
291
+ msgid ""
292
+ "WooCommerce & <abbr title=\"Easy Digital Downloads\">EDD</abbr> integration"
293
+ msgstr ""
294
+
295
+ #: view/sidebar.php:18
296
+ msgid "Awesome email support"
297
+ msgstr ""
298
+
299
+ #: view/sidebar.php:22
300
+ msgid "Your Name"
301
+ msgstr ""
302
+
303
+ #: view/sidebar.php:26
304
+ msgid "Your Email"
305
+ msgstr ""
306
+
307
+ #: view/sidebar.php:30
308
+ msgid "Send me news about a pro version"
309
+ msgstr ""
310
+
311
+ #: view/sidebar.php:34
312
+ msgid ""
313
+ "We promise we will not use your email for anything else and you can "
314
+ "unsubscribe with 1-click anytime."
315
+ msgstr ""
readme.txt CHANGED
@@ -3,8 +3,8 @@ Contributors: bradt
3
  Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=5VPMGLLK94XJC
4
  Tags: uploads, amazon, s3, mirror, admin, media, cdn, cloudfront
5
  Requires at least: 3.5
6
- Tested up to: 3.6.1
7
- Stable tag: 0.6.1
8
  License: GPLv3
9
 
10
  Copies files to Amazon S3 as they are uploaded to the Media Library. Optionally configure Amazon CloudFront for faster delivery.
@@ -17,15 +17,26 @@ Uploading files *directly* to your S3 account is not currently supported by this
17
 
18
  If you're adding this plugin to a site that's been around for a while, your existing media files will not be copied or served from S3. Only newly uploaded files will be copied and served from S3.
19
 
20
- **[Request features, report bugs, and submit pull requests on Github](https://github.com/bradt/wp-tantan-s3/issues)**
21
 
22
- *This plugin has been completely rewritten, but was originally a fork of
23
- [Amazon S3 for WordPress with CloudFront](http://wordpress.org/extend/plugins/tantan-s3-cloudfront/)
 
 
 
 
 
 
 
 
 
 
 
24
  which is a fork of [Amazon S3 for WordPress](http://wordpress.org/extend/plugins/tantan-s3/), also known as tantan-s3.*
25
 
26
  == Installation ==
27
 
28
- 1. Install the required [Amazon Web Services plugin](https://github.com/deliciousbrains/wp-amazon-web-services) using WordPress' built-in installer
29
  2. Follow the instructions to setup your AWS access keys
30
  3. Install this plugin using WordPress' built-in installer
31
  4. Access the *S3 and CloudFront* option under *AWS* and configure
@@ -42,15 +53,41 @@ This version requires PHP 5.3.3+ and the Amazon Web Services plugin
42
  = 0.6.1 =
43
  This version requires PHP 5.3.3+ and the Amazon Web Services plugin
44
 
 
 
 
45
  == Changelog ==
46
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  = 0.6.1 - 2013-09-21 =
48
  * WP.org download of Amazon Web Services plugin is giving a 404 Not Found, so directing people to download from Github instead
49
 
50
  = 0.6 - 2013-09-20 =
51
  * Complete rewrite
52
  * Now requires PHP 5.3.3+
53
- * Now requires the [Amazon Web Services plugin](https://github.com/deliciousbrains/wp-amazon-web-services) which contains the latest PHP libraries from Amazon
54
  * Now works with multisite
55
  * New Option: Custom S3 object path
56
  * New Option: Always serve files over https (SSL)
@@ -62,6 +99,6 @@ This version requires PHP 5.3.3+ and the Amazon Web Services plugin
62
  * Forked [Amazon S3 for WordPress with CloudFront](http://wordpress.org/extend/plugins/tantan-s3-cloudfront/)
63
  * Cleaned up the UI to fit with today's WP UI
64
  * Fixed issues causing error messages when WP_DEBUG is on
65
- * [Delete files on S3 when deleting WP attachment](https://github.com/bradt/wp-tantan-s3/commit/e777cd49a4b6999f999bd969241fb24cbbcece60)
66
- * [Added filter to the get_attachment_url function](https://github.com/bradt/wp-tantan-s3/commit/bbe1aed5c2ae900e9ba1b16ba6806c28ab8e2f1c)
67
- * [Added function to get a temporary, secure download URL for private files](https://github.com/bradt/wp-tantan-s3/commit/11f46ec2714d34907009e37ad3b97f4421aefed3)
3
  Donate link: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=5VPMGLLK94XJC
4
  Tags: uploads, amazon, s3, mirror, admin, media, cdn, cloudfront
5
  Requires at least: 3.5
6
+ Tested up to: 3.9
7
+ Stable tag: 0.7
8
  License: GPLv3
9
 
10
  Copies files to Amazon S3 as they are uploaded to the Media Library. Optionally configure Amazon CloudFront for faster delivery.
17
 
18
  If you're adding this plugin to a site that's been around for a while, your existing media files will not be copied or served from S3. Only newly uploaded files will be copied and served from S3.
19
 
20
+ **Pro Version**
21
 
22
+ We’re working on a pro version that will include the following features:
23
+
24
+ * Copy existing Media Library to S3
25
+ * Serve theme JS & CSS from S3/CloudFront
26
+ * WooCommerce & EDD integration
27
+ * Awesome email support
28
+
29
+ [Sign up for news about the pro version](https://confirmsubscription.com/h/t/295CA85AEB94E879)
30
+
31
+ [Request features, report bugs, and submit pull requests on Github](https://github.com/deliciousbrains/wp-amazon-s3-and-cloudfront/issues)
32
+
33
+ *This plugin has been completely rewritten, but was originally a fork of
34
+ [Amazon S3 for WordPress with CloudFront](http://wordpress.org/extend/plugins/tantan-s3-cloudfront/)
35
  which is a fork of [Amazon S3 for WordPress](http://wordpress.org/extend/plugins/tantan-s3/), also known as tantan-s3.*
36
 
37
  == Installation ==
38
 
39
+ 1. Install the required [Amazon Web Services plugin](http://wordpress.org/extend/plugins/amazon-web-services/) using WordPress' built-in installer
40
  2. Follow the instructions to setup your AWS access keys
41
  3. Install this plugin using WordPress' built-in installer
42
  4. Access the *S3 and CloudFront* option under *AWS* and configure
53
  = 0.6.1 =
54
  This version requires PHP 5.3.3+ and the Amazon Web Services plugin
55
 
56
+ = 0.6.2 =
57
+ This version requires PHP 5.3.3+ and the Amazon Web Services plugin
58
+
59
  == Changelog ==
60
 
61
+ = 0.7 - 2014-12-04 =
62
+ * New: Proper S3 region subdomain in URLs for buckets not in the US Standard region (e.g. https://s3-us-west-2.amazonaws.com/...)
63
+ * New: Update all existing attachment meta with bucket region (automatically runs in the background)
64
+ * New: Get secure URL for different image sizes (iamzozo)
65
+ * New: S3 bucket can be set with constant in wp-config.php (dberube)
66
+ * New: Filter for allowing/disallowing file types: `as3cf_allowed_mime_types`
67
+ * New: Filter to cancel upload to S3 for any reason: `as3cf_pre_update_attachment_metadata`
68
+ * New: Sidebar with email opt-in
69
+ * Improvement: Show warning when S3 policy is read-only
70
+ * Improvement: Tooltip added to clarify option
71
+ * Improvement: Move object versioning option to make it clear it does not require CloudFront
72
+ * Improvement: By default only allow file types in `get_allowed_mime_types()` to be uploaded to S3
73
+ * Improvement: Compatibility with WPML Media plugin
74
+ * Bug Fix: Edited images not removed on S3 when restoring image and IMAGE_EDIT_OVERWRITE true
75
+ * Bug Fix: File names with certain characters broken not working
76
+ * Bug Fix: Edited image uploaded to incorrect month folder
77
+ * Bug Fix: When creating a new bucket the bucket select box appears empty on success
78
+ * Bug Fix: SSL not working in regions other than US Standard
79
+ * Bug Fix: 'Error uploading' and 'Error removing local file' messages when editing an image
80
+ * Bug Fix: Upload and delete failing when bucket is non-US-region and bucket name contains dot
81
+ * Bug Fix: S3 file overwritten when file with same name uploaded and local file removed (dataferret)
82
+ * Bug Fix: Manually resized images not uploaded (gmauricio)
83
+
84
  = 0.6.1 - 2013-09-21 =
85
  * WP.org download of Amazon Web Services plugin is giving a 404 Not Found, so directing people to download from Github instead
86
 
87
  = 0.6 - 2013-09-20 =
88
  * Complete rewrite
89
  * Now requires PHP 5.3.3+
90
+ * Now requires the [Amazon Web Services plugin](http://wordpress.org/extend/plugins/amazon-web-services/) which contains the latest PHP libraries from Amazon
91
  * Now works with multisite
92
  * New Option: Custom S3 object path
93
  * New Option: Always serve files over https (SSL)
99
  * Forked [Amazon S3 for WordPress with CloudFront](http://wordpress.org/extend/plugins/tantan-s3-cloudfront/)
100
  * Cleaned up the UI to fit with today's WP UI
101
  * Fixed issues causing error messages when WP_DEBUG is on
102
+ * [Delete files on S3 when deleting WP attachment](https://github.com/deliciousbrains/wp-amazon-s3-and-cloudfront/commit/e777cd49a4b6999f999bd969241fb24cbbcece60)
103
+ * [Added filter to the get_attachment_url function](https://github.com/deliciousbrains/wp-amazon-s3-and-cloudfront/commit/bbe1aed5c2ae900e9ba1b16ba6806c28ab8e2f1c)
104
+ * [Added function to get a temporary, secure download URL for private files](https://github.com/deliciousbrains/wp-amazon-s3-and-cloudfront/commit/11f46ec2714d34907009e37ad3b97f4421aefed3)
view/error-fatal.php ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
1
+ <div class="aws-content as3cf-error">
2
+
3
+ <?php $this->render_view( 'error', compact( 'message' ) ); ?>
4
+
5
+ </div>
view/error.php CHANGED
@@ -1,7 +1,3 @@
1
- <div class="aws-content as3cf-error">
2
-
3
- <div class="error">
4
- <p><?php echo $error->get_error_message(); ?></p>
5
- </div>
6
-
7
- </div>
1
+ <div class="error">
2
+ <p><?php echo $message; ?></p>
3
+ </div>
 
 
 
 
view/notice.php ADDED
@@ -0,0 +1,3 @@
 
 
 
1
+ <div class="updated as3cf-notice">
2
+ <p><?php echo $message; ?></p>
3
+ </div>
view/settings.php CHANGED
@@ -23,6 +23,40 @@ if ( isset( $_GET['updated'] ) ) {
23
  </div>
24
  <?php
25
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  ?>
27
 
28
  <form method="post">
@@ -37,13 +71,13 @@ if ( isset( $_GET['updated'] ) ) {
37
  <select name="bucket" class="bucket">
38
  <option value="">-- <?php _e( 'Select an S3 Bucket', 'as3cf' ); ?> --</option>
39
  <?php if ( is_array( $buckets ) ) foreach ( $buckets as $bucket ): ?>
40
- <option value="<?php echo esc_attr( $bucket['Name'] ); ?>" <?php echo $bucket['Name'] == $this->get_setting( 'bucket' ) ? 'selected="selected"' : ''; ?>><?php echo esc_html( $bucket['Name'] ); ?></option>
41
  <?php endforeach;?>
42
  <option value="new"><?php _e( 'Create a new bucket...', 'as3cf' ); ?></option>
43
  </select><br />
44
 
45
  <input type="checkbox" name="virtual-host" value="1" id="virtual-host" <?php echo $this->get_setting( 'virtual-host' ) ? 'checked="checked" ' : '';?> />
46
- <label for="virtual-host"> <?php _e( 'Bucket is setup for virtual hosting', 'as3cf' ); ?></label> (<a href="http://docs.amazonwebservices.com/AmazonS3/2006-03-01/VirtualHosting.html">more info</a>)
47
  <br />
48
 
49
  <input type="checkbox" name="expires" value="1" id="expires" <?php echo $this->get_setting( 'expires' ) ? 'checked="checked" ' : ''; ?> />
@@ -67,8 +101,6 @@ if ( isset( $_GET['updated'] ) ) {
67
  <input type="text" name="cloudfront" value="<?php echo esc_attr( $this->get_setting( 'cloudfront' ) ); ?>" size="50" />
68
  <p class="description"><?php _e( 'Leave blank if you aren&#8217;t using CloudFront.', 'as3cf' ); ?></p>
69
 
70
- <input type="checkbox" name="object-versioning" value="1" id="object-versioning" <?php echo $this->get_setting( 'object-versioning' ) ? 'checked="checked" ' : ''; ?> />
71
- <label for="object-versioning"> <?php printf( __( 'Implement <a href="%s">object versioning</a> by appending a timestamp to the S3 file path', 'as3cf' ), 'http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/ReplacingObjects.html' ); ?></label>
72
  </td>
73
  </tr>
74
 
@@ -81,7 +113,11 @@ if ( isset( $_GET['updated'] ) ) {
81
  <br />
82
 
83
  <input type="checkbox" name="serve-from-s3" value="1" id="serve-from-s3" <?php echo $this->get_setting( 'serve-from-s3' ) ? 'checked="checked" ' : ''; ?> />
84
- <label for="serve-from-s3"> <?php _e( 'Point file URLs to S3/CloudFront for files that have been copied to S3', 'as3cf' ); ?></label>
 
 
 
 
85
  <br />
86
 
87
  <input type="checkbox" name="remove-local-file" value="1" id="remove-local-file" <?php echo $this->get_setting( 'remove-local-file' ) ? 'checked="checked" ' : ''; ?> />
@@ -92,6 +128,10 @@ if ( isset( $_GET['updated'] ) ) {
92
  <label for="force-ssl"> <?php _e( 'Always serve files over https (SSL)', 'as3cf' ); ?></label>
93
  <br />
94
 
 
 
 
 
95
  <input type="checkbox" name="hidpi-images" value="1" id="hidpi-images" <?php echo $this->get_setting( 'hidpi-images' ) ? 'checked="checked" ' : ''; ?> />
96
  <label for="hidpi-images"> <?php _e( 'Copy any HiDPI (@2x) images to S3 (works with WP Retina 2x plugin)', 'as3cf' ); ?></label>
97
 
@@ -106,4 +146,6 @@ if ( isset( $_GET['updated'] ) ) {
106
 
107
  </form>
108
 
 
 
109
  </div>
23
  </div>
24
  <?php
25
  }
26
+
27
+ $can_write = true;
28
+ if ( ! is_wp_error( $buckets ) && is_array( $buckets ) ) {
29
+ $can_write = $this->check_write_permission( $buckets[0]['Name'] );
30
+ // catch any file system issues
31
+ if ( is_wp_error( $can_write ) ) {
32
+ $this->render_view( 'error', array( 'error' => $can_write ) );
33
+ return;
34
+ }
35
+ }
36
+ // display a error message if the user does not have write permission to S3
37
+ if ( ! $can_write ) : ?>
38
+ <div class="error">
39
+ <p>
40
+ <strong>
41
+ <?php _e( 'S3 Policy is Read-Only', 'as3cf' ); ?>
42
+ </strong>&mdash;
43
+ <?php printf( __( 'You need to go to <a href="%s">Identity and Access Management</a> in your AWS console and manage the policy for the user you\'re using for this plugin. Your policy should look something like the following:', 'as3cf' ), 'https://console.aws.amazon.com/iam/home' ); ?>
44
+ </p>
45
+ <pre><code>{
46
+ "Version": "2012-10-17",
47
+ "Statement": [
48
+ {
49
+ "Effect": "Allow",
50
+ "Action": "s3:*",
51
+ "Resource": "*"
52
+ }
53
+ ]
54
+ }</code></pre>
55
+ </div>
56
+ <?php
57
+ // don't show the rest of the settings if cannot write
58
+ return;
59
+ endif;
60
  ?>
61
 
62
  <form method="post">
71
  <select name="bucket" class="bucket">
72
  <option value="">-- <?php _e( 'Select an S3 Bucket', 'as3cf' ); ?> --</option>
73
  <?php if ( is_array( $buckets ) ) foreach ( $buckets as $bucket ): ?>
74
+ <option value="<?php echo esc_attr( $bucket['Name'] ); ?>" <?php echo $bucket['Name'] == $this->get_setting( 'bucket' ) ? 'selected="selected"' : ''; ?>><?php echo esc_html( $bucket['Name'] ); ?></option>
75
  <?php endforeach;?>
76
  <option value="new"><?php _e( 'Create a new bucket...', 'as3cf' ); ?></option>
77
  </select><br />
78
 
79
  <input type="checkbox" name="virtual-host" value="1" id="virtual-host" <?php echo $this->get_setting( 'virtual-host' ) ? 'checked="checked" ' : '';?> />
80
+ <label for="virtual-host"> <?php _e( 'Bucket is setup for virtual hosting', 'as3cf' ); ?></label> (<a href="http://docs.aws.amazon.com/AmazonS3/latest/dev/VirtualHosting.html"><?php _e( 'more info', 'as3cf' ); ?></a>)
81
  <br />
82
 
83
  <input type="checkbox" name="expires" value="1" id="expires" <?php echo $this->get_setting( 'expires' ) ? 'checked="checked" ' : ''; ?> />
101
  <input type="text" name="cloudfront" value="<?php echo esc_attr( $this->get_setting( 'cloudfront' ) ); ?>" size="50" />
102
  <p class="description"><?php _e( 'Leave blank if you aren&#8217;t using CloudFront.', 'as3cf' ); ?></p>
103
 
 
 
104
  </td>
105
  </tr>
106
 
113
  <br />
114
 
115
  <input type="checkbox" name="serve-from-s3" value="1" id="serve-from-s3" <?php echo $this->get_setting( 'serve-from-s3' ) ? 'checked="checked" ' : ''; ?> />
116
+ <label for="serve-from-s3"> <?php _e( 'Point file URLs to S3/CloudFront for files that have been copied to S3', 'as3cf' ); ?>
117
+ <span class="tooltip" data-tooltip="<?php _e( 'When this is option unchecked, your Media Library images and other files will be served from your server instead of S3/CloudFront. If any images were removed from your server, they will be broken.', 'as3cf' ); ?>">
118
+ <span class="dashicons dashicons-editor-help"></span>
119
+ </span>
120
+ </label>
121
  <br />
122
 
123
  <input type="checkbox" name="remove-local-file" value="1" id="remove-local-file" <?php echo $this->get_setting( 'remove-local-file' ) ? 'checked="checked" ' : ''; ?> />
128
  <label for="force-ssl"> <?php _e( 'Always serve files over https (SSL)', 'as3cf' ); ?></label>
129
  <br />
130
 
131
+ <input type="checkbox" name="object-versioning" value="1" id="object-versioning" <?php echo $this->get_setting( 'object-versioning' ) ? 'checked="checked" ' : ''; ?> />
132
+ <label for="object-versioning"> <?php printf( __( 'Implement <a href="%s">object versioning</a> by appending a timestamp to the S3 file path', 'as3cf' ), 'http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/ReplacingObjects.html' ); ?></label>
133
+ <br />
134
+
135
  <input type="checkbox" name="hidpi-images" value="1" id="hidpi-images" <?php echo $this->get_setting( 'hidpi-images' ) ? 'checked="checked" ' : ''; ?> />
136
  <label for="hidpi-images"> <?php _e( 'Copy any HiDPI (@2x) images to S3 (works with WP Retina 2x plugin)', 'as3cf' ); ?></label>
137
 
146
 
147
  </form>
148
 
149
+ <?php $this->render_view( 'sidebar' ); ?>
150
+
151
  </div>
view/sidebar.php ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div class="as3cf-sidebar">
2
+
3
+ <div class="as3cf-banner"><img src="<?php echo plugins_url( 'assets/img/snail.jpg', $this->plugin_file_path ); ?>" width="292" height="165" alt="" /></div>
4
+
5
+ <form method="post" action="https://deliciousbrains.createsend.com/t/t/s/dlihik/" target="_blank" class="subscribe block">
6
+ <h2><?php _e( 'Pro Version?', 'as3cf' ); ?></h2>
7
+
8
+ <?php $user = wp_get_current_user(); ?>
9
+
10
+ <p class="intro">
11
+ <?php echo wptexturize( __( "We're working on a pro version that will include the following features:", 'as3cf' ) ); ?>
12
+ </p>
13
+
14
+ <ul>
15
+ <li><?php echo wptexturize( __( 'Copy existing Media Library to S3', 'as3cf' ) ); ?></li>
16
+ <li><?php echo wptexturize( __( 'Serve theme JS & CSS from S3/CloudFront', 'as3cf' ) ); ?></li>
17
+ <li><?php echo wptexturize( __( 'WooCommerce & <abbr title="Easy Digital Downloads">EDD</abbr> integration', 'as3cf' ) ); ?></li>
18
+ <li><?php echo wptexturize( __( 'Awesome email support', 'as3cf' ) ); ?></li>
19
+ </ul>
20
+
21
+ <div class="field notify-name">
22
+ <input type="text" name="cm-name" value="<?php echo trim( esc_attr( $user->first_name ) . ' ' . esc_attr( $user->last_name ) ); ?>" placeholder="<?php _e( 'Your Name', 'as3cf' ); ?>"/>
23
+ </div>
24
+
25
+ <div class="field notify-email">
26
+ <input type="email" name="cm-dlihik-dlihik" value="<?php echo esc_attr( $user->user_email ); ?>" placeholder="<?php _e( 'Your Email', 'as3cf' ); ?>"/>
27
+ </div>
28
+
29
+ <div class="field submit-button">
30
+ <input type="submit" class="button" value="<?php _e( 'Send me news about a pro version', 'as3cf' ); ?>"/>
31
+ </div>
32
+
33
+ <p class="promise">
34
+ <?php _e( 'We promise we will not use your email for anything else and you can unsubscribe with 1-click anytime.', 'as3cf' ); ?>
35
+ </p>
36
+ </form>
37
+
38
+ <div class="block credits">
39
+ <h4>Created &amp; maintained by</h4>
40
+ <ul>
41
+ <li>
42
+ <a href="http://profiles.wordpress.org/bradt/">
43
+ <img src="//www.gravatar.com/avatar/e538ca4cb34839d4e5e3ccf20c37c67b?size=64" alt="" width="32" height="32">
44
+ <span>Brad Touesnard</span>
45
+ </a>
46
+ </li>
47
+ <li>
48
+ <a href="https://deliciousbrains.com/?utm_source=insideplugin&utm_medium=web&utm_content=sidebar&utm_campaign=as3cf">
49
+ <img src="//www.gravatar.com/avatar/e62fc2e9c8d9fc6edd4fea5339036a91?size=64" alt="" width="32" height="32">
50
+ <span>Delicious Brains Inc.</span>
51
+ </a>
52
+ </li>
53
+ </ul>
54
+ </div>
55
+ </div>
wordpress-s3.php CHANGED
@@ -4,8 +4,11 @@ Plugin Name: Amazon S3 and CloudFront
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: Brad Touesnard
7
- Version: 0.6.1
8
  Author URI: http://bradt.ca
 
 
 
9
 
10
  // Copyright (c) 2013 Brad Touesnard. All rights reserved.
11
  //
@@ -23,36 +26,30 @@ Author URI: http://bradt.ca
23
  // Then completely rewritten.
24
  */
25
 
26
- function as3cf_check_required_plugin() {
27
- if ( class_exists( 'Amazon_Web_Services' ) || !is_admin() || ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) {
28
- return;
29
- }
30
 
31
- require_once ABSPATH . '/wp-admin/includes/plugin.php';
32
- deactivate_plugins( __FILE__ );
 
33
 
34
- $msg = sprintf( __( 'Amazon S3 and CloudFront has been deactivated as it requires the <a href="%s">Amazon&nbsp;Web&nbsp;Services</a> plugin.', 'as3cf' ), 'https://github.com/deliciousbrains/wp-amazon-web-services' ) . '<br /><br />';
35
-
36
- if ( file_exists( WP_PLUGIN_DIR . '/amazon-web-services/amazon-web-services.php' ) ) {
37
- $activate_url = wp_nonce_url( 'plugins.php?action=activate&amp;plugin=amazon-web-services/amazon-web-services.php', 'activate-plugin_amazon-web-services/amazon-web-services.php' );
38
- $msg .= sprintf( __( 'It appears to already be installed. <a href="%s">Click here to activate it.</a>', 'as3cf' ), $activate_url );
39
- }
40
- else {
41
- $download_url = 'https://github.com/deliciousbrains/wp-amazon-web-services/releases/download/v0.1/amazon-web-services-0.1.zip';
42
- $msg .= sprintf( __( '<a href="%s">Click here to download a zip of the latest version.</a> Then install and activate it. ', 'as3cf' ), $download_url );
43
- }
44
 
45
- $msg .= '<br /><br />' . __( 'Once it has been activated, you can activate Amazon&nbsp;S3&nbsp;and&nbsp;CloudFront.', 'as3cf' );
46
-
47
- wp_die( $msg );
48
- }
49
-
50
- add_action( 'plugins_loaded', 'as3cf_check_required_plugin' );
51
 
52
  function as3cf_init( $aws ) {
53
- global $as3cf;
54
- require_once 'classes/amazon-s3-and-cloudfront.php';
55
- $as3cf = new Amazon_S3_And_CloudFront( __FILE__, $aws );
 
 
 
 
 
 
 
 
56
  }
57
 
58
- add_action( 'aws_init', 'as3cf_init' );
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: Brad Touesnard
7
+ Version: 0.7
8
  Author URI: http://bradt.ca
9
+ Network: True
10
+ Text Domain: as3cf
11
+ Domain Path: /languages/
12
 
13
  // Copyright (c) 2013 Brad Touesnard. All rights reserved.
14
  //
26
  // Then completely rewritten.
27
  */
28
 
29
+ $GLOBALS['aws_meta']['amazon-s3-and-cloudfront']['version'] = '0.7';
 
 
 
30
 
31
+ $GLOBALS['aws_meta']['amazon-s3-and-cloudfront']['supported_addon_versions'] = array(
32
+ 'amazon-s3-and-cloudfront-edd' => '1.0',
33
+ );
34
 
35
+ $aws_plugin_version_required = '0.2';
 
 
 
 
 
 
 
 
 
36
 
37
+ require dirname( __FILE__ ) . '/classes/as3cf-compatibility-check.php';
38
+ global $as3cf_compat_check;
39
+ $as3cf_compat_check = new AS3CF_Compatibility_Check( __FILE__, $aws_plugin_version_required );
 
 
 
40
 
41
  function as3cf_init( $aws ) {
42
+ global $as3cf_compat_check;
43
+ if ( ! $as3cf_compat_check->is_compatible() ) {
44
+ return;
45
+ }
46
+
47
+ global $as3cf;
48
+ $abspath = dirname( __FILE__ );
49
+ require_once $abspath . '/include/functions.php';
50
+ require_once $abspath . '/classes/as3cf-upgrade.php';
51
+ require_once $abspath . '/classes/amazon-s3-and-cloudfront.php';
52
+ $as3cf = new Amazon_S3_And_CloudFront( __FILE__, $aws );
53
  }
54
 
55
+ add_action( 'aws_init', 'as3cf_init' );