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 @@