first commit

This commit is contained in:
Roman Pyrih
2026-04-21 15:48:41 +02:00
commit 7483681901
10216 changed files with 3236626 additions and 0 deletions

View File

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

View File

@@ -0,0 +1,681 @@
<?php
/**
* @package Duplicator\Installer
*/
namespace Duplicator\Installer\Bootstrap;
use Duplicator\Installer\Bootstrap\BootstrapRunner;
use Duplicator\Libs\Shell\Shell;
use Exception;
use ZipArchive;
class BootstrapUtils
{
/**
* Check if php.ini value is changeable
*
* @param string $setting php setting
*
* @return bool
*/
public static function isIniValChangeable($setting): bool
{
static $ini_all;
if (!isset($ini_all)) {
$ini_all = false;
// Sometimes `ini_get_all()` is disabled via the `disable_functions` option for "security purposes".
if (function_exists('ini_get_all')) {
$ini_all = ini_get_all();
}
}
if (isset($ini_all[$setting]['access']) && (INI_ALL === ($ini_all[$setting]['access'] & 7) || INI_USER === ($ini_all[$setting]['access'] & 7))) {
return true;
}
if (!is_array($ini_all)) {
return true;
}
return false;
}
/**
* Check php version
*
* @param string $minPhpVer PHP minimum version required
* @return void
*/
public static function phpVersionCheck($minPhpVer): void
{
if (version_compare(PHP_VERSION, $minPhpVer, '>=')) {
return;
}
$match = null;
$phpVersion = preg_match("#^\d+(\.\d+)*#", PHP_VERSION, $match) ? $match[0] : PHP_VERSION;
?>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="robots" content="noindex,nofollow">
<title>Duplicator Professional - issue</title>
</head>
<body>
<div>
<h1>DUPLICATOR PRO ISSUE: PHP <?php echo $minPhpVer; ?> REQUIRED</h1>
<p>
This server is running PHP: <b><?php echo $phpVersion; ?></b>. <i>A minimum of <b>PHP
<?php echo $minPhpVer; ?></b> is required</i>.<br><br>
<b>Contact your hosting provider or server administrator and let them know you would like to upgrade your PHP version.</b>
</p>
</div>
</body>
</html>
<?php
die();
}
/**
* Get libzip version
*
* @return string
*/
public static function getLibzipVersion()
{
static $libzipVersion = null;
if (is_null($libzipVersion)) {
ob_start();
if (function_exists('phpinfo')) {
phpinfo(INFO_MODULES);
}
$info = ob_get_clean();
if (preg_match('/<td\s.*?>\s*(libzip.*\sver.+?)\s*<\/td>\s*<td\s.*?>\s*(.+?)\s*<\/td>/i', $info, $matches) !== 1) {
$libzipVersion = "0";
} else {
$libzipVersion = $matches[2];
}
}
return $libzipVersion;
}
/**
* Return true if ZipArchive or shell zip is available
*
* @return bool
*/
public static function isZipAvailable(): bool
{
return (self::isPhpZipAvailable() || self::isShellZipAvailable());
}
/**
* Return true if ZipArchive class is avaliable
*
* @return bool
*/
public static function isPhpZipAvailable()
{
return self::classExists(ZipArchive::class);
}
/**
* Return true if ZipArchive class is avaliable
*
* @return bool
*/
public static function isShellZipAvailable(): bool
{
return (self::getUnzipFilePath() !== false);
}
/**
* Check if zip archive is encrypted
*
* @param string $path zip archive path
* @param string $fileToCheck fil path to check (must be a existing file in archive)
*
* @return bool
*/
public static function isZipArchiveEncrypted($path, $fileToCheck)
{
if (self::isPhpZipAvailable()) {
$zip = new ZipArchive();
if (($zipOpenRes = $zip->open($path)) !== true) {
$message = "[ERROR] Couldn't open archive archive file with ZipArchive CODE[" . $zipOpenRes . "]";
throw new Exception($message);
}
if (($stats = $zip->statName($fileToCheck, ZipArchive::FL_NODIR)) == false) {
throw new Exception('Formatting archive error, cannot find file ' . $fileToCheck);
}
if (isset($stats['encryption_method'])) {
// Before PHP 7.2 encryption_method don't exsts
$isEncrypt = ($stats['encryption_method'] > 0);
} else {
$isEncrypt = ($zip->getFromIndex($stats['index']) === false);
}
$zip->close();
return $isEncrypt;
} elseif (self::isShellZipAvailable()) {
return self::isZipArchiveEncryptedShellUnzip($path, $fileToCheck);
} else {
throw new Exception('Zip archve isn\'t avaliable');
}
}
/**
* Check if zip archive is encrypted by using shell exec and unzip
*
* @param string $path zip archive path
* @param string $fileToCheck file to check (must be an existing file in INSTALLER_DIR_NAME folder)
*
* @return bool
*/
protected static function isZipArchiveEncryptedShellUnzip($path, $fileToCheck)
{
$tempFolderName = "temp_0oA8wkOvxjKtngR_dir";
$unzipFilepath = self::getUnzipFilePath();
$unzipCommand = escapeshellcmd($unzipFilepath) .
" -o " . escapeshellcmd($path) . " " .
escapeshellcmd("dup-installer/$fileToCheck") .
" -d " . escapeshellcmd(dirname($path)) . "/" . escapeshellcmd($tempFolderName) . "/ 2>&1";
$output = Shell::runCommandBuffered($unzipCommand);
$encrypted = true;
if (file_exists(dirname($path) . "/$tempFolderName/dup-installer/$fileToCheck")) {
$encrypted = false;
}
BootstrapUtils::rrmdir(dirname($path) . "/$tempFolderName");
return $encrypted;
}
/**
* Check if password fits encrypted zip archive
*
* @param string $archivePath encrypted zip archive path
* @param string $password user's input, password to check
* @param string $fileToCheck file to check (must be an existing file in archive)
* @param int $zipMode One of BootstrapRunner constants
*
* @return bool
*/
public static function zipArchivePasswordCheck($archivePath, $password, $fileToCheck, $zipMode)
{
if ($zipMode == BootstrapRunner::ZIP_MODE_NONE) {
throw new Exception("NOTICE: ZipArchive and Shell Exec are not enabled on this server. Please " .
"talk to your host or server admin about enabling " .
"<a target='_blank' href='https://duplicator.com/knowledge-base/how-to-fix-installer-archive-extraction-issues'>ZipArchive</a> " .
"or <a target='_blank' href='http://php.net/manual/en/function.shell-exec.php'>Shell Exec</a> " .
"on this server or manually extract archive then choose Advanced > Manual Extract in installer.");
}
if ($zipMode == BootstrapRunner::ZIP_MODE_ARCHIVE) {
$zip = new ZipArchive();
if (($zipOpenRes = $zip->open($archivePath)) !== true) {
$message = "[ERROR] Couldn't open archive archive file with ZipArchive CODE[" . $zipOpenRes . "]";
throw new Exception($message);
}
if (($stats = $zip->statName(basename($fileToCheck), ZipArchive::FL_NODIR)) == false) {
throw new Exception("Formatting archive error, cannot find the file " . basename($fileToCheck));
}
$zip->setPassword($password);
$result = $zip->getFromIndex($stats['index']);
$zip->close();
return $result;
}
if ($zipMode == BootstrapRunner::ZIP_MODE_SHELL) {
if ($password == "") {
return false;
}
$destinationDir = dirname($archivePath) . "/tmp";
$unzip_filepath = self::getUnzipFilePath();
if ($unzip_filepath == null) {
throw new Exception("Could not find unzip app, and ZIP_MODE_SHELL is chosen.");
}
$params = "-o -P " . escapeshellarg($password);
$unzip_command = escapeshellcmd($unzip_filepath) . ' ' . $params . ' ' .
escapeshellarg($archivePath) . ' ' .
escapeshellarg($fileToCheck) .
' -d ' . escapeshellarg($destinationDir) . ' 2>&1';
$shellOutput = Shell::runCommandBuffered($unzip_command);
if ($shellOutput->getCode() < 0) {
$errorMsg = "[ERROR] Shell exec unzip failed. Shell::runCommandBuffered returned false.";
self::rrmdir($destinationDir);
throw new Exception($errorMsg);
}
if (file_exists($destinationDir . "/" . $fileToCheck)) {
self::rrmdir($destinationDir);
return true; // Password is correct
}
$shellOutputAsString = $shellOutput->getOutputAsString();
$matchResult = preg_match('/skipping:.*incorrect password/', $shellOutputAsString);
if ($matchResult) {
self::rrmdir($destinationDir);
return false; // Incorrect password
}
// Some other error happened
$errorMsg = "[ERROR] Shell exec unzip failed. Output={$shellOutputAsString}";
$matchResult = preg_match('/skipping:.*need PK compat./', $shellOutputAsString);
if ($matchResult) {
$errorMsg .= "</br>It looks like you haven't used 'shell zip' engine when you created this archive. "
. "Either create new package and use 'shell zip' as archive engine, or "
. "contact the hosting manager and ask them to activate the ZipArchive class, then try again.";
} else {
$errorMsg .= "</br>If you can't fix the problem with 'shell unzip', contact the hosting manager and "
. "ask them to activate the ZipArchive class, then try again.";
}
self::rrmdir($destinationDir);
throw new Exception($errorMsg);
}
throw new Exception("Unrecognised zipMode = $zipMode passed to function zipArchivePasswordCheck.");
}
/**
* Get current url
*
* @param bool $queryString If true the query string will also be returned.
* @param bool $requestUri if true check request uri
* @param int $getParentDirLevel if 0 get current script name or parent folder, if 1 parent folder if 2 parent of parent folder ...
*
* @return string
*/
public static function getCurrentUrl($queryString = true, $requestUri = false, $getParentDirLevel = 0): string
{
// *** HOST
if (isset($_SERVER['HTTP_X_ORIGINAL_HOST'])) {
$host = $_SERVER['HTTP_X_ORIGINAL_HOST'];
} else {
$host = $_SERVER['HTTP_HOST'] ?? $_SERVER['SERVER_NAME']; //WAS SERVER_NAME and caused problems on some boxes
}
// *** PROTOCOL
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
$_SERVER['HTTPS'] = 'on';
}
if (isset($_SERVER['HTTP_X_FORWARDED_SSL']) && $_SERVER['HTTP_X_FORWARDED_SSL'] === 'https') {
$_SERVER['HTTPS'] = 'on';
}
if (isset($_SERVER['HTTP_CF_VISITOR'])) {
$visitor = json_decode($_SERVER['HTTP_CF_VISITOR']);
if (is_object($visitor) && property_exists($visitor, 'scheme') && $visitor->scheme == 'https') {
$_SERVER['HTTPS'] = 'on';
}
}
$protocol = 'http' . ((isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) === 'on') ? 's' : '');
if ($requestUri) {
$serverUrlSelf = preg_replace('/\?.*$/', '', $_SERVER['REQUEST_URI']);
} else {
// *** SCRIPT NAME
$serverUrlSelf = $_SERVER['SCRIPT_NAME'];
for ($i = 0; $i < $getParentDirLevel; $i++) {
$serverUrlSelf = preg_match('/^[\\\\\/]?$/', dirname($serverUrlSelf)) ? '' : dirname($serverUrlSelf);
}
}
// *** QUERY STRING
$query = ($queryString && isset($_SERVER['QUERY_STRING']) && strlen($_SERVER['QUERY_STRING']) > 0) ? '?' . $_SERVER['QUERY_STRING'] : '';
return $protocol . '://' . $host . $serverUrlSelf . $query;
}
/**
* This function make a chmod only if the are different from perms input and if chmod function is enabled
*
* This function handles the variable MODE in a way similar to the chmod of lunux
* So the MODE variable can be
* 1) an octal number (0755)
* 2) a string that defines an octal number ("644")
* 3) a string with the following format [ugoa]*([-+=]([rwx]*)+
*
* examples
* u+rw add read and write at the user
* u+rw,uo-wx add read and write ad the user and remove wx at groupd and other
* a=rw is equal at 666
* u=rwx,go-rwx is equal at 700
*
* @param string $file file path
* @param int|string $mode mode
*
* @return boolean
*/
public static function chmod($file, $mode)
{
if (!file_exists($file)) {
return false;
}
$octalMode = 0;
if (is_int($mode)) {
$octalMode = $mode;
} elseif (is_string($mode)) {
$mode = trim($mode);
if (preg_match('/([0-7]{1,3})/', $mode)) {
$octalMode = intval(('0' . $mode), 8);
} elseif (preg_match_all('/(a|[ugo]{1,3})([-=+])([rwx]{1,3})/', $mode, $gMatch, PREG_SET_ORDER)) {
if (!function_exists('fileperms')) {
return false;
}
// start by file permission
$octalMode = (fileperms($file) & 0777);
foreach ($gMatch as $matches) {
// [ugo] or a = ugo
$group = $matches[1];
if ($group === 'a') {
$group = 'ugo';
}
// can be + - =
$action = $matches[2];
// [rwx]
$gPerms = $matches[3];
// reset octal group perms
$octalGroupMode = 0;
// Init sub perms
$subPerm = 0;
$subPerm += strpos($gPerms, 'x') !== false ? 1 : 0; // mask 001
$subPerm += strpos($gPerms, 'w') !== false ? 2 : 0; // mask 010
$subPerm += strpos($gPerms, 'r') !== false ? 4 : 0; // mask 100
$ugoLen = strlen($group);
if ($action === '=') {
// generate octal group permsissions and ugo mask invert
$ugoMaskInvert = 0777;
for ($i = 0; $i < $ugoLen; $i++) {
switch ($group[$i]) {
case 'u':
$octalGroupMode |= $subPerm << 6; // mask xxx000000
$ugoMaskInvert &= 077;
break;
case 'g':
$octalGroupMode |= $subPerm << 3; // mask 000xxx000
$ugoMaskInvert &= 0707;
break;
case 'o':
$octalGroupMode |= $subPerm; // mask 000000xxx
$ugoMaskInvert &= 0770;
break;
}
}
// apply = action
$octalMode &= $ugoMaskInvert | $octalGroupMode;
} else {
// generate octal group permsissions
for ($i = 0; $i < $ugoLen; $i++) {
switch ($group[$i]) {
case 'u':
$octalGroupMode |= $subPerm << 6; // mask xxx000000
break;
case 'g':
$octalGroupMode |= $subPerm << 3; // mask 000xxx000
break;
case 'o':
$octalGroupMode |= $subPerm; // mask 000000xxx
break;
}
}
// apply + or - action
switch ($action) {
case '+':
$octalMode |= $octalGroupMode;
break;
case '-':
$octalMode &= ~$octalGroupMode;
break;
}
}
}
}
}
// if input permissions are equal at file permissions return true without performing chmod
if (function_exists('fileperms') && $octalMode === (fileperms($file) & 0777)) {
return true;
}
if (!function_exists('chmod')) {
return false;
}
return @chmod($file, $octalMode);
}
/**
* This function creates a folder if it does not exist and performs a chmod.
* it is different from the normal mkdir function to which an umask is applied to the input permissions.
*
* This function handles the variable MODE in a way similar to the chmod of lunux
* So the MODE variable can be
* 1) an octal number (0755)
* 2) a string that defines an octal number ("644")
* 3) a string with the following format [ugoa]*([-+=]([rwx]*)+
*
* @param string $path folder path
* @param int|string $mode mode permissions
* @param bool $recursive Allows the creation of nested directories specified in the pathname. Default to false.
* @param resource $context not used for windows bug
*
* @return boolean bool TRUE on success or FALSE on failure.
*
* @todo check recursive true and multiple chmod
*/
public static function mkdir($path, $mode = 0777, $recursive = false, $context = null)
{
if (strlen($path) > PHP_MAXPATHLEN) {
throw new Exception('Skipping a file that exceeds allowed max path length [' . PHP_MAXPATHLEN . ']. File: ' . $path);
}
if (!file_exists($path)) {
if (!function_exists('mkdir')) {
return false;
}
if (!@mkdir($path, 0777, $recursive)) {
return false;
}
}
return self::chmod($path, $mode);
}
/**
* Checks to see if a string starts with specific characters
*
* @param string $haystack haystack
* @param string $needle needle
*
* @return bool
*/
public static function startsWith($haystack, $needle): bool
{
return $needle === "" || strrpos($haystack, $needle, -strlen($haystack)) !== false;
}
/**
* Checks to see if the server supports issuing commands to shell_exex
*
* @return bool Returns true shell_exec can be ran on this server
*/
public static function hasShellExec(): bool
{
if (!Shell::test()) {
return false;
}
return true;
}
/**
* Gets the possible system commands for unzip on Linux
*
* @return bool|string Returns unzip file path that can execute the unzip command of false if don't exists
*/
public static function getUnzipFilePath()
{
static $filepath = null;
if ($filepath === null) {
if (!self::hasShellExec()) {
$filepath = false;
} elseif (Shell::runCommandBuffered('hash unzip 2>&1')->getCode() >= 0) {
$filepath = 'unzip';
} else {
$filepath = false;
$possible_paths = [
'/usr/bin/unzip',
'/opt/local/bin/unzip',
'/bin/unzip',
'/usr/local/bin/unzip',
'/usr/sfw/bin/unzip',
'/usr/xdg4/bin/unzip',
'/opt/bin/unzip',
// RSR TODO put back in when we support shellexec on windows,
];
foreach ($possible_paths as $path) {
if (file_exists($path)) {
$filepath = $path;
break;
}
}
}
}
return $filepath;
}
/**
* Display human readable byte sizes such as 150MB
*
* @param int $size The size in bytes
*
* @return string A readable byte size format such as 100MB
*/
public static function readableByteSize($size): string
{
try {
$units = [
'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";
}
}
/**
* Safely remove a directory and recursively files and directory upto multiple sublevels
*
* @param string $path The full path to the directory to remove
*
* @return bool Returns true if all content was removed
*/
public static function rrmdir($path)
{
if (is_dir($path)) {
if (($dh = opendir($path)) === false) {
return false;
}
while (($object = readdir($dh)) !== false) {
if ($object == "." || $object == "..") {
continue;
}
if (!self::rrmdir($path . "/" . $object)) {
closedir($dh);
return false;
}
}
closedir($dh);
return @rmdir($path);
} else {
if (is_writable($path)) {
return @unlink($path);
} else {
return false;
}
}
}
/**
* Makes path safe for any OS for PHP
*
* Paths should ALWAYS READ be "/"
* uni: /home/path/file.txt
* win: D:/home/path/file.txt
*
* @param string $path TThe path to make safe
*
* @return string The original $path with a with all slashes facing '/'.
*/
public static function setSafePath($path): string
{
return str_replace("\\", "/", $path);
}
/**
* remove all non stamp chars from string and newline
* trim string
*
* @param string $string input string
*
* @return string
*/
public static function sanitizeNSCharsNewline($string): string
{
return (string) preg_replace(
'/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F\r\n]/u',
'',
(string) $string
);
}
/**
* Returns true if the class exists, false otherwise
*
* @param string $className Name of the class to check if it exists
* @param boolean $autoload Parameter that will be passed to class_exists as second
*
* @return boolean
*/
public static function classExists($className, $autoload = true)
{
if (function_exists("ini_get")) {
$disabled = explode(',', ini_get('disable_classes'));
return !in_array($className, $disabled);
}
if (!class_exists($className, $autoload)) {
return false;
}
// We can only suppose that it exists, can't be 100% sure, but it's the best guess
return true;
}
}

View File

@@ -0,0 +1,113 @@
<?php
/**
* @package Duplicator\Installer
*/
namespace Duplicator\Installer\Bootstrap;
use Exception;
class LogHandler
{
/** @var bool */
private static $initialized = false;
/** @var callable */
private static $logCallback;
/**
* This function only initializes the error handler the first time it is called
*
* @param callable $logCallback log callback
*
* @return void
*/
public static function initErrorHandler($logCallback): void
{
if (!self::$initialized) {
if (!is_callable($logCallback)) {
throw new Exception('Log callback must be callable');
}
self::$logCallback = $logCallback;
@set_error_handler([self::class, 'error']);
@register_shutdown_function([self::class, 'shutdown']);
self::$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): bool
{
switch ($errno) {
case E_ERROR:
$log_message = self::getMessage($errno, $errstr, $errfile, $errline);
if (call_user_func(self::$logCallback, $log_message) === false) {
$log_message = "Can\'t wrinte logfile\n\n" . $log_message;
}
die('<pre>' . htmlspecialchars($log_message) . '</pre>');
case E_NOTICE:
case E_WARNING:
default:
$log_message = self::getMessage($errno, $errstr, $errfile, $errline);
call_user_func(self::$logCallback, $log_message);
break;
}
return true;
}
/**
* Get message from error
*
* @param int $errno errno
* @param string $errstr message
* @param string $errfile file
* @param int $errline line
*
* @return string
*/
private static function getMessage($errno, $errstr, $errfile, $errline): string
{
$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;
return $result . (' [CODE:' . $errno . '|FILE:' . $errfile . '|LINE:' . $errline . ']');
}
/**
* Shutdown handler
*
* @return void
*/
public static function shutdown(): void
{
if (($error = error_get_last())) {
LogHandler::error($error['type'], $error['message'], $error['file'], $error['line']);
}
}
}

View File

@@ -0,0 +1,254 @@
<?php
/**
* Class that collects the functions of initial checks on the requirements to run the plugin
*
* @package Duplicator
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Core\Addons;
abstract class InstAbstractAddonCore
{
const ADDON_DATA_CONTEXT = 'duplicator_addon';
/**
* Addons instances
*
* @var self[]
*/
private static $instances = [];
/**
* Current addon data
*
* @var mixed[]
*/
protected $addonData = [];
/**
* Get curent addon instance
*
* @return self
*/
public static function getInstance()
{
$class = static::class;
if (!isset(self::$instances[$class])) {
self::$instances[$class] = new static();
}
return self::$instances[$class];
}
/**
* Constructor
*/
final protected function __construct()
{
$reflect = new \ReflectionClass(static::class);
$this->addonData = self::getInitAddonData($reflect->getShortName());
}
/**
* Function called on addon init only if is available
*
* @return void
*/
abstract public function init();
/**
* Get main addon file path
*
* @return string
*/
public static function getAddonFile()
{
// To prevent the warning about static abstract functions that appears in PHP 5.4/5.6 I use this trick.
throw new \Exception('this function have to overwritte on child class');
}
/**
* Get main addon folder
*
* @return string
*/
public static function getAddonPath()
{
// To prevent the warning about static abstract functions that appears in PHP 5.4/5.6 I use this trick.
throw new \Exception('this function have to overwritte on child class');
}
/**
* Get slug of current addon
*
* @return string
*/
public function getSlug()
{
return $this->addonData['slug'];
}
/**
* True if current addon is available
*
* @return boolean
*/
public function canEnable()
{
if (version_compare(PHP_VERSION, $this->addonData['requiresPHP'], '<')) {
return false;
}
if (version_compare(DUPX_VERSION, $this->addonData['requiresDuplcator'], '<')) {
return false;
}
return true;
}
/**
* True if addon has dependencies
*
* @return boolean
*/
public function hasDependencies()
{
$avaliableAddons = InstAddonsManager::getInstance()->getAvailableAddons();
return !array_diff($this->addonData['requiresAddons'], $avaliableAddons);
}
/**
* Get addon data from header addon file
*
* @param string $class addon class
*
* @return mixed[]
*/
protected static function getInitAddonData($class)
{
$data = self::getFileFata(static::getAddonFile(), self::getDefaltHeaders());
$getDefaultVal = self::getDefaultHeadersValues();
foreach ($data as $key => $val) {
if (strlen($val) === 0) {
$data[$key] = $getDefaultVal[$key];
}
}
if (!is_array($data['requiresAddons'])) {
$data['requiresAddons'] = explode(',', $data['requiresAddons']);
}
$data['requiresAddons'] = array_map('trim', $data['requiresAddons']);
$data['slug'] = $class;
if (strlen($data['name']) === 0) {
$data['name'] = $data['slug'];
}
return $data;
}
/**
* Retur default addon date headers
*
* @return array<string, mixed>
*/
protected static function getDefaultHeadersValues()
{
static $defaultHeaders = null;
if (is_null($defaultHeaders)) {
$defaultHeaders = [
'name' => '',
'addonURI' => '',
'version' => '0',
'description' => '',
'author' => '',
'authorURI' => '',
'requiresWP' => '5.3',
'requiresPHP' => '7.4',
'requiresDuplcator' => '4.0.2',
'requiresAddons' => [],
];
}
return $defaultHeaders;
}
/**
* Return headers list keys
*
* @return array<string, string>
*/
protected static function getDefaltHeaders()
{
return [
'name' => 'Name',
'addonURI' => 'Addon URI',
'version' => 'Version',
'description' => 'Description',
'author' => 'Author',
'authorURI' => 'Author URI',
'requiresWP' => 'Requires WP min version',
'requiresPHP' => 'Requires PHP',
'requiresDuplcator' => 'Requires Duplicator min version',
'requiresAddons' => 'Requires addons',
];
}
/**
* Retrieve metadata from a file.
*
* Searches for metadata in the first 8 KB of a file, such as a plugin or theme.
* Each piece of metadata must be on its own line. Fields can not span multiple
* lines, the value will get cut at the end of the first line.
*
* If the file data is not within that first 8 KB, then the author should correct
* their plugin file and move the data headers to the top.
*
* from WordPress get_file_data function
*
* @param string $file Absolute path to the file.
* @param array<string, string> $defaultHeaders List of headers, in the format `array( 'HeaderKey' => 'Header Name' )`.
*
* @return array<string, mixed> Array of file headers in `HeaderKey => Header Value` format.
*/
protected static function getFileFata($file, $defaultHeaders)
{
// We don't need to write to the file, so just open for reading.
$fp = fopen($file, 'r');
// Pull only the first 8 KB of the file in.
$file_data = fread($fp, 8 * KB_IN_BYTES);
// PHP will close file handle, but we are good citizens.
fclose($fp);
// Make sure we catch CR-only line endings.
$file_data = str_replace("\r", "\n", $file_data);
$all_headers = $defaultHeaders;
foreach ($all_headers as $field => $regex) {
if (preg_match('/^[ \t\/*#@]*' . preg_quote($regex, '/') . ':(.*)$/mi', $file_data, $match) && $match[1]) {
$all_headers[$field] = self::cleanupHeaderComment($match[1]);
} else {
$all_headers[$field] = '';
}
}
return $all_headers;
}
/**
* Strip close comment and close php tags from file headers used by WP.
*
* From WordPress _cleanup_header_comment
*
* @param string $str Header comment to clean up.
*
* @return string
*/
protected static function cleanupHeaderComment($str)
{
return trim(preg_replace('/\s*(?:\*\/|\?>).*/', '', $str));
}
}

View File

@@ -0,0 +1,169 @@
<?php
/**
* Class that collects the functions of initial checks on the requirements to run the plugin
*
* @package Duplicator
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Core\Addons;
use Duplicator\Installer\Core\Hooks\HooksMng;
use Duplicator\Libs\Snap\SnapIO;
use Duplicator\Installer\Utils\Log\Log;
final class InstAddonsManager
{
/** @var ?self */
private static $instance;
/** @var InstAbstractAddonCore[] */
private array $addons;
/** @var InstAbstractAddonCore[] */
private $enabledAddons = [];
/**
*
* @return self
*/
public static function getInstance()
{
if (is_null(self::$instance)) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Inizialize addons
*/
private function __construct()
{
$this->addons = self::getAddonListFromFolder();
}
/**
* inizialize all abaiblae addons
*
* @return void
*/
public function inizializeAddons(): void
{
foreach ($this->addons as $addon) {
if ($addon->canEnable() && $addon->hasDependencies()) {
$this->enabledAddons[] = $addon->getSlug();
$addon->init();
Log::info('ADDON ' . $addon->getAddonFile() . ' ENABLED', Log::LV_DETAILED);
} else {
Log::info('CAN\'T ENABLE ADDON ' . $addon->getSlug());
}
}
HooksMng::getInstance()->doAction('duplicator_addons_loaded');
}
/**
*
* @return string[]
*/
public function getAvailableAddons(): array
{
$result = [];
foreach ($this->addons as $addon) {
$result[] = $addon->getSlug();
}
return $result;
}
/**
*
* @return InstAbstractAddonCore[]
*/
public function getEnabledAddons()
{
return $this->enabledAddons;
}
/**
* return addons folder
*
* @return string
*/
public static function getAddonsPath(): string
{
return DUPX_INIT . '/addons';
}
/**
*
* @return InstAbstractAddonCore[]
*/
private static function getAddonListFromFolder(): array
{
$addonList = [];
$checkDir = SnapIO::trailingslashit(self::getAddonsPath());
if (!is_dir($checkDir)) {
return [];
}
if (($dh = opendir($checkDir)) == false) {
return [];
}
while (($elem = readdir($dh)) !== false) {
if ($elem === '.' || $elem === '..') {
continue;
}
$fullPath = $checkDir . $elem;
$addonMainFile = false;
$addonMainClass = '';
if (!is_dir($fullPath)) {
continue;
}
if (($addonDh = opendir($fullPath)) == false) {
continue;
}
while (($addonElem = readdir($addonDh)) !== false) {
if ($addonElem === '.' || $addonElem === '..') {
continue;
}
$info = pathinfo($fullPath . '/' . $addonElem);
if (strcasecmp($elem, $info['filename']) === 0) {
$addonMainFile = $checkDir . $elem . '/' . $addonElem;
$addonMainClass = '\\Duplicator\\Installer\\Addons\\' . $info['filename'] . '\\' . $info['filename'];
break;
}
}
if (empty($addonMainFile)) {
continue;
}
try {
if (!is_subclass_of($addonMainClass, \Duplicator\Installer\Core\Addons\InstAbstractAddonCore::class)) {
continue;
}
} catch (\Exception $e) {
Log::info('Addon file ' . $addonMainFile . ' exists but not countain addon main core class, Exception: ' . $e->getMessage());
continue;
} catch (\Error $e) {
Log::info('Addon file ' . $addonMainFile . ' exists but generate an error, Exception: ' . $e->getMessage());
continue;
}
$addonObj = $addonMainClass::getInstance();
$addonList[$addonObj->getSlug()] = $addonObj;
}
closedir($dh);
return $addonList;
}
}

View File

@@ -0,0 +1,554 @@
<?php
/**
* @package Duplicator
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Core;
use Duplicator\Installer\Core\Addons\InstAddonsManager;
use Duplicator\Installer\Core\Deploy\ServerConfigs;
use Duplicator\Installer\Core\Params\PrmMng;
use Duplicator\Installer\Utils\InstallerOrigFileMng;
use Duplicator\Installer\Utils\InstDescMng;
use Duplicator\Installer\Utils\Log\Log;
use Duplicator\Installer\Utils\Log\LogHandler;
use Duplicator\Libs\Snap\SnapIO;
use Duplicator\Libs\Snap\SnapURL;
use Duplicator\Libs\Snap\SnapUtil;
use DUPX_DBInstall;
use DUPX_Extraction;
use DUPX_S3_Funcs;
use DUPX_U;
/**
* Class that collects the functions of initial duplicator Bootstrap
*/
class Bootstrap
{
const DESCRIPTORS_PREFIX = 'dup_descriptors_';
const MINIMUM_PHP_VERSION = '7.4';
/**
* this variable becomes false after the installer is initialized by skipping the shutdown function defined in the boot class
*
* @var bool
*/
private static $shutdownFunctionEnaled = true;
/**
*
* @var int
*/
public static $dupInitFolderParentLevel = 1;
/**
* Init installer
*
* @param integer $folderParentLevel num folder parents of home path
*
* @return void
*/
public static function init($folderParentLevel = 1): void
{
self::setHTTPHeaders();
self::$dupInitFolderParentLevel = max(1, (int) $folderParentLevel);
self::phpVersionCheck();
$GLOBALS['DUPX_ENFORCE_PHP_INI'] = false;
// INIT ERROR LOG FILE (called before evrithing)
if (function_exists('register_shutdown_function')) {
register_shutdown_function([self::class, 'bootShutdown']);
}
if (self::initPhpErrorLog(false) === false) {
// Enable this only for debugging. Generate a log too alarmist.
SnapUtil::errorLog('DUPLICATOR CAN\'T CHANGE THE PATH OF PHP ERROR LOG FILE');
}
/*
* INIZIALIZE
*/
define('DUPX_INIT_URL', SnapURL::getCurrentUrl(false, false, self::$dupInitFolderParentLevel));
define('DUPX_ROOT_URL', SnapURL::getCurrentUrl(false, false, self::$dupInitFolderParentLevel + 1));
// includes main files
self::includes();
// set time for logging time
Log::resetTime();
// set all PHP.INI settings
self::phpIni();
// init log files
self::initLogs();
// init global values
\DUPX_Constants::init();
// init addond before evrithing
InstAddonsManager::getInstance()->inizializeAddons();
// init templates
self::templatesInit();
// SECURITY CHECK
Security::getInstance()->check();
// init error handler after constant
LogHandler::initErrorHandler();
// init params
PrmMng::getInstance()->initParams();
// read params from request and init global value
self::initInstallerFiles();
// check custom hosts
\DUPX_Custom_Host_Manager::getInstance()->init();
$pathInfo = $_SERVER['PATH_INFO'] ?? '';
Log::info("\n\n"
. "==============================================\n"
. "= BOOT INIT OK [" . $pathInfo . "]\n"
. "==============================================\n", Log::LV_DETAILED);
if (Log::isLevel(Log::LV_DEBUG)) {
Log::info('-------------------');
Log::info('PARAMS');
Log::info(PrmMng::getInstance()->getParamsToText());
Log::info('-------------------');
}
\DUPX_DB_Tables::getInstance();
}
/**
* Init ini_set and default constants
*
* @return void
*/
public static function phpIni(): void
{
// Absolute path to the Installer directory. - necessary for php protection
if (!defined('KB_IN_BYTES')) {
define('KB_IN_BYTES', 1024);
}
if (!defined('MB_IN_BYTES')) {
define('MB_IN_BYTES', 1024 * KB_IN_BYTES);
}
if (!defined('GB_IN_BYTES')) {
define('GB_IN_BYTES', 1024 * MB_IN_BYTES);
}
if (!defined('DUPLICATOR_PHP_MAX_MEMORY')) {
define('DUPLICATOR_PHP_MAX_MEMORY', 4096 * MB_IN_BYTES);
}
date_default_timezone_set('UTC'); // Some machines dont have this set so just do it here.
if (strlen(@ini_get('date.timezone')) === 0) {
// Some machines dont date.timezone set
@ini_set('date.timezone', 'UTC');
}
@ignore_user_abort(true);
@set_time_limit(3600);
$defaultCharset = ini_get("default_charset");
if (empty($defaultCharset) && SnapUtil::isIniValChangeable('default_charset')) {
@ini_set("default_charset", 'utf-8');
}
if (SnapUtil::isIniValChangeable('memory_limit')) {
@ini_set('memory_limit', (string) DUPLICATOR_PHP_MAX_MEMORY);
}
if (SnapUtil::isIniValChangeable('max_input_time')) {
@ini_set('max_input_time', '-1');
}
if (SnapUtil::isIniValChangeable('pcre.backtrack_limit')) {
@ini_set('pcre.backtrack_limit', (string) PHP_INT_MAX);
}
//PHP INI SETUP: all time in seconds
if (!isset($GLOBALS['DUPX_ENFORCE_PHP_INI']) || !$GLOBALS['DUPX_ENFORCE_PHP_INI']) {
if (SnapUtil::isIniValChangeable('mysql.connect_timeout')) {
@ini_set('mysql.connect_timeout', '5000');
}
if (SnapUtil::isIniValChangeable('max_execution_time')) {
@ini_set("max_execution_time", '5000');
}
if (SnapUtil::isIniValChangeable('max_input_time')) {
@ini_set("max_input_time", '5000');
}
if (SnapUtil::isIniValChangeable('default_socket_timeout')) {
@ini_set('default_socket_timeout', '5000');
}
@set_time_limit(0);
}
}
/**
* Include default utils files and constants
*
* @return void
*/
public static function includes(): void
{
require_once(DUPX_INIT . '/classes/config/class.conf.wp.php');
require_once(DUPX_INIT . '/classes/utilities/class.u.php');
require_once(DUPX_INIT . '/classes/utilities/class.u.notices.manager.php');
require_once(DUPX_INIT . '/classes/utilities/template/class.u.template.manager.php');
require_once(DUPX_INIT . '/classes/validation/class.validation.manager.php');
require_once(DUPX_INIT . '/classes/database/class.db.php');
require_once(DUPX_INIT . '/classes/database/class.db.functions.php');
require_once(DUPX_INIT . '/classes/database/class.db.tables.php');
require_once(DUPX_INIT . '/classes/database/class.db.table.item.php');
require_once(DUPX_INIT . '/classes/class.http.php');
require_once(DUPX_INIT . '/classes/class.package.php');
require_once(DUPX_INIT . '/classes/class.server.php');
require_once(DUPX_INIT . '/classes/config/class.archive.config.php');
require_once(DUPX_INIT . '/classes/config/class.constants.php');
require_once(DUPX_INIT . '/classes/config/class.conf.utils.php');
require_once(DUPX_INIT . '/ctrls/classes/class.ctrl.ajax.php');
require_once(DUPX_INIT . '/ctrls/classes/class.ctrl.params.php');
require_once(DUPX_INIT . '/ctrls/ctrl.base.php');
require_once(DUPX_INIT . '/ctrls/classes/class.ctrl.extraction.php');
require_once(DUPX_INIT . '/ctrls/classes/class.ctrl.dbinstall.php');
require_once(DUPX_INIT . '/ctrls/classes/class.ctrl.s3.funcs.php');
require_once(DUPX_INIT . '/classes/view-helpers/class.u.html.php');
require_once(DUPX_INIT . '/classes/view-helpers/class.view.php');
require_once(DUPX_INIT . '/classes/host/class.custom.host.manager.php');
require_once(DUPX_INIT . '/classes/class.engine.php');
}
/**
* This function moves the error_log.php into the dup-installer directory.
* It is called before including any other file so it uses only native PHP functions.
*
* !!! Don't use any Duplicator function within this function. !!!
*
* @param bool $reset if true reset log file
*
* @return boolean
*/
public static function initPhpErrorLog($reset = false): bool
{
if (!function_exists('ini_set')) {
return false;
}
$logFile = DUPX_INIT . '/' . InstDescMng::getInstance()->getName(InstDescMng::TYPE_INST_PHP_ERROR_LOG);
if (file_exists($logFile)) {
if (!is_writable($logFile)) {
return false;
} elseif ($reset && function_exists('unlink')) {
@unlink($logFile);
}
}
if (function_exists('error_reporting')) {
error_reporting(E_ALL);
}
@ini_set("log_errors", '1');
if (@ini_set("error_log", $logFile) === false) {
return false;
}
if (!file_exists($logFile)) {
SnapUtil::errorLog("PHP ERROR LOG INIT");
}
return true;
}
/**
* It is called before including any other file so it uses only native PHP functions.
*
* !!! Don't use any Duplicator function within this function. !!!
*
* @return bool|string package hash or false if fail
*/
public static function getPackageHash()
{
static $packageHash = null;
if (is_null($packageHash)) {
$searchStr = DUPX_INIT . '/' . self::DESCRIPTORS_PREFIX . '*';
$config_files = glob($searchStr, GLOB_ONLYDIR);
if (empty($config_files)) {
$packageHash = false;
} else {
$descriptor_folder_path = array_pop($config_files);
$descriptor_folder_name = basename($descriptor_folder_path);
$packageHash = substr($descriptor_folder_name, strlen(self::DESCRIPTORS_PREFIX));
}
}
return $packageHash;
}
/**
* This function init all params before read from request
*
* @return void
*/
protected static function initParamsBase()
{
// GET PARAMS FROM REQUEST
\DUPX_Ctrl_Params::setParamsBase();
// set log level from params
Log::setLogLevel();
Log::setPostProcessCallback(['DUPX_CTRL', 'renderPostProcessings']);
Log::setAfterFatalErrorCallback(function (): void {
if (InstState::getInstance()->getMode() === InstState::MODE_OVR_INSTALL) {
DUPX_U::maintenanceMode(false);
}
});
$paramsManager = PrmMng::getInstance();
$GLOBALS['DUPX_DEBUG'] = $paramsManager->getValue(PrmMng::PARAM_DEBUG);
}
/**
* Makes sure no caching mechanism is used during install
*
* @return void
*/
protected static function setHTTPHeaders()
{
header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
header("Cache-Control: post-check=0, pre-check=0", false);
header("Pragma: no-cache");
}
/**
* Init log header
*
* @return void
*/
protected static function initLogs()
{
if (!chdir(DUPX_INIT)) {
// RSR TODO: Can't change directories
throw new \Exception("Can't change to directory " . DUPX_INIT);
}
//Restart log if user starts from step 0
if (self::isInit()) {
self::initPhpErrorLog(true);
Log::clearLog();
Log::info("********************************************************************************");
Log::info('* DUPLICATOR-PRO: Install-Log');
Log::info('* STEP-0 START @ ' . @date('h:i:s'));
Log::info('* NOTICE: Do NOT post to public sites or forums!!');
Log::info("********************************************************************************");
}
}
/**
* Init all installer files
*
* @return void
*/
protected static function initInstallerFiles()
{
if (!chdir(DUPX_INIT)) {
// RSR TODO: Can't change directories
throw new \Exception("Can't change to directory " . DUPX_INIT);
}
//Restart log if user starts from step 0
if (self::isInit()) {
self::logHeader();
\DUPX_NOTICE_MANAGER::getInstance()->resetNotices();
// LOAD PARAMS AFTER LOG RESET
$paramManager = PrmMng::getInstance();
$paramManager->load(true);
try {
InstallerOrigFileMng::getInstance()->restoreAll([
ServerConfigs::CONFIG_ORIG_FILE_USERINI_ID,
ServerConfigs::CONFIG_ORIG_FILE_PHPINI_ID,
ServerConfigs::CONFIG_ORIG_FILE_WEBCONFIG_ID,
ServerConfigs::CONFIG_ORIG_FILE_HTACCESS_ID,
ServerConfigs::CONFIG_ORIG_FILE_WPCONFIG_ID,
]);
} catch (\Exception $e) {
Log::logException($e, Log::LV_DEFAULT, 'CANT RESTORE CONFIG FILES FORM PREVISION INSTALLATION');
\DUPX_NOTICE_MANAGER::getInstance()->addNextStepNotice([
'shortMsg' => 'The installer cannot restore files from a previous installation. ',
'longMsg' => 'This problem does not affect the current installation so you can continue.<br>'
. 'This can happen if the root folder does not have write permissions.',
'longMsgMode' => \DUPX_NOTICE_ITEM::MSG_MODE_HTML,
'level' => \DUPX_NOTICE_ITEM::NOTICE,
]);
}
self::initParamsBase();
DUPX_Extraction::resetData();
DUPX_DBInstall::resetData();
DUPX_S3_Funcs::resetData();
self::renameHtaccess();
// update state only if isn't set by param overwrite
InstState::getInstance()->checkState(true, false);
// On init remove maintenance mode
\DUPX_U::maintenanceMode(false);
} else {
// INIT PARAMS
$paramManager = PrmMng::getInstance();
$paramManager->load();
self::initParamsBase();
}
$paramManager->save();
}
/**
* Rename .htaccess file in dup-installer folder if it exists, so that it does not interfere with installer
*
* @return void
*/
protected static function renameHtaccess()
{
$htaccessPath = DUPXABSPATH . "/.htaccess";
if (!file_exists($htaccessPath)) {
return;
}
$htaccessPathRenamed = DUPXABSPATH . "/renamed_" . date_format(new \DateTime(), 'mdYHis') . ".htaccess";
if (!SnapIO::rename($htaccessPath, $htaccessPathRenamed)) {
$errorMsg = "WARNING: Could not delete/rename file \"$htaccessPath\". That file could interfere with the installation.\n";
$errorMsg .= "If you encounter problems like buttons not working, please remove it yourself manually and restart the installer.";
Log::info($errorMsg);
} else {
Log::info(".htaccess file was found in dup-installer folder and it was renamed to avoid interference with installer.");
}
}
/**
* Write log header
*
* @return void
*/
protected static function logHeader()
{
$archiveConfig = \DUPX_ArchiveConfig::getInstance();
$colSize = 60;
$labelPadSize = 20;
$os = defined('PHP_OS') ? PHP_OS : 'unknown';
$log = '';
$log .= str_pad(
str_pad('PACKAGE INFO', $labelPadSize, '_', STR_PAD_RIGHT) . ' ' . 'ORIGINAL SERVER',
$colSize,
' ',
STR_PAD_RIGHT
) . '|' . 'CURRENT SERVER' . "\n";
$log .= str_pad(
str_pad('OS', $labelPadSize, '_', STR_PAD_RIGHT) . ': ' . $archiveConfig->version_os,
$colSize,
' ',
STR_PAD_RIGHT
) . '|' . $os . "\n";
$log .= str_pad(
str_pad('PHP VERSION', $labelPadSize, '_', STR_PAD_RIGHT) . ': ' . $archiveConfig->version_php,
$colSize,
' ',
STR_PAD_RIGHT
) . '|' . phpversion() . "\n";
$log .= "********************************************************************************";
Log::info($log, Log::LV_DEFAULT);
Log::info("CURRENT SERVER INFO");
Log::info(str_pad('PHP', $labelPadSize, '_', STR_PAD_RIGHT) . ': ' . phpversion() . ' | SAPI: ' . php_sapi_name());
Log::info(str_pad('PHP MEMORY', $labelPadSize, '_', STR_PAD_RIGHT) . ': ' . $GLOBALS['PHP_MEMORY_LIMIT'] . ' | SUHOSIN: ' . $GLOBALS['PHP_SUHOSIN_ON']);
Log::info(str_pad('ARCHITECTURE', $labelPadSize, '_', STR_PAD_RIGHT) . ': ' . SnapUtil::getArchitectureString());
Log::info(str_pad('SERVER', $labelPadSize, '_', STR_PAD_RIGHT) . ': ' . $_SERVER['SERVER_SOFTWARE']);
Log::info(str_pad('DOC ROOT', $labelPadSize, '_', STR_PAD_RIGHT) . ': ' . Log::v2str(DUPX_ROOT));
Log::info(str_pad('REQUEST URL', $labelPadSize, '_', STR_PAD_RIGHT) . ': ' . Log::v2str(DUPX_ROOT_URL));
Log::info("********************************************************************************");
}
/**
* return true if is the first installer call from installer.php
*
* @return bool
*/
public static function isInit(): bool
{
// don't use param manager because isn't initialized
$isFirstStep = isset($_REQUEST[PrmMng::PARAM_CTRL_ACTION]) && $_REQUEST[PrmMng::PARAM_CTRL_ACTION] === "ctrl-step1";
$isInitialized = isset($_REQUEST[PrmMng::PARAM_STEP_ACTION]) && !empty($_REQUEST[PrmMng::PARAM_STEP_ACTION]);
return $isFirstStep && !$isInitialized;
}
/**
* This function disables the shutdown function defined in the boot class
*
* @return void
*/
public static function disableBootShutdownFunction(): void
{
self::$shutdownFunctionEnaled = false;
}
/**
* This function sets the shutdown function before the installer is initialized.
* Prevents blank pages.
*
* After the plugin is initialized it will be set as a shudwon function LogHandler::shutdown
*
* !!! Don't use any Duplicator function within this function. !!!
*
* @return void
*/
public static function bootShutdown(): void
{
if (!self::$shutdownFunctionEnaled) {
return;
}
if (($error = error_get_last())) {
?>
<h1>BOOT SHUTDOWN FATAL ERROR</H1>
<pre><?php
echo 'Error: ' . htmlspecialchars($error['message']) . "\n\n\n" .
'Type: ' . $error['type'] . "\n" .
'File: ' . htmlspecialchars($error['file']) . "\n" .
'Line: ' . $error['line'] . "\n";
?></pre>
<?php
}
}
/**
* this function is called before anything else. do not use duplicator functions because nothing is included at this level.
*
* @return boolean
*/
public static function phpVersionCheck(): bool
{
if (version_compare(PHP_VERSION, self::MINIMUM_PHP_VERSION, '>=')) {
return true;
}
$match = null;
$phpVersion = preg_match("#^\d+(\.\d+)*#", PHP_VERSION, $match) ? $match[0] : PHP_VERSION;
// no html
echo 'This server is running PHP: ' . $phpVersion . '. A minimum of PHP ' . self::MINIMUM_PHP_VERSION . ' is required to run the installer.'
. ' Contact your hosting provider or server administrator and let them know you would like to upgrade your PHP version.';
die();
}
/**
* Init templates
*
* @return void
*/
protected static function templatesInit()
{
$tpl = \DUPX_Template::getInstance();
$tpl->addTemplate(\DUPX_Template::TEMPLATE_BASE, DUPX_INIT . '/templates/base', \DUPX_Template::TEMPLATE_ADVANCED);
$tpl->addTemplate(\DUPX_Template::TEMPLATE_IMPORT_ADVANCED, DUPX_INIT . '/templates/import-advanced', \DUPX_Template::TEMPLATE_ADVANCED);
$tpl->addTemplate(\DUPX_Template::TEMPLATE_IMPORT_BASE, DUPX_INIT . '/templates/import-base', \DUPX_Template::TEMPLATE_IMPORT_ADVANCED);
$tpl->addTemplate(\DUPX_Template::TEMPLATE_RECOVERY, DUPX_INIT . '/templates/recovery', \DUPX_Template::TEMPLATE_BASE);
$tpl->setTemplate(\DUPX_Template::TEMPLATE_ADVANCED);
}
}

View File

@@ -0,0 +1,353 @@
<?php
/**
* @package Duplicator/Installer
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Core\Deploy\Chunk;
use ArrayIterator;
use Duplicator\Installer\Utils\Log\Log;
use Duplicator\Libs\Chunking\Iterators\GenericSeekableIteratorInterface;
use DUPX_DB_Tables;
use DUPX_S3_Funcs;
use DUPX_UpdateEngine;
/**
* Description of class
*
* @author andrea
*/
class SiteUpdateChunkIterator implements GenericSeekableIteratorInterface
{
const STEP_START = 'start';
const STEP_CLEANUP_EXTREA = 'cleanup_extra';
const STEP_CLEANUP_PACKAGES = 'cleanup_packages';
const STEP_CLEANUP_OPTIONS = 'cleanup_trans';
const STEP_SEARCH_AND_REPLACE_INIT = 'init';
const STEP_SEARCH_AND_REPLACE = 'search_replace';
const STEP_REMOVE_MAINTENACE = 'rem_maintenance';
const STEP_CREATE_ADMIN = 'create_admin';
const STEP_CONF_UPDATE = 'config_update';
const STEP_GEN_UPD = 'gen_update';
const STEP_GEN_CLEAN = 'gen_clean';
const STEP_NOTICE_TEST = 'notice_test';
const STEP_CLEANUP_TMP_FILES = 'cleanup_tmp_files';
const STEP_SET_FILE_PERMS = 'set_files_perms';
const STEP_FINAL_REPORT_NOTICES = 'final_report';
/** @var array{l0: ?string, l1: ?string, l2: ?int} */
protected $position = [
'l0' => self::STEP_SEARCH_AND_REPLACE_INIT,
'l1' => null,
'l2' => null,
];
/** @var bool */
protected $isValid = true;
protected \ArrayIterator $tablesIterator; // @phpstan-ignore missingType.generics
/**
* Class contructor
*/
public function __construct()
{
$tables = DUPX_DB_Tables::getInstance()->getReplaceTablesNames();
$this->tablesIterator = new ArrayIterator($tables);
$this->rewind();
}
/**
* Iterator rewind
*
* @return void
*/
#[\ReturnTypeWillChange]
public function rewind(): void
{
$this->isValid = true;
$this->position = [
'l0' => self::STEP_START,
'l1' => null,
'l2' => null,
];
$this->tablesIterator->rewind();
}
/**
* Iteratornext
*
* @return void
*/
#[\ReturnTypeWillChange]
public function next(): void
{
switch ($this->position['l0']) {
case self::STEP_START:
$this->position['l0'] = self::STEP_CLEANUP_OPTIONS;
break;
case self::STEP_CLEANUP_OPTIONS:
$this->position['l0'] = self::STEP_CLEANUP_EXTREA;
break;
case self::STEP_CLEANUP_EXTREA:
$this->position['l0'] = self::STEP_CLEANUP_PACKAGES;
break;
case self::STEP_CLEANUP_PACKAGES:
$this->position['l0'] = self::STEP_SEARCH_AND_REPLACE_INIT;
break;
case self::STEP_SEARCH_AND_REPLACE_INIT:
if ($this->getNextSearchReplacePosition(true)) {
// if search and replace is valid go to STEP_SEARCH_AND_REPLACE
$this->position['l0'] = self::STEP_SEARCH_AND_REPLACE;
} else {
// if search and replace isn't valid skip STEP_SEARCH_AND_REPLACE and go to STEP_REMOVE_MAINTENACE
$this->position['l0'] = self::STEP_REMOVE_MAINTENACE;
}
break;
case self::STEP_SEARCH_AND_REPLACE:
if (!$this->getNextSearchReplacePosition()) {
$this->position['l0'] = self::STEP_REMOVE_MAINTENACE;
}
break;
case self::STEP_REMOVE_MAINTENACE:
$this->position['l0'] = self::STEP_CONF_UPDATE;
break;
case self::STEP_CONF_UPDATE:
$this->position['l0'] = self::STEP_GEN_UPD;
break;
case self::STEP_GEN_UPD:
$this->position['l0'] = self::STEP_GEN_CLEAN;
break;
case self::STEP_GEN_CLEAN:
$this->position['l0'] = self::STEP_CREATE_ADMIN;
break;
case self::STEP_CREATE_ADMIN:
$this->position['l0'] = self::STEP_NOTICE_TEST;
break;
case self::STEP_NOTICE_TEST:
$this->position['l0'] = self::STEP_CLEANUP_TMP_FILES;
break;
case self::STEP_CLEANUP_TMP_FILES:
$this->position['l0'] = self::STEP_SET_FILE_PERMS;
break;
case self::STEP_SET_FILE_PERMS:
$this->position['l0'] = self::STEP_FINAL_REPORT_NOTICES;
break;
case self::STEP_FINAL_REPORT_NOTICES:
default:
$this->position['l0'] = null;
$this->isValid = false;
}
}
/**
* Go haead in tables position
*
* @param bool $init if true init engine
*
* @return bool
*/
private function getNextSearchReplacePosition(bool $init = false)
{
$valid = true;
$s3func = DUPX_S3_Funcs::getInstance();
$pages = $s3func->cTableParams['pages'] ?? 0;
$this->position['l2'] = (int) $this->position['l2'];
$this->position['l2']++;
if ($this->position['l2'] < $pages) {
/* NEXT PAGE */
Log::info('ITERATOR INCREMENT PAGE: ' . $this->position['l2'] . ' PAGES[' . $pages . ']', 3);
$s3func->cTableParams['page'] = $this->position['l2'];
} else {
if ($init) {
DUPX_UpdateEngine::loadInit();
Log::info('ITERATOR FIRST TABLE: ' . $this->position['l2'] . ' PAGES[' . $pages . ']', 3);
$this->tablesIterator->rewind();
} else {
Log::info('ITERATOR INCREMENT TABLE: ' . $this->position['l2'] . ' PAGES[' . $pages . ']', 3);
if ($s3func->cTableParams['updated']) {
$s3func->report['updt_tables']++;
}
$this->tablesIterator->next();
}
$this->position['l1'] = $this->tablesIterator->key();
$this->position['l2'] = 0;
// search first table with rows and columns
while ($this->tablesIterator->valid()) {
Log::info('ITERATOR CHECK TABLE: ' . $this->tablesIterator->current(), 3);
// init table params if isn't initialized
if (DUPX_UpdateEngine::initTableParams($this->tablesIterator->current())) {
// table with columns and rows found
break;
}
// NEXT TABLE
$this->tablesIterator->next();
}
if ($this->tablesIterator->valid()) {
$this->position['l1'] = $this->tablesIterator->key();
$this->position['l2'] = 0;
} else {
$this->position['l1'] = null;
$this->position['l2'] = null;
$s3func->cTableParams = null;
DUPX_UpdateEngine::loadEnd();
DUPX_UpdateEngine::replaceSiteTable();
DUPX_UpdateEngine::replaceBlogsTable();
DUPX_UpdateEngine::logStats();
DUPX_UpdateEngine::logErrors();
$valid = false;
}
}
return $valid;
}
/**
* Set position
*
* @param mixed[] $position position
*
* @return bool true on success, false on fail
*/
public function gSeek($position): bool
{
$this->position = $position;
switch ($this->position['l0']) {
case self::STEP_SEARCH_AND_REPLACE:
$this->tablesIterator->seek($this->position['l1']);
break;
default:
}
return true;
}
/**
* Get current position
*
* @return array{l0: string, l1: ?string, l2: ?int}
*/
public function getPosition()
{
return $this->position;
}
/**
* Return position key
*
* @return string
*/
#[\ReturnTypeWillChange]
public function key()
{
return implode('_', $this->position);
}
/**
* Get current
*
* @return array{l0: string, l1: ?string, l2: ?int}
*/
#[\ReturnTypeWillChange]
public function current()
{
$result = [
'l0' => $this->position['l0'],
'l1' => null,
'l2' => null,
];
$result['l0'] = $this->position['l0'];
switch ($this->position['l0']) {
case self::STEP_SEARCH_AND_REPLACE:
$result['l1'] = $this->tablesIterator->current();
$result['l2'] = $this->position['l2'];
break;
default:
}
return $result;
}
/**
* Stop iteration and free resource
*
* @return void
*/
public function stopIteration(): void
{
switch ($this->position['l0']) {
case self::STEP_SEARCH_AND_REPLACE:
DUPX_UpdateEngine::commitAndSave();
break;
default:
}
}
/**
* Return valid status
*
* @return bool
*/
#[\ReturnTypeWillChange]
public function valid()
{
return $this->isValid;
}
/**
* Return progress percentage
*
* @return float progress percentage
*/
public function getProgressPerc()
{
$result = 0;
$s3Func = DUPX_S3_Funcs::getInstance();
switch ($this->position['l0']) {
case self::STEP_SEARCH_AND_REPLACE_INIT:
$result = 5;
break;
case self::STEP_SEARCH_AND_REPLACE:
$lowLimit = 10;
$higthLimit = 90;
$stepDelta = $higthLimit - $lowLimit;
$tables = DUPX_DB_Tables::getInstance()->getReplaceTablesNames();
$tableDelta = $stepDelta / (count($tables) + 1);
$singePagePerc = $tableDelta / ($s3Func->cTableParams['pages'] + 1);
$result = round($lowLimit + ($tableDelta * (int) $this->position['l1']) + ($singePagePerc * (int) $this->position['l2']), 2);
break;
case self::STEP_REMOVE_MAINTENACE:
$result = 90;
break;
case self::STEP_CREATE_ADMIN:
$result = 92;
break;
case self::STEP_CONF_UPDATE:
$result = 93;
break;
case self::STEP_GEN_UPD:
$result = 94;
break;
case self::STEP_GEN_CLEAN:
$result = 95;
break;
case self::STEP_NOTICE_TEST:
$result = 96;
break;
case self::STEP_CLEANUP_TMP_FILES:
$result = 97;
break;
case self::STEP_SET_FILE_PERMS:
$result = 98;
break;
case self::STEP_FINAL_REPORT_NOTICES:
$result = 100;
break;
default:
}
return $result;
}
}

View File

@@ -0,0 +1,132 @@
<?php
/**
* @package Duplicator/Installer
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Core\Deploy\Chunk;
use Duplicator\Installer\Core\Deploy\Database\DbCleanup;
use Duplicator\Installer\Utils\Log\Log;
use Duplicator\Libs\Chunking\ChunkingManager;
use Duplicator\Libs\Chunking\Persistance\PersistanceAdapterInterface;
use DUPX_S3_Funcs;
use DUPX_UpdateEngine;
/**
* Chunk manager step 3
*/
class SiteUpdateChunkManager extends ChunkingManager
{
/**
* Return iterator
*
* @param mixed $extraData extra data for manager used on extended classes
*
* @return SiteUpdateChunkIterator
*/
protected function getIterator($extraData = null): SiteUpdateChunkIterator
{
return new SiteUpdateChunkIterator();
}
/**
* Return persistance adapter
*
* @param mixed $extraData extra data for manager used on extended classes
*
* @return SiteUpdateChunkPersistanceAdapter
*/
protected function getPersistance($extraData = null): SiteUpdateChunkPersistanceAdapter
{
return new SiteUpdateChunkPersistanceAdapter($GLOBALS["CHUNK_DATA_FILE_PATH"]);
}
/**
* Exec action on current position
*
* @param mixed $key Current iterator key
* @param mixed $current Current iterator position
*
* @return bool return true on success, false on failure
*/
protected function action($key, $current)
{
$s3FuncsManager = DUPX_S3_Funcs::getInstance();
Log::info('CHUNK ACTION: CURRENT [' . implode('][', $current) . ']');
switch ($current['l0']) {
case SiteUpdateChunkIterator::STEP_START:
$s3FuncsManager->initLog();
$s3FuncsManager->initChunkLog($this->maxIteration, $this->timeOut, $this->throttling, $GLOBALS['DATABASE_PAGE_SIZE']);
break;
case SiteUpdateChunkIterator::STEP_CLEANUP_OPTIONS:
DbCleanup::cleanupOptions();
DbCleanup::cleanupActivityLogs();
break;
case SiteUpdateChunkIterator::STEP_CLEANUP_EXTREA:
DbCleanup::cleanupExtra();
break;
case SiteUpdateChunkIterator::STEP_CLEANUP_PACKAGES:
DbCleanup::cleanupPackages();
break;
case SiteUpdateChunkIterator::STEP_SEARCH_AND_REPLACE_INIT:
break;
case SiteUpdateChunkIterator::STEP_SEARCH_AND_REPLACE:
DUPX_UpdateEngine::evaluateTableRows($current['l1'], $current['l2']);
DUPX_UpdateEngine::commitAndSave();
break;
case SiteUpdateChunkIterator::STEP_REMOVE_MAINTENACE:
$s3FuncsManager->removeMaintenanceMode();
break;
case SiteUpdateChunkIterator::STEP_CREATE_ADMIN:
$s3FuncsManager->createNewAdminUser();
break;
case SiteUpdateChunkIterator::STEP_CONF_UPDATE:
$s3FuncsManager->configFilesUpdate();
break;
case SiteUpdateChunkIterator::STEP_GEN_UPD:
$s3FuncsManager->generalUpdate();
break;
case SiteUpdateChunkIterator::STEP_GEN_CLEAN:
$s3FuncsManager->generalCleanup();
$s3FuncsManager->forceLogoutOfAllUsers();
$s3FuncsManager->duplicatorMigrationInfoSet();
break;
case SiteUpdateChunkIterator::STEP_NOTICE_TEST:
$s3FuncsManager->checkForIndexHtml();
$s3FuncsManager->noticeTest();
break;
case SiteUpdateChunkIterator::STEP_CLEANUP_TMP_FILES:
$s3FuncsManager->cleanupFiles();
break;
case SiteUpdateChunkIterator::STEP_SET_FILE_PERMS:
$s3FuncsManager->setFilePermsission();
break;
case SiteUpdateChunkIterator::STEP_FINAL_REPORT_NOTICES:
$s3FuncsManager->finalReportNotices();
break;
default:
}
/**
* At each iteration save the status in case of exit with timeout
*/
return $this->saveData();
}
/**
* stop iteration without save data.
* It is already saved every iteration.
*
* @param bool $saveData not used
*
* @return mixed
*/
public function stop($saveData = false)
{
return parent::stop(false);
}
}

View File

@@ -0,0 +1,67 @@
<?php
/**
* @package Duplicator/Installer
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Core\Deploy\Chunk;
use VendorDuplicator\Amk\JsonSerialize\JsonSerialize;
use Duplicator\Installer\Utils\Log\Log;
use Duplicator\Installer\Utils\Log\LogHandler;
use Duplicator\Libs\Chunking\Persistance\FileJsonPersistanceAdapter;
use Duplicator\Libs\Chunking\Iterators\GenericSeekableIteratorInterface;
use DUPX_S3_Funcs;
class SiteUpdateChunkPersistanceAdapter extends FileJsonPersistanceAdapter
{
/**
* Called after loadPersistanceData, so the data is available
*
* @return void
*/
protected function afterLoadPersistanceData()
{
$position = $this->getPersistanceData();
if ($position != null) {
Log::info("CHUNK LOAD DATA: POSITION " . implode(' / ', $position), 2);
} else {
Log::info("CHUNK LOAD DATA: IS NULL ");
}
}
/**
* Delete stored data if exists
*
* @return bool This function returns true on success, or FALSE on failure.
*/
protected function doDeletePersistanceData(): bool
{
Log::info("CHUNK DELETE STORED DATA FILE:" . Log::v2str($this->path), 2);
return parent::doDeletePersistanceData();
}
/**
* Modify the data before write
*
* @param mixed $position the position to save
* @param GenericSeekableIteratorInterface $it current iterator
*
* @return void
*/
protected function beforeWritePersistanceData($position, GenericSeekableIteratorInterface $it)
{
$s3Funcs = DUPX_S3_Funcs::getInstance();
$s3Funcs->report['chunk'] = 1;
$s3Funcs->report['chunkPos'] = $position;
$s3Funcs->report['pass'] = 0;
$s3Funcs->report['progress_perc'] = $it->getProgressPerc();
$s3Funcs->saveData();
// managed output for timeout shutdown
LogHandler::setShutdownReturn(LogHandler::SHUTDOWN_TIMEOUT, JsonSerialize::serialize($s3Funcs->getJsonReport()));
Log::info("CHUNK SAVE DATA: POSITION " . implode(' / ', $position), 2);
}
}

View File

@@ -0,0 +1,125 @@
<?php
namespace Duplicator\Installer\Core\Deploy;
use Duplicator\Installer\Core\Deploy\Plugins\PluginsManager;
use Duplicator\Installer\Core\Params\PrmMng;
use Duplicator\Installer\Utils\Log\Log;
use DUPX_ArchiveConfig;
use DUPX_DB;
use DUPX_DB_Functions;
use DUPX_NOTICE_ITEM;
use DUPX_NOTICE_MANAGER;
use Exception;
use mysqli;
class CleanUp
{
/**
* Remove users without any permissions
*
* @param string $subSiteId ID of the Sub Site
* @param mysqli $dbh Database Connection
*
* @return void
*/
public static function removeUsersWithoutPermissions($subSiteId, $dbh): void
{
Log::info("\n--------------------\n" .
"REMOVING USERS WITHOUT PERMISSIONS");
$paramsManager = PrmMng::getInstance();
$basePrefix = $paramsManager->getValue(PrmMng::PARAM_DB_TABLE_PREFIX);
$usersTableName = DUPX_DB_Functions::getUserTableName();
$userMetaTableName = DUPX_DB_Functions::getUserMetaTableName();
$superAdminUsersIds = Helpers::getSuperAdminsUserIds($dbh);
$superAdminUsersStr = implode(',', $superAdminUsersIds);
$excludeSuperAdminsClause = (!empty($superAdminUsersIds))
? " AND {$usersTableName}.id NOT IN ({$superAdminUsersStr})"
: '';
$usersWithCapabilitiesSql = "SELECT {$userMetaTableName}.user_id FROM {$userMetaTableName}
WHERE {$userMetaTableName}.user_id = {$usersTableName}.id
AND({$userMetaTableName}.meta_key = '{$basePrefix}capabilities'
OR {$userMetaTableName}.meta_key REGEXP '{$basePrefix}[0-9]+_capabilities')";
$sql = "SELECT {$usersTableName}.ID FROM {$usersTableName}
WHERE NOT EXISTS ({$usersWithCapabilitiesSql})" . $excludeSuperAdminsClause;
$results = DUPX_DB::queryToArray($dbh, $sql);
$removeUsers = [];
foreach ($results as [$userId]) {
$removeUsers[] = $userId;
}
$removeUsers = array_unique($removeUsers);
$removeUsersStr = '(' . implode(',', $removeUsers) . ')';
$hasUsersToRemove = count($removeUsers) > 0;
if ($hasUsersToRemove) {
Log::info("REMOVE USER IDS: " . Log::v2str($removeUsers));
DUPX_DB::chunksDelete($dbh, $usersTableName, "id IN " . $removeUsersStr);
DUPX_DB::chunksDelete($dbh, $userMetaTableName, "user_id IN " . $removeUsersStr);
}
}
/**
* Remove all deactivated plugins
*
* @return void
*
* @throws Exception
*/
public static function removeUnusedPlugins(): void
{
Log::info("\n--------------------\n" .
"DELETING INACTIVE PLUGINS");
PluginsManager::getInstance()->uninstallInactivePlugins();
}
/**
* Remove all deactivated plugins
*
* @return void
*/
public static function removeUnusedThemes(): void
{
Log::info("\n--------------------\n" .
"DELETING INACTIVE THEMES");
Helpers::loadWP();
$themes = DUPX_ArchiveConfig::getInstance()->wpInfo->themes;
foreach ($themes as $theme) {
//Log::info('THEME: '.Log::v2str($theme));
if (Helpers::isThemeEnable($theme)) {
Log::info('THEME: ' . Log::v2str($theme->slug) . ' ENABLE');
continue;
}
if (Helpers::haveChildEnable($theme, $themes)) {
Log::info('THEME: ' . Log::v2str($theme->slug) . ' CHILD ENABLE');
continue;
}
if (delete_theme($theme->stylesheet, '')) {
Log::info('THEME: ' . Log::v2str($theme->slug) . ' DELETED');
} else {
$nManager = DUPX_NOTICE_MANAGER::getInstance();
$errorMsg = "**ERROR** The Inactive theme " . $theme->slug . " deletion failed";
Log::info($errorMsg);
$fullPath = PrmMng::getInstance()->getValue(PrmMng::PARAM_PATH_CONTENT_NEW) . '/themes/' . $theme->stylesheet;
$nManager->addFinalReportNotice([
'shortMsg' => $errorMsg,
'level' => DUPX_NOTICE_ITEM::HARD_WARNING,
'longMsg' => 'Please delete the path ' . $fullPath . ' manually',
'sections' => 'general',
]);
}
}
}
}

View File

@@ -0,0 +1,398 @@
<?php
/**
* @package Duplicator/Installer
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Core\Deploy\Database;
use Duplicator\Installer\Core\Params\Models\SiteOwrMap;
use Duplicator\Installer\Core\Params\PrmMng;
use Duplicator\Installer\Utils\Log\Log;
use DUPX_ArchiveConfig;
use DUPX_DB;
use DUPX_DB_Functions;
use DUPX_DBInstall;
use Duplicator\Installer\Core\InstState;
use DUPX_NOTICE_ITEM;
use DUPX_NOTICE_MANAGER;
/**
* Class with db cleanup functions
*/
class DbCleanup
{
/**
* Cleanup extra entities (views, procs, funcs)
*
* @return void
*/
public static function cleanupExtra(): void
{
if (InstState::isRestoreBackup() || InstState::isAddSiteOnMultisite()) {
return;
}
Log::info("CLEANUP EXTRA");
$paramsManager = PrmMng::getInstance();
if (!$paramsManager->getValue(PrmMng::PARAM_DB_VIEW_CREATION)) {
self::dropViews();
Log::info("\t- VIEWS DROPPED");
} else {
Log::info("\t- SKIP DROP VIEWS");
}
if (!$paramsManager->getValue(PrmMng::PARAM_DB_PROC_CREATION)) {
self::dropProcs();
Log::info("\t- PROCS DROPPED");
} else {
Log::info("\t- SKIP DROP PROCS");
}
if (!$paramsManager->getValue(PrmMng::PARAM_DB_FUNC_CREATION)) {
self::dropFuncs();
Log::info("\t- FUNCS DROPPED");
} else {
Log::info("\t- SKIP DROP FUNCS");
}
}
/**
* Cleanup packages
*
* @return void
*/
public static function cleanupPackages(): void
{
if (InstState::isAddSiteOnMultisite()) {
return;
}
if (InstState::isRestoreBackup()) {
Log::info("REMOVE CURRENT PACKAGE IN BACKUP");
self::deletePackageInBackup();
} else {
Log::info("EMPTY PACKAGES TABLE");
self::emptyDuplicatorPackages();
}
}
/**
* Cleanup activity logs
*
* @return void
*/
public static function cleanupActivityLogs(): void
{
if (InstState::isRestoreBackup()) {
return;
}
$dbh = DUPX_DB_Functions::getInstance()->dbConnection();
$activityLogsTable = mysqli_real_escape_string($dbh, DUPX_DB_Functions::getActivityLogsTableName());
if (DUPX_DB::tableExists($dbh, $activityLogsTable)) {
$count = DUPX_DB::chunksDelete($dbh, $activityLogsTable, '1 = 1');
Log::info(sprintf('DATABASE ACTIVITY LOGS DELETED [ROWS:%6d]', $count));
}
}
/**
* Cleanup options tables (remove transientes ..)
*
* @return int return number of items deleted
*/
public static function cleanupOptions()
{
if (InstState::isRestoreBackup()) {
return 0;
}
$dbh = DUPX_DB_Functions::getInstance()->dbConnection();
$archiveConfig = DUPX_ArchiveConfig::getInstance();
$optionsTableList = [];
$deleteOptionConds = [];
if (InstState::isAddSiteOnMultisite()) {
/** @var SiteOwrMap[] $overwriteMapping */
$overwriteMapping = PrmMng::getInstance()->getValue(PrmMng::PARAM_SUBSITE_OVERWRITE_MAPPING);
foreach ($overwriteMapping as $map) {
$targetInfo = $map->getTargetSiteInfo();
$optionsTableList[] = mysqli_real_escape_string($dbh, DUPX_DB_Functions::getOptionsTableName($targetInfo['blog_prefix']));
}
} else {
$optionsTableList[] = mysqli_real_escape_string($dbh, DUPX_DB_Functions::getOptionsTableName());
$deleteOptionConds[] = '`option_name` = "dupli_opt_plugin_data_stats"';
$deleteOptionConds[] = '`option_name` = "dupli_opt_unique_id"';
}
$deleteOptionConds[] = '`option_name` LIKE "\_transient%"';
$deleteOptionConds[] = '`option_name` LIKE "\_site\_transient%"';
$opts_delete = [];
foreach ($archiveConfig->opts_delete as $value) {
$opts_delete[] = '"' . mysqli_real_escape_string($dbh, $value) . '"';
}
if (count($opts_delete) > 0) {
$deleteOptionConds[] = '`option_name` IN (' . implode(',', $opts_delete) . ')';
}
$count = 0;
foreach ($optionsTableList as $optionsTable) {
$log = "CLEAN OPTIONS [" . $optionsTable . "]";
foreach ($deleteOptionConds as $cond) {
$log .= "\n\t" . $cond;
}
Log::info($log);
$count += DUPX_DB::chunksDelete($dbh, $optionsTable, implode(' OR ', $deleteOptionConds));
Log::info(sprintf('DATABASE OPTIONS DELETED [ROWS:%6d]', $count));
}
return $count;
}
/**
* Delete current package in backup
*
* @return void
*/
protected static function deletePackageInBackup()
{
$dbh = DUPX_DB_Functions::getInstance()->dbConnection();
$packageId = DUPX_ArchiveConfig::getInstance()->packInfo->packageId;
Log::info("CLEANUP CURRENT PACKAGE STATUS ID " . $packageId);
$overwriteData = PrmMng::getInstance()->getValue(PrmMng::PARAM_OVERWRITE_SITE_DATA);
if (!$overwriteData['packagesTableExists']) {
// Clean current package only if is extracted from backup
$packagesTable = mysqli_real_escape_string($dbh, DUPX_DB_Functions::getPackagesTableName());
DUPX_DB::mysqli_query($dbh, 'DELETE FROM `' . $packagesTable . '` WHERE `id` = ' . $packageId);
}
}
/**
* Empty duplicator packages table
*
* @return int return number of packages deleted
*/
protected static function emptyDuplicatorPackages()
{
Log::info("CLEAN PACKAGES");
$dbh = DUPX_DB_Functions::getInstance()->dbConnection();
$packagesTable = mysqli_real_escape_string($dbh, DUPX_DB_Functions::getPackagesTableName());
if (DUPX_DB::tableExists($dbh, $packagesTable)) {
$count = DUPX_DB::chunksDelete($dbh, $packagesTable, '1 = 1');
Log::info(sprintf('DATABASE PACKAGE DELETED [ROWS:%6d]', $count));
return $count;
}
Log::info('DATABASE PACKAGES TABLE MISSING');
return 0;
}
/**
* Drop db procedures
*
* @return void
*/
public static function dropProcs(): void
{
$dbh = DUPX_DB_Functions::getInstance()->dbConnection();
$dbName = PrmMng::getInstance()->getValue(PrmMng::PARAM_DB_NAME);
$sql = "SHOW PROCEDURE STATUS WHERE db='{$dbName}'";
$nManager = DUPX_NOTICE_MANAGER::getInstance();
if (!($result = DUPX_DB::mysqli_query($dbh, $sql))) {
$nManager->addFinalReportNotice([
'shortMsg' => 'PROCEDURE CLEAN ERROR: ' . mysqli_error($dbh),
'level' => DUPX_NOTICE_ITEM::SOFT_WARNING,
'longMsg' => sprintf('Unable to get list of PROCEDURES from database "%s".', $dbName),
'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_HTML,
'sections' => 'database',
]);
Log::info("PROCEDURE CLEAN ERROR: Could not get list of PROCEDURES to drop them.");
return;
}
if ($result->num_rows === 0) {
return;
}
while ($row = mysqli_fetch_row($result)) {
$proc_name = $row[1];
$sql = "DROP PROCEDURE IF EXISTS `" . mysqli_real_escape_string($dbh, $dbName) . "`.`" . mysqli_real_escape_string($dbh, $proc_name) . "`";
if (!DUPX_DB::mysqli_query($dbh, $sql)) {
$err = mysqli_error($dbh);
$nManager->addNextStepNotice([
'shortMsg' => 'PROCEDURE CLEAN ERROR',
'level' => DUPX_NOTICE_ITEM::SOFT_WARNING,
'longMsg' => sprintf('Unable to remove PROCEDURE "%s" from database "%s".<br/>', $proc_name, $dbName),
'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_HTML,
], DUPX_NOTICE_MANAGER::ADD_UNIQUE_APPEND, 'drop-proc-fail-msg');
$nManager->addFinalReportNotice([
'shortMsg' => 'PROCEDURE CLEAN ERROR: ' . $err,
'level' => DUPX_NOTICE_ITEM::SOFT_WARNING,
'longMsg' => sprintf('Unable to remove PROCEDURE "%s" from database "%s".', $proc_name, $dbName),
'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_HTML,
'sections' => 'database',
]);
Log::info("PROCEDURE CLEAN ERROR: '{$err}'\n\t[SQL=" . substr($sql, 0, DUPX_DBInstall::QUERY_ERROR_LOG_LEN) . "...]\n\n");
}
}
$nManager->addNextStepNotice([
'shortMsg' => 'PROCEDURE CLEAN ERROR',
'level' => DUPX_NOTICE_ITEM::SOFT_WARNING,
'longMsg' => sprintf(
'PROCEDURE CLEAN FAILURE. ' .
'Please remove all procedures from this database and try the installation again. ' .
'If no procedures show in the database, then Drop the database and re-create it.<br/>' .
'ERROR MESSAGE: %s <br/><br/>',
mysqli_error($dbh)
),
'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_HTML,
], DUPX_NOTICE_MANAGER::ADD_UNIQUE_PREPEND_IF_EXISTS, 'drop-proc-fail-msg');
}
/**
* Drop db functions
*
* @return void
*/
public static function dropFuncs(): void
{
$dbh = DUPX_DB_Functions::getInstance()->dbConnection();
$dbName = PrmMng::getInstance()->getValue(PrmMng::PARAM_DB_NAME);
$sql = "SHOW FUNCTION STATUS WHERE db='{$dbName}'";
$nManager = DUPX_NOTICE_MANAGER::getInstance();
if (!($result = DUPX_DB::mysqli_query($dbh, $sql))) {
$nManager->addFinalReportNotice([
'shortMsg' => 'FUNCTION CLEAN ERROR: ' . mysqli_error($dbh),
'level' => DUPX_NOTICE_ITEM::SOFT_WARNING,
'longMsg' => sprintf('Unable to get list of FUNCTIONS from database "%s".', $dbName),
'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_HTML,
'sections' => 'database',
]);
Log::info("FUNCTION CLEAN ERROR: Could not get list of FUNCTIONS to drop them.");
return;
}
if ($result->num_rows === 0) {
return;
}
while ($row = mysqli_fetch_row($result)) {
$func_name = $row[1];
$sql = "DROP FUNCTION IF EXISTS `" . mysqli_real_escape_string($dbh, $dbName) . "`.`" . mysqli_real_escape_string($dbh, $func_name) . "`";
if (!DUPX_DB::mysqli_query($dbh, $sql)) {
$err = mysqli_error($dbh);
$nManager->addNextStepNotice([
'shortMsg' => 'FUNCTION CLEAN ERROR',
'level' => DUPX_NOTICE_ITEM::SOFT_WARNING,
'longMsg' => sprintf('Unable to remove FUNCTION "%s" from database "%s".<br/>', $func_name, $dbName),
'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_HTML,
], DUPX_NOTICE_MANAGER::ADD_UNIQUE_APPEND, 'drop-func-fail-msg');
$nManager->addFinalReportNotice([
'shortMsg' => 'FUNCTION CLEAN ERROR: ' . $err,
'level' => DUPX_NOTICE_ITEM::SOFT_WARNING,
'longMsg' => sprintf('Unable to remove FUNCTION "%s" from database "%s".', $func_name, $dbName),
'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_HTML,
'sections' => 'database',
]);
Log::info("FUNCTION CLEAN ERROR: '{$err}'\n\t[SQL=" . substr($sql, 0, DUPX_DBInstall::QUERY_ERROR_LOG_LEN) . "...]\n\n");
}
}
$nManager->addNextStepNotice([
'shortMsg' => 'FUNCTION CLEAN ERROR',
'level' => DUPX_NOTICE_ITEM::SOFT_WARNING,
'longMsg' => sprintf(
'FUNCTION CLEAN FAILURE. ' .
'Please remove all functions from this database and try the installation again. ' .
'If no functions show in the database, then Drop the database and re-create it.<br/>' .
'ERROR MESSAGE: %s <br/><br/>',
mysqli_error($dbh)
),
'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_HTML,
], DUPX_NOTICE_MANAGER::ADD_UNIQUE_PREPEND_IF_EXISTS, 'drop-func-fail-msg');
}
/**
* Drop db views
*
* @return void
*/
public static function dropViews(): void
{
$dbh = DUPX_DB_Functions::getInstance()->dbConnection();
$dbName = PrmMng::getInstance()->getValue(PrmMng::PARAM_DB_NAME);
$sql = "SHOW FULL TABLES WHERE Table_Type = 'VIEW'";
$nManager = DUPX_NOTICE_MANAGER::getInstance();
if (!($result = DUPX_DB::mysqli_query($dbh, $sql))) {
$nManager->addFinalReportNotice([
'shortMsg' => 'VIEW CLEAN ERROR: ' . mysqli_error($dbh),
'level' => DUPX_NOTICE_ITEM::SOFT_WARNING,
'longMsg' => sprintf('Unable to get list of VIEWS from database "%s"', $dbName),
'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_HTML,
'sections' => 'database',
]);
Log::info("VIEW CLEAN ERROR: Could not get list of VIEWS to drop them.");
return;
}
if ($result->num_rows === 0) {
return;
}
while ($row = mysqli_fetch_row($result)) {
$view_name = $row[0];
$sql = "DROP VIEW `" . mysqli_real_escape_string($dbh, $dbName) . "`.`" . mysqli_real_escape_string($dbh, $view_name) . "`";
if (!DUPX_DB::mysqli_query($dbh, $sql)) {
$err = mysqli_error($dbh);
$nManager->addNextStepNotice([
'shortMsg' => 'VIEW CLEAN ERROR',
'level' => DUPX_NOTICE_ITEM::SOFT_WARNING,
'longMsg' => sprintf('Unable to remove VIEW "%s" from database "%s".<br/>', $view_name, $dbName),
'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_HTML,
], DUPX_NOTICE_MANAGER::ADD_UNIQUE_APPEND, 'drop-view-fail-msg');
$nManager->addFinalReportNotice([
'shortMsg' => 'VIEW CLEAN ERROR: ' . $err,
'level' => DUPX_NOTICE_ITEM::SOFT_WARNING,
'longMsg' => sprintf('Unable to remove VIEW "%s" from database "%s"', $view_name, $dbName),
'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_HTML,
'sections' => 'database',
]);
Log::info("VIEW CLEAN ERROR: '{$err}'\n\t[SQL=" . substr($sql, 0, DUPX_DBInstall::QUERY_ERROR_LOG_LEN) . "...]\n\n");
}
}
$nManager->addNextStepNotice([
'shortMsg' => 'VIEW CLEAN ERROR',
'level' => DUPX_NOTICE_ITEM::SOFT_WARNING,
'longMsg' => sprintf(
'VIEW CLEAN FAILURE. ' .
'Please remove all views from this database and try the installation again. ' .
'If no views show in the database, then Drop the database and re-create it.<br/>' .
'ERROR MESSAGE: %s <br/><br/>',
mysqli_error($dbh)
),
'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_HTML,
], DUPX_NOTICE_MANAGER::ADD_UNIQUE_PREPEND_IF_EXISTS, 'drop-view-fail-msg');
}
}

View File

@@ -0,0 +1,161 @@
<?php
/**
* @package Duplicator/Installer
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Core\Deploy\Database;
use Duplicator\Libs\Snap\SnapIO;
use DUPX_Package;
use Iterator;
/**
* @implements Iterator<mixed,mixed>
*/
class DbDumpIterator implements Iterator
{
/** @var int */
private $pos = 0;
/** @var array<int, array{0: string, 1: int}> */
private $sqlDumpFiles = [];
/** @var int */
private $totalSize = 0;
/**
* Initialize iterator
*
* @return void
*/
public function __construct()
{
$dbDumpDirPath = DUPX_Package::getSqlDumpDirPath();
if (($tmpFiles = scandir($dbDumpDirPath)) === false) {
throw new \Exception('Can\'t read sql dump dir.');
}
$tmpFiles = array_values(array_filter($tmpFiles, fn($file) => preg_match('/\.sql$/', $file)));
if (count($tmpFiles) < 1) {
throw new \Exception('Couldn\'t find any SQL dump files.');
}
foreach ($tmpFiles as $i => $file) {
$path = SnapIO::trailingslashit($dbDumpDirPath) . $file;
$size = filesize($path);
$this->sqlDumpFiles[$i] = [
$path,
$size,
];
$this->totalSize += $size;
}
$this->rewind();
}
/**
* Rewind
*
* @return void
*/
#[\ReturnTypeWillChange]
public function rewind(): void
{
$this->pos = 0;
}
/**
* Returns the number of SQL dump files
*
* @return int
*/
public function count(): int
{
return count($this->sqlDumpFiles);
}
/**
* Current
*
* @return string
*/
#[\ReturnTypeWillChange]
public function current()
{
return $this->sqlDumpFiles[$this->pos][0];
}
/**
* Get the current file size
*
* @return int Current file size
*/
public function currentSize()
{
return $this->sqlDumpFiles[$this->pos][1];
}
/**
* Get the current file size
*
* @return int Current file size
*/
public function totalSize()
{
return $this->totalSize;
}
/**
* Return the total offset
*
* @param int $offsetInFile Offset in the current file
*
* @return int
*/
public function totalOffset($offsetInFile)
{
$result = 0;
for ($i = 0; $i < $this->pos; $i++) {
$result += $this->sqlDumpFiles[$i][1];
}
return $result + $offsetInFile;
}
/**
* Key
*
* @return int
*/
#[\ReturnTypeWillChange]
public function key()
{
return $this->pos;
}
/**
* Increment the position
*
* @return void
*/
#[\ReturnTypeWillChange]
public function next(): void
{
++$this->pos;
}
/**
* Return true if is valid
*
* @return bool
*/
#[\ReturnTypeWillChange]
public function valid()
{
return isset($this->sqlDumpFiles[$this->pos]);
}
}

View File

@@ -0,0 +1,522 @@
<?php
/**
* @package Duplicator/Installer
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Core\Deploy\Database;
use Duplicator\Installer\Core\Deploy\Multisite;
use Duplicator\Installer\Core\Params\Models\SiteOwrMap;
use Duplicator\Installer\Core\Params\PrmMng;
use Duplicator\Installer\Utils\Log\Log;
use Duplicator\Libs\Snap\SnapIO;
use DUPX_ArchiveConfig;
use DUPX_DB_Tables;
use Duplicator\Installer\Core\InstState;
use Duplicator\Installer\Utils\ReplaceEngine\ReplaceItem;
use Duplicator\Installer\Utils\ReplaceEngine\ReplaceMng;
use DUPX_U;
use DUPX_UpdateEngine;
use Exception;
class DbReplace
{
/** @var string */
protected $mainUrlOld = '';
/** @var string */
protected $mainUrlNew = '';
/** @var bool */
protected $forceReplaceSiteSubfolders = false;
/**
* Class constructor
*/
public function __construct()
{
$prmMng = PrmMng::getInstance();
$this->mainUrlOld = $prmMng->getValue(PrmMng::PARAM_URL_OLD);
$this->mainUrlNew = $prmMng->getValue(PrmMng::PARAM_URL_NEW);
}
/**
* Set search and replace strings
*
* @return bool
*/
public function setSearchReplace(): bool
{
$this->setCustomReplaceList();
switch (InstState::getInstType()) {
case InstState::TYPE_MSUBDOMAIN:
case InstState::TYPE_MSUBFOLDER:
$this->setMultisiteSearchReplace();
$this->setGlobalSearchAndReplaceList();
break;
case InstState::TYPE_STANDALONE:
$this->setStandalonSearchReplace();
$this->setGlobalSearchAndReplaceList();
break;
case InstState::TYPE_SINGLE:
$this->setGlobalSearchAndReplaceList();
break;
case InstState::TYPE_SINGLE_ON_SUBDOMAIN:
case InstState::TYPE_SINGLE_ON_SUBFOLDER:
case InstState::TYPE_SUBSITE_ON_SUBDOMAIN:
case InstState::TYPE_SUBSITE_ON_SUBFOLDER:
$this->setAddOnMultisiteSearchReplace();
$this->setGlobalSearchAndReplaceList();
break;
case InstState::TYPE_RBACKUP_MSUBDOMAIN:
case InstState::TYPE_RBACKUP_MSUBFOLDER:
case InstState::TYPE_RBACKUP_SINGLE:
case InstState::TYPE_RECOVERY_MSUBDOMAIN:
case InstState::TYPE_RECOVERY_MSUBFOLDER:
case InstState::TYPE_RECOVERY_SINGLE:
throw new Exception('Replace engine isn\'t available for restore backup mode');
case InstState::TYPE_NOT_SET:
default:
throw new Exception('Invalid installer mode');
}
return true;
}
/**
* Set global search replace
*
* @return void
*/
private function setGlobalSearchAndReplaceList(): void
{
$srManager = ReplaceMng::getInstance();
$paramsManager = PrmMng::getInstance();
// DIRS PATHS
$this->addReplaceEnginePaths();
Log::info('GLOBAL SEARCH REPLACE ', Log::LV_DETAILED);
if (
!InstState::isInstallerCreatedInThisLocation() &&
!InstState::isAddSiteOnMultisite()
) {
$uploadUrlOld = $paramsManager->getValue(PrmMng::PARAM_URL_UPLOADS_OLD);
$uploadUrlNew = $paramsManager->getValue(PrmMng::PARAM_URL_UPLOADS_NEW);
if (self::checkRelativeAndAbsoluteDiff($this->mainUrlOld, $this->mainUrlNew, $uploadUrlOld, $uploadUrlNew)) {
$srManager->addItem($uploadUrlOld, $uploadUrlNew, ReplaceItem::TYPE_URL_NORMALIZE_DOMAIN, DUPX_UpdateEngine::SR_PRORITY_GENERIC_SUBST_P1);
}
$siteUrlOld = $paramsManager->getValue(PrmMng::PARAM_SITE_URL_OLD);
$siteUrlNew = $paramsManager->getValue(PrmMng::PARAM_SITE_URL);
if (self::checkRelativeAndAbsoluteDiff($this->mainUrlOld, $this->mainUrlNew, $siteUrlOld, $siteUrlNew)) {
$srManager->addItem($siteUrlOld, $siteUrlNew, ReplaceItem::TYPE_URL_NORMALIZE_DOMAIN, DUPX_UpdateEngine::SR_PRORITY_GENERIC_SUBST_P3);
}
$srManager->addItem($this->mainUrlOld, $this->mainUrlNew, ReplaceItem::TYPE_URL_NORMALIZE_DOMAIN, DUPX_UpdateEngine::SR_PRORITY_GENERIC_SUBST_P3);
}
$pluginsUrlOld = $paramsManager->getValue(PrmMng::PARAM_URL_PLUGINS_OLD);
$pluginsUrlNew = $paramsManager->getValue(PrmMng::PARAM_URL_PLUGINS_NEW);
if (
$this->forceReplaceSiteSubfolders ||
self::checkRelativeAndAbsoluteDiff($this->mainUrlOld, $this->mainUrlNew, $pluginsUrlOld, $pluginsUrlNew)
) {
$srManager->addItem($pluginsUrlOld, $pluginsUrlNew, ReplaceItem::TYPE_URL_NORMALIZE_DOMAIN, DUPX_UpdateEngine::SR_PRORITY_GENERIC_SUBST_P1);
}
$mupluginsUrlOld = $paramsManager->getValue(PrmMng::PARAM_URL_MUPLUGINS_OLD);
$mupluginsUrlNew = $paramsManager->getValue(PrmMng::PARAM_URL_MUPLUGINS_NEW);
if (
$this->forceReplaceSiteSubfolders ||
self::checkRelativeAndAbsoluteDiff($this->mainUrlOld, $this->mainUrlNew, $mupluginsUrlOld, $mupluginsUrlNew)
) {
$srManager->addItem($mupluginsUrlOld, $mupluginsUrlNew, ReplaceItem::TYPE_URL_NORMALIZE_DOMAIN, DUPX_UpdateEngine::SR_PRORITY_GENERIC_SUBST_P1);
}
$contentUrlOld = $paramsManager->getValue(PrmMng::PARAM_URL_CONTENT_OLD);
$contentUrlNew = $paramsManager->getValue(PrmMng::PARAM_URL_CONTENT_NEW);
if (
$this->forceReplaceSiteSubfolders ||
self::checkRelativeAndAbsoluteDiff($this->mainUrlOld, $this->mainUrlNew, $contentUrlOld, $contentUrlNew)
) {
$srManager->addItem($contentUrlOld, $contentUrlNew, ReplaceItem::TYPE_URL_NORMALIZE_DOMAIN, DUPX_UpdateEngine::SR_PRORITY_GENERIC_SUBST_P2);
}
// Replace email address (xyz@oldomain.com to xyz@newdomain.com).
if ($paramsManager->getValue(PrmMng::PARAM_EMAIL_REPLACE)) {
$at_old_domain = '@' . DUPX_U::getDomain($this->mainUrlOld);
$at_new_domain = '@' . DUPX_U::getDomain($this->mainUrlNew);
$srManager->addItem($at_old_domain, $at_new_domain, ReplaceItem::TYPE_STRING, DUPX_UpdateEngine::SR_PRORITY_LOW);
}
}
/**
* add paths to replace on sear/replace engine
*
* @return void
*/
private function addReplaceEnginePaths(): void
{
$srManager = ReplaceMng::getInstance();
$paramsManager = PrmMng::getInstance();
if ($paramsManager->getValue(PrmMng::PARAM_SKIP_PATH_REPLACE)) {
return;
}
$archiveConfig = DUPX_ArchiveConfig::getInstance();
$originalPaths = $archiveConfig->getRealValue('originalPaths');
$mainPathOld = $paramsManager->getValue(PrmMng::PARAM_PATH_OLD);
$mainPathNew = $paramsManager->getValue(PrmMng::PARAM_PATH_NEW);
if (
!InstState::isInstallerCreatedInThisLocation() ||
!InstState::isAddSiteOnMultisite()
) {
$uploadPathOld = $paramsManager->getValue(PrmMng::PARAM_PATH_UPLOADS_OLD);
$uploadPathNew = $paramsManager->getValue(PrmMng::PARAM_PATH_UPLOADS_NEW);
if (self::checkRelativeAndAbsoluteDiff($mainPathOld, $mainPathNew, $uploadPathOld, $uploadPathNew)) {
$srManager->addItem($uploadPathOld, $uploadPathNew, ReplaceItem::TYPE_PATH, DUPX_UpdateEngine::SR_PRORITY_GENERIC_SUBST_P1);
}
if (
$originalPaths->uploads != $uploadPathOld &&
self::checkRelativeAndAbsoluteDiff($originalPaths->home, $mainPathNew, $originalPaths->uploads, $uploadPathNew)
) {
$srManager->addItem($originalPaths->uploads, $uploadPathNew, ReplaceItem::TYPE_PATH, DUPX_UpdateEngine::SR_PRORITY_GENERIC_SUBST_P1);
}
$corePathOld = $paramsManager->getValue(PrmMng::PARAM_PATH_WP_CORE_OLD);
$corePathNew = $paramsManager->getValue(PrmMng::PARAM_PATH_WP_CORE_NEW);
if (self::checkRelativeAndAbsoluteDiff($mainPathOld, $mainPathNew, $corePathOld, $corePathNew)) {
$srManager->addItem($corePathOld, $corePathNew, ReplaceItem::TYPE_PATH, DUPX_UpdateEngine::SR_PRORITY_GENERIC_SUBST_P3);
}
if (
$originalPaths->abs != $corePathOld &&
self::checkRelativeAndAbsoluteDiff($originalPaths->home, $mainPathNew, $originalPaths->abs, $corePathNew)
) {
$srManager->addItem($originalPaths->abs, $corePathNew, ReplaceItem::TYPE_PATH, DUPX_UpdateEngine::SR_PRORITY_GENERIC_SUBST_P3);
}
$srManager->addItem($mainPathOld, $mainPathNew, ReplaceItem::TYPE_PATH, DUPX_UpdateEngine::SR_PRORITY_GENERIC_SUBST_P3);
if ($originalPaths->home != $mainPathOld) {
$srManager->addItem($originalPaths->home, $mainPathNew, ReplaceItem::TYPE_PATH, DUPX_UpdateEngine::SR_PRORITY_GENERIC_SUBST_P3);
}
}
$pluginsPathOld = $paramsManager->getValue(PrmMng::PARAM_PATH_PLUGINS_OLD);
$pluginsPathNew = $paramsManager->getValue(PrmMng::PARAM_PATH_PLUGINS_NEW);
if (self::checkRelativeAndAbsoluteDiff($mainPathOld, $mainPathNew, $pluginsPathOld, $pluginsPathNew)) {
$srManager->addItem($pluginsPathOld, $pluginsPathNew, ReplaceItem::TYPE_PATH, DUPX_UpdateEngine::SR_PRORITY_GENERIC_SUBST_P1);
}
if (
$originalPaths->plugins != $pluginsPathOld &&
self::checkRelativeAndAbsoluteDiff($originalPaths->home, $mainPathNew, $originalPaths->plugins, $pluginsPathNew)
) {
$srManager->addItem($originalPaths->plugins, $pluginsPathNew, ReplaceItem::TYPE_PATH, DUPX_UpdateEngine::SR_PRORITY_GENERIC_SUBST_P1);
}
$mupluginsPathOld = $paramsManager->getValue(PrmMng::PARAM_PATH_MUPLUGINS_OLD);
$mupluginsPathNew = $paramsManager->getValue(PrmMng::PARAM_PATH_MUPLUGINS_NEW);
if (self::checkRelativeAndAbsoluteDiff($mainPathOld, $mainPathNew, $mupluginsPathOld, $mupluginsPathNew)) {
$srManager->addItem($mupluginsPathOld, $mupluginsPathNew, ReplaceItem::TYPE_PATH, DUPX_UpdateEngine::SR_PRORITY_GENERIC_SUBST_P1);
}
if (
$originalPaths->muplugins != $mupluginsPathOld &&
self::checkRelativeAndAbsoluteDiff($originalPaths->home, $mainPathNew, $originalPaths->muplugins, $mupluginsPathNew)
) {
$srManager->addItem($originalPaths->muplugins, $mupluginsPathNew, ReplaceItem::TYPE_PATH, DUPX_UpdateEngine::SR_PRORITY_GENERIC_SUBST_P1);
}
$contentPathOld = $paramsManager->getValue(PrmMng::PARAM_PATH_CONTENT_OLD);
$contentPathNew = $paramsManager->getValue(PrmMng::PARAM_PATH_CONTENT_NEW);
if (self::checkRelativeAndAbsoluteDiff($mainPathOld, $mainPathNew, $contentPathOld, $contentPathNew)) {
$srManager->addItem($contentPathOld, $contentPathNew, ReplaceItem::TYPE_PATH, DUPX_UpdateEngine::SR_PRORITY_GENERIC_SUBST_P2);
}
if (
$originalPaths->wpcontent != $contentPathOld &&
self::checkRelativeAndAbsoluteDiff($originalPaths->home, $mainPathNew, $originalPaths->wpcontent, $contentPathNew)
) {
$srManager->addItem($originalPaths->wpcontent, $contentPathNew, ReplaceItem::TYPE_PATH, DUPX_UpdateEngine::SR_PRORITY_GENERIC_SUBST_P2);
}
}
/**
* Custom search and replace
*
* @return void
*/
private function setCustomReplaceList(): void
{
$srManager = ReplaceMng::getInstance();
$searchList = PrmMng::getInstance()->getValue(PrmMng::PARAM_CUSTOM_SEARCH);
$replaceList = PrmMng::getInstance()->getValue(PrmMng::PARAM_CUSTOM_REPLACE);
foreach ($searchList as $search_index => $search_for) {
if (strlen($search_for) > 0) {
$replace_with = $replaceList[$search_index];
$srManager->addItem($search_for, $replace_with, ReplaceItem::TYPE_STRING, DUPX_UpdateEngine::SR_PRORITY_CUSTOM);
}
}
}
/**
* Multisite search and replace
*
* @return void
*/
private function setMultisiteSearchReplace(): void
{
$srManager = ReplaceMng::getInstance();
$prmMng = PrmMng::getInstance();
$arcConfig = DUPX_ArchiveConfig::getInstance();
$oldMuUrls = $arcConfig->getOldUrlsArrayIdVal();
$newMuUrls = Multisite::getMappedSubisteURLs();
$mainSite = $arcConfig->getMainSiteInfo();
// put the main sub site at the end
$subsitesIds = array_keys($oldMuUrls);
if (($delKey = array_search($mainSite->id, $subsitesIds)) !== false) {
unset($subsitesIds[$delKey]);
}
$subsitesIds[] = $mainSite->id;
Log::info("MAIN URL :" . Log::v2str($arcConfig->getUrlFromSubsiteObj($mainSite)), Log::LV_DETAILED);
Log::info('-- SUBSITES --' . "\n" . print_r($arcConfig->subsites, true), Log::LV_DEBUG);
foreach ($subsitesIds as $currentSubid) {
if (($subSiteObj = $arcConfig->getSubsiteObjById($currentSubid)) === false) {
Log::info('INVALID SUBSITE ID: ' . $currentSubid);
throw new Exception('Invalid subsite id');
}
Log::info('SUBSITE ID:' . $currentSubid . 'OLD URL: ' . $oldMuUrls[$currentSubid] . ' NEW URL ' . $newMuUrls[$currentSubid], Log::LV_DEBUG);
$isMainSite = $currentSubid == $mainSite->id;
$search = $oldMuUrls[$currentSubid];
$replace = $newMuUrls[$currentSubid];
// get table for search and replace scope for subsites
if ($prmMng->getValue(PrmMng::PARAM_MULTISITE_CROSS_SEARCH) == false && !$isMainSite) {
$tables = DUPX_DB_Tables::getInstance()->getSubsiteTablesNewNames($currentSubid);
} else {
// global scope
$tables = true;
}
$priority = ($isMainSite) ? DUPX_UpdateEngine::SR_PRORITY_GENERIC_SUBST_P4 : DUPX_UpdateEngine::SR_PRORITY_NETWORK_SUBSITE;
$srManager->addItem($search, $replace, ReplaceItem::TYPE_URL_NORMALIZE_DOMAIN, $priority, $tables);
// Replace email address (xyz@oldomain.com to xyz@newdomain.com).
if ($prmMng->getValue(PrmMng::PARAM_EMAIL_REPLACE)) {
$at_old_domain = '@' . DUPX_U::getDomain($search);
$at_new_domain = '@' . DUPX_U::getDomain($replace);
$srManager->addItem($at_old_domain, $at_new_domain, ReplaceItem::TYPE_STRING, DUPX_UpdateEngine::SR_PRORITY_LOW, $tables);
}
// for domain host and path priority is on main site
$sUrlInfo = parse_url($search);
$sHost = $sUrlInfo['host'] ?? '';
$sPath = $sUrlInfo['path'] ?? '';
$rUrlInfo = parse_url($replace);
$rHost = $rUrlInfo['host'] ?? '';
$rPath = $rUrlInfo['path'] ?? '';
// add path and host scope for custom columns in database
$srManager->addItem($sHost, $rHost, ReplaceItem::TYPE_URL, $priority, 'domain_host');
$srManager->addItem($sPath, $rPath, ReplaceItem::TYPE_STRING, $priority, 'domain_path');
}
}
/**
* Set standalone search replace
*
* @return void
*/
private function setStandalonSearchReplace(): void
{
$srManager = ReplaceMng::getInstance();
$prmMng = PrmMng::getInstance();
$arcConfig = DUPX_ArchiveConfig::getInstance();
$subsiteId = $prmMng->getValue(PrmMng::PARAM_SUBSITE_ID);
$originalPaths = $arcConfig->getRealValue('originalPaths');
$contentPathOld = $prmMng->getValue(PrmMng::PARAM_PATH_CONTENT_OLD);
$uploadPathOld = $prmMng->getValue(PrmMng::PARAM_PATH_UPLOADS_OLD);
if (($subsiteObj = $arcConfig->getSubsiteObjById($subsiteId)) == false) {
throw new Exception('Subite id ' . $subsiteId . ' not valid');
}
if ($subsiteId == 1) {
return;
}
$oldSubsiteUrl = $arcConfig->getUrlFromSubsiteObj($subsiteObj);
$newUrl = $prmMng->getValue(PrmMng::PARAM_URL_NEW);
// Need to swap the subsite prefix for the main table prefix
$uploadsDirSubOld = $uploadPathOld . '/sites/' . $subsiteId;
$uploadsNew = $prmMng->getValue(PrmMng::PARAM_PATH_UPLOADS_NEW);
if (!$prmMng->getValue(PrmMng::PARAM_SKIP_PATH_REPLACE)) {
$srManager->addItem($uploadsDirSubOld, $uploadsNew, ReplaceItem::TYPE_PATH, DUPX_UpdateEngine::SR_PRORITY_NETWORK_SUBSITE_HIGH);
if ($originalPaths->uploads != $uploadPathOld) {
$uploadsDirSubOld = $originalPaths->uploads . '/sites/' . $subsiteId;
$srManager->addItem($uploadsDirSubOld, $uploadsNew, ReplaceItem::TYPE_PATH, DUPX_UpdateEngine::SR_PRORITY_NETWORK_SUBSITE_HIGH);
}
}
$uploadsUrlNew = $prmMng->getValue(PrmMng::PARAM_URL_UPLOADS_NEW);
$uploadsUrlSubOld = $arcConfig->getUploadsUrlFromSubsiteObj($subsiteObj) . '/sites/' . $subsiteId;
$srManager->addItem($uploadsUrlSubOld, $uploadsUrlNew, ReplaceItem::TYPE_URL_NORMALIZE_DOMAIN, DUPX_UpdateEngine::SR_PRORITY_NETWORK_SUBSITE_HIGH);
$uploadsUrlSubOld = $prmMng->getValue(PrmMng::PARAM_URL_UPLOADS_OLD) . '/sites/' . $subsiteId;
$srManager->addItem($uploadsUrlSubOld, $uploadsUrlNew, ReplaceItem::TYPE_URL_NORMALIZE_DOMAIN, DUPX_UpdateEngine::SR_PRORITY_NETWORK_SUBSITE_HIGH);
//Replace WP 3.4.5 subsite uploads path in DB
if ($arcConfig->mu_generation === 1) {
$blogsDirOld = $contentPathOld . '/blogs.dir/' . $subsiteId . '/files';
if (!$prmMng->getValue(PrmMng::PARAM_SKIP_PATH_REPLACE)) {
$srManager->addItem($blogsDirOld, $uploadsNew, ReplaceItem::TYPE_PATH, DUPX_UpdateEngine::SR_PRORITY_NETWORK_SUBSITE_HIGH);
if ($originalPaths->wpcontent != $contentPathOld) {
$blogsDirOld = $originalPaths->wpcontent . '/blogs.dir/' . $subsiteId . '/files';
$srManager->addItem($blogsDirOld, $uploadsNew, ReplaceItem::TYPE_PATH, DUPX_UpdateEngine::SR_PRORITY_NETWORK_SUBSITE_HIGH);
}
}
$subSiteFilesUrl = $prmMng->getValue(PrmMng::PARAM_URL_NEW) . '/files';
$uploadUrlNew = $prmMng->getValue(PrmMng::PARAM_URL_UPLOADS_NEW);
$srManager->addItem($subSiteFilesUrl, $uploadUrlNew, ReplaceItem::TYPE_URL, DUPX_UpdateEngine::SR_PRORITY_NETWORK_SUBSITE_HIGH);
}
$srManager->addItem($oldSubsiteUrl, $newUrl, ReplaceItem::TYPE_URL_NORMALIZE_DOMAIN, DUPX_UpdateEngine::SR_PRORITY_NETWORK_SUBSITE);
}
/**
* Set addon multiste search replace
*
* @return void
*/
private function setAddOnMultisiteSearchReplace(): void
{
$srManager = ReplaceMng::getInstance();
$prmMng = PrmMng::getInstance();
$arcConfig = DUPX_ArchiveConfig::getInstance();
$tablesMng = DUPX_DB_Tables::getInstance();
/** @var SiteOwrMap[] $overwriteMapping */
$overwriteMapping = PrmMng::getInstance()->getValue(PrmMng::PARAM_SUBSITE_OVERWRITE_MAPPING);
$mainSite = (array) $arcConfig->getMainSiteInfo();
$this->forceReplaceSiteSubfolders = true;
foreach ($overwriteMapping as $map) {
$sourceInfo = $map->getSourceSiteInfo();
$targetInfo = $map->getTargetSiteInfo();
$isMainSite = ($sourceInfo['id'] == $mainSite['id']);
// get table for search and replace scope for subsites/
$scope = $tablesMng->getSubsiteTablesNewNames($sourceInfo['id']);
if (!$prmMng->getValue(PrmMng::PARAM_SKIP_PATH_REPLACE)) {
$srManager->addItem(
$sourceInfo['fullUploadPath'],
$targetInfo['fullUploadPath'],
ReplaceItem::TYPE_PATH,
($sourceInfo['id'] == 1 ? DUPX_UpdateEngine::SR_PRORITY_GENERIC_SUBST_P1 : DUPX_UpdateEngine::SR_PRORITY_NETWORK_SUBSITE_HIGH),
$scope
);
if ($sourceInfo['fullUploadPath'] != $sourceInfo['fullUploadSafePath']) {
$srManager->addItem(
$sourceInfo['fullUploadSafePath'],
$targetInfo['fullUploadPath'],
ReplaceItem::TYPE_PATH,
($sourceInfo['id'] == 1 ? DUPX_UpdateEngine::SR_PRORITY_GENERIC_SUBST_P1 : DUPX_UpdateEngine::SR_PRORITY_NETWORK_SUBSITE_HIGH),
$scope
);
}
}
$srManager->addItem(
$sourceInfo['fullUploadUrl'],
$targetInfo['fullUploadUrl'],
ReplaceItem::TYPE_URL_NORMALIZE_DOMAIN,
($isMainSite ? DUPX_UpdateEngine::SR_PRORITY_GENERIC_SUBST_P1 : DUPX_UpdateEngine::SR_PRORITY_NETWORK_SUBSITE),
$scope
);
if (!($isMainSite && InstState::isInstallerCreatedInThisLocation())) {
$srManager->addItem(
$sourceInfo['fullHomeUrl'],
$targetInfo['fullHomeUrl'],
ReplaceItem::TYPE_URL_NORMALIZE_DOMAIN,
($isMainSite ? DUPX_UpdateEngine::SR_PRORITY_GENERIC_SUBST_P3 : DUPX_UpdateEngine::SR_PRORITY_NETWORK_SUBSITE),
$scope
);
}
// Replace email address (xyz@oldomain.com to xyz@newdomain.com).
if ($prmMng->getValue(PrmMng::PARAM_EMAIL_REPLACE)) {
$at_old_domain = '@' . DUPX_U::getDomain($sourceInfo['fullUploadUrl']);
$at_new_domain = '@' . DUPX_U::getDomain($targetInfo['fullUploadUrl']);
$srManager->addItem(
$at_old_domain,
$at_new_domain,
ReplaceItem::TYPE_STRING,
DUPX_UpdateEngine::SR_PRORITY_LOW,
$scope
);
}
}
}
/**
* Check if sub path if different
*
* @param string $mainOld main old path
* @param string $mainNew main new path
* @param string $old old sub path
* @param string $new new sub path
*
* @return bool
*/
private static function checkRelativeAndAbsoluteDiff($mainOld, $mainNew, $old, $new): bool
{
$mainOld = SnapIO::safePath($mainOld);
$mainNew = SnapIO::safePath($mainNew);
$old = SnapIO::safePath($old);
$new = SnapIO::safePath($new);
$log = "CHECK REL AND ABS DIF\n" .
"\tMAIN OLD: " . Log::v2str($mainOld) . "\n" .
"\tMAIN NEW: " . Log::v2str($mainNew) . "\n" .
"\tOLD: " . Log::v2str($old) . "\n" .
"\tNEW: " . Log::v2str($new);
Log::info($log, Log::LV_DEBUG);
$isRelativePathDifferent = substr($old, strlen($mainOld)) !== substr($new, strlen($mainNew));
if ($old === $mainOld) {
// If the main path coincides with the current path it is not possible to distinguish
// the two paths and make different substitutions so I skip the substitution
Log::info("\t*** RESULT: FALSE", Log::LV_DEBUG);
return false;
} elseif (strpos($old, $mainOld) !== 0 || strpos($new, $mainNew) !== 0 || $isRelativePathDifferent) {
Log::info("\t*** RESULT: TRUE", Log::LV_DEBUG);
return true;
} else {
Log::info("\t*** RESULT: FALSE", Log::LV_DEBUG);
return false;
}
}
}

View File

@@ -0,0 +1,663 @@
<?php
/**
* @package Duplicator/Installer
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Core\Deploy\Database;
use Duplicator\Installer\Core\Security;
use Duplicator\Installer\Core\Params\Descriptors\ParamDescUsers;
use Duplicator\Installer\Core\Params\Models\SiteOwrMap;
use Duplicator\Installer\Core\Params\PrmMng;
use Duplicator\Installer\Models\ImportUser;
use Duplicator\Installer\Utils\Log\Log;
use Duplicator\Installer\ViewHelpers\Resources;
use VendorDuplicator\Amk\JsonSerialize\AbstractJsonSerializable;
use Duplicator\Libs\Snap\SnapDB;
use Duplicator\Libs\Snap\SnapUtil;
use DUPX_ArchiveConfig;
use DUPX_DB;
use DUPX_DB_Functions;
use DUPX_DB_Tables;
use Duplicator\Installer\Core\InstState;
use DUPX_NOTICE_ITEM;
use DUPX_NOTICE_MANAGER;
use DUPX_UpdateEngine;
use Error;
use Exception;
/**
* Class DbUserMode
*
* Manages WordPress user table special handling during site migration.
* Provides key functionalities for:
* 1. User Data Management (ID mapping, remapping, meta data migration)
* 2. Security Features (sensitive data protection, user permissions)
* 3. Multisite Support (table prefix changes, user relationships)
* 4. Data Integrity (consistency, auto-increment values, meta key updates)
*
* It's separated from core table handling due to special security and migration requirements.
*/
class DbUserMode extends AbstractJsonSerializable
{
/** @var ImportUser[] */
protected $targetUsersById = [];
/** @var ImportUser[] */
protected $targetUsersByMail = [];
/** @var ImportUser[] */
protected $targetUsersByLogin = [];
/** @var int */
protected $usersAutoIncrement = -1;
/** @var int */
protected $usersMetaAutoIncrement = -1;
/** @var bool[] */
protected $addedUsers = [];
/** @var int[] */
protected $mappingIds = [];
/** @var (bool|int[])[] */
protected $existingsMetaIsd = [];
/** @var int */
protected $userTableNumCols = 0;
/** @var string */
protected $userMode = ParamDescUsers::USER_MODE_OVERWRITE;
protected string $prefixMetaRegexCheck;
/** @var array<string, string> */
protected $prefixMetaMapping = [];
/**
* Class contructor
*/
public function __construct()
{
$prmMng = PrmMng::getInstance();
$this->userMode = ParamDescUsers::getUsersMode();
$this->prefixMetaRegexCheck = '/^' . preg_quote(DUPX_ArchiveConfig::getInstance()->wp_tableprefix, '/') . '(?:(\d+)_)?(.*)$/';
switch (InstState::getInstType()) {
case InstState::TYPE_SINGLE:
$this->addPrefixMetaMapping(
0,
$prmMng->getValue(PrmMng::PARAM_DB_TABLE_PREFIX)
);
break;
case InstState::TYPE_STANDALONE:
$this->addPrefixMetaMapping(
$prmMng->getValue(PrmMng::PARAM_SUBSITE_ID),
$prmMng->getValue(PrmMng::PARAM_DB_TABLE_PREFIX)
);
break;
case InstState::TYPE_SINGLE_ON_SUBDOMAIN:
case InstState::TYPE_SINGLE_ON_SUBFOLDER:
case InstState::TYPE_SUBSITE_ON_SUBDOMAIN:
case InstState::TYPE_SUBSITE_ON_SUBFOLDER:
/** @var SiteOwrMap[] $overwriteMapping */
$overwriteMapping = $prmMng->getValue(PrmMng::PARAM_SUBSITE_OVERWRITE_MAPPING);
foreach ($overwriteMapping as $map) {
if (($targetInfo = $map->getTargetSiteInfo()) == false) {
throw new Exception('Target site info ' . $map->getTargetId() . ' don\'t exists');
}
$this->addPrefixMetaMapping(
$map->getSourceId(),
$targetInfo['blog_prefix']
);
}
break;
case InstState::TYPE_MSUBDOMAIN:
case InstState::TYPE_MSUBFOLDER:
case InstState::TYPE_RBACKUP_SINGLE:
case InstState::TYPE_RBACKUP_MSUBDOMAIN:
case InstState::TYPE_RBACKUP_MSUBFOLDER:
case InstState::TYPE_RECOVERY_SINGLE:
case InstState::TYPE_RECOVERY_MSUBDOMAIN:
case InstState::TYPE_RECOVERY_MSUBFOLDER:
break;
case InstState::TYPE_NOT_SET:
throw new Exception('Cannot change setup with current installation type [' . InstState::getInstType() . ']');
default:
throw new Exception('Unknown mode');
}
}
/**
* Add meta prefix meta mapping
*
* @param int $subsiteId subsite id
* @param string $prefix replace value
*
* @return void
*/
protected function addPrefixMetaMapping($subsiteId, $prefix)
{
Log::info('ADD PREFIX META MAP ID ' . $subsiteId . ' ' . $prefix);
$key = ($subsiteId == 1 ? 0 : $subsiteId);
$this->prefixMetaMapping[$key] = $prefix;
}
/**
* This function renames the user tables of the target site, also updates the user meta keys
*
* @return void
*/
public static function moveTargetUserTablesOnCurrentPrefix(): void
{
$paramsManager = PrmMng::getInstance();
if (ParamDescUsers::getUsersMode() === ParamDescUsers::USER_MODE_OVERWRITE) {
return;
}
Log::info("\nKEEP TARGET SITE USERS TABLES - USER MODE " . ParamDescUsers::getUsersMode());
$dbFunc = DUPX_DB_Functions::getInstance();
$overwriteData = $paramsManager->getValue(PrmMng::PARAM_OVERWRITE_SITE_DATA);
if ($overwriteData['table_prefix'] == $paramsManager->getValue(PrmMng::PARAM_DB_TABLE_PREFIX)) {
Log::info('TABLE NAMES ARE THE SAME, SO SKIP USERS TABLES RENAME');
return;
}
$targetUserTable = DUPX_DB_Functions::getUserTableName($overwriteData['table_prefix']);
$targetUserMetaTable = DUPX_DB_Functions::getUserMetaTableName($overwriteData['table_prefix']);
$currentUserTableName = DUPX_DB_Functions::getUserTableName();
$currentUserMetaTableName = DUPX_DB_Functions::getUserMetaTableName();
$dbFunc->renameTable($targetUserTable, $currentUserTableName, true);
$dbFunc->renameTable($targetUserMetaTable, $currentUserMetaTableName, true);
// Update table prefix on meta key
DUPX_UpdateEngine::updateTablePrefix(
$dbFunc->dbConnection(),
$currentUserMetaTableName,
'meta_key',
$overwriteData['table_prefix'],
$paramsManager->getValue(PrmMng::PARAM_DB_TABLE_PREFIX)
);
Log::info("USER TABLES RENAMED");
}
/**
* This function removes all meta keys of the current prefix in the usermeta table.
* This is needed to replace them with the meta keys that will be imported
*
* @return void
*/
public function removeAllUserMetaKeysOfCurrentPrefix(): void
{
$paramsManager = PrmMng::getInstance();
if (
ParamDescUsers::getUsersMode() !== ParamDescUsers::USER_MODE_IMPORT_USERS ||
!InstState::isAddSiteOnMultisite()
) {
return;
}
$dbh = DUPX_DB_Functions::getInstance()->dbConnection();
$overwriteData = $paramsManager->getValue(PrmMng::PARAM_OVERWRITE_SITE_DATA);
$loggedInUserId = (int) $overwriteData['loggedUser']['ID'];
foreach ($this->prefixMetaMapping as $overwriteId => $prefix) {
$where = 'user_id != ' . $loggedInUserId;
$escPergPrefix = mysqli_real_escape_string($dbh, SnapDB::quoteRegex($prefix));
if ($prefix == $overwriteData['table_prefix']) {
Log::info("\nREMOVE EXISTING USER META KEY WITH PREFIX " . $prefix . ' EXCEPT ' . $prefix . '[0-9]+_');
// SELECT * FROM `prefix_usermeta` WHERE user_id != 2 AND meta_key REGEXP "^prefix_" AND meta_key NOT REGEXP "^prefix_[0-9]+_"
$where .= ' AND meta_key REGEXP "^' . $escPergPrefix . '" AND meta_key NOT REGEXP "^' . $escPergPrefix . '[0-9]+_"';
} else {
Log::info("\nREMOVE EXISTING USER META KEY WITH PREFIX " . $prefix);
// SELECT * FROM `prefix_usermeta` WHERE user_id != 2 AND meta_key REGEXP "^prefix_2_"
$where .= ' AND meta_key REGEXP "^' . $escPergPrefix . '"';
}
DUPX_DB::chunksDelete($dbh, DUPX_DB_Functions::getUserMetaTableName(), $where);
}
}
/**
* Filter props on json encode
*
* @return string[]
*/
public function __sleep()
{
$props = array_keys(get_object_vars($this));
return array_diff($props, ['targetUsersByMail', 'targetUsersByLogin']);
}
/**
* Called after json decode
*
* @return void
*/
public function __wakeup()
{
foreach ($this->targetUsersById as $user) {
$this->targetUsersByMail[$user->getMail()] = $user;
$this->targetUsersByLogin[$user->getLogin()] = $user;
}
}
/**
* Return the list of columns that contain user id to remap in an array( table => numberColumn)
*
* @return int[]
*/
protected static function getTableColIdsToRemap()
{
static $remapTables = null;
if (is_null($remapTables)) {
$remapTables = [];
foreach (DUPX_DB_Tables::getInstance()->getTablesByNameWithoutPrefix('posts') as $table) {
$remapTables[$table] = 1;
}
Log::info('REMAP USERS TABLES/COLUMN ' . Log::v2str($remapTables));
}
return $remapTables;
}
/**
* Load from users table the user list
*
* @return void
*/
public function initTargetSiteUsersData(): void
{
if ($this->userMode !== ParamDescUsers::USER_MODE_IMPORT_USERS) {
return;
}
$dbh = DUPX_DB_Functions::getInstance()->dbConnection();
Log::info('INIT IMPORT TARGET USER TABLE DATA');
$dbName = mysqli_real_escape_string($dbh, PrmMng::getInstance()->getValue(PrmMng::PARAM_DB_NAME));
$userTable = mysqli_real_escape_string($dbh, DUPX_DB_Functions::getUserTableName());
// count num cols of user table, can be different from source to target
$query = 'SELECT count(*) AS num_cols FROM information_schema.columns WHERE table_schema = "' . $dbName . '" AND table_name = "' . $userTable . '"';
if (($queryRes = DUPX_DB::mysqli_query($dbh, $query)) === false) {
$err = mysqli_error($dbh);
throw new Exception('Query error: ' . $err);
}
$row = $queryRes->fetch_array();
$this->userTableNumCols = (int) $row[0];
Log::info('USER TABLE COLUMNS COUNT ' . $this->userTableNumCols, Log::LV_DETAILED);
$query = 'SELECT `ID`,`user_login`,`user_email` FROM `' . $userTable . '`';
if (($queryRes = DUPX_DB::mysqli_query($dbh, $query)) === false) {
$err = mysqli_error($dbh);
throw new Exception('Query error: ' . $err);
}
$this->usersAutoIncrement = -1;
while ($row = $queryRes->fetch_assoc()) {
$rowId = (int) $row['ID'];
$user = new ImportUser($rowId, $row['user_login'], $row['user_email']);
$this->targetUsersById[$user->getId()] = $user;
$this->targetUsersByMail[$user->getMail()] = $user;
$this->targetUsersByLogin[$user->getLogin()] = $user;
if ($rowId > $this->usersAutoIncrement) {
$this->usersAutoIncrement = $rowId;
}
}
$this->usersAutoIncrement++;
$queryRes->free_result();
Log::info('EXISTING USERS COUNT ' . count($this->targetUsersById), Log::LV_DETAILED);
Log::info('USERS TABLE AUTOINCREMENT VALUE ' . $this->usersAutoIncrement, Log::LV_DETAILED);
$this->initTargetSiteUserMetaData();
}
/**
* For each existing meta key, a list of IDs is associated with each user who has that key or true if all users have the key
*
* @return void
*/
protected function initTargetSiteUserMetaData()
{
$dbh = DUPX_DB_Functions::getInstance()->dbConnection();
Log::info('INIT IMPORT TARGET USERMETA TABLE DATA');
$userTable = mysqli_real_escape_string($dbh, DUPX_DB_Functions::getUserTableName());
$userMetaTable = mysqli_real_escape_string($dbh, DUPX_DB_Functions::getUserMetaTableName());
$query = 'SELECT max(umeta_id) AS maxId FROM `' . $userMetaTable . '`';
if (($queryRes = DUPX_DB::mysqli_query($dbh, $query)) === false) {
$err = mysqli_error($dbh);
throw new Exception('Query error: ' . $err);
}
$row = $queryRes->fetch_assoc();
$this->usersMetaAutoIncrement = ((int) $row['maxId']) + 1;
$queryRes->free_result();
Log::info('USERMETA TABLE AUTOINCREMENT VALUE ' . $this->usersAutoIncrement, Log::LV_DETAILED);
$query = 'SELECT COUNT(*) FROM `' . $userTable . '`';
if (($queryRes = DUPX_DB::mysqli_query($dbh, $query)) === false) {
$err = mysqli_error($dbh);
throw new Exception('Query error: ' . $err);
}
$row = $queryRes->fetch_array();
$maxNumIds = $row[0];
$query = 'SELECT `meta_key`, IF(COUNT(`user_id`) >= ' . $maxNumIds . ', "ALL", GROUP_CONCAT(`user_id` ORDER BY `user_id` ASC)) AS IDS ' .
'FROM `' . $userMetaTable . '`' .
'WHERE `user_id` IN (SELECT `ID` FROM `' . $userTable . '`) GROUP BY meta_key';
if (($queryRes = DUPX_DB::mysqli_query($dbh, $query)) === false) {
$err = mysqli_error($dbh);
throw new Exception('Query error: ' . $err);
}
while ($row = $queryRes->fetch_assoc()) {
$this->existingsMetaIsd[$row['meta_key']] = (
($row['IDS'] === 'ALL') ?
true :
array_map('intval', explode(',', $row['IDS']))
);
}
Log::info('NUM META KEYS READ ' . count($this->existingsMetaIsd), Log::LV_DETAILED);
$queryRes->free_result();
}
/**
* Apply inser query user fixes
*
* @param string $query query string
*
* @return string if the string is empty no query must be executed
*/
public function applyUsersFixes(&$query)
{
if ($this->userMode == ParamDescUsers::USER_MODE_OVERWRITE) {
return $query;
}
$matches = [];
if (preg_match('/^\s*(?:\/\*.*\*\/|#.*\n|--.*\n)?\s*INSERT\s+(?:IGNORE\s+)?INTO\s+`?([^\s`]*?)`?\s+VALUES/s', $query, $matches) !== 1) {
return $query;
}
$tableName = SnapDB::parsedQueryValueToString($matches[1]);
if ($this->userMode == ParamDescUsers::USER_MODE_IMPORT_USERS) {
if ($tableName == DUPX_DB_Functions::getUserTableName()) {
return $this->getUserTableQueryFix(SnapDB::getValuesFromQueryInsert($query));
} elseif ($tableName == DUPX_DB_Functions::getUserMetaTableName()) {
return $this->getUserMetaTableQueryFix(SnapDB::getValuesFromQueryInsert($query));
}
}
$tablesColRemap = self::getTableColIdsToRemap();
if (in_array($tableName, array_keys($tablesColRemap))) {
return $this->getTableUserRemapQueryFix(
$tableName,
$tablesColRemap[$tableName],
SnapDB::getValuesFromQueryInsert($query)
);
}
return $query;
}
/**
* Generate import final report
*
* @return void
*/
public function generateImportReport(): void
{
if ($this->userMode !== ParamDescUsers::USER_MODE_IMPORT_USERS) {
return;
}
$numAdded = 0;
$numChanged = 0;
if (($fp = fopen(DUPX_INIT . '/' . self::getCsvReportName(), 'w')) === false) {
Log::info('Can\'t open report file ' . DUPX_INIT . '/' . self::getCsvReportName());
} else {
fputcsv($fp, ImportUser::getArrayReportTitles(), ',', '"', '\\');
}
foreach ($this->targetUsersById as $user) {
if ($user->isAdded()) {
$numAdded++;
} elseif ($user->isChanged()) {
$numChanged++;
} else {
continue;
}
if ($fp != false) {
fputcsv($fp, $user->getArrayReport(), ',', '"', '\\');
}
}
if ($fp != false) {
fclose($fp);
$csvUrl = Resources::getAssetsBaseUrl() . '/' . self::getCsvReportName();
} else {
$csvUrl = false;
}
$longMsg = dupxTplRender(
'parts/reports/import_report',
[
'numAdded' => $numAdded,
'numChanged' => $numChanged,
'csvUrl' => $csvUrl,
],
false
);
$nManager = DUPX_NOTICE_MANAGER::getInstance();
$nManager->addFinalReportNotice(
[
'shortMsg' => 'User import report',
'level' => DUPX_NOTICE_ITEM::NOTICE,
'longMsg' => $longMsg,
'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_HTML,
'sections' => 'general',
]
);
$nManager->saveNotices();
}
/**
* Return csv report file name
*
* @return string
*/
protected static function getCsvReportName()
{
return 'dup-installer-import-report__' . Security::getInstance()->getSecondaryPackageHash() . '.csv';
}
/**
* Apply query fix for user table
*
* @param mixed[] $queryValues two dimensional array where each item is a row containing the list of values
*
* @return string
*/
protected function getUserTableQueryFix($queryValues)
{
$dbh = DUPX_DB_Functions::getInstance()->dbConnection();
$resultValues = [];
$numColsQueryVals = isset($queryValues[0]) ? count($queryValues[0]) : 0;
$colsDeltaDiff = $this->userTableNumCols - $numColsQueryVals;
foreach ($queryValues as $rowVals) {
$rowId = SnapDB::parsedQueryValueToInt($rowVals[0]);
$rowLogin = SnapDB::parsedQueryValueToString($rowVals[1]);
$rowMail = SnapDB::parsedQueryValueToString($rowVals[4]);
if (isset($this->targetUsersByMail[$rowMail])) {
$targetUser = $this->targetUsersByMail[$rowMail];
$targetId = $targetUser->getId();
$targetLogin = $targetUser->getLogin();
if ($rowId != $targetId) {
$targetUser->setOldId($rowId);
$this->mappingIds[$rowId] = $targetId;
}
if ($rowLogin != $targetLogin) {
$targetUser->setOldLogin($rowLogin);
}
} else {
$rowVals[0] = $targetId = $this->usersAutoIncrement;
$this->usersAutoIncrement++;
if ($rowId != $targetId) {
$this->mappingIds[$rowId] = $targetId;
}
$newLogin = $rowLogin;
$postfixIndex = 0;
while (isset($this->targetUsersByLogin[$newLogin])) {
$postfixIndex++;
$newLogin = $rowLogin . $postfixIndex;
}
if ($rowLogin != $newLogin) {
$rowVals[1] = '"' . mysqli_real_escape_string($dbh, $newLogin) . '"';
$niceName = SnapDB::parsedQueryValueToString($rowVals[3]);
$rowVals[3] = '"' . mysqli_real_escape_string($dbh, $niceName) . $postfixIndex . '"';
$displayName = SnapDB::parsedQueryValueToString($rowVals[9]);
if ($rowLogin == $displayName) {
$rowVals[9] = '"' . mysqli_real_escape_string($dbh, $newLogin) . '"';
}
}
$user = new ImportUser($targetId, $newLogin, $rowMail, $rowId, $rowLogin, true);
$this->targetUsersById[$user->getId()] = $user;
$this->targetUsersByMail[$user->getMail()] = $user;
$this->targetUsersByLogin[$user->getLogin()] = $user;
$this->addedUsers[$user->getOldId()] = true;
if ($colsDeltaDiff == 0) {
$resultValues[] = $rowVals;
} elseif ($colsDeltaDiff < 0) {
$resultValues[] = array_slice($rowVals, 0, $this->userTableNumCols);
} else {
for ($i = 0; $i < $colsDeltaDiff; $i++) {
$rowVals[] = '"0"';
}
$resultValues[] = $rowVals;
}
}
}
if (empty($resultValues)) {
return '';
}
return 'INSERT IGNORE INTO `' . mysqli_real_escape_string($dbh, DUPX_DB_Functions::getUserTableName()) . '` ' .
'VALUES ' . SnapDB::getQueryInsertValuesFromArray($resultValues) . ';';
}
/**
* Apply query fix for usermeta table
*
* @param mixed[] $queryValues two dimensional array where each item is a row containing the list of values
*
* @return string
*/
protected function getUserMetaTableQueryFix($queryValues)
{
$dbh = DUPX_DB_Functions::getInstance()->dbConnection();
$resultValues = [];
// reset value
$user = new ImportUser(-1, '', '');
$prefixMatches = null;
foreach ($queryValues as $rowVals) {
try {
$rowUserId = SnapDB::parsedQueryValueToInt($rowVals[1]);
$rowMetakey = SnapDB::parsedQueryValueToString($rowVals[2]);
if ($user->getId() != $rowUserId) {
$userId = $this->mappingIds[$rowUserId] ?? $rowUserId;
if (isset($this->targetUsersById[$userId])) {
$user = $this->targetUsersById[$userId];
} else {
// This happens if there is a meta key that has a user id that does not belong to any user, it is an anomalous thing so it is skipped.
continue;
}
}
if (preg_match($this->prefixMetaRegexCheck, $rowMetakey, $prefixMatches) === 1) {
$currentId = (int)$prefixMatches[1];
if (!isset($this->prefixMetaMapping[$currentId])) {
// if the attribute is not of the selected sub-site then it is not inserted in the import
continue;
}
$rowMetakey = $this->prefixMetaMapping[$currentId] . $prefixMatches[2];
$rowVals[2] = '"' . mysqli_real_escape_string($dbh, $rowMetakey) . '"';
}
if (
$user->isAdded() ||
!isset($this->existingsMetaIsd[$rowMetakey]) ||
(
$this->existingsMetaIsd[$rowMetakey] !== true &&
SnapUtil::binarySearch($this->existingsMetaIsd[$rowMetakey], $user->getId()) == false
)
) {
$rowVals[0] = $this->usersMetaAutoIncrement;
$this->usersMetaAutoIncrement++;
$rowVals[1] = $user->getId();
if ($rowMetakey == 'nickname') {
// update nickname
$rowMetaValue = SnapDB::parsedQueryValueToString($rowVals[3]);
if ($rowMetaValue == $user->getOldLogin()) {
$rowVals[3] = '"' . mysqli_real_escape_string($dbh, $user->getLogin()) . '"';
}
}
$resultValues[] = $rowVals;
}
} catch (Exception | Error $e) {
Log::logException($e, Log::LV_DEFAULT, 'Error on parse user meta row');
}
}
if (empty($resultValues)) {
return '';
}
return 'INSERT IGNORE INTO `' . mysqli_real_escape_string($dbh, DUPX_DB_Functions::getUserMetaTableName()) .
'` VALUES ' . SnapDB::getQueryInsertValuesFromArray($resultValues) . ';';
}
/**
* Apply query fix for table/colum user id
*
* @param string $tableName table name
* @param int $colNum column index, 0 is first
* @param array<int, mixed[]> $queryValues two dimensional array where each item is a row containing the list of values
*
* @return string
*/
protected function getTableUserRemapQueryFix($tableName, $colNum, $queryValues)
{
$dbh = DUPX_DB_Functions::getInstance()->dbConnection();
for ($i = 0; $i < count($queryValues); $i++) {
$rowUserId = SnapDB::parsedQueryValueToInt($queryValues[$i][$colNum]);
if (isset($this->mappingIds[$rowUserId])) {
$queryValues[$i][$colNum] = $this->mappingIds[$rowUserId];
}
}
return 'INSERT IGNORE INTO `' . mysqli_real_escape_string($dbh, $tableName) .
'` VALUES ' . SnapDB::getQueryInsertValuesFromArray($queryValues) . ';';
}
}

View File

@@ -0,0 +1,48 @@
<?php
/**
* @package Duplicator/Installer
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Core\Deploy\Database;
use Duplicator\Installer\Utils\Log\Log;
use DUPX_DB;
use DUPX_DB_Functions;
use mysqli;
/**
* Database utilities
*/
class DbUtils
{
/**
* Update option value or create if don't exists
*
* @param mysqli $dbh database resource
* @param string $name option name
* @param string $value option value
* @param ?string $prefix table prefix, if null is wp main prefix
* @param bool $autoload option autoload
*
* @return bool true on success, false on failure
*/
public static function updateWpOption(mysqli $dbh, $name, $value, $prefix = null, $autoload = true): bool
{
$table = DUPX_DB_Functions::getOptionsTableName($prefix);
Log::info('UPDATE OPTION ' . $name . ' ON TABLE ' . $table);
$escapedTable = mysqli_real_escape_string($dbh, $table);
$name = mysqli_real_escape_string($dbh, $name);
$value = mysqli_real_escape_string($dbh, $value);
$autoload = ($autoload ? 'yes' : 'no');
$query = "INSERT INTO `" . $escapedTable . "` (option_name, option_value, autoload) " .
"VALUES ('" . $name . "','" . $value . "', '" . $autoload . "') " .
"ON DUPLICATE KEY UPDATE " .
"option_value = '" . $value . "', " .
"autoload = '" . $autoload . "'";
return (DUPX_DB::mysqli_query($dbh, $query) !== false);
}
}

View File

@@ -0,0 +1,298 @@
<?php
/**
* @package Duplicator/Installer
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Core\Deploy\Database;
use Duplicator\Installer\Core\Params\PrmMng;
use Duplicator\Installer\Utils\Log\Log;
use VendorDuplicator\Amk\JsonSerialize\AbstractJsonSerializable;
use DUPX_ArchiveConfig;
use DUPX_DB_Functions;
use DUPX_DB_Tables;
use DUPX_DBInstall;
use Duplicator\Installer\Core\InstState;
class QueryFixes extends AbstractJsonSerializable
{
const TEMP_POSTFIX = 't_';
const USER_DEFINER_REPLACE_PATTERN = "/^(\s*(?:\/\*!\d+\s)?\s*(?:CREATE.+)?DEFINER\s*=)([^\*\s]+)(.*)$/m";
const USER_DEFINER_REMOVE_PATTERN = "/^(\s*(?:\/\*!\d+\s)?\s*(?:CREATE.+)?)(DEFINER\s*=\s*\S+)(.*)$/m";
const USER_DEFINER_REMOVE_REPLACE = '$1 $3';
const SQL_SECURITY_INVOKER_PATTERN = "/^(\s*CREATE.+(?:PROCEDURE|FUNCTION)[\s\S]*)(BEGIN)([\s\S]*)$/";
const SQL_SECURITY_INVOKER_REPLACE = "$1SQL SECURITY INVOKER\n$2$3";
/** @var array{search: mixed[], replace: mixed[]} */
protected $globalRules = [
'search' => [],
'replace' => [],
];
/** @var mixed[] */
protected $tablesPrefixRules = [];
/** @var string */
protected $generatorLog = '';
/**
* Class constructor
*/
public function __construct()
{
$this->rulesProcAndViews();
$this->rulesMySQLEngine();
$this->legacyCharsetAndCollation();
$this->rulesTableNames();
}
/**
* Filter props on json encode
*
* @return string[]
*/
public function __sleep()
{
$props = array_keys(get_object_vars($this));
return array_diff($props, ['generatorLog']);
}
/**
* Write rules in log
*
* @return void
*/
public function logRules(): void
{
if (strlen($this->generatorLog) == 0) {
Log::info('NO GENERAL QUERY FIXES');
} else {
Log::info('QUERY FIXES');
Log::info($this->generatorLog);
}
if (count($this->globalRules['search']) > 0) {
Log::info('QUERY FIXES GLOBAL RULES');
Log::incIndent();
foreach ($this->globalRules['search'] as $index => $search) {
Log::info('SEARCH => ' . $search);
Log::info('REPLACE => ' . $this->globalRules['replace'][$index] . "\n");
}
Log::resetIndent();
}
if (count($this->tablesPrefixRules) > 0) {
Log::info('QUERY FIXES TABLES RULES');
Log::incIndent();
foreach ($this->tablesPrefixRules as $indexRulesSet => $ruleSet) {
Log::info('RULESET ' . ($indexRulesSet + 1));
Log::incIndent();
foreach ($ruleSet['search'] as $index => $search) {
Log::info('SEARCH => ' . $search);
Log::info('REPLACE => ' . $ruleSet['replace'][$index] . "\n");
}
Log::decIndent();
}
Log::resetIndent();
}
}
/**
* @param string $query query to fix
*
* @return string The query with appropriate substitutions done
*/
public function applyFixes($query)
{
$query = preg_replace($this->globalRules['search'], $this->globalRules['replace'], $query);
foreach ($this->tablesPrefixRules as $ruleSet) {
$query = preg_replace($ruleSet['search'], $ruleSet['replace'], $query);
}
return $query;
}
/**
* Set search and replace rules
*
* @return void
*/
protected function rulesProcAndViews()
{
if (InstState::isRestoreBackup()) {
return;
}
if (PrmMng::getInstance()->getValue(PrmMng::PARAM_DB_REMOVE_DEFINER)) {
$this->globalRules['search'][] = self::USER_DEFINER_REMOVE_PATTERN;
$this->globalRules['replace'][] = self::USER_DEFINER_REMOVE_REPLACE;
} else {
$this->globalRules['search'][] = self::USER_DEFINER_REPLACE_PATTERN;
$dbHost = PrmMng::getInstance()->getValue(PrmMng::PARAM_DB_HOST);
$dbUser = PrmMng::getInstance()->getValue(PrmMng::PARAM_DB_USER);
$definerHost = (($dbHost == "localhost" || $dbHost == "127.0.0.1") ? $dbHost : '%');
$this->globalRules['replace'][] = '$1' . addcslashes("`" . $dbUser . "`@`" . $definerHost . "`", '\\$') . '$3';
}
$this->globalRules['search'][] = self::SQL_SECURITY_INVOKER_PATTERN;
$this->globalRules['replace'][] = self::SQL_SECURITY_INVOKER_REPLACE;
$this->generatorLog .= "GLOBAL RULES ADDED: PROC AND VIEWS\n";
}
/**
* Check invalid SQL engines
*
* @return void
*/
protected function rulesMySQLEngine()
{
$invalidEngines = array_map(fn($engine): string => preg_quote($engine, '/'), DUPX_ArchiveConfig::getInstance()->invalidEngines());
if (empty($invalidEngines)) {
return;
}
$this->globalRules['search'][] = '/^(\s*(?:\/\*!\d+\s)?\s*CREATE.+ENGINE=)(' . implode('|', $invalidEngines) . ')(.*)$/ms';
$this->globalRules['replace'][] = '$1' . DUPX_DB_Functions::getInstance()->getDefaultEngine() . '$3';
$this->generatorLog .= "GLOBAL RULES ADDED: MYSQL ENGINES\n";
}
/**
* Set legacy charset adn collation rules
*
* Regex managed examples
* - `meta_value` longtext CHARACTER SET utf16 COLLATE utf16_slovak_ci DEFAULT NULL,
* - `comment_author` tinytext COLLATE utf8mb4_unicode_ci NOT NULL,
* - ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci_test;
* - ) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4;
*
* accept ['"`]charset['"`]
*
* @return void
*/
public function legacyCharsetAndCollation(): void
{
$invalidCharsets = DUPX_ArchiveConfig::getInstance()->invalidCharsets();
$invalidCollations = DUPX_ArchiveConfig::getInstance()->invalidCollations();
$defCharsetRegex = addcslashes(DUPX_DB_Functions::getInstance()->getRealCharsetByParam(), '\\$');
$defCollateRegex = addcslashes(DUPX_DB_Functions::getInstance()->getRealCollateByParam(), '\\$');
if (count($invalidCharsets) > 0) {
$invalidChrRegex = '(?:' . implode('|', array_map(fn($val): string => preg_quote($val, '/'), $invalidCharsets)) . ')';
$this->globalRules['search'][] = '/(^.*(?:CHARSET|CHARACTER SET)\s*[\s=]\s*[`\'"]?)(' .
$invalidChrRegex . ')([`\'"]?\s.*COLLATE\s*[\s=]\s*[`\'"]?)([^`\'"\s;,]+)([`\'"]?.*$)/m';
$this->globalRules['replace'][] = '$1' . $defCharsetRegex . '$3' . $defCollateRegex . '$5';
$this->globalRules['search'][] = '/(^.*COLLATE\s*[\s=]\s*[`\'"]?)([^`\'"\s;,]+)([`\'"]?\s.*(?:CHARSET|CHARACTER SET)\s*[\s=]\s*[`\'"]?)(' .
$invalidChrRegex . ')([`\'"]?[\s;,].*$)/m';
$this->globalRules['replace'][] = '$1' . $defCollateRegex . '$3' . $defCharsetRegex . '$5';
$this->globalRules['search'][] = '/(^.*(?:CHARSET|CHARACTER SET)\s*[\s=]\s*[`\'"]?)(' . $invalidChrRegex . ')([`\'"]?[\s;,].*$)/m';
$this->globalRules['replace'][] = '$1' . $defCharsetRegex . '$3';
$this->generatorLog .= "GLOBAL RULES ADDED: INVALID CHARSETS\n";
}
if (count($invalidCollations) > 0) {
$invalidColRegex = '(?:' . implode('|', array_map(fn($val): string => preg_quote($val, '/'), $invalidCollations)) . ')';
$this->globalRules['search'][] = '/(^.*(?:CHARSET|CHARACTER SET)\s*[\s=]\s*[`\'"]?)([^`\'"\s;,]+)([`\'"]?\s.*COLLATE\s*[\s=]\s*[`\'"]?)(' .
$invalidColRegex . ')([`\'"]?[\s;,].*$)/m';
$this->globalRules['replace'][] = '$1' . $defCharsetRegex . '$3' . $defCollateRegex . '$5';
$this->globalRules['search'][] = '/(^.*COLLATE\s*[\s=]\s*[`\'"]?)(' .
$invalidColRegex . ')([`\'"]?\s.*(?:CHARSET|CHARACTER SET)\s*[\s=]\s*[`\'"]?)([^`\'"\s;,]+)([`\'"]?.*$)/m';
$this->globalRules['replace'][] = '$1' . $defCollateRegex . '$3' . $defCharsetRegex . '$5';
$this->globalRules['search'][] = '/(^.*COLLATE\s*[\s=]\s*[`\'"]?)(' . $invalidColRegex . ')([`\'"]?[\s;,].*$)/m';
$this->globalRules['replace'][] = '$1' . $defCollateRegex . '$3';
$this->generatorLog .= "GLOBAL RULES ADDED: INVALID COLLATIONS\n";
}
}
/**
* Set search and replace table prefix rules
*
* @return void
*/
protected function rulesTableNames()
{
$mapping = DUPX_DB_Tables::getInstance()->getRenameTablesMapping();
$oldPrefixes = array_keys($mapping);
$newPrefixes = [];
foreach ($mapping as $oldPrefix => $newMapping) {
$newPrefixes = array_merge($newPrefixes, array_keys($newMapping));
}
$newPrefixes = array_unique($newPrefixes);
// Prevent double transformation with temp prefix
$doublePrefixes = array_intersect($oldPrefixes, $newPrefixes);
if (count($doublePrefixes) > 0) {
$this->generatorLog .= 'DOUBLE PREFIXES ' . Log::v2str($doublePrefixes);
}
foreach ($mapping as $oldPrefix => $newMapping) {
$rulesSet = [
'search' => [],
'replace' => [],
];
$quoteOldPrefix = preg_quote($oldPrefix, '/');
foreach ($newMapping as $newPrefix => $commons) {
$this->generatorLog .= "TABLES RULES ADDED: CHANGE TABLES PREFIX " . $oldPrefix . " TO " . $newPrefix ;
if (in_array($newPrefix, $doublePrefixes)) {
$this->generatorLog .= " [USE TMP PREFIX]\n";
$newPrefix .= self::TEMP_POSTFIX;
} else {
$this->generatorLog .= "\n";
}
$this->generatorLog .= "\tFOR TABLES " . implode(',', $commons) . "\n";
$quoteNewPrefix = addcslashes($newPrefix, '\\$');
$quoteCommons = array_map(
fn($val): string => preg_quote($val, '/'),
$commons
);
for ($i = 0; $i < ceil(count($quoteCommons) / DUPX_DBInstall::TABLES_REGEX_CHUNK_SIZE); $i++) {
$subArray = array_slice($quoteCommons, $i * DUPX_DBInstall::TABLES_REGEX_CHUNK_SIZE, DUPX_DBInstall::TABLES_REGEX_CHUNK_SIZE);
if (count($subArray) == 0) {
break;
}
$rulesSet['search'][] = '/' . $quoteOldPrefix . '(' . implode('|', $subArray) . ')/m';
$rulesSet['replace'][] = $quoteNewPrefix . '$1';
}
$rulesSet['search'][] = '/(CONSTRAINT[\s\t]+[`\'"]?.+)(?-i)' . $quoteOldPrefix . '(?i)(.+[`\'"]?[\s\t]+FOREIGN[\s\t]+KEY)/mi';
$rulesSet['replace'][] = '$1' . $quoteNewPrefix . '$2';
}
if (count($rulesSet['search']) > 0) {
$this->tablesPrefixRules[] = $rulesSet;
}
}
if (count($doublePrefixes)) {
// REMOVE TEMP PREFIXES
$rulesSet = [
'search' => [],
'replace' => [],
];
foreach ($doublePrefixes as $prefix) {
$quoteTempPrefix = preg_quote($prefix . self::TEMP_POSTFIX, '/');
$quotePrefix = addcslashes($prefix, '\\$');
$rulesSet['search'][] = '/' . $quoteTempPrefix . '/m';
$rulesSet['replace'][] = $quotePrefix . '$1';
}
$this->tablesPrefixRules[] = $rulesSet;
}
}
}

View File

@@ -0,0 +1,192 @@
<?php
/**
* Dup archive expander
*
* @package Duplicator
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Core\Deploy\DupArchive;
use DupArchiveStateBase;
use Duplicator\Installer\Core\Security;
use Duplicator\Installer\Utils\Log\Log;
use Duplicator\Libs\DupArchive\DupArchiveEngine;
use Duplicator\Libs\DupArchive\Processors\DupArchiveFileProcessor;
use Duplicator\Libs\Snap\SnapIO;
use Duplicator\Libs\Snap\SnapUtil;
use DUPX_ArchiveConfig;
use DUPX_Package;
use Exception;
use stdClass;
class Daws
{
const DEFAULT_WORKER_TIME = 18;
/** @var ?resource */
protected $lock_handle;
/** @var ?callable */
protected $failureCallback;
protected string $lockFile;
protected string $cancelFile;
/**
* Class contruct
*/
public function __construct()
{
DawsLogger::init();
date_default_timezone_set('UTC'); // Some machines dont have this set so just do it here.
DupArchiveEngine::init(new DawsLogger(), '');
$this->lockFile = DUPX_INIT . '/dup-installer-dawslock__' . DUPX_Package::getPackageHash() . '.bin';
$this->cancelFile = DUPX_INIT . '/dup-installer-dawscancel__' . DUPX_Package::getPackageHash() . '.bin';
}
/**
* Failure callback
*
* @param callable $callback callback
*
* @return void
*/
public function setFailureCallBack($callback): void
{
if (is_callable($callback)) {
$this->failureCallback = $callback;
}
}
/**
* Extract dup archvie
*
* @param array<string, mixed> $params dup archvie params
*
* @return stdClass
*/
public function processRequest($params): stdClass
{
$retVal = new stdClass();
$retVal->pass = false;
$action = $params['action'];
$archiveConfig = DUPX_ArchiveConfig::getInstance();
if (!DupArchiveFileProcessor::setNewFilePathCallback([$archiveConfig, 'destFileFromArchiveName'])) {
Log::info('ERROR: CAN\'T SET THE PATH SE CALLBACK FUNCTION');
} else {
Log::info('PATH SE CALLBACK FUNCTION OK ', Log::LV_DEBUG);
}
$throttleDelayInMs = SnapUtil::getArrayValue($params, 'throttle_delay', false, 0);
$expandState = null;
if ($action == 'start_expand') {
Log::info('DAWN START EXPAND');
DawsExpandState::purgeStatefile();
SnapIO::rm($this->cancelFile);
$archiveFilepath = SnapUtil::getArrayValue($params, 'archive_filepath');
$restoreDirectory = SnapUtil::getArrayValue($params, 'restore_directory');
$workerTime = SnapUtil::getArrayValue($params, 'worker_time', false, self::DEFAULT_WORKER_TIME);
$filteredDirectories = SnapUtil::getArrayValue($params, 'filtered_directories', false, []);
$excludedDirWithoutChilds = SnapUtil::getArrayValue($params, 'excludedDirWithoutChilds', false, []);
$filteredFiles = SnapUtil::getArrayValue($params, 'filtered_files', false, []);
$fileRenames = SnapUtil::getArrayValue($params, 'fileRenames', false, []);
$fileModeOverride = SnapUtil::getArrayValue($params, 'file_mode_override', false, 0644);
$includedFiles = SnapUtil::getArrayValue($params, 'includedFiles', false, []);
$directoryModeOverride = SnapUtil::getArrayValue($params, 'dir_mode_override', false, 0755);
$keepFileTime = SnapUtil::getArrayValue($params, 'keep_file_time', false, false);
$archveHeader = DupArchiveEngine::getArchiveHeader(
$archiveFilepath,
Security::getInstance()->getArchivePassword()
);
$expandState = new DawsExpandState($archveHeader);
$expandState->archivePath = $archiveFilepath;
$expandState->working = true;
$expandState->timeSliceInSecs = $workerTime;
$expandState->basePath = $restoreDirectory;
$expandState->filteredDirectories = $filteredDirectories;
$expandState->excludedDirWithoutChilds = $excludedDirWithoutChilds;
$expandState->includedFiles = $includedFiles;
$expandState->filteredFiles = $filteredFiles;
$expandState->fileRenames = $fileRenames;
$expandState->fileModeOverride = $fileModeOverride;
$expandState->directoryModeOverride = $directoryModeOverride;
$expandState->throttleDelayInUs = 1000 * $throttleDelayInMs;
$expandState->keepFileTime = $keepFileTime;
$expandState->save();
$action = 'expand';
} else {
Log::info('DAWN CONTINUE EXPAND');
$expandState = DawsExpandState::getFromFile();
}
if ($action == 'expand') {
$this->lock_handle = SnapIO::fopen($this->lockFile, 'c+');
SnapIO::flock($this->lock_handle, LOCK_EX);
if ($expandState->working) {
DupArchiveEngine::expandArchive($expandState);
}
if (!$expandState->working) {
$deltaTime = time() - $expandState->startTimestamp;
Log::info("DAWN EXPAND DONE, SECONDS: " . $deltaTime, Log::LV_DETAILED);
if (count($expandState->failures) > 0) {
Log::info('DAWN EXPAND ERRORS DETECTED');
foreach ($expandState->failures as $failure) {
Log::info("{$failure->subject}:{$failure->description}");
if (is_callable($this->failureCallback)) {
call_user_func($this->failureCallback, $failure);
}
}
}
} else {
Log::info("DAWN EXPAND CONTINUE", Log::LV_DETAILED);
}
SnapIO::flock($this->lock_handle, LOCK_UN);
$retVal->pass = true;
$retVal->status = $this->getStatus($expandState);
} elseif ($action == 'get_status') {
$retVal->pass = true;
$retVal->status = $this->getStatus($expandState);
} elseif ($action == 'cancel') {
if (!SnapIO::touch($this->cancelFile)) {
throw new Exception("Couldn't update time on " . $this->cancelFile);
}
$retVal->pass = true;
} else {
throw new Exception('Unknown command.');
}
session_write_close();
return $retVal;
}
/**
* Get dup archive status
*
* @param DawsExpandState $state dup archive state
*
* @return stdClass
*/
private function getStatus(DawsExpandState $state): stdClass
{
$ret_val = new stdClass();
$ret_val->archive_offset = $state->archiveOffset;
$ret_val->archive_size = @filesize($state->archivePath);
$ret_val->failures = $state->failures;
$ret_val->file_index = $state->fileWriteCount;
$ret_val->is_done = !$state->working;
$ret_val->timestamp = time();
return $ret_val;
}
}

View File

@@ -0,0 +1,113 @@
<?php
/**
* Dup archvie expand state
*
* @package Duplicator
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Core\Deploy\DupArchive;
use Duplicator\Libs\DupArchive\Headers\DupArchiveHeader;
use Duplicator\Libs\DupArchive\States\DupArchiveExpandState;
use Duplicator\Libs\DupArchive\Utils\DupArchiveUtil;
use VendorDuplicator\Amk\JsonSerialize\JsonSerialize;
use Duplicator\Libs\Snap\SnapIO;
use DUPX_Package;
use Exception;
class DawsExpandState extends DupArchiveExpandState
{
/**
* Class constructor
*
* @param DupArchiveHeader $archiveHeader archive header
*/
public function __construct(DupArchiveHeader $archiveHeader)
{
parent::__construct($archiveHeader);
}
/**
* Read object from file
*
* @return self
*/
public static function getFromFile()
{
$stateFilepath = self::dataFilePath();
if (!file_exists($stateFilepath)) {
throw new Exception('Data file don\'t exists');
}
if (($json = file_get_contents($stateFilepath)) === false) {
throw new Exception('Can\'t read data file');
}
$result = JsonSerialize::unserialize($json);
if ($result instanceof self) {
return $result;
} else {
$msg = "Invalid data file. It is possible that your disc is full, ";
$msg .= "in which case you need to free up some space, then restart the installer.";
throw new Exception($msg);
}
}
/**
* Remove state file
*
* @return bool
*/
public static function purgeStatefile(): bool
{
$stateFilepath = self::dataFilePath();
if (!file_exists($stateFilepath)) {
return true;
}
SnapIO::rm($stateFilepath, false);
return true;
}
/**
* Reset state
*
* @return void
*/
public function reset(): void
{
parent::reset();
$this->save();
}
/**
* Save state
*
* @return void
*/
public function save(): void
{
$stateFilepath = self::dataFilePath();
$stateHandle = SnapIO::fopen($stateFilepath, 'w');
SnapIO::flock($stateHandle, LOCK_EX);
DupArchiveUtil::tlog("Saving state");
SnapIO::fwrite($stateHandle, JsonSerialize::serialize($this));
SnapIO::flock($stateHandle, LOCK_UN);
SnapIO::fclose($stateHandle);
}
/**
*
* @return string
*/
protected static function dataFilePath()
{
static $path = null;
if (is_null($path)) {
$path = DUPX_INIT . '/dup-dawn-extraction__' . DUPX_Package::getPackageHash() . '.json';
}
return $path;
}
}

View File

@@ -0,0 +1,65 @@
<?php
/**
* Logger for dup archive
*
* @package Duplicator
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Core\Deploy\DupArchive;
use Duplicator\Installer\Utils\Log\Log;
use Duplicator\Libs\DupArchive\DupArchiveLoggerBase;
use Exception;
/**
* Logger for dup archive
*/
class DawsLogger extends DupArchiveLoggerBase
{
/**
* Init logger
*
* @return void
*/
public static function init(): void
{
set_error_handler([self::class, "terminateMissingVariables"], E_ERROR);
}
/**
* Log function
*
* @param string $s string to log
* @param boolean $flush if true flish log
* @param ?callable $callingFunctionOverride call back function
*
* @return void
*/
public function log($s, $flush = false, $callingFunctionOverride = null): void
{
Log::info($s, Log::LV_DEFAULT, $flush);
}
/**
* Throw exception on php error
*
* @param int $errno errno
* @param string $errstr error message
* @param string $errfile file
* @param int $errline line
*
* @return bool
*/
public static function terminateMissingVariables($errno, $errstr, $errfile, $errline): bool
{
Log::info("ERROR $errno, $errstr, {$errfile}:{$errline}");
/**
* INTERCEPT ON processRequest AND RETURN JSON STATUS
*/
throw new Exception("ERROR:{$errfile}:{$errline} | " . $errstr, $errno);
return true; // @phpstan-ignore-line
}
}

View File

@@ -0,0 +1,356 @@
<?php
namespace Duplicator\Installer\Core\Deploy\Files;
use DUPX_Extraction;
use Duplicator\Installer\Core\Params\Models\SiteOwrMap;
use Duplicator\Installer\Core\Security;
use Duplicator\Installer\Core\Params\PrmMng;
use Duplicator\Installer\Utils\Log\Log;
use Duplicator\Libs\Snap\SnapIO;
use Duplicator\Libs\Snap\SnapWP;
use DUPX_ArchiveConfig;
use Duplicator\Installer\Core\InstState;
use Duplicator\Libs\Index\FileIndexManager;
use DUPX_Package;
use DUPX_Server;
use Exception;
class FilterMng
{
/**
* Return filter (folder/files) for extraction
*
* @param string $subFolderArchive sub folder archive
*
* @return Filters
*/
public static function getExtractFilters($subFolderArchive): Filters
{
Log::info("INITIALIZE FILTERS");
$paramsManager = PrmMng::getInstance();
$archiveConfig = DUPX_ArchiveConfig::getInstance();
$result = new Filters();
$relContentPath = '';
$filterFilesChildOfFolders = [];
$acceptFolderOfFilterChilds = [];
$result->addFile($archiveConfig->installer_backup_name);
$result->addDir(ltrim($subFolderArchive . '/' . DUPX_Extraction::DUP_FOLDER_NAME, '/'));
if (self::filterWpCoreFiles()) {
$relAbsPath = $archiveConfig->getRelativePathsInArchive('abs');
$relAbsPath .= (strlen($relAbsPath) > 0 ? '/' : '');
$rootWpCoreItems = SnapWP::getWpCoreFilesListInFolder();
foreach ($rootWpCoreItems['dirs'] as $name) {
$result->addDir($relAbsPath . $name);
}
foreach ($rootWpCoreItems['files'] as $name) {
$result->addFile($relAbsPath . $name);
}
}
if (self::filterAllExceptPlugingThemesMedia()) {
Log::info('FILTER ALL EXCEPT MEDIA');
$filterFilesChildOfFolders[] = $archiveConfig->getRelativePathsInArchive('home');
$filterFilesChildOfFolders[] = $archiveConfig->getRelativePathsInArchive('wpcontent');
$acceptFolderOfFilterChilds[] = $archiveConfig->getRelativePathsInArchive('uploads');
$acceptFolderOfFilterChilds[] = $archiveConfig->getRelativePathsInArchive('wpcontent') . '/blogs.dir';
$acceptFolderOfFilterChilds[] = $archiveConfig->getRelativePathsInArchive('plugins');
$acceptFolderOfFilterChilds[] = $archiveConfig->getRelativePathsInArchive('muplugins');
$acceptFolderOfFilterChilds[] = $archiveConfig->getRelativePathsInArchive('themes');
}
if (InstState::isAddSiteOnMultisite()) {
if (($pos = array_search($archiveConfig->getRelativePathsInArchive('uploads'), $acceptFolderOfFilterChilds)) !== false) {
unset($acceptFolderOfFilterChilds[$pos]);
}
if (($pos = array_search($archiveConfig->getRelativePathsInArchive('wpcontent') . '/blogs.dir', $acceptFolderOfFilterChilds)) !== false) {
unset($acceptFolderOfFilterChilds[$pos]);
}
$filterFilesChildOfFolders[] = $archiveConfig->getRelativePathsInArchive('uploads') . '/sites';
$filterFilesChildOfFolders[] = $archiveConfig->getRelativePathsInArchive('wpcontent') . '/blogs.dir';
/** @var SiteOwrMap[] $overwriteMapping */
$overwriteMapping = $paramsManager->getValue(PrmMng::PARAM_SUBSITE_OVERWRITE_MAPPING);
$mainSiteInSource = false;
foreach ($overwriteMapping as $map) {
if (($subsiteInfo = $map->getSourceSiteInfo()) == false) {
throw new Exception('Source site id ' . $map->getSourceId() . ' not valid');
}
if ($map->getSourceId() == 1) {
$mainSiteInSource = true;
}
$acceptFolderOfFilterChilds[] = $subsiteInfo['uploadPath'];
}
if (!$mainSiteInSource) {
$filterFilesChildOfFolders[] = $archiveConfig->getRelativePathsInArchive('uploads');
}
}
if (
InstState::isInstType(
[InstState::TYPE_STANDALONE]
)
) {
Log::info('FILTER ALL MEDIA EXCEPT STANDALONE');
$subSiteObj = $archiveConfig->getSubsiteObjById($paramsManager->getValue(PrmMng::PARAM_SUBSITE_ID));
if ($subSiteObj->id == 1) {
$result->addDir($archiveConfig->getRelativePathsInArchive('uploads') . '/sites');
$result->addDir($archiveConfig->getRelativePathsInArchive('wpcontent') . '/blogs.dir');
} else {
$filterFilesChildOfFolders[] = $archiveConfig->getRelativePathsInArchive('uploads');
$filterFilesChildOfFolders[] = $archiveConfig->getRelativePathsInArchive('uploads') . '/sites';
$filterFilesChildOfFolders[] = $archiveConfig->getRelativePathsInArchive('wpcontent') . '/blogs.dir';
$acceptFolderOfFilterChilds[] = $subSiteObj->uploadPath;
$result->addDir(DUPX_ArchiveConfig::getInstance()->getRelativePathsInArchive('uploads') . '/sites', true);
$result->addDir(DUPX_ArchiveConfig::getInstance()->getRelativePathsInArchive('wpcontent') . '/blogs.dir', true);
}
}
if (self::filterExistsPlugins()) {
$newPluginDir = $paramsManager->getValue(PrmMng::PARAM_PATH_PLUGINS_NEW);
if (is_dir($newPluginDir)) {
$relPlugPath = $archiveConfig->getRelativePathsInArchive('plugins');
$relPlugPath .= (strlen($relPlugPath) > 0 ? '/' : '');
SnapIO::regexGlobCallback($newPluginDir, function ($item) use ($relPlugPath, &$result): void {
if (is_dir($item)) {
$result->addDir($relPlugPath . pathinfo($item, PATHINFO_BASENAME));
} else {
$result->addFile($relPlugPath . pathinfo($item, PATHINFO_BASENAME));
}
}, []);
}
$newMuPluginDir = $paramsManager->getValue(PrmMng::PARAM_PATH_MUPLUGINS_NEW);
if (is_dir($newMuPluginDir)) {
$relMuPlugPath = $archiveConfig->getRelativePathsInArchive('muplugins');
$relMuPlugPath .= (strlen($relMuPlugPath) > 0 ? '/' : '');
SnapIO::regexGlobCallback($newMuPluginDir, function ($item) use ($relMuPlugPath, &$result): void {
if (is_dir($item)) {
$result->addDir($relMuPlugPath . pathinfo($item, PATHINFO_BASENAME));
} else {
$result->addFile($relMuPlugPath . pathinfo($item, PATHINFO_BASENAME));
}
}, []);
}
$newWpContentDir = $paramsManager->getValue(PrmMng::PARAM_PATH_CONTENT_NEW) . '/';
if (is_dir($newWpContentDir)) {
$relContentPath = $archiveConfig->getRelativePathsInArchive('wpcontent');
$relContentPath .= (strlen($relContentPath) > 0 ? '/' : '');
foreach (SnapWP::getDropinsPluginsNames() as $dropinsPlugin) {
if (file_exists($newWpContentDir . $dropinsPlugin)) {
$result->addFile($relContentPath . $dropinsPlugin);
}
}
}
}
if (self::filterExistsThemes()) {
$newThemesDir = $paramsManager->getValue(PrmMng::PARAM_PATH_CONTENT_NEW) . '/themes';
if (is_dir($newThemesDir)) {
$relThemesPath = $archiveConfig->getRelativePathsInArchive('themes');
$relThemesPath .= (strlen($relContentPath) > 0 ? '/' : '');
SnapIO::regexGlobCallback($newThemesDir, function ($item) use ($relThemesPath, &$result): void {
if (is_dir($item)) {
$result->addDir($relThemesPath . pathinfo($item, PATHINFO_BASENAME));
} else {
$result->addFile($relThemesPath . pathinfo($item, PATHINFO_BASENAME));
}
}, []);
}
}
self::filterAllChildsOfPathExcept($result, $filterFilesChildOfFolders, $acceptFolderOfFilterChilds);
$result->optmizeFilters();
return $result;
}
/**
* Create filters for remove files
*
* @param Filters|null $baseFilters base extraction filters
*
* @return Filters
*/
public static function getRemoveFilters(?Filters $baseFilters = null): Filters
{
$archiveConfig = DUPX_ArchiveConfig::getInstance();
$security = Security::getInstance();
$result = new Filters();
if (!is_null($baseFilters)) {
// convert all relative path from archive to absolute destination path
foreach ($baseFilters->getDirs() as $dir) {
$result->addDir($archiveConfig->destFileFromArchiveName($dir));
}
foreach ($baseFilters->getDirsWithoutChilds() as $dir) {
$result->addDir($archiveConfig->destFileFromArchiveName($dir), true);
}
foreach ($baseFilters->getFiles() as $file) {
$result->addFile($archiveConfig->destFileFromArchiveName($file));
}
}
$result->addFile($security->getArchivePath());
$result->addFile($security->getBootFilePath());
$result->addFile($security->getBootLogFile());
$result->addDir(DUPX_INIT);
foreach (DUPX_Server::getWpAddonsSiteLists() as $addonPath) {
$result->addDir($addonPath);
}
$overwriteData = PrmMng::getInstance()->getValue(PrmMng::PARAM_OVERWRITE_SITE_DATA);
foreach ($overwriteData['removeFilters']['dirs'] as $dir) {
$result->addDir($dir);
}
foreach ($overwriteData['removeFilters']['files'] as $file) {
$result->addFile($file);
}
$result->optmizeFilters();
return $result;
}
/**
* This function update filters from $filterFilesChildOfFolders and $acceptFolders values
*
* @param Filters $filters Filters
* @param string[] $filterFilesChildOfFolders Filter contents of these paths
* @param string[] $acceptFolders Folders not to filtered
*
* @return void
*/
private static function filterAllChildsOfPathExcept(Filters $filters, array $filterFilesChildOfFolders, array $acceptFolders = []): void
{
//No sense adding filters if not folders specified
if (count($filterFilesChildOfFolders) == 0) {
return;
}
$acceptFolders = array_unique($acceptFolders);
$filterFilesChildOfFolders = array_unique($filterFilesChildOfFolders);
Log::info('ACCEPT FOLDERS ' . Log::v2str($acceptFolders), Log::LV_DETAILED);
Log::info('CHILDS FOLDERS ' . Log::v2str($filterFilesChildOfFolders), Log::LV_DETAILED);
foreach (DUPX_Package::getIndexManager()->iteratePaths(FileIndexManager::LIST_TYPE_DIRS) as $path) {
if (in_array($path, $filterFilesChildOfFolders)) {
continue;
}
foreach ($acceptFolders as $acceptFolder) {
if (SnapIO::isChildPath($path, $acceptFolder, true)) {
continue 2;
}
}
$parentFolder = SnapIO::getRelativeDirname($path);
if (in_array($parentFolder, $filterFilesChildOfFolders)) {
$filters->addDir($path);
}
}
foreach (DUPX_Package::getIndexManager()->iteratePaths(FileIndexManager::LIST_TYPE_FILES) as $path) {
$parentFolder = SnapIO::getRelativeDirname($path);
if (in_array($parentFolder, $filterFilesChildOfFolders)) {
$filters->addFile($path);
}
}
Log::info('FILTERS RESULT ' . Log::v2str($filters), log::LV_DETAILED);
}
/**
*
* @return boolean
* @throws Exception
*/
public static function filterWpCoreFiles(): bool
{
switch (PrmMng::getInstance()->getValue(PrmMng::PARAM_ARCHIVE_ENGINE_SKIP_WP_FILES)) {
case DUPX_Extraction::FILTER_NONE:
return false;
case DUPX_Extraction::FILTER_SKIP_WP_CORE:
case DUPX_Extraction::FILTER_SKIP_CORE_PLUG_THEMES:
case DUPX_Extraction::FILTER_ONLY_MEDIA_PLUG_THEMES:
return true;
default:
throw new Exception('Unknown filter type');
}
}
/**
*
* @return boolean
* @throws Exception
*/
protected static function filterExistsPlugins(): bool
{
switch (PrmMng::getInstance()->getValue(PrmMng::PARAM_ARCHIVE_ENGINE_SKIP_WP_FILES)) {
case DUPX_Extraction::FILTER_NONE:
case DUPX_Extraction::FILTER_SKIP_WP_CORE:
return false;
case DUPX_Extraction::FILTER_SKIP_CORE_PLUG_THEMES:
case DUPX_Extraction::FILTER_ONLY_MEDIA_PLUG_THEMES:
return true;
default:
throw new Exception('Unknown filter type');
}
}
/**
*
* @return boolean
* @throws Exception
*/
protected static function filterExistsThemes(): bool
{
switch (PrmMng::getInstance()->getValue(PrmMng::PARAM_ARCHIVE_ENGINE_SKIP_WP_FILES)) {
case DUPX_Extraction::FILTER_NONE:
case DUPX_Extraction::FILTER_SKIP_WP_CORE:
return false;
case DUPX_Extraction::FILTER_SKIP_CORE_PLUG_THEMES:
case DUPX_Extraction::FILTER_ONLY_MEDIA_PLUG_THEMES:
return true;
default:
throw new Exception('Unknown filter type');
}
}
/**
*
* @return boolean
* @throws Exception
*/
protected static function filterAllExceptPlugingThemesMedia(): bool
{
switch (PrmMng::getInstance()->getValue(PrmMng::PARAM_ARCHIVE_ENGINE_SKIP_WP_FILES)) {
case DUPX_Extraction::FILTER_NONE:
case DUPX_Extraction::FILTER_SKIP_WP_CORE:
case DUPX_Extraction::FILTER_SKIP_CORE_PLUG_THEMES:
return false;
case DUPX_Extraction::FILTER_ONLY_MEDIA_PLUG_THEMES:
return true;
default:
throw new Exception('Unknown filter type');
}
}
}

View File

@@ -0,0 +1,166 @@
<?php
namespace Duplicator\Installer\Core\Deploy\Files;
use VendorDuplicator\Amk\JsonSerialize\AbstractJsonSerializable;
use Duplicator\Libs\Snap\SnapIO;
/**
* Manage filters for extraction
*/
class Filters extends AbstractJsonSerializable
{
/** @var string[] */
protected $files = [];
/** @var string[] */
protected $dirsWithoutChilds = [];
/** @var string[] */
protected array $dirs;
/**
* Class contructor
*
* @param string[] $dirs dirs filters
* @param string[] $dirsWithoutChilds dirs without child filters
* @param string[] $files files filters
*/
public function __construct($dirs = [], $dirsWithoutChilds = [], $files = [])
{
$this->files = (array) $files;
$this->dirs = (array) $dirs;
$this->dirsWithoutChilds = (array) $dirsWithoutChilds;
}
/**
* Check if passe path is filterd
*
* @param string $path path to check
*
* @return bool
*/
public function isFiltered($path)
{
if (in_array($path, $this->dirsWithoutChilds)) {
return true;
}
foreach ($this->dirs as $dirFilter) {
if (SnapIO::isChildPath($path, $dirFilter)) {
return true;
}
}
return in_array($path, $this->files);
}
/**
* Add dir filter
*
* @param string $dir dir path
* @param bool $withoutChild if true add dir filter without childs
*
* @return void
*/
public function addDir($dir, $withoutChild = false): void
{
if ($withoutChild) {
$this->dirsWithoutChilds[] = (string) $dir;
} else {
$this->dirs[] = (string) $dir;
}
}
/**
* Add file fo filters
*
* @param string $file file path
*
* @return void
*/
public function addFile($file): void
{
$this->files[] = (string) $file;
}
/**
* Optimize and sort filters
*
* @return bool
*/
public function optmizeFilters()
{
$this->files = array_values(array_unique($this->files));
$this->dirsWithoutChilds = array_values(array_unique($this->dirsWithoutChilds));
$this->dirs = array_values(array_unique($this->dirs));
$optimizedDirs = [];
$optimizedFiles = [];
for ($i = 0; $i < count($this->dirs); $i++) {
$exclude = false;
for ($j = 0; $j < count($this->dirs); $j++) {
if ($i === $j) {
continue;
}
if (SnapIO::isChildPath($this->dirs[$i], $this->dirs[$j])) {
$exclude = true;
break;
}
}
if (!$exclude) {
$optimizedDirs[] = $this->dirs[$i];
}
}
$optimizedDirs = SnapIO::sortBySubfoldersCount($optimizedDirs);
foreach ($this->files as $file) {
$exclude = false;
foreach ($optimizedDirs as $cDir) {
if (SnapIO::isChildPath($file, $cDir)) {
$exclude = true;
break;
}
}
if (!$exclude) {
$optimizedFiles[] = $file;
}
}
$this->files = $optimizedFiles;
$this->dirs = $optimizedDirs;
return true;
}
/**
* Get the value of files
*
* @return string[]
*/
public function getFiles()
{
return $this->files;
}
/**
* Get the value of dirs
*
* @return string[]
*/
public function getDirs()
{
return $this->dirs;
}
/**
* Get the value of dirsWithoutChilds
*
* @return string[]
*/
public function getDirsWithoutChilds()
{
return $this->dirsWithoutChilds;
}
}

View File

@@ -0,0 +1,317 @@
<?php
namespace Duplicator\Installer\Core\Deploy\Files;
use DUPX_Extraction;
use Duplicator\Installer\Core\Deploy\Plugins\PluginsManager;
use Duplicator\Installer\Core\Params\Models\SiteOwrMap;
use Duplicator\Installer\Core\Params\PrmMng;
use Duplicator\Installer\Utils\Log\Log;
use Duplicator\Libs\Snap\SnapIO;
use Duplicator\Libs\Snap\SnapWP;
use DUPX_ArchiveConfig;
use DUPX_Custom_Host_Manager;
use Duplicator\Installer\Core\InstState;
use DUPX_NOTICE_ITEM;
use DUPX_NOTICE_MANAGER;
use Error;
use Exception;
class RemoveFiles
{
protected \Duplicator\Installer\Core\Deploy\Files\Filters $removeFilters;
/**
* Class contructor
*
* @param Filters $filters fles filters
*/
public function __construct(Filters $filters)
{
$this->removeFilters = $filters;
}
/**
* Remove file if action is enableds
*
* @return void
*/
public function remove(): void
{
$paramsManager = PrmMng::getInstance();
if (InstState::isAddSiteOnMultisite()) {
$this->removeAddonSiteToMultisite();
return;
}
switch ($paramsManager->getValue(PrmMng::PARAM_ARCHIVE_ACTION)) {
case DUPX_Extraction::ACTION_REMOVE_ALL_FILES:
$this->removeAllFiles();
break;
case DUPX_Extraction::ACTION_REMOVE_WP_FILES:
$this->removeWpFiles();
break;
case DUPX_Extraction::ACTION_REMOVE_UPLOADS:
$this->removeUploads();
break;
case DUPX_Extraction::ACTION_DO_NOTHING:
$this->removeDoNothing();
break;
default:
throw new Exception('Invalid engine action ' . $paramsManager->getValue(PrmMng::PARAM_ARCHIVE_ACTION));
}
}
/**
* This function remove files before extraction
*
* @param string[] $paths Paths lists
*
* @return void
*/
protected function removeFiles($paths = [])
{
Log::info('REMOVE FILES');
$filesFilters = $this->removeFilters->getFiles();
$excludeFiles = array_map(fn($value): string => '/^' . preg_quote($value, '/') . '$/', $filesFilters);
$excludeFolders = array_map(fn($value): string => '/^' . preg_quote($value, '/') . '(?:\/.*)?$/', $this->removeFilters->getDirs());
$excludeFolders[] = '/.+\/backups-dup-(lite|pro)$/';
$excludeDirsWithoutChilds = $this->removeFilters->getDirsWithoutChilds();
foreach ($paths as $path) {
if (is_file($path)) {
if (in_array($path, $excludeFiles)) {
continue;
}
Log::info('REMOVE FILE ' . Log::v2str($path));
unlink($path);
} else {
Log::info('REMOVE FOLDER ' . Log::v2str($path));
SnapIO::regexGlobCallback($path, function ($path) use ($excludeDirsWithoutChilds): void {
foreach ($excludeDirsWithoutChilds as $excludePath) {
if (SnapIO::isChildPath($excludePath, $path)) {
return;
}
}
$result = (is_dir($path) ? rmdir($path) : unlink($path));
if ($result == false) {
$lastError = error_get_last();
$message = ($lastError['message'] ?? 'Couldn\'t remove file');
RemoveFiles::reportRemoveNotices($path, $message);
}
}, [
'regexFile' => $excludeFiles,
'regexFolder' => $excludeFolders,
'checkFullPath' => true,
'recursive' => true,
'invert' => true,
'childFirst' => true,
]);
}
}
}
/**
* Remove worpdress core files
*
* @return void
*/
protected function removeWpFiles()
{
try {
Log::info('REMOVE WP FILES');
Log::resetTime(Log::LV_DEFAULT, false);
$paramsManager = PrmMng::getInstance();
$absDir = SnapIO::safePathTrailingslashit($paramsManager->getValue(PrmMng::PARAM_PATH_WP_CORE_NEW));
if (!is_dir($absDir) || !is_readable($absDir)) {
return;
}
$removeFolders = [];
if (!FilterMng::filterWpCoreFiles() && ($dh = opendir($absDir))) {
while (($elem = readdir($dh)) !== false) {
if ($elem === '.' || $elem === '..') {
continue;
}
if (SnapWP::isWpCore($elem, SnapWP::PATH_RELATIVE)) {
$fullPath = $absDir . $elem;
if (is_dir($fullPath)) {
$removeFolders[] = $fullPath;
} else {
if (is_writable($fullPath)) {
unlink($fullPath);
}
}
}
}
closedir($dh);
}
if (!InstState::isAddSiteOnMultisite()) {
$removeFolders[] = $paramsManager->getValue(PrmMng::PARAM_PATH_CONTENT_NEW);
}
$removeFolders[] = $paramsManager->getValue(PrmMng::PARAM_PATH_UPLOADS_NEW);
$removeFolders[] = $paramsManager->getValue(PrmMng::PARAM_PATH_PLUGINS_NEW);
$removeFolders[] = $paramsManager->getValue(PrmMng::PARAM_PATH_MUPLUGINS_NEW);
$this->removeFiles(array_unique($removeFolders));
Log::logTime('FOLDERS REMOVED', Log::LV_DEFAULT, false);
} catch (Exception | Error $e) {
Log::logException($e);
}
}
/**
* Clean uplod forlser of selectes subsites
*
* @return void
*/
protected function removeAddonSiteToMultisite()
{
Log::info('CLEAN UPLOAD FOLDERS FOR ADD SITES');
$paramsManager = PrmMng::getInstance();
/** @var SiteOwrMap[] $overwriteMapping */
$overwriteMapping = $paramsManager->getValue(PrmMng::PARAM_SUBSITE_OVERWRITE_MAPPING);
foreach ($overwriteMapping as $map) {
if (($subsiteInfo = $map->getTargetSiteInfo()) == false) {
throw new Exception('Target site id ' . $map->getTargetId() . ' not valid');
}
Log::info("\tEMPTY " . $subsiteInfo['fullUploadPath']);
if ($map->getTargetId() == 1) {
SnapIO::emptyDir($subsiteInfo['fullUploadPath'], ['sites']);
} else {
SnapIO::emptyDir($subsiteInfo['fullUploadPath']);
}
}
}
/**
* Remove ony uploads files
*
* @return void
*/
protected function removeUploads()
{
try {
Log::info('REMOVE UPLOADS FILES');
Log::resetTime(Log::LV_DEFAULT, false);
$paramsManager = PrmMng::getInstance();
$removePaths = [];
$removePaths[] = $paramsManager->getValue(PrmMng::PARAM_PATH_UPLOADS_NEW);
foreach (PluginsManager::getInstance()->getAllPluginsPaths(true, true) as $pluginPath) {
$removePaths[] = $pluginPath;
}
$this->removeFiles(array_unique($removePaths));
Log::logTime('FOLDERS REMOVED', Log::LV_DEFAULT, false);
} catch (Exception | Error $e) {
Log::logException($e);
}
}
/**
* Remove ony uploads files
*
* @return void
*/
protected function removeDoNothing()
{
try {
Log::info('REMOVE DONOTHING FILES');
Log::resetTime(Log::LV_DEFAULT, false);
$removePaths = [];
foreach (PluginsManager::getInstance()->getAllPluginsPaths(true, true) as $pluginPath) {
$removePaths[] = $pluginPath;
}
$this->removeFiles(array_unique($removePaths));
Log::logTime('FOLDERS REMOVED', Log::LV_DEFAULT, false);
} catch (Exception | Error $e) {
Log::logException($e);
}
}
/**
* Remove all files before extraction
*
* @return void
*/
protected function removeAllFiles()
{
try {
Log::info('REMOVE ALL FILES');
Log::resetTime(Log::LV_DEFAULT, false);
$pathsMapping = DUPX_ArchiveConfig::getInstance()->getPathsMapping();
$folders = is_string($pathsMapping) ? [$pathsMapping] : array_values($pathsMapping);
$this->removeFiles($folders);
Log::logTime('FOLDERS REMOVED', Log::LV_DEFAULT, false);
} catch (Exception | Error $e) {
Log::logException($e);
}
}
/**
*
* @param string $fileName package relative path
* @param string $errorMessage error message
*
* @return void
*/
public static function reportRemoveNotices($fileName, $errorMessage): void
{
if (DUPX_Custom_Host_Manager::getInstance()->skipWarningExtractionForManaged($fileName)) {
// @todo skip warning for managed hostiong (it's a temp solution)
return;
}
Log::info('Remove ' . $fileName . ' error message: ' . $errorMessage);
if (is_dir($fileName)) {
// Skip warning message for folders
return;
}
$nManager = DUPX_NOTICE_MANAGER::getInstance();
if (SnapWP::isWpCore($fileName, SnapWP::PATH_RELATIVE)) {
Log::info("FILE CORE REMOVE ERROR: {$fileName} | MSG:" . $errorMessage);
$shortMsg = 'Can\'t remove wp core files';
$errLevel = DUPX_NOTICE_ITEM::CRITICAL;
$idManager = 'wp-remove-error-file-core';
} else {
Log::info("FILE REMOVE ERROR: {$fileName} | MSG:" . $errorMessage);
$shortMsg = 'Can\'t remove files';
$errLevel = DUPX_NOTICE_ITEM::HARD_WARNING;
$idManager = 'wp-remove-error-file-no-core';
}
$longMsg = 'FILE: <b>' . htmlspecialchars($fileName) . '</b><br>Message: ' . htmlspecialchars($errorMessage) . '<br><br>';
$nManager->addBothNextAndFinalReportNotice(
[
'shortMsg' => $shortMsg,
'longMsg' => $longMsg,
'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_HTML,
'level' => $errLevel,
'sections' => ['files'],
],
DUPX_NOTICE_MANAGER::ADD_UNIQUE_APPEND,
$idManager
);
}
}

View File

@@ -0,0 +1,171 @@
<?php
namespace Duplicator\Installer\Core\Deploy;
use Duplicator\Installer\Core\InstState;
use Duplicator\Installer\Core\Params\PrmMng;
use Duplicator\Installer\Package\DescriptorTheme;
use Duplicator\Installer\Utils\Log\Log;
use DUPX_DB;
use DUPX_DB_Functions;
use Exception;
use mysqli;
class Helpers
{
/**
* Load WordPress dependencies
*
* @return bool $loaded
*
* @throws Exception
*/
public static function loadWP()
{
static $loaded = null;
if (is_null($loaded)) {
$wpRootDir = PrmMng::getInstance()->getValue(PrmMng::PARAM_PATH_WP_CORE_NEW);
require_once($wpRootDir . '/wp-load.php');
if (!class_exists('WP_Privacy_Policy_Content')) {
require_once($wpRootDir . '/wp-admin/includes/misc.php');
}
if (!function_exists('request_filesystem_credentials')) {
require_once($wpRootDir . '/wp-admin/includes/file.php');
}
if (!function_exists('get_plugins')) {
require_once $wpRootDir . '/wp-admin/includes/plugin.php';
}
if (!function_exists('delete_theme')) {
require_once $wpRootDir . '/wp-admin/includes/theme.php';
}
$GLOBALS['wpdb']->show_errors(false);
$loaded = true;
}
return $loaded;
}
/**
* Check if Theme is enabled
*
* @param DescriptorTheme $theme Theme object
*
* @return boolean
*
* @throws Exception
*/
public static function isThemeEnable(DescriptorTheme $theme): bool
{
switch (InstState::getInstType()) {
case InstState::TYPE_SINGLE:
case InstState::TYPE_RBACKUP_SINGLE:
case InstState::TYPE_RECOVERY_SINGLE:
if ($theme->isActive) {
return true;
}
break;
case InstState::TYPE_MSUBDOMAIN:
case InstState::TYPE_MSUBFOLDER:
case InstState::TYPE_RBACKUP_MSUBDOMAIN:
case InstState::TYPE_RBACKUP_MSUBFOLDER:
case InstState::TYPE_RECOVERY_MSUBDOMAIN:
case InstState::TYPE_RECOVERY_MSUBFOLDER:
if (count($theme->isActive) > 0) {
return true;
}
break;
case InstState::TYPE_STANDALONE:
if (in_array(PrmMng::getInstance()->getValue(PrmMng::PARAM_SUBSITE_ID), $theme->isActive)) {
return true;
}
break;
case InstState::TYPE_SINGLE_ON_SUBDOMAIN:
case InstState::TYPE_SINGLE_ON_SUBFOLDER:
case InstState::TYPE_SUBSITE_ON_SUBDOMAIN:
case InstState::TYPE_SUBSITE_ON_SUBFOLDER:
return true;
case InstState::TYPE_NOT_SET:
default:
throw new Exception('Invalid installer type');
}
return false;
}
/**
* Check if a parent theme has a child theme enabled
*
* @param DescriptorTheme $parentTheme Parent Theme Object
* @param DescriptorTheme[] $themes Themes List
*
* @return boolean
* @throws Exception
*/
public static function haveChildEnable(DescriptorTheme $parentTheme, &$themes): bool
{
foreach ($themes as $theme) {
if ($theme->parentTheme === $parentTheme->slug) {
if (Helpers::isThemeEnable($theme)) {
return true;
}
}
}
return false;
}
/**
* @param mysqli $dbh Database connection
*
* @return int[]
*/
public static function getSuperAdminsUserIds(\mysqli $dbh): array
{
$result = [];
if (InstState::isNewSiteIsMultisite()) {
$paramsManager = PrmMng::getInstance();
$basePrefix = $paramsManager->getValue(PrmMng::PARAM_DB_TABLE_PREFIX);
$usersTableName = "{$basePrefix}users";
$superAdminsList = self::getSuperAdminUsernames($dbh, $basePrefix);
if (!empty($superAdminsList)) {
$sql = "SELECT ID FROM {$usersTableName} WHERE user_login IN ('" . implode("','", $superAdminsList) . "')";
$queryResult = DUPX_DB::queryToArray($dbh, $sql);
foreach ($queryResult as $superAdminsResult) {
$result[] = $superAdminsResult[0];
}
}
}
return $result;
}
/**
* Get Super Admin Users names
*
* @param mysqli $dbh Database connection
* @param string $basePrefix WordPress Tables Prefix
*
* @return string[]
*
* @throws Exception
*/
public static function getSuperAdminUsernames(\mysqli $dbh, string $basePrefix): array
{
$result = [];
$siteMetaTableName = "{$basePrefix}sitemeta";
if (InstState::isNewSiteIsMultisite() && DUPX_DB_Functions::getInstance()->tablesExist($siteMetaTableName)) {
$sql = "SELECT meta_value FROM {$siteMetaTableName} WHERE meta_key = 'site_admins'";
$superAdminsResults = DUPX_DB::queryToArray($dbh, $sql);
if (isset($superAdminsResults[0][0])) {
$result = unserialize($superAdminsResults[0][0]);
Log::info('SUPER ADMIN USERS: ' . print_r($result, true));
}
}
return $result;
}
}

View File

@@ -0,0 +1,112 @@
<?php
namespace Duplicator\Installer\Core\Deploy;
use VendorDuplicator\Amk\JsonSerialize\JsonSerialize;
use Duplicator\Installer\Core\Params\Descriptors\ParamDescMultisite;
use Duplicator\Installer\Core\Params\Models\SiteOwrMap;
use Duplicator\Installer\Core\Params\PrmMng;
use Duplicator\Installer\REST\RESTPoints;
use Duplicator\Installer\Utils\Log\Log;
use Duplicator\Libs\Snap\SnapURL;
use DUPX_ArchiveConfig;
use DUPX_Ctrl_Params;
use Duplicator\Installer\Core\InstState;
use DUPX_U;
use Exception;
class Multisite
{
/**
* Init new subistes info
*
* @return void
*/
public static function overwriteSubsitesInit(): void
{
$paramsManager = PrmMng::getInstance();
/** @var SiteOwrMap[] $overwriteMapping */
$overwriteMapping = PrmMng::getInstance()->getValue(PrmMng::PARAM_SUBSITE_OVERWRITE_MAPPING);
$sendData = JsonSerialize::serialize($overwriteMapping, JsonSerialize::JSON_SKIP_CLASS_NAME);
$errorMessage = '';
$numSubsites = count($overwriteMapping);
if (($subsitesInfo = RESTPoints::getInstance()->subsiteActions($sendData, $numSubsites, $errorMessage)) == false) {
Log::info('Creation subsites error, message: ' . $errorMessage);
throw new Exception('Can\'t create a new sub site message :' . $errorMessage);
}
$overwriteData = $paramsManager->getValue(PrmMng::PARAM_OVERWRITE_SITE_DATA);
foreach ($subsitesInfo as $subsiteInfo) {
switch ($subsiteInfo['targetId']) {
case SiteOwrMap::NEW_SUBSITE_WITH_SLUG:
case SiteOwrMap::NEW_SUBSITE_WITH_FULL_DOMAIN:
$overwriteData['subsites'][] = $subsiteInfo['info'];
Log::info('NEW SUBSITE CREATED ON ID: ' . $subsiteInfo['info']['id'] . ' URL ' . $subsiteInfo['info']['fullSiteUrl']);
if (($owrMap = ParamDescMultisite::getOwrMapBySourceId($subsiteInfo['sourceId'])) == false) {
throw new Exception('OwrMap object not boud by id :' . $subsiteInfo['sourceId']);
}
$owrMap->setTargetId($subsiteInfo['info']['id']);
break;
default:
// none
break;
}
}
$paramsManager->setValue(PrmMng::PARAM_OVERWRITE_SITE_DATA, $overwriteData);
$paramsManager->setValue(PrmMng::PARAM_SUBSITE_OVERWRITE_MAPPING, $overwriteMapping);
DUPX_Ctrl_Params::setParamsOnAddSiteOnMultisite();
$paramsManager->save();
}
/**
* Return new subsite URLs
*
* @return array<int, string> Return mapped URLs
*/
public static function getMappedSubisteURLs()
{
static $mappedURLs = null;
if (is_null($mappedURLs)) {
$mappedURLs = [];
$config = DUPX_ArchiveConfig::getInstance();
/** @var SiteOwrMap[] */
$customMap = PrmMng::getInstance()->getValue(PrmMng::PARAM_MU_REPLACE);
$mainSiteDomain = SnapURL::parseUrl(PrmMng::getInstance()->getValue(PrmMng::PARAM_URL_NEW), PHP_URL_HOST);
$sourceMainSiteIndex = $config->getMainSiteIndex();
$sourceMainUrl = $config->getUrlFromSubsiteObj($config->subsites[$sourceMainSiteIndex]);
if (InstState::isAddSiteOnMultisite()) {
$overwriteData = PrmMng::getInstance()->getValue(PrmMng::PARAM_OVERWRITE_SITE_DATA);
$subdomain = (isset($overwriteData['subdomain']) && $overwriteData['subdomain']);
} else {
$subdomain = $config->isSubdomain();
}
for ($i = 0; $i < count($config->subsites); $i++) {
$subsite = $config->subsites[$i];
for ($j = 0; $j < count($customMap); $j++) {
if ($subsite->id != $customMap[$j]->getSourceId()) {
continue;
}
$mappedURLs[$subsite->id] = $customMap[$j]->getNewSlugFullUrl($mainSiteDomain, $subdomain, true);
break;
}
if ($j == count($customMap)) {
$mappedURLs[$subsite->id] = DUPX_U::getDefaultURL($config->getUrlFromSubsiteObj($subsite), $sourceMainUrl);
}
}
}
return $mappedURLs;
}
}

View File

@@ -0,0 +1,86 @@
<?php
/**
* @package Duplicator/Installer
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Core\Deploy\Plugins;
class PluginCustomActions
{
const BY_DEFAULT_AUTO = 'plugin_def_auto';
const BY_DEFAULT_DISABLED = 'plugin_def_disabled';
const BY_DEFAULT_ENABLED = 'plugin_def_enabled';
/** @var string */
protected $slug;
/** @var string|callable */
protected $byDefaultStatus = self::BY_DEFAULT_AUTO;
/** @var bool|callable */
protected $enableAfterLogin = false;
/** @var string */
protected $byDefaultMessage = '';
/**
* Class constructor
*
* @param string $slug plugin slug
* @param string|callable $byDefaultStatus set plugin status
* @param bool|callable $enableAfterLogin enable plugin after login
* @param string|callable $byDefaultMessage message if status change
*/
public function __construct(
$slug,
$byDefaultStatus = self::BY_DEFAULT_AUTO,
$enableAfterLogin = false,
$byDefaultMessage = ''
) {
$this->slug = $slug;
$this->byDefaultStatus = $byDefaultStatus;
$this->enableAfterLogin = $enableAfterLogin;
$this->byDefaultMessage = $byDefaultMessage;
}
/**
* Return by defualt status
*
* @return string by default enum
*/
public function byDefaultStatus()
{
if (is_callable($this->byDefaultStatus)) {
return call_user_func($this->byDefaultStatus, $this);
} else {
return $this->byDefaultStatus;
}
}
/**
* return true if plugin must be enabled after login
*
* @return boolean
*/
public function isEnableAfterLogin()
{
if (is_callable($this->enableAfterLogin)) {
return call_user_func($this->enableAfterLogin, $this);
} else {
return $this->enableAfterLogin;
}
}
/**
* By default message
*
* @return string
*/
public function byDefaultMessage()
{
if (is_callable($this->byDefaultMessage)) {
return call_user_func($this->byDefaultMessage, $this);
} else {
return $this->byDefaultMessage;
}
}
}

View File

@@ -0,0 +1,501 @@
<?php
/**
* @package Duplicator/Installer
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Core\Deploy\Plugins;
use Duplicator\Installer\Core\Deploy\Helpers;
use Duplicator\Installer\Core\Params\PrmMng;
use Duplicator\Installer\Package\DescriptorPlugin;
use Duplicator\Installer\Utils\InstallerOrigFileMng;
use Duplicator\Installer\Utils\Log\Log;
use Duplicator\Libs\Snap\SnapUtil;
use DUPX_ArchiveConfig;
use DUPX_NOTICE_ITEM;
use DUPX_NOTICE_MANAGER;
use Error;
use Exception;
use const WP_PLUGIN_DIR;
/**
* Pplugin item descriptor
*/
class PluginItem extends DescriptorPlugin
{
const STATUS_ACTIVE = 'active';
const STATUS_INACTIVE = 'inactive';
const STATUS_NETWORK_ACTIVE = 'network-active';
const STATUS_DROP_INS = 'drop-ins';
const STATUS_MUST_USE = 'must-use';
/** @var bool */
public $activateAction = false;
/** @var bool */
public $deactivateAction = false;
/** @var null|string[] */
public $deactivateMessage;
/**
*
* @param int $subsite // if -1 it checks that at least one site exists in which it is active in the netowrk
*
* @return boolean
*/
public function isActive($subsite = -1): bool
{
if ($this->active === true) {
return true;
} elseif ($subsite === -1 && !empty($this->active)) {
return true;
} elseif (
is_array($this->active) &&
in_array($subsite, $this->active)
) {
return true;
} else {
return false;
}
}
/**
* Return plugin slug
*
* @return string
*/
public function getSlug()
{
return $this->slug;
}
/**
*
* @return boolean
*/
public function isNetworkActive()
{
return $this->networkActive;
}
/**
*
* @return boolean
*/
public function isMustUse()
{
return $this->mustUse;
}
/**
*
* @return boolean
*/
public function isDropIns()
{
return $this->dropIns;
}
/**
* is true if all active status are false
*
* @param int $subsite if -1 it checks that at least one site exists in which it is active in the netowrk
*
* @return boolean
*/
public function isInactive($subsite = -1): bool
{
return !$this->isActive($subsite) && !$this->isNetworkActive() && !$this->isMustUse() && !$this->isDropIns();
}
/**
* return true if isn't networkActive or must-use or drop-ins
*
* @return boolean
*/
public function isNetworkInactive(): bool
{
return !$this->isNetworkActive() && !$this->isMustUse() && !$this->isDropIns();
}
/**
* @return bool
*/
public function isIgnore(): bool
{
return in_array($this->slug, PrmMng::getInstance()->getValue(PrmMng::PARAM_IGNORE_PLUGINS));
}
/**
* @return bool
*/
public function isForceDisabled(): bool
{
return in_array($this->slug, PrmMng::getInstance()->getValue(PrmMng::PARAM_FORCE_DIABLE_PLUGINS));
}
/**
* Set activate action true if the plugin is active or if deactivateAction is enabled
*
* @param int $subsite current subsite id
* @param bool $networkCheck if true check only on network or check by subsite id
* @param bool $forceActivation if true skip all pluginstati check and set activation action
*
* @return bool return activateAction
*/
public function setActivationAction($subsite = -1, $networkCheck = false, $forceActivation = false)
{
if ($this->isIgnore()) {
return true;
}
if ($forceActivation) {
Log::info(
'PLUGINS [' . __FUNCTION__ . ']: set forced activation action ' . Log::v2str($this->slug),
Log::LV_DEBUG
);
return ($this->activateAction = true);
}
$activate = false;
if ($networkCheck) {
if ($this->isNetworkInactive()) {
$activate = true;
}
} else {
if ($this->isInactive($subsite) || ($subsite > -1 && $this->isNetworkActive())) {
$activate = true;
}
}
if ($activate || $this->deactivateAction) {
Log::info(
'PLUGINS [' . __FUNCTION__ . ']: set activation action ' . Log::v2str($this->slug),
Log::LV_DEBUG
);
$this->activateAction = true;
}
return $this->activateAction;
}
/**
* Set deactivation action if the plugin isn't inactive
*
* @param int $subsite Id of a Subsite
* @param ?string $shortMsg Short message for deactivation log
* @param ?string $longMsg long message for deactivation log
* @param boolean $networkCheck if true check if is active only on network
*
* @return boolean return deactivaeAction status
*/
public function setDeactivateAction($subsite = -1, $shortMsg = null, $longMsg = null, $networkCheck = false)
{
if ($this->isIgnore()) {
return true;
}
$deactivate = false;
if ($networkCheck) {
if (!$this->isNetworkInactive()) {
$deactivate = true;
}
} else {
if (!$this->isInactive($subsite)) {
$deactivate = true;
}
}
if ($deactivate) {
Log::info(
'PLUGINS [' . __FUNCTION__ . ']: set deactivate action ' . Log::v2str($this->slug),
Log::LV_DEBUG
);
$this->deactivateAction = true;
if (!empty($shortMsg)) {
$this->deactivateMessage = [
'shortMsg' => $shortMsg,
'longMsg' => $longMsg,
];
}
}
return $this->deactivateAction;
}
/**
* Return plugin archive path
*
* @return string
*/
public function getPluginArchivePath(): string
{
$archiveConfig = DUPX_ArchiveConfig::getInstance();
if ($this->isMustUse()) {
$mainDir = $archiveConfig->getRelativePathsInArchive('muplugins');
} elseif ($this->isDropIns()) {
$mainDir = $archiveConfig->getRelativePathsInArchive('wpcontent');
} else {
$mainDir = $archiveConfig->getRelativePathsInArchive('plugins');
}
return $mainDir . '/' . $this->slug;
}
/**
* Return plugin path, folder if plugin has a folder or file if is a single file plugin (like drop-ins)
*
* @param bool $filterNotExists if true filter paths that don't exists
*
* @return false|string false if plugin path doesn't exists
*/
public function getPluginPath($filterNotExists = true)
{
$paramManager = PrmMng::getInstance();
$mainDir = false;
if ($this->isMustUse()) {
$mainDir = $paramManager->getValue(PrmMng::PARAM_PATH_MUPLUGINS_NEW);
} elseif ($this->isDropIns()) {
$mainDir = $paramManager->getValue(PrmMng::PARAM_PATH_CONTENT_NEW);
} else {
$mainDir = $paramManager->getValue(PrmMng::PARAM_PATH_PLUGINS_NEW);
}
if ($mainDir === false) {
return false;
}
$dirNameRelative = dirname($this->slug);
$relativePath = strlen($dirNameRelative) == 0 || $dirNameRelative == '.' ? $this->slug : $dirNameRelative;
$result = $mainDir . '/' . $relativePath;
if ($filterNotExists && !file_exists($result)) {
return false;
} else {
return $result;
}
}
/**
* @return bool
*/
public function deactivate(): bool
{
if (!$this->deactivateAction) {
return false;
}
Log::info('[PLUGINS MANAGER] deactivate ' . Log::v2str($this->slug), Log::LV_DETAILED);
$deactivated = false;
$origFileManager = InstallerOrigFileMng::getInstance();
if ($this->isMustUse() || $this->isDropIns()) {
if (($pluginPath = $this->getPluginPath()) == false) {
Log::info('PLUGINS: can\'t remove plugin ' . $this->slug . ' because it doesn\'t exists');
} else {
$origFileManager->addEntry($this->slug, $pluginPath, InstallerOrigFileMng::MODE_MOVE);
$deactivated = true;
}
} else {
// for other type of plugins do nothing. They are not activated because they are missing in the table list of plugins
$deactivated = true;
}
if ($deactivated) {
if (is_null($this->deactivateMessage)) {
DUPX_NOTICE_MANAGER::getInstance()->addFinalReportNotice([
'shortMsg' => $this->name . ' has been deactivated',
'level' => DUPX_NOTICE_ITEM::NOTICE,
'sections' => 'plugins',
]);
} else {
DUPX_NOTICE_MANAGER::getInstance()->addFinalReportNotice([
'shortMsg' => $this->deactivateMessage['shortMsg'],
'level' => DUPX_NOTICE_ITEM::SOFT_WARNING,
'longMsg' => $this->deactivateMessage['longMsg'],
'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_HTML,
'sections' => 'plugins',
]);
}
} else {
DUPX_NOTICE_MANAGER::getInstance()->addFinalReportNotice([
'shortMsg' => 'Can\'t deactivate the plugin ' . $this->name,
'level' => DUPX_NOTICE_ITEM::SOFT_WARNING,
'longMsg' => 'Folder of the plugin not found',
'sections' => 'plugins',
]);
}
// prevent multiple decativation action
$this->deactivateAction = false;
return true;
}
/**
*
* @param int $subsiteId Subsite ID
*
* @return string
*/
public function getOrgiStatus($subsiteId): string
{
if ($this->isMustUse()) {
return self::STATUS_MUST_USE;
} elseif ($this->isDropIns()) {
return self::STATUS_DROP_INS;
} elseif ($this->isNetworkActive()) {
return self::STATUS_NETWORK_ACTIVE;
} elseif ($this->isActive($subsiteId)) {
return self::STATUS_ACTIVE;
} else {
return self::STATUS_INACTIVE;
}
}
/**
*
* @param string $status Plugin Status
*
* @return string
*/
public static function getStatusLabel($status): string
{
switch ($status) {
case self::STATUS_MUST_USE:
return 'must-use';
case self::STATUS_DROP_INS:
return 'drop-in';
case self::STATUS_NETWORK_ACTIVE:
return 'network active';
case self::STATUS_ACTIVE:
return 'active';
case self::STATUS_INACTIVE:
return 'inactive';
default:
throw new Exception('Invalid status');
}
}
/**
* Uninstall a single plugin.
*
* Calls the uninstall hook, if it is available.
*
* @return bool True if a plugin's uninstall.php file has been found and included.
*/
public function uninstall(): bool
{
$nManager = DUPX_NOTICE_MANAGER::getInstance();
try {
Helpers::loadWP();
// UNINSTALL PLUGIN IF IS ACTIVE
$level = error_reporting(E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_ERROR | E_WARNING
| E_PARSE | E_USER_ERROR | E_USER_WARNING | E_RECOVERABLE_ERROR);
if (SnapUtil::isIniValChangeable('display_errors')) {
@ini_set('display_errors', '0');
}
Log::info("UNINSTALL PLUGIN " . Log::v2str($this->slug), Log::LV_DEBUG);
$pluginFile = plugin_basename($this->slug);
$uninstallable_plugins = (array)get_option('uninstall_plugins');
/**
* Fires in uninstall_plugin() immediately before the plugin is uninstalled.
*
* @param string $plugin Path to the main plugin file from plugins directory.
* @param array $uninstallable_plugins Uninstallable plugins.
*/
do_action('pre_uninstall_plugin', $this->slug, $uninstallable_plugins);
if (file_exists(WP_PLUGIN_DIR . '/' . dirname($pluginFile) . '/uninstall.php')) {
if (isset($uninstallable_plugins[$pluginFile])) {
unset($uninstallable_plugins[$pluginFile]);
update_option('uninstall_plugins', $uninstallable_plugins);
}
unset($uninstallable_plugins);
if (defined('WP_UNINSTALL_PLUGIN')) {
$already_defined_uninstall_const = true;
} else {
define('WP_UNINSTALL_PLUGIN', $pluginFile);
$already_defined_uninstall_const = false;
}
wp_register_plugin_realpath(WP_PLUGIN_DIR . '/' . $pluginFile);
if ($already_defined_uninstall_const) {
$uninstall_file_content = file_get_contents(WP_PLUGIN_DIR . '/' . dirname($pluginFile) . '/uninstall.php');
/*
$regexProhibited = array(
'dirname[\t\s]*\([\t\s]*WP_UNINSTALL_PLUGIN[\t\s]*\)',
'WP_UNINSTALL_PLUGIN[\t\s]*\!?=',
'\!?=[\t\s]*WP_UNINSTALL_PLUGIN',
'current_user_can'
); */
$prohibited_codes = [
'dirname( WP_UNINSTALL_PLUGIN )',
'dirname(WP_UNINSTALL_PLUGIN )',
'dirname( WP_UNINSTALL_PLUGIN)',
'dirname(WP_UNINSTALL_PLUGIN)',
'WP_UNINSTALL_PLUGIN =',
'WP_UNINSTALL_PLUGIN !=',
'WP_UNINSTALL_PLUGIN=',
'WP_UNINSTALL_PLUGIN!=',
'= WP_UNINSTALL_PLUGIN',
'!= WP_UNINSTALL_PLUGIN',
'=WP_UNINSTALL_PLUGIN=',
'!=WP_UNINSTALL_PLUGIN',
'current_user_can',
];
foreach ($prohibited_codes as $prohibited_code) {
if (false !== stripos($uninstall_file_content, $prohibited_code)) {
Log::info("Can't include uninstall.php file of the " . $this->slug . " because prohibited code found");
return false;
}
}
}
include(WP_PLUGIN_DIR . '/' . dirname($pluginFile) . '/uninstall.php');
} elseif (isset($uninstallable_plugins[$pluginFile])) {
$callable = $uninstallable_plugins[$pluginFile];
unset($uninstallable_plugins[$pluginFile]);
update_option('uninstall_plugins', $uninstallable_plugins);
unset($uninstallable_plugins);
wp_register_plugin_realpath(WP_PLUGIN_DIR . '/' . $pluginFile);
include_once(WP_PLUGIN_DIR . '/' . $pluginFile);
add_action("uninstall_{$pluginFile}", $callable);
/**
* Fires in uninstall_plugin() once the plugin has been uninstalled.
*
* The action concatenates the 'uninstall_' prefix with the basename of the
* plugin passed to uninstall_plugin() to create a dynamically-named action.
*
* @since 2.7.0
*/
do_action("uninstall_{$pluginFile}");
// Extra
// Extra
} else {
// The plugin was never activated so no need to call uninstallation hook
}
// store plugin in original file folder
$origFileManager = InstallerOrigFileMng::getInstance();
if (($pluginPath = $this->getPluginPath()) == false) {
Log::info('PLUGINS: can\'t remove plugin ' . $this->slug . ' because doesn\'t exist');
} else {
$origFileManager->addEntry($this->slug, $pluginPath, InstallerOrigFileMng::MODE_MOVE);
}
} catch (Exception | Error $e) {
$errorMsg = "**ERROR** The Inactive plugin " . $this->name . " can't be deleted";
$longMsg = 'Please delete the plugin ' . $this->name . ' (' . $this->slug . ') manually' . PHP_EOL .
'Exception message: ' . $e->getMessage() . PHP_EOL .
'Trace: ' . $e->getTraceAsString();
Log::info($errorMsg);
$nManager->addFinalReportNotice([
'shortMsg' => $errorMsg,
'level' => DUPX_NOTICE_ITEM::HARD_WARNING,
'longMsg' => $longMsg,
'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_PRE,
'sections' => 'plugins',
]);
return false;
}
error_reporting($level);
return true;
}
}

View File

@@ -0,0 +1,622 @@
<?php
/**
* @package Duplicator/Installer
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Core\Deploy\Plugins;
use Duplicator\Installer\Core\Params\PrmMng;
use Duplicator\Installer\Utils\Log\Log;
use DUPX_ArchiveConfig;
use DUPX_DB;
use DUPX_DB_Functions;
use Duplicator\Installer\Core\InstState;
use Duplicator\Libs\Snap\SnapURL;
use DUPX_NOTICE_ITEM;
use DUPX_NOTICE_MANAGER;
use Exception;
/**
* Original installer files manager
* singleton class
*/
final class PluginsManager
{
const SLUG_WOO_ADMIN = 'woocommerce-admin/woocommerce-admin.php';
const SLUG_SIMPLE_SSL = 'really-simple-ssl/rlrsssl-really-simple-ssl.php';
const SLUG_ONE_CLICK_SSL = 'one-click-ssl/ssl.php';
const SLUG_WP_FORCE_SSL = 'wp-force-ssl/wp-force-ssl.php';
const SLUG_RECAPTCHA = 'simple-google-recaptcha/simple-google-recaptcha.php';
const SLUG_WPBAKERY_PAGE_BUILDER = 'js_composer/js_composer.php';
const SLUG_DUPLICATOR_PRO = 'duplicator-pro/duplicator-pro.php';
const SLUG_DUPLICATOR_LITE = 'duplicator/duplicator.php';
const SLUG_DUPLICATOR_TESTER = 'duplicator-tester-plugin/duplicator-tester.php';
const SLUG_POPUP_MAKER = 'popup-maker/popup-maker.php';
const SLUG_JETPACK = 'jetpack/jetpack.php';
const SLUG_WP_ROCKET = 'wp-rocket/wp-rocket.php';
const SLUG_BETTER_WP_SECURITY = 'better-wp-security/better-wp-security.php';
const SLUG_HTTPS_REDIRECTION = 'https-redirection/https-redirection.php';
const SLUG_LOGIN_NOCAPTCHA = 'login-recaptcha/login-nocaptcha.php';
const SLUG_GOOGLE_CAPTCHA = 'google-captcha/google-captcha.php';
const SLUG_ADVANCED_CAPTCHA = 'advanced-google-recaptcha/advanced-google-recaptcha.php';
const OPTION_ACTIVATE_PLUGINS = 'dupli_opt_activate_plugins_after_installation';
const FORCE_ACTIVATE_PLUGINS = [
self::SLUG_DUPLICATOR_PRO,
self::SLUG_DUPLICATOR_TESTER,
];
/** @var ?self */
private static $instance;
/** @var PluginItem[] */
private $plugins = [];
/** @var PluginItem[] */
private $unistallList = [];
/** @var PluginCustomActions[] */
private $customPluginsActions = [];
/**
*
* @return self
*/
public static function getInstance()
{
if (is_null(self::$instance)) {
self::$instance = new self();
}
return self::$instance;
}
/**
*
* @return void
*/
private function __construct()
{
foreach (DUPX_ArchiveConfig::getInstance()->wpInfo->plugins as $pluginInfo) {
$this->plugins[$pluginInfo->slug] = new PluginItem((array)$pluginInfo);
}
$this->setCustomPluginsActions();
Log::info('CONSTRUCT PLUGINS OBJECTS: ' . Log::v2str($this->plugins), Log::LV_HARD_DEBUG);
}
/**
* This method prepares customPluginActions for further processing
*
* @return void
*/
private function setCustomPluginsActions(): void
{
if (InstState::isAddSiteOnMultisite()) {
$default = PluginCustomActions::BY_DEFAULT_DISABLED;
$afterLogin = false;
$longMsg = 'The plugin is disabled in the single site because it is active on the network.';
} else {
$default = PluginCustomActions::BY_DEFAULT_ENABLED;
$afterLogin = true;
$longMsg = '';
}
$this->customPluginsActions[self::SLUG_DUPLICATOR_PRO] = new PluginCustomActions(
self::SLUG_DUPLICATOR_PRO,
$default,
$afterLogin,
$longMsg
);
$this->customPluginsActions[self::SLUG_DUPLICATOR_TESTER] = new PluginCustomActions(
self::SLUG_DUPLICATOR_TESTER,
$default,
$afterLogin,
$longMsg
);
$this->customPluginsActions[self::SLUG_DUPLICATOR_LITE] = new PluginCustomActions(
self::SLUG_DUPLICATOR_LITE,
PluginCustomActions::BY_DEFAULT_DISABLED,
false,
'Duplicator LITE has been deactivated because in the new versions it is not possible to ' .
'have Duplicator LITE active at the same time as PRO.'
);
$longMsg = "This plugin is deactivated by default automatically. "
. "<strong>You must reactivate from the WordPress admin panel after completing the installation</strong> "
. "or from the plugins tab."
. " Your site's frontend will render properly after reactivating the plugin.";
$this->customPluginsActions[self::SLUG_WPBAKERY_PAGE_BUILDER] = new PluginCustomActions(
self::SLUG_WPBAKERY_PAGE_BUILDER,
PluginCustomActions::BY_DEFAULT_DISABLED,
true,
$longMsg
);
$this->customPluginsActions[self::SLUG_JETPACK] = new PluginCustomActions(
self::SLUG_JETPACK,
PluginCustomActions::BY_DEFAULT_DISABLED,
true,
$longMsg
);
$longMsg = "This plugin is deactivated by default automatically due to issues that one may encounter when migrating. "
. "<strong>You must reactivate from the WordPress admin panel after completing the installation</strong> "
. "or from the plugins tab."
. " Your site's frontend will render properly after reactivating the plugin.";
$this->customPluginsActions[self::SLUG_POPUP_MAKER] = new PluginCustomActions(
self::SLUG_POPUP_MAKER,
PluginCustomActions::BY_DEFAULT_DISABLED,
true,
$longMsg
);
$this->customPluginsActions[self::SLUG_WP_ROCKET] = new PluginCustomActions(
self::SLUG_WP_ROCKET,
PluginCustomActions::BY_DEFAULT_DISABLED,
true,
$longMsg
);
$longMsg = "This plugin is deactivated by default automatically due to issues that one may encounter when migrating. "
. "<strong>You must reactivate from the WordPress admin panel after completing the installation</strong> "
. "or from the plugins tab.";
$this->customPluginsActions[self::SLUG_WOO_ADMIN] = new PluginCustomActions(
self::SLUG_WOO_ADMIN,
PluginCustomActions::BY_DEFAULT_DISABLED,
true,
$longMsg
);
$this->customPluginsActions[self::SLUG_BETTER_WP_SECURITY] = new PluginCustomActions(
self::SLUG_BETTER_WP_SECURITY,
PluginCustomActions::BY_DEFAULT_DISABLED,
true,
$longMsg
);
}
/**
*
* @return PluginItem[]
*/
public function getPlugins()
{
return $this->plugins;
}
/**
* @return string[]
*/
public function getDropInsPaths()
{
static $dropInsPaths = null;
if (is_null($dropInsPaths)) {
$dropInsPaths = [];
foreach ($this->plugins as $plugin) {
if ($plugin->isDropIns()) {
$dropInsPaths[] = $plugin->getPluginArchivePath();
}
}
Log::info('DROP INS PATHS: ' . Log::v2str($dropInsPaths));
}
return $dropInsPaths;
}
/**
* This function performs status checks on plugins and disables those that must disable creating user messages
*
* @param int|null $subsiteId ID of a subsite
* @return void
*/
public function preViewChecks($subsiteId = null): void
{
$noticeManager = DUPX_NOTICE_MANAGER::getInstance();
$paramsManager = PrmMng::getInstance();
if (InstState::isRestoreBackup()) {
return;
}
$activePlugins = $paramsManager->getValue(PrmMng::PARAM_PLUGINS);
$saveParams = false;
foreach ($this->customPluginsActions as $slug => $customPlugin) {
if (!isset($this->plugins[$slug])) {
continue;
}
switch ($customPlugin->byDefaultStatus()) {
case PluginCustomActions::BY_DEFAULT_DISABLED:
if (($delKey = array_search($slug, $activePlugins)) !== false) {
$saveParams = true;
unset($activePlugins[$delKey]);
$noticeManager->addNextStepNotice([
'shortMsg' => 'Plugin ' . $this->plugins[$slug]->name . ' disabled by default',
'level' => DUPX_NOTICE_ITEM::NOTICE,
'longMsg' => $customPlugin->byDefaultMessage(),
'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_HTML,
'sections' => 'plugins',
], DUPX_NOTICE_MANAGER::ADD_UNIQUE, 'custom_plugin_action' . $slug);
}
break;
case PluginCustomActions::BY_DEFAULT_ENABLED:
if (!in_array($slug, $activePlugins)) {
$saveParams = true;
$activePlugins[] = $slug;
$noticeManager->addNextStepNotice([
'shortMsg' => 'Plugin ' . $this->plugins[$slug]->name . ' enabled by default',
'level' => DUPX_NOTICE_ITEM::NOTICE,
'longMsg' => $customPlugin->byDefaultMessage(),
'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_HTML,
'sections' => 'plugins',
], DUPX_NOTICE_MANAGER::ADD_UNIQUE, 'custom_plugin_action' . $slug);
}
break;
case PluginCustomActions::BY_DEFAULT_AUTO:
Log::info("AUTO ACTION WAS TRIGGERED");
$saveParams = false;
if (!$this->plugins[$slug]->isInactive($subsiteId) && $customPlugin->isEnableAfterLogin()) {
$this->plugins[$slug]->setActivationAction($subsiteId);
} elseif (!$customPlugin->isEnableAfterLogin()) {
$this->plugins[$slug]->setDeactivateAction(
$subsiteId,
'Deactivated plugin: ' . $this->plugins[$slug]->name,
$customPlugin->byDefaultMessage()
);
}
break;
default:
break;
}
}
if ($saveParams) {
$paramsManager->setValue(PrmMng::PARAM_PLUGINS, $activePlugins);
$paramsManager->save();
$noticeManager->saveNotices();
}
}
/**
* @param integer $subsiteId ID of a subsite
*
* @return int[]
*/
public function getStatusCounts($subsiteId = -1): array
{
$result = [
PluginItem::STATUS_MUST_USE => 0,
PluginItem::STATUS_DROP_INS => 0,
PluginItem::STATUS_NETWORK_ACTIVE => 0,
PluginItem::STATUS_ACTIVE => 0,
PluginItem::STATUS_INACTIVE => 0,
];
foreach ($this->plugins as $plugin) {
$result[$plugin->getOrgiStatus($subsiteId)]++;
}
return $result;
}
/**
* @param integer $subsiteId ID of a subsite
*
* @return string[]
*/
public function getDefaultActivePluginsList($subsiteId = -1): array
{
$result = [];
$networkInstall = InstState::isNewSiteIsMultisite();
foreach ($this->plugins as $plugin) {
if ($networkInstall) {
if ($plugin->isNetworkActive() || $plugin->isMustUse() || $plugin->isDropIns()) {
$result[] = $plugin->getSlug();
}
} else {
if (!$plugin->isInactive($subsiteId)) {
$result[] = $plugin->getSlug();
}
}
}
return $result;
}
/**
* return alla plugins slugs list
*
* @return string[]
*/
public function getAllPluginsSlugs()
{
return array_keys($this->plugins);
}
/**
* Return all archive plugins archive paths related to target site, folder if plugin has a folder or file if is a single file plugin (like drop-ins)
*
* @param bool $filterNotExists if true filter not exists folders
* @param bool $filterNotInArchive if true filter that plugins files that are not in archive
*
* @return string[]
*/
public function getAllPluginsPaths($filterNotExists = true, $filterNotInArchive = false): array
{
$result = [];
foreach ($this->plugins as $plugin) {
if (($path = $plugin->getPluginPath($filterNotExists)) == false) {
continue;
}
if ($filterNotInArchive && !$plugin->isInArchive) {
continue;
}
$result[] = $path;
}
return $result;
}
/**
* @param string[] $plugins List of plugins
* @param integer $subsiteId ID of a subsite
*
* @return void
*/
public function setActions($plugins, $subsiteId = -1): void
{
Log::info('FUNCTION [' . __FUNCTION__ . ']: plugins ' . Log::v2str($plugins), Log::LV_DEBUG);
$networkInstall = InstState::isNewSiteIsMultisite();
foreach ($this->plugins as $slug => $plugin) {
$deactivate = false;
if ($plugin->isForceDisabled()) {
$deactivate = true;
} else {
if ($networkInstall) {
if (!$this->plugins[$slug]->isNetworkInactive() && !in_array($slug, $plugins)) {
$deactivate = true;
}
} else {
if (!$this->plugins[$slug]->isInactive($subsiteId) && !in_array($slug, $plugins)) {
$deactivate = true;
}
}
}
if ($deactivate) {
$this->plugins[$slug]->setDeactivateAction($subsiteId, null, null, $networkInstall);
}
}
foreach ($plugins as $slug) {
if (isset($this->plugins[$slug])) {
$this->plugins[$slug]->setActivationAction($subsiteId, $networkInstall);
}
}
///Start
$paramManager = PrmMng::getInstance();
$casesToHandle = [
[
'slugs' => [
self::SLUG_SIMPLE_SSL,
self::SLUG_WP_FORCE_SSL,
self::SLUG_HTTPS_REDIRECTION,
],
'longMsg' => "The plugin '%name%' has been deactivated because you are migrating from SSL (HTTPS) to Non-SSL (HTTP).<br>" .
"If it was not deactivated, you would not be able to login.",
'info' => '%name% [as Non-SSL installation] will be deactivated',
'condition' => !SnapURL::isCurrentUrlSSL(),
],
[
'slugs' => [
self::SLUG_RECAPTCHA,
self::SLUG_LOGIN_NOCAPTCHA,
self::SLUG_GOOGLE_CAPTCHA,
self::SLUG_ADVANCED_CAPTCHA,
],
'longMsg' => "The plugin '%name%' has been deactivated because reCaptcha requires a site key which is bound to the site's address." .
"Your package site's address and installed site's address don't match. " .
"You can reactivate it after finishing with the installation.<br>" .
"<strong>Please do not forget to change the reCaptcha site key after activating it.</strong>",
'info' => '%name% [as package creation site URL and the installation site URL are different] will be deactivated',
'condition' => $paramManager->getValue(PrmMng::PARAM_SITE_URL_OLD) != $paramManager->getValue(PrmMng::PARAM_SITE_URL),
],
];
foreach ($casesToHandle as $case) {
foreach ($case['slugs'] as $slug) {
if (isset($this->plugins[$slug]) && $this->plugins[$slug]->isActive($subsiteId) && $case['condition']) {
$info = str_replace('%name%', $this->plugins[$slug]->name, $case['info']);
$longMsg = str_replace('%name%', $this->plugins[$slug]->name, $case['longMsg']);
Log::info($info, Log::LV_DEBUG);
$this->customPluginsActions[$slug] = new PluginCustomActions(
$slug,
PluginCustomActions::BY_DEFAULT_AUTO,
false,
$longMsg
);
}
}
}
///end
DUPX_NOTICE_MANAGER::getInstance()->saveNotices();
}
/**
* @param \mysqli $dbh Connection
* @param integer $subsiteId Subsite ID
*
* @return bool
*/
public function executeActions($dbh, $subsiteId = -1): bool
{
$activePluginsList = [];
$activateOnLoginPluginsList = [];
$removeInactivePlugins = PrmMng::getInstance()->getValue(PrmMng::PARAM_REMOVE_RENDUNDANT);
$this->unistallList = [];
if (InstState::isAddSiteOnMultisite()) {
Log::info('SKIP PLUGIN ACTION FOR ADD SITE IN MULTISITE');
return true;
}
$escapedTablePrefix = mysqli_real_escape_string(
$dbh,
PrmMng::getInstance()->getValue(PrmMng::PARAM_DB_TABLE_PREFIX)
);
$noticeManager = DUPX_NOTICE_MANAGER::getInstance();
Log::info('PLUGINS OBJECTS: ' . Log::v2str($this->plugins), Log::LV_HARD_DEBUG);
foreach ($this->customPluginsActions as $slug => $customPlugin) {
if (!isset($this->plugins[$slug])) {
continue;
}
if (!$this->plugins[$slug]->isInactive($subsiteId) && $customPlugin->isEnableAfterLogin()) {
$this->plugins[$slug]->setActivationAction($subsiteId);
}
}
foreach ($this->plugins as $plugin) {
// Force activate plugins are handled seperately
if (in_array($plugin->getSlug(), self::FORCE_ACTIVATE_PLUGINS)) {
continue;
}
$deactivated = false;
if ($plugin->deactivateAction) {
$plugin->deactivate();
// can't remove deactivate after login
$deactivated = true;
} elseif (InstState::isNewSiteIsMultisite()) {
if ($plugin->isNetworkActive()) {
$activePluginsList[$plugin->getSlug()] = time();
}
} else {
if ($plugin->isActive($subsiteId)) {
$activePluginsList[] = $plugin->getSlug();
}
}
if ($plugin->activateAction) {
$activateOnLoginPluginsList[] = $plugin->getSlug();
$noticeManager->addFinalReportNotice([
'shortMsg' => 'Activate ' . $plugin->name . ' after you login.',
'level' => DUPX_NOTICE_ITEM::NOTICE,
'sections' => 'plugins',
]);
} else {
// remove only if isn't activated
if ($removeInactivePlugins && ($plugin->isInactive($subsiteId) || $deactivated)) {
$this->unistallList[] = $plugin;
}
}
}
foreach (self::FORCE_ACTIVATE_PLUGINS as $slug) {
$this->forceActivatePlugin($slug, $activePluginsList);
}
Log::info('Active plugins: ' . Log::v2str($activePluginsList), Log::LV_DEFAULT);
$value = mysqli_real_escape_string($dbh, @serialize($activePluginsList));
if (InstState::isNewSiteIsMultisite()) {
$table = $escapedTablePrefix . 'sitemeta';
$query = "UPDATE `" . $table . "` SET meta_value = '" . $value . "' WHERE meta_key = 'active_sitewide_plugins'";
} else {
$optionTable = mysqli_real_escape_string($dbh, DUPX_DB_Functions::getOptionsTableName());
$query = "UPDATE `" . $optionTable . "` SET option_value = '" . $value . "' WHERE option_name = 'active_plugins' ";
}
if (!DUPX_DB::mysqli_query($dbh, $query)) {
$noticeManager->addFinalReportNotice([
'shortMsg' => 'QUERY ERROR: MySQL',
'level' => DUPX_NOTICE_ITEM::HARD_WARNING,
'longMsg' => "Error description: " . mysqli_error($dbh),
'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_DEFAULT,
'sections' => 'database',
]);
throw new Exception("Database error description: " . mysqli_error($dbh));
}
if (!InstState::isNewSiteIsMultisite()) {
$value = mysqli_real_escape_string($dbh, @serialize($activateOnLoginPluginsList));
$optionTable = mysqli_real_escape_string($dbh, DUPX_DB_Functions::getOptionsTableName());
$query = "INSERT INTO `" . $optionTable . "` (option_name, option_value)
VALUES('" . self::OPTION_ACTIVATE_PLUGINS . "','" . $value . "') ON DUPLICATE KEY UPDATE option_name=\"" . self::OPTION_ACTIVATE_PLUGINS . "\"";
if (!DUPX_DB::mysqli_query($dbh, $query)) {
$noticeManager->addFinalReportNotice([
'shortMsg' => 'QUERY ERROR: MySQL',
'level' => DUPX_NOTICE_ITEM::HARD_WARNING,
'longMsg' => "Error description: " . mysqli_error($dbh),
'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_DEFAULT,
'sections' => 'database',
]);
throw new Exception("Database error description: " . mysqli_error($dbh));
}
}
return true;
}
/**
* remove inactive plugins
* this method must calle after wp-config set
*
* @return void
*/
public function uninstallInactivePlugins(): void
{
Log::info('FUNCTION [' . __FUNCTION__ . ']: uninstall inactive plugins');
/** @var PluginItem $plugin */
foreach ($this->unistallList as $plugin) {
if ($plugin->uninstall()) {
Log::info("UNINSTALL PLUGIN " . Log::v2str($plugin->getSlug()) . ' DONE');
} else {
Log::info("UNINSTALL PLUGIN " . Log::v2str($plugin->getSlug()) . ' FAILED');
}
}
}
/**
* Force activate a plugin if not already in the active list.
*
* @param string $slug Plugin slug to activate
* @param array<int|string, int|string> $activePluginsList Active plugins list (passed by reference)
*
* @return void
*/
private function forceActivatePlugin(string $slug, array &$activePluginsList): void
{
if (!array_key_exists($slug, $this->plugins)) {
return;
}
$isMultisite = InstState::isNewSiteIsMultisite();
$pluginInList = $isMultisite
? array_key_exists($slug, $activePluginsList)
: in_array($slug, $activePluginsList, true);
if (!$pluginInList) {
if ($isMultisite) {
$activePluginsList[$slug] = time();
} else {
$activePluginsList[] = $slug;
}
}
}
}

View File

@@ -0,0 +1,653 @@
<?php
/**
* @package Duplicator/Installer
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Core\Deploy;
use DUPX_Extraction;
use Duplicator\Installer\Utils\Log\Log;
use Duplicator\Installer\Core\Params\PrmMng;
use Duplicator\Installer\Utils\InstallerOrigFileMng;
use Duplicator\Libs\Snap\SnapIO;
use DUPX_ArchiveConfig;
use DUPX_DB;
use DUPX_DB_Functions;
use Duplicator\Installer\Core\InstState;
use DUPX_NOTICE_ITEM;
use DUPX_NOTICE_MANAGER;
use DUPX_Package;
use DUPX_U;
use DUPX_WPConfig;
use Error;
use Exception;
use mysqli;
class ServerConfigs
{
const INSTALLER_HOST_ENTITY_PREFIX = 'installer_host_';
const CONFIG_ORIG_FILE_USERINI_ID = 'userini';
const CONFIG_ORIG_FILE_HTACCESS_ID = 'htaccess';
const CONFIG_ORIG_FILE_WPCONFIG_ID = 'wpconfig';
const CONFIG_ORIG_FILE_PHPINI_ID = 'phpini';
const CONFIG_ORIG_FILE_WEBCONFIG_ID = 'webconfig';
const CONFIG_ORIG_FILE_USERINI_ID_OVERWRITE_SITE = 'installer_host_userini';
const CONFIG_ORIG_FILE_HTACCESS_ID_OVERWRITE_SITE = 'installer_host_htaccess';
const CONFIG_ORIG_FILE_WPCONFIG_ID_OVERWRITE_SITE = 'installer_host_wpconfig';
const CONFIG_ORIG_FILE_PHPINI_ID_OVERWRITE_SITE = 'installer_host_phpini';
const CONFIG_ORIG_FILE_WEBCONFIG_ID_OVERWRITE_SITE = 'installer_host_webconfig';
const ACTION_WPCONF_MODIFY = 'modify';
const ACTION_WPCONF_NEW = 'new';
const ACTION_WPCONF_NOTHING = 'nothing';
/**
* Common timestamp of all members of this class
*
* @return string|false
*/
public static function getFixedTimestamp()
{
static $time = null;
if (is_null($time)) {
$time = date("ymdHis");
}
return $time;
}
/**
* Creates a copy of the original server config file and resets the original to blank
*
* @param string $rootPath The root path to the location of the server config files
*
* @return void
*/
public static function reset($rootPath): void
{
$rootPath = SnapIO::trailingslashit($rootPath);
$paramsManager = PrmMng::getInstance();
Log::info("\n*** RESET CONFIG FILES IN CURRENT HOSTING");
switch ($paramsManager->getValue(PrmMng::PARAM_WP_CONFIG)) {
case self::ACTION_WPCONF_NEW:
case self::ACTION_WPCONF_MODIFY:
if (InstState::isBridgeInstall()) {
// if bridge wp-config must be mantained
break;
}
if (self::runReset($rootPath . 'wp-config.php', self::CONFIG_ORIG_FILE_WPCONFIG_ID) === false) {
$paramsManager->setValue(PrmMng::PARAM_WP_CONFIG, 'nothing');
}
break;
case self::ACTION_WPCONF_NOTHING:
break;
}
switch ($paramsManager->getValue(PrmMng::PARAM_HTACCESS_CONFIG)) {
case 'new':
case 'original':
if (self::runReset($rootPath . '.htaccess', self::CONFIG_ORIG_FILE_HTACCESS_ID) === false) {
$paramsManager->setValue(PrmMng::PARAM_HTACCESS_CONFIG, 'nothing');
}
break;
case 'nothing':
break;
}
switch ($paramsManager->getValue(PrmMng::PARAM_OTHER_CONFIG)) {
case 'new':
case 'original':
if (self::runReset($rootPath . 'web.config', self::CONFIG_ORIG_FILE_WEBCONFIG_ID) === false) {
$paramsManager->setValue(PrmMng::PARAM_OTHER_CONFIG, 'nothing');
}
if (self::runReset($rootPath . '.user.ini', self::CONFIG_ORIG_FILE_USERINI_ID) === false) {
$paramsManager->setValue(PrmMng::PARAM_OTHER_CONFIG, 'nothing');
}
if (self::runReset($rootPath . 'php.ini', self::CONFIG_ORIG_FILE_PHPINI_ID) === false) {
$paramsManager->setValue(PrmMng::PARAM_OTHER_CONFIG, 'nothing');
}
break;
case 'nothing':
break;
}
$paramsManager->save();
Log::info("*** RESET CONFIG FILES END");
}
/**
* Set config files in target root folder
*
* @param string $rootPath target root path
*
* @return void
*/
public static function setFiles($rootPath): void
{
$paramsManager = PrmMng::getInstance();
$origFiles = InstallerOrigFileMng::getInstance();
Log::info("SET CONFIG FILES");
$entryKey = self::CONFIG_ORIG_FILE_WPCONFIG_ID;
switch ($paramsManager->getValue(PrmMng::PARAM_WP_CONFIG)) {
case 'new':
if (SnapIO::copy(DUPX_Package::getWpconfigSamplePath(), DUPX_WPConfig::getWpConfigPath()) === false) {
DUPX_NOTICE_MANAGER::getInstance()->addFinalReportNotice([
'shortMsg' => 'Can\'t reset wp-config.php to wp-config-sample.php',
'level' => DUPX_NOTICE_ITEM::CRITICAL,
'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_DEFAULT,
'longMsg' => 'Target file entry ' . Log::v2str(DUPX_WPConfig::getWpConfigPath()),
'sections' => 'general',
]);
} else {
Log::info("Copy wp-config-sample.php to target:" . DUPX_WPConfig::getWpConfigPath());
}
break;
case 'modify':
if (SnapIO::copy($origFiles->getEntryStoredPath($entryKey), DUPX_WPConfig::getWpConfigPath()) === false) {
DUPX_NOTICE_MANAGER::getInstance()->addFinalReportNotice([
'shortMsg' => 'Unable to restore the original ' . $entryKey . '.php entries. Please check the file permission on this server.',
'level' => DUPX_NOTICE_ITEM::CRITICAL,
'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_DEFAULT,
'longMsg' => 'Target file entry ' . Log::v2str(DUPX_WPConfig::getWpConfigPath()),
'sections' => 'general',
]);
} else {
Log::info("Retained original entry " . $entryKey . " target:" . DUPX_WPConfig::getWpConfigPath());
}
break;
case 'nothing':
break;
}
$entryKey = self::CONFIG_ORIG_FILE_HTACCESS_ID;
switch ($paramsManager->getValue(PrmMng::PARAM_HTACCESS_CONFIG)) {
case 'new':
$targetHtaccess = self::getHtaccessTargetPath();
if (SnapIO::touch($targetHtaccess) === false) {
DUPX_NOTICE_MANAGER::getInstance()->addFinalReportNotice([
'shortMsg' => 'Can\'t create new .htaccess file',
'level' => DUPX_NOTICE_ITEM::CRITICAL,
'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_DEFAULT,
'longMsg' => 'Target file entry ' . $targetHtaccess,
'sections' => 'general',
]);
} else {
Log::info("New .htaccess file created:" . $targetHtaccess);
}
break;
case 'original':
if (($storedHtaccess = $origFiles->getEntryStoredPath($entryKey)) === false) {
Log::info("Retained original entry. .htaccess doesn\'t exist in original site");
break;
}
$targetHtaccess = self::getHtaccessTargetPath();
if (SnapIO::copy($storedHtaccess, $targetHtaccess) === false) {
DUPX_NOTICE_MANAGER::getInstance()->addFinalReportNotice([
'shortMsg' => 'Unable to restore the original ' . $entryKey . ' entries. Please check the file permission on this server.',
'level' => DUPX_NOTICE_ITEM::HARD_WARNING,
'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_DEFAULT,
'longMsg' => 'Target file entry ' . Log::v2str($targetHtaccess),
'sections' => 'general',
]);
} else {
Log::info("Retained original entry " . $entryKey . " target:" . $targetHtaccess);
}
break;
case 'nothing':
break;
}
switch ($paramsManager->getValue(PrmMng::PARAM_OTHER_CONFIG)) {
case 'new':
if ($origFiles->getEntry(self::CONFIG_ORIG_FILE_WEBCONFIG_ID_OVERWRITE_SITE)) {
//IIS: This is reset because on some instances of IIS having old values cause issues
//Recommended fix for users who want it because errors are triggered is to have
//them check the box for ignoring the web.config files on step 1 of installer
$xml_contents = '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
$xml_contents .= "<!-- Reset by Duplicator Installer. Original can be found in the original_files_ folder-->\n";
$xml_contents .= "<configuration></configuration>\n";
if (file_put_contents($rootPath . "/web.config", $xml_contents) === false) {
Log::info('RESET: can\'t create a new empty web.config');
}
}
break;
case 'original':
$entries = [
self::CONFIG_ORIG_FILE_USERINI_ID,
self::CONFIG_ORIG_FILE_WEBCONFIG_ID,
self::CONFIG_ORIG_FILE_PHPINI_ID,
];
foreach ($entries as $entryKey) {
if ($origFiles->getEntry($entryKey) !== false) {
if (SnapIO::copy($origFiles->getEntryStoredPath($entryKey), $origFiles->getEntryTargetPath($entryKey)) === false) {
DUPX_NOTICE_MANAGER::getInstance()->addFinalReportNotice([
'shortMsg' => 'Unable to restore the original ' . $entryKey . ' entries. Please check the file permission on this server.',
'level' => DUPX_NOTICE_ITEM::HARD_WARNING,
'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_DEFAULT,
'longMsg' => 'Target file entry ' . Log::v2str($origFiles->getEntryTargetPath($entryKey)),
'sections' => 'general',
]);
} else {
Log::info("Retained original entry " . $entryKey . " target:" . $origFiles->getEntryTargetPath($entryKey));
}
}
}
break;
case 'nothing':
break;
}
DUPX_NOTICE_MANAGER::getInstance()->saveNotices();
}
/**
* Get htaccess target path
*
* @return string
*/
public static function getHtaccessTargetPath()
{
if (($targetEnty = InstallerOrigFileMng::getInstance()->getEntryTargetPath(self::CONFIG_ORIG_FILE_HTACCESS_ID)) !== false) {
return $targetEnty;
} else {
return PrmMng::getInstance()->getValue(PrmMng::PARAM_PATH_NEW) . '/.htaccess';
}
}
/**
* Creates a copy of the original server config file and resets the original to blank per file
*
* @param string $filePath file path to store
* @param string $storedName if not false rename
*
* @return bool Returns true if the file was backed-up and reset or there was no file to reset
*/
private static function runReset(string $filePath, string $storedName): bool
{
$fileName = basename($filePath);
try {
if (file_exists($filePath)) {
if (!SnapIO::chmod($filePath, 'u+rw') || !is_readable($filePath) || !is_writable($filePath)) {
throw new Exception("RESET CONFIG FILES: permissions error on file config path " . $filePath);
}
$origFiles = InstallerOrigFileMng::getInstance();
$filePath = SnapIO::safePathUntrailingslashit($filePath);
Log::info("RESET CONFIG FILES: I'M GOING TO MOVE CONFIG FILE " . Log::v2str($fileName) . " IN ORIGINAL FOLDER");
if (
$origFiles->addEntry(
self::INSTALLER_HOST_ENTITY_PREFIX . $storedName,
$filePath,
InstallerOrigFileMng::MODE_MOVE,
self::INSTALLER_HOST_ENTITY_PREFIX . $storedName
)
) {
Log::info("\tCONFIG FILE HAS BEEN RESET");
} else {
throw new Exception("can\'t stored file " . Log::v2str($fileName) . " in orginal file folder");
}
} else {
Log::info("RESET CONFIG FILES: " . Log::v2str($fileName) . " does not exist, no need for rest", Log::LV_DETAILED);
}
} catch (Exception | Error $e) {
Log::logException($e, Log::LV_DEFAULT, 'RESET CONFIG FILES ERROR: ');
DUPX_NOTICE_MANAGER::getInstance()->addBothNextAndFinalReportNotice([
'shortMsg' => 'Can\'t reset config file ' . Log::v2str($fileName) . ' so it will not be modified.',
'level' => DUPX_NOTICE_ITEM::HARD_WARNING,
'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_DEFAULT,
'longMsg' => 'Message: ' . $e->getMessage(),
'sections' => 'general',
]);
return false;
}
return true;
}
/**
* Return wp-config path stored in orig folder of target site
*
* @return false|string false if local config don't exists
*/
public static function getWpConfigLocalStoredPath()
{
return InstallerOrigFileMng::getInstance()->getEntryStoredPath(self::CONFIG_ORIG_FILE_WPCONFIG_ID_OVERWRITE_SITE);
}
/**
* Return wp-config path stored in orig folder of source site
*
* @return false|string false if old wp config does not exist
*/
public static function getSourceWpConfigPath()
{
return InstallerOrigFileMng::getInstance()->getEntryStoredPath(self::CONFIG_ORIG_FILE_WPCONFIG_ID);
}
/**
* Get AddHandler line from existing WP .htaccess file
*
* @return string
*/
private static function getOldHtaccessAddhandlerLine(): string
{
$origFiles = InstallerOrigFileMng::getInstance();
$backupHtaccessPath = $origFiles->getEntryStoredPath(self::CONFIG_ORIG_FILE_HTACCESS_ID_OVERWRITE_SITE);
Log::info("Installer Host Htaccess path: " . $backupHtaccessPath, Log::LV_DEBUG);
if ($backupHtaccessPath !== false && file_exists($backupHtaccessPath)) {
$htaccessContent = file_get_contents($backupHtaccessPath);
if (!empty($htaccessContent)) {
// match and trim non commented line "AddHandler application/x-httpd-XXXX .php" case insenstive
$re = '/^[\s\t]*[^#]?[\s\t]*(AddHandler[\s\t]+.+\.php[ \t]?.*?)[\s\t]*$/mi';
$matches = [];
if (preg_match($re, $htaccessContent, $matches)) {
return "\n" . $matches[1];
}
}
}
return '';
}
/**
* Sets up the web config file based on the inputs from the installer forms.
*
* @param mysqli $dbh The database connection handle for this request
* @param string $path The path to the config file
*
* @return void
*/
public static function setup(\mysqli $dbh, $path): void
{
Log::info("\nWEB SERVER CONFIGURATION FILE UPDATED:");
$paramsManager = PrmMng::getInstance();
$htAccessPath = "{$path}/.htaccess";
$mu_generation = DUPX_ArchiveConfig::getInstance()->mu_generation;
// SKIP HTACCESS
$skipHtaccessConfigVals = [
'nothing',
'original',
];
if (in_array($paramsManager->getValue(PrmMng::PARAM_HTACCESS_CONFIG), $skipHtaccessConfigVals)) {
if (!InstState::isRestoreBackup() && !InstState::isAddSiteOnMultisite()) {
// on restore packup mode no warning needed
$longMsg = 'Retaining the original .htaccess files may cause '
. 'issues with the initial setup of your site. '
. 'If you encounter any problems, check the contents of the configuration files manually or '
. 'reinstall the site again by changing the configuration file settings.';
DUPX_NOTICE_MANAGER::getInstance()->addFinalReportNotice([
'shortMsg' => 'Can\'t update new .htaccess file',
'level' => DUPX_NOTICE_ITEM::NOTICE,
'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_DEFAULT,
'longMsg' => $longMsg,
'sections' => 'general',
]);
}
return;
}
$timestamp = date("Y-m-d H:i:s");
$post_url_new = $paramsManager->getValue(PrmMng::PARAM_URL_NEW);
$newdata = parse_url($post_url_new);
$newpath = DUPX_U::addSlash($newdata['path'] ?? "");
$update_msg = "# This file was updated by Duplicator Pro on {$timestamp}.\n";
$update_msg .= "# See the original_files_ folder for the original source_site_htaccess file.";
$update_msg .= self::getOldHtaccessAddhandlerLine();
switch (InstState::getInstType()) {
case InstState::TYPE_SINGLE:
case InstState::TYPE_STANDALONE:
case InstState::TYPE_RBACKUP_SINGLE:
case InstState::TYPE_RECOVERY_SINGLE:
$tmp_htaccess = self::htAcccessNoMultisite($update_msg, $newpath, $dbh);
Log::info("- Preparing .htaccess file with basic setup.");
break;
case InstState::TYPE_MSUBDOMAIN:
case InstState::TYPE_RBACKUP_MSUBDOMAIN:
case InstState::TYPE_RECOVERY_MSUBDOMAIN:
if ($mu_generation == 1) {
$tmp_htaccess = self::htAccessSubdomainPre53($update_msg, $newpath);
} else {
$tmp_htaccess = self::htAccessSubdomain($update_msg, $newpath);
}
Log::info("- Preparing .htaccess file with multisite subdomain setup.");
break;
case InstState::TYPE_MSUBFOLDER:
case InstState::TYPE_RBACKUP_MSUBFOLDER:
case InstState::TYPE_RECOVERY_MSUBFOLDER:
if ($mu_generation == 1) {
$tmp_htaccess = self::htAccessSubdirectoryPre35($update_msg, $newpath);
} else {
$tmp_htaccess = self::htAccessSubdirectory($update_msg, $newpath);
}
Log::info("- Preparing .htaccess file with multisite subdirectory setup.");
break;
case InstState::TYPE_SINGLE_ON_SUBDOMAIN:
case InstState::TYPE_SINGLE_ON_SUBFOLDER:
case InstState::TYPE_SUBSITE_ON_SUBDOMAIN:
case InstState::TYPE_SUBSITE_ON_SUBFOLDER:
case InstState::TYPE_NOT_SET:
throw new Exception('Cannot change setup with current installation type [' . InstState::getInstType() . ']');
default:
throw new Exception('Unknown mode');
}
if (file_exists($htAccessPath) && SnapIO::chmod($htAccessPath, 'u+rw') === false) {
Log::info("WARNING: Unable to update htaccess file permessition.");
DUPX_NOTICE_MANAGER::getInstance()->addFinalReportNotice([
'shortMsg' => 'Can\'t update new htaccess file',
'level' => DUPX_NOTICE_ITEM::CRITICAL,
'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_DEFAULT,
'longMsg' => 'Unable to update the .htaccess file! Please check the permission on the root directory and make sure the .htaccess exists.',
'sections' => 'general',
]);
} elseif (file_put_contents($htAccessPath, $tmp_htaccess) === false) {
Log::info("WARNING: Unable to update the .htaccess file! Please check the permission on the root directory and make sure the .htaccess exists.");
DUPX_NOTICE_MANAGER::getInstance()->addFinalReportNotice([
'shortMsg' => 'Can\'t update new htaccess file',
'level' => DUPX_NOTICE_ITEM::CRITICAL,
'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_DEFAULT,
'longMsg' => 'Unable to update the .htaccess file! Please check the permission on the root directory and make sure the .htaccess exists.',
'sections' => 'general',
]);
} else {
DUPX_Extraction::setPermsFromParams($htAccessPath);
Log::info("HTACCESS FILE - Successfully updated the .htaccess file setting.");
}
}
/**
* Get htaccess content no multisite
*
* @param string $update_msg update message
* @param string $newpath target path
* @param mysqli $dbh dtabase connection
*
* @return string
*/
private static function htAcccessNoMultisite(string $update_msg, string $newpath, \mysqli $dbh): string
{
$result = '';
// no multisite
$empty_htaccess = false;
$optonsTable = mysqli_real_escape_string($dbh, DUPX_DB_Functions::getOptionsTableName());
$query_result = DUPX_DB::mysqli_query($dbh, "SELECT option_value FROM `" . $optonsTable . "` WHERE option_name = 'permalink_structure' ");
if ($query_result) {
$row = @mysqli_fetch_array($query_result);
if ($row != null) {
$permalink_structure = trim($row[0]);
$empty_htaccess = empty($permalink_structure);
}
}
if ($empty_htaccess) {
Log::info('NO PERMALINK STRUCTURE FOUND: set htaccess without directives');
$result = <<<EMPTYHTACCESS
{$update_msg}
# BEGIN WordPress
# The directives (lines) between `BEGIN WordPress` and `END WordPress` are
# dynamically generated, and should only be modified via WordPress filters.
# Any changes to the directives between these markers will be overwritten.
# END WordPress
EMPTYHTACCESS;
} else {
$result = <<<HTACCESS
{$update_msg}
# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase {$newpath}
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . {$newpath}index.php [L]
</IfModule>
# END WordPress
HTACCESS;
}
return $result;
}
/**
* Get htaccess content subdomain multisite pre WP 5.3
*
* @param string $update_msg update message
* @param string $newpath target path
*
* @return string
*/
private static function htAccessSubdomainPre53(string $update_msg, $newpath): string
{
// Pre WordPress 3.5
$result = <<<HTACCESS
{$update_msg}
# BEGIN WordPress (Pre 3.5 Multisite Subdomain)
RewriteEngine On
RewriteBase {$newpath}
RewriteRule ^index\.php$ - [L]
# uploaded files
RewriteRule ^files/(.+) wp-includes/ms-files.php?file=$1 [L]
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
RewriteRule . index.php [L]
# END WordPress
HTACCESS;
return $result;
}
/**
* Get htaccess content subdomain multisite
*
* @param string $update_msg update message
* @param string $newpath target path
*
* @return string
*/
private static function htAccessSubdomain(string $update_msg, $newpath): string
{
// 3.5+
$result = <<<HTACCESS
{$update_msg}
# BEGIN WordPress (3.5+ Multisite Subdomain)
RewriteEngine On
RewriteBase {$newpath}
RewriteRule ^index\.php$ - [L]
# add a trailing slash to /wp-admin
RewriteRule ^wp-admin$ wp-admin/ [R=301,L]
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
RewriteRule ^(wp-(content|admin|includes).*) $1 [L]
RewriteRule ^(.*\.php)$ $1 [L]
RewriteRule . index.php [L]
# END WordPress
HTACCESS;
return $result;
}
/**
* Get htaccess content subfolder multisite pre WP 5.3
*
* @param string $update_msg update message
* @param string $newpath target path
*
* @return string
*/
private static function htAccessSubdirectoryPre35(string $update_msg, $newpath): string
{
// Pre 3.5
$result = <<<HTACCESS
{$update_msg}
# BEGIN WordPress (Pre 3.5 Multisite Subdirectory)
RewriteEngine On
RewriteBase {$newpath}
RewriteRule ^index\.php$ - [L]
# uploaded files
RewriteRule ^([_0-9a-zA-Z-]+/)?files/(.+) wp-includes/ms-files.php?file=$2 [L]
# add a trailing slash to /wp-admin
RewriteRule ^([_0-9a-zA-Z-]+/)?wp-admin$ $1wp-admin/ [R=301,L]
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
RewriteRule ^[_0-9a-zA-Z-]+/(wp-(content|admin|includes).*) $1 [L]
RewriteRule ^[_0-9a-zA-Z-]+/(.*\.php)$ $1 [L]
RewriteRule . index.php [L]
# END WordPress
HTACCESS;
return $result;
}
/**
* Get htaccess content subfolder multisite
*
* @param string $update_msg update message
* @param string $newpath target path
*
* @return string
*/
private static function htAccessSubdirectory(string $update_msg, $newpath): string
{
return <<<HTACCESS
{$update_msg}
# BEGIN WordPress (3.5+ Multisite Subdirectory)
RewriteEngine On
RewriteBase {$newpath}
RewriteRule ^index\.php$ - [L]
# add a trailing slash to /wp-admin
RewriteRule ^([_0-9a-zA-Z-]+/)?wp-admin$ $1wp-admin/ [R=301,L]
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^ - [L]
RewriteRule ^([_0-9a-zA-Z-]+/)?(wp-(content|admin|includes).*) $2 [L]
RewriteRule ^([_0-9a-zA-Z-]+/)?(.*\.php)$ $2 [L]
RewriteRule . index.php [L]
# END WordPress
HTACCESS;
}
}

View File

@@ -0,0 +1,125 @@
<?php
namespace Duplicator\Installer\Core\Deploy;
use Duplicator\Installer\Core\Params\Descriptors\ParamDescUsers;
use Duplicator\Installer\Core\Params\PrmMng;
use Duplicator\Installer\Utils\Log\Log;
use Duplicator\Libs\Snap\SnapDB;
use DUPX_ArchiveConfig;
use DUPX_DB;
use DUPX_DB_Functions;
use DUPX_UpdateEngine;
use DUPX_WPConfig;
use mysqli;
/**
* Standalone migration functions
*/
class Standalone
{
/**
*
* @param int $subsiteId subsite ID
* @param mysqli $dbh database connection
*
* @return void
*/
public static function updateOptionsTable($subsiteId, $dbh): void
{
$paramsManager = PrmMng::getInstance();
$archiveConfig = DUPX_ArchiveConfig::getInstance();
$base_prefix = $paramsManager->getValue(PrmMng::PARAM_DB_TABLE_PREFIX);
$retained_subsite_prefix = $archiveConfig->getSubsitePrefixByParam($subsiteId);
$optionsTable = DUPX_DB_Functions::getOptionsTableName();
if ($retained_subsite_prefix != $base_prefix) {
DUPX_UpdateEngine::updateTablePrefix($dbh, $optionsTable, 'option_name', $retained_subsite_prefix, $base_prefix);
}
if ($archiveConfig->mu_generation < 2) {
$escapedOptionsTable = mysqli_real_escape_string($dbh, $optionsTable);
$uploadsPath = $paramsManager->getValue(PrmMng::PARAM_PATH_UPLOADS_NEW); //upload_url_path','uploadPath
$sql = "UPDATE `$escapedOptionsTable` SET `option_value` = '$uploadsPath' " .
"WHERE `option_name` = 'uploadPath' AND `option_value` != ''";
DUPX_DB::queryNoReturn($dbh, $sql);
$uploadsUrl = $paramsManager->getValue(PrmMng::PARAM_URL_UPLOADS_NEW);
$sql = "UPDATE `$escapedOptionsTable` SET `option_value` = '$uploadsUrl' WHERE `option_name` = 'upload_url_path' AND `option_value` != ''";
DUPX_DB::queryNoReturn($dbh, $sql);
}
}
/**
* Purge non_site where meta_key in wp_usermeta starts with data from other subsite or root site.
*
* @param int $subsiteId subsite ID
* @param mysqli $dbh database connection
*
* @return void
*/
public static function purgeRedundantData($subsiteId, $dbh): void
{
$paramsManager = PrmMng::getInstance();
if (ParamDescUsers::getUsersMode() != ParamDescUsers::USER_MODE_OVERWRITE) {
Log::info("STANDALONE: skip purging redundant data beacause user mode is " . ParamDescUsers::getUsersMode());
return;
}
Log::info("STANDALONE: purging redundant data. Considering ");
$archiveConfig = DUPX_ArchiveConfig::getInstance();
$base_prefix = $paramsManager->getValue(PrmMng::PARAM_DB_TABLE_PREFIX);
$usermeta_table_name = DUPX_DB_Functions::getUserMetaTableName();
$retained_subsite_prefix = $archiveConfig->getSubsitePrefixByParam($subsiteId);
$superAdminUsersIds = Helpers::getSuperAdminsUserIds($dbh);
Log::info("SUPER USER IDS: " . Log::v2str($superAdminUsersIds), Log::LV_DETAILED);
// Remove unused metauser key prefix
$escPergPrefix = mysqli_real_escape_string($dbh, SnapDB::quoteRegex($base_prefix));
$escPergSubsitePrefix = mysqli_real_escape_string($dbh, SnapDB::quoteRegex($retained_subsite_prefix));
if ($retained_subsite_prefix == $base_prefix) {
Log::info('CLEAN META KEYS ON USER META ' . $base_prefix . '[0-9]+_');
$where = "meta_key REGEXP '^" . $escPergPrefix . "[0-9]+_'";
} else {
Log::info('CLEAN META KEYS ON USER META ' . $base_prefix . ' EXCEPT ' . $retained_subsite_prefix);
$where = "meta_key NOT REGEXP '^" . $escPergSubsitePrefix . "' AND meta_key REGEXP '^" . $escPergPrefix . "'";
}
DUPX_DB::chunksDelete($dbh, $usermeta_table_name, $where);
if ($retained_subsite_prefix != $base_prefix) {
DUPX_UpdateEngine::updateTablePrefix($dbh, $usermeta_table_name, 'meta_key', $retained_subsite_prefix, $base_prefix);
}
if (!empty($superAdminUsersIds)) {
$updateables = [
$base_prefix . 'capabilities' => mysqli_real_escape_string($dbh, DUPX_WPConfig::ADMIN_SERIALIZED_SECURITY_STRING),
$base_prefix . 'user_level' => DUPX_WPConfig::ADMIN_LEVEL,
];
// Ad permission for superadmin users
foreach ($superAdminUsersIds as $suId) {
foreach ($updateables as $meta_key => $meta_value) {
$query = "SELECT `umeta_id` FROM {$usermeta_table_name} WHERE `user_id` = {$suId} AND meta_key = '{$meta_key}'";
if (($result = DUPX_DB::mysqli_query($dbh, $query)) !== false) {
//If entry is present UPDATE otherwise INSERT
if ($result->num_rows > 0) {
$umeta_id = $result->fetch_object()->umeta_id;
$query = "UPDATE {$usermeta_table_name} SET `meta_value` = '{$meta_value}' WHERE `umeta_id` = {$umeta_id}";
if (DUPX_DB::mysqli_query($dbh, $query) === false) {
Log::info("Could not update meta field {$meta_key} for user with id {$suId}");
}
} else {
$query = "INSERT INTO `{$usermeta_table_name}` (user_id, meta_key, meta_value) VALUES ({$suId}, '{$meta_key}', '$meta_value')";
if (DUPX_DB::mysqli_query($dbh, $query) === false) {
Log::info("Could not populate meta field {$meta_key} with the value {$meta_value} for user with id {$suId}");
}
}
$result->free();
}
}
}
}
}
}

View File

@@ -0,0 +1,598 @@
<?php
/**
* From WordPress WP_Hook class
*/
namespace Duplicator\Installer\Core\Hooks;
/**
* Core class used to implement action and filter hook functionality.
*
* @implements \Iterator<mixed,mixed>
* @implements \ArrayAccess<mixed,mixed>
*/
final class Hook implements \Iterator, \ArrayAccess
{
/**
* Hook callbacks.
*
* @var mixed[]
*/
public $callbacks = [];
/**
* The priority keys of actively running iterations of a hook.
*
* @var mixed[]
*/
private $iterations = [];
/**
* The current priority of actively running iterations of a hook.
*
* @var int[]
*/
private $currentPriority = [];
/**
* Number of levels this hook can be recursively called.
*
* @var int
*/
private $nestingLevel = 0;
/**
* Flag for if we're current doing an action, rather than a filter.
*
* @var bool
*/
private $doingAction = false;
/**
* Hooks a function or method to a specific filter action.
*
* @param string $tag The name of the filter to hook the $function_to_add callback to.
* @param callable $function_to_add The callback to be run when the filter is applied.
* @param int $priority The order in which the functions associated with a particular action
* are executed. Lower numbers correspond with earlier execution,
* and functions with the same priority are executed in the order
* in which they were added to the action.
* @param int $accepted_args The number of arguments the function accepts.
*
* @return void
*/
public function addFilter($tag, $function_to_add, $priority, $accepted_args): void
{
$idx = self::wpFilterBuildUniqueId($tag, $function_to_add, $priority);
$priority_existed = isset($this->callbacks[$priority]);
$this->callbacks[$priority][$idx] = [
'function' => $function_to_add,
'accepted_args' => $accepted_args,
];
// If we're adding a new priority to the list, put them back in sorted order.
if (!$priority_existed && count($this->callbacks) > 1) {
ksort($this->callbacks, SORT_NUMERIC);
}
if ($this->nestingLevel > 0) {
$this->resortActiveIterations($priority, $priority_existed);
}
}
/**
* Handles resetting callback priority keys mid-iteration.
*
* @param false|int $new_priority Optional. The priority of the new filter being added. Default false,
* for no priority being added.
* @param bool $priority_existed Optional. Flag for whether the priority already existed before the new
* filter was added. Default false.
*
* @return void
*/
private function resortActiveIterations($new_priority = false, bool $priority_existed = false): void
{
$new_priorities = array_keys($this->callbacks);
// If there are no remaining hooks, clear out all running iterations.
if (!$new_priorities) {
foreach ($this->iterations as $index => $iteration) {
$this->iterations[$index] = $new_priorities;
}
return;
}
$min = min($new_priorities);
foreach ($this->iterations as $index => &$iteration) {
$current = current($iteration);
// If we're already at the end of this iteration, just leave the array pointer where it is.
if (false === $current) {
continue;
}
$iteration = $new_priorities;
if ($current < $min) {
array_unshift($iteration, $current);
continue;
}
while (current($iteration) < $current) {
if (false === next($iteration)) {
break;
}
}
// If we have a new priority that didn't exist, but ::applyFilters() or ::doAction() thinks it's the current priority...
if ($new_priority === $this->currentPriority[$index] && !$priority_existed) {
/*
* ...and the new priority is the same as what $this->iterations thinks is the previous
* priority, we need to move back to it.
*/
if (false === current($iteration)) {
// If we've already moved off the end of the array, go back to the last element.
$prev = end($iteration);
} else {
// Otherwise, just go back to the previous element.
$prev = prev($iteration);
}
if (false === $prev) {
// Start of the array. Reset, and go about our day.
reset($iteration);
} elseif ($new_priority !== $prev) {
// Previous wasn't the same. Move forward again.
next($iteration);
}
}
}
unset($iteration);
}
/**
* Unhooks a function or method from a specific filter action.
*
* @param string $tag The filter hook to which the function to be removed is hooked.
* @param callable $function_to_remove The callback to be removed from running when the filter is applied.
* @param int $priority The exact priority used when adding the original filter callback.
*
* @return bool Whether the callback existed before it was removed.
*/
public function removeFilter($tag, $function_to_remove, $priority)
{
$function_key = self::wpFilterBuildUniqueId($tag, $function_to_remove, $priority);
$exists = isset($this->callbacks[$priority][$function_key]);
if ($exists) {
unset($this->callbacks[$priority][$function_key]);
if (!$this->callbacks[$priority]) {
unset($this->callbacks[$priority]);
if ($this->nestingLevel > 0) {
$this->resortActiveIterations();
}
}
}
return $exists;
}
/**
* Checks if a specific action has been registered for this hook.
*
* When using the `$function_to_check` argument, this function may return a non-boolean value
* that evaluates to false (e.g. 0), so use the `===` operator for testing the return value.
*
* @param string $tag Optional. The name of the filter hook. Default empty.
* @param callable|false $function_to_check Optional. The callback to check for. Default false.
*
* @return bool|int If `$function_to_check` is omitted, returns boolean for whether the hook has
* anything registered. When checking a specific function, the priority of that
* hook is returned, or false if the function is not attached.
*/
public function hasFilter($tag = '', $function_to_check = false)
{
if (false === $function_to_check) {
return $this->hasFilters();
}
$function_key = self::wpFilterBuildUniqueId($tag, $function_to_check, 0);
if (!$function_key) {
return false;
}
foreach ($this->callbacks as $priority => $callbacks) {
if (isset($callbacks[$function_key])) {
return $priority;
}
}
return false;
}
/**
* Checks if any callbacks have been registered for this hook.
*
* @return bool True if callbacks have been registered for the current hook, otherwise false.
*/
public function hasFilters(): bool
{
foreach ($this->callbacks as $callbacks) {
if ($callbacks) {
return true;
}
}
return false;
}
/**
* Removes all callbacks from the current filter.
*
* @param int|false $priority Optional. The priority number to remove. Default false.
*
* @return void
*/
public function removeAllFilters($priority = false): void
{
if (!$this->callbacks) {
return;
}
if (false === $priority) {
$this->callbacks = [];
} elseif (isset($this->callbacks[$priority])) {
unset($this->callbacks[$priority]);
}
if ($this->nestingLevel > 0) {
$this->resortActiveIterations();
}
}
/**
* Calls the callback functions that have been added to a filter hook.
*
* @param mixed $value The value to filter.
* @param mixed[] $args Additional parameters to pass to the callback functions.
* This array is expected to include $value at index 0.
*
* @return mixed The filtered value after all hooked functions are applied to it.
*/
public function applyFilters($value, $args)
{
if (!$this->callbacks) {
return $value;
}
$nestingLevel = $this->nestingLevel++;
$this->iterations[$nestingLevel] = array_keys($this->callbacks);
$num_args = count($args);
do {
$this->currentPriority[$nestingLevel] = current($this->iterations[$nestingLevel]);
$priority = $this->currentPriority[$nestingLevel];
foreach ($this->callbacks[$priority] as $the_) {
if (!$this->doingAction) {
$args[0] = $value;
}
// Avoid the array_slice() if possible.
if (0 == $the_['accepted_args']) {
$value = call_user_func($the_['function']);
} elseif ($the_['accepted_args'] >= $num_args) {
$value = call_user_func_array($the_['function'], $args);
} else {
$value = call_user_func_array($the_['function'], array_slice($args, 0, (int) $the_['accepted_args']));
}
}
} while (false !== next($this->iterations[$nestingLevel]));
unset($this->iterations[$nestingLevel]);
unset($this->currentPriority[$nestingLevel]);
$this->nestingLevel--;
return $value;
}
/**
* Calls the callback functions that have been added to an action hook.
*
* @param mixed[] $args Parameters to pass to the callback functions.
*
* @return void
*/
public function doAction($args): void
{
$this->doingAction = true;
$this->applyFilters('', $args);
// If there are recursive calls to the current action, we haven't finished it until we get to the last one.
if (!$this->nestingLevel) {
$this->doingAction = false;
}
}
/**
* Processes the functions hooked into the 'all' hook.
*
* @param mixed[] $args Arguments to pass to the hook callbacks. Passed by reference.
*
* @return void
*/
public function doAllHook(&$args): void
{
$nestingLevel = $this->nestingLevel++;
$this->iterations[$nestingLevel] = array_keys($this->callbacks);
do {
$priority = current($this->iterations[$nestingLevel]);
foreach ($this->callbacks[$priority] as $the_) {
call_user_func_array($the_['function'], $args);
}
} while (false !== next($this->iterations[$nestingLevel]));
unset($this->iterations[$nestingLevel]);
$this->nestingLevel--;
}
/**
* Return the current priority level of the currently running iteration of the hook.
*
* @return int|false If the hook is running, return the current priority level. If it isn't running, return false.
*/
public function currentPriority()
{
if (false === current($this->iterations)) {
return false;
}
return current(current($this->iterations));
}
/**
* Normalizes filters set up before WordPress has initialized to Hook objects.
*
* The `$filters` parameter should be an array keyed by hook name, with values
* containing either:
*
* - A `Hook` instance
* - An array of callbacks keyed by their priorities
*
* Examples:
*
* $filters = array(
* 'wp_fatal_error_handler_enabled' => array(
* 10 => array(
* array(
* 'accepted_args' => 0,
* 'function' => function() {
* return false;
* },
* ),
* ),
* ),
* );
*
* @param mixed[] $filters Filters to normalize. See documentation above for details.
*
* @return self[] Array of normalized filters.
*/
public static function buildPreinitializedHooks($filters)
{
/**
* @var self[] $normalized
*/
$normalized = [];
foreach ($filters as $tag => $callback_groups) {
if (is_object($callback_groups) && $callback_groups instanceof self) {
$normalized[$tag] = $callback_groups;
continue;
}
$hook = new self();
// Loop through callback groups.
foreach ($callback_groups as $priority => $callbacks) {
// Loop through callbacks.
foreach ($callbacks as $cb) {
$hook->addFilter($tag, $cb['function'], $priority, $cb['accepted_args']);
}
}
$normalized[$tag] = $hook;
}
return $normalized;
}
/**
* Determines whether an offset value exists.
*
* @link https://www.php.net/manual/en/arrayaccess.offsetexists.php
*
* @param mixed $offset An offset to check for.
*
* @return bool True if the offset exists, false otherwise.
*/
#[\ReturnTypeWillChange]
public function offsetExists($offset)
{
return isset($this->callbacks[$offset]);
}
/**
* Retrieves a value at a specified offset.
*
* @link https://www.php.net/manual/en/arrayaccess.offsetget.php
*
* @param mixed $offset The offset to retrieve.
*
* @return mixed If set, the value at the specified offset, null otherwise.
*/
#[\ReturnTypeWillChange]
public function offsetGet($offset)
{
return $this->callbacks[$offset] ?? null;
}
/**
* Sets a value at a specified offset.
*
* @link https://www.php.net/manual/en/arrayaccess.offsetset.php
*
* @param mixed $offset The offset to assign the value to.
* @param mixed $value The value to set.
*
* @return void
*/
#[\ReturnTypeWillChange]
public function offsetSet($offset, $value): void
{
if (is_null($offset)) {
$this->callbacks[] = $value;
} else {
$this->callbacks[$offset] = $value;
}
}
/**
* Unsets a specified offset.
*
* @link https://www.php.net/manual/en/arrayaccess.offsetunset.php
*
* @param mixed $offset The offset to unset.
*
* @return void
*/
#[\ReturnTypeWillChange]
public function offsetUnset($offset): void
{
unset($this->callbacks[$offset]);
}
/**
* Returns the current element.
*
* @link https://www.php.net/manual/en/iterator.current.php
*
* @return mixed[] Of callbacks at current priority.
*/
#[\ReturnTypeWillChange]
public function current()
{
return current($this->callbacks);
}
/**
* Moves forward to the next element.
*
* @link https://www.php.net/manual/en/iterator.next.php
*
* @return mixed[] Of callbacks at next priority.
*/
#[\ReturnTypeWillChange]
public function next()
{
return next($this->callbacks);
}
/**
* Returns the key of the current element.
*
* @link https://www.php.net/manual/en/iterator.key.php
*
* @return mixed Returns current priority on success, or NULL on failure
*/
#[\ReturnTypeWillChange]
public function key()
{
return key($this->callbacks);
}
/**
* Checks if current position is valid.
*
* @link https://www.php.net/manual/en/iterator.valid.php
*
* @return bool Whether the current position is valid.
*/
#[\ReturnTypeWillChange]
public function valid()
{
return key($this->callbacks) !== null;
}
/**
* Rewinds the Iterator to the first element.
*
* @link https://www.php.net/manual/en/iterator.rewind.php
*
* @return void
*/
#[\ReturnTypeWillChange]
public function rewind(): void
{
reset($this->callbacks);
}
/**
* Build Unique ID for storage and retrieval.
*
* The old way to serialize the callback caused issues and this function is the
* solution. It works by checking for objects and creating a new property in
* the class to keep track of the object and new objects of the same class that
* need to be added.
*
* It also allows for the removal of actions and filters for objects after they
* change class properties. It is possible to include the property $wp_filter_id
* in your class and set it to "null" or a number to bypass the workaround.
* However this will prevent you from adding new classes and any new classes
* will overwrite the previous hook by the same class.
*
* Functions and static method callbacks are just returned as strings and
* shouldn't have any speed penalty.
*
* @link https://core.trac.wordpress.org/ticket/3875
*
* @since 2.2.3
* @since 5.3.0 Removed workarounds for spl_object_hash().
* `$tag` and `$priority` are no longer used,
* and the function always returns a string.
* @access private
*
* @param string $tag Unused. The name of the filter to build ID for.
* @param callable $function The function to generate ID for.
* @param int $priority Unused. The order in which the functions
* associated with a particular action are executed.
*
* @return string Unique function ID for usage as array key.
*/
protected static function wpFilterBuildUniqueId($tag, $function, $priority): string
{
if (is_string($function)) {
return $function;
}
if (is_object($function)) {
// Closures are currently implemented as objects.
$function = [
$function,
'',
];
} else {
$function = (array) $function;
}
if (is_object($function[0])) {
// Object class calling.
return spl_object_hash($function[0]) . $function[1];
} elseif (is_string($function[0])) {
// Static calling.
return $function[0] . '::' . $function[1];
} else {
return '';
}
}
}

View File

@@ -0,0 +1,434 @@
<?php
/**
* Installer Hooks Manager
*
* @package Duplicator
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Core\Hooks;
final class HooksMng
{
/** @var ?self */
private static $instance;
/** @var Hook[] */
private $filters = [];
/** @var string[] */
private $currentFilter = [];
/**
*
* @return self
*/
public static function getInstance()
{
if (is_null(self::$instance)) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Init params and load
*/
private function __construct()
{
}
/**
* Hook a function or method to a specific filter action.
*
* WordPress offers filter hooks to allow plugins to modify
* various types of internal data at runtime.
*
* A plugin can modify data by binding a callback to a filter hook. When the filter
* is later applied, each bound callback is run in order of priority, and given
* the opportunity to modify a value by returning a new value.
*
* The following example shows how a callback function is bound to a filter hook.
*
* Note that `$example` is passed to the callback, (maybe) modified, then returned:
*
* function example_callback( $example ) {
* // Maybe modify $example in some way.
* return $example;
* }
* add_filter( 'example_filter', 'example_callback' );
*
* Bound callbacks can accept from none to the total number of arguments passed as parameters
* in the corresponding applyFilters() call.
*
* In other words, if an applyFilters() call passes four total arguments, callbacks bound to
* it can accept none (the same as 1) of the arguments or up to four. The important part is that
* the `$accepted_args` value must reflect the number of arguments the bound callback *actually*
* opted to accept. If no arguments were accepted by the callback that is considered to be the
* same as accepting 1 argument. For example:
*
* // Filter call.
* $value = applyFilters( 'hook', $value, $arg2, $arg3 );
*
* // Accepting zero/one arguments.
* function example_callback() {
* ...
* return 'some value';
* }
* add_filter( 'hook', 'example_callback' ); // Where $priority is default 10, $accepted_args is default 1.
*
* // Accepting two arguments (three possible).
* function example_callback( $value, $arg2 ) {
* ...
* return $maybe_modified_value;
* }
* add_filter( 'hook', 'example_callback', 10, 2 ); // Where $priority is 10, $accepted_args is 2.
*
* *Note:* The function will return true whether or not the callback is valid.
* It is up to you to take care. This is done for optimization purposes, so
* everything is as quick as possible.
*
* @param string $tag The name of the filter to hook the $function_to_add callback to.
* @param callable $function_to_add The callback to be run when the filter is applied.
* @param int $priority Optional. Used to specify the order in which the functions
* associated with a particular action are executed.
* Lower numbers correspond with earlier execution,
* and functions with the same priority are executed
* in the order in which they were added to the action. Default 10.
* @param int $accepted_args Optional. The number of arguments the function accepts. Default 1.
*
* @return true
*/
public function addFilter($tag, $function_to_add, $priority = 10, $accepted_args = 1): bool
{
if (!isset($this->filters[$tag])) {
$this->filters[$tag] = new Hook();
}
$this->filters[$tag]->addFilter($tag, $function_to_add, $priority, $accepted_args);
return true;
}
/**
* Checks if any filter has been registered for a hook.
*
* When using the `$function_to_check` argument, this function may return a non-boolean value
* that evaluates to false (e.g. 0), so use the `===` operator for testing the return value.
*
* @param string $tag The name of the filter hook.
* @param callable|false $function_to_check Optional. The callback to check for. Default false.
*
* @return bool|int If `$function_to_check` is omitted, returns boolean for whether the hook has
* anything registered. When checking a specific function, the priority of that
* hook is returned, or false if the function is not attached.
*/
public function hasFilter($tag, $function_to_check = false)
{
if (!isset($this->filters[$tag])) {
return false;
}
return $this->filters[$tag]->hasFilter($tag, $function_to_check);
}
/**
* Calls the callback functions that have been added to a filter hook.
*
* The callback functions attached to the filter hook are invoked by calling
* this function. This function can be used to create a new filter hook by
* simply calling this function with the name of the new hook specified using
* the `$tag` parameter.
*
* The function also allows for multiple additional arguments to be passed to hooks.
*
* Example usage:
*
* // The filter callback function.
* function example_callback( $string, $arg1, $arg2 ) {
* // (maybe) modify $string.
* return $string;
* }
* add_filter( 'example_filter', 'example_callback', 10, 3 );
*
* /*
* * Apply the filters by calling the 'example_callback()' function
* * that's hooked onto `example_filter` above.
* *
* * - 'example_filter' is the filter hook.
* * - 'filter me' is the value being filtered.
* * - $arg1 and $arg2 are the additional arguments passed to the callback.
* $value = applyFilters( 'example_filter', 'filter me', $arg1, $arg2 );
*
* @param string $tag The name of the filter hook.
* @param mixed $value The value to filter.
* ... $args Additional parameters to pass to the callback functions.
*
* @return mixed The filtered value after all hooked functions are applied to it.
*/
public function applyFilters($tag, $value)
{
$args = func_get_args();
// Do 'all' actions first.
if (isset($this->filters['all'])) {
$this->currentFilter[] = $tag;
$this->callAllHook($args);
}
if (!isset($this->filters[$tag])) {
if (isset($this->filters['all'])) {
array_pop($this->currentFilter);
}
return $value;
}
if (!isset($this->filters['all'])) {
$this->currentFilter[] = $tag;
}
// Don't pass the tag name to Hook.
array_shift($args);
$filtered = $this->filters[$tag]->applyFilters($value, $args);
array_pop($this->currentFilter);
return $filtered;
}
/**
* Removes a function from a specified filter hook.
*
* This function removes a function attached to a specified filter hook. This
* method can be used to remove default functions attached to a specific filter
* hook and possibly replace them with a substitute.
*
* To remove a hook, the $function_to_remove and $priority arguments must match
* when the hook was added. This goes for both filters and actions. No warning
* will be given on removal failure.
*
* @param string $tag The filter hook to which the function to be removed is hooked.
* @param callable $function_to_remove The name of the function which should be removed.
* @param int $priority Optional. The priority of the function. Default 10.
*
* @return bool Whether the function existed before it was removed.
*/
public function removeFilter($tag, $function_to_remove, $priority = 10)
{
$r = false;
if (isset($this->filters[$tag])) {
$r = $this->filters[$tag]->removeFilter($tag, $function_to_remove, $priority);
if (!$this->filters[$tag]->callbacks) {
unset($this->filters[$tag]);
}
}
return $r;
}
/**
* Remove all of the hooks from a filter.
*
* @param string $tag The filter to remove hooks from.
* @param int|false $priority Optional. The priority number to remove. Default false.
*
* @return true True when finished.
*/
public function removeAllFilters($tag, $priority = false): bool
{
if (isset($this->filters[$tag])) {
$this->filters[$tag]->removeAllFilters($priority);
if (!$this->filters[$tag]->hasFilters()) {
unset($this->filters[$tag]);
}
}
return true;
}
/**
* Hooks a function on to a specific action.
*
* Actions are the hooks that the WordPress core launches at specific points
* during execution, or when specific events occur. Plugins can specify that
* one or more of its PHP functions are executed at these points, using the
* Action API.
*
* @param string $tag The name of the action to which the $function_to_add is hooked.
* @param callable $function_to_add The name of the function you wish to be called.
* @param int $priority Optional. Used to specify the order in which the functions
* associated with a particular action are executed. Default 10.
* Lower numbers correspond with earlier execution,
* and functions with the same priority are executed
* in the order in which they were added to the action.
* @param int $accepted_args Optional. The number of arguments the function accepts. Default 1.
*
* @return true Will always return true.
*/
public function addAction($tag, $function_to_add, $priority = 10, $accepted_args = 1): bool
{
return $this->addFilter($tag, $function_to_add, $priority, $accepted_args);
}
/**
* Execute functions hooked on a specific action hook.
*
* This function invokes all functions attached to action hook `$tag`. It is
* possible to create new action hooks by simply calling this function,
* specifying the name of the new hook using the `$tag` parameter.
*
* You can pass extra arguments to the hooks, much like you can with `applyFilters()`.
*
* Example usage:
*
* // The action callback function.
* function example_callback( $arg1, $arg2 ) {
* // (maybe) do something with the args.
* }
* add_action( 'example_action', 'example_callback', 10, 2 );
*
* /*
* * Trigger the actions by calling the 'example_callback()' function
* * that's hooked onto `example_action` above.
* *
* * - 'example_action' is the action hook.
* * - $arg1 and $arg2 are the additional arguments passed to the callback.
* $value = do_action( 'example_action', $arg1, $arg2 );
*
* @global Hook[] $this->filters Stores all of the filters and actions.
* @global string[] $this->currentFilter Stores the list of current filters with the current one last.
*
* @param string $tag The name of the action to be executed.
* ...$arg Optional. Additional arguments which are passed on to the
* functions hooked to the action. Default empty.
*
* @return void
*/
public function doAction($tag): void
{
$arg = $all_args = func_get_args();
array_shift($arg); // remove tag action
// Do 'all' actions first.
if (isset($this->filters['all'])) {
$this->currentFilter[] = $tag;
$this->callAllHook($all_args);
}
if (!isset($this->filters[$tag])) {
if (isset($this->filters['all'])) {
array_pop($this->currentFilter);
}
return;
}
if (!isset($this->filters['all'])) {
$this->currentFilter[] = $tag;
}
if (empty($arg)) {
$arg[] = '';
} elseif (is_array($arg[0]) && 1 === count($arg[0]) && isset($arg[0][0]) && is_object($arg[0][0])) {
// Backward compatibility for PHP4-style passing of `array( &$this )` as action `$arg`.
$arg[0] = $arg[0][0];
}
$this->filters[$tag]->doAction($arg);
array_pop($this->currentFilter);
}
/**
* Checks if any action has been registered for a hook.
*
* When using the `$function_to_check` argument, this function may return a non-boolean value
* that evaluates to false (e.g. 0), so use the `===` operator for testing the return value.
*
* @see hasFilter() has_action() is an alias of hasFilter().
*
* @param string $tag The name of the action hook.
* @param callable|false $function_to_check Optional. The callback to check for. Default false.
*
* @return bool|int If `$function_to_check` is omitted, returns boolean for whether the hook has
* anything registered. When checking a specific function, the priority of that
* hook is returned, or false if the function is not attached.
*/
public function hasAction($tag, $function_to_check = false)
{
return $this->hasFilter($tag, $function_to_check);
}
/**
* Removes a function from a specified action hook.
*
* This function removes a function attached to a specified action hook. This
* method can be used to remove default functions attached to a specific filter
* hook and possibly replace them with a substitute.
*
* @param string $tag The action hook to which the function to be removed is hooked.
* @param callable $function_to_remove The name of the function which should be removed.
* @param int $priority Optional. The priority of the function. Default 10.
*
* @return bool Whether the function is removed.
*/
public function removeAction($tag, $function_to_remove, $priority = 10)
{
return $this->removeFilter($tag, $function_to_remove, $priority);
}
/**
* Remove all of the hooks from an action.
*
* @param string $tag The action to remove hooks from.
* @param int|false $priority The priority number to remove them from. Default false.
*
* @return true True when finished.
*/
public function removeAllActions($tag, $priority = false): bool
{
return $this->removeAllFilters($tag, $priority);
}
/**
* Retrieve the name of the current filter or action.
*
* @global string[] $wp_current_filter Stores the list of current filters with the current one last
*
* @return string Hook name of the current filter or action.
*/
public function currentFilter()
{
return end($this->currentFilter);
}
/**
* Retrieve the name of the current action.
*
* @return string Hook name of the current action.
*/
public function currentAction()
{
return $this->currentFilter();
}
/**
* Call the 'all' hook, which will process the functions hooked into it.
*
* The 'all' hook passes all of the arguments or parameters that were used for
* the hook, which this function was called for.
*
* This function is used internally for apply_filters(), do_action(), and
* do_action_ref_array() and is not meant to be used from outside those
* functions. This function does not check for the existence of the all hook, so
* it will fail unless the all hook exists prior to this function call.
*
* @global Hook[] $this->filters Stores all of the filters and actions.
*
* @param mixed[] $args The collected parameters from the hook that was called.
*
* @return void
*/
private function callAllHook($args): void
{
$this->filters['all']->doAllHook($args);
}
}

View File

@@ -0,0 +1,906 @@
<?php
/**
* @package Duplicator
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Core;
use Duplicator\Installer\Core\Deploy\ServerConfigs;
use Duplicator\Installer\Core\Security;
use Duplicator\Installer\Utils\Log\Log;
use Duplicator\Installer\Core\Params\Descriptors\ParamDescConfigs;
use Duplicator\Installer\Core\Params\PrmMng;
use Duplicator\Installer\Models\MigrateData;
use Duplicator\Installer\Utils\InstallerOrigFileMng;
use Duplicator\Libs\Snap\SnapIO;
use DUPX_ArchiveConfig;
use DUPX_DB;
use DUPX_DB_Functions;
use DUPX_DBInstall;
use DUPX_NOTICE_ITEM;
use DUPX_NOTICE_MANAGER;
use DUPX_Package;
use DUPX_Template;
use DUPX_WPConfig;
use Error;
use Exception;
class InstState
{
/**
* modes
*/
const MODE_UNKNOWN = -1;
const MODE_STD_INSTALL = 0;
const MODE_OVR_INSTALL = 1;
const LOGIC_MODE_IMPORT = 'IMPORT';
const LOGIC_MODE_RECOVERY = 'RECOVERY';
const LOGIC_MODE_CLASSIC = 'CLASSIC';
const LOGIC_MODE_OVERWRITE = 'OVERWRITE';
const LOGIC_MODE_BRIDGE = 'BRIDGE';
const LOGIC_MODE_RESTORE_BACKUP = 'RESTORE_BACKUP';
/**
* install types
*/
const TYPE_NOT_SET = -2;
const TYPE_SINGLE = -1;
const TYPE_STANDALONE = 0;
const TYPE_MSUBDOMAIN = 2;
const TYPE_MSUBFOLDER = 3;
const TYPE_SINGLE_ON_SUBDOMAIN = 4;
const TYPE_SINGLE_ON_SUBFOLDER = 5;
const TYPE_SUBSITE_ON_SUBDOMAIN = 6;
const TYPE_SUBSITE_ON_SUBFOLDER = 7;
const TYPE_RBACKUP_SINGLE = 8;
const TYPE_RBACKUP_MSUBDOMAIN = 9;
const TYPE_RBACKUP_MSUBFOLDER = 10;
const TYPE_RECOVERY_SINGLE = 11;
const TYPE_RECOVERY_MSUBDOMAIN = 12;
const TYPE_RECOVERY_MSUBFOLDER = 13;
const SUBSITE_IMPORT_WP_MIN_VERSION = '4.6';
/** @var int */
protected $mode = self::MODE_UNKNOWN;
/** @var string */
protected $ovr_wp_content_dir = '';
/** @var ?self */
private static $instance;
/**
* Get instance
*
* @return self
*/
public static function getInstance()
{
if (is_null(self::$instance)) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Class constructor
*/
private function __construct()
{
}
/**
* return installer mode
*
* @return int
*/
public function getMode()
{
return PrmMng::getInstance()->getValue(PrmMng::PARAM_INSTALLER_MODE);
}
/**
* check current installer mode
*
* @param bool $onlyIfUnknown check se state only if is unknow state
* @param bool $saveParams if true update params
*
* @return boolean
*/
public function checkState($onlyIfUnknown = true, $saveParams = true)
{
$paramsManager = PrmMng::getInstance();
if ($onlyIfUnknown && $paramsManager->getValue(PrmMng::PARAM_INSTALLER_MODE) !== self::MODE_UNKNOWN) {
return true;
}
$isOverwrite = false;
$overwriteData = false;
$nManager = DUPX_NOTICE_MANAGER::getInstance();
try {
if (self::isImportFromBackendMode() || self::isRecoveryMode()) {
$overwriteData = $this->getOverwriteDataFromParams();
} else {
$overwriteData = $this->getOverwriteDataFromWpConfig();
}
if (!empty($overwriteData)) {
if (!DUPX_DB::testConnection($overwriteData['dbhost'], $overwriteData['dbuser'], $overwriteData['dbpass'], $overwriteData['dbname'])) {
throw new Exception('wp-config.php exists but database data connection isn\'t valid. Continuing with standard install');
}
$isOverwrite = true;
if (self::isClassicInstall()) {
//Add additional overwrite data for standard installs
$overwriteData['adminUsers'] = $this->getAdminUsersOnOverwriteDatabase($overwriteData);
$overwriteData['wpVersion'] = $this->getWordPressVersionOverwrite();
$this->updateOverwriteDataFromDb($overwriteData);
}
}
} catch (Exception | Error $e) {
Log::logException($e);
$longMsg = "Exception message: " . $e->getMessage() . "\n\n";
$nManager->addNextStepNotice([
'shortMsg' => 'wp-config.php exists but isn\'t valid. Continue on standard install.',
'level' => DUPX_NOTICE_ITEM::SOFT_WARNING,
'longMsg' => $longMsg,
'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_PRE,
]);
$nManager->saveNotices();
}
if ($isOverwrite) {
$paramsManager->setValue(PrmMng::PARAM_INSTALLER_MODE, self::MODE_OVR_INSTALL);
$paramsManager->setValue(PrmMng::PARAM_OVERWRITE_SITE_DATA, $overwriteData);
} else {
$paramsManager->setValue(PrmMng::PARAM_INSTALLER_MODE, self::MODE_STD_INSTALL);
}
if ($saveParams) {
return $this->save();
} else {
return true;
}
}
/**
* Install type to string
*
* @param ?int $type install type Enum InstState::TYPE_*, if null get current install type
*
* @return string
*/
public static function installTypeToString($type = null): string
{
if (is_null($type)) {
$type = self::getInstType();
}
switch ($type) {
case self::TYPE_MSUBDOMAIN:
return 'multisite subdomain';
case self::TYPE_MSUBFOLDER:
return 'multisite subfolder';
case self::TYPE_STANDALONE:
return 'standalone subsite';
case self::TYPE_SUBSITE_ON_SUBDOMAIN:
return 'subsite on subdomain multisite';
case self::TYPE_SUBSITE_ON_SUBFOLDER:
return 'subsite on subfolder multisite';
case self::TYPE_SINGLE:
return 'single site';
case self::TYPE_SINGLE_ON_SUBDOMAIN:
return 'single site on subdomain multisite';
case self::TYPE_SINGLE_ON_SUBFOLDER:
return 'single site on subfolder multisite';
case self::TYPE_RBACKUP_SINGLE:
return 'restore single site';
case self::TYPE_RBACKUP_MSUBDOMAIN:
return 'restore subdomain multisite';
case self::TYPE_RBACKUP_MSUBFOLDER:
return 'restore subfolder multisite';
case self::TYPE_RECOVERY_SINGLE:
return 'restore single site';
case self::TYPE_RECOVERY_MSUBDOMAIN:
return 'restore subdomain multisite';
case self::TYPE_RECOVERY_MSUBFOLDER:
return 'restore subfolder multisite';
case self::TYPE_NOT_SET:
return 'NOT SET';
default:
throw new Exception('Invalid installer mode');
}
}
/**
* Overwrite data default values
*
* @return array<string, mixed>
*/
public static function overwriteDataDefault(): array
{
return [
'dupVersion' => '0',
'wpVersion' => '0',
'dbhost' => '',
'dbname' => '',
'dbuser' => '',
'dbpass' => '',
'table_prefix' => '',
'restUrl' => '',
'restNonce' => '',
'restAuthUser' => '',
'restAuthPassword' => '',
'ustatIdentifier' => '',
'isMultisite' => false,
'subdomain' => false,
'subsites' => [],
'nextSubsiteIdAI' => -1,
'adminUsers' => [],
'paths' => [],
'urls' => [],
'dupLicense' => 0,
'loggedUser' => [
'ID' => -1,
'user_login' => '',
],
'packagesTableExists' => false,
'removeFilters' => [
'dirs' => [],
'files' => [],
],
];
}
/**
* Get overwrite data from params
*
* @return false|array<string, mixed>
*/
protected function getOverwriteDataFromParams()
{
$overwriteData = PrmMng::getInstance()->getValue(PrmMng::PARAM_OVERWRITE_SITE_DATA);
if (empty($overwriteData)) {
return false;
}
if (!isset($overwriteData['dbhost']) || !isset($overwriteData['dbname']) || !isset($overwriteData['dbuser']) || !isset($overwriteData['dbpass'])) {
return false;
}
return array_merge(self::overwriteDataDefault(), $overwriteData);
}
/**
* Get overwrite data from wp-config.php
*
* @return false|array<string, mixed>
*/
protected function getOverwriteDataFromWpConfig()
{
if (($wpConfigPath = ServerConfigs::getWpConfigLocalStoredPath()) === false) {
$wpConfigPath = DUPX_WPConfig::getWpConfigPath();
if (!file_exists($wpConfigPath)) {
$wpConfigPath = DUPX_WPConfig::getWpConfigDeafultPath();
}
}
$overwriteData = false;
Log::info('CHECK STATE INSTALLER WP CONFIG PATH: ' . Log::v2str($wpConfigPath), Log::LV_DETAILED);
if (!file_exists($wpConfigPath)) {
return $overwriteData;
}
$nManager = DUPX_NOTICE_MANAGER::getInstance();
try {
if (DUPX_WPConfig::getLocalConfigTransformer() === false) {
throw new Exception('wp-config.php exist but isn\'t valid. continue on standard install');
}
$overwriteData = array_merge(
self::overwriteDataDefault(),
[
'dbhost' => DUPX_WPConfig::getValueFromLocalWpConfig('DB_HOST'),
'dbname' => DUPX_WPConfig::getValueFromLocalWpConfig('DB_NAME'),
'dbuser' => DUPX_WPConfig::getValueFromLocalWpConfig('DB_USER'),
'dbpass' => DUPX_WPConfig::getValueFromLocalWpConfig('DB_PASSWORD'),
'table_prefix' => DUPX_WPConfig::getValueFromLocalWpConfig('table_prefix', 'variable'),
]
);
if (DUPX_WPConfig::getValueFromLocalWpConfig('MULTISITE', 'constant', false)) {
$overwriteData['isMultisite'] = true;
$overwriteData['subdomain'] = DUPX_WPConfig::getValueFromLocalWpConfig('SUBDOMAIN_INSTALL', 'constant', false);
}
} catch (Exception | Error $e) {
$overwriteData = false;
Log::logException($e);
$longMsg = "Exception message: " . $e->getMessage() . "\n\n";
$nManager->addNextStepNotice([
'shortMsg' => 'wp-config.php exists but isn\'t valid. Continue on standard install.',
'level' => DUPX_NOTICE_ITEM::SOFT_WARNING,
'longMsg' => $longMsg,
'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_PRE,
]);
$nManager->saveNotices();
}
return $overwriteData;
}
/**
* Check if is bridge install
*
* @return bool
*/
public static function isBridgeInstall(): bool
{
return defined('DUPLICATOR_MU_PLUGIN_VERSION');
}
/**
* Check if is recovery mode
*
* @param ?int $type install type Enum InstState::TYPE_*, if null get current install type
*
* @return bool
*/
public static function isRecoveryMode($type = null): bool
{
return self::isInstType(
[
self::TYPE_RECOVERY_SINGLE,
self::TYPE_RECOVERY_MSUBDOMAIN,
self::TYPE_RECOVERY_MSUBFOLDER,
],
$type
);
}
/**
* Check if is restore backup
*
* @param ?int $type install type Enum InstState::TYPE_*, if null get current install type
*
* @return bool
*/
public static function isRestoreBackup($type = null): bool
{
return self::isInstType(
[
self::TYPE_RBACKUP_SINGLE,
self::TYPE_RBACKUP_MSUBDOMAIN,
self::TYPE_RBACKUP_MSUBFOLDER,
self::TYPE_RECOVERY_SINGLE,
self::TYPE_RECOVERY_MSUBDOMAIN,
self::TYPE_RECOVERY_MSUBFOLDER,
],
$type
);
}
/**
* Check if is import from backend mode
*
* @return bool
*/
public static function isImportFromBackendMode(): bool
{
$template = PrmMng::getInstance()->getValue(PrmMng::PARAM_TEMPLATE);
return ($template === DUPX_Template::TEMPLATE_IMPORT_BASE ||
$template === DUPX_Template::TEMPLATE_IMPORT_ADVANCED);
}
/**
* Check if is classic install (non import from backend)
*
* @return bool
*/
public static function isClassicInstall(): bool
{
return (!self::isImportFromBackendMode() && !self::isRecoveryMode());
}
/**
* Return true if new target site is multisite
*
* @return bool
*/
public static function isNewSiteIsMultisite(): bool
{
return self::isInstType(
[
InstState::TYPE_MSUBDOMAIN,
InstState::TYPE_MSUBFOLDER,
InstState::TYPE_RBACKUP_MSUBDOMAIN,
InstState::TYPE_RBACKUP_MSUBFOLDER,
InstState::TYPE_RECOVERY_MSUBDOMAIN,
InstState::TYPE_RECOVERY_MSUBFOLDER,
]
);
}
/**
* Check is install type is add site on multisite
*
* @param ?int $type install type Enum InstState::TYPE_*, if null get current install type
*
* @return bool
*/
public static function isAddSiteOnMultisite($type = null): bool
{
return self::isInstType(
[
self::TYPE_SINGLE_ON_SUBDOMAIN,
self::TYPE_SINGLE_ON_SUBFOLDER,
self::TYPE_SUBSITE_ON_SUBDOMAIN,
self::TYPE_SUBSITE_ON_SUBFOLDER,
],
$type
);
}
/**
* Check if is multisite install
*
* @param ?int $type install type Enum InstState::TYPE_*, if null get current install type
*
* @return bool
*/
public static function isMultisiteInstall($type = null): bool
{
return self::isInstType(
[
self::TYPE_MSUBDOMAIN,
self::TYPE_MSUBFOLDER,
],
$type
);
}
/**
* Check if install type is available
*
* @param int|int[] $type install type Enum InstState::TYPE_*
*
* @return bool
*/
public static function instTypeAvailable($type): bool
{
$acceptList = ParamDescConfigs::getInstallTypesAcceptValues();
$typesToCheck = is_array($type) ? $type : [$type];
$typesAvaliables = array_intersect($acceptList, $typesToCheck);
return (count($typesAvaliables) > 0);
}
/**
* Return true if add site on multisite install is available
*
* @return bool
*/
public static function isAddSiteOnMultisiteAvailable(): bool
{
return self::instTypeAvailable(
[
self::TYPE_SINGLE_ON_SUBDOMAIN,
self::TYPE_SINGLE_ON_SUBFOLDER,
self::TYPE_SUBSITE_ON_SUBDOMAIN,
self::TYPE_SUBSITE_ON_SUBFOLDER,
]
);
}
/**
* Return true if multisite install is available
*
* @return bool
*/
public static function isMultisiteInstallAvailable(): bool
{
return self::instTypeAvailable(
[
self::TYPE_MSUBDOMAIN,
self::TYPE_MSUBFOLDER,
]
);
}
/**
* This function in case of an error returns an empty array but never generates exceptions
*
* @param array<string, mixed> $overwriteData Overwrite data
*
* @return array<string, mixed>
*/
protected function getAdminUsersOnOverwriteDatabase($overwriteData)
{
$adminUsers = [];
$dbFuncs = null;
try {
$dbFuncs = DUPX_DB_Functions::getInstance();
if (!$dbFuncs->dbConnection($overwriteData)) {
throw new Exception('GET USERS ON CURRENT DATABASE FAILED. Can\'t connect');
}
$usersTables = [
$dbFuncs->getUserTableName($overwriteData['table_prefix']),
$dbFuncs->getUserMetaTableName($overwriteData['table_prefix']),
];
if (!$dbFuncs->tablesExist($usersTables)) {
throw new Exception(
'GET USERS ON CURRENT DATABASE FAILED. Users tables doesn\'t exist, ' .
'continue with orverwrite installation but with option keep users disabled' . "\n"
);
}
if (($adminUsers = $dbFuncs->getAdminUsers($overwriteData['table_prefix'])) === false) {
$adminUsers = [];
throw new Exception('GET USERS ON CURRENT DATABASE FAILED. OVERWRITE DB USERS NOT FOUND');
}
} catch (Exception | Error $e) {
Log::logException($e, Log::LV_DEFAULT, 'GET ADMIN USER EXECPTION BUT CONTINUE');
} finally {
if ($dbFuncs instanceof DUPX_DB_Functions) {
$dbFuncs->closeDbConnection();
}
}
return $adminUsers;
}
/**
* Returns the WP version from the ./wp-includes/version.php file if it exists, otherwise '0'
*
* @return string WP version
*/
protected function getWordPressVersionOverwrite(): string
{
$wp_version = '0';
try {
$versionFilePath = PrmMng::getInstance()->getValue(PrmMng::PARAM_PATH_WP_CORE_NEW) . "/wp-includes/version.php";
if (!file_exists($versionFilePath) || !is_readable($versionFilePath)) {
Log::info("WordPress Version file does not exist or is not readable at path: {$versionFilePath}");
return $wp_version;
}
include($versionFilePath);
return $wp_version;
} catch (Exception $e) {
Log::logException($e, Log::LV_DEFAULT, 'EXCEPTION GETTING WordPress VERSION, BUT CONTINUE');
} catch (Error $e) {
Log::logException($e, Log::LV_DEFAULT, 'ERROR GETTING WordPress VERSION, BUT CONTINUE');
}
return $wp_version;
}
/**
* Returns the Duplicator Pro version if it exists, otherwise '0'
*
* @param array<string, mixed> $overwriteData Overwrite data
*
* @return bool True on success, false on failure
*/
protected function updateOverwriteDataFromDb(&$overwriteData): bool
{
$dbFuncs = null;
try {
$dbFuncs = DUPX_DB_Functions::getInstance();
if (!$dbFuncs->dbConnection($overwriteData)) {
throw new Exception('OVERWRITE DATA ERROR: Can\'t connect');
}
$optionsTable = DUPX_DB_Functions::getOptionsTableName($overwriteData['table_prefix']);
if (!$dbFuncs->tablesExist($optionsTable)) {
throw new Exception("OVERWRITE DATA ERROR: Options tables doesn't exist.\n");
}
$duplicatorProVersion = $dbFuncs->getDuplicatorVersion($overwriteData['table_prefix']);
$overwriteData['dupVersion'] = (empty($duplicatorProVersion) ? '0' : $duplicatorProVersion);
$overwriteData['ustatIdentifier'] = $dbFuncs->getUniqueId($overwriteData['table_prefix']);
$overwriteData['packagesTableExists'] = $dbFuncs->tablesExist($dbFuncs->getPackagesTableName($overwriteData['table_prefix']));
} catch (Exception $e) {
Log::logException($e, Log::LV_DEFAULT, 'GET DUPLICATOR VERSION EXECPTION BUT CONTINUE');
return false;
} catch (Error $e) {
Log::logException($e, Log::LV_DEFAULT, 'GET DUPLICATOR VERSION ERROR BUT CONTINUE');
return false;
} finally {
if ($dbFuncs instanceof DUPX_DB_Functions) {
$dbFuncs->closeDbConnection();
}
}
return true;
}
/**
* getHtmlModeHeader
*
* @return string
*/
public function getHtmlModeHeader(): string
{
$additional_info = '<span class="requires-no-db"> - No database actions';
$additional_info .= DUPX_ArchiveConfig::getInstance()->isDBExcluded() ? ' (Database Excluded)' : '';
$additional_info .= '</span>';
$additional_info .= (DUPX_ArchiveConfig::getInstance()->isDBOnly()) ? ' - Database Only' : '';
$additional_info .= ($GLOBALS['DUPX_ENFORCE_PHP_INI']) ? '<i style="color:red"><br/>*PHP ini enforced*</i>' : '';
switch ($this->getMode()) {
case self::MODE_OVR_INSTALL:
$label = 'Overwrite Install';
$class = 'dupx-overwrite mode_overwrite';
break;
case self::MODE_STD_INSTALL:
$label = 'Standard Install';
$class = 'dupx-overwrite mode_standard';
break;
case self::MODE_UNKNOWN:
default:
$label = 'Custom Install';
$class = 'mode_unknown';
break;
}
if (strlen($additional_info)) {
return '<span class="' . $class . '">' . $label . ' ' . $additional_info . '</span>';
} else {
return "<span class=\"{$class}\">{$label}</span>";
}
}
/**
* reset current mode
*
* @param boolean $saveParams save params
*
* @return boolean
*/
public function resetState($saveParams = true)
{
$paramsManager = PrmMng::getInstance();
$paramsManager->setValue(PrmMng::PARAM_INSTALLER_MODE, self::MODE_UNKNOWN);
if ($saveParams) {
return $this->save();
} else {
return true;
}
}
/**
* Save current installer state
*
* @return bool
*/
public function save()
{
return PrmMng::getInstance()->save();
}
/**
* Return stru if is overwrite install
*
* @return boolean
*/
public static function isOverwrite(): bool
{
return (PrmMng::getInstance()->getValue(PrmMng::PARAM_INSTALLER_MODE) === self::MODE_OVR_INSTALL);
}
/**
* Returns true if the DB action is set to do nothing
*
* @return bool
*/
public static function dbDoNothing(): bool
{
return PrmMng::getInstance()->getValue(PrmMng::PARAM_DB_ACTION) === DUPX_DBInstall::DBACTION_DO_NOTHING;
}
/**
* Returns true if the installer is running in staging mode
*
* @return bool
*/
public static function isStagingMode(): bool
{
$overwriteData = PrmMng::getInstance()->getValue(PrmMng::PARAM_OVERWRITE_SITE_DATA);
return !empty($overwriteData['stagingMode']);
}
/**
* this function returns true if both the URL and path old and new path are identical
*
* @return bool
*/
public static function isInstallerCreatedInThisLocation(): bool
{
$paramsManager = PrmMng::getInstance();
$urlNew = null;
$pathNew = null;
if (InstState::isImportFromBackendMode()) {
$overwriteData = $paramsManager->getValue(PrmMng::PARAM_OVERWRITE_SITE_DATA);
if (isset($overwriteData['urls']['home']) && isset($overwriteData['paths']['home'])) {
$urlNew = $overwriteData['urls']['home'];
$pathNew = $overwriteData['paths']['home'];
}
}
if (is_null($urlNew) || is_null($pathNew)) {
$pathNew = $paramsManager->getValue(PrmMng::PARAM_PATH_NEW);
$urlNew = $paramsManager->getValue(PrmMng::PARAM_URL_NEW);
}
return self::urlAndPathAreSameOfArchive($urlNew, $pathNew);
}
/**
* Check if the url and path are the same of the archive
*
* @param string $urlNew new url
* @param string $pathNew new path
*
* @return bool
*/
public static function urlAndPathAreSameOfArchive($urlNew, $pathNew): bool
{
$archiveConfig = \DUPX_ArchiveConfig::getInstance();
$urlOld = rtrim($archiveConfig->getRealValue('homeUrl'), '/');
$paths = $archiveConfig->getRealValue('archivePaths');
$pathOld = $paths->home;
$paths = $archiveConfig->getRealValue('originalPaths');
$pathOldOrig = $paths->home;
$urlNew = SnapIO::untrailingslashit($urlNew);
$urlOld = SnapIO::untrailingslashit($urlOld);
$pathNew = SnapIO::untrailingslashit($pathNew);
$pathOld = SnapIO::untrailingslashit($pathOld);
$pathOldOrig = SnapIO::untrailingslashit($pathOldOrig);
return (($pathNew === $pathOld || $pathNew === $pathOldOrig) && $urlNew === $urlOld);
}
/**
* Get migration data to store in wp-options
*
* @return MigrateData
*/
public static function getMigrationData(): MigrateData
{
$sec = Security::getInstance();
$paramsManager = PrmMng::getInstance();
$result = new MigrateData();
$ac = DUPX_ArchiveConfig::getInstance();
$overwriteData = $paramsManager->getValue(PrmMng::PARAM_OVERWRITE_SITE_DATA);
$result->installerVersion = DUPX_VERSION;
$result->installType = $paramsManager->getValue(PrmMng::PARAM_INST_TYPE);
$result->installTime = gmdate("Y-m-d H:i:s");
$result->logicModes = self::getLogicModes();
$result->template = PrmMng::getInstance()->getValue(PrmMng::PARAM_TEMPLATE);
$result->restoreBackupMode = self::isRestoreBackup();
$result->recoveryMode = self::isRecoveryMode();
$result->archivePath = $sec->getArchivePath();
$result->packageId = $ac->packInfo->packageId;
$result->packageHash = $ac->packInfo->packageHash;
$result->installerPath = $sec->getBootFilePath();
$result->installerBootLog = $sec->getBootLogFile();
$result->installerLog = Log::getLogFilePath();
$result->dupInstallerPath = DUPX_INIT;
$result->origFileFolderPath = InstallerOrigFileMng::getInstance()->getMainFolder();
$result->safeMode = $paramsManager->getValue(PrmMng::PARAM_SAFE_MODE);
$result->cleanInstallerFiles = $paramsManager->getValue(PrmMng::PARAM_AUTO_CLEAN_INSTALLER_FILES);
$result->licenseType = $ac->license_type;
$result->phpVersion = $ac->version_php;
$result->archiveType = $ac->isZipArchive() ? 'zip' : 'dup';
$result->siteSize = $ac->fileInfo->size;
$result->siteNumFiles = ($ac->fileInfo->dirCount + $ac->fileInfo->fileCount);
$result->siteDbSize = $ac->dbInfo->tablesSizeOnDisk;
$result->siteDBNumTables = $ac->dbInfo->tablesFinalCount;
$result->components = $ac->components;
$result->ustatIdentifier = $overwriteData['ustatIdentifier'];
// Staging mode data - grouped in array for organization
$result->staging = [
'enabled' => !empty($overwriteData['stagingMode']),
'mainSiteUrl' => $overwriteData['mainSiteUrl'] ?? '',
'pageUrl' => $overwriteData['stagingPageUrl'] ?? '',
'identifier' => $overwriteData['stagingIdentifier'] ?? '',
'colorScheme' => $overwriteData['colorScheme'] ?? 'fresh',
'title' => $overwriteData['stagingTitle'] ?? '',
];
return $result;
}
/**
* Get admin login url
*
* @return string
*/
public static function getAdminLogin()
{
$paramsManager = PrmMng::getInstance();
if (self::isAddSiteOnMultisite()) {
$overwriteData = $paramsManager->getValue(PrmMng::PARAM_OVERWRITE_SITE_DATA);
$loginUrl = $overwriteData['urls']['login'];
} else {
$oldUrl = $paramsManager->getValue(PrmMng::PARAM_SITE_URL_OLD);
$newUrl = $paramsManager->getValue(PrmMng::PARAM_SITE_URL);
$archiveConfig = DUPX_ArchiveConfig::getInstance();
$loginUrl = DUPX_ArchiveConfig::getNewSubUrl($oldUrl, $newUrl, $archiveConfig->getRealValue("loginUrl"));
}
return $loginUrl;
}
/**
* Get install type
*
* @return int install type Enum self::TYPE_*
*/
public static function getInstType()
{
return PrmMng::getInstance()->getValue(PrmMng::PARAM_INST_TYPE);
}
/**
* @param int|int[] $type list of types to check
* @param int $typeToCheck if is null get param install time or check this
*
* @return bool
*/
public static function isInstType($type, $typeToCheck = null): bool
{
$currentType = is_null($typeToCheck) ? self::getInstType() : $typeToCheck;
if (is_array($type)) {
return in_array($currentType, $type);
} else {
return $currentType === $type;
}
}
/**
* Get install logic modes
*
* @return string[]
*/
public static function getLogicModes(): array
{
$modes = [];
if (self::isImportFromBackendMode()) {
$modes[] = self::LOGIC_MODE_IMPORT;
}
if (self::isRecoveryMode()) {
$modes[] = self::LOGIC_MODE_RECOVERY;
}
if (self::isClassicInstall()) {
$modes[] = self::LOGIC_MODE_CLASSIC;
}
if (self::isOverwrite()) {
$modes[] = self::LOGIC_MODE_OVERWRITE;
}
if (self::isBridgeInstall()) {
$modes[] = self::LOGIC_MODE_BRIDGE;
}
if (self::isRestoreBackup()) {
$modes[] = self::LOGIC_MODE_RESTORE_BACKUP;
}
return $modes;
}
}

View File

@@ -0,0 +1,37 @@
<?php
/**
* Installer params manager
*
* Standard: PSR-2
*
* @link http://www.php-fig.org/psr/psr-2 Full Documentation
*
* @package SC\DUPX\U
*/
namespace Duplicator\Installer\Core\Params\Descriptors;
use Duplicator\Installer\Core\Params\Items\ParamForm;
use Duplicator\Installer\Core\Params\Items\ParamItem;
interface DescriptorInterface
{
/**
* Init params
*
* @param (ParamItem|ParamForm)[] $params params list
*
* @return void
*/
public static function init(&$params);
/**
* Update params after overwrite logic
*
* @param (ParamItem|ParamForm)[] $params params list
*
* @return void
*/
public static function updateParamsAfterOverwrite($params);
}

View File

@@ -0,0 +1,386 @@
<?php
/**
* CPanel params descriptions
*
* @category Duplicator
* @package Installer
* @author Snapcreek <admin@snapcreek.com>
* @copyright 2011-2021 Snapcreek LLC
* @license https://www.gnu.org/licenses/gpl-3.0.html GPLv3
*/
namespace Duplicator\Installer\Core\Params\Descriptors;
use Duplicator\Installer\Core\Params\PrmMng;
use Duplicator\Installer\Core\Params\Descriptors\ParamsDescriptors;
use Duplicator\Installer\Core\Params\Items\ParamItem;
use Duplicator\Installer\Core\Params\Items\ParamForm;
use Duplicator\Installer\Core\Params\Items\ParamFormPass;
use Duplicator\Installer\Core\Params\Items\ParamOption;
use Duplicator\Libs\Snap\SnapUtil;
/**
* class where all parameters are initialized. Used by the param manager
*/
final class ParamDescCPanel implements DescriptorInterface
{
const INVALID_EMPTY = 'can\'t be empty';
/**
* Init params
*
* @param (ParamItem|ParamForm)[] $params params list
*
* @return void
*/
public static function init(&$params): void
{
$params[PrmMng::PARAM_CPNL_CAN_SELECTED] = new ParamItem(
PrmMng::PARAM_CPNL_CAN_SELECTED,
ParamItem::TYPE_BOOL,
['default' => true]
);
$params[PrmMng::PARAM_CPNL_HOST] = new ParamForm(
PrmMng::PARAM_CPNL_HOST,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_TEXT,
[
'default' => "https://" . parse_url(DUPX_ROOT_URL, PHP_URL_HOST) . ":2083",
'sanitizeCallback' => [
SnapUtil::class,
'sanitizeNSCharsNewline',
],
'validateCallback' => [
self::class,
'validateNoEmptyIfCpanel',
],
'invalidMessage' => self::INVALID_EMPTY,
],
[
'label' => 'CPanel host:',
'wrapperClasses' => ['revalidate-on-change'],
'attr' => [
'required' => 'true',
'placeholder' => 'cPanel url',
],
'subNote' => '<span id="cpnl-host-warn">'
. 'Caution: The cPanel host name and URL in the browser address bar do not match, '
. 'in rare cases this may be intentional.'
. 'Please be sure this is the correct server to avoid data loss.</span>',
'postfix' => [
'type' => 'button',
'label' => 'get',
'btnAction' => 'DUPX.getcPanelURL(this);',
],
]
);
$params[PrmMng::PARAM_CPNL_USER] = new ParamForm(
PrmMng::PARAM_CPNL_USER,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_TEXT,
[
'default' => '',
'sanitizeCallback' => [
SnapUtil::class,
'sanitizeNSCharsNewline',
],
'validateCallback' => [
self::class,
'validateNoEmptyIfCpanel',
],
'invalidMessage' => self::INVALID_EMPTY,
],
[
'label' => 'CPanel username:',
'wrapperClasses' => ['revalidate-on-change'],
'attr' => [
'required' => 'required',
'data-parsley-pattern' => '/^[\w.-~]+$/',
'placeholder' => 'cPanel username',
],
]
);
$params[PrmMng::PARAM_CPNL_PASS] = new ParamFormPass(
PrmMng::PARAM_CPNL_PASS,
ParamFormPass::TYPE_STRING,
ParamFormPass::FORM_TYPE_PWD_TOGGLE,
[
'default' => '',
'sanitizeCallback' => [
SnapUtil::class,
'sanitizeNSCharsNewline',
],
'validateCallback' => [
self::class,
'validateNoEmptyIfCpanel',
],
'invalidMessage' => self::INVALID_EMPTY,
],
[
'label' => 'CPanel password:',
'wrapperClasses' => ['revalidate-on-change'],
'attr' => [
'required' => 'true',
'placeholder' => 'cPanel password',
],
]
);
/** @var ParamForm */
$paramFormObj = $params[PrmMng::PARAM_DB_ACTION];
$paramFormObj = $paramFormObj->getCopyWithNewName(
PrmMng::PARAM_CPNL_DB_ACTION,
[],
[
'status' => ParamForm::STATUS_DISABLED,
]
);
// force create database enable for cpanel
$paramFormObj->setOptionStatus(0, ParamOption::OPT_ENABLED);
$params[PrmMng::PARAM_CPNL_DB_ACTION] = $paramFormObj;
$params[PrmMng::PARAM_CPNL_PREFIX] = new ParamForm(
PrmMng::PARAM_CPNL_PREFIX,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_HIDDEN,
[
'default' => '',
'sanitizeCallback' => [
SnapUtil::class,
'sanitizeNSCharsNewline',
],
],
[
'label' => 'Control panel prefix',
'renderLabel' => false,
]
);
/** @var ParamForm */
$paramFormObj = $params[PrmMng::PARAM_DB_HOST];
$paramFormObj = $paramFormObj->getCopyWithNewName(
PrmMng::PARAM_CPNL_DB_HOST,
[
'default' => 'localhost',
'sanitizeCallback' => [
SnapUtil::class,
'sanitizeNSCharsNewline',
],
'validateCallback' => [
self::class,
'validateNoEmptyIfCpanel',
],
'invalidMessage' => self::INVALID_EMPTY,
],
[
'status' => ParamForm::STATUS_DISABLED,
'attr' => ['required' => 'true'],
]
);
$params[PrmMng::PARAM_CPNL_DB_HOST] = $paramFormObj;
$params[PrmMng::PARAM_CPNL_DB_NAME_SEL] = new ParamForm(
PrmMng::PARAM_CPNL_DB_NAME_SEL,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_SELECT,
[
'default' => null,
'sanitizeCallback' => [
SnapUtil::class,
'sanitizeNSCharsNewline',
],
],
[
'label' => 'Database:',
'wrapperClasses' => ['revalidate-on-change'],
'status' => ParamForm::STATUS_DISABLED,
'attr' => [
'required' => 'true',
'data-parsley-pattern' => '^((?!-- Select Database --).)*$',
],
'subNote' => '<span class="s2-warning-emptydb">'
. 'Warning: The selected "Action" above will remove <u>all data</u> from this database!'
. '</span>',
]
);
/** @var ParamForm */
$paramFormObj = $params[PrmMng::PARAM_DB_NAME];
$paramFormObj = $paramFormObj->getCopyWithNewName(
PrmMng::PARAM_CPNL_DB_NAME_TXT,
[
'default' => '',
'sanitizeCallback' => [
SnapUtil::class,
'sanitizeNSCharsNewline',
],
'validateCallback' => function ($value, ParamItem $paramObj): bool {
$paramManager = PrmMng::getInstance();
if (
$paramManager->getValue(PrmMng::PARAM_DB_VIEW_MODE) === 'cpnl' &&
$paramManager->getValue(PrmMng::PARAM_CPNL_DB_ACTION) === \DUPX_DBInstall::DBACTION_CREATE
) {
return ParamsDescriptors::validateNotEmpty($value, $paramObj);
}
return true;
},
'invalidMessage' => self::INVALID_EMPTY,
],
[
'label' => 'Database:',
'status' => ParamForm::STATUS_DISABLED,
'attr' => [
'required' => 'true',
'data-parsley-pattern' => '/^[\w.-~]+$/',
'data-parsley-errors-container' => '#cpnl-dbname-txt-error',
],
'subNote' => '<span id="cpnl-dbname-txt-error"></span>',
'prefix' => [
'type' => 'label',
'label' => '',
'id' => 'cpnl-prefix-dbname',
],
]
);
$params[PrmMng::PARAM_CPNL_DB_NAME_TXT] = $paramFormObj;
$params[PrmMng::PARAM_CPNL_DB_USER_SEL] = new ParamForm(
PrmMng::PARAM_CPNL_DB_USER_SEL,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_SELECT,
[
'default' => null,
'sanitizeCallback' => [
SnapUtil::class,
'sanitizeNSCharsNewline',
],
],
[
'label' => 'User:',
'wrapperClasses' => ['revalidate-on-change'],
'status' => ParamForm::STATUS_DISABLED,
'attr' => [
'required' => 'true',
'data-parsley-pattern' => '^((?!-- Select User --).)*$',
],
]
);
/** @var ParamForm */
$paramFormObj = $params[PrmMng::PARAM_DB_USER];
$paramFormObj = $paramFormObj->getCopyWithNewName(
PrmMng::PARAM_CPNL_DB_USER_TXT,
[
'default' => '',
'sanitizeCallback' => [
SnapUtil::class,
'sanitizeNSCharsNewline',
],
'validateCallback' => function ($value, ParamItem $paramObj): bool {
$paramManager = PrmMng::getInstance();
if (
$paramManager->getValue(PrmMng::PARAM_DB_VIEW_MODE) === 'cpnl' &&
$paramManager->getValue(PrmMng::PARAM_CPNL_DB_USER_CHK) === true
) {
return ParamsDescriptors::validateNotEmpty($value, $paramObj);
}
return true;
},
'invalidMessage' => self::INVALID_EMPTY,
],
[
'label' => 'User:',
'status' => ParamForm::STATUS_DISABLED,
'attr' => [
'required' => 'true',
'data-parsley-pattern' => '/^[a-zA-Z0-9-_]+$/',
'data-parsley-errors-container' => '#cpnl-dbuser-txt-error',
'data-parsley-cpnluser' => "16",
],
'subNote' => '<span id="cpnl-dbuser-txt-error"></span>',
'prefix' => [
'type' => 'label',
'label' => '',
'id' => 'cpnl-prefix-dbuser',
],
]
);
$params[PrmMng::PARAM_CPNL_DB_USER_TXT] = $paramFormObj;
$params[PrmMng::PARAM_CPNL_DB_USER_CHK] = new ParamForm(
PrmMng::PARAM_CPNL_DB_USER_CHK,
ParamForm::TYPE_BOOL,
ParamForm::FORM_TYPE_CHECKBOX,
['default' => false],
[
'label' => ' ',
'wrapperClasses' => ['revalidate-on-change'],
'checkboxLabel' => 'Create New Database User',
]
);
/** @var ParamForm */
$paramFormObj = $params[PrmMng::PARAM_DB_PASS];
$paramFormObj = $paramFormObj->getCopyWithNewName(
PrmMng::PARAM_CPNL_DB_PASS,
[],
[
'status' => ParamForm::STATUS_DISABLED,
'attr' => ['required' => 'true'],
]
);
$params[PrmMng::PARAM_CPNL_DB_PASS] = $paramFormObj;
$params[PrmMng::PARAM_CPNL_IGNORE_PREFIX] = new ParamForm(
PrmMng::PARAM_CPNL_IGNORE_PREFIX,
ParamForm::TYPE_BOOL,
ParamForm::FORM_TYPE_CHECKBOX,
['default' => false],
[
'label' => 'CPanel Prefix',
'wrapperClasses' => ['revalidate-on-change'],
'checkboxLabel' => 'Ignore',
'attr' => ['onclick' => 'DUPX.cpnlPrefixIgnore();'],
]
);
}
/**
* Validate function for cpanle params
*
* @param mixed $value input value
* @param ParamItem $paramObj current param object
*
* @return boolean
*/
public static function validateNoEmptyIfCpanel($value, ParamItem $paramObj)
{
if (PrmMng::getInstance()->getValue(PrmMng::PARAM_DB_VIEW_MODE) !== 'cpnl') {
return true;
}
return ParamsDescriptors::validateNotEmpty($value, $paramObj);
}
/**
* Update params after overwrite logic
*
* @param ParamItem[]|ParamForm[] $params params list
*
* @return void
*/
public static function updateParamsAfterOverwrite($params): void
{
}
}

View File

@@ -0,0 +1,637 @@
<?php
/**
* Configs(htaccess, wp-config ...) params descriptions
*
* @category Duplicator
* @package Installer
* @author Snapcreek <admin@snapcreek.com>
* @copyright 2011-2021 Snapcreek LLC
* @license https://www.gnu.org/licenses/gpl-3.0.html GPLv3
*/
namespace Duplicator\Installer\Core\Params\Descriptors;
use Duplicator\Installer\Core\Params\PrmMng;
use Duplicator\Installer\Addons\ProBase\License;
use Duplicator\Installer\Core\InstState;
use Duplicator\Installer\Core\Params\Items\ParamItem;
use Duplicator\Installer\Core\Params\Items\ParamForm;
use Duplicator\Installer\Core\Params\Items\ParamOption;
use DUPX_Template;
use DUPX_WPConfig;
use Exception;
/**
* class where all parameters are initialized. Used by the param manager
*/
final class ParamDescConfigs implements DescriptorInterface
{
/**
* Init params
*
* @param ParamItem[]|ParamForm[] $params params list
*
* @return void
*/
public static function init(&$params): void
{
$params[PrmMng::PARAM_INST_TYPE] = new ParamForm(
PrmMng::PARAM_INST_TYPE,
ParamForm::TYPE_INT,
ParamForm::FORM_TYPE_RADIO,
[
'default' => InstState::TYPE_NOT_SET,
'acceptValues' => [
self::class,
'getInstallTypesAcceptValues',
],
],
[
'status' => ParamForm::STATUS_ENABLED,
'label' => 'Install Type:',
'wrapperClasses' => [
'group-block',
'revalidate-on-change',
],
'options' => self::getInstallTypeOptions(),
]
);
$params[PrmMng::PARAM_WP_CONFIG] = new ParamForm(
PrmMng::PARAM_WP_CONFIG,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_SELECT,
[
'default' => 'modify',
'acceptValues' => [
'modify',
'nothing',
'new',
],
],
[
'label' => 'WordPress:',
'classes' => ['requires-db-disable'],
'wrapperClasses' => 'medium',
'status' => function (ParamItem $paramObj): string {
if (
InstState::isRestoreBackup() ||
InstState::isAddSiteOnMultisite()
) {
return ParamForm::STATUS_INFO_ONLY;
} else {
return ParamForm::STATUS_ENABLED;
}
},
'options' => [
new ParamOption('nothing', 'Do nothing'),
new ParamOption(
'modify',
'Modify original',
fn(ParamOption $opt): string => DUPX_WPConfig::isSourceWpConfigValid() ? ParamOption::OPT_ENABLED : ParamOption::OPT_DISABLED
),
new ParamOption('new', 'Create new from wp-config sample'),
],
'subNote' => 'wp-config.php',
]
);
$params[PrmMng::PARAM_HTACCESS_CONFIG] = new ParamForm(
PrmMng::PARAM_HTACCESS_CONFIG,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_SELECT,
[
'default' => 'new',
'acceptValues' => [
'new',
'original',
'nothing',
],
],
[
'label' => 'Apache:',
'classes' => ['requires-db-disable'],
'wrapperClasses' => 'medium',
'status' => function (ParamItem $paramObj): string {
if (
InstState::isRestoreBackup() ||
InstState::isAddSiteOnMultisite()
) {
return ParamForm::STATUS_INFO_ONLY;
} else {
return ParamForm::STATUS_ENABLED;
}
},
'options' => [
new ParamOption('nothing', 'Do nothing'),
new ParamOption('original', 'Retain original from Archive.zip/daf'),
new ParamOption('new', 'Create new'),
],
'subNote' => '.htaccess',
]
);
$params[PrmMng::PARAM_OTHER_CONFIG] = new ParamForm(
PrmMng::PARAM_OTHER_CONFIG,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_SELECT,
[
'default' => 'new',
'acceptValues' => [
'new',
'original',
'nothing',
],
],
[
'label' => 'General:',
'classes' => ['requires-db-disable'],
'wrapperClasses' => 'medium',
'status' => function (ParamItem $paramObj): string {
if (
InstState::isRestoreBackup() ||
InstState::isAddSiteOnMultisite()
) {
return ParamForm::STATUS_INFO_ONLY;
} else {
return ParamForm::STATUS_ENABLED;
}
},
'options' => [
new ParamOption('nothing', 'Do nothing'),
new ParamOption('original', 'Retain original from Archive.zip/daf'),
new ParamOption('new', 'Reset'),
],
'subNote' => 'includes: php.ini, user.ini, web.config',
]
);
}
/**
* Update params after overwrite logic
*
* @param ParamItem[]|ParamForm[] $params params list
*
* @return void
*/
public static function updateParamsAfterOverwrite($params): void
{
$installType = $params[PrmMng::PARAM_INST_TYPE]->getValue();
if ($installType == InstState::TYPE_NOT_SET) {
$acceptValues = $params[PrmMng::PARAM_INST_TYPE]->getAcceptValues();
$params[PrmMng::PARAM_INST_TYPE]->setValue(self::getInstTypeByPriority($acceptValues));
}
$installType = $params[PrmMng::PARAM_INST_TYPE]->getValue();
if (InstState::isRestoreBackup($installType)) {
if (\DUPX_Custom_Host_Manager::getInstance()->isManaged()) {
$params[PrmMng::PARAM_WP_CONFIG]->setValue('nothing');
$params[PrmMng::PARAM_HTACCESS_CONFIG]->setValue('nothing');
$params[PrmMng::PARAM_OTHER_CONFIG]->setValue('nothing');
} else {
$params[PrmMng::PARAM_WP_CONFIG]->setValue('modify');
$params[PrmMng::PARAM_HTACCESS_CONFIG]->setValue('original');
$params[PrmMng::PARAM_OTHER_CONFIG]->setValue('original');
}
}
if (!DUPX_WPConfig::isSourceWpConfigValid()) {
if ($params[PrmMng::PARAM_WP_CONFIG]->getValue() === 'modify') {
$params[PrmMng::PARAM_WP_CONFIG]->setValue('new');
}
}
}
/**
* Return default install type from install types enabled
*
* @param int[] $acceptValues install types enabled
*
* @return int
*/
protected static function getInstTypeByPriority($acceptValues): int
{
$defaultPriority = [
InstState::TYPE_RECOVERY_MSUBDOMAIN,
InstState::TYPE_RECOVERY_MSUBFOLDER,
InstState::TYPE_RECOVERY_SINGLE,
InstState::TYPE_RBACKUP_MSUBDOMAIN,
InstState::TYPE_RBACKUP_MSUBFOLDER,
InstState::TYPE_RBACKUP_SINGLE,
InstState::TYPE_SINGLE_ON_SUBDOMAIN,
InstState::TYPE_SINGLE_ON_SUBFOLDER,
InstState::TYPE_SUBSITE_ON_SUBDOMAIN,
InstState::TYPE_SUBSITE_ON_SUBFOLDER,
InstState::TYPE_MSUBDOMAIN,
InstState::TYPE_MSUBFOLDER,
InstState::TYPE_SINGLE,
InstState::TYPE_STANDALONE,
];
foreach ($defaultPriority as $current) {
if (in_array($current, $acceptValues)) {
return $current;
}
}
throw new Exception('No default value found on proprity list');
}
/**
*
* @return ParamOption[]
*/
protected static function getInstallTypeOptions(): array
{
$result = [];
$option = new ParamOption(InstState::TYPE_RBACKUP_SINGLE, 'Restore single site', [self::class, 'typeOptionsVisibility']);
$option->setNote([self::class, 'getInstallTypesNotes']);
$result[] = $option;
$option = new ParamOption(
InstState::TYPE_RBACKUP_MSUBDOMAIN,
'<b>Restore</b> multisite network',
[
self::class,
'typeOptionsVisibility',
]
);
$option->setNote([self::class, 'getInstallTypesNotes']);
$result[] = $option;
$option = new ParamOption(
InstState::TYPE_RBACKUP_MSUBFOLDER,
'<b>Restore</b> multisite network',
[
self::class,
'typeOptionsVisibility',
]
);
$option->setNote([self::class, 'getInstallTypesNotes']);
$result[] = $option;
$option = new ParamOption(
InstState::TYPE_SINGLE,
'<b>Full</b> install single site',
[
self::class,
'typeOptionsVisibility',
]
);
$option->setNote([self::class, 'getInstallTypesNotes']);
$result[] = $option;
$option = new ParamOption(
InstState::TYPE_MSUBDOMAIN,
'<b>Full</b> install multisite network',
[
self::class,
'typeOptionsVisibility',
]
);
$option->setNote([self::class, 'getInstallTypesNotes']);
$result[] = $option;
$option = new ParamOption(
InstState::TYPE_MSUBFOLDER,
'<b>Full</b> install multisite network',
[
self::class,
'typeOptionsVisibility',
]
);
$option->setNote([self::class, 'getInstallTypesNotes']);
$result[] = $option;
$option = new ParamOption(
InstState::TYPE_STANDALONE,
'<b>Convert</b> network subsite to standalone site',
[
self::class,
'typeOptionsVisibility',
]
);
$option->setNote([self::class, 'getInstallTypesNotes']);
$result[] = $option;
$option = new ParamOption(
InstState::TYPE_SINGLE_ON_SUBDOMAIN,
'<b>Import</b> single site into multisite network',
[
self::class,
'typeOptionsVisibility',
]
);
$option->setNote([self::class, 'getInstallTypesNotes']);
$result[] = $option;
$option = new ParamOption(
InstState::TYPE_SINGLE_ON_SUBFOLDER,
'<b>Import</b> single site into multisite network',
[
self::class,
'typeOptionsVisibility',
]
);
$option->setNote([self::class, 'getInstallTypesNotes']);
$result[] = $option;
$option = new ParamOption(
InstState::TYPE_SUBSITE_ON_SUBDOMAIN,
'<b>Import</b> subsite into multisite network',
[
self::class,
'typeOptionsVisibility',
]
);
$option->setNote([self::class, 'getInstallTypesNotes']);
$result[] = $option;
$option = new ParamOption(
InstState::TYPE_SUBSITE_ON_SUBFOLDER,
'<b>Import</b> subsite into multisite network',
[
self::class,
'typeOptionsVisibility',
]
);
$option->setNote([self::class, 'getInstallTypesNotes']);
$result[] = $option;
$option = new ParamOption(
InstState::TYPE_RECOVERY_SINGLE,
'<b>Restore Backup</b> single site',
[
self::class,
'typeOptionsVisibility',
]
);
$option->setNote([self::class, 'getInstallTypesNotes']);
$result[] = $option;
$option = new ParamOption(
InstState::TYPE_RECOVERY_MSUBDOMAIN,
'<b>Restore Backup</b> multisite network',
[
self::class,
'typeOptionsVisibility',
]
);
$option->setNote([self::class, 'getInstallTypesNotes']);
$result[] = $option;
$option = new ParamOption(
InstState::TYPE_RECOVERY_MSUBFOLDER,
'<b>Restore Backup</b> multisite network',
[
self::class,
'typeOptionsVisibility',
]
);
$option->setNote([self::class, 'getInstallTypesNotes']);
$result[] = $option;
return $result;
}
/**
* Return option type status
*
* @param ParamOption $option install type option
*
* @return string option status
*/
public static function typeOptionsVisibility(ParamOption $option): string
{
$archiveConfig = \DUPX_ArchiveConfig::getInstance();
$overwriteData = PrmMng::getInstance()->getValue(PrmMng::PARAM_OVERWRITE_SITE_DATA);
$isOwrMode = PrmMng::getInstance()->getValue(PrmMng::PARAM_INSTALLER_MODE) === InstState::MODE_OVR_INSTALL;
switch ($option->value) {
case InstState::TYPE_SINGLE:
if ($archiveConfig->mu_mode != 0) {
return ParamOption::OPT_HIDDEN;
}
break;
case InstState::TYPE_MSUBDOMAIN:
if ($archiveConfig->mu_mode != 1) {
return ParamOption::OPT_HIDDEN;
}
break;
case InstState::TYPE_MSUBFOLDER:
if ($archiveConfig->mu_mode != 2) {
return ParamOption::OPT_HIDDEN;
}
break;
case InstState::TYPE_STANDALONE:
if ($archiveConfig->mu_mode == 0) {
return ParamOption::OPT_HIDDEN;
}
break;
case InstState::TYPE_SINGLE_ON_SUBDOMAIN:
if (!$isOwrMode || $archiveConfig->mu_mode > 0 || !$overwriteData['isMultisite'] || !$overwriteData['subdomain']) {
return ParamOption::OPT_HIDDEN;
}
break;
case InstState::TYPE_SINGLE_ON_SUBFOLDER:
if (!$isOwrMode || $archiveConfig->mu_mode > 0 || !$overwriteData['isMultisite'] || $overwriteData['subdomain']) {
return ParamOption::OPT_HIDDEN;
}
break;
case InstState::TYPE_SUBSITE_ON_SUBDOMAIN:
if (!$isOwrMode || $archiveConfig->mu_mode == 0 || !$overwriteData['isMultisite'] || !$overwriteData['subdomain']) {
return ParamOption::OPT_HIDDEN;
}
break;
case InstState::TYPE_SUBSITE_ON_SUBFOLDER:
if (!$isOwrMode || $archiveConfig->mu_mode == 0 || !$overwriteData['isMultisite'] || $overwriteData['subdomain']) {
return ParamOption::OPT_HIDDEN;
}
break;
case InstState::TYPE_RBACKUP_SINGLE:
if (
$archiveConfig->mu_mode != 0 ||
!InstState::isInstallerCreatedInThisLocation()
) {
return ParamOption::OPT_HIDDEN;
}
break;
case InstState::TYPE_RBACKUP_MSUBDOMAIN:
if (
$archiveConfig->mu_mode != 1 ||
!InstState::isInstallerCreatedInThisLocation()
) {
return ParamOption::OPT_HIDDEN;
}
break;
case InstState::TYPE_RBACKUP_MSUBFOLDER:
if (
$archiveConfig->mu_mode != 2 ||
!InstState::isInstallerCreatedInThisLocation()
) {
return ParamOption::OPT_HIDDEN;
}
break;
case InstState::TYPE_RECOVERY_SINGLE:
case InstState::TYPE_RECOVERY_MSUBDOMAIN:
case InstState::TYPE_RECOVERY_MSUBFOLDER:
return ParamOption::OPT_HIDDEN;
case InstState::TYPE_NOT_SET:
default:
throw new Exception('Install type not valid ' . $option->value);
}
$acceptValues = self::getInstallTypesAcceptValues();
return in_array($option->value, $acceptValues) ? ParamOption::OPT_ENABLED : ParamOption::OPT_DISABLED;
}
/**
*
* @return int[]
*/
public static function getInstallTypesAcceptValues(): array
{
$acceptValues = [];
$archiveConfig = \DUPX_ArchiveConfig::getInstance();
if (PrmMng::getInstance()->getValue(PrmMng::PARAM_TEMPLATE) === DUPX_Template::TEMPLATE_RECOVERY) {
switch ($archiveConfig->mu_mode) {
case 0:
$acceptValues[] = InstState::TYPE_RECOVERY_SINGLE;
break;
case 1:
$acceptValues[] = InstState::TYPE_RECOVERY_MSUBDOMAIN;
break;
case 2:
$acceptValues[] = InstState::TYPE_RECOVERY_MSUBFOLDER;
break;
}
return $acceptValues;
}
$overwriteData = PrmMng::getInstance()->getValue(PrmMng::PARAM_OVERWRITE_SITE_DATA);
$isManaged = \DUPX_Custom_Host_Manager::getInstance()->isManaged();
$isSameLocation = InstState::isInstallerCreatedInThisLocation();
switch ($archiveConfig->mu_mode) {
case 0:
$acceptValues[] = InstState::TYPE_SINGLE;
if ($isSameLocation) {
$acceptValues[] = InstState::TYPE_RBACKUP_SINGLE;
}
break;
case 1:
if (!$isManaged && !$archiveConfig->isPartialNetwork()) {
$acceptValues[] = InstState::TYPE_MSUBDOMAIN;
if ($isSameLocation) {
$acceptValues[] = InstState::TYPE_RBACKUP_MSUBDOMAIN;
}
}
break;
case 2:
if (!$isManaged && !$archiveConfig->isPartialNetwork()) {
$acceptValues[] = InstState::TYPE_MSUBFOLDER;
if ($isSameLocation) {
$acceptValues[] = InstState::TYPE_RBACKUP_MSUBFOLDER;
}
}
break;
}
if (
$archiveConfig->mu_mode > 0 &&
License::can(License::CAPABILITY_MULTISITE_PLUS)
) {
$acceptValues[] = InstState::TYPE_STANDALONE;
}
if (
InstState::isImportFromBackendMode() &&
$overwriteData['isMultisite'] &&
License::can(License::CAPABILITY_MULTISITE_PLUS)
) {
if (version_compare($overwriteData['wpVersion'], InstState::SUBSITE_IMPORT_WP_MIN_VERSION, '>=')) {
if ($archiveConfig->mu_mode == 0) {
if ($overwriteData['subdomain']) {
$acceptValues[] = InstState::TYPE_SINGLE_ON_SUBDOMAIN;
} else {
$acceptValues[] = InstState::TYPE_SINGLE_ON_SUBFOLDER;
}
} else {
if ($overwriteData['subdomain']) {
$acceptValues[] = InstState::TYPE_SUBSITE_ON_SUBDOMAIN;
} else {
$acceptValues[] = InstState::TYPE_SUBSITE_ON_SUBFOLDER;
}
}
} else {
$msg = "The option to import the site into the multisite network has been disabled " .
"since it's only available for WordPress <b>" . InstState::SUBSITE_IMPORT_WP_MIN_VERSION . " +</b>.<br>";
$msg .= " To overcome the issue please update WordPress to the most recent version.";
$noticeManager = \DUPX_NOTICE_MANAGER::getInstance();
$noticeManager->addNextStepNotice([
'shortMsg' => 'Import site into network disabled',
'level' => \DUPX_NOTICE_ITEM::NOTICE,
'longMsg' => $msg,
'longMsgMode' => \DUPX_NOTICE_ITEM::MSG_MODE_HTML,
], \DUPX_NOTICE_MANAGER::ADD_UNIQUE, 'import-site-into-network-disabled');
}
}
return $acceptValues;
}
/**
* Return install type option note
*
* @param ParamOption $option install type option
*
* @return string
*/
public static function getInstallTypesNotes(ParamOption $option): string
{
switch ($option->value) {
case InstState::TYPE_SINGLE:
case InstState::TYPE_MSUBDOMAIN:
case InstState::TYPE_MSUBFOLDER:
return '';
case InstState::TYPE_STANDALONE:
return (License::can(License::CAPABILITY_MULTISITE_PLUS) ? '' : License::getLicenseUpdateText());
case InstState::TYPE_SINGLE_ON_SUBDOMAIN:
case InstState::TYPE_SINGLE_ON_SUBFOLDER:
case InstState::TYPE_SUBSITE_ON_SUBDOMAIN:
case InstState::TYPE_SUBSITE_ON_SUBFOLDER:
$notes = [];
$overwriteData = PrmMng::getInstance()->getValue(PrmMng::PARAM_OVERWRITE_SITE_DATA);
if (!InstState::isImportFromBackendMode()) {
$notes[] = 'This functionality is active only in the Drag&Drop import.';
} else {
if (
!isset($overwriteData['wpVersion']) ||
version_compare($overwriteData['wpVersion'], InstState::SUBSITE_IMPORT_WP_MIN_VERSION, '<')
) {
$notes[] = 'WordPress ' . InstState::SUBSITE_IMPORT_WP_MIN_VERSION .
'+ is required on current multisite to enabled this function.';
}
}
if (!License::can(License::CAPABILITY_MULTISITE_PLUS)) {
$notes[] = License::getLicenseUpdateText();
}
return implode('<br>', $notes);
case InstState::TYPE_RBACKUP_SINGLE:
case InstState::TYPE_RBACKUP_MSUBDOMAIN:
case InstState::TYPE_RBACKUP_MSUBFOLDER:
case InstState::TYPE_RECOVERY_SINGLE:
case InstState::TYPE_RECOVERY_MSUBDOMAIN:
case InstState::TYPE_RECOVERY_MSUBFOLDER:
return '';
case InstState::TYPE_NOT_SET:
default:
throw new Exception('Install type not valid ' . $option->value);
}
}
}

View File

@@ -0,0 +1,190 @@
<?php
/**
* Controller params descriptions
*
* @category Duplicator
* @package Installer
* @author Snapcreek <admin@snapcreek.com>
* @copyright 2011-2021 Snapcreek LLC
* @license https://www.gnu.org/licenses/gpl-3.0.html GPLv3
*/
namespace Duplicator\Installer\Core\Params\Descriptors;
use Duplicator\Installer\Core\InstState;
use Duplicator\Installer\Core\Security;
use Duplicator\Installer\Core\Params\PrmMng;
use Duplicator\Installer\Core\Params\Items\ParamItem;
use Duplicator\Installer\Core\Params\Items\ParamForm;
use Duplicator\Libs\Snap\SnapUtil;
/**
* class where all parameters are initialized. Used by the param manager
*/
final class ParamDescController implements DescriptorInterface
{
/**
* Init params
*
* @param ParamItem[]|ParamForm[] $params params list
*
* @return void
*/
public static function init(&$params): void
{
$params[PrmMng::PARAM_FINAL_REPORT_DATA] = new ParamItem(
PrmMng::PARAM_FINAL_REPORT_DATA,
ParamItem::TYPE_ARRAY_MIXED,
[
'default' => [
'extraction' => [
'table_count' => 0,
'table_rows' => 0,
'query_errs' => 0,
],
'replace' => [
'scan_tables' => 0,
'scan_rows' => 0,
'scan_cells' => 0,
'updt_tables' => 0,
'updt_rows' => 0,
'updt_cells' => 0,
'errsql' => 0,
'errser' => 0,
'errkey' => 0,
'errsql_sum' => 0,
'errser_sum' => 0,
'errkey_sum' => 0,
'err_all' => 0,
'warn_all' => 0,
'warnlist' => [],
],
],
]
);
$params[PrmMng::PARAM_INSTALLER_MODE] = new ParamItem(
PrmMng::PARAM_INSTALLER_MODE,
ParamItem::TYPE_INT,
[
'default' => InstState::MODE_UNKNOWN,
'acceptValues' => [
InstState::MODE_UNKNOWN,
InstState::MODE_STD_INSTALL,
InstState::MODE_OVR_INSTALL,
],
]
);
$params[PrmMng::PARAM_OVERWRITE_SITE_DATA] = new ParamItem(
PrmMng::PARAM_OVERWRITE_SITE_DATA,
ParamItem::TYPE_ARRAY_MIXED,
[
'default' => InstState::overwriteDataDefault(),
]
);
$params[PrmMng::PARAM_DEBUG] = new ParamItem(
PrmMng::PARAM_DEBUG,
ParamItem::TYPE_BOOL,
[
'persistence' => true,
'default' => false,
]
);
$params[PrmMng::PARAM_DEBUG_PARAMS] = new ParamItem(
PrmMng::PARAM_DEBUG_PARAMS,
ParamItem::TYPE_BOOL,
[
'persistence' => true,
'default' => false,
]
);
$params[PrmMng::PARAM_CTRL_ACTION] = new ParamItem(
PrmMng::PARAM_CTRL_ACTION,
ParamForm::TYPE_STRING,
[
'persistence' => false,
'default' => '',
'acceptValues' => [
'',
'ajax',
'secure',
'ctrl-step1',
'ctrl-step2',
'ctrl-step3',
'ctrl-step4',
'help',
],
]
);
$params[PrmMng::PARAM_STEP_ACTION] = new ParamItem(
PrmMng::PARAM_STEP_ACTION,
ParamForm::TYPE_STRING,
[
'persistence' => false,
'default' => '',
'acceptValues' => [
'',
\DUPX_CTRL::ACTION_STEP_INIZIALIZED,
\DUPX_CTRL::ACTION_STEP_ON_VALIDATE,
\DUPX_CTRL::ACTION_STEP_SET_TEMPLATE,
],
]
);
$params[Security::CTRL_TOKEN] = new ParamItem(
Security::CTRL_TOKEN,
ParamForm::TYPE_STRING,
[
'persistence' => false,
'default' => null,
'sanitizeCallback' => [
SnapUtil::class,
'sanitizeNSCharsNewline',
],
]
);
$params[PrmMng::PARAM_ROUTER_ACTION] = new ParamItem(
PrmMng::PARAM_ROUTER_ACTION,
ParamItem::TYPE_STRING,
[
'persistence' => false,
'default' => 'router',
'acceptValues' => ['router'],
]
);
$params[PrmMng::PARAM_TEMPLATE] = new ParamItem(
PrmMng::PARAM_TEMPLATE,
ParamForm::TYPE_STRING,
[
'default' => \DUPX_Template::TEMPLATE_BASE,
'acceptValues' => [
\DUPX_Template::TEMPLATE_BASE,
\DUPX_Template::TEMPLATE_ADVANCED,
\DUPX_Template::TEMPLATE_IMPORT_BASE,
\DUPX_Template::TEMPLATE_IMPORT_ADVANCED,
\DUPX_Template::TEMPLATE_RECOVERY,
],
]
);
}
/**
* Update params after overwrite logic
*
* @param ParamItem[]|ParamForm[] $params params list
*
* @return void
*/
public static function updateParamsAfterOverwrite($params): void
{
}
}

View File

@@ -0,0 +1,570 @@
<?php
/**
* Database params descriptions
*
* @category Duplicator
* @package Installer
* @author Snapcreek <admin@snapcreek.com>
* @copyright 2011-2021 Snapcreek LLC
* @license https://www.gnu.org/licenses/gpl-3.0.html GPLv3
*/
namespace Duplicator\Installer\Core\Params\Descriptors;
use Duplicator\Installer\Core\Params\PrmMng;
use Duplicator\Installer\Addons\ProBase\License;
use Duplicator\Installer\Core\Params\Descriptors\ParamsDescriptors;
use Duplicator\Installer\Core\Params\Items\ParamItem;
use Duplicator\Installer\Core\Params\Items\ParamForm;
use Duplicator\Installer\Core\Params\Items\ParamOption;
use Duplicator\Installer\Core\Params\Items\ParamFormTables;
use Duplicator\Installer\Core\Params\Items\ParamFormPass;
use Duplicator\Installer\Utils\Log\Log;
use Duplicator\Libs\Snap\SnapJson;
use Duplicator\Libs\Snap\SnapUtil;
use Duplicator\Installer\Core\InstState;
/**
* class where all parameters are initialized. Used by the param manager
*/
final class ParamDescDatabase implements DescriptorInterface
{
const INVALID_EMPTY = 'can\'t be empty';
const EMPTY_COLLATION_LABEL = ' --- DEFAULT --- ';
const DEFAULT_CHARSET_POSTFIX = ' (Default)';
const DEFAULT_COLLATE_POSTFIX = ' (Default)';
const SPLIT_CREATE_MAX_VALUE_TO_DEFAULT = 200;
/**
* Init params
*
* @param ParamItem[]|ParamForm[] $params params list
*
* @return void
*/
public static function init(&$params): void
{
$archiveConfig = \DUPX_ArchiveConfig::getInstance();
$params[PrmMng::PARAM_DB_DISPLAY_OVERWIRE_WARNING] = new ParamItem(
PrmMng::PARAM_DB_DISPLAY_OVERWIRE_WARNING,
ParamItem::TYPE_BOOL,
['default' => true]
);
$params[PrmMng::PARAM_DB_VIEW_MODE] = new ParamForm(
PrmMng::PARAM_DB_VIEW_MODE,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_BGROUP,
[
'default' => 'basic',
'acceptValues' => [
'basic',
'cpnl',
],
],
[
'label' => 'Database view mode',
'renderLabel' => false,
'options' => [
new ParamOption('basic', 'Default'),
new ParamOption('cpnl', 'CPanel'),
],
'wrapperClasses' => [
'revalidate-on-change',
'align-right',
'requires-db-hide',
],
'inputContainerClasses' => ['small'],
]
);
$params[PrmMng::PARAM_DB_HOST] = new ParamForm(
PrmMng::PARAM_DB_HOST,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_TEXT,
[
'persistence' => true,
'default' => 'localhost',
'sanitizeCallback' => [
SnapUtil::class,
'sanitizeNSCharsNewline',
],
'validateCallback' => [
self::class,
'validateNoEmptyIfBasic',
],
'invalidMessage' => self::INVALID_EMPTY,
],
[
'label' => 'Host:',
'wrapperClasses' => [
'revalidate-on-change',
'requires-db-hide',
],
'attr' => [
'required' => 'required',
'placeholder' => 'localhost',
],
]
);
$params[PrmMng::PARAM_DB_NAME] = new ParamForm(
PrmMng::PARAM_DB_NAME,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_TEXT,
[
'persistence' => true,
'default' => '',
'sanitizeCallback' => [
SnapUtil::class,
'sanitizeNSCharsNewline',
],
'validateCallback' => [
self::class,
'validateNoEmptyIfBasic',
],
'invalidMessage' => self::INVALID_EMPTY,
],
[
'label' => 'Database:',
'wrapperClasses' => [
'revalidate-on-change',
'requires-db-hide',
],
'attr' => [
'required' => 'required',
'placeholder' => 'new or existing database name',
],
'subNote' => dupxTplRender('parts/params/db-name-notes', [], false),
]
);
$params[PrmMng::PARAM_DB_USER] = new ParamForm(
PrmMng::PARAM_DB_USER,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_TEXT,
[
'persistence' => true,
'default' => '',
'sanitizeCallback' => [
SnapUtil::class,
'sanitizeNSCharsNewline',
],
'validateCallback' => [
self::class,
'validateNoEmptyIfBasic',
],
'invalidMessage' => self::INVALID_EMPTY,
],
[
'label' => 'User:',
'wrapperClasses' => [
'revalidate-on-change',
'requires-db-hide',
],
'attr' => [
'placeholder' => 'valid database username',
// Can be written field wise
// Ref. https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion
'autocomplete' => "off",
],
]
);
$params[PrmMng::PARAM_DB_PASS] = new ParamFormPass(
PrmMng::PARAM_DB_PASS,
ParamFormPass::TYPE_STRING,
ParamFormPass::FORM_TYPE_PWD_TOGGLE,
[
'persistence' => true,
'default' => '',
'sanitizeCallback' => [
SnapUtil::class,
'sanitizeNSCharsNewline',
],
],
[
'label' => 'Password:',
'wrapperClasses' => [
'revalidate-on-change',
'requires-db-hide',
],
'attr' => [
'placeholder' => 'valid database user password',
// Can be written field wise
// Ref. https://devBasicBasiceloper.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion
'autocomplete' => "off",
],
]
);
$params[PrmMng::PARAM_DB_FLAG] = new ParamItem(
PrmMng::PARAM_DB_FLAG,
ParamForm::TYPE_INT,
[
'default' => \DUPX_DB::DB_CONNECTION_FLAG_NOT_SET,
'acceptValues' => function (ParamItem $param): array {
$result = [
\DUPX_DB::MYSQLI_CLIENT_NO_FLAGS,
MYSQLI_CLIENT_SSL,
];
if (defined("MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT")) {
// phpcs:ignore PHPCompatibility.Constants.NewConstants.mysqli_client_ssl_dont_verify_server_certFound
$result[] = MYSQLI_CLIENT_SSL_DONT_VERIFY_SERVER_CERT;
}
return $result;
},
]
);
$params[PrmMng::PARAM_DB_CHARSET] = new ParamForm(
PrmMng::PARAM_DB_CHARSET,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_SELECT,
[
'default' => $archiveConfig->getWpConfigDefineValue('DB_CHARSET', ''),
'sanitizeCallback' => [
SnapUtil::class,
'sanitizeNSCharsNewlineTrim',
],
'validateRegex' => ParamForm::VALIDATE_REGEX_AZ_NUMBER_SEP_EMPTY,
],
[
'label' => 'Charset:',
'status' => function (ParamForm $param): string {
if (InstState::isRestoreBackup()) {
return ParamForm::STATUS_INFO_ONLY;
} else {
return ParamForm::STATUS_ENABLED;
}
},
'options' => [
self::class,
'getCharsetSelectOptions',
],
]
);
$params[PrmMng::PARAM_DB_COLLATE] = new ParamForm(
PrmMng::PARAM_DB_COLLATE,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_SELECT,
[
'default' => $archiveConfig->getWpConfigDefineValue('DB_COLLATE', ''),
'sanitizeCallback' => [
SnapUtil::class,
'sanitizeNSCharsNewlineTrim',
],
'validateRegex' => ParamForm::VALIDATE_REGEX_AZ_NUMBER_SEP_EMPTY,
],
[
'label' => 'Collation:',
'status' => function (): string {
if (InstState::isRestoreBackup()) {
return ParamForm::STATUS_INFO_ONLY;
} else {
return ParamForm::STATUS_ENABLED;
}
},
'options' => [
self::class,
'getCollationSelectOptions',
],
]
);
$tablePrefixWarning = "Changing this setting alters the database table prefix by renaming all tables and references to them.\n"
. "Change it only if you're sure you know what you're doing!";
$params[PrmMng::PARAM_DB_TABLE_PREFIX] = new ParamForm(
PrmMng::PARAM_DB_TABLE_PREFIX,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_TEXT,
[
'default' => \DUPX_ArchiveConfig::getInstance()->wp_tableprefix,
'sanitizeCallback' => [
SnapUtil::class,
'sanitizeNSCharsNewlineTrim',
],
'validateRegex' => ParamForm::VALIDATE_REGEX_AZ_NUMBER_SEP,
],
[
'status' => function (): string {
if (!License::can(License::CAPABILITY_CHANGE_TABLE_PREFIX)) {
return ParamForm::STATUS_INFO_ONLY;
}
if (
InstState::isRestoreBackup() ||
InstState::isAddSiteOnMultisite()
) {
return ParamForm::STATUS_DISABLED;
} else {
return ParamForm::STATUS_READONLY;
}
},
'label' => 'Table Prefix:',
'wrapperClasses' => ['revalidate-on-change'],
'postfix' => [
'type' => 'button',
'label' => 'edit',
'btnAction' => 'DUPX.editActivate(this, ' . SnapJson::jsonEncode($tablePrefixWarning) . ');',
],
'subNote' => (License::can(License::CAPABILITY_CHANGE_TABLE_PREFIX) ? '' : License::getLicenseUpdateText()),
]
);
$params[PrmMng::PARAM_DB_VIEW_CREATION] = new ParamForm(
PrmMng::PARAM_DB_VIEW_CREATION,
ParamForm::TYPE_BOOL,
ParamForm::FORM_TYPE_CHECKBOX,
['default' => true],
[
'label' => 'Objects:',
'checkboxLabel' => 'Enable View Creation',
]
);
$params[PrmMng::PARAM_DB_PROC_CREATION] = new ParamForm(
PrmMng::PARAM_DB_PROC_CREATION,
ParamForm::TYPE_BOOL,
ParamForm::FORM_TYPE_CHECKBOX,
['default' => true],
[
'label' => ' ',
'checkboxLabel' => 'Enable Stored Procedure Creation',
]
);
$params[PrmMng::PARAM_DB_FUNC_CREATION] = new ParamForm(
PrmMng::PARAM_DB_FUNC_CREATION,
ParamForm::TYPE_BOOL,
ParamForm::FORM_TYPE_CHECKBOX,
['default' => true],
[
'label' => ' ',
'checkboxLabel' => 'Enable Function Creation',
]
);
$params[PrmMng::PARAM_DB_REMOVE_DEFINER] = new ParamForm(
PrmMng::PARAM_DB_REMOVE_DEFINER,
ParamForm::TYPE_BOOL,
ParamForm::FORM_TYPE_CHECKBOX,
['default' => false],
[
'label' => ' ',
'checkboxLabel' => 'Remove security DEFINER declarations',
]
);
$numTables = count((array) \DUPX_ArchiveConfig::getInstance()->dbInfo->tablesList);
$params[PrmMng::PARAM_DB_SPLIT_CREATES] = new ParamForm(
PrmMng::PARAM_DB_SPLIT_CREATES,
ParamForm::TYPE_BOOL,
ParamForm::FORM_TYPE_CHECKBOX,
[
'default' => ($numTables <= self::SPLIT_CREATE_MAX_VALUE_TO_DEFAULT),
],
[
'label' => 'Create:',
'checkboxLabel' => 'Run all CREATE SQL statements at once',
]
);
$newObj = new ParamForm(
PrmMng::PARAM_DB_MYSQL_MODE_OPTS,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_TEXT,
[
'default' => '',
'validateRegex' => '/^[A-Za-z0-9_\-,]*$/', // db options with , and can be empty
'sanitizeCallback' => function ($value): string {
$value = SnapUtil::sanitizeNSCharsNewlineTrim($value);
return str_replace(' ', '', $value);
},
],
[
'label' => ' ', // for aligment at PARAM_DB_MYSQL_MODE
'wrapperClasses' => 'no-display',
'subNote' => 'Separate additional ' . \DUPX_View_Funcs::helpLink('step2', 'sql modes', false) . ' with commas &amp; no spaces.<br>'
. 'Example: <i>NO_ENGINE_SUBSTITUTION,NO_ZERO_IN_DATE,...</i>.</small>',
]
);
$params[PrmMng::PARAM_DB_MYSQL_MODE_OPTS] = $newObj;
$modeOptsWrapper = $newObj->getFormWrapperId();
$params[PrmMng::PARAM_DB_MYSQL_MODE] = new ParamForm(
PrmMng::PARAM_DB_MYSQL_MODE,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_RADIO,
[
'default' => 'DEFAULT',
'acceptValues' => [
'DEFAULT',
'DISABLE',
'CUSTOM',
],
],
[
'label' => 'Mode:',
'options' => [
new ParamOption('DEFAULT', 'Default', ParamOption::OPT_ENABLED, [
'onchange' => "if ($(this).is(':checked')) { "
. "jQuery('#" . $modeOptsWrapper . "').addClass('no-display');"
. "}",
]),
new ParamOption('DISABLE', 'Disable', ParamOption::OPT_ENABLED, [
'onchange' => "if ($(this).is(':checked')) { "
. "jQuery('#" . $modeOptsWrapper . "').addClass('no-display');"
. "}",
]),
new ParamOption('CUSTOM', 'Custom', ParamOption::OPT_ENABLED, [
'onchange' => "if ($(this).is(':checked')) { "
. "jQuery('#" . $modeOptsWrapper . "').removeClass('no-display');"
. "}",
]),
],
]
);
$params[PrmMng::PARAM_DB_TABLES] = new ParamFormTables(
PrmMng::PARAM_DB_TABLES,
ParamFormTables::TYPE_ARRAY_TABLES,
ParamFormTables::FORM_TYPE_TABLES_SELECT,
[// ITEM ATTRIBUTES
'default' => [],
],
[// FORM ATTRIBUTES
'label' => 'Tables',
'renderLabel' => false,
'status' => function (ParamForm $paramObj): string {
if (InstState::isRestoreBackup()) {
return ParamForm::STATUS_INFO_ONLY;
} else {
return ParamForm::STATUS_ENABLED;
}
},
]
);
}
/**
* Validate function for database params
*
* @param mixed $value input value
* @param ParamItem $paramObj current param object
*
* @return boolean
*/
public static function validateNoEmptyIfBasic($value, ParamItem $paramObj)
{
if (PrmMng::getInstance()->getValue(PrmMng::PARAM_DB_VIEW_MODE) !== 'basic') {
return true;
}
return ParamsDescriptors::validateNotEmpty($value, $paramObj);
}
/**
* Get charset options list
*
* @return ParamOption[]
*/
public static function getCharsetSelectOptions(): array
{
if (PrmMng::getInstance()->getValue(PrmMng::PARAM_VALIDATION_LEVEL) < \DUPX_Validation_manager::MIN_LEVEL_VALID) {
return [];
}
$data = \DUPX_DB_Functions::getInstance()->getCharsetAndCollationData();
$charsetDef = \DUPX_DB_Functions::getInstance()->getDefaultCharset();
$options = [];
foreach ($data as $charset => $charsetInfo) {
$label = $charset . ($charset == $charsetDef ? self::DEFAULT_CHARSET_POSTFIX : '');
$options[] = new ParamOption($charset, $label, ParamOption::OPT_ENABLED, [
'data-collations' => json_encode($charsetInfo['collations']),
'data-collation-default' => $charsetInfo['defCollation'],
]);
}
return $options;
}
/**
* Get collation options list
*
* @return ParamOption[]
*/
public static function getCollationSelectOptions(): array
{
$options = [new ParamOption('', self::EMPTY_COLLATION_LABEL)];
if (PrmMng::getInstance()->getValue(PrmMng::PARAM_VALIDATION_LEVEL) < \DUPX_Validation_manager::MIN_LEVEL_VALID) {
return $options;
}
$data = \DUPX_DB_Functions::getInstance()->getCharsetAndCollationData();
$currentCharset = PrmMng::getInstance()->getValue(PrmMng::PARAM_DB_CHARSET);
if (!isset($data[$currentCharset])) {
return $options;
}
$defaultCollation = \DUPX_DB_Functions::getInstance()->getDefaultCollateOfCharset($currentCharset);
// if charset exists update default
$options = [new ParamOption('', self::EMPTY_COLLATION_LABEL . ' [' . $defaultCollation . ']')];
foreach ($data[$currentCharset]['collations'] as $collation) {
$label = $collation . ($collation == $data[$currentCharset]['defCollation'] ? self::DEFAULT_COLLATE_POSTFIX : '');
$options[] = new ParamOption($collation, $label);
}
return $options;
}
/**
* Update Charset and collate param by database settings
*
* @return void
*/
public static function updateCharsetAndCollateByDatabaseSettings(): void
{
if (InstState::dbDoNothing()) {
return;
}
$paramsManager = PrmMng::getInstance();
$data = \DUPX_DB_Functions::getInstance()->getCharsetAndCollationData();
$charsetDef = \DUPX_DB_Functions::getInstance()->getDefaultCharset();
$currentCharset = $paramsManager->getValue(PrmMng::PARAM_DB_CHARSET);
$currentCollate = $paramsManager->getValue(PrmMng::PARAM_DB_COLLATE);
if (!array_key_exists($currentCharset, $data)) {
$paramsManager->setValue(PrmMng::PARAM_DB_CHARSET, $charsetDef);
$paramsManager->setValue(PrmMng::PARAM_DB_COLLATE, '');
Log::info('DEFAULT DB_CHARSET [' . $currentCharset . '] isn\'t valid, update DB_CHARSET to ' . $charsetDef . ' and DB_COLLATE set empty');
} elseif (strlen($currentCollate) > 0 && !in_array($currentCollate, $data[$currentCharset]['collations'])) {
$paramsManager->setValue(PrmMng::PARAM_DB_COLLATE, '');
Log::info('DEFAULT DB_COLLATE [' . $currentCollate . '] isn\'t valid, DB_COLLATE set empty');
}
$paramsManager->save();
}
/**
* Update params after overwrite logic
*
* @param ParamItem[]|ParamForm[] $params params list
*
* @return void
*/
public static function updateParamsAfterOverwrite($params): void
{
$params[PrmMng::PARAM_DB_TABLES]->setValue(\DUPX_DB_Tables::getInstance()->getDefaultParamValue());
}
}

View File

@@ -0,0 +1,471 @@
<?php
/**
* Engines params descriptions
*
* @category Duplicator
* @package Installer
* @author Snapcreek <admin@snapcreek.com>
* @copyright 2011-2021 Snapcreek LLC
* @license https://www.gnu.org/licenses/gpl-3.0.html GPLv3
*/
namespace Duplicator\Installer\Core\Params\Descriptors;
use DUPX_Extraction;
use Duplicator\Installer\Core\InstState;
use Duplicator\Installer\Core\Params\PrmMng;
use Duplicator\Installer\Core\Params\Items\ParamItem;
use Duplicator\Installer\Core\Params\Items\ParamForm;
use Duplicator\Installer\Core\Params\Items\ParamOption;
use Duplicator\Installer\Package\PComponents;
use Duplicator\Installer\Utils\Log\Log;
use DUPX_ArchiveConfig;
use DUPX_DBInstall;
/**
* class where all parameters are initialized. Used by the param manager
*/
final class ParamDescEngines implements DescriptorInterface
{
const AUTO_SKIP_PATH_REPLACE_LIST = [
'',
'/html',
];
/**
* Init params
*
* @param ParamItem[]|ParamForm[] $params params list
*
* @return void
*/
public static function init(&$params): void
{
$archiveConfig = \DUPX_ArchiveConfig::getInstance();
$statusRemoveActions = ($archiveConfig->isDBOnly() ? ParamOption::OPT_DISABLED : ParamOption::OPT_ENABLED);
$params[PrmMng::PARAM_ARCHIVE_ACTION] = new ParamForm(
PrmMng::PARAM_ARCHIVE_ACTION,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_SELECT,
[
'default' => DUPX_Extraction::ACTION_DO_NOTHING,
'acceptValues' => [
DUPX_Extraction::ACTION_DO_NOTHING,
DUPX_Extraction::ACTION_REMOVE_WP_FILES,
DUPX_Extraction::ACTION_REMOVE_ALL_FILES,
DUPX_Extraction::ACTION_REMOVE_UPLOADS,
],
],
[
'label' => 'Archive Action:',
'status' => function ($paramObj): string {
if (InstState::isAddSiteOnMultisite()) {
return ParamForm::STATUS_SKIP;
} elseif (InstState::isRecoveryMode()) {
return ParamForm::STATUS_INFO_ONLY;
} else {
return ParamForm::STATUS_ENABLED;
}
},
'options' => [
new ParamOption(DUPX_Extraction::ACTION_DO_NOTHING, 'Extract files over current files'),
new ParamOption(DUPX_Extraction::ACTION_REMOVE_WP_FILES, 'Remove WP core and content and extract', $statusRemoveActions),
new ParamOption(DUPX_Extraction::ACTION_REMOVE_ALL_FILES, 'Remove all files except addon sites and extract', $statusRemoveActions),
new ParamOption(DUPX_Extraction::ACTION_REMOVE_UPLOADS, 'Empty only uploads folder', $statusRemoveActions),
],
'wrapperClasses' => ['revalidate-on-change'],
'subNote' => fn($param) => dupxTplRender('parts/params/archive-action-notes', [
'currentAction' => $param->getValue(),
], false),
]
);
$params[PrmMng::PARAM_ARCHIVE_ENGINE_SKIP_WP_FILES] = new ParamForm(
PrmMng::PARAM_ARCHIVE_ENGINE_SKIP_WP_FILES,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_SELECT,
[
'default' => DUPX_Extraction::FILTER_NONE,
'acceptValues' => [
DUPX_Extraction::FILTER_NONE,
DUPX_Extraction::FILTER_SKIP_WP_CORE,
DUPX_Extraction::FILTER_SKIP_CORE_PLUG_THEMES,
DUPX_Extraction::FILTER_ONLY_MEDIA_PLUG_THEMES,
],
],
[
'label' => 'Skip Files:',
'status' => function ($paramObj): string {
if (
InstState::isRestoreBackup() ||
InstState::isAddSiteOnMultisite()
) {
return ParamForm::STATUS_INFO_ONLY;
} else {
return ParamForm::STATUS_ENABLED;
}
},
'options' => [
new ParamOption(DUPX_Extraction::FILTER_NONE, 'Extract all files'),
new ParamOption(DUPX_Extraction::FILTER_SKIP_WP_CORE, 'Skip extraction of WP core files'),
new ParamOption(
DUPX_Extraction::FILTER_SKIP_CORE_PLUG_THEMES,
'Skip extraction of WP core files and plugins/themes existing on the host'
),
new ParamOption(DUPX_Extraction::FILTER_ONLY_MEDIA_PLUG_THEMES, 'Extract only media files and new plugins and themes'),
],
'wrapperClasses' => ['revalidate-on-change'],
'subNote' => dupxTplRender('parts/params/extract-skip-notes', [
'currentSkipMode' => DUPX_Extraction::FILTER_NONE,
], false),
]
);
$engineOptions = self::getArchiveEngineOptions();
$params[PrmMng::PARAM_ARCHIVE_ENGINE] = new ParamForm(
PrmMng::PARAM_ARCHIVE_ENGINE,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_SELECT,
[
'default' => $engineOptions['default'],
'acceptValues' => $engineOptions['acceptValues'],
'sanitizeCallback' => function ($value) {
if (
PrmMng::getInstance()->getValue(
PrmMng::PARAM_ARCHIVE_ENGINE_SKIP_WP_FILES
) !== DUPX_Extraction::FILTER_NONE && $value === DUPX_Extraction::ENGINE_ZIP_SHELL
) {
return DUPX_Extraction::ENGINE_ZIP_CHUNK;
}
return $value;
},
],
[
'label' => 'Extraction Mode:',
'options' => $engineOptions['options'],
'size' => 0,
'subNote' => $engineOptions['subNote'],
'attr' => ['onchange' => 'DUPX.onSafeModeSwitch();'],
]
);
$params[PrmMng::PARAM_ZIP_THROTTLING] = new ParamForm(
PrmMng::PARAM_ZIP_THROTTLING,
ParamForm::TYPE_BOOL,
ParamForm::FORM_TYPE_CHECKBOX,
['default' => false],
[
'label' => 'Server Throttling:',
'checkboxLabel' => 'Enable archive extraction throttling',
'status' => function (): string {
if (
PrmMng::getInstance()->getValue(PrmMng::PARAM_ARCHIVE_ENGINE) === DUPX_Extraction::ENGINE_ZIP
|| PrmMng::getInstance()->getValue(PrmMng::PARAM_ARCHIVE_ENGINE) === DUPX_Extraction::ENGINE_ZIP_CHUNK
) {
return ParamForm::STATUS_ENABLED;
} else {
return ParamForm::STATUS_DISABLED;
}
},
]
);
$params[PrmMng::PARAM_DB_ACTION] = new ParamForm(
PrmMng::PARAM_DB_ACTION,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_SELECT,
[
'default' => DUPX_DBInstall::DBACTION_EMPTY,
'acceptValues' => [
DUPX_DBInstall::DBACTION_CREATE,
DUPX_DBInstall::DBACTION_EMPTY,
DUPX_DBInstall::DBACTION_REMOVE_ONLY_TABLES,
DUPX_DBInstall::DBACTION_RENAME,
DUPX_DBInstall::DBACTION_MANUAL,
DUPX_DBInstall::DBACTION_ONLY_CONNECT,
DUPX_DBInstall::DBACTION_DO_NOTHING,
],
],
[
'label' => 'Action:',
'status' => function ($paramObj): string {
if (
InstState::isRestoreBackup() ||
InstState::isAddSiteOnMultisite()
) {
return ParamForm::STATUS_INFO_ONLY;
} else {
return ParamForm::STATUS_ENABLED;
}
},
'wrapperClasses' => ['revalidate-on-change'],
'options' => [
new ParamOption(
DUPX_DBInstall::DBACTION_CREATE,
'Create New Database',
function (): string {
if (InstState::getInstance()->getMode() === InstState::MODE_STD_INSTALL) {
return ParamOption::OPT_ENABLED;
} else {
return ParamOption::OPT_DISABLED;
}
}
),
new ParamOption(DUPX_DBInstall::DBACTION_EMPTY, 'Empty Database'),
new ParamOption(DUPX_DBInstall::DBACTION_REMOVE_ONLY_TABLES, 'Overwrite Existing Tables'),
new ParamOption(DUPX_DBInstall::DBACTION_MANUAL, 'Skip Database Extraction'),
new ParamOption(DUPX_DBInstall::DBACTION_RENAME, 'Backup and Rename Existing Tables'),
new ParamOption(DUPX_DBInstall::DBACTION_DO_NOTHING, 'Only Extract Files'),
new ParamOption(DUPX_DBInstall::DBACTION_ONLY_CONNECT, 'Do Nothing (Advanced)', ParamOption::OPT_HIDDEN),
],
'subNote' => dupxTplRender('parts/params/db-action-notes', [], false),
]
);
$params[PrmMng::PARAM_DB_ENGINE] = new ParamForm(
PrmMng::PARAM_DB_ENGINE,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_SELECT,
[
'default' => DUPX_DBInstall::ENGINE_CHUNK,
'acceptValues' => [
DUPX_DBInstall::ENGINE_CHUNK,
DUPX_DBInstall::ENGINE_NORMAL,
],
],
[
'label' => 'Processing:',
'size' => 0,
'options' => [
new ParamOption(DUPX_DBInstall::ENGINE_CHUNK, 'Chunking mode'),
new ParamOption(DUPX_DBInstall::ENGINE_NORMAL, 'Single step'),
],
]
);
$params[PrmMng::PARAM_DB_CHUNK] = new ParamItem(
PrmMng::PARAM_DB_CHUNK,
ParamForm::TYPE_BOOL,
[
'default' => ($params[PrmMng::PARAM_DB_ENGINE]->getValue() === DUPX_DBInstall::ENGINE_CHUNK),
]
);
$params[PrmMng::PARAM_REPLACE_ENGINE] = new ParamItem(
PrmMng::PARAM_REPLACE_ENGINE,
ParamForm::TYPE_INT,
[
'default' => \DUPX_S3_Funcs::MODE_CHUNK,
'acceptValues' => [
\DUPX_S3_Funcs::MODE_NORMAL,
\DUPX_S3_Funcs::MODE_CHUNK,
\DUPX_S3_Funcs::MODE_SKIP,
],
]
);
$oldHomePath = DUPX_ArchiveConfig::getInstance()->getRealValue('archivePaths')->home;
$params[PrmMng::PARAM_SKIP_PATH_REPLACE] = new ParamForm(
PrmMng::PARAM_SKIP_PATH_REPLACE,
ParamForm::TYPE_BOOL,
ParamForm::FORM_TYPE_CHECKBOX,
[
'default' => in_array($oldHomePath, self::AUTO_SKIP_PATH_REPLACE_LIST),
],
[
'label' => 'Skip path replace:',
'checkboxLabel' => 'Skips the replacement of the source path',
'status' => function (ParamForm $paramObj): string {
$sourcePath = PrmMng::getInstance()->getValue(PrmMng::PARAM_PATH_OLD);
if (strlen($sourcePath) == 0) {
return ParamForm::STATUS_DISABLED;
} else {
return ParamForm::STATUS_ENABLED;
}
},
]
);
}
/**
* Update params after overwrite logic
*
* @param ParamItem[]|ParamForm[] $params params list
*
* @return void
*/
public static function updateParamsAfterOverwrite($params): void
{
$archiveConfig = DUPX_ArchiveConfig::getInstance();
if (
$params[PrmMng::PARAM_ARCHIVE_ACTION]->getStatus() !== ParamItem::STATUS_OVERWRITE &&
InstState::isRestoreBackup($params[PrmMng::PARAM_INST_TYPE]->getValue()) &&
PComponents::isFullFilesComponents($archiveConfig->components) &&
$archiveConfig->mu_is_filtered === false
) {
Log::info('IS RESTORE BACKUP WITH FULL FILES COMPONENTS: SET ARCHIVE ACTION TO REMOVE WP FILES');
$params[PrmMng::PARAM_ARCHIVE_ACTION]->setValue(DUPX_Extraction::ACTION_REMOVE_WP_FILES);
}
if (
$params[PrmMng::PARAM_DB_ACTION]->getStatus() !== ParamItem::STATUS_OVERWRITE &&
InstState::isRestoreBackup($params[PrmMng::PARAM_INST_TYPE]->getValue()) &&
$archiveConfig->dbInfo->tablesBaseCount != $archiveConfig->dbInfo->tablesFinalCount &&
$archiveConfig->mu_is_filtered === false
) {
Log::info('IS RESTORE BACKUP WITH PARTIAL DB: SET DB ACTION TO REMOVE ONLY TABLES');
$params[PrmMng::PARAM_DB_ACTION]->setValue(DUPX_DBInstall::DBACTION_REMOVE_ONLY_TABLES);
}
if (
InstState::dbDoNothing() ||
InstState::isRestoreBackup($params[PrmMng::PARAM_INST_TYPE]->getValue())
) {
$default = \DUPX_S3_Funcs::MODE_SKIP;
} elseif ($params[PrmMng::PARAM_DB_ENGINE]->getValue() === DUPX_DBInstall::ENGINE_CHUNK) {
$default = \DUPX_S3_Funcs::MODE_CHUNK;
} else {
$default = \DUPX_S3_Funcs::MODE_NORMAL;
}
$params[PrmMng::PARAM_REPLACE_ENGINE]->setValue($default);
if (
($params[PrmMng::PARAM_ARCHIVE_ENGINE]->getValue() === DUPX_Extraction::ENGINE_ZIP
|| $params[PrmMng::PARAM_ARCHIVE_ENGINE]->getValue() === DUPX_Extraction::ENGINE_ZIP_CHUNK)
&& \DUPX_Custom_Host_Manager::getInstance()->isHosting(\DUPX_Custom_Host_Manager::HOST_SITEGROUND)
) {
$params[PrmMng::PARAM_ZIP_THROTTLING]->setValue(true);
}
}
/**
* Get db chunk engine value
*
* @return boolean
*/
public static function getDbChunkFromParams(): bool
{
return PrmMng::getInstance()->getValue(PrmMng::PARAM_DB_ENGINE) === DUPX_DBInstall::ENGINE_CHUNK;
}
/**
* Get replace engine mode
*
* @return integer
*/
public static function getReplaceEngineModeFromParams(): int
{
$paramsManager = PrmMng::getInstance();
if (InstState::dbDoNothing() || InstState::isRestoreBackup()) {
return \DUPX_S3_Funcs::MODE_SKIP;
} elseif ($paramsManager->getValue(PrmMng::PARAM_DB_ENGINE) === DUPX_DBInstall::ENGINE_CHUNK) {
return \DUPX_S3_Funcs::MODE_CHUNK;
} else {
return \DUPX_S3_Funcs::MODE_NORMAL;
}
}
/**
* Get archive engine options
*
* @return array<string, mixed>
*/
private static function getArchiveEngineOptions(): array
{
$archiveConfig = \DUPX_ArchiveConfig::getInstance();
$acceptValues = [];
$subNote = null;
if (($manualEnable = \DUPX_Conf_Utils::isManualExtractFilePresent()) === true) {
$acceptValues[] = DUPX_Extraction::ENGINE_MANUAL;
} else {
$subNote = <<<SUBNOTEHTML
* Option enabled when archive has been pre-extracted
<a href="https://duplicator.com/knowledge-base/how-to-handle-various-install-scenarios" target="_blank">[more info]</a>
SUBNOTEHTML;
}
if (($zipEnable = ($archiveConfig->isZipArchive() && \DUPX_Conf_Utils::archiveExists() && \DUPX_Conf_Utils::isPhpZipAvailable())) === true) {
$acceptValues[] = DUPX_Extraction::ENGINE_ZIP;
$acceptValues[] = DUPX_Extraction::ENGINE_ZIP_CHUNK;
}
if (($shellZipEnable = ($archiveConfig->isZipArchive() && \DUPX_Conf_Utils::archiveExists() && \DUPX_Conf_Utils::isShellZipAvailable())) === true) {
$acceptValues[] = DUPX_Extraction::ENGINE_ZIP_SHELL;
}
if (($dupEnable = (!$archiveConfig->isZipArchive() && \DUPX_Conf_Utils::archiveExists())) === true) {
$acceptValues[] = DUPX_Extraction::ENGINE_DUP;
}
$options = [];
$options[] = new ParamOption(
DUPX_Extraction::ENGINE_MANUAL,
'Manual Archive Extraction',
$manualEnable ? ParamOption::OPT_ENABLED : ParamOption::OPT_DISABLED
);
if ($archiveConfig->isZipArchive()) {
//ZIP-ARCHIVE
$options[] = new ParamOption(
DUPX_Extraction::ENGINE_ZIP,
'PHP ZipArchive',
$zipEnable ? ParamOption::OPT_ENABLED : ParamOption::OPT_DISABLED
);
$options[] = new ParamOption(
DUPX_Extraction::ENGINE_ZIP_CHUNK,
'PHP ZipArchive Chunking',
$zipEnable ? ParamOption::OPT_ENABLED : ParamOption::OPT_DISABLED
);
$options[] = new ParamOption(
DUPX_Extraction::ENGINE_ZIP_SHELL,
'Shell Exec Unzip',
function (): string {
$archiveConfig = \DUPX_ArchiveConfig::getInstance();
$pathsMapping = $archiveConfig->getPathsMapping();
if (PrmMng::getInstance()->getValue(PrmMng::PARAM_ARCHIVE_ENGINE_SKIP_WP_FILES) !== DUPX_Extraction::FILTER_NONE) {
return ParamOption::OPT_DISABLED;
}
if (is_array($pathsMapping) && count($pathsMapping) > 1) {
return ParamOption::OPT_DISABLED;
}
if ($archiveConfig->isZipArchive() && \DUPX_Conf_Utils::archiveExists() && \DUPX_Conf_Utils::isShellZipAvailable()) {
return ParamOption::OPT_ENABLED;
}
return ParamOption::OPT_DISABLED;
}
);
} else {
// DUPARCHIVE
$options[] = new ParamOption(
DUPX_Extraction::ENGINE_DUP,
'DupArchive',
$dupEnable ? ParamOption::OPT_ENABLED : ParamOption::OPT_DISABLED
);
}
if ($manualEnable) {
$default = DUPX_Extraction::ENGINE_MANUAL;
} elseif ($zipEnable) {
$default = DUPX_Extraction::ENGINE_ZIP_CHUNK;
} elseif ($shellZipEnable) {
$default = DUPX_Extraction::ENGINE_ZIP_SHELL;
} elseif ($dupEnable) {
$default = DUPX_Extraction::ENGINE_DUP;
} else {
$default = null;
}
return [
'options' => $options,
'acceptValues' => $acceptValues,
'default' => $default,
'subNote' => $subNote,
];
}
}

View File

@@ -0,0 +1,264 @@
<?php
/**
* Generic params descriptions
*
* @category Duplicator
* @package Installer
* @author Snapcreek <admin@snapcreek.com>
* @copyright 2011-2021 Snapcreek LLC
* @license https://www.gnu.org/licenses/gpl-3.0.html GPLv3
*/
namespace Duplicator\Installer\Core\Params\Descriptors;
use Duplicator\Installer\Core\InstState;
use Duplicator\Installer\Core\Params\PrmMng;
use Duplicator\Installer\Core\Params\Items\ParamItem;
use Duplicator\Installer\Core\Params\Items\ParamForm;
use Duplicator\Installer\Core\Params\Items\ParamOption;
use Duplicator\Installer\Core\Params\Items\ParamFormPass;
use Duplicator\Installer\Utils\Log\Log;
use Duplicator\Libs\Snap\SnapServer;
use Duplicator\Libs\Snap\SnapUtil;
use DUPX_ArchiveConfig;
/**
* class where all parameters are initialized. Used by the param manager
*/
final class ParamDescGeneric implements DescriptorInterface
{
/**
* Init params
*
* @param ParamItem[]|ParamForm[] $params params list
*
* @return void
*/
public static function init(&$params): void
{
$newObj = new ParamForm(
PrmMng::PARAM_FILE_PERMS_VALUE,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_TEXT,
[
'default' => '644',
'sanitizeCallback' => [
SnapUtil::class,
'sanitizeNSCharsNewlineTrim',
],
'validateRegex' => '/^[ugorwx,\s\+\-0-7]+$/', // octal + ugo rwx,
],
[
'label' => 'File permissions',
'renderLabel' => false,
'status' => SnapServer::isWindows() ? ParamForm::STATUS_SKIP : ParamForm::STATUS_ENABLED,
'wrapperClasses' => ['display-inline-block'],
]
);
$params[PrmMng::PARAM_FILE_PERMS_VALUE] = $newObj;
$permItemId = $newObj->getFormItemId();
$params[PrmMng::PARAM_SET_FILE_PERMS] = new ParamForm(
PrmMng::PARAM_SET_FILE_PERMS,
ParamForm::TYPE_BOOL,
ParamForm::FORM_TYPE_SWITCH,
[
'default' => !SnapServer::isWindows(),
],
[
'status' => SnapServer::isWindows() ? ParamForm::STATUS_SKIP : ParamForm::STATUS_ENABLED,
'label' => 'File permissions:',
'checkboxLabel' => 'All files',
'wrapperClasses' => ['display-inline-block'],
'attr' => [
'onclick' => "jQuery('#" . $permItemId . "').prop('disabled', !jQuery(this).is(':checked'));",
],
]
);
$newObj = new ParamForm(
PrmMng::PARAM_DIR_PERMS_VALUE,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_TEXT,
[
'default' => '755',
'sanitizeCallback' => [
SnapUtil::class,
'sanitizeNSCharsNewlineTrim',
],
'validateRegex' => '/^[ugorwx,\s\+\-0-7]+$/', // octal + ugo rwx
],
[
'label' => 'Folder permissions',
'renderLabel' => false,
'status' => SnapServer::isWindows() ? ParamForm::STATUS_SKIP : ParamForm::STATUS_ENABLED,
'wrapperClasses' => ['display-inline-block'],
]
);
$params[PrmMng::PARAM_DIR_PERMS_VALUE] = $newObj;
$permItemId = $newObj->getFormItemId();
$params[PrmMng::PARAM_SET_DIR_PERMS] = new ParamForm(
PrmMng::PARAM_SET_DIR_PERMS,
ParamForm::TYPE_BOOL,
ParamForm::FORM_TYPE_SWITCH,
[
'default' => !SnapServer::isWindows(),
],
[
'status' => SnapServer::isWindows() ? ParamForm::STATUS_SKIP : ParamForm::STATUS_ENABLED,
'label' => 'Dir permissions:',
'checkboxLabel' => 'All Directories',
'wrapperClasses' => ['display-inline-block'],
'attr' => [
'onclick' => "jQuery('#" . $permItemId . "').prop('disabled', !jQuery(this).is(':checked'));",
],
]
);
$params[PrmMng::PARAM_SAFE_MODE] = new ParamForm(
PrmMng::PARAM_SAFE_MODE,
ParamForm::TYPE_INT,
ParamForm::FORM_TYPE_SELECT,
[
'default' => 0,
'acceptValues' => [
0,
1,
2,
],
],
[
'label' => 'Safe Mode:',
'status' => function (ParamItem $paramObj): string {
if (InstState::isRestoreBackup()) {
return ParamForm::STATUS_DISABLED;
} else {
return ParamForm::STATUS_ENABLED;
}
},
'options' => [
new ParamOption(0, 'Disabled'),
new ParamOption(1, 'Enabled'),
],
'attr' => ['onchange' => 'DUPX.onSafeModeSwitch();'],
]
);
$params[PrmMng::PARAM_FILE_TIME] = new ParamForm(
PrmMng::PARAM_FILE_TIME,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_RADIO,
[
'default' => 'current',
'acceptValues' => [
'current',
'original',
],
],
[
'label' => 'File Times:',
'status' => ParamForm::STATUS_ENABLED,
'options' => [
new ParamOption('current', 'Current', ParamOption::OPT_ENABLED, ['title' => 'Set the files current date time to now']),
new ParamOption('original', 'Original', ParamOption::OPT_ENABLED, ['title' => 'Keep the files date time the same']),
],
'subNote' => 'This option is not supported for extraction mode Shell Exec Unzip',
]
);
$params[PrmMng::PARAM_LOGGING] = new ParamForm(
PrmMng::PARAM_LOGGING,
ParamForm::TYPE_INT,
ParamForm::FORM_TYPE_RADIO,
[
'default' => Log::LV_DEFAULT,
'acceptValues' => [
Log::LV_DEFAULT,
Log::LV_DETAILED,
Log::LV_DEBUG,
Log::LV_HARD_DEBUG,
],
],
[
'label' => 'Logging:',
'options' => [
new ParamOption(Log::LV_DEFAULT, 'Light'),
new ParamOption(Log::LV_DETAILED, 'Detailed'),
new ParamOption(Log::LV_DEBUG, 'Debug'),
// enabled only with overwrite params
new ParamOption(Log::LV_HARD_DEBUG, 'Hard debug', ParamOption::OPT_HIDDEN),
],
]
);
$params[PrmMng::PARAM_REMOVE_RENDUNDANT] = new ParamForm(
PrmMng::PARAM_REMOVE_RENDUNDANT,
ParamForm::TYPE_BOOL,
ParamForm::FORM_TYPE_CHECKBOX,
['default' => false],
[
'label' => 'Cleanup:',
'checkboxLabel' => 'Remove disabled plugins/themes',
'wrapperClasses' => ['requires-db-hide'],
'status' => function (ParamItem $paramObj): string {
if (InstState::isRestoreBackup() || InstState::isAddSiteOnMultisite()) {
return ParamForm::STATUS_DISABLED;
} else {
return ParamForm::STATUS_ENABLED;
}
},
]
);
$params[PrmMng::PARAM_REMOVE_USERS_WITHOUT_PERMISSIONS] = new ParamForm(
PrmMng::PARAM_REMOVE_USERS_WITHOUT_PERMISSIONS,
ParamForm::TYPE_BOOL,
ParamForm::FORM_TYPE_CHECKBOX,
['default' => false],
[
'label' => ' ',
'checkboxLabel' => 'Remove users without permissions',
'wrapperClasses' => ['requires-db-hide'],
]
);
$params[PrmMng::PARAM_RECOVERY_LINK] = new ParamItem(
PrmMng::PARAM_RECOVERY_LINK,
ParamFormPass::TYPE_STRING,
['default' => '']
);
$params[PrmMng::PARAM_FROM_SITE_IMPORT_INFO] = new ParamItem(
PrmMng::PARAM_FROM_SITE_IMPORT_INFO,
ParamFormPass::TYPE_ARRAY_MIXED,
[
'default' => [],
]
);
$params[PrmMng::PARAM_AUTO_CLEAN_INSTALLER_FILES] = new ParamForm(
PrmMng::PARAM_AUTO_CLEAN_INSTALLER_FILES,
ParamForm::TYPE_BOOL,
ParamForm::FORM_TYPE_CHECKBOX,
['default' => true],
[
'label' => 'CLean installation files',
'renderLabel' => false,
'checkboxLabel' => 'Auto delete installer files after login to secure site (recommended!)',
]
);
}
/**
* Update params after overwrite logic
*
* @param ParamItem[]|ParamForm[] $params params list
*
* @return void
*/
public static function updateParamsAfterOverwrite($params): void
{
}
}

View File

@@ -0,0 +1,271 @@
<?php
/**
* Multisite params descriptions
*
* @category Duplicator
* @package Installer
* @author Snapcreek <admin@snapcreek.com>
* @copyright 2011-2021 Snapcreek LLC
* @license https://www.gnu.org/licenses/gpl-3.0.html GPLv3
*/
namespace Duplicator\Installer\Core\Params\Descriptors;
use Duplicator\Installer\Core\InstState;
use Duplicator\Installer\Core\Params\PrmMng;
use Duplicator\Installer\Core\Params\Items\ParamItem;
use Duplicator\Installer\Core\Params\Items\ParamForm;
use Duplicator\Installer\Core\Params\Items\ParamFormSitesOwrMap;
use Duplicator\Installer\Core\Params\Items\ParamOption;
use Duplicator\Installer\Core\Params\Items\ParamFormURLMapping;
use Duplicator\Installer\Core\Params\Models\SiteOwrMap;
use DUPX_ArchiveConfig;
/**
* class where all parameters are initialized. Used by the param manager
*/
final class ParamDescMultisite implements DescriptorInterface
{
/**
* Init params
*
* @param ParamItem[]|ParamForm[] $params params list
*
* @return void
*/
public static function init(&$params): void
{
$archive_config = \DUPX_ArchiveConfig::getInstance();
$params[PrmMng::PARAM_SUBSITE_ID] = new ParamForm(
PrmMng::PARAM_SUBSITE_ID,
ParamForm::TYPE_INT,
ParamForm::FORM_TYPE_SELECT,
[
'default' => -1,
'acceptValues' => [
self::class,
'getSubSiteIdsAcceptValues',
],
],
[
'status' => function (ParamItem $paramObj): string {
if (
InstState::isInstType(
[InstState::TYPE_STANDALONE]
)
) {
return ParamForm::STATUS_ENABLED;
} else {
return ParamForm::STATUS_DISABLED;
}
},
'label' => 'Subsite:',
'wrapperClasses' => ['revalidate-on-change'],
'options' => [
self::class,
'getSubSiteIdsOptions',
],
]
);
$params[PrmMng::PARAM_SUBSITE_OVERWRITE_MAPPING] = new ParamFormSitesOwrMap(
PrmMng::PARAM_SUBSITE_OVERWRITE_MAPPING,
ParamFormSitesOwrMap::TYPE_ARRAY_SITES_OWR_MAP,
ParamFormSitesOwrMap::FORM_TYPE_SITES_OWR_MAP,
[
'default' => [],
'validateCallback' => function ($value, ParamItem $paramObj): bool {
/** @var SiteOwrMap[] $value */
if (!InstState::isAddSiteOnMultisite()) {
return true;
}
$overwriteData = PrmMng::getInstance()->getValue(PrmMng::PARAM_OVERWRITE_SITE_DATA);
$mainSiteURL = $overwriteData['urls']['home'];
$subdomain = (isset($overwriteData['subdomain']) && $overwriteData['subdomain']);
$newFullURLs = [];
foreach ($value as $map) {
switch ($map->getTargetId()) {
case SiteOwrMap::NEW_SUBSITE_WITH_SLUG:
if (($newFullUrl = $map->getNewSlugFullUrl($mainSiteURL, $subdomain)) == false) {
$paramObj->setInvalidMessage('New sub site can\'t have new ' . ($subdomain ? 'subdomain' : 'subpath') . ' empty');
return false;
}
break;
case SiteOwrMap::NEW_SUBSITE_WITH_FULL_DOMAIN:
if (($newFullUrl = $map->getNewSlugFullUrl($mainSiteURL, $subdomain)) == false) {
$paramObj->setInvalidMessage('New sub site URL can\'t be empty');
return false;
}
break;
default:
continue 2;
}
$newFullURLs[] = $newFullUrl;
foreach ($overwriteData['subsites'] as $subsite) {
$subsiteFullUrl = $subsite['domain'] . $subsite['path'];
if (strcmp($newFullUrl, $subsiteFullUrl) === 0) {
$paramObj->setInvalidMessage('New subsite URL already exists');
return false;
}
}
}
if (count($newFullURLs) !== count(array_unique($newFullURLs))) {
$paramObj->setInvalidMessage('Different new sub-sites cannot have the same URL ');
return false;
}
return true;
},
],
[
'label' => 'Overwrite mapping',
'renderLabel' => false,
'wrapperClasses' => ['revalidate-on-change'],
]
);
$params[PrmMng::PARAM_MU_REPLACE] = new ParamFormURLMapping(
PrmMng::PARAM_MU_REPLACE,
ParamFormURLMapping::TYPE_ARRAY_SITES_OWR_MAP,
ParamFormURLMapping::FORM_TYPE_URL_MAPPING,
[
'default' => [],
'validateCallback' => function ($value, ParamItem $paramObj): bool {
/** @var SiteOwrMap[] $value */
if (!InstState::isMultisiteInstall()) {
return true;
}
$config = DUPX_ArchiveConfig::getInstance();
$subdomain = $config->isSubdomain();
foreach ($value as $map) {
switch ($map->getTargetId()) {
case SiteOwrMap::NEW_SUBSITE_WITH_SLUG:
if (strlen($map->getNewSlug()) == 0) {
$paramObj->setInvalidMessage('New sub site can\'t have new ' . ($subdomain ? 'subdomain' : 'subpath') . ' empty');
return false;
}
break;
case SiteOwrMap::NEW_SUBSITE_WITH_FULL_DOMAIN:
if (strlen($map->getNewSlug()) == 0) {
$paramObj->setInvalidMessage('New sub site URL can\'t be empty');
return false;
}
break;
default:
$paramObj->setInvalidMessage('Invalid param');
return false;
}
}
return true;
},
],
[
'label' => 'URLs mapping',
'renderLabel' => false,
'wrapperClasses' => ['revalidate-on-change'],
]
);
$params[PrmMng::PARAM_MULTISITE_CROSS_SEARCH] = new ParamForm(
PrmMng::PARAM_MULTISITE_CROSS_SEARCH,
ParamForm::TYPE_BOOL,
ParamForm::FORM_TYPE_CHECKBOX,
[
'default' => (count($archive_config->subsites) <= MAX_SITES_TO_DEFAULT_ENABLE_CORSS_SEARCH),
],
[
'status' => function ($paramObj): string {
if (InstState::isNewSiteIsMultisite()) {
return ParamForm::STATUS_ENABLED;
} else {
return ParamForm::STATUS_SKIP;
}
},
'label' => 'Database search:',
'checkboxLabel' => 'Cross-search between the sites of the network.',
]
);
}
/**
* Update params after overwrite logic
*
* @param ParamItem[]|ParamForm[] $params params list
*
* @return void
*/
public static function updateParamsAfterOverwrite($params): void
{
}
/**
* Get overwrite map by source id
*
* @param int $sourceId subsite source id
*
* @return SiteOwrMap|bool false if don't exists
*/
public static function getOwrMapBySourceId($sourceId)
{
static $indexCache = [];
if (!isset($indexCache[$sourceId])) {
/** @var SiteOwrMap[] $overwriteMapping */
$overwriteMapping = PrmMng::getInstance()->getValue(PrmMng::PARAM_SUBSITE_OVERWRITE_MAPPING);
foreach ($overwriteMapping as $map) {
if ($map->getSourceId() == $sourceId) {
$indexCache[$sourceId] = $map;
break;
}
}
if (!isset($indexCache[$sourceId])) {
$indexCache[$sourceId] = false;
}
}
return $indexCache[$sourceId];
}
/**
* Return option
*
* @return ParamOption[]
*/
public static function getSubSiteIdsOptions(): array
{
$archive_config = \DUPX_ArchiveConfig::getInstance();
$options = [];
foreach ($archive_config->subsites as $subsite) {
$label = $subsite->domain . $subsite->path;
$options[] = new ParamOption($subsite->id, $label, ParamFormSitesOwrMap::getSourceIdOptionStatus($subsite));
}
return $options;
}
/**
*
* @return int[]
*/
public static function getSubSiteIdsAcceptValues(): array
{
$archive_config = \DUPX_ArchiveConfig::getInstance();
$acceptValues = [-1];
foreach ($archive_config->subsites as $subsite) {
if (ParamFormSitesOwrMap::isQualifiedSourceIdForImport($subsite)) {
$acceptValues[] = $subsite->id;
}
}
return $acceptValues;
}
}

View File

@@ -0,0 +1,271 @@
<?php
/**
* New admin params descriptions
*
* @category Duplicator
* @package Installer
* @author Snapcreek <admin@snapcreek.com>
* @copyright 2011-2021 Snapcreek LLC
* @license https://www.gnu.org/licenses/gpl-3.0.html GPLv3
*/
namespace Duplicator\Installer\Core\Params\Descriptors;
use Duplicator\Installer\Core\Params\PrmMng;
use Duplicator\Installer\Core\Params\Items\ParamItem;
use Duplicator\Installer\Core\Params\Items\ParamForm;
use Duplicator\Installer\Core\Params\Items\ParamFormPass;
use Duplicator\Libs\Snap\SnapUtil;
/**
* class where all parameters are initialized. Used by the param manager
*/
final class ParamDescNewAdmin implements DescriptorInterface
{
/**
* Init params
*
* @param ParamItem[]|ParamForm[] $params params list
*
* @return void
*/
public static function init(&$params): void
{
$params[PrmMng::PARAM_WP_ADMIN_CREATE_NEW] = new ParamForm(
PrmMng::PARAM_WP_ADMIN_CREATE_NEW,
ParamForm::TYPE_BOOL,
ParamForm::FORM_TYPE_SWITCH,
['default' => false],
[
'label' => 'Create New User:',
'status' => function ($paramObj): string {
if (ParamDescUsers::getUsersMode() != ParamDescUsers::USER_MODE_OVERWRITE) {
return ParamForm::STATUS_DISABLED;
} else {
return ParamForm::STATUS_ENABLED;
}
},
'checkboxLabel' => '',
]
);
$params[PrmMng::PARAM_WP_ADMIN_NAME] = new ParamForm(
PrmMng::PARAM_WP_ADMIN_NAME,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_TEXT,
[
'default' => '',
'sanitizeCallback' => [
SnapUtil::class,
'sanitizeNSCharsNewlineTrim',
],
'validateCallback' => function ($value, ParamItem $paramObj): bool {
if (!PrmMng::getInstance()->getValue(PrmMng::PARAM_WP_ADMIN_CREATE_NEW)) {
return true;
}
if (strlen($value) < 4) {
$paramObj->setInvalidMessage('Must have 4 or more characters');
return false;
}
return true;
},
],
[
'status' => [
self::class,
'getStatuOfNewAdminParams',
],
'label' => 'Username:',
'classes' => 'new-admin-field',
'attr' => [
'title' => '4 characters minimum',
'placeholder' => "(4 or more characters)",
],
]
);
$params[PrmMng::PARAM_WP_ADMIN_PASSWORD] = new ParamFormPass(
PrmMng::PARAM_WP_ADMIN_PASSWORD,
ParamFormPass::TYPE_STRING,
ParamFormPass::FORM_TYPE_PWD_TOGGLE,
[
'default' => \DUPX_ArchiveConfig::getInstance()->cpnl_pass,
'sanitizeCallback' => [
SnapUtil::class,
'sanitizeNSCharsNewlineTrim',
],
'validateCallback' => function ($value, ParamItem $paramObj): bool {
if (!PrmMng::getInstance()->getValue(PrmMng::PARAM_WP_ADMIN_CREATE_NEW)) {
return true;
}
if (strlen($value) < \DUPX_Constants::MIN_NEW_PASSWORD_LEN) {
$paramObj->setInvalidMessage('Must have ' . \DUPX_Constants::MIN_NEW_PASSWORD_LEN . ' or more characters');
return false;
}
return true;
},
],
[
'status' => [
self::class,
'getStatuOfNewAdminParams',
],
'label' => 'Password:',
'classes' => [
'strength-pwd-check',
'new-admin-field',
],
'attr' => [
'placeholder' => '(' . \DUPX_Constants::MIN_NEW_PASSWORD_LEN . ' or more characters)',
'title' => \DUPX_Constants::MIN_NEW_PASSWORD_LEN . ' characters minimum',
],
]
);
$params[PrmMng::PARAM_WP_ADMIN_MAIL] = new ParamForm(
PrmMng::PARAM_WP_ADMIN_MAIL,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_TEXT,
[
'default' => '',
'sanitizeCallback' => [
SnapUtil::class,
'sanitizeNSCharsNewlineTrim',
],
'validateCallback' => function ($value, ParamItem $paramObj): bool {
if (!PrmMng::getInstance()->getValue(PrmMng::PARAM_WP_ADMIN_CREATE_NEW)) {
return true;
}
if (strlen($value) < 4) {
$paramObj->setInvalidMessage('Email name must have 4 or more characters');
return false;
}
if (filter_var($value, FILTER_VALIDATE_EMAIL) == false) {
$paramObj->setInvalidMessage('Email "' . $value . '" isn\'t valid');
return false;
}
return true;
},
],
[
'status' => [
self::class,
'getStatuOfNewAdminParams',
],
'label' => 'Email:',
'classes' => 'new-admin-field',
'attr' => [
'title' => '4 characters minimum',
'placeholder' => "(4 or more characters)",
],
]
);
$params[PrmMng::PARAM_WP_ADMIN_NICKNAME] = new ParamForm(
PrmMng::PARAM_WP_ADMIN_NICKNAME,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_TEXT,
[
'default' => '',
'sanitizeCallback' => [
SnapUtil::class,
'sanitizeNSCharsNewlineTrim',
],
],
[
'status' => [
self::class,
'getStatuOfNewAdminParams',
],
'label' => 'Nickname:',
'classes' => 'new-admin-field',
'attr' => [
'title' => 'if username is empty',
'placeholder' => "(if username is empty)",
],
]
);
$params[PrmMng::PARAM_WP_ADMIN_FIRST_NAME] = new ParamForm(
PrmMng::PARAM_WP_ADMIN_FIRST_NAME,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_TEXT,
[
'default' => '',
'sanitizeCallback' => [
SnapUtil::class,
'sanitizeNSCharsNewlineTrim',
],
],
[
'status' => [
self::class,
'getStatuOfNewAdminParams',
],
'label' => 'First Name:',
'classes' => 'new-admin-field',
'attr' => [
'title' => 'optional',
'placeholder' => "(optional)",
],
]
);
$params[PrmMng::PARAM_WP_ADMIN_LAST_NAME] = new ParamForm(
PrmMng::PARAM_WP_ADMIN_LAST_NAME,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_TEXT,
[
'default' => '',
'sanitizeCallback' => [
SnapUtil::class,
'sanitizeNSCharsNewlineTrim',
],
],
[
'status' => [
self::class,
'getStatuOfNewAdminParams',
],
'label' => 'Last Name:',
'classes' => 'new-admin-field',
'attr' => [
'title' => 'optional',
'placeholder' => "(optional)",
],
]
);
}
/**
*
* @return string
*/
public static function getStatuOfNewAdminParams(): string
{
if (PrmMng::getInstance()->getValue(PrmMng::PARAM_WP_ADMIN_CREATE_NEW)) {
return ParamForm::STATUS_ENABLED;
} else {
return ParamForm::STATUS_DISABLED;
}
}
/**
* Update params after overwrite logic
*
* @param ParamItem[]|ParamForm[] $params params list
*
* @return void
*/
public static function updateParamsAfterOverwrite($params): void
{
}
}

View File

@@ -0,0 +1,98 @@
<?php
/**
* Plugins params descriptions
*
* @category Duplicator
* @package Installer
* @author Snapcreek <admin@snapcreek.com>
* @copyright 2011-2021 Snapcreek LLC
* @license https://www.gnu.org/licenses/gpl-3.0.html GPLv3
*/
namespace Duplicator\Installer\Core\Params\Descriptors;
use Duplicator\Installer\Core\Params\PrmMng;
use Duplicator\Installer\Core\Params\Items\ParamItem;
use Duplicator\Installer\Core\Params\Items\ParamForm;
use Duplicator\Installer\Core\Params\Items\ParamFormPlugins;
use Duplicator\Libs\Snap\SnapUtil;
use Duplicator\Installer\Core\InstState;
/**
* class where all parameters are initialized. Used by the param manager
*/
final class ParamDescPlugins implements DescriptorInterface
{
/**
* Init params
*
* @param ParamItem[]|ParamForm[] $params params list
*
* @return void
*/
public static function init(&$params): void
{
$params[PrmMng::PARAM_PLUGINS] = new ParamFormPlugins(
PrmMng::PARAM_PLUGINS,
ParamFormPlugins::TYPE_ARRAY_STRING,
ParamFormPlugins::FORM_TYPE_PLUGINS_SELECT,
[
'default' => [],
'sanitizeCallback' => [
SnapUtil::class,
'sanitizeNSCharsNewline',
],
],
[
'label' => 'Plugins',
'renderLabel' => false,
'status' => function ($paramObj): string {
if (
InstState::isRestoreBackup() ||
InstState::isAddSiteOnMultisite()
) {
return ParamForm::STATUS_DISABLED;
} else {
return ParamForm::STATUS_ENABLED;
}
},
]
);
$params[PrmMng::PARAM_IGNORE_PLUGINS] = new ParamItem(
PrmMng::PARAM_IGNORE_PLUGINS,
ParamItem::TYPE_ARRAY_STRING,
[
'default' => [],
'sanitizeCallback' => [
SnapUtil::class,
'sanitizeNSCharsNewline',
],
]
);
$params[PrmMng::PARAM_FORCE_DIABLE_PLUGINS] = new ParamItem(
PrmMng::PARAM_FORCE_DIABLE_PLUGINS,
ParamItem::TYPE_ARRAY_STRING,
[
'default' => [],
'sanitizeCallback' => [
SnapUtil::class,
'sanitizeNSCharsNewline',
],
]
);
}
/**
* Update params after overwrite logic
*
* @param ParamItem[]|ParamForm[] $params params list
*
* @return void
*/
public static function updateParamsAfterOverwrite($params): void
{
}
}

View File

@@ -0,0 +1,171 @@
<?php
/**
* Replace params descriptions
*
* @category Duplicator
* @package Installer
* @author Snapcreek <admin@snapcreek.com>
* @copyright 2011-2021 Snapcreek LLC
* @license https://www.gnu.org/licenses/gpl-3.0.html GPLv3
*/
namespace Duplicator\Installer\Core\Params\Descriptors;
use Duplicator\Installer\Core\InstState;
use Duplicator\Installer\Core\Params\PrmMng;
use Duplicator\Installer\Core\Params\Items\ParamItem;
use Duplicator\Installer\Core\Params\Items\ParamForm;
use Duplicator\Libs\Snap\SnapUtil;
/**
* class where all parameters are initialized. Used by the param manager
*/
final class ParamDescReplace implements DescriptorInterface
{
/**
* Init params
*
* @param ParamItem[]|ParamForm[] $params params list
*
* @return void
*/
public static function init(&$params): void
{
$params[PrmMng::PARAM_BLOGNAME] = new ParamForm(
PrmMng::PARAM_BLOGNAME,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_TEXT,
[
'default' => '',
'sanitizeCallback' => function ($value): string {
$value = SnapUtil::sanitizeNSCharsNewline($value);
return htmlspecialchars_decode((empty($value) ? 'No Blog Title Set' : $value), ENT_QUOTES);
},
],
[
'label' => 'Site Title:',
'status' => function ($paramObj): string {
if (InstState::isRestoreBackup()) {
return ParamForm::STATUS_DISABLED;
} else {
return ParamForm::STATUS_ENABLED;
}
},
'wrapperClasses' => [
'revalidate-on-change',
'requires-db-hide',
],
]
);
$params[PrmMng::PARAM_CUSTOM_SEARCH] = new ParamItem(
PrmMng::PARAM_CUSTOM_SEARCH,
ParamForm::TYPE_ARRAY_STRING,
[
'default' => [],
'sanitizeCallback' => [
SnapUtil::class,
'sanitizeNSCharsNewline',
],
]
);
$params[PrmMng::PARAM_CUSTOM_REPLACE] = new ParamItem(
PrmMng::PARAM_CUSTOM_REPLACE,
ParamForm::TYPE_ARRAY_STRING,
[
'default' => [],
'sanitizeCallback' => [
SnapUtil::class,
'sanitizeNSCharsNewline',
],
]
);
$params[PrmMng::PARAM_EMPTY_SCHEDULE_STORAGE] = new ParamForm(
PrmMng::PARAM_EMPTY_SCHEDULE_STORAGE,
ParamForm::TYPE_BOOL,
ParamForm::FORM_TYPE_CHECKBOX,
['default' => true],
[
'label' => 'Cleanup:',
'checkboxLabel' => 'Remove schedules and storage endpoints',
]
);
$params[PrmMng::PARAM_EMAIL_REPLACE] = new ParamForm(
PrmMng::PARAM_EMAIL_REPLACE,
ParamForm::TYPE_BOOL,
ParamForm::FORM_TYPE_CHECKBOX,
['default' => false],
[
'label' => 'Email Domains:',
'checkboxLabel' => 'Update',
]
);
$params[PrmMng::PARAM_FULL_SEARCH] = new ParamForm(
PrmMng::PARAM_FULL_SEARCH,
ParamForm::TYPE_BOOL,
ParamForm::FORM_TYPE_CHECKBOX,
['default' => false],
[
'label' => 'Database Search:',
'checkboxLabel' => 'Full Search Mode',
]
);
$params[PrmMng::PARAM_POSTGUID] = new ParamForm(
PrmMng::PARAM_POSTGUID,
ParamForm::TYPE_BOOL,
ParamForm::FORM_TYPE_CHECKBOX,
['default' => false],
[
'label' => 'Post GUID:',
'checkboxLabel' => 'Keep Unchanged',
]
);
$params[PrmMng::PARAM_MAX_SERIALIZE_CHECK] = new ParamForm(
PrmMng::PARAM_MAX_SERIALIZE_CHECK,
ParamForm::TYPE_INT,
ParamForm::FORM_TYPE_NUMBER,
[
'default' => \DUPX_Constants::DEFAULT_MAX_STRLEN_SERIALIZED_CHECK_IN_M,
],
[
'min' => 0,
'max' => 99,
'step' => 1,
'wrapperClasses' => ['small'],
'label' => 'Serialized obj max size:',
'postfix' => [
'type' => 'label',
'label' => 'MB',
],
'subNote' => 'If the serialized object stored in the database exceeds this size, it will not be parsed for replacement.'
. '<br><b>Too large a size in low memory installations can generate a fatal error.</b>',
]
);
}
/**
* Update params after overwrite logic
*
* @param ParamItem[]|ParamForm[] $params params list
*
* @return void
*/
public static function updateParamsAfterOverwrite($params): void
{
if ($params[PrmMng::PARAM_BLOGNAME]->getStatus() !== ParamItem::STATUS_OVERWRITE) {
$params[PrmMng::PARAM_BLOGNAME]->setValue(\DUPX_ArchiveConfig::getInstance()->getBlognameFromSelectedSubsiteId());
}
$installType = $params[PrmMng::PARAM_INST_TYPE]->getValue();
if (InstState::isRestoreBackup($installType)) {
$params[PrmMng::PARAM_EMPTY_SCHEDULE_STORAGE]->setValue(false);
}
}
}

View File

@@ -0,0 +1,113 @@
<?php
/**
* Generic params descriptions
*
* @category Duplicator
* @package Installer
* @author Snapcreek <admin@snapcreek.com>
* @copyright 2011-2021 Snapcreek LLC
* @license https://www.gnu.org/licenses/gpl-3.0.html GPLv3
*/
namespace Duplicator\Installer\Core\Params\Descriptors;
use Duplicator\Installer\Core\Security;
use Duplicator\Installer\Core\Params\PrmMng;
use Duplicator\Installer\Core\Params\Items\ParamItem;
use Duplicator\Installer\Core\Params\Items\ParamForm;
use Duplicator\Installer\Core\Params\Items\ParamFormPass;
use DUPX_ArchiveConfig;
use Duplicator\Installer\Core\InstState;
use Duplicator\Libs\Snap\SnapUtil;
use DUPX_View_Funcs;
/**
* class where all parameters are initialized. Used by the param manager
*/
final class ParamDescSecurity implements DescriptorInterface
{
/**
* Init params
*
* @param ParamItem[]|ParamForm[] $params params list
*
* @return void
*/
public static function init(&$params): void
{
$params[PrmMng::PARAM_SECURE_PASS] = new ParamFormPass(
PrmMng::PARAM_SECURE_PASS,
ParamFormPass::TYPE_STRING,
ParamFormPass::FORM_TYPE_PWD_TOGGLE,
[
'persistence' => false,
'default' => null,
'sanitizeCallback' => [
SnapUtil::class,
'sanitizeNSCharsNewline',
],
],
[
'label' => 'Password:',
'status' => function (ParamForm $param): string {
if (Security::getInstance()->getSecurityType() == Security::SECURITY_PASSWORD) {
return ParamForm::STATUS_ENABLED;
} else {
return ParamForm::STATUS_DISABLED;
}
},
'wrapperClasses' => 'margin-bottom-2',
'attr' => [
'placeholder' => (DUPX_ArchiveConfig::getInstance()->secure_on ? '' : 'Password not enabled'),
],
]
);
$params[PrmMng::PARAM_SECURE_ARCHIVE_HASH] = new ParamForm(
PrmMng::PARAM_SECURE_ARCHIVE_HASH,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_TEXT,
[
'persistence' => false,
'default' => null,
'sanitizeCallback' => [
SnapUtil::class,
'sanitizeNSCharsNewlineTrim',
],
],
[
'label' => 'Backup File Name:',
'status' => function (ParamForm $param): string {
if (!InstState::isOverwrite()) {
return ParamForm::STATUS_SKIP;
} elseif (Security::getInstance()->getSecurityType() == Security::SECURITY_ARCHIVE) {
return ParamForm::STATUS_ENABLED;
} else {
return ParamForm::STATUS_DISABLED;
}
},
'wrapperClasses' => 'margin-bottom-4',
'attr' => ['placeholder' => 'example: [full-unique-name]_archive.zip'],
'subNote' => DUPX_View_Funcs::helpLink('secure', 'How to get archive file name?', false),
]
);
$params[PrmMng::PARAM_SECURE_OK] = new ParamItem(
PrmMng::PARAM_SECURE_OK,
ParamForm::TYPE_BOOL,
['default' => false]
);
}
/**
* Update params after overwrite logic
*
* @param ParamItem[]|ParamForm[] $params params list
*
* @return void
*/
public static function updateParamsAfterOverwrite($params): void
{
}
}

View File

@@ -0,0 +1,689 @@
<?php
/**
* Urls and paths params descriptions
*
* @category Duplicator
* @package Installer
* @author Snapcreek <admin@snapcreek.com>
* @copyright 2011-2021 Snapcreek LLC
* @license https://www.gnu.org/licenses/gpl-3.0.html GPLv3
*/
namespace Duplicator\Installer\Core\Params\Descriptors;
use Duplicator\Installer\Core\InstState;
use Duplicator\Installer\Core\Params\PrmMng;
use Duplicator\Installer\Core\Params\Items\ParamItem;
use Duplicator\Installer\Core\Params\Items\ParamForm;
use Duplicator\Libs\Snap\SnapIO;
use Duplicator\Libs\Snap\SnapJson;
/**
* class where all parameters are initialized. Used by the param manager
*/
final class ParamDescUrlsPaths implements DescriptorInterface
{
const INVALID_PATH_EMPTY = 'can\'t be empty';
const INVALID_URL_EMPTY = 'can\'t be empty';
/**
* Init params
*
* @param ParamItem[]|ParamForm[] $params params list
*
* @return void
*/
public static function init(&$params): void
{
$archive_config = \DUPX_ArchiveConfig::getInstance();
$paths = $archive_config->getRealValue('archivePaths');
$oldMainPath = $paths->home;
$newMainPath = DUPX_ROOT;
$oldHomeUrl = rtrim($archive_config->getRealValue('homeUrl'), '/');
$newHomeUrl = rtrim(DUPX_ROOT_URL, '/');
$oldSiteUrl = rtrim($archive_config->getRealValue('siteUrl'), '/');
$oldContentUrl = rtrim($archive_config->getRealValue('contentUrl'), '/');
$oldUploadUrl = rtrim($archive_config->getRealValue('uploadBaseUrl'), '/');
$oldPluginsUrl = rtrim($archive_config->getRealValue('pluginsUrl'), '/');
$oldMuPluginsUrl = rtrim($archive_config->getRealValue('mupluginsUrl'), '/');
$oldWpAbsPath = $paths->abs;
$oldContentPath = $paths->wpcontent;
$oldUploadsBasePath = $paths->uploads;
$oldPluginsPath = $paths->plugins;
$oldMuPluginsPath = $paths->muplugins;
$defValEdit = "This default value is automatically generated.\n"
. "Change it only if you're sure you know what you're doing!";
$params[PrmMng::PARAM_URL_OLD] = new ParamItem(
PrmMng::PARAM_URL_OLD,
ParamForm::TYPE_STRING,
['default' => $oldHomeUrl]
);
$params[PrmMng::PARAM_WP_ADDON_SITES_PATHS] = new ParamItem(
PrmMng::PARAM_WP_ADDON_SITES_PATHS,
ParamForm::TYPE_ARRAY_STRING,
[
'default' => [],
]
);
$newObj = new ParamForm(
PrmMng::PARAM_URL_NEW,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_TEXT,
[// ITEM ATTRIBUTES
'default' => $newHomeUrl,
'sanitizeCallback' => [
ParamsDescriptors::class,
'sanitizeUrl',
],
'validateCallback' => [
ParamsDescriptors::class,
'validateUrlWithScheme',
],
],
[// FORM ATTRIBUTES
'label' => 'New Site URL:',
'status' => function (ParamForm $param): string {
if (
PrmMng::getInstance()->getValue(PrmMng::PARAM_TEMPLATE) !== \DUPX_Template::TEMPLATE_ADVANCED ||
InstState::isRestoreBackup() ||
InstState::isAddSiteOnMultisite()
) {
return ParamForm::STATUS_INFO_ONLY;
} else {
return ParamForm::STATUS_ENABLED;
}
},
'wrapperClasses' => [
'revalidate-on-change',
'cant-be-empty',
'requires-db-hide',
],
'subNote' => function (ParamForm $param): string {
$archive_config = \DUPX_ArchiveConfig::getInstance();
$oldHomeUrl = rtrim($archive_config->getRealValue('homeUrl'), '/');
$subsiteId = PrmMng::getInstance()->getValue(PrmMng::PARAM_SUBSITE_ID);
if (
InstState::isInstType(
[InstState::TYPE_STANDALONE]
) &&
$subsiteId > 0
) {
$subsiteObj = $archive_config->getSubsiteObjById($subsiteId);
$oldHomeUrl = $subsiteObj->fullHomeUrl ?? $oldHomeUrl;
}
return 'Old value: <b>' . \DUPX_U::esc_html($oldHomeUrl) . '</b>';
},
'postfix' => [
'type' => 'button',
'label' => 'get',
'btnAction' => 'DUPX.getNewUrlByDomObj(this);',
],
]
);
$params[PrmMng::PARAM_URL_NEW] = $newObj;
$urlNewInputId = $newObj->getFormItemId();
$params[PrmMng::PARAM_PATH_OLD] = new ParamItem(
PrmMng::PARAM_PATH_OLD,
ParamForm::TYPE_STRING,
['default' => $oldMainPath]
);
$newObj = new ParamForm(
PrmMng::PARAM_PATH_NEW,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_TEXT,
[// ITEM ATTRIBUTES
'default' => $newMainPath,
'sanitizeCallback' => [
ParamsDescriptors::class,
'sanitizePath',
],
'validateCallback' => function ($value, ParamItem $paramObj): bool {
if (strlen($value) == 0) {
$paramObj->setInvalidMessage('The new path can\'t be empty.');
return false;
}
// if home path is root path is necessary do a trailingslashit
$realPath = SnapIO::safePathTrailingslashit($value);
if (!is_dir($realPath)) {
$paramObj->setInvalidMessage(
'The new path must be an existing folder on the server.<br>' .
'It is not possible to continue the installation without first creating the folder <br>' .
'<b>' . $value . '</b>'
);
return false;
}
// don't check the return of chmod, if fail the installer must continue
SnapIO::chmod($realPath, 'u+rwx');
return true;
},
],
[// FORM ATTRIBUTES
'label' => 'New Path:',
'status' => function (ParamForm $param): string {
if (
PrmMng::getInstance()->getValue(PrmMng::PARAM_TEMPLATE) !== \DUPX_Template::TEMPLATE_ADVANCED ||
InstState::isRestoreBackup() ||
InstState::isAddSiteOnMultisite()
) {
return ParamForm::STATUS_INFO_ONLY;
} else {
return ParamForm::STATUS_ENABLED;
}
},
'subNote' => 'Old value: <b>' . \DUPX_U::esc_html($oldMainPath) . '</b>',
'wrapperClasses' => [
'revalidate-on-change',
'cant-be-empty',
'requires-db-hide',
],
]
);
$params[PrmMng::PARAM_PATH_NEW] = $newObj;
$pathNewInputId = $newObj->getFormItemId();
$params[PrmMng::PARAM_SITE_URL_OLD] = new ParamItem(
PrmMng::PARAM_SITE_URL_OLD,
ParamForm::TYPE_STRING,
['default' => $oldSiteUrl]
);
$wrapClasses = [
'revalidate-on-change',
'cant-be-empty',
'auto-updatable',
'autoupdate-enabled',
];
$postfixElement = [
'type' => 'button',
'label' => 'Auto',
'btnAction' => 'DUPX.autoUpdateToggle(this, ' . SnapJson::jsonEncode($defValEdit) . ');',
];
$params[PrmMng::PARAM_SITE_URL] = new ParamForm(
PrmMng::PARAM_SITE_URL,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_TEXT,
[// ITEM ATTRIBUTES
'default' => '',
'sanitizeCallback' => [
ParamsDescriptors::class,
'sanitizeUrl',
],
'validateCallback' => [
ParamsDescriptors::class,
'validateUrlWithScheme',
],
],
[// FORM ATTRIBUTES
'label' => 'WP core URL:',
'status' => [
self::class,
'statusFormOtherPathsUrls',
],
'postfix' => $postfixElement,
'subNote' => 'Old value: <b>' . \DUPX_U::esc_html($oldSiteUrl) . '</b>',
'wrapperClasses' => $wrapClasses,
'wrapperAttr' => ['data-auto-update-from-input' => $urlNewInputId],
]
);
$params[PrmMng::PARAM_PATH_CONTENT_OLD] = new ParamItem(
PrmMng::PARAM_PATH_CONTENT_OLD,
ParamForm::TYPE_STRING,
['default' => $oldContentPath]
);
$params[PrmMng::PARAM_PATH_CONTENT_NEW] = new ParamForm(
PrmMng::PARAM_PATH_CONTENT_NEW,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_TEXT,
[// ITEM ATTRIBUTES
'default' => '',
'sanitizeCallback' => [
ParamsDescriptors::class,
'sanitizePath',
],
'validateCallback' => [
ParamsDescriptors::class,
'validatePath',
],
],
[// FORM ATTRIBUTES
'label' => 'WP-content path:',
'status' => [
self::class,
'statusFormOtherPathsUrls',
],
'postfix' => $postfixElement,
'subNote' => 'Old value: <b>' . \DUPX_U::esc_html($oldContentPath) . '</b>',
'wrapperClasses' => $wrapClasses,
'wrapperAttr' => ['data-auto-update-from-input' => $pathNewInputId],
]
);
$params[PrmMng::PARAM_PATH_WP_CORE_OLD] = new ParamItem(
PrmMng::PARAM_PATH_WP_CORE_OLD,
ParamForm::TYPE_STRING,
['default' => $oldWpAbsPath]
);
$params[PrmMng::PARAM_PATH_WP_CORE_NEW] = new ParamForm(
PrmMng::PARAM_PATH_WP_CORE_NEW,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_TEXT,
[// ITEM ATTRIBUTES
'default' => '',
'sanitizeCallback' => [
ParamsDescriptors::class,
'sanitizePath',
],
'validateCallback' => function ($value, ParamItem $paramObj): bool {
$homePath = PrmMng::getInstance()->getValue(PrmMng::PARAM_PATH_NEW);
if (!SnapIO::isChildPath($value, $homePath)) {
$paramObj->setInvalidMessage(
'ABSPATH have to be a equal or a child of HOMEPATH' .
'<pre>' .
'ABSPATH : ' . $value . '<br>' .
'HOMEPATH: ' . $homePath . '<br>' .
'</pre>'
);
return false;
}
return true;
},
],
[// FORM ATTRIBUTES
'label' => 'WP core path:',
'status' => [
self::class,
'statusFormOtherPathsUrls',
],
'postfix' => $postfixElement,
'subNote' => 'Old value: <b>' . \DUPX_U::esc_html($oldWpAbsPath) . '</b>',
'wrapperClasses' => $wrapClasses,
'wrapperAttr' => ['data-auto-update-from-input' => $pathNewInputId],
]
);
$params[PrmMng::PARAM_PATH_UPLOADS_OLD] = new ParamItem(
PrmMng::PARAM_PATH_UPLOADS_OLD,
ParamForm::TYPE_STRING,
['default' => $oldUploadsBasePath]
);
$params[PrmMng::PARAM_PATH_UPLOADS_NEW] = new ParamForm(
PrmMng::PARAM_PATH_UPLOADS_NEW,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_TEXT,
[// ITEM ATTRIBUTES
'default' => '',
'sanitizeCallback' => [
ParamsDescriptors::class,
'sanitizePath',
],
'validateCallback' => function ($value, ParamItem $paramObj): bool {
$paramsManager = PrmMng::getInstance();
$result = (
SnapIO::isChildPath($value, $paramsManager->getValue(PrmMng::PARAM_PATH_NEW), false, false) ||
SnapIO::isChildPath($value, $paramsManager->getValue(PrmMng::PARAM_PATH_CONTENT_NEW), false, false)
);
if ($result == false) {
$paramObj->setInvalidMessage('Upload path have to be a child of wp-content path');
}
return $result;
},
],
[// FORM ATTRIBUTES
'label' => 'Uploads path:',
'status' => [
self::class,
'statusFormOtherPathsUrls',
],
'postfix' => $postfixElement,
'subNote' => 'Old value: <b>' . \DUPX_U::esc_html($oldUploadsBasePath) . '</b>',
'wrapperClasses' => $wrapClasses,
'wrapperAttr' => ['data-auto-update-from-input' => $pathNewInputId],
]
);
$params[PrmMng::PARAM_URL_CONTENT_OLD] = new ParamItem(
PrmMng::PARAM_URL_CONTENT_OLD,
ParamForm::TYPE_STRING,
['default' => $oldContentUrl]
);
$params[PrmMng::PARAM_URL_CONTENT_NEW] = new ParamForm(
PrmMng::PARAM_URL_CONTENT_NEW,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_TEXT,
[// ITEM ATTRIBUTES
'default' => '',
'sanitizeCallback' => [
ParamsDescriptors::class,
'sanitizeUrl',
],
'validateCallback' => [
ParamsDescriptors::class,
'validateUrlWithScheme',
],
],
[// FORM ATTRIBUTES
'label' => 'WP-content URL:',
'status' => [
self::class,
'statusFormOtherPathsUrls',
],
'postfix' => $postfixElement,
'subNote' => 'Old value: <b>' . \DUPX_U::esc_html($oldContentUrl) . '</b>',
'wrapperClasses' => $wrapClasses,
'wrapperAttr' => ['data-auto-update-from-input' => $urlNewInputId],
]
);
$params[PrmMng::PARAM_URL_UPLOADS_OLD] = new ParamItem(
PrmMng::PARAM_URL_UPLOADS_OLD,
ParamForm::TYPE_STRING,
['default' => $oldUploadUrl]
);
$params[PrmMng::PARAM_URL_UPLOADS_NEW] = new ParamForm(
PrmMng::PARAM_URL_UPLOADS_NEW,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_TEXT,
[
'default' => '',
'sanitizeCallback' => [
ParamsDescriptors::class,
'sanitizeUrl',
],
'validateCallback' => [
ParamsDescriptors::class,
'validateUrlWithScheme',
],
],
[// FORM ATTRIBUTES
'label' => 'Uploads URL:',
'status' => [
self::class,
'statusFormOtherPathsUrls',
],
'postfix' => $postfixElement,
'subNote' => 'Old value: <b>' . \DUPX_U::esc_html($oldUploadUrl) . '</b>',
'wrapperClasses' => $wrapClasses,
'wrapperAttr' => ['data-auto-update-from-input' => $urlNewInputId],
]
);
$params[PrmMng::PARAM_URL_PLUGINS_OLD] = new ParamItem(
PrmMng::PARAM_URL_PLUGINS_OLD,
ParamForm::TYPE_STRING,
['default' => $oldPluginsUrl]
);
$params[PrmMng::PARAM_URL_PLUGINS_NEW] = new ParamForm(
PrmMng::PARAM_URL_PLUGINS_NEW,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_TEXT,
[// ITEM ATTRIBUTES
'default' => '',
'sanitizeCallback' => [
ParamsDescriptors::class,
'sanitizeUrl',
],
'validateCallback' => [
ParamsDescriptors::class,
'validateUrlWithScheme',
],
],
[// FORM ATTRIBUTES
'label' => 'Plugins URL:',
'status' => [
self::class,
'statusFormOtherPathsUrls',
],
'postfix' => $postfixElement,
'subNote' => 'Old value: <b>' . \DUPX_U::esc_html($oldPluginsUrl) . '</b>',
'wrapperClasses' => $wrapClasses,
'wrapperAttr' => ['data-auto-update-from-input' => $urlNewInputId],
]
);
$params[PrmMng::PARAM_PATH_PLUGINS_OLD] = new ParamItem(
PrmMng::PARAM_PATH_PLUGINS_OLD,
ParamForm::TYPE_STRING,
[
'default' => $oldPluginsPath,
'sanitizeCallback' => [
ParamsDescriptors::class,
'sanitizePath',
],
'validateCallback' => [
ParamsDescriptors::class,
'validatePath',
],
]
);
$params[PrmMng::PARAM_PATH_PLUGINS_NEW] = new ParamForm(
PrmMng::PARAM_PATH_PLUGINS_NEW,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_TEXT,
[// ITEM ATTRIBUTES
'default' => '',
'sanitizeCallback' => [
ParamsDescriptors::class,
'sanitizePath',
],
'validateCallback' => [
ParamsDescriptors::class,
'validatePath',
],
],
[// FORM ATTRIBUTES
'label' => 'Plugins path:',
'status' => [
self::class,
'statusFormOtherPathsUrls',
],
'postfix' => $postfixElement,
'subNote' => 'Old value: <b>' . \DUPX_U::esc_html($oldPluginsPath) . '</b>',
'wrapperClasses' => $wrapClasses,
'wrapperAttr' => ['data-auto-update-from-input' => $pathNewInputId],
]
);
$params[PrmMng::PARAM_URL_MUPLUGINS_OLD] = new ParamItem(
PrmMng::PARAM_URL_MUPLUGINS_OLD,
ParamForm::TYPE_STRING,
['default' => $oldMuPluginsUrl]
);
$params[PrmMng::PARAM_URL_MUPLUGINS_NEW] = new ParamForm(
PrmMng::PARAM_URL_MUPLUGINS_NEW,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_TEXT,
[// ITEM ATTRIBUTES
'default' => '',
'sanitizeCallback' => [
ParamsDescriptors::class,
'sanitizeUrl',
],
'validateCallback' => [
ParamsDescriptors::class,
'validateUrlWithScheme',
],
],
[// FORM ATTRIBUTES
'label' => 'MU-plugins URL:',
'status' => [
self::class,
'statusFormOtherPathsUrls',
],
'postfix' => $postfixElement,
'subNote' => 'Old value: <b>' . \DUPX_U::esc_html($oldMuPluginsUrl) . '</b>',
'wrapperClasses' => $wrapClasses,
'wrapperAttr' => ['data-auto-update-from-input' => $urlNewInputId],
]
);
$params[PrmMng::PARAM_PATH_MUPLUGINS_OLD] = new ParamItem(
PrmMng::PARAM_PATH_MUPLUGINS_OLD,
ParamForm::TYPE_STRING,
['default' => $oldMuPluginsPath]
);
$params[PrmMng::PARAM_PATH_MUPLUGINS_NEW] = new ParamForm(
PrmMng::PARAM_PATH_MUPLUGINS_NEW,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_TEXT,
[// ITEM ATTRIBUTES
'default' => '',
'sanitizeCallback' => [
ParamsDescriptors::class,
'sanitizePath',
],
'validateCallback' => [
ParamsDescriptors::class,
'validatePath',
],
],
[// FORM ATTRIBUTES
'label' => 'MU-plugins path:',
'status' => [
self::class,
'statusFormOtherPathsUrls',
],
'postfix' => $postfixElement,
'subNote' => 'Old value: <b>' . \DUPX_U::esc_html($oldMuPluginsPath) . '</b>',
'wrapperClasses' => $wrapClasses,
'wrapperAttr' => ['data-auto-update-from-input' => $pathNewInputId],
]
);
}
/**
* Return statu form for paths and urls options
*
* @param ParamForm $param current param
*
* @return string
*/
public static function statusFormOtherPathsUrls(ParamForm $param): string
{
if (
PrmMng::getInstance()->getValue(PrmMng::PARAM_TEMPLATE) !== \DUPX_Template::TEMPLATE_ADVANCED ||
InstState::isRestoreBackup() ||
InstState::isAddSiteOnMultisite()
) {
return ParamForm::STATUS_INFO_ONLY;
} else {
return ParamForm::STATUS_READONLY;
}
}
/**
* Update params after overwrite logic
*
* @param ParamItem[]|ParamForm[] $params params list
*
* @return void
*/
public static function updateParamsAfterOverwrite($params): void
{
PrmMng::getInstance();
$archive_config = \DUPX_ArchiveConfig::getInstance();
$paths = $archive_config->getRealValue('archivePaths');
$oldMainPath = $paths->home;
$newMainPath = $params[PrmMng::PARAM_PATH_NEW]->getValue();
$oldHomeUrl = rtrim($archive_config->getRealValue('homeUrl'), '/');
$newHomeUrl = $params[PrmMng::PARAM_URL_NEW]->getValue();
$oldSiteUrl = rtrim($archive_config->getRealValue('siteUrl'), '/');
$oldContentUrl = rtrim($archive_config->getRealValue('contentUrl'), '/');
$oldUploadUrl = rtrim($archive_config->getRealValue('uploadBaseUrl'), '/');
$oldPluginsUrl = rtrim($archive_config->getRealValue('pluginsUrl'), '/');
$oldMuPluginsUrl = rtrim($archive_config->getRealValue('mupluginsUrl'), '/');
// if empty value isn't overwritten
if (strlen($params[PrmMng::PARAM_PATH_WP_CORE_NEW]->getValue()) == 0) {
$newVal = \DUPX_ArchiveConfig::getNewSubString($oldMainPath, $newMainPath, $paths->abs);
$params[PrmMng::PARAM_PATH_WP_CORE_NEW]->setValue($newVal);
}
// if empty value isn't overwritten
if (strlen($params[PrmMng::PARAM_PATH_CONTENT_NEW]->getValue()) == 0) {
$newVal = \DUPX_ArchiveConfig::getNewSubString($oldMainPath, $newMainPath, $paths->wpcontent);
$params[PrmMng::PARAM_PATH_CONTENT_NEW]->setValue($newVal);
}
// if empty value isn't overwritten
if (strlen($params[PrmMng::PARAM_PATH_UPLOADS_NEW]->getValue()) == 0) {
$newVal = \DUPX_ArchiveConfig::getNewSubString($oldMainPath, $newMainPath, $paths->uploads);
$params[PrmMng::PARAM_PATH_UPLOADS_NEW]->setValue($newVal);
}
// if empty value isn't overwritten
if (strlen($params[PrmMng::PARAM_PATH_PLUGINS_NEW]->getValue()) == 0) {
$newVal = \DUPX_ArchiveConfig::getNewSubString($oldMainPath, $newMainPath, $paths->plugins);
$params[PrmMng::PARAM_PATH_PLUGINS_NEW]->setValue($newVal);
}
// if empty value isn't overwritten
if (strlen($params[PrmMng::PARAM_PATH_MUPLUGINS_NEW]->getValue()) == 0) {
$newVal = \DUPX_ArchiveConfig::getNewSubString($oldMainPath, $newMainPath, $paths->muplugins);
$params[PrmMng::PARAM_PATH_MUPLUGINS_NEW]->setValue($newVal);
}
// if empty value isn't overwritten
if (strlen($params[PrmMng::PARAM_SITE_URL]->getValue()) == 0) {
$newVal = \DUPX_ArchiveConfig::getNewSubUrl($oldHomeUrl, $newHomeUrl, $oldSiteUrl);
$params[PrmMng::PARAM_SITE_URL]->setValue($newVal);
}
// if empty value isn't overwritten
if (strlen($params[PrmMng::PARAM_URL_CONTENT_NEW]->getValue()) == 0) {
$newVal = \DUPX_ArchiveConfig::getNewSubUrl($oldHomeUrl, $newHomeUrl, $oldContentUrl);
$params[PrmMng::PARAM_URL_CONTENT_NEW]->setValue($newVal);
}
// if empty value isn't overwritten
if (strlen($params[PrmMng::PARAM_URL_UPLOADS_NEW]->getValue()) == 0) {
$newVal = \DUPX_ArchiveConfig::getNewSubUrl($oldHomeUrl, $newHomeUrl, $oldUploadUrl);
$params[PrmMng::PARAM_URL_UPLOADS_NEW]->setValue($newVal);
}
// if empty value isn't overwritten
if (strlen($params[PrmMng::PARAM_URL_PLUGINS_NEW]->getValue()) == 0) {
$newVal = \DUPX_ArchiveConfig::getNewSubUrl($oldHomeUrl, $newHomeUrl, $oldPluginsUrl);
$params[PrmMng::PARAM_URL_PLUGINS_NEW]->setValue($newVal);
}
// if empty value isn't overwritten
if (strlen($params[PrmMng::PARAM_URL_MUPLUGINS_NEW]->getValue()) == 0) {
$newVal = \DUPX_ArchiveConfig::getNewSubUrl($oldHomeUrl, $newHomeUrl, $oldMuPluginsUrl);
$params[PrmMng::PARAM_URL_MUPLUGINS_NEW]->setValue($newVal);
}
}
}

View File

@@ -0,0 +1,195 @@
<?php
/**
* Users params descriptions
*
* @category Duplicator
* @package Installer
* @author Snapcreek <admin@snapcreek.com>
* @copyright 2011-2021 Snapcreek LLC
* @license https://www.gnu.org/licenses/gpl-3.0.html GPLv3
*/
namespace Duplicator\Installer\Core\Params\Descriptors;
use Duplicator\Installer\Core\Params\PrmMng;
use Duplicator\Installer\Core\Params\Items\ParamItem;
use Duplicator\Installer\Core\Params\Items\ParamForm;
use Duplicator\Installer\Core\Params\Items\ParamOption;
use Duplicator\Installer\Core\Params\Items\ParamFormUsersReset;
use DUPX_DBInstall;
use Duplicator\Installer\Core\InstState;
use Duplicator\Libs\Snap\SnapUtil;
/**
* class where all parameters are initialized. Used by the param manager
*/
final class ParamDescUsers implements DescriptorInterface
{
const USER_MODE_OVERWRITE = 'overwrite';
const USER_MODE_IMPORT_USERS = 'import_users';
/**
* Init params
*
* @param ParamItem[]|ParamForm[] $params params list
*
* @return void
*/
public static function init(&$params): void
{
$params[PrmMng::PARAM_USERS_MODE] = new ParamForm(
PrmMng::PARAM_USERS_MODE,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_RADIO,
[
'default' => self::USER_MODE_OVERWRITE,
'sanitizeCallback' => function ($value) {
if (
InstState::getInstance()->getMode() !== InstState::MODE_OVR_INSTALL ||
InstState::isRestoreBackup() ||
InstState::isAddSiteOnMultisite()
) {
// if is restore backup user mode must be overwrite
return ParamDescUsers::USER_MODE_OVERWRITE;
}
$overwriteData = PrmMng::getInstance()->getValue(PrmMng::PARAM_OVERWRITE_SITE_DATA);
if ($overwriteData['isMultisite']) {
return ParamDescUsers::USER_MODE_OVERWRITE;
}
// disable keep users for some db actions
switch (PrmMng::getInstance()->getValue(PrmMng::PARAM_DB_ACTION)) {
case DUPX_DBInstall::DBACTION_CREATE:
case DUPX_DBInstall::DBACTION_MANUAL:
case DUPX_DBInstall::DBACTION_ONLY_CONNECT:
return ParamDescUsers::USER_MODE_OVERWRITE;
case DUPX_DBInstall::DBACTION_EMPTY:
case DUPX_DBInstall::DBACTION_REMOVE_ONLY_TABLES:
case DUPX_DBInstall::DBACTION_RENAME:
return $value;
}
},
'acceptValues' => [
self::USER_MODE_OVERWRITE,
self::USER_MODE_IMPORT_USERS,
],
],
[
'status' => function (): string {
// Hide user mode instandalone migration for now
return ParamForm::STATUS_SKIP;
if (InstState::getInstance()->getMode() !== InstState::MODE_OVR_INSTALL) { // @phpstan-ignore-line
return ParamForm::STATUS_DISABLED;
}
$overwriteData = PrmMng::getInstance()->getValue(PrmMng::PARAM_OVERWRITE_SITE_DATA);
if (
$overwriteData['isMultisite'] ||
InstState::isRestoreBackup() ||
InstState::isAddSiteOnMultisite() ||
InstState::isNewSiteIsMultisite()
) {
return ParamForm::STATUS_DISABLED;
}
return ParamForm::STATUS_ENABLED;
},
'label' => 'Users:',
'options' => function ($item): array {
$result = [];
$result[] = new ParamOption(ParamDescUsers::USER_MODE_OVERWRITE, 'Overwrite');
$result[] = new ParamOption(ParamDescUsers::USER_MODE_IMPORT_USERS, 'Merge');
return $result;
},
'inlineHelp' => dupxTplRender('parts/params/inline_helps/user_mode', [], false),
'wrapperClasses' => ['revalidate-on-change'],
]
);
$params[PrmMng::PARAM_ADD_SUBSITE_USER_MODE] = new ParamForm(
PrmMng::PARAM_ADD_SUBSITE_USER_MODE,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_RADIO,
[
'default' => ParamDescUsers::USER_MODE_IMPORT_USERS,
'acceptValues' => [ParamDescUsers::USER_MODE_IMPORT_USERS],
],
[
'status' => fn(): string => ParamForm::STATUS_SKIP,
'label' => 'Users:',
'options' => function ($item): array {
$result = [];
$result[] = new ParamOption(ParamDescUsers::USER_MODE_IMPORT_USERS, 'Import');
return $result;
},
'inlineHelpTitle' => 'User Mode',
'inlineHelp' => dupxTplRender('parts/params/inline_helps/subsite_user_mode', [], false),
'wrapperClasses' => ['revalidate-on-change'],
]
);
$params[PrmMng::PARAM_USERS_PWD_RESET] = new ParamFormUsersReset(
PrmMng::PARAM_USERS_PWD_RESET,
ParamFormUsersReset::TYPE_ARRAY_STRING,
ParamFormUsersReset::FORM_TYPE_USERS_PWD_RESET,
[ // ITEM ATTRIBUTES
'default' => array_map(fn($value): string => '', \DUPX_ArchiveConfig::getInstance()->getUsersLists()),
'sanitizeCallback' => [
SnapUtil::class,
'sanitizeNSCharsNewlineTrim',
],
'validateCallback' => function ($value, ParamItem $paramObj): bool {
if (strlen($value) > 0 && strlen($value) < \DUPX_Constants::MIN_NEW_PASSWORD_LEN) {
$paramObj->setInvalidMessage('New password must have ' . \DUPX_Constants::MIN_NEW_PASSWORD_LEN . ' or more characters');
return false;
}
return true;
},
],
[ // FORM ATTRIBUTES
'status' => function ($paramObj): string {
if (ParamDescUsers::getUsersMode() != ParamDescUsers::USER_MODE_OVERWRITE) {
return ParamForm::STATUS_DISABLED;
} else {
return ParamForm::STATUS_ENABLED;
}
},
'label' => 'Existing user reset password:',
'classes' => 'strength-pwd-check',
'attr' => [
'title' => \DUPX_Constants::MIN_NEW_PASSWORD_LEN . ' characters minimum',
'placeholder' => "Reset user password",
],
]
);
}
/**
* Return import users mode
*
* @return string
*/
public static function getUsersMode()
{
$paramsManager = PrmMng::getInstance();
if (InstState::isAddSiteOnMultisite()) {
return $paramsManager->getValue(PrmMng::PARAM_ADD_SUBSITE_USER_MODE);
} else {
return $paramsManager->getValue(PrmMng::PARAM_USERS_MODE);
}
}
/**
* Update params after overwrite logic
*
* @param ParamItem[]|ParamForm[] $params params list
*
* @return void
*/
public static function updateParamsAfterOverwrite($params): void
{
}
}

View File

@@ -0,0 +1,96 @@
<?php
/**
* Validation params descriptions
*
* @category Duplicator
* @package Installer
* @author Snapcreek <admin@snapcreek.com>
* @copyright 2011-2021 Snapcreek LLC
* @license https://www.gnu.org/licenses/gpl-3.0.html GPLv3
*/
namespace Duplicator\Installer\Core\Params\Descriptors;
use Duplicator\Installer\Core\Params\PrmMng;
use Duplicator\Installer\Core\Params\Items\ParamItem;
use Duplicator\Installer\Core\Params\Items\ParamForm;
/**
* class where all parameters are initialized. Used by the param manager
*/
final class ParamDescValidation implements DescriptorInterface
{
/**
* Init params
*
* @param ParamItem[]|ParamForm[] $params params list
*
* @return void
*/
public static function init(&$params): void
{
$params[PrmMng::PARAM_VALIDATION_LEVEL] = new ParamItem(
PrmMng::PARAM_VALIDATION_LEVEL,
ParamItem::TYPE_INT,
[
'default' => \DUPX_Validation_abstract_item::LV_FAIL,
'acceptValues' => [
\DUPX_Validation_abstract_item::LV_FAIL,
\DUPX_Validation_abstract_item::LV_HARD_WARNING,
\DUPX_Validation_abstract_item::LV_SOFT_WARNING,
\DUPX_Validation_abstract_item::LV_GOOD,
\DUPX_Validation_abstract_item::LV_PASS,
],
]
);
$params[PrmMng::PARAM_VALIDATION_ACTION_ON_START] = new ParamItem(
PrmMng::PARAM_VALIDATION_ACTION_ON_START,
ParamForm::TYPE_STRING,
[
'default' => \DUPX_Validation_manager::ACTION_ON_START_NORMAL,
'acceptValues' => [
\DUPX_Validation_manager::ACTION_ON_START_NORMAL,
\DUPX_Validation_manager::ACTION_ON_START_AUTO,
],
]
);
$params[PrmMng::PARAM_VALIDATION_SHOW_ALL] = new ParamForm(
PrmMng::PARAM_VALIDATION_SHOW_ALL,
ParamForm::TYPE_BOOL,
ParamForm::FORM_TYPE_SWITCH,
['default' => false],
[
'label' => 'Show all',
'wrapperClasses' => 'align-right',
]
);
$params[PrmMng::PARAM_ACCEPT_TERM_COND] = new ParamForm(
PrmMng::PARAM_ACCEPT_TERM_COND,
ParamForm::TYPE_BOOL,
ParamForm::FORM_TYPE_CHECKBOX,
['default' => false],
[
'label' => 'Accept term and conditions',
'renderLabel' => false,
'checkboxLabel' => 'I have read and accept all <a href="#" onclick="DUPX.viewTerms()" >terms &amp; notices</a>*',
'subNote' => '<div class="required-txt">* required to continue</div>',
'attr' => ['onclick' => 'DUPX.acceptWarning();'],
]
);
}
/**
* Update params after overwrite logic
*
* @param ParamItem[]|ParamForm[] $params params list
*
* @return void
*/
public static function updateParamsAfterOverwrite($params): void
{
}
}

View File

@@ -0,0 +1,765 @@
<?php
/**
* WP-config params descriptions
*
* @category Duplicator
* @package Installer
* @author Snapcreek <admin@snapcreek.com>
* @copyright 2011-2021 Snapcreek LLC
* @license https://www.gnu.org/licenses/gpl-3.0.html GPLv3
*/
namespace Duplicator\Installer\Core\Params\Descriptors;
use Duplicator\Installer\Core\Params\PrmMng;
use Duplicator\Installer\Core\Params\Items\ParamItem;
use Duplicator\Installer\Core\Params\Items\ParamForm;
use Duplicator\Installer\Core\Params\Items\ParamOption;
use Duplicator\Installer\Core\Params\Items\ParamFormWpConfig;
use Duplicator\Libs\Snap\SnapUtil;
use Duplicator\Libs\Snap\SnapIO;
use Duplicator\Installer\Addons\ProBase\License;
use Duplicator\Libs\Snap\SnapDB;
use DUPX_ArchiveConfig;
use Duplicator\Installer\Core\InstState;
use Duplicator\Libs\Snap\SnapURL;
/**
* class where all parameters are initialized. Used by the param manager
*/
final class ParamDescWpConfig implements DescriptorInterface
{
const NOTICE_ID_WP_CONF_PARAM_PATHS_EMPTY = 'wp_conf_param_paths_empty_to_validate';
const NOTICE_ID_WP_CONF_FORCE_SSL_ADMIN = 'wp_conf_disabled_force_ssl_admin';
const NOTICE_ID_WP_CONF_PARAM_DOMAINS_MODIFIED = 'wp_conf_param_domains_empty_to_validate';
/**
* Init params
*
* @param ParamItem[]|ParamForm[] $params params list
*
* @return void
*/
public static function init(&$params): void
{
$archiveConfig = \DUPX_ArchiveConfig::getInstance();
$params[PrmMng::PARAM_WP_CONF_DISALLOW_FILE_EDIT] = new ParamFormWpConfig(
PrmMng::PARAM_WP_CONF_DISALLOW_FILE_EDIT,
ParamForm::TYPE_BOOL,
ParamForm::FORM_TYPE_CHECKBOX,
[
'default' => $archiveConfig->getDefineArrayValue(
'DISALLOW_FILE_EDIT'
),
],
[
'label' => 'DISALLOW_FILE_EDIT:',
'checkboxLabel' => 'Disable the Plugin/Theme Editor',
]
);
$params[PrmMng::PARAM_WP_CONF_DISALLOW_FILE_MODS] = new ParamFormWpConfig(
PrmMng::PARAM_WP_CONF_DISALLOW_FILE_MODS,
ParamForm::TYPE_BOOL,
ParamForm::FORM_TYPE_CHECKBOX,
[
'default' => $archiveConfig->getDefineArrayValue(
'DISALLOW_FILE_MODS',
[
'value' => false,
'inWpConfig' => false,
]
),
],
[
'label' => 'DISALLOW_FILE_MODS:',
'checkboxLabel' => 'This will block users being able to use the plugin and theme installation/update ' .
'functionality from the WordPress admin area',
]
);
$params[PrmMng::PARAM_WP_CONF_AUTOSAVE_INTERVAL] = new ParamFormWpConfig(
PrmMng::PARAM_WP_CONF_AUTOSAVE_INTERVAL,
ParamForm::TYPE_INT,
ParamForm::FORM_TYPE_NUMBER,
[ // ITEM ATTRIBUTES
'default' => $archiveConfig->getDefineArrayValue(
'AUTOSAVE_INTERVAL',
[
'value' => 60,
'inWpConfig' => false,
]
),
],
[ // FORM ATTRIBUTES
'label' => 'AUTOSAVE_INTERVAL:',
'subNote' => 'Auto-save interval in seconds (default:60)',
'min' => 5,
'step' => 1,
'wrapperClasses' => ['small'],
'postfix' => [
'type' => 'label',
'label' => 'Sec.',
],
]
);
$params[PrmMng::PARAM_WP_CONF_WP_POST_REVISIONS] = new ParamFormWpConfig(
PrmMng::PARAM_WP_CONF_WP_POST_REVISIONS,
ParamForm::TYPE_INT,
ParamForm::FORM_TYPE_NUMBER,
[ // ITEM ATTRIBUTES
'default' => $archiveConfig->getDefineArrayValue(
'WP_POST_REVISIONS',
[
'value' => true,
'inWpConfig' => false,
]
),
'sanitizeCallback' => function ($value) {
//convert bool on int
if ($value === true) {
$value = PHP_INT_MAX;
}
if ($value === false) {
$value = 0;
}
return $value;
},
],
[ // FORM ATTRIBUTES
'label' => 'WP_POST_REVISIONS:',
'subNote' => 'Number of article revisions. Select 0 to disable revisions. Disable the field to enable revisions.',
'min' => 0,
'step' => 1,
'wrapperClasses' => ['small'],
]
);
$params[PrmMng::PARAM_WP_CONF_FORCE_SSL_ADMIN] = new ParamFormWpConfig(
PrmMng::PARAM_WP_CONF_FORCE_SSL_ADMIN,
ParamForm::TYPE_BOOL,
ParamForm::FORM_TYPE_CHECKBOX,
[
'default' => self::getDefaultForceSSLAdminConfig(),
],
[
'label' => 'FORCE_SSL_ADMIN:',
'checkboxLabel' => 'Enforce Admin SSL',
]
);
$params[PrmMng::PARAM_WP_CONF_AUTOMATIC_UPDATER_DISABLED] = new ParamFormWpConfig(
PrmMng::PARAM_WP_CONF_AUTOMATIC_UPDATER_DISABLED,
ParamForm::TYPE_BOOL,
ParamForm::FORM_TYPE_CHECKBOX,
[
'default' => $archiveConfig->getDefineArrayValue(
'AUTOMATIC_UPDATER_DISABLED',
[
'value' => false,
'inWpConfig' => false,
]
),
],
[
'label' => 'AUTOMATIC_UPDATER_DISABLED:',
'checkboxLabel' => 'Disable automatic updater',
]
);
$autoUpdateValue = $archiveConfig->getWpConfigDefineValue('WP_AUTO_UPDATE_CORE');
if (is_bool($autoUpdateValue)) {
$autoUpdateValue = ($autoUpdateValue ? 'true' : 'false');
}
$params[PrmMng::PARAM_WP_CONF_WP_AUTO_UPDATE_CORE] = new ParamFormWpConfig(
PrmMng::PARAM_WP_CONF_WP_AUTO_UPDATE_CORE,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_SELECT,
[
'default' => [
'value' => $autoUpdateValue,
'inWpConfig' => $archiveConfig->inWpConfigDefine('WP_AUTO_UPDATE_CORE'),
],
'acceptValues' => [
'',
'false',
'true',
'minor',
],
],
[
'label' => 'WP_AUTO_UPDATE_CORE:',
'options' => [
new ParamOption('minor', 'Enable only core minor updates - Default'),
new ParamOption('false', 'Disable all core updates'),
new ParamOption('true', 'Enable all core updates'),
],
]
);
$params[PrmMng::PARAM_WP_CONF_IMAGE_EDIT_OVERWRITE] = new ParamFormWpConfig(
PrmMng::PARAM_WP_CONF_IMAGE_EDIT_OVERWRITE,
ParamForm::TYPE_BOOL,
ParamForm::FORM_TYPE_CHECKBOX,
[
'default' => $archiveConfig->getDefineArrayValue(
'IMAGE_EDIT_OVERWRITE',
[
'value' => true,
'inWpConfig' => false,
]
),
],
[
'label' => 'IMAGE_EDIT_OVERWRITE:',
'checkboxLabel' => 'Create only one set of image edits',
]
);
$params[PrmMng::PARAM_WP_CONF_WP_CACHE] = new ParamFormWpConfig(
PrmMng::PARAM_WP_CONF_WP_CACHE,
ParamForm::TYPE_BOOL,
ParamForm::FORM_TYPE_CHECKBOX,
[
'default' => $archiveConfig->getDefineArrayValue('WP_CACHE'),
],
[
'label' => 'WP_CACHE:',
'checkboxLabel' => 'Keep Enabled',
]
);
$params[PrmMng::PARAM_WP_CONF_WPCACHEHOME] = new ParamFormWpConfig(
PrmMng::PARAM_WP_CONF_WPCACHEHOME,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_TEXT,
[ // ITEM ATTRIBUTES
'default' => $archiveConfig->getDefineArrayValue("WPCACHEHOME"),
'sanitizeCallback' => function ($value): string {
$result = SnapUtil::sanitizeNSCharsNewlineTrim($value);
// WPCACHEHOME want final slash
return SnapIO::safePathTrailingslashit($result);
},
],
[ // FORM ATTRIBUTES
'label' => 'WPCACHEHOME:',
'subNote' => 'This define is not part of the WordPress core but is a define used by WP Super Cache.',
]
);
$params[PrmMng::PARAM_WP_CONF_WP_TEMP_DIR] = new ParamFormWpConfig(
PrmMng::PARAM_WP_CONF_WP_TEMP_DIR,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_TEXT,
[ // ITEM ATTRIBUTES
'default' => $archiveConfig->getDefineArrayValue("WP_TEMP_DIR"),
'sanitizeCallback' => [
ParamsDescriptors::class,
'sanitizePath',
],
],
['label' => 'WP_TEMP_DIR:']
);
$params[PrmMng::PARAM_WP_CONF_WP_DEBUG] = new ParamFormWpConfig(
PrmMng::PARAM_WP_CONF_WP_DEBUG,
ParamForm::TYPE_BOOL,
ParamForm::FORM_TYPE_CHECKBOX,
[
'default' => $archiveConfig->getDefineArrayValue('WP_DEBUG'),
],
[
'label' => 'WP_DEBUG:',
'checkboxLabel' => 'Display errors and warnings',
]
);
$debugLogValue = $archiveConfig->getWpConfigDefineValue('WP_DEBUG_LOG');
if (is_string($debugLogValue)) {
$debugLogValue = !empty($debugLogValue);
}
$params[PrmMng::PARAM_WP_CONF_WP_DEBUG_LOG] = new ParamFormWpConfig(
PrmMng::PARAM_WP_CONF_WP_DEBUG_LOG,
ParamForm::TYPE_BOOL,
ParamForm::FORM_TYPE_CHECKBOX,
[
'default' => [
'value' => $debugLogValue,
'inWpConfig' => $archiveConfig->inWpConfigDefine('WP_DEBUG_LOG'),
],
],
[
'label' => 'WP_DEBUG_LOG:',
'checkboxLabel' => 'Log errors and warnings',
]
);
$params[PrmMng::PARAM_WP_CONF_WP_DISABLE_FATAL_ERROR_HANDLER] = new ParamFormWpConfig(
PrmMng::PARAM_WP_CONF_WP_DISABLE_FATAL_ERROR_HANDLER,
ParamForm::TYPE_BOOL,
ParamForm::FORM_TYPE_CHECKBOX,
[
'default' => $archiveConfig->getDefineArrayValue('WP_DISABLE_FATAL_ERROR_HANDLER'),
],
[
'label' => 'WP_DISABLE_FATAL_ERROR_HANDLER:',
'checkboxLabel' => 'Disable fatal error handler',
'status' => version_compare($archiveConfig->version_wp, '5.2.0', '<') ? ParamForm::STATUS_SKIP : ParamForm::STATUS_ENABLED,
]
);
$params[PrmMng::PARAM_WP_CONF_WP_DEBUG_DISPLAY] = new ParamFormWpConfig(
PrmMng::PARAM_WP_CONF_WP_DEBUG_DISPLAY,
ParamForm::TYPE_BOOL,
ParamForm::FORM_TYPE_CHECKBOX,
[
'default' => $archiveConfig->getDefineArrayValue('WP_DEBUG_DISPLAY'),
],
[
'label' => 'WP_DEBUG_DISPLAY:',
'checkboxLabel' => 'Display errors and warnings',
]
);
$params[PrmMng::PARAM_WP_CONF_SCRIPT_DEBUG] = new ParamFormWpConfig(
PrmMng::PARAM_WP_CONF_SCRIPT_DEBUG,
ParamForm::TYPE_BOOL,
ParamForm::FORM_TYPE_CHECKBOX,
[
'default' => $archiveConfig->getDefineArrayValue('SCRIPT_DEBUG'),
],
[
'label' => 'SCRIPT_DEBUG:',
'checkboxLabel' => 'JavaScript or CSS errors',
]
);
$params[PrmMng::PARAM_WP_CONF_CONCATENATE_SCRIPTS] = new ParamFormWpConfig(
PrmMng::PARAM_WP_CONF_CONCATENATE_SCRIPTS,
ParamForm::TYPE_BOOL,
ParamForm::FORM_TYPE_CHECKBOX,
[
'default' => $archiveConfig->getDefineArrayValue('CONCATENATE_SCRIPTS', [
'value' => false,
'inWpConfig' => false,
]),
],
[
'label' => 'CONCATENATE_SCRIPTS:',
'checkboxLabel' => 'Concatenate all JavaScript files into one URL',
]
);
$params[PrmMng::PARAM_WP_CONF_SAVEQUERIES] = new ParamFormWpConfig(
PrmMng::PARAM_WP_CONF_SAVEQUERIES,
ParamForm::TYPE_BOOL,
ParamForm::FORM_TYPE_CHECKBOX,
[
'default' => $archiveConfig->getDefineArrayValue('SAVEQUERIES'),
],
[
'label' => 'SAVEQUERIES:',
'checkboxLabel' => 'Save database queries in an array ($wpdb->queries)',
]
);
$params[PrmMng::PARAM_WP_CONF_ALTERNATE_WP_CRON] = new ParamFormWpConfig(
PrmMng::PARAM_WP_CONF_ALTERNATE_WP_CRON,
ParamForm::TYPE_BOOL,
ParamForm::FORM_TYPE_CHECKBOX,
[
'default' => $archiveConfig->getDefineArrayValue('ALTERNATE_WP_CRON', [
'value' => false,
'inWpConfig' => false,
]),
],
[
'label' => 'ALTERNATE_WP_CRON:',
'checkboxLabel' => 'Use an alternative Cron with WP',
]
);
$params[PrmMng::PARAM_WP_CONF_DISABLE_WP_CRON] = new ParamFormWpConfig(
PrmMng::PARAM_WP_CONF_DISABLE_WP_CRON,
ParamForm::TYPE_BOOL,
ParamForm::FORM_TYPE_CHECKBOX,
[
'default' => $archiveConfig->getDefineArrayValue('DISABLE_WP_CRON', [
'value' => false,
'inWpConfig' => false,
]),
],
[
'label' => 'DISABLE_WP_CRON:',
'checkboxLabel' => 'Disable cron entirely',
]
);
$params[PrmMng::PARAM_WP_CONF_WP_CRON_LOCK_TIMEOUT] = new ParamFormWpConfig(
PrmMng::PARAM_WP_CONF_WP_CRON_LOCK_TIMEOUT,
ParamForm::TYPE_INT,
ParamForm::FORM_TYPE_NUMBER,
[
'default' => $archiveConfig->getDefineArrayValue('WP_CRON_LOCK_TIMEOUT', [
'value' => 60,
'inWpConfig' => false,
]),
'min_range' => 1,
],
[
'min' => 1,
'step' => 1,
'label' => 'WP_CRON_LOCK_TIMEOUT:',
'wrapperClasses' => ['small'],
'subNote' => 'Cron process cannot run more than once every WP_CRON_LOCK_TIMEOUT seconds',
]
);
$params[PrmMng::PARAM_WP_CONF_EMPTY_TRASH_DAYS] = new ParamFormWpConfig(
PrmMng::PARAM_WP_CONF_EMPTY_TRASH_DAYS,
ParamForm::TYPE_INT,
ParamForm::FORM_TYPE_NUMBER,
[
'default' => $archiveConfig->getDefineArrayValue('EMPTY_TRASH_DAYS', [
'value' => 30,
'inWpConfig' => false,
]),
'min_range' => 0,
],
[
'min' => 0,
'step' => 1,
'label' => 'EMPTY_TRASH_DAYS:',
'wrapperClasses' => ['small'],
'subNote' => 'How many days deleted post should be kept in trash before being deleted permanently',
]
);
$params[PrmMng::PARAM_WP_CONF_COOKIE_DOMAIN] = new ParamFormWpConfig(
PrmMng::PARAM_WP_CONF_COOKIE_DOMAIN,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_TEXT,
[ // ITEM ATTRIBUTES
'default' => $archiveConfig->getDefineArrayValue("COOKIE_DOMAIN"),
'sanitizeCallback' => [
SnapUtil::class,
'sanitizeNSCharsNewlineTrim',
],
],
[ // FORM ATTRIBUTES
'label' => 'COOKIE_DOMAIN:',
'subNote' => 'Set <a href="http://www.askapache.com/htaccess/apache-speed-subdomains.html" target="_blank">' .
'different domain</a> for cookies.subdomain.example.com',
]
);
$params[PrmMng::PARAM_WP_CONF_WP_MEMORY_LIMIT] = new ParamFormWpConfig(
PrmMng::PARAM_WP_CONF_WP_MEMORY_LIMIT,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_TEXT,
[ // ITEM ATTRIBUTES
'default' => $archiveConfig->getDefineArrayValue('WP_MEMORY_LIMIT'),
'sanitizeCallback' => [
SnapUtil::class,
'sanitizeNSCharsNewlineTrim',
],
'validateRegex' => ParamItem::VALIDATE_REGEX_AZ_NUMBER,
],
[ // FORM ATTRIBUTES
'label' => 'WP_MEMORY_LIMIT:',
'wrapperClasses' => ['small'],
'subNote' => 'PHP memory limit (default:30M; Multisite default:64M)',
]
);
$params[PrmMng::PARAM_WP_CONF_WP_MAX_MEMORY_LIMIT] = new ParamFormWpConfig(
PrmMng::PARAM_WP_CONF_WP_MAX_MEMORY_LIMIT,
ParamForm::TYPE_STRING,
ParamForm::FORM_TYPE_TEXT,
[ // ITEM ATTRIBUTES
'default' => $archiveConfig->getDefineArrayValue('WP_MAX_MEMORY_LIMIT'),
'sanitizeCallback' => [
SnapUtil::class,
'sanitizeNSCharsNewlineTrim',
],
'validateRegex' => ParamItem::VALIDATE_REGEX_AZ_NUMBER,
],
[ // FORM ATTRIBUTES
'label' => 'WP_MAX_MEMORY_LIMIT:',
'wrapperClasses' => ['small'],
'subNote' => 'Wordpress admin maximum memory limit (default:256M)',
]
);
$params[PrmMng::PARAM_WP_CONF_MYSQL_CLIENT_FLAGS] = new ParamFormWpConfig(
PrmMng::PARAM_WP_CONF_MYSQL_CLIENT_FLAGS,
ParamForm::TYPE_ARRAY_INT,
ParamForm::FORM_TYPE_SELECT,
[ // ITEM ATTRIBUTES
'default' => self::getMysqlClientFlagsDefaultVals(),
],
[ // FORM ATTRIBUTES
'label' => 'MYSQL_CLIENT_FLAGS:',
'options' => self::getMysqlClientFlagsOptions(),
'multiple' => true,
]
);
}
/**
* Update params after overwrite logic
*
* @param ParamItem[]|ParamForm[] $params params list
*
* @return void
*/
public static function updateParamsAfterOverwrite($params): void
{
//UPDATE PATHS AUTOMATICALLY
self::setDefaultWpConfigPathValue($params, PrmMng::PARAM_WP_CONF_WP_TEMP_DIR, 'WP_TEMP_DIR');
self::setDefaultWpConfigPathValue($params, PrmMng::PARAM_WP_CONF_WPCACHEHOME, 'WPCACHEHOME');
self::wpConfigPathsNotices();
//UPDATE DOMAINS AUTOMATICALLY
self::setDefaultWpConfigDomainValue($params, PrmMng::PARAM_WP_CONF_COOKIE_DOMAIN, "COOKIE_DOMAIN");
self::wpConfigDomainNotices();
}
/**
* Returns wp counfi default value
*
* @return array<string, mixed>
*/
protected static function getMysqlClientFlagsDefaultVals()
{
$result = DUPX_ArchiveConfig::getInstance()->getDefineArrayValue(
'MYSQL_CLIENT_FLAGS',
[
'value' => [],
'inWpConfig' => false,
]
);
$result['value'] = array_intersect($result['value'], SnapDB::getMysqlConnectFlagsList(false));
return $result;
}
/**
* Returns the list of options of the mysql real connect flags
*
* @return ParamOption[]
*/
protected static function getMysqlClientFlagsOptions(): array
{
$result = [];
foreach (SnapDB::getMysqlConnectFlagsList() as $flag) {
$result[] = new ParamOption(constant($flag), $flag);
}
return $result;
}
/**
* Tries to replace the old path with the new path for the given wp config define.
* If that's not possible returns a notice to the user.
*
* @param ParamItem[] $params params list
* @param string $paramKey param key
* @param string $wpConfigKey wp config key
*
* @return void
*/
protected static function setDefaultWpConfigPathValue(&$params, $paramKey, $wpConfigKey): void
{
if (!self::wpConfigNeedsUpdate($params, $paramKey, $wpConfigKey)) {
return;
}
$oldMainPath = $params[PrmMng::PARAM_PATH_OLD]->getValue();
$newMainPath = $params[PrmMng::PARAM_PATH_NEW]->getValue();
$wpConfigVal = \DUPX_ArchiveConfig::getInstance()->getDefineArrayValue($wpConfigKey);
// TRY TO CHANGE THE VALUE OR RESET
if (($wpConfigVal['value'] = \DUPX_ArchiveConfig::getNewSubString($oldMainPath, $newMainPath, $wpConfigVal['value'])) === false) {
$wpConfigVal['inWpConfig'] = false;
$wpConfigVal['value'] = '';
\DUPX_NOTICE_MANAGER::getInstance()->addNextStepNotice([
'shortMsg' => 'WP CONFIG custom paths disabled.',
'level' => \DUPX_NOTICE_ITEM::NOTICE,
'longMsg' => "The " . $params[$paramKey]->getLabel() . " path could not be set programmatically and has been disabled<br>\n",
'longMsgMode' => \DUPX_NOTICE_ITEM::MSG_MODE_HTML,
], \DUPX_NOTICE_MANAGER::ADD_UNIQUE_APPEND, self::NOTICE_ID_WP_CONF_PARAM_PATHS_EMPTY);
}
$params[$paramKey]->setValue($wpConfigVal);
}
/**
* Tries to replace the old domain with the new domain for the given wp config define.
* If that's not possible returns a notice to the user.
*
* @param ParamItem[] $params params list
* @param string $paramKey param key
* @param string $wpConfigKey wp config key
*
* @return void
*/
protected static function setDefaultWpConfigDomainValue(&$params, $paramKey, $wpConfigKey): void
{
if (!self::wpConfigNeedsUpdate($params, $paramKey, $wpConfigKey)) {
return;
}
$wpConfigVal = \DUPX_ArchiveConfig::getInstance()->getDefineArrayValue($wpConfigKey);
$parsedUrlNew = parse_url(PrmMng::getInstance()->getValue(PrmMng::PARAM_URL_NEW));
$parsedUrlOld = parse_url(PrmMng::getInstance()->getValue(PrmMng::PARAM_URL_OLD));
if ($wpConfigVal['value'] == $parsedUrlOld['host']) {
$wpConfigVal['value'] = $parsedUrlNew['host'];
} else {
$wpConfigVal['inWpConfig'] = false;
$wpConfigVal['value'] = '';
\DUPX_NOTICE_MANAGER::getInstance()->addNextStepNotice([
'shortMsg' => 'WP CONFIG domains disabled.',
'level' => \DUPX_NOTICE_ITEM::NOTICE,
'longMsg' => "The " . $params[$paramKey]->getLabel() . " domain could not be set programmatically and has been disabled<br>\n",
'longMsgMode' => \DUPX_NOTICE_ITEM::MSG_MODE_HTML,
], \DUPX_NOTICE_MANAGER::ADD_UNIQUE_APPEND, self::NOTICE_ID_WP_CONF_PARAM_DOMAINS_MODIFIED);
}
$params[$paramKey]->setValue($wpConfigVal);
}
/**
* Return true if wp config key need update
*
* @param ParamItem[] $params params list
* @param string $paramKey param key
* @param string $wpConfigKey wp config key
*
* @return bool
*/
protected static function wpConfigNeedsUpdate(&$params, $paramKey, $wpConfigKey): bool
{
if (
InstState::isRestoreBackup($params[PrmMng::PARAM_INST_TYPE]->getValue())
) {
return false;
}
// SKIP IF PARAM IS OVERWRITTEN
if ($params[$paramKey]->getStatus() === ParamItem::STATUS_OVERWRITE) {
return false;
}
// SKIP IF EMPTY
$wpConfigVal = \DUPX_ArchiveConfig::getInstance()->getDefineArrayValue($wpConfigKey);
if (strlen($wpConfigVal['value']) === 0) {
return false;
}
// EMPTY IF DISABLED
if ($wpConfigVal['inWpConfig'] == false) {
$wpConfigVal['value'] = '';
$params[$paramKey]->setValue($wpConfigVal);
return false;
}
return true;
}
/**
* Set wp config paths notices
*
* @return void
*/
protected static function wpConfigPathsNotices(): void
{
$noticeManager = \DUPX_NOTICE_MANAGER::getInstance();
// PREPEND IF EXISTS
$noticeManager->addNextStepNotice([
'shortMsg' => '',
'level' => \DUPX_NOTICE_ITEM::NOTICE,
'longMsg' => "It was found that the following config paths were outside of the source site's home path (" .
\DUPX_ArchiveConfig::getInstance()->getRealValue("originalPaths")->home . "):<br><br>\n",
'longMsgMode' => \DUPX_NOTICE_ITEM::MSG_MODE_HTML,
], \DUPX_NOTICE_MANAGER::ADD_UNIQUE_PREPEND_IF_EXISTS, self::NOTICE_ID_WP_CONF_PARAM_PATHS_EMPTY);
// APPEND IF EXISTS
$msg = '<br>Keeping config paths that are outside of the home path may cause malfunctions, so these settings have been disabled by default,';
$msg .= ' but you can set them manually if necessary by switching the install mode ';
$msg .= 'to "Advanced" and at Step 3 navigating to "Options" &gt; "WP-Config File"';
$noticeManager->addNextStepNotice([
'shortMsg' => '',
'level' => \DUPX_NOTICE_ITEM::NOTICE,
'longMsg' => $msg,
'longMsgMode' => \DUPX_NOTICE_ITEM::MSG_MODE_HTML,
], \DUPX_NOTICE_MANAGER::ADD_UNIQUE_APPEND_IF_EXISTS, self::NOTICE_ID_WP_CONF_PARAM_PATHS_EMPTY);
$noticeManager->saveNotices();
}
/**
* Set wp config domain notices
*
* @return void
*/
protected static function wpConfigDomainNotices(): void
{
$noticeManager = \DUPX_NOTICE_MANAGER::getInstance();
// PREPEND IF EXISTS
$noticeManager->addNextStepNotice([
'shortMsg' => '',
'level' => \DUPX_NOTICE_ITEM::NOTICE,
'longMsg' => "The following config domains were disabled:<br><br>\n",
'longMsgMode' => \DUPX_NOTICE_ITEM::MSG_MODE_HTML,
], \DUPX_NOTICE_MANAGER::ADD_UNIQUE_PREPEND_IF_EXISTS, self::NOTICE_ID_WP_CONF_PARAM_DOMAINS_MODIFIED);
// APPEND IF EXISTS
$msg = '<br>The plugin was unable to automatically replace the domain, so the setting has been disabled by default.';
$msg .= ' Please review them by switching the install mode to "Advanced" and at Step 3 navigating to "Options" &gt; "WP-Config File"';
$noticeManager->addNextStepNotice([
'shortMsg' => '',
'level' => \DUPX_NOTICE_ITEM::NOTICE,
'longMsg' => $msg,
'longMsgMode' => \DUPX_NOTICE_ITEM::MSG_MODE_HTML,
], \DUPX_NOTICE_MANAGER::ADD_UNIQUE_APPEND_IF_EXISTS, self::NOTICE_ID_WP_CONF_PARAM_DOMAINS_MODIFIED);
$noticeManager->saveNotices();
}
/**
* Returns default config value for FORCE_SSL_ADMIN depending on current site's settings
*
* @return array<string, mixed>
*/
protected static function getDefaultForceSSLAdminConfig()
{
$forceAdminSSLConfig = \DUPX_ArchiveConfig::getInstance()->getDefineArrayValue('FORCE_SSL_ADMIN');
if (!SnapURL::isCurrentUrlSSL() && $forceAdminSSLConfig['inWpConfig'] === true) {
$noticeMng = \DUPX_NOTICE_MANAGER::getInstance();
$noticeMng->addFinalReportNotice(
[
'shortMsg' => "FORCE_SSL_ADMIN was enabled on none SSL",
'level' => \DUPX_NOTICE_ITEM::SOFT_WARNING,
'longMsg' => 'It was found that FORCE_SSL_ADMIN is enabled and you are installing on a site without SSL, ' .
'so that config has been disabled.',
'sections' => 'general',
],
\DUPX_NOTICE_MANAGER::ADD_UNIQUE,
self::NOTICE_ID_WP_CONF_FORCE_SSL_ADMIN
);
$noticeMng->saveNotices();
$forceAdminSSLConfig['value'] = false;
}
return $forceAdminSSLConfig;
}
}

View File

@@ -0,0 +1,184 @@
<?php
/**
* Main params descriptions
*
* @category Duplicator
* @package Installer
* @author Snapcreek <admin@snapcreek.com>
* @copyright 2011-2021 Snapcreek LLC
* @license https://www.gnu.org/licenses/gpl-3.0.html GPLv3
*/
namespace Duplicator\Installer\Core\Params\Descriptors;
use Duplicator\Installer\Core\Hooks\HooksMng;
use Duplicator\Installer\Core\Params\Items\ParamForm;
use Duplicator\Installer\Core\Params\Items\ParamItem;
use Duplicator\Installer\Utils\Log\Log;
use Duplicator\Libs\Snap\SnapUtil;
use Duplicator\Libs\Snap\SnapIO;
/**
* class where all parameters are initialized. Used by the param manager
*/
final class ParamsDescriptors
{
/**
* Params init
*
* @return void
*/
public static function init(): void
{
HooksMng::getInstance()->addAction('after_params_overwrite', [self::class, 'updateParamsAfterOverwrite']);
}
/**
* Init params
*
* @param array<ParamItem|ParamForm> $params params list
*
* @return void
*/
public static function initParams(&$params): void
{
ParamDescUrlsPaths::init($params);
ParamDescController::init($params);
ParamDescSecurity::init($params);
ParamDescGeneric::init($params);
ParamDescConfigs::init($params);
ParamDescEngines::init($params);
ParamDescValidation::init($params);
ParamDescDatabase::init($params);
ParamDescCPanel::init($params);
ParamDescReplace::init($params);
ParamDescMultisite::init($params);
ParamDescPlugins::init($params);
ParamDescUsers::init($params);
ParamDescNewAdmin::init($params);
ParamDescWpConfig::init($params);
}
/**
* Update params after overwrite logic
*
* @param ParamItem[]|ParamForm[] $params params list
*
* @return void
*/
public static function updateParamsAfterOverwrite($params): void
{
Log::info('UPDATE PARAMS AFTER OVERWRITE', Log::LV_DETAILED);
ParamDescUrlsPaths::updateParamsAfterOverwrite($params);
ParamDescController::updateParamsAfterOverwrite($params);
ParamDescSecurity::updateParamsAfterOverwrite($params);
ParamDescGeneric::updateParamsAfterOverwrite($params);
ParamDescConfigs::updateParamsAfterOverwrite($params);
ParamDescEngines::updateParamsAfterOverwrite($params);
ParamDescValidation::updateParamsAfterOverwrite($params);
ParamDescDatabase::updateParamsAfterOverwrite($params);
ParamDescCPanel::updateParamsAfterOverwrite($params);
ParamDescReplace::updateParamsAfterOverwrite($params);
ParamDescMultisite::updateParamsAfterOverwrite($params);
ParamDescPlugins::updateParamsAfterOverwrite($params);
ParamDescUsers::updateParamsAfterOverwrite($params);
ParamDescNewAdmin::updateParamsAfterOverwrite($params);
ParamDescWpConfig::updateParamsAfterOverwrite($params);
}
/**
* Validate function, return true if value isn't empty
*
* @param mixed $value input value
* @param ParamItem $paramObj current param object
*
* @return boolean
*/
public static function validateNotEmpty($value, ParamItem $paramObj): bool
{
$result = is_string($value) ? strlen($value) > 0 : !empty($value);
if ($result == false) {
$paramObj->setInvalidMessage('Can\'t be empty');
}
return true;
}
/**
* Sanitize path
*
* @param string $value input value
*
* @return string
*/
public static function sanitizePath($value): string
{
$result = SnapUtil::sanitizeNSCharsNewlineTrim($value);
return SnapIO::safePathUntrailingslashit($result);
}
/**
* The path can't be empty
*
* @param string $value input value
* @param ParamItem $paramObj current param object
*
* @return bool
*/
public static function validatePath($value, ParamItem $paramObj): bool
{
if (strlen($value) > 1) {
return true;
} else {
$paramObj->setInvalidMessage('Path can\'t empty');
return false;
}
}
/**
* Sanitize URL
*
* @param string $value input value
*
* @return string
*/
public static function sanitizeUrl($value): string
{
$result = SnapUtil::sanitizeNSCharsNewlineTrim($value);
if (empty($value)) {
return '';
}
// if scheme not set add http by default
if (!preg_match('/^[a-zA-Z]+\:\/\//', $result)) {
$result = 'http://' . ltrim($result, '/');
}
return rtrim($result, '/\\');
}
/**
* The URL can't be empty
*
* @param string $value input value
* @param ParamItem $paramObj current param object
*
* @return bool
*/
public static function validateUrlWithScheme($value, ParamItem $paramObj): bool
{
if (strlen($value) == 0) {
$paramObj->setInvalidMessage('URL can\'t be empty');
return false;
}
if (($parsed = parse_url($value)) === false) {
$paramObj->setInvalidMessage('URL isn\'t valid');
return false;
}
if (!isset($parsed['host']) || empty($parsed['host'])) {
$paramObj->setInvalidMessage('URL must be a valid host');
return false;
}
return true;
}
}

View File

@@ -0,0 +1,89 @@
<?php
/**
* @package Duplicator\Installer
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Core\Params\Items;
/**
* this class manages a password type input with the hide / show password button
*/
class ParamFormPass extends ParamForm
{
const FORM_TYPE_PWD_TOGGLE = 'pwdtoggle';
/**
* Render HTML
*
* @return void
*/
protected function htmlItem()
{
if ($this->formType == self::FORM_TYPE_PWD_TOGGLE) {
$this->pwdToggleHtml();
} else {
parent::htmlItem();
}
}
/**
* return the text of current object for info only status
*
* @return string
*/
protected function valueToInfo(): string
{
return '**********';
}
/**
* Render PWD toggle element
*
* @return void
*/
protected function pwdToggleHtml()
{
$attrs = [
'value' => $this->getInputValue(),
];
if ($this->isDisabled()) {
$attrs['disabled'] = 'disabled';
}
if ($this->isReadonly()) {
$attrs['readonly'] = 'readonly';
}
if (!is_null($this->formAttr['maxLength'])) {
$attrs['maxLength'] = $this->formAttr['maxLength'];
}
if (!is_null($this->formAttr['size'])) {
$attrs['size'] = $this->formAttr['size'];
}
$attrs = array_merge($attrs, $this->formAttr['attr']);
\DUPX_U_Html::inputPasswordToggle($this->getAttrName(), $this->formAttr['id'], $this->formAttr['classes'], $attrs, true);
}
/**
* Get default form attributes
*
* @param string $formType form type
*
* @return array<string, mixed>
*/
protected static function getDefaultAttrForFormType($formType): array
{
$attrs = parent::getDefaultAttrForFormType($formType);
if ($formType == self::FORM_TYPE_PWD_TOGGLE) {
$attrs['maxLength'] = null; // if null have no limit
$attrs['size'] = null;
}
return $attrs;
}
}

View File

@@ -0,0 +1,311 @@
<?php
/**
* @package Duplicator\Installer
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Core\Params\Items;
use Duplicator\Installer\Core\Params\PrmMng;
use Duplicator\Libs\Snap\SnapJson;
/**
* this class handles the entire block selection block.
*/
class ParamFormPlugins extends ParamForm
{
const FORM_TYPE_PLUGINS_SELECT = 'pluginssel';
/**
* Render HTML
*
* @return void
*/
protected function htmlItem()
{
if ($this->formType == self::FORM_TYPE_PLUGINS_SELECT) {
$this->pluginSelectHtml();
} else {
parent::htmlItem();
}
}
/**
* Render plugin selector HTML
*
* @return void
*/
protected function pluginSelectHtml()
{
$pluginsManager = \Duplicator\Installer\Core\Deploy\Plugins\PluginsManager::getInstance();
$plugns_list = $pluginsManager->getPlugins();
$attrs = [
'id' => $this->formAttr['id'],
'name' => $this->getAttrName() . '[]',
'multiple' => '',
];
$this->formAttr['classes'][] = 'no-display';
if (!empty($this->formAttr['classes'])) {
$attrs['class'] = implode(' ', array_unique($this->formAttr['classes']));
}
if ($this->isDisabled()) {
$attrs['disabled'] = 'disabled';
}
if ($this->isReadonly()) {
$attrs['readonly'] = 'readonly';
}
$attrs = array_merge($attrs, $this->formAttr['attr']);
?>
<select <?php echo \DUPX_U_Html::arrayAttrToHtml($attrs); ?> >
<?php
foreach ($plugns_list as $pluginSlug => $plugin) {
if ($plugin->isIgnore() || $plugin->isForceDisabled()) {
continue;
}
$optAttr = ['value' => $pluginSlug];
if (self::isValueInValue($pluginSlug, $this->getInputValue())) {
// can't be selected if is disabled
$optAttr['selected'] = 'selected';
}
?>
<option <?php echo \DUPX_U_Html::arrayAttrToHtml($optAttr); ?> >
<?php echo \DUPX_U::esc_html($plugin->name); ?>
</option>
<?php
}
?>
</select>
<?php
echo $this->getSubNote();
$this->pluginsSelector();
}
/**
* Render plugin selector
*
* @return void
*/
protected function pluginsSelector()
{
$pluginsManager = \Duplicator\Installer\Core\Deploy\Plugins\PluginsManager::getInstance();
$plugns_list = $pluginsManager->getPlugins();
$paramsManager = PrmMng::getInstance();
$subsiteId = $paramsManager->getValue(PrmMng::PARAM_SUBSITE_ID);
$safe_mode = $paramsManager->getValue(PrmMng::PARAM_SAFE_MODE);
?>
<div>
<?php if (!$this->isDisabled()) { ?>
<?php
if ($safe_mode > 0) {
echo
'<div class="s3-warn">'
. '<i class="fas fa-exclamation-triangle"></i> Safe Mode Enabled: <i>Only Duplicator Pro will be enabled during install.</i>'
. '</div>';
}
?>
<div class="s3-allnonelinks" style="<?php echo ($safe_mode > 0) ? 'display:none' : ''; ?>">
<button type="button" id="select-all-plugins" class="no-layout">[All]</button>
<button type="button" id="unselect-all-plugins" class="no-layout">[None]</button>
</div><br style="clear:both" />
<?php } ?>
</div>
<ul id="plugins-filters" >
<li class="all" data-filter-target="all" >
<a href="#" class="current">
All <span class="count">(<?php echo count($plugns_list); ?>)</span>
</a>
</li>
<?php
foreach ($pluginsManager->getStatusCounts($subsiteId) as $status => $count) {
if ($count) {
?>
<li class="<?php echo \DUPX_U::esc_attr($status); ?>" data-filter-target="orig-<?php echo \DUPX_U::esc_attr($status); ?>" >
<a href="#">
<?php echo \DUPX_U::esc_html(\Duplicator\Installer\Core\Deploy\Plugins\PluginItem::getStatusLabel($status)); ?>
<span class="count"> (<?php echo $count; ?>)</span>
</a>
</li>
<?php
}
}
?>
</ul>
<table id="plugins_list_table_selector" class="list_table_selector<?php echo ($safe_mode > 0) ? ' disabled' : ''; ?>" >
<thead>
<tr>
<th class="check_input" ></th>
<th class="name" >Name</th>
<th class="info" >Details</th>
<th class="orig_status" >Original<br>Status</th>
</tr>
</thead>
<tbody>
<?php
foreach ($plugns_list as $pluginObj) {
if ($pluginObj->isIgnore() || $pluginObj->isForceDisabled()) {
continue;
}
$this->pluginHtmlItem($pluginObj, $subsiteId);
}
?>
</tbody>
</table>
<?php
$this->pluginTableSelectorJs();
}
/**
* Render plugin item
*
* @param \Duplicator\Installer\Core\Deploy\Plugins\PluginItem $pluginObj plugin object
* @param int $subsiteId selected subsite id
*
* @return void
*/
protected function pluginHtmlItem($pluginObj, $subsiteId)
{
$itemClasses = ['table-item'];
$orgiStats = $pluginObj->getOrgiStatus($subsiteId);
$itemClasses[] = 'orig-' . $orgiStats;
$itemClasses[] = self::isValueInValue($pluginObj->getSlug(), $this->getInputValue()) ? 'active' : 'inactive';
//$authorURI = $pluginObj->authorURI;
if (empty($pluginObj->authorURI)) {
$author = \DUPX_U::esc_html($pluginObj->author);
} else {
$author = '<a href="' . \DUPX_U::esc_attr($pluginObj->authorURI) . '" target="_blank">' . \DUPX_U::esc_html($pluginObj->author) . '</a>';
}
?>
<tr class="<?php echo implode(' ', $itemClasses); ?>" data-plugin-slug="<?php echo \DUPX_U::esc_attr($pluginObj->getSlug()); ?>">
<td class="check_input" >
<input type="checkbox" <?php echo $this->isReadonly() ? 'readonly' : ''; ?> <?php echo $this->isDisabled() ? 'disabled' : ''; ?>>
</td>
<td class="name" ><?php echo \DUPX_U::esc_html($pluginObj->name); ?></td>
<td class="info" >
Version: <?php echo \DUPX_U::esc_html($pluginObj->version); ?><br>
URL: <a href="<?php echo \DUPX_U::esc_attr($pluginObj->pluginURI); ?>" target="_blank" class="plugin-link" >
<?php echo \DUPX_U::esc_html($pluginObj->pluginURI); ?>
</a><br/>
Author: <?php echo $author; ?><br>
</td>
<td class="orig_status" ><?php echo \DUPX_U::esc_html($pluginObj->getStatusLabel($orgiStats)); ?></td>
</tr>
<?php
}
/**
* Render javascript
*
* @return void
*/
protected function pluginTableSelectorJs()
{
?>
<script>
(function ($) {
var pluginsWrapper = $('#' + <?php echo SnapJson::jsonEncode($this->formAttr['wrapperId']); ?>);
var pluginsSelect = $('#' + <?php echo SnapJson::jsonEncode($this->formAttr['id']); ?>);
var tableSelect = $('#plugins_list_table_selector');
var pluginsSelectIsDisabled = pluginsWrapper.hasClass('param-wrapper-disabled');
function setItemTable(item, enable) {
if (enable) {
item.removeClass('inactive').addClass('active');
item.find('.check_input input').prop('checked', true);
pluginsSelect.find('option[value="' + item.data('plugin-slug') + '"]').prop('selected', true);
} else {
item.removeClass('active').addClass('inactive');
item.find('.check_input input').prop('checked', false);
pluginsSelect.find('option[value="' + item.data('plugin-slug') + '"]').prop('selected', false);
}
}
// prevent select on unselect on external link click
tableSelect.find('.table-item a').click(function (event) {
event.stopPropagation();
return true;
});
tableSelect.find('.table-item').each(function () {
var current = $(this);
// init select element
if (current.hasClass('active')) {
setItemTable(current, true);
} else {
setItemTable(current, false);
}
// change on click
current.click(function () {
if (pluginsSelectIsDisabled) {
return;
}
if (current.hasClass('active')) {
setItemTable(current, false);
} else {
setItemTable(current, true);
}
});
});
$('#select-all-plugins').click(function () {
tableSelect.find('.table-item').each(function () {
setItemTable($(this), true);
});
});
$('#unselect-all-plugins').click(function () {
tableSelect.find('.table-item').each(function () {
setItemTable($(this), false);
});
});
$('#plugins-filters a').click(function () {
var obj = $(this);
if (obj.hasClass('current')) {
return false;
}
$('#plugins-filters a').removeClass('current');
obj.addClass('current');
var filterTarget = obj.parent().data('filter-target');
if (filterTarget === 'all') {
tableSelect.find('.table-item').removeClass('no-display');
} else {
tableSelect.find('.table-item').removeClass('no-display').not('.' + filterTarget).addClass('no-display');
}
return false;
});
})(jQuery);
</script>
<?php
}
/**
* Return default form attribute
*
* @param string $formType form type
*
* @return array<string, mixed>
*/
protected static function getDefaultAttrForFormType($formType): array
{
$attrs = parent::getDefaultAttrForFormType($formType);
if ($formType == self::FORM_TYPE_PLUGINS_SELECT) {
$attrs['wrapperContainerTag'] = 'div';
$attrs['inputContainerTag'] = 'div';
}
return $attrs;
}
}

View File

@@ -0,0 +1,771 @@
<?php
/**
* @package Duplicator\Installer
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Core\Params\Items;
use Duplicator\Installer\Core\Params\Models\SiteOwrMap;
use Duplicator\Installer\Core\Params\PrmMng;
use Duplicator\Installer\Utils\Log\Log;
use Duplicator\Libs\Snap\SnapIO;
use Duplicator\Libs\Snap\SnapURL;
use Duplicator\Libs\Snap\SnapUtil;
use DUPX_ArchiveConfig;
use Duplicator\Installer\Core\InstState;
use DUPX_U;
use DUPX_U_Html;
use Exception;
/**
* Item for overwrite mapping
*/
class ParamFormSitesOwrMap extends ParamForm
{
const SOFT_LIMIT_NUM = 10;
const HARD_LIMIT_NUM = 20;
const TYPE_ARRAY_SITES_OWR_MAP = 'arrayowrmap';
const FORM_TYPE_SITES_OWR_MAP = 'sitesowrmap';
const NAME_POSTFIX_SOURCE_ID = '_source_id';
const NAME_POSTFIX_TARGET_ID = '_target_id';
const NAME_POSTFIX_NEW_SLUG = '_new_slug';
const STRING_ADD_NEW_SUBSITE = "Add as New Subsite in Network";
/** @var ?mixed[] */
protected $extraData;
/** @var int<0,max> minimum item in list */
protected $minListItems = 1;
/**
* Class constructor
*
* @param string $name param identifier
* @param string $type Enum: TYPE_STRING | TYPE_ARRAY_STRING | ...
* @param string $formType FORM_TYPE_HIDDEN | FORM_TYPE_TEXT | ...
* @param array<string, mixed> $attr list of attributes
* @param array<string, mixed> $formAttr list of form attributes
*/
public function __construct($name, $type, $formType, array $attr = [], array $formAttr = [])
{
if ($type != static::TYPE_ARRAY_SITES_OWR_MAP) {
throw new Exception('the type must be ' . static::TYPE_ARRAY_SITES_OWR_MAP);
}
if ($formType != static::FORM_TYPE_SITES_OWR_MAP) {
throw new Exception('the form type must be ' . static::FORM_TYPE_SITES_OWR_MAP);
}
parent::__construct($name, $type, $formType, $attr, $formAttr);
}
/**
* Render HTML
*
* @return void
*/
protected function htmlItem()
{
if ($this->formType == static::FORM_TYPE_SITES_OWR_MAP) {
$this->sitesOrwHtml();
} else {
parent::htmlItem();
}
}
/**
* Return soft limit message
*
* @return string
*/
protected static function getEmptyListMessage(): string
{
return 'Add site to import';
}
/**
* Return full list limit message
*
* @return string
*/
protected static function getFullListMessage(): string
{
return 'All available sites have been added to the list.';
}
/**
* Return soft limit message
*
* @return string
*/
protected static function getSoftLimitMessage(): string
{
return 'It is possible to import a larger number of sites simultaneously,' .
'but multiple installations are recommended to prevent stability errors.';
}
/**
* Return hard limit message
*
* @return string
*/
protected static function getHardLimitMesssage(): string
{
return 'Maximum number of sites that can be imported in a single installation reached. (' .
static::HARD_LIMIT_NUM .
') If you wish to import several sites, carry out separate installations.';
}
/**
* Render subsite owr mapping
*
* @return void
*/
protected function sitesOrwHtml()
{
$extraData = $this->getListExtraData();
$numSites = $extraData['sourceInfo']['numSites'];
$haveMultipleItems = $numSites > 1;
$numListItem = 0;
$hardLimit = false;
$softLimit = false;
if (count($this->value) >= static::HARD_LIMIT_NUM) {
$hardLimit = true;
} elseif (count($this->value) >= static::SOFT_LIMIT_NUM) {
$softLimit = true;
}
$addDisabled = (count($this->value) >= $extraData['sourceInfo']['numSites'] || $hardLimit);
?>
<div
class="overwrite_sites_list <?php echo ($haveMultipleItems ? '' : 'no-multiple'); ?>"
data-list-info="<?php echo DUPX_U::esc_attr(json_encode($this->getListExtraData())); ?>">
<div class="overwrite_site_item title">
<div class="col">
<?php echo $this->getItemsLabels('sourceSite'); ?>
</div>
<div class="col">
<?php echo $this->getItemsLabels('targetSite'); ?>
</div>
<div class="col del">
<span class="del_item hidden">
<i class="fa fa-minus-square"></i>
</span>
</div>
</div>
<?php
if (empty($this->value)) {
for ($i = 0; $i < $this->minListItems; $i++) {
if (($defaultId = static::getDefaultSourceId($i)) == false) {
break;
}
$defaultItem = new SiteOwrMap($defaultId, SiteOwrMap::NEW_SUBSITE_WITH_SLUG, '');
$this->itemOwrHtml($defaultItem, 0, false);
$numListItem++;
}
} else {
$canDelete = (count($this->value) > $this->minListItems);
foreach ($this->value as $index => $siteMap) {
$this->itemOwrHtml($siteMap, $index, $canDelete);
$numListItem++;
}
}
?>
<div class="overwrite_site_item add_item">
<div class="full">
<button
type="button"
class="secondary-btn float-right add_button"
data-new-item="<?php echo DUPX_U::esc_attr($this->itemOwrHtml(null, 0, false, false)); ?>"
<?php echo ($addDisabled ? 'disabled' : ''); ?>>
<?php echo $this->getItemsLabels('addItem'); ?>
</button>
<?php if (strlen(static::getEmptyListMessage())) { ?>
<div class="overwrite_msg overwrite_site_empty_list_msg <?php echo ($numListItem == 0 ? '' : 'no-display'); ?>">
<i class="fas fa-info-circle"></i> <?php echo static::getEmptyListMessage(); ?>
</div>
<?php } ?>
<?php if (strlen(static::getFullListMessage())) { ?>
<div
class="overwrite_msg overwrite_site_full_list_msg <?php echo ($numListItem > $numSites ? '' : 'no-display'); ?>">
<i class="fas fa-info-circle"></i> <?php echo static::getFullListMessage(); ?>
</div>
<?php } ?>
<?php if (strlen(static::getSoftLimitMessage())) { ?>
<div class="overwrite_msg overwrite_site_soft_limit_msg maroon <?php echo ($softLimit ? '' : 'no-display'); ?>">
<i class="fas fa-exclamation-triangle"></i> <?php echo static::getSoftLimitMessage(); ?>
</div>
<?php } ?>
<?php if (strlen(static::getHardLimitMesssage())) { ?>
<div class="overwrite_msg overwrite_site_hard_limit_msg maroon <?php echo ($hardLimit ? '' : 'no-display'); ?>">
<i class="fas fa-exclamation-triangle"></i> <?php echo static::getHardLimitMesssage(); ?>
</div>
<?php } ?>
</div>
</div>
</div>
<?php
}
/**
* Get add itm button label
*
* @param string $key label key
*
* @return string
*/
protected function getItemsLabels($key): string
{
$paramLabels = [
'addItem' => 'Add Site to Import',
'sourceSite' => 'Source Site',
'targetSite' => 'Target Site',
];
return ($paramLabels[$key] ?? 'unknown label key');
}
/**
* Render item html
*
* @param SiteOwrMap|null $map map item
* @param int $index current item inder
* @param bool $canDelete if false disable delete button
* @param bool $echo if false return HTML
*
* @return string
*/
protected function itemOwrHtml(?SiteOwrMap $map = null, $index = 0, $canDelete = false, $echo = true)
{
ob_start();
$selectSourceAttrs = [
'name' => $this->getName() . static::NAME_POSTFIX_SOURCE_ID . '[]',
'class' => 'source_id js-select ' . $this->getFormItemId() . static::NAME_POSTFIX_SOURCE_ID,
];
$selectTargetAttrs = [
'name' => $this->getName() . static::NAME_POSTFIX_TARGET_ID . '[]',
'class' => 'target_id js-select ' . $this->getFormItemId() . static::NAME_POSTFIX_TARGET_ID,
];
$newSlugAttrs = [
'name' => $this->getName() . static::NAME_POSTFIX_NEW_SLUG . '[]',
'class' => 'new_slug ' . $this->getFormItemId() . static::NAME_POSTFIX_NEW_SLUG,
];
$extraData = $this->getListExtraData();
if (is_null($map)) {
$selectSourceAttrs['disabled'] = true;
$selectedSource = false;
$noteSourceSlug = '';
$selectTargetAttrs['disabled'] = true;
$selectedTarget = SiteOwrMap::NEW_SUBSITE_WITH_SLUG;
$noteTargetSlug = '_____';
$newSlugAttrs['disabled'] = true;
$newSlugAttrs['value'] = '';
} else {
$selectedSource = $map->getSourceId();
$selectedSourceInfo = $extraData['sourceInfo']['sites']['id_' . $selectedSource];
$noteSourceSlug = $selectedSourceInfo['domain'] . $selectedSourceInfo['path'];
$selectedTarget = $map->getTargetId();
$selectedTargetInfo = $extraData['targetInfo']['sites']['id_' . $selectedTarget];
switch ($selectedTarget) {
case SiteOwrMap::NEW_SUBSITE_WITH_SLUG:
$noteTargetSlug = (
strlen($selectedTargetInfo['slug']) == 0 ?
'_____' :
$extraData['sourceInfo']['urlPrefix'] . $selectedTargetInfo['slug'] . $extraData['sourceInfo']['urlPostfix']
);
break;
case SiteOwrMap::NEW_SUBSITE_WITH_FULL_DOMAIN:
$noteTargetSlug = (strlen($selectedTargetInfo['slug']) == 0 ? '_____/_____' : $selectedTargetInfo['slug']);
break;
default:
$noteTargetSlug = $selectedTargetInfo['domain'] . $selectedTargetInfo['path'];
break;
}
$newSlugAttrs['value'] = $map->getNewSlug();
}
$sourceIdsOptions = static::getSourceIdsOptions();
?>
<div class="overwrite_site_item">
<div class="col">
<select <?php echo DUPX_U_Html::arrayAttrToHtml($selectSourceAttrs); ?>>
<?php static::renderSelectOptions($sourceIdsOptions, $selectedSource); ?>
</select>
<div class="sub-note source-site-note">
<span class="site-prefix-slug"><?php echo DUpx_u::esc_html($extraData['sourceInfo']['urlScheme']); ?></span>
<span class="site-slug"><?php echo DUpx_u::esc_html($noteSourceSlug); ?></span><span class="site-postfix-slug"></span>
</div>
</div>
<div class="col">
<div class="target_select_wrapper">
<select <?php echo DUPX_U_Html::arrayAttrToHtml($selectTargetAttrs); ?>>
<?php static::renderSelectOptions(static::getTargetIdsOptions(), $selectedTarget); ?>
</select>
<div class="new-slug-wrapper">
<input
type="text" <?php echo DUPX_U_Html::arrayAttrToHtml($newSlugAttrs); ?>
placeholder="Insert the new site slug">
</div>
</div>
<div class="sub-note target-site-note">
<span class="site-prefix-slug"><?php echo DUpx_u::esc_html($extraData['targetInfo']['urlScheme']); ?></span>
<span class="site-slug"><?php echo DUpx_u::esc_html($noteTargetSlug); ?></span><span class="site-postfix-slug"></span>
</div>
</div>
<div class="col del">
<span class="del_item <?php echo $canDelete ? '' : 'disabled'; ?>" title="Remove this site">
<i class="fa fa-minus-square"></i>
</span>
</div>
</div>
<?php
if ($echo) {
ob_end_flush();
return '';
} else {
return ob_get_clean();
}
}
/**
* Get default type attributes
*
* @param string $type param value type
*
* @return array<string, mixed>
*/
protected static function getDefaultAttrForType($type): array
{
$attrs = parent::getDefaultAttrForType($type);
if ($type == static::TYPE_ARRAY_SITES_OWR_MAP) {
$attrs['default'] = [];
}
return $attrs;
}
/**
* Apply filter to value input
*
* @param mixed[] $superObject query string super object
*
* @return mixed
*/
public function getValueFilter($superObject)
{
if (($items = json_decode($superObject[$this->getName()], true)) == false) {
throw new Exception('Invalid json string');
}
return $items;
}
/**
* Return sanitized value
*
* @param mixed $value value input
*
* @return SiteOwrMap[]
*/
public function getSanitizeValue($value)
{
if (!is_array($value)) {
return [];
}
for ($i = 0; $i < count($value); $i++) {
$sourceId = (isset($value[$i]['sourceId']) ? (int) $value[$i]['sourceId'] : SiteOwrMap::NEW_SUBSITE_NOT_VALID);
$targetId = (isset($value[$i]['targetId']) ? (int) $value[$i]['targetId'] : SiteOwrMap::NEW_SUBSITE_NOT_VALID);
$newSlug = (isset($value[$i]['newSlug']) ? SnapUtil::sanitizeNSCharsNewlineTrim($value[$i]['newSlug']) : '');
switch ($targetId) {
case SiteOwrMap::NEW_SUBSITE_WITH_SLUG:
$newSlug = preg_replace('/[\s"\'\\\\\/&?#,\.:;]+/m', '', $newSlug);
break;
case SiteOwrMap::NEW_SUBSITE_WITH_FULL_DOMAIN:
$newSlug = preg_replace('/[\s"\'\\\\&?#,:;]+/m', '', $newSlug);
if (strlen($newSlug) > 0) {
$newSlug = SnapIO::trailingslashit($newSlug);
}
break;
default:
$newSlug = '';
break;
}
$value[$i] = [
'sourceId' => $sourceId,
'targetId' => $targetId,
'newSlug' => $newSlug,
];
}
return $value;
}
/**
* Check if value is valid
*
* @param mixed $value value
* @param mixed $validateValue variable passed by reference. Updated to validated value in the case, the value is a valid value.
*
* @return bool true if is a valid value for this object
*/
public function isValid($value, &$validateValue = null)
{
$validateValue = [];
try {
foreach ($value as $item) {
if ($item instanceof SiteOwrMap) {
$validateValue[] = $item;
continue;
}
$validateValue[] = new SiteOwrMap(
$item['sourceId'],
$item['targetId'],
$item['newSlug']
);
}
if (($result = $this->callValidateCallback($validateValue)) === false) {
$validateValue = null;
}
} catch (Exception $e) {
Log::info('Validation error message: ' . $e->getMessage());
return false;
}
return $result;
}
/**
* Set value from array. This function is used to set data from json array
*
* @param array<string, mixed> $data form data
*
* @return boolean
*/
public function fromArrayData($data)
{
return parent::fromArrayData($data);
}
/**
* return array dato to store in json array data
*
* @return array{value: mixed, status: string}
*/
public function toArrayData(): array
{
$result = parent::toArrayData();
$result['value'] = [];
foreach ($this->value as $obj) {
$result['value'][] = $obj->jsonSerialize();
}
return $result;
}
/**
* Get subsite slug by subsitedata
*
* @param object|array<string, mixed> $subsite subsite info
* @param string $mainUrl main site url
* @param bool $isSubdomain if true is subdomain
*
* @return string
*/
public static function getSubsiteSlug($subsite, $mainUrl, $isSubdomain): string
{
$subsite = (object) $subsite;
if ($isSubdomain) {
$mainDomain = SnapURL::wwwRemove(SnapURL::parseUrl($mainUrl, PHP_URL_HOST));
$subDomain = SnapURL::wwwRemove($subsite->domain);
if ($subDomain == $mainDomain) {
return '/';
} elseif (strpos($subDomain, '.' . $mainDomain) !== false) {
return (string)substr($subDomain, 0, strpos($subDomain, '.' . $mainDomain));
} else {
return $subDomain;
}
} else {
$maiPath = SnapIO::trailingslashit((string) SnapURL::parseUrl($mainUrl, PHP_URL_PATH));
$subsitePath = SnapIO::trailingslashit($subsite->path);
if ($maiPath == $subsitePath) {
return '/';
} else {
return trim(SnapIO::getRelativePath($subsitePath, $maiPath));
}
}
}
/**
* Get subsites list in packages
*
* @return ParamOption[]
*/
public static function getSourceIdsOptions()
{
static $sourceOpt = null;
if (is_null($sourceOpt)) {
$archiveConfig = DUPX_ArchiveConfig::getInstance();
$sourceOpt = [];
foreach ($archiveConfig->subsites as $subsite) {
$option = new ParamOption(
$subsite->id,
$subsite->path,
ParamFormSitesOwrMap::getSourceIdOptionStatus($subsite)
);
$option->setOptGroup($subsite->domain);
$sourceOpt[] = $option;
}
}
return $sourceOpt;
}
/**
* @param object $subsite Sub Site Object
*
* @return string String that indicated if the option should be enabled or disabled
*/
public static function getSourceIdOptionStatus($subsite): string
{
return (self::isQualifiedSourceIdForImport($subsite))
? ParamOption::OPT_ENABLED
: ParamOption::OPT_DISABLED;
}
/**
* @param object $subsite Sub Site Object
*
* @return bool true or false if the site object if the source can be imported
*/
public static function isQualifiedSourceIdForImport($subsite): bool
{
return (!InstState::isImportFromBackendMode() || count($subsite->filteredTables) === 0);
}
/**
* Get default source id
*
* @param int $index default index
*
* @return bool|int
*/
protected static function getDefaultSourceId($index = 0)
{
if (!isset(DUPX_ArchiveConfig::getInstance()->subsites[$index])) {
return false;
}
return DUPX_ArchiveConfig::getInstance()->subsites[$index]->id;
}
/**
*
* @return int[]
*/
protected static function getSubSiteIdsAcceptValues(): array
{
$archiveConfig = DUPX_ArchiveConfig::getInstance();
$acceptValues = [-1];
foreach ($archiveConfig->subsites as $subsite) {
if (ParamFormSitesOwrMap::isQualifiedSourceIdForImport($subsite)) {
$acceptValues[] = $subsite->id;
}
}
return $acceptValues;
}
/**
* Get existing subsites list on import site
*
* @return ParamOption[]
*/
public static function getTargetIdsOptions()
{
static $targetOpt = null;
if (is_null($targetOpt)) {
$overwriteData = PrmMng::getInstance()->getValue(PrmMng::PARAM_OVERWRITE_SITE_DATA);
$targetOpt = [];
if (!is_array($overwriteData) || !isset($overwriteData['subsites'])) {
return $targetOpt;
}
$targetOpt[] = new ParamOption(
SiteOwrMap::NEW_SUBSITE_WITH_SLUG,
'New ' . ($overwriteData['subdomain'] ? 'Domain' : 'Path'),
ParamOption::OPT_ENABLED
);
$targetOpt[] = new ParamOption(
SiteOwrMap::NEW_SUBSITE_WITH_FULL_DOMAIN,
'New URL',
ParamOption::OPT_ENABLED
);
foreach ($overwriteData['subsites'] as $subsite) {
$subsite = (object) $subsite;
$option = new ParamOption(
$subsite->id,
$subsite->path,
ParamOption::OPT_ENABLED
);
$option->setOptGroup($subsite->domain);
$targetOpt[] = $option;
}
}
return $targetOpt;
}
/**
* Get extra fata for data attribute list
*
* @return array<string, mixed>
*/
protected function getListExtraData()
{
if (is_null($this->extraData)) {
$archiveConfig = DUPX_ArchiveConfig::getInstance();
$this->extraData = [
'minListItems' => $this->minListItems,
'softLimit' => static::SOFT_LIMIT_NUM,
'hardLimit' => static::HARD_LIMIT_NUM,
];
$isSubdomain = ($archiveConfig->mu_mode == 1);
$mainSiteUrl = $archiveConfig->getRealValue('siteUrl');
$this->extraData['sourceInfo'] = [
'numSites' => count(static::getSourceIdsOptions()),
'urlScheme' => static::getUrlScheme($mainSiteUrl),
'urlPrefix' => static::prefixSlugByURL($mainSiteUrl, $isSubdomain),
'urlPostfix' => static::postfixSlugByURL($mainSiteUrl, $isSubdomain),
'sites' => [],
];
foreach ($archiveConfig->subsites as $subsite) {
$this->extraData['sourceInfo']['sites']['id_' . $subsite->id] = [
'domain' => $subsite->domain,
'path' => $subsite->path,
'slug' => static::getSubsiteSlug($subsite, $mainSiteUrl, $isSubdomain),
/** @todo remove */
];
}
$targetData = $this->getTargetData();
$isSubdomain = $targetData['isSubdomain'];
$mainSiteUrl = $targetData['mainSiteUrl'];
$this->extraData['targetInfo'] = [
'numSites' => count(static::getTargetIdsOptions()),
'urlScheme' => static::getUrlScheme($mainSiteUrl),
'urlPrefix' => static::prefixSlugByURL($mainSiteUrl, $isSubdomain),
'urlPostfix' => static::postfixSlugByURL($mainSiteUrl, $isSubdomain),
'sites' => [
'id_' . SiteOwrMap::NEW_SUBSITE_WITH_FULL_DOMAIN => [
'id' => SiteOwrMap::NEW_SUBSITE_WITH_FULL_DOMAIN,
'slug' => '_____/_____',
'domain' => '',
'path' => '',
],
'id_' . SiteOwrMap::NEW_SUBSITE_WITH_SLUG => [
'id' => SiteOwrMap::NEW_SUBSITE_WITH_SLUG,
'slug' => '_____',
'domain' => '',
/** @todo set set according to the type of multisite */
'path' => '',
],
],
];
foreach ($targetData['subsites'] as $subsite) {
$subsite = (object) $subsite;
$this->extraData['targetInfo']['sites']['id_' . $subsite->id] = [
'slug' => static::getSubsiteSlug($subsite, $mainSiteUrl, $isSubdomain),
'domain' => $subsite->domain,
'path' => $subsite->path,
];
}
}
return $this->extraData;
}
/**
* Return target data
*
* @return mixed[]
*/
protected function getTargetData(): array
{
$overwriteData = PrmMng::getInstance()->getValue(PrmMng::PARAM_OVERWRITE_SITE_DATA);
$result = [];
$result['isSubdomain'] = $overwriteData['subdomain'];
$result['mainSiteUrl'] = $overwriteData['urls']['home'];
$result['subsites'] = $overwriteData['subsites'];
return $result;
}
/**
* Return URL scheme
*
* @param string $url URL input
*
* @return string
*/
protected static function getUrlScheme($url): string
{
return SnapURL::parseUrl($url, PHP_URL_SCHEME) . '://';
}
/**
* Get prefix URL slug
*
* @param string $url URL string
* @param bool $isSubdomain if true is subdomain
*
* @return string
*/
protected static function prefixSlugByURL($url, $isSubdomain = false): string
{
if ($isSubdomain) {
return '';
} else {
$parseUrl = SnapURL::parseUrl($url);
return $parseUrl['host'] . SnapIO::trailingslashit($parseUrl['path']);
}
}
/**
* Get postifx URL slug
*
* @param string $url URL string
* @param bool $isSubdomain if true is subdomain
*
* @return string
*/
protected static function postfixSlugByURL($url, $isSubdomain = false): string
{
if (!$isSubdomain) {
return '/';
}
$parseUrl = SnapURL::parseUrl($url);
return '.' . SnapURL::wwwRemove($parseUrl['host']) . SnapIO::trailingslashit($parseUrl['path']);
}
}

View File

@@ -0,0 +1,349 @@
<?php
/**
* @package Duplicator\Installer
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Core\Params\Items;
use Duplicator\Installer\Utils\Log\Log;
use Duplicator\Libs\Snap\SnapUtil;
/**
* this class handles the entire block selection block.
*/
class ParamFormTables extends ParamForm
{
const TYPE_ARRAY_TABLES = 'arraytbl';
const FORM_TYPE_TABLES_SELECT = 'tablessel';
const TABLE_ITEM_POSTFIX = '_item';
const TABLE_NAME_POSTFIX_TNAME = '_tname';
const TABLE_NAME_POSTFIX_EXTRACT = '_extract';
const TABLE_NAME_POSTFIX_REPLACE = '_replace';
/**
* Class constructor
*
* @param string $name param identifier
* @param string $type Enum: TYPE_STRING | TYPE_ARRAY_STRING | ...
* @param string $formType FORM_TYPE_HIDDEN | FORM_TYPE_TEXT | ...
* @param array<string, mixed> $attr list of attributes
* @param array<string, mixed> $formAttr list of form attributes
*/
public function __construct($name, $type, $formType, array $attr = [], array $formAttr = [])
{
if ($type != self::TYPE_ARRAY_TABLES) {
throw new \Exception('the type must be ' . self::TYPE_ARRAY_TABLES);
}
if ($formType != self::FORM_TYPE_TABLES_SELECT) {
throw new \Exception('the form type must be ' . self::FORM_TYPE_TABLES_SELECT);
}
parent::__construct($name, $type, $formType, $attr, $formAttr);
}
/**
* Render HTML
*
* @return void
*/
protected function htmlItem()
{
if ($this->formType == self::FORM_TYPE_TABLES_SELECT) {
$this->tablesSelectHtml();
} else {
parent::htmlItem();
}
}
/**
* Render tables selector HTML
*
* @return void
*/
protected function tablesSelectHtml()
{
$tables = \DUPX_DB_Tables::getInstance();
$value = $this->getInputValue();
?>
<table id="plugins_list_table_selector" class="list_table_selector list-import-upt-tables">
<thead>
<tr>
<td class="name"></td>
<td class="info toggle-all">
Toggle All
</td>
<td class="action">
<span title="Check all Extract" class="checkbox-switch">
<input class="select-all-import" checked type="checkbox" >
<span class="slider"></span>
</span>
</td>
<td class="action">
<span title="Check all Replace" class="checkbox-switch">
<input class="select-all-replace" checked type="checkbox">
<span class="slider"></span>
</span>
</td>
</tr>
<tr>
<th class="name">Original&nbsp;Name</th>
<th class="info">New&nbsp;Name</th>
<th class="action">Import</th>
<th class="action">Update</th>
</tr>
</thead>
<tbody>
<?php
$index = 0;
foreach ($value as $name => $tableVals) {
$this->tableHtmlItem($tableVals, $tables->getTableObjByName($name), $index);
$index++;
}
?>
</tbody>
<tfoot>
<tr>
<th class="name">Original&nbsp;Name</th>
<th class="info">New&nbsp;Name</th>
<th class="action">Import</th>
<th class="action">Update</th>
</tr>
</tfoot>
</table>
<?php
}
/**
* Renter tables items selector
*
* @param array<string, array{name: string, extract: bool, replace: bool}> $vals form values
* @param \DUPX_DB_Table_item $tableOjb table object
* @param integer $index infex of current item
*
* @return void
*/
protected function tableHtmlItem($vals, \DUPX_DB_Table_item $tableOjb, $index)
{
$itemClasses = [
'table-item',
$this->getFormItemId() . self::TABLE_ITEM_POSTFIX,
];
$hiddenNameAttrs = [
'id' => $this->getFormItemId() . self::TABLE_NAME_POSTFIX_TNAME . '_' . $index,
'type' => 'hidden',
'name' => $this->getName() . '[]',
'class' => $this->getFormItemId() . self::TABLE_NAME_POSTFIX_TNAME,
'value' => $tableOjb->getOriginalName(),
];
$extractCheckboxAttrs = [
'id' => $this->getFormItemId() . self::TABLE_NAME_POSTFIX_EXTRACT . '_' . $index,
'name' => $this->getName() . self::TABLE_NAME_POSTFIX_EXTRACT . '[]',
'class' => $this->getFormItemId() . self::TABLE_NAME_POSTFIX_EXTRACT,
'value' => 1,
];
$replaceCheckboxAttrs = [
'id' => $this->getFormItemId() . self::TABLE_NAME_POSTFIX_REPLACE . '_' . $index,
'name' => $this->getName() . self::TABLE_NAME_POSTFIX_REPLACE . '[]',
'class' => $this->getFormItemId() . self::TABLE_NAME_POSTFIX_REPLACE,
'value' => 1,
];
if ($tableOjb->canBeExctracted()) {
if ($vals['extract']) {
$extractCheckboxAttrs['checked'] = '';
}
if ($vals['replace']) {
$replaceCheckboxAttrs['checked'] = '';
}
} else {
$itemClasses[] = 'no-display';
$extractCheckboxAttrs['disabled'] = '';
$replaceCheckboxAttrs['disabled'] = '';
}
if ($this->isDisabled() || $this->isReadonly()) {
$extractCheckboxAttrs['disabled'] = '';
$replaceCheckboxAttrs['disabled'] = '';
$skipSendValue = true;
} else {
$skipSendValue = false;
}
?>
<tr class="<?php echo implode(' ', $itemClasses); ?>" >
<td class="name" >
<span class="table-name" ><?php echo \DUPX_U::esc_html($tableOjb->getOriginalName()); ?></span><br>
Rows: <b><?php echo $tableOjb->getRows(); ?></b> Size: <b><?php echo $tableOjb->getSize(true); ?></b>
</td>
<td class="info" >
<span class="table-name" ><b><?php echo \DUPX_U::esc_html($tableOjb->getNewName()); ?></b></span><br>
&nbsp;
</td>
<td class="action extract" >
<?php
if (!$skipSendValue) {
// if is disabled or readonly don't senta tables nme so params isn't updated
?>
<input <?php echo \DUPX_U_Html::arrayAttrToHtml($hiddenNameAttrs); ?> >
<?php
}
\DUPX_U_Html::checkboxSwitch(
$extractCheckboxAttrs,
['title' => 'Extract in database']
);
?>
</td>
<td class="action replace" >
<?php
\DUPX_U_Html::checkboxSwitch(
$replaceCheckboxAttrs,
['title' => 'Apply replace engine at URLs and paths in database']
);
?>
</td>
</tr>
<?php
}
/**
* Check if value is valid
*
* @param mixed $value value
* @param mixed $validateValue variable passed by reference. Updated to validated value in the case, the value is a valid value.
*
* @return bool true if is a valid value for this object
*/
public function isValid($value, &$validateValue = null): bool
{
$validateValue = (array) $value;
$availableTables = \DUPX_DB_Tables::getInstance()->getTablesNames();
$validateTables = array_keys($validateValue);
// all tables in list have to exist in available tables
foreach ($validateValue as $table => $tableValues) {
if (!in_array($table, $availableTables)) {
Log::info('INVALID ' . $table . ' ISN\'T IN AVAILABLE LIST: ' . Log::v2str($availableTables));
return false;
}
}
// all tables abaliable have to exists in list
foreach ($availableTables as $avaibleTable) {
if (!in_array($avaibleTable, $validateTables)) {
Log::info('AVAILABLE ' . $avaibleTable . ' ISN\'T IN PARAM LIST TABLE');
return false;
}
}
return true;
}
/**
* Appli filter to value input
*
* @param array<string, mixed> $superObject query string values
*
* @return array<string, array{name: string, extract: bool, replace: bool}>
*/
public function getValueFilter($superObject): array
{
$result = [];
if (($tables = json_decode($superObject[$this->getName()])) == false) {
throw new \Exception('Invalid json string');
}
foreach ($tables as $table) {
$table = (array) $table;
if ($table['extract'] == false) {
// replace can't be true if extract if false
$table['replace'] = false;
}
$result[$table['name']] = $table;
}
return $result;
}
/**
* Return sanitized value
*
* @param mixed $value value input
*
* @return array<string, array{name: string, extract: bool,replace: bool}>
*/
public function getSanitizeValue($value): array
{
$newValues = (array) $value;
$sanitizeValues = [];
foreach ($newValues as $key => $newValue) {
$sanitizedKey = SnapUtil::sanitizeNSCharsNewlineTrim($key);
$newValue = (array) $newValue;
$sanitizedNewValue = self::getParamItemValueFromData();
$sanitizedNewValue['name'] = isset($newValue['name']) ? SnapUtil::sanitizeNSCharsNewlineTrim($newValue['name']) : '';
$sanitizedNewValue['extract'] = isset($newValue['extract']) && filter_var($newValue['extract'], FILTER_VALIDATE_BOOLEAN);
$sanitizedNewValue['replace'] = isset($newValue['replace']) && filter_var($newValue['replace'], FILTER_VALIDATE_BOOLEAN);
$sanitizeValues[$sanitizedKey] = $sanitizedNewValue;
}
return $sanitizeValues;
}
/**
* Get default type attributes
*
* @param string $type param value type
*
* @return array<string, mixed>
*/
protected static function getDefaultAttrForType($type): array
{
$attrs = parent::getDefaultAttrForType($type);
if ($type == self::TYPE_ARRAY_TABLES) {
$attrs['default'] = [];
}
return $attrs;
}
/**
* Get default form attributes
*
* @param string $formType form type
*
* @return array<string, mixed>
*/
protected static function getDefaultAttrForFormType($formType): array
{
$attrs = parent::getDefaultAttrForFormType($formType);
if ($formType == self::FORM_TYPE_TABLES_SELECT) {
$attrs['wrapperContainerTag'] = 'div';
$attrs['inputContainerTag'] = 'div';
}
return $attrs;
}
/**
* Return param item from data
*
* @param string $name table name
* @param bool $extract extract
* @param bool $replace replace
*
* @return array{name: string, extract: bool, replace: bool}
*/
public static function getParamItemValueFromData($name = '', $extract = false, $replace = false): array
{
return [
'name' => $name,
'extract' => $extract,
'replace' => $replace,
];
}
}

View File

@@ -0,0 +1,171 @@
<?php
/**
* @package Duplicator\Installer
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Core\Params\Items;
use Duplicator\Installer\Core\Params\Models\SiteOwrMap;
use Duplicator\Installer\Core\Params\PrmMng;
use DUPX_ArchiveConfig;
use Exception;
/**
* this class handles the entire block selection block.
*/
class ParamFormURLMapping extends ParamFormSitesOwrMap
{
const SOFT_LIMIT_NUM = PHP_INT_MAX;
const HARD_LIMIT_NUM = PHP_INT_MAX;
const FORM_TYPE_URL_MAPPING = 'url_mapping';
/** @var int<-1, max> */
protected $currentSubsiteId = -1;
/**
* Class constructor
*
* @param string $name param identifier
* @param string $type Enum: TYPE_STRING | TYPE_ARRAY_STRING | ...
* @param string $formType FORM_TYPE_HIDDEN | FORM_TYPE_TEXT | ...
* @param array<string, mixed> $attr list of attributes
* @param array<string, mixed> $formAttr list of form attributes
*/
public function __construct($name, $type, $formType, array $attr = [], array $formAttr = [])
{
if ($type != self::TYPE_ARRAY_SITES_OWR_MAP) {
throw new Exception('the type must be ' . self::TYPE_ARRAY_SITES_OWR_MAP);
}
if ($formType != self::FORM_TYPE_URL_MAPPING) {
throw new Exception('the form type must be ' . self::FORM_TYPE_URL_MAPPING);
}
ParamForm::__construct($name, $type, $formType, $attr, $formAttr);
$this->minListItems = 0;
}
/**
* Render HTML
*
* @return void
*/
protected function htmlItem()
{
if ($this->formType == self::FORM_TYPE_URL_MAPPING) {
$this->sitesOrwHtml();
} else {
parent::htmlItem();
}
}
/**
* Return soft limit message
*
* @return string
*/
protected static function getEmptyListMessage(): string
{
return 'It\'s possible customize the subfolder/subodmains or full domains of subsites, <br>' .
'to change the main site use the "new site URL" option in the advanced mode.';
}
/**
* Return soft limit message
*
* @return string
*/
protected static function getSoftLimitMessage(): string
{
return '';
}
/**
* Return hard limit message
*
* @return string
*/
protected static function getHardLimitMesssage(): string
{
return '';
}
/**
* Get add itm button label
*
* @param string $key label key
*
* @return string
*/
protected function getItemsLabels($key): string
{
$paramLabels = [
'addItem' => 'Add Custom URL',
'sourceSite' => 'Source Site',
'targetSite' => 'Custom URL',
];
return ($paramLabels[$key] ?? 'unknown label key');
}
/**
* Return target data
*
* @return mixed[]
*/
protected function getTargetData(): array
{
$result = [];
$result['isSubdomain'] = (DUPX_ArchiveConfig::getInstance()->mu_mode === 1);
$result['mainSiteUrl'] = PrmMng::getInstance()->getValue(PrmMng::PARAM_URL_NEW);
$result['subsites'] = [];
return $result;
}
/**
* Get subsites list in packages
*
* @return ParamOption[]
*/
public static function getSourceIdsOptions()
{
$mainSiteId = DUPX_ArchiveConfig::getInstance()->main_site_id;
$options = parent::getSourceIdsOptions();
foreach ($options as $index => $opt) {
if ($opt->value == $mainSiteId) {
unset($options[$index]);
break;
}
}
return array_values($options);
}
/**
* Get existing subsites list on import site
*
* @return ParamOption[]
*/
public static function getTargetIdsOptions()
{
static $targetOpt = null;
if (is_null($targetOpt)) {
$targetOpt[] = new ParamOption(
SiteOwrMap::NEW_SUBSITE_WITH_SLUG,
'New ' . (DUPX_ArchiveConfig::getInstance()->mu_mode == 1 ? 'Domain' : 'Path'),
ParamOption::OPT_ENABLED
);
$targetOpt[] = new ParamOption(
SiteOwrMap::NEW_SUBSITE_WITH_FULL_DOMAIN,
'New URL',
ParamOption::OPT_ENABLED
);
}
return $targetOpt;
}
}

View File

@@ -0,0 +1,98 @@
<?php
/**
* @package Duplicator\Installer
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Core\Params\Items;
/**
* this class manages a password type input with the hide / show passwrd button
*/
class ParamFormUsersReset extends ParamFormPass
{
const FORM_TYPE_USERS_PWD_RESET = 'usrpwdreset';
/** @var int<-1, max> */
protected $currentUserId = -1;
/**
* Get html form option of current item
*
* @param bool $echo if true echo html
*
* @return string
*/
public function getHtml($echo = true)
{
if ($this->formType == self::FORM_TYPE_USERS_PWD_RESET) {
$result = '';
$users = \DUPX_ArchiveConfig::getInstance()->getUsersLists();
$mainInputId = $this->formAttr['id'];
foreach ($users as $userId => $login) {
$this->currentUserId = $userId;
$this->formAttr['id'] = $mainInputId . '_' . $this->currentUserId;
$this->formAttr['label'] = $login;
$result .= parent::getHtml($echo);
}
$this->currentUserId = -1;
$this->formAttr['id'] = $mainInputId;
return $result;
} else {
return parent::getHtml($echo);
}
}
/**
* Display the html input of current item
*
* @return void
*/
protected function htmlItem()
{
if ($this->formType == self::FORM_TYPE_USERS_PWD_RESET) {
$this->pwdToggleHtml();
} else {
parent::htmlItem();
}
}
/**
* Return attribute name
*
* @return string
*/
protected function getAttrName(): string
{
return $this->name . '[' . $this->currentUserId . ']';
}
/**
* Return input value
*
* @return mixed
*/
protected function getInputValue()
{
return $this->value[$this->currentUserId] ?? '';
}
/**
* Get default form attributes
*
* @param string $formType form type
*
* @return array<string, mixed>
*/
protected static function getDefaultAttrForFormType($formType): array
{
$attrs = parent::getDefaultAttrForFormType($formType);
if ($formType == self::FORM_TYPE_USERS_PWD_RESET) {
$attrs['maxLength'] = null; // if null have no limit
$attrs['size'] = null;
}
return $attrs;
}
}

View File

@@ -0,0 +1,214 @@
<?php
/**
* @package Duplicator\Installer
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Core\Params\Items;
use Duplicator\Installer\Utils\Log\Log;
/**
* this class manages a password type input with the hide / show passwrd button
*/
class ParamFormWpConfig extends ParamForm
{
const IN_WP_CONF_POSTFIX = '_inwpc';
/**
* Class constructor
*
* @param string $name param identifier
* @param string $type TYPE_STRING | TYPE_ARRAY_STRING | ...
* @param string $formType FORM_TYPE_HIDDEN | FORM_TYPE_TEXT | ...
* @param array<string, mixed> $attr list of attributes
* @param array<string, mixed> $formAttr list of form attributes
*/
public function __construct($name, $type, $formType, array $attr = [], array $formAttr = [])
{
parent::__construct($name, $type, $formType, $attr, $formAttr);
$this->attr['defaultFromInput'] = $this->attr['default'];
$this->attr['defaultFromInput']['inWpConfig'] = false;
if ($type === self::TYPE_BOOL) {
$this->attr['defaultFromInput']['value'] = false;
}
}
/**
* this function is calle before sanitization.
* Is use in extendend classs and transform value before the sanitization and validation process
*
* @param array<string,mixed> $superObject query string super object
*
* @return mixed[]
*/
protected function getValueFilter($superObject): array
{
$result = [
'value' => parent::getValueFilter($superObject),
'inWpConfig' => filter_var($superObject[$this->name . self::IN_WP_CONF_POSTFIX], FILTER_VALIDATE_BOOLEAN),
];
if (!parent::isValueInInput($superObject)) {
$result['value'] = $this->attr['defaultFromInput']['value'];
}
return $result;
}
/**
* Return sanitized value
*
* @param mixed $value input value
*
* @return mixed[]
*/
public function getSanitizeValue($value): array
{
$result = (array) $value;
$result['value'] = parent::getSanitizeValue($result['value']);
return $result;
}
/**
* Get value info from value
*
* @return string
*/
protected function valueToInfo(): string
{
if ($this->value['inWpConfig']) {
return 'Set in wp config with value ' . parent::valueToInfo();
} else {
return 'Not set in wp config';
}
}
/**
* Return input value
*
* @return mixed
*/
protected function getInputValue()
{
return $this->value['value'];
}
/**
* Return true if value is in input method
*
* @param array<string, mixed> $superObject query string super object
*
* @return bool
*/
protected function isValueInInput($superObject): bool
{
return parent::isValueInInput($superObject) || isset($superObject[$this->name . self::IN_WP_CONF_POSTFIX]);
}
/**
* Check if input value is valid
*
* @param mixed $value input value
* @param mixed $validateValue variable passed by reference. Updated to validated value in the case, the value is a valid value.
*
* @return bool true if is a valid value for this object
*/
public function isValid($value, &$validateValue = null): bool
{
if (!is_array($value) || !isset($value['value']) || !isset($value['inWpConfig'])) {
Log::info('WP CONFIG INVALID ARRAY VAL:' . Log::v2str($value));
return false;
}
// IF isn't in wp config the value isn't validate
if ($value['inWpConfig'] === false) {
$validateValue = $value;
return true;
} else {
$confValidValue = $value['value'];
if (parent::isValid($value['value'], $confValidValue) === false) {
Log::info('WP CONFIG INVALID VALUE:' . Log::v2str($confValidValue));
return false;
} else {
$validateValue = $value;
$validateValue['value'] = $confValidValue;
return true;
}
}
}
/**
* Render HTML input before content
*
* @return void
*/
protected function htmlInputContBefore()
{
if ($this->getFormStatus() == self::STATUS_INFO_ONLY) {
return;
}
if (!$this->value['inWpConfig']) {
$this->formAttr['inputContainerClasses'][] = 'no-display';
if ($this->formAttr['status'] == self::STATUS_ENABLED) {
$this->formAttr['status'] = self::STATUS_DISABLED;
}
}
$inputAttrs = [
'name' => $this->name . self::IN_WP_CONF_POSTFIX,
'value' => 1,
];
if ($this->value['inWpConfig']) {
$inputAttrs['checked'] = 'checked';
}
echo '<span class="wpinconf-check-wrapper" >';
\DUPX_U_Html::checkboxSwitch(
$inputAttrs,
['title' => 'Add in wp config']
);
echo '</span>';
}
/**
* This function return the default attr for each type.
* in the constructor an array merge is made between the result of this function and the parameters passed.
* In this way the values in $ this -> ['attr'] are always consistent.
*
* @param string $type param value type
*
* @return array<string,mixed>
*/
protected static function getDefaultAttrForType($type): array
{
$attrs = parent::getDefaultAttrForType($type);
$valFromInput = $attrs['defaultFromInput'];
$attrs['defaultFromInput'] = [
'value' => $valFromInput,
'inWpConfig' => false,
];
return $attrs;
}
/**
* this function return the default formAttr for each type.
* in the constructor an array merge is made between the result of this function and the parameters passed.
* In this way the values in $ this -> ['attr'] are always consistent.
*
* @param string $formType form type
*
* @return array<string,mixed>
*/
protected static function getDefaultAttrForFormType($formType): array
{
$attrs = parent::getDefaultAttrForFormType($formType);
$attrs['wrapperClasses'][] = 'wp-config-item';
$attrs['wrapperContainerTag'] = 'div';
return $attrs;
}
}

View File

@@ -0,0 +1,662 @@
<?php
/**
* @package Duplicator\Installer
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Core\Params\Items;
use Duplicator\Installer\Utils\Log\Log;
/**
* this class describes the value of a parameter.
* therefore the type of data is sanitization and validation.
* In addition to other features such as, for example, if it is a persistent parameter.
*/
class ParamItem
{
const INPUT_GET = 'g';
const INPUT_POST = 'p';
const INPUT_REQUEST = 'r';
const INPUT_COOKIE = 'c';
const INPUT_SERVER = 's';
const INPUT_ENV = 'e';
const TYPE_STRING = 'str';
const TYPE_ARRAY_STRING = 'arr_str';
const TYPE_ARRAY_MIXED = 'arr_mix';
const TYPE_INT = 'int';
const TYPE_ARRAY_INT = 'arr_int';
const TYPE_BOOL = 'bool';
const STATUS_INIT = 'init';
const STATUS_OVERWRITE = 'owr';
const STATUS_UPD_FROM_INPUT = 'updinp';
/**
* validate regexes for test input
*/
const VALIDATE_REGEX_INT_NUMBER = '/^[\+\-]?[0-9]+$/';
const VALIDATE_REGEX_INT_NUMBER_EMPTY = '/^[\+\-]?[0-9]*$/'; // can be empty
const VALIDATE_REGEX_AZ_NUMBER = '/^[A-Za-z0-9]+$/';
const VALIDATE_REGEX_AZ_NUMBER_EMPTY = '/^[A-Za-z0-9]*$/'; // can be empty
const VALIDATE_REGEX_AZ_NUMBER_SEP = '/^[A-Za-z0-9_\-]+$/'; // laddate Az 09 plus - and _
const VALIDATE_REGEX_AZ_NUMBER_SEP_EMPTY = '/^[A-Za-z0-9_\-]*$/'; // laddate Az 09 plus - and _, can be empty
const VALIDATE_REGEX_DIR_PATH = '/^([a-zA-Z]:[\\\\\/]|\/|\\\\\\\\|\/\/)[^<>\0]+$/';
const VALIDATE_REGEX_FILE_PATH = '/^([a-zA-Z]:[\\\\\/]|\/|\\\\\\\\|\/\/)[^<>\0]+$/';
/** @var string */
protected $name = '';
/** @var string */
protected $type = '';
/** @var array<string, mixed> */
protected array $attr;
/** @var mixed */
protected $value;
/** @var string */
protected $status = self::STATUS_INIT;
/**
* Class constructor
*
* @param string $name param identifier
* @param string $type TYPE_STRING | TYPE_ARRAY_STRING | ...
* @param array<string, mixed> $attr list of attributes
*/
public function __construct($name, $type, array $attr = [])
{
if (empty($name) || strlen($name) < 4) {
throw new \Exception('the name can\'t be empty or len can\'t be minor of 4');
}
$this->type = $type;
$this->attr = array_merge(static::getDefaultAttrForType($type), (array) $attr);
if ($type == self::TYPE_ARRAY_STRING || $type == self::TYPE_ARRAY_INT || $type == self::TYPE_ARRAY_MIXED) {
$this->attr['default'] = (array) $this->attr['default'];
}
$this->name = $name;
$this->value = $this->getSanitizeValue($this->attr['default']);
if (is_null($this->attr['defaultFromInput'])) {
$this->attr['defaultFromInput'] = $this->attr['default'];
} else {
if ($type == self::TYPE_ARRAY_STRING || $type == self::TYPE_ARRAY_INT || $type == self::TYPE_ARRAY_MIXED) {
$this->attr['defaultFromInput'] = (array) $this->attr['defaultFromInput'];
}
}
}
/**
* this funtion return the discursive label
*
* @return string
*/
public function getLabel()
{
return $this->name;
}
/**
* get current item identifier
*
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* get current item value
*
* @return mixed
*/
public function getValue()
{
return $this->value;
}
/**
*
* @return string // STATUS_INIT | STATUS_OVERWRITE | STATUS_UPD_FROM_INPUT
*/
public function getStatus()
{
return $this->status;
}
/**
* Set item status with overwrite value
*
* @return void
*/
public function setOveriteStatus(): void
{
$this->status = self::STATUS_OVERWRITE;
}
/**
* if it is true, this object is defined as persistent and will be saved in the parameter persistence file otherwise the param manager
* will not save this value and at each call of the script the parameter will assume the default value.
*
* @return bool
*/
public function isPersistent()
{
return $this->attr['persistence'];
}
/**
* return the invalid param message or empty string
*
* @return string
*/
public function getInvalidMessage()
{
if (is_callable($this->attr['invalidMessage'])) {
return call_user_func($this->attr['invalidMessage'], $this);
} else {
return (string) $this->attr['invalidMessage'];
}
}
/**
* Set the invalid param message
*
* @param string $message invalid message
*
* @return void
*/
public function setInvalidMessage($message): void
{
$this->attr['invalidMessage'] = (string) $message;
}
/**
* Update item attribute
*
* @param string $key attribute key
* @param mixed $value value
*
* @return void
*/
public function setAttr($key, $value): void
{
$this->attr[$key] = $value;
}
/**
* Set param value
*
* @param mixed $value value to set
*
* @return boolean false if value isn't validated
*/
public function setValue($value): bool
{
$validateValue = null;
if (!$this->isValid($value, $validateValue)) {
return false;
}
$this->value = $validateValue;
return true;
}
/**
* Get super object from method
*
* @param string $method query string method
*
* @return array<string, mixed> return the reference
*/
protected static function getSuperObjectByMethod($method): array
{
$superObject = [];
switch ($method) {
case self::INPUT_GET:
$superObject = &$_GET;
break;
case self::INPUT_POST:
$superObject = &$_POST;
break;
case self::INPUT_REQUEST:
$superObject = &$_REQUEST;
break;
case self::INPUT_COOKIE:
$superObject = &$_COOKIE;
break;
case self::INPUT_SERVER:
$superObject = &$_SERVER;
break;
case self::INPUT_ENV:
$superObject = &$_ENV;
break;
default:
throw new \Exception('INVALID SUPER OBJECT METHOD ' . Log::v2str($method));
}
return $superObject;
}
/**
* Return true if value is in input method
*
* @param mixed[] $superObject query string super object
*
* @return bool
*/
protected function isValueInInput($superObject): bool
{
return isset($superObject[$this->name]);
}
/**
* update the value from input if exists ot set the default
* sanitation and validation are performed
*
* @param string $method query string method
*
* @return boolean false if value isn't validated
*/
public function setValueFromInput($method = self::INPUT_POST)
{
$superObject = self::getSuperObjectByMethod($method);
Log::info(
'SET VALUE FROM INPUT KEY [' . $this->name . '] VALUE[' .
Log::v2str($superObject[$this->name] ?? '') .
']',
Log::LV_DEBUG
);
if (!$this->isValueInInput($superObject)) {
$inputValue = $this->attr['defaultFromInput'];
} else {
// get value from input
$inputValue = $this->getValueFilter($superObject);
// sanitize value
$inputValue = $this->getSanitizeValue($inputValue);
}
if (($result = $this->setValue($inputValue)) === false) {
$msg = 'PARAM [' . $this->name . '] ERROR: Invalid value ' . Log::v2str($inputValue);
Log::info($msg);
return false;
} else {
$this->status = self::STATUS_UPD_FROM_INPUT;
}
return $result;
}
/**
* Check if input value is valid
*
* @param mixed $value input value
* @param mixed $validateValue variable passed by reference. Updated to validated value in the case, the value is a valid value.
*
* @return bool true if is a valid value for this object
*/
public function isValid($value, &$validateValue = null)
{
switch ($this->type) {
case self::TYPE_STRING:
case self::TYPE_BOOL:
case self::TYPE_INT:
return $this->isValidScalar($value, $validateValue);
case self::TYPE_ARRAY_STRING:
case self::TYPE_ARRAY_INT:
case self::TYPE_ARRAY_MIXED:
return $this->isValidArray($value, $validateValue);
default:
throw new \Exception('ITEM ERROR invalid type ' . $this->type);
}
}
/**
* Validate function for scalar value
*
* @param mixed $value input value
* @param mixed $validateValue variable passed by reference. Updated to validated value in the case, the value is a valid value.
*
* @return boolean false if value isn't a valid value
*/
protected function isValidScalar($value, &$validateValue = null)
{
if (!is_null($value) && !is_scalar($value)) {
return false;
}
$result = true;
switch ($this->type) {
case self::TYPE_STRING:
case self::TYPE_ARRAY_STRING:
$validateValue = (string) $value;
if (strlen($validateValue) < $this->attr['min_len']) {
$this->setInvalidMessage('Must have ' . $this->attr['min_len'] . ' or more characters');
$result = false;
}
if ($this->attr['max_len'] > 0 && strlen($validateValue) > $this->attr['max_len']) {
$this->setInvalidMessage('Must have max ' . $this->attr['mimax_lenn_len'] . ' characters');
$result = false;
}
if (!empty($this->attr['validateRegex']) && preg_match($this->attr['validateRegex'], $validateValue) !== 1) {
$this->setInvalidMessage('String isn\'t valid');
$result = false;
}
break;
case self::TYPE_INT:
case self::TYPE_ARRAY_INT:
$validateValue = filter_var($value, FILTER_VALIDATE_INT, [
'options' => [
'default' => false, // value to return if the filter fails
'min_range' => $this->attr['min_range'],
'max_range' => $this->attr['max_range'],
],
]);
if ($validateValue === false) {
$this->setInvalidMessage('Isn\'t a valid number');
$result = false;
}
break;
case self::TYPE_BOOL:
$validateValue = is_bool($value) ? $value : filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
if (($result = !is_null($validateValue)) === false) {
$this->setInvalidMessage('Isn\'t a valid value');
}
break;
default:
throw new \Exception('ITEM ERROR ' . $this->name . ' Invalid type ' . $this->type);
}
if ($result == true) {
$acceptValues = $this->getAcceptValues();
if (empty($acceptValues)) {
$result = $this->callValidateCallback($validateValue);
} else {
if (in_array($validateValue, $acceptValues)) {
$result = true;
} else {
$this->setInvalidMessage('Isn\'t a accepted value');
$result = false;
}
}
}
if ($result === false) {
$validateValue = null;
}
return $result;
}
/**
* Validate function for array value
*
* @param mixed $value input value
* @param mixed $validateValue variable passed by reference. Updated to validated value in the case, the value is a valid value.
*
* @return boolean false if value isn't a valid value
*/
protected function isValidArray($value, &$validateValue = null)
{
$newValues = (array) $value;
$validateValue = [];
$validValue = null;
if ($this->type == self::TYPE_ARRAY_MIXED) {
$validateValue = $newValues;
return $this->callValidateCallback($newValues);
} else {
foreach ($newValues as $key => $newValue) {
if (!$this->isValidScalar($newValue, $validValue)) {
return false;
}
$validateValue[$key] = $validValue;
}
}
return true;
}
/**
* Call attribute validate callback
*
* @param mixed $value input value
*
* @return mixed
*/
protected function callValidateCallback($value)
{
if (is_callable($this->attr['validateCallback'])) {
return call_user_func($this->attr['validateCallback'], $value, $this);
} elseif (!is_null($this->attr['validateCallback'])) {
throw new \Exception('PARAM ' . $this->name . ' validateCallback isn\'t null and isn\'t callable');
} else {
return true;
}
}
/**
* this function is calle before sanitization.
* Is use in extendend classs and transform value before the sanitization and validation process
*
* @param mixed[] $superObject query string super object
*
* @return mixed
*/
protected function getValueFilter($superObject)
{
if (isset($superObject[$this->name])) {
return $superObject[$this->name];
} else {
return null;
}
}
/**
* Return sanitized value
*
* @param mixed $value input value
*
* @return mixed
*/
public function getSanitizeValue($value)
{
switch ($this->type) {
case self::TYPE_STRING:
case self::TYPE_BOOL:
case self::TYPE_INT:
return $this->getSanitizeValueScalar($value);
case self::TYPE_ARRAY_STRING:
case self::TYPE_ARRAY_INT:
return $this->getSanitizeValueArray($value);
case self::TYPE_ARRAY_MIXED:
// global sanitize for mixed
return $this->getSanitizeValueScalar($value);
default:
throw new \Exception('ITEM ERROR invalid type ' . $this->type);
}
}
/**
* If sanitizeCallback is apply sanitizeCallback at current value else return value.
*
* @param mixed $value input value
*
* @return mixed
*/
protected function getSanitizeValueScalar($value)
{
if (is_callable($this->attr['sanitizeCallback'])) {
return call_user_func($this->attr['sanitizeCallback'], $value);
} elseif (!is_null($this->attr['sanitizeCallback'])) {
throw new \Exception('PARAM ' . $this->name . ' sanitizeCallback isn\'t null and isn\'t callable');
} else {
return $value;
}
}
/**
* If sanitizeCallback is apply sanitizeCallback at each value of array.
*
* @param mixed $value input value
*
* @return mixed[]
*/
protected function getSanitizeValueArray($value): array
{
$newValues = (array) $value;
$sanitizeValues = [];
foreach ($newValues as $key => $newValue) {
$sanitizeValues[$key] = $this->getSanitizeValueScalar($newValue);
}
return $sanitizeValues;
}
/**
* Get accept values
*
* @return array<int, mixed>
*/
public function getAcceptValues()
{
if (is_callable($this->attr['acceptValues'])) {
return call_user_func($this->attr['acceptValues'], $this);
} else {
return $this->attr['acceptValues'];
}
}
/**
* Set value from array. This function is used to set data from json array
*
* @param array{value: mixed, status?: string} $data param data
*
* @return boolean
*/
public function fromArrayData($data)
{
$data = (array) $data;
if (isset($data['status'])) {
$this->status = $data['status'];
}
// only if value is different from current value
if (isset($data['value']) && $data['value'] !== $this->value) {
$sanitizedVal = $this->getSanitizeValue($data['value']);
return $this->setValue($sanitizedVal);
} else {
return true;
}
}
/**
* Return array dato to store in json array data
*
* @return array{value: mixed, status: string}
*/
public function toArrayData(): array
{
return [
'value' => $this->value,
'status' => $this->status,
];
}
/**
* Return a copy of this object with a new name ad overwrite attr
*
* @param string $newName new name
* @param array<string, mixed> $attr overwrite attributes
*
* @return static
*/
public function getCopyWithNewName($newName, $attr = [])
{
$copy = clone $this;
$reflect = new \ReflectionObject($copy);
$nameProp = $reflect->getProperty('name');
if (PHP_VERSION_ID < 80100) {
$nameProp->setAccessible(true);
}
$nameProp->setValue($copy, $newName);
$attrProp = $reflect->getProperty('attr');
if (PHP_VERSION_ID < 80100) {
$attrProp->setAccessible(true);
}
$newAttr = array_merge($attrProp->getValue($copy), $attr);
$attrProp->setValue($copy, $newAttr);
$valueProp = $reflect->getProperty('value');
if (PHP_VERSION_ID < 80100) {
$valueProp->setAccessible(true);
}
$valueProp->setValue($copy, $newAttr['default']);
return $copy;
}
/**
* This function return the default attr for each type.
* in the constructor an array merge is made between the result of this function and the parameters passed.
* In this way the values in $ this -> ['attr'] are always consistent.
*
* @param string $type param value type
*
* @return array<string, mixed>
*/
protected static function getDefaultAttrForType($type): array
{
$attrs = [
'default' => null, // the default value on init
'defaultFromInput' => null, // if value isn't set in query form when setValueFromInput is called set this valus.
// (normally defaultFromInput is equal to default)
'acceptValues' => [], // if not empty accept only values in list | callback
'sanitizeCallback' => null, // function (ParamItem $obj, $inputValue)
'validateCallback' => null, // function (ParamItem $obj, $validateValue, $originalValue)
'persistence' => true, // if false don't store value in persistence file
'invalidMessage' => '',//this message is added at next step validation error message if not empty
];
switch ($type) {
case self::TYPE_STRING: // value type is a string
$attrs['min_len'] = 0; // min string len. used in validation
$attrs['max_len'] = 0; // max string len. used in validation
$attrs['default'] = ''; // set default at empty string
$attrs['validateRegex'] = null; // if isn;t null this regex is called to pass for validation.
//Can be combined with validateCallback. If both are active, the validation must pass both.
break;
case self::TYPE_ARRAY_STRING: // value type is array of string
$attrs['min_len'] = 0; // min string len. used in validation
$attrs['max_len'] = 0; // max string len. used in validation
$attrs['default'] = []; // set default at empty array
$attrs['validateRegex'] = null; // if isn;t null this regex is called to pass for validation.
// Can be combined with validateCallback. If both are active, the validation must pass both.
break;
case self::TYPE_INT: // value type is a int
$attrs['min_range'] = PHP_INT_MAX * -1;
$attrs['max_range'] = PHP_INT_MAX;
$attrs['default'] = 0; // set default at 0
break;
case self::TYPE_ARRAY_INT: // value type is an array of int
$attrs['min_range'] = PHP_INT_MAX * -1;
$attrs['max_range'] = PHP_INT_MAX;
$attrs['default'] = []; // set default at empty array
break;
case self::TYPE_BOOL:
$attrs['default'] = false; // set default fals
$attrs['defaultFromInput'] = false; // if value isn't set in input the default must be false for bool values
break;
case self::TYPE_ARRAY_MIXED:
break;
default:
// accepts unknown values because this class can be extended
}
return $attrs;
}
}

View File

@@ -0,0 +1,144 @@
<?php
/**
* @package Duplicator\Installer
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Core\Params\Items;
/**
* this class describes the options for select, radio and multiple checboxes
*/
class ParamOption
{
const OPT_ENABLED = 'opt_enabled';
const OPT_DISABLED = 'opt_disabled';
const OPT_HIDDEN = 'opt_hidden';
/** @var string */
public $value = '';
/** @var string */
public $label = '';
/** @var array<string, mixed> */
public $attrs = [];
/** @var string */
protected $optStatus = self::OPT_ENABLED;
/** @var string */
protected $note = '';
/** @var string */
protected $groupLabel = '';
/**
* Class constructor
*
* @param mixed $value option value
* @param string $label label
* @param string|callable $optStatus option status. can be a fixed status or a callback
* @param array<string, mixed> $attrs option attributes
*/
public function __construct($value, $label, $optStatus = self::OPT_ENABLED, $attrs = [])
{
$this->value = $value;
$this->label = $label;
$this->optStatus = $optStatus;
$this->attrs = (array) $attrs;
}
/**
* get current statis.
*
* @return string
*/
public function getStatus()
{
if (is_callable($this->optStatus)) {
return call_user_func($this->optStatus, $this);
} else {
return $this->optStatus;
}
}
/**
* Set options status
*
* @param string|callable $optStatus option status. can be a fixed status or a callback
*
* @return void
*/
public function setStatus($optStatus): void
{
$this->optStatus = $optStatus;
}
/**
* Set option note
*
* @param string|callable $note option note
*
* @return void
*/
public function setNote($note): void
{
$this->note = is_callable($note) ? $note : ((string) $note);
}
/**
*
* @return string
*/
public function getNote(): string
{
$note = is_callable($this->note) ? call_user_func($this->note, $this) : $this->note;
return (empty($note) ? '' : '<div class="sub-note" >' . $note . '</div>');
}
/**
* Set option group, used on select
*
* @param string $label optiongroup label is empty reset option
*
* @return void
*/
public function setOptGroup($label): void
{
$this->groupLabel = (string) $label;
}
/**
* Return option group label, empty if not set
*
* @return string
*/
public function getOptGroup()
{
return $this->groupLabel;
}
/**
*
* @return bool
*/
public function isEnable(): bool
{
return $this->getStatus() == self::OPT_ENABLED;
}
/**
*
* @return bool
*/
public function isDisabled(): bool
{
return $this->getStatus() == self::OPT_DISABLED;
}
/**
*
* @return bool
*/
public function isHidden(): bool
{
return $this->getStatus() == self::OPT_HIDDEN;
}
}

View File

@@ -0,0 +1,177 @@
<?php
/**
* @package Duplicator\Installer
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Core\Params\Models;
use Duplicator\Installer\Core\Params\PrmMng;
use Duplicator\Libs\Snap\SnapURL;
use VendorDuplicator\Amk\JsonSerialize\AbstractJsonSerializable;
use DUPX_ArchiveConfig;
use Exception;
class SiteOwrMap extends AbstractJsonSerializable
{
const NEW_SUBSITE_WITH_SLUG = 0;
const NEW_SUBSITE_WITH_FULL_DOMAIN = -1;
const NEW_SUBSITE_NOT_VALID = -2;
/** @var int */
protected $sourceId = -1;
/** @var int */
protected $targetId = -1;
/** @var string */
protected $newSlug = '';
/** @var string */
protected $blogName;
/**
* Class constructor
*
* @param int $sourceId source subsite id
* @param int $targetId target subsite id
* @param string $newSlug new slug on new site
*/
public function __construct($sourceId, $targetId, $newSlug = '')
{
if ($sourceId < 1) {
throw new Exception('Source id [' . $sourceId . '] invalid ');
}
if ($targetId <= self::NEW_SUBSITE_NOT_VALID) {
throw new Exception('Target id [' . $targetId . '] invalid ');
}
if (($sourceObj = DUPX_ArchiveConfig::getInstance()->getSubsiteObjById($sourceId)) === false) {
throw new Exception('Source site info don\'t exists');
}
$this->sourceId = $sourceId;
$this->targetId = $targetId;
$this->newSlug = $newSlug;
$this->blogName = $sourceObj->blogname;
}
/**
* Get the value of targetId
*
* @return int
*/
public function getTargetId()
{
return $this->targetId;
}
/**
* Update target id
*
* @param int $targetId new target id
*
* @return void
*/
public function setTargetId($targetId): void
{
$this->targetId = (int) $targetId;
}
/**
* Get the value of sourceId
*
* @return int
*/
public function getSourceId()
{
return $this->sourceId;
}
/**
* Get the value of newSlug
*
* @return string
*/
public function getNewSlug()
{
return $this->newSlug;
}
/**
* Return full URL from new slug or false if isn't new mode
*
* @param string $mainSiteURL Main site domain
* @param bool $subdomain if true is subdomain else subfolder
* @param bool $scheme if true return URL with scheme
*
* @return false|string
*/
public function getNewSlugFullUrl($mainSiteURL, $subdomain, $scheme = false)
{
$mainSiteDomain = SnapURL::parseUrl($mainSiteURL, PHP_URL_HOST);
if (($schemeURL = SnapURL::parseUrl($mainSiteURL, PHP_URL_SCHEME)) == false) {
$schemeURL = 'http';
}
$result = '';
switch ($this->targetId) {
case SiteOwrMap::NEW_SUBSITE_WITH_SLUG:
if (strlen($this->newSlug) == 0) {
return false;
}
if ($subdomain) {
$result = $this->newSlug . '.' . SnapURL::wwwRemove($mainSiteDomain);
} else {
$result = $mainSiteDomain . '/' . $this->newSlug;
}
break;
case SiteOwrMap::NEW_SUBSITE_WITH_FULL_DOMAIN:
if (strlen($this->newSlug) == 0) {
return false;
}
$result = $this->newSlug;
break;
default:
return false;
}
if ($scheme) {
$result = $schemeURL . '://' . $result;
}
return $result;
}
/**
* Get source sibsite info
*
* @return false|array<string, mixed>
*/
public function getSourceSiteInfo()
{
if (($info = \DUPX_ArchiveConfig::getInstance()->getSubsiteObjById($this->sourceId)) == false) {
return false;
}
return (array) $info;
}
/**
* Return target site info
*
* @return false|array<string, mixed>
*/
public function getTargetSiteInfo()
{
$overwriteData = PrmMng::getInstance()->getValue(PrmMng::PARAM_OVERWRITE_SITE_DATA);
foreach ($overwriteData['subsites'] as $subsite) {
if ($subsite['id'] == $this->targetId) {
return $subsite;
}
}
return false;
}
}

View File

@@ -0,0 +1,877 @@
<?php
/**
* @package Duplicator\Installer
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Core\Params;
use Duplicator\Installer\Core\Bootstrap;
use Duplicator\Installer\Core\Hooks\HooksMng;
use Duplicator\Installer\Utils\Log\Log;
use Duplicator\Installer\Core\Params\Items\ParamForm;
use Duplicator\Installer\Core\Params\Descriptors\ParamsDescriptors;
use Duplicator\Installer\Core\Params\Items\ParamItem;
use Duplicator\Installer\Utils\InstDescMng;
use Duplicator\Libs\Snap\SnapIO;
use Duplicator\Libs\Snap\SnapJson;
use Duplicator\Libs\Snap\SnapString;
/**
* Params Manager
*/
class PrmMng
{
const ENV_PARAMS_KEY = 'DUPLICATOR_PARAMS';
/**
* overwrite file content example
* <?php
* $json = <<<JSON
* {
* "debug": {
* "value": false
* },
* "debug_params": {
* "value": true
* },
* "logging": {
* "value": 2
* }
* }
* JSON;
* // OVERWRITE FILE END
*/
const LOCAL_OVERWRITE_PARAMS = 'duplicator_params_overwrite';
const LOCAL_OVERWRITE_PARAMS_LEGACY = 'duplicator_pro_params_overwrite';
const LOCAL_OVERWRITE_PARAMS_EXTENSION = '.php';
// actionsLOCAL_OVERWRITE_PARAMS
const PARAM_INSTALLER_MODE = 'inst_mode';
const PARAM_OVERWRITE_SITE_DATA = 'ovr_site_data';
const PARAM_CTRL_ACTION = 'ctrl_action';
const PARAM_ROUTER_ACTION = 'router-action';
const PARAM_SECURE_PASS = 'secure-pass';
const PARAM_SECURE_ARCHIVE_HASH = 'secure-archive';
const PARAM_SECURE_OK = 'secure-ok';
const PARAM_STEP_ACTION = 'step-action';
const PARAM_TEMPLATE = 'template';
const PARAM_VALIDATION_LEVEL = 'valid-level';
const PARAM_VALIDATION_ACTION_ON_START = 'valid-act';
const PARAM_VALIDATION_SHOW_ALL = 'valid-show-all';
const PARAM_ACCEPT_TERM_COND = 'accept-warnings';
const PARAM_RECOVERY_LINK = 'recovery-link';
const PARAM_FROM_SITE_IMPORT_INFO = 'import-info';
// input params
const PARAM_DEBUG = 'debug';
const PARAM_DEBUG_PARAMS = 'debug_params';
const PARAM_ARCHIVE_ENGINE = 'archive_engine';
const PARAM_ARCHIVE_ENGINE_SKIP_WP_FILES = 'archive_engine_wpskip';
const PARAM_ARCHIVE_ACTION = 'archive_action';
const PARAM_LOGGING = 'logging';
const PARAM_ZIP_THROTTLING = 'zip_throttling';
const PARAM_REMOVE_RENDUNDANT = 'remove-redundant';
const PARAM_REMOVE_USERS_WITHOUT_PERMISSIONS = 'remove-users-without-permissions';
const PARAM_FILE_TIME = 'zip_filetime';
const PARAM_HTACCESS_CONFIG = 'ht_config';
const PARAM_OTHER_CONFIG = 'other_config';
const PARAM_WP_CONFIG = 'wp_config';
const PARAM_SAFE_MODE = 'exe_safe_mode';
const PARAM_SET_FILE_PERMS = 'set_file_perms';
const PARAM_FILE_PERMS_VALUE = 'file_perms_value';
const PARAM_SET_DIR_PERMS = 'set_dir_perms';
const PARAM_DIR_PERMS_VALUE = 'dir_perms_value';
const PARAM_INST_TYPE = 'install-type';
const PARAM_SUBSITE_ID = 'subsite_id';
const PARAM_SUBSITE_OVERWRITE_MAPPING = 'subsite_owr_mapping';
const PARAM_DB_DISPLAY_OVERWIRE_WARNING = 'db-display-overwarn';
const PARAM_DB_ENGINE = 'db-engine';
const PARAM_DB_VIEW_MODE = 'view_mode';
const PARAM_DB_ACTION = 'dbaction';
const PARAM_DB_HOST = 'dbhost';
const PARAM_DB_NAME = 'dbname';
const PARAM_DB_USER = 'dbuser';
const PARAM_DB_FLAG = 'dbflag';
const PARAM_DB_TABLE_PREFIX = 't_prefix';
const PARAM_DB_PASS = 'dbpass';
const PARAM_DB_CHARSET = 'dbcharset';
const PARAM_DB_COLLATE = 'dbcollate';
const PARAM_DB_CHUNK = 'dbchunk';
const PARAM_DB_VIEW_CREATION = 'dbobj_views';
const PARAM_DB_PROC_CREATION = 'dbobj_procs';
const PARAM_DB_FUNC_CREATION = 'dbobj_funcs';
const PARAM_DB_REMOVE_DEFINER = 'db_remove_definer';
const PARAM_DB_SPLIT_CREATES = 'dbsplit_creates';
const PARAM_DB_MYSQL_MODE = 'dbmysqlmode';
const PARAM_DB_MYSQL_MODE_OPTS = 'dbmysqlmode_opts';
const PARAM_DB_DONE_CREATES = 'db_done_creates';
const PARAM_CPNL_CAN_SELECTED = 'cpnl-can-sel';
const PARAM_CPNL_HOST = 'cpnl-host';
const PARAM_CPNL_USER = 'cpnl-user';
const PARAM_CPNL_PASS = 'cpnl-pass';
const PARAM_CPNL_IGNORE_PREFIX = 'cpnl_ignore_prefix';
const PARAM_CPNL_DB_ACTION = 'cpnl-dbaction';
const PARAM_CPNL_DB_HOST = 'cpnl-dbhost';
const PARAM_CPNL_PREFIX = 'cpnl-prefix';
const PARAM_CPNL_DB_NAME_SEL = 'cpnl-dbname-select';
const PARAM_CPNL_DB_NAME_TXT = 'cpnl-dbname-txt';
const PARAM_CPNL_DB_USER_SEL = 'cpnl-dbuser-select';
const PARAM_CPNL_DB_USER_TXT = 'cpnl-dbuser-txt';
const PARAM_CPNL_DB_USER_CHK = 'cpnl-dbuser-chk';
const PARAM_CPNL_DB_PASS = 'cpnl-dbpass';
const PARAM_URL_OLD = 'url_old';
const PARAM_URL_NEW = 'url_new';
const PARAM_SITE_URL_OLD = 'siteurl_old';
const PARAM_SITE_URL = 'siteurl';
const PARAM_PATH_WP_CORE_OLD = 'path_core_old';
const PARAM_PATH_WP_CORE_NEW = 'path_core_new';
const PARAM_PATH_OLD = 'path_old';
const PARAM_PATH_NEW = 'path_new';
const PARAM_PATH_CONTENT_OLD = 'path_cont_old';
const PARAM_PATH_CONTENT_NEW = 'path_cont_new';
const PARAM_URL_CONTENT_OLD = 'url_cont_old';
const PARAM_URL_CONTENT_NEW = 'url_cont_new';
const PARAM_PATH_UPLOADS_OLD = 'path_upl_old';
const PARAM_PATH_UPLOADS_NEW = 'path_upl_new';
const PARAM_URL_UPLOADS_OLD = 'url_upl_old';
const PARAM_URL_UPLOADS_NEW = 'url_upl_new';
const PARAM_PATH_PLUGINS_OLD = 'path_plug_old';
const PARAM_PATH_PLUGINS_NEW = 'path_plug_new';
const PARAM_URL_PLUGINS_OLD = 'url_plug_old';
const PARAM_URL_PLUGINS_NEW = 'url_plug_new';
const PARAM_PATH_MUPLUGINS_OLD = 'path_muplug_old';
const PARAM_PATH_MUPLUGINS_NEW = 'path_muplug_new';
const PARAM_URL_MUPLUGINS_OLD = 'url_muplug_old';
const PARAM_URL_MUPLUGINS_NEW = 'url_muplug_new';
const PARAM_WP_ADDON_SITES_PATHS = 'wpaddon_sites';
const PARAM_BLOGNAME = 'blogname';
const PARAM_MU_REPLACE = 'mu_replace';
const PARAM_REPLACE_ENGINE = 'mode_chunking';
const PARAM_SKIP_PATH_REPLACE = 'skip_path_replace';
const PARAM_EMPTY_SCHEDULE_STORAGE = 'empty_schedule_storage';
const PARAM_DB_TABLES = 'tables';
const PARAM_EMAIL_REPLACE = 'search_replace_email_domain';
const PARAM_FULL_SEARCH = 'fullsearch';
const PARAM_POSTGUID = 'postguid';
const PARAM_MAX_SERIALIZE_CHECK = 'mstrlim';
const PARAM_MULTISITE_CROSS_SEARCH = 'cross_search';
const PARAM_PLUGINS = 'plugins';
const PARAM_IGNORE_PLUGINS = 'ignore_plugins';
const PARAM_FORCE_DIABLE_PLUGINS = 'fd_plugins';
const PARAM_CUSTOM_SEARCH = 'search';
const PARAM_CUSTOM_REPLACE = 'replace';
const PARAM_USERS_MODE = 'users_mode';
const PARAM_ADD_SUBSITE_USER_MODE = 'subsite_users_mode';
const PARAM_USERS_PWD_RESET = 'users_pwd_reset';
const PARAM_WP_ADMIN_CREATE_NEW = 'wp_new_admin';
const PARAM_WP_ADMIN_NAME = 'wp_username';
const PARAM_WP_ADMIN_PASSWORD = 'wp_password';
const PARAM_WP_ADMIN_MAIL = 'wp_mail';
const PARAM_WP_ADMIN_NICKNAME = 'wp_nickname';
const PARAM_WP_ADMIN_FIRST_NAME = 'wp_first_name';
const PARAM_WP_ADMIN_LAST_NAME = 'wp_last_name';
// WP CONFIG
const PARAM_GEN_WP_AUTH_KEY = 'auth_keys_and_salts';
const PARAM_WP_CONF_WP_SITEURL = 'wpc_WP_SITEURL';
const PARAM_WP_CONF_MYSQL_CLIENT_FLAGS = 'wpc_MYSQL_CLIENT_FLAGS';
const PARAM_WP_CONF_WP_HOME = 'wpc_WP_HOME';
const PARAM_WP_CONF_WP_CONTENT_DIR = 'wpc_WP_CONTENT_DIR';
const PARAM_WP_CONF_WP_CONTENT_URL = 'wpc_WP_CONTENT_URL';
const PARAM_WP_CONF_WP_PLUGIN_DIR = 'wpc_WP_PLUGIN_DIR';
const PARAM_WP_CONF_WP_PLUGIN_URL = 'wpc_WP_PLUGIN_URL';
const PARAM_WP_CONF_PLUGINDIR = 'wpc_PLUGINDIR';
const PARAM_WP_CONF_UPLOADS = 'wpc_UPLOADS';
const PARAM_WP_CONF_AUTOSAVE_INTERVAL = 'wpc_AUTOSAVE_INTERVAL';
const PARAM_WP_CONF_WP_POST_REVISIONS = 'wpc_WP_POST_REVISIONS';
const PARAM_WP_CONF_COOKIE_DOMAIN = 'wpc_COOKIE_DOMAIN';
const PARAM_WP_CONF_WP_ALLOW_MULTISITE = 'wpc_WP_ALLOW_MULTISITE';
const PARAM_WP_CONF_NOBLOGREDIRECT = 'wpc_NOBLOGREDIRECT';
const PARAM_WP_CONF_WP_DEBUG = 'wpc_WP_DEBUG';
const PARAM_WP_CONF_SCRIPT_DEBUG = 'wpc_SCRIPT_DEBUG';
const PARAM_WP_CONF_CONCATENATE_SCRIPTS = 'wpc_CONCATENATE_SCRIPTS';
const PARAM_WP_CONF_WP_DEBUG_LOG = 'wpc_WP_DEBUG_LOG';
const PARAM_WP_CONF_WP_DISABLE_FATAL_ERROR_HANDLER = 'wpc_WP_DISABLE_FATAL_ERROR_HANDLER';
const PARAM_WP_CONF_WP_DEBUG_DISPLAY = 'wpc_WP_DEBUG_DISPLAY';
const PARAM_WP_CONF_WP_MEMORY_LIMIT = 'wpc_WP_MEMORY_LIMIT';
const PARAM_WP_CONF_WP_MAX_MEMORY_LIMIT = 'wpc_WP_MAX_MEMORY_LIMIT';
const PARAM_WP_CONF_WP_TEMP_DIR = 'wpc_WP_TEMP_DIR';
const PARAM_WP_CONF_WP_CACHE = 'wpc_WP_CACHE';
const PARAM_WP_CONF_CUSTOM_USER_TABLE = 'wpc_CUSTOM_USER_TABLE';
const PARAM_WP_CONF_CUSTOM_USER_META_TABLE = 'wpc_CUSTOM_USER_META_TABLE';
const PARAM_WP_CONF_WPLANG = 'wpc_WPLANG';
const PARAM_WP_CONF_WP_LANG_DIR = 'wpc_WP_LANG_DIR';
const PARAM_WP_CONF_SAVEQUERIES = 'wpc_SAVEQUERIES';
const PARAM_WP_CONF_FS_CHMOD_DIR = 'wpc_FS_CHMOD_DIR';
const PARAM_WP_CONF_FS_CHMOD_FILE = 'wpc_FS_CHMOD_FILE';
const PARAM_WP_CONF_FS_METHOD = 'wpc_FS_METHOD';
const PARAM_WP_CONF_ALTERNATE_WP_CRON = 'wpc_ALTERNATE_WP_CRON';
const PARAM_WP_CONF_DISABLE_WP_CRON = 'wpc_DISABLE_WP_CRON';
const PARAM_WP_CONF_WP_CRON_LOCK_TIMEOUT = 'wpc_WP_CRON_LOCK_TIMEOUT';
const PARAM_WP_CONF_COOKIEPATH = 'wpc_COOKIEPATH';
const PARAM_WP_CONF_SITECOOKIEPATH = 'wpc_SITECOOKIEPATH';
const PARAM_WP_CONF_ADMIN_COOKIE_PATH = 'wpc_ADMIN_COOKIE_PATH';
const PARAM_WP_CONF_PLUGINS_COOKIE_PATH = 'wpc_PLUGINS_COOKIE_PATH';
const PARAM_WP_CONF_TEMPLATEPATH = 'wpc_TEMPLATEPATH';
const PARAM_WP_CONF_STYLESHEETPATH = 'wpc_STYLESHEETPATH';
const PARAM_WP_CONF_EMPTY_TRASH_DAYS = 'wpc_EMPTY_TRASH_DAYS';
const PARAM_WP_CONF_WP_ALLOW_REPAIR = 'wpc_WP_ALLOW_REPAIR';
const PARAM_WP_CONF_DO_NOT_UPGRADE_GLOBAL_TABLES = 'wpc_DO_NOT_UPGRADE_GLOBAL_TABLES';
const PARAM_WP_CONF_DISALLOW_FILE_EDIT = 'wpc_DISALLOW_FILE_EDIT';
const PARAM_WP_CONF_DISALLOW_FILE_MODS = 'wpc_DISALLOW_FILE_MODS';
const PARAM_WP_CONF_FORCE_SSL_ADMIN = 'wpc_FORCE_SSL_ADMIN';
const PARAM_WP_CONF_WP_HTTP_BLOCK_EXTERNAL = 'wpc_WP_HTTP_BLOCK_EXTERNAL';
const PARAM_WP_CONF_WP_ACCESSIBLE_HOSTS = 'wpc_WP_ACCESSIBLE_HOSTS';
const PARAM_WP_CONF_AUTOMATIC_UPDATER_DISABLED = 'wpc_AUTOMATIC_UPDATER_DISABLED';
const PARAM_WP_CONF_WP_AUTO_UPDATE_CORE = 'wpc_WP_AUTO_UPDATE_CORE';
const PARAM_WP_CONF_IMAGE_EDIT_OVERWRITE = 'wpc_IMAGE_EDIT_OVERWRITE';
const PARAM_WP_CONF_WPMU_PLUGIN_DIR = 'wpc_WPMU_PLUGIN_DIR';
const PARAM_WP_CONF_WPMU_PLUGIN_URL = 'wpc_WPMU_PLUGIN_URL';
const PARAM_WP_CONF_MUPLUGINDIR = 'wpc_MUPLUGINDIR';
// OTHER WP CONFIG SETTINGS NOT IN WP CORE
const PARAM_WP_CONF_WPCACHEHOME = 'wpc_WPCACHEHOME';
const PARAM_FINAL_REPORT_DATA = 'final_report';
const PARAM_AUTO_CLEAN_INSTALLER_FILES = 'auto-delete';
/** @var ?self */
private static $instance;
/** @var bool */
private static $initialized = false;
/** @var array<ParamItem|ParamForm> */
private $params = [];
/** @var string[] */
private $paramsHtmlInfo = [];
/**
*
* @return self
*/
public static function getInstance()
{
if (is_null(self::$instance)) {
self::$instance = new self();
}
return self::$instance;
}
/**
* __construct
*
* @return void
*/
private function __construct()
{
ParamsDescriptors::init();
}
/**
*
* @return boolean
* @throws \Exception
*/
public function initParams(): bool
{
if (self::$initialized) {
// prevent multiple inizialization
return true;
}
self::$initialized = true;
ParamsDescriptors::initParams($this->params);
$this->params = HooksMng::getInstance()->applyFilters('installer_get_init_params', $this->params);
$this->paramsHtmlInfo[] = '***** INIT PARAMS WITH STD VALUlES';
return true;
}
/**
* get value of param key.
* thorw execption if key don't exists
*
* @param string $key param key
*
* @return mixed
*
* @throws \Exception
*/
public function getValue($key)
{
if (!isset($this->params[$key])) {
throw new \Exception('Param key ' . Log::v2str($key) . 'don\' exists');
}
return $this->params[$key]->getValue();
}
/**
* Get param status
*
* @param string $key param key
*
* @return string // STATUS_INIT | STATUS_OVERWRITE | STATUS_UPD_FROM_INPUT
*
* @throws \Exception
*/
public function getInitStatus($key)
{
if (!isset($this->params[$key])) {
throw new \Exception('Param key ' . Log::v2str($key) . 'does not exists');
}
return $this->params[$key]->getStatus();
}
/**
* get the label of param key.
* thorw execption if key don't exists
*
* @param string $key param key
*
* @return string
*
* @throws \Exception
*/
public function getLabel($key): string
{
if (!isset($this->params[$key])) {
throw new \Exception('Param key ' . Log::v2str($key) . 'don\' exists');
}
return rtrim($this->params[$key]->getLabel(), ": \n\t");
}
/**
* Set param value
*
* @param string $key param key
* @param mixed $value value
*
* @return boolean // return false if params isn't valid
*
* @throws \Exception // if key don't exists
*/
public function setValue($key, $value): bool
{
if (!isset($this->params[$key])) {
throw new \Exception('Param key ' . Log::v2str($key) . 'don\' exists');
}
return $this->params[$key]->setValue($value);
}
/**
* this cungion set value get from input method.
*
* @param string $key param key
* @param string $method input method (GET, POST ... )
* @param boolean $thowException if true throw exception if value isn't valid.
* @param boolean $nextStepErrorMessage if true and param isn't valid add next step message
*
* @return boolean
*/
public function setValueFromInput($key, $method = ParamForm::INPUT_POST, $thowException = true, $nextStepErrorMessage = false)
{
if (!isset($this->params[$key])) {
throw new \Exception('Param key ' . Log::v2str($key) . 'don\' exists');
}
if (($result = $this->params[$key]->setValueFromInput($method)) === false) {
$this->paramsHtmlInfo[] = 'INVALID VALUE INPUT <b>' . $key . '</b>';
if ($nextStepErrorMessage) {
$this->addParamValidationFaliedNotice($key);
}
if ($thowException) {
$errorMessage = 'Parameter "' . $this->getLabel($key) . '" have invalid value';
throw new \Exception('PARAM ERROR: ' . $errorMessage);
}
} else {
$this->paramsHtmlInfo[] = 'SET FROM INPUT <b>' . $key . '</b> VALUE: ' . Log::v2str($this->params[$key]->getValue());
}
return $result;
}
/**
* Add next step validation failed notice
*
* @param string $key param key
*
* @return void
*/
public function addParamValidationFaliedNotice($key): void
{
if (!isset($this->params[$key])) {
throw new \Exception('Param key ' . Log::v2str($key) . 'don\' exists');
}
$longMessage = '<b>' . $this->getLabel($key) . '</b> ' . $this->params[$key]->getInvalidMessage() . "<br>\n";
\DUPX_NOTICE_MANAGER::getInstance()->addNextStepNotice([
'shortMsg' => 'Parameter validation failed',
'level' => \DUPX_NOTICE_ITEM::CRITICAL,
'longMsg' => $longMessage,
'longMsgMode' => \DUPX_NOTICE_ITEM::MSG_MODE_HTML,
], \DUPX_NOTICE_MANAGER::ADD_UNIQUE_APPEND, 'params_validation_fail');
}
/**
* Return the form param wrapper id
*
* @param string $key param key
*
* @return boolean|string return false if the item key isn't a instance of ParamForm
*/
public function getFormWrapperId($key)
{
if (!isset($this->params[$key])) {
throw new \Exception('Param key ' . Log::v2str($key) . 'don\' exists');
}
if (method_exists($this->params[$key], 'getFormWrapperId')) {
return $this->params[$key]->getFormWrapperId();
} else {
return false;
}
}
/**
* Return form item id
*
* @param string $key param key
*
* @return boolean|string return false if the item key isn't a instance of ParamForm
*/
public function getFormItemId($key)
{
if (!isset($this->params[$key])) {
throw new \Exception('Param key ' . Log::v2str($key) . 'don\' exists');
}
if (method_exists($this->params[$key], 'getFormItemId')) {
return $this->params[$key]->getFormItemId();
} else {
return false;
}
}
/**
* Get param form status
*
* @param string $key param key
*
* @return boolean|string return false if the item key isn't a instance of ParamForm
*/
public function getFormStatus($key)
{
if (!isset($this->params[$key])) {
throw new \Exception('Param key ' . Log::v2str($key) . 'don\' exists');
}
if (method_exists($this->params[$key], 'getFormStatus')) {
return $this->params[$key]->getFormStatus();
} else {
return false;
}
}
/**
* Set form param status
*
* @param string $key param key
* @param string|callable $status STATUS_ENABLED , STATUS_READONLY or callable function
*
* @return bool return false if the item key isn't a instance of ParamForm
*/
public function setFormStatus($key, $status): bool
{
if (!isset($this->params[$key])) {
throw new \Exception('Param key ' . Log::v2str($key) . 'don\' exists');
}
if (method_exists($this->params[$key], 'setFormAttr')) {
$this->params[$key]->setFormAttr('status', $status);
return true;
} else {
return false;
}
}
/**
* Set form wrapper class
*
* @param string $key param key
* @param string $class wrapper class
*
* @return bool return false if the item key isn't a instance of ParamForm
*/
public function addFormWrapperClass($key, $class): bool
{
if (!isset($this->params[$key])) {
throw new \Exception('Param key ' . Log::v2str($key) . 'don\' exists');
}
if (method_exists($this->params[$key], 'addWrapperClass')) {
$this->params[$key]->addWrapperClass($class);
return true;
} else {
return false;
}
}
/**
* Remove form wrapper class
*
* @param string $key param key
* @param string $class wrapper class
*
* @return bool return false if the item key isn't a instance of ParamForm
*/
public function removeFormWrapperClass($key, $class): bool
{
if (!isset($this->params[$key])) {
throw new \Exception('Param key ' . Log::v2str($key) . 'don\' exists');
}
if (method_exists($this->params[$key], 'removeWrapperClass')) {
$this->params[$key]->removeWrapperClass($class);
return true;
} else {
return false;
}
}
/**
* This tunction add o remove note on the param form
*
* @param string $key param key
* @param string $htmlString true if is html
*
* @return boolean
*/
public function setFormNote($key, $htmlString): bool
{
if (!isset($this->params[$key])) {
throw new \Exception('Param key ' . Log::v2str($key) . 'don\' exists');
}
if (method_exists($this->params[$key], 'setFormAttr')) {
$this->params[$key]->setFormAttr('subNote', $htmlString);
return true;
} else {
return false;
}
}
/**
* return true if the input exists in html form
* false if isn't ParamForm object or status is STATUS_INFO_ONLY or STATUS_SKIP
*
* @param string $key param key
*
* @return boolean
*/
public function isHtmlInput($key): bool
{
$status = $this->getFormStatus($key);
switch ($status) {
case ParamForm::STATUS_ENABLED:
case ParamForm::STATUS_READONLY:
case ParamForm::STATUS_DISABLED:
return true;
case ParamForm::STATUS_INFO_ONLY:
case ParamForm::STATUS_SKIP:
default:
return false;
}
}
/**
* get the input form html
*
* @param string $key the param identifier
* @param mixed $overwriteValue if not null set overwriteValue begore get html
* (IMPORTANT: the stored param value don't change. To change it use setValue.)
* @param bool $echo true echo else return string
*
* @return bool|string return false if the item kay isn't a instance of ParamForm
*/
public function getHtmlFormParam($key, $overwriteValue = null, $echo = true)
{
if (!isset($this->params[$key])) {
throw new \Exception('Param key ' . Log::v2str($key) . 'don\' exists');
}
if (!($this->params[$key] instanceof ParamForm)) {
return false;
}
if (is_null($overwriteValue)) {
return $this->params[$key]->getHtml($echo);
} else {
$tmpParam = clone $this->params[$key];
if ($tmpParam->setValue($overwriteValue) === false) {
throw new \Exception('Can\'t set overwriteValue ' . Log::v2str($overwriteValue) . ' in param:' . $tmpParam->getName());
}
return $tmpParam->getHtml($echo);
}
}
/**
* Load params from persistance files
*
* @param boolean $reset if true reset params
*
* @return boolean
*/
public function load($reset = false): bool
{
if ($reset) {
$this->resetParams();
$this->initParamsOverwrite();
return true;
} else {
if (($json = SnapIO::safeFileGetContents(self::getPersistanceFilePath())) === false) {
return false;
}
$this->paramsHtmlInfo[] = '***** LOAD PARAMS FROM PERSISTENCE FILE';
$arrayData = json_decode($json, true);
if ($this->setParamsValues($arrayData) === false) {
throw new \Exception('Can\'t set params from persistence file ' . Log::v2str(self::getPersistanceFilePath()));
}
return true;
}
}
/**
* Remove persistance file and all params and reinit all
*
* @return boolean
*/
protected function resetParams(): bool
{
$this->paramsHtmlInfo[] = '***** RESET PARAMS';
SnapIO::rm(self::getPersistanceFilePath());
$this->params = [];
self::$initialized = false;
return $this->initParams();
}
/**
* Ovrewrite params from sources
*
* @return boolean
*/
public function initParamsOverwrite(): bool
{
Log::info('OVERWRITE PARAMS');
$this->paramsHtmlInfo[] = '***** LOAD OVERWRITE INFO';
/**
* @todo temp disabled require major study
* if (isset($_ENV[self::ENV_PARAMS_KEY])) {
* $this->paramsHtmlInfo[] = 'LOAD FROM ENV VARS';
$arrayData = json_decode($_ENV[self::ENV_PARAMS_KEY]);
$this->setParamsValues($arrayData, true);
} */
// LOAD PARAMS FROM PACKAGE OVERWRITE
$arrayData = (array) \DUPX_ArchiveConfig::getInstance()->overwriteInstallerParams;
if (!empty($arrayData)) {
$this->paramsHtmlInfo[] = '***** LOAD FROM PACKAGE OVERWRITE';
Log::info(' *** FROM PACKAGE');
if ($this->setParamsValues($arrayData, true, Log::LV_DEFAULT) === false) {
throw new \Exception('Can\'t set params from package overwrite ');
}
}
// LOAD PARAMS FROM LOCAL OVERWRITE
$localOverwritePath = DUPX_ROOT . '/' . self::LOCAL_OVERWRITE_PARAMS . self::LOCAL_OVERWRITE_PARAMS_EXTENSION;
if (is_readable($localOverwritePath)) {
// json file is set in $localOverwritePath php file
/** @var ?string $json */
$json = null;
include($localOverwritePath);
if (empty($json)) {
Log::info('LOCAL OVERWRITE PARAMS FILE ISN\'T WELL FORMED');
} else {
$arrayData = json_decode($json, true);
if (!empty($arrayData)) {
$this->paramsHtmlInfo[] = '***** LOAD FROM LOCAL OVERWRITE';
Log::info(' *** FROM LOCAL FILE');
if ($this->setParamsValues($arrayData, true, Log::LV_DEFAULT) === false) {
throw new \Exception('Can\'t set params from local overwrite ');
}
}
}
}
// LOAD PARAMS FROM LOCAL OVERWRITE PACKAGE_HASH
// Try new prefix first, fallback to legacy prefix for packages created by older Duplicator Pro versions
$localOverwritePath = DUPX_ROOT . '/' . self::LOCAL_OVERWRITE_PARAMS . '_' . Bootstrap::getPackageHash() . '.json';
if (!is_readable($localOverwritePath)) {
$localOverwritePath = DUPX_ROOT . '/' . self::LOCAL_OVERWRITE_PARAMS_LEGACY . '_' . Bootstrap::getPackageHash() . '.json';
}
if (is_readable($localOverwritePath)) {
if (($json = file_get_contents($localOverwritePath)) === false) {
Log::info('CAN\'T READ LOCAL OVERWRITE PARAM HASH FILE');
} else {
$arrayData = json_decode($json, true);
if (!empty($arrayData)) {
$this->paramsHtmlInfo[] = '***** LOAD FROM LOCAL OVERWRITE HASH';
Log::info(' *** FROM LOCAL FILE');
if ($this->setParamsValues($arrayData, true, Log::LV_DEFAULT) === false) {
throw new \Exception('Can\'t set params from local overwrite ');
}
}
}
}
HooksMng::getInstance()->doAction('after_params_overwrite', $this->params);
Log::info("********************************************************************************");
return true;
}
/**
* Update params values from arrayData
*
* @param array<string, mixed> $arrayData params data
* @param boolean $overwrite if true overwrite status
* @param integer $logginLevelSet log level
*
* @return bool returns false if a parameter has not been set
*/
protected function setParamsValues($arrayData, $overwrite = false, $logginLevelSet = Log::LV_DEBUG)
{
if (!is_array($arrayData)) {
throw new \Exception('Invalid data params ');
}
$result = true;
foreach ($arrayData as $key => $arrayValues) {
if (isset($this->params[$key])) {
$arrayValues = (array) $arrayValues;
$arrayValValToStr = array_map([Log::class, 'v2str'], $arrayValues);
$this->paramsHtmlInfo[] = 'SET PARAM <b>' . $key . '</b> ARRAY DATA: ' .
SnapString::implodeKeyVals(', ', $arrayValValToStr, '[<b>%s</b> = %s]');
if ($this->params[$key]->fromArrayData($arrayValues) === false) {
Log::info('PARAM ISSUE SET KEY[' . $key . '] VALUE: ' . SnapString::implodeKeyVals(', ', $arrayValValToStr, '[%s = %s]'));
// phpcs:ignore PHPCompatibility.FunctionUse.ArgumentFunctionsReportCurrentValue.NeedsInspection
Log::info(Log::traceToString(debug_backtrace()));
// $result = false;
} else {
$log = 'PARAM SET KEY[' . $key . ']';
$log .= (Log::isLevel(Log::LV_DEBUG) ? (' VALUE: ' . SnapString::implodeKeyVals(', ', $arrayValValToStr, '[%s = %s]')) : '');
Log::info($log, $logginLevelSet);
if ($overwrite) {
$this->params[$key]->setOveriteStatus();
}
}
}
}
return $result;
}
/**
* update persistance file
*
* @return bool|int // This function returns the number of bytes that were written to the file, or FALSE on failure.
*/
public function save()
{
Log::info("SAVE PARAMS\n" . Log::traceToString(debug_backtrace(), 0, 1), Log::LV_DEBUG);
$arrayData = [];
foreach ($this->params as $param) {
if ($param->isPersistent()) {
$arrayData[$param->getName()] = $param->toArrayData();
}
}
$json = SnapJson::jsonEncodePPrint($arrayData);
if (($result = file_put_contents(self::getPersistanceFilePath(), $json, LOCK_EX)) === false) {
Log::info('PRAMS: can\'t save persistence file');
}
return $result;
}
/**
*
* @return string
*/
protected static function getPersistanceFilePath()
{
static $path = null;
if (is_null($path)) {
$path = DUPX_INIT . '/' . InstDescMng::getInstance()->getName(InstDescMng::TYPE_PARAMS);
}
return $path;
}
/**
* html params info for debug params
*
* @return void
*/
public function getParamsHtmlInfo(): void
{
if (!$this->getValue(self::PARAM_DEBUG_PARAMS)) {
return;
}
?>
<div id="params-html-info">
<h3>CURRENT VALUES</h3>
<ul class="values">
<?php foreach ($this->params as $param) { ?>
<li>
PARAM <b><?php echo $param->getName(); ?></b> VALUE: <b><?php echo htmlentities(Log::v2str($param->getValue())); ?></b>
</li>
<?php } ?>
</ul>
<h3>LOAD SEQUENCE</h3>
<ul class="load-sequence">
<?php foreach ($this->paramsHtmlInfo as $info) { ?>
<li>
<?php echo $info; ?>
</li>
<?php } ?>
</ul>
<h3>ARCHIVE PARAM DATA</h3>
<pre><?php
$data = \DUPX_ArchiveConfig::getInstance();
var_dump($data);
?></pre>
</div>
<?php
}
/**
* Get params value list for log
*
* @return string
*/
public function getParamsToText(): string
{
$result = [];
foreach ($this->params as $param) {
if (method_exists($param, 'getFormStatus')) {
$line = 'PARAM FORM ' . $param->getName() . ' VALUE: ' . Log::v2str($param->getValue()) . ' STATUS: ' . $param->getFormStatus();
} else {
$line = 'PARAM ITEM ' . $param->getName() . ' VALUE: ' . Log::v2str($param->getValue());
}
$result[] = $line;
}
return implode("\n", $result);
}
/**
* Prevent clone object
*
* @return void
*/
private function __clone()
{
}
}

View File

@@ -0,0 +1,397 @@
<?php
/**
* Interface that collects the functions of initial duplicator Bootstrap
*
* @package Duplicator
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Core;
use Duplicator\Installer\Bootstrap\BootstrapRunner;
use Duplicator\Installer\Utils\Log\Log;
use Duplicator\Installer\Core\Bootstrap;
use Duplicator\Installer\Core\Descriptors\ArchiveConfig;
use Duplicator\Installer\Core\Params\PrmMng;
use Duplicator\Installer\Package\ArchiveDescriptor;
use Duplicator\Installer\Utils\SecureCsrf;
use Duplicator\Libs\Snap\SnapIO;
use Duplicator\Libs\Snap\SnapUtil;
use DUPX_ArchiveConfig;
use DUPX_Ctrl_ajax;
use Duplicator\Installer\Core\InstState;
use Exception;
/**
* singleton class
*
* In this class all installer security checks are performed. If the security checks are not passed, an exception is thrown and the installer is stopped.
* This happens before anything else so the class must work without the initialization of all global duplicator variables.
*/
class Security
{
const CTRL_TOKEN = 'ctrl_csrf_token';
const ROUTER_TOKEN = 'router_csrf_token';
const SECURITY_NONE = 'none';
const SECURITY_PASSWORD = 'pwd';
const SECURITY_ARCHIVE = 'archive';
/** @var ?self */
private static $instance;
/** @var string read from from csrf file */
private $archivePath;
/** @var string read from from csrf file */
private $bootloader;
/** @var string read from from csrf file */
private $bootUrl;
/** @var string read from from csrf file */
private $bootFilePath;
/** @var string read from from csrf file */
private $bootLogFile;
/** @var string read from from csrf file */
private $packageHash;
/** @var string read from from csrf file */
private $secondaryPackageHash;
/** @var string read from from csrf file */
private $archivePwd = '';
/** @var bool read from csrf file */
private $isManualExtractFound = false;
/**
*
* @return self
*/
public static function getInstance()
{
if (is_null(self::$instance)) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Class contructor
*/
private function __construct()
{
SecureCsrf::init(DUPX_INIT, Bootstrap::getPackageHash());
if (!file_exists(SecureCsrf::getFilePath())) {
throw new Exception("CSRF FILE NOT FOUND\n"
. "Please, check webroot file permsission and dup-installer folder permission");
}
$this->bootloader = SecureCsrf::getVal('bootloader');
$this->bootUrl = SecureCsrf::getVal('booturl');
$this->bootLogFile = SnapIO::safePath(SecureCsrf::getVal('bootLogFile'));
$this->bootFilePath = SnapIO::safePath(SecureCsrf::getVal('installerOrigPath'));
$this->archivePath = SnapIO::safePath(SecureCsrf::getVal('archive'));
$this->archivePwd = SecureCsrf::getVal(BootstrapRunner::CSRF_KEY_ARCHIVE_PASSWORD);
$this->packageHash = SecureCsrf::getVal('package_hash');
$this->secondaryPackageHash = SecureCsrf::getVal('secondaryHash');
$this->isManualExtractFound = SecureCsrf::getVal('isManualExtractFound');
}
/**
* archive path read from intaller.php passed by DUPX_CSFR
*
* @return string
*/
public function getArchivePath()
{
return $this->archivePath;
}
/**
* installer full path read from intaller.php passed by DUPX_CSFR
*
* @return string
*/
public function getBootFilePath()
{
return $this->bootFilePath;
}
/**
* boot log file full path read from intaller.php passed by DUPX_CSFR
*
* @return string
*/
public function getBootLogFile()
{
return $this->bootLogFile;
}
/**
* bootloader path read from intaller.php passed by DUPX_CSFR
*
* @return string
*/
public function getBootloader()
{
return $this->bootloader;
}
/**
* bootloader path read from intaller.php passed by DUPX_CSFR
*
* @return string
*/
public function getBootUrl()
{
return $this->bootUrl;
}
/**
* package hash read from intaller.php passed by DUPX_CSFR
*
* @return string
*/
public function getPackageHash()
{
return $this->packageHash;
}
/**
* Get archvie password
*
* @return string
*/
public function getArchivePassword()
{
return $this->archivePwd;
}
/**
* package public hash read from intaller.php passed by DUPX_CSFR
*
* @return string
*/
public function getSecondaryPackageHash()
{
return $this->secondaryPackageHash;
}
/**
* Get original installer URL, used if installer is called from mu-plugin
*
* @return string
*/
public function getOriginalInstallerUrl()
{
return SecureCsrf::getVal('originalDupInstallerUrl');
}
/**
*
* @return boolean
* @throws Exception // if fail throw exception of return true
*/
public function check(): bool
{
try {
// check if current package hash is equal at bootloader package hash
if ($this->packageHash !== Bootstrap::getPackageHash()) {
throw new Exception('Incorrect hash package');
}
// checks if the version of the package descriptor is consistent with the version of the files.
if (DUPX_ArchiveConfig::getInstance()->version_dup !== DUPX_VERSION) {
throw new Exception('The version of the archive is different from the version of the PHP scripts');
}
$token_tested = false;
$debug = self::isDebug();
$action = null;
if (DUPX_Ctrl_ajax::isAjax($action) == true) {
if (($token = self::getTokenFromInput(DUPX_Ctrl_ajax::TOKEN_NAME)) === false) {
$msg = 'Security issue' . ($debug ? ' LINE: ' . __LINE__ . ' TOKEN: ' . $token . ' KEY NAME: ' . DUPX_Ctrl_ajax::TOKEN_NAME : '');
throw new Exception($msg);
}
if (!SecureCsrf::check(self::getTokenFromInput(DUPX_Ctrl_ajax::TOKEN_NAME), DUPX_Ctrl_ajax::getTokenKeyByAction($action))) {
$msg = 'Security issue';
if ($debug) {
$msg .= ' LINE: ' . __LINE__ .
' TOKEN: ' . $token .
' KEY NAME: ' . DUPX_Ctrl_ajax::getTokenKeyByAction($action) .
' KEY VALUE ' . DUPX_Ctrl_ajax::getTokenKeyByAction($action);
}
throw new Exception($msg);
}
$token_tested = true;
} elseif (($token = self::getTokenFromInput(self::CTRL_TOKEN)) !== false) {
if (!isset($_REQUEST[PrmMng::PARAM_CTRL_ACTION])) {
$msg = 'Security issue' . ($debug ? ' LINE: ' . __LINE__ . ' TOKEN: ' . $token . ' KEY NAME: ' . PrmMng::PARAM_CTRL_ACTION : '');
throw new Exception($msg);
}
if (!SecureCsrf::check($token, $_REQUEST[PrmMng::PARAM_CTRL_ACTION])) {
$msg = 'Security issue';
if ($debug) {
$msg .= ' LINE: ' . __LINE__ .
' TOKEN: ' . $token .
' KEY NAME: ' . PrmMng::PARAM_CTRL_ACTION .
' KEY VALUE ' . $_REQUEST[PrmMng::PARAM_CTRL_ACTION];
}
throw new Exception($msg);
}
$token_tested = true;
}
if (($token = self::getTokenFromInput(self::ROUTER_TOKEN)) !== false) {
if (!isset($_REQUEST[PrmMng::PARAM_ROUTER_ACTION])) {
$msg = 'Security issue' . ($debug ? ' LINE: ' . __LINE__ . ' TOKEN: ' . $token . ' KEY NAME: ' . PrmMng::PARAM_ROUTER_ACTION : '');
throw new Exception($msg);
}
if (!SecureCsrf::check($token, $_REQUEST[PrmMng::PARAM_ROUTER_ACTION])) {
$msg = 'Security issue';
if ($debug) {
$msg .= ' LINE: ' . __LINE__ .
' TOKEN: ' . $token .
' KEY NAME: ' . PrmMng::PARAM_ROUTER_ACTION .
' KEY VALUE ' . $_REQUEST[PrmMng::PARAM_ROUTER_ACTION];
}
throw new Exception($msg);
}
$token_tested = true;
}
// At least one token must always and in any case be tested
if (!$token_tested) {
throw new Exception('Security Check Validation - No Token Found');
}
} catch (Exception $e) {
if (function_exists('error_clear_last')) {
// comment error_clear_last if you want see te exception html on shutdown
error_clear_last(); // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.error_clear_lastFound
}
Log::logException($e, Log::LV_DEFAULT, 'SECURITY CHECK: ');
dupxTplRender('page-security-error', [
'message' => $e->getMessage(),
]);
die();
}
return true;
}
/**
* Return tru if is debug
*
* @return bool
*/
protected static function isDebug(): bool
{
/** @todo connect with global debug */
return false;
}
/**
* Get sanitized token frominput
*
* @param string $tokenName token name
*
* @return false|string get token or false if don't exists
*/
protected static function getTokenFromInput($tokenName)
{
return SnapUtil::sanitizeDefaultInput(SnapUtil::INPUT_REQUEST, $tokenName, false);
}
/**
* Get security tipe (NONE, PASSWORD, ARCHIVE)
*
* @return string enum type
*/
public function getSecurityType(): string
{
if (PrmMng::getInstance()->getValue(PrmMng::PARAM_SECURE_OK) == true) {
return self::SECURITY_NONE;
}
$archiveConfig = DUPX_ArchiveConfig::getInstance();
if ($this->isManualExtractFound && $archiveConfig->secure_on === ArchiveDescriptor::SECURE_MODE_ARC_ENCRYPT) {
$archiveConfig->secure_on = ArchiveDescriptor::SECURE_MODE_INST_PWD;
}
if ($archiveConfig->secure_on === ArchiveDescriptor::SECURE_MODE_INST_PWD) {
return self::SECURITY_PASSWORD;
}
if (
strlen($this->archivePwd) == 0 &&
InstState::isOverwrite() &&
basename($this->bootFilePath) == 'installer.php' &&
!in_array($_SERVER['REMOTE_ADDR'], self::getSecurityAddrWhitelist())
) {
return self::SECURITY_ARCHIVE;
}
return self::SECURITY_NONE;
}
/**
* Get IPs white list for remote requests
*
* @return string[]
*/
private static function getSecurityAddrWhitelist(): array
{
// uncomment this to test security archive on localhost
// return array();
// -------
return [
'127.0.0.1',
'::1',
];
}
/**
* return true if security check is passed
*
* @return bool
*/
public function securityCheck()
{
$paramsManager = PrmMng::getInstance();
$archiveConfig = DUPX_ArchiveConfig::getInstance();
$result = false;
switch ($this->getSecurityType()) {
case self::SECURITY_NONE:
$result = true;
break;
case self::SECURITY_PASSWORD:
$paramsManager->setValueFromInput(PrmMng::PARAM_SECURE_PASS);
$inputPwd = $paramsManager->getValue(PrmMng::PARAM_SECURE_PASS);
$result = hash_equals($archiveConfig->secure_pass, self::passwordHash($inputPwd));
break;
case self::SECURITY_ARCHIVE:
$paramsManager->setValueFromInput(PrmMng::PARAM_SECURE_ARCHIVE_HASH);
$result = (strcmp(basename($this->archivePath), $paramsManager->getValue(PrmMng::PARAM_SECURE_ARCHIVE_HASH)) == 0);
break;
default:
throw new Exception('Security type not valid ' . $this->getSecurityType());
}
$paramsManager->setValue(PrmMng::PARAM_SECURE_OK, $result);
$paramsManager->save();
return $result;
}
/**
* Hash password
*
* @param string $password input password
*
* @return string Returns a string containing the calculated message digest as lowercase hexit
*/
public static function passwordHash($password): string
{
return hash('sha512', $password);
}
}

View File

@@ -0,0 +1,181 @@
<?php
namespace Duplicator\Installer\Models;
class ImportUser
{
protected int $id;
/** @var string */
protected $login = '';
/** @var string */
protected $mail = '';
protected int $oldId;
/** @var string */
protected $oldLogin = '';
/** @var bool */
protected $isAdded = false;
/**
* Class contructor
*
* @param int $id user id
* @param string $login user login
* @param string $mail user e-mail
* @param integer $oldId old user id, if 0 isn't changed
* @param string $oldLogin old user login, if empty isn't changed
* @param boolean $isAdded if true this is new user
*/
public function __construct($id, $login, $mail, $oldId = 0, $oldLogin = '', $isAdded = false)
{
$this->id = (int) $id;
$this->login = $login;
$this->mail = $mail;
$this->oldId = (int) $oldId;
$this->oldLogin = $oldLogin;
$this->isAdded = $isAdded;
if ($this->id == $this->oldId) {
$this->oldId = 0;
}
if ($this->login == $this->oldLogin) {
$this->oldLogin = '';
}
}
/**
* Return CSV report columns title
*
* @return string[]
*/
public static function getArrayReportTitles(): array
{
return [
'e-mail',
'original login',
'new login',
'original id',
'new id',
];
}
/**
* Return array for CSV report
*
* @return array<int, mixed>
*/
public function getArrayReport(): array
{
$result = [$this->mail];
if (strlen($this->oldLogin) == 0) {
$result[] = $this->login;
$result[] = '';
} else {
$result[] = $this->oldLogin;
$result[] = $this->login;
}
if ($this->oldId == 0) {
$result[] = $this->id;
$result[] = '';
} else {
$result[] = $this->oldId;
$result[] = $this->id;
}
return $result;
}
/**
* Get the value of id
*
* @return int
*/
public function getId(): int
{
return $this->id;
}
/**
* Get the value of login
*
* @return string
*/
public function getLogin()
{
return $this->login;
}
/**
* Get the value of mail
*
* @return string
*/
public function getMail()
{
return $this->mail;
}
/**
* Get the value of oldId
*
* @return int
*/
public function getOldId(): int
{
return ($this->oldId == 0 ? $this->id : $this->oldId);
}
/**
* Set the value of oldId
*
* @param int $oldId old mapped id
*
* @return void
*/
public function setOldId($oldId): void
{
$this->oldId = (int) ($this->id == $oldId ? 0 : $oldId);
}
/**
* Get the value of oldLogin
*
* @return string
*/
public function getOldLogin()
{
return (strlen($this->oldLogin) == 0 ? $this->login : $this->oldLogin);
}
/**
* Set the value of oldLogin
*
* @param string $oldLogin old login
*
* @return void
*/
public function setOldLogin($oldLogin): void
{
$this->oldLogin = ($this->login == $oldLogin ? '' : $oldLogin);
}
/**
* True if current user have changed values (login or id)
*
* @return boolean
*/
public function isChanged(): bool
{
return ($this->oldId > 0 || strlen($this->oldLogin) > 0);
}
/**
* True if current user is added
*
* @return bool
*/
public function isAdded()
{
return $this->isAdded;
}
}

View File

@@ -0,0 +1,84 @@
<?php
namespace Duplicator\Installer\Models;
use Duplicator\Installer\Core\InstState;
class MigrateData
{
/** @var string */
public $plugin = 'dup-pro';
/** @var string */
public $installerVersion = '';
/** @var int */
public $installType = InstState::TYPE_NOT_SET;
/** @var string */
public $installTime = '';
/** @var string[] */
public $logicModes = [];
/** @var string */
public $template = '';
/** @var bool */
public $restoreBackupMode = false;
/** @var bool */
public $recoveryMode = false;
/** @var string */
public $archivePath = '';
/** @var int */
public $packageId = 0;
/** @var string */
public $packageHash = '';
/** @var string */
public $installerPath = '';
/** @var string */
public $installerBootLog = '';
/** @var string */
public $installerLog = '';
/** @var string */
public $dupInstallerPath = '';
/** @var string */
public $origFileFolderPath = '';
/** @var bool */
public $safeMode = false;
/** @var bool */
public $cleanInstallerFiles = false;
/** @var int */
public $licenseType = -1;
/** @var string */
public $phpVersion = '';
/** @var string */
public $archiveType = '';
/** @var float */
public $siteSize = 0.0;
/** @var int */
public $siteNumFiles = 0;
/** @var float */
public $siteDbSize = 0.0;
/** @var int */
public $siteDBNumTables = 0;
/** @var string[] */
public $components = [];
/** @var string */
public $ustatIdentifier = '';
/** @var bool */
public $stagingMode = false;
/** @var string */
public $mainSiteUrl = '';
/** @var string */
public $stagingPageUrl = '';
/** @var string */
public $stagingIdentifier = '';
/** @var string */
public $colorScheme = 'fresh';
/** @var string */
public $stagingTitle = '';
/** @var array{enabled: bool, mainSiteUrl: string, pageUrl: string, identifier: string, colorScheme: string, title: string} */
public $staging = [
'enabled' => false,
'mainSiteUrl' => '',
'pageUrl' => '',
'identifier' => '',
'colorScheme' => 'fresh',
'title' => '',
];
}

View File

@@ -0,0 +1,75 @@
<?php
/**
* @package Duplicator
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Models;
use DUPX_Package;
use Exception;
/**
* Package scan info
*/
final class ScanInfo
{
/** @var array<string, mixed> */
private $data = [];
/** @var ?self */
private static $instance;
/**
* Get instance
*
* @return self
*/
public static function getInstance()
{
if (is_null(self::$instance)) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Singleton class constructor
*/
private function __construct()
{
$scanFile = DUPX_Package::getScanJsonPath();
if (!file_exists($scanFile)) {
throw new Exception("Archive file $scanFile doesn't exist");
}
if (($contents = file_get_contents($scanFile)) === false) {
throw new Exception("Can\'t read Archive file $scanFile");
}
if (($this->data = json_decode($contents, true)) === null) {
throw new Exception("Can\'t decode archive json");
}
}
/**
* Get uncompressed size, -1 unknown
*
* @return int
*/
public function getUSize()
{
return $this->data['ARC']['Usize'] ?? -1;
}
/**
* Return true if package has filtered core folders
*
* @return bool
*/
public function hasFilteredCoreFolders()
{
return $this->data['ARC']['Status']['HasFilteredCoreFolders'];
}
}

View File

@@ -0,0 +1,149 @@
<?php
/**
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Package;
use Duplicator\Installer\Addons\ProBase\AbstractLicense;
class ArchiveDescriptor
{
const SECURE_MODE_NONE = 0;
const SECURE_MODE_INST_PWD = 1;
const SECURE_MODE_ARC_ENCRYPT = 2;
const GEN_FILE_REGEX_PATTERN = '/^(.+_[a-z0-9]{7,}_[0-9]{14})_.+\.(?:zip|daf|php|bak)$/';
const ARCHIVE_REGEX_PATTERN = '/^(.+_[a-z0-9]{7,}_[0-9]{14})_archive\.(?:zip|daf)$/';
const INSTALLER_REGEX_PATTERN = '/^(?:.+_[a-z0-9]{7,}_[0-9]{14}_)?installer(?:-backup)?\.(php|php\.bak)$/';
const NAME_PARTS_REGEX_PATTERN = '/^(.+)_([a-z0-9]{7,})_([0-9]{14})_.+\.(?:zip|daf|php|bak)$/';
/** @var string */
public $dup_type = 'pro';
/** @var string */
public $created = '';
/** @var string */
public $version_dup = '';
/** @var string */
public $version_wp = '';
/** @var string */
public $version_db = '';
/** @var string */
public $version_php = '';
/** @var string */
public $version_os = '';
/** @var string */
public $blogname = '';
/** @var bool */
public $exportOnlyDB = false;
/** @var int<0,2> */
public $secure_on = self::SECURE_MODE_NONE;
/** @var string */
public $secure_pass = '';
/** @var ?string */
public $dbhost;
/** @var ?string */
public $dbname;
/** @var ?string */
public $dbuser;
/** @var ?string */
public $cpnl_host;
/** @var ?string */
public $cpnl_user;
/** @var ?string */
public $cpnl_pass;
/** @var ?string */
public $cpnl_enable;
/** @var string */
public $wp_tableprefix = '';
/** @var int<0, 2> */
public $mu_mode = 0;
/** @var int<0, 2> */
public $mu_generation = 0;
/** @var string[] */
public $mu_siteadmins = [];
/** @var DescriptorSubsite[] */
public $subsites = [];
/** @var int */
public $main_site_id = 1;
/** @var bool */
public $mu_is_filtered = false;
/** @var int */
public $license_limit = 0;
/** @var int */
public $license_type = AbstractLicense::TYPE_UNLICENSED;
/** @var ?DescriptorDBInfo */
public $dbInfo;
/** @var ?DescriptorPackageInfo */
public $packInfo;
/** @var ?DescriptorFileInfo*/
public $fileInfo;
/** @var ?DescriptorWpInfo */
public $wpInfo;
/** @var int<-1,max> */
public $defaultStorageId = -1;
/** @var string[] */
public $components = [];
/** @var string[] */
public $opts_delete = [];
/** @var array<string, mixed> */
public $brand = [];
/** @var array<string, mixed> */
public $overwriteInstallerParams = [];
/**
* @var string
*
* @deprecated This option is no longer taken into account
*/
public $installer_base_name = '';
/** @var string */
public $installer_backup_name = '';
/** @var string */
public $package_name = '';
/** @var string */
public $package_hash = '';
/** @var string */
public $package_notes = '';
/**
* Check if the file path is have a valid archive file name
*
* @param string $filePath File path, accept full path or just file name
*
* @return bool
*/
public static function isArchiveFile($filePath)
{
if (empty($filePath)) {
return false;
}
$filePath = basename($filePath);
return preg_match(self::GEN_FILE_REGEX_PATTERN, $filePath) === 1;
}
/**
* Get archive name parts, name, hash, date, package hash or false if name is invalid
*
* @param string $filePath Archive or installer name path, accept full path or just file name
*
* @return array{name:string,hash:string,date:string,packageHash:string}|false
*/
public static function getArchiveNameParts($filePath)
{
if (empty($filePath)) {
return false;
}
$filePath = basename($filePath);
$matches = [];
if (preg_match(self::NAME_PARTS_REGEX_PATTERN, $filePath, $matches)) {
return [
'name' => $matches[1],
'hash' => $matches[2],
'date' => $matches[3],
'packageHash' => substr($matches[2], 0, 7) . '-' . substr($matches[3], -8),
];
}
return false;
}
}

View File

@@ -0,0 +1,56 @@
<?php
/**
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Package;
/**
* The database descriptor is used to store the database meta data
*/
class DescriptorDBInfo
{
/** @var string The SQL file was built with mysqldump or PHP */
public $buildMode = 'PHP';
/** @var string[] A unique list of all the charSet table types used in the database */
public $charSetList = [];
/** @var string[] A unique list of all the collation table types used in the database */
public $collationList = [];
/** @var string[] A unique list of all the engine types used in the database */
public $engineList = [];
/** @var bool Does any filtered table have an upper case character in it */
public $isTablesUpperCase = false;
/** @var int Value of the DB variable lower_case_table_names */
public $lowerCaseTableNames = 0;
/** @var bool Does the database name have any filtered characters in it */
public $isNameUpperCase = false;
/** @var string The real name of the database */
public $name = '';
/** @var int he full count of all tables in the database */
public $tablesBaseCount = 0;
/** @var int The count of tables after the tables filter has been applied */
public $tablesFinalCount = 0;
/** @var int The count of tables filtered programmatically for multi-site purposes */
public $muFilteredTableCount = 0;
/** @var int The number of rows from all filtered tables in the database */
public $tablesRowCount = 0;
/** @var int The estimated data size on disk from all filtered tables in the database */
public $tablesSizeOnDisk = 0;
/** @var DescriptorDBTableInfo[] */
public $tablesList = [];
/** @var string The database engine (MySQL/MariaDB/Percona) */
public $dbEngine = '';
/** @var string The simple numeric version number of the database server @exmaple: 5.5 */
public $version = '0';
/** @var string The full text version number of the database server @exmaple: 10.2 mariadb.org binary distribution */
public $versionComment = '';
/** @var int Number of VIEWs in the database */
public $viewCount = 0;
/** @var int Number of PROCEDUREs in the database */
public $procCount = 0;
/** @var int Number of PROCEDUREs in the database */
public $funcCount = 0;
/** @var array<string, array{event: string, table: string, timing: string, create: string}> Trigger information */
public $triggerList = [];
}

View File

@@ -0,0 +1,34 @@
<?php
/**
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Package;
/**
* The database table descriptor
*/
class DescriptorDBTableInfo
{
/** @var int<0, max> */
public $inaccurateRows = 0;
/** @var false|int<0, max> */
public $insertedRows = 0;
/** @var int<0, max> */
public $size = 0;
/**
* Classs constructor
*
* @param int<0, max> $inaccurateRows This data is intended as a preliminary count and therefore not necessarily accurate
* @param int<0, max> $size This data is intended as a preliminary count and therefore not necessarily accurate
* @param false|int<0, max> $insertedRows This value, if other than false, is the exact line value inserted into the dump file
*/
public function __construct($inaccurateRows = 0, $size = 0, $insertedRows = false)
{
$this->inaccurateRows = $inaccurateRows;
$this->insertedRows = (int) $insertedRows;
$this->size = (int) $size;
}
}

View File

@@ -0,0 +1,20 @@
<?php
/**
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Package;
/**
* The file descriptor is used to store the file meta data
*/
class DescriptorFileInfo
{
/** @var int */
public $dirCount = 0;
/** @var int */
public $fileCount = 0;
/** @var int */
public $size = 0;
}

View File

@@ -0,0 +1,19 @@
<?php
/**
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Package;
class DescriptorPackageInfo
{
/** @var int */
public $packageId = 0;
/** @var string */
public $packageName = '';
/** @var string */
public $packageHash = '';
/** @var string */
public $secondaryHash = '';
}

View File

@@ -0,0 +1,79 @@
<?php
/**
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Package;
/**
* Plugin descriptor
*/
class DescriptorPlugin
{
/** @var string */
public $slug = '';
/** @var string */
public $name = '';
/** @var string */
public $version = '';
/** @var string */
public $pluginURI = '';
/** @var string */
public $author = '';
/** @var string */
public $authorURI = '';
/** @var string */
public $description = '';
/** @var string */
public $title = '';
/** @var bool */
public $networkActive = false;
/** @var bool|int[] list of subsites ids if is multisite */
public $active = false;
/** @var bool */
public $mustUse = false;
/** @var bool */
public $dropIns = false;
/** @var bool */
public $isInArchive = true;
const DEFAULT_DATA = [
'slug' => '',
'name' => '',
'version' => '',
'pluginURI' => '',
'author' => '',
'authorURI' => '',
'description' => '',
'title' => '',
'networkActive' => false,
'active' => false,
'mustUse' => false,
'dropIns' => false,
'isInArchive' => true,
];
/**
* Class constructor
*
* @param array<string, mixed> $pluginData plugin info data
*/
public function __construct(array $pluginData = [])
{
$data = array_merge(self::DEFAULT_DATA, $pluginData);
$this->slug = $data['slug'];
$this->name = $data['name'];
$this->version = $data['version'];
$this->pluginURI = $data['pluginURI'];
$this->author = $data['author'];
$this->authorURI = $data['authorURI'];
$this->description = $data['description'];
$this->title = $data['title'];
$this->networkActive = $data['networkActive'];
$this->active = $data['active'];
$this->mustUse = $data['mustUse'];
$this->dropIns = $data['dropIns'];
$this->isInArchive = $data['isInArchive'];
}
}

View File

@@ -0,0 +1,42 @@
<?php
/**
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Package;
/**
* Subsite descriptor
*/
class DescriptorSubsite
{
/** @var int */
public $id = 0;
/** @var string */
public $domain = '';
/** @var string */
public $path = '';
/** @var string */
public $blogname = '';
/** @var string */
public $blog_prefix = '';
/** @var string[] */
public $filteredTables = [];
/** @var array<object{ID: int, user_login: string}> */
public $adminUsers = [];
/** @var string */
public $fullHomeUrl = '';
/** @var string */
public $fullSiteUrl = '';
/** @var string */
public $uploadPath = '';
/** @var string */
public $fullUploadPath = '';
/** @var string */
public $fullUploadSafePath = '';
/** @var string */
public $fullUploadUrl = '';
/** @var string[] */
public $filteredPaths = [];
}

View File

@@ -0,0 +1,83 @@
<?php
/**
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Package;
/**
* Theme descriptor
*/
class DescriptorTheme
{
/** @var string */
public $slug = '';
/** @var string */
public $themeName = '';
/** @var string */
public $version = '';
/** @var string */
public $themeURI = '';
/** @var string */
public $parentTheme = '';
/** @var string */
public $template = '';
/** @var string */
public $stylesheet = '';
/** @var string */
public $description = '';
/** @var string */
public $author = '';
/** @var string */
public $authorURI = '';
/** @var string[] */
public $tags = [];
/** @var bool */
public $isAllowed = false;
/** @var bool|int[] list of subsites ids if is multisite */
public $isActive = false;
/** @var bool */
public $defaultTheme = false;
const DEFAULT_DATA = [
'slug' => '',
'themeName' => '',
'version' => '',
'themeURI' => '',
'parentTheme' => '',
'template' => '',
'stylesheet' => '',
'description' => '',
'author' => '',
'authorURI' => '',
'tags' => [],
'isAllowed' => false,
'isActive' => false,
'defaultTheme' => false,
];
/**
* Class constructor
*
* @param array<string, mixed> $themeData theme info data
*/
public function __construct($themeData = [])
{
$data = array_merge(self::DEFAULT_DATA, $themeData);
$this->slug = $data['slug'];
$this->themeName = $data['themeName'];
$this->version = $data['version'];
$this->themeURI = $data['themeURI'];
$this->parentTheme = $data['parentTheme'];
$this->template = $data['template'];
$this->stylesheet = $data['stylesheet'];
$this->description = $data['description'];
$this->author = $data['author'];
$this->authorURI = $data['authorURI'];
$this->tags = $data['tags'];
$this->isAllowed = $data['isAllowed'];
$this->isActive = $data['isActive'];
$this->defaultTheme = $data['defaultTheme'];
}
}

View File

@@ -0,0 +1,44 @@
<?php
/**
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Package;
use stdClass;
/**
* The wp descriptor is used to store the wp meta data
*/
class DescriptorWpInfo
{
/** @var string */
public $version = '';
/** @var bool */
public $is_multisite = false;
/** @var int */
public $network_id = 1;
/** @var string */
public $targetRoot = '';
/** @var string[] */
public $targetPaths = [];
/** @var array<object{ID: int, user_login: string}> */
public $adminUsers = [];
/** @var ?stdClass */
public $configs;
/** @var DescriptorPlugin[] */
public $plugins = [];
/** @var DescriptorTheme[] */
public $themes = [];
/**
* Class constructor.
*/
public function __construct()
{
$this->configs = new stdClass();
$this->configs->defines = new stdClass();
$this->configs->realValues = new stdClass();
}
}

View File

@@ -0,0 +1,195 @@
<?php
/**
*
* @package Duplicator
* @copyright (c) 2024, Snap Creek LLC
*/
namespace Duplicator\Installer\Package;
class InstallerDescriptors
{
const FIRST_VERSION_NEW_ARCHIVE_FILES = '4.5.20';
const DESCRIPTORS_FOLDER_PREFIX = 'dup_descriptors_';
const TYPE_ORIG_FILES = 'orig_files';
const TYPE_ARCHIVE_CONFIG = 'archive_config';
const TYPE_SCAN = 'scan';
const TYPE_INDEX = 'file_index';
const TYPE_FILE_LIST = 'file_list';
const TYPE_DIR_LIST = 'dir_list';
const TYPE_MANUAL_EXTRACT = 'manual_extract';
const TYPE_DB_DUMP = 'db_dump';
const TYPE_PARAMS = 'params';
const TYPE_INST_CHUNK_DATA = 'inst_chunk_data';
const TYPE_INST_NOTICES = 'inst_notices';
const TYPE_INST_DB_DATA = 'inst_db_data';
const TYPE_INST_DB_SEEK_TELL_LOG = 'inst_db_seek_log';
const TYPE_INST_EXTRACTION_DATA = 'inst_extraction_data';
const TYPE_INST_S3_DATA = 'inst_s3_data';
const TYPE_INST_PHP_ERROR_LOG = 'inst_php_error_log';
/** @var string The backup hash */
protected $hash = '';
/** @var string The backup date in YmdHis format */
protected $date = '';
/** @var array<string,string> Names mapping*/
protected $mapping = [];
/**
* Constructor
*
* @param string $hash The backup hash
* @param string $date The backup date in YmdHis format
*/
public function __construct($hash, $date)
{
if (empty($hash)) {
throw new \Exception('The hash is required');
}
if (empty($date)) {
throw new \Exception('The date is required');
}
$this->hash = $hash;
$this->date = $date;
$this->initMapping();
}
/**
* Returns the folder in which all descriptors are stored
*
* @return string
*/
public function getDescriptorsFolder(): string
{
return self::DESCRIPTORS_FOLDER_PREFIX . $this->hash . '/';
}
/**
* Init descriptors mapping
*
* @return void
*/
protected function initMapping()
{
$this->mapping = [
self::TYPE_ORIG_FILES => 'orig_files',
self::TYPE_ARCHIVE_CONFIG => 'archive.txt',
self::TYPE_SCAN => 'scan.json',
self::TYPE_FILE_LIST => 'scanned-files.txt',
self::TYPE_DIR_LIST => 'scanned-dirs.txt',
self::TYPE_INDEX => 'index.txt',
self::TYPE_MANUAL_EXTRACT => 'manual-extract',
self::TYPE_DB_DUMP => 'db_dumps/' . $this->date . '-dump.sql',
self::TYPE_INST_CHUNK_DATA => 'installer-chunk-data.json',
self::TYPE_INST_NOTICES => 'installer-notices.json',
self::TYPE_INST_DB_DATA => 'installer-db-data.json',
self::TYPE_INST_DB_SEEK_TELL_LOG => 'installer-db-seek-tell-log.txt',
self::TYPE_INST_EXTRACTION_DATA => 'installer-extraction.json',
self::TYPE_INST_S3_DATA => 'installer-s3data.json',
self::TYPE_INST_PHP_ERROR_LOG => 'php_error.log',
self::TYPE_PARAMS => 'params.json',
];
}
/**
* Get the file name of the descriptor file with hash
*
* @param string $descriptorType The archive file type
*
* @return string
*/
public function getName($descriptorType): string
{
if (!isset($this->mapping[$descriptorType])) {
throw new \Exception('Invalid descriptor type ' . $descriptorType);
}
return $this->getDescriptorsFolder() . $this->mapping[$descriptorType];
}
/**
* Get the file name of the descriptor file without hash or date
*
* @param string $descriptorType The archive file type
*
* @return string
*/
public function getGenericName($descriptorType): string
{
if (!isset($this->mapping[$descriptorType])) {
throw new \Exception('Invalid descriptor type ' . $descriptorType);
}
return str_replace(
[
$this->hash,
$this->date,
],
[
'[HASH]',
'[DATE]',
],
$this->getDescriptorsFolder() . $this->mapping[$descriptorType]
);
}
/**
* Get the file name of the descriptor file with hash
*
* @param string $descriptorType The archive file type
* @param string $hash (Optional) The hash of the archive
*
* @return string
*/
public function getOldName($descriptorType, $hash = ''): string
{
if (empty($hash)) {
$hash = $this->hash;
}
return self::getHashedOldName($descriptorType, $hash);
}
/**
* Get the location of the old archive files
*
* @param string $descriptorType The descriptor type
* @param string $hash The hash of the archive
*
* @return string
*/
protected static function getHashedOldName($descriptorType, $hash): string
{
$mapping = [
self::TYPE_ORIG_FILES => 'orig_files__' . $hash,
self::TYPE_ARCHIVE_CONFIG => 'dup-archive__' . $hash . '.txt',
self::TYPE_SCAN => 'dup-scan__' . $hash . '.json',
self::TYPE_FILE_LIST => 'dup-scanned-files__' . $hash . '.txt',
self::TYPE_DIR_LIST => 'dup-scanned-dirs__' . $hash . '.txt',
self::TYPE_MANUAL_EXTRACT => 'dup-manual-extract__' . $hash,
self::TYPE_DB_DUMP => 'dup-database__' . $hash . '.sql',
self::TYPE_INST_CHUNK_DATA => 'dup-installer-chunk__' . $hash . '.json',
self::TYPE_INST_NOTICES => 'dup-installer-notices__' . $hash . '.json',
self::TYPE_INST_DB_DATA => 'dup-installer-dbinstall__' . $hash . '.json',
self::TYPE_INST_DB_SEEK_TELL_LOG => 'dup-database-seek-tell-log__' . $hash . '.txt',
self::TYPE_INST_EXTRACTION_DATA => 'dup-installer-extraction__' . $hash . '.json',
self::TYPE_INST_S3_DATA => 'dup-installer-s3data__' . $hash . '.json',
self::TYPE_INST_PHP_ERROR_LOG => 'php_error__' . $hash . '.log',
self::TYPE_PARAMS => 'dup-params__' . $hash . '.json',
];
if (!isset($mapping[$descriptorType])) {
throw new \Exception('Invalid descriptor type');
}
return $mapping[$descriptorType];
}
}

View File

@@ -0,0 +1,51 @@
<?php
/**
*
* @package Duplicator
* @copyright (c) 2024, Snap Creek LLC
*/
namespace Duplicator\Installer\Package;
/**
* This class is used to manage old installer files previously version 4.5.20
*/
class LegacyInstallerDescriptors extends InstallerDescriptors
{
/**
* Init descriptors mapping
*
* @return void
*/
protected function initMapping()
{
$this->mapping = [
self::TYPE_ORIG_FILES => 'orig_files__' . $this->hash,
self::TYPE_ARCHIVE_CONFIG => 'dup-archive__' . $this->hash . '.txt',
self::TYPE_SCAN => 'dup-scan__' . $this->hash . '.json',
self::TYPE_FILE_LIST => 'dup-scanned-files__' . $this->hash . '.txt',
self::TYPE_DIR_LIST => 'dup-scanned-dirs__' . $this->hash . '.txt',
self::TYPE_MANUAL_EXTRACT => 'dup-manual-extract__' . $this->hash,
self::TYPE_DB_DUMP => 'dup-database__' . $this->hash . '.sql',
self::TYPE_INST_CHUNK_DATA => 'dup-installer-chunk__' . $this->hash . '.json',
self::TYPE_INST_NOTICES => 'dup-installer-notices__' . $this->hash . '.json',
self::TYPE_INST_DB_DATA => 'dup-installer-dbinstall__' . $this->hash . '.json',
self::TYPE_INST_DB_SEEK_TELL_LOG => 'dup-database-seek-tell-log__' . $this->hash . '.txt',
self::TYPE_INST_EXTRACTION_DATA => 'dup-installer-extraction__' . $this->hash . '.json',
self::TYPE_INST_S3_DATA => 'dup-installer-s3data__' . $this->hash . '.json',
self::TYPE_INST_PHP_ERROR_LOG => 'php_error__' . $this->hash . '.log',
self::TYPE_PARAMS => 'dup-params__' . $this->hash . '.json',
];
}
/**
* Returns the folder in which all descriptors are stored
*
* @return string
*/
public function getDescriptorsFolder(): string
{
return '';
}
}

View File

@@ -0,0 +1,149 @@
<?php
/**
*
* @package Duplicator
* @copyright (c) 2022, Snap Creek LLC
*/
namespace Duplicator\Installer\Package;
use Exception;
class PComponents
{
const COMP_DB = 'package_component_db';
const COMP_CORE = 'package_component_core';
const COMP_PLUGINS = 'package_component_plugins';
const COMP_PLUGINS_ACTIVE = 'package_component_plugins_active';
const COMP_THEMES = 'package_component_themes';
const COMP_THEMES_ACTIVE = 'package_component_themes_active';
const COMP_UPLOADS = 'package_component_uploads';
const COMP_OTHER = 'package_component_other';
const COMPONENTS = [
self::COMP_DB,
self::COMP_CORE,
self::COMP_PLUGINS,
self::COMP_PLUGINS_ACTIVE,
self::COMP_THEMES,
self::COMP_THEMES_ACTIVE,
self::COMP_UPLOADS,
self::COMP_OTHER,
];
const COMPONENTS_DEFAULT = [
self::COMP_DB,
self::COMP_CORE,
self::COMP_PLUGINS,
self::COMP_THEMES,
self::COMP_UPLOADS,
self::COMP_OTHER,
];
const SUB_OPTIONS = [
self::COMP_PLUGINS_ACTIVE,
self::COMP_THEMES_ACTIVE,
];
/**
* Get label by compoentent
*
* @param string $component The component
*
* @return string
*/
public static function getLabel($component): string
{
switch ($component) {
case self::COMP_DB:
return 'Database';
case self::COMP_CORE:
return 'Core';
case self::COMP_PLUGINS:
return 'Plugins';
case self::COMP_PLUGINS_ACTIVE:
return 'Only Active Plugins';
case self::COMP_THEMES:
return 'Themes';
case self::COMP_THEMES_ACTIVE:
return 'Only Active Themes';
case self::COMP_UPLOADS:
return 'Media';
case self::COMP_OTHER:
return 'Other';
default:
throw new Exception('Invalid component: ' . $component);
}
}
/**
* Returns the component labels imploded by seperator
*
* @param string[] $components array of components
* @param string $seperator the seperator string
*
* @return string
*/
public static function displayComponentsList($components, $seperator = ', '): string
{
return implode($seperator, array_map(fn($component): string => self::getLabel($component), $components));
}
/**
* Returns true if this is a DB only package
*
* @param string[] $components active components
*
* @return bool
*/
public static function isDBOnly($components): bool
{
return count($components) === 1 && in_array(self::COMP_DB, $components);
}
/**
* Returns true if this is a DB only package
*
* @param string[] $components active components
*
* @return bool
*/
public static function isMediaOnly($components): bool
{
return count($components) === 1 && in_array(self::COMP_UPLOADS, $components);
}
/**
* Returns true if the DB package component has been excluded
*
* @param string[] $activeComponents list of active components
*
* @return bool
*/
public static function isDBExcluded($activeComponents): bool
{
return !in_array(self::COMP_DB, $activeComponents);
}
/**
* Returns true if there are all files components
*
* @param string[] $components list of active components
*
* @return bool
*/
public static function isFullFilesComponents($components): bool
{
$result = array_diff(
[
self::COMP_CORE,
self::COMP_PLUGINS,
self::COMP_THEMES,
self::COMP_UPLOADS,
],
$components
);
return count($result) === 0;
}
}

View File

@@ -0,0 +1,70 @@
<?php
namespace Duplicator\Installer\REST;
use VendorDuplicator\WpOrg\Requests\Auth;
use VendorDuplicator\WpOrg\Requests\Auth\Basic;
use VendorDuplicator\WpOrg\Requests\Hooks;
class RESTAuth implements Auth
{
/** @var string */
protected $nonce = '';
/** @var string */
protected $basicAuthUser = '';
/** @var string */
protected $basicAuthPassword = '';
/**
* Class constructor
*
* @param string $nonce nonce user
* @param string $basicAuthUser auth user
* @param string $basicAuthPassword auth password
*/
public function __construct($nonce, $basicAuthUser = "", $basicAuthPassword = "")
{
$this->nonce = $nonce;
$this->basicAuthUser = $basicAuthUser;
$this->basicAuthPassword = $basicAuthPassword;
}
/**
* Register auth hooks
*
* @param Hooks $hooks hooks
*
* @return void
*/
public function register(Hooks $hooks): void
{
if (strlen($this->basicAuthUser) > 0) {
$basicAuth = new Basic([
$this->basicAuthUser,
$this->basicAuthPassword,
]);
$basicAuth->register($hooks);
}
$hooks->register('requests.before_request', [$this, 'beforeRequest']);
}
/**
* Before request hook
*
* @param string $url request URL
* @param mixed[] $headers headers
* @param mixed[] $data data
* @param string $type type
* @param mixed[] $options options
*
* @return void
*/
public function beforeRequest(&$url, &$headers, &$data, &$type, &$options): void
{
$data['_wpnonce'] = $this->nonce;
foreach ($_COOKIE as $key => $val) {
$options['cookies'][$key] = $val;
}
}
}

View File

@@ -0,0 +1,206 @@
<?php
namespace Duplicator\Installer\REST;
use Duplicator\Installer\Core\Params\PrmMng;
use Duplicator\Installer\Utils\Log\Log;
use Duplicator\Libs\Snap\SnapIO;
use Exception;
use VendorDuplicator\WpOrg\Requests\Requests;
use VendorDuplicator\WpOrg\Requests\Response;
class RESTPoints
{
const DUPLICATOR_NAMESPACE = 'duplicator/v1/';
/** @var string */
private $nonce = '';
/** @var string */
private $basicAuthUser = "";
/** @var string */
private $basicAuthPassword = "";
private string $url = '';
/** @var ?self */
private static $instance;
/**
* Get instance
*
* @return self
*/
public static function getInstance()
{
if (is_null(self::$instance)) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Class constructor
*/
private function __construct()
{
$paramsManager = PrmMng::getInstance();
$overwriteData = $paramsManager->getValue(PrmMng::PARAM_OVERWRITE_SITE_DATA);
if (is_array($overwriteData)) {
if (
isset($overwriteData['restUrl']) &&
strlen($overwriteData['restUrl']) > 0 &&
isset($overwriteData['restNonce']) &&
strlen($overwriteData['restNonce']) > 0
) {
$this->url = SnapIO::untrailingslashit($overwriteData['restUrl']);
$this->nonce = $overwriteData['restNonce'];
}
if (strlen($overwriteData['restAuthUser']) > 0) {
$this->basicAuthUser = $overwriteData['restAuthUser'];
$this->basicAuthPassword = $overwriteData['restAuthPassword'];
}
}
}
/**
* Check if REST is available
*
* @param bool $reset re-check
* @param string $errorMessage Error message
*
* @return bool
*/
public function checkRest($reset = false, &$errorMessage = "")
{
static $success = null;
if (is_null($success) || $reset) {
try {
$success = true;
if (strlen($this->nonce) == 0) {
throw new Exception("Nonce is not set.");
}
if (strlen($testUrl = $this->getRestUrl('versions')) === 0) {
throw new Exception("Couldn't get REST API backed URL to do tests. REST API URL was empty.");
}
$response = Requests::get($testUrl, [], $this->getRequestAuthOptions());
if ($response->success == false) {
Log::info(Log::v2str($response));
throw new Exception("REST API request on $testUrl failed");
}
if (($result = json_decode($response->body, true)) === null) {
throw new Exception("Can't decode json.");
}
if (!isset($result["dup"])) {
Log::info('RESPONSE BODY ' . Log::v2str($response->body));
throw new Exception("Did not receive the expected result.");
}
} catch (Exception $ex) {
$success = false;
$errorMessage = $ex->getMessage();
Log::info("FAILED REST API CHECK. MESSAGE: " . $ex->getMessage());
}
}
return $success;
}
/**
* Return wp and dup version
*
* @return false|string[] false on failure
*/
public function getVersions()
{
$response = Requests::get($this->getRestUrl('versions'), [], $this->getRequestAuthOptions());
if (!$response->success) {
return false;
}
if (($result = json_decode($response->body)) === null) {
return false;
}
return $result;
}
/**
* Create new subsites
*
* @param string $data data
* @param int $numSubisites subsites number
* @param string $errorMessage essromessage
*
* @return false|array<string, mixed> return subistes info or false on failure
*/
public function subsiteActions($data, $numSubisites, &$errorMessage = '')
{
if (Log::isLevel(Log::LV_DETAILED)) {
Log::info('SUBSITE ACTION CALL NUM SUBISTES ' . $numSubisites . ' DATA: ' . $data);
}
$options = $this->getRequestAuthOptions();
// ten seconds foreach subsite
$options['timeout'] = 10 * max(1, $numSubisites);
/** @var Response */
$response = Requests::post(
$this->getRestUrl('multisite/subsite/actions'),
[],
['data' => $data],
$options
);
if (($result = json_decode($response->body, true)) === null) {
Log::info('REST CALL: can\'t decode json ' . $response->body);
$errorMessage = 'REST CALL: can\'t decode json response';
return false;
}
if (!$response->success) {
Log::info('REST CALL FAIL REPONSE: ' . Log::v2str($response));
$errorMessage = $result['message'] ?? 'REST call fail, error code: ' . $response->status_code;
return false;
} elseif (Log::isLevel(Log::LV_DEBUG)) {
Log::info('REST CALL REPONSE: ' . Log::v2str($response));
}
if (!$result['success']) {
$errorMessage = $result['message'] ?? 'REST call fail, invalid reponse values';
return false;
}
return $result['subsitesInfo'];
}
/**
* Return request auth options
*
* @return array<string, mixed>
*/
private function getRequestAuthOptions(): array
{
return [
'auth' => new RESTAuth($this->nonce, $this->basicAuthUser, $this->basicAuthPassword),
'verify' => false,
'verifyname' => false,
];
}
/**
* Return RST URL
*
* @param string $subPath sub path
*
* @return string
*/
private function getRestUrl(string $subPath = ''): string
{
return $this->url ? $this->url . '/' . self::DUPLICATOR_NAMESPACE . $subPath : '';
}
}

View File

@@ -0,0 +1,158 @@
<?php
/**
* @package Duplicator\Installer
*/
namespace Duplicator\Installer\Utils;
/**
* Autoloader class
*/
final class Autoloader
{
const ROOT_NAMESPACE = 'Duplicator\\';
const ROOT_INSTALLER_NAMESPACE = self::ROOT_NAMESPACE . 'Installer\\';
const ROOT_ADDON_INSTALLER_NAMESPACE = self::ROOT_INSTALLER_NAMESPACE . 'Addons\\';
const ROOT_LIBS_NAMESPACE = self::ROOT_NAMESPACE . 'Libs\\';
const ROOT_VENDOR = 'VendorDuplicator\\';
const VENDOR_PATH = DUPX_INIT . '/vendor-prefixed/';
/**
* register autooader
*
* @return void
*/
public static function register(): void
{
spl_autoload_register([self::class, 'load']);
}
/**
*
* @param string $className class name
*
* @return void
*/
public static function load($className): void
{
if (strpos($className, self::ROOT_NAMESPACE) === 0) {
if (($filepath = self::getAddonFile($className)) === false) {
foreach (self::getNamespacesMapping() as $namespace => $mappedPath) {
if (strpos($className, (string) $namespace) !== 0) {
continue;
}
$filepath = self::getFilenameFromClass($className, $namespace, $mappedPath);
if (file_exists($filepath)) {
include $filepath;
return;
}
}
} else {
if (file_exists($filepath)) {
include $filepath;
return;
}
}
} elseif (strpos($className, self::ROOT_VENDOR) === 0) {
foreach (self::getNamespacesVendorMapping() as $namespace => $mappedPath) {
if (strpos($className, (string) $namespace) !== 0) {
continue;
}
$filepath = self::getFilenameFromClass($className, $namespace, $mappedPath);
if (file_exists($filepath)) {
include $filepath;
return;
}
}
}
}
/**
* Return PHP file full class from class name
*
* @param string $class Name of class
* @param string $namespace Base namespace
* @param string $mappedPath Base path
*
* @return string
*/
protected static function getFilenameFromClass($class, $namespace, $mappedPath): string
{
$subPath = str_replace('\\', '/', substr($class, strlen($namespace))) . '.php';
$subPath = ltrim($subPath, '/');
return rtrim($mappedPath, '\\/') . '/' . $subPath;
}
/**
*
* @param string $class class name
*
* @return boolean|string
*/
protected static function getAddonFile($class)
{
$matches = null;
if (preg_match('/^\\\\?Duplicator\\\\Installer\\\\Addons\\\\(.+?)\\\\(.+)$/', $class, $matches) !== 1) {
return false;
}
$addonName = $matches[1];
$subClass = $matches[2];
$basePath = DUPX_INIT . '/addons/' . strtolower($addonName) . '/';
if (self::endsWith($class, $addonName) === false) {
$basePath .= 'src/';
}
return $basePath . str_replace('\\', '/', $subClass) . '.php';
}
/**
* Return duplicator clases mapping
*
* @return string[]
*/
protected static function getNamespacesMapping(): array
{
// the order is important, it is necessary to insert the longest namespaces first
return [
self::ROOT_ADDON_INSTALLER_NAMESPACE => DUPX_INIT . '/addons/',
self::ROOT_INSTALLER_NAMESPACE => DUPX_INIT . '/src/',
self::ROOT_LIBS_NAMESPACE => DUPX_INIT . '/libs/',
];
}
/**
* Return namespace mapping
*
* @return string[]
*/
protected static function getNamespacesVendorMapping(): array
{
return [
self::ROOT_VENDOR . 'WpOrg\\Requests' => self::VENDOR_PATH . 'rmccue/requests/src',
self::ROOT_VENDOR . 'Amk\\JsonSerialize' => self::VENDOR_PATH . 'andreamk/jsonserialize/src/',
];
}
/**
* Returns true if the $haystack string end with the $needle, only for internal use
*
* @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
*/
protected static function endsWith($haystack, string $needle)
{
$length = strlen($needle);
if ($length == 0) {
return true;
}
return (substr($haystack, -$length) === $needle);
}
}

View File

@@ -0,0 +1,47 @@
<?php
/**
* @package Duplicator\Installer
*/
namespace Duplicator\Installer\Utils;
use Duplicator\Installer\Core\Security;
use Duplicator\Installer\Package\ArchiveDescriptor;
use Duplicator\Installer\Package\InstallerDescriptors;
/**
* Descriptors Manager class for installer
*
* singleton class
*/
final class InstDescMng extends InstallerDescriptors
{
/** @var ?self */
private static $instance;
/**
* Get instance
*
* @return self
*/
public static function getInstance()
{
if (is_null(self::$instance)) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Class constructor
*/
public function __construct()
{
if (($nameInfo = ArchiveDescriptor::getArchiveNameParts(Security::getInstance()->getArchivePath())) === false) {
throw new \Exception('PACKAGE ERROR: can\'t read archive name parts');
}
parent::__construct($nameInfo['packageHash'], $nameInfo['date']);
}
}

View File

@@ -0,0 +1,66 @@
<?php
/**
* @package Duplicator\Installer
*/
namespace Duplicator\Installer\Utils;
use Duplicator\Installer\Core\Bootstrap;
use Duplicator\Installer\Core\Params\PrmMng;
use Duplicator\Libs\Snap\SnapIO;
use Duplicator\Libs\Snap\SnapOrigFileManager;
/**
* Original installer files manager
*
* This class saves a file or folder in the original files folder and saves the original location persistant.
* By entry we mean a file or a folder but not the files contained within it.
* In this way it is possible, for example, to move an entire plugin to restore it later.
*
* singleton class
*/
final class InstallerOrigFileMng extends SnapOrigFileManager
{
/** @var ?self */
private static $instance;
/**
* Get instance
*
* @return self
*/
public static function getInstance()
{
if (is_null(self::$instance)) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Class constructor
* This class should be singleton, but unfortunately it is not possible to change the constructor in private with versions prior to PHP 7.2.
*/
public function __construct()
{
//Init Original File Manager
$packageHash = Bootstrap::getPackageHash();
$root = PrmMng::getInstance()->getValue(PrmMng::PARAM_PATH_NEW);
parent::__construct($root, DUPX_INIT, $packageHash);
}
/**
* Generate orig folder path
*
* @param string $origFolderParentPath orig files folder parent path
* @param string $hash package hash
*
* @return string
*/
protected function generateOrigFolderPath($origFolderParentPath, $hash): string
{
return SnapIO::safePathTrailingslashit($origFolderParentPath) . InstDescMng::getInstance()->getName(InstDescMng::TYPE_ORIG_FILES);
}
}

View File

@@ -0,0 +1,492 @@
<?php
/**
* Class used to log information
*
* Standard: PSR-2
*
* @link http://www.php-fig.org/psr/psr-2 Full Documentation
*
* @package SC\DUPX\Log
*/
namespace Duplicator\Installer\Utils\Log;
use Duplicator\Installer\Core\Security;
use Duplicator\Installer\Core\Params\PrmMng;
use Duplicator\Installer\ViewHelpers\Resources;
use Duplicator\Libs\Snap\SnapIO;
use Error;
use Exception;
/**
* Log
* Class used to log information
*/
class Log
{
/**
* Maximum length of the log on the log.
* Prevents uncontrolled increase in log size. This dimension should never be reached
*/
const MAX_LENGTH_FWRITE = 50000;
const LV_DEFAULT = 1;
const LV_DETAILED = 2;
const LV_DEBUG = 3;
const LV_HARD_DEBUG = 4;
/**
* if true throw exception on error else die on error
*
* @var bool
*/
private static $thowExceptionOnError = false;
/**
* log level
*
* @var int
*/
private static $logLevel = self::LV_DEFAULT;
/**
* num of \t before log string.
*
* @var int
*/
private static $indentation = 0;
/**
*
* @var float
*/
private static $microtimeStart = 0;
/**
*
* @var callable
*/
private static $postprocessCallback;
/**
* @var callable
*/
private static $afterFatalErrorCallback;
/**
*
* @var null|resource
*/
private static $logHandle;
/**
* set log level from param manager
*
* @return void
*/
public static function setLogLevel(): void
{
self::$logLevel = PrmMng::getInstance()->getValue(PrmMng::PARAM_LOGGING);
}
/**
* Used to write debug info to the text log file
*
* @param string $msg Any text data
* @param int $logging Log level
* @param bool $flush if true flush file log
*
* @return void
*/
public static function info($msg, $logging = self::LV_DEFAULT, $flush = false): void
{
if ($logging > self::$logLevel) {
return;
}
$preLog = '';
if (self::$indentation) {
$preLog .= str_repeat("\t", self::$indentation);
}
if (self::$logLevel >= self::LV_DETAILED) {
$preLog .= sprintf('[DELTA:%10.5f] ', microtime(true) - self::$microtimeStart);
}
if (is_callable(self::$postprocessCallback)) {
$msg = call_user_func(self::$postprocessCallback, $msg);
}
@fwrite(self::getFileHandle(), $preLog . $msg . "\n", self::MAX_LENGTH_FWRITE);
if ($flush) {
self::flush();
}
}
/**
*
* @return bool Returns true on success or false on failure.
*/
public static function clearLog()
{
self::close();
if (file_exists(self::getLogFilePath())) {
return unlink(self::getLogFilePath());
} else {
return true;
}
}
/**
*
* @return string
*/
protected static function getLogFileName(): string
{
return 'dup-installer-log__' . Security::getInstance()->getSecondaryPackageHash() . '.txt';
}
/**
*
* @return string
*/
public static function getLogFilePath(): string
{
return DUPX_INIT . '/' . self::getLogFileName();
}
/**
*
* @return string
*/
public static function getLogFileUrl(): string
{
return Resources::getAssetsBaseUrl() . '/' . self::getLogFileName() . '?now=' . $GLOBALS['NOW_TIME'];
}
/**
* Get trace string
*
* @param array<int, mixed> $callers result of debug_backtrace
* @param int $fromLevel level to start
* @param int<0,max> $tabs num tabs on each line
*
* @return string
*/
public static function traceToString($callers, $fromLevel = 0, $tabs = 0): string
{
$result = '';
for ($i = $fromLevel; $i < count($callers); $i++) {
$trace = $callers[$i];
$result .= str_repeat("\t", $tabs);
if (!empty($trace['class'])) {
$result .= str_pad('TRACE[' . $i . '] CLASS___: ' . $trace['class'] . $trace['type'] . $trace['function'], 45, ' ');
} else {
$result .= str_pad('TRACE[' . $i . '] FUNCTION: ' . $trace['function'], 45, ' ');
}
if (isset($trace['file'])) {
$result .= ' FILE: ' . $trace['file'] . '[' . $trace['line'] . ']';
} else {
$result .= ' NO FILE';
}
$result .= "\n";
}
return $result;
}
/**
* Set post process callback
*
* @param callable $callback callback function
*
* @return void
*/
public static function setPostProcessCallback($callback): void
{
self::$postprocessCallback = is_callable($callback) ? $callback : null;
}
/**
* Set after fatal error callback
*
* @param callable $callback callback function
*
* @return void
*/
public static function setAfterFatalErrorCallback($callback): void
{
self::$afterFatalErrorCallback = is_callable($callback) ? $callback : null;
}
/**
* Reset time counter
*
* @param int $logging log level
* @param boolean $fileInfo Log file info (file, line)
*
* @return void
*/
public static function resetTime($logging = self::LV_DEFAULT, $fileInfo = true): void
{
self::$microtimeStart = microtime(true);
if ($logging > self::$logLevel) {
return;
}
// phpcs:ignore PHPCompatibility.FunctionUse.ArgumentFunctionsReportCurrentValue.NeedsInspection
$callers = debug_backtrace();
$file = $callers[0]['file'];
$line = $callers[0]['line'];
Log::info('LOG-TIME' . ($fileInfo ? '[' . $file . ':' . $line . ']' : '') . ' RESET TIME', $logging);
}
/**
* Log time delta from last resetTime call
*
* @param string $msg message
* @param int $logging log level
* @param bool $fileInfo if strue write file info (file, line)
*
* @return void
*/
public static function logTime($msg = '', $logging = self::LV_DEFAULT, $fileInfo = true): void
{
if ($logging > self::$logLevel) {
return;
}
// phpcs:ignore PHPCompatibility.FunctionUse.ArgumentFunctionsReportCurrentValue.NeedsInspection
$callers = debug_backtrace();
$file = $callers[0]['file'];
$line = $callers[0]['line'];
if ($fileInfo) {
Log::info(
sprintf('LOG-TIME[%s:%s][DELTA:%10.5f] ', $file, $line, microtime(true) - self::$microtimeStart) . (empty($msg) ? '' : ' MESSAGE:' . $msg),
$logging
);
} else {
Log::info(sprintf('LOG-TIME[DELTA:%10.5f] ', microtime(true) - self::$microtimeStart) . (empty($msg) ? '' : ' MESSAGE:' . $msg), $logging);
}
}
/**
* Increment indentation
*
* @return void
*/
public static function incIndent(): void
{
self::$indentation++;
}
/**
* Decrease indentation
*
* @return void
*/
public static function decIndent(): void
{
if (self::$indentation > 0) {
self::$indentation--;
}
}
/**
* Reset indentation
*
* @return void
*/
public static function resetIndent(): void
{
self::$indentation = 0;
}
/**
* Return true if log level is >= of loggin level
*
* @param int $logging log level
*
* @return boolean
*/
public static function isLevel($logging): bool
{
return $logging <= self::$logLevel;
}
/**
* Log passed object
*
* @param string $msg log message
* @param mixed $object object to log
* @param int $logging log level
*
* @return void
*/
public static function infoObject($msg, $object, $logging = self::LV_DEFAULT): void
{
$msg = $msg . "\n" . print_r($object, true);
self::info($msg, $logging);
}
/**
* Flush log file
*
* @return void
*/
public static function flush(): void
{
if (is_resource(self::$logHandle)) {
fflush(self::$logHandle);
}
}
/**
* Close log file
*
* @return bool
*/
public static function close(): bool
{
if (is_resource(self::$logHandle)) {
fclose(self::$logHandle);
}
self::$logHandle = null;
return true;
}
/**
* Get log file stream
*
* @return resource
*/
public static function getFileHandle()
{
if (is_resource(self::$logHandle)) {
return self::$logHandle;
}
if (!is_writable(dirname(self::getLogFilePath()))) {
throw new \Exception('Can\'t write in dup-installer folder, please check the dup-installer permission folder');
}
if (file_exists(self::getLogFilePath())) {
SnapIO::chmod(self::getLogFilePath(), 'u+rw');
}
if ((self::$logHandle = fopen(self::getLogFilePath(), "a+")) === false) {
self::$logHandle = null;
throw new \Exception('Can\'t open the log file, please check the dup-installer permission folder');
}
SnapIO::chmod(self::getLogFilePath(), 'u+rw');
return self::$logHandle;
}
/**
* Log error and die or thore exception if self::$thowExceptionOnError is true
*
* @param string $errorMessage error message
*
* @return void
*/
public static function error($errorMessage): void
{
$breaks = [
"<br />",
"<br>",
"<br/>",
];
$spaces = ["&nbsp;"];
$log_msg = str_ireplace($breaks, "\r\n", $errorMessage);
$log_msg = str_ireplace($spaces, " ", $log_msg);
$log_msg = strip_tags($log_msg);
self::info("\nINSTALLER ERROR:\n{$log_msg}\n");
if (is_callable(self::$afterFatalErrorCallback)) {
call_user_func(self::$afterFatalErrorCallback);
}
if (self::$thowExceptionOnError) {
throw new \Exception($errorMessage);
} else {
self::close();
die("<div class='dupx-ui-error'><hr size='1' /><b style='color:#B80000;'>INSTALL ERROR!</b><br/><pre>{$errorMessage}</pre></div>");
}
}
/**
* Get log exception string
*
* @param Exception $e exception object
* @param string $title log message
*
* @return string
*/
public static function getLogException($e, $title = 'EXCEPTION ERROR: '): string
{
return $title . ' ' . $e->getMessage() . "[CODE:" . $e->getCode() . "]\n" .
"\tFILE:" . $e->getFile() . '[' . $e->getLIne() . "]\n" .
"\tTRACE:\n" . $e->getTraceAsString();
}
/**
* Log exception
*
* @param Error|Exception $e exception object
* @param int $logging log level
* @param string $title log message
*
* @return void
*/
public static function logException($e, $logging = self::LV_DEFAULT, $title = 'EXCEPTION ERROR: '): void
{
if ($logging <= self::$logLevel) {
Log::info("\n" . self::getLogException($e, $title) . "\n");
}
}
/**
* If set true error function thorw exception instead die
*
* @param boolean $set enable/disable thorw exception
*
* @return void
*/
public static function setThrowExceptionOnError($set): void
{
self::$thowExceptionOnError = (bool) $set;
}
/**
* Get string from generic value
*
* @param mixed $var value
* @param bool $checkCallable if true check if var is callable and display it
*
* @return string
*/
public static function v2str($var, $checkCallable = false): string
{
if ($checkCallable && is_callable($var)) {
return '(callable) ' . print_r($var, true);
}
switch (gettype($var)) {
case "boolean":
return $var ? 'true' : 'false';
case "integer":
case "double":
return (string) $var;
case "string":
return '"' . $var . '"';
case "array":
case "object":
return print_r($var, true);
case "resource":
case "resource (closed)":
case "NULL":
case "unknown type":
default:
return gettype($var);
}
}
}

View File

@@ -0,0 +1,260 @@
<?php
/**
* Error Hadler logging
*
* Standard: PSR-2
*
* @link http://www.php-fig.org/psr/psr-2 Full Documentation
*
* @package SC\DUPX\Log
*/
namespace Duplicator\Installer\Utils\Log;
use Duplicator\Installer\Core\Bootstrap;
class LogHandler
{
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';
const ERRNO_EXCEPTION = 1073741824; // 31 pos of bit mask
/**
* Set error handler
*
* @return void
*/
public static function initErrorHandler(): void
{
Bootstrap::disableBootShutdownFunction();
set_error_handler([self::class, 'error']);
register_shutdown_function([self::class, 'shutdown']);
}
/**
*
* @var array<string, mixed>
*/
private static $shutdownReturns = ['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 = '';
/**
* 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): bool
{
switch (self::$handlerMode) {
case self::MODE_OFF:
if ($errno == E_ERROR) {
$log_message = self::getMessage($errno, $errstr, $errfile, $errline);
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);
Log::error($log_message);
break;
case E_NOTICE:
case E_WARNING:
default:
$log_message = self::getMessage($errno, $errstr, $errfile, $errline);
Log::info($log_message);
break;
}
}
return true;
}
/**
* Get error message from erro data
*
* @param int $errno error code
* @param string $errstr error message
* @param string $errfile file
* @param int $errline line
*
* @return string
*/
private static function getMessage($errno, $errstr, $errfile, $errline): string
{
$result = '';
if (self::$errPrefix) {
$result = '[PHP ERR][' . self::errnoToString($errno) . '] MSG:';
}
$result .= $errstr;
if (self::$codeReference) {
$result .= ' [CODE:' . $errno . '|FILE:' . $errfile . '|LINE:' . $errline . ']';
if (Log::isLevel(Log::LV_DEBUG)) {
// phpcs:ignore PHPCompatibility.FunctionUse.ArgumentFunctionsReportCurrentValue.NeedsInspection
$result .= "\n" . Log::traceToString(debug_backtrace(), 1, 1);
}
}
return $result;
}
/**
* Errno code to string
*
* @param int $errno error code
*
* @return string
*/
public static function errnoToString($errno): string
{
switch ($errno) {
case E_PARSE:
return 'E_PARSE';
case E_ERROR:
return 'E_ERROR';
case E_CORE_ERROR:
return 'E_CORE_ERROR';
case E_COMPILE_ERROR:
return 'E_COMPILE_ERROR';
case E_USER_ERROR:
return 'E_USER_ERROR';
case E_WARNING:
return 'E_WARNING';
case E_USER_WARNING:
return 'E_USER_WARNING';
case E_COMPILE_WARNING:
return 'E_COMPILE_WARNING';
case E_NOTICE:
return 'E_NOTICE';
case E_USER_NOTICE:
return 'E_USER_NOTICE';
case self::ERRNO_EXCEPTION:
return 'EXCEPTION';
default:
break;
}
if (defined('E_RECOVERABLE_ERROR') && $errno === E_RECOVERABLE_ERROR) {
return 'E_RECOVERABLE_ERROR';
}
if (defined('E_DEPRECATED') && $errno === E_DEPRECATED) {
return 'E_DEPRECATED';
}
if (defined('E_USER_DEPRECATED') && $errno === E_USER_DEPRECATED) {
return 'E_USER_DEPRECATED';
}
return 'E_UNKNOWN CODE: ' . $errno;
}
/**
* if setMode is called without params set as default
*
* @param int $mode log 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): void
{
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;
}
/**
* Set shutdown print string
*
* @param string $status status type
* @param string $str string to print if is shouddown status
*
* @return void
*/
public static function setShutdownReturn($status, $str): void
{
self::$shutdownReturns[$status] = $str;
}
/**
* Shutdown handler
*
* @return void
*/
public static function shutdown(): void
{
if (($error = error_get_last())) {
if (preg_match('/^Maximum execution time (?:.+) exceeded$/i', $error['message'])) {
echo self::$shutdownReturns[self::SHUTDOWN_TIMEOUT];
}
self::error($error['type'], $error['message'], $error['file'], $error['line']);
}
}
}

View File

@@ -0,0 +1,424 @@
<?php
namespace Duplicator\Installer\Utils\ReplaceEngine;
use Duplicator\Libs\Snap\SnapIO;
use Duplicator\Libs\Snap\SnapJson;
/**
* search and replace item use in manager to creat the search and replace list.
*/
class ReplaceItem
{
const PATH_SEPARATOR_REGEX_NORMAL = '[\/\\\\]';
const PATH_SEPARATOR_REGEX_JSON = '(?:\\\\\/|\\\\\\\\)';
const PATH_END_REGEX_MATCH_NORMAL = '([\/\\\\"\'\n\r]|$)';
const PATH_END_REGEX_MATCH_JSON = '(\\\\\/|\\\\\\\\|["\'\n\r]|$)';
const URL_END_REGEX_MATCH_NORMAL = '([\/?"\'\n\r]|$)';
const URL_END_REGEX_MATCH_JSON = '(\\\\\/|[?"\'\n\r]|$)';
const URL_END_REGEX_MATCH_URLENCODE = '(%2F|%3F|["\'\n\r]|$)';
const TYPE_STRING = 'str';
const TYPE_URL = 'url';
const TYPE_URL_NORMALIZE_DOMAIN = 'urlnd';
const TYPE_PATH = 'path';
/** @var int */
private static $uniqueIdCount = 0;
/** @var int */
private $id = 0;
/** @var int prority lower first */
public $prority = 10;
/** @var string[] scope list */
public $scope = [];
/** @var string type of string ENUM self::TYPE_* */
public $type = self::TYPE_STRING;
/** @var string search string */
public $search = '';
/** @var string replace string */
public $replace = '';
/**
* Class constructor
*
* @param string $search search string
* @param string $replace replace string
* @param string $type type of string
* @param int $prority lower first
* @param string|string[] $scope if empty never used
*/
public function __construct($search, $replace, $type = self::TYPE_STRING, $prority = 10, $scope = [])
{
if (!is_array($scope)) {
$this->scope = empty($scope) ? [] : [(string) $scope];
} else {
$this->scope = $scope;
}
$this->prority = (int) $prority;
switch ($type) {
case self::TYPE_URL:
case self::TYPE_URL_NORMALIZE_DOMAIN:
$this->search = rtrim($search, '/');
$this->replace = rtrim($replace, '/');
break;
case self::TYPE_PATH:
$this->search = SnapIO::safePathUntrailingslashit($search);
$this->replace = SnapIO::safePathUntrailingslashit($replace);
break;
case self::TYPE_STRING:
default:
$this->search = (string) $search;
$this->replace = (string) $replace;
break;
}
$this->type = $type;
$this->id = self::$uniqueIdCount;
self::$uniqueIdCount++;
}
/**
* Return array
*
* @return array{id:int,search:string,replace:string,type:string,prority:int,scope:string[]}
*/
public function toArray(): array
{
return [
'id' => $this->id,
'prority' => $this->prority,
'scope' => $this->scope,
'type' => $this->type,
'search' => $this->search,
'replace' => $this->replace,
];
}
/**
* Return item from array
*
* @param array{search:string,replace:string,type:string,prority:int,scope:string[]} $array Array data
*
* @return self
*/
public static function getItemFromArray($array): self
{
return new self($array['search'], $array['replace'], $array['type'], $array['prority'], $array['scope']);
}
/**
* Return search an replace string
*
* result
* [
* ['search' => ...,'replace' => ...]
* ['search' => ...,'replace' => ...]
* ]
*
* @return array<array{search:string,replace:string}>
*/
public function getPairsSearchReplace(): array
{
switch ($this->type) {
case self::TYPE_URL:
return self::searchReplaceUrl($this->search, $this->replace);
case self::TYPE_URL_NORMALIZE_DOMAIN:
return self::searchReplaceUrl($this->search, $this->replace, true, true);
case self::TYPE_PATH:
return self::searchReplacePath($this->search, $this->replace);
case self::TYPE_STRING:
default:
return self::searchReplaceWithEncodings($this->search, $this->replace);
}
}
/**
* Get search and replace strings with encodings
* prevents unnecessary substitution like when search and reaplace are the same.
*
* result
* [
* ['search' => ...,'replace' => ...]
* ['search' => ...,'replace' => ...]
* ]
*
* @param string $search search string
* @param string $replace replace string
* @param bool $json add json encode string
* @param bool $urlencode add urlencode string
*
* @return array<array{search:string,replace:string}> pairs search and replace
*/
public static function searchReplaceWithEncodings($search, $replace, $json = true, $urlencode = true): array
{
$result = [];
if ($search != $replace) {
$result[] = [
'search' => '/' . preg_quote($search, '/') . '/m',
'replace' => addcslashes($replace, '\\$'),
];
} else {
return [];
}
// JSON ENCODE
if ($json) {
$search_json = SnapJson::getJsonWithoutQuotes($search);
$replace_json = SnapJson::getJsonWithoutQuotes($replace);
if ($search != $search_json && $search_json != $replace_json) {
$result[] = [
'search' => '/' . preg_quote($search_json, '/') . '/m',
'replace' => addcslashes($replace_json, '\\$'),
];
}
}
// URL ENCODE
if ($urlencode) {
$search_urlencode = urlencode($search);
$replace_urlencode = urlencode($replace);
if ($search != $search_urlencode && $search_urlencode != $replace_urlencode) {
$result[] = [
'search' => '/' . preg_quote($search_urlencode, '/') . '/m',
'replace' => addcslashes($replace_urlencode, '\\$'),
];
}
}
return $result;
}
/**
* Add replace strings to substitute old url to new url
* 1) no protocol old url to no protocol new url (es. //www.hold.url => //www.new.url)
* 2) wrong protocol new url to right protocol new url (es. http://www.new.url => https://www.new.url)
*
* result
* [
* ['search' => ...,'replace' => ...]
* ['search' => ...,'replace' => ...]
* ]
*
* @param string $search_url old url
* @param string $replace_url new url
* @param bool $force_new_protocol if true force http or https protocol (work only if replace url have http or https scheme)
* @param bool $normalizeWww if true normalize www
*
* @return array<array{search:string,replace:string}> pairs search and replace
*/
public static function searchReplaceUrl($search_url, $replace_url, $force_new_protocol = true, $normalizeWww = false): array
{
$result = [];
if (($parse_search_url = parse_url($search_url)) !== false && isset($parse_search_url['scheme'])) {
$search_url_raw = substr($search_url, strlen($parse_search_url['scheme']) + 1);
} else {
$search_url_raw = $search_url;
}
$search_url_raw = trim($search_url_raw, '/');
if (($parse_replace_url = parse_url($replace_url)) !== false && isset($parse_replace_url['scheme'])) {
$replace_url_raw = substr($replace_url, strlen($parse_replace_url['scheme']) + 1);
} else {
$replace_url_raw = $replace_url;
}
$replace_url_raw = trim($replace_url_raw, '/');
// (?<!https:|http:)\/\/(?:www\.|)aaaa\.it([?\/'"]|$)
if ($normalizeWww && self::domainCanNormalized($search_url)) {
$baseSearchUrl = self::isWww($search_url_raw) ? substr($search_url_raw, strlen('www.')) : $search_url_raw;
$regExSearchUrlNormal = '\/\/(?:www\.)?' . preg_quote($baseSearchUrl, '/');
$regExSearchUrlJson = '\\\\\/\\\\\/(?:www\.)?' . preg_quote(SnapJson::getJsonWithoutQuotes($baseSearchUrl), '/');
$regExSearchUrlEncode = '%2F%2F(?:www\.)?' . preg_quote(urlencode($baseSearchUrl), '/');
//'/https?:\/\/(?:www\.|)aaaa\.it(?<end>[?\/\'"]|$)/m'
//$searchRawRegEx = '/(?<!https:|http:)\/\/(?:www\.|)'.preg_quote($baseSearchUrl, '/').'([?\/\'"]|$)/m';
} else {
$regExSearchUrlNormal = '\/\/' . preg_quote($search_url_raw, '/');
$regExSearchUrlJson = '\\\\\/\\\\\/' . preg_quote(SnapJson::getJsonWithoutQuotes($search_url_raw), '/');
$regExSearchUrlEncode = '%2F%2F' . preg_quote(urlencode($search_url_raw), '/');
}
// NORMALIZE source protocol
if ($force_new_protocol && $parse_replace_url !== false && isset($parse_replace_url['scheme'])) {
$result[] = [
'search' => '/(?<!https:|http:)' . $regExSearchUrlNormal . self::URL_END_REGEX_MATCH_NORMAL . '/m',
'replace' => addcslashes('//' . $replace_url_raw, '\\$') . '$1',
];
$result[] = [
'search' => '/https?:' . $regExSearchUrlNormal . self::URL_END_REGEX_MATCH_NORMAL . '/m',
'replace' => addcslashes($replace_url, '\\$') . '$1',
];
$result[] = [
'search' => '/(?<!https:|http:)' . $regExSearchUrlJson . self::URL_END_REGEX_MATCH_JSON . '/m',
'replace' => addcslashes(SnapJson::getJsonWithoutQuotes('//' . $replace_url_raw), '\\$') . '$1',
];
$result[] = [
'search' => '/https?:' . $regExSearchUrlJson . self::URL_END_REGEX_MATCH_JSON . '/m',
'replace' => addcslashes(SnapJson::getJsonWithoutQuotes($replace_url), '\\$') . '$1',
];
$result[] = [
'search' => '/(?<!https%3A|http%3A)' . $regExSearchUrlEncode . self::URL_END_REGEX_MATCH_URLENCODE . '/m',
'replace' => addcslashes(urlencode('//' . $replace_url_raw), '\\$') . '$1',
];
$result[] = [
'search' => '/https?%3A' . $regExSearchUrlEncode . self::URL_END_REGEX_MATCH_URLENCODE . '/m',
'replace' => addcslashes(urlencode($replace_url), '\\$') . '$1',
];
} else {
$result[] = [
'search' => '/' . $regExSearchUrlNormal . self::URL_END_REGEX_MATCH_NORMAL . '/m',
'replace' => addcslashes('//' . $replace_url_raw, '\\$') . '$1',
];
$result[] = [
'search' => '/' . $regExSearchUrlJson . self::URL_END_REGEX_MATCH_JSON . '/m',
'replace' => addcslashes(SnapJson::getJsonWithoutQuotes('//' . $replace_url_raw), '\\$') . '$1',
];
$result[] = [
'search' => '/' . $regExSearchUrlEncode . self::URL_END_REGEX_MATCH_URLENCODE . '/m',
'replace' => addcslashes(urlencode('//' . $replace_url_raw), '\\$') . '$1',
];
}
return $result;
}
/**
* result
* [
* ['search' => ...,'replace' => ...]
* ['search' => ...,'replace' => ...]
* ]
*
* @param string $search_path search path
* @param string $replace_path replace path
*
* @return array<array{search:string,replace:string}> pairs search and replace
*/
public static function searchReplacePath($search_path, $replace_path): array
{
$result = [];
if ($search_path == $replace_path) {
return $result;
}
$explodeSearch = explode('/', $search_path);
$normaSearchArray = array_map(fn($val): string => preg_quote(SnapJson::getJsonWithoutQuotes($val), '/'), $explodeSearch);
$normalPathSearch = '/' . implode(self::PATH_SEPARATOR_REGEX_NORMAL, $normaSearchArray) . self::PATH_END_REGEX_MATCH_NORMAL . '/m';
$result[] = [
'search' => $normalPathSearch,
'replace' => addcslashes($replace_path, '\\$') . '$1',
];
$jsonSearchArray = array_map(fn($val): string => preg_quote(SnapJson::getJsonWithoutQuotes($val), '/'), $explodeSearch);
$jsonPathSearch = '/' . implode(self::PATH_SEPARATOR_REGEX_JSON, $jsonSearchArray) . self::PATH_END_REGEX_MATCH_JSON . '/m';
$result[] = [
'search' => $jsonPathSearch,
'replace' => addcslashes(SnapJson::getJsonWithoutQuotes($replace_path), '\\$') . '$1',
];
return $result;
}
/**
* get unique item id
*
* @return int
*/
public function getId()
{
return $this->id;
}
/**
* @param string $url string The URL whichs domain you want to get
*
* @return string The domain part of the given URL
* www.myurl.co.uk => myurl.co.uk
* www.google.com => google.com
* my.test.myurl.co.uk => myurl.co.uk
* www.myurl.localweb => myurl.localweb
*/
public static function getDomain($url)
{
$pieces = parse_url($url);
$domain = $pieces['host'] ?? '';
$regs = null;
if (strpos($domain, ".") !== false) {
if (preg_match('/(?P<domain>[a-z0-9][a-z0-9\-]{1,63}\.[a-z\.]{2,6})$/i', $domain, $regs)) {
return $regs['domain'];
} else {
$exDomain = explode('.', $domain);
return implode('.', array_slice($exDomain, -2, 2));
}
} else {
return $domain;
}
}
/**
* Check if domain can be normalized
*
* @param string $url The URL whichs domain you want to check
*
* @return bool
*/
public static function domainCanNormalized($url)
{
$pieces = parse_url($url);
if (!isset($pieces['host'])) {
return false;
}
if (strpos($pieces['host'], ".") === false) {
return false;
}
$dLevels = explode('.', $pieces['host']);
if ($dLevels[0] == 'www') {
return true;
}
switch (count($dLevels)) {
case 1:
return false;
case 2:
return true;
case 3:
if (preg_match('/(?P<domain>[a-z0-9][a-z0-9\-]{1,63}\.[a-z\.]{2,6})$/i', $pieces['host'], $regs)) {
return $regs['domain'] == $pieces['host'];
}
return false;
default:
return false;
}
}
/**
* Check if domain is www
*
* @param string $url The URL whichs domain you want to check
*
* @return bool
*/
public static function isWww($url)
{
$pieces = parse_url($url);
if (!isset($pieces['host'])) {
return false;
} else {
return strpos($pieces['host'], 'www.') === 0;
}
}
}

View File

@@ -0,0 +1,299 @@
<?php
/**
* @package Duplicator\Installer
*/
namespace Duplicator\Installer\Utils\ReplaceEngine;
use Duplicator\Installer\Utils\Log\Log;
/**
* Search and replace manager
* singleton class
*/
final class ReplaceMng
{
const GLOBAL_SCOPE_KEY = '___!GLOBAL!___!SCOPE!___';
/** @var ?self */
private static $instance;
/**
* full list items not sorted
*
* @var ReplaceItem[]
*/
private $items = [];
/**
* items sorted by priority and scope
* [
* 10 => [
* '___!GLOBAL!___!SCOPE!___' => [
* SearchReplaceItem
* SearchReplaceItem
* SearchReplaceItem
* ],
* 'scope_one' => [
* SearchReplaceItem
* SearchReplaceItem
* ]
* ],
* 20 => [
* .
* .
* .
* ]
* ]
*
* @var array<int,array<string,ReplaceItem[]>>
*/
private $prorityScopeItems = [];
/**
*
* @return ReplaceMng
*/
public static function getInstance()
{
if (is_null(self::$instance)) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Class constructor
*/
private function __construct()
{
}
/**
*
* @return array<array{id:int,search:string,replace:string,type:string,prority:int,scope:string[]}>
*/
public function getArrayData(): array
{
$data = [];
foreach ($this->items as $item) {
$data[] = $item->toArray();
}
return $data;
}
/**
* Set data from array
*
* @param array<array{search:string,replace:string,type:string,prority:int,scope:string[]}> $data Array of data
*
* @return void
*/
public function setFromArrayData($data): void
{
foreach ($data as $itemArray) {
$new_item = ReplaceItem::getItemFromArray($itemArray);
$this->setNewItem($new_item);
}
}
/**
*
* @param string $search search string
* @param string $replace replace string
* @param string $type item type ReplaceItem::[TYPE_STRING|TYPE_URL|TYPE_URL_NORMALIZE_DOMAIN|TYPE_PATH]
* @param int $prority lower first
* @param bool|string|string[] $scope true = global scope | false = never | string signle scope | string[] scope list
*
* @return boolean|ReplaceItem false if fail
*/
public function addItem($search, $replace, $type = ReplaceItem::TYPE_STRING, $prority = 10, $scope = true)
{
$search = (string) $search;
$replace = (string) $replace;
if (strlen($search) == 0 || $search === $replace) {
return false;
}
if (is_bool($scope)) {
$scope = $scope ? self::GLOBAL_SCOPE_KEY : '';
}
if (is_array($scope)) {
$scopeStr = implode(',', $scope);
$scopeStr = (strlen($scopeStr) > 50 ? substr($scopeStr, 0, 50) . "..." : $scopeStr);
} else {
$scopeStr = 'ALL';
}
Log::info(
"SEARCH ITEM[T:" . str_pad($type, 5) . "|P:" . str_pad((string) $prority, 2) . "]" .
" SEARCH: " . $search .
" REPLACE: " . $replace . " [SCOPE: " . $scopeStr . "]"
);
$new_item = new ReplaceItem($search, $replace, $type, $prority, $scope);
return $this->setNewItem($new_item);
}
/**
* Set new item
*
* @param ReplaceItem $new_item new item
*
* @return ReplaceItem
*/
private function setNewItem(ReplaceItem $new_item): ReplaceItem
{
$this->items[$new_item->getId()] = $new_item;
// create priority array
if (!isset($this->prorityScopeItems[$new_item->prority])) {
$this->prorityScopeItems[$new_item->prority] = [];
// sort by priority
ksort($this->prorityScopeItems);
}
// create scope list
foreach ($new_item->scope as $scope) {
if (!isset($this->prorityScopeItems[$new_item->prority][$scope])) {
$this->prorityScopeItems[$new_item->prority][$scope] = [];
}
$this->prorityScopeItems[$new_item->prority][$scope][] = $new_item;
}
return $new_item;
}
/**
* get all search and reaple items by scpoe
*
* @param null|string $scope if scope is empty get only global scope
* @param bool $globalScope if true get global scope
*
* @return ReplaceItem[]
*/
private function getSearchReplaceItems($scope = null, $globalScope = true): array
{
$items_list = [];
foreach ($this->prorityScopeItems as $priority => $priority_list) {
// get scope list
if (!empty($scope) && isset($priority_list[$scope])) {
foreach ($priority_list[$scope] as $item) {
$items_list[] = $item;
}
}
// get global scope
if ($globalScope && isset($priority_list[self::GLOBAL_SCOPE_KEY])) {
foreach ($priority_list[self::GLOBAL_SCOPE_KEY] as $item) {
$items_list[] = $item;
}
}
}
return $items_list;
}
/**
* get replace list by scope
* result
* [
* ['search' => ...,'replace' => ...]
* ['search' => ...,'replace' => ...]
* ]
*
* @param null|string $scope if scope is empty get only global scope
* @param bool $unique_search If true it eliminates the double searches leaving the one with lower priority.
* @param bool $globalScope if true get global scope
*
* @return array<array{search:string,replace:string}> pairs search and replace
*/
public function getSearchReplaceList($scope = null, $unique_search = true, $globalScope = true): array
{
Log::info('-- SEARCH LIST -- SCOPE: ' . Log::v2str($scope), Log::LV_DEBUG);
$items_list = $this->getSearchReplaceItems($scope, $globalScope);
if (Log::isLevel(Log::LV_HARD_DEBUG)) {
Log::info('-- SEARCH LIST ITEMS --' . "\n" . print_r($items_list, true), Log::LV_HARD_DEBUG);
}
if ($unique_search) {
$items_list = self::uniqueSearchListItem($items_list);
if (Log::isLevel(Log::LV_HARD_DEBUG)) {
Log::info('-- UNIQUE LIST ITEMS --' . "\n" . print_r($items_list, true), Log::LV_HARD_DEBUG);
}
}
Log::info('--- BASE STRINGS ---');
foreach ($items_list as $index => $item) {
Log::info(
'SEARCH[' . str_pad($item->type, 5, ' ', STR_PAD_RIGHT) . ']' . str_pad((string) ($index + 1), 3, ' ', STR_PAD_LEFT) . ":" .
str_pad(Log::v2str($item->search) . " ", 50, '=', STR_PAD_RIGHT) .
"=> " .
Log::v2str($item->replace)
);
}
$result = [];
foreach ($items_list as $item) {
$result = array_merge($result, $item->getPairsSearchReplace());
}
// remove empty search strings
$result = array_filter($result, function ($val): bool {
if (!empty($val['search'])) {
return true;
} else {
Log::info('Empty search string remove, replace: ' . Log::v2str($val['replace']), Log::LV_DETAILED);
return false;
}
});
if (Log::isLevel(Log::LV_DEBUG)) {
Log::info('--- REXEXES ---');
foreach ($result as $index => $c_sr) {
Log::info(
'SEARCH' . str_pad((string) ($index + 1), 3, ' ', STR_PAD_LEFT) . ":" .
str_pad(Log::v2str($c_sr['search']) . " ", 50, '=', STR_PAD_RIGHT) .
"=> " .
Log::v2str($c_sr['replace'])
);
}
}
return $result;
}
/**
* remove duplicated search strings.
* Leave the object at lower priority
*
* @param ReplaceItem[] $list list of SearchReplaceItem
*
* @return ReplaceItem[]
*/
private static function uniqueSearchListItem(array $list): array
{
$search_strings = [];
$result = [];
foreach ($list as $item) {
if (!in_array($item->search, $search_strings)) {
$result[] = $item;
$search_strings[] = $item->search;
}
}
return $result;
}
}

View File

@@ -0,0 +1,234 @@
<?php
/**
* @package Duplicator\Installer
*/
namespace Duplicator\Installer\Utils;
use Exception;
class SecureCsrf
{
/**
* Session var name prefix
*
* @var string
*/
const PREFIX = '_DUPX_CSRF';
/** @var string */
private static $packagHash = '';
/** @var string */
private static $mainFolder = '';
/**
* Stores all CSRF values: Key as CSRF name and Val as CRF value
*
* @var ?mixed[]
*/
private static $CSRFVars;
/**
* Init CSRF
*
* @param string $mainFolderm folter to store CSRF file
* @param string $packageHash package has
*
* @return void
*/
public static function init($mainFolderm, $packageHash): void
{
self::$mainFolder = $mainFolderm;
self::$packagHash = $packageHash;
self::$CSRFVars = null;
}
/**
* Set new CSRF
*
* @param string $key CSRF Key
* @param mixed $val CSRF Val
*
* @return void
*/
public static function setKeyVal($key, $val): void
{
self::getCSRFVars();
self::$CSRFVars[$key] = $val;
self::saveCSRFVars();
}
/**
* Remove CSRF if exists
*
* @param string $key CSRF Key
*
* @return void
*/
public static function removeKeyVal($key): void
{
self::getCSRFVars();
if (isset(self::$CSRFVars[$key])) {
unset(self::$CSRFVars[$key]);
self::saveCSRFVars();
}
}
/**
* Get CSRF value by passing CSRF key
*
* @param string $key CSRF key
*
* @return string|boolean If CSRF value set for give n Key, It returns CRF value otherise returns false
*/
public static function getVal($key)
{
self::getCSRFVars();
if (isset(self::$CSRFVars[$key])) {
return self::$CSRFVars[$key];
} else {
return false;
}
}
/**
* Generate SecureCsrf value for form
*
* @param string $form // Form name as session key
*
* @return string // token
*/
public static function generate($form = null)
{
$keyName = self::getKeyName($form);
$existingToken = self::getVal($keyName);
$token = false !== $existingToken ? $existingToken : self::token() . self::fingerprint();
self::setKeyVal($keyName, $token);
return $token;
}
/**
* Check SecureCsrf value of form
*
* @param string $token - Token
* @param string $form - Form name as session key
*
* @return boolean
*/
public static function check($token, $form = null)
{
if (empty($form)) {
return false;
}
$keyName = self::getKeyName($form);
self::getCSRFVars();
return (isset(self::$CSRFVars[$keyName]) && self::$CSRFVars[$keyName] == $token);
}
/**
* Generate token
*
* @return string
*/
protected static function token(): string
{
$microtime = (int) (microtime(true) * 10000);
mt_srand($microtime);
$charid = strtoupper(md5(uniqid((string) random_int(0, mt_getrandmax()), true)));
return substr($charid, 0, 8) . substr($charid, 8, 4) . substr($charid, 12, 4) . substr($charid, 16, 4) . substr($charid, 20, 12);
}
/**
* Returns "digital fingerprint" of user
*
* @return string - MD5 hashed data
*/
protected static function fingerprint(): string
{
return strtoupper(md5(implode('|', [$_SERVER['REMOTE_ADDR'], $_SERVER['HTTP_USER_AGENT']])));
}
/**
* Generate CSRF Key name
*
* @param string $form the form name for which CSRF key need to generate
*
* @return string CSRF key
*/
private static function getKeyName($form): string
{
return self::PREFIX . '_' . $form;
}
/**
* Get Package hash
*
* @return string Package hash
*/
private static function getPackageHash()
{
if (strlen(self::$packagHash) == 0) {
throw new Exception('Not init CSFR CLASS');
}
return self::$packagHash;
}
/**
* Get file path where CSRF tokens are stored in JSON encoded format
*
* @return string file path where CSRF token stored
*/
public static function getFilePath(): string
{
if (strlen(self::$mainFolder) == 0) {
throw new Exception('Not init CSFR CLASS');
}
$dupInstallerfolderPath = self::$mainFolder;
$packageHash = self::getPackageHash();
//Can't use the descriptor manager because it is not initialized yet
$fileName = 'dup_descriptors_' . $packageHash . '/installer-csrf.txt';
return $dupInstallerfolderPath . '/' . $fileName;
}
/**
* Get all CSRF vars in array format
*
* @return mixed[] Key as CSRF name and value as CSRF value
*/
private static function getCSRFVars()
{
if (is_null(self::$CSRFVars)) {
$filePath = self::getFilePath();
if (file_exists($filePath)) {
$contents = file_get_contents($filePath);
if (empty($contents)) {
self::$CSRFVars = [];
} else {
$CSRFobjs = json_decode($contents);
foreach ($CSRFobjs as $key => $value) {
self::$CSRFVars[$key] = $value;
}
}
} else {
self::$CSRFVars = [];
}
}
return self::$CSRFVars;
}
/**
* Stores all CSRF vars
*
* @return void
*/
private static function saveCSRFVars(): void
{
$contents = json_encode(self::$CSRFVars);
$filePath = self::getFilePath();
file_put_contents($filePath, $contents);
}
}

View File

@@ -0,0 +1,207 @@
<?php
/**
* Customizes final report error messages
*
* Standard: PSR-2
*
* @link http://www.php-fig.org/psr/psr-2 Full Documentation
*
* @package SC\DUPX\U
*/
namespace Duplicator\Installer\Utils\Tests;
use Duplicator\Installer\Utils\InstDescMng;
use Duplicator\Libs\Snap\SnapIO;
class MessageCustomizer
{
const CONTEXT_SHORT_MESSAGE = 'short';
const CONTEXT_LONG_MESSAGE = 'long';
const CONTEXT_NOTICE_ID = 'notice-id';
/**
* Tries to apply each customization until one of them works
*
* @param string $shortMessage short message of notice to be customized
* @param string $longMessage long message of notice to be customized
* @param string $noticeId notice ID
*
* @return bool true if any of the customizations were applied, false otherwise
*/
public static function applyAllNoticeCustomizations(&$shortMessage, &$longMessage, &$noticeId): bool
{
foreach (self::getCustomizationItems() as $item) {
if ($item->conditionSatisfied($longMessage)) {
$shortMessage = $item->apply($shortMessage, self::CONTEXT_SHORT_MESSAGE);
$longMessage = $item->apply($longMessage, self::CONTEXT_LONG_MESSAGE);
$noticeId = $item->apply($noticeId, self::CONTEXT_NOTICE_ID);
return true;
}
}
return false;
}
/**
* Get customization to apply at error messages
*
* @return MessageCustomizerItem[] customizations list
*/
protected static function getCustomizationItems(): array
{
$items = [];
$items[] = new MessageCustomizerItem(
function ($string): bool {
if (self::getArchiveConfigData() == false) {
return false;
}
return preg_match("/undefined.*create_function/", $string) &&
version_compare(phpversion(), "8") >= 0 &&
version_compare(self::getArchiveConfigData()->version_php, "8") < 0;
},
function ($string, $context) {
if (self::getArchiveConfigData() == false) {
return $string;
}
$phpVersionNew = self::getTwoLevelVersion(phpversion());
$phpVersionOld = self::getTwoLevelVersion(self::getArchiveConfigData()->version_php);
$longMsgPrefix = "There is code in this site that is not compatible with PHP " . $phpVersionNew . ". " .
"To make the install work you will either have to\ninstall on PHP " .
$phpVersionOld . " or ";
switch ($context) {
case self::CONTEXT_SHORT_MESSAGE:
return "Source site or plugins are incompatible with PHP " . $phpVersionNew;
case self::CONTEXT_LONG_MESSAGE:
if (($plugin = self::getProblematicPluginFromError($string)) !== false) {
return $longMsgPrefix . "disable the plugin '{$plugin->name}' (slug: $plugin->slug) using a " .
"file manager of your choice.\nSee full error message below: \n\n" . $string;
} elseif (($theme = self::getProblematicThemeFromError($string)) !== false) {
return $longMsgPrefix . "disable the theme '{$theme->themeName}' (slug: $theme->slug) using a " .
"file manager of your choice.\nSee full error message below: \n\n" . $string;
} else {
return $longMsgPrefix . "manually modify the affected files mentioned in error trace below: \n\n" .
$string;
}
case self::CONTEXT_NOTICE_ID:
return $string . '_php8';
}
}
);
return $items;
}
/**
* Return the plugin that is causing the error message if present
*
* @param string $longMessage the long error message containing the error trace
*
* @return false|object object containing plugin info or false on failure
*/
protected static function getProblematicPluginFromError($longMessage)
{
if (($archiveConfig = self::getArchiveConfigData()) === false) {
return false;
}
$oldMain = $archiveConfig->wpInfo->targetRoot;
$oldMuPlugins = $archiveConfig->wpInfo->configs->realValues->originalPaths->muplugins;
$oldPlugins = $archiveConfig->wpInfo->configs->realValues->originalPaths->plugins;
$relativeMuPlugins = SnapIO::getRelativePath($oldMuPlugins, $oldMain);
$relativePlugins = SnapIO::getRelativePath($oldPlugins, $oldMain);
$regex = "/(?:" . preg_quote($relativePlugins, "/") . "\/|" . preg_quote($relativeMuPlugins, "/") . "\/)(.*?)(\/|\.php).*$/m";
if (!preg_match($regex, $longMessage, $matches)) {
return false;
}
//matches the first part of the slug related to the plugin directory
$slug = $matches[1];
foreach ($archiveConfig->wpInfo->plugins as $plugin) {
if (strpos($plugin->slug, $slug) === 0) {
return $plugin;
}
}
return false;
}
/**
* Returns the theme that is causing the error message if present
*
* @param string $longMessage the long error message containing the error trace
*
* @return false|object object containing theme info or false
*/
protected static function getProblematicThemeFromError($longMessage)
{
if (($archiveConfig = self::getArchiveConfigData()) === false) {
return false;
}
$oldMain = $archiveConfig->wpInfo->targetRoot;
$oldThemes = $archiveConfig->wpInfo->configs->realValues->originalPaths->themes;
$relativeThemes = SnapIO::getRelativePath($oldThemes, $oldMain);
file_put_contents(
DUPX_INIT . "/my_log.txt",
"OLD THEMES: {$oldThemes} \n" .
"Relative themes: {$relativeThemes} \n" .
"regex: " . "/(" . preg_quote($relativeThemes, "/") . "\/)(.*?)(\/|\.php).*$/m"
);
if (!preg_match("/(?:" . preg_quote($relativeThemes, "/") . "\/)(.*?)(?:\/|\.php).*$/m", $longMessage, $matches)) {
return false;
}
$slug = $matches[1];
foreach ($archiveConfig->wpInfo->themes as $theme) {
if ($theme->slug == $slug) {
return $theme;
}
}
return false;
}
/**
* Get package config data
*
* @return false|object package config data or false on failure
*/
protected static function getArchiveConfigData()
{
static $archiveConfig = null;
if (is_null($archiveConfig)) {
$archiveConfigPath = DUPX_INIT . "/" . InstDescMng::getInstance()->getName(InstDescMng::TYPE_ARCHIVE_CONFIG);
if (!file_exists($archiveConfigPath)) {
$archiveConfig = false;
return $archiveConfig;
}
if (($json = file_get_contents($archiveConfigPath)) === false) {
$archiveConfig = false;
return $archiveConfig;
}
$archiveConfig = json_decode($json);
if (!is_object($archiveConfig)) {
$archiveConfig = false;
}
}
return $archiveConfig;
}
/**
* @param string $version a version number
*
* @return string returns only the first 2 levels of the version numbers
*/
private static function getTwoLevelVersion($version): string
{
$arr = explode(".", $version);
return $arr[0] . "." . $arr[1];
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace Duplicator\Installer\Utils\Tests;
class MessageCustomizerItem
{
/** @var callable|bool */
private $checkCallback;
/** @var callable */
private $applyCallback;
/**
* Class contructor
*
* @param callable|bool $checkCallback callback or bool whether to apply customization
* @param callable $applyCallback the customizations to be applied
*/
public function __construct($checkCallback, $applyCallback)
{
if (!is_bool($checkCallback) && !is_callable($checkCallback)) {
throw new \Exception("check callback must be either bool or callable");
}
$this->checkCallback = $checkCallback;
if (!is_callable($applyCallback)) {
throw new \Exception("customization callback must be callable");
}
$this->applyCallback = $applyCallback;
}
/**
* @param mixed $input necessary input to check condition
*
* @return bool
*/
public function conditionSatisfied($input): bool
{
return (is_bool($this->checkCallback) && $this->checkCallback) || call_user_func($this->checkCallback, $input);
}
/**
* @param string $string string to be customized
* @param mixed $context context about what to apply
*
* @return false|mixed
*/
public function apply($string, $context)
{
return call_user_func($this->applyCallback, $string, $context);
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace Duplicator\Installer\Utils\Tests;
interface TestInterface
{
/**
* @return bool true on success
*/
public static function preTestPrepare();
/**
* @return bool true on success
*/
public static function afterTestClean();
}

View File

@@ -0,0 +1,295 @@
<?php
/**
* Error handler for test scripts
* *******************
* IMPORTANT
* Don\'t use snap lib functions o other duplicator functions
* *******************
*
* Standard: PSR-2
*
* @link http://www.php-fig.org/psr/psr-2 Full Documentation
*
* @package SC\DUPX\U
*/
namespace Duplicator\Installer\Utils\Tests\WP;
use Error;
use Exception;
use Throwable;
class TestsErrorHandler
{
const ERR_TYPE_ERROR = 'error';
const ERR_TYPE_WARNING = 'warning';
const ERR_TYPE_NOTICE = 'notice';
const ERR_TYPE_DEPRECATED = 'deprecated';
const ERRNO_EXCEPTION = 1073741824; // 31 pos of bit mask
/** @var array<int, array<int, array<string, mixed>>> */
protected static $errors = [];
/**
* If it is null a json is displayed otherwise the callback function is executed in the shutd
*
* @var ?callable
*/
protected static $shutdownCallback;
/**
* Register error handlers
*
* @return void
*/
public static function register(): void
{
@register_shutdown_function([self::class, 'shutdown']);
@set_error_handler([self::class, 'error']);
@set_exception_handler([self::class, 'exception']);
}
/**
* @param callable $callback shutdown callback
*
* @return void
*/
public static function setShutdownCallabck($callback): void
{
self::$shutdownCallback = is_callable($callback) ? $callback : null;
}
/**
* Add error on list
*
* @param int $errno error number
* @param string $errstr error string
* @param string $errfile error file
* @param int $errline error line
* @param array<int, array<string, mixed>> $trace error trace
*
* @return void
*/
protected static function addError($errno, $errstr, $errfile, $errline, $trace)
{
$newError = [
'error_cat' => self::getErrorCategoryFromErrno($errno),
'errno' => $errno,
'errno_str' => self::errnoToString($errno),
'errstr' => $errstr,
'errfile' => $errfile,
'errline' => $errline,
'trace' => array_map([self::class, 'normalizeTraceElement'], $trace),
];
self::$errors[] = $newError;
if (function_exists('error_clear_last')) {
error_clear_last(); // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctions.error_clear_lastFound
}
}
/**
* @param array<string, mixed> $error the error array
*
* @return string human-readable error message with trace
*/
public static function errorToString($error): string
{
$result = $error['errno_str'] . ' ' . $error['errstr'] . "\n";
$result .= "\tFILE: " . $error['errfile'] . '[' . $error['errline'] . ']' . "\n";
$result .= "\t--- TRACE ---\n";
foreach ($error['trace'] as $trace) {
$result .= "\t";
if (!empty($trace['class'])) {
$result .= str_pad('CLASS___: ' . $trace['class'] . $trace['type'] . $trace['function'], 40, ' ');
} else {
$result .= str_pad('FUNCTION: ' . $trace['function'], 40, ' ');
}
$result .= 'FILE: ' . $trace['file'] . '[' . $trace['line'] . ']' . "\n";
}
return $result;
}
/**
* 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): bool
{
$trace = debug_backtrace();
array_shift($trace);
self::addError($errno, $errstr, $errfile, $errline, $trace);
return true;
}
/**
* Exception handler
*
* @param Exception|Error|Throwable $e Throwable in php 7
*
* @return void
*/
public static function exception($e): void
{
self::addError(self::ERRNO_EXCEPTION, $e->getMessage(), $e->getFile(), $e->getLine(), $e->getTrace());
}
/**
* Shutdown handler
*
* @return void
*/
public static function shutdown(): void
{
self::obCleanAll();
if (($error = error_get_last())) {
self::error($error['type'], $error['message'], $error['file'], $error['line']);
}
ob_end_clean();
if (is_callable(self::$shutdownCallback)) {
call_user_func(self::$shutdownCallback, self::$errors);
} else {
echo json_encode(self::$errors);
}
// prevent other shutdown functions
exit();
}
/**
* Close all buffers and return content
*
* @param bool $getContent If true it returns buffer content, otherwise it is discarded
*
* @return string
*/
protected static function obCleanAll($getContent = true): string
{
$result = '';
for ($i = 0; $i < ob_get_level(); $i++) {
if ($getContent) {
$result .= ob_get_contents();
}
ob_clean();
}
return $result;
}
/**
* @param array<string, scalar> $elem normalize error element
*
* @return array{file: string, line: int, function: string, class: string, type: string}
*/
public static function normalizeTraceElement($elem): array
{
if (!is_array($elem)) {
$elem = [];
}
unset($elem['args']);
unset($elem['object']);
return array_merge([
'file' => '',
'line' => -1,
'function' => '',
'class' => '',
'type' => '',
], $elem);
}
/**
*
* @param int $errno error number
*
* @return string
*/
public static function getErrorCategoryFromErrno($errno): string
{
switch ($errno) {
case E_PARSE:
case E_ERROR:
case E_CORE_ERROR:
case E_COMPILE_ERROR:
case E_USER_ERROR:
case self::ERRNO_EXCEPTION:
return self::ERR_TYPE_ERROR;
case E_WARNING:
case E_USER_WARNING:
case E_COMPILE_WARNING:
return self::ERR_TYPE_WARNING;
case E_NOTICE:
case E_USER_NOTICE:
return self::ERR_TYPE_NOTICE;
default:
break;
}
if (defined('E_RECOVERABLE_ERROR') && $errno === E_RECOVERABLE_ERROR) {
return self::ERR_TYPE_WARNING;
}
if (defined('E_DEPRECATED') && $errno === E_DEPRECATED) {
return self::ERR_TYPE_DEPRECATED;
}
if (defined('E_USER_DEPRECATED') && $errno === E_USER_DEPRECATED) {
return self::ERR_TYPE_DEPRECATED;
}
return self::ERR_TYPE_WARNING;
}
/**
*
* @param int $errno error number
*
* @return string
*/
public static function errnoToString($errno): string
{
switch ($errno) {
case E_PARSE:
return 'E_PARSE';
case E_ERROR:
return 'E_ERROR';
case E_CORE_ERROR:
return 'E_CORE_ERROR';
case E_COMPILE_ERROR:
return 'E_COMPILE_ERROR';
case E_USER_ERROR:
return 'E_USER_ERROR';
case E_WARNING:
return 'E_WARNING';
case E_USER_WARNING:
return 'E_USER_WARNING';
case E_COMPILE_WARNING:
return 'E_COMPILE_WARNING';
case E_NOTICE:
return 'E_NOTICE';
case E_USER_NOTICE:
return 'E_USER_NOTICE';
case self::ERRNO_EXCEPTION:
return 'EXCEPTION';
default:
break;
}
if (defined('E_RECOVERABLE_ERROR') && $errno === E_RECOVERABLE_ERROR) {
return 'E_RECOVERABLE_ERROR';
}
if (defined('E_DEPRECATED') && $errno === E_DEPRECATED) {
return 'E_DEPRECATED';
}
if (defined('E_USER_DEPRECATED') && $errno === E_USER_DEPRECATED) {
return 'E_USER_DEPRECATED';
}
return 'E_UNKNOWN CODE: ' . $errno;
}
}

View File

@@ -0,0 +1,145 @@
<?php
/**
* plugin custom actions
*
* Standard: PSR-2
*
* @link http://www.php-fig.org/psr/psr-2 Full Documentation
*
* @package SC\DUPX\U
*/
namespace Duplicator\Installer\Utils\Tests\WP;
use Duplicator\Installer\Core\Security;
use Duplicator\Installer\Utils\Log\Log;
use Duplicator\Installer\Core\Params\PrmMng;
use Duplicator\Installer\Utils\Tests\TestInterface;
use DUPX_NOTICE_ITEM;
use DUPX_NOTICE_MANAGER;
class TestsExecuter implements TestInterface
{
const SCRIPT_NAME_HTTP_PARAM = 'dupli_test_script_name';
/**
* @return bool true on success
* @throws \Exception
*/
public static function preTestPrepare(): bool
{
$nManager = DUPX_NOTICE_MANAGER::getInstance();
$scriptFilePath = self::getScriptTestPath();
Log::info('PREPARE FILE BEFORE TEST: ' . $scriptFilePath, Log::LV_DETAILED);
if (file_put_contents($scriptFilePath, self::getExecFileContent()) === false) {
$nManager->addFinalReportNotice([
'shortMsg' => 'Can\'t create final text script file',
'longMsg' => 'Can\'t create file ' . $scriptFilePath,
'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_DEFAULT,
'level' => DUPX_NOTICE_ITEM::HARD_WARNING,
'sections' => ['general'],
]);
return false;
}
return true;
}
/**
* @return bool true on success
* @throws \Exception
*/
public static function afterTestClean(): bool
{
$nManager = DUPX_NOTICE_MANAGER::getInstance();
$scriptFilePath = self::getScriptTestPath();
Log::info('DELETE FILE AFTER TEST: ' . $scriptFilePath, Log::LV_DETAILED);
if (file_exists($scriptFilePath)) {
if (unlink($scriptFilePath) == false) {
$nManager->addFinalReportNotice([
'shortMsg' => 'Can\'t deleta final text script file',
'longMsg' => 'Can\'t delete file ' . $scriptFilePath . '. Remove it manually',
'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_DEFAULT,
'level' => DUPX_NOTICE_ITEM::HARD_WARNING,
'sections' => ['general'],
]);
}
}
return true;
}
/**
* @return string url of WP front-end
* @throws \Exception
*/
public static function getFrontendUrl(): string
{
$indexPath = PrmMng::getInstance()->getValue(PrmMng::PARAM_PATH_NEW) . '/index.php';
$data = [self::SCRIPT_NAME_HTTP_PARAM => $indexPath];
return self::getScriptTestUrl() . '?' . http_build_query($data);
}
/**
* @return string url of WP back-end
* @throws \Exception
*/
public static function getBackendUrl(): string
{
$indexPath = PrmMng::getInstance()->getValue(PrmMng::PARAM_PATH_WP_CORE_NEW) . '/wp-login.php';
$data = [self::SCRIPT_NAME_HTTP_PARAM => $indexPath];
return self::getScriptTestUrl() . '?' . http_build_query($data);
}
/**
* @return string test script name
*/
protected static function getScriptTestName(): string
{
return 'wp_test_script_' . Security::getInstance()->getSecondaryPackageHash() . '.php';
}
/**
* @return string test script path
* @throws \Exception
*/
public static function getScriptTestPath(): string
{
// use wp-content path and not root path
return PrmMng::getInstance()->getValue(PrmMng::PARAM_PATH_CONTENT_NEW) . '/' . self::getScriptTestName();
}
/**
* @return string test script url
* @throws \Exception
*/
public static function getScriptTestUrl(): string
{
// use wp-content path and not root path
return PrmMng::getInstance()->getValue(PrmMng::PARAM_URL_CONTENT_NEW) . '/' . self::getScriptTestName();
}
/**
* @return string contents to be added to the test script file
*/
public static function getExecFileContent(): string
{
$result = file_get_contents(__DIR__ . '/tests_template.php');
$result = preg_replace('/^.*\[REMOVE LINE BY SCRIPT].*\n/m', '', $result); // remove first line with die
return str_replace(
[
'$_$_NOTICES_FILE_PATH_$_$',
'$_$_DUPX_INIT_$_$',
],
[
$GLOBALS["NOTICES_FILE_PATH"],
DUPX_INIT,
],
$result
);
}
}

View File

@@ -0,0 +1,139 @@
<?php
namespace Duplicator\Installer\Utils\Tests\WP;
use Duplicator\Installer\Utils\Autoloader;
use Duplicator\Libs\Snap\SnapUtil;
use Duplicator\Installer\Utils\Tests\MessageCustomizer;
use DUPX_NOTICE_ITEM;
use DUPX_NOTICE_MANAGER;
use Exception;
// phpcs:disable
die(); // [REMOVE LINE BY SCRIPT] don't remove/change this *********************************
if (!defined('DUPXABSPATH')) { // @phpstan-ignore-line
define('DUPXABSPATH', __DIR__);
}
if (!defined('DUPX_INIT')) {
define('DUPX_INIT', '$_$_DUPX_INIT_$_$');
}
// phpcs:enable
require_once(DUPX_INIT . '/src/Utils/Autoloader.php');
Autoloader::register();
require_once(DUPX_INIT . '/classes/utilities/class.u.notices.manager.php');
$GLOBALS["NOTICES_FILE_PATH"] = '$_$_NOTICES_FILE_PATH_$_$';
$GLOBALS["TEST_SCRIPT"] = SnapUtil::sanitizeDefaultInput(INPUT_GET, 'dupli_test_script_name');
ob_start();
TestsErrorHandler::register();
TestsErrorHandler::setShutdownCallabck(function ($errors): void {
$nManager = DUPX_NOTICE_MANAGER::getInstance();
$scriptName = basename($GLOBALS["TEST_SCRIPT"]);
if (!file_exists($GLOBALS["TEST_SCRIPT"])) {
$longMessage = "- The file " . $GLOBALS["TEST_SCRIPT"] . " doesn't exist.\n";
$data = [
'shortMsg' => "Some files required for the final tests were not found.",
'level' => DUPX_NOTICE_ITEM::HARD_WARNING,
'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_PRE,
'longMsg' => $longMessage,
'sections' => 'general',
];
$nManager->addBothNextAndFinalReportNotice($data, DUPX_NOTICE_MANAGER::ADD_UNIQUE_APPEND, 'test_file_not_found');
if ($nManager->saveNotices()) {
echo json_encode(true);
} else {
echo json_encode(false);
}
return;
}
$scriptNameId = str_replace(['.', '-', '#'], '_', $scriptName);
$firstFatal = true;
$firstNotice = true;
switch ($scriptName) {
case 'index.php':
$shortMessageFatal = 'Fatal error on WordPress front-end tests!';
$shortMessageNotice = 'Warnings or notices on WordPress front-end tests!';
$fatalErrorLevel = DUPX_NOTICE_ITEM::CRITICAL;
break;
case 'wp-login.php':
$shortMessageFatal = 'Fatal error on WordPress login tests!';
$shortMessageNotice = 'Warnings or notices on WordPress backend tests!';
$fatalErrorLevel = DUPX_NOTICE_ITEM::FATAL;
break;
default:
$shortMessageFatal = 'Fatal error on php script ' . $scriptName;
$shortMessageNotice = 'Warnings or notices on php script ' . $scriptName;
$fatalErrorLevel = DUPX_NOTICE_ITEM::CRITICAL;
break;
}
foreach ($errors as $error) {
$addBeforeNotice = false;
switch ($error['error_cat']) {
case TestsErrorHandler::ERR_TYPE_ERROR:
$noticeId = 'wptest_fatal_error_' . $scriptNameId;
$errorLevel = $fatalErrorLevel;
$shortMessage = $shortMessageFatal;
if ($firstFatal) {
$addBeforeNotice = true;
$firstFatal = false;
}
break;
case TestsErrorHandler::ERR_TYPE_NOTICE:
case TestsErrorHandler::ERR_TYPE_DEPRECATED:
case TestsErrorHandler::ERR_TYPE_WARNING:
default:
$noticeId = 'wptest_notice_' . $scriptNameId;
$errorLevel = DUPX_NOTICE_ITEM::NOTICE;
$shortMessage = $shortMessageNotice;
if ($firstNotice) {
$addBeforeNotice = true;
$firstNotice = false;
}
break;
}
$longMessage = $addBeforeNotice ? 'SCRIPT FILE TEST: ' . $GLOBALS["TEST_SCRIPT"] . "\n\n" : '';
$longMessage .= TestsErrorHandler::errorToString($error) . "\n-----\n\n";
$longMessage .= "For solutions to these issues see the online FAQs \nhttps://duplicator.com/knowledge-base \n\n";
MessageCustomizer::applyAllNoticeCustomizations($shortMessage, $longMessage, $noticeId);
$data = [
'shortMsg' => $shortMessage,
'level' => $errorLevel,
'longMsgMode' => DUPX_NOTICE_ITEM::MSG_MODE_PRE,
'longMsg' => $longMessage,
'sections' => 'general',
];
if ($errorLevel == DUPX_NOTICE_ITEM::FATAL) {
$nManager->addBothNextAndFinalReportNotice($data, DUPX_NOTICE_MANAGER::ADD_UNIQUE_APPEND, $noticeId);
} else {
$nManager->addFinalReportNotice($data, DUPX_NOTICE_MANAGER::ADD_UNIQUE_APPEND, $noticeId);
}
}
if ($nManager->saveNotices()) {
echo json_encode(true);
} else {
echo json_encode(false);
}
});
$_SERVER['REQUEST_URI'] = '/';
if (file_exists($GLOBALS["TEST_SCRIPT"])) {
require_once($GLOBALS["TEST_SCRIPT"]);
} else {
throw new Exception('test script file ' . $GLOBALS["TEST_SCRIPT"] . ' doesn\'t exist');
}

View File

@@ -0,0 +1,27 @@
<?php
/**
* @package Duplicator\Installer
*/
namespace Duplicator\Installer\ViewHelpers;
use Duplicator\Installer\Core\Security;
use Duplicator\Installer\Core\InstState;
class Resources
{
/**
* Return assets base URL
*
* @return string
*/
public static function getAssetsBaseUrl()
{
if (InstState::isBridgeInstall()) {
return Security::getInstance()->getOriginalInstallerUrl();
} else {
return DUPX_INIT_URL;
}
}
}