<?php

  // Namespace
  namespace BMI\Plugin\Zipper;

  // Use
  use BMI\Plugin\Backup_Migration_Plugin as BMP;
  use BMI\Plugin\BMI_Logger as Logger;
  use BMI\Plugin\Progress\BMI_ZipProgress as Progress;
  use BMI\Plugin\BMI_Zip_Explorer as ZipExplorer;

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

  /**
   * BMI_Zipper
   */
  class BMI_Zipper {
    public function makeZIP($files, $output, $name, &$zip_progress, $cron = false) {

      // Verbose
      Logger::log(__("Creating backup ", 'backup-backup'));
      Logger::log(__("Found ", 'backup-backup') . sizeof($files) . __(" files to backup.", 'backup-backup'));

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

      // Start microtime for ZIP Process
      $start = microtime(true);

      // Logs
      $zip_progress->log(__("Preparing map of files...", 'backup-backup'), 'step');

      // Try to catch error
      try {

        // Create new ZIP
        $zip = new Zip();
        $zip->zip_start($output, $files, $name, $zip_progress, $start);

        // Logs
        $zip_progress->log(__("Files prepared.", 'backup-backup'), 'success');
        $zip_progress->log(__("Starting compression process...", 'backup-backup'), 'info');

        // Close ZIP and Save
        $lala = $zip->zip_end(2, $cron);
        if (!$lala) {
          $zip_progress->log(__("Something went wrong (pclzip) – removing backup files...", 'backup-backup'), 'error');

          return false;
        }

        return $lala;
      } catch (\Throwable $e) {

        // Error print
        $zip_progress->log(__("Reverting backup, removing file...", 'backup-backup'), 'step');
        $zip_progress->log(__("There was an error during backup...", 'backup-backup'), 'error');
        $zip_progress->log($e->getMessage(), 'error');

        return false;
      } catch (\Exception $e) {

        // Error print
        $zip_progress->log(__("Reverting backup, removing file...", 'backup-backup'), 'step');
        $zip_progress->log(__("There was an error during backup...", 'backup-backup'), 'error');
        $zip_progress->log($e->getMessage(), 'error');

        return false;
      }

      return true;
    }

    public function getZipFileContent($zipname, $filename) {
      if (class_exists('ZipArchive') || class_exists('\ZipArchive')) {
        $zip = new \ZipArchive();

        if ($zip->open($zipname) === true) {
          if ($content = $zip->getFromName($filename)) {
            return json_decode($content);
          } else {
            return false;
          }
        } else {
          return false;
        }
      } else {
        if (!class_exists('PclZip')) {
          if (!defined('PCLZIP_TEMPORARY_DIR')) {
            $bmi_tmp_dir = BMI_TMP;
            if (!file_exists($bmi_tmp_dir)) {
              @mkdir($bmi_tmp_dir, 0775, true);
            }
            define('PCLZIP_TEMPORARY_DIR', $bmi_tmp_dir . DIRECTORY_SEPARATOR . 'bmi-');
          }
          if (defined('BMI_PRO_PCLZIP') && file_exists(BMI_PRO_PCLZIP)) {
            require_once BMI_PRO_PCLZIP;
          } else {
            require_once trailingslashit(ABSPATH) . 'wp-admin/includes/class-pclzip.php';
          }
        }
        $lib = new \PclZip($zipname);
        $content = $lib->extract(PCLZIP_OPT_BY_NAME, $filename, PCLZIP_OPT_EXTRACT_AS_STRING);
        if (isset($content[0]) && isset($content[0]['content'])) {
          return json_decode($content[0]['content']);
        } else {
          return false;
        }
      }
    }

    public function getZipContentList($zippath, $savepath) {

      if (class_exists('ZipArchive') || class_exists('\ZipArchive')) {

        $zip = new \ZipArchive();
        $zip->open($zippath);

        if (!isset($zip->numFiles) || $zip->numFiles == 0 || $zip->numFiles === false) {
          $zip->close();
          return false;
        }

        $tmpf = fopen($savepath, 'a+');
        $totalAmount = $zip->numFiles;

        for ($i = 0; $i < $zip->numFiles; ++$i) {

          $stat = $zip->statIndex($i);
          fwrite($tmpf, $stat['name'] . "\n");
          unset($stat);

        }

        fclose($tmpf);
        $zip->close();

        return $totalAmount;

      } else {

        if (!class_exists('PclZip')) {
          if (!defined('PCLZIP_TEMPORARY_DIR')) {
            $bmi_tmp_dir = BMI_TMP;
            if (!file_exists($bmi_tmp_dir)) {
              @mkdir($bmi_tmp_dir, 0775, true);
            }
            define('PCLZIP_TEMPORARY_DIR', $bmi_tmp_dir . DIRECTORY_SEPARATOR . 'bmi-');
          }
          if (defined('BMI_PRO_PCLZIP') && file_exists(BMI_PRO_PCLZIP)) {
            require_once BMI_PRO_PCLZIP;
          } else {
            require_once trailingslashit(ABSPATH) . 'wp-admin/includes/class-pclzip.php';
          }
        }

        $zip = new \PclZip($zippath);
        $list = $zip->listContent();
        if ($list == 0) {
          return false;
        }

        $tmpf = fopen($savepath, 'a+');

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

          fwrite($tmpf, $list[$i]['filename'] . "\n");

        }

        fclose($tmpf);

        return $totalAmount;

      }

    }

    public function getPartsToRestore($zippath, $savepath) {
      if (!defined('BMI_PRO_INC') || !file_exists(BMI_PRO_INC . '/zip-explorer.php')) {
          return false;
      }
  
      require_once BMI_PRO_INC . '/zip-explorer.php';

      $restorePartsFile = BMI_TMP . DIRECTORY_SEPARATOR . 'restore_parts.json';
      if (!file_exists($restorePartsFile)) {
          return false;
      }
  
      $restorePartsContent = json_decode(file_get_contents($restorePartsFile), true);
      if (!$restorePartsContent || !isset($restorePartsContent['backupName'])) {
          return false;
      }
  
      if ($restorePartsContent['backupName'] !== basename($zippath)) {
          Logger::log('Ignoring restore parts file, because it does not match the backup name.');
          @unlink($restorePartsFile);
          return false;
      }
  
      $zipExplorer = new ZipExplorer($zippath);
      $amount = 0;
      if (isset($restorePartsContent['dirs']) && is_array($restorePartsContent['dirs'])) {
          foreach ($restorePartsContent['dirs'] as $dir) {
              $amount += $zipExplorer->scanDir($dir, $savepath);
          }

      }
  
      if (isset($restorePartsContent['files']) && is_array($restorePartsContent['files'])) {
          $amount += sizeof($restorePartsContent['files']);
          $files = implode("\n", $restorePartsContent['files']);
          file_put_contents($savepath, $files, FILE_APPEND);
      }

      file_put_contents($savepath, "\nbmi_backup_manifest.json", FILE_APPEND);
      
  
      return $amount;
    }

    public function getZipFileContentPlain($zipname, $filename) {
      if (class_exists('ZipArchive')) {
        $zip = new \ZipArchive();

        if ($zip->open($zipname) === true) {
          if ($content = $zip->getFromName($filename)) {
            return $content;
          } else {
            return false;
          }
        } else {
          return false;
        }
      } else {
        if (!class_exists('PclZip')) {
          if (!defined('PCLZIP_TEMPORARY_DIR')) {
            $bmi_tmp_dir = BMI_TMP;
            if (!file_exists($bmi_tmp_dir)) {
              @mkdir($bmi_tmp_dir, 0775, true);
            }
            define('PCLZIP_TEMPORARY_DIR', $bmi_tmp_dir . DIRECTORY_SEPARATOR . 'bmi-');
          }
          if (defined('BMI_PRO_PCLZIP') && file_exists(BMI_PRO_PCLZIP)) {
            require_once BMI_PRO_PCLZIP;
          } else {
            require_once trailingslashit(ABSPATH) . 'wp-admin/includes/class-pclzip.php';
          }
        }
        $lib = new \PclZip($zipname);

        $content = $lib->extract(PCLZIP_OPT_BY_NAME, $filename, PCLZIP_OPT_EXTRACT_AS_STRING);
        if (sizeof($content) > 0) {
          return $content[0]['content'];
        } else {
          return false;
        }
      }
    }

    public function lock_zip($zippath, $unlock = false) {

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

      try {

        // Path to lock file
        $filename = '.lock';

        // Load lib
        if (!class_exists('PclZip')) {
          if (!defined('PCLZIP_TEMPORARY_DIR')) {
            $bmi_tmp_dir = BMI_TMP;
            if (!file_exists($bmi_tmp_dir)) {
              @mkdir($bmi_tmp_dir, 0775, true);
            }
            define('PCLZIP_TEMPORARY_DIR', $bmi_tmp_dir . DIRECTORY_SEPARATOR . 'bmi-');
          }
          if (defined('BMI_PRO_PCLZIP') && file_exists(BMI_PRO_PCLZIP)) {
            require_once BMI_PRO_PCLZIP;
          } else {
            require_once trailingslashit(ABSPATH) . 'wp-admin/includes/class-pclzip.php';
          }
        }
        $lib = new \PclZip($zippath);

        $md5_file_summary_path = BMI_BACKUPS . DIRECTORY_SEPARATOR. 'md5summary.php';
        if (file_exists($md5_file_summary_path)) {
          $zip_name = basename($zippath);
          $md5summary = file_get_contents($md5_file_summary_path);
          $md5summary = substr($md5summary, 18, -2);
          if (is_serialized($md5summary)) {
            $md5summary = maybe_unserialize($md5summary);
          }
        }

        if (isset($md5summary[$zip_name])) {
          $md5s = $md5summary[$zip_name];
          for ($i = 0; $i < sizeof($md5s); ++$i) {
            $md5_file_path = BMI_BACKUPS . DIRECTORY_SEPARATOR. $md5s[$i] . '.json';
            if (file_exists($md5_file_path)) {
              @unlink($md5_file_path);
            }
          }
        }

        // Unlocking case
        if ($unlock) {
          if ($this->is_locked_zip($zippath)) {
            $lib->delete(PCLZIP_OPT_BY_NAME, $filename);
          } else {
            return true;
          }
        } else {
          if (!$this->is_locked_zip($zippath)) {

            // Locking case
            $content = json_encode(['locked' => 'true']);
            $lib->add([[PCLZIP_ATT_FILE_NAME => $filename, PCLZIP_ATT_FILE_CONTENT => $content]]);
          }
        }

        return true;
      } catch (\Exception $e) {
        Logger::error($e);

        return false;
      } catch (\Throwable $e) {
        Logger::error($e);

        return false;
      }
    }

    public function is_locked_zip($zippath) {
      $lock = $this->getZipFileContent($zippath, '.lock');
      if ($lock) {
        if ($lock->locked == 'true') {
          return true;
        } else {
          return false;
        }
      } else {
        return false;
      }
    }
  }
