first commit
This commit is contained in:
@@ -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
481
wp-content/plugins/duplicator-pro-v4.5.16.2/classes/class.db.php
Normal file
481
wp-content/plugins/duplicator-pro-v4.5.16.2/classes/class.db.php
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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 > Tools > 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']);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
//silent
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
//silent
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
//silent
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
//silent
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
//silent
|
||||
Reference in New Issue
Block a user