first commit

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

View File

@@ -0,0 +1,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__;
}
}

View 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;
}
}
}

View File

@@ -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']; ?>"
>&nbsp;<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> &nbsp;
<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> &nbsp;
<?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
}
}

View File

@@ -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;
}

View 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',
];
}
}

View File

@@ -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;
}
}

View File

@@ -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>&nbsp;
<?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"
>&nbsp;
<?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>