$dropbox->delete($ufile);
$this->log($e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
if (isset($file_success)) {
$this->log('deletion succeeded');
$this->log('deletion failed');
return $any_failures ? 'file_delete_error' : true;
public function download($file) {
$opts = $this->get_options();
if (empty($opts['tk_access_token'])) {
$this->log('You do not appear to be authenticated with Dropbox (4)');
$this->log(sprintf(__('You do not appear to be authenticated with %s', 'updraftplus'), 'Dropbox'), 'error');
$dropbox = $this->bootstrap();
$this->log($e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')');
$this->log($e->getMessage().' (line: '.$e->getLine().', file: '.$e->getFile().')', 'error');
if (false === $dropbox) return false;
$remote_files = $this->listfiles($file);
foreach ($remote_files as $file_info) {
if ($file_info['name'] == $file) {
return $updraftplus->chunked_download($file, $this, $file_info['size'], apply_filters('updraftplus_dropbox_downloads_manually_break_up', false), null, 2*1048576);
$this->log("$file: file not found in listing of remote directory");
* Callback used by by chunked downloading API
* @param String $file - the file (basename) to be downloaded
* @param Array $headers - supplied headers
* @param Mixed $data - pass-back from our call to the API (which we don't use)
* @param resource $fh - the local file handle
* @return String - the data downloaded
public function chunked_download($file, $headers, $data, $fh) {// phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
$opts = $this->get_options();// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- filter use
$storage = $this->get_storage();
$try_the_other_one = false;// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable -- filter use
$ufile = apply_filters('updraftplus_dropbox_modpath', $file, $this);
if (!empty($headers)) $options['headers'] = $headers;
$get = $storage->download($ufile, $fh, $options);
$this->log($e->getMessage(), 'error');
* 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 $classes . ' ' . 'dropbox_pre_config_container';?>">
<img alt="<?php _e(sprintf(__('%s logo', 'updraftplus'), 'Dropbox')); ?>" src="<?php echo UPDRAFTPLUS_URL.'/images/dropbox-logo.png'; ?>">
global $updraftplus_admin;
$updraftplus_admin->curl_check('Dropbox', false, 'dropbox');
<?php echo sprintf(__('Please read %s for use of our %s authorization app (none of your backup data is sent to us).', 'updraftplus'), '<a target="_blank" href="https://updraftplus.com/faqs/what-is-your-privacy-policy-for-the-use-of-your-dropbox-app/">'.__('this privacy policy', 'updraftplus').'</a>', 'Dropbox');?>
* 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();
$defmsg = '<tr class="'.$classes.'"><td></td><td><strong>'.__('Need to use sub-folders?', 'updraftplus').'</strong> '.sprintf(__('Backups are saved in %s.', 'updraftplus'), 'apps/UpdraftPlus').' '.sprintf(__('If you backup several sites into the same Dropbox and want to organize with sub-folders, then %scheck out Premium%s', 'updraftplus'), '<a href="'.apply_filters("updraftplus_com_link", "https://updraftplus.com/shop/").'" target="_blank">', '</a>').'</td></tr>';
$extra_config = apply_filters('updraftplus_dropbox_extra_config_template', $defmsg, $this);
<tr class="<?php echo $classes;?>">
<th><?php echo sprintf(__('Authenticate with %s', 'updraftplus'), __('Dropbox', 'updraftplus'));?>:</th>
echo "<p><strong>".__('(You appear to be already authenticated).', 'updraftplus')."</strong>";
$this->get_deauthentication_link();
{{#if ownername_sentence}}
$this->get_authentication_link();
{{!-- Legacy: only show this next setting to old users who had a setting stored --}}
{{#if old_user_settings}}
<tr class="<?php echo $classes;?>">
<?php echo '<p>'.htmlspecialchars(__('You must add the following as the authorised redirect URI in your Dropbox console (under "API Settings") when asked', 'updraftplus')).': <kbd>'.UpdraftPlus_Options::admin_page_url().'?page=updraftplus&action=updraftmethod-dropbox-auth</kbd></p>'; ?>
<tr class="<?php echo $classes;?>">
<th>Your Dropbox App Key:</th>
<td><input type="text" autocomplete="off" style="width:332px" <?php $this->output_settings_field_name_and_id('appkey');?> value="{{appkey}}" /></td>
<tr class="<?php echo $classes;?>">
<th>Your Dropbox App Secret:</th>
<td><input type="text" style="width:332px" <?php $this->output_settings_field_name_and_id('secret');?> value="{{secret}}" /></td>
<?php if (false === strpos($extra_config, '<input')) {
// We need to make sure that it is not the case that the module has no settings whatsoever - this can result in the module being effectively invisible.
<input type="hidden" <?php $this->output_settings_field_name_and_id('dummy-nosave');?> value="0">
* Modifies handerbar template options
* @return Array - Modified handerbar template options
public function transform_options_for_template($opts) {
if (!empty($opts['tk_access_token'])) {
$opts['ownername'] = empty($opts['ownername']) ? '' : $opts['ownername'];
if ($opts['ownername']) {
$opts['ownername_sentence'] = sprintf(__("Account holder's name: %s.", 'updraftplus'), $opts['ownername']).' ';
$opts['is_authenticated'] = true;
$opts['old_user_settings'] = (!empty($opts['appkey']) || (defined('UPDRAFTPLUS_CUSTOM_DROPBOX_APP') && UPDRAFTPLUS_CUSTOM_DROPBOX_APP));
if ($opts['old_user_settings']) {
$opts['appkey'] = empty($opts['appkey']) ? '' : $opts['appkey'];
$opts['secret'] = empty($opts['secret']) ? '' : $opts['secret'];
$opts = apply_filters("updraftplus_options_dropbox_options", $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() {
* Over-rides the parent to allow this method to output extra information about using the correct account for OAuth authentication
* @return [boolean] - return false so that no extra information is output
public function output_account_warning() {
* Handles various URL actions, as indicated by the updraftplus_dropboxauth URL parameter
public function action_auth() {
if (isset($_GET['updraftplus_dropboxauth'])) {
if ('doit' == $_GET['updraftplus_dropboxauth']) {
$this->action_authenticate_storage();
} elseif ('deauth' == $_GET['updraftplus_dropboxauth']) {
$this->action_deauthenticate_storage();
} elseif (isset($_REQUEST['state'])) {
if ('POST' == $_SERVER['REQUEST_METHOD']) {
$raw_state = urldecode($_POST['state']);
if (isset($_POST['code'])) $raw_code = urldecode($_POST['code']);
$raw_state = $_GET['state'];
if (isset($_GET['code'])) $raw_code = $_GET['code'];
$this->do_complete_authentication($raw_state, $raw_code);
$this->log(sprintf(__("%s error: %s", 'updraftplus'), sprintf(__("%s authentication", 'updraftplus'), 'Dropbox'), $e->getMessage()), 'error');
* This function will complete the oAuth flow, if return_instead_of_echo is true then add the action to display the authed admin notice, otherwise echo this notice to page.
* @param string $raw_state - the state
* @param string $raw_code - the oauth code
* @param boolean $return_instead_of_echo - a boolean to indicate if we should return the result or echo it
* @return void|string - returns the authentication message if return_instead_of_echo is true
public function do_complete_authentication($raw_state, $raw_code, $return_instead_of_echo = false) {
// Get the CSRF from setting and check it matches the one returned if it does no CSRF attack has happened
$opts = $this->get_options();
$state = stripslashes($raw_state);
// Check the state to see if an instance_id has been attached and if it has then extract the state
$parts = explode(':', $state);
if (strcmp($csrf, $state) == 0) {
// set code so it can be accessed in the next authentication step
$opts['code'] = stripslashes($raw_code);
// remove our flag so we know this authentication is complete
if (isset($opts['auth_in_progress'])) unset($opts['auth_in_progress']);
$this->set_options($opts, true);
$auth_result = $this->auth_token($return_instead_of_echo);
if ($return_instead_of_echo) return $auth_result;
error_log("UpdraftPlus: CSRF comparison failure: $csrf != $state");
* This method will reset any saved options and start the bootstrap process for an authentication
* @param String $instance_id - the instance id of the settings we want to authenticate
public function do_authenticate_storage($instance_id) {
// Clear out the existing credentials
$opts = $this->get_options();
$opts['tk_access_token'] = '';
unset($opts['tk_request_token']);
// Set a flag so we know this authentication is in progress
$opts['auth_in_progress'] = true;
$this->set_options($opts, true);
$this->set_instance_id($instance_id);
$this->log(sprintf(__("%s error: %s", 'updraftplus'), sprintf(__("%s authentication", 'updraftplus'), 'Dropbox'), $e->getMessage()), 'error');
* This method will start the bootstrap process for a de-authentication
* @param String $instance_id - the instance id of the settings we want to de-authenticate
public function do_deauthenticate_storage($instance_id) {
$this->set_instance_id($instance_id);
$this->log(sprintf(__("%s error: %s", 'updraftplus'), sprintf(__("%s de-authentication", 'updraftplus'), 'Dropbox'), $e->getMessage()), 'error');
* This method will setup the authenticated admin warning, it can either return this or echo it
* @param boolean $return_instead_of_echo - a boolean to indicate if we should return the result or echo it
* @return void|string - returns the authentication message if return_instead_of_echo is true
public function show_authed_admin_warning($return_instead_of_echo) {
global $updraftplus_admin;
$dropbox = $this->bootstrap();
if (false === $dropbox) return false;
$account_info = $dropbox->accountInfo();
$accountinfo_err = sprintf(__("%s error: %s", 'updraftplus'), 'Dropbox', $e->getMessage()).' ('.$e->getCode().')';
$message = "<strong>".__('Success:', 'updraftplus').'</strong> '.sprintf(__('you have authenticated your %s account', 'updraftplus'), 'Dropbox');
// We log, because otherwise people get confused by the most recent log message of 'Parameter not found: oauth_token' and raise support requests
$this->log(__('Success:', 'updraftplus').' '.sprintf(__('you have authenticated your %s account', 'updraftplus'), 'Dropbox'));
if (empty($account_info['code']) || "200" != $account_info['code']) {
$message .= " (".__('though part of the returned information was not as expected - your mileage may vary', 'updraftplus').") ". $account_info['code'];
if (!empty($accountinfo_err)) $message .= "<br>".htmlspecialchars($accountinfo_err);
$body = $account_info['body'];
if (isset($body->display_name)) {
$name = $body->display_name;
$name = $body->name->display_name;
$message .= ". <br>".sprintf(__('Your %s account name: %s', 'updraftplus'), 'Dropbox', htmlspecialchars($name));
$opts = $this->get_options();
$opts['ownername'] = $name;
$this->set_options($opts, true);
* Quota information is no longer provided with account information a new call to qoutaInfo must be made to get this information. The timeout is because we've seen cases where it returned after 180 seconds (apparently a faulty outgoing proxy), and we may as well wait as cause an error leading to user confusion.
$quota_info = $dropbox->quotaInfo(array('timeout' => 190));
if (empty($quota_info['code']) || "200" != $quota_info['code']) {
$message .= " (".__('though part of the returned information was not as expected - your mileage may vary', 'updraftplus').")". $quota_info['code'];
if (!empty($accountinfo_err)) $message .= "<br>".htmlspecialchars($accountinfo_err);
$body = $quota_info['body'];
if (isset($body->quota_info)) {
$quota_info = $body->quota_info;
$total_quota = max($quota_info->quota, 1);
$normal_quota = $quota_info->normal;
$shared_quota = $quota_info->shared;
$available_quota =$total_quota - ($normal_quota + $shared_quota);
$used_perc = round(($normal_quota + $shared_quota)*100/$total_quota, 1);
$message .= ' <br>'.sprintf(__('Your %s quota usage: %s %% used, %s available', 'updraftplus'), 'Dropbox', $used_perc, round($available_quota/1048576, 1).' MB');
$total_quota = max($body->allocation->allocated, 1);
/* check here to see if the account is a team account and if so use the other used value
This will give us their total usage including their individual account and team account */
if (isset($body->allocation->used)) $used = $body->allocation->used;
$available_quota =$total_quota - $used;
$used_perc = round($used*100/$total_quota, 1);
$message .= ' <br>'.sprintf(__('Your %s quota usage: %s %% used, %s available', 'updraftplus'), 'Dropbox', $used_perc, round($available_quota/1048576, 1).' MB');
if ($return_instead_of_echo) {
return "<div class='updraftmessage updated'><p>{$message}</p></div>";
$updraftplus_admin->show_admin_warning($message);
* Bootstrap and check token, can also return the authentication method if return_instead_of_echo is true
* @param boolean $return_instead_of_echo - a boolean to indicate if we should return the result or echo it
* @return void|string - returns the authentication message if return_instead_of_echo is true
public function auth_token($return_instead_of_echo) {
$opts = $this->get_options();
if (!empty($opts['tk_access_token'])) {
if ($return_instead_of_echo) {
return $this->show_authed_admin_warning($return_instead_of_echo);
add_action('all_admin_notices', array($this, 'show_authed_admin_warning'));
* Acquire single-use authorization code
public function auth_request() {
* This basically reproduces the relevant bits of bootstrap.php from the SDK
* @param Boolean $deauthenticate indicates if we should bootstrap for a deauth or auth request
public function bootstrap($deauthenticate = false) {
$storage = $this->get_storage();
if (!empty($storage) && !is_wp_error($storage)) return $storage;
// Dropbox APIv1 is dead, but we'll keep the variable in case v3 is ever announced
$dropbox_api = 'Dropbox2';
include_once(UPDRAFTPLUS_DIR.'/includes/'.$dropbox_api.'/API.php');
include_once(UPDRAFTPLUS_DIR.'/includes/'.$dropbox_api.'/Exception.php');
include_once(UPDRAFTPLUS_DIR.'/includes/'.$dropbox_api.'/OAuth/Consumer/ConsumerAbstract.php');
include_once(UPDRAFTPLUS_DIR.'/includes/'.$dropbox_api.'/OAuth/Storage/StorageInterface.php');
include_once(UPDRAFTPLUS_DIR.'/includes/'.$dropbox_api.'/OAuth/Storage/Encrypter.php');
include_once(UPDRAFTPLUS_DIR.'/includes/'.$dropbox_api.'/OAuth/Storage/WordPress.php');
include_once(UPDRAFTPLUS_DIR.'/includes/'.$dropbox_api.'/OAuth/Consumer/Curl.php');
// require_once(UPDRAFTPLUS_DIR.'/includes/'.$dropbox_api.'/OAuth/Consumer/WordPress.php');
$opts = $this->get_options();
$key = empty($opts['secret']) ? '' : $opts['secret'];
$sec = empty($opts['appkey']) ? '' : $opts['appkey'];
$oauth2_id = defined('UPDRAFTPLUS_DROPBOX_CLIENT_ID') ? UPDRAFTPLUS_DROPBOX_CLIENT_ID : base64_decode('dzQxM3o0cWhqejY1Nm5l');
$callbackhome = UpdraftPlus_Options::admin_page_url().'?page=updraftplus&action=updraftmethod-dropbox-auth';
$callback = defined('UPDRAFTPLUS_DROPBOX_AUTH_RETURN_URL') ? UPDRAFTPLUS_DROPBOX_AUTH_RETURN_URL : 'https://auth.updraftplus.com/auth/dropbox/';
$instance_id = $this->get_instance_id();
// Instantiate the Encrypter and storage objects
$encrypter = new Dropbox_Encrypter('ThisOneDoesNotMatterBeyondLength');
// Instantiate the storage
$dropbox_storage = new Dropbox_WordPress($encrypter, "tk_", 'updraft_dropbox', $this);
// WordPress consumer does not yet work
// $oauth = new Dropbox_ConsumerWordPress($sec, $key, $dropbox_storage, $callback);
// Get the DropBox API access details
list($d2, $d1) = $this->defaults();
$sec = base64_decode($d1);
$key = base64_decode($d2);
if ('dropbox:' == substr($sec, 0, 8)) {