first commit

This commit is contained in:
Roman Pyrih
2026-04-21 15:48:41 +02:00
commit 7483681901
10216 changed files with 3236626 additions and 0 deletions

View File

@@ -0,0 +1,90 @@
<?php
namespace Duplicator\Addons\OneDriveAddon;
use Duplicator\Addons\OneDriveAddon\Models\OneDriveStorage;
use Duplicator\Core\Addons\AbstractAddonCore;
use Duplicator\Models\Storages\AbstractStorageEntity;
class OneDriveAddon extends AbstractAddonCore
{
const ADDON_PATH = __DIR__;
/**
* @return void
*/
public function init(): void
{
add_action('duplicator_register_storage_types', [$this, 'registerStorages']);
add_filter('duplicator_template_file', [self::class, 'getTemplateFile'], 10, 2);
add_filter('duplicator_usage_stats_storages_infos', [self::class, 'getStorageUsageStats'], 10);
}
/**
* Register storages
*
* @return void
*
* @throws \Exception
*/
public function registerStorages(): void
{
OneDriveStorage::registerType();
}
/**
* 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, 'onedriveaddon/') === 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_onedrive_count'] = 0;
foreach ($storages as $storage) {
if ($storage->getSType() === OneDriveStorage::getSType()) {
$storageNums['storages_onedrive_count']++;
}
}
return $storageNums;
}
/**
* @return string
*/
public static function getAddonPath()
{
return static::ADDON_PATH;
}
/**
* @return string
*/
public static function getAddonFile(): string
{
return __FILE__;
}
}

View File

@@ -0,0 +1,579 @@
<?php
/**
*
* @package Duplicator
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Addons\OneDriveAddon\Models;
use Duplicator\Models\GlobalEntity;
use Duplicator\Utils\Logging\DupLog;
use Duplicator\Addons\OneDriveAddon\OnedriveAdapter;
use Duplicator\Addons\OneDriveAddon\OneDriveStoragePathInfo;
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\OAuth\TokenEntity;
use Duplicator\Utils\OAuth\TokenService;
use Exception;
/**
* @property OneDriveAdapter $adapter
*/
class OneDriveStorage extends AbstractStorageEntity implements StorageAuthInterface
{
const GRAPH_SCOPES = [
'openid',
'offline_access',
'files.readwrite.appfolder',
];
const CLIENT_ID = '15fa3a0d-b7ee-447c-8093-7bfcf30b0797';
const LOGOUT_REDIRECT_URI = 'https://snapcreek.com/misc/onedrive/redir3.php';
const UPLOAD_CHUNK_MIN_SIZE_IN_KB = 320;
const UPLOAD_CHUNK_DEFAULT_SIZE_IN_KB = 3200;
const DEFAULT_DOWNLOAD_CHUNK_SIZE_IN_KB = 10 * 1024;
const MIN_DOWNLOAD_CHUNK_SIZE_IN_KB = 2 * 1024;
/**
* @var null|OneDriveAdapter Storage adapter
*/
protected $adapter;
/**
* Get priority, used to sort storages.
* 100 is neutral value, 0 is the highest priority
*
* @return int
*/
public static function getPriority(): int
{
return 320;
}
/**
* Get default config
*
* @return array<string,scalar>
*/
protected static function getDefaultConfig(): array
{
$config = parent::getDefaultConfig();
return array_merge(
$config,
[
'endpoint_url' => '',
'resource_id' => '',
'access_token' => '',
'refresh_token' => '',
'token_obtained' => 0,
'storage_folder_id' => '',
'storage_folder_web_url' => '',
'all_folders_perm' => false,
'authorized' => false,
]
);
}
/**
* Will be called, automatically, when Serialize
*
* @return array<string, mixed>
*/
public function __serialize(): array
{
$data = parent::__serialize();
unset($data['client']);
return $data;
}
/**
* Return the storage type
*
* @return int
*/
public static function getSType(): int
{
return 7;
}
/**
* Returns the storage type icon URL
*
* @return string Returns the storage icon URL
*/
public static function getStypeIconURL(): string
{
return DUPLICATOR_IMG_URL . '/onedrive.svg';
}
/**
* Returns the storage type name.
*
* @return string
*/
public static function getStypeName(): string
{
return __('OneDrive', 'duplicator-pro');
}
/**
* Get storage location string
*
* @return string
*/
public function getLocationString(): string
{
if (!$this->isAuthorized()) {
return __("Not Authenticated", "duplicator-pro");
} else {
return (string) $this->config['storage_folder_web_url'];
}
}
/**
* Returns an html anchor tag of location
*
* @return string Returns an html anchor tag with the storage location as a hyperlink.
*
* @example
* OneDrive Example return
* <a target="_blank" href="https://1drv.ms/f/sAFrQtasdrewasyghg">https://1drv.ms/f/sAFrQtasdrewasyghg</a>
*/
public function getHtmlLocationLink(): string
{
if ($this->isAuthorized()) {
return '<a href="' . esc_url($this->getLocationString()) . '" target="_blank" >' . esc_html($this->getStorageFolder()) . '</a>';
} else {
return $this->getLocationString();
}
}
/**
* Check if storage is supported
*
* @return bool
*/
public static function isSupported(): bool
{
return SnapUtil::isCurlEnabled();
}
/**
* Get supported notice, displayed if storage isn't supported
*
* @return string html string or empty if storage is supported
*/
public static function getNotSupportedNotice(): string
{
if (static::isSupported()) {
return '';
}
return esc_html__('OneDrive requires the PHP CURL extension enabled.', 'duplicator-pro');
}
/**
* Check if storage is valid
*
* @param ?string $errorMsg Reference to store error message
* @param bool $force Force the storage to be revalidated
*
* @return bool Return true if storage is valid and ready to use, false otherwise
*/
public function isValid(?string &$errorMsg = '', bool $force = false): bool
{
if (!$this->isAuthorized()) {
$errorMsg = __('Not authenticated.', 'duplicator-pro');
return false;
}
return true;
}
/**
* Is autorized
*
* @return bool
*/
public function isAuthorized(): bool
{
return (bool) ($this->config['authorized'] ?? false);
}
/**
* Get the chunk size bytes
*
* @return int
*/
public function getUploadChunkSize(): int
{
return DynamicGlobalEntity::getInstance()->getValInt(
'onedrive_upload_chunksize_in_kb',
self::UPLOAD_CHUNK_DEFAULT_SIZE_IN_KB
) * KB_IN_BYTES;
}
/**
* Get the chunk size bytes
*
* @return int
*/
public function getDownloadChunkSize(): int
{
return DynamicGlobalEntity::getInstance()->getValInt(
'onedrive_download_chunksize_in_kb',
self::DEFAULT_DOWNLOAD_CHUNK_SIZE_IN_KB
) * KB_IN_BYTES;
}
/**
* Get upload chunk timeout in seconds
*
* @return int timeout in microseconds, 0 unlimited
*/
public function getUploadChunkTimeout(): int
{
$global = GlobalEntity::getInstance();
return (int) ($global->php_max_worker_time_in_sec <= 0 ? 0 : $global->php_max_worker_time_in_sec * SECONDS_IN_MICROSECONDS);
}
/**
* Authorized from HTTP request
*
* @param string $message Message
*
* @return bool True if authorized, false if failed
*/
public function authorizeFromRequest(&$message = ''): bool
{
try {
if (($refreshToken = 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();
$token = (new TokenEntity(self::getSType(), ['refresh_token' => $refreshToken]));
if (! $token->refresh(true)) {
throw new Exception(__('Failed to fetch information from OneDrive. Make sure the token is valid.', 'duplicator-pro'));
}
$this->config['access_token'] = $token->getAccessToken();
$this->config['refresh_token'] = $token->getRefreshToken();
$this->config['token_obtained'] = $token->getCreated();
$this->config['endpoint_url'] = $this->config['resource_id'] = '';
$this->config['authorized'] = true;
DupLog::traceObject("OneDrive App folder: ", $storageFolder = $this->getOneDriveStorageFolder());
if (! $storageFolder) {
throw new Exception("Failed to fetch information from OneDrive. Make sure the token is valid.");
}
// Get the storage folder id
$this->config['storage_folder_id'] = $storageFolder->id;
$this->config['storage_folder_web_url'] = $storageFolder->webUrl;
} catch (Exception $e) {
DupLog::trace("Problem authorizing OneDrive access token msg: " . $e->getMessage());
$message = $e->getMessage();
return false;
}
$message = __('OneDrive 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 = ''): bool
{
if (!$this->isAuthorized()) {
$message = __('Onedrive isn\'t authorized.', 'duplicator-pro');
return true;
}
$this->config['endpoint_url'] = '';
$this->config['resource_id'] = '';
$this->config['access_token'] = '';
$this->config['refresh_token'] = '';
$this->config['token_obtained'] = 0;
$this->config['storage_folder_id'] = '';
$this->config['storage_folder_web_url'] = '';
$this->config['authorized'] = false;
$message = __('Onedrive is disconnected successfully.', 'duplicator-pro');
return true;
}
/**
* Get external revoke url
*
* @return string
*/
public function getExternalRevokeUrl(): string
{
$base_url = "https://login.microsoftonline.com/common/oauth2/v2.0/logout";
$fields_arr = [
"client_id" => self::CLIENT_ID,
"post_logout_redirect_uri" => self::LOGOUT_REDIRECT_URI,
];
$query = http_build_query($fields_arr);
return $base_url . "?$query";
}
/**
* Get authorization URL
*
* @return string
*/
public function getAuthorizationUrl(): string
{
return (new TokenService(static::getSType()))->getRedirectUri();
}
/**
* Returns the config fields template data
*
* @return array<string, mixed>
*/
protected function getConfigFieldsData(): array
{
$hasError = $this->isAuthorized() && !$this->getAdapter()->isValid();
return array_merge($this->getDefaultConfigFieldsData(), [
'accountInfo' => $this->getAccountInfo(),
'hasError' => $hasError,
]);
}
/**
* Returns the default config fields template data
*
* @return array<string, mixed>
*/
protected function getDefaultConfigFieldsData(): array
{
return [
'storage' => $this,
'storageFolder' => $this->config['storage_folder'],
'maxPackages' => $this->config['max_packages'],
'allFolderPers' => $this->config['all_folders_perm'],
'accountInfo' => false,
'hasError' => false,
'externalRevokeUrl' => $this->getExternalRevokeUrl(),
];
}
/**
* Returns the config fields template path
*
* @return string
*/
protected function getConfigFieldsTemplatePath(): string
{
return 'onedriveaddon/configs/onedrive';
}
/**
* 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 = ''): bool
{
if ((parent::updateFromHttpRequest($message) === false)) {
return false;
}
$this->config['max_packages'] = SnapUtil::sanitizeIntInput(SnapUtil::INPUT_REQUEST, 'onedrive_msgraph_max_files', 10);
$oldFolder = $this->config['storage_folder'];
$this->config['storage_folder'] = self::getSanitizedInputFolder('_onedrive_msgraph_storage_folder', 'remove');
$this->config['all_folders_perm'] = SnapUtil::sanitizeBoolInput(SnapUtil::INPUT_REQUEST, 'onedrive_msgraph_all_folders_read_write_perm', false);
if ($this->isAuthorized() && $oldFolder != $this->config['storage_folder']) {
$this->config['storage_folder_id'] = '';
// Create new folder
$folder = $this->getOneDriveStorageFolder();
$this->config['storage_folder_id'] = $folder->id;
$this->config['storage_folder_web_url'] = $folder->webUrl;
$this->save();
}
$message = sprintf(
__('OneDrive Storage Updated.', 'duplicator-pro'),
$this->getStorageFolder()
);
return true;
}
/**
* Get account info
*
* @return false|object
*/
protected function getAccountInfo()
{
if (! $this->isAuthorized()) {
return false;
}
$storageFolder = $this->getOneDriveStorageFolder();
if (!$storageFolder || empty($storageFolder->user)) {
return false;
}
return (object) $storageFolder->user;
}
/**
* Get onedrive storage folder
*
* @return OneDriveStoragePathInfo|false
*/
protected function getOneDriveStorageFolder()
{
$adapter = $this->getAdapter();
if (! $adapter->isValid()) {
DupLog::trace("OneDrive adapter is not valid, can't get storage folder.");
return false;
}
if (!$this->config['storage_folder_id']) {
if (! $adapter->initialize($error)) {
DupLog::trace("Failed to initialize OneDrive adapter: $error");
return false;
}
$folder = $adapter->getPathInfo('/');
$this->config['storage_folder_id'] = $folder->id;
$this->save();
} else {
$folder = $adapter->getPathInfo('/');
}
return $folder;
}
/**
* Get stoage adapter
*
* @return OnedriveAdapter
*/
protected function getAdapter(): OneDriveAdapter
{
if (!$this->adapter) {
$global = GlobalEntity::getInstance();
$token = $this->getTokenFromConfig();
$this->adapter = new OneDriveAdapter(
$token,
$this->config['storage_folder'],
$this->config['storage_folder_id'],
!$global->ssl_disableverify,
($global->ssl_useservercerts ? '' : DUPLICATOR_CERT_PATH)
);
if (! $this->adapter->initialize($error)) {
DupLog::trace("Failed to initialize OneDrive adapter: $error");
}
}
return $this->adapter;
}
/**
* Get the token entity using config values
*
* @return TokenEntity|false
*/
public function getTokenFromConfig()
{
$token = new TokenEntity(self::getSType(), [
'created' => $this->config['token_obtained'],
'expires_in' => 3600,
'scope' => self::GRAPH_SCOPES,
'access_token' => $this->config['access_token'],
'refresh_token' => $this->config['refresh_token'],
]);
if ($token->isAboutToExpire()) {
$token->refresh(true);
if (!$token->isValid()) {
return false;
}
$this->config['token_obtained'] = $token->getCreated();
$this->config['refresh_token'] = $token->getRefreshToken();
$this->config['access_token'] = $token->getAccessToken();
$this->save();
}
return $token;
}
/**
* @return void
* @throws Exception
*/
public static function registerType(): void
{
parent::registerType();
add_action('duplicator_update_global_storage_settings', function (): void {
$dGlobal = DynamicGlobalEntity::getInstance();
foreach (static::getDefaultSettings() as $key => $default) {
$value = SnapUtil::sanitizeIntInput(SnapUtil::INPUT_REQUEST, $key, $default);
$dGlobal->setValInt($key, $value);
}
});
}
/**
* Get default settings
*
* @return array<string, scalar>
*/
protected static function getDefaultSettings(): array
{
return [
'onedrive_upload_chunksize_in_kb' => self::UPLOAD_CHUNK_DEFAULT_SIZE_IN_KB,
'onedrive_download_chunksize_in_kb' => self::DEFAULT_DOWNLOAD_CHUNK_SIZE_IN_KB,
];
}
/**
* @return void
*/
public static function renderGlobalOptions(): void
{
$dGlobal = DynamicGlobalEntity::getInstance();
TplMng::getInstance()->render(
'onedriveaddon/configs/global_options',
[
'uploadChunkSize' => $dGlobal->getValInt('onedrive_upload_chunksize_in_kb', self::UPLOAD_CHUNK_DEFAULT_SIZE_IN_KB),
'downloadChunkSize' => $dGlobal->getValInt('onedrive_download_chunksize_in_kb', self::DEFAULT_DOWNLOAD_CHUNK_SIZE_IN_KB),
]
);
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Duplicator\Addons\OneDriveAddon;
use Duplicator\Models\Storages\StoragePathInfo;
class OneDriveStoragePathInfo extends StoragePathInfo
{
/** @var string */
public $id = '';
/** @var string */
public $name = '';
/** @var string */
public $webUrl = '';
/**
* @var ?array{mimeType: string, hashes: array{sha1Hash: string, quickXorHash: string, sha256Hash: string}}
*/
public $file;
/**
* @var array{id: string, displayName: string}|null
*/
public $user;
}

View File

@@ -0,0 +1,906 @@
<?php
namespace Duplicator\Addons\OneDriveAddon;
use Duplicator\Utils\Logging\DupLog;
use Duplicator\Models\Storages\AbstractStorageAdapter;
use Duplicator\Utils\OAuth\TokenEntity;
use Exception;
use VendorDuplicator\WpOrg\Requests\Requests;
use VendorDuplicator\WpOrg\Requests\Response;
/**
* @method OneDriveStoragePathInfo getPathInfo($path)
*/
class OnedriveAdapter extends AbstractStorageAdapter
{
/** @var TokenEntity The token object to use for authentication */
protected \Duplicator\Utils\OAuth\TokenEntity $token;
/** @var string The ID of the folder to use for storage */
protected $storageFolderId = '';
/** @var string The name of the folder to use for storage */
protected string $storageFolderName;
/** @var ?OneDriveStoragePathInfo The app folder object */
protected $appFolder;
/** @var string Base URL for API requests */
protected $baseUrl = 'https://graph.microsoft.com/v1.0';
/** @var bool */
protected $sslVerify = true;
/** @var string If empty use server cert else use custom cert path */
protected $sslCert = '';
/** @var int The microsecond at which the current operation started */
protected $startTime = 0;
/**
* Class constructor
*
* @param TokenEntity $token The token object to use for authentication
* @param string $storageFolder The folder to use for storage
* @param string $storageFolderId The ID of the folder to use for storage
* @param bool $sslVerify If true, use SSL
* @param string $sslCert If empty use server cert
*/
public function __construct(
TokenEntity $token,
$storageFolder,
$storageFolderId = '',
$sslVerify = true,
$sslCert = ''
) {
$this->token = $token;
$this->storageFolderName = trim($storageFolder, '/');
$this->storageFolderId = $storageFolderId;
$this->sslVerify = $sslVerify;
$this->sslCert = $sslCert;
}
/**
* Initialize the storage adapter
*
* @param string $errorMsg The error message to modify if initialization fails
*
* @return bool
*/
public function initialize(&$errorMsg = ''): bool
{
if (! $this->token->isValid()) {
$errorMsg = __('Invalid token supplied for OneDrive', 'duplicator-pro');
return false;
}
if (! $this->exists('/') && ! $this->createDir('/')) {
$errorMsg = __('Unable to create root directory for OneDrive', 'duplicator-pro');
return false;
}
if (empty($this->storageFolderId)) {
$root = $this->getPathInfo('/');
if (! $root || ! $root->exists) {
$errorMsg = 'OneDrive root folder does not exist.';
return false;
}
$this->storageFolderId = $root->id;
}
return true;
}
/**
* Destroy the storage adapter
*
* @return bool
*/
public function destroy(): bool
{
$storageFolder = $this->getPathInfo('/');
if (! $storageFolder || ! $storageFolder->exists) {
return true; // nothing to delete
}
return $this->delete('/', true);
}
/**
* Check if the storage adapter is valid
*
* @param string $errorMsg The error message to modify if validation fails
*
* @return bool
*/
protected function realIsValid(string &$errorMsg = ''): bool
{
if (!$this->token->isValid() && !$this->token->refresh()) {
$errorMsg = __('Invalid token supplied or token refresh failed.', 'duplicator-pro');
return false;
}
$root = $this->getPathInfo('/');
if (! $root || ! $root->exists) {
$errorMsg = __('OneDrive root folder does not exist.', 'duplicator-pro');
return false;
}
return true;
}
/**
* Create a directory in the storage
* If the given path is a nested path, it will create all the parent directories
*
* @param string $path The path to create
*
* @return bool
*/
protected function realCreateDir(string $path): bool
{
if (empty($this->storageFolderId)) {
$parentFolder = $this->getAppFolder()->id;
$path = trim($this->storageFolderName . '/' . trim($path, '/'), '/');
} else {
$parentFolder = $this->storageFolderId;
$path = trim($path, '/');
}
$folders = explode('/', $path);
foreach ($folders as $folder) {
$item = $this->getItemDetailsByPath($folder, $parentFolder);
if (!isset($item['id'])) {
try {
$item = $this->createDriveDirectory($parentFolder, $folder);
if (!isset($item['id'])) {
return false;
}
} catch (Exception $e) {
return false;
}
}
$parentFolder = $item['id'];
}
return true;
}
/**
* Create a file which is less that 4MB in the storage
*
* @see https://github.com/OneDrive/onedrive-api-docs/blob/live/docs/rest-api/api/driveitem_put_content.md
*
* @param string $path The path in which the file will be created
* @param string $content The content of the file
*
* @return false|int
*/
protected function realCreateFile(string $path, string $content)
{
// maximum content length is 4MB
if (strlen($content) > 4 * 1024 * 1024) {
return false;
}
$file = ltrim($path, '/');
try {
$response = $this->request("/me/drive/items/{$this->storageFolderId}:/{$file}:/content", ['Content-Type' => 'text/plain'], $content, Requests::PUT);
} catch (Exception $e) {
// Request failed from curl
DupLog::infoTrace("Failed to create file in OneDrive: {$e->getMessage()}");
return false;
}
$response = $response->decode_body();
if (!isset($response['id'])) {
return false;
}
return (int) $response['size'];
}
/**
* Delete relative path from storage root.
*
* @see https://github.com/OneDrive/onedrive-api-docs/blob/live/docs/rest-api/api/driveitem_delete.md
*
* @param string $path The path or drive item id to delete
* @param bool $recursive Whether to delete recursively
*
* @return bool
*/
protected function realDelete(string $path, bool $recursive = false): bool
{
if (! $this->exists($path)) {
return true;
}
$info = $this->getItemDetailsByPath($path);
if (! $recursive && isset($info['folder']) && $info['folder']['childCount'] > 0) {
return false;
}
try {
$response = $this->request("/me/drive/items/{$info['id']}", [], [], Requests::DELETE);
} catch (Exception $e) {
// Request failed from curl
DupLog::infoTrace("Failed to delete file in OneDrive: {$e->getMessage()}");
return false;
}
return $response->status_code === 204;
}
/**
* Get the contents of a file
*
* @param string $path The path to the file
*
* @return false|string
*/
public function getFileContent(string $path)
{
$item = $this->getItemDetailsByPath($path);
if (!isset($item['@microsoft.graph.downloadUrl'])) {
return false;
}
return file_get_contents($item['@microsoft.graph.downloadUrl']);
}
/**
* Get path info and cache it, is path not exists return path info with exists property set to false.
*
* @param string $path Relative storage path, if empty, return root path info.
*
* @return OneDriveStoragePathInfo|false The path info or false on error.
*/
protected function getRealPathInfo(string $path)
{
$path = '/' . ltrim($path, '/');
$item = $this->getItemDetailsByPath($path);
return $this->buildStoragePathInfo($item);
}
/**
* Move a file or directory. The destination path must not exist.
*
* @see https://github.com/OneDrive/onedrive-api-docs/blob/live/docs/rest-api/api/driveitem_move.md
*
* @param string $oldPath The path to the file or directory to move
* @param string $newPath The destination path
*
* @return bool
*/
protected function realMove(string $oldPath, string $newPath): bool
{
$oldItem = $this->getPathInfo($oldPath);
$newDirectoryItem = $this->getPathInfo(dirname($newPath));
if (!$oldItem || !$newDirectoryItem) {
return false;
}
try {
$response = $this->request(
"/me/drive/items/{$oldItem->id}",
[],
[
'parentReference' => [
'id' => $newDirectoryItem->id,
],
'name' => basename($newPath),
],
Requests::PATCH
);
} catch (Exception $e) {
// Request failed error from curl
DupLog::infoTrace("Failed to move file in OneDrive: {$e->getMessage()}");
return false;
}
$response = $response->decode_body();
return isset($response['id']);
}
/**
* @param string $path The path to the directory to scan
* @param bool $files Whether to include files
* @param bool $folders Whether to include folders
*
* @return string[]
*/
public function scanDir(string $path, bool $files = true, bool $folders = true): array
{
$path = '/' . ltrim($path, '/');
if ($path !== '/') {
// Paths under the storage folder must be prefixed with a colon, no need to do this for the storage folder itself
$path = ":{$path}:";
}
try {
$response = $this->request("/me/drive/items/{$this->storageFolderId}{$path}/children");
} catch (Exception $e) {
DupLog::infoTrace("Failed to scan dir in OneDrive: {$e->getMessage()}");
return [];
}
$items = $response->decode_body();
if (!isset($items['value'])) {
return [];
} else {
$items = $items['value'];
}
foreach ($items as $index => $item) {
$item = $this->buildStoragePathInfo($item);
$items[$index] = $item->name;
if (!$folders && $item->isDir) {
unset($items[$index]);
}
if (!$files && !$item->isDir) {
unset($items[$index]);
}
}
return $items;
}
/**
* Check if a directory is empty
*
* @param string $path The path to the directory
* @param string[] $filters An array of filters to apply
*
* @return bool
*/
public function isDirEmpty(string $path, array $filters = []): bool
{
$item = $this->getItemDetailsByPath($path);
if (!isset($item['folder'])) {
return false;
}
if ($item['folder']['childCount'] === 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;
}
/**
* Start tracking the time for the current operation
*
* @return void
*/
protected function startTrackingTime()
{
$this->startTime = (int) (microtime(true) * SECONDS_IN_MICROSECONDS);
}
/**
* Get the elapsed time since the start of the current operation
*
* @return float
*/
protected function getElapsedTime()
{
return (int) (microtime(true) * SECONDS_IN_MICROSECONDS) - $this->startTime;
}
/**
* Check if the operation has reached the timeout
*
* @param int $timeout The timeout in microseconds
*
* @return bool
*/
protected function hasReachedTimeout($timeout): bool
{
return $timeout > 0 && $this->getElapsedTime() > $timeout;
}
/**
* 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.
* This data is intended to be per-file and may be reset between files.
* @param array<string,mixed> $generalExtraData Extra data to pass to copy function that persists across files
* during the entire transfer operation.
*
* @return false|int The number of bytes that were written to the file, or false on failure.
*/
protected function realCopyToStorage(
string $sourceFile,
string $storageFile,
int $offset = 0,
int $length = -1,
int $timeout = 0,
array &$extraData = [],
array &$generalExtraData = []
) {
$this->startTrackingTime();
$sessionKey = md5($sourceFile . $storageFile);
if (! isset($extraData[$sessionKey]) || ! isset($extraData[$sessionKey]['uploadUrl'])) {
$extraData[$sessionKey] = $this->createUploadSession($storageFile);
DupLog::infoTrace("Created upload session for {$storageFile}, current session is: " . print_r($extraData, true));
}
$uploadSession = $extraData[$sessionKey];
if (! $uploadSession || ! isset($uploadSession['uploadUrl'])) {
DupLog::infoTrace("Failed to create upload session for {$storageFile}, try uploading again.");
return false;
}
$expiration = strtotime($uploadSession['expirationDateTime']);
$fileSize = filesize($sourceFile);
$defaultChunkSize = MB_IN_BYTES; // 1MB
$chunkSize = $length > 0 ? $length : $defaultChunkSize;
$stream = fopen($sourceFile, 'rb');
$bytesRemaining = $fileSize - $offset;
$bytesUploaded = $offset;
fseek($stream, $bytesUploaded);
// We stop uploading if we have reached the timeout or if we have uploaded the entire file.
while ($bytesRemaining > 0 && ! $this->hasReachedTimeout($timeout)) {
if (time() > $expiration) {
DupLog::infoTrace("OneDrive Upload session expired for {$storageFile}, try uploading again.");
unset($extraData[$sessionKey]);
return false;
}
$chunkSize = min($chunkSize, $bytesRemaining);
if (($chunk = fread($stream, $chunkSize)) === false) {
DupLog::infoTrace("Failed to read file chunk for {$storageFile}, try uploading again.");
return false;
}
try {
$response = $this->request(
$uploadSession['uploadUrl'],
[
'Content-Length' => (string) $chunkSize,
'Content-Range' => sprintf('bytes %d-%d/%d', $bytesUploaded, $bytesUploaded + $chunkSize - 1, $fileSize),
],
$chunk,
Requests::PUT,
false
);
$code = $response->status_code;
if ($code === 416) {
DupLog::infoTrace("Wrong Chunk uploaded, trying to recover...");
if (($nextOffset = $this->getOffsetFromAPI($uploadSession['uploadUrl'])) === false) {
DupLog::infoTrace("Failed to get offset from API, try uploading again.");
return false;
}
$bytesUploaded = $nextOffset;
$bytesRemaining = $fileSize - $nextOffset;
if (@fseek($stream, $bytesUploaded) === -1) {
DupLog::infoTrace("Failed to seek file to offset {$nextOffset}, try uploading again.");
return false;
}
//try to upload the correct chunk
continue;
}
} catch (Exception $e) {
DupLog::infoTrace("Failed to copy file to OneDrive: {$e->getMessage()}");
return false;
}
if ($code > 499 && $code < 600) {
// 5XX means we can resume uploading later, so we don't consider this as an error.
DupLog::infoTrace("OneDrive 5XX error for {$storageFile}, will try later.");
break;
}
$bytesUploaded += $chunkSize;
$bytesRemaining -= $chunkSize;
if (in_array($code, [200, 201])) {
// We have finished uploading the file
unset($extraData[$sessionKey]);
return $length > 0 && $timeout === 0 ? $length : $fileSize;
}
// At this point only 202 is expected, which means we have to continue uploading
if ($code !== 202) {
// 4XX means we cannot resume uploading.
DupLog::infoTrace("OneDrive Upload error for {$storageFile}, try uploading again.");
DupLog::infoTrace("OneDrive responded with code {$code} & sent us: " . $response->body);
return false;
}
$responseData = $response->decode_body();
if (isset($responseData['expirationDateTime'])) {
$extraData[$sessionKey]['expirationDateTime'] = $responseData['expirationDateTime'];
}
if (isset($responseData['nextExpectedRanges'])) {
$extraData[$sessionKey]['nextExpectedRanges'] = $responseData['nextExpectedRanges'];
$nextRange = explode('-', $responseData['nextExpectedRanges'][0]);
$bytesUploaded = (int) $nextRange[0];
}
// timeout set to 0 so just uploading length amount
if ($timeout === 0) {
break;
}
}
// We return the amount of bytes uploaded.
return $bytesUploaded - $offset;
}
/**
* Get the offset from the API
*
* @param string $uploadUrl The URL to the upload session
*
* @return int|false
*/
protected function getOffsetFromAPI($uploadUrl)
{
$statusResponse = $this->request($uploadUrl);
if ($statusResponse->status_code !== 200) {
DupLog::infoTrace("Failed to get upload status.");
return false;
}
if (($statusData = json_decode($statusResponse->body, true)) === null) {
DupLog::infoTrace("Failed to decode upload status.");
return false;
}
if (!isset($statusData['nextExpectedRanges'][0])) {
DupLog::infoTrace("Failed to get next expected range.");
return false;
}
$nextRangeArr = explode('-', $statusData['nextExpectedRanges'][0]);
return (int) $nextRangeArr[0];
}
/**
* Get the app folder object
*
* @see https://github.com/OneDrive/onedrive-api-docs/blob/live/docs/rest-api/api/drive_get_specialfolder.md
*
* @return OneDriveStoragePathInfo|false
*/
protected function getAppFolder()
{
if ($this->appFolder !== null) {
return $this->appFolder;
}
try {
$response = $this->request('/me/drive/special/approot');
} catch (Exception $e) {
DupLog::infoTrace("Failed to get app folder in OneDrive: {$e->getMessage()}");
return false;
}
return $this->appFolder = $this->buildStoragePathInfo($response->decode_body());
}
/**
* Get the details of an item by path
*
* @see https://github.com/OneDrive/onedrive-api-docs/blob/live/docs/rest-api/api/driveitem_get.md#http-request
*
* @param string $path The path to the item
* @param null|string $parent The ID of the parent directory
*
* @return array<string, mixed>|false
*/
protected function getItemDetailsByPath($path, $parent = null)
{
$path = '/' . ltrim($path, '/');
if ($parent === null && empty($this->storageFolderId)) {
$parent = $this->getAppFolder()->id;
$path = '/' . trim($this->storageFolderName . $path, '/');
} elseif ($parent === null) {
$parent = $this->storageFolderId;
}
try {
$path = implode('/', array_map('rawurlencode', explode('/', $path)));
$response = $this->request("/me/drive/items/{$parent}:{$path}");
} catch (Exception $e) {
DupLog::infoTrace("Failed to get item details in OneDrive: {$e->getMessage()}");
return false;
}
return $response->decode_body();
}
/**
* Create a directory in the storage
*
* @see https://github.com/OneDrive/onedrive-api-docs/blob/live/docs/rest-api/api/driveitem_post_children.md
*
* @param string $parent The ID of the parent directory
* @param string $directory The name of the directory to create
*
* @return array<string, mixed>
*/
protected function createDriveDirectory($parent, $directory)
{
try {
$response = $this->request(
'/me/drive/items/' . $parent . '/children',
[],
[
'name' => $directory,
'folder' => new \stdClass(),
'@microsoft.graph.conflictBehavior' => 'fail',
],
Requests::POST
);
DupLog::traceObject("Response", $response);
} catch (Exception $e) {
DupLog::infoTrace("Failed to create directory in OneDrive: {$e->getMessage()}");
return [];
}
return $response->decode_body();
}
/**
* Create a new upload session
*
* @see https://github.com/OneDrive/onedrive-api-docs/blob/live/docs/rest-api/api/driveitem_createuploadsession.md
*
* @param string $targetFile The path to the destination file
*
* @return array{uploadUrl: string, expirationDateTime: string}|false
*/
protected function createUploadSession($targetFile)
{
$parent = $this->storageFolderId;
$file = basename($targetFile);
if ($file !== $targetFile) {
$directory = dirname($targetFile);
$this->createDir($directory);
$targetDir = $this->getPathInfo($directory);
$parent = $targetDir->id;
}
try {
$response = $this->request(
"/me/drive/items/{$parent}:/{$file}:/createUploadSession",
[],
[
'item' => ["name" => $file],
],
Requests::POST
);
} catch (Exception $e) {
// Request failed from curl
DupLog::infoTrace("Failed to create upload session: {$e->getMessage()}, token " . print_r($this->token, true));
return false;
}
if ($response->status_code !== 200) {
// Failed to create the upload session, error exists in the response body
DupLog::infoTrace("Failed to create upload session, response: {$response['body']}, token " . print_r($this->token, true));
return false;
}
return $response->decode_body();
}
/**
* Build a StoragePathInfo object from the given array
*
* @param array<string, mixed> $item The array to build the object from
*
* @return OneDriveStoragePathInfo
*/
protected function buildStoragePathInfo($item)
{
$info = new OneDriveStoragePathInfo();
if (!isset($item['id'])) {
return $info;
}
$info->exists = true;
$info->id = $item['id'];
$info->name = $item['name'];
$info->isDir = isset($item['folder']);
$info->created = isset($item['createdDateTime']) ? strtotime($item['createdDateTime']) : 0;
$info->modified = isset($item['lastModifiedDateTime']) ? strtotime($item['lastModifiedDateTime']) : 0;
$info->size = $item['size'] ?? 0;
$info->webUrl = $item['webUrl'] ?? '';
if (isset($item['file'])) {
$info->file = $item['file'];
}
if (isset($item['createdBy']['user'])) {
$info->user = $item['createdBy']['user'];
}
if (isset($item['parentReference']['path'])) {
// path can be different from the name, e.g. when the file is in a subdirectory
$fullPath = $item['parentReference']['path'] . '/' . $info->name;
$storagePosition = strpos($fullPath, $this->storageFolderName); // calculate the position of the storage folder name
$filePathStartPosition = $storagePosition + strlen($this->storageFolderName) + 1; // file path starts after the storage folder name & the slash
$info->path = substr($fullPath, $filePathStartPosition);
}
return $info;
}
/**
* Generate info on create dir
*
* @param string $path Dir path
*
* @return OneDriveStoragePathInfo
*/
protected function generateCreateDirInfo(string $path): OneDriveStoragePathInfo
{
return $this->getRealPathInfo($path);
}
/**
* Generate info on delete item
*
* @param string $path Item path
*
* @return OneDriveStoragePathInfo
*/
protected function generateDeleteInfo(string $path): OneDriveStoragePathInfo
{
$info = new OneDriveStoragePathInfo();
$info->path = $path;
$info->exists = false;
$info->isDir = false;
$info->size = 0;
$info->created = 0;
$info->modified = 0;
return $info;
}
/**
* 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.
* This data is intended to be per-file and may be reset between files.
* @param array<string,mixed> $generalExtraData Extra data to pass to copy function that persists across files
* during the entire transfer operation.
*
* @return false|int The number of bytes that were written to the file, or false on failure.
*/
public function copyFromStorage(
string $storageFile,
string $destFile,
int $offset = 0,
int $length = -1,
int $timeout = 0,
array &$extraData = [],
array &$generalExtraData = []
) {
$this->startTrackingTime();
if ($offset > 0 && !@file_exists($destFile)) {
return false;
}
if ($length < 0) {
// We can use the download URL to download the file in one go
$content = $this->getFileContent($storageFile);
if ($content === false) {
return false;
}
$content = substr($content, $offset);
if (file_put_contents($destFile, $content) === false) {
return false;
}
return strlen($content);
}
if (! isset($extraData['downloadUrl'])) {
$item = $this->getItemDetailsByPath($storageFile);
if (!isset($item['@microsoft.graph.downloadUrl'])) {
return false;
}
$extraData['downloadUrl'] = $item['@microsoft.graph.downloadUrl'];
if (file_put_contents($destFile, '') === false) {
DupLog::infoTrace("[OnedriveAddon] Failed to open file for writing: {$destFile}");
return false;
}
}
$downloadUrl = $extraData['downloadUrl'];
$range = "bytes={$offset}-" . ($offset + $length - 1);
try {
$response = $this->request($downloadUrl, ['Range' => $range], [], Requests::GET, false);
} catch (Exception $e) {
DupLog::infoTrace("[OnedriveAddon] Failed to download file: {$storageFile}. Error: {$e->getMessage()}");
return false;
}
if ($response->status_code !== 206 && $response->status_code !== 200) {
DupLog::infoTrace("[OnedriveAddon] Failed to download file: {$storageFile}. Response " . $response->body);
return false;
}
file_put_contents($destFile, $response->body, FILE_APPEND);
return $length;
}
/**
* Method to send remote requests. By default add the authorization bearer header to the requests. If the data is an
* array it will be sent in JSON format, as the OneDrive API expects JSON.
*
* @param string $url URL to request
* @param array<mixed> $headers Extra headers to send with the request
* @param array<mixed>|string $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests
* @param string $type HTTP request type (use Requests constants)
* @param bool $auth Include authorization headers
*
* @return \VendorDuplicator\WpOrg\Requests\Response
*/
protected function request($url, array $headers = [], $data = [], string $type = Requests::GET, $auth = true): Response
{
// Set auth bearer header if needed
if ($auth === false) {
unset($headers['Authorization']);
} elseif (!isset($headers['Authorization'])) {
$headers['Authorization'] = 'Bearer ' . $this->token->getAccessToken();
}
//By default send JSON data if data is an array
if (!isset($headers['Content-Type']) && !empty($data) && is_array($data)) {
$headers['Content-Type'] = 'application/json';
$data = json_encode($data);
}
$options = [
'timeout' => 1000,
'connect_timeout' => 1000,
'verify' => $this->sslVerify,
];
// Set custom SSL cert
if ($this->sslVerify && $this->sslCert !== '') {
$options['verify'] = $this->sslCert;
}
// Assume relative URL, if it doesn't start with http(s)://
if (!preg_match('/^http(s)?:\/\//i', $url)) {
$url = $this->baseUrl . '/' . ltrim($url, '/');
}
return Requests::request($url, $headers, $data, $type, $options);
}
}

View File

@@ -0,0 +1,84 @@
<?php
/**
* Duplicator messages sections
*
* @package Duplicator
* @copyright (c) 2022, Snap Creek LLC
*/
use Duplicator\Addons\OneDriveAddon\Models\OneDriveStorage;
defined("ABSPATH") or die("");
/**
* Variables
*
* @var \Duplicator\Core\Controllers\ControllersManager $ctrlMng
* @var \Duplicator\Core\Views\TplMng $tplMng
* @var array<string, mixed> $tplData
*/
?>
<div class="dup-accordion-wrapper display-separators close">
<div class="accordion-header">
<h3 class="title"><?php echo esc_html(OneDriveStorage::getStypeName()); ?></h3>
</div>
<div class="accordion-content">
<label class="lbl-larger">
<?php esc_html_e("Upload Chunk Size", 'duplicator-pro'); ?>
</label>
<div class="margin-bottom-1">
<input
class="text-right inline-display width-small margin-bottom-0"
name="onedrive_upload_chunksize_in_kb"
id="onedrive_upload_chunksize_in_kb"
type="number"
min="<?php echo intval(OneDriveStorage::UPLOAD_CHUNK_MIN_SIZE_IN_KB); ?>"
step="320"
data-parsley-required
data-parsley-type="number"
data-parsley-errors-container="#onedrive_upload_chunksize_in_kb_error_container"
value="<?php echo (int) $tplData['uploadChunkSize']; ?>">&nbsp;<b>KB</b>
<div id="onedrive_upload_chunksize_in_kb_error_container" class="duplicator-error-container"></div>
<p class="description">
<?php
printf(
esc_html__(
'How much should be uploaded to OneDrive per attempt. It should be multiple of %1$dkb.
Higher=faster but less reliable. Default size %2$dkb. Min size %3$dkb.',
'duplicator-pro'
),
(int) OneDriveStorage::UPLOAD_CHUNK_MIN_SIZE_IN_KB,
(int) OneDriveStorage::UPLOAD_CHUNK_DEFAULT_SIZE_IN_KB,
(int) OneDriveStorage::UPLOAD_CHUNK_MIN_SIZE_IN_KB
);
?>
</p>
</div>
<label class="lbl-larger">
<?php esc_html_e("Download Chunk Size", 'duplicator-pro'); ?>
</label>
<div class="margin-bottom-1">
<input
class="text-right inline-display width-small margin-bottom-0"
name="onedrive_download_chunksize_in_kb"
id="onedrive_download_chunksize_in_kb"
type="number"
min="<?php echo (int) OneDriveStorage::MIN_DOWNLOAD_CHUNK_SIZE_IN_KB; ?>"
data-parsley-required
data-parsley-type="number"
data-parsley-errors-container="#onedrive_download_chunksize_in_kb_error_container"
value="<?php echo (int) $tplData['downloadChunkSize']; ?>">&nbsp;<b>KB</b>
<div id="onedrive_download_chunksize_in_kb_error_container" class="duplicator-error-container"></div>
<p class="description">
<?php esc_html_e('How much should be downloaded from OneDrive per attempt.', 'duplicator-pro');
printf(
esc_html__('Default size %1$dkb. Min size %2$dkb.', 'duplicator-pro'),
(int) OneDriveStorage::DEFAULT_DOWNLOAD_CHUNK_SIZE_IN_KB,
(int) OneDriveStorage::MIN_DOWNLOAD_CHUNK_SIZE_IN_KB
); ?>
</p>
</div>
</div>
</div>

View File

@@ -0,0 +1,300 @@
<?php
/**
* Duplicator messages sections
*
* @package Duplicator
* @copyright (c) 2022, Snap Creek LLC
*/
use Duplicator\Addons\OneDriveAddon\Models\OneDriveStorage;
use Duplicator\Views\UI\UiDialog;
defined("ABSPATH") or die("");
/**
* Variables
*
* @var \Duplicator\Core\Controllers\ControllersManager $ctrlMng
* @var \Duplicator\Core\Views\TplMng $tplMng
* @var array<string, mixed> $tplData
* @var OneDriveStorage $storage
*/
$storage = $tplData["storage"];
/** @var string */
$storageFolder = $tplData["storageFolder"];
/** @var int */
$maxPackages = $tplData["maxPackages"];
/** @var bool */
$allFolderPers = $tplData["allFolderPers"];
/** @var false|object */
$accountInfo = $tplData["accountInfo"];
/** @var false|object */
$hasError = $tplData["hasError"];
/** @var string */
$externalRevokeUrl = $tplData["externalRevokeUrl"];
$tplMng->render('admin_pages/storages/parts/provider_head');
?>
<tr>
<th scope="row"><label><?php esc_html_e("Authorization", 'duplicator-pro'); ?></label></th>
<td class="onedrive-authorize">
<?php if (!$storage->isAuthorized()) : ?>
<div class='onedrive-msgraph-authorization-state' id="onedrive-msgraph-state-unauthorized">
<div id="dup-all-onedrive-allperms-wrapper">
<?php esc_html_e('Are you using a business account?', 'duplicator-pro'); ?>
<label class="switch">
<input
id="onedrive_msgraph_all_folders_read_write_perm"
name="onedrive_msgraph_all_folders_read_write_perm"
type="checkbox"
value="1"
<?php checked($allFolderPers); ?>>
<span class="slider round"></span>
</label>
<div class="auth-code-popup-note" style="margin-top:1px; margin-left: 0;">
<?php
echo esc_html__('By default, we only request read/write permission for the "App Folder"', 'duplicator-pro') . ' ' .
esc_html__('If you are planning to use a business account, we need read/write permission for all folders.', 'duplicator-pro'); ?>
</div>
</div>
<!-- CONNECT -->
<button
id="dupli-onedrive-msgraph-connect-btn"
type="button"
class="button secondary hollow margin-bottom-0"
onclick="DupliJs.Storage.OneDrive.GetAuthUrl(); return false;">
<i class="fa fa-plug"></i> <?php esc_html_e('Connect to OneDrive', 'duplicator-pro'); ?>
<img
src="<?php echo esc_url(DUPLICATOR_IMG_URL . '/onedrive.svg'); ?>"
style='vertical-align: middle; margin:-2px 0 0 3px; height:18px; width:18px'>
</button>
<div class='onedrive-msgraph-auth-container' style="display: none;">
<!-- STEP 1 -->
<div class="storage-auth-step">
<p>
<b><?php esc_html_e("Step 1:", 'duplicator-pro'); ?></b>&nbsp;
<?php esc_html_e(' Duplicator needs to authorize at OneDrive.', 'duplicator-pro'); ?>
</p>
<div class="auth-code-popup-note">
<?php esc_html_e(
'Note: Clicking the button below will open a new tab/window. Please be sure your browser does not block popups.',
'duplicator-pro'
); ?>
<?php esc_html_e(
'If a new tab/window does not open check your browsers address bar to allow popups from this URL.',
'duplicator-pro'
); ?>
</div>
<button
id="dupli-onedrive-msgraph-auth-btn"
type="button"
class="button secondary hollow margin-bottom-0"
data-auth-url="<?php echo esc_attr($storage->getAuthorizationUrl()); ?>">
<i class="fa fa-user"></i> <?php esc_html_e('Authorize OneDrive', 'duplicator-pro'); ?>
</button>
</div>
<!-- STEP 2 -->
<div class="storage-auth-step">
<p>
<b><?php esc_html_e('Step 2:', 'duplicator-pro'); ?></b>
<?php esc_html_e("Paste code from OneDrive authorization page.", 'duplicator-pro'); ?> <br />
</p>
<input style="width:400px" id="onedrive-msgraph-auth-code" name="onedrive-msgraph-auth-code" type="text" />
</div>
<!-- STEP 3 -->
<div class="storage-auth-step">
<p>
<b><?php esc_html_e("Step 3:", 'duplicator-pro'); ?></b>&nbsp;
<?php esc_html_e('Finalize OneDrive validation by clicking the "Finalize Setup" button.', 'duplicator-pro'); ?>
</p>
<button
type="button"
id="onedrive-msgraph-finalize-setup"
class="button secondary margin-bottom-0">
<i class="fa fa-check-square"></i> <?php esc_html_e('Finalize Setup', 'duplicator-pro'); ?>
</button>
</div>
</div>
</div>
<?php endif; ?>
<div class="onedrive-msgraph-authorization-state" id="onedrive-msgraph-state-authorized">
<?php if ($storage->isAuthorized()) : ?>
<h3>
<?php esc_html_e('OneDrive Account', 'duplicator-pro'); ?><br />
<i class="dupli-edit-info">
<?php esc_html_e('Duplicator has been authorized to access this user\'s OneDrive account', 'duplicator-pro'); ?>
</i>
</h3>
<?php
if ($accountInfo !== false) {
?>
<div id="onedrive-account-info">
<label><?php esc_html_e('Name', 'duplicator-pro'); ?>:</label>
<?php echo esc_html($accountInfo->displayName); ?> <br />
</div>
</div>
<?php
} elseif ($hasError) {
?>
<div class="error-txt">
<?php
echo '<strong>';
esc_html_e('Please click on the "Cancel Authorization" button and reauthorize the OneDrive storage', 'duplicator-pro');
echo '</strong>';
?>
</div>
<?php
}
?>
<br />
<button type="button" class="button secondary hollow small" onclick='DupliJs.Storage.OneDrive.CancelAuthorization();'>
<?php esc_html_e('Cancel Authorization', 'duplicator-pro'); ?>
</button><br />
<i class="dupli-edit-info">
<?php
esc_html_e(
'Disassociates storage provider with the OneDrive account. Will require re-authorization.',
'duplicator-pro'
);
?>
</i>
<?php endif; ?>
</div>
</td>
</tr>
<tr>
<th scope="row"><label for="_onedrive_msgraph_storage_folder"><?php esc_html_e("Storage Folder", 'duplicator-pro'); ?></label></th>
<td>
<div class="horizontal-input-row">
<b>//OneDrive/Apps/Duplicator Pro/</b>
<?php $parselyMessage = __(
'The folder path shouldn\'t include the special characters " * : < > ? / \ | or shouldn\'t end with a dot(".").',
'duplicator-pro'
); ?>
<input
id="_onedrive_msgraph_storage_folder"
name="_onedrive_msgraph_storage_folder"
type="text"
value="<?php echo esc_attr($storageFolder); ?>"
class="dupli-storeage-folder-path" data-parsley-pattern='^((?![\"*\:<>?\\|]).)*[^\.\:]$'
data-parsley-errors-container="#onedrive_msgraph_storage_folder_error_container"
data-parsley-pattern-message="<?php echo esc_attr($parselyMessage); ?>">
</div>
<p>
<i>
<?php
esc_html_e(
"Folder where backups will be stored. This should be unique for each web-site using Duplicator.",
'duplicator-pro'
); ?>
</i>
</p>
<div id="onedrive_msgraph_storage_folder_error_container" class="duplicator-error-container"></div>
</td>
</tr>
<tr>
<th scope="row"><label for=""><?php esc_html_e("Max Backups", 'duplicator-pro'); ?></label></th>
<td>
<div class="horizontal-input-row">
<input
data-parsley-errors-container="#onedrive_msgraph_max_files_error_container"
id="onedrive_msgraph_max_files"
name="onedrive_msgraph_max_files"
type="text"
value="<?php echo absint($maxPackages); ?>" maxlength="4">
<label for="onedrive_msgraph_max_files">
<?php esc_html_e("Number of Backups to keep in folder.", 'duplicator-pro'); ?>
</label>
</div>
<?php $tplMng->render('admin_pages/storages/parts/max_backups_description'); ?>
<div id="onedrive_msgraph_max_files_error_container" class="duplicator-error-container"></div>
</td>
</tr>
<?php $tplMng->render('admin_pages/storages/parts/provider_foot');
// Alerts for OneDrive
$alertConnStatus = new UiDialog();
$alertConnStatus->title = __('OneDrive Connection Status', 'duplicator-pro');
$alertConnStatus->message = ''; // javascript inserted message
$alertConnStatus->initAlert();
?>
<script>
jQuery(document).ready(function($) {
let storageId = <?php echo (int) $storage->getId(); ?>;
let additionalScopes = [];
DupliJs.Storage.OneDrive.GetAuthUrl = function() {
$("#dupli-onedrive-msgraph-connect-btn").hide();
$(".onedrive-msgraph-auth-container").show();
};
$('#dupli-onedrive-msgraph-auth-btn').click(function() {
let authUrl = $(this).data('auth-url');
if (additionalScopes.length > 0) {
authUrl += '?' + $.param({
additionalScopes
});
}
window.open(authUrl, '_blank');
});
$('#onedrive_msgraph_all_folders_read_write_perm').change(function() {
let allFolderPermission = $(this).is(':checked');
if (allFolderPermission) {
additionalScopes = ['Files.ReadWrite'];
} else {
additionalScopes = [];
}
});
DupliJs.Storage.OneDrive.CancelAuthorization = function() {
window.open(<?php echo json_encode($externalRevokeUrl); ?>, '_blank');
DupliJs.Storage.RevokeAuth(storageId);
}
DupliJs.Storage.OneDrive.FinalizeSetup = function() {
if ($('#onedrive-msgraph-auth-code').val().length > 5) {
$("#dup-storage-form").submit();
} else {
<?php $alertConnStatus->showAlert(); ?>
let alertMsg = "<i class='fas fa-exclamation-triangle'></i> " +
"<?php esc_html_e('Please enter your OneDrive authorization code!', 'duplicator-pro'); ?>";
<?php $alertConnStatus->updateMessage("alertMsg"); ?>
}
}
$('#onedrive-msgraph-finalize-setup').click(function(event) {
event.stopPropagation();
if ($('#onedrive-msgraph-auth-code').val().length > 5) {
DupliJs.Storage.PrepareForSubmit();
//$("#dup-storage-form").submit();
DupliJs.Storage.Authorize(
<?php echo (int) $storage->getId(); ?>,
<?php echo (int) $storage->getSType(); ?>, {
'name': $('#name').val(),
'notes': $('#notes').val(),
'storage_folder': $('#_onedrive_msgraph_storage_folder').val(),
'max_packages': $('#onedrive_msgraph_max_files').val(),
'auth_code': $('#onedrive-msgraph-auth-code').val()
}
);
} else {
<?php $alertConnStatus->showAlert(); ?>
let alertMsg = "<i class='fas fa-exclamation-triangle'></i> " +
"<?php esc_html_e('Please enter your OneDrive authorization code!', 'duplicator-pro'); ?>";
<?php $alertConnStatus->updateMessage("alertMsg"); ?>
}
return false;
});
});
</script>