Current Path : /var/www/html/wp-content/plugins/duplicator-pro/classes/package/ |
Current File : /var/www/html/wp-content/plugins/duplicator-pro/classes/package/class.pack.archive.zip.php |
<?php /** * Class to create a zip file using PHP ZipArchive * * Standard: PSR-2 (almost) * * @link http://www.php-fig.org/psr/psr-2 * * @package DUP_PRO * @subpackage classes/package * @copyright (c) 2017, Snapcreek LLC * @license https://opensource.org/licenses/GPL-3.0 GNU Public License * @since 1.0.0 * * @notes: Trace process time * $timer01 = DUP_PRO_U::getMicrotime(); * DUP_PRO_Log::trace("SCAN TIME-B = " . DUP_PRO_U::elapsedTime(DUP_PRO_U::getMicrotime(), $timer01)); */ defined('ABSPATH') || defined('DUPXABSPATH') || exit; use Duplicator\Libs\Snap\SnapIO; use Duplicator\Models\SystemGlobalEntity; use Duplicator\Package\Create\BuildProgress; use Duplicator\Utils\ZipArchiveExtended; class DUP_PRO_ZipArchive { /** @var DUP_PRO_Global_Entity */ private $global = null; /** @var bool */ private $optMaxBuildTimeOn = true; /** @var int */ private $maxBuildTimeFileSize = 100000; /** @var int */ private $throttleDelayInUs = 0; /** @var DUP_PRO_Package */ private $package = null; /** @var ZipArchiveExtended */ private $zipArchive = null; /** * Class constructor * * @param DUP_PRO_Package $package The package to create the zip file for */ public function __construct(DUP_PRO_Package $package) { $this->global = DUP_PRO_Global_Entity::getInstance(); $this->optMaxBuildTimeOn = ($this->global->max_package_runtime_in_min > 0); $this->throttleDelayInUs = $this->global->getMicrosecLoadReduction(); $this->package = $package; $this->zipArchive = new ZipArchiveExtended($this->package->StorePath . '/' . $this->package->Archive->File); $password = $this->package->Archive->getArchivePassword(); if (strlen($password) > 0) { $this->zipArchive->setEncrypt(true, $password); } } /** * Creates the zip file and adds the SQL file to the archive * * @param BuildProgress $build_progress A copy of the current build progress * * @return bool Returns true if the process was successful */ public function create(BuildProgress $build_progress) { try { if (!ZipArchiveExtended::isPhpZipAvailable()) { DUP_PRO_Log::trace("Zip archive doesn't exist?"); return false; } $this->package->safe_tmp_cleanup(true); if ($this->package->ziparchive_mode == DUP_PRO_ZipArchive_Mode::SingleThread) { return $this->createSingleThreaded($build_progress); } else { return $this->createMultiThreaded($build_progress); } } catch (Exception $ex) { DUP_PRO_Log::error("Runtime error in class-package-archive-zip.php.", "Exception: {$ex}"); return false; } } /** * Creates the zip file using a single thread approach * * @param BuildProgress $build_progress A copy of the current build progress * * @return bool Returns true if the process was successful */ private function createSingleThreaded(BuildProgress $build_progress) { $countFiles = 0; $compressDir = rtrim(SnapIO::safePath($this->package->Archive->PackDir), '/'); $sqlPath = $this->package->StorePath . '/' . $this->package->Database->File; $zipPath = $this->package->StorePath . '/' . $this->package->Archive->File; $filterDirs = empty($this->package->Archive->FilterDirs) ? 'not set' : rtrim(str_replace(';', "\n\t", $this->package->Archive->FilterDirs)); $filterFiles = empty($this->package->Archive->FilterFiles) ? 'not set' : rtrim(str_replace(';', "\n\t", $this->package->Archive->FilterFiles)); $filterExts = empty($this->package->Archive->FilterExts) ? 'not set' : $this->package->Archive->FilterExts; $filterOn = ($this->package->Archive->FilterOn) ? 'ON' : 'OFF'; $validation = ($this->global->ziparchive_validation) ? 'ON' : 'OFF'; $compression = $build_progress->current_build_compression ? 'ON' : 'OFF'; $this->zipArchive->setCompressed($build_progress->current_build_compression); //PREVENT RETRIES PAST 3: Default is 10 (DUP_PRO_Constants::MAX_BUILD_RETRIES) //since this is ST Mode no reason to keep trying like MT if ($build_progress->retries >= 3) { $err = __('Package build appears stuck so marking package as failed. Is the PHP or Web Server timeouts too low?', 'duplicator-pro'); DUP_PRO_Log::error(__('Build Failure', 'duplicator-pro'), $err, false); DUP_PRO_Log::trace($err); return $build_progress->failed = true; } else { if ($build_progress->retries > 0) { DUP_PRO_Log::infoTrace("**NOTICE: Retry count at: {$build_progress->retries}"); } $build_progress->retries++; $this->package->update(); } //LOAD SCAN REPORT try { $scanReport = $this->package->getScanReportFromJson(DUPLICATOR_PRO_SSDIR_PATH_TMP . "/{$this->package->NameHash}_scan.json"); } catch (DUP_PRO_NoScanFileException $ex) { DUP_PRO_Log::trace("**** scan file doesn't exist!!"); DUP_PRO_Log::error($ex->getMessage(), '', false); $build_progress->failed = true; return true; } catch (DUP_PRO_NoFileListException $ex) { DUP_PRO_Log::trace("**** list of files doesn't exist!!"); DUP_PRO_Log::error($ex->getMessage(), '', false); $build_progress->failed = true; return true; } catch (DUP_PRO_NoDirListException $ex) { DUP_PRO_Log::trace("**** list of directories doesn't exist!!"); DUP_PRO_Log::error($ex->getMessage(), '', false); $build_progress->failed = true; return true; } //============================================ //ST: START ZIP //============================================ if ($build_progress->archive_started === false) { DUP_PRO_Log::info("\n********************************************************************************"); DUP_PRO_Log::info("ARCHIVE ZipArchive Single-Threaded"); DUP_PRO_Log::info("********************************************************************************"); DUP_PRO_Log::info("ARCHIVE DIR: " . $compressDir); DUP_PRO_Log::info("ARCHIVE FILE: " . basename($zipPath)); DUP_PRO_Log::info("COMPRESSION: *{$compression}*"); DUP_PRO_Log::info("VALIDATION: *{$validation}*"); DUP_PRO_Log::info("FILTERS: *{$filterOn}*"); DUP_PRO_Log::info("DIRS:\t{$filterDirs}"); DUP_PRO_Log::info("EXTS: {$filterExts}"); DUP_PRO_Log::info("FILES: {$filterFiles}"); DUP_PRO_Log::info("----------------------------------------"); DUP_PRO_Log::info("COMPRESSING"); DUP_PRO_Log::info("SIZE:\t" . $scanReport->ARC->Size); DUP_PRO_Log::info("STATS:\tDirs " . $scanReport->ARC->DirCount . " | Files " . $scanReport->ARC->FileCount . " | Total " . $scanReport->ARC->FullCount); if (($scanReport->ARC->DirCount == '') || ($scanReport->ARC->FileCount == '') || ($scanReport->ARC->FullCount == '')) { DUP_PRO_Log::error('Invalid Scan Report Detected', 'Invalid Scan Report Detected', false); return $build_progress->failed = true; } $build_progress->archive_started = true; $build_progress->archive_start_time = DUP_PRO_U::getMicrotime(); } //============================================ //ST: ADD DATABASE FILE //============================================ if ($build_progress->archive_has_database === false) { if (!$this->zipArchive->open()) { DUP_PRO_Log::error("Couldn't open $zipPath", '', false); return $build_progress->failed = true; } if ($this->zipArchive->addFile($sqlPath, $this->package->get_sql_ark_file_path())) { DUP_PRO_Log::info("SQL ADDED: " . basename($sqlPath)); } else { DUP_PRO_Log::error("Unable to add database.sql to archive.", "SQL File Path [" . $sqlPath . "]", false); return $build_progress->failed = true; } if ($this->zipArchive->close()) { $build_progress->archive_has_database = true; $this->package->update(); } else { $err = 'ZipArchive close failure during database.sql phase.'; $this->setDupArchiveSwitchFix($err); return $build_progress->failed = true; } } //============================================ //ST: ZIP DIRECTORIES //Keep this loop tight: ZipArchive can handle over 10k+ dir entries in under 0.01 seconds. //Its really fast without files so no need to do status pushes or other checks in loop //============================================ if ($build_progress->next_archive_dir_index < count($scanReport->ARC->Dirs)) { if (!$this->zipArchive->open()) { DUP_PRO_Log::error("Couldn't open $zipPath", '', false); return $build_progress->failed = true; } foreach ($scanReport->ARC->Dirs as $dir) { $emptyDir = $this->package->Archive->getLocalDirPath($dir); DUP_PRO_Log::trace("ADD DIR TO ZIP: '{$emptyDir}'"); if (!$this->zipArchive->addEmptyDir($emptyDir)) { if (empty($compressDir) || strpos($dir, rtrim($compressDir, '/')) != 0) { DUP_PRO_Log::infoTrace("WARNING: Unable to zip directory: '{$dir}'"); } } $build_progress->next_archive_dir_index++; } if ($this->zipArchive->close()) { $this->package->update(); } else { $err = 'ZipArchive close failure during directory add phase.'; $this->setDupArchiveSwitchFix($err); return $build_progress->failed = true; } } //============================================ //ST: ZIP FILES //============================================ if ($build_progress->archive_built === false) { if ($this->zipArchive->open() === false) { DUP_PRO_Log::error("Can not open zip file at: [{$zipPath}]", '', false); return $build_progress->failed = true; } // Since we have to estimate progress in Single Thread mode // set the status when we start archiving just like Shell Exec $this->package->set_status(DUP_PRO_PackageStatus::ARCSTART); $total_file_size = 0; $total_file_count_trip = ($scanReport->ARC->UFileCount + 1000); foreach ($scanReport->ARC->Files as $file) { //NON-ASCII check if (preg_match('/[^\x20-\x7f]/', $file)) { if (!$this->isUTF8FileSafe($file)) { continue; } } if ($this->global->ziparchive_validation) { if (!is_readable($file)) { DUP_PRO_Log::infoTrace("NOTICE: File [{$file}] is unreadable!"); continue; } } $local_name = $this->package->Archive->getLocalFilePath($file); if (!$this->zipArchive->addFile($file, $local_name)) { // Assumption is that we continue?? for some things this would be fatal others it would be ok - leave up to user DUP_PRO_Log::info("WARNING: Unable to zip file: {$file}"); continue; } $total_file_size += filesize($file); //ST: SERVER THROTTLE if ($this->throttleDelayInUs !== 0) { usleep($this->throttleDelayInUs); } //Prevent Overflow if ($countFiles++ > $total_file_count_trip) { DUP_PRO_Log::error("ZipArchive-ST: file loop overflow detected at {$countFiles}", '', false); return $build_progress->failed = true; } } //START ARCHIVE CLOSE $total_file_size_easy = DUP_PRO_U::byteSize($total_file_size); DUP_PRO_Log::trace("Doing final zip close after adding $total_file_size_easy ({$total_file_size})"); if ($this->zipArchive->close()) { DUP_PRO_Log::trace("Final zip closed."); $build_progress->next_archive_file_index = $countFiles; $build_progress->archive_built = true; $this->package->update(); } else { if ($this->global->ziparchive_validation === false) { $this->global->ziparchive_validation = true; $this->global->save(); DUP_PRO_Log::infoTrace("**NOTICE: ZipArchive: validation mode enabled"); } else { $err = 'ZipArchive close failure during file phase with file validation enabled'; $this->setDupArchiveSwitchFix($err); return $build_progress->failed = true; } } } //============================================ //ST: LOG FINAL RESULTS //============================================ if ($build_progress->archive_built) { $timerAllEnd = DUP_PRO_U::getMicrotime(); $timerAllSum = DUP_PRO_U::elapsedTime($timerAllEnd, $build_progress->archive_start_time); $zipFileSize = @filesize($zipPath); DUP_PRO_Log::info("MEMORY STACK: " . DUP_PRO_Server::getPHPMemory()); DUP_PRO_Log::info("FINAL SIZE: " . DUP_PRO_U::byteSize($zipFileSize)); DUP_PRO_Log::info("ARCHIVE RUNTIME: {$timerAllSum}"); if ($this->zipArchive->open()) { $this->package->Archive->file_count = $this->zipArchive->getNumFiles(); $this->package->update(); $this->zipArchive->close(); } else { DUP_PRO_Log::error("ZipArchive open failure.", "Encountered when retrieving final archive file count.", false); return $build_progress->failed = true; } } return true; } /** * Creates the zip file using a multi-thread approach * * @param BuildProgress $build_progress A copy of the current build progress * * @return bool Returns true if the process was successful */ private function createMultiThreaded(BuildProgress $build_progress) { $timed_out = false; $countFiles = 0; $compressDir = rtrim(SnapIO::safePath($this->package->Archive->PackDir), '/'); $sqlPath = $this->package->StorePath . '/' . $this->package->Database->File; $zipPath = $this->package->StorePath . '/' . $this->package->Archive->File; $filterDirs = empty($this->package->Archive->FilterDirs) ? 'not set' : rtrim(str_replace(';', "\n\t", $this->package->Archive->FilterDirs)); $filterFiles = empty($this->package->Archive->FilterFiles) ? 'not set' : rtrim(str_replace(';', "\n\t", $this->package->Archive->FilterFiles)); $filterExts = empty($this->package->Archive->FilterExts) ? 'not set' : $this->package->Archive->FilterExts; $filterOn = ($this->package->Archive->FilterOn) ? 'ON' : 'OFF'; $compression = $build_progress->current_build_compression ? 'ON' : 'OFF'; $this->zipArchive->setCompressed($build_progress->current_build_compression); $scanFilepath = DUPLICATOR_PRO_SSDIR_PATH_TMP . "/{$this->package->NameHash}_scan.json"; //LOAD SCAN REPORT try { $scanReport = $this->package->getScanReportFromJson($scanFilepath); } catch (DUP_PRO_NoScanFileException $ex) { DUP_PRO_Log::trace("**** scan file $scanFilepath doesn't exist!!"); DUP_PRO_Log::error($ex->getMessage(), '', false); $build_progress->failed = true; return true; } catch (DUP_PRO_NoFileListException $ex) { DUP_PRO_Log::trace("**** list of files doesn't exist!!"); DUP_PRO_Log::error($ex->getMessage(), '', false); $build_progress->failed = true; return true; } catch (DUP_PRO_NoDirListException $ex) { DUP_PRO_Log::trace("**** list of directories doesn't exist!!"); DUP_PRO_Log::error($ex->getMessage(), '', false); $build_progress->failed = true; return true; } //============================================ //MT: START ZIP & ADD SQL FILE //============================================ if ($build_progress->archive_started === false) { DUP_PRO_Log::info("\n********************************************************************************"); DUP_PRO_Log::info("ARCHIVE Mode:ZipArchive Multi-Threaded"); DUP_PRO_Log::info("********************************************************************************"); DUP_PRO_Log::info("ARCHIVE DIR: " . $compressDir); DUP_PRO_Log::info("ARCHIVE FILE: " . basename($zipPath)); DUP_PRO_Log::info("COMPRESSION: *{$compression}*"); DUP_PRO_Log::info("FILTERS: *{$filterOn}*"); DUP_PRO_Log::info("DIRS: {$filterDirs}"); DUP_PRO_Log::info("EXTS: {$filterExts}"); DUP_PRO_Log::info("FILES: {$filterFiles}"); DUP_PRO_Log::info("----------------------------------------"); DUP_PRO_Log::info("COMPRESSING"); DUP_PRO_Log::info("SIZE:\t" . $scanReport->ARC->Size); DUP_PRO_Log::info("STATS:\tDirs " . $scanReport->ARC->DirCount . " | Files " . $scanReport->ARC->FileCount . " | Total " . $scanReport->ARC->FullCount); if (($scanReport->ARC->DirCount == '') || ($scanReport->ARC->FileCount == '') || ($scanReport->ARC->FullCount == '')) { DUP_PRO_Log::error('Invalid Scan Report Detected', 'Invalid Scan Report Detected', false); return $build_progress->failed = true; } if (!$this->zipArchive->open()) { DUP_PRO_Log::error("Couldn't open $zipPath", '', false); return $build_progress->failed = true; } if ($this->zipArchive->addFile($sqlPath, $this->package->get_sql_ark_file_path())) { DUP_PRO_Log::info("SQL ADDED: " . basename($sqlPath)); } else { DUP_PRO_Log::error("Unable to add database.sql to archive.", "SQL File Path [" . $sqlPath . "]", false); return $build_progress->failed = true; } if ($this->zipArchive->close()) { $build_progress->archive_has_database = true; $this->package->update(); } else { $err = 'ZipArchive close failure during database.sql phase.'; $this->setDupArchiveSwitchFix($err); return $build_progress->failed = true; } } //============================================ //MT: ZIP DIRECTORIES //Keep this loop tight: ZipArchive can handle over 10k dir entries in under 0.01 seconds. //Its really fast without files no need to do status pushes or other checks in loop //============================================ if ($this->zipArchive->open()) { foreach ($scanReport->ARC->Dirs as $dir) { $emptyDir = $this->package->Archive->getLocalDirPath($dir); DUP_PRO_Log::trace("ADD DIR TO ZIP: '{$emptyDir}'"); if (!$this->zipArchive->addEmptyDir($emptyDir)) { if (empty($compressDir) || strpos($dir, rtrim($compressDir, '/')) != 0) { DUP_PRO_Log::infoTrace("WARNING: Unable to zip directory: '{$dir}'"); } } $build_progress->next_archive_dir_index++; } $this->package->update(); if ($build_progress->timedOut($this->global->php_max_worker_time_in_sec)) { $timed_out = true; $diff = time() - $build_progress->thread_start_time; DUP_PRO_Log::trace("Timed out after hitting thread time of $diff {$this->global->php_max_worker_time_in_sec} so quitting zipping early in the directory phase"); } } else { DUP_PRO_Log::error("Couldn't open $zipPath", '', false); return $build_progress->failed = true; } if ($this->zipArchive->close() === false) { $err = __('ZipArchive close failure during directory add phase.', 'duplicator-pro'); $this->setDupArchiveSwitchFix($err); return $build_progress->failed = true; } //============================================ //MT: ZIP FILES //============================================ if ($timed_out === false) { // PREVENT RETRIES (10x) if ($build_progress->retries > DUP_PRO_Constants::MAX_BUILD_RETRIES) { $err = __('Zip build appears stuck.', 'duplicator-pro'); $this->setDupArchiveSwitchFix($err); $error_msg = __('Package build appears stuck so marking package failed. Recommend setting Settings > Packages > Archive Engine to DupArchive', 'duplicator-pro'); DUP_PRO_Log::error(__('Build Failure', 'duplicator-pro'), $error_msg, false); DUP_PRO_Log::trace($error_msg); return $build_progress->failed = true; } else { $build_progress->retries++; $this->package->update(); } $zip_is_open = false; $total_file_size = 0; $incremental_file_size = 0; $used_zip_file_descriptor_count = 0; $total_file_count = empty($scanReport->ARC->UFileCount) ? 0 : $scanReport->ARC->UFileCount; foreach ($scanReport->ARC->Files as $file) { if ($zip_is_open || ($countFiles == $build_progress->next_archive_file_index)) { if ($zip_is_open === false) { DUP_PRO_Log::trace("resuming archive building at file # $countFiles"); if ($this->zipArchive->open() !== true) { DUP_PRO_Log::error("Couldn't open $zipPath", '', false); $build_progress->failed = true; return true; } $zip_is_open = true; } //NON-ASCII check if (preg_match('/[^\x20-\x7f]/', $file)) { if (!$this->isUTF8FileSafe($file)) { continue; } } elseif (!file_exists($file)) { DUP_PRO_Log::trace("NOTICE: ASCII file [{$file}] does not exist!"); continue; } $local_name = $this->package->Archive->getLocalFilePath($file); $file_size = filesize($file); $zip_status = $this->zipArchive->addFile($file, $local_name); if ($zip_status) { $total_file_size += $file_size; $incremental_file_size += $file_size; } else { // Assumption is that we continue?? for some things this would be fatal others it would be ok - leave up to user DUP_PRO_Log::info("WARNING: Unable to zip file: {$file}"); } $countFiles++; $chunk_size_in_bytes = $this->global->ziparchive_chunk_size_in_mb * 1000000; if ($incremental_file_size > $chunk_size_in_bytes) { // Only close because of chunk size and file descriptors when in legacy mode DUP_PRO_Log::trace("closing zip because ziparchive mode = {$this->global->ziparchive_mode} fd count = $used_zip_file_descriptor_count or incremental file size=$incremental_file_size and chunk size = $chunk_size_in_bytes"); $incremental_file_size = 0; $used_zip_file_descriptor_count = 0; if ($this->zipArchive->close() == true) { $adjusted_percent = floor(DUP_PRO_PackageStatus::ARCSTART + ((DUP_PRO_PackageStatus::ARCDONE - DUP_PRO_PackageStatus::ARCSTART) * ($countFiles / (float) $total_file_count))); $build_progress->next_archive_file_index = $countFiles; $build_progress->retries = 0; $this->package->Status = $adjusted_percent; $this->package->update(); $zip_is_open = false; DUP_PRO_Log::trace("closed zip"); } else { $err = 'ZipArchive close failure during file phase using multi-threaded setting.'; $this->setDupArchiveSwitchFix($err); return $build_progress->failed = true; } } //MT: SERVER THROTTLE if ($this->throttleDelayInUs !== 0) { usleep($this->throttleDelayInUs); } //MT: MAX WORKER TIME (SECS) if ($build_progress->timedOut($this->global->php_max_worker_time_in_sec)) { // Only close because of timeout $timed_out = true; $diff = time() - $build_progress->thread_start_time; DUP_PRO_Log::trace("Timed out after hitting thread time of $diff so quitting zipping early in the file phase"); break; } //MT: MAX BUILD TIME (MINUTES) //Only stop to check on larger files above 100K to avoid checking every single file if ($file_size > $this->maxBuildTimeFileSize && $this->optMaxBuildTimeOn) { $elapsed_sec = time() - $this->package->timer_start; $elapsed_minutes = $elapsed_sec / 60; if ($elapsed_minutes > $this->global->max_package_runtime_in_min) { DUP_PRO_Log::trace("ZipArchive: Multi-thread max build time {$this->global->max_package_runtime_in_min} minutes reached killing process."); return false; } } } else { $countFiles++; } } DUP_PRO_Log::trace("total file size added to zip = $total_file_size"); if ($zip_is_open) { DUP_PRO_Log::trace("Doing final zip close after adding $incremental_file_size"); if ($this->zipArchive->close()) { DUP_PRO_Log::trace("Final zip closed."); $build_progress->next_archive_file_index = $countFiles; $build_progress->retries = 0; $this->package->update(); } else { $err = __('ZipArchive close failure.', 'duplicator-pro'); $this->setDupArchiveSwitchFix($err); DUP_PRO_Log::error($err); return $build_progress->failed = true; } } } //============================================ //MT: LOG FINAL RESULTS //============================================ if ($timed_out === false) { $build_progress->archive_built = true; $build_progress->retries = 0; $this->package->update(); $timerAllEnd = DUP_PRO_U::getMicrotime(); $timerAllSum = DUP_PRO_U::elapsedTime($timerAllEnd, $build_progress->archive_start_time); $zipFileSize = @filesize($zipPath); DUP_PRO_Log::info("COMPRESSED SIZE: " . DUP_PRO_U::byteSize($zipFileSize)); DUP_PRO_Log::info("ARCHIVE RUNTIME: {$timerAllSum}"); DUP_PRO_Log::info("MEMORY STACK: " . DUP_PRO_Server::getPHPMemory()); if ($this->zipArchive->open() === true) { $this->package->Archive->file_count = $this->zipArchive->getNumFiles(); $this->package->update(); $this->zipArchive->close(); } else { DUP_PRO_Log::error("ZipArchive open failure.", "Encountered when retrieving final archive file count.", false); return $build_progress->failed = true; } } return !$timed_out; } /** * Encodes a UTF8 file and then determines if it is safe to add to an archive * * @param string $file The file to test * * @return bool Returns true if the file is readable and safe to add to archive */ private function isUTF8FileSafe($file) { $is_safe = true; $original_file = $file; DUP_PRO_Log::trace("[{$file}] is non ASCII"); // Necessary for adfron type files if (DUP_PRO_STR::hasUTF8($file)) { $file = utf8_decode($file); } if (file_exists($file) === false) { if (file_exists($original_file) === false) { DUP_PRO_Log::trace("$file CAN'T BE READ!"); DUP_PRO_Log::info("WARNING: Unable to zip file: {$file}. Cannot be read"); $is_safe = false; } } return $is_safe; } /** * Wrapper for switching to DupArchive quick fix * * @param string $message The error message * * @return void */ private function setDupArchiveSwitchFix($message) { $fix_text = __('Click to switch archive engine to DupArchive.', 'duplicator-pro'); $this->setFix( $message, $fix_text, array( 'global' => array( 'archive_build_mode' => DUP_PRO_Archive_Build_Mode::DupArchive, ), ) ); } /** * Sends an error to the trace and build logs and sets the UI message * * @param string $message The error message * @param string $fix The details for how to fix the issue * @param mixed[] $option The options to set * * @return void */ private function setFix($message, $fix, $option) { DUP_PRO_Log::trace($message); DUP_PRO_Log::error("$message **FIX: $fix.", '', false); $system_global = SystemGlobalEntity::getInstance(); $system_global->addQuickFix($message, $fix, $option); } }