<?php

  // Namespace
  namespace BMI\Plugin\Extracter;

  // Use
  use BMI\Plugin\BMI_Logger as Logger;
  use BMI\Plugin\Dashboard as Dashboard;
  use BMI\Plugin\Database\BMI_Database as Database;
  use BMI\Plugin\Database\BMI_Database_Importer as BetterDatabaseImport;
  use BMI\Plugin\Database\BMI_Even_Better_Database_Restore as EvenBetterDatabaseImport;
  use BMI\Plugin\Progress\BMI_ZipProgress as Progress;
  use BMI\Plugin\Backup_Migration_Plugin as BMP;
  use BMI\Plugin\Zipper\Zip as Zip;
  use BMI\Plugin\Zipper\BMI_Zipper as ZipManager;
  use BMI\Plugin\Database\BMI_Database_Sorting as SmartDatabaseSort;

  // Exit on direct access
  if (!defined('ABSPATH')) {
    exit;
  }

  /**
   * BMI_Extracter
   */
  class BMI_Extracter {

    public function __construct($backup, &$migration, $tmptime = false, $isCLI = false, $options = []) {

      // Globals
      global $table_prefix;

      // Requirements
      require_once BMI_INCLUDES . DIRECTORY_SEPARATOR . 'database' . DIRECTORY_SEPARATOR . 'manager.php';
      require_once BMI_INCLUDES . DIRECTORY_SEPARATOR . 'database' . DIRECTORY_SEPARATOR . 'better-restore.php';
      require_once BMI_INCLUDES . DIRECTORY_SEPARATOR . 'database' . DIRECTORY_SEPARATOR . 'smart-sort.php';

      // IsCLI?
      $this->isCLI = $isCLI;

      // Backup name
      $this->backup_name = $backup;

      // Logger
      $this->migration = $migration;

      // Temp name
      $this->tmptime = time();

      // Use specified name if it is in batching mode
      if (is_numeric($tmptime)) $this->tmptime = $tmptime;

      // Splitting enabled?
      $this->splitting = Dashboard\bmi_get_config('OTHER:RESTORE:SPLITTING') ? true : false;
      $this->v3engine = Dashboard\bmi_get_config('OTHER:RESTORE:DB:V3') ? true : false;
      $this->cleanupbefore = Dashboard\bmi_get_config('OTHER:RESTORE:BEFORE:CLEANUP') ? true : false;

      // Restore start time
      $this->start = intval(microtime(true));

      // File amount by default 0 later we replace it with scan
      $this->fileAmount = 0;
      $this->recent_export_seek = 0;
      $this->processData = [];
      $this->conversionStats = [];

      // Options
      $this->batchStep = 0;
      if (isset($options['amount'])) {
        $this->fileAmount = intval($options['amount']);
      }
      if (isset($options['start'])) {
        $this->start = intval($options['start']);
      }
      $this->continueFile = false;
      if (isset($options['continueFile'])) {
        $this->continueFile = $options['continueFile'];
      }
      $this->continueSeek = false;
      if (isset($options['continueSeek'])) {
        $this->continueSeek = $options['continueSeek'];
      }
      if (isset($options['step'])) {
        $this->batchStep = intval($options['step']);
      }
      $this->databaseExist = false;
      if (isset($options['databaseExist'])) {
        $this->databaseExist = (($options['databaseExist'] == 'true' || $options['databaseExist'] === '1' || $options['databaseExist'] === 1 || $options['databaseExist'] === true) ? true : false);
      }
      $this->firstDB = true;
      if (isset($options['firstDB'])) {
        $this->firstDB = (($options['firstDB'] == 'true' || $options['firstDB'] === '1' || $options['firstDB'] === 1 || $options['firstDB'] === true) ? true : false);
      }
      $this->v3RestoreUsed = false;
      if (isset($options['v3RestoreUsed'])) {
        $this->v3RestoreUsed = (($options['v3RestoreUsed'] == 'true' || $options['v3RestoreUsed'] === '1' || $options['v3RestoreUsed'] === 1 || $options['v3RestoreUsed'] === true) ? true : false);
      }
      $this->firstExtract = true;
      if (isset($options['firstExtract'])) {
        $this->firstExtract = (($options['firstExtract'] == 'false' || $options['firstExtract'] === '1' || $options['firstExtract'] === 1 || $options['firstExtract'] === false) ? false : true);
      }

      $this->db_xi = 0;
      $this->ini_start = 0;
      $this->table_names_alter = [];

      if (isset($options['db_xi'])) {
        $this->db_xi = ((is_numeric($options['db_xi'])) ? intval($options['db_xi']) : 0);
      }
      if (isset($options['ini_start'])) {
        $this->ini_start = ((is_numeric($options['ini_start'])) ? intval($options['ini_start']) : microtime(true));
      }
      if (isset($options['table_names_alter'])) {
        $this->table_names_alter = $options['table_names_alter'];
      }
      if (isset($options['recent_export_seek'])) {
        $this->recent_export_seek = intval($options['recent_export_seek']);
      }
      if (isset($options['processData'])) {
        $this->processData = $options['processData'];
      }
      if (isset($options['conversionStats'])) {
        $this->conversionStats = $options['conversionStats'];
      }

      $this->tableIndex = 0;
      $this->replaceStep = 0;
      $this->totalReplacePage = 0;
      $this->currentReplacePage = 0;
      $this->fieldAdjustments = 0;
      $this->dbFoundPrefix = 'wp_';

      if (isset($options['replaceStep'])) {
        $this->replaceStep = intval($options['replaceStep']);
      }
      if (isset($options['tableIndex'])) {
        $this->tableIndex = intval($options['tableIndex']);
      }
      if (isset($options['currentReplacePage'])) {
        $this->currentReplacePage = intval($options['currentReplacePage']);
      }
      if (isset($options['totalReplacePage'])) {
        $this->totalReplacePage = intval($options['totalReplacePage']);
      }
      if (isset($options['fieldAdjustments'])) {
        $this->fieldAdjustments = intval($options['fieldAdjustments']);
      }
      if (isset($options['dbFoundPrefix'])) {
        $this->dbFoundPrefix = sanitize_text_field($options['dbFoundPrefix']);
      }

      // Name
      // $this->tmp = untrailingslashit(ABSPATH) . DIRECTORY_SEPARATOR . 'backup-migration_' . $this->tmptime;
      $this->tmp = BMI_TMP . DIRECTORY_SEPARATOR . 'backup-migration_' . $this->tmptime;
      $GLOBALS['bmi_current_tmp_restore'] = $this->tmp;
      $GLOBALS['bmi_current_tmp_restore_unique'] = $this->tmptime;

      // Scan file
      $this->scanFile = BMI_TMP . DIRECTORY_SEPARATOR . '.restore_scan_' . $this->tmptime;

      // Prepare database connection
      $this->db = new Database(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);

      // Save current wp-config to replace (only those required)
      $this->DB_NAME = DB_NAME;
      $this->DB_USER = DB_USER;
      $this->DB_PASSWORD = DB_PASSWORD;
      $this->DB_HOST = DB_HOST;
      $this->DB_CHARSET = (defined('DB_CHARSET') ? DB_CHARSET : '');
      $this->DB_COLLATE = (defined('DB_COLLATE') ? DB_COLLATE : '');

      $this->AUTH_KEY = (defined('AUTH_KEY') ? AUTH_KEY : '');
      $this->SECURE_AUTH_KEY = (defined('SECURE_AUTH_KEY') ? SECURE_AUTH_KEY : '');
      $this->LOGGED_IN_KEY = (defined('LOGGED_IN_KEY') ? LOGGED_IN_KEY : '');
      $this->NONCE_KEY = (defined('NONCE_KEY') ? NONCE_KEY : '');
      $this->AUTH_SALT = (defined('AUTH_SALT') ? AUTH_SALT : '');
      $this->SECURE_AUTH_SALT = (defined('SECURE_AUTH_SALT') ? SECURE_AUTH_SALT : '');
      $this->LOGGED_IN_SALT = (defined('LOGGED_IN_SALT') ? LOGGED_IN_SALT : '');
      $this->NONCE_SALT = (defined('NONCE_SALT') ? NONCE_SALT : '');

      $this->ABSPATH = ABSPATH;
      $this->WP_CONTENT_DIR = trailingslashit(WP_CONTENT_DIR);

      $this->WP_DEBUG_LOG = WP_DEBUG_LOG;
      $this->table_prefix = $table_prefix;
      $this->code = get_option('z__bmi_xhria', false);
      if (isset($options['code']) && $this->code == false) {
        $this->code = $options['code'];
      }

      $this->backupStorage = get_option('BMI::STORAGE::LOCAL::PATH', false);
      if (isset($options['storage'])) $this->backupStorage = $options['storage'];

      $this->siteurl = get_option('siteurl');
      $this->home = get_option('home');

      $this->src = BMI_BACKUPS . DIRECTORY_SEPARATOR . $this->backup_name;

      $this->v3Importer = null;
      $this->usingDbEngineV4 = null;
      
      $this->backupStorage = str_replace('/', DIRECTORY_SEPARATOR, $this->backupStorage);

    }
    
    public function removeUnwantedFiles() {
      $preventMoveFiles = [
        'wp-config.php',
        'debug.log',
        '.user.ini',
        'php.ini',
        '.htaccess'
      ];
      
      $base = $this->tmp . DIRECTORY_SEPARATOR . 'wordpress' . DIRECTORY_SEPARATOR;
      
      foreach ($preventMoveFiles as $idx => $value) {
        if (file_exists($base . $value)) @unlink($base . $value);
      }
    }

    public function replacePath($path, $sub, $content) {
      $path .= DIRECTORY_SEPARATOR . 'wordpress' . $sub;

      // Handle only database backup
      if (!file_exists($path)) return;

      $rii = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST);

      $clent = strlen($content);
      $sublen = strlen($path);
      $files = [];
      $dirs = [];

      $preventMoveFiles = [
        'wp-config.php',
        'debug.log',
        '.user.ini',
        'php.ini',
        '.htaccess'
      ];

      foreach ($rii as $file) {
        if (!$file->isDir()) {
          $files[] = substr($file->getPathname(), $sublen);
        } else {
          $dirs[] = substr($file->getPathname(), $sublen);
        }
      }

      for ($i = 0; $i < sizeof($dirs); ++$i) {
        $src = $path . $dirs[$i];
        if (strpos($dirs[$i], $content) !== false) {
          $dest = untrailingslashit($this->WP_CONTENT_DIR) . $sub . ltrim(substr($dirs[$i], $clent), DIRECTORY_SEPARATOR);
        } else {
          $dest = untrailingslashit($this->ABSPATH) . $sub . ltrim($dirs[$i], DIRECTORY_SEPARATOR);
        }

        $dest = untrailingslashit($dest);
        if (!(file_exists($dest) && is_dir($dest))) {
          try { @mkdir($dest, 0755, true); }
          catch (Exception $e) { /* Slience */ }
          catch (Throwable $t) { /* Slience */ }
        }
      }

      $max = sizeof($files);
      for ($i = 0; $i < $max; ++$i) {
        $src = $path . $files[$i];
        if (strpos($files[$i], $content) !== false) {
          $dest = untrailingslashit($this->WP_CONTENT_DIR) . $sub . substr($files[$i], $clent);
        } else {
          $dest = untrailingslashit($this->ABSPATH) . $sub . $files[$i];
        }

        if (file_exists($src)) {
          $fileDest = BMP::fixSlashes($dest);
          $srcFileName = basename($src);

          if (!in_array($srcFileName, $preventMoveFiles)) {
            rename($src, $fileDest);
          }
        }

        if ($i % 100 === 0 || ($i == ($max - 1))) {
          $this->migration->progress(25 + intval((($i / $max) * 100) / 4));
          if ($i != 0 && ($i % 500 === 0 || ($i == ($max - 1)))) {
            if ($i == ($max - 1)) $i++;
            $this->migration->log(sprintf(__('File replacement progress: %s/%s (%s%%)', 'backup-backup'), $i, $max, intval(($i / $max) * 100)));
          }
        }
      }
    }

    public function removePreviousSelectionsIfDatabaseIncluded() {
      if (!isset($manifest)) {
        $manifest = $this->getCurrentManifest();
      }
      $restorePartsFile= BMI_TMP . DIRECTORY_SEPARATOR . 'restore_parts.json';
      $prefix = $manifest->config->table_prefix;
      if (file_exists($restorePartsFile)) {
        $restoreParts = json_decode(file_get_contents($restorePartsFile));
        if (isset($restoreParts->backupName) && $restoreParts->backupName == basename($this->src)) {
          if (!isset($restoreParts->dirs['db_tables']) &&  !isset($restoreParts->files[$prefix . 'options.sql'])) {
            return;
          }
        }
      } else {
        $manager = new ZipManager();
        $optionsTable = $manager->getZipFileContent($this->src, 'db_tables' . DIRECTORY_SEPARATOR . $prefix . 'options.sql');
        if ($optionsTable === false) {
          return;
        }
      }

      $themedir = get_theme_root();
      $tempTheme = $themedir . DIRECTORY_SEPARATOR . 'backup_migration_restoration_in_progress';

      if (file_exists($tempTheme . DIRECTORY_SEPARATOR . '.previous_theme')) {
        @unlink($tempTheme . DIRECTORY_SEPARATOR . '.previous_theme');
      }

      if (file_exists($tempTheme . DIRECTORY_SEPARATOR . '.previous_stylesheet')) {
        @unlink($tempTheme . DIRECTORY_SEPARATOR . '.previous_stylesheet');
      }

      if (file_exists($tempTheme . DIRECTORY_SEPARATOR . '.earlier_active_plugins')) {
        @unlink($tempTheme . DIRECTORY_SEPARATOR . '.earlier_active_plugins');
      }

    }

    public function replaceAll($content) {

      $themedir = get_theme_root();
      $tempTheme = $themedir . DIRECTORY_SEPARATOR . 'backup_migration_restoration_in_progress';
      if (!(file_exists($tempTheme) && is_dir($tempTheme))) {
        @mkdir($tempTheme, 0755, true);
      }

      $visitLaterText = __('Site restoration in progress, please visit that website a bit later, thank you! :)', 'backup-backup');
      file_put_contents($tempTheme . DIRECTORY_SEPARATOR . 'header.php', '<?php wp_head(); show_admin_bar(true);');
      file_put_contents($tempTheme . DIRECTORY_SEPARATOR . 'footer.php', '<?php wp_footer(); get_footer();');
      file_put_contents($tempTheme . DIRECTORY_SEPARATOR . 'index.php', '<?php get_header(); wp_body_open(); ?>' . $visitLaterText);
      file_put_contents($tempTheme . DIRECTORY_SEPARATOR . '.previous_theme', get_option('template', ''));
      file_put_contents($tempTheme . DIRECTORY_SEPARATOR . '.previous_stylesheet', get_option('stylesheet', ''));
      file_put_contents($tempTheme . DIRECTORY_SEPARATOR . '.earlier_active_plugins', serialize(get_option('active_plugins')));

      update_option('active_plugins', ['backup-backup/backup-backup.php']);
      update_option('template', 'backup_migration_restoration_in_progress');
      update_option('stylesheet', 'backup_migration_restoration_in_progress');

      $this->replacePath($this->tmp, DIRECTORY_SEPARATOR, $content);

    }

    public function cleanup() {

      // Fix for automatic redirection module at TasteWP
      if (strpos(site_url(), 'tastewp') !== false) {
        if (function_exists('wp_load_alloptions')) wp_load_alloptions(true);
        delete_option('__tastewp_redirection_performed', true);
        delete_option('auto_smart_tastewp_redirect_performed', 1);
        delete_option('tastewp_auto_activated', true);
        delete_option('__tastewp_sub_requested', true);

        if (function_exists('wp_load_alloptions')) wp_load_alloptions(true);
        update_option('__tastewp_redirection_performed', true);
        update_option('auto_smart_tastewp_redirect_performed', 1);
        update_option('tastewp_auto_activated', true);
        update_option('__tastewp_sub_requested', true);
      }

      delete_option('bmi_pro_cron_new_domain_done');

      $filesToBeRemoved = [];
      $dir = $this->tmp;

      $themedir = get_theme_root();
      $tempTheme = $themedir . DIRECTORY_SEPARATOR . 'backup_migration_restoration_in_progress';

      if (get_option('template') == 'backup_migration_restoration_in_progress' || get_option('stylesheet') == 'backup_migration_restoration_in_progress') {
        if (file_exists($tempTheme . DIRECTORY_SEPARATOR . '.previous_theme')) {
          update_option('template', file_get_contents($tempTheme . DIRECTORY_SEPARATOR . '.previous_theme'));
        }
        if (file_exists($tempTheme . DIRECTORY_SEPARATOR . '.previous_stylesheet')) {
          update_option('stylesheet', file_get_contents($tempTheme . DIRECTORY_SEPARATOR . '.previous_stylesheet'));
        }
        if (file_exists($tempTheme . DIRECTORY_SEPARATOR . '.earlier_active_plugins')) {
          update_option('active_plugins', unserialize(file_get_contents($tempTheme . DIRECTORY_SEPARATOR . '.earlier_active_plugins')));
        }
      }

      $filesToBeRemoved[] = $tempTheme;

      if (is_dir($dir) && file_exists($dir)) {

        $it = new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS);
        $files = new \RecursiveIteratorIterator($it, \RecursiveIteratorIterator::CHILD_FIRST);

        $this->migration->log(__('Removing ', 'backup-backup') . iterator_count($files) . __(' files', 'backup-backup'), 'INFO');
        foreach ($files as $file) {
          $pathReal = $file->getRealPath();
          if (!file_exists($pathReal)) continue;
          if ($file->isDir()) {
            @rmdir($pathReal);
          } else {
            gc_collect_cycles();
            @unlink($pathReal);
          }
        }

        @rmdir($dir);

      }

      if (file_exists($this->scanFile)) {
        @unlink($this->scanFile);
      }

      $sc = BMI_TMP . DIRECTORY_SEPARATOR . '.restore_secret';
      if (file_exists($sc)) {
        @unlink($sc);
      }

      $tblmap = BMI_TMP . DIRECTORY_SEPARATOR . '.table_map';
      if (file_exists($tblmap)) {
        @unlink($tblmap);
      }
      $allowedFiles = ['wp-config.php', '.htaccess', '.litespeed', '.default.json', 'driveKeys.php', 'dropboxKeys.php', '.autologin.php', '.migrationFinished', 'onedriveKeys.php', 'awsKeys.php', 'wasabiKeys.php', 'backupblissKeys.php', 'sftpKeys.php', 'pcloudKeys.php'];
      foreach (glob(BMI_TMP . DIRECTORY_SEPARATOR . 'backup-migration_??????????') as $filename) {

        $basename = basename($filename);

        if (is_dir($filename) && !in_array($basename, ['.', '..'])) {
          $filesToBeRemoved[] = $filename;
        }

      }

      foreach (glob(BMI_TMP . DIRECTORY_SEPARATOR . '.*') as $filename) {

        $basename = basename($filename);

        if (in_array($basename, ['.', '..'])) continue;
        if (is_file($filename) && !in_array($basename, $allowedFiles)) {
          $filesToBeRemoved[] = $filename;
        }

      }

      foreach (glob(BMI_TMP . DIRECTORY_SEPARATOR . 'restore_scan_*') as $filename) {

        $basename = basename($filename);

        if (in_array($basename, ['.', '..'])) continue;
        if (is_file($filename) && !in_array($basename, $allowedFiles)) {
          $filesToBeRemoved[] = $filename;
        }

      }

      foreach (glob(untrailingslashit(ABSPATH) . DIRECTORY_SEPARATOR . 'wp-config.??????????.php') as $filename) {

        $basename = basename($filename);

        if (in_array($basename, ['.', '..'])) continue;
        if (is_file($filename) && !in_array($filename, $allowedFiles)) {
          $filesToBeRemoved[] = $filename;
        }

      }

      if (is_array($filesToBeRemoved) || is_object($filesToBeRemoved)) {
        foreach ((array) $filesToBeRemoved as $file) {
          $this->rrmdir($file);
        }
      }

    }

    private function rrmdir($dir) {

      if (is_dir($dir)) {

        $objects = scandir($dir);
        foreach ($objects as $object) {

          if ($object != "." && $object != "..") {

            if (is_dir($dir . DIRECTORY_SEPARATOR . $object) && !is_link($dir . DIRECTORY_SEPARATOR . $object)) {

              $this->rrmdir($dir . DIRECTORY_SEPARATOR . $object);

            } else {

              @unlink($dir . DIRECTORY_SEPARATOR . $object);

            }

          }

        }

        @rmdir($dir);

      } else {

        if (file_exists($dir) && is_file($dir)) {

          @unlink($dir);

        }

      }

    }

    public function fixDumbWindowsSlashes() {

      // Extraction directory (no trailing slash)
      $tmp = $this->tmp;

      $files = scandir($tmp);
      if (sizeof($files) > 10) {

        $this->migration->log(__("Performing solution to Windows backslashes...", 'backup-backup'), 'STEP');

        foreach ($files as $index => $file) {

          if (strpos($file, '\\') !== false) {

            $path = explode('\\', $file);
            $filename = array_pop($path);
            $dirname = $tmp . DIRECTORY_SEPARATOR . join(DIRECTORY_SEPARATOR, $path);

            if (!(file_exists($dirname) && is_dir($dirname))) {
              mkdir($dirname, 0755, true);
            }

            rename($tmp . DIRECTORY_SEPARATOR . $file, $dirname . DIRECTORY_SEPARATOR . $filename);

          }

        }

        $this->migration->log(__("Windows file structure fixed...", 'backup-backup'), 'SUCCESS');

      }

    }

    public function findTablePrefixByFiles($manifestPrefix) {

      $tmp = $this->tmp . DIRECTORY_SEPARATOR . 'db_tables';
      $manifestPrefixExist = false;

      if (!(file_exists($tmp) && is_dir($tmp))) {
        return $manifestPrefix;
      }
      
      $originalPrefix = [
        file_exists($tmp . DIRECTORY_SEPARATOR . $manifestPrefix . 'options.sql'),
        file_exists($tmp . DIRECTORY_SEPARATOR . $manifestPrefix . 'users.sql'),
        file_exists($tmp . DIRECTORY_SEPARATOR . $manifestPrefix . 'usermeta.sql'),
        file_exists($tmp . DIRECTORY_SEPARATOR . $manifestPrefix . 'posts.sql'),
        file_exists($tmp . DIRECTORY_SEPARATOR . $manifestPrefix . 'postmeta.sql')
      ];
      
      $lowerPrefix = [
        file_exists($tmp . DIRECTORY_SEPARATOR . strtolower($manifestPrefix) . 'options.sql'),
        file_exists($tmp . DIRECTORY_SEPARATOR . strtolower($manifestPrefix) . 'users.sql'),
        file_exists($tmp . DIRECTORY_SEPARATOR . strtolower($manifestPrefix) . 'usermeta.sql'),
        file_exists($tmp . DIRECTORY_SEPARATOR . strtolower($manifestPrefix) . 'posts.sql'),
        file_exists($tmp . DIRECTORY_SEPARATOR . strtolower($manifestPrefix) . 'postmeta.sql')
      ];
      
      if (count(array_filter($lowerPrefix)) == 5 || count(array_filter($originalPrefix)) == 5) {
        return $manifestPrefix;
      }

      $files = scandir($tmp);
      $prefixes = [];

      foreach ($files as $index => $file) {

        if ($file == '.' || $file == '..') continue;

        if (substr($file, 0, strlen($manifestPrefix)) == $manifestPrefix) {
          $manifestPrefixExist = true;
          return $manifestPrefix;
        }

        foreach ($files as $index2 => $comparefile) {

          if ($comparefile == $file) continue;
          $currentTopPrefix = '';

          for ($i = 0; $i < min(strlen($comparefile), strlen($file)); ++$i) {

            if ($file[$i] == $comparefile[$i]) {
              $currentTopPrefix .= $file[$i];
            } else break;

          }

          if ($currentTopPrefix != '') {
            if (isset($prefixes[$currentTopPrefix])) {
              $prefixes[$currentTopPrefix]++;
            } else {
              $prefixes[$currentTopPrefix] = 1;
            }
          }

        }

      }

      if (sizeof($prefixes) <= 0) return $manifestPrefix;
      else return array_search(max($prefixes), $prefixes);

    }

    public function makeUnZIP() {

      // Source
      $src = $this->src;

      // Extract
      $this->zip = new Zip();

      if ($this->isCLI) {

        $isOk = $this->zip->unzip_file($src, $this->tmp, $this->migration);

      } else {

        $last_seek = $this->recent_export_seek;

        $file = new \SplFileObject($this->scanFile);
        $file->seek($file->getSize());
        $total_lines = $file->key() + 1;
        $files = [];
        $seek_begin = 0;
        $recent_seek = $last_seek;
        $shouldRepeat = false;

        $batch = 50;
        if ($total_lines > 1000) $batch = 100;
        if ($total_lines > 2000) $batch = 200;
        if ($total_lines > 6000) $batch = 300;
        if ($total_lines > 12000) $batch = 500;
        if ($total_lines > 36000) $batch = 1000;
        if ($total_lines > 50000) $batch = 2500;
        if ($total_lines > 100000) $batch = 5000;
        if ($total_lines > 150000) $batch = 10000;
        if ($total_lines > 200000) $batch = 20000;

        if (defined('BMI_MAX_FILE_EXTRACTION_LIMIT')) {
          $definedSize = BMI_MAX_FILE_EXTRACTION_LIMIT;
          if (is_numeric($definedSize) && $definedSize > 50 && $definedSize < 20000) {
            $batch = intval($definedSize);
          }
        }

        if ($this->firstExtract == true) {
          $this->migration->log(__("Preparing batching technique for extraction...", 'backup-backup'), 'STEP');
          $this->migration->log(__('Files exported per batch: ', 'backup-backup') . $batch, 'INFO');
        }

        for ($i = $last_seek; $i < $total_lines; ++$i) {

          $file->seek($i);
          $line = trim($file->current());

          if ($line && strlen($line) > 0) {

            $files[] = $line;

          }

          $seek_begin++;
          $recent_seek = $i;
          if ($seek_begin > $batch) {

            $shouldRepeat = true;
            break;

          }

        }

        $isOk = $this->zip->extract_files($src, $files, $this->tmp, $this->migration, $this->firstExtract);

      }


      if (!$isOk) {

        // Verbose
        $this->migration->log(__('Failed to extract the files...', 'backup-backup'), 'WARN');
        $this->cleanup();

        return false;

      } else {

        if (!$this->isCLI) {

          $i = $recent_seek + 1;
          $milestone = intval((($i / $total_lines) * 100) / 4);
          $this->migration->progress($milestone);
          
          $plus = -1;
          if ($shouldRepeat != true) $plus = 0;
          
          $this->migration->log(__('Extraction milestone: ', 'backup-backup') . ($i + $plus) . '/' . $total_lines . ' (' . number_format(($i / $total_lines) * 100, 2) . '%)', 'INFO');

        }

      }

      // Verbose
      if (!$this->isCLI && $shouldRepeat === true) {

        $this->recent_export_seek = $recent_seek;
        return 'repeat';

      } else {

        $this->migration->log(__('Files extracted...', 'backup-backup'), 'SUCCESS');
        return true;

      }

    }

    public function fixWPLogin(&$manifest) {

      try {

        global $wpdb;

        $loginslug = false;
        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Identifier is safely escaped via escapeSQLIDentifier()
        $results = $wpdb->get_results("SELECT option_value FROM " . BMP::escapeSQLIDentifier($this->dbFoundPrefix . "options") . " WHERE option_name = 'bwpl_slug';");

        if (sizeof($results) > 0) $loginslug = $results[0]->option_value;

        if ($loginslug != false && is_string($loginslug) && strlen($loginslug) >= 1) {

          $wploginfile = trailingslashit(ABSPATH) . 'wp-login.php';
          $blockedloginfile = trailingslashit(ABSPATH) . $loginslug . '-wp-login.php';

          if (file_exists($wploginfile) && !file_exists($blockedloginfile)) {
            @copy($wploginfile, $blockedloginfile);
          }

        }

      }
      catch (\Exception $e) {}
      catch (\Throwable $e) {}

    }

    public function randomString($length = 64) {

      $chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
      $str = "";

      for ($i = 0; $i < $length; ++$i) {

        $str .= $chars[mt_rand(0, strlen($chars) - 1)];

      }

      return $str;

    }

    public function makeWPConfigCopy() {

      $this->migration->log(__('Saving wp-config file...', 'backup-backup'), 'STEP');
      $configData = file_get_contents(ABSPATH . 'wp-config.php');
      if ($configData && strlen($configData) > 0) {
        file_put_contents(ABSPATH . 'wp-config.' . $this->tmptime . '.php', $configData);
        $this->migration->log(__('File wp-config saved', 'backup-backup'), 'SUCCESS');
      } else {
        $this->migration->log(__('Could not backup/read wp-config file.', 'backup-backup'), 'WARN');
      }

    }

    public function getCurrentManifest($first = false) {

      if ($first == true) {
        $this->migration->log(__('Getting backup manifest...', 'backup-backup'), 'STEP');
      }

      $manifest = json_decode(file_get_contents($this->tmp . DIRECTORY_SEPARATOR . 'bmi_backup_manifest.json'));

      if ($first == true) {
        $this->migration->log(__('Manifest loaded', 'backup-backup'), 'SUCCESS');
      }

      return $manifest;

    }

    public function restoreBackupFromFiles($manifest) {

      $this->same_domain = untrailingslashit($manifest->dbdomain) == untrailingslashit($this->siteurl) ? true : false;
      $this->migration->log(__('Restoring files (this process may take a while)...', 'backup-backup'), 'STEP');
      $contentDirectory = $this->WP_CONTENT_DIR;
      $pathtowp = DIRECTORY_SEPARATOR . 'wp-content';
      if (isset($manifest->config->WP_CONTENT_DIR) && isset($manifest->config->ABSPATH)) {
        $absi = $manifest->config->ABSPATH;
        $cotsi = $manifest->config->WP_CONTENT_DIR;
        if (strlen($absi) <= strlen($cotsi) && substr($cotsi, 0, strlen($absi)) == $absi) {
          $inside = true;
          $pathtowp = substr($cotsi, strlen($absi));
        } else {
          $inside = false;
          $pathtowp = $cotsi;
        }
      }

      $this->replaceAll($pathtowp);
      $this->migration->log(__('All files restored successfully.', 'backup-backup'), 'SUCCESS');

    }

    public function restoreDatabaseV1(&$manifest) {

      $this->migration->log(__('Older backup detected, using V1 engine to restore database...', 'backup-backup'), 'WARN');
      $this->migration->log(__('Database size: ' . BMP::humanSize(filesize($this->tmp . DIRECTORY_SEPARATOR . 'bmi_database_backup.sql')), 'backup-backup'), 'INFO');
      $old_domain = $manifest->dbdomain;
      $new_domain = $this->siteurl; // parse_url(home_url())['host'];

      $abs = BMP::fixSlashes($manifest->config->ABSPATH);
      $newabs = BMP::fixSlashes(ABSPATH);
      $file = $this->tmp . DIRECTORY_SEPARATOR . 'bmi_database_backup.sql';
      $this->db->importDatabase($file, $old_domain, $new_domain, $abs, $newabs, $this->dbFoundPrefix, $this->siteurl, $this->home);
      $this->migration->log(__('Database restored', 'backup-backup'), 'SUCCESS');

    }

    public function setDBProgress($xi, $init_start, $table_names_alter) {

      $this->db_xi = $xi;
      $this->ini_start = $init_start;
      $this->table_names_alter = $table_names_alter;

    }

    public function alter_tables(&$manifest) {

      $storage = $this->tmp . DIRECTORY_SEPARATOR . 'db_tables';

      $queriesAll = $manifest->total_queries;
      if (isset($this->conversionStats['total_queries'])) {
        $queriesAll = $this->conversionStats['total_queries'];
      }

      //                                             $manifest->total_queries # the other solution
      $importer = new BetterDatabaseImport($storage, $queriesAll, $manifest->config->ABSPATH, $manifest->dbdomain, $this->siteurl, $this->migration, $this->isCLI, $this->conversionStats);

      $importer->xi = $this->db_xi;
      $importer->init_start = $this->ini_start;
      $importer->table_names_alter = $this->table_names_alter;

      $importer->alter_names();
      $this->migration->log(__('Database restored', 'backup-backup'), 'SUCCESS');

    }

    public function search_replace_v3(&$manifest) {

      $res = false;
      if (!$this->isCLI || $this->v3Importer == null) {
        $storage = $this->tmp . DIRECTORY_SEPARATOR . 'db_tables';
        $importer = new EvenBetterDatabaseImport($storage, false, $manifest, $this->migration, $this->splitting, $this->isCLI);
        $res = $importer->searchReplace($this->replaceStep, $this->tableIndex, $this->currentReplacePage, $this->totalReplacePage, $this->fieldAdjustments, $manifest->config->table_prefix);
      } else {
        $res = $this->v3Importer->searchReplace($this->replaceStep, $this->tableIndex, $this->currentReplacePage, $this->totalReplacePage, $this->fieldAdjustments, $manifest->config->table_prefix);
      }

      if ($res && is_array($res) && $res['finished'] == true) {
        $this->migration->log(__('Database restored', 'backup-backup'), 'SUCCESS');
      }

      return $res;

    }

    public function alter_tables_v3(&$manifest) {

      if (!$this->isCLI || $this->v3Importer == null) {
        $storage = $this->tmp . DIRECTORY_SEPARATOR . 'db_tables';
        $importer = new EvenBetterDatabaseImport($storage, false, $manifest, $this->migration, $this->splitting, $this->isCLI);
        $importer->alter_tables();
        
        // Modify the WP Config and replace
        $this->replaceDbPrefixInWPConfig($manifest);
        
        $importer->enablePlugins();
      } else {
        $this->v3Importer->alter_tables();
        
        // Modify the WP Config and replace
        $this->replaceDbPrefixInWPConfig($manifest);
        
        $this->v3Importer->enablePlugins();
      }

      $this->migration->log(__('Database restored', 'backup-backup'), 'SUCCESS');

    }

    public function restoreDatabaseV3(&$manifest) {

      $storage = $this->tmp . DIRECTORY_SEPARATOR . 'db_tables';
      $this->v3Importer = new EvenBetterDatabaseImport($storage, $this->firstDB, $manifest, $this->migration, $this->splitting, $this->isCLI);
      $finished = $this->v3Importer->start();

      if ($finished === true) {

        return true;

      } else {

        return ['status' => 'new_file'];

      }

    }

    public function restoreDatabaseV2(&$manifest) {

      $storage = $this->tmp . DIRECTORY_SEPARATOR . 'db_tables';

      if ($this->firstDB == true) {
        $this->migration->log(__('Successfully detected backup created with V2 engine, importing...', 'backup-backup'), 'INFO');
        $this->migration->log(__('Restoring database...', 'backup-backup'), 'STEP');
      }

      $queriesAll = $manifest->total_queries;
      if (isset($this->conversionStats['total_queries'])) {
        $queriesAll = $this->conversionStats['total_queries'];
      }
      $importer = new BetterDatabaseImport($storage, $queriesAll, $manifest->config->ABSPATH, $manifest->dbdomain, $this->siteurl, $this->migration, $this->isCLI, $this->conversionStats);

      if ($this->isCLI) {

        $importer->showFirstLogs();
        $importer->import();

      } else {

        if ($this->firstDB == true) {
          $importer->showFirstLogs();
        }

        $sqlFiles = $importer->get_sql_files($this->firstDB);

        if ($this->firstDB != true) {
          $importer->xi = $this->db_xi;
          $importer->init_start = $this->ini_start;
          $importer->table_names_alter = $this->table_names_alter;
        }

        if ($this->continueFile != false && $this->continueFile != '' && $this->continueSeek != false && $this->continueSeek != '') {

          $import = $importer->restore_by_file($this->continueFile, $this->continueSeek);
          $importer->queries_ended();
          $this->continueFile = $this->continueFile;
          $this->setDBProgress($importer->xi, $importer->init_start, $importer->table_names_alter);

        } else {

          if (sizeof($sqlFiles) > 0) {

            $import = $importer->restore_by_file($sqlFiles[0]);
            $importer->queries_ended();
            $this->continueFile = $sqlFiles[0];
            $this->setDBProgress($importer->xi, $importer->init_start, $importer->table_names_alter);

          } else {

            return true;

          }

        }

        if ($import !== true) {

          return ['status' => 'repeat', 'file' => $this->continueFile, 'seek' => $import];

        } else {

          return ['status' => 'new_file'];

        }

      }

      $this->migration->log(__('Database restored', 'backup-backup'), 'SUCCESS');

    }

    public function restoreDatabaseDynamic(&$manifest) {

      if ($this->firstDB == true) {
        $this->migration->log(__('Checking the database structure...', 'backup-backup'), 'STEP');
      }

      if (is_dir($this->tmp . DIRECTORY_SEPARATOR . 'db_tables')) {

        $forcev3Engine = false;
        if (isset($manifest->db_backup_engine) && $manifest->db_backup_engine === 'v4') {
          if ($this->v3engine == false) {
            $forcev3Engine = true;

            if ($this->firstDB == true) {
              $this->migration->log(__('New search replace is disabled, nevertheless your backup does not support it, forcing to use new S&R engine.', 'backup-backup'), 'WARN');
            }
          }
        }

        if ($this->v3engine || $forcev3Engine) {

          if (!$this->isCLI) {

            $this->v3RestoreUsed = true;
            $import = $this->restoreDatabaseV3($manifest);
            return $import;

          } else {

            $this->v3RestoreUsed = true;
            $this->restoreDatabaseV3($manifest);

          }

        } else {

          if (!$this->isCLI) {

            $import = $this->restoreDatabaseV2($manifest);
            return $import;

          } else {

            $this->restoreDatabaseV2($manifest);

          }

        }

      } elseif (file_exists($this->tmp . DIRECTORY_SEPARATOR . 'bmi_database_backup.sql')) {

        $this->restoreDatabaseV1($manifest);

      } else {

        $this->migration->log(__('This backup does not contain database copy, omitting...', 'backup-backup'), 'INFO');
        return false;

      }

      return true;

    }

    public function cleanupCurrentThemesAndPlugins() {

      if ($this->cleanupbefore == true) {

        $this->migration->log(__('Moving current themes and plugins.', 'backup-backup'), 'STEP');

        $plugins_path = BMP::fixSlashes(WP_PLUGIN_DIR);
        $themes_path = BMP::fixSlashes(dirname(get_template_directory()));

        $plugins = [];
        if (file_exists($plugins_path)) {
          $plugins = array_values(array_diff(scandir($plugins_path), ['..', '.', 'backup-backup', 'backup-backup-pro']));
        }

        $themes = [];
        if (file_exists($themes_path)) {
          $themes = array_values(array_diff(scandir($themes_path), ['..', '.', 'backup-backup', 'backup-backup-pro']));
        }

        $destination = BMI_BACKUPS_DEFAULT . DIRECTORY_SEPARATOR . 'clean-ups';
        $destination_unique = $destination . DIRECTORY_SEPARATOR . 'restoration_' . intval($this->start);

        $destination_plugins = $destination_unique . DIRECTORY_SEPARATOR . 'plugins';
        $destination_themes = $destination_unique . DIRECTORY_SEPARATOR . 'themes';

        if (!file_exists($destination)) @mkdir($destination, 0775, true);
        if (!file_exists($destination_unique)) @mkdir($destination_unique, 0775, true);
        if (!file_exists($destination_plugins)) @mkdir($destination_plugins, 0775, true);
        if (!file_exists($destination_themes)) @mkdir($destination_themes, 0775, true);

        for ($i = 0; $i < sizeof($plugins); ++$i) {
          $pluginPath = trailingslashit($plugins_path) . $plugins[$i];
          $destPath = trailingslashit($destination_plugins) . $plugins[$i];
          rename($pluginPath, $destPath);
        }

        for ($i = 0; $i < sizeof($themes); ++$i) {
          $themePath = trailingslashit($themes_path) . $themes[$i];
          $destPath = trailingslashit($destination_themes) . $themes[$i];
          rename($themePath, $destPath);
        }

        $this->migration->log(__('Themes and plugins moved to safe directory.', 'backup-backup'), 'SUCCESS');

      }

      return true;

    }

    public function rescueCleanedThemesAndPlugins() {

      if ($this->cleanupbefore == true) {

        $this->migration->log(__('Restoring moved themes and plugins.', 'backup-backup'), 'INFO');

        $plugins_path = BMP::fixSlashes(WP_PLUGIN_DIR);
        $themes_path = BMP::fixSlashes(dirname(get_template_directory()));

        $destination = BMI_BACKUPS_DEFAULT . DIRECTORY_SEPARATOR . 'clean-ups';
        $destination_unique = $destination . DIRECTORY_SEPARATOR . 'restoration_' . intval($this->start);

        $destination_plugins = $destination_unique . DIRECTORY_SEPARATOR . 'plugins';
        $destination_themes = $destination_unique . DIRECTORY_SEPARATOR . 'themes';

        $plugins = [];
        if (file_exists($destination_plugins)) {
          $plugins = array_values(array_diff(scandir($destination_plugins), ['..', '.']));
        }

        $themes = [];
        if (file_exists($destination_themes)) {
          $themes = array_values(array_diff(scandir($destination_themes), ['..', '.']));
        }

        if (!file_exists($plugins_path)) @mkdir($plugins_path, 0775, true);
        if (!file_exists($themes_path)) @mkdir($themes_path, 0775, true);

        for ($i = 0; $i < sizeof($plugins); ++$i) {
          $pluginPath = trailingslashit($destination_plugins) . $plugins[$i];
          $destPath = trailingslashit($plugins_path) . $plugins[$i];
          rename($pluginPath, $destPath);
        }

        for ($i = 0; $i < sizeof($themes); ++$i) {
          $themePath = trailingslashit($destination_themes) . $themes[$i];
          $destPath = trailingslashit($themes_path) . $themes[$i];
          rename($themePath, $destPath);
        }

      }

      return true;

    }

    public function removeCleanedThemesAndPlugins() {

      if (defined('BMI_KEEP_CLEANUPS') && BMI_KEEP_CLEANUPS == true) {

        return true;

      } else {

        if ($this->cleanupbefore == true) {

          $this->migration->log(__('Removing old plugins and themes moved before restoration.', 'backup-backup'), 'INFO');

          $destination = BMI_BACKUPS_DEFAULT . DIRECTORY_SEPARATOR . 'clean-ups';
          $destination_unique = $destination . DIRECTORY_SEPARATOR . 'restoration_' . intval($this->start);
          $this->rrmdir($destination_unique);

        }

      }

    }

    public function replaceDbPrefixInWPConfig(&$manifest) {

      $abs = untrailingslashit(ABSPATH);
      $curr_prefix = $this->table_prefix;
      $new_prefix = $this->dbFoundPrefix;
      
      $this->migration->log('Detected table prefix: ' . $new_prefix, 'VERBOSE');
      $this->migration->log('Forwarded table prefix: ' . $curr_prefix, 'VERBOSE');
      $this->migration->log('Manifest table prefix: ' . $manifest->config->table_prefix, 'VERBOSE');
      
      // if (strtolower($manifest->config->table_prefix) == strtolower($new_prefix)) {
      //   $new_prefix = $manifest->config->table_prefix;
      // }
      
      // if (strlen(trim($manifest->config->table_prefix)) == 0) {
      //   return;
      // }
      
      // if (strlen(trim($new_prefix)) == 0) {
      //   return;
      // }
      
      $new_prefix = $manifest->config->table_prefix;
      
      $this->migration->log(__('Restoring wp-config file...', 'backup-backup'), 'STEP');
      $wpconfigDir = $abs . DIRECTORY_SEPARATOR . 'wp-config.' . $this->tmptime . '.php';
      if (file_exists($wpconfigDir) && is_readable($wpconfigDir) && is_writable($wpconfigDir)) {

        // rename($abs . DIRECTORY_SEPARATOR . 'wp-config.' . $this->tmptime . '.php', $abs . DIRECTORY_SEPARATOR . 'wp-config.php');
        $wpconfig = file_get_contents($abs . DIRECTORY_SEPARATOR . 'wp-config.php');
        if (strpos($wpconfig, '"' . $curr_prefix . '";') !== false) {
          $wpconfig = str_replace('"' . $curr_prefix . '";', '"' . $new_prefix . '";', $wpconfig);
        } elseif (strpos($wpconfig, "'" . $curr_prefix . "';") !== false) {
          $wpconfig = str_replace("'" . $curr_prefix . "';", "'" . $new_prefix . "';", $wpconfig);
        }

        file_put_contents($abs . DIRECTORY_SEPARATOR . 'wp-config.php', $wpconfig);

        $this->migration->log(__('WP-Config restored', 'backup-backup'), 'SUCCESS');

      } else {

        $this->migration->log(__('Cannot write to WP-Config, if you need to change database prefix, please do it manually.', 'backup-backup'), 'WARN');

      }

    }

    public function restoreOriginalWPConfig($remove = true) {

      // $abs = untrailingslashit(ABSPATH);
      // $tmp_file_f = $abs . DIRECTORY_SEPARATOR . 'wp-config.' . $this->tmptime . '.php';
      // if (file_exists($tmp_file_f)) {
      //   copy($tmp_file_f, $abs . DIRECTORY_SEPARATOR . 'wp-config.php');
      //   if ($remove === true) @unlink($tmp_file_f);
      // }
      //
      // wp_load_alloptions(true);

    }

    public function makeNewLoginSession(&$manifest) {
        global $wpdb;

        $prefix = sanitize_key($manifest->config->table_prefix);
        // Ensure correct prefix is used before anything
        $wpdb->set_prefix($manifest->config->table_prefix);
        wp_load_alloptions(true);
        $this->migration->log(__('Making new login session', 'backup-backup'), 'STEP');

        $prefix = $wpdb->prefix;
        $cap_key = $prefix . 'capabilities';
        $uid = isset($manifest->uid) ? intval($manifest->uid) : 0;

        // Check if provided UID is valid
        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Identifier is safely escaped via escapeSQLIDentifier()
        $is_valid_uid = $uid > 0 && $wpdb->get_var(
            $wpdb->prepare("SELECT ID FROM " . BMP::escapeSQLIDentifier($prefix . "users") . " WHERE ID = %d", $uid)
        );

        // If no UID, cron mode, or invalid UID, find an administrator manually
        if (
            !$is_valid_uid ||
            $manifest->cron === true ||
            $manifest->cron === 'true'
        ) {
            // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Identifier is safely escaped via escapeSQLIDentifier()
            $uid = $wpdb->get_var($wpdb->prepare("
                SELECT u.ID
                FROM " . BMP::escapeSQLIDentifier($prefix . "users") . " u
                INNER JOIN " . BMP::escapeSQLIDentifier($prefix . "usermeta") . " um ON u.ID = um.user_id
                WHERE um.meta_key = %s
                  AND um.meta_value LIKE %s
                LIMIT 1
            ", $cap_key, '%administrator%'));

            // Fallback to first user if no admin found
            if (!$uid) {
                // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Identifier is safely escaped via escapeSQLIDentifier()
                $uid = $wpdb->get_var("SELECT ID FROM " . BMP::escapeSQLIDentifier($prefix . "users") . " LIMIT 1");
            }

            $uid = intval($uid);
        }

        // Get user login info from correct (possibly changed) users table
        // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Identifier is safely escaped via escapeSQLIDentifier()
        $user = $wpdb->get_row(
            $wpdb->prepare("SELECT ID, user_login FROM " . BMP::escapeSQLIDentifier($prefix . "users") . " WHERE ID = %d", $uid)
        );

        if ($user && isset($user->ID)) {
            remove_all_actions('wp_login', -1000);

            clean_user_cache(get_current_user_id());
            clean_user_cache($user->ID);

            wp_clear_auth_cookie();
            wp_set_current_user($user->ID, $user->user_login);
            wp_set_auth_cookie($user->ID, true, is_ssl());

            // Manually trigger wp_login with minimal object
            $fake_user = (object) [
                'ID'         => $user->ID,
                'user_login' => $user->user_login,
            ];
            do_action('wp_login', $user->user_login, $fake_user);

            $manifest->uid = $user->ID;
            $this->migration->log(__('User should be logged in', 'backup-backup'), 'SUCCESS');
        } else {
            $this->migration->log(__('User login failed. Could not find user.', 'backup-backup'), 'ERROR');
        }
    }


    public function setOrUpdateXhria() {

      // Update Original Local Storage Path
      if ($this->backupStorage && is_string($this->backupStorage)) {
        if (function_exists('wp_load_alloptions')) wp_load_alloptions(true);
        delete_option('BMI::STORAGE::LOCAL::PATH');
        if (function_exists('wp_load_alloptions')) wp_load_alloptions(true);
        update_option('BMI::STORAGE::LOCAL::PATH', $this->backupStorage);
      }

      if ($this->code && is_string($this->code) && strlen($this->code) > 0) update_option('z__bmi_xhria', $this->code);
      else delete_option('z__bmi_xhria');

    }

    public function clearElementorCache() {

      $file = trailingslashit(wp_upload_dir()['basedir']) . 'elementor';
      if (file_exists($file) && is_dir($file)) {
        $this->migration->log(__('Clearing elementor template cache...', 'backup-backup'), 'STEP');
        $path = $file . DIRECTORY_SEPARATOR . '*';
        foreach (glob($path) as $file_path) if (!is_dir($file_path)) @unlink($file_path);
        $this->migration->log(__('Elementor cache cleared!', 'backup-backup'), 'SUCCESS');
      }

    }

    public function finalCleanUP() {

      $this->migration->log(__('Cleaning temporary files...', 'backup-backup'), 'STEP');
      $this->cleanup();
      $this->removeCleanedThemesAndPlugins();
      $this->migration->log(__('Temporary files cleaned', 'backup-backup'), 'SUCCESS');

    }

    public function handleError($e) {

      // Restore moved themes and plugins
      $this->rescueCleanedThemesAndPlugins();

      // On this tragedy at least remove tmp files
      $this->migration->log(__('Something bad happened...', 'backup-backup'), 'ERROR');
      if (method_exists($e, 'getMessage')) {
        $this->migration->log($e->getMessage(), 'ERROR');
        $this->migration->log($e->getLine() . ' @ ' . $e->getFile(), 'ERROR');
      }
      $this->cleanup();

    }

    public function makeTMPDirectory() {

      // Make temp dir
      $this->migration->log(__('Making temporary directory', 'backup-backup'), 'INFO');
      if (!(is_dir($this->tmp) || file_exists($this->tmp))) {
        mkdir($this->tmp, 0755, true);
      }

      // Deny read of this folder
      copy(BMI_INCLUDES . DIRECTORY_SEPARATOR . 'htaccess' . DIRECTORY_SEPARATOR . '.htaccess', $this->tmp . DIRECTORY_SEPARATOR . '.htaccess');
      touch($this->tmp . DIRECTORY_SEPARATOR . 'index.html');
      touch($this->tmp . DIRECTORY_SEPARATOR . 'index.php');

    }

    public function backupLocalOptions() {
      global $wpdb;
      $bmi_config_options = $wpdb->get_results( "SELECT option_name, option_value FROM $wpdb->options WHERE option_name LIKE '%bmi_%' OR option_name LIKE '%bmip_%' OR option_name LIKE '%bmi_pro_%'" );
      $tempConfigFile = BMI_TMP . DIRECTORY_SEPARATOR . 'bmi_local_options.json';
      $options = [];
      foreach ( $bmi_config_options as $option ) {
        $options[ $option->option_name ] = maybe_unserialize( $option->option_value );
      }
      $content = json_encode( $options );
      file_put_contents($tempConfigFile, $content);

    }

    public function restoreLocalPluginConfiguration() {      
      $tempConfigFile = BMI_TMP . DIRECTORY_SEPARATOR . 'bmi_local_options.json';
      
      if (!file_exists($tempConfigFile) || !is_readable($tempConfigFile)) {
        return;
      }

      $json_data = file_get_contents($tempConfigFile);
      $options = json_decode($json_data, true);
      wp_cache_flush();
      wp_load_alloptions(true);

      global $wpdb;
      $bmi_config_options = $wpdb->get_results( "SELECT option_name FROM $wpdb->options WHERE option_name LIKE '%bmi_%' OR option_name LIKE '%bmip_%' OR option_name LIKE '%bmi_pro_%'" );
      foreach ( $bmi_config_options as $option ) {
        delete_option( $option->option_name );
      }

      if (is_array($options)) {
        foreach ($options as $name => $value) {
          update_option($name, $value);
          
        }
      }

      wp_cache_flush();
      wp_load_alloptions(true);
      
      unlink($tempConfigFile);
    }

    private function makeRestoreSecret() {

      $this->migration->log(__('Making new secret key for current restore process.', 'backup-backup'), 'STEP');
      $secret = $this->randomString();
      file_put_contents(BMI_TMP . DIRECTORY_SEPARATOR . '.restore_secret', $secret);
      $this->migration->log(__('Secret key generated, it will be returned to you (ping).', 'backup-backup'), 'SUCCESS');

      return $secret;

    }

    public function listBackupContents() {

      $manager = new ZipManager();

      $save = $this->scanFile;
      $amount = $manager->getPartsToRestore($this->src, $save);
      if ($amount === false) {
        $amount = $manager->getZipContentList($this->src, $save);
      }

      $this->migration->log(__('Scan found ', 'backup-backup') . $amount . __(' files inside the backup.', 'backup-backup'), 'INFO');

      return $amount;

    }

    public function extractTo($secret = null) {

      try {

        // Require Universal Zip Library
        require_once BMI_INCLUDES . DIRECTORY_SEPARATOR . 'zipper' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'zip.php';

        // Make restore secret
        if (!$this->isCLI && $this->batchStep == 0) {

          // Verbose
          Logger::log('Restoring site...');

          if ((gettype($secret) != 'string' || strlen($secret) != 64)) {

            $secret = $this->makeRestoreSecret();
            BMP::res(['status' => 'secret', 'tmp' => $this->tmptime, 'secret' => $secret, 'options' => [
              'code' => $this->code,
              'start' => $this->start,
              'step' => 0
            ]]);
            return;

          } else {

            // $this->migration->log(__('Secret key detected successfully (pong)!', 'backup-backup'), 'INFO');

          }

        }

        // STEP: 1
        if ($this->isCLI || $this->batchStep == 1) {

          if (!$this->isCLI) {

            $this->migration->log(__('Secret key detected successfully (pong)!', 'backup-backup'), 'INFO');

          }

          // Make temporary directory
          $this->makeTMPDirectory();

          // Migrate local options
          $this->backupLocalOptions();

          // Time start
          $this->migration->log(__('Scanning archive...', 'backup-backup'), 'STEP');

          if (!$this->isCLI) {

            BMP::res(['status' => 'restore_ongoing', 'tmp' => $this->tmptime, 'secret' => $secret, 'options' => [
              'code' => $this->code,
              'start' => $this->start,
              'step' => 1
            ]]);

            return;

          }

        }

        // STEP: 2
        if ($this->isCLI || $this->batchStep == 2) {

          // Get ZIP contents for batch unzipping
          $this->fileAmount = $this->listBackupContents();
          $this->cleanupCurrentThemesAndPlugins();

          if (!$this->isCLI) {

            BMP::res(['status' => 'restore_ongoing', 'tmp' => $this->tmptime, 'secret' => $secret, 'options' => [
              'code' => $this->code,
              'start' => $this->start,
              'amount' => $this->fileAmount,
              'step' => 2
            ]]);

            return;

          }

        }

        // STEP: 3
        if ($this->isCLI || $this->batchStep == 3) {

          // UnZIP the backup
          try {
            $unzipped = $this->makeUnZIP();
          } catch (\Exception $e) {
            $this->handleError($e);
            return;
          } catch (\Throwable $t) {
            $this->handleError($t);
            return;
          }
          
          if ($unzipped === false) {

            $this->handleError(__('File extraction process failed.', 'backup-backup'));
            return;

          }

          if (!$this->isCLI) {

            $shouldRepeat = false;
            if ($unzipped === 'repeat') {

              $shouldRepeat = true;

            } else {

              $shouldRepeat = false;

            }

            BMP::res(['status' => 'restore_ongoing', 'tmp' => $this->tmptime, 'secret' => $secret, 'options' => [
              'code' => $this->code,
              'start' => $this->start,
              'amount' => $this->fileAmount,
              'recent_export_seek' => $this->recent_export_seek,
              'repeat_export' => $shouldRepeat,
              'firstExtract' => $this->firstExtract,
              'step' => 3
            ]]);

            return;

          }

        }

        // STEP: 4
        if ($this->isCLI || $this->batchStep == 4) {

          // Check if extracted files are not in backslashed Windows version, otherwise fix it
          $this->fixDumbWindowsSlashes();
          $this->removeUnwantedFiles();

          // WP Config backup
          $this->makeWPConfigCopy();

          if (!$this->isCLI) {

            BMP::res(['status' => 'restore_ongoing', 'tmp' => $this->tmptime, 'secret' => $secret, 'options' => [
              'code' => $this->code,
              'start' => $this->start,
              'amount' => $this->fileAmount,
              'storage' => get_option('BMI::STORAGE::LOCAL::PATH', false),
              'step' => 4
            ]]);

            return;

          }

        }

        // STEP: 5
        if ($this->isCLI || $this->batchStep == 5) {

          // Get manifest
          $manifest = $this->getCurrentManifest(true);

          try {

            if (isset($manifest->version)) {
              $this->migration->log(__('Backup Migration version used for that backup: ', 'backup-backup') . $manifest->version, 'INFO');
            } else {
              $this->migration->log(__('Backup was made with unknown version of Backup Migration plugin.', 'backup-backup'), 'INFO');
            }

          } catch (\Exception $e) {

            $this->migration->log(__('Backup was made with unknown version of Backup Migration plugin.', 'backup-backup'), 'INFO');

          } catch (\Throwable $e) {

            $this->migration->log(__('Backup was made with unknown version of Backup Migration plugin.', 'backup-backup'), 'INFO');

          }

          // Even remove extracted WP-config if it's different site.
          if (untrailingslashit($manifest->dbdomain) != untrailingslashit($this->siteurl)) {

            // Unlink wp-config inside extracted directory
            $extractedWpConfigPath = $this->tmp . DIRECTORY_SEPARATOR . 'wordpress' . DIRECTORY_SEPARATOR . 'wp-config.php';
            if (file_exists($extractedWpConfigPath)) @unlink($extractedWpConfigPath);

          }

          // Restore files
          $this->restoreBackupFromFiles($manifest);


          if (untrailingslashit($manifest->dbdomain) != untrailingslashit($this->siteurl)) {

            // Restore WP Config if it's different domain
            $this->restoreOriginalWPConfig(false);

          }

          if (!$this->isCLI) {

            BMP::res(['status' => 'restore_ongoing', 'tmp' => $this->tmptime, 'secret' => $secret, 'options' => [
              'code' => $this->code,
              'start' => $this->start,
              'amount' => $this->fileAmount,
              'storage' => $this->backupStorage,
              'step' => 5
            ]]);

            return;

          }

        }

        // STEP: 6
        if ($this->isCLI || $this->batchStep == 6) {

          // This literally does nothing.

          if (!$this->isCLI) {

            BMP::res(['status' => 'restore_ongoing', 'tmp' => $this->tmptime, 'secret' => $secret, 'options' => [
              'code' => $this->code,
              'start' => $this->start,
              'amount' => $this->fileAmount,
              'storage' => $this->backupStorage,
              'step' => 6
            ]]);

            return;

          }

        }

        // STEP 7
        if ($this->isCLI || $this->batchStep == 7) {

          // Get manifest
          if (!isset($manifest)) {
            $manifest = $this->getCurrentManifest();
          }

          $this->migration->log(__('Validating table prefix...', 'backup-backup'), 'STEP');
          $dbPrefix = $this->findTablePrefixByFiles($manifest->config->table_prefix);
          $this->migration->log(__('Table prefix in manifest: ', 'backup-backup') . $manifest->config->table_prefix, 'INFO');
          $this->migration->log(__('Detected table prefix: ', 'backup-backup') . $dbPrefix, 'INFO');

          $wasDisabled = 0;
          $dbFinishedConv = 'false';
          $newDataProcess = $this->processData;

          $forcev3Engine = false;
          if (isset($manifest->db_backup_engine) && $manifest->db_backup_engine === 'v4') {
            if ($this->v3engine == false) {
              $forcev3Engine = true;
            }
          }

          if ($this->v3engine || $forcev3Engine) {

            if ($this->usingDbEngineV4) {
              $this->migration->log(__('Splitting process is disabled because v4 restore engine is enabled.', 'backup-backup'), 'INFO');
            } else {
              $this->migration->log(__('Splitting process is disabled because v3 restore engine is enabled.', 'backup-backup'), 'INFO');
            }

            $wasDisabled = 1;

          } else if (!$this->splitting) {

            $this->migration->log(__('Splitting process is disabled in the settings, omitting.', 'backup-backup'), 'INFO');
            $wasDisabled = 1;

          } else {

            $db_tables = $this->tmp . DIRECTORY_SEPARATOR . 'db_tables';

            if (is_dir($db_tables)) {

              if (empty($this->processData)) {
                $this->migration->log(__('Converting database files into partial files.', 'backup-backup'), 'STEP');
                if (defined('BMI_DB_MAX_ROWS_PER_QUERY')) {
                  $this->migration->log(__('Max rows per query (this site): ', 'backup-backup') . BMI_DB_MAX_ROWS_PER_QUERY, 'INFO');
                }

                try {

                  if (isset($manifest->source_query_output)) {
                    $this->migration->log(__('Max rows per query (source site): ', 'backup-backup') . $manifest->source_query_output, 'INFO');
                  } else {
                    $this->migration->log(__('Unknown query output value of backup file, maybe it was made before v1.1.7', 'backup-backup'), 'INFO');
                  }

                } catch (\Exception $e) {

                  $this->migration->log(__('Unknown query output value of backup file, maybe it was made before v1.1.7', 'backup-backup'), 'INFO');

                } catch (\Throwable $e) {

                  $this->migration->log(__('Unknown query output value of backup file, maybe it was made before v1.1.7', 'backup-backup'), 'INFO');

                }

              }


              $dbsort = new SmartDatabaseSort($db_tables, $this->migration, $this->isCLI);
              $process = $dbsort->sortUnsorted($this->processData);

              if (!is_null($process) && isset($process)) {
                $newDataProcess = $process;
              }

              if ($this->isCLI || (isset($process['convertionFinished']) && $process['convertionFinished'] == 'yes')) {
                $this->migration->log(__('Database convertion finished successfully.', 'backup-backup'), 'SUCCESS');

                $this->migration->log(__('Calculating new query size and counts.', 'backup-backup'), 'STEP');
                $stats = $dbsort->countAllFilesAndQueries();
                $this->migration->log(__('Calculaion completed, printing details.', 'backup-backup'), 'SUCCESS');

                $this->migration->log(__('Total queries to insert after conversion: ', 'backup-backup') . $stats['total_queries'], 'INFO');
                $this->migration->log(__('Partial files count after conversion: ', 'backup-backup') . sizeof($stats['all_files']), 'INFO');
                $this->migration->log(__('Total size of the database: ', 'backup-backup') . BMP::humanSize($stats['total_size']), 'INFO');
                $this->migration->log(__('Table count to be imported: ', 'backup-backup') . sizeof($stats['all_tables']), 'INFO');

                $total_qrs = $stats['total_queries'];
                $this->conversionStats = [];
                $this->conversionStats['total_queries'] = $total_qrs;

                $dbFinishedConv = 'true';
              }

            } else {

              if (file_exists($this->tmp . DIRECTORY_SEPARATOR . 'bmi_database_backup.sql')) {

                $this->migration->log(__('Ommiting database convert step as the database backup included was not made with V2 engine.', 'backup-backup'), 'WARN');
                $this->migration->log(__('The process may be less stable if the database is larger than usual.', 'backup-backup'), 'WARN');
                $dbFinishedConv = 'true';

              } else {

                $this->migration->log(__('Ommiting database convert step as there is no database backup included.', 'backup-backup'), 'INFO');
                $dbFinishedConv = 'true';

              }

            }

          }

          if (!$this->isCLI) {

            BMP::res(['status' => 'restore_ongoing', 'tmp' => $this->tmptime, 'secret' => $secret, 'options' => [
              'code' => $this->code,
              'start' => $this->start,
              'amount' => $this->fileAmount,
              'dbConvertionFinished' => $dbFinishedConv,
              'processData' => $newDataProcess,
              'conversionStats' => $this->conversionStats,
              'storage' => $this->backupStorage,
              'dbFoundPrefix' => $dbPrefix,
              'step' => 7 + $wasDisabled
            ]]);

            return;

          }

        }

        // STEP: 8
        if ($this->isCLI || $this->batchStep == 8) {

          // Get manifest
          if (!isset($manifest)) {
            $manifest = $this->getCurrentManifest();
          }

          if (isset($manifest->db_backup_engine) && $manifest->db_backup_engine === 'v4') {
            require_once BMI_INCLUDES . DIRECTORY_SEPARATOR . 'database' . DIRECTORY_SEPARATOR . 'even-better-restore-v4.php';
            $this->usingDbEngineV4 = true;
          } else {
            require_once BMI_INCLUDES . DIRECTORY_SEPARATOR . 'database' . DIRECTORY_SEPARATOR . 'even-better-restore-v3.php';
            $this->usingDbEngineV4 = false;
          }

          // Try to restore database
          if (!$this->isCLI) {

            $dbFinished = false;
            $database_exist = $this->restoreDatabaseDynamic($manifest);

            if ($database_exist === false || $database_exist === true) {

              $dbFinished = true;

            } else {

              if ($database_exist['status'] == 'new_file') {

                $this->continueFile = false;
                $this->continueSeek = false;

              } else {

                $this->continueFile = $database_exist['file'];
                $this->continueSeek = $database_exist['seek'];

              }

            }

          } else {

            $database_exist = $this->restoreDatabaseDynamic($manifest);

          }

          $this->databaseExist = $database_exist;

          if (!$this->isCLI) {

            BMP::res(['status' => 'restore_ongoing', 'tmp' => $this->tmptime, 'secret' => $secret, 'options' => [
              'code' => $this->code,
              'start' => $this->start,
              'amount' => $this->fileAmount,
              'databaseExist' => $database_exist === true ? 'true' : 'false',
              'continueFile' => $this->continueFile,
              'continueSeek' => $this->continueSeek,
              'dbFinished' => $dbFinished,
              'firstDB' => $this->firstDB,
              'db_xi' => $this->db_xi,
              'ini_start' => $this->ini_start,
              'table_names_alter' => $this->table_names_alter,
              'conversionStats' => $this->conversionStats,
              'v3RestoreUsed' => $this->v3RestoreUsed,
              'dbFoundPrefix' => $this->dbFoundPrefix,
              'storage' => $this->backupStorage,
              'step' => 8
            ]]);

            return;

          }

        }

        // STEP: 9
        if ($this->isCLI || $this->batchStep == 9) {

          // Get manifest
          if (!isset($manifest)) {
            $manifest = $this->getCurrentManifest();
          }

          if (isset($manifest->db_backup_engine) && $manifest->db_backup_engine === 'v4') {
            require_once BMI_INCLUDES . DIRECTORY_SEPARATOR . 'database' . DIRECTORY_SEPARATOR . 'even-better-restore-v4.php';
            $this->usingDbEngineV4 = true;
          } else {
            require_once BMI_INCLUDES . DIRECTORY_SEPARATOR . 'database' . DIRECTORY_SEPARATOR . 'even-better-restore-v3.php';
            $this->usingDbEngineV4 = false;
          }

          $database_exist = $this->databaseExist;
          if ($database_exist === true || $database_exist === 'true') {
            $this->removePreviousSelectionsIfDatabaseIncluded();
          }

          // Alter all tables
          $status = false;
          $tableIndex = $this->tableIndex;
          $replaceStep = $this->replaceStep;
          $replaceFinished = false;
          $currentReplacePage = 0;
          $totalReplacePage = 0;
          $fieldAdjustments = 0;

          if ($this->isCLI) {

            $srFinished = false;
            if ($database_exist && $this->v3RestoreUsed == true) {
              while (!$srFinished) {

                $status = $this->search_replace_v3($manifest);

                if ($status != false && is_array($status)) {
                  $this->replaceStep = $status['step'];
                  $this->tableIndex = $status['tableIndex'];
                  $this->replaceFinished = $status['finished'];
                  $this->currentReplacePage = $status['currentPage'];
                  $this->totalReplacePage = $status['totalPages'];
                  $this->fieldAdjustments = $status['fieldAdjustments'];

                  if ($this->replaceFinished == true) $srFinished = true;
                }

              }
            } else {
              $this->replaceFinished = true;
              $this->migration->progress(98);
            }

          } else {

            if ($database_exist && $this->v3RestoreUsed == true) {
              $status = $this->search_replace_v3($manifest);
            } else {
              $this->replaceFinished = true;
              $this->migration->progress(98);
            }

            if ($status != false && is_array($status)) {
              $this->replaceStep = $status['step'];
              $this->tableIndex = $status['tableIndex'];
              $this->replaceFinished = $status['finished'];
              $this->currentReplacePage = $status['currentPage'];
              $this->totalReplacePage = $status['totalPages'];
              $this->fieldAdjustments = $status['fieldAdjustments'];
            }

          }

          if (!$this->isCLI) {

            BMP::res([
              'status' => 'restore_ongoing',
              'tmp' => $this->tmptime,
              'secret' => $secret,
              'options' => [
                'code' => $this->code,
                'start' => $this->start,
                'amount' => $this->fileAmount,
                'databaseExist' => $database_exist === true ? 'true' : 'false',
                'firstDB' => $this->firstDB,
                'db_xi' => $this->db_xi,
                'ini_start' => $this->ini_start,
                'table_names_alter' => $this->table_names_alter,
                'conversionStats' => $this->conversionStats,
                'v3RestoreUsed' => $this->v3RestoreUsed,
                'replaceStep' => $this->replaceStep,
                'tableIndex' => $this->tableIndex,
                'replaceFinished' => $this->replaceFinished,
                'currentReplacePage' => $this->currentReplacePage,
                'totalReplacePage' => $this->totalReplacePage,
                'fieldAdjustments' => $this->fieldAdjustments,
                'dbFoundPrefix' => $this->dbFoundPrefix,
                'storage' => $this->backupStorage,
                'step' => 9
              ]
            ]);

            return;

          }

        }

        // STEP: 10
        if ($this->isCLI || $this->batchStep == 10) {

          // Rename database from temporary to destination
          // And do the rest
          // Step 10 runs only at the end of database import
          // Get manifest
          if (!isset($manifest)) {
            $manifest = $this->getCurrentManifest();
          }

          if (isset($manifest->db_backup_engine) && $manifest->db_backup_engine === 'v4') {
            require_once BMI_INCLUDES . DIRECTORY_SEPARATOR . 'database' . DIRECTORY_SEPARATOR . 'even-better-restore-v4.php';
            $this->usingDbEngineV4 = true;
          } else {
            require_once BMI_INCLUDES . DIRECTORY_SEPARATOR . 'database' . DIRECTORY_SEPARATOR . 'even-better-restore-v3.php';
            $this->usingDbEngineV4 = false;
          }

          $database_exist = $this->databaseExist;

          // Restore WP Config ** It allows to recover session after restore no matter what
          if ($database_exist == true || $database_exist == 'true') {

            // Alter all tables
            if ($this->v3RestoreUsed == true) {
              
              $this->alter_tables_v3($manifest);
              
            } else {
              
              $this->alter_tables($manifest);
              
              // Modify the WP Config and replace
              $this->replaceDbPrefixInWPConfig($manifest);
              
            }

            // User is logged off at this point, try to log in
            $this->makeNewLoginSession($manifest);

          } else {

            // Restore WP Config without modifications
            $this->restoreOriginalWPConfig();

          }

          // Make sure the Xhria was not modified
          $this->setOrUpdateXhria();

          // Fix elementor templates
          $this->clearElementorCache();

          // Make final cleanup
          $this->finalCleanUP();

          // Final flush of rewrite rules
          flush_rewrite_rules();
          
          // Remove backup migration temporary options
          $this->restoreLocalPluginConfiguration();

          // Dedicated fix for block-wp-login plugin
          $this->fixWPLogin($manifest);

          // Touch autologin file
          $autologin_file = BMI_BACKUPS . DIRECTORY_SEPARATOR . '.autologin';
          touch($autologin_file);

          // Final verbose
          if ((intval(microtime(true)) - intval($this->start)) > 0) {
            $this->migration->log(__('Restore process took: ', 'backup-backup') . (intval(microtime(true)) - intval($this->start)) . ' seconds.', 'INFO');
          } else {
            $this->migration->log(__('Restore process fully finished.', 'INFO'));
          }
          Logger::log('Site restored...');

          // Return success
          return true;

        }

      } catch (\Exception $e) {

        // On this tragedy at least remove tmp files
        $this->handleError($e);
        return false;

      } catch (\Throwable $e) {

        // On this tragedy at least remove tmp files
        $this->handleError($e);
        return false;

      }

    }

  }
