PluginProbe
UpdraftPlus: WP Backup & Migration Plugin / 1.12.4
UpdraftPlus: WP Backup & Migration Plugin v1.12.4
1.26.7 1.26.6 1.26.5 1.26.4 1.26.3 1.9.19 1.9.25 1.9.26 1.9.30 1.9.31 1.9.32 1.9.4 1.9.40 1.9.41 1.9.42 1.9.43 1.9.44 1.9.45 1.9.46 1.9.5 1.9.50 1.9.51 1.9.60 1.9.62 1.9.63 All 371 releases
updraftplus / methods / s3.php

s3.php in UpdraftPlus: WP Backup & Migration Plugin 1.12.4, at methods/s3.php

904 lines 37.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('UPDRAFTPLUS_DIR')) die('No direct access allowed.');
4
5 # Migrate options to new-style storage - Jan 2014
6 if (!is_array(UpdraftPlus_Options::get_updraft_option('updraft_s3')) && '' != UpdraftPlus_Options::get_updraft_option('updraft_s3_login', '')) {
7 $opts = array(
8 'accesskey' => UpdraftPlus_Options::get_updraft_option('updraft_s3_login'),
9 'secretkey' => UpdraftPlus_Options::get_updraft_option('updraft_s3_pass'),
10 'path' => UpdraftPlus_Options::get_updraft_option('updraft_s3_remote_path')
11 );
12 UpdraftPlus_Options::update_updraft_option('updraft_s3', $opts);
13 UpdraftPlus_Options::delete_updraft_option('updraft_s3_login');
14 UpdraftPlus_Options::delete_updraft_option('updraft_s3_pass');
15 UpdraftPlus_Options::delete_updraft_option('updraft_s3_remote_path');
16 }
17
18 // This class is used by both UpdraftPlus_S3 and UpdraftPlus_S3_Compat
19 class UpdraftPlus_S3Exception extends Exception {
20 public function __construct($message, $file, $line, $code = 0)
21 {
22 parent::__construct($message, $code);
23 $this->file = $file;
24 $this->line = $line;
25 }
26 }
27
28 class UpdraftPlus_BackupModule_s3 {
29
30 private $s3_object;
31 private $got_with;
32 protected $quota_used = null;
33
34 protected function get_config() {
35 global $updraftplus;
36 $opts = $updraftplus->get_job_option('updraft_s3');
37 if (!is_array($opts)) $opts = array('accesskey' => '', 'secretkey' => '', 'path' => '');
38 $opts['whoweare'] = 'S3';
39 $opts['whoweare_long'] = 'Amazon S3';
40 $opts['key'] = 's3';
41 return $opts;
42 }
43
44 public function get_credentials() {
45 return array('updraft_s3');
46 }
47
48 protected function indicate_s3_class() {
49 // N.B. : The classes must have different names, as if multiple remote storage options are chosen, then we could theoretically need both (if both Amazon and a compatible-S3 provider are used)
50 // Conditional logic, for new AWS SDK (N.B. 3.x branch requires PHP 5.5, so we're on 2.x - requires 5.3.3)
51
52 $opts = $this->get_config();
53 $class_to_use = 'UpdraftPlus_S3';
54 if (version_compare(PHP_VERSION, '5.3.3', '>=') && !empty($opts['key']) && ('s3' == $opts['key'] || 'updraftvault' == $opts['key']) && (!defined('UPDRAFTPLUS_S3_OLDLIB') || !UPDRAFTPLUS_S3_OLDLIB)) {
55 $class_to_use = 'UpdraftPlus_S3_Compat';
56 }
57
58 if ('UpdraftPlus_S3_Compat' == $class_to_use) {
59 if (!class_exists($class_to_use)) require_once(UPDRAFTPLUS_DIR.'/includes/S3compat.php');
60 } else {
61 if (!class_exists($class_to_use)) require_once(UPDRAFTPLUS_DIR.'/includes/S3.php');
62 }
63 return $class_to_use;
64 }
65
66 // Get an S3 object, after setting our options
67 public function getS3($key, $secret, $useservercerts, $disableverify, $nossl, $endpoint = null, $sse = false) {
68
69 if (!empty($this->s3_object) && !is_wp_error($this->s3_object)) return $this->s3_object;
70
71 if (is_string($key)) $key = trim($key);
72 if (is_string($secret)) $secret = trim($secret);
73
74 // Saved in case the object needs recreating for the corner-case where there is no permission to look up the bucket location
75 $this->got_with = array(
76 'key' => $key,
77 'secret' => $secret,
78 'useservercerts' => $useservercerts,
79 'disableverify' => $disableverify,
80 'nossl' => $nossl,
81 'server_side_encryption' => $sse
82 );
83
84 if (is_wp_error($key)) return $key;
85
86 if ('' == $key || '' == $secret) {
87 return new WP_Error('no_settings', __('No settings were found','updraftplus'));
88 }
89
90 global $updraftplus;
91
92 $use_s3_class = $this->indicate_s3_class();
93
94 if (!class_exists('WP_HTTP_Proxy')) require_once(ABSPATH.WPINC.'/class-http.php');
95 $proxy = new WP_HTTP_Proxy();
96
97 $use_ssl = true;
98 $ssl_ca = true;
99 if (!$nossl) {
100 $curl_version = (function_exists('curl_version')) ? curl_version() : array('features' => null);
101 $curl_ssl_supported = ($curl_version['features'] & CURL_VERSION_SSL);
102 if ($curl_ssl_supported) {
103 if ($disableverify) {
104 $ssl_ca = false;
105 //$s3->setSSL(true, false);
106 $updraftplus->log("S3: Disabling verification of SSL certificates");
107 } else {
108 if ($useservercerts) {
109 $updraftplus->log("S3: Using the server's SSL certificates");
110 $ssl_ca = 'system';
111 } else {
112 $ssl_ca = file_exists(UPDRAFTPLUS_DIR.'/includes/cacert.pem') ? UPDRAFTPLUS_DIR.'/includes/cacert.pem' : true;
113 }
114 }
115 } else {
116 $use_ssl = false;
117 $updraftplus->log("S3: Curl/SSL is not available. Communications will not be encrypted.");
118 }
119 } else {
120 $use_ssl = false;
121 $updraftplus->log("SSL was disabled via the user's preference. Communications will not be encrypted.");
122 }
123
124 try {
125 $s3 = new $use_s3_class($key, $secret, $use_ssl, $ssl_ca, $endpoint);
126 } catch (Exception $e) {
127 $updraftplus->log(sprintf(__('%s Error: Failed to initialise','updraftplus'), 'S3').": ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
128 $updraftplus->log(sprintf(__('%s Error: Failed to initialise','updraftplus'), $key), 'S3');
129 return new WP_Error('s3_init_failed', sprintf(__('%s Error: Failed to initialise','updraftplus'), 'S3').": ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
130 }
131
132 if ($proxy->is_enabled()) {
133 # WP_HTTP_Proxy returns empty strings where we want nulls
134 $user = $proxy->username();
135 if (empty($user)) {
136 $user = null;
137 $pass = null;
138 } else {
139 $pass = $proxy->password();
140 if (empty($pass)) $pass = null;
141 }
142 $port = (int)$proxy->port();
143 if (empty($port)) $port = 8080;
144 $s3->setProxy($proxy->host(), $user, $pass, CURLPROXY_HTTP, $port);
145 }
146
147 // Old: from before we passed the SSL options when getting the object
148 // if (!$nossl) {
149 // $curl_version = (function_exists('curl_version')) ? curl_version() : array('features' => null);
150 // $curl_ssl_supported = ($curl_version['features'] & CURL_VERSION_SSL);
151 // if ($curl_ssl_supported) {
152 // if ($disableverify) {
153 // $s3->setSSL(true, false);
154 // $updraftplus->log("S3: Disabling verification of SSL certificates");
155 // } else {
156 // $s3->setSSL(true, true);
157 // }
158 // if ($useservercerts) {
159 // $updraftplus->log("S3: Using the server's SSL certificates");
160 // } else {
161 // $s3->setSSLAuth(null, null, UPDRAFTPLUS_DIR.'/includes/cacert.pem');
162 // }
163 // } else {
164 // $s3->setSSL(false, false);
165 // $updraftplus->log("S3: Curl/SSL is not available. Communications will not be encrypted.");
166 // }
167 // } else {
168 // $s3->setSSL(false, false);
169 // $updraftplus->log("SSL was disabled via the user's preference. Communications will not be encrypted.");
170 // }
171
172 if (method_exists($s3, 'setServerSideEncryption') && (is_a($this, 'UpdraftPlus_BackupModule_updraftvault') || $sse)) $s3->setServerSideEncryption('AES256');
173
174 $this->s3_object = $s3;
175
176 return $this->s3_object;
177 }
178
179 protected function set_region($obj, $region, $bucket_name = '') {
180 global $updraftplus;
181 switch ($region) {
182 case 'EU':
183 case 'eu-west-1':
184 $endpoint = 's3-eu-west-1.amazonaws.com';
185 break;
186 case 'us-west-1':
187 $endpoint = 's3.amazonaws.com';
188 break;
189 case 'us-west-2':
190 case 'ap-southeast-1':
191 case 'ap-southeast-2':
192 case 'ap-northeast-1':
193 case 'ap-northeast-2':
194 case 'sa-east-1':
195 case 'us-gov-west-1':
196 case 'eu-central-1':
197 $endpoint = 's3-'.$region.'.amazonaws.com';
198 break;
199 case 'cn-north-1':
200 $endpoint = 's3.'.$region.'.amazonaws.com.cn';
201 break;
202 default:
203 break;
204 }
205
206 if (isset($endpoint)) {
207 if (is_a($obj, 'UpdraftPlus_S3_Compat')) {
208 $updraftplus->log("Set region: $region");
209 $obj->setRegion($region);
210 return;
211 }
212
213 $updraftplus->log("Set endpoint: $endpoint");
214
215 if ($region == 'us-west-1') {
216 $obj->useDNSBucketName(true, $bucket_name);
217 return;
218 }
219
220 return $obj->setEndpoint($endpoint);
221 }
222 }
223
224 public function backup($backup_array) {
225
226 global $updraftplus, $updraftplus_backup;
227
228 $config = $this->get_config();
229
230 if (empty($config['accesskey']) && !empty($config['error_message'])) {
231 $err = new WP_Error('no_settings', $config['error_message']);
232 return $updraftplus->log_wp_error($err, false, true);
233 }
234
235 $whoweare = $config['whoweare'];
236 $whoweare_key = $config['key'];
237 $whoweare_keys = substr($whoweare_key, 0, 3);
238 $sse = (empty($config['server_side_encryption'])) ? false : true;
239
240 $s3 = $this->getS3(
241 $config['accesskey'],
242 $config['secretkey'],
243 UpdraftPlus_Options::get_updraft_option('updraft_ssl_useservercerts'), UpdraftPlus_Options::get_updraft_option('updraft_ssl_disableverify'),
244 UpdraftPlus_Options::get_updraft_option('updraft_ssl_nossl'),
245 null,
246 $sse
247 );
248
249 if (is_wp_error($s3)) return $updraftplus->log_wp_error($s3, false, true);
250
251 if (is_a($s3, 'UpdraftPlus_S3_Compat') && !class_exists('XMLWriter')) {
252 $updraftplus->log('The required XMLWriter PHP module is not installed');
253 $updraftplus->log(sprintf(__('The required %s PHP module is not installed - ask your web hosting company to enable it', 'updraftplus'), 'XMLWriter'), 'error');
254 return false;
255 }
256
257 $bucket_name = untrailingslashit($config['path']);
258 $bucket_path = "";
259 $orig_bucket_name = $bucket_name;
260
261 if (preg_match("#^([^/]+)/(.*)$#",$bucket_name,$bmatches)) {
262 $bucket_name = $bmatches[1];
263 $bucket_path = $bmatches[2]."/";
264 }
265
266 // This needs to cope with both original S3 and others (where there is no getBucketLocation())
267 $region = ($config['key'] == 's3' || $config['key'] == 'updraftvault') ? @$s3->getBucketLocation($bucket_name) : 'n/a';
268
269 // See if we can detect the region (which implies the bucket exists and is ours), or if not create it
270 if (!empty($region) || @$s3->putBucket($bucket_name, 'private') || ('s3' == $config['key'] && false !== ($s3 = $this->use_dns_bucket_name($s3, $bucket_name)) && false !== @$s3->getBucket($bucket_name, $bucket_path, null, 1))) {
271 if (empty($region) && ($config['key'] == 's3' || $config['key'] == 'updraftvault')) $region = $s3->getBucketLocation($bucket_name);
272 if (!empty($region)) $this->set_region($s3, $region, $bucket_name);
273
274 $updraft_dir = trailingslashit($updraftplus->backups_dir_location());
275
276 foreach ($backup_array as $key => $file) {
277
278 // We upload in 5MB chunks to allow more efficient resuming and hence uploading of larger files
279 // N.B.: 5MB is Amazon's minimum. So don't go lower or you'll break it.
280 $fullpath = $updraft_dir.$file;
281 $orig_file_size = filesize($fullpath);
282
283 if (isset($config['quota']) && method_exists($this, 's3_get_quota_info')) {
284 $quota_used = $this->s3_get_quota_info('numeric', $config['quota']);
285 if (false === $quota_used) {
286 $updraftplus->log("Quota usage: count failed");
287 } else {
288 $this->quota_used = $quota_used;
289 if ($config['quota'] - $this->quota_used < $orig_file_size) {
290 if (method_exists($this, 's3_out_of_quota')) call_user_func(array($this, 's3_out_of_quota'), $config['quota'], $this->quota_used, $orig_file_size);
291 continue;
292 } else {
293 // We don't need to log this always - the s3_out_of_quota method will do its own logging
294 $updraftplus->log("$whoweare: Quota is available: used=$quota_used (".round($quota_used/1048576, 1)." MB), total=".$config['quota']." (".round($config['quota']/1048576, 1)." MB), needed=$orig_file_size (".round($orig_file_size/1048576, 1)." MB)");
295 }
296 }
297 }
298
299 $chunks = floor($orig_file_size / 5242880);
300 // There will be a remnant unless the file size was exactly on a 5MB boundary
301 if ($orig_file_size % 5242880 > 0) $chunks++;
302 $hash = md5($file);
303
304 $updraftplus->log("$whoweare upload ($region): $file (chunks: $chunks) -> $whoweare_key://$bucket_name/$bucket_path$file");
305
306 $filepath = $bucket_path.$file;
307
308 // This is extra code for the 1-chunk case, but less overhead (no bothering with job data)
309 if ($chunks < 2) {
310 $s3->setExceptions(true);
311 try {
312 if (!$s3->putObjectFile($fullpath, $bucket_name, $filepath, 'private', array(), array(), apply_filters('updraft_'.$whoweare_key.'_storageclass', 'STANDARD', $s3, $config))) {
313 $updraftplus->log("$whoweare regular upload: failed ($fullpath)");
314 $updraftplus->log("$file: ".sprintf(__('%s Error: Failed to upload','updraftplus'),$whoweare), 'error');
315 } else {
316 $this->quota_used += $orig_file_size;
317 if (method_exists($this, 's3_record_quota_info')) $this->s3_record_quota_info($this->quota_used, $config['quota']);
318 $extra_log = '';
319 if (method_exists($this, 's3_get_quota_info')) {
320 $extra_log = ', quota used now: '.round($this->quota_used / 1048576, 1).' MB';
321 }
322 $updraftplus->log("$whoweare regular upload: success$extra_log");
323 $updraftplus->uploaded_file($file);
324 }
325 } catch (Exception $e) {
326 $updraftplus->log("$file: ".sprintf(__('%s Error: Failed to upload','updraftplus'),$whoweare).": ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile());
327 $updraftplus->log("$file: ".sprintf(__('%s Error: Failed to upload','updraftplus'),$whoweare), 'error');
328 }
329 $s3->setExceptions(false);
330 } else {
331
332 // Retrieve the upload ID
333 $uploadId = $updraftplus->jobdata_get("upd_${whoweare_keys}_${hash}_uid");
334 if (empty($uploadId)) {
335 $s3->setExceptions(true);
336 try {
337 $uploadId = $s3->initiateMultipartUpload($bucket_name, $filepath, 'private', array(), array(), apply_filters('updraft_'.$whoweare_key.'_storageclass', 'STANDARD', $s3, $config));
338 } catch (Exception $e) {
339 $updraftplus->log("$whoweare error whilst trying initiateMultipartUpload: ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
340 $uploadId = false;
341 }
342 $s3->setExceptions(false);
343
344 if (empty($uploadId)) {
345 $updraftplus->log("$whoweare upload: failed: could not get uploadId for multipart upload ($filepath)");
346 $updraftplus->log(sprintf(__("%s upload: getting uploadID for multipart upload failed - see log file for more details",'updraftplus'),$whoweare), 'error');
347 continue;
348 } else {
349 $updraftplus->log("$whoweare chunked upload: got multipart ID: $uploadId");
350 $updraftplus->jobdata_set("upd_${whoweare_keys}_${hash}_uid", $uploadId);
351 }
352 } else {
353 $updraftplus->log("$whoweare chunked upload: retrieved previously obtained multipart ID: $uploadId");
354 }
355
356 $successes = 0;
357 $etags = array();
358 for ($i = 1 ; $i <= $chunks; $i++) {
359 # Shorted to upd here to avoid hitting the 45-character limit
360 $etag = $updraftplus->jobdata_get("ud_${whoweare_keys}_${hash}_e$i");
361 if (strlen($etag) > 0) {
362 $updraftplus->log("$whoweare chunk $i: was already completed (etag: $etag)");
363 $successes++;
364 array_push($etags, $etag);
365 } else {
366 // Sanity check: we've seen a case where an overlap was truncating the file from underneath us
367 if (filesize($fullpath) < $orig_file_size) {
368 $updraftplus->log("$whoweare error: $key: chunk $i: file was truncated underneath us (orig_size=$orig_file_size, now_size=".filesize($fullpath).")");
369 $updraftplus->log(sprintf(__('%s error: file %s was shortened unexpectedly', 'updraftplus'), $whoweare, $fullpath), 'error');
370 }
371 $etag = $s3->uploadPart($bucket_name, $filepath, $uploadId, $fullpath, $i);
372 if ($etag !== false && is_string($etag)) {
373 $updraftplus->record_uploaded_chunk(round(100*$i/$chunks,1), "$i, $etag", $fullpath);
374 array_push($etags, $etag);
375 $updraftplus->jobdata_set("ud_${whoweare_keys}_${hash}_e$i", $etag);
376 $successes++;
377 } else {
378 $updraftplus->log("$whoweare chunk $i: upload failed");
379 $updraftplus->log(sprintf(__("%s chunk %s: upload failed",'updraftplus'),$whoweare, $i), 'error');
380 }
381 }
382 }
383 if ($successes >= $chunks) {
384 $updraftplus->log("$whoweare upload: all chunks uploaded; will now instruct $whoweare to re-assemble");
385
386 $s3->setExceptions(true);
387 try {
388 if ($s3->completeMultipartUpload($bucket_name, $filepath, $uploadId, $etags)) {
389 $updraftplus->log("$whoweare upload ($key): re-assembly succeeded");
390 $updraftplus->uploaded_file($file);
391 $this->quota_used += $orig_file_size;
392 if (method_exists($this, 's3_record_quota_info')) $this->s3_record_quota_info($this->quota_used, $config['quota']);
393 } else {
394 $updraftplus->log("$whoweare upload ($key): re-assembly failed ($file)");
395 $updraftplus->log(sprintf(__('%s upload (%s): re-assembly failed (see log for more details)','updraftplus'),$whoweare, $key), 'error');
396 }
397 } catch (Exception $e) {
398 $updraftplus->log("$whoweare re-assembly error ($key): ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
399 $updraftplus->log($e->getMessage().": ".sprintf(__('%s re-assembly error (%s): (see log file for more)','updraftplus'),$whoweare, $e->getMessage()), 'error');
400 }
401 // Remember to unset, as the deletion code later reuses the object
402 $s3->setExceptions(false);
403 } else {
404 $updraftplus->log("$whoweare upload: upload was not completely successful on this run");
405 }
406 }
407 }
408 return array('s3_object' => $s3, 's3_orig_bucket_name' => $orig_bucket_name);
409 } else {
410 $updraftplus->log("$whoweare Error: Failed to create bucket $bucket_name.");
411 $updraftplus->log(sprintf(__('%s Error: Failed to create bucket %s. Check your permissions and credentials.','updraftplus'),$whoweare, $bucket_name), 'error');
412 }
413 }
414
415 public function listfiles($match = 'backup_') {
416
417 global $updraftplus;
418
419 $config = $this->get_config();
420 $whoweare = $config['whoweare'];
421 $whoweare_key = $config['key'];
422 $sse = empty($config['server_side_encryption']) ? false : true;
423
424 $s3 = $this->getS3(
425 $config['accesskey'],
426 $config['secretkey'],
427 UpdraftPlus_Options::get_updraft_option('updraft_ssl_useservercerts'), UpdraftPlus_Options::get_updraft_option('updraft_ssl_disableverify'),
428 UpdraftPlus_Options::get_updraft_option('updraft_ssl_nossl'),
429 null,
430 $sse
431 );
432
433 if (is_wp_error($s3)) return $s3;
434 if (!is_a($s3, 'UpdraftPlus_S3') && !is_a($s3, 'UpdraftPlus_S3_Compat')) return new WP_Error('no_s3object', 'Failed to gain access to '.$config['whoweare']);
435
436 $bucket_name = untrailingslashit($config['path']);
437 $bucket_path = '';
438
439 if (preg_match("#^([^/]+)/(.*)$#", $bucket_name, $bmatches)) {
440 $bucket_name = $bmatches[1];
441 $bucket_path = trailingslashit($bmatches[2]);
442 }
443
444 if (!empty($config['is_new_bucket'])) {
445 if (method_exists($s3, 'waitForBucket')) {
446 $s3->setExceptions(true);
447 try {
448 $s3->waitForBucket($bucket_name);
449 } catch (Exception $e) {
450 // This seems to often happen - we get a 403 on a newly created user/bucket pair, even though the bucket was already waited for by the creator
451 // We could just sleep() - a sleep(5) seems to do it. However, given that it's a new bucket, that's unnecessary.
452 $s3->setExceptions(false);
453 return array();
454 }
455 $s3->setExceptions(false);
456 } else {
457 sleep(4);
458 }
459 } elseif (!empty($config['is_new_user'])) {
460 // A crude waiter, because the AWS toolkit does not have one for IAM propagation - basically, loop around a few times whilst the access attempt still fails
461 $attempt_flag = 0;
462 while ($attempt_flag < 5) {
463
464 $attempt_flag++;
465 if (@$s3->getBucketLocation($bucket_name)) {
466 $attempt_flag = 100;
467 } else {
468
469 sleep($attempt_flag*1.5 + 1);
470
471 // Get the bucket object again... because, for some reason, the AWS PHP SDK (at least on the current version we're using, March 2016) calculates an incorrect signature on subsequent attempts
472 $this->s3_object = null;
473 $s3 = $this->getS3(
474 $config['accesskey'],
475 $config['secretkey'],
476 UpdraftPlus_Options::get_updraft_option('updraft_ssl_useservercerts'), UpdraftPlus_Options::get_updraft_option('updraft_ssl_disableverify'),
477 UpdraftPlus_Options::get_updraft_option('updraft_ssl_nossl'),
478 null,
479 $sse
480 );
481
482 if (is_wp_error($s3)) return $s3;
483 if (!is_a($s3, 'UpdraftPlus_S3') && !is_a($s3, 'UpdraftPlus_S3_Compat')) return new WP_Error('no_s3object', 'Failed to gain access to '.$config['whoweare']);
484
485 }
486 }
487 }
488
489 $region = ($config['key'] == 'dreamobjects' || $config['key'] == 's3generic') ? 'n/a' : @$s3->getBucketLocation($bucket_name);
490 if (!empty($region)) {
491 $this->set_region($s3, $region, $bucket_name);
492 } else {
493 # Final thing to attempt - see if it was just the location request that failed
494 $s3 = $this->use_dns_bucket_name($s3, $bucket_name);
495 if (false === ($gb = @$s3->getBucket($bucket_name, $bucket_path, null, 1))) {
496 $updraftplus->log("$whoweare Error: Failed to access bucket $bucket_name. Check your permissions and credentials.");
497 return new WP_Error('bucket_not_accessed', sprintf(__('%s Error: Failed to access bucket %s. Check your permissions and credentials.','updraftplus'),$whoweare, $bucket_name));
498 }
499 }
500
501 $bucket = $s3->getBucket($bucket_name, $bucket_path.$match);
502
503 if (!is_array($bucket)) return array();
504
505 $results = array();
506
507 foreach ($bucket as $key => $object) {
508 if (!is_array($object) || empty($object['name'])) continue;
509 if (isset($object['size']) && 0 == $object['size']) continue;
510
511 if ($bucket_path) {
512 if (0 !== strpos($object['name'], $bucket_path)) continue;
513 $object['name'] = substr($object['name'], strlen($bucket_path));
514 } else {
515 if (false !== strpos($object['name'], '/')) continue;
516 }
517
518 $result = array('name' => $object['name']);
519 if (isset($object['size'])) $result['size'] = $object['size'];
520 unset($bucket[$key]);
521 $results[] = $result;
522 }
523
524 return $results;
525
526 }
527
528 public function delete($files, $s3arr = false, $sizeinfo = array()) {
529
530 global $updraftplus;
531 if (is_string($files)) $files=array($files);
532
533 $config = $this->get_config();
534 $sse = (empty($config['server_side_encryption'])) ? false : true;
535 $whoweare = $config['whoweare'];
536
537 if ($s3arr) {
538 $s3 = $s3arr['s3_object'];
539 $orig_bucket_name = $s3arr['s3_orig_bucket_name'];
540 } else {
541
542 $s3 = $this->getS3(
543 $config['accesskey'],
544 $config['secretkey'],
545 UpdraftPlus_Options::get_updraft_option('updraft_ssl_useservercerts'), UpdraftPlus_Options::get_updraft_option('updraft_ssl_disableverify'),
546 UpdraftPlus_Options::get_updraft_option('updraft_ssl_nossl'),
547 null,
548 $sse
549 );
550
551 if (is_wp_error($s3)) return $updraftplus->log_wp_error($s3, false, false);
552
553 $bucket_name = untrailingslashit($config['path']);
554 $orig_bucket_name = $bucket_name;
555
556 if (preg_match("#^([^/]+)/(.*)$#",$bucket_name,$bmatches)) {
557 $bucket_name = $bmatches[1];
558 $bucket_path = $bmatches[2]."/";
559 } else {
560 $bucket_path = '';
561 }
562
563 $region = ($config['key'] == 'dreamobjects' || $config['key'] == 's3generic') ? 'n/a' : @$s3->getBucketLocation($bucket_name);
564 if (!empty($region)) {
565 $this->set_region($s3, $region, $bucket_name);
566 } else {
567 # Final thing to attempt - see if it was just the location request that failed
568 $s3 = $this->use_dns_bucket_name($s3, $bucket_name);
569 if (false === ($gb = @$s3->getBucket($bucket_name, $bucket_path, null, 1))) {
570 $updraftplus->log("$whoweare Error: Failed to access bucket $bucket_name. Check your permissions and credentials.");
571 $updraftplus->log(sprintf(__('%s Error: Failed to access bucket %s. Check your permissions and credentials.','updraftplus'),$whoweare, $bucket_name), 'error');
572 return false;
573 }
574 }
575 }
576
577 $ret = true;
578
579 foreach ($files as $i => $file) {
580
581 if (preg_match("#^([^/]+)/(.*)$#", $orig_bucket_name, $bmatches)) {
582 $s3_bucket=$bmatches[1];
583 $s3_uri = $bmatches[2]."/".$file;
584 } else {
585 $s3_bucket = $orig_bucket_name;
586 $s3_uri = $file;
587 }
588 $updraftplus->log("$whoweare: Delete remote: bucket=$s3_bucket, URI=$s3_uri");
589
590 $s3->setExceptions(true);
591 try {
592 if (!$s3->deleteObject($s3_bucket, $s3_uri)) {
593 $updraftplus->log("$whoweare: Delete failed");
594 } elseif (null !== $this->quota_used && !empty($sizeinfo[$i]) && isset($config['quota']) && method_exists($this, 's3_record_quota_info')) {
595 $this->quota_used -= $sizeinfo[$i];
596 $this->s3_record_quota_info($this->quota_used, $config['quota']);
597 }
598 } catch (Exception $e) {
599 $updraftplus->log("$whoweare delete failed: ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
600 $s3->setExceptions(false);
601 $ret = false;
602 }
603 $s3->setExceptions(false);
604
605 }
606
607 return $ret;
608
609 }
610
611 public function download($file) {
612
613 global $updraftplus;
614
615 $config = $this->get_config();
616 $whoweare = $config['whoweare'];
617 $sse = (empty($config['server_side_encryption'])) ? false : true;
618
619 $s3 = $this->getS3(
620 $config['accesskey'],
621 $config['secretkey'],
622 UpdraftPlus_Options::get_updraft_option('updraft_ssl_useservercerts'), UpdraftPlus_Options::get_updraft_option('updraft_ssl_disableverify'),
623 UpdraftPlus_Options::get_updraft_option('updraft_ssl_nossl'),
624 null,
625 $sse
626 );
627 if (is_wp_error($s3)) return $updraftplus->log_wp_error($s3, false, true);
628
629 $bucket_name = untrailingslashit($config['path']);
630 $bucket_path = "";
631
632 if (preg_match("#^([^/]+)/(.*)$#", $bucket_name, $bmatches)) {
633 $bucket_name = $bmatches[1];
634 $bucket_path = $bmatches[2]."/";
635 }
636
637 $region = ($config['key'] == 'dreamobjects' || $config['key'] == 's3generic') ? 'n/a' : @$s3->getBucketLocation($bucket_name);
638
639 if (empty($region) && 's3' == $config['key']) {
640 # Final thing to attempt - see if it was just the location request that failed
641 $s3 = $this->use_dns_bucket_name($s3, $bucket_name);
642 if (false !== ($gb = @$s3->getBucket($bucket_name, $bucket_path, null, 1))) {
643 $keep_going = true;
644 }
645 }
646
647 if (!empty($region) || !empty($keep_going)) {
648 $this->set_region($s3, $region, $bucket_name);
649 $fullpath = $updraftplus->backups_dir_location().'/'.$file;
650 if (!$s3->getObject($bucket_name, $bucket_path.$file, $fullpath, true)) {
651 $updraftplus->log("$whoweare Error: Failed to download $file. Check your permissions and credentials.");
652 $updraftplus->log(sprintf(__('%s Error: Failed to download %s. Check your permissions and credentials.','updraftplus'),$whoweare, $file), 'error');
653 return false;
654 }
655 } else {
656 $updraftplus->log("$whoweare Error: Failed to access bucket $bucket_name. Check your permissions and credentials.");
657 $updraftplus->log(sprintf(__('%s Error: Failed to access bucket %s. Check your permissions and credentials.','updraftplus'),$whoweare, $bucket_name), 'error');
658 return false;
659 }
660 return true;
661
662 }
663
664 public function config_print() {
665
666 # White: https://d36cz9buwru1tt.cloudfront.net/Powered-by-Amazon-Web-Services.jpg
667 $this->config_print_engine('s3', 'S3', 'Amazon S3', 'AWS', 'https://aws.amazon.com/console/', '<img src="//awsmedia.s3.amazonaws.com/AWS_logo_poweredby_black_127px.png" alt="Amazon Web Services">');
668
669 }
670
671 public function config_print_engine($key, $whoweare_short, $whoweare_long, $console_descrip, $console_url, $img_html = '', $include_endpoint_chooser = false) {
672
673 $opts = $this->get_config();
674 ?>
675 <tr class="updraftplusmethod <?php echo $key; ?>">
676 <td></td>
677 <td><?php echo $img_html ?><p><em><?php printf(__('%s is a great choice, because UpdraftPlus supports chunked uploads - no matter how big your site is, UpdraftPlus can upload it a little at a time, and not get thwarted by timeouts.','updraftplus'),$whoweare_long);?></em></p>
678 <?php
679 if ('s3generic' == $key) {
680 _e('Examples of S3-compatible storage providers:').' ';
681 echo '<a href="http://www.cloudian.com/">Cloudian</a>, ';
682 echo '<a href="https://www.mh.connectria.com/rp/order/cloud_storage_index">Connectria</a>, ';
683 echo '<a href="http://www.constant.com/cloud/storage/">Constant</a>, ';
684 echo '<a href="http://www.eucalyptus.com/eucalyptus-cloud/iaas">Eucalyptus</a>, ';
685 echo '<a href="http://cloud.nifty.com/storage/">Nifty</a>, ';
686 echo '<a href="http://www.ntt.com/cloudn/data/storage.html">Cloudn</a>';
687 echo ''.__('... and many more!', 'updraftplus').'<br>';
688 }
689 ?>
690 </td>
691 </tr>
692 <tr class="updraftplusmethod <?php echo $key; ?>">
693 <th></th>
694 <td>
695 <?php
696 global $updraftplus_admin;
697
698 $use_s3_class = $this->indicate_s3_class();
699
700 if ('UpdraftPlus_S3_Compat' == $use_s3_class && !class_exists('XMLWriter')) {
701 $updraftplus_admin->show_double_warning('<strong>'.__('Warning', 'updraftplus').':</strong> '. sprintf(__("Your web server's PHP installation does not included a required module (%s). Please contact your web hosting provider's support and ask for them to enable it.", 'updraftplus'), 'XMLWriter'));
702 }
703
704 if (!class_exists('SimpleXMLElement')) {
705 $updraftplus_admin->show_double_warning('<strong>'.__('Warning', 'updraftplus').':</strong> '.sprintf(__("Your web server's PHP installation does not included a required module (%s). Please contact your web hosting provider's support.", 'updraftplus'), 'SimpleXMLElement').' '.sprintf(__("UpdraftPlus's %s module <strong>requires</strong> %s. Please do not file any support requests; there is no alternative.", 'updraftplus'),$whoweare_long, 'SimpleXMLElement'), $key);
706 }
707 $updraftplus_admin->curl_check($whoweare_long, true, $key);
708 ?>
709
710 </td>
711 </tr>
712 <tr class="updraftplusmethod <?php echo $key; ?>">
713 <th></th>
714 <td>
715 <p>
716 <?php if ($console_url) echo sprintf(__('Get your access key and secret key <a href="%s">from your %s console</a>, then pick a (globally unique - all %s users) bucket name (letters and numbers) (and optionally a path) to use for storage. This bucket will be created for you if it does not already exist.','updraftplus'), $console_url, $console_descrip, $whoweare_long);?>
717
718 <a href="https://updraftplus.com/faqs/i-get-ssl-certificate-errors-when-backing-up-andor-restoring/"><?php _e('If you see errors about SSL certificates, then please go here for help.','updraftplus');?></a>
719
720 <a href="https://updraftplus.com/faq-category/amazon-s3/"><?php if ('s3' == $key) echo sprintf(__('Other %s FAQs.', 'updraftplus'), 'S3');?></a>
721 </p>
722 </td></tr>
723 <?php if ($include_endpoint_chooser) { ?>
724 <tr class="updraftplusmethod <?php echo $key; ?>">
725 <th><?php echo sprintf(__('%s end-point','updraftplus'), $whoweare_short);?>:</th>
726 <td><input data-updraft_settings_test="endpoint" type="text" style="width: 360px" id="updraft_<?php echo $key; ?>_endpoint" name="updraft_<?php echo $key; ?>[endpoint]" value="<?php if (!empty($opts['endpoint'])) echo esc_attr($opts['endpoint']); ?>" /></td>
727 </tr>
728 <?php } else { ?>
729 <input data-updraft_settings_test="endpoint" type="hidden" id="updraft_<?php echo $key; ?>_endpoint" name="updraft_<?php echo $key; ?>_endpoint" value="">
730 <?php } ?>
731 <?php if ('s3' == $key && version_compare(PHP_VERSION, '5.3.3', '>=') && class_exists('UpdraftPlus_Addon_S3_Enhanced')) { ?>
732 <tr class="updraftplusmethod <?php echo $key; ?>">
733 <th></th>
734 <td><?php echo apply_filters('updraft_s3_apikeysetting', '<a href="https://updraftplus.com/shop/s3-enhanced/"><em>'.__('To create a new IAM sub-user and access key that has access only to this bucket, use this add-on.', 'updraftplus').'</em></a>'); ?></td>
735 </tr>
736 <?php } ?>
737
738 <tr class="updraftplusmethod <?php echo $key; ?>">
739 <th><?php echo sprintf(__('%s access key','updraftplus'), $whoweare_short);?>:</th>
740 <td><input data-updraft_settings_test="apikey" type="text" autocomplete="off" style="width: 360px" id="updraft_<?php echo $key; ?>_apikey" name="updraft_<?php echo $key; ?>[accesskey]" value="<?php echo esc_attr($opts['accesskey']); ?>" /></td>
741 </tr>
742 <tr class="updraftplusmethod <?php echo $key; ?>">
743 <th><?php echo sprintf(__('%s secret key','updraftplus'), $whoweare_short);?>:</th>
744 <td><input data-updraft_settings_test="apisecret" type="<?php echo apply_filters('updraftplus_admin_secret_field_type', 'text'); ?>" autocomplete="off" style="width: 360px" id="updraft_<?php echo $key; ?>_apisecret" name="updraft_<?php echo $key; ?>[secretkey]" value="<?php echo esc_attr($opts['secretkey']); ?>" /></td>
745 </tr>
746 <tr class="updraftplusmethod <?php echo $key; ?>">
747 <th><?php echo sprintf(__('%s location','updraftplus'), $whoweare_short);?>:</th>
748 <td><?php echo $key; ?>://<input data-updraft_settings_test="path" title="<?php echo htmlspecialchars(__('Enter only a bucket name or a bucket and path. Examples: mybucket, mybucket/mypath', 'updraftplus')); ?>" type="text" style="width: 360px" name="updraft_<?php echo $key; ?>[path]" id="updraft_<?php echo $key; ?>_path" value="<?php echo esc_attr($opts['path']); ?>" /></td>
749 </tr>
750 <?php do_action('updraft_'.$key.'_extra_storage_options', $opts); ?>
751 <tr class="updraftplusmethod <?php echo $key; ?>">
752 <th></th>
753 <td><p><button id="updraft-<?php echo $key; ?>-test" type="button" class="button-primary updraft-test-button" data-method_label="<?php esc_attr_e($whoweare_short);?>" data-method="<?php echo $key;?>"><?php echo htmlspecialchars(sprintf(__('Test %s Settings','updraftplus'),$whoweare_short));?></button></p></td>
754 </tr>
755
756 <?php
757 }
758
759 public function credentials_test($posted_settings) {
760 return $this->credentials_test_engine($this->get_config(), $posted_settings);
761 }
762
763 // This is not pretty, but is the simplest way to accomplish the task within the pre-existing structure (no need to re-invent the wheel of code with corner-cases debugged over years)
764 public function use_dns_bucket_name($s3, $bucket, $echo_errors = false) {
765 if (is_a($s3, 'UpdraftPlus_S3_Compat')) {
766 $this->s3_object = null;
767 $s3 = $this->getS3($this->got_with['key'], $this->got_with['secret'], $this->got_with['useservercerts'], $this->got_with['disableverify'], $this->got_with['nossl'], $bucket.".s3.amazonaws.com", $this->got_with['server_side_encryption']);
768 if (is_wp_error($s3)) {
769 if ($echo_errors) {
770 foreach ($s3->get_error_messages() as $msg) { echo $msg."\n"; }
771 } else {
772 global $updraftplus;
773 $updraftplus->log_wp_error($s3);
774 }
775 return;
776 }
777 global $updraftplus;
778 if (!$echo_errors) $updraftplus->log("Using DNS bucket name: $bucket.s3.amazonaws.com");
779 } else {
780 $s3->useDNSBucketName(true, $bucket);
781 }
782 return $s3;
783 }
784
785 public function credentials_test_engine($config, $posted_settings) {
786
787 if (empty($posted_settings['apikey'])) {
788 printf(__("Failure: No %s was given.",'updraftplus'),__('API key','updraftplus'));
789 return;
790 }
791 if (empty($posted_settings['apisecret'])) {
792 printf(__("Failure: No %s was given.",'updraftplus'),__('API secret','updraftplus'));
793 return;
794 }
795
796 $key = $posted_settings['apikey'];
797 $secret = stripslashes($posted_settings['apisecret']);
798 $path = $posted_settings['path'];
799 $useservercerts = (isset($posted_settings['useservercerts'])) ? absint($posted_settings['useservercerts']) : 0;
800 $disableverify = (isset($posted_settings['disableverify'])) ? absint($posted_settings['disableverify']) : 0;
801 $nossl = (isset($posted_settings['nossl'])) ? absint($posted_settings['nossl']) : 0;
802 $endpoint = (isset($posted_settings['endpoint'])) ? $posted_settings['endpoint'] : '';
803 $sse = !empty($posted_settings['sse']) ? true : false;
804
805 if (preg_match("#^/*([^/]+)/(.*)$#", $path, $bmatches)) {
806 $bucket = $bmatches[1];
807 $path = trailingslashit($bmatches[2]);
808 } else {
809 $bucket = $path;
810 $path = "";
811 }
812
813 if (empty($bucket)) {
814 _e("Failure: No bucket details were given.",'updraftplus');
815 return;
816 }
817 $whoweare = $config['whoweare'];
818
819 $s3 = $this->getS3($key, $secret, $useservercerts, $disableverify, $nossl, null, $sse);
820 if (is_wp_error($s3)) {
821 foreach ($s3->get_error_messages() as $msg) {
822 echo $msg."\n";
823 }
824 return;
825 }
826
827 $location = ('s3' == $config['key'] || 'updraftvault' == $config['key']) ? @$s3->getBucketLocation($bucket) : 'n/a';
828
829 if ('s3' != $config['key']) $this->set_region($s3, $endpoint, $bucket);
830
831 if ($location && 'n/a' != $location) {
832 if ('s3' == $config['key']) {
833 $bucket_exists = true;
834 $bucket_verb = __('Region','updraftplus').": $location: ";
835 } else {
836 $bucket_verb = '';
837 }
838 }
839
840 # Saw one case where there was read/write permission, but no permission to get the location - yet the bucket did exist. Try to detect that.
841 # Feb 2015: after we moved to the new SDK which didn't support this, two more reports came in
842 if (!isset($bucket_exists) && 's3' == $config['key']) {
843 $s3 = $this->use_dns_bucket_name($s3, $bucket, true);
844 $gb = @$s3->getBucket($bucket, $path, null, 1);
845 if ($gb !== false) {
846 $bucket_exists = true;
847 $location = '';
848 $bucket_verb = '';
849 }
850 }
851
852 if (!isset($bucket_exists)) {
853 $s3->setExceptions(true);
854 try {
855 $try_to_create_bucket = @$s3->putBucket($bucket, 'private');
856 } catch (Exception $e) {
857 $try_to_create_bucket = false;
858 $s3_error = $e->getMessage();
859 }
860 $s3->setExceptions(false);
861 if ($try_to_create_bucket) {
862 $bucket_verb = '';
863 $bucket_exists = true;
864 } else {
865 echo sprintf(__("Failure: We could not successfully access or create such a bucket. Please check your access credentials, and if those are correct then try another bucket name (as another %s user may already have taken your name).",'updraftplus'),$whoweare);
866 if (isset($s3_error)) echo "\n\n".sprintf(__('The error reported by %s was:','updraftplus'), $config['key']).' '.$s3_error;
867 }
868 }
869
870 if (isset($bucket_exists)) {
871 $try_file = md5(rand());
872 if ($config['key'] != 'dreamobjects' && $config['key'] != 's3generic') $this->set_region($s3, $location, $bucket);
873 $s3->setExceptions(true);
874 try {
875 if (!$s3->putObjectString($try_file, $bucket, $path.$try_file)) {
876 echo __('Failure','updraftplus').": ${bucket_verb}".__('We successfully accessed the bucket, but the attempt to create a file in it failed.','updraftplus');
877 } else {
878 echo __('Success', 'updraftplus').": ${bucket_verb}".__('We accessed the bucket, and were able to create files within it.','updraftplus').' ';
879 $comm_with = ($config['key'] == 's3generic') ? $endpoint : $config['whoweare_long'];
880 if ($s3->getuseSSL()) {
881 echo sprintf(__('The communication with %s was encrypted.', 'updraftplus'), $comm_with);
882 } else {
883 echo sprintf(__('The communication with %s was not encrypted.', 'updraftplus'), $comm_with);
884 }
885 $create_success = true;
886 }
887 } catch (Exception $e) {
888 echo __('Failure','updraftplus').": ${bucket_verb}".__('We successfully accessed the bucket, but the attempt to create a file in it failed.','updraftplus').' '.__('Please check your access credentials.','updraftplus').' ('.$e->getMessage().')';
889 }
890
891 if (!empty($create_success)) {
892 try {
893 @$s3->deleteObject($bucket, $path.$try_file);
894 } catch (Exception $e) {
895 echo ' '.__('Delete failed:', 'updraftplus').' '.$e->getMessage();
896 }
897 }
898
899 }
900
901 }
902
903 }
904