first commit

This commit is contained in:
2025-02-24 22:33:42 +01:00
commit 737c037e85
18358 changed files with 5392983 additions and 0 deletions

View File

@@ -0,0 +1,9 @@
<Files *.php>
Order Deny,Allow
Deny from all
</Files>
<Files index.php>
Order Allow,Deny
Allow from all
</Files>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,481 @@
<?php
/**
* Lightweight abstraction layer for common simple database routines
*
* Standard: PSR-2
*
* @package SC\DupPro\DB
* @copyright (c) 2017, Snapcreek LLC
* @license https://opensource.org/licenses/GPL-3.0 GNU Public License
*/
defined('ABSPATH') || defined('DUPXABSPATH') || exit;
use Duplicator\Libs\Shell\Shell;
use Duplicator\Libs\Snap\SnapOS;
class DUP_PRO_DB extends wpdb
{
const BUILD_MODE_MYSQLDUMP = 'MYSQLDUMP';
const BUILD_MODE_PHP_SINGLE_THREAD = 'PHP';
const BUILD_MODE_PHP_MULTI_THREAD = 'PHPCHUNKING';
const PHPDUMP_MODE_MULTI = 0;
const PHPDUMP_MODE_SINGLE = 1;
const MAX_TABLE_COUNT_IN_PACKET = 100;
/**
* Get the requested MySQL system variable
*
* @param string $variable The database variable name to lookup
*
* @return ?string the server variable to query for
*/
public static function getVariable($variable)
{
global $wpdb;
$row = $wpdb->get_row("SHOW VARIABLES LIKE '{$variable}'", ARRAY_N);
return isset($row[1]) ? $row[1] : null;
}
/**
* Return the value of lower_case_table_names
*
* @see https://dev.mysql.com/doc/refman/8.0/en/server-system-variables.html#sysvar_lower_case_table_names
*
* @return int
*/
public static function getLowerCaseTableNames()
{
if (($result = self::getVariable("lower_case_table_names")) === null) {
if (SnapOS::isOSX()) {
return 2;
} elseif (SnapOS::isWindows()) {
return 1;
} else {
return 0;
}
}
return (int) $result;
}
/**
* Returns true if table names are case sensitive in this DB
*
* @return bool
*/
public static function dbIsCaseSensitive()
{
return self::getLowerCaseTableNames() === 0;
}
/**
* Return table have real case sensitive prefix.
*
* @param string $table Table name
*
* @return string
*/
public static function updateCaseSensitivePrefix($table)
{
global $wpdb;
if (!self::dbIsCaseSensitive() && stripos($table, $wpdb->prefix) === 0) {
return $wpdb->prefix . substr($table, strlen($wpdb->prefix));
}
return $table;
}
/**
* Gets the MySQL database version number
*
* @param bool $full True: Gets the full version if available (i.e 10.2.3-MariaDB)
* False: Gets only the numeric portion i.e. (5.5.6 -or- 10.1.2)
*
* @return false|string 0 on failure, version number on success
*/
public static function getVersion($full = false)
{
global $wpdb;
if ($full) {
$version = self::getVariable('version');
} else {
$version = preg_replace('/[^0-9.].*/', '', self::getVariable('version'));
}
//Fall-back for servers that have restricted SQL for SHOW statement
//Note: For MariaDB this will report something like 5.5.5 when it is really 10.2.1.
//This mainly is due to mysqli_get_server_info method which gets the version comment
//and uses a regex vs getting just the int version of the value. So while the former
//code above is much more accurate it may fail in rare situations
if (empty($version)) {
$version = $wpdb->db_version();
}
return empty($version) ? 0 : $version;
}
/**
* Try to return the mysqldump path on Windows servers
*
* @return boolean|string
*/
public static function getWindowsMySqlDumpRealPath()
{
if (function_exists('php_ini_loaded_file')) {
$get_php_ini_path = php_ini_loaded_file();
if (@file_exists($get_php_ini_path)) {
$search = array(
dirname(dirname($get_php_ini_path)) . '/mysql/bin/mysqldump.exe',
dirname(dirname(dirname($get_php_ini_path))) . '/mysql/bin/mysqldump.exe',
dirname(dirname($get_php_ini_path)) . '/mysql/bin/mysqldump',
dirname(dirname(dirname($get_php_ini_path))) . '/mysql/bin/mysqldump',
);
foreach ($search as $mysqldump) {
if (@file_exists($mysqldump)) {
return str_replace("\\", "/", $mysqldump);
}
}
}
}
unset($search);
unset($get_php_ini_path);
return false;
}
/**
* Returns the mysqldump path if the server is enabled to execute it
*
* @return boolean|string
*/
public static function getMySqlDumpPath()
{
$global = DUP_PRO_Global_Entity::getInstance();
if (!Shell::test()) {
return false;
}
$custom_mysqldump_path = (strlen($global->package_mysqldump_path)) ? $global->package_mysqldump_path : '';
$custom_mysqldump_path = escapeshellcmd($custom_mysqldump_path);
//Common Windows Paths
if (SnapOS::isWindows()) {
$paths = array(
$custom_mysqldump_path,
DUP_PRO_DB::getWindowsMySqlDumpRealPath(),
'C:/xampp/mysql/bin/mysqldump.exe',
'C:/Program Files/xampp/mysql/bin/mysqldump',
'C:/Program Files/MySQL/MySQL Server 6.0/bin/mysqldump',
'C:/Program Files/MySQL/MySQL Server 5.5/bin/mysqldump',
'C:/Program Files/MySQL/MySQL Server 5.4/bin/mysqldump',
'C:/Program Files/MySQL/MySQL Server 5.1/bin/mysqldump',
'C:/Program Files/MySQL/MySQL Server 5.0/bin/mysqldump',
);
} else {
//Common Linux Paths
$paths = [];
if (strlen($custom_mysqldump_path)) {
$paths[] = $custom_mysqldump_path;
}
// Add possible executeable path if that exists instead of empty string
if (($shellResult = Shell::runCommand('which mysqldump', Shell::AVAILABLE_COMMANDS)) !== false) {
$mysqlDumpExecPath = trim($shellResult->getOutputAsString());
if (strlen($mysqlDumpExecPath) > 0) {
$paths[] = $mysqlDumpExecPath;
}
}
$paths = array_merge(
$paths,
[
'/usr/local/bin/mysqldump',
'/usr/local/mysql/bin/mysqldump',
'/usr/mysql/bin/mysqldump',
'/usr/bin/mysqldump',
'/opt/local/lib/mysql6/bin/mysqldump',
'/opt/local/lib/mysql5/bin/mysqldump',
'/opt/local/lib/mysql4/bin/mysqldump',
'/usr/bin/mysqldump',
]
);
$paths = array_values($paths);
}
foreach ($paths as $path) {
if (strlen($path) === 0) {
continue;
}
$cmd = $path . ' --version';
$shellOutput = Shell::runCommand($cmd);
if ($shellOutput !== false && $shellOutput->getCode() === 0) {
return $path;
}
}
return false;
}
/**
* Get Sql query to create table which is given.
*
* @param string $table Table name
*
* @return string mysql query create table
*/
private static function getCreateTableQuery($table)
{
$row = $GLOBALS['wpdb']->get_row('SHOW CREATE TABLE `' . esc_sql($table) . '`', ARRAY_N);
return $row[1];
}
/**
* Returns all collation types that are assigned to the tables in
* the current database. Each element in the array is unique
*
* @param string[] $tables A list of tables to include from the search
*
* @return string[] Returns an array with all the character set being used
*/
public static function getTableCharSetList($tables)
{
$charSets = array();
try {
foreach ($tables as $table) {
$createTableQuery = self::getCreateTableQuery($table);
if (preg_match('/ CHARSET=([^\s;]+)/i', $createTableQuery, $charsetMatch)) {
if (!in_array($charsetMatch[1], $charSets)) {
$charSets[] = $charsetMatch[1];
}
}
}
return $charSets;
} catch (Exception $ex) {
return $charSets;
}
}
/**
* Returns all collation types that are assigned to the tables and columns table in
* the current database. Each element in the array is unique
*
* @param string[] $tablesToInclude A list of tables to include in the search
*
* @return string[] Returns an array with all the collation types being used
*/
public static function getTableCollationList($tablesToInclude)
{
global $wpdb;
static $collations = null;
if (is_null($collations)) {
$collations = array();
//use half the number of tables since we are using them twice
foreach (array_chunk($tablesToInclude, self::MAX_TABLE_COUNT_IN_PACKET) as $tablesChunk) {
$sqlTables = implode(",", array_map(array(__CLASS__, 'escValueToQueryString'), $tablesChunk));
//UNION is by default DISTINCT
$query = "SELECT `COLLATION_NAME` FROM `information_schema`.`columns` WHERE `COLLATION_NAME` IS NOT NULL AND `table_schema` = '{$wpdb->dbname}' "
. "AND `table_name` in (" . $sqlTables . ")"
. "UNION SELECT `TABLE_COLLATION` FROM `information_schema`.`tables` WHERE `TABLE_COLLATION` IS NOT NULL AND `table_schema` = '{$wpdb->dbname}' "
. "AND `table_name` in (" . $sqlTables . ")";
if (!$wpdb->query($query)) {
DUP_PRO_Log::info("GET TABLE COLLATION ERROR: " . $wpdb->last_error);
continue;
}
$collations = array_merge($collations, $wpdb->get_col());
}
$collations = array_values(array_unique($collations));
sort($collations);
}
return $collations;
}
/**
* Returns list of MySQL engines used by $tablesToInclude in the current DB
*
* @param string[] $tablesToInclude tables to check the engines for
*
* @return string[]
*/
public static function getTableEngineList($tablesToInclude)
{
/** @var wpdb $wpdb */
global $wpdb;
static $engines = null;
if (is_null($engines)) {
$engines = array();
foreach (array_chunk($tablesToInclude, self::MAX_TABLE_COUNT_IN_PACKET) as $tablesChunk) {
$query = "SELECT DISTINCT `ENGINE` FROM `information_schema`.`tables` WHERE `ENGINE` IS NOT NULL AND `table_schema` = '{$wpdb->dbname}' "
. "AND `table_name` in (" . implode(",", array_map(array(__CLASS__, 'escValueToQueryString'), $tablesChunk)) . ")";
if (!$wpdb->query($query)) {
DUP_PRO_Log::info("GET TABLE ENGINES ERROR: " . $wpdb->last_error);
}
$engines = array_merge($engines, $wpdb->get_col($query));
}
$engines = array_values(array_unique($engines));
}
return $engines;
}
/**
* Returns the correct database build mode PHP, MYSQLDUMP, PHPCHUNKING
*
* @return string Returns a string with one of theses three values PHP, MYSQLDUMP, PHPCHUNKING
*/
public static function getBuildMode()
{
$global = DUP_PRO_Global_Entity::getInstance();
if ($global->package_mysqldump) {
$mysqlDumpPath = DUP_PRO_DB::getMySqlDumpPath();
if ($mysqlDumpPath === false) {
DUP_PRO_Log::trace("Forcing into PHP mode - the mysqldump executable wasn't found!");
$global->package_mysqldump = false;
$global->save();
}
}
if ($global->package_mysqldump) {
return self::BUILD_MODE_MYSQLDUMP;
} elseif ($global->package_phpdump_mode == self::PHPDUMP_MODE_MULTI) {
return self::BUILD_MODE_PHP_MULTI_THREAD;
} else {
return self::BUILD_MODE_PHP_SINGLE_THREAD;
}
}
/**
* Returns an escaped sql string
*
* @param string $sql The sql to escape
* @param bool $removePlaceholderEscape Patch for how the default WP function works.
*
* @return boolean|string
* @also see: https://make.wordpress.org/core/2017/10/31/changed-behaviour-of-esc_sql-in-wordpress-4-8-3/
*/
public static function escSQL($sql, $removePlaceholderEscape = false)
{
global $wpdb;
static $removePlMethodExists = null;
if (is_null($removePlMethodExists)) {
$removePlMethodExists = method_exists($wpdb, 'remove_placeholder_escape');
}
if ($removePlaceholderEscape && $removePlMethodExists) {
return $wpdb->remove_placeholder_escape(esc_sql($sql));
} else {
return esc_sql($sql);
}
}
/**
* Get tables list in database
*
* @return string[]
*/
public static function getTablesList()
{
global $wpdb;
$result = $wpdb->get_col("SHOW FULL TABLES FROM `" . DB_NAME . "` WHERE Table_Type = 'BASE TABLE' ", 0);
if (!is_array($result)) {
return array();
}
return $result;
}
/**
* This function escape sql string without add and remove remove_placeholder_escape
* Don't use esc_sql wordpress function
*
* @param null|scalar $value input value
*
* @return string
*/
public static function escValueToQueryString($value)
{
if (is_null($value)) {
return 'NULL';
}
global $wpdb;
return '"' . mysqli_real_escape_string($wpdb->dbh, (string) $value) . '"';
}
/**
* This function returns the list of tables with the number of rows for each table.
* Using the count the number is the real and not approximate number of the table schema.
*
* @param string|string[] $tables list of tables os single table
*
* @return array<string,int> key table nale val table rows
*/
public static function getTablesRows($tables = array())
{
$result = array();
if (empty($tables)) {
return $result;
}
$tables = (array) $tables;
global $wpdb;
$query = '';
foreach ($tables as $index => $table) {
$query .= ($index > 0 ? ' UNION ' : '');
$query .= 'SELECT "' . $wpdb->_real_escape($table) . '" AS `table`, COUNT(*) AS `rows` FROM `' . $wpdb->_real_escape($table) . '`';
}
$queryResult = $wpdb->get_results($query);
if ($wpdb->last_error) {
DUP_PRO_Log::info("QUERY ERROR: " . $wpdb->last_error);
throw new Exception('SET TOTAL QUERY ERROR: ' . $wpdb->last_error);
}
foreach ($queryResult as $tableInfo) {
$result[self::updateCaseSensitivePrefix($tableInfo->table)] = $tableInfo->rows;
}
return $result;
}
/**
* This function returns the total number of rows in the listed tables.
* It does not count the real number of rows but evaluates the number present in the table schema.
* This number is a rough estimate that may be different from the real number.
*
* The advantage of this function is that it is instantaneous unlike the actual counting of lines that take several seconds.
* But the number returned by this function cannot be used for any type of line count validation in the database.
*
* @param string|string[] $tables list of tables os single table
*
* @return int
*/
public static function getImpreciseTotaTablesRows($tables = array())
{
$tables = (array) $tables;
if (count($tables) == 0) {
return 0;
}
global $wpdb;
$query = 'SELECT SUM(TABLE_ROWS) as "totalRows" FROM information_schema.TABLES '
. 'WHERE TABLE_SCHEMA = "' . $wpdb->_real_escape($wpdb->dbname) . '" '
. 'AND TABLE_NAME IN (' . implode(',', array_map(array(__CLASS__, 'escValueToQueryString'), $tables)) . ')';
$result = (int) $wpdb->get_var($query);
if ($wpdb->last_error) {
DUP_PRO_Log::info("QUERY ERROR: " . $wpdb->last_error);
throw new Exception('SET TOTAL QUERY ERROR: ' . $wpdb->last_error);
}
return $result;
}
}

View File

@@ -0,0 +1,18 @@
<?php
class DUP_PRO_NoScanFileException extends Exception
{
}
class DUP_PRO_JsonDecodeException extends Exception
{
}
class DUP_PRO_NoFileListException extends Exception
{
}
class DUP_PRO_NoDirListException extends Exception
{
}

View File

@@ -0,0 +1,696 @@
<?php
/**
* Used to create package and application trace logs
*
* Package logs: Consist of a separate log file for each package created
* Trace logs: Created only when tracing is enabled see Settings > General
* One trace log is created and when it hits a threshold a
* second one is made
*
* Standard: PSR-2
*
* @link http://www.php-fig.org/psr/psr-2
*
* @package DUP_PRO
* @subpackage classes
* @copyright (c) 2017, Snapcreek LLC
* @license https://opensource.org/licenses/GPL-3.0 GNU Public License
* @since 3.0.0
*/
defined('ABSPATH') || defined('DUPXABSPATH') || exit;
use Duplicator\Libs\Snap\SnapLog;
use Duplicator\Libs\Snap\SnapUtil;
class DUP_PRO_Log
{
/** @var ?resource The file handle used to write to the package log file */
private static $logFileHandle = null;
/**
* Is tracing enabled
*
* @return bool
*/
public static function isTraceEnabled()
{
static $traceEnabled = null;
if (is_null($traceEnabled)) {
$traceEnabled = (bool) get_option('duplicator_pro_trace_log_enabled', false);
// Create trace log file if it doesn't exist
if ($traceEnabled) {
$trace_filepath = self::getTraceFilepath();
if (!file_exists(dirname($trace_filepath))) {
return false;
}
if (!self::traceFileExists()) {
if (file_put_contents($trace_filepath, "") === false) {
throw new Exception("Could not initialize trace file: " . $trace_filepath);
}
}
}
}
return $traceEnabled;
}
/**
* Open a log file connection for writing to the package log file
*
* @param string $nameHash The Name of the log file to create
*
* @return bool
*/
public static function open($nameHash)
{
if (strlen($nameHash) == 0) {
throw new Exception("A name value is required to open a file log.");
}
self::close();
if ((self::$logFileHandle = @fopen(DUPLICATOR_PRO_SSDIR_PATH . "/{$nameHash}_log.txt", "a+")) === false) {
self::$logFileHandle = null;
return false;
} else {
// By initializing the error_handler on opening the log, I am sure that when a package is processed, the handler is active.
DUP_PRO_Handler::init_error_handler();
return true;
}
}
/**
* Close the package log file connection if is opened
*
* @return bool Returns TRUE on success or FALSE on failure.
*/
public static function close()
{
$result = true;
if (!is_null(self::$logFileHandle)) {
$result = @fclose(self::$logFileHandle);
self::$logFileHandle = null;
} else {
$result = true;
}
return $result;
}
/**
* General information send to the package log if opened
*
* @param string $msg The message to log
*
* @return void
*/
public static function info($msg)
{
if (!is_null(self::$logFileHandle)) {
@fwrite(self::$logFileHandle, $msg . "\n");
}
}
/**
* Info exception
*
* @param Exception $e The exception to trace
* @param string $msg Addtional message
*
* @return void
*/
public static function infoException(Exception $e, $msg = '')
{
$log = '';
if (strlen($msg) > 0) {
$log = $msg . "\n";
}
$log .= SnapLog::getTextException($e);
self::info($msg);
}
/**
* Print_r to the package log if opened
*
* @param mixed $val The value to print_r
* @param string $name The name of the value
*
* @return void
*/
public static function print_r_info($val, $name = '')
{
$msg = empty($name) ? '' : 'VALUE ' . $name . ': ';
$msg .= print_r($val, true);
self::info($msg);
}
/**
* General information send to the package log and trace log
*
* @param string $msg The message to log
* @param bool $audit Add the trace message to the PHP error log
* additional constraints are required
* @param string $calling_function_override Override the calling function name
* @param bool $force_trace Force the trace to be written to the trace log
*
* @return void
*/
public static function infoTrace($msg, $audit = true, $calling_function_override = null, $force_trace = false)
{
self::info($msg);
self::trace($msg, $audit, $calling_function_override, $force_trace, 1);
}
/**
* Info trace exception
*
* @param Exception $e The exception to trace
* @param string $msg Addtional message
*
* @return void
*/
public static function infoTraceException(Exception $e, $msg = '')
{
self::infoException($e, $msg);
self::traceException($e, $msg);
}
/**
* Called for the package log when an error is detected and no further processing should occur
*
* @param string $msg The message to log
* @param string $detail Additional details to help resolve the issue if possible
* @param bool $die Issue a die command when finished logging
*
* @return void
*/
public static function error($msg, $detail = '', $die = true)
{
if ($detail == '') {
$detail = '(no detail)';
}
// phpcs:ignore PHPCompatibility.FunctionUse.ArgumentFunctionsReportCurrentValue.Changed
$source = self::getStack(debug_backtrace());
$err_msg = "\n\n====================================================================\n";
$err_msg .= "!RUNTIME ERROR!\n";
$err_msg .= "---------------------------------------------------------------------\n";
$err_msg .= "MESSAGE:\n{$msg}\n";
$err_msg .= "DETAILS:\n{$detail}\n";
$err_msg .= "---------------------------------------------------------------------\n";
$err_msg .= "TRACE:\n{$source}";
$err_msg .= "====================================================================\n\n";
self::infoTrace($err_msg);
if ($die) {
self::close();
// Output to browser
$browser_msg = "RUNTIME ERROR:<br/>An error has occured. Please try again!<br/>";
$browser_msg .= "See the duplicator log file for full details: Duplicator Pro &gt; Tools &gt; Logging<br/><br/>";
$browser_msg .= "MESSAGE:<br/> {$msg} <br/><br/>";
$browser_msg .= "DETAILS: {$detail} <br/>";
die(wp_kses($browser_msg, ['br' => []]));
}
}
/**
* The current stack trace of a PHP call
*
* @param array<array<string, mixed>> $stacktrace The current debug stack
*
* @return string A log friend stack-trace view of info
*/
public static function getStack($stacktrace)
{
$output = "";
$i = 1;
foreach ($stacktrace as $node) {
$file_output = isset($node['file']) ? basename($node['file']) : '';
$function_output = isset($node['function']) ? basename($node['function']) : '';
$line_output = isset($node['line']) ? basename($node['line']) : '';
$output .= "$i. " . $file_output . " : " . $function_output . " (" . $line_output . ")\n";
$i++;
}
return $output;
}
/**
* Deletes the trace log and backup trace log files
*
* @return boolean true on success of deletion of trace log otherwise returns false
*/
public static function deleteTraceLog()
{
$file_path = self::getTraceFilepath();
$backup_path = self::getBackupTraceFilepath();
self::trace("deleting $file_path");
$traceDelete = @unlink($file_path);
if (file_exists($backup_path)) {
self::trace("deleting $backup_path");
$bkTraceDelete = @unlink($backup_path);
} else {
$bkTraceDelete = true;
}
return ($traceDelete && $bkTraceDelete);
}
/**
* Gets the backup trace file path
*
* @return string Returns the full path to the backup trace file (i.e. dup-pro_hash.txt)
*/
public static function getBackupTraceFilepath()
{
static $backupTraceName = null;
if ($backupTraceName == null) {
$backupTraceName = "dup_pro_" . self::getTraceHash() . "_log_bak.txt";
}
return DUPLICATOR_PRO_SSDIR_PATH . "/" . $backupTraceName;
}
/**
* Geyt trace name
*
* @return string
*/
protected static function getTraceName()
{
static $traceName = null;
if ($traceName == null) {
$traceName = "dup_pro_" . self::getTraceHash() . "_log.txt";
}
return $traceName;
}
/**
* Gets the active trace file path
*
* @return string Returns the full path to the active trace file (i.e. dup-pro_hash.txt)
*/
public static function getTraceFilepath()
{
return DUPLICATOR_PRO_SSDIR_PATH . "/" . self::getTraceName();
}
/**
* Gets the active trace file URL path
*
* @return string Returns the URL to the active trace file
*/
public static function getTraceURL()
{
return DUPLICATOR_PRO_SSDIR_URL . "/" . self::getTraceName();
}
/**
* Gets the current file size of the active trace file
*
* @return string Returns a human readable file size of the active trace file
*/
public static function getTraceStatus()
{
$file_path = DUP_PRO_Log::getTraceFilepath();
$backup_path = DUP_PRO_Log::getBackupTraceFilepath();
if (file_exists($file_path)) {
$filesize = filesize($file_path);
if (file_exists($backup_path)) {
$filesize += filesize($backup_path);
}
$message = DUP_PRO_U::byteSize($filesize);
} else {
$message = __('No Log', 'duplicator-pro');
}
return $message;
}
/**
* Adds a message to the active trace log
*
* @param string $message The message to add to the active trace
* @param bool $audit Add the trace message to the PHP error log
* additional constraints are required
* @param string $calling_function_override Override the calling function name
* @param bool $force_trace Force the trace to be written to the trace log
* @param int $backTraceBack The number of backtrace calls to go back
*
* @return void
*/
public static function trace($message, $audit = true, $calling_function_override = null, $force_trace = false, $backTraceBack = 0)
{
if (self::isTraceEnabled() || $force_trace) {
$send_trace_to_error_log = (bool) get_option('duplicator_pro_send_trace_to_error_log', false);
if (isset($_SERVER['REMOTE_PORT'])) {
$unique_id = sprintf("%08x", abs(crc32($_SERVER['REMOTE_ADDR'] . $_SERVER['REQUEST_TIME'] . $_SERVER['REMOTE_PORT'])));
} else {
$unique_id = sprintf("%08x", abs(crc32($_SERVER['REMOTE_ADDR'] . $_SERVER['REQUEST_TIME'])));
}
if ($calling_function_override == null) {
$calling_function = SnapUtil::getCallingFunctionName($backTraceBack);
} else {
$calling_function = $calling_function_override;
}
if (is_object($message)) {
$ov = get_object_vars($message);
$message = print_r($ov, true);
} elseif (is_array($message)) {
$message = print_r($message, true);
}
$ticks = time() + \Duplicator\Libs\Snap\SnapWP::getGMTOffset();
$formatted_time = date('d-m H:i:s', $ticks);
$logging_message = "[{$unique_id}] {$calling_function} {$message}";
$formatted_logging_message = "{$formatted_time} {$logging_message}\r\n";
// Write to error log if warranted - if either it's a non audit(error) or tracing has been piped to the error log
if (($audit == false) || ($send_trace_to_error_log) || ($force_trace) && WP_DEBUG && WP_DEBUG_LOG) {
SnapLog::phpErr($logging_message);
}
// Everything goes to the plugin log, whether it's part of package generation or not.
self::writeToTrace($formatted_logging_message);
}
}
/**
* Trace exception
*
* @param Exception $e The exception to trace
* @param string $msg Addtional message
*
* @return void
*/
public static function traceException(Exception $e, $msg = '')
{
$log = '';
if (strlen($msg) > 0) {
$log = $msg . "\n";
}
$log .= SnapLog::getTextException($e);
self::trace($log);
}
/**
* Prints a variable to the trace log
*
* @param mixed $val The value to print_r
* @param string $name The name of the value
* @param bool $audit Add the trace message to the PHP error log
* additional constraints are required
* @param string $calling_function_override Override the calling function name
* @param bool $force_trace Force the trace to be written to the trace log
*
* @return void
*/
public static function print_r_trace($val, $name = '', $audit = true, $calling_function_override = null, $force_trace = false)
{
$msg = empty($name) ? '' : 'VALUE ' . $name . ': ';
$msg .= print_r($val, true);
if ($calling_function_override == null) {
$calling_function = SnapUtil::getCallingFunctionName();
} else {
$calling_function = $calling_function_override;
}
self::trace($msg, $audit, $calling_function, $force_trace, 1);
}
/**
* Adds a message to the active trace log with ***ERROR*** prepended
*
* @param string $message The error message to add to the active trace
*
* @return void
*/
public static function traceError($message)
{
error_log("***ERROR*** $message");
self::infoTrace("***ERROR*** $message", false, SnapUtil::getCallingFunctionName());
}
/**
* Adds a message followed by an object dump to the message trace
*
* @param string $message The message to add to the active trace
* @param mixed $object Generic data
*
* @return void
*/
public static function traceObject($message, $object)
{
$calling = SnapUtil::getCallingFunctionName();
self::trace($message . '<br\>', true, $calling);
self::trace(SnapLog::v2str($object), true, $calling);
}
/**
* Does the trace file exists
*
* @return bool Returns true if an active trace file exists
*/
public static function traceFileExists()
{
$file_path = DUP_PRO_Log::getTraceFilepath();
return file_exists($file_path);
}
/**
* Manages writing the active or backup log based on the size setting
*
* @param string $formatted_logging_message The message to write to the trace log
*
* @return void
*/
private static function writeToTrace($formatted_logging_message)
{
$log_filepath = DUP_PRO_Log::getTraceFilepath();
if (!file_exists($log_filepath)) {
return;
}
if (@filesize($log_filepath) > DUP_PRO_Constants::MAX_LOG_SIZE) {
$backup_log_filepath = DUP_PRO_Log::getBackupTraceFilepath();
if (file_exists($backup_log_filepath)) {
if (@unlink($backup_log_filepath) === false) {
SnapLog::phpErr("Couldn't delete backup log $backup_log_filepath");
}
}
if (@rename($log_filepath, $backup_log_filepath) === false) {
SnapLog::phpErr("Couldn't rename log $log_filepath to $backup_log_filepath");
}
}
if (@file_put_contents($log_filepath, $formatted_logging_message, FILE_APPEND) === false) {
// Not en error worth reporting
}
}
/**
* Get default key encryption
*
* @return string
*/
protected static function getTraceHash()
{
$auth_key = defined('AUTH_KEY') ? AUTH_KEY : 'atk';
$auth_key .= defined('DB_HOST') ? DB_HOST : 'dbh';
$auth_key .= defined('DB_NAME') ? DB_NAME : 'dbn';
$auth_key .= defined('DB_USER') ? DB_USER : 'dbu';
return hash('md5', $auth_key);
}
}
class DUP_PRO_Handler
{
const MODE_OFF = 0;
// don't write in log
const MODE_LOG = 1;
// write errors in log file
const MODE_VAR = 2;
// put php errors in $varModeLog static var
const SHUTDOWN_TIMEOUT = 'tm';
/** @var array<string,mixed> */
private static $shutdownReturns = array('tm' => 'timeout');
/** @var int */
private static $handlerMode = self::MODE_LOG;
/** @var bool print code reference and errno at end of php error line [CODE:10|FILE:test.php|LINE:100] */
private static $codeReference = true;
/** @var bool print prefix in php error line [PHP ERR][WARN] MSG: ..... */
private static $errPrefix = true;
/** @var string php errors in MODE_VAR */
private static $varModeLog = '';
/**
* This function only initializes the error handler the first time it is called
*
* @return void
*/
public static function init_error_handler()
{
static $initialized = null;
if ($initialized === null) {
@set_error_handler(array(__CLASS__, 'error'));
@register_shutdown_function(array(__CLASS__, 'shutdown'));
$initialized = true;
}
}
/**
* Error handler
*
* @param integer $errno Error level
* @param string $errstr Error message
* @param string $errfile Error file
* @param integer $errline Error line
*
* @return bool
*/
public static function error($errno, $errstr, $errfile, $errline)
{
switch (self::$handlerMode) {
case self::MODE_OFF:
if ($errno == E_ERROR) {
$log_message = self::getMessage($errno, $errstr, $errfile, $errline);
DUP_PRO_Log::error($log_message);
}
break;
case self::MODE_VAR:
self::$varModeLog .= self::getMessage($errno, $errstr, $errfile, $errline) . "\n";
break;
case self::MODE_LOG:
default:
switch ($errno) {
case E_ERROR:
$log_message = self::getMessage($errno, $errstr, $errfile, $errline);
DUP_PRO_Log::error($log_message);
break;
case E_NOTICE:
case E_WARNING:
default:
$log_message = self::getMessage($errno, $errstr, $errfile, $errline);
DUP_PRO_Log::infoTrace($log_message);
break;
}
}
return true;
}
/**
* Get error message
*
* @param integer $errno Error level
* @param string $errstr Error message
* @param string $errfile Error file
* @param integer $errline Error line
*
* @return string
*/
private static function getMessage($errno, $errstr, $errfile, $errline)
{
$result = '';
if (self::$errPrefix) {
$result = '[PHP ERR]';
switch ($errno) {
case E_ERROR:
$result .= '[FATAL]';
break;
case E_WARNING:
$result .= '[WARN]';
break;
case E_NOTICE:
$result .= '[NOTICE]';
break;
default:
$result .= '[ISSUE]';
break;
}
$result .= ' MSG:';
}
$result .= $errstr;
if (self::$codeReference) {
$result .= ' [CODE:' . $errno . '|FILE:' . $errfile . '|LINE:' . $errline . ']';
}
return $result;
}
/**
* if setMode is called without params set as default
*
* @param int $mode ENUM self::MODE_*
* @param bool $errPrefix print prefix in php error line [PHP ERR][WARN] MSG: .....
* @param bool $codeReference print code reference and errno at end of php error line [CODE:10|FILE:test.php|LINE:100]
*
* @return void
*/
public static function setMode($mode = self::MODE_LOG, $errPrefix = true, $codeReference = true)
{
switch ($mode) {
case self::MODE_OFF:
case self::MODE_VAR:
self::$handlerMode = $mode;
break;
case self::MODE_LOG:
default:
self::$handlerMode = self::MODE_LOG;
}
self::$varModeLog = '';
self::$errPrefix = $errPrefix;
self::$codeReference = $codeReference;
}
/**
*
* @return string return var log string in MODE_VAR
*/
public static function getVarLog()
{
return self::$varModeLog;
}
/**
*
* @return string return var log string in MODE_VAR and clean var
*/
public static function getVarLogClean()
{
$result = self::$varModeLog;
self::$varModeLog = '';
return $result;
}
/**
*
* @param string $status timeout
* @param string $str string
*
* @return void
*/
public static function setShutdownReturn($status, $str)
{
self::$shutdownReturns[$status] = $str;
}
/**
* Shutdown handler
*
* @return void
*/
public static function shutdown()
{
if (($error = error_get_last())) {
if (preg_match('/^Maximum execution time (?:.+) exceeded$/i', $error['message'])) {
echo esc_html(self::$shutdownReturns[self::SHUTDOWN_TIMEOUT]);
}
self::error($error['type'], $error['message'], $error['file'], $error['line']);
}
}
}

View File

@@ -0,0 +1,41 @@
<?php
use Duplicator\Views\UserUIOptions;
if (! class_exists('DUP_PRO_WP_List_Table')) {
require_once dirname(__FILE__) . '/class.wp.list.table.php';
}
/**
* List table class
*/
class DUP_PRO_Package_Pagination extends DUP_PRO_WP_List_Table
{
/**
* Get num items per page
*
* @return int
*/
public static function get_per_page()
{
return UserUIOptions::getInstance()->get(UserUIOptions::VAL_PACKAGES_PER_PAGE);
}
/**
* Display pagination
*
* @param int $total_items Total items
* @param int $per_page Per page
*
* @return void
*/
public function display_pagination($total_items, $per_page = 10)
{
$this->set_pagination_args(array(
'total_items' => $total_items,
'per_page' => $per_page,
));
$which = 'top';
$this->pagination($which);
}
}

View File

@@ -0,0 +1,361 @@
<?php
defined("ABSPATH") or die("");
use Duplicator\Libs\Snap\SnapOS;
use Duplicator\Libs\Shell\Shell;
class DUP_PRO_PHP_Log
{
/**
* GET ERROR LOG DIRECT PATH
*
* @param ?string $custom Custom path
* @param bool $unsafe If is true, function only check is file exists but not chmod and type
*
* @return false|string Return path or false on fail
*/
public static function get_path($custom = null, $unsafe = false)
{
// Find custom path
if (!empty($custom)) {
if ($unsafe === true && file_exists($custom) && is_file($custom)) {
return $custom;
} elseif (is_file($custom) && is_readable($custom)) {
return $custom;
} else {
return false;
}
}
$path = self::find_path($unsafe);
if ($path !== false) {
return strtr($path, array(
'\\' => '/',
'//' => '/',
));
}
return false;
}
/**
* GET ERROR LOG DATA
*
* @param int $limit Number of lines
* @param string $time_format Time format how you like to see in log
*
* @return false|array<int|string,array<string,mixed>> array log or false on failure
*/
public static function get_log($limit = 200, $time_format = "Y-m-d H:i:s")
{
return self::parse_log($limit, $time_format);
}
/**
* GET FILENAME FROM PATH
*
* @param string $path Path to file
*
* @return false|string Filename or false on failure
*/
public static function get_filename($path)
{
if ($path === false || !is_readable($path) || !is_file($path)) {
return false;
}
return basename($path);
}
/**
* CLEAR PHP ERROR LOG
*
* @return bool
*/
public static function clear_log()
{
return self::clear_error_log();
}
/**
* Parses the PHP error log to an array.
*
* @param int $limit number of lines
* @param string $time_format time format how you like to see in log
*
* @return false|array<int|string,array<string,mixed>> array log or false on failure
*/
private static function parse_log($limit = 200, $time_format = "Y-m-d H:i:s")
{
$parsedLogs = array();
$path = self::find_path();
$contents = null;
if ($path === false) {
return false;
}
try {
// Good old shell can solve this in less of second
if (!SnapOS::isWindows()) {
$shellOutput = Shell::runCommand("tail -{$limit} {$path}", Shell::AVAILABLE_COMMANDS);
if ($shellOutput !== false) {
$contents = $shellOutput->getOutputAsString();
}
}
// Shell fail on various cases, now we are ready to rock
if (empty($contents)) {
// If "SplFileObject" is available use it
if (class_exists('SplFileObject') && class_exists('LimitIterator')) {
$file = new SplFileObject($path, 'rb');
$file->seek(PHP_INT_MAX);
$last_line = $file->key();
if ($last_line > 0) {
++$limit;
$lines = new LimitIterator($file, (($last_line - $limit) <= 0 ? 0 : $last_line - $limit), ($last_line > 1 ? ($last_line + 1) : $last_line));
$contents = iterator_to_array($lines);
$contents = join("\n", $contents);
}
} else {
// Or good old fashion fopen()
$contents = null;
$limit = ($limit + 2);
$lines = array();
if ($fp = fopen($path, "rb")) {
while (!feof($fp)) {
$line = fgets($fp, 4096);
array_push($lines, $line);
if (count($lines) > $limit) {
array_shift($lines);
}
}
fclose($fp);
if (count($lines) > 0) {
foreach ($lines as $a => $line) {
$contents .= "\n{$line}";
}
}
} else {
return false;
}
}
}
} catch (Exception $exc) {
return false;
}
// Little magic with \n
$contents = trim($contents, "\n");
$contents = preg_replace("/\n{2,}/U", "\n", $contents);
$lines = explode("\n", $contents);
/* DEBUG */
if (isset($_GET['debug_log']) && $_GET['debug_log'] == 'true') {
echo '<pre style="background:#fff; padding:10px;word-break: break-all;display:block;white-space: pre-line;">',
esc_html(var_export($contents, true)),
'</pre>';
}
// Must clean memory ASAP
unset($contents);
// Let's arse things on the right way
$currentLineNumberCount = count($lines);
for ($currentLineNumber = 0; $currentLineNumber < $currentLineNumberCount; ++$currentLineNumber) {
$currentLine = trim($lines[$currentLineNumber]);
// Normal error log line starts with the date & time in []
if ('[' === substr($currentLine, 0, 1)) {
// Get the datetime when the error occurred
$dateArr = array();
preg_match('~^\[(.*?)\]~', $currentLine, $dateArr);
$currentLine = str_replace($dateArr[0], '', $currentLine);
$currentLine = trim($currentLine);
$dateArr = explode(' ', $dateArr[1]);
$errorDateTime = date($time_format, strtotime($dateArr[0] . ' ' . $dateArr[1]));
// Get the type of the error
$errorType = null;
if (false !== strpos($currentLine, 'PHP Warning')) {
$currentLine = str_replace('PHP Warning:', '', $currentLine);
$currentLine = trim($currentLine);
$errorType = 'WARNING';
} elseif (false !== strpos($currentLine, 'PHP Notice')) {
$currentLine = str_replace('PHP Notice:', '', $currentLine);
$currentLine = trim($currentLine);
$errorType = 'NOTICE';
} elseif (false !== strpos($currentLine, 'PHP Fatal error')) {
$currentLine = str_replace('PHP Fatal error:', '', $currentLine);
$currentLine = trim($currentLine);
$errorType = 'FATAL';
} elseif (false !== strpos($currentLine, 'PHP Parse error')) {
$currentLine = str_replace('PHP Parse error:', '', $currentLine);
$currentLine = trim($currentLine);
$errorType = 'SYNTAX';
} elseif (false !== strpos($currentLine, 'PHP Exception')) {
$currentLine = str_replace('PHP Exception:', '', $currentLine);
$currentLine = trim($currentLine);
$errorType = 'EXCEPTION';
}
if (false !== strpos($currentLine, ' on line ')) {
$errorLine = explode(' on line ', $currentLine);
$errorLine = trim($errorLine[1]);
$currentLine = str_replace(' on line ' . $errorLine, '', $currentLine);
} else {
$errorLine = substr($currentLine, strrpos($currentLine, ':') + 1);
$currentLine = str_replace(':' . $errorLine, '', $currentLine);
}
$errorFile = explode(' in ', $currentLine);
$errorFile = (isset($errorFile[1]) ? trim($errorFile[1]) : '');
$currentLine = str_replace(' in ' . $errorFile, '', $currentLine);
// The message of the error
$errorMessage = trim($currentLine);
$parsedLogs[] = array(
'dateTime' => $errorDateTime,
'type' => $errorType,
'file' => $errorFile,
'line' => (int)$errorLine,
'message' => $errorMessage,
'stackTrace' => array(),
);
} elseif ('Stack trace:' === $currentLine) {
// Stack trace beginning line
$stackTraceLineNumber = 0;
for (++$currentLineNumber; $currentLineNumber < $currentLineNumberCount; ++$currentLineNumber) {
$currentLine = null;
if (isset($lines[$currentLineNumber])) {
$currentLine = trim($lines[$currentLineNumber]);
}
// If the current line is a stack trace line
if ('#' === substr($currentLine, 0, 1)) {
$parsedLogsKeys = array_keys($parsedLogs);
$parsedLogsLastKey = end($parsedLogsKeys);
$currentLine = str_replace('#' . $stackTraceLineNumber, '', $currentLine);
$parsedLogs[$parsedLogsLastKey]['stackTrace'][] = trim($currentLine);
++$stackTraceLineNumber;
} else {
// If the current line is the last stack trace ('thrown in...')
break;
}
}
}
}
rsort($parsedLogs);
return $parsedLogs;
}
/**
* Clear error log file
*
* @return bool true on success or false on failure.
*/
private static function clear_error_log()
{
// Get error log
$path = self::find_path();
// Get log file name
$filename = self::get_filename($path);
// Reutn error
if (!$filename) {
return false;
}
$dir = dirname($path);
$dir = strtr($dir, array(
'\\' => '/',
'//' => '/',
));
unlink($path);
return touch($dir . '/' . $filename);
}
/**
* Find PHP error log file
*
* @param bool $unsafe If is true, function only check is file exists but not chmod and type
*
* @return false|string return path or false on failure
*/
private static function find_path($unsafe = false)
{
// If ini_get is enabled find path
if (function_exists('ini_get')) {
$path = ini_get('error_log');
if ($unsafe === true && file_exists($path) && is_file($path)) {
return $path;
}
if (is_file($path) && is_readable($path)) {
return $path;
}
}
// HACK: If ini_get is disabled, try to parse php.ini
if (function_exists('php_ini_loaded_file') && function_exists('parse_ini_file')) {
$ini_path = php_ini_loaded_file();
if (is_file($ini_path) && is_readable($ini_path)) {
$parse_ini = parse_ini_file($ini_path);
if ($unsafe === true && isset($parse_ini["error_log"]) && file_exists($parse_ini["error_log"]) && is_file($parse_ini["error_log"])) {
return $parse_ini["error_log"];
}
if (isset($parse_ini["error_log"]) && file_exists($parse_ini["error_log"]) && is_readable($parse_ini["error_log"])) {
return $parse_ini["error_log"];
}
}
}
// PHP.ini fail or not contain informations what we need. Let's look on few places
$possible_places = array(
// Look into root
duplicator_pro_get_home_path(),
// Look out of root
dirname(duplicator_pro_get_home_path()),
//Other places
'/etc/httpd/logs',
'/var/log/apache2',
'/var/log/httpd',
'/var/log',
'/var/www/html',
'/var/www',
// Some wierd cases
duplicator_pro_get_home_path() . '/logs',
duplicator_pro_get_home_path() . '/log',
dirname(duplicator_pro_get_home_path()) . '/logs',
dirname(duplicator_pro_get_home_path()) . '/log',
'/etc/httpd/log',
'/var/logs/apache2',
'/var/logs/httpd',
'/var/logs',
'/var/www/html/logs',
'/var/www/html/log',
'/var/www/logs',
'/var/www/log',
);
$possible_filenames = array(
'error.log',
'error_log',
'php_error',
'php5-fpm.log',
'error_log.txt',
'php_error.txt',
);
foreach ($possible_filenames as $filename) {
foreach ($possible_places as $possibility) {
$possibility = $possibility . '/' . $filename;
if ($unsafe === true && file_exists($possibility) && is_file($possibility)) {
return $possibility;
} elseif (is_file($possibility) && is_readable($possibility)) {
return $possibility;
}
}
}
return false;
}
}

View File

@@ -0,0 +1,153 @@
<?php
defined("ABSPATH") or die("");
use Duplicator\Libs\Snap\SnapOS;
use Duplicator\Libs\Snap\SnapUtil;
/**
* Runs a recursive scan on a directory and finds symlinks and unreadable files
* and returns the results as an array
*
* @package DupicatorPro\classes
*/
class DUP_PRO_ScanValidator
{
/** @var string[] Paths to scan */
public $scanPaths = array();
/** @var int The number of files scanned */
public $fileCount = 0;
/** @var int The number of directories scanned*/
public $dirCount = 0;
/** @var int The maximum count of files before the recursive function stops */
public $maxFiles = 1000000;
/** @var int The maximum count of directories before the recursive function stops */
public $maxDirs = 75000;
/** @var bool Recursively scan the root directory provided */
public $recursion = true;
/** @var string[] Stores a list of symbolic link files */
public $symLinks = array();
/** @var string[] Stores a list of files unreadable by PHP */
public $unreadable = array();
/** @var string[] Stores a list of directories with UTF8 settings */
public $nameTestDirs = array();
/** @var string[] Stores a list of files with utf8 settings */
public $nameTestFiles = array();
/** @var bool If the maxFiles or maxDirs limit is reached then true */
protected $limitReached = false;
/**
* Class constructor
*/
public function __construct()
{
}
/**
* Run the scan
*
* @param string[] $scanPaths An array of paths to scan
*
* @return self The scan check object with the results of the scan
*/
public function run($scanPaths)
{
$this->scanPaths = $scanPaths;
foreach ($this->scanPaths as $path) {
$this->recursiveScan($path);
}
return $this;
}
/**
* Start the scan process
*
* @param string $dir A valid directory path where the scan will run
* @param string[] $results Used for recursion, do not pass in value with calling
*
* @return self The scan check object with the results of the scan
*/
private function recursiveScan($dir, &$results = array())
{
//Stop Recursion if Max search is reached
if ($this->fileCount > $this->maxFiles || $this->dirCount > $this->maxDirs) {
$this->limitReached = true;
return $this;
}
$files = array_diff(@scandir($dir), array('..', '.'));
if (is_array($files)) {
foreach ($files as $key => $value) {
$path = $dir . '/' . $value;
//Files
if (!is_dir($path)) {
if (!is_readable($path)) {
$results[] = $path;
$this->unreadable[] = $path;
} elseif ($this->isLink($path)) {
$results[] = $path;
$this->symLinks[] = $path;
} else {
$name = basename($path);
$invalid_test = preg_match('/(\/|\*|\?|\>|\<|\:|\\|\|)/', $name) || trim($name) == '' || (strrpos($name, '.') == strlen($name) - 1 && substr($name, -1) == '.') || preg_match('/[^\x20-\x7f]/', $name);
if ($invalid_test) {
if (!SnapUtil::isPHP7Plus() && SnapOS::isWindows()) {
$this->nameTestFiles[] = utf8_decode($path);
} else {
$this->nameTestFiles[] = $path;
}
}
}
$this->fileCount++;
} elseif ($value != "." && $value != "..") {
//Dirs
if (!$this->isLink($path) && $this->recursion) {
$this->recursiveScan($path, $results);
}
if (!is_readable($path)) {
$results[] = $path;
$this->unreadable[] = $path;
} elseif ($this->isLink($path)) {
$results[] = $path;
$this->symLinks[] = $path;
} else {
$invalid_test = strlen($path) > 244 || trim($path) == '' || preg_match('/[^\x20-\x7f]/', $path);
if ($invalid_test) {
if (!SnapUtil::isPHP7Plus() && SnapOS::isWindows()) {
$this->nameTestDirs[] = utf8_decode($path);
} else {
$this->nameTestDirs[] = $path;
}
}
}
$this->dirCount++;
}
}
}
return $this;
}
/**
* Separation logic for supporting how different operating systems work
*
* @param string $target A valid file path
*
* @return bool Is the target a sym link
*/
private function isLink($target)
{
//Currently Windows does not support sym-link detection
if (SnapOS::isWindows()) {
return false;
} elseif (is_link($target)) {
return true;
}
return false;
}
}

View File

@@ -0,0 +1,561 @@
<?php
defined("ABSPATH") or die("");
use Duplicator\Addons\DropboxAddon\Models\DropboxStorage;
use Duplicator\Addons\ProBase\License\License;
use Duplicator\Core\MigrationMng;
use Duplicator\Libs\Snap\FunctionalityCheck;
use Duplicator\Libs\Snap\SnapUtil;
use Duplicator\Models\DynamicGlobalEntity;
use Duplicator\Models\Storages\StoragesUtil;
/**
* Class used to get server info
*
* @package Duplicator\classes
*/
class DUP_PRO_Server
{
/**
* Is URL Fopen enabled
*
* @return bool
*/
public static function isURLFopenEnabled()
{
$val = ini_get('allow_url_fopen');
return ($val == true);
}
/**
* MySQL escape test
*
* @return bool
*/
public static function mysqlEscapeIsOk()
{
$escape_test_string = chr(0) . chr(26) . "\r\n'\"\\";
$escape_expected_result = "\"\\0\Z\\r\\n\\'\\\"\\\\\"";
$escape_actual_result = DUP_PRO_DB::escValueToQueryString($escape_test_string);
$result = $escape_expected_result === $escape_actual_result;
if (!$result) {
$msg = "mysqli_real_escape_string test results\n" .
"Expected escape result: " . $escape_expected_result . "\n" .
"Actual escape result: " . $escape_actual_result;
DUP_PRO_Log::trace($msg);
}
return $result;
}
/**
* Gets string representation of outbound IP address
*
* @return bool|string Outbound IP Address or false on error
*/
public static function getOutboundIP()
{
$context = stream_context_create(array(
'http' =>
array('timeout' => 15),
));
$outboundIP = @file_get_contents('https://checkip.amazonaws.com', false, $context);
if ($outboundIP !== false) {
// Make sure it's a properly formatted IP address
if (preg_match('/^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/', $outboundIP) !== 1) {
$outboundIP = false;
}
}
return $outboundIP;
}
/**
* Gets the system requirements which must pass to build a package
*
* @return array<string, mixed> An array of requirements
*/
public static function getRequirments()
{
$dup_tests = array();
StoragesUtil::getDefaultStorage()->initStorageDirectory(true);
//PHP SUPPORT
$dup_tests['PHP']['SAFE_MODE'] = 'Pass'; /** @todo remove safe mode check, not used after php 5.4 */
self::logRequirementFail($dup_tests['PHP']['SAFE_MODE'], 'SAFE_MODE is on.');
$phpversion = phpversion();
$dup_tests['PHP']['VERSION'] = version_compare($phpversion, '5.2.9') >= 0 ? 'Pass' : 'Fail';
self::logRequirementFail($dup_tests['PHP']['SAFE_MODE'], 'PHP version(' . $phpversion . ') is lower than 5.2.9');
$allRequiredPass = FunctionalityCheck::checkList(self::getFunctionalitiesCheckList(), true, $noPassFuncs);
foreach ($noPassFuncs as $func) {
switch ($func->getType()) {
case FunctionalityCheck::TYPE_FUNCTION:
$errorMessage = $func->getItemKey() . " function doesn't exist.";
break;
case FunctionalityCheck::TYPE_CLASS:
$errorMessage = $func->getItemKey() . " class doesn't exist.";
break;
default:
throw new Exception('Invalid item type');
}
// We will log even if non-required functionalities fail
self::logRequirementFail('Fail', $errorMessage);
}
$dup_tests['PHP']['ALL'] = !in_array('Fail', $dup_tests['PHP']) && $allRequiredPass ? 'Pass' : 'Fail';
//PERMISSIONS
$home_path = DUP_PRO_Archive::getArchiveListPaths('home');
if (strlen($home_path) === 0) {
$home_path = DIRECTORY_SEPARATOR;
}
if (($handle_test = @opendir($home_path)) === false) {
$dup_tests['IO']['WPROOT'] = 'Fail';
} else {
@closedir($handle_test);
$dup_tests['IO']['WPROOT'] = 'Pass';
}
self::logRequirementFail($dup_tests['IO']['WPROOT'], $home_path . ' (home path) can\'t be opened.');
$dup_tests['IO']['SSDIR'] = is_writeable(DUPLICATOR_PRO_SSDIR_PATH) ? 'Pass' : 'Fail';
self::logRequirementFail($dup_tests['IO']['SSDIR'], DUPLICATOR_PRO_SSDIR_PATH . ' (DUPLICATOR_PRO_SSDIR_PATH) can\'t be writeable.');
$dup_tests['IO']['SSTMP'] = is_writeable(DUPLICATOR_PRO_SSDIR_PATH_TMP) ? 'Pass' : 'Fail';
self::logRequirementFail($dup_tests['IO']['SSTMP'], DUPLICATOR_PRO_SSDIR_PATH_TMP . ' (DUPLICATOR_PRO_SSDIR_PATH_TMP) can\'t be writeable.');
$dup_tests['IO']['ALL'] = !in_array('Fail', $dup_tests['IO']) ? 'Pass' : 'Fail';
//SERVER SUPPORT
$db_version = DUP_PRO_DB::getVersion();
$dup_tests['SRV']['MYSQL_VER'] = version_compare($db_version, '5.0', '>=') ? 'Pass' : 'Fail';
self::logRequirementFail($dup_tests['SRV']['MYSQL_VER'], 'MySQL version ' . $db_version . ' is lower than 5.0.');
//mysqli_real_escape_string test
$dup_tests['SRV']['MYSQL_ESC'] = self::mysqlEscapeIsOk() ? 'Pass' : 'Fail';
self::logRequirementFail($dup_tests['SRV']['MYSQL_ESC'], "The function mysqli_real_escape_string is not escaping strings as expected.");
$dup_tests['SRV']['ALL'] = !in_array('Fail', $dup_tests['SRV']) ? 'Pass' : 'Fail';
//INSTALLATION FILES
$dup_tests['RES']['INSTALL'] = !(self::hasInstallFiles()) ? 'Pass' : 'Fail';
self::logRequirementFail($dup_tests['RES']['INSTALL'], 'Installer file(s) are exist on the server.');
$dup_tests['Success'] = $dup_tests['PHP']['ALL'] == 'Pass' && $dup_tests['IO']['ALL'] == 'Pass' &&
$dup_tests['SRV']['ALL'] == 'Pass' && $dup_tests['RES']['INSTALL'] == 'Pass';
return $dup_tests;
}
/**
* Cet list of functionalities to check
*
* @return FunctionalityCheck[]
*/
public static function getFunctionalitiesCheckList()
{
$global = DUP_PRO_Global_Entity::getInstance();
$result = [];
if ($global->getBuildMode() == DUP_PRO_Archive_Build_Mode::ZipArchive) {
$result[] = new FunctionalityCheck(
FunctionalityCheck::TYPE_CLASS,
\ZipArchive::class,
true,
'https://www.php.net/manual/en/class.ziparchive.php',
'<i style="font-size:12px">'
. '<a href="' . DUPLICATOR_PRO_DUPLICATOR_DOCS_URL . 'how-to-work-with-the-different-zip-engines" target="_blank">'
. esc_html__('Overview on how to enable ZipArchive', 'duplicator-pro') . '</i></a>'
);
}
$result[] = new FunctionalityCheck(
FunctionalityCheck::TYPE_FUNCTION,
'json_encode',
true,
'https://www.php.net/manual/en/function.json-encode.php'
);
$result[] = new FunctionalityCheck(
FunctionalityCheck::TYPE_FUNCTION,
'token_get_all',
true,
'https://www.php.net/manual/en/function.token-get-all'
);
$result[] = new FunctionalityCheck(
FunctionalityCheck::TYPE_FUNCTION,
'file_get_contents',
true,
'https://www.php.net/manual/en/function.file-get-contents.php'
);
$result[] = new FunctionalityCheck(
FunctionalityCheck::TYPE_FUNCTION,
'file_put_contents',
true,
'https://www.php.net/manual/en/function.file-put-contents.php'
);
$result[] = new FunctionalityCheck(
FunctionalityCheck::TYPE_FUNCTION,
'mb_strlen',
true,
'https://www.php.net/manual/en/mbstring.installation.php'
);
return $result;
}
/**
* Logs requirement fail status informative message
*
* @param string $testStatus Either it is Pass or Fail
* @param string $errorMessage Error message which should be logged
*
* @return void
*/
private static function logRequirementFail($testStatus, $errorMessage)
{
if (empty($testStatus)) {
throw new Exception('Exception: Empty $testStatus [File: ' . __FILE__ . ', Ln: ' . __LINE__);
}
if (empty($errorMessage)) {
throw new Exception('Exception: Empty $errorMessage [File: ' . __FILE__ . ', Ln: ' . __LINE__);
}
$validTestStatuses = array(
'Pass',
'Fail',
);
if (!in_array($testStatus, $validTestStatuses)) {
throw new Exception('Exception: Invalid $testStatus value: ' . $testStatus . ' [File: ' . __FILE__ . ', Ln: ' . __LINE__);
}
if ('Fail' == $testStatus) {
DUP_PRO_Log::trace($errorMessage);
}
}
/**
* Gets the system checks which are not required
*
* @param DUP_PRO_Package $package The package to check
*
* @return array<string,mixed> An array of system checks
*/
public static function getChecks($package)
{
$global = DUP_PRO_Global_Entity::getInstance();
$checks = array();
//-----------------------------
//PHP SETTINGS
$testWebSrv = false;
foreach ($GLOBALS['DUPLICATOR_PRO_SERVER_LIST'] as $value) {
if (stristr($_SERVER['SERVER_SOFTWARE'], $value)) {
$testWebSrv = true;
break;
}
}
self::logCheckFalse($testWebSrv, 'Any out of server software (' . implode(', ', $GLOBALS['DUPLICATOR_PRO_SERVER_LIST']) . ') doesn\'t exist.');
$testOpenBaseDir = ini_get("open_basedir");
$testOpenBaseDir = empty($testOpenBaseDir) ? true : false;
self::logCheckFalse($testOpenBaseDir, 'open_basedir is enabled.');
$max_execution_time = ini_get("max_execution_time");
$testMaxExecTime = ($max_execution_time > DUPLICATOR_PRO_SCAN_TIMEOUT) || (strcmp($max_execution_time, 'Off') == 0 || $max_execution_time == 0) ? true : false;
if (strcmp($max_execution_time, 'Off') == 0) {
$max_execution_time_error_message = 'max_execution_time should not be' . $max_execution_time;
} else {
$max_execution_time_error_message = 'max_execution_time (' . $max_execution_time . ') should not be lower than the DUPLICATOR_PRO_SCAN_TIMEOUT' . DUPLICATOR_PRO_SCAN_TIMEOUT;
}
self::logCheckFalse($testMaxExecTime, $max_execution_time_error_message);
$testDropbox = true;
if ($package->contains_storage_type(DropboxStorage::getSType())) {
$testDropbox = function_exists('openssl_csr_new');
self::logCheckFalse($testDropbox, 'openssl_csr_new function doesn\'t exist and package storage have DropBox storage.');
}
$testMySqlConnect = function_exists('mysqli_connect');
self::logCheckFalse($testMySqlConnect, 'mysqli_connect function doesn\'t exist.');
$testURLFopen = self::isURLFopenEnabled();
self::logCheckFalse($testURLFopen, 'URL Fopen isn\'t enabled.');
$testCURL = SnapUtil::isCurlEnabled();
self::logCheckFalse($testCURL, 'curl_init function doesn\'t exist.');
$test64Bit = strstr(SnapUtil::getArchitectureString(), '64') ? true : false ;
self::logCheckFalse($test64Bit, 'This servers PHP architecture is NOT 64-bit. Packages over 2GB are not possible.');
$testMemory = self::hasEnoughMemory();
self::logCheckFalse($testCURL, 'memory_limit is less than DUPLICATOR_PRO_MIN_MEMORY_LIMIT: ' . DUPLICATOR_PRO_MIN_MEMORY_LIMIT);
$checks['SRV']['Brand'] = DUP_PRO_Package::is_active_brand_prepared();
$checks['SRV']['HOST'] = DUP_PRO_Custom_Host_Manager::getInstance()->getActiveHostings();
$checks['SRV']['PHP']['websrv'] = $testWebSrv;
$checks['SRV']['PHP']['openbase'] = $testOpenBaseDir;
$checks['SRV']['PHP']['maxtime'] = $testMaxExecTime;
$checks['SRV']['PHP']['openssl'] = $testDropbox;
$checks['SRV']['PHP']['mysqli'] = $testMySqlConnect;
$checks['SRV']['PHP']['allowurlfopen'] = $testURLFopen;
$checks['SRV']['PHP']['curlavailable'] = $testCURL;
$checks['SRV']['PHP']['arch64bit'] = $test64Bit;
$checks['SRV']['PHP']['minMemory'] = $testMemory;
$checks['SRV']['PHP']['version'] = true; // now the plugin is activated only if the minimum version is valid, so this check is always true
if ($package->contains_storage_type(DropboxStorage::getSType())) {
$dropbox_transfer_test = true;
$transferMode = DynamicGlobalEntity::getInstance()->getVal('dropbox_transfer_mode');
if ($transferMode == DUP_PRO_Dropbox_Transfer_Mode::cURL) {
$dropbox_transfer_test = $testCURL;
self::logCheckFalse($dropbox_transfer_test, 'DropBox transfer mode is CURL and curl_init function doesn\'t exist.');
} elseif ($transferMode == DUP_PRO_Dropbox_Transfer_Mode::FOpen_URL) {
$dropbox_transfer_test = $testURLFopen;
self::logCheckFalse($dropbox_transfer_test, 'DropBox transfer mode is Fopen URL and Fopen URL is not enabled.');
}
$checks['SRV']['PHP']['ALL'] = ($testWebSrv && $testOpenBaseDir && $testMaxExecTime && $testDropbox && $testMySqlConnect && $test64Bit &&
$testMemory && $dropbox_transfer_test && $checks['SRV']['Brand']['LogoImageExists']) ? 'Good' : 'Warn';
} else {
$checks['SRV']['PHP']['ALL'] = ($testWebSrv && $testOpenBaseDir && $testMaxExecTime && $testMySqlConnect && $test64Bit &&
$testMemory && $checks['SRV']['Brand']['LogoImageExists']) ? 'Good' : 'Warn';
}
//-----------------------------
//WORDPRESS SETTINGS
global $wp_version;
$testMinWpVersion = version_compare($wp_version, DUPLICATOR_PRO_SCAN_MIN_WP) >= 0 ? true : false;
self::logCheckFalse($testMinWpVersion, 'WP version (' . $wp_version . ') is lower than the DUPLICATOR_PRO_SCAN_MIN_WP (' . DUPLICATOR_PRO_SCAN_MIN_WP . ').');
//Core dir and files logic
$testHasWpCoreFiltered = !$package->Archive->hasWpCoreFolderFiltered();
$testIsMultisite = is_multisite();
$checks['SRV']['WP']['version'] = $testMinWpVersion;
$checks['SRV']['WP']['core'] = $testHasWpCoreFiltered;
// $checks['SRV']['WP']['cache'] = $testCache;
$checks['SRV']['WP']['ismu'] = $testIsMultisite;
$checks['SRV']['WP']['ismuplus'] = License::can(License::CAPABILITY_MULTISITE_PLUS);
if ($testIsMultisite) {
// $checks['SRV']['WP']['ALL'] = ($wp_test1 && $wp_test2 && $testCache && $wp_test5) ? 'Good' : 'Warn';
$checks['SRV']['WP']['ALL'] = ($testMinWpVersion && $testHasWpCoreFiltered && $checks['SRV']['WP']['ismuplus']) ? 'Good' : 'Warn';
self::logCheckFalse($checks['SRV']['WP']['ismuplus'], 'WP is multi-site setup and licence type is not Business Gold.');
} else {
// $checks['SRV']['WP']['ALL'] = ($wp_test1 && $wp_test2 && $testCache) ? 'Good' : 'Warn';
$checks['SRV']['WP']['ALL'] = ($testMinWpVersion && $testHasWpCoreFiltered) ? 'Good' : 'Warn';
}
return $checks;
}
/**
* Logs checks false informative message
*
* @param boolean $check Either it is true or false
* @param string $errorMessage Error message which should be logged when check is false
*
* @return void
*/
private static function logCheckFalse($check, $errorMessage)
{
if (!is_bool($check)) {
throw new Exception('Exception: Not boolean $check [File: ' . __FILE__ . ', Ln: ' . __LINE__);
}
if (empty($errorMessage)) {
throw new Exception('Exception: Empty $errorMessage [File: ' . __FILE__ . ', Ln: ' . __LINE__);
}
if (false === $check) {
DUP_PRO_Log::trace($errorMessage);
}
}
/**
* Return true if memory_limit is >= 256 MB, otherwise false
*
* @return bool
*/
public static function hasEnoughMemory()
{
//In case we can't get the ini value, assume it's ok
if (($memory_limit = @ini_get('memory_limit')) === false || empty($memory_limit)) {
return true;
}
if (SnapUtil::convertToBytes($memory_limit) >= SnapUtil::convertToBytes(DUPLICATOR_PRO_MIN_MEMORY_LIMIT)) {
return true;
}
return false;
}
/**
* Check to see if duplicator installation files are present
*
* @return bool True if any installation files are found
*/
public static function hasInstallFiles()
{
$fileToRemove = MigrationMng::checkInstallerFilesList();
return count($fileToRemove) > 0;
}
/**
* Returns an array with stats about the orphaned files
*
* @return string[] The full path of the orphaned file
*/
public static function getOrphanedPackageFiles()
{
$global = DUP_PRO_Global_Entity::getInstance();
$orphans = array();
$endPackagesFile = array(
'archive.daf',
'archive.zip',
'database.sql',
'dirs.txt',
'files.txt',
'log.txt',
'scan.json',
);
$endPackagesFile[] = $global->installer_base_name;
for ($i = 0; $i < count($endPackagesFile); $i++) {
$endPackagesFile[$i] = preg_quote($endPackagesFile[$i], '/');
}
$regexMatch = '/(' . implode('|', $endPackagesFile) . ')$/';
$numPackages = DUP_PRO_Package::count_by_status();
$numPerPage = 100;
$pages = floor($numPackages / $numPerPage) + 1;
$skipStart = array('dup_pro');
for ($page = 0; $page < $pages; $page++) {
$offset = $page * $numPerPage;
$pagePackages = DUP_PRO_Package::get_row_by_status(array(), $numPerPage, $offset);
foreach ($pagePackages as $cPack) {
$skipStart[] = $cPack->name . '_' . $cPack->hash;
}
}
$pagePackages = null;
$fileTimeSkipInSec = (max(DUP_PRO_Constants::DEFAULT_MAX_PACKAGE_RUNTIME_IN_MIN, $global->max_package_runtime_in_min) + DUP_PRO_Constants::ORPAHN_CLEANUP_DELAY_MAX_PACKAGE_RUNTIME) * 60;
if (file_exists(DUPLICATOR_PRO_SSDIR_PATH) && ($handle = opendir(DUPLICATOR_PRO_SSDIR_PATH)) !== false) {
while (false !== ($fileName = readdir($handle))) {
if ($fileName == '.' || $fileName == '..') {
continue;
}
$fileFullPath = DUPLICATOR_PRO_SSDIR_PATH . '/' . $fileName;
if (is_dir($fileFullPath)) {
continue;
}
if (time() - filemtime($fileFullPath) < $fileTimeSkipInSec) {
// file younger than 2 hours skip for security
continue;
}
if (!preg_match($regexMatch, $fileName)) {
continue;
}
foreach ($skipStart as $skip) {
if (strpos($fileName, $skip) === 0) {
continue 2;
}
}
$orphans[] = $fileFullPath;
}
closedir($handle);
}
return $orphans;
}
/**
* Returns an array with stats about the orphaned files
*
* @return array{size:int,count:int} The total count and file size of orphaned files
*/
public static function getOrphanedPackageInfo()
{
$files = self::getOrphanedPackageFiles();
$info = array();
$info['size'] = 0;
$info['count'] = 0;
if (count($files)) {
foreach ($files as $path) {
$get_size = @filesize($path);
if ($get_size > 0) {
$info['size'] += $get_size;
$info['count']++;
}
}
}
return $info;
}
/**
* Get the IP of a client machine
*
* @return string IP of the client machine
*/
public static function getClientIP()
{
if (array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER)) {
return $_SERVER["HTTP_X_FORWARDED_FOR"];
} elseif (array_key_exists('REMOTE_ADDR', $_SERVER)) {
return $_SERVER["REMOTE_ADDR"];
} elseif (array_key_exists('HTTP_CLIENT_IP', $_SERVER)) {
return $_SERVER["HTTP_CLIENT_IP"];
}
return '';
}
/**
* Get PHP memory usage
*
* @param bool $peak If true, returns peak memory usage
*
* @return string Returns human readable memory usage.
*/
public static function getPHPMemory($peak = false)
{
if ($peak) {
$result = 'Unable to read PHP peak memory usage';
if (function_exists('memory_get_peak_usage')) {
$result = DUP_PRO_U::byteSize(memory_get_peak_usage(true));
}
} else {
$result = 'Unable to read PHP memory usage';
if (function_exists('memory_get_usage')) {
$result = DUP_PRO_U::byteSize(memory_get_usage(true));
}
}
return $result;
}
/**
* Gets the name of the owner of the current PHP script
*
* @return string The name of the owner of the current PHP script
*/
public static function getCurrentUser()
{
$unreadable = 'Undetectable';
if (function_exists('get_current_user') && is_callable('get_current_user')) {
$user = get_current_user();
return strlen($user) ? $user : $unreadable;
}
return $unreadable;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,536 @@
<?php
/**
* @package Duplicator
* @copyright (c) 2022, Snap Creek LLC
*/
use VendorDuplicator\Amk\JsonSerialize\JsonSerialize;
use Duplicator\Core\Models\AbstractEntityList;
use Duplicator\Core\Models\UpdateFromInputInterface;
use Duplicator\Installer\Package\ArchiveDescriptor;
use Duplicator\Libs\Snap\SnapUtil;
use Duplicator\Package\Create\BuildComponents;
use Duplicator\Package\Recovery\RecoveryStatus;
use Duplicator\Utils\Crypt\CryptBlowfish;
use Duplicator\Utils\Settings\ModelMigrateSettingsInterface;
class DUP_PRO_Package_Template_Entity extends AbstractEntityList implements UpdateFromInputInterface, ModelMigrateSettingsInterface
{
/** @var string */
public $name = '';
/** @var string */
public $notes = '';
//MULTISITE:Filter
/** @var int[] */
public $filter_sites = array();
//ARCHIVE:Files
/** @var bool */
public $archive_export_onlydb = false;
/** @var bool */
public $archive_filter_on = false;
/** @var string */
public $archive_filter_dirs = '';
/** @var string */
public $archive_filter_exts = '';
/** @var string */
public $archive_filter_files = '';
/** @var bool */
public $archive_filter_names = false;
/** @var string[] */
public $components = array();
//ARCHIVE:Database
/** @var bool */
public $database_filter_on = false; // Enable Table Filters
/** @var bool */
public $databasePrefixFilter = false; // If true exclude tables without prefix
/** @var bool */
public $databasePrefixSubFilter = false; // If true exclude unexisting subsite id tables
/** @var string */
public $database_filter_tables = ''; // List of filtered tables
/** @var string */
public $database_compatibility_modes = ''; // Older style sql compatibility
//INSTALLER
//Setup
/** @var int */
public $installer_opts_secure_on = 0; // Enable Password Protection
/** @var string */
public $installer_opts_secure_pass = ''; // Old password Protection password, deprecated
/** @var string */
public $installerPassowrd = ''; // Password Protection password
/** @var bool */
public $installer_opts_skip_scan = false; // Skip Scanner
//Basic DB
/** @var string */
public $installer_opts_db_host = ''; // MySQL Server Host
/** @var string */
public $installer_opts_db_name = ''; // Database
/** @var string */
public $installer_opts_db_user = ''; // User
//cPanel Login
/** @var bool */
public $installer_opts_cpnl_enable = false;
/** @var string */
public $installer_opts_cpnl_host = '';
/** @var string */
public $installer_opts_cpnl_user = '';
/** @var string */
public $installer_opts_cpnl_pass = '';
//cPanel DB
/** @var string */
public $installer_opts_cpnl_db_action = 'create';
/** @var string */
public $installer_opts_cpnl_db_host = '';
/** @var string */
public $installer_opts_cpnl_db_name = '';
/** @var string */
public $installer_opts_cpnl_db_user = '';
//Brand
/** @var int */
public $installer_opts_brand = -2;
/** @var bool */
public $is_default = false;
/** @var bool */
public $is_manual = false;
/**
* Class constructor
*/
public function __construct()
{
$this->name = __('New Template', 'duplicator-pro');
$this->components = BuildComponents::COMPONENTS_DEFAULT;
}
/**
* Entity type
*
* @return string
*/
public static function getType()
{
return 'DUP_PRO_Package_Template_Entity';
}
/**
* Will be called, automatically, when Serialize
*
* @return array<string, mixed>
*/
public function __serialize() // phpcs:ignore PHPCompatibility.FunctionNameRestrictions.NewMagicMethods.__serializeFound
{
$data = JsonSerialize::serializeToData($this, JsonSerialize::JSON_SKIP_MAGIC_METHODS | JsonSerialize::JSON_SKIP_CLASS_NAME);
$data['installer_opts_secure_pass'] = '';
$data['installerPassowrd'] = CryptBlowfish::encrypt($this->installerPassowrd, null, true);
return $data;
}
/**
* Serialize
*
* Wakeup method.
*
* @return void
*/
public function __wakeup()
{
/*if ($obj->installer_opts_secure_on == ArchiveDescriptor::SECURE_MODE_ARC_ENCRYPT && !SettingsUtils::isArchiveEncryptionAvailable()) {
$obj->installer_opts_secure_on = ArchiveDescriptor::SECURE_MODE_INST_PWD;
}*/
if (strlen($this->installer_opts_secure_pass) > 0) {
$this->installerPassowrd = base64_decode($this->installer_opts_secure_pass);
} elseif (strlen($this->installerPassowrd) > 0) {
$this->installerPassowrd = CryptBlowfish::decrypt($this->installerPassowrd, null, true);
}
if (is_array($this->database_compatibility_modes)) {
// for old version compatibility
$this->database_compatibility_modes = implode(',', $this->database_compatibility_modes);
}
$this->installer_opts_secure_pass = '';
$this->archive_filter_dirs = (string) $this->archive_filter_dirs;
$this->archive_filter_files = (string) $this->archive_filter_files;
$this->archive_filter_exts = (string) $this->archive_filter_exts;
$this->archive_filter_on = filter_var($this->archive_filter_on, FILTER_VALIDATE_BOOLEAN);
$this->database_filter_on = filter_var($this->database_filter_on, FILTER_VALIDATE_BOOLEAN);
if (is_string($this->filter_sites)) {
$this->filter_sites = [];
}
}
/**
* To export data
*
* @return array<string, mixed>
*/
public function settingsExport()
{
return JsonSerialize::serializeToData($this, JsonSerialize::JSON_SKIP_MAGIC_METHODS | JsonSerialize::JSON_SKIP_CLASS_NAME);
}
/**
* Update object properties from import data
*
* @param array<string, mixed> $data data to import
* @param string $dataVersion version of data
* @param array<string, mixed> $extraData extra data, useful form id mapping etc.
*
* @return bool True if success, otherwise false
*/
public function settingsImport($data, $dataVersion, array $extraData = [])
{
$skipProps = ['id'];
$reflect = new ReflectionClass(self::class);
$props = $reflect->getProperties();
foreach ($props as $prop) {
if (in_array($prop->getName(), $skipProps)) {
continue;
}
if (!isset($data[$prop->getName()])) {
continue;
}
$prop->setAccessible(true);
$prop->setValue($this, $data[$prop->getName()]);
}
if (!isset($data['components'])) {
// Allow import of older templsates that did not have package components
if ($this->archive_export_onlydb) {
$this->components = [BuildComponents::COMP_DB];
} else {
$this->components = BuildComponents::COMPONENTS_DEFAULT;
}
}
return true;
}
/**
* Create default template
*
* @return void
*/
public static function create_default()
{
if (self::get_default_template() == null) {
$template = new self();
$template->name = __('Default', 'duplicator-pro');
$template->notes = __('The default template.', 'duplicator-pro');
$template->is_default = true;
$template->save();
DUP_PRO_Log::trace('Created default template');
} else {
// Update it
DUP_PRO_Log::trace('Default template already exists so not creating');
}
}
/**
* Create manual mode template
*
* @return void
*/
public static function create_manual()
{
if (self::get_manual_template() == null) {
$template = new self();
$template->name = __('[Manual Mode]', 'duplicator-pro');
$template->notes = '';
$template->is_manual = true;
// Copy over the old temporary template settings into this - required for legacy manual
$temp_package = DUP_PRO_Package::get_temporary_package(false);
if ($temp_package != null) {
DUP_PRO_Log::trace('SET TEMPLATE FROM TEMP PACKAGE pwd ' . $temp_package->Installer->passowrd);
$template->components = $temp_package->components;
$template->filter_sites = $temp_package->Multisite->FilterSites;
$template->archive_filter_on = $temp_package->Archive->FilterOn;
$template->archive_filter_dirs = $temp_package->Archive->FilterDirs;
$template->archive_filter_exts = $temp_package->Archive->FilterExts;
$template->archive_filter_files = $temp_package->Archive->FilterFiles;
$template->archive_filter_names = $temp_package->Archive->FilterNames;
$template->installer_opts_brand = $temp_package->Brand_ID;
$template->database_filter_on = $temp_package->Database->FilterOn;
$template->databasePrefixFilter = $temp_package->Database->prefixFilter;
$template->databasePrefixSubFilter = $temp_package->Database->prefixSubFilter;
$template->database_filter_tables = $temp_package->Database->FilterTables;
$template->database_compatibility_modes = $temp_package->Database->Compatible;
$template->installer_opts_db_host = $temp_package->Installer->OptsDBHost;
$template->installer_opts_db_name = $temp_package->Installer->OptsDBName;
$template->installer_opts_db_user = $temp_package->Installer->OptsDBUser;
$template->installer_opts_secure_on = $temp_package->Installer->OptsSecureOn;
$template->installerPassowrd = $temp_package->Installer->passowrd;
$template->installer_opts_skip_scan = $temp_package->Installer->OptsSkipScan;
$global = DUP_PRO_Global_Entity::getInstance();
$storageIds = [];
foreach ($temp_package->get_storages() as $storage) {
$storageIds[] = $storage->getId();
}
$global->setManualModeStorageIds($storageIds);
$global->save();
}
$template->save();
DUP_PRO_Log::trace('Created manual mode template');
} else {
// Update it
DUP_PRO_Log::trace('Manual mode template already exists so not creating');
}
}
/**
*
* @return bool
*/
public function isRecoveable()
{
$status = new RecoveryStatus($this);
return $status->isRecoveable();
}
/**
* Display HTML info
*
* @param bool $isList is list
*
* @return void
*/
public function recoveableHtmlInfo($isList = false)
{
$template = $this;
require DUPLICATOR____PATH . '/views/tools/templates/widget/recoveable-template-info.php';
}
/**
* Set data from query input
*
* @param int $type One of INPUT_GET, INPUT_POST, INPUT_COOKIE, INPUT_SERVER, or INPUT_ENV, SnapUtil::INPUT_REQUEST
*
* @return bool true on success or false on failure
*/
public function setFromInput($type)
{
$input = SnapUtil::getInputFromType($type);
$this->setFromArrayKey(
$input,
function ($key, $val) {
if (is_string($val)) {
$val = stripslashes($val);
}
return (is_scalar($val) ? SnapUtil::sanitizeNSChars($val) : $val);
}
);
$this->components = BuildComponents::getFromInput($input);
$this->database_filter_tables = isset($input['dbtables-list']) ? SnapUtil::sanitizeNSCharsNewlineTrim($input['dbtables-list']) : '';
if (isset($input['filter-paths'])) {
$filterPaths = SnapUtil::sanitizeNSChars($input['filter-paths']);
$this->archive_filter_dirs = DUP_PRO_Archive::parseDirectoryFilter($filterPaths);
$this->archive_filter_files = DUP_PRO_Archive::parseFileFilter($filterPaths);
} else {
$this->archive_filter_dirs = '';
$this->archive_filter_files = '';
}
if (isset($input['filter-exts'])) {
$post_filter_exts = SnapUtil::sanitizeNSCharsNewlineTrim($input['filter-exts']);
$this->archive_filter_exts = DUP_PRO_Archive::parseExtensionFilter($post_filter_exts);
} else {
$this->archive_filter_exts = '';
}
$this->filter_sites = !empty($input['_mu_exclude']) ? $input['_mu_exclude'] : [];
//Archive
$this->archive_filter_on = isset($input['filter-on']);
$this->database_filter_on = isset($input['dbfilter-on']);
$this->databasePrefixFilter = isset($input['db-prefix-filter']);
$this->databasePrefixSubFilter = isset($input['db-prefix-sub-filter']);
$this->archive_filter_names = isset($input['archive_filter_names']);
//Installer
$this->installer_opts_secure_on = filter_input(INPUT_POST, 'secure-on', FILTER_VALIDATE_INT);
switch ($this->installer_opts_secure_on) {
case ArchiveDescriptor::SECURE_MODE_NONE:
case ArchiveDescriptor::SECURE_MODE_INST_PWD:
case ArchiveDescriptor::SECURE_MODE_ARC_ENCRYPT:
break;
default:
throw new Exception(__('Select valid secure mode', 'duplicator-pro'));
}
$this->installer_opts_skip_scan = isset($input['_installer_opts_skip_scan']);
$this->installer_opts_cpnl_enable = isset($input['installer_opts_cpnl_enable']);
$this->installerPassowrd = SnapUtil::sanitizeNSCharsNewline(stripslashes($input['secure-pass']));
$this->notes = SnapUtil::sanitizeNSCharsNewlineTrim(stripslashes($input['notes']));
return true;
}
/**
* Copy template from id
*
* @param int<0, max> $templateId template id
*
* @return void
*/
public function copy_from_source_id($templateId)
{
if (($source = self::getById($templateId)) === false) {
throw new Exception('Can\'t get tempalte id' . $templateId);
}
$skipProps = [
'id',
'is_manual',
'is_default',
];
$reflect = new ReflectionClass($this);
$props = $reflect->getProperties();
foreach ($props as $prop) {
if (in_array($prop->getName(), $skipProps)) {
continue;
}
$prop->setAccessible(true);
$prop->setValue($this, $prop->getValue($source));
}
$source_template_name = $source->is_manual ? __("Active Build Settings", 'duplicator-pro') : $source->name;
$this->name = sprintf(__('%1$s - Copy', 'duplicator-pro'), $source_template_name);
}
/**
* Gets a list of core WordPress folders that have been filtered
*
* @return string[] Returns and array of folders paths
*/
public function getWordPressCoreFilteredFoldersList()
{
return array_intersect(explode(';', $this->archive_filter_dirs), DUP_PRO_U::getWPCoreDirs());
}
/**
* Is any of the WordPress core folders in the folder filter list
*
* @return bool Returns true if a WordPress core path is being filtered
*/
public function isWordPressCoreFolderFiltered()
{
return count($this->getWordPressCoreFilteredFoldersList()) > 0;
}
/**
* Get all entities of current type
*
* @param int<0, max> $page current page, if $pageSize is 0 o 1 $pase is the offset
* @param int<0, max> $pageSize page size, 0 return all entities
* @param callable $sortCallback sort function on items result
* @param callable $filterCallback filter on items result
* @param array{'col': string, 'mode': string} $orderby query ordder by
*
* @return static[]|false return entities list of false on failure
*/
public static function getAll(
$page = 0,
$pageSize = 0,
$sortCallback = null,
$filterCallback = null,
$orderby = [
'col' => 'id',
'mode' => 'ASC',
]
) {
if (is_null($sortCallback)) {
$sortCallback = function (self $a, self $b) {
if ($a->is_default) {
return -1;
} elseif ($b->is_default) {
return 1;
} else {
return strcasecmp($a->name, $b->name);
}
};
}
return parent::getAll($page, $pageSize, $sortCallback, $filterCallback, $orderby);
}
/**
* Return list template json encoded data for javascript
*
* @return string
*/
public static function getTemplatesFrontendListData()
{
$templates = self::getAll();
return JsonSerialize::serialize($templates, JsonSerialize::JSON_SKIP_MAGIC_METHODS | JsonSerialize::JSON_SKIP_CLASS_NAME);
}
/**
* Get all entities of current type
*
* @param int<0, max> $page current page, if $pageSize is 0 o 1 $pase is the offset
* @param int<0, max> $pageSize page size, 0 return all entities
*
* @return static[]|false return entities list of false on failure
*/
public static function getAllWithoutManualMode(
$page = 0,
$pageSize = 0
) {
$filterManualCallback = function (self $obj) {
return ($obj->is_manual === false);
};
return self::getAll($page, $pageSize, null, $filterManualCallback);
}
/**
* Get default template if exists
*
* @return null|self
*/
public static function get_default_template()
{
$templates = self::getAll();
foreach ($templates as $template) {
if ($template->is_default) {
return $template;
}
}
return null;
}
/**
* Return manual template entity if exists
*
* @return null|self
*/
public static function get_manual_template()
{
$templates = self::getAll();
foreach ($templates as $template) {
if ($template->is_manual) {
return $template;
}
}
return null;
}
}

View File

@@ -0,0 +1,870 @@
<?php
/**
* @package Duplicator
* @copyright (c) 2022, Snap Creek LLC
*/
use Duplicator\Addons\ProBase\License\License;
use Duplicator\Core\Models\AbstractEntityList;
use Duplicator\Core\Models\UpdateFromInputInterface;
use Duplicator\Libs\Snap\SnapLog;
use Duplicator\Libs\Snap\SnapUtil;
use Duplicator\Libs\Snap\SnapWP;
use Duplicator\Models\BrandEntity;
use Duplicator\Models\Storages\StoragesUtil;
use Duplicator\Models\SystemGlobalEntity;
use Duplicator\Utils\Settings\ModelMigrateSettingsInterface;
use VendorDuplicator\Amk\JsonSerialize\JsonSerialize;
use VendorDuplicator\Cron\CronExpression;
/**
* Schedule entity
*/
class DUP_PRO_Schedule_Entity extends AbstractEntityList implements UpdateFromInputInterface, ModelMigrateSettingsInterface
{
const RUN_STATUS_SUCCESS = 0;
const RUN_STATUS_FAILURE = 1;
const REPEAT_DAILY = 0;
const REPEAT_WEEKLY = 1;
const REPEAT_MONTHLY = 2;
const REPEAT_HOURLY = 3;
const DAY_MONDAY = 0b0000001;
const DAY_TUESDAY = 0b0000010;
const DAY_WEDNESDAY = 0b0000100;
const DAY_THURSDAY = 0b0001000;
const DAY_FRIDAY = 0b0010000;
const DAY_SATURDAY = 0b0100000;
const DAY_SUNDAY = 0b1000000;
/** @var string */
public $name = '';
/** @var int<-1, max> */
public $template_id = -1;
/** @var int<-1, max> */
public $start_ticks = 0;
/** @var int<0, 3> */
public $repeat_type = self::REPEAT_WEEKLY;
/** @var bool */
public $active = true;
/** @var int<-1, max> */
public $next_run_time = -1;
/** @var int<1, max> */
public $run_every = 1;
/** @var int<0, max> bitmask */
public $weekly_days = 0;
/** @var int<1, max> */
public $day_of_month = 1;
/** @var string */
public $cron_string = '';
/** @var int<-1, max> */
public $last_run_time = -1;
/** @var int<0, 1> */
public $last_run_status = self::RUN_STATUS_FAILURE;
/** @var int<0, max> */
public $times_run = 0;
/** @var int[] */
public $storage_ids = [];
/**
* Class contructor
*/
public function __construct()
{
$this->name = __('New Schedule', 'duplicator-pro');
$this->storage_ids = [StoragesUtil::getDefaultStorageId()];
}
/**
* Entity type
*
* @return string
*/
public static function getType()
{
return 'DUP_PRO_Schedule_Entity';
}
/**
* Delete schedule
*
* @return bool true on success or false on failure
*/
public function delete()
{
$id = $this->id;
do_action('duplicator_pro_before_schedule_delete', $this);
if (!parent::delete()) {
return false;
}
do_action('duplicator_pro_after_schedule_delete', $id);
return true;
}
/**
* Insert new schedule
*
* @return bool true on success or false on failure
*/
public function insert()
{
do_action('duplicator_pro_before_schedule_create', $this);
if (!parent::insert()) {
return false;
}
do_action('duplicator_pro_after_schedule_create', $this);
return true;
}
/**
* Set data from query input
*
* @param int $type One of INPUT_GET, INPUT_POST, INPUT_COOKIE, INPUT_SERVER, or INPUT_ENV, SnapUtil::INPUT_REQUEST
*
* @return bool true on success or false on failure
*/
public function setFromInput($type)
{
$input = SnapUtil::getInputFromType($type);
$this->setFromArrayKey(
$input,
function ($key, $val) {
if (is_string($val)) {
$val = stripslashes($val);
}
return (is_scalar($val) ? SnapUtil::sanitizeNSChars($val) : $val);
}
);
if (strlen($this->name) == 0) {
throw new Exception(__('Schedule name can\'t be empty', 'duplicator-pro'));
}
$this->template_id = intval($this->template_id);
if (DUP_PRO_Package_Template_Entity::getById($this->template_id) === false) {
throw new Exception(__('Invalid template id', 'duplicator-pro'));
}
$this->repeat_type = intval($this->repeat_type);
$this->day_of_month = intval($this->day_of_month);
switch ($this->repeat_type) {
case DUP_PRO_Schedule_Entity::REPEAT_HOURLY:
$this->run_every = intval($input['_run_every_hours']);
DUP_PRO_Log::trace("run every hours: " . $input['_run_every_hours']);
break;
case DUP_PRO_Schedule_Entity::REPEAT_DAILY:
$this->run_every = intval($input['_run_every_days']);
DUP_PRO_Log::trace("run every days: " . $input['_run_every_days']);
break;
case DUP_PRO_Schedule_Entity::REPEAT_MONTHLY:
$this->run_every = intval($input['_run_every_months']);
DUP_PRO_Log::trace("run every months: " . $input['_run_every_months']);
break;
case DUP_PRO_Schedule_Entity::REPEAT_WEEKLY:
$this->set_weekdays_from_request($input);
break;
}
if (isset($input['_storage_ids'])) {
$this->storage_ids = array_map('intval', $input['_storage_ids']);
} else {
$this->storage_ids = [StoragesUtil::getDefaultStorageId()];
}
$this->set_start_date_time($input['_start_time']);
$this->build_cron_string();
$this->next_run_time = $this->get_next_run_time();
// Checkboxes don't set post values when off so have to manually set these
$this->active = isset($input['_active']);
return true;
}
/**
* To export data
*
* @return array<string, mixed>
*/
public function settingsExport()
{
return JsonSerialize::serializeToData($this, JsonSerialize::JSON_SKIP_MAGIC_METHODS | JsonSerialize::JSON_SKIP_CLASS_NAME);
}
/**
* Update object properties from import data
*
* @param array<string, mixed> $data data to import
* @param string $dataVersion version of data
* @param array<string, mixed> $extraData extra data, useful form id mapping etc.
*
* @return bool True if success, otherwise false
*/
public function settingsImport($data, $dataVersion, array $extraData = [])
{
$storage_map = (isset($extraData['storage_map']) ? $extraData['storage_map'] : []);
$template_map = (isset($extraData['template_map']) ? $extraData['template_map'] : []);
$skipProps = [
'id',
'last_run_time',
'next_run_time',
'times_run',
];
$reflect = new ReflectionClass(self::class);
$props = $reflect->getProperties();
foreach ($props as $prop) {
if (in_array($prop->getName(), $skipProps)) {
continue;
}
if (!isset($data[$prop->getName()])) {
continue;
}
$prop->setAccessible(true);
$prop->setValue($this, $data[$prop->getName()]);
}
if (isset($template_map[$this->template_id])) {
$this->template_id = $template_map[$this->template_id];
}
for ($i = 0; $i < count($this->storage_ids); $i++) {
if (isset($storage_map[$this->storage_ids[$i]])) {
$this->storage_ids[$i] = $storage_map[$this->storage_ids[$i]];
}
}
return true;
}
/**
* If it should run, queue up a package then update the run time
*
* @return void
*/
public function process()
{
DUP_PRO_Log::trace("process");
$now = time();
if ($this->next_run_time == -1) {
return;
}
if ($this->active && ($this->next_run_time <= $now)) {
$exception = null;
try {
if (!License::can(License::CAPABILITY_SCHEDULE)) {
DUP_PRO_Log::trace("Can't process schedule " . $this->getId() . " because Duplicator isn't licensed");
return;
}
$next_run_time_string = DUP_PRO_DATE::getLocalTimeFromGMTTicks($this->next_run_time);
$now_string = DUP_PRO_DATE::getLocalTimeFromGMTTicks($this->next_run_time);
DUP_PRO_Log::trace("NEXT RUN IS NOW! $next_run_time_string <= $now_string so trying to queue package");
$this->insert_new_package();
$this->next_run_time = $this->get_next_run_time();
$this->save();
$next_run_time_string = DUP_PRO_DATE::getLocalTimeFromGMTTicks($this->next_run_time);
DUP_PRO_Log::trace("******PACKAGE JUST CREATED. UPDATED NEXT RUN TIME TO $next_run_time_string");
} catch (Exception $e) {
$exception = $e;
} catch (Error $e) {
$exception = $e;
}
if (!is_null($exception)) {
$msg = "Start schedule error " . $exception->getMessage() . "\n";
$msg .= SnapLog::getTextException($exception);
error_log($msg);
\DUP_PRO_Log::trace($msg);
$system_global = SystemGlobalEntity::getInstance();
$system_global->schedule_failed = true;
$system_global->save();
}
} else {
DUP_PRO_Log::trace("active and runtime=$this->next_run_time >= $now");
}
}
/**
* Copy schedule from id
*
* @param int $scheduleId template id
*
* @return void
*/
public function copy_from_source_id($scheduleId)
{
if (($source = self::getById($scheduleId)) === false) {
throw new Exception('Can\'t get tempalte id' . $scheduleId);
}
$skipProps = [
'id',
'last_run_time',
'next_run_time',
'times_run',
];
$reflect = new ReflectionClass($this);
$props = $reflect->getProperties();
foreach ($props as $prop) {
if (in_array($prop->getName(), $skipProps)) {
continue;
}
$prop->setAccessible(true);
$prop->setValue($this, $prop->getValue($source));
}
$this->name = sprintf(__('%1$s - Copy', 'duplicator-pro'), $source->name);
}
/**
* Create new packag from schedule, to run
*
* @param bool $run_now If true the package creation is started immediately, otherwise it is scheduled
*
* @return void
*/
public function insert_new_package($run_now = false)
{
$global = DUP_PRO_Global_Entity::getInstance();
DUP_PRO_Log::trace("NEW PACKAGE FROM SCHEDULE ID: " . $this->getId() . " Name: " . $this->name);
DUP_PRO_Log::trace("Archive build mode before calling insert new package, build mode:" . $global->getBuildMode());
if (($template = DUP_PRO_Package_Template_Entity::getById((int) $this->template_id)) === false) {
DUP_PRO_Log::traceError("No settings object exists for schedule {$this->name}!");
return;
}
$type = ($run_now ? DUP_PRO_PackageType::RUN_NOW : DUP_PRO_PackageType::SCHEDULED);
$package = new DUP_PRO_Package(
$type,
$this->generate_package_name(),
$this->storage_ids,
$template,
$this
);
DUP_PRO_Log::trace('NEW PACKAGE NAME ' . $package->Name);
//PACKAGE
$package->notes = sprintf(esc_html__('Created by schedule %1$s', 'duplicator-pro'), $this->name);
$system_global = SystemGlobalEntity::getInstance();
$system_global->clearFixes();
$system_global->package_check_ts = 0;
$system_global->save();
if ($package->save(false) == false) {
$msg = "Duplicator is unable to insert a package record into the database table from schedule {$this->name}.";
DUP_PRO_Log::trace($msg);
throw new Exception($msg);
}
DUP_PRO_Log::trace("archive build mode after calling insert new package ID = " . $package->ID . " build mode = " . $global->archive_build_mode);
}
/**
* Get schedule template object or false if don't exists
*
* @return false|DUP_PRO_Package_Template_Entity
*/
public function getTemplate()
{
if ($this->template_id > 0) {
$template = DUP_PRO_Package_Template_Entity::getById($this->template_id);
} else {
$template = null;
}
if (!$template instanceof DUP_PRO_Package_Template_Entity) {
return false;
}
return $template;
}
/**
* Display HTML info
*
* @param bool $isList if true display info for list
*
* @return void
*/
public function recoveableHtmlInfo($isList = false)
{
if (($template = $this->getTemplate()) === false) {
return;
}
$schedule = $this;
require DUPLICATOR____PATH . '/views/tools/templates/widget/recoveable-template-info.php';
}
/**
* Return package name
*
* @return string
*/
private function generate_package_name()
{
$ticks = time() + SnapWP::getGMTOffset();
//Remove specail_chars from final result
$sanitize_special_chars = array(
".",
"-",
"?",
"[",
"]",
"/",
"\\",
"=",
"<",
">",
":",
";",
",",
"'",
"\"",
"&",
"$",
"#",
"*",
"(",
")",
"|",
"~",
"`",
"!",
"{",
"}",
"%",
"+",
chr(0),
);
$scheduleName = SnapUtil::sanitizeNSCharsNewlineTabs($this->name);
$scheduleName = trim(str_replace($sanitize_special_chars, '', $scheduleName), '_');
DUP_PRO_Log::trace('SCHEDULE NAME ' . $scheduleName);
$blogName = sanitize_title(SnapUtil::sanitizeNSCharsNewlineTabs(get_bloginfo('name', 'display')));
$blogName = trim(str_replace($sanitize_special_chars, '', $blogName), '_');
DUP_PRO_Log::trace('BLOG NAME NAME ' . $blogName);
$name = date('Ymd_His', $ticks) . '_' . $scheduleName . '_' . $blogName;
return substr($name, 0, 40);
}
/**
* Update schedule next run time
*
* @return bool true on success or false on failure
*/
public function updateNextRuntime()
{
$newTime = $this->get_next_run_time();
if ($newTime == $this->next_run_time) {
return true;
}
$this->next_run_time = $newTime;
return $this->save();
}
/**
* Return the next run time in UTC
*
* @return int<-1, max>
*/
public function get_next_run_time()
{
if (!License::can(License::CAPABILITY_SCHEDULE)) {
return -1;
}
if (!$this->active) {
return -1;
}
$nextMinute = time() + 60; // We look ahead starting from next minute
$date = new DateTime();
$date->setTimestamp($nextMinute + SnapWP::getGMTOffset());//Add timezone specific offset
//Get next run time relative to $date
$nextRunTime = CronExpression::factory($this->cron_string)->getNextRunDate($date)->getTimestamp();
// Have to negate the offset and add. For instance for az time -7
// we want the next run time to be 7 ahead in UTC time
$nextRunTime -= SnapWP::getGMTOffset();
// Handling DST problem that happens when there is a change of DST between $nextMinute and $nextRunTime.
// The problem does not happen if manual offset is selected, because in that case there is no DST.
$timezoneString = SnapWP::getTimeZoneString();
if ($timezoneString) {
// User selected particular timezone (not manual offset), so the problem needs to be handled.
$DST_NextMinute = SnapWP::getDST($nextMinute);
$DST_NextRunTime = SnapWP::getDST($nextRunTime);
$DST_NextRunTime_HourBack = SnapWP::getDST($nextRunTime - 3600);
if ($DST_NextMinute && !$DST_NextRunTime) {
$nextRunTime += 3600; // Move one hour ahead because of DST change
} elseif (!$DST_NextMinute && $DST_NextRunTime && $DST_NextRunTime_HourBack) {
$nextRunTime -= 3600; // Move one hour back because of DST change
}
}
return $nextRunTime;
}
/**
* Set week days from input data
*
* @param array<string, mixed> $request input data
*
* @return void
*/
protected function set_weekdays_from_request($request)
{
$weekday = $request['weekday'];
if (in_array('mon', $weekday)) {
$this->weekly_days |= self::DAY_MONDAY;
} else {
$this->weekly_days &= ~self::DAY_MONDAY;
}
if (in_array('tue', $weekday)) {
$this->weekly_days |= self::DAY_TUESDAY;
} else {
$this->weekly_days &= ~self::DAY_TUESDAY;
}
if (in_array('wed', $weekday)) {
$this->weekly_days |= self::DAY_WEDNESDAY;
} else {
$this->weekly_days &= ~self::DAY_WEDNESDAY;
}
if (in_array('thu', $weekday)) {
$this->weekly_days |= self::DAY_THURSDAY;
} else {
$this->weekly_days &= ~self::DAY_THURSDAY;
}
if (in_array('fri', $weekday)) {
$this->weekly_days |= self::DAY_FRIDAY;
} else {
$this->weekly_days &= ~self::DAY_FRIDAY;
}
if (in_array('sat', $weekday)) {
$this->weekly_days |= self::DAY_SATURDAY;
} else {
$this->weekly_days &= ~self::DAY_SATURDAY;
}
if (in_array('sun', $weekday)) {
$this->weekly_days |= self::DAY_SUNDAY;
} else {
$this->weekly_days &= ~self::DAY_SUNDAY;
}
}
/**
* Check if day is set
*
* @param string $day_string day string
*
* @return bool
*/
public function is_day_set($day_string)
{
$day_bit = 0;
switch ($day_string) {
case 'mon':
$day_bit = self::DAY_MONDAY;
break;
case 'tue':
$day_bit = self::DAY_TUESDAY;
break;
case 'wed':
$day_bit = self::DAY_WEDNESDAY;
break;
case 'thu':
$day_bit = self::DAY_THURSDAY;
break;
case 'fri':
$day_bit = self::DAY_FRIDAY;
break;
case 'sat':
$day_bit = self::DAY_SATURDAY;
break;
case 'sun':
$day_bit = self::DAY_SUNDAY;
break;
}
return (($this->weekly_days & $day_bit) != 0);
}
/**
* Returns a list of all schedules associated with a storage
*
* @param int $storageID The storage id
*
* @return self[]
*/
public static function get_schedules_by_storage_id($storageID)
{
return array_filter(self::getAll(), function ($schedule) use ($storageID) {
return in_array($storageID, $schedule->storage_ids);
});
}
/**
* Runs the callback on all schedules
*
* @param callable $callback The callback function
*
* @return void
*/
public static function run_on_all($callback)
{
if (!is_callable($callback)) {
throw new Exception('No callback function passed');
}
foreach (self::getAll() as $schedule) {
call_user_func($callback, $schedule);
}
}
/**
* Get active schedule
*
* @return self[]
*/
public static function get_active()
{
$result = self::getAll(
0,
0,
null,
function (self $schedule) {
return $schedule->active;
}
);
return ($result ? $result : []);
}
/**
* Get stazrt time piece
*
* @param int $piece 0 = hour; 1 = minute;
*
* @return int
*/
public function get_start_time_piece($piece)
{
switch ($piece) {
case 0:
return (int) date('G', $this->start_ticks);
case 1:
return (int) date('i', $this->start_ticks);
default:
return -1;
}
}
/**
* Return next run date
*
* @return string
*/
public function get_next_run_time_string()
{
if ($this->next_run_time == -1) {
return __('Unscheduled', 'duplicator-pro');
} else {
$date_portion = SnapWP::getDateInWPTimezone(
get_option('date_format', 'n/j/y') . ' G:i',
$this->next_run_time
);
$repeat_portion = $this->get_repeat_text();
return "$date_portion - $repeat_portion";
}
}
/**
* Return last run date
*
* @return string
*/
public function get_last_ran_string()
{
if ($this->last_run_time == -1) {
return __('Never Ran', 'duplicator-pro');
} else {
$date_portion = SnapWP::getDateInWPTimezone(
get_option('date_format', 'n/j/y') . ' G:i',
$this->last_run_time
);
$status_portion = (($this->last_run_status == self::RUN_STATUS_SUCCESS) ? __('Success', 'duplicator-pro') : __('Failed', 'duplicator-pro'));
return "$date_portion - $status_portion";
}
}
/**
* Set start time from string date format
*
* @param int|string $startTime start time string HH:MM or int 0-23 for hour
* @param string $startDate date format
*
* @return int return start time
*/
public function set_start_date_time($startTime, $startDate = '2015/1/1')
{
if (is_numeric($startTime)) {
$startTime = sprintf('%02d:00', $startTime);
}
$this->start_ticks = (int) strtotime("$startDate $startTime");
DUP_PRO_Log::trace("start ticks = $this->start_ticks for $startTime $startDate");
return $this->start_ticks;
}
/**
* Get schedules entity by template id
*
* @param int $template_id template id
*
* @return self[]
*/
public static function get_by_template_id($template_id)
{
$schedules = self::getAll();
$filtered_schedules = array();
foreach ($schedules as $schedule) {
if ($schedule->template_id == $template_id) {
array_push($filtered_schedules, $schedule);
}
}
DUP_PRO_Log::trace("get by template id $template_id schedules = " . count($filtered_schedules));
return $filtered_schedules;
}
/**
* Return repeat text
*
* @return string
*/
public function get_repeat_text()
{
switch ($this->repeat_type) {
case self::REPEAT_DAILY:
return __('Daily', 'duplicator-pro');
case self::REPEAT_WEEKLY:
return __('Weekly', 'duplicator-pro');
case self::REPEAT_MONTHLY:
return __('Monthly', 'duplicator-pro');
case self::REPEAT_HOURLY:
return __('Hourly', 'duplicator-pro');
default:
return __('Unknown', 'duplicator-pro');
}
}
/**
* Build cron string
*
* @return void
*/
public function build_cron_string()
{
// Special cron string for debugging if name set to 'bobtest'
if ($this->name == 'bobtest') {
$this->cron_string = '*/5 * * * *';
} else {
$start_hour = $this->get_start_time_piece(0);
$start_min = $this->get_start_time_piece(1);
if ($this->run_every == 1) {
$run_every_string = '*';
} else {
$run_every_string = "*/$this->run_every";
}
// Generated cron patterns using http://www.cronmaker.com/
switch ($this->repeat_type) {
case self::REPEAT_HOURLY:
$this->cron_string = "$start_min $run_every_string * * *";
break;
case self::REPEAT_DAILY:
$this->cron_string = "$start_min $start_hour $run_every_string * *";
break;
case self::REPEAT_WEEKLY:
$day_of_week_string = $this->get_day_of_week_string();
$this->cron_string = "$start_min $start_hour * * $day_of_week_string";
DUP_PRO_Log::trace("day of week cron string: $this->cron_string");
break;
case self::REPEAT_MONTHLY:
$this->cron_string = "$start_min $start_hour $this->day_of_month $run_every_string *";
break;
}
}
DUP_PRO_Log::trace("cron string = $this->cron_string");
}
/**
* Return day of weeks list with commad separated
*
* @return string
*/
private function get_day_of_week_string()
{
$day_array = [];
DUP_PRO_Log::trace("weekly days=$this->weekly_days");
if (($this->weekly_days & self::DAY_MONDAY) != 0) {
$day_array[] = '1';
}
if (($this->weekly_days & self::DAY_TUESDAY) != 0) {
$day_array[] = '2';
}
if (($this->weekly_days & self::DAY_WEDNESDAY) != 0) {
$day_array[] = '3';
}
if (($this->weekly_days & self::DAY_THURSDAY) != 0) {
$day_array[] = '4';
}
if (($this->weekly_days & self::DAY_FRIDAY) != 0) {
array_push($day_array, '5');
}
if (($this->weekly_days & self::DAY_SATURDAY) != 0) {
$day_array[] = '6';
}
if (($this->weekly_days & self::DAY_SUNDAY) != 0) {
$day_array[] = '0';
}
return implode(',', $day_array);
}
}

View File

@@ -0,0 +1,161 @@
<?php
/**
* @package Duplicator
* @copyright (c) 2022, Snap Creek LLC
*/
use Duplicator\Core\Models\AbstractEntitySingleton;
use Duplicator\Core\Models\UpdateFromInputInterface;
use Duplicator\Libs\Snap\SnapUtil;
use Duplicator\Utils\Crypt\CryptBlowfish;
use Duplicator\Utils\Settings\ModelMigrateSettingsInterface;
use VendorDuplicator\Amk\JsonSerialize\JsonSerialize;
/**
* Secure Global Entity. Used to store settings requiring encryption.
*
* @todo remove this entity and put props on globals
*/
class DUP_PRO_Secure_Global_Entity extends AbstractEntitySingleton implements UpdateFromInputInterface, ModelMigrateSettingsInterface
{
/** @var string */
public $basic_auth_password = '';
/** @var string */
public $lkp = '';
/**
* Class contructor
*/
protected function __construct()
{
}
/**
* Entity type
*
* @return string
*/
public static function getType()
{
return 'DUP_PRO_Secure_Global_Entity';
}
/**
* Will be called, automatically, when Serialize
*
* @return array<string,mixed>
*/
public function __serialize() // phpcs:ignore PHPCompatibility.FunctionNameRestrictions.NewMagicMethods.__serializeFound
{
$data = JsonSerialize::serializeToData($this, JsonSerialize::JSON_SKIP_MAGIC_METHODS | JsonSerialize::JSON_SKIP_CLASS_NAME);
if (strlen($this->basic_auth_password)) {
$data['basic_auth_password'] = CryptBlowfish::encrypt($this->basic_auth_password);
}
if (strlen($this->lkp)) {
$data['lkp'] = CryptBlowfish::encrypt($this->lkp);
}
return $data;
}
/**
* Serialize
*
* Wakeup method.
*
* @return void
*/
public function __wakeup()
{
$this->basic_auth_password = (string) $this->basic_auth_password; // for old versions
if (strlen($this->basic_auth_password)) {
$this->basic_auth_password = CryptBlowfish::decrypt($this->basic_auth_password);
}
$this->lkp = (string) $this->lkp; // for old versions
if (strlen($this->lkp)) {
$this->lkp = CryptBlowfish::decrypt($this->lkp);
}
}
/**
* Set data from query input
*
* @param int $type One of INPUT_GET, INPUT_POST, INPUT_COOKIE, INPUT_SERVER, or INPUT_ENV, SnapUtil::INPUT_REQUEST
*
* @return bool true on success or false on failure
*/
public function setFromInput($type)
{
$input = SnapUtil::getInputFromType($type);
$this->basic_auth_password = isset($input['basic_auth_password']) ? SnapUtil::sanitizeNSCharsNewlineTrim($input['basic_auth_password']) : '';
$this->basic_auth_password = stripslashes($this->basic_auth_password);
return true;
}
/**
* To export data
*
* @return array<string, mixed>
*/
public function settingsExport()
{
$skipProps = [
'id',
'lkp',
];
$data = JsonSerialize::serializeToData($this, JsonSerialize::JSON_SKIP_MAGIC_METHODS | JsonSerialize::JSON_SKIP_CLASS_NAME);
foreach ($skipProps as $prop) {
unset($data[$prop]);
}
return $data;
}
/**
* Update object properties from import data
*
* @param array<string, mixed> $data data to import
* @param string $dataVersion version of data
* @param array<string, mixed> $extraData extra data, useful form id mapping etc.
*
* @return bool True if success, otherwise false
*/
public function settingsImport($data, $dataVersion, array $extraData = [])
{
$skipProps = [
'id',
'lkp',
];
$reflect = new ReflectionClass(self::class);
$props = $reflect->getProperties();
foreach ($props as $prop) {
if (in_array($prop->getName(), $skipProps)) {
continue;
}
if (!isset($data[$prop->getName()])) {
continue;
}
$prop->setAccessible(true);
$prop->setValue($this, $data[$prop->getName()]);
}
return true;
}
/**
* Set data from import data
*
* @param object $global_data Global data
*
* @return void
*/
public function setFromImportData($global_data)
{
$this->basic_auth_password = $global_data->basic_auth_password;
// skip in import settings
//$this->lkp = $global_data->lkp;
}
}

View File

@@ -0,0 +1,200 @@
<?php
/**
* Storage entity layer
*/
defined('ABSPATH') || defined('DUPXABSPATH') || exit;
use Duplicator\Core\Models\AbstractEntityList;
use Duplicator\Utils\Crypt\CryptBlowfish;
use VendorDuplicator\Amk\JsonSerialize\JsonSerialize;
/**
* @copyright 2016 Snap Creek LLC
*/
abstract class DUP_PRO_Storage_Entity extends AbstractEntityList
{
const PROPERTIES_TO_ENCRYPT = [
'dropbox_access_token',
'dropbox_v2_access_token',
'dropbox_access_token_secret',
'gdrive_access_token_set_json',
'gdrive_refresh_token',
's3_access_key',
's3_secret_key',
'ftp_username',
'ftp_password',
'ftp_storage_folder',
'sftp_username',
'sftp_password',
'sftp_private_key',
'sftp_private_key_password',
'sftp_storage_folder',
'onedrive_user_id',
'onedrive_access_token',
'onedrive_refresh_token',
];
/** @todo Legacy values to remove when storages will'be fully migrated */
/** @var string */
protected $local_storage_folder = '';
/** @var int */
protected $local_max_files = 10;
/** @var bool */
protected $local_filter_protection = true;
/** @var bool */
protected $purge_package_record = true;
// DROPBOX FIELDS
/** @var string */
protected $dropbox_access_token = '';
/** @var string */
protected $dropbox_access_token_secret = '';
/** @var string */
protected $dropbox_v2_access_token = '';
//to use different name for OAuth 2 token
/** @var string */
protected $dropbox_storage_folder = '';
/** @var int */
protected $dropbox_max_files = 10;
/** @var int */
protected $dropbox_authorization_state = 0;
//ONEDRIVE FIELDS
/** @var string */
protected $onedrive_endpoint_url = '';
/** @var string */
protected $onedrive_resource_id = '';
/** @var string */
protected $onedrive_access_token = '';
/** @var string */
protected $onedrive_refresh_token = '';
/** @var int */
protected $onedrive_token_obtained = 0;
/** @var string */
protected $onedrive_user_id = '';
/** @var string */
protected $onedrive_storage_folder = '';
/** @var int */
protected $onedrive_max_files = 10;
/** @var string */
protected $onedrive_storage_folder_id = '';
/** @var int */
protected $onedrive_authorization_state = 0;
/** @var string */
protected $onedrive_storage_folder_web_url = '';
// FTP FIELDS
/** @var string */
protected $ftp_server = '';
/** @var int */
protected $ftp_port = 21;
/** @var string */
protected $ftp_username = '';
/** @var string */
protected $ftp_password = '';
/** @var bool */
protected $ftp_use_curl = false;
/** @var string */
protected $ftp_storage_folder = '';
/** @var int */
protected $ftp_max_files = 10;
/** @var int */
protected $ftp_timeout_in_secs = 15;
/** @var bool */
protected $ftp_ssl = false;
/** @var bool */
protected $ftp_passive_mode = false;
// SFTP FIELDS
/** @var string */
protected $sftp_server = '';
/** @var int */
protected $sftp_port = 22;
/** @var string */
protected $sftp_username = '';
/** @var string */
protected $sftp_password = '';
/** @var string */
protected $sftp_private_key = '';
/** @var string */
protected $sftp_private_key_password = '';
/** @var string */
protected $sftp_storage_folder = '';
/** @var int */
protected $sftp_timeout_in_secs = 15;
/** @var int */
protected $sftp_max_files = 10;
/** @var bool */
protected $sftp_disable_chunking_mode = false;
// GOOGLE DRIVE FIELDS
/** @var string */
protected $gdrive_access_token_set_json = '';
/** @var string */
protected $gdrive_refresh_token = '';
/** @var string */
protected $gdrive_storage_folder = '';
/** @var int */
protected $gdrive_max_files = 10;
/** @var int */
protected $gdrive_authorization_state = 0;
/** @var int */
protected $gdrive_client_number = -1;
// S3 FIELDS
/** @var string */
protected $s3_access_key = '';
/** @var string */
protected $s3_bucket = '';
/** @var int */
protected $s3_max_files = 10;
/** @var string */
protected $s3_provider = 'amazon';
/** @var string */
protected $s3_region = '';
/** @var string */
protected $s3_endpoint = '';
/** @var string */
protected $s3_secret_key = '';
/** @var string */
protected $s3_storage_class = 'STANDARD';
/** @var string */
protected $s3_storage_folder = '';
/** @var bool */
protected $s3_ACL_full_control = true;
/**
* Will be called, automatically, when Serialize
*
* @return array<string, mixed>
*/
public function __serialize() // phpcs:ignore PHPCompatibility.FunctionNameRestrictions.NewMagicMethods.__serializeFound
{
$data = JsonSerialize::serializeToData($this, JsonSerialize::JSON_SKIP_MAGIC_METHODS | JsonSerialize::JSON_SKIP_CLASS_NAME);
if (DUP_PRO_Global_Entity::getInstance()->crypt) {
foreach (self::PROPERTIES_TO_ENCRYPT as $prop) {
if (!empty($data[$prop])) {
$data[$prop] = CryptBlowfish::encrypt($data[$prop]);
}
}
}
return $data;
}
/**
* Serialize
*
* Wakeup method.
*
* @return void
*/
public function __wakeup()
{
if (DUP_PRO_Global_Entity::getInstance()->crypt) {
foreach (self::PROPERTIES_TO_ENCRYPT as $prop) {
if (!empty($this->{$prop})) {
$this->{$prop} = CryptBlowfish::decrypt($this->{$prop});
}
}
}
}
}

View File

@@ -0,0 +1,3 @@
<?php
//silent

View File

@@ -0,0 +1,32 @@
<?php
/**
* cloudways custom hosting class
*
* Standard: PSR-2
*
* @package SC\DUPX\HOST
* @link http://www.php-fig.org/psr/psr-2/
*/
use Duplicator\Libs\Snap\SnapUtil;
class DUP_PRO_Cloudways_Host implements DUP_PRO_Host_interface
{
public static function getIdentifier()
{
return DUP_PRO_Custom_Host_Manager::HOST_CLOUDWAYS;
}
public function isHosting()
{
ob_start();
SnapUtil::phpinfo();
$serverinfo = ob_get_clean();
return (strpos($serverinfo, "cloudwaysapps") !== false);
}
public function init()
{
}
}

View File

@@ -0,0 +1,167 @@
<?php
/**
* custom hosting manager
* singleton class
*
* Standard: PSR-2
*
* @package SC\DUPX\HOST
* @link http://www.php-fig.org/psr/psr-2/
*/
defined('ABSPATH') || defined('DUPXABSPATH') || exit;
require_once(DUPLICATOR____PATH . '/classes/host/interface.host.php');
require_once(DUPLICATOR____PATH . '/classes/host/class.godaddy.host.php');
require_once(DUPLICATOR____PATH . '/classes/host/class.wpengine.host.php');
require_once(DUPLICATOR____PATH . '/classes/host/class.cloudways.host.php');
require_once(DUPLICATOR____PATH . '/classes/host/class.wordpresscom.host.php');
require_once(DUPLICATOR____PATH . '/classes/host/class.liquidweb.host.php');
require_once(DUPLICATOR____PATH . '/classes/host/class.pantheon.host.php');
require_once(DUPLICATOR____PATH . '/classes/host/class.flywheel.host.php');
class DUP_PRO_Custom_Host_Manager
{
const HOST_GODADDY = 'godaddy';
const HOST_WPENGINE = 'wpengine';
const HOST_CLOUDWAYS = 'cloudways';
const HOST_WORDPRESSCOM = 'wordpresscom';
const HOST_LIQUIDWEB = 'liquidweb';
const HOST_PANTHEON = 'pantheon';
const HOST_FLYWHEEL = 'flywheel';
/** @var ?self */
protected static $instance = null;
/** @var bool */
private $initialized = false;
/** @var DUP_PRO_Host_interface[] */
private $customHostings = array();
/** @var string[] */
private $activeHostings = array();
/**
*
* @return self
*/
public static function getInstance()
{
if (is_null(self::$instance)) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Class constructor
*/
private function __construct()
{
$this->customHostings[DUP_PRO_WPEngine_Host::getIdentifier()] = new DUP_PRO_WPEngine_Host();
$this->customHostings[DUP_PRO_Cloudways_Host::getIdentifier()] = new DUP_PRO_Cloudways_Host();
$this->customHostings[DUP_PRO_GoDaddy_Host::getIdentifier()] = new DUP_PRO_GoDaddy_Host();
$this->customHostings[DUP_PRO_WordpressCom_Host::getIdentifier()] = new DUP_PRO_WordpressCom_Host();
$this->customHostings[DUP_PRO_Liquidweb_Host::getIdentifier()] = new DUP_PRO_Liquidweb_Host();
$this->customHostings[DUP_PRO_Pantheon_Host::getIdentifier()] = new DUP_PRO_Pantheon_Host();
$this->customHostings[DUP_PRO_Flywheel_Host::getIdentifier()] = new DUP_PRO_Flywheel_Host();
}
/**
* Initialize custom hostings
*
* @return bool
*/
public function init()
{
if ($this->initialized) {
return true;
}
foreach ($this->customHostings as $cHost) {
if (!($cHost instanceof DUP_PRO_Host_interface)) {
throw new Exception('Host must implement DUP_PRO_Host_interface');
}
if ($cHost->isHosting()) {
$this->activeHostings[] = $cHost->getIdentifier();
$cHost->init();
}
}
$this->initialized = true;
return true;
}
/**
* Return active hostings
*
* @return string[]
*/
public function getActiveHostings()
{
return $this->activeHostings;
}
/**
* Returns true if the current site is hosted on identified host
*
* @param string $identifier Host identifier
*
* @return bool
*/
public function isHosting($identifier)
{
return in_array($identifier, $this->activeHostings);
}
/**
* Returns true if the current site is hosted on a managed host
*
* @return bool
*/
public function isManaged()
{
if ($this->isHosting(self::HOST_WORDPRESSCOM)) {
return true;
}
if ($this->isHosting(self::HOST_GODADDY)) {
return true;
}
if ($this->isHosting(self::HOST_WPENGINE)) {
return true;
}
if ($this->isHosting(self::HOST_CLOUDWAYS)) {
return true;
}
if ($this->isHosting(self::HOST_LIQUIDWEB)) {
return true;
}
if ($this->isHosting(self::HOST_PANTHEON)) {
return true;
}
if ($this->isHosting(self::HOST_FLYWHEEL)) {
return true;
}
return false;
}
/**
* Get hostg object
*
* @param string $identifier Host identifier
*
* @return DUP_PRO_Host_interface|false
*/
public function getHosting($identifier)
{
if ($this->isHosting($identifier)) {
return $this->customHostings[$identifier];
} else {
return false;
}
}
}

View File

@@ -0,0 +1,72 @@
<?php
/**
* Flywheel custom hosting class
*
* Standard: PSR-2
*
* @package SC\DUPX\HOST
* @link http://www.php-fig.org/psr/psr-2/
*/
use Duplicator\Libs\Snap\SnapWP;
class DUP_PRO_Flywheel_Host implements DUP_PRO_Host_interface
{
public static function getIdentifier()
{
return DUP_PRO_Custom_Host_Manager::HOST_FLYWHEEL;
}
public function isHosting()
{
return apply_filters('duplicator_pro_host_check', file_exists(self::getFlywheelMainPluginPaht()), self::getIdentifier());
}
public function init()
{
add_filter('duplicator_pro_overwrite_params_data', array(__CLASS__, 'installerParams'));
add_filter('duplicator_pro_global_file_filters', array(__CLASS__, 'filterPluginFile'));
}
/**
* Get the path to the main plugin file
*
* @return string
*/
public static function getFlywheelMainPluginPaht()
{
return trailingslashit(SnapWP::getHomePath()) . '.fw-config.php';
}
/**
* Filter plugin file
*
* @param array<string> $globalsFileFilters Global file filters
*
* @return array<string>
*/
public static function filterPluginFile($globalsFileFilters)
{
$globalsFileFilters[] = self::getFlywheelMainPluginPaht();
return $globalsFileFilters;
}
/**
* Add installer params
*
* @param array<string,array{formStatus?:string,value:mixed}> $data Data
*
* @return array<string,array{formStatus?:string,value:mixed}>
*/
public static function installerParams($data)
{
// generare new wp-config.php file
$data['wp_config'] = array(
'value' => 'new',
'formStatus' => 'st_infoonly',
);
return $data;
}
}

View File

@@ -0,0 +1,69 @@
<?php
/**
* godaddy custom hosting class
*
* Standard: PSR-2
*
* @package SC\DUPX\HOST
* @link http://www.php-fig.org/psr/psr-2/
*/
defined('ABSPATH') || defined('DUPXABSPATH') || exit;
class DUP_PRO_GoDaddy_Host implements DUP_PRO_Host_interface
{
public static function getIdentifier()
{
return DUP_PRO_Custom_Host_Manager::HOST_GODADDY;
}
public function isHosting()
{
return apply_filters('duplicator_pro_godaddy_host_check', file_exists(WPMU_PLUGIN_DIR . '/gd-system-plugin.php'));
}
public function init()
{
add_filter('duplicator_pro_default_archive_build_mode', array(__CLASS__, 'defaultArchiveBuildMode'), 20, 1);
add_filter('duplicator_pro_overwrite_params_data', array(__CLASS__, 'installerParams'));
}
/**
* In godaddy the packag build mode must be Dup archive
*
* @param int $archiveBuildMode archive build mode
*
* @return int
*/
public static function defaultArchiveBuildMode($archiveBuildMode)
{
return DUP_PRO_Archive_Build_Mode::DupArchive;
}
/**
* Add installer params
*
* @param array<string,array{formStatus?:string,value:mixed}> $data Data
*
* @return array<string,array{formStatus?:string,value:mixed}>
*/
public static function installerParams($data)
{
// disable wp engine plugins
$data['fd_plugins'] = array(
'value' => array(
'gd-system-plugin.php',
'object-cache.php',
),
);
// generate new wp-config.php file
$data['wp_config'] = array(
'value' => 'new',
'formStatus' => 'st_infoonly',
);
return $data;
}
}

View File

@@ -0,0 +1,50 @@
<?php
/**
* wpengine custom hosting class
*
* Standard: PSR-2
*
* @package SC\DUPX\HOST
* @link http://www.php-fig.org/psr/psr-2/
*/
defined('ABSPATH') || defined('DUPXABSPATH') || exit;
class DUP_PRO_Liquidweb_Host implements DUP_PRO_Host_interface
{
public static function getIdentifier()
{
return DUP_PRO_Custom_Host_Manager::HOST_LIQUIDWEB;
}
public function isHosting()
{
return apply_filters('duplicator_pro_liquidweb_host_check', file_exists(WPMU_PLUGIN_DIR . '/liquid-web.php'));
}
public function init()
{
add_filter('duplicator_pro_overwrite_params_data', array(__CLASS__, 'installerParams'));
}
/**
* Add installer params
*
* @param array<string,array{formStatus?:string,value:mixed}> $data Data
*
* @return array<string,array{formStatus?:string,value:mixed}>
*/
public static function installerParams($data)
{
$data['fd_plugins'] = array(
'value' => array(
'liquidweb_mwp.php',
'000-liquidweb-config.php',
'liquid-web.php',
'lw_disable_nags.php',
),
);
return $data;
}
}

View File

@@ -0,0 +1,53 @@
<?php
/**
* godaddy custom hosting class
*
* Standard: PSR-2
*
* @package SC\DUPX\HOST
* @link http://www.php-fig.org/psr/psr-2/
*/
defined('ABSPATH') || defined('DUPXABSPATH') || exit;
class DUP_PRO_Pantheon_Host implements DUP_PRO_Host_interface
{
public static function getIdentifier()
{
return DUP_PRO_Custom_Host_Manager::HOST_PANTHEON;
}
public function isHosting()
{
return apply_filters('duplicator_pro_pantheon_host_check', file_exists(WPMU_PLUGIN_DIR . '/pantheon.php'));
}
public function init()
{
add_filter('duplicator_pro_overwrite_params_data', array(__CLASS__, 'installerParams'));
}
/**
* Add installer params
*
* @param array<string,array{formStatus?:string,value:mixed}> $data Data
*
* @return array<string,array{formStatus?:string,value:mixed}>
*/
public static function installerParams($data)
{
// disable wp engine plugins
$data['fd_plugins'] = array(
'value' => array('pantheon.php'),
);
// generare new wp-config.php file
$data['wp_config'] = array(
'value' => 'new',
'formStatus' => 'st_infoonly',
);
return $data;
}
}

View File

@@ -0,0 +1,75 @@
<?php
/**
* godaddy custom hosting class
*
* Standard: PSR-2
*
* @package SC\DUPX\HOST
* @link http://www.php-fig.org/psr/psr-2/
*/
defined('ABSPATH') || defined('DUPXABSPATH') || exit;
class DUP_PRO_WordpressCom_Host implements DUP_PRO_Host_interface
{
/**
* Get the identifier for this host
*
* @return string
*/
public static function getIdentifier()
{
return DUP_PRO_Custom_Host_Manager::HOST_WORDPRESSCOM;
}
/**
* @return bool
*/
public function isHosting()
{
return apply_filters('duplicator_pro_wordpress_host_check', file_exists(WPMU_PLUGIN_DIR . '/wpcomsh-loader.php'));
}
public function init()
{
add_filter('duplicator_pro_is_shellzip_available', '__return_false');
add_filter('duplicator_pro_overwrite_params_data', array(__CLASS__, 'installerParams'));
}
/**
* Add installer params
*
* @param array<string,array{formStatus?:string,value:mixed}> $data Data
*
* @return array<string,array{formStatus?:string,value:mixed}>
*/
public static function installerParams($data)
{
// disable plugins
$data['fd_plugins'] = array(
'value' => array(
'wpcomsh-loader.php',
'advanced-cache.php',
'object-cache.php',
),
);
// generare new wp-config.php file
$data['wp_config'] = array(
'value' => 'new',
'formStatus' => 'st_infoonly',
);
// disable WP_CACHE
$data['wpc_WP_CACHE'] = array(
'value' => array(
'value' => false,
'inWpConfig' => false,
),
'formStatus' => 'st_infoonly',
);
return $data;
}
}

View File

@@ -0,0 +1,72 @@
<?php
/**
* wpengine custom hosting class
*
* Standard: PSR-2
*
* @package SC\DUPX\HOST
* @link http://www.php-fig.org/psr/psr-2/
*/
use Duplicator\Libs\Snap\SnapUtil;
class DUP_PRO_WPEngine_Host implements DUP_PRO_Host_interface
{
public static function getIdentifier()
{
return DUP_PRO_Custom_Host_Manager::HOST_WPENGINE;
}
public function isHosting()
{
ob_start();
SnapUtil::phpinfo(INFO_ENVIRONMENT);
$serverinfo = ob_get_clean();
return apply_filters('duplicator_pro_wp_engine_host_check', (strpos($serverinfo, "WPENGINE_ACCOUNT") !== false));
}
public function init()
{
add_filter('duplicator_pro_overwrite_params_data', array(__CLASS__, 'installerParams'));
}
/**
* Add installer params
*
* @param array<string,array{formStatus?:string,value:mixed}> $data Data
*
* @return array<string,array{formStatus?:string,value:mixed}>
*/
public static function installerParams($data)
{
// disable wp engine plugins
$data['fd_plugins'] = array(
'value' => array(
'mu-plugin.php',
'advanced-cache.php',
'wpengine-security-auditor.php',
'stop-long-comments.php',
'slt-force-strong-passwords.php',
'wpe-wp-sign-on-plugin.php',
),
);
// generare new wp-config.php file
$data['wp_config'] = array(
'value' => 'new',
'formStatus' => 'st_infoonly',
);
// disable WP_CACHE
$data['wpc_WP_CACHE'] = array(
'value' => array(
'value' => false,
'inWpConfig' => false,
),
'formStatus' => 'st_infoonly',
);
return $data;
}
}

View File

@@ -0,0 +1,35 @@
<?php
/**
* interface for specific hostings class
*
* Standard: PSR-2
*
* @package SC\DUPX\HOST
* @link http://www.php-fig.org/psr/psr-2/
*/
defined('ABSPATH') || defined('DUPXABSPATH') || exit;
interface DUP_PRO_Host_interface
{
/**
* return the current host itentifier
*
* @return string
*/
public static function getIdentifier();
/**
* @return bool true if is current host
*/
public function isHosting();
/**
* the init function.
* is called only if isHosting is true
*
* @return void
*/
public function init();
}

View File

@@ -0,0 +1,3 @@
<?php
//silent

View File

@@ -0,0 +1,286 @@
<?php
/**
* archive path file list object
*
* Standard: PSR-2
*
* @link http://www.php-fig.org/psr/psr-2
*
* @package name
* @copyright (c) 2019, Snapcreek LLC
* @license https://opensource.org/licenses/GPL-3.0 GNU Public License
*/
defined('ABSPATH') || defined('DUPXABSPATH') || exit;
use Duplicator\Libs\Snap\SnapIO;
use Duplicator\Libs\Snap\SnapJson;
class DUP_PRO_Archive_File_List
{
/** @var string */
protected $path = '';
/** @var ?resource */
protected $handle = null;
/** @var ?array<array{p:string,s:int,n:int}> */
protected $cache = null;
/**
* CLass constructor
*
* @param string $path path to file
*/
public function __construct($path)
{
if (empty($path)) {
throw new Exception('path can\'t be empty');
}
$this->path = SnapIO::safePath($path);
}
/**
* Class destructor
*
* @return void
*/
public function __destruct()
{
$this->close();
}
/**
* Get path
*
* @return string
*/
public function getPath()
{
return $this->path;
}
/**
* Open file
*
* @param bool $truncate if true truncate file
*
* @return bool
*/
public function open($truncate = false)
{
if (is_null($this->handle)) {
if (($this->handle = fopen($this->path, 'a+')) === false) {
DUP_PRO_Log::trace('Can\'t open ' . $this->path);
$this->handle = null;
return false;
}
}
if ($truncate) {
$this->emptyFile();
}
return true;
}
/**
* Empty file
*
* @return bool
*/
public function emptyFile()
{
if (!$this->open(false)) {
return false;
}
if (($res = ftruncate($this->handle, 0)) === false) {
DUP_PRO_Log::trace('Can\'t truncate file ' . $this->path);
return false;
}
return true;
}
/**
* Close file
*
* @return bool
*/
public function close()
{
if (!is_null($this->handle)) {
if (($res = @fclose($this->handle)) === false) {
DUP_PRO_Log::trace('Can\'t close ' . $this->path);
return false;
}
$this->handle = null;
}
return true;
}
/**
* Add entry
*
* @param string $path path
* @param int $size size
* @param int $nodes nodes
*
* @return bool true if success else false
*/
public function addEntry($path, $size, $nodes)
{
if (is_null($this->handle)) { // check to generate less overhead
if (!$this->open()) {
return false;
}
}
$entry = array(
'p' => $path,
's' => $size,
'n' => $nodes,
);
return (fwrite($this->handle, SnapJson::jsonEncode($entry) . "\n") !== false);
}
/**
* Get entry
*
* @param bool $pathOnly if true return only payth
*
* @return false|array{p:string,s:int,n:int}|string return false if is end of filer.
*/
public function getEntry($pathOnly = false)
{
if (is_null($this->handle)) { // check to generate less overhead
if (!$this->open()) {
return false;
}
}
if (($json = fgets($this->handle, 4196)) === false) {
// end of file return false
return false;
}
$result = json_decode($json, true);
if ($pathOnly) {
return $result['p'];
} else {
return $result;
}
}
/**
* Clean cache
*
* @return bool
*/
protected function cleanCache()
{
$this->cache = null;
return true;
}
/**
* Load cache
*
* @param bool $refreshCache if true refresh cache
*
* @return bool
*/
protected function loadCache($refreshCache = false)
{
if ($refreshCache || is_null($this->cache)) {
if (!$this->open()) {
return false;
}
$this->cache = array();
if (@fseek($this->handle, 0) === -1) {
DUP_PRO_Log::trace('Can\'t seek at 0 pos for file ' . $this->path);
$this->cleanCache();
return false;
}
while (($entry = $this->getEntry()) !== false) {
$this->cache[$entry['p']] = $entry;
}
if (!feof($this->handle)) {
DUP_PRO_Log::trace('Error: unexpected fgets() fail', false);
}
}
return true;
}
/**
* Get entry from path
*
* @param string $path path
* @param bool $refreshCache if true refresh cache
*
* @return bool|array{p:string,s:int,n:int}
*/
public function getEntryFromPath($path, $refreshCache = false)
{
if (!$this->loadCache($refreshCache)) {
return false;
}
if (array_key_exists($path, $this->cache)) {
return $this->cache[$path];
} else {
return false;
}
}
/**
* Get entries from path
*
* @param string $path path
* @param bool $refreshCache if true refresh cache
*
* @return bool|array<array{p:string,s:int,n:int}>
*/
public function getEntriesFormPath($path, $refreshCache = false)
{
if (!$this->loadCache($refreshCache)) {
return false;
}
if (array_key_exists($path, $this->cache)) {
$result = array();
foreach ($this->cache as $current => $entry) {
if (preg_match('/^' . preg_quote($path, '/') . '\/[^\/]+$/', $current)) {
$result[] = $entry;
}
}
return $result;
} else {
return false;
}
}
/**
* Get array paths
*
* @param string $pathPrefix path prefix
*
* @return bool|string[]
*/
public function getArrayPaths($pathPrefix = '')
{
if (!$this->open()) {
return false;
}
$result = array();
if (@fseek($this->handle, 0) === -1) {
DUP_PRO_Log::trace('Can\'t seek at 0 pos for file ' . $this->path);
return false;
}
$safePrefix = SnapIO::safePathUntrailingslashit($pathPrefix);
while (($path = $this->getEntry(true)) !== false) {
$result[] = $safePrefix . '/' . $path;
}
if (!feof($this->handle)) {
DUP_PRO_Log::trace('Error: unexpected fgets() fail', false);
}
return $result;
}
}

View File

@@ -0,0 +1,187 @@
<?php
defined('ABSPATH') || defined('DUPXABSPATH') || exit;
/**
* Defines the scope from which a filter item was created/retrieved from
*
* @package DupicatorPro\classes
*/
class DUP_PRO_Archive_Filter_Scope_Base
{
/** @var string[] All internal storage items that we decide to filter */
public $Core = array();
//TODO: Enable with Settings UI
/** @var string[] Global filter items added from settings */
public $Global = array();
/** @var string[] Items when creating a package or template */
public $Instance = array();
/** @var string[] Items that are not readable */
public $Unreadable = array();
/** @var int Number of unreadable items */
private $unreadableCount = 0;
/**
* Filter props on json encode
*
* @return string[]
*/
public function __sleep()
{
$props = array_keys(get_object_vars($this));
return array_diff($props, array('unreadableCount'));
}
/**
* @param string $item A path to an unreadable item
*
* @return void
*/
public function addUnreadableItem($item)
{
$this->unreadableCount++;
if ($this->unreadableCount <= DUPLICATOR_PRO_SCAN_MAX_UNREADABLE_COUNT) {
$this->Unreadable[] = $item;
}
}
/**
* @return int returns number of unreadable items
*/
public function getUnreadableCount()
{
return $this->unreadableCount;
}
}
/**
* Defines the scope from which a filter item was created/retrieved from
*
* @package DupicatorPro\classes
*/
class DUP_PRO_Archive_Filter_Scope_Directory extends DUP_PRO_Archive_Filter_Scope_Base
{
/**
* @var string[] Directories containing other WordPress installs
*/
public $AddonSites = array();
/**
* @var array<array<string,mixed>> Items that are too large
*/
public $Size = array();
}
/**
* Defines the scope from which a filter item was created/retrieved from
*
* @package DupicatorPro\classes
*/
class DUP_PRO_Archive_Filter_Scope_File extends DUP_PRO_Archive_Filter_Scope_Base
{
/**
* @var array<array<string,mixed>> Items that are too large
*/
public $Size = array();
}
/**
* Defines the filtered items that are pulled from there various scopes
*
* @package DupicatorPro\classes
*/
class DUP_PRO_Archive_Filter_Info
{
/** @var ?DUP_PRO_Archive_Filter_Scope_Directory Contains all folder filter info */
public $Dirs = null;
/** @var ?DUP_PRO_Archive_Filter_Scope_File Contains all folder filter info */
public $Files = null;
/** @var ?DUP_PRO_Archive_Filter_Scope_Base Contains all folder filter info */
public $Exts = null;
/** @var null|array<string,mixed>|DUP_PRO_Tree_files tree size structure for client jstree */
public $TreeSize = null;
/**
* Class constructor
*/
public function __construct()
{
$this->reset(true);
}
/**
* Clone
*
* @return void
*/
public function __clone()
{
if (is_object($this->Dirs)) {
$this->Dirs = clone $this->Dirs;
}
if (is_object($this->Files)) {
$this->Files = clone $this->Files;
}
if (is_object($this->Exts)) {
$this->Exts = clone $this->Exts;
}
if (is_object($this->TreeSize)) {
$this->TreeSize = clone $this->TreeSize;
}
}
/**
* reset and clean all object
*
* @param bool $initTreeObjs if true then init tree size object
*
* @return void
*/
public function reset($initTreeObjs = false)
{
$exclude = array(
"Unreadable",
"Instance",
);
if (is_null($this->Dirs)) {
$this->Dirs = new DUP_PRO_Archive_Filter_Scope_Directory();
} else {
$this->resetMember($this->Dirs, $exclude);
}
if (is_null($this->Files)) {
$this->Files = new DUP_PRO_Archive_Filter_Scope_File();
} else {
$this->resetMember($this->Files, $exclude);
}
$this->Exts = new DUP_PRO_Archive_Filter_Scope_Base();
if ($initTreeObjs) {
$this->TreeSize = new DUP_PRO_Tree_files(ABSPATH, false);
} else {
$this->TreeSize = null;
}
}
/**
* Resets all properties of $member to their default values except the ones in $exclude
*
* @param object $member Object to reset
* @param string[] $exclude Properties to exclude from resetting
*
* @return void
*/
private function resetMember($member, $exclude = array())
{
$refClass = new ReflectionClass($member);
$defaults = $refClass->getDefaultProperties();
foreach ($member as $key => $value) {
if (!in_array($key, $exclude)) {
if (isset($defaults[$key])) {
$member->$key = $defaults[$key];
} else {
$member->$key = null;
}
}
}
}
}

View File

@@ -0,0 +1,266 @@
<?php
defined('ABSPATH') || defined('DUPXABSPATH') || exit;
use Duplicator\Libs\Snap\SnapIO;
use Duplicator\Libs\Shell\Shell;
use Duplicator\Models\Storages\AbstractStorageEntity;
use Duplicator\Models\Storages\Local\LocalStorage;
use Duplicator\Models\SystemGlobalEntity;
use Duplicator\Package\Create\BuildProgress;
/**
* Creates a zip file using Shell_Exec and the system zip command
* Not available on all system
**/
class DUP_PRO_ShellZip
{
/**
* Creates the zip file and adds the SQL file to the archive
*
* @param DUP_PRO_Package $package The package object
* @param BuildProgress $build_progress The build progress object
*
* @return boolean
*/
public static function create(DUP_PRO_Package $package, BuildProgress $build_progress)
{
$archive = $package->Archive;
try {
if ($package->Status == DUP_PRO_PackageStatus::ARCSTART) {
$error_text = __('Zip process getting killed due to limited server resources.', 'duplicator-pro');
$fix_text = __('Click to switch Archive Engine DupArchive.', 'duplicator-pro');
$system_global = SystemGlobalEntity::getInstance();
$system_global->addQuickFix(
$error_text,
$fix_text,
array(
'global' => array('archive_build_mode' => 3),
)
);
DUP_PRO_Log::traceError("$error_text **RECOMMENDATION: $fix_text");
if ($build_progress->retries > 1) {
$build_progress->failed = true;
return true;
} else {
$build_progress->retries++;
$package->update();
}
}
$package->set_status(DUP_PRO_PackageStatus::ARCSTART);
$package->safe_tmp_cleanup(true);
$compressDir = rtrim(SnapIO::safePath($archive->PackDir), '/');
$zipPath = SnapIO::safePath("{$package->StorePath}/{$archive->File}");
$sql_filepath = SnapIO::safePath("{$package->StorePath}/{$package->Database->File}");
$filterDirs = empty($archive->FilterDirs) ? 'not set' : rtrim(str_replace(';', "\n\t", $archive->FilterDirs));
$filterFiles = empty($archive->FilterFiles) ? 'not set' : rtrim(str_replace(';', "\n\t", $archive->FilterFiles));
$filterExts = empty($archive->FilterExts) ? 'not set' : $archive->FilterExts;
$filterOn = ($archive->FilterOn) ? 'ON' : 'OFF';
$scanFilepath = DUPLICATOR_PRO_SSDIR_PATH_TMP . "/{$package->NameHash}_scan.json";
// LOAD SCAN REPORT
try {
$scanReport = $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;
}
DUP_PRO_Log::info("\n********************************************************************************");
DUP_PRO_Log::info("ARCHIVE Type=ZIP Mode=Shell");
DUP_PRO_Log::info("********************************************************************************");
DUP_PRO_Log::info("ARCHIVE DIR: " . $compressDir);
DUP_PRO_Log::info("ARCHIVE FILE: " . basename($zipPath));
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);
$build_progress->archive_started = true;
$build_progress->archive_start_time = DUP_PRO_U::getMicrotime();
$contains_root = false;
$exclude_string = '';
$filterDirs = $archive->FilterDirsAll;
$filterExts = $archive->FilterExtsAll;
$filterFiles = $archive->FilterFilesAll;
// DIRS LIST
foreach ($filterDirs as $filterDir) {
if (trim($filterDir) != '') {
$relative_filter_dir = DUP_PRO_U::getRelativePath($compressDir, $filterDir);
DUP_PRO_Log::trace("Adding relative filter dir $relative_filter_dir for $filterDir relative to $compressDir");
if (trim($relative_filter_dir) == '') {
$contains_root = true;
break;
} else {
$exclude_string .= DUP_PRO_Zip_U::customShellArgEscapeSequence($relative_filter_dir) . "**\* ";
$exclude_string .= DUP_PRO_Zip_U::customShellArgEscapeSequence($relative_filter_dir) . " ";
}
}
}
//EXT LIST
foreach ($filterExts as $filterExt) {
$exclude_string .= "\*.$filterExt ";
}
//FILE LIST
foreach ($filterFiles as $filterFile) {
if (trim($filterFile) != '') {
$relative_filter_file = DUP_PRO_U::getRelativePath($compressDir, trim($filterFile));
DUP_PRO_Log::trace("Full file=$filterFile relative=$relative_filter_file compressDir=$compressDir");
$exclude_string .= "\"$relative_filter_file\" ";
}
}
//DB ONLY
if ($package->isDBOnly()) {
$contains_root = true;
}
if ($contains_root == false) {
// Only attempt to zip things up if root isn't in there since stderr indicates when it cant do anything
$storages = AbstractStorageEntity::getAll();
foreach ($storages as $storage) {
if ($storage->getSType() !== LocalStorage::getSType()) {
continue;
}
/** @var LocalStorage $storage */
if ($storage->isFilterProtection()) {
continue;
}
$storagePath = SnapIO::safePath($storage->getLocationString());
$storagePath = DUP_PRO_U::getRelativePath($compressDir, $storagePath);
$exclude_string .= "$storagePath**\* ";
}
$relative_backup_dir = DUP_PRO_U::getRelativePath($compressDir, DUPLICATOR_PRO_SSDIR_PATH);
$exclude_string .= "$relative_backup_dir**\* ";
$params = Shell::getCompressionParam($build_progress->current_build_compression);
if (strlen($package->Archive->getArchivePassword()) > 0) {
$params .= ' --password ' . escapeshellarg($package->Archive->getArchivePassword());
}
$params .= ' -rq';
$command = 'cd ' . escapeshellarg($compressDir);
$command .= ' && ' . escapeshellcmd(DUP_PRO_Zip_U::getShellExecZipPath()) . ' ' . $params . ' ';
$command .= escapeshellarg($zipPath) . ' ./';
$command .= " -x $exclude_string 2>&1";
DUP_PRO_Log::infoTrace("SHELL COMMAND: $command");
$shellOutput = Shell::runCommand($command, Shell::AVAILABLE_COMMANDS);
DUP_PRO_Log::trace("After shellzip command");
if ($shellOutput !== false && !$shellOutput->isEmpty()) {
$stderr = $shellOutput->getOutputAsString();
$error_text = "Error executing shell exec zip: $stderr.";
$system_global = SystemGlobalEntity::getInstance();
if (DUP_PRO_STR::contains($stderr, 'quota')) {
$fix_text = __("Account out of space so purge large files or talk to your host about increasing quota.", 'duplicator-pro');
$system_global->addTextFix($error_text, $fix_text);
} elseif (DUP_PRO_STR::contains($stderr, 'such file or')) {
$fix_text = sprintf(
"%s <a href='" . DUPLICATOR_PRO_DUPLICATOR_DOCS_URL . "how-to-resolve-zip-format-related-build-issues' target='_blank'>%s</a>",
__('See FAQ:', 'duplicator-pro'),
__('How to resolve "zip warning: No such file or directory"?', 'duplicator-pro')
);
$system_global->addTextFix($error_text, $fix_text);
} else {
$fix_text = __("Click on button to switch to the DupArchive engine.", 'duplicator-pro');
$system_global->addQuickFix(
$error_text,
$fix_text,
array(
'global' => array('archive_build_mode' => 3),
)
);
}
DUP_PRO_Log::error("$error_text **RECOMMENDATION: $fix_text", '', false);
$build_progress->failed = true;
return true;
} else {
DUP_PRO_Log::trace("Stderr is null");
}
$file_count_string = '';
if (!file_exists($zipPath)) {
$file_count_string = sprintf(__('Zip file %s does not exist.', 'duplicator-pro'), $zipPath);
} elseif (DUP_PRO_U::getExeFilepath('zipinfo') != null) {
DUP_PRO_Log::trace("zipinfo exists");
$file_count_string = "zipinfo -t '$zipPath'";
} elseif (DUP_PRO_U::getExeFilepath('unzip') != null) {
DUP_PRO_Log::trace("zipinfo doesn't exist so reverting to unzip");
$file_count_string = "unzip -l '$zipPath' | wc -l";
}
if ($file_count_string != '') {
$shellOutput = Shell::runCommand($file_count_string . ' | awk \'{print $1 }\'', Shell::AVAILABLE_COMMANDS);
$file_count = ($shellOutput !== false)
? trim($shellOutput->getOutputAsString())
: null;
if (is_numeric($file_count)) {
// Accounting for the sql and installer back files
$archive->file_count = (int) $file_count + 2;
} else {
$error_text = sprintf(
__("Error retrieving file count in shell zip %s.", 'duplicator-pro'),
$file_count_string
);
DUP_PRO_Log::trace("Executed file count string of $file_count_string");
DUP_PRO_Log::trace($error_text);
$fix_text = __("Click on button to switch to the DupArchive engine.", 'duplicator-pro');
$system_global = SystemGlobalEntity::getInstance();
$system_global->addQuickFix(
$error_text,
$fix_text,
array(
'global' => array('archive_build_mode' => 3),
)
);
DUP_PRO_Log::error("$error_text **RECOMMENDATION:$fix_text", '', false);
DUP_PRO_Log::trace("$error_text **RECOMMENDATION:$fix_text");
$build_progress->failed = true;
$archive->file_count = -2;
return true;
}
} else {
DUP_PRO_Log::trace("zipinfo doesn't exist");
// The -1 and -2 should be constants since they signify different things
$archive->file_count = -1;
}
} else {
$archive->file_count = 2;
// Installer bak and database.sql
}
DUP_PRO_Log::trace("archive file count from shellzip is $archive->file_count");
$build_progress->archive_built = true;
$build_progress->retries = 0;
$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());
} catch (Exception $e) {
DUP_PRO_Log::error("Runtime error in shell exec zip compression.", "Exception: {$e}");
}
return true;
}
}

View File

@@ -0,0 +1,665 @@
<?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);
}
}

View File

@@ -0,0 +1,404 @@
<?php
/**
* Build insert iterator
* Standard: PSR-2
*
* @link http://www.php-fig.org/psr/psr-2
*/
defined('ABSPATH') || defined('DUPXABSPATH') || exit;
use Duplicator\Libs\Snap\SnapLog;
/**
* Dump database tables for PHPDump (single and multi)
*/
class DUP_PRO_DB_Build_Iterator implements Iterator
{
const TEMP_COUNTER_FILE_PREFIX = 'dup_pro_db_build_progress_';
const UPDATE_POINTER_FILE_EACH_ROWS = 500;
/** @var ?string store file where pute last offsets */
private $storeProgressFile = null;
/** @var bool is true if use store progress file */
private $isStoreProgress = false;
/** @var bool */
private $isValid = false;
/** @var string[] tables list to iterate */
private $tables = array();
/** @var int count of tables */
private $numTables = 0;
/** @var int current table index */
private $tableIndex = -1;
/** @var int current table offset */
private $tableOffset = 0;
/** @var int table rows */
private $tableRows = 0;
/** @var mixed is last index offset, can be last primary key or unique key, single o compound */
private $lastIndexOffset = 0;
/** @var int total rows insered count. */
private $totalRowsOffset = 0;
/** @var int files size */
private $fileSize = 0;
/** @var bool This value becomes true only by calling a specific function and returns false to the first next row or table. */
private $lastIsCompleteInsert = false;
/** @var callable function called at the beginning of the table parsing. */
private $startTableCallback = null;
/** @var callable function called at the end of the table parsing. */
private $endTableCallback = null;
/** @var int */
private $storePointerRowCount = 0;
/**
* Class constructor
*
* @param string[] $tables tables list to iterate
* @param string|null $storeProgressFile if null the store progress file system isn\'t used. I'ts faster
* @param callable|null $startTableCallback callback called at the begin of current table insert
* @param callable|null $endTableCallback ccallback called at the end of current table insert
*/
public function __construct($tables, $storeProgressFile = null, $startTableCallback = null, $endTableCallback = null)
{
$this->tables = (array) $tables;
$this->numTables = count($this->tables);
if ($this->setStoreProgressFile($storeProgressFile) == false) {
throw new Exception('Can\t set progress file');
}
$this->setPosition();
if (is_callable($startTableCallback)) {
$this->startTableCallback = $startTableCallback;
}
if (is_callable($endTableCallback)) {
$this->endTableCallback = $endTableCallback;
}
}
/**
* set current position if progress file exists or rewrind the iterator
*
* @return void
*/
protected function setPosition()
{
if (!$this->isStoreProgress) {
$this->rewind();
return;
}
DUP_PRO_Log::trace("LOAD DATA DATABASE ITERATOR");
if (($content = file_get_contents($this->storeProgressFile)) === false) {
throw new Exception('Can\'t read database store progress file');
}
if (strlen($content) === 0) {
throw new Exception('Store progress file is empty');
}
if (($data = json_decode($content)) === null) {
throw new Exception('Can\'t decode json progress data content: ' . SnapLog::v2str($content));
}
$this->tableIndex = $data[0];
$this->tableOffset = $data[1];
$this->lastIndexOffset = is_scalar($data[2]) ? $data[2] : (array) $data[2];
$this->totalRowsOffset = $data[3];
$this->lastIsCompleteInsert = $data[4];
$this->tableRows = $data[5];
$this->fileSize = $data[6];
$this->isValid = $data[7];
DUP_PRO_Log::trace("SET POSITION TABLE INDEX " . $this->tableIndex . " OFFSET INDEX " . SnapLog::v2str($this->lastIndexOffset));
}
/**
* save current position in progress file if initialized
*
* @return bool
*/
protected function saveCounterFile()
{
$this->storePointerRowCount = 0;
if (!$this->isStoreProgress) {
return true;
}
$data = array(
$this->tableIndex,
$this->tableOffset,
$this->lastIndexOffset,
$this->totalRowsOffset,
$this->lastIsCompleteInsert,
$this->tableRows,
$this->fileSize,
$this->isValid,
);
if (($dataEncoded = json_encode($data)) === false) {
throw new Exception('Can\'t encode database iterator pointer DATA: ' . SnapLog::v2str($data));
}
// file_put_content is less optimized than fopen,
// fwrite but in some serve keep the hadler file open and do fseek in massive way rarely generate corrupted files.
// So writing and closing the file is the safest method.
if (file_put_contents($this->storeProgressFile, $dataEncoded) !== strlen($dataEncoded)) {
throw new Exception('Can\'t write database store progress file');
}
return true;
}
/**
* rewind current iterator (reset all offset and table counts)
*
* @return void
*/
#[\ReturnTypeWillChange]
public function rewind()
{
DUP_PRO_Log::infoTrace("REWIND DATABASE ITERATOR");
$this->tableIndex = -1;
$this->tableOffset = 0;
$this->lastIndexOffset = 0;
$this->totalRowsOffset = 0;
$this->lastIsCompleteInsert = true;
$this->tableRows = 0;
$this->fileSize = 0;
$this->storePointerRowCount = 0;
$this->next();
}
/**
* remove store progress file
*
* @return void
*/
public function removeCounterFile()
{
if (file_exists($this->storeProgressFile)) {
unlink($this->storeProgressFile);
}
$this->isStoreProgress = false;
$this->storeProgressFile = null;
}
/**
* open store pregress file, if don't exists create and initialize it.
*
* @param string $storeProgressFile path to store progress file
*
* @return boolean
*/
public function setStoreProgressFile($storeProgressFile = null)
{
$this->storeProgressFile = null;
$this->isStoreProgress = false;
if (empty($storeProgressFile)) {
return true;
}
if (($fileExists = file_exists($storeProgressFile))) {
if (!is_writable($storeProgressFile)) {
return false;
}
} elseif (!is_writable(dirname($storeProgressFile))) {
return false;
}
$this->storeProgressFile = $storeProgressFile;
$this->isStoreProgress = true;
if (!$fileExists) {
$this->rewind();
}
return true;
}
/**
* next element (table) of iterator, put all table offsets at 0 and count tableRows
*
* If set call endTableCallback and startTableCallback
*
* @return boolean
*/
#[\ReturnTypeWillChange]
public function next()
{
if ($this->tableIndex >= 0 && is_callable($this->endTableCallback)) {
call_user_func($this->endTableCallback, $this);
}
$this->tableOffset = 0;
$this->lastIndexOffset = 0;
$this->tableRows = 0;
$this->tableIndex++;
if (($this->isValid = ($this->tableIndex < $this->numTables))) {
$res = DUP_PRO_DB::getTablesRows($this->current());
$this->tableRows = $res[$this->current()];
if (is_callable($this->startTableCallback)) {
call_user_func($this->startTableCallback, $this);
}
DUP_PRO_Log::infoTrace("INSERT ROWS TABLE[INDEX:" . $this->tableIndex . "] " . $this->tables[$this->tableIndex] . " NUM ROWS: " . $this->tableRows);
}
$this->saveCounterFile();
return $this->isValid;
}
/**
* increment current table offsets and update store process file if exists
*
* @param mixed $lastIndexOffset last index offset selected, can be a primary key or mixed unique key also composed
* @param int $addFileSize add file size to current file size
*
* @return int return total rows parsed count
*/
public function nextRow($lastIndexOffset = 0, $addFileSize = 0)
{
$this->totalRowsOffset++;
$this->tableOffset++;
$this->lastIndexOffset = $lastIndexOffset;
$this->lastIsCompleteInsert = false;
$this->fileSize += $addFileSize;
$this->storePointerRowCount++;
if ($this->storePointerRowCount >= self::UPDATE_POINTER_FILE_EACH_ROWS) {
$this->saveCounterFile();
}
return $this->totalRowsOffset;
}
/**
* set last is complete inster at true and save it in store preocess file.
*
* @param int $addFileSize size to add
*
* @return void
*/
public function setLastIsCompleteInsert($addFileSize = 0)
{
$this->fileSize += $addFileSize;
$this->lastIsCompleteInsert = true;
$this->saveCounterFile();
}
/**
* @param int $fileSize Size to add
*
* @return void
*/
public function addFileSize($fileSize = 0)
{
$this->fileSize += $fileSize;
$this->saveCounterFile();
}
/**
* @return bool
*/
public function isCurrentTableOffsetValid()
{
return $this->tableOffset < $this->tableRows;
}
/**
*
* @return bool
*/
#[\ReturnTypeWillChange]
public function valid()
{
return $this->isValid;
}
/**
*
* @return string|bool // current table name or false if isn\'t valid
*/
#[\ReturnTypeWillChange]
public function current()
{
return $this->isValid ? $this->tables[$this->tableIndex] : false;
}
/**
*
* @return int // table rows of current table
*/
public function getCurrentRows()
{
return $this->tableRows;
}
/**
*
* @return int // current offset of current table
*/
public function getCurrentOffset()
{
return $this->tableOffset;
}
/**
*
* @return mixed // last index offset selecte, can be a primary key or mixed unique key also composed
*/
public function getLastIndexOffset()
{
return $this->lastIndexOffset;
}
/**
*
* @return int // total rows dumped
*/
public function getTotalsRowsOffset()
{
return $this->totalRowsOffset;
}
/**
*
* @return int // stored file size
*/
public function getFileSize()
{
return $this->fileSize;
}
/**
*
* @return bool // return true if the last inserted sub loop is completed
*/
public function lastIsCompleteInsert()
{
return $this->lastIsCompleteInsert;
}
/**
*
* @return int // current table index
*/
#[\ReturnTypeWillChange]
public function key()
{
return $this->tableIndex;
}
/**
*
* @return int // num table to process
*/
public function count()
{
return $this->numTables;
}
}

View File

@@ -0,0 +1,128 @@
<?php
use Duplicator\Libs\Snap\SnapDB;
class DUP_PRO_Multisite
{
/** @var int[] */
public $FilterSites = array();
/** @var ?string[] */
protected $tablesFilters = null;
/**
* Filter props on json encode
*
* @return string[]
*/
public function __sleep()
{
$props = array_keys(get_object_vars($this));
return array_diff($props, array('tablesFilters'));
}
/**
* Wakeup
*
* @return void
*/
public function __wakeup()
{
if (is_string($this->FilterSites)) {
$this->FilterSites = [];
}
}
/**
* Get dirs to filter
*
* @return string[]
*/
public function getDirsToFilter()
{
if (!empty($this->FilterSites)) {
$path_arr = array();
$wp_content_dir = str_replace("\\", "/", WP_CONTENT_DIR);
foreach ($this->FilterSites as $site_id) {
if ($site_id == 1) {
if (DUP_PRO_MU::getGeneration() == DUP_PRO_MU_Generations::ThreeFivePlus) {
$uploads_dir = $wp_content_dir . '/uploads';
foreach (scandir($uploads_dir) as $node) {
$fullpath = $uploads_dir . '/' . $node;
if ($node == '.' || $node == '.htaccess' || $node == '..') {
continue;
}
if (is_dir($fullpath)) {
if ($node != 'sites') {
$path_arr[] = $fullpath;
}
}
}
} else {
$path_arr[] = $wp_content_dir . '/uploads';
}
} else {
if (file_exists($wp_content_dir . '/uploads/sites/' . $site_id)) {
$path_arr[] = $wp_content_dir . '/uploads/sites/' . $site_id;
}
if (file_exists($wp_content_dir . '/blogs.dir/' . $site_id)) {
$path_arr[] = $wp_content_dir . '/blogs.dir/' . $site_id;
}
}
}
return $path_arr;
} else {
return array();
}
}
/**
* Get tables to filter
*
* @return string[]
*/
public function getTablesToFilter()
{
if (is_null($this->tablesFilters)) {
global $wpdb;
$this->tablesFilters = array();
if (!empty($this->FilterSites)) {
$prefixes = array();
foreach ($this->FilterSites as $site_id) {
$prefix = $wpdb->get_blog_prefix($site_id);
if ($site_id == 1) {
$default_tables = array(
'commentmeta',
'comments',
'links',
//'options', include always options table
'postmeta',
'posts',
'terms',
'term_relationships',
'term_taxonomy',
'termmeta',
);
foreach ($default_tables as $tb) {
$this->tablesFilters[] = $prefix . $tb;
}
} else {
$prefixes[] = $prefix;
}
}
if (count($prefixes)) {
foreach ($prefixes as &$value) {
$value = SnapDB::quoteRegex($value);
}
$regex = '^(' . implode('|', $prefixes) . ').+';
$sql_query = "SHOW TABLES WHERE Tables_in_" . esc_sql(DB_NAME) . " REGEXP '" . esc_sql($regex) . "'";
DUP_PRO_Log::trace('TABLE QUERY PREFIX FILTER: ' . $sql_query);
$sub_tables = $wpdb->get_col($sql_query);
$this->tablesFilters = array_merge($this->tablesFilters, $sub_tables);
}
}
DUP_PRO_Log::traceObject('TABLES TO FILTERS:', $this->tablesFilters);
}
return $this->tablesFilters;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,601 @@
<?php
/*
Duplicator Pro Plugin
Copyright (C) 2016, Snap Creek LLC
website: snapcreek.com
Duplicator Pro Plugin is distributed under the GNU General Public License, Version 3,
June 2007. Copyright (C) 2007 Free Software Foundation, Inc., 51 Franklin
St, Fifth Floor, Boston, MA 02110, USA
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
defined('ABSPATH') || defined('DUPXABSPATH') || exit;
use Duplicator\Addons\ProBase\License\License;
use Duplicator\Libs\Snap\SnapURL;
use Duplicator\Libs\Snap\SnapUtil;
use Duplicator\Models\Storages\StoragesUtil;
use Duplicator\Models\SystemGlobalEntity;
class DUP_PRO_Package_Runner
{
const DEFAULT_MAX_BUILD_TIME_IN_MIN = 270;
const PACKAGE_STUCK_TIME_IN_SEC = 375; // 75 x 5;
/** @var bool */
public static $delayed_exit_and_kickoff = false;
/**
* Init package runner
*
* @return void
* @throws Exception
*/
public static function init()
{
$kick_off_worker = false;
$global = DUP_PRO_Global_Entity::getInstance();
$system_global = SystemGlobalEntity::getInstance();
if ($global->clientside_kickoff === false) {
if ((time() - $system_global->package_check_ts) < DUP_PRO_Constants::PACKAGE_CHECK_TIME_IN_SEC) {
return;
}
}
if ($global->lock_mode == DUP_PRO_Thread_Lock_Mode::Flock) {
$locking_file = @fopen(DUPLICATOR_PRO_LOCKING_FILE_FILENAME, 'c+');
} else {
$locking_file = true;
}
DUP_PRO_Log::trace('Running package runner init');
if ($locking_file != false) {
if ($global->lock_mode == DUP_PRO_Thread_Lock_Mode::Flock) {
$acquired_lock = (flock($locking_file, LOCK_EX | LOCK_NB) != false);
if ($acquired_lock) {
DUP_PRO_Log::trace("File lock acquired: " . DUPLICATOR_PRO_LOCKING_FILE_FILENAME);
} else {
DUP_PRO_Log::trace("File lock denied " . DUPLICATOR_PRO_LOCKING_FILE_FILENAME);
}
} else {
$acquired_lock = DUP_PRO_U::getSqlLock();
}
if ($acquired_lock) {
DUP_PRO_Log::trace("Acquired lock so executing package runner init core code");
$system_global->package_check_ts = time();
$system_global->save();
$pending_cancellations = DUP_PRO_Package::get_pending_cancellations();
self::cancel_long_running($pending_cancellations);
if (count($pending_cancellations) > 0) {
foreach ($pending_cancellations as $package_id_to_cancel) {
DUP_PRO_Log::trace("looking to cancel $package_id_to_cancel");
$package_to_cancel = DUP_PRO_Package::get_by_id((int) $package_id_to_cancel);
if ($package_to_cancel == false) {
continue;
}
if ($package_to_cancel->Status == DUP_PRO_PackageStatus::STORAGE_PROCESSING) {
$package_to_cancel->cancel_all_uploads();
$package_to_cancel->process_storages();
$package_to_cancel->set_status(DUP_PRO_PackageStatus::STORAGE_CANCELLED);
} else {
$package_to_cancel->set_status(DUP_PRO_PackageStatus::BUILD_CANCELLED);
}
$package_to_cancel->post_scheduled_build_failure();
}
DUP_PRO_Package::clear_pending_cancellations();
}
if (empty($_REQUEST['action']) || $_REQUEST['action'] != 'duplicator_pro_process_worker') {
self::process_schedules();
$kick_off_worker = DUP_PRO_Package::isPackageRunning();
}
if ($global->lock_mode == DUP_PRO_Thread_Lock_Mode::Flock) {
if (!flock($locking_file, LOCK_UN)) {
DUP_PRO_Log::trace("File lock cant release " . $locking_file);
} else {
DUP_PRO_Log::trace("File lock released " . $locking_file);
}
fclose($locking_file);
} else {
DUP_PRO_U::releaseSqlLock();
}
}
} else {
DUP_PRO_Log::trace("Problem opening locking file so auto switching to SQL lock mode");
$global->lock_mode = DUP_PRO_Thread_Lock_Mode::SQL_Lock;
$global->save();
exit();
}
if ($kick_off_worker || self::$delayed_exit_and_kickoff) {
self::kick_off_worker();
} elseif (is_admin() && (isset($_REQUEST['page']) && (strpos($_REQUEST['page'], DUP_PRO_Constants::PLUGIN_SLUG) !== false))) {
DUP_PRO_Log::trace("************kicking off slug worker");
// If it's one of our pages force it to kick off the client
self::kick_off_worker(true);
}
if (self::$delayed_exit_and_kickoff) {
self::$delayed_exit_and_kickoff = false;
exit();
}
}
/**
* Add javascript for cliean side Kick off
*
* @return void
*/
public static function add_kickoff_worker_javascript()
{
$global = DUP_PRO_Global_Entity::getInstance();
$custom_url = strtolower($global->custom_ajax_url);
$CLIENT_CALL_PERIOD_IN_MS = 20000;
// How often client calls into the service
if ($global->ajax_protocol == 'custom') {
if (DUP_PRO_STR::startsWith($custom_url, 'http')) {
$ajax_url = $custom_url;
} else {
// Revert to http standard if they don't have the url correct
$ajax_url = admin_url('admin-ajax.php', 'http');
DUP_PRO_Log::trace("Even though custom ajax url configured, incorrect url set so reverting to $ajax_url");
}
} else {
$ajax_url = admin_url('admin-ajax.php', $global->ajax_protocol);
}
$gateway = array(
'ajaxurl' => $ajax_url,
'client_call_frequency' => $CLIENT_CALL_PERIOD_IN_MS,
'duplicator_pro_process_worker_nonce' => wp_create_nonce('duplicator_pro_process_worker'),
);
wp_register_script('dup-pro-kick', DUPLICATOR_PRO_PLUGIN_URL . 'assets/js/dp-kick.js', array('jquery'), DUPLICATOR_PRO_VERSION);
wp_localize_script('dup-pro-kick', 'dp_gateway', $gateway);
DUP_PRO_Log::trace('KICKOFF: Client Side');
wp_enqueue_script('dup-pro-kick');
}
/**
* Checks active packages for being stuck or running too long and adds them for canceling
*
* @param int[] $pending_cancellations List of package ids to be cancelled
*
* @return void
*/
public static function cancel_long_running(&$pending_cancellations)
{
if (!DUP_PRO_Package::isPackageRunning()) {
return;
}
$active_package = DUP_PRO_Package::get_next_active_package();
if ($active_package === null) {
DUP_PRO_Log::trace("Active package returned null");
return;
}
$global = DUP_PRO_Global_Entity::getInstance();
$system_global = SystemGlobalEntity::getInstance();
$buildStarted = $active_package->timer_start > 0;
$active_package->timer_start = $buildStarted ? $active_package->timer_start : DUP_PRO_U::getMicrotime();
$elapsed_sec = $buildStarted ? DUP_PRO_U::getMicrotime() - $active_package->timer_start : 0;
$elapsed_minutes = $elapsed_sec / 60;
$addedForCancelling = false;
if ($buildStarted && $global->max_package_runtime_in_min > 0 && $elapsed_minutes > $global->max_package_runtime_in_min) {
if ($active_package->build_progress->current_build_mode != DUP_PRO_Archive_Build_Mode::DupArchive) {
$system_global->addQuickFix(
__('Package was cancelled because it exceeded Max Build Time.', 'duplicator-pro'),
sprintf(
__(
'Click button to switch to the DupArchive engine. Please see this %1$sFAQ%2$s for other possible solutions.',
'duplicator-pro'
),
'<a href="' . DUPLICATOR_PRO_DUPLICATOR_DOCS_URL . 'how-to-resolve-schedule-build-failures" target="_blank">',
'</a>'
),
array(
'global' => array(
'archive_build_mode' => DUP_PRO_Archive_Build_Mode::DupArchive,
),
)
);
} elseif ($global->max_package_runtime_in_min < self::DEFAULT_MAX_BUILD_TIME_IN_MIN) {
$system_global->addQuickFix(
__('Package was cancelled because it exceeded Max Build Time.', 'duplicator-pro'),
sprintf(
__(
'Click button to increase Max Build Time. Please see this %1$sFAQ%2$s for other possible solutions.',
'duplicator-pro'
),
'<a href="' . DUPLICATOR_PRO_DUPLICATOR_DOCS_URL . 'how-to-resolve-schedule-build-failures" target="_blank">',
'</a>'
),
array(
'global' => array(
'max_package_runtime_in_min' => self::DEFAULT_MAX_BUILD_TIME_IN_MIN,
),
)
);
}
DUP_PRO_Log::infoTrace("Package $active_package->ID has been going for $elapsed_minutes minutes so cancelling. ($elapsed_sec)");
array_push($pending_cancellations, $active_package->ID);
$addedForCancelling = true;
}
if ((($active_package->Status == DUP_PRO_PackageStatus::AFTER_SCAN) || ($active_package->Status == DUP_PRO_PackageStatus::PRE_PROCESS)) && ($global->clientside_kickoff == false)) {
// Traditionally package considered stuck if > 75 but that was with time % 5 so multiplying by 5 to compensate now
if ($elapsed_sec > self::PACKAGE_STUCK_TIME_IN_SEC) {
DUP_PRO_Log::trace("*** STUCK");
$showDefault = true;
if (isset($_SERVER['AUTH_TYPE']) && $_SERVER['AUTH_TYPE'] == 'Basic' && !$global->basic_auth_enabled) {
$system_global->addQuickFix(
__('Set authentication username and password', 'duplicator-pro'),
__('Automatically set basic auth username and password', 'duplicator-pro'),
array(
'special' => array('set_basic_auth' => 1),
)
);
$showDefault = false;
}
if (SnapURL::isCurrentUrlSSL() && $global->ajax_protocol == 'http') {
$system_global->addQuickFix(
__('Communication to AJAX is blocked.', 'duplicator-pro'),
__('Click button to configure plugin to use HTTPS.', 'duplicator-pro'),
array(
'special' => array('stuck_5percent_pending_fix' => 1),
)
);
} elseif (!SnapURL::isCurrentUrlSSL() && $global->ajax_protocol == 'https') {
$system_global->addQuickFix(
__('Communication to AJAX is blocked.', 'duplicator-pro'),
__('Click button to configure plugin to use HTTP.', 'duplicator-pro'),
array(
'special' => array('stuck_5percent_pending_fix' => 1),
)
);
} elseif ($global->ajax_protocol == 'custom') {
$system_global->addQuickFix(
__('Communication to AJAX is blocked.', 'duplicator-pro'),
__('Click button to fix the admin-ajax URL setting.', 'duplicator-pro'),
array(
'special' => array('stuck_5percent_pending_fix' => 1),
)
);
} elseif ($showDefault) {
$system_global->addTextFix(
__('Communication to AJAX is blocked.', 'duplicator-pro'),
sprintf(
"%s <a href='" . DUPLICATOR_PRO_DUPLICATOR_DOCS_URL . "how-to-resolve-builds-getting-stuck-at-a-certain-point/' target='_blank'>%s</a>",
__('See FAQ:', 'duplicator-pro'),
__('Why is the package build stuck at 5%?', 'duplicator-pro')
)
);
}
DUP_PRO_Log::infoTrace("Package $active_package->ID has been stuck for $elapsed_minutes minutes so cancelling. ($elapsed_sec)");
array_push($pending_cancellations, $active_package->ID);
$addedForCancelling = true;
}
}
if ($addedForCancelling) {
$active_package->buildFail(
'Package was cancelled because it exceeded Max Build Time.',
false
);
} else {
$active_package->save();
}
}
/**
* Kick off worker
*
* @param bool $run_only_if_client If true then only kick off worker if the request came from the client
*
* @return void
*/
public static function kick_off_worker($run_only_if_client = false)
{
/* @var $global DUP_PRO_Global_Entity */
$global = DUP_PRO_Global_Entity::getInstance();
if (!$run_only_if_client || $global->clientside_kickoff) {
$calling_function_name = SnapUtil::getCallingFunctionName();
DUP_PRO_Log::trace("Kicking off worker process as requested by $calling_function_name");
$custom_url = strtolower($global->custom_ajax_url);
if ($global->ajax_protocol == 'custom') {
if (DUP_PRO_STR::startsWith($custom_url, 'http')) {
$ajax_url = $custom_url;
} else {
// Revert to http standard if they don't have the url correct
$ajax_url = admin_url('admin-ajax.php', 'http');
DUP_PRO_Log::trace("Even though custom ajax url configured, incorrect url set so reverting to $ajax_url");
}
} else {
$ajax_url = admin_url('admin-ajax.php', $global->ajax_protocol);
}
DUP_PRO_Log::trace("Attempting to use ajax url $ajax_url");
if ($global->clientside_kickoff) {
add_action('wp_enqueue_scripts', 'DUP_PRO_Package_Runner::add_kickoff_worker_javascript');
add_action('admin_enqueue_scripts', 'DUP_PRO_Package_Runner::add_kickoff_worker_javascript');
} else {
// Server-side kickoff
$ajax_url = SnapURL::appendQueryValue($ajax_url, 'action', 'duplicator_pro_process_worker');
$ajax_url = SnapURL::appendQueryValue($ajax_url, 'now', time());
// $duplicator_pro_process_worker_nonce = wp_create_nonce('duplicator_pro_process_worker');
//require_once(ABSPATH.'wp-includes/pluggable.php');
//$ajax_url = wp_nonce_url($ajax_url, 'duplicator_pro_process_worker', 'nonce');
DUP_PRO_Log::trace('KICKOFF: Server Side');
if ($global->basic_auth_enabled) {
$sglobal = DUP_PRO_Secure_Global_Entity::getInstance();
$args = array(
'blocking' => false,
'headers' => array('Authorization' => 'Basic ' . base64_encode($global->basic_auth_user . ':' . $sglobal->basic_auth_password)),
);
} else {
$args = array('blocking' => false);
}
$args['sslverify'] = false;
wp_remote_get($ajax_url, $args);
}
DUP_PRO_Log::trace("after sent kickoff request");
}
}
/**
* Process schedules by cron
*
* @return void
*/
public static function process()
{
if (!defined('WP_MAX_MEMORY_LIMIT')) {
define('WP_MAX_MEMORY_LIMIT', '512M');
}
if (SnapUtil::isIniValChangeable('memory_limit')) {
@ini_set('memory_limit', WP_MAX_MEMORY_LIMIT);
}
@set_time_limit(7200);
@ignore_user_abort(true);
if (SnapUtil::isIniValChangeable('pcre.backtrack_limit')) {
@ini_set('pcre.backtrack_limit', (string) PHP_INT_MAX);
}
if (SnapUtil::isIniValChangeable('default_socket_timeout')) {
@ini_set('default_socket_timeout', '7200');
// 2 Hours
}
/* @var $global DUP_PRO_Global_Entity */
$global = DUP_PRO_Global_Entity::getInstance();
if ($global->clientside_kickoff) {
DUP_PRO_Log::trace("PROCESS: From client");
session_write_close();
} else {
DUP_PRO_Log::trace("PROCESS: From server");
}
// Only attempt to process schedules if manual isn't running
if ($global->lock_mode == DUP_PRO_Thread_Lock_Mode::Flock) {
$locking_file = fopen(DUPLICATOR_PRO_LOCKING_FILE_FILENAME, 'c+');
} else {
$locking_file = true;
}
if ($locking_file == false) {
DUP_PRO_Log::trace("Problem opening locking file so auto switching to SQL lock mode");
$global->lock_mode = DUP_PRO_Thread_Lock_Mode::SQL_Lock;
$global->save();
exit();
}
// Here we know that $locking_file != false
if ($global->lock_mode == DUP_PRO_Thread_Lock_Mode::Flock) {
$acquired_lock = (flock($locking_file, LOCK_EX | LOCK_NB) != false);
if ($acquired_lock) {
DUP_PRO_Log::trace("File lock acquired " . $locking_file);
} else {
DUP_PRO_Log::trace("File lock denied " . $locking_file);
}
} else {
// DUP_PRO_U::getSqlLock will write details into trace log, logging is not needed here
$acquired_lock = DUP_PRO_U::getSqlLock();
}
if (!$acquired_lock) {
// File locked so another cron already running so just skip
DUP_PRO_Log::trace("File locked so skipping");
return;
}
// Here we know that $acquired_lock == true
self::process_schedules();
$package = DUP_PRO_Package::get_next_active_package();
if ($package != null) {
StoragesUtil::getDefaultStorage()->initStorageDirectory(true);
$dup_tests = self::get_requirements_tests();
if ($dup_tests['Success'] == true) {
$start_time = time();
DUP_PRO_Log::trace("PACKAGE $package->ID:PROCESSING");
ignore_user_abort(true);
if ($package->Status < DUP_PRO_PackageStatus::AFTER_SCAN) {
// Scan step built into package build - used by schedules - NOT manual build where scan is done in web service.
DUP_PRO_Log::trace("PACKAGE $package->ID:SCANNING");
//After scanner runs. Save FilterInfo (unreadable, warnings, globals etc)
$package->create_scan_report();
$package->update();
//del if($package->Archive->ScanStatus == DUP_PRO_Archive::ScanStatusComplete){
$dupe_package = DUP_PRO_Package::get_by_id($package->ID);
$dupe_package->set_status(DUP_PRO_PackageStatus::AFTER_SCAN);
//del }
$end_time = time();
$scan_time = $end_time - $start_time;
//del $end_time = DUP_PRO_U::getMicrotime();
//
// $scan_time = $end_time - $package->Archive->ScanTimeStart;
DUP_PRO_Log::trace("SCAN TIME=$scan_time seconds");
} elseif ($package->Status < DUP_PRO_PackageStatus::COPIEDPACKAGE) {
DUP_PRO_Log::trace("PACKAGE $package->ID:BUILDING");
$package->run_build();
$end_time = time();
$build_time = $end_time - $start_time;
DUP_PRO_Log::trace("BUILD TIME=$build_time seconds");
} elseif ($package->Status < DUP_PRO_PackageStatus::COMPLETE) {
DUP_PRO_Log::trace("PACKAGE $package->ID:STORAGE PROCESSING");
$package->set_status(DUP_PRO_PackageStatus::STORAGE_PROCESSING);
$package->process_storages();
$end_time = time();
$build_time = $end_time - $start_time;
DUP_PRO_Log::trace("STORAGE CHUNK PROCESSING TIME=$build_time seconds");
if ($package->Status == DUP_PRO_PackageStatus::COMPLETE) {
DUP_PRO_Log::trace("PACKAGE $package->ID COMPLETE");
} elseif ($package->Status == DUP_PRO_PackageStatus::ERROR) {
DUP_PRO_Log::trace("PACKAGE $package->ID IN ERROR STATE");
}
$packageCompleteStatuses = array(
DUP_PRO_PackageStatus::COMPLETE,
DUP_PRO_PackageStatus::ERROR,
);
if (in_array($package->Status, $packageCompleteStatuses)) {
$info = "\n";
$info .= "********************************************************************************\n";
$info .= "********************************************************************************\n";
$info .= "DUPLICATOR PRO PACKAGE CREATION OR MANUAL STORAGE TRANSFER END: " . @date("Y-m-d H:i:s") . "\n";
$info .= "NOTICE: Do NOT post to public sites or forums \n";
$info .= "********************************************************************************\n";
$info .= "********************************************************************************\n";
DUP_PRO_Log::infoTrace($info);
}
}
ignore_user_abort(false);
} else {
DUP_PRO_Log::open($package->NameHash);
if ($dup_tests['RES']['INSTALL'] == 'Fail') {
DUP_PRO_Log::info('Installer files still present on site. Remove using Tools > Stored Data > "Remove Installer Files".');
}
DUP_PRO_Log::error(__('Requirements Failed', 'duplicator-pro'), print_r($dup_tests, true), false);
DUP_PRO_Log::traceError('Requirements didn\'t pass so can\'t perform backup!');
$package->post_scheduled_build_failure($dup_tests);
$package->set_status(DUP_PRO_PackageStatus::REQUIREMENTS_FAILED);
}
}
//$kick_off_worker = (DUP_PRO_Package::get_next_active_package() != null);
$kick_off_worker = DUP_PRO_Package::isPackageRunning();
if ($global->lock_mode == DUP_PRO_Thread_Lock_Mode::Flock) {
DUP_PRO_Log::trace("File lock released");
if (!flock($locking_file, LOCK_UN)) {
DUP_PRO_Log::trace("File lock cant release " . $locking_file);
} else {
DUP_PRO_Log::trace("File lock released " . $locking_file);
}
fclose($locking_file);
} else {
DUP_PRO_U::releaseSqlLock();
}
if ($kick_off_worker) {
self::kick_off_worker();
}
}
/**
* Gets the requirements tests
*
* @return array<string,mixed>
*/
public static function get_requirements_tests()
{
$dup_tests = DUP_PRO_Server::getRequirments();
if ($dup_tests['Success'] != true) {
DUP_PRO_Log::traceObject('requirements', $dup_tests);
}
return $dup_tests;
}
/**
* Calculates the earliest schedule run time
*
* @return int
*/
public static function calculate_earliest_schedule_run_time()
{
if (!License::can(License::CAPABILITY_SCHEDULE)) {
return -1;
}
$next_run_time = PHP_INT_MAX;
$schedules = DUP_PRO_Schedule_Entity::get_active();
foreach ($schedules as $schedule) {
if ($schedule->next_run_time == -1) {
$schedule->updateNextRuntime();
}
if ($schedule->next_run_time !== -1 && $schedule->next_run_time < $next_run_time) {
$next_run_time = $schedule->next_run_time;
}
}
if ($next_run_time == PHP_INT_MAX) {
$next_run_time = -1;
}
return $next_run_time;
}
/**
* Start schedule package creation
*
* @return void
*/
public static function process_schedules()
{
// Hack fix - observed issue on a machine where schedule process bombs
$next_run_time = self::calculate_earliest_schedule_run_time();
if ($next_run_time != -1 && ($next_run_time <= time())) {
$schedules = DUP_PRO_Schedule_Entity::get_active();
foreach ($schedules as $schedule) {
$schedule->process();
}
}
}
}

View File

@@ -0,0 +1,355 @@
<?php
use Duplicator\Models\Storages\AbstractStorageEntity;
use Duplicator\Models\Storages\Local\DefaultLocalStorage;
use Duplicator\Models\Storages\StoragesUtil;
use Duplicator\Models\Storages\UnknownStorage;
use VendorDuplicator\Amk\JsonSerialize\JsonSerialize;
defined("ABSPATH") or die("");
abstract class DUP_PRO_Upload_Status
{
const Pending = 0;
const Running = 1;
const Succeeded = 2;
const Failed = 3;
const Cancelled = 4;
}
// Tracks the progress of the package with relation to a specific storage provider
// Used to track a specific upload as well as later report on its' progress
class DUP_PRO_Package_Upload_Info
{
/** @var int<-1,max> */
protected $storage_id = -1;
/** @var int */
public $archive_offset = 0;
/** @var bool Next byte of archive to copy */
public $copied_installer = false;
/** @var bool Whether installer has been copied */
public $copied_archive = false;
/** @var float Whether archive has been copied */
public $progress = 0;
/** @var int 0-100 where this particular storage is at */
public $num_failures = 0;
/** @var bool */
protected $failed = false;
/** @var bool If catastrophic failure has been experienced or num_failures exceeded threshold */
public $cancelled = false;
/** @var scalar */
public $upload_id = null;
/** @var int */
public $failure_count = 0;
/** @var mixed */
public $data = '';
/** @var mixed */
public $data2 = '';
// Storage specific data
// Log related properties - these all SHOULD be public but since we need to json_encode them they have to be public. Ugh.
/** @var bool */
public $has_started = false;
/** @var string */
public $status_message_details = '';
// Details about the storage run (success or failure)
/** @var int */
public $started_timestamp = 0;
/** @var int */
public $stopped_timestamp = 0;
/** @var mixed[] chunk iterator data */
public $chunkPosition = [];
/** @var ?AbstractStorageEntity */
protected $storage = null;
/** @var array<string,mixed> Copy to persistance extra data */
public $copyToExtraData = [];
/**
* Class constructor
*
* @param int $storage_id The storage id
*/
public function __construct($storage_id)
{
$this->setStorageId($storage_id);
}
/**
* Will be called, automatically, when Serialize
*
* @return array<string, mixed>
*/
public function __serialize() // phpcs:ignore PHPCompatibility.FunctionNameRestrictions.NewMagicMethods.__serializeFound
{
$data = JsonSerialize::serializeToData($this, JsonSerialize::JSON_SKIP_MAGIC_METHODS | JsonSerialize::JSON_SKIP_CLASS_NAME);
$data['storage'] = null;
return $data;
}
/**
* Set the storage id
*
* @param int $storage_id The storage id
*
* @return void
*/
public function setStorageId($storage_id)
{
if ($storage_id < 0) {
$this->storage_id = -1;
}
$this->storage_id = (int) $storage_id;
$this->storage = null;
}
/**
* Get the storage object
*
* @return AbstractStorageEntity
*/
public function getStorage()
{
if ($this->storage === null) {
if ($this->storage_id == DefaultLocalStorage::OLD_VIRTUAL_STORAGE_ID) {
// Legacy old packages use virtual storage id -2
$this->storage = StoragesUtil::getDefaultStorage();
$this->storage_id = $this->storage->getId();
} else {
$this->storage = AbstractStorageEntity::getById($this->storage_id);
}
if ($this->storage === false) {
$this->storage = new UnknownStorage();
}
}
return $this->storage;
}
/**
* Get storage id
*
* @return int
*/
public function getStorageId()
{
// For old packages, some storage ids are strings
return (int) $this->storage_id;
}
/**
* Return true if is local
*
* @return bool
*/
public function isLocal()
{
$storage = $this->getStorage();
if ($storage instanceof UnknownStorage) {
return false;
}
return $this->getStorage()->isLocal();
}
/**
* Return true if is remote
*
* @return bool
*/
public function isRemote()
{
$storage = $this->getStorage();
if ($storage instanceof UnknownStorage) {
return false;
}
return !$this->getStorage()->isLocal();
}
/**
* Is failed
*
* @return bool True if upload has failed
*/
public function isFailed()
{
return $this->failed;
}
/**
* Return true if the upload has started
*
* @return bool
*/
public function has_started()
{
return $this->has_started;
}
/**
* Start the upload
*
* @return void
*/
public function start()
{
$this->has_started = true;
$this->started_timestamp = time();
}
/**
* Stop the upload
*
* @return void
*/
public function stop()
{
$this->stopped_timestamp = time();
}
/**
* Get started timestamp
*
* @return int
*/
public function get_started_timestamp()
{
return $this->started_timestamp;
}
/**
* Get stopped timestamp
*
* @return int
*/
public function get_stopped_timestamp()
{
return $this->stopped_timestamp;
}
/**
* Get the status text
*
* @return string
*/
public function get_status_text()
{
$status = $this->get_status();
$status_text = __('Unknown', 'duplicator-pro');
if ($status == DUP_PRO_Upload_Status::Pending) {
$status_text = __('Pending', 'duplicator-pro');
} elseif ($status == DUP_PRO_Upload_Status::Running) {
$status_text = __('Running', 'duplicator-pro');
} elseif ($status == DUP_PRO_Upload_Status::Succeeded) {
$status_text = __('Succeeded', 'duplicator-pro');
} elseif ($status == DUP_PRO_Upload_Status::Failed) {
$status_text = __('Failed', 'duplicator-pro');
} elseif ($status == DUP_PRO_Upload_Status::Cancelled) {
$status_text = __('Cancelled', 'duplicator-pro');
}
return $status_text;
}
/**
* Get the status
*
* @return int
*/
public function get_status()
{
if ($this->cancelled) {
$status = DUP_PRO_Upload_Status::Cancelled;
} elseif ($this->failed) {
$status = DUP_PRO_Upload_Status::Failed;
} elseif ($this->has_started() === false) {
$status = DUP_PRO_Upload_Status::Pending;
} elseif ($this->has_completed(true)) {
$status = DUP_PRO_Upload_Status::Succeeded;
} else {
$status = DUP_PRO_Upload_Status::Running;
}
return $status;
}
/**
* Set the status message details
*
* @param string $status_message_details The status message details
*
* @return void
*/
public function set_status_message_details($status_message_details)
{
$this->status_message_details = $status_message_details;
}
/**
* Get the status message
*
* @return string
*/
public function get_status_message()
{
$message = '';
$status = $this->get_status();
$storage = AbstractStorageEntity::getById($this->storage_id);
if ($storage !== false) {
if ($status == DUP_PRO_Upload_Status::Pending) {
$message = $storage->getPendingText();
} elseif ($status == DUP_PRO_Upload_Status::Failed) {
$message = $storage->getFailedText();
} elseif ($status == DUP_PRO_Upload_Status::Cancelled) {
$message = $storage->getCancelledText();
} elseif ($status == DUP_PRO_Upload_Status::Succeeded) {
$message = $storage->getSuccessText();
} else {
$message = $storage->getActionText();
}
} else {
$message = "Error. Unknown storage id {$this->storage_id}";
DUP_PRO_Log::trace($message);
}
$message_details = $this->status_message_details == '' ? '' : " ($this->status_message_details)";
$message = "$message$message_details";
return $message;
}
/**
* Return true if the upload has completed
*
* @param bool $count_only_success If true then only return true if the upload has completed successfully
*
* @return bool
*/
public function has_completed($count_only_success = false)
{
$retval = false;
if ($count_only_success) {
$retval = (($this->failed == false) && ($this->cancelled == false) && ($this->copied_installer && $this->copied_archive));
} else {
$retval = $this->failed || ($this->copied_installer && $this->copied_archive) || $this->cancelled;
}
if ($retval && ($this->stopped_timestamp == null)) {
// Having to set stopped this way because we aren't OO and allow everyone to set failed/other flags so impossible to know exactly when its done
$this->stop();
}
return $retval;
}
/**
* Increase the failure count
*
* @return void
*/
public function increase_failure_count()
{
$global = DUP_PRO_Global_Entity::getInstance();
$this->failure_count++;
DUP_PRO_Log::infoTrace("Failure count increasing to $this->failure_count [Storage Id: $this->storage_id]");
if ($this->failure_count > $global->max_storage_retries) {
DUP_PRO_Log::infoTrace("* Failure count reached to max level, Storage Status updated to failed [Storage Id: $this->storage_id]");
$this->failed = true;
}
}
}

View File

@@ -0,0 +1,3 @@
<?php
//silent

View File

@@ -0,0 +1,288 @@
<?php
defined("ABSPATH") or die("");
/**
* Used to generate a thick box inline dialog such as an alert or confirm pop-up
*
* Standard: PSR-2
*
* @link http://www.php-fig.org/psr/psr-2
*
* @package DUP_PRO
* @subpackage classes/ui
* @copyright (c) 2017, Snapcreek LLC
* @license https://opensource.org/licenses/GPL-3.0 GNU Public License
* @since 3.3.0
*/
class DUP_PRO_UI_Dialog
{
/** @var int */
protected static $uniqueIdCounter = 0;
/** @var string if not empty contains class of box */
public $boxClass = '';
/** @var bool if false don't disaply ok,confirm and cancel buttons */
public $showButtons = true;
/** @var bool if false don't disaply textarea */
public $showTextArea = false;
/** @var integer rows attribute of textarea */
public $textAreaRows = 15;
/** @var int cols attribute of textarea */
public $textAreaCols = 100;
/** @var string if not empty set class on wrapper buttons div */
public $wrapperClassButtons = null;
/** @var string The title that shows up in the dialog */
public $title = '';
/** @var string The message displayed in the body of the dialog */
public $message = '';
/** @var int The width of the dialog the default is used if not set */
public $width = 500;
/** @var int The height of the dialog the default is used if not set */
public $height = 225;
/** @var string When the progress meter is running show this text, Available only on confirm dialogs */
public $progressText;
/** @var bool When true a progress meter will run until page is reloaded, Available only on confirm dialogs */
public $progressOn = true;
/** @var ?string The javascript call back method to call when the 'Yes' or 'Ok' button is clicked */
public $jsCallback = null;
/** @var string */
public $okText = '';
/** @var string */
public $cancelText = '';
/** @var bool If true close dialog on confirm */
public $closeOnConfirm = false;
/** @var string The id given to the full dialog */
private $id = '';
/** @var int A unique id that is added to all id elements */
private $uniqid = 0;
/**
* Init this object when created
*/
public function __construct()
{
add_thickbox();
$this->progressText = __('Processing please wait...', 'duplicator-pro');
$this->uniqid = ++self::$uniqueIdCounter;
$this->id = 'dpro-dlg-' . $this->uniqid;
$this->okText = __('OK', 'duplicator-pro');
$this->cancelText = __('Cancel', 'duplicator-pro');
}
/**
*
* @return int
*/
public function getUniqueIdCounter()
{
return $this->uniqid;
}
/**
* Gets the unique id that is assigned to each instance of a dialog
*
* @return string The unique ID of this dialog
*/
public function getID()
{
return $this->id;
}
/**
* Gets the unique id that is assigned to each instance of a dialogs message text
*
* @return string The unique ID of the message
*/
public function getMessageID()
{
return "{$this->id}_message";
}
/**
* Display The html content used for the alert dialog
*
* @return void
*/
public function initAlert()
{
?>
<div id="<?php echo $this->id; ?>" style="display:none;" >
<?php if ($this->showTextArea) { ?>
<div class="dpro-dlg-textarea-caption">Status</div>
<textarea id="<?php echo $this->id; ?>_textarea" class="dpro-dlg-textarea" rows="<?php echo $this->textAreaRows; ?>" cols="<?php echo $this->textAreaCols; ?>"></textarea>
<?php } ?>
<div id="<?php echo $this->id; ?>-alert-txt" class="dpro-dlg-alert-txt <?php echo $this->boxClass; ?>" >
<span id="<?php echo $this->id; ?>_message">
<?php echo $this->message; ?>
</span>
</div>
<?php if ($this->showButtons) { ?>
<div class="dpro-dlg-alert-btns <?php echo $this->wrapperClassButtons; ?>" >
<input
id="<?php echo $this->id; ?>-confirm"
type="button"
class="button button-large dup-dialog-confirm"
value="<?php echo esc_attr($this->okText); ?>"
onclick="<?php echo esc_attr($this->closeAlert(false)); ?>"
>
</div>
<?php } ?>
</div>
<?php
}
/**
* Shows the alert base JS code used to display when needed
*
* @return void
*/
public function showAlert()
{
$title = esc_js($this->title);
$id = esc_js($this->id);
$html = "tb_show('" . $title . "', '#TB_inline?width=" . $this->width . "&height=" . $this->height . "&inlineId=" . $id . "');" .
"var styleData = jQuery('#TB_window').attr('style') + 'height: " . $this->height . "px !important';\n" .
"jQuery('#TB_window').attr('style', styleData);" .
"DuplicatorTooltip.reload();";
echo $html;
}
/**
* Close tick box
*
* @param boolean $echo if true echo javascript else return string
*
* @return string
*/
public function closeAlert($echo = true)
{
$onClickClose = '';
if (!is_null($this->jsCallback)) {
$onClickClose .= $this->jsCallback . ';';
}
$onClickClose .= 'tb_remove();';
if ($echo) {
echo $onClickClose;
return '';
} else {
return $onClickClose;
}
}
/**
* js code to update html message content from js var name
*
* @param string $jsVarName js var name
*
* @return void
*/
public function updateMessage($jsVarName)
{
$js = '$("#' . $this->getID() . '_message").html(' . $jsVarName . ');';
echo $js;
}
/**
* js code to update textarea content from js var name
*
* @param string $jsVarName js var name
*
* @return void
*/
public function updateTextareaMessage($jsVarName)
{
$js = '$("#' . $this->getID() . '_textarea").val(' . $jsVarName . ');';
echo $js;
}
/**
* Shows the confirm base JS code used to display when needed
*
* @return void
*/
public function initConfirm()
{
$progress_data = '';
$progress_func2 = '';
$onClickConfirm = '';
if (!is_null($this->jsCallback)) {
$onClickConfirm .= $this->jsCallback . ';';
}
//Enable the progress spinner
if ($this->progressOn) {
$progress_func1 = "__dpro_dialog_" . $this->uniqid;
$progress_func2 = ";{$progress_func1}(this)";
$progress_data = <<<HTML
<div class='dpro-dlg-confirm-progress' id="{$this->id}-progress">
<br/><br/>
<i class='fa fa-circle-notch fa-spin fa-lg fa-fw'></i> {$this->progressText}</div>
<script>
function {$progress_func1}(obj)
{
(function($,obj){
console.log($('#{$this->id}'));
// Set object for reuse
var e = $(obj);
// Check and set progress
if($('#{$this->id}-progress')) $('#{$this->id}-progress').show();
// Check and set confirm button
if($('#{$this->id}-confirm')) $('#{$this->id}-confirm').attr('disabled', 'true');
// Check and set cancel button
if($('#{$this->id}-cancel')) $('#{$this->id}-cancel').attr('disabled', 'true');
}(window.jQuery, obj));
}
</script>
HTML;
$onClickConfirm .= $progress_func2 . ';';
}
if ($this->closeOnConfirm) {
$onClickConfirm .= 'tb_remove();';
} ?>
<div id="<?php echo $this->id; ?>" style="display:none">
<div class="dpro-dlg-confirm-txt" id="<?php echo $this->id; ?>-confirm-txt">
<div id="<?php echo $this->id; ?>_message">
<?php echo $this->message; ?>
</div>
<?php echo $progress_data; ?>
</div>
<?php if ($this->showButtons) { ?>
<div class="dpro-dlg-confirm-btns <?php echo $this->wrapperClassButtons; ?>" >
<input
id="<?php echo $this->id; ?>-confirm"
type="button"
class="button button-large dup-dialog-confirm"
value="<?php echo esc_attr($this->okText); ?>"
onclick="<?php echo esc_attr($onClickConfirm); ?>"
>
<input
id="<?php echo $this->id; ?>-cancel"
type="button"
class="button button-large dup-dialog-cancel"
value="<?php echo esc_attr($this->cancelText); ?>"
onclick="tb_remove();"
>
</div>
<?php } ?>
</div>
<?php
}
/**
* Shows the confirm base JS code used to display when needed
*
* @return void
*/
public function showConfirm()
{
$html = "tb_show('" . esc_js($this->title) . "', '#TB_inline?width=" . $this->width . "&height=" . $this->height . "&inlineId=" . $this->id . "');\n" .
"var styleData = jQuery('#TB_window').attr('style') + 'height: " . $this->height . "px !important';\n" .
"jQuery('#TB_window').attr('style', styleData); DuplicatorTooltip.reload();";
echo $html;
}
}

View File

@@ -0,0 +1,180 @@
<?php
defined('ABSPATH') || defined('DUPXABSPATH') || exit;
/**
* Used to generate a thick box inline dialog such as an alert or confirm pop-up
*
* Standard: PSR-2
*
* @link http://www.php-fig.org/psr/psr-2
*
* @package Duplicator
* @subpackage classes/ui
* @copyright (c) 2017, Snapcreek LLC
*/
use Duplicator\Libs\Snap\SnapJson;
class DUP_PRO_UI_Messages
{
const UNIQUE_ID_PREFIX = 'dup_ui_msg_';
const NOTICE = 'updated';
const WARNING = 'update-nag';
const ERROR = 'error';
/** @var int */
private static $unique_id = 0;
/** @var string */
private $id = '';
/** @var string */
public $type = self::NOTICE;
/** @var string */
public $content = '';
/** @var string */
public $wrap_tag = 'p';
/** @var string */
public $wrap_cont_tag = 'p';
/** @var bool */
public $hide_on_init = true;
/** @var bool */
public $is_dismissible = false;
/** @var int delay in milliseconds */
public $auto_hide_delay = 0;
/** @var string */
public $callback_on_show = '';
/** @var string */
public $callback_on_hide = '';
/**
* Class constructor
*
* @param string $content Content of the message
* @param string $type Type of the message (NOTICE, WARNING, ERROR)
*/
public function __construct($content = '', $type = self::NOTICE)
{
self::$unique_id++;
$this->id = self::UNIQUE_ID_PREFIX . self::$unique_id;
$this->content = (string) $content;
$this->type = $type;
}
/**
* Get the classes for the notice
*
* @param string[] $classes Additional classes
*
* @return string
*/
protected function get_notice_classes($classes = array())
{
if (is_string($classes)) {
$classes = explode(' ', $classes);
} elseif (is_array($classes)) {
} else {
$classes = array();
}
if ($this->is_dismissible) {
$classes[] = 'is-dismissible';
}
$result = array_merge(array('notice', $this->type), $classes);
return trim(implode(' ', $result));
}
/**
* Initialize the message
*
* @param bool $jsBodyAppend If true, the message will be appended to the body tag
*
* @return void
*/
public function initMessage($jsBodyAppend = false)
{
$classes = array();
if ($this->hide_on_init) {
$classes[] = 'no_display';
}
$this->wrap_tag = empty($this->wrap_tag) ? 'p' : $this->wrap_tag;
$result = '<div id="' . $this->id . '" class="' . $this->get_notice_classes($classes) . '">' .
'<' . $this->wrap_cont_tag . ' class="msg-content">' .
$this->content .
'</' . $this->wrap_cont_tag . '>' .
'</div>';
if ($jsBodyAppend) {
echo '$("body").append(' . SnapJson::jsonEncode($result) . ');';
} else {
echo $result;
}
}
/**
* Update the message content
*
* @param string $jsVarName Name of the variable containing the new content
* @param bool $echo If true, the result will be echoed
*
* @return string
*/
public function updateMessage($jsVarName, $echo = true)
{
$result = 'jQuery("#' . $this->id . ' > .msg-content").html(' . $jsVarName . ');';
if ($echo) {
echo $result;
return '';
} else {
return $result;
}
}
/**
* Show the message
*
* @param bool $echo If true, the result will be echoed
*
* @return string
*/
public function showMessage($echo = true)
{
$callStr = (strlen($this->callback_on_show) ? $this->callback_on_show . ';' : '');
$result = 'jQuery("body, html").animate({ scrollTop: 0 }, 500 );';
$result .= 'jQuery("#' . $this->id . '").fadeIn( "slow", function() { jQuery(this).removeClass("no_display");' . $callStr . ' });';
if ($this->auto_hide_delay > 0) {
$result .= 'setTimeout(function () { ' . $this->hideMessage(false) . ' }, ' . $this->auto_hide_delay . ');';
}
if ($echo) {
echo $result;
return '';
} else {
return $result;
}
}
/**
* Hide the message
*
* @param bool $echo If true, the result will be echoed
*
* @return string
*/
public function hideMessage($echo = true)
{
$callStr = (strlen($this->callback_on_hide) ? $this->callback_on_hide . ';' : '');
$result = 'jQuery("#' . $this->id . '").fadeOut( "slow", function() { jQuery(this).addClass("no_display");' . $callStr . ' });';
if ($echo) {
echo $result;
return '';
} else {
return $result;
}
}
}

View File

@@ -0,0 +1,166 @@
<?php
defined("ABSPATH") or die("");
/**
* Gets the view state of UI elements to remember its viewable state
*
* Standard: PSR-2
*
* @link http://www.php-fig.org/psr/psr-2
*
* @package DUP_PRO
* @subpackage classes/ui
* @copyright (c) 2017, Snapcreek LLC
* @license https://opensource.org/licenses/GPL-3.0 GNU Public License
* @since 3.3.0
*/
class DUP_PRO_UI_ViewState
{
/** @var string The key used in the wp_options table */
private static $optionsTableKey = 'duplicator_pro_ui_view_state';
/**
* Save the view state of UI elements
*
* @param string $key A unique key to define the UI element
* @param string $value A generic value to use for the view state
*
* @return bool Returns true if the value was successfully saved
*/
public static function save($key, $value)
{
$view_state = array();
$view_state = get_option(self::$optionsTableKey);
$view_state[$key] = $value;
$success = update_option(self::$optionsTableKey, $view_state);
return $success;
}
/**
* Saves the state of a UI element via post params
*
* @return void
*
* <code>
* //JavaScript Ajax Request
* DupPro.UI.SaveViewStateByPost('dup-pack-archive-panel', 1);
*
* //Call PHP Code
* $view_state = DUP_PRO_UI_ViewState::getValue('dup-pack-archive-panel');
* $ui_css_archive = ($view_state == 1) ? 'display:block' : 'display:none';
* </code>
*
* @todo: Move this method to a controller see dlite (ctrl)
*/
public static function saveByPost()
{
DUP_PRO_Handler::init_error_handler();
check_ajax_referer('DUP_PRO_UI_ViewState_SaveByPost', 'nonce');
$json = array(
'update-success' => false,
'error-message' => '',
'key' => '',
'value' => '',
);
$isValid = true;
$inputData = filter_input_array(INPUT_POST, array(
'states' => array(
'filter' => FILTER_UNSAFE_RAW,
'flags' => FILTER_FORCE_ARRAY,
'options' => array(
'default' => array(),
),
),
'key' => array(
'filter' => FILTER_SANITIZE_SPECIAL_CHARS,
'options' => array('default' => false),
),
'value' => array(
'filter' => FILTER_SANITIZE_SPECIAL_CHARS,
'options' => array('default' => false),
),
));
if (isset($inputData['states']) && !empty($inputData['states'])) {
foreach ($inputData['states'] as $index => $state) {
$filteredState = filter_var_array($state, array(
'key' => array(
'filter' => FILTER_SANITIZE_SPECIAL_CHARS,
'options' => array('default' => false),
),
'value' => array(
'filter' => FILTER_SANITIZE_SPECIAL_CHARS,
'options' => array('default' => false),
),
));
if ($filteredState['key'] === false && $filteredState['value']) {
$isValid = false;
break;
}
$inputData['states'][$index] = $filteredState;
}
}
if ($inputData['key'] === false || $inputData['value'] === false) {
$isValid = false;
}
// VALIDATIO END
if ($isValid) {
if (!empty($inputData['states'])) {
$view_state = self::getArray();
$last_key = '';
foreach ($inputData['states'] as $state) {
$view_state[$state['key']] = $state['value'];
$last_key = $state['key'];
}
$json['update-success'] = self::setArray($view_state);
$json['key'] = esc_html($last_key);
$json['value'] = esc_html($view_state[$last_key]);
} else {
$json['update-success'] = self::save($inputData['key'], $inputData['value']);
$json['key'] = esc_html($inputData['key']);
$json['value'] = esc_html($inputData['value']);
}
} else {
$json['update-success'] = false;
$json['error-message'] = "Sent data is not valid.";
}
die(json_encode($json));
}
/**
* Gets all the values from the settings array
*
* @return array<string, mixed> Returns and array of all the values stored in the settings array
*/
public static function getArray()
{
return get_option(self::$optionsTableKey);
}
/**
* Gwer view statue value or default if don't exists
*
* @param string $key key
* @param mixed $default default value
*
* @return mixed
*/
public static function getValue($key, $default = false)
{
$vals = self::getArray();
return (isset($vals[$key]) ? $vals[$key] : $default);
}
/**
* Sets all the values from the settings array
*
* @param array<string, mixed> $view_state states
*
* @return boolean Returns whether updated or not
*/
public static function setArray($view_state)
{
return update_option(self::$optionsTableKey, $view_state);
}
}

View File

@@ -0,0 +1,3 @@
<?php
//silent

View File

@@ -0,0 +1,108 @@
<?php
defined("ABSPATH") or die("");
/**
* Utility class working with date time values
*
* Standard: PSR-2
*
* @link http://www.php-fig.org/psr/psr-2
*
* @package DUP_PRO
* @subpackage classes/utilities
* @copyright (c) 2017, Snapcreek LLC
* @license https://opensource.org/licenses/GPL-3.0 GNU Public License
* @since 3.0.0
*
* @todo Finish Docs
*/
class DUP_PRO_DATE
{
/**
* Get local time from GMT
*
* @param int $timestamp timestamp
*
* @return string
*/
public static function getLocalTimeFromGMT($timestamp)
{
$local_ticks = self::getLocalTicksFromGMT($timestamp);
$date_portion = date('M j,', $local_ticks);
$time_portion = date('g:i:s a', $local_ticks);
return "$date_portion $time_portion";
}
/**
* Get local ticks from GMT
*
* @param int $timestamp timestamp
*
* @return int
*/
public static function getLocalTicksFromGMT($timestamp)
{
return $timestamp + \Duplicator\Libs\Snap\SnapWP::getGMTOffset();
}
/**
* Get local time from GMT ticks
*
* @param int $ticks timestamp
*
* @return string
*/
public static function getLocalTimeFromGMTTicks($ticks)
{
return self::getStandardTime($ticks + \Duplicator\Libs\Snap\SnapWP::getGMTOffset());
}
/**
* Get the current GMT time in ticks
*
* @param int $ticks timestamp
*
* @return string
*/
public static function getStandardTime($ticks)
{
//return date('D, d M Y H:i:s', $ticks);
return date('D, d M H:i:s', $ticks);
}
/**
* Returns a string representation of the GMT time in the format of the WP date and time settings
*
* @param int $timestamp The GMT timestamp
* @param bool $includeDate Whether to include the date portion
* @param bool $includeTime Whether to include the time portion
*
* @return string
*/
public static function getWPTimeFromGMTTime($timestamp, $includeDate = true, $includeTime = true)
{
$ticks = self::getLocalTicksFromGMT($timestamp);
$date_format = get_option('date_format');
$time_format = get_option('time_format');
if ($includeDate) {
$date_portion = date($date_format, $ticks);
} else {
$date_portion = '';
}
if ($includeTime) {
$time_portion = date($time_format, $ticks);
} else {
$time_portion = '';
}
if ($includeDate && $includeTime) {
$seperator = ' ';
} else {
$seperator = '';
}
return "$date_portion$seperator$time_portion";
}
}

View File

@@ -0,0 +1,249 @@
<?php
defined("ABSPATH") or die("");
use Duplicator\Installer\Package\DescriptorSubsite;
use Duplicator\Libs\Snap\SnapDB;
use Duplicator\Libs\Snap\SnapIO;
use Duplicator\Libs\Snap\SnapURL;
use Duplicator\Libs\Snap\SnapWP;
class DUP_PRO_MU_Generations
{
const NotMultisite = 0;
const PreThreeFive = 1;
const ThreeFivePlus = 2;
}
class DUP_PRO_MU
{
/**
* return multisite mode
* 0 = single site
* 1 = multisite subdomain
* 2 = multisite subdirectory
*
* @return int
*/
public static function getMode()
{
if (is_multisite()) {
if (defined('SUBDOMAIN_INSTALL') && SUBDOMAIN_INSTALL) {
return 1;
} else {
return 2;
}
} else {
return 0;
}
}
/**
* This function is wrong because it assumes that if the folder sites exist, blogs.dir cannot exist.
* This is not true because if the network is old but a new site is created after the WordPress update both blogs.dir and sites folders exist.
*
* @return int
*/
public static function getGeneration()
{
if (self::getMode() == 0) {
return DUP_PRO_MU_Generations::NotMultisite;
} else {
$sitesDir = WP_CONTENT_DIR . '/uploads/sites';
if (file_exists($sitesDir)) {
return DUP_PRO_MU_Generations::ThreeFivePlus;
} else {
return DUP_PRO_MU_Generations::PreThreeFive;
}
}
}
/**
* Returns the subsite info list
*
* @param int[] $filteredSites List of sites to filter
* @param string[] $filteredTables List of tables to filter
* @param string[] $filteredPaths List of paths to filter
*
* @return DescriptorSubsite[]
*/
public static function getSubsites($filteredSites = array(), $filteredTables = array(), $filteredPaths = array())
{
if (!is_multisite()) {
return array(self::getSubsiteInfo(1, $filteredTables, $filteredPaths));
}
$site_array = array();
$filteredSites = is_array($filteredSites) ? $filteredSites : array();
DUP_PRO_Log::trace("NETWORK SITES");
foreach (SnapWP::getSitesIds() as $siteId) {
if (in_array($siteId, $filteredSites)) {
continue;
}
if (($siteInfo = self::getSubsiteInfo($siteId, $filteredTables, $filteredPaths)) == false) {
continue;
}
array_push($site_array, $siteInfo);
DUP_PRO_Log::trace("Multisite subsite detected. ID={$siteInfo->id} Domain={$siteInfo->domain} Path={$siteInfo->path} Blogname={$siteInfo->blogname}");
}
return $site_array;
}
/**
* Returns the subsite info by id
*
* @param int $subsiteId subsite id
*
* @return false|DescriptorSubsite false on failure
*/
public static function getSubsiteInfoById($subsiteId)
{
if (!is_multisite()) {
$subsiteId = 1;
}
return self::getSubsiteInfo($subsiteId);
}
/**
* Get subsite info
*
* @param int $siteId subsite id
* @param string[] $filteredTables Filtered tables
* @param false|string[] $filteredPaths Filtered paths
*
* @return false|DescriptorSubsite false on failure
*/
public static function getSubsiteInfo($siteId = 1, $filteredTables = array(), $filteredPaths = array())
{
if (is_multisite()) {
if (($siteDetails = get_blog_details($siteId)) == false) {
return false;
}
} else {
$siteId = 1;
$siteDetails = new stdClass();
$home = DUP_PRO_Archive::getOriginalUrls('home');
$parsedHome = SnapURL::parseUrl($home);
$siteDetails->domain = $parsedHome['host'];
$siteDetails->path = trailingslashit($parsedHome['path']);
$siteDetails->blogname = sanitize_text_field(get_option('blogname'));
}
$subsiteID = $siteId;
$siteInfo = new DescriptorSubsite();
$siteInfo->id = $subsiteID;
$siteInfo->domain = $siteDetails->domain;
$siteInfo->path = $siteDetails->path;
$siteInfo->blogname = $siteDetails->blogname;
$siteInfo->blog_prefix = $GLOBALS['wpdb']->get_blog_prefix($subsiteID);
if (count($filteredTables) > 0) {
$siteInfo->filteredTables = array_values(array_intersect(self::getSubsiteTables($subsiteID), $filteredTables));
} else {
$siteInfo->filteredTables = array();
}
$siteInfo->adminUsers = SnapWP::getAdminUserLists($siteInfo->id);
$siteInfo->fullHomeUrl = get_home_url($siteId);
$siteInfo->fullSiteUrl = get_site_url($siteId);
if ($siteId > 1) {
switch_to_blog($siteId);
}
$uploadData = wp_upload_dir();
$uploadPath = $uploadData['basedir'];
$siteInfo->uploadPath = SnapIO::getRelativePath($uploadPath, DUP_PRO_Archive::getTargetRootPath(), true);
$siteInfo->fullUploadPath = untrailingslashit($uploadPath);
$siteInfo->fullUploadSafePath = SnapIO::safePathUntrailingslashit($uploadPath);
$siteInfo->fullUploadUrl = $uploadData['baseurl'];
if (count($filteredPaths)) {
$globalDirFilters = DUP_PRO_Archive::getDefaultGlobalDirFilter();
$siteInfo->filteredPaths = array_values(array_filter($filteredPaths, function ($path) use ($uploadPath, $subsiteID, $globalDirFilters) {
if (
($relativeUpload = SnapIO::getRelativePath($path, $uploadPath)) === false ||
in_array($path, $globalDirFilters)
) {
return false;
}
if ($subsiteID > 1) {
return true;
} else {
// no check on blogs.dir because in wp-content/blogs.dir not in upload folder
return !(strpos($relativeUpload, 'sites') === 0);
}
}));
} else {
$siteInfo->filteredPaths = array();
}
if ($siteId > 1) {
restore_current_blog();
}
return $siteInfo;
}
/**
* @param int $subsiteID subsite id
*
* @return string[] List of tables belonging to subsite
*/
public static function getSubsiteTables($subsiteID)
{
/** @var \wpdb $wpdb */
global $wpdb;
$basePrefix = $wpdb->base_prefix;
$subsitePrefix = $wpdb->get_blog_prefix($subsiteID);
$sharedTables = array(
$basePrefix . 'users',
$basePrefix . 'usermeta',
);
$multisiteOnlyTables = array(
$basePrefix . 'blogmeta',
$basePrefix . 'blogs',
$basePrefix . 'blog_versions',
$basePrefix . 'registration_log',
$basePrefix . 'signups',
$basePrefix . 'site',
$basePrefix . 'sitemeta',
);
$subsiteTables = array();
$sql = "";
$dbnameSafe = esc_sql(DB_NAME);
if ($subsiteID != 1) {
$regex = '^' . SnapDB::quoteRegex($subsitePrefix);
$regexpSafe = esc_sql($regex);
$sharedTablesSafe = "'" . implode(
"', '",
esc_sql($sharedTables)
) . "'";
$sql = "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '$dbnameSafe' AND ";
$sql .= "(TABLE_NAME REGEXP '$regexpSafe' OR TABLE_NAME IN ($sharedTablesSafe))";
} else {
$regexMain = '^' . SnapDB::quoteRegex($basePrefix);
$regexpMainSafe = esc_sql($regexMain);
$regexNotSub = '^' . SnapDB::quoteRegex($basePrefix) . '[0-9]+_';
$regexpNotSubSafe = esc_sql($regexNotSub);
$multisiteOnlyTablesSafe = "'" . implode(
"', '",
esc_sql($multisiteOnlyTables)
) . "'";
$sql = "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '$dbnameSafe' AND ";
$sql .= "TABLE_NAME REGEXP '$regexpMainSafe' AND ";
$sql .= "TABLE_NAME NOT REGEXP '$regexpNotSubSafe' AND ";
$sql .= "TABLE_NAME NOT IN ($multisiteOnlyTablesSafe)";
}
$subsiteTables = $wpdb->get_col($sql);
return $subsiteTables;
}
}

View File

@@ -0,0 +1,556 @@
<?php
/**
* Utility class used for various task
*
* Standard: PSR-2
*
* @link http://www.php-fig.org/psr/psr-2
*
* @package DUP_PRO
* @subpackage classes/utilities
* @copyright (c) 2017, Snapcreek LLC
* @license https://opensource.org/licenses/GPL-3.0 GNU Public License
*/
defined("ABSPATH") or die("");
use Duplicator\Libs\Shell\Shell;
class DUP_PRO_U
{
/**
* return absolute path for the directories that are core directories
*
* @param bool $original If true it returns yes the original realpaths and paths, in case they are links, Otherwise it returns only the realpaths.
*
* @return string[]
*/
public static function getWPCoreDirs($original = false)
{
$corePaths = DUP_PRO_Archive::getArchiveListPaths();
$corePaths[] = $corePaths['abs'] . '/wp-admin';
$corePaths[] = $corePaths['abs'] . '/wp-includes';
if ($original) {
$origPaths = DUP_PRO_Archive::getOriginalPaths();
$origPaths[] = $origPaths['abs'] . '/wp-admin';
$origPaths[] = $origPaths['abs'] . '/wp-includes';
$corePaths = array_merge($corePaths, $origPaths);
}
return array_values(array_unique($corePaths));
}
/**
* return absolute path for the files that are core directories
*
* @return string[]
*/
public static function getWPCoreFiles()
{
return array(DUP_PRO_Archive::getArchiveListPaths('wpconfig') . '/wp-config.php');
}
/**
* Converts an absolute path to a relative path
*
* @param string $from The the path relative to $to
* @param string $to The full path of the directory to transform
*
* @return string A string of the result
*/
public static function getRelativePath($from, $to)
{
// some compatibility fixes for Windows paths
$from = is_dir($from) ? rtrim($from, '\/') . '/' : $from;
$to = is_dir($to) ? rtrim($to, '\/') . '/' : $to;
$from = str_replace('\\', '/', $from);
$to = str_replace('\\', '/', $to);
$from = explode('/', $from);
$to = explode('/', $to);
$relPath = $to;
foreach ($from as $depth => $dir) {
// find first non-matching dir
if ($dir === $to[$depth]) {
// ignore this directory
array_shift($relPath);
} else {
// get number of remaining dirs to $from
$remaining = count($from) - $depth;
if ($remaining > 1) {
// add traversals up to first matching dir
$padLength = (count($relPath) + $remaining - 1) * -1;
$relPath = array_pad($relPath, $padLength, '..');
break;
} else {
//$relPath[0] = './' . $relPath[0];
}
}
}
return implode('/', $relPath);
}
/**
* Gets the percentage of one value to another
* example:
* $val1 = 100
* $val2 = 400
* $res = 25
*
* @param int|float $val1 The value to calculate the percentage
* @param int|float $val2 The total value to calculate the percentage against
* @param int $precision The number of decimal places to round to
*
* @return float Returns the results
*/
public static function percentage($val1, $val2, $precision = 0)
{
$division = $val1 / (float) $val2;
$res = $division * 100;
return round($res, $precision);
}
/**
* Display human readable byte sizes
*
* @param int $size The size in bytes
*
* @return string The size of bytes readable such as 100KB, 20MB, 1GB etc.
*/
public static function byteSize($size)
{
try {
$size = (int) $size;
$units = array(
'B',
'KB',
'MB',
'GB',
'TB',
);
for ($i = 0; $size >= 1024 && $i < 4; $i++) {
$size /= 1024;
}
return round($size, 2) . $units[$i];
} catch (Exception $e) {
return "n/a";
}
}
/**
* Return a string with the elapsed time in seconds
*
* @see getMicrotime()
*
* @param int|float $end The final time in the sequence to measure
* @param int|float $start The start time in the sequence to measure
*
* @return string The time elapsed from $start to $end as 5.89 sec.
*/
public static function elapsedTime($end, $start)
{
return sprintf('%.3f sec.', abs($end - $start));
}
/**
* Return a float with the elapsed time in seconds
*
* @see getMicrotime(), elapsedTime()
*
* @param int|float $end The final time in the sequence to measure
* @param int|float $start The start time in the sequence to measure
*
* @return string The time elapsed from $start to $end as 5.89
*/
public static function elapsedTimeU($end, $start)
{
return sprintf('%.3f', abs($end - $start));
}
/**
* Gets the contents of the file as an attachment type
*
* @param string $filepath The full path the file to read
* @param string $contentType The header content type to force when pushing the attachment
*
* @return void
*/
public static function getDownloadAttachment($filepath, $contentType)
{
// Clean previous or after eny notice texts
ob_clean();
ob_start();
$filename = basename($filepath);
header("Content-Type: {$contentType}");
header("Content-Disposition: attachment; filename={$filename}");
header("Pragma: public");
if (readfile($filepath) === false) {
$msg = sprintf(__('Couldn\'t read %s', 'duplicator-pro'), $filepath);
throw new Exception($msg);
}
ob_end_flush();
}
/**
* Return the path of an executable program
*
* @param string $exeFilename A file name or path to a file name of the executable
*
* @return string|null Returns the full path of the executable or null if not found
*/
public static function getExeFilepath($exeFilename)
{
$filepath = null;
if (!Shell::test()) {
return null;
}
$shellOutput = Shell::runCommand("hash $exeFilename 2>&1", Shell::AVAILABLE_COMMANDS);
if ($shellOutput !== false && $shellOutput->isEmpty()) {
$filepath = $exeFilename;
} else {
$possible_paths = array(
"/usr/bin/$exeFilename",
"/opt/local/bin/$exeFilename",
);
foreach ($possible_paths as $path) {
if (@file_exists($path)) {
$filepath = $path;
break;
}
}
}
return $filepath;
}
/**
* Get current microtime as a float. Method is used for simple profiling
*
* @see elapsedTime
*
* @return float A float in the form "msec sec", where sec is the number of seconds since the Unix epoch
*/
public static function getMicrotime()
{
return microtime(true);
}
/**
* Gets an SQL lock request
*
* @see releaseSqlLock()
*
* @param string $lock_name The name of the lock to check
*
* @return bool Returns true if an SQL lock request was successful
*/
public static function getSqlLock($lock_name = 'duplicator_pro_lock')
{
global $wpdb;
$query_string = "select GET_LOCK('{$lock_name}', 0)";
$ret_val = $wpdb->get_var($query_string);
if ($ret_val == 0) {
DUP_PRO_Log::trace("Mysql lock {$lock_name} denied");
return false;
} elseif ($ret_val == null) {
DUP_PRO_Log::trace("Error retrieving mysql lock {$lock_name}");
return false;
}
DUP_PRO_Log::trace("Mysql lock {$lock_name} acquired");
return true;
}
/**
* Gets an SQL lock request
*
* @see releaseSqlLock()
*
* @param string $lock_name The name of the lock to check
*
* @return bool Returns true if an SQL lock request was successful
*/
public static function isSqlLockLocked($lock_name = 'duplicator_pro_lock')
{
global $wpdb;
$query_string = "select IS_FREE_LOCK('{$lock_name}')";
$ret_val = $wpdb->get_var($query_string);
if ($ret_val == 0) {
DUP_PRO_Log::trace("MySQL lock {$lock_name} is in use");
return true;
} elseif ($ret_val == null) {
DUP_PRO_Log::trace("Error retrieving mysql lock {$lock_name}");
return false;
} else {
DUP_PRO_Log::trace("MySQL lock {$lock_name} is free");
return false;
}
}
/**
* Verifies that a correct security nonce was used. If correct nonce is not used, It will cause to die
*
* A nonce is valid for 24 hours (by default).
*
* @param string $nonce Nonce value that was used for verification, usually via a form field.
* @param string|int $action Should give context to what is taking place and be the same when nonce was created.
*
* @return void
*/
public static function verifyNonce($nonce, $action)
{
if (!wp_verify_nonce($nonce, $action)) {
die('Security issue');
}
}
/**
* Does the current user have the capability
* Dies if user doesn't have the correct capability
*
* @return void
*/
public static function checkAjax()
{
if (!wp_doing_ajax()) {
$errorMsg = esc_html__('You do not have called from AJAX to access this page.', 'duplicator-pro');
DUP_PRO_Log::trace($errorMsg);
error_log($errorMsg);
wp_die(esc_html($errorMsg));
}
}
/**
* Rturn true if sql lock is set
*
* @param string $lock_name lock nam
*
* @return bool
*/
public static function checkSqlLock($lock_name = 'duplicator_pro_lock')
{
global $wpdb;
$query_string = "SELECT IS_USED_LOCK('{$lock_name}')";
$ret_val = $wpdb->get_var($query_string);
return $ret_val > 0;
}
/**
* Releases the SQL lock request
*
* @see getSqlLock()
*
* @param string $lock_name The name of the lock to release
*
* @return void
*/
public static function releaseSqlLock($lock_name = 'duplicator_pro_lock')
{
global $wpdb;
$query_string = "select RELEASE_LOCK('{$lock_name}')";
$ret_val = $wpdb->get_var($query_string);
if ($ret_val == 0) {
DUP_PRO_Log::trace("Failed releasing sql lock {$lock_name} because it wasn't established by this thread");
} elseif ($ret_val == null) {
DUP_PRO_Log::trace("Tried to release sql lock {$lock_name} but it didn't exist");
} else {
// Lock was released
DUP_PRO_Log::trace("SQL lock {$lock_name} released");
}
}
/**
* Sets a value or returns a default
*
* @param mixed $val The value to set
* @param mixed $default The value to default to if the val is not set
*
* @return mixed A value or a default
*/
public static function setVal($val, $default = null)
{
return isset($val) ? $val : $default;
}
/**
* Check is set and not empty, sets a value or returns a default
*
* @param mixed $val The value to set
* @param mixed $default The value to default to if the val is not set
*
* @return mixed A value or a default
*/
public static function isEmpty($val, $default = null)
{
return isset($val) && !empty($val) ? $val : $default;
}
/**
* Returns the last N lines of a file. Simular to tail command
*
* @param string $filepath The full path to the file to be tailed
* @param int $lines The number of lines to return with each tail call
*
* @return false|string The last N parts of the file, false on failure
*/
public static function tailFile($filepath, $lines = 2)
{
// Open file
$f = @fopen($filepath, "rb");
if ($f === false) {
return false;
}
// Sets buffer size
$buffer = 256;
// Jump to last character
fseek($f, -1, SEEK_END);
// Read it and adjust line number if necessary
// (Otherwise the result would be wrong if file doesn't end with a blank line)
if (fread($f, 1) != "\n") {
$lines -= 1;
}
// Start reading
$output = '';
$chunk = '';
// While we would like more
while (ftell($f) > 0 && $lines >= 0) {
// Figure out how far back we should jump
$seek = min(ftell($f), $buffer);
// Do the jump (backwards, relative to where we are)
fseek($f, -$seek, SEEK_CUR);
// Read a chunk and prepend it to our output
$output = ($chunk = fread($f, $seek)) . $output;
// Jump back to where we started reading
fseek($f, -mb_strlen($chunk, '8bit'), SEEK_CUR);
// Decrease our line counter
$lines -= substr_count($chunk, "\n");
}
// While we have too many lines
// (Because of buffer size we might have read too many)
while ($lines++ < 0) {
// Find first newline and remove all text before that
$output = substr($output, strpos($output, "\n") + 1);
}
fclose($f);
return trim($output);
}
/**
* Check given table is exist in real
*
* @param string $table string Table name
*
* @return bool
*/
public static function isTableExists($table)
{
// It will clear the $GLOBALS['wpdb']->last_error var
$GLOBALS['wpdb']->flush();
$sql = "SELECT 1 FROM `" . esc_sql($table) . "` LIMIT 1;";
$ret = $GLOBALS['wpdb']->get_var($sql);
if (empty($GLOBALS['wpdb']->last_error)) {
return true;
}
return false;
}
/**
* Finds if its a valid executable or not
*
* @param string $cmd A non zero length executable path to find if that is executable or not.
*
* @return bool
*/
public static function isExecutable($cmd)
{
if (strlen($cmd) == 0) {
return false;
}
if (
@is_executable($cmd)
|| !Shell::runCommand($cmd, Shell::AVAILABLE_COMMANDS)->isEmpty()
|| !Shell::runCommand($cmd . ' -?', Shell::AVAILABLE_COMMANDS)->isEmpty()
) {
return true;
}
return false;
}
/**
* Look into string and try to fix its natural expected value type
*
* @param mixed $data Simple string
*
* @return mixed value with it's natural string type
*/
public static function valType($data)
{
if (is_string($data)) {
if (is_numeric($data)) {
if ((int) $data == $data) {
return (int) $data;
} elseif ((float) $data == $data) {
return (float) $data;
}
} elseif (in_array(strtolower($data), array('true', 'false'), true)) {
return ($data == 'true');
}
} elseif (is_array($data)) {
foreach ($data as $key => $str) {
$data[$key] = DUP_PRO_U::valType($str);
}
}
return $data;
}
/**
* Check given var is curl resource or instance of CurlHandle or CurlMultiHandle
* It is used for check curl_init() return, because
* curl_init() returns resource in lower PHP version than 8.0
* curl_init() returns class instance in PHP version 8.0
* Ref. https://php.watch/versions/8.0/resource-CurlHandle
*
* @param resource|object $var var to check
*
* @return boolean
*/
public static function isCurlResourceOrInstance($var)
{
// CurlHandle class instance return of curl_init() in php 8.0
// CurlMultiHandle class instance return of curl_multi_init() in php 8.0
if (is_resource($var) || ($var instanceof CurlHandle) || ($var instanceof CurlMultiHandle)) {
return true;
} else {
return false;
}
}
}

View File

@@ -0,0 +1,110 @@
<?php
defined("ABSPATH") or die("");
/**
* Utility class working with strings
*
* Standard: PSR-2
*
* @link http://www.php-fig.org/psr/psr-2
*
* @package DUP_PRO
* @subpackage classes/utilities
* @copyright (c) 2017, Snapcreek LLC
* @license https://opensource.org/licenses/GPL-3.0 GNU Public License
* @since 3.0.0
*/
class DUP_PRO_STR
{
/**
* Append the value to the string if it doesn't already exist
*
* @param string $string The string to append to
* @param string $value The string to append to the $string
*
* @return string Returns the string with the $value appended once
*/
public static function appendOnce($string, $value)
{
return $string . (substr($string, -1) == $value ? '' : $value);
}
/**
* Returns true if the string contains UTF8 characters
*
* @see http://php.net/manual/en/function.mb-detect-encoding.php
*
* @param string $string The class name where the $destArray exists
*
* @return bool
*/
public static function hasUTF8($string)
{
return (preg_match('%(?:
[\xC2-\xDF][\x80-\xBF] # non-overlong 2-byte
|\xE0[\xA0-\xBF][\x80-\xBF] # excluding overlongs
|[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
|\xED[\x80-\x9F][\x80-\xBF] # excluding surrogates
|\xF0[\x90-\xBF][\x80-\xBF]{2} # planes 1-3
|[\xF1-\xF3][\x80-\xBF]{3} # planes 4-15
|\xF4[\x80-\x8F][\x80-\xBF]{2} # plane 16
)+%xs', $string) === 1);
}
/**
* Returns true if the $needle is found in the $haystack
*
* @param string $haystack The full string to search in
* @param string $needle The string to for
*
* @return bool
*/
public static function contains($haystack, $needle)
{
$pos = strpos($haystack, $needle);
return ($pos !== false);
}
/**
* Converts the boolean type to a string version of 'True', 'False'
*
* @param bool $b The boolean value
*
* @return string Returns the boolean type to a string version of 'True', 'False'
*/
public static function boolToString($b)
{
return ($b ? __('True', 'duplicator-pro') : __('False', 'duplicator-pro'));
}
/**
* Returns true if the $haystack string starts with the $needle
*
* @param string $haystack The full string to search in
* @param string $needle The string to for
*
* @return bool Returns true if the $haystack string starts with the $needle
*/
public static function startsWith($haystack, $needle)
{
$length = strlen($needle);
return (substr($haystack, 0, $length) === $needle);
}
/**
* Returns true if the $haystack string ends with the $needle
*
* @param string $haystack The full string to search in
* @param string $needle The string to for
*
* @return bool Returns true if the $haystack string ends with the $needle
*/
public static function endsWith($haystack, $needle)
{
$length = strlen($needle);
if ($length == 0) {
return true;
}
return (substr($haystack, -$length) === $needle);
}
}

View File

@@ -0,0 +1,390 @@
<?php
/**
* Utility class to create a tree structure from paths
*
* Standard: PSR-2
*
* @link http://www.php-fig.org/psr/psr-2
*
* @package DUP_PRO
* @subpackage classes/utilities
* @copyright (c) 2017, Snapcreek LLC
* @license https://opensource.org/licenses/GPL-3.0 GNU Public License
* @since 3.8.1
*/
defined("ABSPATH") or die("");
use Duplicator\Libs\Snap\SnapIO;
/**
* Tree files utility
*/
class DUP_PRO_Tree_files
{
/**
* All props must be public for json encode
*/
/**
*
* @var DUP_PRO_Tree_files_node[]
*/
protected $treeList = array();
/**
* Class contructor
*
* @param string|string[] $rootPaths root paths
* @param bool $addAllChilds if true add all childs
* @param null|false|string|string[] $excludeList if null or false no exclude else exclude list
*/
public function __construct($rootPaths, $addAllChilds = true, $excludeList = array())
{
if (is_null($excludeList) || $excludeList === false) {
$excludeList = array();
} elseif (!is_array($excludeList)) {
$excludeList = array($excludeList);
}
if (!is_array($rootPaths)) {
$rootPaths = array($rootPaths);
}
$rootPaths = array_map(array('\\Duplicator\\Libs\\Snap\\SnapIO', 'safePathUntrailingslashit'), $rootPaths);
foreach ($rootPaths as $path) {
$this->treeList[$path] = new DUP_PRO_Tree_files_node($path);
if ($addAllChilds) {
$this->treeList[$path]->addAllChilds($excludeList);
}
}
}
/**
*
* @param string $path full path
* @param array<string,mixed> $data optiona data associated at node
*
* @return bool|DUP_PRO_Tree_files_node
*/
public function addElement($path, $data = array())
{
foreach ($this->treeList as $rootPath => $tree) {
if (strpos($path, trailingslashit($rootPath)) === 0) {
$newElem = $tree->addChild($path, true, false);
if ($newElem) {
$newElem->data = $data;
}
return $newElem;
}
}
return false;
}
/**
* Sort child list with callback function of all trees root nodes
*
* @param callable $callback function to call
*
* @return void
*/
public function uasort($callback)
{
foreach ($this->treeList as $tree) {
$tree->uasort($callback);
}
}
/**
* traverse tree anche call callback function of all trees root nodes
*
* @param callable $callback function to call
*
* @return void
*/
public function treeTraverseCallback($callback)
{
foreach ($this->treeList as $tree) {
$tree->treeTraverseCallback($callback);
}
}
/**
*
* @return DUP_PRO_Tree_files_node[]
*/
public function getTreeList()
{
return $this->treeList;
}
}
/**
* Tree node data
*/
class DUP_PRO_Tree_files_node
{
const MAX_CHILDS_FOR_FOLDER = 250;
const MAX_TREE_NODES = 5000;
/**
* All props must be public for json encode
*/
/**
*
* @var string unique id l0_l1_l2....
*/
public $id = '';
/**
*
* @var string parent id l0_l1_...
*/
public $parentId = '';
/**
*
* @var string file basename
*/
public $name = '';
/**
*
* @var string full path
*/
public $fullPath = '';
/**
*
* @var bool is directory
*/
public $isDir = false;
/**
*
* @var DUP_PRO_Tree_files_node[] childs nodes
*/
public $childs = array();
/**
*
* @var array<string, mixed> optiona data associated ad node
*/
public $data = array();
/**
*
* @var bool true if folder have a childs
*/
public $haveChildren = null;
/**
*
* @var bool
*/
private $traversed = false;
/**
*
* @var int if can't add a child increment exceeede count
*/
private $nodesExceeded = 0;
/**
*
* @var int
*/
private $numTreeNodes = 1;
/**
*
* @param string $path file path
* @param string $id current level unique id
* @param string $parent_id parent id
*/
public function __construct($path, $id = '0', $parent_id = '')
{
$safePath = SnapIO::safePathUntrailingslashit($path);
$this->id = (strlen($parent_id) == 0 ? '' : $parent_id . '_') . $id;
$this->parentId = $parent_id;
$this->name = basename($safePath);
$this->fullPath = $safePath;
$this->isDir = @is_dir($this->fullPath);
$this->haveChildrenCheck();
}
/**
* create tree tructure until at basename
*
* @param string $path file path
* @param bool $fullPath if true is considered a full path and must be a child of a current node else is a relative path
* @param bool $loadTraverse if true, add the files and folders present at the level of each node
*
* @return boolean|DUP_PRO_Tree_files_node if fails terurn false ellse return the leaf child added
*/
public function addChild($path, $fullPath = true, $loadTraverse = false)
{
if (empty($path)) {
return false;
}
$safePath = SnapIO::safePathUntrailingslashit($path);
if ($fullPath) {
if (strpos($safePath, trailingslashit($this->fullPath)) !== 0) {
throw new Exception('Can\'t add no child on tree; file: "' . $safePath . '" || fullpath: "' . $this->fullPath . '"');
}
$childPath = substr($safePath, strlen($this->fullPath));
} else {
$childPath = $safePath;
}
$tree_list = explode('/', $childPath);
if (empty($tree_list[0])) {
array_shift($tree_list);
}
if (($child = $this->checkAndAddChild($tree_list[0])) === false) {
return false;
}
if ($loadTraverse) {
$child->addAllChilds();
}
if (count($tree_list) > 1) {
array_shift($tree_list);
$nodesBefore = $child->getNumTreeNodes();
$result = $child->addChild(implode('/', $tree_list), false, $loadTraverse);
$this->numTreeNodes += $child->getNumTreeNodes() - $nodesBefore;
return $result;
} else {
return $child;
}
}
/**
* If is dir scan all children files and add on childs list
*
* @param string[] $excludeList child to add
*
* @return void
*/
public function addAllChilds($excludeList = array())
{
if ($this->traversed === false) {
$this->traversed = true;
if ($this->isDir) {
if ($dh = @opendir($this->fullPath)) {
while (($childName = readdir($dh)) !== false) {
if ($childName == '.' || $childName == '..' || in_array($childName, $excludeList)) {
continue;
}
$this->checkAndAddChild($childName);
}
closedir($dh);
}
}
}
}
/**
* check if current dir have children without load nodes
*
* @return void
*/
private function haveChildrenCheck()
{
if ($this->isDir) {
$this->haveChildren = false;
if ($dh = opendir($this->fullPath)) {
while (!$this->haveChildren && ($file = readdir($dh)) !== false) {
$this->haveChildren = $file !== "." && $file !== "..";
}
closedir($dh);
}
}
}
/**
*
* @param string $name child name
*
* @return boolean|DUP_PRO_Tree_files_node if notes exceeded return false
*/
private function checkAndAddChild($name)
{
if (!array_key_exists($name, $this->childs)) {
if ($this->numTreeNodes > self::MAX_TREE_NODES || count($this->childs) >= self::MAX_CHILDS_FOR_FOLDER) {
$this->nodesExceeded++;
return false;
} else {
$child = new DUP_PRO_Tree_files_node($this->fullPath . '/' . $name, (string) count($this->childs), $this->id);
$this->childs[$name] = $child;
$this->numTreeNodes++;
}
}
return $this->childs[$name];
}
/**
* sort child list with callback function
*
* @param callable $value_compare_func function to call
*
* @return void
*/
public function uasort($value_compare_func)
{
if (!is_callable($value_compare_func)) {
return;
}
foreach ($this->childs as $child) {
$child->uasort($value_compare_func);
}
uasort($this->childs, $value_compare_func);
}
/**
* traverse tree anche call callback function
*
* @param callable $callback function to call
*
* @return void
*/
public function treeTraverseCallback($callback)
{
if (!is_callable($callback)) {
return;
}
foreach ($this->childs as $child) {
$child->treeTraverseCallback($callback);
}
call_user_func($callback, $this);
}
/**
* Get the value of nodesExceeded
*
* @return int
*/
public function getNodesExceeded()
{
return $this->nodesExceeded;
}
/**
* Get the value of numTreeNodes
*
* @return int
*/
public function getNumTreeNodes()
{
return $this->numTreeNodes;
}
}

View File

@@ -0,0 +1,230 @@
<?php
/**
* Validate variables
*
* Standard: PSR-2
*
* @link http://www.php-fig.org/psr/psr-2
*
* @package Duplicator
* @subpackage classes/utilities
* @copyright (c) 2017, Snapcreek LLC
*/
// Exit if accessed directly
defined("ABSPATH") or die("");
class DUP_PRO_Validator
{
/** @var array<string,string> $patterns */
private static $patterns = array(
'fdir' => '/^([a-zA-Z]:[\\\\\/]|\/|\\\\\\\\|\/\/)[^<>\0]+$/',
'fdirwc' => '/^[\s\t]*?(#.*|[a-zA-Z]:[\\\\\/]|\/|\\\\\\\\|\/\/)[^<>\0]*$/',
'ffile' => '/^([a-zA-Z]:[\\\\\/]|\/|\\\\\\\\|\/\/)[^<>\0]+$/',
'ffilewc' => '/^[\s\t]*?(#.*|[a-zA-Z]:[\\\\\/]|\/|\\\\\\\\|\/\/)[^<>\0]*$/',
'fext' => '/^\.?[^\\\\\/*:<>\0?"|\s\.]+$/',
'email' => '/^[a-zA-Z0-9.!#$%&\'*+\/=?^_\`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/',
'empty' => '/^$/',
'nempty' => '/^.+$/',
);
const FILTER_VALIDATE_IS_EMPTY = 'empty';
const FILTER_VALIDATE_NOT_EMPTY = 'nempty';
const FILTER_VALIDATE_FILE = 'ffile';
const FILTER_VALIDATE_FILE_WITH_COMMENT = 'ffilewc';
const FILTER_VALIDATE_FOLDER = 'fdir';
const FILTER_VALIDATE_FOLDER_WITH_COMMENT = 'fdirwc';
const FILTER_VALIDATE_FILE_EXT = 'fext';
const FILTER_VALIDATE_EMAIL = 'email';
/** @var array<array{key:string,msg:string}> */
private $errors = array();
/**
* Class contructor
*/
public function __construct()
{
$this->errors = array();
}
/**
* @return void
*/
public function reset()
{
$this->errors = array();
}
/**
*
* @return bool
*/
public function isSuccess()
{
return empty($this->errors);
}
/**
* Return errors
*
* @return array<array{key:string,msg:string}>
*/
public function getErrors()
{
return $this->errors;
}
/**
* Return errors messages
*
* @return string[]
*/
public function getErrorsMsg()
{
$result = array();
foreach ($this->errors as $err) {
$result[] = $err['msg'];
}
return $result;
}
/**
*
* @param string $format printf format message where %s is the variable content default "%s\n"
* @param bool $echo if false return string
*
* @return void|string
*/
public function getErrorsFormat($format = "%s\n", $echo = true)
{
$msgs = $this->getErrorsMsg();
ob_start();
foreach ($msgs as $msg) {
printf($format, esc_html($msg)); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
if ($echo) {
ob_end_flush();
} else {
return ob_get_clean();
}
}
/**
* @param string $key field key
* @param string $msg error message
*
* @return void
*/
public function addError($key, $msg)
{
$this->errors[] = array(
'key' => $key,
'msg' => $msg,
);
}
/**
* filter_var function wrapper see http://php.net/manual/en/function.filter-var.php
*
* additional options
* valkey => key of field
* errmsg => error message; % s will be replaced with the contents of the variable es. "<b>%s</b> isn't a valid field"
* acc_vals => array of accepted values that skip validation
*
* @param mixed $variable variable to validate
* @param int $filter filter name
* @param array<string,mixed> $options additional options for filter_var
*
* @return mixed
*/
public function filter_var($variable, $filter = FILTER_DEFAULT, $options = array())
{
$success = true;
$result = null;
if (isset($options['acc_vals']) && in_array($variable, $options['acc_vals'])) {
return $variable;
}
if ($filter === FILTER_VALIDATE_BOOLEAN) {
$options['flags'] = FILTER_NULL_ON_FAILURE;
/** @var null|bool */
$result = is_bool($variable) ? $variable : filter_var($variable, $filter, $options);
if (is_null($result)) {
$success = false;
}
} else {
$result = filter_var($variable, $filter, $options);
if ($result === false) {
$success = false;
}
}
if (!$success) {
$key = isset($options['valkey']) ? $options['valkey'] : '';
if (isset($options['errmsg'])) {
$msg = sprintf($options['errmsg'], esc_html($variable));
} else {
$msg = sprintf('%1$s isn\'t a valid value', $variable);
}
$this->addError($key, $msg);
}
return $result;
}
/**
* Validation of predefined regular expressions
*
* @param mixed $variable variable to validate
* @param string $filter filter name
* @param array<string, mixed> $options additional options for filter_var
*
* @return mixed
*/
public function filter_custom($variable, $filter, $options = array())
{
if (!isset(self::$patterns[$filter])) {
throw new Exception('Filter not valid');
}
$options = array_merge($options, array(
'options' => array(
'regexp' => self::$patterns[$filter],
),
));
//$options['regexp'] = self::$patterns[$filter];
return $this->filter_var($variable, FILTER_VALIDATE_REGEXP, $options);
}
/**
* it explodes a string with a delimiter and validates every element of the array
*
* @param string $variable string to explode
* @param string $delimiter delimiter
* @param string $filter filter name
* @param array<string, mixed> $options additional options for filter_var
*
* @return mixed[]
*/
public function explode_filter_custom($variable, $delimiter, $filter, $options = array())
{
if (empty($variable)) {
return array();
}
if (strlen($delimiter) == 0) {
throw new Exception('Delimiter can\'t be empty');
}
$vals = explode($delimiter, trim($variable, $delimiter));
$res = array();
foreach ($vals as $val) {
$res[] = $this->filter_custom($val, $filter, $options);
}
return $res;
}
}

View File

@@ -0,0 +1,152 @@
<?php
/**
* Utility class for zipping up content
*
* Standard: PSR-2
*
* @link http://www.php-fig.org/psr/psr-2
*
* @package DUP_PRO
* @subpackage classes/utilities
* @copyright (c) 2017, Snapcreek LLC
* @license https://opensource.org/licenses/GPL-3.0 GNU Public License
* @since 3.3.0
*/
use Duplicator\Libs\Shell\Shell;
/**
* Helper class for reporting problems with zipping
*
* @see DUP_PRO_Zip_U
*/
class DUP_PRO_Problem_Fix
{
/** @var string The detected problem */
public $problem = '';
/** @var string A recommended fix for the problem */
public $fix = '';
}
class DUP_PRO_Zip_U
{
/**
* Gets an array of possible ZipArchive problems on the server
*
* @return string[]
*/
private static function getPossibleZipPaths()
{
return array(
'/usr/bin/zip',
'/opt/local/bin/zip', // RSR TODO put back in when we support shellexec on windows,
//'C:/Program\ Files\ (x86)/GnuWin32/bin/zip.exe');
'/opt/bin/zip',
'/bin/zip',
'/usr/local/bin/zip',
'/usr/sfw/bin/zip',
'/usr/xdg4/bin/zip',
);
}
/**
* Gets an array of possible ShellExec Zip problems on the server
*
* @return DUP_PRO_Problem_Fix[]
*/
public static function getShellExecZipProblems()
{
$problem_fixes = array();
if (!self::getShellExecZipPath()) {
$filepath = null;
$possible_paths = self::getPossibleZipPaths();
foreach ($possible_paths as $path) {
if (file_exists($path)) {
$filepath = $path;
break;
}
}
if ($filepath == null) {
$problem_fix = new DUP_PRO_Problem_Fix();
$problem_fix->problem = __('Zip executable not present', 'duplicator-pro');
$problem_fix->fix = __('Install the zip executable and make it accessible to PHP.', 'duplicator-pro');
$problem_fixes[] = $problem_fix;
}
if (Shell::isSuhosinEnabled()) {
$fixDisabled = __(
'Remove any of the following from the disable_functions or suhosin.executor.func.blacklist setting in the php.ini files: %1$s',
'duplicator-pro'
);
} else {
$fixDisabled = __(
'Remove any of the following from the disable_functions setting in the php.ini files: %1$s',
'duplicator-pro'
);
}
//Function disabled at server level
if (Shell::hasDisabledFunctions(array('escapeshellarg', 'escapeshellcmd', 'extension_loaded'))) {
$problem_fix = new DUP_PRO_Problem_Fix();
$problem_fix->problem = __('Required functions disabled in the php.ini.', 'duplicator-pro');
$problem_fix->fix = sprintf($fixDisabled, 'escapeshellarg, escapeshellcmd, extension_loaded.');
$problem_fixes[] = $problem_fix;
}
if (Shell::hasDisabledFunctions(array('popen', 'pclose', 'exec', 'shell_exec'))) {
$problem_fix = new DUP_PRO_Problem_Fix();
$problem_fix->problem = __('Required functions disabled in the php.ini.', 'duplicator-pro');
$problem_fix->fix = sprintf($fixDisabled, 'popen, pclose or exec or shell_exec.');
$problem_fixes[] = $problem_fix;
}
}
return $problem_fixes;
}
/**
* Get the path to the zip program executable on the server
* If wordpress have multiple scan path shell zip archive is disabled
*
* @return null|string Returns the path to the zip program or null if isn't available
*/
public static function getShellExecZipPath()
{
$filepath = null;
if (apply_filters('duplicator_pro_is_shellzip_available', Shell::test(Shell::AVAILABLE_COMMANDS))) {
$scanPath = DUP_PRO_Archive::getScanPaths();
if (count($scanPath) > 1) {
return null;
}
$shellOutput = Shell::runCommand('hash zip 2>&1', Shell::AVAILABLE_COMMANDS);
if ($shellOutput !== false && $shellOutput->isEmpty()) {
$filepath = 'zip';
} else {
$possible_paths = self::getPossibleZipPaths();
foreach ($possible_paths as $path) {
if (file_exists($path)) {
$filepath = $path;
break;
}
}
}
}
return $filepath;
}
/**
* custom shell arg escape sequence
*
* @param string $arg argument to escape
*
* @return string
*/
public static function customShellArgEscapeSequence($arg)
{
return str_replace(array(' ', '-'), array('\ ', '\-'), $arg);
}
}

View File

@@ -0,0 +1,3 @@
<?php
//silent