<?php

/**
 * Author: Mikołaj `iClyde` Chodorowski
 * Contact: kontakt@iclyde.pl
 * Package: Backup Migration – WP Plugin
 */

// Namespace
namespace BMI\Plugin\Database;

// Use
use BMI\Plugin\BMI_Logger AS Logger;
use BMI\Plugin\Backup_Migration_Plugin as BMP;
use BMI\Plugin\Progress\BMI_ZipProgress AS Progress;
use BMI\Plugin\Database\BMI_Search_Replace_Engine as BMISearchReplace;
use BMI\Plugin\Dashboard as Dashboard;

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

/**
 * Database Restore Enginge v4
 */
class BMI_Even_Better_Database_Restore {

  // Unwanted tables
  private $unwantedTables = [
    'wfblockediplog',
    'wfblocks7',
    'wfcrawlers',
    'wffilechanges',
    'wffilemods',
    'wfhits',
    'wfhoover',
    'wfissues',
    'wfknownfilelist',
    'wflivetraffichuman',
    'wflocs',
    'wflogins',
    'wfnotifications',
    'wfpendingissues',
    'wfreversecache',
    'wfsnipcache',
    'wfstatus',
    'wftrafficrate',
    'actionscheduler_logs',
    'slim_stats',
    'woocommerce_sessions',
    'yoast_indexable',
    'slim_events',
    'cerber_files',
    'cerber_traffic',
    'cerber_log',
    'cerber_countries',
    'cerber_blocks',
    'cerber_acl',
    'statistics_views',
    'pcachewpr',
    'statistics_visitor_relationships',
    'statistics_visitor',
    'statistics_visit',
    'statistics_search',
    'statistics_pages',
    'icl_languages_translations',
    'icl_string_pages',
    'icl_string_translations',
    'itsec_log',
    'actionscheduler_actions',
    'aepc_logs',
    'WP_SEO_404_links',
    'wp_seo_404_links',
    'WP_SEO_Redirection_LOG'
  ];
  
  private $excludeSearchReplaceTables = [
    'itsec_log'
  ];

  /**
   * __construct - Make connection
   *
   * @return @self
   */
  function __construct($storage, $firstDB, &$manifest, &$logger, $splitting, $isCLI) {

    $this->isCLI = $isCLI;
    $this->splitting = $splitting;
    $this->storage = $storage;
    $this->logger = &$logger;
    $this->manifest = &$manifest;
    $this->tablemap = BMI_TMP . DIRECTORY_SEPARATOR . '.table_map';

    if ($firstDB) $this->initMessage();

    $this->map = $this->getTableMap();
    $this->seek = &$this->map['seek'];

  }

  public function start() {

    if ($this->isCLI) {

      while ($nextFile = $this->getNextFile()) {
        $this->processFile($nextFile);
      }

      return true;

    } else {

      $nextFile = $this->getNextFile();
      if ($nextFile === false) return true;
      else {

        $this->processFile($nextFile);

        return false;

      }

    }

  }

  private function getTableMap() {

    if (file_exists($this->tablemap)) {

      $data = json_decode(file_get_contents($this->tablemap), true);
      $this->map = $data;

    } else {

      $data = [
        'tables' => [],
        'seek' => [
          'last_seek' => 0,
          'last_file' => '...',
          'last_start' => 0,
          'total_tables' => sizeof(array_diff(scandir($this->storage), ['..', '.'])),
          'active_plugins' => 'a:1:{i:0;s:31:"backup-backup/backup-backup.php";}'
        ]
      ];

      file_put_contents($this->tablemap, json_encode($data));

    }

    return $data;

  }

  private function getTableProgress() {

    $total_tables = $this->seek['total_tables'];
    $tables_left = sizeof(array_diff(scandir($this->storage), ['..', '.']));

    $finished_tables = ($total_tables - $tables_left) + 1;
    $percentage = number_format(($finished_tables / $total_tables) * 100, 2);

    $this->logger->progress(50 + ((number_format($percentage, 0) / 2) - 10));

    return $finished_tables . '/' . $total_tables . ' (' . $percentage . '%)';

  }

  private function queryFile(&$objFile, $filePath, $tableName, $realTableName) {

    global $wpdb;

    $seek = &$this->seek['last_seek'];
    if ($seek == 0) {
      $seek = 5;
      // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Identifier is safely escaped via escapeSQLIDentifier()
      $wpdb->query("DROP TABLE IF EXISTS " . BMP::escapeSQLIDentifier($tableName) . ";");

      $str = __("Started restoration of %table_name% %total_tables% table", 'backup-backup');
      $str = str_replace('%table_name%', $realTableName, $str);
      $str = str_replace('%total_tables%', $this->getTableProgress(), $str);
      $this->logger->log($str, 'STEP');

      // Check if file can be cleaned
      $this->filterFile($filePath, basename($filePath));
    }

    $wpdb->suppress_errors();

    $wpdb->query('SET autocommit = 0;');
    $wpdb->query('SET foreign_key_checks = 0;');
    $wpdb->query("SET SQL_MODE = '';");
    $wpdb->query('START TRANSACTION;');

    $sql = '';
    while (!$objFile->eof()) {
      $objFile->seek($seek); $seek++;
      $line = rtrim($objFile->current(), "\n");
      if (strlen($line) !== 0) {
        $sql = $line;
        unset($line);
        break;
      }
    }

    // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Query is from trusted backup SQL file, not user input
    $wpdb->query($sql);
    unset($sql);
    $wpdb->query('COMMIT;');
    $wpdb->query('SET autocommit = 1;');
    $wpdb->query('SET foreign_key_checks = 1;');

    $str = __("Progress of %table_name%: %progress%", 'backup-backup');
    $str = str_replace('%table_name%', $realTableName, $str);

    $objFile->seek($objFile->getSize());
    $total_size = $objFile->key() - 5;
    $objFile->seek($seek);

    if ($total_size <= 0) $total_size = 1;
    if (($seek - 5) <= 0) $seek = 6;
    if ($total_size > 0) $total_size += 1;

    $progress = ($seek - 5) . '/' . $total_size . " (" . number_format(($seek - 5) / $total_size * 100, 2) . "%)";
    $str = str_replace('%progress%', $progress, $str);
    $this->logger->log($str, 'INFO');

    $wpdb->show_errors();

    if ($objFile->eof()) {
      return true;
    } else {
      return false;
    }

  }

  private function addNewTableToMap($from, $to, $file) {

    if (!array_key_exists($from, $this->map['tables'])) {
      $this->map['tables'][$from] = $to;
    }

    file_put_contents($this->tablemap, json_encode($this->map));

  }

  private function processFile($file) {

    if ($this->seek['last_seek'] == 0) {
      $this->seek['last_start'] = microtime(true);
    }

    $objFile = new \SplFileObject($file);

    $objFile->seek(1);
    $realTableName = explode('`', $objFile->current())[1];

    $objFile->seek(2);
    $tmpTableName = explode('`', $objFile->current())[1];

    $finished = $this->queryFile($objFile, $file, $tmpTableName, $realTableName);

    if ($finished && file_exists($file)) {
      $this->seek['last_seek'] = 0;
      $this->seek['last_file'] = '...';
      @unlink($file);

      $totalTime = microtime(true) - intval($this->seek['last_start']);
      $totalTime = number_format($totalTime, 5);

      $str = __("Table %table_name% restoration took %time% seconds", 'backup-backup');
      $str = str_replace('%table_name%', $realTableName, $str);
      $str = str_replace('%time%', $totalTime, $str);

      $this->logger->log($str, 'SUCCESS');
      $this->seek['last_start'] = 0;
    }

    $this->addNewTableToMap($tmpTableName, $realTableName, $file);

    return true;

  }

  private function parseDomain($domain, $removeWWW = true) {

    if (substr($domain, 0, 8) == 'https://') $domain = substr($domain, 8);
    if (substr($domain, 0, 7) == 'http://') $domain = substr($domain, 7);
    if ($removeWWW === true) {
      if (substr($domain, 0, 4) == 'www.') $domain = substr($domain, 4);
    }
    $domain = untrailingslashit($domain);

    return $domain;

  }

  private function replaceTableNames($tables) {

    global $wpdb;

    $this->logger->log(__('Performing table replacement', 'backup-backup'), 'STEP');

    $wpdb->suppress_errors();
    foreach ($tables as $oldTable => $newTable) {

      // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Identifier is safely escaped via escapeSQLIDentifier()
      $wpdb->query("DROP TABLE IF EXISTS " . BMP::escapeSQLIDentifier($newTable) . ";");

      // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Identifier is safely escaped via escapeSQLIDentifier()
      $wpdb->query("ALTER TABLE " . BMP::escapeSQLIDentifier($oldTable) . " RENAME TO " . BMP::escapeSQLIDentifier($newTable) . ";");

      $str = __('Table %old% renamed to %new%', 'backup-backup');
      $str = str_replace('%old%', $oldTable, $str);
      $str = str_replace('%new%', $newTable, $str);
      $this->logger->log($str, 'INFO');

    }

    $wpdb->show_errors();
    $this->logger->log(__('All tables replaced', 'backup-backup'), 'SUCCESS');

  }

  private function performReplace($step = 0, $tableIndex = 0, $currentPage = 0, $totalPages = 0, $fieldAdjustments = 0, $newPrefix = "wp_") {

    $status = [
      'step' => $step,
      'tableIndex' => $tableIndex,
      'finished' => false,
      'currentPage' => $currentPage,
      'totalPages' => $totalPages,
      'fieldAdjustments' => $fieldAdjustments
    ];

    require_once BMI_INCLUDES . DIRECTORY_SEPARATOR . 'database' . DIRECTORY_SEPARATOR . 'search-replace.php';

    $backupRootDir = $this->manifest->config->ABSPATH;
    $currentRootDir = ABSPATH;
    
    $homeURL = site_url();
    if (strlen($homeURL) <= 8) $homeURL = home_url();
    if (defined('WP_SITEURL') && strlen(WP_SITEURL) > 8) $homeURL = WP_SITEURL;

    $backupDomain = $this->parseDomain($this->manifest->dbdomain);
    $currentDomain = $this->parseDomain($homeURL, false);

    $currentTable = false;
    $allTables = array_keys($this->map['tables']);

    if ($tableIndex < sizeof($allTables) && array_key_exists($tableIndex, $allTables)) {
      $currentTable = $allTables[$tableIndex];
    }

    if ($currentTable == false && !$currentTable) {
      if ($backupRootDir != $currentRootDir || $currentDomain != $this->parseDomain($backupDomain, false)) {
        $this->logger->log(__('Search & Replace finished successfully.', 'backup-backup'), 'SUCCESS');
      }

      $status['finished'] = true;
      return $status;
    }
    
    if (strpos($currentTable, $newPrefix) === false) {
      $this->logger->log(__('Adjustments are not required for this table.', 'backup-backup') . "(" . sanitize_text_field(strval($currentTable)) .  ")", 'INFO');

      $status['step'] = 1;
      $status['fieldAdjustments'] = 0;
      $status['tableIndex'] = $tableIndex + 1;
      return $status;
    }
    
    if ($this->searchForIncludesInList($this->excludeSearchReplaceTables, $currentTable)) {
      $this->logger->log(__('Adjustments are not required for this table.', 'backup-backup') . "(" . sanitize_text_field(strval($currentTable)) .  ")", 'INFO');

      $status['step'] = 1;
      $status['fieldAdjustments'] = 0;
      $status['tableIndex'] = $tableIndex + 1;
      return $status;
    }

    $replaceEngine = new BMISearchReplace([$currentTable], $currentPage, $totalPages);

    if ($step == 0) {
      $ssl = is_ssl() == true ? 'https://' : 'http://';
      $this->logger->log('Previous detected domain: ' . $backupDomain, 'VERBOSE');
      $this->logger->log('Previous detected domain parsed: ' . $this->parseDomain($backupDomain, false), 'VERBOSE');
      $this->logger->log('New detected domain: ' . $currentDomain, 'VERBOSE');
      $this->logger->log('SSL: ' . $ssl, 'VERBOSE');
      
      $this->logger->log('Previous ABSPATH: ' . $backupRootDir, 'VERBOSE');
      $this->logger->log('New detected ABSPATH: ' . $currentRootDir, 'VERBOSE');
      
      if ($backupRootDir != $currentRootDir || $currentDomain != $this->parseDomain($backupDomain, false)) {
        $this->logger->log(__('Performing Search & Replace', 'backup-backup'), 'STEP');
        $pagesize = '?';
        if (defined('BMI_MAX_SEARCH_REPLACE_PAGE')) $pagesize = BMI_MAX_SEARCH_REPLACE_PAGE;
        $this->logger->log(__('Page size for that restoration: ', 'backup-backup') . $pagesize, 'INFO');
        $status['step'] = $step + 1; $step++;
      } else {
        $this->logger->log(__('This backup was made on the same site, ommiting search & replace.', 'backup-backup'), 'INFO');
        $status['finished'] = true;
        return $status;
      }
    }

    if ($step == 1) {
      $replaceProgress = ($tableIndex + 1) . "/" . sizeof($allTables);
      $replaceProgressPercentage = number_format((($tableIndex + 1) / sizeof($allTables) * 100), 2);
      $progressLogT = __('Performing database adjustments for table %progress%: %table_name% (%progress_percentage%)', 'backup-backup');
      $progressLogT = str_replace('%progress%', $replaceProgress, $progressLogT);
      $progressLogT = str_replace('%table_name%', $currentTable, $progressLogT);
      $progressLogT = str_replace('%progress_percentage%', $replaceProgressPercentage . '%', $progressLogT);
      $this->logger->log($progressLogT, 'STEP');

      $percentageProgress = number_format($replaceProgressPercentage, 0);
      $this->logger->progress(number_format(90 + ($percentageProgress / 100) * 8, 0));

      if ($backupRootDir != $currentRootDir) {

        $dtables = 0; $drows = 0; $dchange = 0; $dupdates = 0;

        $r = $replaceEngine->perform($backupRootDir, $currentRootDir);
        $dtables += $r['tables']; $drows += $r['rows']; $dchange += $r['change']; $dupdates += $r['updates'];

        $status['currentPage'] = $r['currentPage'];
        $status['totalPages'] = $r['totalPages'];

        if ($status['totalPages'] > 0) {

          $info = __("Batch for path adjustment (%page%/%allPages%) updated: %updates% fields.", 'backup-backup');
          $updates = $dupdates;
          if ($updates == 0) $updates = 1;
          $info = str_replace('%page%', $status['currentPage'], $info);
          $info = str_replace('%allPages%', $status['totalPages'], $info);
          $info = str_replace('%updates%', $updates, $info);
          $this->logger->log($info, 'INFO');
          $status['fieldAdjustments']++;

        }

        if ($status['currentPage'] >= $status['totalPages']) {
          $status['currentPage'] = 0;
          $status['totalPages'] = 0;
          $status['step'] = $step + 1;
        }
        return $status;

      } else {

        $status['step'] = $step + 1;
        $step++;

      }
    }

    if ($step == 2 || $step == 3 || $step == 4 || $step == 5 || $step == 6 || $step == 7) {
      if ($currentDomain != $this->parseDomain($backupDomain, false)) {
        $ssl = is_ssl() == true ? 'https://' : 'http://';

        $dtables = 0; $drows = 0; $dchange = 0; $dupdates = 0;

        $possibleDomainsBackup = [
          "https://www." . $backupDomain,
          "http://www." . $backupDomain,
          "https://" . $backupDomain,
          "http://" . $backupDomain,
          'www.' . $backupDomain,
          $backupDomain
        ];

        $possibleDomainsCurrent = [
          $ssl . $currentDomain,
          $ssl . $currentDomain,
          $ssl . $currentDomain,
          $ssl . $currentDomain,
          $currentDomain,
          $currentDomain
        ];

        if ($step == 2) {
          $r = $replaceEngine->perform($possibleDomainsBackup[0], $possibleDomainsCurrent[0]);
          $dtables += $r['tables']; $drows += $r['rows']; $dchange += $r['change']; $dupdates += $r['updates'];
        }

        if ($step == 3) {
          $r = $replaceEngine->perform($possibleDomainsBackup[1], $possibleDomainsCurrent[1]);
          $dtables += $r['tables']; $drows += $r['rows']; $dchange += $r['change']; $dupdates += $r['updates'];
        }

        if ($step == 4) {
          $r = $replaceEngine->perform($possibleDomainsBackup[2], $possibleDomainsCurrent[2]);
          $dtables += $r['tables']; $drows += $r['rows']; $dchange += $r['change']; $dupdates += $r['updates'];
        }

        if ($step == 5) {
          $r = $replaceEngine->perform($possibleDomainsBackup[3], $possibleDomainsCurrent[3]);
          $dtables += $r['tables']; $drows += $r['rows']; $dchange += $r['change']; $dupdates += $r['updates'];
        }

        if ($step == 6) {
          $r = $replaceEngine->perform($possibleDomainsBackup[4], $possibleDomainsCurrent[4]);
          $dchange += $r['change']; $dupdates += $r['updates'];
        }

        if ($step == 7) {
          if (!(substr($currentDomain, -strlen($backupDomain)) === $backupDomain)) {
            $r = $replaceEngine->perform($possibleDomainsBackup[5], $possibleDomainsCurrent[5]);
            $dchange += $r['change']; $dupdates += $r['updates'];
          }
        }

        $status['currentPage'] = $r['currentPage'];
        $status['totalPages'] = $r['totalPages'];

        $variants = [
          __('variant A', 'backup-backup'),
          __('variant B', 'backup-backup'),
          __('variant C', 'backup-backup'),
          __('variant D', 'backup-backup'),
          __('variant E', 'backup-backup'),
          __('variant F', 'backup-backup'),
        ];

        if ($status['totalPages'] > 0) {

          $info = __("Batch for domain (%variant%) adjustments (%page%/%allPages%) updated: %updates% fields.", 'backup-backup');
          $updates = $dupdates;
          if ($updates == 0) $updates = 1;
          $info = str_replace('%variant%', $variants[$step - 2], $info);
          $info = str_replace('%page%', $status['currentPage'], $info);
          $info = str_replace('%allPages%', $status['totalPages'], $info);
          $info = str_replace('%updates%', $updates, $info);
          $this->logger->log($info, 'INFO');
          $status['fieldAdjustments']++;

        } else {

          // $info = __('Domain (%variant%) adjustments are not required for this table.', 'backup-backup');
          // $info = str_replace('%variant%', $variants[$step - 2], $info);
          // $this->logger->log($info, 'INFO');

        }

        if ($status['currentPage'] >= $status['totalPages']) {
          $status['currentPage'] = 0;
          $status['totalPages'] = 0;
          $status['step'] = $step + 1;
        }

        return $status;

      } else {

        $status['currentPage'] = 0;
        $status['totalPages'] = 0;
        $status['step'] = 8;
        $step = 8;

      }
    }

    if ($step == 8) {
      if ($fieldAdjustments === 0) {
        $this->logger->log(__('Adjustments are not required for this table.', 'backup-backup'), 'INFO');
      }

      $status['step'] = 1;
      $status['fieldAdjustments'] = 0;
      $status['tableIndex'] = $tableIndex + 1;
      return $status;
    }

    return $status;

  }
  
  public function searchForIncludesInList($items, $search) {
    $search = strtolower($search);
    foreach ($items as $index => $item) {
      if (strpos($search, strtolower($item)) !== false) return true;
    }
    
    return false;
  }
  
  public function is_valid_plugin($plugin) {
    
    global $wp_version;
    
    $default_headers = array( 'wp'  => 'Requires at least', 'php' => 'Requires PHP' );
    
    $wp = false;
    $php = false;
    
    $detectedwp = '0.0.0';
    $detectedphp = '0.0.0';
    
    try {
    
      $plugin_file = BMP::fixSlashes(WP_PLUGIN_DIR . '/' . $plugin);
      $plugin_readme = BMP::fixSlashes(WP_PLUGIN_DIR . '/' . dirname($plugin) . '/readme.txt');
      if (!file_exists($plugin_readme)) $plugin_readme = BMP::fixSlashes(WP_PLUGIN_DIR . '/' . dirname($plugin) . '/README.txt');
      
      if (file_exists($plugin_file)) {
        $plugin_data = get_file_data($plugin_file, $default_headers, 'plugin');
        if (!empty($plugin_data['wp']) && version_compare($plugin_data['wp'], $wp_version, '<=')) {
          $detectedwp = $plugin_data['wp'];
          $wp = true;
        }
        if (!empty($plugin_data['php']) && version_compare($plugin_data['php'], PHP_VERSION, '<=')) {
          $detectedphp = $plugin_data['php'];
          $php = true;
        }
      }
      
      if (file_exists($plugin_readme)) {
        $readme_data = get_file_data($plugin_readme, $default_headers, 'plugin');
        if (!empty($readme_data['wp']) && version_compare($readme_data['wp'], $wp_version, '<=')) {
          $detectedwp = $readme_data['wp'];
          $wp = true;
        }
        
        if (!empty($readme_data['php']) && version_compare($readme_data['php'], PHP_VERSION, '<=')) {
          $detectedphp = $readme_data['php'];
          $php = true;
        }
      }
      
      $this->logger->log(sprintf('Detected version WP/PHP for plugin: %s, is (%s/%s)', $plugin, $detectedwp, $detectedphp), 'VERBOSE');
      
      if ($plugin == 'hello.php') return true;
      if ($php && $wp) return true;
      else return false;
          
    } catch (Error $e) {
      return false;
    }
    
    return false;
    
  }

  private function try_activate_plugins($plugins, $sucstr_source, $failstr_source, $failed_plugins = []) {

    $plugins_copy = array_values($plugins);
    $should_continue = false;
    $activated_plugins = [];
    $failed_plugins = $failed_plugins;
    $disallowed_plugins = [
      'bluehost-wordpress-plugin/bluehost-wordpress-plugin.php',
      'sg-cachepress/sg-cachepress.php',
      'wordpress-starter/siteground-wizard.php',
      'revslider/revslider.php',
      'easy-soundcloud-shortcode/easy-soundcloud-shortcode.php',
      'easy-soundcloud-shortcode/EasySoundcloudShortcode.php',
      'mainwp-child/mainwp-child.php',
      'wp-google-maps/wp-google-maps.php',
      'wp-google-maps/wpGoogleMaps.php'
    ];

    for ($i = 0; $i < sizeof($plugins_copy); ++$i) {

      $plugin_name = $plugins_copy[$i];
      $plugin_display = $plugin_name;

      $shouldActivate = true;

      if (empty($plugin_name)) {
        $shouldActivate = false;
        $plugin_display = '(---)';
      } else if ($plugin_name == 'hello.php') {
        $shouldActivate = true;
      } else if (strpos($plugin_name, '/') === false) {
        $shouldActivate = false;
      }

      $sucstr = str_replace('%plugin_name%', $plugin_display, $sucstr_source);
      $failstr = str_replace('%plugin_name%', $plugin_display, $failstr_source);

      if ($shouldActivate) {
        try {

          if ($this->is_valid_plugin($plugin_name) && !in_array($plugin_name, $disallowed_plugins)) {

            $resultWP = activate_plugin($plugin_name, '', true, true);

            $this->logger->log($sucstr, 'INFO');
            $activated_plugins[] = $plugin_name;

            $should_continue = true;
            break;

          } else {

            if (!in_array($plugin_name, $failed_plugins)) {
              $failed_plugins[] = $plugin_name;
            }

          }

        } catch (\Exception $e) {

          if (!in_array($plugin_name, $failed_plugins)) {

            $failed_plugins[] = $plugin_name;
            error_log(strval($e));

          }

        } catch (\Throwable $e) {

          if (!in_array($plugin_name, $failed_plugins)) {

            $msg = $e->getMessage();
            if (strpos($msg, 'add_rule()') != false || strpos($msg, 'rewrite.php:143') != false) {

              $activated_plugins[] = $plugin_name;
              error_log(strval($e));

            } else {

              $failed_plugins[] = $plugin_name;
              error_log(strval($e));

            }

          }

        }

      } else {

        $this->logger->log($failstr, 'WARN');

      }

    }

    return [ 'failed' => $failed_plugins, 'active' => $activated_plugins, 'should_continue' => $should_continue ];

  }

  public function enablePlugins() {

    global $wpdb;

    $this->logger->log(__('Enabling plugins included in the backup', 'backup-backup'), 'STEP');

    if (is_serialized($this->seek['active_plugins'])) {

      $plugins = unserialize($this->seek['active_plugins']);
      usort($plugins, function ($a, $b) { return strlen($a) - strlen($b); });
      $plugins = array_values($plugins);

      $sucstr_source = __('Plugin %plugin_name% enabled successfully.', 'backup-backup');
      $failstr_source = __('Failed to enable plugin %plugin_name%, trying to not end at fatal error...', 'backup-backup');

      $fullyActive = [];
      $failed_plugins = [];

      if (!function_exists('activate_plugin')) {
        require_once(ABSPATH .'/wp-admin/includes/plugin.php');
      }

      $one_more_time = true;
      $try_again = true;
      while ($try_again) {

        $res = $this->try_activate_plugins($plugins, $sucstr_source, $failstr_source, $failed_plugins);

        $fullyActive = array_unique(array_merge($fullyActive, $res['active']));
        $plugins = array_diff($plugins, $res['active']);
        $failed_plugins = array_unique(array_diff(array_merge($failed_plugins, $res['failed']), $fullyActive));

        $try_again = $res['should_continue'];
        if ($try_again == false && $one_more_time == true) {
          $one_more_time = false;
          $try_again = true;
        }

      }

      for ($i = 0; $i < sizeof($failed_plugins); ++$i) {

        $failstr = str_replace('%plugin_name%', $failed_plugins[$i], $failstr_source);
        $this->logger->log($failstr, 'WARN');

      }

      if (!in_array('backup-backup/backup-backup.php', $fullyActive)) {
        $fullyActive[] = 'backup-backup/backup-backup.php';
      }

      update_option('active_plugins', $fullyActive);

    }

    $this->logger->progress(100);
    $this->logger->log(__('All plugins enabled, you are ready to go :)', 'backup-backup'), 'SUCCESS');

  }

  public function searchReplace($step = 0, $tableIndex = 0, $currentPage = 0, $totalPages = 0, $fieldAdjustments = 0, $newPrefix = 'wp_') {

    $this->logger->progress(90);
    $searchReplaceEngine = Dashboard\bmi_get_config('OTHER::NEW_SEARCH_REPLACE_ENGINE');
    if ($searchReplaceEngine == false) {
      return $this->performReplace($step, $tableIndex, $currentPage, $totalPages, $fieldAdjustments, $newPrefix);
    }
    return $this->performReplaceV2($step, $tableIndex, $currentPage, $totalPages, $fieldAdjustments, $newPrefix);

  }

  public function alter_tables() {

    $this->logger->progress(98);
    $this->prepareFinalDatabase();
    $this->replaceTableNames($this->map['tables']);

  }

  private function prepareFinalDatabase() {

    global $wpdb;

    $tables = array_keys($this->map['tables']);
    $unique_prefix = explode('_', $tables[0])[0];
    $backupPrefix = $this->manifest->config->table_prefix;

    $options_table = $unique_prefix . '_' . $backupPrefix . 'options';
    if (!in_array($options_table, $tables)) {
      $tablename = false;
      for ($i = 0; $i < sizeof($tables); ++$i) {
        $table = $tables[$i];
        if (substr($table, -7) == 'options') {
          $tablename = $table;
          break;
        }
      }

      $options_table = $tablename;
    }

    if ($options_table != false && in_array($options_table, $tables)) {

      // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Identifier is safely escaped via escapeSQLIDentifier()
      $wpdb->query($wpdb->prepare("DELETE FROM " . BMP::escapeSQLIDentifier($options_table) . " WHERE option_name LIKE %s", '%\_transient\_%'));

      // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Identifier is safely escaped via escapeSQLIDentifier()
      $active_plugins = $wpdb->get_results($wpdb->prepare("SELECT option_value FROM " . BMP::escapeSQLIDentifier($options_table) . " WHERE option_name = %s", 'active_plugins'));
      $active_plugins = $active_plugins[0]->option_value;

      $this->seek['active_plugins'] = $active_plugins;

      update_option('active_plugins', ['backup-backup/backup-backup.php']);
      
      $homeURL = site_url();
      if (strlen($homeURL) <= 8) $homeURL = home_url();
      if (defined('WP_SITEURL') && strlen(WP_SITEURL) > 8) $homeURL = WP_SITEURL;
      
      $ssl = is_ssl() == true ? 'https://' : 'http://';
      $currentDomain = $ssl . $this->parseDomain($homeURL, false);

      // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Identifier is safely escaped via escapeSQLIDentifier()
      $wpdb->query($wpdb->prepare("UPDATE " . BMP::escapeSQLIDentifier($options_table) . " SET option_value = %s WHERE option_name = %s", $currentDomain, 'siteurl'));

      // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Identifier is safely escaped via escapeSQLIDentifier()
      $wpdb->query($wpdb->prepare("UPDATE " . BMP::escapeSQLIDentifier($options_table) . " SET option_value = %s WHERE option_name = %s", $currentDomain, 'home'));

    }

  }

  private function filterFile($path, $name) {

    $blacklist = $this->unwantedTables;
    $tablename = strtolower(substr($name, 0, -4));

    $shouldBeExcluded = false;
    for ($i = 0; $i < sizeof($blacklist); ++$i) {

      $rule = strtolower($blacklist[$i]);
      if (substr($tablename, -(strlen($rule))) == $rule) {
        $shouldBeExcluded = true;
        break;
      }

    }

    if ($shouldBeExcluded == true) {

      $file = new \SplFileObject($path);
      $file->seek($file->getSize());
      $total_lines = $file->key() + 1;

      if ($total_lines >= 6) {

        $str = "\n\n\n\n\n";

        $file->seek(5);
        $str .= trim($file->current());

        $this->logger->log(str_replace('%s', substr($name, 0, -4), __('Cleaning up contents of %s table.', 'backup-backup')), 'INFO');
        file_put_contents($path, $str);

        $file = null;

      }

    }

  }

  private function getNextFile() {

    if ($this->seek['last_file'] == '...') {

      $nextFile = false;

      $sqlFiles = array_diff(scandir($this->storage), ['..', '.']);
      $sqlFiles = array_values($sqlFiles);

      if (sizeof($sqlFiles) > 0) {
        $nextFilePath = $this->storage . DIRECTORY_SEPARATOR . $sqlFiles[0];
        return $nextFilePath;
      }

      $this->seek['last_file'] = $nextFile;
      return $nextFile;

    } else {

      return $this->seek['last_file'];

    }

  }

  private function initMessage() {

    $this->logger->log(__('Successfully detected backup created with v3 engine, importing...', 'backup-backup'), 'INFO');
    $this->logger->log(__('Restoring database (using v4 engine)...', 'backup-backup'), 'STEP');

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

  }

  private function performReplaceV2($step = 0, $tableIndex = 0, $currentPage = 0, $totalPages = 0, $fieldAdjustments = 0, $newPrefix = "wp_")
  {

    require_once BMI_INCLUDES . DIRECTORY_SEPARATOR . 'database' . DIRECTORY_SEPARATOR . 'search-replace-processor.php';
    require_once BMI_INCLUDES . DIRECTORY_SEPARATOR . 'database' . DIRECTORY_SEPARATOR . 'search-replace-v2.php';

    // Initialize Processor
    $processor = new \BMI\Plugin\Database\BMISearchReplaceProcessor(
      $this->map['tables'],
      $this->logger,
      $this->manifest,
      $this->excludeSearchReplaceTables
    );

    // Process Current Step
    return $processor->process($step, $tableIndex, $newPrefix);

  }


}
