first commit

This commit is contained in:
Roman Pyrih
2026-04-03 10:22:35 +02:00
commit 5de35e358d
8424 changed files with 2907254 additions and 0 deletions

View File

@@ -0,0 +1,93 @@
<?php
namespace Duplicator\Addons\DropboxAddon;
use Duplicator\Addons\DropboxAddon\Models\DropboxStorage;
use Duplicator\Addons\DropboxAddon\Utils\Autoloader;
use Duplicator\Core\Addons\AbstractAddonCore;
use Duplicator\Models\Storages\AbstractStorageEntity;
class DropboxAddon extends AbstractAddonCore
{
const ADDON_PATH = __DIR__;
/**
* @return void
*/
public function init()
{
Autoloader::register();
add_action('duplicator_pro_register_storage_types', [$this, 'registerStorages']);
add_filter('duplicator_template_file', array(__CLASS__, 'getTemplateFile'), 10, 2);
add_filter('duplicator_usage_stats_storages_infos', array(__CLASS__, 'getStorageUsageStats'), 10);
}
/**
* Return template file path
*
* @param string $path path to the template file
* @param string $slugTpl slug of the template
*
* @return string
*/
public static function getTemplateFile($path, $slugTpl)
{
if (strpos($slugTpl, 'dropboxaddon/') === 0) {
return self::getAddonPath() . '/template/' . $slugTpl . '.php';
}
return $path;
}
/**
* Get storage usage stats
*
* @param array<string,int> $storageNums Storages num
*
* @return array<string,int>
*/
public static function getStorageUsageStats($storageNums)
{
if (($storages = AbstractStorageEntity::getAll()) === false) {
$storages = [];
}
$storageNums['storages_dropbox_count'] = 0;
foreach ($storages as $storage) {
if ($storage->getSType() === DropboxStorage::getSType()) {
$storageNums['storages_dropbox_count']++;
}
}
return $storageNums;
}
/**
* Register storages
*
* @return void
*/
public function registerStorages()
{
DropboxStorage::registerType();
}
/**
*
* @return string
*/
public static function getAddonPath()
{
return self::ADDON_PATH;
}
/**
*
* @return string
*/
public static function getAddonFile()
{
return __FILE__;
}
}

View File

@@ -0,0 +1,527 @@
<?php
namespace Duplicator\Addons\DropboxAddon\Models;
use Duplicator\Addons\DropboxAddon\Utils\DropboxClient;
use Duplicator\Models\Storages\AbstractStorageAdapter;
use Duplicator\Models\Storages\StoragePathInfo;
use VendorDuplicator\Dropbox\Spatie\Dropbox\UploadSessionCursor;
class DropboxAdapter extends AbstractStorageAdapter
{
/** @var string */
protected $accessToken = '';
/** @var DropboxClient */
protected $client = null;
/** @var string */
protected $storageFolder = '';
/** @var bool */
protected $sslVerify = true;
/** @var string If empty use server cert else use custom cert path */
protected $sslCert = '';
/** @var bool */
protected $ipv4Only = false;
/**
* @param string $accessToken Dropbox access token.
* @param string $storageFolder Dropbox storage folder.
* @param bool $sslVerify If true, use SSL
* @param string $sslCert If empty use server cert
* @param bool $ipv4Only If true, use IPv4 only
*/
public function __construct(
$accessToken,
$storageFolder = '',
$sslVerify = true,
$sslCert = '',
$ipv4Only = false
) {
$this->accessToken = $accessToken;
$this->storageFolder = '/' . trim($storageFolder, '/') . '/';
$this->sslVerify = $sslVerify;
$this->sslCert = $sslCert;
$this->ipv4Only = $ipv4Only;
$this->client = new DropboxClient($accessToken, null, DropboxClient::MAX_CHUNK_SIZE, 0, $sslVerify, $sslCert, $ipv4Only);
}
/**
* Get the Dropbox client.
*
* @return DropboxClient
*/
public function getClient()
{
return $this->client;
}
/**
* Initialize the storage on creation.
*
* @param string $errorMsg The error message if storage is invalid.
*
* @return bool true on success or false on failure.
*/
public function initialize(&$errorMsg = '')
{
if (! $this->exists('/')) {
try {
$this->createDir('/');
} catch (\Exception $e) {
\DUP_PRO_Log::trace($e->getMessage());
$errorMsg = $e->getMessage();
return false;
}
}
return true;
}
/**
* Destroy the storage on deletion.
*
* @return bool true on success or false on failure.
*/
public function destroy()
{
$this->delete('/', true);
return true;
}
/**
* Check if storage is valid and ready to use.
*
* @param string $errorMsg The error message if storage is invalid.
*
* @return bool
*/
public function isValid(&$errorMsg = '')
{
try {
$this->client->getMetadata($this->storageFolder);
} catch (\Exception $e) {
\DUP_PRO_Log::trace("Dropbox storage is invalid: " . $e->getMessage());
$errorMsg = $e->getMessage();
return false;
}
return true;
}
/**
* Create the directory specified by pathname, recursively if necessary.
*
* @param string $path The directory path.
*
* @return bool true on success or false on failure.
*/
protected function realCreateDir($path)
{
$path = $this->formatPath($path);
try {
$this->client->createFolder($path);
} catch (\Exception $e) {
\DUP_PRO_Log::trace($e->getMessage());
return false;
}
return true;
}
/**
* Create file with content.
*
* @param string $path The path to file.
* @param string $content The content of file.
*
* @return false|int The number of bytes that were written to the file, or false on failure.
*/
protected function realCreateFile($path, $content)
{
$path = $this->formatPath($path);
try {
$response = $this->client->upload($path, $content, 'overwrite');
} catch (\Exception $e) {
\DUP_PRO_Log::trace($e->getMessage());
return false;
}
return $response['size'];
}
/**
* Delete relative path from storage root.
*
* @param string $path The path to delete. (Accepts directories and files)
* @param bool $recursive Allows the deletion of nested directories specified in the pathname. Default to false.
*
* @return bool true on success or false on failure.
*/
protected function realDelete($path, $recursive = false)
{
$path = $this->formatPath($path);
if (! $recursive) {
try {
$response = $this->client->listFolder($path);
if (count($response['entries']) > 0) {
return false;
}
} catch (\Exception $e) {
// Path is not a directory, so we can delete it.
}
}
try {
$this->client->delete($path);
} catch (\Exception $e) {
\DUP_PRO_Log::trace($e->getMessage());
return false;
}
return true;
}
/**
* Get file content.
*
* @param string $path The path to file.
*
* @return string|false The content of file or false on failure.
*/
public function getFileContent($path)
{
$content = '';
try {
$stream = $this->client->download($this->formatPath($path));
while ($chunk = fgets($stream)) {
$content .= $chunk;
}
} catch (\Exception $e) {
\DUP_PRO_Log::trace($e->getMessage());
return false;
}
return $content;
}
/**
* Move and/or rename a file or directory.
*
* @param string $oldPath Relative storage path
* @param string $newPath Relative storage path
*
* @return bool true on success or false on failure.
*/
protected function realMove($oldPath, $newPath)
{
$oldPath = $this->formatPath($oldPath);
$newPath = $this->formatPath($newPath);
try {
$this->client->move($oldPath, $newPath);
} catch (\Exception $e) {
\DUP_PRO_Log::trace($e->getMessage());
return false;
}
return true;
}
/**
* Get path info.
*
* @param string $path Relative storage path, if empty, return root path info.
*
* @return StoragePathInfo The path info or false if path is invalid.
*/
protected function getRealPathInfo($path)
{
try {
$response = $this->client->getMetadata($this->formatPath($path));
} catch (\Exception $e) {
$response = [];
}
return $this->buildPathInfo($response);
}
/**
* Get the list of files and directories inside the specified path.
*
* @param string $path Relative storage path, if empty, scan root path.
* @param bool $files If true, add files to the list. Default to true.
* @param bool $folders If true, add folders to the list. Default to true.
*
* @return string[] The list of files and directories, empty array if path is invalid.
*/
public function scanDir($path, $files = true, $folders = true)
{
$path = rtrim($this->formatPath($path), '/') . '/';
$filterFunc = function ($entry) use ($files, $folders) {
if ($entry['.tag'] === 'file' && $files) {
return true;
}
if ($entry['.tag'] === 'folder' && $folders) {
return true;
}
return false;
};
try {
$response = $this->client->listFolder($path);
} catch (\Exception $e) {
\DUP_PRO_Log::trace('[DropboxAddon] ' . $e->getMessage());
return [];
}
// We filter out the entries as needed, then only keep the path.
// We do this early to keep the memory usage as low as possible.
$entries = array_map(function ($entry) use ($path) {
return substr($entry['path_display'], strlen($path));
}, array_filter($response['entries'], $filterFunc));
while ($response['has_more']) {
$response = $this->client->listFolderContinue($response['cursor']);
$entries = array_merge($entries, array_map(function ($entry) use ($path) {
return substr($entry['path_display'], strlen($path));
}, array_filter($response['entries'], $filterFunc)));
}
return $entries;
}
/**
* Check if directory is empty.
*
* @param string $path The folder path
* @param string[] $filters Filters to exclude files and folders from the check, if start and end with /, use regex.
*
* @return bool True is ok, false otherwise
*/
public function isDirEmpty($path, $filters = [])
{
$path = $this->formatPath($path);
try {
$response = $this->client->listFolder($path);
} catch (\Exception $e) {
\DUP_PRO_Log::trace($e->getMessage());
return false;
}
if (count($response['entries']) === 0) {
return true;
} elseif (empty($filters)) {
// we have no filters, and the folder is not empty, so it must contain something
return false;
}
$regexFilters = $normalFilters = [];
foreach ($filters as $filter) {
if ($filter[0] === '/' && substr($filter, -1) === '/') {
$regexFilters[] = $filter; // It's a regex filter as it starts and ends with a slash
} else {
$normalFilters[] = $filter;
}
}
$contents = $this->scanDir($path);
foreach ($contents as $item) {
if (in_array($item, $normalFilters)) {
continue;
}
foreach ($regexFilters as $regexFilter) {
if (preg_match($regexFilter, $item) === 1) {
continue 2;
}
}
return false;
}
return true;
}
/**
* Copy local file to storage, partial copy is supported.
* If destination file exists, it will be overwritten.
* If offset is less than the destination file size, the file will be truncated.
*
* @param string $sourceFile The source file full path
* @param string $storageFile Storage destination path
* @param int<0,max> $offset The offset where the data starts.
* @param int $length The maximum number of bytes read. Default to -1 (read all the remaining buffer).
* @param int $timeout The timeout for the copy operation in microseconds. Default to 0 (no timeout).
* @param array<string,mixed> $extraData Extra data to pass to copy function and updated during copy.
* Used for storages that need to maintain persistent data during copy intra-session.
*
* @return false|int The number of bytes that were written to the file, or false on failure.
*/
protected function realCopyToStorage($sourceFile, $storageFile, $offset = 0, $length = -1, $timeout = 0, &$extraData = [])
{
$storageFile = $this->formatPath($storageFile);
if (!$handle = fopen($sourceFile, 'rb')) {
\DUP_PRO_Log::trace("[DropboxAddon] Could not open source file: {$sourceFile}");
return false;
}
fseek($handle, $offset);
$chunkSize = $length > 0 ? $length : MB_IN_BYTES;
if (!empty($extraData['sessionId'])) {
$sessionId = $extraData['sessionId'];
$cursor = new UploadSessionCursor($sessionId, $offset);
} else {
// We need to start a new session.
try {
$contents = fread($handle, $chunkSize);
$cursor = $this->client->uploadSessionStart($contents);
$extraData['sessionId'] = $cursor->session_id;
} catch (\Exception $e) {
\DUP_PRO_Log::trace($e->getMessage());
return false;
}
}
\DUP_PRO_Log::info("[Dropbox] Setting timeout for upload request for: {$storageFile} to " . ($timeout / SECONDS_IN_MICROSECONDS) . " seconds");
$this->client->setTimeout($timeout / SECONDS_IN_MICROSECONDS);
while (($contents = fread($handle, $chunkSize)) && ($length < 0 || $offset > 0)) {
try {
$this->client->uploadSessionAppend($contents, $cursor);
} catch (\Exception $e) {
unset($extraData['sessionId']);
$this->client->setTimeout(0);
\DUP_PRO_Log::trace('[DropboxAddon] ' . $e->getMessage());
return false;
}
if ($length > 0) {
// A specific length was requested, so we can break out of the loop.
break;
}
}
$fileSize = filesize($sourceFile);
$this->client->setTimeout(0);
\DUP_PRO_Log::info("[Dropbox] uploaded {$storageFile} from {$offset} to {$cursor->offset}, progress: " . ceil($cursor->offset / $fileSize * 100) . "%");
// If we have finished uploading, we need to finish the session & clear the cache
if ($cursor->offset >= $fileSize) {
try {
\DUP_PRO_Log::info("[DropboxAddon] Finishing upload request for: {$storageFile}");
$this->client->uploadSessionFinish('', $cursor, $storageFile, 'overwrite'); // this will return the file metadata
} catch (\Exception $e) {
\DUP_PRO_Log::trace('[DropboxAddon] ' . $e->getMessage());
return false;
}
unset($extraData['sessionId']);
}
return $length > 0 ? $length : $fileSize;
}
/**
* Normalize path, add storage root path if needed.
*
* @param string $path Relative storage path.
*
* @return string
*/
protected function formatPath($path)
{
return $this->storageFolder . ltrim($path, '/');
}
/**
* Build StoragePathInfo object from Dropbox API response.
*
* @param array<string,mixed> $response Dropbox API response.
*
* @return StoragePathInfo
*/
protected function buildPathInfo($response)
{
$info = new StoragePathInfo();
$info->exists = isset($response['.tag']);
if (!$info->exists) {
return $info;
}
$info->path = $this->getRelativeStoragePath($response['path_display']);
$info->isDir = $response['.tag'] === 'folder';
$info->size = isset($response['size']) ? $response['size'] : 0;
$info->created = isset($response['client_modified']) ? strtotime($response['client_modified']) : time();
$info->modified = isset($response['server_modified']) ? strtotime($response['server_modified']) : time();
return $info;
}
/**
* Get relative storage path from Dropbox path display.
*
* @param string $path_display Dropbox path display.
* @param string $subPath Sub path to remove from the path display.
*
* @return string
*/
protected function getRelativeStoragePath($path_display, $subPath = '')
{
$rootPath = $this->storageFolder;
if (!empty($subPath)) {
$rootPath .= trim($subPath) . '/';
}
return substr($path_display, strlen($rootPath));
}
/**
* Copy storage file to local file, partial copy is supported.
* If destination file exists, it will be overwritten.
* If offset is less than the destination file size, the file will be truncated.
*
* @param string $storageFile The storage file path
* @param string $destFile The destination local file full path
* @param int<0,max> $offset The offset where the data starts.
* @param int $length The maximum number of bytes read. Default to -1 (read all the remaining buffer).
* @param int $timeout The timeout for the copy operation in microseconds. Default to 0 (no timeout).
* @param array<string,mixed> $extraData Extra data to pass to copy function and updated during copy.
* Used for storages that need to maintain persistent data during copy intra-session.
*
* @return false|int The number of bytes that were written to the file, or false on failure.
*/
public function copyFromStorage($storageFile, $destFile, $offset = 0, $length = -1, $timeout = 0, &$extraData = [])
{
if (! $this->exists($storageFile)) {
\DUP_PRO_Log::trace("[DropboxAddon] Storage file {$storageFile} does not exist");
return false;
}
if (! isset($extraData['resuming']) && file_put_contents($destFile, '') === false) {
\DUP_PRO_Log::trace("[DropboxAddon] Could not open destination file for writing. File: {$destFile}");
return false;
}
$extraData['resuming'] = true;
$this->client->setTimeout($timeout / SECONDS_IN_MICROSECONDS);
$resource = $this->client->download($this->formatPath($storageFile));
if (! $resource) {
$this->client->setTimeout(0);
\DUP_PRO_Log::trace("[DropboxAddon] Could not open storage file for downloading. File: {$storageFile}");
return false;
}
fseek($resource, $offset);
$content = '';
while (!feof($resource)) {
$content .= fread($resource, $length > 0 ? $length : MB_IN_BYTES);
if ($length > 0 && strlen($content) > $length) {
$content = substr($content, 0, $length);
break;
}
}
$this->client->setTimeout(0);
if (file_put_contents($destFile, $content, FILE_APPEND) !== false) {
return $length < 0 ? strlen($content) : $length;
}
return false;
}
}

View File

@@ -0,0 +1,595 @@
<?php
/**
*
* @package Duplicator
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Addons\DropboxAddon\Models;
use DUP_PRO_Dropbox_Transfer_Mode;
use DUP_PRO_Global_Entity;
use DUP_PRO_Log;
use Duplicator\Addons\DropboxAddon\Utils\DropboxClient;
use Duplicator\Core\Views\TplMng;
use Duplicator\Libs\Snap\SnapUtil;
use Duplicator\Models\DynamicGlobalEntity;
use Duplicator\Models\Storages\AbstractStorageEntity;
use Duplicator\Models\Storages\StorageAuthInterface;
use Duplicator\Utils\Crypt\CryptBlowfish;
use Exception;
/**
* @property DropboxAdapter $adapter
*/
class DropboxStorage extends AbstractStorageEntity implements StorageAuthInterface
{
/**
* Get default config
*
* @return array<string,scalar>
*/
protected static function getDefaultConfig()
{
$config = parent::getDefaultConfig();
return array_merge(
$config,
[
'access_token' => '',
'access_token_secret' => '',
'v2_access_token' => '',
'authorized' => false,
]
);
}
/**
* Serialize
*
* Wakeup method.
*
* @return void
*/
public function __wakeup()
{
parent::__wakeup();
if ($this->legacyEntity) {
// Old storage entity
$this->legacyEntity = false;
// Make sure the storage type is right from the old entity
$this->storage_type = $this->getSType();
$this->config = [
'access_token' => $this->dropbox_access_token,
'access_token_secret' => $this->dropbox_access_token_secret,
'v2_access_token' => $this->dropbox_v2_access_token,
'storage_folder' => ltrim($this->dropbox_storage_folder, '/\\'),
'max_packages' => $this->dropbox_max_files,
'authorized' => ($this->dropbox_authorization_state == 4),
];
// reset old values
$this->dropbox_access_token = '';
$this->dropbox_access_token_secret = '';
$this->dropbox_v2_access_token = '';
$this->dropbox_storage_folder = '';
$this->dropbox_max_files = 10;
$this->dropbox_authorization_state = 0;
}
}
/**
* Return the storage type
*
* @return int
*/
public static function getSType()
{
return 1;
}
/**
* Returns the storage type icon.
*
* @return string Returns the storage icon
*/
public static function getStypeIcon()
{
return '<img src="' . esc_url(DUPLICATOR_PRO_IMG_URL . '/dropbox.svg') . '" class="dup-storage-icon" alt="' . esc_attr(static::getStypeName()) . '" />';
}
/**
* Returns the storage type name.
*
* @return string
*/
public static function getStypeName()
{
return __('Dropbox', 'duplicator-pro');
}
/**
* Get storage location string
*
* @return string
*/
public function getLocationString()
{
$dropBoxInfo = $this->getAccountInfo();
if (!isset($dropBoxInfo['locale']) || $dropBoxInfo['locale'] == 'en') {
return "https://dropbox.com/home/Apps/Duplicator%20Pro/" . ltrim($this->getStorageFolder(), '/');
} else {
return "https://dropbox.com/home";
}
}
/**
* Check if storage is valid
*
* @return bool Return true if storage is valid and ready to use, false otherwise
*/
public function isValid()
{
return $this->isAuthorized();
}
/**
* Is autorized
*
* @return bool
*/
public function isAuthorized()
{
return $this->config['authorized'];
}
/**
* Authorized from HTTP request
*
* @param string $message Message
*
* @return bool True if authorized, false if failed
*/
public function authorizeFromRequest(&$message = '')
{
try {
if (($authCode = SnapUtil::sanitizeTextInput(SnapUtil::INPUT_REQUEST, 'auth_code')) === '') {
throw new Exception(__('Authorization code is empty', 'duplicator-pro'));
}
$this->name = SnapUtil::sanitizeTextInput(SnapUtil::INPUT_REQUEST, 'name', '');
$this->notes = SnapUtil::sanitizeDefaultInput(SnapUtil::INPUT_REQUEST, 'notes', '');
$this->config['max_packages'] = SnapUtil::sanitizeIntInput(SnapUtil::INPUT_REQUEST, 'max_packages', 10);
$this->config['storage_folder'] = self::getSanitizedInputFolder('storage_folder', 'remove');
$this->revokeAuthorization();
$client = $this->getAdapter()->getClient();
if (($token = $client->authenticate($authCode, self::getApiKeySecret())) === false) {
throw new Exception(__("Couldn't connect. Dropbox access token not found.", 'duplicator-pro'));
}
$this->config['v2_access_token'] = $token;
$this->config['authorized'] = true;
} catch (Exception $e) {
DUP_PRO_Log::trace("Problem authorizing Dropbox access token msg: " . $e->getMessage());
$message = $e->getMessage();
return false;
}
$message = __('Dropbox is connected successfully and Storage Provider Updated.', 'duplicator-pro');
return true;
}
/**
* Revokes authorization
*
* @param string $message Message
*
* @return bool True if authorized, false if failed
*/
public function revokeAuthorization(&$message = '')
{
if (!$this->isAuthorized()) {
$message = __('Dropbox isn\'t authorized.', 'duplicator-pro');
return true;
}
try {
$client = $this->getAdapter()->getClient();
if ($client->revokeToken() === false) {
throw new Exception(__('DropBox can\'t be unauthorized.', 'duplicator-pro'));
}
$this->config['v2_access_token'] = '';
$this->config['authorized'] = false;
} catch (Exception $e) {
DUP_PRO_Log::trace("Problem revoking Dropbox access token msg: " . $e->getMessage());
$message = $e->getMessage();
return false;
}
$message = __('Dropbox is disconnected successfully.', 'duplicator-pro');
return true;
}
/**
* Get authorization URL
*
* @todo: This should be refactored to use the new TokenService class.
*
* @return string
*/
public function getAuthorizationUrl()
{
$config = self::getApiKeySecret();
return DropboxClient::OAUTH2_URL . 'authorize?client_id=' . $config['app_key'] . '&response_type=code';
}
/**
* Get action key text
*
* @param string $key Key name (action, pending, failed, cancelled, success)
*
* @return string
*/
protected function getActionKeyText($key)
{
switch ($key) {
case 'action':
return sprintf(
__('Transferring to Dropbox folder:<br/> <i>%1$s</i>', "duplicator-pro"),
$this->getStorageFolder()
);
case 'pending':
return sprintf(
__('Transfer to Dropbox folder %1$s is pending', "duplicator-pro"),
$this->getStorageFolder()
);
case 'failed':
return sprintf(
__('Failed to transfer to Dropbox folder %1$s', "duplicator-pro"),
$this->getStorageFolder()
);
case 'cancelled':
return sprintf(
__('Cancelled before could transfer to Dropbox folder %1$s', "duplicator-pro"),
$this->getStorageFolder()
);
case 'success':
return sprintf(
__('Transferred package to Dropbox folder %1$s', "duplicator-pro"),
$this->getStorageFolder()
);
default:
throw new Exception('Invalid key');
}
}
/**
* Render form config fields
*
* @param bool $echo Echo or return
*
* @return string
*/
public function renderConfigFields($echo = true)
{
return TplMng::getInstance()->render(
'dropboxaddon/configs/dropbox',
[
'storage' => $this,
'accountInfo' => $this->getAccountInfo(),
'quotaInfo' => $this->getQuota(),
'storageFolder' => $this->config['storage_folder'],
'maxPackages' => $this->config['max_packages'],
],
$echo
);
}
/**
* Update data from http request, this method don't save data, just update object properties
*
* @param string $message Message
*
* @return bool True if success and all data is valid, false otherwise
*/
public function updateFromHttpRequest(&$message = '')
{
if ((parent::updateFromHttpRequest($message) === false)) {
return false;
}
$this->config['max_packages'] = SnapUtil::sanitizeIntInput(SnapUtil::INPUT_REQUEST, 'dropbox_max_files', 10);
$this->config['storage_folder'] = self::getSanitizedInputFolder('_dropbox_storage_folder', 'remove');
$message = sprintf(
__('Dropbox Storage Updated. Folder: %1$s', 'duplicator-pro'),
$this->getStorageFolder()
);
return true;
}
/**
* Get the storage adapter
*
* @return DropboxAdapter
*/
public function getAdapter()
{
if (! $this->adapter) {
$global = DUP_PRO_Global_Entity::getInstance();
$this->adapter = new DropboxAdapter(
$this->setV2AccessTokenFromV1Client(),
$this->getStorageFolder(),
!$global->ssl_disableverify,
($global->ssl_useservercerts ? '' : DUPLICATOR_PRO_CERT_PATH),
$global->ipv4_only
);
}
return $this->adapter;
}
/**
* Get dropbox api key and secret
*
* @return array{app_key:string,app_secret:string}
*/
protected static function getApiKeySecret()
{
$dk = self::getDk1();
$dk = self::getDk2() . $dk;
$akey = CryptBlowfish::decrypt('EQNJ53++6/40fuF5ke+IaQ==', $dk);
$asec = CryptBlowfish::decrypt('ui25chqoBexPt6QDi9qmGg==', $dk);
$akey = trim($akey);
$asec = trim($asec);
if (($akey != $asec) || ($akey != "fdda100")) {
$akey = self::getAk1() . self::getAk2();
$asec = self::getAs1() . self::getAs2();
}
return [
'app_key' => $asec,
'app_secret' => $akey,
];
}
/**
* Get dk1
*
* @return string
*/
private static function getDk1()
{
return 'y8!!';
}
/**
* Get dk2
*
* @return string
*/
private static function getDk2()
{
return '32897';
}
/**
* Get ak1
*
* @return string
*/
private static function getAk1()
{
return strrev('i6gh72iv');
}
/**
* Get ak2
*
* @return string
*/
private static function getAk2()
{
return strrev('1xgkhw2');
}
/**
* Get as1
*
* @return string
*/
private static function getAs1()
{
return strrev('z7fl2twoo');
}
/**
* Get as2
*
* @return string
*/
private static function getAs2()
{
return strrev('2z2bfm');
}
/**
* Set v2 access token from v1 client
*
* @return string V2 access token
*/
protected function setV2AccessTokenFromV1Client()
{
if (strlen($this->config['v2_access_token']) > 0) {
return $this->config['v2_access_token'];
}
if (strlen($this->config['access_token']) == 0 || strlen($this->config['access_token_secret']) == 0) {
return '';
}
$oldToken = [
't' => $this->config['access_token'],
's' => $this->config['access_token_secret'],
];
$accessToken = DropboxClient::accessTokenFromOauth1($oldToken, self::getApiKeySecret());
if ($accessToken) {
$this->config['access_token'] = '';
$this->config['access_token_secret'] = '';
$this->config['v2_access_token'] = $accessToken;
$this->save();
} else {
DUP_PRO_Log::trace("Problem converting Dropbox access token");
$this->config['v2_access_token'] = '';
}
return $this->config['v2_access_token'];
}
/**
* Get account info
*
* @return false|array<string,mixed>
*/
protected function getAccountInfo()
{
if (!$this->isAuthorized()) {
return false;
}
try {
return $this->getAdapter()->getClient()->getAccountInfo();
} catch (Exception $e) {
DUP_PRO_Log::trace("Problem getting Dropbox account info. " . $e->getMessage());
}
return false;
}
/**
* Get dropbox quota
*
* @return false|array{used:int,total:int,perc:float,available:string}
*/
protected function getQuota()
{
if (!$this->isAuthorized()) {
return false;
}
$quota = $this->getAdapter()->getClient()->getQuota();
if (
!isset($quota['used']) ||
!isset($quota['allocation']['allocated']) ||
$quota['allocation']['allocated'] <= 0
) {
return false;
}
$quota_used = $quota['used'];
$quota_total = $quota['allocation']['allocated'];
$used_perc = round($quota_used * 100 / $quota_total, 1);
$available_quota = $quota_total - $quota_used;
return [
'used' => $quota_used,
'total' => $quota_total,
'perc' => $used_perc,
'available' => size_format($available_quota),
];
}
/**
* Get upload chunk size in bytes
*
* @return int bytes
*/
public function getUploadChunkSize()
{
$dGlobal = DynamicGlobalEntity::getInstance();
$size = (int) $dGlobal->getVal('dropbox_upload_chunksize_in_kb', 2000);
return $size * KB_IN_BYTES;
}
/**
* Get upload chunk timeout in seconds
*
* @return int timeout in microseconds, 0 unlimited
*/
public function getUploadChunkTimeout()
{
$global = DUP_PRO_Global_Entity::getInstance();
return (int) ($global->php_max_worker_time_in_sec <= 0 ? 0 : $global->php_max_worker_time_in_sec * SECONDS_IN_MICROSECONDS);
}
/**
* @return void
*/
public static function registerType()
{
parent::registerType();
add_action('duplicator_update_global_storage_settings', function () {
$dGlobal = DynamicGlobalEntity::getInstance();
foreach (static::getDefaultSettings() as $key => $default) {
$value = SnapUtil::sanitizeIntInput(SnapUtil::INPUT_REQUEST, $key, $default);
$dGlobal->setVal($key, $value);
}
});
}
/**
* Get default settings
*
* @return array<string, scalar>
*/
protected static function getDefaultSettings()
{
return [
'dropbox_upload_chunksize_in_kb' => 2000,
'dropbox_transfer_mode' => DUP_PRO_Dropbox_Transfer_Mode::Unconfigured,
];
}
/**
* @return void
*/
public static function renderGlobalOptions()
{
$values = static::getDefaultSettings();
$dGlobal = DynamicGlobalEntity::getInstance();
foreach ($values as $key => $default) {
$values[$key] = $dGlobal->getVal($key, $default);
}
?>
<h3 class="title"><?php echo esc_html(static::getStypeName()) ?> </h3>
<hr size="1" />
<table class="form-table">
<tr valign="top">
<th scope="row"><label><?php esc_html_e("Upload Chunk Size", 'duplicator-pro'); ?></label></th>
<td>
<input
class="dup-narrow-input text-right"
name="dropbox_upload_chunksize_in_kb"
id="dropbox_upload_chunksize_in_kb"
type="number"
min="100"
data-parsley-required
data-parsley-type="number"
data-parsley-errors-container="#dropbox_upload_chunksize_in_kb_error_container"
value="<?php echo (int) $values['dropbox_upload_chunksize_in_kb']; ?>"
>&nbsp;<b>KB</b>
<div id="dropbox_upload_chunksize_in_kb_error_container" class="duplicator-error-container"></div>
<p class="description">
<?php esc_html_e('How much should be uploaded to Dropbox per attempt. Higher=faster but less reliable.', 'duplicator-pro'); ?>
</p>
</td>
</tr>
</table>
<?php
}
}

View File

@@ -0,0 +1,72 @@
<?php
namespace Duplicator\Addons\DropboxAddon\Utils;
use Duplicator\Addons\DropboxAddon\DropboxAddon;
use Duplicator\Utils\AbstractAutoloader;
class Autoloader extends AbstractAutoloader
{
const ROOT_VENDOR = 'VendorDuplicator\\Dropbox\\';
const VENDOR_PATH = DropboxAddon::ADDON_PATH . '/vendor-prefixed/';
/**
* Register autoloader function
*
* @return void
*/
public static function register()
{
spl_autoload_register([__CLASS__, 'load']);
require_once DropboxAddon::ADDON_PATH . '/vendor-prefixed/guzzlehttp/guzzle/src/functions_include.php';
require_once DropboxAddon::ADDON_PATH . '/vendor-prefixed/guzzlehttp/promises/src/functions_include.php';
require_once DropboxAddon::ADDON_PATH . '/vendor-prefixed/guzzlehttp/psr7/src/functions_include.php';
require_once DropboxAddon::ADDON_PATH . '/vendor-prefixed/ralouphie/getallheaders/src/getallheaders.php';
}
/**
* Load class
*
* @param string $className class name
*
* @return void
*/
public static function load($className)
{
if (strpos($className, self::ROOT_VENDOR) === 0) {
foreach (self::getNamespacesVendorMapping() as $namespace => $mappedPath) {
if (strpos($className, $namespace) !== 0) {
continue;
}
$filepath = self::getFilenameFromClass($className, $namespace, $mappedPath);
if (file_exists($filepath)) {
include $filepath;
return;
}
}
}
}
/**
* Return namespace mapping
*
* @return string[]
*/
protected static function getNamespacesVendorMapping()
{
return [
self::ROOT_VENDOR . 'Google\\Service' => self::VENDOR_PATH . 'google/apiclient-services/src',
self::ROOT_VENDOR . 'Google\\Auth' => self::VENDOR_PATH . 'google/auth/src',
self::ROOT_VENDOR . 'Google' => self::VENDOR_PATH . 'google/apiclient/src',
self::ROOT_VENDOR . 'GuzzleHttp\\Promise' => self::VENDOR_PATH . 'guzzlehttp/promises/src',
self::ROOT_VENDOR . 'GuzzleHttp\\Psr7' => self::VENDOR_PATH . 'guzzlehttp/psr7/src',
self::ROOT_VENDOR . 'GuzzleHttp' => self::VENDOR_PATH . 'guzzlehttp/guzzle/src',
self::ROOT_VENDOR . 'Psr\\Http\\Message' => self::VENDOR_PATH . 'psr/http-message/src',
self::ROOT_VENDOR . 'Psr\\Log' => self::VENDOR_PATH . 'psr/log/Psr/Log',
self::ROOT_VENDOR . 'Psr\\Cache' => self::VENDOR_PATH . 'psr/cache/src',
self::ROOT_VENDOR . 'Spatie\\Dropbox' => self::VENDOR_PATH . 'spatie/dropbox-api/src',
];
}
}

View File

@@ -0,0 +1,179 @@
<?php
namespace Duplicator\Addons\DropboxAddon\Utils;
use DUP_PRO_Global_Entity;
use DUP_PRO_Log;
use VendorDuplicator\Dropbox\GuzzleHttp\Client as GuzzleClient;
use VendorDuplicator\Dropbox\Spatie\Dropbox\Client;
class DropboxClient extends Client
{
const OAUTH2_URL = 'https://www.dropbox.com/oauth2/';
/**
* Class contructor
*
* @param string $accessToken The access token
* @param GuzzleClient|null $client The HTTP client
* @param int $maxChunkSize The maximum size of a chunk
* @param int $maxUploadChunkRetries The maximum number of times to retry a chunk upload
* @param bool $sslVerify If true, use SSL
* @param string $sslCert If empty use server cert
* @param bool $ipv4Only If true, use IPv4 only
*/
public function __construct(
$accessToken,
GuzzleClient $client = null,
$maxChunkSize = self::MAX_CHUNK_SIZE,
$maxUploadChunkRetries = 0,
$sslVerify = true,
$sslCert = '',
$ipv4Only = false
) {
if (!$client) {
$options = [];
if ($sslVerify === false) {
$verify = false;
} elseif (strlen($sslCert) === 0) {
$verify = true;
} else {
$verify = $sslCert;
}
$options['verify'] = $verify;
if ($ipv4Only) {
$options['force_ip_resolve'] = 'v4';
}
$options['handler'] = self::createHandler();
$client = new GuzzleClient($options);
}
parent::__construct($accessToken, $client, $maxChunkSize, $maxUploadChunkRetries);
}
/**
* Exchange the oauth1 token for an oauth2 token
*
* @see https://www.dropbox.com/developers/documentation/http/documentation#auth-token-from_oauth1
*
* @param array{t: string, s: string} $token The oauth1 token
* @param array{app_key: string, app_secret: string} $app_config The app config
*
* @return string|false
*/
public static function accessTokenFromOauth1($token, $app_config)
{
$url = "https://api.dropboxapi.com/2/auth/token/from_oauth1";
$response = wp_remote_post($url, [
'timeout' => 30,
'headers' => [
'Authorization' => 'Basic ' . base64_encode($app_config['app_key'] . ':' . $app_config['app_secret']),
],
'body' => [
'oauth1_token' => $token['t'],
'oauth1_token_secret' => $token['s'],
],
]);
if (is_wp_error($response)) {
DUP_PRO_Log::traceObject("[DropboxClient] Something wrong with while trying to get v2_access_token with oauth1 token.", $response);
return false;
}
$ret_obj = json_decode($response['body']);
if (isset($ret_obj->oauth2_token)) {
return $ret_obj->oauth2_token;
}
return false;
}
/**
* Use the app config to authenticate and get the access token
*
* @param string $auth_code The authorization code
* @param array{app_key: string, app_secret: string} $app_config The app config
*
* @return bool
*/
public function authenticate($auth_code, $app_config)
{
$url = self::OAUTH2_URL . 'token';
$args = $this->injectExtraReqArgs([
'timeout' => 30,
'body' => [
'client_id' => $app_config['app_key'],
'client_secret' => $app_config['app_secret'],
'code' => $auth_code,
'grant_type' => 'authorization_code',
],
]);
$response = wp_remote_post($url, $args);
if (is_wp_error($response)) {
DUP_PRO_Log::traceObject("Something wrong with while trying to get v2_access_token with code", $response);
return false;
}
DUP_PRO_Log::traceObject("Got v2 access_token", $response);
$ret_obj = json_decode($response['body']);
if (isset($ret_obj->access_token)) {
return $ret_obj->access_token;
}
return false;
}
/**
* Get the account's usage quota information.
*
* @return array{used: int, allocation: array{allocated: int}}|false
*/
public function getQuota()
{
try {
return $this->rpcEndpointRequest('users/get_space_usage');
} catch (\Exception $e) {
\DUP_PRO_Log::trace('[DropboxClient] ' . $e->getMessage());
return false;
}
}
/**
* Set the timeout for the client
*
* @param int $timeout The timeout in seconds
*
* @return void
*/
public function setTimeout($timeout)
{
if ($timeout > 0) {
$this->client = new GuzzleClient(['handler' => self::createHandler(), 'timeout' => $timeout]);
} else {
$this->client = new GuzzleClient(['handler' => self::createHandler()]);
}
}
/**
* Inject extra request arguments
*
* @param array<string, mixed> $opts The request options
*
* @return array<string, mixed>
*/
private function injectExtraReqArgs(array $opts)
{
$global = DUP_PRO_Global_Entity::getInstance();
$opts['sslverify'] = !$global->ssl_disableverify;
if (!$global->ssl_useservercerts) {
$opts['sslcertificates'] = DUPLICATOR_PRO_CERT_PATH;
}
return $opts;
}
}

View File

@@ -0,0 +1,285 @@
<?php
/**
* Duplicator messages sections
*
* @package Duplicator
* @copyright (c) 2022, Snap Creek LLC
*/
use Duplicator\Addons\DropboxAddon\Models\DropboxStorage;
defined("ABSPATH") or die("");
/**
* Variables
*
* @var \Duplicator\Core\Controllers\ControllersManager $ctrlMng
* @var \Duplicator\Core\Views\TplMng $tplMng
* @var array<string, mixed> $tplData
* @var DropboxStorage $storage
*/
$storage = $tplData["storage"];
/** @var false|object */
$accountInfo = $tplData["accountInfo"];
/** @var false|array{used:int,total:int,perc:float,available:string} */
$quotaInfo = $tplData["quotaInfo"];
/** @var string */
$storageFolder = $tplData["storageFolder"];
/** @var int */
$maxPackages = $tplData["maxPackages"];
$tplMng->render('admin_pages/storages/parts/provider_head');
?>
<tr>
<th scope="row"><label><?php esc_html_e("Authorization", 'duplicator-pro'); ?></label></th>
<td class="dropbox-authorize">
<div class="authorization-state" id="state-unauthorized">
<!-- CONNECT -->
<button id="dpro-dropbox-connect-btn" type="button" class="button button-large" onclick="DupPro.Storage.Dropbox.DropboxGetAuthUrl();">
<i class="fa fa-plug"></i> <?php esc_html_e('Connect to Dropbox', 'duplicator-pro'); ?>
<img
src="<?php echo esc_url(DUPLICATOR_PRO_IMG_URL . '/dropbox.svg'); ?>"
style='vertical-align: middle; margin:-2px 0 0 3px; height:18px; width:18px'
>
</button>
</div>
<div class="authorization-state" id="state-waiting-for-request-token">
<div style="padding:10px">
<i class="fas fa-circle-notch fa-spin"></i> <?php esc_html_e('Getting Dropbox request token...', 'duplicator-pro'); ?>
</div>
</div>
<div class="authorization-state" id="state-waiting-for-auth-button-click">
<!-- STEP 2 -->
<b><?php esc_html_e("Step 1:", 'duplicator-pro'); ?></b>&nbsp;
<?php esc_html_e(' Duplicator needs to authorize at the Dropbox.com website.', 'duplicator-pro'); ?>
<div class="auth-code-popup-note">
<?php
echo esc_html__(
'Note: Clicking the button below will open a new tab/window. Please be sure your browser does not block popups.',
'duplicator-pro'
) . ' ' .
esc_html__('If a new tab/window does not open check your browsers address bar to allow popups from this URL.', 'duplicator-pro');
?>
</div>
<button id="auth-redirect" type="button" class="button button-large" onclick="DupPro.Storage.Dropbox.OpenAuthPage(); return false;">
<i class="fa fa-user"></i> <?php esc_html_e('Authorize Dropbox', 'duplicator-pro'); ?>
</button>
<br/><br/>
<div id="dropbox-auth-code-area">
<b><?php esc_html_e('Step 2:', 'duplicator-pro'); ?></b>
<?php esc_html_e("Paste code from Dropbox authorization page.", 'duplicator-pro'); ?> <br/>
<input style="width:400px" id="dropbox-auth-code" name="dropbox-auth-code" />
</div>
<!-- STEP 3 -->
<b><?php esc_html_e("Step 3:", 'duplicator-pro'); ?></b>&nbsp;
<?php esc_html_e('Finalize Dropbox validation by clicking the "Finalize Setup" button.', 'duplicator-pro'); ?>
<br/>
<button id="dropbox-finalize-setup" type="button" class="button" >
<i class="fa fa-check-square"></i> <?php esc_html_e('Finalize Setup', 'duplicator-pro'); ?>
</button>
</div>
<div class="authorization-state" id="state-waiting-for-access-token">
<div>
<i class="fas fa-circle-notch fa-spin"></i>
<?php esc_html_e('Performing final authorization...Please wait', 'duplicator-pro'); ?>
</div>
</div>
<div class="authorization-state" id="state-authorized" style="margin-top:-5px">
<?php if ($storage->isAuthorized() && is_array($accountInfo)) : ?>
<h3>
<?php esc_html_e('Dropbox Account', 'duplicator-pro'); ?><br/>
<i class="dpro-edit-info">
<?php esc_html_e('Duplicator has been authorized to access this user\'s Dropbox account', 'duplicator-pro'); ?>
</i>
</h3>
<div id="dropbox-account-info">
<label><?php esc_html_e('Name', 'duplicator-pro'); ?>:</label>
<?php echo esc_html($accountInfo['name']['display_name']); ?><br/>
<label><?php esc_html_e('Email', 'duplicator-pro'); ?>:</label>
<?php echo esc_html($accountInfo['email']); ?>
<?php if ($quotaInfo) { ?>
<br/>
<label><?php esc_html_e('Quota Usage', 'duplicator-pro'); ?>:</label>
<?php
printf(esc_html__('%1$s%% used, %2$s available', 'duplicator-pro'), (int) $quotaInfo['perc'], $quotaInfo['available']);
}
?>
</div>
<?php endif; ?>
<br/>
<button type="button" class="button" onclick='DupPro.Storage.Dropbox.CancelAuthorization();'>
<?php esc_html_e('Cancel Authorization', 'duplicator-pro'); ?>
</button><br/>
<i class="dpro-edit-info">
<?php esc_html_e('Disassociates storage provider with the Dropbox account. Will require re-authorization.', 'duplicator-pro'); ?>
</i>
</div>
</td>
</tr>
<tr>
<th scope="row"><label for="_dropbox_storage_folder"><?php esc_html_e("Storage Folder", 'duplicator-pro'); ?></label></th>
<td>
<b>//Dropbox/Apps/Duplicator Pro/</b>
<input id="_dropbox_storage_folder" name="_dropbox_storage_folder"
type="text" value="<?php echo esc_attr($storageFolder); ?>" class="dpro-storeage-folder-path" />
<p>
<i>
<?php
esc_html_e(
"Folder where packages will be stored. This should be unique for each web-site using Duplicator.",
'duplicator-pro'
); ?>
</i>
</p>
</td>
</tr>
<tr>
<th scope="row"><label for=""><?php esc_html_e("Max Packages", 'duplicator-pro'); ?></label></th>
<td>
<label for="dropbox_max_files">
<input
id="dropbox_max_files"
name="dropbox_max_files"
type="number"
value="<?php echo (int) $maxPackages; ?>"
min="0"
maxlength="4"
data-parsley-errors-container="#dropbox_max_files_error_container"
data-parsley-required="true"
data-parsley-type="number"
data-parsley-min="0"
>
<?php esc_html_e("Number of packages to keep in folder.", 'duplicator-pro'); ?> <br/>
<i><?php esc_html_e("When this limit is exceeded, the oldest package will be deleted. Set to 0 for no limit.", 'duplicator-pro'); ?></i>
</label>
<div id="dropbox_max_files_error_container" class="duplicator-error-container"></div>
</td>
</tr>
<?php $tplMng->render('admin_pages/storages/parts/provider_foot'); ?>
<?php
$alertConnStatus = new DUP_PRO_UI_Dialog();
$alertConnStatus->title = __('Dropbox Connection Status', 'duplicator-pro');
$alertConnStatus->message = ''; // javascript inserted message
$alertConnStatus->initAlert();
?>
<script>
jQuery(document).ready(function ($) {
// DROPBOX RELATED METHODS
DupPro.Storage.Dropbox.AuthorizationStates = {
UNAUTHORIZED: 0,
WAITING_FOR_REQUEST_TOKEN: 1,
WAITING_FOR_AUTH_BUTTON_CLICK: 2,
WAITING_FOR_ACCESS_TOKEN: 3,
AUTHORIZED: 4
}
DupPro.Storage.Dropbox.authorizationState = <?php echo ($storage->isAuthorized() ? 4 : 0); ?>;
DupPro.Storage.Dropbox.CancelAuthorization = function () {
DupPro.Storage.RevokeAuth(<?php echo (int) $storage->getId(); ?>);
}
DupPro.Storage.Dropbox.DropboxGetAuthUrl = function ()
{
DupPro.Storage.Dropbox.AuthUrl = <?php echo json_encode($storage->getAuthorizationUrl()); ?>;
jQuery("#state-waiting-for-auth-button-click").show();
};
DupPro.Storage.Dropbox.TransitionAuthorizationState = function (newState)
{
jQuery('.authorization-state').hide();
jQuery('.dropbox_access_type').prop('disabled', true);
jQuery('.button_dropbox_test').prop('disabled', true);
switch (newState) {
case DupPro.Storage.Dropbox.AuthorizationStates.UNAUTHORIZED:
jQuery('.dropbox_access_type').prop('disabled', false);
$("#dropbox_authorization_state").val(DupPro.Storage.Dropbox.AuthorizationStates.UNAUTHORIZED);
DupPro.Storage.Dropbox.requestToken = null;
jQuery("#state-unauthorized").show();
break;
case DupPro.Storage.Dropbox.AuthorizationStates.WAITING_FOR_REQUEST_TOKEN:
DupPro.Storage.Dropbox.GetRequestToken();
jQuery("#state-waiting-for-request-token").show();
break;
case DupPro.Storage.Dropbox.AuthorizationStates.WAITING_FOR_AUTH_BUTTON_CLICK:
// Nothing to do here other than show the button and wait
jQuery("#state-waiting-for-auth-button-click").show();
break;
case DupPro.Storage.Dropbox.AuthorizationStates.WAITING_FOR_ACCESS_TOKEN:
jQuery("#state-waiting-for-access-token").show();
if (DupPro.Storage.Dropbox.requestToken != null) {
DupPro.Storage.Dropbox.GetAccessToken();
} else {
<?php $alertConnStatus->showAlert(); ?>
let alertMsg = "<i class='fas fa-exclamation-triangle'></i> " +
"<?php esc_html_e('Tried transitioning to auth button click but don\'t have the request token!', 'duplicator-pro'); ?>";
<?php $alertConnStatus->updateMessage("alertMsg"); ?>
DupPro.Storage.Dropbox.TransitionAuthorizationState(DupPro.Storage.Dropbox.AuthorizationStates.UNAUTHORIZED);
}
break;
case DupPro.Storage.Dropbox.AuthorizationStates.AUTHORIZED:
var token = $("#dropbox_access_token").val();
var token_secret = $("#dropbox_access_token_secret").val();
DupPro.Storage.Dropbox.accessToken = {};
DupPro.Storage.Dropbox.accessToken.t = token;
DupPro.Storage.Dropbox.accessToken.s = token_secret;
jQuery("#state-authorized").show();
jQuery('.button_dropbox_test').prop('disabled', false);
break;
}
DupPro.Storage.Dropbox.authorizationState = newState;
}
DupPro.Storage.Dropbox.OpenAuthPage = function ()
{
window.open(DupPro.Storage.Dropbox.AuthUrl, '_blank');
}
$('#dropbox-finalize-setup').click(function (event) {
event.stopPropagation();
if ($('#dropbox-auth-code').val().length > 5) {
DupPro.Storage.PrepareForSubmit();
//$("#dup-storage-form").submit();
DupPro.Storage.Authorize(
<?php echo (int) $storage->getId(); ?>,
<?php echo (int) $storage->getSType(); ?>,
{
'name': $('#name').val(),
'notes': $('#notes').val(),
'storage_folder': $('#_dropbox_storage_folder').val(),
'max_packages': $('#dropbox_max_files').val(),
'auth_code' : $('#dropbox-auth-code').val()
}
);
} else {
<?php $alertConnStatus->showAlert(); ?>
let alertMsg = "<i class='fas fa-exclamation-triangle'></i> " +
"<?php esc_html_e('Please enter your Dropbox authorization code!', 'duplicator-pro'); ?>";
<?php $alertConnStatus->updateMessage("alertMsg"); ?>
}
return false;
});
DupPro.Storage.Dropbox.TransitionAuthorizationState(DupPro.Storage.Dropbox.authorizationState);
$('button#auth-validate').prop('disabled', true);
});
</script>