<?php

  // Namespace
  namespace BMI\Plugin\Staging;

  // Use
  use BMI\Plugin\BMI_Logger as Logger;
  use BMI\Plugin\Backup_Migration_Plugin as BMP;
  use BMI\Plugin\Progress\BMI_StagingProgress as StagingProgress;

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

  // Require logger
  require_once BMI_INCLUDES . '/progress/staging.php';

  /**
   * Main File of Staging Controller
   */
  class BMI_Staging {

    public $continue = false;
    public $continuationData = [];
    public $tastewpURL = 'https://tastewp.com';
    protected $name = false;
    protected $logger = false;
    protected $config = [];
    protected $siteConfig = [];
    protected $siteConfigPath = false;
    protected $wasError = false;
    protected $wasErrorMsg = 'No error message...';
    protected $isRemoved = false;
    protected $configPath = BMI_STAGING . DIRECTORY_SEPARATOR . 'configuration.php';

    public function __construct($name, $initialize = false) {

      if ($name == '..ajax..') return;

      // Set name of staging site
      $this->name = strval($name);

      // Initialize staging site logger
      $this->logger = new StagingProgress(!$initialize);
      $this->logger->start();

      // Load configuration
      $this->loadConfig();

      // If it's initial request add intro logs and set progress to 0
      if ($initialize) {
        $this->log(__('Preparing creation of staging site...', 'backup-backup'), 'STEP');
        $this->log(__('Starting custom error handler...', 'backup-backup'));
        $this->progress('0');
      }

      $this->errorHandler();
      $this->exceptionHandler();

    }
    
    protected function httpRequest($url, &$data, $method = 'POST', $headers = []) {
      
      // URL & Body data
      $baseurl = home_url();
      if (substr($baseurl, 0, 4) != 'http') {
        if (is_ssl()) $baseurl = 'https://' . home_url();
        else $baseurl = 'http://' . home_url();
      }
      
      // Add base URL to data
      if ($method != 'PUT') {
        $data['baseurl'] = $baseurl;
      } else {
        $headers['x-baseurl'] = $baseurl;
      }
      
      // Get disabled functions to check if CURL can be used
      $disabled_functions = explode(',', ini_get('disable_functions'));
      $vA = !in_array('curl_exec', $disabled_functions);
      $vB = !in_array('curl_init', $disabled_functions);
      
      if (function_exists('curl_version') && function_exists('curl_exec') && function_exists('curl_init') && $vA && $vB) {
        
        // Make request
        $response = wp_remote_post($url, [
          'method' => $method,
          'httpversion' => '1.0',
          'blocking' => true,
          'timeout' => 25,
          'sslverify' => false,
          'headers' => $headers,
          'body' => $data
        ]);

      } else {

        return ['status' => 'error', 'message' => 'Library missing. CURL is required for this feature to work, please add cURL extension to your PHP.'];

      }

      // Validate response
      if (!(is_array($response) && isset($response['body']) && is_string($response['body']))) {

        // Faile if wrong response
        return ['status' => 'error', 'message' => 'Server response empty, or timed out, please try again.'];

      } else {

        // Return data
        $response['body'] = substr($response['body'], strpos($response['body'], '{'));
        $json = json_decode($response['body'], true);

        return $json;

      }
     
    }

    public function getStagingSites($localOnly = false) {

      if (!file_exists($this->configPath)) return [];
      $config = file_get_contents($this->configPath);
      $config = trim(substr($config, 8));

      if (is_serialized($config)) $config = maybe_unserialize($config);
      else return [];

      $tasteWPSites = [];
      $parsedSites = [];
      foreach ($config as $name => $data) {
        $this->name = strval($name);
        $this->loadConfig();

        if (!isset($this->siteConfig['name'])) continue;
        if (isset($this->siteConfig['expiration_time']) && $this->siteConfig['expiration_time'] != 'never' && !is_nan(intval($this->siteConfig['expiration_time']))) {
          if ($this->siteConfig['expiration_time'] != 'Never' && intval($this->siteConfig['expiration_time']) < time()) {
            $this->deleteRelatedConfigs();
            continue;
          }
        }
        
        // Exclude TasteWP Sites if only local should be returned
        if ($localOnly === true) {
          if ($this->siteConfig['communication_secret'] != 'local' && isset($this->siteConfig['entry'])) {
            continue;
          }
        }

        $parsedSites[$this->name] = [];
        $parsedSites[$this->name]['name'] = $this->siteConfig['name'];
        $parsedSites[$this->name]['url'] = $this->siteConfig['url'];
        $parsedSites[$this->name]['db_prefix'] = $this->siteConfig['db_prefix'];
        $parsedSites[$this->name]['total_files'] = $this->siteConfig['total_files'];
        $parsedSites[$this->name]['communication_secret'] = $this->siteConfig['communication_secret'];
        $parsedSites[$this->name]['expiration_time'] = __('Never', 'backup-backup');
        if (isset($this->siteConfig['expiration_time'])) {
          $parsedSites[$this->name]['expiration_time'] = $this->siteConfig['expiration_time'];
        }
        $parsedSites[$this->name]['creation_date'] = date('Y-m-d H:i:s', $this->siteConfig['creation_date']);
        $parsedSites[$this->name]['creation_date_plain'] = intval($this->siteConfig['creation_date']);

        $parsedSites[$this->name]['display_name'] = $this->siteConfig['name'];
        if (isset($this->siteConfig['display_name'])) {
          $parsedSites[$this->name]['display_name'] = $this->siteConfig['display_name'];
        }

        if (isset($this->siteConfig['sumOfTotalSize'])) {
          $parsedSites[$this->name]['total_size'] = BMP::humanSize($this->siteConfig['sumOfTotalSize']);
        } else {
          $parsedSites[$this->name]['total_size'] = '---';
        }
        
        if (isset($this->siteConfig['is_premium'])) {
          $parsedSites[$this->name]['is_premium'] = $this->siteConfig['is_premium'];
        }
        
        if (isset($this->siteConfig['is_affiliated'])) {
          $parsedSites[$this->name]['is_affiliated'] = $this->siteConfig['is_affiliated'];
        }
        
        if ($this->siteConfig['communication_secret'] != 'local' && isset($this->siteConfig['entry'])) {
          $tasteWPSites[] = [$this->siteConfig['entry'], $this->siteConfig['communication_secret'], $this->name];
        }
      }
      
      if (sizeof($tasteWPSites) > 0) {
        
        $url = $this->tastewpURL . '/stg/details';
        $data = [ 'entries' => $tasteWPSites ];
        $details = $this->httpRequest($url, $data);
        
        foreach ($details as $entry => $data) {
          
          if (isset($data['index']) && isset($parsedSites[$data['index']])) {
            $indexName = $data['index'];
            $this->name = strval($data['index']);
            $this->loadConfig();
            $changes = false;
            
            if (isset($data['is_deleted']) && $data['is_deleted'] == true) {
              $this->delete($this->name, true);
              unset($parsedSites[$this->name]);
              continue;
            }
            
            if (isset($data['expiration']) && isset($this->siteConfig['expiration_time'])) {
              if ($this->siteConfig['expiration_time'] != $data['expiration']) {
                $this->siteConfig['expiration_time'] = $data['expiration'];
                $parsedSites[$this->name]['expiration_time'] = $data['expiration'];
                $changes = true;
              }
            }
            
            if ((isset($data['is_premium']) && $data['is_premium'] == '1') || (isset($data['is_affiliated']) && $data['is_affiliated'] == '1')) {
              $never = __('Never', 'backup-backup');
              if ($this->siteConfig['expiration_time'] != $never) {
                $this->siteConfig['expiration_time'] = $never;
                $parsedSites[$this->name]['expiration_time'] = $never;
                $changes = true;
                
                if (isset($data['is_premium']) && $data['is_premium'] == '1') {
                  if (!isset($this->siteConfig['is_premium'])) {
                    $this->siteConfig['is_premium'] = '1';
                    $parsedSites[$this->name]['is_premium'] = '1';
                    $changes = true;
                  }
                }
                
                if (isset($data['is_affiliated']) && $data['is_affiliated'] == '1') {
                  if (!isset($this->siteConfig['is_affiliated'])) {
                    $this->siteConfig['is_affiliated'] = '1';
                    $parsedSites[$this->name]['is_affiliated'] = '1';
                    $changes = true;
                  }
                }
              }
            } else if (isset($data['expiration']) && isset($this->siteConfig['expiration_time'])) {
              if ($this->siteConfig['expiration_time'] != $data['expiration']) {
                $this->siteConfig['expiration_time'] = $data['expiration'];
                $parsedSites[$this->name]['expiration_time'] = $data['expiration'];
                $changes = true;
              }
            }
            
            if (isset($this->siteConfig['url']) && isset($data['domain'])) {
              if ($this->siteConfig['url'] != $data['domain']) {
                $this->siteConfig['url'] = $data['domain'];
                $parsedSites[$this->name]['url'] = $data['domain'];
                $changes = true;
              }
            }
            
            if ($changes) $this->saveConfigSite();
          }
          
        }
        
      }
      
      usort($parsedSites, function ($a, $b) {
        $timeA = intval($a['creation_date_plain']);
        $timeB = intval($b['creation_date_plain']);
        
        return ($timeA > $timeB) ? 1 : -1;
      });

      return $parsedSites;

    }

    public function rename($name, $displayName) {

      $empty = __('You have to provide some staging site name before process.', 'backup-backup');
      $toolong = __('Staging site name cannot be longer than 24 characters.', 'backup-backup');
      $invalid = __('Provided name contains prohibited characters.', 'backup-backup');

      if (strlen($displayName) <= 0) {
        return ['status' => 'fail', 'message' => $empty];
      }

      if (!preg_match('/^[a-zA-Z0-9-_]+$/', $displayName)) {
        return ['status' => 'fail', 'message' => $invalid];
      }

      if (strlen($displayName) >= 24) {
        return ['status' => 'fail', 'message' => $toolong];
      }

      $this->name = strval($name);
      $this->loadConfig();
      $this->siteConfig['display_name'] = $displayName;
      $this->saveConfigSite();

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

    }

    public function prepareLogin($name) {

      $this->name = strval($name);
      $this->loadConfig();
      $this->copyOverPasswordLessScript();

      $user_id = $this->siteConfig['login_user_id'];
      $pass = $this->siteConfig['password'];
      $base = $this->siteConfig['url'];

      $url = $base . '/wp-login.php?autologin=true&user=' . $user_id . '&secret=' . $pass;
      return ['status' => 'success', 'url' => $url ];

    }

    public function deleteAllStagingFiles() {

      // Won't exist for TasteWP
      if (!isset($this->siteConfig['root_staging'])) return;
      if (!isset($this->siteConfig['communication_secret'])) return;
      if ($this->siteConfig['communication_secret'] != 'local') return;
      
      $rootDirectory = untrailingslashit($this->siteConfig['root_staging']);
      if (!(file_exists($rootDirectory) && is_dir($rootDirectory))) return;
      
      // Do not even try to touch files under ABSPATH by any chance.... It would be disaster.
      if ($rootDirectory == untrailingslashit(ABSPATH)) return;
      if ($rootDirectory == ABSPATH) return;
      if ($this->siteConfig['root_staging'] == ABSPATH) return;
      if (trailingslashit($this->siteConfig['root_staging']) == trailingslashit(ABSPATH)) return;
      if (BMP::fixSlashes($this->siteConfig['root_staging']) == BMP::fixSlashes(ABSPATH)) return;

      $this->rrmdir($rootDirectory);

    }

    public function deleteRelatedDatabase() {

      global $wpdb, $table_prefix;
      
      if (!isset($this->siteConfig['db_prefix'])) return;
      if (!isset($this->siteConfig['communication_secret'])) return;
      if (isset($this->siteConfig['entry'])) return;
      
      // For TasteWP websites it won't exist
      if ($this->siteConfig['communication_secret'] != 'local') return;

      $prefix = $this->siteConfig['db_prefix'];
      
      // Hell don't even try to remove these.
      if ($table_prefix == $prefix) return;
      if ($table_prefix == '') return;
      if ($prefix == '') return;
      if (trim($table_prefix) == '') return;
      if (trim($prefix) == '') return;
      if ($prefix == null) return;
      if (strlen($prefix) == 0) return;
      if (strlen(trim($prefix)) == 0) return;
      if (strtolower($table_prefix) == strtolower($prefix)) return;

      $allTables = $wpdb->get_results('SHOW TABLES');
      foreach ($allTables as $table) {
        foreach ($table as $name) {
          
          // Twice because why not.
          if (substr($name, 0, strlen($table_prefix)) == $table_prefix) continue;
          if (substr($name, 0, strlen($table_prefix)) == $table_prefix) continue;
          if (strtolower(substr($name, 0, strlen($table_prefix))) == strtolower($table_prefix)) continue;
          
          if (substr($name, 0, strlen($prefix)) == $prefix) {
            // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Identifier is safely escaped via escapeSQLIDentifier()
            $wpdb->query("DROP TABLE " . BMP::escapeSQLIDentifier($name) . ";");
          }
        }
      }

    }
    
    public function deleteFromTasteWP() {
      
      if (isset($this->siteConfig['communication_secret']) && $this->siteConfig['communication_secret'] == 'local') return;
      $url = $this->tastewpURL . '/stg/delete/' . $this->siteConfig['communication_secret'];
      if (isset($this->siteConfig['entry'])) $data = [ 'entry' => $this->siteConfig['entry'] ];
      else $data = [];
      
      $this->httpRequest($url, $data);
      
    }

    public function deleteRelatedConfigs() {
      
      unset($this->config[$this->name]);
      if (file_exists($this->siteConfigPath)) {
        @unlink($this->siteConfigPath);
      }

      $this->saveConfig();
      $this->isRemoved = true;

    }

    public function delete($name, $ignoreTWP = false) {

      $this->name = strval($name);
      $this->loadConfig();

      $this->deleteAllStagingFiles();
      $this->deleteRelatedDatabase();
      $this->deleteRelatedConfigs();
      
      // In case of some false positive, keep the site on TasteWP.
      if ($ignoreTWP != true) $this->deleteFromTasteWP();

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

    }

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

    protected function copyOverPasswordLessScript($quiet = false) {

      if (!$quiet) $this->log(__('Inserting auto-login script for staging site', 'backup-backup'), 'STEP');

      $this->siteConfig['login_user_id'] = get_current_user_id();
      $this->siteConfig['password'] = $this->getRandomPassword();
      $this->saveConfigSite();

      $pathToScript = BMI_INCLUDES . DIRECTORY_SEPARATOR . 'htaccess' . DIRECTORY_SEPARATOR . '.autologin.php';

      $mudirPath = WP_CONTENT_DIR . DIRECTORY_SEPARATOR . 'mu-plugins';
      if (defined('WPMU_PLUGIN_DIR')) $mudirPath = WPMU_PLUGIN_DIR;

      $sourceABSPATH = untrailingslashit($this->siteConfig['root_source']);
      $destinationABSPATH = untrailingslashit($this->siteConfig['root_staging']);
      $pathToDestination = str_replace($sourceABSPATH, $destinationABSPATH, $mudirPath);
      $pathToDestination = untrailingslashit($pathToDestination) . DIRECTORY_SEPARATOR . 'bmi-autologin.php';

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

      $script = file_get_contents($pathToScript);
      $script = str_replace('%%user_ip%%', $this->getIpAddress(), $script);
      $script = str_replace('%%user_id%%', $this->siteConfig['login_user_id'], $script);
      $script = str_replace('%%secret_password%%', $this->siteConfig['password'], $script);
      file_put_contents($pathToDestination, $script);

      if (!$quiet) $this->log(__('Inserting auto-login script for staging site', 'backup-backup'), 'SUCCESS');

    }

    protected function getRandomPassword() {

      $alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
      $pass = [];
      $alphaLength = strlen($alphabet) - 1;

      for ($i = 0; $i < 24; $i++) {
        $n = rand(0, $alphaLength);
        $pass[] = $alphabet[$n];
      }

      return implode($pass);

    }

    private function createDefaultConfig() {

      $defaultConfig = [];
      file_put_contents($this->configPath, '<?php //' . serialize($defaultConfig));

      return $defaultConfig;

    }

    private function createDefaultConfigForSite($rand) {

      $defaultConfig = [];
      $configPath = BMI_STAGING . DIRECTORY_SEPARATOR . $rand . '.php';
      file_put_contents($configPath, '<?php //' . serialize($defaultConfig));

      return $defaultConfig;

    }

    protected function loadConfig() {

      $name = $this->name;
      if (!$name || !is_string($name)) return;
      if (!file_exists($this->configPath)) $this->createDefaultConfig();

      $config = file_get_contents($this->configPath);
      $config = trim(substr($config, 8));

      if (is_serialized($config)) $config = maybe_unserialize($config);
      else $config = $this->createDefaultConfig();

      if (isset($config[$name]) && isset($config[$name]['config'])) {

        $this->siteConfigPath = BMI_STAGING . DIRECTORY_SEPARATOR . sanitize_text_field($config[$name]['config']) . '.php';

      } else {

        $config[$name] = [];
        $config[$name]['config'] = uniqid();
        $this->siteConfigPath = BMI_STAGING . DIRECTORY_SEPARATOR . $config[$name]['config'] . '.php';
        $this->createDefaultConfigForSite($config[$name]['config']);

      }
      
      if (file_exists($this->siteConfigPath)) {
        
        $siteConfig = file_get_contents($this->siteConfigPath);
        $siteConfig = trim(substr($siteConfig, 8));

        if (is_serialized($siteConfig)) $siteConfig = maybe_unserialize($siteConfig);
        else $siteConfig = $this->createDefaultConfigForSite($config[$name]['config']);

        $this->siteConfig = $siteConfig;
        
      } else {
         
        unset($this->config[$this->name]);
        $this->siteConfig = [];
        
      }
      
      $this->config = $config;

    }

    protected function saveConfig() {

      if (!$this->configPath || !isset($this->config)) return;
      file_put_contents($this->configPath, '<?php //' . serialize($this->config));

    }

    protected function saveConfigSite() {

      if (!$this->siteConfigPath || !$this->siteConfig) return;
      file_put_contents($this->siteConfigPath, '<?php //' . serialize($this->siteConfig));

    }

    protected function log($msg, $level = 'INFO') {

      // Append logs to live-log file
      if ($this->logger) $this->logger->log($msg, $level);

    }

    protected function printInitialLogs() {

      global $wp_version;

      $this->log(__("Backup & Migration version: ", 'backup-backup') . BMI_VERSION);
      $this->log(__("PHP Version: ", 'backup-backup') . PHP_VERSION);
      $this->log(__("WP Version: ", 'backup-backup') . $wp_version);
      $this->log(__("MySQL Version: ", 'backup-backup') . $GLOBALS['wpdb']->db_version());
      $this->log(__("MySQL Max Length: ", 'backup-backup') . $GLOBALS['wpdb']->get_results("SHOW VARIABLES LIKE 'max_allowed_packet';")[0]->Value);

      if (isset($_SERVER['SERVER_SOFTWARE']) && !empty($_SERVER['SERVER_SOFTWARE'])) {
        $this->log(__("Web server: ", 'backup-backup') . $_SERVER['SERVER_SOFTWARE']);
      } else {
        $this->log(__("Web server: Not available", 'backup-backup'));
      }

      $this->log(__("Max execution time (in seconds): ", 'backup-backup') . @ini_get('max_execution_time'));

      $this->log(__("Memory limit (server): ", 'backup-backup') . @ini_get('memory_limit'));

      if (defined('WP_MEMORY_LIMIT')) {
        $this->log(__("Memory limit (wp-config): ", 'backup-backup') . WP_MEMORY_LIMIT);
      }

      if (defined('WP_MAX_MEMORY_LIMIT')) {
        $this->log(__("Memory limit (wp-config admin): ", 'backup-backup') . WP_MAX_MEMORY_LIMIT);
      }

      if (defined('BMI_DB_MAX_ROWS_PER_QUERY')) {
        $this->log(__('Max rows per query (this site): ', 'backup-backup') . BMI_DB_MAX_ROWS_PER_QUERY);
      }

      if (defined('BMI_BACKUP_PRO')) {
        if (BMI_BACKUP_PRO == 1) {
          $this->log(__("Premium plugin is enabled and activated", 'backup-backup'));
        } else {
          $this->log(__("Premium version is enabled but not active, using free plugin.", 'backup-backup'), 'warn');
        }
      }

    }

    protected function progress($progress) {

      // Sets new percentage of progress
      if ($this->logger) $this->logger->progress($progress);

    }

    protected function returnError($reason, $english) {

      $this->log(__('Aborting staging site creation process...', 'backup-backup'), 'STEP');
      $this->log(__('There was an error during creation of staging site:', 'backup-backup'), 'ERROR');
      $this->log($reason, 'ERROR');
      $this->log($english, 'VERBOSE');
      $this->log('#300', 'END-CODE');

      $this->wasError = true;
      $this->wasErrorMsg = $english;
      $this->continue = true;
      $this->continuationData = [ 'status' => 'fail', 'message' => $reason ];

      $this->abort();

      BMP::res($this->continuationData);
      exit;

    }

    protected function sendSuccess($isTasteWP = false) {

      $this->log(__('Performing final cleanup', 'backup-backup'), 'STEP');
      $this->cleanup();
      $this->log(__('Cleanup performed, entire process finished successfully', 'backup-backup'), 'SUCCESS');

      if ($isTasteWP) {
        
        $siteURL = $this->siteConfig['url'];
        
        $this->continue = true;
        $this->continuationData = [ 'status' => 'success', 'url' => $siteURL ];
        
      } else {
       
        $siteURL = $this->siteConfig['url'];
        $userid = $this->siteConfig['login_user_id'];
        $password = $this->siteConfig['password'];

        $this->continue = true;
        $this->continuationData = [ 'status' => 'success', 'url' => $siteURL, 'password' => $password, 'userid' => $userid ];
        
      }

      $this->saveConfig();
      $this->saveConfigSite();

      BMP::res($this->continuationData);
      exit;

    }

    protected function errorHandler() {
      set_error_handler(function ($errno, $errstr, $errfile, $errline) {
        
        if (BMI_DEBUG) {
          error_log('BMI DEBUG ENABLED, HERE IS THE COMPLETE REPORT (ERROR HANDLER #5):');
          error_log(print_r($errno, true));
          error_log(print_r($errstr, true));
          error_log(print_r($errfile, true));
          error_log(print_r($errline, true));
        }

        if (strpos($errstr, 'deprecated') !== false) return;
        if (strpos($errstr, 'php_uname') !== false) return;

        if ($errno != E_ERROR && $errno != E_CORE_ERROR && $errno != E_COMPILE_ERROR && $errno != E_USER_ERROR && $errno != E_RECOVERABLE_ERROR) {
          if (strpos($errfile, 'backup-backup') === false && strpos($errfile, 'backup-migration') === false) return;
          Logger::error(__('There was an error before request shutdown (but it was not logged to staging log)', 'backup-backup'));
          Logger::error(__('Error message: ', 'backup-backup') . $errstr);
          Logger::error(__('Error file/line: ', 'backup-backup') . $errfile . '|' . $errline);
          Logger::error(__('Error handler: ', 'backup-backup') . 'ajax#04' . '|' . $errno);
          return;
        }

        if (strpos($errfile, 'backup-backup') === false) {
          Logger::error(__("Restore process was not aborted because this error is not related to Backup Migration.", 'backup-backup'));
          $this->log(__("There was an error not related to Backup Migration Plugin.", 'backup-backup'), 'warn');
          $this->log(__("Message: ", 'backup-backup') . $errstr, 'warn');
          $this->log(__("Staging process will not be aborted because of this.", 'backup-backup'), 'warn');
          return;
        }

        $this->log(__("There was an error during staging process:", 'backup-backup'), 'error');
        $this->log(__("Message: ", 'backup-backup') . $errstr, 'error');
        $this->log(__("File/line: ", 'backup-backup') . $errfile . '|' . $errline, 'error');
        $this->log(__('Unfortunately we had to remove the site (if partly created).', 'backup-backup'), 'error');

        $this->abort();

        $this->log(__("Aborting staging site creation process...", 'backup-backup'), 'step');
        $this->log('#003', 'end-code');
        $this->end();

        exit;

      }, E_ALL);
    }

    protected function exceptionHandler() {
      set_exception_handler(function ($exception) {
        if (BMI_DEBUG) {
          error_log('BMI DEBUG ENABLED, HERE IS THE COMPLETE REPORT (EXCEPTION HANDLER #3):');
          error_log(print_r($exception, true));
        }
        
        $this->log(__("Exception: ", 'backup-backup') . $exception->getMessage(), 'warn');
        Logger::log(__("Exception: ", 'backup-backup') . $exception->getMessage());
      });
    }

    protected function getIpAddress() {

      $ip = '127.0.0.1';
      if (isset($_SERVER['HTTP_CLIENT_IP'])) {
        $ip = $_SERVER['HTTP_CLIENT_IP'];
      } else {
        if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
          $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
        }
        if ($ip === false) {
          if (isset($_SERVER['REMOTE_ADDR'])) $ip = $_SERVER['REMOTE_ADDR'];
        }
      }

      return $ip;

    }

    protected function getAllStagingSiteDirectories() {

      $sites = array_keys($this->config);
      return $sites;

    }

    protected function getAllStagingSitePrefixes() {

      $prefixes = [];
      $sites = $this->getAllStagingSiteDirectories();

      foreach ($sites as $siteName) {
        $site = $this->config[$siteName];
        if (isset($site['prefix'])) {
          $prefix = $site['prefix'];
          $prefixes[] = $prefix;
        }
      }

      return $prefixes;

    }

    protected function setContinuation($step, $batch = 1, $siteData = []) {

      $this->siteConfig['step'] = $step;
      $this->siteConfig['batch'] = $batch;

      $siteData['name'] = $this->name;

      $this->continue = true;
      $this->continuationData = [ 'status' => 'continue', 'data' => $siteData ];

      $this->log('Setting continuation for: ' . $step . ' ' . $batch, 'VERBOSE');

      $this->saveConfig();
      $this->saveConfigSite();

      BMP::res($this->continuationData);
      exit;

    }

    protected function cleanup() {

      $this->log('Cleaning up not needed files and parsing configs.', 'VERBOSE');
      $pathDirsListFile = BMI_TMP . DIRECTORY_SEPARATOR . '.staging_directories';
      $pathFilesListFile = BMI_TMP . DIRECTORY_SEPARATOR . '.staging_files';
      $staging_lock = BMI_STAGING . '/.staging_lock';

      if (file_exists($pathDirsListFile)) @unlink($pathDirsListFile);
      if (file_exists($pathFilesListFile)) @unlink($pathFilesListFile);
      if (file_exists($staging_lock)) @unlink($staging_lock);

    }

    protected function abort($delete = false) {

      if ($delete) $this->log(__('Aborting the process due to user will.', 'backup-backup'), 'STEP');

      $this->log('Aborting the staging process and removing all related configs, files.', 'VERBOSE');
      $this->cleanup();
      $this->delete($this->name);
      
      if ($delete) $this->log(__('Process successfully aborted and cleaned up.', 'backup-backup'), 'SUCCESS');

      if ($delete) {
        $this->continuationData = BMP::res([ 'status' => 'deleted', 'name' => $this->name ]);
        return $this->continuationData;
      }
      
    }

    public function __destruct() {

      // End logger
      $this->log('Saving staging sites configuration (end of request/batch)...', 'VERBOSE');
      if ($this->isRemoved == false) {
        $this->saveConfig();
        $this->saveConfigSite();
      }
      $this->log('Configuration saved, destructing the logger (controller)...', 'VERBOSE');
      if ($this->logger) $this->logger->end();

    }

  }
