Your IP : 216.73.216.95


Current Path : /var/www/html/wp-content/plugins/duplicator-pro/src/Models/Storages/
Upload File :
Current File : /var/www/html/wp-content/plugins/duplicator-pro/src/Models/Storages/GDriveStorage.php

<?php

/**
 *
 * @package   Duplicator
 * @copyright (c) 2022, Snap Creek LLC
 */

namespace Duplicator\Models\Storages;

use DUP_PRO_GDrive_U;
use DUP_PRO_Global_Entity;
use DUP_PRO_Log;
use DUP_PRO_Package;
use DUP_PRO_Package_File_Type;
use DUP_PRO_Package_Upload_Info;
use DUP_PRO_STR;
use DUP_PRO_U;
use Duplicator\Core\Views\TplMng;
use Duplicator\Libs\Snap\SnapUtil;
use Duplicator_Pro_Google_Client;
use Duplicator_Pro_Google_Service_Drive;
use Duplicator_Pro_Google_Service_Drive_DriveFile;
use Exception;

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

    /**
     * Get default config
     *
     * @return array<string,scalar>
     */
    protected static function getDefaultCoinfig()
    {
        $config = parent::getDefaultCoinfig();
        $config = array_merge(
            $config,
            [
                '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()
    {
        return 'google://' . $this->getStorageFolder();
    }

    /**
     * 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');
        }
    }

    /**
     * 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()
    {
        return '<span>' . esc_html($this->getLocationString()) . '</span>';
    }

    /**
     * Authorized from HTTP request
     *
     * @param string $message Message
     *
     * @return bool True if authorized, false if failed
     */
    public function authorizeFromRequest(&$message = '')
    {
        $tokenPairString = '';
        try {
            if (($authCode = SnapUtil::sanitizeTextInput(SnapUtil::INPUT_REQUEST, 'auth_code')) === '') {
                throw new Exception(__('Authorization code is empty', 'duplicator-pro'));
            }

            $this->name                     = SnapUtil::sanitizeTextInput(SnapUtil::INPUT_REQUEST, 'name', '');
            $this->notes                    = SnapUtil::sanitizeDefaultInput(SnapUtil::INPUT_REQUEST, 'notes', '');
            $this->config['max_packages']   = SnapUtil::sanitizeIntInput(SnapUtil::INPUT_REQUEST, 'max_packages', 10);
            $this->config['storage_folder'] = self::getSanitizedInputFolder('storage_folder', 'remove');

            $this->revokeAuthorization();

            $rawClient       = DUP_PRO_GDrive_U::get_raw_google_client();
            $tokenPairString = $rawClient->authenticate($authCode);
            $tokenPair       = json_decode($tokenPairString, true);

            if (!is_array($tokenPair)) {
                throw new Exception(__('Couldn\'t connect. Google Drive token pair not found.', 'duplicator-pro'));
            }

            if (!isset($tokenPair['refresh_token'])) {
                throw new Exception(__("Couldn't connect. Google Drive refresh token not found.", 'duplicator-pro'));
            }

            if (!isset($tokenPair['scope'])) {
                throw new Exception(__("Couldn't connect. Google Drive scopes not found.", 'duplicator-pro'));
            }

            if (!DUP_PRO_GDrive_U::checkScopes($tokenPair['scope'])) {
                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'] = $tokenPair['refresh_token'];
            $this->config['token_json']    = $rawClient->getAccessToken();
            $this->config['client_number'] = self::GDRIVE_CLIENT_LATEST;

            $this->config['authorized'] = true;
        } 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;
        }

        $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 = DUP_PRO_GDrive_U::get_raw_google_client($this->config['client_number']);

            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()
    {
        $google_client = DUP_PRO_GDrive_U::get_raw_google_client();
        return $google_client->createAuthUrl();
    }

    /**
     * Render form config fields
     *
     * @param bool $echo Echo or return
     *
     * @return string
     */
    public function renderConfigFields($echo = true)
    {
        $userInfo    = false;
        $quotaString = '';

        if ($this->isAuthorized() && ($client = $this->getClient()) != null) {
            $userInfo = DUP_PRO_GDrive_U::get_user_info($client);

            $serviceDrive = new Duplicator_Pro_Google_Service_Drive($client);
            $optParams    = array('fields' => '*');
            $about        = $serviceDrive->about->get($optParams);
            $quota_total  = max($about->storageQuota['limit'], 1);
            $quota_used   = $about->storageQuota['usage'];

            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,
                    round($available_quota / 1048576, 1) . ' MB'
                );
            }
        }

        return TplMng::getInstance()->render(
            'admin_pages/storages/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;
        }

        $this->config['max_packages']   = SnapUtil::sanitizeIntInput(SnapUtil::INPUT_REQUEST, 'gdrive_max_files', 10);
        $this->config['storage_folder'] = self::getSanitizedInputFolder('_gdrive_storage_folder', 'remove');

        $message = sprintf(
            __('Google Drive Storage Updated.', 'duplicator-pro'),
            $this->config['server'],
            $this->getStorageFolder()
        );
        return true;
    }

    /**
     * Storages test
     *
     * @param string $message Test message
     *
     * @return bool return true if success, false otherwise
     */
    public function test(&$message = '')
    {
        if (parent::test($message) == false) {
            return false;
        }

        $result          = false;
        $source_handle   = null;
        $dest_handle     = null;
        $source_filepath = '';
        $dest_filepath   = '';

        try {
            $storageFolder   = $this->getStorageFolder();
            $source_filepath = wp_tempnam('DUP', DUPLICATOR_PRO_SSDIR_PATH_TMP);
            DUP_PRO_Log::trace("Created temp file $source_filepath");

            $source_handle = fopen($source_filepath, 'w');
            $rnd           = rand();
            fwrite($source_handle, "$rnd");
            DUP_PRO_Log::trace("Wrote $rnd to $source_filepath");
            fclose($source_handle);
            $source_handle = null;

            // -- Send the file --
            $basename        = basename($source_filepath);
            $gdrive_filepath = $storageFolder . '/' . $basename;

            $this->testLog->addMessage('Init Google Drive client');
            $client = $this->getClient();
            if ($client == null) {
                throw new Exception(__("Couldn't get Google client when performing Google Drive file test", 'duplicator-pro'));
            }

            DUP_PRO_Log::trace("About to send $source_filepath to $gdrive_filepath on Google Drive");

            $google_service_drive = new Duplicator_Pro_Google_Service_Drive($client);
            $this->testLog->addMessage('Get Google Drive folder id');
            $directory_id = DUP_PRO_GDrive_U::get_directory_id($google_service_drive, $storageFolder);
            if ($directory_id == null) {
                $msg = sprintf(__('Couldn\'t get directory ID for folder %s when performing Google Drive file test', 'duplicator-pro'), $storageFolder);
                throw new Exception($msg);
            }

            $this->testLog->addMessage('Start upload file ' . $source_filepath . ' to Google Drive');
            $google_file = DUP_PRO_GDrive_U::upload_file($client, $source_filepath, $directory_id);
            if ($google_file == null) {
                throw new Exception(__("Couldn't upload file to Google Drive.", 'duplicator-pro'));
            }

            // -- Download the file --
            $dest_filepath = wp_tempnam('GDRIVE_TMP', DUPLICATOR_PRO_SSDIR_PATH_TMP);

            if (file_exists($dest_filepath)) {
                @unlink($dest_filepath);
            }

            DUP_PRO_Log::trace("About to download $gdrive_filepath on Google Drive to $dest_filepath");

            $this->testLog->addMessage('Try to download the file uploaded to Google Drive to ' . $dest_filepath);

            if (DUP_PRO_GDrive_U::download_file($client, $google_file, $dest_filepath)) {
                try {
                    $google_service_drive = new Duplicator_Pro_Google_Service_Drive($client);
                    $google_service_drive->files->delete($google_file->id);
                } catch (Exception $ex) {
                    DUP_PRO_Log::traceException($ex, "Error deleting temporary file generated on Google File test");
                }

                /** @todo add rturn chcks for all IO functions */
                $dest_handle = fopen($dest_filepath, 'r');
                $dest_string = fread($dest_handle, 100);
                fclose($dest_handle);
                $dest_handle = null;

                /* The values better match or there was a problem */
                if ($rnd == (int) $dest_string) {
                    DUP_PRO_Log::trace("Files match! $rnd $dest_string");
                    $result  = true;
                    $message = esc_html__('Successfully stored and retrieved file', 'duplicator-pro');
                } else {
                    DUP_PRO_Log::traceError("mismatch in files $rnd != $dest_string");
                    $message = esc_html__('There was a problem storing or retrieving the temporary file on this account.', 'duplicator-pro');
                }
            } else {
                DUP_PRO_Log::traceError("Couldn't download $source_filepath after it had been uploaded");
            }
        } catch (Exception $e) {
            DUP_PRO_Log::traceException($e, 'Google Drive test error');
            $message = $e->getMessage();
        }

        if (file_exists($source_filepath)) {
            unlink($source_filepath);
            DUP_PRO_Log::trace("Deleted temp file $source_filepath");
        }

        if (file_exists($dest_filepath)) {
            unlink($dest_filepath);
            DUP_PRO_Log::trace("Deleted temp file $dest_filepath");
        }


        if ($result) {
            $this->testLog->addMessage(__('Successfully stored and deleted file', 'duplicator-pro'));
            $message = __('Successfully stored and deleted file', 'duplicator-pro');
            return true;
        } else {
            return false;
        }
    }

    /**
     * Copies the package files from the default local storage to another local storage location
     *
     * @param DUP_PRO_Package             $package     the package
     * @param DUP_PRO_Package_Upload_Info $upload_info the upload info
     *
     * @return void
     */
    public function copyFromDefault(DUP_PRO_Package $package, DUP_PRO_Package_Upload_Info $upload_info)
    {
        DUP_PRO_Log::infoTrace("Copyng to Storage " . $this->name . '[ID: ' . $this->id . '] type:' . $this->getStypeName());

        $source_archive_filepath   = $package->getLocalPackageFilePath(DUP_PRO_Package_File_Type::Archive);
        $source_installer_filepath = $package->getLocalPackageFilePath(DUP_PRO_Package_File_Type::Installer);
        $dest_installer_filename   = $package->Installer->getInstallerName();

        if ($source_archive_filepath === false) {
            DUP_PRO_Log::traceError("Archive doesn't exist for $package->Name!? - $source_archive_filepath");
            $upload_info->failed = true;
        }

        if ($source_installer_filepath === false) {
            DUP_PRO_Log::traceError("Installer doesn't exist for $package->Name!? - $source_installer_filepath");
            $upload_info->failed = true;
        }

        if ($upload_info->failed == true) {
            DUP_PRO_Log::infoTrace('Google Drive storage failed flag ($upload_info->failed) has been already set.');
            $package->update();
            return;
        }

        try {
            $client = $this->getClient();
            if ($client == null) {
                throw new Exception("Google client is null!");
            }

            if (empty($upload_info->data)) {
                $google_service_drive = new Duplicator_Pro_Google_Service_Drive($client);
                $upload_info->data    = DUP_PRO_GDrive_U::get_directory_id($google_service_drive, $this->getStorageFolder());
                if ($upload_info->data == null) {
                    $upload_info->failed = true;
                    DUP_PRO_Log::infoTrace("Error getting/creating Google Drive directory " . $this->getStorageFolder());
                    $package->update();
                    return;
                }
            }

            $tried_copying_installer = false;
            if (!$upload_info->copied_installer) {
                $tried_copying_installer = true;
                DUP_PRO_Log::trace("ATTEMPT: GDrive upload installer file $source_installer_filepath to " . $this->getStorageFolder());
                $google_service_drive = new Duplicator_Pro_Google_Service_Drive($client);
                //$upload_info->data is the parent file id
                $source_installer_filename = basename($source_installer_filepath);
                $existing_file_id          = DUP_PRO_GDrive_U::get_file(
                    $google_service_drive,
                    $source_installer_filename,
                    $upload_info->data
                );
                if ($existing_file_id != null) {
                    DUP_PRO_Log::trace(
                        "Installer already exists so deleting $source_installer_filename before uploading again. " .
                        "Existing file id = $existing_file_id"
                    );
                    DUP_PRO_GDrive_U::delete_file($google_service_drive, $existing_file_id);
                } else {
                    DUP_PRO_Log::trace("Installer doesn't exist already so no need to delete $source_installer_filename");
                }

                if (DUP_PRO_GDrive_U::upload_file($client, $source_installer_filepath, $upload_info->data, $dest_installer_filename)) {
                    DUP_PRO_Log::infoTrace('SUCCESS: Installer upload to Google Drive.');
                    $upload_info->copied_installer = true;
                    $upload_info->progress         = 5;
                } else {
                    $upload_info->failed = true;
                    DUP_PRO_Log::infoTrace('FAIL: Installer upload to Google Drive.');
                }

                // The package update will automatically capture the upload_info since its part of the package
                $package->update();
            } else {
                DUP_PRO_Log::trace("Already copied installer on previous execution of Google Drive $this->name so skipping");
            }

            if ((!$upload_info->copied_archive) && (!$tried_copying_installer)) {
                $global = DUP_PRO_Global_Entity::getInstance();

                // Warning: Google client is set to defer mode within this function
                // The upload_id for google drive is just the resume uri

                if ($upload_info->archive_offset == 0) {
                    // If just starting on this go ahead and delete existing file

                    $google_service_drive = new Duplicator_Pro_Google_Service_Drive($client);
                    //$upload_info->data is the parent file id
                    $source_archive_filename = basename($source_archive_filepath);
                    $existing_file_id        = DUP_PRO_GDrive_U::get_file($google_service_drive, $source_archive_filename, $upload_info->data);
                    if ($existing_file_id != null) {
                        DUP_PRO_Log::trace("Archive already exists so deleting $source_archive_filename before uploading again");
                        DUP_PRO_GDrive_U::delete_file($google_service_drive, $existing_file_id);
                    } else {
                        DUP_PRO_Log::trace("Archive doesn't exist so no need to delete $source_archive_filename");
                    }
                }

                // error_log('## offset: '.$upload_info->archive_offset);
                // Google Drive worker time capped at 10 seconds
                $gdrive_upload_info = DUP_PRO_GDrive_U::upload_file_chunk(
                    $client,
                    $source_archive_filepath,
                    $upload_info->data,
                    $global->gdrive_upload_chunksize_in_kb * 1024,
                    10,
                    $upload_info->archive_offset,
                    $upload_info->upload_id,
                    (50 + $global->getMicrosecLoadReduction())
                );
                $file_size          = filesize($source_archive_filepath);
                // Attempt to test self killing
                /*
                if (time() % 5 === 0) {
                    error_log('Attempting to make custom error');
                    $gdrive_upload_info->error_details = "Custom Error";
                }
                */

                if ($gdrive_upload_info->error_details == null) {
                    // Clear the failure count - we are just looking for consecutive errors
                    $upload_info->failure_count  = 0;
                    $upload_info->archive_offset = $gdrive_upload_info->next_offset;
                    // We are considering the whole Resume URI as the Upload ID
                    $upload_info->upload_id = $gdrive_upload_info->resume_uri;
                    $upload_info->progress  = max(5, DUP_PRO_U::percentage($upload_info->archive_offset, $file_size, 0));
                    DUP_PRO_Log::infoTrace(
                        "Archive upload offset: $upload_info->archive_offset [File size: $file_size] [Upload progress: $upload_info->progress%]"
                    );
                    if ($gdrive_upload_info->is_complete) {
                        DUP_PRO_Log::infoTrace('SUCCESS: Archive upload to Google Drive.');
                        $upload_info->copied_archive = true;
                        $this->purgeOldPackages();
                    }
                } else {
                    DUP_PRO_Log::traceError('FAIL: Archive upload to Google Drive. ERROR: ' . $gdrive_upload_info->error_details);
                    // error_log('$$ ELSE: '.$gdrive_upload_info->error_details);
                    $this->setArchiveOffset($upload_info);
                    $upload_info->increase_failure_count();
                }
            } else {
                DUP_PRO_Log::trace("Already copied archive on previous execution of Google Drive $this->name so skipping");
            }
        } catch (Exception $e) {
            // error_log('**** Catch ****');
            DUP_PRO_Log::traceError(
                "EXCEPTION ERROR: Problems copying package " . $package->Name . " to " . $this->getStorageFolder() . ". Message: " . $e->getMessage()
            );
            $this->setArchiveOffset($upload_info);
            $upload_info->increase_failure_count();
        }

        if ($upload_info->failed) {
            DUP_PRO_Log::infoTrace('Google Drive storage failed flag ($upload_info->failed) has been already set.');
        }

        // The package update will automatically capture the upload_info since its part of the package
        $package->update();
    }

    /**
     * Set google drive archive offset
     *
     * @param DUP_PRO_Package_Upload_Info $upload_info Upload info
     *
     * @return void
     */
    protected function setArchiveOffset(DUP_PRO_Package_Upload_Info $upload_info)
    {
        $resume_url = $upload_info->upload_id;
        if (is_null($resume_url)) {
            $upload_info->archive_offset = 0;
        } else {
            $args          = array(
                'headers' => array(
                    'Content-Length' => "0",
                    'Content-Range'  => "bytes */*",
                ),
                'method'  => 'PUT',
                'timeout' => 60,
            );
            $response      = wp_remote_request($resume_url, $args);
            $response_code = wp_remote_retrieve_response_code($response);
            DUP_PRO_Log::infoTrace("Google Drive API response code: $response_code");
            // error_log('response code:'.$response_code);
            switch ($response_code) {
                case 308:
                    DUP_PRO_Log::infoTrace("Google Drive transfer is incomplete.");
                    // error_log("Google Drive transfer is incomplete");
                    $range = wp_remote_retrieve_header($response, 'range');
                    if (!empty($range) && preg_match('/bytes=0-(\d+)$/', $range, $matches)) {
                        $upload_info->archive_offset = 1 + (int) $matches[1];
                    } else {
                        $upload_info->archive_offset = 0;
                    }
                    break;
                case 200:
                case 201:
                    DUP_PRO_Log::infoTrace("SUCCESS: archive upload to Google Drive.");
                    $upload_info->copied_archive = true;
                    $this->purgeOldPackages();
                    break;
                case 404:
                default:
                    $upload_info->archive_offset = 0;
                    break;
            }
        }
        // error_log("Setting archive offset to the ".$upload_info->archive_offset);
        DUP_PRO_Log::trace("Setting archive offset to the " . $upload_info->archive_offset);
    }

    /**
     * Purge old packages
     *
     * @return bool true if success, false otherwise
     */
    public function purgeOldPackages()
    {
        if ($this->config['max_packages'] <= 0) {
            return true;
        }

        DUP_PRO_Log::infoTrace("Attempting to purge old packages at " . $this->name . '[ID: ' . $this->id . '] type:' . $this->getSTypeName());

        try {
            $client = $this->getClient();
            if ($client == null) {
                throw new Exception("Google client is null!");
            }

            $serviceDrive = new Duplicator_Pro_Google_Service_Drive($client);
            if (($directory_id = DUP_PRO_GDrive_U::get_directory_id($serviceDrive, $this->getStorageFolder())) == null) {
                throw new Exception("Couldn't get directory ID for folder {$this->getStorageFolder()} when performing Google Drive file test");
            }

            $global = DUP_PRO_Global_Entity::getInstance();
            if (($file_list            = DUP_PRO_GDrive_U::get_files_in_directory($serviceDrive, $directory_id)) == null) {
                throw new Exception("ERROR: Couldn't retrieve file list from Google Drive so can purge old packages");
            }

            /** @var Duplicator_Pro_Google_Service_Drive_DriveFile[] */
            $php_files         = array();
            $archive_filenames = array();

            foreach ($file_list as $drive_file) {
                $file_title = $drive_file->getName();
                if (DUP_PRO_STR::endsWith($file_title, "_{$global->installer_base_name}")) {
                    array_push($php_files, $drive_file);
                } elseif (DUP_PRO_STR::endsWith($file_title, '_archive.zip') || DUP_PRO_STR::endsWith($file_title, '_archive.daf')) {
                    array_push($archive_filenames, $drive_file);
                }
            }

            $index                  = 0;
            $num_archives           = count($archive_filenames);
            $num_archives_to_delete = $num_archives - $this->config['max_packages'];
            DUP_PRO_Log::trace(
                "Num zip files to delete=$num_archives_to_delete since there are $num_archives on the drive and max files={$this->config['max_packages']}"
            );

            while ($index < $num_archives_to_delete) {
                $archive_file  = $archive_filenames[$index];
                $archive_title = $archive_file->getName();
                // Matching installer has to be present for us to delete
                if (DUP_PRO_STR::endsWith($archive_title, '_archive.zip')) {
                    $installer_title = str_replace('_archive.zip', "_{$global->installer_base_name}", $archive_title);
                } else {
                    $installer_title = str_replace('_archive.daf', "_{$global->installer_base_name}", $archive_title);
                }

                // Now get equivalent installer
                foreach ($php_files as $installer_file) {
                    if ($installer_title == $installer_file->getName()) {
                        DUP_PRO_Log::trace("Attempting to delete $installer_title from Google Drive");
                        if (DUP_PRO_GDrive_U::delete_file($serviceDrive, $installer_file->getid()) == false) {
                            DUP_PRO_Log::traceError("FAIL: purging Google Drive packages. Error purging old Google Drive file $installer_title");
                        }

                        DUP_PRO_Log::trace("Attempting to delete $archive_title from Google Drive");
                        if (DUP_PRO_GDrive_U::delete_file($serviceDrive, $archive_file->getid()) == false) {
                            DUP_PRO_Log::traceError("FAIL: purging Google Drive packages. Error in purging old Google Drive file $archive_title");
                        }
                        break;
                    }
                }

                $index++;
            }
        } catch (Exception $e) {
            DUP_PRO_Log::infoTraceException($e, "FAIL: purge package for storage " . $this->name . '[ID: ' . $this->id . '] type:' . $this->getStypeName());
            return false;
        }

        DUP_PRO_Log::infoTrace("Purge of old packages at " . $this->name . '[ID: ' . $this->id . "] storage completed.");

        return true;
    }

    /**
     * Retrieves the google client based on storage and auto updates the access token if necessary
     *
     * @return ?Duplicator_Pro_Google_Client
     */
    public function getClient()
    {
        $client = null;

        if (!empty($this->config['token_json'])) {
            $client = DUP_PRO_GDrive_U::get_raw_google_client($this->config['client_number']);
            $client->setAccessToken($this->config['token_json']);
            // Reference on access/refresh token http://stackoverflow.com/questions/9241213/how-to-refresh-token-with-google-api-client
            if ($client->isAccessTokenExpired()) {
                DUP_PRO_Log::trace("Access token is expired so checking token.");
                $client->refreshToken($this->config['refresh_token']);
                // getAccessToken return json encoded value of access token and other stuff
                $token_json = $client->getAccessToken();
                if ($token_json != null) {
                    $this->config['token_json'] = $token_json;
                    DUP_PRO_Log::trace("Retrieved acess token set from google: " . $this->config['token_json']);
                    $this->save();
                } else {
                    DUP_PRO_Log::trace("Can't retrieve access token!");
                    $client = null;
                }
            } else {
                DUP_PRO_Log::trace("Access token ISNT expired");
            }
        } else {
            DUP_PRO_Log::trace("Access token not set!");
        }

        return $client;
    }
}