<?php

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

use Duplicator\Addons\ProBase\License\License;
use Duplicator\Core\MigrationMng;
use Duplicator\Core\Models\AbstractEntitySingleton;
use Duplicator\Libs\Snap\SnapIO;
use Duplicator\Libs\Snap\SnapLog;
use Duplicator\Libs\Snap\SnapURL;
use Duplicator\Libs\Snap\SnapWP;
use Duplicator\Libs\Snap\SnapUtil;
use Duplicator\Models\DynamicGlobalEntity;
use Duplicator\Models\Storages\Local\LocalStorage;
use Duplicator\Models\Storages\StoragesUtil;
use Duplicator\Utils\Crypt\CryptBlowfish;
use Duplicator\Utils\Email\EmailSummaryBootstrap;
use Duplicator\Utils\Email\EmailSummary;
use Duplicator\Utils\ZipArchiveExtended;
use VendorDuplicator\Amk\JsonSerialize\JsonSerialize;
use Duplicator\Utils\GroupOptions;
use Duplicator\Utils\PathUtil;
use Duplicator\Utils\Settings\ModelMigrateSettingsInterface;
use Duplicator\Utils\UsageStatistics\CommStats;

abstract class DUP_PRO_Dropbox_Transfer_Mode
{
    const Unconfigured = -1;
    const Disabled     = 0;
    const cURL         = 1;
    const FOpen_URL    = 2;
}

abstract class DUP_PRO_Google_Drive_Transfer_Mode
{
    const Unconfigured = -1;
    const Auto         = 0;
    const FOpen_URL    = 1;
}

abstract class DUP_PRO_Thread_Lock_Mode
{
    const Flock    = 0;
    const SQL_Lock = 1;
}

abstract class DUP_PRO_Sql_Lock_Check
{
    const Sql_Success = 1;
    const Sql_Fail    = -1;
}

abstract class DUP_PRO_Email_Build_Mode
{
    const No_Emails           = 0;
    const Email_On_Failure    = 1;
    const Email_On_All_Builds = 2;
}

abstract class DUP_PRO_Archive_Build_Mode
{
    const Unconfigured = -1;
    const Shell_Exec   = 1;
    const ZipArchive   = 2;
    const DupArchive   = 3;
}

class DUP_PRO_Server_Load_Reduction
{
    const None  = 0;
    const A_Bit = 1;
    const More  = 2;
    const A_Lot = 3;

    /**
     * @param int $reduction ENUM::Server_Load_Reduction
     *
     * @return int<0,max> microseconds
     */
    public static function microseconds_from_reduction($reduction)
    {
        switch ($reduction) {
            case self::A_Bit:
                return 20;
            case self::More:
                return 100;
            case self::A_Lot:
                return 500;
            case self::None:
            default:
                return 0;
        }
    }
}

abstract class DUP_PRO_ZipArchive_Mode
{
    const Multithreaded = 0;
    const SingleThread  = 1;
}

class DUP_PRO_Global_Entity extends AbstractEntitySingleton implements ModelMigrateSettingsInterface
{
    const INSTALLER_NAME_MODE_WITH_HASH = 'withhash';
    const INSTALLER_NAME_MODE_SIMPLE    = 'simple';

    const CLEANUP_HOOK                  = 'dup_pro_cleanup_hook';
    const CLEANUP_INTERVAL_NAME         = 'dup_pro_custom_interval';
    const CLEANUP_FILE_TIME_DELAY       = 81000; // In seconds, 22.5 hours
    const CLEANUP_EMAIL_NOTICE_INTERVAL = 24; // In hours

    const CLEANUP_MODE_OFF  = 0;
    const CLEANUP_MODE_MAIL = 1;
    const CLEANUP_MODE_AUTO = 2;

    const UNINSTALL_PACKAGE_OPTION_KEY  = 'duplicator_pro_uninstall_package';
    const UNINSTALL_SETTINGS_OPTION_KEY = 'duplicator_pro_uninstall_settings';

    const INPUT_MYSQLDUMP_OPTION_PREFIX = 'package_mysqldump_';

    //GENERAL
    /** @var bool */
    public $uninstall_settings = false;
    /** @var bool */
    public $uninstall_packages = false;
    /** @var bool */
    public $crypt = true;
    /** @var string email summary frequency */
    private $email_summary_frequency = EmailSummary::SEND_FREQ_WEEKLY;
    /** @var string[] email summary recipients */
    private $email_summary_recipients = [];
    /** @var bool */
    private $usageTracking = true;
    /** @var bool if true AM Notifications are enabled */
    private $amNotices = true;
    //PACKAGES::Visual
    /** @var bool */
    public $package_mysqldump = false;
    /** @var string */
    public $package_mysqldump_path = '';
    /** @var int<0, 1> ENUM */
    public $package_phpdump_mode = DUP_PRO_DB::PHPDUMP_MODE_MULTI;
    /** @var int<0, max> */
    public $package_mysqldump_qrylimit = DUP_PRO_Constants::DEFAULT_MYSQL_DUMP_CHUNK_SIZE;
    /** @var GroupOptions[] */
    private $packageMysqldumpOptions = [];
    /** @var int<-1, 3> ENUM */
    public $archive_build_mode = DUP_PRO_Archive_Build_Mode::Unconfigured;
    /** @var bool */
    public $archive_compression = true;
    /** @var bool */
    public $ziparchive_validation = false;
    /** @var int<0, 1> ENUM */
    public $ziparchive_mode = DUP_PRO_ZipArchive_Mode::Multithreaded;
    /** @var int<0, max> */
    public $ziparchive_chunk_size_in_mb = DUP_PRO_Constants::DEFAULT_ZIP_ARCHIVE_CHUNK;
    /** @var bool */
    public $homepath_as_abspath = false;
    //PACKAGES::Basic::Processing
    /** @var int<0, 3> ENUM */
    public $server_load_reduction = DUP_PRO_Server_Load_Reduction::None;
    /** @var int<0, max> */
    public $max_package_runtime_in_min = DUP_PRO_Constants::DEFAULT_MAX_PACKAGE_RUNTIME_IN_MIN;
    /** @var int<0, max> */
    public $php_max_worker_time_in_sec = DUP_PRO_Constants::DEFAULT_MAX_WORKER_TIME;
    //PACKAGES::Basic::Cleanup
    /** @var int<0, 2> ENUM */
    public $cleanup_mode = self::CLEANUP_MODE_OFF;
    /** @var string */
    public $cleanup_email = '';
    /** @var int<0, max> */
    public $auto_cleanup_hours = 24;
    //PACKAGES::Adanced
    /** @var int<0, 1> ENUM */
    public $lock_mode = DUP_PRO_Thread_Lock_Mode::SQL_Lock;
    /** @var string */
    public $ajax_protocol = '';
    /** @var string */
    public $custom_ajax_url = '';
    /** @var bool */
    public $clientside_kickoff = false;
    /** @var bool */
    public $basic_auth_enabled = false;
    /** @var string */
    public $basic_auth_user = '';  // Not actively used but required for upgrade
    /** @var string */
    public $basic_auth_password = '';
    /** @var string ENUM */
    public $installer_name_mode = self::INSTALLER_NAME_MODE_SIMPLE;
    /** @var string */
    public $installer_base_name = DUP_PRO_Installer::DEFAULT_INSTALLER_FILE_NAME_WITHOUT_HASH;
    /** @var int<0, max> */
    public $chunk_size = 2048;
    /** @var bool */
    public $skip_archive_scan = false;
    //SCHEDULES
    /** @var int<0, 2> ENUM */
    public $send_email_on_build_mode = DUP_PRO_Email_Build_Mode::Email_On_Failure;
    /** @var string */
    public $notification_email_address = '';
    //STORAGE
    /** @var bool */
    public $storage_htaccess_off = false;
    /** @var int<0, max> */
    public $max_storage_retries = 10;
    /**
     * Used on old versions before 4.5.13, mantained for upgrade. Remove on 4.5.14 or later
     *
     * @deprecated since 4.5.13
     *
     * @var int<0, max>
     **/
    public $max_default_store_files = 20;
    /**
     * Used on old versions before 4.5.13, mantained for upgrade. Remove on 4.5.14 or later
     *
     * @deprecated since 4.5.13
     *
     * @var bool
     **/
    public $purge_default_package_record = false;

    /**
     * @deprecated 4.5.15 use {@see DynamicEntity::get()} instead
     * @var        null|int<0, max>
     */
    public $dropbox_upload_chunksize_in_kb = 2000;

    /**
     * @deprecated 4.5.15 use {@see DynamicEntity::get()} instead
     * @var        null|int<-1, 2> ENUM
     */
    public $dropbox_transfer_mode = DUP_PRO_Dropbox_Transfer_Mode::Unconfigured;

    /**
     * @deprecated 4.5.15 use {@see DynamicEntity::get()} instead
     * @var        null|int<0, max>
     */
    public $gdrive_upload_chunksize_in_kb = 1024;  // Not exposed through the UI (yet)

    /**
     * @deprecated 4.5.15 use {@see DynamicEntity::get()} instead
     * @var        null|int<-1, 1> ENUM
     */
    public $gdrive_transfer_mode = DUP_PRO_Google_Drive_Transfer_Mode::Auto;

    /**
     * @deprecated 4.5.15 use {@see DynamicEntity::get()} instead
     * @var        null|int<0, max>
     */
    public $s3_upload_part_size_in_kb = 6000;

    /**
     * @deprecated 4.5.15 use {@see DynamicEntity::get()} instead
     * @var        null|int<0, max>
     */
    public $onedrive_upload_chunksize_in_kb = DUPLICATOR_PRO_ONEDRIVE_UPLOAD_CHUNK_DEFAULT_SIZE_IN_KB;

    /**
     * @deprecated 4.5.15 use {@see DynamicEntity::get()} instead
     * @var        null|int<0, max>
     */
    public $local_upload_chunksize_in_MB = LocalStorage::LOCAL_STORAGE_CHUNK_SIZE_IN_MB;

    /** @var int[] */
    protected $manual_mode_storage_ids = [];
    //LICENSING
    /** @var int<0, 2> License Visibility ENUM */
    public $license_key_visible = License::VISIBILITY_ALL;
    /** @var string */
    public $lkp = ''; // Not actively used but required for upgrade
    //UPDATE CACHING
    /** @var int<0, max> */
    public $last_system_check_timestamp = 0;
    /** @var int<0, max> */
    public $initial_activation_timestamp = 0;
    /** @var bool */
    public $ssl_useservercerts = true;
    /** @var bool */
    public $ssl_disableverify = true;
    /** @var int<0, max> */
    public $import_chunk_size = DUPLICATOR_PRO_DEFAULT_CHUNK_UPLOAD_SIZE; // in KB, 0 no chunk
    /** @var string */
    public $import_custom_path = '';
    /** @var bool */
    public $ipv4_only = false;
    /** @var bool */
    public $unhook_third_party_js = false;
    /** @var bool */
    public $unhook_third_party_css = false;
    /** @var string if empty custom path is disabled */
    private $recoveryCustomPath = '';

    /**
     * Class constructor
     */
    protected function __construct()
    {
        $this->packageMysqldumpOptions = $this->getDefaultMysqlDumpOptions();
    }

    /**
     * Return entity type identifier
     *
     * @return string
     */
    public static function getType()
    {
        return 'DUP_PRO_Global_Entity';
    }

    /**
     * Will be called, automatically, when Serialize
     *
     * @return array<string, mixed>
     */
    public function __serialize() // phpcs:ignore PHPCompatibility.FunctionNameRestrictions.NewMagicMethods.__serializeFound
    {
        update_option(self::UNINSTALL_PACKAGE_OPTION_KEY, $this->uninstall_packages);
        update_option(self::UNINSTALL_SETTINGS_OPTION_KEY, $this->uninstall_settings);

        $data = JsonSerialize::serializeToData($this, JsonSerialize::JSON_SKIP_MAGIC_METHODS |  JsonSerialize::JSON_SKIP_CLASS_NAME);
        if ($this->crypt && strlen($this->basic_auth_password)) {
            $data['basic_auth_password'] = CryptBlowfish::encrypt($this->basic_auth_password);
        }
        if ($this->crypt && strlen($this->lkp)) {
            $data['lkp'] = CryptBlowfish::encrypt($this->lkp);
        }
        return $data;
    }

    /**
     * Serialize
     *
     * Wakeup method.
     *
     * @return void
     */
    public function __wakeup()
    {
        // fix boolean value from old version
        $this->license_key_visible = (int) $this->license_key_visible;

        $loadedOptionNames = [];
        foreach ($this->packageMysqldumpOptions as $index => $data) {
            $this->packageMysqldumpOptions[$index] = GroupOptions::getObjectFromArray($data); // @phpstan-ignore-line
            $loadedOptionNames[]                   = $this->packageMysqldumpOptions[$index]->getOptionName();
        }
        foreach ($this->getDefaultMysqlDumpOptions() as $defOpt) {
            if (in_array($defOpt->getOptionName(), $loadedOptionNames)) {
                continue;
            }
            $this->packageMysqldumpOptions[] = $defOpt;
        }

        if ($this->crypt && strlen($this->basic_auth_password)) {
            $this->basic_auth_password = CryptBlowfish::decrypt($this->basic_auth_password);
        }

        if ($this->crypt && strlen($this->lkp)) {
            $this->lkp = CryptBlowfish::decrypt($this->lkp);
        }
    }

    /**
     * Return default options
     *
     * @return GroupOptions[]
     */
    private function getDefaultMysqlDumpOptions()
    {
        return [
            new GroupOptions('quick', self::INPUT_MYSQLDUMP_OPTION_PREFIX, false),
            new GroupOptions('extended-insert', self::INPUT_MYSQLDUMP_OPTION_PREFIX, false),
            new GroupOptions('routines', self::INPUT_MYSQLDUMP_OPTION_PREFIX, true),
            new GroupOptions('disable-keys', self::INPUT_MYSQLDUMP_OPTION_PREFIX, false),
            new GroupOptions('compact', self::INPUT_MYSQLDUMP_OPTION_PREFIX, false),
        ];
    }

    /**
     * This function is called on first istance of singletion object
     * Can be used to set dynamic properties values
     *
     * @return void
     */
    protected function firstIstanceInit()
    {
        $result = $this->reset(
            [],
            [
                __CLASS__,
                'getDefaultPropInitVal',
            ],
            function () {
                $this->set_build_mode();
            }
        );
        if ($result === false) {
            throw new Exception('Can\'t reset the user settings');
        }
    }

    /**
     * Return default prop val by system config
     *
     * @param string $name prop nam
     * @param mixed  $val  prop val
     *
     * @return mixed
     */
    protected static function getDefaultPropInitVal($name, $val)
    {
        switch ($name) {
            case 'cleanup_email':
                return get_option('admin_email');
            case 'lock_mode':
                return self::getDefaultLockType();
            case 'ajax_protocol':
                return strtolower(parse_url(network_admin_url(), PHP_URL_SCHEME));
            case 'php_max_worker_time_in_sec':
                // Default is just a bit under the .7 max
                return min(
                    floor(0.7 * SnapUtil::phpIniGet("max_execution_time", 30, 'int')),
                    DUP_PRO_Constants::DEFAULT_MAX_WORKER_TIME
                );
            case 'crypt':
                $test_str      = 'aaa';
                $encrypted_str = CryptBlowfish::encrypt($test_str);
                $decrypted_str = CryptBlowfish::decrypt($encrypted_str);
                return ($test_str == $decrypted_str) ? true : false;
            case 'custom_ajax_url':
                return admin_url('admin-ajax.php');
            case 'email_summary_recipients':
                return EmailSummary::getDefaultRecipients();
        }
        return $val;
    }

    /**
     * Reset default values
     *
     * @return bool
     */
    public function resetUserSettings()
    {
        try {
            $result = $this->reset(
                [
                    'manual_mode_storage_ids',
                    'license_key_visible',
                    'lkp',
                    'last_system_check_timestamp',
                    'initial_activation_timestamp',
                ],
                [
                    __CLASS__,
                    'getDefaultPropInitVal',
                ],
                function () {
                    $this->set_build_mode();
                }
            );

            if ($result == false) {
                throw new Exception('Can\'t reset global entity values');
            }

            $sglobal = DUP_PRO_Secure_Global_Entity::getInstance();
            if ($sglobal->save() == false) {
                throw new Exception('Can\'t save secure global');
            }
        } catch (Exception $e) {
            DUP_PRO_Log::traceError('Reset user settings error mrg: ' . $e->getMessage());
            return false;
        }

        return true;
    }

    /**
     * Get usage tracking
     *
     * @return bool
     */
    public function getUsageTracking()
    {
        return $this->usageTracking;
    }

    /**
     * Set usage tracking
     *
     * @param bool $value value
     *
     * @return void
     */
    public function setUsageTracking($value)
    {
        if (DUPLICATOR_USTATS_DISALLOW) { // @phpstan-ignore-line
            // If usagfe tracking is hardcoded disabled, don't change the setting value
            return;
        }

        $value               = (bool) $value;
        $oldValue            = $this->usageTracking;
        $this->usageTracking = $value;

        if ($value == false && $oldValue != $value) {
            CommStats::disableUsageTracking();
        }
    }

    /**
     * Return recovery custom path
     *
     * @return string
     */
    public function getRecoveryCustomPath()
    {
        return $this->recoveryCustomPath;
    }

    /**
     * Return recovery custom URL
     *
     * @return string return empty URL if custom path isn't set
     */
    public function getRecoveryCustomURL()
    {
        if (strlen($this->recoveryCustomPath) == 0) {
            return '';
        }

        if (SnapIO::isChildPath($this->recoveryCustomPath, DUP_PRO_Archive::getArchiveListPaths('wpcontent'), false, true, true)) {
            $mainPath = DUP_PRO_Archive::getArchiveListPaths('wpcontent');
            $mainURL  = DUP_PRO_Archive::getOriginalUrls('wpcontent');
        } else {
            $mainPath = DUP_PRO_Archive::getArchiveListPaths('home');
            $mainURL  = DUP_PRO_Archive::getOriginalUrls('home');
        }

        return $mainURL . '/' . SnapIo::getRelativePath($this->recoveryCustomPath, $mainPath, true);
    }

    /**
     * Set recovery custom path
     *
     * @param string $path        path
     * @param string $failMessage return fail message
     *
     * @return bool
     */
    public function setRecoveryCustomPath($path, &$failMessage = '')
    {
        $remove = false;

        try {
            $this->recoveryCustomPath = '';

            if (strlen($path) == 0) {
                return true;
            }

            if (file_exists($path)) {
                if (
                    !is_dir($path) ||
                    !is_writable($path)
                ) {
                    throw new Exception(__('The Recovery custom path must be a folder with write permissions.', 'duplicator-pro'));
                }
            } else {
                if (wp_mkdir_p($path) == false) {
                    throw new Exception(sprintf(__('It is not possible to create the folder %s', 'duplicator-pro'), $path));
                }
            }

            if (
                !SnapIO::isChildPath($path, DUP_PRO_Archive::getArchiveListPaths('home'), false, false, true) &&
                !SnapIO::isChildPath($path, DUP_PRO_Archive::getArchiveListPaths('wpcontent'), false, false, true)
            ) {
                throw new Exception(__('The custom Recovery path must be a child folder of the home path or wp-content', 'duplicator-pro'));
            }

            if (PathUtil::isPathInCoreDirs($path)) {
                throw new Exception(__('The Recovery custom path cannot be a wordpress core folder.', 'duplicator-pro'));
            }
        } catch (Exception $e) {
            $remove      = true;
            $failMessage = $e->getMessage();
            return false;
        } finally {
            if ($remove) {
                rmdir($path);
            }
        }

        $this->recoveryCustomPath = $path;
        return true;
    }

    /**
     * Update global settings after install
     *
     * @return bool true on success false on failure
     */
    public function updateAftreInstall()
    {
        $this->lock_mode     = DUP_PRO_Global_Entity::getDefaultLockType();
        $this->ajax_protocol = DUPLICATOR_PRO_DEFAULT_AJAX_PROTOCOL;
        if ($this->getBuildMode() !== DUP_PRO_Archive_Build_Mode::DupArchive) {
            $this->set_build_mode();
        }
        return $this->save();
    }

    /**
     * Return default lock
     *
     * @return int Enum lock type
     */
    protected static function getDefaultLockType()
    {
        $lockType = DUP_PRO_Thread_Lock_Mode::Flock;

        if (DUP_PRO_U::getSqlLock(DUPLICATOR_PRO_TEST_SQL_LOCK_NAME)) {
            $lockType = (DUP_PRO_U::checkSqlLock(DUPLICATOR_PRO_TEST_SQL_LOCK_NAME) ? DUP_PRO_Thread_Lock_Mode::SQL_Lock : DUP_PRO_Thread_Lock_Mode::Flock);
            DUP_PRO_U::releaseSqlLock(DUPLICATOR_PRO_TEST_SQL_LOCK_NAME);
        }
        DUP_PRO_Log::trace("Lock type auto set to {$lockType}");
        return $lockType;
    }

    /**
     * To export data
     *
     * @return array<string, mixed>
     */
    public function settingsExport()
    {
        $skipProps = [
            'id',
            'last_system_check_timestamp',
            'initial_activation_timestamp',
            'manual_mode_storage_ids',
            'license_key_visible',
            'lkp',
        ];

        $data = JsonSerialize::serializeToData($this, JsonSerialize::JSON_SKIP_MAGIC_METHODS |  JsonSerialize::JSON_SKIP_CLASS_NAME);
        foreach ($skipProps as $prop) {
            unset($data[$prop]);
        }
        return $data;
    }

    /**
     * Update object properties from import data
     *
     * @param array<string, mixed> $data        data to import
     * @param string               $dataVersion version of data
     * @param array<string, mixed> $extraData   extra data, useful form id mapping etc.
     *
     * @return bool True if success, otherwise false
     */
    public function settingsImport($data, $dataVersion, array $extraData = [])
    {
        $skipProps = [
            'id',
            'last_system_check_timestamp',
            'initial_activation_timestamp',
            'manual_mode_storage_ids',
            'license_key_visible',
            'lkp',
        ];

        $reflect = new ReflectionClass(self::class);
        $props   = $reflect->getProperties();

        foreach ($props as $prop) {
            if (in_array($prop->getName(), $skipProps)) {
                continue;
            }
            if (!isset($data[$prop->getName()])) {
                continue;
            }
            $prop->setAccessible(true);
            $prop->setValue($this, $data[$prop->getName()]);
        }
        return true;
    }

    /**
     * Set from object
     *
     * @param self $global_data global data
     *
     * @return void
     */
    public function setFromImportData(self $global_data)
    {
        $reflect = new ReflectionClass(self::class);
        $props   = $reflect->getProperties();

        $skipProps = [
            'id',
            'last_system_check_timestamp',
            'initial_activation_timestamp',
            'manual_mode_storage_ids',
            'license_key_visible',
            'lkp',
        ];

        foreach ($props as $prop) {
            if (in_array($prop->getName(), $skipProps)) {
                continue;
            }
            $prop->setAccessible(true);
            $prop->setValue($this, $prop->getValue($global_data));
        }
    }

    /**
     * Check if build mode is available
     *
     * @param int $buildMode build mode constant
     *
     * @return bool
     */
    public static function isBuildModeAvailable($buildMode)
    {
        switch ($buildMode) {
            case DUP_PRO_Archive_Build_Mode::Unconfigured:
                return false;
            case DUP_PRO_Archive_Build_Mode::Shell_Exec:
                return (DUP_PRO_Zip_U::getShellExecZipPath() != null);
            case DUP_PRO_Archive_Build_Mode::ZipArchive:
                return ZipArchiveExtended::isPhpZipAvailable();
            case DUP_PRO_Archive_Build_Mode::DupArchive:
                return true;
            default:
                throw new Exception('Invalid engine');
        }
    }

    /**
     * Return package build mode
     *
     * @return int Return enum DUP_PRO_Archive_Build_Mode
     */
    public function getBuildMode()
    {
        $archive_build_mode = $this->archive_build_mode;

        switch ($archive_build_mode) {
            case DUP_PRO_Archive_Build_Mode::Unconfigured:
                if (self::isBuildModeAvailable(DUP_PRO_Archive_Build_Mode::Shell_Exec)) {
                    $archive_build_mode = DUP_PRO_Archive_Build_Mode::Shell_Exec;
                } elseif (self::isBuildModeAvailable(DUP_PRO_Archive_Build_Mode::ZipArchive)) {
                    $archive_build_mode = DUP_PRO_Archive_Build_Mode::ZipArchive;
                } else {
                    $archive_build_mode = DUP_PRO_Archive_Build_Mode::DupArchive;
                }
                break;
            case DUP_PRO_Archive_Build_Mode::Shell_Exec:
                if (!self::isBuildModeAvailable(DUP_PRO_Archive_Build_Mode::Shell_Exec)) {
                    if (self::isBuildModeAvailable(DUP_PRO_Archive_Build_Mode::ZipArchive)) {
                        $archive_build_mode = DUP_PRO_Archive_Build_Mode::ZipArchive;
                    } else {
                        $archive_build_mode = DUP_PRO_Archive_Build_Mode::DupArchive;
                    }
                }
                break;
            case DUP_PRO_Archive_Build_Mode::ZipArchive:
                if (!self::isBuildModeAvailable(DUP_PRO_Archive_Build_Mode::ZipArchive)) {
                    if (self::isBuildModeAvailable(DUP_PRO_Archive_Build_Mode::Shell_Exec)) {
                        $archive_build_mode = DUP_PRO_Archive_Build_Mode::Shell_Exec;
                    } else {
                        $archive_build_mode = DUP_PRO_Archive_Build_Mode::DupArchive;
                    }
                }
                break;
            case DUP_PRO_Archive_Build_Mode::DupArchive:
                break;
            default:
                throw new Exception('Invalid engine');
        }
        return $archive_build_mode;
    }

    /**
     * Selt build mode and return it
     *
     * @param bool $save if true save update global entity only if build mode is changed
     *
     * @return int Return enum DUP_PRO_Archive_Build_Mode
     */
    public function set_build_mode($save = false)
    {
        $newBuildMode = apply_filters('duplicator_pro_default_archive_build_mode', $this->getBuildMode());
        if ($newBuildMode != $this->archive_build_mode) {
            $this->archive_build_mode = $newBuildMode;
            if ($save) {
                $this->save();
            }
        }
        return $this->archive_build_mode;
    }

    /**
     *
     * @return int<0,max> microsenconds
     */
    public function getMicrosecLoadReduction()
    {
        return DUP_PRO_Server_Load_Reduction::microseconds_from_reduction($this->server_load_reduction);
    }

    /**
     * set db mode ant all related params.
     * check mysqldump
     *
     * @param null|string $dbMode                if null get INPUT_POST
     * @param null|int    $phpDumpMode           if null get INPUT_POST
     * @param null|int    $dbPhpQueryLimit       if null get INPUT_POST
     * @param null|string $packageMysqldumpPath  if null get INPUT_POST
     * @param null|int    $dbMysqlDumpQueryLimit if null get INPUT_POST
     *
     * @return void
     */
    public function setDbMode(
        $dbMode = null,
        $phpDumpMode = null,
        $dbPhpQueryLimit = null,
        $packageMysqldumpPath = null,
        $dbMysqlDumpQueryLimit = null
    ) {
        //DATABASE
        $dbMode                = is_null($dbMode) ? SnapUtil::sanitizeDefaultInput(INPUT_POST, '_package_dbmode') : $dbMode;
        $phpDumpMode           = is_null($phpDumpMode) ? filter_input(
            INPUT_POST,
            '_phpdump_mode',
            FILTER_VALIDATE_INT,
            array(
                'options' => array(
                    'default'   => 0,
                    'min_range' => 0,
                    'max_range' => 1,
                ),
            )
        ) : $phpDumpMode;
        $dbMysqlDumpQueryLimit = is_null($dbMysqlDumpQueryLimit) ? filter_input(
            INPUT_POST,
            '_package_mysqldump_qrylimit',
            FILTER_VALIDATE_INT,
            array(
                'options' => array(
                    'default'   => DUP_PRO_Constants::DEFAULT_MYSQL_DUMP_CHUNK_SIZE,
                    'min_range' => DUP_PRO_Constants::MYSQL_DUMP_CHUNK_SIZE_MIN_LIMIT,
                    'max_range' => DUP_PRO_Constants::MYSQL_DUMP_CHUNK_SIZE_MAX_LIMIT,
                ),
            )
        ) : $dbMysqlDumpQueryLimit;

        $packageMysqldumpPath = is_null($packageMysqldumpPath) ?
            SnapUtil::sanitizeDefaultInput(INPUT_POST, '_package_mysqldump_path') :
            $packageMysqldumpPath;
        $packageMysqldumpPath = SnapUtil::sanitizeNSCharsNewlineTabs($packageMysqldumpPath);
        $packageMysqldumpPath = preg_match('/^([A-Za-z]\:)?[\/\\\\]/', $packageMysqldumpPath) ? $packageMysqldumpPath : '';
        $packageMysqldumpPath = preg_replace('/[\'"]/m', '', $packageMysqldumpPath);
        $packageMysqldumpPath = SnapIO::safePathUntrailingslashit($packageMysqldumpPath);

        $mysqlDumpPath = empty($packageMysqldumpPath) ? DUP_PRO_DB::getMySqlDumpPath() : $packageMysqldumpPath;
        if ($dbMode == 'mysql' && empty($mysqlDumpPath)) {
            $dbMode = 'php';
        }

        $this->package_mysqldump          = ($dbMode == 'mysql');
        $this->package_phpdump_mode       = $phpDumpMode;
        $this->package_mysqldump_path     = $packageMysqldumpPath;
        $this->package_mysqldump_qrylimit = $dbMysqlDumpQueryLimit;

        array_map(function ($option) {
            $option->update();
        }, $this->getMysqldumpOptions());
    }

    /**
     * Sets cleanup fields and configures WP Cron accordingly
     *
     * @param int    $cleanup_mode       Cleanup mode to set
     * @param string $cleanup_email      Email address to send cleanup notification to
     * @param int    $auto_cleanup_hours Number of hours after which cleanup should be performed
     *
     * @return void
     */
    public function setCleanupFields($cleanup_mode = null, $cleanup_email = null, $auto_cleanup_hours = null)
    {
        $this->cleanup_mode = is_null($cleanup_mode) ? filter_input(
            INPUT_POST,
            'cleanup_mode',
            FILTER_VALIDATE_INT,
            array(
                'options' => array(
                    'default'   => self::CLEANUP_MODE_OFF,
                    'min_range' => 0,
                    'max_range' => 2,
                ),
            )
        ) : $cleanup_mode;

        $this->cleanup_email = is_null($cleanup_email) ? $_REQUEST['cleanup_email'] : $cleanup_email;

        $this->auto_cleanup_hours = is_null($auto_cleanup_hours) ? filter_input(
            INPUT_POST,
            'auto_cleanup_hours',
            FILTER_VALIDATE_INT,
            array(
                'options' => array(
                    'default'   => 24,
                    'min_range' => 1,
                ),
            )
        ) : $auto_cleanup_hours;

        self::cleanupScheduleSetup();
    }

    /**
     * Schedules cron event for installer files cleanup purposes,
     * and unschedules it if it's not needed anymore.
     *
     * @return void
     */
    public static function cleanupScheduleSetup()
    {
        $global = self::getInstance();
        SnapWP::unscheduleEvent(self::CLEANUP_HOOK);
        if ($global->cleanup_mode == self::CLEANUP_MODE_MAIL) {
            $nextRunTime = time() + self::CLEANUP_EMAIL_NOTICE_INTERVAL * 3600;
            SnapWP::scheduleEvent($nextRunTime, self::CLEANUP_INTERVAL_NAME, self::CLEANUP_HOOK);
        } elseif ($global->cleanup_mode == self::CLEANUP_MODE_AUTO) {
            $nextRunTime = time() + $global->auto_cleanup_hours * 3600;
            SnapWP::scheduleEvent($nextRunTime, self::CLEANUP_INTERVAL_NAME, self::CLEANUP_HOOK);
        }
    }

    /**
     * Customizes schedules according to current cleanup_mode. If necessary, it
     * adds a custom cron schedule that will run every N hours.
     *
     * @param array<string,array{interval:int,display:string}> $schedules An array of non-default cron schedules.
     *
     * @return array<string,array{interval:int,display:string}> Filtered array of non-default cron schedules.
     */
    public static function customCleanupCronInterval($schedules)
    {
        $global = self::getInstance();

        switch ($global->cleanup_mode) {
            case self::CLEANUP_MODE_OFF:
                // No need to modify anything
                break;
            case self::CLEANUP_MODE_MAIL:
                $schedules[self::CLEANUP_INTERVAL_NAME] = array(
                    'interval' => self::CLEANUP_EMAIL_NOTICE_INTERVAL * 3600, // In seconds, every N hours
                    'display'  => sprintf(esc_html__('Every %1$d hours', 'duplicator-pro'), self::CLEANUP_EMAIL_NOTICE_INTERVAL),
                );
                break;
            case self::CLEANUP_MODE_AUTO:
                $schedules[self::CLEANUP_INTERVAL_NAME] = array(
                    'interval' => $global->auto_cleanup_hours * 3600, // In seconds, every N hours
                    'display'  => sprintf(esc_html__('Every %1$d hours', 'duplicator-pro'), $global->auto_cleanup_hours),
                );
                break;
            default:
                throw new Exception('Invalid cleanup mode:' . SnapLog::v2str($global->cleanup_mode));
        }
        return $schedules;
    }

    /**
     * The function that gets executed by WP Cron for cleanup of installer files.
     * It does different tasks based on current cleanup_mode setting.
     *
     * @return void
     */
    public static function cleanupCronJob()
    {
        $global = self::getInstance();

        $websiteUrl = SnapURL::getCurrentUrl(false, false, 1);
        $to         = $global->cleanup_email;
        if (empty($to)) {
            $to = get_option('admin_email');
        }

        switch ($global->cleanup_mode) {
            case self::CLEANUP_MODE_MAIL:
                // Email Notice cron job routine for cleanup of installer files
                $listOfInstallerFiles = MigrationMng::checkInstallerFilesList();
                $filesToRemove        = array();

                foreach ($listOfInstallerFiles as $path) {
                    if (time() - filectime($path) > self::CLEANUP_FILE_TIME_DELAY) {
                        $filesToRemove[] = $path;
                    }
                }

                if (count($filesToRemove) > 0 && !empty($to)) {
                    // Send an Email Notice
                    $subject  = __("Action required", 'duplicator-pro');
                    $message  = sprintf(__('This email is sent by your Wordpress plugin "Duplicator Pro" from website: %1$s. ', 'duplicator-pro'), $websiteUrl);
                    $message .= __('You received this email because Cleanup mode is set to "Email Notice". ', 'duplicator-pro');
                    $message .= __('Cleanup routine discovered that some installer files (leftovers from migration) were not removed. ', 'duplicator-pro');
                    $message .= __('We strongly advise you to remove these files. ', 'duplicator-pro');
                    $message .= __('Here is the list of files found on your website that you should remove:', 'duplicator-pro') . "<br/>";
                    foreach ($filesToRemove as $path) {
                        $message .= "-> $path<br/>";
                    }
                    $message .= "<br/>";
                    $message .= __('Note: You could enable "Auto Cleanup" mode if you go to:', 'duplicator-pro') . "<br/>";
                    $message .= __('WordPress Admin > Duplicator Pro > Settings > Packages Tab > Cleanup.', 'duplicator-pro') . "<br/>";
                    $message .= __('That mode will do cleanup of those files automatically for you.', 'duplicator-pro') . "<br/>";
                    $message .= "<br/>";
                    $message .= __('Best regards,', 'duplicator-pro') . "<br/>";
                    $message .= __('Duplicator Pro', 'duplicator-pro');

                    if (wp_mail($to, $subject, $message, array('Content-Type: text/html; charset=UTF-8'))) {
                        // OK
                        \DUP_PRO_Log::trace('wp_mail sent email notice regarding cleanup of installer files');
                    } else {
                        \DUP_PRO_Log::trace("Problem sending email notice regarding cleanup of installer files to {$to}");
                    }
                }
                break;
            case self::CLEANUP_MODE_AUTO:
                // Auto Cleanup cron job routine for cleanup of installer files
                $installerFiles = MigrationMng::cleanMigrationFiles(false, self::CLEANUP_FILE_TIME_DELAY);
                if (count($installerFiles) == 0) {
                    // No installer files were found, so we do nothing else
                    return;
                }

                $filesFailedRemoval = array();
                foreach ($installerFiles as $path => $success) {
                    if (!$success) {
                        $filesFailedRemoval[] = $path;
                    }
                }
                if (count($filesFailedRemoval) == 0) {
                    // All found installer files were removed successfully,
                    // or they did not even need to be removed yet because of CLEANUP_FILE_TIME_DELAY
                    return;
                }

                // If this is executed that means that some of installer files
                // could not be removed for some reason (permission issues?)
                if (!empty($to)) {
                    // Send an Email Notice about files that could not be removed during auto cleanup
                    $subject  = __("Action required", 'duplicator-pro');
                    $message  = sprintf(__('This email is sent by your Wordpress plugin "Duplicator Pro" from website: %1$s. ', 'duplicator-pro'), $websiteUrl);
                    $message .= __('"Auto Cleanup" mode is ON, ', 'duplicator-pro');
                    $message .= __('however cleanup routine discovered that some installer files (leftovers from migration) could not be removed. ', 'duplicator-pro');
                    $message .= __('We strongly advise you to remove those files manually. ', 'duplicator-pro');
                    $message .= __('Here is the list of files found on your website that you should remove:', 'duplicator-pro') . "<br/>";
                    foreach ($filesFailedRemoval as $path) {
                        $message .= "-> $path<br/>";
                    }
                    $message .= "<br/>";
                    $message .= __('Those files probably could not be removed due to permission issues. ', 'duplicator-pro');
                    $message .= sprintf(
                        __('You can find more info in FAQ %1$son this link%2$s.', 'duplicator-pro'),
                        "<a href='" . DUPLICATOR_PRO_DUPLICATOR_DOCS_URL . "how-to-fix-file-permissions-issues' target='_blank'>",
                        "</a>"
                    ) . "<br/>";
                    $message .= "<br/>";
                    $message .= __('Note: To edit "Cleanup" settings go to:', 'duplicator-pro') . "<br/>";
                    $message .= __('WordPress Admin > Duplicator Pro > Settings > Packages Tab > Cleanup.', 'duplicator-pro') . "<br/>";
                    $message .= "<br/>";
                    $message .= __('Best regards,', 'duplicator-pro') . "<br/>";
                    $message .= __('Duplicator Pro', 'duplicator-pro');

                    if (wp_mail($to, $subject, $message, array('Content-Type: text/html; charset=UTF-8'))) {
                        // OK
                        \DUP_PRO_Log::trace('wp_mail sent email notice regarding failed auto cleanup of installer files');
                    } else {
                        \DUP_PRO_Log::trace("Problem sending email notice regarding failed auto cleanup of installer files to {$to}");
                    }
                }
                break;
            case self::CLEANUP_MODE_OFF:
            default:
                break;
        }
    }

    /**
     * Set archive mode
     *
     * @param ?int  $archiveBuildMode        Archive build mode, if null get INPUT_POST
     * @param ?int  $zipArchiveMode          Zip archive mode, if null get INPUT_POST
     * @param ?bool $archiveCompression      Archive compression, if null get INPUT_POST
     * @param ?bool $ziparchiveValidation    Zip archive validation, if null get INPUT_POST
     * @param ?int  $ziparchiveChunkSizeInMb Zip archive chunk size in MB, if null get INPUT_POST
     *
     * @return void
     */
    public function setArchiveMode(
        $archiveBuildMode = null,
        $zipArchiveMode = null,
        $archiveCompression = null,
        $ziparchiveValidation = null,
        $ziparchiveChunkSizeInMb = null
    ) {
        $isZipAvailable = (DUP_PRO_Zip_U::getShellExecZipPath() != null);

        $prelimBuildMode = is_null($archiveBuildMode) ? filter_input(
            INPUT_POST,
            'archive_build_mode',
            FILTER_VALIDATE_INT,
            array(
                'options' => array(
                    'min_range' => 1,
                    'max_range' => 3,
                ),
            )
        ) : $archiveBuildMode;

        // Something has changed which invalidates Shell exec so move it to ZA
        $this->archive_build_mode = (!$isZipAvailable && ($prelimBuildMode == DUP_PRO_Archive_Build_Mode::Shell_Exec)) ? DUP_PRO_Archive_Build_Mode::ZipArchive : $prelimBuildMode;
        $this->ziparchive_mode    = is_null($zipArchiveMode) ? filter_input(
            INPUT_POST,
            'ziparchive_mode',
            FILTER_VALIDATE_INT,
            array(
                'options' => array(
                    'default'   => 0,
                    'min_range' => 0,
                    'max_range' => 1,
                ),
            )
        ) : $zipArchiveMode;

        $this->archive_compression         = is_null($archiveCompression) ? filter_input(INPUT_POST, 'archive_compression', FILTER_VALIDATE_BOOLEAN) : $archiveCompression;
        $this->ziparchive_validation       = is_null($ziparchiveValidation) ? filter_input(INPUT_POST, 'ziparchive_validation', FILTER_VALIDATE_BOOLEAN) : $ziparchiveValidation;
        $this->ziparchive_chunk_size_in_mb = is_null($ziparchiveChunkSizeInMb) ? filter_input(
            INPUT_POST,
            'ziparchive_chunk_size_in_mb',
            FILTER_VALIDATE_INT,
            array(
                'options' => array(
                    'default'   => DUP_PRO_Constants::DEFAULT_ZIP_ARCHIVE_CHUNK,
                    'min_range' => 1,
                ),
            )
        ) : $ziparchiveChunkSizeInMb;
    }

    /**
     * Set clientside kickoff
     *
     * @param bool $enable enable or disable
     *
     * @return void
     */
    public function setClientsideKickoff($enable)
    {
        if ($this->clientside_kickoff != $enable) {
            $this->clientside_kickoff = $enable;

            if ($this->clientside_kickoff) {
                // Auto setting the max package runtime in case of client kickoff is turned on and
                // the max package runtime is less than 480 minutes - 8 hours
                $this->max_package_runtime_in_min = max(480, $this->max_package_runtime_in_min);
                $this->setDbMode('mysql');

                // RSR 4/29/19 not setting archive mode for now - too risky
                // $mode = (DUP_PRO_Zip_U::getShellExecZipPath() != null) ? DUP_PRO_Archive_Build_Mode::Shell_Exec : DUP_PRO_Archive_Build_Mode::DupArchive;
                // $this->setArchiveMode($mode);
            }
        }
    }

    /**
     * Configure dropbox transfer mode
     *
     * @return void
     */
    public function configure_dropbox_transfer_mode()
    {
        $dGlobal = DynamicGlobalEntity::getInstance();
        if ($dGlobal->getVal('dropbox_transfer_mode') == DUP_PRO_Dropbox_Transfer_Mode::Unconfigured) {
            $mode = DUP_PRO_Dropbox_Transfer_Mode::Disabled;
            if (SnapUtil::isCurlEnabled()) {
                $mode = DUP_PRO_Dropbox_Transfer_Mode::cURL;
            } elseif (DUP_PRO_Server::isURLFopenEnabled()) {
                $mode = DUP_PRO_Dropbox_Transfer_Mode::FOpen_URL;
            }
            $dGlobal->setVal('dropbox_transfer_mode', $mode, true);
        }
    }

    /**
     * Get installer backup filename
     *
     * @return string
     */
    public function get_installer_backup_filename()
    {
        $installer_extension = $this->get_installer_extension();

        if (trim($installer_extension) == '') {
            return 'installer-backup';
        } else {
            return "installer-backup.$installer_extension";
        }
    }

    /**
     * Get installer extension
     *
     * @return string
     */
    public function get_installer_extension()
    {
        return pathinfo($this->installer_base_name, PATHINFO_EXTENSION);
    }

    /**
     * Get archive engine label
     *
     * @return string
     */
    public function get_archive_engine()
    {
        $mode = '';
        switch ($this->archive_build_mode) {
            case DUP_PRO_Archive_Build_Mode::ZipArchive:
                $mode = ($this->ziparchive_mode == DUP_PRO_ZipArchive_Mode::Multithreaded) ?
                    __("ZipArchive: multi-thread", 'duplicator-pro') :
                    __("ZipArchive: single-thread", 'duplicator-pro');
                break;

            case DUP_PRO_Archive_Build_Mode::DupArchive:
                $mode = __('DupArchive', 'duplicator-pro');
                break;

            default:
                $mode = __("Shell Zip", 'duplicator-pro');
                break;
        }

        return $mode;
    }

    /**
     * Return archive extension type
     *
     * @return string
     */
    public function get_archive_extension_type()
    {
        $mode = 'zip';
        if ($this->archive_build_mode == DUP_PRO_Archive_Build_Mode::DupArchive) {
            $mode = 'daf';
        }
        return $mode;
    }

    /**
     * Return Mysqldump options
     *
     * @return GroupOptions[]
     */
    public function getMysqldumpOptions()
    {
        return $this->packageMysqldumpOptions;
    }

    /**
     * Get manual mode storage ids
     *
     * @return int[]
     */
    public function getManualModeStorageIds()
    {
        if (count($this->manual_mode_storage_ids) == 0) {
            $this->manual_mode_storage_ids = [StoragesUtil::getDefaultStorageId()];
        }
        return $this->manual_mode_storage_ids;
    }

    /**
     * Set manual mode storage ids
     *
     * @param int[] $storageIds Storage ids
     *
     * @return void
     */
    public function setManualModeStorageIds(array $storageIds)
    {
        $this->manual_mode_storage_ids = [];
        foreach ($storageIds as $id) {
            $id = (int) $id;
            if ($id <= 0) {
                continue;
            }
            $this->manual_mode_storage_ids[] = $id;
        }
        if (count($this->manual_mode_storage_ids) == 0) {
            $this->manual_mode_storage_ids = [StoragesUtil::getDefaultStorageId()];
        }
    }

    /**
     * Get Email Summary Recipients
     *
     * @return string[]
     */
    public function getEmailSummaryRecipients()
    {
        return $this->email_summary_recipients;
    }

    /**
     * Set Email Summary Recipients
     *
     * @param string[] $recipients List of recipient email addreses
     *
     * @return void
     */
    public function setEmailSummaryRecipients($recipients)
    {
        $recipients = filter_var($recipients, FILTER_VALIDATE_EMAIL, FILTER_REQUIRE_ARRAY);
        if ($recipients === false) {
            $recipients = array();
        }

        foreach ($recipients as $key => $recipient) {
            if ($recipient === false) {
                continue;
            }

            $recipients[$key] = sanitize_email($recipient);
        }

        $this->email_summary_recipients = array_values(array_unique($recipients));
    }

    /**
     * Get email summary frequency
     *
     * @return string
     */
    public function getEmailSummaryFrequency()
    {
        return $this->email_summary_frequency;
    }

    /**
     * Set email summary frequency
     *
     * @param string $frequency The frequency
     *
     * @return void
     */
    public function setEmailSummaryFrequency($frequency)
    {
        if (EmailSummaryBootstrap::updateFrequency($this->email_summary_frequency, $frequency) === false) {
            DUP_PRO_Log::trace("Invalid email summary frequency: {$frequency}");
            return;
        }
        $this->email_summary_frequency = $frequency;
    }

    /**
     * True if AM notifications are enabled
     *
     * @return bool
     */
    public function isAmNoticesEnabled()
    {
        return $this->amNotices;
    }

    /**
     * Set notifications enabled
     *
     * @param bool $enable true if enabled
     *
     * @return void
     */
    public function setAmNotices($enable)
    {
        $this->amNotices = $enable;
    }
}
