Initial commit: Atomaste website

This commit is contained in:
2025-12-10 12:17:30 -05:00
commit 0b9e5d1605
19260 changed files with 5206382 additions and 0 deletions

View File

@@ -0,0 +1,367 @@
<?php
if (!defined('UPDRAFTPLUS_DIR')) die('No direct access allowed');
/*
Methods to define when extending this class (can use $this->storage and $this->options where relevant):
do_bootstrap($possible_options_array) # Return a WP_Error object if something goes wrong
do_upload($file, $sourcefile) # Return true/false
do_listfiles($match)
do_delete($file) - return true/false
do_download($file, $fullpath, $start_offset) - return true/false
do_config_print()
get_credentials_test_required_parameters() - return an array: keys = required _POST parameters; values = description of each
do_credentials_test($testfile, $posted_settings) - return true/false; or alternatively an array with keys 'result' (true/false) and 'data' (arbitrary debug data)
do_credentials_test_deletefile($testfile, $posted_settings)
*/
// Uses job options: Yes
// Uses single-array storage: Yes
if (!class_exists('UpdraftPlus_BackupModule')) updraft_try_include_file('methods/backup-module.php', 'require_once');
/**
* Note that the naming of this class is historical. There is nothing inherent which restricts it to add-ons, or requires add-ons to use it. It is just an abstraction layer that results in needing to write less code for the storage module.
*/
abstract class UpdraftPlus_RemoteStorage_Addons_Base_v2 extends UpdraftPlus_BackupModule {
protected $method;
protected $description;
protected $options;
private $chunked;
/**
* Decides whether to print the test button
*
* @var Boolean
*/
protected $test_button;
public function __construct($method, $description, $chunked = true, $test_button = true) {
$this->method = $method;
$this->description = $description;
$this->chunked = $chunked;
$this->test_button = $test_button;
}
/**
* download method: takes a file name (base name), and removes it from the cloud storage
*
* @param String $file specific file for being removed from cloud storage
* @return Array
*/
public function download($file) {
return $this->download_file(false, $file);
}
public function backup($backup_array) {
return $this->upload_files(null, $backup_array);
}
public function delete($files, $method_obj = false, $sizeinfo = array()) {
return $this->delete_files(false, $files, $method_obj, $sizeinfo);
}
protected function required_configuration_keys() {
}
public function upload_files($ret, $backup_array) {// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found -- Unused parameter is present because the caller from this class uses 2 arguments.
global $updraftplus;
$this->options = $this->get_options();
if (!$this->options_exist($this->options)) {
$this->log('No settings were found');
$this->log(sprintf(__('No %s settings were found', 'updraftplus'), $this->description), 'error');
return false;
}
$storage = $this->bootstrap();
if (is_wp_error($storage)) return $updraftplus->log_wp_error($storage, false, true);
$this->set_storage($storage);
$updraft_dir = trailingslashit($updraftplus->backups_dir_location());
foreach ($backup_array as $file) {
$this->log("upload ".((!empty($this->options['ownername'])) ? '(account owner: '.$this->options['ownername'].')' : '').": attempt: $file");
try {
if ($this->do_upload($file, $updraft_dir.$file)) {
$updraftplus->uploaded_file($file);
} else {
$any_failures = true;
$this->log('ERROR: Failed to upload file: '.$file);
$this->log(__('Error', 'updraftplus').': '.$this->description.': '.sprintf(__('Failed to upload %s', 'updraftplus'), $file), 'error');
}
} catch (Exception $e) {
$any_failures = true;
$this->log('ERROR ('.get_class($e).'): '.$file.': Failed to upload file: '.$e->getMessage().' (code: '.$e->getCode().', line: '.$e->getLine().', file: '.$e->getFile().')');
$this->log(__('Error', 'updraftplus').': '.$this->description.': '.sprintf(__('Failed to upload %s', 'updraftplus'), $file), 'error');
}
}
return (!empty($any_failures)) ? null : true;
}
/**
* This function lists the files found in the configured storage location
*
* @param String $match a substring to require
*
* @return Array|WP_Error - each file is represented by an array with entries 'name' and (optional) 'size'
*/
public function listfiles($match = 'backup_') {
try {
if (!method_exists($this, 'do_listfiles')) {
return new WP_Error('no_listing', 'This remote storage method does not support file listing');
}
$this->options = $this->get_options();
if (!$this->options_exist($this->options)) return new WP_Error('no_settings', sprintf(__('No %s settings were found', 'updraftplus'), $this->description));
$storage = $this->bootstrap();
if (is_wp_error($storage)) return $storage;
return $this->do_listfiles($match);
} catch (Exception $e) {
$this->log('ERROR: Failed to list files: '.$e->getMessage().' (code: '.$e->getCode().', line: '.$e->getLine().', file: '.$e->getFile().')');
return new WP_Error('list_failed', $this->description.': '.__('failed to list files', 'updraftplus'));
}
}
/**
* This function handles bootstrapping and calling the remote methods delete function
*
* @param Boolean $ret - A boolean value
* @param Array $files - An array of files to delete.
*
* @return - On success returns true, false or WordPress Error on failure
*/
public function delete_files($ret, $files) {
global $updraftplus;
if (is_string($files)) $files = array($files);
if (empty($files)) return true;
if (!method_exists($this, 'do_delete')) {
$this->log("Delete failed: this storage method does not allow deletions");
return false;
}
$storage = $this->get_storage();
if (empty($storage)) {
$this->options = $this->get_options();
if (!$this->options_exist($this->options)) {
$this->log('No settings were found');
$this->log(sprintf(__('No %s settings were found', 'updraftplus'), $this->description), 'error');
return false;
}
$storage = $this->bootstrap();
if (is_wp_error($storage)) return $storage;
}
$ret = true;
if ($this->supports_feature('multi_delete')) {
$updraftplus->log("Delete remote files: ".implode(', ', $files));
try {
$responses = $this->do_delete($files);
$ret = $this->process_multi_delete_responses($files, $responses);
} catch (Exception $e) {
$updraftplus->log('ERROR:'.implode($files).': Failed to delete files: '.$e->getMessage().' (code: '.$e->getCode().', line: '.$e->getLine().', file: '.$e->getFile().')');
$ret = false;
}
return $ret;
}
foreach ($files as $file) {
$this->log("Delete remote: $file");
try {
$ret = $this->do_delete($file);
if (true === $ret) {
$this->log("$file: Delete succeeded");
} else {
$this->log("Delete failed");
}
} catch (Exception $e) {
$this->log('ERROR: '.$file.': Failed to delete file: '.$e->getMessage().' (code: '.$e->getCode().', line: '.$e->getLine().', file: '.$e->getFile().')');
$ret = false;
}
}
return $ret;
}
public function download_file($ret, $files) {
global $updraftplus;
if (is_string($files)) $files = array($files);
if (empty($files)) return true;
if (!method_exists($this, 'do_download')) {
$this->log("Download failed: this storage method does not allow downloading");
$this->log(__('This storage method does not allow downloading', 'updraftplus'), 'error');
return false;
}
$this->options = $this->get_options();
if (!$this->options_exist($this->options)) {
$this->log('No settings were found');
$this->log(sprintf(__('No %s settings were found', 'updraftplus'), $this->description), 'error');
return false;
}
try {
$storage = $this->bootstrap();
if (is_wp_error($storage)) return $updraftplus->log_wp_error($storage, false, true);
} catch (Exception $e) {
$ret = false;
$this->log('ERROR: '.$files[0].': Failed to download file: '.$e->getMessage().' (code: '.$e->getCode().', line: '.$e->getLine().', file: '.$e->getFile().')');
$this->log(__('Error', 'updraftplus').': '.$this->description.': '.sprintf(__('Failed to download %s', 'updraftplus'), $files[0]), 'error');
}
$ret = true;
$updraft_dir = untrailingslashit($updraftplus->backups_dir_location());
foreach ($files as $file) {
try {
$fullpath = $updraft_dir.'/'.$file;
$start_offset = file_exists($fullpath) ? filesize($fullpath) : 0;
if (false == $this->do_download($file, $fullpath, $start_offset)) {
$ret = false;
$this->log("error: failed to download: $file");
$this->log("$file: ".sprintf(__("%s Error", 'updraftplus'), $this->description).": ".__('Failed to download', 'updraftplus'), 'error');
}
} catch (Exception $e) {
$ret = false;
$this->log('ERROR: '.$file.': Failed to download file: '.$e->getMessage().' (code: '.$e->getCode().', line: '.$e->getLine().', file: '.$e->getFile().')');
$this->log(__('Error', 'updraftplus').': '.$this->description.': '.sprintf(__('Failed to download %s', 'updraftplus'), $file), 'error');
}
}
return $ret;
}
/**
* Get the configuration template
*
* @return String - the template, ready for substitutions to be carried out
*/
public function get_configuration_template() {
$template_str = '';
if (method_exists($this, 'do_get_configuration_template')) {
$template_str .= $this->do_get_configuration_template();
}
if (!$this->test_button || (method_exists($this, 'should_print_test_button') && !$this->should_print_test_button())) return $template_str;
$template_str .= $this->get_test_button_html($this->description);
return $template_str;
}
/**
* Modifies handerbar template options
*
* @param array $opts
* @return Array - Modified handerbar template options
*/
public function transform_options_for_template($opts) {
if (method_exists($this, 'do_transform_options_for_template')) {
$opts = $this->do_transform_options_for_template($opts);
}
return $opts;
}
public function config_print_javascript_onready() {
$this->do_config_javascript();
}
protected function do_config_javascript() {
}
/**
* Analyse the passed-in options to indicate whether something is configured or not.
*
* @param Array $opts - options to examine
*
* @return Boolean
*/
public function options_exist($opts) {
if (is_array($opts) && !empty($opts)) return true;
return false;
}
public function bootstrap($opts = false, $connect = true) {
if (false === $opts) $opts = $this->options;
$storage = $this->get_storage();
// Be careful of checking empty($opts) here - some storage methods may have no options until the OAuth token has been obtained
if ($connect && !$this->options_exist($opts)) return new WP_Error('no_settings', sprintf(__('No %s settings were found', 'updraftplus'), $this->description));
if (!empty($storage) && !is_wp_error($storage)) return $storage;
return $this->do_bootstrap($opts, $connect);
}
/**
* Run a credentials test. Output can be echoed.
*
* @param Array $posted_settings - settings to use
*
* @return Mixed - any data to return (gets logged in the browser eventually)
*/
public function credentials_test($posted_settings) {
$required_test_parameters = $this->get_credentials_test_required_parameters();
foreach ($required_test_parameters as $param => $descrip) {
if (empty($posted_settings[$param])) {
echo esc_html(sprintf(__("Failure: No %s was given.", 'updraftplus'), $descrip))."\n";
return;
}
}
$storage = $this->bootstrap($posted_settings);
if (is_wp_error($storage)) {
echo esc_html__("Failed", 'updraftplus').": ";
foreach ($storage->get_error_messages() as $msg) {
echo esc_html($msg) .'\n';
}
return;
}
$testfile = md5(time().rand()).'.txt';
$test_results = $this->do_credentials_test($testfile, $posted_settings);
$data = (is_array($test_results) && isset($test_results['data'])) ? $test_results['data'] : null;
if ((is_array($test_results) && $test_results['result']) || (!is_array($test_results) && $test_results)) {
esc_html_e('Success', 'updraftplus');
$this->do_credentials_test_deletefile($testfile, $posted_settings);
} else {
esc_html_e("Failed: We were not able to place a file in that directory - please check your credentials.", 'updraftplus');
}
return $data;
}
}

View File

@@ -0,0 +1,131 @@
<?php
if (!defined('UPDRAFTPLUS_DIR')) die('No direct access allowed.');
if (!class_exists('UpdraftPlus_BackupModule')) updraft_try_include_file('methods/backup-module.php', 'require_once');
class UpdraftPlus_BackupModule_AddonNotYetPresent extends UpdraftPlus_BackupModule {
private $method;
private $description;
private $required_php;
private $image;
private $error_msg;
private $error_msg_trans;
public function __construct($method, $description, $required_php = false, $image = null) {
$this->method = $method;
$this->description = $description;
$this->required_php = $required_php;
$this->image = $image;
$this->error_msg = 'This remote storage method ('.$this->description.') requires PHP '.$this->required_php.' or later';
$this->error_msg_trans = sprintf(__('This remote storage method (%s) requires PHP %s or later.', 'updraftplus'), $this->description, $this->required_php);
}
public function backup($backup_array) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Unused variable is present because the function to perform backup for specific storage is not exist.
$this->log("You do not have the UpdraftPlus ".$this->method.' add-on installed - get it from '.apply_filters("updraftplus_com_link", "https://updraftplus.com/shop/").'');
$this->log(sprintf(__('You do not have the UpdraftPlus %s add-on installed - get it from %s', 'updraftplus'), $this->description, ''.apply_filters("updraftplus_com_link", "https://updraftplus.com/shop/").''), 'error', 'missingaddon-'.$this->method);
return false;
}
/**
* Retrieve a list of supported features for this storage method
*
* Currently known features:
*
* - multi_options : indicates that the remote storage module
* can handle its options being in the Feb-2017 multi-options
* format. N.B. This only indicates options handling, not any
* other multi-destination options.
*
* - multi_servers : not implemented yet: indicates that the
* remote storage module can handle multiple servers at backup
* time. This should not be specified without multi_options.
* multi_options without multi_servers is fine - it will just
* cause only the first entry in the options array to be used.
*
* - config_templates : not implemented yet: indicates that
* the remote storage module can output its configuration in
* Handlebars format via the get_configuration_template() method.
*
* - conditional_logic : indicates that the remote storage module
* can handle predefined logics regarding how backups should be
* sent to the remote storage
*
* @return Array - an array of supported features (any features not
* mentioned are assumed to not be supported)
*/
public function get_supported_features() {
// The 'multi_options' options format is handled via only accessing options via $this->get_options()
return array('multi_options', 'config_templates');
}
public function delete($files, $method_obj = false, $sizeinfo = array()) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Unused variable is present because the function to perform delete for specific storage is not exist.
$this->log('You do not have the UpdraftPlus '.$this->method.' add-on installed - get it from '.apply_filters("updraftplus_com_link", "https://updraftplus.com/shop/").'');
$this->log(sprintf(__('You do not have the UpdraftPlus %s add-on installed - get it from %s', 'updraftplus'), $this->description, ''.apply_filters("updraftplus_com_link", "https://updraftplus.com/shop/").''), 'error', 'missingaddon-'.$this->method);
return false;
}
public function listfiles($match = 'backup_') {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Unused variable is present because the function to perform listfiles for specific storage is not exist.
return new WP_Error('no_addon', sprintf(__('You do not have the UpdraftPlus %s add-on installed - get it from %s', 'updraftplus'), $this->description, ''.apply_filters("updraftplus_com_link", "https://updraftplus.com/shop/")));
}
/**
* Get the configuration template
*
* @return String - the template, ready for substitutions to be carried out
*/
public function get_configuration_template() {
ob_start();
?>
<tr class="{{css_class}} {{method_id}}">
<th>{{description}}:</th>
<td>{{{image}}}<a href="{{premium_url}}" target="_blank">{{addon_text}}</a></td>
</tr>
{{#unless php_version_supported}}
<tr class="{{css_class}} {{method_id}}">
<th></th>
<td>
<em>{{error_msg_trans}} {{hosting_text}} {{php_version_text}}</em>
</td>
</tr>
{{/unless}}
<?php
return ob_get_clean();
}
/**
* Retrieve a list of template properties by taking all the persistent variables and methods of the parent class and combining them with the ones that are unique to this module, also the necessary HTML element attributes and texts which are also unique only to this backup module
* NOTE: Please sanitise all strings that are required to be shown as HTML content on the frontend side (i.e. wp_kses()), or any other technique to prevent XSS attacks that could come via WP hooks
*
* @return Array an associative array keyed by names that describe themselves as they are
*/
public function get_template_properties() {
global $updraftplus;
$properties = array(
'description' => $this->description,
'php_version_supported' => (bool) apply_filters('updraftplus_storage_meets_php_requirement', version_compare(phpversion(), $this->required_php, '>='), $this->method),
'image' => (!empty($this->image)) ? '<p><img src="'.UPDRAFTPLUS_URL.'/images/'.$this->image.'"></p>' : '',
'error_msg_trans' => $this->error_msg_trans,
'premium_url' => $updraftplus->get_url('premium_'.$this->get_id()),
'addon_text' => sprintf(__('Back up to %s with %s.', 'updraftplus'), $this->description, 'UpdraftPlus Premium'),
'php_version_text' => sprintf(__('Your PHP version: %s.', 'updraftplus'), phpversion()),
'hosting_text' => __('You will need to ask your web hosting company to upgrade.', 'updraftplus'),
);
if ('sftp' === $this->get_id()) $properties['addon_text'] = sprintf(__('Back up via %s with %s.', 'updraftplus'), $this->description, 'UpdraftPlus Premium');
return wp_parse_args($properties, $this->get_persistent_variables_and_methods());
}
}

View File

@@ -0,0 +1,40 @@
<?php
if (!defined('UPDRAFTPLUS_DIR')) die('No direct access.');
if (version_compare(phpversion(), '5.3.3', '>=')) {
if (class_exists('UpdraftPlus_Addons_RemoteStorage_azure')) {
class UpdraftPlus_BackupModule_azure extends UpdraftPlus_Addons_RemoteStorage_azure {
public function __construct() {
parent::__construct('azure', 'Microsoft Azure', true, true);
}
}
} else {
updraft_try_include_file('methods/addon-not-yet-present.php', 'include_once');
/**
* N.B. UpdraftPlus_BackupModule_AddonNotYetPresent extends UpdraftPlus_BackupModule
*/
class UpdraftPlus_BackupModule_azure extends UpdraftPlus_BackupModule_AddonNotYetPresent {
public function __construct() {
parent::__construct('azure', 'Microsoft Azure', '5.3.3', 'azure.png');
}
}
}
} else {
updraft_try_include_file('methods/insufficient.php', 'include_once');
/**
* N.B. UpdraftPlus_BackupModule_insufficientphp extends UpdraftPlus_BackupModule
*/
class UpdraftPlus_BackupModule_azure extends UpdraftPlus_BackupModule_insufficientphp {
public function __construct() {
parent::__construct('azure', 'Microsoft Azure', '5.3.3', 'azure.png');
}
}
}

View File

@@ -0,0 +1,40 @@
<?php
if (!defined('UPDRAFTPLUS_DIR')) die('No direct access.');
if (version_compare(phpversion(), '5.3.3', '>=')) {
if (class_exists('UpdraftPlus_Addons_RemoteStorage_backblaze')) {
class UpdraftPlus_BackupModule_backblaze extends UpdraftPlus_Addons_RemoteStorage_backblaze {
public function __construct() {
parent::__construct('backblaze', 'Backblaze', true, true);
}
}
} else {
updraft_try_include_file('methods/addon-not-yet-present.php', 'include_once');
/**
* N.B. UpdraftPlus_BackupModule_AddonNotYetPresent extends UpdraftPlus_BackupModule
*/
class UpdraftPlus_BackupModule_backblaze extends UpdraftPlus_BackupModule_AddonNotYetPresent {
public function __construct() {
parent::__construct('backblaze', 'Backblaze', '5.3.3', 'backblaze.png');
}
}
}
} else {
updraft_try_include_file('methods/insufficient.php', 'include_once');
/**
* N.B. UpdraftPlus_BackupModule_insufficientphp extends UpdraftPlus_BackupModule
*/
class UpdraftPlus_BackupModule_backblaze extends UpdraftPlus_BackupModule_insufficientphp {
public function __construct() {
parent::__construct('backblaze', 'Backblaze', '5.3.3', 'backblaze.png');
}
}
}

View File

@@ -0,0 +1,867 @@
<?php
if (!defined('UPDRAFTPLUS_DIR')) die('No direct access allowed.');
abstract class UpdraftPlus_BackupModule {
private $_options;
private $_instance_id;
private $_storage;
/**
* Store options (within this class) for this remote storage module. There is also a parameter for saving to the permanent storage (i.e. database).
*
* @param array $options array of options to store
* @param Boolean $save whether or not to also save the options to the database
* @param null|String $instance_id optionally set the instance ID for this instance at the same time. This is required if you have not already set an instance ID with set_instance_id()
* @return void|Boolean If saving to DB, then the result of the DB save operation is returned.
*/
public function set_options($options, $save = false, $instance_id = null) {
$this->_options = $options;
// Remove any previously-stored storage object, because this is usually tied to the options
if (!empty($this->_storage)) unset($this->_storage);
if ($instance_id) $this->set_instance_id($instance_id);
if ($save) return $this->save_options();
}
/**
* Saves the current options to the database. This is a private function; external callers should use set_options().
*
* @throws Exception if trying to save options without indicating an instance_id, or if the remote storage module does not have the multi-option capability
*/
private function save_options() {
if (!$this->supports_feature('multi_options')) {
throw new Exception('save_options() can only be called on a storage method which supports multi_options (this module, '.$this->get_id().', does not)'); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error message to be escaped when caught and printed.
}
if (!$this->_instance_id) {
throw new Exception('save_options() requires an instance ID, but was called without setting one (either directly or via set_instance_id())');
}
$current_db_options = UpdraftPlus_Storage_Methods_Interface::update_remote_storage_options_format($this->get_id());
if (is_wp_error($current_db_options)) {
throw new Exception('save_options(): options fetch/update failed ('.$current_db_options->get_error_code().': '.$current_db_options->get_error_message().')'); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error message to be escaped when caught and printed.
}
$current_db_options['settings'][$this->_instance_id] = $this->_options;
return UpdraftPlus_Options::update_updraft_option('updraft_'.$this->get_id(), $current_db_options);
}
/**
* Retrieve default options for this remote storage module.
* This method would normally be over-ridden by the child.
*
* @return Array - an array of options
*/
public function get_default_options() {
return array();
}
/**
* Retrieve persistent class variables and/or methods (the ones that don't get changed during runtime) and transform them into a list of template properties
*
* @return Array an associative array keyed by names of the corresponding variables/methods, the keys might not exactly be the same with the name of the variables/methods
*/
protected function get_persistent_variables_and_methods() {
global $updraftplus;
return array(
'css_class' => 'updraftplusmethod',
'is_multi_options_feature_supported' => $this->supports_feature('multi_options'),
'is_config_templates_feature_supported' => $this->supports_feature('config_templates'),
'is_conditional_logic_feature_supported' => $this->supports_feature('conditional_logic'),
'is_multi_servers_feature_supported' => $this->supports_feature('multi_servers'),
'method_id' => $this->get_id(),
'_instance_id' => $this->_instance_id,
'method_display_name' => $updraftplus->backup_methods[$this->get_id()],
'admin_page_url' => UpdraftPlus_Options::admin_page_url(),
'storage_auth_nonce' =>wp_create_nonce('storage_auth_nonce'),
'input_select_folder_label' => __('Select existing folder', 'updraftplus'),
'input_confirm_label' => __('Confirm', 'updraftplus'),
'input_cancel_label' => __('Cancel', 'updraftplus'),
);
}
/**
* Get all persistent variables and methods across the modules (this could mean the child including its parent), also the necessary required HTML element attributes and texts which are unique to each child
* NOTE: Since this method would normally be over-ridden by the child, please sanitise all strings that are required to be shown as HTML content on the frontend side (i.e. wp_kses())
*
* @return Array an associative array keyed by names that describe themselves as they are
*/
public function get_template_properties() {
return array();
}
/**
* List all allowed HTML tags for content sanitisation
*
* @return Array an associatve array keyed by name of the allowed HTML tags
*/
protected function allowed_html_for_content_sanitisation() {
return array(
'a' => array(
'href' => array(),
'title' => array(),
'target' => array(),
),
'br' => array(),
'em' => array(),
'strong' => array(),
'p' => array(),
'div' => array(
'class' => array(),
),
'kbd' => array(),
);
}
/**
* Get partial templates associated to the corresponding backup module (remote storage object)
* N.B. This method would normally be over-ridden by the child.
*
* @return Array an associative array keyed by name of the partial templates
*/
public function get_partial_templates() {
return array();
}
/**
* Check whether options have been set up by the user, or not
* This method would normally be over-ridden by the child.
*
* @param Array $opts - the potential options
*
* @return Boolean
*/
public function options_exist($opts) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Unused parameter is present because the caller uses 1 argument and normally this method will be over-ridden by the child class.
return false;
}
/**
* Retrieve a list of supported features for this storage method
* This method should be over-ridden by methods supporting new
* features.
*
* Keys are strings, and values are booleans.
*
* Currently known features:
*
* - multi_options : indicates that the remote storage module
* can handle its options being in the Feb-2017 multi-options
* format. N.B. This only indicates options handling, not any
* other multi-destination options.
*
* - multi_servers : not implemented yet: indicates that the
* remote storage module can handle multiple servers at backup
* time. This should not be specified without multi_options.
* multi_options without multi_servers is fine - it will just
* cause only the first entry in the options array to be used.
*
* - config_templates : not implemented yet: indicates that
* the remote storage module can output its configuration in
* Handlebars format via the get_configuration_template() method.
*
* - conditional_logic : indicates that the remote storage module
* can handle predefined logics regarding how backups should be
* sent to the remote storage
*
* @return Array - an array of supported features (any features not
* mentioned are assumed to not be supported)
*/
public function get_supported_features() {
return array();
}
/**
* This method should only be called if the feature 'multi storage' is supported. In that case, it returns a template with information about the remote storage. The code below is a placeholder, and methods supporting the feature should always over-ride it.
*
* @return String - HTML template
*/
public function get_pre_configuration_template() {
return $this->get_id().": called, but not implemented in the child class (coding error)";
}
/**
* This method should only be called if the feature 'config templates' is supported. In that case, it returns a template with appropriate placeholders for specific settings. The code below is a placeholder, and methods supporting the feature should always over-ride it.
*
* @return String - HTML template
*/
public function get_configuration_template() {
return $this->get_id().": called, but not implemented in the child class (coding error)";
}
/**
* This method will set the stored storage object to that indicated
*
* @param Object $storage - the storage client
*/
public function set_storage($storage) {
$this->_storage = $storage;
}
/**
* This method will return the stored storage client
*
* @return Object - the stored remote storage client
*/
public function get_storage() {
if (!empty($this->_storage)) return $this->_storage;
}
/**
* Outputs id and name fields, as if currently within an input tag
*
* This assumes standardised options handling (i.e. that the options array is updraft_(method-id))
*
* @param Array|String $field - the field identifiers
* @param Boolean $return_instead_of_echo - tells the method if it should return the output or echo it to page
*/
public function output_settings_field_name_and_id($field, $return_instead_of_echo = false) {
$method_id = $this->get_id();
$instance_id = $this->supports_feature('config_templates') ? '{{instance_id}}' : $this->_instance_id;
$id = '';
$name = '';
if (is_array($field)) {
foreach ($field as $value) {
$id .= '_'.$value;
$name .= '['.$value.']';
}
} else {
$id = '_'.$field;
$name = '['.$field.']';
}
$output = "id=\"updraft_{$method_id}{$id}_{$instance_id}\" name=\"updraft_{$method_id}[settings][{$instance_id}]{$name}\" ";
if ($return_instead_of_echo) {
return $output;
} else {
echo wp_kses_post($output);
}
}
/**
* Get the CSS ID
*
* @param String $field - the field identifier to return a CSS ID for
*
* @return String
*/
public function get_css_id($field) {
$method_id = $this->get_id();
$instance_id = $this->supports_feature('config_templates') ? '{{instance_id}}' : $this->_instance_id;
return "updraft_{$method_id}_{$field}_{$instance_id}";
}
/**
* Get handlebarsjs template
* This deals with any boiler-plate, prior to calling config_print()
*
* @uses self::config_print()
* @uses self::get_configuration_template()
*
* return handlebarsjs template or html
*/
public function get_template() {
ob_start();
// Allow methods to not use this hidden field, if they do not output any settings (to prevent their saved settings being over-written by just this hidden field)
if ($this->print_shared_settings_fields()) {
?><tr class="<?php echo esc_attr($this->get_css_classes()); ?>"><input type="hidden" name="updraft_<?php echo esc_attr($this->get_id());?>[version]" value="1"></tr><?php
}
if ($this->supports_feature('config_templates')) {
?>
{{#if first_instance}}
<?php
$this->get_pre_configuration_template();
if ($this->supports_feature('multi_storage')) {
do_action('updraftplus_config_print_add_multi_storage', $this->get_id(), $this);
}
?>
{{/if}}
<?php
do_action('updraftplus_config_print_before_storage', $this->get_id(), $this);
if ('updraftvault' !== $this->get_id()) do_action('updraftplus_config_print_add_conditional_logic', $this->get_id(), $this);
if ($this->supports_feature('multi_storage')) {
do_action('updraftplus_config_print_add_instance_label', $this->get_id(), $this);
}
$template = ob_get_clean();
$template .= $this->get_configuration_template();
if ('updraftvault' === $this->get_id()) {
ob_start();
do_action('updraftplus_config_print_add_conditional_logic', $this->get_id(), $this);
$template .= ob_get_clean();
}
} else {
do_action('updraftplus_config_print_before_storage', $this->get_id(), $this);
do_action('updraftplus_config_print_add_conditional_logic', $this->get_id(), $this);
// N.B. These are mutually exclusive: config_print() is not used if config_templates is supported. So, even during transition, the UpdraftPlus_BackupModule instance only needs to support one of the two, not both.
$this->config_print();
$template = ob_get_clean();
}
return $template;
}
/**
* Modifies handerbar template options. Other child class can extend it.
*
* @param array $opts
* @return Array - Modified handerbar template options
*/
public function transform_options_for_template($opts) {
return $opts;
}
/**
* Gives settings keys which values should not passed to handlebarsjs context.
* The settings stored in UD in the database sometimes also include internal information that it would be best not to send to the front-end (so that it can't be stolen by a man-in-the-middle attacker)
*
* @return Array - Settings array keys which should be filtered
*/
public function filter_frontend_settings_keys() {
return array();
}
/**
* Over-ride this to allow methods to not use the hidden version field, if they do not output any settings (to prevent their saved settings being over-written by just this hidden field
*
* @return [boolean] - return true to output the version field or false to not output the field
*/
public function print_shared_settings_fields() {
return true;
}
/**
* Prints out the configuration section for a particular module. This is now (Sep 2017) considered deprecated; things are being ported over to get_configuration_template(), indicated via the feature 'config_templates'.
*/
public function config_print() {
echo esc_html($this->get_id()).": module neither declares config_templates support, nor has a config_print() method (coding bug)";
}
/**
* Supplies the list of keys for options to be saved in the backup job.
*
* @return Array
*/
public function get_credentials() {
$keys = array('updraft_ssl_disableverify', 'updraft_ssl_nossl', 'updraft_ssl_useservercerts');
if (!$this->supports_feature('multi_servers')) $keys[] = 'updraft_'.$this->get_id();
return $keys;
}
/**
* Returns a space-separated list of CSS classes suitable for rows in the configuration section
*
* @param Boolean $include_instance - a boolean value to indicate if we want to include the instance_id in the css class, we may not want to include the instance if it's for a UI element that we don't want to be removed along with other UI elements that do include a instance id.
*
* @returns String - the list of CSS classes
*/
public function get_css_classes($include_instance = true) {
$classes = 'updraftplusmethod '.$this->get_id();
if (!$include_instance) return $classes;
if ($this->supports_feature('multi_options')) {
if ($this->supports_feature('config_templates')) {
$classes .= ' '.$this->get_id().'-{{instance_id}}';
} else {
$classes .= ' '.$this->get_id().'-'.$this->_instance_id;
}
}
return $classes;
}
/**
*
* Returns HTML for a row for a test button
*
* @param String $title - The text to be used in the button
*
* @returns String - The HTML to be inserted into the settings page
*/
protected function get_test_button_html($title) {
ob_start();
$instance_id = $this->supports_feature('config_templates') ? '{{instance_id}}' : $this->_instance_id;
?>
<tr class="<?php echo esc_attr($this->get_css_classes()); ?>">
<th></th>
<td><p><button id="updraft-<?php echo esc_attr($this->get_id());?>-test-<?php echo esc_attr($instance_id);?>" type="button" class="button-primary updraft-test-button updraft-<?php echo esc_attr($this->get_id());?>-test" data-instance_id="<?php echo esc_attr($instance_id);?>" data-method="<?php echo esc_attr($this->get_id());?>" data-method_label="<?php echo esc_attr($title);?>"><?php echo esc_html(sprintf(__('Test %s Settings', 'updraftplus'), $title));?></button></p></td>
</tr>
<?php
return ob_get_clean();
}
/**
* Get the backup method identifier for this class
*
* @return String - the identifier
*/
public function get_id() {
$class = get_class($this);
// UpdraftPlus_BackupModule_
return substr($class, 25);
}
/**
* Get the backup method description for this class
*
* @return String - the identifier
*/
public function get_description() {
global $updraftplus;
$methods = $updraftplus->backup_methods;
$id = $this->get_id();
return isset($methods[$id]) ? $methods[$id] : $id;
}
/**
* Sets the instance ID - for supporting multi_options
*
* @param String $instance_id - the instance ID
*/
public function set_instance_id($instance_id) {
$this->_instance_id = $instance_id;
}
/**
* Sets the instance ID - for supporting multi_options
*
* @returns String the instance ID
*/
public function get_instance_id() {
return $this->_instance_id;
}
/**
* Check whether this storage module supports a mentioned feature
*
* @param String $feature - the feature concerned
*
* @returns Boolean
*/
public function supports_feature($feature) {
return in_array($feature, $this->get_supported_features());
}
/**
* Retrieve options for this remote storage module.
* N.B. The option name instance_id is reserved and should not be used.
*
* @uses get_default_options
*
* @return Array - array of options. This will include default values for any options not set.
*/
public function get_options() {
global $updraftplus;
$supports_multi_options = $this->supports_feature('multi_options');
if (is_array($this->_options)) {
// First, prioritise any options that were explicitly set. This is the eventual goal for all storage modules.
$options = $this->_options;
} elseif (is_callable(array($this, 'get_opts'))) {
// Next, get any options available via a legacy / over-ride method.
if ($supports_multi_options) {
// This is forbidden, because get_opts() is legacy and is for methods that do not support multi-options. Supporting multi-options leads to the array format being updated, which will then break get_opts().
die('Fatal error: method '.esc_html($this->get_id()).' both supports multi_options and provides a get_opts method');
}
$options = $this->get_opts();
} else {
// Next, look for job options (which in turn, falls back to saved settings if no job options were set)
$options = $updraftplus->get_job_option('updraft_'.$this->get_id());
if (!is_array($options)) $options = array();
if ($supports_multi_options) {
if (!isset($options['version'])) {
$options_full = UpdraftPlus_Storage_Methods_Interface::update_remote_storage_options_format($this->get_id());
if (is_wp_error($options_full)) {
$updraftplus->log("Options retrieval failure: ".$options_full->get_error_code().": ".$options_full->get_error_message()." (".json_encode($options_full->get_error_data()).")");
return array();
}
} else {
$options_full = $options;
}
// UpdraftPlus_BackupModule::get_options() is for getting the current instance's options. So, this branch (going via the job option) is a legacy route, and hence we just give back the first one. The non-legacy route is to call the set_options() method externally.
$options = (isset($options_full['settings']) && is_array($options_full['settings'])) ? reset($options_full['settings']) : false;
if (false === $options) {
$updraftplus->log("Options retrieval failure (no options set)");
return array();
}
$instance_id = key($options_full['settings']);
$this->set_options($options, false, $instance_id);
}
}
$options = apply_filters(
'updraftplus_backupmodule_get_options',
wp_parse_args($options, $this->get_default_options()),
$this
);
return $options;
}
/**
* Set job data that is local to this storage instance
* (i.e. the key does not need to be unique across instances)
*
* @uses UpdraftPlus::jobdata_set()
*
* @param String $key - the key for the job data
* @param Mixed $value - the data to be stored
*/
public function jobdata_set($key, $value) {
$instance_key = $this->get_id().'-'.($this->_instance_id ? $this->_instance_id : 'no_instance');
global $updraftplus;
$instance_data = $updraftplus->jobdata_get($instance_key);
if (!is_array($instance_data)) $instance_data = array();
$instance_data[$key] = $value;
$updraftplus->jobdata_set($instance_key, $instance_data);
}
/**
* Get job data that is local to this storage instance
* (i.e. the key does not need to be unique across instances)
*
* @uses UpdraftPlus::jobdata_get()
*
* @param String $key - the key for the job data
* @param Mixed $default - the default to return if nothing was set
* @param String|Null $legacy_key - the previous name of the key, prior to instance-specific job data (so that upgrades across versions whilst a backup is in progress can still find its data). In future, support for this can be removed.
*/
public function jobdata_get($key, $default = null, $legacy_key = null) {
$instance_key = $this->get_id().'-'.($this->_instance_id ? $this->_instance_id : 'no_instance');
global $updraftplus;
$instance_data = $updraftplus->jobdata_get($instance_key);
if (is_array($instance_data) && isset($instance_data[$key])) return $instance_data[$key];
return is_string($legacy_key) ? $updraftplus->jobdata_get($legacy_key, $default) : $default;
}
/**
* Delete job data that is local to this storage instance
* (i.e. the key does not need to be unique across instances)
*
* @uses UpdraftPlus::jobdata_set()
*
* @param String $key - the key for the job data
* @param String|Null $legacy_key - the previous name of the key, prior to instance-specific job data (so that upgrades across versions whilst a backup is in progress can still find its data)
*/
public function jobdata_delete($key, $legacy_key = null) {
$instance_key = $this->get_id().'-'.($this->_instance_id ? $this->_instance_id : 'no_instance');
global $updraftplus;
$instance_data = $updraftplus->jobdata_get($instance_key);
if (is_array($instance_data) && isset($instance_data[$key])) {
unset($instance_data[$key]);
$updraftplus->jobdata_set($instance_key, $instance_data);
}
if (is_string($legacy_key)) $updraftplus->jobdata_delete($legacy_key);
}
/**
* This method will either return or echo the constructed auth link for the remote storage method
*
* @param Boolean $echo_instead_of_return - a boolean to indicate if the authentication link should be echo or returned
* @param Boolean $template_instead_of_notice - a boolean to indicate if the authentication link is for a template or a notice
* @return Void|String - returns a string or nothing depending on the parameters
*/
public function get_authentication_link($echo_instead_of_return = true, $template_instead_of_notice = true) {
if (!$echo_instead_of_return) {
ob_start();
}
$account_warning = '';
$description = $this->get_description();
if ($this->output_account_warning()) {
$account_warning = __('Ensure you are logged into the correct account before continuing.', 'updraftplus');
}
if ($template_instead_of_notice) {
$instance_id = "{{instance_id}}";
$text = sprintf(__("<strong>After</strong> you have saved your settings (by clicking 'Save Changes' below), then come back here and follow this link to complete authentication with %s.", 'updraftplus'), $description);
} else {
$instance_id = $this->get_instance_id();
$text = sprintf(__('Follow this link to authorize access to your %s account (you will not be able to backup to %s without it).', 'updraftplus'), $description, $description);
}
echo esc_html($account_warning) . ' ' . wp_kses_post($this->build_authentication_link($instance_id, $text));
if (!$echo_instead_of_return) {
return ob_get_clean();
}
}
/**
* This function will build and return the authentication link
*
* @param String $instance_id - the instance id
* @param String $text - the link text
*
* @return String - the authentication link
*/
public function build_authentication_link($instance_id, $text) {
$id = $this->get_id();
if (!preg_match('/^[-A-Z0-9]+$/i', $instance_id)) return '';
return '<a class="updraft_authlink" href="'.UpdraftPlus_Options::admin_page_url().'?&action=updraftmethod-'.$id.'-auth&page=updraftplus&updraftplus_'.$id.'auth=doit&nonce='.wp_create_nonce('storage_auth_nonce').'&updraftplus_instance='.$instance_id.'" data-instance_id="'.$instance_id.'" data-remote_method="'.$id.'">'.$text.'</a>';
}
/**
* Check the authentication is valid before proceeding to call the authentication method
*/
public function action_authenticate_storage() {
if (isset($_GET['updraftplus_'.$this->get_id().'auth']) && 'doit' == $_GET['updraftplus_'.$this->get_id().'auth'] && !empty($_GET['updraftplus_instance']) && preg_match('/^[-A-Z0-9]+$/i', $_GET['updraftplus_instance']) && isset($_GET['nonce']) && wp_verify_nonce($_GET['nonce'], 'storage_auth_nonce')) {
$this->authenticate_storage((string) $_GET['updraftplus_instance']);
}
}
/**
* Authenticate the remote storage and save settings
*
* @param String $instance_id - The remote storage instance id
*/
public function authenticate_storage($instance_id) {
if (method_exists($this, 'do_authenticate_storage')) {
$this->do_authenticate_storage($instance_id);
} else {
error_log($this->get_id().": module does not have an authenticate storage method (coding bug)");
}
}
/**
* This method will either return or echo the constructed deauth link for the remote storage method
*
* @param Boolean $echo_instead_of_return - a boolean to indicate if the deauthentication link should be echo or returned
* @return Void|String - returns a string or nothing depending on the parameters
*/
public function get_deauthentication_link($echo_instead_of_return = true) {
if (!$echo_instead_of_return) {
ob_start();
}
$id = $this->get_id();
$description = $this->get_description();
echo ' <a class="updraft_deauthlink" href="'.esc_url(UpdraftPlus_Options::admin_page_url().'?action=updraftmethod-'.$id.'-auth&page=updraftplus&updraftplus_'.$id.'auth=deauth&nonce='.wp_create_nonce($id.'_deauth_nonce').'&updraftplus_instance={{instance_id}}').'" data-instance_id="{{instance_id}}" data-remote_method="'.esc_attr($id).'">'.esc_html(sprintf(__("Follow this link to remove these settings for %s.", 'updraftplus'), $description)).'</a>';
if (!$echo_instead_of_return) {
return ob_get_clean();
}
}
/**
* Check the deauthentication is valid before proceeding to call the deauthentication method
*/
public function action_deauthenticate_storage() {
if (isset($_GET['updraftplus_'.$this->get_id().'auth']) && 'deauth' == $_GET['updraftplus_'.$this->get_id().'auth'] && !empty($_GET['nonce']) && !empty($_GET['updraftplus_instance']) && preg_match('/^[-A-Z0-9]+$/i', $_GET['updraftplus_instance']) && wp_verify_nonce($_GET['nonce'], $this->get_id().'_deauth_nonce')) {
$this->deauthenticate_storage($_GET['updraftplus_instance']);
}
}
/**
* Deauthenticate the remote storage and remove the saved settings
*
* @param String $instance_id - The remote storage instance id
*/
public function deauthenticate_storage($instance_id) {
if (method_exists($this, 'do_deauthenticate_storage')) {
$this->do_deauthenticate_storage($instance_id);
}
$opts = $this->get_default_options();
$this->set_options($opts, true, $instance_id);
}
/**
* Get the manual authorisation template
*
* @return String - the template
*/
public function get_manual_authorisation_template() {
$id = $this->get_id();
$description = $this->get_description();
$template = "<div id='updraftplus_manual_authorisation_template_{$id}'>";
$template .= "<strong>".sprintf(__('%s authentication:', 'updraftplus'), $description)."</strong>";
$template .= "<p>".sprintf(__('If you are having problems authenticating with %s you can manually authorize here.', 'updraftplus'), $description)."</p>";
$template .= "<p>".__('To complete manual authentication, at the orange UpdraftPlus authentication screen select the "Having problems authenticating?" link, then copy and paste the code given here.', 'updraftplus')."</p>";
$template .= "<label for='updraftplus_manual_authentication_data_{$id}'>".sprintf(__('%s authentication code:', 'updraftplus'), $description)."</label> <input type='text' id='updraftplus_manual_authentication_data_{$id}' name='updraftplus_manual_authentication_data_{$id}'>";
$template .= "<p id='updraftplus_manual_authentication_error_{$id}'></p>";
$template .= "<button type='button' data-method='{$id}' class='button button-primary' id='updraftplus_manual_authorisation_submit_{$id}'>".__('Complete manual authentication', 'updraftplus')."</button>";
$template .= '<span class="updraftplus_spinner spinner">' . __('Processing', 'updraftplus') . '...</span>';
$template .= "</div>";
return $template;
}
/**
* This will call the remote storage methods complete authentication function
*
* @param string $state - the remote storage authentication state
* @param string $code - the remote storage authentication code
*
* @return String - returns a string response
*/
public function complete_authentication($state, $code) {
if (method_exists($this, 'do_complete_authentication')) {
return $this->do_complete_authentication($state, $code, true);
} else {
$message = $this->get_id().": module does not have an complete authentication method (coding bug)";
error_log($message);
return $message;
}
}
/**
* Over-ride this to allow methods to output extra information about using the correct account for OAuth storage methods
*
* @return Boolean - return false so that no extra information is output
*/
public function output_account_warning() {
return false;
}
/**
* This function is a wrapper and will call $updraftplus->log(), the backup modules should use this so we can add information to the log lines to do with the remote storage and instance settings.
*
* @param string $line - the log line
* @param string $level - the log level: notice, warning, error. If suffixed with a hyphen and a destination, then the default destination is changed too.
* @param boolean $uniq_id - each of these will only be logged once
* @param boolean $skip_dblog - if true, then do not write to the database
*
* @return void
*/
public function log($line, $level = 'notice', $uniq_id = false, $skip_dblog = false) {
global $updraftplus;
$prefix = $this->get_storage_label();
$updraftplus->log("$prefix: $line", $level, $uniq_id, $skip_dblog);
}
/**
* Log appropriate messages for a multi-delete response.
*
* @param Array $files
* @param Array $responses - using the same keys as $files
*
* @return Boolean - true if no errors were found, otherwise false
*/
protected function process_multi_delete_responses($files, $responses) {
global $updraftplus;
$ret = true;
if (is_array($responses)) {
foreach ($responses as $key => $response) {
if ('success' == $response) {
$updraftplus->log("$files[$key]: Delete succeeded");
} elseif (is_array($response)) {
$ret = false;
if (isset($response['error']) && isset($response['error']['code']) && isset($response['error']['message'])) {
$updraftplus->log("Delete failed for file: $files[$key] with error code: ".$response['error']['code']." message: ".$response['error']['message']);
} else {
$updraftplus->log("Delete failed for file: $files[$key]");
}
}
}
} elseif (!$responses) {
$ret = false;
$updraftplus->log("Delete failed for files: ".implode($files));
}
return $ret;
}
/**
* This function will build and return the remote storage instance label
*
* @return String - the remote storage instance label
*/
private function get_storage_label() {
$opts = $this->get_options();
$label = isset($opts['instance_label']) ? $opts['instance_label'] : '';
$description = $this->get_description();
if (!empty($label)) {
$prefix = (false !== strpos($label, $description)) ? $label : "$description: $label";
} else {
$prefix = $description;
}
return $prefix;
}
/**
* This method will output any needed js for the JSTree.
*
* @return void
*/
public function admin_footer_jstree() {
static $script_output = array(); // Static array to store script output status.
$id = $this->get_id();
// Check if the script has already been output for this ID.
if (!isset($script_output[$id])) {
wp_add_inline_script('updraft-admin-common', "var js_tree_".esc_js($id)." = new updraft_js_tree('".esc_js($id)."'); js_tree_".esc_js($id).".init();", 'after');
// Mark the script as output for this ID.
$script_output[$id] = true;
}
}
}

View File

@@ -0,0 +1,303 @@
<?php
if (!defined('UPDRAFTPLUS_DIR')) die('No direct access allowed.');
// SDK uses namespacing - requires PHP 5.3 (actually the SDK states its requirements as 5.3.3)
use OpenCloud\Rackspace;
// New SDK - https://github.com/rackspace/php-opencloud and http://docs.rackspace.com/sdks/guide/content/php.html
// Uploading: https://github.com/rackspace/php-opencloud/blob/master/docs/userguide/ObjectStore/Storage/Object.md
updraft_try_include_file('methods/openstack-base.php', 'require_once');
class UpdraftPlus_BackupModule_cloudfiles_opencloudsdk extends UpdraftPlus_BackupModule_openstack_base {
public function __construct() {
parent::__construct('cloudfiles', 'Cloud Files', 'Rackspace Cloud Files', '/images/rackspacecloud-logo.png');
}
public function get_client() {
return $this->client;
}
public function get_openstack_service($opts, $useservercerts = false, $disablesslverify = null) {
$user = $opts['user'];
$apikey = $opts['apikey'];
$authurl = $opts['authurl'];
$region = (!empty($opts['region'])) ? $opts['region'] : null;
updraft_try_include_file('vendor/autoload.php', 'include_once');
// The new authentication APIs don't match the values we were storing before
$new_authurl = ('https://lon.auth.api.rackspacecloud.com' == $authurl || 'uk' == $authurl) ? Rackspace::UK_IDENTITY_ENDPOINT : Rackspace::US_IDENTITY_ENDPOINT;
if (null === $disablesslverify) $disablesslverify = UpdraftPlus_Options::get_updraft_option('updraft_ssl_disableverify');
if (empty($user) || empty($apikey)) throw new Exception(__('Authorisation failed (check your credentials)', 'updraftplus')); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error message to be escaped when caught and printed.
$this->log("authentication URL: ".$new_authurl);
$client = new Rackspace($new_authurl, array(
'username' => $user,
'apiKey' => $apikey
));
$this->client = $client;
if ($disablesslverify) {
$client->setSslVerification(false);
} else {
if ($useservercerts) {
$client->setConfig(array($client::SSL_CERT_AUTHORITY, 'system'));
} else {
$client->setSslVerification(UPDRAFTPLUS_DIR.'/includes/cacert.pem', true, 2);
}
}
return $client->objectStoreService('cloudFiles', $region);
}
/**
* This method overrides the parent method and lists the supported features of this remote storage option.
*
* @return Array - an array of supported features (any features not
* mentioned are assumed to not be supported)
*/
public function get_supported_features() {
// This options format is handled via only accessing options via $this->get_options()
return array('multi_options', 'config_templates', 'multi_storage', 'conditional_logic');
}
/**
* Retrieve default options for this remote storage module.
*
* @return Array - an array of options
*/
public function get_default_options() {
return array(
'user' => '',
'authurl' => 'https://auth.api.rackspacecloud.com',
'apikey' => '',
'path' => '',
'region' => null
);
}
/**
* This gives the partial template string to the settings page for the CloudFiles settings.
*
* @return String - the partial template, ready for substitutions to be carried out
*/
public function get_configuration_middlesection_template() {
global $updraftplus;
$classes = $this->get_css_classes();
$template_str = '
<tr class="'.$classes.'">
<th title="'.__('Accounts created at rackspacecloud.com are US accounts; accounts created at rackspace.co.uk are UK accounts.', 'updraftplus').'">'.__('US or UK-based Rackspace Account', 'updraftplus').':</th>
<td>
<select data-updraft_settings_test="authurl" '.$this->output_settings_field_name_and_id('authurl', true).' title="'.__('Accounts created at rackspacecloud.com are US-accounts; accounts created at rackspace.co.uk are UK-based', 'updraftplus').'">
<option {{#ifeq "https://auth.api.rackspacecloud.com" authurl}}selected="selected"{{/ifeq}} value="https://auth.api.rackspacecloud.com">'.__('US (default)', 'updraftplus').'</option>
<option {{#ifeq "https://lon.auth.api.rackspacecloud.com" authurl}}selected="selected"{{/ifeq}} value="https://lon.auth.api.rackspacecloud.com">'.__('UK', 'updraftplus').'</option>
</select>
</td>
</tr>
<tr class="'.$classes.'">
<th>'.__('Cloud Files Storage Region', 'updraftplus').':</th>
<td>
<select data-updraft_settings_test="region" '.$this->output_settings_field_name_and_id('region', true).'>
{{#each regions as |desc reg|}}
<option {{#ifeq ../region reg}}selected="selected"{{/ifeq}} value="{{reg}}">{{desc}}</option>
{{/each}}
</select>
</td>
</tr>
<tr class="'.$classes.'">
<th>'.__('Cloud Files Username', 'updraftplus').':</th>
<td><input data-updraft_settings_test="user" type="text" autocomplete="off" class="updraft_input--wide" '.$this->output_settings_field_name_and_id('user', true).' value="{{user}}" />
<div style="clear:both;">
'.apply_filters('updraft_cloudfiles_apikeysetting', '<a href="'.$updraftplus->get_url('premium').'" target="_blank"><em>'.__('To create a new Rackspace API sub-user and API key that has access only to this Rackspace container, use Premium.', 'updraftplus').'</em></a>').'
</div>
</td>
</tr>
<tr class="'.$classes.'">
<th>'.__('Cloud Files API Key', 'updraftplus').':</th>
<td><input data-updraft_settings_test="apikey" type="'.apply_filters('updraftplus_admin_secret_field_type', 'password').'" autocomplete="off" class="updraft_input--wide" '.$this->output_settings_field_name_and_id('apikey', true).' value="{{apikey}}" />
</td>
</tr>
<tr class="'.$classes.'">
<th>'.apply_filters('updraftplus_cloudfiles_location_description', __('Cloud Files Container', 'updraftplus')).':</th>
<td><input data-updraft_settings_test="path" type="text" class="updraft_input--wide" '.$this->output_settings_field_name_and_id('path', true).' value="{{path}}" /></td>
</tr>';
return $template_str;
}
/**
* Modifies handerbar template options
*
* @param array $opts handerbar template options
* @return Array - Modified handerbar template options
*/
public function transform_options_for_template($opts) {
$opts['regions'] = array(
'DFW' => __('Dallas (DFW) (default)', 'updraftplus'),
'SYD' => __('Sydney (SYD)', 'updraftplus'),
'ORD' => __('Chicago (ORD)', 'updraftplus'),
'IAD' => __('Northern Virginia (IAD)', 'updraftplus'),
'HKG' => __('Hong Kong (HKG)', 'updraftplus'),
'LON' => __('London (LON)', 'updraftplus')
);
$opts['region'] = (empty($opts['region'])) ? 'DFW' : $opts['region'];
if (isset($opts['apikey'])) {
$opts['apikey'] = trim($opts['apikey']);
}
$opts['authurl'] = !empty($opts['authurl']) ? $opts['authurl'] : '';
return $opts;
}
/**
* Perform a test of user-supplied credentials, and echo the result
*
* @param Array $posted_settings - settings to test
*/
public function credentials_test($posted_settings) {
if (empty($posted_settings['apikey'])) {
echo esc_html(sprintf(__("Failure: No %s was given.", 'updraftplus'), __('API key', 'updraftplus')));
return;
}
if (empty($posted_settings['user'])) {
echo esc_html(sprintf(__("Failure: No %s was given.", 'updraftplus'), __('Username', 'updraftplus')));
return;
}
$opts = array(
'user' => $posted_settings['user'],
'apikey' => $posted_settings['apikey'],
'authurl' => $posted_settings['authurl'],
'region' => (empty($posted_settings['region'])) ? null : $posted_settings['region']
);
$this->credentials_test_go($opts, $posted_settings['path'], $posted_settings['useservercerts'], $posted_settings['disableverify']);
}
/**
* Check whether options have been set up by the user, or not
*
* @param Array $opts - the potential options
*
* @return Boolean
*/
public function options_exist($opts) {
if (is_array($opts) && isset($opts['user']) && '' != $opts['user'] && !empty($opts['apikey'])) return true;
return false;
}
/**
* Get the pre configuration template
*
* @return String - the template
*/
public function get_pre_configuration_template() {
?>
<tr class="{{get_template_css_classes false}} {{method_id}}_pre_config_container">
<td colspan="2">
{{#if storage_image_url}}
<img alt="{{storage_long_description}}" src="{{storage_image_url}}">
{{/if}}
<br>
{{{mb_substr_existence_label}}}
{{{json_last_error_existence_label}}}
{{{curl_existence_label}}}
<br>
<p>{{{rackspace_text_description}}} <a href="{{{faq_link_url}}}" target="_blank">{{faq_link_text}}</a></p>
</td>
</tr>
<?php
}
/**
* Get the configuration template
*
* @return String - the template, ready for substitutions to be carried out
*/
public function get_configuration_template() {
ob_start();
?>
<tr class="{{get_template_css_classes true}}">
<th title="{{input_account_title}}">{{input_account_label}}:</th>
<td>
<select data-updraft_settings_test="authurl" class="udc-wd-600" id="{{get_template_input_attribute_value "id" "authurl"}}" name="{{get_template_input_attribute_value "name" "authurl"}}" title="{{input_account_title}}">
{{#each input_account_option_labels}}
<option {{#ifeq ../authurl @key}}selected="selected"{{/ifeq}} value="{{@key}}">{{this}}</option>
{{/each}}
</select>
</td>
</tr>
<tr class="{{get_template_css_classes true}}">
<th>{{input_region_label}}:</th>
<td>
<select data-updraft_settings_test="region" class="udc-wd-600" id="{{get_template_input_attribute_value "id" "region"}}" name="{{get_template_input_attribute_value "name" "region"}}">
{{#each regions as |desc reg|}}
<option {{#ifeq ../region reg}}selected="selected"{{/ifeq}} value="{{reg}}">{{desc}}</option>
{{/each}}
</select>
</td>
</tr>
<tr class="{{get_template_css_classes true}}">
<th>{{input_username_label}}:</th>
<td><input data-updraft_settings_test="user" type="text" autocomplete="off" class="updraft_input--wide udc-wd-600" id="{{get_template_input_attribute_value "id" "user"}}" name="{{get_template_input_attribute_value "name" "user"}}" value="{{user}}" />
<div style="clear:both;">
{{{input_username_title}}}
</div>
</td>
</tr>
<tr class="{{get_template_css_classes true}}">
<th>{{input_apikey_label}}:</th>
<td><input data-updraft_settings_test="apikey" type="{{input_apikey_type}}" autocomplete="off" class="updraft_input--wide udc-wd-600" id="{{get_template_input_attribute_value "id" "apikey"}}" name="{{get_template_input_attribute_value "name" "apikey"}}" value="{{apikey}}" />
</td>
</tr>
<tr class="{{get_template_css_classes true}}">
<th>{{input_container_label}}:</th>
<td><input data-updraft_settings_test="path" type="text" class="updraft_input--wide udc-wd-600" id="{{get_template_input_attribute_value "id" "path"}}" name="{{get_template_input_attribute_value "name" "path"}}" value="{{path}}" /></td>
</tr>';
{{{get_template_test_button_html "Rackspace Cloud Files"}}}
<?php
return ob_get_clean();
}
/**
* Retrieve a list of template properties by taking all the persistent variables and methods of the parent class and combining them with the ones that are unique to this module, also the necessary HTML element attributes and texts which are also unique only to this backup module
* NOTE: Please sanitise all strings that are required to be shown as HTML content on the frontend side (i.e. wp_kses()), or any other technique to prevent XSS attacks that could come via WP hooks
*
* @return Array an associative array keyed by names that describe themselves as they are
*/
public function get_template_properties() {
global $updraftplus, $updraftplus_admin;
$properties = array(
'storage_image_url' => !empty($this->img_url) ? UPDRAFTPLUS_URL.$this->img_url : '',
'storage_long_description' => $this->long_desc,
'mb_substr_existence_label' => !apply_filters('updraftplus_openstack_mbsubstr_exists', function_exists('mb_substr')) ? wp_kses($updraftplus_admin->show_double_warning('<strong>'.__('Warning', 'updraftplus').':</strong> '.sprintf(__('Your web server\'s PHP installation does not include a required module (%s).', 'updraftplus'), 'mbstring').' '.__('Please contact your web hosting provider\'s support.', 'updraftplus').' '.sprintf(__("UpdraftPlus's %s module <strong>requires</strong> %s.", 'updraftplus'), $this->desc, 'mbstring').' '.__('Please do not file any support requests; there is no alternative.', 'updraftplus'), $this->method, false), $this->allowed_html_for_content_sanitisation()) : '',
'json_last_error_existence_label' => !apply_filters('updraftplus_rackspace_jsonlasterror_exists', function_exists('json_last_error')) ? wp_kses($updraftplus_admin->show_double_warning('<strong>'.__('Warning', 'updraftplus').':</strong> '.sprintf(__('Your web server\'s PHP installation does not include a required module (%s).', 'updraftplus'), 'json').' '.__('Please contact your web hosting provider\'s support.', 'updraftplus').' '.sprintf(__("UpdraftPlus's %s module <strong>requires</strong> %s.", 'updraftplus'), 'Cloud Files', 'json').' '.__('Please do not file any support requests; there is no alternative.', 'updraftplus'), 'cloudfiles', false), $this->allowed_html_for_content_sanitisation()) : '',
'curl_existence_label' => wp_kses($updraftplus_admin->curl_check($this->long_desc, false, $this->method.' hidden-in-updraftcentral', false), $this->allowed_html_for_content_sanitisation()),
'rackspace_text_description' => wp_kses(sprintf(__('Get your API key <a href="%s" target="_blank">from your Rackspace Cloud console</a> (<a href="%s" target="_blank">read instructions here</a>), then pick a container name to use for storage.', 'updraftplus'), 'https://mycloud.rackspace.com/', 'https://docs.rackspace.com/support/how-to/set-up-an-api-key-cloud-office-control-panel').' '.__('This container will be created for you if it does not already exist.', 'updraftplus'), $this->allowed_html_for_content_sanitisation()),
'faq_link_text' => __('Also, you should read this important FAQ.', 'updraftplus'),
'faq_link_url' => esc_url(apply_filters("updraftplus_com_link", "https://teamupdraft.com/documentation/updraftplus/topics/cloud-storage/rackspace/there-are-extra-files-in-my-rackspace-cloud-files-container?utm_source=udp-plugin&utm_medium=referral&utm_campaign=paac&utm_content=read-important-faq&utm_creative_format=text")),
'input_account_label' => __('US or UK-based Rackspace Account', 'updraftplus'),
'input_account_title' => __('Accounts created at rackspacecloud.com are US accounts; accounts created at rackspace.co.uk are UK accounts.', 'updraftplus'),
'input_account_option_labels' => array(
'https://auth.api.rackspacecloud.com' => __('US (default)', 'updraftplus'),
'https://lon.auth.api.rackspacecloud.com' => __('UK', 'updraftplus'),
),
'input_region_label' => __('Cloud Files Storage Region', 'updraftplus'),
'input_username_label' => __('Cloud Files Username', 'updraftplus'),
'input_username_title' => wp_kses(apply_filters('updraft_cloudfiles_apikeysetting', '<a href="'.$updraftplus->get_url('premium_rackspace').'" target="_blank">'.__('To create a new Rackspace API sub-user and API key that has access only to this Rackspace container, use Premium.', 'updraftplus').'</a>'), $this->allowed_html_for_content_sanitisation()),
'input_apikey_label' => __('Cloud Files API Key', 'updraftplus'),
'input_apikey_type' => apply_filters('updraftplus_admin_secret_field_type', 'password'),
'input_container_label' => wp_kses(apply_filters('updraftplus_cloudfiles_location_description', __('Cloud Files Container', 'updraftplus')), $this->allowed_html_for_content_sanitisation()),
'input_test_label' => sprintf(__('Test %s Settings', 'updraftplus'), $updraftplus->backup_methods[$this->get_id()]),
);
return wp_parse_args($properties, $this->get_persistent_variables_and_methods());
}
}

View File

@@ -0,0 +1,612 @@
<?php
if (!defined('UPDRAFTPLUS_DIR')) die('No direct access.');
/**
* Converted to job_options: yes
* Converted to array options: yes
* Migration code for "new"-style options removed: Feb 2017 (created: Dec 2013)
*/
if (!class_exists('UpdraftPlus_BackupModule')) updraft_try_include_file('methods/backup-module.php', 'require_once');
/**
* Old SDK
*/
class UpdraftPlus_BackupModule_cloudfiles_oldsdk extends UpdraftPlus_BackupModule {
/**
* This function does not catch any exceptions - that should be done by the caller
*
* @param String $user
* @param String $apikey
* @param String $authurl
* @param Boolean $useservercerts
* @return Array
*/
private function getCF($user, $apikey, $authurl, $useservercerts = false) {
$storage = $this->get_storage();
if (!empty($storage)) return $storage;
if (!class_exists('UpdraftPlus_CF_Authentication')) updraft_try_include_file('includes/cloudfiles/cloudfiles.php', 'include_once');
if (!defined('UPDRAFTPLUS_SSL_DISABLEVERIFY')) define('UPDRAFTPLUS_SSL_DISABLEVERIFY', UpdraftPlus_Options::get_updraft_option('updraft_ssl_disableverify'));
$auth = new UpdraftPlus_CF_Authentication($user, trim($apikey), null, $authurl);
$this->log("authentication URL: $authurl");
$auth->authenticate();
$storage = new UpdraftPlus_CF_Connection($auth);
if (!$useservercerts) $storage->ssl_use_cabundle(UPDRAFTPLUS_DIR.'/includes/cacert.pem');
$this->set_storage($storage);
return $storage;
}
/**
* This method overrides the parent method and lists the supported features of this remote storage option.
*
* @return Array - an array of supported features (any features not
* mentioned are assumed to not be supported)
*/
public function get_supported_features() {
// This options format is handled via only accessing options via $this->get_options()
return array('multi_options', 'config_templates', 'multi_storage', 'conditional_logic');
}
/**
* Retrieve default options for this remote storage module.
*
* @return Array - an array of options
*/
public function get_default_options() {
return array(
'user' => '',
'authurl' => 'https://auth.api.rackspacecloud.com',
'apikey' => '',
'path' => '',
'region' => null
);
}
/**
* Check whether options have been set up by the user, or not
*
* @param Array $opts - the potential options
*
* @return Boolean
*/
public function options_exist($opts) {
if (is_array($opts) && isset($opts['user']) && '' != $opts['user'] && !empty($opts['apikey'])) return true;
return false;
}
public function backup($backup_array) {
global $updraftplus;
$opts = $this->get_options();
$updraft_dir = $updraftplus->backups_dir_location().'/';
$container = $opts['path'];
try {
$storage = $this->getCF($opts['user'], $opts['apikey'], $opts['authurl'], UpdraftPlus_Options::get_updraft_option('updraft_ssl_useservercerts'));
$container_object = $storage->create_container($container);
} catch (AuthenticationException $e) {
$this->log('authentication failed ('.$e->getMessage().')');
$this->log(__('authentication failed', 'updraftplus').' ('.$e->getMessage().')', 'error');
return false;
} catch (NoSuchAccountException $s) {
$this->log('authentication failed ('.$e->getMessage().')');
$this->log(__('authentication failed', 'updraftplus').' ('.$e->getMessage().')', 'error');
return false;
} catch (Exception $e) {
$this->log('error - failed to create and access the container ('.$e->getMessage().')');
$this->log(__('error - failed to create and access the container', 'updraftplus').' ('.$e->getMessage().')', 'error');
return false;
}
$chunk_size = 5*1024*1024;
foreach ($backup_array as $file) {
$fullpath = $updraft_dir.$file;
$orig_file_size = filesize($fullpath);
// $cfpath = ($path == '') ? $file : "$path/$file";
// $chunk_path = ($path == '') ? "chunk-do-not-delete-$file" : "$path/chunk-do-not-delete-$file";
$cfpath = $file;
$chunk_path = "chunk-do-not-delete-$file";
try {
$object = new UpdraftPlus_CF_Object($container_object, $cfpath);
$object->content_type = "application/zip";
$uploaded_size = (isset($object->content_length)) ? $object->content_length : 0;
if ($uploaded_size <= $orig_file_size) {
$fp = @fopen($fullpath, "rb");// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the function.
if (!$fp) {
$this->log("failed to open file: $fullpath");
$this->log("$file: ".__('Error: Failed to open local file', 'updraftplus'), 'error');
return false;
}
$chunks = floor($orig_file_size / $chunk_size);
// There will be a remnant unless the file size was exactly on a 5MB boundary
if ($orig_file_size % $chunk_size > 0) $chunks++;
$this->log("upload: $file (chunks: $chunks) -> cloudfiles://$container/$cfpath ($uploaded_size)");
if ($chunks < 2) {
try {
$object->load_from_filename($fullpath);
$this->log("regular upload: success");
$updraftplus->uploaded_file($file);
} catch (Exception $e) {
$this->log("regular upload: failed ($file) (".$e->getMessage().")");
$this->log("$file: ".__('Error: Failed to upload', 'updraftplus'), 'error');
}
} else {
$errors_so_far = 0;
for ($i = 1; $i <= $chunks; $i++) {
$upload_start = ($i-1)*$chunk_size;
// The file size -1 equals the byte offset of the final byte
$upload_end = min($i*$chunk_size-1, $orig_file_size-1);
$upload_remotepath = $chunk_path."_$i";
// Don't forget the +1; otherwise the last byte is omitted
$upload_size = $upload_end - $upload_start + 1;
$chunk_object = new UpdraftPlus_CF_Object($container_object, $upload_remotepath);
$chunk_object->content_type = "application/zip";
// Without this, some versions of Curl add Expect: 100-continue, which results in Curl then giving this back: curl error: 55) select/poll returned error
// Didn't make the difference - instead we just check below for actual success even when Curl reports an error
// $chunk_object->headers = array('Expect' => '');
$remote_size = (isset($chunk_object->content_length)) ? $chunk_object->content_length : 0;
if ($remote_size >= $upload_size) {
$this->log("Chunk $i ($upload_start - $upload_end): already uploaded");
} else {
$this->log("Chunk $i ($upload_start - $upload_end): begin upload");
// Upload the chunk
fseek($fp, $upload_start);
try {
$chunk_object->write($fp, $upload_size, false);
$updraftplus->record_uploaded_chunk(round(100*$i/$chunks, 1), $i, $fullpath);
} catch (Exception $e) {
$this->log("chunk upload: error: ($file / $i) (".$e->getMessage().")");
// Experience shows that Curl sometimes returns a select/poll error (curl error 55) even when everything succeeded. Google seems to indicate that this is a known bug.
$chunk_object = new UpdraftPlus_CF_Object($container_object, $upload_remotepath);
$chunk_object->content_type = "application/zip";
$remote_size = (isset($chunk_object->content_length)) ? $chunk_object->content_length : 0;
if ($remote_size >= $upload_size) {
$this->log("$file: Chunk now exists; ignoring error (presuming it was an apparently known curl bug)");
} else {
$this->log("$file: ".__('Error: Failed to upload', 'updraftplus'), 'error');
$errors_so_far++;
if ($errors_so_far >=3) return false;
}
}
}
}
if ($errors_so_far) return false;
// All chunks are uploaded - now upload the manifest
try {
$object->manifest = $container."/".$chunk_path."_";
// Put a zero-length file
$object->write("", 0, false);
$object->sync_manifest();
$this->log("upload: success");
$updraftplus->uploaded_file($file);
// } catch (InvalidResponseException $e) {
} catch (Exception $e) {
$this->log('error - failed to re-assemble chunks ('.$e->getMessage().')');
$this->log(__('error - failed to re-assemble chunks', 'updraftplus').' ('.$e->getMessage().')', 'error');
return false;
}
}
}
} catch (Exception $e) {
$this->log(__('error - failed to upload file', 'updraftplus').' ('.$e->getMessage().')');
$this->log(__('error - failed to upload file', 'updraftplus').' ('.$e->getMessage().')', 'error');
return false;
}
}
return array('cloudfiles_object' => $container_object, 'cloudfiles_orig_path' => $opts['path'], 'cloudfiles_container' => $container);
}
public function listfiles($match = 'backup_') {
$opts = $this->get_options();
$container = $opts['path'];
if (empty($opts['user']) || empty($opts['apikey'])) new WP_Error('no_settings', __('No settings were found', 'updraftplus'));
try {
$storage = $this->getCF($opts['user'], $opts['apikey'], $opts['authurl'], UpdraftPlus_Options::get_updraft_option('updraft_ssl_useservercerts'));
$container_object = $storage->create_container($container);
} catch (Exception $e) {
return new WP_Error('no_access', sprintf(__('%s authentication failed', 'updraftplus'), 'Cloud Files').' ('.$e->getMessage().')');
}
$results = array();
try {
$objects = $container_object->list_objects(0, null, $match);
foreach ($objects as $name) {
$result = array('name' => $name);
try {
$object = new UpdraftPlus_CF_Object($container_object, $name, true);
if (0 == $object->content_length) {
$result = false;
} else {
$result['size'] = $object->content_length;
}
} catch (Exception $e) {
// Catch
}
if (is_array($result)) $results[] = $result;
}
} catch (Exception $e) {
return new WP_Error('cf_error', 'Cloud Files error ('.$e->getMessage().')');
}
return $results;
}
/**
* Delete a single file from the service using the CloudFiles API
*
* @param Array $files - array of file paths to delete
* @param Array $cloudfilesarr - CloudFiles container and object details
* @param Array $sizeinfo - unused here
* @return Boolean|String - either a boolean true or an error code string
*/
public function delete($files, $cloudfilesarr = false, $sizeinfo = array()) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- $sizeinfo is unused
if (is_string($files)) $files =array($files);
if ($cloudfilesarr) {
$container_object = $cloudfilesarr['cloudfiles_object'];
$container = $cloudfilesarr['cloudfiles_container'];
} else {
try {
$opts = $this->get_options();
$container = $opts['path'];
$storage = $this->getCF($opts['user'], $opts['apikey'], $opts['authurl'], UpdraftPlus_Options::get_updraft_option('updraft_ssl_useservercerts'));
$container_object = $storage->create_container($container);
} catch (Exception $e) {
$this->log('authentication failed ('.$e->getMessage().')');
$this->log(__('authentication failed', 'updraftplus').' ('.$e->getMessage().')', 'error');
return 'authentication_fail';
}
}
$ret = true;
foreach ($files as $file) {
$fpath = $file;
$this->log("Delete remote: container=$container, path=$fpath");
// We need to search for chunks
$chunk_path = "chunk-do-not-delete-$file";
try {
$objects = $container_object->list_objects(0, null, $chunk_path.'_');
foreach ($objects as $chunk) {
$this->log('Chunk to delete: '.$chunk);
$container_object->delete_object($chunk);
$this->log('Chunk deleted: '.$chunk);
}
} catch (Exception $e) {
$this->log('chunk delete failed: '.$e->getMessage());
}
try {
$container_object->delete_object($fpath);
$this->log('Deleted: '.$fpath);
} catch (Exception $e) {
$this->log('delete failed: '.$e->getMessage());
$ret = 'file_delete_error';
}
}
return $ret;
}
public function download($file) {
global $updraftplus;
$updraft_dir = $updraftplus->backups_dir_location();
$opts = $this->get_options();
try {
$storage = $this->getCF($opts['user'], $opts['apikey'], $opts['authurl'], UpdraftPlus_Options::get_updraft_option('updraft_ssl_useservercerts'));
} catch (AuthenticationException $e) {
$this->log('authentication failed ('.$e->getMessage().')');
$this->log(__('authentication failed', 'updraftplus').' ('.$e->getMessage().')', 'error');
return false;
} catch (NoSuchAccountException $s) {
$this->log('authentication failed ('.$e->getMessage().')');
$this->log(__('authentication failed', 'updraftplus').' ('.$e->getMessage().')', 'error');
return false;
} catch (Exception $e) {
$this->log('error - failed to create and access the container ('.$e->getMessage().')');
$this->log(__('error - failed to create and access the container', 'updraftplus').' ('.$e->getMessage().')', 'error');
return false;
}
$path = untrailingslashit($opts['path']);
$container = $path;
try {
$container_object = $storage->create_container($container);
} catch (Exception $e) {
$this->log('error - failed to create and access the container ('.$e->getMessage().')');
$this->log(__('error - failed to create and access the container', 'updraftplus').' ('.$e->getMessage().')', 'error');
return false;
}
$path = $file;
$this->log("download: cloudfiles://$container/$path");
try {
// The third parameter causes an exception to be thrown if the object does not exist remotely
$object = new UpdraftPlus_CF_Object($container_object, $path, true);
$fullpath = $updraft_dir.'/'.$file;
$start_offset = (file_exists($fullpath)) ? filesize($fullpath) : 0;
// Get file size from remote - see if we've already finished
$remote_size = $object->content_length;
if ($start_offset >= $remote_size) {
$this->log("file is already completely downloaded ($start_offset/$remote_size)");
return true;
}
// Some more remains to download - so let's do it
if (!$fh = fopen($fullpath, 'a')) {
$this->log("Error opening local file: $fullpath");
$this->log("$file: ".__('Error opening local file: Failed to download', 'updraftplus'), 'error');
return false;
}
$headers = array();
// If resuming, then move to the end of the file
if ($start_offset) {
$this->log("local file is already partially downloaded ($start_offset/$remote_size)");
fseek($fh, $start_offset);
$headers['Range'] = "bytes=$start_offset-";
}
// Now send the request itself
try {
$object->stream($fh, $headers);
} catch (Exception $e) {
$this->log("Failed to download: $file (".$e->getMessage().")");
$this->log("$file: ".__('Error downloading remote file: Failed to download', 'updraftplus').' ('.$e->getMessage().")", 'error');
return false;
}
// All-in-one-go method:
// $object->save_to_filename($fullpath);
} catch (NoSuchObjectException $e) {
$this->log('error - no such file exists. ('.$e->getMessage().')');
$this->log(__('Error - no such file exists.', 'updraftplus').' ('.$e->getMessage().')', 'error');
return false;
} catch (Exception $e) {
$this->log('error - failed to download the file ('.$e->getMessage().')');
$this->log(__('Error - failed to download the file', 'updraftplus').' ('.$e->getMessage().')', 'error');
return false;
}
return true;
}
/**
* Get the pre configuration template
*
* @return String - the template
*/
public function get_pre_configuration_template() {
global $updraftplus_admin;
$classes = $this->get_css_classes(false);
?>
<tr class="<?php echo esc_attr($classes) . ' ' . 'cloudfiles_pre_config_container';?>">
<td colspan="2">
<img alt="Rackspace Cloud Files" src="<?php echo esc_url(UPDRAFTPLUS_URL);?>/images/rackspacecloud-logo.png"><br>
<?php
// Check requirements.
global $updraftplus_admin;
if (!function_exists('mb_substr')) {
$updraftplus_admin->show_double_warning('<strong>'.__('Warning', 'updraftplus').':</strong> '.sprintf(__('Your web server\'s PHP installation does not included a required module (%s).', 'updraftplus'), 'mbstring').' '.__('Please contact your web hosting provider\'s support.', 'updraftplus').' '.sprintf(__("UpdraftPlus's %s module <strong>requires</strong> %s.", 'updraftplus'), 'Cloud Files', 'mbstring').' '.__('Please do not file any support requests; there is no alternative.', 'updraftplus'), 'cloudfiles', false);
}
$updraftplus_admin->curl_check('Rackspace Cloud Files', false, 'cloudfiles', false);
?>
<p>
<?php
printf(
// translators: %1$s - opening link tag to Rackspace Cloud console, %2$s - closing link tag, %3$s - opening link tag to instructions.
esc_html__('Get your API key from your %1$sRackspace Cloud console%2$s (%3$sread instructions here%2$s), then pick a container name to use for storage.', 'updraftplus'),
'<a href="https://mycloud.rackspace.com/" target="_blank">',
'</a>',
'<a href="http://www.rackspace.com/knowledge_center/article/rackspace-cloud-essentials-1-generating-your-api-key" target="_blank">'
);
echo ' '.esc_html__('This container will be created for you if it does not already exist.', 'updraftplus').' <a href="https://updraftplus.com/faqs/there-appear-to-be-lots-of-extra-files-in-my-rackspace-cloud-files-container/" target="_blank">'.esc_html__('Also, you should read this important FAQ.', 'updraftplus').'</a>';
?>
</p>
</td>
</tr>
<?php
}
/**
* Get the configuration template
*
* @return String - the template, ready for substitutions to be carried out
*/
public function get_configuration_template() {
$classes = $this->get_css_classes();
$template_str = '
<tr class="'.$classes.'">
<th>'.__('US or UK Cloud', 'updraftplus').':</th>
<td>
<select data-updraft_settings_test="authurl" '.$this->output_settings_field_name_and_id('authurl', true).'>
<option {{#ifeq "https://auth.api.rackspacecloud.com" authurl}}selected="selected"{{/ifeq}} value="https://auth.api.rackspacecloud.com">'.__('US (default)', 'updraftplus').'</option>
<option {{#ifeq "https://lon.auth.api.rackspacecloud.com" authurl}}selected="selected"{{/ifeq}} value="https://lon.auth.api.rackspacecloud.com">'.__('UK', 'updraftplus').'</option>
</select>
</td>
</tr>
<input type="hidden" data-updraft_settings_test="region" '.$this->output_settings_field_name_and_id('region', true).' value="">';
/*
// Can put a message here if someone asks why region storage is not available (only available on new SDK)
<tr class="updraftplusmethod cloudfiles">
<th><?php _e('Rackspace Storage Region','updraftplus');?>:</th>
<td>
</td>
</tr>
*/
$template_str .= '
<tr class="'.$classes.'">
<th>'.__('Cloud Files username', 'updraftplus').':</th>
<td><input data-updraft_settings_test="user" type="text" autocomplete="off" class="updraft_input--wide" '.$this->output_settings_field_name_and_id('user', true).' value="{{user}}" /></td>
</tr>
<tr class="'.$classes.'">
<th>'.__('Cloud Files API Key', 'updraftplus').':</th>
<td><input data-updraft_settings_test="apikey" type="'.apply_filters('updraftplus_admin_secret_field_type', 'password').'" autocomplete="off" class="updraft_input--wide" '.$this->output_settings_field_name_and_id('apikey', true).' value="{{apikey}}" />
</td>
</tr>
<tr class="'.$classes.'">
<th>'.apply_filters('updraftplus_cloudfiles_location_description', __('Cloud Files Container', 'updraftplus')).':</th>
<td><input data-updraft_settings_test="path" type="text" class="updraft_input--wide" '.$this->output_settings_field_name_and_id('path', true).' value="{{path}}" /></td>
</tr>';
$template_str .= $this->get_test_button_html(__('Cloud Files', 'updraftplus'));
return $template_str;
}
/**
* Modifies handerbar template options
*
* @param array $opts handerbar template options
* @return Array - Modified handerbar template options
*/
public function transform_options_for_template($opts) {
$opts['apikey'] = trim($opts['apikey']);
$opts['authurl'] = isset($opts['authurl']) ? $opts['authurl'] : '';
return $opts;
}
/**
* Perform a test of user-supplied credentials, and echo the result
*
* @param Array $posted_settings - settings to test
*/
public function credentials_test($posted_settings) {
if (empty($posted_settings['apikey'])) {
echo esc_html(sprintf(__("Failure: No %s was given.", 'updraftplus'), __('API key', 'updraftplus')));
return;
}
if (empty($posted_settings['user'])) {
echo esc_html(sprintf(__("Failure: No %s was given.", 'updraftplus'), __('Username', 'updraftplus')));
return;
}
$key = $posted_settings['apikey'];
$user = $posted_settings['user'];
$path = $posted_settings['path'];
$authurl = $posted_settings['authurl'];
$useservercerts = $posted_settings['useservercerts'];
$disableverify = $posted_settings['disableverify'];
if (preg_match("#^([^/]+)/(.*)$#", $path, $bmatches)) {
$container = $bmatches[1];
$path = $bmatches[2];
} else {
$container = $path;
$path = "";
}
if (empty($container)) {
esc_html_e("Failure: No container details were given.", 'updraftplus');
return;
}
define('UPDRAFTPLUS_SSL_DISABLEVERIFY', $disableverify);
try {
$storage = $this->getCF($user, $key, $authurl, $useservercerts);
$container_object = $storage->create_container($container);
} catch (AuthenticationException $e) {
echo esc_html(__('Cloud Files authentication failed', 'updraftplus').' ('.$e->getMessage().')');
return;
} catch (NoSuchAccountException $s) {
echo esc_html(__('Cloud Files authentication failed', 'updraftplus').' ('.$e->getMessage().')');
return;
} catch (Exception $e) {
echo esc_html(__('Cloud Files authentication failed', 'updraftplus').' ('.$e->getMessage().')');
return;
}
$try_file = md5(rand()).'.txt';
try {
$object = $container_object->create_object($try_file);
$object->content_type = "text/plain";
$object->write('UpdraftPlus test file');
} catch (Exception $e) {
echo esc_html(__('Cloud Files error - we accessed the container, but failed to create a file within it', 'updraftplus').' ('.$e->getMessage().')');
return;
}
echo esc_html(__('Success', 'updraftplus').": ".__('We accessed the container, and were able to create files within it.', 'updraftplus'));
@$container_object->delete_object($try_file);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the method.
}
}
// Moved to the bottom to fix a bug in some version or install of PHP which required UpdraftPlus_BackupModule_cloudfiles_oldsdk to be defined earlier in the file (despite the conditionality) - see HS#19911
if (version_compare(PHP_VERSION, '5.3.3', '>=') && (!defined('UPDRAFTPLUS_CLOUDFILES_USEOLDSDK') || UPDRAFTPLUS_CLOUDFILES_USEOLDSDK != true)) {
updraft_try_include_file('methods/cloudfiles-new.php', 'include_once');
class UpdraftPlus_BackupModule_cloudfiles extends UpdraftPlus_BackupModule_cloudfiles_opencloudsdk {
}
} else {
class UpdraftPlus_BackupModule_cloudfiles extends UpdraftPlus_BackupModule_cloudfiles_oldsdk {
}
}

View File

@@ -0,0 +1,198 @@
<?php
if (!defined('UPDRAFTPLUS_DIR')) die('No direct access allowed.');
updraft_try_include_file('methods/s3.php', 'require_once');
/**
* Converted to multi-options (Feb 2017-) and previous options conversion removed: Yes
*/
class UpdraftPlus_BackupModule_dreamobjects extends UpdraftPlus_BackupModule_s3 {
// This gets populated in the constructor
private $dreamobjects_endpoints = array();
protected $provider_can_use_aws_sdk = false;
protected $provider_has_regions = true;
/**
* Class constructor
*/
public function __construct() {
// When new endpoint introduced in future, Please add it here and also add it as hard coded option for endpoint dropdown in self::get_partial_configuration_template_for_endpoint()
// Put the default first
$this->dreamobjects_endpoints = array(
// Endpoint, then the label
'objects-us-east-1.dream.io' => 'objects-us-east-1.dream.io',
'objects-us-west-1.dream.io' => 'objects-us-west-1.dream.io ('.__('Closing 1st October 2018', 'updraftplus').')',
);
}
protected $use_v4 = false;
/**
* Given an S3 object, possibly set the region on it
*
* @param Object $obj - like UpdraftPlus_S3
* @param String $region - or empty to fetch one from saved configuration
* @param String $bucket_name
*/
protected function set_region($obj, $region = '', $bucket_name = '') {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- $bucket_name
$config = $this->get_config();
$endpoint = ('' != $region && 'n/a' != $region) ? $region : $config['endpoint'];
global $updraftplus;
if ($updraftplus->backup_time) {
$updraftplus->log("Set endpoint (".get_class($obj)."): $endpoint");
// Warning for objects-us-west-1 shutdown in Oct 2018
if ('objects-us-west-1.dream.io' == $endpoint) {
$updraftplus->log("The objects-us-west-1.dream.io endpoint shut down on the 1st October 2018. The upload is expected to fail. Please see the following article for more information https://help.dreamhost.com/hc/en-us/articles/360002135871-Cluster-migration-procedure", 'warning', 'dreamobjects_west_shutdown');
}
}
$obj->setEndpoint($endpoint);
}
/**
* This method overrides the parent method and lists the supported features of this remote storage option.
*
* @return Array - an array of supported features (any features not mentioned are asuumed to not be supported)
*/
public function get_supported_features() {
// This options format is handled via only accessing options via $this->get_options()
return array('multi_options', 'config_templates', 'multi_storage', 'conditional_logic');
}
/**
* Retrieve default options for this remote storage module.
*
* @return Array - an array of options
*/
public function get_default_options() {
return array(
'accesskey' => '',
'secretkey' => '',
'path' => '',
);
}
/**
* Retrieve specific options for this remote storage module
*
* @param Boolean $force_refresh - if set, and if relevant, don't use cached credentials, but get them afresh
*
* @return Array - an array of options
*/
protected function get_config($force_refresh = false) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- $force_refresh unused
$opts = $this->get_options();
$opts['whoweare'] = 'DreamObjects';
$opts['whoweare_long'] = 'DreamObjects';
$opts['key'] = 'dreamobjects';
if (empty($opts['endpoint'])) {
$endpoints = array_keys($this->dreamobjects_endpoints);
$opts['endpoint'] = $endpoints[0];
}
return $opts;
}
/**
* Get the pre configuration template
*/
public function get_pre_configuration_template() {
?>
<tr class="{{get_template_css_classes false}} {{method_display_name}}_pre_config_container">
<td colspan="2">
<a href="https://dreamhost.com/cloud/dreamobjects/" target="_blank"><img alt="{{method_display_name}}" src="{{storage_image_url}}"></a>
<br>
{{{xmlwriter_existence_label}}}
{{{simplexmlelement_existence_label}}}
{{{curl_existence_label}}}
<br>
{{{console_url_text}}}
<p>
<a href="{{updraftplus_com_link}}" target="_blank">{{ssl_error_text}}</a>
</p>
</td>
</tr>
<?php
}
/**
* Get the configuration template
*
* @return String - the template, ready for substitutions to be carried out
*/
public function get_configuration_template() {
// return $this->get_configuration_template_engine('dreamobjects', 'DreamObjects', 'DreamObjects', 'DreamObjects', 'https://panel.dreamhost.com/index.cgi?tree=storage.dreamhostobjects', '<a href="https://dreamhost.com/cloud/dreamobjects/" target="_blank"><img alt="DreamObjects" src="'.UPDRAFTPLUS_URL.'/images/dreamobjects_logo-horiz-2013.png"></a>');
ob_start();
?>
<tr class="{{get_template_css_classes true}}">
<th>{{input_accesskey_label}}:</th>
<td><input class="updraft_input--wide udc-wd-600" data-updraft_settings_test="accesskey" type="text" autocomplete="off" id="{{get_template_input_attribute_value "id" "accesskey"}}" name="{{get_template_input_attribute_value "name" "accesskey"}}" value="{{accesskey}}" /></td>
</tr>
<tr class="{{get_template_css_classes true}}">
<th>{{input_secretkey_label}}:</th>
<td><input class="updraft_input--wide udc-wd-600" data-updraft_settings_test="secretkey" type="{{input_secretkey_type}}" autocomplete="off" id="{{get_template_input_attribute_value "id" "secretkey"}}" name="{{get_template_input_attribute_value "name" "secretkey"}}" value="{{secretkey}}" /></td>
</tr>
<tr class="{{get_template_css_classes true}}">
<th>{{input_location_label}}:</th>
<td>{{method_id}}://<input class="updraft_input--wide udc-wd-600" data-updraft_settings_test="path" title="{{input_location_title}}" type="text" id="{{get_template_input_attribute_value "id" "path"}}" name="{{get_template_input_attribute_value "name" "path"}}" value="{{path}}" /></td>
</tr>
<tr class="{{get_template_css_classes true}}">
<th>{{input_endpoint_label}}</th>
<td>
<select data-updraft_settings_test="endpoint" id="{{get_template_input_attribute_value "id" "endpoint"}}" name="{{get_template_input_attribute_value "name" "endpoint"}}" style="width: 360px">
{{#each dreamobjects_endpoints as |description endpoint|}}
<option value="{{endpoint}}" {{#ifeq ../endpoint endpoint}}selected="selected"{{/ifeq}}>{{description}}</option>
{{/each}}
</select>
</td>
</tr>
{{{get_template_test_button_html "DreamObjects"}}}
<?php
return ob_get_clean();
}
/**
* Modifies handerbar template options
*
* @param array $opts
* @return Array - Modified handerbar template options
*/
public function transform_options_for_template($opts) {
$opts['endpoint'] = empty($opts['endpoint']) ? '' : $opts['endpoint'];
$opts['dreamobjects_endpoints'] = $this->dreamobjects_endpoints;
return $opts;
}
/**
* Retrieve a list of template properties by taking all the persistent variables and methods of the parent class and combining them with the ones that are unique to this module, also the necessary HTML element attributes and texts which are also unique only to this backup module
* NOTE: Please sanitise all strings that are required to be shown as HTML content on the frontend side (i.e. wp_kses()), or any other technique to prevent XSS attacks that could come via WP hooks
*
* @return Array an associative array keyed by names that describe themselves as they are
*/
public function get_template_properties() {
global $updraftplus, $updraftplus_admin;
$properties = array(
'storage_image_url' => UPDRAFTPLUS_URL."/images/dreamobjects_logo-horiz-2013.png",
'curl_existence_label' => wp_kses($updraftplus_admin->curl_check($updraftplus->backup_methods[$this->get_id()], false, $this->get_id()." hidden-in-updraftcentral", false), $this->allowed_html_for_content_sanitisation()),
'simplexmlelement_existence_label' => !apply_filters('updraftplus_dreamobjects_simplexmlelement_exists', class_exists('SimpleXMLElement')) ? wp_kses($updraftplus_admin->show_double_warning('<strong>'.__('Warning', 'updraftplus').':</strong> '.sprintf(__("Your web server's PHP installation does not included a required module (%s).", 'updraftplus'), 'SimpleXMLElement').' '.__("Please contact your web hosting provider's support.", 'updraftplus').' '.sprintf(__("UpdraftPlus's %s module <strong>requires</strong> %s.", 'updraftplus'), $updraftplus->backup_methods[$this->get_id()], 'SimpleXMLElement').' '.__('Please do not file any support requests; there is no alternative.', 'updraftplus'), $this->get_id(), false), $this->allowed_html_for_content_sanitisation()) : '',
'xmlwriter_existence_label' => !apply_filters('updraftplus_dreamobjects_xmlwriter_exists', 'UpdraftPlus_S3_Compat' != $this->indicate_s3_class() || !class_exists('XMLWriter')) ? wp_kses($updraftplus_admin->show_double_warning('<strong>'.__('Warning', 'updraftplus').':</strong> '. sprintf(__("Your web server's PHP installation does not included a required module (%s).", 'updraftplus'), 'XMLWriter').' '.__("Please contact your web hosting provider's support and ask for them to enable it.", 'updraftplus'), $this->get_id(), false), $this->allowed_html_for_content_sanitisation()) : '',
'console_url_text' => sprintf(__('Get your access key and secret key from your <a href="%s">%s console</a>, then pick a (globally unique - all %s users) bucket name (letters and numbers) (and optionally a path) to use for storage.', 'updraftplus'), 'https://panel.dreamhost.com/index.cgi?tree=storage.dreamhostobjects', $updraftplus->backup_methods[$this->get_id()], $updraftplus->backup_methods[$this->get_id()]).' '.__('This bucket will be created for you if it does not already exist.', 'updraftplus'),
'updraftplus_com_link' => apply_filters("updraftplus_com_link", "https://teamupdraft.com/documentation/updraftplus/topics/backing-up/troubleshooting/i-get-ssl-certificate-errors-when-backing-up-and-or-restoring/?utm_source=udp-plugin&utm_medium=referral&utm_campaign=paac&utm_content=dreamobjects-ssl-certificates&utm_creative_format=text"),
'ssl_error_text' => __('If you see errors about SSL certificates, then please go here for help.', 'updraftplus'),
'credentials_creation_link_text' => __('Create Azure credentials in your Azure developer console.', 'updraftplus'),
'configuration_helper_link_text' => __('For more detailed instructions, follow this link.', 'updraftplus'),
'input_accesskey_label' => sprintf(__('%s access key', 'updraftplus'), $updraftplus->backup_methods[$this->get_id()]),
'input_secretkey_label' => sprintf(__('%s secret key', 'updraftplus'), $updraftplus->backup_methods[$this->get_id()]),
'input_secretkey_type' => apply_filters('updraftplus_admin_secret_field_type', 'password'),
'input_location_label' => sprintf(__('%s location', 'updraftplus'), $updraftplus->backup_methods[$this->get_id()]),
'input_location_title' => __('Enter only a bucket name or a bucket and path.', 'updraftplus').' '.__('Examples: mybucket, mybucket/mypath', 'updraftplus'),
'input_endpoint_label' => sprintf(__('%s end-point', 'updraftplus'), $updraftplus->backup_methods[$this->get_id()]),
'input_test_label' => sprintf(__('Test %s Settings', 'updraftplus'), $updraftplus->backup_methods[$this->get_id()]),
);
return wp_parse_args($properties, $this->get_persistent_variables_and_methods());
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,131 @@
<?php
if (!defined('UPDRAFTPLUS_DIR')) die('No direct access allowed.');
// Files can easily get too big for this method
if (!class_exists('UpdraftPlus_BackupModule')) updraft_try_include_file('methods/backup-module.php', 'require_once');
class UpdraftPlus_BackupModule_email extends UpdraftPlus_BackupModule {
public function backup($backup_array) {
global $updraftplus;
$updraft_dir = trailingslashit($updraftplus->backups_dir_location());
$email = $updraftplus->just_one_email(UpdraftPlus_Options::get_updraft_option('updraft_email'), true);
if (!is_array($email)) $email = array_filter(array($email));
foreach ($backup_array as $type => $file) {
$descrip_type = preg_match('/^(.*)\d+$/', $type, $matches) ? $matches[1] : $type;
$fullpath = $updraft_dir.$file;
if (file_exists($fullpath) && filesize($fullpath) > UPDRAFTPLUS_WARN_EMAIL_SIZE) {
$size_in_mb_of_big_file = round(filesize($fullpath)/1048576, 1);
$toobig_hash = md5($file);
$this->log($file.': '.sprintf(__('This backup archive is %s MB in size - the attempt to send this via email is likely to fail (few email servers allow attachments of this size).', 'updraftplus'), $size_in_mb_of_big_file).' '.__('If so, you should switch to using a different remote storage method.', 'updraftplus'), 'warning', 'toobigforemail_'.$toobig_hash);
}
$any_attempted = false;
$any_sent = false;
$any_skip = false;
foreach ($email as $ind => $addr) {
if (apply_filters('updraftplus_email_backup', true, $addr, $ind, $type)) {
foreach (explode(',', $addr) as $sendmail_addr) {
if (!preg_match('/^https?:\/\//i', $sendmail_addr)) {
$send_short = (strlen($sendmail_addr)>5) ? substr($sendmail_addr, 0, 5).'...' : $sendmail_addr;
$this->log("$file: email to: $send_short");
$any_attempted = true;
$headers = array();
$subject = __("WordPress Backup", 'updraftplus').': '.get_bloginfo('name').' (UpdraftPlus '.$updraftplus->version.') '.get_date_from_gmt(gmdate('Y-m-d H:i:s', $updraftplus->backup_time), 'Y-m-d H:i');
$from_email = apply_filters('updraftplus_email_from_header', $updraftplus->get_email_from_header());
$from_name = apply_filters('updraftplus_email_from_name_header', $updraftplus->get_email_from_name_header());
$use_wp_from_name_filter = '' === $from_email;
// Notice that we don't use the 'wp_mail_from' filter, but only the 'From:' header to set sender name and sender email address, the reason behind it is that some SMTP plugins override the "wp_mail()" function and they do anything they want inside their own "wp_mail()" function, including not to call the php_mailer filter nor the wp_mail_from and wp_mail_from_name filters, but since the function signature remain the same as the WP one, so they may evaluate and do something with the header parameter
if (!$use_wp_from_name_filter) {
$headers[] = sprintf('From: %s <%s>', $from_name, $from_email);
} else {
add_filter('wp_mail_from_name', array($updraftplus, 'get_email_from_name_header'), 9);
}
add_action('wp_mail_failed', array($updraftplus, 'log_email_delivery_failure'));
$sent = wp_mail(trim($sendmail_addr), $subject, sprintf(__("Backup is of: %s.", 'updraftplus'), site_url().' ('.$descrip_type.')'), $headers, array($fullpath));
remove_action('wp_mail_failed', array($updraftplus, 'log_email_delivery_failure'));
if ($use_wp_from_name_filter) remove_filter('wp_mail_from_name', array($this, 'get_email_from_name_header'), 9);
if ($sent) $any_sent = true;
}
}
} else {
$log_message = apply_filters('updraftplus_email_backup_skip_log_message', '', $addr, $ind, $descrip_type);
if (!empty($log_message)) {
$this->log($log_message);
}
$any_skip = true;
}
}
if ($any_sent) {
if (isset($toobig_hash)) {
$updraftplus->log_remove_warning('toobigforemail_'.$toobig_hash);
// Don't leave it still set for the next archive
unset($toobig_hash);
}
$updraftplus->uploaded_file($file);
} elseif ($any_attempted) {
$this->log('Mails were not sent successfully');
$this->log(__('The attempt to send the backup via email failed (probably the backup was too large for this method)', 'updraftplus'), 'error');
} elseif ($any_skip) {
$this->log('No email addresses were configured to send email to '.$descrip_type);
} else {
$this->log('No email addresses were configured to send to');
}
}
return null;
}
/**
* Acts as a WordPress options filter
*
* @param Array $options - An array of options
*
* @return Array - the returned array can either be the set of updated settings or a WordPress error array
*/
public function options_filter($options) {
global $updraftplus;
return $updraftplus->just_one_email($options);
}
public function config_print() {
global $updraftplus;
?>
<tr class="updraftplusmethod email">
<th><?php esc_html_e('Email:', 'updraftplus');?></th>
<td><?php
$used = apply_filters('updraftplus_email_whichaddresses',
sprintf(__("Your site's admin email address (%s) will be used.", 'updraftplus'), get_bloginfo('admin_email').' - <a href="'.esc_attr(admin_url('options-general.php')).'">'.__("configure it here", 'updraftplus').'</a>').
' <a href="'.$updraftplus->get_url('premium_email').'" target="_blank">'.__('For more options, use Premium', 'updraftplus').'</a>'
);
$allowed_html = array(
'a' => array(
'href' => array(),
'target' => array(),
)
);
echo wp_kses($used.' '.sprintf(__('Be aware that mail servers tend to have size limits; typically around %s MB; backups larger than any limits will likely not arrive.', 'updraftplus'), '10-20'), $allowed_html);
?>
</td>
</tr>
<?php
}
public function delete($files, $data = null, $sizeinfo = array()) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Unused parameter is present because the caller uses 3 arguments.
return true;
}
}

View File

@@ -0,0 +1,483 @@
<?php
if (!defined('UPDRAFTPLUS_DIR')) die('No direct access allowed.');
// Converted to array options: yes
// Converted to job_options: yes
// Migrate options to new-style storage - May 2014
if (!is_array(UpdraftPlus_Options::get_updraft_option('updraft_ftp')) && '' != UpdraftPlus_Options::get_updraft_option('updraft_server_address', '')) {
$opts = array(
'user' => UpdraftPlus_Options::get_updraft_option('updraft_ftp_login'),
'pass' => UpdraftPlus_Options::get_updraft_option('updraft_ftp_pass'),
'host' => UpdraftPlus_Options::get_updraft_option('updraft_server_address'),
'path' => UpdraftPlus_Options::get_updraft_option('updraft_ftp_remote_path'),
'passive' => true
);
UpdraftPlus_Options::update_updraft_option('updraft_ftp', $opts);
UpdraftPlus_Options::delete_updraft_option('updraft_server_address');
UpdraftPlus_Options::delete_updraft_option('updraft_ftp_pass');
UpdraftPlus_Options::delete_updraft_option('updraft_ftp_remote_path');
UpdraftPlus_Options::delete_updraft_option('updraft_ftp_login');
}
if (!class_exists('UpdraftPlus_BackupModule')) updraft_try_include_file('methods/backup-module.php', 'require_once');
class UpdraftPlus_BackupModule_ftp extends UpdraftPlus_BackupModule {
/**
* Get FTP object with parameters set
*
* @param String $server Specify Server
* @param String $user Specify Username
* @param String $pass Specify Password
* @param Boolean $disable_ssl Indicate whether to disable SSL
* @param Boolean $disable_verify Indicate whether to disable verifiction
* @param Boolean $use_server_certs Indicate whether to use server certificates
* @param Boolean $passive Indicate whether to use passive FTP mode
* @return Array
*/
private function getFTP($server, $user, $pass, $disable_ssl = false, $disable_verify = true, $use_server_certs = false, $passive = true) {
if ('' == trim($server) || '' == trim($user) || '' == trim($pass)) return new WP_Error('no_settings', sprintf(__('No %s settings were found', 'updraftplus'), 'FTP'));
if (!class_exists('UpdraftPlus_ftp_wrapper')) updraft_try_include_file('includes/ftp.class.php', 'include_once');
$port = 21;
if (preg_match('/^(.*):(\d+)$/', $server, $matches)) {
$server = $matches[1];
$port = $matches[2];
}
$ftp = new UpdraftPlus_ftp_wrapper($server, $user, $pass, $port);
if ($disable_ssl) $ftp->ssl = false;
$ftp->use_server_certs = $use_server_certs;
$ftp->disable_verify = $disable_verify;
$ftp->passive = ($passive) ? true : false;
return $ftp;
}
/**
* WordPress options filter, sanitising the FTP options saved from the options page
*
* @param Array $settings - the options, prior to sanitisation
*
* @return Array - the sanitised options for saving
*/
public function options_filter($settings) {
if (is_array($settings) && !empty($settings['version']) && !empty($settings['settings'])) {
foreach ($settings['settings'] as $instance_id => $instance_settings) {
if (!empty($instance_settings['host']) && preg_match('#ftp(es|s)?://(.*)#i', $instance_settings['host'], $matches)) {
$settings['settings'][$instance_id]['host'] = rtrim($matches[2], "/ \t\n\r\0x0B");
}
if (isset($instance_settings['pass'])) {
$settings['settings'][$instance_id]['pass'] = trim($instance_settings['pass'], "\n\r\0\x0B");
}
}
}
return $settings;
}
public function get_supported_features() {
// The 'multi_options' options format is handled via only accessing options via $this->get_options()
return array('multi_options', 'config_templates', 'multi_storage', 'conditional_logic');
}
public function get_default_options() {
return array(
'host' => '',
'user' => '',
'pass' => '',
'path' => '',
'passive' => 1
);
}
/**
* Retrieve a list of template properties by taking all the persistent variables and methods of the parent class and combining them with the ones that are unique to this module, also the necessary HTML element attributes and texts which are also unique only to this backup module
* NOTE: Please sanitise all strings that are required to be shown as HTML content on the frontend side (i.e. wp_kses())
*
* @return Array an associative array keyed by names that describe themselves as they are
*/
public function get_template_properties() {
global $updraftplus;
$possible = $this->ftp_possible();
$ftp_not_possible = array();
if (is_array($possible)) {
$trans = array(
'ftp' => __('regular non-encrypted FTP', 'updraftplus'),
'ftpsslimplicit' => __('encrypted FTP (implicit encryption)', 'updraftplus'),
'ftpsslexplicit' => __('encrypted FTP (explicit encryption)', 'updraftplus')
);
foreach ($possible as $type => $missing) {
$ftp_not_possible[] = wp_kses('<strong>'.__('Warning', 'updraftplus').':</strong> '. sprintf(__("Your web server's PHP installation has these functions disabled: %s.", 'updraftplus'), implode(', ', $missing)).' '.sprintf(__('Your hosting company must enable these functions before %s can work.', 'updraftplus'), $trans[$type]), $this->allowed_html_for_content_sanitisation());
}
}
$properties = array(
'updraft_sftp_ftps_notice' => wp_kses(apply_filters('updraft_sftp_ftps_notice', '<strong>'.__('Only non-encrypted FTP is supported by regular UpdraftPlus.').'</strong> <a href="https://teamupdraft.com/updraftplus/wordpress-cloud-storage-options/?utm_source=udp-plugin&utm_medium=referral&utm_campaign=paac&utm_content=ftp-encryption&utm_creative_format=text" target="_blank">'.__('If you want encryption (e.g. you are storing sensitive business data), then choose UpdraftPlus Premium.', 'updraftplus')), $this->allowed_html_for_content_sanitisation()),
'ftp_not_possible_warnings' => $ftp_not_possible,
'input_host_label' => __('FTP server', 'updraftplus'),
'input_user_label' => __('FTP login', 'updraftplus'),
'input_password_label' => __('FTP password', 'updraftplus'),
'input_password_type' => apply_filters('updraftplus_admin_secret_field_type', 'password'),
'input_path_label' => __('Remote path', 'updraftplus'),
'input_path_title' => __('Needs to already exist', 'updraftplus'),
'input_passive_label' => __('Passive mode', 'updraftplus'),
'input_passive_title' => __('Almost all FTP servers will want passive mode; but if you need active mode, then uncheck this.', 'updraftplus'),
'input_test_label' => sprintf(__('Test %s Settings', 'updraftplus'), $updraftplus->backup_methods[$this->get_id()])
);
return wp_parse_args($properties, $this->get_persistent_variables_and_methods());
}
public function backup($backup_array) {
global $updraftplus;
$opts = $this->get_options();
$ftp = $this->getFTP(
$opts['host'],
$opts['user'],
$opts['pass'],
$updraftplus->get_job_option('updraft_ssl_nossl'),
$updraftplus->get_job_option('updraft_ssl_disableverify'),
$updraftplus->get_job_option('updraft_ssl_useservercerts'),
$opts['passive']
);
if (is_wp_error($ftp) || !$ftp->connect()) {
if (is_wp_error($ftp)) {
$updraftplus->log_wp_error($ftp);
} else {
$this->log("Failure: we did not successfully log in with those credentials.");
}
$this->log(__("login failure", 'updraftplus'), 'error');
return false;
}
// $ftp->make_dir(); we may need to recursively create dirs? TODO
$updraft_dir = $updraftplus->backups_dir_location().'/';
$ftp_remote_path = trailingslashit($opts['path']);
foreach ($backup_array as $file) {
$fullpath = $updraft_dir.$file;
$this->log("upload attempt: $file -> ftp://".$opts['user']."@".$opts['host']."/{$ftp_remote_path}{$file}");
$timer_start = microtime(true);
$size_k = round(filesize($fullpath)/1024, 1);
// Note :Setting $resume to true unnecessarily is not meant to be a problem. Only ever (Feb 2014) seen one weird FTP server where calling SIZE on a non-existent file did create a problem. So, this code just helps that case. (the check for non-empty upload_status[p] is being cautious.
$upload_status = $updraftplus->jobdata_get('uploading_substatus');
if (0 == $updraftplus->current_resumption || (is_array($upload_status) && !empty($upload_status['p']) && 0 == $upload_status['p'])) {
$resume = false;
} else {
$resume = true;
}
if ($ftp->put($fullpath, $ftp_remote_path.$file, FTP_BINARY, $resume, $updraftplus)) {
$this->log("upload attempt successful (".$size_k."KB in ".(round(microtime(true)-$timer_start, 2)).'s)');
$updraftplus->uploaded_file($file);
} else {
$this->log("ERROR: FTP upload failed");
$this->log(__("upload failed", 'updraftplus'), 'error');
}
}
return array('ftp_object' => $ftp, 'ftp_remote_path' => $ftp_remote_path);
}
public function listfiles($match = 'backup_') {
global $updraftplus;
$opts = $this->get_options();
$ftp = $this->getFTP(
$opts['host'],
$opts['user'],
$opts['pass'],
$updraftplus->get_job_option('updraft_ssl_nossl'),
$updraftplus->get_job_option('updraft_ssl_disableverify'),
$updraftplus->get_job_option('updraft_ssl_useservercerts'),
$opts['passive']
);
if (is_wp_error($ftp)) return $ftp;
if (!$ftp->connect()) return new WP_Error('ftp_login_failed', sprintf(__("%s login failure", 'updraftplus'), 'FTP'));
$ftp_remote_path = $opts['path'];
if ($ftp_remote_path) $ftp_remote_path = trailingslashit($ftp_remote_path);
$dirlist = $ftp->dir_list($ftp_remote_path);
if (!is_array($dirlist)) return array();
$results = array();
foreach ($dirlist as $k => $path) {
if ($ftp_remote_path) {
// Feb 2015 - found a case where the directory path was not prefixed on
if (0 !== strpos($path, $ftp_remote_path) && (false !== strpos('/', $ftp_remote_path) && false !== strpos('\\', $ftp_remote_path))) continue;
if (0 === strpos($path, $ftp_remote_path)) $path = substr($path, strlen($ftp_remote_path));
// if (0 !== strpos($path, $ftp_remote_path)) continue;
// $path = substr($path, strlen($ftp_remote_path));
if (0 === strpos($path, $match)) $results[]['name'] = $path;
} else {
if ('/' == substr($path, 0, 1)) $path = substr($path, 1);
if (false !== strpos($path, '/')) continue;
if (0 === strpos($path, $match)) $results[]['name'] = $path;
}
unset($dirlist[$k]);
}
// ftp_nlist() doesn't return file sizes. rawlist() does, but is tricky to parse. So, we get the sizes manually.
foreach ($results as $ind => $name) {
$size = $ftp->size($ftp_remote_path.$name['name']);
if (0 === $size) {
unset($results[$ind]);
} elseif ($size>0) {
$results[$ind]['size'] = $size;
}
}
return $results;
}
/**
* Delete a single file from the service using FTP protocols
*
* @param Array $files - array of file names to delete
* @param Array $ftparr - FTP details/credentials
* @param Array $sizeinfo - unused here
* @return Boolean|String - either a boolean true or an error code string
*/
public function delete($files, $ftparr = array(), $sizeinfo = array()) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- $sizeinfo unused
global $updraftplus;
if (is_string($files)) $files = array($files);
$opts = $this->get_options();
if (is_array($ftparr) && isset($ftparr['ftp_object'])) {
$ftp = $ftparr['ftp_object'];
} else {
$ftp = $this->getFTP(
$opts['host'],
$opts['user'],
$opts['pass'],
$updraftplus->get_job_option('updraft_ssl_nossl'),
$updraftplus->get_job_option('updraft_ssl_disableverify'),
$updraftplus->get_job_option('updraft_ssl_useservercerts'),
$opts['passive']
);
if (is_wp_error($ftp) || !$ftp->connect()) {
if (is_wp_error($ftp)) $updraftplus->log_wp_error($ftp);
$this->log("Failure: we did not successfully log in with those credentials (host=".$opts['host'].").");
return 'authentication_fail';
}
}
$ftp_remote_path = isset($ftparr['ftp_remote_path']) ? $ftparr['ftp_remote_path'] : trailingslashit($opts['path']);
$ret = true;
foreach ($files as $file) {
if (@$ftp->delete($ftp_remote_path.$file)) {// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the method.
$this->log("delete: succeeded ({$ftp_remote_path}{$file})");
} else {
$this->log("delete: failed ({$ftp_remote_path}{$file})");
$ret = 'file_delete_error';
}
}
return $ret;
}
public function download($file) {
global $updraftplus;
$opts = $this->get_options();
$ftp = $this->getFTP(
$opts['host'],
$opts['user'],
$opts['pass'],
$updraftplus->get_job_option('updraft_ssl_nossl'),
$updraftplus->get_job_option('updraft_ssl_disableverify'),
$updraftplus->get_job_option('updraft_ssl_useservercerts'),
$opts['passive']
);
if (is_wp_error($ftp)) {
$this->log('Failure to get FTP object: '.$ftp->get_error_code().': '.$ftp->get_error_message());
$this->log($ftp->get_error_message().' ('.$ftp->get_error_code().')', 'error');
return false;
}
if (!$ftp->connect()) {
$this->log('Failure: we did not successfully log in with those credentials.');
$this->log(__('login failure', 'updraftplus'), 'error');
return false;
}
// $ftp->make_dir(); we may need to recursively create dirs? TODO
$ftp_remote_path = trailingslashit($opts['path']);
$fullpath = $updraftplus->backups_dir_location().'/'.$file;
$resume = false;
if (file_exists($fullpath)) {
$resume = true;
$this->log("File already exists locally; will resume: size: ".filesize($fullpath));
}
return $ftp->get($fullpath, $ftp_remote_path.$file, FTP_BINARY, $resume, $updraftplus);
}
private function ftp_possible() {
$funcs_disabled = array();
foreach (array('ftp_connect', 'ftp_login', 'ftp_nb_fput') as $func) {
if (!function_exists($func)) $funcs_disabled['ftp'][] = $func;
}
$funcs_disabled = apply_filters('updraftplus_ftp_possible', $funcs_disabled);
return (0 == count($funcs_disabled)) ? true : $funcs_disabled;
}
/**
* Get the pre configuration template
*
* @return String - the template
*/
public function get_pre_configuration_template() {
?>
<tr class="{{get_template_css_classes false}} ftp_pre_config_container">
<td colspan="2">
<h3>{{method_display_name}}</h3>
{{#each ftp_not_possible_warnings}}
<div class="error updraftplusmethod ftp"><p>{{{this}}}</p></div>
<div class="notice error below-h2"><p>{{{this}}}</p></div>
{{/each}}
{{#ifCond "undefined" "not_typeof" updraft_sftp_ftps_notice}}
<em><p>{{{updraft_sftp_ftps_notice}}}</p></em>
{{/ifCond}}
</td>
</tr>
<?php
}
/**
* Get the configuration template
*
* @return String - the template, ready for substitutions to be carried out
*/
public function get_configuration_template() {
ob_start();
?>
<tr class="{{get_template_css_classes true}}">
<th>{{input_host_label}}:</th>
<td><input class="updraft_input--wide" type="text" size="40" data-updraft_settings_test="server" id="{{get_template_input_attribute_value "id" "host"}}" name="{{get_template_input_attribute_value "name" "host"}}" value="{{host}}" /></td>
</tr>
<tr class="{{get_template_css_classes true}}">
<th>{{input_user_label}}:</th>
<td><input class="updraft_input--wide" type="text" size="40" data-updraft_settings_test="login" id="{{get_template_input_attribute_value "id" "user"}}" name="{{get_template_input_attribute_value "name" "user"}}" value="{{user}}" /></td>
</tr>
<tr class="{{get_template_css_classes true}}">
<th>{{input_password_label}}:</th>
<td><input class="updraft_input--wide" type="{{input_password_type}}" size="40" data-updraft_settings_test="pass" id="{{get_template_input_attribute_value "id" "pass"}}" name="{{get_template_input_attribute_value "name" "pass"}}" value="{{pass}}" /></td>
</tr>
<tr class="{{get_template_css_classes true}}">
<th>{{input_path_label}}:</th>
<td><input title="{{input_path_title}}" class="updraft_input--wide" type="text" size="64" data-updraft_settings_test="path" id="{{get_template_input_attribute_value "id" "path"}}" name="{{get_template_input_attribute_value "name" "path"}}" value="{{path}}" /> <em>{{input_path_title}}</em></td>
</tr>
<tr class="{{get_template_css_classes true}}">
<th>{{input_passive_label}}:</th>
<td>
<input title="{{input_passive_title}}" type="checkbox" data-updraft_settings_test="passive" id="{{get_template_input_attribute_value "id" "passive"}}" name="{{get_template_input_attribute_value "name" "passive"}}" value="1" {{#ifeq '1' passive}}checked="checked"{{/ifeq}}> <br><em>{{input_passive_title}}</em></td>
</tr>
{{{get_template_test_button_html "FTP"}}}
<?php
return ob_get_clean();
}
/**
* Perform a test of user-supplied credentials, and echo the result
*
* @param Array $posted_settings - settings to test
*/
public function credentials_test($posted_settings) {
$server = $posted_settings['server'];
$login = $posted_settings['login'];
$pass = $posted_settings['pass'];
$path = $posted_settings['path'];
$nossl = $posted_settings['nossl'];
$passive = empty($posted_settings['passive']) ? false : true;
$disable_verify = $posted_settings['disableverify'];
$use_server_certs = $posted_settings['useservercerts'];
if (empty($server)) {
esc_html_e('Failure: No server details were given.', 'updraftplus');
return;
}
if (empty($login)) {
echo esc_html(sprintf(__('Failure: No %s was given.', 'updraftplus'), __('login', 'updraftplus')));
return;
}
if (empty($pass)) {
echo esc_html(sprintf(__('Failure: No %s was given.', 'updraftplus'), __('password', 'updraftplus')));
return;
}
if (preg_match('#ftp(es|s)?://(.*)#i', $server, $matches)) $server = untrailingslashit($matches[2]);
// $ftp = $this->getFTP($server, $login, $pass, $nossl, $disable_verify, $use_server_certs);
$ftp = $this->getFTP($server, $login, $pass, $nossl, $disable_verify, $use_server_certs, $passive);
if (!$ftp->connect()) {
esc_html_e('Failure: we did not successfully log in with those credentials.', 'updraftplus');
return;
}
// $ftp->make_dir(); we may need to recursively create dirs? TODO
$file = md5(rand(0, 99999999)).'.tmp';
$fullpath = trailingslashit($path).$file;
if ($ftp->put(ABSPATH.WPINC.'/version.php', $fullpath, FTP_BINARY, false, true)) {
echo esc_html(__("Success: we successfully logged in, and confirmed our ability to create a file in the given directory (login type:", 'updraftplus')." ".$ftp->login_type.')');
@$ftp->delete($fullpath);// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged -- Silenced to suppress errors that may arise because of the method.
} else {
esc_html_e('Failure: we successfully logged in, but were not able to create a file in the given directory.', 'updraftplus');
if (!empty($ftp->ssl)) {
echo ' '.esc_html__('This is sometimes caused by a firewall - try turning off SSL in the expert settings, and testing again.', 'updraftplus');
}
}
}
/**
* Check whether options have been set up by the user, or not
*
* @param Array $opts - the potential options
*
* @return Boolean
*/
public function options_exist($opts) {
if (is_array($opts) && !empty($opts['host']) && isset($opts['user']) && '' != $opts['user']) return true;
return false;
}
}

View File

@@ -0,0 +1,33 @@
<?php
if (!defined('UPDRAFTPLUS_DIR')) die('No direct access.');
if (class_exists('UpdraftPlus_BackupModule_googlecloud')) return;
if (version_compare(PHP_VERSION, '5.2.4', '>=')) {
if (class_exists('UpdraftPlus_Addons_RemoteStorage_googlecloud')) {
class UpdraftPlus_BackupModule_googlecloud extends UpdraftPlus_Addons_RemoteStorage_googlecloud {
public function __construct() {
parent::__construct('googlecloud', 'Google Cloud', '5.2.4', 'googlecloud.png');
}
}
} else {
updraft_try_include_file('methods/addon-not-yet-present.php', 'include_once');
/**
* N.B. UpdraftPlus_BackupModule_AddonNotYetPresent extends UpdraftPlus_BackupModule
*/
class UpdraftPlus_BackupModule_googlecloud extends UpdraftPlus_BackupModule_AddonNotYetPresent {
public function __construct() {
parent::__construct('googlecloud', 'Google Cloud', '5.2.4', 'googlecloud.png');
}
}
}
} else {
updraft_try_include_file('methods/insufficient.php', 'include_once');
class UpdraftPlus_BackupModule_googlecloud extends UpdraftPlus_BackupModule_insufficientphp {
public function __construct() {
parent::__construct('googlecloud', 'Google Cloud', '5.2.4', 'googlecloud.png');
}
}
}

View File

@@ -0,0 +1,134 @@
<?php
if (!defined('UPDRAFTPLUS_DIR')) die('No direct access allowed.');
if (!class_exists('UpdraftPlus_BackupModule')) updraft_try_include_file('methods/backup-module.php', 'require_once');
class UpdraftPlus_BackupModule_insufficientphp extends UpdraftPlus_BackupModule {
private $required_php;
private $error_msg;
private $method;
private $desc;
private $image;
private $error_msg_trans;
public function __construct($method, $desc, $php, $image = null) {
$this->method = $method;
$this->desc = $desc;
$this->required_php = $php;
$this->image = $image;
$this->error_msg = 'This remote storage method ('.$this->desc.') requires PHP '.$this->required_php.' or later';
$this->error_msg_trans = sprintf(__('This remote storage method (%s) requires PHP %s or later.', 'updraftplus'), $this->desc, $this->required_php);
}
private function log_error() {
global $updraftplus;
$updraftplus->log($this->error_msg);
$updraftplus->log($this->error_msg_trans, 'error', 'insufficientphp');
return false;
}
/**
* backup method: takes an array, and shovels them off to the cloud storage
*
* @param array $backup_array An array backups
* @return Array
*/
public function backup($backup_array) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Unused variable is present because the function to perform backup for specific storage is not exist.
return $this->log_error();
}
/**
* Retrieve a list of supported features for this storage method
*
* Currently known features:
*
* - multi_options : indicates that the remote storage module
* can handle its options being in the Feb-2017 multi-options
* format. N.B. This only indicates options handling, not any
* other multi-destination options.
*
* - multi_servers : not implemented yet: indicates that the
* remote storage module can handle multiple servers at backup
* time. This should not be specified without multi_options.
* multi_options without multi_servers is fine - it will just
* cause only the first entry in the options array to be used.
*
* - config_templates : not implemented yet: indicates that
* the remote storage module can output its configuration in
* Handlebars format via the get_configuration_template() method.
*
* @return Array - an array of supported features (any features not
* mentioned are assumed to not be supported)
*/
public function get_supported_features() {
// The 'multi_options' options format is handled via only accessing options via $this->get_options()
return array('multi_options', 'config_templates');
}
/**
* $match: a substring to require (tested via strpos() !== false)
*
* @param String $match THis will specify which match is used for the SQL but by default it is set to 'backup_' unless specified
* @return Array
*/
public function listfiles($match = 'backup_') {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Unused variable is present because the function to perform listfiles for specific storage is not exist.
return new WP_Error('insufficient_php', $this->error_msg_trans);
}
/**
* delete method: takes an array of file names (base name) or a single string, and removes them from the cloud storage
*
* @param String $files List of files
* @param Boolean $data Specifies data or not
* @param array $sizeinfo This is the size info on the file.
* @return Array
*/
public function delete($files, $data = false, $sizeinfo = array()) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Unused variable is present because the function to perform delete for specific storage is not exist.
return $this->log_error();
}
/**
* download method: takes a file name (base name), and brings it back from the cloud storage into Updraft's directory
* You can register errors with $updraftplus->log("my error message", 'error')
*
* @param String $file List of files
* @return Array
*/
public function download($file) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Unused variable is present because the function to perform download for specific storage is not exist.
return $this->log_error();
}
private function extra_config() {
}
/**
* Get the configuration template
*
* @return String - the template, ready for substitutions to be carried out
*/
public function get_configuration_template() {
ob_start();
$this->extra_config();
?>
<tr class="updraftplusmethod <?php echo esc_attr($this->method);?>">
<th><?php echo esc_html($this->desc);?>:</th>
<td>
<em>
<?php echo (!empty($this->image)) ? '<p><img src="'.esc_url(UPDRAFTPLUS_URL.'/images/'.$this->image).'"></p>' : ''; ?>
<?php echo esc_html($this->error_msg_trans);?>
<?php esc_html_e('You will need to ask your web hosting company to upgrade.', 'updraftplus');?>
<?php echo esc_html(sprintf(__('Your %s version: %s.', 'updraftplus'), 'PHP', phpversion()));?>
</em>
</td>
</tr>
<?php
return ob_get_clean();
}
}

View File

@@ -0,0 +1,36 @@
<?php
if (!defined('UPDRAFTPLUS_DIR')) die('No direct access.');
if (version_compare(phpversion(), '5.3.3', '>=')) {
if (class_exists('UpdraftPlus_Addons_RemoteStorage_onedrive')) {
class UpdraftPlus_BackupModule_onedrive extends UpdraftPlus_Addons_RemoteStorage_onedrive {
public function __construct() {
parent::__construct('onedrive', 'Microsoft OneDrive', '5.3.3', 'onedrive.png');
}
}
} else {
updraft_try_include_file('methods/addon-not-yet-present.php', 'include_once');
/**
* N.B. UpdraftPlus_BackupModule_AddonNotYetPresent extends UpdraftPlus_BackupModule
*/
class UpdraftPlus_BackupModule_onedrive extends UpdraftPlus_BackupModule_AddonNotYetPresent {
public function __construct() {
parent::__construct('onedrive', 'Microsoft OneDrive', '5.3.3', 'onedrive.png');
}
}
}
} else {
updraft_try_include_file('methods/insufficient.php', 'include_once');
class UpdraftPlus_BackupModule_onedrive extends UpdraftPlus_BackupModule_insufficientphp {
public function __construct() {
parent::__construct('onedrive', 'Microsoft OneDrive', '5.3.3', 'onedrive.png');
}
}
}

View File

@@ -0,0 +1,602 @@
<?php
if (!defined('UPDRAFTPLUS_DIR')) die('No direct access allowed.');
if (!class_exists('UpdraftPlus_BackupModule')) updraft_try_include_file('methods/backup-module.php', 'require_once');
class UpdraftPlus_BackupModule_openstack_base extends UpdraftPlus_BackupModule {
protected $chunk_size;
protected $client;
protected $method;
protected $desc;
protected $long_desc;
protected $img_url;
public function __construct($method, $desc, $long_desc = null, $img_url = '') {
$this->method = $method;
$this->desc = $desc;
$this->long_desc = (is_string($long_desc)) ? $long_desc : $desc;
$this->img_url = $img_url;
}
public function backup($backup_array) {
global $updraftplus;
$default_chunk_size = (defined('UPDRAFTPLUS_UPLOAD_CHUNKSIZE') && UPDRAFTPLUS_UPLOAD_CHUNKSIZE > 0) ? max(UPDRAFTPLUS_UPLOAD_CHUNKSIZE, 1048576) : 5242880;
$this->chunk_size = $updraftplus->jobdata_get('openstack_chunk_size', $default_chunk_size);
$opts = $this->get_options();
$this->container = $opts['path'];
try {
$storage = $this->get_openstack_service($opts, UpdraftPlus_Options::get_updraft_option('updraft_ssl_useservercerts'), UpdraftPlus_Options::get_updraft_option('updraft_ssl_disableverify'));
} catch (AuthenticationError $e) {
$updraftplus->log($this->desc.' authentication failed ('.$e->getMessage().')');
$updraftplus->log(sprintf(__('%s authentication failed', 'updraftplus'), $this->desc).' ('.$e->getMessage().')', 'error');
return false;
} catch (Exception $e) {
$updraftplus->log($this->desc.' error - failed to access the container ('.$e->getMessage().') (line: '.$e->getLine().', file: '.$e->getFile().')');
$updraftplus->log(sprintf(__('%s error - failed to access the container', 'updraftplus'), $this->desc).' ('.$e->getMessage().')', 'error');
return false;
}
// Get the container
try {
$this->container_object = $storage->getContainer($this->container);
} catch (Exception $e) {
$updraftplus->log('Could not access '.$this->desc.' container ('.get_class($e).', '.$e->getMessage().') (line: '.$e->getLine().', file: '.$e->getFile().')');
$updraftplus->log(sprintf(__('Could not access %s container', 'updraftplus'), $this->desc).' ('.get_class($e).', '.$e->getMessage().')', 'error');
return false;
}
foreach ($backup_array as $file) {
$file_key = 'status_'.md5($file);
$file_status = $this->jobdata_get($file_key, null, 'openstack_'.$file_key);
if (is_array($file_status) && !empty($file_status['chunks']) && !empty($file_status['chunks'][1]['size'])) $this->chunk_size = $file_status['chunks'][1]['size'];
// First, see the object's existing size (if any)
$uploaded_size = $this->get_remote_size($file);
try {
if (1 === $updraftplus->chunked_upload($this, $file, $this->method."://".$this->container."/$file", $this->desc, $this->chunk_size, $uploaded_size)) {
try {
if (false !== ($data = fopen($updraftplus->backups_dir_location().'/'.$file, 'r+'))) {
$this->container_object->uploadObject($file, $data);
$updraftplus->log($this->desc." regular upload: success");
$updraftplus->uploaded_file($file);
} else {
throw new Exception('uploadObject failed: fopen failed');
}
} catch (Exception $e) {
$this->log("regular upload: failed ($file) (".$e->getMessage().")");
$this->log("$file: ".sprintf(__('Error: Failed to upload', 'updraftplus')), 'error');
}
}
} catch (Exception $e) {
$updraftplus->log($this->desc.' error - failed to upload file'.' ('.$e->getMessage().') (line: '.$e->getLine().', file: '.$e->getFile().')');
$updraftplus->log(sprintf(__('%s error - failed to upload file', 'updraftplus'), $this->desc).' ('.$e->getMessage().')', 'error');
return false;
}
}
return array('object' => $this->container_object, 'orig_path' => $opts['path'], 'container' => $this->container);
}
private function get_remote_size($file) {
try {
$response = $this->container_object->getClient()->head($this->container_object->getUrl($file))->send();
$response_object = $this->container_object->dataObject()->populateFromResponse($response)->setName($file);
return $response_object->getContentLength();
} catch (Exception $e) {
// Allow caller to distinguish between zero-sized and not-found
return false;
}
}
/**
* This function lists the files found in the configured storage location
*
* @param String $match a substring to require (tested via strpos() !== false)
*
* @return Array - each file is represented by an array with entries 'name' and (optional) 'size'
*/
public function listfiles($match = 'backup_') {
$opts = $this->get_options();
$container = $opts['path'];
if (empty($opts['user']) || (empty($opts['apikey']) && empty($opts['password']))) return new WP_Error('no_settings', __('No settings were found', 'updraftplus'));
try {
$storage = $this->get_openstack_service($opts, UpdraftPlus_Options::get_updraft_option('updraft_ssl_useservercerts'), UpdraftPlus_Options::get_updraft_option('updraft_ssl_disableverify'));
} catch (Exception $e) {
return new WP_Error('no_access', sprintf(__('%s error - failed to access the container', 'updraftplus'), $this->desc).' ('.$e->getMessage().')');
}
// Get the container
try {
$this->container_object = $storage->getContainer($container);
} catch (Exception $e) {
return new WP_Error('no_access', sprintf(__('%s error - failed to access the container', 'updraftplus'), $this->desc).' ('.$e->getMessage().')');
}
$results = array();
$marker = '';
$page_size = 1000;
try {
// http://php-opencloud.readthedocs.io/en/latest/services/object-store/objects.html#list-objects-in-a-container
while (null !== $marker) {
$params = array(
'prefix' => $match,
'limit' => $page_size,
'marker' => $marker
);
$objects = $this->container_object->objectList($params);
$total = $objects->count();
if (0 == $total) break;
$index = 0;
while (false !== ($file = $objects->offsetGet($index)) && !empty($file)) {
$index++;
try {
if ((is_object($file) && !empty($file->name))) {
$result = array('name' => $file->name);
// Rackspace returns the size of a manifested file properly; other OpenStack implementations may not
if (!empty($file->bytes)) {
$result['size'] = $file->bytes;
} else {
$size = $this->get_remote_size($file->name);
if (false !== $size && $size > 0) $result['size'] = $size;
}
$results[] = $result;
}
} catch (Exception $e) {
// Catch
}
$marker = (!empty($file->name) && $total >= $page_size) ? $file->name : null;
}
}
} catch (Exception $e) {
// Catch
}
return $results;
}
/**
* Called when all chunks have been uploaded, to allow any required finishing actions to be carried out
*
* @param String $file - the basename of the file being uploaded
*
* @return Boolean - success or failure state of any finishing actions
*/
public function chunked_upload_finish($file) {
$chunk_path = 'chunk-do-not-delete-'.$file;
try {
$headers = array(
'Content-Length' => 0,
'X-Object-Manifest' => sprintf('%s/%s', $this->container, $chunk_path.'_')
);
$url = $this->container_object->getUrl($file);
$this->container_object->getClient()->put($url, $headers)->send();
return true;
} catch (Exception $e) {
global $updraftplus;
$updraftplus->log("Error when sending manifest (".get_class($e)."): ".$e->getMessage());
return false;
}
}
/**
* N.B. Since we use varying-size chunks, we must be careful as to what we do with $chunk_index
*
* @param String $file Full path for the file being uploaded
* @param Resource $fp File handle to read upload data from
* @param Integer $chunk_index Index of chunked upload
* @param Integer $upload_size Size of the upload, in bytes
* @param Integer $upload_start How many bytes into the file the upload process has got
* @param Integer $upload_end How many bytes into the file we will be after this chunk is uploaded
* @param Integer $total_file_size Total file size
*
* @return Boolean
*/
public function chunked_upload($file, $fp, $chunk_index, $upload_size, $upload_start, $upload_end, $total_file_size) {
global $updraftplus;
$file_key = 'status_'.md5($file);
$file_status = $this->jobdata_get($file_key, null, 'openstack_'.$file_key);
$next_chunk_size = $upload_size;
$bytes_already_uploaded = 0;
$last_uploaded_chunk_index = 0;
// Once a chunk is uploaded, its status is set, allowing the sequence to be reconstructed
if (is_array($file_status) && isset($file_status['chunks']) && !empty($file_status['chunks'])) {
foreach ($file_status['chunks'] as $c_id => $c_status) {
if ($c_id > $last_uploaded_chunk_index) $last_uploaded_chunk_index = $c_id;
if ($chunk_index + 1 == $c_id) {
$next_chunk_size = $c_status['size'];
}
$bytes_already_uploaded += $c_status['size'];
}
} else {
$file_status = array('chunks' => array());
}
$this->jobdata_set($file_key, $file_status);
if ($upload_start < $bytes_already_uploaded) {
if ($next_chunk_size != $upload_size) {
$response = new stdClass;
$response->new_chunk_size = $upload_size;
$response->log = false;
return $response;
} else {
return 1;
}
}
// Shouldn't be able to happen
if ($chunk_index <= $last_uploaded_chunk_index) {
$updraftplus->log($this->desc.": Chunk sequence error; chunk_index=$chunk_index, last_uploaded_chunk_index=$last_uploaded_chunk_index, upload_start=$upload_start, upload_end=$upload_end, file_status=".json_encode($file_status));
}
// Used to use $chunk_index here, before switching to variable chunk sizes
$upload_remotepath = 'chunk-do-not-delete-'.$file.'_'.sprintf("%016d", $chunk_index);
$remote_size = $this->get_remote_size($upload_remotepath);
// Without this, some versions of Curl add Expect: 100-continue, which results in Curl then giving this back: curl error: 55) select/poll returned error
// Didn't make the difference - instead we just check below for actual success even when Curl reports an error
// $chunk_object->headers = array('Expect' => '');
if ($remote_size >= $upload_size) {
$updraftplus->log($this->desc.": Chunk ($upload_start - $upload_end, $chunk_index): already uploaded");
} else {
$updraftplus->log($this->desc.": Chunk ($upload_start - $upload_end, $chunk_index): begin upload");
// Upload the chunk
try {
$data = fread($fp, $upload_size);
$time_start = microtime(true);
$this->container_object->uploadObject($upload_remotepath, $data);
$time_now = microtime(true);
$time_taken = $time_now - $time_start;
if ($next_chunk_size < 52428800 && $total_file_size > 0 && $upload_end + 1 < $total_file_size) {
$job_run_time = $time_now - $updraftplus->job_time_ms;
if ($time_taken < 10) {
$upload_rate = $upload_size / max($time_taken, 0.0001);
$upload_secs = min(floor($job_run_time), 10);
if ($job_run_time < 15) $upload_secs = max(6, $job_run_time*0.6);
// In megabytes
$memory_limit_mb = $updraftplus->memory_check_current();
$bytes_used = memory_get_usage();
$bytes_free = $memory_limit_mb * 1048576 - $bytes_used;
$new_chunk = max(min($upload_secs * $upload_rate * 0.9, 52428800, $bytes_free), 5242880);
$new_chunk = $new_chunk - ($new_chunk % 5242880);
$next_chunk_size = (int) $new_chunk;
$updraftplus->jobdata_set('openstack_chunk_size', $next_chunk_size);
}
}
} catch (Exception $e) {
$updraftplus->log($this->desc." chunk upload: error: ($file / $chunk_index) (".$e->getMessage().") (line: ".$e->getLine().', file: '.$e->getFile().')');
// Experience shows that Curl sometimes returns a select/poll error (curl error 55) even when everything succeeded. Google seems to indicate that this is a known bug.
$remote_size = $this->get_remote_size($upload_remotepath);
if ($remote_size >= $upload_size) {
$updraftplus->log("$file: Chunk now exists; ignoring error (presuming it was an apparently known curl bug)");
} else {
$updraftplus->log("$file: ".sprintf(__('%s Error: Failed to upload', 'updraftplus'), $this->desc), 'error');
return false;
}
}
}
$file_status['chunks'][$chunk_index]['size'] = $upload_size;
$this->jobdata_set($file_key, $file_status);
if ($next_chunk_size != $upload_size) {
$response = new stdClass;
$response->new_chunk_size = $next_chunk_size;
$response->log = true;
return $response;
}
return true;
}
/**
* Delete a single file from the service using OpenStack API
*
* @param Array|String $files - array of file names to delete
* @param Array $data - service object and container details
* @param Array $sizeinfo - unused here
* @return Boolean|String - either a boolean true or an error code string
*/
public function delete($files, $data = false, $sizeinfo = array()) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- $sizeinfo unused
global $updraftplus;
if (is_string($files)) $files = array($files);
if (is_array($data)) {
$container_object = $data['object'];
$container = $data['container'];
} else {
$opts = $this->get_options();
$container = $opts['path'];
try {
$storage = $this->get_openstack_service($opts, UpdraftPlus_Options::get_updraft_option('updraft_ssl_useservercerts'), UpdraftPlus_Options::get_updraft_option('updraft_ssl_disableverify'));
} catch (AuthenticationError $e) {
$updraftplus->log($this->desc.' authentication failed ('.$e->getMessage().')');
$updraftplus->log(sprintf(__('%s authentication failed', 'updraftplus'), $this->desc).' ('.$e->getMessage().')', 'error');
return 'authentication_fail';
} catch (Exception $e) {
$updraftplus->log($this->desc.' error - failed to access the container ('.$e->getMessage().')');
$updraftplus->log(sprintf(__('%s error - failed to access the container', 'updraftplus'), $this->desc).' ('.$e->getMessage().')', 'error');
return 'service_unavailable';
}
// Get the container
try {
$container_object = $storage->getContainer($container);
} catch (Exception $e) {
$updraftplus->log('Could not access '.$this->desc.' container ('.get_class($e).', '.$e->getMessage().')');
$updraftplus->log(sprintf(__('Could not access %s container', 'updraftplus'), $this->desc).' ('.get_class($e).', '.$e->getMessage().')', 'error');
return 'container_access_error';
}
}
$ret = true;
foreach ($files as $file) {
$updraftplus->log($this->desc.": Delete remote: container=$container, path=$file");
// We need to search for chunks
$chunk_path = "chunk-do-not-delete-".$file;
try {
$objects = $container_object->objectList(array('prefix' => $chunk_path));
$index = 0;
while (false !== ($chunk = $objects->offsetGet($index)) && !empty($chunk)) {
try {
$name = $chunk->name;
$container_object->dataObject()->setName($name)->delete();
$updraftplus->log($this->desc.': Chunk deleted: '.$name);
} catch (Exception $e) {
$updraftplus->log($this->desc." chunk delete failed: $name: ".$e->getMessage());
}
$index++;
}
} catch (Exception $e) {
$updraftplus->log($this->desc.' chunk delete failed: '.$e->getMessage());
}
// Finally, delete the object itself
try {
$container_object->dataObject()->setName($file)->delete();
$updraftplus->log($this->desc.': Deleted: '.$file);
} catch (Exception $e) {
$updraftplus->log($this->desc.' delete failed: '.$e->getMessage());
$ret = 'file_delete_error';
}
}
return $ret;
}
public function download($file) {
global $updraftplus;
$opts = $this->get_options();
try {
$storage = $this->get_openstack_service($opts, UpdraftPlus_Options::get_updraft_option('updraft_ssl_useservercerts'), UpdraftPlus_Options::get_updraft_option('updraft_ssl_disableverify'));
} catch (AuthenticationError $e) {
$updraftplus->log($this->desc.' authentication failed ('.$e->getMessage().')');
$updraftplus->log(sprintf(__('%s authentication failed', 'updraftplus'), $this->desc).' ('.$e->getMessage().')', 'error');
return false;
} catch (Exception $e) {
$updraftplus->log($this->desc.' error - failed to access the container ('.$e->getMessage().')');
$updraftplus->log(sprintf(__('%s error - failed to access the container', 'updraftplus'), $this->desc).' ('.$e->getMessage().')', 'error');
return false;
}
$container = untrailingslashit($opts['path']);
$updraftplus->log($this->desc." download: ".$this->method."://$container/$file");
// Get the container
try {
$this->container_object = $storage->getContainer($container);
} catch (Exception $e) {
$updraftplus->log('Could not access '.$this->desc.' container ('.get_class($e).', '.$e->getMessage().')');
$updraftplus->log(sprintf(__('Could not access %s container', 'updraftplus'), $this->desc).' ('.get_class($e).', '.$e->getMessage().')', 'error');
return false;
}
// Get information about the object within the container
$remote_size = $this->get_remote_size($file);
if (false === $remote_size) {
$updraftplus->log('Could not access '.$this->desc.' object');
$updraftplus->log(sprintf(__('The %s object was not found', 'updraftplus'), $this->desc), 'error');
return false;
}
return (!is_bool($remote_size)) ? $updraftplus->chunked_download($file, $this, $remote_size, true, $this->container_object) : false;
}
public function chunked_download($file, $headers, $container_object) {
try {
$dl = $container_object->getObject($file, $headers);
} catch (Exception $e) {
global $updraftplus;
$updraftplus->log("$file: Failed to download (".$e->getMessage().")");
$updraftplus->log("$file: ".sprintf(__("%s Error", 'updraftplus'), $this->desc).": ".__('Error downloading remote file: Failed to download', 'updraftplus').' ('.$e->getMessage().")", 'error');
return false;
}
return $dl->getContent();
}
public function credentials_test_go($opts, $path, $useservercerts, $disableverify) {
if (preg_match("#^([^/]+)/(.*)$#", $path, $bmatches)) {
$container = $bmatches[1];
$path = $bmatches[2];
} else {
$container = $path;
$path = '';
}
if (empty($container)) {
esc_html_e('Failure: No container details were given.', 'updraftplus');
return;
}
try {
$storage = $this->get_openstack_service($opts, $useservercerts, $disableverify);
// @codingStandardsIgnoreLine
} catch (Guzzle\Http\Exception\ClientErrorResponseException $e) {
$response = $e->getResponse();
$code = $response->getStatusCode();
$reason = $response->getReasonPhrase();
if (401 == $code && 'Unauthorized' == $reason) {
esc_html_e('Authorisation failed (check your credentials)', 'updraftplus');
} else {
echo esc_html(__('Authorisation failed (check your credentials)', 'updraftplus')." ($code:$reason)");
}
return;
} catch (AuthenticationError $e) {
echo esc_html(sprintf(__('%s authentication failed', 'updraftplus'), $this->desc).' ('.$e->getMessage().')');
return;
} catch (Exception $e) {
echo esc_html(sprintf(__('%s authentication failed', 'updraftplus'), $this->desc).' ('.get_class($e).', '.$e->getMessage().')');
return;
}
try {
$container_object = $storage->getContainer($container);
// @codingStandardsIgnoreLine
} catch (Guzzle\Http\Exception\ClientErrorResponseException $e) {
$response = $e->getResponse();
$code = $response->getStatusCode();
$reason = $response->getReasonPhrase();
if (404 == $code) {
$container_object = $storage->createContainer($container);
} else {
echo esc_html(__('Authorisation failed (check your credentials)', 'updraftplus')." ($code:$reason)");
return;
}
} catch (Exception $e) {
echo esc_html(sprintf(__('%s authentication failed', 'updraftplus'), $this->desc).' ('.get_class($e).', '.$e->getMessage().')');
return;
}
if (!is_a($container_object, 'OpenCloud\ObjectStore\Resource\Container') && !is_a($container_object, 'Container')) {
echo esc_html(sprintf(__('%s authentication failed', 'updraftplus'), $this->desc).' ('.get_class($container_object).')');
return;
}
$try_file = md5(rand()).'.txt';
try {
$object = $container_object->uploadObject($try_file, 'UpdraftPlus test file', array('content-type' => 'text/plain'));
} catch (Exception $e) {
echo esc_html(sprintf(__('%s error - we accessed the container, but failed to create a file within it', 'updraftplus'), $this->desc).' ('.get_class($e).', '.$e->getMessage().')');
if (!empty($this->region)) echo ' '.esc_html(sprintf(__('Region: %s', 'updraftplus'), $this->region));
return;
}
echo esc_html(__('Success', 'updraftplus').": ".__('We accessed the container, and were able to create files within it.', 'updraftplus'));
if (!empty($this->region)) echo ' '.esc_html(sprintf(__('Region: %s', 'updraftplus'), $this->region));
try {
if (!empty($object)) {
// One OpenStack server we tested on did not delete unless we slept... some kind of race condition at their end
sleep(1);
$object->delete();
}
} catch (Exception $e) {
// Catch
}
}
/**
* Get the pre configuration template
* DEVELOPER NOTES: Please don't use/call this method anymore as it is currently used by OpenStack and Cloudfiles(Rackspace) storage, and it's consider to be removed in future versions. Once OpenStack and Cloudfiles templates are CSP-compliant, this should be removed and should be placed in the class child instead of the base class.
*
* @return String - the template
*/
public function get_pre_configuration_template() {
global $updraftplus_admin;
$classes = $this->get_css_classes(false);
?>
<tr class="<?php echo esc_attr($classes . ' ' . $this->method . '_pre_config_container');?>">
<td colspan="2">
<?php
if (!empty($this->img_url)) {
?>
<img alt="<?php echo esc_attr($this->long_desc); ?>" src="<?php echo esc_url(UPDRAFTPLUS_URL.$this->img_url); ?>">
<?php
}
?>
<br>
<?php
// Check requirements.
global $updraftplus_admin;
if (!function_exists('mb_substr')) {
$updraftplus_admin->show_double_warning('<strong>'.__('Warning', 'updraftplus').':</strong> '.sprintf(__('Your web server\'s PHP installation does not included a required module (%s).', 'updraftplus'), 'mbstring').' '.__('Please contact your web hosting provider\'s support.', 'updraftplus').' '.sprintf(__("UpdraftPlus's %s module <strong>requires</strong> %s.", 'updraftplus'), $this->desc, 'mbstring').' '.__('Please do not file any support requests; there is no alternative.', 'updraftplus'), $this->method);
}
$updraftplus_admin->curl_check($this->long_desc, false, $this->method);
echo '<br>';
$this->get_pre_configuration_middlesection_template();
?>
</td>
</tr>
<?php
}
/**
* Get the configuration template
* DEVELOPER NOTES: Please don't use/call this method anymore as it is currently used by OpenStack and Cloudfiles(Rackspace) storage, and it's consider to be removed in future versions. Once OpenStack and Cloudfiles templates are CSP-compliant, this should be removed and should be placed in the class child instead of the base class.
*
* @return String - the template, ready for substitutions to be carried out
*/
public function get_configuration_template() {
ob_start();
$template_str = ob_get_clean();
$template_str .= $this->get_configuration_middlesection_template();
$template_str .= $this->get_test_button_html($this->desc);
return $template_str;
}
}

View File

@@ -0,0 +1,15 @@
<?php
if (!defined('UPDRAFTPLUS_DIR')) die('No direct access.');
// Necessary to place the code in a separate file, because it uses namespaces, which cause a fatal error in PHP 5.2
if (version_compare(phpversion(), '5.3.3', '>=')) {
updraft_try_include_file('methods/openstack2.php', 'include_once');
} else {
updraft_try_include_file('methods/insufficient.php', 'include_once');
class UpdraftPlus_BackupModule_openstack extends UpdraftPlus_BackupModule_insufficientphp {
public function __construct() {
parent::__construct('openstack', 'OpenStack', '5.3.3');
}
}
}

View File

@@ -0,0 +1,264 @@
<?php
if (!defined('UPDRAFTPLUS_DIR')) die('No direct access allowed.');
// SDK uses namespacing - requires PHP 5.3 (actually the SDK states its requirements as 5.3.3)
// @codingStandardsIgnoreLine
use OpenCloud\OpenStack;
updraft_try_include_file('methods/openstack-base.php', 'require_once');
class UpdraftPlus_BackupModule_openstack extends UpdraftPlus_BackupModule_openstack_base {
public function __construct() {
// 4th parameter is a relative (to UPDRAFTPLUS_DIR) logo URL, which should begin with /, should we get approved for use of the OpenStack logo in future (have requested info)
parent::__construct('openstack', 'OpenStack', 'OpenStack (Swift)', '');
}
/**
* Get Openstack service
*
* @param String $opts THis contains: 'tenant', 'user', 'password', 'authurl', (optional) 'region'
* @param Boolean $useservercerts User server certificates
* @param String $disablesslverify Check to disable SSL Verify
* @return Array
*/
public function get_openstack_service($opts, $useservercerts = false, $disablesslverify = null) {
// 'tenant', 'user', 'password', 'authurl', 'path', (optional) 'region'
extract($opts);
if (null === $disablesslverify) $disablesslverify = UpdraftPlus_Options::get_updraft_option('updraft_ssl_disableverify');
if (empty($user) || empty($password) || empty($authurl)) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- $user, $password and $authurl being extracted in extract() line 29
throw new Exception(__('Authorisation failed (check your credentials)', 'updraftplus')); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error message to be escaped when caught and printed.
}
updraft_try_include_file('vendor/autoload.php', 'include_once');
global $updraftplus;
$updraftplus->log("OpenStack authentication URL: ".$authurl);// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- $authurl being extracted in extract() line 29
$client = new OpenStack($authurl, array(// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- $authurl being extracted in extract() line 29
'username' => $user,// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- $user being extracted in extract() line 29
'password' => $password,// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- $password being extracted in extract() line 29
'tenantName' => $tenant// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- $tenant being extracted in extract() line 29
));
$this->client = $client;
if ($disablesslverify) {
$client->setSslVerification(false);
} else {
if ($useservercerts) {
$client->setConfig(array($client::SSL_CERT_AUTHORITY => false));
} else {
$client->setSslVerification(UPDRAFTPLUS_DIR.'/includes/cacert.pem', true, 2);
}
}
$client->authenticate();
if (empty($region)) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UndefinedVariable -- The variable is defined below.
$catalog = $client->getCatalog();
if (!empty($catalog)) {
$items = $catalog->getItems();
if (is_array($items)) {
foreach ($items as $item) {
$name = $item->getName();
$type = $item->getType();
if ('swift' != $name || 'object-store' != $type) continue;
$eps = $item->getEndpoints();
if (!is_array($eps)) continue;
foreach ($eps as $ep) {
if (is_object($ep) && !empty($ep->region)) {
$region = $ep->region;
}
}
}
}
}
}
$this->region = $region;
return $client->objectStoreService('swift', $region);
}
/**
* This method overrides the parent method and lists the supported features of this remote storage option.
*
* @return Array - an array of supported features (any features not
* mentioned are assumed to not be supported)
*/
public function get_supported_features() {
// This options format is handled via only accessing options via $this->get_options()
return array('multi_options', 'config_templates', 'multi_storage', 'conditional_logic');
}
/**
* Retrieve default options for this remote storage module.
*
* @return Array - an array of options
*/
public function get_default_options() {
return array(
'user' => '',
'authurl' => '',
'password' => '',
'tenant' => '',
'path' => '',
'region' => ''
);
}
public function credentials_test($posted_settings) {
if (empty($posted_settings['user'])) {
echo esc_html(sprintf(__("Failure: No %s was given.", 'updraftplus'), __('username', 'updraftplus')));
return;
}
if (empty($posted_settings['password'])) {
echo esc_html(sprintf(__("Failure: No %s was given.", 'updraftplus'), __('password', 'updraftplus')));
return;
}
if (empty($posted_settings['tenant'])) {
echo esc_html(sprintf(__("Failure: No %s was given.", 'updraftplus'), _x('tenant', '"tenant" is a term used with OpenStack storage - Google for "OpenStack tenant" to get more help on its meaning', 'updraftplus')));
return;
}
if (empty($posted_settings['authurl'])) {
echo esc_html(sprintf(__("Failure: No %s was given.", 'updraftplus'), __('authentication URI', 'updraftplus')));
return;
}
$opts = array(
'user' => $posted_settings['user'],
'password' => $posted_settings['password'],
'authurl' => $posted_settings['authurl'],
'tenant' => $posted_settings['tenant'],
'region' => empty($posted_settings['region']) ? '' : $posted_settings['region'],
);
$this->credentials_test_go($opts, $posted_settings['path'], $posted_settings['useservercerts'], $posted_settings['disableverify']);
}
/**
* Check whether options have been set up by the user, or not
*
* @param Array $opts - the potential options
*
* @return Boolean
*/
public function options_exist($opts) {
if (is_array($opts) && $opts['user'] && '' !== $opts['user'] && !empty($opts['authurl'])) return true;
return false;
}
/**
* Get the pre configuration template
*
* @return String - the template
*/
public function get_pre_configuration_template() {
?>
<tr class="{{get_template_css_classes false}} {{method_id}}_pre_config_container">
<td colspan="2">
{{#if storage_image_url}}
<img alt="{{storage_long_description}}" src="{{storage_image_url}}">
{{/if}}
<br>
{{{mb_substr_existence_label}}}
{{{curl_existence_label}}}
<br>
<p>{{openstack_text_description}} <a href="{{{faq_link_url}}}" target="_blank">{{faq_link_text}}</a></p>
</td>
</tr>
<?php
}
/**
* Get the configuration template
*
* @return String - the template, ready for substitutions to be carried out
*/
public function get_configuration_template() {
ob_start();
?>
<tr class="{{get_template_css_classes true}}">
<th>{{input_authentication_uri_label}}:</th>
<td><input title="{{input_authentication_uri_title}}" data-updraft_settings_test="authurl" type="text" autocomplete="off" class="updraft_input--wide udc-wd-600" id="{{get_template_input_attribute_value "id" "authurl"}}" name="{{get_template_input_attribute_value "name" "authurl"}}" value="{{authurl}}" />
<br>
<em>{{input_authentication_uri_title}}</em>
</td>
</tr>
<tr class="{{get_template_css_classes true}}">
<th><a href="{{input_tenant_link_url}}" title="{{input_tenant_link_title}}" target="_blank">{{input_tenant_label}}</a>:</th>
<td><input data-updraft_settings_test="tenant" type="text" autocomplete="off" class="updraft_input--wide udc-wd-600" id="{{get_template_input_attribute_value "id" "tenant"}}" name="{{get_template_input_attribute_value "name" "tenant"}}" value="{{tenant}}" />
</td>
</tr>
<tr class="{{get_template_css_classes true}}">
<th>{{input_region_label}}:</th>
<td><input title="{{input_region_title}}" data-updraft_settings_test="region" type="text" autocomplete="off" class="updraft_input--wide udc-wd-600" id="{{get_template_input_attribute_value "id" "region"}}" name="{{get_template_input_attribute_value "name" "region"}}" value="{{region}}" />
<br>
<em>{{input_region_title}}</em>
</td>
</tr>
<tr class="{{get_template_css_classes true}}">
<th>{{input_username_label}}:</th>
<td><input data-updraft_settings_test="user" type="text" autocomplete="off" class="updraft_input--wide udc-wd-600" id="{{get_template_input_attribute_value "id" "user"}}" name="{{get_template_input_attribute_value "name" "user"}}" value="{{user}}" />
</td>
</tr>
<tr class="{{get_template_css_classes true}}">
<th>{{input_password_label}}:</th>
<td><input data-updraft_settings_test="password" type="{{input_password_type}}" autocomplete="off" class="updraft_input--wide udc-wd-600" id="{{get_template_input_attribute_value "id" "password"}}" name="{{get_template_input_attribute_value "name" "password"}}" value="{{password}}" />
</td>
</tr>
<tr class="{{get_template_css_classes true}}">
<th>{{input_container_label}}:</th>
<td><input data-updraft_settings_test="path" type="text" class="updraft_input--wide udc-wd-600" id="{{get_template_input_attribute_value "id" "path"}}" name="{{get_template_input_attribute_value "name" "path"}}" value="{{path}}" /></td>
</tr>
{{{get_template_test_button_html "OpenStack (Swift)"}}}
<?php
return ob_get_clean();
}
/**
* Retrieve a list of template properties by taking all the persistent variables and methods of the parent class and combining them with the ones that are unique to this module, also the necessary HTML element attributes and texts which are also unique only to this backup module
* NOTE: Please sanitise all strings that are required to be shown as HTML content on the frontend side (i.e. wp_kses()), or any other technique to prevent XSS attacks that could come via WP hooks
*
* @return Array an associative array keyed by names that describe themselves as they are
*/
public function get_template_properties() {
global $updraftplus, $updraftplus_admin;
$properties = array(
'storage_image_url' => !empty($this->img_url) ? UPDRAFTPLUS_URL.$this->img_url : '',
'storage_long_description' => $this->long_desc,
'mb_substr_existence_label' => !apply_filters('updraftplus_openstack_mbsubstr_exists', function_exists('mb_substr')) ? wp_kses($updraftplus_admin->show_double_warning('<strong>'.__('Warning', 'updraftplus').':</strong> '.sprintf(__('Your web server\'s PHP installation does not include a required module (%s).', 'updraftplus'), 'mbstring').' '.__('Please contact your web hosting provider\'s support.', 'updraftplus').' '.sprintf(__("UpdraftPlus's %s module <strong>requires</strong> %s.", 'updraftplus'), $this->desc, 'mbstring').' '.__('Please do not file any support requests; there is no alternative.', 'updraftplus'), $this->method, false), $this->allowed_html_for_content_sanitisation()) : '',
'curl_existence_label' => wp_kses($updraftplus_admin->curl_check($this->long_desc, false, $this->method.' hidden-in-updraftcentral', false), $this->allowed_html_for_content_sanitisation()),
'openstack_text_description' => __('Get your access credentials from your OpenStack Swift provider, and then pick a container name to use for storage.', 'updraftplus').' '.__('This container will be created for you if it does not already exist.', 'updraftplus'),
'faq_link_text' => __('Also, you should read this important FAQ.', 'updraftplus'),
'faq_link_url' => wp_kses(apply_filters("updraftplus_com_link", "https://teamupdraft.com/documentation/updraftplus/topics/cloud-storage/rackspace/there-are-extra-files-in-my-rackspace-cloud-files-container/?utm_source=udp-plugin&utm_medium=referral&utm_campaign=paac&utm_content=openstack-important-faq&utm_creative_format=tex"), array(), array('http', 'https')),
'input_authentication_uri_label' => __('Authentication URI', 'updraftplus'),
'input_authentication_uri_title' => _x('This needs to be a v2 (Keystone) authentication URI; v1 (Swauth) is not supported.', 'Keystone and swauth are technical terms which cannot be translated', 'updraftplus'),
'input_tenant_label' => __('Tenant', 'updraftplus'),
'input_tenant_link_url' => 'https://docs.openstack.org/openstack-ops/content/projects_users.html',
'input_tenant_link_title' => __('Follow this link for more information', 'updraftplus'),
'input_region_label' => __('Region', 'updraftplus'),
'input_region_title' => __('Leave this blank, and a default will be chosen.', 'updraftplus'),
'input_username_label' => __('Username', 'updraftplus'),
'input_password_label' => __('Password', 'updraftplus'),
'input_password_type' => apply_filters('updraftplus_admin_secret_field_type', 'password'),
'input_container_label' => __('Container', 'updraftplus'),
'input_test_label' => sprintf(__('Test %s Settings', 'updraftplus'), $updraftplus->backup_methods[$this->get_id()]),
);
return wp_parse_args($properties, $this->get_persistent_variables_and_methods());
}
}

View File

@@ -0,0 +1,25 @@
<?php
if (!defined('UPDRAFTPLUS_DIR')) die('No direct access.');
if (class_exists('UpdraftPlus_Addons_RemoteStorage_pcloud')) {
class UpdraftPlus_BackupModule_pcloud extends UpdraftPlus_Addons_RemoteStorage_pcloud {
public function __construct() {
parent::__construct('pcloud', 'pCloud', false, 'pcloud-logo.png');
}
}
} else {
updraft_try_include_file('methods/addon-not-yet-present.php', 'include_once');
/**
* N.B. UpdraftPlus_BackupModule_AddonNotYetPresent extends UpdraftPlus_BackupModule
*/
class UpdraftPlus_BackupModule_pcloud extends UpdraftPlus_BackupModule_AddonNotYetPresent {
public function __construct() {
parent::__construct('pcloud', 'pCloud', false, 'pcloud-logo.png');
}
}
}

View File

@@ -0,0 +1,539 @@
<?php
if (!defined('UPDRAFTPLUS_DIR')) die('No direct access allowed');
if (!class_exists('UpdraftPlus_RemoteStorage_Addons_Base_v2')) updraft_try_include_file('methods/addon-base-v2.php', 'require_once');
class UpdraftPlus_BackupModule_remotesend extends UpdraftPlus_RemoteStorage_Addons_Base_v2 {
private $default_chunk_size;
private $remotesend_use_chunk_size;
private $remotesend_chunked_wp_error;
private $try_format_upgrade = false;
private $remotesend_file_size;
private $remotesend_uploaded_size;
private $remote_sent_defchunk_transient;
/**
* Class constructor
*/
public function __construct() {
// 3rd parameter: chunking? 4th: Test button?
parent::__construct('remotesend', 'Remote send', false, false);
}
/**
* Supplies the list of keys for options to be saved in the backup job.
*
* @return Array
*/
public function get_credentials() {
return array('updraft_ssl_disableverify', 'updraft_ssl_nossl', 'updraft_ssl_useservercerts');
}
/**
* Upload a single file
*
* @param String $file - the basename of the file to upload
* @param String $from - the full path of the file
*
* @return Boolean - success status. Failures can also be thrown as exceptions.
*/
public function do_upload($file, $from) {
global $updraftplus;
$opts = $this->options;
try {
$storage = $this->bootstrap();
if (is_wp_error($storage)) throw new Exception($storage->get_error_message());
if (!is_object($storage)) throw new Exception("RPC service error");
} catch (Exception $e) {
$message = $e->getMessage().' ('.get_class($e).') (line: '.$e->getLine().', file: '.$e->getFile().')';
$this->log("RPC service error: ".$message);
$this->log($message, 'error');
return false;
}
$filesize = filesize($from);
$this->remotesend_file_size = $filesize;
// See what the sending side currently has. This also serves as a ping. For that reason, we don't try/catch - we let them be caught at the next level up.
$get_remote_size = $this->send_message('get_file_status', $file, 30);
if (is_wp_error($get_remote_size)) {
$err_msg = $get_remote_size->get_error_message();
$err_data = $get_remote_size->get_error_data();
$err_code = $get_remote_size->get_error_code();
if (!is_numeric($err_code) && isset($err_data['response']['code'])) {
$err_code = $err_data['response']['code'];
$err_msg = UpdraftPlus_HTTP_Error_Descriptions::get_http_status_code_description($err_code);
} elseif (is_string($err_data) && preg_match('/captcha|verify.*human|turnstile/i', $err_data)) {
$err_msg = __('We are unable to proceed with the process due to a bot verification requirement', 'updraftplus');
}
throw new Exception($err_msg.' (get_file_status: '.$err_code.')'); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error message to be escaped when caught and printed.
}
if (!is_array($get_remote_size) || empty($get_remote_size['response'])) throw new Exception(__('Unexpected response:', 'updraftplus').' '.serialize($get_remote_size)); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error message to be escaped when caught and printed.
if ('error' == $get_remote_size['response']) {
$msg = $get_remote_size['data'];
// Could interpret the codes to get more interesting messages directly to the user
throw new Exception(__('Error:', 'updraftplus').' '.$msg); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error message to be escaped when caught and printed.
}
if (empty($get_remote_size['data']) || !isset($get_remote_size['data']['size']) || 'file_status' != $get_remote_size['response']) throw new Exception(__('Unexpected response:', 'updraftplus').' '.serialize($get_remote_size)); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error message to be escaped when caught and printed.
// Possible statuses: 0=temporary file (or not present), 1=file
if (empty($get_remote_size['data'])) {
$remote_size = 0;
$remote_status = 0;
} else {
$remote_size = (int) $get_remote_size['data']['size'];
$remote_status = $get_remote_size['data']['status'];
}
$this->log("$file: existing size: ".$remote_size);
// Perhaps it already exists? (if we didn't get the final confirmation)
if ($remote_size >= $filesize && $remote_status) {
$this->log("$file: already uploaded");
return true;
}
// Length = 44 (max = 45)
$this->remote_sent_defchunk_transient = 'ud_rsenddck_'.md5($opts['name_indicator']);
if (empty($this->default_chunk_size)) {
// Default is 2MB. After being b64-encoded twice, this is ~ 3.7MB = 113 seconds on 32KB/s uplink
$default_chunk_size = $updraftplus->jobdata_get('clone_job') ? 4194304 : 2097152;
if (defined('UPDRAFTPLUS_REMOTESEND_DEFAULT_CHUNK_BYTES') && UPDRAFTPLUS_REMOTESEND_DEFAULT_CHUNK_BYTES >= 16384) $default_chunk_size = UPDRAFTPLUS_REMOTESEND_DEFAULT_CHUNK_BYTES;
$this->default_chunk_size = $default_chunk_size;
}
$default_chunk_size = $this->default_chunk_size;
if (false !== ($saved_default_chunk_size = get_transient($this->remote_sent_defchunk_transient)) && $saved_default_chunk_size > 16384) {
// Don't go lower than 256KB for the *default*. (The job size can go lower).
$default_chunk_size = max($saved_default_chunk_size, 262144);
}
$this->remotesend_use_chunk_size = $updraftplus->jobdata_get('remotesend_chunksize', $default_chunk_size);
if (0 == $remote_size && $this->remotesend_use_chunk_size == $this->default_chunk_size && $updraftplus->current_resumption - max($updraftplus->jobdata_get('uploaded_lastreset'), 1) > 1) {
$new_chunk_size = floor($this->remotesend_use_chunk_size / 2);
$this->log("No uploading activity has been detected for a while; reducing chunk size in case a timeout was occurring. New chunk size: ".$new_chunk_size);
$this->remotesend_set_new_chunk_size($new_chunk_size);
}
try {
if (false != ($handle = fopen($from, 'rb'))) {
$this->remotesend_uploaded_size = $remote_size;
$ret = $updraftplus->chunked_upload($this, $file, $this->method."://".trailingslashit($opts['url']).$file, $this->description, $this->remotesend_use_chunk_size, $remote_size, true);
fclose($handle);
return $ret;
} else {
throw new Exception("Failed to open file for reading: $from");
}
} catch (Exception $e) {
$this->log("upload: error (".get_class($e)."): ($file) (".$e->getMessage().") (line: ".$e->getLine().', file: '.$e->getFile().')');
if (!empty($this->remotesend_chunked_wp_error) && is_wp_error($this->remotesend_chunked_wp_error)) {
$this->log("Exception data: ".base64_encode(serialize($this->remotesend_chunked_wp_error->get_error_data())));
}
return false;
}
return true;
}
/**
* Chunked upload
*
* @param String $file Specific file to be used in chunked upload
* @param Resource $fp File handle
* @param Integer $chunk_index The index of the chunked data
* @param Integer $upload_size Size of the upload
* @param Integer $upload_start String the upload starts on
* @param Integer $upload_end String the upload ends on
*
* @return Boolean|Integer Result (N.B> (int)1 means the same as true, but additionally indicates "don't log it")
*/
public function chunked_upload($file, $fp, $chunk_index, $upload_size, $upload_start, $upload_end) {
// Condition used to be "$upload_start < $this->remotesend_uploaded_size" - but this assumed that the other side never failed after writing only some bytes to disk
// $upload_end is the byte offset of the final byte. Therefore, add 1 onto it when comparing with a size.
if ($upload_end + 1 <= $this->remotesend_uploaded_size) return 1;
global $updraftplus;
$chunk = fread($fp, $upload_size);
if (false === $chunk) {
$this->log("upload: $file: fread failure ($upload_start)");
return false;
}
$try_again = false;
$data = array('file' => $file, 'data' => base64_encode($chunk), 'start' => $upload_start);
if ($upload_end+1 >= $this->remotesend_file_size) {
$data['last_chunk'] = true;
if ('' != ($label = $updraftplus->jobdata_get('label'))) $data['label'] = $label;
}
// ~ 3.7MB of data typically - timeout allows for 15.9KB/s
try {
$put_chunk = $this->send_message('send_chunk', $data, 240);
} catch (Exception $e) {
$try_again = true;
}
if ($try_again || is_wp_error($put_chunk)) {
// 413 = Request entity too large
// Don't go lower than 64KB chunks (i.e. 128KB/2)
// Note that mod_security can be configured to 'helpfully' decides to replace HTTP error codes + messages with a simple serving up of the site home page, which means that we need to also guess about other reasons this condition may have occurred other than detecting via the direct 413 code. Of course, our search for wp-includes|wp-content|WordPress|/themes/ would be thwarted by someone who tries to hide their WP. The /themes/ is pretty hard to hide, as the theme directory is always <wp-content-dir>/themes - even if you moved your wp-content. The point though is just a 'best effort' - this doesn't have to be infallible.
if (is_wp_error($put_chunk)) {
$error_data = $put_chunk->get_error_data();
$is_413 = ('unexpected_http_code' == $put_chunk->get_error_code() && (
413 == $error_data
|| (is_array($error_data) && !empty($error_data['response']['code']) && 413 == $error_data['response']['code'])
)
);
$is_timeout = ('http_request_failed' == $put_chunk->get_error_code() && false !== strpos($put_chunk->get_error_message(), 'timed out'));
if ($this->remotesend_use_chunk_size >= 131072 && ($is_413 || $is_timeout || ('response_not_understood' == $put_chunk->get_error_code() && (false !== strpos($error_data, 'wp-includes') || false !== strpos($error_data, 'wp-content') || false !== strpos($error_data, 'WordPress') || false !== strpos($put_chunk->get_error_data(), '/themes/'))))) {
if (1 == $chunk_index) {
$new_chunk_size = floor($this->remotesend_use_chunk_size / 2);
$this->remotesend_set_new_chunk_size($new_chunk_size);
$log_msg = "Returned WP_Error: code=".$put_chunk->get_error_code();
if ('unexpected_http_code' == $put_chunk->get_error_code()) $log_msg .= ' ('.$error_data.')';
$log_msg .= " - reducing chunk size to: ".$new_chunk_size;
$this->log($log_msg);
return new WP_Error('reduce_chunk_size', 'HTTP 413 or possibly equivalent condition on first chunk - should reduce chunk size', $new_chunk_size);
} elseif ($this->remotesend_use_chunk_size >= 131072 && $is_413) {
// In this limited case, where we got a 413 but the chunk is not number 1, our algorithm/architecture doesn't allow us to just resume immediately with a new chunk size. However, we can just have UD reduce the chunk size on its next resumption.
$new_chunk_size = floor($this->remotesend_use_chunk_size / 2);
$this->remotesend_set_new_chunk_size($new_chunk_size);
$log_msg = "Returned WP_Error: code=".$put_chunk->get_error_code().", message=".$put_chunk->get_error_message();
$log_msg .= " - reducing chunk size to: ".$new_chunk_size." and then scheduling resumption/aborting";
$this->log($log_msg);
UpdraftPlus_Job_Scheduler::reschedule(50);
UpdraftPlus_Job_Scheduler::record_still_alive();
die;
}
}
}
$put_chunk = $this->send_message('send_chunk', $data, 240);
}
if (is_wp_error($put_chunk)) {
// The exception handler is within this class. So we can store the data.
$this->remotesend_chunked_wp_error = $put_chunk;
throw new Exception($put_chunk->get_error_message().' ('.$put_chunk->get_error_code().')'); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error message to be escaped when caught and printed.
}
if (!is_array($put_chunk) || empty($put_chunk['response'])) throw new Exception(__('Unexpected response:', 'updraftplus').' '.serialize($put_chunk)); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error message to be escaped when caught and printed.
if ('error' == $put_chunk['response']) {
$msg = $put_chunk['data'];
// Could interpret the codes to get more interesting messages directly to the user
// The textual prefixes here were added after 1.12.5 - hence optional when parsing
if (preg_match('/^invalid_start_too_big:(start=)?(\d+),(existing_size=)?(\d+)/', $msg, $matches)) {
$existing_size = $matches[2];
if ($existing_size < $this->remotesend_uploaded_size) {
// The file on the remote system seems to have shrunk. Could be some load-balancing system with a distributed filesystem that is only eventually consistent.
return new WP_Error('try_again', 'File on remote system is smaller than expected - perhaps an eventually-consistent filesystem (wait and retry)');
}
}
throw new Exception(__('Error:', 'updraftplus').' '.$msg); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error message to be escaped when caught and printed.
}
if ('file_status' != $put_chunk['response']) throw new Exception(__('Unexpected response:', 'updraftplus').' '.serialize($put_chunk)); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Error message to be escaped when caught and printed.
// Possible statuses: 0=temporary file (or not present), 1=file
if (empty($put_chunk['data']) || !is_array($put_chunk['data'])) {
$this->log("Unexpected response when putting chunk $chunk_index: ".serialize($put_chunk));
return false;
} else {
$remote_size = (int) $put_chunk['data']['size'];
$this->remotesend_uploaded_size = $remote_size;
}
return true;
}
/**
* This function will send a message to the remote site to inform it that the backup has finished sending, on success will update the jobdata key upload_completed and return true else false
*
* @return Boolean - returns true on success or false on error, all errors are logged to the backup log
*/
public function upload_completed() {
global $updraftplus;
$service = $updraftplus->jobdata_get('service');
$remote_sent = (!empty($service) && ((is_array($service) && in_array('remotesend', $service)) || 'remotesend' === $service));
if (!$remote_sent) return;
// If this is a partial upload then don't send the upload complete signal
if ('partialclouduploading' == $updraftplus->jobdata_get('jobstatus')) return;
// ensure options have been loaded
$this->options = $this->get_options();
try {
$storage = $this->bootstrap();
if (is_wp_error($storage)) throw new Exception($storage->get_error_message());
if (!is_object($storage)) throw new Exception("RPC service error");
} catch (Exception $e) {
$message = $e->getMessage().' ('.get_class($e).') (line: '.$e->getLine().', file: '.$e->getFile().')';
$this->log("RPC service error: ".$message);
$this->log($message, 'error');
return false;
}
if (is_wp_error($storage)) return $updraftplus->log_wp_error($storage, false, true);
for ($i = 0; $i < 3; $i++) {
$success = false;
$response = $this->send_message('upload_complete', array('job_id' => $updraftplus->nonce), 30);
if (is_wp_error($response)) {
$message = $response->get_error_message().' (upload_complete: '.$response->get_error_code().')';
$this->log("RPC service error: ".$message);
$this->log($message, 'error');
} elseif (!is_array($response) || empty($response['response'])) {
$this->log("RPC service error: ".serialize($response));
$this->log(serialize($response), 'error');
} elseif ('error' == $response['response']) {
// Could interpret the codes to get more interesting messages directly to the user
$msg = $response['data'];
$this->log("RPC service error: ".$msg);
$this->log($msg, 'error');
} elseif ('file_status' == $response['response']) {
$success = true;
break;
}
sleep(5);
}
return $success;
}
/**
* Change the chunk size
*
* @param Integer $new_chunk_size - in bytes
*/
private function remotesend_set_new_chunk_size($new_chunk_size) {
global $updraftplus;
$this->remotesend_use_chunk_size = $new_chunk_size;
$updraftplus->jobdata_set('remotesend_chunksize', $new_chunk_size);
// Save, so that we don't have to cycle through the illegitimate/larger chunk sizes each time. Set the transient expiry to 120 days, in case they change hosting/configuration - so that we're not stuck on the lower size forever.
set_transient($this->remote_sent_defchunk_transient, $new_chunk_size, 86400*120);
}
/**
* Send a message to the remote site
*
* @param String $message - the message identifier
* @param Array|Null $data - the data to send with the message
* @param Integer $timeout - timeout in waiting for a response
*
* @return Array|WP_Error - results, or an error
*/
private function send_message($message, $data = null, $timeout = 30) {
$storage = $this->get_storage();
if (is_array($this->try_format_upgrade) && is_array($data)) {
$data['sender_public'] = $this->try_format_upgrade['local_public'];
}
$response = $storage->send_message($message, $data, $timeout);
if (is_array($response) && !empty($response['data']) && is_array($response['data'])) {
if (!empty($response['data']['php_events']) && !empty($response['data']['previous_data'])) {
foreach ($response['data']['php_events'] as $logline) {
$this->log("From remote side: ".$logline);
}
$response['data'] = $response['data']['previous_data'];
}
if (is_array($response) && is_array($response['data']) && !empty($response['data']['got_public'])) {
$name_indicator = $this->try_format_upgrade['name_indicator'];
$remotesites = UpdraftPlus_Options::get_updraft_option('updraft_remotesites');
foreach ($remotesites as $key => $site) {
if (!is_array($site) || empty($site['name_indicator']) || $site['name_indicator'] != $name_indicator) continue;
// This means 'format 2'
$this->try_format_upgrade = true;
$remotesites[$key]['remote_got_public'] = 1;
// If this DB save fails, they'll have to recreate the key
UpdraftPlus_Options::update_updraft_option('updraft_remotesites', $remotesites);
// Now we need to get a fresh storage object, because the remote end will no longer accept messages with format=1
$this->set_storage(null);
$this->do_bootstrap(null);
break;
}
}
}
return $response;
}
public function do_bootstrap($opts, $connect = true) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- $connect unused
global $updraftplus;
$updraftplus->ensure_phpseclib();
if (!class_exists('UpdraftPlus_Remote_Communications_V2')) include_once(apply_filters('updraftplus_class_udrpc_path', UPDRAFTPLUS_DIR.'/vendor/team-updraft/common-libs/src/updraft-rpc/class-udrpc2.php', $updraftplus->version));
$opts = $this->get_opts();
try {
$ud_rpc = new UpdraftPlus_Remote_Communications_V2($opts['name_indicator']);
if (!empty($opts['format_support']) && 2 == $opts['format_support'] && !empty($opts['local_private']) && !empty($opts['local_public']) && !empty($opts['remote_got_public'])) {
$ud_rpc->set_message_format(2);
$ud_rpc->set_key_remote($opts['key']);
$ud_rpc->set_key_local($opts['local_private']);
} else {
// Enforce the legacy communications protocol (which is only suitable for when only one side only sends, and the other only receives - which is what we happen to do)
$ud_rpc->set_message_format(1);
$ud_rpc->set_key_local($opts['key']);
if (!empty($opts['format_support']) && 2 == $opts['format_support'] && !empty($opts['local_public']) && !empty($opts['local_private'])) {
$this->try_format_upgrade = array('name_indicator' => $opts['name_indicator'], 'local_public' => $opts['local_public']);
}
}
$ud_rpc->set_destination_url($opts['url']);
$ud_rpc->activate_replay_protection();
} catch (Exception $e) {
return new WP_Error('rpc_failure', "Commmunications failure: ".$e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
}
do_action('updraftplus_remotesend_udrpc_object_obtained', $ud_rpc, $opts);
$this->set_storage($ud_rpc);
return $ud_rpc;
}
public function options_exist($opts) {
if (is_array($opts) && !empty($opts['url']) && !empty($opts['name_indicator']) && !empty($opts['key'])) return true;
return false;
}
public function get_opts() {
global $updraftplus;
$opts = $updraftplus->jobdata_get('remotesend_info');
$opts = $this->clone_remotesend_options($opts);
if (true === $this->try_format_upgrade && is_array($opts)) $opts['remote_got_public'] = 1;
return is_array($opts) ? $opts : array();
}
/**
* This function will check the options we have for the remote send and if it's a clone job and there are missing settings it will call the mothership to get this information.
*
* @param Array $opts - an array of remote send options
*
* @return Array - an array of options
*/
public function clone_remotesend_options($opts) {
// Don't call self::log() - this then requests options (to get the label), causing an infinite loop.
global $updraftplus, $updraftplus_admin;
if (empty($updraftplus_admin)) updraft_try_include_file('admin.php', 'include_once');
$clone_job = $updraftplus->jobdata_get('clone_job');
// check this is a clone job before we proceed
if (empty($clone_job)) return $opts;
// check that we don't already have the needed information
if (is_array($opts) && !empty($opts['url']) && !empty($opts['name_indicator']) && !empty($opts['key'])) return $opts;
$current_stage = $updraftplus->jobdata_get('jobstatus');
$updraftplus->jobdata_set('jobstatus', 'clonepolling');
$clone_id = $updraftplus->jobdata_get('clone_id');
$clone_url = $updraftplus->jobdata_get('clone_url');
$clone_key = $updraftplus->jobdata_get('clone_key');
$secret_token = $updraftplus->jobdata_get('secret_token');
$clone_region = $updraftplus->jobdata_get('clone_region');
if (empty($clone_id) && empty($secret_token)) return $opts;
$updraftplus->log("Polling for UpdraftClone (ID: {$clone_id} Region: {$clone_region}) migration information.");
$params = array('clone_id' => $clone_id, 'secret_token' => $secret_token);
$response = $updraftplus->get_updraftplus_clone()->clone_info_poll($params);
if (!isset($response['status']) || 'success' != $response['status']) {
if ('clone_network_not_found' == $response['code'] && 0 === $updraftplus->current_resumption) {
$updraftplus->log("UpdraftClone network information is not ready yet please wait while the clone finishes provisioning.");
} else {
$updraftplus->log("UpdraftClone migration information poll failed with code: " . $response['code']);
}
return $opts;
}
if (!isset($response['data']) || !isset($response['data']['url']) || !isset($response['data']['key'])) {
$updraftplus->log("UpdraftClone migration information poll unexpected return information with code:" . $response['code']);
return $opts;
}
$clone_url = $response['data']['url'];
$clone_key = json_decode($response['data']['key'], true);
if (empty($clone_url) || empty($clone_key)) {
$updraftplus->log("UpdraftClone migration information not found (probably still provisioning): will poll again in 60");
UpdraftPlus_Job_Scheduler::reschedule(60);
UpdraftPlus_Job_Scheduler::record_still_alive();
die;
}
// Store the information
$remotesites = UpdraftPlus_Options::get_updraft_option('updraft_remotesites');
if (!is_array($remotesites)) $remotesites = array();
foreach ($remotesites as $k => $rsite) {
if (!is_array($rsite)) continue;
if ($rsite['url'] == $clone_key['url']) unset($remotesites[$k]);
}
$remotesites[] = $clone_key;
UpdraftPlus_Options::update_updraft_option('updraft_remotesites', $remotesites);
$updraftplus->jobdata_set_multi('clone_url', $clone_url, 'clone_key', $clone_key, 'remotesend_info', $clone_key, 'jobstatus', $current_stage);
return $clone_key;
}
// do_listfiles(), do_download(), do_delete() : the absence of any method here means that the parent will correctly throw an error
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,261 @@
<?php
if (!defined('UPDRAFTPLUS_DIR')) die('No direct access allowed.');
updraft_try_include_file('methods/s3.php', 'require_once');
/**
* Converted to multi-options (Feb 2017-) and previous options conversion removed: Yes
*/
class UpdraftPlus_BackupModule_s3generic extends UpdraftPlus_BackupModule_s3 {
protected $provider_can_use_aws_sdk = false;
protected $provider_has_regions = false;
/**
* Given an S3 object, possibly set the region on it
*
* @param Object $obj - like UpdraftPlus_S3
* @param String $region
* @param String $bucket_name
*/
protected function set_region($obj, $region = '', $bucket_name = '') {
$config = $this->get_config();
$endpoint = ('' != $region && 'n/a' != $region) ? $region : $config['endpoint'];
if (!empty($endpoint)) {
$endpoint = preg_replace('/^(http|https):\/\//i', '', trim($endpoint));
}
$log_message = "Set endpoint (".get_class($obj)."): $endpoint";
$log_message_append = '';
if (is_string($endpoint) && preg_match('/^(.*):(\d+)$/', $endpoint, $matches)) {
$endpoint = $matches[1];
$port = $matches[2];
$log_message_append = ", port=$port";
$obj->setPort($port);
}
// This provider requires domain-style access. In future it might be better to provide an option rather than hard-coding the knowledge.
if (is_string($endpoint) && preg_match('/\.aliyuncs\.com$/i', $endpoint)) {
$obj->useDNSBucketName(true, $bucket_name);
}
global $updraftplus;
if ($updraftplus->backup_time) $this->log($log_message.$log_message_append);
$obj->setEndpoint($endpoint);
}
/**
* This method overrides the parent method and lists the supported features of this remote storage option.
*
* @return Array - an array of supported features (any features not mentioned are asuumed to not be supported)
*/
public function get_supported_features() {
// This options format is handled via only accessing options via $this->get_options()
return array('multi_options', 'config_templates', 'multi_storage', 'conditional_logic');
}
/**
* Retrieve default options for this remote storage module.
*
* @return Array - an array of options
*/
public function get_default_options() {
return array(
'accesskey' => '',
'secretkey' => '',
'path' => '',
'endpoint' => '',
);
}
/**
* Retrieve specific options for this remote storage module
*
* @param Boolean $force_refresh - if set, and if relevant, don't use cached credentials, but get them afresh
*
* @return Array - an array of options
*/
protected function get_config($force_refresh = false) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- Filter use
$opts = $this->get_options();
$opts['whoweare'] = 'S3';
$opts['whoweare_long'] = __('S3 (Compatible)', 'updraftplus');
$opts['key'] = 's3generic';
return $opts;
}
/**
* Get the pre configuration template
*/
public function get_pre_configuration_template() {
?>
<tr class="{{get_template_css_classes false}} S3_pre_config_container">
<td colspan="2">
{{{pre_template_opening_html}}}
<br>
{{{xmlwriter_existence_label}}}
{{{simplexmlelement_existence_label}}}
{{{curl_existence_label}}}
<br>
<p>
{{{ssl_certificates_errors_link_text}}}
</p>
</td>
</tr>
<?php
}
/**
* Get partial templates of the S3-Generic remote storage, the partial template is recognised by its name. To find out a name of partial template, look for the partial call syntax in the template, it's enclosed by double curly braces (i.e. {{> partial_template_name }})
*
* @return Array an associative array keyed by name of the partial templates
*/
public function get_partial_templates() {
$partial_templates = array();
$partial_templates['s3generic_additional_configuration_top'] = '';
ob_start();
?>
<tr class="{{get_template_css_classes true}}">
<th>{{input_endpoint_label}}:</th>
<td>
<input data-updraft_settings_test="endpoint" type="text" class="updraft_input--wide udc-wd-600" id="{{get_template_input_attribute_value "id" "endpoint"}}" name="{{get_template_input_attribute_value "name" "endpoint"}}" value="{{endpoint}}" />
</td>
</tr>
<tr class="{{get_template_css_classes true}}">
<th>{{input_bucket_access_style_label}}:<br>{{{input_bucket_access_style_readmore}}}</th>
<td>
<select data-updraft_settings_test="bucket_access_style" id="{{get_template_input_attribute_value "id" "bucket_access_style"}}" name="{{get_template_input_attribute_value "name" "bucket_access_style"}}" class="udc-wd-600">
{{#each input_bucket_access_style_option_labels}}
<option {{#ifeq ../bucket_access_style @key}}selected="selected"{{/ifeq}} value="{{@key}}">{{this}}</option>
{{/each}}
</select>
</td>
</tr>
<tr class="{{get_template_css_classes true}}">
<th>{{input_signature_version_label}}:<br>{{{input_signature_version_readmore}}}</th>
<td>
<select data-updraft_settings_test="signature_version" id="{{get_template_input_attribute_value "id" "signature_version"}}" name="{{get_template_input_attribute_value "name" "signature_version"}}" class="udc-wd-600">
{{#each input_signature_version_option_labels}}
<option {{#ifeq ../signature_version @key}}selected="selected"{{/ifeq}} value="{{@key}}">{{this}}</option>
{{/each}}
</select>
</td>
</tr>
<?php
$partial_templates['s3generic_additional_configuration_bottom'] = ob_get_clean();
return wp_parse_args(apply_filters('updraft_'.$this->get_id().'_partial_templates', $partial_templates), parent::get_partial_templates());
}
/**
* Modifies handerbar template options
* The function require because It should override parent class's UpdraftPlus_BackupModule_s3::transform_options_for_template() functionality with no operation.
*
* @param array $opts
* @return Array - Modified handerbar template options
*/
public function transform_options_for_template($opts) {
if (!empty($opts['instance_id']) && 'default' !== $opts['instance_id']) {
if (!isset($opts['signature_version'])) { // this check is to find out whether we're dealing with a pre-existing configuration or not
$opts['signature_version'] = 'v2'; // the pre-existing S3-Compatible configurations before signature_version was introduced by default use SigV2, so we wan't to keep it that way as we don't want to break what's already working
$opts['signature_version'] = apply_filters('updraftplus_s3_signature_version', $opts['signature_version'], false, $this);
if (!empty($opts['endpoint'])) {
if (preg_match('/\.(leviia|r2\.cloudflarestorage)\.com$/i', $opts['endpoint']) || (preg_match('/\.amazonaws\.com$/i', $opts['endpoint']) && !empty($opts['bucket_access_style']) && 'virtual_host_style' === $opts['bucket_access_style'])) {
// due to the merge of S3-generic bucket access style MR on March 2021, if virtual-host bucket access style is selected, connecting to an amazonaws bucket location where the user doesn't have an access to it will throw an S3 InvalidRequest exception. It requires the signature to be set to version 4
$opts['signature_version'] = 'v4';
}
}
}
if (!$this->options_exist($opts)) $opts['signature_version'] = 'v4'; // if no pre-existing S3-Compatible configurations were setup, there would always be an initial instance id created with a blank configuration form (no access key, secret key, location, and endpoint), this initial instance id/form should use SigV4
}
return $opts;
}
/**
* Check whether options have been set up by the user, or not
*
* @param Array $opts - the potential options
*
* @return Boolean
*/
public function options_exist($opts) {
return (parent::options_exist($opts) && !empty($opts['endpoint']));
}
/**
* Retrieve a list of template properties by taking all the persistent variables and methods of the parent class and combining them with the ones that are unique to this module, also the necessary HTML element attributes and texts which are also unique only to this backup module
* NOTE: Please sanitise all strings that are required to be shown as HTML content on the frontend side (i.e. wp_kses()), or any other technique to prevent XSS attacks that could come via WP hooks
*
* @return Array an associative array keyed by names that describe themselves as they are
*/
public function get_template_properties() {
global $updraftplus, $updraftplus_admin;
$properties = array(
'pre_template_opening_html' => wp_kses('<p>'.__('Examples of S3-compatible storage providers:', 'updraftplus').' <a href="https://teamupdraft.com/documentation/updraftplus/topics/general/faqs/how-do-i-use-updraftplus-with-digitalocean-spaces/?utm_source=udp-plugin&utm_medium=referral&utm_campaign=paac&utm_content=digitalocean-spaces&utm_creative_format=text" target="_blank">DigitalOcean Spaces</a>, <a href="https://www.linode.com/products/object-storage/" target="_blank">Linode Object Storage</a>, <a href="https://www.cloudian.com" target="_blank">Cloudian</a>, <a href="https://www.mh.connectria.com/rp/order/cloud_storage_index" target="_blank">Connectria</a>, <a href="https://www.constant.com/cloud/storage/" target="_blank">Constant</a>, <a href="https://www.eucalyptus.cloud/" target="_blank">Eucalyptus</a>, <a href="http://cloud.nifty.com/storage/" target="_blank">Nifty</a>, <a href="http://www.ntt.com/business/services/cloud/iaas/cloudn.html" target="_blank">Cloudn</a>'.__('... and many more!', 'updraftplus').'</p>', $this->allowed_html_for_content_sanitisation()),
'xmlwriter_existence_label' => !apply_filters('updraftplus_s3generic_xmlwriter_exists', 'UpdraftPlus_S3_Compat' != $this->indicate_s3_class() || !class_exists('XMLWriter')) ? wp_kses($updraftplus_admin->show_double_warning('<strong>'.__('Warning', 'updraftplus').':</strong> '.sprintf(__("Your web server's PHP installation does not include a required module (%s).", 'updraftplus'), 'XMLWriter').' '.__("Please contact your web hosting provider's support and ask for them to enable it.", 'updraftplus'), $this->get_id(), false), $this->allowed_html_for_content_sanitisation()) : '',
'simplexmlelement_existence_label' => !apply_filters('updraftplus_s3generic_simplexmlelement_exists', class_exists('SimpleXMLElement')) ? wp_kses($updraftplus_admin->show_double_warning('<strong>'.__('Warning', 'updraftplus').':</strong> '.sprintf(__("Your web server's PHP installation does not include a required module (%s).", 'updraftplus'), 'SimpleXMLElement').' '.__("Please contact your web hosting provider's support.", 'updraftplus').' '.sprintf(__("UpdraftPlus's %s module <strong>requires</strong> %s.", 'updraftplus'), $updraftplus->backup_methods[$this->get_id()], 'SimpleXMLElement').' '.__('Please do not file any support requests; there is no alternative.', 'updraftplus'), $this->get_id(), false), $this->allowed_html_for_content_sanitisation()) : '',
'curl_existence_label' => wp_kses($updraftplus_admin->curl_check($updraftplus->backup_methods[$this->get_id()], true, $this->get_id().' hide-in-udc', false), $this->allowed_html_for_content_sanitisation()),
'ssl_certificates_errors_link_text' => wp_kses('<a href="'.apply_filters("updraftplus_com_link", "https://teamupdraft.com/documentation/updraftplus/topics/backing-up/troubleshooting/i-get-ssl-certificate-errors-when-backing-up-and-or-restoring/?utm_source=udp-plugin&utm_medium=referral&utm_campaign=paac&utm_content=s3-ssl-certificates&utm_creative_format=text").'" target="_blank">'.__('If you see errors about SSL certificates, then please go here for help.', 'updraftplus').'</a>', $this->allowed_html_for_content_sanitisation()),
'input_access_key_label' => sprintf(__('%s access key', 'updraftplus'), 'S3'),
'input_secret_key_label' => sprintf(__('%s secret key', 'updraftplus'), 'S3'),
'input_secret_key_type' => apply_filters('updraftplus_admin_secret_field_type', 'password'),
'input_location_label' => sprintf(__('%s location', 'updraftplus'), 'S3'),
'input_location_title' => __('Enter only a bucket name or a bucket and path.', 'updraftplus').' '.__('Examples: mybucket, mybucket/mypath', 'updraftplus'),
'input_endpoint_label' => sprintf(__('%s end-point', 'updraftplus'), 'S3'),
'input_bucket_access_style_label' => __('Bucket access style', 'updraftplus'),
'input_bucket_access_style_readmore' => wp_kses('<a aria-label="'.esc_attr__('Read more about bucket access style', 'updraftplus').'" href="https://updraftplus.com/faqs/what-is-the-different-between-path-style-and-bucket-style-access-to-an-s3-compatible-bucket/" target="_blank"><em>'.__('(Read more)', 'updraftplus').'</em></a>', $this->allowed_html_for_content_sanitisation()),
'input_bucket_access_style_option_labels' => array(
'path_style' => __('Path style', 'updraftplus'),
'virtual_host_style' => __('Virtual-host style', 'updraftplus'),
),
'input_signature_version_label' => __('Signature version', 'updraftplus'),
'input_signature_version_readmore' => wp_kses('<a aria-label="'.esc_attr__('Read more about signature version', 'updraftplus').'" href="https://aws.amazon.com/blogs/aws/amazon-s3-update-sigv2-deprecation-period-extended-modified/" target="_blank"><em>'.__('(Read more)', 'updraftplus').'</em></a>', $this->allowed_html_for_content_sanitisation()),
'input_signature_version_option_labels' => array(
'v4' => __('SigV4', 'updraftplus'),
'v2' => __('SigV2', 'updraftplus'),
),
'input_test_label' => sprintf(__('Test %s Settings', 'updraftplus'), $updraftplus->backup_methods[$this->get_id()]),
);
return wp_parse_args($properties, $this->get_persistent_variables_and_methods());
}
/**
* Use DNS bucket name if the remote storage is found to be using s3generic and its bucket access style is set to virtual-host
*
* @param Object $storage - S3 Name
* @param String $bucket - storage path
* @param Array $config - configuration - may not be complete at this stage, so be careful about which properties are used
* *
* @return Boolean true if currently processing s3generic remote storage that uses virtual-host style, false otherwise
*/
protected function maybe_use_dns_bucket_name($storage, $bucket, $config) {
$signature_version = !empty($config['signature_version']) ? $config['signature_version'] : apply_filters('updraftplus_s3_signature_version', 'v2', false, $this); // the 'v2' value used to be handled by the use_v4 class variable, but since the signature_version (dropdown) setting has been introduced then use_v4 class variable has been removed and is no longer needed
if (is_callable(array($storage, 'setSignatureVersion'))) $storage->setSignatureVersion($signature_version); // we don't prioritise the hardcoded endpoint if signature_version is set, which means users are aware of this new option and intentionally set this option and/or save the settings
if ((!empty($config['endpoint']) && preg_match('/\.(leviia|aliyuncs|r2\.cloudflarestorage)\.com$/i', $config['endpoint'])) || (!empty($config['bucket_access_style']) && 'virtual_host_style' === $config['bucket_access_style'])) {
// due to the recent merge of S3-generic bucket access style on March 2021, if virtual-host bucket access style is selected, connecting to an amazonaws bucket location where the user doesn't have an access to it will throw an S3 InvalidRequest exception. It requires the signature to be set to version 4
// Cloudflare R2 supports V4 only
if (empty($config['signature_version']) && preg_match('/\.(leviia|amazonaws|r2\.cloudflarestorage)\.com$/i', $config['endpoint'])) {
if (is_callable(array($storage, 'setSignatureVersion'))) $storage->setSignatureVersion('v4');
}
return $this->use_dns_bucket_name($storage, '');
}
return false;
}
/**
* Acts as a WordPress options filter
*
* @param Array $settings - pre-filtered settings
*
* @return Array filtered settings
*/
public function options_filter($settings) {
$settings = parent::options_filter($settings);
if (!empty($settings['version']) && !empty($settings['settings'])) {
foreach ($settings['settings'] as $instance_id => $instance_settings) {
if (!empty($instance_settings['endpoint'])) {
$settings['settings'][$instance_id]['endpoint'] = preg_replace('/^(http|https):\/\//i', '', trim($instance_settings['endpoint']));
}
}
}
return $settings;
}
}

View File

@@ -0,0 +1,33 @@
<?php
if (!defined('UPDRAFTPLUS_DIR')) die('No direct access allowed.');
if (class_exists('UpdraftPlus_Addons_RemoteStorage_sftp')) {
// Migrate options to standard-style - April 2017. This then enables them to get picked up by the multi-options settings translation code
if (!is_array(UpdraftPlus_Options::get_updraft_option('updraft_sftp')) && '' != UpdraftPlus_Options::get_updraft_option('updraft_sftp_settings', '')) {
$opts = UpdraftPlus_Options::get_updraft_option('updraft_sftp_settings');
UpdraftPlus_Options::update_updraft_option('updraft_sftp', $opts);
UpdraftPlus_Options::delete_updraft_option('updraft_sftp_settings');
}
class UpdraftPlus_BackupModule_sftp extends UpdraftPlus_Addons_RemoteStorage_sftp {
public function __construct() {
parent::__construct('sftp', 'SFTP/SCP');
}
}
} else {
updraft_try_include_file('methods/addon-not-yet-present.php', 'include_once');
/**
* N.B. UpdraftPlus_BackupModule_AddonNotYetPresent extends UpdraftPlus_BackupModule
*/
class UpdraftPlus_BackupModule_sftp extends UpdraftPlus_BackupModule_AddonNotYetPresent {
public function __construct() {
parent::__construct('sftp', 'SFTP and SCP');
}
}
}

View File

@@ -0,0 +1,115 @@
<?php
/**
* This is a bare-bones to get you started with developing an access method. The methods provided below are all ones you will want to use (though note that the provided email.php method is an
* example of truly bare-bones for a method that cannot delete or download and has no configuration).
*
* Read the existing methods for help. There is no hard-and-fast need to put all your code in this file; it is just for increasing convenience and maintainability; there are no bonus points for 100% elegance. If you need access to some part of WordPress that you can only reach through the main plugin file (updraftplus.php), then go right ahead and patch that.
*
* Some handy tips:
* - Search-and-replace "template" for the name of your access method
* - You can also add the methods config_print_javascript_onready and credentials_test if you like
* - Name your file accordingly (it is now template.php)
* - Add the method to the array $backup_methods in updraftplus.php when ready
* - Use the constant UPDRAFTPLUS_DIR to reach Updraft's plugin directory
* - Call $updraftplus->log("my log message") to log things, which greatly helps debugging
* - UpdraftPlus is licenced under the GPLv3 or later. In order to combine your backup method with UpdraftPlus, you will need to licence to anyone and everyone that you distribute it to in a compatible way.
*/
if (!defined('UPDRAFTPLUS_DIR')) die('No direct access allowed.');
if (!class_exists('UpdraftPlus_BackupModule')) updraft_try_include_file('methods/backup-module.php', 'require_once');
class UpdraftPlus_BackupModule_template extends UpdraftPlus_BackupModule {
/**
* backup method: takes an array, and shovels them off to the cloud storage
*
* @param Array $backup_array Array of files (basenames) to sent to remote storage
* @return Mixed - (boolean)false to indicate failure; otherwise, something to be passed back when deleting files
*/
public function backup($backup_array) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- This is a template file and can be ignored
global $updraftplus;// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- This is a template file and can be ignored
// foreach ($backup_array as $file) {
// Do our uploading stuff...
// If successful, then you must do this:
// $updraftplus->uploaded_file($file);
// }
}
/**
* This function lists the files found in the configured storage location
*
* @param String $match a substring to require (tested via strpos() !== false)
*
* @return Array - each file is represented by an array with entries 'name' and (optional) 'size'
*/
public function listfiles($match = 'backup_') {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- This is a template file and can be ignored
// This function needs to return an array of arrays. The keys for the sub-arrays are name (a path-less filename, i.e. a basename), (optional)size, and should be a list of matching files from the storage backend. A WP_Error object can also be returned; and the error code should be no_settings if that is relevant.
return array();
}
/**
* delete method: takes an array of file names (base name) or a single string, and removes them from the cloud storage
*
* @param string $files The specific files
* @param mixed $data Anything passed back from self::backup()
* @param array $sizeinfo Size information
* @return Boolean - whether the operation succeeded or not
*/
public function delete($files, $data = false, $sizeinfo = array()) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- This is a template file and can be ignored
global $updraftplus;// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- This is a template file and can be ignored
if (is_string($files)) $files = array($files);
}
/**
* download method: takes a file name (base name), and brings it back from the cloud storage into Updraft's directory
* You can register errors with $updraftplus->log("my error message", 'error')
*
* @param String $file The specific file to be downloaded from the Cloud Storage
*
* @return Boolean - success or failure state
*/
public function download($file) {// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- This is a template file and can be ignored
global $updraftplus;// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- This is a template file and can be ignored
}
public function get_supported_features() {
// This options format is handled via only accessing options via $this->get_options()
return array('multi_options', 'config_templates');
}
/**
* Get the configuration template, in Handlebars format.
* Note that logging is not available from this context; it will do nothing.
*
* @return String - the template, ready for substitutions to be carried out
*/
public function get_configuration_template() {
ob_start();
$classes = $this->get_css_classes();
?>
<tr class="updraftplusmethod <?php echo esc_attr($classes);?>">
<th>My Method:</th>
<td>
</td>
</tr>
<?php
return ob_get_clean();
}
}

View File

@@ -0,0 +1,33 @@
<?php
if (!defined('UPDRAFTPLUS_DIR')) die('No direct access allowed.');
if (class_exists('UpdraftPlus_Addons_RemoteStorage_webdav')) {
// Migrate options to new-style storage - April 2017
if (!is_array(UpdraftPlus_Options::get_updraft_option('updraft_webdav')) && '' != UpdraftPlus_Options::get_updraft_option('updraft_webdav_settings', '')) {
$opts = UpdraftPlus_Options::get_updraft_option('updraft_webdav_settings');
UpdraftPlus_Options::update_updraft_option('updraft_webdav', $opts);
UpdraftPlus_Options::delete_updraft_option('updraft_webdav_settings');
}
class UpdraftPlus_BackupModule_webdav extends UpdraftPlus_Addons_RemoteStorage_webdav {
public function __construct() {
parent::__construct('webdav', 'WebDAV');
}
}
} else {
updraft_try_include_file('methods/addon-not-yet-present.php', 'include_once');
/**
* N.B. UpdraftPlus_BackupModule_AddonNotYetPresent extends UpdraftPlus_BackupModule
*/
class UpdraftPlus_BackupModule_webdav extends UpdraftPlus_BackupModule_AddonNotYetPresent {
public function __construct() {
parent::__construct('webdav', 'WebDAV');
}
}
}