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