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/SFTPStorage.php

<?php

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

namespace Duplicator\Models\Storages;

use DUP_PRO_FTP_Chunker;
use DUP_PRO_FTPcURL;
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\Utils\Exceptions\ChunkingTimeoutException;
use Duplicator\Utils\SFTPAdapter;
use Exception;

class SFTPStorage extends AbstractStorageEntity
{
    /** @var null|DUP_PRO_FTPcURL|DUP_PRO_FTP_Chunker */
    protected $client = null;

    /**
     * Get default config
     *
     * @return array<string,scalar>
     */
    protected static function getDefaultCoinfig()
    {
        $config = parent::getDefaultCoinfig();
        $config = array_merge(
            $config,
            [
                'server'               => '',
                'port'                 => 21,
                'username'             => '',
                'password'             => '',
                'private_key'          => '',
                'private_key_password' => '',
                'timeout_in_secs'      => 15,
                'chunking'             => true,
            ]
        );
        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       = [
                'server'               => $this->sftp_server,
                'port'                 => $this->sftp_port,
                'username'             => $this->sftp_username,
                'password'             => $this->sftp_password,
                'private_key'          => $this->sftp_private_key,
                'private_key_password' => $this->sftp_private_key_password,
                'storage_folder'       => '/' . ltrim($this->sftp_storage_folder, '/\\'),
                'max_packages'         => $this->sftp_max_files,
                'timeout_in_secs'      => $this->sftp_timeout_in_secs,
                'chunking'             => !$this->sftp_disable_chunking_mode,
            ];
            // reset old values
            $this->sftp_server                = '';
            $this->sftp_port                  = 21;
            $this->sftp_username              = '';
            $this->sftp_password              = '';
            $this->sftp_private_key           = '';
            $this->sftp_private_key_password  = '';
            $this->sftp_storage_folder        = '';
            $this->sftp_max_files             = 10;
            $this->sftp_timeout_in_secs       = 15;
            $this->sftp_disable_chunking_mode = false;
        }
    }

    /**
     * Return the storage type
     *
     * @return int
     */
    public static function getSType()
    {
        return 5;
    }

    /**
     * Returns the FontAwesome storage type icon.
     *
     * @return string Returns the font-awesome icon
     */
    public static function getStypeIcon()
    {
        return '<i class="fas fa-network-wired fa-fw"></i>';
    }

    /**
     * Returns the storage type name.
     *
     * @return string
     */
    public static function getStypeName()
    {
        return __('SFTP', 'duplicator-pro');
    }

    /**
     * Get storage location string
     *
     * @return string
     */
    public function getLocationString()
    {
        return $this->config['server'] . ":" . $this->config['port'];
    }

    /**
     * Get priority, used to sort storages.
     * 100 is neutral value, 0 is the highest priority
     *
     * @return int
     */
    public static function getPriority()
    {
        return 90;
    }

    /**
     * Check if storage is supported
     *
     * @return bool
     */
    public static function isSupported()
    {
        return extension_loaded('gmp');
    }

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

        return sprintf(
            _x(
                'SFTP requires the %1$sgmp extension%2$s. Please contact your host to install.',
                '1: <a> tag, 2: </a> tag',
                'duplicator-pro'
            ),
            '<a href="http://php.net/manual/en/book.gmp.php" target="_blank">',
            '</a>'
        );
    }

    /**
     * Check if storage is valid
     *
     * @return bool Return true if storage is valid and ready to use, false otherwise
     */
    public function isValid()
    {
        if (strlen($this->config['server']) < 1) {
            return false;
        }
        if (strlen($this->config['username']) < 1) {
            return false;
        }
        if (strlen($this->config['storage_folder']) < 1) {
            return false;
        }
        if ($this->config['port'] < 0) {
            return false;
        }
        return true;
    }

    /**
     * Get action key text
     *
     * @param string $key Key name (action, pending, failed, cancelled, success)
     *
     * @return string
     */
    protected function getActionKeyText($key)
    {
        switch ($key) {
            case 'action':
                return sprintf(
                    __('Transferring to SFTP server %1$s in folder:<br/> <i>%2$s</i>', "duplicator-pro"),
                    $this->config['server'],
                    $this->getStorageFolder()
                );
            case 'pending':
                return sprintf(
                    __('Transfer to SFTP server %1$s in folder %2$s is pending', "duplicator-pro"),
                    $this->config['server'],
                    $this->getStorageFolder()
                );
            case 'failed':
                return sprintf(
                    __('Failed to transfer to SFTP server %1$s in folder %2$s', "duplicator-pro"),
                    $this->config['server'],
                    $this->getStorageFolder()
                );
            case 'cancelled':
                return sprintf(
                    __('Cancelled before could transfer to SFTP server:<br/>%1$s in folder %2$s', "duplicator-pro"),
                    $this->config['server'],
                    $this->getStorageFolder()
                );
            case 'success':
                return sprintf(
                    __('Transferred package to SFTP server:<br/>%1$s in folder %2$s', "duplicator-pro"),
                    $this->config['server'],
                    $this->getStorageFolder()
                );
            default:
                throw new Exception('Invalid key');
        }
    }

    /**
     * List quick view
     *
     * @param bool $echo Echo or return
     *
     * @return string
     */
    public function getListQuickView($echo = true)
    {
        ob_start();
        ?>
        <div>
            <label><?php esc_html_e('Server', 'duplicator-pro'); ?>:</label>
            <?php echo esc_html($this->config['server']); ?>: <?php echo intval($this->config['port']);  ?>  <br/>
            <label><?php esc_html_e('Location', 'duplicator-pro') ?>:</label>
            <?php echo $this->getHtmlLocationLink(); ?>
        </div>
        <?php
        if ($echo) {
            ob_end_flush();
            return '';
        } else {
            return ob_get_clean();
        }
    }

    /**
     * Render form config fields
     *
     * @param bool $echo Echo or return
     *
     * @return string
     */
    public function renderConfigFields($echo = true)
    {
        return TplMng::getInstance()->render(
            'admin_pages/storages/configs/sftp',
            [
                'storage'       => $this,
                'server'        => $this->config['server'],
                'port'          => $this->config['port'],
                'username'      => $this->config['username'],
                'password'      => $this->config['password'],
                'privateKey'    => $this->config['private_key'],
                'privateKeyPwd' => $this->config['private_key_password'],
                'storageFolder' => $this->config['storage_folder'],
                'maxPackages'   => $this->config['max_packages'],
                'timeout'       => $this->config['timeout_in_secs'],
                'chunking'      => $this->config['chunking'],
            ],
            $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, 'sftp_max_files', 10);
        $this->config['server']       = SnapUtil::sanitizeTextInput(SnapUtil::INPUT_REQUEST, 'sftp_server', '');
        $this->config['port']         = SnapUtil::sanitizeIntInput(SnapUtil::INPUT_REQUEST, 'sftp_port', 10);
        $this->config['username']     = SnapUtil::sanitizeTextInput(SnapUtil::INPUT_REQUEST, 'sftp_username', '');
        $password                     = SnapUtil::sanitizeTextInput(SnapUtil::INPUT_REQUEST, 'sftp_password', '');
        $password2                    = SnapUtil::sanitizeTextInput(SnapUtil::INPUT_REQUEST, 'sftp_password2', '');
        $this->config['private_key']  = SnapUtil::sanitizeDefaultInput(SnapUtil::INPUT_REQUEST, 'sftp_private_key', '');
        $keyPassword                  = SnapUtil::sanitizeTextInput(SnapUtil::INPUT_REQUEST, 'sftp_private_key_password', '');
        $keyPassword2                 = SnapUtil::sanitizeTextInput(SnapUtil::INPUT_REQUEST, 'sftp_private_key_password2', '');
        if (strlen($password) > 0) {
            if ($password !== $password2) {
                $message = __('Passwords do not match', 'duplicator-pro');
                return false;
            }
            $this->config['password'] = $password;
        } elseif (strlen($keyPassword) > 0) {
            if ($keyPassword !== $keyPassword2) {
                $message = __('Priva key Passwords do not match', 'duplicator-pro');
                return false;
            }
            $this->config['private_key_password'] = $keyPassword;
        }
        $this->config['storage_folder']  = self::getSanitizedInputFolder('_sftp_storage_folder', 'add');
        $this->config['timeout_in_secs'] = max(10, SnapUtil::sanitizeIntInput(SnapUtil::INPUT_REQUEST, 'sftp_timeout_in_secs', 15));
        $this->config['chunking']        = SnapUtil::sanitizeBoolInput(SnapUtil::INPUT_REQUEST, 'sftp_chunking_mode', false);

        $message = sprintf(
            __('SFTP Storage Updated - Server %1$s, Folder %2$s was created.', 'duplicator-pro'),
            $this->config['server'],
            $this->getStorageFolder()
        );
        return true;
    }

    /**
     * Close connection
     *
     * @return void
     */
    protected function closeConnection()
    {
        if ($this->client) {
            if (!$this->config['use_curl']) {
                $this->client->close();
            }
            $this->client = null;
        }
    }

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

        $storage_folder       = $this->config['storage_folder'];
        $server               = $this->config['server'];
        $port                 = $this->config['port'];
        $username             = $this->config['username'];
        $password             = $this->config['password'];
        $private_key          = $this->config['private_key'];
        $private_key_password = $this->config['private_key_password'];

        $result = true;

        try {
            $source_filepath = false;
            // -- Store the temp file --
            $this->testLog->addMessage(__('Attempting to create a temp file', 'duplicator-pro'));
            DUP_PRO_Log::trace("Attempting to create a temp file");
            $source_filepath = tempnam(sys_get_temp_dir(), 'DUP');

            if ($source_filepath === false) {
                DUP_PRO_Log::trace("Couldn't create the temp file for the SFTP send test");
                throw new Exception(__("Couldn't create the temp file for the SFTP send test", 'duplicator-pro'));
            }

            $basename = basename($source_filepath);
            $this->testLog->addMessage(sprintf(__('Created a temp file "%1$s"', 'duplicator-pro'), $source_filepath));
            DUP_PRO_Log::trace("Created a temp file $source_filepath");

            if (DUP_PRO_STR::startsWith($storage_folder, '/') == false) {
                $storage_folder = '/' . $storage_folder;
            }

            if (DUP_PRO_STR::endsWith($storage_folder, '/') == false) {
                $storage_folder = $storage_folder . '/';
            }

            $sFtpAdapter = new SFTPAdapter($server, $port, $username, $password, $private_key, $private_key_password);
            $sFtpAdapter->setMessages($this->testLog);

            if (!$sFtpAdapter->connect()) {
                DUP_PRO_Log::trace("Couldn't connect to sftp server while doing the SFTP send test");
                throw new Exception(__("Couldn't connect to sftp server while doing the SFTP send test", 'duplicator-pro'));
            }

            $this->testLog->addMessage(sprintf(__('Checking if remote storage folder "%1$s" already exists', 'duplicator-pro'), $storage_folder));
            DUP_PRO_Log::trace("Checking if remote storage folder '$storage_folder' already exists");
            if (!$sFtpAdapter->fileExists($storage_folder)) {
                DUP_PRO_Log::trace("The remote storage folder '$storage_folder' does not exist, attempting to create it");
                $this->testLog->addMessage(
                    sprintf(
                        __('The remote storage folder "%1$s" does not exist, attempting to create it', 'duplicator-pro'),
                        $storage_folder
                    )
                );
                $storage_folder = $sFtpAdapter->mkDirRecursive($storage_folder);
                if (!$sFtpAdapter->fileExists($storage_folder)) {
                    DUP_PRO_Log::trace("The SFTP connection is working fine, but the directory can't be created.");
                    throw new Exception(__("The SFTP connection is working fine, but the directory can't be created.", 'duplicator-pro'));
                } else {
                    DUP_PRO_Log::trace("The remote storage folder is created successfully");
                    $this->testLog->addMessage(__('The remote storage folder is created successfully', 'duplicator-pro'));
                }
            } else {
                DUP_PRO_Log::trace("The remote storage folder already exists");
                $this->testLog->addMessage(__('The remote storage folder already exists', 'duplicator-pro'));
            }

            // Try to upload a test file
            $this->testLog->addMessage(__('Attempting to upload the test file', 'duplicator-pro'));
            DUP_PRO_Log::trace("Attempting to upload the test file");
            $continueUpload = true;
            try {
                if (!$sFtpAdapter->put($storage_folder . $basename, $source_filepath)) {
                    $continueUpload = false;
                    $this->testLog->addMessage(
                        __(
                            'Error uploading test file, maybe the directory does not exist or you have no write permissions',
                            'duplicator-pro'
                        )
                    );
                    DUP_PRO_Log::trace("Error uploading test file, maybe the directory does not exist or you have no write permissions.");
                    $message = __('Error uploading test file.', 'duplicator-pro');
                }
            } catch (ChunkingTimeoutException $e) {
                $continueUpload = true;
            }
            if ($continueUpload) {
                $result = true;
                $this->testLog->addMessage(__('Test file uploaded successfully', 'duplicator-pro'));
                DUP_PRO_Log::trace("Test file uploaded successfully.");
                $message = __('The connection was successful.', 'duplicator-pro');
                $this->testLog->addMessage(__('Attempting to delete the remote test file', 'duplicator-pro'));
                DUP_PRO_Log::trace("Attempting to delete the remote test file");
                if ($sFtpAdapter->delete($storage_folder . $basename)) {
                    $this->testLog->addMessage(__('Remote test file deleted successfully', 'duplicator-pro'));
                    DUP_PRO_Log::trace("Remote test file deleted successfully.");
                } else {
                    $this->testLog->addMessage(__('Couldn\'t delete the remote test file', 'duplicator-pro'));
                    DUP_PRO_Log::trace("Couldn't delete the remote test file.");
                }
            }
        } catch (Exception $e) {
            $this->testLog->addMessage($e->getMessage());
            DUP_PRO_Log::trace($e->getMessage());
            $message = $e->getMessage();
            $result  = false;
        }

        if (file_exists($source_filepath)) {
            $this->testLog->addMessage(sprintf(__('Attempting to delete local temp file "%1$s"', 'duplicator-pro'), $source_filepath));
            if (unlink($source_filepath) == false) {
                $this->testLog->addMessage(sprintf(__('Could not delete the temp file "%1$s"', 'duplicator-pro'), $source_filepath));
                DUP_PRO_Log::trace("Could not delete the temp file $source_filepath");
            } else {
                $this->testLog->addMessage(sprintf(__('Deleted temp file "%1$s"', 'duplicator-pro'), $source_filepath));
                DUP_PRO_Log::trace("Deleted temp file $source_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);

        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('SFTP storage failed flag ($upload_info->failed) has been already set.');
            $package->update();
            return;
        }

        $sFtpAdapter = null;

        try {
            $storage_folder       = $this->config['storage_folder'];
            $server               = $this->config['server'];
            $port                 = $this->config['port'];
            $username             = $this->config['username'];
            $password             = $this->config['password'];
            $private_key          = $this->config['private_key'];
            $private_key_password = $this->config['private_key_password'];
            $chunking_mode        = $this->config['chunking'];

            if (DUP_PRO_STR::startsWith($storage_folder, '/') == false) {
                $storage_folder = '/' . $storage_folder;
            }

            if (DUP_PRO_STR::endsWith($storage_folder, '/') == false) {
                $storage_folder = $storage_folder . '/';
            }

            $sFtpAdapter = new SFTPAdapter($server, $port, $username, $password, $private_key, $private_key_password);

            if ($sFtpAdapter->connect() === false) {
                DUP_PRO_Log::trace("SFTP connection fail");
                throw new Exception('SFTP connection fail');
            }
            if (!$sFtpAdapter->fileExists($storage_folder)) {
                DUP_PRO_Log::trace("Attempting to create $storage_folder via SFTP");
                $sFtpAdapter->mkDirRecursive($storage_folder);
            }

            if ($upload_info->copied_installer == false) {
                $source_filepath    = $source_installer_filepath;
                $basename           = $package->Installer->getInstallerName();
                $continueWithUpload = true;
                try {
                    $sFtpAdapter->startChunkingTimer();
                    if (!$sFtpAdapter->put($storage_folder . $basename, $source_filepath)) {
                        $upload_info->failed = true;
                        $continueWithUpload  = false;
                        DUP_PRO_Log::infoTrace("FAIL: installer $source_installer_filepath upload to SFTP $storage_folder.");
                    }
                } catch (ChunkingTimeoutException $e) {
                    $continueWithUpload = true;
                }

                if ($continueWithUpload) {
                    DUP_PRO_Log::infoTrace("SUCCESS: installer upload to SFTP $storage_folder.");
                    $upload_info->progress         = 5;
                    $upload_info->copied_installer = true;
                }
                // 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 SFTP $this->name so skipping");
            }

            if ($upload_info->copied_archive == false) {
                if ($chunking_mode) {
                    //Make sure time threshold not exceed the server maximum execution time
                    $time_threshold = $this->config['timeout_in_secs'];
                    DUP_PRO_Log::trace('SFTP chunking mode is enabled, so setting the time_threshold=' . $time_threshold);
                } else {
                    DUP_PRO_Log::trace('SFTP chunking mode is disabled.');
                    $time_threshold = -1;
                }

                $source_filepath = $source_archive_filepath;
                $basename        = basename($source_filepath);

                $continueWithUpload = false;
                try {
                    $continueWithUpload = true;
                    $sFtpAdapter->startChunkingTimer($time_threshold);
                    $upload_info->archive_offset = $sFtpAdapter->filesize($storage_folder . $basename);
                    if (!$upload_info->archive_offset) {
                        $upload_info->archive_offset = 0;
                    }
                    DUP_PRO_Log::trace("At start of iteration archive offset is: " . $upload_info->archive_offset);
                    if (!$sFtpAdapter->put($storage_folder . $basename, $source_filepath, $upload_info->archive_offset)) {
                        $upload_info->failed = true;
                        $continueWithUpload  = false;
                        DUP_PRO_Log::infoTrace("FAIL: archive upload to SFTP.");
                    }
                } catch (ChunkingTimeoutException $e) {
                    $continueWithUpload = true;
                }

                // For some reason $sFtpAdapter->filesize($storage_folder . $basename) does not work here,
                // so there is no way to know new archive offset after call to put command.

                if ($continueWithUpload) {
                    $file_size             = filesize($source_filepath);
                    $upload_info->progress = max(
                        5,
                        DUP_PRO_U::percentage($upload_info->archive_offset, $file_size, 0)
                    );

                    DUP_PRO_Log::infoTrace(
                        "At start of iteration, archive upload offset: " . $upload_info->archive_offset .
                        " [File size: $file_size] [Upload progress: $upload_info->progress%]"
                    );

                    if ($upload_info->progress >= 100) {
                        $upload_info->copied_archive = true;
                        DUP_PRO_Log::infoTrace("SUCCESS: archive upload to SFTP.");
                        $this->purgeOldPackages();
                    }
                }

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

            if ($upload_info->failed) {
                $source_filepath = $source_archive_filepath;
                $basename        = basename($source_filepath);
                $sFtpAdapter->delete($storage_folder . $basename);

                $source_filepath = $source_installer_filepath;
                $basename        = basename($source_filepath);
                $sFtpAdapter->delete($storage_folder . $basename);
            } else {
                $upload_info->failure_count = 0;
            }
        } catch (Exception $e) {
            $upload_info->increase_failure_count();
            DUP_PRO_Log::trace("Exception caught copying package $package->Name to $storage_folder. " . $e->getMessage());
        }

        if ($upload_info->failed) {
            DUP_PRO_Log::infoTrace('SFTP 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();
    }

    /**
     * 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 {
            $storage_folder       = $this->config['storage_folder'];
            $server               = $this->config['server'];
            $port                 = $this->config['port'];
            $username             = $this->config['username'];
            $password             = $this->config['password'];
            $private_key          = $this->config['private_key'];
            $private_key_password = $this->config['private_key_password'];

            if (DUP_PRO_STR::startsWith($storage_folder, '/') == false) {
                $storage_folder = '/' . $storage_folder;
            }

            if (DUP_PRO_STR::endsWith($storage_folder, '/') == false) {
                $storage_folder = $storage_folder . '/';
            }

            $sFtpAdapter = new SFTPAdapter($server, $port, $username, $password, $private_key, $private_key_password);
            if (!$sFtpAdapter->connect()) {
                throw new Exception('Connction fail');
            }

            $storage_folder = $storage_folder;
            if (DUP_PRO_STR::startsWith($storage_folder, '/') == false) {
                $storage_folder = '/' . $storage_folder;
            }
            if (DUP_PRO_STR::endsWith($storage_folder, '/') == false) {
                $storage_folder = $storage_folder . '/';
            }
            $global    = DUP_PRO_Global_Entity::getInstance();
            $file_list = $sFtpAdapter->filesList($storage_folder);
            $file_list = array_diff($file_list, array(".", ".."));
            if (empty($file_list)) {
                DUP_PRO_Log::traceError(
                    "FAIL: purging SFTP packages. Problems making SFTP connection, Purging old packages not possible. Error retrieving file list for " .
                    $server . ":" . $port . " Storage Dir: " . $storage_folder
                );
            } else {
                $valid_file_list = array();
                foreach ($file_list as $file_name) {
                    DUP_PRO_Log::trace("considering filename {$file_name}");
                    if (DUP_PRO_Package::get_timestamp_from_filename($file_name) !== false) {
                        $valid_file_list[] = $file_name;
                    }
                }

                DUP_PRO_Log::traceObject('valid file list', $valid_file_list);
                try {
                    // Sort list by the timestamp associated with it
                    usort($valid_file_list, array(DUP_PRO_Package::class, 'compare_package_filenames_by_date'));
                } catch (Exception $e) {
                    DUP_PRO_Log::trace("Sort error when attempting to purge old FTP files");
                    return false;
                }

                $php_files         = array();
                $archive_filepaths = array();
                foreach ($valid_file_list as $file_name) {
                    $file_path = "$storage_folder/$file_name";
                    // just look for the archives and delete only if has matching _installer
                    if (DUP_PRO_STR::endsWith($file_path, "_{$global->installer_base_name}")) {
                        array_push($php_files, $file_path);
                    } elseif (DUP_PRO_STR::endsWith($file_path, '_archive.zip') || DUP_PRO_STR::endsWith($file_path, '_archive.daf')) {
                        array_push($archive_filepaths, $file_path);
                    }
                }

                $index                  = 0;
                $num_archives           = count($archive_filepaths);
                $num_archives_to_delete = $num_archives - $this->config['max_packages'];
                DUP_PRO_Log::trace("Num archives to delete=$num_archives_to_delete");
                while ($index < $num_archives_to_delete) {
                    $archive_filepath = $archive_filepaths[$index];
                    // Matching installer has to be present for us to delete
                    if (DUP_PRO_STR::endsWith($archive_filepath, '_archive.zip')) {
                        $installer_filepath = str_replace('_archive.zip', "_{$global->installer_base_name}", $archive_filepath);
                    } else {
                        $installer_filepath = str_replace('_archive.daf', "_{$global->installer_base_name}", $archive_filepath);
                    }

                    if (in_array($installer_filepath, $php_files)) {
                        DUP_PRO_Log::trace("$installer_filepath in array so deleting installer and archive");
                        $sFtpAdapter->delete($installer_filepath);
                        $sFtpAdapter->delete($archive_filepath);
                    } else {
                        DUP_PRO_Log::trace("$installer_filepath not in array so NOT deleting");
                    }

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