first commit
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Google Drive ADDON
|
||||
*
|
||||
* Name: Google Drive ADDON
|
||||
* Version: 1
|
||||
* Author: Duplicator
|
||||
* Author URI: https://duplicator.com/
|
||||
*
|
||||
* PHP version 5.6
|
||||
*
|
||||
* @category Duplicator
|
||||
* @package Plugin
|
||||
* @author Duplicator
|
||||
* @copyright 2011-2021 Snapcreek LLC
|
||||
* @license https://www.gnu.org/licenses/gpl-3.0.html GPLv3
|
||||
* @version GIT: $Id$
|
||||
* @link https://duplicator.com/
|
||||
*/
|
||||
|
||||
namespace Duplicator\Addons\GDriveAddon;
|
||||
|
||||
use Duplicator\Addons\GDriveAddon\Models\GDriveStorage;
|
||||
use Duplicator\Addons\GDriveAddon\Utils\Autoloader;
|
||||
use Duplicator\Core\Addons\AbstractAddonCore;
|
||||
use Duplicator\Models\Storages\AbstractStorageEntity;
|
||||
|
||||
class GDriveAddon 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 void
|
||||
*/
|
||||
public function registerStorages()
|
||||
{
|
||||
GDriveStorage::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, 'gdriveaddon/') === 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_gdrive_count'] = 0;
|
||||
|
||||
foreach ($storages as $index => $storage) {
|
||||
if ($storage->getSType() === GDriveStorage::getSType()) {
|
||||
$storageNums['storages_gdrive_count']++;
|
||||
}
|
||||
}
|
||||
|
||||
return $storageNums;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getAddonPath()
|
||||
{
|
||||
return __DIR__;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getAddonFile()
|
||||
{
|
||||
return __FILE__;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,903 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2022, Snap Creek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Addons\GDriveAddon\Models;
|
||||
|
||||
use Duplicator\Addons\GDriveAddon\Utils\GoogleClient;
|
||||
use Duplicator\Models\Storages\AbstractStorageAdapter;
|
||||
use Duplicator\Utils\OAuth\TokenEntity;
|
||||
use Exception;
|
||||
use VendorDuplicator\Psr\Http\Message\RequestInterface;
|
||||
use VendorDuplicator\Google\Client;
|
||||
use VendorDuplicator\Google\Http\MediaFileUpload;
|
||||
use VendorDuplicator\Google\Service\Drive;
|
||||
use VendorDuplicator\GuzzleHttp\Psr7\Request;
|
||||
use VendorDuplicator\GuzzleHttp\Psr7\Response;
|
||||
|
||||
/**
|
||||
* @method GDriveStoragePathInfo getPathInfo(string $path)
|
||||
*/
|
||||
class GDriveAdapter extends AbstractStorageAdapter
|
||||
{
|
||||
const FOLDER_MIME_TYPE = 'application/vnd.google-apps.folder';
|
||||
const CHUNK_SIZE_STEP = 256 * KB_IN_BYTES;
|
||||
|
||||
/** @var Drive The Google Drive service */
|
||||
protected $drive = null;
|
||||
/** @var string The root storage path */
|
||||
protected $storagePath = '';
|
||||
/** @var string The root storage path id */
|
||||
protected $storagePathId = '';
|
||||
/** @var TokenEntity The OAuth token entity */
|
||||
protected $token = null;
|
||||
/** @var int */
|
||||
protected $startTime = 0;
|
||||
/** @var bool */
|
||||
protected $sslVerify = true;
|
||||
/** @var string If empty use server cert else use custom cert path */
|
||||
protected $sslCert = '';
|
||||
/** @var bool */
|
||||
protected $ipv4Only = false;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param TokenEntity $token The OAuth token entity.
|
||||
* @param string $storagePath The root storage path.
|
||||
* @param string $storagePathId The root storage path id.
|
||||
* @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(
|
||||
TokenEntity $token,
|
||||
$storagePath,
|
||||
$storagePathId = '',
|
||||
$sslVerify = true,
|
||||
$sslCert = '',
|
||||
$ipv4Only = false
|
||||
) {
|
||||
$this->token = $token;
|
||||
$this->storagePath = $storagePath;
|
||||
$this->storagePathId = $storagePathId;
|
||||
$this->sslVerify = $sslVerify;
|
||||
$this->sslCert = $sslCert;
|
||||
$this->ipv4Only = $ipv4Only;
|
||||
|
||||
if ($token->isAboutToExpire()) {
|
||||
$token->refresh(true);
|
||||
}
|
||||
|
||||
$httpOptions = [];
|
||||
if ($this->sslVerify === false) {
|
||||
$verify = false;
|
||||
} elseif (strlen($this->sslCert) === 0) {
|
||||
$verify = true;
|
||||
} else {
|
||||
$verify = $this->sslCert;
|
||||
}
|
||||
$httpOptions['verify'] = $verify;
|
||||
if ($this->ipv4Only) {
|
||||
$httpOptions['force_ip_resolve'] = 'v4';
|
||||
}
|
||||
$client = new GoogleClient();
|
||||
$client->setHttpClientOptions($httpOptions);
|
||||
|
||||
$client->setAccessToken([
|
||||
'created' => $token->getCreated(),
|
||||
'access_token' => $token->getAccessToken(),
|
||||
'refresh_token' => $token->getRefreshToken(),
|
||||
'expires_in' => $token->getExpiresIn(),
|
||||
'scope' => $token->getScope(),
|
||||
]);
|
||||
|
||||
$this->drive = new Drive($client);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Google Drive service.
|
||||
*
|
||||
* @return Drive
|
||||
*/
|
||||
public function getService()
|
||||
{
|
||||
return $this->drive;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->token->isValid()) {
|
||||
$errorMsg = __('Invalid token supplied for google drive', 'duplicator-pro');
|
||||
return false;
|
||||
}
|
||||
if (! $this->exists('/') && ! $this->createDir('/')) {
|
||||
$errorMsg = __('Unable to create root directory on google drive', 'duplicator-pro');
|
||||
return false;
|
||||
}
|
||||
if (empty($this->storagePathId)) {
|
||||
$storage = $this->getPathInfo('/');
|
||||
if ($storage->exists) {
|
||||
$this->storagePathId = $storage->id;
|
||||
} else {
|
||||
$errorMsg = __('Unable to fetch root directory info from Google Drive', 'duplicator-pro');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the storage on deletion.
|
||||
*
|
||||
* @return bool true on success or false on failure.
|
||||
*/
|
||||
public function destroy()
|
||||
{
|
||||
$this->storagePathId = '';
|
||||
return $this->delete('/', 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 = '')
|
||||
{
|
||||
if (! $this->token->isValid()) {
|
||||
$errorMsg = 'Invalid token supplied for google drive';
|
||||
return false;
|
||||
}
|
||||
|
||||
$root = $this->getPathInfo('/');
|
||||
if (! $root || !$root->exists) {
|
||||
$errorMsg = 'Root directory does not exist on google drive, false for isValid';
|
||||
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 = trim($path, '/');
|
||||
|
||||
if (empty($this->storagePathId)) {
|
||||
// if we don't have the storage path id set, we fetch it
|
||||
$storageFolder = $this->getPathInfo('/');
|
||||
if ($storageFolder->exists) {
|
||||
$this->storagePathId = $storageFolder->id;
|
||||
} else {
|
||||
$path = $this->storagePath . '/' . $path;
|
||||
}
|
||||
}
|
||||
|
||||
$parts = array_filter(explode('/', $path));
|
||||
$parent = $this->storagePathId;
|
||||
|
||||
// At this point, if we don't have a parent, we need to create from the root path.
|
||||
|
||||
// We assume that a partial path may exist
|
||||
// So we try to search for the path and create it if it doesn't exist
|
||||
// But once we create one directory, we assume that the rest of the path doesn't exist
|
||||
// This saves us a lot of calls to the Google Drive API
|
||||
$pathMayExist = true;
|
||||
|
||||
foreach ($parts as $part) {
|
||||
$query = "name = '{$part}' and trashed = false and mimeType = '" . self::FOLDER_MIME_TYPE . "'";
|
||||
if ($parent) {
|
||||
$query .= " and '{$parent}' in parents";
|
||||
}
|
||||
|
||||
if ($pathMayExist) {
|
||||
// At first, we try to find the directory
|
||||
$response = $this->drive->files->listFiles([
|
||||
'q' => $query,
|
||||
'fields' => 'files(id)',
|
||||
]);
|
||||
if ($response->count() > 0) {
|
||||
$file = $response->getFiles()[0];
|
||||
$parent = $file->getId();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$pathMayExist = false;
|
||||
// If we didn't find the directory, we create it
|
||||
$file = new Drive\DriveFile([
|
||||
'name' => $part,
|
||||
'mimeType' => self::FOLDER_MIME_TYPE,
|
||||
]);
|
||||
|
||||
if (! empty($parent)) {
|
||||
$file->setParents([$parent]);
|
||||
}
|
||||
|
||||
try {
|
||||
$file = $this->drive->files->create($file, ['fields' => 'id']);
|
||||
} catch (\Exception $e) {
|
||||
\DUP_PRO_Log::traceObject('[GDriveAdapter] Unable to create directory: ' . $e->getMessage(), $e);
|
||||
return false;
|
||||
}
|
||||
$parent = $file->getId();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
$response = $this->createNewFile($path, [
|
||||
'data' => $content,
|
||||
'uploadType' => 'multipart',
|
||||
'fields' => 'id,size',
|
||||
]);
|
||||
|
||||
if (!$response) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (int) $response->getSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
$info = $this->getPathInfo($path);
|
||||
|
||||
if (! $info->exists) {
|
||||
return true; // if the path doesn't exist, we can consider it deleted
|
||||
}
|
||||
|
||||
if ($info->isDir && ! $recursive && ! $this->isDirEmpty($path)) {
|
||||
return false; // if it's a directory and, we are not deleting recursively, we can't delete it
|
||||
}
|
||||
|
||||
try {
|
||||
$this->drive->files->delete($info->id);
|
||||
} catch (\Exception $e) {
|
||||
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)
|
||||
{
|
||||
$info = $this->getPathInfo($path);
|
||||
|
||||
if (! $info->exists) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
/** @var Response $response */
|
||||
$response = $this->drive->files->get($info->id, [
|
||||
'alt' => 'media',
|
||||
'acknowledgeAbuse' => true,
|
||||
]);
|
||||
|
||||
return $response->getBody()->getContents();
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
$fileInfo = $this->getPathInfo($oldPath);
|
||||
$oldDirInfo = $this->getPathInfo(dirname($oldPath));
|
||||
$newDirInfo = $this->getPathInfo(dirname($newPath));
|
||||
$file = $fileInfo->file;
|
||||
|
||||
try {
|
||||
$this->drive->files->update($fileInfo->id, $file, [
|
||||
'addParents' => $newDirInfo->id,
|
||||
'removeParents' => $oldDirInfo->id,
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 GDriveStoragePathInfo|false The path info or false on error.
|
||||
*/
|
||||
protected function getRealPathInfo($path)
|
||||
{
|
||||
try {
|
||||
$info = $this->nestedPathInfo($path);
|
||||
} catch (\Exception $e) {
|
||||
$info = false;
|
||||
}
|
||||
|
||||
return $this->buildPathInfo($info);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
$info = $this->getPathInfo($path);
|
||||
|
||||
if (! $info->exists) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$query = "'{$info->id}' in parents and trashed = false";
|
||||
|
||||
if (! $files) {
|
||||
$query .= " and mimeType = '" . self::FOLDER_MIME_TYPE . "'";
|
||||
}
|
||||
if (! $folders) {
|
||||
$query .= " and mimeType != '" . self::FOLDER_MIME_TYPE . "'";
|
||||
}
|
||||
|
||||
$nextPageToken = null;
|
||||
$result = [];
|
||||
do {
|
||||
$response = $this->drive->files->listFiles([
|
||||
'q' => $query,
|
||||
'pageToken' => $nextPageToken,
|
||||
]);
|
||||
|
||||
$result = array_merge($result, array_map(function ($file) {
|
||||
$info = $this->buildPathInfo($file);
|
||||
return $info->path;
|
||||
}, $response->getFiles()));
|
||||
} while ($nextPageToken = $response->getNextPageToken());
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = [])
|
||||
{
|
||||
$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 = [])
|
||||
{
|
||||
$this->startTrackingTime();
|
||||
|
||||
$chunkSize = max(self::CHUNK_SIZE_STEP, floor($length / self::CHUNK_SIZE_STEP) * self::CHUNK_SIZE_STEP);
|
||||
|
||||
$source = fopen($sourceFile, 'rb');
|
||||
if (! $source) {
|
||||
\DUP_PRO_Log::info(sprintf('[GDriveAdapter] Unable to open source file %s', $sourceFile));
|
||||
return false;
|
||||
}
|
||||
|
||||
fseek($source, $offset);
|
||||
|
||||
$storageFile = '/' . trim($storageFile, '/');
|
||||
$targetPath = dirname($storageFile);
|
||||
|
||||
if ($targetPath === '/' && ! empty($this->storagePathId)) {
|
||||
$target = new Drive\DriveFile();
|
||||
$target->setId($this->storagePathId);
|
||||
} else {
|
||||
$target = $this->getPathInfo($targetPath);
|
||||
if (! $target->exists) {
|
||||
$this->createDir($targetPath);
|
||||
$target = $this->getPathInfo($targetPath);
|
||||
}
|
||||
}
|
||||
|
||||
if (! $target) {
|
||||
\DUP_PRO_Log::info(sprintf('[GDriveAdapter] Unable to get target path info for %s', $targetPath));
|
||||
return false;
|
||||
}
|
||||
|
||||
$client = $this->drive->getClient();
|
||||
$client->setDefer(true);
|
||||
$originalHttpClient = $client->getHttpClient();
|
||||
|
||||
$file = new Drive\DriveFile([
|
||||
'name' => basename($storageFile),
|
||||
'parents' => [$target->id],
|
||||
]);
|
||||
|
||||
// The file create call returns the request object as we have set client defer to true
|
||||
/** @var Request $request */
|
||||
$request = $this->drive->files->create($file);
|
||||
|
||||
$media = new MediaFileUpload(
|
||||
$client,
|
||||
$request,
|
||||
'application/octet-stream',
|
||||
'',
|
||||
true,
|
||||
$chunkSize
|
||||
);
|
||||
|
||||
$firstChunk = true;
|
||||
|
||||
$media->setFileSize($filesize = filesize($sourceFile));
|
||||
|
||||
if (! empty($extraData['resume_uri'])) {
|
||||
$resumeUri = $extraData['resume_uri'];
|
||||
try {
|
||||
$this->forceSet($media, 'progress', $offset);
|
||||
$this->forceSet($media, 'resumeUri', $resumeUri);
|
||||
} catch (\Exception $e) {
|
||||
$client->setHttpClient($originalHttpClient);
|
||||
\DUP_PRO_Log::info('[GDriveAdapter] Unable to set resume uri: ' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
$firstChunk = false;
|
||||
\DUP_PRO_Log::trace(sprintf('[GDriveAdapter] Resuming upload for %s from offset %s timeout %d', $sourceFile, $offset, $timeout));
|
||||
}
|
||||
|
||||
do {
|
||||
if ($timeout > 0) {
|
||||
// Set the timeout for the client (in seconds)
|
||||
$client->setHttpClient(new \VendorDuplicator\GuzzleHttp\Client([
|
||||
'base_uri' => $originalHttpClient->getConfig('base_uri'),
|
||||
'http_errors' => false,
|
||||
'timeout' => $timeout / SECONDS_IN_MICROSECONDS, // convert microseconds to seconds
|
||||
]));
|
||||
}
|
||||
$chunk = fread($source, $chunkSize);
|
||||
if (! $chunk) {
|
||||
\DUP_PRO_Log::trace(sprintf('[GDriveAdapter] Unable to read chunk from %s', $sourceFile));
|
||||
$status = true; // we can't set it to false, because drive sdk returns false when chunk upload is successful.
|
||||
break;
|
||||
}
|
||||
$status = $media->nextChunk($chunk);
|
||||
if ($firstChunk) {
|
||||
\DUP_PRO_Log::trace(sprintf('[GDriveAdapter] Created resume uri for %s and it\'s %s', $sourceFile, $media->getResumeUri()));
|
||||
$extraData['resume_uri'] = $media->getResumeUri(); // we need to cache the resume uri for the next chunk
|
||||
$firstChunk = false;
|
||||
}
|
||||
$message = '[GDriveAdapter] Uploaded %d/%d bytes, requested [%d, %d] of %s';
|
||||
\DUP_PRO_Log::trace(sprintf($message, $media->getProgress(), $filesize, $offset, $length, $sourceFile));
|
||||
if ($length > 0) {
|
||||
// if we have a length, we need to stop when we reach it
|
||||
break;
|
||||
}
|
||||
} while (!feof($source) && ! $status && ! $this->hasReachedTimeout($timeout));
|
||||
|
||||
if (feof($source)) {
|
||||
// if we reached the end of the file, we can delete the cached resume uri
|
||||
unset($extraData['resume_uri']);
|
||||
\DUP_PRO_Log::trace(sprintf('[GDriveAdapter] File %s copied successfully to %s', $sourceFile, $storageFile));
|
||||
}
|
||||
|
||||
$client->setDefer(false);
|
||||
$client->setHttpClient($originalHttpClient);
|
||||
|
||||
// If we have false as status, it means the upload is not finished yet
|
||||
// On the final chunk upload, we get the file info
|
||||
if ($status === false || ($status instanceof Drive\DriveFile)) {
|
||||
return $length > 0 ? $length : $filesize;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate info on create dir, this method is exendable by child classes if StoragePathInfo is extended.
|
||||
*
|
||||
* @param string $path Dir path
|
||||
*
|
||||
* @return GDriveStoragePathInfo
|
||||
*/
|
||||
protected function generateCreateDirInfo($path)
|
||||
{
|
||||
return $this->getRealPathInfo($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start tracking the time for the current operation
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function startTrackingTime()
|
||||
{
|
||||
$this->startTime = (int) (microtime(true) * 1000000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the elapsed time since the start of the current operation
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function getElapsedTime()
|
||||
{
|
||||
return (int) (microtime(true) * 1000000) - $this->startTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the operation has reached the timeout
|
||||
*
|
||||
* @param int $timeout The timeout in microseconds
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasReachedTimeout($timeout)
|
||||
{
|
||||
return $timeout > 0 && $this->getElapsedTime() >= ($timeout - 1000000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate info on delete item, this methos is exendable by child classes if StoragePathInfo is extended.
|
||||
*
|
||||
* @param string $path Item path
|
||||
*
|
||||
* @return GDriveStoragePathInfo
|
||||
*/
|
||||
protected function generateDeleteInfo($path)
|
||||
{
|
||||
$info = new GDriveStoragePathInfo();
|
||||
$info->path = $path;
|
||||
$info->exists = false;
|
||||
$info->isDir = false;
|
||||
$info->size = 0;
|
||||
$info->created = 0;
|
||||
$info->modified = 0;
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new file in the specified path.
|
||||
*
|
||||
* @param string $path The path to create the file in
|
||||
* @param array<string, string> $options The options to create the file with
|
||||
*
|
||||
* @return false|Drive\DriveFile
|
||||
*/
|
||||
protected function createNewFile($path, $options = [])
|
||||
{
|
||||
$path = '/' . trim($path, '/');
|
||||
|
||||
$parent = $this->getPathInfo(dirname($path));
|
||||
|
||||
if (! $parent->exists) {
|
||||
if ($this->createDir(dirname($path))) {
|
||||
$parent = $this->getPathInfo(dirname($path));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$file = new Drive\DriveFile([
|
||||
'name' => basename($path),
|
||||
'parents' => [$parent->id],
|
||||
]);
|
||||
|
||||
try {
|
||||
return $this->drive->files->create($file, $options);
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverse the path folder by folder and fetch the file info.
|
||||
*
|
||||
* @param string $path The path get information for
|
||||
* @param ?string $parent The parent folder id
|
||||
*
|
||||
* @return false|Drive\DriveFile
|
||||
*/
|
||||
protected function nestedPathInfo($path, $parent = null)
|
||||
{
|
||||
$path = trim($path, '/');
|
||||
$traversed = explode('/', $this->storagePath); // keep track of the traversed path, by default the root folder is traversed
|
||||
|
||||
if (! $parent) {
|
||||
$parent = $this->storagePathId;
|
||||
}
|
||||
if (! $parent) {
|
||||
// if we don't have a parent, we need to traverse from the root folder
|
||||
$path = $this->storagePath . '/' . $path;
|
||||
$traversed = []; // we are traversing from the root folder, so we reset the traversed path
|
||||
$info = false;
|
||||
} else {
|
||||
$info = $this->drive->files->get($parent, ['fields' => 'id,name,mimeType,size,createdTime,modifiedTime,md5Checksum,webViewLink']);
|
||||
}
|
||||
|
||||
$parts = array_filter(explode('/', $path));
|
||||
|
||||
foreach ($parts as $index => $part) {
|
||||
$query = "name = '{$part}' and trashed = false";
|
||||
if ($parent) {
|
||||
$query .= " and '{$parent}' in parents";
|
||||
}
|
||||
if ($index < count($parts) - 1) {
|
||||
// if we are not in the last iteration, it's most definitely a folder
|
||||
$query .= " and mimeType = '" . self::FOLDER_MIME_TYPE . "'";
|
||||
}
|
||||
|
||||
$result = $this->drive->files->listFiles([
|
||||
'q' => $query,
|
||||
'fields' => 'files(id,name,mimeType,size,createdTime,modifiedTime,md5Checksum,webViewLink)',
|
||||
]);
|
||||
$traversed[] = $part;
|
||||
|
||||
if ($result->count() === 0) {
|
||||
// if we didn't find anything, we can stop here
|
||||
return false;
|
||||
}
|
||||
foreach ($result->getFiles() as $file) {
|
||||
if ($file->getName() !== $part) {
|
||||
continue; // we are looking for a file/folder with the same name
|
||||
}
|
||||
if ($index < (count($parts) - 1) && $file->getMimeType() !== self::FOLDER_MIME_TYPE) {
|
||||
// if we are not in the last iteration, we are most definitely looking for a folder, so we skip if it's not
|
||||
continue;
|
||||
}
|
||||
// At this point we have found the file or folder we were looking for
|
||||
$props = $file->getProperties();
|
||||
$props['path'] = implode('/', $traversed);
|
||||
$file->setProperties($props); // add the path to the file properties
|
||||
$parent = $file->getId();
|
||||
$info = $file;
|
||||
|
||||
// we need to keep looking for the next part
|
||||
continue 2;
|
||||
}
|
||||
// if we got here, we didn't find the file or folder we were looking for
|
||||
$info = false;
|
||||
break;
|
||||
}
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the path info object from Google Drive's file info.
|
||||
*
|
||||
* @param Drive\DriveFile|false $file The file info
|
||||
*
|
||||
* @return GDriveStoragePathInfo
|
||||
*/
|
||||
protected function buildPathInfo($file)
|
||||
{
|
||||
$info = new GDriveStoragePathInfo();
|
||||
|
||||
if (! $file) {
|
||||
$info->exists = false;
|
||||
return $info;
|
||||
}
|
||||
|
||||
$props = $file->getProperties();
|
||||
|
||||
$info->exists = true;
|
||||
$info->id = $file->getId();
|
||||
$info->name = $file->getName();
|
||||
$info->mimeType = $file->getMimeType();
|
||||
$info->isDir = $info->mimeType === self::FOLDER_MIME_TYPE;
|
||||
$info->size = (int) $file->getSize();
|
||||
$info->webUrl = $file->getWebViewLink();
|
||||
$info->created = $file->getCreatedTime() ? strtotime($file->getCreatedTime()) : time();
|
||||
$info->modified = $file->getModifiedTime() ? strtotime($file->getModifiedTime()) : time();
|
||||
$info->md5Checksum = $file->getMd5Checksum();
|
||||
|
||||
if (isset($props['path'])) {
|
||||
// if we have the path in the properties, that's a path from the storage folder
|
||||
// so we remove the storage folder and the slash after that from the path
|
||||
$info->path = substr($props['path'], strlen($this->storagePath) + 1);
|
||||
} else {
|
||||
// if we don't have the path in the properties, we "assume" it's under the storage folder
|
||||
$info->path = $file->getName();
|
||||
}
|
||||
|
||||
$info->file = $file;
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forcefully set a property on an object
|
||||
*
|
||||
* @param object $object The object to set the property on
|
||||
* @param string $property The property to set
|
||||
* @param mixed $value The value to set
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function forceSet($object, $property, $value)
|
||||
{
|
||||
if (!is_object($object)) {
|
||||
throw new Exception('Object must be an object');
|
||||
}
|
||||
if (!property_exists($object, $property)) {
|
||||
throw new Exception('Property ' . $property . ' does not exist on object ' . get_class($object));
|
||||
}
|
||||
$reflection = new \ReflectionProperty($object, $property);
|
||||
$reflection->setAccessible(true);
|
||||
$reflection->setValue($object, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = [])
|
||||
{
|
||||
$client = $this->drive->getClient();
|
||||
$originalHttpClient = $client->getHttpClient();
|
||||
if ($timeout > 0) {
|
||||
$baseUri = $originalHttpClient->getConfig('base_uri');
|
||||
$client->setHttpClient(new \VendorDuplicator\GuzzleHttp\Client([
|
||||
'base_uri' => $baseUri,
|
||||
'http_errors' => \false,
|
||||
'timeout' => $timeout / 1000000, // convert microseconds to seconds
|
||||
]));
|
||||
}
|
||||
|
||||
if ($offset == 0 && $length < 0) {
|
||||
// If we are copying the entire file, we can use the export link
|
||||
$contents = $this->getFileContent($storageFile);
|
||||
if ($contents === false) {
|
||||
$client->setHttpClient($originalHttpClient);
|
||||
return false;
|
||||
}
|
||||
// This may return 0 if the file is empty, so we specifically check for false
|
||||
return file_put_contents($destFile, $contents);
|
||||
}
|
||||
|
||||
if (! isset($extraData['fileId'])) {
|
||||
// this is the first chunk, we need to get the file id & make sure the destination is writable & empty.
|
||||
if (file_put_contents($destFile, '') === false) {
|
||||
\DUP_PRO_Log::trace('[GDriveAdapter] Unable to write to destination file: ' . $destFile);
|
||||
$client->setHttpClient($originalHttpClient);
|
||||
return false;
|
||||
}
|
||||
if (! $this->exists($storageFile)) {
|
||||
$client->setHttpClient($originalHttpClient);
|
||||
return false;
|
||||
}
|
||||
$extraData['fileId'] = $this->getPathInfo($storageFile)->id;
|
||||
}
|
||||
$fileId = $extraData['fileId'];
|
||||
|
||||
try {
|
||||
$client->setDefer(true);
|
||||
/** @var RequestInterface $request */
|
||||
$request = $this->drive->files->get($fileId, [
|
||||
'alt' => 'media',
|
||||
'acknowledgeAbuse' => true,
|
||||
]);
|
||||
$client->setDefer(false);
|
||||
|
||||
$request = $request->withHeader('Range', 'bytes=' . $offset . '-' . ($length > 0 ? ($offset + $length - 1) : ''));
|
||||
|
||||
$response = $client->execute($request, null);
|
||||
$contents = $response->getBody()->getContents();
|
||||
|
||||
$client->setHttpClient($originalHttpClient);
|
||||
if (file_put_contents($destFile, $contents, FILE_APPEND) !== false) {
|
||||
return $length;
|
||||
}
|
||||
return false;
|
||||
} catch (\Exception $e) {
|
||||
$client->setHttpClient($originalHttpClient);
|
||||
\DUP_PRO_Log::trace('[GDriveAdapter] Unable to get file content: ' . $e->getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,617 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2022, Snap Creek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Addons\GDriveAddon\Models;
|
||||
|
||||
use DUP_PRO_Global_Entity;
|
||||
use DUP_PRO_Google_Drive_Transfer_Mode;
|
||||
use DUP_PRO_Log;
|
||||
use DUP_PRO_Package_Upload_Info;
|
||||
use DUP_PRO_Server;
|
||||
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 GDriveAdapter $adapter
|
||||
*/
|
||||
class GDriveStorage extends AbstractStorageEntity implements StorageAuthInterface
|
||||
{
|
||||
// These numbers represent clients created in Google Cloud Console
|
||||
const GDRIVE_CLIENT_NATIVE = 1; // Native client 1
|
||||
const GDRIVE_CLIENT_WEB0722 = 2; // Web client 07/2022
|
||||
const GDRIVE_CLIENT_LATEST = 2; // Latest out of these above
|
||||
|
||||
const REQUIRED_SCOPES = [
|
||||
"openid",
|
||||
"https://www.googleapis.com/auth/userinfo.profile",
|
||||
"https://www.googleapis.com/auth/userinfo.email",
|
||||
// The drive.file scope limits access to just those files created by the plugin
|
||||
"https://www.googleapis.com/auth/drive.file",
|
||||
];
|
||||
|
||||
/**
|
||||
* Get default config
|
||||
*
|
||||
* @return array<string,scalar>
|
||||
*/
|
||||
protected static function getDefaultConfig()
|
||||
{
|
||||
$config = parent::getDefaultConfig();
|
||||
$config = array_merge(
|
||||
$config,
|
||||
[
|
||||
'storage_folder_id' => '',
|
||||
'storage_folder_web_url' => '',
|
||||
'token_json' => '',
|
||||
'refresh_token' => '',
|
||||
'client_number' => -1,
|
||||
'authorized' => false,
|
||||
]
|
||||
);
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = [
|
||||
'token_json' => $this->gdrive_access_token_set_json,
|
||||
'refresh_token' => $this->gdrive_refresh_token,
|
||||
'storage_folder' => ltrim($this->gdrive_storage_folder, '/\\'),
|
||||
'client_number' => $this->gdrive_client_number,
|
||||
'max_packages' => $this->gdrive_max_files,
|
||||
'authorized' => ($this->gdrive_authorization_state == 1),
|
||||
];
|
||||
// reset old values
|
||||
$this->gdrive_access_token_set_json = '';
|
||||
$this->gdrive_refresh_token = '';
|
||||
$this->gdrive_storage_folder = '';
|
||||
$this->gdrive_client_number = -1;
|
||||
$this->gdrive_max_files = 10;
|
||||
$this->gdrive_authorization_state = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the storage type
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function getSType()
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the storage type icon.
|
||||
*
|
||||
* @return string Returns the storage icon
|
||||
*/
|
||||
public static function getStypeIcon()
|
||||
{
|
||||
$imgUrl = DUPLICATOR_PRO_IMG_URL . '/google-drive.svg';
|
||||
return '<img src="' . esc_url($imgUrl) . '" class="dup-storage-icon" alt="' . esc_attr(static::getStypeName()) . '" />';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the storage type name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getStypeName()
|
||||
{
|
||||
return __('Google Drive', 'duplicator-pro');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get storage location string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLocationString()
|
||||
{
|
||||
if ($this->isAuthorized()) {
|
||||
return $this->config['storage_folder_web_url'];
|
||||
} else {
|
||||
return __('Not Authenticated', 'duplicator-pro');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if storage is supported
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function isSupported()
|
||||
{
|
||||
return (SnapUtil::isCurlEnabled() || SnapUtil::isUrlFopenEnabled());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get supported notice, displayed if storage isn't supported
|
||||
*
|
||||
* @return string html string or empty if storage is supported
|
||||
*/
|
||||
public static function getNotSupportedNotice()
|
||||
{
|
||||
if (static::isSupported()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!SnapUtil::isCurlEnabled() && !SnapUtil::isUrlFopenEnabled()) {
|
||||
return esc_html__(
|
||||
'Google Drive requires either the PHP CURL extension enabled or the allow_url_fopen runtime configuration to be enabled.',
|
||||
'duplicator-pro'
|
||||
);
|
||||
} elseif (!SnapUtil::isCurlEnabled()) {
|
||||
return esc_html__('Google Drive requires the PHP CURL extension enabled.', 'duplicator-pro');
|
||||
} else {
|
||||
return esc_html__('Google Drive requires the allow_url_fopen runtime configuration to be enabled.', 'duplicator-pro');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get upload chunk size in bytes
|
||||
*
|
||||
* @return int bytes
|
||||
*/
|
||||
public function getUploadChunkSize()
|
||||
{
|
||||
$dGlobal = DynamicGlobalEntity::getInstance();
|
||||
$chunkSizeKb = $dGlobal->getVal('gdrive_upload_chunksize_in_kb', 256);
|
||||
|
||||
return $chunkSizeKb * KB_IN_BYTES;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get upload chunk timeout in seconds
|
||||
*
|
||||
* @return int timeout in microseconds, 0 unlimited
|
||||
*/
|
||||
public function getUploadChunkTimeout()
|
||||
{
|
||||
// @todo: fixed to 10 seconds for historical reasons, make it configurable.
|
||||
return 10 * 1000000;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an HTML anchor tag of location
|
||||
*
|
||||
* @return string Returns an HTML anchor tag with the storage location as a hyperlink.
|
||||
*/
|
||||
public function getHtmlLocationLink()
|
||||
{
|
||||
if (! $this->isAuthorized() || empty($this->config['storage_folder_web_url'])) {
|
||||
return '<span>' . esc_html($this->getStorageFolder()) . '</span>';
|
||||
}
|
||||
|
||||
return sprintf("<a href=\"%s\" target=\"_blank\">%s</a>", esc_url($this->config['storage_folder_web_url']), esc_html($this->getStorageFolder()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Authorized from HTTP request
|
||||
*
|
||||
* @param string $message Message
|
||||
*
|
||||
* @return bool True if authorized, false if failed
|
||||
*/
|
||||
public function authorizeFromRequest(&$message = '')
|
||||
{
|
||||
$tokenPairString = '';
|
||||
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(static::getSType(), ['refresh_token' => $refreshToken]));
|
||||
if (!$token->refresh(true)) {
|
||||
throw new Exception(__('Failed to fetch information from Google Drive. Make sure the token is valid.', 'duplicator-pro'));
|
||||
}
|
||||
|
||||
if (empty($token->getScope())) {
|
||||
throw new Exception(__("Couldn't connect. Google Drive scopes not found.", 'duplicator-pro'));
|
||||
}
|
||||
|
||||
if (! $token->hasScopes(static::REQUIRED_SCOPES)) {
|
||||
throw new Exception(
|
||||
__(
|
||||
"Authorization failed. You did not allow all required permissions. Try again and make sure that you checked all checkboxes.",
|
||||
'duplicator-pro'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$this->config['refresh_token'] = $token->getRefreshToken();
|
||||
$this->config['token_json'] = wp_json_encode([
|
||||
'created' => $token->getCreated(),
|
||||
'access_token' => $token->getAccessToken(),
|
||||
'refresh_token' => $token->getRefreshToken(),
|
||||
'expires_in' => $token->getExpiresIn(),
|
||||
'scope' => $token->getScope(),
|
||||
]);
|
||||
$this->config['client_number'] = self::GDRIVE_CLIENT_LATEST;
|
||||
|
||||
$this->config['authorized'] = $token->isValid();
|
||||
} catch (Exception $e) {
|
||||
DUP_PRO_Log::traceException($e, "Problem authorizing Google Drive access token");
|
||||
DUP_PRO_Log::traceObject('Token pair string from authorization:', $tokenPairString);
|
||||
$message = $e->getMessage();
|
||||
return false;
|
||||
}
|
||||
$this->save();
|
||||
|
||||
$message = __('Google Drive 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 = __('Google Drive isn\'t authorized.', 'duplicator-pro');
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
$client = $this->getAdapter()->getService()->getClient();
|
||||
|
||||
if (!empty($this->config['refresh_token'])) {
|
||||
$client->revokeToken($this->config['refresh_token']);
|
||||
}
|
||||
|
||||
$accessTokenObj = json_decode($this->config['token_json']);
|
||||
if (is_object($accessTokenObj) && property_exists($accessTokenObj, 'access_token')) {
|
||||
$gdrive_access_token = $accessTokenObj->access_token;
|
||||
} else {
|
||||
$gdrive_access_token = false;
|
||||
}
|
||||
|
||||
if (!empty($gdrive_access_token)) {
|
||||
$client->revokeToken($gdrive_access_token);
|
||||
}
|
||||
|
||||
$this->config['token_json'] = '';
|
||||
$this->config['refresh_token'] = '';
|
||||
$this->config['client_number'] = -1;
|
||||
$this->config['authorized'] = false;
|
||||
} catch (Exception $e) {
|
||||
DUP_PRO_Log::trace("Problem revoking Google Drive access token msg: " . $e->getMessage());
|
||||
$message = $e->getMessage();
|
||||
return false;
|
||||
}
|
||||
|
||||
$message = __('Google Drive is disconnected successfully.', 'duplicator-pro');
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get authorization URL
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getAuthorizationUrl()
|
||||
{
|
||||
return (new TokenService(static::getSType()))->getRedirectUri();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get storage adapter
|
||||
*
|
||||
* @return GDriveAdapter
|
||||
*/
|
||||
public function getAdapter()
|
||||
{
|
||||
$global = DUP_PRO_Global_Entity::getInstance();
|
||||
$token = $this->getTokenFromConfig();
|
||||
if (! $this->adapter) {
|
||||
if (! isset($this->config['storage_folder_id']) || empty($this->config['storage_folder_id'])) {
|
||||
$this->adapter = new GDriveAdapter(
|
||||
$token,
|
||||
$this->config['storage_folder'],
|
||||
'',
|
||||
!$global->ssl_disableverify,
|
||||
($global->ssl_useservercerts ? '' : DUPLICATOR_PRO_CERT_PATH),
|
||||
$global->ipv4_only
|
||||
);
|
||||
$this->adapter->initialize();
|
||||
$storageFolder = $this->adapter->getPathInfo('/');
|
||||
$this->config['storage_folder_id'] = $storageFolder->id;
|
||||
$this->config['storage_folder_web_url'] = $storageFolder->webUrl;
|
||||
$this->save();
|
||||
} else {
|
||||
$this->adapter = new GDriveAdapter(
|
||||
$token,
|
||||
$this->config['storage_folder'],
|
||||
$this->config['storage_folder_id'],
|
||||
!$global->ssl_disableverify,
|
||||
($global->ssl_useservercerts ? '' : DUPLICATOR_PRO_CERT_PATH),
|
||||
$global->ipv4_only
|
||||
);
|
||||
$this->adapter->initialize();
|
||||
}
|
||||
}
|
||||
$storageFolder = $this->adapter->getPathInfo('/');
|
||||
if ($storageFolder->name !== basename($this->getStorageFolder())) {
|
||||
// root folder id & storage folder name is different.
|
||||
$this->adapter = new GDriveAdapter(
|
||||
$token,
|
||||
$this->config['storage_folder'],
|
||||
'',
|
||||
!$global->ssl_disableverify,
|
||||
($global->ssl_useservercerts ? '' : DUPLICATOR_PRO_CERT_PATH),
|
||||
$global->ipv4_only
|
||||
);
|
||||
$this->adapter->initialize();
|
||||
$storageFolder = $this->adapter->getPathInfo('/');
|
||||
$this->config['storage_folder_id'] = $storageFolder->id;
|
||||
$this->config['storage_folder_web_url'] = $storageFolder->webUrl;
|
||||
$this->save();
|
||||
}
|
||||
|
||||
return $this->adapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render form config fields
|
||||
*
|
||||
* @param bool $echo Echo or return
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function renderConfigFields($echo = true)
|
||||
{
|
||||
$userInfo = false;
|
||||
$quotaString = '';
|
||||
|
||||
if ($this->isAuthorized()) {
|
||||
$adapter = $this->getAdapter();
|
||||
try {
|
||||
$serviceDrive = $adapter->getService();
|
||||
$optParams = array('fields' => '*');
|
||||
$about = $serviceDrive->about->get($optParams);
|
||||
$storageQuota = $about->getStorageQuota();
|
||||
$quota_total = max($storageQuota->getLimit(), 1);
|
||||
$quota_used = $storageQuota->getUsage();
|
||||
$userInfo = $about->getUser();
|
||||
|
||||
if (is_numeric($quota_total) && is_numeric($quota_used)) {
|
||||
$available_quota = $quota_total - $quota_used;
|
||||
$used_perc = round($quota_used * 100 / $quota_total, 1);
|
||||
$quotaString = sprintf(
|
||||
__('%1$s%% used, %2$s available', 'duplicator-pro'),
|
||||
$used_perc,
|
||||
size_format($available_quota)
|
||||
);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
DUP_PRO_Log::info("Problem getting Google Drive user info and quota: " . $e->getMessage());
|
||||
$userInfo = $quotaString = null;
|
||||
}
|
||||
}
|
||||
|
||||
return TplMng::getInstance()->render(
|
||||
'gdriveaddon/configs/google_drive',
|
||||
[
|
||||
'storage' => $this,
|
||||
'storageFolder' => $this->config['storage_folder'],
|
||||
'maxPackages' => $this->config['max_packages'],
|
||||
'userInfo' => $userInfo,
|
||||
'quotaString' => $quotaString,
|
||||
],
|
||||
$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;
|
||||
}
|
||||
|
||||
$previousStorageFolder = $this->config['storage_folder'];
|
||||
$this->config['max_packages'] = SnapUtil::sanitizeIntInput(SnapUtil::INPUT_REQUEST, 'gdrive_max_files', 10);
|
||||
$this->config['storage_folder'] = self::getSanitizedInputFolder('_gdrive_storage_folder', 'remove');
|
||||
|
||||
if ($previousStorageFolder !== $this->config['storage_folder']) {
|
||||
$this->config['storage_folder_id'] = '';
|
||||
$this->config['storage_folder_web_url'] = '';
|
||||
}
|
||||
|
||||
$message = sprintf(
|
||||
__('Google Drive Storage Updated.', 'duplicator-pro'),
|
||||
$this->config['server'],
|
||||
$this->getStorageFolder()
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the token entity from config
|
||||
*
|
||||
* @return TokenEntity
|
||||
*/
|
||||
protected function getTokenFromConfig()
|
||||
{
|
||||
$token = new TokenEntity(static::getSType(), $this->config['token_json']);
|
||||
if ($token->isAboutToExpire()) {
|
||||
try {
|
||||
$token->refresh(true);
|
||||
} catch (Exception $e) {
|
||||
DUP_PRO_Log::traceException($e, "Problem refreshing Google Drive access token");
|
||||
}
|
||||
$this->config['token_json'] = wp_json_encode([
|
||||
'created' => $token->getCreated(),
|
||||
'access_token' => $token->getAccessToken(),
|
||||
'refresh_token' => $token->getRefreshToken(),
|
||||
'expires_in' => $token->getExpiresIn(),
|
||||
'scope' => $token->getScope(),
|
||||
]);
|
||||
$this->save();
|
||||
}
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 [
|
||||
'gdrive_upload_chunksize_in_kb' => 1024,
|
||||
'gdrive_transfer_mode' => DUP_PRO_Google_Drive_Transfer_Mode::Auto,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @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="gdrive_upload_chunksize_in_kb"
|
||||
id="gdrive_upload_chunksize_in_kb"
|
||||
type="number"
|
||||
min="256"
|
||||
step="256"
|
||||
data-parsley-required
|
||||
data-parsley-type="number"
|
||||
data-parsley-errors-container="#gdrive_upload_chunksize_in_kb_error_container"
|
||||
value="<?php echo (int) $values['gdrive_upload_chunksize_in_kb']; ?>"
|
||||
> <b>KB</b>
|
||||
<div id="gdrive_upload_chunksize_in_kb_error_container" class="duplicator-error-container"></div>
|
||||
<p class="description">
|
||||
<?php esc_html_e(
|
||||
'How much should be uploaded to Google Drive per attempt. Higher=faster but less reliable. It should be multiple of 256.',
|
||||
'duplicator-pro'
|
||||
); ?>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr valign="top">
|
||||
<th scope="row"><label><?php esc_html_e("Transfer Mode", 'duplicator-pro'); ?></label></th>
|
||||
<td>
|
||||
<input
|
||||
type="radio"
|
||||
value="<?php echo (int) DUP_PRO_Google_Drive_Transfer_Mode::Auto ?>"
|
||||
name="gdrive_transfer_mode" id="gdrive_transfer_mode_auto"
|
||||
<?php checked($values['gdrive_transfer_mode'], DUP_PRO_Google_Drive_Transfer_Mode::Auto); ?>
|
||||
>
|
||||
<label for="gdrive_transfer_mode_auto"><?php esc_html_e("Auto", 'duplicator-pro'); ?></label>
|
||||
|
||||
<input
|
||||
type="radio" <?php disabled(!DUP_PRO_Server::isURLFopenEnabled()) ?>
|
||||
value="<?php echo (int) DUP_PRO_Google_Drive_Transfer_Mode::FOpen_URL ?>"
|
||||
name="gdrive_transfer_mode"
|
||||
id="gdrive_transfer_mode_stream"
|
||||
<?php checked($values['gdrive_transfer_mode'], DUP_PRO_Google_Drive_Transfer_Mode::FOpen_URL); ?>
|
||||
>
|
||||
<label for="gdrive_transfer_mode_stream"><?php esc_html_e("FOpen URL", 'duplicator-pro'); ?></label>
|
||||
<?php if (!DUP_PRO_Server::isURLFopenEnabled()) : ?>
|
||||
<i
|
||||
class="fas fa-question-circle fa-sm"
|
||||
data-tooltip-title="<?php esc_attr_e("FOpen URL", 'duplicator-pro'); ?>"
|
||||
data-tooltip="<?php esc_attr_e('Not available because "allow_url_fopen" is turned off in the php.ini', 'duplicator-pro'); ?>">
|
||||
</i>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2022, Snap Creek LLC
|
||||
*/
|
||||
|
||||
namespace Duplicator\Addons\GDriveAddon\Models;
|
||||
|
||||
use Duplicator\Models\Storages\StoragePathInfo;
|
||||
use VendorDuplicator\Google\Service\Drive\DriveFile;
|
||||
|
||||
class GDriveStoragePathInfo extends StoragePathInfo
|
||||
{
|
||||
/** @var string */
|
||||
public $id = '';
|
||||
|
||||
/** @var string */
|
||||
public $name = '';
|
||||
|
||||
/** @var string */
|
||||
public $mimeType = '';
|
||||
|
||||
/** @var string */
|
||||
public $webUrl = '';
|
||||
|
||||
/** @var string */
|
||||
public $md5Checksum = '';
|
||||
|
||||
/** @var DriveFile */
|
||||
public $file;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace Duplicator\Addons\GDriveAddon\Utils;
|
||||
|
||||
use Duplicator\Addons\GDriveAddon\GDriveAddon;
|
||||
use Duplicator\Utils\AbstractAutoloader;
|
||||
|
||||
class Autoloader extends AbstractAutoloader
|
||||
{
|
||||
const VENDOR_PATH = GDriveAddon::ADDON_PATH . '/vendor-prefixed/';
|
||||
/**
|
||||
* Register autoloader function
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register()
|
||||
{
|
||||
spl_autoload_register([__CLASS__, 'load']);
|
||||
|
||||
require_once GDriveAddon::ADDON_PATH . '/vendor-prefixed/guzzlehttp/guzzle/src/functions_include.php';
|
||||
require_once GDriveAddon::ADDON_PATH . '/vendor-prefixed/guzzlehttp/promises/src/functions_include.php';
|
||||
require_once GDriveAddon::ADDON_PATH . '/vendor-prefixed/guzzlehttp/psr7/src/functions_include.php';
|
||||
require_once GDriveAddon::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 . 'Firebase\\JWT' => self::VENDOR_PATH . 'firebase/php-jwt/src/',
|
||||
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 . 'Monolog' => self::VENDOR_PATH . 'monolog/monolog/src/Monolog',
|
||||
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',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Duplicator\Addons\GDriveAddon\Utils;
|
||||
|
||||
use VendorDuplicator\Google\Client;
|
||||
use VendorDuplicator\GuzzleHttp\Client as GuzzleClient;
|
||||
|
||||
class GoogleClient extends Client
|
||||
{
|
||||
/** @var array<string,mixed> */
|
||||
protected $customHttpOptions = [];
|
||||
|
||||
/**
|
||||
* Set http client options
|
||||
*
|
||||
* @param array<string,mixed> $options options
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setHttpClientOptions(array $options)
|
||||
{
|
||||
$this->customHttpOptions = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new http client.
|
||||
*
|
||||
* @return GuzzleClient
|
||||
*/
|
||||
protected function createDefaultHttpClient()
|
||||
{
|
||||
$options = [
|
||||
'base_uri' => $this->getConfig('base_path'),
|
||||
'http_errors' => false,
|
||||
];
|
||||
$options = array_merge($options, $this->customHttpOptions);
|
||||
$guzzleClient = new GuzzleClient($options);
|
||||
|
||||
return $guzzleClient;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Duplicator messages sections
|
||||
*
|
||||
* @package Duplicator
|
||||
* @copyright (c) 2022, Snap Creek LLC
|
||||
*/
|
||||
|
||||
use Duplicator\Addons\GDriveAddon\Models\GDriveStorage;
|
||||
|
||||
defined("ABSPATH") or die("");
|
||||
|
||||
/**
|
||||
* Variables
|
||||
*
|
||||
* @var \Duplicator\Core\Controllers\ControllersManager $ctrlMng
|
||||
* @var \Duplicator\Core\Views\TplMng $tplMng
|
||||
* @var array<string, mixed> $tplData
|
||||
* @var GDriveStorage $storage
|
||||
*/
|
||||
$storage = $tplData["storage"];
|
||||
/** @var string */
|
||||
$storageFolder = $tplData["storageFolder"];
|
||||
/** @var int */
|
||||
$maxPackages = $tplData["maxPackages"];
|
||||
/** @var \VendorDuplicator\Google\Service\Drive\User $userInfo */
|
||||
$userInfo = $tplData["userInfo"];
|
||||
/** @var string */
|
||||
$quotaString = $tplData["quotaString"];
|
||||
|
||||
$tplMng->render('admin_pages/storages/parts/provider_head');
|
||||
?>
|
||||
<tr>
|
||||
<th scope="row"><label for=""><?php esc_html_e("Authorization", 'duplicator-pro'); ?></label></th>
|
||||
<td class="gdrive-authorize">
|
||||
<?php if (!$storage->isAuthorized()) : ?>
|
||||
<div class='gdrive-authorization-state' id="gdrive-state-unauthorized">
|
||||
<!-- CONNECT -->
|
||||
<div id="dpro-gdrive-connect-btn-area">
|
||||
<button
|
||||
id="dpro-gdrive-connect-btn"
|
||||
type="button"
|
||||
class="button button-large"
|
||||
onclick="DupPro.Storage.GDrive.GoogleGetAuthUrl();"
|
||||
>
|
||||
<i class="fa fa-plug"></i> <?php esc_html_e('Connect to Google Drive', 'duplicator-pro'); ?>
|
||||
<img
|
||||
src="<?php echo esc_url(DUPLICATOR_PRO_IMG_URL . '/google-drive.svg'); ?>"
|
||||
style='vertical-align: middle; margin:-2px 0 0 3px; height:18px; width:18px'
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div class="authorization-state" id="dpro-gdrive-connect-progress">
|
||||
<div style="padding:10px">
|
||||
<i class="fas fa-circle-notch fa-spin"></i> <?php esc_html_e('Getting Google Drive Request Token...', 'duplicator-pro'); ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- STEPS -->
|
||||
<div id="dpro-gdrive-steps">
|
||||
<div>
|
||||
<b><?php esc_html_e('Step 1:', 'duplicator-pro'); ?></b>
|
||||
<?php
|
||||
esc_html_e(
|
||||
"Duplicator needs to authorize Google Drive. Make sure to allow all required permissions.",
|
||||
'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'
|
||||
) . ' google_drive.php' .
|
||||
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="gdrive-auth-window-button" class="button" onclick="DupPro.Storage.GDrive.OpenAuthPage(); return false;">
|
||||
<i class="fa fa-user"></i> <?php esc_html_e("Authorize Google Drive", 'duplicator-pro'); ?>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="gdrive-auth-code-area">
|
||||
<b><?php esc_html_e('Step 2:', 'duplicator-pro'); ?></b>
|
||||
<?php esc_html_e("Paste code from Google authorization page.", 'duplicator-pro'); ?> <br/>
|
||||
<input style="width:400px" id="gdrive-auth-code" name="gdrive-auth-code" />
|
||||
</div>
|
||||
|
||||
<b><?php esc_html_e('Step 3:', 'duplicator-pro'); ?></b>
|
||||
<?php esc_html_e('Finalize Google Drive setup by clicking the "Finalize Setup" button.', 'duplicator-pro') ?>
|
||||
<br/>
|
||||
<button
|
||||
id="gdrive-finalize-setup"
|
||||
type="button"
|
||||
class="button"
|
||||
>
|
||||
<i class="fa fa-check-square"></i> <?php esc_html_e('Finalize Setup', 'duplicator-pro'); ?>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<div class='gdrive-authorization-state' id="gdrive-state-authorized" style="margin-top:-10px">
|
||||
<?php if ($userInfo != null) : ?>
|
||||
<h3>
|
||||
<?php esc_html_e('Google Drive Account', 'duplicator-pro'); ?><br/>
|
||||
<i class="dpro-edit-info">
|
||||
<?php esc_html_e('Duplicator has been authorized to access this user\'s Google Drive account', 'duplicator-pro'); ?>
|
||||
</i>
|
||||
</h3>
|
||||
<div id="gdrive-account-info">
|
||||
<label><?php esc_html_e('Name', 'duplicator-pro'); ?>:</label>
|
||||
<?php echo esc_html($userInfo->getDisplayName()); ?><br/>
|
||||
|
||||
<label><?php esc_html_e('Email', 'duplicator-pro'); ?>:</label> <?php echo esc_html($userInfo->getEmailAddress()); ?>
|
||||
<?php if (strlen($quotaString) > 0) { ?>
|
||||
<br>
|
||||
<label><?php esc_html_e('Quota', 'duplicator-pro'); ?>:</label> <?php echo esc_html($quotaString); ?>
|
||||
<?php } ?>
|
||||
</div><br/>
|
||||
<?php else : ?>
|
||||
<div><?php esc_html_e('Error retrieving user information.', 'duplicator-pro'); ?></div>
|
||||
<?php endif ?>
|
||||
|
||||
<button
|
||||
id="dup-gdrive-cancel-authorization"
|
||||
type="button"
|
||||
class="button"
|
||||
>
|
||||
<?php esc_html_e('Cancel Authorization', 'duplicator-pro'); ?>
|
||||
</button><br/>
|
||||
<i class="dpro-edit-info">
|
||||
<?php
|
||||
esc_html_e(
|
||||
'Disassociates storage provider with the Google Drive account. Will require re-authorization.',
|
||||
'duplicator-pro'
|
||||
); ?>
|
||||
</i>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="_gdrive_storage_folder">
|
||||
<?php esc_html_e("Storage Folder", 'duplicator-pro'); ?>
|
||||
</label>
|
||||
</th>
|
||||
<td>
|
||||
<b>//Google Drive/</b>
|
||||
<input
|
||||
id="_gdrive_storage_folder"
|
||||
name="_gdrive_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>
|
||||
<?php
|
||||
$tipContent = __(
|
||||
'If the directory path above is already in Google Drive before connecting then a duplicate folder name will be made in the same path.',
|
||||
'duplicator-pro'
|
||||
) . ' google_drive.php' . __('This is because the plugin only has rights to folders it creates.', 'duplicator-pro');
|
||||
?>
|
||||
<i
|
||||
class="fas fa-question-circle fa-sm"
|
||||
data-tooltip-title="<?php esc_attr_e("Storage Folder Notice", 'duplicator-pro'); ?>"
|
||||
data-tooltip="<?php esc_attr($tipContent); ?>"
|
||||
>
|
||||
</i>
|
||||
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for=""><?php esc_html_e("Max Packages", 'duplicator-pro'); ?></label></th>
|
||||
<td>
|
||||
<label for="gdrive_max_files">
|
||||
<input
|
||||
id="gdrive_max_files"
|
||||
name="gdrive_max_files"
|
||||
type="number"
|
||||
value="<?php echo (int) $maxPackages; ?>"
|
||||
min="0"
|
||||
maxlength="4"
|
||||
data-parsley-errors-container="#gdrive_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="gdrive_max_files_error_container" class="duplicator-error-container"></div>
|
||||
</td>
|
||||
</tr>
|
||||
<?php
|
||||
$tplMng->render('admin_pages/storages/parts/provider_foot');
|
||||
|
||||
// Alerts for Google Drive
|
||||
$alertConnStatus = new DUP_PRO_UI_Dialog();
|
||||
$alertConnStatus->title = __('Google Drive Authorization Error', 'duplicator-pro');
|
||||
$alertConnStatus->message = ''; // javascript inserted message
|
||||
$alertConnStatus->initAlert();
|
||||
|
||||
?>
|
||||
<script>
|
||||
jQuery(document).ready(function ($) {
|
||||
DupPro.Storage.GDrive = DupPro.Storage.GDrive || {};
|
||||
|
||||
$('#dup-gdrive-cancel-authorization').click(function (event) {
|
||||
event.stopPropagation();
|
||||
DupPro.Storage.RevokeAuth(<?php echo (int) $storage->getId(); ?>);
|
||||
});
|
||||
|
||||
DupPro.Storage.GDrive.GoogleGetAuthUrl = function ()
|
||||
{
|
||||
$('#dpro-gdrive-connect-btn-area').hide();
|
||||
$('#dpro-gdrive-steps').show();
|
||||
DupPro.Storage.GDrive.AuthUrl = <?php echo json_encode($storage->getAuthorizationUrl()); ?>;
|
||||
}
|
||||
|
||||
DupPro.Storage.GDrive.OpenAuthPage = function ()
|
||||
{
|
||||
window.open(DupPro.Storage.GDrive.AuthUrl, '_blank');
|
||||
}
|
||||
|
||||
$('#gdrive-finalize-setup').click(function (event) {
|
||||
event.stopPropagation();
|
||||
|
||||
if ($('#gdrive-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': $('#_gdrive_storage_folder').val(),
|
||||
'max_packages': $('#gdrive_max_files').val(),
|
||||
'auth_code' : $('#gdrive-auth-code').val()
|
||||
}
|
||||
);
|
||||
} else {
|
||||
<?php $alertConnStatus->showAlert(); ?>
|
||||
let alertMsg = "<i class='fas fa-exclamation-triangle'></i> " +
|
||||
"<?php esc_html_e('Please enter your Google Drive authorization code!', 'duplicator-pro'); ?>";
|
||||
<?php $alertConnStatus->updateMessage("alertMsg"); ?>
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
Reference in New Issue
Block a user