UpdraftPlus_Options::get_updraft_option('updraft_s3_login'),
'secretkey' => UpdraftPlus_Options::get_updraft_option('updraft_s3_pass'),
'path' => UpdraftPlus_Options::get_updraft_option('updraft_s3_remote_path')
);
UpdraftPlus_Options::update_updraft_option('updraft_s3', $opts);
UpdraftPlus_Options::delete_updraft_option('updraft_s3_login');
UpdraftPlus_Options::delete_updraft_option('updraft_s3_pass');
UpdraftPlus_Options::delete_updraft_option('updraft_s3_remote_path');
}
class UpdraftPlus_BackupModule_s3 {
protected function get_config() {
global $updraftplus;
$opts = $updraftplus->get_job_option('updraft_s3');
if (!is_array($opts)) $opts = array('accesskey' => '', 'secretkey' => '', 'path' => '');
$opts['whoweare'] = 'S3';
$opts['whoweare_long'] = 'Amazon S3';
$opts['key'] = 's3';
return $opts;
}
public function get_credentials() {
return array('updraft_s3');
}
// Get an S3 object, after setting our options
protected function getS3($key, $secret, $useservercerts, $disableverify, $nossl) {
global $updraftplus;
if (!class_exists('UpdraftPlus_S3')) require_once(UPDRAFTPLUS_DIR.'/includes/S3.php');
if (!class_exists('WP_HTTP_Proxy')) require_once(ABSPATH.'wp-includes/class-http.php');
$proxy = new WP_HTTP_Proxy();
$s3 = new UpdraftPlus_S3($key, $secret);
if ( $proxy->is_enabled()) {
# WP_HTTP_Proxy returns empty strings where we want nulls
$user = $proxy->username();
if (empty($user)) {
$user = null;
$pass = null;
} else {
$pass = $proxy->password();
if (empty($pass)) $pass = null;
}
$port = (int)$proxy->port();
if (empty($port)) $port = 8080;
$s3->setProxy($proxy->host(), $user, $pass, CURLPROXY_HTTP, $port);
}
if (!$nossl) {
$curl_version = (function_exists('curl_version')) ? curl_version() : array('features' => null);
$curl_ssl_supported= ($curl_version['features'] & CURL_VERSION_SSL);
if ($curl_ssl_supported) {
$s3->useSSL = true;
if ($disableverify) {
$s3->useSSLValidation = false;
$updraftplus->log("S3: Disabling verification of SSL certificates");
}
if ($useservercerts) {
$updraftplus->log("S3: Using the server's SSL certificates");
} else {
$s3->SSLCACert = UPDRAFTPLUS_DIR.'/includes/cacert.pem';
}
} else {
$updraftplus->log("S3: Curl/SSL is not available. Communications will not be encrypted.");
}
} else {
$s3->useSSL = false;
$updraftplus->log("SSL was disabled via the user's preference. Communications will not be encrypted.");
}
return $s3;
}
protected function set_endpoint($obj, $region) {
switch ($region) {
case 'EU':
case 'eu-west-1':
$endpoint = 's3-eu-west-1.amazonaws.com';
break;
case 'us-west-1':
case 'us-west-2':
case 'ap-southeast-1':
case 'ap-southeast-2':
case 'ap-northeast-1':
case 'sa-east-1':
case 'us-gov-west-1':
$endpoint = 's3-'.$region.'.amazonaws.com';
break;
case 'cn-north-1':
$endpoint = 's3.'.$region.'.amazonaws.com.cn';
break;
default:
break;
}
if (isset($endpoint)) {
global $updraftplus;
$updraftplus->log("Set endpoint: $endpoint");
$obj->setEndpoint($endpoint);
}
}
public function backup($backup_array) {
global $updraftplus, $updraftplus_backup;
$config = $this->get_config();
$whoweare = $config['whoweare'];
$whoweare_key = $config['key'];
$whoweare_keys = substr($whoweare_key, 0, 3);
$s3 = $this->getS3(
$config['accesskey'],
$config['secretkey'],
UpdraftPlus_Options::get_updraft_option('updraft_ssl_useservercerts'), UpdraftPlus_Options::get_updraft_option('updraft_ssl_disableverify'),
UpdraftPlus_Options::get_updraft_option('updraft_ssl_nossl')
);
$bucket_name = untrailingslashit($config['path']);
$bucket_path = "";
$orig_bucket_name = $bucket_name;
if (preg_match("#^([^/]+)/(.*)$#",$bucket_name,$bmatches)) {
$bucket_name = $bmatches[1];
$bucket_path = $bmatches[2]."/";
}
// This needs to cope with both original S3 and others (where there is no getBucketLocation())
$region = ($config['key'] == 's3') ? @$s3->getBucketLocation($bucket_name) : 'n/a';
// See if we can detect the region (which implies the bucket exists and is ours), or if not create it
if (!empty($region) || @$s3->putBucket($bucket_name, UpdraftPlus_S3::ACL_PRIVATE)) {
if (empty($region) && $config['key'] == 's3') $region = $s3->getBucketLocation($bucket_name);
$this->set_endpoint($s3, $region);
$updraft_dir = trailingslashit($updraftplus->backups_dir_location());
foreach($backup_array as $key => $file) {
// We upload in 5Mb chunks to allow more efficient resuming and hence uploading of larger files
// N.B.: 5Mb is Amazon's minimum. So don't go lower or you'll break it.
$fullpath = $updraft_dir.$file;
$orig_file_size = filesize($fullpath);
$chunks = floor($orig_file_size / 5242880);
// There will be a remnant unless the file size was exactly on a 5Mb boundary
if ($orig_file_size % 5242880 > 0) $chunks++;
$hash = md5($file);
$updraftplus->log("$whoweare upload ($region): $file (chunks: $chunks) -> s3://$bucket_name/$bucket_path$file");
$filepath = $bucket_path.$file;
// This is extra code for the 1-chunk case, but less overhead (no bothering with job data)
if ($chunks < 2) {
$s3->setExceptions(true);
try {
if (!$s3->putObjectFile($fullpath, $bucket_name, $filepath, UpdraftPlus_S3::ACL_PRIVATE, array(), array(), apply_filters('updraft_'.$whoweare_key.'_storageclass', UpdraftPlus_S3::STORAGE_CLASS_STANDARD, $s3, $config))) {
$updraftplus->log("$whoweare regular upload: failed ($fullpath)");
$updraftplus->log("$file: ".sprintf(__('%s Error: Failed to upload','updraftplus'),$whoweare), 'error');
} else {
$updraftplus->log("$whoweare regular upload: success");
$updraftplus->uploaded_file($file);
}
} catch (Exception $e) {
$updraftplus->log("$file: ".sprintf(__('%s Error: Failed to upload','updraftplus'),$whoweare).": ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile());
$updraftplus->log("$file: ".sprintf(__('%s Error: Failed to upload','updraftplus'),$whoweare), 'error');
}
$s3->setExceptions(false);
} else {
// Retrieve the upload ID
$uploadId = $updraftplus->jobdata_get("upd_${whoweare_keys}_${hash}_uid");
if (empty($uploadId)) {
$s3->setExceptions(true);
try {
$uploadId = $s3->initiateMultipartUpload($bucket_name, $filepath, UpdraftPlus_S3::ACL_PRIVATE, array(), array(), apply_filters('updraft_'.$whoweare_key.'_storageclass', UpdraftPlus_S3::STORAGE_CLASS_STANDARD, $s3, $config));
} catch (Exception $e) {
$updraftplus->log("$whoweare error whilst trying initiateMultipartUpload: ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
$uploadId = false;
}
$s3->setExceptions(false);
if (empty($uploadId)) {
$updraftplus->log("$whoweare upload: failed: could not get uploadId for multipart upload ($filepath)");
$updraftplus->log(sprintf(__("%s upload: getting uploadID for multipart upload failed - see log file for more details",'updraftplus'),$whoweare), 'error');
continue;
} else {
$updraftplus->log("$whoweare chunked upload: got multipart ID: $uploadId");
$updraftplus->jobdata_set("upd_${whoweare_keys}_${hash}_uid", $uploadId);
}
} else {
$updraftplus->log("$whoweare chunked upload: retrieved previously obtained multipart ID: $uploadId");
}
$successes = 0;
$etags = array();
for ($i = 1 ; $i <= $chunks; $i++) {
# Shorted to upd here to avoid hitting the 45-character limit
$etag = $updraftplus->jobdata_get("ud_${whoweare_keys}_${hash}_e$i");
if (strlen($etag) > 0) {
$updraftplus->log("$whoweare chunk $i: was already completed (etag: $etag)");
$successes++;
array_push($etags, $etag);
} else {
// Sanity check: we've seen a case where an overlap was truncating the file from underneath us
if (filesize($fullpath) < $orig_file_size) {
$updraftplus->log("$whoweare error: $key: chunk $i: file was truncated underneath us (orig_size=$orig_file_size, now_size=".filesize($fullpath).")");
$updraftplus->log(sprintf(__('%s error: file %s was shortened unexpectedly', 'updraftplus'), $whoweare, $fullpath), 'error');
}
$etag = $s3->uploadPart($bucket_name, $filepath, $uploadId, $fullpath, $i);
if ($etag !== false && is_string($etag)) {
$updraftplus->record_uploaded_chunk(round(100*$i/$chunks,1), "$i, $etag", $fullpath);
array_push($etags, $etag);
$updraftplus->jobdata_set("ud_${whoweare_keys}_${hash}_e$i", $etag);
$successes++;
} else {
$updraftplus->log("$whoweare chunk $i: upload failed");
$updraftplus->log(sprintf(__("%s chunk %s: upload failed",'updraftplus'),$whoweare, $i), 'error');
}
}
}
if ($successes >= $chunks) {
$updraftplus->log("$whoweare upload: all chunks uploaded; will now instruct $whoweare to re-assemble");
$s3->setExceptions(true);
try {
if ($s3->completeMultipartUpload($bucket_name, $filepath, $uploadId, $etags)) {
$updraftplus->log("$whoweare upload ($key): re-assembly succeeded");
$updraftplus->uploaded_file($file);
} else {
$updraftplus->log("$whoweare upload ($key): re-assembly failed ($file)");
$updraftplus->log(sprintf(__('%s upload (%s): re-assembly failed (see log for more details)','updraftplus'),$whoweare, $key), 'error');
}
} catch (Exception $e) {
$updraftplus->log("$whoweare re-assembly error ($key): ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
$updraftplus->log($e->getMessage().": ".sprintf(__('%s re-assembly error (%s): (see log file for more)','updraftplus'),$whoweare, $e->getMessage()), 'error');
}
// Remember to unset, as the deletion code later reuses the object
$s3->setExceptions(false);
} else {
$updraftplus->log("$whoweare upload: upload was not completely successful on this run");
}
}
}
return array('s3_object' => $s3, 's3_orig_bucket_name' => $orig_bucket_name);
} else {
$updraftplus->log("$whoweare Error: Failed to create bucket $bucket_name.");
$updraftplus->log(sprintf(__('%s Error: Failed to create bucket %s. Check your permissions and credentials.','updraftplus'),$whoweare, $bucket_name), 'error');
}
}
public function delete($files, $s3arr = false) {
global $updraftplus;
if (is_string($files)) $files=array($files);
$config = $this->get_config();
$whoweare = $config['whoweare'];
if ($s3arr) {
$s3 = $s3arr['s3_object'];
$orig_bucket_name = $s3arr['s3_orig_bucket_name'];
} else {
$s3 = $this->getS3(
$config['accesskey'],
$config['secretkey'],
UpdraftPlus_Options::get_updraft_option('updraft_ssl_useservercerts'), UpdraftPlus_Options::get_updraft_option('updraft_ssl_disableverify'),
UpdraftPlus_Options::get_updraft_option('updraft_ssl_nossl')
);
$bucket_name = untrailingslashit($config['path']);
$orig_bucket_name = $bucket_name;
if (preg_match("#^([^/]+)/(.*)$#",$bucket_name,$bmatches)) {
$bucket_name = $bmatches[1];
$bucket_path = $bmatches[2]."/";
}
$region = ($config['key'] == 'dreamobjects' || $config['key'] == 's3generic') ? 'n/a' : @$s3->getBucketLocation($bucket_name);
if (!empty($region)) {
$this->set_endpoint($s3, $region);
} else {
$updraftplus->log("$whoweare Error: Failed to access bucket $bucket_name. Check your permissions and credentials.");
$updraftplus->log(sprintf(__('%s Error: Failed to access bucket %s. Check your permissions and credentials.','updraftplus'),$whoweare, $bucket_name), 'error');
return false;
}
}
$ret = true;
foreach ($files as $file) {
if (preg_match("#^([^/]+)/(.*)$#", $orig_bucket_name, $bmatches)) {
$s3_bucket=$bmatches[1];
$s3_uri = $bmatches[2]."/".$file;
} else {
$s3_bucket = $orig_bucket_name;
$s3_uri = $file;
}
$updraftplus->log("$whoweare: Delete remote: bucket=$s3_bucket, URI=$s3_uri");
$s3->setExceptions(true);
try {
if (!$s3->deleteObject($s3_bucket, $s3_uri)) {
$updraftplus->log("$whoweare: Delete failed");
}
} catch (Exception $e) {
$updraftplus->log("$whoweare delete failed: ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
$s3->setExceptions(false);
$ret = false;
}
$s3->setExceptions(false);
}
return $ret;
}
public function download($file) {
global $updraftplus;
$config = $this->get_config();
$whoweare = $config['whoweare'];
$s3 = $this->getS3(
$config['accesskey'],
$config['secretkey'],
UpdraftPlus_Options::get_updraft_option('updraft_ssl_useservercerts'), UpdraftPlus_Options::get_updraft_option('updraft_ssl_disableverify'),
UpdraftPlus_Options::get_updraft_option('updraft_ssl_nossl')
);
$bucket_name = untrailingslashit($config['path']);
$bucket_path = "";
if (preg_match("#^([^/]+)/(.*)$#", $bucket_name, $bmatches)) {
$bucket_name = $bmatches[1];
$bucket_path = $bmatches[2]."/";
}
$region = ($config['key'] == 'dreamobjects' || $config['key'] == 's3generic') ? 'n/a' : @$s3->getBucketLocation($bucket_name);
if (!empty($region)) {
$this->set_endpoint($s3, $region);
$fullpath = $updraftplus->backups_dir_location().'/'.$file;
if (!$s3->getObject($bucket_name, $bucket_path.$file, $fullpath, true)) {
$updraftplus->log("$whoweare Error: Failed to download $file. Check your permissions and credentials.");
$updraftplus->log(sprintf(__('%s Error: Failed to download %s. Check your permissions and credentials.','updraftplus'),$whoweare, $file), 'error');
return false;
}
} else {
$updraftplus->log("$whoweare Error: Failed to access bucket $bucket_name. Check your permissions and credentials.");
$updraftplus->log(sprintf(__('%s Error: Failed to access bucket %s. Check your permissions and credentials.','updraftplus'),$whoweare, $bucket_name), 'error');
return false;
}
return true;
}
public function config_print_javascript_onready() {
$this->config_print_javascript_onready_engine('s3', 'S3');
}
public function config_print_javascript_onready_engine($key, $whoweare) {
?>
jQuery('#updraft--test').click(function(){
jQuery('#updraft--test').html('');
var data = {
action: 'updraft_ajax',
subaction: 'credentials_test',
method: '',
nonce: '',
apikey: jQuery('#updraft__apikey').val(),
apisecret: jQuery('#updraft__apisecret').val(),
path: jQuery('#updraft__path').val(),
endpoint: jQuery('#updraft__endpoint').val(),
disableverify: (jQuery('#updraft_ssl_disableverify').is(':checked')) ? 1 : 0,
useservercerts: (jQuery('#updraft_ssl_useservercerts').is(':checked')) ? 1 : 0,
nossl: (jQuery('#updraft_ssl_nossl').is(':checked')) ? 1 : 0,
};
jQuery.post(ajaxurl, data, function(response) {
jQuery('#updraft--test').html('');
alert(' ' + response);
});
});
config_print_engine('s3', 'S3', 'Amazon S3', 'AWS', 'http://aws.amazon.com/console/', '
');
}
public function config_print_engine($key, $whoweare_short, $whoweare_long, $console_descrip, $console_url, $img_html = '', $include_endpoint_chooser = false) {
$opts = $this->get_config();
?>
Cloudian, '; echo 'Google Cloud Storage, '; echo 'Connectria, '; echo 'Constant, '; echo 'Eucalyptus, '; echo 'Nifty, '; echo 'Cloudn'; echo ''.__('... and many more!', 'updraftplus').'
from your %s console, 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);?>