<?php

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

defined('ABSPATH') || defined('DUPXABSPATH') || exit;

use Duplicator\Installer\Addons\ProBase\License;
use Duplicator\Installer\Core\Deploy\Chunk\SiteUpdateChunkManager;
use Duplicator\Installer\Core\Deploy\CleanUp;
use Duplicator\Installer\Core\Deploy\Database\DbCleanup;
use Duplicator\Installer\Core\Deploy\Database\DbReplace;
use Duplicator\Installer\Core\Deploy\Database\DbUtils;
use Duplicator\Installer\Core\Deploy\Plugins\PluginsManager;
use Duplicator\Installer\Core\Deploy\ServerConfigs;
use Duplicator\Installer\Core\Deploy\Standalone;
use Duplicator\Installer\Core\InstState;
use Duplicator\Installer\Core\Params\Models\SiteOwrMap;
use Duplicator\Installer\Core\Params\PrmMng;
use Duplicator\Installer\Utils\InstallerOrigFileMng;
use Duplicator\Installer\Utils\InstDescMng;
use Duplicator\Installer\Utils\Log\Log;
use Duplicator\Installer\Utils\ReplaceEngine\ReplaceMng;
use Duplicator\Libs\Snap\SnapDB;
use Duplicator\Libs\Snap\SnapIO;
use Duplicator\Libs\Snap\SnapJson;
use Duplicator\Libs\Snap\SnapString;
use Duplicator\Libs\Snap\SnapUtil;
use Duplicator\Libs\WpConfig\WPConfigTransformer;
use Duplicator\Libs\WpConfig\WPConfigTransformerSrc;
use VendorDuplicator\Amk\JsonSerialize\JsonSerialize;

/**
 * Step 3 functions
 * Singlethon
 */
final class DUPX_S3_Funcs
{
    const MODE_NORMAL           = 1;
    const MODE_CHUNK            = 2;
    const MODE_SKIP             = 3;
    const FIRST_LOGIN_OPTION    = 'dupli_opt_first_login_after_install';
    const MIGRATION_DATA_OPTION = 'dupli_opt_migration_data';

    /** @var ?self */
    protected static $instance;
    /** @var ?mixed[] */
    public $cTableParams;
    /** @var array<string, mixed> */
    public $report = [
        'pass'          => 0,
        'chunk'         => 0,
        'chunkPos'      => [],
        'progress_perc' => 0,
        'scan_tables'   => 0,
        'scan_rows'     => 0,
        'scan_cells'    => 0,
        'updt_tables'   => 0,
        'updt_rows'     => 0,
        'updt_cells'    => 0,
        'errsql'        => [],
        'errser'        => [],
        'errkey'        => [],
        'errsql_sum'    => 0,
        'errser_sum'    => 0,
        'errkey_sum'    => 0,
        'profile_start' => '',
        'profile_end'   => '',
        'time'          => '',
        'err_all'       => 0,
        'warn_all'      => 0,
        'warnlist'      => [],
    ];
    private float $timeStart;
    /** @var null|mysqli connection */
    private $dbh;

    /**
     * Class constructor
     */
    private function __construct()
    {
        $this->initData();
        $this->timeStart = DUPX_U::getMicrotime();
    }

    /**
     * Main update controller for the installer
     *
     * @return array{step3: mixed[]}
     */
    public function updateWebsite(): array
    {
        Log::setThrowExceptionOnError(true);
        $nManager = DUPX_NOTICE_MANAGER::getInstance();

        switch ($this->getEngineMode()) {
            case DUPX_S3_Funcs::MODE_CHUNK:
                // START CHUNK MANAGER
                $maxIteration = 0;     // max iteration before stop. If 0 have no limit
                // auto set prevent timeout
                $inimaxExecutionTime           = ini_get('max_execution_time');
                $maxExecutionTime              = (int) (empty($inimaxExecutionTime) ? DUPX_Constants::CHUNK_MAX_TIMEOUT_TIME : $inimaxExecutionTime);
                $timeOut                       = max(5, $maxExecutionTime - 2) * 1000000;    // timeout in microseconds before stop exectution
                $throttling                    = 2000;  // sleep in microseconds every iteration
                $GLOBALS['DATABASE_PAGE_SIZE'] = 1000;   // database pagination size for engine update queries

                /* TEST INIT SINGLE FUNC
                  $maxIteration                  = 1;  // max iteration before stop. If 0 have no limit
                  $timeOut                       = 0;  // timeout in milliseconds before stop exectution
                  $throttling                    = 0;  // sleep in microseconds every iteration
                  $GLOBALS['DATABASE_PAGE_SIZE'] = 1000000;   // database pagination size for engine update queries
                 */

                $chunkmManager = new SiteUpdateChunkManager([], $maxIteration, $timeOut, $throttling);

                if ($chunkmManager->wasProcessingIncomplete()) {
                    Log::info('Previous deployment process exited unexpectedly, resuming from last saved position');
                }

                switch ($chunkmManager->start()) {
                    case SiteUpdateChunkManager::CHUNK_COMPLETE:
                        $this->complete();
                        break;
                    case SiteUpdateChunkManager::CHUNK_STOP:
                        // Stop executions
                        $this->chunkStop($chunkmManager->getProgressPerc(), $chunkmManager->getLastPosition());
                        break;
                    case SiteUpdateChunkManager::CHUNK_ERROR:
                    default:
                        // chunk error
                        throw new Exception('Chunk error, message: ' . $chunkmManager->getLastErrorMessage());
                }
                break;
            case DUPX_S3_Funcs::MODE_SKIP:
                $this->initLog();
                $this->removeMaintenanceMode();
                $this->checkForIndexHtml();
                if (!InstState::dbDoNothing()) {
                    DbCleanup::cleanupOptions();
                    DbCleanup::cleanupExtra();
                    DbCleanup::cleanupPackages();
                    DbCleanup::cleanupActivityLogs();
                    $this->configFilesUpdate();
                    $this->forceLogoutOfAllUsers();
                    $this->duplicatorMigrationInfoSet();
                    $this->noticeTest();
                } elseif (InstState::isImportFromBackendMode() || InstState::isRecoveryMode()) {
                    $this->forceLogoutOfAllUsers();
                    $this->duplicatorMigrationInfoSet();
                }
                $this->cleanupFiles();
                $this->setFilePermsission();
                $this->finalReportNotices();
                $this->complete();
                break;
            case DUPX_S3_Funcs::MODE_NORMAL:
            default:
                $chunkmManager = new SiteUpdateChunkManager();
                switch ($chunkmManager->start()) {
                    case SiteUpdateChunkManager::CHUNK_COMPLETE:
                        $this->complete();
                        break;
                    case SiteUpdateChunkManager::CHUNK_STOP:
                        throw new Exception('No chunk in normal mode');
                    case SiteUpdateChunkManager::CHUNK_ERROR:
                    default:
                        // chunk error
                        throw new Exception('Chunk error, message: ' . $chunkmManager->getLastErrorMessage());
                }
        }
        $nManager->saveNotices();
        return $this->getJsonReport();
    }

    /**
     * Get single instance
     *
     * @return self
     */
    public static function getInstance()
    {
        if (is_null(self::$instance)) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * inizialize 3sFunc data
     *
     * @return void
     */
    public function initData(): void
    {
        // if data file exists load saved data
        if (file_exists(self::getS3dataFilePath())) {
            Log::info('LOAD S3 DATA FROM JSON', Log::LV_DETAILED);
            if ($this->loadData() == false) {
                throw new Exception('Can\'t load s3 data');
            }
        } else {
            Log::info('INIT S3 DATA', Log::LV_DETAILED);
            // else init data from $_POST
            $this->setReplaceList();
        }
    }

    /**
     * Get data file path
     *
     * @return string
     */
    private static function getS3dataFilePath()
    {
        static $path = null;
        if (is_null($path)) {
            $path = DUPX_INIT . '/' . InstDescMng::getInstance()->getName(InstDescMng::TYPE_INST_S3_DATA);
        }
        return $path;
    }

    /**
     * Save data to json file
     *
     * @return bool
     */
    public function saveData(): bool
    {
        $data = [
            'report'       => $this->report,
            'cTableParams' => $this->cTableParams,
            'replaceData'  => ReplaceMng::getInstance()->getArrayData(),
        ];

        if (($json = SnapJson::jsonEncodePPrint($data)) === false) {
            Log::info('Can\'t encode json data');
            return false;
        }

        if (@file_put_contents(self::getS3dataFilePath(), $json) === false) {
            Log::info('Can\'t save s3 data file');
            return false;
        }

        return true;
    }

    /**
     * Load data from json file
     *
     * @return bool
     */
    private function loadData(): bool
    {
        if (!file_exists(self::getS3dataFilePath())) {
            return false;
        }

        if (($json = @file_get_contents(self::getS3dataFilePath())) === false) {
            Log::info('Can\'t load s3 data file');
            return false;
        }

        $data = json_decode($json, true);

        if (!is_array($data)) {
            Log::info('Can\'t decode json data');
            return false;
        }

        if (array_key_exists('cTableParams', $data)) {
            $this->cTableParams = $data['cTableParams'];
        } else {
            Log::info('S3 data not well formed: cTableParams not found.');
            return false;
        }

        if (array_key_exists('replaceData', $data)) {
            ReplaceMng::getInstance()->setFromArrayData($data['replaceData']);
        } else {
            Log::info('S3 data not well formed: replace not found.');
            return false;
        }

        if (array_key_exists('report', $data)) {
            $this->report = $data['report'];
        } else {
            Log::info('S3 data not well formed: report not found.');
            return false;
        }

        return true;
    }

    /**
     * Reset step data
     *
     * @return boolean
     */
    public static function resetData()
    {
        $result = true;
        if (file_exists(self::getS3dataFilePath())) {
            if (@unlink(self::getS3dataFilePath()) === false) {
                Log::info('Can\'t delete s3 data file');
                $result = false;
            }
        }

        if (file_exists($GLOBALS["CHUNK_DATA_FILE_PATH"])) {
            if (@unlink($GLOBALS["CHUNK_DATA_FILE_PATH"]) === false) {
                Log::info('Can\'t delete s3 chunk file');
                $result = false;
            }
        }
        return $result;
    }

    /**
     * Get json report
     *
     * @return array{step3: mixed[]}
     */
    public function getJsonReport(): array
    {
        $this->report['warn_all'] = empty($this->report['warnlist']) ? 0 : count($this->report['warnlist']);

        return [
            'step3' => $this->report,
        ];
    }

    /**
     * Add section headr to log
     *
     * @param string $title Section title
     * @param string $func  fun
     * @param int    $line  line
     *
     * @return void
     */
    private static function logSectionHeader(string $title, string $func, int $line): void
    {
        $log = "\n" . '====================================' . "\n" .
            $title;

        if (Log::isLevel(Log::LV_DETAILED)) {
            $log .= ' [FUNC: ' . $func . ' L:' . $line . ']';
        }
        $log .= "\n" .
            '====================================';
        Log::info($log);
    }

    /**
     * Open db connection if is closed
     *
     * @return mysqli connection handle
     */
    private function dbConnection()
    {
        if (is_null($this->dbh)) {
            $this->dbh = DUPX_DB_Functions::getInstance()->dbConnection();
        }
        return $this->dbh;
    }

    /**
     * Get db connection handle
     *
     * @return mysqli connection handle
     */
    public function getDbConnection()
    {
        return $this->dbConnection();
    }

    /**
     * Close db connection if is open
     *
     * @return void
     */
    public function closeDbConnection(): void
    {
        DUPX_DB_Functions::getInstance()->closeDbConnection();
        $this->dbh = null;
    }

    /**
     * Step 3 log header
     *
     * @return void
     */
    public function initLog(): void
    {
        $paramsManager = PrmMng::getInstance();
        $labelPadSize  = 22;

        $charsetServer = "Not Available";
        $charsetClient = "Not Available";
        if (!InstState::dbDoNothing()) {
            $this->dbConnection();
            $charsetServer = @mysqli_character_set_name($this->dbh);
            $charsetClient = @mysqli_character_set_name($this->dbh);
        }

        //LOGGING
        $date = @date('h:i:s');
        $log  = "\n\n" .
            "********************************************************************************\n" .
            "DUPLICATOR PRO INSTALL-LOG\n" .
            "STEP-3 START @ " . $date . "\n" .
            "NOTICE: Do NOT post to public sites or forums\n" .
            "********************************************************************************\n" .
            "CHARSET SERVER:\t" . Log::v2str($charsetServer) . "\n" .
            "CHARSET CLIENT:\t" . Log::v2str($charsetClient) . "\n" .
            "********************************************************************************\n" .
            "OPTIONS:\n";

        $log .= str_pad('SKIP PATH REPLACE', $labelPadSize, '_', STR_PAD_RIGHT) . ': ' . Log::v2str($paramsManager->getValue(PrmMng::PARAM_SKIP_PATH_REPLACE)) . "\n";

        $wpConfigsKeys = [
            PrmMng::PARAM_WP_CONF_DISALLOW_FILE_EDIT,
            PrmMng::PARAM_WP_CONF_DISALLOW_FILE_MODS,
            PrmMng::PARAM_WP_CONF_AUTOSAVE_INTERVAL,
            PrmMng::PARAM_WP_CONF_WP_POST_REVISIONS,
            PrmMng::PARAM_WP_CONF_FORCE_SSL_ADMIN,
            PrmMng::PARAM_WP_CONF_WP_AUTO_UPDATE_CORE,
            PrmMng::PARAM_WP_CONF_WP_CACHE,
            PrmMng::PARAM_WP_CONF_WPCACHEHOME,
            PrmMng::PARAM_WP_CONF_WP_DEBUG,
            PrmMng::PARAM_WP_CONF_WP_DEBUG_LOG,
            PrmMng::PARAM_WP_CONF_WP_DEBUG_DISPLAY,
            PrmMng::PARAM_WP_CONF_WP_DISABLE_FATAL_ERROR_HANDLER,
            PrmMng::PARAM_WP_CONF_SCRIPT_DEBUG,
            PrmMng::PARAM_WP_CONF_CONCATENATE_SCRIPTS,
            PrmMng::PARAM_WP_CONF_SAVEQUERIES,
            PrmMng::PARAM_WP_CONF_ALTERNATE_WP_CRON,
            PrmMng::PARAM_WP_CONF_DISABLE_WP_CRON,
            PrmMng::PARAM_WP_CONF_WP_CRON_LOCK_TIMEOUT,
            PrmMng::PARAM_WP_CONF_COOKIE_DOMAIN,
            PrmMng::PARAM_WP_CONF_WP_MEMORY_LIMIT,
            PrmMng::PARAM_WP_CONF_WP_MAX_MEMORY_LIMIT,
            PrmMng::PARAM_WP_CONF_WP_TEMP_DIR,
        ];
        foreach ($wpConfigsKeys as $key) {
            $label = $paramsManager->getLabel($key);
            $value = SnapString::implodeKeyVals(', ', $paramsManager->getValue($key), '[%s = %s]');
            $log  .= str_pad($label, $labelPadSize, '_', STR_PAD_RIGHT) . ': ' . $value . "\n";
        }
        $log .= "********************************************************************************\n";

        Log::info($log);

        $log    .= "--------------------------------------\n";
        $log    .= "KEEP PLUGINS ACTIVE\n";
        $log    .= "--------------------------------------\n";
        $plugins = $paramsManager->getValue(PrmMng::PARAM_PLUGINS);
        $log    .= (count($plugins) > 0 ? Log::v2str($plugins) : 'No plugins selected for activation');
        Log::info($log, Log::LV_DETAILED);
        Log::flush();
    }

    /**
     * Init chunk log
     *
     * @param int $maxIteration max iteration
     * @param int $timeOut      time out
     * @param int $throttling   throttling
     * @param int $rowsPerPage  rows per page
     *
     * @return void
     */
    public function initChunkLog($maxIteration, $timeOut, $throttling, $rowsPerPage): void
    {
        $log  = "********************************************************************************\n" .
            "CHUNK PARAMS:\n";
        $log .= str_pad('maxIteration', 22, '_', STR_PAD_RIGHT) . ': ' . Log::v2str($maxIteration) . "\n";
        $log .= str_pad('timeOut', 22, '_', STR_PAD_RIGHT) . ': ' . Log::v2str($timeOut) . "\n";
        $log .= str_pad('throttling', 22, '_', STR_PAD_RIGHT) . ': ' . Log::v2str($throttling) . "\n";
        $log .= str_pad('rowsPerPage', 22, '_', STR_PAD_RIGHT) . ': ' . Log::v2str($rowsPerPage) . "\n";
        $log .= "********************************************************************************\n";
        Log::info($log);
    }

    /**
     * Set replace list
     *
     * @return void
     */
    public function setReplaceList(): void
    {
        if ($this->getEngineMode() === self::MODE_SKIP) {
            return;
        }

        self::logSectionHeader('SET SEARCH AND REPLACE LIST INSTALL TYPE ' . InstState::installTypeToString(), __FUNCTION__, __LINE__);

        $dbReplace = new DbReplace();
        $dbReplace->setSearchReplace();
    }

    /**
     * Return engine mode
     *
     * @return int Enum MODE_NORAML|MODE_CHUNK|MODE_SKIP
     */
    public function getEngineMode()
    {
        return PrmMng::getInstance()->getValue(PrmMng::PARAM_REPLACE_ENGINE);
    }

    /**
     *
     * @return bool
     */
    public function isChunk(): bool
    {
        return PrmMng::getInstance()->getValue(PrmMng::PARAM_REPLACE_ENGINE) === self::MODE_CHUNK;
    }

    /**
     * Run search and replace
     *
     * @return void
     */
    public function runSearchAndReplace(): void
    {
        self::logSectionHeader('RUN SEARCH AND REPLACE', __FUNCTION__, __LINE__);

        $tables = DUPX_DB_Tables::getInstance()->getReplaceTablesNames();

        DUPX_UpdateEngine::load($tables);
        DUPX_UpdateEngine::replaceSiteTable();
        DUPX_UpdateEngine::replaceBlogsTable();
        DUPX_UpdateEngine::logStats();
        DUPX_UpdateEngine::logErrors();
    }


    /**
     * Remove maintenance mode
     *
     * @return void
     */
    public function removeMaintenanceMode(): void
    {
        self::logSectionHeader('REMOVE MAINTENANCE MODE', __FUNCTION__, __LINE__);
        DUPX_U::maintenanceMode(false);
    }

    /**
     * reset all users passwords
     *
     * @return void
     */
    protected function resetUsersPasswords(): void
    {
        self::logSectionHeader('RESET USERS PASSWORD', __FUNCTION__, __LINE__);

        $usersLoginsName = DUPX_ArchiveConfig::getInstance()->getUsersLists();
        foreach (PrmMng::getInstance()->getValue(PrmMng::PARAM_USERS_PWD_RESET) as $userId => $newPassword) {
            if (strlen($newPassword) > 0) {
                Log::info('RESET USER ID ' . $userId . ' NAME ' . $usersLoginsName[$userId] . ' PASSWORD');
                DUPX_DB_Functions::getInstance()->userPwdReset($userId, $newPassword);
            }
        }
    }

    /**
     * reset all users session tokens
     *
     * @return void
     */
    public function forceLogoutOfAllUsers(): void
    {
        Log::info('RESET ALL USERS SESSION TOKENS');
        // make sure dbConnection is initialized
        $this->dbConnection();
        $escapedTablePrefix = mysqli_real_escape_string($this->dbh, PrmMng::getInstance()->getValue(PrmMng::PARAM_DB_TABLE_PREFIX));

        try {
            DUPX_DB::chunksDelete($this->dbh, $escapedTablePrefix . 'usermeta', "meta_key='session_tokens'");
        } catch (Exception $e) {
            Log::info('RESET USER SESSION TOKENS EXCEPTION: ' . $e->getMessage());
        }
    }

    /**
     * create new admin user
     *
     * @return void
     */
    public function createNewAdminUser(): void
    {
        $this->resetUsersPasswords();

        if (!PrmMng::getInstance()->getValue(PrmMng::PARAM_WP_ADMIN_CREATE_NEW)) {
            return;
        }

        self::logSectionHeader('CREATE NEW ADMIN USER', __FUNCTION__, __LINE__);
        // make sure dbConnection is initialized
        $this->dbConnection();

        $nManager           = DUPX_NOTICE_MANAGER::getInstance();
        $escapedTablePrefix = mysqli_real_escape_string($this->dbh, PrmMng::getInstance()->getValue(PrmMng::PARAM_DB_TABLE_PREFIX));
        $archiveConfig      = DUPX_ArchiveConfig::getInstance();
        $paramsManager      = PrmMng::getInstance();

        $wpUserName        = $paramsManager->getValue(PrmMng::PARAM_WP_ADMIN_NAME);
        $wpUserNameEscaped = mysqli_real_escape_string($this->dbh, $wpUserName);

        $newuser_check = DUPX_DB::mysqli_query(
            $this->dbh,
            "SELECT COUNT(*) AS count FROM `" . $escapedTablePrefix . "users` WHERE user_login = '{$wpUserNameEscaped}' "
        );
        $newuser_row   = mysqli_fetch_row($newuser_check);
        $newuser_count = is_null($newuser_row) ? 0 : $newuser_row[0];

        if ($newuser_count == 0) {
            $newuser_datetime = @date("Y-m-d H:i:s");
            $newuser_datetime = mysqli_real_escape_string($this->dbh, $newuser_datetime);
            $newuser_security = mysqli_real_escape_string($this->dbh, DUPX_WPConfig::ADMIN_SERIALIZED_SECURITY_STRING);

            $post_wp_password = $paramsManager->getValue(PrmMng::PARAM_WP_ADMIN_PASSWORD);
            $post_wp_mail     = $paramsManager->getValue(PrmMng::PARAM_WP_ADMIN_MAIL);
            $post_wp_nickname = $paramsManager->getValue(PrmMng::PARAM_WP_ADMIN_NICKNAME);
            if (empty($post_wp_nickname)) {
                $post_wp_nickname = $wpUserName;
            }
            $post_wp_first_name = $paramsManager->getValue(PrmMng::PARAM_WP_ADMIN_FIRST_NAME);
            $post_wp_last_name  = $paramsManager->getValue(PrmMng::PARAM_WP_ADMIN_LAST_NAME);

            $wp_password   = mysqli_real_escape_string($this->dbh, $post_wp_password);
            $wp_mail       = mysqli_real_escape_string($this->dbh, $post_wp_mail);
            $wp_nickname   = mysqli_real_escape_string($this->dbh, $post_wp_nickname);
            $wp_first_name = mysqli_real_escape_string($this->dbh, $post_wp_first_name);
            $wp_last_name  = mysqli_real_escape_string($this->dbh, $post_wp_last_name);

            $usermeta_table = $escapedTablePrefix . 'usermeta';

            $newuser1 = DUPX_DB::mysqli_query(
                $this->dbh,
                "INSERT INTO `" . $escapedTablePrefix . "users`
                        (`user_login`, `user_pass`, `user_nicename`, `user_email`, `user_registered`, `user_activation_key`, `user_status`, `display_name`)
                        VALUES ('{$wpUserNameEscaped}', MD5('{$wp_password}'), '{$wpUserNameEscaped}', '{$wp_mail}', '{$newuser_datetime}', '', '0', '{$wpUserNameEscaped}')"
            );

            $newuser1_insert_id = intval(mysqli_insert_id($this->dbh));

            $newuser2 = DUPX_DB::mysqli_query(
                $this->dbh,
                "INSERT INTO `" . $usermeta_table . "`
                        (`user_id`, `meta_key`, `meta_value`) VALUES ('{$newuser1_insert_id}', '" . $escapedTablePrefix . "capabilities', '{$newuser_security}')"
            );

            $newuser3 = DUPX_DB::mysqli_query(
                $this->dbh,
                "INSERT INTO `" . $usermeta_table . "`
                        (`user_id`, `meta_key`, `meta_value`) VALUES ('{$newuser1_insert_id}', '" . $escapedTablePrefix . "user_level', '10')"
            );

            //Misc Meta-Data Settings:
            DUPX_DB::mysqli_query($this->dbh, "INSERT INTO `" . $usermeta_table . "` (`user_id`, `meta_key`, `meta_value`) VALUES ('{$newuser1_insert_id}', 'rich_editing', 'true')");
            DUPX_DB::mysqli_query($this->dbh, "INSERT INTO `" . $usermeta_table . "` (`user_id`, `meta_key`, `meta_value`) VALUES ('{$newuser1_insert_id}', 'admin_color',  'fresh')");
            DUPX_DB::mysqli_query($this->dbh, "INSERT INTO `" . $usermeta_table . "` (`user_id`, `meta_key`, `meta_value`) VALUES ('{$newuser1_insert_id}', 'nickname', '{$wp_nickname}')");
            DUPX_DB::mysqli_query($this->dbh, "INSERT INTO `" . $usermeta_table . "` (`user_id`, `meta_key`, `meta_value`) VALUES ('{$newuser1_insert_id}', 'first_name', '{$wp_first_name}')");
            DUPX_DB::mysqli_query($this->dbh, "INSERT INTO `" . $usermeta_table . "` (`user_id`, `meta_key`, `meta_value`) VALUES ('{$newuser1_insert_id}', 'last_name', '{$wp_last_name}')");

            //Add super admin permissions
            if (InstState::isNewSiteIsMultisite()) {
                $site_admins_query = DUPX_DB::mysqli_query($this->dbh, "SELECT meta_value FROM `" . $escapedTablePrefix . "sitemeta` WHERE meta_key = 'site_admins'");
                $site_admins       = mysqli_fetch_row($site_admins_query);
                $site_admins[0]    = stripslashes($site_admins[0]);
                $site_admins_array = unserialize($site_admins[0]);

                array_push($site_admins_array, $wpUserName);

                $site_admins_serialized = serialize($site_admins_array);

                DUPX_DB::mysqli_query($this->dbh, "UPDATE `" . $escapedTablePrefix . "sitemeta` SET meta_value = '{$site_admins_serialized}' WHERE meta_key = 'site_admins'");
                // Adding permission for each sub-site to the newly created user
                $admin_user_level   = DUPX_WPConfig::ADMIN_LEVEL; // For wp_2_user_level
                $sql_values_array   = [];
                $sql_values_array[] = "('{$newuser1_insert_id}', 'primary_blog', '{$archiveConfig->main_site_id}')";
                foreach ($archiveConfig->subsites as $subsite_info) {
                    // No need to add permission for main site
                    if ($subsite_info->id == $archiveConfig->main_site_id) {
                        continue;
                    }
                    $escapeBlogPrefix = mysqli_real_escape_string($this->dbh, $archiveConfig->getSubsitePrefixByParam($subsite_info->id));

                    $cap_meta_key       = $escapeBlogPrefix . 'capabilities';
                    $sql_values_array[] = "('{$newuser1_insert_id}', '{$cap_meta_key}', '{$newuser_security}')";

                    $user_level_meta_key = $escapeBlogPrefix . 'user_level';
                    $sql_values_array[]  = "('{$newuser1_insert_id}', '{$user_level_meta_key}', '{$admin_user_level}')";
                }
                $sql = "INSERT INTO " . $escapedTablePrefix . "usermeta (user_id, meta_key, meta_value) VALUES " . implode(', ', $sql_values_array);
                DUPX_DB::mysqli_query($this->dbh, $sql);
            }

            Log::info("\nNEW WP-ADMIN USER:");
            if ($newuser1 && $newuser2 && $newuser3) {
                Log::info("- New username '{$wpUserName}' was created successfully allong with MU usermeta.");
            } elseif ($newuser1) {
                Log::info("- New username '{$wpUserName}' was created successfully.");
            } else {
                $newuser_warnmsg            = "- Failed to create the user '{$wpUserName}' \n ";
                $this->report['warnlist'][] = $newuser_warnmsg;

                $nManager->addFinalReportNotice([
                    'shortMsg' => 'New admin user create error',
                    'level'    => DUPX_NOTICE_ITEM::HARD_WARNING,
                    'longMsg'  => $newuser_warnmsg,
                    'sections' => 'general',
                ], DUPX_NOTICE_MANAGER::ADD_UNIQUE_UPDATE, 'new-user-create-error');

                Log::info($newuser_warnmsg);
            }
        } else {
            $newuser_warnmsg            = "\nNEW WP-ADMIN USER:\n - Username '{$wpUserName}' already exists in the database.  Unable to create new account.\n";
            $this->report['warnlist'][] = $newuser_warnmsg;

            $nManager->addFinalReportNotice([
                'shortMsg'    => 'New admin user create error',
                'level'       => DUPX_NOTICE_ITEM::SOFT_WARNING,
                'longMsg'     => $newuser_warnmsg,
                'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_PRE,
                'sections'    => 'general',
            ], DUPX_NOTICE_MANAGER::ADD_UNIQUE_UPDATE, 'new-user-create-error');

            Log::info($newuser_warnmsg);
        }
    }

    /**
     * Update all config files
     *
     * @return void
     */
    public function configFilesUpdate(): void
    {
        $nManager = DUPX_NOTICE_MANAGER::getInstance();
        // SET FILES
        ServerConfigs::setFiles(PrmMng::getInstance()->getValue(PrmMng::PARAM_PATH_NEW));
        $wpConfigFile = DUPX_WPConfig::getWpConfigPath();

        // UPDATE FILES
        if (PrmMng::getInstance()->getValue(PrmMng::PARAM_WP_CONFIG) == 'nothing') {
            Log::info('SKIP WP CONFIG UPDATE');
        } elseif (file_exists(($wpConfigFile))) {
            if (SnapIO::chmod($wpConfigFile, 'u+rw') === false) {
                $err_log  = "\nWARNING: Unable to update file permissions and write to wp-config.php.  ";
                $err_log .= "Check that the wp-config.php is in the archive.zip and check with your host or administrator to enable PHP to write to the wp-config.php file.  ";
                $err_log .= "If performing a 'Manual Extraction' please be sure to select the 'Manual Archive Extraction' option on step 1 under options.";
                Log::error("{$err_log}");
            }
            $configTransformer = new WPConfigTransformer($wpConfigFile);
            $this->wpConfigUpdate($configTransformer);
            DUPX_Extraction::setPermsFromParams($wpConfigFile);
        } else {
            $msg  = "WP-CONFIG NOTICE: <b>wp-config.php not found.</b><br><br>";
            $msg .= "No action on the wp-config was possible.<br>";
            $msg .= "Be sure to insert a properly modified wp-config for correct wordpress operation.";

            $nManager->addBothNextAndFinalReportNotice([
                'shortMsg'    => 'wp-config.php file not found',
                'level'       => DUPX_NOTICE_ITEM::CRITICAL,
                'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_HTML,
                'longMsg'     => $msg,
                'sections'    => 'general',
            ]);
        }

        $this->htaccessUpdate();
        $this->indexPhpUpdate();
        DUPX_NOTICE_MANAGER::getInstance()->saveNotices();
    }

    /**
     * Update index.php file with right wp-blog-header include related to installation ABSPATH
     *
     * @return bool
     */
    protected function indexPhpUpdate(): bool
    {
        $paramsManager = PrmMng::getInstance();

        if (
            InstState::isRestoreBackup() ||
            InstState::isAddSiteOnMultisite()
        ) {
            return true;
        }

        self::logSectionHeader('INDEX.PHP UPDATE', __FUNCTION__, __LINE__);

        $pathNew   = $paramsManager->getValue(PrmMng::PARAM_PATH_NEW);
        $indexPath = $pathNew . '/index.php';

        if (!is_writable($indexPath)) {
            Log::info('index.php isn\'t writable');
            return false;
        }

        if (($relativeAbsPath = SnapIO::getRelativePath($paramsManager->getValue(PrmMng::PARAM_PATH_WP_CORE_NEW), $pathNew)) === false) {
            $blogHeaderValue = "'" . $paramsManager->getValue(PrmMng::PARAM_PATH_WP_CORE_NEW) . "/wp-blog-header.php'";
        } else {
            $relativeAbsPath = strlen($relativeAbsPath) ? '/' . $relativeAbsPath : '';
            $blogHeaderValue = "__DIR__ . '" . $relativeAbsPath . "/wp-blog-header.php'";
        }

        if (($indexContent = file_get_contents($indexPath)) === false) {
            Log::info('Can\'t read index.php content');
            return false;
        }
        $subst        = '$1require ' . $blogHeaderValue . ';$3';
        $indexContent = preg_replace('/^(.*\s)(require.*?[\'"].*wp-blog-header\.php[\'"].*?;)(.*)$/s', $subst, $indexContent);

        if (file_put_contents($indexPath, $indexContent) === false) {
            Log::info('Can\'t update index.php content');
            return false;
        }

        Log::info('INDEX.PHP updated with new blog header ' . Log::v2str($blogHeaderValue) . "\n");
        return true;
    }

    /**
     * Update the wp-config.php file
     *
     * @param WPConfigTransformer $confTransformer wp config transformer
     *
     * @return void
     */
    protected function wpConfigUpdate(WPConfigTransformer $confTransformer): void
    {
        self::logSectionHeader('CONFIGURATION FILE UPDATES', __FUNCTION__, __LINE__);
        Log::incIndent();

        $nManager      = DUPX_NOTICE_MANAGER::getInstance();
        $paramsManager = PrmMng::getInstance();
        $archiveConfig = DUPX_ArchiveConfig::getInstance();

        try {
            $this->configurationMultisiteUpdate($confTransformer);
            $this->configurationUrlsAndPaths($confTransformer);

            $dbhost    = DUPX_U::getEscapedGenericString($paramsManager->getValue(PrmMng::PARAM_DB_HOST));
            $dbname    = DUPX_U::getEscapedGenericString($paramsManager->getValue(PrmMng::PARAM_DB_NAME));
            $dbuser    = DUPX_U::getEscapedGenericString($paramsManager->getValue(PrmMng::PARAM_DB_USER));
            $dbpass    = DUPX_U::getEscapedGenericString($paramsManager->getValue(PrmMng::PARAM_DB_PASS));
            $dbcharset = $paramsManager->getValue(PrmMng::PARAM_DB_CHARSET);
            $dbcollate = $paramsManager->getValue(PrmMng::PARAM_DB_COLLATE);

            $confTransformer->update('constant', 'DB_NAME', $dbname, ['raw' => true]);
            Log::info('UPDATE DB_NAME ' . Log::v2str($dbname));

            $confTransformer->update('constant', 'DB_USER', $dbuser, ['raw' => true]);
            Log::info('UPDATE DB_USER ' . Log::v2str('** OBSCURED **'));

            $confTransformer->update('constant', 'DB_PASSWORD', $dbpass, ['raw' => true]);
            Log::info('UPDATE DB_PASSWORD ' . Log::v2str('** OBSCURED **'));

            $confTransformer->update('constant', 'DB_HOST', $dbhost, ['raw' => true]);
            Log::info('UPDATE DB_HOST ' . Log::v2str($dbhost));

            $confTransformer->update('constant', 'DB_CHARSET', $dbcharset);
            Log::info('UPDATE DB_CHARSET ' . Log::v2str($dbcharset));

            $confTransformer->update('constant', 'DB_COLLATE', $dbcollate);
            Log::info('UPDATE DB_COLLATE ' . Log::v2str($dbcollate));

            if (InstState::isRestoreBackup()) {
                Log::info("\nRESTORE BACKUP MODE: SKIP OTHER WP-CONFIGS UPDATE ***");
                Log::resetIndent();
                return;
            }

            $auth_keys = [
                'AUTH_KEY',
                'SECURE_AUTH_KEY',
                'LOGGED_IN_KEY',
                'NONCE_KEY',
                'AUTH_SALT',
                'SECURE_AUTH_SALT',
                'LOGGED_IN_SALT',
                'NONCE_SALT',
            ];

            if (License::can(License::CAPABILITY_UPDATE_AUTH) && $paramsManager->getValue(PrmMng::PARAM_GEN_WP_AUTH_KEY)) {
                foreach ($auth_keys as $const_key) {
                    $key = SnapUtil::generatePassword(64, true, true);

                    if ($confTransformer->exists('constant', $const_key)) {
                        $confTransformer->update('constant', $const_key, $key);
                        Log::info('UPDATE ' . $const_key . ' ' . Log::v2str('**OBSCURED**'));
                    } else {
                        $confTransformer->add('constant', $const_key, $key);
                        Log::info('ADD ' . $const_key . ' ' . Log::v2str('**OBSCURED**'));
                    }
                }
            } else {
                // FORCE OLD VALUES
                foreach ($auth_keys as $const_key) {
                    $confTransformer->update('constant', $const_key, $archiveConfig->getDefineValue($const_key, ''));
                }
            }

            $confTransformer->update('variable', 'table_prefix', $paramsManager->getValue(PrmMng::PARAM_DB_TABLE_PREFIX));

            DUPX_ArchiveConfig::updateWpConfigByParam($confTransformer, 'DISALLOW_FILE_EDIT', PrmMng::PARAM_WP_CONF_DISALLOW_FILE_EDIT);
            DUPX_ArchiveConfig::updateWpConfigByParam($confTransformer, 'DISALLOW_FILE_MODS', PrmMng::PARAM_WP_CONF_DISALLOW_FILE_MODS);
            DUPX_ArchiveConfig::updateWpConfigByParam($confTransformer, 'FORCE_SSL_ADMIN', PrmMng::PARAM_WP_CONF_FORCE_SSL_ADMIN);
            DUPX_ArchiveConfig::updateWpConfigByParam($confTransformer, 'IMAGE_EDIT_OVERWRITE', PrmMng::PARAM_WP_CONF_IMAGE_EDIT_OVERWRITE);
            DUPX_ArchiveConfig::updateWpConfigByParam($confTransformer, 'WP_CACHE', PrmMng::PARAM_WP_CONF_WP_CACHE);
            DUPX_ArchiveConfig::updateWpConfigByParam($confTransformer, 'WPCACHEHOME', PrmMng::PARAM_WP_CONF_WPCACHEHOME);
            DUPX_ArchiveConfig::updateWpConfigByParam($confTransformer, 'COOKIE_DOMAIN', PrmMng::PARAM_WP_CONF_COOKIE_DOMAIN);
            DUPX_ArchiveConfig::updateWpConfigByParam($confTransformer, 'AUTOSAVE_INTERVAL', PrmMng::PARAM_WP_CONF_AUTOSAVE_INTERVAL);
            DUPX_ArchiveConfig::updateWpConfigByParam($confTransformer, 'WP_POST_REVISIONS', PrmMng::PARAM_WP_CONF_WP_POST_REVISIONS);
            DUPX_ArchiveConfig::updateWpConfigByParam($confTransformer, 'WP_DEBUG', PrmMng::PARAM_WP_CONF_WP_DEBUG);
            DUPX_ArchiveConfig::updateWpConfigByParam($confTransformer, 'WP_DEBUG_LOG', PrmMng::PARAM_WP_CONF_WP_DEBUG_LOG);
            DUPX_ArchiveConfig::updateWpConfigByParam($confTransformer, 'WP_DISABLE_FATAL_ERROR_HANDLER', PrmMng::PARAM_WP_CONF_WP_DISABLE_FATAL_ERROR_HANDLER);
            DUPX_ArchiveConfig::updateWpConfigByParam($confTransformer, 'WP_DEBUG_DISPLAY', PrmMng::PARAM_WP_CONF_WP_DEBUG_DISPLAY);
            DUPX_ArchiveConfig::updateWpConfigByParam($confTransformer, 'SCRIPT_DEBUG', PrmMng::PARAM_WP_CONF_SCRIPT_DEBUG);
            DUPX_ArchiveConfig::updateWpConfigByParam($confTransformer, 'CONCATENATE_SCRIPTS', PrmMng::PARAM_WP_CONF_CONCATENATE_SCRIPTS);
            DUPX_ArchiveConfig::updateWpConfigByParam($confTransformer, 'SAVEQUERIES', PrmMng::PARAM_WP_CONF_SAVEQUERIES);
            DUPX_ArchiveConfig::updateWpConfigByParam($confTransformer, 'ALTERNATE_WP_CRON', PrmMng::PARAM_WP_CONF_ALTERNATE_WP_CRON);
            DUPX_ArchiveConfig::updateWpConfigByParam($confTransformer, 'DISABLE_WP_CRON', PrmMng::PARAM_WP_CONF_DISABLE_WP_CRON);
            DUPX_ArchiveConfig::updateWpConfigByParam($confTransformer, 'WP_CRON_LOCK_TIMEOUT', PrmMng::PARAM_WP_CONF_WP_CRON_LOCK_TIMEOUT);
            DUPX_ArchiveConfig::updateWpConfigByParam($confTransformer, 'EMPTY_TRASH_DAYS', PrmMng::PARAM_WP_CONF_EMPTY_TRASH_DAYS);
            DUPX_ArchiveConfig::updateWpConfigByParam($confTransformer, 'WP_MEMORY_LIMIT', PrmMng::PARAM_WP_CONF_WP_MEMORY_LIMIT);
            DUPX_ArchiveConfig::updateWpConfigByParam($confTransformer, 'WP_MAX_MEMORY_LIMIT', PrmMng::PARAM_WP_CONF_WP_MAX_MEMORY_LIMIT);
            DUPX_ArchiveConfig::updateWpConfigByParam($confTransformer, 'WP_TEMP_DIR', PrmMng::PARAM_WP_CONF_WP_TEMP_DIR);
            DUPX_ArchiveConfig::updateWpConfigByParam($confTransformer, 'AUTOMATIC_UPDATER_DISABLED', PrmMng::PARAM_WP_CONF_AUTOMATIC_UPDATER_DISABLED);

            $wpConfigValue = $paramsManager->getValue(PrmMng::PARAM_WP_CONF_WP_AUTO_UPDATE_CORE);
            switch ($wpConfigValue['value']) {
                case 'false':
                    $wpConfigValue['value'] = false;
                    break;
                case 'true':
                    $wpConfigValue['value'] = true;
                    break;
                case 'minor':
                default:
                    $wpConfigValue['value'] = 'minor';
                    break;
            }
            DUPX_ArchiveConfig::updateWpConfigByValue($confTransformer, 'WP_AUTO_UPDATE_CORE', $wpConfigValue);

            $wpConfigValue = $paramsManager->getValue(PrmMng::PARAM_WP_CONF_MYSQL_CLIENT_FLAGS);
            $constantValue = implode(' | ', SnapDB::getMysqlConnectFlagsList(true, $wpConfigValue['value']));
            DUPX_ArchiveConfig::updateWpConfigByValue($confTransformer, 'MYSQL_CLIENT_FLAGS', $wpConfigValue, $constantValue);

            Log::info("\n*** UPDATED WP CONFIG FILE ***");
        } catch (Exception $e) {
            $shortMsg = 'wp-config.php transformer:' . $e->getMessage();
            $longMsg  = <<<LONGMSG
Error updating wp-config file.<br>
The installation is finished but check the wp-config.php file and manually update the incorrect values.
LONGMSG;
            /*    $nManager->addNextStepNotice(array(
              'shortMsg' => $shortMsg,
              'level' => DUPX_NOTICE_ITEM::CRITICAL,

              ), DUPX_NOTICE_MANAGER::ADD_UNIQUE , 'wp-config-transformer-exception'); */
            $nManager->addFinalReportNotice([
                'shortMsg'    => $shortMsg,
                'level'       => DUPX_NOTICE_ITEM::CRITICAL,
                'longMsg'     => $longMsg,
                'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_HTML,
                'sections'    => 'general',
            ], DUPX_NOTICE_MANAGER::ADD_UNIQUE, 'wp-config-transformer-exception');

            Log::info("WP-CONFIG TRANSFORMER EXCEPTION\n" . $e->getTraceAsString());
        }
        Log::resetIndent();
    }

    /**
     * Update wp-config.php file for multisite
     *
     * @param WPConfigTransformer $confTransformer WPConfigTransformer instance
     *
     * @return void
     */
    protected function configurationMultisiteUpdate(WPConfigTransformer $confTransformer): void
    {
        $muDefines = [
            'WP_ALLOW_MULTISITE',
            'ALLOW_MULTISITE',
            'MULTISITE',
            'DOMAIN_CURRENT_SITE',
            'PATH_CURRENT_SITE',
            'SITE_ID_CURRENT_SITE',
            'BLOG_ID_CURRENT_SITE',
            'NOBLOGREDIRECT',
            'SUBDOMAIN_INSTALL',
            'VHOST',
            'SUNRISE',
            'COOKIEPATH',
            'SITECOOKIEPATH',
            'ADMIN_COOKIE_PATH',
            'PLUGINS_COOKIE_PATH',
        ];

        /**
         * if is single site clean all mu site define
         */
        if (!InstState::isNewSiteIsMultisite()) {
            foreach ($muDefines as $key) {
                if ($confTransformer->exists('constant', $key)) {
                    $confTransformer->remove('constant', $key);
                    Log::info('TRANSFORMER[no wpmu]: ' . $key . ' constant removed from WP config file');
                }
            }
        } elseif (PrmMng::getInstance()->getValue(PrmMng::PARAM_WP_CONFIG) == 'new') {
            Log::info('TRANSFORMER[wpmu]: new wp-config from sample');
            $archiveConfig = DUPX_ArchiveConfig::getInstance();

            foreach ($muDefines as $key) {
                DUPX_ArchiveConfig::updateWpConfigByValue($confTransformer, $key, $archiveConfig->getDefineArrayValue($key));
            }
        }
    }

    /**
     * Updat wp-config.php file with new urls and paths
     *
     * @param WPConfigTransformer $confTransformer WPConfigTransformer instance
     *
     * @return void
     */
    protected function configurationUrlsAndPaths(WPConfigTransformer $confTransformer): void
    {
        $paramsManager = PrmMng::getInstance();

        $urlNew  = $paramsManager->getValue(PrmMng::PARAM_URL_NEW);
        $pathNew = $paramsManager->getValue(PrmMng::PARAM_PATH_NEW);

        $absPathNew = $paramsManager->getValue(PrmMng::PARAM_PATH_WP_CORE_NEW);
        $absUrlNew  = $paramsManager->getValue(PrmMng::PARAM_SITE_URL);

        $mu_newDomain     = parse_url($urlNew);
        $mu_newDomainHost = $mu_newDomain['host'];
        $mu_newUrlPath    = parse_url($urlNew, PHP_URL_PATH);

        $mu_newUrlPath = empty($mu_newUrlPath) || $mu_newUrlPath == '/' ? '/' : rtrim($mu_newUrlPath, '/') . '/';

        if ($confTransformer->exists('constant', 'ABSPATH')) {
            if (($relativeAbsPath = SnapIO::getRelativePath($absPathNew, $pathNew)) === false) {
                $absPathValue = "'" . $absPathNew . "'";
            } else {
                $absPathValue = "__DIR__ . '/" . $relativeAbsPath . "'";
            }
            $confTransformer->update('constant', 'ABSPATH', $absPathValue, ['raw' => true]);
            Log::info('UPDATE ABSPATH ' . Log::v2str($absPathValue));
        }

        if ($confTransformer->exists('constant', 'WP_HOME')) {
            $confTransformer->update('constant', 'WP_HOME', $urlNew, ['normalize' => true, 'add' => true]);
            Log::info('UPDATE WP_HOME ' . Log::v2str($urlNew));
        }

        if ($confTransformer->exists('constant', 'WP_SITEURL') || $urlNew != $absUrlNew) {
            $confTransformer->update('constant', 'WP_SITEURL', $absUrlNew, ['normalize' => true, 'add' => true]);
            Log::info('UPDATE WP_SITEURL ' . Log::v2str($absUrlNew));
        }

        if ($confTransformer->exists('constant', 'DOMAIN_CURRENT_SITE')) {
            $confTransformer->update('constant', 'DOMAIN_CURRENT_SITE', $mu_newDomainHost, ['normalize' => true, 'add' => true]);
            Log::info('UPDATE DOMAIN_CURRENT_SITE ' . Log::v2str($mu_newDomainHost));
        }

        if ($confTransformer->exists('constant', 'PATH_CURRENT_SITE')) {
            $confTransformer->update('constant', 'PATH_CURRENT_SITE', $mu_newUrlPath, ['normalize' => true, 'add' => true]);
            Log::info('UPDATE PATH_CURRENT_SITE ' . Log::v2str($mu_newUrlPath));
        }

        $pathContent    = $paramsManager->getValue(PrmMng::PARAM_PATH_CONTENT_NEW);
        $pathContentDef = $absPathNew . '/wp-content';
        if ($confTransformer->exists('constant', 'WP_CONTENT_DIR') || $pathContentDef != $pathContent) {
            $confTransformer->update('constant', 'WP_CONTENT_DIR', $pathContent, ['normalize' => true, 'add' => true]);
            Log::info('UPDATE WP_CONTENT_DIR ' . Log::v2str($pathContent));
        }

        $urlContent    = $paramsManager->getValue(PrmMng::PARAM_URL_CONTENT_NEW);
        $urlContentDef =  $absUrlNew . '/wp-content';
        if ($confTransformer->exists('constant', 'WP_CONTENT_URL') || $urlContentDef != $urlContent) {
            $confTransformer->update('constant', 'WP_CONTENT_URL', $urlContent, ['normalize' => true, 'add' => true]);
            Log::info('UPDATE WP_CONTENT_URL ' . Log::v2str($urlContent));
        }

        $pathPlugins    = $paramsManager->getValue(PrmMng::PARAM_PATH_PLUGINS_NEW);
        $pathPluginsDef = $pathContentDef . '/plugins';
        if ($confTransformer->exists('constant', 'WP_PLUGIN_DIR') || $pathPluginsDef != $pathPlugins) {
            $confTransformer->update('constant', 'WP_PLUGIN_DIR', $pathPlugins, ['normalize' => true, 'add' => true]);
            Log::info('UPDATE WP_PLUGIN_DIR ' . Log::v2str($pathPlugins));
        }

        $urlPlugins    = $paramsManager->getValue(PrmMng::PARAM_URL_PLUGINS_NEW);
        $urlPluginsDef = $urlContentDef . '/plugins';
        if ($confTransformer->exists('constant', 'WP_PLUGIN_URL') || $urlPluginsDef != $urlPlugins) {
            $confTransformer->update('constant', 'WP_PLUGIN_URL', $urlPlugins, ['normalize' => true, 'add' => true]);
            Log::info('UPDATE WP_PLUGIN_URL ' . Log::v2str($urlPlugins));
        }

        $pathMuPlugins    = $paramsManager->getValue(PrmMng::PARAM_PATH_MUPLUGINS_NEW);
        $pathMuPluginsDef = $pathContentDef . '/mu-plugins';
        if ($confTransformer->exists('constant', 'WPMU_PLUGIN_DIR') || $pathMuPluginsDef != $pathMuPlugins) {
            $confTransformer->update('constant', 'WPMU_PLUGIN_DIR', $pathMuPlugins, ['normalize' => true, 'add' => true]);
            Log::info('UPDATE WPMU_PLUGIN_DIR ' . Log::v2str($pathMuPlugins));
        }

        $urlMuPlugins    = $paramsManager->getValue(PrmMng::PARAM_URL_MUPLUGINS_NEW);
        $urlMuPluginsDef = $urlContentDef . '/mu-plugins';
        if ($confTransformer->exists('constant', 'WPMU_PLUGIN_URL') || $urlMuPluginsDef != $urlMuPlugins) {
            $confTransformer->update('constant', 'WPMU_PLUGIN_URL', $urlMuPlugins, ['normalize' => true, 'add' => true]);
            Log::info('UPDATE WPMU_PLUGIN_URL ' . Log::v2str($urlMuPlugins));
        }
    }

    /**
     * Update the .htaccess file
     *
     * @return void
     */
    protected function htaccessUpdate(): void
    {
        self::logSectionHeader('HTACCESS UPDATE', __FUNCTION__, __LINE__);
        // make sure dbConnection is initialized
        $this->dbConnection();

        ServerConfigs::setup($this->dbh, PrmMng::getInstance()->getValue(PrmMng::PARAM_PATH_NEW));
    }

    /**
     * Update the blogname in the database
     *
     * @return void
     */
    protected function updateBlogName(): void
    {
        if (InstState::isAddSiteOnMultisite()) {
            return;
        }

        $paramsManager = PrmMng::getInstance();

        $escapedOptionTable = mysqli_real_escape_string($this->dbh, DUPX_DB_Functions::getOptionsTableName());
        $escapedBlogName    = htmlspecialchars($paramsManager->getValue(PrmMng::PARAM_BLOGNAME), ENT_QUOTES);
        $escapedBlogName    = mysqli_real_escape_string($this->dbh, $escapedBlogName);

        Log::info('UPATE BLOG NAME ' . Log::v2str($escapedBlogName), Log::LV_DETAILED);
        DUPX_DB::mysqli_query(
            $this->dbh,
            "UPDATE `" . $escapedOptionTable .
                "` SET option_value = '" . mysqli_real_escape_string($this->dbh, $escapedBlogName) .
                "' WHERE option_name = 'blogname' "
        );
    }

    /**
     * Update options URLs
     *
     * @param string  $urlNew  new url
     * @param string  $siteUrl site url
     * @param ?string $prefix  table prefix, if null is wp main prefix
     *
     * @return void
     */
    protected function updateOptionsUrls($urlNew, $siteUrl, $prefix = null): void
    {
        $paramsManager = PrmMng::getInstance();

        Log::info('UPATE URL NEW ' . Log::v2str($urlNew), Log::LV_DETAILED);
        DbUtils::updateWpOption($this->dbh, 'home', $urlNew, $prefix);
        Log::info('UPATE SITE URL ' . Log::v2str($siteUrl), Log::LV_DETAILED);
        DbUtils::updateWpOption($this->dbh, 'siteurl', $siteUrl, $prefix);
        DbUtils::updateWpOption($this->dbh, 'dupli_opt_exe_safe_mode', $paramsManager->getValue(PrmMng::PARAM_SAFE_MODE));
    }

    /**
     * Update post GUID
     *
     * @param string $table  table name
     * @param string $urlNew new url
     *
     * @return void
     */
    protected function updatePostsGuid(string $table, $urlNew): void
    {
        $paramsManager = PrmMng::getInstance();

        //Reset the postguid data
        if (!$paramsManager->getValue(PrmMng::PARAM_POSTGUID)) {
            return;
        }
        $escapedPostsTable = mysqli_real_escape_string($this->dbh, $table);

        Log::info('UPATE postguid');
        DUPX_DB::mysqli_query(
            $this->dbh,
            "UPDATE `" . $escapedPostsTable . "` SET guid = REPLACE(guid, '" . mysqli_real_escape_string($this->dbh, $urlNew) . "', '" . mysqli_real_escape_string(
                $this->dbh,
                $paramsManager->getValue(PrmMng::PARAM_URL_OLD)
            ) . "')"
        );
        $update_guid = @mysqli_affected_rows($this->dbh) or 0;
        Log::info("Reverted '{$update_guid}' post guid columns back to '" . $paramsManager->getValue(PrmMng::PARAM_URL_OLD) . "'");
    }

    /**
     * Genral db update
     *
     * @return void
     */
    public function generalUpdate(): void
    {
        self::logSectionHeader('GENERAL UPDATES', __FUNCTION__, __LINE__);
        // make sure dbConnection is initialized
        $this->dbConnection();

        $this->updateBlogName();

        $paramsManager = PrmMng::getInstance();

        if (InstState::isAddSiteOnMultisite()) {
            /** @var SiteOwrMap[] $overwriteMapping */
            $overwriteMapping = PrmMng::getInstance()->getValue(PrmMng::PARAM_SUBSITE_OVERWRITE_MAPPING);

            foreach ($overwriteMapping as $map) {
                if (($targetInfo = $map->getTargetSiteInfo()) == false) {
                    throw new Exception('Target site info ' . $map->getTargetId() . ' don\'t exists');
                }

                $urlNew  = $targetInfo['fullHomeUrl'];
                $siteUrl = $targetInfo['fullSiteUrl'];

                $this->updateOptionsUrls(
                    $urlNew,
                    $siteUrl,
                    $targetInfo['blog_prefix']
                );
                $this->updatePostsGuid(
                    DUPX_DB_Functions::getPostsTableName($targetInfo['blog_prefix']),
                    $urlNew
                );
            }
        } else {
            $urlNew  = $paramsManager->getValue(PrmMng::PARAM_URL_NEW);
            $siteUrl = $paramsManager->getValue(PrmMng::PARAM_SITE_URL);

            $this->updateOptionsUrls(
                $urlNew,
                $siteUrl
            );
            $this->updatePostsGuid(
                DUPX_DB_Functions::getPostsTableName(),
                $urlNew
            );
        }

        $this->managePlugins();
    }

    /**
     * Migration info set
     *
     * @return bool true if success
     */
    public function duplicatorMigrationInfoSet(): bool
    {
        Log::info('MIGRATION INFO SET');
        // make sure dbConnection is initialized
        $this->dbConnection();

        // on main options tables in all installation
        $optionTable   = mysqli_real_escape_string($this->dbh, DUPX_DB_Functions::getOptionsTableName());
        $migrationData = InstState::getMigrationData();
        $json          = JsonSerialize::serialize($migrationData, JSON_PRETTY_PRINT | JsonSerialize::JSON_SKIP_CLASS_NAME);

        $query = "REPLACE INTO `" . $optionTable . "` (`option_id`, `option_name`, `option_value`, `autoload`) VALUES " .
            "(NULL, '" . self::FIRST_LOGIN_OPTION . "', '1', 'no'), " .
            "(NULL, '" . self::MIGRATION_DATA_OPTION . "', '" . mysqli_real_escape_string($this->dbh, $json) . "', 'no');";

        if (DUPX_DB::mysqli_query($this->dbh, $query) === false) {
            $errMsg = "DATABASE ERROR \"" . mysqli_error($this->dbh) . "\"<br>[sql=" . substr($query, 0, DUPX_DBInstall::QUERY_ERROR_LOG_LEN) . "...]";
            DUPX_NOTICE_MANAGER::getInstance()->addBothNextAndFinalReportNotice([
                'shortMsg'    => 'UPDATE MIRATION INFO ISSUE',
                'level'       => DUPX_NOTICE_ITEM::SOFT_WARNING,
                'longMsg'     => $errMsg,
                'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_HTML,
                'sections'    => 'database',
            ]);

            return false;
        } else {
            return true;
        }
    }

    /**
     * General cleanup
     *
     * @return void
     */
    public function generalCleanup(): void
    {
        self::logSectionHeader('GENERAL CLEANUP', __FUNCTION__, __LINE__);
        // make sure dbConnection is initialized
        $this->dbConnection();
        $archiveConfig = DUPX_ArchiveConfig::getInstance();
        $paramsManager = PrmMng::getInstance();

        if (!DUPX_UpdateEngine::updateTablePrefixKeys()) {
            // @todo display erorr on notice manager
        }

        if (InstState::isInstType(InstState::TYPE_STANDALONE)) {
            Log::info('UPDATE DATA FOR STANDALONE MIGRATION');
            $siteId = $paramsManager->getValue(PrmMng::PARAM_SUBSITE_ID);
            Standalone::updateOptionsTable($siteId, $this->dbh);
            Standalone::purgeRedundantData($siteId, $this->dbh);
        }

        //SCHEDULE STORAGE CLEANUP
        if ($paramsManager->getValue(PrmMng::PARAM_EMPTY_SCHEDULE_STORAGE)) {
            $entitiesTable = mysqli_real_escape_string($this->dbh, DUPX_DB_Functions::getEntitiesTableName());

            DUPX_DB::mysqli_query(
                $this->dbh,
                "DELETE FROM `" . $entitiesTable . "` WHERE `type` = 'Storage_Entity' AND `id` != " . $archiveConfig->defaultStorageId
            );
            Log::info(" - REMOVED " . mysqli_affected_rows($this->dbh) . " storage items");

            DUPX_DB::mysqli_query(
                $this->dbh,
                "DELETE FROM `" . $entitiesTable . "` WHERE `type` = 'Schedule_Entity'"
            );
            Log::info(" - REMOVED " . mysqli_affected_rows($this->dbh) . " schedule items");
        }
    }

    /**
     * Activate and deactivate plugins
     *
     * @return void
     */
    protected function managePlugins(): void
    {
        self::logSectionHeader("MANAGE PLUGINS", __FUNCTION__, __LINE__);
        $paramsManager = PrmMng::getInstance();
        $subsite_id    = $paramsManager->getValue(PrmMng::PARAM_SUBSITE_ID);

        try {
            $pluginsManager = PluginsManager::getInstance();
            $pluginsManager->setActions($paramsManager->getValue(PrmMng::PARAM_PLUGINS), $subsite_id);
            $pluginsManager->preViewChecks($subsite_id);
            $pluginsManager->executeActions($this->dbConnection(), $subsite_id);
        } catch (Exception $e) {
            $nManager = DUPX_NOTICE_MANAGER::getInstance();
            $nManager->addFinalReportNotice([
                'shortMsg'    => 'Plugins settings error ' . $e->getMessage(),
                'level'       => DUPX_NOTICE_ITEM::CRITICAL,
                'longMsg'     => $e->getTraceAsString(),
                'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_PRE,
                'sections'    => 'general',
            ]);

            Log::info("PLUGIN MANAGER EXCEPTIOMN\n" . $e->getTraceAsString());
        }
    }

    /**
     * hecks for index.html in root, and if found, issues a soft warning
     *
     * @return void
     */
    public function checkForIndexHtml(): void
    {
        self::logSectionHeader('CHECK FOR INDEX.HTML', __FUNCTION__, __LINE__);

        //scan for index.html
        if (file_exists(PrmMng::getInstance()->getValue(PrmMng::PARAM_PATH_NEW) . '/index.html')) {
            $nManager = DUPX_NOTICE_MANAGER::getInstance();
            $nManager->addFinalReportNotice(
                [
                    'shortMsg'    => 'An index.html was found.',
                    'level'       => DUPX_NOTICE_ITEM::SOFT_WARNING,
                    'longMsg'     => 'An index.html was found in the existing site. You may need to manually remove it for the new site to work.',
                    'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_DEFAULT,
                    'sections'    => 'general',
                ]
            );

            Log::info("AN INDEX.HTML WAS FOUND IN THE ROOT OF THE INSTALLATION.");
        } else {
            Log::info("NO INDEX.HTML WAS FOUND");
        }
    }

    /**
     * Notice tests
     *
     * @return void
     */
    public function noticeTest(): void
    {
        self::logSectionHeader('NOTICES TEST', __FUNCTION__, __LINE__);
        // make sure dbConnection is initialized
        $this->dbConnection();
        $optonsTable = mysqli_real_escape_string($this->dbh, DUPX_DB_Functions::getOptionsTableName());

        $nManager = DUPX_NOTICE_MANAGER::getInstance();

        //Database
        $result = DUPX_DB::mysqli_query(
            $this->dbh,
            "SELECT option_value FROM `" . $optonsTable . "` WHERE option_name IN ('upload_url_path','uploadPath')"
        );
        if ($result) {
            while ($row = mysqli_fetch_row($result)) {
                if (strlen($row[0])) {
                    $msg  = "MEDIA SETTINGS NOTICE: The table '" . $optonsTable . "' has at least one the following values ['upload_url_path','uploadPath'] \n";
                    $msg .= "set please validate settings. These settings can be changed in the wp-admin by going to /wp-admin/options.php'";

                    $this->report['warnlist'][] = $msg;
                    Log::info($msg);

                    $nManager->addFinalReportNotice([
                        'shortMsg'    => 'Media settings notice',
                        'level'       => DUPX_NOTICE_ITEM::SOFT_WARNING,
                        'longMsg'     => $msg,
                        'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_PRE,
                        'sections'    => 'general',
                    ], DUPX_NOTICE_MANAGER::ADD_UNIQUE_UPDATE, 'media-settings-notice');

                    break;
                }
            }
        }

        if (empty($this->report['warnlist'])) {
            Log::info("No General Notices Found\n");
        }
    }

    /**
     * Remove redundant data
     *
     * @return void
     */
    protected function removeRedundant(): void
    {
        $paramsManager = PrmMng::getInstance();

        if ($paramsManager->getValue(PrmMng::PARAM_REMOVE_RENDUNDANT)) {
            self::logSectionHeader('REMOVE REDUNDANT', __FUNCTION__, __LINE__);

            // make sure maintenance mode is disabled
            DUPX_U::maintenanceMode(false);

            // Need to load if user selected redundant-data checkbox
            $nManager = DUPX_NOTICE_MANAGER::getInstance();

            try {
                CleanUp::removeUnusedPlugins();
            } catch (Exception $ex) {
                // Technically it can complete but this should be brought to their attention
                $errorMsg = "**EXCEPTION ERROR** The Inactive Plugins deletion failed";
                Log::info($errorMsg);
                $nManager->addFinalReportNotice([
                    'shortMsg' => $errorMsg,
                    'level'    => DUPX_NOTICE_ITEM::HARD_WARNING,
                    'longMsg'  => 'Please uninstall all inactive plugins manually',
                    'sections' => 'general',
                ]);
            }

            try {
                CleanUp::removeUnusedThemes();
            } catch (Exception $ex) {
                // Technically it can complete but this should be brought to their attention
                $errorMsg = "**EXCEPTION ERROR** The Inactive Themes deletion failed";
                Log::info($errorMsg);
                $nManager->addFinalReportNotice([
                    'shortMsg' => $errorMsg,
                    'level'    => DUPX_NOTICE_ITEM::HARD_WARNING,
                    'longMsg'  => 'Please uninstall all inactive themes manually',
                    'sections' => 'general',
                ]);
            } catch (Error $ex) {
                $errorMsg = "**FATAL ERROR** The Inactive Themes deletion failed";
                Log::info($errorMsg);
                $nManager->addFinalReportNotice([
                    'shortMsg' => $errorMsg,
                    'level'    => DUPX_NOTICE_ITEM::HARD_WARNING,
                    'longMsg'  => 'Please uninstall all inactive themes manually',
                    'sections' => 'general',
                ]);
            }
        }

        if ($paramsManager->getValue(PrmMng::PARAM_REMOVE_USERS_WITHOUT_PERMISSIONS)) {
            // make sure maintenance mode is disabled
            DUPX_U::maintenanceMode(false);

            // Need to load if user selected redundant-data checkbox
            $nManager = DUPX_NOTICE_MANAGER::getInstance();

            try {
                $siteId = $paramsManager->getValue(PrmMng::PARAM_SUBSITE_ID);
                CleanUp::removeUsersWithoutPermissions($siteId, $this->dbh);
            } catch (Exception $ex) {
                $errorMsg = "**EXCEPTION ERROR** Removing Users without permissions failed";
                Log::info($errorMsg);
                $nManager->addFinalReportNotice([
                    'shortMsg' => $errorMsg,
                    'level'    => DUPX_NOTICE_ITEM::HARD_WARNING,
                    'longMsg'  => 'Please remove all users without permissions manually',
                    'sections' => 'general',
                ]);
            } catch (Error $ex) {
                // Technically it can complete but this should be brought to their attention
                $errorMsg = "**FATAL ERROR** Removing Users without permissions failed";
                Log::info($errorMsg);
                $nManager->addFinalReportNotice([
                    'shortMsg' => $errorMsg,
                    'level'    => DUPX_NOTICE_ITEM::HARD_WARNING,
                    'longMsg'  => 'Please remove all users without permissions manually',
                    'sections' => 'general',
                ]);
            }
        }
    }

    /**
     * Cleanup any tmp files
     *
     * @return void
     */
    public function cleanupFiles(): void
    {
        $this->removeRedundant();
        self::logSectionHeader('CLEANUP TMP FILES', __FUNCTION__, __LINE__);

        //Cleanup any tmp files a developer may have forgotten about
        //Lets be proactive for the developer just in case
        $pathNew             = PrmMng::getInstance()->getValue(PrmMng::PARAM_PATH_NEW);
        $wpconfig_safe_check = [
            $pathNew . "/wp-config.bak",
            $pathNew . "/wp-config.old",
            $pathNew . "/wp-config.org",
            $pathNew . "/wp-config.orig",

        ];
        foreach ($wpconfig_safe_check as $file) {
            if (file_exists($file)) {
                $tmp_newfile = $file . uniqid('_');
                if (rename($file, $tmp_newfile) === false) {
                    Log::info("WARNING: Unable to rename '{$file}' to '{$tmp_newfile}'");
                }
            }
        }

        $wpContent   = PrmMng::getInstance()->getValue(PrmMng::PARAM_PATH_CONTENT_NEW);
        $removeFiled = [
            $wpContent . "/debug.log",
        ];
        foreach ($removeFiled as $file) {
            if (file_exists($file)) {
                if (unlink($file) === false) {
                    Log::info("WARNING: Unable to remove '{$file}'");
                }
            }
        }
    }

    /**
     * Set file permission
     *
     * @return void
     */
    public function setFilePermsission(): void
    {
        self::logSectionHeader('SET PARAMS PERMISSION', __FUNCTION__, __LINE__);
        DUPX_Extraction::setFolderPermissionAfterExtraction();
    }

    /**
     * Final report notices
     *
     * @return void
     */
    public function finalReportNotices(): void
    {
        self::logSectionHeader('FINAL REPORT NOTICES', __FUNCTION__, __LINE__);

        $this->wpConfigFinalReport();
        $this->htaccessFinalReport();
    }

    /**
     * Htaccess final report
     *
     * @return void
     */
    private function htaccessFinalReport(): void
    {
        $nManager = DUPX_NOTICE_MANAGER::getInstance();

        $origHtaccessPath = InstallerOrigFileMng::getInstance()->getEntryStoredPath(ServerConfigs::CONFIG_ORIG_FILE_HTACCESS_ID);
        if ($origHtaccessPath === false || ($orig             = file_get_contents($origHtaccessPath)) === false) {
            $orig = 'Original .htaccess file doesn\'t exist';
        }

        $targetHtaccessPath = ServerConfigs::getHtaccessTargetPath();
        if (!file_exists($targetHtaccessPath) || ($new                = file_get_contents($targetHtaccessPath)) === false) {
            $new = 'New .htaccess file doesn\'t exist';
        }

        $lightBoxContent = '<div class="row-cols-2">' .
            '<div class="col col-1"><b>Original .htaccess</b><pre>' . htmlspecialchars($orig) . '</pre></div>' .
            '<div class="col col-2"><b>New .htaccess</b><pre>' . htmlspecialchars($new) . '</pre></div>' .
            '</div>';
        $longMsg         = DUPX_U_Html::getLigthBox('.htaccess changes', 'HTACCESS COMPARE', $lightBoxContent, false);

        $nManager->addFinalReportNotice([
            'shortMsg'    => 'htaccess changes',
            'level'       => DUPX_NOTICE_ITEM::INFO,
            'longMsg'     => $longMsg,
            'sections'    => 'changes',
            'open'        => true,
            'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_HTML,
        ], DUPX_NOTICE_MANAGER::ADD_UNIQUE, 'htaccess-changes');
    }

    /**
     * wp-config final report
     *
     * @return void
     */
    private function wpConfigFinalReport(): void
    {
        $nManager     = DUPX_NOTICE_MANAGER::getInstance();
        $wpConfigPath = InstallerOrigFileMng::getInstance()->getEntryStoredPath(ServerConfigs::CONFIG_ORIG_FILE_WPCONFIG_ID);

        if ($wpConfigPath === false || ($orig = file_get_contents($wpConfigPath)) === false) {
            $orig = 'Can\'t read origin wp-config.php file';
        } else {
            $orig = $this->obscureWpConfig($orig);
        }

        $wpConfigFile = DUPX_WPConfig::getWpConfigPath();
        if (!is_readable($wpConfigFile)) {
            $new = 'Can read wp-config.php file';
        } elseif (($new = file_get_contents($wpConfigFile)) === false) {
            $new = 'Can read wp-config.php file';
        } else {
            $new = $this->obscureWpConfig($new);
        }

        $lightBoxContent = '<div class="row-cols-2">' .
            '<div class="col col-1"><b>Original wp-config.php</b><pre>' . htmlspecialchars($orig) . '</pre></div>' .
            '<div class="col col-2"><b>New wp-config.php</b><pre>' . htmlspecialchars($new) . '</pre></div>' .
            '</div>';
        $longMsg         = DUPX_U_Html::getLigthBox('wp-config.php changes', 'WP-CONFIG.PHP COMPARE', $lightBoxContent, false);

        $nManager->addFinalReportNotice([
            'shortMsg'    => 'wp-config.php changes',
            'level'       => DUPX_NOTICE_ITEM::INFO,
            'longMsg'     => $longMsg,
            'sections'    => 'changes',
            'open'        => true,
            'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_HTML,
        ], DUPX_NOTICE_MANAGER::ADD_UNIQUE, 'wp-config-changes');

        if (PrmMng::getInstance()->getValue(PrmMng::PARAM_WP_CONFIG) == 'new') {
            DUPX_NOTICE_MANAGER::getInstance()->addFinalReportNotice([
                'shortMsg'    => 'New wp-config.php was created',
                'level'       => DUPX_NOTICE_ITEM::SOFT_WARNING,
                'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_HTML,
                'open'        => true,
                'longMsg'     => 'A new wp-config has been created, unless you have selected this option and in order to make sure that everything will work as it should '
                    . 'please make a comparison between the old and the new wp-config with the following link ' . $longMsg,
                'sections'    => 'general',
            ]);
        }
    }

    /**
     * Obscure wp-config.php critical data in wp-config
     *
     * @param string $src wp-config.php content
     *
     * @return string
     */
    private function obscureWpConfig(string $src)
    {
        $transformer = new WPConfigTransformerSrc($src);
        $obsKeys     = [
            'DB_NAME',
            'DB_USER',
            'DB_HOST',
            'DB_PASSWORD',
            'AUTH_KEY',
            'SECURE_AUTH_KEY',
            'LOGGED_IN_KEY',
            'NONCE_KEY',
            'AUTH_SALT',
            'SECURE_AUTH_SALT',
            'LOGGED_IN_SALT',
            'NONCE_SALT',
        ];

        foreach ($obsKeys as $key) {
            if ($transformer->exists('constant', $key)) {
                $transformer->update('constant', $key, '**OBSCURED**');
            }
        }

        return $transformer->getSrc();
    }

    /**
     * Update report
     *
     * @return array<string, mixed> return report
     */
    public function reportLoadEnd()
    {
        $this->report['profile_end'] = DUPX_U::getMicrotime();
        $this->report['time']        = DUPX_U::elapsedTime($this->report['profile_end'], $this->report['profile_start']);
        $this->report['errsql_sum']  = empty($this->report['errsql']) ? 0 : count($this->report['errsql']);
        $this->report['errser_sum']  = empty($this->report['errser']) ? 0 : count($this->report['errser']);
        $this->report['errkey_sum']  = empty($this->report['errkey']) ? 0 : count($this->report['errkey']);
        $this->report['err_all']     = $this->report['errsql_sum'] + $this->report['errser_sum'] + $this->report['errkey_sum'];
        return $this->report;
    }

    /**
     * Chunk stop
     *
     * @param float $progressPerc progress percentage
     * @param mixed $position     chunk position
     *
     * @return void
     */
    private function chunkStop($progressPerc, $position): void
    {
        $this->closeDbConnection();

        $ajax3_sum = DUPX_U::elapsedTime(DUPX_U::getMicrotime(), $this->timeStart);
        Log::info("\nSTEP-3 CHUNK STOP @ " . @date('h:i:s') . " - RUNTIME: {$ajax3_sum} \n\n");

        $this->report['chunk']         = 1;
        $this->report['chunkPos']      = $position;
        $this->report['pass']          = 0;
        $this->report['progress_perc'] = $progressPerc;
    }

    /**
     * Complete step, update final report
     *
     * @return void
     */
    public function complete(): void
    {
        $this->closeDbConnection();

        $paramsManager = PrmMng::getInstance();

        $ajax3_sum = DUPX_U::elapsedTime(DUPX_U::getMicrotime(), $this->timeStart);
        Log::info("\nSTEP-3 COMPLETE @ " . @date('h:i:s') . " - RUNTIME: {$ajax3_sum} \n\n");

        $finalReport = $paramsManager->getValue(PrmMng::PARAM_FINAL_REPORT_DATA);

        $finalReport['replace']['scan_tables'] = $this->report['scan_tables'];
        $finalReport['replace']['scan_rows']   = $this->report['scan_rows'];
        $finalReport['replace']['scan_cells']  = $this->report['scan_cells'];
        $finalReport['replace']['updt_tables'] = $this->report['updt_tables'];
        $finalReport['replace']['updt_rows']   = $this->report['updt_rows'];
        $finalReport['replace']['updt_cells']  = $this->report['updt_cells'];
        $finalReport['replace']['errsql']      = $this->report['errsql'];
        $finalReport['replace']['errser']      = $this->report['errser'];
        $finalReport['replace']['errkey']      = $this->report['errkey'];
        $finalReport['replace']['errsql_sum']  = $this->report['errsql_sum'];
        $finalReport['replace']['errser_sum']  = $this->report['errser_sum'];
        $finalReport['replace']['errkey_sum']  = $this->report['errkey_sum'];
        $finalReport['replace']['err_all']     = $this->report['err_all'];
        $finalReport['replace']['warn_all']    = $this->report['warn_all'];
        $finalReport['replace']['warnlist']    = $this->report['warnlist'];

        $paramsManager->setValue(PrmMng::PARAM_FINAL_REPORT_DATA, $finalReport);
        $paramsManager->save();

        $this->report['pass']          = 1;
        $this->report['chunk']         = 0;
        $this->report['chunkPos']      = null;
        $this->report['progress_perc'] = 100;
        // error_reporting($ajax3_error_level);
    }
}
